mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
dmaengine: dw-edma: Configure remote interrupt routing
An endpoint function can reserve an endpoint-local channel while the RC programs it through an exposed register window. Such a channel must route interrupts remotely and ignore them on the endpoint. Use dma_slave_config to set per-channel interrupt routing on idle channels of a local eDMA or HDMA instance. Releasing a remote-routed channel quiesces the hardware and drains its local IRQ before restoring default routing. The eDMA quiesce may stop a complete direction. The caller must own every channel in that direction and stop remote programming first. Suggested-by: Frank Li <Frank.Li@nxp.com> Signed-off-by: Koichiro Den <den@valinux.co.jp> Reviewed-by: Frank Li <Frank.Li@nxp.com> Link: https://patch.msgid.link/20260912174023.1227397-3-den@valinux.co.jp Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
parent
f54701da5c
commit
9d31ad3b33
|
|
@ -309,48 +309,76 @@ dw_edma_get_default_irq_mode(struct dw_edma_chan *chan)
|
|||
DW_EDMA_CH_IRQ_REMOTE;
|
||||
}
|
||||
|
||||
static int dw_edma_device_config_irq_mode(struct dw_edma_chan *chan,
|
||||
enum dw_edma_ch_irq_mode mode)
|
||||
{
|
||||
if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) ||
|
||||
(mode != DW_EDMA_CH_IRQ_LOCAL && mode != DW_EDMA_CH_IRQ_REMOTE))
|
||||
return -EINVAL;
|
||||
|
||||
guard(spinlock_irqsave)(&chan->vc.lock);
|
||||
|
||||
if (chan->status != EDMA_ST_IDLE || chan->request != EDMA_REQ_NONE)
|
||||
return -EBUSY;
|
||||
|
||||
/* IRQ routing cannot change after the initial configuration. */
|
||||
if (chan->irq_mode == mode)
|
||||
return 0;
|
||||
|
||||
if (chan->configured)
|
||||
return -EBUSY;
|
||||
|
||||
chan->irq_mode = mode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_edma_device_config(struct dma_chan *dchan,
|
||||
struct dma_slave_config *config)
|
||||
{
|
||||
const struct dw_edma_chan_config *dw_config = config->peripheral_config;
|
||||
struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
|
||||
bool cfg_non_ll;
|
||||
int non_ll = 0;
|
||||
bool non_ll = false;
|
||||
u32 flags = 0;
|
||||
int ret;
|
||||
|
||||
chan->non_ll = false;
|
||||
if (chan->dw->chip->mf == EDMA_MF_HDMA_NATIVE) {
|
||||
if (config->peripheral_config &&
|
||||
config->peripheral_size != sizeof(int)) {
|
||||
dev_err(dchan->device->dev,
|
||||
"config param peripheral size mismatch\n");
|
||||
if (dw_config) {
|
||||
if (config->peripheral_size != sizeof(*dw_config) ||
|
||||
dw_config->flags & ~(DW_EDMA_CH_CONFIG_NON_LL |
|
||||
DW_EDMA_CH_CONFIG_IRQ_MODE))
|
||||
return -EINVAL;
|
||||
flags = dw_config->flags;
|
||||
}
|
||||
|
||||
/*
|
||||
* When there is no valid LLP base address available then the
|
||||
* default DMA ops will use the non-LL mode.
|
||||
*
|
||||
* When LL mode is the default, clients can request non-LL mode
|
||||
* through DW_EDMA_CH_CONFIG_NON_LL.
|
||||
*/
|
||||
non_ll = chan->dw->chip->mf == EDMA_MF_HDMA_NATIVE &&
|
||||
chan->dw->chip->cfg_non_ll;
|
||||
|
||||
if (flags & DW_EDMA_CH_CONFIG_NON_LL) {
|
||||
if (chan->dw->chip->mf != EDMA_MF_HDMA_NATIVE)
|
||||
return -EINVAL;
|
||||
|
||||
if (chan->dw->chip->cfg_non_ll && !dw_config->non_ll) {
|
||||
dev_err(dchan->device->dev, "invalid configuration\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* When there is no valid LLP base address available then the
|
||||
* default DMA ops will use the non-LL mode.
|
||||
*
|
||||
* Cases where LL mode is enabled and client wants to use the
|
||||
* non-LL mode then also client can do so via providing the
|
||||
* peripheral_config param.
|
||||
*/
|
||||
cfg_non_ll = chan->dw->chip->cfg_non_ll;
|
||||
if (config->peripheral_config) {
|
||||
non_ll = *(int *)config->peripheral_config;
|
||||
|
||||
if (cfg_non_ll && !non_ll) {
|
||||
dev_err(dchan->device->dev, "invalid configuration\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
non_ll = dw_config->non_ll;
|
||||
}
|
||||
|
||||
if (flags & DW_EDMA_CH_CONFIG_IRQ_MODE) {
|
||||
ret = dw_edma_device_config_irq_mode(chan, dw_config->irq_mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
chan->non_ll = non_ll;
|
||||
memcpy(&chan->config, config, sizeof(*config));
|
||||
chan->configured = true;
|
||||
|
||||
|
|
@ -1014,11 +1042,53 @@ static void dw_edma_wait_termination(struct dma_chan *dchan)
|
|||
"timeout waiting for channel termination\n");
|
||||
}
|
||||
|
||||
static void dw_edma_synchronize_chan_irq(struct dw_edma_chan *chan)
|
||||
{
|
||||
struct dw_edma *dw = chan->dw;
|
||||
unsigned long *mask;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* A shared handler may retain this channel's status across quiesce.
|
||||
* With nr_irqs == 1, it scans both directions even if routing and
|
||||
* delegation are direction-wide. Drain it before allowing a routing change.
|
||||
*/
|
||||
for (i = 0; i < dw->nr_irqs; i++) {
|
||||
mask = chan->dir == EDMA_DIR_WRITE ? dw->irq[i].wr_mask :
|
||||
dw->irq[i].rd_mask;
|
||||
if (!test_bit(chan->id, mask))
|
||||
continue;
|
||||
|
||||
synchronize_irq(dw->chip->ops->irq_vector(dw->chip->dev, i));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void dw_edma_device_synchronize(struct dma_chan *dchan)
|
||||
{
|
||||
struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
|
||||
bool remote;
|
||||
|
||||
/*
|
||||
* irq_mode is fixed after initial configuration. The free path
|
||||
* restores it only after synchronization.
|
||||
*/
|
||||
remote = chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL &&
|
||||
chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE;
|
||||
|
||||
/*
|
||||
* Peer-driven transfers bypass local descriptor tracking, so quiesce
|
||||
* the hardware explicitly.
|
||||
*/
|
||||
if (remote && dw_edma_core_ch_quiesce(chan))
|
||||
dev_warn(chan->dw->chip->dev,
|
||||
"failed to quiesce remote-routed %s channel %u\n",
|
||||
chan->dir == EDMA_DIR_WRITE ? "write" : "read",
|
||||
chan->id);
|
||||
|
||||
dw_edma_wait_termination(dchan);
|
||||
if (remote)
|
||||
dw_edma_synchronize_chan_irq(chan);
|
||||
cancel_work_sync(&chan->irq_work);
|
||||
atomic_set(&chan->irq_pending, 0);
|
||||
vchan_synchronize(&chan->vc);
|
||||
|
|
@ -1031,8 +1101,10 @@ static void dw_edma_free_chan_resources(struct dma_chan *dchan)
|
|||
dw_edma_device_terminate_all(dchan);
|
||||
dw_edma_device_synchronize(dchan);
|
||||
|
||||
scoped_guard(spinlock_irqsave, &chan->vc.lock)
|
||||
scoped_guard(spinlock_irqsave, &chan->vc.lock) {
|
||||
chan->configured = false;
|
||||
chan->irq_mode = dw_edma_get_default_irq_mode(chan);
|
||||
}
|
||||
|
||||
vchan_free_chan_resources(&chan->vc);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,27 @@ enum dw_edma_ch_irq_mode {
|
|||
DW_EDMA_CH_IRQ_REMOTE,
|
||||
};
|
||||
|
||||
#define DW_EDMA_CH_CONFIG_NON_LL BIT(0)
|
||||
#define DW_EDMA_CH_CONFIG_IRQ_MODE BIT(1)
|
||||
|
||||
/**
|
||||
* struct dw_edma_chan_config - dw-edma channel configuration
|
||||
* @flags: fields selected by DW_EDMA_CH_CONFIG_*
|
||||
* @non_ll: use HDMA non-linked-list mode
|
||||
* @irq_mode: interrupt routing mode
|
||||
*
|
||||
* Pass this structure through dma_slave_config.peripheral_config. Before
|
||||
* synchronizing a remote-routed channel, the client must stop remote
|
||||
* programming and own every channel affected by the hardware quiesce: the
|
||||
* entire direction for eDMA-compatible layouts, or the individual channel for
|
||||
* native HDMA.
|
||||
*/
|
||||
struct dw_edma_chan_config {
|
||||
u32 flags;
|
||||
bool non_ll;
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user