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(-)
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";