From 007c617b92dcf7b3b10eef6c6d7a351a33373d79 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 4 Oct 2021 16:28:38 -0700 Subject: [PATCH 01/12] lib: dai: expose and rename dai_config_reset() Move the function dai_config_reset to the common DAI code so it can be reused. Also, rename it to dai_dma_release as it does not really reset the DAI config but rather releases the DMA channel. Signed-off-by: Ranjani Sridharan --- src/audio/dai.c | 23 +---------------------- src/include/sof/lib/dai.h | 5 +++++ src/ipc/ipc3/dai.c | 20 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/audio/dai.c b/src/audio/dai.c index bc437939b36e..5f374c4b281f 100644 --- a/src/audio/dai.c +++ b/src/audio/dai.c @@ -609,27 +609,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 +665,7 @@ static int dai_reset(struct comp_dev *dev) comp_info(dev, "dai_reset()"); - dai_config_reset(dev); + dai_dma_release(dev); dma_sg_free(&config->elem_array); diff --git a/src/include/sof/lib/dai.h b/src/include/sof/lib/dai.h index fe7f488732b0..5f4efd2870b5 100644 --- a/src/include/sof/lib/dai.h +++ b/src/include/sof/lib/dai.h @@ -508,6 +508,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/ipc/ipc3/dai.c b/src/ipc/ipc3/dai.c index 3f920e924e00..b22d9d0930a1 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,25 @@ 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 = NULL; + } +} + int dai_config(struct comp_dev *dev, struct ipc_config_dai *common_config, void *spec_config) { From ed3599c385b8c6783055fa10da7bb0001a013914 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 6 Oct 2021 10:12:48 -0700 Subject: [PATCH 02/12] IPC: DAI: Expand DAI_CONFIG IPC flags Some DAI components, such as HDaudio, need to be stopped in two steps a) stop the DAI component b) stop the DAI DMA This patch enables this two-step stop by expanding the DAI_CONFIG IPC flags and split them into 2 parts. The 4 LSB bits indicate when the DAI_CONFIG IPC is sent, ex: hw_params, hw_free or pause. The 4 MSB bits are used as the quirk flags to be used along with the command flags. The quirk flag called SOF_DAI_CONFIG_FLAGS_2_STEP_STOP shall be set along with the HW_PARAMS command flag, i.e. before the pipeline is started so that the stop/pause trigger op in the FW can take the appropriate action to either perform/skip the DMA stop. If set, the DMA stop will be executed when the DAI_CONFIG IPC is sent during hw_free. In the case of pause, DMA pause will be handled when the DAI_CONFIG IPC is sent with the PAUSE command flag. Along with this, modify the signature for the hda_ctrl_dai_widget_setup/ hda_ctrl_dai_widget_free() functions to take additional flags as an argument and modify all users to pass the appropriate quirk flags. Only the HDA DAI's need to pass the SOF_DAI_CONFIG_FLAGS_2_STEP_STOP quirk flag during hw_params to indicate that it supports two-step stop and pause. Signed-off-by: Ranjani Sridharan --- src/drivers/intel/ssp/ssp.c | 2 +- src/include/ipc/dai.h | 25 +++++++++++++++++++------ src/include/kernel/abi.h | 2 +- 3 files changed, 21 insertions(+), 8 deletions(-) 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..d376da385e55 100644 --- a/src/include/ipc/dai.h +++ b/src/include/ipc/dai.h @@ -53,12 +53,25 @@ #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) /** \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 */ From 133d1623f0fd2063821bdfb9a1c0c0f7ef5697e3 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 6 Oct 2021 10:38:45 -0700 Subject: [PATCH 03/12] audio: DAI: Add a delayed_dma_stop flag to DAI data Add a new field, delayed_dma_stop to struct dai_data that will be set by the host when the DAI_CONFIG IPC is sent during hw_params. Signed-off-by: Ranjani Sridharan --- src/include/ipc/dai.h | 3 +++ src/include/sof/lib/dai.h | 9 +++++++++ src/ipc/ipc3/dai.c | 10 ++++++++++ 3 files changed, 22 insertions(+) diff --git a/src/include/ipc/dai.h b/src/include/ipc/dai.h index d376da385e55..147afca2227d 100644 --- a/src/include/ipc/dai.h +++ b/src/include/ipc/dai.h @@ -73,6 +73,9 @@ */ #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 { SOF_DAI_INTEL_NONE = 0, /**< None */ diff --git a/src/include/sof/lib/dai.h b/src/include/sof/lib/dai.h index 5f4efd2870b5..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 { diff --git a/src/ipc/ipc3/dai.c b/src/ipc/ipc3/dai.c index b22d9d0930a1..12e8176b20b9 100644 --- a/src/ipc/ipc3/dai.c +++ b/src/ipc/ipc3/dai.c @@ -288,6 +288,16 @@ int dai_config(struct comp_dev *dev, struct ipc_config_dai *common_config, return 0; } + 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; + break; + default: + break; + } + if (dd->chan) { comp_info(dev, "dai_config(): Configured. dma channel index %d, ignore...", dd->chan->index); From adf140555d5343b0814975a1dd2c61c608c59b68 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 6 Oct 2021 10:41:06 -0700 Subject: [PATCH 04/12] lib: dma: make dma stop op optional In preparation for adding a reset op that could be used in place of the stop op for DAI's that support the two-step stop option. Signed-off-by: Ranjani Sridharan --- src/include/sof/lib/dma.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/include/sof/lib/dma.h b/src/include/sof/lib/dma.h index b9f19469dbc1..eac30c4aabce 100644 --- a/src/include/sof/lib/dma.h +++ b/src/include/sof/lib/dma.h @@ -311,7 +311,10 @@ 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; } /** \defgroup sof_dma_copy_func static int dma_copy (struct dma_chan_data * channel, int bytes, uint32_t flags) From 5abc075b0e8855963376314798df2a95ec43283f Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 6 Oct 2021 10:42:53 -0700 Subject: [PATCH 05/12] lib: dma: Add stop_delayed op Add a new optional op for DMA that can be used to stop the DMA after the pipeline has been reset. Signed-off-by: Ranjani Sridharan --- src/include/sof/lib/dma.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/include/sof/lib/dma.h b/src/include/sof/lib/dma.h index eac30c4aabce..fd6d6182c47d 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); @@ -284,7 +285,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, @@ -317,6 +319,14 @@ static inline int dma_stop(struct dma_chan_data *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) * * This function is in a separate subgroup to solve a name clash with From 44158b3b44575f143f4dec9169f27698b24466d9 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 6 Oct 2021 10:46:38 -0700 Subject: [PATCH 06/12] audio: DAI: handle two-step stop and pause If the two_step_stop flag is set for a DAI, DMA reset will be skipped during DAI reset and it will be done when the DAI_CONFIG IPC is sent during hw_free. Also, for the pause push case, the DAI_CONFIG IPC will be sent with the flag SOF_DAI_CONFIG_FLAGS_2_STEP_STOP_PAUSE which call dma_reset to stop the DMA. Signed-off-by: Ranjani Sridharan --- src/audio/dai.c | 7 ++++++- src/ipc/ipc3/dai.c | 31 +++++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/audio/dai.c b/src/audio/dai.c index 5f374c4b281f..449d7ae14503 100644 --- a/src/audio/dai.c +++ b/src/audio/dai.c @@ -665,7 +665,12 @@ static int dai_reset(struct comp_dev *dev) comp_info(dev, "dai_reset()"); - dai_dma_release(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/ipc/ipc3/dai.c b/src/ipc/ipc3/dai.c index 12e8176b20b9..87618754d84e 100644 --- a/src/ipc/ipc3/dai.c +++ b/src/ipc/ipc3/dai.c @@ -293,15 +293,34 @@ int dai_config(struct comp_dev *dev, struct ipc_config_dai *common_config, /* 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; - default: - 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); + } - if (dd->chan) { - comp_info(dev, "dai_config(): Configured. dma channel index %d, ignore...", - dd->chan->index); 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) { From 97bf71cd77335e692ac64d60fe942f533016d53d Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 6 Oct 2021 10:48:37 -0700 Subject: [PATCH 07/12] audio: host: call dma_stop_delayed during host reset Some DMA's like the HD-Audio DMA need to be stopped ater the pipeline has been reset. So call dma_stop_delayed op in host reset so that the DMA can be stopped during the PCM_FREE IPC when the host gets reset. Signed-off-by: Ranjani Sridharan --- src/audio/host.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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; From f0fe7d24f00d8129d00b1b753c38df7929cc0a3b Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 6 Oct 2021 15:10:39 -0700 Subject: [PATCH 08/12] lib: dma: Add a new field to struct dma_chan_data Add a pointer to save the device-specific data for the client device that requests the DMA channel in struct dma_chan_data. Signed-off-by: Ranjani Sridharan --- src/include/sof/lib/dma.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/include/sof/lib/dma.h b/src/include/sof/lib/dma.h index fd6d6182c47d..dd61129fbe27 100644 --- a/src/include/sof/lib/dma.h +++ b/src/include/sof/lib/dma.h @@ -220,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; }; From dc252d77baf49179d7c00c87322361a53d40dda5 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 6 Oct 2021 15:11:41 -0700 Subject: [PATCH 09/12] audio: dai: Set dev_data for DMA chan This can be used by the DMA drivers to access device-specific data. Signed-off-by: Ranjani Sridharan --- src/audio/dai.c | 3 +++ src/ipc/ipc3/dai.c | 1 + 2 files changed, 4 insertions(+) diff --git a/src/audio/dai.c b/src/audio/dai.c index 449d7ae14503..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); diff --git a/src/ipc/ipc3/dai.c b/src/ipc/ipc3/dai.c index 87618754d84e..6d1b0992ceff 100644 --- a/src/ipc/ipc3/dai.c +++ b/src/ipc/ipc3/dai.c @@ -263,6 +263,7 @@ void dai_dma_release(struct comp_dev *dev) /* remove callback */ notifier_unregister(dev, dd->chan, NOTIFIER_ID_DMA_COPY); dma_channel_put(dd->chan); + dd->chan->dev_data = NULL; dd->chan = NULL; } } From 936526b8e592bd8324d3a6600353d1e535a06aff Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 6 Oct 2021 10:53:23 -0700 Subject: [PATCH 10/12] drivers: Intel: hda-dma: Handle two-step stop and pause The recommended programming sequence for HD-Audio DMA's is to clear the GEN bit after the host has cleared the RUN bit. For host DMA, we can replace the stop op with the stop_delayed op so that the GEN bit can be cleared during host reset. For link DMA, add the stop_delayed op to perform DMA stop during DAI_CONFIG IPC with hw_free if the two-step stop flag is set for the DAI. For single-step op, there's no change in sequence. The two-step sequence for pause is similar to stop. The DMA will be stopped with dma_stop_delayed when the DAI_CONFIG IPC with the SOF_DAI_CONFIG_FLAGS_2_STEP_STOP_PAUSE flag after the host has cleard the RUN bit. Signed-off-by: Ranjani Sridharan --- src/drivers/intel/hda/hda-dma.c | 66 ++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/src/drivers/intel/hda/hda-dma.c b/src/drivers/intel/hda/hda-dma.c index 40c6ce3a2e3a..128902110921 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 @@ -148,7 +149,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,27 +571,7 @@ 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; @@ -624,6 +605,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 +989,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 +1006,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, From 083113f20ed101fd08efe7e8990ff8b196ce22fe Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Tue, 5 Oct 2021 11:14:11 -0700 Subject: [PATCH 11/12] drivers: Intel: hda-dma: ensure DMA channel is idle after stop Make sure the HDA DMA channel is idle after stopping by checking the GBUSY bit. Signed-off-by: Ranjani Sridharan --- src/drivers/intel/hda/hda-dma.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/drivers/intel/hda/hda-dma.c b/src/drivers/intel/hda/hda-dma.c index 128902110921..77418047f71a 100644 --- a/src/drivers/intel/hda/hda-dma.c +++ b/src/drivers/intel/hda/hda-dma.c @@ -58,6 +58,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 @@ -574,7 +575,7 @@ static int hda_dma_release(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); @@ -594,6 +595,15 @@ static int hda_dma_stop_common(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; From e0cb877507ac6d156ee50235104266d3bcb0b7a3 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Tue, 5 Oct 2021 11:39:27 -0700 Subject: [PATCH 12/12] drivers: Intel: hda-dma: Add documentation for HDA DMA programming sequence Spell out the HD-Audio DMA programming sequence to make it easier to follow. Signed-off-by: Ranjani Sridharan --- src/drivers/intel/hda/hda-dma.c | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/drivers/intel/hda/hda-dma.c b/src/drivers/intel/hda/hda-dma.c index 77418047f71a..0479b7cf9c16 100644 --- a/src/drivers/intel/hda/hda-dma.c +++ b/src/drivers/intel/hda/hda-dma.c @@ -29,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);