blob: dec6f2c8dbf989742ae124bbffe924121f3eb4f4 [file] [log] [blame]
eric miao75540c12008-04-13 21:44:04 +01001/*
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>
eric miao75540c12008-04-13 21:44:04 +010023
Eric Miao3d2a98c2009-04-13 15:59:03 +080024#define HAS_SECONDARY_PWM 0x10
25
26static const struct platform_device_id pwm_id_table[] = {
27 /* PWM has_secondary_pwm? */
28 { "pxa25x-pwm", 0 },
29 { "pxa27x-pwm", HAS_SECONDARY_PWM },
30 { },
31};
32MODULE_DEVICE_TABLE(platform, pwm_id_table);
33
eric miao75540c12008-04-13 21:44:04 +010034/* PWM registers and bits definitions */
35#define PWMCR (0x00)
36#define PWMDCR (0x04)
37#define PWMPCR (0x08)
38
39#define PWMCR_SD (1 << 6)
40#define PWMDCR_FD (1 << 10)
41
42struct pwm_device {
43 struct list_head node;
Eric Miao3d2a98c2009-04-13 15:59:03 +080044 struct pwm_device *secondary;
45 struct platform_device *pdev;
eric miao75540c12008-04-13 21:44:04 +010046
47 const char *label;
48 struct clk *clk;
Robert Jarzmikc860d702008-06-10 23:02:31 +010049 int clk_enabled;
eric miao75540c12008-04-13 21:44:04 +010050 void __iomem *mmio_base;
51
52 unsigned int use_count;
53 unsigned int pwm_id;
54};
55
56/*
57 * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE
58 * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
59 */
60int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
61{
62 unsigned long long c;
63 unsigned long period_cycles, prescale, pv, dc;
64
65 if (pwm == NULL || period_ns == 0 || duty_ns > period_ns)
66 return -EINVAL;
67
68 c = clk_get_rate(pwm->clk);
69 c = c * period_ns;
70 do_div(c, 1000000000);
71 period_cycles = c;
72
roelkluin71a35d72008-10-14 21:28:51 +010073 if (period_cycles < 1)
eric miao75540c12008-04-13 21:44:04 +010074 period_cycles = 1;
75 prescale = (period_cycles - 1) / 1024;
76 pv = period_cycles / (prescale + 1) - 1;
77
78 if (prescale > 63)
79 return -EINVAL;
80
81 if (duty_ns == period_ns)
82 dc = PWMDCR_FD;
83 else
84 dc = (pv + 1) * duty_ns / period_ns;
85
86 /* NOTE: the clock to PWM has to be enabled first
87 * before writing to the registers
88 */
89 clk_enable(pwm->clk);
90 __raw_writel(prescale, pwm->mmio_base + PWMCR);
91 __raw_writel(dc, pwm->mmio_base + PWMDCR);
92 __raw_writel(pv, pwm->mmio_base + PWMPCR);
93 clk_disable(pwm->clk);
94
95 return 0;
96}
97EXPORT_SYMBOL(pwm_config);
98
99int pwm_enable(struct pwm_device *pwm)
100{
Robert Jarzmikc860d702008-06-10 23:02:31 +0100101 int rc = 0;
102
103 if (!pwm->clk_enabled) {
104 rc = clk_enable(pwm->clk);
105 if (!rc)
106 pwm->clk_enabled = 1;
107 }
108 return rc;
eric miao75540c12008-04-13 21:44:04 +0100109}
110EXPORT_SYMBOL(pwm_enable);
111
112void pwm_disable(struct pwm_device *pwm)
113{
Robert Jarzmikc860d702008-06-10 23:02:31 +0100114 if (pwm->clk_enabled) {
115 clk_disable(pwm->clk);
116 pwm->clk_enabled = 0;
117 }
eric miao75540c12008-04-13 21:44:04 +0100118}
119EXPORT_SYMBOL(pwm_disable);
120
121static DEFINE_MUTEX(pwm_lock);
122static LIST_HEAD(pwm_list);
123
124struct pwm_device *pwm_request(int pwm_id, const char *label)
125{
126 struct pwm_device *pwm;
127 int found = 0;
128
129 mutex_lock(&pwm_lock);
130
131 list_for_each_entry(pwm, &pwm_list, node) {
Ben Dooks43bda1a2008-07-01 14:18:27 +0100132 if (pwm->pwm_id == pwm_id) {
eric miao75540c12008-04-13 21:44:04 +0100133 found = 1;
134 break;
135 }
136 }
137
Ben Dooks43bda1a2008-07-01 14:18:27 +0100138 if (found) {
139 if (pwm->use_count == 0) {
140 pwm->use_count++;
141 pwm->label = label;
142 } else
143 pwm = ERR_PTR(-EBUSY);
144 } else
145 pwm = ERR_PTR(-ENOENT);
eric miao75540c12008-04-13 21:44:04 +0100146
Ben Dooks43bda1a2008-07-01 14:18:27 +0100147 mutex_unlock(&pwm_lock);
148 return pwm;
eric miao75540c12008-04-13 21:44:04 +0100149}
150EXPORT_SYMBOL(pwm_request);
151
152void pwm_free(struct pwm_device *pwm)
153{
154 mutex_lock(&pwm_lock);
155
156 if (pwm->use_count) {
157 pwm->use_count--;
158 pwm->label = NULL;
159 } else
160 pr_warning("PWM device already freed\n");
161
162 mutex_unlock(&pwm_lock);
163}
164EXPORT_SYMBOL(pwm_free);
165
166static inline void __add_pwm(struct pwm_device *pwm)
167{
168 mutex_lock(&pwm_lock);
169 list_add_tail(&pwm->node, &pwm_list);
170 mutex_unlock(&pwm_lock);
171}
172
Eric Miao3d2a98c2009-04-13 15:59:03 +0800173static int __devinit pwm_probe(struct platform_device *pdev)
eric miao75540c12008-04-13 21:44:04 +0100174{
Eric Miao3d2a98c2009-04-13 15:59:03 +0800175 struct platform_device_id *id = platform_get_device_id(pdev);
176 struct pwm_device *pwm, *secondary = NULL;
eric miao75540c12008-04-13 21:44:04 +0100177 struct resource *r;
178 int ret = 0;
179
180 pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
181 if (pwm == NULL) {
182 dev_err(&pdev->dev, "failed to allocate memory\n");
Eric Miao3d2a98c2009-04-13 15:59:03 +0800183 return -ENOMEM;
eric miao75540c12008-04-13 21:44:04 +0100184 }
185
Russell Kinge0d8b132008-11-11 17:52:32 +0000186 pwm->clk = clk_get(&pdev->dev, NULL);
eric miao75540c12008-04-13 21:44:04 +0100187 if (IS_ERR(pwm->clk)) {
188 ret = PTR_ERR(pwm->clk);
189 goto err_free;
190 }
Robert Jarzmikc860d702008-06-10 23:02:31 +0100191 pwm->clk_enabled = 0;
eric miao75540c12008-04-13 21:44:04 +0100192
193 pwm->use_count = 0;
Eric Miao3d2a98c2009-04-13 15:59:03 +0800194 pwm->pwm_id = pdev->id;
eric miao75540c12008-04-13 21:44:04 +0100195 pwm->pdev = pdev;
196
eric miao75540c12008-04-13 21:44:04 +0100197 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
198 if (r == NULL) {
199 dev_err(&pdev->dev, "no memory resource defined\n");
200 ret = -ENODEV;
201 goto err_free_clk;
202 }
203
204 r = request_mem_region(r->start, r->end - r->start + 1, pdev->name);
205 if (r == NULL) {
206 dev_err(&pdev->dev, "failed to request memory resource\n");
207 ret = -EBUSY;
208 goto err_free_clk;
209 }
210
211 pwm->mmio_base = ioremap(r->start, r->end - r->start + 1);
212 if (pwm->mmio_base == NULL) {
213 dev_err(&pdev->dev, "failed to ioremap() registers\n");
214 ret = -ENODEV;
215 goto err_free_mem;
216 }
217
Eric Miao3d2a98c2009-04-13 15:59:03 +0800218 if (id->driver_data & HAS_SECONDARY_PWM) {
219 secondary = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
220 if (secondary == NULL) {
221 ret = -ENOMEM;
222 goto err_free_mem;
223 }
224
225 *secondary = *pwm;
226 pwm->secondary = secondary;
227
228 /* registers for the second PWM has offset of 0x10 */
229 secondary->mmio_base = pwm->mmio_base + 0x10;
230 secondary->pwm_id = pdev->id + 2;
231 }
232
eric miao75540c12008-04-13 21:44:04 +0100233 __add_pwm(pwm);
Eric Miao3d2a98c2009-04-13 15:59:03 +0800234 if (secondary)
235 __add_pwm(secondary);
236
eric miao75540c12008-04-13 21:44:04 +0100237 platform_set_drvdata(pdev, pwm);
Eric Miao3d2a98c2009-04-13 15:59:03 +0800238 return 0;
eric miao75540c12008-04-13 21:44:04 +0100239
240err_free_mem:
241 release_mem_region(r->start, r->end - r->start + 1);
242err_free_clk:
243 clk_put(pwm->clk);
244err_free:
245 kfree(pwm);
Eric Miao3d2a98c2009-04-13 15:59:03 +0800246 return ret;
eric miao75540c12008-04-13 21:44:04 +0100247}
248
249static int __devexit pwm_remove(struct platform_device *pdev)
250{
251 struct pwm_device *pwm;
252 struct resource *r;
253
254 pwm = platform_get_drvdata(pdev);
255 if (pwm == NULL)
256 return -ENODEV;
257
258 mutex_lock(&pwm_lock);
Eric Miao3d2a98c2009-04-13 15:59:03 +0800259
260 if (pwm->secondary) {
261 list_del(&pwm->secondary->node);
262 kfree(pwm->secondary);
263 }
264
eric miao75540c12008-04-13 21:44:04 +0100265 list_del(&pwm->node);
266 mutex_unlock(&pwm_lock);
267
268 iounmap(pwm->mmio_base);
269
270 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
271 release_mem_region(r->start, r->end - r->start + 1);
272
273 clk_put(pwm->clk);
274 kfree(pwm);
275 return 0;
276}
277
Eric Miao3d2a98c2009-04-13 15:59:03 +0800278static struct platform_driver pwm_driver = {
eric miao75540c12008-04-13 21:44:04 +0100279 .driver = {
280 .name = "pxa25x-pwm",
Eric Miao3d2a98c2009-04-13 15:59:03 +0800281 .owner = THIS_MODULE,
eric miao75540c12008-04-13 21:44:04 +0100282 },
Eric Miao3d2a98c2009-04-13 15:59:03 +0800283 .probe = pwm_probe,
eric miao75540c12008-04-13 21:44:04 +0100284 .remove = __devexit_p(pwm_remove),
Eric Miao3d2a98c2009-04-13 15:59:03 +0800285 .id_table = pwm_id_table,
eric miao75540c12008-04-13 21:44:04 +0100286};
287
288static int __init pwm_init(void)
289{
Eric Miao3d2a98c2009-04-13 15:59:03 +0800290 return platform_driver_register(&pwm_driver);
eric miao75540c12008-04-13 21:44:04 +0100291}
292arch_initcall(pwm_init);
293
294static void __exit pwm_exit(void)
295{
Eric Miao3d2a98c2009-04-13 15:59:03 +0800296 platform_driver_unregister(&pwm_driver);
eric miao75540c12008-04-13 21:44:04 +0100297}
298module_exit(pwm_exit);
Guennadi Liakhovetskib5f02282008-06-05 10:45:02 +0100299
300MODULE_LICENSE("GPL v2");