]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
ASoC: renesas: add MSIOF sound support
authorKuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Thu, 17 Apr 2025 23:23:32 +0000 (23:23 +0000)
committerMark Brown <broonie@kernel.org>
Sat, 26 Apr 2025 01:17:37 +0000 (02:17 +0100)
Renesas MSIOF (Clock-Synchronized Serial Interface with FIFO) can work as
both SPI and I2S. Adds MSIOF-I2S driver.

MSIOF-SPI/I2S are using same DT compatible properties.
MSIOF-I2S         uses Of-Graph for Audio-Graph-Card/Card2,
MSIOF-SPI doesn't use  Of-Graph.

Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Link: https://patch.msgid.link/87r01q2wzv.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/renesas/Kconfig
sound/soc/renesas/rcar/Makefile
sound/soc/renesas/rcar/msiof.c [new file with mode: 0644]

index cb01fb36355f02c3a7c90d2b870e96b780ee29d5..dabf02a955ca1f28283c797684574fe6e2de13ba 100644 (file)
@@ -46,6 +46,13 @@ config SND_SOC_RCAR
        help
          This option enables R-Car SRU/SCU/SSIU/SSI sound support
 
+config SND_SOC_MSIOF
+       tristate "R-Car series MSIOF support"
+       depends on OF
+       select SND_DMAENGINE_PCM
+       help
+         This option enables R-Car MSIOF sound support
+
 config SND_SOC_RZ
        tristate "RZ/G2L series SSIF-2 support"
        depends on ARCH_RZG2L || COMPILE_TEST
index 45eb875a912a66ce5245149d975bbf49d1cdf4ce..3a2c875595bd83204d2724dc5ac99637ab3193e0 100644 (file)
@@ -1,3 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 snd-soc-rcar-y         := core.o gen.o dma.o adg.o ssi.o ssiu.o src.o ctu.o mix.o dvc.o cmd.o debugfs.o
 obj-$(CONFIG_SND_SOC_RCAR)     += snd-soc-rcar.o
+
+snd-soc-msiof-y                        := msiof.o
+obj-$(CONFIG_SND_SOC_MSIOF)    += snd-soc-msiof.o
diff --git a/sound/soc/renesas/rcar/msiof.c b/sound/soc/renesas/rcar/msiof.c
new file mode 100644 (file)
index 0000000..75c9e91
--- /dev/null
@@ -0,0 +1,566 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Renesas R-Car MSIOF (Clock-Synchronized Serial Interface with FIFO) I2S driver
+//
+// Copyright (C) 2025 Renesas Solutions Corp.
+// Author: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+//
+
+/*
+ * [NOTE]
+ *
+ * This driver doesn't support Clock/Frame Provider Mode
+ *
+ * Basically MSIOF is created for SPI, but we can use it as I2S (Sound), etc. Because of it, when
+ * we use it as I2S (Sound) with Provider Mode, we need to send dummy TX data even though it was
+ * used for RX. Because SPI HW needs TX Clock/Frame output for RX purpose.
+ * But it makes driver code complex in I2S (Sound).
+ *
+ * And when we use it as I2S (Sound) as Provider Mode, the clock source is [MSO clock] (= 133.33MHz)
+ * SoC internal clock. It is not for 48kHz/44.1kHz base clock. Thus the output/input will not be
+ * accurate sound.
+ *
+ * Because of these reasons, this driver doesn't support Clock/Frame Provider Mode. Use it as
+ * Clock/Frame Consumer Mode.
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_dma.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/soc.h>
+
+/* register */
+#define SITMDR1                0x00
+#define SITMDR2                0x04
+#define SITMDR3                0x08
+#define SIRMDR1                0x10
+#define SIRMDR2                0x14
+#define SIRMDR3                0x18
+#define SICTR          0x28
+#define SISTR          0x40
+#define SIIER          0x44
+#define SITFDR         0x50
+#define SIRFDR         0x60
+
+/* SITMDR1/ SIRMDR1 */
+#define PCON           (1 << 30)               /* Transfer Signal Connection */
+#define SYNCMD_LR      (3 << 28)               /* L/R mode */
+#define SYNCAC         (1 << 25)               /* Sync Polarity (Active-low) */
+#define DTDL_1         (1 << 20)               /* 1-clock-cycle delay */
+#define TXSTP          (1 <<  0)               /* Transmission/Reception Stop on FIFO */
+
+/* SITMDR2 and SIRMDR2 */
+#define BITLEN1(x)     (((x) - 1) << 24)       /* Data Size (8-32 bits) */
+#define GRP            (1 << 30)               /* Group count */
+
+/* SICTR */
+#define TEDG           (1 << 27)               /* Transmit Timing (1 = falling edge) */
+#define REDG           (1 << 26)               /* Receive  Timing (1 = rising  edge) */
+#define TXE            (1 <<  9)               /* Transmit Enable */
+#define RXE            (1 <<  8)               /* Receive Enable */
+
+/* SISTR */
+#define TFSERR         (1 << 21)               /* Transmit Frame Synchronization Error */
+#define TFOVF          (1 << 20)               /* Transmit FIFO Overflow */
+#define TFUDF          (1 << 19)               /* Transmit FIFO Underflow */
+#define RFSERR         (1 <<  5)               /* Receive Frame Synchronization Error */
+#define RFUDF          (1 <<  4)               /* Receive FIFO Underflow */
+#define RFOVF          (1 <<  3)               /* Receive FIFO Overflow */
+#define SISTR_ERR_TX   (TFSERR | TFOVF | TFUDF)
+#define SISTR_ERR_RX   (RFSERR | RFOVF | RFUDF)
+#define SISTR_ERR      (SISTR_ERR_TX | SISTR_ERR_RX)
+
+/* SIIER */
+#define TDMAE          (1 << 31)               /* Transmit Data DMA Transfer Req. Enable */
+#define TDREQE         (1 << 28)               /* Transmit Data Transfer Request Enable */
+#define RDMAE          (1 << 15)               /* Receive Data DMA Transfer Req. Enable */
+#define RDREQE         (1 << 12)               /* Receive Data Transfer Request Enable */
+
+/*
+ * The data on memory in 24bit case is located at <right> side
+ *     [  xxxxxx]
+ *     [  xxxxxx]
+ *     [  xxxxxx]
+ *
+ * HW assuming signal in 24bit case is located at <left> side
+ *     ---+         +---------+
+ *        +---------+         +---------+...
+ *        [xxxxxx  ][xxxxxx  ][xxxxxx  ]
+ *
+ * When we use 24bit data, it will be transferred via 32bit width via DMA,
+ * and MSIOF/DMA doesn't support data shift, we can't use 24bit data correctly.
+ * There is no such issue on 16/32bit data case.
+ */
+#define MSIOF_RATES    SNDRV_PCM_RATE_8000_192000
+#define MSIOF_FMTS     (SNDRV_PCM_FMTBIT_S16_LE |\
+                        SNDRV_PCM_FMTBIT_S32_LE)
+
+struct msiof_priv {
+       struct device *dev;
+       struct snd_pcm_substream *substream[SNDRV_PCM_STREAM_LAST + 1];
+       spinlock_t lock;
+       void __iomem *base;
+       resource_size_t phy_addr;
+
+       /* for error */
+       int err_syc[SNDRV_PCM_STREAM_LAST + 1];
+       int err_ovf[SNDRV_PCM_STREAM_LAST + 1];
+       int err_udf[SNDRV_PCM_STREAM_LAST + 1];
+
+       /* bit field */
+       u32 flags;
+#define MSIOF_FLAGS_NEED_DELAY         (1 << 0)
+};
+#define msiof_flag_has(priv, flag)     (priv->flags &  flag)
+#define msiof_flag_set(priv, flag)     (priv->flags |= flag)
+
+#define msiof_is_play(substream)       ((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK)
+#define msiof_read(priv, reg)          ioread32((priv)->base + reg)
+#define msiof_write(priv, reg, val)    iowrite32(val, (priv)->base + reg)
+#define msiof_status_clear(priv)       msiof_write(priv, SISTR, SISTR_ERR)
+
+static void msiof_update(struct msiof_priv *priv, u32 reg, u32 mask, u32 val)
+{
+       u32 old = msiof_read(priv, reg);
+       u32 new = (old & ~mask) | (val & mask);
+
+       if (old != new)
+               msiof_write(priv, reg, new);
+}
+
+static void msiof_update_and_wait(struct msiof_priv *priv, u32 reg, u32 mask, u32 val, u32 expect)
+{
+       u32 data;
+       int ret;
+
+       msiof_update(priv, reg, mask, val);
+
+       ret = readl_poll_timeout_atomic(priv->base + reg, data,
+                                       (data & mask) == expect, 1, 128);
+       if (ret)
+               dev_warn(priv->dev, "write timeout [0x%02x] 0x%08x / 0x%08x\n",
+                        reg, data, expect);
+}
+
+static int msiof_hw_start(struct snd_soc_component *component,
+                         struct snd_pcm_substream *substream, int cmd)
+{
+       struct msiof_priv *priv = snd_soc_component_get_drvdata(component);
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       int is_play = msiof_is_play(substream);
+       int width = snd_pcm_format_width(runtime->format);
+       u32 val;
+
+       /*
+        * see
+        *      [NOTE] on top of this driver
+        */
+       /*
+        * see
+        *      Datasheet 109.3.6 [Transmit and Receive Procedures]
+        *
+        *      TX: Fig 109.14  - Fig 109.23
+        *      RX: Fig 109.15
+        */
+
+       /* reset errors */
+       priv->err_syc[substream->stream] =
+       priv->err_ovf[substream->stream] =
+       priv->err_udf[substream->stream] = 0;
+
+       /* SITMDRx */
+       if (is_play) {
+               val = PCON | SYNCMD_LR | SYNCAC | TXSTP;
+               if (msiof_flag_has(priv, MSIOF_FLAGS_NEED_DELAY))
+                       val |= DTDL_1;
+
+               msiof_write(priv, SITMDR1, val);
+
+               val = BITLEN1(width);
+               msiof_write(priv, SITMDR2, val | GRP);
+               msiof_write(priv, SITMDR3, val);
+
+       }
+       /* SIRMDRx */
+       else {
+               val = SYNCMD_LR | SYNCAC;
+               if (msiof_flag_has(priv, MSIOF_FLAGS_NEED_DELAY))
+                       val |= DTDL_1;
+
+               msiof_write(priv, SIRMDR1, val);
+
+               val = BITLEN1(width);
+               msiof_write(priv, SIRMDR2, val | GRP);
+               msiof_write(priv, SIRMDR3, val);
+       }
+
+       /* SIIER */
+       if (is_play)
+               val = TDREQE | TDMAE | SISTR_ERR_TX;
+       else
+               val = RDREQE | RDMAE | SISTR_ERR_RX;
+       msiof_update(priv, SIIER, val, val);
+
+       /* SICTR */
+       if (is_play)
+               val = TXE | TEDG;
+       else
+               val = RXE | REDG;
+       msiof_update_and_wait(priv, SICTR, val, val, val);
+
+       msiof_status_clear(priv);
+
+       /* Start DMAC */
+       snd_dmaengine_pcm_trigger(substream, cmd);
+
+       return 0;
+}
+
+static int msiof_hw_stop(struct snd_soc_component *component,
+                        struct snd_pcm_substream *substream, int cmd)
+{
+       struct msiof_priv *priv = snd_soc_component_get_drvdata(component);
+       struct device *dev = component->dev;
+       int is_play = msiof_is_play(substream);
+       u32 val;
+
+       /* SIIER */
+       if (is_play)
+               val = TDREQE | TDMAE | SISTR_ERR_TX;
+       else
+               val = RDREQE | RDMAE | SISTR_ERR_RX;
+       msiof_update(priv, SIIER, val, 0);
+
+       /* Stop DMAC */
+       snd_dmaengine_pcm_trigger(substream, cmd);
+
+       /* SICTR */
+       if (is_play)
+               val = TXE;
+       else
+               val = RXE;
+       msiof_update_and_wait(priv, SICTR, val, 0, 0);
+
+       /* indicate error status if exist */
+       if (priv->err_syc[substream->stream] ||
+           priv->err_ovf[substream->stream] ||
+           priv->err_udf[substream->stream])
+               dev_warn(dev, "FSERR(%s) = %d, FOVF = %d, FUDF = %d\n",
+                        snd_pcm_direction_name(substream->stream),
+                        priv->err_syc[substream->stream],
+                        priv->err_ovf[substream->stream],
+                        priv->err_udf[substream->stream]);
+
+       return 0;
+}
+
+static int msiof_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+       struct msiof_priv *priv = snd_soc_dai_get_drvdata(dai);
+
+       switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+       /*
+        * It supports Clock/Frame Consumer Mode only
+        * see
+        *      [NOTE] on top of this driver
+        */
+       case SND_SOC_DAIFMT_BC_FC:
+               break;
+       /* others are error */
+       default:
+               return -EINVAL;
+       }
+
+       switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+       /* it supports NB_NF only */
+       case SND_SOC_DAIFMT_NB_NF:
+       default:
+               break;
+       /* others are error */
+       case SND_SOC_DAIFMT_NB_IF:
+       case SND_SOC_DAIFMT_IB_NF:
+       case SND_SOC_DAIFMT_IB_IF:
+               return -EINVAL;
+       }
+
+       switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+       case SND_SOC_DAIFMT_I2S:
+               msiof_flag_set(priv, MSIOF_FLAGS_NEED_DELAY);
+               break;
+       case SND_SOC_DAIFMT_LEFT_J:
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/*
+ * Select below from Sound Card, not auto
+ *     SND_SOC_DAIFMT_CBC_CFC
+ *     SND_SOC_DAIFMT_CBP_CFP
+ */
+static const u64 msiof_dai_formats = SND_SOC_POSSIBLE_DAIFMT_I2S       |
+                                    SND_SOC_POSSIBLE_DAIFMT_LEFT_J     |
+                                    SND_SOC_POSSIBLE_DAIFMT_NB_NF;
+
+static const struct snd_soc_dai_ops msiof_dai_ops = {
+       .set_fmt                        = msiof_dai_set_fmt,
+       .auto_selectable_formats        = &msiof_dai_formats,
+       .num_auto_selectable_formats    = 1,
+};
+
+static struct snd_soc_dai_driver msiof_dai_driver = {
+       .name = "msiof-dai",
+       .playback = {
+               .rates          = MSIOF_RATES,
+               .formats        = MSIOF_FMTS,
+               .channels_min   = 2,
+               .channels_max   = 2,
+       },
+       .capture = {
+               .rates          = MSIOF_RATES,
+               .formats        = MSIOF_FMTS,
+               .channels_min   = 2,
+               .channels_max   = 2,
+       },
+       .ops = &msiof_dai_ops,
+};
+
+static struct snd_pcm_hardware msiof_pcm_hardware = {
+       .info = SNDRV_PCM_INFO_INTERLEAVED      |
+               SNDRV_PCM_INFO_MMAP             |
+               SNDRV_PCM_INFO_MMAP_VALID,
+       .buffer_bytes_max       = 64 * 1024,
+       .period_bytes_min       = 32,
+       .period_bytes_max       = 8192,
+       .periods_min            = 1,
+       .periods_max            = 32,
+       .fifo_size              = 64,
+};
+
+static int msiof_open(struct snd_soc_component *component,
+                     struct snd_pcm_substream *substream)
+{
+       struct device *dev = component->dev;
+       struct dma_chan *chan;
+       static const char * const dma_names[] = {"rx", "tx"};
+       int is_play = msiof_is_play(substream);
+       int ret;
+
+       chan = of_dma_request_slave_channel(dev->of_node, dma_names[is_play]);
+       if (IS_ERR(chan))
+               return PTR_ERR(chan);
+
+       ret = snd_dmaengine_pcm_open(substream, chan);
+       if (ret < 0)
+               goto open_err_dma;
+
+       snd_soc_set_runtime_hwparams(substream, &msiof_pcm_hardware);
+
+       ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+open_err_dma:
+       if (ret < 0)
+               dma_release_channel(chan);
+
+       return ret;
+}
+
+static int msiof_close(struct snd_soc_component *component,
+                      struct snd_pcm_substream *substream)
+{
+       return snd_dmaengine_pcm_close_release_chan(substream);
+}
+
+static snd_pcm_uframes_t msiof_pointer(struct snd_soc_component *component,
+                                      struct snd_pcm_substream *substream)
+{
+       return snd_dmaengine_pcm_pointer(substream);
+}
+
+#define PREALLOC_BUFFER                (32 * 1024)
+#define PREALLOC_BUFFER_MAX    (32 * 1024)
+static int msiof_new(struct snd_soc_component *component,
+                    struct snd_soc_pcm_runtime *rtd)
+{
+       snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
+                                      rtd->card->snd_card->dev,
+                                      PREALLOC_BUFFER, PREALLOC_BUFFER_MAX);
+       return 0;
+}
+
+static int msiof_trigger(struct snd_soc_component *component,
+                        struct snd_pcm_substream *substream, int cmd)
+{
+       struct device *dev = component->dev;
+       struct msiof_priv *priv = dev_get_drvdata(dev);
+       unsigned long flags;
+       int ret = -EINVAL;
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+               priv->substream[substream->stream] = substream;
+               fallthrough;
+       case SNDRV_PCM_TRIGGER_RESUME:
+               ret = msiof_hw_start(component, substream, cmd);
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+               priv->substream[substream->stream] = NULL;
+               fallthrough;
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+               ret = msiof_hw_stop(component, substream, cmd);
+               break;
+       }
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return ret;
+}
+
+static int msiof_hw_params(struct snd_soc_component *component,
+                          struct snd_pcm_substream *substream,
+                          struct snd_pcm_hw_params *params)
+{
+       struct msiof_priv *priv = dev_get_drvdata(component->dev);
+       struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
+       struct dma_slave_config cfg = {};
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       ret = snd_hwparams_to_dma_slave_config(substream, params, &cfg);
+       if (ret < 0)
+               goto hw_params_out;
+
+       cfg.dst_addr = priv->phy_addr + SITFDR;
+       cfg.src_addr = priv->phy_addr + SIRFDR;
+
+       ret = dmaengine_slave_config(chan, &cfg);
+hw_params_out:
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return ret;
+}
+
+static const struct snd_soc_component_driver msiof_component_driver = {
+       .name           = "msiof",
+       .open           = msiof_open,
+       .close          = msiof_close,
+       .pointer        = msiof_pointer,
+       .pcm_construct  = msiof_new,
+       .trigger        = msiof_trigger,
+       .hw_params      = msiof_hw_params,
+};
+
+static irqreturn_t msiof_interrupt(int irq, void *data)
+{
+       struct msiof_priv *priv = data;
+       struct snd_pcm_substream *substream;
+       u32 sistr;
+
+       spin_lock(&priv->lock);
+
+       sistr = msiof_read(priv, SISTR);
+       msiof_status_clear(priv);
+
+       spin_unlock(&priv->lock);
+
+       /* overflow/underflow error */
+       substream = priv->substream[SNDRV_PCM_STREAM_PLAYBACK];
+       if (substream && (sistr & SISTR_ERR_TX)) {
+               // snd_pcm_stop_xrun(substream);
+               if (sistr & TFSERR)
+                       priv->err_syc[SNDRV_PCM_STREAM_PLAYBACK]++;
+               if (sistr & TFOVF)
+                       priv->err_ovf[SNDRV_PCM_STREAM_PLAYBACK]++;
+               if (sistr & TFUDF)
+                       priv->err_udf[SNDRV_PCM_STREAM_PLAYBACK]++;
+       }
+
+       substream = priv->substream[SNDRV_PCM_STREAM_CAPTURE];
+       if (substream && (sistr & SISTR_ERR_RX)) {
+               // snd_pcm_stop_xrun(substream);
+               if (sistr & RFSERR)
+                       priv->err_syc[SNDRV_PCM_STREAM_CAPTURE]++;
+               if (sistr & RFOVF)
+                       priv->err_ovf[SNDRV_PCM_STREAM_CAPTURE]++;
+               if (sistr & RFUDF)
+                       priv->err_udf[SNDRV_PCM_STREAM_CAPTURE]++;
+       }
+
+       return IRQ_HANDLED;
+}
+
+static int msiof_probe(struct platform_device *pdev)
+{
+       struct msiof_priv *priv;
+       struct device *dev = &pdev->dev;
+       struct resource *res;
+       int irq, ret;
+
+       /* Check MSIOF as Sound mode or SPI mode */
+       struct device_node *port __free(device_node) = of_graph_get_next_port(dev->of_node, NULL);
+       if (!port)
+               return -ENODEV;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res)
+               return -ENODEV;
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq <= 0)
+               return irq;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->base = devm_ioremap_resource(dev, res);
+       if (IS_ERR(priv->base))
+               return PTR_ERR(priv->base);
+
+       ret = devm_request_irq(dev, irq, msiof_interrupt, 0, dev_name(dev), priv);
+       if (ret)
+               return ret;
+
+       priv->dev       = dev;
+       priv->phy_addr  = res->start;
+
+       spin_lock_init(&priv->lock);
+       platform_set_drvdata(pdev, priv);
+
+       devm_pm_runtime_enable(dev);
+
+       ret = devm_snd_soc_register_component(dev, &msiof_component_driver,
+                                             &msiof_dai_driver, 1);
+
+       return ret;
+}
+
+static const struct of_device_id msiof_of_match[] = {
+       { .compatible = "renesas,rcar-gen4-msiof", },
+       {},
+};
+MODULE_DEVICE_TABLE(of, msiof_of_match);
+
+static struct platform_driver msiof_driver = {
+       .driver = {
+               .name   = "msiof-pcm-audio",
+               .of_match_table = msiof_of_match,
+       },
+       .probe          = msiof_probe,
+};
+module_platform_driver(msiof_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Renesas R-Car MSIOF I2S audio driver");
+MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");