[PATCH 04/15] dmaengine: dw-edma: Add per-channel interrupt routing control
Koichiro Den <[email protected]> Fri, 13 Mar 2026 01:49:54 +0900
| 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 | <[email protected]> |
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]> --- 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