11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2501c574fSSean Wang /*
3501c574fSSean Wang  * Copyright (c) 2015 Linaro Ltd.
4501c574fSSean Wang  * Author: Pi-Cheng Chen <pi-cheng.chen@linaro.org>
5501c574fSSean Wang  */
6501c574fSSean Wang 
7501c574fSSean Wang #include <linux/clk.h>
8501c574fSSean Wang #include <linux/cpu.h>
9501c574fSSean Wang #include <linux/cpufreq.h>
10501c574fSSean Wang #include <linux/cpumask.h>
11501c574fSSean Wang #include <linux/module.h>
12501c574fSSean Wang #include <linux/of.h>
13501c574fSSean Wang #include <linux/platform_device.h>
14501c574fSSean Wang #include <linux/pm_opp.h>
15501c574fSSean Wang #include <linux/regulator/consumer.h>
16501c574fSSean Wang #include <linux/slab.h>
17501c574fSSean Wang #include <linux/thermal.h>
18501c574fSSean Wang 
19501c574fSSean Wang #define MIN_VOLT_SHIFT		(100000)
20501c574fSSean Wang #define MAX_VOLT_SHIFT		(200000)
21501c574fSSean Wang #define MAX_VOLT_LIMIT		(1150000)
22501c574fSSean Wang #define VOLT_TOL		(10000)
23501c574fSSean Wang 
24501c574fSSean Wang /*
25501c574fSSean Wang  * The struct mtk_cpu_dvfs_info holds necessary information for doing CPU DVFS
26501c574fSSean Wang  * on each CPU power/clock domain of Mediatek SoCs. Each CPU cluster in
27501c574fSSean Wang  * Mediatek SoCs has two voltage inputs, Vproc and Vsram. In some cases the two
28501c574fSSean Wang  * voltage inputs need to be controlled under a hardware limitation:
29501c574fSSean Wang  * 100mV < Vsram - Vproc < 200mV
30501c574fSSean Wang  *
31501c574fSSean Wang  * When scaling the clock frequency of a CPU clock domain, the clock source
32501c574fSSean Wang  * needs to be switched to another stable PLL clock temporarily until
33501c574fSSean Wang  * the original PLL becomes stable at target frequency.
34501c574fSSean Wang  */
35501c574fSSean Wang struct mtk_cpu_dvfs_info {
36501c574fSSean Wang 	struct cpumask cpus;
37501c574fSSean Wang 	struct device *cpu_dev;
38501c574fSSean Wang 	struct regulator *proc_reg;
39501c574fSSean Wang 	struct regulator *sram_reg;
40501c574fSSean Wang 	struct clk *cpu_clk;
41501c574fSSean Wang 	struct clk *inter_clk;
42501c574fSSean Wang 	struct list_head list_head;
43501c574fSSean Wang 	int intermediate_voltage;
44501c574fSSean Wang 	bool need_voltage_tracking;
45501c574fSSean Wang };
46501c574fSSean Wang 
47501c574fSSean Wang static LIST_HEAD(dvfs_info_list);
48501c574fSSean Wang 
49501c574fSSean Wang static struct mtk_cpu_dvfs_info *mtk_cpu_dvfs_info_lookup(int cpu)
50501c574fSSean Wang {
51501c574fSSean Wang 	struct mtk_cpu_dvfs_info *info;
52501c574fSSean Wang 
53501c574fSSean Wang 	list_for_each_entry(info, &dvfs_info_list, list_head) {
54501c574fSSean Wang 		if (cpumask_test_cpu(cpu, &info->cpus))
55501c574fSSean Wang 			return info;
56501c574fSSean Wang 	}
57501c574fSSean Wang 
58501c574fSSean Wang 	return NULL;
59501c574fSSean Wang }
60501c574fSSean Wang 
61501c574fSSean Wang static int mtk_cpufreq_voltage_tracking(struct mtk_cpu_dvfs_info *info,
62501c574fSSean Wang 					int new_vproc)
63501c574fSSean Wang {
64501c574fSSean Wang 	struct regulator *proc_reg = info->proc_reg;
65501c574fSSean Wang 	struct regulator *sram_reg = info->sram_reg;
66501c574fSSean Wang 	int old_vproc, old_vsram, new_vsram, vsram, vproc, ret;
67501c574fSSean Wang 
68501c574fSSean Wang 	old_vproc = regulator_get_voltage(proc_reg);
69501c574fSSean Wang 	if (old_vproc < 0) {
70501c574fSSean Wang 		pr_err("%s: invalid Vproc value: %d\n", __func__, old_vproc);
71501c574fSSean Wang 		return old_vproc;
72501c574fSSean Wang 	}
73501c574fSSean Wang 	/* Vsram should not exceed the maximum allowed voltage of SoC. */
74501c574fSSean Wang 	new_vsram = min(new_vproc + MIN_VOLT_SHIFT, MAX_VOLT_LIMIT);
75501c574fSSean Wang 
76501c574fSSean Wang 	if (old_vproc < new_vproc) {
77501c574fSSean Wang 		/*
78501c574fSSean Wang 		 * When scaling up voltages, Vsram and Vproc scale up step
79501c574fSSean Wang 		 * by step. At each step, set Vsram to (Vproc + 200mV) first,
80501c574fSSean Wang 		 * then set Vproc to (Vsram - 100mV).
81501c574fSSean Wang 		 * Keep doing it until Vsram and Vproc hit target voltages.
82501c574fSSean Wang 		 */
83501c574fSSean Wang 		do {
84501c574fSSean Wang 			old_vsram = regulator_get_voltage(sram_reg);
85501c574fSSean Wang 			if (old_vsram < 0) {
86501c574fSSean Wang 				pr_err("%s: invalid Vsram value: %d\n",
87501c574fSSean Wang 				       __func__, old_vsram);
88501c574fSSean Wang 				return old_vsram;
89501c574fSSean Wang 			}
90501c574fSSean Wang 			old_vproc = regulator_get_voltage(proc_reg);
91501c574fSSean Wang 			if (old_vproc < 0) {
92501c574fSSean Wang 				pr_err("%s: invalid Vproc value: %d\n",
93501c574fSSean Wang 				       __func__, old_vproc);
94501c574fSSean Wang 				return old_vproc;
95501c574fSSean Wang 			}
96501c574fSSean Wang 
97501c574fSSean Wang 			vsram = min(new_vsram, old_vproc + MAX_VOLT_SHIFT);
98501c574fSSean Wang 
99501c574fSSean Wang 			if (vsram + VOLT_TOL >= MAX_VOLT_LIMIT) {
100501c574fSSean Wang 				vsram = MAX_VOLT_LIMIT;
101501c574fSSean Wang 
102501c574fSSean Wang 				/*
103501c574fSSean Wang 				 * If the target Vsram hits the maximum voltage,
104501c574fSSean Wang 				 * try to set the exact voltage value first.
105501c574fSSean Wang 				 */
106501c574fSSean Wang 				ret = regulator_set_voltage(sram_reg, vsram,
107501c574fSSean Wang 							    vsram);
108501c574fSSean Wang 				if (ret)
109501c574fSSean Wang 					ret = regulator_set_voltage(sram_reg,
110501c574fSSean Wang 							vsram - VOLT_TOL,
111501c574fSSean Wang 							vsram);
112501c574fSSean Wang 
113501c574fSSean Wang 				vproc = new_vproc;
114501c574fSSean Wang 			} else {
115501c574fSSean Wang 				ret = regulator_set_voltage(sram_reg, vsram,
116501c574fSSean Wang 							    vsram + VOLT_TOL);
117501c574fSSean Wang 
118501c574fSSean Wang 				vproc = vsram - MIN_VOLT_SHIFT;
119501c574fSSean Wang 			}
120501c574fSSean Wang 			if (ret)
121501c574fSSean Wang 				return ret;
122501c574fSSean Wang 
123501c574fSSean Wang 			ret = regulator_set_voltage(proc_reg, vproc,
124501c574fSSean Wang 						    vproc + VOLT_TOL);
125501c574fSSean Wang 			if (ret) {
126501c574fSSean Wang 				regulator_set_voltage(sram_reg, old_vsram,
127501c574fSSean Wang 						      old_vsram);
128501c574fSSean Wang 				return ret;
129501c574fSSean Wang 			}
130501c574fSSean Wang 		} while (vproc < new_vproc || vsram < new_vsram);
131501c574fSSean Wang 	} else if (old_vproc > new_vproc) {
132501c574fSSean Wang 		/*
133501c574fSSean Wang 		 * When scaling down voltages, Vsram and Vproc scale down step
134501c574fSSean Wang 		 * by step. At each step, set Vproc to (Vsram - 200mV) first,
135501c574fSSean Wang 		 * then set Vproc to (Vproc + 100mV).
136501c574fSSean Wang 		 * Keep doing it until Vsram and Vproc hit target voltages.
137501c574fSSean Wang 		 */
138501c574fSSean Wang 		do {
139501c574fSSean Wang 			old_vproc = regulator_get_voltage(proc_reg);
140501c574fSSean Wang 			if (old_vproc < 0) {
141501c574fSSean Wang 				pr_err("%s: invalid Vproc value: %d\n",
142501c574fSSean Wang 				       __func__, old_vproc);
143501c574fSSean Wang 				return old_vproc;
144501c574fSSean Wang 			}
145501c574fSSean Wang 			old_vsram = regulator_get_voltage(sram_reg);
146501c574fSSean Wang 			if (old_vsram < 0) {
147501c574fSSean Wang 				pr_err("%s: invalid Vsram value: %d\n",
148501c574fSSean Wang 				       __func__, old_vsram);
149501c574fSSean Wang 				return old_vsram;
150501c574fSSean Wang 			}
151501c574fSSean Wang 
152501c574fSSean Wang 			vproc = max(new_vproc, old_vsram - MAX_VOLT_SHIFT);
153501c574fSSean Wang 			ret = regulator_set_voltage(proc_reg, vproc,
154501c574fSSean Wang 						    vproc + VOLT_TOL);
155501c574fSSean Wang 			if (ret)
156501c574fSSean Wang 				return ret;
157501c574fSSean Wang 
158501c574fSSean Wang 			if (vproc == new_vproc)
159501c574fSSean Wang 				vsram = new_vsram;
160501c574fSSean Wang 			else
161501c574fSSean Wang 				vsram = max(new_vsram, vproc + MIN_VOLT_SHIFT);
162501c574fSSean Wang 
163501c574fSSean Wang 			if (vsram + VOLT_TOL >= MAX_VOLT_LIMIT) {
164501c574fSSean Wang 				vsram = MAX_VOLT_LIMIT;
165501c574fSSean Wang 
166501c574fSSean Wang 				/*
167501c574fSSean Wang 				 * If the target Vsram hits the maximum voltage,
168501c574fSSean Wang 				 * try to set the exact voltage value first.
169501c574fSSean Wang 				 */
170501c574fSSean Wang 				ret = regulator_set_voltage(sram_reg, vsram,
171501c574fSSean Wang 							    vsram);
172501c574fSSean Wang 				if (ret)
173501c574fSSean Wang 					ret = regulator_set_voltage(sram_reg,
174501c574fSSean Wang 							vsram - VOLT_TOL,
175501c574fSSean Wang 							vsram);
176501c574fSSean Wang 			} else {
177501c574fSSean Wang 				ret = regulator_set_voltage(sram_reg, vsram,
178501c574fSSean Wang 							    vsram + VOLT_TOL);
179501c574fSSean Wang 			}
180501c574fSSean Wang 
181501c574fSSean Wang 			if (ret) {
182501c574fSSean Wang 				regulator_set_voltage(proc_reg, old_vproc,
183501c574fSSean Wang 						      old_vproc);
184501c574fSSean Wang 				return ret;
185501c574fSSean Wang 			}
186501c574fSSean Wang 		} while (vproc > new_vproc + VOLT_TOL ||
187501c574fSSean Wang 			 vsram > new_vsram + VOLT_TOL);
188501c574fSSean Wang 	}
189501c574fSSean Wang 
190501c574fSSean Wang 	return 0;
191501c574fSSean Wang }
192501c574fSSean Wang 
193501c574fSSean Wang static int mtk_cpufreq_set_voltage(struct mtk_cpu_dvfs_info *info, int vproc)
194501c574fSSean Wang {
195501c574fSSean Wang 	if (info->need_voltage_tracking)
196501c574fSSean Wang 		return mtk_cpufreq_voltage_tracking(info, vproc);
197501c574fSSean Wang 	else
198501c574fSSean Wang 		return regulator_set_voltage(info->proc_reg, vproc,
199501c574fSSean Wang 					     vproc + VOLT_TOL);
200501c574fSSean Wang }
201501c574fSSean Wang 
202501c574fSSean Wang static int mtk_cpufreq_set_target(struct cpufreq_policy *policy,
203501c574fSSean Wang 				  unsigned int index)
204501c574fSSean Wang {
205501c574fSSean Wang 	struct cpufreq_frequency_table *freq_table = policy->freq_table;
206501c574fSSean Wang 	struct clk *cpu_clk = policy->clk;
207501c574fSSean Wang 	struct clk *armpll = clk_get_parent(cpu_clk);
208501c574fSSean Wang 	struct mtk_cpu_dvfs_info *info = policy->driver_data;
209501c574fSSean Wang 	struct device *cpu_dev = info->cpu_dev;
210501c574fSSean Wang 	struct dev_pm_opp *opp;
211501c574fSSean Wang 	long freq_hz, old_freq_hz;
212501c574fSSean Wang 	int vproc, old_vproc, inter_vproc, target_vproc, ret;
213501c574fSSean Wang 
214501c574fSSean Wang 	inter_vproc = info->intermediate_voltage;
215501c574fSSean Wang 
216501c574fSSean Wang 	old_freq_hz = clk_get_rate(cpu_clk);
217501c574fSSean Wang 	old_vproc = regulator_get_voltage(info->proc_reg);
218501c574fSSean Wang 	if (old_vproc < 0) {
219501c574fSSean Wang 		pr_err("%s: invalid Vproc value: %d\n", __func__, old_vproc);
220501c574fSSean Wang 		return old_vproc;
221501c574fSSean Wang 	}
222501c574fSSean Wang 
223501c574fSSean Wang 	freq_hz = freq_table[index].frequency * 1000;
224501c574fSSean Wang 
225501c574fSSean Wang 	opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz);
226501c574fSSean Wang 	if (IS_ERR(opp)) {
227501c574fSSean Wang 		pr_err("cpu%d: failed to find OPP for %ld\n",
228501c574fSSean Wang 		       policy->cpu, freq_hz);
229501c574fSSean Wang 		return PTR_ERR(opp);
230501c574fSSean Wang 	}
231501c574fSSean Wang 	vproc = dev_pm_opp_get_voltage(opp);
232501c574fSSean Wang 	dev_pm_opp_put(opp);
233501c574fSSean Wang 
234501c574fSSean Wang 	/*
235501c574fSSean Wang 	 * If the new voltage or the intermediate voltage is higher than the
236501c574fSSean Wang 	 * current voltage, scale up voltage first.
237501c574fSSean Wang 	 */
238501c574fSSean Wang 	target_vproc = (inter_vproc > vproc) ? inter_vproc : vproc;
239501c574fSSean Wang 	if (old_vproc < target_vproc) {
240501c574fSSean Wang 		ret = mtk_cpufreq_set_voltage(info, target_vproc);
241501c574fSSean Wang 		if (ret) {
242501c574fSSean Wang 			pr_err("cpu%d: failed to scale up voltage!\n",
243501c574fSSean Wang 			       policy->cpu);
244501c574fSSean Wang 			mtk_cpufreq_set_voltage(info, old_vproc);
245501c574fSSean Wang 			return ret;
246501c574fSSean Wang 		}
247501c574fSSean Wang 	}
248501c574fSSean Wang 
249501c574fSSean Wang 	/* Reparent the CPU clock to intermediate clock. */
250501c574fSSean Wang 	ret = clk_set_parent(cpu_clk, info->inter_clk);
251501c574fSSean Wang 	if (ret) {
252501c574fSSean Wang 		pr_err("cpu%d: failed to re-parent cpu clock!\n",
253501c574fSSean Wang 		       policy->cpu);
254501c574fSSean Wang 		mtk_cpufreq_set_voltage(info, old_vproc);
255501c574fSSean Wang 		WARN_ON(1);
256501c574fSSean Wang 		return ret;
257501c574fSSean Wang 	}
258501c574fSSean Wang 
259501c574fSSean Wang 	/* Set the original PLL to target rate. */
260501c574fSSean Wang 	ret = clk_set_rate(armpll, freq_hz);
261501c574fSSean Wang 	if (ret) {
262501c574fSSean Wang 		pr_err("cpu%d: failed to scale cpu clock rate!\n",
263501c574fSSean Wang 		       policy->cpu);
264501c574fSSean Wang 		clk_set_parent(cpu_clk, armpll);
265501c574fSSean Wang 		mtk_cpufreq_set_voltage(info, old_vproc);
266501c574fSSean Wang 		return ret;
267501c574fSSean Wang 	}
268501c574fSSean Wang 
269501c574fSSean Wang 	/* Set parent of CPU clock back to the original PLL. */
270501c574fSSean Wang 	ret = clk_set_parent(cpu_clk, armpll);
271501c574fSSean Wang 	if (ret) {
272501c574fSSean Wang 		pr_err("cpu%d: failed to re-parent cpu clock!\n",
273501c574fSSean Wang 		       policy->cpu);
274501c574fSSean Wang 		mtk_cpufreq_set_voltage(info, inter_vproc);
275501c574fSSean Wang 		WARN_ON(1);
276501c574fSSean Wang 		return ret;
277501c574fSSean Wang 	}
278501c574fSSean Wang 
279501c574fSSean Wang 	/*
280501c574fSSean Wang 	 * If the new voltage is lower than the intermediate voltage or the
281501c574fSSean Wang 	 * original voltage, scale down to the new voltage.
282501c574fSSean Wang 	 */
283501c574fSSean Wang 	if (vproc < inter_vproc || vproc < old_vproc) {
284501c574fSSean Wang 		ret = mtk_cpufreq_set_voltage(info, vproc);
285501c574fSSean Wang 		if (ret) {
286501c574fSSean Wang 			pr_err("cpu%d: failed to scale down voltage!\n",
287501c574fSSean Wang 			       policy->cpu);
288501c574fSSean Wang 			clk_set_parent(cpu_clk, info->inter_clk);
289501c574fSSean Wang 			clk_set_rate(armpll, old_freq_hz);
290501c574fSSean Wang 			clk_set_parent(cpu_clk, armpll);
291501c574fSSean Wang 			return ret;
292501c574fSSean Wang 		}
293501c574fSSean Wang 	}
294501c574fSSean Wang 
295501c574fSSean Wang 	return 0;
296501c574fSSean Wang }
297501c574fSSean Wang 
298501c574fSSean Wang #define DYNAMIC_POWER "dynamic-power-coefficient"
299501c574fSSean Wang 
300501c574fSSean Wang static int mtk_cpu_dvfs_info_init(struct mtk_cpu_dvfs_info *info, int cpu)
301501c574fSSean Wang {
302501c574fSSean Wang 	struct device *cpu_dev;
303501c574fSSean Wang 	struct regulator *proc_reg = ERR_PTR(-ENODEV);
304501c574fSSean Wang 	struct regulator *sram_reg = ERR_PTR(-ENODEV);
305501c574fSSean Wang 	struct clk *cpu_clk = ERR_PTR(-ENODEV);
306501c574fSSean Wang 	struct clk *inter_clk = ERR_PTR(-ENODEV);
307501c574fSSean Wang 	struct dev_pm_opp *opp;
308501c574fSSean Wang 	unsigned long rate;
309501c574fSSean Wang 	int ret;
310501c574fSSean Wang 
311501c574fSSean Wang 	cpu_dev = get_cpu_device(cpu);
312501c574fSSean Wang 	if (!cpu_dev) {
313501c574fSSean Wang 		pr_err("failed to get cpu%d device\n", cpu);
314501c574fSSean Wang 		return -ENODEV;
315501c574fSSean Wang 	}
316501c574fSSean Wang 
317501c574fSSean Wang 	cpu_clk = clk_get(cpu_dev, "cpu");
318501c574fSSean Wang 	if (IS_ERR(cpu_clk)) {
319501c574fSSean Wang 		if (PTR_ERR(cpu_clk) == -EPROBE_DEFER)
320501c574fSSean Wang 			pr_warn("cpu clk for cpu%d not ready, retry.\n", cpu);
321501c574fSSean Wang 		else
322501c574fSSean Wang 			pr_err("failed to get cpu clk for cpu%d\n", cpu);
323501c574fSSean Wang 
324501c574fSSean Wang 		ret = PTR_ERR(cpu_clk);
325501c574fSSean Wang 		return ret;
326501c574fSSean Wang 	}
327501c574fSSean Wang 
328501c574fSSean Wang 	inter_clk = clk_get(cpu_dev, "intermediate");
329501c574fSSean Wang 	if (IS_ERR(inter_clk)) {
330501c574fSSean Wang 		if (PTR_ERR(inter_clk) == -EPROBE_DEFER)
331501c574fSSean Wang 			pr_warn("intermediate clk for cpu%d not ready, retry.\n",
332501c574fSSean Wang 				cpu);
333501c574fSSean Wang 		else
334501c574fSSean Wang 			pr_err("failed to get intermediate clk for cpu%d\n",
335501c574fSSean Wang 			       cpu);
336501c574fSSean Wang 
337501c574fSSean Wang 		ret = PTR_ERR(inter_clk);
338501c574fSSean Wang 		goto out_free_resources;
339501c574fSSean Wang 	}
340501c574fSSean Wang 
341dce0bb84SAndrew-sh.Cheng 	proc_reg = regulator_get_optional(cpu_dev, "proc");
342501c574fSSean Wang 	if (IS_ERR(proc_reg)) {
343501c574fSSean Wang 		if (PTR_ERR(proc_reg) == -EPROBE_DEFER)
344501c574fSSean Wang 			pr_warn("proc regulator for cpu%d not ready, retry.\n",
345501c574fSSean Wang 				cpu);
346501c574fSSean Wang 		else
347501c574fSSean Wang 			pr_err("failed to get proc regulator for cpu%d\n",
348501c574fSSean Wang 			       cpu);
349501c574fSSean Wang 
350501c574fSSean Wang 		ret = PTR_ERR(proc_reg);
351501c574fSSean Wang 		goto out_free_resources;
352501c574fSSean Wang 	}
353501c574fSSean Wang 
354501c574fSSean Wang 	/* Both presence and absence of sram regulator are valid cases. */
355501c574fSSean Wang 	sram_reg = regulator_get_exclusive(cpu_dev, "sram");
356501c574fSSean Wang 
357501c574fSSean Wang 	/* Get OPP-sharing information from "operating-points-v2" bindings */
358501c574fSSean Wang 	ret = dev_pm_opp_of_get_sharing_cpus(cpu_dev, &info->cpus);
359501c574fSSean Wang 	if (ret) {
360501c574fSSean Wang 		pr_err("failed to get OPP-sharing information for cpu%d\n",
361501c574fSSean Wang 		       cpu);
362501c574fSSean Wang 		goto out_free_resources;
363501c574fSSean Wang 	}
364501c574fSSean Wang 
365501c574fSSean Wang 	ret = dev_pm_opp_of_cpumask_add_table(&info->cpus);
366501c574fSSean Wang 	if (ret) {
367501c574fSSean Wang 		pr_warn("no OPP table for cpu%d\n", cpu);
368501c574fSSean Wang 		goto out_free_resources;
369501c574fSSean Wang 	}
370501c574fSSean Wang 
371501c574fSSean Wang 	/* Search a safe voltage for intermediate frequency. */
372501c574fSSean Wang 	rate = clk_get_rate(inter_clk);
373501c574fSSean Wang 	opp = dev_pm_opp_find_freq_ceil(cpu_dev, &rate);
374501c574fSSean Wang 	if (IS_ERR(opp)) {
375501c574fSSean Wang 		pr_err("failed to get intermediate opp for cpu%d\n", cpu);
376501c574fSSean Wang 		ret = PTR_ERR(opp);
377501c574fSSean Wang 		goto out_free_opp_table;
378501c574fSSean Wang 	}
379501c574fSSean Wang 	info->intermediate_voltage = dev_pm_opp_get_voltage(opp);
380501c574fSSean Wang 	dev_pm_opp_put(opp);
381501c574fSSean Wang 
382501c574fSSean Wang 	info->cpu_dev = cpu_dev;
383501c574fSSean Wang 	info->proc_reg = proc_reg;
384501c574fSSean Wang 	info->sram_reg = IS_ERR(sram_reg) ? NULL : sram_reg;
385501c574fSSean Wang 	info->cpu_clk = cpu_clk;
386501c574fSSean Wang 	info->inter_clk = inter_clk;
387501c574fSSean Wang 
388501c574fSSean Wang 	/*
389501c574fSSean Wang 	 * If SRAM regulator is present, software "voltage tracking" is needed
390501c574fSSean Wang 	 * for this CPU power domain.
391501c574fSSean Wang 	 */
392501c574fSSean Wang 	info->need_voltage_tracking = !IS_ERR(sram_reg);
393501c574fSSean Wang 
394501c574fSSean Wang 	return 0;
395501c574fSSean Wang 
396501c574fSSean Wang out_free_opp_table:
397501c574fSSean Wang 	dev_pm_opp_of_cpumask_remove_table(&info->cpus);
398501c574fSSean Wang 
399501c574fSSean Wang out_free_resources:
400501c574fSSean Wang 	if (!IS_ERR(proc_reg))
401501c574fSSean Wang 		regulator_put(proc_reg);
402501c574fSSean Wang 	if (!IS_ERR(sram_reg))
403501c574fSSean Wang 		regulator_put(sram_reg);
404501c574fSSean Wang 	if (!IS_ERR(cpu_clk))
405501c574fSSean Wang 		clk_put(cpu_clk);
406501c574fSSean Wang 	if (!IS_ERR(inter_clk))
407501c574fSSean Wang 		clk_put(inter_clk);
408501c574fSSean Wang 
409501c574fSSean Wang 	return ret;
410501c574fSSean Wang }
411501c574fSSean Wang 
412501c574fSSean Wang static void mtk_cpu_dvfs_info_release(struct mtk_cpu_dvfs_info *info)
413501c574fSSean Wang {
414501c574fSSean Wang 	if (!IS_ERR(info->proc_reg))
415501c574fSSean Wang 		regulator_put(info->proc_reg);
416501c574fSSean Wang 	if (!IS_ERR(info->sram_reg))
417501c574fSSean Wang 		regulator_put(info->sram_reg);
418501c574fSSean Wang 	if (!IS_ERR(info->cpu_clk))
419501c574fSSean Wang 		clk_put(info->cpu_clk);
420501c574fSSean Wang 	if (!IS_ERR(info->inter_clk))
421501c574fSSean Wang 		clk_put(info->inter_clk);
422501c574fSSean Wang 
423501c574fSSean Wang 	dev_pm_opp_of_cpumask_remove_table(&info->cpus);
424501c574fSSean Wang }
425501c574fSSean Wang 
426501c574fSSean Wang static int mtk_cpufreq_init(struct cpufreq_policy *policy)
427501c574fSSean Wang {
428501c574fSSean Wang 	struct mtk_cpu_dvfs_info *info;
429501c574fSSean Wang 	struct cpufreq_frequency_table *freq_table;
430501c574fSSean Wang 	int ret;
431501c574fSSean Wang 
432501c574fSSean Wang 	info = mtk_cpu_dvfs_info_lookup(policy->cpu);
433501c574fSSean Wang 	if (!info) {
434501c574fSSean Wang 		pr_err("dvfs info for cpu%d is not initialized.\n",
435501c574fSSean Wang 		       policy->cpu);
436501c574fSSean Wang 		return -EINVAL;
437501c574fSSean Wang 	}
438501c574fSSean Wang 
439501c574fSSean Wang 	ret = dev_pm_opp_init_cpufreq_table(info->cpu_dev, &freq_table);
440501c574fSSean Wang 	if (ret) {
441501c574fSSean Wang 		pr_err("failed to init cpufreq table for cpu%d: %d\n",
442501c574fSSean Wang 		       policy->cpu, ret);
443501c574fSSean Wang 		return ret;
444501c574fSSean Wang 	}
445501c574fSSean Wang 
446501c574fSSean Wang 	cpumask_copy(policy->cpus, &info->cpus);
447b563afbaSViresh Kumar 	policy->freq_table = freq_table;
448501c574fSSean Wang 	policy->driver_data = info;
449501c574fSSean Wang 	policy->clk = info->cpu_clk;
450501c574fSSean Wang 
4511058d1efSMatthias Kaehlcke 	dev_pm_opp_of_register_em(policy->cpus);
4521058d1efSMatthias Kaehlcke 
453501c574fSSean Wang 	return 0;
454501c574fSSean Wang }
455501c574fSSean Wang 
456501c574fSSean Wang static int mtk_cpufreq_exit(struct cpufreq_policy *policy)
457501c574fSSean Wang {
458501c574fSSean Wang 	struct mtk_cpu_dvfs_info *info = policy->driver_data;
459501c574fSSean Wang 
460501c574fSSean Wang 	dev_pm_opp_free_cpufreq_table(info->cpu_dev, &policy->freq_table);
461501c574fSSean Wang 
462501c574fSSean Wang 	return 0;
463501c574fSSean Wang }
464501c574fSSean Wang 
465862e0104SSean Wang static struct cpufreq_driver mtk_cpufreq_driver = {
466501c574fSSean Wang 	.flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK |
4670db60d6bSAmit Kucheria 		 CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
4680db60d6bSAmit Kucheria 		 CPUFREQ_IS_COOLING_DEV,
469501c574fSSean Wang 	.verify = cpufreq_generic_frequency_table_verify,
470501c574fSSean Wang 	.target_index = mtk_cpufreq_set_target,
471501c574fSSean Wang 	.get = cpufreq_generic_get,
472501c574fSSean Wang 	.init = mtk_cpufreq_init,
473501c574fSSean Wang 	.exit = mtk_cpufreq_exit,
474501c574fSSean Wang 	.name = "mtk-cpufreq",
475501c574fSSean Wang 	.attr = cpufreq_generic_attr,
476501c574fSSean Wang };
477501c574fSSean Wang 
478862e0104SSean Wang static int mtk_cpufreq_probe(struct platform_device *pdev)
479501c574fSSean Wang {
480501c574fSSean Wang 	struct mtk_cpu_dvfs_info *info, *tmp;
481501c574fSSean Wang 	int cpu, ret;
482501c574fSSean Wang 
483501c574fSSean Wang 	for_each_possible_cpu(cpu) {
484501c574fSSean Wang 		info = mtk_cpu_dvfs_info_lookup(cpu);
485501c574fSSean Wang 		if (info)
486501c574fSSean Wang 			continue;
487501c574fSSean Wang 
488501c574fSSean Wang 		info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
489501c574fSSean Wang 		if (!info) {
490501c574fSSean Wang 			ret = -ENOMEM;
491501c574fSSean Wang 			goto release_dvfs_info_list;
492501c574fSSean Wang 		}
493501c574fSSean Wang 
494501c574fSSean Wang 		ret = mtk_cpu_dvfs_info_init(info, cpu);
495501c574fSSean Wang 		if (ret) {
496501c574fSSean Wang 			dev_err(&pdev->dev,
497501c574fSSean Wang 				"failed to initialize dvfs info for cpu%d\n",
498501c574fSSean Wang 				cpu);
499501c574fSSean Wang 			goto release_dvfs_info_list;
500501c574fSSean Wang 		}
501501c574fSSean Wang 
502501c574fSSean Wang 		list_add(&info->list_head, &dvfs_info_list);
503501c574fSSean Wang 	}
504501c574fSSean Wang 
505862e0104SSean Wang 	ret = cpufreq_register_driver(&mtk_cpufreq_driver);
506501c574fSSean Wang 	if (ret) {
507501c574fSSean Wang 		dev_err(&pdev->dev, "failed to register mtk cpufreq driver\n");
508501c574fSSean Wang 		goto release_dvfs_info_list;
509501c574fSSean Wang 	}
510501c574fSSean Wang 
511501c574fSSean Wang 	return 0;
512501c574fSSean Wang 
513501c574fSSean Wang release_dvfs_info_list:
514501c574fSSean Wang 	list_for_each_entry_safe(info, tmp, &dvfs_info_list, list_head) {
515501c574fSSean Wang 		mtk_cpu_dvfs_info_release(info);
516501c574fSSean Wang 		list_del(&info->list_head);
517501c574fSSean Wang 	}
518501c574fSSean Wang 
519501c574fSSean Wang 	return ret;
520501c574fSSean Wang }
521501c574fSSean Wang 
522862e0104SSean Wang static struct platform_driver mtk_cpufreq_platdrv = {
523501c574fSSean Wang 	.driver = {
524862e0104SSean Wang 		.name	= "mtk-cpufreq",
525501c574fSSean Wang 	},
526862e0104SSean Wang 	.probe		= mtk_cpufreq_probe,
527501c574fSSean Wang };
528501c574fSSean Wang 
529501c574fSSean Wang /* List of machines supported by this driver */
530862e0104SSean Wang static const struct of_device_id mtk_cpufreq_machines[] __initconst = {
531501c574fSSean Wang 	{ .compatible = "mediatek,mt2701", },
532a9596dbcSAndrew-sh Cheng 	{ .compatible = "mediatek,mt2712", },
533ccc03d86SSean Wang 	{ .compatible = "mediatek,mt7622", },
534501c574fSSean Wang 	{ .compatible = "mediatek,mt7623", },
535501c574fSSean Wang 	{ .compatible = "mediatek,mt817x", },
536501c574fSSean Wang 	{ .compatible = "mediatek,mt8173", },
537501c574fSSean Wang 	{ .compatible = "mediatek,mt8176", },
538683df830SFabien Parent 	{ .compatible = "mediatek,mt8516", },
539501c574fSSean Wang 
540501c574fSSean Wang 	{ }
541501c574fSSean Wang };
542501c574fSSean Wang 
543862e0104SSean Wang static int __init mtk_cpufreq_driver_init(void)
544501c574fSSean Wang {
545501c574fSSean Wang 	struct device_node *np;
546501c574fSSean Wang 	const struct of_device_id *match;
547501c574fSSean Wang 	struct platform_device *pdev;
548501c574fSSean Wang 	int err;
549501c574fSSean Wang 
550501c574fSSean Wang 	np = of_find_node_by_path("/");
551501c574fSSean Wang 	if (!np)
552501c574fSSean Wang 		return -ENODEV;
553501c574fSSean Wang 
554862e0104SSean Wang 	match = of_match_node(mtk_cpufreq_machines, np);
555501c574fSSean Wang 	of_node_put(np);
556501c574fSSean Wang 	if (!match) {
557cb8bd2ffSViresh Kumar 		pr_debug("Machine is not compatible with mtk-cpufreq\n");
558501c574fSSean Wang 		return -ENODEV;
559501c574fSSean Wang 	}
560501c574fSSean Wang 
561862e0104SSean Wang 	err = platform_driver_register(&mtk_cpufreq_platdrv);
562501c574fSSean Wang 	if (err)
563501c574fSSean Wang 		return err;
564501c574fSSean Wang 
565501c574fSSean Wang 	/*
566501c574fSSean Wang 	 * Since there's no place to hold device registration code and no
567501c574fSSean Wang 	 * device tree based way to match cpufreq driver yet, both the driver
568501c574fSSean Wang 	 * and the device registration codes are put here to handle defer
569501c574fSSean Wang 	 * probing.
570501c574fSSean Wang 	 */
571862e0104SSean Wang 	pdev = platform_device_register_simple("mtk-cpufreq", -1, NULL, 0);
572501c574fSSean Wang 	if (IS_ERR(pdev)) {
573501c574fSSean Wang 		pr_err("failed to register mtk-cpufreq platform device\n");
574501c574fSSean Wang 		return PTR_ERR(pdev);
575501c574fSSean Wang 	}
576501c574fSSean Wang 
577501c574fSSean Wang 	return 0;
578501c574fSSean Wang }
579862e0104SSean Wang device_initcall(mtk_cpufreq_driver_init);
5807e8a09e0SJesse Chan 
5817e8a09e0SJesse Chan MODULE_DESCRIPTION("MediaTek CPUFreq driver");
5827e8a09e0SJesse Chan MODULE_AUTHOR("Pi-Cheng Chen <pi-cheng.chen@linaro.org>");
5837e8a09e0SJesse Chan MODULE_LICENSE("GPL v2");
584