diff options
Diffstat (limited to 'arch/arm/plat-s3c/pwm-clock.c')
-rw-r--r-- | arch/arm/plat-s3c/pwm-clock.c | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/arch/arm/plat-s3c/pwm-clock.c b/arch/arm/plat-s3c/pwm-clock.c new file mode 100644 index 000000000000..a318215ab535 --- /dev/null +++ b/arch/arm/plat-s3c/pwm-clock.c | |||
@@ -0,0 +1,463 @@ | |||
1 | /* linux/arch/arm/plat-s3c24xx/pwm-clock.c | ||
2 | * | ||
3 | * Copyright (c) 2007 Simtec Electronics | ||
4 | * Copyright (c) 2007, 2008 Ben Dooks | ||
5 | * Ben Dooks <ben-linux@fluff.org> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation; either version 2 of the License. | ||
10 | */ | ||
11 | |||
12 | #include <linux/init.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/kernel.h> | ||
15 | #include <linux/list.h> | ||
16 | #include <linux/errno.h> | ||
17 | #include <linux/log2.h> | ||
18 | #include <linux/clk.h> | ||
19 | #include <linux/err.h> | ||
20 | #include <linux/io.h> | ||
21 | |||
22 | #include <mach/hardware.h> | ||
23 | #include <mach/map.h> | ||
24 | #include <asm/irq.h> | ||
25 | |||
26 | #include <plat/clock.h> | ||
27 | #include <plat/cpu.h> | ||
28 | |||
29 | #include <plat/regs-timer.h> | ||
30 | #include <mach/pwm-clock.h> | ||
31 | |||
32 | /* Each of the timers 0 through 5 go through the following | ||
33 | * clock tree, with the inputs depending on the timers. | ||
34 | * | ||
35 | * pclk ---- [ prescaler 0 ] -+---> timer 0 | ||
36 | * +---> timer 1 | ||
37 | * | ||
38 | * pclk ---- [ prescaler 1 ] -+---> timer 2 | ||
39 | * +---> timer 3 | ||
40 | * \---> timer 4 | ||
41 | * | ||
42 | * Which are fed into the timers as so: | ||
43 | * | ||
44 | * prescaled 0 ---- [ div 2,4,8,16 ] ---\ | ||
45 | * [mux] -> timer 0 | ||
46 | * tclk 0 ------------------------------/ | ||
47 | * | ||
48 | * prescaled 0 ---- [ div 2,4,8,16 ] ---\ | ||
49 | * [mux] -> timer 1 | ||
50 | * tclk 0 ------------------------------/ | ||
51 | * | ||
52 | * | ||
53 | * prescaled 1 ---- [ div 2,4,8,16 ] ---\ | ||
54 | * [mux] -> timer 2 | ||
55 | * tclk 1 ------------------------------/ | ||
56 | * | ||
57 | * prescaled 1 ---- [ div 2,4,8,16 ] ---\ | ||
58 | * [mux] -> timer 3 | ||
59 | * tclk 1 ------------------------------/ | ||
60 | * | ||
61 | * prescaled 1 ---- [ div 2,4,8, 16 ] --\ | ||
62 | * [mux] -> timer 4 | ||
63 | * tclk 1 ------------------------------/ | ||
64 | * | ||
65 | * Since the mux and the divider are tied together in the | ||
66 | * same register space, it is impossible to set the parent | ||
67 | * and the rate at the same time. To avoid this, we add an | ||
68 | * intermediate 'prescaled-and-divided' clock to select | ||
69 | * as the parent for the timer input clock called tdiv. | ||
70 | * | ||
71 | * prescaled clk --> pwm-tdiv ---\ | ||
72 | * [ mux ] --> timer X | ||
73 | * tclk -------------------------/ | ||
74 | */ | ||
75 | |||
76 | static struct clk clk_timer_scaler[]; | ||
77 | |||
78 | static unsigned long clk_pwm_scaler_get_rate(struct clk *clk) | ||
79 | { | ||
80 | unsigned long tcfg0 = __raw_readl(S3C2410_TCFG0); | ||
81 | |||
82 | if (clk == &clk_timer_scaler[1]) { | ||
83 | tcfg0 &= S3C2410_TCFG_PRESCALER1_MASK; | ||
84 | tcfg0 >>= S3C2410_TCFG_PRESCALER1_SHIFT; | ||
85 | } else { | ||
86 | tcfg0 &= S3C2410_TCFG_PRESCALER0_MASK; | ||
87 | } | ||
88 | |||
89 | return clk_get_rate(clk->parent) / (tcfg0 + 1); | ||
90 | } | ||
91 | |||
92 | static unsigned long clk_pwm_scaler_round_rate(struct clk *clk, | ||
93 | unsigned long rate) | ||
94 | { | ||
95 | unsigned long parent_rate = clk_get_rate(clk->parent); | ||
96 | unsigned long divisor = parent_rate / rate; | ||
97 | |||
98 | if (divisor > 256) | ||
99 | divisor = 256; | ||
100 | else if (divisor < 2) | ||
101 | divisor = 2; | ||
102 | |||
103 | return parent_rate / divisor; | ||
104 | } | ||
105 | |||
106 | static int clk_pwm_scaler_set_rate(struct clk *clk, unsigned long rate) | ||
107 | { | ||
108 | unsigned long round = clk_pwm_scaler_round_rate(clk, rate); | ||
109 | unsigned long tcfg0; | ||
110 | unsigned long divisor; | ||
111 | unsigned long flags; | ||
112 | |||
113 | divisor = clk_get_rate(clk->parent) / round; | ||
114 | divisor--; | ||
115 | |||
116 | local_irq_save(flags); | ||
117 | tcfg0 = __raw_readl(S3C2410_TCFG0); | ||
118 | |||
119 | if (clk == &clk_timer_scaler[1]) { | ||
120 | tcfg0 &= ~S3C2410_TCFG_PRESCALER1_MASK; | ||
121 | tcfg0 |= divisor << S3C2410_TCFG_PRESCALER1_SHIFT; | ||
122 | } else { | ||
123 | tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK; | ||
124 | tcfg0 |= divisor; | ||
125 | } | ||
126 | |||
127 | __raw_writel(tcfg0, S3C2410_TCFG0); | ||
128 | local_irq_restore(flags); | ||
129 | |||
130 | return 0; | ||
131 | } | ||
132 | |||
133 | static struct clk clk_timer_scaler[] = { | ||
134 | [0] = { | ||
135 | .name = "pwm-scaler0", | ||
136 | .id = -1, | ||
137 | .get_rate = clk_pwm_scaler_get_rate, | ||
138 | .set_rate = clk_pwm_scaler_set_rate, | ||
139 | .round_rate = clk_pwm_scaler_round_rate, | ||
140 | }, | ||
141 | [1] = { | ||
142 | .name = "pwm-scaler1", | ||
143 | .id = -1, | ||
144 | .get_rate = clk_pwm_scaler_get_rate, | ||
145 | .set_rate = clk_pwm_scaler_set_rate, | ||
146 | .round_rate = clk_pwm_scaler_round_rate, | ||
147 | }, | ||
148 | }; | ||
149 | |||
150 | static struct clk clk_timer_tclk[] = { | ||
151 | [0] = { | ||
152 | .name = "pwm-tclk0", | ||
153 | .id = -1, | ||
154 | }, | ||
155 | [1] = { | ||
156 | .name = "pwm-tclk1", | ||
157 | .id = -1, | ||
158 | }, | ||
159 | }; | ||
160 | |||
161 | struct pwm_tdiv_clk { | ||
162 | struct clk clk; | ||
163 | unsigned int divisor; | ||
164 | }; | ||
165 | |||
166 | static inline struct pwm_tdiv_clk *to_tdiv(struct clk *clk) | ||
167 | { | ||
168 | return container_of(clk, struct pwm_tdiv_clk, clk); | ||
169 | } | ||
170 | |||
171 | static unsigned long clk_pwm_tdiv_get_rate(struct clk *clk) | ||
172 | { | ||
173 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
174 | unsigned int divisor; | ||
175 | |||
176 | tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id); | ||
177 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
178 | |||
179 | if (pwm_cfg_src_is_tclk(tcfg1)) | ||
180 | divisor = to_tdiv(clk)->divisor; | ||
181 | else | ||
182 | divisor = tcfg_to_divisor(tcfg1); | ||
183 | |||
184 | return clk_get_rate(clk->parent) / divisor; | ||
185 | } | ||
186 | |||
187 | static unsigned long clk_pwm_tdiv_round_rate(struct clk *clk, | ||
188 | unsigned long rate) | ||
189 | { | ||
190 | unsigned long parent_rate; | ||
191 | unsigned long divisor; | ||
192 | |||
193 | parent_rate = clk_get_rate(clk->parent); | ||
194 | divisor = parent_rate / rate; | ||
195 | |||
196 | if (divisor <= 1 && pwm_tdiv_has_div1()) | ||
197 | divisor = 1; | ||
198 | else if (divisor <= 2) | ||
199 | divisor = 2; | ||
200 | else if (divisor <= 4) | ||
201 | divisor = 4; | ||
202 | else if (divisor <= 8) | ||
203 | divisor = 8; | ||
204 | else | ||
205 | divisor = 16; | ||
206 | |||
207 | return parent_rate / divisor; | ||
208 | } | ||
209 | |||
210 | static unsigned long clk_pwm_tdiv_bits(struct pwm_tdiv_clk *divclk) | ||
211 | { | ||
212 | return pwm_tdiv_div_bits(divclk->divisor); | ||
213 | } | ||
214 | |||
215 | static void clk_pwm_tdiv_update(struct pwm_tdiv_clk *divclk) | ||
216 | { | ||
217 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
218 | unsigned long bits = clk_pwm_tdiv_bits(divclk); | ||
219 | unsigned long flags; | ||
220 | unsigned long shift = S3C2410_TCFG1_SHIFT(divclk->clk.id); | ||
221 | |||
222 | local_irq_save(flags); | ||
223 | |||
224 | tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
225 | tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift); | ||
226 | tcfg1 |= bits << shift; | ||
227 | __raw_writel(tcfg1, S3C2410_TCFG1); | ||
228 | |||
229 | local_irq_restore(flags); | ||
230 | } | ||
231 | |||
232 | static int clk_pwm_tdiv_set_rate(struct clk *clk, unsigned long rate) | ||
233 | { | ||
234 | struct pwm_tdiv_clk *divclk = to_tdiv(clk); | ||
235 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
236 | unsigned long parent_rate = clk_get_rate(clk->parent); | ||
237 | unsigned long divisor; | ||
238 | |||
239 | tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id); | ||
240 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
241 | |||
242 | rate = clk_round_rate(clk, rate); | ||
243 | divisor = parent_rate / rate; | ||
244 | |||
245 | if (divisor > 16) | ||
246 | return -EINVAL; | ||
247 | |||
248 | divclk->divisor = divisor; | ||
249 | |||
250 | /* Update the current MUX settings if we are currently | ||
251 | * selected as the clock source for this clock. */ | ||
252 | |||
253 | if (!pwm_cfg_src_is_tclk(tcfg1)) | ||
254 | clk_pwm_tdiv_update(divclk); | ||
255 | |||
256 | return 0; | ||
257 | } | ||
258 | |||
259 | static struct pwm_tdiv_clk clk_timer_tdiv[] = { | ||
260 | [0] = { | ||
261 | .clk = { | ||
262 | .name = "pwm-tdiv", | ||
263 | .parent = &clk_timer_scaler[0], | ||
264 | .get_rate = clk_pwm_tdiv_get_rate, | ||
265 | .set_rate = clk_pwm_tdiv_set_rate, | ||
266 | .round_rate = clk_pwm_tdiv_round_rate, | ||
267 | }, | ||
268 | }, | ||
269 | [1] = { | ||
270 | .clk = { | ||
271 | .name = "pwm-tdiv", | ||
272 | .parent = &clk_timer_scaler[0], | ||
273 | .get_rate = clk_pwm_tdiv_get_rate, | ||
274 | .set_rate = clk_pwm_tdiv_set_rate, | ||
275 | .round_rate = clk_pwm_tdiv_round_rate, | ||
276 | } | ||
277 | }, | ||
278 | [2] = { | ||
279 | .clk = { | ||
280 | .name = "pwm-tdiv", | ||
281 | .parent = &clk_timer_scaler[1], | ||
282 | .get_rate = clk_pwm_tdiv_get_rate, | ||
283 | .set_rate = clk_pwm_tdiv_set_rate, | ||
284 | .round_rate = clk_pwm_tdiv_round_rate, | ||
285 | }, | ||
286 | }, | ||
287 | [3] = { | ||
288 | .clk = { | ||
289 | .name = "pwm-tdiv", | ||
290 | .parent = &clk_timer_scaler[1], | ||
291 | .get_rate = clk_pwm_tdiv_get_rate, | ||
292 | .set_rate = clk_pwm_tdiv_set_rate, | ||
293 | .round_rate = clk_pwm_tdiv_round_rate, | ||
294 | }, | ||
295 | }, | ||
296 | [4] = { | ||
297 | .clk = { | ||
298 | .name = "pwm-tdiv", | ||
299 | .parent = &clk_timer_scaler[1], | ||
300 | .get_rate = clk_pwm_tdiv_get_rate, | ||
301 | .set_rate = clk_pwm_tdiv_set_rate, | ||
302 | .round_rate = clk_pwm_tdiv_round_rate, | ||
303 | }, | ||
304 | }, | ||
305 | }; | ||
306 | |||
307 | static int __init clk_pwm_tdiv_register(unsigned int id) | ||
308 | { | ||
309 | struct pwm_tdiv_clk *divclk = &clk_timer_tdiv[id]; | ||
310 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
311 | |||
312 | tcfg1 >>= S3C2410_TCFG1_SHIFT(id); | ||
313 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
314 | |||
315 | divclk->clk.id = id; | ||
316 | divclk->divisor = tcfg_to_divisor(tcfg1); | ||
317 | |||
318 | return s3c24xx_register_clock(&divclk->clk); | ||
319 | } | ||
320 | |||
321 | static inline struct clk *s3c24xx_pwmclk_tclk(unsigned int id) | ||
322 | { | ||
323 | return (id >= 2) ? &clk_timer_tclk[1] : &clk_timer_tclk[0]; | ||
324 | } | ||
325 | |||
326 | static inline struct clk *s3c24xx_pwmclk_tdiv(unsigned int id) | ||
327 | { | ||
328 | return &clk_timer_tdiv[id].clk; | ||
329 | } | ||
330 | |||
331 | static int clk_pwm_tin_set_parent(struct clk *clk, struct clk *parent) | ||
332 | { | ||
333 | unsigned int id = clk->id; | ||
334 | unsigned long tcfg1; | ||
335 | unsigned long flags; | ||
336 | unsigned long bits; | ||
337 | unsigned long shift = S3C2410_TCFG1_SHIFT(id); | ||
338 | |||
339 | if (parent == s3c24xx_pwmclk_tclk(id)) | ||
340 | bits = S3C_TCFG1_MUX_TCLK << shift; | ||
341 | else if (parent == s3c24xx_pwmclk_tdiv(id)) | ||
342 | bits = clk_pwm_tdiv_bits(to_tdiv(parent)) << shift; | ||
343 | else | ||
344 | return -EINVAL; | ||
345 | |||
346 | clk->parent = parent; | ||
347 | |||
348 | local_irq_save(flags); | ||
349 | |||
350 | tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
351 | tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift); | ||
352 | __raw_writel(tcfg1 | bits, S3C2410_TCFG1); | ||
353 | |||
354 | local_irq_restore(flags); | ||
355 | |||
356 | return 0; | ||
357 | } | ||
358 | |||
359 | static struct clk clk_tin[] = { | ||
360 | [0] = { | ||
361 | .name = "pwm-tin", | ||
362 | .id = 0, | ||
363 | .set_parent = clk_pwm_tin_set_parent, | ||
364 | }, | ||
365 | [1] = { | ||
366 | .name = "pwm-tin", | ||
367 | .id = 1, | ||
368 | .set_parent = clk_pwm_tin_set_parent, | ||
369 | }, | ||
370 | [2] = { | ||
371 | .name = "pwm-tin", | ||
372 | .id = 2, | ||
373 | .set_parent = clk_pwm_tin_set_parent, | ||
374 | }, | ||
375 | [3] = { | ||
376 | .name = "pwm-tin", | ||
377 | .id = 3, | ||
378 | .set_parent = clk_pwm_tin_set_parent, | ||
379 | }, | ||
380 | [4] = { | ||
381 | .name = "pwm-tin", | ||
382 | .id = 4, | ||
383 | .set_parent = clk_pwm_tin_set_parent, | ||
384 | }, | ||
385 | }; | ||
386 | |||
387 | static __init int clk_pwm_tin_register(struct clk *pwm) | ||
388 | { | ||
389 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
390 | unsigned int id = pwm->id; | ||
391 | |||
392 | struct clk *parent; | ||
393 | int ret; | ||
394 | |||
395 | ret = s3c24xx_register_clock(pwm); | ||
396 | if (ret < 0) | ||
397 | return ret; | ||
398 | |||
399 | tcfg1 >>= S3C2410_TCFG1_SHIFT(id); | ||
400 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
401 | |||
402 | if (pwm_cfg_src_is_tclk(tcfg1)) | ||
403 | parent = s3c24xx_pwmclk_tclk(id); | ||
404 | else | ||
405 | parent = s3c24xx_pwmclk_tdiv(id); | ||
406 | |||
407 | return clk_set_parent(pwm, parent); | ||
408 | } | ||
409 | |||
410 | /** | ||
411 | * s3c_pwmclk_init() - initialise pwm clocks | ||
412 | * | ||
413 | * Initialise and register the clocks which provide the inputs for the | ||
414 | * pwm timer blocks. | ||
415 | * | ||
416 | * Note, this call is required by the time core, so must be called after | ||
417 | * the base clocks are added and before any of the initcalls are run. | ||
418 | */ | ||
419 | __init void s3c_pwmclk_init(void) | ||
420 | { | ||
421 | struct clk *clk_timers; | ||
422 | unsigned int clk; | ||
423 | int ret; | ||
424 | |||
425 | clk_timers = clk_get(NULL, "timers"); | ||
426 | if (IS_ERR(clk_timers)) { | ||
427 | printk(KERN_ERR "%s: no parent clock\n", __func__); | ||
428 | return; | ||
429 | } | ||
430 | |||
431 | for (clk = 0; clk < ARRAY_SIZE(clk_timer_scaler); clk++) { | ||
432 | clk_timer_scaler[clk].parent = clk_timers; | ||
433 | ret = s3c24xx_register_clock(&clk_timer_scaler[clk]); | ||
434 | if (ret < 0) { | ||
435 | printk(KERN_ERR "error adding pwm scaler%d clock\n", clk); | ||
436 | return; | ||
437 | } | ||
438 | } | ||
439 | |||
440 | for (clk = 0; clk < ARRAY_SIZE(clk_timer_tclk); clk++) { | ||
441 | ret = s3c24xx_register_clock(&clk_timer_tclk[clk]); | ||
442 | if (ret < 0) { | ||
443 | printk(KERN_ERR "error adding pww tclk%d\n", clk); | ||
444 | return; | ||
445 | } | ||
446 | } | ||
447 | |||
448 | for (clk = 0; clk < ARRAY_SIZE(clk_timer_tdiv); clk++) { | ||
449 | ret = clk_pwm_tdiv_register(clk); | ||
450 | if (ret < 0) { | ||
451 | printk(KERN_ERR "error adding pwm%d tdiv clock\n", clk); | ||
452 | return; | ||
453 | } | ||
454 | } | ||
455 | |||
456 | for (clk = 0; clk < ARRAY_SIZE(clk_tin); clk++) { | ||
457 | ret = clk_pwm_tin_register(&clk_tin[clk]); | ||
458 | if (ret < 0) { | ||
459 | printk(KERN_ERR "error adding pwm%d tin clock\n", clk); | ||
460 | return; | ||
461 | } | ||
462 | } | ||
463 | } | ||