Quoting Loic Poulain (2020-03-26 05:00:06) > Each of the CPU clusters (Power and Perf) on msm8996 are > clocked via 2 PLLs, a primary and alternate. There are also > 2 Mux'es, a primary and secondary all connected together > as shown below > > +-------+ > XO | | > +------------------>0 | > | | > PLL/2 | SMUX +----+ > +------->1 | | > | | | | > | +-------+ | +-------+ > | +---->0 | > | | | > +---------------+ | +----------->1 | CPU clk > |Primary PLL +----+ PLL_EARLY | | +------> > | +------+-----------+ +------>2 PMUX | > +---------------+ | | | | > | +------+ | +-->3 | > +--^+ ACD +-----+ | +-------+ > +---------------+ +------+ | > |Alt PLL | | > | +---------------------------+ > +---------------+ PLL_EARLY > > The primary PLL is what drives the CPU clk, except for times > when we are reprogramming the PLL itself (for rate changes) when > we temporarily switch to an alternate PLL. A subsequent patch adds > support to switch between primary and alternate PLL during rate > changes. > > The primary PLL operates on a single VCO range, between 600MHz > and 3GHz. However the CPUs do support OPPs with frequencies > between 300MHz and 600MHz. In order to support running the CPUs > at those frequencies we end up having to lock the PLL at twice > the rate and drive the CPU clk via the PLL/2 output and SMUX. > > So for frequencies above 600MHz we follow the following path > Primary PLL --> PLL_EARLY --> PMUX(1) --> CPU clk > and for frequencies between 300MHz and 600MHz we follow > Primary PLL --> PLL/2 --> SMUX(1) --> PMUX(0) --> CPU clk > Support for this is added in a subsequent patch as well. > > ACD stands for Adaptive Clock Distribution and is used to > detect voltage droops. > > Signed-off-by: Rajendra Nayak <rnayak@xxxxxxxxxxxxxx> > Signed-off-by: Ilia Lin <ilialin@xxxxxxxxxxxxxx> Some Co-developed-by tags here? > Signed-off-by: Loic Poulain <loic.poulain@xxxxxxxxxx> > --- > drivers/clk/qcom/Kconfig | 10 + > drivers/clk/qcom/Makefile | 1 + > drivers/clk/qcom/clk-alpha-pll.h | 6 + > drivers/clk/qcom/clk-cpu-8996.c | 547 +++++++++++++++++++++++++++++++++++++++ > 4 files changed, 564 insertions(+) > create mode 100644 drivers/clk/qcom/clk-cpu-8996.c > > diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig > index 15cdcdc..48934b2 100644 > --- a/drivers/clk/qcom/Kconfig > +++ b/drivers/clk/qcom/Kconfig > @@ -37,6 +37,16 @@ config QCOM_CLK_APCS_MSM8916 > Say Y if you want to support CPU frequency scaling on devices > such as msm8916. > > +config QCOM_CLK_APCC_MSM8996 > + tristate "MSM8996 CPU Clock Controller" > + depends on ARM64 > + depends on COMMON_CLK_QCOM This should be dropped if this is inside the if in this Kconfig file. > + select QCOM_KRYO_L2_ACCESSORS > + help > + Support for the CPU clock controller on msm8996 devices. > + Say Y if you want to support CPU clock scaling using CPUfreq > + drivers for dyanmic power management. > + > config QCOM_CLK_RPM > tristate "RPM based Clock Controller" > depends on MFD_QCOM_RPM > diff --git a/drivers/clk/qcom/clk-cpu-8996.c b/drivers/clk/qcom/clk-cpu-8996.c > new file mode 100644 > index 0000000..a977d5a > --- /dev/null > +++ b/drivers/clk/qcom/clk-cpu-8996.c > @@ -0,0 +1,547 @@ > +// SPDX-License-Identifier: GPL-2.0 [...] > + > +#include <linux/clk.h> > +#include <linux/clk-provider.h> > +#include <linux/io.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/regmap.h> > +#include <soc/qcom/kryo-l2-accessors.h> > + > +#include "clk-alpha-pll.h" > +#include "clk-regmap.h" > + [...] > + > +static int clk_cpu_8996_mux_determine_rate(struct clk_hw *hw, > + struct clk_rate_request *req) > +{ > + struct clk_cpu_8996_mux *cpuclk = to_clk_cpu_8996_mux_hw(hw); > + struct clk_hw *parent = cpuclk->pll; > + > + if (cpuclk->pll_div_2 && req->rate < DIV_2_THRESHOLD) { > + if (req->rate < (DIV_2_THRESHOLD / 2)) > + return -EINVAL; > + > + parent = cpuclk->pll_div_2; > + } > + > + req->best_parent_rate = clk_hw_round_rate(parent, req->rate); > + req->best_parent_hw = parent; > + > + return 0; > +} > + [...] > + > +static struct clk_cpu_8996_mux pwrcl_pmux = { > + .reg = PWRCL_REG_OFFSET + MUX_OFFSET, > + .shift = 0, > + .width = 2, > + .pll = &pwrcl_pll.clkr.hw, > + .pll_div_2 = &pwrcl_smux.clkr.hw, > + .nb.notifier_call = cpu_clk_notifier_cb, > + .clkr.hw.init = &(struct clk_init_data) { > + .name = "pwrcl_pmux", > + .parent_names = (const char *[]){ > + "pwrcl_smux", > + "pwrcl_pll", > + "pwrcl_pll_acd", > + "pwrcl_alt_pll", > + }, I suppose use clk_hw pointers directly. > + > +static struct clk_cpu_8996_mux perfcl_pmux = { > + .reg = PERFCL_REG_OFFSET + MUX_OFFSET, > + .shift = 0, > + .width = 2, > + .pll = &perfcl_pll.clkr.hw, > + .pll_div_2 = &perfcl_smux.clkr.hw, > + .nb.notifier_call = cpu_clk_notifier_cb, > + .clkr.hw.init = &(struct clk_init_data) { > + .name = "perfcl_pmux", > + .parent_names = (const char *[]){ > + "perfcl_smux", > + "perfcl_pll", > + "perfcl_pll_acd", > + "perfcl_alt_pll", > + }, > + .num_parents = 4, > + .ops = &clk_cpu_8996_mux_ops, > + /* do not gate if unclaimed */ > + .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, Why CLK_IGNORE_UNUSED? Should it be CLK_IS_CRITICAL? The comment says what's going on but doesn't tell us _why_ which is the important part. > + }, > +}; > + > +static const struct regmap_config cpu_msm8996_regmap_config = { > + .reg_bits = 32, > + .reg_stride = 4, > + .val_bits = 32, > + .max_register = 0x80210, > + .fast_io = true, > + .val_format_endian = REGMAP_ENDIAN_LITTLE, > +}; > + > +struct clk_regmap *cpu_msm8996_clks[] = { > + &perfcl_pll.clkr, > + &pwrcl_pll.clkr, > + &perfcl_alt_pll.clkr, > + &pwrcl_alt_pll.clkr, > + &perfcl_smux.clkr, > + &pwrcl_smux.clkr, > + &perfcl_pmux.clkr, > + &pwrcl_pmux.clkr, > +}; > + > +static int qcom_cpu_clk_msm8996_register_clks(struct device *dev, > + struct regmap *regmap) > +{ > + int i, ret; > + > + perfcl_smux.pll = clk_hw_register_fixed_factor(dev, "perfcl_pll_main", > + "perfcl_pll", > + CLK_SET_RATE_PARENT, > + 1, 2); > + if (IS_ERR(perfcl_smux.pll)) { > + dev_err(dev, "Failed to initialize perfcl_pll_main\n"); > + return PTR_ERR(perfcl_smux.pll); > + } > + > + pwrcl_smux.pll = clk_hw_register_fixed_factor(dev, "pwrcl_pll_main", > + "pwrcl_pll", > + CLK_SET_RATE_PARENT, > + 1, 2); > + if (IS_ERR(pwrcl_smux.pll)) { > + dev_err(dev, "Failed to initialize pwrcl_pll_main\n"); > + clk_hw_unregister(perfcl_smux.pll); > + return PTR_ERR(pwrcl_smux.pll); > + } > + > + for (i = 0; i < ARRAY_SIZE(cpu_msm8996_clks); i++) { > + ret = devm_clk_register_regmap(dev, cpu_msm8996_clks[i]); > + if (ret) { > + clk_hw_unregister(perfcl_smux.pll); > + clk_hw_unregister(pwrcl_smux.pll); > + return ret; > + } > + } > + > + clk_alpha_pll_configure(&perfcl_pll, regmap, &hfpll_config); > + clk_alpha_pll_configure(&pwrcl_pll, regmap, &hfpll_config); > + clk_alpha_pll_configure(&perfcl_alt_pll, regmap, &altpll_config); > + clk_alpha_pll_configure(&pwrcl_alt_pll, regmap, &altpll_config); > + > + /* Enable alt PLLs */ > + clk_prepare_enable(pwrcl_alt_pll.clkr.hw.clk); > + clk_prepare_enable(perfcl_alt_pll.clkr.hw.clk); > + > + clk_notifier_register(pwrcl_pmux.clkr.hw.clk, &pwrcl_pmux.nb); > + clk_notifier_register(perfcl_pmux.clkr.hw.clk, &perfcl_pmux.nb); > + > + return ret; > +} > + > +static int qcom_cpu_clk_msm8996_unregister_clks(void) > +{ > + int ret = 0; > + > + ret = clk_notifier_unregister(pwrcl_pmux.clkr.hw.clk, &pwrcl_pmux.nb); > + if (ret) > + return ret; > + > + ret = clk_notifier_unregister(perfcl_pmux.clkr.hw.clk, &perfcl_pmux.nb); > + if (ret) > + return ret; > + > + clk_hw_unregister(perfcl_smux.pll); > + clk_hw_unregister(pwrcl_smux.pll); > + > + return 0; > +} > + > +#define CPU_AFINITY_MASK 0xFFF > +#define PWRCL_CPU_REG_MASK 0x3 > +#define PERFCL_CPU_REG_MASK 0x103 > + > +#define L2ACDCR_REG 0x580ULL > +#define L2ACDTD_REG 0x581ULL > +#define L2ACDDVMRC_REG 0x584ULL > +#define L2ACDSSCR_REG 0x589ULL > + > +static DEFINE_SPINLOCK(acd_lock); qcom_clk_acd_lock? acd_lock is pretty generic. > +static void __iomem *base; > + > +static void qcom_cpu_clk_msm8996_acd_init(void __iomem *base) > +{ > + u64 hwid; > + unsigned long flags; > + > + spin_lock_irqsave(&acd_lock, flags); > + > + hwid = read_cpuid_mpidr() & CPU_AFINITY_MASK; > + > + kryo_l2_set_indirect_reg(L2ACDTD_REG, 0x00006A11); > + kryo_l2_set_indirect_reg(L2ACDDVMRC_REG, 0x000E0F0F); > + kryo_l2_set_indirect_reg(L2ACDSSCR_REG, 0x00000601); > + > + if (PWRCL_CPU_REG_MASK == (hwid | PWRCL_CPU_REG_MASK)) { > + writel(0xF, base + PWRCL_REG_OFFSET + SSSCTL_OFFSET); Lowercase hex please. > + wmb(); Can we have a comment in front of these wmb()s here? I think they're needed to make sure the writel doesn't get executed out of oorder with the kryo_l2_set_indirect_reg() but I'm not positive. > + kryo_l2_set_indirect_reg(L2ACDCR_REG, 0x002C5FFD); > + } > + > + if (PERFCL_CPU_REG_MASK == (hwid | PERFCL_CPU_REG_MASK)) { > + kryo_l2_set_indirect_reg(L2ACDCR_REG, 0x002C5FFD); > + writel(0xF, base + PERFCL_REG_OFFSET + SSSCTL_OFFSET); > + wmb(); And this one is barriered why? > + } > + > + spin_unlock_irqrestore(&acd_lock, flags); > +} > + > +static int cpu_clk_notifier_cb(struct notifier_block *nb, unsigned long event, > + void *data) > +{ > + struct clk_cpu_8996_mux *cpuclk = to_clk_cpu_8996_mux_nb(nb); > + struct clk_notifier_data *cnd = data; > + int ret; > + > + switch (event) { > + case PRE_RATE_CHANGE: > + ret = clk_cpu_8996_mux_set_parent(&cpuclk->clkr.hw, ALT_INDEX); > + qcom_cpu_clk_msm8996_acd_init(base); > + break; > + case POST_RATE_CHANGE: > + if (cnd->new_rate < DIV_2_THRESHOLD) > + ret = clk_cpu_8996_mux_set_parent(&cpuclk->clkr.hw, > + DIV_2_INDEX); > + else > + ret = clk_cpu_8996_mux_set_parent(&cpuclk->clkr.hw, > + ACD_INDEX); > + break; > + default: > + ret = 0; > + break; > + } > + > + return notifier_from_errno(ret); > +}; > + > +static int qcom_cpu_clk_msm8996_driver_probe(struct platform_device *pdev) > +{ > + int ret; > + struct resource *res; > + struct regmap *regmap; > + struct clk_hw_onecell_data *data; > + struct device *dev = &pdev->dev; > + > + data = devm_kzalloc(dev, sizeof(*data) + 2 * sizeof(struct clk_hw *), Is this struct_size() or some variant of it? > + GFP_KERNEL); > + if (!data) > + return -ENOMEM; > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + base = devm_ioremap_resource(dev, res); Use devm_platform_ioremap_resource()? > + if (IS_ERR(base)) > + return PTR_ERR(base); > + > + regmap = devm_regmap_init_mmio(dev, base, &cpu_msm8996_regmap_config); > + if (IS_ERR(regmap)) > + return PTR_ERR(regmap); > + > + ret = qcom_cpu_clk_msm8996_register_clks(dev, regmap); > + if (ret) > + return ret; > + > + qcom_cpu_clk_msm8996_acd_init(base); > + > + data->hws[0] = &pwrcl_pmux.clkr.hw; > + data->hws[1] = &perfcl_pmux.clkr.hw; > + data->num = 2; > + > + return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, data); > +} > + > +static int qcom_cpu_clk_msm8996_driver_remove(struct platform_device *pdev) > +{ > + return qcom_cpu_clk_msm8996_unregister_clks(); > +} > + > +static struct platform_driver qcom_cpu_clk_msm8996_driver = { > + .probe = qcom_cpu_clk_msm8996_driver_probe, > + .remove = qcom_cpu_clk_msm8996_driver_remove, > + .driver = { > + .name = "qcom-msm8996-apcc", > + .of_match_table = qcom_cpu_clk_msm8996_match_table, > + }, > +}; > +module_platform_driver(qcom_cpu_clk_msm8996_driver); > + > +MODULE_ALIAS("platform:msm8996-apcc"); Is this alias needed? > +MODULE_DESCRIPTION("QCOM MSM8996 CPU Clock Driver"); > +MODULE_LICENSE("GPL v2");