[PATCH 0/4] ASoC: meson: tdm fixes
This patchset fixes 2 problems on TDM which both find a solution by properly implementing the .trigger() callback for the TDM backend.
ATM, enabling the TDM formatters is done by the .prepare() callback because handling the formatter is slow due to necessary calls to CCF.
The first problem affects the TDMIN. Because .prepare() is called on DPCM backend first, the formatter are started before the FIFOs and this may cause a random channel shifts if the TDMIN use multiple lanes with more than 2 slots per lanes. Using trigger() allows to set the FE/BE order, solving the problem.
There has already been an attempt to fix this 3y ago [1] and reverted [2] It triggered a 'sleep in irq' error on the period IRQ. The solution is to just use the bottom half of threaded IRQ. This is patch #1. Patch #2 and #3 remain mostly the same as 3y ago.
For TDMOUT, the problem is on pause. ATM pause only stops the FIFO and the TDMOUT just starves. When it does, it will actually repeat the last sample continuously. Depending on the platform, if there is no high-pass filter on the analog path, this may translate to a constant position of the speaker membrane. There is no audible glitch but it may damage the speaker coil.
Properly stopping the TDMOUT in pause solves the problem. There is behaviour change associated with that fix. Clocks used to be continuous on pause because of the problem above. They will now be gated on pause by default, as they should. The last change introduce the proper support for continuous clocks, if needed.
[1]: https://lore.kernel.org/linux-amlogic/20211020114217.133153-1-jbrunet@baylib... [2]: https://lore.kernel.org/linux-amlogic/20220421155725.2589089-1-narmstrong@ba...
Jerome Brunet (4): ASoC: meson: axg-fifo: use threaded irq to check periods ASoC: meson: axg-card: make links nonatomic ASoC: meson: axg-tdm-interface: manage formatters in trigger ASoC: meson: axg-tdm: add continuous clock support
sound/soc/meson/axg-card.c | 1 + sound/soc/meson/axg-fifo.c | 29 +++++++++++++-------- sound/soc/meson/axg-tdm-formatter.c | 40 +++++++++++++++++++++++++++++ sound/soc/meson/axg-tdm-interface.c | 38 +++++++++++++++++++-------- sound/soc/meson/axg-tdm.h | 5 ++++ 5 files changed, 93 insertions(+), 20 deletions(-)
With the AXG audio subsystem, there is a possible random channel shift on TDM capture, when the slot number per lane is more than 2, and there is more than one lane used.
The problem has been there since the introduction of the axg audio support but such scenario is pretty uncommon. This is why there is no loud complains about the problem.
Solving the problem require to make the links non-atomic and use the trigger() callback to start FEs and BEs in the appropriate order.
This was tried in the past and reverted because it caused the block irq to sleep while atomic. However, instead of reverting, the solution is to call snd_pcm_period_elapsed() in a non atomic context.
Use the bottom half of a threaded IRQ to do so.
Fixes: 6dc4fa179fb8 ("ASoC: meson: add axg fifo base driver") Signed-off-by: Jerome Brunet jbrunet@baylibre.com --- sound/soc/meson/axg-fifo.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-)
diff --git a/sound/soc/meson/axg-fifo.c b/sound/soc/meson/axg-fifo.c index bebee0ca8e38..ecb3eb7a9723 100644 --- a/sound/soc/meson/axg-fifo.c +++ b/sound/soc/meson/axg-fifo.c @@ -204,18 +204,26 @@ static irqreturn_t axg_fifo_pcm_irq_block(int irq, void *dev_id) unsigned int status;
regmap_read(fifo->map, FIFO_STATUS1, &status); - status = FIELD_GET(STATUS1_INT_STS, status); + axg_fifo_ack_irq(fifo, status); + + /* Use the thread to call period elapsed on nonatomic links */ if (status & FIFO_INT_COUNT_REPEAT) - snd_pcm_period_elapsed(ss); - else - dev_dbg(axg_fifo_dev(ss), "unexpected irq - STS 0x%02x\n", - status); + return IRQ_WAKE_THREAD;
- /* Ack irqs */ - axg_fifo_ack_irq(fifo, status); + dev_dbg(axg_fifo_dev(ss), "unexpected irq - STS 0x%02x\n", + status); + + return IRQ_NONE; +} + +static irqreturn_t axg_fifo_pcm_irq_block_thread(int irq, void *dev_id) +{ + struct snd_pcm_substream *ss = dev_id; + + snd_pcm_period_elapsed(ss);
- return IRQ_RETVAL(status); + return IRQ_HANDLED; }
int axg_fifo_pcm_open(struct snd_soc_component *component, @@ -243,8 +251,9 @@ int axg_fifo_pcm_open(struct snd_soc_component *component, if (ret) return ret;
- ret = request_irq(fifo->irq, axg_fifo_pcm_irq_block, 0, - dev_name(dev), ss); + ret = request_threaded_irq(fifo->irq, axg_fifo_pcm_irq_block, + axg_fifo_pcm_irq_block_thread, + IRQF_ONESHOT, dev_name(dev), ss); if (ret) return ret;
Non atomic operations need to be performed in the trigger callback of the TDM interfaces. Those are BEs but what matters is the nonatomic flag of the FE in the DPCM context. Just set nonatomic for everything so, at least, what is done is clear.
Fixes: 7864a79f37b5 ("ASoC: meson: add axg sound card support") Signed-off-by: Jerome Brunet jbrunet@baylibre.com --- sound/soc/meson/axg-card.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/sound/soc/meson/axg-card.c b/sound/soc/meson/axg-card.c index 3180aa4d3a15..8c5605c1e34e 100644 --- a/sound/soc/meson/axg-card.c +++ b/sound/soc/meson/axg-card.c @@ -318,6 +318,7 @@ static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np,
dai_link->cpus = cpu; dai_link->num_cpus = 1; + dai_link->nonatomic = true;
ret = meson_card_parse_dai(card, np, dai_link->cpus); if (ret)
So far, the formatters have been reset/enabled using the .prepare() callback. This was done in this callback because walking the formatters use a mutex. A mutex is used because formatter handling require dealing possibly slow clock operation.
With the support of non-atomic, .trigger() callback may be used which also allows to properly enable and disable formatters on start but also pause/resume.
This solve a random shift on TDMIN as well repeated samples on for TDMOUT.
Fixes: d60e4f1e4be5 ("ASoC: meson: add tdm interface driver") Signed-off-by: Jerome Brunet jbrunet@baylibre.com --- sound/soc/meson/axg-tdm-interface.c | 34 ++++++++++++++++------------- 1 file changed, 19 insertions(+), 15 deletions(-)
diff --git a/sound/soc/meson/axg-tdm-interface.c b/sound/soc/meson/axg-tdm-interface.c index bf708717635b..8bf3735dedaa 100644 --- a/sound/soc/meson/axg-tdm-interface.c +++ b/sound/soc/meson/axg-tdm-interface.c @@ -349,26 +349,31 @@ static int axg_tdm_iface_hw_params(struct snd_pcm_substream *substream, return 0; }
-static int axg_tdm_iface_hw_free(struct snd_pcm_substream *substream, +static int axg_tdm_iface_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) { - struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct axg_tdm_stream *ts = + snd_soc_dai_get_dma_data(dai, substream);
- /* Stop all attached formatters */ - axg_tdm_stream_stop(ts); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + axg_tdm_stream_start(ts); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + axg_tdm_stream_stop(ts); + break; + default: + return -EINVAL; + }
return 0; }
-static int axg_tdm_iface_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); - - /* Force all attached formatters to update */ - return axg_tdm_stream_reset(ts); -} - static int axg_tdm_iface_remove_dai(struct snd_soc_dai *dai) { int stream; @@ -412,8 +417,7 @@ static const struct snd_soc_dai_ops axg_tdm_iface_ops = { .set_fmt = axg_tdm_iface_set_fmt, .startup = axg_tdm_iface_startup, .hw_params = axg_tdm_iface_hw_params, - .prepare = axg_tdm_iface_prepare, - .hw_free = axg_tdm_iface_hw_free, + .trigger = axg_tdm_iface_trigger, };
/* TDM Backend DAIs */
Some devices may need the clocks running, even while paused. Add support for this use case.
Signed-off-by: Jerome Brunet jbrunet@baylibre.com --- sound/soc/meson/axg-tdm-formatter.c | 40 +++++++++++++++++++++++++++++ sound/soc/meson/axg-tdm-interface.c | 16 +++++++++++- sound/soc/meson/axg-tdm.h | 5 ++++ 3 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/sound/soc/meson/axg-tdm-formatter.c b/sound/soc/meson/axg-tdm-formatter.c index 63333a2b0a9c..a6579efd3775 100644 --- a/sound/soc/meson/axg-tdm-formatter.c +++ b/sound/soc/meson/axg-tdm-formatter.c @@ -392,6 +392,46 @@ void axg_tdm_stream_free(struct axg_tdm_stream *ts) } EXPORT_SYMBOL_GPL(axg_tdm_stream_free);
+int axg_tdm_stream_set_cont_clocks(struct axg_tdm_stream *ts, + unsigned int fmt) +{ + int ret = 0; + + if (fmt & SND_SOC_DAIFMT_CONT) { + /* Clock are already enabled - skipping */ + if (ts->clk_enabled) + return 0; + + ret = clk_prepare_enable(ts->iface->mclk); + if (ret) + return ret; + + ret = clk_prepare_enable(ts->iface->sclk); + if (ret) + goto err_sclk; + + ret = clk_prepare_enable(ts->iface->lrclk); + if (ret) + goto err_lrclk; + + ts->clk_enabled = true; + return 0; + } + + /* Clocks are already disabled - skipping */ + if (!ts->clk_enabled) + return 0; + + clk_disable_unprepare(ts->iface->lrclk); +err_lrclk: + clk_disable_unprepare(ts->iface->sclk); +err_sclk: + clk_disable_unprepare(ts->iface->mclk); + ts->clk_enabled = false; + return ret; +} +EXPORT_SYMBOL_GPL(axg_tdm_stream_set_cont_clocks); + MODULE_DESCRIPTION("Amlogic AXG TDM formatter driver"); MODULE_AUTHOR("Jerome Brunet jbrunet@baylibre.com"); MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/meson/axg-tdm-interface.c b/sound/soc/meson/axg-tdm-interface.c index 8bf3735dedaa..62057c71f742 100644 --- a/sound/soc/meson/axg-tdm-interface.c +++ b/sound/soc/meson/axg-tdm-interface.c @@ -309,6 +309,7 @@ static int axg_tdm_iface_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); int ret;
switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { @@ -346,7 +347,19 @@ static int axg_tdm_iface_hw_params(struct snd_pcm_substream *substream, return ret; }
- return 0; + ret = axg_tdm_stream_set_cont_clocks(ts, iface->fmt); + if (ret) + dev_err(dai->dev, "failed to apply continuous clock setting\n"); + + return ret; +} + +static int axg_tdm_iface_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + + return axg_tdm_stream_set_cont_clocks(ts, 0); }
static int axg_tdm_iface_trigger(struct snd_pcm_substream *substream, @@ -417,6 +430,7 @@ static const struct snd_soc_dai_ops axg_tdm_iface_ops = { .set_fmt = axg_tdm_iface_set_fmt, .startup = axg_tdm_iface_startup, .hw_params = axg_tdm_iface_hw_params, + .hw_free = axg_tdm_iface_hw_free, .trigger = axg_tdm_iface_trigger, };
diff --git a/sound/soc/meson/axg-tdm.h b/sound/soc/meson/axg-tdm.h index 42f7470b9a7f..daaca10fec9e 100644 --- a/sound/soc/meson/axg-tdm.h +++ b/sound/soc/meson/axg-tdm.h @@ -58,12 +58,17 @@ struct axg_tdm_stream { unsigned int physical_width; u32 *mask; bool ready; + + /* For continuous clock tracking */ + bool clk_enabled; };
struct axg_tdm_stream *axg_tdm_stream_alloc(struct axg_tdm_iface *iface); void axg_tdm_stream_free(struct axg_tdm_stream *ts); int axg_tdm_stream_start(struct axg_tdm_stream *ts); void axg_tdm_stream_stop(struct axg_tdm_stream *ts); +int axg_tdm_stream_set_cont_clocks(struct axg_tdm_stream *ts, + unsigned int fmt);
static inline int axg_tdm_stream_reset(struct axg_tdm_stream *ts) {
On Fri, 26 Apr 2024 17:29:37 +0200, Jerome Brunet wrote:
This patchset fixes 2 problems on TDM which both find a solution by properly implementing the .trigger() callback for the TDM backend.
ATM, enabling the TDM formatters is done by the .prepare() callback because handling the formatter is slow due to necessary calls to CCF.
The first problem affects the TDMIN. Because .prepare() is called on DPCM backend first, the formatter are started before the FIFOs and this may cause a random channel shifts if the TDMIN use multiple lanes with more than 2 slots per lanes. Using trigger() allows to set the FE/BE order, solving the problem.
[...]
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
Thanks!
[1/4] ASoC: meson: axg-fifo: use threaded irq to check periods commit: b11d26660dff8d7430892008616452dc8e5fb0f3 [2/4] ASoC: meson: axg-card: make links nonatomic commit: dcba52ace7d4c12e2c8c273eff55ea03a84c8baf [3/4] ASoC: meson: axg-tdm-interface: manage formatters in trigger commit: f949ed458ad15a00d41b37c745ebadaef171aaae [4/4] ASoC: meson: axg-tdm: add continuous clock support commit: a5a89037d080e0870d7517c61f8b2123d58ab33b
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
participants (2)
-
Jerome Brunet
-
Mark Brown