ASoC: Tegra: Improve capture stopping logic
Sumit Bhattacharya [Wed, 17 Apr 2013 12:43:04 +0000 (17:43 +0530)]
During stopping capture session when I2s RX port is disabled I2S
FIFO may contain 2 bytes of data. In case of stereo capture done
in I2S mode I2S CIF will be configured for stereo and it will not
transmit residual 2 byte I2S FIFO data. As a result when next
capture session starts audio channel will get reversed due to
residual 2 bytes of data.

To solve this issue do a SOFT_RESET of I2S channel if after
disabling of I2S RX port I2S FIFO does not get empty. Also disable
APBIF FIFO after I2S RX port is disabled to follow source to
destination disabling sequence.

As a precaution similar check is also added form playback stop path.

Bug 1255915

Change-Id: I8ab74c96ca00e2a1fda0abfeb73244a83b847005
Signed-off-by: Sumit Bhattacharya <sumitb@nvidia.com>
Reviewed-on: http://git-master/r/220203
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Scott Peterson <speterson@nvidia.com>
GVS: Gerrit_Virtual_Submit

sound/soc/tegra/tegra30_i2s.c

index 20faef8..da18984 100644 (file)
@@ -790,6 +790,25 @@ static int tegra30_i2s_hw_params(struct snd_pcm_substream *substream,
        return 0;
 }
 
+static int tegra30_i2s_soft_reset(struct tegra30_i2s *i2s)
+{
+       int dcnt = 10;
+
+       i2s->reg_ctrl |= TEGRA30_I2S_CTRL_SOFT_RESET;
+       tegra30_i2s_write(i2s, TEGRA30_I2S_CTRL, i2s->reg_ctrl);
+
+       while ((tegra30_i2s_read(i2s, TEGRA30_I2S_CTRL) &
+                      TEGRA30_I2S_CTRL_SOFT_RESET) && dcnt--)
+               udelay(100);
+
+       /* Restore reg_ctrl to ensure if a concurrent playback/capture
+          session was active it continues after SOFT_RESET */
+       i2s->reg_ctrl &= ~TEGRA30_I2S_CTRL_SOFT_RESET;
+       tegra30_i2s_write(i2s, TEGRA30_I2S_CTRL, i2s->reg_ctrl);
+
+       return (dcnt < 0) ? -ETIMEDOUT : 0;
+}
+
 static void tegra30_i2s_start_playback(struct tegra30_i2s *i2s)
 {
        tegra30_ahub_enable_tx_fifo(i2s->txcif);
@@ -808,9 +827,24 @@ static void tegra30_i2s_stop_playback(struct tegra30_i2s *i2s)
        if (i2s->playback_ref_count == 1) {
                i2s->reg_ctrl &= ~TEGRA30_I2S_CTRL_XFER_EN_TX;
                tegra30_i2s_write(i2s, TEGRA30_I2S_CTRL, i2s->reg_ctrl);
+               while (tegra30_ahub_tx_fifo_is_enabled(i2s->id) && dcnt--)
+                       udelay(100);
+
+               while (!tegra30_ahub_tx_fifo_is_empty(i2s->id) && dcnt--)
+                       udelay(100);
+
+               /* In case I2S FIFO does not get empty do a soft reset of the
+                  I2S channel to prevent channel reversal in next session */
+               if (dcnt < 0) {
+                       tegra30_i2s_soft_reset(i2s);
+
+                       dcnt = 10;
+                       while (!tegra30_ahub_tx_fifo_is_empty(i2s->id) &&
+                              dcnt--)
+                               udelay(100);
+               }
        }
-       while (!tegra30_ahub_tx_fifo_is_empty(i2s->id) && dcnt--)
-               udelay(100);
+
 }
 
 static void tegra30_i2s_start_capture(struct tegra30_i2s *i2s)
@@ -826,15 +860,27 @@ static void tegra30_i2s_stop_capture(struct tegra30_i2s *i2s)
 {
        int dcnt = 10;
        if (!i2s->is_call_mode_rec && (i2s->capture_ref_count == 1)) {
-               tegra30_ahub_disable_rx_fifo(i2s->rxcif);
                i2s->reg_ctrl &= ~TEGRA30_I2S_CTRL_XFER_EN_RX;
                tegra30_i2s_write(i2s, TEGRA30_I2S_CTRL, i2s->reg_ctrl);
                while (tegra30_ahub_rx_fifo_is_enabled(i2s->id) && dcnt--)
                        udelay(100);
-       }
 
-       while (!tegra30_ahub_rx_fifo_is_empty(i2s->id) && dcnt--)
-               udelay(100);
+               while (!tegra30_ahub_rx_fifo_is_empty(i2s->id) && dcnt--)
+                       udelay(100);
+
+               /* In case I2S FIFO does not get empty do a soft reset of
+                  the I2S channel to prevent channel reversal in next capture
+                  session */
+               if (dcnt < 0) {
+                       tegra30_i2s_soft_reset(i2s);
+
+                       dcnt = 10;
+                       while (!tegra30_ahub_rx_fifo_is_empty(i2s->id) &&
+                              dcnt--)
+                               udelay(100);
+               }
+               tegra30_ahub_disable_rx_fifo(i2s->rxcif);
+       }
 }
 
 static int tegra30_i2s_trigger(struct snd_pcm_substream *substream, int cmd,