[PATCH v2 11/19] dmaengine: dw-edma: Reconcile lost completions from a stopped LLP re-sample

From: Koichiro Den

Date: Thu Jul 23 2026 - 04:47:55 EST


The IRQ-time DMA_LLP read can race the final internal LLP update and leave
the recorded boundary one entry short. No later DONE may arrive to report
that progress.

Before re-kicking a stopped channel, read LLP again. Legacy eDMA requires
STOPPED status and zero transfer size; native HDMA has a dedicated STOP
event. If the boundary advanced, consume the progress and refill the ring
before deciding whether another kick is needed.

Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
Changes in v2:
- New patch.

drivers/dma/dw-edma/dw-edma-core.c | 45 ++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)

diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index c6c2655c6c43..9489ca7e70fd 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -396,6 +396,47 @@ static bool dw_edma_ll_consume_progress(struct dw_edma_chan *chan)
return true;
}

+static bool dw_edma_ll_has_hdma_stop_event(struct dw_edma_chan *chan)
+{
+ return chan->dw->chip->mf == EDMA_MF_HDMA_NATIVE;
+}
+
+/*
+ * Must be called with vc.lock held. A DONE-time DMA_LLP sample may miss
+ * the final burst element. For the legacy interrupt interface, accept a
+ * fresh LLP sample only when status is STOPPED and transfer size is zero.
+ * Native HDMA reports STOP directly.
+ */
+static bool dw_edma_ll_reconcile_and_refill(struct dw_edma_chan *chan)
+{
+ u32 old_done;
+ int idx;
+
+ if (dw_edma_core_ch_status(chan) != DMA_COMPLETE)
+ return false;
+
+ /* Native HDMA reports STOP directly, without a transfer-size check. */
+ if (!dw_edma_ll_has_hdma_stop_event(chan) &&
+ dw_edma_core_ch_transfer_size(chan) != 0)
+ return false;
+
+ idx = dw_edma_ll_recycle_idx(chan, dw_edma_core_ll_cur_idx(chan),
+ true);
+ if (idx < 0)
+ return false;
+
+ if (!dw_edma_ll_advance(chan, idx, &old_done))
+ return false;
+
+ dw_edma_ll_irq_idx_discard(chan);
+ dw_edma_ll_clean_pending(chan, old_done);
+ dw_edma_start_transfer(chan);
+ chan->status = dw_edma_ll_pending(chan) ?
+ EDMA_ST_BUSY : EDMA_ST_IDLE;
+
+ return true;
+}
+
/* Must be called with vc.lock held. */
static void dw_edma_core_ch_maybe_doorbell(struct dw_edma_chan *chan)
{
@@ -410,6 +451,10 @@ static void dw_edma_core_ch_maybe_doorbell(struct dw_edma_chan *chan)
if (dw_edma_core_ch_status(chan) == DMA_IN_PROGRESS)
return;

+ dw_edma_ll_reconcile_and_refill(chan);
+ if (!dw_edma_ll_pending(chan))
+ return;
+
dw_edma_core_ch_doorbell(chan);
}

--
2.51.0