2023-12-21 03:32:53

by Xingyu Wu

[permalink] [raw]
Subject: [PATCH v1 2/2] ASoC: starfive: Add drivers of Cadence Multi-Channel I2S Controller

Add the drivers of Cadence Multi-Channel I2S Controller on the
StarFive JH8100 SoC.

The Cadence I2S Controller implements a function of the multi-channel
(up to 8-channel) bus. Each stereo channel combines functions of a
transmitter and a receiver. Each channel has independent gating, clock
and interruption control. It alos support some of these channels are
used as playback and others can also be used as record in the same time.

Signed-off-by: Xingyu Wu <[email protected]>
---
MAINTAINERS | 7 +
sound/soc/starfive/Kconfig | 18 +
sound/soc/starfive/Makefile | 4 +
sound/soc/starfive/cdns-jh8100-i2s-pcm.c | 262 ++++++++
sound/soc/starfive/cdns-jh8100-i2s.c | 727 +++++++++++++++++++++++
sound/soc/starfive/cdns-jh8100-i2s.h | 171 ++++++
6 files changed, 1189 insertions(+)
create mode 100644 sound/soc/starfive/cdns-jh8100-i2s-pcm.c
create mode 100644 sound/soc/starfive/cdns-jh8100-i2s.c
create mode 100644 sound/soc/starfive/cdns-jh8100-i2s.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 9104430e148e..f920297e1594 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20658,6 +20658,13 @@ F: Documentation/devicetree/bindings/power/starfive*
F: drivers/pmdomain/starfive/
F: include/dt-bindings/power/starfive,jh7110-pmu.h

+STARFIVE JH8100 CDNS I2S CONTROLLER DRIVER
+M: Xingyu Wu <[email protected]>
+M: Walker Chen <[email protected]>
+S: Supported
+F: Documentation/devicetree/bindings/sound/cdns,jh8100-i2s.yaml
+F: sound/soc/starfive/cdns-jh8100-i2s*
+
STARFIVE SOC DRIVERS
M: Conor Dooley <[email protected]>
S: Maintained
diff --git a/sound/soc/starfive/Kconfig b/sound/soc/starfive/Kconfig
index 279ac5c1d309..f2ee72e8536d 100644
--- a/sound/soc/starfive/Kconfig
+++ b/sound/soc/starfive/Kconfig
@@ -22,3 +22,21 @@ config SND_SOC_JH7110_TDM
select SND_SOC_GENERIC_DMAENGINE_PCM
help
Say Y or M if you want to add support for StarFive TDM driver.
+
+config SND_SOC_JH8100_CADENCE_I2S
+ tristate "Cadence I2S Controller Device Driver for StarFive JH8100 SoC"
+ depends on HAVE_CLK && SND_SOC_STARFIVE
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ help
+ Say Y or M if you want to add support for I2S driver for the
+ StarFive JH8100 Cadence Multi-Channel I2S Controller device.
+
+
+config SND_SOC_JH8100_CADENCE_I2S_PCM
+ bool "PCM PIO extension for CDNS I2S driver on the StarFive JH8100 SoC"
+ depends on SND_SOC_JH8100_CADENCE_I2S
+ help
+ Say Y or N if you want to add a custom ALSA extension that registers
+ a PCM and uses PIO to transfer data.
+ This functionality is specially suited for I2S devices that don't have
+ DMA support.
diff --git a/sound/soc/starfive/Makefile b/sound/soc/starfive/Makefile
index 9e958f70ef51..9f8fd0ad6187 100644
--- a/sound/soc/starfive/Makefile
+++ b/sound/soc/starfive/Makefile
@@ -1,3 +1,7 @@
# StarFive Platform Support
obj-$(CONFIG_SND_SOC_JH7110_PWMDAC) += jh7110_pwmdac.o
obj-$(CONFIG_SND_SOC_JH7110_TDM) += jh7110_tdm.o
+
+obj-$(CONFIG_SND_SOC_JH8100_CADENCE_I2S) += jh8100-i2s.o
+jh8100-i2s-y := cdns-jh8100-i2s.o
+jh8100-i2s-$(CONFIG_SND_SOC_JH8100_CADENCE_I2S_PCM) += cdns-jh8100-i2s-pcm.o
diff --git a/sound/soc/starfive/cdns-jh8100-i2s-pcm.c b/sound/soc/starfive/cdns-jh8100-i2s-pcm.c
new file mode 100644
index 000000000000..e0172d109925
--- /dev/null
+++ b/sound/soc/starfive/cdns-jh8100-i2s-pcm.c
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cadence Multi-Channel I2S controller PCM driver
+ *
+ * Copyright (c) 2022-2023 StarFive Technology Co., Ltd.
+ */
+
+#include <linux/io.h>
+#include <linux/rcupdate.h>
+#include <sound/pcm_params.h>
+#include "cdns-jh8100-i2s.h"
+
+#define PERIOD_BYTES_MIN 4096
+#define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN)
+#define PERIODS_MIN 2
+
+static unsigned int cdns_jh8100_i2s_pcm_tx(struct cdns_jh8100_i2s_dev *dev,
+ struct snd_pcm_runtime *runtime,
+ unsigned int tx_ptr, bool *period_elapsed,
+ snd_pcm_format_t format)
+{
+ unsigned int period_pos = tx_ptr % runtime->period_size;
+ const u16 (*p16)[2] = (void *)runtime->dma_area;
+ const u32 (*p32)[2] = (void *)runtime->dma_area;
+ u32 data[2];
+ int i;
+
+ for (i = 0; i < CDNS_JH8100_I2S_FIFO_DEPTH; i++) {
+ if (format == SNDRV_PCM_FORMAT_S16_LE) {
+ data[0] = p16[tx_ptr][0];
+ data[1] = p16[tx_ptr][1];
+ } else if (format == SNDRV_PCM_FORMAT_S32_LE) {
+ data[0] = p32[tx_ptr][0];
+ data[1] = p32[tx_ptr][1];
+ }
+
+ iowrite32(data[0], dev->base + CDNS_JH8100_FIFO_MEM);
+ iowrite32(data[1], dev->base + CDNS_JH8100_FIFO_MEM);
+ period_pos++;
+ if (++tx_ptr >= runtime->buffer_size)
+ tx_ptr = 0;
+ }
+
+ *period_elapsed = period_pos >= runtime->period_size;
+ return tx_ptr;
+}
+
+static unsigned int cdns_jh8100_i2s_pcm_rx(struct cdns_jh8100_i2s_dev *dev,
+ struct snd_pcm_runtime *runtime,
+ unsigned int rx_ptr, bool *period_elapsed,
+ snd_pcm_format_t format)
+{
+ unsigned int period_pos = rx_ptr % runtime->period_size;
+ u16 (*p16)[2] = (void *)runtime->dma_area;
+ u32 (*p32)[2] = (void *)runtime->dma_area;
+ u32 data[2];
+ int i;
+
+ for (i = 0; i < CDNS_JH8100_I2S_FIFO_DEPTH; i++) {
+ data[0] = ioread32(dev->base + CDNS_JH8100_FIFO_MEM);
+ data[1] = ioread32(dev->base + CDNS_JH8100_FIFO_MEM);
+ if (format == SNDRV_PCM_FORMAT_S16_LE) {
+ p16[rx_ptr][0] = data[0];
+ p16[rx_ptr][1] = data[1];
+ } else if (format == SNDRV_PCM_FORMAT_S32_LE) {
+ p32[rx_ptr][0] = data[0];
+ p32[rx_ptr][1] = data[1];
+ }
+
+ period_pos++;
+ if (++rx_ptr >= runtime->buffer_size)
+ rx_ptr = 0;
+ }
+
+ *period_elapsed = period_pos >= runtime->period_size;
+ return rx_ptr;
+}
+
+static const struct snd_pcm_hardware cdns_jh8100_i2s_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .rates = SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_11025 |
+ SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_22050 |
+ SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = BUFFER_BYTES_MAX,
+ .period_bytes_min = PERIOD_BYTES_MIN,
+ .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
+ .periods_min = PERIODS_MIN,
+ .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+ .fifo_size = 16,
+};
+
+static void cdns_jh8100_i2s_pcm_transfer(struct cdns_jh8100_i2s_dev *dev, bool push)
+{
+ struct snd_pcm_substream *substream;
+ bool active, period_elapsed;
+
+ rcu_read_lock();
+ if (push)
+ substream = rcu_dereference(dev->tx_substream);
+ else
+ substream = rcu_dereference(dev->rx_substream);
+
+ active = substream && snd_pcm_running(substream);
+ if (active) {
+ unsigned int ptr;
+ unsigned int new_ptr;
+
+ if (push) {
+ ptr = READ_ONCE(dev->tx_ptr);
+ new_ptr = dev->tx_fn(dev, substream->runtime, ptr,
+ &period_elapsed, dev->format);
+ cmpxchg(&dev->tx_ptr, ptr, new_ptr);
+ } else {
+ ptr = READ_ONCE(dev->rx_ptr);
+ new_ptr = dev->rx_fn(dev, substream->runtime, ptr,
+ &period_elapsed, dev->format);
+ cmpxchg(&dev->rx_ptr, ptr, new_ptr);
+ }
+
+ if (period_elapsed)
+ snd_pcm_period_elapsed(substream);
+ }
+ rcu_read_unlock();
+}
+
+void cdns_jh8100_i2s_pcm_push_tx(struct cdns_jh8100_i2s_dev *dev)
+{
+ cdns_jh8100_i2s_pcm_transfer(dev, true);
+}
+
+void cdns_jh8100_i2s_pcm_pop_rx(struct cdns_jh8100_i2s_dev *dev)
+{
+ cdns_jh8100_i2s_pcm_transfer(dev, false);
+}
+
+static int cdns_jh8100_i2s_pcm_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct cdns_jh8100_i2s_dev *dev = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0));
+
+ snd_soc_set_runtime_hwparams(substream, &cdns_jh8100_i2s_pcm_hardware);
+ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+ runtime->private_data = dev;
+
+ return 0;
+}
+
+static int cdns_jh8100_i2s_pcm_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ synchronize_rcu();
+ return 0;
+}
+
+static int cdns_jh8100_i2s_pcm_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct cdns_jh8100_i2s_dev *dev = runtime->private_data;
+
+ dev->format = params_format(hw_params);
+ dev->tx_fn = cdns_jh8100_i2s_pcm_tx;
+ dev->rx_fn = cdns_jh8100_i2s_pcm_rx;
+
+ return 0;
+}
+
+static int cdns_jh8100_i2s_pcm_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct cdns_jh8100_i2s_dev *dev = runtime->private_data;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ WRITE_ONCE(dev->tx_ptr, 0);
+ rcu_assign_pointer(dev->tx_substream, substream);
+ } else {
+ WRITE_ONCE(dev->rx_ptr, 0);
+ rcu_assign_pointer(dev->rx_substream, substream);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rcu_assign_pointer(dev->tx_substream, NULL);
+ else
+ rcu_assign_pointer(dev->rx_substream, NULL);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t cdns_jh8100_i2s_pcm_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct cdns_jh8100_i2s_dev *dev = runtime->private_data;
+ snd_pcm_uframes_t pos;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ pos = READ_ONCE(dev->tx_ptr);
+ else
+ pos = READ_ONCE(dev->rx_ptr);
+
+ return pos < runtime->buffer_size ? pos : 0;
+}
+
+static int cdns_jh8100_i2s_pcm_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ size_t size = cdns_jh8100_i2s_pcm_hardware.buffer_bytes_max;
+
+ snd_pcm_set_managed_buffer_all(rtd->pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ NULL, size, size);
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver cdns_jh8100_i2s_pcm_component = {
+ .open = cdns_jh8100_i2s_pcm_open,
+ .close = cdns_jh8100_i2s_pcm_close,
+ .hw_params = cdns_jh8100_i2s_pcm_hw_params,
+ .trigger = cdns_jh8100_i2s_pcm_trigger,
+ .pointer = cdns_jh8100_i2s_pcm_pointer,
+ .pcm_construct = cdns_jh8100_i2s_pcm_new,
+};
+
+int cdns_jh8100_i2s_pcm_register(struct platform_device *pdev)
+{
+ return devm_snd_soc_register_component(&pdev->dev,
+ &cdns_jh8100_i2s_pcm_component,
+ NULL, 0);
+}
diff --git a/sound/soc/starfive/cdns-jh8100-i2s.c b/sound/soc/starfive/cdns-jh8100-i2s.c
new file mode 100644
index 000000000000..924f12af9f3a
--- /dev/null
+++ b/sound/soc/starfive/cdns-jh8100-i2s.c
@@ -0,0 +1,727 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cadence Multi-Channel I2S controller driver on the StarFive JH8100 SoC
+ *
+ * Copyright (c) 2023 StarFive Technology Co., Ltd.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "cdns-jh8100-i2s.h"
+
+static void cdns_jh8100_i2s_set_fifo_mask(struct cdns_jh8100_i2s_dev *i2s, u32 type)
+{
+ unsigned int temp = readl(i2s->base + CDNS_JH8100_CID_CTRL);
+
+ temp &= ~CDNS_JH8100_I2S_IT_ALL;
+ temp |= type;
+ writel(temp, i2s->base + CDNS_JH8100_CID_CTRL);
+}
+
+static inline void cdns_jh8100_i2s_clear_int(struct cdns_jh8100_i2s_dev *i2s)
+{
+ writel(0, i2s->base + CDNS_JH8100_I2S_INTR_STAT);
+}
+
+static int cdns_jh8100_i2s_reset_mask(struct cdns_jh8100_i2s_dev *i2s, u32 mask)
+{
+ unsigned int val = readl(i2s->base + CDNS_JH8100_I2S_CTRL);
+
+ val &= ~mask;
+ writel(val, i2s->base + CDNS_JH8100_I2S_CTRL);
+
+ /* Wait for the reset bit to done and is set to 1 */
+ return readl_poll_timeout_atomic(i2s->base + CDNS_JH8100_I2S_CTRL, val,
+ (val & mask), 0,
+ CDNS_JH8100_FIFO_ACK_TIMEOUT_US);
+}
+
+/* Reset for TX and RX control unit */
+static void cdns_jh8100_i2s_reset_txrx_unit(struct cdns_jh8100_i2s_dev *i2s)
+{
+ unsigned int val = readl(i2s->base + CDNS_JH8100_I2S_CTRL);
+
+ val |= CDNS_JH8100_I2S_CTRL_TXRX_RST;
+ writel(val, i2s->base + CDNS_JH8100_I2S_CTRL);
+}
+
+static void cdns_jh8100_i2s_set_ms_mode(struct cdns_jh8100_i2s_dev *i2s)
+{
+ unsigned int val = readl(i2s->base + CDNS_JH8100_I2S_CTRL);
+
+ val &= ~(CDNS_JH8100_I2S_CTRL_T_MS_MASK | CDNS_JH8100_I2S_CTRL_R_MS_MASK);
+ val |= (FIELD_PREP(CDNS_JH8100_I2S_CTRL_T_MS_MASK, i2s->tx_sync_ms_mode) |
+ FIELD_PREP(CDNS_JH8100_I2S_CTRL_R_MS_MASK, i2s->rx_sync_ms_mode));
+
+ writel(val, i2s->base + CDNS_JH8100_I2S_CTRL);
+}
+
+/* The threshold of almost empty & full config */
+static void cdns_jh8100_i2s_set_aempty_afull_th(struct cdns_jh8100_i2s_dev *i2s,
+ unsigned int aempty,
+ unsigned int afull)
+{
+ unsigned int val = aempty | (afull << CDNS_TRFIFO_CTRL_AFULL_THRESHOLD_SHIFT);
+
+ writel(val, i2s->base + CDNS_JH8100_TFIFO_CTRL);
+ writel(val, i2s->base + CDNS_JH8100_RFIFO_CTRL);
+}
+
+static void cdns_jh8100_i2s_set_channel_strobes(struct cdns_jh8100_i2s_dev *i2s,
+ u32 ch, bool strobe)
+{
+ unsigned int val = readl(i2s->base + CDNS_JH8100_CID_CTRL);
+
+ /* Active Low */
+ if (strobe)
+ val &= ~ch;
+ else
+ val |= ch;
+
+ writel(val, i2s->base + CDNS_JH8100_CID_CTRL);
+}
+
+/* Enable TX or RX clock */
+static void cdns_jh8100_i2s_enable_clock(struct cdns_jh8100_i2s_dev *i2s,
+ bool is_rx)
+{
+ unsigned int val = readl(i2s->base + CDNS_JH8100_CID_CTRL);
+ unsigned int mask = (is_rx ? CDNS_JH8100_CID_CTRL_STROBE_TX :
+ CDNS_JH8100_CID_CTRL_STROBE_RX);
+
+ /* Active Low */
+ val &= ~mask;
+ writel(val, i2s->base + CDNS_JH8100_CID_CTRL);
+}
+
+static void cdns_jh8100_i2s_set_transmitter_receiver(struct cdns_jh8100_i2s_dev *i2s,
+ u32 ch, bool is_transmit)
+{
+ unsigned int val = readl(i2s->base + CDNS_JH8100_I2S_CTRL);
+
+ /* 1: Transmitter, 0: Receiver */
+ if (is_transmit)
+ val |= (ch << CDNS_JH8100_I2S_CTRL_TR_CFG_0_SHIFT);
+ else
+ val &= ~(ch << CDNS_JH8100_I2S_CTRL_TR_CFG_0_SHIFT);
+
+ writel(val, i2s->base + CDNS_JH8100_I2S_CTRL);
+}
+
+static irqreturn_t cdns_jh8100_i2s_irq_handler(int irq, void *data)
+{
+ struct cdns_jh8100_i2s_dev *i2s = data;
+ unsigned int val = readl(i2s->base + CDNS_JH8100_I2S_INTR_STAT);
+ irqreturn_t ret = IRQ_NONE;
+
+ cdns_jh8100_i2s_clear_int(i2s);
+
+ if (val & CDNS_JH8100_I2S_STAT_TX_UNDERRUN)
+ dev_err(i2s->dev, "TX underrun on channel %ld!\n",
+ FIELD_GET(CDNS_JH8100_I2S_STAT_UNDERR_CODE, val));
+
+ if (val & CDNS_JH8100_I2S_STAT_RX_OVERRUN)
+ dev_err(i2s->dev, "RX overrun on channel %ld!\n",
+ FIELD_GET(CDNS_JH8100_I2S_STAT_OVERR_CODE, val));
+
+ /* FIFO is empty when playback start and I2S also need to push the data. */
+ if (val & (CDNS_JH8100_I2S_STAT_TFIFO_AEMPTY | CDNS_JH8100_I2S_STAT_TFIFO_EMPTY)) {
+ cdns_jh8100_i2s_pcm_push_tx(i2s);
+ ret = IRQ_HANDLED;
+ }
+
+ if (val & CDNS_JH8100_I2S_STAT_RFIFO_AFULL) {
+ cdns_jh8100_i2s_pcm_pop_rx(i2s);
+ ret = IRQ_HANDLED;
+ }
+
+ return ret;
+}
+
+static void cdns_jh8100_i2s_enable_channel(struct cdns_jh8100_i2s_dev *i2s,
+ u32 ch, bool enable)
+{
+ unsigned int val = readl(i2s->base + CDNS_JH8100_I2S_CTRL);
+
+ /* Active High */
+ if (enable)
+ val |= ch;
+ else
+ val &= ~ch;
+
+ writel(val, i2s->base + CDNS_JH8100_I2S_CTRL);
+}
+
+/* Bit masking all interrupt requests */
+static void cdns_jh8100_i2s_set_all_irq_mask(struct cdns_jh8100_i2s_dev *i2s, bool mask)
+{
+ unsigned int val = readl(i2s->base + CDNS_JH8100_CID_CTRL);
+
+ /* Active Low: IRQ are masked */
+ if (mask)
+ val &= ~CDNS_JH8100_CID_CTRL_INTREQ_MASK;
+ else
+ val |= CDNS_JH8100_CID_CTRL_INTREQ_MASK;
+
+ writel(val, i2s->base + CDNS_JH8100_CID_CTRL);
+}
+
+static void cdns_jh8100_i2s_enable_channel_int(struct cdns_jh8100_i2s_dev *i2s,
+ u32 ch, bool enable)
+{
+ unsigned int val = readl(i2s->base + CDNS_JH8100_CID_CTRL);
+
+ /* Active High */
+ if (enable)
+ val |= (ch << CDNS_JH8100_CID_CTRL_I2S_MASK_0_SHIFT);
+ else
+ val &= ~(ch << CDNS_JH8100_CID_CTRL_I2S_MASK_0_SHIFT);
+
+ writel(val, i2s->base + CDNS_JH8100_CID_CTRL);
+}
+
+static void cdns_jh8100_i2s_channel_start(struct cdns_jh8100_i2s_dev *i2s,
+ u32 ch, bool is_transmit)
+{
+ cdns_jh8100_i2s_set_transmitter_receiver(i2s, ch, is_transmit);
+ cdns_jh8100_i2s_enable_channel(i2s, ch, true);
+ cdns_jh8100_i2s_set_channel_strobes(i2s, ch, true);
+ if (i2s->irq >= 0)
+ cdns_jh8100_i2s_enable_channel_int(i2s, ch, true);
+}
+
+static void cdns_jh8100_i2s_channel_stop(struct cdns_jh8100_i2s_dev *i2s, u32 ch)
+{
+ cdns_jh8100_i2s_enable_channel(i2s, ch, false);
+ if (i2s->irq >= 0)
+ cdns_jh8100_i2s_enable_channel_int(i2s, ch, false);
+}
+
+static int cdns_jh8100_i2s_start(struct cdns_jh8100_i2s_dev *i2s,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned char max_ch = i2s->max_channels;
+ unsigned char i2s_ch;
+ int i;
+
+ /* Each channel is stereo */
+ i2s_ch = runtime->channels / 2;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if ((i2s_ch + i2s->rx_using_channels) > max_ch) {
+ dev_err(i2s->dev,
+ "Max %d channels: using %d for RX, do not support %d for TX\n",
+ max_ch, i2s->rx_using_channels, i2s_ch);
+ return -ENOMEM;
+ }
+
+ i2s->tx_using_channels = i2s_ch;
+ /* Enable channels from 0 to 'max_ch' as tx */
+ for (i = 0; i < i2s_ch; i++)
+ cdns_jh8100_i2s_channel_start(i2s, CDNS_JH8100_I2S_CM_0 << i,
+ CDNS_JH8100_I2S_TC_TRANSMITTER);
+
+ } else {
+ if ((i2s_ch + i2s->tx_using_channels) > max_ch) {
+ dev_err(i2s->dev,
+ "Max %d channels: using %d for TX, do not support %d for RX\n",
+ max_ch, i2s->tx_using_channels, i2s_ch);
+ return -ENOMEM;
+ }
+
+ i2s->rx_using_channels = i2s_ch;
+ /* Enable channels from 'max_ch' to 0 as rx */
+ for (i = (max_ch - 1); i > (max_ch - i2s_ch - 1); i--) {
+ if (i < 0)
+ return -EINVAL;
+
+ cdns_jh8100_i2s_channel_start(i2s, CDNS_JH8100_I2S_CM_0 << i,
+ CDNS_JH8100_I2S_TC_RECEIVER);
+ }
+ }
+ cdns_jh8100_i2s_enable_clock(i2s, substream->stream);
+
+ if (i2s->irq >= 0)
+ cdns_jh8100_i2s_set_all_irq_mask(i2s, false);
+
+ cdns_jh8100_i2s_clear_int(i2s);
+
+ return 0;
+}
+
+static int cdns_jh8100_i2s_stop(struct cdns_jh8100_i2s_dev *i2s,
+ struct snd_pcm_substream *substream)
+{
+ unsigned char i2s_ch;
+ int i;
+
+ cdns_jh8100_i2s_clear_int(i2s);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ i2s_ch = i2s->tx_using_channels;
+ for (i = 0; i < i2s_ch; i++)
+ cdns_jh8100_i2s_channel_stop(i2s, (CDNS_JH8100_I2S_CM_0 << i));
+
+ i2s->tx_using_channels = 0;
+ } else {
+ unsigned char max_ch = i2s->max_channels;
+
+ i2s_ch = i2s->rx_using_channels;
+ for (i = (max_ch - 1); i > (max_ch - i2s_ch - 1); i--) {
+ if (i < 0)
+ return -EINVAL;
+
+ cdns_jh8100_i2s_channel_stop(i2s, (CDNS_JH8100_I2S_CM_0 << i));
+ }
+
+ i2s->rx_using_channels = 0;
+ }
+
+ if (i2s->irq >= 0 && !i2s->tx_using_channels && !i2s->rx_using_channels)
+ cdns_jh8100_i2s_set_all_irq_mask(i2s, true);
+
+ return 0;
+}
+
+static int cdns_jh8100_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct cdns_jh8100_i2s_dev *i2s = snd_soc_dai_get_drvdata(dai);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai_link *dai_link = rtd->dai_link;
+
+ if (i2s->irq < 0)
+ dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC;
+
+ return 0;
+}
+
+static void cdns_jh8100_i2s_config(struct cdns_jh8100_i2s_dev *i2s, int stream)
+{
+ unsigned int val = readl(i2s->base + CDNS_JH8100_I2S_SRR);
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ val &= ~(CDNS_JH8100_I2S_SRR_TRATE_MASK | CDNS_JH8100_I2S_SRR_TRESOLUTION_MASK);
+ val |= (FIELD_PREP(CDNS_JH8100_I2S_SRR_TRATE_MASK, i2s->sample_rate_param) |
+ FIELD_PREP(CDNS_JH8100_I2S_SRR_TRESOLUTION_MASK, (i2s->resolution - 1)));
+ } else {
+ val &= ~(CDNS_JH8100_I2S_SRR_RRATE_MASK | CDNS_JH8100_I2S_SRR_RRESOLUTION_MASK);
+ val |= (FIELD_PREP(CDNS_JH8100_I2S_SRR_RRATE_MASK, i2s->sample_rate_param) |
+ FIELD_PREP(CDNS_JH8100_I2S_SRR_RRESOLUTION_MASK, (i2s->resolution - 1)));
+ }
+
+ writel(val, i2s->base + CDNS_JH8100_I2S_SRR);
+}
+
+static int cdns_jh8100_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct cdns_jh8100_i2s_dev *i2s = snd_soc_dai_get_drvdata(dai);
+ unsigned int sample_rate = params_rate(params);
+ unsigned int channels = params_channels(params);
+ unsigned int fclk_hz = clk_get_rate(i2s->clks[2].clk); /* mclk_inner */
+ unsigned int bclk_rate;
+ int ret;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = &i2s->tx_dma_data;
+ else
+ dma_data = &i2s->rx_dma_data;
+
+ switch (sample_rate) {
+ case 8000:
+ bclk_rate = 512000;
+ break;
+ case 11025:
+ bclk_rate = 705600;
+ break;
+ case 16000:
+ bclk_rate = 1024000;
+ break;
+ case 22050:
+ bclk_rate = 1411200;
+ break;
+ case 32000:
+ bclk_rate = 2048000;
+ break;
+ case 44100:
+ bclk_rate = 2822400;
+ break;
+ case 48000:
+ bclk_rate = 3072000;
+ break;
+ default:
+ dev_err(dai->dev, "%d rate not supported\n", sample_rate);
+ return -EINVAL;
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ dma_data->addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ dma_data->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ break;
+ default:
+ dev_err(i2s->dev, "unsupported PCM fmt\n");
+ return -EINVAL;
+ }
+
+ ret = clk_set_rate(i2s->clks[0].clk, bclk_rate); /* bclk */
+ if (ret < 0) {
+ dev_err(i2s->dev, "Can't set i2s bclk: %d\n", ret);
+ return ret;
+ }
+
+ i2s->resolution = params_width(params);
+ i2s->sample_rate_param = fclk_hz / (sample_rate * channels * 32);
+ cdns_jh8100_i2s_config(i2s, substream->stream);
+
+ if (i2s->irq < 0)
+ snd_soc_dai_set_dma_data(dai, substream, dma_data);
+
+ return 0;
+}
+
+static int cdns_jh8100_i2s_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct cdns_jh8100_i2s_dev *i2s = snd_soc_dai_get_drvdata(dai);
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = cdns_jh8100_i2s_start(i2s, substream);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = cdns_jh8100_i2s_stop(i2s, substream);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int cdns_jh8100_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct cdns_jh8100_i2s_dev *i2s = snd_soc_dai_get_drvdata(cpu_dai);
+ int ret = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ i2s->tx_sync_ms_mode = CDNS_JH8100_I2S_MASTER_MODE;
+ i2s->rx_sync_ms_mode = CDNS_JH8100_I2S_MASTER_MODE;
+ cdns_jh8100_i2s_set_ms_mode(i2s);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ i2s->tx_sync_ms_mode = CDNS_JH8100_I2S_SLAVE_MODE;
+ i2s->rx_sync_ms_mode = CDNS_JH8100_I2S_SLAVE_MODE;
+ cdns_jh8100_i2s_set_ms_mode(i2s);
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ case SND_SOC_DAIFMT_CBS_CFM:
+ ret = -EINVAL;
+ break;
+ default:
+ dev_dbg(i2s->dev, "Invalid master/slave format\n");
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int cdns_jh8100_i2s_dai_probe(struct snd_soc_dai *dai)
+{
+ struct cdns_jh8100_i2s_dev *i2s = snd_soc_dai_get_drvdata(dai);
+ struct snd_dmaengine_dai_dma_data *tx = &i2s->tx_dma_data;
+ struct snd_dmaengine_dai_dma_data *rx = &i2s->rx_dma_data;
+
+ if (i2s->irq >= 0)
+ return 0;
+
+ /* Buswidth will be set by framework */
+ tx->addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED;
+ tx->addr = i2s->phybase + CDNS_JH8100_FIFO_MEM;
+ tx->maxburst = 16;
+ tx->fifo_size = 16;
+
+ rx->addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED;
+ rx->addr = i2s->phybase + CDNS_JH8100_FIFO_MEM;
+ rx->maxburst = 16;
+ rx->fifo_size = 16;
+
+ snd_soc_dai_init_dma_data(dai, tx, rx);
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver cdns_jh8100_i2s_component = {
+ .name = "cdns-jh8100-i2s",
+};
+
+static const struct snd_soc_dai_ops cdns_jh8100_i2s_dai_ops = {
+ .probe = cdns_jh8100_i2s_dai_probe,
+ .startup = cdns_jh8100_i2s_startup,
+ .hw_params = cdns_jh8100_i2s_hw_params,
+ .trigger = cdns_jh8100_i2s_trigger,
+ .set_fmt = cdns_jh8100_i2s_set_fmt,
+};
+
+static struct snd_soc_dai_driver cdns_jh8100_i2s_dai = {
+ .name = "cdns-jh8100-i2s",
+ .id = 0,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &cdns_jh8100_i2s_dai_ops,
+ .symmetric_rate = 1,
+};
+
+static int cdns_jh8100_i2s_runtime_suspend(struct device *dev)
+{
+ struct cdns_jh8100_i2s_dev *i2s = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(i2s->clks[1].clk); /* ICG clock */
+ return 0;
+}
+
+static int cdns_jh8100_i2s_runtime_resume(struct device *dev)
+{
+ struct cdns_jh8100_i2s_dev *i2s = dev_get_drvdata(dev);
+
+ return clk_prepare_enable(i2s->clks[1].clk); /* ICG clock */
+}
+
+static int cdns_jh8100_i2s_crg_init(struct cdns_jh8100_i2s_dev *i2s)
+{
+ struct reset_control *reset = devm_reset_control_get_exclusive(i2s->dev, NULL);
+ int ret;
+
+ if (IS_ERR(reset))
+ return dev_err_probe(i2s->dev, PTR_ERR(reset), "failed to get i2s resets\n");
+
+ i2s->clks[0].id = "bclk";
+ i2s->clks[1].id = "icg";
+ i2s->clks[2].id = "mclk_inner";
+
+ ret = devm_clk_bulk_get(i2s->dev, ARRAY_SIZE(i2s->clks), i2s->clks);
+ if (ret)
+ return dev_err_probe(i2s->dev, ret, "failed to get i2s clocks\n");
+
+ ret = clk_prepare_enable(i2s->clks[1].clk); /* ICG clock */
+ if (ret)
+ return dev_err_probe(i2s->dev, ret, "failed to enable icg clock\n");
+
+ ret = reset_control_deassert(reset);
+ if (ret)
+ goto rst_err;
+
+ return 0;
+
+rst_err:
+ clk_disable_unprepare(i2s->clks[1].clk);
+ return ret;
+}
+
+static int cdns_jh8100_i2s_init(struct cdns_jh8100_i2s_dev *i2s)
+{
+ int ret = cdns_jh8100_i2s_crg_init(i2s);
+ unsigned int tmp;
+
+ if (ret)
+ return ret;
+
+ /* Software reset i2s controller */
+ ret = cdns_jh8100_i2s_reset_mask(i2s, CDNS_JH8100_I2S_CTRL_SFR_RST_MASK);
+ if (ret) {
+ dev_err(i2s->dev, "Failed to reset I2S.\n");
+ return ret;
+ }
+
+ /* reset TX FIFO */
+ ret = cdns_jh8100_i2s_reset_mask(i2s, CDNS_JH8100_I2S_CTRL_TFIFO_RST_MASK);
+ if (ret) {
+ dev_err(i2s->dev, "Failed to reset tx fifo.\n");
+ return ret;
+ }
+
+ /* reset RX FIFO */
+ ret = cdns_jh8100_i2s_reset_mask(i2s, CDNS_JH8100_I2S_CTRL_RFIFO_RST_MASK);
+ if (ret) {
+ dev_err(i2s->dev, "Failed to reset rx fifo.\n");
+ return ret;
+ }
+
+ /* default master mode to init */
+ i2s->tx_sync_ms_mode = CDNS_JH8100_I2S_MASTER_MODE;
+ i2s->rx_sync_ms_mode = CDNS_JH8100_I2S_MASTER_MODE;
+ cdns_jh8100_i2s_set_ms_mode(i2s);
+
+ /* Should do it after setting Master/Slave mode */
+ cdns_jh8100_i2s_reset_txrx_unit(i2s);
+ cdns_jh8100_i2s_clear_int(i2s);
+
+ cdns_jh8100_i2s_set_aempty_afull_th(i2s, (CDNS_JH8100_I2S_FIFO_DEPTH / 4),
+ (CDNS_JH8100_I2S_FIFO_DEPTH / 4 * 3));
+ cdns_jh8100_i2s_set_fifo_mask(i2s, CDNS_JH8100_I2S_IT_TFIFO_AEMPTY |
+ CDNS_JH8100_I2S_IT_RFIFO_AFULL);
+
+ i2s->rx_using_channels = 0;
+ i2s->tx_using_channels = 0;
+
+ /* cdns,i2s-max-channels is optional property and default 8 */
+ ret = device_property_read_u32(i2s->dev, "cdns,i2s-max-channels", &tmp);
+ if (ret) {
+ i2s->max_channels = CDNS_JH8100_I2S_CHANNEL_MAX;
+ } else {
+ if (tmp > CDNS_JH8100_I2S_CHANNEL_MAX) {
+ dev_err(i2s->dev,
+ "The number %d of max channels from DTS is out of range!\n",
+ tmp);
+ return -EINVAL;
+ }
+
+ i2s->max_channels = tmp;
+ }
+
+ return 0;
+}
+
+static int cdns_jh8100_i2s_probe(struct platform_device *pdev)
+{
+ struct cdns_jh8100_i2s_dev *i2s;
+ struct resource *res;
+ int ret;
+
+ i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
+ if (!i2s) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ platform_set_drvdata(pdev, i2s);
+
+ i2s->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(i2s->base)) {
+ ret = PTR_ERR(i2s->base);
+ goto err;
+ }
+
+ i2s->dev = &pdev->dev;
+ i2s->phybase = res->start;
+
+ ret = cdns_jh8100_i2s_init(i2s);
+ if (ret)
+ goto err;
+
+ i2s->irq = platform_get_irq(pdev, 0);
+ if (i2s->irq >= 0) {
+ ret = devm_request_irq(&pdev->dev, i2s->irq, cdns_jh8100_i2s_irq_handler,
+ 0, pdev->name, i2s);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "request_irq failed\n");
+ goto err;
+ }
+ }
+
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &cdns_jh8100_i2s_component,
+ &cdns_jh8100_i2s_dai, 1);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "couldn't register component\n");
+ goto err;
+ }
+
+ if (i2s->irq >= 0)
+ ret = cdns_jh8100_i2s_pcm_register(pdev);
+ else
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+
+ if (ret) {
+ dev_err(&pdev->dev, "could not register pcm: %d\n", ret);
+ goto err;
+ }
+
+ pm_runtime_enable(&pdev->dev);
+ if (pm_runtime_enabled(&pdev->dev))
+ cdns_jh8100_i2s_runtime_suspend(&pdev->dev);
+
+ dev_info(&pdev->dev, "I2S supports %d stereo channels with %s.\n",
+ i2s->max_channels, ((i2s->irq < 0) ? "dma" : "interrupt"));
+
+ return 0;
+
+err:
+ return ret;
+}
+
+static int cdns_jh8100_i2s_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ cdns_jh8100_i2s_runtime_suspend(&pdev->dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id cdns_jh8100_i2s_of_match[] = {
+ { .compatible = "starfive,jh8100-i2s", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, cdns_jh8100_i2s_of_match);
+#endif
+
+static const struct dev_pm_ops cdns_jh8100_i2s_pm_ops = {
+ SET_RUNTIME_PM_OPS(cdns_jh8100_i2s_runtime_suspend,
+ cdns_jh8100_i2s_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver cdns_jh8100_i2s_driver = {
+ .probe = cdns_jh8100_i2s_probe,
+ .remove = cdns_jh8100_i2s_remove,
+ .driver = {
+ .name = "cdns-jh8100-i2s",
+ .of_match_table = of_match_ptr(cdns_jh8100_i2s_of_match),
+ .pm = &cdns_jh8100_i2s_pm_ops,
+ },
+};
+
+module_platform_driver(cdns_jh8100_i2s_driver);
+
+MODULE_AUTHOR("Xingyu Wu <[email protected]>");
+MODULE_AUTHOR("Walker Chen <[email protected]>");
+MODULE_DESCRIPTION("Cadence Multi-Channel I2S controller driver for StarFive JH8100 SoC");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/starfive/cdns-jh8100-i2s.h b/sound/soc/starfive/cdns-jh8100-i2s.h
new file mode 100644
index 000000000000..d4e0350de7bf
--- /dev/null
+++ b/sound/soc/starfive/cdns-jh8100-i2s.h
@@ -0,0 +1,171 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Cadence Multi-Channel I2S Controller driver header file for StarFive JH8100 SoC
+ *
+ * Copyright (c) 2023 StarFive Technology Co., Ltd.
+ * Author: Walker Chen <[email protected]>
+ * Xingyu Wu <[email protected]>
+ */
+
+#ifndef __CDNS_JH8100_I2S_MC_H
+#define __CDNS_JH8100_I2S_MC_H
+
+#include <linux/clk.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+
+#define CDNS_JH8100_I2S_FIFO_DEPTH 128
+#define CDNS_JH8100_FIFO_ACK_TIMEOUT_US 200
+#define CDNS_JH8100_I2S_CHANNEL_MAX 8
+
+/* I2S REGS */
+#define CDNS_JH8100_I2S_CTRL 0x00
+#define CDNS_JH8100_I2S_INTR_STAT 0x04
+#define CDNS_JH8100_I2S_SRR 0x08
+#define CDNS_JH8100_CID_CTRL 0x0c
+#define CDNS_JH8100_TFIFO_CTRL 0x18
+#define CDNS_JH8100_RFIFO_CTRL 0x1c
+#define CDNS_JH8100_FIFO_MEM 0x3c
+
+/*
+ * I2S_CTRL: I2S transceiver control register
+ */
+#define CDNS_JH8100_I2S_CTRL_TR_CFG_0_SHIFT 8
+#define CDNS_JH8100_I2S_CTRL_SFR_RST_MASK BIT(20)
+#define CDNS_JH8100_I2S_CTRL_T_MS_MASK BIT(21)
+#define CDNS_JH8100_I2S_CTRL_R_MS_MASK BIT(22)
+#define CDNS_JH8100_I2S_CTRL_TFIFO_RST_MASK BIT(23)
+#define CDNS_JH8100_I2S_CTRL_RFIFO_RST_MASK BIT(24)
+#define CDNS_JH8100_I2S_CTRL_TXRX_RST GENMASK(26, 25)
+
+/*
+ * I2S_INTR_STAT: I2S Interrupt status register
+ */
+#define CDNS_JH8100_I2S_STAT_TX_UNDERRUN BIT(0)
+#define CDNS_JH8100_I2S_STAT_UNDERR_CODE GENMASK(3, 1)
+#define CDNS_JH8100_I2S_STAT_RX_OVERRUN BIT(4)
+#define CDNS_JH8100_I2S_STAT_OVERR_CODE GENMASK(7, 5)
+#define CDNS_JH8100_I2S_STAT_TFIFO_EMPTY BIT(8)
+#define CDNS_JH8100_I2S_STAT_TFIFO_AEMPTY BIT(9)
+#define CDNS_JH8100_I2S_STAT_RFIFO_AFULL BIT(15)
+
+/*
+ * CID_CTRL: Clock strobes and interrupt masks control register
+ */
+#define CDNS_JH8100_CID_CTRL_STROBE_TX BIT(8)
+#define CDNS_JH8100_CID_CTRL_STROBE_RX BIT(9)
+#define CDNS_JH8100_CID_CTRL_INTREQ_MASK BIT(15)
+#define CDNS_JH8100_CID_CTRL_I2S_MASK_0_SHIFT 16
+
+/*
+ * I2S_SRR: Sample rate and resolution control register
+ */
+#define CDNS_JH8100_I2S_SRR_TRATE_MASK GENMASK(9, 0)
+#define CDNS_JH8100_I2S_SRR_RRATE_MASK GENMASK(25, 16)
+#define CDNS_JH8100_I2S_SRR_TRESOLUTION_MASK GENMASK(15, 11)
+#define CDNS_JH8100_I2S_SRR_RRESOLUTION_MASK GENMASK(31, 27)
+
+/*
+ * TFIFO_CTRL & RFIFO_CTRL: The FIFO thresholds control register
+ * AEMPTY: [15:0]
+ * AFULL: [31:16]
+ */
+#define CDNS_TRFIFO_CTRL_AFULL_THRESHOLD_SHIFT 16
+
+enum cdns_jh8100_i2s_channel_mask {
+ CDNS_JH8100_I2S_CM_0 = BIT(0),
+ CDNS_JH8100_I2S_CM_1 = BIT(1),
+ CDNS_JH8100_I2S_CM_2 = BIT(2),
+ CDNS_JH8100_I2S_CM_3 = BIT(3),
+ CDNS_JH8100_I2S_CM_4 = BIT(4),
+ CDNS_JH8100_I2S_CM_5 = BIT(5),
+ CDNS_JH8100_I2S_CM_6 = BIT(6),
+ CDNS_JH8100_I2S_CM_7 = BIT(7),
+ CDNS_JH8100_I2S_CM_ALL = GENMASK(7, 0),
+};
+
+enum i2s_int_type {
+ CDNS_JH8100_I2S_IT_TFIFO_EMPTY = BIT(24),
+ CDNS_JH8100_I2S_IT_TFIFO_AEMPTY = BIT(25),
+ CDNS_JH8100_I2S_IT_TFIFO_FULL = BIT(26),
+ CDNS_JH8100_I2S_IT_TFIFO_AFULL = BIT(27),
+ CDNS_JH8100_I2S_IT_RFIFO_EMPTY = BIT(28),
+ CDNS_JH8100_I2S_IT_RFIFO_AEMPTY = BIT(29),
+ CDNS_JH8100_I2S_IT_RFIFO_FULL = BIT(30),
+ CDNS_JH8100_I2S_IT_RFIFO_AFULL = BIT(31),
+ CDNS_JH8100_I2S_IT_ALL = GENMASK(31, 24),
+};
+
+enum cdns_jh8100_i2s_master_slave_mode {
+ CDNS_JH8100_I2S_SLAVE_MODE = 0,
+ CDNS_JH8100_I2S_MASTER_MODE = 1,
+};
+
+enum cdns_jh8100_i2s_transmit_config {
+ CDNS_JH8100_I2S_TC_RECEIVER = 0,
+ CDNS_JH8100_I2S_TC_TRANSMITTER = 1,
+};
+
+struct cdns_jh8100_i2s_dev {
+ struct device *dev;
+ struct clk_bulk_data clks[3];
+ void __iomem *base;
+ resource_size_t phybase; /* the physical memory */
+ int irq;
+ unsigned int sample_rate_param;
+ unsigned char resolution;
+ unsigned char max_channels /* up to CDNS_JH8100_I2S_CHANNEL_MAX */;
+ unsigned char tx_using_channels;
+ unsigned char rx_using_channels;
+
+ /*
+ * Master (value '1') or slave (value '0') configuration bit
+ * for unit synchronizing all transmitters(receivers) with I2S bus
+ */
+ bool tx_sync_ms_mode;
+ bool rx_sync_ms_mode;
+
+#if IS_ENABLED(CONFIG_SND_SOC_JH8100_CADENCE_I2S_PCM)
+ /* current playback substream. NULL if not playing.
+ *
+ * Access to that field is synchronized between the interrupt handler
+ * and userspace through RCU.
+ *
+ * Interrupt handler (threaded part) does PIO on substream data in RCU
+ * read-side critical section. Trigger callback sets and clears the
+ * pointer when the playback is started and stopped with
+ * rcu_assign_pointer. When userspace is about to free the playback
+ * stream in the pcm_close callback it synchronizes with the interrupt
+ * handler by means of synchronize_rcu call.
+ */
+ struct snd_pcm_substream __rcu *tx_substream;
+ struct snd_pcm_substream __rcu *rx_substream;
+ unsigned int (*tx_fn)(struct cdns_jh8100_i2s_dev *i2s,
+ struct snd_pcm_runtime *runtime, unsigned int tx_ptr,
+ bool *period_elapsed, snd_pcm_format_t format);
+ unsigned int (*rx_fn)(struct cdns_jh8100_i2s_dev *dev,
+ struct snd_pcm_runtime *runtime, unsigned int rx_ptr,
+ bool *period_elapsed, snd_pcm_format_t format);
+ snd_pcm_format_t format;
+ unsigned int tx_ptr; /* next frame index in the sample buffer */
+ unsigned int rx_ptr;
+#endif
+
+ struct snd_dmaengine_dai_dma_data tx_dma_data;
+ struct snd_dmaengine_dai_dma_data rx_dma_data;
+};
+
+#if IS_ENABLED(CONFIG_SND_SOC_JH8100_CADENCE_I2S_PCM)
+void cdns_jh8100_i2s_pcm_push_tx(struct cdns_jh8100_i2s_dev *dev);
+void cdns_jh8100_i2s_pcm_pop_rx(struct cdns_jh8100_i2s_dev *dev);
+int cdns_jh8100_i2s_pcm_register(struct platform_device *pdev);
+#else
+void cdns_jh8100_i2s_pcm_push_tx(struct cdns_jh8100_i2s_dev *dev) { }
+void cdns_jh8100_i2s_pcm_pop_rx(struct cdns_jh8100_i2s_dev *dev) { }
+int cdns_jh8100_i2s_pcm_register(struct platform_device *pdev)
+{
+ return -EINVAL;
+}
+#endif
+
+#endif /* __CDNS_JH8100_I2S_MC_H */
--
2.25.1



2023-12-21 13:00:34

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] ASoC: starfive: Add drivers of Cadence Multi-Channel I2S Controller

On Thu, Dec 21, 2023 at 11:32:23AM +0800, Xingyu Wu wrote:

> sound/soc/starfive/cdns-jh8100-i2s-pcm.c | 262 ++++++++
> sound/soc/starfive/cdns-jh8100-i2s.c | 727 +++++++++++++++++++++++
> sound/soc/starfive/cdns-jh8100-i2s.h | 171 ++++++

If this is a Cadence IP why is the entire driver SoC specific?


Attachments:
(No filename) (321.00 B)
signature.asc (499.00 B)
Download all attachments

2023-12-22 09:20:04

by Xingyu Wu

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] ASoC: starfive: Add drivers of Cadence Multi-Channel I2S Controller

On 2023/12/21 20:55, Mark Brown wrote:
> On Thu, Dec 21, 2023 at 11:32:23AM +0800, Xingyu Wu wrote:
>
>> sound/soc/starfive/cdns-jh8100-i2s-pcm.c | 262 ++++++++
>> sound/soc/starfive/cdns-jh8100-i2s.c | 727 +++++++++++++++++++++++
>> sound/soc/starfive/cdns-jh8100-i2s.h | 171 ++++++
>
> If this is a Cadence IP why is the entire driver SoC specific?

This a new driver about this Cadence I2S controller. Should I create a new folder and file like this:

sound/soc/cdns/cdns-i2s*

and I use different compatible to support other SoC like JH8100?

Best regards,
Xingyu Wu

2023-12-22 12:40:46

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] ASoC: starfive: Add drivers of Cadence Multi-Channel I2S Controller

On Fri, Dec 22, 2023 at 05:11:17PM +0800, Xingyu Wu wrote:
> On 2023/12/21 20:55, Mark Brown wrote:
> > On Thu, Dec 21, 2023 at 11:32:23AM +0800, Xingyu Wu wrote:

> >> sound/soc/starfive/cdns-jh8100-i2s-pcm.c | 262 ++++++++
> >> sound/soc/starfive/cdns-jh8100-i2s.c | 727 +++++++++++++++++++++++
> >> sound/soc/starfive/cdns-jh8100-i2s.h | 171 ++++++

> > If this is a Cadence IP why is the entire driver SoC specific?

> This a new driver about this Cadence I2S controller. Should I create a new folder and file like this:

> sound/soc/cdns/cdns-i2s*

> and I use different compatible to support other SoC like JH8100?

Yes, that seems more what I'd expect - with a fallback compatible for
things that don't need any special quirks.


Attachments:
(No filename) (764.00 B)
signature.asc (499.00 B)
Download all attachments

2023-12-22 21:34:49

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] ASoC: starfive: Add drivers of Cadence Multi-Channel I2S Controller

Hi Xingyu,

kernel test robot noticed the following build warnings:

[auto build test WARNING on broonie-sound/for-next]
[also build test WARNING on robh/for-next linus/master v6.7-rc6 next-20231222]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/Xingyu-Wu/dt-bindings-ASoC-Add-Cadence-I2S-controller-for-StarFive-JH8100-SoC/20231222-172628
base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
patch link: https://lore.kernel.org/r/20231221033223.73201-3-xingyu.wu%40starfivetech.com
patch subject: [PATCH v1 2/2] ASoC: starfive: Add drivers of Cadence Multi-Channel I2S Controller
config: parisc-allyesconfig (https://download.01.org/0day-ci/archive/20231223/[email protected]/config)
compiler: hppa-linux-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231223/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All warnings (new ones prefixed by >>):

>> sound/soc/starfive/cdns-jh8100-i2s.c:519:12: warning: 'cdns_jh8100_i2s_runtime_resume' defined but not used [-Wunused-function]
519 | static int cdns_jh8100_i2s_runtime_resume(struct device *dev)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


vim +/cdns_jh8100_i2s_runtime_resume +519 sound/soc/starfive/cdns-jh8100-i2s.c

518
> 519 static int cdns_jh8100_i2s_runtime_resume(struct device *dev)
520 {
521 struct cdns_jh8100_i2s_dev *i2s = dev_get_drvdata(dev);
522
523 return clk_prepare_enable(i2s->clks[1].clk); /* ICG clock */
524 }
525

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

2023-12-23 12:22:24

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] ASoC: starfive: Add drivers of Cadence Multi-Channel I2S Controller

Hi Xingyu,

kernel test robot noticed the following build warnings:

[auto build test WARNING on broonie-sound/for-next]
[also build test WARNING on robh/for-next linus/master v6.7-rc6 next-20231222]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/Xingyu-Wu/dt-bindings-ASoC-Add-Cadence-I2S-controller-for-StarFive-JH8100-SoC/20231222-172628
base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
patch link: https://lore.kernel.org/r/20231221033223.73201-3-xingyu.wu%40starfivetech.com
patch subject: [PATCH v1 2/2] ASoC: starfive: Add drivers of Cadence Multi-Channel I2S Controller
config: sparc64-randconfig-r071-20231223 (https://download.01.org/0day-ci/archive/20231223/[email protected]/config)
compiler: sparc64-linux-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231223/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All warnings (new ones prefixed by >>):

In file included from sound/soc/starfive/cdns-jh8100-i2s.c:18:
>> sound/soc/starfive/cdns-jh8100-i2s.h:163:6: warning: no previous prototype for 'cdns_jh8100_i2s_pcm_push_tx' [-Wmissing-prototypes]
163 | void cdns_jh8100_i2s_pcm_push_tx(struct cdns_jh8100_i2s_dev *dev) { }
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
>> sound/soc/starfive/cdns-jh8100-i2s.h:164:6: warning: no previous prototype for 'cdns_jh8100_i2s_pcm_pop_rx' [-Wmissing-prototypes]
164 | void cdns_jh8100_i2s_pcm_pop_rx(struct cdns_jh8100_i2s_dev *dev) { }
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
>> sound/soc/starfive/cdns-jh8100-i2s.h:165:5: warning: no previous prototype for 'cdns_jh8100_i2s_pcm_register' [-Wmissing-prototypes]
165 | int cdns_jh8100_i2s_pcm_register(struct platform_device *pdev)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~


vim +/cdns_jh8100_i2s_pcm_push_tx +163 sound/soc/starfive/cdns-jh8100-i2s.h

157
158 #if IS_ENABLED(CONFIG_SND_SOC_JH8100_CADENCE_I2S_PCM)
159 void cdns_jh8100_i2s_pcm_push_tx(struct cdns_jh8100_i2s_dev *dev);
160 void cdns_jh8100_i2s_pcm_pop_rx(struct cdns_jh8100_i2s_dev *dev);
161 int cdns_jh8100_i2s_pcm_register(struct platform_device *pdev);
162 #else
> 163 void cdns_jh8100_i2s_pcm_push_tx(struct cdns_jh8100_i2s_dev *dev) { }
> 164 void cdns_jh8100_i2s_pcm_pop_rx(struct cdns_jh8100_i2s_dev *dev) { }
> 165 int cdns_jh8100_i2s_pcm_register(struct platform_device *pdev)
166 {
167 return -EINVAL;
168 }
169 #endif
170

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

2023-12-29 08:34:39

by Xingyu Wu

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] ASoC: starfive: Add drivers of Cadence Multi-Channel I2S Controller

On 2023/12/22 20:39, Mark Brown wrote:
> On Fri, Dec 22, 2023 at 05:11:17PM +0800, Xingyu Wu wrote:
>> On 2023/12/21 20:55, Mark Brown wrote:
>> > On Thu, Dec 21, 2023 at 11:32:23AM +0800, Xingyu Wu wrote:
>
>> >> sound/soc/starfive/cdns-jh8100-i2s-pcm.c | 262 ++++++++
>> >> sound/soc/starfive/cdns-jh8100-i2s.c | 727 +++++++++++++++++++++++
>> >> sound/soc/starfive/cdns-jh8100-i2s.h | 171 ++++++
>
>> > If this is a Cadence IP why is the entire driver SoC specific?
>
>> This a new driver about this Cadence I2S controller. Should I create a new folder and file like this:
>
>> sound/soc/cdns/cdns-i2s*
>
>> and I use different compatible to support other SoC like JH8100?
>
> Yes, that seems more what I'd expect - with a fallback compatible for
> things that don't need any special quirks.

Okay, I will modify these in next version.

Thanks,
Xingyu Wu