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/platform_device.h> |
23 | #include <linux/slab.h> |
24 | #include <linux/types.h> |
25 | |
26 | /* SPEAr CPUFreq driver data structure */ |
27 | static struct { |
28 | struct clk *clk; |
29 | unsigned int transition_latency; |
30 | struct cpufreq_frequency_table *freq_tbl; |
31 | u32 cnt; |
32 | } spear_cpufreq; |
33 | |
34 | static struct clk *spear1340_cpu_get_possible_parent(unsigned long newfreq) |
35 | { |
36 | struct clk *sys_pclk; |
37 | int pclk; |
38 | /* |
39 | * In SPEAr1340, cpu clk's parent sys clk can take input from |
40 | * following sources |
41 | */ |
42 | static const char * const sys_clk_src[] = { |
43 | "sys_syn_clk" , |
44 | "pll1_clk" , |
45 | "pll2_clk" , |
46 | "pll3_clk" , |
47 | }; |
48 | |
49 | /* |
50 | * As sys clk can have multiple source with their own range |
51 | * limitation so we choose possible sources accordingly |
52 | */ |
53 | if (newfreq <= 300000000) |
54 | pclk = 0; /* src is sys_syn_clk */ |
55 | else if (newfreq > 300000000 && newfreq <= 500000000) |
56 | pclk = 3; /* src is pll3_clk */ |
57 | else if (newfreq == 600000000) |
58 | pclk = 1; /* src is pll1_clk */ |
59 | else |
60 | return ERR_PTR(error: -EINVAL); |
61 | |
62 | /* Get parent to sys clock */ |
63 | sys_pclk = clk_get(NULL, id: sys_clk_src[pclk]); |
64 | if (IS_ERR(ptr: sys_pclk)) |
65 | pr_err("Failed to get %s clock\n" , sys_clk_src[pclk]); |
66 | |
67 | return sys_pclk; |
68 | } |
69 | |
70 | /* |
71 | * In SPEAr1340, we cannot use newfreq directly because we need to actually |
72 | * access a source clock (clk) which might not be ancestor of cpu at present. |
73 | * Hence in SPEAr1340 we would operate on source clock directly before switching |
74 | * cpu clock to it. |
75 | */ |
76 | static int spear1340_set_cpu_rate(struct clk *sys_pclk, unsigned long newfreq) |
77 | { |
78 | struct clk *sys_clk; |
79 | int ret = 0; |
80 | |
81 | sys_clk = clk_get_parent(clk: spear_cpufreq.clk); |
82 | if (IS_ERR(ptr: sys_clk)) { |
83 | pr_err("failed to get cpu's parent (sys) clock\n" ); |
84 | return PTR_ERR(ptr: sys_clk); |
85 | } |
86 | |
87 | /* Set the rate of the source clock before changing the parent */ |
88 | ret = clk_set_rate(clk: sys_pclk, rate: newfreq); |
89 | if (ret) { |
90 | pr_err("Failed to set sys clk rate to %lu\n" , newfreq); |
91 | return ret; |
92 | } |
93 | |
94 | ret = clk_set_parent(clk: sys_clk, parent: sys_pclk); |
95 | if (ret) { |
96 | pr_err("Failed to set sys clk parent\n" ); |
97 | return ret; |
98 | } |
99 | |
100 | return 0; |
101 | } |
102 | |
103 | static int spear_cpufreq_target(struct cpufreq_policy *policy, |
104 | unsigned int index) |
105 | { |
106 | long newfreq; |
107 | struct clk *srcclk; |
108 | int ret, mult = 1; |
109 | |
110 | newfreq = spear_cpufreq.freq_tbl[index].frequency * 1000; |
111 | |
112 | if (of_machine_is_compatible(compat: "st,spear1340" )) { |
113 | /* |
114 | * SPEAr1340 is special in the sense that due to the possibility |
115 | * of multiple clock sources for cpu clk's parent we can have |
116 | * different clock source for different frequency of cpu clk. |
117 | * Hence we need to choose one from amongst these possible clock |
118 | * sources. |
119 | */ |
120 | srcclk = spear1340_cpu_get_possible_parent(newfreq); |
121 | if (IS_ERR(ptr: srcclk)) { |
122 | pr_err("Failed to get src clk\n" ); |
123 | return PTR_ERR(ptr: srcclk); |
124 | } |
125 | |
126 | /* SPEAr1340: src clk is always 2 * intended cpu clk */ |
127 | mult = 2; |
128 | } else { |
129 | /* |
130 | * src clock to be altered is ancestor of cpu clock. Hence we |
131 | * can directly work on cpu clk |
132 | */ |
133 | srcclk = spear_cpufreq.clk; |
134 | } |
135 | |
136 | newfreq = clk_round_rate(clk: srcclk, rate: newfreq * mult); |
137 | if (newfreq <= 0) { |
138 | pr_err("clk_round_rate failed for cpu src clock\n" ); |
139 | return newfreq; |
140 | } |
141 | |
142 | if (mult == 2) |
143 | ret = spear1340_set_cpu_rate(sys_pclk: srcclk, newfreq); |
144 | else |
145 | ret = clk_set_rate(clk: spear_cpufreq.clk, rate: newfreq); |
146 | |
147 | if (ret) |
148 | pr_err("CPU Freq: cpu clk_set_rate failed: %d\n" , ret); |
149 | |
150 | return ret; |
151 | } |
152 | |
153 | static int spear_cpufreq_init(struct cpufreq_policy *policy) |
154 | { |
155 | policy->clk = spear_cpufreq.clk; |
156 | cpufreq_generic_init(policy, table: spear_cpufreq.freq_tbl, |
157 | transition_latency: spear_cpufreq.transition_latency); |
158 | return 0; |
159 | } |
160 | |
161 | static struct cpufreq_driver spear_cpufreq_driver = { |
162 | .name = "cpufreq-spear" , |
163 | .flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK, |
164 | .verify = cpufreq_generic_frequency_table_verify, |
165 | .target_index = spear_cpufreq_target, |
166 | .get = cpufreq_generic_get, |
167 | .init = spear_cpufreq_init, |
168 | .attr = cpufreq_generic_attr, |
169 | }; |
170 | |
171 | static int spear_cpufreq_probe(struct platform_device *pdev) |
172 | { |
173 | struct device_node *np; |
174 | const struct property *prop; |
175 | struct cpufreq_frequency_table *freq_tbl; |
176 | const __be32 *val; |
177 | int cnt, i, ret; |
178 | |
179 | np = of_cpu_device_node_get(cpu: 0); |
180 | if (!np) { |
181 | pr_err("No cpu node found\n" ); |
182 | return -ENODEV; |
183 | } |
184 | |
185 | if (of_property_read_u32(np, propname: "clock-latency" , |
186 | out_value: &spear_cpufreq.transition_latency)) |
187 | spear_cpufreq.transition_latency = CPUFREQ_ETERNAL; |
188 | |
189 | prop = of_find_property(np, name: "cpufreq_tbl" , NULL); |
190 | if (!prop || !prop->value) { |
191 | pr_err("Invalid cpufreq_tbl\n" ); |
192 | ret = -ENODEV; |
193 | goto out_put_node; |
194 | } |
195 | |
196 | cnt = prop->length / sizeof(u32); |
197 | val = prop->value; |
198 | |
199 | freq_tbl = kcalloc(n: cnt + 1, size: sizeof(*freq_tbl), GFP_KERNEL); |
200 | if (!freq_tbl) { |
201 | ret = -ENOMEM; |
202 | goto out_put_node; |
203 | } |
204 | |
205 | for (i = 0; i < cnt; i++) |
206 | freq_tbl[i].frequency = be32_to_cpup(p: val++); |
207 | |
208 | freq_tbl[i].frequency = CPUFREQ_TABLE_END; |
209 | |
210 | spear_cpufreq.freq_tbl = freq_tbl; |
211 | |
212 | of_node_put(node: np); |
213 | |
214 | spear_cpufreq.clk = clk_get(NULL, id: "cpu_clk" ); |
215 | if (IS_ERR(ptr: spear_cpufreq.clk)) { |
216 | pr_err("Unable to get CPU clock\n" ); |
217 | ret = PTR_ERR(ptr: spear_cpufreq.clk); |
218 | goto out_put_mem; |
219 | } |
220 | |
221 | ret = cpufreq_register_driver(driver_data: &spear_cpufreq_driver); |
222 | if (!ret) |
223 | return 0; |
224 | |
225 | pr_err("failed register driver: %d\n" , ret); |
226 | clk_put(clk: spear_cpufreq.clk); |
227 | |
228 | out_put_mem: |
229 | kfree(objp: freq_tbl); |
230 | return ret; |
231 | |
232 | out_put_node: |
233 | of_node_put(node: np); |
234 | return ret; |
235 | } |
236 | |
237 | static struct platform_driver spear_cpufreq_platdrv = { |
238 | .driver = { |
239 | .name = "spear-cpufreq" , |
240 | }, |
241 | .probe = spear_cpufreq_probe, |
242 | }; |
243 | module_platform_driver(spear_cpufreq_platdrv); |
244 | |
245 | MODULE_AUTHOR("Deepak Sikri <deepak.sikri@st.com>" ); |
246 | MODULE_DESCRIPTION("SPEAr CPUFreq driver" ); |
247 | MODULE_LICENSE("GPL" ); |
248 | |