diff --git a/src/audio/dai.c b/src/audio/dai.c index bc437939b36e..0a576c912181 100644 --- a/src/audio/dai.c +++ b/src/audio/dai.c @@ -219,6 +219,7 @@ static void dai_free(struct comp_dev *dev) if (dd->chan) { notifier_unregister(dev, dd->chan, NOTIFIER_ID_DMA_COPY); dma_channel_put(dd->chan); + dd->chan->dev_data = NULL; } dma_put(dd->dma); @@ -599,6 +600,8 @@ static int dai_config_prepare(struct comp_dev *dev) return -EIO; } + dd->chan->dev_data = dd; + comp_info(dev, "dai_config_prepare(): new configured dma channel index %d", dd->chan->index); @@ -609,27 +612,6 @@ static int dai_config_prepare(struct comp_dev *dev) return 0; } -static void dai_config_reset(struct comp_dev *dev) -{ - struct dai_data *dd = comp_get_drvdata(dev); - - /* cannot configure DAI while active */ - if (dev->state == COMP_STATE_ACTIVE) { - comp_info(dev, "dai_config(): Component is in active state. Ignore resetting"); - return; - } - - /* put the allocated DMA channel first */ - if (dd->chan) { - dma_channel_put(dd->chan); - dd->chan = NULL; - - /* remove callback */ - notifier_unregister(dev, dd->chan, - NOTIFIER_ID_DMA_COPY); - } -} - static int dai_prepare(struct comp_dev *dev) { struct dai_data *dd = comp_get_drvdata(dev); @@ -686,7 +668,12 @@ static int dai_reset(struct comp_dev *dev) comp_info(dev, "dai_reset()"); - dai_config_reset(dev); + /* + * DMA channel release should be skipped now for DAI's that support the two-step stop option. + * It will be done when the host sends the DAI_CONFIG IPC during hw_free. + */ + if (!dd->delayed_dma_stop) + dai_dma_release(dev); dma_sg_free(&config->elem_array); diff --git a/src/audio/host.c b/src/audio/host.c index 3b126a3095de..525d6389d45b 100644 --- a/src/audio/host.c +++ b/src/audio/host.c @@ -847,9 +847,12 @@ static int host_reset(struct comp_dev *dev) comp_dbg(dev, "host_reset()"); if (hd->chan) { + dma_stop_delayed(hd->chan); + /* remove callback */ notifier_unregister(dev, hd->chan, NOTIFIER_ID_DMA_COPY); dma_channel_put(hd->chan); + hd->chan = NULL; } /* free all DMA elements */ @@ -863,9 +866,6 @@ static int host_reset(struct comp_dev *dev) hd->dma_buffer = NULL; } - /* reset dma channel as we have put it */ - hd->chan = NULL; - host_pointer_reset(dev); hd->copy_type = COMP_COPY_NORMAL; hd->source = NULL; diff --git a/src/drivers/intel/hda/hda-dma.c b/src/drivers/intel/hda/hda-dma.c index 40c6ce3a2e3a..0479b7cf9c16 100644 --- a/src/drivers/intel/hda/hda-dma.c +++ b/src/drivers/intel/hda/hda-dma.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,77 @@ #include #include +/* + * HD-Audio DMA programming sequence + * + * START stream (Playback): + * 1. Host sends the DAI_CONFIG IPC with SOF_DAI_CONFIG_FLAGS_2_STEP_STOP_PAUSE flag along with + * SOF_DAI_CONFIG_FLAGS_HW_PARAMS + * 2. Host sets DGCS.RUN bit for link DMA. This step would be skipped if link DMA is already + * running with mixer-based pipelines + * 3. Host sets DGCS.RUN bit for host DMA + * 4. Host sends the STREAM_TRIG_START IPC to the DSP + * 5. FW starts the pipeline and sets DGCS.GEN bit to 1 + * + * START stream (Capture): + * 1. Host sends the DAI_CONFIG IPC with SOF_DAI_CONFIG_FLAGS_2_STEP_STOP_PAUSE flag along with + * SOF_DAI_CONFIG_FLAGS_HW_PARAMS + * 2. Host sets DGCS.RUN bit for host DMA + * 3. Host sends the STREAM_TRIG_START IPC to the DSP + * 4. FW starts the pipeline and sets DGCS.GEN bit to 1 + * 5. Host sets DGCS.RUN bit for link DMA + * + * PAUSE_PUSH an active stream (Playback): + * 1. Host sends the STREAM_TRIG_PAUSE IPC to the DSP + * 2. FW pauses the pipeline but keeps the DGCS.GEN bit set to 1 for host DMA + * 3. Host clears DGCS.RUN bit for host DMA + * 4. Host clears DGCS.RUN bit for link DMA + * 5. Host sends the DAI_CONFIG IPC with only the SOF_DAI_CONFIG_FLAGS_PAUSE command flag + * 6. FW clears the DGCS.GEN bit for link DMA + * + * PAUSE_PUSH an active stream (Capture): + * 1. Host clears DGCS.RUN bit for link DMA + * 2. Host sends the DAI_CONFIG IPC with only the SOF_DAI_CONFIG_FLAGS_PAUSE command flag + * 3. FW clears the DGCS.GEN bit for link DMA + * 4. Host sends the STREAM_TRIG_PAUSE IPC to the DSP + * 5. FW pauses the pipeline but keeps DGCS.GEN bit set to 1 for host DMA + * 6. Host clears DGCS.RUN bit for host DMA + * + * PAUSE_RELEASE a paused stream (Playback): + * 1. Host sets DGCS.RUN bit for link DMA + * 2. Host sets DGCS.RUN bit for host DMA + * 3. Host sends the STREAM_TRIG_PAUSE_RELEASE IPC to the DSP + * 4. FW releases the pipeline and sets the DGCS.GEN bit to 1 + * + * PAUSE_RELEASE a paused stream (Capture): + * 1. Host sets DGCS.RUN bit for host DMA + * 2. Host sends the STREAM_TRIG_PAUSE_RELEASE IPC to the DSP + * 3. FW releases the pipeline and sets the DGCS.GEN bit to 1 + * 4. Host sets DGCS.RUN bit for link DMA + * + * STOP an active/paused stream (Playback): + * 1. Host sends the STREAM_TRIG_STOP IPC to the DSP + * 2. FW stops the pipeline + * 3. Host clears the DGCS.RUN bit for host DMA + * 4. Host sends the PCM_FREE IPC + * 5. FW clears DGCS.GEN bit for host DMA during host component reset and checks DGCS.GBUSY bit + * to ensure DMA is idle + * 6. Host clears DGCS.RUN bit for link DMA + * 7. Host sends the DAI_CONFIG IPC with the SOF_DAI_CONFIG_FLAGS_HW_FREE flag + * 8. FW clears the DGCS.GEN bit for link DMA and checks DGCS.GBUSY bit to ensure DMA is idle + * + * STOP an active/paused stream (Capture): + * 1. Host clears DGCS.RUN bit for link DMA + * 2. Host sends the DAI_CONFIG IPC with the SOF_DAI_CONFIG_FLAGS_HW_FREE flag + * 3. FW clears the DGCS.GEN bit for link DMA and checks GBUSY bit to ensure DMA is idle + * 4. Host sends the STREAM_TRIG_STOP IPC to the DSP + * 5. FW stops the pipeline + * 6. Host clears the DGCS.RUN bit for host DMA + * 7. Host sends the PCM_FREE IPC + * 8. FW clears DGCS.GEN bit for host DMA during host component reset and checks DGCS.GBUSY bit + * to ensure DMA is idle + */ + /* ee12fa71-4579-45d7-bde2-b32c6893a122 */ DECLARE_SOF_UUID("hda-dma", hda_dma_uuid, 0xee12fa71, 0x4579, 0x45d7, 0xbd, 0xe2, 0xb3, 0x2c, 0x68, 0x93, 0xa1, 0x22); @@ -57,6 +129,7 @@ DECLARE_TR_CTX(hdma_tr, SOF_UUID(hda_dma_uuid), LOG_LEVEL_INFO); #define DGCS_BF BIT(9) /* buffer full */ #define DGCS_BNE BIT(8) /* buffer not empty */ #define DGCS_FIFORDY BIT(5) /* enable FIFO */ +#define DGCS_GBUSY BIT(15) /* indicates if the DMA channel is idle or not */ /* DGBBA */ #define DGBBA_MASK 0xffff80 @@ -148,7 +221,7 @@ struct hda_chan_data { #endif }; -static int hda_dma_stop(struct dma_chan_data *channel); +static int hda_dma_stop_common(struct dma_chan_data *channel); static inline void hda_dma_inc_fp(struct dma_chan_data *chan, uint32_t value) @@ -570,30 +643,10 @@ static int hda_dma_release(struct dma_chan_data *channel) return 0; } -static int hda_dma_pause(struct dma_chan_data *channel) -{ - uint32_t flags; - - irq_local_disable(flags); - - tr_dbg(&hdma_tr, "hda-dmac: %d channel %d -> pause", - channel->dma->plat_data.id, channel->index); - - if (channel->status != COMP_STATE_ACTIVE) - goto out; - - /* stop the channel */ - hda_dma_stop(channel); - -out: - irq_local_enable(flags); - return 0; -} - -static int hda_dma_stop(struct dma_chan_data *channel) +static int hda_dma_stop_common(struct dma_chan_data *channel) { struct hda_chan_data *hda_chan; - uint32_t flags; + uint32_t flags, dgcs; irq_local_disable(flags); @@ -613,6 +666,15 @@ static int hda_dma_stop(struct dma_chan_data *channel) } else { dma_chan_reg_update_bits(channel, DGCS, DGCS_GEN, 0); } + + /* check if channel is idle. No need to wait after clearing the GEN bit */ + dgcs = dma_chan_reg_read(channel, DGCS); + if (dgcs & DGCS_GBUSY) { + tr_err(&hdma_tr, "hda-dmac: %d channel %d not idle after stop", + channel->dma->plat_data.id, channel->index); + irq_local_enable(flags); + return -EBUSY; + } channel->status = COMP_STATE_PREPARE; hda_chan = dma_chan_get_data(channel); hda_chan->state = 0; @@ -624,6 +686,40 @@ static int hda_dma_stop(struct dma_chan_data *channel) return 0; } +static int hda_dma_link_stop(struct dma_chan_data *channel) +{ + struct dai_data *dd = channel->dev_data; + + if (!dd) { + tr_err(&hdma_tr, "hda-dmac: %d channel %d no device data", + channel->dma->plat_data.id, channel->index); + return -EINVAL; + } + + /* + * The delayed_dma_stop flag will be set with the newer kernel and DMA will be stopped during + * reset. With older kernel, the DMA will be stopped during the stop/pause trigger. + */ + if (!dd->delayed_dma_stop) + return hda_dma_stop_common(channel); + + return 0; +} + +static int hda_dma_link_pause(struct dma_chan_data *channel) +{ + /* + * with two-step pause, DMA will be stopped when the DAI_CONFIG IPC is sent with the + * SOF_DAI_CONFIG_FLAGS_TWO_STEP_STOP flag + */ + return hda_dma_link_stop(channel); +} + +static int hda_dma_stop_delayed(struct dma_chan_data *channel) +{ + return hda_dma_stop_common(channel); +} + /* fill in "status" with current DMA channel state and position */ static int hda_dma_status(struct dma_chan_data *channel, struct dma_chan_status *status, uint8_t direction) @@ -974,7 +1070,7 @@ const struct dma_ops hda_host_dma_ops = { .channel_get = hda_dma_channel_get, .channel_put = hda_dma_channel_put, .start = hda_dma_start, - .stop = hda_dma_stop, + .stop_delayed = hda_dma_stop_delayed, .copy = hda_dma_host_copy, .status = hda_dma_status, .set_config = hda_dma_set_config, @@ -991,9 +1087,10 @@ const struct dma_ops hda_link_dma_ops = { .channel_get = hda_dma_channel_get, .channel_put = hda_dma_channel_put, .start = hda_dma_start, - .stop = hda_dma_stop, + .stop = hda_dma_link_stop, + .stop_delayed = hda_dma_stop_delayed, .copy = hda_dma_link_copy, - .pause = hda_dma_pause, + .pause = hda_dma_link_pause, .release = hda_dma_release, .status = hda_dma_status, .set_config = hda_dma_set_config, diff --git a/src/drivers/intel/ssp/ssp.c b/src/drivers/intel/ssp/ssp.c index 70b7f2e3ef7c..25a8b8f4f6bf 100644 --- a/src/drivers/intel/ssp/ssp.c +++ b/src/drivers/intel/ssp/ssp.c @@ -714,7 +714,7 @@ static int ssp_set_config_tplg(struct dai *dai, struct ipc_config_dai *common_co ssp->state[DAI_DIR_CAPTURE] = COMP_STATE_PREPARE; clk: - switch (config->flags & SOF_DAI_CONFIG_FLAGS_MASK) { + switch (config->flags & SOF_DAI_CONFIG_FLAGS_CMD_MASK) { case SOF_DAI_CONFIG_FLAGS_HW_PARAMS: if (ssp->params.clks_control & SOF_DAI_INTEL_SSP_CLKCTRL_MCLK_ES) { ret = ssp_mclk_prepare_enable(dai); diff --git a/src/include/ipc/dai.h b/src/include/ipc/dai.h index 230446f2285d..147afca2227d 100644 --- a/src/include/ipc/dai.h +++ b/src/include/ipc/dai.h @@ -53,12 +53,28 @@ #define SOF_DAI_FMT_INV_MASK 0x0f00 #define SOF_DAI_FMT_CLOCK_PROVIDER_MASK 0xf000 -/* DAI_CONFIG flags */ -#define SOF_DAI_CONFIG_FLAGS_MASK 0x3 -#define SOF_DAI_CONFIG_FLAGS_NONE (0 << 0) /**< DAI_CONFIG sent without stage information */ -#define SOF_DAI_CONFIG_FLAGS_HW_PARAMS (1 << 0) /**< DAI_CONFIG sent during hw_params stage */ -#define SOF_DAI_CONFIG_FLAGS_HW_FREE (2 << 0) /**< DAI_CONFIG sent during hw_free stage */ -#define SOF_DAI_CONFIG_FLAGS_RFU (3 << 0) /**< not used, reserved for future use */ +/* + * DAI_CONFIG flags. The 4 LSB bits are used for the commands, HW_PARAMS, HW_FREE and PAUSE + * representing when the IPC is sent. The 4 MSB bits are used to add quirks along with the above + * commands. + */ +#define SOF_DAI_CONFIG_FLAGS_CMD_MASK 0xF +#define SOF_DAI_CONFIG_FLAGS_NONE 0 /**< DAI_CONFIG sent without stage information */ +#define SOF_DAI_CONFIG_FLAGS_HW_PARAMS BIT(0) /**< DAI_CONFIG sent during hw_params stage */ +#define SOF_DAI_CONFIG_FLAGS_HW_FREE BIT(1) /**< DAI_CONFIG sent during hw_free stage */ +/**< DAI_CONFIG sent during pause trigger. Only available ABI 3.20 onwards */ +#define SOF_DAI_CONFIG_FLAGS_PAUSE BIT(2) +#define SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT 4 +#define SOF_DAI_CONFIG_FLAGS_QUIRK_MASK (0xF << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT) +/* + * This should be used along with the SOF_DAI_CONFIG_FLAGS_HW_PARAMS to indicate that pipeline + * stop/pause and DAI DMA stop/pause should happen in two steps. This change is only available + * ABI 3.20 onwards. + */ +#define SOF_DAI_CONFIG_FLAGS_2_STEP_STOP BIT(0) + +#define SOF_DAI_QUIRK_IS_SET(flags, quirk) \ + (((flags & SOF_DAI_CONFIG_FLAGS_QUIRK_MASK) >> SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT) & quirk) /** \brief Types of DAI */ enum sof_ipc_dai_type { diff --git a/src/include/kernel/abi.h b/src/include/kernel/abi.h index b9847c7de9f3..c9db26448c22 100644 --- a/src/include/kernel/abi.h +++ b/src/include/kernel/abi.h @@ -29,7 +29,7 @@ /** \brief SOF ABI version major, minor and patch numbers */ #define SOF_ABI_MAJOR 3 -#define SOF_ABI_MINOR 19 +#define SOF_ABI_MINOR 20 #define SOF_ABI_PATCH 0 /** \brief SOF ABI version number. Format within 32bit word is MMmmmppp */ diff --git a/src/include/sof/lib/dai.h b/src/include/sof/lib/dai.h index fe7f488732b0..7ce7f11814be 100644 --- a/src/include/sof/lib/dai.h +++ b/src/include/sof/lib/dai.h @@ -179,6 +179,15 @@ struct dai_data { void *dai_spec_config; /* dai specific config from the host */ uint64_t wallclock; /* wall clock at stream start */ + + /* + * flag indicating two-step stop/pause for DAI comp and DAI DMA. + * DAI stop occurs during STREAM_TRIG_STOP IPC and DMA stop during DAI_CONFIG IPC with + * the SOF_DAI_CONFIG_FLAGS_HW_FREE flag. + * DAI pause occurs during STREAM_TRIG_PAUSE IPC and DMA pause during DAI_CONFIG IPC with + * the SOF_DAI_CONFIG_FLAGS_PAUSE flag. + */ + bool delayed_dma_stop; }; struct dai { @@ -508,6 +517,11 @@ static inline const struct dai_info *dai_info_get(void) */ int dai_config_dma_channel(struct comp_dev *dev, void *config); +/** + * \brief Reset DAI DMA config + */ +void dai_dma_release(struct comp_dev *dev); + /** * \brief Configure DAI physical interface. */ diff --git a/src/include/sof/lib/dma.h b/src/include/sof/lib/dma.h index b9f19469dbc1..dd61129fbe27 100644 --- a/src/include/sof/lib/dma.h +++ b/src/include/sof/lib/dma.h @@ -160,6 +160,7 @@ struct dma_ops { int (*start)(struct dma_chan_data *channel); int (*stop)(struct dma_chan_data *channel); + int (*stop_delayed)(struct dma_chan_data *channel); int (*copy)(struct dma_chan_data *channel, int bytes, uint32_t flags); int (*pause)(struct dma_chan_data *channel); int (*release)(struct dma_chan_data *channel); @@ -219,6 +220,9 @@ struct dma_chan_data { /* true if this DMA channel is the scheduling source */ bool is_scheduling_source; + /* device specific data set by the device that requests the DMA channel */ + void *dev_data; + void *priv_data; }; @@ -284,7 +288,8 @@ void dma_put(struct dma *dma); * 4) dma_start() * ... DMA now running ... * 5) dma_stop() - * 6) dma_channel_put() + * 6) dma_stop_delayed() + * 7) dma_channel_put() */ static inline struct dma_chan_data *dma_channel_get(struct dma *dma, @@ -311,7 +316,18 @@ static inline int dma_start(struct dma_chan_data *channel) static inline int dma_stop(struct dma_chan_data *channel) { - return channel->dma->ops->stop(channel); + if (channel->dma->ops->stop) + return channel->dma->ops->stop(channel); + + return 0; +} + +static inline int dma_stop_delayed(struct dma_chan_data *channel) +{ + if (channel->dma->ops->stop_delayed) + return channel->dma->ops->stop_delayed(channel); + + return 0; } /** \defgroup sof_dma_copy_func static int dma_copy (struct dma_chan_data * channel, int bytes, uint32_t flags) diff --git a/src/ipc/ipc3/dai.c b/src/ipc/ipc3/dai.c index 3f920e924e00..6d1b0992ceff 100644 --- a/src/ipc/ipc3/dai.c +++ b/src/ipc/ipc3/dai.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -247,6 +248,26 @@ int ipc_comp_dai_config(struct ipc *ipc, struct ipc_config_dai *common_config, return ret; } +void dai_dma_release(struct comp_dev *dev) +{ + struct dai_data *dd = comp_get_drvdata(dev); + + /* cannot configure DAI while active */ + if (dev->state == COMP_STATE_ACTIVE) { + comp_info(dev, "dai_config(): Component is in active state. Ignore resetting"); + return; + } + + /* put the allocated DMA channel first */ + if (dd->chan) { + /* remove callback */ + notifier_unregister(dev, dd->chan, NOTIFIER_ID_DMA_COPY); + dma_channel_put(dd->chan); + dd->chan->dev_data = NULL; + dd->chan = NULL; + } +} + int dai_config(struct comp_dev *dev, struct ipc_config_dai *common_config, void *spec_config) { @@ -268,10 +289,39 @@ int dai_config(struct comp_dev *dev, struct ipc_config_dai *common_config, return 0; } - if (dd->chan) { - comp_info(dev, "dai_config(): Configured. dma channel index %d, ignore...", - dd->chan->index); + switch (config->flags & SOF_DAI_CONFIG_FLAGS_CMD_MASK) { + case SOF_DAI_CONFIG_FLAGS_HW_PARAMS: + /* set the delayed_dma_stop flag */ + if (SOF_DAI_QUIRK_IS_SET(config->flags, SOF_DAI_CONFIG_FLAGS_2_STEP_STOP)) + dd->delayed_dma_stop = true; + + if (dd->chan) { + comp_info(dev, "dai_config(): Configured. dma channel index %d, ignore...", + dd->chan->index); + return 0; + } + break; + case SOF_DAI_CONFIG_FLAGS_HW_FREE: + if (!dd->chan) + return 0; + + /* stop DMA and reset config for two-step stop DMA */ + if (dd->delayed_dma_stop) { + ret = dma_stop_delayed(dd->chan); + if (ret < 0) + return ret; + + dai_dma_release(dev); + } + return 0; + case SOF_DAI_CONFIG_FLAGS_PAUSE: + if (!dd->chan) + return 0; + + return dma_stop_delayed(dd->chan); + default: + break; } if (config->group_id) {