1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * NXP LPC18xx State Configurable Timer - Pulse Width Modulator driver |
4 | * |
5 | * Copyright (c) 2015 Ariel D'Alessandro <ariel@vanguardiasur.com> |
6 | * |
7 | * Notes |
8 | * ===== |
9 | * NXP LPC18xx provides a State Configurable Timer (SCT) which can be configured |
10 | * as a Pulse Width Modulator. |
11 | * |
12 | * SCT supports 16 outputs, 16 events and 16 registers. Each event will be |
13 | * triggered when its related register matches the SCT counter value, and it |
14 | * will set or clear a selected output. |
15 | * |
16 | * One of the events is preselected to generate the period, thus the maximum |
17 | * number of simultaneous channels is limited to 15. Notice that period is |
18 | * global to all the channels, thus PWM driver will refuse setting different |
19 | * values to it, unless there's only one channel requested. |
20 | */ |
21 | |
22 | #include <linux/clk.h> |
23 | #include <linux/err.h> |
24 | #include <linux/io.h> |
25 | #include <linux/mod_devicetable.h> |
26 | #include <linux/module.h> |
27 | #include <linux/platform_device.h> |
28 | #include <linux/pwm.h> |
29 | |
30 | /* LPC18xx SCT registers */ |
31 | #define LPC18XX_PWM_CONFIG 0x000 |
32 | #define LPC18XX_PWM_CONFIG_UNIFY BIT(0) |
33 | #define LPC18XX_PWM_CONFIG_NORELOAD BIT(7) |
34 | |
35 | #define LPC18XX_PWM_CTRL 0x004 |
36 | #define LPC18XX_PWM_CTRL_HALT BIT(2) |
37 | #define LPC18XX_PWM_BIDIR BIT(4) |
38 | #define LPC18XX_PWM_PRE_SHIFT 5 |
39 | #define LPC18XX_PWM_PRE_MASK (0xff << LPC18XX_PWM_PRE_SHIFT) |
40 | #define LPC18XX_PWM_PRE(x) (x << LPC18XX_PWM_PRE_SHIFT) |
41 | |
42 | #define LPC18XX_PWM_LIMIT 0x008 |
43 | |
44 | #define LPC18XX_PWM_RES_BASE 0x058 |
45 | #define LPC18XX_PWM_RES_SHIFT(_ch) (_ch * 2) |
46 | #define LPC18XX_PWM_RES(_ch, _action) (_action << LPC18XX_PWM_RES_SHIFT(_ch)) |
47 | #define LPC18XX_PWM_RES_MASK(_ch) (0x3 << LPC18XX_PWM_RES_SHIFT(_ch)) |
48 | |
49 | #define LPC18XX_PWM_MATCH_BASE 0x100 |
50 | #define LPC18XX_PWM_MATCH(_ch) (LPC18XX_PWM_MATCH_BASE + _ch * 4) |
51 | |
52 | #define LPC18XX_PWM_MATCHREL_BASE 0x200 |
53 | #define LPC18XX_PWM_MATCHREL(_ch) (LPC18XX_PWM_MATCHREL_BASE + _ch * 4) |
54 | |
55 | #define LPC18XX_PWM_EVSTATEMSK_BASE 0x300 |
56 | #define LPC18XX_PWM_EVSTATEMSK(_ch) (LPC18XX_PWM_EVSTATEMSK_BASE + _ch * 8) |
57 | #define LPC18XX_PWM_EVSTATEMSK_ALL 0xffffffff |
58 | |
59 | #define LPC18XX_PWM_EVCTRL_BASE 0x304 |
60 | #define LPC18XX_PWM_EVCTRL(_ev) (LPC18XX_PWM_EVCTRL_BASE + _ev * 8) |
61 | |
62 | #define LPC18XX_PWM_EVCTRL_MATCH(_ch) _ch |
63 | |
64 | #define LPC18XX_PWM_EVCTRL_COMB_SHIFT 12 |
65 | #define LPC18XX_PWM_EVCTRL_COMB_MATCH (0x1 << LPC18XX_PWM_EVCTRL_COMB_SHIFT) |
66 | |
67 | #define LPC18XX_PWM_OUTPUTSET_BASE 0x500 |
68 | #define LPC18XX_PWM_OUTPUTSET(_ch) (LPC18XX_PWM_OUTPUTSET_BASE + _ch * 8) |
69 | |
70 | #define LPC18XX_PWM_OUTPUTCL_BASE 0x504 |
71 | #define LPC18XX_PWM_OUTPUTCL(_ch) (LPC18XX_PWM_OUTPUTCL_BASE + _ch * 8) |
72 | |
73 | /* LPC18xx SCT unified counter */ |
74 | #define LPC18XX_PWM_TIMER_MAX 0xffffffff |
75 | |
76 | /* LPC18xx SCT events */ |
77 | #define LPC18XX_PWM_EVENT_PERIOD 0 |
78 | #define LPC18XX_PWM_EVENT_MAX 16 |
79 | |
80 | #define LPC18XX_NUM_PWMS 16 |
81 | |
82 | /* SCT conflict resolution */ |
83 | enum lpc18xx_pwm_res_action { |
84 | LPC18XX_PWM_RES_NONE, |
85 | LPC18XX_PWM_RES_SET, |
86 | LPC18XX_PWM_RES_CLEAR, |
87 | LPC18XX_PWM_RES_TOGGLE, |
88 | }; |
89 | |
90 | struct lpc18xx_pwm_data { |
91 | unsigned int duty_event; |
92 | }; |
93 | |
94 | struct lpc18xx_pwm_chip { |
95 | struct device *dev; |
96 | struct pwm_chip chip; |
97 | void __iomem *base; |
98 | struct clk *pwm_clk; |
99 | unsigned long clk_rate; |
100 | unsigned int period_ns; |
101 | unsigned int min_period_ns; |
102 | u64 max_period_ns; |
103 | unsigned int period_event; |
104 | unsigned long event_map; |
105 | struct mutex res_lock; |
106 | struct mutex period_lock; |
107 | struct lpc18xx_pwm_data channeldata[LPC18XX_NUM_PWMS]; |
108 | }; |
109 | |
110 | static inline struct lpc18xx_pwm_chip * |
111 | to_lpc18xx_pwm_chip(struct pwm_chip *chip) |
112 | { |
113 | return container_of(chip, struct lpc18xx_pwm_chip, chip); |
114 | } |
115 | |
116 | static inline void lpc18xx_pwm_writel(struct lpc18xx_pwm_chip *lpc18xx_pwm, |
117 | u32 reg, u32 val) |
118 | { |
119 | writel(val, addr: lpc18xx_pwm->base + reg); |
120 | } |
121 | |
122 | static inline u32 lpc18xx_pwm_readl(struct lpc18xx_pwm_chip *lpc18xx_pwm, |
123 | u32 reg) |
124 | { |
125 | return readl(addr: lpc18xx_pwm->base + reg); |
126 | } |
127 | |
128 | static void lpc18xx_pwm_set_conflict_res(struct lpc18xx_pwm_chip *lpc18xx_pwm, |
129 | struct pwm_device *pwm, |
130 | enum lpc18xx_pwm_res_action action) |
131 | { |
132 | u32 val; |
133 | |
134 | mutex_lock(&lpc18xx_pwm->res_lock); |
135 | |
136 | /* |
137 | * Simultaneous set and clear may happen on an output, that is the case |
138 | * when duty_ns == period_ns. LPC18xx SCT allows to set a conflict |
139 | * resolution action to be taken in such a case. |
140 | */ |
141 | val = lpc18xx_pwm_readl(lpc18xx_pwm, LPC18XX_PWM_RES_BASE); |
142 | val &= ~LPC18XX_PWM_RES_MASK(pwm->hwpwm); |
143 | val |= LPC18XX_PWM_RES(pwm->hwpwm, action); |
144 | lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_RES_BASE, val); |
145 | |
146 | mutex_unlock(lock: &lpc18xx_pwm->res_lock); |
147 | } |
148 | |
149 | static void lpc18xx_pwm_config_period(struct pwm_chip *chip, u64 period_ns) |
150 | { |
151 | struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip); |
152 | u32 val; |
153 | |
154 | /* |
155 | * With clk_rate < NSEC_PER_SEC this cannot overflow. |
156 | * With period_ns < max_period_ns this also fits into an u32. |
157 | * As period_ns >= min_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, lpc18xx_pwm->clk_rate); |
158 | * we have val >= 1. |
159 | */ |
160 | val = mul_u64_u64_div_u64(a: period_ns, mul: lpc18xx_pwm->clk_rate, NSEC_PER_SEC); |
161 | |
162 | lpc18xx_pwm_writel(lpc18xx_pwm, |
163 | LPC18XX_PWM_MATCH(lpc18xx_pwm->period_event), |
164 | val: val - 1); |
165 | |
166 | lpc18xx_pwm_writel(lpc18xx_pwm, |
167 | LPC18XX_PWM_MATCHREL(lpc18xx_pwm->period_event), |
168 | val: val - 1); |
169 | } |
170 | |
171 | static void lpc18xx_pwm_config_duty(struct pwm_chip *chip, |
172 | struct pwm_device *pwm, u64 duty_ns) |
173 | { |
174 | struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip); |
175 | struct lpc18xx_pwm_data *lpc18xx_data = &lpc18xx_pwm->channeldata[pwm->hwpwm]; |
176 | u32 val; |
177 | |
178 | /* |
179 | * With clk_rate <= NSEC_PER_SEC this cannot overflow. |
180 | * With duty_ns <= period_ns < max_period_ns this also fits into an u32. |
181 | */ |
182 | val = mul_u64_u64_div_u64(a: duty_ns, mul: lpc18xx_pwm->clk_rate, NSEC_PER_SEC); |
183 | |
184 | lpc18xx_pwm_writel(lpc18xx_pwm, |
185 | LPC18XX_PWM_MATCH(lpc18xx_data->duty_event), |
186 | val); |
187 | |
188 | lpc18xx_pwm_writel(lpc18xx_pwm, |
189 | LPC18XX_PWM_MATCHREL(lpc18xx_data->duty_event), |
190 | val); |
191 | } |
192 | |
193 | static int lpc18xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, |
194 | int duty_ns, int period_ns) |
195 | { |
196 | struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip); |
197 | int requested_events, i; |
198 | |
199 | if (period_ns < lpc18xx_pwm->min_period_ns || |
200 | period_ns > lpc18xx_pwm->max_period_ns) { |
201 | dev_err(chip->dev, "period %d not in range\n" , period_ns); |
202 | return -ERANGE; |
203 | } |
204 | |
205 | mutex_lock(&lpc18xx_pwm->period_lock); |
206 | |
207 | requested_events = bitmap_weight(src: &lpc18xx_pwm->event_map, |
208 | LPC18XX_PWM_EVENT_MAX); |
209 | |
210 | /* |
211 | * The PWM supports only a single period for all PWM channels. |
212 | * Once the period is set, it can only be changed if no more than one |
213 | * channel is requested at that moment. |
214 | */ |
215 | if (requested_events > 2 && lpc18xx_pwm->period_ns != period_ns && |
216 | lpc18xx_pwm->period_ns) { |
217 | dev_err(chip->dev, "conflicting period requested for PWM %u\n" , |
218 | pwm->hwpwm); |
219 | mutex_unlock(lock: &lpc18xx_pwm->period_lock); |
220 | return -EBUSY; |
221 | } |
222 | |
223 | if ((requested_events <= 2 && lpc18xx_pwm->period_ns != period_ns) || |
224 | !lpc18xx_pwm->period_ns) { |
225 | lpc18xx_pwm->period_ns = period_ns; |
226 | for (i = 0; i < chip->npwm; i++) |
227 | pwm_set_period(pwm: &chip->pwms[i], period: period_ns); |
228 | lpc18xx_pwm_config_period(chip, period_ns); |
229 | } |
230 | |
231 | mutex_unlock(lock: &lpc18xx_pwm->period_lock); |
232 | |
233 | lpc18xx_pwm_config_duty(chip, pwm, duty_ns); |
234 | |
235 | return 0; |
236 | } |
237 | |
238 | static int lpc18xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm, enum pwm_polarity polarity) |
239 | { |
240 | struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip); |
241 | struct lpc18xx_pwm_data *lpc18xx_data = &lpc18xx_pwm->channeldata[pwm->hwpwm]; |
242 | enum lpc18xx_pwm_res_action res_action; |
243 | unsigned int set_event, clear_event; |
244 | |
245 | lpc18xx_pwm_writel(lpc18xx_pwm, |
246 | LPC18XX_PWM_EVCTRL(lpc18xx_data->duty_event), |
247 | LPC18XX_PWM_EVCTRL_MATCH(lpc18xx_data->duty_event) | |
248 | LPC18XX_PWM_EVCTRL_COMB_MATCH); |
249 | |
250 | lpc18xx_pwm_writel(lpc18xx_pwm, |
251 | LPC18XX_PWM_EVSTATEMSK(lpc18xx_data->duty_event), |
252 | LPC18XX_PWM_EVSTATEMSK_ALL); |
253 | |
254 | if (polarity == PWM_POLARITY_NORMAL) { |
255 | set_event = lpc18xx_pwm->period_event; |
256 | clear_event = lpc18xx_data->duty_event; |
257 | res_action = LPC18XX_PWM_RES_SET; |
258 | } else { |
259 | set_event = lpc18xx_data->duty_event; |
260 | clear_event = lpc18xx_pwm->period_event; |
261 | res_action = LPC18XX_PWM_RES_CLEAR; |
262 | } |
263 | |
264 | lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_OUTPUTSET(pwm->hwpwm), |
265 | BIT(set_event)); |
266 | lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_OUTPUTCL(pwm->hwpwm), |
267 | BIT(clear_event)); |
268 | lpc18xx_pwm_set_conflict_res(lpc18xx_pwm, pwm, action: res_action); |
269 | |
270 | return 0; |
271 | } |
272 | |
273 | static void lpc18xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) |
274 | { |
275 | struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip); |
276 | struct lpc18xx_pwm_data *lpc18xx_data = &lpc18xx_pwm->channeldata[pwm->hwpwm]; |
277 | |
278 | lpc18xx_pwm_writel(lpc18xx_pwm, |
279 | LPC18XX_PWM_EVCTRL(lpc18xx_data->duty_event), val: 0); |
280 | lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_OUTPUTSET(pwm->hwpwm), val: 0); |
281 | lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_OUTPUTCL(pwm->hwpwm), val: 0); |
282 | } |
283 | |
284 | static int lpc18xx_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) |
285 | { |
286 | struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip); |
287 | struct lpc18xx_pwm_data *lpc18xx_data = &lpc18xx_pwm->channeldata[pwm->hwpwm]; |
288 | unsigned long event; |
289 | |
290 | event = find_first_zero_bit(addr: &lpc18xx_pwm->event_map, |
291 | LPC18XX_PWM_EVENT_MAX); |
292 | |
293 | if (event >= LPC18XX_PWM_EVENT_MAX) { |
294 | dev_err(lpc18xx_pwm->dev, |
295 | "maximum number of simultaneous channels reached\n" ); |
296 | return -EBUSY; |
297 | } |
298 | |
299 | set_bit(nr: event, addr: &lpc18xx_pwm->event_map); |
300 | lpc18xx_data->duty_event = event; |
301 | |
302 | return 0; |
303 | } |
304 | |
305 | static void lpc18xx_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) |
306 | { |
307 | struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip); |
308 | struct lpc18xx_pwm_data *lpc18xx_data = &lpc18xx_pwm->channeldata[pwm->hwpwm]; |
309 | |
310 | clear_bit(nr: lpc18xx_data->duty_event, addr: &lpc18xx_pwm->event_map); |
311 | } |
312 | |
313 | static int lpc18xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, |
314 | const struct pwm_state *state) |
315 | { |
316 | int err; |
317 | bool enabled = pwm->state.enabled; |
318 | |
319 | if (state->polarity != pwm->state.polarity && pwm->state.enabled) { |
320 | lpc18xx_pwm_disable(chip, pwm); |
321 | enabled = false; |
322 | } |
323 | |
324 | if (!state->enabled) { |
325 | if (enabled) |
326 | lpc18xx_pwm_disable(chip, pwm); |
327 | |
328 | return 0; |
329 | } |
330 | |
331 | err = lpc18xx_pwm_config(chip: pwm->chip, pwm, duty_ns: state->duty_cycle, period_ns: state->period); |
332 | if (err) |
333 | return err; |
334 | |
335 | if (!enabled) |
336 | err = lpc18xx_pwm_enable(chip, pwm, polarity: state->polarity); |
337 | |
338 | return err; |
339 | } |
340 | static const struct pwm_ops lpc18xx_pwm_ops = { |
341 | .apply = lpc18xx_pwm_apply, |
342 | .request = lpc18xx_pwm_request, |
343 | .free = lpc18xx_pwm_free, |
344 | .owner = THIS_MODULE, |
345 | }; |
346 | |
347 | static const struct of_device_id lpc18xx_pwm_of_match[] = { |
348 | { .compatible = "nxp,lpc1850-sct-pwm" }, |
349 | {} |
350 | }; |
351 | MODULE_DEVICE_TABLE(of, lpc18xx_pwm_of_match); |
352 | |
353 | static int lpc18xx_pwm_probe(struct platform_device *pdev) |
354 | { |
355 | struct lpc18xx_pwm_chip *lpc18xx_pwm; |
356 | int ret; |
357 | u64 val; |
358 | |
359 | lpc18xx_pwm = devm_kzalloc(dev: &pdev->dev, size: sizeof(*lpc18xx_pwm), |
360 | GFP_KERNEL); |
361 | if (!lpc18xx_pwm) |
362 | return -ENOMEM; |
363 | |
364 | lpc18xx_pwm->dev = &pdev->dev; |
365 | |
366 | lpc18xx_pwm->base = devm_platform_ioremap_resource(pdev, index: 0); |
367 | if (IS_ERR(ptr: lpc18xx_pwm->base)) |
368 | return PTR_ERR(ptr: lpc18xx_pwm->base); |
369 | |
370 | lpc18xx_pwm->pwm_clk = devm_clk_get_enabled(dev: &pdev->dev, id: "pwm" ); |
371 | if (IS_ERR(ptr: lpc18xx_pwm->pwm_clk)) |
372 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: lpc18xx_pwm->pwm_clk), |
373 | fmt: "failed to get pwm clock\n" ); |
374 | |
375 | lpc18xx_pwm->clk_rate = clk_get_rate(clk: lpc18xx_pwm->pwm_clk); |
376 | if (!lpc18xx_pwm->clk_rate) |
377 | return dev_err_probe(dev: &pdev->dev, |
378 | err: -EINVAL, fmt: "pwm clock has no frequency\n" ); |
379 | |
380 | /* |
381 | * If clkrate is too fast, the calculations in .apply() might overflow. |
382 | */ |
383 | if (lpc18xx_pwm->clk_rate > NSEC_PER_SEC) |
384 | return dev_err_probe(dev: &pdev->dev, err: -EINVAL, fmt: "pwm clock to fast\n" ); |
385 | |
386 | mutex_init(&lpc18xx_pwm->res_lock); |
387 | mutex_init(&lpc18xx_pwm->period_lock); |
388 | |
389 | lpc18xx_pwm->max_period_ns = |
390 | mul_u64_u64_div_u64(NSEC_PER_SEC, LPC18XX_PWM_TIMER_MAX, div: lpc18xx_pwm->clk_rate); |
391 | |
392 | lpc18xx_pwm->min_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, |
393 | lpc18xx_pwm->clk_rate); |
394 | |
395 | lpc18xx_pwm->chip.dev = &pdev->dev; |
396 | lpc18xx_pwm->chip.ops = &lpc18xx_pwm_ops; |
397 | lpc18xx_pwm->chip.npwm = LPC18XX_NUM_PWMS; |
398 | |
399 | /* SCT counter must be in unify (32 bit) mode */ |
400 | lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_CONFIG, |
401 | LPC18XX_PWM_CONFIG_UNIFY); |
402 | |
403 | /* |
404 | * Everytime the timer counter reaches the period value, the related |
405 | * event will be triggered and the counter reset to 0. |
406 | */ |
407 | set_bit(LPC18XX_PWM_EVENT_PERIOD, addr: &lpc18xx_pwm->event_map); |
408 | lpc18xx_pwm->period_event = LPC18XX_PWM_EVENT_PERIOD; |
409 | |
410 | lpc18xx_pwm_writel(lpc18xx_pwm, |
411 | LPC18XX_PWM_EVSTATEMSK(lpc18xx_pwm->period_event), |
412 | LPC18XX_PWM_EVSTATEMSK_ALL); |
413 | |
414 | val = LPC18XX_PWM_EVCTRL_MATCH(lpc18xx_pwm->period_event) | |
415 | LPC18XX_PWM_EVCTRL_COMB_MATCH; |
416 | lpc18xx_pwm_writel(lpc18xx_pwm, |
417 | LPC18XX_PWM_EVCTRL(lpc18xx_pwm->period_event), val); |
418 | |
419 | lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_LIMIT, |
420 | BIT(lpc18xx_pwm->period_event)); |
421 | |
422 | val = lpc18xx_pwm_readl(lpc18xx_pwm, LPC18XX_PWM_CTRL); |
423 | val &= ~LPC18XX_PWM_BIDIR; |
424 | val &= ~LPC18XX_PWM_CTRL_HALT; |
425 | val &= ~LPC18XX_PWM_PRE_MASK; |
426 | val |= LPC18XX_PWM_PRE(0); |
427 | lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_CTRL, val); |
428 | |
429 | ret = pwmchip_add(chip: &lpc18xx_pwm->chip); |
430 | if (ret < 0) |
431 | return dev_err_probe(dev: &pdev->dev, err: ret, fmt: "pwmchip_add failed\n" ); |
432 | |
433 | platform_set_drvdata(pdev, data: lpc18xx_pwm); |
434 | |
435 | return 0; |
436 | } |
437 | |
438 | static void lpc18xx_pwm_remove(struct platform_device *pdev) |
439 | { |
440 | struct lpc18xx_pwm_chip *lpc18xx_pwm = platform_get_drvdata(pdev); |
441 | u32 val; |
442 | |
443 | pwmchip_remove(chip: &lpc18xx_pwm->chip); |
444 | |
445 | val = lpc18xx_pwm_readl(lpc18xx_pwm, LPC18XX_PWM_CTRL); |
446 | lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_CTRL, |
447 | val: val | LPC18XX_PWM_CTRL_HALT); |
448 | } |
449 | |
450 | static struct platform_driver lpc18xx_pwm_driver = { |
451 | .driver = { |
452 | .name = "lpc18xx-sct-pwm" , |
453 | .of_match_table = lpc18xx_pwm_of_match, |
454 | }, |
455 | .probe = lpc18xx_pwm_probe, |
456 | .remove_new = lpc18xx_pwm_remove, |
457 | }; |
458 | module_platform_driver(lpc18xx_pwm_driver); |
459 | |
460 | MODULE_AUTHOR("Ariel D'Alessandro <ariel@vanguardiasur.com.ar>" ); |
461 | MODULE_DESCRIPTION("NXP LPC18xx PWM driver" ); |
462 | MODULE_LICENSE("GPL v2" ); |
463 | |