diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c index 70a6159430ed..709543308fe9 100644 --- a/sound/soc/fsl/fsl-asoc-card.c +++ b/sound/soc/fsl/fsl-asoc-card.c @@ -40,6 +40,33 @@ /* Default DAI format without Master and Slave flag */ #define DAI_FMT_BASE (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF) +static const u32 cs42888_rates_48k[] = { + 48000, 96000, 192000, +}; + +static const u32 cs42888_rates_44k[] = { + 44100, 88200, 176400, +}; + +static const u32 cs42888_channels[] = { + 1, 2, 4, 6, 8, +}; + +static const struct snd_pcm_hw_constraint_list cs42888_rate_48k_constraints = { + .list = cs42888_rates_48k, + .count = ARRAY_SIZE(cs42888_rates_48k), +}; + +static const struct snd_pcm_hw_constraint_list cs42888_rate_44k_constraints = { + .list = cs42888_rates_44k, + .count = ARRAY_SIZE(cs42888_rates_44k), +}; + +static const struct snd_pcm_hw_constraint_list cs42888_channel_constraints = { + .list = cs42888_channels, + .count = ARRAY_SIZE(cs42888_channels), +}; + /** * struct codec_priv - CODEC private data * @mclk: Main clock of the CODEC @@ -48,6 +75,9 @@ * @mclk_id: MCLK (or main clock) id for set_sysclk() * @fll_id: FLL (or secordary clock) id for set_sysclk() * @pll_id: PLL id for set_pll() + * @pll_ratio_s24: PLL output ratio for S24_LE format (PLL_freq = sample_rate × ratio) + * Default is 384, but some codecs (e.g., WM8904) require lower values + * to stay within PLL frequency limits */ struct codec_priv { struct clk *mclk; @@ -56,6 +86,7 @@ struct codec_priv { u32 mclk_id; int fll_id; int pll_id; + int pll_ratio_s24; }; /** @@ -87,12 +118,15 @@ struct cpu_priv { * @codec_priv: CODEC private data * @cpu_priv: CPU private data * @card: ASoC card structure + * @constraint_rates: array of supported rates + * @constraint_channels: array of supported channels * @streams: Mask of current active streams * @sample_rate: Current sample rate * @sample_format: Current sample format * @asrc_rate: ASRC sample rate used by Back-Ends * @asrc_format: ASRC sample format used by Back-Ends * @dai_fmt: DAI format between CPU and CODEC + * @exclude_format: excluded format; * @name: Card name */ @@ -104,12 +138,15 @@ struct fsl_asoc_card_priv { struct codec_priv codec_priv[2]; struct cpu_priv cpu_priv; struct snd_soc_card card; + const struct snd_pcm_hw_constraint_list *constraint_rates; + const struct snd_pcm_hw_constraint_list *constraint_channels; u8 streams; u32 sample_rate; snd_pcm_format_t sample_format; u32 asrc_rate; snd_pcm_format_t asrc_format; u32 dai_fmt; + u64 exclude_format; char name[32]; }; @@ -222,7 +259,7 @@ static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream, if (codec_priv->pll_id >= 0 && codec_priv->fll_id >= 0) { if (priv->sample_format == SNDRV_PCM_FORMAT_S24_LE) - pll_out = priv->sample_rate * 384; + pll_out = priv->sample_rate * codec_priv->pll_ratio_s24; else pll_out = priv->sample_rate * 256; @@ -291,7 +328,47 @@ static int fsl_asoc_card_hw_free(struct snd_pcm_substream *substream) return 0; } +static int fsl_asoc_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + if (priv->exclude_format && !rtd->dai_link->no_pcm) { + ret = snd_pcm_hw_constraint_mask64(runtime, + SNDRV_PCM_HW_PARAM_FORMAT, + ~priv->exclude_format); + if (ret) + return ret; + } + + if (priv->constraint_channels) { + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + priv->constraint_channels); + if (ret) + return ret; + } + + /* + * Apply rate constraints only to frontend DAI links (no_pcm = 0). + * Skip DPCM backend (no_pcm = 1) as rate is fixed by be_hw_params_fixup() + * and ASRC frontend handles rate conversion. + */ + if (priv->constraint_rates && !rtd->dai_link->no_pcm) { + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + priv->constraint_rates); + if (ret) + return ret; + } + + return 0; +} + static const struct snd_soc_ops fsl_asoc_card_ops = { + .startup = fsl_asoc_card_startup, .hw_params = fsl_asoc_card_hw_params, .hw_free = fsl_asoc_card_hw_free, }; @@ -742,6 +819,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) for (codec_idx = 0; codec_idx < 2; codec_idx++) { priv->codec_priv[codec_idx].fll_id = -1; priv->codec_priv[codec_idx].pll_id = -1; + priv->codec_priv[codec_idx].pll_ratio_s24 = 384; } /* Diversify the card configurations */ @@ -753,6 +831,14 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; priv->cpu_priv.slot_width = 32; priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; + priv->constraint_channels = &cs42888_channel_constraints; + if (priv->codec_priv[0].mclk_freq % 12288000 == 0) + priv->constraint_rates = &cs42888_rate_48k_constraints; + else if (priv->codec_priv[0].mclk_freq % 11289600 == 0) + priv->constraint_rates = &cs42888_rate_44k_constraints; + else + dev_warn(&pdev->dev, "Unknown MCLK frequency %lu, no rate constraints\n", + priv->codec_priv[0].mclk_freq); } else if (of_device_is_compatible(np, "fsl,imx-audio-cs427x")) { codec_dai_name[0] = "cs4271-hifi"; priv->codec_priv[0].mclk_id = CS427x_SYSCLK_MCLK; @@ -779,11 +865,30 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) priv->codec_priv[0].fll_id = WM8962_SYSCLK_FLL; priv->codec_priv[0].pll_id = WM8962_FLL; priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; + /* + * WM8962 has same BCLK generation limitations as WM8960. + * See WM8960 section for detailed explanation. + */ + if (of_node_name_eq(cpu_np, "sai")) + priv->exclude_format = SNDRV_PCM_FMTBIT_S20_3LE; } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8960")) { codec_dai_name[0] = "wm8960-hifi"; priv->codec_priv[0].fll_id = WM8960_SYSCLK_AUTO; priv->codec_priv[0].pll_id = WM8960_SYSCLK_AUTO; priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; + /* + * WM8960 in master mode cannot generate exact 1.92 MHz BCLK + * required for S20_3LE (48kHz × 2ch × 20bit). Closest available + * is 2.048 MHz (SYSCLK/6), which causes right channel corruption. + * + * In SAI master mode, SAI derive BCLK from MCLK using integer + * dividers only. S20_3LE requires non-integer divider ratios + * with standard MCLK frequencies. For example, 48kHz stereo + * needs 1.920 MHz BCLK, which requires a divider of 6.4 from + * 12.288 MHz MCLK (not an integer). + */ + if (of_node_name_eq(cpu_np, "sai")) + priv->exclude_format = SNDRV_PCM_FMTBIT_S20_3LE; } else if (of_device_is_compatible(np, "fsl,imx-audio-ac97")) { codec_dai_name[0] = "ac97-hifi"; priv->dai_fmt = SND_SOC_DAIFMT_AC97; @@ -835,6 +940,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) priv->codec_priv[0].mclk_id = WM8904_FLL_MCLK; priv->codec_priv[0].fll_id = WM8904_CLK_FLL; priv->codec_priv[0].pll_id = WM8904_FLL_MCLK; + priv->codec_priv[0].pll_ratio_s24 = 192; priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; } else if (of_device_is_compatible(np, "fsl,imx-audio-spdif")) { ret = fsl_asoc_card_spdif_init(codec_np, cpu_np, codec_dai_name, priv); @@ -989,6 +1095,8 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) if (asrc_pdev) { /* DPCM DAI Links only if ASRC exists */ + priv->dai_link[1].dpcm_merged_chan = 1; + priv->dai_link[1].ignore_pmdown_time = 1; priv->dai_link[1].cpus->of_node = asrc_np; priv->dai_link[1].platforms->of_node = asrc_np; for_each_link_codecs((&(priv->dai_link[2])), codec_idx, codec_comp) { @@ -998,6 +1106,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) } priv->dai_link[2].cpus->of_node = cpu_np; priv->dai_link[2].dai_fmt = priv->dai_fmt; + priv->dai_link[2].ignore_pmdown_time = 1; priv->card.num_links = 3; ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",