From 3f4436826e70ed33c848f0bfc13a06f1d98be4f0 Mon Sep 17 00:00:00 2001 From: Sugar Zhang Date: Mon, 5 Aug 2024 14:27:29 +0800 Subject: [PATCH] ASoC: rockchip: i2s-tdm: Fix 1-Bit offset case There is a ASYNC bridge between HCLK and SCLK domain. On AUPLL case, we found RX data shift ahead 1-Bit on TRCM-TX sometime (0.00x%), but it success on GPLL(50w+). The root cause: HCLK Domain: Config the XFER-3, pull up SIGNAL to HIGH. SCLK Domain: SCLK_RX/TX samples the HIGH level to start. Because HCLK Domain is async to SCLK Domain, So, there is a risk that RX samels the HIGH Level, but RX not. (at the edge XFER from LOW to HIGH) Solution: 1, Gate the SCLK 2, Config the XFER-3 3, Ungate the SCLK Thus, TX/RX Always samples the right Level at the same time. After this patch, Test passed over 50w+. Test Script: #!/bin/sh count=0 killall aplay sleep 1 while true do yes `echo -en "\x11\x11\x22\x22"` | tr -d '\n' | \ aplay -D hw:2,0 --period-size=1024 --buffer-size=4096 -r 192000 -c 2 -f s16_le &>/dev/null & sleep 0.1 rxd=`io -4 0xfe470028 | awk '{print $2}'` echo "[$count]: $rxd" if [ "$rxd" != "22221111" ]; then echo "FAIL: mismatch: $rxd, expected: 22221111" break fi count=$((count + 1)) killall aplay sleep .1 done Signed-off-by: Sugar Zhang Change-Id: I10258b92d2f62ab36b5ccc55e8bcc752f3af9d4f --- sound/soc/rockchip/rockchip_i2s_tdm.c | 48 ++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c index cc7ca3a14b50..20709c36bfa3 100644 --- a/sound/soc/rockchip/rockchip_i2s_tdm.c +++ b/sound/soc/rockchip/rockchip_i2s_tdm.c @@ -986,6 +986,48 @@ static int rockchip_i2s_tdm_slave_one_frame_start(struct rk_i2s_tdm_dev *i2s_tdm return 0; } +static int rockchip_i2s_tdm_xfer_with_gate(struct rk_i2s_tdm_dev *i2s_tdm) +{ + struct clk *mclk = NULL; + + switch (i2s_tdm->clk_trcm) { + case I2S_CKR_TRCM_TXONLY: + mclk = i2s_tdm->mclk_tx; + break; + case I2S_CKR_TRCM_RXONLY: + mclk = i2s_tdm->mclk_rx; + break; + default: + dev_err(i2s_tdm->dev, "Must use in TRCM mode.\n"); + return -EINVAL; + } + + rockchip_utils_clk_gate_endisable(i2s_tdm->dev, mclk, 0); + udelay(10); + regmap_update_bits(i2s_tdm->regmap, I2S_XFER, + I2S_XFER_TXS_MASK | + I2S_XFER_RXS_MASK, + I2S_XFER_TXS_START | + I2S_XFER_RXS_START); + udelay(10); + rockchip_utils_clk_gate_endisable(i2s_tdm->dev, mclk, 1); + + return 0; +} + +static int rockchip_i2s_tdm_trcm_xfer(struct rk_i2s_tdm_dev *i2s_tdm) +{ + /* No need to do GATE for HAVE_SYNC_RESET case */ + if (i2s_tdm->soc_data && i2s_tdm->soc_data->src_clk_ctrl) + return regmap_update_bits(i2s_tdm->regmap, I2S_XFER, + I2S_XFER_TXS_MASK | + I2S_XFER_RXS_MASK, + I2S_XFER_TXS_START | + I2S_XFER_RXS_START); + + return rockchip_i2s_tdm_xfer_with_gate(i2s_tdm); +} + static void rockchip_i2s_tdm_xfer_start(struct rk_i2s_tdm_dev *i2s_tdm, int stream) { @@ -1000,11 +1042,7 @@ static void rockchip_i2s_tdm_xfer_start(struct rk_i2s_tdm_dev *i2s_tdm, if (i2s_tdm->clk_trcm) { rockchip_i2s_tdm_reset_assert(i2s_tdm); - regmap_update_bits(i2s_tdm->regmap, I2S_XFER, - I2S_XFER_TXS_MASK | - I2S_XFER_RXS_MASK, - I2S_XFER_TXS_START | - I2S_XFER_RXS_START); + rockchip_i2s_tdm_trcm_xfer(i2s_tdm); rockchip_i2s_tdm_reset_deassert(i2s_tdm); } else if (stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_update_bits(i2s_tdm->regmap, I2S_XFER,