aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-pxa/pwm.c
diff options
context:
space:
mode:
authoreric miao <eric.miao@marvell.com>2008-04-13 16:44:04 -0400
committerRussell King <rmk+kernel@arm.linux.org.uk>2008-05-19 11:25:39 -0400
commit75540c1ac3c7bd72ac8e092058f9714875239995 (patch)
tree9d6dc7cb389f879be36a1583ce8949023a55dd2d /arch/arm/mach-pxa/pwm.c
parent1a189b97190d3f0f8cf0379a799d3555b2d648bb (diff)
[ARM] pxa: Add PXA support for PWM API
Patch mainly from Eric Miao, with minor edits by rmk. Note: PWM0 and PWM2 share the same register I/O space and clock gating on pxa{27x, 3xx}, thus PWM2 is treated in the driver as a child PWM of PWM0. And this is also true for PWM1/3. Signed-off-by: eric miao <eric.miao@marvell.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'arch/arm/mach-pxa/pwm.c')
-rw-r--r--arch/arm/mach-pxa/pwm.c299
1 files changed, 299 insertions, 0 deletions
diff --git a/arch/arm/mach-pxa/pwm.c b/arch/arm/mach-pxa/pwm.c
new file mode 100644
index 000000000000..70fb3ca6a058
--- /dev/null
+++ b/arch/arm/mach-pxa/pwm.c
@@ -0,0 +1,299 @@
1/*
2 * linux/arch/arm/mach-pxa/pwm.c
3 *
4 * simple driver for PWM (Pulse Width Modulator) controller
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * 2008-02-13 initial version
11 * eric miao <eric.miao@marvell.com>
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/div64.h>
23#include <asm/arch/pxa-regs.h>
24
25/* PWM registers and bits definitions */
26#define PWMCR (0x00)
27#define PWMDCR (0x04)
28#define PWMPCR (0x08)
29
30#define PWMCR_SD (1 << 6)
31#define PWMDCR_FD (1 << 10)
32
33struct pwm_device {
34 struct list_head node;
35 struct platform_device *pdev;
36
37 const char *label;
38 struct clk *clk;
39 void __iomem *mmio_base;
40
41 unsigned int use_count;
42 unsigned int pwm_id;
43};
44
45/*
46 * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE
47 * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
48 */
49int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
50{
51 unsigned long long c;
52 unsigned long period_cycles, prescale, pv, dc;
53
54 if (pwm == NULL || period_ns == 0 || duty_ns > period_ns)
55 return -EINVAL;
56
57 c = clk_get_rate(pwm->clk);
58 c = c * period_ns;
59 do_div(c, 1000000000);
60 period_cycles = c;
61
62 if (period_cycles < 0)
63 period_cycles = 1;
64 prescale = (period_cycles - 1) / 1024;
65 pv = period_cycles / (prescale + 1) - 1;
66
67 if (prescale > 63)
68 return -EINVAL;
69
70 if (duty_ns == period_ns)
71 dc = PWMDCR_FD;
72 else
73 dc = (pv + 1) * duty_ns / period_ns;
74
75 /* NOTE: the clock to PWM has to be enabled first
76 * before writing to the registers
77 */
78 clk_enable(pwm->clk);
79 __raw_writel(prescale, pwm->mmio_base + PWMCR);
80 __raw_writel(dc, pwm->mmio_base + PWMDCR);
81 __raw_writel(pv, pwm->mmio_base + PWMPCR);
82 clk_disable(pwm->clk);
83
84 return 0;
85}
86EXPORT_SYMBOL(pwm_config);
87
88int pwm_enable(struct pwm_device *pwm)
89{
90 return clk_enable(pwm->clk);
91}
92EXPORT_SYMBOL(pwm_enable);
93
94void pwm_disable(struct pwm_device *pwm)
95{
96 clk_disable(pwm->clk);
97}
98EXPORT_SYMBOL(pwm_disable);
99
100static DEFINE_MUTEX(pwm_lock);
101static LIST_HEAD(pwm_list);
102
103struct pwm_device *pwm_request(int pwm_id, const char *label)
104{
105 struct pwm_device *pwm;
106 int found = 0;
107
108 mutex_lock(&pwm_lock);
109
110 list_for_each_entry(pwm, &pwm_list, node) {
111 if (pwm->pwm_id == pwm_id && pwm->use_count == 0) {
112 pwm->use_count++;
113 pwm->label = label;
114 found = 1;
115 break;
116 }
117 }
118
119 mutex_unlock(&pwm_lock);
120
121 return (found) ? pwm : NULL;
122}
123EXPORT_SYMBOL(pwm_request);
124
125void pwm_free(struct pwm_device *pwm)
126{
127 mutex_lock(&pwm_lock);
128
129 if (pwm->use_count) {
130 pwm->use_count--;
131 pwm->label = NULL;
132 } else
133 pr_warning("PWM device already freed\n");
134
135 mutex_unlock(&pwm_lock);
136}
137EXPORT_SYMBOL(pwm_free);
138
139static inline void __add_pwm(struct pwm_device *pwm)
140{
141 mutex_lock(&pwm_lock);
142 list_add_tail(&pwm->node, &pwm_list);
143 mutex_unlock(&pwm_lock);
144}
145
146static struct pwm_device *pwm_probe(struct platform_device *pdev,
147 unsigned int pwm_id, struct pwm_device *parent_pwm)
148{
149 struct pwm_device *pwm;
150 struct resource *r;
151 int ret = 0;
152
153 pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
154 if (pwm == NULL) {
155 dev_err(&pdev->dev, "failed to allocate memory\n");
156 return ERR_PTR(-ENOMEM);
157 }
158
159 pwm->clk = clk_get(&pdev->dev, "PWMCLK");
160 if (IS_ERR(pwm->clk)) {
161 ret = PTR_ERR(pwm->clk);
162 goto err_free;
163 }
164
165 pwm->use_count = 0;
166 pwm->pwm_id = pwm_id;
167 pwm->pdev = pdev;
168
169 if (parent_pwm != NULL) {
170 /* registers for the second PWM has offset of 0x10 */
171 pwm->mmio_base = parent_pwm->mmio_base + 0x10;
172 __add_pwm(pwm);
173 return pwm;
174 }
175
176 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
177 if (r == NULL) {
178 dev_err(&pdev->dev, "no memory resource defined\n");
179 ret = -ENODEV;
180 goto err_free_clk;
181 }
182
183 r = request_mem_region(r->start, r->end - r->start + 1, pdev->name);
184 if (r == NULL) {
185 dev_err(&pdev->dev, "failed to request memory resource\n");
186 ret = -EBUSY;
187 goto err_free_clk;
188 }
189
190 pwm->mmio_base = ioremap(r->start, r->end - r->start + 1);
191 if (pwm->mmio_base == NULL) {
192 dev_err(&pdev->dev, "failed to ioremap() registers\n");
193 ret = -ENODEV;
194 goto err_free_mem;
195 }
196
197 __add_pwm(pwm);
198 platform_set_drvdata(pdev, pwm);
199 return pwm;
200
201err_free_mem:
202 release_mem_region(r->start, r->end - r->start + 1);
203err_free_clk:
204 clk_put(pwm->clk);
205err_free:
206 kfree(pwm);
207 return ERR_PTR(ret);
208}
209
210static int __devinit pxa25x_pwm_probe(struct platform_device *pdev)
211{
212 struct pwm_device *pwm = pwm_probe(pdev, pdev->id, NULL);
213
214 if (IS_ERR(pwm))
215 return PTR_ERR(pwm);
216
217 return 0;
218}
219
220static int __devinit pxa27x_pwm_probe(struct platform_device *pdev)
221{
222 struct pwm_device *pwm;
223
224 pwm = pwm_probe(pdev, pdev->id * 2, NULL);
225 if (IS_ERR(pwm))
226 return PTR_ERR(pwm);
227
228 pwm = pwm_probe(pdev, pdev->id * 2 + 1, pwm);
229 if (IS_ERR(pwm))
230 return PTR_ERR(pwm);
231
232 return 0;
233}
234
235static int __devexit pwm_remove(struct platform_device *pdev)
236{
237 struct pwm_device *pwm;
238 struct resource *r;
239
240 pwm = platform_get_drvdata(pdev);
241 if (pwm == NULL)
242 return -ENODEV;
243
244 mutex_lock(&pwm_lock);
245 list_del(&pwm->node);
246 mutex_unlock(&pwm_lock);
247
248 iounmap(pwm->mmio_base);
249
250 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
251 release_mem_region(r->start, r->end - r->start + 1);
252
253 clk_put(pwm->clk);
254 kfree(pwm);
255 return 0;
256}
257
258static struct platform_driver pxa25x_pwm_driver = {
259 .driver = {
260 .name = "pxa25x-pwm",
261 },
262 .probe = pxa25x_pwm_probe,
263 .remove = __devexit_p(pwm_remove),
264};
265
266static struct platform_driver pxa27x_pwm_driver = {
267 .driver = {
268 .name = "pxa27x-pwm",
269 },
270 .probe = pxa27x_pwm_probe,
271 .remove = __devexit_p(pwm_remove),
272};
273
274static int __init pwm_init(void)
275{
276 int ret = 0;
277
278 ret = platform_driver_register(&pxa25x_pwm_driver);
279 if (ret) {
280 printk(KERN_ERR "failed to register pxa25x_pwm_driver\n");
281 return ret;
282 }
283
284 ret = platform_driver_register(&pxa27x_pwm_driver);
285 if (ret) {
286 printk(KERN_ERR "failed to register pxa27x_pwm_driver\n");
287 return ret;
288 }
289
290 return ret;
291}
292arch_initcall(pwm_init);
293
294static void __exit pwm_exit(void)
295{
296 platform_driver_unregister(&pxa25x_pwm_driver);
297 platform_driver_unregister(&pxa27x_pwm_driver);
298}
299module_exit(pwm_exit);