DesignWare EP eDMA can generate interrupts both locally and remotely
(LIE/RIE). Remote eDMA users need to decide, per channel, whether
completions should be handled locally, remotely, or both. Unless
carefully configured, the endpoint and host would race to ack the
interrupt.
Introduce a dw_edma_peripheral_config that holds per-channel interrupt
routing mode. 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 peripheral_config is explicitly
used and set, no functional changes.
For now, HDMA is not supported for the peripheral_config. Until the
support is implemented and validated, explicitly reject it for HDMA to
avoid silently misconfiguring interrupt routing.
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
drivers/dma/dw-edma/dw-edma-core.c | 37 +++++++++++++++++++++++++++
drivers/dma/dw-edma/dw-edma-core.h | 13 ++++++++++
drivers/dma/dw-edma/dw-edma-v0-core.c | 26 +++++++++++++------
include/linux/dma/edma.h | 28 ++++++++++++++++++++
4 files changed, 96 insertions(+), 8 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 8e5f7defa6b6..0c3461f9174a 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -219,11 +219,47 @@ static void dw_edma_device_caps(struct dma_chan *dchan,
}
}
+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_peripheral_config *pcfg;
+
+ /* peripheral_config is optional, default keeps legacy behaviour. */
+ *mode = DW_EDMA_CH_IRQ_DEFAULT;
+ 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;
+ switch (pcfg->irq_mode) {
+ case DW_EDMA_CH_IRQ_DEFAULT:
+ case DW_EDMA_CH_IRQ_LOCAL:
+ case DW_EDMA_CH_IRQ_REMOTE:
+ *mode = pcfg->irq_mode;
+ 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;
+ int ret;
+
+ ret = dw_edma_parse_irq_mode(chan, config, &mode);
+ if (ret)
+ return ret;
+ chan->irq_mode = mode;
memcpy(&chan->config, config, sizeof(*config));
chan->configured = true;
@@ -749,6 +785,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);
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index 71894b9e0b15..0608b9044a08 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;
@@ -206,4 +208,15 @@ void dw_edma_core_debugfs_on(struct dw_edma *dw)
dw->core->debugfs_on(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 b75fdaffad9a..a0441e8aa3b3 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_core_ch_ignore_irq(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_core_ch_ignore_irq(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 3080747689f6..53b31a974331 100644
--- a/include/linux/dma/edma.h
+++ b/include/linux/dma/edma.h
@@ -60,6 +60,34 @@ 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, local interrupt unmasked
+ * @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,
+};
+
+/**
+ * struct dw_edma_peripheral_config - dw-edma specific slave configuration
+ * @irq_mode: per-channel interrupt routing control.
+ *
+ * Pass this structure via dma_slave_config.peripheral_config and
+ * dma_slave_config.peripheral_size.
+ */
+struct dw_edma_peripheral_config {
+ enum dw_edma_ch_irq_mode irq_mode;
+};
+
/**
* struct dw_edma_chip - representation of DesignWare eDMA controller hardware
* @dev: struct device of the eDMA controller
--
2.51.0
On Sat, Feb 07, 2026 at 02:26:38AM +0900, Koichiro Den wrote:
> DesignWare EP eDMA can generate interrupts both locally and remotely
> (LIE/RIE). Remote eDMA users need to decide, per channel, whether
> completions should be handled locally, remotely, or both. Unless
> carefully configured, the endpoint and host would race to ack the
> interrupt.
>
> Introduce a dw_edma_peripheral_config that holds per-channel interrupt
> routing mode. 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 peripheral_config is explicitly
> used and set, no functional changes.
>
> For now, HDMA is not supported for the peripheral_config. Until the
> support is implemented and validated, explicitly reject it for HDMA to
> avoid silently misconfiguring interrupt routing.
>
> Signed-off-by: Koichiro Den <den@valinux.co.jp>
> ---
> drivers/dma/dw-edma/dw-edma-core.c | 37 +++++++++++++++++++++++++++
> drivers/dma/dw-edma/dw-edma-core.h | 13 ++++++++++
> drivers/dma/dw-edma/dw-edma-v0-core.c | 26 +++++++++++++------
> include/linux/dma/edma.h | 28 ++++++++++++++++++++
> 4 files changed, 96 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> index 8e5f7defa6b6..0c3461f9174a 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-core.c
> @@ -219,11 +219,47 @@ static void dw_edma_device_caps(struct dma_chan *dchan,
> }
> }
>
> +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_peripheral_config *pcfg;
> +
> + /* peripheral_config is optional, default keeps legacy behaviour. */
> + *mode = DW_EDMA_CH_IRQ_DEFAULT;
> + 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;
> + switch (pcfg->irq_mode) {
> + case DW_EDMA_CH_IRQ_DEFAULT:
> + case DW_EDMA_CH_IRQ_LOCAL:
> + case DW_EDMA_CH_IRQ_REMOTE:
> + *mode = pcfg->irq_mode;
> + 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;
> + int ret;
> +
> + ret = dw_edma_parse_irq_mode(chan, config, &mode);
> + if (ret)
> + return ret;
>
> + chan->irq_mode = mode;
> memcpy(&chan->config, config, sizeof(*config));
> chan->configured = true;
>
> @@ -749,6 +785,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);
> diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
> index 71894b9e0b15..0608b9044a08 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;
> @@ -206,4 +208,15 @@ void dw_edma_core_debugfs_on(struct dw_edma *dw)
> dw->core->debugfs_on(dw);
> }
>
> +static inline
> +bool dw_edma_core_ch_ignore_irq(struct dw_edma_chan *chan)
nit:
static inline bool
dw_edma_core_ch_ignore_irq()
> +{
> + 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 b75fdaffad9a..a0441e8aa3b3 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_core_ch_ignore_irq(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_core_ch_ignore_irq(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 3080747689f6..53b31a974331 100644
> --- a/include/linux/dma/edma.h
> +++ b/include/linux/dma/edma.h
> @@ -60,6 +60,34 @@ 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, local interrupt unmasked
> + * @DW_EDMA_CH_IRQ_REMOTE: LIE=1/RIE=1, local interrupt masked
You'd better descript remote interrupt also.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> + *
> + * 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,
> +};
> +
> +/**
> + * struct dw_edma_peripheral_config - dw-edma specific slave configuration
> + * @irq_mode: per-channel interrupt routing control.
> + *
> + * Pass this structure via dma_slave_config.peripheral_config and
> + * dma_slave_config.peripheral_size.
> + */
> +struct dw_edma_peripheral_config {
> + enum dw_edma_ch_irq_mode irq_mode;
> +};
> +
> /**
> * struct dw_edma_chip - representation of DesignWare eDMA controller hardware
> * @dev: struct device of the eDMA controller
> --
> 2.51.0
>
On Fri, Feb 06, 2026 at 12:43:54PM -0500, Frank Li wrote:
> On Sat, Feb 07, 2026 at 02:26:38AM +0900, Koichiro Den wrote:
> > DesignWare EP eDMA can generate interrupts both locally and remotely
> > (LIE/RIE). Remote eDMA users need to decide, per channel, whether
> > completions should be handled locally, remotely, or both. Unless
> > carefully configured, the endpoint and host would race to ack the
> > interrupt.
> >
> > Introduce a dw_edma_peripheral_config that holds per-channel interrupt
> > routing mode. 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 peripheral_config is explicitly
> > used and set, no functional changes.
> >
> > For now, HDMA is not supported for the peripheral_config. Until the
> > support is implemented and validated, explicitly reject it for HDMA to
> > avoid silently misconfiguring interrupt routing.
> >
> > Signed-off-by: Koichiro Den <den@valinux.co.jp>
> > ---
> > drivers/dma/dw-edma/dw-edma-core.c | 37 +++++++++++++++++++++++++++
> > drivers/dma/dw-edma/dw-edma-core.h | 13 ++++++++++
> > drivers/dma/dw-edma/dw-edma-v0-core.c | 26 +++++++++++++------
> > include/linux/dma/edma.h | 28 ++++++++++++++++++++
> > 4 files changed, 96 insertions(+), 8 deletions(-)
> >
> > diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> > index 8e5f7defa6b6..0c3461f9174a 100644
> > --- a/drivers/dma/dw-edma/dw-edma-core.c
> > +++ b/drivers/dma/dw-edma/dw-edma-core.c
> > @@ -219,11 +219,47 @@ static void dw_edma_device_caps(struct dma_chan *dchan,
> > }
> > }
> >
> > +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_peripheral_config *pcfg;
> > +
> > + /* peripheral_config is optional, default keeps legacy behaviour. */
> > + *mode = DW_EDMA_CH_IRQ_DEFAULT;
> > + 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;
> > + switch (pcfg->irq_mode) {
> > + case DW_EDMA_CH_IRQ_DEFAULT:
> > + case DW_EDMA_CH_IRQ_LOCAL:
> > + case DW_EDMA_CH_IRQ_REMOTE:
> > + *mode = pcfg->irq_mode;
> > + 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;
> > + int ret;
> > +
> > + ret = dw_edma_parse_irq_mode(chan, config, &mode);
> > + if (ret)
> > + return ret;
> >
> > + chan->irq_mode = mode;
> > memcpy(&chan->config, config, sizeof(*config));
> > chan->configured = true;
> >
> > @@ -749,6 +785,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);
> > diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
> > index 71894b9e0b15..0608b9044a08 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;
> > @@ -206,4 +208,15 @@ void dw_edma_core_debugfs_on(struct dw_edma *dw)
> > dw->core->debugfs_on(dw);
> > }
> >
> > +static inline
> > +bool dw_edma_core_ch_ignore_irq(struct dw_edma_chan *chan)
>
> nit:
>
> static inline bool
> dw_edma_core_ch_ignore_irq()
Thanks for the nit. I followed the style of the neighboring codes, but I
agree it's better on its own. Let me fix that in the next revision.
>
> > +{
> > + 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 b75fdaffad9a..a0441e8aa3b3 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_core_ch_ignore_irq(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_core_ch_ignore_irq(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 3080747689f6..53b31a974331 100644
> > --- a/include/linux/dma/edma.h
> > +++ b/include/linux/dma/edma.h
> > @@ -60,6 +60,34 @@ 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, local interrupt unmasked
> > + * @DW_EDMA_CH_IRQ_REMOTE: LIE=1/RIE=1, local interrupt masked
>
> You'd better descript remote interrupt also.
Thanks for the comment. Hm, while revisiting this I noticed the current
enum comment is overly LIE/RIE-specific and can even be misleading (e.g.
*_IRQ_DEFAULT does not imply a RIE=1 behavior when the eDMA instance is
operated purely in a local-only setup).
I think it’s clearer to describe the modes in terms of behavior and add a
short explanation of what each mode means outside the individual field
descriptions. Below is a draft of the updated kernel-doc comment. How does
this look to you?
/**
* 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.
*
* 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,
};
Also, for v5 I think I can drop this patch 1/9 to keep the series minimal
and focused on the doorbell fallback implementation (and its testing). I
can repost this patch later together with the actual consumer. Please let
me know if you have any objections.
If you're ok with the updated comment above, I believe it should also be
fine to keep your Reviewed-by tag on the repost.
Thanks for the review,
Koichiro
>
> Reviewed-by: Frank Li <Frank.Li@nxp.com>
> > + *
> > + * 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,
> > +};
> > +
> > +/**
> > + * struct dw_edma_peripheral_config - dw-edma specific slave configuration
> > + * @irq_mode: per-channel interrupt routing control.
> > + *
> > + * Pass this structure via dma_slave_config.peripheral_config and
> > + * dma_slave_config.peripheral_size.
> > + */
> > +struct dw_edma_peripheral_config {
> > + enum dw_edma_ch_irq_mode irq_mode;
> > +};
> > +
> > /**
> > * struct dw_edma_chip - representation of DesignWare eDMA controller hardware
> > * @dev: struct device of the eDMA controller
> > --
> > 2.51.0
> >
© 2016 - 2026 Red Hat, Inc.