linux/drivers/clk/qcom/apcs-sdx55.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Qualcomm SDX55 APCS clock controller driver
   4 *
   5 * Copyright (c) 2020, Linaro Limited
   6 * Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
   7 */
   8
   9#include <linux/clk.h>
  10#include <linux/clk-provider.h>
  11#include <linux/cpu.h>
  12#include <linux/kernel.h>
  13#include <linux/module.h>
  14#include <linux/platform_device.h>
  15#include <linux/pm_domain.h>
  16#include <linux/regmap.h>
  17#include <linux/slab.h>
  18
  19#include "clk-regmap.h"
  20#include "clk-regmap-mux-div.h"
  21
  22static const u32 apcs_mux_clk_parent_map[] = { 0, 1, 5 };
  23
  24static const struct clk_parent_data pdata[] = {
  25        { .fw_name = "ref" },
  26        { .fw_name = "aux" },
  27        { .fw_name = "pll" },
  28};
  29
  30/*
  31 * We use the notifier function for switching to a temporary safe configuration
  32 * (mux and divider), while the A7 PLL is reconfigured.
  33 */
  34static int a7cc_notifier_cb(struct notifier_block *nb, unsigned long event,
  35                            void *data)
  36{
  37        int ret = 0;
  38        struct clk_regmap_mux_div *md = container_of(nb,
  39                                                     struct clk_regmap_mux_div,
  40                                                     clk_nb);
  41        if (event == PRE_RATE_CHANGE)
  42                /* set the mux and divider to safe frequency (400mhz) */
  43                ret = mux_div_set_src_div(md, 1, 2);
  44
  45        return notifier_from_errno(ret);
  46}
  47
  48static int qcom_apcs_sdx55_clk_probe(struct platform_device *pdev)
  49{
  50        struct device *dev = &pdev->dev;
  51        struct device *parent = dev->parent;
  52        struct device *cpu_dev;
  53        struct clk_regmap_mux_div *a7cc;
  54        struct regmap *regmap;
  55        struct clk_init_data init = { };
  56        int ret;
  57
  58        regmap = dev_get_regmap(parent, NULL);
  59        if (!regmap) {
  60                dev_err(dev, "Failed to get parent regmap\n");
  61                return -ENODEV;
  62        }
  63
  64        a7cc = devm_kzalloc(dev, sizeof(*a7cc), GFP_KERNEL);
  65        if (!a7cc)
  66                return -ENOMEM;
  67
  68        init.name = "a7mux";
  69        init.parent_data = pdata;
  70        init.num_parents = ARRAY_SIZE(pdata);
  71        init.ops = &clk_regmap_mux_div_ops;
  72
  73        a7cc->clkr.hw.init = &init;
  74        a7cc->clkr.regmap = regmap;
  75        a7cc->reg_offset = 0x8;
  76        a7cc->hid_width = 5;
  77        a7cc->hid_shift = 0;
  78        a7cc->src_width = 3;
  79        a7cc->src_shift = 8;
  80        a7cc->parent_map = apcs_mux_clk_parent_map;
  81
  82        a7cc->pclk = devm_clk_get(parent, "pll");
  83        if (IS_ERR(a7cc->pclk))
  84                return dev_err_probe(dev, PTR_ERR(a7cc->pclk),
  85                                     "Failed to get PLL clk\n");
  86
  87        a7cc->clk_nb.notifier_call = a7cc_notifier_cb;
  88        ret = clk_notifier_register(a7cc->pclk, &a7cc->clk_nb);
  89        if (ret)
  90                return dev_err_probe(dev, ret,
  91                                     "Failed to register clock notifier\n");
  92
  93        ret = devm_clk_register_regmap(dev, &a7cc->clkr);
  94        if (ret) {
  95                dev_err_probe(dev, ret, "Failed to register regmap clock\n");
  96                goto err;
  97        }
  98
  99        ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
 100                                          &a7cc->clkr.hw);
 101        if (ret) {
 102                dev_err_probe(dev, ret, "Failed to add clock provider\n");
 103                goto err;
 104        }
 105
 106        platform_set_drvdata(pdev, a7cc);
 107
 108        /*
 109         * Attach the power domain to cpudev. Since there is no dedicated driver
 110         * for CPUs and the SDX55 platform lacks hardware specific CPUFreq
 111         * driver, there seems to be no better place to do this. So do it here!
 112         */
 113        cpu_dev = get_cpu_device(0);
 114        dev_pm_domain_attach(cpu_dev, true);
 115
 116        return 0;
 117
 118err:
 119        clk_notifier_unregister(a7cc->pclk, &a7cc->clk_nb);
 120        return ret;
 121}
 122
 123static int qcom_apcs_sdx55_clk_remove(struct platform_device *pdev)
 124{
 125        struct device *cpu_dev = get_cpu_device(0);
 126        struct clk_regmap_mux_div *a7cc = platform_get_drvdata(pdev);
 127
 128        clk_notifier_unregister(a7cc->pclk, &a7cc->clk_nb);
 129        dev_pm_domain_detach(cpu_dev, true);
 130
 131        return 0;
 132}
 133
 134static struct platform_driver qcom_apcs_sdx55_clk_driver = {
 135        .probe = qcom_apcs_sdx55_clk_probe,
 136        .remove = qcom_apcs_sdx55_clk_remove,
 137        .driver = {
 138                .name = "qcom-sdx55-acps-clk",
 139        },
 140};
 141module_platform_driver(qcom_apcs_sdx55_clk_driver);
 142
 143MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>");
 144MODULE_LICENSE("GPL v2");
 145MODULE_DESCRIPTION("Qualcomm SDX55 APCS clock driver");
 146