diff options
Diffstat (limited to 'arch/arm/plat-samsung/pwm-clock.c')
-rw-r--r-- | arch/arm/plat-samsung/pwm-clock.c | 455 |
1 files changed, 455 insertions, 0 deletions
diff --git a/arch/arm/plat-samsung/pwm-clock.c b/arch/arm/plat-samsung/pwm-clock.c new file mode 100644 index 000000000000..46c9381e083b --- /dev/null +++ b/arch/arm/plat-samsung/pwm-clock.c | |||
@@ -0,0 +1,455 @@ | |||
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_ops clk_pwm_scaler_ops = { | ||
134 | .get_rate = clk_pwm_scaler_get_rate, | ||
135 | .set_rate = clk_pwm_scaler_set_rate, | ||
136 | .round_rate = clk_pwm_scaler_round_rate, | ||
137 | }; | ||
138 | |||
139 | static struct clk clk_timer_scaler[] = { | ||
140 | [0] = { | ||
141 | .name = "pwm-scaler0", | ||
142 | .id = -1, | ||
143 | .ops = &clk_pwm_scaler_ops, | ||
144 | }, | ||
145 | [1] = { | ||
146 | .name = "pwm-scaler1", | ||
147 | .id = -1, | ||
148 | .ops = &clk_pwm_scaler_ops, | ||
149 | }, | ||
150 | }; | ||
151 | |||
152 | static struct clk clk_timer_tclk[] = { | ||
153 | [0] = { | ||
154 | .name = "pwm-tclk0", | ||
155 | .id = -1, | ||
156 | }, | ||
157 | [1] = { | ||
158 | .name = "pwm-tclk1", | ||
159 | .id = -1, | ||
160 | }, | ||
161 | }; | ||
162 | |||
163 | struct pwm_tdiv_clk { | ||
164 | struct clk clk; | ||
165 | unsigned int divisor; | ||
166 | }; | ||
167 | |||
168 | static inline struct pwm_tdiv_clk *to_tdiv(struct clk *clk) | ||
169 | { | ||
170 | return container_of(clk, struct pwm_tdiv_clk, clk); | ||
171 | } | ||
172 | |||
173 | static unsigned long clk_pwm_tdiv_get_rate(struct clk *clk) | ||
174 | { | ||
175 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
176 | unsigned int divisor; | ||
177 | |||
178 | tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id); | ||
179 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
180 | |||
181 | if (pwm_cfg_src_is_tclk(tcfg1)) | ||
182 | divisor = to_tdiv(clk)->divisor; | ||
183 | else | ||
184 | divisor = tcfg_to_divisor(tcfg1); | ||
185 | |||
186 | return clk_get_rate(clk->parent) / divisor; | ||
187 | } | ||
188 | |||
189 | static unsigned long clk_pwm_tdiv_round_rate(struct clk *clk, | ||
190 | unsigned long rate) | ||
191 | { | ||
192 | unsigned long parent_rate; | ||
193 | unsigned long divisor; | ||
194 | |||
195 | parent_rate = clk_get_rate(clk->parent); | ||
196 | divisor = parent_rate / rate; | ||
197 | |||
198 | if (divisor <= 1 && pwm_tdiv_has_div1()) | ||
199 | divisor = 1; | ||
200 | else if (divisor <= 2) | ||
201 | divisor = 2; | ||
202 | else if (divisor <= 4) | ||
203 | divisor = 4; | ||
204 | else if (divisor <= 8) | ||
205 | divisor = 8; | ||
206 | else | ||
207 | divisor = 16; | ||
208 | |||
209 | return parent_rate / divisor; | ||
210 | } | ||
211 | |||
212 | static unsigned long clk_pwm_tdiv_bits(struct pwm_tdiv_clk *divclk) | ||
213 | { | ||
214 | return pwm_tdiv_div_bits(divclk->divisor); | ||
215 | } | ||
216 | |||
217 | static void clk_pwm_tdiv_update(struct pwm_tdiv_clk *divclk) | ||
218 | { | ||
219 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
220 | unsigned long bits = clk_pwm_tdiv_bits(divclk); | ||
221 | unsigned long flags; | ||
222 | unsigned long shift = S3C2410_TCFG1_SHIFT(divclk->clk.id); | ||
223 | |||
224 | local_irq_save(flags); | ||
225 | |||
226 | tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
227 | tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift); | ||
228 | tcfg1 |= bits << shift; | ||
229 | __raw_writel(tcfg1, S3C2410_TCFG1); | ||
230 | |||
231 | local_irq_restore(flags); | ||
232 | } | ||
233 | |||
234 | static int clk_pwm_tdiv_set_rate(struct clk *clk, unsigned long rate) | ||
235 | { | ||
236 | struct pwm_tdiv_clk *divclk = to_tdiv(clk); | ||
237 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
238 | unsigned long parent_rate = clk_get_rate(clk->parent); | ||
239 | unsigned long divisor; | ||
240 | |||
241 | tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id); | ||
242 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
243 | |||
244 | rate = clk_round_rate(clk, rate); | ||
245 | divisor = parent_rate / rate; | ||
246 | |||
247 | if (divisor > 16) | ||
248 | return -EINVAL; | ||
249 | |||
250 | divclk->divisor = divisor; | ||
251 | |||
252 | /* Update the current MUX settings if we are currently | ||
253 | * selected as the clock source for this clock. */ | ||
254 | |||
255 | if (!pwm_cfg_src_is_tclk(tcfg1)) | ||
256 | clk_pwm_tdiv_update(divclk); | ||
257 | |||
258 | return 0; | ||
259 | } | ||
260 | |||
261 | static struct clk_ops clk_tdiv_ops = { | ||
262 | .get_rate = clk_pwm_tdiv_get_rate, | ||
263 | .set_rate = clk_pwm_tdiv_set_rate, | ||
264 | .round_rate = clk_pwm_tdiv_round_rate, | ||
265 | }; | ||
266 | |||
267 | static struct pwm_tdiv_clk clk_timer_tdiv[] = { | ||
268 | [0] = { | ||
269 | .clk = { | ||
270 | .name = "pwm-tdiv", | ||
271 | .ops = &clk_tdiv_ops, | ||
272 | .parent = &clk_timer_scaler[0], | ||
273 | }, | ||
274 | }, | ||
275 | [1] = { | ||
276 | .clk = { | ||
277 | .name = "pwm-tdiv", | ||
278 | .ops = &clk_tdiv_ops, | ||
279 | .parent = &clk_timer_scaler[0], | ||
280 | } | ||
281 | }, | ||
282 | [2] = { | ||
283 | .clk = { | ||
284 | .name = "pwm-tdiv", | ||
285 | .ops = &clk_tdiv_ops, | ||
286 | .parent = &clk_timer_scaler[1], | ||
287 | }, | ||
288 | }, | ||
289 | [3] = { | ||
290 | .clk = { | ||
291 | .name = "pwm-tdiv", | ||
292 | .ops = &clk_tdiv_ops, | ||
293 | .parent = &clk_timer_scaler[1], | ||
294 | }, | ||
295 | }, | ||
296 | [4] = { | ||
297 | .clk = { | ||
298 | .name = "pwm-tdiv", | ||
299 | .ops = &clk_tdiv_ops, | ||
300 | .parent = &clk_timer_scaler[1], | ||
301 | }, | ||
302 | }, | ||
303 | }; | ||
304 | |||
305 | static int __init clk_pwm_tdiv_register(unsigned int id) | ||
306 | { | ||
307 | struct pwm_tdiv_clk *divclk = &clk_timer_tdiv[id]; | ||
308 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
309 | |||
310 | tcfg1 >>= S3C2410_TCFG1_SHIFT(id); | ||
311 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
312 | |||
313 | divclk->clk.id = id; | ||
314 | divclk->divisor = tcfg_to_divisor(tcfg1); | ||
315 | |||
316 | return s3c24xx_register_clock(&divclk->clk); | ||
317 | } | ||
318 | |||
319 | static inline struct clk *s3c24xx_pwmclk_tclk(unsigned int id) | ||
320 | { | ||
321 | return (id >= 2) ? &clk_timer_tclk[1] : &clk_timer_tclk[0]; | ||
322 | } | ||
323 | |||
324 | static inline struct clk *s3c24xx_pwmclk_tdiv(unsigned int id) | ||
325 | { | ||
326 | return &clk_timer_tdiv[id].clk; | ||
327 | } | ||
328 | |||
329 | static int clk_pwm_tin_set_parent(struct clk *clk, struct clk *parent) | ||
330 | { | ||
331 | unsigned int id = clk->id; | ||
332 | unsigned long tcfg1; | ||
333 | unsigned long flags; | ||
334 | unsigned long bits; | ||
335 | unsigned long shift = S3C2410_TCFG1_SHIFT(id); | ||
336 | |||
337 | if (parent == s3c24xx_pwmclk_tclk(id)) | ||
338 | bits = S3C_TCFG1_MUX_TCLK << shift; | ||
339 | else if (parent == s3c24xx_pwmclk_tdiv(id)) | ||
340 | bits = clk_pwm_tdiv_bits(to_tdiv(parent)) << shift; | ||
341 | else | ||
342 | return -EINVAL; | ||
343 | |||
344 | clk->parent = parent; | ||
345 | |||
346 | local_irq_save(flags); | ||
347 | |||
348 | tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
349 | tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift); | ||
350 | __raw_writel(tcfg1 | bits, S3C2410_TCFG1); | ||
351 | |||
352 | local_irq_restore(flags); | ||
353 | |||
354 | return 0; | ||
355 | } | ||
356 | |||
357 | static struct clk_ops clk_tin_ops = { | ||
358 | .set_parent = clk_pwm_tin_set_parent, | ||
359 | }; | ||
360 | |||
361 | static struct clk clk_tin[] = { | ||
362 | [0] = { | ||
363 | .name = "pwm-tin", | ||
364 | .id = 0, | ||
365 | .ops = &clk_tin_ops, | ||
366 | }, | ||
367 | [1] = { | ||
368 | .name = "pwm-tin", | ||
369 | .id = 1, | ||
370 | .ops = &clk_tin_ops, | ||
371 | }, | ||
372 | [2] = { | ||
373 | .name = "pwm-tin", | ||
374 | .id = 2, | ||
375 | .ops = &clk_tin_ops, | ||
376 | }, | ||
377 | [3] = { | ||
378 | .name = "pwm-tin", | ||
379 | .id = 3, | ||
380 | .ops = &clk_tin_ops, | ||
381 | }, | ||
382 | [4] = { | ||
383 | .name = "pwm-tin", | ||
384 | .id = 4, | ||
385 | .ops = &clk_tin_ops, | ||
386 | }, | ||
387 | }; | ||
388 | |||
389 | static __init int clk_pwm_tin_register(struct clk *pwm) | ||
390 | { | ||
391 | unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); | ||
392 | unsigned int id = pwm->id; | ||
393 | |||
394 | struct clk *parent; | ||
395 | int ret; | ||
396 | |||
397 | ret = s3c24xx_register_clock(pwm); | ||
398 | if (ret < 0) | ||
399 | return ret; | ||
400 | |||
401 | tcfg1 >>= S3C2410_TCFG1_SHIFT(id); | ||
402 | tcfg1 &= S3C2410_TCFG1_MUX_MASK; | ||
403 | |||
404 | if (pwm_cfg_src_is_tclk(tcfg1)) | ||
405 | parent = s3c24xx_pwmclk_tclk(id); | ||
406 | else | ||
407 | parent = s3c24xx_pwmclk_tdiv(id); | ||
408 | |||
409 | return clk_set_parent(pwm, parent); | ||
410 | } | ||
411 | |||
412 | /** | ||
413 | * s3c_pwmclk_init() - initialise pwm clocks | ||
414 | * | ||
415 | * Initialise and register the clocks which provide the inputs for the | ||
416 | * pwm timer blocks. | ||
417 | * | ||
418 | * Note, this call is required by the time core, so must be called after | ||
419 | * the base clocks are added and before any of the initcalls are run. | ||
420 | */ | ||
421 | __init void s3c_pwmclk_init(void) | ||
422 | { | ||
423 | struct clk *clk_timers; | ||
424 | unsigned int clk; | ||
425 | int ret; | ||
426 | |||
427 | clk_timers = clk_get(NULL, "timers"); | ||
428 | if (IS_ERR(clk_timers)) { | ||
429 | printk(KERN_ERR "%s: no parent clock\n", __func__); | ||
430 | return; | ||
431 | } | ||
432 | |||
433 | for (clk = 0; clk < ARRAY_SIZE(clk_timer_scaler); clk++) | ||
434 | clk_timer_scaler[clk].parent = clk_timers; | ||
435 | |||
436 | s3c_register_clocks(clk_timer_scaler, ARRAY_SIZE(clk_timer_scaler)); | ||
437 | s3c_register_clocks(clk_timer_tclk, ARRAY_SIZE(clk_timer_tclk)); | ||
438 | |||
439 | for (clk = 0; clk < ARRAY_SIZE(clk_timer_tdiv); clk++) { | ||
440 | ret = clk_pwm_tdiv_register(clk); | ||
441 | |||
442 | if (ret < 0) { | ||
443 | printk(KERN_ERR "error adding pwm%d tdiv clock\n", clk); | ||
444 | return; | ||
445 | } | ||
446 | } | ||
447 | |||
448 | for (clk = 0; clk < ARRAY_SIZE(clk_tin); clk++) { | ||
449 | ret = clk_pwm_tin_register(&clk_tin[clk]); | ||
450 | if (ret < 0) { | ||
451 | printk(KERN_ERR "error adding pwm%d tin clock\n", clk); | ||
452 | return; | ||
453 | } | ||
454 | } | ||
455 | } | ||