aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm
diff options
context:
space:
mode:
authorBen Dooks <ben-linux@fluff.org>2008-07-01 08:17:24 -0400
committerBen Dooks <ben-linux@fluff.org>2008-07-03 11:51:30 -0400
commit6fc601e37bbb4045ee0afefc76b64284ea800c89 (patch)
treea13ef2ff40cd759b48a340225720ae6edfeef2d7 /arch/arm
parentd5c52922b618aa6ddd6b5ebebc8d0a5ec9a20f10 (diff)
[ARM] S3C24XX: PWM API support.
Add support for PWM in the S3C24XX series of SoC via the PWM API. Signed-off-by: Ben Dooks <ben-linux@fluff.org>
Diffstat (limited to 'arch/arm')
-rw-r--r--arch/arm/plat-s3c24xx/Kconfig7
-rw-r--r--arch/arm/plat-s3c24xx/Makefile1
-rw-r--r--arch/arm/plat-s3c24xx/devs.c100
-rw-r--r--arch/arm/plat-s3c24xx/pwm.c402
4 files changed, 410 insertions, 100 deletions
diff --git a/arch/arm/plat-s3c24xx/Kconfig b/arch/arm/plat-s3c24xx/Kconfig
index 1521f1ce3b2b..5e28c217b8c2 100644
--- a/arch/arm/plat-s3c24xx/Kconfig
+++ b/arch/arm/plat-s3c24xx/Kconfig
@@ -21,6 +21,13 @@ config CPU_S3C244X
21 help 21 help
22 Support for S3C2440 and S3C2442 Samsung Mobile CPU based systems. 22 Support for S3C2440 and S3C2442 Samsung Mobile CPU based systems.
23 23
24config S3C24XX_PWM
25 bool "PWM device support"
26 select HAVE_PWM
27 help
28 Support for exporting the PWM timer blocks via the pwm device
29 system.
30
24config PM_SIMTEC 31config PM_SIMTEC
25 bool 32 bool
26 help 33 help
diff --git a/arch/arm/plat-s3c24xx/Makefile b/arch/arm/plat-s3c24xx/Makefile
index c925d577fef4..d82767b2b833 100644
--- a/arch/arm/plat-s3c24xx/Makefile
+++ b/arch/arm/plat-s3c24xx/Makefile
@@ -29,5 +29,6 @@ obj-$(CONFIG_CPU_S3C244X) += s3c244x-clock.o
29obj-$(CONFIG_PM_SIMTEC) += pm-simtec.o 29obj-$(CONFIG_PM_SIMTEC) += pm-simtec.o
30obj-$(CONFIG_PM) += pm.o 30obj-$(CONFIG_PM) += pm.o
31obj-$(CONFIG_PM) += sleep.o 31obj-$(CONFIG_PM) += sleep.o
32obj-$(CONFIG_HAVE_PWM) += pwm.o
32obj-$(CONFIG_S3C2410_DMA) += dma.o 33obj-$(CONFIG_S3C2410_DMA) += dma.o
33obj-$(CONFIG_MACH_SMDK) += common-smdk.o 34obj-$(CONFIG_MACH_SMDK) += common-smdk.o
diff --git a/arch/arm/plat-s3c24xx/devs.c b/arch/arm/plat-s3c24xx/devs.c
index e546e933b3f7..eea3b32ff798 100644
--- a/arch/arm/plat-s3c24xx/devs.c
+++ b/arch/arm/plat-s3c24xx/devs.c
@@ -495,106 +495,6 @@ struct platform_device s3c_device_spi1 = {
495 495
496EXPORT_SYMBOL(s3c_device_spi1); 496EXPORT_SYMBOL(s3c_device_spi1);
497 497
498/* pwm timer blocks */
499
500static struct resource s3c_timer0_resource[] = {
501 [0] = {
502 .start = S3C24XX_PA_TIMER + 0x0C,
503 .end = S3C24XX_PA_TIMER + 0x0C + 0xB,
504 .flags = IORESOURCE_MEM,
505 },
506 [1] = {
507 .start = IRQ_TIMER0,
508 .end = IRQ_TIMER0,
509 .flags = IORESOURCE_IRQ,
510 }
511
512};
513
514struct platform_device s3c_device_timer0 = {
515 .name = "s3c2410-timer",
516 .id = 0,
517 .num_resources = ARRAY_SIZE(s3c_timer0_resource),
518 .resource = s3c_timer0_resource,
519};
520
521EXPORT_SYMBOL(s3c_device_timer0);
522
523/* timer 1 */
524
525static struct resource s3c_timer1_resource[] = {
526 [0] = {
527 .start = S3C24XX_PA_TIMER + 0x18,
528 .end = S3C24XX_PA_TIMER + 0x23,
529 .flags = IORESOURCE_MEM,
530 },
531 [1] = {
532 .start = IRQ_TIMER1,
533 .end = IRQ_TIMER1,
534 .flags = IORESOURCE_IRQ,
535 }
536
537};
538
539struct platform_device s3c_device_timer1 = {
540 .name = "s3c2410-timer",
541 .id = 1,
542 .num_resources = ARRAY_SIZE(s3c_timer1_resource),
543 .resource = s3c_timer1_resource,
544};
545
546EXPORT_SYMBOL(s3c_device_timer1);
547
548/* timer 2 */
549
550static struct resource s3c_timer2_resource[] = {
551 [0] = {
552 .start = S3C24XX_PA_TIMER + 0x24,
553 .end = S3C24XX_PA_TIMER + 0x2F,
554 .flags = IORESOURCE_MEM,
555 },
556 [1] = {
557 .start = IRQ_TIMER2,
558 .end = IRQ_TIMER2,
559 .flags = IORESOURCE_IRQ,
560 }
561
562};
563
564struct platform_device s3c_device_timer2 = {
565 .name = "s3c2410-timer",
566 .id = 2,
567 .num_resources = ARRAY_SIZE(s3c_timer2_resource),
568 .resource = s3c_timer2_resource,
569};
570
571EXPORT_SYMBOL(s3c_device_timer2);
572
573/* timer 3 */
574
575static struct resource s3c_timer3_resource[] = {
576 [0] = {
577 .start = S3C24XX_PA_TIMER + 0x30,
578 .end = S3C24XX_PA_TIMER + 0x3B,
579 .flags = IORESOURCE_MEM,
580 },
581 [1] = {
582 .start = IRQ_TIMER3,
583 .end = IRQ_TIMER3,
584 .flags = IORESOURCE_IRQ,
585 }
586
587};
588
589struct platform_device s3c_device_timer3 = {
590 .name = "s3c2410-timer",
591 .id = 3,
592 .num_resources = ARRAY_SIZE(s3c_timer3_resource),
593 .resource = s3c_timer3_resource,
594};
595
596EXPORT_SYMBOL(s3c_device_timer3);
597
598#ifdef CONFIG_CPU_S3C2440 498#ifdef CONFIG_CPU_S3C2440
599 499
600/* Camif Controller */ 500/* Camif Controller */
diff --git a/arch/arm/plat-s3c24xx/pwm.c b/arch/arm/plat-s3c24xx/pwm.c
new file mode 100644
index 000000000000..18c4bdc49a05
--- /dev/null
+++ b/arch/arm/plat-s3c24xx/pwm.c
@@ -0,0 +1,402 @@
1/* arch/arm/plat-s3c24xx/pwm.c
2 *
3 * Copyright (c) 2007 Ben Dooks
4 * Copyright (c) 2008 Simtec Electronics
5 * Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org>
6 *
7 * S3C24XX PWM device core
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License.
12*/
13
14#include <linux/module.h>
15#include <linux/kernel.h>
16#include <linux/platform_device.h>
17#include <linux/err.h>
18#include <linux/clk.h>
19#include <linux/io.h>
20#include <linux/pwm.h>
21
22#include <asm/plat-s3c/regs-timer.h>
23
24struct pwm_device {
25 struct list_head list;
26 struct platform_device *pdev;
27
28 struct clk *clk_div;
29 struct clk *clk;
30 const char *label;
31
32 unsigned int period_ns;
33 unsigned int duty_ns;
34
35 unsigned char tcon_base;
36 unsigned char running;
37 unsigned char use_count;
38 unsigned char pwm_id;
39};
40
41#define pwm_dbg(_pwm, msg...) dev_info(&(_pwm)->pdev->dev, msg)
42
43static struct clk *clk_scaler[2];
44
45/* Standard setup for a timer block. */
46
47#define TIMER_RESOURCE_SIZE (1)
48
49#define TIMER_RESOURCE(_tmr, _irq) \
50 (struct resource [TIMER_RESOURCE_SIZE]) { \
51 [0] = { \
52 .start = _irq, \
53 .end = _irq, \
54 .flags = IORESOURCE_IRQ \
55 } \
56 }
57
58#define DEFINE_TIMER(_tmr_no, _irq) \
59 .name = "s3c24xx-pwm", \
60 .id = _tmr_no, \
61 .num_resources = TIMER_RESOURCE_SIZE, \
62 .resource = TIMER_RESOURCE(_tmr_no, _irq), \
63
64/* since we already have an static mapping for the timer, we do not
65 * bother setting any IO resource for the base.
66 */
67
68struct platform_device s3c_device_timer[] = {
69 [0] = { DEFINE_TIMER(0, IRQ_TIMER0) },
70 [1] = { DEFINE_TIMER(1, IRQ_TIMER1) },
71 [2] = { DEFINE_TIMER(2, IRQ_TIMER2) },
72 [3] = { DEFINE_TIMER(3, IRQ_TIMER3) },
73 [4] = { DEFINE_TIMER(4, IRQ_TIMER4) },
74};
75
76static inline int pwm_is_tdiv(struct pwm_device *pwm)
77{
78 return clk_get_parent(pwm->clk) == pwm->clk_div;
79}
80
81static DEFINE_MUTEX(pwm_lock);
82static LIST_HEAD(pwm_list);
83
84struct pwm_device *pwm_request(int pwm_id, const char *label)
85{
86 struct pwm_device *pwm;
87 int found = 0;
88
89 mutex_lock(&pwm_lock);
90
91 list_for_each_entry(pwm, &pwm_list, list) {
92 if (pwm->pwm_id == pwm_id) {
93 found = 1;
94 break;
95 }
96 }
97
98 if (found) {
99 if (pwm->use_count == 0) {
100 pwm->use_count = 1;
101 pwm->label = label;
102 } else
103 pwm = ERR_PTR(-EBUSY);
104 } else
105 pwm = ERR_PTR(-ENOENT);
106
107 mutex_unlock(&pwm_lock);
108 return pwm;
109}
110
111EXPORT_SYMBOL(pwm_request);
112
113
114void pwm_free(struct pwm_device *pwm)
115{
116 mutex_lock(&pwm_lock);
117
118 if (pwm->use_count) {
119 pwm->use_count--;
120 pwm->label = NULL;
121 } else
122 printk(KERN_ERR "PWM%d device already freed\n", pwm->pwm_id);
123
124 mutex_unlock(&pwm_lock);
125}
126
127EXPORT_SYMBOL(pwm_free);
128
129#define pwm_tcon_start(pwm) (1 << (pwm->tcon_base + 0))
130#define pwm_tcon_invert(pwm) (1 << (pwm->tcon_base + 2))
131#define pwm_tcon_autoreload(pwm) (1 << (pwm->tcon_base + 3))
132#define pwm_tcon_manulupdate(pwm) (1 << (pwm->tcon_base + 1))
133
134int pwm_enable(struct pwm_device *pwm)
135{
136 unsigned long flags;
137 unsigned long tcon;
138
139 local_irq_save(flags);
140
141 tcon = __raw_readl(S3C2410_TCON);
142 tcon |= pwm_tcon_start(pwm);
143 __raw_writel(tcon, S3C2410_TCON);
144
145 local_irq_restore(flags);
146
147 pwm->running = 1;
148 return 0;
149}
150
151EXPORT_SYMBOL(pwm_enable);
152
153void pwm_disable(struct pwm_device *pwm)
154{
155 unsigned long flags;
156 unsigned long tcon;
157
158 local_irq_save(flags);
159
160 tcon = __raw_readl(S3C2410_TCON);
161 tcon &= ~pwm_tcon_start(pwm);
162 __raw_writel(tcon, S3C2410_TCON);
163
164 local_irq_restore(flags);
165
166 pwm->running = 0;
167}
168
169EXPORT_SYMBOL(pwm_disable);
170
171unsigned long pwm_calc_tin(struct pwm_device *pwm, unsigned long freq)
172{
173 unsigned long tin_parent_rate;
174 unsigned int div;
175
176 tin_parent_rate = clk_get_rate(clk_get_parent(pwm->clk_div));
177 pwm_dbg(pwm, "tin parent at %lu\n", tin_parent_rate);
178
179 for (div = 2; div <= 16; div *= 2) {
180 if ((tin_parent_rate / (div << 16)) < freq)
181 return tin_parent_rate / div;
182 }
183
184 return tin_parent_rate / 16;
185}
186
187#define NS_IN_HZ (1000000000UL)
188
189int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
190{
191 unsigned long tin_rate;
192 unsigned long tin_ns;
193 unsigned long period;
194 unsigned long flags;
195 unsigned long tcon;
196 unsigned long tcnt;
197 long tcmp;
198
199 /* We currently avoid using 64bit arithmetic by using the
200 * fact that anything faster than 1Hz is easily representable
201 * by 32bits. */
202
203 if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ)
204 return -ERANGE;
205
206 if (duty_ns > period_ns)
207 return -EINVAL;
208
209 if (period_ns == pwm->period_ns &&
210 duty_ns == pwm->duty_ns)
211 return 0;
212
213 /* The TCMP and TCNT can be read without a lock, they're not
214 * shared between the timers. */
215
216 tcmp = __raw_readl(S3C2410_TCMPB(pwm->pwm_id));
217 tcnt = __raw_readl(S3C2410_TCNTB(pwm->pwm_id));
218
219 period = NS_IN_HZ / period_ns;
220
221 pwm_dbg(pwm, "duty_ns=%d, period_ns=%d (%lu)\n",
222 duty_ns, period_ns, period);
223
224 /* Check to see if we are changing the clock rate of the PWM */
225
226 if (pwm->period_ns != period_ns) {
227 if (pwm_is_tdiv(pwm)) {
228 tin_rate = pwm_calc_tin(pwm, period);
229 clk_set_rate(pwm->clk_div, tin_rate);
230 } else
231 tin_rate = clk_get_rate(pwm->clk);
232
233 pwm->period_ns = period_ns;
234
235 pwm_dbg(pwm, "tin_rate=%lu\n", tin_rate);
236
237 tin_ns = NS_IN_HZ / tin_rate;
238 tcnt = period_ns / tin_ns;
239 } else
240 tin_ns = NS_IN_HZ / clk_get_rate(pwm->clk);
241
242 /* Note, counters count down */
243
244 tcmp = duty_ns / tin_ns;
245 tcmp = tcnt - tcmp;
246
247 pwm_dbg(pwm, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt);
248
249 if (tcmp < 0)
250 tcmp = 0;
251
252 /* Update the PWM register block. */
253
254 local_irq_save(flags);
255
256 __raw_writel(tcmp, S3C2410_TCMPB(pwm->pwm_id));
257 __raw_writel(tcnt, S3C2410_TCNTB(pwm->pwm_id));
258
259 tcon = __raw_readl(S3C2410_TCON);
260 tcon |= pwm_tcon_manulupdate(pwm);
261 tcon |= pwm_tcon_autoreload(pwm);
262 __raw_writel(tcon, S3C2410_TCON);
263
264 tcon &= ~pwm_tcon_manulupdate(pwm);
265 __raw_writel(tcon, S3C2410_TCON);
266
267 local_irq_restore(flags);
268
269 return 0;
270}
271
272EXPORT_SYMBOL(pwm_config);
273
274static int pwm_register(struct pwm_device *pwm)
275{
276 pwm->duty_ns = -1;
277 pwm->period_ns = -1;
278
279 mutex_lock(&pwm_lock);
280 list_add_tail(&pwm->list, &pwm_list);
281 mutex_unlock(&pwm_lock);
282
283 return 0;
284}
285
286static int s3c_pwm_probe(struct platform_device *pdev)
287{
288 struct device *dev = &pdev->dev;
289 struct pwm_device *pwm;
290 unsigned long flags;
291 unsigned long tcon;
292 unsigned int id = pdev->id;
293 int ret;
294
295 if (id == 4) {
296 dev_err(dev, "TIMER4 is currently not supported\n");
297 return -ENXIO;
298 }
299
300 pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
301 if (pwm == NULL) {
302 dev_err(dev, "failed to allocate pwm_device\n");
303 return -ENOMEM;
304 }
305
306 pwm->pdev = pdev;
307 pwm->pwm_id = id;
308
309 /* calculate base of control bits in TCON */
310 pwm->tcon_base = id == 0 ? 0 : (id * 4) + 4;
311
312 pwm->clk = clk_get(dev, "pwm-tin");
313 if (IS_ERR(pwm->clk)) {
314 dev_err(dev, "failed to get pwm tin clk\n");
315 ret = PTR_ERR(pwm->clk);
316 goto err_alloc;
317 }
318
319 pwm->clk_div = clk_get(dev, "pwm-tdiv");
320 if (IS_ERR(pwm->clk_div)) {
321 dev_err(dev, "failed to get pwm tdiv clk\n");
322 ret = PTR_ERR(pwm->clk_div);
323 goto err_clk_tin;
324 }
325
326 local_irq_save(flags);
327
328 tcon = __raw_readl(S3C2410_TCON);
329 tcon |= pwm_tcon_invert(pwm);
330 __raw_writel(tcon, S3C2410_TCON);
331
332 local_irq_restore(flags);
333
334
335 ret = pwm_register(pwm);
336 if (ret) {
337 dev_err(dev, "failed to register pwm\n");
338 goto err_clk_tdiv;
339 }
340
341 pwm_dbg(pwm, "config bits %02x\n",
342 (__raw_readl(S3C2410_TCON) >> pwm->tcon_base) & 0x0f);
343
344 dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n",
345 clk_get_rate(pwm->clk),
346 clk_get_rate(pwm->clk_div),
347 pwm_is_tdiv(pwm) ? "div" : "ext", pwm->tcon_base);
348
349 platform_set_drvdata(pdev, pwm);
350 return 0;
351
352 err_clk_tdiv:
353 clk_put(pwm->clk_div);
354
355 err_clk_tin:
356 clk_put(pwm->clk);
357
358 err_alloc:
359 kfree(pwm);
360 return ret;
361}
362
363static int s3c_pwm_remove(struct platform_device *pdev)
364{
365 struct pwm_device *pwm = platform_get_drvdata(pdev);
366
367 clk_put(pwm->clk_div);
368 clk_put(pwm->clk);
369 kfree(pwm);
370
371 return 0;
372}
373
374static struct platform_driver s3c_pwm_driver = {
375 .driver = {
376 .name = "s3c24xx-pwm",
377 .owner = THIS_MODULE,
378 },
379 .probe = s3c_pwm_probe,
380 .remove = __devexit_p(s3c_pwm_remove),
381};
382
383static int __init pwm_init(void)
384{
385 int ret;
386
387 clk_scaler[0] = clk_get(NULL, "pwm-scaler0");
388 clk_scaler[1] = clk_get(NULL, "pwm-scaler1");
389
390 if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) {
391 printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__);
392 return -EINVAL;
393 }
394
395 ret = platform_driver_register(&s3c_pwm_driver);
396 if (ret)
397 printk(KERN_ERR "%s: failed to add pwm driver\n", __func__);
398
399 return ret;
400}
401
402arch_initcall(pwm_init);