]>
Commit | Line | Data |
---|---|---|
6114067e BB |
1 | /* |
2 | * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com> | |
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 as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | */ | |
10 | ||
11 | #include <linux/clk-provider.h> | |
12 | #include <linux/clkdev.h> | |
13 | #include <linux/clk/at91_pmc.h> | |
14 | #include <linux/of.h> | |
1bdf0232 BB |
15 | #include <linux/mfd/syscon.h> |
16 | #include <linux/regmap.h> | |
6114067e BB |
17 | |
18 | #include "pmc.h" | |
19 | ||
1bdf0232 BB |
20 | DEFINE_SPINLOCK(pmc_pcr_lock); |
21 | ||
6114067e BB |
22 | #define PERIPHERAL_MAX 64 |
23 | ||
24 | #define PERIPHERAL_AT91RM9200 0 | |
25 | #define PERIPHERAL_AT91SAM9X5 1 | |
26 | ||
27 | #define PERIPHERAL_ID_MIN 2 | |
28 | #define PERIPHERAL_ID_MAX 31 | |
29 | #define PERIPHERAL_MASK(id) (1 << ((id) & PERIPHERAL_ID_MAX)) | |
30 | ||
31 | #define PERIPHERAL_RSHIFT_MASK 0x3 | |
32 | #define PERIPHERAL_RSHIFT(val) (((val) >> 16) & PERIPHERAL_RSHIFT_MASK) | |
33 | ||
86e4404a | 34 | #define PERIPHERAL_MAX_SHIFT 3 |
6114067e BB |
35 | |
36 | struct clk_peripheral { | |
37 | struct clk_hw hw; | |
1bdf0232 | 38 | struct regmap *regmap; |
6114067e BB |
39 | u32 id; |
40 | }; | |
41 | ||
42 | #define to_clk_peripheral(hw) container_of(hw, struct clk_peripheral, hw) | |
43 | ||
44 | struct clk_sam9x5_peripheral { | |
45 | struct clk_hw hw; | |
1bdf0232 | 46 | struct regmap *regmap; |
6114067e | 47 | struct clk_range range; |
1bdf0232 | 48 | spinlock_t *lock; |
6114067e BB |
49 | u32 id; |
50 | u32 div; | |
51 | bool auto_div; | |
52 | }; | |
53 | ||
54 | #define to_clk_sam9x5_peripheral(hw) \ | |
55 | container_of(hw, struct clk_sam9x5_peripheral, hw) | |
56 | ||
57 | static int clk_peripheral_enable(struct clk_hw *hw) | |
58 | { | |
59 | struct clk_peripheral *periph = to_clk_peripheral(hw); | |
6114067e BB |
60 | int offset = AT91_PMC_PCER; |
61 | u32 id = periph->id; | |
62 | ||
63 | if (id < PERIPHERAL_ID_MIN) | |
64 | return 0; | |
65 | if (id > PERIPHERAL_ID_MAX) | |
66 | offset = AT91_PMC_PCER1; | |
1bdf0232 BB |
67 | regmap_write(periph->regmap, offset, PERIPHERAL_MASK(id)); |
68 | ||
6114067e BB |
69 | return 0; |
70 | } | |
71 | ||
72 | static void clk_peripheral_disable(struct clk_hw *hw) | |
73 | { | |
74 | struct clk_peripheral *periph = to_clk_peripheral(hw); | |
6114067e BB |
75 | int offset = AT91_PMC_PCDR; |
76 | u32 id = periph->id; | |
77 | ||
78 | if (id < PERIPHERAL_ID_MIN) | |
79 | return; | |
80 | if (id > PERIPHERAL_ID_MAX) | |
81 | offset = AT91_PMC_PCDR1; | |
1bdf0232 | 82 | regmap_write(periph->regmap, offset, PERIPHERAL_MASK(id)); |
6114067e BB |
83 | } |
84 | ||
85 | static int clk_peripheral_is_enabled(struct clk_hw *hw) | |
86 | { | |
87 | struct clk_peripheral *periph = to_clk_peripheral(hw); | |
6114067e | 88 | int offset = AT91_PMC_PCSR; |
1bdf0232 | 89 | unsigned int status; |
6114067e BB |
90 | u32 id = periph->id; |
91 | ||
92 | if (id < PERIPHERAL_ID_MIN) | |
93 | return 1; | |
94 | if (id > PERIPHERAL_ID_MAX) | |
95 | offset = AT91_PMC_PCSR1; | |
1bdf0232 BB |
96 | regmap_read(periph->regmap, offset, &status); |
97 | ||
98 | return status & PERIPHERAL_MASK(id) ? 1 : 0; | |
6114067e BB |
99 | } |
100 | ||
101 | static const struct clk_ops peripheral_ops = { | |
102 | .enable = clk_peripheral_enable, | |
103 | .disable = clk_peripheral_disable, | |
104 | .is_enabled = clk_peripheral_is_enabled, | |
105 | }; | |
106 | ||
f5644f10 | 107 | static struct clk_hw * __init |
1bdf0232 | 108 | at91_clk_register_peripheral(struct regmap *regmap, const char *name, |
6114067e BB |
109 | const char *parent_name, u32 id) |
110 | { | |
111 | struct clk_peripheral *periph; | |
6114067e | 112 | struct clk_init_data init; |
f5644f10 SB |
113 | struct clk_hw *hw; |
114 | int ret; | |
6114067e | 115 | |
1bdf0232 | 116 | if (!name || !parent_name || id > PERIPHERAL_ID_MAX) |
6114067e BB |
117 | return ERR_PTR(-EINVAL); |
118 | ||
119 | periph = kzalloc(sizeof(*periph), GFP_KERNEL); | |
120 | if (!periph) | |
121 | return ERR_PTR(-ENOMEM); | |
122 | ||
123 | init.name = name; | |
124 | init.ops = &peripheral_ops; | |
125 | init.parent_names = (parent_name ? &parent_name : NULL); | |
126 | init.num_parents = (parent_name ? 1 : 0); | |
127 | init.flags = 0; | |
128 | ||
129 | periph->id = id; | |
130 | periph->hw.init = &init; | |
1bdf0232 | 131 | periph->regmap = regmap; |
6114067e | 132 | |
f5644f10 SB |
133 | hw = &periph->hw; |
134 | ret = clk_hw_register(NULL, &periph->hw); | |
135 | if (ret) { | |
6114067e | 136 | kfree(periph); |
f5644f10 SB |
137 | hw = ERR_PTR(ret); |
138 | } | |
6114067e | 139 | |
f5644f10 | 140 | return hw; |
6114067e BB |
141 | } |
142 | ||
143 | static void clk_sam9x5_peripheral_autodiv(struct clk_sam9x5_peripheral *periph) | |
144 | { | |
d0979335 | 145 | struct clk_hw *parent; |
6114067e BB |
146 | unsigned long parent_rate; |
147 | int shift = 0; | |
148 | ||
149 | if (!periph->auto_div) | |
150 | return; | |
151 | ||
152 | if (periph->range.max) { | |
d0979335 SB |
153 | parent = clk_hw_get_parent_by_index(&periph->hw, 0); |
154 | parent_rate = clk_hw_get_rate(parent); | |
6114067e BB |
155 | if (!parent_rate) |
156 | return; | |
157 | ||
158 | for (; shift < PERIPHERAL_MAX_SHIFT; shift++) { | |
159 | if (parent_rate >> shift <= periph->range.max) | |
160 | break; | |
161 | } | |
162 | } | |
163 | ||
164 | periph->auto_div = false; | |
165 | periph->div = shift; | |
166 | } | |
167 | ||
168 | static int clk_sam9x5_peripheral_enable(struct clk_hw *hw) | |
169 | { | |
170 | struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw); | |
1bdf0232 | 171 | unsigned long flags; |
6114067e BB |
172 | |
173 | if (periph->id < PERIPHERAL_ID_MIN) | |
174 | return 0; | |
175 | ||
1bdf0232 BB |
176 | spin_lock_irqsave(periph->lock, flags); |
177 | regmap_write(periph->regmap, AT91_PMC_PCR, | |
178 | (periph->id & AT91_PMC_PCR_PID_MASK)); | |
179 | regmap_update_bits(periph->regmap, AT91_PMC_PCR, | |
180 | AT91_PMC_PCR_DIV_MASK | AT91_PMC_PCR_CMD | | |
181 | AT91_PMC_PCR_EN, | |
182 | AT91_PMC_PCR_DIV(periph->div) | | |
183 | AT91_PMC_PCR_CMD | | |
184 | AT91_PMC_PCR_EN); | |
185 | spin_unlock_irqrestore(periph->lock, flags); | |
186 | ||
6114067e BB |
187 | return 0; |
188 | } | |
189 | ||
190 | static void clk_sam9x5_peripheral_disable(struct clk_hw *hw) | |
191 | { | |
192 | struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw); | |
1bdf0232 | 193 | unsigned long flags; |
6114067e BB |
194 | |
195 | if (periph->id < PERIPHERAL_ID_MIN) | |
196 | return; | |
197 | ||
1bdf0232 BB |
198 | spin_lock_irqsave(periph->lock, flags); |
199 | regmap_write(periph->regmap, AT91_PMC_PCR, | |
200 | (periph->id & AT91_PMC_PCR_PID_MASK)); | |
201 | regmap_update_bits(periph->regmap, AT91_PMC_PCR, | |
202 | AT91_PMC_PCR_EN | AT91_PMC_PCR_CMD, | |
203 | AT91_PMC_PCR_CMD); | |
204 | spin_unlock_irqrestore(periph->lock, flags); | |
6114067e BB |
205 | } |
206 | ||
207 | static int clk_sam9x5_peripheral_is_enabled(struct clk_hw *hw) | |
208 | { | |
209 | struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw); | |
1bdf0232 BB |
210 | unsigned long flags; |
211 | unsigned int status; | |
6114067e BB |
212 | |
213 | if (periph->id < PERIPHERAL_ID_MIN) | |
214 | return 1; | |
215 | ||
1bdf0232 BB |
216 | spin_lock_irqsave(periph->lock, flags); |
217 | regmap_write(periph->regmap, AT91_PMC_PCR, | |
218 | (periph->id & AT91_PMC_PCR_PID_MASK)); | |
219 | regmap_read(periph->regmap, AT91_PMC_PCR, &status); | |
220 | spin_unlock_irqrestore(periph->lock, flags); | |
6114067e | 221 | |
1bdf0232 | 222 | return status & AT91_PMC_PCR_EN ? 1 : 0; |
6114067e BB |
223 | } |
224 | ||
225 | static unsigned long | |
226 | clk_sam9x5_peripheral_recalc_rate(struct clk_hw *hw, | |
227 | unsigned long parent_rate) | |
228 | { | |
229 | struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw); | |
1bdf0232 BB |
230 | unsigned long flags; |
231 | unsigned int status; | |
6114067e BB |
232 | |
233 | if (periph->id < PERIPHERAL_ID_MIN) | |
234 | return parent_rate; | |
235 | ||
1bdf0232 BB |
236 | spin_lock_irqsave(periph->lock, flags); |
237 | regmap_write(periph->regmap, AT91_PMC_PCR, | |
238 | (periph->id & AT91_PMC_PCR_PID_MASK)); | |
239 | regmap_read(periph->regmap, AT91_PMC_PCR, &status); | |
240 | spin_unlock_irqrestore(periph->lock, flags); | |
6114067e | 241 | |
1bdf0232 BB |
242 | if (status & AT91_PMC_PCR_EN) { |
243 | periph->div = PERIPHERAL_RSHIFT(status); | |
6114067e BB |
244 | periph->auto_div = false; |
245 | } else { | |
246 | clk_sam9x5_peripheral_autodiv(periph); | |
247 | } | |
248 | ||
249 | return parent_rate >> periph->div; | |
250 | } | |
251 | ||
252 | static long clk_sam9x5_peripheral_round_rate(struct clk_hw *hw, | |
253 | unsigned long rate, | |
254 | unsigned long *parent_rate) | |
255 | { | |
256 | int shift = 0; | |
257 | unsigned long best_rate; | |
258 | unsigned long best_diff; | |
259 | unsigned long cur_rate = *parent_rate; | |
260 | unsigned long cur_diff; | |
261 | struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw); | |
262 | ||
263 | if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) | |
264 | return *parent_rate; | |
265 | ||
266 | if (periph->range.max) { | |
86e4404a | 267 | for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) { |
6114067e BB |
268 | cur_rate = *parent_rate >> shift; |
269 | if (cur_rate <= periph->range.max) | |
270 | break; | |
271 | } | |
272 | } | |
273 | ||
274 | if (rate >= cur_rate) | |
275 | return cur_rate; | |
276 | ||
277 | best_diff = cur_rate - rate; | |
278 | best_rate = cur_rate; | |
86e4404a | 279 | for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) { |
6114067e BB |
280 | cur_rate = *parent_rate >> shift; |
281 | if (cur_rate < rate) | |
282 | cur_diff = rate - cur_rate; | |
283 | else | |
284 | cur_diff = cur_rate - rate; | |
285 | ||
286 | if (cur_diff < best_diff) { | |
287 | best_diff = cur_diff; | |
288 | best_rate = cur_rate; | |
289 | } | |
290 | ||
291 | if (!best_diff || cur_rate < rate) | |
292 | break; | |
293 | } | |
294 | ||
295 | return best_rate; | |
296 | } | |
297 | ||
298 | static int clk_sam9x5_peripheral_set_rate(struct clk_hw *hw, | |
299 | unsigned long rate, | |
300 | unsigned long parent_rate) | |
301 | { | |
302 | int shift; | |
303 | struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw); | |
304 | if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) { | |
305 | if (parent_rate == rate) | |
306 | return 0; | |
307 | else | |
308 | return -EINVAL; | |
309 | } | |
310 | ||
311 | if (periph->range.max && rate > periph->range.max) | |
312 | return -EINVAL; | |
313 | ||
86e4404a | 314 | for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) { |
6114067e BB |
315 | if (parent_rate >> shift == rate) { |
316 | periph->auto_div = false; | |
317 | periph->div = shift; | |
318 | return 0; | |
319 | } | |
320 | } | |
321 | ||
322 | return -EINVAL; | |
323 | } | |
324 | ||
325 | static const struct clk_ops sam9x5_peripheral_ops = { | |
326 | .enable = clk_sam9x5_peripheral_enable, | |
327 | .disable = clk_sam9x5_peripheral_disable, | |
328 | .is_enabled = clk_sam9x5_peripheral_is_enabled, | |
329 | .recalc_rate = clk_sam9x5_peripheral_recalc_rate, | |
330 | .round_rate = clk_sam9x5_peripheral_round_rate, | |
331 | .set_rate = clk_sam9x5_peripheral_set_rate, | |
332 | }; | |
333 | ||
f5644f10 | 334 | static struct clk_hw * __init |
1bdf0232 BB |
335 | at91_clk_register_sam9x5_peripheral(struct regmap *regmap, spinlock_t *lock, |
336 | const char *name, const char *parent_name, | |
337 | u32 id, const struct clk_range *range) | |
6114067e BB |
338 | { |
339 | struct clk_sam9x5_peripheral *periph; | |
6114067e | 340 | struct clk_init_data init; |
f5644f10 SB |
341 | struct clk_hw *hw; |
342 | int ret; | |
6114067e | 343 | |
1bdf0232 | 344 | if (!name || !parent_name) |
6114067e BB |
345 | return ERR_PTR(-EINVAL); |
346 | ||
347 | periph = kzalloc(sizeof(*periph), GFP_KERNEL); | |
348 | if (!periph) | |
349 | return ERR_PTR(-ENOMEM); | |
350 | ||
351 | init.name = name; | |
352 | init.ops = &sam9x5_peripheral_ops; | |
353 | init.parent_names = (parent_name ? &parent_name : NULL); | |
354 | init.num_parents = (parent_name ? 1 : 0); | |
355 | init.flags = 0; | |
356 | ||
357 | periph->id = id; | |
358 | periph->hw.init = &init; | |
359 | periph->div = 0; | |
1bdf0232 BB |
360 | periph->regmap = regmap; |
361 | periph->lock = lock; | |
6114067e BB |
362 | periph->auto_div = true; |
363 | periph->range = *range; | |
364 | ||
f5644f10 SB |
365 | hw = &periph->hw; |
366 | ret = clk_hw_register(NULL, &periph->hw); | |
367 | if (ret) { | |
6114067e | 368 | kfree(periph); |
f5644f10 SB |
369 | hw = ERR_PTR(ret); |
370 | } else | |
6114067e BB |
371 | clk_sam9x5_peripheral_autodiv(periph); |
372 | ||
f5644f10 | 373 | return hw; |
6114067e BB |
374 | } |
375 | ||
376 | static void __init | |
1bdf0232 | 377 | of_at91_clk_periph_setup(struct device_node *np, u8 type) |
6114067e BB |
378 | { |
379 | int num; | |
380 | u32 id; | |
f5644f10 | 381 | struct clk_hw *hw; |
6114067e BB |
382 | const char *parent_name; |
383 | const char *name; | |
384 | struct device_node *periphclknp; | |
1bdf0232 | 385 | struct regmap *regmap; |
6114067e BB |
386 | |
387 | parent_name = of_clk_get_parent_name(np, 0); | |
388 | if (!parent_name) | |
389 | return; | |
390 | ||
391 | num = of_get_child_count(np); | |
392 | if (!num || num > PERIPHERAL_MAX) | |
393 | return; | |
394 | ||
1bdf0232 BB |
395 | regmap = syscon_node_to_regmap(of_get_parent(np)); |
396 | if (IS_ERR(regmap)) | |
397 | return; | |
398 | ||
6114067e BB |
399 | for_each_child_of_node(np, periphclknp) { |
400 | if (of_property_read_u32(periphclknp, "reg", &id)) | |
401 | continue; | |
402 | ||
403 | if (id >= PERIPHERAL_MAX) | |
404 | continue; | |
405 | ||
406 | if (of_property_read_string(np, "clock-output-names", &name)) | |
407 | name = periphclknp->name; | |
408 | ||
409 | if (type == PERIPHERAL_AT91RM9200) { | |
f5644f10 | 410 | hw = at91_clk_register_peripheral(regmap, name, |
6114067e BB |
411 | parent_name, id); |
412 | } else { | |
413 | struct clk_range range = CLK_RANGE(0, 0); | |
414 | ||
415 | of_at91_get_clk_range(periphclknp, | |
416 | "atmel,clk-output-range", | |
417 | &range); | |
418 | ||
f5644f10 | 419 | hw = at91_clk_register_sam9x5_peripheral(regmap, |
1bdf0232 BB |
420 | &pmc_pcr_lock, |
421 | name, | |
6114067e BB |
422 | parent_name, |
423 | id, &range); | |
424 | } | |
425 | ||
f5644f10 | 426 | if (IS_ERR(hw)) |
6114067e BB |
427 | continue; |
428 | ||
f5644f10 | 429 | of_clk_add_hw_provider(periphclknp, of_clk_hw_simple_get, hw); |
6114067e BB |
430 | } |
431 | } | |
432 | ||
1bdf0232 | 433 | static void __init of_at91rm9200_clk_periph_setup(struct device_node *np) |
6114067e | 434 | { |
1bdf0232 | 435 | of_at91_clk_periph_setup(np, PERIPHERAL_AT91RM9200); |
6114067e | 436 | } |
1bdf0232 BB |
437 | CLK_OF_DECLARE(at91rm9200_clk_periph, "atmel,at91rm9200-clk-peripheral", |
438 | of_at91rm9200_clk_periph_setup); | |
6114067e | 439 | |
1bdf0232 | 440 | static void __init of_at91sam9x5_clk_periph_setup(struct device_node *np) |
6114067e | 441 | { |
1bdf0232 | 442 | of_at91_clk_periph_setup(np, PERIPHERAL_AT91SAM9X5); |
6114067e | 443 | } |
1bdf0232 BB |
444 | CLK_OF_DECLARE(at91sam9x5_clk_periph, "atmel,at91sam9x5-clk-peripheral", |
445 | of_at91sam9x5_clk_periph_setup); | |
446 |