Re: [PATCH 2/4] clk: qcom: Add CPU clock driver for msm8996

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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");




[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [Linux for Sparc]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux