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