mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 12:44:03 +02:00
dmaengine: dw-edma: Add per-channel interrupt routing control
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 channel interrupt routing state and initialize it from the controller instance configuration. Update the eDMA and HDMA native paths so linked-list interrupt generation, HDMA non-linked-list interrupt enables, and DONE/ABORT masking follow the selected mode. For HDMA native non-linked-list channels, keep the local stop/abort enables set so status is latched. In remote mode, also enable remote signaling and mask the local interrupt pins. Keep the existing dw-edma-pcie host-side instances in remote interrupt routing mode so their IMWr/MSI completion model remains unchanged after local routing becomes the zero value. Note: - The routing mode describes where a channel should report completion. It does not by itself say whether this dw-edma instance owns the interrupt status. A local instance must ignore remote-only channels, and a remote instance must ignore local-only channels, even if such interrupts are unexpectedly delivered. Otherwise the non-owner side could steal the interrupt from the owner by clearing shared DONE/ABORT status. Cc: Devendra K Verma <devendra.verma@amd.com> Suggested-by: Frank Li <Frank.Li@nxp.com> Signed-off-by: Koichiro Den <den@valinux.co.jp> Link: https://patch.msgid.link/20260721062815.4117887-3-den@valinux.co.jp Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
parent
3e5933f200
commit
9c317842ec
|
|
@ -168,6 +168,15 @@ 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)
|
||||
{
|
||||
struct dw_edma_chip *chip = chan->dw->chip;
|
||||
|
||||
return chip->flags & DW_EDMA_CHIP_LOCAL ? DW_EDMA_CH_IRQ_LOCAL :
|
||||
DW_EDMA_CH_IRQ_REMOTE;
|
||||
}
|
||||
|
||||
static int dw_edma_device_config(struct dma_chan *dchan,
|
||||
struct dma_slave_config *config)
|
||||
{
|
||||
|
|
@ -931,6 +940,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);
|
||||
INIT_WORK(&chan->irq_work, dw_edma_irq_work);
|
||||
atomic_set(&chan->irq_pending, 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,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;
|
||||
|
|
@ -259,4 +261,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 */
|
||||
|
|
|
|||
|
|
@ -255,6 +255,9 @@ 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 (unlikely(dw_edma_core_ch_ignore_irq(chan)))
|
||||
continue;
|
||||
|
||||
dw_edma_v0_core_clear_done_int(chan);
|
||||
done(chan);
|
||||
|
||||
|
|
@ -266,6 +269,9 @@ 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 (unlikely(dw_edma_core_ch_ignore_irq(chan)))
|
||||
continue;
|
||||
|
||||
dw_edma_v0_core_clear_abort_int(chan);
|
||||
abort(chan);
|
||||
|
||||
|
|
@ -353,12 +359,17 @@ static void dw_edma_v0_core_ch_enable(struct dw_edma_chan *chan)
|
|||
break;
|
||||
}
|
||||
}
|
||||
/* Interrupt unmask - done, abort */
|
||||
/* Interrupt mask/unmask - done, abort */
|
||||
raw_spin_lock_irqsave(&dw->lock, flags);
|
||||
|
||||
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);
|
||||
|
|
@ -473,7 +484,18 @@ dw_edma_v0_core_ll_data(struct dw_edma_chan *chan, struct dw_edma_burst *burst,
|
|||
if (irq) {
|
||||
control |= DW_EDMA_V0_LIE;
|
||||
|
||||
if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
|
||||
/*
|
||||
* A local instance never issues transfers on a remote-routed
|
||||
* channel: on CHIP_LOCAL instances, REMOTE routing denotes a
|
||||
* channel handed over to the remote side, which programs the
|
||||
* linked list through its own instance. The remote-only
|
||||
* recipe (LIE|RIE with the local interrupt masked) is thus
|
||||
* applied by the instance that owns the transfer, and the
|
||||
* LIE-only write below never executes for a remote-routed
|
||||
* channel.
|
||||
*/
|
||||
if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) &&
|
||||
chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE)
|
||||
control |= DW_EDMA_V0_RIE;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,15 +51,22 @@ __dw_ch_regs(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch)
|
|||
|
||||
static u32 dw_hdma_v0_core_int_setup(struct dw_edma_chan *chan, u32 val)
|
||||
{
|
||||
if (chan->non_ll)
|
||||
val |= HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK;
|
||||
else
|
||||
val &= ~(HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
|
||||
val &= ~(HDMA_V0_LOCAL_ABORT_INT_EN | HDMA_V0_REMOTE_ABORT_INT_EN |
|
||||
HDMA_V0_LOCAL_STOP_INT_EN | HDMA_V0_REMOTE_STOP_INT_EN |
|
||||
HDMA_V0_ABORT_INT_MASK | HDMA_V0_STOP_INT_MASK);
|
||||
|
||||
val |= HDMA_V0_LOCAL_STOP_INT_EN | HDMA_V0_LOCAL_ABORT_INT_EN;
|
||||
if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
|
||||
val |= HDMA_V0_REMOTE_STOP_INT_EN |
|
||||
HDMA_V0_REMOTE_ABORT_INT_EN;
|
||||
/*
|
||||
* HDMA_INT_STATUS.STOP and .ABORT are latched only when LSIE and
|
||||
* LAIE are enabled. A remote handler needs those status bits to
|
||||
* identify the source of the IMWr, so keep local generation enabled
|
||||
* and mask the local interrupt pins instead.
|
||||
*/
|
||||
val |= HDMA_V0_LOCAL_ABORT_INT_EN | HDMA_V0_LOCAL_STOP_INT_EN;
|
||||
|
||||
if (chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE)
|
||||
val |= HDMA_V0_REMOTE_ABORT_INT_EN |
|
||||
HDMA_V0_REMOTE_STOP_INT_EN |
|
||||
HDMA_V0_ABORT_INT_MASK | HDMA_V0_STOP_INT_MASK;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
|
@ -158,6 +165,8 @@ dw_hdma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
|
|||
|
||||
for_each_set_bit(pos, mask, total) {
|
||||
chan = &dw->chan[pos + off];
|
||||
if (unlikely(dw_edma_core_ch_ignore_irq(chan)))
|
||||
continue;
|
||||
|
||||
val = dw_hdma_v0_core_status_int(chan);
|
||||
if (FIELD_GET(HDMA_V0_STOP_INT_MASK, val)) {
|
||||
|
|
|
|||
|
|
@ -62,6 +62,38 @@ 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_LOCAL: local interrupt only (edma_int[])
|
||||
* @DW_EDMA_CH_IRQ_REMOTE: remote interrupt only (IMWr/MSI), without
|
||||
* delivering local edma_int[].
|
||||
*
|
||||
* 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 linked-list 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. See the
|
||||
* DesignWare endpoint databook 6.30a, Linked List Mode interrupt handling
|
||||
* ("Software Programming of an Endpoint's LIE and RIE Bits for Linked List
|
||||
* Transfers", Attention).
|
||||
*
|
||||
* A local (DW_EDMA_CHIP_LOCAL) instance never issues transfers on a
|
||||
* remote-routed channel: REMOTE routing on such an instance denotes a channel
|
||||
* handed over to and driven by the remote side, and the recipe above is
|
||||
* applied by the driving instance.
|
||||
*
|
||||
* HDMA linked-list watermark interrupts have the same LWIE/RWIE guidance. HDMA
|
||||
* non-linked-list mode has dedicated local and remote stop/abort interrupt
|
||||
* enables.
|
||||
*/
|
||||
enum dw_edma_ch_irq_mode {
|
||||
DW_EDMA_CH_IRQ_LOCAL = 0,
|
||||
DW_EDMA_CH_IRQ_REMOTE,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dw_edma_chip - representation of DesignWare eDMA controller hardware
|
||||
* @dev: struct device of the eDMA controller
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user