1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (c) 2018, The Linux Foundation. All rights reserved.
6 #include <linux/bitfield.h>
7 #include <linux/cpufreq.h>
8 #include <linux/init.h>
9 #include <linux/interconnect.h>
10 #include <linux/kernel.h>
11 #include <linux/module.h>
12 #include <linux/of_address.h>
13 #include <linux/of_platform.h>
14 #include <linux/pm_opp.h>
15 #include <linux/slab.h>
17 #define LUT_MAX_ENTRIES 40U
18 #define LUT_SRC GENMASK(31, 30)
19 #define LUT_L_VAL GENMASK(7, 0)
20 #define LUT_CORE_COUNT GENMASK(18, 16)
21 #define LUT_VOLT GENMASK(11, 0)
23 #define LUT_TURBO_IND 1
25 struct qcom_cpufreq_soc_data
{
33 struct qcom_cpufreq_data
{
35 const struct qcom_cpufreq_soc_data
*soc_data
;
38 static unsigned long cpu_hw_rate
, xo_rate
;
39 static bool icc_scaling_enabled
;
41 static int qcom_cpufreq_set_bw(struct cpufreq_policy
*policy
,
42 unsigned long freq_khz
)
44 unsigned long freq_hz
= freq_khz
* 1000;
45 struct dev_pm_opp
*opp
;
49 dev
= get_cpu_device(policy
->cpu
);
53 opp
= dev_pm_opp_find_freq_exact(dev
, freq_hz
, true);
57 ret
= dev_pm_opp_set_bw(dev
, opp
);
62 static int qcom_cpufreq_update_opp(struct device
*cpu_dev
,
63 unsigned long freq_khz
,
66 unsigned long freq_hz
= freq_khz
* 1000;
69 /* Skip voltage update if the opp table is not available */
70 if (!icc_scaling_enabled
)
71 return dev_pm_opp_add(cpu_dev
, freq_hz
, volt
);
73 ret
= dev_pm_opp_adjust_voltage(cpu_dev
, freq_hz
, volt
, volt
, volt
);
75 dev_err(cpu_dev
, "Voltage update failed freq=%ld\n", freq_khz
);
79 return dev_pm_opp_enable(cpu_dev
, freq_hz
);
82 static int qcom_cpufreq_hw_target_index(struct cpufreq_policy
*policy
,
85 struct qcom_cpufreq_data
*data
= policy
->driver_data
;
86 const struct qcom_cpufreq_soc_data
*soc_data
= data
->soc_data
;
87 unsigned long freq
= policy
->freq_table
[index
].frequency
;
89 writel_relaxed(index
, data
->base
+ soc_data
->reg_perf_state
);
91 if (icc_scaling_enabled
)
92 qcom_cpufreq_set_bw(policy
, freq
);
97 static unsigned int qcom_cpufreq_hw_get(unsigned int cpu
)
99 struct qcom_cpufreq_data
*data
;
100 const struct qcom_cpufreq_soc_data
*soc_data
;
101 struct cpufreq_policy
*policy
;
104 policy
= cpufreq_cpu_get_raw(cpu
);
108 data
= policy
->driver_data
;
109 soc_data
= data
->soc_data
;
111 index
= readl_relaxed(data
->base
+ soc_data
->reg_perf_state
);
112 index
= min(index
, LUT_MAX_ENTRIES
- 1);
114 return policy
->freq_table
[index
].frequency
;
117 static unsigned int qcom_cpufreq_hw_fast_switch(struct cpufreq_policy
*policy
,
118 unsigned int target_freq
)
120 struct qcom_cpufreq_data
*data
= policy
->driver_data
;
121 const struct qcom_cpufreq_soc_data
*soc_data
= data
->soc_data
;
124 index
= policy
->cached_resolved_idx
;
125 writel_relaxed(index
, data
->base
+ soc_data
->reg_perf_state
);
127 return policy
->freq_table
[index
].frequency
;
130 static int qcom_cpufreq_hw_read_lut(struct device
*cpu_dev
,
131 struct cpufreq_policy
*policy
)
133 u32 data
, src
, lval
, i
, core_count
, prev_freq
= 0, freq
;
135 struct cpufreq_frequency_table
*table
;
136 struct dev_pm_opp
*opp
;
139 struct qcom_cpufreq_data
*drv_data
= policy
->driver_data
;
140 const struct qcom_cpufreq_soc_data
*soc_data
= drv_data
->soc_data
;
142 table
= kcalloc(LUT_MAX_ENTRIES
+ 1, sizeof(*table
), GFP_KERNEL
);
146 ret
= dev_pm_opp_of_add_table(cpu_dev
);
148 /* Disable all opps and cross-validate against LUT later */
149 icc_scaling_enabled
= true;
150 for (rate
= 0; ; rate
++) {
151 opp
= dev_pm_opp_find_freq_ceil(cpu_dev
, &rate
);
156 dev_pm_opp_disable(cpu_dev
, rate
);
158 } else if (ret
!= -ENODEV
) {
159 dev_err(cpu_dev
, "Invalid opp table in device tree\n");
162 policy
->fast_switch_possible
= true;
163 icc_scaling_enabled
= false;
166 for (i
= 0; i
< LUT_MAX_ENTRIES
; i
++) {
167 data
= readl_relaxed(drv_data
->base
+ soc_data
->reg_freq_lut
+
168 i
* soc_data
->lut_row_size
);
169 src
= FIELD_GET(LUT_SRC
, data
);
170 lval
= FIELD_GET(LUT_L_VAL
, data
);
171 core_count
= FIELD_GET(LUT_CORE_COUNT
, data
);
173 data
= readl_relaxed(drv_data
->base
+ soc_data
->reg_volt_lut
+
174 i
* soc_data
->lut_row_size
);
175 volt
= FIELD_GET(LUT_VOLT
, data
) * 1000;
178 freq
= xo_rate
* lval
/ 1000;
180 freq
= cpu_hw_rate
/ 1000;
182 if (freq
!= prev_freq
&& core_count
!= LUT_TURBO_IND
) {
183 if (!qcom_cpufreq_update_opp(cpu_dev
, freq
, volt
)) {
184 table
[i
].frequency
= freq
;
185 dev_dbg(cpu_dev
, "index=%d freq=%d, core_count %d\n", i
,
188 dev_warn(cpu_dev
, "failed to update OPP for freq=%d\n", freq
);
189 table
[i
].frequency
= CPUFREQ_ENTRY_INVALID
;
192 } else if (core_count
== LUT_TURBO_IND
) {
193 table
[i
].frequency
= CPUFREQ_ENTRY_INVALID
;
197 * Two of the same frequencies with the same core counts means
200 if (i
> 0 && prev_freq
== freq
) {
201 struct cpufreq_frequency_table
*prev
= &table
[i
- 1];
204 * Only treat the last frequency that might be a boost
205 * as the boost frequency
207 if (prev
->frequency
== CPUFREQ_ENTRY_INVALID
) {
208 if (!qcom_cpufreq_update_opp(cpu_dev
, prev_freq
, volt
)) {
209 prev
->frequency
= prev_freq
;
210 prev
->flags
= CPUFREQ_BOOST_FREQ
;
212 dev_warn(cpu_dev
, "failed to update OPP for freq=%d\n",
223 table
[i
].frequency
= CPUFREQ_TABLE_END
;
224 policy
->freq_table
= table
;
225 dev_pm_opp_set_sharing_cpus(cpu_dev
, policy
->cpus
);
230 static void qcom_get_related_cpus(int index
, struct cpumask
*m
)
232 struct device_node
*cpu_np
;
233 struct of_phandle_args args
;
236 for_each_possible_cpu(cpu
) {
237 cpu_np
= of_cpu_device_node_get(cpu
);
241 ret
= of_parse_phandle_with_args(cpu_np
, "qcom,freq-domain",
242 "#freq-domain-cells", 0,
248 if (index
== args
.args
[0])
249 cpumask_set_cpu(cpu
, m
);
253 static const struct qcom_cpufreq_soc_data qcom_soc_data
= {
255 .reg_freq_lut
= 0x110,
256 .reg_volt_lut
= 0x114,
257 .reg_perf_state
= 0x920,
261 static const struct qcom_cpufreq_soc_data epss_soc_data
= {
263 .reg_freq_lut
= 0x100,
264 .reg_volt_lut
= 0x200,
265 .reg_perf_state
= 0x320,
269 static const struct of_device_id qcom_cpufreq_hw_match
[] = {
270 { .compatible
= "qcom,cpufreq-hw", .data
= &qcom_soc_data
},
271 { .compatible
= "qcom,cpufreq-epss", .data
= &epss_soc_data
},
274 MODULE_DEVICE_TABLE(of
, qcom_cpufreq_hw_match
);
276 static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy
*policy
)
278 struct platform_device
*pdev
= cpufreq_get_driver_data();
279 struct device
*dev
= &pdev
->dev
;
280 struct of_phandle_args args
;
281 struct device_node
*cpu_np
;
282 struct device
*cpu_dev
;
284 struct qcom_cpufreq_data
*data
;
287 cpu_dev
= get_cpu_device(policy
->cpu
);
289 pr_err("%s: failed to get cpu%d device\n", __func__
,
294 cpu_np
= of_cpu_device_node_get(policy
->cpu
);
298 ret
= of_parse_phandle_with_args(cpu_np
, "qcom,freq-domain",
299 "#freq-domain-cells", 0, &args
);
304 index
= args
.args
[0];
306 base
= devm_platform_ioremap_resource(pdev
, index
);
308 return PTR_ERR(base
);
310 data
= devm_kzalloc(dev
, sizeof(*data
), GFP_KERNEL
);
316 data
->soc_data
= of_device_get_match_data(&pdev
->dev
);
319 /* HW should be in enabled state to proceed */
320 if (!(readl_relaxed(base
+ data
->soc_data
->reg_enable
) & 0x1)) {
321 dev_err(dev
, "Domain-%d cpufreq hardware not enabled\n", index
);
326 qcom_get_related_cpus(index
, policy
->cpus
);
327 if (!cpumask_weight(policy
->cpus
)) {
328 dev_err(dev
, "Domain-%d failed to get related CPUs\n", index
);
333 policy
->driver_data
= data
;
335 ret
= qcom_cpufreq_hw_read_lut(cpu_dev
, policy
);
337 dev_err(dev
, "Domain-%d failed to read LUT\n", index
);
341 ret
= dev_pm_opp_get_opp_count(cpu_dev
);
343 dev_err(cpu_dev
, "Failed to add OPPs\n");
348 dev_pm_opp_of_register_em(cpu_dev
, policy
->cpus
);
352 devm_iounmap(dev
, base
);
356 static int qcom_cpufreq_hw_cpu_exit(struct cpufreq_policy
*policy
)
358 struct device
*cpu_dev
= get_cpu_device(policy
->cpu
);
359 struct qcom_cpufreq_data
*data
= policy
->driver_data
;
360 struct platform_device
*pdev
= cpufreq_get_driver_data();
362 dev_pm_opp_remove_all_dynamic(cpu_dev
);
363 dev_pm_opp_of_cpumask_remove_table(policy
->related_cpus
);
364 kfree(policy
->freq_table
);
365 devm_iounmap(&pdev
->dev
, data
->base
);
370 static struct freq_attr
*qcom_cpufreq_hw_attr
[] = {
371 &cpufreq_freq_attr_scaling_available_freqs
,
372 &cpufreq_freq_attr_scaling_boost_freqs
,
376 static struct cpufreq_driver cpufreq_qcom_hw_driver
= {
377 .flags
= CPUFREQ_STICKY
| CPUFREQ_NEED_INITIAL_FREQ_CHECK
|
378 CPUFREQ_HAVE_GOVERNOR_PER_POLICY
|
379 CPUFREQ_IS_COOLING_DEV
,
380 .verify
= cpufreq_generic_frequency_table_verify
,
381 .target_index
= qcom_cpufreq_hw_target_index
,
382 .get
= qcom_cpufreq_hw_get
,
383 .init
= qcom_cpufreq_hw_cpu_init
,
384 .exit
= qcom_cpufreq_hw_cpu_exit
,
385 .fast_switch
= qcom_cpufreq_hw_fast_switch
,
386 .name
= "qcom-cpufreq-hw",
387 .attr
= qcom_cpufreq_hw_attr
,
390 static int qcom_cpufreq_hw_driver_probe(struct platform_device
*pdev
)
392 struct device
*cpu_dev
;
396 clk
= clk_get(&pdev
->dev
, "xo");
400 xo_rate
= clk_get_rate(clk
);
403 clk
= clk_get(&pdev
->dev
, "alternate");
407 cpu_hw_rate
= clk_get_rate(clk
) / CLK_HW_DIV
;
410 cpufreq_qcom_hw_driver
.driver_data
= pdev
;
412 /* Check for optional interconnect paths on CPU0 */
413 cpu_dev
= get_cpu_device(0);
415 return -EPROBE_DEFER
;
417 ret
= dev_pm_opp_of_find_icc_paths(cpu_dev
, NULL
);
421 ret
= cpufreq_register_driver(&cpufreq_qcom_hw_driver
);
423 dev_err(&pdev
->dev
, "CPUFreq HW driver failed to register\n");
425 dev_dbg(&pdev
->dev
, "QCOM CPUFreq HW driver initialized\n");
430 static int qcom_cpufreq_hw_driver_remove(struct platform_device
*pdev
)
432 return cpufreq_unregister_driver(&cpufreq_qcom_hw_driver
);
435 static struct platform_driver qcom_cpufreq_hw_driver
= {
436 .probe
= qcom_cpufreq_hw_driver_probe
,
437 .remove
= qcom_cpufreq_hw_driver_remove
,
439 .name
= "qcom-cpufreq-hw",
440 .of_match_table
= qcom_cpufreq_hw_match
,
444 static int __init
qcom_cpufreq_hw_init(void)
446 return platform_driver_register(&qcom_cpufreq_hw_driver
);
448 postcore_initcall(qcom_cpufreq_hw_init
);
450 static void __exit
qcom_cpufreq_hw_exit(void)
452 platform_driver_unregister(&qcom_cpufreq_hw_driver
);
454 module_exit(qcom_cpufreq_hw_exit
);
456 MODULE_DESCRIPTION("QCOM CPUFREQ HW Driver");
457 MODULE_LICENSE("GPL v2");