diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2012-03-15 05:04:36 -0400 |
---|---|---|
committer | Thierry Reding <thierry.reding@avionic-design.de> | 2012-07-02 15:39:02 -0400 |
commit | 215c29d3d0e925189ade522d1ea6052a320d7692 (patch) | |
tree | 8540120b08bc764be976d9503acd93ebdd9d8fdb /drivers/pwm/pwm-samsung.c | |
parent | 29693248edf10830db2ef82b36806e378ff75c67 (diff) |
ARM Samsung: Move s3c pwm driver to pwm framework
Move the driver to drivers/pwm/ and convert it to use the framework.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Cc: Ben Dooks <ben-linux@fluff.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
[eric@eukrea.com: fix pwmchip_add return code test]
Signed-off-by: Eric Bénard <eric@eukrea.com>
Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
Diffstat (limited to 'drivers/pwm/pwm-samsung.c')
-rw-r--r-- | drivers/pwm/pwm-samsung.c | 366 |
1 files changed, 366 insertions, 0 deletions
diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c new file mode 100644 index 000000000000..c40c37e968e6 --- /dev/null +++ b/drivers/pwm/pwm-samsung.c | |||
@@ -0,0 +1,366 @@ | |||
1 | /* arch/arm/plat-s3c/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 | * S3C series 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/export.h> | ||
15 | #include <linux/kernel.h> | ||
16 | #include <linux/platform_device.h> | ||
17 | #include <linux/slab.h> | ||
18 | #include <linux/err.h> | ||
19 | #include <linux/clk.h> | ||
20 | #include <linux/io.h> | ||
21 | #include <linux/pwm.h> | ||
22 | |||
23 | #include <mach/map.h> | ||
24 | |||
25 | #include <plat/regs-timer.h> | ||
26 | |||
27 | struct s3c_chip { | ||
28 | struct platform_device *pdev; | ||
29 | |||
30 | struct clk *clk_div; | ||
31 | struct clk *clk; | ||
32 | const char *label; | ||
33 | |||
34 | unsigned int period_ns; | ||
35 | unsigned int duty_ns; | ||
36 | |||
37 | unsigned char tcon_base; | ||
38 | unsigned char pwm_id; | ||
39 | struct pwm_chip chip; | ||
40 | }; | ||
41 | |||
42 | #define to_s3c_chip(chip) container_of(chip, struct s3c_chip, chip) | ||
43 | |||
44 | #define pwm_dbg(_pwm, msg...) dev_dbg(&(_pwm)->pdev->dev, msg) | ||
45 | |||
46 | static struct clk *clk_scaler[2]; | ||
47 | |||
48 | static inline int pwm_is_tdiv(struct s3c_chip *chip) | ||
49 | { | ||
50 | return clk_get_parent(chip->clk) == chip->clk_div; | ||
51 | } | ||
52 | |||
53 | #define pwm_tcon_start(pwm) (1 << (pwm->tcon_base + 0)) | ||
54 | #define pwm_tcon_invert(pwm) (1 << (pwm->tcon_base + 2)) | ||
55 | #define pwm_tcon_autoreload(pwm) (1 << (pwm->tcon_base + 3)) | ||
56 | #define pwm_tcon_manulupdate(pwm) (1 << (pwm->tcon_base + 1)) | ||
57 | |||
58 | static int s3c_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
59 | { | ||
60 | struct s3c_chip *s3c = to_s3c_chip(chip); | ||
61 | unsigned long flags; | ||
62 | unsigned long tcon; | ||
63 | |||
64 | local_irq_save(flags); | ||
65 | |||
66 | tcon = __raw_readl(S3C2410_TCON); | ||
67 | tcon |= pwm_tcon_start(s3c); | ||
68 | __raw_writel(tcon, S3C2410_TCON); | ||
69 | |||
70 | local_irq_restore(flags); | ||
71 | |||
72 | return 0; | ||
73 | } | ||
74 | |||
75 | static void s3c_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
76 | { | ||
77 | struct s3c_chip *s3c = to_s3c_chip(chip); | ||
78 | unsigned long flags; | ||
79 | unsigned long tcon; | ||
80 | |||
81 | local_irq_save(flags); | ||
82 | |||
83 | tcon = __raw_readl(S3C2410_TCON); | ||
84 | tcon &= ~pwm_tcon_start(s3c); | ||
85 | __raw_writel(tcon, S3C2410_TCON); | ||
86 | |||
87 | local_irq_restore(flags); | ||
88 | } | ||
89 | |||
90 | static unsigned long pwm_calc_tin(struct s3c_chip *s3c, unsigned long freq) | ||
91 | { | ||
92 | unsigned long tin_parent_rate; | ||
93 | unsigned int div; | ||
94 | |||
95 | tin_parent_rate = clk_get_rate(clk_get_parent(s3c->clk_div)); | ||
96 | pwm_dbg(s3c, "tin parent at %lu\n", tin_parent_rate); | ||
97 | |||
98 | for (div = 2; div <= 16; div *= 2) { | ||
99 | if ((tin_parent_rate / (div << 16)) < freq) | ||
100 | return tin_parent_rate / div; | ||
101 | } | ||
102 | |||
103 | return tin_parent_rate / 16; | ||
104 | } | ||
105 | |||
106 | #define NS_IN_HZ (1000000000UL) | ||
107 | |||
108 | static int s3c_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | ||
109 | int duty_ns, int period_ns) | ||
110 | { | ||
111 | struct s3c_chip *s3c = to_s3c_chip(chip); | ||
112 | unsigned long tin_rate; | ||
113 | unsigned long tin_ns; | ||
114 | unsigned long period; | ||
115 | unsigned long flags; | ||
116 | unsigned long tcon; | ||
117 | unsigned long tcnt; | ||
118 | long tcmp; | ||
119 | |||
120 | /* We currently avoid using 64bit arithmetic by using the | ||
121 | * fact that anything faster than 1Hz is easily representable | ||
122 | * by 32bits. */ | ||
123 | |||
124 | if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ) | ||
125 | return -ERANGE; | ||
126 | |||
127 | if (duty_ns > period_ns) | ||
128 | return -EINVAL; | ||
129 | |||
130 | if (period_ns == s3c->period_ns && | ||
131 | duty_ns == s3c->duty_ns) | ||
132 | return 0; | ||
133 | |||
134 | /* The TCMP and TCNT can be read without a lock, they're not | ||
135 | * shared between the timers. */ | ||
136 | |||
137 | tcmp = __raw_readl(S3C2410_TCMPB(s3c->pwm_id)); | ||
138 | tcnt = __raw_readl(S3C2410_TCNTB(s3c->pwm_id)); | ||
139 | |||
140 | period = NS_IN_HZ / period_ns; | ||
141 | |||
142 | pwm_dbg(s3c, "duty_ns=%d, period_ns=%d (%lu)\n", | ||
143 | duty_ns, period_ns, period); | ||
144 | |||
145 | /* Check to see if we are changing the clock rate of the PWM */ | ||
146 | |||
147 | if (s3c->period_ns != period_ns) { | ||
148 | if (pwm_is_tdiv(s3c)) { | ||
149 | tin_rate = pwm_calc_tin(s3c, period); | ||
150 | clk_set_rate(s3c->clk_div, tin_rate); | ||
151 | } else | ||
152 | tin_rate = clk_get_rate(s3c->clk); | ||
153 | |||
154 | s3c->period_ns = period_ns; | ||
155 | |||
156 | pwm_dbg(s3c, "tin_rate=%lu\n", tin_rate); | ||
157 | |||
158 | tin_ns = NS_IN_HZ / tin_rate; | ||
159 | tcnt = period_ns / tin_ns; | ||
160 | } else | ||
161 | tin_ns = NS_IN_HZ / clk_get_rate(s3c->clk); | ||
162 | |||
163 | /* Note, counters count down */ | ||
164 | |||
165 | tcmp = duty_ns / tin_ns; | ||
166 | tcmp = tcnt - tcmp; | ||
167 | /* the pwm hw only checks the compare register after a decrement, | ||
168 | so the pin never toggles if tcmp = tcnt */ | ||
169 | if (tcmp == tcnt) | ||
170 | tcmp--; | ||
171 | |||
172 | pwm_dbg(s3c, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt); | ||
173 | |||
174 | if (tcmp < 0) | ||
175 | tcmp = 0; | ||
176 | |||
177 | /* Update the PWM register block. */ | ||
178 | |||
179 | local_irq_save(flags); | ||
180 | |||
181 | __raw_writel(tcmp, S3C2410_TCMPB(s3c->pwm_id)); | ||
182 | __raw_writel(tcnt, S3C2410_TCNTB(s3c->pwm_id)); | ||
183 | |||
184 | tcon = __raw_readl(S3C2410_TCON); | ||
185 | tcon |= pwm_tcon_manulupdate(s3c); | ||
186 | tcon |= pwm_tcon_autoreload(s3c); | ||
187 | __raw_writel(tcon, S3C2410_TCON); | ||
188 | |||
189 | tcon &= ~pwm_tcon_manulupdate(s3c); | ||
190 | __raw_writel(tcon, S3C2410_TCON); | ||
191 | |||
192 | local_irq_restore(flags); | ||
193 | |||
194 | return 0; | ||
195 | } | ||
196 | |||
197 | static struct pwm_ops s3c_pwm_ops = { | ||
198 | .enable = s3c_pwm_enable, | ||
199 | .disable = s3c_pwm_disable, | ||
200 | .config = s3c_pwm_config, | ||
201 | .owner = THIS_MODULE, | ||
202 | }; | ||
203 | |||
204 | static int s3c_pwm_probe(struct platform_device *pdev) | ||
205 | { | ||
206 | struct device *dev = &pdev->dev; | ||
207 | struct s3c_chip *s3c; | ||
208 | unsigned long flags; | ||
209 | unsigned long tcon; | ||
210 | unsigned int id = pdev->id; | ||
211 | int ret; | ||
212 | |||
213 | if (id == 4) { | ||
214 | dev_err(dev, "TIMER4 is currently not supported\n"); | ||
215 | return -ENXIO; | ||
216 | } | ||
217 | |||
218 | s3c = kzalloc(sizeof(*s3c), GFP_KERNEL); | ||
219 | if (s3c == NULL) { | ||
220 | dev_err(dev, "failed to allocate pwm_device\n"); | ||
221 | return -ENOMEM; | ||
222 | } | ||
223 | |||
224 | /* calculate base of control bits in TCON */ | ||
225 | s3c->tcon_base = id == 0 ? 0 : (id * 4) + 4; | ||
226 | s3c->chip.ops = &s3c_pwm_ops; | ||
227 | s3c->chip.base = -1; | ||
228 | s3c->chip.npwm = 1; | ||
229 | |||
230 | s3c->clk = clk_get(dev, "pwm-tin"); | ||
231 | if (IS_ERR(s3c->clk)) { | ||
232 | dev_err(dev, "failed to get pwm tin clk\n"); | ||
233 | ret = PTR_ERR(s3c->clk); | ||
234 | goto err_alloc; | ||
235 | } | ||
236 | |||
237 | s3c->clk_div = clk_get(dev, "pwm-tdiv"); | ||
238 | if (IS_ERR(s3c->clk_div)) { | ||
239 | dev_err(dev, "failed to get pwm tdiv clk\n"); | ||
240 | ret = PTR_ERR(s3c->clk_div); | ||
241 | goto err_clk_tin; | ||
242 | } | ||
243 | |||
244 | clk_enable(s3c->clk); | ||
245 | clk_enable(s3c->clk_div); | ||
246 | |||
247 | local_irq_save(flags); | ||
248 | |||
249 | tcon = __raw_readl(S3C2410_TCON); | ||
250 | tcon |= pwm_tcon_invert(s3c); | ||
251 | __raw_writel(tcon, S3C2410_TCON); | ||
252 | |||
253 | local_irq_restore(flags); | ||
254 | |||
255 | ret = pwmchip_add(&s3c->chip); | ||
256 | if (ret < 0) { | ||
257 | dev_err(dev, "failed to register pwm\n"); | ||
258 | goto err_clk_tdiv; | ||
259 | } | ||
260 | |||
261 | pwm_dbg(s3c, "config bits %02x\n", | ||
262 | (__raw_readl(S3C2410_TCON) >> s3c->tcon_base) & 0x0f); | ||
263 | |||
264 | dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n", | ||
265 | clk_get_rate(s3c->clk), | ||
266 | clk_get_rate(s3c->clk_div), | ||
267 | pwm_is_tdiv(s3c) ? "div" : "ext", s3c->tcon_base); | ||
268 | |||
269 | platform_set_drvdata(pdev, s3c); | ||
270 | return 0; | ||
271 | |||
272 | err_clk_tdiv: | ||
273 | clk_disable(s3c->clk_div); | ||
274 | clk_disable(s3c->clk); | ||
275 | clk_put(s3c->clk_div); | ||
276 | |||
277 | err_clk_tin: | ||
278 | clk_put(s3c->clk); | ||
279 | |||
280 | err_alloc: | ||
281 | kfree(s3c); | ||
282 | return ret; | ||
283 | } | ||
284 | |||
285 | static int __devexit s3c_pwm_remove(struct platform_device *pdev) | ||
286 | { | ||
287 | struct s3c_chip *s3c = platform_get_drvdata(pdev); | ||
288 | int err; | ||
289 | |||
290 | err = pwmchip_remove(&s3c->chip); | ||
291 | if (err < 0) | ||
292 | return err; | ||
293 | |||
294 | clk_disable(s3c->clk_div); | ||
295 | clk_disable(s3c->clk); | ||
296 | clk_put(s3c->clk_div); | ||
297 | clk_put(s3c->clk); | ||
298 | kfree(s3c); | ||
299 | |||
300 | return 0; | ||
301 | } | ||
302 | |||
303 | #ifdef CONFIG_PM | ||
304 | static int s3c_pwm_suspend(struct platform_device *pdev, pm_message_t state) | ||
305 | { | ||
306 | struct s3c_chip *s3c = platform_get_drvdata(pdev); | ||
307 | |||
308 | /* No one preserve these values during suspend so reset them | ||
309 | * Otherwise driver leaves PWM unconfigured if same values | ||
310 | * passed to pwm_config | ||
311 | */ | ||
312 | s3c->period_ns = 0; | ||
313 | s3c->duty_ns = 0; | ||
314 | |||
315 | return 0; | ||
316 | } | ||
317 | |||
318 | static int s3c_pwm_resume(struct platform_device *pdev) | ||
319 | { | ||
320 | struct s3c_chip *s3c = platform_get_drvdata(pdev); | ||
321 | unsigned long tcon; | ||
322 | |||
323 | /* Restore invertion */ | ||
324 | tcon = __raw_readl(S3C2410_TCON); | ||
325 | tcon |= pwm_tcon_invert(s3c); | ||
326 | __raw_writel(tcon, S3C2410_TCON); | ||
327 | |||
328 | return 0; | ||
329 | } | ||
330 | |||
331 | #else | ||
332 | #define s3c_pwm_suspend NULL | ||
333 | #define s3c_pwm_resume NULL | ||
334 | #endif | ||
335 | |||
336 | static struct platform_driver s3c_pwm_driver = { | ||
337 | .driver = { | ||
338 | .name = "s3c24xx-pwm", | ||
339 | .owner = THIS_MODULE, | ||
340 | }, | ||
341 | .probe = s3c_pwm_probe, | ||
342 | .remove = __devexit_p(s3c_pwm_remove), | ||
343 | .suspend = s3c_pwm_suspend, | ||
344 | .resume = s3c_pwm_resume, | ||
345 | }; | ||
346 | |||
347 | static int __init pwm_init(void) | ||
348 | { | ||
349 | int ret; | ||
350 | |||
351 | clk_scaler[0] = clk_get(NULL, "pwm-scaler0"); | ||
352 | clk_scaler[1] = clk_get(NULL, "pwm-scaler1"); | ||
353 | |||
354 | if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) { | ||
355 | printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__); | ||
356 | return -EINVAL; | ||
357 | } | ||
358 | |||
359 | ret = platform_driver_register(&s3c_pwm_driver); | ||
360 | if (ret) | ||
361 | printk(KERN_ERR "%s: failed to add pwm driver\n", __func__); | ||
362 | |||
363 | return ret; | ||
364 | } | ||
365 | |||
366 | arch_initcall(pwm_init); | ||