[PATCH v2 02/12] dmaengine: dw-edma: Add per-channel interrupt routing control
From: Koichiro Den
Date: Mon May 25 2026 - 02:25:09 EST
DesignWare eDMA can signal completion locally through edma_int[] and
remotely through IMWr/MSI. When channels are delegated to a remote
frontend, the local endpoint side and the remote host side must not both
service the same DONE/ABORT status.
Add dw_edma_irq_config, carried through dma_slave_config, so a frontend
can choose default, local, or remote IRQ handling per channel. Update the
v0 path so linked-list interrupt generation and DONE/ABORT masking follow
the selected mode. If a frontend does not supply the config, keep the
existing behavior.
HDMA native already uses dma_slave_config.peripheral_config as an int for
non-LL mode selection. Keep that interface unchanged and reject the new
IRQ config there until an IRQ routing model is implemented and validated.
Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
Changes in v2:
- Rename dw_edma_peripheral_config to dw_edma_irq_config.
- Keep the IRQ config distinct from HDMA native's int config.
- Reject remote-only IRQ mode on local instances.
- Report IRQ_HANDLED only after servicing non-ignored status.
- Drop the lockless irq_mode reset in free_chan_resources().
- Revise the commit message.
- Drop Frank's Reviewed-by due to the above changes.
drivers/dma/dw-edma/dw-edma-core.c | 66 +++++++++++++++++++++++++--
drivers/dma/dw-edma/dw-edma-core.h | 13 ++++++
drivers/dma/dw-edma/dw-edma-v0-core.c | 22 ++++++---
include/linux/dma/edma.h | 40 ++++++++++++++++
4 files changed, 131 insertions(+), 10 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 80b4a168225b..a70e0640d082 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -219,12 +219,66 @@ static void dw_edma_device_caps(struct dma_chan *dchan,
}
}
+static enum dw_edma_ch_irq_mode
+dw_edma_get_default_irq_mode(struct dw_edma_chan *chan)
+{
+ switch (chan->dw->chip->default_irq_mode) {
+ case DW_EDMA_CH_IRQ_DEFAULT:
+ case DW_EDMA_CH_IRQ_LOCAL:
+ return chan->dw->chip->default_irq_mode;
+ case DW_EDMA_CH_IRQ_REMOTE:
+ if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
+ return DW_EDMA_CH_IRQ_REMOTE;
+ return DW_EDMA_CH_IRQ_DEFAULT;
+ default:
+ return DW_EDMA_CH_IRQ_DEFAULT;
+ }
+}
+
+static int dw_edma_parse_irq_mode(struct dw_edma_chan *chan,
+ const struct dma_slave_config *config,
+ enum dw_edma_ch_irq_mode *mode)
+{
+ const struct dw_edma_irq_config *pcfg;
+
+ /* peripheral_config is optional, fall back to the frontend default. */
+ *mode = dw_edma_get_default_irq_mode(chan);
+ if (!config || !config->peripheral_config)
+ return 0;
+
+ if (chan->dw->chip->mf == EDMA_MF_HDMA_NATIVE)
+ return -EOPNOTSUPP;
+
+ if (config->peripheral_size != sizeof(*pcfg))
+ return -EINVAL;
+
+ pcfg = config->peripheral_config;
+ if (pcfg->reserved)
+ return -EINVAL;
+
+ switch (pcfg->irq_mode) {
+ case DW_EDMA_CH_IRQ_DEFAULT:
+ case DW_EDMA_CH_IRQ_LOCAL:
+ *mode = pcfg->irq_mode;
+ return 0;
+ case DW_EDMA_CH_IRQ_REMOTE:
+ if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL)
+ return -EINVAL;
+ *mode = DW_EDMA_CH_IRQ_REMOTE;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
static int dw_edma_device_config(struct dma_chan *dchan,
struct dma_slave_config *config)
{
struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ enum dw_edma_ch_irq_mode mode;
bool cfg_non_ll;
int non_ll = 0;
+ int ret;
chan->non_ll = false;
if (chan->dw->chip->mf == EDMA_MF_HDMA_NATIVE) {
@@ -255,10 +309,11 @@ static int dw_edma_device_config(struct dma_chan *dchan,
if (cfg_non_ll || non_ll)
chan->non_ll = true;
- } else if (config->peripheral_config) {
- dev_err(dchan->device->dev,
- "peripheral config param applicable only for HDMA\n");
- return -EINVAL;
+ } else {
+ ret = dw_edma_parse_irq_mode(chan, config, &mode);
+ if (ret)
+ return ret;
+ chan->irq_mode = mode;
}
memcpy(&chan->config, config, sizeof(*config));
@@ -853,6 +908,8 @@ static int dw_edma_alloc_chan_resources(struct dma_chan *dchan)
if (chan->status != EDMA_ST_IDLE)
return -EBUSY;
+ chan->irq_mode = dw_edma_get_default_irq_mode(chan);
+
return 0;
}
@@ -904,6 +961,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_get_default_irq_mode(chan);
if (chan->dir == EDMA_DIR_WRITE)
chan->ll_max = (chip->ll_region_wr[chan->id].sz / EDMA_LL_SZ);
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index 902574b1ba86..e2aadf0109b6 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;
@@ -224,4 +226,15 @@ dw_edma_core_db_offset(struct dw_edma *dw)
return dw->core->db_offset(dw);
}
+static inline bool
+dw_edma_core_ch_ignore_irq(struct dw_edma_chan *chan)
+{
+ struct dw_edma *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;
+}
+
#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 69e8279adec8..08ec2bd7856e 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -256,9 +256,11 @@ 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];
+ if (dw_edma_core_ch_ignore_irq(chan))
+ continue;
+
dw_edma_v0_core_clear_done_int(chan);
done(chan);
-
ret = IRQ_HANDLED;
}
@@ -267,9 +269,11 @@ 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];
+ if (dw_edma_core_ch_ignore_irq(chan))
+ continue;
+
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 3e15cf83b784..2bf2298711e1 100644
--- a/include/linux/dma/edma.h
+++ b/include/linux/dma/edma.h
@@ -60,6 +60,43 @@ 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: keep legacy behavior
+ * @DW_EDMA_CH_IRQ_LOCAL: local interrupt only (edma_int[])
+ * @DW_EDMA_CH_IRQ_REMOTE: remote interrupt only (IMWr/MSI),
+ * while masking local DONE/ABORT output.
+ *
+ * DesignWare EP eDMA can signal interrupts locally through the edma_int[]
+ * bus, and remotely using posted memory writes (IMWr) that may be
+ * interpreted as MSI/MSI-X by the RC.
+ *
+ * For the v0 eDMA programming path, DMA_*_INT_MASK gates the local edma_int[]
+ * assertion, while there is no dedicated per-channel mask for IMWr generation.
+ * To request a remote-only interrupt, Synopsys recommends setting both LIE and
+ * RIE, and masking the local interrupt in DMA_*_INT_MASK (rather than relying
+ * on LIE=0/RIE=1). See the DesignWare endpoint databook 5.40a, Non Linked
+ * List Mode interrupt handling ("Hint").
+ */
+enum dw_edma_ch_irq_mode {
+ DW_EDMA_CH_IRQ_DEFAULT = 0,
+ DW_EDMA_CH_IRQ_LOCAL,
+ DW_EDMA_CH_IRQ_REMOTE,
+};
+
+/**
+ * struct dw_edma_irq_config - dw-edma interrupt routing configuration
+ * @irq_mode: per-channel interrupt routing control.
+ * @reserved: must be zero.
+ *
+ * Pass this structure via dma_slave_config.peripheral_config and
+ * dma_slave_config.peripheral_size.
+ */
+struct dw_edma_irq_config {
+ enum dw_edma_ch_irq_mode irq_mode;
+ u32 reserved;
+};
+
/**
* struct dw_edma_chip - representation of DesignWare eDMA controller hardware
* @dev: struct device of the eDMA controller
@@ -76,6 +113,8 @@ enum dw_edma_chip_flags {
* @db_irq: Virtual IRQ dedicated to interrupt emulation
* @db_offset: Offset from DMA register base
* @mf: DMA register map format
+ * @default_irq_mode: default per-channel interrupt routing when client
+ * does not supply dw_edma_irq_config
* @dw: struct dw_edma that is filled by dw_edma_probe()
*/
struct dw_edma_chip {
@@ -101,6 +140,7 @@ struct dw_edma_chip {
resource_size_t db_offset;
enum dw_edma_map_format mf;
+ enum dw_edma_ch_irq_mode default_irq_mode;
struct dw_edma *dw;
bool cfg_non_ll;
--
2.51.0