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:
Koichiro Den 2026-07-21 15:28:06 +09:00 committed by Vinod Koul
parent 647217abea
commit b24a5e9db6
2 changed files with 76 additions and 12 deletions

View File

@ -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);

View File

@ -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),
};
/**