Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1b3d7510 authored by Shengjiu Wang's avatar Shengjiu Wang Committed by Greg Kroah-Hartman
Browse files

ASoC: fsl_sai: Refine enable/disable TE/RE sequence in trigger()



[ Upstream commit 94741eba63c23b0f1527b0ae0125e6b553bde10e ]

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: default avatarShengjiu Wang <shengjiu.wang@nxp.com>
Reviewed-by: default avatarNicolin Chen <nicoleotsuka@gmail.com>
Link: https://lore.kernel.org/r/20200805063413.4610-2-shengjiu.wang@nxp.com


Signed-off-by: default avatarMark Brown <broonie@kernel.org>
Stable-dep-of: 269f399dc19f ("ASoC: fsl_sai: Disable bit clock with transmitter")
Signed-off-by: default avatarSasha Levin <sashal@kernel.org>
parent f9afb326
Loading
Loading
Loading
Loading
+85 −41
Original line number Diff line number Diff line
@@ -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;
@@ -523,6 +541,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)
@@ -531,7 +581,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.
@@ -554,9 +606,21 @@ 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),
		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_TCSR(ofs),
		/*
		 * 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),
@@ -572,43 +636,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);
		/*
		 * 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);

		/*
			 * 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.
		 * 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 (!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 (!fsl_sai_dir_is_synced(sai, dir) || !(xcsr & FSL_SAI_CSR_FRDE))
			fsl_sai_config_disable(sai, dir);

		break;
	default:
		return -EINVAL;