1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2015 Linaro Ltd. |
4 | * Author: Pi-Cheng Chen <pi-cheng.chen@linaro.org> |
5 | */ |
6 | |
7 | #include <linux/clk.h> |
8 | #include <linux/cpu.h> |
9 | #include <linux/cpufreq.h> |
10 | #include <linux/cpumask.h> |
11 | #include <linux/minmax.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/of_platform.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/pm_opp.h> |
17 | #include <linux/regulator/consumer.h> |
18 | |
19 | struct mtk_cpufreq_platform_data { |
20 | int min_volt_shift; |
21 | int max_volt_shift; |
22 | int proc_max_volt; |
23 | int sram_min_volt; |
24 | int sram_max_volt; |
25 | bool ccifreq_supported; |
26 | }; |
27 | |
28 | /* |
29 | * The struct mtk_cpu_dvfs_info holds necessary information for doing CPU DVFS |
30 | * on each CPU power/clock domain of Mediatek SoCs. Each CPU cluster in |
31 | * Mediatek SoCs has two voltage inputs, Vproc and Vsram. In some cases the two |
32 | * voltage inputs need to be controlled under a hardware limitation: |
33 | * 100mV < Vsram - Vproc < 200mV |
34 | * |
35 | * When scaling the clock frequency of a CPU clock domain, the clock source |
36 | * needs to be switched to another stable PLL clock temporarily until |
37 | * the original PLL becomes stable at target frequency. |
38 | */ |
39 | struct mtk_cpu_dvfs_info { |
40 | struct cpumask cpus; |
41 | struct device *cpu_dev; |
42 | struct device *cci_dev; |
43 | struct regulator *proc_reg; |
44 | struct regulator *sram_reg; |
45 | struct clk *cpu_clk; |
46 | struct clk *inter_clk; |
47 | struct list_head list_head; |
48 | int intermediate_voltage; |
49 | bool need_voltage_tracking; |
50 | int vproc_on_boot; |
51 | int pre_vproc; |
52 | /* Avoid race condition for regulators between notify and policy */ |
53 | struct mutex reg_lock; |
54 | struct notifier_block opp_nb; |
55 | unsigned int opp_cpu; |
56 | unsigned long current_freq; |
57 | const struct mtk_cpufreq_platform_data *soc_data; |
58 | int vtrack_max; |
59 | bool ccifreq_bound; |
60 | }; |
61 | |
62 | static struct platform_device *cpufreq_pdev; |
63 | |
64 | static LIST_HEAD(dvfs_info_list); |
65 | |
66 | static struct mtk_cpu_dvfs_info *mtk_cpu_dvfs_info_lookup(int cpu) |
67 | { |
68 | struct mtk_cpu_dvfs_info *info; |
69 | |
70 | list_for_each_entry(info, &dvfs_info_list, list_head) { |
71 | if (cpumask_test_cpu(cpu, cpumask: &info->cpus)) |
72 | return info; |
73 | } |
74 | |
75 | return NULL; |
76 | } |
77 | |
78 | static int mtk_cpufreq_voltage_tracking(struct mtk_cpu_dvfs_info *info, |
79 | int new_vproc) |
80 | { |
81 | const struct mtk_cpufreq_platform_data *soc_data = info->soc_data; |
82 | struct regulator *proc_reg = info->proc_reg; |
83 | struct regulator *sram_reg = info->sram_reg; |
84 | int pre_vproc, pre_vsram, new_vsram, vsram, vproc, ret; |
85 | int retry = info->vtrack_max; |
86 | |
87 | pre_vproc = regulator_get_voltage(regulator: proc_reg); |
88 | if (pre_vproc < 0) { |
89 | dev_err(info->cpu_dev, |
90 | "invalid Vproc value: %d\n" , pre_vproc); |
91 | return pre_vproc; |
92 | } |
93 | |
94 | pre_vsram = regulator_get_voltage(regulator: sram_reg); |
95 | if (pre_vsram < 0) { |
96 | dev_err(info->cpu_dev, "invalid Vsram value: %d\n" , pre_vsram); |
97 | return pre_vsram; |
98 | } |
99 | |
100 | new_vsram = clamp(new_vproc + soc_data->min_volt_shift, |
101 | soc_data->sram_min_volt, soc_data->sram_max_volt); |
102 | |
103 | do { |
104 | if (pre_vproc <= new_vproc) { |
105 | vsram = clamp(pre_vproc + soc_data->max_volt_shift, |
106 | soc_data->sram_min_volt, new_vsram); |
107 | ret = regulator_set_voltage(regulator: sram_reg, min_uV: vsram, |
108 | max_uV: soc_data->sram_max_volt); |
109 | |
110 | if (ret) |
111 | return ret; |
112 | |
113 | if (vsram == soc_data->sram_max_volt || |
114 | new_vsram == soc_data->sram_min_volt) |
115 | vproc = new_vproc; |
116 | else |
117 | vproc = vsram - soc_data->min_volt_shift; |
118 | |
119 | ret = regulator_set_voltage(regulator: proc_reg, min_uV: vproc, |
120 | max_uV: soc_data->proc_max_volt); |
121 | if (ret) { |
122 | regulator_set_voltage(regulator: sram_reg, min_uV: pre_vsram, |
123 | max_uV: soc_data->sram_max_volt); |
124 | return ret; |
125 | } |
126 | } else if (pre_vproc > new_vproc) { |
127 | vproc = max(new_vproc, |
128 | pre_vsram - soc_data->max_volt_shift); |
129 | ret = regulator_set_voltage(regulator: proc_reg, min_uV: vproc, |
130 | max_uV: soc_data->proc_max_volt); |
131 | if (ret) |
132 | return ret; |
133 | |
134 | if (vproc == new_vproc) |
135 | vsram = new_vsram; |
136 | else |
137 | vsram = max(new_vsram, |
138 | vproc + soc_data->min_volt_shift); |
139 | |
140 | ret = regulator_set_voltage(regulator: sram_reg, min_uV: vsram, |
141 | max_uV: soc_data->sram_max_volt); |
142 | if (ret) { |
143 | regulator_set_voltage(regulator: proc_reg, min_uV: pre_vproc, |
144 | max_uV: soc_data->proc_max_volt); |
145 | return ret; |
146 | } |
147 | } |
148 | |
149 | pre_vproc = vproc; |
150 | pre_vsram = vsram; |
151 | |
152 | if (--retry < 0) { |
153 | dev_err(info->cpu_dev, |
154 | "over loop count, failed to set voltage\n" ); |
155 | return -EINVAL; |
156 | } |
157 | } while (vproc != new_vproc || vsram != new_vsram); |
158 | |
159 | return 0; |
160 | } |
161 | |
162 | static int mtk_cpufreq_set_voltage(struct mtk_cpu_dvfs_info *info, int vproc) |
163 | { |
164 | const struct mtk_cpufreq_platform_data *soc_data = info->soc_data; |
165 | int ret; |
166 | |
167 | if (info->need_voltage_tracking) |
168 | ret = mtk_cpufreq_voltage_tracking(info, new_vproc: vproc); |
169 | else |
170 | ret = regulator_set_voltage(regulator: info->proc_reg, min_uV: vproc, |
171 | max_uV: soc_data->proc_max_volt); |
172 | if (!ret) |
173 | info->pre_vproc = vproc; |
174 | |
175 | return ret; |
176 | } |
177 | |
178 | static bool is_ccifreq_ready(struct mtk_cpu_dvfs_info *info) |
179 | { |
180 | struct device_link *sup_link; |
181 | |
182 | if (info->ccifreq_bound) |
183 | return true; |
184 | |
185 | sup_link = device_link_add(consumer: info->cpu_dev, supplier: info->cci_dev, |
186 | DL_FLAG_AUTOREMOVE_CONSUMER); |
187 | if (!sup_link) { |
188 | dev_err(info->cpu_dev, "cpu%d: sup_link is NULL\n" , info->opp_cpu); |
189 | return false; |
190 | } |
191 | |
192 | if (sup_link->supplier->links.status != DL_DEV_DRIVER_BOUND) |
193 | return false; |
194 | |
195 | info->ccifreq_bound = true; |
196 | |
197 | return true; |
198 | } |
199 | |
200 | static int mtk_cpufreq_set_target(struct cpufreq_policy *policy, |
201 | unsigned int index) |
202 | { |
203 | struct cpufreq_frequency_table *freq_table = policy->freq_table; |
204 | struct clk *cpu_clk = policy->clk; |
205 | struct clk *armpll = clk_get_parent(clk: cpu_clk); |
206 | struct mtk_cpu_dvfs_info *info = policy->driver_data; |
207 | struct device *cpu_dev = info->cpu_dev; |
208 | struct dev_pm_opp *opp; |
209 | long freq_hz, pre_freq_hz; |
210 | int vproc, pre_vproc, inter_vproc, target_vproc, ret; |
211 | |
212 | inter_vproc = info->intermediate_voltage; |
213 | |
214 | pre_freq_hz = clk_get_rate(clk: cpu_clk); |
215 | |
216 | mutex_lock(&info->reg_lock); |
217 | |
218 | if (unlikely(info->pre_vproc <= 0)) |
219 | pre_vproc = regulator_get_voltage(regulator: info->proc_reg); |
220 | else |
221 | pre_vproc = info->pre_vproc; |
222 | |
223 | if (pre_vproc < 0) { |
224 | dev_err(cpu_dev, "invalid Vproc value: %d\n" , pre_vproc); |
225 | ret = pre_vproc; |
226 | goto out; |
227 | } |
228 | |
229 | freq_hz = freq_table[index].frequency * 1000; |
230 | |
231 | opp = dev_pm_opp_find_freq_ceil(dev: cpu_dev, freq: &freq_hz); |
232 | if (IS_ERR(ptr: opp)) { |
233 | dev_err(cpu_dev, "cpu%d: failed to find OPP for %ld\n" , |
234 | policy->cpu, freq_hz); |
235 | ret = PTR_ERR(ptr: opp); |
236 | goto out; |
237 | } |
238 | vproc = dev_pm_opp_get_voltage(opp); |
239 | dev_pm_opp_put(opp); |
240 | |
241 | /* |
242 | * If MediaTek cci is supported but is not ready, we will use the value |
243 | * of max(target cpu voltage, booting voltage) to prevent high freqeuncy |
244 | * low voltage crash. |
245 | */ |
246 | if (info->soc_data->ccifreq_supported && !is_ccifreq_ready(info)) |
247 | vproc = max(vproc, info->vproc_on_boot); |
248 | |
249 | /* |
250 | * If the new voltage or the intermediate voltage is higher than the |
251 | * current voltage, scale up voltage first. |
252 | */ |
253 | target_vproc = max(inter_vproc, vproc); |
254 | if (pre_vproc <= target_vproc) { |
255 | ret = mtk_cpufreq_set_voltage(info, vproc: target_vproc); |
256 | if (ret) { |
257 | dev_err(cpu_dev, |
258 | "cpu%d: failed to scale up voltage!\n" , policy->cpu); |
259 | mtk_cpufreq_set_voltage(info, vproc: pre_vproc); |
260 | goto out; |
261 | } |
262 | } |
263 | |
264 | /* Reparent the CPU clock to intermediate clock. */ |
265 | ret = clk_set_parent(clk: cpu_clk, parent: info->inter_clk); |
266 | if (ret) { |
267 | dev_err(cpu_dev, |
268 | "cpu%d: failed to re-parent cpu clock!\n" , policy->cpu); |
269 | mtk_cpufreq_set_voltage(info, vproc: pre_vproc); |
270 | goto out; |
271 | } |
272 | |
273 | /* Set the original PLL to target rate. */ |
274 | ret = clk_set_rate(clk: armpll, rate: freq_hz); |
275 | if (ret) { |
276 | dev_err(cpu_dev, |
277 | "cpu%d: failed to scale cpu clock rate!\n" , policy->cpu); |
278 | clk_set_parent(clk: cpu_clk, parent: armpll); |
279 | mtk_cpufreq_set_voltage(info, vproc: pre_vproc); |
280 | goto out; |
281 | } |
282 | |
283 | /* Set parent of CPU clock back to the original PLL. */ |
284 | ret = clk_set_parent(clk: cpu_clk, parent: armpll); |
285 | if (ret) { |
286 | dev_err(cpu_dev, |
287 | "cpu%d: failed to re-parent cpu clock!\n" , policy->cpu); |
288 | mtk_cpufreq_set_voltage(info, vproc: inter_vproc); |
289 | goto out; |
290 | } |
291 | |
292 | /* |
293 | * If the new voltage is lower than the intermediate voltage or the |
294 | * original voltage, scale down to the new voltage. |
295 | */ |
296 | if (vproc < inter_vproc || vproc < pre_vproc) { |
297 | ret = mtk_cpufreq_set_voltage(info, vproc); |
298 | if (ret) { |
299 | dev_err(cpu_dev, |
300 | "cpu%d: failed to scale down voltage!\n" , policy->cpu); |
301 | clk_set_parent(clk: cpu_clk, parent: info->inter_clk); |
302 | clk_set_rate(clk: armpll, rate: pre_freq_hz); |
303 | clk_set_parent(clk: cpu_clk, parent: armpll); |
304 | goto out; |
305 | } |
306 | } |
307 | |
308 | info->current_freq = freq_hz; |
309 | |
310 | out: |
311 | mutex_unlock(lock: &info->reg_lock); |
312 | |
313 | return ret; |
314 | } |
315 | |
316 | static int mtk_cpufreq_opp_notifier(struct notifier_block *nb, |
317 | unsigned long event, void *data) |
318 | { |
319 | struct dev_pm_opp *opp = data; |
320 | struct dev_pm_opp *new_opp; |
321 | struct mtk_cpu_dvfs_info *info; |
322 | unsigned long freq, volt; |
323 | struct cpufreq_policy *policy; |
324 | int ret = 0; |
325 | |
326 | info = container_of(nb, struct mtk_cpu_dvfs_info, opp_nb); |
327 | |
328 | if (event == OPP_EVENT_ADJUST_VOLTAGE) { |
329 | freq = dev_pm_opp_get_freq(opp); |
330 | |
331 | mutex_lock(&info->reg_lock); |
332 | if (info->current_freq == freq) { |
333 | volt = dev_pm_opp_get_voltage(opp); |
334 | ret = mtk_cpufreq_set_voltage(info, vproc: volt); |
335 | if (ret) |
336 | dev_err(info->cpu_dev, |
337 | "failed to scale voltage: %d\n" , ret); |
338 | } |
339 | mutex_unlock(lock: &info->reg_lock); |
340 | } else if (event == OPP_EVENT_DISABLE) { |
341 | freq = dev_pm_opp_get_freq(opp); |
342 | |
343 | /* case of current opp item is disabled */ |
344 | if (info->current_freq == freq) { |
345 | freq = 1; |
346 | new_opp = dev_pm_opp_find_freq_ceil(dev: info->cpu_dev, |
347 | freq: &freq); |
348 | if (IS_ERR(ptr: new_opp)) { |
349 | dev_err(info->cpu_dev, |
350 | "all opp items are disabled\n" ); |
351 | ret = PTR_ERR(ptr: new_opp); |
352 | return notifier_from_errno(err: ret); |
353 | } |
354 | |
355 | dev_pm_opp_put(opp: new_opp); |
356 | policy = cpufreq_cpu_get(cpu: info->opp_cpu); |
357 | if (policy) { |
358 | cpufreq_driver_target(policy, target_freq: freq / 1000, |
359 | CPUFREQ_RELATION_L); |
360 | cpufreq_cpu_put(policy); |
361 | } |
362 | } |
363 | } |
364 | |
365 | return notifier_from_errno(err: ret); |
366 | } |
367 | |
368 | static struct device *of_get_cci(struct device *cpu_dev) |
369 | { |
370 | struct device_node *np; |
371 | struct platform_device *pdev; |
372 | |
373 | np = of_parse_phandle(np: cpu_dev->of_node, phandle_name: "mediatek,cci" , index: 0); |
374 | if (!np) |
375 | return ERR_PTR(error: -ENODEV); |
376 | |
377 | pdev = of_find_device_by_node(np); |
378 | of_node_put(node: np); |
379 | if (!pdev) |
380 | return ERR_PTR(error: -ENODEV); |
381 | |
382 | return &pdev->dev; |
383 | } |
384 | |
385 | static int mtk_cpu_dvfs_info_init(struct mtk_cpu_dvfs_info *info, int cpu) |
386 | { |
387 | struct device *cpu_dev; |
388 | struct dev_pm_opp *opp; |
389 | unsigned long rate; |
390 | int ret; |
391 | |
392 | cpu_dev = get_cpu_device(cpu); |
393 | if (!cpu_dev) { |
394 | dev_err(cpu_dev, "failed to get cpu%d device\n" , cpu); |
395 | return -ENODEV; |
396 | } |
397 | info->cpu_dev = cpu_dev; |
398 | |
399 | info->ccifreq_bound = false; |
400 | if (info->soc_data->ccifreq_supported) { |
401 | info->cci_dev = of_get_cci(cpu_dev: info->cpu_dev); |
402 | if (IS_ERR(ptr: info->cci_dev)) { |
403 | ret = PTR_ERR(ptr: info->cci_dev); |
404 | dev_err(cpu_dev, "cpu%d: failed to get cci device\n" , cpu); |
405 | return -ENODEV; |
406 | } |
407 | } |
408 | |
409 | info->cpu_clk = clk_get(dev: cpu_dev, id: "cpu" ); |
410 | if (IS_ERR(ptr: info->cpu_clk)) { |
411 | ret = PTR_ERR(ptr: info->cpu_clk); |
412 | return dev_err_probe(dev: cpu_dev, err: ret, |
413 | fmt: "cpu%d: failed to get cpu clk\n" , cpu); |
414 | } |
415 | |
416 | info->inter_clk = clk_get(dev: cpu_dev, id: "intermediate" ); |
417 | if (IS_ERR(ptr: info->inter_clk)) { |
418 | ret = PTR_ERR(ptr: info->inter_clk); |
419 | dev_err_probe(dev: cpu_dev, err: ret, |
420 | fmt: "cpu%d: failed to get intermediate clk\n" , cpu); |
421 | goto out_free_mux_clock; |
422 | } |
423 | |
424 | info->proc_reg = regulator_get_optional(dev: cpu_dev, id: "proc" ); |
425 | if (IS_ERR(ptr: info->proc_reg)) { |
426 | ret = PTR_ERR(ptr: info->proc_reg); |
427 | dev_err_probe(dev: cpu_dev, err: ret, |
428 | fmt: "cpu%d: failed to get proc regulator\n" , cpu); |
429 | goto out_free_inter_clock; |
430 | } |
431 | |
432 | ret = regulator_enable(regulator: info->proc_reg); |
433 | if (ret) { |
434 | dev_warn(cpu_dev, "cpu%d: failed to enable vproc\n" , cpu); |
435 | goto out_free_proc_reg; |
436 | } |
437 | |
438 | /* Both presence and absence of sram regulator are valid cases. */ |
439 | info->sram_reg = regulator_get_optional(dev: cpu_dev, id: "sram" ); |
440 | if (IS_ERR(ptr: info->sram_reg)) { |
441 | ret = PTR_ERR(ptr: info->sram_reg); |
442 | if (ret == -EPROBE_DEFER) |
443 | goto out_disable_proc_reg; |
444 | |
445 | info->sram_reg = NULL; |
446 | } else { |
447 | ret = regulator_enable(regulator: info->sram_reg); |
448 | if (ret) { |
449 | dev_warn(cpu_dev, "cpu%d: failed to enable vsram\n" , cpu); |
450 | goto out_free_sram_reg; |
451 | } |
452 | } |
453 | |
454 | /* Get OPP-sharing information from "operating-points-v2" bindings */ |
455 | ret = dev_pm_opp_of_get_sharing_cpus(cpu_dev, cpumask: &info->cpus); |
456 | if (ret) { |
457 | dev_err(cpu_dev, |
458 | "cpu%d: failed to get OPP-sharing information\n" , cpu); |
459 | goto out_disable_sram_reg; |
460 | } |
461 | |
462 | ret = dev_pm_opp_of_cpumask_add_table(cpumask: &info->cpus); |
463 | if (ret) { |
464 | dev_warn(cpu_dev, "cpu%d: no OPP table\n" , cpu); |
465 | goto out_disable_sram_reg; |
466 | } |
467 | |
468 | ret = clk_prepare_enable(clk: info->cpu_clk); |
469 | if (ret) |
470 | goto out_free_opp_table; |
471 | |
472 | ret = clk_prepare_enable(clk: info->inter_clk); |
473 | if (ret) |
474 | goto out_disable_mux_clock; |
475 | |
476 | if (info->soc_data->ccifreq_supported) { |
477 | info->vproc_on_boot = regulator_get_voltage(regulator: info->proc_reg); |
478 | if (info->vproc_on_boot < 0) { |
479 | ret = info->vproc_on_boot; |
480 | dev_err(info->cpu_dev, |
481 | "invalid Vproc value: %d\n" , info->vproc_on_boot); |
482 | goto out_disable_inter_clock; |
483 | } |
484 | } |
485 | |
486 | /* Search a safe voltage for intermediate frequency. */ |
487 | rate = clk_get_rate(clk: info->inter_clk); |
488 | opp = dev_pm_opp_find_freq_ceil(dev: cpu_dev, freq: &rate); |
489 | if (IS_ERR(ptr: opp)) { |
490 | dev_err(cpu_dev, "cpu%d: failed to get intermediate opp\n" , cpu); |
491 | ret = PTR_ERR(ptr: opp); |
492 | goto out_disable_inter_clock; |
493 | } |
494 | info->intermediate_voltage = dev_pm_opp_get_voltage(opp); |
495 | dev_pm_opp_put(opp); |
496 | |
497 | mutex_init(&info->reg_lock); |
498 | info->current_freq = clk_get_rate(clk: info->cpu_clk); |
499 | |
500 | info->opp_cpu = cpu; |
501 | info->opp_nb.notifier_call = mtk_cpufreq_opp_notifier; |
502 | ret = dev_pm_opp_register_notifier(dev: cpu_dev, nb: &info->opp_nb); |
503 | if (ret) { |
504 | dev_err(cpu_dev, "cpu%d: failed to register opp notifier\n" , cpu); |
505 | goto out_disable_inter_clock; |
506 | } |
507 | |
508 | /* |
509 | * If SRAM regulator is present, software "voltage tracking" is needed |
510 | * for this CPU power domain. |
511 | */ |
512 | info->need_voltage_tracking = (info->sram_reg != NULL); |
513 | |
514 | /* |
515 | * We assume min voltage is 0 and tracking target voltage using |
516 | * min_volt_shift for each iteration. |
517 | * The vtrack_max is 3 times of expeted iteration count. |
518 | */ |
519 | info->vtrack_max = 3 * DIV_ROUND_UP(max(info->soc_data->sram_max_volt, |
520 | info->soc_data->proc_max_volt), |
521 | info->soc_data->min_volt_shift); |
522 | |
523 | return 0; |
524 | |
525 | out_disable_inter_clock: |
526 | clk_disable_unprepare(clk: info->inter_clk); |
527 | |
528 | out_disable_mux_clock: |
529 | clk_disable_unprepare(clk: info->cpu_clk); |
530 | |
531 | out_free_opp_table: |
532 | dev_pm_opp_of_cpumask_remove_table(cpumask: &info->cpus); |
533 | |
534 | out_disable_sram_reg: |
535 | if (info->sram_reg) |
536 | regulator_disable(regulator: info->sram_reg); |
537 | |
538 | out_free_sram_reg: |
539 | if (info->sram_reg) |
540 | regulator_put(regulator: info->sram_reg); |
541 | |
542 | out_disable_proc_reg: |
543 | regulator_disable(regulator: info->proc_reg); |
544 | |
545 | out_free_proc_reg: |
546 | regulator_put(regulator: info->proc_reg); |
547 | |
548 | out_free_inter_clock: |
549 | clk_put(clk: info->inter_clk); |
550 | |
551 | out_free_mux_clock: |
552 | clk_put(clk: info->cpu_clk); |
553 | |
554 | return ret; |
555 | } |
556 | |
557 | static void mtk_cpu_dvfs_info_release(struct mtk_cpu_dvfs_info *info) |
558 | { |
559 | regulator_disable(regulator: info->proc_reg); |
560 | regulator_put(regulator: info->proc_reg); |
561 | if (info->sram_reg) { |
562 | regulator_disable(regulator: info->sram_reg); |
563 | regulator_put(regulator: info->sram_reg); |
564 | } |
565 | clk_disable_unprepare(clk: info->cpu_clk); |
566 | clk_put(clk: info->cpu_clk); |
567 | clk_disable_unprepare(clk: info->inter_clk); |
568 | clk_put(clk: info->inter_clk); |
569 | dev_pm_opp_of_cpumask_remove_table(cpumask: &info->cpus); |
570 | dev_pm_opp_unregister_notifier(dev: info->cpu_dev, nb: &info->opp_nb); |
571 | } |
572 | |
573 | static int mtk_cpufreq_init(struct cpufreq_policy *policy) |
574 | { |
575 | struct mtk_cpu_dvfs_info *info; |
576 | struct cpufreq_frequency_table *freq_table; |
577 | int ret; |
578 | |
579 | info = mtk_cpu_dvfs_info_lookup(cpu: policy->cpu); |
580 | if (!info) { |
581 | pr_err("dvfs info for cpu%d is not initialized.\n" , |
582 | policy->cpu); |
583 | return -EINVAL; |
584 | } |
585 | |
586 | ret = dev_pm_opp_init_cpufreq_table(dev: info->cpu_dev, table: &freq_table); |
587 | if (ret) { |
588 | dev_err(info->cpu_dev, |
589 | "failed to init cpufreq table for cpu%d: %d\n" , |
590 | policy->cpu, ret); |
591 | return ret; |
592 | } |
593 | |
594 | cpumask_copy(dstp: policy->cpus, srcp: &info->cpus); |
595 | policy->freq_table = freq_table; |
596 | policy->driver_data = info; |
597 | policy->clk = info->cpu_clk; |
598 | |
599 | return 0; |
600 | } |
601 | |
602 | static int mtk_cpufreq_exit(struct cpufreq_policy *policy) |
603 | { |
604 | struct mtk_cpu_dvfs_info *info = policy->driver_data; |
605 | |
606 | dev_pm_opp_free_cpufreq_table(dev: info->cpu_dev, table: &policy->freq_table); |
607 | |
608 | return 0; |
609 | } |
610 | |
611 | static struct cpufreq_driver mtk_cpufreq_driver = { |
612 | .flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK | |
613 | CPUFREQ_HAVE_GOVERNOR_PER_POLICY | |
614 | CPUFREQ_IS_COOLING_DEV, |
615 | .verify = cpufreq_generic_frequency_table_verify, |
616 | .target_index = mtk_cpufreq_set_target, |
617 | .get = cpufreq_generic_get, |
618 | .init = mtk_cpufreq_init, |
619 | .exit = mtk_cpufreq_exit, |
620 | .register_em = cpufreq_register_em_with_opp, |
621 | .name = "mtk-cpufreq" , |
622 | .attr = cpufreq_generic_attr, |
623 | }; |
624 | |
625 | static int mtk_cpufreq_probe(struct platform_device *pdev) |
626 | { |
627 | const struct mtk_cpufreq_platform_data *data; |
628 | struct mtk_cpu_dvfs_info *info, *tmp; |
629 | int cpu, ret; |
630 | |
631 | data = dev_get_platdata(dev: &pdev->dev); |
632 | if (!data) { |
633 | dev_err(&pdev->dev, |
634 | "failed to get mtk cpufreq platform data\n" ); |
635 | return -ENODEV; |
636 | } |
637 | |
638 | for_each_possible_cpu(cpu) { |
639 | info = mtk_cpu_dvfs_info_lookup(cpu); |
640 | if (info) |
641 | continue; |
642 | |
643 | info = devm_kzalloc(dev: &pdev->dev, size: sizeof(*info), GFP_KERNEL); |
644 | if (!info) { |
645 | ret = -ENOMEM; |
646 | goto release_dvfs_info_list; |
647 | } |
648 | |
649 | info->soc_data = data; |
650 | ret = mtk_cpu_dvfs_info_init(info, cpu); |
651 | if (ret) { |
652 | dev_err(&pdev->dev, |
653 | "failed to initialize dvfs info for cpu%d\n" , |
654 | cpu); |
655 | goto release_dvfs_info_list; |
656 | } |
657 | |
658 | list_add(new: &info->list_head, head: &dvfs_info_list); |
659 | } |
660 | |
661 | ret = cpufreq_register_driver(driver_data: &mtk_cpufreq_driver); |
662 | if (ret) { |
663 | dev_err(&pdev->dev, "failed to register mtk cpufreq driver\n" ); |
664 | goto release_dvfs_info_list; |
665 | } |
666 | |
667 | return 0; |
668 | |
669 | release_dvfs_info_list: |
670 | list_for_each_entry_safe(info, tmp, &dvfs_info_list, list_head) { |
671 | mtk_cpu_dvfs_info_release(info); |
672 | list_del(entry: &info->list_head); |
673 | } |
674 | |
675 | return ret; |
676 | } |
677 | |
678 | static struct platform_driver mtk_cpufreq_platdrv = { |
679 | .driver = { |
680 | .name = "mtk-cpufreq" , |
681 | }, |
682 | .probe = mtk_cpufreq_probe, |
683 | }; |
684 | |
685 | static const struct mtk_cpufreq_platform_data mt2701_platform_data = { |
686 | .min_volt_shift = 100000, |
687 | .max_volt_shift = 200000, |
688 | .proc_max_volt = 1150000, |
689 | .sram_min_volt = 0, |
690 | .sram_max_volt = 1150000, |
691 | .ccifreq_supported = false, |
692 | }; |
693 | |
694 | static const struct mtk_cpufreq_platform_data mt7622_platform_data = { |
695 | .min_volt_shift = 100000, |
696 | .max_volt_shift = 200000, |
697 | .proc_max_volt = 1350000, |
698 | .sram_min_volt = 0, |
699 | .sram_max_volt = 1350000, |
700 | .ccifreq_supported = false, |
701 | }; |
702 | |
703 | static const struct mtk_cpufreq_platform_data mt7623_platform_data = { |
704 | .min_volt_shift = 100000, |
705 | .max_volt_shift = 200000, |
706 | .proc_max_volt = 1300000, |
707 | .ccifreq_supported = false, |
708 | }; |
709 | |
710 | static const struct mtk_cpufreq_platform_data mt8183_platform_data = { |
711 | .min_volt_shift = 100000, |
712 | .max_volt_shift = 200000, |
713 | .proc_max_volt = 1150000, |
714 | .sram_min_volt = 0, |
715 | .sram_max_volt = 1150000, |
716 | .ccifreq_supported = true, |
717 | }; |
718 | |
719 | static const struct mtk_cpufreq_platform_data mt8186_platform_data = { |
720 | .min_volt_shift = 100000, |
721 | .max_volt_shift = 250000, |
722 | .proc_max_volt = 1118750, |
723 | .sram_min_volt = 850000, |
724 | .sram_max_volt = 1118750, |
725 | .ccifreq_supported = true, |
726 | }; |
727 | |
728 | static const struct mtk_cpufreq_platform_data mt8516_platform_data = { |
729 | .min_volt_shift = 100000, |
730 | .max_volt_shift = 200000, |
731 | .proc_max_volt = 1310000, |
732 | .sram_min_volt = 0, |
733 | .sram_max_volt = 1310000, |
734 | .ccifreq_supported = false, |
735 | }; |
736 | |
737 | /* List of machines supported by this driver */ |
738 | static const struct of_device_id mtk_cpufreq_machines[] __initconst = { |
739 | { .compatible = "mediatek,mt2701" , .data = &mt2701_platform_data }, |
740 | { .compatible = "mediatek,mt2712" , .data = &mt2701_platform_data }, |
741 | { .compatible = "mediatek,mt7622" , .data = &mt7622_platform_data }, |
742 | { .compatible = "mediatek,mt7623" , .data = &mt7623_platform_data }, |
743 | { .compatible = "mediatek,mt8167" , .data = &mt8516_platform_data }, |
744 | { .compatible = "mediatek,mt817x" , .data = &mt2701_platform_data }, |
745 | { .compatible = "mediatek,mt8173" , .data = &mt2701_platform_data }, |
746 | { .compatible = "mediatek,mt8176" , .data = &mt2701_platform_data }, |
747 | { .compatible = "mediatek,mt8183" , .data = &mt8183_platform_data }, |
748 | { .compatible = "mediatek,mt8186" , .data = &mt8186_platform_data }, |
749 | { .compatible = "mediatek,mt8365" , .data = &mt2701_platform_data }, |
750 | { .compatible = "mediatek,mt8516" , .data = &mt8516_platform_data }, |
751 | { } |
752 | }; |
753 | MODULE_DEVICE_TABLE(of, mtk_cpufreq_machines); |
754 | |
755 | static int __init mtk_cpufreq_driver_init(void) |
756 | { |
757 | struct device_node *np; |
758 | const struct of_device_id *match; |
759 | const struct mtk_cpufreq_platform_data *data; |
760 | int err; |
761 | |
762 | np = of_find_node_by_path(path: "/" ); |
763 | if (!np) |
764 | return -ENODEV; |
765 | |
766 | match = of_match_node(matches: mtk_cpufreq_machines, node: np); |
767 | of_node_put(node: np); |
768 | if (!match) { |
769 | pr_debug("Machine is not compatible with mtk-cpufreq\n" ); |
770 | return -ENODEV; |
771 | } |
772 | data = match->data; |
773 | |
774 | err = platform_driver_register(&mtk_cpufreq_platdrv); |
775 | if (err) |
776 | return err; |
777 | |
778 | /* |
779 | * Since there's no place to hold device registration code and no |
780 | * device tree based way to match cpufreq driver yet, both the driver |
781 | * and the device registration codes are put here to handle defer |
782 | * probing. |
783 | */ |
784 | cpufreq_pdev = platform_device_register_data(NULL, name: "mtk-cpufreq" , id: -1, |
785 | data, size: sizeof(*data)); |
786 | if (IS_ERR(ptr: cpufreq_pdev)) { |
787 | pr_err("failed to register mtk-cpufreq platform device\n" ); |
788 | platform_driver_unregister(&mtk_cpufreq_platdrv); |
789 | return PTR_ERR(ptr: cpufreq_pdev); |
790 | } |
791 | |
792 | return 0; |
793 | } |
794 | module_init(mtk_cpufreq_driver_init) |
795 | |
796 | static void __exit mtk_cpufreq_driver_exit(void) |
797 | { |
798 | platform_device_unregister(cpufreq_pdev); |
799 | platform_driver_unregister(&mtk_cpufreq_platdrv); |
800 | } |
801 | module_exit(mtk_cpufreq_driver_exit) |
802 | |
803 | MODULE_DESCRIPTION("MediaTek CPUFreq driver" ); |
804 | MODULE_AUTHOR("Pi-Cheng Chen <pi-cheng.chen@linaro.org>" ); |
805 | MODULE_LICENSE("GPL v2" ); |
806 | |