From cae65d78f0f440c12703abd102056806f3a2b59c Mon Sep 17 00:00:00 2001 From: Sugar Zhang Date: Fri, 8 Jul 2022 19:11:23 +0800 Subject: [PATCH] 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 Change-Id: I4416c3424f3778560cadb94f585c0acb06eb3f40 --- sound/soc/rockchip/rockchip_i2s_tdm.c | 82 ++++++++++++++++++++++++++- sound/soc/rockchip/rockchip_i2s_tdm.h | 8 +-- 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c index e192befa027b..20c8a4e9c3bc 100644 --- a/sound/soc/rockchip/rockchip_i2s_tdm.c +++ b/sound/soc/rockchip/rockchip_i2s_tdm.c @@ -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; diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.h b/sound/soc/rockchip/rockchip_i2s_tdm.h index e46b63031ca0..2a8c48930aa6 100644 --- a/sound/soc/rockchip/rockchip_i2s_tdm.h +++ b/sound/soc/rockchip/rockchip_i2s_tdm.h @@ -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