aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/misc
diff options
context:
space:
mode:
authorHartley Sweeten <hartleys@visionengravers.com>2009-07-29 17:41:06 -0400
committerRussell King <rmk+kernel@arm.linux.org.uk>2009-07-30 12:45:43 -0400
commitef12379f205bed7e92434e12ddd44e62d13bebe1 (patch)
treec372f5741826e7fbdaec8eac86191b52e3e621af /drivers/misc
parent3aa7a9a3cf8774f6701b1903a6353f9545f561ce (diff)
ARM: 5628/1: ep93xx: Introduce Pulse Width Modulator (PWM) driver
The EP93xx features two PWMs (one on the EP9307) with the following features: * Configurable dual output * Separate input clocks for each PWM output * 16-bit resolution * Programmable pulse width (duty cycle), interval (frequency), and polarity This adds the necessary core support as well as the driver. A sysfs interface is provided to control the PWM outputs. Signed-off-by: Matthieu Crapet <mcrapet@gmail.com> Signed-off-by: H Hartley Sweeten <hsweeten@visionengravers.com> Acked-by: Ryan Mallon <ryan@bluewatersys.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'drivers/misc')
-rw-r--r--drivers/misc/Kconfig13
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/ep93xx_pwm.c384
3 files changed, 398 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 68ab39d7cb35..df1f86b5c83e 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -233,6 +233,19 @@ config ISL29003
233 This driver can also be built as a module. If so, the module 233 This driver can also be built as a module. If so, the module
234 will be called isl29003. 234 will be called isl29003.
235 235
236config EP93XX_PWM
237 tristate "EP93xx PWM support"
238 depends on ARCH_EP93XX
239 help
240 This option enables device driver support for the PWM channels
241 on the Cirrus EP93xx processors. The EP9307 chip only has one
242 PWM channel all the others have two, the second channel is an
243 alternate function of the EGPIO14 pin. A sysfs interface is
244 provided to control the PWM channels.
245
246 To compile this driver as a module, choose M here: the module will
247 be called ep93xx_pwm.
248
236source "drivers/misc/c2port/Kconfig" 249source "drivers/misc/c2port/Kconfig"
237source "drivers/misc/eeprom/Kconfig" 250source "drivers/misc/eeprom/Kconfig"
238source "drivers/misc/cb710/Kconfig" 251source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 36f733cd60e6..f982d2ecfde7 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_SGI_XP) += sgi-xp/
19obj-$(CONFIG_SGI_GRU) += sgi-gru/ 19obj-$(CONFIG_SGI_GRU) += sgi-gru/
20obj-$(CONFIG_HP_ILO) += hpilo.o 20obj-$(CONFIG_HP_ILO) += hpilo.o
21obj-$(CONFIG_ISL29003) += isl29003.o 21obj-$(CONFIG_ISL29003) += isl29003.o
22obj-$(CONFIG_EP93XX_PWM) += ep93xx_pwm.o
22obj-$(CONFIG_C2PORT) += c2port/ 23obj-$(CONFIG_C2PORT) += c2port/
23obj-y += eeprom/ 24obj-y += eeprom/
24obj-y += cb710/ 25obj-y += cb710/
diff --git a/drivers/misc/ep93xx_pwm.c b/drivers/misc/ep93xx_pwm.c
new file mode 100644
index 000000000000..ba4694169d79
--- /dev/null
+++ b/drivers/misc/ep93xx_pwm.c
@@ -0,0 +1,384 @@
1/*
2 * Simple PWM driver for EP93XX
3 *
4 * (c) Copyright 2009 Matthieu Crapet <mcrapet@gmail.com>
5 * (c) Copyright 2009 H Hartley Sweeten <hsweeten@visionengravers.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 *
12 * EP9307 has only one channel:
13 * - PWMOUT
14 *
15 * EP9301/02/12/15 have two channels:
16 * - PWMOUT
17 * - PWMOUT1 (alternate function for EGPIO14)
18 */
19
20#include <linux/module.h>
21#include <linux/platform_device.h>
22#include <linux/clk.h>
23#include <linux/err.h>
24#include <linux/io.h>
25
26#include <mach/platform.h>
27
28#define EP93XX_PWMx_TERM_COUNT 0x00
29#define EP93XX_PWMx_DUTY_CYCLE 0x04
30#define EP93XX_PWMx_ENABLE 0x08
31#define EP93XX_PWMx_INVERT 0x0C
32
33#define EP93XX_PWM_MAX_COUNT 0xFFFF
34
35struct ep93xx_pwm {
36 void __iomem *mmio_base;
37 struct clk *clk;
38 u32 duty_percent;
39};
40
41static inline void ep93xx_pwm_writel(struct ep93xx_pwm *pwm,
42 unsigned int val, unsigned int off)
43{
44 __raw_writel(val, pwm->mmio_base + off);
45}
46
47static inline unsigned int ep93xx_pwm_readl(struct ep93xx_pwm *pwm,
48 unsigned int off)
49{
50 return __raw_readl(pwm->mmio_base + off);
51}
52
53static inline void ep93xx_pwm_write_tc(struct ep93xx_pwm *pwm, u16 value)
54{
55 ep93xx_pwm_writel(pwm, value, EP93XX_PWMx_TERM_COUNT);
56}
57
58static inline u16 ep93xx_pwm_read_tc(struct ep93xx_pwm *pwm)
59{
60 return ep93xx_pwm_readl(pwm, EP93XX_PWMx_TERM_COUNT);
61}
62
63static inline void ep93xx_pwm_write_dc(struct ep93xx_pwm *pwm, u16 value)
64{
65 ep93xx_pwm_writel(pwm, value, EP93XX_PWMx_DUTY_CYCLE);
66}
67
68static inline void ep93xx_pwm_enable(struct ep93xx_pwm *pwm)
69{
70 ep93xx_pwm_writel(pwm, 0x1, EP93XX_PWMx_ENABLE);
71}
72
73static inline void ep93xx_pwm_disable(struct ep93xx_pwm *pwm)
74{
75 ep93xx_pwm_writel(pwm, 0x0, EP93XX_PWMx_ENABLE);
76}
77
78static inline int ep93xx_pwm_is_enabled(struct ep93xx_pwm *pwm)
79{
80 return ep93xx_pwm_readl(pwm, EP93XX_PWMx_ENABLE) & 0x1;
81}
82
83static inline void ep93xx_pwm_invert(struct ep93xx_pwm *pwm)
84{
85 ep93xx_pwm_writel(pwm, 0x1, EP93XX_PWMx_INVERT);
86}
87
88static inline void ep93xx_pwm_normal(struct ep93xx_pwm *pwm)
89{
90 ep93xx_pwm_writel(pwm, 0x0, EP93XX_PWMx_INVERT);
91}
92
93static inline int ep93xx_pwm_is_inverted(struct ep93xx_pwm *pwm)
94{
95 return ep93xx_pwm_readl(pwm, EP93XX_PWMx_INVERT) & 0x1;
96}
97
98/*
99 * /sys/devices/platform/ep93xx-pwm.N
100 * /min_freq read-only minimum pwm output frequency
101 * /max_req read-only maximum pwm output frequency
102 * /freq read-write pwm output frequency (0 = disable output)
103 * /duty_percent read-write pwm duty cycle percent (1..99)
104 * /invert read-write invert pwm output
105 */
106
107static ssize_t ep93xx_pwm_get_min_freq(struct device *dev,
108 struct device_attribute *attr, char *buf)
109{
110 struct platform_device *pdev = to_platform_device(dev);
111 struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
112 unsigned long rate = clk_get_rate(pwm->clk);
113
114 return sprintf(buf, "%ld\n", rate / (EP93XX_PWM_MAX_COUNT + 1));
115}
116
117static ssize_t ep93xx_pwm_get_max_freq(struct device *dev,
118 struct device_attribute *attr, char *buf)
119{
120 struct platform_device *pdev = to_platform_device(dev);
121 struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
122 unsigned long rate = clk_get_rate(pwm->clk);
123
124 return sprintf(buf, "%ld\n", rate / 2);
125}
126
127static ssize_t ep93xx_pwm_get_freq(struct device *dev,
128 struct device_attribute *attr, char *buf)
129{
130 struct platform_device *pdev = to_platform_device(dev);
131 struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
132
133 if (ep93xx_pwm_is_enabled(pwm)) {
134 unsigned long rate = clk_get_rate(pwm->clk);
135 u16 term = ep93xx_pwm_read_tc(pwm);
136
137 return sprintf(buf, "%ld\n", rate / (term + 1));
138 } else {
139 return sprintf(buf, "disabled\n");
140 }
141}
142
143static ssize_t ep93xx_pwm_set_freq(struct device *dev,
144 struct device_attribute *attr, const char *buf, size_t count)
145{
146 struct platform_device *pdev = to_platform_device(dev);
147 struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
148 long val;
149 int err;
150
151 err = strict_strtol(buf, 10, &val);
152 if (err)
153 return -EINVAL;
154
155 if (val == 0) {
156 ep93xx_pwm_disable(pwm);
157 } else if (val <= (clk_get_rate(pwm->clk) / 2)) {
158 u32 term, duty;
159
160 val = (clk_get_rate(pwm->clk) / val) - 1;
161 if (val > EP93XX_PWM_MAX_COUNT)
162 val = EP93XX_PWM_MAX_COUNT;
163 if (val < 1)
164 val = 1;
165
166 term = ep93xx_pwm_read_tc(pwm);
167 duty = ((val + 1) * pwm->duty_percent / 100) - 1;
168
169 /* If pwm is running, order is important */
170 if (val > term) {
171 ep93xx_pwm_write_tc(pwm, val);
172 ep93xx_pwm_write_dc(pwm, duty);
173 } else {
174 ep93xx_pwm_write_dc(pwm, duty);
175 ep93xx_pwm_write_tc(pwm, val);
176 }
177
178 if (!ep93xx_pwm_is_enabled(pwm))
179 ep93xx_pwm_enable(pwm);
180 } else {
181 return -EINVAL;
182 }
183
184 return count;
185}
186
187static ssize_t ep93xx_pwm_get_duty_percent(struct device *dev,
188 struct device_attribute *attr, char *buf)
189{
190 struct platform_device *pdev = to_platform_device(dev);
191 struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
192
193 return sprintf(buf, "%d\n", pwm->duty_percent);
194}
195
196static ssize_t ep93xx_pwm_set_duty_percent(struct device *dev,
197 struct device_attribute *attr, const char *buf, size_t count)
198{
199 struct platform_device *pdev = to_platform_device(dev);
200 struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
201 long val;
202 int err;
203
204 err = strict_strtol(buf, 10, &val);
205 if (err)
206 return -EINVAL;
207
208 if (val > 0 && val < 100) {
209 u32 term = ep93xx_pwm_read_tc(pwm);
210 ep93xx_pwm_write_dc(pwm, ((term + 1) * val / 100) - 1);
211 pwm->duty_percent = val;
212 return count;
213 }
214
215 return -EINVAL;
216}
217
218static ssize_t ep93xx_pwm_get_invert(struct device *dev,
219 struct device_attribute *attr, char *buf)
220{
221 struct platform_device *pdev = to_platform_device(dev);
222 struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
223
224 return sprintf(buf, "%d\n", ep93xx_pwm_is_inverted(pwm));
225}
226
227static ssize_t ep93xx_pwm_set_invert(struct device *dev,
228 struct device_attribute *attr, const char *buf, size_t count)
229{
230 struct platform_device *pdev = to_platform_device(dev);
231 struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
232 long val;
233 int err;
234
235 err = strict_strtol(buf, 10, &val);
236 if (err)
237 return -EINVAL;
238
239 if (val == 0)
240 ep93xx_pwm_normal(pwm);
241 else if (val == 1)
242 ep93xx_pwm_invert(pwm);
243 else
244 return -EINVAL;
245
246 return count;
247}
248
249static DEVICE_ATTR(min_freq, S_IRUGO, ep93xx_pwm_get_min_freq, NULL);
250static DEVICE_ATTR(max_freq, S_IRUGO, ep93xx_pwm_get_max_freq, NULL);
251static DEVICE_ATTR(freq, S_IWUGO | S_IRUGO,
252 ep93xx_pwm_get_freq, ep93xx_pwm_set_freq);
253static DEVICE_ATTR(duty_percent, S_IWUGO | S_IRUGO,
254 ep93xx_pwm_get_duty_percent, ep93xx_pwm_set_duty_percent);
255static DEVICE_ATTR(invert, S_IWUGO | S_IRUGO,
256 ep93xx_pwm_get_invert, ep93xx_pwm_set_invert);
257
258static struct attribute *ep93xx_pwm_attrs[] = {
259 &dev_attr_min_freq.attr,
260 &dev_attr_max_freq.attr,
261 &dev_attr_freq.attr,
262 &dev_attr_duty_percent.attr,
263 &dev_attr_invert.attr,
264 NULL
265};
266
267static const struct attribute_group ep93xx_pwm_sysfs_files = {
268 .attrs = ep93xx_pwm_attrs,
269};
270
271static int __init ep93xx_pwm_probe(struct platform_device *pdev)
272{
273 struct ep93xx_pwm *pwm;
274 struct resource *res;
275 int err;
276
277 err = ep93xx_pwm_acquire_gpio(pdev);
278 if (err)
279 return err;
280
281 pwm = kzalloc(sizeof(struct ep93xx_pwm), GFP_KERNEL);
282 if (!pwm) {
283 err = -ENOMEM;
284 goto fail_no_mem;
285 }
286
287 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
288 if (res == NULL) {
289 err = -ENXIO;
290 goto fail_no_mem_resource;
291 }
292
293 res = request_mem_region(res->start, resource_size(res), pdev->name);
294 if (res == NULL) {
295 err = -EBUSY;
296 goto fail_no_mem_resource;
297 }
298
299 pwm->mmio_base = ioremap(res->start, resource_size(res));
300 if (pwm->mmio_base == NULL) {
301 err = -ENXIO;
302 goto fail_no_ioremap;
303 }
304
305 err = sysfs_create_group(&pdev->dev.kobj, &ep93xx_pwm_sysfs_files);
306 if (err)
307 goto fail_no_sysfs;
308
309 pwm->clk = clk_get(&pdev->dev, "pwm_clk");
310 if (IS_ERR(pwm->clk)) {
311 err = PTR_ERR(pwm->clk);
312 goto fail_no_clk;
313 }
314
315 pwm->duty_percent = 50;
316
317 platform_set_drvdata(pdev, pwm);
318
319 /* disable pwm at startup. Avoids zero value. */
320 ep93xx_pwm_disable(pwm);
321 ep93xx_pwm_write_tc(pwm, EP93XX_PWM_MAX_COUNT);
322 ep93xx_pwm_write_dc(pwm, EP93XX_PWM_MAX_COUNT / 2);
323
324 clk_enable(pwm->clk);
325
326 return 0;
327
328fail_no_clk:
329 sysfs_remove_group(&pdev->dev.kobj, &ep93xx_pwm_sysfs_files);
330fail_no_sysfs:
331 iounmap(pwm->mmio_base);
332fail_no_ioremap:
333 release_mem_region(res->start, resource_size(res));
334fail_no_mem_resource:
335 kfree(pwm);
336fail_no_mem:
337 ep93xx_pwm_release_gpio(pdev);
338 return err;
339}
340
341static int __exit ep93xx_pwm_remove(struct platform_device *pdev)
342{
343 struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
344 struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
345
346 ep93xx_pwm_disable(pwm);
347 clk_disable(pwm->clk);
348 clk_put(pwm->clk);
349 platform_set_drvdata(pdev, NULL);
350 sysfs_remove_group(&pdev->dev.kobj, &ep93xx_pwm_sysfs_files);
351 iounmap(pwm->mmio_base);
352 release_mem_region(res->start, resource_size(res));
353 kfree(pwm);
354 ep93xx_pwm_release_gpio(pdev);
355
356 return 0;
357}
358
359static struct platform_driver ep93xx_pwm_driver = {
360 .driver = {
361 .name = "ep93xx-pwm",
362 .owner = THIS_MODULE,
363 },
364 .remove = __exit_p(ep93xx_pwm_remove),
365};
366
367static int __init ep93xx_pwm_init(void)
368{
369 return platform_driver_probe(&ep93xx_pwm_driver, ep93xx_pwm_probe);
370}
371
372static void __exit ep93xx_pwm_exit(void)
373{
374 platform_driver_unregister(&ep93xx_pwm_driver);
375}
376
377module_init(ep93xx_pwm_init);
378module_exit(ep93xx_pwm_exit);
379
380MODULE_AUTHOR("Matthieu Crapet <mcrapet@gmail.com>, "
381 "H Hartley Sweeten <hsweeten@visionengravers.com>");
382MODULE_DESCRIPTION("EP93xx PWM driver");
383MODULE_LICENSE("GPL");
384MODULE_ALIAS("platform:ep93xx-pwm");