Unverified Commit 5998f0d0 authored by Mark Brown's avatar Mark Brown
Browse files

ASoC: fsl: fsl_qmc_audio: Reduce amount of

Merge series from Christophe Leroy <christophe.leroy@csgroup.eu>:

This is a RESEND of v3 sent one month ago, see:
https://lore.kernel.org/all/cover.1754993232.git.christophe.leroy@csgroup.eu/

This series reduces significantly the amount of interrupts on
fsl_qmc_audio device.

Patches 1 and 2 are preparatory patches.
Patch 3 is the main change
Patch 4 is a cleanup which is enabled by previous patch
parents 309e94a6 2c618f36
Loading
Loading
Loading
Loading
+33 −11
Original line number Diff line number Diff line
@@ -461,10 +461,17 @@ int qmc_chan_write_submit(struct qmc_chan *chan, dma_addr_t addr, size_t length,

	ctrl = qmc_read16(&bd->cbd_sc);
	if (ctrl & (QMC_BD_TX_R | QMC_BD_TX_UB)) {
		if (!(ctrl & (QMC_BD_TX_R | QMC_BD_TX_I)) && bd == chan->txbd_done) {
			if (ctrl & QMC_BD_TX_W)
				chan->txbd_done = chan->txbds;
			else
				chan->txbd_done++;
		} else {
			/* We are full ... */
			ret = -EBUSY;
			goto end;
		}
	}

	qmc_write16(&bd->cbd_datlen, length);
	qmc_write32(&bd->cbd_bufaddr, addr);
@@ -475,6 +482,10 @@ int qmc_chan_write_submit(struct qmc_chan *chan, dma_addr_t addr, size_t length,

	/* Activate the descriptor */
	ctrl |= (QMC_BD_TX_R | QMC_BD_TX_UB);
	if (complete)
		ctrl |= QMC_BD_TX_I;
	else
		ctrl &= ~QMC_BD_TX_I;
	wmb(); /* Be sure to flush the descriptor before control update */
	qmc_write16(&bd->cbd_sc, ctrl);

@@ -569,10 +580,17 @@ int qmc_chan_read_submit(struct qmc_chan *chan, dma_addr_t addr, size_t length,

	ctrl = qmc_read16(&bd->cbd_sc);
	if (ctrl & (QMC_BD_RX_E | QMC_BD_RX_UB)) {
		if (!(ctrl & (QMC_BD_RX_E | QMC_BD_RX_I)) && bd == chan->rxbd_done) {
			if (ctrl & QMC_BD_RX_W)
				chan->rxbd_done = chan->rxbds;
			else
				chan->rxbd_done++;
		} else {
			/* We are full ... */
			ret = -EBUSY;
			goto end;
		}
	}

	qmc_write16(&bd->cbd_datlen, 0); /* data length is updated by the QMC */
	qmc_write32(&bd->cbd_bufaddr, addr);
@@ -587,6 +605,10 @@ int qmc_chan_read_submit(struct qmc_chan *chan, dma_addr_t addr, size_t length,

	/* Activate the descriptor */
	ctrl |= (QMC_BD_RX_E | QMC_BD_RX_UB);
	if (complete)
		ctrl |= QMC_BD_RX_I;
	else
		ctrl &= ~QMC_BD_RX_I;
	wmb(); /* Be sure to flush data before descriptor activation */
	qmc_write16(&bd->cbd_sc, ctrl);

@@ -1482,19 +1504,19 @@ static int qmc_setup_chan(struct qmc *qmc, struct qmc_chan *chan)

	/* Init Rx BDs and set Wrap bit on last descriptor */
	BUILD_BUG_ON(QMC_NB_RXBDS == 0);
	val = QMC_BD_RX_I;
	for (i = 0; i < QMC_NB_RXBDS; i++) {
		bd = chan->rxbds + i;
		qmc_write16(&bd->cbd_sc, val);
		qmc_write16(&bd->cbd_sc, 0);
	}
	bd = chan->rxbds + QMC_NB_RXBDS - 1;
	qmc_write16(&bd->cbd_sc, val | QMC_BD_RX_W);
	qmc_write16(&bd->cbd_sc, QMC_BD_RX_W);

	/* Init Tx BDs and set Wrap bit on last descriptor */
	BUILD_BUG_ON(QMC_NB_TXBDS == 0);
	val = QMC_BD_TX_I;
	if (chan->mode == QMC_HDLC)
		val |= QMC_BD_TX_L | QMC_BD_TX_TC;
		val = QMC_BD_TX_L | QMC_BD_TX_TC;
	else
		val = 0;
	for (i = 0; i < QMC_NB_TXBDS; i++) {
		bd = chan->txbds + i;
		qmc_write16(&bd->cbd_sc, val);
+54 −71
Original line number Diff line number Diff line
@@ -17,12 +17,6 @@
#include <sound/pcm_params.h>
#include <sound/soc.h>

struct qmc_dai_chan {
	struct qmc_dai_prtd *prtd_tx;
	struct qmc_dai_prtd *prtd_rx;
	struct qmc_chan *qmc_chan;
};

struct qmc_dai {
	char *name;
	int id;
@@ -33,7 +27,7 @@ struct qmc_dai {
	unsigned int nb_chans_avail;
	unsigned int nb_chans_used_tx;
	unsigned int nb_chans_used_rx;
	struct qmc_dai_chan *chans;
	struct qmc_chan **qmc_chans;
};

struct qmc_audio {
@@ -57,7 +51,6 @@ struct qmc_dai_prtd {
	size_t ch_dma_offset;

	unsigned int channels;
	DECLARE_BITMAP(chans_pending, 64);
	struct snd_pcm_substream *substream;
};

@@ -126,17 +119,14 @@ static int qmc_audio_pcm_write_submit(struct qmc_dai_prtd *prtd)
	int ret;

	for (i = 0; i < prtd->channels; i++) {
		bitmap_set(prtd->chans_pending, i, 1);

		ret = qmc_chan_write_submit(prtd->qmc_dai->chans[i].qmc_chan,
		ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chans[i],
					    prtd->ch_dma_addr_current + i * prtd->ch_dma_offset,
					    prtd->ch_dma_size,
					    qmc_audio_pcm_write_complete,
					    &prtd->qmc_dai->chans[i]);
					    i == prtd->channels - 1 ? qmc_audio_pcm_write_complete :
								      NULL, prtd);
		if (ret) {
			dev_err(prtd->qmc_dai->dev, "write_submit %u failed %d\n",
				i, ret);
			bitmap_clear(prtd->chans_pending, i, 1);
			return ret;
		}
	}
@@ -146,20 +136,7 @@ static int qmc_audio_pcm_write_submit(struct qmc_dai_prtd *prtd)

static void qmc_audio_pcm_write_complete(void *context)
{
	struct qmc_dai_chan *chan = context;
	struct qmc_dai_prtd *prtd;

	prtd = chan->prtd_tx;

	/* Mark the current channel as completed */
	bitmap_clear(prtd->chans_pending, chan - prtd->qmc_dai->chans, 1);

	/*
	 * All QMC channels involved must have completed their transfer before
	 * submitting a new one.
	 */
	if (!bitmap_empty(prtd->chans_pending, 64))
		return;
	struct qmc_dai_prtd *prtd = context;

	prtd->buffer_ended += prtd->period_size;
	if (prtd->buffer_ended >= prtd->buffer_size)
@@ -182,17 +159,14 @@ static int qmc_audio_pcm_read_submit(struct qmc_dai_prtd *prtd)
	int ret;

	for (i = 0; i < prtd->channels; i++) {
		bitmap_set(prtd->chans_pending, i, 1);

		ret = qmc_chan_read_submit(prtd->qmc_dai->chans[i].qmc_chan,
		ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chans[i],
					   prtd->ch_dma_addr_current + i * prtd->ch_dma_offset,
					   prtd->ch_dma_size,
					   qmc_audio_pcm_read_complete,
					   &prtd->qmc_dai->chans[i]);
					   i == prtd->channels - 1 ? qmc_audio_pcm_read_complete :
								     NULL, prtd);
		if (ret) {
			dev_err(prtd->qmc_dai->dev, "read_submit %u failed %d\n",
				i, ret);
			bitmap_clear(prtd->chans_pending, i, 1);
			return ret;
		}
	}
@@ -202,26 +176,13 @@ static int qmc_audio_pcm_read_submit(struct qmc_dai_prtd *prtd)

static void qmc_audio_pcm_read_complete(void *context, size_t length, unsigned int flags)
{
	struct qmc_dai_chan *chan = context;
	struct qmc_dai_prtd *prtd;

	prtd = chan->prtd_rx;

	/* Mark the current channel as completed */
	bitmap_clear(prtd->chans_pending, chan - prtd->qmc_dai->chans, 1);
	struct qmc_dai_prtd *prtd = context;

	if (length != prtd->ch_dma_size) {
		dev_err(prtd->qmc_dai->dev, "read complete length = %zu, exp %zu\n",
			length, prtd->ch_dma_size);
	}

	/*
	 * All QMC channels involved must have completed their transfer before
	 * submitting a new one.
	 */
	if (!bitmap_empty(prtd->chans_pending, 64))
		return;

	prtd->buffer_ended += prtd->period_size;
	if (prtd->buffer_ended >= prtd->buffer_size)
		prtd->buffer_ended = 0;
@@ -239,7 +200,6 @@ static int qmc_audio_pcm_trigger(struct snd_soc_component *component,
				 struct snd_pcm_substream *substream, int cmd)
{
	struct qmc_dai_prtd *prtd = substream->runtime->private_data;
	unsigned int i;
	int ret;

	if (!prtd->qmc_dai) {
@@ -249,14 +209,10 @@ static int qmc_audio_pcm_trigger(struct snd_soc_component *component,

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		bitmap_zero(prtd->chans_pending, 64);
		prtd->buffer_ended = 0;
		prtd->ch_dma_addr_current = prtd->ch_dma_addr_start;

		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
			for (i = 0; i < prtd->channels; i++)
				prtd->qmc_dai->chans[i].prtd_tx = prtd;

			/* Submit first chunk ... */
			ret = qmc_audio_pcm_write_submit(prtd);
			if (ret)
@@ -272,9 +228,6 @@ static int qmc_audio_pcm_trigger(struct snd_soc_component *component,
			if (ret)
				return ret;
		} else {
			for (i = 0; i < prtd->channels; i++)
				prtd->qmc_dai->chans[i].prtd_rx = prtd;

			/* Submit first chunk ... */
			ret = qmc_audio_pcm_read_submit(prtd);
			if (ret)
@@ -644,9 +597,9 @@ static int qmc_dai_hw_params(struct snd_pcm_substream *substream,
		chan_param.mode = QMC_TRANSPARENT;
		chan_param.transp.max_rx_buf_size = params_period_bytes(params) / nb_chans_used;
		for (i = 0; i < nb_chans_used; i++) {
			ret = qmc_chan_set_param(qmc_dai->chans[i].qmc_chan, &chan_param);
			ret = qmc_chan_set_param(qmc_dai->qmc_chans[i], &chan_param);
			if (ret) {
				dev_err(dai->dev, "chans[%u], set param failed %d\n",
				dev_err(dai->dev, "qmc_chans[%u], set param failed %d\n",
					i, ret);
				return ret;
			}
@@ -688,7 +641,7 @@ static int qmc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
	case SNDRV_PCM_TRIGGER_RESUME:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		for (i = 0; i < nb_chans_used; i++) {
			ret = qmc_chan_start(qmc_dai->chans[i].qmc_chan, direction);
			ret = qmc_chan_start(qmc_dai->qmc_chans[i], direction);
			if (ret)
				goto err_stop;
		}
@@ -697,13 +650,13 @@ static int qmc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
	case SNDRV_PCM_TRIGGER_STOP:
		/* Stop and reset all QMC channels and return the first error encountered */
		for (i = 0; i < nb_chans_used; i++) {
			ret_tmp = qmc_chan_stop(qmc_dai->chans[i].qmc_chan, direction);
			ret_tmp = qmc_chan_stop(qmc_dai->qmc_chans[i], direction);
			if (!ret)
				ret = ret_tmp;
			if (ret_tmp)
				continue;

			ret_tmp = qmc_chan_reset(qmc_dai->chans[i].qmc_chan, direction);
			ret_tmp = qmc_chan_reset(qmc_dai->qmc_chans[i], direction);
			if (!ret)
				ret = ret_tmp;
		}
@@ -715,7 +668,7 @@ static int qmc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		/* Stop all QMC channels and return the first error encountered */
		for (i = 0; i < nb_chans_used; i++) {
			ret_tmp = qmc_chan_stop(qmc_dai->chans[i].qmc_chan, direction);
			ret_tmp = qmc_chan_stop(qmc_dai->qmc_chans[i], direction);
			if (!ret)
				ret = ret_tmp;
		}
@@ -731,8 +684,8 @@ static int qmc_dai_trigger(struct snd_pcm_substream *substream, int cmd,

err_stop:
	while (i--) {
		qmc_chan_stop(qmc_dai->chans[i].qmc_chan, direction);
		qmc_chan_reset(qmc_dai->chans[i].qmc_chan, direction);
		qmc_chan_stop(qmc_dai->qmc_chans[i], direction);
		qmc_chan_reset(qmc_dai->qmc_chans[i], direction);
	}
	return ret;
}
@@ -791,12 +744,17 @@ static int qmc_audio_dai_parse(struct qmc_audio *qmc_audio, struct device_node *
			       struct qmc_dai *qmc_dai,
			       struct snd_soc_dai_driver *qmc_soc_dai_driver)
{
	struct qmc_chan_ts_info ts_info;
	struct qmc_chan_info info;
	unsigned long rx_fs_rate;
	unsigned long tx_fs_rate;
	int prev_last_rx_ts = 0;
	int prev_last_tx_ts = 0;
	unsigned int nb_tx_ts;
	unsigned int nb_rx_ts;
	unsigned int i;
	int last_rx_ts;
	int last_tx_ts;
	int count;
	u32 val;
	int ret;
@@ -823,19 +781,20 @@ static int qmc_audio_dai_parse(struct qmc_audio *qmc_audio, struct device_node *
		return dev_err_probe(qmc_audio->dev, -EINVAL,
				     "dai %d no QMC channel defined\n", qmc_dai->id);

	qmc_dai->chans = devm_kcalloc(qmc_audio->dev, count, sizeof(*qmc_dai->chans), GFP_KERNEL);
	if (!qmc_dai->chans)
	qmc_dai->qmc_chans = devm_kcalloc(qmc_audio->dev, count, sizeof(*qmc_dai->qmc_chans),
					  GFP_KERNEL);
	if (!qmc_dai->qmc_chans)
		return -ENOMEM;

	for (i = 0; i < count; i++) {
		qmc_dai->chans[i].qmc_chan = devm_qmc_chan_get_byphandles_index(qmc_audio->dev, np,
		qmc_dai->qmc_chans[i] = devm_qmc_chan_get_byphandles_index(qmc_audio->dev, np,
									   "fsl,qmc-chan", i);
		if (IS_ERR(qmc_dai->chans[i].qmc_chan)) {
			return dev_err_probe(qmc_audio->dev, PTR_ERR(qmc_dai->chans[i].qmc_chan),
		if (IS_ERR(qmc_dai->qmc_chans[i])) {
			return dev_err_probe(qmc_audio->dev, PTR_ERR(qmc_dai->qmc_chans[i]),
					     "dai %d get QMC channel %d failed\n", qmc_dai->id, i);
		}

		ret = qmc_chan_get_info(qmc_dai->chans[i].qmc_chan, &info);
		ret = qmc_chan_get_info(qmc_dai->qmc_chans[i], &info);
		if (ret) {
			dev_err(qmc_audio->dev, "dai %d get QMC %d channel info failed %d\n",
				qmc_dai->id, i, ret);
@@ -879,6 +838,30 @@ static int qmc_audio_dai_parse(struct qmc_audio *qmc_audio, struct device_node *
				return -EINVAL;
			}
		}

		ret = qmc_chan_get_ts_info(qmc_dai->qmc_chans[i], &ts_info);
		if (ret) {
			dev_err(qmc_audio->dev, "dai %d get QMC %d channel TS info failed %d\n",
				qmc_dai->id, i, ret);
			return ret;
		}

		last_rx_ts = fls64(ts_info.rx_ts_mask);
		last_tx_ts = fls64(ts_info.tx_ts_mask);

		if (prev_last_rx_ts > last_rx_ts) {
			dev_err(qmc_audio->dev, "dai %d QMC chan %d unordered channels (RX timeslot %d before %d)\n",
				qmc_dai->id, i, prev_last_rx_ts, last_rx_ts);
			return -EINVAL;
		}
		if (prev_last_tx_ts > last_tx_ts) {
			dev_err(qmc_audio->dev, "dai %d QMC chan %d unordered channels (TX timeslot %d before %d)\n",
				qmc_dai->id, i, prev_last_tx_ts, last_tx_ts);
			return -EINVAL;
		}

		prev_last_rx_ts = last_rx_ts;
		prev_last_tx_ts = last_tx_ts;
	}

	qmc_dai->nb_chans_avail = count;