diff options
author | Ingo Molnar <mingo@elte.hu> | 2008-07-18 13:53:16 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2008-07-18 13:53:16 -0400 |
commit | 9b610fda0df5d0f0b0c64242e37441ad1b384aac (patch) | |
tree | 0ea14b15f2e6546f37fe18d8ac3dc83077ec0e55 /arch/arm/plat-s3c24xx/pwm-clock.c | |
parent | b8f8c3cf0a4ac0632ec3f0e15e9dc0c29de917af (diff) | |
parent | 5b664cb235e97afbf34db9c4d77f08ebd725335e (diff) |
Merge branch 'linus' into timers/nohz
Diffstat (limited to 'arch/arm/plat-s3c24xx/pwm-clock.c')
-rw-r--r-- | arch/arm/plat-s3c24xx/pwm-clock.c | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/arch/arm/plat-s3c24xx/pwm-clock.c b/arch/arm/plat-s3c24xx/pwm-clock.c new file mode 100644 index 000000000000..2cda3e3c6786 --- /dev/null +++ b/arch/arm/plat-s3c24xx/pwm-clock.c | |||
@@ -0,0 +1,437 @@ | |||
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 <asm/hardware.h> | ||
22 | #include <asm/irq.h> | ||
23 | |||
24 | #include <asm/arch/regs-clock.h> | ||
25 | #include <asm/arch/regs-gpio.h> | ||
26 | |||
27 | #include <asm/plat-s3c24xx/clock.h> | ||
28 | #include <asm/plat-s3c24xx/cpu.h> | ||
29 | |||
30 | #include <asm/plat-s3c/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 unsigned long clk_pwm_scaler_getrate(struct clk *clk) | ||
77 | { | ||
78 | unsigned long tcfg0 = __raw_readl(S3C2410_TCFG0); | ||
79 | |||
80 | if (clk->id == 1) { | ||
81 | tcfg0 &= S3C2410_TCFG_PRESCALER1_MASK; | ||
82 | tcfg0 >>= S3C2410_TCFG_PRESCALER1_SHIFT; | ||
83 | } else { | ||
84 | tcfg0 &= S3C2410_TCFG_PRESCALER0_MASK; | ||
85 | } | ||
86 | |||
87 | return clk_get_rate(clk->parent) / (tcfg0 + 1); | ||
88 | } | ||
89 | |||
90 | /* TODO - add set rate calls. */ | ||
91 | |||
92 | struct clk clk_timer_scaler[] = { | ||
93 | [0] = { | ||
94 | .name = "pwm-scaler0", | ||
95 | .id = -1, | ||
96 | .get_rate = clk_pwm_scaler_getrate, | ||
97 | }, | ||
98 | [1] = { | ||
99 | .name = "pwm-scaler1", | ||
100 | .id = -1, | ||
101 | .get_rate = clk_pwm_scaler_getrate, | ||
102 | }, | ||
103 | }; | ||
104 | |||
105 | struct clk clk_timer_tclk[] = { | ||
106 | [0] = { | ||
107 | .name = "pwm-tclk0", | ||
108 | .id = -1, | ||
109 | }, | ||
110 | [1] = { | ||
111 | .name = "pwm-tclk1", | ||
112 | .id = -1, | ||
113 | }, | ||
114 | }; | ||
115 | |||
116 | struct pwm_tdiv_clk { | ||
117 | struct clk clk; | ||
118 | unsigned int divisor; | ||
119 | }; | ||
120 | |||
121 | static inline struct pwm_tdiv_clk *to_tdiv(struct clk *clk) | ||
122 | { | ||
123 | return container_of(clk, struct pwm_tdiv_clk, clk); | ||
124 | } | ||
125 | |||
126 | static inline unsigned long tcfg_to_divisor(unsigned long tcfg1) | ||
127 | { | ||
128 | return 1 << (1 + tcfg1); | ||
129 | } | ||
130 | |||
131 | static unsigned long clk_pwm_tdiv_get_rate(struct clk *clk) | ||
132 | { | ||
133 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
134 | unsigned int divisor; | ||
135 | |||
136 | tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id); | ||
137 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
138 | |||
139 | if (tcfg1 == S3C2410_TCFG1_MUX_TCLK) | ||
140 | divisor = to_tdiv(clk)->divisor; | ||
141 | else | ||
142 | divisor = tcfg_to_divisor(tcfg1); | ||
143 | |||
144 | return clk_get_rate(clk->parent) / divisor; | ||
145 | } | ||
146 | |||
147 | static unsigned long clk_pwm_tdiv_round_rate(struct clk *clk, | ||
148 | unsigned long rate) | ||
149 | { | ||
150 | unsigned long parent_rate; | ||
151 | unsigned long divisor; | ||
152 | |||
153 | parent_rate = clk_get_rate(clk->parent); | ||
154 | divisor = parent_rate / rate; | ||
155 | |||
156 | if (divisor <= 2) | ||
157 | divisor = 2; | ||
158 | else if (divisor <= 4) | ||
159 | divisor = 4; | ||
160 | else if (divisor <= 8) | ||
161 | divisor = 8; | ||
162 | else | ||
163 | divisor = 16; | ||
164 | |||
165 | return parent_rate / divisor; | ||
166 | } | ||
167 | |||
168 | static unsigned long clk_pwm_tdiv_bits(struct pwm_tdiv_clk *divclk) | ||
169 | { | ||
170 | unsigned long bits; | ||
171 | |||
172 | switch (divclk->divisor) { | ||
173 | case 2: | ||
174 | bits = S3C2410_TCFG1_MUX_DIV2; | ||
175 | break; | ||
176 | case 4: | ||
177 | bits = S3C2410_TCFG1_MUX_DIV4; | ||
178 | break; | ||
179 | case 8: | ||
180 | bits = S3C2410_TCFG1_MUX_DIV8; | ||
181 | break; | ||
182 | case 16: | ||
183 | default: | ||
184 | bits = S3C2410_TCFG1_MUX_DIV16; | ||
185 | break; | ||
186 | } | ||
187 | |||
188 | return bits; | ||
189 | } | ||
190 | |||
191 | static void clk_pwm_tdiv_update(struct pwm_tdiv_clk *divclk) | ||
192 | { | ||
193 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
194 | unsigned long bits = clk_pwm_tdiv_bits(divclk); | ||
195 | unsigned long flags; | ||
196 | unsigned long shift = S3C2410_TCFG1_SHIFT(divclk->clk.id); | ||
197 | |||
198 | local_irq_save(flags); | ||
199 | |||
200 | tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
201 | tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift); | ||
202 | tcfg1 |= bits << shift; | ||
203 | __raw_writel(tcfg1, S3C2410_TCFG1); | ||
204 | |||
205 | local_irq_restore(flags); | ||
206 | } | ||
207 | |||
208 | static int clk_pwm_tdiv_set_rate(struct clk *clk, unsigned long rate) | ||
209 | { | ||
210 | struct pwm_tdiv_clk *divclk = to_tdiv(clk); | ||
211 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
212 | unsigned long parent_rate = clk_get_rate(clk->parent); | ||
213 | unsigned long divisor; | ||
214 | |||
215 | tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id); | ||
216 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
217 | |||
218 | rate = clk_round_rate(clk, rate); | ||
219 | divisor = parent_rate / rate; | ||
220 | |||
221 | if (divisor > 16) | ||
222 | return -EINVAL; | ||
223 | |||
224 | divclk->divisor = divisor; | ||
225 | |||
226 | /* Update the current MUX settings if we are currently | ||
227 | * selected as the clock source for this clock. */ | ||
228 | |||
229 | if (tcfg1 != S3C2410_TCFG1_MUX_TCLK) | ||
230 | clk_pwm_tdiv_update(divclk); | ||
231 | |||
232 | return 0; | ||
233 | } | ||
234 | |||
235 | struct pwm_tdiv_clk clk_timer_tdiv[] = { | ||
236 | [0] = { | ||
237 | .clk = { | ||
238 | .name = "pwm-tdiv", | ||
239 | .parent = &clk_timer_scaler[0], | ||
240 | .get_rate = clk_pwm_tdiv_get_rate, | ||
241 | .set_rate = clk_pwm_tdiv_set_rate, | ||
242 | .round_rate = clk_pwm_tdiv_round_rate, | ||
243 | }, | ||
244 | }, | ||
245 | [1] = { | ||
246 | .clk = { | ||
247 | .name = "pwm-tdiv", | ||
248 | .parent = &clk_timer_scaler[0], | ||
249 | .get_rate = clk_pwm_tdiv_get_rate, | ||
250 | .set_rate = clk_pwm_tdiv_set_rate, | ||
251 | .round_rate = clk_pwm_tdiv_round_rate, | ||
252 | } | ||
253 | }, | ||
254 | [2] = { | ||
255 | .clk = { | ||
256 | .name = "pwm-tdiv", | ||
257 | .parent = &clk_timer_scaler[1], | ||
258 | .get_rate = clk_pwm_tdiv_get_rate, | ||
259 | .set_rate = clk_pwm_tdiv_set_rate, | ||
260 | .round_rate = clk_pwm_tdiv_round_rate, | ||
261 | }, | ||
262 | }, | ||
263 | [3] = { | ||
264 | .clk = { | ||
265 | .name = "pwm-tdiv", | ||
266 | .parent = &clk_timer_scaler[1], | ||
267 | .get_rate = clk_pwm_tdiv_get_rate, | ||
268 | .set_rate = clk_pwm_tdiv_set_rate, | ||
269 | .round_rate = clk_pwm_tdiv_round_rate, | ||
270 | }, | ||
271 | }, | ||
272 | [4] = { | ||
273 | .clk = { | ||
274 | .name = "pwm-tdiv", | ||
275 | .parent = &clk_timer_scaler[1], | ||
276 | .get_rate = clk_pwm_tdiv_get_rate, | ||
277 | .set_rate = clk_pwm_tdiv_set_rate, | ||
278 | .round_rate = clk_pwm_tdiv_round_rate, | ||
279 | }, | ||
280 | }, | ||
281 | }; | ||
282 | |||
283 | static int __init clk_pwm_tdiv_register(unsigned int id) | ||
284 | { | ||
285 | struct pwm_tdiv_clk *divclk = &clk_timer_tdiv[id]; | ||
286 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
287 | |||
288 | tcfg1 >>= S3C2410_TCFG1_SHIFT(id); | ||
289 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
290 | |||
291 | divclk->clk.id = id; | ||
292 | divclk->divisor = tcfg_to_divisor(tcfg1); | ||
293 | |||
294 | return s3c24xx_register_clock(&divclk->clk); | ||
295 | } | ||
296 | |||
297 | static inline struct clk *s3c24xx_pwmclk_tclk(unsigned int id) | ||
298 | { | ||
299 | return (id >= 2) ? &clk_timer_tclk[1] : &clk_timer_tclk[0]; | ||
300 | } | ||
301 | |||
302 | static inline struct clk *s3c24xx_pwmclk_tdiv(unsigned int id) | ||
303 | { | ||
304 | return &clk_timer_tdiv[id].clk; | ||
305 | } | ||
306 | |||
307 | static int clk_pwm_tin_set_parent(struct clk *clk, struct clk *parent) | ||
308 | { | ||
309 | unsigned int id = clk->id; | ||
310 | unsigned long tcfg1; | ||
311 | unsigned long flags; | ||
312 | unsigned long bits; | ||
313 | unsigned long shift = S3C2410_TCFG1_SHIFT(id); | ||
314 | |||
315 | if (parent == s3c24xx_pwmclk_tclk(id)) | ||
316 | bits = S3C2410_TCFG1_MUX_TCLK << shift; | ||
317 | else if (parent == s3c24xx_pwmclk_tdiv(id)) | ||
318 | bits = clk_pwm_tdiv_bits(to_tdiv(clk)) << shift; | ||
319 | else | ||
320 | return -EINVAL; | ||
321 | |||
322 | clk->parent = parent; | ||
323 | |||
324 | local_irq_save(flags); | ||
325 | |||
326 | tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
327 | tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift); | ||
328 | __raw_writel(tcfg1 | bits, S3C2410_TCFG1); | ||
329 | |||
330 | local_irq_restore(flags); | ||
331 | |||
332 | return 0; | ||
333 | } | ||
334 | |||
335 | static struct clk clk_tin[] = { | ||
336 | [0] = { | ||
337 | .name = "pwm-tin", | ||
338 | .id = 0, | ||
339 | .set_parent = clk_pwm_tin_set_parent, | ||
340 | }, | ||
341 | [1] = { | ||
342 | .name = "pwm-tin", | ||
343 | .id = 1, | ||
344 | .set_parent = clk_pwm_tin_set_parent, | ||
345 | }, | ||
346 | [2] = { | ||
347 | .name = "pwm-tin", | ||
348 | .id = 2, | ||
349 | .set_parent = clk_pwm_tin_set_parent, | ||
350 | }, | ||
351 | [3] = { | ||
352 | .name = "pwm-tin", | ||
353 | .id = 3, | ||
354 | .set_parent = clk_pwm_tin_set_parent, | ||
355 | }, | ||
356 | [4] = { | ||
357 | .name = "pwm-tin", | ||
358 | .id = 4, | ||
359 | .set_parent = clk_pwm_tin_set_parent, | ||
360 | }, | ||
361 | }; | ||
362 | |||
363 | static __init int clk_pwm_tin_register(struct clk *pwm) | ||
364 | { | ||
365 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
366 | unsigned int id = pwm->id; | ||
367 | |||
368 | struct clk *parent; | ||
369 | int ret; | ||
370 | |||
371 | ret = s3c24xx_register_clock(pwm); | ||
372 | if (ret < 0) | ||
373 | return ret; | ||
374 | |||
375 | tcfg1 >>= S3C2410_TCFG1_SHIFT(id); | ||
376 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
377 | |||
378 | if (tcfg1 == S3C2410_TCFG1_MUX_TCLK) | ||
379 | parent = s3c24xx_pwmclk_tclk(id); | ||
380 | else | ||
381 | parent = s3c24xx_pwmclk_tdiv(id); | ||
382 | |||
383 | return clk_set_parent(pwm, parent); | ||
384 | } | ||
385 | |||
386 | static __init int s3c24xx_pwmclk_init(void) | ||
387 | { | ||
388 | struct clk *clk_timers; | ||
389 | unsigned int clk; | ||
390 | int ret; | ||
391 | |||
392 | clk_timers = clk_get(NULL, "timers"); | ||
393 | if (IS_ERR(clk_timers)) { | ||
394 | printk(KERN_ERR "%s: no parent clock\n", __func__); | ||
395 | return -EINVAL; | ||
396 | } | ||
397 | |||
398 | for (clk = 0; clk < ARRAY_SIZE(clk_timer_scaler); clk++) { | ||
399 | clk_timer_scaler[clk].parent = clk_timers; | ||
400 | ret = s3c24xx_register_clock(&clk_timer_scaler[clk]); | ||
401 | if (ret < 0) { | ||
402 | printk(KERN_ERR "error adding pwm scaler%d clock\n", clk); | ||
403 | goto err; | ||
404 | } | ||
405 | } | ||
406 | |||
407 | for (clk = 0; clk < ARRAY_SIZE(clk_timer_tclk); clk++) { | ||
408 | ret = s3c24xx_register_clock(&clk_timer_tclk[clk]); | ||
409 | if (ret < 0) { | ||
410 | printk(KERN_ERR "error adding pww tclk%d\n", clk); | ||
411 | goto err; | ||
412 | } | ||
413 | } | ||
414 | |||
415 | for (clk = 0; clk < ARRAY_SIZE(clk_timer_tdiv); clk++) { | ||
416 | ret = clk_pwm_tdiv_register(clk); | ||
417 | if (ret < 0) { | ||
418 | printk(KERN_ERR "error adding pwm%d tdiv clock\n", clk); | ||
419 | goto err; | ||
420 | } | ||
421 | } | ||
422 | |||
423 | for (clk = 0; clk < ARRAY_SIZE(clk_tin); clk++) { | ||
424 | ret = clk_pwm_tin_register(&clk_tin[clk]); | ||
425 | if (ret < 0) { | ||
426 | printk(KERN_ERR "error adding pwm%d tin clock\n", clk); | ||
427 | goto err; | ||
428 | } | ||
429 | } | ||
430 | |||
431 | return 0; | ||
432 | |||
433 | err: | ||
434 | return ret; | ||
435 | } | ||
436 | |||
437 | arch_initcall(s3c24xx_pwmclk_init); | ||