ASoC: meson: tdm fixes

Merge series from Jerome Brunet <jbrunet@baylibre.com>:

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@baylibre.com
[2]: https://lore.kernel.org/linux-amlogic/20220421155725.2589089-1-narmstrong@baylibre.com
This commit is contained in:
Mark Brown 2024-04-30 23:36:23 +09:00
commit c5782bb546
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
5 changed files with 94 additions and 21 deletions

View File

@ -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)

View File

@ -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);
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);
/* Ack irqs */
axg_fifo_ack_irq(fifo, status);
return IRQ_RETVAL(status);
/* Use the thread to call period elapsed on nonatomic links */
if (status & FIFO_INT_COUNT_REPEAT)
return IRQ_WAKE_THREAD;
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_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;

View File

@ -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");

View File

@ -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,11 @@ 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,
@ -354,19 +359,32 @@ static int axg_tdm_iface_hw_free(struct snd_pcm_substream *substream,
{
struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
/* Stop all attached formatters */
axg_tdm_stream_stop(ts);
return 0;
return axg_tdm_stream_set_cont_clocks(ts, 0);
}
static int axg_tdm_iface_prepare(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);
/* Force all attached formatters to update */
return axg_tdm_stream_reset(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_remove_dai(struct snd_soc_dai *dai)
@ -412,8 +430,8 @@ 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 */

View File

@ -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)
{