1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * jh7110_tdm.c -- StarFive JH7110 TDM driver |
4 | * |
5 | * Copyright (C) 2023 StarFive Technology Co., Ltd. |
6 | * |
7 | * Author: Walker Chen <walker.chen@starfivetech.com> |
8 | */ |
9 | |
10 | #include <linux/clk.h> |
11 | #include <linux/device.h> |
12 | #include <linux/dmaengine.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of_irq.h> |
15 | #include <linux/of_platform.h> |
16 | #include <linux/pm_runtime.h> |
17 | #include <linux/regmap.h> |
18 | #include <linux/reset.h> |
19 | #include <linux/types.h> |
20 | #include <sound/dmaengine_pcm.h> |
21 | #include <sound/initval.h> |
22 | #include <sound/pcm.h> |
23 | #include <sound/pcm_params.h> |
24 | #include <sound/soc.h> |
25 | #include <sound/soc-dai.h> |
26 | |
27 | #define TDM_PCMGBCR 0x00 |
28 | #define PCMGBCR_ENABLE BIT(0) |
29 | #define CLKPOL_BIT 5 |
30 | #define ELM_BIT 3 |
31 | #define SYNCM_BIT 2 |
32 | #define MS_BIT 1 |
33 | #define TDM_PCMTXCR 0x04 |
34 | #define PCMTXCR_TXEN BIT(0) |
35 | #define IFL_BIT 11 |
36 | #define WL_BIT 8 |
37 | #define SSCALE_BIT 4 |
38 | #define SL_BIT 2 |
39 | #define LRJ_BIT 1 |
40 | #define TDM_PCMRXCR 0x08 |
41 | #define PCMRXCR_RXEN BIT(0) |
42 | #define TDM_PCMDIV 0x0c |
43 | |
44 | #define JH7110_TDM_FIFO 0x170c0000 |
45 | #define JH7110_TDM_FIFO_DEPTH 32 |
46 | |
47 | enum TDM_MASTER_SLAVE_MODE { |
48 | TDM_AS_MASTER = 0, |
49 | TDM_AS_SLAVE, |
50 | }; |
51 | |
52 | enum TDM_CLKPOL { |
53 | /* tx raising and rx falling */ |
54 | TDM_TX_RASING_RX_FALLING = 0, |
55 | /* tx falling and rx raising */ |
56 | TDM_TX_FALLING_RX_RASING, |
57 | }; |
58 | |
59 | enum TDM_ELM { |
60 | /* only work while SYNCM=0 */ |
61 | TDM_ELM_LATE = 0, |
62 | TDM_ELM_EARLY, |
63 | }; |
64 | |
65 | enum TDM_SYNCM { |
66 | /* short frame sync */ |
67 | TDM_SYNCM_SHORT = 0, |
68 | /* long frame sync */ |
69 | TDM_SYNCM_LONG, |
70 | }; |
71 | |
72 | enum TDM_IFL { |
73 | /* FIFO to send or received : half-1/2, Quarter-1/4 */ |
74 | TDM_FIFO_HALF = 0, |
75 | TDM_FIFO_QUARTER, |
76 | }; |
77 | |
78 | enum TDM_WL { |
79 | /* send or received word length */ |
80 | TDM_8BIT_WORD_LEN = 0, |
81 | TDM_16BIT_WORD_LEN, |
82 | TDM_20BIT_WORD_LEN, |
83 | TDM_24BIT_WORD_LEN, |
84 | TDM_32BIT_WORD_LEN, |
85 | }; |
86 | |
87 | enum TDM_SL { |
88 | /* send or received slot length */ |
89 | TDM_8BIT_SLOT_LEN = 0, |
90 | TDM_16BIT_SLOT_LEN, |
91 | TDM_32BIT_SLOT_LEN, |
92 | }; |
93 | |
94 | enum TDM_LRJ { |
95 | /* left-justify or right-justify */ |
96 | TDM_RIGHT_JUSTIFY = 0, |
97 | TDM_LEFT_JUSTIFT, |
98 | }; |
99 | |
100 | struct tdm_chan_cfg { |
101 | enum TDM_IFL ifl; |
102 | enum TDM_WL wl; |
103 | unsigned char sscale; |
104 | enum TDM_SL sl; |
105 | enum TDM_LRJ lrj; |
106 | unsigned char enable; |
107 | }; |
108 | |
109 | struct jh7110_tdm_dev { |
110 | void __iomem *tdm_base; |
111 | struct device *dev; |
112 | struct clk_bulk_data clks[6]; |
113 | struct reset_control *resets; |
114 | |
115 | enum TDM_CLKPOL clkpolity; |
116 | enum TDM_ELM elm; |
117 | enum TDM_SYNCM syncm; |
118 | enum TDM_MASTER_SLAVE_MODE ms_mode; |
119 | |
120 | struct tdm_chan_cfg tx; |
121 | struct tdm_chan_cfg rx; |
122 | |
123 | u16 syncdiv; |
124 | u32 samplerate; |
125 | u32 pcmclk; |
126 | |
127 | /* data related to DMA transfers b/w tdm and DMAC */ |
128 | struct snd_dmaengine_dai_dma_data play_dma_data; |
129 | struct snd_dmaengine_dai_dma_data capture_dma_data; |
130 | u32 saved_pcmgbcr; |
131 | u32 saved_pcmtxcr; |
132 | u32 saved_pcmrxcr; |
133 | u32 saved_pcmdiv; |
134 | }; |
135 | |
136 | static inline u32 jh7110_tdm_readl(struct jh7110_tdm_dev *tdm, u16 reg) |
137 | { |
138 | return readl_relaxed(tdm->tdm_base + reg); |
139 | } |
140 | |
141 | static inline void jh7110_tdm_writel(struct jh7110_tdm_dev *tdm, u16 reg, u32 val) |
142 | { |
143 | writel_relaxed(val, tdm->tdm_base + reg); |
144 | } |
145 | |
146 | static void jh7110_tdm_save_context(struct jh7110_tdm_dev *tdm, |
147 | struct snd_pcm_substream *substream) |
148 | { |
149 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
150 | tdm->saved_pcmtxcr = jh7110_tdm_readl(tdm, TDM_PCMTXCR); |
151 | else |
152 | tdm->saved_pcmrxcr = jh7110_tdm_readl(tdm, TDM_PCMRXCR); |
153 | } |
154 | |
155 | static void jh7110_tdm_start(struct jh7110_tdm_dev *tdm, |
156 | struct snd_pcm_substream *substream) |
157 | { |
158 | u32 data; |
159 | |
160 | data = jh7110_tdm_readl(tdm, TDM_PCMGBCR); |
161 | jh7110_tdm_writel(tdm, TDM_PCMGBCR, val: data | PCMGBCR_ENABLE); |
162 | |
163 | /* restore context */ |
164 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
165 | jh7110_tdm_writel(tdm, TDM_PCMTXCR, val: tdm->saved_pcmtxcr | PCMTXCR_TXEN); |
166 | else |
167 | jh7110_tdm_writel(tdm, TDM_PCMRXCR, val: tdm->saved_pcmrxcr | PCMRXCR_RXEN); |
168 | } |
169 | |
170 | static void jh7110_tdm_stop(struct jh7110_tdm_dev *tdm, |
171 | struct snd_pcm_substream *substream) |
172 | { |
173 | unsigned int val; |
174 | |
175 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
176 | val = jh7110_tdm_readl(tdm, TDM_PCMTXCR); |
177 | val &= ~PCMTXCR_TXEN; |
178 | jh7110_tdm_writel(tdm, TDM_PCMTXCR, val); |
179 | } else { |
180 | val = jh7110_tdm_readl(tdm, TDM_PCMRXCR); |
181 | val &= ~PCMRXCR_RXEN; |
182 | jh7110_tdm_writel(tdm, TDM_PCMRXCR, val); |
183 | } |
184 | } |
185 | |
186 | static int jh7110_tdm_syncdiv(struct jh7110_tdm_dev *tdm) |
187 | { |
188 | u32 sl, sscale, syncdiv; |
189 | |
190 | if (tdm->rx.sl >= tdm->tx.sl) |
191 | sl = tdm->rx.sl; |
192 | else |
193 | sl = tdm->tx.sl; |
194 | |
195 | if (tdm->rx.sscale >= tdm->tx.sscale) |
196 | sscale = tdm->rx.sscale; |
197 | else |
198 | sscale = tdm->tx.sscale; |
199 | |
200 | syncdiv = tdm->pcmclk / tdm->samplerate - 1; |
201 | |
202 | if ((syncdiv + 1) < (sl * sscale)) { |
203 | dev_err(tdm->dev, "Failed to set syncdiv!\n" ); |
204 | return -EINVAL; |
205 | } |
206 | |
207 | if (tdm->syncm == TDM_SYNCM_LONG && |
208 | (tdm->rx.sscale <= 1 || tdm->tx.sscale <= 1) && |
209 | ((syncdiv + 1) <= sl)) { |
210 | dev_err(tdm->dev, "Wrong syncdiv! It must be (syncdiv+1) > max[tx.sl, rx.sl]\n" ); |
211 | return -EINVAL; |
212 | } |
213 | |
214 | jh7110_tdm_writel(tdm, TDM_PCMDIV, val: syncdiv); |
215 | return 0; |
216 | } |
217 | |
218 | static int jh7110_tdm_config(struct jh7110_tdm_dev *tdm, |
219 | struct snd_pcm_substream *substream) |
220 | { |
221 | u32 datarx, datatx; |
222 | int ret; |
223 | |
224 | ret = jh7110_tdm_syncdiv(tdm); |
225 | if (ret) |
226 | return ret; |
227 | |
228 | datarx = (tdm->rx.ifl << IFL_BIT) | |
229 | (tdm->rx.wl << WL_BIT) | |
230 | (tdm->rx.sscale << SSCALE_BIT) | |
231 | (tdm->rx.sl << SL_BIT) | |
232 | (tdm->rx.lrj << LRJ_BIT); |
233 | |
234 | datatx = (tdm->tx.ifl << IFL_BIT) | |
235 | (tdm->tx.wl << WL_BIT) | |
236 | (tdm->tx.sscale << SSCALE_BIT) | |
237 | (tdm->tx.sl << SL_BIT) | |
238 | (tdm->tx.lrj << LRJ_BIT); |
239 | |
240 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
241 | jh7110_tdm_writel(tdm, TDM_PCMTXCR, val: datatx); |
242 | else |
243 | jh7110_tdm_writel(tdm, TDM_PCMRXCR, val: datarx); |
244 | |
245 | return 0; |
246 | } |
247 | |
248 | static void jh7110_tdm_clk_disable(struct jh7110_tdm_dev *tdm) |
249 | { |
250 | clk_bulk_disable_unprepare(ARRAY_SIZE(tdm->clks), clks: tdm->clks); |
251 | } |
252 | |
253 | static int jh7110_tdm_clk_enable(struct jh7110_tdm_dev *tdm) |
254 | { |
255 | int ret; |
256 | |
257 | ret = clk_bulk_prepare_enable(ARRAY_SIZE(tdm->clks), clks: tdm->clks); |
258 | if (ret) { |
259 | dev_err(tdm->dev, "Failed to enable tdm clocks\n" ); |
260 | return ret; |
261 | } |
262 | |
263 | ret = reset_control_deassert(rstc: tdm->resets); |
264 | if (ret) { |
265 | dev_err(tdm->dev, "Failed to deassert tdm resets\n" ); |
266 | goto dis_tdm_clk; |
267 | } |
268 | |
269 | /* select tdm_ext clock as the clock source for tdm */ |
270 | ret = clk_set_parent(clk: tdm->clks[5].clk, parent: tdm->clks[4].clk); |
271 | if (ret) { |
272 | dev_err(tdm->dev, "Can't set extern clock source for clk_tdm\n" ); |
273 | goto dis_tdm_clk; |
274 | } |
275 | |
276 | return 0; |
277 | |
278 | dis_tdm_clk: |
279 | clk_bulk_disable_unprepare(ARRAY_SIZE(tdm->clks), clks: tdm->clks); |
280 | |
281 | return ret; |
282 | } |
283 | |
284 | static int jh7110_tdm_runtime_suspend(struct device *dev) |
285 | { |
286 | struct jh7110_tdm_dev *tdm = dev_get_drvdata(dev); |
287 | |
288 | jh7110_tdm_clk_disable(tdm); |
289 | return 0; |
290 | } |
291 | |
292 | static int jh7110_tdm_runtime_resume(struct device *dev) |
293 | { |
294 | struct jh7110_tdm_dev *tdm = dev_get_drvdata(dev); |
295 | |
296 | return jh7110_tdm_clk_enable(tdm); |
297 | } |
298 | |
299 | static int jh7110_tdm_system_suspend(struct device *dev) |
300 | { |
301 | struct jh7110_tdm_dev *tdm = dev_get_drvdata(dev); |
302 | |
303 | /* save context */ |
304 | tdm->saved_pcmgbcr = jh7110_tdm_readl(tdm, TDM_PCMGBCR); |
305 | tdm->saved_pcmdiv = jh7110_tdm_readl(tdm, TDM_PCMDIV); |
306 | |
307 | return pm_runtime_force_suspend(dev); |
308 | } |
309 | |
310 | static int jh7110_tdm_system_resume(struct device *dev) |
311 | { |
312 | struct jh7110_tdm_dev *tdm = dev_get_drvdata(dev); |
313 | |
314 | /* restore context */ |
315 | jh7110_tdm_writel(tdm, TDM_PCMGBCR, val: tdm->saved_pcmgbcr); |
316 | jh7110_tdm_writel(tdm, TDM_PCMDIV, val: tdm->saved_pcmdiv); |
317 | |
318 | return pm_runtime_force_resume(dev); |
319 | } |
320 | |
321 | static const struct snd_soc_component_driver jh7110_tdm_component = { |
322 | .name = "jh7110-tdm" , |
323 | }; |
324 | |
325 | static int jh7110_tdm_startup(struct snd_pcm_substream *substream, |
326 | struct snd_soc_dai *cpu_dai) |
327 | { |
328 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
329 | struct snd_soc_dai_link *dai_link = rtd->dai_link; |
330 | |
331 | dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC; |
332 | |
333 | return 0; |
334 | } |
335 | |
336 | static int jh7110_tdm_hw_params(struct snd_pcm_substream *substream, |
337 | struct snd_pcm_hw_params *params, |
338 | struct snd_soc_dai *dai) |
339 | { |
340 | struct jh7110_tdm_dev *tdm = snd_soc_dai_get_drvdata(dai); |
341 | int chan_wl, chan_sl, chan_nr; |
342 | unsigned int data_width; |
343 | unsigned int dma_bus_width; |
344 | struct snd_dmaengine_dai_dma_data *dma_data = NULL; |
345 | int ret; |
346 | |
347 | data_width = params_width(p: params); |
348 | |
349 | tdm->samplerate = params_rate(p: params); |
350 | tdm->pcmclk = params_channels(p: params) * tdm->samplerate * data_width; |
351 | |
352 | switch (params_format(p: params)) { |
353 | case SNDRV_PCM_FORMAT_S16_LE: |
354 | chan_wl = TDM_16BIT_WORD_LEN; |
355 | chan_sl = TDM_16BIT_SLOT_LEN; |
356 | dma_bus_width = DMA_SLAVE_BUSWIDTH_2_BYTES; |
357 | break; |
358 | |
359 | case SNDRV_PCM_FORMAT_S32_LE: |
360 | chan_wl = TDM_32BIT_WORD_LEN; |
361 | chan_sl = TDM_32BIT_SLOT_LEN; |
362 | dma_bus_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
363 | break; |
364 | |
365 | default: |
366 | dev_err(tdm->dev, "tdm: unsupported PCM fmt" ); |
367 | return -EINVAL; |
368 | } |
369 | |
370 | chan_nr = params_channels(p: params); |
371 | switch (chan_nr) { |
372 | case 1: |
373 | case 2: |
374 | case 4: |
375 | case 6: |
376 | case 8: |
377 | break; |
378 | default: |
379 | dev_err(tdm->dev, "channel not supported\n" ); |
380 | return -EINVAL; |
381 | } |
382 | |
383 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
384 | tdm->tx.wl = chan_wl; |
385 | tdm->tx.sl = chan_sl; |
386 | tdm->tx.sscale = chan_nr; |
387 | tdm->play_dma_data.addr_width = dma_bus_width; |
388 | dma_data = &tdm->play_dma_data; |
389 | } else { |
390 | tdm->rx.wl = chan_wl; |
391 | tdm->rx.sl = chan_sl; |
392 | tdm->rx.sscale = chan_nr; |
393 | tdm->capture_dma_data.addr_width = dma_bus_width; |
394 | dma_data = &tdm->capture_dma_data; |
395 | } |
396 | |
397 | snd_soc_dai_set_dma_data(dai, substream, dma_data); |
398 | |
399 | ret = jh7110_tdm_config(tdm, substream); |
400 | if (ret) |
401 | return ret; |
402 | |
403 | jh7110_tdm_save_context(tdm, substream); |
404 | return 0; |
405 | } |
406 | |
407 | static int jh7110_tdm_trigger(struct snd_pcm_substream *substream, |
408 | int cmd, struct snd_soc_dai *dai) |
409 | { |
410 | struct jh7110_tdm_dev *tdm = snd_soc_dai_get_drvdata(dai); |
411 | int ret = 0; |
412 | |
413 | switch (cmd) { |
414 | case SNDRV_PCM_TRIGGER_START: |
415 | case SNDRV_PCM_TRIGGER_RESUME: |
416 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
417 | jh7110_tdm_start(tdm, substream); |
418 | break; |
419 | |
420 | case SNDRV_PCM_TRIGGER_STOP: |
421 | case SNDRV_PCM_TRIGGER_SUSPEND: |
422 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
423 | jh7110_tdm_stop(tdm, substream); |
424 | break; |
425 | default: |
426 | ret = -EINVAL; |
427 | break; |
428 | } |
429 | |
430 | return ret; |
431 | } |
432 | |
433 | static int jh7110_tdm_set_dai_fmt(struct snd_soc_dai *cpu_dai, |
434 | unsigned int fmt) |
435 | { |
436 | struct jh7110_tdm_dev *tdm = snd_soc_dai_get_drvdata(dai: cpu_dai); |
437 | unsigned int gbcr; |
438 | |
439 | /* set master/slave audio interface */ |
440 | switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { |
441 | case SND_SOC_DAIFMT_BP_FP: |
442 | /* cpu is master */ |
443 | tdm->ms_mode = TDM_AS_MASTER; |
444 | break; |
445 | case SND_SOC_DAIFMT_BC_FC: |
446 | /* codec is master */ |
447 | tdm->ms_mode = TDM_AS_SLAVE; |
448 | break; |
449 | case SND_SOC_DAIFMT_BC_FP: |
450 | case SND_SOC_DAIFMT_BP_FC: |
451 | return -EINVAL; |
452 | default: |
453 | dev_dbg(tdm->dev, "dwc : Invalid clock provider format\n" ); |
454 | return -EINVAL; |
455 | } |
456 | |
457 | gbcr = (tdm->clkpolity << CLKPOL_BIT) | |
458 | (tdm->elm << ELM_BIT) | |
459 | (tdm->syncm << SYNCM_BIT) | |
460 | (tdm->ms_mode << MS_BIT); |
461 | jh7110_tdm_writel(tdm, TDM_PCMGBCR, val: gbcr); |
462 | |
463 | return 0; |
464 | } |
465 | |
466 | static int jh7110_tdm_dai_probe(struct snd_soc_dai *dai) |
467 | { |
468 | struct jh7110_tdm_dev *tdm = snd_soc_dai_get_drvdata(dai); |
469 | |
470 | snd_soc_dai_init_dma_data(dai, playback: &tdm->play_dma_data, capture: &tdm->capture_dma_data); |
471 | snd_soc_dai_set_drvdata(dai, data: tdm); |
472 | return 0; |
473 | } |
474 | |
475 | static const struct snd_soc_dai_ops jh7110_tdm_dai_ops = { |
476 | .probe = jh7110_tdm_dai_probe, |
477 | .startup = jh7110_tdm_startup, |
478 | .hw_params = jh7110_tdm_hw_params, |
479 | .trigger = jh7110_tdm_trigger, |
480 | .set_fmt = jh7110_tdm_set_dai_fmt, |
481 | }; |
482 | |
483 | #define JH7110_TDM_RATES SNDRV_PCM_RATE_8000_48000 |
484 | |
485 | #define JH7110_TDM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ |
486 | SNDRV_PCM_FMTBIT_S32_LE) |
487 | |
488 | static struct snd_soc_dai_driver jh7110_tdm_dai = { |
489 | .name = "sf_tdm" , |
490 | .id = 0, |
491 | .playback = { |
492 | .stream_name = "Playback" , |
493 | .channels_min = 1, |
494 | .channels_max = 8, |
495 | .rates = JH7110_TDM_RATES, |
496 | .formats = JH7110_TDM_FORMATS, |
497 | }, |
498 | .capture = { |
499 | .stream_name = "Capture" , |
500 | .channels_min = 1, |
501 | .channels_max = 8, |
502 | .rates = JH7110_TDM_RATES, |
503 | .formats = JH7110_TDM_FORMATS, |
504 | }, |
505 | .ops = &jh7110_tdm_dai_ops, |
506 | .symmetric_rate = 1, |
507 | }; |
508 | |
509 | static const struct snd_pcm_hardware jh7110_pcm_hardware = { |
510 | .info = (SNDRV_PCM_INFO_MMAP | |
511 | SNDRV_PCM_INFO_MMAP_VALID | |
512 | SNDRV_PCM_INFO_PAUSE | |
513 | SNDRV_PCM_INFO_RESUME | |
514 | SNDRV_PCM_INFO_INTERLEAVED | |
515 | SNDRV_PCM_INFO_BLOCK_TRANSFER), |
516 | .buffer_bytes_max = 192512, |
517 | .period_bytes_min = 4096, |
518 | .period_bytes_max = 32768, |
519 | .periods_min = 1, |
520 | .periods_max = 48, |
521 | .fifo_size = 16, |
522 | }; |
523 | |
524 | static const struct snd_dmaengine_pcm_config jh7110_dmaengine_pcm_config = { |
525 | .pcm_hardware = &jh7110_pcm_hardware, |
526 | .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, |
527 | .prealloc_buffer_size = 192512, |
528 | }; |
529 | |
530 | static void jh7110_tdm_init_params(struct jh7110_tdm_dev *tdm) |
531 | { |
532 | tdm->clkpolity = TDM_TX_RASING_RX_FALLING; |
533 | tdm->elm = TDM_ELM_LATE; |
534 | tdm->syncm = TDM_SYNCM_SHORT; |
535 | |
536 | tdm->rx.ifl = TDM_FIFO_HALF; |
537 | tdm->tx.ifl = TDM_FIFO_HALF; |
538 | tdm->rx.wl = TDM_16BIT_WORD_LEN; |
539 | tdm->tx.wl = TDM_16BIT_WORD_LEN; |
540 | tdm->rx.sscale = 2; |
541 | tdm->tx.sscale = 2; |
542 | tdm->rx.lrj = TDM_LEFT_JUSTIFT; |
543 | tdm->tx.lrj = TDM_LEFT_JUSTIFT; |
544 | |
545 | tdm->play_dma_data.addr = JH7110_TDM_FIFO; |
546 | tdm->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; |
547 | tdm->play_dma_data.fifo_size = JH7110_TDM_FIFO_DEPTH / 2; |
548 | tdm->play_dma_data.maxburst = 16; |
549 | |
550 | tdm->capture_dma_data.addr = JH7110_TDM_FIFO; |
551 | tdm->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; |
552 | tdm->capture_dma_data.fifo_size = JH7110_TDM_FIFO_DEPTH / 2; |
553 | tdm->capture_dma_data.maxburst = 8; |
554 | } |
555 | |
556 | static int jh7110_tdm_clk_reset_get(struct platform_device *pdev, |
557 | struct jh7110_tdm_dev *tdm) |
558 | { |
559 | int ret; |
560 | |
561 | tdm->clks[0].id = "mclk_inner" ; |
562 | tdm->clks[1].id = "tdm_ahb" ; |
563 | tdm->clks[2].id = "tdm_apb" ; |
564 | tdm->clks[3].id = "tdm_internal" ; |
565 | tdm->clks[4].id = "tdm_ext" ; |
566 | tdm->clks[5].id = "tdm" ; |
567 | |
568 | ret = devm_clk_bulk_get(dev: &pdev->dev, ARRAY_SIZE(tdm->clks), clks: tdm->clks); |
569 | if (ret) { |
570 | dev_err(&pdev->dev, "Failed to get tdm clocks\n" ); |
571 | return ret; |
572 | } |
573 | |
574 | tdm->resets = devm_reset_control_array_get_exclusive(dev: &pdev->dev); |
575 | if (IS_ERR(ptr: tdm->resets)) { |
576 | dev_err(&pdev->dev, "Failed to get tdm resets\n" ); |
577 | return PTR_ERR(ptr: tdm->resets); |
578 | } |
579 | |
580 | return 0; |
581 | } |
582 | |
583 | static int jh7110_tdm_probe(struct platform_device *pdev) |
584 | { |
585 | struct jh7110_tdm_dev *tdm; |
586 | int ret; |
587 | |
588 | tdm = devm_kzalloc(dev: &pdev->dev, size: sizeof(*tdm), GFP_KERNEL); |
589 | if (!tdm) |
590 | return -ENOMEM; |
591 | |
592 | tdm->tdm_base = devm_platform_ioremap_resource(pdev, index: 0); |
593 | if (IS_ERR(ptr: tdm->tdm_base)) |
594 | return PTR_ERR(ptr: tdm->tdm_base); |
595 | |
596 | tdm->dev = &pdev->dev; |
597 | |
598 | ret = jh7110_tdm_clk_reset_get(pdev, tdm); |
599 | if (ret) { |
600 | dev_err(&pdev->dev, "Failed to enable audio-tdm clock\n" ); |
601 | return ret; |
602 | } |
603 | |
604 | jh7110_tdm_init_params(tdm); |
605 | |
606 | dev_set_drvdata(dev: &pdev->dev, data: tdm); |
607 | ret = devm_snd_soc_register_component(dev: &pdev->dev, component_driver: &jh7110_tdm_component, |
608 | dai_drv: &jh7110_tdm_dai, num_dai: 1); |
609 | if (ret) { |
610 | dev_err(&pdev->dev, "Failed to register dai\n" ); |
611 | return ret; |
612 | } |
613 | |
614 | ret = devm_snd_dmaengine_pcm_register(dev: &pdev->dev, |
615 | config: &jh7110_dmaengine_pcm_config, |
616 | SND_DMAENGINE_PCM_FLAG_COMPAT); |
617 | if (ret) { |
618 | dev_err(&pdev->dev, "Could not register pcm: %d\n" , ret); |
619 | return ret; |
620 | } |
621 | |
622 | pm_runtime_enable(dev: &pdev->dev); |
623 | if (!pm_runtime_enabled(dev: &pdev->dev)) { |
624 | ret = jh7110_tdm_runtime_resume(dev: &pdev->dev); |
625 | if (ret) |
626 | goto err_pm_disable; |
627 | } |
628 | |
629 | return 0; |
630 | |
631 | err_pm_disable: |
632 | pm_runtime_disable(dev: &pdev->dev); |
633 | |
634 | return ret; |
635 | } |
636 | |
637 | static void jh7110_tdm_dev_remove(struct platform_device *pdev) |
638 | { |
639 | pm_runtime_disable(dev: &pdev->dev); |
640 | } |
641 | |
642 | static const struct of_device_id jh7110_tdm_of_match[] = { |
643 | { .compatible = "starfive,jh7110-tdm" , }, |
644 | {} |
645 | }; |
646 | |
647 | MODULE_DEVICE_TABLE(of, jh7110_tdm_of_match); |
648 | |
649 | static const struct dev_pm_ops jh7110_tdm_pm_ops = { |
650 | RUNTIME_PM_OPS(jh7110_tdm_runtime_suspend, |
651 | jh7110_tdm_runtime_resume, NULL) |
652 | SYSTEM_SLEEP_PM_OPS(jh7110_tdm_system_suspend, |
653 | jh7110_tdm_system_resume) |
654 | }; |
655 | |
656 | static struct platform_driver jh7110_tdm_driver = { |
657 | .driver = { |
658 | .name = "jh7110-tdm" , |
659 | .of_match_table = jh7110_tdm_of_match, |
660 | .pm = pm_ptr(&jh7110_tdm_pm_ops), |
661 | }, |
662 | .probe = jh7110_tdm_probe, |
663 | .remove_new = jh7110_tdm_dev_remove, |
664 | }; |
665 | module_platform_driver(jh7110_tdm_driver); |
666 | |
667 | MODULE_DESCRIPTION("StarFive JH7110 TDM ASoC Driver" ); |
668 | MODULE_AUTHOR("Walker Chen <walker.chen@starfivetech.com>" ); |
669 | MODULE_LICENSE("GPL" ); |
670 | |