ASoC: fsl_sai: Refine enable/disable TE/RE sequence in trigger()
authorShengjiu Wang <shengjiu.wang@nxp.com>
Wed, 5 Aug 2020 06:34:11 +0000 (14:34 +0800)
committerMark Brown <broonie@kernel.org>
Mon, 17 Aug 2020 13:56:49 +0000 (14:56 +0100)
Current code enables TCSR.TE and RCSR.RE together, and disable
TCSR.TE and RCSR.RE together in trigger(), which only supports
one operation mode:
1. Rx synchronous with Tx: TE is last enabled and first disabled

Other operation mode need to be considered also:
2. Tx synchronous with Rx: RE is last enabled and first disabled.
3. Asynchronous mode: Tx and Rx are independent.

So the enable TCSR.TE and RCSR.RE sequence and the disable
sequence need to be refined accordingly for #2 and #3.

There is slightly against what RM recommennds with this change.
For example in Rx synchronous with Tx mode, case "aplay 1.wav;
arecord 2.wav" enable TE before RE. But it should be safe to
do so, judging by years of testing results.

Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
Reviewed-by: Nicolin Chen <nicoleotsuka@gmail.com>
Link: https://lore.kernel.org/r/20200805063413.4610-2-shengjiu.wang@nxp.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/fsl/fsl_sai.c

index cdff739..566c474 100644 (file)
@@ -37,6 +37,24 @@ static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = {
        .list = fsl_sai_rates,
 };
 
+/**
+ * fsl_sai_dir_is_synced - Check if stream is synced by the opposite stream
+ *
+ * SAI supports synchronous mode using bit/frame clocks of either Transmitter's
+ * or Receiver's for both streams. This function is used to check if clocks of
+ * the stream's are synced by the opposite stream.
+ *
+ * @sai: SAI context
+ * @dir: stream direction
+ */
+static inline bool fsl_sai_dir_is_synced(struct fsl_sai *sai, int dir)
+{
+       int adir = (dir == TX) ? RX : TX;
+
+       /* current dir in async mode while opposite dir in sync mode */
+       return !sai->synchronous[dir] && sai->synchronous[adir];
+}
+
 static irqreturn_t fsl_sai_isr(int irq, void *devid)
 {
        struct fsl_sai *sai = (struct fsl_sai *)devid;
@@ -522,6 +540,38 @@ static int fsl_sai_hw_free(struct snd_pcm_substream *substream,
        return 0;
 }
 
+static void fsl_sai_config_disable(struct fsl_sai *sai, int dir)
+{
+       unsigned int ofs = sai->soc_data->reg_offset;
+       bool tx = dir == TX;
+       u32 xcsr, count = 100;
+
+       regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs),
+                          FSL_SAI_CSR_TERE, 0);
+
+       /* TERE will remain set till the end of current frame */
+       do {
+               udelay(10);
+               regmap_read(sai->regmap, FSL_SAI_xCSR(tx, ofs), &xcsr);
+       } while (--count && xcsr & FSL_SAI_CSR_TERE);
+
+       regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs),
+                          FSL_SAI_CSR_FR, FSL_SAI_CSR_FR);
+
+       /*
+        * For sai master mode, after several open/close sai,
+        * there will be no frame clock, and can't recover
+        * anymore. Add software reset to fix this issue.
+        * This is a hardware bug, and will be fix in the
+        * next sai version.
+        */
+       if (!sai->is_slave_mode) {
+               /* Software Reset */
+               regmap_write(sai->regmap, FSL_SAI_xCSR(tx, ofs), FSL_SAI_CSR_SR);
+               /* Clear SR bit to finish the reset */
+               regmap_write(sai->regmap, FSL_SAI_xCSR(tx, ofs), 0);
+       }
+}
 
 static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd,
                struct snd_soc_dai *cpu_dai)
@@ -530,7 +580,9 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd,
        unsigned int ofs = sai->soc_data->reg_offset;
 
        bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
-       u32 xcsr, count = 100;
+       int adir = tx ? RX : TX;
+       int dir = tx ? TX : RX;
+       u32 xcsr;
 
        /*
         * Asynchronous mode: Clear SYNC for both Tx and Rx.
@@ -553,10 +605,22 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd,
                regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs),
                                   FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE);
 
-               regmap_update_bits(sai->regmap, FSL_SAI_RCSR(ofs),
-                                  FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
-               regmap_update_bits(sai->regmap, FSL_SAI_TCSR(ofs),
+               regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs),
                                   FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
+               /*
+                * Enable the opposite direction for synchronous mode
+                * 1. Tx sync with Rx: only set RE for Rx; set TE & RE for Tx
+                * 2. Rx sync with Tx: only set TE for Tx; set RE & TE for Rx
+                *
+                * RM recommends to enable RE after TE for case 1 and to enable
+                * TE after RE for case 2, but we here may not always guarantee
+                * that happens: "arecord 1.wav; aplay 2.wav" in case 1 enables
+                * TE after RE, which is against what RM recommends but should
+                * be safe to do, judging by years of testing results.
+                */
+               if (fsl_sai_dir_is_synced(sai, adir))
+                       regmap_update_bits(sai->regmap, FSL_SAI_xCSR((!tx), ofs),
+                                          FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
 
                regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs),
                                   FSL_SAI_CSR_xIE_MASK, FSL_SAI_FLAGS);
@@ -571,43 +635,23 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd,
 
                /* Check if the opposite FRDE is also disabled */
                regmap_read(sai->regmap, FSL_SAI_xCSR(!tx, ofs), &xcsr);
-               if (!(xcsr & FSL_SAI_CSR_FRDE)) {
-                       /* Disable both directions and reset their FIFOs */
-                       regmap_update_bits(sai->regmap, FSL_SAI_TCSR(ofs),
-                                          FSL_SAI_CSR_TERE, 0);
-                       regmap_update_bits(sai->regmap, FSL_SAI_RCSR(ofs),
-                                          FSL_SAI_CSR_TERE, 0);
-
-                       /* TERE will remain set till the end of current frame */
-                       do {
-                               udelay(10);
-                               regmap_read(sai->regmap,
-                                           FSL_SAI_xCSR(tx, ofs), &xcsr);
-                       } while (--count && xcsr & FSL_SAI_CSR_TERE);
-
-                       regmap_update_bits(sai->regmap, FSL_SAI_TCSR(ofs),
-                                          FSL_SAI_CSR_FR, FSL_SAI_CSR_FR);
-                       regmap_update_bits(sai->regmap, FSL_SAI_RCSR(ofs),
-                                          FSL_SAI_CSR_FR, FSL_SAI_CSR_FR);
-
-                       /*
-                        * For sai master mode, after several open/close sai,
-                        * there will be no frame clock, and can't recover
-                        * anymore. Add software reset to fix this issue.
-                        * This is a hardware bug, and will be fix in the
-                        * next sai version.
-                        */
-                       if (!sai->is_slave_mode) {
-                               /* Software Reset for both Tx and Rx */
-                               regmap_write(sai->regmap, FSL_SAI_TCSR(ofs),
-                                            FSL_SAI_CSR_SR);
-                               regmap_write(sai->regmap, FSL_SAI_RCSR(ofs),
-                                            FSL_SAI_CSR_SR);
-                               /* Clear SR bit to finish the reset */
-                               regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), 0);
-                               regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), 0);
-                       }
-               }
+
+               /*
+                * If opposite stream provides clocks for synchronous mode and
+                * it is inactive, disable it before disabling the current one
+                */
+               if (fsl_sai_dir_is_synced(sai, adir) && !(xcsr & FSL_SAI_CSR_FRDE))
+                       fsl_sai_config_disable(sai, adir);
+
+               /*
+                * Disable current stream if either of:
+                * 1. current stream doesn't provide clocks for synchronous mode
+                * 2. current stream provides clocks for synchronous mode but no
+                *    more stream is active.
+                */
+               if (!fsl_sai_dir_is_synced(sai, dir) || !(xcsr & FSL_SAI_CSR_FRDE))
+                       fsl_sai_config_disable(sai, dir);
+
                break;
        default:
                return -EINVAL;