[PATCH v2 3/6] dmaengine: arm-dma350: Sync the slave command list before starting
From: Jelly Jia
Date: Fri Oct 09 2026 - 01:20:11 EST
The slave command list is mapped with dma_map_single(DMA_TO_DEVICE)
before the command words are written into it. For a non-coherent device
this leaves the channel reading stale memory: the direct mapping path
only performs cache maintenance at map time, and a mapping bounced
through the SWIOTLB keeps the copy taken at map time, so writes after
the map never reach the device.
The channel then executes only the first (register-programmed) command
and stops when it follows CH_LINKADDR into the stale list. On Sky1 the
audio playback died after exactly one period (0.34 s at 48 kHz S16
stereo) with "tx data underrun"; the same path is used for multi-entry
slave sg lists.
Sync the command list for the device after filling it in, which also
refreshes the bounce buffer. This is a no-op for a DMA coherent device.
Assisted-by: LLM checkpatch sparse dt_binding_check dtbs_check
Signed-off-by: Jelly Jia <Jelly.Jia@xxxxxxxxxxx>
---
v1 -> v2:
- New patch: the command list is streaming-mapped, so it must be synced
before the channel is started. Without it a non-coherent DMA-350
reads stale command words and the transfer stalls after the first
command.
drivers/dma/arm-dma350.c | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index bbcf4071c3e7..e3fbfcce66e8 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -306,6 +306,21 @@ static int d350_alloc_cmds(struct dma_chan *dchan, struct d350_desc *desc,
return 0;
}
+/*
+ * The command words are written after the command list has been mapped, so
+ * make them visible to the device before the descriptor is submitted. This
+ * also refreshes the bounce buffer when the mapping is bounced through the
+ * SWIOTLB. It is a no-op for a DMA coherent device.
+ */
+static void d350_sync_cmds(struct device *dev, struct d350_desc *desc)
+{
+ if (!desc->cmds)
+ return;
+
+ dma_sync_single_for_device(dev, desc->cmds_bus, desc->cmds_size,
+ DMA_TO_DEVICE);
+}
+
static void d350_unmap_resource(struct d350_chan *dch)
{
struct device *dev = dch->vc.chan.device->dev;
@@ -599,6 +614,8 @@ d350_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,
if (sg_len > 1)
memcpy(desc->command, desc->cmds, cmd_size);
+ d350_sync_cmds(dchan->device->dev, desc);
+
return vchan_tx_prep(&dch->vc, &desc->vd, flags);
err_free_cmds:
@@ -668,6 +685,8 @@ d350_prep_dma_cyclic(struct dma_chan *dchan, dma_addr_t buf_addr,
}
memcpy(desc->command, desc->cmds, cmd_size);
+ d350_sync_cmds(dchan->device->dev, desc);
+
return vchan_tx_prep(&dch->vc, &desc->vd, flags);
}
--
2.54.0