Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Qualcomm SDX55 APCS clock controller driver
0004  *
0005  * Copyright (c) 2020, Linaro Limited
0006  * Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
0007  */
0008 
0009 #include <linux/clk.h>
0010 #include <linux/clk-provider.h>
0011 #include <linux/cpu.h>
0012 #include <linux/kernel.h>
0013 #include <linux/module.h>
0014 #include <linux/platform_device.h>
0015 #include <linux/pm_domain.h>
0016 #include <linux/regmap.h>
0017 #include <linux/slab.h>
0018 
0019 #include "clk-regmap.h"
0020 #include "clk-regmap-mux-div.h"
0021 
0022 static const u32 apcs_mux_clk_parent_map[] = { 0, 1, 5 };
0023 
0024 static const struct clk_parent_data pdata[] = {
0025     { .fw_name = "ref" },
0026     { .fw_name = "aux" },
0027     { .fw_name = "pll" },
0028 };
0029 
0030 /*
0031  * We use the notifier function for switching to a temporary safe configuration
0032  * (mux and divider), while the A7 PLL is reconfigured.
0033  */
0034 static int a7cc_notifier_cb(struct notifier_block *nb, unsigned long event,
0035                 void *data)
0036 {
0037     int ret = 0;
0038     struct clk_regmap_mux_div *md = container_of(nb,
0039                              struct clk_regmap_mux_div,
0040                              clk_nb);
0041     if (event == PRE_RATE_CHANGE)
0042         /* set the mux and divider to safe frequency (400mhz) */
0043         ret = mux_div_set_src_div(md, 1, 2);
0044 
0045     return notifier_from_errno(ret);
0046 }
0047 
0048 static int qcom_apcs_sdx55_clk_probe(struct platform_device *pdev)
0049 {
0050     struct device *dev = &pdev->dev;
0051     struct device *parent = dev->parent;
0052     struct device *cpu_dev;
0053     struct clk_regmap_mux_div *a7cc;
0054     struct regmap *regmap;
0055     struct clk_init_data init = { };
0056     int ret;
0057 
0058     regmap = dev_get_regmap(parent, NULL);
0059     if (!regmap) {
0060         dev_err(dev, "Failed to get parent regmap\n");
0061         return -ENODEV;
0062     }
0063 
0064     a7cc = devm_kzalloc(dev, sizeof(*a7cc), GFP_KERNEL);
0065     if (!a7cc)
0066         return -ENOMEM;
0067 
0068     init.name = "a7mux";
0069     init.parent_data = pdata;
0070     init.num_parents = ARRAY_SIZE(pdata);
0071     init.ops = &clk_regmap_mux_div_ops;
0072 
0073     a7cc->clkr.hw.init = &init;
0074     a7cc->clkr.regmap = regmap;
0075     a7cc->reg_offset = 0x8;
0076     a7cc->hid_width = 5;
0077     a7cc->hid_shift = 0;
0078     a7cc->src_width = 3;
0079     a7cc->src_shift = 8;
0080     a7cc->parent_map = apcs_mux_clk_parent_map;
0081 
0082     a7cc->pclk = devm_clk_get(parent, "pll");
0083     if (IS_ERR(a7cc->pclk))
0084         return dev_err_probe(dev, PTR_ERR(a7cc->pclk),
0085                      "Failed to get PLL clk\n");
0086 
0087     a7cc->clk_nb.notifier_call = a7cc_notifier_cb;
0088     ret = clk_notifier_register(a7cc->pclk, &a7cc->clk_nb);
0089     if (ret)
0090         return dev_err_probe(dev, ret,
0091                      "Failed to register clock notifier\n");
0092 
0093     ret = devm_clk_register_regmap(dev, &a7cc->clkr);
0094     if (ret) {
0095         dev_err_probe(dev, ret, "Failed to register regmap clock\n");
0096         goto err;
0097     }
0098 
0099     ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
0100                       &a7cc->clkr.hw);
0101     if (ret) {
0102         dev_err_probe(dev, ret, "Failed to add clock provider\n");
0103         goto err;
0104     }
0105 
0106     platform_set_drvdata(pdev, a7cc);
0107 
0108     /*
0109      * Attach the power domain to cpudev. Since there is no dedicated driver
0110      * for CPUs and the SDX55 platform lacks hardware specific CPUFreq
0111      * driver, there seems to be no better place to do this. So do it here!
0112      */
0113     cpu_dev = get_cpu_device(0);
0114     dev_pm_domain_attach(cpu_dev, true);
0115 
0116     return 0;
0117 
0118 err:
0119     clk_notifier_unregister(a7cc->pclk, &a7cc->clk_nb);
0120     return ret;
0121 }
0122 
0123 static int qcom_apcs_sdx55_clk_remove(struct platform_device *pdev)
0124 {
0125     struct device *cpu_dev = get_cpu_device(0);
0126     struct clk_regmap_mux_div *a7cc = platform_get_drvdata(pdev);
0127 
0128     clk_notifier_unregister(a7cc->pclk, &a7cc->clk_nb);
0129     dev_pm_domain_detach(cpu_dev, true);
0130 
0131     return 0;
0132 }
0133 
0134 static struct platform_driver qcom_apcs_sdx55_clk_driver = {
0135     .probe = qcom_apcs_sdx55_clk_probe,
0136     .remove = qcom_apcs_sdx55_clk_remove,
0137     .driver = {
0138         .name = "qcom-sdx55-acps-clk",
0139     },
0140 };
0141 module_platform_driver(qcom_apcs_sdx55_clk_driver);
0142 
0143 MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>");
0144 MODULE_LICENSE("GPL v2");
0145 MODULE_DESCRIPTION("Qualcomm SDX55 APCS clock driver");