};
 
 /*
- * Unlike GP dma, there is a set of stream registers in hda controller
- * to control the link dma channels. Each register controls one link
- * dma channel and the relation is fixed. To make sure FW uses correct
- * link dma channels, host allocates stream registers and sends the
- * corresponding link dma channels to FW to allocate link dma channel
- *
- * FIXME: this API is abused in the sense that tx_num and rx_num are
- * passed as arguments, not returned. We need to find a better way to
- * retrieve the stream tag allocated for the link DMA
+ * This function checks if the host dma channel corresponding
+ * to the link DMA stream_tag argument is assigned to one
+ * of the FEs connected to the BE DAI.
  */
-static int hda_link_dma_get_channels(struct snd_soc_dai *dai,
-                                    unsigned int *tx_num,
-                                    unsigned int *tx_slot,
-                                    unsigned int *rx_num,
-                                    unsigned int *rx_slot)
+static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd,
+                         int dir, int stream_tag)
 {
-       struct hdac_bus *bus;
-       struct hdac_ext_stream *stream;
-       struct snd_pcm_substream substream;
-       struct snd_sof_dev *sdev =
-               snd_soc_component_get_drvdata(dai->component);
-
-       bus = sof_to_bus(sdev);
-
-       memset(&substream, 0, sizeof(substream));
-       if (*tx_num == 1) {
-               substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
-               stream = snd_hdac_ext_stream_assign(bus, &substream,
-                                                   HDAC_EXT_STREAM_TYPE_LINK);
-               if (!stream) {
-                       dev_err(bus->dev, "error: failed to find a free hda ext stream for playback");
-                       return -EBUSY;
-               }
+       struct snd_pcm_substream *fe_substream;
+       struct hdac_stream *fe_hstream;
+       struct snd_soc_dpcm *dpcm;
+
+       for_each_dpcm_fe(rtd, dir, dpcm) {
+               fe_substream = snd_soc_dpcm_get_substream(dpcm->fe, dir);
+               fe_hstream = fe_substream->runtime->private_data;
+               if (fe_hstream->stream_tag == stream_tag)
+                       return true;
+       }
 
-               snd_soc_dai_set_dma_data(dai, &substream, stream);
-               *tx_slot = hdac_stream(stream)->stream_tag - 1;
+       return false;
+}
+
+static struct hdac_ext_stream *
+       hda_link_stream_assign(struct hdac_bus *bus,
+                              struct snd_pcm_substream *substream)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct sof_intel_hda_stream *hda_stream;
+       struct hdac_ext_stream *res = NULL;
+       struct hdac_stream *stream = NULL;
 
-               dev_dbg(bus->dev, "link dma channel %d for playback", *tx_slot);
+       int stream_dir = substream->stream;
+
+       if (!bus->ppcap) {
+               dev_err(bus->dev, "stream type not supported\n");
+               return NULL;
        }
 
-       if (*rx_num == 1) {
-               substream.stream = SNDRV_PCM_STREAM_CAPTURE;
-               stream = snd_hdac_ext_stream_assign(bus, &substream,
-                                                   HDAC_EXT_STREAM_TYPE_LINK);
-               if (!stream) {
-                       dev_err(bus->dev, "error: failed to find a free hda ext stream for capture");
-                       return -EBUSY;
+       list_for_each_entry(stream, &bus->stream_list, list) {
+               struct hdac_ext_stream *hstream =
+                       stream_to_hdac_ext_stream(stream);
+               if (stream->direction != substream->stream)
+                       continue;
+
+               hda_stream = hstream_to_sof_hda_stream(hstream);
+
+               /* check if available */
+               if (!hstream->link_locked) {
+                       if (stream->opened) {
+                               /*
+                                * check if the stream tag matches the stream
+                                * tag of one of the connected FEs
+                                */
+                               if (hda_check_fes(rtd, stream_dir,
+                                                 stream->stream_tag)) {
+                                       res = hstream;
+                                       break;
+                               }
+                       } else {
+                               res = hstream;
+                               break;
+                       }
                }
+       }
 
-               snd_soc_dai_set_dma_data(dai, &substream, stream);
-               *rx_slot = hdac_stream(stream)->stream_tag - 1;
-
-               dev_dbg(bus->dev, "link dma channel %d for capture", *rx_slot);
+       if (res) {
+               /*
+                * Decouple host and link DMA. The decoupled flag
+                * is updated in snd_hdac_ext_stream_decouple().
+                */
+               if (!res->decoupled)
+                       snd_hdac_ext_stream_decouple(bus, res, true);
+               spin_lock_irq(&bus->reg_lock);
+               res->link_locked = 1;
+               res->link_substream = substream;
+               spin_unlock_irq(&bus->reg_lock);
        }
 
-       return 0;
+       return res;
 }
 
 static int hda_link_dma_params(struct hdac_ext_stream *stream,
        return 0;
 }
 
+/* Send DAI_CONFIG IPC to the DAI that matches the dai_name and direction */
+static int hda_link_config_ipc(struct sof_intel_hda_stream *hda_stream,
+                              const char *dai_name, int channel, int dir)
+{
+       struct sof_ipc_dai_config *config;
+       struct snd_sof_dai *sof_dai;
+       struct sof_ipc_reply reply;
+       int ret = 0;
+
+       list_for_each_entry(sof_dai, &hda_stream->sdev->dai_list, list) {
+               if (!sof_dai->cpu_dai_name)
+                       continue;
+
+               if (!strcmp(dai_name, sof_dai->cpu_dai_name) &&
+                   dir == sof_dai->comp_dai.direction) {
+                       config = sof_dai->dai_config;
+
+                       if (!config) {
+                               dev_err(hda_stream->sdev->dev,
+                                       "error: no config for DAI %s\n",
+                                       sof_dai->name);
+                               return -EINVAL;
+                       }
+
+                       /* update config with stream tag */
+                       config->hda.link_dma_ch = channel;
+
+                       /* send IPC */
+                       ret = sof_ipc_tx_message(hda_stream->sdev->ipc,
+                                                config->hdr.cmd,
+                                                config,
+                                                config->hdr.size,
+                                                &reply, sizeof(reply));
+
+                       if (ret < 0)
+                               dev_err(hda_stream->sdev->dev,
+                                       "error: failed to set dai config for %s\n",
+                                       sof_dai->name);
+                       return ret;
+               }
+       }
+
+       return -EINVAL;
+}
+
 static int hda_link_hw_params(struct snd_pcm_substream *substream,
                              struct snd_pcm_hw_params *params,
                              struct snd_soc_dai *dai)
        struct hda_pipe_params p_params = {0};
        struct hdac_ext_link *link;
        int stream_tag;
+       int ret;
 
-       link_dev = snd_soc_dai_get_dma_data(dai, substream);
+       link_dev = hda_link_stream_assign(bus, substream);
+       if (!link_dev)
+               return -EBUSY;
+
+       stream_tag = hdac_stream(link_dev)->stream_tag;
+
+       hda_stream = hstream_to_sof_hda_stream(link_dev);
+
+       /* update the DSP with the new tag */
+       ret = hda_link_config_ipc(hda_stream, dai->name, stream_tag - 1,
+                                 substream->stream);
+       if (ret < 0)
+               return ret;
+
+       snd_soc_dai_set_dma_data(dai, substream, (void *)link_dev);
 
-       hda_stream = container_of(link_dev, struct sof_intel_hda_stream,
-                                 hda_stream);
        hda_stream->hw_params_upon_resume = 0;
 
        link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name);
        if (!link)
                return -EINVAL;
 
-       stream_tag = hdac_stream(link_dev)->stream_tag;
-
-       /* set the stream tag in the codec dai dma params  */
+       /* set the stream tag in the codec dai dma params */
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                snd_soc_dai_set_tdm_slot(codec_dai, stream_tag, 0, 0, 0);
        else
        struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
        int stream = substream->stream;
 
-       hda_stream = container_of(link_dev, struct sof_intel_hda_stream,
-                                 hda_stream);
+       hda_stream = hstream_to_sof_hda_stream(link_dev);
 
        /* setup hw_params again only if resuming from system suspend */
        if (!hda_stream->hw_params_upon_resume)
 {
        struct hdac_ext_stream *link_dev =
                                snd_soc_dai_get_dma_data(dai, substream);
+       struct sof_intel_hda_stream *hda_stream;
+       struct snd_soc_pcm_runtime *rtd;
+       struct hdac_ext_link *link;
+       struct hdac_stream *hstream;
+       struct hdac_bus *bus;
+       int stream_tag;
        int ret;
 
+       hstream = substream->runtime->private_data;
+       bus = hstream->bus;
+       rtd = snd_pcm_substream_chip(substream);
+
+       link = snd_hdac_ext_bus_get_link(bus, rtd->codec_dai->component->name);
+       if (!link)
+               return -EINVAL;
+
+       hda_stream = hstream_to_sof_hda_stream(link_dev);
+
        dev_dbg(dai->dev, "In %s cmd=%d\n", __func__, cmd);
        switch (cmd) {
        case SNDRV_PCM_TRIGGER_RESUME:
        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
                snd_hdac_ext_link_stream_start(link_dev);
                break;
-       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
        case SNDRV_PCM_TRIGGER_SUSPEND:
+               /*
+                * clear and release link DMA channel. It will be assigned when
+                * hw_params is set up again after resume.
+                */
+               ret = hda_link_config_ipc(hda_stream, dai->name,
+                                         DMA_CHAN_INVALID, substream->stream);
+               if (ret < 0)
+                       return ret;
+               stream_tag = hdac_stream(link_dev)->stream_tag;
+               snd_hdac_ext_link_clear_stream_id(link, stream_tag);
+               snd_hdac_ext_stream_release(link_dev,
+                                           HDAC_EXT_STREAM_TYPE_LINK);
+
+               /* fallthrough */
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
        case SNDRV_PCM_TRIGGER_STOP:
                snd_hdac_ext_link_stream_clear(link_dev);
                break;
        return 0;
 }
 
-/*
- * FIXME: This API is also abused since it's used for two purposes.
- * when the substream argument is NULL this function is used for cleanups
- * that aren't necessarily required, and called explicitly by handling
- * ASoC core structures, which is not recommended.
- * This part will be reworked in follow-up patches.
- */
 static int hda_link_hw_free(struct snd_pcm_substream *substream,
                            struct snd_soc_dai *dai)
 {
-       const char *name;
        unsigned int stream_tag;
+       struct sof_intel_hda_stream *hda_stream;
        struct hdac_bus *bus;
        struct hdac_ext_link *link;
        struct hdac_stream *hstream;
-       struct hdac_ext_stream *stream;
        struct snd_soc_pcm_runtime *rtd;
        struct hdac_ext_stream *link_dev;
-       struct snd_pcm_substream pcm_substream;
-
-       memset(&pcm_substream, 0, sizeof(pcm_substream));
-       if (substream) {
-               hstream = substream->runtime->private_data;
-               bus = hstream->bus;
-               rtd = snd_pcm_substream_chip(substream);
-               link_dev = snd_soc_dai_get_dma_data(dai, substream);
-               snd_hdac_ext_stream_decouple(bus, link_dev, false);
-               name = rtd->codec_dai->component->name;
-               link = snd_hdac_ext_bus_get_link(bus, name);
-               if (!link)
-                       return -EINVAL;
-
-               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
-                       stream_tag = hdac_stream(link_dev)->stream_tag;
-                       snd_hdac_ext_link_clear_stream_id(link, stream_tag);
-               }
+       int ret;
 
-               link_dev->link_prepared = 0;
-       } else {
-               /* release all hda streams when dai link is unloaded */
-               pcm_substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
-               stream = snd_soc_dai_get_dma_data(dai, &pcm_substream);
-               if (stream) {
-                       snd_soc_dai_set_dma_data(dai, &pcm_substream, NULL);
-                       snd_hdac_ext_stream_release(stream,
-                                                   HDAC_EXT_STREAM_TYPE_LINK);
-               }
+       hstream = substream->runtime->private_data;
+       bus = hstream->bus;
+       rtd = snd_pcm_substream_chip(substream);
+       link_dev = snd_soc_dai_get_dma_data(dai, substream);
+       hda_stream = hstream_to_sof_hda_stream(link_dev);
 
-               pcm_substream.stream = SNDRV_PCM_STREAM_CAPTURE;
-               stream = snd_soc_dai_get_dma_data(dai, &pcm_substream);
-               if (stream) {
-                       snd_soc_dai_set_dma_data(dai, &pcm_substream, NULL);
-                       snd_hdac_ext_stream_release(stream,
-                                                   HDAC_EXT_STREAM_TYPE_LINK);
-               }
-       }
+       /* free the link DMA channel in the FW */
+       ret = hda_link_config_ipc(hda_stream, dai->name, DMA_CHAN_INVALID,
+                                 substream->stream);
+       if (ret < 0)
+               return ret;
+
+       link = snd_hdac_ext_bus_get_link(bus, rtd->codec_dai->component->name);
+       if (!link)
+               return -EINVAL;
+
+       stream_tag = hdac_stream(link_dev)->stream_tag;
+       snd_hdac_ext_link_clear_stream_id(link, stream_tag);
+       snd_hdac_ext_stream_release(link_dev, HDAC_EXT_STREAM_TYPE_LINK);
+       link_dev->link_prepared = 0;
 
        return 0;
 }
        .hw_free = hda_link_hw_free,
        .trigger = hda_link_pcm_trigger,
        .prepare = hda_link_pcm_prepare,
-       .get_channel_map = hda_link_dma_get_channels,
 };
 #endif