blob: 07af3b0de0699605b5d5bd292cdbc53e84d5bb6d [file] [log] [blame]
Shawn Guo1dd538f2013-02-04 05:46:29 +00001/*
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>
Sudeep KarkadaNageshab494b482013-09-10 18:59:47 +010010#include <linux/cpu.h>
Shawn Guo1dd538f2013-02-04 05:46:29 +000011#include <linux/cpufreq.h>
12#include <linux/delay.h>
13#include <linux/err.h>
14#include <linux/module.h>
15#include <linux/of.h>
Nishanth Menone4db1c72013-09-19 16:03:52 -050016#include <linux/pm_opp.h>
Shawn Guo1dd538f2013-02-04 05:46:29 +000017#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
24static struct regulator *arm_reg;
25static struct regulator *pu_reg;
26static struct regulator *soc_reg;
27
28static struct clk *arm_clk;
29static struct clk *pll1_sys_clk;
30static struct clk *pll1_sw_clk;
31static struct clk *step_clk;
32static struct clk *pll2_pfd2_396m_clk;
33
34static struct device *cpu_dev;
35static struct cpufreq_frequency_table *freq_table;
36static unsigned int transition_latency;
37
Shawn Guo1dd538f2013-02-04 05:46:29 +000038static unsigned int imx6q_get_speed(unsigned int cpu)
39{
40 return clk_get_rate(arm_clk) / 1000;
41}
42
Viresh Kumar9c0ebcf2013-10-25 19:45:48 +053043static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index)
Shawn Guo1dd538f2013-02-04 05:46:29 +000044{
45 struct cpufreq_freqs freqs;
Nishanth Menon47d43ba2013-09-19 16:03:51 -050046 struct dev_pm_opp *opp;
Shawn Guo1dd538f2013-02-04 05:46:29 +000047 unsigned long freq_hz, volt, volt_old;
Shawn Guo1dd538f2013-02-04 05:46:29 +000048 int ret;
49
Shawn Guo1dd538f2013-02-04 05:46:29 +000050 freqs.new = freq_table[index].frequency;
51 freq_hz = freqs.new * 1000;
52 freqs.old = clk_get_rate(arm_clk) / 1000;
53
Shawn Guo1dd538f2013-02-04 05:46:29 +000054 rcu_read_lock();
Nishanth Menon5d4879c2013-09-19 16:03:50 -050055 opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz);
Shawn Guo1dd538f2013-02-04 05:46:29 +000056 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
Nishanth Menon5d4879c2013-09-19 16:03:50 -050062 volt = dev_pm_opp_get_voltage(opp);
Shawn Guo1dd538f2013-02-04 05:46:29 +000063 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
Viresh Kumar5a571c32013-06-19 11:18:20 +053070 cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
71
Shawn Guo1dd538f2013-02-04 05:46:29 +000072 /* 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);
Viresh Kumar5a571c32013-06-19 11:18:20 +053078 freqs.new = freqs.old;
79 goto post_notify;
Shawn Guo1dd538f2013-02-04 05:46:29 +000080 }
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 */
Shawn Guo1dd538f2013-02-04 05:46:29 +0000103 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);
Shawn Guo1dd538f2013-02-04 05:46:29 +0000107 clk_set_parent(pll1_sw_clk, pll1_sys_clk);
Shawn Guo1dd538f2013-02-04 05:46:29 +0000108 }
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);
Viresh Kumar5a571c32013-06-19 11:18:20 +0530115 freqs.new = freqs.old;
116 goto post_notify;
Shawn Guo1dd538f2013-02-04 05:46:29 +0000117 }
118
119 /* scaling down? scale voltage after frequency */
120 if (freqs.new < freqs.old) {
121 ret = regulator_set_voltage_tol(arm_reg, volt, 0);
Viresh Kumar5a571c32013-06-19 11:18:20 +0530122 if (ret) {
Shawn Guo1dd538f2013-02-04 05:46:29 +0000123 dev_warn(cpu_dev,
124 "failed to scale vddarm down: %d\n", ret);
Viresh Kumar5a571c32013-06-19 11:18:20 +0530125 ret = 0;
126 }
Shawn Guo1dd538f2013-02-04 05:46:29 +0000127
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
Viresh Kumar5a571c32013-06-19 11:18:20 +0530136post_notify:
Viresh Kumarb43a7ff2013-03-24 11:56:43 +0530137 cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
Shawn Guo1dd538f2013-02-04 05:46:29 +0000138
Viresh Kumar5a571c32013-06-19 11:18:20 +0530139 return ret;
Shawn Guo1dd538f2013-02-04 05:46:29 +0000140}
141
142static int imx6q_cpufreq_init(struct cpufreq_policy *policy)
143{
Viresh Kumar17922dd2013-10-03 20:29:14 +0530144 return cpufreq_generic_init(policy, freq_table, transition_latency);
Shawn Guo1dd538f2013-02-04 05:46:29 +0000145}
146
Shawn Guo1dd538f2013-02-04 05:46:29 +0000147static struct cpufreq_driver imx6q_cpufreq_driver = {
Viresh Kumar4f6ba382013-10-03 20:28:08 +0530148 .verify = cpufreq_generic_frequency_table_verify,
Viresh Kumar9c0ebcf2013-10-25 19:45:48 +0530149 .target_index = imx6q_set_target,
Shawn Guo1dd538f2013-02-04 05:46:29 +0000150 .get = imx6q_get_speed,
151 .init = imx6q_cpufreq_init,
Viresh Kumar4f6ba382013-10-03 20:28:08 +0530152 .exit = cpufreq_generic_exit,
Shawn Guo1dd538f2013-02-04 05:46:29 +0000153 .name = "imx6q-cpufreq",
Viresh Kumar4f6ba382013-10-03 20:28:08 +0530154 .attr = cpufreq_generic_attr,
Shawn Guo1dd538f2013-02-04 05:46:29 +0000155};
156
157static int imx6q_cpufreq_probe(struct platform_device *pdev)
158{
159 struct device_node *np;
Nishanth Menon47d43ba2013-09-19 16:03:51 -0500160 struct dev_pm_opp *opp;
Shawn Guo1dd538f2013-02-04 05:46:29 +0000161 unsigned long min_volt, max_volt;
162 int num, ret;
163
Sudeep KarkadaNageshab494b482013-09-10 18:59:47 +0100164 cpu_dev = get_cpu_device(0);
165 if (!cpu_dev) {
166 pr_err("failed to get cpu0 device\n");
167 return -ENODEV;
168 }
Shawn Guo1dd538f2013-02-04 05:46:29 +0000169
Sudeep KarkadaNageshacdc58d62013-06-17 14:58:48 +0100170 np = of_node_get(cpu_dev->of_node);
Shawn Guo1dd538f2013-02-04 05:46:29 +0000171 if (!np) {
172 dev_err(cpu_dev, "failed to find cpu0 node\n");
173 return -ENOENT;
174 }
175
Shawn Guo1dd538f2013-02-04 05:46:29 +0000176 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");
Wei Yongjun3a3656d2013-02-22 04:39:30 +0000191 if (IS_ERR(arm_reg) || IS_ERR(pu_reg) || IS_ERR(soc_reg)) {
Shawn Guo1dd538f2013-02-04 05:46:29 +0000192 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 */
Nishanth Menon5d4879c2013-09-19 16:03:50 -0500198 num = dev_pm_opp_get_opp_count(cpu_dev);
Shawn Guo1dd538f2013-02-04 05:46:29 +0000199 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
Nishanth Menon5d4879c2013-09-19 16:03:50 -0500205 ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
Shawn Guo1dd538f2013-02-04 05:46:29 +0000206 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();
Nishanth Menon5d4879c2013-09-19 16:03:50 -0500220 opp = dev_pm_opp_find_freq_exact(cpu_dev,
Shawn Guo1dd538f2013-02-04 05:46:29 +0000221 freq_table[0].frequency * 1000, true);
Nishanth Menon5d4879c2013-09-19 16:03:50 -0500222 min_volt = dev_pm_opp_get_voltage(opp);
223 opp = dev_pm_opp_find_freq_exact(cpu_dev,
Shawn Guo1dd538f2013-02-04 05:46:29 +0000224 freq_table[--num].frequency * 1000, true);
Nishanth Menon5d4879c2013-09-19 16:03:50 -0500225 max_volt = dev_pm_opp_get_voltage(opp);
Shawn Guo1dd538f2013-02-04 05:46:29 +0000226 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
252free_freq_table:
Nishanth Menon5d4879c2013-09-19 16:03:50 -0500253 dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
Shawn Guo1dd538f2013-02-04 05:46:29 +0000254put_node:
255 of_node_put(np);
256 return ret;
257}
258
259static int imx6q_cpufreq_remove(struct platform_device *pdev)
260{
261 cpufreq_unregister_driver(&imx6q_cpufreq_driver);
Nishanth Menon5d4879c2013-09-19 16:03:50 -0500262 dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
Shawn Guo1dd538f2013-02-04 05:46:29 +0000263
264 return 0;
265}
266
267static 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};
275module_platform_driver(imx6q_cpufreq_platdrv);
276
277MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
278MODULE_DESCRIPTION("Freescale i.MX6Q cpufreq driver");
279MODULE_LICENSE("GPL");