diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c
index 9c6b3938c542..c868da4751e7 100644
--- a/sound/soc/intel/avs/pcm.c
+++ b/sound/soc/intel/avs/pcm.c
@@ -10,6 +10,7 @@
 #include <linux/device.h>
 #include <sound/hda_register.h>
 #include <sound/hdaudio_ext.h>
+#include <sound/pcm_params.h>
 #include <sound/soc-acpi.h>
 #include <sound/soc-acpi-intel-match.h>
 #include <sound/soc-component.h>
@@ -29,6 +30,356 @@ struct avs_pcm_dma_data {
 	struct hdac_ext_stream *host_stream;
 };
 
+static struct avs_tplg_path_template *
+avs_pcm_find_path_template(struct snd_soc_dai *dai,
+			   struct snd_soc_component *component,
+			   bool fe, int direction)
+{
+	struct snd_soc_dapm_widget *dw;
+	struct snd_soc_dapm_path *dp;
+	enum snd_soc_dapm_direction dir;
+
+	if (direction == SNDRV_PCM_STREAM_CAPTURE) {
+		dw = dai->capture_widget;
+		dir = fe ? SND_SOC_DAPM_DIR_OUT : SND_SOC_DAPM_DIR_IN;
+	} else {
+		dw = dai->playback_widget;
+		dir = fe ? SND_SOC_DAPM_DIR_IN : SND_SOC_DAPM_DIR_OUT;
+	}
+
+	dp = list_first_entry_or_null(&dw->edges[dir], typeof(*dp), list_node[dir]);
+	if (!dp)
+		return NULL;
+
+	/* Get the other widget, with actual path template data */
+	dw = (dp->source == dw) ? dp->sink : dp->source;
+
+	return dw->priv;
+}
+
+static int avs_pcm_hw_params(struct avs_dev *adev,
+			     struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *fe_hw_params,
+			     struct snd_pcm_hw_params *be_hw_params,
+			     struct snd_soc_dai *dai, int dma_id)
+{
+	struct avs_pcm_dma_data *data;
+	struct avs_path *path;
+	int ret;
+
+	data = snd_soc_dai_get_dma_data(dai, substream);
+
+	dev_dbg(dai->dev, "%s FE hw_params str %p rtd %p",
+		__func__, substream, substream->runtime);
+	dev_dbg(dai->dev, "rate %d chn %d vbd %d bd %d\n",
+		params_rate(fe_hw_params), params_channels(fe_hw_params),
+		params_width(fe_hw_params),
+		params_physical_width(fe_hw_params));
+
+	dev_dbg(dai->dev, "%s BE hw_params str %p rtd %p",
+		__func__, substream, substream->runtime);
+	dev_dbg(dai->dev, "rate %d chn %d vbd %d bd %d\n",
+		params_rate(be_hw_params), params_channels(be_hw_params),
+		params_width(be_hw_params),
+		params_physical_width(be_hw_params));
+
+	path = avs_path_create(adev, dma_id, data->template, fe_hw_params, be_hw_params);
+	if (IS_ERR(path)) {
+		ret = PTR_ERR(path);
+		dev_err(dai->dev, "create BE path failed: %d\n", ret);
+		return ret;
+	}
+
+	data->path = path;
+	return 0;
+}
+
+static int avs_pcm_fe_hw_params(struct avs_dev *adev,
+				struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *fe_hw_params,
+				struct snd_soc_dai *dai, int dma_id)
+{
+	struct snd_pcm_hw_params *be_hw_params = NULL;
+	struct snd_soc_pcm_runtime *fe, *be;
+	struct snd_soc_dpcm *dpcm;
+
+	fe = asoc_substream_to_rtd(substream);
+	for_each_dpcm_be(fe, substream->stream, dpcm) {
+		be = dpcm->be;
+		be_hw_params = &be->dpcm[substream->stream].hw_params;
+	}
+
+	return avs_pcm_hw_params(adev, substream, fe_hw_params, be_hw_params,
+				 dai, dma_id);
+}
+
+static int avs_dai_prepare(struct avs_dev *adev,
+			   struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *dai)
+{
+	struct avs_pcm_dma_data *data;
+	int ret;
+
+	data = snd_soc_dai_get_dma_data(dai, substream);
+	if (!data->path)
+		return 0;
+
+	ret = avs_path_reset(data->path);
+	if (ret < 0) {
+		dev_err(dai->dev, "reset path failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = avs_path_pause(data->path);
+	if (ret < 0)
+		dev_err(dai->dev, "pause path failed: %d\n", ret);
+	return ret;
+}
+
+static const unsigned int rates[] = {
+	8000, 11025, 12000, 16000,
+	22050, 24000, 32000, 44100,
+	48000, 64000, 88200, 96000,
+	128000, 176400, 192000,
+};
+
+static const struct snd_pcm_hw_constraint_list hw_rates = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+	.mask = 0,
+};
+
+static int avs_dai_fe_startup(struct snd_pcm_substream *substream,
+			      struct snd_soc_dai *dai)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct avs_dev *adev = to_avs_dev(dai->dev);
+	struct avs_tplg_path_template *template;
+	struct avs_pcm_dma_data *data;
+	struct hdac_bus *bus = &adev->base.core;
+	struct hdac_ext_stream *estream;
+
+	template = avs_pcm_find_path_template(dai, dai->component, true,
+					      substream->stream);
+	if (!template) {
+		dev_err(dai->dev, "no %s path for dai %s, invalid tplg?\n",
+			snd_pcm_stream_str(substream), dai->name);
+		return -EINVAL;
+	}
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	estream = snd_hdac_ext_stream_assign(bus, substream,
+					     HDAC_EXT_STREAM_TYPE_HOST);
+	if (!estream) {
+		kfree(data);
+		return -EBUSY;
+	}
+
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	/* avoid wrap-around with wall-clock */
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_TIME,
+				     20, 178000000);
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				   &hw_rates);
+
+	dev_dbg(dai->dev, "%s fe STARTUP tag %d str %p",
+		__func__, hdac_stream(estream)->stream_tag, substream);
+
+	data->host_stream = estream;
+	data->template = template;
+	snd_soc_dai_set_dma_data(dai, substream, data);
+	snd_pcm_set_sync(substream);
+
+	return 0;
+}
+
+static void avs_dai_fe_shutdown(struct snd_pcm_substream *substream,
+				struct snd_soc_dai *dai)
+{
+	struct avs_pcm_dma_data *data;
+
+	data = snd_soc_dai_get_dma_data(dai, substream);
+
+	snd_soc_dai_set_dma_data(dai, substream, NULL);
+	snd_hdac_ext_stream_release(data->host_stream, HDAC_EXT_STREAM_TYPE_HOST);
+	kfree(data);
+}
+
+static int avs_dai_fe_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *hw_params,
+				struct snd_soc_dai *dai)
+{
+	struct avs_pcm_dma_data *data;
+	struct avs_dev *adev = to_avs_dev(dai->dev);
+	struct hdac_stream *hstream;
+	int ret;
+
+	data = snd_soc_dai_get_dma_data(dai, substream);
+	if (data->path)
+		return 0;
+
+	hstream = hdac_stream(data->host_stream);
+	hstream->bufsize = 0;
+	hstream->period_bytes = 0;
+	hstream->format_val = 0;
+
+	ret = avs_pcm_fe_hw_params(adev, substream, hw_params, dai,
+				   hstream->stream_tag - 1);
+	if (ret)
+		goto create_err;
+
+	ret = avs_path_bind(data->path);
+	if (ret < 0) {
+		dev_err(dai->dev, "bind FE <-> BE failed: %d\n", ret);
+		goto bind_err;
+	}
+
+	return 0;
+
+bind_err:
+	avs_path_free(data->path);
+	data->path = NULL;
+create_err:
+	snd_pcm_lib_free_pages(substream);
+	return ret;
+}
+
+static int avs_dai_fe_hw_free(struct snd_pcm_substream *substream,
+			      struct snd_soc_dai *dai)
+{
+	struct avs_pcm_dma_data *data;
+	struct hdac_stream *hstream;
+	int ret;
+
+	dev_dbg(dai->dev, "%s fe HW_FREE str %p rtd %p",
+		__func__, substream, substream->runtime);
+
+	data = snd_soc_dai_get_dma_data(dai, substream);
+	if (!data->path)
+		return 0;
+
+	hstream = hdac_stream(data->host_stream);
+
+	ret = avs_path_unbind(data->path);
+	if (ret < 0)
+		dev_err(dai->dev, "unbind FE <-> BE failed: %d\n", ret);
+
+	avs_path_free(data->path);
+	data->path = NULL;
+	snd_hdac_stream_cleanup(hstream);
+	hstream->prepared = false;
+
+	ret = snd_pcm_lib_free_pages(substream);
+	if (ret < 0)
+		dev_dbg(dai->dev, "Failed to free pages!\n");
+
+	return ret;
+}
+
+static int avs_dai_fe_prepare(struct snd_pcm_substream *substream,
+			      struct snd_soc_dai *dai)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct avs_pcm_dma_data *data;
+	struct avs_dev *adev = to_avs_dev(dai->dev);
+	struct hdac_stream *hstream;
+	struct hdac_bus *bus;
+	unsigned int format_val;
+	int ret;
+
+	data = snd_soc_dai_get_dma_data(dai, substream);
+	hstream = hdac_stream(data->host_stream);
+
+	if (hstream->prepared)
+		return 0;
+
+	bus = hstream->bus;
+	snd_hdac_ext_stream_decouple(bus, data->host_stream, true);
+	snd_hdac_stream_reset(hstream);
+
+	format_val = snd_hdac_calc_stream_format(runtime->rate,
+						 runtime->channels,
+						 runtime->format,
+						 runtime->sample_bits, 0);
+
+	ret = snd_hdac_stream_set_params(hstream, format_val);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_hdac_stream_setup(hstream);
+	if (ret < 0)
+		return ret;
+
+	ret = avs_dai_prepare(adev, substream, dai);
+	if (ret)
+		return ret;
+
+	hstream->prepared = true;
+	return 0;
+}
+
+static int avs_dai_fe_trigger(struct snd_pcm_substream *substream,
+			      int cmd, struct snd_soc_dai *dai)
+{
+	struct avs_pcm_dma_data *data;
+	struct hdac_stream *hstream;
+	struct hdac_bus *bus;
+	unsigned long flags;
+	int ret = 0;
+
+	data = snd_soc_dai_get_dma_data(dai, substream);
+	hstream = hdac_stream(data->host_stream);
+	bus = hstream->bus;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		spin_lock_irqsave(&bus->reg_lock, flags);
+		snd_hdac_stream_start(hstream, true);
+		spin_unlock_irqrestore(&bus->reg_lock, flags);
+
+		ret = avs_path_run(data->path, AVS_TPLG_TRIGGER_AUTO);
+		if (ret < 0)
+			dev_err(dai->dev, "run FE path failed: %d\n", ret);
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_STOP:
+		ret = avs_path_pause(data->path);
+		if (ret < 0)
+			dev_err(dai->dev, "pause FE path failed: %d\n", ret);
+
+		spin_lock_irqsave(&bus->reg_lock, flags);
+		snd_hdac_stream_stop(hstream);
+		spin_unlock_irqrestore(&bus->reg_lock, flags);
+
+		if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+			ret = avs_path_reset(data->path);
+			if (ret < 0)
+				dev_err(dai->dev, "reset FE path failed: %d\n", ret);
+		}
+		break;
+
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+const struct snd_soc_dai_ops avs_dai_fe_ops = {
+	.startup = avs_dai_fe_startup,
+	.shutdown = avs_dai_fe_shutdown,
+	.hw_params = avs_dai_fe_hw_params,
+	.hw_free = avs_dai_fe_hw_free,
+	.prepare = avs_dai_fe_prepare,
+	.trigger = avs_dai_fe_trigger,
+};
+
 static ssize_t topology_name_read(struct file *file, char __user *user_buf,
 				  size_t count, loff_t *ppos)
 {
diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c
index ae267a3b7127..64f2a1e0c68a 100644
--- a/sound/soc/intel/avs/topology.c
+++ b/sound/soc/intel/avs/topology.c
@@ -15,8 +15,6 @@
 #include "avs.h"
 #include "topology.h"
 
-const struct snd_soc_dai_ops avs_dai_fe_ops;
-
 /* Vendor tuples manipulation helpers */
 #define avs_tplg_vendor_array_at(array, offset) \
 	((struct snd_soc_tplg_vendor_array *)((u8 *)array + offset))
