[alsa-devel] [PATCH 1/2] ASoC: AMD: Fix simultaneous playback and capture on different channel
If capture and playback are started on different channel (I2S/BT) there is a possibilty that channel information passed from machine driver is overwritten before the configuration is done in dma driver. Example: 113.597588: cz_max_startup: ---playback sets BT channel 113.597694: cz_dmic1_startup: ---capture sets I2S channel 113.597979: acp_dma_hw_params: ---configures capture for I2S channel 113.598114: acp_dma_hw_params: ---configures playback for I2S channel
This is fixed by having lock between startup and prepare. This ensures no other codec startup gets called between a codec's startup(where channel info is set) and hw_params(where channel info is read).
Signed-off-by: Akshu Agrawal akshu.agrawal@amd.com --- sound/soc/amd/acp-da7219-max98357a.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+)
diff --git a/sound/soc/amd/acp-da7219-max98357a.c b/sound/soc/amd/acp-da7219-max98357a.c index 3879ccc..b98ffbc 100644 --- a/sound/soc/amd/acp-da7219-max98357a.c +++ b/sound/soc/amd/acp-da7219-max98357a.c @@ -47,6 +47,7 @@
static struct snd_soc_jack cz_jack; static struct clk *da7219_dai_clk; +static struct mutex instance_lock; extern int bt_uart_enable;
static int cz_da7219_init(struct snd_soc_pcm_runtime *rtd) @@ -150,6 +151,7 @@ static int cz_da7219_startup(struct snd_pcm_substream *substream) snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+ mutex_lock(&instance_lock); machine->i2s_instance = I2S_SP_INSTANCE; machine->capture_channel = CAP_CHANNEL1; return da7219_clk_enable(substream); @@ -160,6 +162,12 @@ static void cz_da7219_shutdown(struct snd_pcm_substream *substream) da7219_clk_disable(); }
+static int cz_da7219_prepare(struct snd_pcm_substream *substream) +{ + mutex_unlock(&instance_lock); + return 0; +} + static int cz_max_startup(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; @@ -177,6 +185,7 @@ static int cz_max_startup(struct snd_pcm_substream *substream) snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+ mutex_lock(&instance_lock); machine->i2s_instance = I2S_BT_INSTANCE; return da7219_clk_enable(substream); } @@ -186,6 +195,12 @@ static void cz_max_shutdown(struct snd_pcm_substream *substream) da7219_clk_disable(); }
+static int cz_max_prepare(struct snd_pcm_substream *substream) +{ + mutex_unlock(&instance_lock); + return 0; +} + static int cz_dmic0_startup(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; @@ -203,6 +218,7 @@ static int cz_dmic0_startup(struct snd_pcm_substream *substream) snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+ mutex_lock(&instance_lock); machine->i2s_instance = I2S_BT_INSTANCE; return da7219_clk_enable(substream); } @@ -224,6 +240,7 @@ static int cz_dmic1_startup(struct snd_pcm_substream *substream) snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+ mutex_lock(&instance_lock); machine->i2s_instance = I2S_SP_INSTANCE; machine->capture_channel = CAP_CHANNEL0; return da7219_clk_enable(substream); @@ -234,24 +251,34 @@ static void cz_dmic_shutdown(struct snd_pcm_substream *substream) da7219_clk_disable(); }
+static int cz_dmic_prepare(struct snd_pcm_substream *substream) +{ + mutex_unlock(&instance_lock); + return 0; +} + static const struct snd_soc_ops cz_da7219_cap_ops = { .startup = cz_da7219_startup, .shutdown = cz_da7219_shutdown, + .prepare = cz_da7219_prepare, };
static const struct snd_soc_ops cz_max_play_ops = { .startup = cz_max_startup, .shutdown = cz_max_shutdown, + .prepare = cz_max_prepare, };
static const struct snd_soc_ops cz_dmic0_cap_ops = { .startup = cz_dmic0_startup, .shutdown = cz_dmic_shutdown, + .prepare = cz_dmic_prepare, };
static const struct snd_soc_ops cz_dmic1_cap_ops = { .startup = cz_dmic1_startup, .shutdown = cz_dmic_shutdown, + .prepare = cz_dmic_prepare, };
static struct snd_soc_dai_link cz_dai_7219_98357[] = { @@ -409,6 +436,7 @@ static int cz_probe(struct platform_device *pdev) card = &cz_card; cz_card.dev = &pdev->dev; platform_set_drvdata(pdev, card); + mutex_init(&instance_lock); snd_soc_card_set_drvdata(card, machine); ret = devm_snd_soc_register_card(&pdev->dev, &cz_card); if (ret) {
HW register descriptions says: "DMA Channel Reset...Software must confirm that this bit is cleared before reprogramming any of the channel configuration registers." There could be cases where dma stop errored out leaving dma channel in reset state. We need to ensure that before the start of another dma, channel is out of the reset state.
Signed-off-by: Akshu Agrawal akshu.agrawal@amd.com --- sound/soc/amd/acp-pcm-dma.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+)
diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c index e359938..77b265b 100644 --- a/sound/soc/amd/acp-pcm-dma.c +++ b/sound/soc/amd/acp-pcm-dma.c @@ -16,6 +16,7 @@ #include <linux/module.h> #include <linux/delay.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <linux/sizes.h> #include <linux/pm_runtime.h>
@@ -184,6 +185,24 @@ static void config_dma_descriptor_in_sram(void __iomem *acp_mmio, acp_reg_write(descr_info->xfer_val, acp_mmio, mmACP_SRBM_Targ_Idx_Data); }
+static void pre_config_reset(void __iomem *acp_mmio, u16 ch_num) +{ + u32 dma_ctrl; + int ret; + + /* clear the reset bit */ + dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRst_MASK; + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + /* check the reset bit before programming configuration registers */ + ret = readl_poll_timeout(acp_mmio + ((mmACP_DMA_CNTL_0 + ch_num) * 4), + dma_ctrl, + !(dma_ctrl & ACP_DMA_CNTL_0__DMAChRst_MASK), + 100, ACP_DMA_RESET_TIME); + if (ret < 0) + pr_err("Failed to clear reset of channel : %d\n", ch_num); +} + /* * Initialize the DMA descriptor information for transfer between * system memory <-> ACP SRAM @@ -236,6 +255,7 @@ static void set_acp_sysmem_dma_descriptors(void __iomem *acp_mmio, config_dma_descriptor_in_sram(acp_mmio, dma_dscr_idx, &dmadscr[i]); } + pre_config_reset(acp_mmio, ch); config_acp_dma_channel(acp_mmio, ch, dma_dscr_idx - 1, NUM_DSCRS_PER_CHANNEL, @@ -275,6 +295,7 @@ static void set_acp_to_i2s_dma_descriptors(void __iomem *acp_mmio, u32 size, config_dma_descriptor_in_sram(acp_mmio, dma_dscr_idx, &dmadscr[i]); } + pre_config_reset(acp_mmio, ch); /* Configure the DMA channel with the above descriptore */ config_acp_dma_channel(acp_mmio, ch, dma_dscr_idx - 1, NUM_DSCRS_PER_CHANNEL,
The patch
ASoC: AMD: Ensure reset bit is cleared before configuring
has been applied to the asoc tree at
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
From 2a665dba016d5493c7d826fec82b0cb643b30d42 Mon Sep 17 00:00:00 2001
From: Akshu Agrawal akshu.agrawal@amd.com Date: Mon, 10 Sep 2018 13:36:30 +0530 Subject: [PATCH] ASoC: AMD: Ensure reset bit is cleared before configuring
HW register descriptions says: "DMA Channel Reset...Software must confirm that this bit is cleared before reprogramming any of the channel configuration registers." There could be cases where dma stop errored out leaving dma channel in reset state. We need to ensure that before the start of another dma, channel is out of the reset state.
Signed-off-by: Akshu Agrawal akshu.agrawal@amd.com Signed-off-by: Mark Brown broonie@kernel.org --- sound/soc/amd/acp-pcm-dma.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+)
diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c index e359938e3d7e..77b265bd0505 100644 --- a/sound/soc/amd/acp-pcm-dma.c +++ b/sound/soc/amd/acp-pcm-dma.c @@ -16,6 +16,7 @@ #include <linux/module.h> #include <linux/delay.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <linux/sizes.h> #include <linux/pm_runtime.h>
@@ -184,6 +185,24 @@ static void config_dma_descriptor_in_sram(void __iomem *acp_mmio, acp_reg_write(descr_info->xfer_val, acp_mmio, mmACP_SRBM_Targ_Idx_Data); }
+static void pre_config_reset(void __iomem *acp_mmio, u16 ch_num) +{ + u32 dma_ctrl; + int ret; + + /* clear the reset bit */ + dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRst_MASK; + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + /* check the reset bit before programming configuration registers */ + ret = readl_poll_timeout(acp_mmio + ((mmACP_DMA_CNTL_0 + ch_num) * 4), + dma_ctrl, + !(dma_ctrl & ACP_DMA_CNTL_0__DMAChRst_MASK), + 100, ACP_DMA_RESET_TIME); + if (ret < 0) + pr_err("Failed to clear reset of channel : %d\n", ch_num); +} + /* * Initialize the DMA descriptor information for transfer between * system memory <-> ACP SRAM @@ -236,6 +255,7 @@ static void set_acp_sysmem_dma_descriptors(void __iomem *acp_mmio, config_dma_descriptor_in_sram(acp_mmio, dma_dscr_idx, &dmadscr[i]); } + pre_config_reset(acp_mmio, ch); config_acp_dma_channel(acp_mmio, ch, dma_dscr_idx - 1, NUM_DSCRS_PER_CHANNEL, @@ -275,6 +295,7 @@ static void set_acp_to_i2s_dma_descriptors(void __iomem *acp_mmio, u32 size, config_dma_descriptor_in_sram(acp_mmio, dma_dscr_idx, &dmadscr[i]); } + pre_config_reset(acp_mmio, ch); /* Configure the DMA channel with the above descriptore */ config_acp_dma_channel(acp_mmio, ch, dma_dscr_idx - 1, NUM_DSCRS_PER_CHANNEL,
On Mon, Sep 10, 2018 at 01:36:29PM +0530, Akshu Agrawal wrote:
If capture and playback are started on different channel (I2S/BT) there is a possibilty that channel information passed from machine driver is overwritten before the configuration is done in dma driver. Example: 113.597588: cz_max_startup: ---playback sets BT channel 113.597694: cz_dmic1_startup: ---capture sets I2S channel 113.597979: acp_dma_hw_params: ---configures capture for I2S channel 113.598114: acp_dma_hw_params: ---configures playback for I2S channel
This is fixed by having lock between startup and prepare. This ensures no other codec startup gets called between a codec's startup(where channel info is set) and hw_params(where channel info is read).
This isn't viable - the driver will deadlock if the application hits an error and never gets to startup, or if the application tries to simultaneously configure two channels (ie, do all the prepares and then all the parameter configuration and then startup). The DMA driver needs to remember the configurations for the different channels separately.
On 9/10/2018 5:08 PM, Mark Brown wrote:
On Mon, Sep 10, 2018 at 01:36:29PM +0530, Akshu Agrawal wrote:
If capture and playback are started on different channel (I2S/BT) there is a possibilty that channel information passed from machine driver is overwritten before the configuration is done in dma driver. Example: 113.597588: cz_max_startup: ---playback sets BT channel 113.597694: cz_dmic1_startup: ---capture sets I2S channel 113.597979: acp_dma_hw_params: ---configures capture for I2S channel 113.598114: acp_dma_hw_params: ---configures playback for I2S channel
This is fixed by having lock between startup and prepare. This ensures no other codec startup gets called between a codec's startup(where channel info is set) and hw_params(where channel info is read).
This isn't viable - the driver will deadlock if the application hits an error and never gets to startup, or if the application tries to simultaneously configure two channels (ie, do all the prepares and then all the parameter configuration and then startup).
We can avoid deadlock by having another mutex_unlock in the shutdown call of each of codec's ops. Wouldn't in all possible termination scenarios, it will cleanup and exit via shutdown callback?
Having said that I think there is a better approach to this, is by having 2 separate instance variable for playback and capture for passing instance info from machine driver to dma driver. Respective codec in machine driver will set the capture/playback instance. dma driver on the basis of substream->stream can read the correct one. No fear of deadlock in this.
Thanks, Akshu
participants (3)
-
Agrawal, Akshu
-
Akshu Agrawal
-
Mark Brown