[PATCH 3/3] dmaengine: dw-axi-dmac: preserve suspension status until pause observes it

From: James Hilliard

Date: Mon Nov 09 2026 - 00:04:00 EST


dma_chan_pause() clears SUSPENDED even when polling times out. Hardware
can set that status after the last poll, including during the final
delay, and the unconditional clear then discards an acknowledgment the
caller never observed. Another channel's interrupt can also consume the
status because the shared IRQ handler clears every channel's status
before taking its lock. Masking the suspend interrupt signal does not
prevent either path from clearing the status used by the polling code.

Leave SUSPENDED to the pause path and acknowledge it only after a
successful poll. A timed-out pause can then be retried without losing a
late acknowledgment. Mark the channel paused only after confirmation,
and let repeated pause calls succeed while it remains paused.

Clear the saved state and any old suspension status when enabling a
channel or completing termination, so a later transfer must obtain its
own acknowledgment. These state changes are serialized with pause by
the channel lock.

Fixes: 1fe20f1b8454 ("dmaengine: Introduce DW AXI DMAC driver")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: James Hilliard <james.hilliard1@xxxxxxxxx>
---
drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
index de092f190528..33340f0e7462 100644
--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
+++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
@@ -226,6 +226,9 @@ static inline void axi_chan_enable(struct axi_dma_chan *chan)
{
u64 val;

+ axi_chan_irq_clear(chan, DWAXIDMAC_IRQ_SUSPENDED);
+ chan->is_paused = false;
+
if (chan->chip->dw->hdata->nr_channels >= DMAC_CHAN_16) {
val = axi_dma_ioread64(chan->chip, DMAC_CHEN);
if (chan->id >= DMAC_CHAN_16) {
@@ -1161,6 +1164,8 @@ static irqreturn_t dw_axi_dma_interrupt(int irq, void *dev_id)
for (i = 0; i < dw->hdata->nr_channels; i++) {
chan = &dw->chan[i];
status = axi_chan_irq_read(chan);
+ /* Suspension status is consumed by dma_chan_pause(). */
+ status &= ~DWAXIDMAC_IRQ_SUSPENDED;
axi_chan_irq_clear(chan, status);

dev_vdbg(chip->dev, "%s %u IRQ status: 0x%08x\n",
@@ -1206,6 +1211,8 @@ static int dma_chan_terminate_all(struct dma_chan *dchan)

vchan_get_all_descriptors(&chan->vc, &head);

+ axi_chan_irq_clear(chan, DWAXIDMAC_IRQ_SUSPENDED);
+ chan->is_paused = false;
chan->cyclic = false;
spin_unlock_irqrestore(&chan->vc.lock, flags);

@@ -1225,6 +1232,9 @@ static int dma_chan_pause(struct dma_chan *dchan)

spin_lock_irqsave(&chan->vc.lock, flags);

+ if (chan->is_paused)
+ goto out;
+
if (chan->chip->dw->hdata->nr_channels >= DMAC_CHAN_16) {
val = axi_dma_ioread64(chan->chip, DMAC_CHSUSPREG);
if (chan->id >= DMAC_CHAN_16) {
@@ -1258,10 +1268,12 @@ static int dma_chan_pause(struct dma_chan *dchan)
udelay(2);
} while (--timeout);

- axi_chan_irq_clear(chan, DWAXIDMAC_IRQ_SUSPENDED);
-
- chan->is_paused = true;
+ if (timeout) {
+ axi_chan_irq_clear(chan, DWAXIDMAC_IRQ_SUSPENDED);
+ chan->is_paused = true;
+ }

+out:
spin_unlock_irqrestore(&chan->vc.lock, flags);

return timeout ? 0 : -EAGAIN;

--
2.53.0