1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (c) 2020, The Linux Foundation. All rights reserved.
7 * Each of the CPU clusters (Power and Perf) on msm8996 are
8 * clocked via 2 PLLs, a primary and alternate. There are also
9 * 2 Mux'es, a primary and secondary all connected together
14 * +------------------>0 |
19 * | +-------+ | +-------+
22 * +---------------+ | +----------->1 | CPU clk
23 * |Primary PLL +----+ PLL_EARLY | | +------>
24 * | +------+-----------+ +------>2 PMUX |
25 * +---------------+ | | | |
26 * | +------+ | +-->3 |
27 * +--^+ ACD +-----+ | +-------+
28 * +---------------+ +------+ |
30 * | +---------------------------+
31 * +---------------+ PLL_EARLY
33 * The primary PLL is what drives the CPU clk, except for times
34 * when we are reprogramming the PLL itself (for rate changes) when
35 * we temporarily switch to an alternate PLL.
37 * The primary PLL operates on a single VCO range, between 600MHz
38 * and 3GHz. However the CPUs do support OPPs with frequencies
39 * between 300MHz and 600MHz. In order to support running the CPUs
40 * at those frequencies we end up having to lock the PLL at twice
41 * the rate and drive the CPU clk via the PLL/2 output and SMUX.
43 * So for frequencies above 600MHz we follow the following path
44 * Primary PLL --> PLL_EARLY --> PMUX(1) --> CPU clk
45 * and for frequencies between 300MHz and 600MHz we follow
46 * Primary PLL --> PLL/2 --> SMUX(1) --> PMUX(0) --> CPU clk
48 * ACD stands for Adaptive Clock Distribution and is used to
49 * detect voltage droops.
52 #include <linux/clk.h>
53 #include <linux/clk-provider.h>
55 #include <linux/module.h>
56 #include <linux/platform_device.h>
57 #include <linux/regmap.h>
58 #include <soc/qcom/kryo-l2-accessors.h>
60 #include "clk-alpha-pll.h"
61 #include "clk-regmap.h"
71 #define DIV_2_THRESHOLD 600000000
72 #define PWRCL_REG_OFFSET 0x0
73 #define PERFCL_REG_OFFSET 0x80000
74 #define MUX_OFFSET 0x40
75 #define ALT_PLL_OFFSET 0x100
76 #define SSSCTL_OFFSET 0x160
78 static const u8 prim_pll_regs
[PLL_OFF_MAX_REGS
] = {
79 [PLL_OFF_L_VAL
] = 0x04,
80 [PLL_OFF_ALPHA_VAL
] = 0x08,
81 [PLL_OFF_USER_CTL
] = 0x10,
82 [PLL_OFF_CONFIG_CTL
] = 0x18,
83 [PLL_OFF_CONFIG_CTL_U
] = 0x1c,
84 [PLL_OFF_TEST_CTL
] = 0x20,
85 [PLL_OFF_TEST_CTL_U
] = 0x24,
86 [PLL_OFF_STATUS
] = 0x28,
89 static const u8 alt_pll_regs
[PLL_OFF_MAX_REGS
] = {
90 [PLL_OFF_L_VAL
] = 0x04,
91 [PLL_OFF_ALPHA_VAL
] = 0x08,
92 [PLL_OFF_ALPHA_VAL_U
] = 0x0c,
93 [PLL_OFF_USER_CTL
] = 0x10,
94 [PLL_OFF_USER_CTL_U
] = 0x14,
95 [PLL_OFF_CONFIG_CTL
] = 0x18,
96 [PLL_OFF_TEST_CTL
] = 0x20,
97 [PLL_OFF_TEST_CTL_U
] = 0x24,
98 [PLL_OFF_STATUS
] = 0x28,
103 static const struct alpha_pll_config hfpll_config
= {
105 .config_ctl_val
= 0x200d4aa8,
106 .config_ctl_hi_val
= 0x006,
107 .pre_div_mask
= BIT(12),
108 .post_div_mask
= 0x3 << 8,
109 .post_div_val
= 0x1 << 8,
110 .main_output_mask
= BIT(0),
111 .early_output_mask
= BIT(3),
114 static struct clk_alpha_pll perfcl_pll
= {
115 .offset
= PERFCL_REG_OFFSET
,
116 .regs
= prim_pll_regs
,
117 .flags
= SUPPORTS_DYNAMIC_UPDATE
| SUPPORTS_FSM_MODE
,
118 .clkr
.hw
.init
= &(struct clk_init_data
){
119 .name
= "perfcl_pll",
120 .parent_names
= (const char *[]){ "xo" },
122 .ops
= &clk_alpha_pll_huayra_ops
,
126 static struct clk_alpha_pll pwrcl_pll
= {
127 .offset
= PWRCL_REG_OFFSET
,
128 .regs
= prim_pll_regs
,
129 .flags
= SUPPORTS_DYNAMIC_UPDATE
| SUPPORTS_FSM_MODE
,
130 .clkr
.hw
.init
= &(struct clk_init_data
){
132 .parent_names
= (const char *[]){ "xo" },
134 .ops
= &clk_alpha_pll_huayra_ops
,
138 static const struct pll_vco alt_pll_vco_modes
[] = {
139 VCO(3, 250000000, 500000000),
140 VCO(2, 500000000, 750000000),
141 VCO(1, 750000000, 1000000000),
142 VCO(0, 1000000000, 2150400000),
145 static const struct alpha_pll_config altpll_config
= {
147 .vco_val
= 0x3 << 20,
148 .vco_mask
= 0x3 << 20,
149 .config_ctl_val
= 0x4001051b,
150 .post_div_mask
= 0x3 << 8,
151 .post_div_val
= 0x1 << 8,
152 .main_output_mask
= BIT(0),
153 .early_output_mask
= BIT(3),
156 static struct clk_alpha_pll perfcl_alt_pll
= {
157 .offset
= PERFCL_REG_OFFSET
+ ALT_PLL_OFFSET
,
158 .regs
= alt_pll_regs
,
159 .vco_table
= alt_pll_vco_modes
,
160 .num_vco
= ARRAY_SIZE(alt_pll_vco_modes
),
161 .flags
= SUPPORTS_OFFLINE_REQ
| SUPPORTS_FSM_MODE
,
162 .clkr
.hw
.init
= &(struct clk_init_data
) {
163 .name
= "perfcl_alt_pll",
164 .parent_names
= (const char *[]){ "xo" },
166 .ops
= &clk_alpha_pll_hwfsm_ops
,
170 static struct clk_alpha_pll pwrcl_alt_pll
= {
171 .offset
= PWRCL_REG_OFFSET
+ ALT_PLL_OFFSET
,
172 .regs
= alt_pll_regs
,
173 .vco_table
= alt_pll_vco_modes
,
174 .num_vco
= ARRAY_SIZE(alt_pll_vco_modes
),
175 .flags
= SUPPORTS_OFFLINE_REQ
| SUPPORTS_FSM_MODE
,
176 .clkr
.hw
.init
= &(struct clk_init_data
) {
177 .name
= "pwrcl_alt_pll",
178 .parent_names
= (const char *[]){ "xo" },
180 .ops
= &clk_alpha_pll_hwfsm_ops
,
184 struct clk_cpu_8996_mux
{
188 struct notifier_block nb
;
190 struct clk_hw
*pll_div_2
;
191 struct clk_regmap clkr
;
194 static int cpu_clk_notifier_cb(struct notifier_block
*nb
, unsigned long event
,
197 #define to_clk_cpu_8996_mux_nb(_nb) \
198 container_of(_nb, struct clk_cpu_8996_mux, nb)
200 static inline struct clk_cpu_8996_mux
*to_clk_cpu_8996_mux_hw(struct clk_hw
*hw
)
202 return container_of(to_clk_regmap(hw
), struct clk_cpu_8996_mux
, clkr
);
205 static u8
clk_cpu_8996_mux_get_parent(struct clk_hw
*hw
)
207 struct clk_regmap
*clkr
= to_clk_regmap(hw
);
208 struct clk_cpu_8996_mux
*cpuclk
= to_clk_cpu_8996_mux_hw(hw
);
209 u32 mask
= GENMASK(cpuclk
->width
- 1, 0);
212 regmap_read(clkr
->regmap
, cpuclk
->reg
, &val
);
213 val
>>= cpuclk
->shift
;
218 static int clk_cpu_8996_mux_set_parent(struct clk_hw
*hw
, u8 index
)
220 struct clk_regmap
*clkr
= to_clk_regmap(hw
);
221 struct clk_cpu_8996_mux
*cpuclk
= to_clk_cpu_8996_mux_hw(hw
);
222 u32 mask
= GENMASK(cpuclk
->width
+ cpuclk
->shift
- 1, cpuclk
->shift
);
226 val
<<= cpuclk
->shift
;
228 return regmap_update_bits(clkr
->regmap
, cpuclk
->reg
, mask
, val
);
231 static int clk_cpu_8996_mux_determine_rate(struct clk_hw
*hw
,
232 struct clk_rate_request
*req
)
234 struct clk_cpu_8996_mux
*cpuclk
= to_clk_cpu_8996_mux_hw(hw
);
235 struct clk_hw
*parent
= cpuclk
->pll
;
237 if (cpuclk
->pll_div_2
&& req
->rate
< DIV_2_THRESHOLD
) {
238 if (req
->rate
< (DIV_2_THRESHOLD
/ 2))
241 parent
= cpuclk
->pll_div_2
;
244 req
->best_parent_rate
= clk_hw_round_rate(parent
, req
->rate
);
245 req
->best_parent_hw
= parent
;
250 static const struct clk_ops clk_cpu_8996_mux_ops
= {
251 .set_parent
= clk_cpu_8996_mux_set_parent
,
252 .get_parent
= clk_cpu_8996_mux_get_parent
,
253 .determine_rate
= clk_cpu_8996_mux_determine_rate
,
256 static struct clk_cpu_8996_mux pwrcl_smux
= {
257 .reg
= PWRCL_REG_OFFSET
+ MUX_OFFSET
,
260 .clkr
.hw
.init
= &(struct clk_init_data
) {
261 .name
= "pwrcl_smux",
262 .parent_names
= (const char *[]){
267 .ops
= &clk_cpu_8996_mux_ops
,
268 .flags
= CLK_SET_RATE_PARENT
,
272 static struct clk_cpu_8996_mux perfcl_smux
= {
273 .reg
= PERFCL_REG_OFFSET
+ MUX_OFFSET
,
276 .clkr
.hw
.init
= &(struct clk_init_data
) {
277 .name
= "perfcl_smux",
278 .parent_names
= (const char *[]){
283 .ops
= &clk_cpu_8996_mux_ops
,
284 .flags
= CLK_SET_RATE_PARENT
,
288 static struct clk_cpu_8996_mux pwrcl_pmux
= {
289 .reg
= PWRCL_REG_OFFSET
+ MUX_OFFSET
,
292 .pll
= &pwrcl_pll
.clkr
.hw
,
293 .pll_div_2
= &pwrcl_smux
.clkr
.hw
,
294 .nb
.notifier_call
= cpu_clk_notifier_cb
,
295 .clkr
.hw
.init
= &(struct clk_init_data
) {
296 .name
= "pwrcl_pmux",
297 .parent_names
= (const char *[]){
304 .ops
= &clk_cpu_8996_mux_ops
,
305 /* CPU clock is critical and should never be gated */
306 .flags
= CLK_SET_RATE_PARENT
| CLK_IS_CRITICAL
,
310 static struct clk_cpu_8996_mux perfcl_pmux
= {
311 .reg
= PERFCL_REG_OFFSET
+ MUX_OFFSET
,
314 .pll
= &perfcl_pll
.clkr
.hw
,
315 .pll_div_2
= &perfcl_smux
.clkr
.hw
,
316 .nb
.notifier_call
= cpu_clk_notifier_cb
,
317 .clkr
.hw
.init
= &(struct clk_init_data
) {
318 .name
= "perfcl_pmux",
319 .parent_names
= (const char *[]){
326 .ops
= &clk_cpu_8996_mux_ops
,
327 /* CPU clock is critical and should never be gated */
328 .flags
= CLK_SET_RATE_PARENT
| CLK_IS_CRITICAL
,
332 static const struct regmap_config cpu_msm8996_regmap_config
= {
336 .max_register
= 0x80210,
338 .val_format_endian
= REGMAP_ENDIAN_LITTLE
,
341 static struct clk_regmap
*cpu_msm8996_clks
[] = {
344 &perfcl_alt_pll
.clkr
,
352 static int qcom_cpu_clk_msm8996_register_clks(struct device
*dev
,
353 struct regmap
*regmap
)
357 perfcl_smux
.pll
= clk_hw_register_fixed_factor(dev
, "perfcl_pll_main",
361 if (IS_ERR(perfcl_smux
.pll
)) {
362 dev_err(dev
, "Failed to initialize perfcl_pll_main\n");
363 return PTR_ERR(perfcl_smux
.pll
);
366 pwrcl_smux
.pll
= clk_hw_register_fixed_factor(dev
, "pwrcl_pll_main",
370 if (IS_ERR(pwrcl_smux
.pll
)) {
371 dev_err(dev
, "Failed to initialize pwrcl_pll_main\n");
372 clk_hw_unregister(perfcl_smux
.pll
);
373 return PTR_ERR(pwrcl_smux
.pll
);
376 for (i
= 0; i
< ARRAY_SIZE(cpu_msm8996_clks
); i
++) {
377 ret
= devm_clk_register_regmap(dev
, cpu_msm8996_clks
[i
]);
379 clk_hw_unregister(perfcl_smux
.pll
);
380 clk_hw_unregister(pwrcl_smux
.pll
);
385 clk_alpha_pll_configure(&perfcl_pll
, regmap
, &hfpll_config
);
386 clk_alpha_pll_configure(&pwrcl_pll
, regmap
, &hfpll_config
);
387 clk_alpha_pll_configure(&perfcl_alt_pll
, regmap
, &altpll_config
);
388 clk_alpha_pll_configure(&pwrcl_alt_pll
, regmap
, &altpll_config
);
390 /* Enable alt PLLs */
391 clk_prepare_enable(pwrcl_alt_pll
.clkr
.hw
.clk
);
392 clk_prepare_enable(perfcl_alt_pll
.clkr
.hw
.clk
);
394 clk_notifier_register(pwrcl_pmux
.clkr
.hw
.clk
, &pwrcl_pmux
.nb
);
395 clk_notifier_register(perfcl_pmux
.clkr
.hw
.clk
, &perfcl_pmux
.nb
);
400 static int qcom_cpu_clk_msm8996_unregister_clks(void)
404 ret
= clk_notifier_unregister(pwrcl_pmux
.clkr
.hw
.clk
, &pwrcl_pmux
.nb
);
408 ret
= clk_notifier_unregister(perfcl_pmux
.clkr
.hw
.clk
, &perfcl_pmux
.nb
);
412 clk_hw_unregister(perfcl_smux
.pll
);
413 clk_hw_unregister(pwrcl_smux
.pll
);
418 #define CPU_AFINITY_MASK 0xFFF
419 #define PWRCL_CPU_REG_MASK 0x3
420 #define PERFCL_CPU_REG_MASK 0x103
422 #define L2ACDCR_REG 0x580ULL
423 #define L2ACDTD_REG 0x581ULL
424 #define L2ACDDVMRC_REG 0x584ULL
425 #define L2ACDSSCR_REG 0x589ULL
427 static DEFINE_SPINLOCK(qcom_clk_acd_lock
);
428 static void __iomem
*base
;
430 static void qcom_cpu_clk_msm8996_acd_init(void __iomem
*base
)
435 spin_lock_irqsave(&qcom_clk_acd_lock
, flags
);
437 hwid
= read_cpuid_mpidr() & CPU_AFINITY_MASK
;
439 kryo_l2_set_indirect_reg(L2ACDTD_REG
, 0x00006a11);
440 kryo_l2_set_indirect_reg(L2ACDDVMRC_REG
, 0x000e0f0f);
441 kryo_l2_set_indirect_reg(L2ACDSSCR_REG
, 0x00000601);
443 if (PWRCL_CPU_REG_MASK
== (hwid
| PWRCL_CPU_REG_MASK
)) {
444 writel(0xf, base
+ PWRCL_REG_OFFSET
+ SSSCTL_OFFSET
);
445 kryo_l2_set_indirect_reg(L2ACDCR_REG
, 0x002c5ffd);
448 if (PERFCL_CPU_REG_MASK
== (hwid
| PERFCL_CPU_REG_MASK
)) {
449 kryo_l2_set_indirect_reg(L2ACDCR_REG
, 0x002c5ffd);
450 writel(0xf, base
+ PERFCL_REG_OFFSET
+ SSSCTL_OFFSET
);
453 spin_unlock_irqrestore(&qcom_clk_acd_lock
, flags
);
456 static int cpu_clk_notifier_cb(struct notifier_block
*nb
, unsigned long event
,
459 struct clk_cpu_8996_mux
*cpuclk
= to_clk_cpu_8996_mux_nb(nb
);
460 struct clk_notifier_data
*cnd
= data
;
464 case PRE_RATE_CHANGE
:
465 ret
= clk_cpu_8996_mux_set_parent(&cpuclk
->clkr
.hw
, ALT_INDEX
);
466 qcom_cpu_clk_msm8996_acd_init(base
);
468 case POST_RATE_CHANGE
:
469 if (cnd
->new_rate
< DIV_2_THRESHOLD
)
470 ret
= clk_cpu_8996_mux_set_parent(&cpuclk
->clkr
.hw
,
473 ret
= clk_cpu_8996_mux_set_parent(&cpuclk
->clkr
.hw
,
481 return notifier_from_errno(ret
);
484 static int qcom_cpu_clk_msm8996_driver_probe(struct platform_device
*pdev
)
486 struct regmap
*regmap
;
487 struct clk_hw_onecell_data
*data
;
488 struct device
*dev
= &pdev
->dev
;
491 data
= devm_kzalloc(dev
, struct_size(data
, hws
, 2), GFP_KERNEL
);
495 base
= devm_platform_ioremap_resource(pdev
, 0);
497 return PTR_ERR(base
);
499 regmap
= devm_regmap_init_mmio(dev
, base
, &cpu_msm8996_regmap_config
);
501 return PTR_ERR(regmap
);
503 ret
= qcom_cpu_clk_msm8996_register_clks(dev
, regmap
);
507 qcom_cpu_clk_msm8996_acd_init(base
);
509 data
->hws
[0] = &pwrcl_pmux
.clkr
.hw
;
510 data
->hws
[1] = &perfcl_pmux
.clkr
.hw
;
513 return devm_of_clk_add_hw_provider(dev
, of_clk_hw_onecell_get
, data
);
516 static int qcom_cpu_clk_msm8996_driver_remove(struct platform_device
*pdev
)
518 return qcom_cpu_clk_msm8996_unregister_clks();
521 static const struct of_device_id qcom_cpu_clk_msm8996_match_table
[] = {
522 { .compatible
= "qcom,msm8996-apcc" },
525 MODULE_DEVICE_TABLE(of
, qcom_cpu_clk_msm8996_match_table
);
527 static struct platform_driver qcom_cpu_clk_msm8996_driver
= {
528 .probe
= qcom_cpu_clk_msm8996_driver_probe
,
529 .remove
= qcom_cpu_clk_msm8996_driver_remove
,
531 .name
= "qcom-msm8996-apcc",
532 .of_match_table
= qcom_cpu_clk_msm8996_match_table
,
535 module_platform_driver(qcom_cpu_clk_msm8996_driver
);
537 MODULE_DESCRIPTION("QCOM MSM8996 CPU Clock Driver");
538 MODULE_LICENSE("GPL v2");