summaryrefslogtreecommitdiff
path: root/sound/soc/fsl/imx-cs42888.c
diff options
context:
space:
mode:
authorMihai Serban <mihai.serban@nxp.com>2017-05-15 15:52:57 +0300
committerDong Aisheng <aisheng.dong@nxp.com>2019-11-25 15:50:08 +0800
commitebdb55bf3a98d88dff22476446a04a04a54ff87d (patch)
tree216099f2603df626f1f4eee82fab5338a2ee617d /sound/soc/fsl/imx-cs42888.c
parentc554bf7027c9ef24b5bf9891656d8bd4aeb19c95 (diff)
MLK-14778: ASoC: fsl: imx-cs42888: Improve support for odd number of channels
For samples with more than 2 and odd number of channels the I2S mode does not work correctly. In I2S mode we are required to activate an even number of channels (possibly on multiple datalines) and thus configure the BCLK for even channels. In this case samples with odd (smaller) number of channels are played faster and the sound is distorted. To fix this behavior we can enable TDM mode for the special cases of samples with 3, 5 or 7 channels. But even TDM has some restrictions that prevent us from having full support for the special cases: 1. TDM is not supported by codec in master mode so 3, 5 and 7 channels usage is denied. 2. In codec slave mode TDM works only with 8 slots and slot width of 32 bits. For an often used MCLK frequency of 24MHz and the above restrictions the maximum sample rate is limited to 48KHz = 24576000/(2*8*32). The 2 denominator is required by ESAI BCLK divisors. Signed-off-by: Mihai Serban <mihai.serban@nxp.com>
Diffstat (limited to 'sound/soc/fsl/imx-cs42888.c')
-rw-r--r--sound/soc/fsl/imx-cs42888.c72
1 files changed, 66 insertions, 6 deletions
diff --git a/sound/soc/fsl/imx-cs42888.c b/sound/soc/fsl/imx-cs42888.c
index 8bdb1709be26..43a1584a688f 100644
--- a/sound/soc/fsl/imx-cs42888.c
+++ b/sound/soc/fsl/imx-cs42888.c
@@ -50,12 +50,22 @@ static int imx_cs42888_surround_hw_params(struct snd_pcm_substream *substream,
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct imx_priv *priv = &card_priv;
struct device *dev = &priv->pdev->dev;
- u32 dai_format = 0;
+ u32 channels = params_channels(params);
+
+ bool enable_tdm = channels > 1 && channels % 2;
+ u32 dai_format = SND_SOC_DAIFMT_NB_NF |
+ (enable_tdm ? SND_SOC_DAIFMT_DSP_A : SND_SOC_DAIFMT_LEFT_J);
+
int ret = 0;
if (priv->is_codec_master) {
- dai_format = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
- SND_SOC_DAIFMT_CBM_CFM;
+ /* TDM is not supported by codec in master mode */
+ if (enable_tdm) {
+ dev_err(dev, "%d channels are not supported in codec master mode\n",
+ channels);
+ return -EINVAL;
+ }
+ dai_format |= SND_SOC_DAIFMT_CBM_CFM;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKT_EXTAL,
priv->mclk_freq, SND_SOC_CLOCK_IN);
@@ -75,8 +85,7 @@ static int imx_cs42888_surround_hw_params(struct snd_pcm_substream *substream,
}
} else {
- dai_format = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
- SND_SOC_DAIFMT_CBS_CFS;
+ dai_format |= SND_SOC_DAIFMT_CBS_CFS;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKT_EXTAL,
priv->mclk_freq, SND_SOC_CLOCK_OUT);
@@ -103,7 +112,16 @@ static int imx_cs42888_surround_hw_params(struct snd_pcm_substream *substream,
return ret;
}
/* set i.MX active slot mask */
- snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 32);
+ if (enable_tdm)
+ /*
+ * Per datasheet, the codec expects 8 slots and 32 bits
+ * for every slot in TDM mode.
+ */
+ snd_soc_dai_set_tdm_slot(cpu_dai,
+ BIT(channels) - 1, BIT(channels) - 1,
+ 8, 32);
+ else
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 32);
/* set codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
@@ -114,6 +132,39 @@ static int imx_cs42888_surround_hw_params(struct snd_pcm_substream *substream,
return 0;
}
+static int imx_cs42888_hw_rule_rate_by_channels(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct snd_interval *c = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_interval *r = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval t;
+ snd_interval_any(&t);
+ if (r->min > 48000) {
+ if (c->min > 2 && c->min % 2)
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int imx_cs42888_hw_rule_channels_by_rate(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct snd_interval *c = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_interval *r = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval t;
+ snd_interval_any(&t);
+ if (c->min > 2 && c->min % 2) {
+ t.min = t.max = 48000;
+ t.integer = 1;
+ return snd_interval_refine(r, &t);
+ }
+ return 0;
+}
+
static int imx_cs42888_surround_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
@@ -134,6 +185,15 @@ static int imx_cs42888_surround_startup(struct snd_pcm_substream *substream)
&constraint_rates);
if (ret)
return ret;
+
+ snd_pcm_hw_rule_add(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ imx_cs42888_hw_rule_rate_by_channels,
+ 0, SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+ snd_pcm_hw_rule_add(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ imx_cs42888_hw_rule_channels_by_rate,
+ 0, SNDRV_PCM_HW_PARAM_RATE, -1);
} else
dev_warn(dev, "mclk may be not supported %d\n", priv->mclk_freq);