]>
Commit | Line | Data |
---|---|---|
3b2bd70f CYT |
1 | /* |
2 | * Copyright 2014 Chen-Yu Tsai | |
3 | * | |
4 | * Chen-Yu Tsai <wens@csie.org> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | */ | |
16 | ||
9dfefe8c | 17 | #include <linux/clk.h> |
3b2bd70f | 18 | #include <linux/clk-provider.h> |
3b2bd70f CYT |
19 | #include <linux/of.h> |
20 | #include <linux/of_address.h> | |
21 | #include <linux/log2.h> | |
22 | ||
23 | #include "clk-factors.h" | |
24 | ||
25 | ||
26 | /** | |
6424e0ae | 27 | * sun9i_a80_get_pll4_factors() - calculates n, p, m factors for PLL4 |
3b2bd70f CYT |
28 | * PLL4 rate is calculated as follows |
29 | * rate = (parent_rate * n >> p) / (m + 1); | |
6424e0ae | 30 | * parent_rate is always 24MHz |
3b2bd70f CYT |
31 | * |
32 | * p and m are named div1 and div2 in Allwinner's SDK | |
33 | */ | |
34 | ||
35 | static void sun9i_a80_get_pll4_factors(u32 *freq, u32 parent_rate, | |
6424e0ae | 36 | u8 *n_ret, u8 *k, u8 *m_ret, u8 *p_ret) |
3b2bd70f | 37 | { |
6424e0ae HG |
38 | int n; |
39 | int m = 1; | |
40 | int p = 1; | |
3b2bd70f | 41 | |
6424e0ae HG |
42 | /* Normalize value to a 6 MHz multiple (24 MHz / 4) */ |
43 | n = DIV_ROUND_UP(*freq, 6000000); | |
3b2bd70f | 44 | |
6424e0ae HG |
45 | /* If n is too large switch to steps of 12 MHz */ |
46 | if (n > 255) { | |
47 | m = 0; | |
48 | n = (n + 1) / 2; | |
49 | } | |
3b2bd70f | 50 | |
6424e0ae HG |
51 | /* If n is still too large switch to steps of 24 MHz */ |
52 | if (n > 255) { | |
53 | p = 0; | |
54 | n = (n + 1) / 2; | |
55 | } | |
3b2bd70f | 56 | |
6424e0ae HG |
57 | /* n must be between 12 and 255 */ |
58 | if (n > 255) | |
59 | n = 255; | |
60 | else if (n < 12) | |
61 | n = 12; | |
62 | ||
63 | *freq = ((24000000 * n) >> p) / (m + 1); | |
3b2bd70f CYT |
64 | |
65 | /* we were called to round the frequency, we can now return */ | |
6424e0ae | 66 | if (n_ret == NULL) |
3b2bd70f CYT |
67 | return; |
68 | ||
6424e0ae HG |
69 | *n_ret = n; |
70 | *m_ret = m; | |
71 | *p_ret = p; | |
3b2bd70f CYT |
72 | } |
73 | ||
74 | static struct clk_factors_config sun9i_a80_pll4_config = { | |
75 | .mshift = 18, | |
76 | .mwidth = 1, | |
77 | .nshift = 8, | |
78 | .nwidth = 8, | |
79 | .pshift = 16, | |
80 | .pwidth = 1, | |
81 | }; | |
82 | ||
83 | static const struct factors_data sun9i_a80_pll4_data __initconst = { | |
84 | .enable = 31, | |
85 | .table = &sun9i_a80_pll4_config, | |
86 | .getter = sun9i_a80_get_pll4_factors, | |
87 | }; | |
88 | ||
89 | static DEFINE_SPINLOCK(sun9i_a80_pll4_lock); | |
90 | ||
91 | static void __init sun9i_a80_pll4_setup(struct device_node *node) | |
92 | { | |
66e79cf1 CYT |
93 | void __iomem *reg; |
94 | ||
95 | reg = of_io_request_and_map(node, 0, of_node_full_name(node)); | |
5ac382c3 | 96 | if (IS_ERR(reg)) { |
66e79cf1 CYT |
97 | pr_err("Could not get registers for a80-pll4-clk: %s\n", |
98 | node->name); | |
99 | return; | |
100 | } | |
101 | ||
102 | sunxi_factors_register(node, &sun9i_a80_pll4_data, | |
103 | &sun9i_a80_pll4_lock, reg); | |
3b2bd70f CYT |
104 | } |
105 | CLK_OF_DECLARE(sun9i_a80_pll4, "allwinner,sun9i-a80-pll4-clk", sun9i_a80_pll4_setup); | |
106 | ||
107 | ||
108 | /** | |
109 | * sun9i_a80_get_gt_factors() - calculates m factor for GT | |
110 | * GT rate is calculated as follows | |
111 | * rate = parent_rate / (m + 1); | |
112 | */ | |
113 | ||
114 | static void sun9i_a80_get_gt_factors(u32 *freq, u32 parent_rate, | |
115 | u8 *n, u8 *k, u8 *m, u8 *p) | |
116 | { | |
117 | u32 div; | |
118 | ||
119 | if (parent_rate < *freq) | |
120 | *freq = parent_rate; | |
121 | ||
122 | div = DIV_ROUND_UP(parent_rate, *freq); | |
123 | ||
124 | /* maximum divider is 4 */ | |
125 | if (div > 4) | |
126 | div = 4; | |
127 | ||
128 | *freq = parent_rate / div; | |
129 | ||
130 | /* we were called to round the frequency, we can now return */ | |
131 | if (!m) | |
132 | return; | |
133 | ||
134 | *m = div; | |
135 | } | |
136 | ||
137 | static struct clk_factors_config sun9i_a80_gt_config = { | |
138 | .mshift = 0, | |
139 | .mwidth = 2, | |
140 | }; | |
141 | ||
142 | static const struct factors_data sun9i_a80_gt_data __initconst = { | |
143 | .mux = 24, | |
144 | .muxmask = BIT(1) | BIT(0), | |
145 | .table = &sun9i_a80_gt_config, | |
146 | .getter = sun9i_a80_get_gt_factors, | |
147 | }; | |
148 | ||
149 | static DEFINE_SPINLOCK(sun9i_a80_gt_lock); | |
150 | ||
151 | static void __init sun9i_a80_gt_setup(struct device_node *node) | |
152 | { | |
66e79cf1 CYT |
153 | void __iomem *reg; |
154 | struct clk *gt; | |
155 | ||
156 | reg = of_io_request_and_map(node, 0, of_node_full_name(node)); | |
5ac382c3 | 157 | if (IS_ERR(reg)) { |
66e79cf1 CYT |
158 | pr_err("Could not get registers for a80-gt-clk: %s\n", |
159 | node->name); | |
160 | return; | |
161 | } | |
162 | ||
163 | gt = sunxi_factors_register(node, &sun9i_a80_gt_data, | |
164 | &sun9i_a80_gt_lock, reg); | |
3b2bd70f CYT |
165 | |
166 | /* The GT bus clock needs to be always enabled */ | |
167 | __clk_get(gt); | |
168 | clk_prepare_enable(gt); | |
169 | } | |
170 | CLK_OF_DECLARE(sun9i_a80_gt, "allwinner,sun9i-a80-gt-clk", sun9i_a80_gt_setup); | |
171 | ||
172 | ||
173 | /** | |
174 | * sun9i_a80_get_ahb_factors() - calculates p factor for AHB0/1/2 | |
175 | * AHB rate is calculated as follows | |
176 | * rate = parent_rate >> p; | |
177 | */ | |
178 | ||
179 | static void sun9i_a80_get_ahb_factors(u32 *freq, u32 parent_rate, | |
180 | u8 *n, u8 *k, u8 *m, u8 *p) | |
181 | { | |
182 | u32 _p; | |
183 | ||
184 | if (parent_rate < *freq) | |
185 | *freq = parent_rate; | |
186 | ||
187 | _p = order_base_2(DIV_ROUND_UP(parent_rate, *freq)); | |
188 | ||
189 | /* maximum p is 3 */ | |
190 | if (_p > 3) | |
191 | _p = 3; | |
192 | ||
193 | *freq = parent_rate >> _p; | |
194 | ||
195 | /* we were called to round the frequency, we can now return */ | |
196 | if (!p) | |
197 | return; | |
198 | ||
199 | *p = _p; | |
200 | } | |
201 | ||
202 | static struct clk_factors_config sun9i_a80_ahb_config = { | |
203 | .pshift = 0, | |
204 | .pwidth = 2, | |
205 | }; | |
206 | ||
207 | static const struct factors_data sun9i_a80_ahb_data __initconst = { | |
208 | .mux = 24, | |
209 | .muxmask = BIT(1) | BIT(0), | |
210 | .table = &sun9i_a80_ahb_config, | |
211 | .getter = sun9i_a80_get_ahb_factors, | |
212 | }; | |
213 | ||
214 | static DEFINE_SPINLOCK(sun9i_a80_ahb_lock); | |
215 | ||
216 | static void __init sun9i_a80_ahb_setup(struct device_node *node) | |
217 | { | |
66e79cf1 CYT |
218 | void __iomem *reg; |
219 | ||
220 | reg = of_io_request_and_map(node, 0, of_node_full_name(node)); | |
5ac382c3 | 221 | if (IS_ERR(reg)) { |
66e79cf1 CYT |
222 | pr_err("Could not get registers for a80-ahb-clk: %s\n", |
223 | node->name); | |
224 | return; | |
225 | } | |
226 | ||
227 | sunxi_factors_register(node, &sun9i_a80_ahb_data, | |
228 | &sun9i_a80_ahb_lock, reg); | |
3b2bd70f CYT |
229 | } |
230 | CLK_OF_DECLARE(sun9i_a80_ahb, "allwinner,sun9i-a80-ahb-clk", sun9i_a80_ahb_setup); | |
231 | ||
232 | ||
233 | static const struct factors_data sun9i_a80_apb0_data __initconst = { | |
234 | .mux = 24, | |
235 | .muxmask = BIT(0), | |
236 | .table = &sun9i_a80_ahb_config, | |
237 | .getter = sun9i_a80_get_ahb_factors, | |
238 | }; | |
239 | ||
240 | static DEFINE_SPINLOCK(sun9i_a80_apb0_lock); | |
241 | ||
242 | static void __init sun9i_a80_apb0_setup(struct device_node *node) | |
243 | { | |
66e79cf1 CYT |
244 | void __iomem *reg; |
245 | ||
246 | reg = of_io_request_and_map(node, 0, of_node_full_name(node)); | |
5ac382c3 | 247 | if (IS_ERR(reg)) { |
66e79cf1 CYT |
248 | pr_err("Could not get registers for a80-apb0-clk: %s\n", |
249 | node->name); | |
250 | return; | |
251 | } | |
252 | ||
253 | sunxi_factors_register(node, &sun9i_a80_apb0_data, | |
254 | &sun9i_a80_apb0_lock, reg); | |
3b2bd70f CYT |
255 | } |
256 | CLK_OF_DECLARE(sun9i_a80_apb0, "allwinner,sun9i-a80-apb0-clk", sun9i_a80_apb0_setup); | |
257 | ||
258 | ||
259 | /** | |
260 | * sun9i_a80_get_apb1_factors() - calculates m, p factors for APB1 | |
261 | * APB1 rate is calculated as follows | |
262 | * rate = (parent_rate >> p) / (m + 1); | |
263 | */ | |
264 | ||
265 | static void sun9i_a80_get_apb1_factors(u32 *freq, u32 parent_rate, | |
266 | u8 *n, u8 *k, u8 *m, u8 *p) | |
267 | { | |
268 | u32 div; | |
269 | u8 calcm, calcp; | |
270 | ||
271 | if (parent_rate < *freq) | |
272 | *freq = parent_rate; | |
273 | ||
274 | div = DIV_ROUND_UP(parent_rate, *freq); | |
275 | ||
276 | /* Highest possible divider is 256 (p = 3, m = 31) */ | |
277 | if (div > 256) | |
278 | div = 256; | |
279 | ||
280 | calcp = order_base_2(div); | |
281 | calcm = (parent_rate >> calcp) - 1; | |
282 | *freq = (parent_rate >> calcp) / (calcm + 1); | |
283 | ||
284 | /* we were called to round the frequency, we can now return */ | |
285 | if (n == NULL) | |
286 | return; | |
287 | ||
288 | *m = calcm; | |
289 | *p = calcp; | |
290 | } | |
291 | ||
292 | static struct clk_factors_config sun9i_a80_apb1_config = { | |
293 | .mshift = 0, | |
294 | .mwidth = 5, | |
295 | .pshift = 16, | |
296 | .pwidth = 2, | |
297 | }; | |
298 | ||
299 | static const struct factors_data sun9i_a80_apb1_data __initconst = { | |
300 | .mux = 24, | |
301 | .muxmask = BIT(0), | |
302 | .table = &sun9i_a80_apb1_config, | |
303 | .getter = sun9i_a80_get_apb1_factors, | |
304 | }; | |
305 | ||
306 | static DEFINE_SPINLOCK(sun9i_a80_apb1_lock); | |
307 | ||
308 | static void __init sun9i_a80_apb1_setup(struct device_node *node) | |
309 | { | |
66e79cf1 CYT |
310 | void __iomem *reg; |
311 | ||
312 | reg = of_io_request_and_map(node, 0, of_node_full_name(node)); | |
5ac382c3 | 313 | if (IS_ERR(reg)) { |
66e79cf1 CYT |
314 | pr_err("Could not get registers for a80-apb1-clk: %s\n", |
315 | node->name); | |
316 | return; | |
317 | } | |
318 | ||
319 | sunxi_factors_register(node, &sun9i_a80_apb1_data, | |
320 | &sun9i_a80_apb1_lock, reg); | |
3b2bd70f CYT |
321 | } |
322 | CLK_OF_DECLARE(sun9i_a80_apb1, "allwinner,sun9i-a80-apb1-clk", sun9i_a80_apb1_setup); |