ASoC: fsl_sai: Set SAI Channel Mode to Output Mode
[linux-2.6-microblaze.git] / sound / soc / fsl / fsl_sai.c
index cdff739..b2d65e5 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;
@@ -332,6 +350,8 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
        unsigned int ofs = sai->soc_data->reg_offset;
        unsigned long clk_rate;
        u32 savediv = 0, ratio, savesub = freq;
+       int adir = tx ? RX : TX;
+       int dir = tx ? TX : RX;
        u32 id;
        int ret = 0;
 
@@ -390,19 +410,17 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
         * 4) For Tx and Rx are both Synchronous with another SAI, we just
         *    ignore it.
         */
-       if ((sai->synchronous[TX] && !sai->synchronous[RX]) ||
-           (!tx && !sai->synchronous[RX])) {
-               regmap_update_bits(sai->regmap, FSL_SAI_RCR2(ofs),
+       if (fsl_sai_dir_is_synced(sai, adir)) {
+               regmap_update_bits(sai->regmap, FSL_SAI_xCR2(!tx, ofs),
                                   FSL_SAI_CR2_MSEL_MASK,
                                   FSL_SAI_CR2_MSEL(sai->mclk_id[tx]));
-               regmap_update_bits(sai->regmap, FSL_SAI_RCR2(ofs),
+               regmap_update_bits(sai->regmap, FSL_SAI_xCR2(!tx, ofs),
                                   FSL_SAI_CR2_DIV_MASK, savediv - 1);
-       } else if ((sai->synchronous[RX] && !sai->synchronous[TX]) ||
-                  (tx && !sai->synchronous[TX])) {
-               regmap_update_bits(sai->regmap, FSL_SAI_TCR2(ofs),
+       } else if (!sai->synchronous[dir]) {
+               regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, ofs),
                                   FSL_SAI_CR2_MSEL_MASK,
                                   FSL_SAI_CR2_MSEL(sai->mclk_id[tx]));
-               regmap_update_bits(sai->regmap, FSL_SAI_TCR2(ofs),
+               regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, ofs),
                                   FSL_SAI_CR2_DIV_MASK, savediv - 1);
        }
 
@@ -424,6 +442,8 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream,
        u32 val_cr4 = 0, val_cr5 = 0;
        u32 slots = (channels == 1) ? 2 : channels;
        u32 slot_width = word_width;
+       int adir = tx ? RX : TX;
+       u32 pins;
        int ret;
 
        if (sai->slots)
@@ -432,6 +452,8 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream,
        if (sai->slot_width)
                slot_width = sai->slot_width;
 
+       pins = DIV_ROUND_UP(channels, slots);
+
        if (!sai->is_slave_mode) {
                if (sai->bclk_ratio)
                        ret = fsl_sai_set_bclk(cpu_dai, tx,
@@ -467,42 +489,38 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream,
 
        val_cr4 |= FSL_SAI_CR4_FRSZ(slots);
 
+       /* Set to output mode to avoid tri-stated data pins */
+       if (tx)
+               val_cr4 |= FSL_SAI_CR4_CHMOD;
+
        /*
         * For SAI master mode, when Tx(Rx) sync with Rx(Tx) clock, Rx(Tx) will
         * generate bclk and frame clock for Tx(Rx), we should set RCR4(TCR4),
-        * RCR5(TCR5) and RMR(TMR) for playback(capture), or there will be sync
-        * error.
+        * RCR5(TCR5) for playback(capture), or there will be sync error.
         */
 
-       if (!sai->is_slave_mode) {
-               if (!sai->synchronous[TX] && sai->synchronous[RX] && !tx) {
-                       regmap_update_bits(sai->regmap, FSL_SAI_TCR4(ofs),
-                               FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK,
-                               val_cr4);
-                       regmap_update_bits(sai->regmap, FSL_SAI_TCR5(ofs),
-                               FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
-                               FSL_SAI_CR5_FBT_MASK, val_cr5);
-                       regmap_write(sai->regmap, FSL_SAI_TMR,
-                               ~0UL - ((1 << channels) - 1));
-               } else if (!sai->synchronous[RX] && sai->synchronous[TX] && tx) {
-                       regmap_update_bits(sai->regmap, FSL_SAI_RCR4(ofs),
-                               FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK,
-                               val_cr4);
-                       regmap_update_bits(sai->regmap, FSL_SAI_RCR5(ofs),
-                               FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
-                               FSL_SAI_CR5_FBT_MASK, val_cr5);
-                       regmap_write(sai->regmap, FSL_SAI_RMR,
-                               ~0UL - ((1 << channels) - 1));
-               }
+       if (!sai->is_slave_mode && fsl_sai_dir_is_synced(sai, adir)) {
+               regmap_update_bits(sai->regmap, FSL_SAI_xCR4(!tx, ofs),
+                                  FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK |
+                                  FSL_SAI_CR4_CHMOD_MASK,
+                                  val_cr4);
+               regmap_update_bits(sai->regmap, FSL_SAI_xCR5(!tx, ofs),
+                                  FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
+                                  FSL_SAI_CR5_FBT_MASK, val_cr5);
        }
 
+       regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, ofs),
+                          FSL_SAI_CR3_TRCE_MASK,
+                          FSL_SAI_CR3_TRCE((1 << pins) - 1));
        regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs),
-                          FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK,
+                          FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK |
+                          FSL_SAI_CR4_CHMOD_MASK,
                           val_cr4);
        regmap_update_bits(sai->regmap, FSL_SAI_xCR5(tx, ofs),
                           FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
                           FSL_SAI_CR5_FBT_MASK, val_cr5);
-       regmap_write(sai->regmap, FSL_SAI_xMR(tx), ~0UL - ((1 << channels) - 1));
+       regmap_write(sai->regmap, FSL_SAI_xMR(tx),
+                    ~0UL - ((1 << min(channels, slots)) - 1));
 
        return 0;
 }
@@ -512,6 +530,10 @@ static int fsl_sai_hw_free(struct snd_pcm_substream *substream,
 {
        struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
        bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+       unsigned int ofs = sai->soc_data->reg_offset;
+
+       regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, ofs),
+                          FSL_SAI_CR3_TRCE_MASK, 0);
 
        if (!sai->is_slave_mode &&
                        sai->mclk_streams & BIT(substream->stream)) {
@@ -522,6 +544,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 +584,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 +609,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 +639,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;
@@ -620,14 +668,9 @@ static int fsl_sai_startup(struct snd_pcm_substream *substream,
                struct snd_soc_dai *cpu_dai)
 {
        struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
-       unsigned int ofs = sai->soc_data->reg_offset;
        bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
        int ret;
 
-       regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, ofs),
-                          FSL_SAI_CR3_TRCE_MASK,
-                          FSL_SAI_CR3_TRCE);
-
        /*
         * EDMA controller needs period size to be a multiple of
         * tx/rx maxburst
@@ -644,17 +687,6 @@ static int fsl_sai_startup(struct snd_pcm_substream *substream,
        return ret;
 }
 
-static void fsl_sai_shutdown(struct snd_pcm_substream *substream,
-               struct snd_soc_dai *cpu_dai)
-{
-       struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
-       unsigned int ofs = sai->soc_data->reg_offset;
-       bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
-
-       regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, ofs),
-                          FSL_SAI_CR3_TRCE_MASK, 0);
-}
-
 static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = {
        .set_bclk_ratio = fsl_sai_set_dai_bclk_ratio,
        .set_sysclk     = fsl_sai_set_dai_sysclk,
@@ -664,7 +696,6 @@ static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = {
        .hw_free        = fsl_sai_hw_free,
        .trigger        = fsl_sai_trigger,
        .startup        = fsl_sai_startup,
-       .shutdown       = fsl_sai_shutdown,
 };
 
 static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai)
@@ -928,7 +959,7 @@ static int fsl_sai_probe(struct platform_device *pdev)
                        "bus", base, &fsl_sai_regmap_config);
 
        /* Compatible with old DTB cases */
-       if (IS_ERR(sai->regmap))
+       if (IS_ERR(sai->regmap) && PTR_ERR(sai->regmap) != -EPROBE_DEFER)
                sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
                                "sai", base, &fsl_sai_regmap_config);
        if (IS_ERR(sai->regmap)) {