From b24a5e9db6863567df1a10fcd29e01467672c56e Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Tue, 21 Jul 2026 15:28:06 +0900 Subject: [PATCH] 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 Link: https://patch.msgid.link/20260721062815.4117887-6-den@valinux.co.jp Signed-off-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 81 +++++++++++++++++++++++++----- include/linux/dma/edma.h | 7 +++ 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index ee9a8c32adf4..c9df5bbe2392 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -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); diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h index 17cbe7ce55aa..2ce9d2d49aef 100644 --- a/include/linux/dma/edma.h +++ b/include/linux/dma/edma.h @@ -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), }; /**