]>
Commit | Line | Data |
---|---|---|
2bf7ecf7 CD |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * corePWM driver for Microchip "soft" FPGA IP cores. | |
4 | * | |
5 | * Copyright (c) 2021-2023 Microchip Corporation. All rights reserved. | |
6 | * Author: Conor Dooley <[email protected]> | |
7 | * Documentation: | |
8 | * https://www.microsemi.com/document-portal/doc_download/1245275-corepwm-hb | |
9 | * | |
10 | * Limitations: | |
11 | * - If the IP block is configured without "shadow registers", all register | |
12 | * writes will take effect immediately, causing glitches on the output. | |
13 | * If shadow registers *are* enabled, setting the "SYNC_UPDATE" register | |
14 | * notifies the core that it needs to update the registers defining the | |
15 | * waveform from the contents of the "shadow registers". Otherwise, changes | |
16 | * will take effective immediately, even for those channels. | |
17 | * As setting the period/duty cycle takes 4 register writes, there is a window | |
18 | * in which this races against the start of a new period. | |
19 | * - The IP block has no concept of a duty cycle, only rising/falling edges of | |
20 | * the waveform. Unfortunately, if the rising & falling edges registers have | |
21 | * the same value written to them the IP block will do whichever of a rising | |
22 | * or a falling edge is possible. I.E. a 50% waveform at twice the requested | |
23 | * period. Therefore to get a 0% waveform, the output is set the max high/low | |
24 | * time depending on polarity. | |
25 | * If the duty cycle is 0%, and the requested period is less than the | |
26 | * available period resolution, this will manifest as a ~100% waveform (with | |
27 | * some output glitches) rather than 50%. | |
28 | * - The PWM period is set for the whole IP block not per channel. The driver | |
29 | * will only change the period if no other PWM output is enabled. | |
30 | */ | |
31 | ||
32 | #include <linux/clk.h> | |
33 | #include <linux/delay.h> | |
34 | #include <linux/err.h> | |
35 | #include <linux/io.h> | |
36 | #include <linux/ktime.h> | |
37 | #include <linux/math.h> | |
38 | #include <linux/module.h> | |
39 | #include <linux/mutex.h> | |
0a41b0c5 | 40 | #include <linux/of.h> |
2bf7ecf7 CD |
41 | #include <linux/platform_device.h> |
42 | #include <linux/pwm.h> | |
43 | ||
44 | #define MCHPCOREPWM_PRESCALE_MAX 0xff | |
45 | #define MCHPCOREPWM_PERIOD_STEPS_MAX 0xfe | |
46 | #define MCHPCOREPWM_PERIOD_MAX 0xff00 | |
47 | ||
48 | #define MCHPCOREPWM_PRESCALE 0x00 | |
49 | #define MCHPCOREPWM_PERIOD 0x04 | |
50 | #define MCHPCOREPWM_EN(i) (0x08 + 0x04 * (i)) /* 0x08, 0x0c */ | |
51 | #define MCHPCOREPWM_POSEDGE(i) (0x10 + 0x08 * (i)) /* 0x10, 0x18, ..., 0x88 */ | |
52 | #define MCHPCOREPWM_NEGEDGE(i) (0x14 + 0x08 * (i)) /* 0x14, 0x1c, ..., 0x8c */ | |
53 | #define MCHPCOREPWM_SYNC_UPD 0xe4 | |
54 | #define MCHPCOREPWM_TIMEOUT_MS 100u | |
55 | ||
56 | struct mchp_core_pwm_chip { | |
57 | struct pwm_chip chip; | |
58 | struct clk *clk; | |
59 | void __iomem *base; | |
60 | struct mutex lock; /* protects the shared period */ | |
61 | ktime_t update_timestamp; | |
62 | u32 sync_update_mask; | |
63 | u16 channel_enabled; | |
64 | }; | |
65 | ||
66 | static inline struct mchp_core_pwm_chip *to_mchp_core_pwm(struct pwm_chip *chip) | |
67 | { | |
68 | return container_of(chip, struct mchp_core_pwm_chip, chip); | |
69 | } | |
70 | ||
71 | static void mchp_core_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm, | |
72 | bool enable, u64 period) | |
73 | { | |
74 | struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip); | |
75 | u8 channel_enable, reg_offset, shift; | |
76 | ||
77 | /* | |
78 | * There are two adjacent 8 bit control regs, the lower reg controls | |
79 | * 0-7 and the upper reg 8-15. Check if the pwm is in the upper reg | |
80 | * and if so, offset by the bus width. | |
81 | */ | |
82 | reg_offset = MCHPCOREPWM_EN(pwm->hwpwm >> 3); | |
83 | shift = pwm->hwpwm & 7; | |
84 | ||
85 | channel_enable = readb_relaxed(mchp_core_pwm->base + reg_offset); | |
86 | channel_enable &= ~(1 << shift); | |
87 | channel_enable |= (enable << shift); | |
88 | ||
89 | writel_relaxed(channel_enable, mchp_core_pwm->base + reg_offset); | |
90 | mchp_core_pwm->channel_enabled &= ~BIT(pwm->hwpwm); | |
91 | mchp_core_pwm->channel_enabled |= enable << pwm->hwpwm; | |
92 | ||
93 | /* | |
94 | * The updated values will not appear on the bus until they have been | |
95 | * applied to the waveform at the beginning of the next period. | |
96 | * This is a NO-OP if the channel does not have shadow registers. | |
97 | */ | |
98 | if (mchp_core_pwm->sync_update_mask & (1 << pwm->hwpwm)) | |
99 | mchp_core_pwm->update_timestamp = ktime_add_ns(ktime_get(), period); | |
100 | } | |
101 | ||
102 | static void mchp_core_pwm_wait_for_sync_update(struct mchp_core_pwm_chip *mchp_core_pwm, | |
103 | unsigned int channel) | |
104 | { | |
105 | /* | |
106 | * If a shadow register is used for this PWM channel, and iff there is | |
107 | * a pending update to the waveform, we must wait for it to be applied | |
108 | * before attempting to read its state. Reading the registers yields | |
109 | * the currently implemented settings & the new ones are only readable | |
110 | * once the current period has ended. | |
111 | */ | |
112 | ||
113 | if (mchp_core_pwm->sync_update_mask & (1 << channel)) { | |
114 | ktime_t current_time = ktime_get(); | |
115 | s64 remaining_ns; | |
116 | u32 delay_us; | |
117 | ||
118 | remaining_ns = ktime_to_ns(ktime_sub(mchp_core_pwm->update_timestamp, | |
119 | current_time)); | |
120 | ||
121 | /* | |
122 | * If the update has gone through, don't bother waiting for | |
123 | * obvious reasons. Otherwise wait around for an appropriate | |
124 | * amount of time for the update to go through. | |
125 | */ | |
126 | if (remaining_ns <= 0) | |
127 | return; | |
128 | ||
129 | delay_us = DIV_ROUND_UP_ULL(remaining_ns, NSEC_PER_USEC); | |
130 | fsleep(delay_us); | |
131 | } | |
132 | } | |
133 | ||
134 | static u64 mchp_core_pwm_calc_duty(const struct pwm_state *state, u64 clk_rate, | |
135 | u8 prescale, u8 period_steps) | |
136 | { | |
137 | u64 duty_steps, tmp; | |
138 | ||
139 | /* | |
140 | * Calculate the duty cycle in multiples of the prescaled period: | |
141 | * duty_steps = duty_in_ns / step_in_ns | |
142 | * step_in_ns = (prescale * NSEC_PER_SEC) / clk_rate | |
143 | * The code below is rearranged slightly to only divide once. | |
144 | */ | |
145 | tmp = (((u64)prescale) + 1) * NSEC_PER_SEC; | |
146 | duty_steps = mul_u64_u64_div_u64(state->duty_cycle, clk_rate, tmp); | |
147 | ||
148 | return duty_steps; | |
149 | } | |
150 | ||
151 | static void mchp_core_pwm_apply_duty(struct pwm_chip *chip, struct pwm_device *pwm, | |
152 | const struct pwm_state *state, u64 duty_steps, | |
153 | u16 period_steps) | |
154 | { | |
155 | struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip); | |
156 | u8 posedge, negedge; | |
157 | u8 first_edge = 0, second_edge = duty_steps; | |
158 | ||
159 | /* | |
160 | * Setting posedge == negedge doesn't yield a constant output, | |
161 | * so that's an unsuitable setting to model duty_steps = 0. | |
162 | * In that case set the unwanted edge to a value that never | |
163 | * triggers. | |
164 | */ | |
165 | if (duty_steps == 0) | |
166 | first_edge = period_steps + 1; | |
167 | ||
168 | if (state->polarity == PWM_POLARITY_INVERSED) { | |
169 | negedge = first_edge; | |
170 | posedge = second_edge; | |
171 | } else { | |
172 | posedge = first_edge; | |
173 | negedge = second_edge; | |
174 | } | |
175 | ||
176 | /* | |
177 | * Set the sync bit which ensures that periods that already started are | |
178 | * completed unaltered. At each counter reset event the values are | |
179 | * updated from the shadow registers. | |
180 | */ | |
181 | writel_relaxed(posedge, mchp_core_pwm->base + MCHPCOREPWM_POSEDGE(pwm->hwpwm)); | |
182 | writel_relaxed(negedge, mchp_core_pwm->base + MCHPCOREPWM_NEGEDGE(pwm->hwpwm)); | |
183 | } | |
184 | ||
185 | static int mchp_core_pwm_calc_period(const struct pwm_state *state, unsigned long clk_rate, | |
186 | u16 *prescale, u16 *period_steps) | |
187 | { | |
188 | u64 tmp; | |
189 | ||
190 | /* | |
191 | * Calculate the period cycles and prescale values. | |
192 | * The registers are each 8 bits wide & multiplied to compute the period | |
193 | * using the formula: | |
194 | * (prescale + 1) * (period_steps + 1) | |
195 | * period = ------------------------------------- | |
196 | * clk_rate | |
197 | * so the maximum period that can be generated is 0x10000 times the | |
198 | * period of the input clock. | |
199 | * However, due to the design of the "hardware", it is not possible to | |
200 | * attain a 100% duty cycle if the full range of period_steps is used. | |
201 | * Therefore period_steps is restricted to 0xfe and the maximum multiple | |
202 | * of the clock period attainable is (0xff + 1) * (0xfe + 1) = 0xff00 | |
203 | * | |
204 | * The prescale and period_steps registers operate similarly to | |
205 | * CLK_DIVIDER_ONE_BASED, where the value used by the hardware is that | |
206 | * in the register plus one. | |
207 | * It's therefore not possible to set a period lower than 1/clk_rate, so | |
208 | * if tmp is 0, abort. Without aborting, we will set a period that is | |
209 | * greater than that requested and, more importantly, will trigger the | |
210 | * neg-/pos-edge issue described in the limitations. | |
211 | */ | |
212 | tmp = mul_u64_u64_div_u64(state->period, clk_rate, NSEC_PER_SEC); | |
213 | if (tmp >= MCHPCOREPWM_PERIOD_MAX) { | |
214 | *prescale = MCHPCOREPWM_PRESCALE_MAX; | |
215 | *period_steps = MCHPCOREPWM_PERIOD_STEPS_MAX; | |
216 | ||
217 | return 0; | |
218 | } | |
219 | ||
220 | /* | |
221 | * There are multiple strategies that could be used to choose the | |
222 | * prescale & period_steps values. | |
223 | * Here the idea is to pick values so that the selection of duty cycles | |
224 | * is as finegrain as possible, while also keeping the period less than | |
225 | * that requested. | |
226 | * | |
227 | * A simple way to satisfy the first condition is to always set | |
228 | * period_steps to its maximum value. This neatly also satisfies the | |
229 | * second condition too, since using the maximum value of period_steps | |
230 | * to calculate prescale actually calculates its upper bound. | |
231 | * Integer division will ensure a round down, so the period will thereby | |
232 | * always be less than that requested. | |
233 | * | |
234 | * The downside of this approach is a significant degree of inaccuracy, | |
235 | * especially as tmp approaches integer multiples of | |
236 | * MCHPCOREPWM_PERIOD_STEPS_MAX. | |
237 | * | |
238 | * As we must produce a period less than that requested, and for the | |
239 | * sake of creating a simple algorithm, disallow small values of tmp | |
240 | * that would need special handling. | |
241 | */ | |
242 | if (tmp < MCHPCOREPWM_PERIOD_STEPS_MAX + 1) | |
243 | return -EINVAL; | |
244 | ||
245 | /* | |
246 | * This "optimal" value for prescale is be calculated using the maximum | |
247 | * permitted value of period_steps, 0xfe. | |
248 | * | |
249 | * period * clk_rate | |
250 | * prescale = ------------------------- - 1 | |
251 | * NSEC_PER_SEC * (0xfe + 1) | |
252 | * | |
253 | * | |
254 | * period * clk_rate | |
255 | * ------------------- was precomputed as `tmp` | |
256 | * NSEC_PER_SEC | |
257 | */ | |
258 | *prescale = ((u16)tmp) / (MCHPCOREPWM_PERIOD_STEPS_MAX + 1) - 1; | |
259 | ||
260 | /* | |
261 | * period_steps can be computed from prescale: | |
262 | * period * clk_rate | |
263 | * period_steps = ----------------------------- - 1 | |
264 | * NSEC_PER_SEC * (prescale + 1) | |
265 | * | |
266 | * However, in this approximation, we simply use the maximum value that | |
267 | * was used to compute prescale. | |
268 | */ | |
269 | *period_steps = MCHPCOREPWM_PERIOD_STEPS_MAX; | |
270 | ||
271 | return 0; | |
272 | } | |
273 | ||
274 | static int mchp_core_pwm_apply_locked(struct pwm_chip *chip, struct pwm_device *pwm, | |
275 | const struct pwm_state *state) | |
276 | { | |
277 | struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip); | |
278 | bool period_locked; | |
279 | unsigned long clk_rate; | |
280 | u64 duty_steps; | |
281 | u16 prescale, period_steps; | |
282 | int ret; | |
283 | ||
284 | if (!state->enabled) { | |
285 | mchp_core_pwm_enable(chip, pwm, false, pwm->state.period); | |
286 | return 0; | |
287 | } | |
288 | ||
289 | /* | |
290 | * If clk_rate is too big, the following multiplication might overflow. | |
291 | * However this is implausible, as the fabric of current FPGAs cannot | |
292 | * provide clocks at a rate high enough. | |
293 | */ | |
294 | clk_rate = clk_get_rate(mchp_core_pwm->clk); | |
295 | if (clk_rate >= NSEC_PER_SEC) | |
296 | return -EINVAL; | |
297 | ||
298 | ret = mchp_core_pwm_calc_period(state, clk_rate, &prescale, &period_steps); | |
299 | if (ret) | |
300 | return ret; | |
301 | ||
302 | /* | |
303 | * If the only thing that has changed is the duty cycle or the polarity, | |
304 | * we can shortcut the calculations and just compute/apply the new duty | |
305 | * cycle pos & neg edges | |
306 | * As all the channels share the same period, do not allow it to be | |
307 | * changed if any other channels are enabled. | |
308 | * If the period is locked, it may not be possible to use a period | |
309 | * less than that requested. In that case, we just abort. | |
310 | */ | |
311 | period_locked = mchp_core_pwm->channel_enabled & ~(1 << pwm->hwpwm); | |
312 | ||
313 | if (period_locked) { | |
314 | u16 hw_prescale; | |
315 | u16 hw_period_steps; | |
316 | ||
317 | hw_prescale = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PRESCALE); | |
318 | hw_period_steps = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PERIOD); | |
319 | ||
320 | if ((period_steps + 1) * (prescale + 1) < | |
321 | (hw_period_steps + 1) * (hw_prescale + 1)) | |
322 | return -EINVAL; | |
323 | ||
324 | /* | |
325 | * It is possible that something could have set the period_steps | |
326 | * register to 0xff, which would prevent us from setting a 100% | |
327 | * or 0% relative duty cycle, as explained above in | |
328 | * mchp_core_pwm_calc_period(). | |
329 | * The period is locked and we cannot change this, so we abort. | |
330 | */ | |
331 | if (hw_period_steps == MCHPCOREPWM_PERIOD_STEPS_MAX) | |
332 | return -EINVAL; | |
333 | ||
334 | prescale = hw_prescale; | |
335 | period_steps = hw_period_steps; | |
336 | } | |
337 | ||
338 | duty_steps = mchp_core_pwm_calc_duty(state, clk_rate, prescale, period_steps); | |
339 | ||
340 | /* | |
341 | * Because the period is not per channel, it is possible that the | |
342 | * requested duty cycle is longer than the period, in which case cap it | |
343 | * to the period, IOW a 100% duty cycle. | |
344 | */ | |
345 | if (duty_steps > period_steps) | |
346 | duty_steps = period_steps + 1; | |
347 | ||
348 | if (!period_locked) { | |
349 | writel_relaxed(prescale, mchp_core_pwm->base + MCHPCOREPWM_PRESCALE); | |
350 | writel_relaxed(period_steps, mchp_core_pwm->base + MCHPCOREPWM_PERIOD); | |
351 | } | |
352 | ||
353 | mchp_core_pwm_apply_duty(chip, pwm, state, duty_steps, period_steps); | |
354 | ||
355 | mchp_core_pwm_enable(chip, pwm, true, pwm->state.period); | |
356 | ||
357 | return 0; | |
358 | } | |
359 | ||
360 | static int mchp_core_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, | |
361 | const struct pwm_state *state) | |
362 | { | |
363 | struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip); | |
364 | int ret; | |
365 | ||
366 | mutex_lock(&mchp_core_pwm->lock); | |
367 | ||
368 | mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm); | |
369 | ||
370 | ret = mchp_core_pwm_apply_locked(chip, pwm, state); | |
371 | ||
372 | mutex_unlock(&mchp_core_pwm->lock); | |
373 | ||
374 | return ret; | |
375 | } | |
376 | ||
377 | static int mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, | |
378 | struct pwm_state *state) | |
379 | { | |
380 | struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip); | |
381 | u64 rate; | |
382 | u16 prescale, period_steps; | |
383 | u8 duty_steps, posedge, negedge; | |
384 | ||
385 | mutex_lock(&mchp_core_pwm->lock); | |
386 | ||
387 | mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm); | |
388 | ||
389 | if (mchp_core_pwm->channel_enabled & (1 << pwm->hwpwm)) | |
390 | state->enabled = true; | |
391 | else | |
392 | state->enabled = false; | |
393 | ||
394 | rate = clk_get_rate(mchp_core_pwm->clk); | |
395 | ||
396 | /* | |
397 | * Calculating the period: | |
398 | * The registers are each 8 bits wide & multiplied to compute the period | |
399 | * using the formula: | |
400 | * (prescale + 1) * (period_steps + 1) | |
401 | * period = ------------------------------------- | |
402 | * clk_rate | |
403 | * | |
404 | * Note: | |
405 | * The prescale and period_steps registers operate similarly to | |
406 | * CLK_DIVIDER_ONE_BASED, where the value used by the hardware is that | |
407 | * in the register plus one. | |
408 | */ | |
409 | prescale = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PRESCALE); | |
410 | period_steps = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PERIOD); | |
411 | ||
412 | state->period = (period_steps + 1) * (prescale + 1); | |
413 | state->period *= NSEC_PER_SEC; | |
414 | state->period = DIV64_U64_ROUND_UP(state->period, rate); | |
415 | ||
416 | posedge = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_POSEDGE(pwm->hwpwm)); | |
417 | negedge = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_NEGEDGE(pwm->hwpwm)); | |
418 | ||
419 | mutex_unlock(&mchp_core_pwm->lock); | |
420 | ||
421 | if (negedge == posedge) { | |
422 | state->duty_cycle = state->period; | |
423 | state->period *= 2; | |
424 | } else { | |
425 | duty_steps = abs((s16)posedge - (s16)negedge); | |
426 | state->duty_cycle = duty_steps * (prescale + 1) * NSEC_PER_SEC; | |
427 | state->duty_cycle = DIV64_U64_ROUND_UP(state->duty_cycle, rate); | |
428 | } | |
429 | ||
430 | state->polarity = negedge < posedge ? PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL; | |
431 | ||
432 | return 0; | |
433 | } | |
434 | ||
435 | static const struct pwm_ops mchp_core_pwm_ops = { | |
436 | .apply = mchp_core_pwm_apply, | |
437 | .get_state = mchp_core_pwm_get_state, | |
2bf7ecf7 CD |
438 | }; |
439 | ||
440 | static const struct of_device_id mchp_core_of_match[] = { | |
441 | { | |
442 | .compatible = "microchip,corepwm-rtl-v4", | |
443 | }, | |
444 | { /* sentinel */ } | |
445 | }; | |
446 | MODULE_DEVICE_TABLE(of, mchp_core_of_match); | |
447 | ||
448 | static int mchp_core_pwm_probe(struct platform_device *pdev) | |
449 | { | |
450 | struct mchp_core_pwm_chip *mchp_core_pwm; | |
451 | struct resource *regs; | |
452 | int ret; | |
453 | ||
454 | mchp_core_pwm = devm_kzalloc(&pdev->dev, sizeof(*mchp_core_pwm), GFP_KERNEL); | |
455 | if (!mchp_core_pwm) | |
456 | return -ENOMEM; | |
457 | ||
458 | mchp_core_pwm->base = devm_platform_get_and_ioremap_resource(pdev, 0, ®s); | |
459 | if (IS_ERR(mchp_core_pwm->base)) | |
460 | return PTR_ERR(mchp_core_pwm->base); | |
461 | ||
462 | mchp_core_pwm->clk = devm_clk_get_enabled(&pdev->dev, NULL); | |
463 | if (IS_ERR(mchp_core_pwm->clk)) | |
464 | return dev_err_probe(&pdev->dev, PTR_ERR(mchp_core_pwm->clk), | |
465 | "failed to get PWM clock\n"); | |
466 | ||
467 | if (of_property_read_u32(pdev->dev.of_node, "microchip,sync-update-mask", | |
468 | &mchp_core_pwm->sync_update_mask)) | |
469 | mchp_core_pwm->sync_update_mask = 0; | |
470 | ||
471 | mutex_init(&mchp_core_pwm->lock); | |
472 | ||
473 | mchp_core_pwm->chip.dev = &pdev->dev; | |
474 | mchp_core_pwm->chip.ops = &mchp_core_pwm_ops; | |
475 | mchp_core_pwm->chip.npwm = 16; | |
476 | ||
477 | mchp_core_pwm->channel_enabled = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_EN(0)); | |
478 | mchp_core_pwm->channel_enabled |= | |
479 | readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_EN(1)) << 8; | |
480 | ||
481 | /* | |
482 | * Enable synchronous update mode for all channels for which shadow | |
483 | * registers have been synthesised. | |
484 | */ | |
485 | writel_relaxed(1U, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD); | |
486 | mchp_core_pwm->update_timestamp = ktime_get(); | |
487 | ||
488 | ret = devm_pwmchip_add(&pdev->dev, &mchp_core_pwm->chip); | |
489 | if (ret) | |
490 | return dev_err_probe(&pdev->dev, ret, "Failed to add pwmchip\n"); | |
491 | ||
492 | return 0; | |
493 | } | |
494 | ||
495 | static struct platform_driver mchp_core_pwm_driver = { | |
496 | .driver = { | |
497 | .name = "mchp-core-pwm", | |
498 | .of_match_table = mchp_core_of_match, | |
499 | }, | |
500 | .probe = mchp_core_pwm_probe, | |
501 | }; | |
502 | module_platform_driver(mchp_core_pwm_driver); | |
503 | ||
504 | MODULE_LICENSE("GPL"); | |
505 | MODULE_AUTHOR("Conor Dooley <[email protected]>"); | |
506 | MODULE_DESCRIPTION("corePWM driver for Microchip FPGAs"); |