]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blob - drivers/cpufreq/imx6q-cpufreq.c
cpufreq: Implement light weight ->target_index() routine
[mirror_ubuntu-artful-kernel.git] / drivers / cpufreq / imx6q-cpufreq.c
1 /*
2 * Copyright (C) 2013 Freescale Semiconductor, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8
9 #include <linux/clk.h>
10 #include <linux/cpu.h>
11 #include <linux/cpufreq.h>
12 #include <linux/delay.h>
13 #include <linux/err.h>
14 #include <linux/module.h>
15 #include <linux/of.h>
16 #include <linux/pm_opp.h>
17 #include <linux/platform_device.h>
18 #include <linux/regulator/consumer.h>
19
20 #define PU_SOC_VOLTAGE_NORMAL 1250000
21 #define PU_SOC_VOLTAGE_HIGH 1275000
22 #define FREQ_1P2_GHZ 1200000000
23
24 static struct regulator *arm_reg;
25 static struct regulator *pu_reg;
26 static struct regulator *soc_reg;
27
28 static struct clk *arm_clk;
29 static struct clk *pll1_sys_clk;
30 static struct clk *pll1_sw_clk;
31 static struct clk *step_clk;
32 static struct clk *pll2_pfd2_396m_clk;
33
34 static struct device *cpu_dev;
35 static struct cpufreq_frequency_table *freq_table;
36 static unsigned int transition_latency;
37
38 static unsigned int imx6q_get_speed(unsigned int cpu)
39 {
40 return clk_get_rate(arm_clk) / 1000;
41 }
42
43 static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index)
44 {
45 struct cpufreq_freqs freqs;
46 struct dev_pm_opp *opp;
47 unsigned long freq_hz, volt, volt_old;
48 int ret;
49
50 freqs.new = freq_table[index].frequency;
51 freq_hz = freqs.new * 1000;
52 freqs.old = clk_get_rate(arm_clk) / 1000;
53
54 rcu_read_lock();
55 opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz);
56 if (IS_ERR(opp)) {
57 rcu_read_unlock();
58 dev_err(cpu_dev, "failed to find OPP for %ld\n", freq_hz);
59 return PTR_ERR(opp);
60 }
61
62 volt = dev_pm_opp_get_voltage(opp);
63 rcu_read_unlock();
64 volt_old = regulator_get_voltage(arm_reg);
65
66 dev_dbg(cpu_dev, "%u MHz, %ld mV --> %u MHz, %ld mV\n",
67 freqs.old / 1000, volt_old / 1000,
68 freqs.new / 1000, volt / 1000);
69
70 cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
71
72 /* scaling up? scale voltage before frequency */
73 if (freqs.new > freqs.old) {
74 ret = regulator_set_voltage_tol(arm_reg, volt, 0);
75 if (ret) {
76 dev_err(cpu_dev,
77 "failed to scale vddarm up: %d\n", ret);
78 freqs.new = freqs.old;
79 goto post_notify;
80 }
81
82 /*
83 * Need to increase vddpu and vddsoc for safety
84 * if we are about to run at 1.2 GHz.
85 */
86 if (freqs.new == FREQ_1P2_GHZ / 1000) {
87 regulator_set_voltage_tol(pu_reg,
88 PU_SOC_VOLTAGE_HIGH, 0);
89 regulator_set_voltage_tol(soc_reg,
90 PU_SOC_VOLTAGE_HIGH, 0);
91 }
92 }
93
94 /*
95 * The setpoints are selected per PLL/PDF frequencies, so we need to
96 * reprogram PLL for frequency scaling. The procedure of reprogramming
97 * PLL1 is as below.
98 *
99 * - Enable pll2_pfd2_396m_clk and reparent pll1_sw_clk to it
100 * - Reprogram pll1_sys_clk and reparent pll1_sw_clk back to it
101 * - Disable pll2_pfd2_396m_clk
102 */
103 clk_set_parent(step_clk, pll2_pfd2_396m_clk);
104 clk_set_parent(pll1_sw_clk, step_clk);
105 if (freq_hz > clk_get_rate(pll2_pfd2_396m_clk)) {
106 clk_set_rate(pll1_sys_clk, freqs.new * 1000);
107 clk_set_parent(pll1_sw_clk, pll1_sys_clk);
108 }
109
110 /* Ensure the arm clock divider is what we expect */
111 ret = clk_set_rate(arm_clk, freqs.new * 1000);
112 if (ret) {
113 dev_err(cpu_dev, "failed to set clock rate: %d\n", ret);
114 regulator_set_voltage_tol(arm_reg, volt_old, 0);
115 freqs.new = freqs.old;
116 goto post_notify;
117 }
118
119 /* scaling down? scale voltage after frequency */
120 if (freqs.new < freqs.old) {
121 ret = regulator_set_voltage_tol(arm_reg, volt, 0);
122 if (ret) {
123 dev_warn(cpu_dev,
124 "failed to scale vddarm down: %d\n", ret);
125 ret = 0;
126 }
127
128 if (freqs.old == FREQ_1P2_GHZ / 1000) {
129 regulator_set_voltage_tol(pu_reg,
130 PU_SOC_VOLTAGE_NORMAL, 0);
131 regulator_set_voltage_tol(soc_reg,
132 PU_SOC_VOLTAGE_NORMAL, 0);
133 }
134 }
135
136 post_notify:
137 cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
138
139 return ret;
140 }
141
142 static int imx6q_cpufreq_init(struct cpufreq_policy *policy)
143 {
144 return cpufreq_generic_init(policy, freq_table, transition_latency);
145 }
146
147 static struct cpufreq_driver imx6q_cpufreq_driver = {
148 .verify = cpufreq_generic_frequency_table_verify,
149 .target_index = imx6q_set_target,
150 .get = imx6q_get_speed,
151 .init = imx6q_cpufreq_init,
152 .exit = cpufreq_generic_exit,
153 .name = "imx6q-cpufreq",
154 .attr = cpufreq_generic_attr,
155 };
156
157 static int imx6q_cpufreq_probe(struct platform_device *pdev)
158 {
159 struct device_node *np;
160 struct dev_pm_opp *opp;
161 unsigned long min_volt, max_volt;
162 int num, ret;
163
164 cpu_dev = get_cpu_device(0);
165 if (!cpu_dev) {
166 pr_err("failed to get cpu0 device\n");
167 return -ENODEV;
168 }
169
170 np = of_node_get(cpu_dev->of_node);
171 if (!np) {
172 dev_err(cpu_dev, "failed to find cpu0 node\n");
173 return -ENOENT;
174 }
175
176 arm_clk = devm_clk_get(cpu_dev, "arm");
177 pll1_sys_clk = devm_clk_get(cpu_dev, "pll1_sys");
178 pll1_sw_clk = devm_clk_get(cpu_dev, "pll1_sw");
179 step_clk = devm_clk_get(cpu_dev, "step");
180 pll2_pfd2_396m_clk = devm_clk_get(cpu_dev, "pll2_pfd2_396m");
181 if (IS_ERR(arm_clk) || IS_ERR(pll1_sys_clk) || IS_ERR(pll1_sw_clk) ||
182 IS_ERR(step_clk) || IS_ERR(pll2_pfd2_396m_clk)) {
183 dev_err(cpu_dev, "failed to get clocks\n");
184 ret = -ENOENT;
185 goto put_node;
186 }
187
188 arm_reg = devm_regulator_get(cpu_dev, "arm");
189 pu_reg = devm_regulator_get(cpu_dev, "pu");
190 soc_reg = devm_regulator_get(cpu_dev, "soc");
191 if (IS_ERR(arm_reg) || IS_ERR(pu_reg) || IS_ERR(soc_reg)) {
192 dev_err(cpu_dev, "failed to get regulators\n");
193 ret = -ENOENT;
194 goto put_node;
195 }
196
197 /* We expect an OPP table supplied by platform */
198 num = dev_pm_opp_get_opp_count(cpu_dev);
199 if (num < 0) {
200 ret = num;
201 dev_err(cpu_dev, "no OPP table is found: %d\n", ret);
202 goto put_node;
203 }
204
205 ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
206 if (ret) {
207 dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
208 goto put_node;
209 }
210
211 if (of_property_read_u32(np, "clock-latency", &transition_latency))
212 transition_latency = CPUFREQ_ETERNAL;
213
214 /*
215 * OPP is maintained in order of increasing frequency, and
216 * freq_table initialised from OPP is therefore sorted in the
217 * same order.
218 */
219 rcu_read_lock();
220 opp = dev_pm_opp_find_freq_exact(cpu_dev,
221 freq_table[0].frequency * 1000, true);
222 min_volt = dev_pm_opp_get_voltage(opp);
223 opp = dev_pm_opp_find_freq_exact(cpu_dev,
224 freq_table[--num].frequency * 1000, true);
225 max_volt = dev_pm_opp_get_voltage(opp);
226 rcu_read_unlock();
227 ret = regulator_set_voltage_time(arm_reg, min_volt, max_volt);
228 if (ret > 0)
229 transition_latency += ret * 1000;
230
231 /* Count vddpu and vddsoc latency in for 1.2 GHz support */
232 if (freq_table[num].frequency == FREQ_1P2_GHZ / 1000) {
233 ret = regulator_set_voltage_time(pu_reg, PU_SOC_VOLTAGE_NORMAL,
234 PU_SOC_VOLTAGE_HIGH);
235 if (ret > 0)
236 transition_latency += ret * 1000;
237 ret = regulator_set_voltage_time(soc_reg, PU_SOC_VOLTAGE_NORMAL,
238 PU_SOC_VOLTAGE_HIGH);
239 if (ret > 0)
240 transition_latency += ret * 1000;
241 }
242
243 ret = cpufreq_register_driver(&imx6q_cpufreq_driver);
244 if (ret) {
245 dev_err(cpu_dev, "failed register driver: %d\n", ret);
246 goto free_freq_table;
247 }
248
249 of_node_put(np);
250 return 0;
251
252 free_freq_table:
253 dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
254 put_node:
255 of_node_put(np);
256 return ret;
257 }
258
259 static int imx6q_cpufreq_remove(struct platform_device *pdev)
260 {
261 cpufreq_unregister_driver(&imx6q_cpufreq_driver);
262 dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
263
264 return 0;
265 }
266
267 static struct platform_driver imx6q_cpufreq_platdrv = {
268 .driver = {
269 .name = "imx6q-cpufreq",
270 .owner = THIS_MODULE,
271 },
272 .probe = imx6q_cpufreq_probe,
273 .remove = imx6q_cpufreq_remove,
274 };
275 module_platform_driver(imx6q_cpufreq_platdrv);
276
277 MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
278 MODULE_DESCRIPTION("Freescale i.MX6Q cpufreq driver");
279 MODULE_LICENSE("GPL");