diff options
author | eric miao <eric.miao@marvell.com> | 2008-04-13 16:44:04 -0400 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2008-05-19 11:25:39 -0400 |
commit | 75540c1ac3c7bd72ac8e092058f9714875239995 (patch) | |
tree | 9d6dc7cb389f879be36a1583ce8949023a55dd2d /arch/arm/mach-pxa/pwm.c | |
parent | 1a189b97190d3f0f8cf0379a799d3555b2d648bb (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.c | 299 |
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 | |||
33 | struct 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 | */ | ||
49 | int 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 | } | ||
86 | EXPORT_SYMBOL(pwm_config); | ||
87 | |||
88 | int pwm_enable(struct pwm_device *pwm) | ||
89 | { | ||
90 | return clk_enable(pwm->clk); | ||
91 | } | ||
92 | EXPORT_SYMBOL(pwm_enable); | ||
93 | |||
94 | void pwm_disable(struct pwm_device *pwm) | ||
95 | { | ||
96 | clk_disable(pwm->clk); | ||
97 | } | ||
98 | EXPORT_SYMBOL(pwm_disable); | ||
99 | |||
100 | static DEFINE_MUTEX(pwm_lock); | ||
101 | static LIST_HEAD(pwm_list); | ||
102 | |||
103 | struct 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 | } | ||
123 | EXPORT_SYMBOL(pwm_request); | ||
124 | |||
125 | void 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 | } | ||
137 | EXPORT_SYMBOL(pwm_free); | ||
138 | |||
139 | static 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 | |||
146 | static 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 | |||
201 | err_free_mem: | ||
202 | release_mem_region(r->start, r->end - r->start + 1); | ||
203 | err_free_clk: | ||
204 | clk_put(pwm->clk); | ||
205 | err_free: | ||
206 | kfree(pwm); | ||
207 | return ERR_PTR(ret); | ||
208 | } | ||
209 | |||
210 | static 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 | |||
220 | static 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 | |||
235 | static 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 | |||
258 | static 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 | |||
266 | static 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 | |||
274 | static 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 | } | ||
292 | arch_initcall(pwm_init); | ||
293 | |||
294 | static void __exit pwm_exit(void) | ||
295 | { | ||
296 | platform_driver_unregister(&pxa25x_pwm_driver); | ||
297 | platform_driver_unregister(&pxa27x_pwm_driver); | ||
298 | } | ||
299 | module_exit(pwm_exit); | ||