aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBo Shen <voice.shen@atmel.com>2013-12-13 01:41:49 -0500
committerThierry Reding <thierry.reding@gmail.com>2013-12-17 05:26:42 -0500
commit32b16d46e4154d6f9d9c1d5dd35f64cdf08b7aea (patch)
treedb63f8597e2a2ccad3cfacd94536b74a4c6d64ed
parentca7a97add4d4a7b0602b3bd1eff5c89da8636713 (diff)
pwm: atmel-pwm: Add Atmel PWM controller driver
Add a PWM framework driver for the PWM controller found on Atmel SoCs. Signed-off-by: Bo Shen <voice.shen@atmel.com> Acked-by: Alexandre Belloni <alexandre.belloni@free-electrons.com> Acked-by: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> [thierry.reding: coding style and other minor cleanups] Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
-rw-r--r--drivers/pwm/Kconfig9
-rw-r--r--drivers/pwm/Makefile1
-rw-r--r--drivers/pwm/pwm-atmel.c393
3 files changed, 403 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 3f6642751ce5..6a2a1e0a4886 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -41,6 +41,15 @@ config PWM_AB8500
41 To compile this driver as a module, choose M here: the module 41 To compile this driver as a module, choose M here: the module
42 will be called pwm-ab8500. 42 will be called pwm-ab8500.
43 43
44config PWM_ATMEL
45 tristate "Atmel PWM support"
46 depends on ARCH_AT91
47 help
48 Generic PWM framework driver for Atmel SoC.
49
50 To compile this driver as a module, choose M here: the module
51 will be called pwm-atmel.
52
44config PWM_ATMEL_TCB 53config PWM_ATMEL_TCB
45 tristate "Atmel TC Block PWM support" 54 tristate "Atmel TC Block PWM support"
46 depends on ATMEL_TCLIB && OF 55 depends on ATMEL_TCLIB && OF
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 8b754e4dba4a..1b99cfbde527 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -1,6 +1,7 @@
1obj-$(CONFIG_PWM) += core.o 1obj-$(CONFIG_PWM) += core.o
2obj-$(CONFIG_PWM_SYSFS) += sysfs.o 2obj-$(CONFIG_PWM_SYSFS) += sysfs.o
3obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o 3obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
4obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o
4obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o 5obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o
5obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o 6obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o
6obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o 7obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c
new file mode 100644
index 000000000000..657e90ab6e0a
--- /dev/null
+++ b/drivers/pwm/pwm-atmel.c
@@ -0,0 +1,393 @@
1/*
2 * Driver for Atmel Pulse Width Modulation Controller
3 *
4 * Copyright (C) 2013 Atmel Corporation
5 * Bo Shen <voice.shen@atmel.com>
6 *
7 * Licensed under GPLv2.
8 */
9
10#include <linux/clk.h>
11#include <linux/err.h>
12#include <linux/io.h>
13#include <linux/module.h>
14#include <linux/of.h>
15#include <linux/of_device.h>
16#include <linux/platform_device.h>
17#include <linux/pwm.h>
18#include <linux/slab.h>
19
20/* The following is global registers for PWM controller */
21#define PWM_ENA 0x04
22#define PWM_DIS 0x08
23#define PWM_SR 0x0C
24/* Bit field in SR */
25#define PWM_SR_ALL_CH_ON 0x0F
26
27/* The following register is PWM channel related registers */
28#define PWM_CH_REG_OFFSET 0x200
29#define PWM_CH_REG_SIZE 0x20
30
31#define PWM_CMR 0x0
32/* Bit field in CMR */
33#define PWM_CMR_CPOL (1 << 9)
34#define PWM_CMR_UPD_CDTY (1 << 10)
35
36/* The following registers for PWM v1 */
37#define PWMV1_CDTY 0x04
38#define PWMV1_CPRD 0x08
39#define PWMV1_CUPD 0x10
40
41/* The following registers for PWM v2 */
42#define PWMV2_CDTY 0x04
43#define PWMV2_CDTYUPD 0x08
44#define PWMV2_CPRD 0x0C
45#define PWMV2_CPRDUPD 0x10
46
47/*
48 * Max value for duty and period
49 *
50 * Although the duty and period register is 32 bit,
51 * however only the LSB 16 bits are significant.
52 */
53#define PWM_MAX_DTY 0xFFFF
54#define PWM_MAX_PRD 0xFFFF
55#define PRD_MAX_PRES 10
56
57struct atmel_pwm_chip {
58 struct pwm_chip chip;
59 struct clk *clk;
60 void __iomem *base;
61
62 void (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
63 unsigned long dty, unsigned long prd);
64};
65
66static inline struct atmel_pwm_chip *to_atmel_pwm_chip(struct pwm_chip *chip)
67{
68 return container_of(chip, struct atmel_pwm_chip, chip);
69}
70
71static inline u32 atmel_pwm_readl(struct atmel_pwm_chip *chip,
72 unsigned long offset)
73{
74 return readl_relaxed(chip->base + offset);
75}
76
77static inline void atmel_pwm_writel(struct atmel_pwm_chip *chip,
78 unsigned long offset, unsigned long val)
79{
80 writel_relaxed(val, chip->base + offset);
81}
82
83static inline u32 atmel_pwm_ch_readl(struct atmel_pwm_chip *chip,
84 unsigned int ch, unsigned long offset)
85{
86 unsigned long base = PWM_CH_REG_OFFSET + ch * PWM_CH_REG_SIZE;
87
88 return readl_relaxed(chip->base + base + offset);
89}
90
91static inline void atmel_pwm_ch_writel(struct atmel_pwm_chip *chip,
92 unsigned int ch, unsigned long offset,
93 unsigned long val)
94{
95 unsigned long base = PWM_CH_REG_OFFSET + ch * PWM_CH_REG_SIZE;
96
97 writel_relaxed(val, chip->base + base + offset);
98}
99
100static int atmel_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
101 int duty_ns, int period_ns)
102{
103 struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
104 unsigned long clk_rate, prd, dty;
105 unsigned long long div;
106 unsigned int pres = 0;
107 int ret;
108
109 if (test_bit(PWMF_ENABLED, &pwm->flags) && (period_ns != pwm->period)) {
110 dev_err(chip->dev, "cannot change PWM period while enabled\n");
111 return -EBUSY;
112 }
113
114 clk_rate = clk_get_rate(atmel_pwm->clk);
115 div = clk_rate;
116
117 /* Calculate the period cycles */
118 while (div > PWM_MAX_PRD) {
119 div = clk_rate / (1 << pres);
120 div = div * period_ns;
121 /* 1/Hz = 100000000 ns */
122 do_div(div, 1000000000);
123
124 if (pres++ > PRD_MAX_PRES) {
125 dev_err(chip->dev, "pres exceeds the maximum value\n");
126 return -EINVAL;
127 }
128 }
129
130 /* Calculate the duty cycles */
131 prd = div;
132 div *= duty_ns;
133 do_div(div, period_ns);
134 dty = div;
135
136 ret = clk_enable(atmel_pwm->clk);
137 if (ret) {
138 dev_err(chip->dev, "failed to enable PWM clock\n");
139 return ret;
140 }
141
142 atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, pres);
143 atmel_pwm->config(chip, pwm, dty, prd);
144
145 clk_disable(atmel_pwm->clk);
146 return ret;
147}
148
149static void atmel_pwm_config_v1(struct pwm_chip *chip, struct pwm_device *pwm,
150 unsigned long dty, unsigned long prd)
151{
152 struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
153 unsigned int val;
154
155 if (test_bit(PWMF_ENABLED, &pwm->flags)) {
156 /*
157 * If the PWM channel is enabled, using the update register,
158 * it needs to set bit 10 of CMR to 0
159 */
160 atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CUPD, dty);
161
162 val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
163 val &= ~PWM_CMR_UPD_CDTY;
164 atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
165 } else {
166 /*
167 * If the PWM channel is disabled, write value to duty and
168 * period registers directly.
169 */
170 atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CDTY, dty);
171 atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CPRD, prd);
172 }
173}
174
175static void atmel_pwm_config_v2(struct pwm_chip *chip, struct pwm_device *pwm,
176 unsigned long dty, unsigned long prd)
177{
178 struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
179
180 if (test_bit(PWMF_ENABLED, &pwm->flags)) {
181 /*
182 * If the PWM channel is enabled, using the duty update register
183 * to update the value.
184 */
185 atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV2_CDTYUPD, dty);
186 } else {
187 /*
188 * If the PWM channel is disabled, write value to duty and
189 * period registers directly.
190 */
191 atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV2_CDTY, dty);
192 atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV2_CPRD, prd);
193 }
194}
195
196static int atmel_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
197 enum pwm_polarity polarity)
198{
199 struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
200 u32 val;
201 int ret;
202
203 val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
204
205 if (polarity == PWM_POLARITY_NORMAL)
206 val &= ~PWM_CMR_CPOL;
207 else
208 val |= PWM_CMR_CPOL;
209
210 ret = clk_enable(atmel_pwm->clk);
211 if (ret) {
212 dev_err(chip->dev, "failed to enable PWM clock\n");
213 return ret;
214 }
215
216 atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
217
218 clk_disable(atmel_pwm->clk);
219
220 return 0;
221}
222
223static int atmel_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
224{
225 struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
226 int ret;
227
228 ret = clk_enable(atmel_pwm->clk);
229 if (ret) {
230 dev_err(chip->dev, "failed to enable PWM clock\n");
231 return ret;
232 }
233
234 atmel_pwm_writel(atmel_pwm, PWM_ENA, 1 << pwm->hwpwm);
235
236 return 0;
237}
238
239static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
240{
241 struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
242
243 atmel_pwm_writel(atmel_pwm, PWM_DIS, 1 << pwm->hwpwm);
244
245 clk_disable(atmel_pwm->clk);
246}
247
248static const struct pwm_ops atmel_pwm_ops = {
249 .config = atmel_pwm_config,
250 .set_polarity = atmel_pwm_set_polarity,
251 .enable = atmel_pwm_enable,
252 .disable = atmel_pwm_disable,
253 .owner = THIS_MODULE,
254};
255
256struct atmel_pwm_data {
257 void (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
258 unsigned long dty, unsigned long prd);
259};
260
261static const struct atmel_pwm_data atmel_pwm_data_v1 = {
262 .config = atmel_pwm_config_v1,
263};
264
265static const struct atmel_pwm_data atmel_pwm_data_v2 = {
266 .config = atmel_pwm_config_v2,
267};
268
269static const struct platform_device_id atmel_pwm_devtypes[] = {
270 {
271 .name = "at91sam9rl-pwm",
272 .driver_data = (kernel_ulong_t)&atmel_pwm_data_v1,
273 }, {
274 .name = "sama5d3-pwm",
275 .driver_data = (kernel_ulong_t)&atmel_pwm_data_v2,
276 }, {
277 /* sentinel */
278 },
279};
280MODULE_DEVICE_TABLE(platform, atmel_pwm_devtypes);
281
282static const struct of_device_id atmel_pwm_dt_ids[] = {
283 {
284 .compatible = "atmel,at91sam9rl-pwm",
285 .data = &atmel_pwm_data_v1,
286 }, {
287 .compatible = "atmel,sama5d3-pwm",
288 .data = &atmel_pwm_data_v2,
289 }, {
290 /* sentinel */
291 },
292};
293MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids);
294
295static inline const struct atmel_pwm_data *
296atmel_pwm_get_driver_data(struct platform_device *pdev)
297{
298 if (pdev->dev.of_node) {
299 const struct of_device_id *match;
300
301 match = of_match_device(atmel_pwm_dt_ids, &pdev->dev);
302 if (!match)
303 return NULL;
304
305 return match->data;
306 } else {
307 const struct platform_device_id *id;
308
309 id = platform_get_device_id(pdev);
310
311 return (struct atmel_pwm_data *)id->driver_data;
312 }
313}
314
315static int atmel_pwm_probe(struct platform_device *pdev)
316{
317 const struct atmel_pwm_data *data;
318 struct atmel_pwm_chip *atmel_pwm;
319 struct resource *res;
320 int ret;
321
322 data = atmel_pwm_get_driver_data(pdev);
323 if (!data)
324 return -ENODEV;
325
326 atmel_pwm = devm_kzalloc(&pdev->dev, sizeof(*atmel_pwm), GFP_KERNEL);
327 if (!atmel_pwm)
328 return -ENOMEM;
329
330 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
331 atmel_pwm->base = devm_ioremap_resource(&pdev->dev, res);
332 if (IS_ERR(atmel_pwm->base))
333 return PTR_ERR(atmel_pwm->base);
334
335 atmel_pwm->clk = devm_clk_get(&pdev->dev, NULL);
336 if (IS_ERR(atmel_pwm->clk))
337 return PTR_ERR(atmel_pwm->clk);
338
339 ret = clk_prepare(atmel_pwm->clk);
340 if (ret) {
341 dev_err(&pdev->dev, "failed to prepare PWM clock\n");
342 return ret;
343 }
344
345 atmel_pwm->chip.dev = &pdev->dev;
346 atmel_pwm->chip.ops = &atmel_pwm_ops;
347
348 if (pdev->dev.of_node) {
349 atmel_pwm->chip.of_xlate = of_pwm_xlate_with_flags;
350 atmel_pwm->chip.of_pwm_n_cells = 3;
351 }
352
353 atmel_pwm->chip.base = -1;
354 atmel_pwm->chip.npwm = 4;
355 atmel_pwm->config = data->config;
356
357 ret = pwmchip_add(&atmel_pwm->chip);
358 if (ret < 0) {
359 dev_err(&pdev->dev, "failed to add PWM chip %d\n", ret);
360 goto unprepare_clk;
361 }
362
363 platform_set_drvdata(pdev, atmel_pwm);
364
365unprepare_clk:
366 clk_unprepare(atmel_pwm->clk);
367 return ret;
368}
369
370static int atmel_pwm_remove(struct platform_device *pdev)
371{
372 struct atmel_pwm_chip *atmel_pwm = platform_get_drvdata(pdev);
373
374 clk_unprepare(atmel_pwm->clk);
375
376 return pwmchip_remove(&atmel_pwm->chip);
377}
378
379static struct platform_driver atmel_pwm_driver = {
380 .driver = {
381 .name = "atmel-pwm",
382 .of_match_table = of_match_ptr(atmel_pwm_dt_ids),
383 },
384 .id_table = atmel_pwm_devtypes,
385 .probe = atmel_pwm_probe,
386 .remove = atmel_pwm_remove,
387};
388module_platform_driver(atmel_pwm_driver);
389
390MODULE_ALIAS("platform:atmel-pwm");
391MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>");
392MODULE_DESCRIPTION("Atmel PWM driver");
393MODULE_LICENSE("GPL v2");