ASoC: SOF: sof-audio: Set up widgets from source to sink
authorRanjani Sridharan <ranjani.sridharan@linux.intel.com>
Tue, 26 Apr 2022 17:17:35 +0000 (10:17 -0700)
committerMark Brown <broonie@kernel.org>
Fri, 29 Apr 2022 12:05:56 +0000 (13:05 +0100)
For IPC3, the order of setting up the widgets associated with a PCM
doesn't matter. But for IPC4, widgets must be set up from the source to
the sink in order. In order to accommodate this, change the
sof_widget_list_setup/free() functions to set up/free widgets starting
with the source widget all the way to the sink widget for all pipelines.

Signed-off-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Reviewed-by: Bard Liao <yung-chuan.liao@linux.intel.com>
Reviewed-by: Péter Ujfalusi <peter.ujfalusi@linux.intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Reviewed-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Link: https://lore.kernel.org/r/20220426171743.171061-4-ranjani.sridharan@linux.intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/sof/sof-audio.c
sound/soc/sof/sof-audio.h

index 453e1ed..6e43f1a 100644 (file)
@@ -253,29 +253,135 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
        return 0;
 }
 
+/*
+ * free all widgets in the sink path starting from the source widget
+ * (DAI type for capture, AIF type for playback)
+ */
+static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
+                                   int dir)
+{
+       struct snd_soc_dapm_path *p;
+       int err;
+       int ret = 0;
+
+       /* free all widgets even in case of error to keep use counts balanced */
+       snd_soc_dapm_widget_for_each_sink_path(widget, p) {
+               if (!p->walking && p->sink->dobj.private && widget->dobj.private) {
+                       p->walking = true;
+                       if (WIDGET_IS_AIF_OR_DAI(widget->id)) {
+                               err = sof_widget_free(sdev, widget->dobj.private);
+                               if (err < 0)
+                                       ret = err;
+                       }
+
+                       err = sof_widget_free(sdev, p->sink->dobj.private);
+                       if (err < 0)
+                               ret = err;
+
+                       err = sof_free_widgets_in_path(sdev, p->sink, dir);
+                       if (err < 0)
+                               ret = err;
+                       p->walking = false;
+               }
+       }
+
+       return ret;
+}
+
+/*
+ * set up all widgets in the sink path starting from the source widget
+ * (DAI type for capture, AIF type for playback).
+ * The error path in this function ensures that all successfully set up widgets getting freed.
+ */
+static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
+                                     int dir)
+{
+       struct snd_soc_dapm_path *p;
+       int ret;
+
+       snd_soc_dapm_widget_for_each_sink_path(widget, p) {
+               if (!p->walking && p->sink->dobj.private && widget->dobj.private) {
+                       p->walking = true;
+                       if (WIDGET_IS_AIF_OR_DAI(widget->id)) {
+                               ret = sof_widget_setup(sdev, widget->dobj.private);
+                               if (ret < 0)
+                                       goto out;
+                       }
+
+                       ret = sof_widget_setup(sdev, p->sink->dobj.private);
+                       if (ret < 0) {
+                               if (WIDGET_IS_AIF_OR_DAI(widget->id))
+                                       sof_widget_free(sdev, widget->dobj.private);
+                               goto out;
+                       }
+
+                       ret = sof_set_up_widgets_in_path(sdev, p->sink, dir);
+                       if (ret < 0) {
+                               if (WIDGET_IS_AIF_OR_DAI(widget->id))
+                                       sof_widget_free(sdev, widget->dobj.private);
+                               sof_widget_free(sdev, p->sink->dobj.private);
+                       }
+out:
+                       p->walking = false;
+                       if (ret < 0)
+                               return ret;
+               }
+       }
+
+       return 0;
+}
+
+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)
+{
+       struct snd_soc_dapm_widget *widget;
+       int ret, i;
+
+       for_each_dapm_widgets(list, i, widget) {
+               /* starting widget for playback is AIF type */
+               if (dir == SNDRV_PCM_STREAM_PLAYBACK && !WIDGET_IS_AIF(widget->id))
+                       continue;
+
+               /* starting widget for capture is DAI type */
+               if (dir == SNDRV_PCM_STREAM_CAPTURE && !WIDGET_IS_DAI(widget->id))
+                       continue;
+
+               switch (op) {
+               case SOF_WIDGET_SETUP:
+                       ret = sof_set_up_widgets_in_path(sdev, widget, dir);
+                       break;
+               case SOF_WIDGET_FREE:
+                       ret = sof_free_widgets_in_path(sdev, widget, dir);
+                       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");
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
 int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, 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;
        struct snd_soc_dapm_widget *widget;
-       int i, ret, num_widgets;
+       int i, ret;
 
        /* nothing to set up */
        if (!list)
                return 0;
 
-       /* set up widgets in the list */
-       for_each_dapm_widgets(list, num_widgets, widget) {
-               struct snd_sof_widget *swidget = widget->dobj.private;
-
-               if (!swidget)
-                       continue;
-
-               /* set up the widget */
-               ret = sof_widget_setup(sdev, swidget);
-               if (ret < 0)
-                       goto widget_free;
-       }
+       ret = sof_setup_or_free_widgets_in_order(sdev, list, dir, SOF_WIDGET_SETUP);
+       if (ret < 0)
+               return ret;
 
        /*
         * error in setting pipeline connections will result in route status being reset for
@@ -316,18 +422,7 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, in
        return 0;
 
 widget_free:
-       /* free all widgets that have been set up successfully */
-       for_each_dapm_widgets(list, i, widget) {
-               struct snd_sof_widget *swidget = widget->dobj.private;
-
-               if (!swidget)
-                       continue;
-
-               if (!num_widgets--)
-                       break;
-
-               sof_widget_free(sdev, swidget);
-       }
+       sof_setup_or_free_widgets_in_order(sdev, list, dir, SOF_WIDGET_FREE);
 
        return ret;
 }
@@ -335,37 +430,18 @@ widget_free:
 int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir)
 {
        struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
-       struct snd_soc_dapm_widget *widget;
-       int i, ret;
-       int ret1 = 0;
+       int ret;
 
        /* nothing to free */
        if (!list)
                return 0;
 
-       /*
-        * Free widgets in the list. This can fail but continue freeing other widgets to keep
-        * use_counts balanced.
-        */
-       for_each_dapm_widgets(list, i, widget) {
-               struct snd_sof_widget *swidget = widget->dobj.private;
-
-               if (!swidget)
-                       continue;
-
-               /*
-                * free widget and its pipe_widget. Either of these can fail, but free as many as
-                * possible before freeing the list and returning the error.
-                */
-               ret = sof_widget_free(sdev, swidget);
-               if (ret < 0)
-                       ret1 = ret;
-       }
+       ret = sof_setup_or_free_widgets_in_order(sdev, list, dir, SOF_WIDGET_FREE);
 
        snd_soc_dapm_dai_free_widgets(&list);
        spcm->stream[dir].list = NULL;
 
-       return ret1;
+       return ret;
 }
 
 /*
index d8ab860..88def70 100644 (file)
 #define DMA_CHAN_INVALID       0xFFFFFFFF
 
 #define WIDGET_IS_DAI(id) ((id) == snd_soc_dapm_dai_in || (id) == snd_soc_dapm_dai_out)
+#define WIDGET_IS_AIF(id) ((id) == snd_soc_dapm_aif_in || (id) == snd_soc_dapm_aif_out)
+#define WIDGET_IS_AIF_OR_DAI(id) (WIDGET_IS_DAI(id) || WIDGET_IS_AIF(id))
 
 #define SOF_DAI_CLK_INTEL_SSP_MCLK     0
 #define SOF_DAI_CLK_INTEL_SSP_BCLK     1
 
+enum sof_widget_op {
+       SOF_WIDGET_FREE,
+       SOF_WIDGET_SETUP,
+};
+
 /*
  * Volume fractional word length define to 16 sets
  * the volume linear gain value to use Qx.16 format