]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
ASoC: dwc: Add TDM mode support
authorMaxim Kochetkov <fido_max@inbox.ru>
Thu, 22 Jun 2023 20:00:29 +0000 (23:00 +0300)
committerMark Brown <broonie@kernel.org>
Sun, 9 Jul 2023 21:51:01 +0000 (22:51 +0100)
Depending on hardware implementaion of DWC I2S controller may support
TDM mode if enabled in SoC at design time.
Unfortunately there is no way to detect TDM capability for DWC by
reading registers. Anyway, if such capability enabled, TDM mode
can be enabled and configured by dai-tdm-slot-* DT options.

Signed-off-by: Maxim Kochetkov <fido_max@inbox.ru>
Link: https://lore.kernel.org/r/20230622200031.120168-1-fido_max@inbox.ru
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/dwc/dwc-i2s.c
sound/soc/dwc/dwc-pcm.c
sound/soc/dwc/local.h

index 97d652f0e84d362a49f2402ed848e1efe2ba99af..1f1ee14b04e6494555918eb5b9409bd28e989a10 100644 (file)
@@ -183,7 +183,15 @@ static void i2s_start(struct dw_i2s_dev *dev,
 {
        struct i2s_clk_config_data *config = &dev->config;
 
-       i2s_write_reg(dev->i2s_base, IER, 1);
+       u32 reg = IER_IEN;
+
+       if (dev->tdm_slots) {
+               reg |= (dev->tdm_slots - 1) << IER_TDM_SLOTS_SHIFT;
+               reg |= IER_INTF_TYPE;
+               reg |= dev->frame_offset << IER_FRAME_OFF_SHIFT;
+       }
+
+       i2s_write_reg(dev->i2s_base, IER, reg);
 
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                i2s_write_reg(dev->i2s_base, ITER, 1);
@@ -233,13 +241,15 @@ static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
                                      dev->xfer_resolution);
                        i2s_write_reg(dev->i2s_base, TFCR(ch_reg),
                                      dev->fifo_th - 1);
-                       i2s_write_reg(dev->i2s_base, TER(ch_reg), 1);
+                       i2s_write_reg(dev->i2s_base, TER(ch_reg), TER_TXCHEN |
+                                     dev->tdm_mask << TER_TXSLOT_SHIFT);
                } else {
                        i2s_write_reg(dev->i2s_base, RCR(ch_reg),
                                      dev->xfer_resolution);
                        i2s_write_reg(dev->i2s_base, RFCR(ch_reg),
                                      dev->fifo_th - 1);
-                       i2s_write_reg(dev->i2s_base, RER(ch_reg), 1);
+                       i2s_write_reg(dev->i2s_base, RER(ch_reg), RER_RXCHEN |
+                                     dev->tdm_mask << RER_RXSLOT_SHIFT);
                }
 
        }
@@ -276,6 +286,9 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
                return -EINVAL;
        }
 
+       if (dev->tdm_slots)
+               config->data_width = 32;
+
        config->chan_nr = params_channels(params);
 
        switch (config->chan_nr) {
@@ -384,14 +397,58 @@ static int dw_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
                ret = -EINVAL;
                break;
        }
+
+       switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+       case SND_SOC_DAIFMT_I2S:
+       case SND_SOC_DAIFMT_LEFT_J:
+       case SND_SOC_DAIFMT_RIGHT_J:
+               break;
+       case SND_SOC_DAIFMT_DSP_A:
+               dev->frame_offset = 1;
+               break;
+       case SND_SOC_DAIFMT_DSP_B:
+               dev->frame_offset = 0;
+               break;
+       default:
+               dev_err(dev->dev, "DAI format unsupported");
+               return -EINVAL;
+       }
+
        return ret;
 }
 
+static int dw_i2s_set_tdm_slot(struct snd_soc_dai *cpu_dai,    unsigned int tx_mask,
+                          unsigned int rx_mask, int slots, int slot_width)
+{
+       struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+
+       if (slot_width != 32)
+               return -EINVAL;
+
+       if (slots < 0 || slots > 16)
+               return -EINVAL;
+
+       if (rx_mask != tx_mask)
+               return -EINVAL;
+
+       if (!rx_mask)
+               return -EINVAL;
+
+       dev->tdm_slots = slots;
+       dev->tdm_mask = rx_mask;
+
+       dev->l_reg = RSLOT_TSLOT(ffs(rx_mask) - 1);
+       dev->r_reg = RSLOT_TSLOT(fls(rx_mask) - 1);
+
+       return 0;
+}
+
 static const struct snd_soc_dai_ops dw_i2s_dai_ops = {
        .hw_params      = dw_i2s_hw_params,
        .prepare        = dw_i2s_prepare,
        .trigger        = dw_i2s_trigger,
        .set_fmt        = dw_i2s_set_fmt,
+       .set_tdm_slot   = dw_i2s_set_tdm_slot,
 };
 
 #ifdef CONFIG_PM
@@ -726,6 +783,8 @@ static int dw_i2s_probe(struct platform_device *pdev)
                if (irq >= 0) {
                        ret = dw_pcm_register(pdev);
                        dev->use_pio = true;
+                       dev->l_reg = LRBR_LTHR(0);
+                       dev->r_reg = RRBR_RTHR(0);
                } else {
                        ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL,
                                        0);
index 9f25631d43d3029bec4ef336f0bb089223049257..f99262b89008268ed88e62cd4291983fe7cfd3fb 100644 (file)
@@ -31,8 +31,8 @@ static unsigned int dw_pcm_tx_##sample_bits(struct dw_i2s_dev *dev, \
        int i; \
 \
        for (i = 0; i < dev->fifo_th; i++) { \
-               iowrite32(p[tx_ptr][0], dev->i2s_base + LRBR_LTHR(0)); \
-               iowrite32(p[tx_ptr][1], dev->i2s_base + RRBR_RTHR(0)); \
+               iowrite32(p[tx_ptr][0], dev->i2s_base + dev->l_reg); \
+               iowrite32(p[tx_ptr][1], dev->i2s_base + dev->r_reg); \
                period_pos++; \
                if (++tx_ptr >= runtime->buffer_size) \
                        tx_ptr = 0; \
@@ -51,8 +51,8 @@ static unsigned int dw_pcm_rx_##sample_bits(struct dw_i2s_dev *dev, \
        int i; \
 \
        for (i = 0; i < dev->fifo_th; i++) { \
-               p[rx_ptr][0] = ioread32(dev->i2s_base + LRBR_LTHR(0)); \
-               p[rx_ptr][1] = ioread32(dev->i2s_base + RRBR_RTHR(0)); \
+               p[rx_ptr][0] = ioread32(dev->i2s_base + dev->l_reg); \
+               p[rx_ptr][1] = ioread32(dev->i2s_base + dev->r_reg); \
                period_pos++; \
                if (++rx_ptr >= runtime->buffer_size) \
                        rx_ptr = 0; \
index ba4e397099be44d10e89dd4f22ba94e42732dcdb..4ce96bac2f39984511fe93a6d4cd864a6bcf1c3f 100644 (file)
 #define RXFFR          0x014
 #define TXFFR          0x018
 
+/* Enable register fields */
+#define IER_TDM_SLOTS_SHIFT    8
+#define IER_FRAME_OFF_SHIFT    5
+#define IER_FRAME_OFF  BIT(5)
+#define IER_INTF_TYPE  BIT(1)
+#define IER_IEN                BIT(0)
+
 /* Interrupt status register fields */
 #define ISR_TXFO       BIT(5)
 #define ISR_TXFE       BIT(4)
 #define TFCR(x)                (0x40 * x + 0x04C)
 #define RFF(x)         (0x40 * x + 0x050)
 #define TFF(x)         (0x40 * x + 0x054)
+#define RSLOT_TSLOT(x) (0x4 * (x) + 0x224)
+
+/* Receive enable register fields */
+#define RER_RXSLOT_SHIFT       8
+#define RER_RXCHEN     BIT(0)
+
+/* Transmit enable register fields */
+#define TER_TXSLOT_SHIFT       8
+#define TER_TXCHEN     BIT(0)
 
 /* I2SCOMPRegisters */
 #define I2S_COMP_PARAM_2       0x01F0
@@ -105,6 +121,8 @@ struct dw_i2s_dev {
        u32 ccr;
        u32 xfer_resolution;
        u32 fifo_th;
+       u32 l_reg;
+       u32 r_reg;
 
        /* data related to DMA transfers b/w i2s and DMAC */
        union dw_i2s_snd_dma_data play_dma_data;
@@ -114,6 +132,12 @@ struct dw_i2s_dev {
 
        /* data related to PIO transfers */
        bool use_pio;
+
+       /* data related to TDM mode */
+       u32 tdm_slots;
+       u32 tdm_mask;
+       u32 frame_offset;
+
        struct snd_pcm_substream __rcu *tx_substream;
        struct snd_pcm_substream __rcu *rx_substream;
        unsigned int (*tx_fn)(struct dw_i2s_dev *dev,