From: Stefano Radaelli <stefano.r@variscite.com>
When simple-audio-card programs sysclk for CPU and codec DAIs during
hw_params, the ordering of these calls can matter on some platforms.
Some CPU DAIs apply the final MCLK rate as part of their set_sysclk()
callback (for example by changing the underlying clock rate via
clk_set_rate()). If the codec sysclk is configured before the CPU DAI
applies the final MCLK rate, the codec may configure its internal
clocking based on a non-final MCLK value, leading to an incorrect
clocking state on the first playback after boot.
This behaviour was observed on i.MX95 systems using fsl_sai with
downstream kernels, but the issue is generic and can affect any setup
where the CPU DAI finalizes the MCLK rate in set_sysclk().
A reproducible symptom is that the first playback runs at the wrong
speed, while subsequent playbacks work correctly.
Keep the existing default behaviour (codec sysclk configured before
CPU sysclk) to avoid regressions, and add a DT flag that allows
selecting CPU-first sysclk ordering when needed.
Example setup:
- CPU DAI: fsl_sai (i.MX95)
- Codec: wm8904
Reproducer:
aplay -D hw:wm8904audio /home/Front_Center_8k.wav
Signed-off-by: Stefano Radaelli <stefano.r@variscite.com>
---
include/sound/simple_card_utils.h | 1 +
sound/soc/generic/simple-card-utils.c | 39 +++++++++++++++++++--------
sound/soc/generic/simple-card.c | 7 +++++
3 files changed, 36 insertions(+), 11 deletions(-)
diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h
index 69a9c9c4d0e9..60ab4df8e954 100644
--- a/include/sound/simple_card_utils.h
+++ b/include/sound/simple_card_utils.h
@@ -63,6 +63,7 @@ struct simple_util_priv {
struct snd_soc_codec_conf *codec_conf;
struct prop_nums num;
unsigned int mclk_fs;
+ bool sysclk_cpu_first;
} *dai_props;
struct simple_util_jack hp_jack;
struct simple_util_jack mic_jack;
diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c
index bdc02e85b089..f5b851e7782d 100644
--- a/sound/soc/generic/simple-card-utils.c
+++ b/sound/soc/generic/simple-card-utils.c
@@ -501,18 +501,35 @@ int simple_util_hw_params(struct snd_pcm_substream *substream,
goto end;
}
- for_each_rtd_codec_dais(rtd, i, sdai) {
- pdai = simple_props_to_dai_codec(props, i);
- ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction);
- if (ret && ret != -ENOTSUPP)
- goto end;
- }
+ if (props->sysclk_cpu_first) {
+ for_each_rtd_cpu_dais(rtd, i, sdai) {
+ pdai = simple_props_to_dai_cpu(props, i);
+ ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction);
+ if (ret && ret != -ENOTSUPP)
+ goto end;
+ }
- for_each_rtd_cpu_dais(rtd, i, sdai) {
- pdai = simple_props_to_dai_cpu(props, i);
- ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction);
- if (ret && ret != -ENOTSUPP)
- goto end;
+ for_each_rtd_codec_dais(rtd, i, sdai) {
+ pdai = simple_props_to_dai_codec(props, i);
+ ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction);
+ if (ret && ret != -ENOTSUPP)
+ goto end;
+ }
+ /* default: codec first */
+ } else {
+ for_each_rtd_codec_dais(rtd, i, sdai) {
+ pdai = simple_props_to_dai_codec(props, i);
+ ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction);
+ if (ret && ret != -ENOTSUPP)
+ goto end;
+ }
+
+ for_each_rtd_cpu_dais(rtd, i, sdai) {
+ pdai = simple_props_to_dai_cpu(props, i);
+ ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction);
+ if (ret && ret != -ENOTSUPP)
+ goto end;
+ }
}
}
diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c
index 5af6d1b308f2..a16ead577f50 100644
--- a/sound/soc/generic/simple-card.c
+++ b/sound/soc/generic/simple-card.c
@@ -188,6 +188,7 @@ static int simple_link_init(struct simple_util_priv *priv,
enum snd_soc_trigger_order trigger_start = SND_SOC_TRIGGER_ORDER_DEFAULT;
enum snd_soc_trigger_order trigger_stop = SND_SOC_TRIGGER_ORDER_DEFAULT;
bool playback_only = 0, capture_only = 0;
+ bool cpu_first = false;
int ret;
ret = simple_util_parse_daifmt(dev, node, codec,
@@ -209,6 +210,12 @@ static int simple_link_init(struct simple_util_priv *priv,
of_property_read_u32(codec, "mclk-fs", &dai_props->mclk_fs);
of_property_read_u32(codec, PREFIX "mclk-fs", &dai_props->mclk_fs);
+ cpu_first |= of_property_read_bool(top, "sysclk-cpu-first");
+ cpu_first |= of_property_read_bool(top, PREFIX "sysclk-cpu-first");
+ cpu_first |= of_property_read_bool(node, "sysclk-cpu-first");
+ cpu_first |= of_property_read_bool(node, PREFIX "sysclk-cpu-first");
+ dai_props->sysclk_cpu_first = cpu_first;
+
graph_util_parse_trigger_order(priv, top, &trigger_start, &trigger_stop);
graph_util_parse_trigger_order(priv, node, &trigger_start, &trigger_stop);
graph_util_parse_trigger_order(priv, cpu, &trigger_start, &trigger_stop);
--
2.47.3
© 2016 - 2026 Red Hat, Inc.