mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Changhuang Liang <changhuang.liang@starfivetech.com>
To: Mark Brown <broonie@kernel.org>
Cc: Serge Semin <fancer.lancer@gmail.com>,
	linux-spi@vger.kernel.org, linux-kernel@vger.kernel.org,
	Changhuang Liang <changhuang.liang@starfivetech.com>
Subject: [PATCH v2 2/3] spi: dw: Support DMA with a single channel
Date: Wed, 30 Sep 2026 00:15:34 -0700	[thread overview]
Message-ID: <20260930071535.7947-3-changhuang.liang@starfivetech.com> (raw)
In-Reply-To: <20260930071535.7947-1-changhuang.liang@starfivetech.com>

Some platforms, such as StarFive JHB100, provide only one DMA channel
for the DW SPI controller. dw_spi_dma_init_generic() requires both the
Tx and Rx channels, so DMA is disabled entirely on such platforms.

Request both channels at probe time and decide what is usable from the
return values. If the first (Rx) channel request fails, fall back to
non-DMA transfers as before. If only the second (Tx) channel request
fails, keep the Rx channel. Each successfully requested channel is
counted in dws->dma_nr_chans. -EPROBE_DEFER on the second channel is
still propagated.

Full-duplex standard SPI transfers need both channels, so only set
ctlr->can_dma and SPI_CONTROLLER_MUST_TX when two channels are
available. A single channel can still serve half-duplex transfers,
which will be used by the enhanced SPI memory operations in a
follow-up patch.

Make dw_spi_dma_caps_init() and dw_spi_dma_maxburst_init() cope with a
missing channel, and set ctlr->dma_tx/dma_rx only after the capability
check has passed so that they never point to a released channel.

Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com>
---
 drivers/spi/spi-dw-core.c |  3 ++
 drivers/spi/spi-dw-dma.c  | 88 ++++++++++++++++++++++++++++-----------
 drivers/spi/spi-dw.h      |  1 +
 3 files changed, 67 insertions(+), 25 deletions(-)

diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c
index 206d3f9dd83d..6889f4446ecb 100644
--- a/drivers/spi/spi-dw-core.c
+++ b/drivers/spi/spi-dw-core.c
@@ -1360,6 +1360,9 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws)
 			goto err_free_irq;
 		} else if (ret) {
 			dev_warn(dev, "DMA init failed\n");
+		} else if (dws->dma_nr_chans == 1) {
+			/* Full-duplex DMA transfers need both channels */
+			dev_info(dev, "Single DMA channel, full-duplex DMA disabled\n");
 		} else {
 			ctlr->can_dma = dws->dma_ops->can_dma;
 			ctlr->flags |= SPI_CONTROLLER_MUST_TX;
diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c
index d0b1a03682e6..76d7814f0dcf 100644
--- a/drivers/spi/spi-dw-dma.c
+++ b/drivers/spi/spi-dw-dma.c
@@ -42,14 +42,19 @@ static void dw_spi_dma_maxburst_init(struct dw_spi *dws)
 
 	def_burst = dws->fifo_len / 2;
 
-	ret = dma_get_slave_caps(dws->rxchan, &caps);
-	if (!ret && caps.max_burst)
-		max_burst = caps.max_burst;
-	else
-		max_burst = DW_SPI_RX_BURST_LEVEL;
+	if (dws->rxchan) {
+		ret = dma_get_slave_caps(dws->rxchan, &caps);
+		if (!ret && caps.max_burst)
+			max_burst = caps.max_burst;
+		else
+			max_burst = DW_SPI_RX_BURST_LEVEL;
+
+		dws->rxburst = min(max_burst, def_burst);
+		dw_writel(dws, DW_SPI_DMARDLR, dws->rxburst - 1);
+	}
 
-	dws->rxburst = min(max_burst, def_burst);
-	dw_writel(dws, DW_SPI_DMARDLR, dws->rxburst - 1);
+	if (!dws->txchan)
+		return;
 
 	ret = dma_get_slave_caps(dws->txchan, &caps);
 	if (!ret && caps.max_burst)
@@ -74,20 +79,39 @@ static void dw_spi_dma_maxburst_init(struct dw_spi *dws)
 
 static int dw_spi_dma_caps_init(struct dw_spi *dws)
 {
-	struct dma_slave_caps tx, rx;
+	struct dma_slave_caps tx = {}, rx = {};
 	int ret;
 
-	ret = dma_get_slave_caps(dws->txchan, &tx);
-	if (ret)
-		return ret;
+	if (dws->txchan) {
+		ret = dma_get_slave_caps(dws->txchan, &tx);
+		if (ret)
+			return ret;
 
-	ret = dma_get_slave_caps(dws->rxchan, &rx);
-	if (ret)
-		return ret;
+		if (!(tx.directions & BIT(DMA_MEM_TO_DEV)))
+			return -ENXIO;
+	}
+
+	if (dws->rxchan) {
+		ret = dma_get_slave_caps(dws->rxchan, &rx);
+		if (ret)
+			return ret;
+
+		if (!(rx.directions & BIT(DMA_DEV_TO_MEM)))
+			return -ENXIO;
+	}
 
-	if (!(tx.directions & BIT(DMA_MEM_TO_DEV) &&
-	      rx.directions & BIT(DMA_DEV_TO_MEM)))
-		return -ENXIO;
+	/* With a single channel only one direction is available at a time */
+	if (!dws->rxchan) {
+		dws->dma_sg_burst = tx.max_sg_burst;
+		dws->dma_addr_widths = tx.dst_addr_widths;
+		return 0;
+	}
+
+	if (!dws->txchan) {
+		dws->dma_sg_burst = rx.max_sg_burst;
+		dws->dma_addr_widths = rx.src_addr_widths;
+		return 0;
+	}
 
 	if (tx.max_sg_burst > 0 && rx.max_sg_burst > 0)
 		dws->dma_sg_burst = min(tx.max_sg_burst, rx.max_sg_burst);
@@ -169,40 +193,54 @@ static int dw_spi_dma_init_generic(struct device *dev, struct dw_spi *dws)
 {
 	int ret;
 
+	dws->dma_nr_chans = 0;
+
 	dws->rxchan = dma_request_chan(dev, "rx");
 	if (IS_ERR(dws->rxchan)) {
 		ret = PTR_ERR(dws->rxchan);
 		dws->rxchan = NULL;
 		goto err_exit;
 	}
+	dws->dma_nr_chans++;
 
+	/*
+	 * Some platforms have only one DMA channel for the controller. Keep
+	 * the Rx channel in that case, it can still serve half-duplex
+	 * transfers.
+	 */
 	dws->txchan = dma_request_chan(dev, "tx");
 	if (IS_ERR(dws->txchan)) {
 		ret = PTR_ERR(dws->txchan);
 		dws->txchan = NULL;
-		goto free_rxchan;
+		if (ret == -EPROBE_DEFER)
+			goto free_rxchan;
+	} else {
+		dws->dma_nr_chans++;
 	}
 
-	dws->ctlr->dma_rx = dws->rxchan;
-	dws->ctlr->dma_tx = dws->txchan;
-
-	init_completion(&dws->dma_completion);
-
 	ret = dw_spi_dma_caps_init(dws);
 	if (ret)
 		goto free_txchan;
 
 	dw_spi_dma_maxburst_init(dws);
 
+	init_completion(&dws->dma_completion);
+
+	dws->ctlr->dma_rx = dws->rxchan;
+	dws->ctlr->dma_tx = dws->txchan;
+
 	return 0;
 
 free_txchan:
-	dma_release_channel(dws->txchan);
-	dws->txchan = NULL;
+	if (dws->txchan) {
+		dma_release_channel(dws->txchan);
+		dws->txchan = NULL;
+	}
 free_rxchan:
 	dma_release_channel(dws->rxchan);
 	dws->rxchan = NULL;
 err_exit:
+	dws->dma_nr_chans = 0;
 	return ret;
 }
 
diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h
index e62f919f1188..01eb24ae7fe0 100644
--- a/drivers/spi/spi-dw.h
+++ b/drivers/spi/spi-dw.h
@@ -227,6 +227,7 @@ struct dw_spi {
 	u32			txburst;
 	struct dma_chan		*rxchan;
 	u32			rxburst;
+	unsigned int		dma_nr_chans;
 	u32			dma_sg_burst;
 	u32			dma_addr_widths;
 	unsigned long		dma_chan_busy;
-- 
2.25.1


  parent reply	other threads:[~2026-09-30  7:16 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-30  7:15 [PATCH v2 0/3] Add enhance SPI DMA support for JHB100 SFC Changhuang Liang
2026-09-30  7:15 ` [PATCH v2 1/3] spi: dw: Convert the Tx done delay from SCK cycles to time Changhuang Liang
2026-09-30  7:15 ` Changhuang Liang [this message]
2026-09-30  7:15 ` [PATCH v2 3/3] spi: dw: Add DMA support for enhanced memory operations Changhuang Liang

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=20260930071535.7947-3-changhuang.liang@starfivetech.com \
    --to=changhuang.liang@starfivetech.com \
    --cc=broonie@kernel.org \
    --cc=fancer.lancer@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-spi@vger.kernel.org \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®