1 // SPDX-License-Identifier: GPL-2.0
3 * jh7110_tdm.c -- StarFive JH7110 TDM driver
5 * Copyright (C) 2023 StarFive Technology Co., Ltd.
7 * Author: Walker Chen <walker.chen@starfivetech.com>
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>
27 #define TDM_PCMGBCR 0x00
28 #define PCMGBCR_ENABLE BIT(0)
33 #define TDM_PCMTXCR 0x04
34 #define PCMTXCR_TXEN BIT(0)
40 #define TDM_PCMRXCR 0x08
41 #define PCMRXCR_RXEN BIT(0)
42 #define TDM_PCMDIV 0x0c
44 #define JH7110_TDM_FIFO 0x170c0000
45 #define JH7110_TDM_FIFO_DEPTH 32
47 enum TDM_MASTER_SLAVE_MODE
{
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
,
60 /* only work while SYNCM=0 */
66 /* short frame sync */
73 /* FIFO to send or received : half-1/2, Quarter-1/4 */
79 /* send or received word length */
80 TDM_8BIT_WORD_LEN
= 0,
88 /* send or received slot length */
89 TDM_8BIT_SLOT_LEN
= 0,
95 /* left-justify or right-justify */
96 TDM_RIGHT_JUSTIFY
= 0,
100 struct tdm_chan_cfg
{
103 unsigned char sscale
;
106 unsigned char enable
;
109 struct jh7110_tdm_dev
{
110 void __iomem
*tdm_base
;
112 struct clk_bulk_data clks
[6];
113 struct reset_control
*resets
;
115 enum TDM_CLKPOL clkpolity
;
117 enum TDM_SYNCM syncm
;
118 enum TDM_MASTER_SLAVE_MODE ms_mode
;
120 struct tdm_chan_cfg tx
;
121 struct tdm_chan_cfg rx
;
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
;
136 static inline u32
jh7110_tdm_readl(struct jh7110_tdm_dev
*tdm
, u16 reg
)
138 return readl_relaxed(tdm
->tdm_base
+ reg
);
141 static inline void jh7110_tdm_writel(struct jh7110_tdm_dev
*tdm
, u16 reg
, u32 val
)
143 writel_relaxed(val
, tdm
->tdm_base
+ reg
);
146 static void jh7110_tdm_save_context(struct jh7110_tdm_dev
*tdm
,
147 struct snd_pcm_substream
*substream
)
149 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
)
150 tdm
->saved_pcmtxcr
= jh7110_tdm_readl(tdm
, TDM_PCMTXCR
);
152 tdm
->saved_pcmrxcr
= jh7110_tdm_readl(tdm
, TDM_PCMRXCR
);
155 static void jh7110_tdm_start(struct jh7110_tdm_dev
*tdm
,
156 struct snd_pcm_substream
*substream
)
160 data
= jh7110_tdm_readl(tdm
, TDM_PCMGBCR
);
161 jh7110_tdm_writel(tdm
, TDM_PCMGBCR
, data
| PCMGBCR_ENABLE
);
163 /* restore context */
164 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
)
165 jh7110_tdm_writel(tdm
, TDM_PCMTXCR
, tdm
->saved_pcmtxcr
| PCMTXCR_TXEN
);
167 jh7110_tdm_writel(tdm
, TDM_PCMRXCR
, tdm
->saved_pcmrxcr
| PCMRXCR_RXEN
);
170 static void jh7110_tdm_stop(struct jh7110_tdm_dev
*tdm
,
171 struct snd_pcm_substream
*substream
)
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
);
180 val
= jh7110_tdm_readl(tdm
, TDM_PCMRXCR
);
181 val
&= ~PCMRXCR_RXEN
;
182 jh7110_tdm_writel(tdm
, TDM_PCMRXCR
, val
);
186 static int jh7110_tdm_syncdiv(struct jh7110_tdm_dev
*tdm
)
188 u32 sl
, sscale
, syncdiv
;
190 if (tdm
->rx
.sl
>= tdm
->tx
.sl
)
195 if (tdm
->rx
.sscale
>= tdm
->tx
.sscale
)
196 sscale
= tdm
->rx
.sscale
;
198 sscale
= tdm
->tx
.sscale
;
200 syncdiv
= tdm
->pcmclk
/ tdm
->samplerate
- 1;
202 if ((syncdiv
+ 1) < (sl
* sscale
)) {
203 dev_err(tdm
->dev
, "Failed to set syncdiv!\n");
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");
214 jh7110_tdm_writel(tdm
, TDM_PCMDIV
, syncdiv
);
218 static int jh7110_tdm_config(struct jh7110_tdm_dev
*tdm
,
219 struct snd_pcm_substream
*substream
)
224 ret
= jh7110_tdm_syncdiv(tdm
);
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
);
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
);
240 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
)
241 jh7110_tdm_writel(tdm
, TDM_PCMTXCR
, datatx
);
243 jh7110_tdm_writel(tdm
, TDM_PCMRXCR
, datarx
);
248 static void jh7110_tdm_clk_disable(struct jh7110_tdm_dev
*tdm
)
250 clk_bulk_disable_unprepare(ARRAY_SIZE(tdm
->clks
), tdm
->clks
);
253 static int jh7110_tdm_clk_enable(struct jh7110_tdm_dev
*tdm
)
257 ret
= clk_bulk_prepare_enable(ARRAY_SIZE(tdm
->clks
), tdm
->clks
);
259 dev_err(tdm
->dev
, "Failed to enable tdm clocks\n");
263 ret
= reset_control_deassert(tdm
->resets
);
265 dev_err(tdm
->dev
, "Failed to deassert tdm resets\n");
269 /* select tdm_ext clock as the clock source for tdm */
270 ret
= clk_set_parent(tdm
->clks
[5].clk
, tdm
->clks
[4].clk
);
272 dev_err(tdm
->dev
, "Can't set extern clock source for clk_tdm\n");
279 clk_bulk_disable_unprepare(ARRAY_SIZE(tdm
->clks
), tdm
->clks
);
284 static int jh7110_tdm_runtime_suspend(struct device
*dev
)
286 struct jh7110_tdm_dev
*tdm
= dev_get_drvdata(dev
);
288 jh7110_tdm_clk_disable(tdm
);
292 static int jh7110_tdm_runtime_resume(struct device
*dev
)
294 struct jh7110_tdm_dev
*tdm
= dev_get_drvdata(dev
);
296 return jh7110_tdm_clk_enable(tdm
);
299 static int jh7110_tdm_system_suspend(struct device
*dev
)
301 struct jh7110_tdm_dev
*tdm
= dev_get_drvdata(dev
);
304 tdm
->saved_pcmgbcr
= jh7110_tdm_readl(tdm
, TDM_PCMGBCR
);
305 tdm
->saved_pcmdiv
= jh7110_tdm_readl(tdm
, TDM_PCMDIV
);
307 return pm_runtime_force_suspend(dev
);
310 static int jh7110_tdm_system_resume(struct device
*dev
)
312 struct jh7110_tdm_dev
*tdm
= dev_get_drvdata(dev
);
314 /* restore context */
315 jh7110_tdm_writel(tdm
, TDM_PCMGBCR
, tdm
->saved_pcmgbcr
);
316 jh7110_tdm_writel(tdm
, TDM_PCMDIV
, tdm
->saved_pcmdiv
);
318 return pm_runtime_force_resume(dev
);
321 static const struct snd_soc_component_driver jh7110_tdm_component
= {
322 .name
= "jh7110-tdm",
325 static int jh7110_tdm_startup(struct snd_pcm_substream
*substream
,
326 struct snd_soc_dai
*cpu_dai
)
328 struct snd_soc_pcm_runtime
*rtd
= snd_soc_substream_to_rtd(substream
);
329 struct snd_soc_dai_link
*dai_link
= rtd
->dai_link
;
331 dai_link
->trigger_stop
= SND_SOC_TRIGGER_ORDER_LDC
;
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
)
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
;
347 data_width
= params_width(params
);
349 tdm
->samplerate
= params_rate(params
);
350 tdm
->pcmclk
= params_channels(params
) * tdm
->samplerate
* data_width
;
352 switch (params_format(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
;
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
;
366 dev_err(tdm
->dev
, "tdm: unsupported PCM fmt");
370 chan_nr
= params_channels(params
);
379 dev_err(tdm
->dev
, "channel not supported\n");
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
;
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
;
397 snd_soc_dai_set_dma_data(dai
, substream
, dma_data
);
399 ret
= jh7110_tdm_config(tdm
, substream
);
403 jh7110_tdm_save_context(tdm
, substream
);
407 static int jh7110_tdm_trigger(struct snd_pcm_substream
*substream
,
408 int cmd
, struct snd_soc_dai
*dai
)
410 struct jh7110_tdm_dev
*tdm
= snd_soc_dai_get_drvdata(dai
);
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
);
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
);
433 static int jh7110_tdm_set_dai_fmt(struct snd_soc_dai
*cpu_dai
,
436 struct jh7110_tdm_dev
*tdm
= snd_soc_dai_get_drvdata(cpu_dai
);
439 /* set master/slave audio interface */
440 switch (fmt
& SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK
) {
441 case SND_SOC_DAIFMT_BP_FP
:
443 tdm
->ms_mode
= TDM_AS_MASTER
;
445 case SND_SOC_DAIFMT_BC_FC
:
446 /* codec is master */
447 tdm
->ms_mode
= TDM_AS_SLAVE
;
449 case SND_SOC_DAIFMT_BC_FP
:
450 case SND_SOC_DAIFMT_BP_FC
:
453 dev_dbg(tdm
->dev
, "dwc : Invalid clock provider format\n");
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
, gbcr
);
466 static int jh7110_tdm_dai_probe(struct snd_soc_dai
*dai
)
468 struct jh7110_tdm_dev
*tdm
= snd_soc_dai_get_drvdata(dai
);
470 snd_soc_dai_init_dma_data(dai
, &tdm
->play_dma_data
, &tdm
->capture_dma_data
);
471 snd_soc_dai_set_drvdata(dai
, tdm
);
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
,
483 #define JH7110_TDM_RATES SNDRV_PCM_RATE_8000_48000
485 #define JH7110_TDM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
486 SNDRV_PCM_FMTBIT_S32_LE)
488 static struct snd_soc_dai_driver jh7110_tdm_dai
= {
492 .stream_name
= "Playback",
495 .rates
= JH7110_TDM_RATES
,
496 .formats
= JH7110_TDM_FORMATS
,
499 .stream_name
= "Capture",
502 .rates
= JH7110_TDM_RATES
,
503 .formats
= JH7110_TDM_FORMATS
,
505 .ops
= &jh7110_tdm_dai_ops
,
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,
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,
530 static void jh7110_tdm_init_params(struct jh7110_tdm_dev
*tdm
)
532 tdm
->clkpolity
= TDM_TX_RASING_RX_FALLING
;
533 tdm
->elm
= TDM_ELM_LATE
;
534 tdm
->syncm
= TDM_SYNCM_SHORT
;
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
;
542 tdm
->rx
.lrj
= TDM_LEFT_JUSTIFT
;
543 tdm
->tx
.lrj
= TDM_LEFT_JUSTIFT
;
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;
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;
556 static int jh7110_tdm_clk_reset_get(struct platform_device
*pdev
,
557 struct jh7110_tdm_dev
*tdm
)
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";
568 ret
= devm_clk_bulk_get(&pdev
->dev
, ARRAY_SIZE(tdm
->clks
), tdm
->clks
);
570 dev_err(&pdev
->dev
, "Failed to get tdm clocks\n");
574 tdm
->resets
= devm_reset_control_array_get_exclusive(&pdev
->dev
);
575 if (IS_ERR(tdm
->resets
)) {
576 dev_err(&pdev
->dev
, "Failed to get tdm resets\n");
577 return PTR_ERR(tdm
->resets
);
583 static int jh7110_tdm_probe(struct platform_device
*pdev
)
585 struct jh7110_tdm_dev
*tdm
;
588 tdm
= devm_kzalloc(&pdev
->dev
, sizeof(*tdm
), GFP_KERNEL
);
592 tdm
->tdm_base
= devm_platform_ioremap_resource(pdev
, 0);
593 if (IS_ERR(tdm
->tdm_base
))
594 return PTR_ERR(tdm
->tdm_base
);
596 tdm
->dev
= &pdev
->dev
;
598 ret
= jh7110_tdm_clk_reset_get(pdev
, tdm
);
600 dev_err(&pdev
->dev
, "Failed to enable audio-tdm clock\n");
604 jh7110_tdm_init_params(tdm
);
606 dev_set_drvdata(&pdev
->dev
, tdm
);
607 ret
= devm_snd_soc_register_component(&pdev
->dev
, &jh7110_tdm_component
,
610 dev_err(&pdev
->dev
, "Failed to register dai\n");
614 ret
= devm_snd_dmaengine_pcm_register(&pdev
->dev
,
615 &jh7110_dmaengine_pcm_config
,
616 SND_DMAENGINE_PCM_FLAG_COMPAT
);
618 dev_err(&pdev
->dev
, "Could not register pcm: %d\n", ret
);
622 pm_runtime_enable(&pdev
->dev
);
623 if (!pm_runtime_enabled(&pdev
->dev
)) {
624 ret
= jh7110_tdm_runtime_resume(&pdev
->dev
);
632 pm_runtime_disable(&pdev
->dev
);
637 static void jh7110_tdm_dev_remove(struct platform_device
*pdev
)
639 pm_runtime_disable(&pdev
->dev
);
642 static const struct of_device_id jh7110_tdm_of_match
[] = {
643 { .compatible
= "starfive,jh7110-tdm", },
647 MODULE_DEVICE_TABLE(of
, jh7110_tdm_of_match
);
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
)
656 static struct platform_driver jh7110_tdm_driver
= {
658 .name
= "jh7110-tdm",
659 .of_match_table
= jh7110_tdm_of_match
,
660 .pm
= pm_ptr(&jh7110_tdm_pm_ops
),
662 .probe
= jh7110_tdm_probe
,
663 .remove
= jh7110_tdm_dev_remove
,
665 module_platform_driver(jh7110_tdm_driver
);
667 MODULE_DESCRIPTION("StarFive JH7110 TDM ASoC Driver");
668 MODULE_AUTHOR("Walker Chen <walker.chen@starfivetech.com>");
669 MODULE_LICENSE("GPL");