diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c
index 095ca0d9d5ae..a76d0b5b2ad9 100644
--- a/sound/soc/sof/pcm.c
+++ b/sound/soc/sof/pcm.c
@@ -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;
 	}
diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c
index f7c9e5db3d9e..26a62b3b3c37 100644
--- a/sound/soc/sof/sof-audio.c
+++ b/sound/soc/sof/sof-audio.c
@@ -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;
diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h
index 2db4eb5edcf5..195248d11aaa 100644
--- a/sound/soc/sof/sof-audio.h
+++ b/sound/soc/sof/sof-audio.h
@@ -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);
