1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * jh7110_pwmdac.c -- StarFive JH7110 PWM-DAC driver |
4 | * |
5 | * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. |
6 | * |
7 | * Authors: Jenny Zhang |
8 | * Curry Zhang |
9 | * Xingyu Wu <xingyu.wu@starfivetech.com> |
10 | * Hal Feng <hal.feng@starfivetech.com> |
11 | */ |
12 | |
13 | #include <linux/clk.h> |
14 | #include <linux/device.h> |
15 | #include <linux/init.h> |
16 | #include <linux/interrupt.h> |
17 | #include <linux/io.h> |
18 | #include <linux/module.h> |
19 | #include <linux/pm_runtime.h> |
20 | #include <linux/reset.h> |
21 | #include <linux/slab.h> |
22 | #include <linux/types.h> |
23 | #include <sound/dmaengine_pcm.h> |
24 | #include <sound/pcm.h> |
25 | #include <sound/pcm_params.h> |
26 | #include <sound/soc.h> |
27 | |
28 | #define JH7110_PWMDAC_WDATA 0x00 |
29 | #define JH7110_PWMDAC_CTRL 0x04 |
30 | #define JH7110_PWMDAC_ENABLE BIT(0) |
31 | #define JH7110_PWMDAC_SHIFT BIT(1) |
32 | #define JH7110_PWMDAC_DUTY_CYCLE_SHIFT 2 |
33 | #define JH7110_PWMDAC_DUTY_CYCLE_MASK GENMASK(3, 2) |
34 | #define JH7110_PWMDAC_CNT_N_SHIFT 4 |
35 | #define JH7110_PWMDAC_CNT_N_MASK GENMASK(12, 4) |
36 | #define JH7110_PWMDAC_DATA_CHANGE BIT(13) |
37 | #define JH7110_PWMDAC_DATA_MODE BIT(14) |
38 | #define JH7110_PWMDAC_DATA_SHIFT_SHIFT 15 |
39 | #define JH7110_PWMDAC_DATA_SHIFT_MASK GENMASK(17, 15) |
40 | |
41 | enum JH7110_PWMDAC_SHIFT_VAL { |
42 | PWMDAC_SHIFT_8 = 0, |
43 | PWMDAC_SHIFT_10, |
44 | }; |
45 | |
46 | enum JH7110_PWMDAC_DUTY_CYCLE_VAL { |
47 | PWMDAC_CYCLE_LEFT = 0, |
48 | PWMDAC_CYCLE_RIGHT, |
49 | PWMDAC_CYCLE_CENTER, |
50 | }; |
51 | |
52 | enum JH7110_PWMDAC_CNT_N_VAL { |
53 | PWMDAC_SAMPLE_CNT_1 = 1, |
54 | PWMDAC_SAMPLE_CNT_2, |
55 | PWMDAC_SAMPLE_CNT_3, |
56 | PWMDAC_SAMPLE_CNT_512 = 512, /* max */ |
57 | }; |
58 | |
59 | enum JH7110_PWMDAC_DATA_CHANGE_VAL { |
60 | NO_CHANGE = 0, |
61 | CHANGE, |
62 | }; |
63 | |
64 | enum JH7110_PWMDAC_DATA_MODE_VAL { |
65 | UNSIGNED_DATA = 0, |
66 | INVERTER_DATA_MSB, |
67 | }; |
68 | |
69 | enum JH7110_PWMDAC_DATA_SHIFT_VAL { |
70 | PWMDAC_DATA_LEFT_SHIFT_BIT_0 = 0, |
71 | PWMDAC_DATA_LEFT_SHIFT_BIT_1, |
72 | PWMDAC_DATA_LEFT_SHIFT_BIT_2, |
73 | PWMDAC_DATA_LEFT_SHIFT_BIT_3, |
74 | PWMDAC_DATA_LEFT_SHIFT_BIT_4, |
75 | PWMDAC_DATA_LEFT_SHIFT_BIT_5, |
76 | PWMDAC_DATA_LEFT_SHIFT_BIT_6, |
77 | PWMDAC_DATA_LEFT_SHIFT_BIT_7, |
78 | }; |
79 | |
80 | struct jh7110_pwmdac_cfg { |
81 | enum JH7110_PWMDAC_SHIFT_VAL shift; |
82 | enum JH7110_PWMDAC_DUTY_CYCLE_VAL duty_cycle; |
83 | u16 cnt_n; |
84 | enum JH7110_PWMDAC_DATA_CHANGE_VAL data_change; |
85 | enum JH7110_PWMDAC_DATA_MODE_VAL data_mode; |
86 | enum JH7110_PWMDAC_DATA_SHIFT_VAL data_shift; |
87 | }; |
88 | |
89 | struct jh7110_pwmdac_dev { |
90 | void __iomem *base; |
91 | resource_size_t mapbase; |
92 | struct jh7110_pwmdac_cfg cfg; |
93 | |
94 | struct clk_bulk_data clks[2]; |
95 | struct reset_control *rst_apb; |
96 | struct device *dev; |
97 | struct snd_dmaengine_dai_dma_data play_dma_data; |
98 | u32 saved_ctrl; |
99 | }; |
100 | |
101 | static inline void jh7110_pwmdac_write_reg(void __iomem *io_base, int reg, u32 val) |
102 | { |
103 | writel(val, addr: io_base + reg); |
104 | } |
105 | |
106 | static inline u32 jh7110_pwmdac_read_reg(void __iomem *io_base, int reg) |
107 | { |
108 | return readl(addr: io_base + reg); |
109 | } |
110 | |
111 | static void jh7110_pwmdac_set_enable(struct jh7110_pwmdac_dev *dev, bool enable) |
112 | { |
113 | u32 value; |
114 | |
115 | value = jh7110_pwmdac_read_reg(io_base: dev->base, JH7110_PWMDAC_CTRL); |
116 | if (enable) |
117 | value |= JH7110_PWMDAC_ENABLE; |
118 | else |
119 | value &= ~JH7110_PWMDAC_ENABLE; |
120 | |
121 | jh7110_pwmdac_write_reg(io_base: dev->base, JH7110_PWMDAC_CTRL, val: value); |
122 | } |
123 | |
124 | static void jh7110_pwmdac_set_shift(struct jh7110_pwmdac_dev *dev) |
125 | { |
126 | u32 value; |
127 | |
128 | value = jh7110_pwmdac_read_reg(io_base: dev->base, JH7110_PWMDAC_CTRL); |
129 | if (dev->cfg.shift == PWMDAC_SHIFT_8) |
130 | value &= ~JH7110_PWMDAC_SHIFT; |
131 | else if (dev->cfg.shift == PWMDAC_SHIFT_10) |
132 | value |= JH7110_PWMDAC_SHIFT; |
133 | |
134 | jh7110_pwmdac_write_reg(io_base: dev->base, JH7110_PWMDAC_CTRL, val: value); |
135 | } |
136 | |
137 | static void jh7110_pwmdac_set_duty_cycle(struct jh7110_pwmdac_dev *dev) |
138 | { |
139 | u32 value; |
140 | |
141 | value = jh7110_pwmdac_read_reg(io_base: dev->base, JH7110_PWMDAC_CTRL); |
142 | value &= ~JH7110_PWMDAC_DUTY_CYCLE_MASK; |
143 | value |= (dev->cfg.duty_cycle & 0x3) << JH7110_PWMDAC_DUTY_CYCLE_SHIFT; |
144 | |
145 | jh7110_pwmdac_write_reg(io_base: dev->base, JH7110_PWMDAC_CTRL, val: value); |
146 | } |
147 | |
148 | static void jh7110_pwmdac_set_cnt_n(struct jh7110_pwmdac_dev *dev) |
149 | { |
150 | u32 value; |
151 | |
152 | value = jh7110_pwmdac_read_reg(io_base: dev->base, JH7110_PWMDAC_CTRL); |
153 | value &= ~JH7110_PWMDAC_CNT_N_MASK; |
154 | value |= ((dev->cfg.cnt_n - 1) & 0x1ff) << JH7110_PWMDAC_CNT_N_SHIFT; |
155 | |
156 | jh7110_pwmdac_write_reg(io_base: dev->base, JH7110_PWMDAC_CTRL, val: value); |
157 | } |
158 | |
159 | static void jh7110_pwmdac_set_data_change(struct jh7110_pwmdac_dev *dev) |
160 | { |
161 | u32 value; |
162 | |
163 | value = jh7110_pwmdac_read_reg(io_base: dev->base, JH7110_PWMDAC_CTRL); |
164 | if (dev->cfg.data_change == NO_CHANGE) |
165 | value &= ~JH7110_PWMDAC_DATA_CHANGE; |
166 | else if (dev->cfg.data_change == CHANGE) |
167 | value |= JH7110_PWMDAC_DATA_CHANGE; |
168 | |
169 | jh7110_pwmdac_write_reg(io_base: dev->base, JH7110_PWMDAC_CTRL, val: value); |
170 | } |
171 | |
172 | static void jh7110_pwmdac_set_data_mode(struct jh7110_pwmdac_dev *dev) |
173 | { |
174 | u32 value; |
175 | |
176 | value = jh7110_pwmdac_read_reg(io_base: dev->base, JH7110_PWMDAC_CTRL); |
177 | if (dev->cfg.data_mode == UNSIGNED_DATA) |
178 | value &= ~JH7110_PWMDAC_DATA_MODE; |
179 | else if (dev->cfg.data_mode == INVERTER_DATA_MSB) |
180 | value |= JH7110_PWMDAC_DATA_MODE; |
181 | |
182 | jh7110_pwmdac_write_reg(io_base: dev->base, JH7110_PWMDAC_CTRL, val: value); |
183 | } |
184 | |
185 | static void jh7110_pwmdac_set_data_shift(struct jh7110_pwmdac_dev *dev) |
186 | { |
187 | u32 value; |
188 | |
189 | value = jh7110_pwmdac_read_reg(io_base: dev->base, JH7110_PWMDAC_CTRL); |
190 | value &= ~JH7110_PWMDAC_DATA_SHIFT_MASK; |
191 | value |= (dev->cfg.data_shift & 0x7) << JH7110_PWMDAC_DATA_SHIFT_SHIFT; |
192 | |
193 | jh7110_pwmdac_write_reg(io_base: dev->base, JH7110_PWMDAC_CTRL, val: value); |
194 | } |
195 | |
196 | static void jh7110_pwmdac_set(struct jh7110_pwmdac_dev *dev) |
197 | { |
198 | jh7110_pwmdac_set_shift(dev); |
199 | jh7110_pwmdac_set_duty_cycle(dev); |
200 | jh7110_pwmdac_set_cnt_n(dev); |
201 | jh7110_pwmdac_set_enable(dev, enable: true); |
202 | |
203 | jh7110_pwmdac_set_data_change(dev); |
204 | jh7110_pwmdac_set_data_mode(dev); |
205 | jh7110_pwmdac_set_data_shift(dev); |
206 | } |
207 | |
208 | static void jh7110_pwmdac_stop(struct jh7110_pwmdac_dev *dev) |
209 | { |
210 | jh7110_pwmdac_set_enable(dev, enable: false); |
211 | } |
212 | |
213 | static int jh7110_pwmdac_startup(struct snd_pcm_substream *substream, |
214 | struct snd_soc_dai *dai) |
215 | { |
216 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
217 | struct snd_soc_dai_link *dai_link = rtd->dai_link; |
218 | |
219 | dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC; |
220 | |
221 | return 0; |
222 | } |
223 | |
224 | static int jh7110_pwmdac_hw_params(struct snd_pcm_substream *substream, |
225 | struct snd_pcm_hw_params *params, |
226 | struct snd_soc_dai *dai) |
227 | { |
228 | struct jh7110_pwmdac_dev *dev = dev_get_drvdata(dev: dai->dev); |
229 | unsigned long core_clk_rate; |
230 | int ret; |
231 | |
232 | switch (params_rate(p: params)) { |
233 | case 8000: |
234 | dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_3; |
235 | core_clk_rate = 6144000; |
236 | break; |
237 | case 11025: |
238 | dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_2; |
239 | core_clk_rate = 5644800; |
240 | break; |
241 | case 16000: |
242 | dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_3; |
243 | core_clk_rate = 12288000; |
244 | break; |
245 | case 22050: |
246 | dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1; |
247 | core_clk_rate = 5644800; |
248 | break; |
249 | case 32000: |
250 | dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1; |
251 | core_clk_rate = 8192000; |
252 | break; |
253 | case 44100: |
254 | dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1; |
255 | core_clk_rate = 11289600; |
256 | break; |
257 | case 48000: |
258 | dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1; |
259 | core_clk_rate = 12288000; |
260 | break; |
261 | default: |
262 | dev_err(dai->dev, "%d rate not supported\n" , |
263 | params_rate(params)); |
264 | return -EINVAL; |
265 | } |
266 | |
267 | switch (params_channels(p: params)) { |
268 | case 1: |
269 | dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; |
270 | break; |
271 | case 2: |
272 | dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
273 | break; |
274 | default: |
275 | dev_err(dai->dev, "%d channels not supported\n" , |
276 | params_channels(params)); |
277 | return -EINVAL; |
278 | } |
279 | |
280 | /* |
281 | * The clock rate always rounds down when using clk_set_rate() |
282 | * so increase the rate a bit |
283 | */ |
284 | core_clk_rate += 64; |
285 | jh7110_pwmdac_set(dev); |
286 | |
287 | ret = clk_set_rate(clk: dev->clks[1].clk, rate: core_clk_rate); |
288 | if (ret) |
289 | return dev_err_probe(dev: dai->dev, err: ret, |
290 | fmt: "failed to set rate %lu for core clock\n" , |
291 | core_clk_rate); |
292 | |
293 | return 0; |
294 | } |
295 | |
296 | static int jh7110_pwmdac_trigger(struct snd_pcm_substream *substream, int cmd, |
297 | struct snd_soc_dai *dai) |
298 | { |
299 | struct jh7110_pwmdac_dev *dev = snd_soc_dai_get_drvdata(dai); |
300 | int ret = 0; |
301 | |
302 | switch (cmd) { |
303 | case SNDRV_PCM_TRIGGER_START: |
304 | case SNDRV_PCM_TRIGGER_RESUME: |
305 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
306 | jh7110_pwmdac_set(dev); |
307 | break; |
308 | |
309 | case SNDRV_PCM_TRIGGER_STOP: |
310 | case SNDRV_PCM_TRIGGER_SUSPEND: |
311 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
312 | jh7110_pwmdac_stop(dev); |
313 | break; |
314 | default: |
315 | ret = -EINVAL; |
316 | break; |
317 | } |
318 | |
319 | return ret; |
320 | } |
321 | |
322 | static int jh7110_pwmdac_crg_enable(struct jh7110_pwmdac_dev *dev, bool enable) |
323 | { |
324 | int ret; |
325 | |
326 | if (enable) { |
327 | ret = clk_bulk_prepare_enable(ARRAY_SIZE(dev->clks), clks: dev->clks); |
328 | if (ret) |
329 | return dev_err_probe(dev: dev->dev, err: ret, |
330 | fmt: "failed to enable pwmdac clocks\n" ); |
331 | |
332 | ret = reset_control_deassert(rstc: dev->rst_apb); |
333 | if (ret) { |
334 | dev_err(dev->dev, "failed to deassert pwmdac apb reset\n" ); |
335 | goto err_rst_apb; |
336 | } |
337 | } else { |
338 | clk_bulk_disable_unprepare(ARRAY_SIZE(dev->clks), clks: dev->clks); |
339 | } |
340 | |
341 | return 0; |
342 | |
343 | err_rst_apb: |
344 | clk_bulk_disable_unprepare(ARRAY_SIZE(dev->clks), clks: dev->clks); |
345 | |
346 | return ret; |
347 | } |
348 | |
349 | static int jh7110_pwmdac_dai_probe(struct snd_soc_dai *dai) |
350 | { |
351 | struct jh7110_pwmdac_dev *dev = dev_get_drvdata(dev: dai->dev); |
352 | |
353 | snd_soc_dai_init_dma_data(dai, playback: &dev->play_dma_data, NULL); |
354 | snd_soc_dai_set_drvdata(dai, data: dev); |
355 | |
356 | return 0; |
357 | } |
358 | |
359 | static const struct snd_soc_dai_ops jh7110_pwmdac_dai_ops = { |
360 | .probe = jh7110_pwmdac_dai_probe, |
361 | .startup = jh7110_pwmdac_startup, |
362 | .hw_params = jh7110_pwmdac_hw_params, |
363 | .trigger = jh7110_pwmdac_trigger, |
364 | }; |
365 | |
366 | static const struct snd_soc_component_driver jh7110_pwmdac_component = { |
367 | .name = "jh7110-pwmdac" , |
368 | }; |
369 | |
370 | static struct snd_soc_dai_driver jh7110_pwmdac_dai = { |
371 | .name = "jh7110-pwmdac" , |
372 | .id = 0, |
373 | .playback = { |
374 | .channels_min = 1, |
375 | .channels_max = 2, |
376 | .rates = SNDRV_PCM_RATE_8000_48000, |
377 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
378 | }, |
379 | .ops = &jh7110_pwmdac_dai_ops, |
380 | }; |
381 | |
382 | static int jh7110_pwmdac_runtime_suspend(struct device *dev) |
383 | { |
384 | struct jh7110_pwmdac_dev *pwmdac = dev_get_drvdata(dev); |
385 | |
386 | return jh7110_pwmdac_crg_enable(dev: pwmdac, enable: false); |
387 | } |
388 | |
389 | static int jh7110_pwmdac_runtime_resume(struct device *dev) |
390 | { |
391 | struct jh7110_pwmdac_dev *pwmdac = dev_get_drvdata(dev); |
392 | |
393 | return jh7110_pwmdac_crg_enable(dev: pwmdac, enable: true); |
394 | } |
395 | |
396 | static int jh7110_pwmdac_system_suspend(struct device *dev) |
397 | { |
398 | struct jh7110_pwmdac_dev *pwmdac = dev_get_drvdata(dev); |
399 | |
400 | /* save the CTRL register value */ |
401 | pwmdac->saved_ctrl = jh7110_pwmdac_read_reg(io_base: pwmdac->base, |
402 | JH7110_PWMDAC_CTRL); |
403 | return pm_runtime_force_suspend(dev); |
404 | } |
405 | |
406 | static int jh7110_pwmdac_system_resume(struct device *dev) |
407 | { |
408 | struct jh7110_pwmdac_dev *pwmdac = dev_get_drvdata(dev); |
409 | int ret; |
410 | |
411 | ret = pm_runtime_force_resume(dev); |
412 | if (ret) |
413 | return ret; |
414 | |
415 | /* restore the CTRL register value */ |
416 | jh7110_pwmdac_write_reg(io_base: pwmdac->base, JH7110_PWMDAC_CTRL, |
417 | val: pwmdac->saved_ctrl); |
418 | return 0; |
419 | } |
420 | |
421 | static const struct dev_pm_ops jh7110_pwmdac_pm_ops = { |
422 | RUNTIME_PM_OPS(jh7110_pwmdac_runtime_suspend, |
423 | jh7110_pwmdac_runtime_resume, NULL) |
424 | SYSTEM_SLEEP_PM_OPS(jh7110_pwmdac_system_suspend, |
425 | jh7110_pwmdac_system_resume) |
426 | }; |
427 | |
428 | static void jh7110_pwmdac_init_params(struct jh7110_pwmdac_dev *dev) |
429 | { |
430 | dev->cfg.shift = PWMDAC_SHIFT_8; |
431 | dev->cfg.duty_cycle = PWMDAC_CYCLE_CENTER; |
432 | dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1; |
433 | dev->cfg.data_change = NO_CHANGE; |
434 | dev->cfg.data_mode = INVERTER_DATA_MSB; |
435 | dev->cfg.data_shift = PWMDAC_DATA_LEFT_SHIFT_BIT_0; |
436 | |
437 | dev->play_dma_data.addr = dev->mapbase + JH7110_PWMDAC_WDATA; |
438 | dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
439 | dev->play_dma_data.fifo_size = 1; |
440 | dev->play_dma_data.maxburst = 16; |
441 | } |
442 | |
443 | static int jh7110_pwmdac_probe(struct platform_device *pdev) |
444 | { |
445 | struct jh7110_pwmdac_dev *dev; |
446 | struct resource *res; |
447 | int ret; |
448 | |
449 | dev = devm_kzalloc(dev: &pdev->dev, size: sizeof(*dev), GFP_KERNEL); |
450 | if (!dev) |
451 | return -ENOMEM; |
452 | |
453 | dev->base = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
454 | if (IS_ERR(ptr: dev->base)) |
455 | return PTR_ERR(ptr: dev->base); |
456 | |
457 | dev->mapbase = res->start; |
458 | |
459 | dev->clks[0].id = "apb" ; |
460 | dev->clks[1].id = "core" ; |
461 | |
462 | ret = devm_clk_bulk_get(dev: &pdev->dev, ARRAY_SIZE(dev->clks), clks: dev->clks); |
463 | if (ret) |
464 | return dev_err_probe(dev: &pdev->dev, err: ret, |
465 | fmt: "failed to get pwmdac clocks\n" ); |
466 | |
467 | dev->rst_apb = devm_reset_control_get_exclusive(dev: &pdev->dev, NULL); |
468 | if (IS_ERR(ptr: dev->rst_apb)) |
469 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: dev->rst_apb), |
470 | fmt: "failed to get pwmdac apb reset\n" ); |
471 | |
472 | jh7110_pwmdac_init_params(dev); |
473 | |
474 | dev->dev = &pdev->dev; |
475 | dev_set_drvdata(dev: &pdev->dev, data: dev); |
476 | ret = devm_snd_soc_register_component(dev: &pdev->dev, |
477 | component_driver: &jh7110_pwmdac_component, |
478 | dai_drv: &jh7110_pwmdac_dai, num_dai: 1); |
479 | if (ret) |
480 | return dev_err_probe(dev: &pdev->dev, err: ret, fmt: "failed to register dai\n" ); |
481 | |
482 | ret = devm_snd_dmaengine_pcm_register(dev: &pdev->dev, NULL, flags: 0); |
483 | if (ret) |
484 | return dev_err_probe(dev: &pdev->dev, err: ret, fmt: "failed to register pcm\n" ); |
485 | |
486 | pm_runtime_enable(dev: dev->dev); |
487 | if (!pm_runtime_enabled(dev: &pdev->dev)) { |
488 | ret = jh7110_pwmdac_runtime_resume(dev: &pdev->dev); |
489 | if (ret) |
490 | goto err_pm_disable; |
491 | } |
492 | |
493 | return 0; |
494 | |
495 | err_pm_disable: |
496 | pm_runtime_disable(dev: &pdev->dev); |
497 | |
498 | return ret; |
499 | } |
500 | |
501 | static void jh7110_pwmdac_remove(struct platform_device *pdev) |
502 | { |
503 | pm_runtime_disable(dev: &pdev->dev); |
504 | } |
505 | |
506 | static const struct of_device_id jh7110_pwmdac_of_match[] = { |
507 | { .compatible = "starfive,jh7110-pwmdac" }, |
508 | { /* sentinel */ } |
509 | }; |
510 | MODULE_DEVICE_TABLE(of, jh7110_pwmdac_of_match); |
511 | |
512 | static struct platform_driver jh7110_pwmdac_driver = { |
513 | .driver = { |
514 | .name = "jh7110-pwmdac" , |
515 | .of_match_table = jh7110_pwmdac_of_match, |
516 | .pm = pm_ptr(&jh7110_pwmdac_pm_ops), |
517 | }, |
518 | .probe = jh7110_pwmdac_probe, |
519 | .remove_new = jh7110_pwmdac_remove, |
520 | }; |
521 | module_platform_driver(jh7110_pwmdac_driver); |
522 | |
523 | MODULE_AUTHOR("Jenny Zhang" ); |
524 | MODULE_AUTHOR("Curry Zhang" ); |
525 | MODULE_AUTHOR("Xingyu Wu <xingyu.wu@starfivetech.com>" ); |
526 | MODULE_AUTHOR("Hal Feng <hal.feng@starfivetech.com>" ); |
527 | MODULE_DESCRIPTION("StarFive JH7110 PWM-DAC driver" ); |
528 | MODULE_LICENSE("GPL" ); |
529 | |