ASoC: rockchip: i2s-tdm: Add support for FIFO-XRUN detection

This is useful for performance debug, throwing a WARNNING if
FIFO-XRUN detected and then do snd_pcm_stop_xrun to stop stream
and notify user XRUN state.

TESTCASE:
On the 192K-8CH-32BIT situation, FIFO full time is 83us (16 / 192000).
Considering WATERLEVEL-16: 41.6us, WATERLEVEL-24: 62.5us.
if any path (I2S<->DMA<->BUS<->DDR) blocked over this duration,
FIFO xrun raised.

This testcase injects a DMC-DVFS (> 80us) to reproduce FIFO-XRUN.

Suggested to disable DMC-DVFS or switch to DMC-DVFS-SCENE policy
to fix this on this situation.

OTOH, optimizing DMC-DVFS (<40us) or place audio buffer into SRAM
is also an alternative.

x.sh:

  /#!/bin/sh

  echo performance > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor
  echo performance > /sys/devices/system/cpu/cpufreq/policy4/scaling_governor
  echo performance > /sys/devices/system/cpu/cpufreq/policy6/scaling_governor

  freqs=`cat /sys/devices/platform/dmc/devfreq/dmc/available_frequencies`

  echo "userspace" > /sys/devices/platform/dmc/devfreq/dmc/governor

  aplay -D hw:3,0 --period-size=1024 --buffer-size=4096 -r 192000 -c 8 \
        -f s32_le /dev/zero &
  arecord -D hw:1,0 --period-size=1024 --buffer-size=4096 -r 192000 -c 8 \
        -f s32_le /dev/zero &
  sleep 1

  for f in $freqs
  do
          echo "dmc: set_freq $f"
          echo $f > /sys/devices/platform/dmc/devfreq/dmc/userspace/set_freq
          sleep 1
  done

  killall aplay
  killall arecord

Result:

  root@RK3588:/data# ./x.sh
  Playing raw data '/dev/zero' : Signed 32 bit Little Endian, Rate 192000 Hz, Channels 8
  Recording WAVE '/dev/zero'   : Signed 32 bit Little Endian, Rate 192000 Hz, Channels 8

  dmc: set_freq 528000000
  [   69.222572] rockchip-i2s-tdm fddf0000.i2s: TX FIFO Underrun
  [   69.222615] rockchip-i2s-tdm fe470000.i2s: RX FIFO Overrun
  dmc: set_freq 1068000000
  [   70.241013] rockchip-i2s-tdm fddf0000.i2s: TX FIFO Underrun
  [   70.241109] rockchip-i2s-tdm fe470000.i2s: RX FIFO Overrun
  dmc: set_freq 1560000000
  [   71.259416] rockchip-i2s-tdm fddf0000.i2s: TX FIFO Underrun
  [   71.259452] rockchip-i2s-tdm fe470000.i2s: RX FIFO Overrun
  dmc: set_freq 2112000000
  [   72.277841] rockchip-i2s-tdm fddf0000.i2s: TX FIFO Underrun
  [   72.277875] rockchip-i2s-tdm fe470000.i2s: RX FIFO Overrun

Signed-off-by: Sugar Zhang <sugar.zhang@rock-chips.com>
Change-Id: I4416c3424f3778560cadb94f585c0acb06eb3f40
This commit is contained in:
Sugar Zhang
2022-07-08 19:11:23 +08:00
committed by Tao Huang
parent d2df348f0c
commit cae65d78f0
2 changed files with 85 additions and 5 deletions
+81 -1
View File
@@ -85,6 +85,7 @@ struct rk_i2s_tdm_dev {
struct regmap *grf;
struct snd_dmaengine_dai_dma_data capture_dma_data;
struct snd_dmaengine_dai_dma_data playback_dma_data;
struct snd_pcm_substream *substreams[SNDRV_PCM_STREAM_LAST + 1];
struct reset_control *tx_reset;
struct reset_control *rx_reset;
const struct rk_i2s_soc_data *soc_data;
@@ -438,9 +439,32 @@ static void rockchip_i2s_tdm_tx_fifo_padding(struct rk_i2s_tdm_dev *i2s_tdm, boo
regmap_write(i2s_tdm->regmap, I2S_TXDR, 0x0);
}
static void rockchip_i2s_tdm_fifo_xrun_detect(struct rk_i2s_tdm_dev *i2s_tdm,
int stream, bool en)
{
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
/* clear irq status which was asserted before TXUIE enabled */
regmap_update_bits(i2s_tdm->regmap, I2S_INTCR,
I2S_INTCR_TXUIC, I2S_INTCR_TXUIC);
regmap_update_bits(i2s_tdm->regmap, I2S_INTCR,
I2S_INTCR_TXUIE_MASK,
I2S_INTCR_TXUIE(en));
} else {
/* clear irq status which was asserted before RXOIE enabled */
regmap_update_bits(i2s_tdm->regmap, I2S_INTCR,
I2S_INTCR_RXOIC, I2S_INTCR_RXOIC);
regmap_update_bits(i2s_tdm->regmap, I2S_INTCR,
I2S_INTCR_RXOIE_MASK,
I2S_INTCR_RXOIE(en));
}
}
static void rockchip_i2s_tdm_dma_ctrl(struct rk_i2s_tdm_dev *i2s_tdm,
int stream, bool en)
{
if (!en)
rockchip_i2s_tdm_fifo_xrun_detect(i2s_tdm, stream, 0);
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
if (i2s_tdm->quirks & QUIRK_HDMI_PATH)
rockchip_i2s_tdm_tx_fifo_padding(i2s_tdm, en);
@@ -463,6 +487,9 @@ static void rockchip_i2s_tdm_dma_ctrl(struct rk_i2s_tdm_dev *i2s_tdm,
I2S_DMACR_RDE_MASK,
I2S_DMACR_RDE(en));
}
if (en)
rockchip_i2s_tdm_fifo_xrun_detect(i2s_tdm, stream, 1);
}
static void rockchip_i2s_tdm_xfer_start(struct rk_i2s_tdm_dev *i2s_tdm,
@@ -1466,6 +1493,11 @@ static int rockchip_i2s_tdm_startup(struct snd_pcm_substream *substream,
{
struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai);
if (i2s_tdm->substreams[substream->stream])
return -EBUSY;
i2s_tdm->substreams[substream->stream] = substream;
/*
* Suggested to stop audio source before HDMI configure to make
* sure audio data integrity on HDMI-PATH-ALWAYS-ON situation.
@@ -1479,8 +1511,17 @@ static int rockchip_i2s_tdm_startup(struct snd_pcm_substream *substream,
return 0;
}
static void rockchip_i2s_tdm_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai);
i2s_tdm->substreams[substream->stream] = NULL;
}
static const struct snd_soc_dai_ops rockchip_i2s_tdm_dai_ops = {
.startup = rockchip_i2s_tdm_startup,
.shutdown = rockchip_i2s_tdm_shutdown,
.hw_params = rockchip_i2s_tdm_hw_params,
.set_sysclk = rockchip_i2s_tdm_set_sysclk,
.set_fmt = rockchip_i2s_tdm_set_fmt,
@@ -1542,6 +1583,7 @@ static bool rockchip_i2s_tdm_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case I2S_TXFIFOLR:
case I2S_INTCR:
case I2S_INTSR:
case I2S_CLR:
case I2S_TXDR:
@@ -1932,6 +1974,34 @@ static const struct snd_dlp_config dconfig = {
.get_fifo_count = rockchip_i2s_tdm_get_fifo_count,
};
static irqreturn_t rockchip_i2s_tdm_isr(int irq, void *devid)
{
struct rk_i2s_tdm_dev *i2s_tdm = (struct rk_i2s_tdm_dev *)devid;
struct snd_pcm_substream *substream;
u32 val;
regmap_read(i2s_tdm->regmap, I2S_INTSR, &val);
if (val & I2S_INTSR_TXUI_ACT) {
dev_warn_ratelimited(i2s_tdm->dev, "TX FIFO Underrun\n");
regmap_update_bits(i2s_tdm->regmap, I2S_INTCR,
I2S_INTCR_TXUIC, I2S_INTCR_TXUIC);
substream = i2s_tdm->substreams[SNDRV_PCM_STREAM_PLAYBACK];
if (substream)
snd_pcm_stop_xrun(substream);
}
if (val & I2S_INTSR_RXOI_ACT) {
dev_warn_ratelimited(i2s_tdm->dev, "RX FIFO Overrun\n");
regmap_update_bits(i2s_tdm->regmap, I2S_INTCR,
I2S_INTCR_RXOIC, I2S_INTCR_RXOIC);
substream = i2s_tdm->substreams[SNDRV_PCM_STREAM_CAPTURE];
if (substream)
snd_pcm_stop_xrun(substream);
}
return IRQ_HANDLED;
}
static int rockchip_i2s_tdm_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
@@ -1943,7 +2013,7 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev)
#ifdef HAVE_SYNC_RESET
bool sync;
#endif
int ret, val, i;
int ret, val, i, irq;
ret = rockchip_i2s_tdm_dai_prepare(pdev, &soc_dai);
if (ret)
@@ -2076,6 +2146,16 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev)
if (IS_ERR(i2s_tdm->regmap))
return PTR_ERR(i2s_tdm->regmap);
irq = platform_get_irq_optional(pdev, 0);
if (irq > 0) {
ret = devm_request_irq(&pdev->dev, irq, rockchip_i2s_tdm_isr,
IRQF_SHARED, node->name, i2s_tdm);
if (ret) {
dev_err(&pdev->dev, "failed to request irq %u\n", irq);
return ret;
}
}
i2s_tdm->playback_dma_data.addr = res->start + I2S_TXDR;
i2s_tdm->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
i2s_tdm->playback_dma_data.maxburst = MAXBURST_PER_FIFO;
+4 -4
View File
@@ -166,8 +166,8 @@
#define I2S_INTCR_RFT(x) (((x) - 1) << I2S_INTCR_RFT_SHIFT)
#define I2S_INTCR_RXOIC BIT(18)
#define I2S_INTCR_RXOIE_SHIFT 17
#define I2S_INTCR_RXOIE_DISABLE (0 << I2S_INTCR_RXOIE_SHIFT)
#define I2S_INTCR_RXOIE_ENABLE (1 << I2S_INTCR_RXOIE_SHIFT)
#define I2S_INTCR_RXOIE_MASK (1 << I2S_INTCR_RXOIE_SHIFT)
#define I2S_INTCR_RXOIE(x) ((x) << I2S_INTCR_RXOIE_SHIFT)
#define I2S_INTCR_RXFIE_SHIFT 16
#define I2S_INTCR_RXFIE_DISABLE (0 << I2S_INTCR_RXFIE_SHIFT)
#define I2S_INTCR_RXFIE_ENABLE (1 << I2S_INTCR_RXFIE_SHIFT)
@@ -176,8 +176,8 @@
#define I2S_INTCR_TFT_MASK (0x1f << I2S_INTCR_TFT_SHIFT)
#define I2S_INTCR_TXUIC BIT(2)
#define I2S_INTCR_TXUIE_SHIFT 1
#define I2S_INTCR_TXUIE_DISABLE (0 << I2S_INTCR_TXUIE_SHIFT)
#define I2S_INTCR_TXUIE_ENABLE (1 << I2S_INTCR_TXUIE_SHIFT)
#define I2S_INTCR_TXUIE_MASK (1 << I2S_INTCR_TXUIE_SHIFT)
#define I2S_INTCR_TXUIE(x) ((x) << I2S_INTCR_TXUIE_SHIFT)
/*
* INTSR