[PATCH v6 4/5] dmaengine: dw-edma: Dynamically append requests while running

From: Koichiro Den

Date: Fri Oct 30 2026 - 11:08:57 EST


From: Frank Li <Frank.Li@xxxxxxx>

Allow issue_pending() to fill free LL entries while the channel is busy,
then check progress and restart it if it has stopped. Publish requests
in descriptor order.

Multiple descriptors can now have entries in the LL ring. When resetting
the ring after an abort, mark the other descriptors with pending entries
as aborted too, so they cannot be replayed.

On resume, keep an LL channel BUSY while entries remain published,
even if dw_edma_start_transfer() publishes nothing new.

Signed-off-by: Frank Li <Frank.Li@xxxxxxx>
Co-developed-by: Koichiro Den <den@xxxxxxxxxxxxx>
Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
Reworked v5 patches 18 and 20:
https://lore.kernel.org/r/20260812155721.2807506-19-den@xxxxxxxxxxxxx/
https://lore.kernel.org/r/20260812155721.2807506-21-den@xxxxxxxxxxxxx/

drivers/dma/dw-edma/dw-edma-core.c | 67 +++++++++++++++++++++++-------
1 file changed, 51 insertions(+), 16 deletions(-)

diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 74613c95ae11..3e23d177a579 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -102,8 +102,24 @@ static void dw_hdma_set_callback_result(struct virt_dma_desc *vd,

static void dw_edma_core_reset_ll(struct dw_edma_chan *chan)
{
+ struct virt_dma_desc *vd, *tmp;
u32 i;

+ /*
+ * We cannot tell which pending entries completed before the reset.
+ * Abort their descriptors to avoid repeating a transfer.
+ */
+ list_for_each_entry_safe(vd, tmp, &chan->vc.desc_issued, node) {
+ struct dw_edma_desc *desc = vd2dw_edma_desc(vd);
+
+ if (desc->start_burst == desc->done_burst)
+ continue;
+
+ dw_hdma_set_callback_result(vd, DMA_TRANS_ABORTED);
+ list_del(&vd->node);
+ vchan_cookie_complete(vd);
+ }
+
chan->ll_head = 0;
chan->ll_done = 0;
/* Drop stale CB bits before reusing the circular LL ring. */
@@ -259,21 +275,34 @@ static int dw_edma_start_transfer(struct dw_edma_chan *chan)
{
struct dw_edma_desc *desc;
struct virt_dma_desc *vd;
+ int ret = 0;

- vd = vchan_next_desc(&chan->vc);
- if (!vd)
- return 0;
+ if (chan->non_ll) {
+ vd = vchan_next_desc(&chan->vc);
+ if (!vd)
+ return 0;

- desc = vd2dw_edma_desc(vd);
- if (!desc)
- return 0;
+ dw_edma_core_start(vd2dw_edma_desc(vd));
+ return 1;
+ }

- if (!chan->non_ll && !chan->ll_valid)
- dw_edma_core_reset_ll(chan);
+ list_for_each_entry(vd, &chan->vc.desc_issued, node) {
+ if (!dw_edma_core_get_free_num(chan))
+ break;

- dw_edma_core_start(desc);
+ desc = vd2dw_edma_desc(vd);
+ /* Fully published descriptors may still be in flight. */
+ if (desc->start_burst == desc->nburst)
+ continue;

- return 1;
+ if (!chan->ll_valid)
+ dw_edma_core_reset_ll(chan);
+
+ dw_edma_core_start(desc);
+ ret = 1;
+ }
+
+ return ret;
}

static void dw_edma_terminate_vdesc(struct virt_dma_desc *vd)
@@ -535,7 +564,8 @@ static int dw_edma_device_resume(struct dma_chan *dchan)
err = -EPERM;
} else {
chan->status = EDMA_ST_BUSY;
- if (!dw_edma_start_transfer(chan))
+ if (!dw_edma_start_transfer(chan) &&
+ (chan->non_ll || !dw_edma_ll_pending(chan)))
chan->status = EDMA_ST_IDLE;
dw_edma_core_ch_maybe_doorbell(chan);
}
@@ -580,11 +610,16 @@ static void dw_edma_device_issue_pending(struct dma_chan *dchan)

spin_lock_irqsave(&chan->vc.lock, flags);
if (chan->configured && vchan_issue_pending(&chan->vc) &&
- chan->request == EDMA_REQ_NONE &&
- chan->status == EDMA_ST_IDLE) {
- chan->status = EDMA_ST_BUSY;
- dw_edma_start_transfer(chan);
- dw_edma_core_ch_maybe_doorbell(chan);
+ chan->request == EDMA_REQ_NONE) {
+ if (chan->status == EDMA_ST_IDLE) {
+ chan->status = EDMA_ST_BUSY;
+ dw_edma_start_transfer(chan);
+ dw_edma_core_ch_maybe_doorbell(chan);
+ } else if (!chan->non_ll &&
+ chan->status == EDMA_ST_BUSY) {
+ dw_edma_start_transfer(chan);
+ dw_edma_ll_advance(chan, false);
+ }
}
spin_unlock_irqrestore(&chan->vc.lock, flags);
}
--
2.54.0