On Mon, Feb 09, 2026 at 09:53:09PM +0900, Koichiro Den wrote: > Some DesignWare eDMA instances support "interrupt emulation", where a > software write can assert the IRQ line without setting the normal > DONE/ABORT status bits. > > With a shared IRQ handler the driver cannot reliably distinguish an > emulated interrupt from a real one by only looking at DONE/ABORT status > bits. Leaving the emulated IRQ asserted may leave a level-triggered IRQ > line permanently asserted. > > Add a core callback, .ack_emulated_irq(), to perform the core-specific > deassert sequence and call it from the read/write/common IRQ handlers. > Note that previously a direct software write could assert the emulated > IRQ without DMA activity, leading to the interrupt never getting > deasserted. This patch resolves it. > > For v0, a zero write to INT_CLEAR deasserts the emulated IRQ and is a > no-op for real interrupts. HDMA is not tested or verified and is > therefore unsupported for now. > > Signed-off-by: Koichiro Den <[email protected]> > ---
Reviewed-by: Frank Li <[email protected]> > drivers/dma/dw-edma/dw-edma-core.c | 48 ++++++++++++++++++++++++--- > drivers/dma/dw-edma/dw-edma-core.h | 10 ++++++ > drivers/dma/dw-edma/dw-edma-v0-core.c | 11 ++++++ > 3 files changed, 64 insertions(+), 5 deletions(-) > > diff --git a/drivers/dma/dw-edma/dw-edma-core.c > b/drivers/dma/dw-edma/dw-edma-core.c > index 8e5f7defa6b6..fe131abf1ca3 100644 > --- a/drivers/dma/dw-edma/dw-edma-core.c > +++ b/drivers/dma/dw-edma/dw-edma-core.c > @@ -663,7 +663,24 @@ static void dw_edma_abort_interrupt(struct dw_edma_chan > *chan) > chan->status = EDMA_ST_IDLE; > } > > -static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data) > +static inline irqreturn_t dw_edma_interrupt_emulated(void *data) > +{ > + struct dw_edma_irq *dw_irq = data; > + struct dw_edma *dw = dw_irq->dw; > + > + /* > + * Interrupt emulation may assert the IRQ line without updating the > + * normal DONE/ABORT status bits. With a shared IRQ handler we > + * cannot reliably detect such events by status registers alone, so > + * always perform the core-specific deassert sequence. > + */ > + if (dw_edma_core_ack_emulated_irq(dw)) > + return IRQ_NONE; > + > + return IRQ_HANDLED; > +} > + > +static inline irqreturn_t dw_edma_interrupt_write_inner(int irq, void *data) > { > struct dw_edma_irq *dw_irq = data; > > @@ -672,7 +689,7 @@ static inline irqreturn_t dw_edma_interrupt_write(int > irq, void *data) > dw_edma_abort_interrupt); > } > > -static inline irqreturn_t dw_edma_interrupt_read(int irq, void *data) > +static inline irqreturn_t dw_edma_interrupt_read_inner(int irq, void *data) > { > struct dw_edma_irq *dw_irq = data; > > @@ -681,12 +698,33 @@ static inline irqreturn_t dw_edma_interrupt_read(int > irq, void *data) > dw_edma_abort_interrupt); > } > > -static irqreturn_t dw_edma_interrupt_common(int irq, void *data) > +static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data) > +{ > + irqreturn_t ret = IRQ_NONE; > + > + ret |= dw_edma_interrupt_write_inner(irq, data); > + ret |= dw_edma_interrupt_emulated(data); > + > + return ret; > +} > + > +static inline irqreturn_t dw_edma_interrupt_read(int irq, void *data) > +{ > + irqreturn_t ret = IRQ_NONE; > + > + ret |= dw_edma_interrupt_read_inner(irq, data); > + ret |= dw_edma_interrupt_emulated(data); > + > + return ret; > +} > + > +static inline irqreturn_t dw_edma_interrupt_common(int irq, void *data) > { > irqreturn_t ret = IRQ_NONE; > > - ret |= dw_edma_interrupt_write(irq, data); > - ret |= dw_edma_interrupt_read(irq, data); > + ret |= dw_edma_interrupt_write_inner(irq, data); > + ret |= dw_edma_interrupt_read_inner(irq, data); > + ret |= dw_edma_interrupt_emulated(data); > > return ret; > } > diff --git a/drivers/dma/dw-edma/dw-edma-core.h > b/drivers/dma/dw-edma/dw-edma-core.h > index 71894b9e0b15..50b87b63b581 100644 > --- a/drivers/dma/dw-edma/dw-edma-core.h > +++ b/drivers/dma/dw-edma/dw-edma-core.h > @@ -126,6 +126,7 @@ struct dw_edma_core_ops { > void (*start)(struct dw_edma_chunk *chunk, bool first); > void (*ch_config)(struct dw_edma_chan *chan); > void (*debugfs_on)(struct dw_edma *dw); > + void (*ack_emulated_irq)(struct dw_edma *dw); > }; > > struct dw_edma_sg { > @@ -206,4 +207,13 @@ void dw_edma_core_debugfs_on(struct dw_edma *dw) > dw->core->debugfs_on(dw); > } > > +static inline int dw_edma_core_ack_emulated_irq(struct dw_edma *dw) > +{ > + if (!dw->core->ack_emulated_irq) > + return -EOPNOTSUPP; > + > + dw->core->ack_emulated_irq(dw); > + return 0; > +} > + > #endif /* _DW_EDMA_CORE_H */ > diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c > b/drivers/dma/dw-edma/dw-edma-v0-core.c > index b75fdaffad9a..82b9c063c10f 100644 > --- a/drivers/dma/dw-edma/dw-edma-v0-core.c > +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c > @@ -509,6 +509,16 @@ static void dw_edma_v0_core_debugfs_on(struct dw_edma > *dw) > dw_edma_v0_debugfs_on(dw); > } > > +static void dw_edma_v0_core_ack_emulated_irq(struct dw_edma *dw) > +{ > + /* > + * Interrupt emulation may assert the IRQ without setting > + * DONE/ABORT status bits. A zero write to INT_CLEAR deasserts the > + * emulated IRQ, while being a no-op for real interrupts. > + */ > + SET_BOTH_32(dw, int_clear, 0); > +} > + > static const struct dw_edma_core_ops dw_edma_v0_core = { > .off = dw_edma_v0_core_off, > .ch_count = dw_edma_v0_core_ch_count, > @@ -517,6 +527,7 @@ static const struct dw_edma_core_ops dw_edma_v0_core = { > .start = dw_edma_v0_core_start, > .ch_config = dw_edma_v0_core_ch_config, > .debugfs_on = dw_edma_v0_core_debugfs_on, > + .ack_emulated_irq = dw_edma_v0_core_ack_emulated_irq, > }; > > void dw_edma_v0_core_register(struct dw_edma *dw) > -- > 2.51.0 >

