[RFC PATCH v3 18/35] dmaengine: dw-edma: Add per-channel interrupt routing mode
From: Koichiro Den <hidden>
Date: 2025-12-17 16:51:06
Also in:
dmaengine, linux-pci, linux-renesas-soc, lkml
Subsystem:
designware edma core ip driver, dma generic offload engine subsystem, the rest · Maintainers:
Manivannan Sadhasivam, Vinod Koul, Linus Torvalds
DesignWare eDMA linked-list mode supports both local and remote completion interrupts (LIE/RIE). For remote eDMA users, we need to decide per-channel whether completion should be handled locally, remotely, or both. Introduce a per-channel interrupt routing mode and export a small API to configure/query it. Update v0 programming so that RIE and local done/abort interrupt masking follow the selected mode. The default mode keeps the original behavior, so unless the new APIs are explicitly used, no functional changes. Signed-off-by: Koichiro Den <redacted> --- drivers/dma/dw-edma/dw-edma-core.c | 49 +++++++++++++++++++++++++++ drivers/dma/dw-edma/dw-edma-core.h | 2 ++ drivers/dma/dw-edma/dw-edma-v0-core.c | 26 +++++++++----- include/linux/dma/edma.h | 46 +++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 8 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 1b935da65d05..0bceca2d56c5 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c@@ -765,6 +765,7 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc) chan->configured = false; chan->request = EDMA_REQ_NONE; chan->status = EDMA_ST_IDLE; + chan->irq_mode = DW_EDMA_CH_IRQ_DEFAULT; if (chan->dir == EDMA_DIR_WRITE) chan->ll_max = (chip->ll_region_wr[chan->id].sz / EDMA_LL_SZ);
@@ -1059,6 +1060,54 @@ int dw_edma_remove(struct dw_edma_chip *chip) } EXPORT_SYMBOL_GPL(dw_edma_remove); +int dw_edma_chan_irq_config(struct dma_chan *dchan, + enum dw_edma_ch_irq_mode mode) +{ + struct dw_edma_chan *chan; + + /* Only LOCAL/REMOTE bits are valid. Zero keeps legacy behaviour. */ + if (mode & ~(DW_EDMA_CH_IRQ_LOCAL | DW_EDMA_CH_IRQ_REMOTE)) + return -EINVAL; + + if (!dchan || !dchan->device || + dchan->device->device_prep_slave_sg_config != dw_edma_device_prep_slave_sg_config) + return -ENODEV; + + chan = dchan2dw_edma_chan(dchan); + if (!chan) + return -ENODEV; + + chan->irq_mode = mode; + + dev_vdbg(chan->dw->chip->dev, "Channel: %s[%u] set irq_mode=%u\n", + str_write_read(chan->dir == EDMA_DIR_WRITE), + chan->id, mode); + + return 0; +} +EXPORT_SYMBOL_GPL(dw_edma_chan_irq_config); + +bool dw_edma_chan_ignore_irq(struct dma_chan *dchan) +{ + struct dw_edma_chan *chan; + struct dw_edma *dw; + + if (!dchan || !dchan->device || + dchan->device->device_prep_slave_sg_config != dw_edma_device_prep_slave_sg_config) + return false; + + chan = dchan2dw_edma_chan(dchan); + if (!chan) + return false; + + dw = chan->dw; + if (dw->chip->flags & DW_EDMA_CHIP_LOCAL) + return chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE; + else + return chan->irq_mode == DW_EDMA_CH_IRQ_LOCAL; +} +EXPORT_SYMBOL_GPL(dw_edma_chan_ignore_irq); + MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Synopsys DesignWare eDMA controller core driver"); MODULE_AUTHOR("Gustavo Pimentel <gustavo.pimentel@synopsys.com>");
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index 71894b9e0b15..8458d676551a 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h@@ -81,6 +81,8 @@ struct dw_edma_chan { struct msi_msg msi; + enum dw_edma_ch_irq_mode irq_mode; + enum dw_edma_request request; enum dw_edma_status status; u8 configured;
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index b75fdaffad9a..42a254eb9379 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c@@ -256,8 +256,10 @@ dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir, for_each_set_bit(pos, &val, total) { chan = &dw->chan[pos + off]; - dw_edma_v0_core_clear_done_int(chan); - done(chan); + if (!dw_edma_chan_ignore_irq(&chan->vc.chan)) { + dw_edma_v0_core_clear_done_int(chan); + done(chan); + } ret = IRQ_HANDLED; }
@@ -267,8 +269,10 @@ dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir, for_each_set_bit(pos, &val, total) { chan = &dw->chan[pos + off]; - dw_edma_v0_core_clear_abort_int(chan); - abort(chan); + if (!dw_edma_chan_ignore_irq(&chan->vc.chan)) { + dw_edma_v0_core_clear_abort_int(chan); + abort(chan); + } ret = IRQ_HANDLED; }
@@ -331,7 +335,8 @@ static void dw_edma_v0_core_write_chunk(struct dw_edma_chunk *chunk) j--; if (!j) { control |= DW_EDMA_V0_LIE; - if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL)) + if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) && + chan->irq_mode != DW_EDMA_CH_IRQ_LOCAL) control |= DW_EDMA_V0_RIE; }
@@ -407,10 +412,15 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first) break; } } - /* Interrupt unmask - done, abort */ + /* Interrupt mask/unmask - done, abort */ tmp = GET_RW_32(dw, chan->dir, int_mask); - tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id)); - tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id)); + if (chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE) { + tmp |= FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id)); + tmp |= FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id)); + } else { + tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id)); + tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id)); + } SET_RW_32(dw, chan->dir, int_mask, tmp); /* Linked list error */ tmp = GET_RW_32(dw, chan->dir, linked_list_err_en);
diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h
index 11d6eeb19fff..8c1b1d25fa44 100644
--- a/include/linux/dma/edma.h
+++ b/include/linux/dma/edma.h@@ -61,6 +61,40 @@ enum dw_edma_chip_flags { DW_EDMA_CHIP_LOCAL = BIT(0), }; +/* + * enum dw_edma_ch_irq_mode - per-channel interrupt routing control + * @DW_EDMA_CH_IRQ_DEFAULT: LIE=1/RIE=1, local interrupt unmasked + * @DW_EDMA_CH_IRQ_LOCAL: LIE=1/RIE=0 + * @DW_EDMA_CH_IRQ_REMOTE: LIE=1/RIE=1, local interrupt masked + * + * Some implementations require using LIE=1/RIE=1 with the local interrupt + * masked to generate a remote-only interrupt (rather than LIE=0/RIE=1). + * See the DesignWare endpoint databook 5.40, "Hint" below "Figure 8-22 + * Write Interrupt Generation". + */ +enum dw_edma_ch_irq_mode { + DW_EDMA_CH_IRQ_DEFAULT = 0, + DW_EDMA_CH_IRQ_LOCAL, + DW_EDMA_CH_IRQ_REMOTE, +}; + +/** + * dw_edma_chan_irq_config - configure per-channel interrupt routing + * @chan: DMA channel obtained from dma_request_channel() + * @mode: interrupt routing mode + * + * Returns 0 on success, -EINVAL for invalid @mode, or -ENODEV if @chan does + * not belong to the DesignWare eDMA driver. + */ +int dw_edma_chan_irq_config(struct dma_chan *chan, + enum dw_edma_ch_irq_mode mode); + +/** + * dw_edma_chan_ignore_irq - tell whether local IRQ handling should be ignored + * @chan: DMA channel obtained from dma_request_channel() + */ +bool dw_edma_chan_ignore_irq(struct dma_chan *chan); + #if IS_REACHABLE(CONFIG_PCIE_DW) /** * dw_edma_get_reg_window - get eDMA register base and size
@@ -141,4 +175,16 @@ static inline int dw_edma_remove(struct dw_edma_chip *chip) } #endif /* CONFIG_DW_EDMA */ +#if !IS_ENABLED(CONFIG_DW_EDMA) +static inline int dw_edma_chan_irq_config(struct dma_chan *chan, + enum dw_edma_ch_irq_mode mode) +{ + return -ENODEV; +} +static inline bool dw_edma_chan_ignore_irq(struct dma_chan *chan) +{ + return false; +} +#endif + #endif /* _DW_EDMA_H */
--
2.51.0