[PATCH 09/17] dmaengine: dw-edma: Add LL interrupt placement policy

From: Koichiro Den

Date: Mon Jun 15 2026 - 11:43:56 EST


Move the linked-list (LL) interrupt placement decision behind a core
callback so eDMA and HDMA can use different policies.

Keep eDMA interrupts at descriptor ends and at the last free slot,
matching its existing DONE interrupt semantics.

For HDMA, use watermark placement as producer-consumer progress points.
Keep descriptor-end and the last data entry before the link element
unconditional: descriptor-end gives completion accounting a progress
point even for a single descriptor, while the last data entry provides
an end-of-lap producer-consumer checkpoint.

Add fixed-interval watermarks only when the current descriptor cannot
fit in the LL ring, or when another issued descriptor is waiting behind
it. This keeps low-depth traffic from taking extra watermark interrupts
without making large rings mostly STOP-driven. The interval is a driver
coalescing policy, not a databook-mandated value.

This patch only decides where LL interrupt bits should be set. A later
HDMA patch wires those bits to the hardware watermark interrupt path.

Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
drivers/dma/dw-edma/dw-edma-core.c | 9 ++++++--
drivers/dma/dw-edma/dw-edma-core.h | 1 +
drivers/dma/dw-edma/dw-edma-v0-core.c | 10 +++++++++
drivers/dma/dw-edma/dw-hdma-v0-core.c | 31 +++++++++++++++++++++++++++
4 files changed, 49 insertions(+), 2 deletions(-)

diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index cac03c59bfe4..2165f2fa5398 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -83,6 +83,12 @@ static u32 dw_edma_core_get_free_num(struct dw_edma_chan *chan)
(chan->ll_max - 1);
}

+static bool dw_edma_core_enable_ll_irq(struct dw_edma_desc *desc, u32 i,
+ u32 free)
+{
+ return desc->chan->dw->core->ll_irq(desc, i, free);
+}
+
static void dw_edma_core_start(struct dw_edma_desc *desc)
{
struct dw_edma_chan *chan = desc->chan;
@@ -103,10 +109,9 @@ static void dw_edma_core_start(struct dw_edma_desc *desc)
dw_edma_core_ll_link(chan, chan->ll_max - 1, chan->cb,
chan->ll_region.paddr);

- /* Enable irq for last free entry or last burst */
dw_edma_core_ll_data(chan, &desc->burst[i],
chan->ll_head, chan->cb,
- i == desc->nburst - 1 || free == 1);
+ dw_edma_core_enable_ll_irq(desc, i, free));

chan->ll_head++;

diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index 46af4ea3ae5f..ea9f4292c40e 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -147,6 +147,7 @@ struct dw_edma_core_ops {
u32 idx, bool cb, bool irq);
void (*ll_link)(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr);
int (*ll_cur_idx)(struct dw_edma_chan *chan);
+ bool (*ll_irq)(struct dw_edma_desc *desc, u32 i, u32 free);
void (*ch_doorbell)(struct dw_edma_chan *chan);
void (*ch_enable)(struct dw_edma_chan *chan);
void (*ch_config)(struct dw_edma_chan *chan);
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index edc71a4dbc79..47faedd14dc2 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -520,6 +520,15 @@ static int dw_edma_v0_core_ll_cur_idx(struct dw_edma_chan *chan)
return (val - (paddr & 0xFFFFFFFF)) / EDMA_LL_SZ;
}

+static bool dw_edma_v0_core_ll_irq(struct dw_edma_desc *desc, u32 i, u32 free)
+{
+ /*
+ * eDMA reports LL interrupts through DONE. Keep them at
+ * descriptor ends, plus the last free slot to refill the ring.
+ */
+ return i == desc->nburst - 1 || free == 1;
+}
+
/* eDMA debugfs callbacks */
static void dw_edma_v0_core_debugfs_on(struct dw_edma *dw)
{
@@ -534,6 +543,7 @@ static const struct dw_edma_core_ops dw_edma_v0_core = {
.ll_data = dw_edma_v0_core_ll_data,
.ll_link = dw_edma_v0_core_ll_link,
.ll_cur_idx = dw_edma_v0_core_ll_cur_idx,
+ .ll_irq = dw_edma_v0_core_ll_irq,
.ch_doorbell = dw_edma_v0_core_ch_doorbell,
.ch_enable = dw_edma_v0_core_ch_enable,
.ch_config = dw_edma_v0_core_ch_config,
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
index 677416f422ff..b9e193774714 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
@@ -13,6 +13,9 @@
#include "dw-hdma-v0-regs.h"
#include "dw-hdma-v0-debugfs.h"

+/* Empirically chosen; can become debugfs-tunable if needed. */
+#define HDMA_V0_WATERMARK_INTERVAL 4
+
enum dw_hdma_control {
DW_HDMA_V0_CB = BIT(0),
DW_HDMA_V0_TCB = BIT(1),
@@ -301,6 +304,33 @@ static int dw_hdma_v0_core_ll_cur_idx(struct dw_edma_chan *chan)
return (val - (paddr & 0xFFFFFFFF)) / EDMA_LL_SZ;
}

+static bool dw_hdma_v0_core_ll_irq(struct dw_edma_desc *desc, u32 i, u32 free)
+{
+ struct dw_edma_chan *chan = desc->chan;
+ bool needs_progress;
+
+ /*
+ * Keep descriptor-end and the last data entry before the link element
+ * unconditional: descriptor-end gives completion accounting a progress
+ * point even for a single descriptor, while the last data entry provides
+ * an end-of-lap producer-consumer checkpoint.
+ */
+ if (i == desc->nburst - 1 || chan->ll_head == chan->ll_max - 2)
+ return true;
+
+ /*
+ * Additional fixed-interval watermarks keep large LL rings from becoming
+ * mostly STOP-driven. They are useful only when there is more work to
+ * feed or the current descriptor cannot fit in the LL ring without
+ * progress.
+ */
+ needs_progress = desc->nburst > chan->ll_max - 2 ||
+ !list_is_last(&desc->vd.node, &chan->vc.desc_issued);
+
+ return needs_progress && chan->ll_head &&
+ chan->ll_head % HDMA_V0_WATERMARK_INTERVAL == 0;
+}
+
/* HDMA debugfs callbacks */
static void dw_hdma_v0_core_debugfs_on(struct dw_edma *dw)
{
@@ -315,6 +345,7 @@ static const struct dw_edma_core_ops dw_hdma_v0_core = {
.ll_data = dw_hdma_v0_core_ll_data,
.ll_link = dw_hdma_v0_core_ll_link,
.ll_cur_idx = dw_hdma_v0_core_ll_cur_idx,
+ .ll_irq = dw_hdma_v0_core_ll_irq,
.ch_doorbell = dw_hdma_v0_core_ch_doorbell,
.ch_enable = dw_hdma_v0_core_ch_enable,
.ch_config = dw_hdma_v0_core_ch_config,
--
2.51.0