All of lore.kernel.org
 help / color / mirror / Atom feed
From: Linus Walleij <linusw@kernel.org>
To: Liam Girdwood <lgirdwood@gmail.com>,
	Mark Brown <broonie@kernel.org>,
	 Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.com>,
	 Philipp Zabel <p.zabel@pengutronix.de>
Cc: linux-sound@vger.kernel.org, Linus Walleij <linusw@kernel.org>
Subject: [PATCH v2 1/9] ASoC: ux500: Fix MSP stream lifecycle handling
Date: Wed, 02 Sep 2026 09:55:51 +0200	[thread overview]
Message-ID: <20260902-ux500-msp-fixes-v2-1-4b60b002d55a@kernel.org> (raw)
In-Reply-To: <20260902-ux500-msp-fixes-v2-0-4b60b002d55a@kernel.org>

The trigger stop path drops the direction busy flag even though ALSA
still owns the stream until shutdown. A later trigger cannot reliably
restart it, shutdown may leave the block configured, and a second
stream may overwrite shared duplex configuration.

Keep configured and running directions as separate state. Program
shared settings only for the first direction, require a compatible
configuration for the other half of a duplex stream, and enable the
frame generator only while a provider stream is running. Also fix the
RX-disable direction test and preserve the other direction multichannel
setup.

Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 sound/soc/ux500/ux500_msp_dai.c |   8 +-
 sound/soc/ux500/ux500_msp_i2s.c | 183 ++++++++++++++++++++++++++++++----------
 sound/soc/ux500/ux500_msp_i2s.h |   4 +
 3 files changed, 149 insertions(+), 46 deletions(-)

diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c
index 499e826d7120..994422e72512 100644
--- a/sound/soc/ux500/ux500_msp_dai.c
+++ b/sound/soc/ux500/ux500_msp_dai.c
@@ -34,8 +34,10 @@ static int setup_pcm_multichan(struct snd_soc_dai *dai,
 	if (drvdata->slots > 1) {
 		msp_config->multichannel_configured = 1;
 
-		multi->tx_multichannel_enable = true;
-		multi->rx_multichannel_enable = true;
+		multi->tx_multichannel_enable =
+			msp_config->direction & MSP_DIR_TX;
+		multi->rx_multichannel_enable =
+			msp_config->direction & MSP_DIR_RX;
 		multi->rx_comparison_enable_mode = MSP_COMPARISON_DISABLED;
 
 		multi->tx_channel_0_enable = drvdata->tx_mask;
@@ -192,6 +194,7 @@ static int setup_clocking(struct snd_soc_dai *dai,
 	case SND_SOC_DAIFMT_BC_FC:
 		dev_dbg(dai->dev, "%s: Codec is master.\n", __func__);
 
+		msp_config->clock_provider = false;
 		msp_config->iodelay = 0x20;
 		msp_config->rx_fsync_sel = 0;
 		msp_config->tx_fsync_sel = 1 << TFSSEL_SHIFT;
@@ -204,6 +207,7 @@ static int setup_clocking(struct snd_soc_dai *dai,
 	case SND_SOC_DAIFMT_BP_FP:
 		dev_dbg(dai->dev, "%s: Codec is slave.\n", __func__);
 
+		msp_config->clock_provider = true;
 		msp_config->tx_clk_sel = TX_CLK_SEL_SRG;
 		msp_config->tx_fsync_sel = TX_SYNC_SRG_PROG;
 		msp_config->rx_clk_sel = RX_CLK_SEL_SRG;
diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c
index fbfeefa418ca..ec6f0874294a 100644
--- a/sound/soc/ux500/ux500_msp_i2s.c
+++ b/sound/soc/ux500/ux500_msp_i2s.c
@@ -344,20 +344,27 @@ static int configure_multichannel(struct ux500_msp *msp,
 	return 0;
 }
 
-static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config)
+static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config,
+		      bool first)
 {
-	int status = 0;
-	u32 reg_val_DMACR, reg_val_GCR;
+	int status;
+	u32 reg_val_DMACR;
 
 	/* Configure msp with protocol dependent settings */
-	configure_protocol(msp, config);
-	setup_bitclk(msp, config);
+	status = configure_protocol(msp, config);
+	if (status)
+		return status;
+
+	if (first && config->clock_provider) {
+		status = setup_bitclk(msp, config);
+		if (status)
+			return status;
+	}
+
 	if (config->multichannel_configured == 1) {
 		status = configure_multichannel(msp, config);
 		if (status)
-			dev_warn(msp->dev,
-				"%s: WARN: configure_multichannel failed (%d)!\n",
-				__func__, status);
+			return status;
 	}
 
 	reg_val_DMACR = readl(msp->registers + MSP_DMACR);
@@ -369,11 +376,7 @@ static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config)
 
 	writel(config->iodelay, msp->registers + MSP_IODLY);
 
-	/* Enable frame generation logic */
-	reg_val_GCR = readl(msp->registers + MSP_GCR);
-	writel(reg_val_GCR | FRAME_GEN_ENABLE, msp->registers + MSP_GCR);
-
-	return status;
+	return 0;
 }
 
 static void flush_fifo_rx(struct ux500_msp *msp)
@@ -411,12 +414,36 @@ static void flush_fifo_tx(struct ux500_msp *msp)
 	writel(reg_val_GCR, msp->registers + MSP_GCR);
 }
 
+static bool ux500_msp_config_compatible(struct ux500_msp *msp,
+					struct ux500_msp_config *config)
+{
+	struct ux500_msp_config *active = &msp->config;
+
+	return active->f_inputclk == config->f_inputclk &&
+	       active->tx_clk_sel == config->tx_clk_sel &&
+	       active->rx_clk_sel == config->rx_clk_sel &&
+	       active->srg_clk_sel == config->srg_clk_sel &&
+	       active->rx_fsync_pol == config->rx_fsync_pol &&
+	       active->tx_fsync_pol == config->tx_fsync_pol &&
+	       active->rx_fsync_sel == config->rx_fsync_sel &&
+	       active->tx_fsync_sel == config->tx_fsync_sel &&
+	       active->default_protdesc == config->default_protdesc &&
+	       active->protocol == config->protocol &&
+	       active->frame_freq == config->frame_freq &&
+	       active->data_size == config->data_size &&
+	       active->def_elem_len == config->def_elem_len &&
+	       active->clock_provider == config->clock_provider &&
+	       !memcmp(&active->protdesc, &config->protdesc,
+		       sizeof(active->protdesc));
+}
+
 int ux500_msp_i2s_open(struct ux500_msp *msp,
 		struct ux500_msp_config *config)
 {
 	u32 old_reg, new_reg, mask;
 	int res;
 	unsigned int tx_sel, rx_sel, tx_busy, rx_busy;
+	bool first;
 
 	if (in_interrupt()) {
 		dev_err(msp->dev,
@@ -444,40 +471,66 @@ int ux500_msp_i2s_open(struct ux500_msp *msp,
 		return -EBUSY;
 	}
 
-	msp->dir_busy |= (tx_sel ? MSP_DIR_TX : 0) | (rx_sel ? MSP_DIR_RX : 0);
-
-	/* First do the global config register */
-	mask = RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FSYNC_MASK |
-	    TX_FSYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK |
-	    RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK | SRG_CLK_SEL_MASK |
-	    LOOPBACK_MASK | TX_EXTRA_DELAY_MASK;
-
-	new_reg = (config->tx_clk_sel | config->rx_clk_sel |
-		config->rx_fsync_pol | config->tx_fsync_pol |
-		config->rx_fsync_sel | config->tx_fsync_sel |
-		config->rx_fifo_config | config->tx_fifo_config |
-		config->srg_clk_sel | config->loopback_enable |
-		config->tx_data_enable);
+	first = !msp->dir_busy;
+	if (!first && !ux500_msp_config_compatible(msp, config)) {
+		dev_err(msp->dev, "%s: Incompatible duplex configuration\n",
+			__func__);
+		return -EBUSY;
+	}
 
-	old_reg = readl(msp->registers + MSP_GCR);
-	old_reg &= ~mask;
-	new_reg |= old_reg;
-	writel(new_reg, msp->registers + MSP_GCR);
+	if (first) {
+		/* First do the global config register */
+		mask = RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FSYNC_MASK |
+		       TX_FSYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK |
+		       RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK |
+		       SRG_CLK_SEL_MASK | LOOPBACK_MASK | TX_EXTRA_DELAY_MASK;
+
+		new_reg = config->tx_clk_sel | config->rx_clk_sel |
+			  config->rx_fsync_pol | config->tx_fsync_pol |
+			  config->rx_fsync_sel | config->tx_fsync_sel |
+			  config->rx_fifo_config | config->tx_fifo_config |
+			  config->srg_clk_sel | config->loopback_enable |
+			  config->tx_data_enable;
+
+		old_reg = readl(msp->registers + MSP_GCR);
+		old_reg &= ~mask;
+		new_reg |= old_reg;
+		writel(new_reg, msp->registers + MSP_GCR);
+	}
 
-	res = enable_msp(msp, config);
+	res = enable_msp(msp, config, first);
 	if (res < 0) {
 		dev_err(msp->dev, "%s: ERROR: enable_msp failed (%d)!\n",
 			__func__, res);
-		return -EBUSY;
+		if (tx_sel)
+			writel(0, msp->registers + MSP_TCF);
+		if (rx_sel)
+			writel(0, msp->registers + MSP_RCF);
+		if (first) {
+			writel(0, msp->registers + MSP_GCR);
+			writel(0, msp->registers + MSP_DMACR);
+			writel(0, msp->registers + MSP_SRG);
+			writel(0, msp->registers + MSP_MCR);
+		}
+		return res;
+	}
+
+	msp->dir_busy |= config->direction;
+	if (first) {
+		msp->config = *config;
+		msp->clock_provider = config->clock_provider;
 	}
 	if (config->loopback_enable & 0x80)
 		msp->loopback_enable = 1;
 
 	/* Flush FIFOs */
-	flush_fifo_tx(msp);
-	flush_fifo_rx(msp);
+	if (tx_sel)
+		flush_fifo_tx(msp);
+	if (rx_sel)
+		flush_fifo_rx(msp);
 
-	msp->msp_state = MSP_STATE_CONFIGURED;
+	if (!msp->dir_running)
+		msp->msp_state = MSP_STATE_CONFIGURED;
 	return 0;
 }
 
@@ -494,7 +547,6 @@ static void disable_msp_rx(struct ux500_msp *msp)
 			~(RX_SERVICE_INT | RX_OVERRUN_ERROR_INT),
 			msp->registers + MSP_IMSC);
 
-	msp->dir_busy &= ~MSP_DIR_RX;
 }
 
 static void disable_msp_tx(struct ux500_msp *msp)
@@ -510,7 +562,6 @@ static void disable_msp_tx(struct ux500_msp *msp)
 			~(TX_SERVICE_INT | TX_UNDERRUN_ERR_INT),
 			msp->registers + MSP_IMSC);
 
-	msp->dir_busy &= ~MSP_DIR_TX;
 }
 
 static int disable_msp(struct ux500_msp *msp, unsigned int dir)
@@ -520,7 +571,7 @@ static int disable_msp(struct ux500_msp *msp, unsigned int dir)
 
 	reg_val_GCR = readl(msp->registers + MSP_GCR);
 	disable_tx = dir & MSP_DIR_TX;
-	disable_rx = dir & MSP_DIR_TX;
+	disable_rx = dir & MSP_DIR_RX;
 	if (disable_tx && disable_rx) {
 		reg_val_GCR = readl(msp->registers + MSP_GCR);
 		writel(reg_val_GCR | LOOPBACK_MASK,
@@ -553,7 +604,15 @@ static int disable_msp(struct ux500_msp *msp, unsigned int dir)
 
 int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd, int direction)
 {
-	u32 reg_val_GCR, enable_bit;
+	u32 reg_val_DMACR, reg_val_GCR, dma_enable_bit, enable_bit;
+	unsigned int dir;
+
+	if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+		dir = MSP_DIR_TX;
+	else if (direction == SNDRV_PCM_STREAM_CAPTURE)
+		dir = MSP_DIR_RX;
+	else
+		return -EINVAL;
 
 	if (msp->msp_state == MSP_STATE_IDLE) {
 		dev_err(msp->dev, "%s: ERROR: MSP is not configured!\n",
@@ -565,21 +624,44 @@ int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd, int direction)
 	case SNDRV_PCM_TRIGGER_START:
 	case SNDRV_PCM_TRIGGER_RESUME:
 	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
-		if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+		if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
 			enable_bit = TX_ENABLE;
-		else
+			dma_enable_bit = TX_DMA_ENABLE;
+		} else {
 			enable_bit = RX_ENABLE;
+			dma_enable_bit = RX_DMA_ENABLE;
+		}
+		if (!(msp->dir_busy & dir))
+			return -EINVAL;
+		reg_val_DMACR = readl(msp->registers + MSP_DMACR);
+		writel(reg_val_DMACR | dma_enable_bit,
+		       msp->registers + MSP_DMACR);
 		reg_val_GCR = readl(msp->registers + MSP_GCR);
+		if (msp->clock_provider)
+			enable_bit |= FRAME_GEN_ENABLE;
 		writel(reg_val_GCR | enable_bit, msp->registers + MSP_GCR);
+		msp->dir_running |= dir;
+		msp->msp_state = MSP_STATE_RUNNING;
 		break;
 
 	case SNDRV_PCM_TRIGGER_STOP:
 	case SNDRV_PCM_TRIGGER_SUSPEND:
 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
-		if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+		if (!(msp->dir_busy & dir))
+			return -EINVAL;
+		if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
 			disable_msp_tx(msp);
-		else
+			msp->dir_running &= ~MSP_DIR_TX;
+		} else {
 			disable_msp_rx(msp);
+			msp->dir_running &= ~MSP_DIR_RX;
+		}
+		if (!msp->dir_running) {
+			reg_val_GCR = readl(msp->registers + MSP_GCR);
+			writel(reg_val_GCR & ~FRAME_GEN_ENABLE,
+			       msp->registers + MSP_GCR);
+			msp->msp_state = MSP_STATE_CONFIGURED;
+		}
 		break;
 	default:
 		return -EINVAL;
@@ -594,7 +676,18 @@ int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir)
 
 	dev_dbg(msp->dev, "%s: Enter (dir = 0x%01x).\n", __func__, dir);
 
+	if (!dir || dir & ~(MSP_DIR_TX | MSP_DIR_RX) ||
+	    (msp->dir_busy & dir) != dir)
+		return -EINVAL;
+
 	status = disable_msp(msp, dir);
+	msp->dir_busy &= ~dir;
+	msp->dir_running &= ~dir;
+	if (msp->dir_busy && !msp->dir_running) {
+		writel(readl(msp->registers + MSP_GCR) & ~FRAME_GEN_ENABLE,
+		       msp->registers + MSP_GCR);
+		msp->msp_state = MSP_STATE_CONFIGURED;
+	}
 	if (msp->dir_busy == 0) {
 		/* disable sample rate and frame generators */
 		msp->msp_state = MSP_STATE_IDLE;
@@ -618,6 +711,8 @@ int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir)
 		writel(0, msp->registers + MSP_RCE1);
 		writel(0, msp->registers + MSP_RCE2);
 		writel(0, msp->registers + MSP_RCE3);
+		memset(&msp->config, 0, sizeof(msp->config));
+		msp->clock_provider = false;
 	}
 
 	return status;
diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h
index 69d4ebc409fc..d75a0974369a 100644
--- a/sound/soc/ux500/ux500_msp_i2s.h
+++ b/sound/soc/ux500/ux500_msp_i2s.h
@@ -460,6 +460,7 @@ struct ux500_msp_config {
 	enum msp_data_size data_size;
 	unsigned int def_elem_len;
 	unsigned int iodelay;
+	bool clock_provider;
 };
 
 struct ux500_msp {
@@ -470,8 +471,11 @@ struct ux500_msp {
 	enum msp_state msp_state;
 	int def_elem_len;
 	unsigned int dir_busy;
+	unsigned int dir_running;
 	int loopback_enable;
 	unsigned int f_bitclk;
+	bool clock_provider;
+	struct ux500_msp_config config;
 };
 
 int ux500_msp_i2s_init_msp(struct platform_device *pdev,

-- 
2.55.0


  reply	other threads:[~2026-09-02  7:55 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-02  7:55 [PATCH v2 0/9] ASoC: ux500: Fix MSP lifecycle, clocking and resources Linus Walleij
2026-09-02  7:55 ` Linus Walleij [this message]
2026-09-02  7:55 ` [PATCH v2 2/9] ASoC: ux500: Propagate MSP setup errors Linus Walleij
2026-09-02  7:55 ` [PATCH v2 3/9] ASoC: ux500: Correct MSP frame and bit clock setup Linus Walleij
2026-09-02  7:55 ` [PATCH v2 4/9] ASoC: ux500: Validate MSP DAI configuration Linus Walleij
2026-09-02  7:55 ` [PATCH v2 5/9] ASoC: ux500: Deassert the MSP reset during probe Linus Walleij
2026-09-02  7:55 ` [PATCH v2 6/9] ASoC: ux500: Request the MSP MMIO resource Linus Walleij
2026-09-02  7:55 ` [PATCH v2 7/9] ASoC: ux500: Remove obsolete PRCMU QoS calls Linus Walleij
2026-09-02  7:55 ` [PATCH v2 8/9] ASoC: ux500: Allow repeated MSP prepare calls Linus Walleij
2026-09-02  7:55 ` [PATCH v2 9/9] ASoC: ux500: Program the MSP FIFO watermarks Linus Walleij
2026-09-02 11:58 ` [PATCH v2 0/9] ASoC: ux500: Fix MSP lifecycle, clocking and resources Mark Brown

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260902-ux500-msp-fixes-v2-1-4b60b002d55a@kernel.org \
    --to=linusw@kernel.org \
    --cc=broonie@kernel.org \
    --cc=lgirdwood@gmail.com \
    --cc=linux-sound@vger.kernel.org \
    --cc=p.zabel@pengutronix.de \
    --cc=perex@perex.cz \
    --cc=tiwai@suse.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.