summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-02-05 03:45:16 +0300
committerMark Brown <broonie@kernel.org>2026-02-05 03:45:16 +0300
commitdc8384d85c034b3c3912ec7fc43784da5b884c27 (patch)
tree9e34b051b0beab534ec5d6bed99ce5574de4141a
parent6f0fce21ba2d2e5ae3cdfb6133f14e92916f7a96 (diff)
parent6c52fda42066a87b76fd140e027280907071dd8a (diff)
downloadlinux-dc8384d85c034b3c3912ec7fc43784da5b884c27.tar.xz
ASoC: SOF: Support for echoref (virtual DAI)
Merge series from Peter Ujfalusi <peter.ujfalusi@linux.intel.com>: The series adds support for echo reference functionality by allowing the capturing of playback audio right before it leaves the DSP. For this to work correctly we need a virtual DAI that is also connected to the echo reference capture device and in absence of playback a signal generator generates silence to allow the capture to run. When the real playback starts, application will start to receive the playback audio to be usable for echo reference.
-rw-r--r--include/uapi/sound/sof/tokens.h3
-rw-r--r--sound/soc/intel/boards/sof_sdw.c43
-rw-r--r--sound/soc/sof/intel/hda-dai.c22
-rw-r--r--sound/soc/sof/intel/hda.h4
-rw-r--r--sound/soc/sof/ipc4-topology.c82
-rw-r--r--sound/soc/sof/ipc4-topology.h4
-rw-r--r--sound/soc/sof/pcm.c81
-rw-r--r--sound/soc/sof/sof-audio.c215
-rw-r--r--sound/soc/sof/sof-audio.h15
9 files changed, 370 insertions, 99 deletions
diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h
index 5fa8ab5088e0..f4a7baadb44d 100644
--- a/include/uapi/sound/sof/tokens.h
+++ b/include/uapi/sound/sof/tokens.h
@@ -56,6 +56,9 @@
#define SOF_TKN_SCHED_LP_MODE 207
#define SOF_TKN_SCHED_MEM_USAGE 208
#define SOF_TKN_SCHED_USE_CHAIN_DMA 209
+#define SOF_TKN_SCHED_KCPS 210
+#define SOF_TKN_SCHED_DIRECTION 211
+#define SOF_TKN_SCHED_DIRECTION_VALID 212
/* volume */
#define SOF_TKN_VOLUME_RAMP_STEP_TYPE 250
diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c
index 50b838be24e9..ee34282828e4 100644
--- a/sound/soc/intel/boards/sof_sdw.c
+++ b/sound/soc/intel/boards/sof_sdw.c
@@ -1186,6 +1186,34 @@ static int create_bt_dailinks(struct snd_soc_card *card,
return 0;
}
+static int create_echoref_dailink(struct snd_soc_card *card,
+ struct snd_soc_dai_link **dai_links, int *be_id)
+{
+ struct device *dev = card->dev;
+ int ret;
+ char *name = devm_kasprintf(dev, GFP_KERNEL, "Loopback_Virtual");
+
+ if (!name)
+ return -ENOMEM;
+
+ /*
+ * use dummy DAI names as this won't be connected to an actual DAI but just to establish a
+ * fe <-> be connection for loopback capture for echo reference
+ */
+ ret = asoc_sdw_init_simple_dai_link(dev, *dai_links, be_id, name,
+ 0, 1, "Loopback Virtual Pin", "dummy",
+ snd_soc_dummy_dlc.name, snd_soc_dummy_dlc.dai_name,
+ 1, NULL, NULL);
+ if (ret)
+ return ret;
+
+ (*dai_links)++;
+
+ dev_dbg(dev, "Added echo reference DAI link\n");
+
+ return 0;
+}
+
static int sof_card_dai_links_create(struct snd_soc_card *card)
{
struct device *dev = card->dev;
@@ -1294,8 +1322,12 @@ static int sof_card_dai_links_create(struct snd_soc_card *card)
goto err_end;
}
- /* allocate BE dailinks */
- num_links = sdw_be_num + ssp_num + dmic_num + hdmi_num + bt_num;
+ /*
+ * allocate BE dailinks, add an extra DAI link for echo reference capture.
+ * This should be the last DAI link and it is expected both for monolithic
+ * and functional SOF topologies to support echo reference.
+ */
+ num_links = sdw_be_num + ssp_num + dmic_num + hdmi_num + bt_num + 1;
dai_links = devm_kcalloc(dev, num_links, sizeof(*dai_links), GFP_KERNEL);
if (!dai_links) {
ret = -ENOMEM;
@@ -1344,6 +1376,13 @@ static int sof_card_dai_links_create(struct snd_soc_card *card)
goto err_end;
}
+ /* dummy echo ref link. keep this as the last DAI link. The DAI link ID does not matter */
+ ret = create_echoref_dailink(card, &dai_links, &be_id);
+ if (ret) {
+ dev_err(dev, "failed to create echo ref dai link: %d\n", ret);
+ goto err_end;
+ }
+
WARN_ON(codec_conf != card->codec_conf + card->num_configs);
WARN_ON(dai_links != card->dai_link + card->num_links);
diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c
index 883d0d3bae9e..15faedeec16d 100644
--- a/sound/soc/sof/intel/hda-dai.c
+++ b/sound/soc/sof/intel/hda-dai.c
@@ -70,12 +70,22 @@ static const struct hda_dai_widget_dma_ops *
hda_dai_get_ops(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai)
{
struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
- struct snd_sof_widget *swidget = w->dobj.private;
+ struct snd_sof_widget *swidget;
struct snd_sof_dev *sdev;
struct snd_sof_dai *sdai;
- sdev = widget_to_sdev(w);
+ /*
+ * this is unlikely if the topology and the machine driver DAI links match.
+ * But if there's a missing DAI link in topology, this will prevent a NULL pointer
+ * dereference later on.
+ */
+ if (!w) {
+ dev_err(cpu_dai->dev, "%s: widget is NULL\n", __func__);
+ return NULL;
+ }
+ sdev = widget_to_sdev(w);
+ swidget = w->dobj.private;
if (!swidget) {
dev_err(sdev->dev, "%s: swidget is NULL\n", __func__);
return NULL;
@@ -856,6 +866,14 @@ struct snd_soc_dai_driver skl_dai[] = {
.channels_max = 4,
},
},
+{
+ /* Virtual CPU DAI for Echo reference */
+ .name = "Loopback Virtual Pin",
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+},
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
{
.name = "iDisp1 Pin",
diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h
index 3fe00c269114..3f0966477ace 100644
--- a/sound/soc/sof/intel/hda.h
+++ b/sound/soc/sof/intel/hda.h
@@ -418,10 +418,10 @@
(HDA_DSP_BDL_SIZE / sizeof(struct sof_intel_dsp_bdl))
/* Number of DAIs */
-#define SOF_SKL_NUM_DAIS_NOCODEC 8
+#define SOF_SKL_NUM_DAIS_NOCODEC 9
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
-#define SOF_SKL_NUM_DAIS 15
+#define SOF_SKL_NUM_DAIS 16
#else
#define SOF_SKL_NUM_DAIS SOF_SKL_NUM_DAIS_NOCODEC
#endif
diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c
index 1bc95e3e584c..622bffb50a1c 100644
--- a/sound/soc/sof/ipc4-topology.c
+++ b/sound/soc/sof/ipc4-topology.c
@@ -76,6 +76,10 @@ static const struct sof_topology_token ipc4_sched_tokens[] = {
offsetof(struct sof_ipc4_pipeline, core_id)},
{SOF_TKN_SCHED_PRIORITY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc4_pipeline, priority)},
+ {SOF_TKN_SCHED_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pipeline, direction)},
+ {SOF_TKN_SCHED_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16,
+ offsetof(struct sof_ipc4_pipeline, direction_valid)},
};
static const struct sof_topology_token pipeline_tokens[] = {
@@ -939,6 +943,10 @@ static int sof_ipc4_widget_setup_comp_pipeline(struct snd_sof_widget *swidget)
swidget->core = pipeline->core_id;
spipe->core_mask |= BIT(pipeline->core_id);
+ if (pipeline->direction_valid) {
+ spipe->direction = pipeline->direction;
+ spipe->direction_valid = true;
+ }
if (pipeline->use_chain_dma) {
dev_dbg(scomp->dev, "Set up chain DMA for %s\n", swidget->widget->name);
@@ -954,9 +962,9 @@ static int sof_ipc4_widget_setup_comp_pipeline(struct snd_sof_widget *swidget)
goto err;
}
- dev_dbg(scomp->dev, "pipeline '%s': id %d, pri %d, core_id %u, lp mode %d\n",
+ dev_dbg(scomp->dev, "pipeline '%s': id %d, pri %d, core_id %u, lp mode %d direction %d\n",
swidget->widget->name, swidget->pipeline_id,
- pipeline->priority, pipeline->core_id, pipeline->lp_mode);
+ pipeline->priority, pipeline->core_id, pipeline->lp_mode, pipeline->direction);
swidget->private = pipeline;
@@ -2004,6 +2012,25 @@ sof_ipc4_prepare_dai_copier(struct snd_sof_dev *sdev, struct snd_sof_dai *dai,
return ret;
}
+static void sof_ipc4_host_config(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
+ struct snd_sof_platform_stream_params *platform_params)
+{
+ struct sof_ipc4_copier *ipc4_copier = (struct sof_ipc4_copier *)swidget->private;
+ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
+ struct sof_ipc4_copier_data *copier_data = &ipc4_copier->data;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+ u32 host_dma_id = platform_params->stream_tag - 1;
+
+ if (pipeline->use_chain_dma) {
+ pipeline->msg.primary &= ~SOF_IPC4_GLB_CHAIN_DMA_HOST_ID_MASK;
+ pipeline->msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_HOST_ID(host_dma_id);
+ return;
+ }
+
+ copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK;
+ copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(host_dma_id);
+}
+
static int
sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget,
struct snd_pcm_hw_params *fe_params,
@@ -2726,12 +2753,14 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget,
int input_fmt_index = 0;
int ret;
- input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget,
- &process->base_config,
- pipeline_params,
- available_fmt);
- if (input_fmt_index < 0)
- return input_fmt_index;
+ if (available_fmt->num_input_formats) {
+ input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget,
+ &process->base_config,
+ pipeline_params,
+ available_fmt);
+ if (input_fmt_index < 0)
+ return input_fmt_index;
+ }
/* Configure output audio format only if the module supports output */
if (available_fmt->num_output_formats) {
@@ -2740,12 +2769,28 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget,
u32 out_ref_rate, out_ref_channels;
int out_ref_valid_bits, out_ref_type;
- in_fmt = &available_fmt->input_pin_fmts[input_fmt_index].audio_fmt;
+ if (available_fmt->num_input_formats) {
+ in_fmt = &available_fmt->input_pin_fmts[input_fmt_index].audio_fmt;
- out_ref_rate = in_fmt->sampling_frequency;
- out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg);
- out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg);
- out_ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg);
+ out_ref_rate = in_fmt->sampling_frequency;
+ out_ref_channels =
+ SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg);
+ out_ref_valid_bits =
+ SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg);
+ out_ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg);
+ } else {
+ /* for modules without input formats, use FE params as reference */
+ out_ref_rate = params_rate(fe_params);
+ out_ref_channels = params_channels(fe_params);
+ ret = sof_ipc4_get_sample_type(sdev, fe_params);
+ if (ret < 0)
+ return ret;
+ out_ref_type = (u32)ret;
+
+ out_ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params);
+ if (out_ref_valid_bits < 0)
+ return out_ref_valid_bits;
+ }
output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget,
&process->base_config,
@@ -2773,6 +2818,16 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget,
if (ret)
return ret;
}
+
+ /* set base cfg to match the first output format if there are no input formats */
+ if (!available_fmt->num_input_formats) {
+ struct sof_ipc4_audio_format *out_fmt;
+
+ out_fmt = &available_fmt->output_pin_fmts[0].audio_fmt;
+
+ /* copy output format */
+ memcpy(&process->base_config.audio_fmt, out_fmt, sizeof(*out_fmt));
+ }
}
sof_ipc4_dbg_module_audio_format(sdev->dev, swidget, available_fmt,
@@ -3929,4 +3984,5 @@ const struct sof_ipc_tplg_ops ipc4_tplg_ops = {
.dai_get_param = sof_ipc4_dai_get_param,
.tear_down_all_pipelines = sof_ipc4_tear_down_all_pipelines,
.link_setup = sof_ipc4_link_setup,
+ .host_config = sof_ipc4_host_config,
};
diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h
index 9a028a59c553..a289c1d8f3ff 100644
--- a/sound/soc/sof/ipc4-topology.h
+++ b/sound/soc/sof/ipc4-topology.h
@@ -150,6 +150,8 @@ struct sof_ipc4_copier_config_set_sink_format {
* @use_chain_dma: flag to indicate if the firmware shall use chained DMA
* @msg: message structure for pipeline
* @skip_during_fe_trigger: skip triggering this pipeline during the FE DAI trigger
+ * @direction_valid: flag indicating if valid direction is set in topology
+ * @direction: pipeline direction set in topology if direction_valid is true
*/
struct sof_ipc4_pipeline {
uint32_t priority;
@@ -160,6 +162,8 @@ struct sof_ipc4_pipeline {
bool use_chain_dma;
struct sof_ipc4_msg msg;
bool skip_during_fe_trigger;
+ bool direction_valid;
+ u32 direction;
};
/**
diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c
index 31879a11c33e..5b598d0940eb 100644
--- a/sound/soc/sof/pcm.c
+++ b/sound/soc/sof/pcm.c
@@ -88,9 +88,9 @@ sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_run
spcm->stream[dir].list = list;
- ret = sof_widget_list_setup(sdev, spcm, params, platform_params, dir);
+ ret = sof_widget_list_prepare(sdev, spcm, params, platform_params, dir);
if (ret < 0) {
- spcm_err(spcm, dir, "Widget list set up failed\n");
+ spcm_err(spcm, dir, "widget list prepare failed\n");
spcm->stream[dir].list = NULL;
snd_soc_dapm_dai_free_widgets(&list);
return ret;
@@ -100,15 +100,30 @@ sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_run
return 0;
}
+static struct snd_sof_widget *snd_sof_find_swidget_by_comp_id(struct snd_sof_dev *sdev,
+ int comp_id)
+{
+ struct snd_sof_widget *swidget;
+
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ if (comp_id == swidget->comp_id)
+ return swidget;
+ }
+
+ return NULL;
+}
+
static int sof_pcm_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm);
- struct snd_sof_platform_stream_params platform_params = { 0 };
+ struct snd_sof_platform_stream_params *platform_params;
struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_sof_widget *host_widget;
struct snd_sof_pcm *spcm;
int ret;
@@ -144,7 +159,8 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
spcm->prepared[substream->stream] = false;
}
- ret = snd_sof_pcm_platform_hw_params(sdev, substream, params, &platform_params);
+ platform_params = &spcm->platform_params[substream->stream];
+ ret = snd_sof_pcm_platform_hw_params(sdev, substream, params, platform_params);
if (ret < 0) {
spcm_err(spcm, substream->stream, "platform hw params failed\n");
return ret;
@@ -152,12 +168,27 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
/* if this is a repeated hw_params without hw_free, skip setting up widgets */
if (!spcm->stream[substream->stream].list) {
- ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, params, &platform_params,
+ ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, params, platform_params,
substream->stream);
if (ret < 0)
return ret;
}
+ if (!sdev->dspless_mode_selected) {
+ int host_comp_id = spcm->stream[substream->stream].comp_id;
+
+ host_widget = snd_sof_find_swidget_by_comp_id(sdev, host_comp_id);
+ if (!host_widget) {
+ spcm_err(spcm, substream->stream,
+ "failed to find host widget with comp_id %d\n", host_comp_id);
+ return -EINVAL;
+ }
+
+ /* set the host DMA ID */
+ if (tplg_ops && tplg_ops->host_config)
+ tplg_ops->host_config(sdev, host_widget, platform_params);
+ }
+
/* create compressed page table for audio firmware */
if (runtime->buffer_changed) {
struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream);
@@ -169,14 +200,6 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
return ret;
}
- if (pcm_ops && pcm_ops->hw_params) {
- ret = pcm_ops->hw_params(component, substream, params, &platform_params);
- if (ret < 0)
- return ret;
- }
-
- spcm->prepared[substream->stream] = true;
-
/* save pcm hw_params */
memcpy(&spcm->params[substream->stream], params, sizeof(*params));
@@ -281,6 +304,9 @@ static int sof_pcm_hw_free(struct snd_soc_component *component,
ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, true);
+ /* unprepare and free the list of DAPM widgets */
+ sof_widget_list_unprepare(sdev, spcm, substream->stream);
+
cancel_work_sync(&spcm->stream[substream->stream].period_elapsed_work);
return ret;
@@ -291,7 +317,12 @@ static int sof_pcm_prepare(struct snd_soc_component *component,
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm);
+ struct snd_sof_platform_stream_params *platform_params;
+ struct snd_soc_dapm_widget_list *list;
+ struct snd_pcm_hw_params *params;
struct snd_sof_pcm *spcm;
+ int dir = substream->stream;
int ret;
/* nothing to do for BE */
@@ -317,15 +348,33 @@ static int sof_pcm_prepare(struct snd_soc_component *component,
return ret;
}
- /* set hw_params */
- ret = sof_pcm_hw_params(component,
- substream, &spcm->params[substream->stream]);
+ ret = sof_pcm_hw_params(component, substream, &spcm->params[substream->stream]);
if (ret < 0) {
spcm_err(spcm, substream->stream,
"failed to set hw_params after resume\n");
return ret;
}
+ list = spcm->stream[dir].list;
+ params = &spcm->params[substream->stream];
+ platform_params = &spcm->platform_params[substream->stream];
+ ret = sof_widget_list_setup(sdev, spcm, params, platform_params, dir);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed widget list set up for pcm %d dir %d\n",
+ spcm->pcm.pcm_id, dir);
+ spcm->stream[dir].list = NULL;
+ snd_soc_dapm_dai_free_widgets(&list);
+ return ret;
+ }
+
+ if (pcm_ops && pcm_ops->hw_params) {
+ ret = pcm_ops->hw_params(component, substream, params, platform_params);
+ if (ret < 0)
+ return ret;
+ }
+
+ spcm->prepared[substream->stream] = true;
+
return 0;
}
diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c
index d55ee7343f8e..acf56607bc9c 100644
--- a/sound/soc/sof/sof-audio.c
+++ b/sound/soc/sof/sof-audio.c
@@ -13,6 +13,21 @@
#include "sof-audio.h"
#include "ops.h"
+/*
+ * Check if a DAI widget is an aggregated DAI. Aggregated DAI's have names ending in numbers
+ * starting with 0. For example: in the case of a SDW speaker with 2 amps, the topology contains
+ * 2 DAI's names alh-copier.SDW1.Playback.0 and alh-copier-SDW1.Playback.1. In this case, only the
+ * DAI alh-copier.SDW1.Playback.0 is set up in the firmware. The other DAI,
+ * alh-copier.SDW1.Playback.1 in topology is for the sake of completeness to show aggregation for
+ * the speaker amp and does not need any firmware configuration.
+ */
+static bool is_aggregated_dai(struct snd_sof_widget *swidget)
+{
+ return (WIDGET_IS_DAI(swidget->id) &&
+ isdigit(swidget->widget->name[strlen(swidget->widget->name) - 1]) &&
+ swidget->widget->name[strlen(swidget->widget->name) - 1] != '0');
+}
+
static bool is_virtual_widget(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
const char *func)
{
@@ -254,6 +269,10 @@ int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsourc
is_virtual_widget(sdev, sink_widget->widget, __func__))
return 0;
+ /* skip route if source/sink widget is not set up */
+ if (!src_widget->use_count || !sink_widget->use_count)
+ return 0;
+
/* find route matching source and sink widgets */
list_for_each_entry(sroute, &sdev->route_list, list)
if (sroute->src_widget == src_widget && sroute->sink_widget == sink_widget) {
@@ -282,10 +301,34 @@ int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsourc
return 0;
}
+static bool sof_widget_in_same_direction(struct snd_sof_widget *swidget, int dir)
+{
+ return swidget->spipe->direction == dir;
+}
+
+static int sof_set_up_same_dir_widget_routes(struct snd_sof_dev *sdev,
+ struct snd_soc_dapm_widget *wsource,
+ struct snd_soc_dapm_widget *wsink)
+{
+ struct snd_sof_widget *src_widget = wsource->dobj.private;
+ struct snd_sof_widget *sink_widget = wsink->dobj.private;
+
+ /*
+ * skip setting up route if source and sink are in different directions (ex. playback and
+ * echo ref) if the direction is set in topology. These will be set up later. It is enough
+ * to check if the direction_valid is set for one of the widgets as all widgets will have
+ * the direction set in topology if one is set.
+ */
+ if (sink_widget->spipe && sink_widget->spipe->direction_valid &&
+ !sof_widget_in_same_direction(sink_widget, src_widget->spipe->direction))
+ return 0;
+
+ return sof_route_setup(sdev, wsource, wsink);
+}
+
static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
struct snd_soc_dapm_widget_list *list, int dir)
{
- const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
struct snd_soc_dapm_widget *widget;
struct snd_sof_route *sroute;
struct snd_soc_dapm_path *p;
@@ -308,7 +351,8 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
continue;
if (p->sink->dobj.private) {
- ret = sof_route_setup(sdev, widget, p->sink);
+ ret = sof_set_up_same_dir_widget_routes(sdev, widget,
+ p->sink);
if (ret < 0)
return ret;
}
@@ -324,7 +368,8 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
continue;
if (p->source->dobj.private) {
- ret = sof_route_setup(sdev, p->source, widget);
+ ret = sof_set_up_same_dir_widget_routes(sdev, p->source,
+ widget);
if (ret < 0)
return ret;
}
@@ -340,7 +385,6 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
*/
list_for_each_entry(sroute, &sdev->route_list, list) {
bool src_widget_in_dapm_list, sink_widget_in_dapm_list;
- struct snd_sof_widget *swidget;
if (sroute->setup)
continue;
@@ -349,40 +393,37 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
sink_widget_in_dapm_list = widget_in_list(list, sroute->sink_widget->widget);
/*
- * if both source and sink are in the DAPM list, the route must already have been
- * set up above. And if neither are in the DAPM list, the route shouldn't be
- * handled now.
+ * no need to set up the route if both the source and sink widgets are not in the
+ * DAPM list
*/
- if (src_widget_in_dapm_list == sink_widget_in_dapm_list)
+ if (!src_widget_in_dapm_list && !sink_widget_in_dapm_list)
continue;
/*
- * At this point either the source widget or the sink widget is in the DAPM list
- * with a route that might need to be set up. Check the use_count of the widget
- * that is not in the DAPM list to confirm if it is in use currently before setting
- * up the route.
+ * set up the route only if both the source and sink widgets are in the DAPM list
+ * but are in different directions. The ones in the same direction would already
+ * have been set up in the previous loop.
*/
- if (src_widget_in_dapm_list)
- swidget = sroute->sink_widget;
- else
- swidget = sroute->src_widget;
+ if (src_widget_in_dapm_list && sink_widget_in_dapm_list) {
+ struct snd_sof_widget *src_widget, *sink_widget;
- scoped_guard(mutex, &swidget->setup_mutex) {
- if (!swidget->use_count)
- continue;
+ src_widget = sroute->src_widget->widget->dobj.private;
+ sink_widget = sroute->sink_widget->widget->dobj.private;
- if (tplg_ops && tplg_ops->route_setup) {
- /*
- * this route will get freed when either the
- * source widget or the sink widget is freed
- * during hw_free
- */
- ret = tplg_ops->route_setup(sdev, sroute);
- if (!ret)
- sroute->setup = true;
- }
+ /*
+ * it is enough to check if the direction_valid is set for one of the
+ * widgets as all widgets will have the direction set in topology if one
+ * is set.
+ */
+ if (src_widget && sink_widget &&
+ src_widget->spipe && src_widget->spipe->direction_valid &&
+ sof_widget_in_same_direction(sink_widget, src_widget->spipe->direction))
+ continue;
}
+ ret = sof_route_setup(sdev, sroute->src_widget->widget,
+ sroute->sink_widget->widget);
+
if (ret < 0)
return ret;
}
@@ -392,7 +433,7 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
static void
sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
- struct snd_soc_dapm_widget_list *list)
+ struct snd_soc_dapm_widget_list *list, int dir)
{
const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
struct snd_sof_widget *swidget = widget->dobj.private;
@@ -402,8 +443,15 @@ sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widg
if (is_virtual_widget(sdev, widget, __func__))
return;
- /* skip if the widget is in use or if it is already unprepared */
- if (!swidget || !swidget->prepared || swidget->use_count > 0)
+ if (!swidget)
+ goto sink_unprepare;
+
+ if (swidget->spipe && swidget->spipe->direction_valid &&
+ !sof_widget_in_same_direction(swidget, dir))
+ return;
+
+ /* skip widgets in use, those already unprepared or aggregated DAIs */
+ if (!swidget->prepared || swidget->use_count > 0 || is_aggregated_dai(swidget))
goto sink_unprepare;
widget_ops = tplg_ops ? tplg_ops->widget : NULL;
@@ -418,9 +466,10 @@ sink_unprepare:
snd_soc_dapm_widget_for_each_sink_path(widget, p) {
if (!widget_in_list(list, p->sink))
continue;
+
if (!p->walking && p->sink->dobj.private) {
p->walking = true;
- sof_unprepare_widgets_in_path(sdev, p->sink, list);
+ sof_unprepare_widgets_in_path(sdev, p->sink, list, dir);
p->walking = false;
}
}
@@ -442,11 +491,20 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget
if (is_virtual_widget(sdev, widget, __func__))
return 0;
+ if (!swidget)
+ goto sink_prepare;
+
widget_ops = tplg_ops ? tplg_ops->widget : NULL;
if (!widget_ops)
return 0;
- if (!swidget || !widget_ops[widget->id].ipc_prepare || swidget->prepared)
+ if (swidget->spipe && swidget->spipe->direction_valid &&
+ !sof_widget_in_same_direction(swidget, dir))
+ return 0;
+
+ /* skip widgets already prepared or aggregated DAI widgets*/
+ if (!widget_ops[widget->id].ipc_prepare || swidget->prepared ||
+ is_aggregated_dai(swidget))
goto sink_prepare;
/* prepare the source widget */
@@ -464,6 +522,7 @@ sink_prepare:
snd_soc_dapm_widget_for_each_sink_path(widget, p) {
if (!widget_in_list(list, p->sink))
continue;
+
if (!p->walking && p->sink->dobj.private) {
p->walking = true;
ret = sof_prepare_widgets_in_path(sdev, p->sink, fe_params,
@@ -493,6 +552,7 @@ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dap
int dir, struct snd_sof_pcm *spcm)
{
struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
+ struct snd_sof_widget *swidget = widget->dobj.private;
struct snd_soc_dapm_path *p;
int err;
int ret = 0;
@@ -500,12 +560,21 @@ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dap
if (is_virtual_widget(sdev, widget, __func__))
return 0;
- if (widget->dobj.private) {
- err = sof_widget_free(sdev, widget->dobj.private);
- if (err < 0)
- ret = err;
- }
+ if (!swidget)
+ goto sink_free;
+
+ if (swidget->spipe && swidget->spipe->direction_valid &&
+ !sof_widget_in_same_direction(swidget, dir))
+ return 0;
+ /* skip aggregated DAIs */
+ if (is_aggregated_dai(swidget))
+ goto sink_free;
+
+ err = sof_widget_free(sdev, widget->dobj.private);
+ if (err < 0)
+ ret = err;
+sink_free:
/* free all widgets in the sink paths even in case of error to keep use counts balanced */
snd_soc_dapm_widget_for_each_sink_path(widget, p) {
if (!p->walking) {
@@ -545,7 +614,15 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d
if (swidget) {
int i;
- ret = sof_widget_setup(sdev, widget->dobj.private);
+ if (swidget->spipe && swidget->spipe->direction_valid &&
+ !sof_widget_in_same_direction(swidget, dir))
+ return 0;
+
+ /* skip aggregated DAIs */
+ if (is_aggregated_dai(swidget))
+ goto sink_setup;
+
+ ret = sof_widget_setup(sdev, swidget);
if (ret < 0)
return ret;
@@ -607,15 +684,13 @@ sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
return 0;
for_each_dapm_widgets(list, i, widget) {
- if (is_virtual_widget(sdev, widget, __func__))
- continue;
-
- /* starting widget for playback is AIF type */
+ /* starting widget for playback is of AIF type */
if (dir == SNDRV_PCM_STREAM_PLAYBACK && widget->id != snd_soc_dapm_aif_in)
continue;
/* starting widget for capture is DAI type */
- if (dir == SNDRV_PCM_STREAM_CAPTURE && widget->id != snd_soc_dapm_dai_out)
+ if (dir == SNDRV_PCM_STREAM_CAPTURE && widget->id != snd_soc_dapm_dai_out &&
+ widget->id != snd_soc_dapm_output)
continue;
switch (op) {
@@ -645,7 +720,7 @@ sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
break;
}
case SOF_WIDGET_UNPREPARE:
- sof_unprepare_widgets_in_path(sdev, widget, list);
+ sof_unprepare_widgets_in_path(sdev, widget, list, dir);
break;
default:
dev_err(sdev->dev, "Invalid widget op %d\n", op);
@@ -660,6 +735,30 @@ sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
return 0;
}
+int sof_widget_list_prepare(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ int dir)
+{
+ /*
+ * Prepare widgets for set up. The prepare step is used to allocate memory, assign
+ * instance ID and pick the widget configuration based on the runtime PCM params.
+ */
+ return sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params,
+ dir, SOF_WIDGET_PREPARE);
+}
+
+void sof_widget_list_unprepare(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir)
+{
+ struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
+
+ /* unprepare the widget */
+ sof_walk_widgets_in_order(sdev, spcm, NULL, NULL, dir, SOF_WIDGET_UNPREPARE);
+
+ snd_soc_dapm_dai_free_widgets(&list);
+ spcm->stream[dir].list = NULL;
+}
+
int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
struct snd_pcm_hw_params *fe_params,
struct snd_sof_platform_stream_params *platform_params,
@@ -670,19 +769,10 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
struct snd_soc_dapm_widget *widget;
int i, ret;
- /* nothing to set up */
- if (!list)
+ /* nothing to set up or setup has been already done */
+ if (!list || spcm->setup_done[dir])
return 0;
- /*
- * Prepare widgets for set up. The prepare step is used to allocate memory, assign
- * instance ID and pick the widget configuration based on the runtime PCM params.
- */
- ret = sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params,
- dir, SOF_WIDGET_PREPARE);
- if (ret < 0)
- return ret;
-
/* Set up is used to send the IPC to the DSP to create the widget */
ret = sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params,
dir, SOF_WIDGET_SETUP);
@@ -737,6 +827,8 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
}
}
+ spcm->setup_done[dir] = true;
+
return 0;
widget_free:
@@ -754,18 +846,13 @@ int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int
int ret;
/* nothing to free */
- if (!list)
+ if (!list || !spcm->setup_done[dir])
return 0;
/* send IPC to free widget in the DSP */
ret = sof_walk_widgets_in_order(sdev, spcm, NULL, NULL, dir, SOF_WIDGET_FREE);
- /* unprepare the widget */
- sof_walk_widgets_in_order(sdev, spcm, NULL, NULL, dir, SOF_WIDGET_UNPREPARE);
-
- snd_soc_dapm_dai_free_widgets(&list);
- spcm->stream[dir].list = NULL;
-
+ spcm->setup_done[dir] = false;
pipeline_list->count = 0;
return ret;
diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h
index 03c8dd29e071..36082e764bf9 100644
--- a/sound/soc/sof/sof-audio.h
+++ b/sound/soc/sof/sof-audio.h
@@ -209,6 +209,7 @@ struct sof_ipc_tplg_widget_ops {
* @widget_setup: Function pointer for setting up setup in the DSP
* @widget_free: Function pointer for freeing widget in the DSP
* @dai_config: Function pointer for sending DAI config IPC to the DSP
+ * @host_config: Function pointer for setting the DMA ID for host widgets
* @dai_get_param: Function pointer for getting the DAI parameter
* @set_up_all_pipelines: Function pointer for setting up all topology pipelines
* @tear_down_all_pipelines: Function pointer for tearing down all topology pipelines
@@ -230,6 +231,8 @@ struct sof_ipc_tplg_ops {
int (*widget_free)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
int (*dai_config)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
unsigned int flags, struct snd_sof_dai_config_data *data);
+ void (*host_config)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
+ struct snd_sof_platform_stream_params *platform_params);
int (*dai_get_param)(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int param_type);
int (*set_up_all_pipelines)(struct snd_sof_dev *sdev, bool verify);
int (*tear_down_all_pipelines)(struct snd_sof_dev *sdev, bool verify);
@@ -351,7 +354,9 @@ struct snd_sof_pcm {
struct snd_sof_pcm_stream stream[2];
struct list_head list; /* list in sdev pcm list */
struct snd_pcm_hw_params params[2];
+ struct snd_sof_platform_stream_params platform_params[2];
bool prepared[2]; /* PCM_PARAMS set successfully */
+ bool setup_done[2]; /* the setup of the SOF PCM device is done */
bool pending_stop[2]; /* only used if (!pcm_ops->platform_stop_during_hw_free) */
/* Must be last - ends in a flex-array member. */
@@ -507,6 +512,9 @@ struct snd_sof_widget {
* @complete: flag used to indicate that pipeline set up is complete.
* @core_mask: Mask containing target cores for all modules in the pipeline
* @list: List item in sdev pipeline_list
+ * @direction_valid: flag indicating if the direction is set in topology
+ * @direction: pipeline direction set in topology, valid is direction_valid is true
+ *
*/
struct snd_sof_pipeline {
struct snd_sof_widget *pipe_widget;
@@ -515,6 +523,8 @@ struct snd_sof_pipeline {
int complete;
unsigned long core_mask;
struct list_head list;
+ bool direction_valid;
+ u32 direction;
};
/* ASoC SOF DAPM route */
@@ -673,6 +683,11 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
struct snd_pcm_hw_params *fe_params,
struct snd_sof_platform_stream_params *platform_params,
int dir);
+int sof_widget_list_prepare(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ int dir);
+void sof_widget_list_unprepare(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir);
int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir);
int sof_pcm_dsp_pcm_free(struct snd_pcm_substream *substream, struct snd_sof_dev *sdev,
struct snd_sof_pcm *spcm);