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_selfirq(), 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]> --- drivers/dma/dw-edma/dw-edma-core.c | 48 ++++++++++++++++++++++++--- drivers/dma/dw-edma/dw-edma-core.h | 11 ++++++ drivers/dma/dw-edma/dw-edma-v0-core.c | 11 ++++++ 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index 0c3461f9174a..dd01a9aa8ad8 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -699,7 +699,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_selfirq(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; @@ -708,7 +725,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; @@ -717,12 +734,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 0608b9044a08..abc97e375484 100644 --- a/drivers/dma/dw-edma/dw-edma-core.h +++ b/drivers/dma/dw-edma/dw-edma-core.h @@ -128,6 +128,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_selfirq)(struct dw_edma *dw); }; struct dw_edma_sg { @@ -208,6 +209,16 @@ void dw_edma_core_debugfs_on(struct dw_edma *dw) dw->core->debugfs_on(dw); } +static inline +int dw_edma_core_ack_selfirq(struct dw_edma *dw) +{ + if (!dw->core->ack_selfirq) + return -EOPNOTSUPP; + + dw->core->ack_selfirq(dw); + return 0; +} + static inline bool dw_edma_core_ch_ignore_irq(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 a0441e8aa3b3..68e0d088570d 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-core.c +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c @@ -519,6 +519,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_selfirq(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, @@ -527,6 +537,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_selfirq = dw_edma_v0_core_ack_selfirq, }; void dw_edma_v0_core_register(struct dw_edma *dw) -- 2.51.0

