mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 12:44:03 +02:00
dmaengine: dw-edma: Add partial channel ownership mode
A DesignWare eDMA instance may represent only a subset of channels that
is also initialized by another OS instance, such as an endpoint-side OS.
Add a partial ownership flag for instances that must preserve
controller-wide state owned by that peer.
In partial ownership mode, dw-edma skips the initial core reset and uses
the limited quiesce path in probe() and remove() instead of the full
core-off path. The flag also makes the driver validate the ownership
granularity required by each register layout before registering
channels.
Probe fails if the limited quiesce cannot stop its resources; remove
reports the error after completing the remaining driver teardown.
Partial instances also skip interrupt-emulation doorbell allocation: the
emulated doorbell is a controller-level resource, and a partial owner
must not claim it on behalf of the whole block.
For EDMA_MF_EDMA_UNROLL and EDMA_MF_HDMA_COMPAT, the driver programs
per-direction registers, such as DMA_{WRITE,READ}_INT_MASK_OFF and
DMA_{WRITE,READ}_INT_CLEAR_OFF. These register layouts have at most
EDMA_MAX_{WR,RD}_CH channels per direction, so the capped hardware
channel count still represents the whole direction. A partial instance
can therefore expose write or read channels only if it owns every
channel in that direction; otherwise two OS instances could update the
same direction-wide registers without a shared locking protocol.
In contrast, HDMA native uses per-channel registers, so it can be owned
at channel granularity.
Signed-off-by: Koichiro Den <den@valinux.co.jp>
Link: https://patch.msgid.link/20260721062815.4117887-6-den@valinux.co.jp
Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
parent
647217abea
commit
b24a5e9db6
|
|
@ -745,6 +745,9 @@ static int dw_edma_emul_irq_alloc(struct dw_edma *dw)
|
|||
chip->db_irq = 0;
|
||||
chip->db_offset = ~0;
|
||||
|
||||
if (chip->flags & DW_EDMA_CHIP_PARTIAL)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Only meaningful when the core provides the deassert sequence
|
||||
* for interrupt emulation.
|
||||
|
|
@ -1104,10 +1107,33 @@ static int dw_edma_irq_request(struct dw_edma *dw,
|
|||
return err;
|
||||
}
|
||||
|
||||
static int dw_edma_check_partial(struct dw_edma_chip *chip,
|
||||
u16 hw_wr_ch_cnt, u16 hw_rd_ch_cnt)
|
||||
{
|
||||
if (!(chip->flags & DW_EDMA_CHIP_PARTIAL))
|
||||
return 0;
|
||||
|
||||
if (chip->mf != EDMA_MF_EDMA_UNROLL &&
|
||||
chip->mf != EDMA_MF_HDMA_COMPAT)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Direction-wide registers are shared by all channels in that
|
||||
* direction, so a direction must have a single owner.
|
||||
*/
|
||||
if ((chip->ll_wr_cnt && chip->ll_wr_cnt != hw_wr_ch_cnt) ||
|
||||
(chip->ll_rd_cnt && chip->ll_rd_cnt != hw_rd_ch_cnt))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dw_edma_probe(struct dw_edma_chip *chip)
|
||||
{
|
||||
struct device *dev;
|
||||
struct dw_edma *dw;
|
||||
u16 hw_wr_ch_cnt;
|
||||
u16 hw_rd_ch_cnt;
|
||||
u32 wr_alloc = 0;
|
||||
u32 rd_alloc = 0;
|
||||
u16 max_wr_cnt;
|
||||
|
|
@ -1121,6 +1147,17 @@ int dw_edma_probe(struct dw_edma_chip *chip)
|
|||
if (!dev || !chip->ops)
|
||||
return -EINVAL;
|
||||
|
||||
if (chip->flags & DW_EDMA_CHIP_PARTIAL) {
|
||||
switch (chip->mf) {
|
||||
case EDMA_MF_EDMA_UNROLL:
|
||||
case EDMA_MF_HDMA_COMPAT:
|
||||
case EDMA_MF_HDMA_NATIVE:
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
dw = devm_kzalloc(dev, sizeof(*dw), GFP_KERNEL);
|
||||
if (!dw)
|
||||
return -ENOMEM;
|
||||
|
|
@ -1139,13 +1176,21 @@ int dw_edma_probe(struct dw_edma_chip *chip)
|
|||
|
||||
raw_spin_lock_init(&dw->lock);
|
||||
|
||||
dw->wr_ch_cnt = min_t(u16, chip->ll_wr_cnt,
|
||||
dw_edma_core_ch_count(dw, EDMA_DIR_WRITE));
|
||||
dw->wr_ch_cnt = min_t(u16, dw->wr_ch_cnt, max_wr_cnt);
|
||||
/*
|
||||
* chip->ll_*_cnt describes the channels exposed by this instance. Keep
|
||||
* the usable hardware counts separate for partial ownership checks.
|
||||
*/
|
||||
hw_wr_ch_cnt = min(dw_edma_core_ch_count(dw, EDMA_DIR_WRITE),
|
||||
max_wr_cnt);
|
||||
hw_rd_ch_cnt = min(dw_edma_core_ch_count(dw, EDMA_DIR_READ),
|
||||
max_rd_cnt);
|
||||
|
||||
dw->rd_ch_cnt = min_t(u16, chip->ll_rd_cnt,
|
||||
dw_edma_core_ch_count(dw, EDMA_DIR_READ));
|
||||
dw->rd_ch_cnt = min_t(u16, dw->rd_ch_cnt, max_rd_cnt);
|
||||
err = dw_edma_check_partial(chip, hw_wr_ch_cnt, hw_rd_ch_cnt);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
dw->wr_ch_cnt = min(chip->ll_wr_cnt, hw_wr_ch_cnt);
|
||||
dw->rd_ch_cnt = min(chip->ll_rd_cnt, hw_rd_ch_cnt);
|
||||
|
||||
if (!dw->wr_ch_cnt && !dw->rd_ch_cnt)
|
||||
return -EINVAL;
|
||||
|
|
@ -1162,8 +1207,18 @@ int dw_edma_probe(struct dw_edma_chip *chip)
|
|||
snprintf(dw->name, sizeof(dw->name), "dw-edma-core:%s",
|
||||
dev_name(chip->dev));
|
||||
|
||||
/* Disable eDMA, only to establish the ideal initial conditions */
|
||||
dw_edma_core_off(dw);
|
||||
if (chip->flags & DW_EDMA_CHIP_PARTIAL) {
|
||||
/*
|
||||
* Do not reset the shared controller, but drain stale state
|
||||
* from resources represented by this instance.
|
||||
*/
|
||||
err = dw_edma_core_quiesce(dw);
|
||||
if (err)
|
||||
return err;
|
||||
} else {
|
||||
/* Disable eDMA only when this instance owns the controller. */
|
||||
dw_edma_core_off(dw);
|
||||
}
|
||||
|
||||
/*
|
||||
* Deferred IRQ works are queued from the hard IRQ handlers, so the
|
||||
|
|
@ -1213,14 +1268,16 @@ int dw_edma_remove(struct dw_edma_chip *chip)
|
|||
struct dw_edma_chan *chan, *_chan;
|
||||
struct device *dev = chip->dev;
|
||||
struct dw_edma *dw = chip->dw;
|
||||
int i;
|
||||
int i, err = 0;
|
||||
|
||||
/* Skip removal if no private data found */
|
||||
if (!dw)
|
||||
return -ENODEV;
|
||||
|
||||
/* Disable eDMA */
|
||||
dw_edma_core_off(dw);
|
||||
if (chip->flags & DW_EDMA_CHIP_PARTIAL)
|
||||
err = dw_edma_core_quiesce(dw);
|
||||
else
|
||||
dw_edma_core_off(dw);
|
||||
|
||||
/* Free irqs */
|
||||
for (i = (dw->nr_irqs - 1); i >= 0; i--)
|
||||
|
|
@ -1240,7 +1297,7 @@ int dw_edma_remove(struct dw_edma_chip *chip)
|
|||
list_del(&chan->vc.chan.device_node);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dw_edma_remove);
|
||||
|
||||
|
|
|
|||
|
|
@ -57,9 +57,16 @@ enum dw_edma_map_format {
|
|||
/**
|
||||
* enum dw_edma_chip_flags - Flags specific to an eDMA chip
|
||||
* @DW_EDMA_CHIP_LOCAL: eDMA is used locally by an endpoint
|
||||
* @DW_EDMA_CHIP_PARTIAL: Only channels described by this instance are
|
||||
* owned by this driver. Controller-wide state
|
||||
* must be preserved, and layouts with shared
|
||||
* direction-wide registers must only be shared at
|
||||
* direction granularity. Layouts with per-channel
|
||||
* registers may be shared at channel granularity.
|
||||
*/
|
||||
enum dw_edma_chip_flags {
|
||||
DW_EDMA_CHIP_LOCAL = BIT(0),
|
||||
DW_EDMA_CHIP_PARTIAL = BIT(1),
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user