ASoC: fsl_sai: Refine enable/disable TE/RE sequence in trigger()
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>
This commit is contained in:
parent
549ade5721
commit
94741eba63
|
@ -37,6 +37,24 @@ static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = {
|
||||||
.list = fsl_sai_rates,
|
.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)
|
static irqreturn_t fsl_sai_isr(int irq, void *devid)
|
||||||
{
|
{
|
||||||
struct fsl_sai *sai = (struct fsl_sai *)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;
|
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,
|
static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||||
struct snd_soc_dai *cpu_dai)
|
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;
|
unsigned int ofs = sai->soc_data->reg_offset;
|
||||||
|
|
||||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
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.
|
* 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),
|
regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs),
|
||||||
FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE);
|
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),
|
|
||||||
FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
|
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),
|
regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs),
|
||||||
FSL_SAI_CSR_xIE_MASK, FSL_SAI_FLAGS);
|
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 */
|
/* Check if the opposite FRDE is also disabled */
|
||||||
regmap_read(sai->regmap, FSL_SAI_xCSR(!tx, ofs), &xcsr);
|
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 {
|
* If opposite stream provides clocks for synchronous mode and
|
||||||
udelay(10);
|
* it is inactive, disable it before disabling the current one
|
||||||
regmap_read(sai->regmap,
|
*/
|
||||||
FSL_SAI_xCSR(tx, ofs), &xcsr);
|
if (fsl_sai_dir_is_synced(sai, adir) && !(xcsr & FSL_SAI_CSR_FRDE))
|
||||||
} while (--count && xcsr & FSL_SAI_CSR_TERE);
|
fsl_sai_config_disable(sai, adir);
|
||||||
|
|
||||||
regmap_update_bits(sai->regmap, FSL_SAI_TCSR(ofs),
|
/*
|
||||||
FSL_SAI_CSR_FR, FSL_SAI_CSR_FR);
|
* Disable current stream if either of:
|
||||||
regmap_update_bits(sai->regmap, FSL_SAI_RCSR(ofs),
|
* 1. current stream doesn't provide clocks for synchronous mode
|
||||||
FSL_SAI_CSR_FR, FSL_SAI_CSR_FR);
|
* 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);
|
||||||
|
|
||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
Loading…
Reference in New Issue