[PATCH 14/17] dmaengine: dw-edma: Reset LL state after terminate and abort
From: Koichiro Den
Date: Mon Jun 15 2026 - 11:45:33 EST
Termination or abort can leave appended entries in the circular LL ring.
Reset the LL state before the channel is reused, so the next transfer
starts from a clean ring instead of stale producer state or old LL
entries.
Since this now resets the hardware-visible LL ring, terminate_all() must
base the decision on the hardware channel state, not only on the
driver's software status. If the channel is still running, defer the
reset to STOP acknowledgment. If the hardware has already stopped, no
later interrupt will acknowledge a pending STOP or PAUSE request, so
clean up immediately.
Once STOP has been requested, do not recycle LL progress as successful
completion. Move pending descriptors to the terminated list without
callbacks and deconfigure the channel. For abort interrupts, complete
all issued descriptors with DMA_TRANS_ABORTED, reset the LL state, and
leave the channel configured for reuse.
Also handle STOP and PAUSE acknowledgments even if the issued list has
already become empty.
Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
drivers/dma/dw-edma/dw-edma-core.c | 60 +++++++++++++++---------------
1 file changed, 29 insertions(+), 31 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index ae38ff0a8b83..4036adafedfa 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -397,34 +397,33 @@ static int dw_edma_device_terminate_all(struct dma_chan *dchan)
{
struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
unsigned long flags;
- int err = 0;
spin_lock_irqsave(&chan->vc.lock, flags);
if (!chan->configured) {
dw_edma_terminate_all_descs(chan);
- } else if (chan->status == EDMA_ST_PAUSE) {
- dw_edma_terminate_all_descs(chan);
- chan->status = EDMA_ST_IDLE;
- chan->configured = false;
- } else if (chan->status == EDMA_ST_IDLE) {
- dw_edma_terminate_all_descs(chan);
- chan->configured = false;
- } else if (dw_edma_core_ch_status(chan) == DMA_COMPLETE) {
+ } else if (dw_edma_core_ch_status(chan) == DMA_IN_PROGRESS) {
+ /*
+ * Defer the cleanup to the STOP interrupt. This also keeps a
+ * pending STOP request idempotent and promotes a pending
+ * PAUSE request to STOP.
+ */
+ chan->request = EDMA_REQ_STOP;
+ } else {
/*
- * The channel is in a false BUSY state, probably didn't
- * receive or lost an interrupt
+ * The channel is not running from the hardware point of view:
+ * it has either never started or already stopped. No later
+ * interrupt will clean up descriptors for us, nor can it
+ * acknowledge a pending STOP or PAUSE request.
*/
dw_edma_terminate_all_descs(chan);
+ dw_edma_core_reset_ll(chan);
+ chan->request = EDMA_REQ_NONE;
chan->status = EDMA_ST_IDLE;
chan->configured = false;
- } else if (chan->request > EDMA_REQ_PAUSE) {
- err = -EPERM;
- } else {
- chan->request = EDMA_REQ_STOP;
}
spin_unlock_irqrestore(&chan->vc.lock, flags);
- return err;
+ return 0;
}
static void dw_edma_device_issue_pending(struct dma_chan *dchan)
@@ -713,11 +712,11 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
spin_lock_irqsave(&chan->vc.lock, flags);
idx = dw_edma_core_ll_cur_idx(chan);
- dw_edma_ll_recycle(chan, idx);
- vd = vchan_next_desc(&chan->vc);
switch (chan->request) {
case EDMA_REQ_NONE:
+ dw_edma_ll_recycle(chan, idx);
+ vd = vchan_next_desc(&chan->vc);
if (vd) {
desc = vd2dw_edma_desc(vd);
if (desc->start_burst >= desc->nburst) {
@@ -738,18 +737,17 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
break;
case EDMA_REQ_STOP:
- if (vd) {
- dw_edma_terminate_all_descs(chan);
- chan->request = EDMA_REQ_NONE;
- chan->status = EDMA_ST_IDLE;
- }
+ dw_edma_terminate_all_descs(chan);
+ dw_edma_core_reset_ll(chan);
+ chan->request = EDMA_REQ_NONE;
+ chan->status = EDMA_ST_IDLE;
+ chan->configured = false;
break;
case EDMA_REQ_PAUSE:
- if (vd) {
- chan->request = EDMA_REQ_NONE;
- chan->status = EDMA_ST_PAUSE;
- }
+ dw_edma_ll_recycle(chan, idx);
+ chan->request = EDMA_REQ_NONE;
+ chan->status = EDMA_ST_PAUSE;
break;
default:
@@ -777,19 +775,19 @@ static void dw_edma_progress_interrupt(struct dw_edma_chan *chan)
static void dw_edma_abort_interrupt(struct dw_edma_chan *chan)
{
- struct virt_dma_desc *vd;
+ struct virt_dma_desc *vd, *_vd;
unsigned long flags;
spin_lock_irqsave(&chan->vc.lock, flags);
- vd = vchan_next_desc(&chan->vc);
- if (vd) {
+ list_for_each_entry_safe(vd, _vd, &chan->vc.desc_issued, node) {
dw_hdma_set_callback_result(vd, DMA_TRANS_ABORTED);
list_del(&vd->node);
vchan_cookie_complete(vd);
}
- spin_unlock_irqrestore(&chan->vc.lock, flags);
+ dw_edma_core_reset_ll(chan);
chan->request = EDMA_REQ_NONE;
chan->status = EDMA_ST_IDLE;
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
}
static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data)
--
2.51.0