aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/cpufreq/spear-cpufreq.c
diff options
context:
space:
mode:
authorDeepak Sikri <deepak.sikri@st.com>2012-11-27 08:05:26 -0500
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2012-11-27 08:05:26 -0500
commit420993221175a45db5af012c53fd2fef4d9533dc (patch)
treed860d8781144eb6d8cd18ace65be8b432691b10c /drivers/cpufreq/spear-cpufreq.c
parenta0e5af3cb89b59aa6c62b1f97c8d553ff3fb51c1 (diff)
cpufreq: SPEAr: Add CPUFreq driver
SPEAr is an ARM based family of SoCs. This patch adds in support of cpufreq driver for SPEAr SoCs. It is supported via DT only and so bindings are present in binding document. Signed-off-by: Deepak Sikri <deepak.sikri@st.com> Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Diffstat (limited to 'drivers/cpufreq/spear-cpufreq.c')
-rw-r--r--drivers/cpufreq/spear-cpufreq.c291
1 files changed, 291 insertions, 0 deletions
diff --git a/drivers/cpufreq/spear-cpufreq.c b/drivers/cpufreq/spear-cpufreq.c
new file mode 100644
index 000000000000..4575cfe41755
--- /dev/null
+++ b/drivers/cpufreq/spear-cpufreq.c
@@ -0,0 +1,291 @@
1/*
2 * drivers/cpufreq/spear-cpufreq.c
3 *
4 * CPU Frequency Scaling for SPEAr platform
5 *
6 * Copyright (C) 2012 ST Microelectronics
7 * Deepak Sikri <deepak.sikri@st.com>
8 *
9 * This file is licensed under the terms of the GNU General Public
10 * License version 2. This program is licensed "as is" without any
11 * warranty of any kind, whether express or implied.
12 */
13
14#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
15
16#include <linux/clk.h>
17#include <linux/cpufreq.h>
18#include <linux/err.h>
19#include <linux/init.h>
20#include <linux/module.h>
21#include <linux/of.h>
22#include <linux/slab.h>
23#include <linux/types.h>
24
25/* SPEAr CPUFreq driver data structure */
26static struct {
27 struct clk *clk;
28 unsigned int transition_latency;
29 struct cpufreq_frequency_table *freq_tbl;
30 u32 cnt;
31} spear_cpufreq;
32
33int spear_cpufreq_verify(struct cpufreq_policy *policy)
34{
35 return cpufreq_frequency_table_verify(policy, spear_cpufreq.freq_tbl);
36}
37
38static unsigned int spear_cpufreq_get(unsigned int cpu)
39{
40 return clk_get_rate(spear_cpufreq.clk) / 1000;
41}
42
43static struct clk *spear1340_cpu_get_possible_parent(unsigned long newfreq)
44{
45 struct clk *sys_pclk;
46 int pclk;
47 /*
48 * In SPEAr1340, cpu clk's parent sys clk can take input from
49 * following sources
50 */
51 const char *sys_clk_src[] = {
52 "sys_syn_clk",
53 "pll1_clk",
54 "pll2_clk",
55 "pll3_clk",
56 };
57
58 /*
59 * As sys clk can have multiple source with their own range
60 * limitation so we choose possible sources accordingly
61 */
62 if (newfreq <= 300000000)
63 pclk = 0; /* src is sys_syn_clk */
64 else if (newfreq > 300000000 && newfreq <= 500000000)
65 pclk = 3; /* src is pll3_clk */
66 else if (newfreq == 600000000)
67 pclk = 1; /* src is pll1_clk */
68 else
69 return ERR_PTR(-EINVAL);
70
71 /* Get parent to sys clock */
72 sys_pclk = clk_get(NULL, sys_clk_src[pclk]);
73 if (IS_ERR(sys_pclk))
74 pr_err("Failed to get %s clock\n", sys_clk_src[pclk]);
75
76 return sys_pclk;
77}
78
79/*
80 * In SPEAr1340, we cannot use newfreq directly because we need to actually
81 * access a source clock (clk) which might not be ancestor of cpu at present.
82 * Hence in SPEAr1340 we would operate on source clock directly before switching
83 * cpu clock to it.
84 */
85static int spear1340_set_cpu_rate(struct clk *sys_pclk, unsigned long newfreq)
86{
87 struct clk *sys_clk;
88 int ret = 0;
89
90 sys_clk = clk_get_parent(spear_cpufreq.clk);
91 if (IS_ERR(sys_clk)) {
92 pr_err("failed to get cpu's parent (sys) clock\n");
93 return PTR_ERR(sys_clk);
94 }
95
96 /* Set the rate of the source clock before changing the parent */
97 ret = clk_set_rate(sys_pclk, newfreq);
98 if (ret) {
99 pr_err("Failed to set sys clk rate to %lu\n", newfreq);
100 return ret;
101 }
102
103 ret = clk_set_parent(sys_clk, sys_pclk);
104 if (ret) {
105 pr_err("Failed to set sys clk parent\n");
106 return ret;
107 }
108
109 return 0;
110}
111
112static int spear_cpufreq_target(struct cpufreq_policy *policy,
113 unsigned int target_freq, unsigned int relation)
114{
115 struct cpufreq_freqs freqs;
116 unsigned long newfreq;
117 struct clk *srcclk;
118 int index, ret, mult = 1;
119
120 if (cpufreq_frequency_table_target(policy, spear_cpufreq.freq_tbl,
121 target_freq, relation, &index))
122 return -EINVAL;
123
124 freqs.cpu = policy->cpu;
125 freqs.old = spear_cpufreq_get(0);
126
127 newfreq = spear_cpufreq.freq_tbl[index].frequency * 1000;
128 if (of_machine_is_compatible("st,spear1340")) {
129 /*
130 * SPEAr1340 is special in the sense that due to the possibility
131 * of multiple clock sources for cpu clk's parent we can have
132 * different clock source for different frequency of cpu clk.
133 * Hence we need to choose one from amongst these possible clock
134 * sources.
135 */
136 srcclk = spear1340_cpu_get_possible_parent(newfreq);
137 if (IS_ERR(srcclk)) {
138 pr_err("Failed to get src clk\n");
139 return PTR_ERR(srcclk);
140 }
141
142 /* SPEAr1340: src clk is always 2 * intended cpu clk */
143 mult = 2;
144 } else {
145 /*
146 * src clock to be altered is ancestor of cpu clock. Hence we
147 * can directly work on cpu clk
148 */
149 srcclk = spear_cpufreq.clk;
150 }
151
152 newfreq = clk_round_rate(srcclk, newfreq * mult);
153 if (newfreq < 0) {
154 pr_err("clk_round_rate failed for cpu src clock\n");
155 return newfreq;
156 }
157
158 freqs.new = newfreq / 1000;
159 freqs.new /= mult;
160 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
161
162 if (mult == 2)
163 ret = spear1340_set_cpu_rate(srcclk, newfreq);
164 else
165 ret = clk_set_rate(spear_cpufreq.clk, newfreq);
166
167 /* Get current rate after clk_set_rate, in case of failure */
168 if (ret) {
169 pr_err("CPU Freq: cpu clk_set_rate failed: %d\n", ret);
170 freqs.new = clk_get_rate(spear_cpufreq.clk) / 1000;
171 }
172
173 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
174 return ret;
175}
176
177static int spear_cpufreq_init(struct cpufreq_policy *policy)
178{
179 int ret;
180
181 ret = cpufreq_frequency_table_cpuinfo(policy, spear_cpufreq.freq_tbl);
182 if (ret) {
183 pr_err("cpufreq_frequency_table_cpuinfo() failed");
184 return ret;
185 }
186
187 cpufreq_frequency_table_get_attr(spear_cpufreq.freq_tbl, policy->cpu);
188 policy->cpuinfo.transition_latency = spear_cpufreq.transition_latency;
189 policy->cur = spear_cpufreq_get(0);
190
191 cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu));
192 cpumask_copy(policy->related_cpus, policy->cpus);
193
194 return 0;
195}
196
197static int spear_cpufreq_exit(struct cpufreq_policy *policy)
198{
199 cpufreq_frequency_table_put_attr(policy->cpu);
200 return 0;
201}
202
203static struct freq_attr *spear_cpufreq_attr[] = {
204 &cpufreq_freq_attr_scaling_available_freqs,
205 NULL,
206};
207
208static struct cpufreq_driver spear_cpufreq_driver = {
209 .name = "cpufreq-spear",
210 .flags = CPUFREQ_STICKY,
211 .verify = spear_cpufreq_verify,
212 .target = spear_cpufreq_target,
213 .get = spear_cpufreq_get,
214 .init = spear_cpufreq_init,
215 .exit = spear_cpufreq_exit,
216 .attr = spear_cpufreq_attr,
217};
218
219static int spear_cpufreq_driver_init(void)
220{
221 struct device_node *np;
222 const struct property *prop;
223 struct cpufreq_frequency_table *freq_tbl;
224 const __be32 *val;
225 int cnt, i, ret;
226
227 np = of_find_node_by_path("/cpus/cpu@0");
228 if (!np) {
229 pr_err("No cpu node found");
230 return -ENODEV;
231 }
232
233 if (of_property_read_u32(np, "clock-latency",
234 &spear_cpufreq.transition_latency))
235 spear_cpufreq.transition_latency = CPUFREQ_ETERNAL;
236
237 prop = of_find_property(np, "cpufreq_tbl", NULL);
238 if (!prop || !prop->value) {
239 pr_err("Invalid cpufreq_tbl");
240 ret = -ENODEV;
241 goto out_put_node;
242 }
243
244 cnt = prop->length / sizeof(u32);
245 val = prop->value;
246
247 freq_tbl = kmalloc(sizeof(*freq_tbl) * (cnt + 1), GFP_KERNEL);
248 if (!freq_tbl) {
249 ret = -ENOMEM;
250 goto out_put_node;
251 }
252
253 for (i = 0; i < cnt; i++) {
254 freq_tbl[i].index = i;
255 freq_tbl[i].frequency = be32_to_cpup(val++);
256 }
257
258 freq_tbl[i].index = i;
259 freq_tbl[i].frequency = CPUFREQ_TABLE_END;
260
261 spear_cpufreq.freq_tbl = freq_tbl;
262
263 of_node_put(np);
264
265 spear_cpufreq.clk = clk_get(NULL, "cpu_clk");
266 if (IS_ERR(spear_cpufreq.clk)) {
267 pr_err("Unable to get CPU clock\n");
268 ret = PTR_ERR(spear_cpufreq.clk);
269 goto out_put_mem;
270 }
271
272 ret = cpufreq_register_driver(&spear_cpufreq_driver);
273 if (!ret)
274 return 0;
275
276 pr_err("failed register driver: %d\n", ret);
277 clk_put(spear_cpufreq.clk);
278
279out_put_mem:
280 kfree(freq_tbl);
281 return ret;
282
283out_put_node:
284 of_node_put(np);
285 return ret;
286}
287late_initcall(spear_cpufreq_driver_init);
288
289MODULE_AUTHOR("Deepak Sikri <deepak.sikri@st.com>");
290MODULE_DESCRIPTION("SPEAr CPUFreq driver");
291MODULE_LICENSE("GPL");