* prepare ioctl before the START trigger.
  */
 
+/*
+ * Chained DMA is a special case where there is no processing on
+ * DSP. The samples are just moved over by host side DMA to a single
+ * buffer on DSP and directly from there to link DMA. However, the
+ * model on SOF driver has two notional pipelines, one at host DAI,
+ * and another at link DAI. They both shall have the use_chain_dma
+ * attribute.
+ */
+
+static int sof_ipc4_chain_dma_trigger(struct snd_sof_dev *sdev,
+                                     struct snd_sof_pcm_stream_pipeline_list *pipeline_list,
+                                     int state, int cmd)
+{
+       bool allocate, enable, set_fifo_size;
+       struct sof_ipc4_msg msg = {{ 0 }};
+       int i;
+
+       switch (state) {
+       case SOF_IPC4_PIPE_RUNNING: /* Allocate and start chained dma */
+               allocate = true;
+               enable = true;
+               /*
+                * SOF assumes creation of a new stream from the presence of fifo_size
+                * in the message, so we must leave it out in pause release case.
+                */
+               if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE)
+                       set_fifo_size = false;
+               else
+                       set_fifo_size = true;
+               break;
+       case SOF_IPC4_PIPE_PAUSED: /* Disable chained DMA. */
+               allocate = true;
+               enable = false;
+               set_fifo_size = false;
+               break;
+       case SOF_IPC4_PIPE_RESET: /* Disable and free chained DMA. */
+               allocate = false;
+               enable = false;
+               set_fifo_size = false;
+               break;
+       default:
+               dev_err(sdev->dev, "Unexpected state %d", state);
+               return -EINVAL;
+       }
+
+       msg.primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_CHAIN_DMA);
+       msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+       msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
+
+       /*
+        * To set-up the DMA chain, the host DMA ID and SCS setting
+        * are retrieved from the host pipeline configuration. Likewise
+        * the link DMA ID and fifo_size are retrieved from the link
+        * pipeline configuration.
+        */
+       for (i = 0; i < pipeline_list->count; i++) {
+               struct snd_sof_pipeline *spipe = pipeline_list->pipelines[i];
+               struct snd_sof_widget *pipe_widget = spipe->pipe_widget;
+               struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+               if (!pipeline->use_chain_dma) {
+                       dev_err(sdev->dev,
+                               "All pipelines in chained DMA stream should have use_chain_dma attribute set.");
+                       return -EINVAL;
+               }
+
+               msg.primary |= pipeline->msg.primary;
+
+               /* Add fifo_size (actually DMA buffer size) field to the message */
+               if (set_fifo_size)
+                       msg.extension |= pipeline->msg.extension;
+       }
+
+       if (allocate)
+               msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ALLOCATE_MASK;
+
+       if (enable)
+               msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ENABLE_MASK;
+
+       return sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0);
+}
+
 static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component,
                                      struct snd_pcm_substream *substream, int state, int cmd)
 {
        struct snd_sof_pcm_stream_pipeline_list *pipeline_list;
        struct sof_ipc4_fw_data *ipc4_data = sdev->private;
        struct ipc4_pipeline_set_state_data *trigger_list;
+       struct snd_sof_widget *pipe_widget;
+       struct sof_ipc4_pipeline *pipeline;
        struct snd_sof_pipeline *spipe;
        struct snd_sof_pcm *spcm;
        int ret;
        if (!pipeline_list->pipelines || !pipeline_list->count)
                return 0;
 
+       spipe = pipeline_list->pipelines[0];
+       pipe_widget = spipe->pipe_widget;
+       pipeline = pipe_widget->private;
+
+       /*
+        * If use_chain_dma attribute is set we proceed to chained DMA
+        * trigger function that handles the rest for the substream.
+        */
+       if (pipeline->use_chain_dma)
+               return sof_ipc4_chain_dma_trigger(sdev, pipeline_list, state, cmd);
+
        /* allocate memory for the pipeline data */
        trigger_list = kzalloc(struct_size(trigger_list, pipeline_ids, pipeline_list->count),
                               GFP_KERNEL);
        struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
        struct snd_sof_dai *dai = snd_sof_find_dai(component, rtd->dai_link->name);
        struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+       struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
        struct sof_ipc4_copier *ipc4_copier;
-       int ret;
+       bool use_chain_dma = false;
+       int dir;
 
        if (!dai) {
                dev_err(component->dev, "%s: No DAI found with name %s\n", __func__,
                return -EINVAL;
        }
 
-       ret = sof_ipc4_pcm_dai_link_fixup_rate(sdev, params, ipc4_copier);
-       if (ret)
-               return ret;
+       for_each_pcm_streams(dir) {
+               struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, dir);
+
+               if (w) {
+                       struct snd_sof_widget *swidget = w->dobj.private;
+                       struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
+                       struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+                       if (pipeline->use_chain_dma)
+                               use_chain_dma = true;
+               }
+       }
+
+       /* Chain DMA does not use copiers, so no fixup needed */
+       if (!use_chain_dma) {
+               int ret = sof_ipc4_pcm_dai_link_fixup_rate(sdev, params, ipc4_copier);
+
+               if (ret)
+                       return ret;
+       }
 
        switch (ipc4_copier->dai_type) {
        case SOF_DAI_INTEL_SSP:
 
 
 #define SOF_IPC4_GAIN_PARAM_ID  0
 #define SOF_IPC4_TPLG_ABI_SIZE 6
+#define SOF_IPC4_CHAIN_DMA_BUF_SIZE_MS 2
 
 static DEFINE_IDA(alh_group_ida);
 static DEFINE_IDA(pipeline_ida);
 static const struct sof_topology_token ipc4_sched_tokens[] = {
        {SOF_TKN_SCHED_LP_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
                offsetof(struct sof_ipc4_pipeline, lp_mode)},
+       {SOF_TKN_SCHED_USE_CHAIN_DMA, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16,
+               offsetof(struct sof_ipc4_pipeline, use_chain_dma)},
        {SOF_TKN_SCHED_CORE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
                offsetof(struct sof_ipc4_pipeline, core_id)},
 };
        struct snd_soc_component *scomp = swidget->scomp;
        struct snd_sof_dai *dai = swidget->private;
        struct sof_ipc4_copier *ipc4_copier;
+       struct snd_sof_widget *pipe_widget;
+       struct sof_ipc4_pipeline *pipeline;
        int node_type = 0;
        int ret;
 
 
        ipc4_copier->data.gtw_cfg.node_id = SOF_IPC4_NODE_TYPE(node_type);
 
+       pipe_widget = swidget->spipe->pipe_widget;
+       pipeline = pipe_widget->private;
+       if (pipeline->use_chain_dma && ipc4_copier->dai_type != SOF_DAI_INTEL_HDA) {
+               dev_err(scomp->dev,
+                       "Bad DAI type '%d', Chained DMA is only supported by HDA DAIs (%d).\n",
+                       ipc4_copier->dai_type, SOF_DAI_INTEL_HDA);
+               ret = -ENODEV;
+               goto free_available_fmt;
+       }
+
        switch (ipc4_copier->dai_type) {
        case SOF_DAI_INTEL_ALH:
        {
 
        swidget->core = pipeline->core_id;
 
+       if (pipeline->use_chain_dma) {
+               dev_dbg(scomp->dev, "Set up chain DMA for %s\n", swidget->widget->name);
+               swidget->private = pipeline;
+               return 0;
+       }
+
        /* parse one set of pipeline tokens */
        ret = sof_update_ipc_object(scomp, swidget, SOF_PIPELINE_TOKENS, swidget->tuples,
                                    swidget->num_tuples, sizeof(*swidget), 1);
        pipeline->mem_usage = 0;
 
        if (WIDGET_IS_AIF(swidget->id) || swidget->id == snd_soc_dapm_buffer) {
+               if (pipeline->use_chain_dma) {
+                       pipeline->msg.primary = 0;
+                       pipeline->msg.extension = 0;
+               }
                ipc4_copier = swidget->private;
        } else if (WIDGET_IS_DAI(swidget->id)) {
                struct snd_sof_dai *dai = swidget->private;
 
                ipc4_copier = dai->private;
+
+               if (pipeline->use_chain_dma) {
+                       pipeline->msg.primary = 0;
+                       pipeline->msg.extension = 0;
+               }
+
                if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) {
                        struct sof_ipc4_copier_data *copier_data = &ipc4_copier->data;
                        struct sof_ipc4_alh_configuration_blob *blob;
                        return ret;
                }
 
-               pipe_widget = swidget->spipe->pipe_widget;
-               pipeline = pipe_widget->private;
                ipc4_copier = (struct sof_ipc4_copier *)swidget->private;
                gtw_attr = ipc4_copier->gtw_attr;
                copier_data = &ipc4_copier->data;
                available_fmt = &ipc4_copier->available_fmt;
 
+               pipe_widget = swidget->spipe->pipe_widget;
+               pipeline = pipe_widget->private;
+
+               if (pipeline->use_chain_dma) {
+                       u32 host_dma_id;
+                       u32 fifo_size;
+
+                       host_dma_id = platform_params->stream_tag - 1;
+                       pipeline->msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_HOST_ID(host_dma_id);
+
+                       /* Set SCS bit for S16_LE format only */
+                       if (params_format(fe_params) == SNDRV_PCM_FORMAT_S16_LE)
+                               pipeline->msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_SCS_MASK;
+
+                       /*
+                        * Despite its name the bitfield 'fifo_size' is used to define DMA buffer
+                        * size. The expression calculates 2ms buffer size.
+                        */
+                       fifo_size = DIV_ROUND_UP((SOF_IPC4_CHAIN_DMA_BUF_SIZE_MS *
+                                                 params_rate(fe_params) *
+                                                 params_channels(fe_params) *
+                                                 params_physical_width(fe_params)), 8000);
+                       pipeline->msg.extension |= SOF_IPC4_GLB_EXT_CHAIN_DMA_FIFO_SIZE(fifo_size);
+
+                       /*
+                        * Chain DMA does not support stream timestamping, set node_id to invalid
+                        * to skip the code in sof_ipc4_get_stream_start_offset().
+                        */
+                       copier_data->gtw_cfg.node_id = SOF_IPC4_INVALID_NODE_ID;
+
+                       return 0;
+               }
+
                /*
                 * Use the input_pin_fmts to match pcm params for playback and the output_pin_fmts
                 * for capture.
        case snd_soc_dapm_dai_in:
        case snd_soc_dapm_dai_out:
        {
+               struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
+               struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+               if (pipeline->use_chain_dma)
+                       return 0;
+
                dai = swidget->private;
 
                ipc4_copier = (struct sof_ipc4_copier *)dai->private;
        case snd_soc_dapm_scheduler:
                pipeline = swidget->private;
 
+               if (pipeline->use_chain_dma)
+                       return 0;
+
                dev_dbg(sdev->dev, "pipeline: %d memory pages: %d\n", swidget->pipeline_id,
                        pipeline->mem_usage);
 
        {
                struct sof_ipc4_copier *ipc4_copier = swidget->private;
 
+               pipeline = pipe_widget->private;
+               if (pipeline->use_chain_dma)
+                       return 0;
+
                ipc_size = ipc4_copier->ipc_config_size;
                ipc_data = ipc4_copier->ipc_config_data;
 
                struct snd_sof_dai *dai = swidget->private;
                struct sof_ipc4_copier *ipc4_copier = dai->private;
 
+               pipeline = pipe_widget->private;
+               if (pipeline->use_chain_dma)
+                       return 0;
+
                ipc_size = ipc4_copier->ipc_config_size;
                ipc_data = ipc4_copier->ipc_config_data;
 
                struct sof_ipc4_msg msg = {{ 0 }};
                u32 header;
 
+               if (pipeline->use_chain_dma)
+                       return 0;
+
                header = SOF_IPC4_GLB_PIPE_INSTANCE_ID(swidget->instance_id);
                header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_DELETE_PIPELINE);
                header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
                pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED;
                ida_free(&pipeline_ida, swidget->instance_id);
        } else {
-               ida_free(&fw_module->m_ida, swidget->instance_id);
+               struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
+               struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+               if (!pipeline->use_chain_dma)
+                       ida_free(&fw_module->m_ida, swidget->instance_id);
        }
 
        mutex_unlock(&ipc4_data->pipeline_state_mutex);
 {
        struct snd_sof_widget *src_widget = sroute->src_widget;
        struct snd_sof_widget *sink_widget = sroute->sink_widget;
+       struct snd_sof_widget *src_pipe_widget = src_widget->spipe->pipe_widget;
+       struct snd_sof_widget *sink_pipe_widget = sink_widget->spipe->pipe_widget;
        struct sof_ipc4_fw_module *src_fw_module = src_widget->module_info;
        struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info;
+       struct sof_ipc4_pipeline *src_pipeline = src_pipe_widget->private;
+       struct sof_ipc4_pipeline *sink_pipeline = sink_pipe_widget->private;
        struct sof_ipc4_msg msg = {{ 0 }};
        u32 header, extension;
        int ret;
 
+       /* no route set up if chain DMA is used */
+       if (src_pipeline->use_chain_dma || sink_pipeline->use_chain_dma) {
+               if (!src_pipeline->use_chain_dma || !sink_pipeline->use_chain_dma) {
+                       dev_err(sdev->dev,
+                               "use_chain_dma must be set for both src %s and sink %s pipelines\n",
+                               src_widget->widget->name, sink_widget->widget->name);
+                       return -EINVAL;
+               }
+               return 0;
+       }
+
        sroute->src_queue_id = sof_ipc4_get_queue_id(src_widget, sink_widget,
                                                     SOF_PIN_TYPE_OUTPUT);
        if (sroute->src_queue_id < 0) {
        struct sof_ipc4_fw_module *src_fw_module = src_widget->module_info;
        struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info;
        struct sof_ipc4_msg msg = {{ 0 }};
+       struct snd_sof_widget *src_pipe_widget = src_widget->spipe->pipe_widget;
+       struct snd_sof_widget *sink_pipe_widget = sink_widget->spipe->pipe_widget;
+       struct sof_ipc4_pipeline *src_pipeline = src_pipe_widget->private;
+       struct sof_ipc4_pipeline *sink_pipeline = sink_pipe_widget->private;
        u32 header, extension;
        int ret = 0;
 
+       /* no route is set up if chain DMA is used */
+       if (src_pipeline->use_chain_dma || sink_pipeline->use_chain_dma)
+               return 0;
+
        dev_dbg(sdev->dev, "unbind modules %s:%d -> %s:%d\n",
                src_widget->widget->name, sroute->src_queue_id,
                sink_widget->widget->name, sroute->dst_queue_id);
 
        switch (ipc4_copier->dai_type) {
        case SOF_DAI_INTEL_HDA:
+               if (pipeline->use_chain_dma) {
+                       pipeline->msg.primary &= ~SOF_IPC4_GLB_CHAIN_DMA_LINK_ID_MASK;
+                       pipeline->msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_LINK_ID(data->dai_data);
+                       break;
+               }
                gtw_attr = ipc4_copier->gtw_attr;
                gtw_attr->lp_buffer_alloc = pipeline->lp_mode;
                pipeline->skip_during_fe_trigger = true;