[RFC PATCH 03/23] ASoC: qcom: sdw: add per-direction stream runtimes for multi_direction links
From: Srinivas Kandagatla
Date: Fri Oct 23 2026 - 13:19:37 EST
A SoundWire BE link with multi_direction_codecs carries codec DAIs of
both directions, and each direction needs its own sdw_stream_runtime.
Split startup, prepare, hw_free and shutdown into per-direction helpers
and run them for both directions on such links.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@xxxxxxxxxxxxxxxx>
---
sound/soc/qcom/sdw.c | 236 +++++++++++++++++++++++++++++++++----------
1 file changed, 183 insertions(+), 53 deletions(-)
diff --git a/sound/soc/qcom/sdw.c b/sound/soc/qcom/sdw.c
index 6576b47a4c8c..e72424ce5cb0 100644
--- a/sound/soc/qcom/sdw.c
+++ b/sound/soc/qcom/sdw.c
@@ -8,6 +8,15 @@
#include <sound/soc.h>
#include "sdw.h"
+/*
+ * Inline direction check that doesn't require snd_soc_dai_stream_valid
+ * to be exported for module use.
+ */
+static bool sdw_dai_has_dir(struct snd_soc_dai *dai, int dir)
+{
+ return snd_soc_dai_pcm_stream_get_i(dai, dir)->channels_min != 0;
+}
+
static bool qcom_snd_is_sdw_dai(int id)
{
switch (id) {
@@ -49,43 +58,42 @@ static bool qcom_snd_is_sdw_dai(int id)
return false;
}
-/**
- * qcom_snd_sdw_startup() - Helper to start Soundwire stream for SoC audio card
- * @substream: The PCM substream from audio, as passed to snd_soc_ops->startup()
- *
- * Helper for the SoC audio card (snd_soc_ops->startup()) to allocate and set
- * Soundwire stream runtime to each codec DAI.
- *
- * The shutdown() callback should call sdw_release_stream() on the same
- * sdw_stream_runtime.
- *
- * Return: 0 or errno
+/*
+ * Allocate a stream runtime for one direction and set it on the codec DAIs
+ * of that direction.
*/
-int qcom_snd_sdw_startup(struct snd_pcm_substream *substream)
+static int qcom_snd_sdw_setup_dir(struct snd_soc_pcm_runtime *rtd,
+ int dir, const char *cpu_name)
{
- struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
- struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
- u32 rx_ch[SDW_MAX_PORTS], tx_ch[SDW_MAX_PORTS];
struct sdw_stream_runtime *sruntime;
struct snd_soc_dai *codec_dai;
+ u32 rx_ch[SDW_MAX_PORTS], tx_ch[SDW_MAX_PORTS];
u32 rx_ch_cnt = 0, tx_ch_cnt = 0;
int ret, i, j;
+ bool used = false;
- if (!qcom_snd_is_sdw_dai(cpu_dai->id))
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ if (sdw_dai_has_dir(codec_dai, dir)) {
+ used = true;
+ break;
+ }
+ }
+ if (!used)
return 0;
- sruntime = sdw_alloc_stream(cpu_dai->name, SDW_STREAM_PCM);
+ sruntime = sdw_alloc_stream(cpu_name, SDW_STREAM_PCM);
if (!sruntime)
return -ENOMEM;
for_each_rtd_codec_dais(rtd, i, codec_dai) {
- ret = snd_soc_dai_set_stream(codec_dai, sruntime,
- substream->stream);
+ if (!sdw_dai_has_dir(codec_dai, dir))
+ continue;
+
+ ret = snd_soc_dai_set_stream(codec_dai, sruntime, dir);
if (ret < 0 && ret != -ENOTSUPP) {
dev_err(rtd->dev, "Failed to set sdw stream on %s\n", codec_dai->name);
- goto err_set_stream;
+ goto err;
} else if (ret == -ENOTSUPP) {
- /* Ignore unsupported */
continue;
}
@@ -93,59 +101,125 @@ int qcom_snd_sdw_startup(struct snd_pcm_substream *substream)
&rx_ch_cnt, rx_ch);
if (ret != 0 && ret != -ENOTSUPP) {
dev_err(rtd->dev, "Failed to get codec chan map %s\n", codec_dai->name);
- goto err_set_stream;
- } else if (ret == -ENOTSUPP) {
- /* Ignore unsupported */
- continue;
+ goto err;
}
}
- switch (cpu_dai->id) {
+ switch (snd_soc_rtd_to_cpu(rtd, 0)->id) {
case RX_CODEC_DMA_RX_0:
case TX_CODEC_DMA_TX_3:
if (tx_ch_cnt || rx_ch_cnt) {
for_each_rtd_codec_dais(rtd, j, codec_dai) {
+ if (!sdw_dai_has_dir(codec_dai, dir))
+ continue;
ret = snd_soc_dai_set_channel_map(codec_dai,
tx_ch_cnt, tx_ch,
rx_ch_cnt, rx_ch);
if (ret != 0 && ret != -ENOTSUPP)
- goto err_set_stream;
+ goto err;
}
}
}
return 0;
-
-err_set_stream:
+err:
sdw_release_stream(sruntime);
-
return ret;
}
-EXPORT_SYMBOL_GPL(qcom_snd_sdw_startup);
-int qcom_snd_sdw_prepare(struct snd_pcm_substream *substream,
- bool *stream_prepared)
+/**
+ * qcom_snd_sdw_startup() - Helper to start Soundwire stream for SoC audio card
+ * @substream: The PCM substream from audio, as passed to snd_soc_ops->startup()
+ *
+ * Helper for the SoC audio card (snd_soc_ops->startup()) to allocate and set
+ * Soundwire stream runtime to each codec DAI.
+ *
+ * The shutdown() callback should call sdw_release_stream() on the same
+ * sdw_stream_runtime.
+ *
+ * Return: 0 or errno
+ */
+int qcom_snd_sdw_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
- struct sdw_stream_runtime *sruntime;
int ret;
-
if (!qcom_snd_is_sdw_dai(cpu_dai->id))
return 0;
- sruntime = qcom_snd_sdw_get_stream(substream);
- if (!sruntime)
- return 0;
+ ret = qcom_snd_sdw_setup_dir(rtd, substream->stream, cpu_dai->name);
+ if (ret)
+ return ret;
- if (*stream_prepared)
+ /* Also set up the stream of the other direction on such links */
+ if (rtd->dai_link->multi_direction_codecs) {
+ ret = qcom_snd_sdw_setup_dir(rtd, substream->stream ^ 1,
+ cpu_dai->name);
+ if (ret) {
+ struct sdw_stream_runtime *sr;
+ struct snd_soc_dai *cd;
+ int i;
+
+ for_each_rtd_codec_dais(rtd, i, cd) {
+ if (!sdw_dai_has_dir(cd, substream->stream))
+ continue;
+ sr = snd_soc_dai_get_stream(cd, substream->stream);
+ if (!IS_ERR_OR_NULL(sr)) {
+ sdw_release_stream(sr);
+ break;
+ }
+ }
+ return ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(qcom_snd_sdw_startup);
+
+/* Prepare+enable the SDW stream for one direction. */
+static int qcom_snd_sdw_prepare_dir(struct snd_soc_pcm_runtime *rtd, int dir)
+{
+ struct sdw_stream_runtime *sruntime = NULL;
+ struct snd_soc_dai *codec_dai;
+ int ret, i;
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ if (!sdw_dai_has_dir(codec_dai, dir))
+ continue;
+ sruntime = snd_soc_dai_get_stream(codec_dai, dir);
+ if (!IS_ERR(sruntime) && sruntime)
+ break;
+ }
+ if (!sruntime || IS_ERR(sruntime))
return 0;
ret = sdw_prepare_stream(sruntime);
if (ret)
return ret;
+ ret = sdw_enable_stream(sruntime);
+ if (ret) {
+ sdw_deprepare_stream(sruntime);
+ return ret;
+ }
+ return 0;
+}
+
+int qcom_snd_sdw_prepare(struct snd_pcm_substream *substream,
+ bool *stream_prepared)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ int ret;
+
+ if (!qcom_snd_is_sdw_dai(cpu_dai->id))
+ return 0;
+
+ if (*stream_prepared)
+ return 0;
+
/**
* NOTE: there is a strict hw requirement about the ordering of port
* enables and actual WSA881x PA enable. PA enable should only happen
@@ -153,15 +227,34 @@ int qcom_snd_sdw_prepare(struct snd_pcm_substream *substream,
* accumulated resulting in Click/Pop Noise
* PA enable/mute are handled as part of codec DAPM and digital mute.
*/
-
- ret = sdw_enable_stream(sruntime);
- if (ret) {
- sdw_deprepare_stream(sruntime);
+ ret = qcom_snd_sdw_prepare_dir(rtd, substream->stream);
+ if (ret)
return ret;
+
+ if (rtd->dai_link->multi_direction_codecs) {
+ ret = qcom_snd_sdw_prepare_dir(rtd, substream->stream ^ 1);
+ if (ret) {
+ struct sdw_stream_runtime *sr = NULL;
+ struct snd_soc_dai *cd;
+ int i;
+
+ for_each_rtd_codec_dais(rtd, i, cd) {
+ if (!sdw_dai_has_dir(cd, substream->stream))
+ continue;
+ sr = snd_soc_dai_get_stream(cd, substream->stream);
+ if (!IS_ERR(sr) && sr)
+ break;
+ }
+ if (sr && !IS_ERR(sr)) {
+ sdw_disable_stream(sr);
+ sdw_deprepare_stream(sr);
+ }
+ return ret;
+ }
}
- *stream_prepared = true;
- return ret;
+ *stream_prepared = true;
+ return 0;
}
EXPORT_SYMBOL_GPL(qcom_snd_sdw_prepare);
@@ -185,11 +278,37 @@ struct sdw_stream_runtime *qcom_snd_sdw_get_stream(struct snd_pcm_substream *sub
}
EXPORT_SYMBOL_GPL(qcom_snd_sdw_get_stream);
+static struct sdw_stream_runtime *
+sdw_find_dir_stream(struct snd_soc_pcm_runtime *rtd, int dir)
+{
+ struct snd_soc_dai *codec_dai;
+ struct sdw_stream_runtime *sr;
+ int i;
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ if (!sdw_dai_has_dir(codec_dai, dir))
+ continue;
+ sr = snd_soc_dai_get_stream(codec_dai, dir);
+ if (!IS_ERR(sr) && sr)
+ return sr;
+ }
+ return NULL;
+}
+
void qcom_snd_sdw_shutdown(struct snd_pcm_substream *substream)
{
- struct sdw_stream_runtime *sruntime = qcom_snd_sdw_get_stream(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct sdw_stream_runtime *sr;
- sdw_release_stream(sruntime);
+ sr = sdw_find_dir_stream(rtd, substream->stream);
+ if (sr)
+ sdw_release_stream(sr);
+
+ if (rtd->dai_link->multi_direction_codecs) {
+ sr = sdw_find_dir_stream(rtd, substream->stream ^ 1);
+ if (sr)
+ sdw_release_stream(sr);
+ }
}
EXPORT_SYMBOL_GPL(qcom_snd_sdw_shutdown);
@@ -197,18 +316,29 @@ int qcom_snd_sdw_hw_free(struct snd_pcm_substream *substream, bool *stream_prepa
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
- struct sdw_stream_runtime *sruntime;
+ struct sdw_stream_runtime *sr;
if (!qcom_snd_is_sdw_dai(cpu_dai->id))
return 0;
- sruntime = qcom_snd_sdw_get_stream(substream);
- if (sruntime && *stream_prepared) {
- sdw_disable_stream(sruntime);
- sdw_deprepare_stream(sruntime);
- *stream_prepared = false;
+ if (!*stream_prepared)
+ return 0;
+
+ sr = sdw_find_dir_stream(rtd, substream->stream);
+ if (sr) {
+ sdw_disable_stream(sr);
+ sdw_deprepare_stream(sr);
+ }
+
+ if (rtd->dai_link->multi_direction_codecs) {
+ sr = sdw_find_dir_stream(rtd, substream->stream ^ 1);
+ if (sr) {
+ sdw_disable_stream(sr);
+ sdw_deprepare_stream(sr);
+ }
}
+ *stream_prepared = false;
return 0;
}
EXPORT_SYMBOL_GPL(qcom_snd_sdw_hw_free);
--
2.47.3