[1/6] ASoC: samsung: i2s: TDM Support for CPU DAI driver

Message ID 20221014102151.108539-2-p.rajanbabu@samsung.com
State New
Headers
Series ASoC: samsung: fsd: audio support for FSD SoC |

Commit Message

Padmanabhan Rajanbabu Oct. 14, 2022, 10:21 a.m. UTC
  Add support to configure samsung I2S CPU DAI in TDM mode.

Signed-off-by: Chandrasekar R <rcsekar@samsung.com>
Signed-off-by: Padmanabhan Rajanbabu <p.rajanbabu@samsung.com>
---
 sound/soc/samsung/i2s-regs.h | 15 +++++++
 sound/soc/samsung/i2s.c      | 84 +++++++++++++++++++++++++++++++++++-
 2 files changed, 98 insertions(+), 1 deletion(-)
  

Patch

diff --git a/sound/soc/samsung/i2s-regs.h b/sound/soc/samsung/i2s-regs.h
index b4b5d6053503..cb2be4a3b70b 100644
--- a/sound/soc/samsung/i2s-regs.h
+++ b/sound/soc/samsung/i2s-regs.h
@@ -154,4 +154,19 @@ 
 #define I2SSIZE_TRNMSK		(0xffff)
 #define I2SSIZE_SHIFT		(16)
 
+#define TDM_LRCLK_WIDTH_SHIFT	12
+#define TDM_LRCLK_WIDTH_MASK	0xFF
+#define TDM_RX_SLOTS_SHIFT	8
+#define TDM_RX_SLOTS_MASK	7
+#define TDM_TX_SLOTS_SHIFT	4
+#define TDM_TX_SLOTS_MASK	7
+#define TDM_MODE_MASK		1
+#define TDM_MODE_SHIFT		1
+#define TDM_MODE_DSPA		0
+#define TDM_MODE_DSPB		1
+#define TDM_ENABLE		(1 << 0)
+
+/* stereo default */
+#define TDM_DEFAULT_SLOT_NUM_DIVIDER	2
+
 #endif /* __SND_SOC_SAMSUNG_I2S_REGS_H */
diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c
index 9505200f3d11..fb806b0af6ab 100644
--- a/sound/soc/samsung/i2s.c
+++ b/sound/soc/samsung/i2s.c
@@ -117,6 +117,8 @@  struct samsung_i2s_priv {
 	struct clk *clk_table[3];
 	struct clk_onecell_data clk_data;
 
+	int tdm_slots;
+
 	/* Spinlock protecting member fields below */
 	spinlock_t lock;
 
@@ -625,15 +627,19 @@  static int i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 	struct samsung_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
 	struct i2s_dai *i2s = to_info(dai);
 	int lrp_shift, sdf_shift, sdf_mask, lrp_rlow, mod_slave;
+	int tdm_mod_mask, tdm_mod_shift;
+	u32 tdm = 0, tdm_tmp = 0;
 	u32 mod, tmp = 0;
 	unsigned long flags;
 
 	lrp_shift = priv->variant_regs->lrp_off;
 	sdf_shift = priv->variant_regs->sdf_off;
+	tdm_mod_shift = TDM_MODE_SHIFT;
 	mod_slave = 1 << priv->variant_regs->mss_off;
 
 	sdf_mask = MOD_SDF_MASK << sdf_shift;
 	lrp_rlow = MOD_LR_RLOW << lrp_shift;
+	tdm_mod_mask = TDM_MODE_MASK << tdm_mod_shift;
 
 	/* Format is priority */
 	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
@@ -648,6 +654,20 @@  static int i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 	case SND_SOC_DAIFMT_I2S:
 		tmp |= (MOD_SDF_IIS << sdf_shift);
 		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		if (!(priv->quirks & QUIRK_SUPPORTS_TDM)) {
+			dev_err(&i2s->pdev->dev, "TDM mode not supported\n");
+			return -EINVAL;
+		}
+		tdm_tmp |= (TDM_MODE_DSPA << tdm_mod_shift);
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		if (!(priv->quirks & QUIRK_SUPPORTS_TDM)) {
+			dev_err(&i2s->pdev->dev, "TDM mode not supported\n");
+			return -EINVAL;
+		}
+		tdm_tmp |= (TDM_MODE_DSPB << tdm_mod_shift);
+		break;
 	default:
 		dev_err(&i2s->pdev->dev, "Format not supported\n");
 		return -EINVAL;
@@ -693,12 +713,17 @@  static int i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 	pm_runtime_get_sync(dai->dev);
 	spin_lock_irqsave(&priv->lock, flags);
 	mod = readl(priv->addr + I2SMOD);
+
+	if (priv->quirks & QUIRK_SUPPORTS_TDM)
+		tdm = readl(priv->addr + I2STDM);
 	/*
 	 * Don't change the I2S mode if any controller is active on this
 	 * channel.
 	 */
 	if (any_active(i2s) &&
-		((mod & (sdf_mask | lrp_rlow | mod_slave)) != tmp)) {
+		(((mod & (sdf_mask | lrp_rlow | mod_slave)) != tmp) ||
+		((priv->quirks & QUIRK_SUPPORTS_TDM) &&
+		 ((tdm & tdm_mod_mask) != tdm_tmp)))) {
 		spin_unlock_irqrestore(&priv->lock, flags);
 		pm_runtime_put(dai->dev);
 		dev_err(&i2s->pdev->dev,
@@ -706,6 +731,12 @@  static int i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 		return -EAGAIN;
 	}
 
+	if (priv->quirks & QUIRK_SUPPORTS_TDM) {
+		tdm &= ~(tdm_mod_mask);
+		tdm |= tdm_tmp;
+		writel(tdm, priv->addr + I2STDM);
+	}
+
 	mod &= ~(sdf_mask | lrp_rlow | mod_slave);
 	mod |= tmp;
 	writel(mod, priv->addr + I2SMOD);
@@ -812,6 +843,47 @@  static int i2s_hw_params(struct snd_pcm_substream *substream,
 	return 0;
 }
 
+static int i2s_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+				unsigned int rx_mask, int slots, int slot_width)
+{
+	struct samsung_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
+	struct i2s_dai *i2s = to_info(dai);
+	u32 tdm = 0, mask = 0, val = 0;
+	unsigned long flags;
+
+	if (!(priv->quirks & QUIRK_SUPPORTS_TDM)) {
+		dev_err(&i2s->pdev->dev, "Invalid request: TDM not enabled\n");
+		return -EINVAL;
+	}
+
+	mask |= (TDM_ENABLE);
+	mask |= (TDM_TX_SLOTS_MASK << TDM_TX_SLOTS_SHIFT);
+	mask |= (TDM_RX_SLOTS_MASK << TDM_RX_SLOTS_SHIFT);
+
+	if (slots) {
+		val |= ((slots-1) & TDM_TX_SLOTS_MASK) << TDM_TX_SLOTS_SHIFT;
+		val |= ((slots-1) & TDM_RX_SLOTS_MASK) << TDM_RX_SLOTS_SHIFT;
+
+		dev_info(&i2s->pdev->dev,
+			"TDM Mode Configured - TX and RX Slots: %d\n", slots);
+
+		val |= TDM_ENABLE;
+
+		priv->tdm_slots = slots;
+	} else {
+		val = 0;
+		priv->tdm_slots = 0;
+	}
+
+	spin_lock_irqsave(&priv->lock, flags);
+	tdm = readl(priv->addr + I2STDM);
+	tdm = (tdm & ~mask) | val;
+	writel(tdm, priv->addr + I2STDM);
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	return 0;
+}
+
 /* We set constraints on the substream according to the version of I2S */
 static int i2s_startup(struct snd_pcm_substream *substream,
 	  struct snd_soc_dai *dai)
@@ -879,6 +951,9 @@  static int config_setup(struct i2s_dai *i2s)
 	if (!bfs && other)
 		bfs = other->bfs;
 
+	if (!bfs && (priv->quirks & QUIRK_SUPPORTS_TDM) && priv->tdm_slots)
+		bfs = blc * priv->tdm_slots;
+
 	/* Select least possible multiple(2) if no constraint set */
 	if (!bfs)
 		bfs = blc * 2;
@@ -899,6 +974,9 @@  static int config_setup(struct i2s_dai *i2s)
 			rfs = 256;
 		else
 			rfs = 384;
+
+		if ((priv->quirks & QUIRK_SUPPORTS_TDM) && priv->tdm_slots)
+			rfs /= (priv->tdm_slots / TDM_DEFAULT_SLOT_NUM_DIVIDER);
 	}
 
 	/* If already setup and running */
@@ -1110,6 +1188,7 @@  static const struct snd_soc_dai_ops samsung_i2s_dai_ops = {
 	.set_fmt = i2s_set_fmt,
 	.set_clkdiv = i2s_set_clkdiv,
 	.set_sysclk = i2s_set_sysclk,
+	.set_tdm_slot = i2s_set_tdm_slot,
 	.startup = i2s_startup,
 	.shutdown = i2s_shutdown,
 	.delay = i2s_delay,
@@ -1464,6 +1543,9 @@  static int samsung_i2s_probe(struct platform_device *pdev)
 		dev_err(&pdev->dev, "failed to enable clock: %d\n", ret);
 		return ret;
 	}
+
+	priv->tdm_slots = 0;
+
 	pri_dai->dma_playback.addr = regs_base + I2STXD;
 	pri_dai->dma_capture.addr = regs_base + I2SRXD;
 	pri_dai->dma_playback.chan_name = "tx";