[PATCH v1 1/2] spi: dw: Request DMA channels at runtime instead of probe

From: Changhuang Liang

Date: Wed Sep 23 2026 - 06:17:28 EST


The DW SPI controller requests its DMA channels in
dw_spi_add_controller(), which ties them up for the entire lifetime of
the controller. Even a controller that is only ever used for standard
SPI transfers and never for enhanced SPI transfers holds the channels
exclusively from probe onwards, preventing them from being shared with
other users.

At probe time, first check whether the controller has DMA channels,
and release them immediately after the check. Remove the DMA channel
release from the remove path. Implement prepare_transfer_hardware()
and unprepare_transfer_hardware() to acquire and release the channels
later at runtime. This ensures that the DMA channels can be
time-multiplexed between enhanced SPI and standard SPI.

Since DMA is no longer set up at probe and torn down at remove.
dw_spi_dma_exit() now also clears txchan/rxchan, the controller's
dma_tx/dma_rx pointers, so that the state is consistent across
repeated prepare/unprepare cycles. Also check whether the channels
are valid in dw_spi_can_dma().

Signed-off-by: Changhuang Liang <changhuang.liang@xxxxxxxxxxxxxxxx>
---
drivers/spi/spi-dw-core.c | 47 ++++++++++++++++++++++++++++++++-------
drivers/spi/spi-dw-dma.c | 8 +++++++
2 files changed, 47 insertions(+), 8 deletions(-)

diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c
index 206d3f9dd83d..04a5b48373e1 100644
--- a/drivers/spi/spi-dw-core.c
+++ b/drivers/spi/spi-dw-core.c
@@ -545,6 +545,39 @@ static int dw_spi_transfer_one(struct spi_controller *ctlr,
return 1;
}

+static int dw_spi_prepare_hardware(struct spi_controller *ctlr)
+{
+ struct dw_spi *dws = spi_controller_get_devdata(ctlr);
+ int ret;
+
+ if (!ctlr->can_dma)
+ return 0;
+
+ ret = dws->dma_ops->dma_init(ctlr->dev.parent, dws);
+ if (ret) {
+ /*
+ * DMA is optional: fall back to the PIO/IRQ path instead of
+ * failing the message. Use dev_dbg() since this may happen
+ * on every prepare.
+ */
+ dev_dbg(&ctlr->dev, "DMA init failed (%d), using PIO\n", ret);
+
+ return 0;
+ }
+
+ return 0;
+}
+
+static int dw_spi_unprepare_hardware(struct spi_controller *ctlr)
+{
+ struct dw_spi *dws = spi_controller_get_devdata(ctlr);
+
+ if (dws->dma_ops && dws->dma_ops->dma_exit)
+ dws->dma_ops->dma_exit(dws);
+
+ return 0;
+}
+
static inline void dw_spi_abort(struct spi_controller *ctlr)
{
struct dw_spi *dws = spi_controller_get_devdata(ctlr);
@@ -1330,6 +1363,8 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws)
ctlr->setup = dw_spi_setup;
ctlr->cleanup = dw_spi_cleanup;
ctlr->transfer_one = dw_spi_transfer_one;
+ ctlr->prepare_transfer_hardware = dw_spi_prepare_hardware;
+ ctlr->unprepare_transfer_hardware = dw_spi_unprepare_hardware;
ctlr->handle_err = dw_spi_handle_err;
ctlr->auto_runtime_pm = true;

@@ -1354,13 +1389,14 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws)
device_property_read_u32(dev, "rx-sample-delay-ns",
&dws->def_rx_sample_dly_ns);

- if (dws->dma_ops && dws->dma_ops->dma_init) {
+ if (dws->dma_ops && dws->dma_ops->dma_init && dws->dma_ops->dma_exit) {
ret = dws->dma_ops->dma_init(dev, dws);
if (ret == -EPROBE_DEFER) {
goto err_free_irq;
} else if (ret) {
dev_warn(dev, "DMA init failed\n");
} else {
+ dws->dma_ops->dma_exit(dws);
ctlr->can_dma = dws->dma_ops->can_dma;
ctlr->flags |= SPI_CONTROLLER_MUST_TX;
}
@@ -1369,15 +1405,13 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws)
ret = spi_register_controller(ctlr);
if (ret) {
dev_err_probe(dev, ret, "problem registering spi controller\n");
- goto err_dma_exit;
+ goto err_disable_ctlr;
}

dw_spi_debugfs_init(dws);
return 0;

-err_dma_exit:
- if (dws->dma_ops && dws->dma_ops->dma_exit)
- dws->dma_ops->dma_exit(dws);
+err_disable_ctlr:
dw_spi_enable_chip(dws, 0);
err_free_irq:
free_irq(dws->irq, ctlr);
@@ -1393,9 +1427,6 @@ void dw_spi_remove_controller(struct dw_spi *dws)

spi_unregister_controller(dws->ctlr);

- if (dws->dma_ops && dws->dma_ops->dma_exit)
- dws->dma_ops->dma_exit(dws);
-
dw_spi_shutdown_chip(dws);

free_irq(dws->irq, dws->ctlr);
diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c
index f7d848fec9ab..022eb4fae33d 100644
--- a/drivers/spi/spi-dw-dma.c
+++ b/drivers/spi/spi-dw-dma.c
@@ -211,11 +211,15 @@ static void dw_spi_dma_exit(struct dw_spi *dws)
if (dws->txchan) {
dmaengine_terminate_sync(dws->txchan);
dma_release_channel(dws->txchan);
+ dws->txchan = NULL;
+ dws->ctlr->dma_tx = NULL;
}

if (dws->rxchan) {
dmaengine_terminate_sync(dws->rxchan);
dma_release_channel(dws->rxchan);
+ dws->rxchan = NULL;
+ dws->ctlr->dma_rx = NULL;
}
}

@@ -249,6 +253,10 @@ static bool dw_spi_can_dma(struct spi_controller *ctlr,
enum dma_slave_buswidth dma_bus_width;
u8 n_bytes = roundup_pow_of_two(BITS_TO_BYTES(xfer->bits_per_word));

+ /* Channels are acquired at runtime and may be unavailable */
+ if (!dws->txchan || !dws->rxchan)
+ return false;
+
if (xfer->len <= dws->fifo_len)
return false;

--
2.25.1