[PATCH v2 15/19] dmaengine: dw-edma: Add engine reset and enable operations
From: Koichiro Den
Date: Thu Jul 23 2026 - 04:50:24 EST
Direction recovery must keep the engine disabled while its configured LL
channel contexts are rebuilt.
Add separate engine_reset and engine_enable operations. For eDMA, reset
clears ENGINE_EN, waits for it to read back zero, then clears latched DONE
and ABORT status. If ENGINE_EN does not clear, dump the state and do not
try to enable it again. A separate enable operation lets the caller
rebuild channel contexts first.
Enable the direction after channel context programming. Keep enable a
no-op in HDMA compatibility mode, which does not expose ENGINE_EN.
Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
Changes in v2:
- New patch.
drivers/dma/dw-edma/dw-edma-core.h | 5 +++
drivers/dma/dw-edma/dw-edma-v0-core.c | 63 ++++++++++++++++++++++++++-
2 files changed, 66 insertions(+), 2 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index d235046ccb90..e3b42ad14f78 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -180,6 +180,9 @@ struct dw_edma_core_ops {
int (*ll_cur_idx)(struct dw_edma_chan *chan);
/* Called with chan->vc.lock held. */
bool (*ll_irq)(struct dw_edma_desc *desc, u32 i, u32 free);
+ /* Reset one direction, discard its IRQ status, and leave it disabled. */
+ bool (*engine_reset)(struct dw_edma *dw, enum dw_edma_dir dir);
+ void (*engine_enable)(struct dw_edma *dw, enum dw_edma_dir dir);
void (*ch_doorbell)(struct dw_edma_chan *chan);
void (*ch_enable)(struct dw_edma_chan *chan);
void (*ch_config)(struct dw_edma_chan *chan);
@@ -310,6 +313,8 @@ static inline void dw_edma_core_ch_doorbell(struct dw_edma_chan *chan)
static inline void dw_edma_core_ch_enable(struct dw_edma_chan *chan)
{
chan->dw->core->ch_enable(chan);
+ if (chan->dw->core->engine_enable)
+ chan->dw->core->engine_enable(chan->dw, chan->dir);
}
static inline
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index 42036239ba79..d834ae1633ef 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -220,6 +220,65 @@ static int dw_edma_v0_core_engine_disable(struct dw_edma *dw,
return ret;
}
+static void dw_edma_v0_core_engine_dump(struct dw_edma *dw,
+ enum dw_edma_dir dir)
+{
+ u16 cnt = dir == EDMA_DIR_WRITE ? dw->wr_ch_cnt : dw->rd_ch_cnt;
+ u16 i;
+
+ dev_warn(dw->chip->dev, "%s engine: int_status 0x%08x err_status 0x%08x\n",
+ dir == EDMA_DIR_WRITE ? "wr" : "rd",
+ GET_RW_32(dw, dir, int_status),
+ dir == EDMA_DIR_WRITE ? GET_32(dw, wr_err_status) :
+ GET_32(dw, rd_err_status.lsb));
+ for (i = 0; i < cnt; i++)
+ dev_warn(dw->chip->dev,
+ " ch%u: ch_control1 0x%08x transfer_size 0x%08x llp.lsb 0x%08x\n",
+ i, GET_CH_32(dw, dir, i, ch_control1),
+ GET_CH_32(dw, dir, i, transfer_size),
+ GET_CH_32(dw, dir, i, llp.lsb));
+}
+
+static bool dw_edma_v0_core_engine_reset(struct dw_edma *dw,
+ enum dw_edma_dir dir)
+{
+ /*
+ * The databook says clearing ENGINE_EN resets the DMA logic while
+ * preserving configuration registers, but does not document drain
+ * semantics. On the tested integration, ENGINE_EN read back as zero
+ * only after outstanding transactions appeared to drain; the stalled
+ * channel then resumed after reset.
+ *
+ * If ENGINE_EN does not clear, do not re-enable the engine. On the
+ * tested integration, re-enabling after such a timeout wedged the
+ * controller and also blocked inbound BAR accesses. Return failure so
+ * the caller can keep channels gated and retry.
+ */
+ if (dw_edma_v0_core_engine_disable(dw, dir)) {
+ dw_edma_v0_core_engine_dump(dw, dir);
+ return false;
+ }
+
+ /* Discard interrupt status belonging to the contexts being reset. */
+ SET_RW_32(dw, dir, int_clear,
+ EDMA_V0_DONE_INT_MASK | EDMA_V0_ABORT_INT_MASK);
+ GET_RW_32(dw, dir, int_status);
+
+ return true;
+}
+
+/*
+ * Keep enable separate from reset. Re-enabling can resume a previously
+ * running channel from preserved internal state without a doorbell, so the
+ * caller must rebuild every channel context first.
+ */
+static void dw_edma_v0_core_engine_enable(struct dw_edma *dw,
+ enum dw_edma_dir dir)
+{
+ if (dw->chip->mf != EDMA_MF_HDMA_COMPAT)
+ SET_RW_32(dw, dir, engine_en, BIT(0));
+}
+
static int dw_edma_v0_core_dir_off(struct dw_edma *dw, enum dw_edma_dir dir)
{
u16 count, id;
@@ -452,8 +511,6 @@ static void dw_edma_v0_core_ch_enable(struct dw_edma_chan *chan)
unsigned long flags;
u32 tmp;
- /* Enable engine */
- SET_RW_32(dw, chan->dir, engine_en, BIT(0));
if (dw->chip->mf == EDMA_MF_HDMA_COMPAT)
dw_edma_v0_core_ch_power(dw, chan->dir, chan->id, true);
/* Interrupt mask/unmask - done, abort */
@@ -703,6 +760,8 @@ static const struct dw_edma_core_ops dw_edma_v0_core = {
.ll_clear = dw_edma_v0_core_ll_clear,
.ll_cur_idx = dw_edma_v0_core_ll_cur_idx,
.ll_irq = dw_edma_v0_core_ll_irq,
+ .engine_reset = dw_edma_v0_core_engine_reset,
+ .engine_enable = dw_edma_v0_core_engine_enable,
.ch_doorbell = dw_edma_v0_core_ch_doorbell,
.ch_enable = dw_edma_v0_core_ch_enable,
.ch_config = dw_edma_v0_core_ch_config,
--
2.51.0