diff options
Diffstat (limited to 'arch/arm/plat-s3c/pwm-clock.c')
-rw-r--r-- | arch/arm/plat-s3c/pwm-clock.c | 482 |
1 files changed, 482 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..e07d82891a92 --- /dev/null +++ b/arch/arm/plat-s3c/pwm-clock.c | |||
@@ -0,0 +1,482 @@ | |||
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/clk.h> | ||
18 | #include <linux/err.h> | ||
19 | #include <linux/io.h> | ||
20 | |||
21 | #include <mach/hardware.h> | ||
22 | #include <asm/irq.h> | ||
23 | |||
24 | #include <mach/regs-clock.h> | ||
25 | #include <mach/regs-gpio.h> | ||
26 | |||
27 | #include <plat/clock.h> | ||
28 | #include <plat/cpu.h> | ||
29 | |||
30 | #include <plat/regs-timer.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 inline unsigned long tcfg_to_divisor(unsigned long tcfg1) | ||
172 | { | ||
173 | return 1 << (1 + tcfg1); | ||
174 | } | ||
175 | |||
176 | static unsigned long clk_pwm_tdiv_get_rate(struct clk *clk) | ||
177 | { | ||
178 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
179 | unsigned int divisor; | ||
180 | |||
181 | tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id); | ||
182 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
183 | |||
184 | if (tcfg1 == S3C2410_TCFG1_MUX_TCLK) | ||
185 | divisor = to_tdiv(clk)->divisor; | ||
186 | else | ||
187 | divisor = tcfg_to_divisor(tcfg1); | ||
188 | |||
189 | return clk_get_rate(clk->parent) / divisor; | ||
190 | } | ||
191 | |||
192 | static unsigned long clk_pwm_tdiv_round_rate(struct clk *clk, | ||
193 | unsigned long rate) | ||
194 | { | ||
195 | unsigned long parent_rate; | ||
196 | unsigned long divisor; | ||
197 | |||
198 | parent_rate = clk_get_rate(clk->parent); | ||
199 | divisor = parent_rate / rate; | ||
200 | |||
201 | if (divisor <= 2) | ||
202 | divisor = 2; | ||
203 | else if (divisor <= 4) | ||
204 | divisor = 4; | ||
205 | else if (divisor <= 8) | ||
206 | divisor = 8; | ||
207 | else | ||
208 | divisor = 16; | ||
209 | |||
210 | return parent_rate / divisor; | ||
211 | } | ||
212 | |||
213 | static unsigned long clk_pwm_tdiv_bits(struct pwm_tdiv_clk *divclk) | ||
214 | { | ||
215 | unsigned long bits; | ||
216 | |||
217 | switch (divclk->divisor) { | ||
218 | case 2: | ||
219 | bits = S3C2410_TCFG1_MUX_DIV2; | ||
220 | break; | ||
221 | case 4: | ||
222 | bits = S3C2410_TCFG1_MUX_DIV4; | ||
223 | break; | ||
224 | case 8: | ||
225 | bits = S3C2410_TCFG1_MUX_DIV8; | ||
226 | break; | ||
227 | case 16: | ||
228 | default: | ||
229 | bits = S3C2410_TCFG1_MUX_DIV16; | ||
230 | break; | ||
231 | } | ||
232 | |||
233 | return bits; | ||
234 | } | ||
235 | |||
236 | static void clk_pwm_tdiv_update(struct pwm_tdiv_clk *divclk) | ||
237 | { | ||
238 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
239 | unsigned long bits = clk_pwm_tdiv_bits(divclk); | ||
240 | unsigned long flags; | ||
241 | unsigned long shift = S3C2410_TCFG1_SHIFT(divclk->clk.id); | ||
242 | |||
243 | local_irq_save(flags); | ||
244 | |||
245 | tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
246 | tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift); | ||
247 | tcfg1 |= bits << shift; | ||
248 | __raw_writel(tcfg1, S3C2410_TCFG1); | ||
249 | |||
250 | local_irq_restore(flags); | ||
251 | } | ||
252 | |||
253 | static int clk_pwm_tdiv_set_rate(struct clk *clk, unsigned long rate) | ||
254 | { | ||
255 | struct pwm_tdiv_clk *divclk = to_tdiv(clk); | ||
256 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
257 | unsigned long parent_rate = clk_get_rate(clk->parent); | ||
258 | unsigned long divisor; | ||
259 | |||
260 | tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id); | ||
261 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
262 | |||
263 | rate = clk_round_rate(clk, rate); | ||
264 | divisor = parent_rate / rate; | ||
265 | |||
266 | if (divisor > 16) | ||
267 | return -EINVAL; | ||
268 | |||
269 | divclk->divisor = divisor; | ||
270 | |||
271 | /* Update the current MUX settings if we are currently | ||
272 | * selected as the clock source for this clock. */ | ||
273 | |||
274 | if (tcfg1 != S3C2410_TCFG1_MUX_TCLK) | ||
275 | clk_pwm_tdiv_update(divclk); | ||
276 | |||
277 | return 0; | ||
278 | } | ||
279 | |||
280 | static struct pwm_tdiv_clk clk_timer_tdiv[] = { | ||
281 | [0] = { | ||
282 | .clk = { | ||
283 | .name = "pwm-tdiv", | ||
284 | .parent = &clk_timer_scaler[0], | ||
285 | .get_rate = clk_pwm_tdiv_get_rate, | ||
286 | .set_rate = clk_pwm_tdiv_set_rate, | ||
287 | .round_rate = clk_pwm_tdiv_round_rate, | ||
288 | }, | ||
289 | }, | ||
290 | [1] = { | ||
291 | .clk = { | ||
292 | .name = "pwm-tdiv", | ||
293 | .parent = &clk_timer_scaler[0], | ||
294 | .get_rate = clk_pwm_tdiv_get_rate, | ||
295 | .set_rate = clk_pwm_tdiv_set_rate, | ||
296 | .round_rate = clk_pwm_tdiv_round_rate, | ||
297 | } | ||
298 | }, | ||
299 | [2] = { | ||
300 | .clk = { | ||
301 | .name = "pwm-tdiv", | ||
302 | .parent = &clk_timer_scaler[1], | ||
303 | .get_rate = clk_pwm_tdiv_get_rate, | ||
304 | .set_rate = clk_pwm_tdiv_set_rate, | ||
305 | .round_rate = clk_pwm_tdiv_round_rate, | ||
306 | }, | ||
307 | }, | ||
308 | [3] = { | ||
309 | .clk = { | ||
310 | .name = "pwm-tdiv", | ||
311 | .parent = &clk_timer_scaler[1], | ||
312 | .get_rate = clk_pwm_tdiv_get_rate, | ||
313 | .set_rate = clk_pwm_tdiv_set_rate, | ||
314 | .round_rate = clk_pwm_tdiv_round_rate, | ||
315 | }, | ||
316 | }, | ||
317 | [4] = { | ||
318 | .clk = { | ||
319 | .name = "pwm-tdiv", | ||
320 | .parent = &clk_timer_scaler[1], | ||
321 | .get_rate = clk_pwm_tdiv_get_rate, | ||
322 | .set_rate = clk_pwm_tdiv_set_rate, | ||
323 | .round_rate = clk_pwm_tdiv_round_rate, | ||
324 | }, | ||
325 | }, | ||
326 | }; | ||
327 | |||
328 | static int __init clk_pwm_tdiv_register(unsigned int id) | ||
329 | { | ||
330 | struct pwm_tdiv_clk *divclk = &clk_timer_tdiv[id]; | ||
331 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
332 | |||
333 | tcfg1 >>= S3C2410_TCFG1_SHIFT(id); | ||
334 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
335 | |||
336 | divclk->clk.id = id; | ||
337 | divclk->divisor = tcfg_to_divisor(tcfg1); | ||
338 | |||
339 | return s3c24xx_register_clock(&divclk->clk); | ||
340 | } | ||
341 | |||
342 | static inline struct clk *s3c24xx_pwmclk_tclk(unsigned int id) | ||
343 | { | ||
344 | return (id >= 2) ? &clk_timer_tclk[1] : &clk_timer_tclk[0]; | ||
345 | } | ||
346 | |||
347 | static inline struct clk *s3c24xx_pwmclk_tdiv(unsigned int id) | ||
348 | { | ||
349 | return &clk_timer_tdiv[id].clk; | ||
350 | } | ||
351 | |||
352 | static int clk_pwm_tin_set_parent(struct clk *clk, struct clk *parent) | ||
353 | { | ||
354 | unsigned int id = clk->id; | ||
355 | unsigned long tcfg1; | ||
356 | unsigned long flags; | ||
357 | unsigned long bits; | ||
358 | unsigned long shift = S3C2410_TCFG1_SHIFT(id); | ||
359 | |||
360 | if (parent == s3c24xx_pwmclk_tclk(id)) | ||
361 | bits = S3C2410_TCFG1_MUX_TCLK << shift; | ||
362 | else if (parent == s3c24xx_pwmclk_tdiv(id)) | ||
363 | bits = clk_pwm_tdiv_bits(to_tdiv(parent)) << shift; | ||
364 | else | ||
365 | return -EINVAL; | ||
366 | |||
367 | clk->parent = parent; | ||
368 | |||
369 | local_irq_save(flags); | ||
370 | |||
371 | tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
372 | tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift); | ||
373 | __raw_writel(tcfg1 | bits, S3C2410_TCFG1); | ||
374 | |||
375 | local_irq_restore(flags); | ||
376 | |||
377 | return 0; | ||
378 | } | ||
379 | |||
380 | static struct clk clk_tin[] = { | ||
381 | [0] = { | ||
382 | .name = "pwm-tin", | ||
383 | .id = 0, | ||
384 | .set_parent = clk_pwm_tin_set_parent, | ||
385 | }, | ||
386 | [1] = { | ||
387 | .name = "pwm-tin", | ||
388 | .id = 1, | ||
389 | .set_parent = clk_pwm_tin_set_parent, | ||
390 | }, | ||
391 | [2] = { | ||
392 | .name = "pwm-tin", | ||
393 | .id = 2, | ||
394 | .set_parent = clk_pwm_tin_set_parent, | ||
395 | }, | ||
396 | [3] = { | ||
397 | .name = "pwm-tin", | ||
398 | .id = 3, | ||
399 | .set_parent = clk_pwm_tin_set_parent, | ||
400 | }, | ||
401 | [4] = { | ||
402 | .name = "pwm-tin", | ||
403 | .id = 4, | ||
404 | .set_parent = clk_pwm_tin_set_parent, | ||
405 | }, | ||
406 | }; | ||
407 | |||
408 | static __init int clk_pwm_tin_register(struct clk *pwm) | ||
409 | { | ||
410 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
411 | unsigned int id = pwm->id; | ||
412 | |||
413 | struct clk *parent; | ||
414 | int ret; | ||
415 | |||
416 | ret = s3c24xx_register_clock(pwm); | ||
417 | if (ret < 0) | ||
418 | return ret; | ||
419 | |||
420 | tcfg1 >>= S3C2410_TCFG1_SHIFT(id); | ||
421 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
422 | |||
423 | if (tcfg1 == S3C2410_TCFG1_MUX_TCLK) | ||
424 | parent = s3c24xx_pwmclk_tclk(id); | ||
425 | else | ||
426 | parent = s3c24xx_pwmclk_tdiv(id); | ||
427 | |||
428 | return clk_set_parent(pwm, parent); | ||
429 | } | ||
430 | |||
431 | static __init int s3c24xx_pwmclk_init(void) | ||
432 | { | ||
433 | struct clk *clk_timers; | ||
434 | unsigned int clk; | ||
435 | int ret; | ||
436 | |||
437 | clk_timers = clk_get(NULL, "timers"); | ||
438 | if (IS_ERR(clk_timers)) { | ||
439 | printk(KERN_ERR "%s: no parent clock\n", __func__); | ||
440 | return -EINVAL; | ||
441 | } | ||
442 | |||
443 | for (clk = 0; clk < ARRAY_SIZE(clk_timer_scaler); clk++) { | ||
444 | clk_timer_scaler[clk].parent = clk_timers; | ||
445 | ret = s3c24xx_register_clock(&clk_timer_scaler[clk]); | ||
446 | if (ret < 0) { | ||
447 | printk(KERN_ERR "error adding pwm scaler%d clock\n", clk); | ||
448 | goto err; | ||
449 | } | ||
450 | } | ||
451 | |||
452 | for (clk = 0; clk < ARRAY_SIZE(clk_timer_tclk); clk++) { | ||
453 | ret = s3c24xx_register_clock(&clk_timer_tclk[clk]); | ||
454 | if (ret < 0) { | ||
455 | printk(KERN_ERR "error adding pww tclk%d\n", clk); | ||
456 | goto err; | ||
457 | } | ||
458 | } | ||
459 | |||
460 | for (clk = 0; clk < ARRAY_SIZE(clk_timer_tdiv); clk++) { | ||
461 | ret = clk_pwm_tdiv_register(clk); | ||
462 | if (ret < 0) { | ||
463 | printk(KERN_ERR "error adding pwm%d tdiv clock\n", clk); | ||
464 | goto err; | ||
465 | } | ||
466 | } | ||
467 | |||
468 | for (clk = 0; clk < ARRAY_SIZE(clk_tin); clk++) { | ||
469 | ret = clk_pwm_tin_register(&clk_tin[clk]); | ||
470 | if (ret < 0) { | ||
471 | printk(KERN_ERR "error adding pwm%d tin clock\n", clk); | ||
472 | goto err; | ||
473 | } | ||
474 | } | ||
475 | |||
476 | return 0; | ||
477 | |||
478 | err: | ||
479 | return ret; | ||
480 | } | ||
481 | |||
482 | arch_initcall(s3c24xx_pwmclk_init); | ||