[PATCH 4/4] ASoC: cdns: Add Cadence I2S-MC controller driver

joakim.zhang at cixtech.com joakim.zhang at cixtech.com
Tue Sep 22 04:21:34 PDT 2026


From: Joakim Zhang <joakim.zhang at cixtech.com>

Add support for the Cadence I2S-MC multi-channel controller found in
the CIX SKY1 audio subsystem.

The controller moves up to 16 channels through a configurable set of
its 8 data pins, each pin carrying two slots. Wired-out pins may sit
at any pin index and are described by cix,pin-out-num, cix,pin-rx-mask
and cix,pin-tx-mask. The driver exposes a transmit DAI and a receive
DAI which can act as clock provider or consumer independently, and
streams with fewer channels than the wired pins narrow the enabled
pin mask at runtime.

The driver registers the dmaengine PCM helper, selects the audio
reference clock parent for the 8 kHz or 11.025 kHz sample rate family
and keeps the minimum functional-clock to bit-clock ratio required
for safe clock domain crossing, with an extra margin cycle when the
bit clock is sourced externally. Runtime and system suspend/resume
restore the registers through the regcache.

Signed-off-by: Joakim Zhang <joakim.zhang at cixtech.com>
---
 sound/soc/cdns/Kconfig       |   10 +
 sound/soc/cdns/Makefile      |    3 +
 sound/soc/cdns/cdns-i2s-mc.c | 1149 ++++++++++++++++++++++++++++++++++
 3 files changed, 1162 insertions(+)
 create mode 100644 sound/soc/cdns/cdns-i2s-mc.c

diff --git a/sound/soc/cdns/Kconfig b/sound/soc/cdns/Kconfig
index 631e2e0764cb..aa5d35992a81 100644
--- a/sound/soc/cdns/Kconfig
+++ b/sound/soc/cdns/Kconfig
@@ -9,3 +9,13 @@ config SND_SOC_CDNS_I2S_SC
 	  controller found in the CIX SKY1 audio subsystem. It provides
 	  full-duplex stereo playback and capture in standard I2S and
 	  TDM modes with up to 16 slots.
+
+config SND_SOC_CDNS_I2S_MC
+	tristate "Cadence I2S-MC controller support"
+	depends on ARCH_CIX || COMPILE_TEST
+	select SND_SOC_GENERIC_DMAENGINE_PCM
+	help
+	  Say Y or M if you want to add support for the Cadence I2S-MC
+	  multi-channel controller found in the CIX SKY1 audio subsystem.
+	  It provides multi-channel playback and capture through a
+	  configurable number of I2S data pins.
diff --git a/sound/soc/cdns/Makefile b/sound/soc/cdns/Makefile
index 60e69993116d..a99050b41c59 100644
--- a/sound/soc/cdns/Makefile
+++ b/sound/soc/cdns/Makefile
@@ -2,3 +2,6 @@
 
 obj-$(CONFIG_SND_SOC_CDNS_I2S_SC) += snd-soc-cdns-i2s-sc.o
 snd-soc-cdns-i2s-sc-objs := cdns-i2s-sc.o
+
+obj-$(CONFIG_SND_SOC_CDNS_I2S_MC) += snd-soc-cdns-i2s-mc.o
+snd-soc-cdns-i2s-mc-objs := cdns-i2s-mc.o
diff --git a/sound/soc/cdns/cdns-i2s-mc.c b/sound/soc/cdns/cdns-i2s-mc.c
new file mode 100644
index 000000000000..f3415175554c
--- /dev/null
+++ b/sound/soc/cdns/cdns-i2s-mc.c
@@ -0,0 +1,1149 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2026 Cix Technology Group Co., Ltd.
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+/* Special Function Registers */
+/* Control Register */
+#define I2S_CTRL				0x00
+#define I2S_CTRL_RSYNC_LOOP_BACK		BIT(28)
+#define I2S_CTRL_TSYNC_LOOP_BACK		BIT(27)
+#define I2S_CTRL_RSYNC_RST			BIT(26)
+#define I2S_CTRL_TSYNC_RST			BIT(25)
+#define I2S_CTRL_RFIFO_RST			BIT(24)
+#define I2S_CTRL_TFIFO_RST			BIT(23)
+#define I2S_CTRL_R_MS				BIT(22)
+#define I2S_CTRL_T_MS				BIT(21)
+#define I2S_CTRL_SFR_RST			BIT(20)
+#define I2S_CTRL_LOOP_BACK_6_7			BIT(19)
+#define I2S_CTRL_LOOP_BACK_4_5			BIT(18)
+#define I2S_CTRL_LOOP_BACK_2_3			BIT(17)
+#define I2S_CTRL_LOOP_BACK_0_1			BIT(16)
+#define I2S_CTRL_TR_CFG				GENMASK(15, 8)
+#define I2S_CTRL_I2S_EN				GENMASK(7, 0)
+
+/* Interrupt Status Register */
+#define I2S_INTR_STAT				0x04
+#define I2S_INTR_STAT_RFIFO_AFULL		BIT(15)
+#define I2S_INTR_STAT_RFIFO_FULL		BIT(14)
+#define I2S_INTR_STAT_RFIFO_AEMPTY		BIT(13)
+#define I2S_INTR_STAT_RFIFO_EMPTY		BIT(12)
+#define I2S_INTR_STAT_TFIFO_AFULL		BIT(11)
+#define I2S_INTR_STAT_TFIFO_FULL		BIT(10)
+#define I2S_INTR_STAT_TFIFO_AEMPTY		BIT(9)
+#define I2S_INTR_STAT_TFIFO_EMPTY		BIT(8)
+#define I2S_INTR_STAT_OVERR_CODE		GENMASK(7, 5)
+#define I2S_INTR_STAT_RDATA_OVERR		BIT(4)
+#define I2S_INTR_STAT_UNDERR_CODE		GENMASK(3, 1)
+#define I2S_INTR_STAT_TDATA_UNDERR		BIT(0)
+
+/* Sample Rate And Resolution Control Register */
+#define I2S_SRR					0x08
+#define I2S_SRR_RRESOLUTION			GENMASK(31, 27)
+#define I2S_SRR_RSAMPLE_RATE			GENMASK(26, 16)
+#define I2S_SRR_TRESOLUTION			GENMASK(15, 11)
+#define I2S_SRR_TSAMPLE_RATE			GENMASK(10, 0)
+
+/* Clock Strobes And Interrupt Masks Control Register */
+#define I2S_CID_CTRL				0x0c
+#define I2S_CID_CTRL_RFIFO_AFULL_MASK		BIT(31)
+#define I2S_CID_CTRL_RFIFO_FULL_MASK		BIT(30)
+#define I2S_CID_CTRL_RFIFO_AEMPTY_MASK		BIT(29)
+#define I2S_CID_CTRL_RFIFO_EMPTY_MASK		BIT(28)
+#define I2S_CID_CTRL_TFIFO_AFULL_MASK		BIT(27)
+#define I2S_CID_CTRL_TFIFO_FULL_MASK		BIT(26)
+#define I2S_CID_CTRL_TFIFO_AEMPTY_MASK		BIT(25)
+#define I2S_CID_CTRL_TFIFO_EMPTY_MASK		BIT(24)
+#define I2S_CID_CTRL_I2S_MASK			GENMASK(23, 16)
+#define I2S_CID_CTRL_INTREQ_MASK		BIT(15)
+#define I2S_CID_CTRL_STROBE_RS			BIT(9)
+#define I2S_CID_CTRL_STROBE_TS			BIT(8)
+#define I2S_CID_CTRL_I2S_STROBE			GENMASK(7, 0)
+
+/* Transmit FIFO Level Status Register, read only */
+#define I2S_TFIFO_STAT				0x10
+
+/* Receive FIFO Level Status Register, read only */
+#define I2S_RFIFO_STAT				0x14
+
+/* Transmit FIFO Thresholds Control Register */
+#define I2S_TFIFO_CTRL				0x18
+#define I2S_TFIFO_CTRL_TAFULL_THRESHOLD		GENMASK(31, 16)
+#define I2S_TFIFO_CTRL_TAEMPTY_THRESHOLD	GENMASK(15, 0)
+
+/* Receive FIFO Thresholds Control Register */
+#define I2S_RFIFO_CTRL				0x1c
+#define I2S_RFIFO_CTRL_RAFULL_THRESHOLD		GENMASK(31, 16)
+#define I2S_RFIFO_CTRL_RAEMPTY_THRESHOLD	GENMASK(15, 0)
+
+/* Device Configuration Register */
+#define I2S_DEV_CONF				0x20
+#define I2S_DEV_CONF_REC_WS_DSP_MODE		BIT(11)
+#define I2S_DEV_CONF_REC_DATA_WS_DEL		BIT(10)
+#define I2S_DEV_CONF_REC_I2S_ALIGN_LR		BIT(9)
+#define I2S_DEV_CONF_REC_APB_ALIGN_LR		BIT(8)
+#define I2S_DEV_CONF_REC_WS_POLAR		BIT(7)
+#define I2S_DEV_CONF_REC_SCK_POLAR		BIT(6)
+#define I2S_DEV_CONF_TRAN_WS_DSP_MODE		BIT(5)
+#define I2S_DEV_CONF_TRAN_DATA_WS_DEL		BIT(4)
+#define I2S_DEV_CONF_TRAN_I2S_ALIGN_LR		BIT(3)
+#define I2S_DEV_CONF_TRAN_APB_ALIGN_LR		BIT(2)
+#define I2S_DEV_CONF_TRAN_WS_POLAR		BIT(1)
+#define I2S_DEV_CONF_TRAN_SCK_POLAR		BIT(0)
+
+/* Status Register, read only */
+#define I2S_POLL_STAT				0x24
+#define I2S_POLL_STAT_RX_OVERRUN		BIT(6)
+#define I2S_POLL_STAT_RX_AFULL			BIT(5)
+#define I2S_POLL_STAT_RX_FULL			BIT(4)
+#define I2S_POLL_STAT_TX_UNDERRUN		BIT(2)
+#define I2S_POLL_STAT_TX_AEMPTY			BIT(1)
+#define I2S_POLL_STAT_TX_EMPTY			BIT(0)
+
+/* Transmit And Recevie FIFOs Address */
+#define I2S_FIFO_ADDRESS			0x3c
+
+/*
+ * The I2S functional clock must run at a minimum ratio above the audio
+ * bit clock to safely cross clock domains; a bit clock sourced
+ * externally (slave mode) needs one more cycle of margin. See the
+ * controller reference manual, section 3.3 "Clocks and Clock Domain
+ * Crossing".
+ */
+#define I2S_CLK_RATIO_MIN_MASTER		6
+#define I2S_CLK_RATIO_MIN_SLAVE			7
+
+#define DRV_NAME	"cdns-i2s-mc"
+
+enum {
+	AUDIO_CLK0,
+	AUDIO_CLK2,
+	AUDIO_CLK_NUM,
+};
+
+static const char *cdns_i2s_mc_clk_pll_names[AUDIO_CLK_NUM] = {
+	[AUDIO_CLK0] = "audio-clk0",
+	[AUDIO_CLK2] = "audio-clk2",
+};
+
+enum {
+	I2S_MC_AIF1,
+	I2S_MC_AIF2,
+};
+
+struct cdns_i2s_mc_devtype_data {
+	u32 data_width;
+	u32 rfifo_aempty_threshold;
+	u32 rfifo_afull_threshold;
+	u32 tfifo_aempty_threshold;
+	u32 tfifo_afull_threshold;
+};
+
+struct cdns_i2s_mc_priv {
+	struct device *dev;
+	struct regmap *regmap;
+	struct reset_control *i2s_rst;
+
+	int irq;
+
+	struct clk *clk_hst;
+	struct clk *clk_i2s;
+	struct clk *clks[AUDIO_CLK_NUM];
+
+	struct snd_dmaengine_dai_dma_data capture_dma_data;
+	struct snd_dmaengine_dai_dma_data playback_dma_data;
+
+	const struct cdns_i2s_mc_devtype_data *devtype_data;
+
+	u8 pin_out_num;
+	u8 pin_rx_mask;
+	u8 pin_rx_mask_adjust;
+	u8 pin_tx_mask;
+	u8 pin_tx_mask_adjust;
+};
+
+static int cdns_i2s_mc_clks_enable(struct cdns_i2s_mc_priv *i2s_mc_priv)
+{
+	int ret;
+
+	ret = clk_prepare_enable(i2s_mc_priv->clk_hst);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(i2s_mc_priv->clk_i2s);
+	if (ret)
+		clk_disable_unprepare(i2s_mc_priv->clk_hst);
+
+	return ret;
+}
+
+static void cdns_i2s_mc_clks_disable(struct cdns_i2s_mc_priv *i2s_mc_priv)
+{
+	clk_disable_unprepare(i2s_mc_priv->clk_hst);
+	clk_disable_unprepare(i2s_mc_priv->clk_i2s);
+}
+
+static void cdns_i2s_mc_adjust_pin_config(u8 *pin_mask, u32 slots)
+{
+	u8 mask = 0, num = 0;
+	int i;
+
+	/*
+	 * Wired-out pins may sit at any index among the 8 data pins, so
+	 * scan the whole mask and keep the lowest pins until enough slots
+	 * are covered.
+	 */
+	for (i = 0; i < BITS_PER_BYTE; i++) {
+		if (*pin_mask & (0x1 << i)) {
+			mask |= (0x1 << i);
+			if (++num == slots / 2) {
+				*pin_mask = mask;
+				break;
+			}
+		}
+	}
+}
+
+static void cdns_i2s_mc_tx_config(struct cdns_i2s_mc_priv *i2s_mc_priv, bool on)
+{
+	u32 irq_mask = 0, clk_mask = 0, i2s_mask = 0;
+
+	irq_mask |= FIELD_PREP(I2S_CID_CTRL_I2S_MASK,
+			       i2s_mc_priv->pin_tx_mask_adjust);
+
+	clk_mask |= FIELD_PREP(I2S_CID_CTRL_I2S_STROBE,
+			       i2s_mc_priv->pin_tx_mask_adjust) |
+		    I2S_CID_CTRL_STROBE_TS;
+
+	i2s_mask |= FIELD_PREP(I2S_CTRL_I2S_EN, i2s_mc_priv->pin_tx_mask_adjust);
+
+	if (on) {
+		/* Transmitter data underrun interrupt unmask */
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, irq_mask, irq_mask);
+
+		/* Transmitter clock enable */
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, clk_mask, 0);
+
+		/*
+		 * Transmitter enable
+		 * Transmitter synchronizing unit out of reset
+		 */
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL,
+				   i2s_mask | I2S_CTRL_TSYNC_RST,
+				   i2s_mask | I2S_CTRL_TSYNC_RST);
+	} else {
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL,
+				   i2s_mask | I2S_CTRL_TSYNC_RST, 0);
+
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, clk_mask, clk_mask);
+
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, irq_mask, 0);
+	}
+}
+
+static void cdns_i2s_mc_rx_config(struct cdns_i2s_mc_priv *i2s_mc_priv, bool on)
+{
+	u32 irq_mask = 0, clk_mask = 0, i2s_mask = 0;
+
+	irq_mask |= FIELD_PREP(I2S_CID_CTRL_I2S_MASK,
+			       i2s_mc_priv->pin_rx_mask_adjust);
+
+	clk_mask |= FIELD_PREP(I2S_CID_CTRL_I2S_STROBE,
+			       i2s_mc_priv->pin_rx_mask_adjust) |
+		    I2S_CID_CTRL_STROBE_RS;
+
+	i2s_mask |= FIELD_PREP(I2S_CTRL_I2S_EN, i2s_mc_priv->pin_rx_mask_adjust);
+
+	if (on) {
+		/* Receiver data overrun interrupt unmask */
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, irq_mask, irq_mask);
+
+		/* Receiver clock enable */
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, clk_mask, 0);
+
+		/*
+		 * Receiver enable
+		 * Receiver synchronizing unit out of reset
+		 */
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL,
+				   i2s_mask | I2S_CTRL_RSYNC_RST,
+				   i2s_mask | I2S_CTRL_RSYNC_RST);
+	} else {
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL,
+				   i2s_mask | I2S_CTRL_RSYNC_RST, 0);
+
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, clk_mask, clk_mask);
+
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, irq_mask, 0);
+	}
+}
+
+static irqreturn_t cdns_i2s_mc_isr(int irq, void *devid)
+{
+	struct cdns_i2s_mc_priv *i2s_mc_priv = (struct cdns_i2s_mc_priv *)devid;
+	struct device *dev = i2s_mc_priv->dev;
+	u32 stat;
+
+	regmap_read(i2s_mc_priv->regmap, I2S_INTR_STAT, &stat);
+
+	stat &= I2S_INTR_STAT_TDATA_UNDERR | I2S_INTR_STAT_RDATA_OVERR;
+	if (!stat)
+		return IRQ_NONE;
+
+	/* Clear the status */
+	regmap_update_bits(i2s_mc_priv->regmap, I2S_INTR_STAT, stat, 0);
+
+	/* Transmitter status */
+	if (stat & I2S_INTR_STAT_TDATA_UNDERR)
+		dev_dbg(dev, "isr: tx data underrun\n");
+
+	/* Receiver status */
+	if (stat & I2S_INTR_STAT_RDATA_OVERR)
+		dev_dbg(dev, "isr: rx data overrun\n");
+
+	return IRQ_HANDLED;
+}
+
+static int cdns_i2s_mc_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+	struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct device *dev = i2s_mc_priv->dev;
+	u32 ctrl = 0, dev_conf = 0;
+
+	dev_dbg(dev, "fmt = 0x%x\n", fmt);
+
+	/*
+	 * Configure the data pin channel directions, shared by both DAIs:
+	 * a set TR_CFG bit marks the pin as a transmitter channel, a clear
+	 * one as a receiver channel. Program the combined value so the
+	 * write is idempotent and independent of the set_fmt call order.
+	 */
+	ctrl |= FIELD_PREP(I2S_CTRL_TR_CFG,
+			   i2s_mc_priv->pin_tx_mask |
+			   (u8)~i2s_mc_priv->pin_rx_mask);
+
+	if (cpu_dai->id == I2S_MC_AIF1) {
+		/* DAI hardware signal polarity */
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			/* both normal clocks */
+			dev_conf |= I2S_DEV_CONF_TRAN_SCK_POLAR;
+			dev_conf &= ~I2S_DEV_CONF_TRAN_WS_POLAR;
+			break;
+		case SND_SOC_DAIFMT_NB_IF:
+			/* invert frame clock */
+			dev_conf |= I2S_DEV_CONF_TRAN_SCK_POLAR;
+			dev_conf |= I2S_DEV_CONF_TRAN_WS_POLAR;
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			/* invert bit clock */
+			dev_conf &= ~I2S_DEV_CONF_TRAN_SCK_POLAR;
+			dev_conf &= ~I2S_DEV_CONF_TRAN_WS_POLAR;
+			break;
+		case SND_SOC_DAIFMT_IB_IF:
+			/* invert both clocks */
+			dev_conf &= ~I2S_DEV_CONF_TRAN_SCK_POLAR;
+			dev_conf |= I2S_DEV_CONF_TRAN_WS_POLAR;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		/* DAI hardware audio formats */
+		switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+		case SND_SOC_DAIFMT_I2S:
+			dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR;
+			dev_conf &= ~(I2S_DEV_CONF_TRAN_APB_ALIGN_LR |
+				      I2S_DEV_CONF_TRAN_DATA_WS_DEL |
+				      I2S_DEV_CONF_TRAN_WS_DSP_MODE);
+			break;
+		case SND_SOC_DAIFMT_RIGHT_J:
+			dev_conf |= I2S_DEV_CONF_TRAN_DATA_WS_DEL;
+			dev_conf &= ~(I2S_DEV_CONF_TRAN_I2S_ALIGN_LR |
+				      I2S_DEV_CONF_TRAN_APB_ALIGN_LR |
+				      I2S_DEV_CONF_TRAN_WS_DSP_MODE);
+			break;
+		case SND_SOC_DAIFMT_LEFT_J:
+			dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | I2S_DEV_CONF_TRAN_DATA_WS_DEL;
+			dev_conf &= ~(I2S_DEV_CONF_TRAN_APB_ALIGN_LR |
+				      I2S_DEV_CONF_TRAN_WS_DSP_MODE);
+			break;
+		case SND_SOC_DAIFMT_DSP_A:
+			dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | I2S_DEV_CONF_TRAN_DATA_WS_DEL |
+				    I2S_DEV_CONF_TRAN_WS_DSP_MODE;
+			dev_conf &= ~I2S_DEV_CONF_TRAN_APB_ALIGN_LR;
+			break;
+		case SND_SOC_DAIFMT_DSP_B:
+			dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | I2S_DEV_CONF_TRAN_WS_DSP_MODE;
+			dev_conf &= ~(I2S_DEV_CONF_TRAN_APB_ALIGN_LR |
+				      I2S_DEV_CONF_TRAN_DATA_WS_DEL);
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		/* DAI hardware clock masters */
+		switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+		case SND_SOC_DAIFMT_CBC_CFC:
+			ctrl &= ~I2S_CTRL_T_MS;
+			break;
+		case SND_SOC_DAIFMT_CBP_CFP:
+			ctrl |= I2S_CTRL_T_MS;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_DEV_CONF,
+				   I2S_DEV_CONF_TRAN_SCK_POLAR | I2S_DEV_CONF_TRAN_WS_POLAR |
+				   I2S_DEV_CONF_TRAN_APB_ALIGN_LR | I2S_DEV_CONF_TRAN_I2S_ALIGN_LR |
+				   I2S_DEV_CONF_TRAN_DATA_WS_DEL |
+				   I2S_DEV_CONF_TRAN_WS_DSP_MODE, dev_conf);
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL,
+				   I2S_CTRL_T_MS | I2S_CTRL_TR_CFG, ctrl);
+	} else if (cpu_dai->id == I2S_MC_AIF2) {
+		/* DAI hardware signal polarity */
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			/* both normal clocks */
+			dev_conf |= I2S_DEV_CONF_REC_SCK_POLAR;
+			dev_conf &= ~I2S_DEV_CONF_REC_WS_POLAR;
+			break;
+		case SND_SOC_DAIFMT_NB_IF:
+			/* invert frame clock */
+			dev_conf |= I2S_DEV_CONF_REC_SCK_POLAR;
+			dev_conf |= I2S_DEV_CONF_REC_WS_POLAR;
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			/* invert bit clock */
+			dev_conf &= ~I2S_DEV_CONF_REC_SCK_POLAR;
+			dev_conf &= ~I2S_DEV_CONF_REC_WS_POLAR;
+			break;
+		case SND_SOC_DAIFMT_IB_IF:
+			/* invert both clocks */
+			dev_conf &= ~I2S_DEV_CONF_REC_SCK_POLAR;
+			dev_conf |= I2S_DEV_CONF_REC_WS_POLAR;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		/* DAI hardware audio formats */
+		switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+		case SND_SOC_DAIFMT_I2S:
+			dev_conf |= I2S_DEV_CONF_REC_I2S_ALIGN_LR;
+			dev_conf &= ~(I2S_DEV_CONF_REC_APB_ALIGN_LR | I2S_DEV_CONF_REC_DATA_WS_DEL |
+				      I2S_DEV_CONF_REC_WS_DSP_MODE);
+			break;
+		case SND_SOC_DAIFMT_RIGHT_J:
+			dev_conf |= I2S_DEV_CONF_REC_DATA_WS_DEL;
+			dev_conf &= ~(I2S_DEV_CONF_REC_I2S_ALIGN_LR |
+				      I2S_DEV_CONF_REC_APB_ALIGN_LR |
+				      I2S_DEV_CONF_REC_WS_DSP_MODE);
+			break;
+		case SND_SOC_DAIFMT_LEFT_J:
+			dev_conf |= I2S_DEV_CONF_REC_I2S_ALIGN_LR | I2S_DEV_CONF_REC_DATA_WS_DEL;
+			dev_conf &= ~(I2S_DEV_CONF_REC_APB_ALIGN_LR | I2S_DEV_CONF_REC_WS_DSP_MODE);
+			break;
+		case SND_SOC_DAIFMT_DSP_A:
+			dev_conf |= I2S_DEV_CONF_REC_I2S_ALIGN_LR | I2S_DEV_CONF_REC_DATA_WS_DEL |
+				    I2S_DEV_CONF_REC_WS_DSP_MODE;
+			dev_conf &= ~I2S_DEV_CONF_REC_APB_ALIGN_LR;
+			break;
+		case SND_SOC_DAIFMT_DSP_B:
+			dev_conf |= I2S_DEV_CONF_REC_I2S_ALIGN_LR | I2S_DEV_CONF_REC_WS_DSP_MODE;
+			dev_conf &= ~(I2S_DEV_CONF_REC_APB_ALIGN_LR | I2S_DEV_CONF_REC_DATA_WS_DEL);
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		/* DAI hardware clock masters */
+		switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+		case SND_SOC_DAIFMT_CBC_CFC:
+			ctrl &= ~I2S_CTRL_R_MS;
+			break;
+		case SND_SOC_DAIFMT_CBP_CFP:
+			ctrl |= I2S_CTRL_R_MS;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_DEV_CONF,
+				   I2S_DEV_CONF_REC_SCK_POLAR | I2S_DEV_CONF_REC_WS_POLAR |
+				   I2S_DEV_CONF_REC_APB_ALIGN_LR | I2S_DEV_CONF_REC_I2S_ALIGN_LR |
+				   I2S_DEV_CONF_REC_DATA_WS_DEL |
+				   I2S_DEV_CONF_REC_WS_DSP_MODE, dev_conf);
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL,
+				   I2S_CTRL_R_MS | I2S_CTRL_TR_CFG, ctrl);
+	} else {
+		dev_err(i2s_mc_priv->dev, "Invalid dai id\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int cdns_i2s_mc_prepare(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *cpu_dai)
+{
+	struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai);
+	u32 ctrl = 0, fifo = 0;
+
+	/*
+	 * FIFO reset should meet two requirements
+	 * 1) after strobe_ts and strobe_rs clock on
+	 * 2) before dma ready to receive trigger
+	 */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		/* Transmitter clock enable */
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL,
+				   I2S_CID_CTRL_STROBE_TS, 0);
+
+		/*
+		 * Transmitter FIFO reset
+		 * When '0', transmit FIFO pointers are reset to zero. Threshold level for
+		 * this FIFO is unchanged. This bit is automatically set to '1' after one
+		 * clock cycle if TX FIFO reset has been acknowledged.
+		 * Deassert then assert this bit here, since I2S_CTRL register is not
+		 * volatile, would not read from hardware any longer. If not, it would
+		 * clear tx fifo every time when write this register.
+		 */
+		ctrl &= ~I2S_CTRL_TFIFO_RST;
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL,
+				   I2S_CTRL_TFIFO_RST, ctrl);
+		ctrl |= I2S_CTRL_TFIFO_RST;
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL,
+				   I2S_CTRL_TFIFO_RST, ctrl);
+
+		/* Transmitter FIFO threshold set */
+		fifo |= FIELD_PREP(I2S_TFIFO_CTRL_TAEMPTY_THRESHOLD,
+				   i2s_mc_priv->devtype_data->tfifo_aempty_threshold) |
+			FIELD_PREP(I2S_TFIFO_CTRL_TAFULL_THRESHOLD,
+				   i2s_mc_priv->devtype_data->tfifo_afull_threshold);
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_TFIFO_CTRL,
+				   I2S_TFIFO_CTRL_TAEMPTY_THRESHOLD |
+				   I2S_TFIFO_CTRL_TAFULL_THRESHOLD, fifo);
+	} else {
+		/* Receiver clock enable */
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL,
+				   I2S_CID_CTRL_STROBE_RS, 0);
+
+		/*
+		 * Receiver FIFO reset
+		 * When '0', receive FIFO pointers are reset to zero. Threshold level for
+		 * this FIFO is unchanged. This bit is automatically set to '1' after one
+		 * clock cycle if RX FIFO reset has been acknowledged.
+		 * Deassert then assert this bit here, since I2S_CTRL register is not
+		 * volatile, would not read from hardware any longer. If not, it would
+		 * clear rx fifo every time when write this register.
+		 */
+		ctrl &= ~I2S_CTRL_RFIFO_RST;
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL,
+				   I2S_CTRL_RFIFO_RST, ctrl);
+		ctrl |= I2S_CTRL_RFIFO_RST;
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL,
+				   I2S_CTRL_RFIFO_RST, ctrl);
+
+		/* Receiver FIFO threshold set */
+		fifo |= FIELD_PREP(I2S_RFIFO_CTRL_RAEMPTY_THRESHOLD,
+				   i2s_mc_priv->devtype_data->rfifo_aempty_threshold) |
+			FIELD_PREP(I2S_RFIFO_CTRL_RAFULL_THRESHOLD,
+				   i2s_mc_priv->devtype_data->rfifo_afull_threshold);
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_RFIFO_CTRL,
+				   I2S_RFIFO_CTRL_RAEMPTY_THRESHOLD |
+				   I2S_RFIFO_CTRL_RAFULL_THRESHOLD, fifo);
+	}
+
+	/*
+	 * Enable global interrupt mask, for both transmitter and receiver, use
+	 * individual interrupt masks
+	 */
+	regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL,
+			   I2S_CID_CTRL_INTREQ_MASK, I2S_CID_CTRL_INTREQ_MASK);
+
+	return 0;
+}
+
+static int cdns_i2s_mc_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *cpu_dai)
+{
+	struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct device *dev = i2s_mc_priv->dev;
+	u32 rate, sample_rate = 0;
+	u32 slots, slot_width, resolution, ctrl;
+	unsigned long i2s_clk_rate;
+	u8 pin_tx_num, pin_rx_num;
+	struct clk *clk_parent;
+	bool is_master_mode;
+	int ret;
+
+	rate = params_rate(params);
+	slot_width = i2s_mc_priv->devtype_data->data_width;
+	dev_dbg(dev, "rate = %d, slot_width = %d\n", rate, slot_width);
+
+	/* switch clk mux to select the appropriate clk parent */
+	if (rate % 8000 == 0) {
+		/* Sampling rate is a multiple of 8KHz, select "audio-clk0" */
+		clk_parent = i2s_mc_priv->clks[AUDIO_CLK0];
+
+		ret = clk_set_parent(i2s_mc_priv->clk_i2s, clk_parent);
+	} else if (rate % 11025 == 0) {
+		/* Sampling rate is a multiple of 11.025KHz, select "audio-clk2" */
+		clk_parent = i2s_mc_priv->clks[AUDIO_CLK2];
+
+		ret = clk_set_parent(i2s_mc_priv->clk_i2s, clk_parent);
+	} else {
+		dev_err(i2s_mc_priv->dev, "Invalid sample rate\n");
+		return -EINVAL;
+	}
+	if (ret) {
+		dev_err(i2s_mc_priv->dev, "Failed to set i2s clock parent\n");
+		return ret;
+	}
+
+	i2s_clk_rate = clk_get_rate(i2s_mc_priv->clk_i2s);
+	dev_dbg(dev, "i2s clk rate = %lu\n", i2s_clk_rate);
+
+	regmap_read(i2s_mc_priv->regmap, I2S_CTRL, &ctrl);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		is_master_mode = ctrl & I2S_CTRL_T_MS;
+	else
+		is_master_mode = ctrl & I2S_CTRL_R_MS;
+
+	if (is_master_mode) {
+		if (i2s_clk_rate < rate * 2 * slot_width * I2S_CLK_RATIO_MIN_MASTER) {
+			dev_err(i2s_mc_priv->dev,
+				"clk freq %lu is too low, must >= (%d * %d * %d * %d = %d)\n",
+				i2s_clk_rate, rate, slot_width, 2, I2S_CLK_RATIO_MIN_MASTER,
+				rate * slot_width * 2 * I2S_CLK_RATIO_MIN_MASTER);
+			return -EINVAL;
+		}
+
+		sample_rate = DIV_ROUND_CLOSEST(i2s_clk_rate, (rate * 2 * slot_width));
+	} else {
+		if (i2s_clk_rate < rate * 2 * slot_width * I2S_CLK_RATIO_MIN_SLAVE) {
+			dev_err(i2s_mc_priv->dev,
+				"clk freq %lu is too low, must >= (%d * %d * %d * %d = %d)\n",
+				i2s_clk_rate, rate, slot_width, 2, I2S_CLK_RATIO_MIN_SLAVE,
+				rate * slot_width * 2 * I2S_CLK_RATIO_MIN_SLAVE);
+			return -EINVAL;
+		}
+	}
+
+	slots = params_channels(params);
+	resolution = params_width(params);
+	dev_dbg(dev, "slots = %d, resolution = %d\n", slots, resolution);
+
+	/* Each data pin carries two slots, so the channel count must be even */
+	if (slots % 2) {
+		dev_err(i2s_mc_priv->dev, "Channel number must be a multiple of two\n");
+		return -EINVAL;
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		i2s_mc_priv->pin_tx_mask_adjust = i2s_mc_priv->pin_tx_mask;
+
+		pin_tx_num = hweight8(i2s_mc_priv->pin_tx_mask);
+		if (slots > 2 * pin_tx_num) {
+			dev_warn(i2s_mc_priv->dev,
+				 "Transmit channel number is larger than hardware config\n");
+			return -EOPNOTSUPP;
+		} else if (slots < 2 * pin_tx_num) {
+			cdns_i2s_mc_adjust_pin_config(&i2s_mc_priv->pin_tx_mask_adjust, slots);
+		}
+
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_SRR, I2S_SRR_TSAMPLE_RATE,
+				   FIELD_PREP(I2S_SRR_TSAMPLE_RATE, sample_rate));
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_SRR, I2S_SRR_TRESOLUTION,
+				   FIELD_PREP(I2S_SRR_TRESOLUTION, (resolution - 1)));
+	} else {
+		i2s_mc_priv->pin_rx_mask_adjust = i2s_mc_priv->pin_rx_mask;
+
+		pin_rx_num = hweight8(i2s_mc_priv->pin_rx_mask);
+		if (slots > 2 * pin_rx_num) {
+			dev_warn(i2s_mc_priv->dev,
+				 "Receive channel number is larger than hardware config\n");
+			return -EOPNOTSUPP;
+		} else if (slots < 2 * pin_rx_num) {
+			cdns_i2s_mc_adjust_pin_config(&i2s_mc_priv->pin_rx_mask_adjust, slots);
+		}
+
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_SRR, I2S_SRR_RSAMPLE_RATE,
+				   FIELD_PREP(I2S_SRR_RSAMPLE_RATE, sample_rate));
+		regmap_update_bits(i2s_mc_priv->regmap, I2S_SRR, I2S_SRR_RRESOLUTION,
+				   FIELD_PREP(I2S_SRR_RRESOLUTION, (resolution - 1)));
+	}
+
+	return 0;
+}
+
+static int cdns_i2s_mc_trigger(struct snd_pcm_substream *substream,
+			       int cmd, struct snd_soc_dai *cpu_dai)
+{
+	struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai);
+
+	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)
+			cdns_i2s_mc_tx_config(i2s_mc_priv, true);
+		else
+			cdns_i2s_mc_rx_config(i2s_mc_priv, true);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			cdns_i2s_mc_tx_config(i2s_mc_priv, false);
+		else
+			cdns_i2s_mc_rx_config(i2s_mc_priv, false);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int cdns_i2s_mc_dai_probe(struct snd_soc_dai *cpu_dai)
+{
+	struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai);
+	u32 ctrl = 0, cid_ctrl = 0;
+
+	/*
+	 * Transceiver disable
+	 * Transceiver clock disable
+	 * All interrupts masked
+	 * Reset for transmitter synchronizing unit
+	 * Reset for receiver synchronizing unit
+	 */
+	ctrl |= FIELD_PREP(I2S_CTRL_I2S_EN, 0);
+	ctrl &= ~(I2S_CTRL_TSYNC_RST | I2S_CTRL_RSYNC_RST);
+	cid_ctrl |= I2S_CID_CTRL_STROBE_TS | I2S_CID_CTRL_STROBE_RS;
+	cid_ctrl &= ~I2S_CID_CTRL_INTREQ_MASK;
+
+	regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, I2S_CTRL_I2S_EN, ctrl);
+	regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL,
+			   I2S_CID_CTRL_STROBE_TS | I2S_CID_CTRL_STROBE_RS |
+			   I2S_CID_CTRL_INTREQ_MASK, cid_ctrl);
+
+	snd_soc_dai_init_dma_data(cpu_dai, &i2s_mc_priv->playback_dma_data,
+				  &i2s_mc_priv->capture_dma_data);
+
+	snd_soc_dai_set_drvdata(cpu_dai, i2s_mc_priv);
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops cdns_i2s_mc_dai_ops = {
+	.probe = cdns_i2s_mc_dai_probe,
+	.set_fmt = cdns_i2s_mc_set_fmt,
+
+	.hw_params = cdns_i2s_mc_hw_params,
+	.prepare = cdns_i2s_mc_prepare,
+	.trigger = cdns_i2s_mc_trigger,
+};
+
+static struct snd_soc_dai_driver cdns_i2s_mc_dai[] = {
+	{
+		.name = "i2s-mc-aif1",
+		.id = I2S_MC_AIF1,
+		.playback = {
+			.stream_name = "I2S-MC-Playback",
+			.channels_min = 2,
+			.channels_max = 16,
+			.rates = SNDRV_PCM_RATE_8000_192000,
+			.formats = (SNDRV_PCM_FMTBIT_S16_LE |
+				    SNDRV_PCM_FMTBIT_S24_LE |
+				    SNDRV_PCM_FMTBIT_S32_LE),
+		},
+		.ops = &cdns_i2s_mc_dai_ops,
+	},
+	{
+		.name = "i2s-mc-aif2",
+		.id = I2S_MC_AIF2,
+		.capture = {
+			.stream_name = "I2S-MC-Capture",
+			.channels_min = 2,
+			.channels_max = 16,
+			.rates = SNDRV_PCM_RATE_8000_192000,
+			.formats = (SNDRV_PCM_FMTBIT_S16_LE |
+				    SNDRV_PCM_FMTBIT_S24_LE |
+				    SNDRV_PCM_FMTBIT_S32_LE),
+		},
+		.ops = &cdns_i2s_mc_dai_ops,
+	},
+};
+
+static const struct snd_soc_component_driver cdns_i2s_mc_component = {
+	.name = DRV_NAME,
+	.legacy_dai_naming = 1,
+};
+
+static const struct reg_default cdns_i2s_mc_reg_defaults[] = {
+	{I2S_CTRL, 0x01900000},
+	{I2S_INTR_STAT, 0x00003300},
+	{I2S_SRR, 0x00000000},
+	{I2S_CID_CTRL, 0x00000000},
+	{I2S_TFIFO_STAT, 0x00000000},
+	{I2S_RFIFO_STAT, 0x00000000},
+	{I2S_TFIFO_CTRL, 0x000f0000},
+	{I2S_RFIFO_CTRL, 0x000f0000},
+	{I2S_DEV_CONF, 0x00000208},
+	{I2S_POLL_STAT, 0x00000003},
+};
+
+static bool cdns_i2s_mc_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case I2S_CTRL:
+	case I2S_INTR_STAT:
+	case I2S_SRR:
+	case I2S_CID_CTRL:
+	case I2S_TFIFO_STAT:
+	case I2S_RFIFO_STAT:
+	case I2S_TFIFO_CTRL:
+	case I2S_RFIFO_CTRL:
+	case I2S_DEV_CONF:
+	case I2S_POLL_STAT:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool cdns_i2s_mc_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case I2S_CTRL:
+	case I2S_INTR_STAT:
+	case I2S_SRR:
+	case I2S_CID_CTRL:
+	case I2S_TFIFO_CTRL:
+	case I2S_RFIFO_CTRL:
+	case I2S_DEV_CONF:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool cdns_i2s_mc_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case I2S_INTR_STAT:
+	case I2S_TFIFO_STAT:
+	case I2S_RFIFO_STAT:
+	case I2S_POLL_STAT:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config cdns_i2s_mc_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+
+	.max_register = I2S_POLL_STAT,
+	.reg_defaults = cdns_i2s_mc_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(cdns_i2s_mc_reg_defaults),
+	.readable_reg = cdns_i2s_mc_readable_reg,
+	.writeable_reg = cdns_i2s_mc_writeable_reg,
+	.volatile_reg = cdns_i2s_mc_volatile_reg,
+	.cache_type = REGCACHE_FLAT,
+};
+
+static int cdns_i2s_get_pin_config(struct platform_device *pdev,
+				   struct cdns_i2s_mc_priv *i2s_mc_priv)
+{
+	u8 rxtx_mask;
+	int ret;
+
+	ret = device_property_read_u8(&pdev->dev,
+				      "cix,pin-out-num", &i2s_mc_priv->pin_out_num);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to get pin-out-num: %d\n", ret);
+		return ret;
+	}
+
+	if (!i2s_mc_priv->pin_out_num || i2s_mc_priv->pin_out_num > BITS_PER_BYTE) {
+		dev_err(&pdev->dev, "Pin out number should be in range [1, 8]\n");
+		return -EINVAL;
+	}
+
+	ret = device_property_read_u8(&pdev->dev,
+				      "cix,pin-rx-mask", &i2s_mc_priv->pin_rx_mask);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to get pin-rx-mask: %d\n", ret);
+		return ret;
+	}
+
+	ret = device_property_read_u8(&pdev->dev,
+				      "cix,pin-tx-mask", &i2s_mc_priv->pin_tx_mask);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to get pin-tx-mask: %d\n", ret);
+		return ret;
+	}
+
+	rxtx_mask = i2s_mc_priv->pin_rx_mask & i2s_mc_priv->pin_tx_mask;
+	if (rxtx_mask) {
+		dev_err(&pdev->dev, "Pin configuration for transmitter and receiver is conflict\n");
+		return -EINVAL;
+	}
+
+	rxtx_mask = i2s_mc_priv->pin_rx_mask | i2s_mc_priv->pin_tx_mask;
+	if (hweight8(rxtx_mask) > i2s_mc_priv->pin_out_num) {
+		dev_err(&pdev->dev, "Pin configuration is out of range\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void cdns_i2s_mc_rst(struct cdns_i2s_mc_priv *i2s_mc_priv)
+{
+	/* reset */
+	reset_control_assert(i2s_mc_priv->i2s_rst);
+
+	usleep_range(1, 2);
+
+	/* release reset */
+	reset_control_deassert(i2s_mc_priv->i2s_rst);
+}
+
+static int cdns_i2s_mc_probe(struct platform_device *pdev)
+{
+	struct cdns_i2s_mc_priv *i2s_mc_priv;
+	struct resource *res;
+	void __iomem *base;
+	int i, irq, ret;
+
+	i2s_mc_priv = devm_kzalloc(&pdev->dev, sizeof(*i2s_mc_priv), GFP_KERNEL);
+	if (!i2s_mc_priv)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, i2s_mc_priv);
+
+	base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	i2s_mc_priv->dev = &pdev->dev;
+	i2s_mc_priv->devtype_data = device_get_match_data(&pdev->dev);
+
+	i2s_mc_priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+						    &cdns_i2s_mc_regmap_config);
+	if (IS_ERR(i2s_mc_priv->regmap))
+		return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->regmap),
+				     "Failed to initialize managed register map\n");
+
+	i2s_mc_priv->clk_hst = devm_clk_get(&pdev->dev, "hst");
+	if (IS_ERR(i2s_mc_priv->clk_hst))
+		return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->clk_hst),
+				     "Failed to get hst clock\n");
+
+	i2s_mc_priv->clk_i2s = devm_clk_get(&pdev->dev, "i2s");
+	if (IS_ERR(i2s_mc_priv->clk_i2s))
+		return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->clk_i2s),
+				     "Failed to get i2s clock\n");
+
+	for (i = 0; i < AUDIO_CLK_NUM; i++) {
+		i2s_mc_priv->clks[i] = devm_clk_get(&pdev->dev,
+						    cdns_i2s_mc_clk_pll_names[i]);
+		if (IS_ERR(i2s_mc_priv->clks[i]))
+			return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->clks[i]),
+					     "Failed to get %s clock\n",
+					     cdns_i2s_mc_clk_pll_names[i]);
+	}
+
+	i2s_mc_priv->i2s_rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+	if (IS_ERR(i2s_mc_priv->i2s_rst))
+		return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->i2s_rst),
+				     "Failed to get reset control\n");
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+	i2s_mc_priv->irq = irq;
+
+	ret = devm_request_irq(&pdev->dev, irq, cdns_i2s_mc_isr, 0,
+			       dev_name(&pdev->dev), i2s_mc_priv);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
+		return ret;
+	}
+
+	ret = cdns_i2s_get_pin_config(pdev, i2s_mc_priv);
+	if (ret)
+		return ret;
+
+	i2s_mc_priv->playback_dma_data.addr = res->start + I2S_FIFO_ADDRESS;
+	/* Buswidth will be set by framework at runtime */
+	i2s_mc_priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED;
+	i2s_mc_priv->playback_dma_data.maxburst = 4;
+
+	i2s_mc_priv->capture_dma_data.addr = res->start + I2S_FIFO_ADDRESS;
+	/* Buswidth will be set by framework at runtime */
+	i2s_mc_priv->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED;
+	i2s_mc_priv->capture_dma_data.maxburst = 4;
+
+	ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "Failed to register dmaengine component\n");
+
+	pm_runtime_get_noresume(&pdev->dev);
+	pm_runtime_set_active(&pdev->dev);
+	pm_runtime_enable(&pdev->dev);
+
+	ret = cdns_i2s_mc_clks_enable(i2s_mc_priv);
+	if (ret) {
+		dev_err_probe(&pdev->dev, ret, "Failed to enable clocks\n");
+		pm_runtime_disable(&pdev->dev);
+		pm_runtime_put_noidle(&pdev->dev);
+		return ret;
+	}
+
+	cdns_i2s_mc_rst(i2s_mc_priv);
+
+	/*
+	 * Let pm_runtime_put_sync() disable the clocks, and take regcache effect.
+	 * If CONFIG_PM is not enabled, the clock will stay powered.
+	 */
+	pm_runtime_put_sync(&pdev->dev);
+
+	ret = devm_snd_soc_register_component(&pdev->dev, &cdns_i2s_mc_component,
+					      cdns_i2s_mc_dai, ARRAY_SIZE(cdns_i2s_mc_dai));
+	if (ret) {
+		dev_err_probe(&pdev->dev, ret, "Failed to register soc component\n");
+		pm_runtime_disable(&pdev->dev);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void cdns_i2s_mc_remove(struct platform_device *pdev)
+{
+	if (!pm_runtime_status_suspended(&pdev->dev))
+		pm_runtime_force_suspend(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+}
+
+static int __maybe_unused cdns_i2s_mc_runtime_suspend(struct device *dev)
+{
+	struct cdns_i2s_mc_priv *i2s_mc_priv = dev_get_drvdata(dev);
+
+	regcache_cache_only(i2s_mc_priv->regmap, true);
+	disable_irq(i2s_mc_priv->irq);
+	cdns_i2s_mc_clks_disable(i2s_mc_priv);
+
+	return 0;
+}
+
+static int __maybe_unused cdns_i2s_mc_runtime_resume(struct device *dev)
+{
+	struct cdns_i2s_mc_priv *i2s_mc_priv = dev_get_drvdata(dev);
+	int ret;
+
+	ret = cdns_i2s_mc_clks_enable(i2s_mc_priv);
+	if (ret) {
+		dev_err(dev, "Failed to enable clocks:%d\n", ret);
+		return ret;
+	}
+
+	cdns_i2s_mc_rst(i2s_mc_priv);
+
+	enable_irq(i2s_mc_priv->irq);
+
+	regcache_cache_only(i2s_mc_priv->regmap, false);
+	regcache_mark_dirty(i2s_mc_priv->regmap);
+
+	ret = regcache_sync(i2s_mc_priv->regmap);
+	if (ret) {
+		dev_err(dev, "Failed to sync regcache: %d\n", ret);
+		disable_irq(i2s_mc_priv->irq);
+		regcache_cache_only(i2s_mc_priv->regmap, true);
+		cdns_i2s_mc_clks_disable(i2s_mc_priv);
+	}
+
+	return ret;
+}
+
+static int __maybe_unused cdns_i2s_mc_system_suspend(struct device *dev)
+{
+	int ret;
+
+	ret = pm_runtime_force_suspend(dev);
+	if (ret)
+		return ret;
+
+	pinctrl_pm_select_sleep_state(dev);
+
+	return 0;
+}
+
+static int __maybe_unused cdns_i2s_mc_system_resume(struct device *dev)
+{
+	int ret;
+
+	pinctrl_pm_select_default_state(dev);
+
+	ret = pm_runtime_force_resume(dev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct dev_pm_ops cdns_i2s_mc_pm_ops = {
+	SET_RUNTIME_PM_OPS(cdns_i2s_mc_runtime_suspend,
+			   cdns_i2s_mc_runtime_resume, NULL)
+	SET_SYSTEM_SLEEP_PM_OPS(cdns_i2s_mc_system_suspend,
+				cdns_i2s_mc_system_resume)
+};
+
+static const struct cdns_i2s_mc_devtype_data sky1_devtype_data = {
+	.data_width = 32,
+	.rfifo_aempty_threshold = 4,
+	.rfifo_afull_threshold = 12,
+	.tfifo_aempty_threshold = 4,
+	.tfifo_afull_threshold = 12,
+};
+
+static const struct of_device_id cdns_i2s_mc_of_match[] = {
+	{ .compatible = "cix,sky1-i2s-mc", .data = &sky1_devtype_data},
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, cdns_i2s_mc_of_match);
+
+static struct platform_driver cdns_i2s_mc_driver = {
+	.probe = cdns_i2s_mc_probe,
+	.remove = cdns_i2s_mc_remove,
+	.driver = {
+		.name = DRV_NAME,
+		.pm = &cdns_i2s_mc_pm_ops,
+		.of_match_table = cdns_i2s_mc_of_match,
+	},
+};
+module_platform_driver(cdns_i2s_mc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Joakim Zhang <joakim.zhang at cixtech.com>");
+MODULE_DESCRIPTION("Cadence I2S-MC Controller Driver");
-- 
2.50.1




More information about the linux-arm-kernel mailing list