ASoC: fsl-asoc-card: Add some improvements

Shengjiu Wang <shengjiu.wang@nxp.com> says:

This patch series addresses several issues in the Freescale Generic ASoC
Sound Card driver related to hardware limitations, DPCM path switching,
and codec-specific constraints.

The fsl-asoc-card driver provides a generic machine driver for i.MX SoCs,
supporting various codecs and optional ASRC (Asynchronous Sample Rate
Converter) for sample rate conversion. During testing several issues were
identified:

1. Missing channel constraint propagation in DPCM mode
2. DPCM path switching causing audio dropouts
3. Hardware channel alignment requirements
4. Clock generation limitations preventing certain audio formats
5. Codec-specific PLL frequency violations
This commit is contained in:
Mark Brown 2026-04-30 21:08:00 +09:00
commit b772d5b485
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0

View File

@ -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",