Re: [PATCH 04/15] dmaengine: dw-edma: Add per-channel interrupt routing control
Frank Li <[email protected]> Thu, 12 Mar 2026 16:22:06 -0400
| Newsgroups | dev.linux.lists.ntb,org.kernel.vger.dmaengine,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pci |
|---|---|
| Message-ID | <abMgboE6g2DY7vAh@lizhi-Precision-Tower-5810> |
On Fri, Mar 13, 2026 at 01:49:54AM +0900, Koichiro Den wrote: > DesignWare endpoint eDMA can signal completion both locally and > remotely through LIE/RIE. A remotely controlled channel needs a > per-channel policy for whether completions are handled locally, > remotely, or both; otherwise the endpoint and host can race to > acknowledge the interrupt. > > Add dw_edma_peripheral_config, carried through dma_slave_config, to let > a frontend select the interrupt routing mode for each channel. Update > the v0 programming path so linked-list interrupt generation and > DONE/ABORT masking follow the selected mode. If a frontend does nothing, > the default keeps the existing behavior. > > For now reject the new peripheral_config on HDMA, where the routing > model has not been implemented or validated yet, instead of silently > misprogramming interrupts. > > Signed-off-by: Koichiro Den <[email protected]> > --- Reviewed-by: Frank Li <[email protected]> > drivers/dma/dw-edma/dw-edma-core.c | 55 +++++++++++++++++++++++++++ > drivers/dma/dw-edma/dw-edma-core.h | 13 +++++++ > drivers/dma/dw-edma/dw-edma-v0-core.c | 26 +++++++++---- > include/linux/dma/edma.h | 38 ++++++++++++++++++ > 4 files changed, 124 insertions(+), 8 deletions(-) > > diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c > index a13beacce2e7..6341bda4c303 100644 > --- a/drivers/dma/dw-edma/dw-edma-core.c > +++ b/drivers/dma/dw-edma/dw-edma-core.c > @@ -219,11 +219,60 @@ 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: > + case DW_EDMA_CH_IRQ_REMOTE: > + return chan->dw->chip->default_irq_mode; > + 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_peripheral_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; > + 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; > > @@ -808,11 +857,14 @@ 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; > } > > static void dw_edma_free_chan_resources(struct dma_chan *dchan) > { > + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); > unsigned long timeout = jiffies + msecs_to_jiffies(5000); > int ret; > > @@ -826,6 +878,8 @@ static void dw_edma_free_chan_resources(struct dma_chan *dchan) > > cpu_relax(); > } > + > + chan->irq_mode = dw_edma_get_default_irq_mode(chan); > } > > static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc) > @@ -860,6 +914,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 59b24973fa7d..e021551b0b9f 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; > @@ -223,4 +225,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..2e95da0d6fc2 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 0b861e8d305e..e4a6302bd04c 100644 > --- a/include/linux/dma/edma.h > +++ b/include/linux/dma/edma.h > @@ -60,6 +60,41 @@ 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. > + * > + * 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_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 > @@ -76,6 +111,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_peripheral_config > * @dw: struct dw_edma that is filled by dw_edma_probe() > */ > struct dw_edma_chip { > @@ -105,6 +142,7 @@ struct dw_edma_chip { > int chan_ids_rd[EDMA_MAX_RD_CH]; > > enum dw_edma_map_format mf; > + enum dw_edma_ch_irq_mode default_irq_mode; > > struct dw_edma *dw; > }; > -- > 2.51.0 >