@@ -84,7 +84,8 @@ EXPORT_SYMBOL(snd_sof_pcm_period_elapsed);
static int
sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd,
- struct snd_sof_pcm *spcm, int dir)
+ struct snd_sof_pcm *spcm, struct snd_pcm_hw_params *params,
+ struct snd_sof_platform_stream_params *platform_params, int dir)
{
struct snd_soc_dai *dai;
int ret, j;
@@ -103,7 +104,7 @@ 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, dir);
+ ret = sof_widget_list_setup(sdev, spcm, params, platform_params, dir);
if (ret < 0) {
dev_err(sdev->dev, "error: failed widget list set up for pcm %d dir %d\n",
spcm->pcm.pcm_id, dir);
@@ -159,7 +160,8 @@ 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, substream->stream);
+ ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, params, &platform_params,
+ substream->stream);
if (ret < 0)
return ret;
}
@@ -257,6 +257,79 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
return 0;
}
+static void
+sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget)
+{
+ const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg;
+ const struct sof_ipc_tplg_widget_ops *widget_ops = ipc_tplg_ops->widget;
+ struct snd_sof_widget *swidget = widget->dobj.private;
+ struct snd_soc_dapm_path *p;
+
+ if (!widget_ops[widget->id].ipc_unprepare || !swidget->prepared)
+ goto sink_unprepare;
+
+ /* unprepare the source widget */
+ widget_ops[widget->id].ipc_unprepare(swidget);
+ swidget->prepared = false;
+
+sink_unprepare:
+ /* unprepare all widgets in the sink paths */
+ snd_soc_dapm_widget_for_each_sink_path(widget, p) {
+ if (!p->walking && p->sink->dobj.private) {
+ p->walking = true;
+ sof_unprepare_widgets_in_path(sdev, p->sink);
+ p->walking = false;
+ }
+ }
+}
+
+static int
+sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ struct snd_pcm_hw_params *pipeline_params, int dir)
+{
+ const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg;
+ const struct sof_ipc_tplg_widget_ops *widget_ops = ipc_tplg_ops->widget;
+ struct snd_sof_widget *swidget = widget->dobj.private;
+ struct snd_soc_dapm_path *p;
+ int ret;
+
+ if (!widget_ops[widget->id].ipc_prepare || swidget->prepared)
+ goto sink_prepare;
+
+ /* prepare the source widget */
+ ret = widget_ops[widget->id].ipc_prepare(swidget, fe_params, platform_params,
+ pipeline_params, dir);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to prepare widget %s\n", widget->name);
+ return ret;
+ }
+
+ swidget->prepared = true;
+
+sink_prepare:
+ /* prepare all widgets in the sink paths */
+ snd_soc_dapm_widget_for_each_sink_path(widget, p) {
+ if (!p->walking && p->sink->dobj.private) {
+ p->walking = true;
+ ret = sof_prepare_widgets_in_path(sdev, p->sink, fe_params,
+ platform_params, pipeline_params, dir);
+ p->walking = false;
+ if (ret < 0) {
+ /* unprepare the source widget */
+ if (!widget_ops[widget->id].ipc_unprepare && swidget->prepared) {
+ widget_ops[widget->id].ipc_unprepare(swidget);
+ swidget->prepared = false;
+ }
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
/*
* free all widgets in the sink path starting from the source widget
* (DAI type for capture, AIF type for playback)
@@ -336,11 +409,15 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d
}
static int
-sof_setup_or_free_widgets_in_order(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget_list *list,
- bool dir, enum sof_widget_op op)
+sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget_list *list,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params, int dir,
+ enum sof_widget_op op)
{
struct snd_soc_dapm_widget *widget;
- int ret, i;
+ char *str;
+ int ret = 0;
+ int i;
for_each_dapm_widgets(list, i, widget) {
/* starting widget for playback is AIF type */
@@ -354,17 +431,38 @@ sof_setup_or_free_widgets_in_order(struct snd_sof_dev *sdev, struct snd_soc_dapm
switch (op) {
case SOF_WIDGET_SETUP:
ret = sof_set_up_widgets_in_path(sdev, widget, dir);
+ str = "set up";
break;
case SOF_WIDGET_FREE:
ret = sof_free_widgets_in_path(sdev, widget, dir);
+ str = "free";
+ break;
+ case SOF_WIDGET_PREPARE:
+ {
+ struct snd_pcm_hw_params pipeline_params;
+
+ str = "prepare";
+ /*
+ * When walking the list of connected widgets, the pipeline_params for each
+ * widget is modified by the source widget in the path. Use a local
+ * copy of the runtime params as the pipeline_params so that the runtime
+ * params does not get overwritten.
+ */
+ memcpy(&pipeline_params, fe_params, sizeof(*fe_params));
+
+ ret = sof_prepare_widgets_in_path(sdev, widget, fe_params,
+ platform_params, &pipeline_params, dir);
+ break;
+ }
+ case SOF_WIDGET_UNPREPARE:
+ sof_unprepare_widgets_in_path(sdev, widget);
break;
default:
dev_err(sdev->dev, "Invalid widget op %d\n", op);
return -EINVAL;
}
if (ret < 0) {
- dev_err(sdev->dev, "Failed to %s connected widgets\n",
- op == SOF_WIDGET_SETUP ? "set up" : "free");
+ dev_err(sdev->dev, "Failed to %s connected widgets\n", str);
return ret;
}
}
@@ -372,7 +470,10 @@ sof_setup_or_free_widgets_in_order(struct snd_sof_dev *sdev, struct snd_soc_dapm
return 0;
}
-int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir)
+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)
{
const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg;
struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
@@ -383,10 +484,24 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, in
if (!list)
return 0;
- ret = sof_setup_or_free_widgets_in_order(sdev, list, dir, SOF_WIDGET_SETUP);
+ /*
+ * 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, list, 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, list, fe_params, platform_params,
+ dir, SOF_WIDGET_SETUP);
+ if (ret < 0) {
+ ret = sof_walk_widgets_in_order(sdev, list, fe_params, platform_params,
+ dir, SOF_WIDGET_UNPREPARE);
+ return ret;
+ }
+
/*
* error in setting pipeline connections will result in route status being reset for
* routes that were successfully set up when the widgets are freed.
@@ -426,7 +541,9 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, in
return 0;
widget_free:
- sof_setup_or_free_widgets_in_order(sdev, list, dir, SOF_WIDGET_FREE);
+ sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, dir,
+ SOF_WIDGET_FREE);
+ sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_UNPREPARE);
return ret;
}
@@ -440,7 +557,11 @@ int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int
if (!list)
return 0;
- ret = sof_setup_or_free_widgets_in_order(sdev, list, dir, SOF_WIDGET_FREE);
+ /* send IPC to free widget in the DSP */
+ ret = sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_FREE);
+
+ /* unprepare the widget */
+ sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_UNPREPARE);
snd_soc_dapm_dai_free_widgets(&list);
spcm->stream[dir].list = NULL;
@@ -36,8 +36,10 @@
#define SOF_DAI_CLK_INTEL_SSP_BCLK 1
enum sof_widget_op {
- SOF_WIDGET_FREE,
+ SOF_WIDGET_PREPARE,
SOF_WIDGET_SETUP,
+ SOF_WIDGET_FREE,
+ SOF_WIDGET_UNPREPARE,
};
/*
@@ -129,6 +131,8 @@ struct sof_ipc_tplg_control_ops {
* @token_list: List of token ID's that should be parsed for the widget
* @token_list_size: number of elements in token_list
* @bind_event: Function pointer for binding events to the widget
+ * @ipc_prepare: Optional op for preparing a widget for set up
+ * @ipc_unprepare: Optional op for unpreparing a widget
*/
struct sof_ipc_tplg_widget_ops {
int (*ipc_setup)(struct snd_sof_widget *swidget);
@@ -137,6 +141,11 @@ struct sof_ipc_tplg_widget_ops {
int token_list_size;
int (*bind_event)(struct snd_soc_component *scomp, struct snd_sof_widget *swidget,
u16 event_type);
+ int (*ipc_prepare)(struct snd_sof_widget *swidget,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ struct snd_pcm_hw_params *source_params, int dir);
+ void (*ipc_unprepare)(struct snd_sof_widget *swidget);
};
/**
@@ -332,6 +341,11 @@ struct snd_sof_widget {
* widgets. It is unused for all other widget types.
*/
int complete;
+ /*
+ * the prepared flag is used to indicate that a widget has been prepared for getting set
+ * up in the DSP.
+ */
+ bool prepared;
int use_count; /* use_count will be protected by the PCM mutex held by the core */
int core;
int id; /* id is the DAPM widget type */
@@ -492,7 +506,10 @@ int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsourc
struct snd_soc_dapm_widget *wsink);
/* PCM */
-int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir);
+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_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);