Unverified Commit 4f8cd05a authored by Biju Das's avatar Biju Das Committed by Mark Brown
Browse files

ASoC: sh: rz-ssi: Add full duplex support



Add full duplex support, to support simultaneous
playback/record on the same ssi channel.

Signed-off-by: default avatarBiju Das <biju.das.jz@bp.renesas.com>
Link: https://patch.msgid.link/20240715092322.119879-1-biju.das.jz@bp.renesas.com


Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent b3f35bae
Loading
Loading
Loading
Loading
+182 −75
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@
#define SSIFCR_RIE		BIT(2)
#define SSIFCR_TFRST		BIT(1)
#define SSIFCR_RFRST		BIT(0)
#define SSIFCR_FIFO_RST		(SSIFCR_TFRST | SSIFCR_RFRST)

#define SSIFSR_TDC_MASK		0x3f
#define SSIFSR_TDC_SHIFT	24
@@ -130,6 +131,14 @@ struct rz_ssi_priv {
	bool lrckp_fsync_fall;	/* LR clock polarity (SSICR.LRCKP) */
	bool bckp_rise;	/* Bit clock polarity (SSICR.BCKP) */
	bool dma_rt;

	/* Full duplex communication support */
	struct {
		unsigned int rate;
		unsigned int channels;
		unsigned int sample_width;
		unsigned int sample_bits;
	} hw_params_cache;
};

static void rz_ssi_dma_complete(void *data);
@@ -208,6 +217,11 @@ static bool rz_ssi_stream_is_valid(struct rz_ssi_priv *ssi,
	return ret;
}

static inline bool rz_ssi_is_stream_running(struct rz_ssi_stream *strm)
{
	return strm->substream && strm->running;
}

static void rz_ssi_stream_init(struct rz_ssi_stream *strm,
			       struct snd_pcm_substream *substream)
{
@@ -303,13 +317,53 @@ static int rz_ssi_clk_setup(struct rz_ssi_priv *ssi, unsigned int rate,
	return 0;
}

static void rz_ssi_set_idle(struct rz_ssi_priv *ssi)
{
	int timeout;

	/* Disable irqs */
	rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TUIEN | SSICR_TOIEN |
			     SSICR_RUIEN | SSICR_ROIEN, 0);
	rz_ssi_reg_mask_setl(ssi, SSIFCR, SSIFCR_TIE | SSIFCR_RIE, 0);

	/* Clear all error flags */
	rz_ssi_reg_mask_setl(ssi, SSISR,
			     (SSISR_TOIRQ | SSISR_TUIRQ | SSISR_ROIRQ |
			      SSISR_RUIRQ), 0);

	/* Wait for idle */
	timeout = 100;
	while (--timeout) {
		if (rz_ssi_reg_readl(ssi, SSISR) & SSISR_IIRQ)
			break;
		udelay(1);
	}

	if (!timeout)
		dev_info(ssi->dev, "timeout waiting for SSI idle\n");

	/* Hold FIFOs in reset */
	rz_ssi_reg_mask_setl(ssi, SSIFCR, 0,
			     SSIFCR_TFRST | SSIFCR_RFRST);
}

static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
{
	bool is_play = rz_ssi_stream_is_play(ssi, strm->substream);
	bool is_full_duplex;
	u32 ssicr, ssifcr;

	is_full_duplex = rz_ssi_is_stream_running(&ssi->playback) ||
		rz_ssi_is_stream_running(&ssi->capture);
	ssicr = rz_ssi_reg_readl(ssi, SSICR);
	ssifcr = rz_ssi_reg_readl(ssi, SSIFCR) & ~0xF;
	ssifcr = rz_ssi_reg_readl(ssi, SSIFCR);
	if (!is_full_duplex) {
		ssifcr &= ~0xF;
	} else {
		rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TEN | SSICR_REN, 0);
		rz_ssi_set_idle(ssi);
		ssifcr &= ~SSIFCR_FIFO_RST;
	}

	/* FIFO interrupt thresholds */
	if (rz_ssi_is_dma_enabled(ssi))
@@ -322,10 +376,14 @@ static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
	/* enable IRQ */
	if (is_play) {
		ssicr |= SSICR_TUIEN | SSICR_TOIEN;
		ssifcr |= SSIFCR_TIE | SSIFCR_RFRST;
		ssifcr |= SSIFCR_TIE;
		if (!is_full_duplex)
			ssifcr |= SSIFCR_RFRST;
	} else {
		ssicr |= SSICR_RUIEN | SSICR_ROIEN;
		ssifcr |= SSIFCR_RIE | SSIFCR_TFRST;
		ssifcr |= SSIFCR_RIE;
		if (!is_full_duplex)
			ssifcr |= SSIFCR_TFRST;
	}

	rz_ssi_reg_writel(ssi, SSICR, ssicr);
@@ -337,7 +395,11 @@ static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
			      SSISR_RUIRQ), 0);

	strm->running = 1;
	if (is_full_duplex)
		ssicr |= SSICR_TEN | SSICR_REN;
	else
		ssicr |= is_play ? SSICR_TEN : SSICR_REN;

	rz_ssi_reg_writel(ssi, SSICR, ssicr);

	return 0;
@@ -345,10 +407,12 @@ static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)

static int rz_ssi_stop(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
{
	int timeout;

	strm->running = 0;

	if (rz_ssi_is_stream_running(&ssi->playback) ||
	    rz_ssi_is_stream_running(&ssi->capture))
		return 0;

	/* Disable TX/RX */
	rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TEN | SSICR_REN, 0);

@@ -356,30 +420,7 @@ static int rz_ssi_stop(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
	if (rz_ssi_is_dma_enabled(ssi))
		dmaengine_terminate_async(strm->dma_ch);

	/* Disable irqs */
	rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TUIEN | SSICR_TOIEN |
			     SSICR_RUIEN | SSICR_ROIEN, 0);
	rz_ssi_reg_mask_setl(ssi, SSIFCR, SSIFCR_TIE | SSIFCR_RIE, 0);

	/* Clear all error flags */
	rz_ssi_reg_mask_setl(ssi, SSISR,
			     (SSISR_TOIRQ | SSISR_TUIRQ | SSISR_ROIRQ |
			      SSISR_RUIRQ), 0);

	/* Wait for idle */
	timeout = 100;
	while (--timeout) {
		if (rz_ssi_reg_readl(ssi, SSISR) & SSISR_IIRQ)
			break;
		udelay(1);
	}

	if (!timeout)
		dev_info(ssi->dev, "timeout waiting for SSI idle\n");

	/* Hold FIFOs in reset */
	rz_ssi_reg_mask_setl(ssi, SSIFCR, 0,
			     SSIFCR_TFRST | SSIFCR_RFRST);
	rz_ssi_set_idle(ssi);

	return 0;
}
@@ -512,66 +553,90 @@ static int rz_ssi_pio_send(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)

static irqreturn_t rz_ssi_interrupt(int irq, void *data)
{
	struct rz_ssi_stream *strm = NULL;
	struct rz_ssi_stream *strm_playback = NULL;
	struct rz_ssi_stream *strm_capture = NULL;
	struct rz_ssi_priv *ssi = data;
	u32 ssisr = rz_ssi_reg_readl(ssi, SSISR);

	if (ssi->playback.substream)
		strm = &ssi->playback;
	else if (ssi->capture.substream)
		strm = &ssi->capture;
	else
		strm_playback = &ssi->playback;
	if (ssi->capture.substream)
		strm_capture = &ssi->capture;

	if (!strm_playback && !strm_capture)
		return IRQ_HANDLED; /* Left over TX/RX interrupt */

	if (irq == ssi->irq_int) { /* error or idle */
		if (ssisr & SSISR_TUIRQ)
			strm->uerr_num++;
		if (ssisr & SSISR_TOIRQ)
			strm->oerr_num++;
		bool is_stopped = false;
		int i, count;

		if (rz_ssi_is_dma_enabled(ssi))
			count = 4;
		else
			count = 1;

		if (ssisr & (SSISR_RUIRQ | SSISR_ROIRQ | SSISR_TUIRQ | SSISR_TOIRQ))
			is_stopped = true;

		if (ssi->capture.substream && is_stopped) {
			if (ssisr & SSISR_RUIRQ)
			strm->uerr_num++;
				strm_capture->uerr_num++;
			if (ssisr & SSISR_ROIRQ)
			strm->oerr_num++;
				strm_capture->oerr_num++;

		if (ssisr & (SSISR_TUIRQ | SSISR_TOIRQ | SSISR_RUIRQ |
			     SSISR_ROIRQ)) {
			/* Error handling */
			/* You must reset (stop/restart) after each interrupt */
			rz_ssi_stop(ssi, strm);
			rz_ssi_stop(ssi, strm_capture);
		}

		if (ssi->playback.substream && is_stopped) {
			if (ssisr & SSISR_TUIRQ)
				strm_playback->uerr_num++;
			if (ssisr & SSISR_TOIRQ)
				strm_playback->oerr_num++;

			rz_ssi_stop(ssi, strm_playback);
		}

		/* Clear all flags */
			rz_ssi_reg_mask_setl(ssi, SSISR, SSISR_TOIRQ |
					     SSISR_TUIRQ | SSISR_ROIRQ |
					     SSISR_RUIRQ, 0);
		rz_ssi_reg_mask_setl(ssi, SSISR, SSISR_TOIRQ | SSISR_TUIRQ |
				     SSISR_ROIRQ | SSISR_RUIRQ, 0);

		/* Add/remove more data */
			strm->transfer(ssi, strm);
		if (ssi->capture.substream && is_stopped) {
			for (i = 0; i < count; i++)
				strm_capture->transfer(ssi, strm_capture);
		}

			/* Resume */
			rz_ssi_start(ssi, strm);
		if (ssi->playback.substream && is_stopped) {
			for (i = 0; i < count; i++)
				strm_playback->transfer(ssi, strm_playback);
		}

		/* Resume */
		if (ssi->playback.substream && is_stopped)
			rz_ssi_start(ssi, &ssi->playback);
		if (ssi->capture.substream && is_stopped)
			rz_ssi_start(ssi, &ssi->capture);
	}

	if (!strm->running)
	if (!rz_ssi_is_stream_running(&ssi->playback) &&
	    !rz_ssi_is_stream_running(&ssi->capture))
		return IRQ_HANDLED;

	/* tx data empty */
	if (irq == ssi->irq_tx)
		strm->transfer(ssi, &ssi->playback);
	if (irq == ssi->irq_tx && rz_ssi_is_stream_running(&ssi->playback))
		strm_playback->transfer(ssi, &ssi->playback);

	/* rx data full */
	if (irq == ssi->irq_rx) {
		strm->transfer(ssi, &ssi->capture);
	if (irq == ssi->irq_rx && rz_ssi_is_stream_running(&ssi->capture)) {
		strm_capture->transfer(ssi, &ssi->capture);
		rz_ssi_reg_mask_setl(ssi, SSIFSR, SSIFSR_RDF, 0);
	}

	if (irq == ssi->irq_rt) {
		struct snd_pcm_substream *substream = strm->substream;

		if (rz_ssi_stream_is_play(ssi, substream)) {
			strm->transfer(ssi, &ssi->playback);
		if (ssi->playback.substream) {
			strm_playback->transfer(ssi, &ssi->playback);
		} else {
			strm->transfer(ssi, &ssi->capture);
			strm_capture->transfer(ssi, &ssi->capture);
			rz_ssi_reg_mask_setl(ssi, SSIFSR, SSIFSR_RDF, 0);
		}
	}
@@ -731,9 +796,12 @@ static int rz_ssi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		/* Soft Reset */
		if (!rz_ssi_is_stream_running(&ssi->playback) &&
		    !rz_ssi_is_stream_running(&ssi->capture)) {
			rz_ssi_reg_mask_setl(ssi, SSIFCR, 0, SSIFCR_SSIRST);
			rz_ssi_reg_mask_setl(ssi, SSIFCR, SSIFCR_SSIRST, 0);
			udelay(5);
		}

		rz_ssi_stream_init(strm, substream);

@@ -824,14 +892,41 @@ static int rz_ssi_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
	return 0;
}

static bool rz_ssi_is_valid_hw_params(struct rz_ssi_priv *ssi, unsigned int rate,
				      unsigned int channels,
				      unsigned int sample_width,
				      unsigned int sample_bits)
{
	if (ssi->hw_params_cache.rate != rate ||
	    ssi->hw_params_cache.channels != channels ||
	    ssi->hw_params_cache.sample_width != sample_width ||
	    ssi->hw_params_cache.sample_bits != sample_bits)
		return false;

	return true;
}

static void rz_ssi_cache_hw_params(struct rz_ssi_priv *ssi, unsigned int rate,
				   unsigned int channels,
				   unsigned int sample_width,
				   unsigned int sample_bits)
{
	ssi->hw_params_cache.rate = rate;
	ssi->hw_params_cache.channels = channels;
	ssi->hw_params_cache.sample_width = sample_width;
	ssi->hw_params_cache.sample_bits = sample_bits;
}

static int rz_ssi_dai_hw_params(struct snd_pcm_substream *substream,
				struct snd_pcm_hw_params *params,
				struct snd_soc_dai *dai)
{
	struct rz_ssi_priv *ssi = snd_soc_dai_get_drvdata(dai);
	struct rz_ssi_stream *strm = rz_ssi_stream_get(ssi, substream);
	unsigned int sample_bits = hw_param_interval(params,
					SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
	unsigned int channels = params_channels(params);
	unsigned int rate = params_rate(params);

	if (sample_bits != 16) {
		dev_err(ssi->dev, "Unsupported sample width: %d\n",
@@ -845,8 +940,20 @@ static int rz_ssi_dai_hw_params(struct snd_pcm_substream *substream,
		return -EINVAL;
	}

	return rz_ssi_clk_setup(ssi, params_rate(params),
				params_channels(params));
	if (rz_ssi_is_stream_running(&ssi->playback) ||
	    rz_ssi_is_stream_running(&ssi->capture)) {
		if (rz_ssi_is_valid_hw_params(ssi, rate, channels,
					      strm->sample_width, sample_bits))
			return 0;

		dev_err(ssi->dev, "Full duplex needs same HW params\n");
		return -EINVAL;
	}

	rz_ssi_cache_hw_params(ssi, rate, channels, strm->sample_width,
			       sample_bits);

	return rz_ssi_clk_setup(ssi, rate, channels);
}

static const struct snd_soc_dai_ops rz_ssi_dai_ops = {