diff options
author | Boris BREZILLON <linux-arm@overkiz.com> | 2013-01-08 10:36:42 -0500 |
---|---|---|
committer | Thierry Reding <thierry.reding@avionic-design.de> | 2013-01-08 10:52:10 -0500 |
commit | 9421bade0765d8ffb86b8a99213b611278a3542a (patch) | |
tree | 83bacefbd5536255dae0fba58bff5bdacdf2b6b8 /drivers/pwm | |
parent | d1c3ed669a2d452cacfb48c2d171a1f364dae2ed (diff) |
pwm: atmel: add Timer Counter Block PWM driver
This patch adds a PWM driver based on Atmel Timer Counter Block. The
Timer Counter Block is used in Waveform generator mode.
A Timer Counter Block provides up to 6 PWM devices grouped by 2:
* group 0 = PWM 0 and 1
* group 1 = PWM 2 and 3
* group 2 = PMW 4 and 5
PWM devices in a given group must be configured with the same period
value. If a PWM device in a group tries to change the period value and
the other device is already configured with a different value an error
will be returned.
This driver requires device tree support. The Timer Counter Block number
used to create a PWM chip is given by the tc-block field in an
"atmel,tcb-pwm" compatible node.
This patch was tested on kizbox board (at91sam9g20 SoC) with pwm-leds.
Signed-off-by: Boris BREZILLON <linux-arm@overkiz.com>
Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
Diffstat (limited to 'drivers/pwm')
-rw-r--r-- | drivers/pwm/Kconfig | 12 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-atmel-tcb.c | 445 |
3 files changed, 458 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index e513cd998170..10b6afc94bc3 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig | |||
@@ -37,6 +37,18 @@ config PWM_AB8500 | |||
37 | To compile this driver as a module, choose M here: the module | 37 | To compile this driver as a module, choose M here: the module |
38 | will be called pwm-ab8500. | 38 | will be called pwm-ab8500. |
39 | 39 | ||
40 | config PWM_ATMEL_TCB | ||
41 | tristate "TC Block PWM" | ||
42 | depends on ATMEL_TCLIB && OF | ||
43 | help | ||
44 | Generic PWM framework driver for Atmel Timer Counter Block. | ||
45 | |||
46 | A Timer Counter Block provides 6 PWM devices grouped by 2. | ||
47 | Devices in a given group must have the same period. | ||
48 | |||
49 | To compile this driver as a module, choose M here: the module | ||
50 | will be called pwm-atmel-tcb. | ||
51 | |||
40 | config PWM_BFIN | 52 | config PWM_BFIN |
41 | tristate "Blackfin PWM support" | 53 | tristate "Blackfin PWM support" |
42 | depends on BFIN_GPTIMERS | 54 | depends on BFIN_GPTIMERS |
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 62a2963cfe58..94ba21e24bd6 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile | |||
@@ -1,5 +1,6 @@ | |||
1 | obj-$(CONFIG_PWM) += core.o | 1 | obj-$(CONFIG_PWM) += core.o |
2 | obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o | 2 | obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o |
3 | obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o | ||
3 | obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o | 4 | obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o |
4 | obj-$(CONFIG_PWM_IMX) += pwm-imx.o | 5 | obj-$(CONFIG_PWM_IMX) += pwm-imx.o |
5 | obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o | 6 | obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o |
diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c new file mode 100644 index 000000000000..16cb53092857 --- /dev/null +++ b/drivers/pwm/pwm-atmel-tcb.c | |||
@@ -0,0 +1,445 @@ | |||
1 | /* | ||
2 | * Copyright (C) Overkiz SAS 2012 | ||
3 | * | ||
4 | * Author: Boris BREZILLON <b.brezillon@overkiz.com> | ||
5 | * License terms: GNU General Public License (GPL) version 2 | ||
6 | */ | ||
7 | |||
8 | #include <linux/module.h> | ||
9 | #include <linux/init.h> | ||
10 | #include <linux/clocksource.h> | ||
11 | #include <linux/clockchips.h> | ||
12 | #include <linux/interrupt.h> | ||
13 | #include <linux/irq.h> | ||
14 | |||
15 | #include <linux/clk.h> | ||
16 | #include <linux/err.h> | ||
17 | #include <linux/ioport.h> | ||
18 | #include <linux/io.h> | ||
19 | #include <linux/platform_device.h> | ||
20 | #include <linux/atmel_tc.h> | ||
21 | #include <linux/pwm.h> | ||
22 | #include <linux/of_device.h> | ||
23 | #include <linux/slab.h> | ||
24 | |||
25 | #define NPWM 6 | ||
26 | |||
27 | #define ATMEL_TC_ACMR_MASK (ATMEL_TC_ACPA | ATMEL_TC_ACPC | \ | ||
28 | ATMEL_TC_AEEVT | ATMEL_TC_ASWTRG) | ||
29 | |||
30 | #define ATMEL_TC_BCMR_MASK (ATMEL_TC_BCPB | ATMEL_TC_BCPC | \ | ||
31 | ATMEL_TC_BEEVT | ATMEL_TC_BSWTRG) | ||
32 | |||
33 | struct atmel_tcb_pwm_device { | ||
34 | enum pwm_polarity polarity; /* PWM polarity */ | ||
35 | unsigned div; /* PWM clock divider */ | ||
36 | unsigned duty; /* PWM duty expressed in clk cycles */ | ||
37 | unsigned period; /* PWM period expressed in clk cycles */ | ||
38 | }; | ||
39 | |||
40 | struct atmel_tcb_pwm_chip { | ||
41 | struct pwm_chip chip; | ||
42 | spinlock_t lock; | ||
43 | struct atmel_tc *tc; | ||
44 | struct atmel_tcb_pwm_device *pwms[NPWM]; | ||
45 | }; | ||
46 | |||
47 | static inline struct atmel_tcb_pwm_chip *to_tcb_chip(struct pwm_chip *chip) | ||
48 | { | ||
49 | return container_of(chip, struct atmel_tcb_pwm_chip, chip); | ||
50 | } | ||
51 | |||
52 | static int atmel_tcb_pwm_set_polarity(struct pwm_chip *chip, | ||
53 | struct pwm_device *pwm, | ||
54 | enum pwm_polarity polarity) | ||
55 | { | ||
56 | struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); | ||
57 | |||
58 | tcbpwm->polarity = polarity; | ||
59 | |||
60 | return 0; | ||
61 | } | ||
62 | |||
63 | static int atmel_tcb_pwm_request(struct pwm_chip *chip, | ||
64 | struct pwm_device *pwm) | ||
65 | { | ||
66 | struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); | ||
67 | struct atmel_tcb_pwm_device *tcbpwm; | ||
68 | struct atmel_tc *tc = tcbpwmc->tc; | ||
69 | void __iomem *regs = tc->regs; | ||
70 | unsigned group = pwm->hwpwm / 2; | ||
71 | unsigned index = pwm->hwpwm % 2; | ||
72 | unsigned cmr; | ||
73 | int ret; | ||
74 | |||
75 | tcbpwm = devm_kzalloc(chip->dev, sizeof(*tcbpwm), GFP_KERNEL); | ||
76 | if (!tcbpwm) | ||
77 | return -ENOMEM; | ||
78 | |||
79 | ret = clk_enable(tc->clk[group]); | ||
80 | if (ret) { | ||
81 | devm_kfree(chip->dev, tcbpwm); | ||
82 | return ret; | ||
83 | } | ||
84 | |||
85 | pwm_set_chip_data(pwm, tcbpwm); | ||
86 | tcbpwm->polarity = PWM_POLARITY_NORMAL; | ||
87 | tcbpwm->duty = 0; | ||
88 | tcbpwm->period = 0; | ||
89 | tcbpwm->div = 0; | ||
90 | |||
91 | spin_lock(&tcbpwmc->lock); | ||
92 | cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR)); | ||
93 | /* | ||
94 | * Get init config from Timer Counter registers if | ||
95 | * Timer Counter is already configured as a PWM generator. | ||
96 | */ | ||
97 | if (cmr & ATMEL_TC_WAVE) { | ||
98 | if (index == 0) | ||
99 | tcbpwm->duty = | ||
100 | __raw_readl(regs + ATMEL_TC_REG(group, RA)); | ||
101 | else | ||
102 | tcbpwm->duty = | ||
103 | __raw_readl(regs + ATMEL_TC_REG(group, RB)); | ||
104 | |||
105 | tcbpwm->div = cmr & ATMEL_TC_TCCLKS; | ||
106 | tcbpwm->period = __raw_readl(regs + ATMEL_TC_REG(group, RC)); | ||
107 | cmr &= (ATMEL_TC_TCCLKS | ATMEL_TC_ACMR_MASK | | ||
108 | ATMEL_TC_BCMR_MASK); | ||
109 | } else | ||
110 | cmr = 0; | ||
111 | |||
112 | cmr |= ATMEL_TC_WAVE | ATMEL_TC_WAVESEL_UP_AUTO | ATMEL_TC_EEVT_XC0; | ||
113 | __raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR)); | ||
114 | spin_unlock(&tcbpwmc->lock); | ||
115 | |||
116 | tcbpwmc->pwms[pwm->hwpwm] = tcbpwm; | ||
117 | |||
118 | return 0; | ||
119 | } | ||
120 | |||
121 | static void atmel_tcb_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) | ||
122 | { | ||
123 | struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); | ||
124 | struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); | ||
125 | struct atmel_tc *tc = tcbpwmc->tc; | ||
126 | |||
127 | clk_disable(tc->clk[pwm->hwpwm / 2]); | ||
128 | tcbpwmc->pwms[pwm->hwpwm] = NULL; | ||
129 | devm_kfree(chip->dev, tcbpwm); | ||
130 | } | ||
131 | |||
132 | static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
133 | { | ||
134 | struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); | ||
135 | struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); | ||
136 | struct atmel_tc *tc = tcbpwmc->tc; | ||
137 | void __iomem *regs = tc->regs; | ||
138 | unsigned group = pwm->hwpwm / 2; | ||
139 | unsigned index = pwm->hwpwm % 2; | ||
140 | unsigned cmr; | ||
141 | enum pwm_polarity polarity = tcbpwm->polarity; | ||
142 | |||
143 | /* | ||
144 | * If duty is 0 the timer will be stopped and we have to | ||
145 | * configure the output correctly on software trigger: | ||
146 | * - set output to high if PWM_POLARITY_INVERSED | ||
147 | * - set output to low if PWM_POLARITY_NORMAL | ||
148 | * | ||
149 | * This is why we're reverting polarity in this case. | ||
150 | */ | ||
151 | if (tcbpwm->duty == 0) | ||
152 | polarity = !polarity; | ||
153 | |||
154 | spin_lock(&tcbpwmc->lock); | ||
155 | cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR)); | ||
156 | |||
157 | /* flush old setting and set the new one */ | ||
158 | if (index == 0) { | ||
159 | cmr &= ~ATMEL_TC_ACMR_MASK; | ||
160 | if (polarity == PWM_POLARITY_INVERSED) | ||
161 | cmr |= ATMEL_TC_ASWTRG_CLEAR; | ||
162 | else | ||
163 | cmr |= ATMEL_TC_ASWTRG_SET; | ||
164 | } else { | ||
165 | cmr &= ~ATMEL_TC_BCMR_MASK; | ||
166 | if (polarity == PWM_POLARITY_INVERSED) | ||
167 | cmr |= ATMEL_TC_BSWTRG_CLEAR; | ||
168 | else | ||
169 | cmr |= ATMEL_TC_BSWTRG_SET; | ||
170 | } | ||
171 | |||
172 | __raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR)); | ||
173 | |||
174 | /* | ||
175 | * Use software trigger to apply the new setting. | ||
176 | * If both PWM devices in this group are disabled we stop the clock. | ||
177 | */ | ||
178 | if (!(cmr & (ATMEL_TC_ACPC | ATMEL_TC_BCPC))) | ||
179 | __raw_writel(ATMEL_TC_SWTRG | ATMEL_TC_CLKDIS, | ||
180 | regs + ATMEL_TC_REG(group, CCR)); | ||
181 | else | ||
182 | __raw_writel(ATMEL_TC_SWTRG, regs + | ||
183 | ATMEL_TC_REG(group, CCR)); | ||
184 | |||
185 | spin_unlock(&tcbpwmc->lock); | ||
186 | } | ||
187 | |||
188 | static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
189 | { | ||
190 | struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); | ||
191 | struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); | ||
192 | struct atmel_tc *tc = tcbpwmc->tc; | ||
193 | void __iomem *regs = tc->regs; | ||
194 | unsigned group = pwm->hwpwm / 2; | ||
195 | unsigned index = pwm->hwpwm % 2; | ||
196 | u32 cmr; | ||
197 | enum pwm_polarity polarity = tcbpwm->polarity; | ||
198 | |||
199 | /* | ||
200 | * If duty is 0 the timer will be stopped and we have to | ||
201 | * configure the output correctly on software trigger: | ||
202 | * - set output to high if PWM_POLARITY_INVERSED | ||
203 | * - set output to low if PWM_POLARITY_NORMAL | ||
204 | * | ||
205 | * This is why we're reverting polarity in this case. | ||
206 | */ | ||
207 | if (tcbpwm->duty == 0) | ||
208 | polarity = !polarity; | ||
209 | |||
210 | spin_lock(&tcbpwmc->lock); | ||
211 | cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR)); | ||
212 | |||
213 | /* flush old setting and set the new one */ | ||
214 | cmr &= ~ATMEL_TC_TCCLKS; | ||
215 | |||
216 | if (index == 0) { | ||
217 | cmr &= ~ATMEL_TC_ACMR_MASK; | ||
218 | |||
219 | /* Set CMR flags according to given polarity */ | ||
220 | if (polarity == PWM_POLARITY_INVERSED) | ||
221 | cmr |= ATMEL_TC_ASWTRG_CLEAR; | ||
222 | else | ||
223 | cmr |= ATMEL_TC_ASWTRG_SET; | ||
224 | } else { | ||
225 | cmr &= ~ATMEL_TC_BCMR_MASK; | ||
226 | if (polarity == PWM_POLARITY_INVERSED) | ||
227 | cmr |= ATMEL_TC_BSWTRG_CLEAR; | ||
228 | else | ||
229 | cmr |= ATMEL_TC_BSWTRG_SET; | ||
230 | } | ||
231 | |||
232 | /* | ||
233 | * If duty is 0 or equal to period there's no need to register | ||
234 | * a specific action on RA/RB and RC compare. | ||
235 | * The output will be configured on software trigger and keep | ||
236 | * this config till next config call. | ||
237 | */ | ||
238 | if (tcbpwm->duty != tcbpwm->period && tcbpwm->duty > 0) { | ||
239 | if (index == 0) { | ||
240 | if (polarity == PWM_POLARITY_INVERSED) | ||
241 | cmr |= ATMEL_TC_ACPA_SET | ATMEL_TC_ACPC_CLEAR; | ||
242 | else | ||
243 | cmr |= ATMEL_TC_ACPA_CLEAR | ATMEL_TC_ACPC_SET; | ||
244 | } else { | ||
245 | if (polarity == PWM_POLARITY_INVERSED) | ||
246 | cmr |= ATMEL_TC_BCPB_SET | ATMEL_TC_BCPC_CLEAR; | ||
247 | else | ||
248 | cmr |= ATMEL_TC_BCPB_CLEAR | ATMEL_TC_BCPC_SET; | ||
249 | } | ||
250 | } | ||
251 | |||
252 | __raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR)); | ||
253 | |||
254 | if (index == 0) | ||
255 | __raw_writel(tcbpwm->duty, regs + ATMEL_TC_REG(group, RA)); | ||
256 | else | ||
257 | __raw_writel(tcbpwm->duty, regs + ATMEL_TC_REG(group, RB)); | ||
258 | |||
259 | __raw_writel(tcbpwm->period, regs + ATMEL_TC_REG(group, RC)); | ||
260 | |||
261 | /* Use software trigger to apply the new setting */ | ||
262 | __raw_writel(ATMEL_TC_CLKEN | ATMEL_TC_SWTRG, | ||
263 | regs + ATMEL_TC_REG(group, CCR)); | ||
264 | spin_unlock(&tcbpwmc->lock); | ||
265 | return 0; | ||
266 | } | ||
267 | |||
268 | static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | ||
269 | int duty_ns, int period_ns) | ||
270 | { | ||
271 | struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); | ||
272 | struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); | ||
273 | unsigned group = pwm->hwpwm / 2; | ||
274 | unsigned index = pwm->hwpwm % 2; | ||
275 | struct atmel_tcb_pwm_device *atcbpwm = NULL; | ||
276 | struct atmel_tc *tc = tcbpwmc->tc; | ||
277 | int i; | ||
278 | int slowclk = 0; | ||
279 | unsigned period; | ||
280 | unsigned duty; | ||
281 | unsigned rate = clk_get_rate(tc->clk[group]); | ||
282 | unsigned long long min; | ||
283 | unsigned long long max; | ||
284 | |||
285 | /* | ||
286 | * Find best clk divisor: | ||
287 | * the smallest divisor which can fulfill the period_ns requirements. | ||
288 | */ | ||
289 | for (i = 0; i < 5; ++i) { | ||
290 | if (atmel_tc_divisors[i] == 0) { | ||
291 | slowclk = i; | ||
292 | continue; | ||
293 | } | ||
294 | min = div_u64((u64)NSEC_PER_SEC * atmel_tc_divisors[i], rate); | ||
295 | max = min << tc->tcb_config->counter_width; | ||
296 | if (max >= period_ns) | ||
297 | break; | ||
298 | } | ||
299 | |||
300 | /* | ||
301 | * If none of the divisor are small enough to represent period_ns | ||
302 | * take slow clock (32KHz). | ||
303 | */ | ||
304 | if (i == 5) { | ||
305 | i = slowclk; | ||
306 | rate = 32768; | ||
307 | min = div_u64(NSEC_PER_SEC, rate); | ||
308 | max = min << 16; | ||
309 | |||
310 | /* If period is too big return ERANGE error */ | ||
311 | if (max < period_ns) | ||
312 | return -ERANGE; | ||
313 | } | ||
314 | |||
315 | duty = div_u64(duty_ns, min); | ||
316 | period = div_u64(period_ns, min); | ||
317 | |||
318 | if (index == 0) | ||
319 | atcbpwm = tcbpwmc->pwms[pwm->hwpwm + 1]; | ||
320 | else | ||
321 | atcbpwm = tcbpwmc->pwms[pwm->hwpwm - 1]; | ||
322 | |||
323 | /* | ||
324 | * PWM devices provided by TCB driver are grouped by 2: | ||
325 | * - group 0: PWM 0 & 1 | ||
326 | * - group 1: PWM 2 & 3 | ||
327 | * - group 2: PWM 4 & 5 | ||
328 | * | ||
329 | * PWM devices in a given group must be configured with the | ||
330 | * same period_ns. | ||
331 | * | ||
332 | * We're checking the period value of the second PWM device | ||
333 | * in this group before applying the new config. | ||
334 | */ | ||
335 | if ((atcbpwm && atcbpwm->duty > 0 && | ||
336 | atcbpwm->duty != atcbpwm->period) && | ||
337 | (atcbpwm->div != i || atcbpwm->period != period)) { | ||
338 | dev_err(chip->dev, | ||
339 | "failed to configure period_ns: PWM group already configured with a different value\n"); | ||
340 | return -EINVAL; | ||
341 | } | ||
342 | |||
343 | tcbpwm->period = period; | ||
344 | tcbpwm->div = i; | ||
345 | tcbpwm->duty = duty; | ||
346 | |||
347 | /* If the PWM is enabled, call enable to apply the new conf */ | ||
348 | if (test_bit(PWMF_ENABLED, &pwm->flags)) | ||
349 | atmel_tcb_pwm_enable(chip, pwm); | ||
350 | |||
351 | return 0; | ||
352 | } | ||
353 | |||
354 | static const struct pwm_ops atmel_tcb_pwm_ops = { | ||
355 | .request = atmel_tcb_pwm_request, | ||
356 | .free = atmel_tcb_pwm_free, | ||
357 | .config = atmel_tcb_pwm_config, | ||
358 | .set_polarity = atmel_tcb_pwm_set_polarity, | ||
359 | .enable = atmel_tcb_pwm_enable, | ||
360 | .disable = atmel_tcb_pwm_disable, | ||
361 | }; | ||
362 | |||
363 | static int atmel_tcb_pwm_probe(struct platform_device *pdev) | ||
364 | { | ||
365 | struct atmel_tcb_pwm_chip *tcbpwm; | ||
366 | struct device_node *np = pdev->dev.of_node; | ||
367 | struct atmel_tc *tc; | ||
368 | int err; | ||
369 | int tcblock; | ||
370 | |||
371 | err = of_property_read_u32(np, "tc-block", &tcblock); | ||
372 | if (err < 0) { | ||
373 | dev_err(&pdev->dev, | ||
374 | "failed to get Timer Counter Block number from device tree (error: %d)\n", | ||
375 | err); | ||
376 | return err; | ||
377 | } | ||
378 | |||
379 | tc = atmel_tc_alloc(tcblock, "tcb-pwm"); | ||
380 | if (tc == NULL) { | ||
381 | dev_err(&pdev->dev, "failed to allocate Timer Counter Block\n"); | ||
382 | return -ENOMEM; | ||
383 | } | ||
384 | |||
385 | tcbpwm = devm_kzalloc(&pdev->dev, sizeof(*tcbpwm), GFP_KERNEL); | ||
386 | if (tcbpwm == NULL) { | ||
387 | atmel_tc_free(tc); | ||
388 | dev_err(&pdev->dev, "failed to allocate memory\n"); | ||
389 | return -ENOMEM; | ||
390 | } | ||
391 | |||
392 | tcbpwm->chip.dev = &pdev->dev; | ||
393 | tcbpwm->chip.ops = &atmel_tcb_pwm_ops; | ||
394 | tcbpwm->chip.of_xlate = of_pwm_xlate_with_flags; | ||
395 | tcbpwm->chip.of_pwm_n_cells = 3; | ||
396 | tcbpwm->chip.base = -1; | ||
397 | tcbpwm->chip.npwm = NPWM; | ||
398 | tcbpwm->tc = tc; | ||
399 | |||
400 | spin_lock_init(&tcbpwm->lock); | ||
401 | |||
402 | err = pwmchip_add(&tcbpwm->chip); | ||
403 | if (err < 0) { | ||
404 | atmel_tc_free(tc); | ||
405 | return err; | ||
406 | } | ||
407 | |||
408 | platform_set_drvdata(pdev, tcbpwm); | ||
409 | |||
410 | return 0; | ||
411 | } | ||
412 | |||
413 | static int atmel_tcb_pwm_remove(struct platform_device *pdev) | ||
414 | { | ||
415 | struct atmel_tcb_pwm_chip *tcbpwm = platform_get_drvdata(pdev); | ||
416 | int err; | ||
417 | |||
418 | err = pwmchip_remove(&tcbpwm->chip); | ||
419 | if (err < 0) | ||
420 | return err; | ||
421 | |||
422 | atmel_tc_free(tcbpwm->tc); | ||
423 | |||
424 | return 0; | ||
425 | } | ||
426 | |||
427 | static const struct of_device_id atmel_tcb_pwm_dt_ids[] = { | ||
428 | { .compatible = "atmel,tcb-pwm", }, | ||
429 | { /* sentinel */ } | ||
430 | }; | ||
431 | MODULE_DEVICE_TABLE(of, atmel_tcb_pwm_dt_ids); | ||
432 | |||
433 | static struct platform_driver atmel_tcb_pwm_driver = { | ||
434 | .driver = { | ||
435 | .name = "atmel-tcb-pwm", | ||
436 | .of_match_table = atmel_tcb_pwm_dt_ids, | ||
437 | }, | ||
438 | .probe = atmel_tcb_pwm_probe, | ||
439 | .remove = atmel_tcb_pwm_remove, | ||
440 | }; | ||
441 | module_platform_driver(atmel_tcb_pwm_driver); | ||
442 | |||
443 | MODULE_AUTHOR("Boris BREZILLON <b.brezillon@overkiz.com>"); | ||
444 | MODULE_DESCRIPTION("Atmel Timer Counter Pulse Width Modulation Driver"); | ||
445 | MODULE_LICENSE("GPL v2"); | ||