From 490c367b5fbcba6bb653077312c8ef477adf79b5 Mon Sep 17 00:00:00 2001 From: Zhen Ni Date: Tue, 14 Oct 2025 10:47:30 +0800 Subject: [PATCH 01/65] dmaengine: fsl-edma: Remove redundant check in fsl_edma_free_chan_resources() clk_disable_unprepare() is safe to call with a NULL clk, the FSL_EDMA_DRV_HAS_CHCLK check is reduntante. Clean up redundant checks. Suggested-by: Frank Li Signed-off-by: Zhen Ni Reviewed-by: Frank Li Link: https://patch.msgid.link/20251014024730.751237-1-zhen.ni@easystack.cn Signed-off-by: Vinod Koul --- drivers/dma/fsl-edma-common.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/dma/fsl-edma-common.c b/drivers/dma/fsl-edma-common.c index 6a38738e56e2..bb7531c456df 100644 --- a/drivers/dma/fsl-edma-common.c +++ b/drivers/dma/fsl-edma-common.c @@ -905,8 +905,7 @@ void fsl_edma_free_chan_resources(struct dma_chan *chan) fsl_chan->is_sw = false; fsl_chan->srcid = 0; fsl_chan->is_remote = false; - if (fsl_edma_drvflags(fsl_chan) & FSL_EDMA_DRV_HAS_CHCLK) - clk_disable_unprepare(fsl_chan->clk); + clk_disable_unprepare(fsl_chan->clk); } void fsl_edma_cleanup_vchan(struct dma_device *dmadev) From 2e8726879559144b4582fa059780e452b3a1ad41 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 16 Feb 2026 00:22:15 +0900 Subject: [PATCH 02/65] dmaengine: dw-edma: Add interrupt-emulation hooks DesignWare eDMA instances support "interrupt emulation", where a software write can assert the IRQ line without setting the normal DONE/ABORT status bits. Introduce core callbacks needed to support this feature: - .ack_emulated_irq(): core-specific sequence to deassert an emulated IRQ - .db_offset(): offset from the DMA register base that is suitable as a host-writable doorbell target for interrupt emulation Implement both hooks for the v0 register map. For dw-hdma-v0, provide a stub .db_offset() returning ~0 until the correct offset is known. The next patch wires these hooks into the dw-edma IRQ path and exports the doorbell resources to platform users. Signed-off-by: Koichiro Den Reviewed-by: Frank Li Link: https://patch.msgid.link/20260215152216.3393561-2-den@valinux.co.jp Signed-off-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.h | 17 +++++++++++++++++ drivers/dma/dw-edma/dw-edma-v0-core.c | 21 +++++++++++++++++++++ drivers/dma/dw-edma/dw-hdma-v0-core.c | 7 +++++++ 3 files changed, 45 insertions(+) diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h index 71894b9e0b15..59b24973fa7d 100644 --- a/drivers/dma/dw-edma/dw-edma-core.h +++ b/drivers/dma/dw-edma/dw-edma-core.h @@ -126,6 +126,8 @@ struct dw_edma_core_ops { void (*start)(struct dw_edma_chunk *chunk, bool first); void (*ch_config)(struct dw_edma_chan *chan); void (*debugfs_on)(struct dw_edma *dw); + void (*ack_emulated_irq)(struct dw_edma *dw); + resource_size_t (*db_offset)(struct dw_edma *dw); }; struct dw_edma_sg { @@ -206,4 +208,19 @@ void dw_edma_core_debugfs_on(struct dw_edma *dw) dw->core->debugfs_on(dw); } +static inline int dw_edma_core_ack_emulated_irq(struct dw_edma *dw) +{ + if (!dw->core->ack_emulated_irq) + return -EOPNOTSUPP; + + dw->core->ack_emulated_irq(dw); + return 0; +} + +static inline resource_size_t +dw_edma_core_db_offset(struct dw_edma *dw) +{ + return dw->core->db_offset(dw); +} + #endif /* _DW_EDMA_CORE_H */ diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c index b75fdaffad9a..69e8279adec8 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-core.c +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c @@ -509,6 +509,25 @@ static void dw_edma_v0_core_debugfs_on(struct dw_edma *dw) dw_edma_v0_debugfs_on(dw); } +static void dw_edma_v0_core_ack_emulated_irq(struct dw_edma *dw) +{ + /* + * Interrupt emulation may assert the IRQ without setting + * DONE/ABORT status bits. A zero write to INT_CLEAR deasserts the + * emulated IRQ, while being a no-op for real interrupts. + */ + SET_BOTH_32(dw, int_clear, 0); +} + +static resource_size_t dw_edma_v0_core_db_offset(struct dw_edma *dw) +{ + /* + * rd_int_status is chosen arbitrarily, but wr_int_status would be + * equally suitable. + */ + return offsetof(struct dw_edma_v0_regs, rd_int_status); +} + static const struct dw_edma_core_ops dw_edma_v0_core = { .off = dw_edma_v0_core_off, .ch_count = dw_edma_v0_core_ch_count, @@ -517,6 +536,8 @@ static const struct dw_edma_core_ops dw_edma_v0_core = { .start = dw_edma_v0_core_start, .ch_config = dw_edma_v0_core_ch_config, .debugfs_on = dw_edma_v0_core_debugfs_on, + .ack_emulated_irq = dw_edma_v0_core_ack_emulated_irq, + .db_offset = dw_edma_v0_core_db_offset, }; void dw_edma_v0_core_register(struct dw_edma *dw) diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c index e3f8db4fe909..1ae8e44f0a67 100644 --- a/drivers/dma/dw-edma/dw-hdma-v0-core.c +++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c @@ -283,6 +283,12 @@ static void dw_hdma_v0_core_debugfs_on(struct dw_edma *dw) dw_hdma_v0_debugfs_on(dw); } +static resource_size_t dw_hdma_v0_core_db_offset(struct dw_edma *dw) +{ + /* Implement once the correct offset is known. */ + return ~0; +} + static const struct dw_edma_core_ops dw_hdma_v0_core = { .off = dw_hdma_v0_core_off, .ch_count = dw_hdma_v0_core_ch_count, @@ -291,6 +297,7 @@ static const struct dw_edma_core_ops dw_hdma_v0_core = { .start = dw_hdma_v0_core_start, .ch_config = dw_hdma_v0_core_ch_config, .debugfs_on = dw_hdma_v0_core_debugfs_on, + .db_offset = dw_hdma_v0_core_db_offset, }; void dw_hdma_v0_core_register(struct dw_edma *dw) From d9d5e1bdd18074ea27985c777ddc3a8a0b007468 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 16 Feb 2026 00:22:16 +0900 Subject: [PATCH 03/65] dmaengine: dw-edma: Add virtual IRQ for interrupt-emulation doorbells Interrupt emulation can assert the dw-edma IRQ line without updating the DONE/ABORT bits. With the shared read/write/common IRQ handlers, the driver cannot reliably distinguish such an emulated interrupt from a real one and leaving a level IRQ asserted may wedge the line. Allocate a dedicated, requestable Linux virtual IRQ (db_irq) for interrupt emulation and attach an irq_chip whose .irq_ack runs the core-specific deassert sequence (.ack_emulated_irq()). The physical dw-edma interrupt handlers raise this virtual IRQ via generic_handle_irq(), ensuring emulated IRQs are always deasserted. Export the virtual IRQ number (db_irq) and the doorbell register offset (db_offset) via struct dw_edma_chip so platform users can expose interrupt emulation as a doorbell. Without this, a single interrupt-emulation write can leave the level IRQ line asserted and cause the generic IRQ layer to disable it. Signed-off-by: Koichiro Den Reviewed-by: Frank Li Link: https://patch.msgid.link/20260215152216.3393561-3-den@valinux.co.jp Signed-off-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 127 +++++++++++++++++++++++++++-- include/linux/dma/edma.h | 6 ++ 2 files changed, 128 insertions(+), 5 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index e7d698b352d3..cd34a3ea602d 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -663,7 +663,96 @@ static void dw_edma_abort_interrupt(struct dw_edma_chan *chan) chan->status = EDMA_ST_IDLE; } -static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data) +static void dw_edma_emul_irq_ack(struct irq_data *d) +{ + struct dw_edma *dw = irq_data_get_irq_chip_data(d); + + dw_edma_core_ack_emulated_irq(dw); +} + +/* + * irq_chip implementation for interrupt-emulation doorbells. + * + * The emulated source has no mask/unmask mechanism. With handle_level_irq(), + * the flow is therefore: + * 1) .irq_ack() deasserts the source + * 2) registered handlers (if any) are dispatched + * Since deassertion is already done in .irq_ack(), handlers do not need to take + * care of it, hence IRQCHIP_ONESHOT_SAFE. + */ +static struct irq_chip dw_edma_emul_irqchip = { + .name = "dw-edma-emul", + .irq_ack = dw_edma_emul_irq_ack, + .flags = IRQCHIP_ONESHOT_SAFE | IRQCHIP_SKIP_SET_WAKE, +}; + +static int dw_edma_emul_irq_alloc(struct dw_edma *dw) +{ + struct dw_edma_chip *chip = dw->chip; + int virq; + + chip->db_irq = 0; + chip->db_offset = ~0; + + /* + * Only meaningful when the core provides the deassert sequence + * for interrupt emulation. + */ + if (!dw->core->ack_emulated_irq) + return 0; + + /* + * Allocate a single, requestable Linux virtual IRQ number. + * Use >= 1 so that 0 can remain a "not available" sentinel. + */ + virq = irq_alloc_desc(NUMA_NO_NODE); + if (virq < 0) + return virq; + + irq_set_chip_and_handler(virq, &dw_edma_emul_irqchip, handle_level_irq); + irq_set_chip_data(virq, dw); + irq_set_noprobe(virq); + + chip->db_irq = virq; + chip->db_offset = dw_edma_core_db_offset(dw); + + return 0; +} + +static void dw_edma_emul_irq_free(struct dw_edma *dw) +{ + struct dw_edma_chip *chip = dw->chip; + + if (!chip) + return; + if (chip->db_irq <= 0) + return; + + irq_free_descs(chip->db_irq, 1); + chip->db_irq = 0; + chip->db_offset = ~0; +} + +static inline irqreturn_t dw_edma_interrupt_emulated(void *data) +{ + struct dw_edma_irq *dw_irq = data; + struct dw_edma *dw = dw_irq->dw; + int db_irq = dw->chip->db_irq; + + if (db_irq > 0) { + /* + * Interrupt emulation may assert the IRQ line without updating the + * normal DONE/ABORT status bits. With a shared IRQ handler we + * cannot reliably detect such events by status registers alone, so + * always perform the core-specific deassert sequence. + */ + generic_handle_irq(db_irq); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static inline irqreturn_t dw_edma_interrupt_write_inner(int irq, void *data) { struct dw_edma_irq *dw_irq = data; @@ -672,7 +761,7 @@ static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data) dw_edma_abort_interrupt); } -static inline irqreturn_t dw_edma_interrupt_read(int irq, void *data) +static inline irqreturn_t dw_edma_interrupt_read_inner(int irq, void *data) { struct dw_edma_irq *dw_irq = data; @@ -681,12 +770,33 @@ static inline irqreturn_t dw_edma_interrupt_read(int irq, void *data) dw_edma_abort_interrupt); } -static irqreturn_t dw_edma_interrupt_common(int irq, void *data) +static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data) { irqreturn_t ret = IRQ_NONE; - ret |= dw_edma_interrupt_write(irq, data); - ret |= dw_edma_interrupt_read(irq, data); + ret |= dw_edma_interrupt_write_inner(irq, data); + ret |= dw_edma_interrupt_emulated(data); + + return ret; +} + +static inline irqreturn_t dw_edma_interrupt_read(int irq, void *data) +{ + irqreturn_t ret = IRQ_NONE; + + ret |= dw_edma_interrupt_read_inner(irq, data); + ret |= dw_edma_interrupt_emulated(data); + + return ret; +} + +static inline irqreturn_t dw_edma_interrupt_common(int irq, void *data) +{ + irqreturn_t ret = IRQ_NONE; + + ret |= dw_edma_interrupt_write_inner(irq, data); + ret |= dw_edma_interrupt_read_inner(irq, data); + ret |= dw_edma_interrupt_emulated(data); return ret; } @@ -973,6 +1083,11 @@ int dw_edma_probe(struct dw_edma_chip *chip) if (err) return err; + /* Allocate a dedicated virtual IRQ for interrupt-emulation doorbells */ + err = dw_edma_emul_irq_alloc(dw); + if (err) + dev_warn(dev, "Failed to allocate emulation IRQ: %d\n", err); + /* Setup write/read channels */ err = dw_edma_channel_setup(dw, wr_alloc, rd_alloc); if (err) @@ -988,6 +1103,7 @@ int dw_edma_probe(struct dw_edma_chip *chip) err_irq_free: for (i = (dw->nr_irqs - 1); i >= 0; i--) free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]); + dw_edma_emul_irq_free(dw); return err; } @@ -1010,6 +1126,7 @@ int dw_edma_remove(struct dw_edma_chip *chip) /* Free irqs */ for (i = (dw->nr_irqs - 1); i >= 0; i--) free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]); + dw_edma_emul_irq_free(dw); /* Deregister eDMA device */ dma_async_device_unregister(&dw->dma); diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h index 270b5458aecf..9da53c75e49b 100644 --- a/include/linux/dma/edma.h +++ b/include/linux/dma/edma.h @@ -73,6 +73,8 @@ enum dw_edma_chip_flags { * @ll_region_rd: DMA descriptor link list memory for read channel * @dt_region_wr: DMA data memory for write channel * @dt_region_rd: DMA data memory for read channel + * @db_irq: Virtual IRQ dedicated to interrupt emulation + * @db_offset: Offset from DMA register base * @mf: DMA register map format * @dw: struct dw_edma that is filled by dw_edma_probe() */ @@ -94,6 +96,10 @@ struct dw_edma_chip { struct dw_edma_region dt_region_wr[EDMA_MAX_WR_CH]; struct dw_edma_region dt_region_rd[EDMA_MAX_RD_CH]; + /* interrupt emulation */ + int db_irq; + resource_size_t db_offset; + enum dw_edma_map_format mf; struct dw_edma *dw; From e45cf0c7d9b960f1aae4ee56c3c3d46549ccde86 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Tue, 3 Feb 2026 10:30:09 +0000 Subject: [PATCH 04/65] dt-bindings: dma: rz-dmac: Document RZ/G3L SoC Document the Renesas RZ/G3L DMAC block. This is identical to the one found on the RZ/G3S SoC. Reviewed-by: Fabrizio Castro Acked-by: Conor Dooley Signed-off-by: Biju Das Link: https://patch.msgid.link/20260203103031.247435-2-biju.das.jz@bp.renesas.com Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/dma/renesas,rz-dmac.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/dma/renesas,rz-dmac.yaml b/Documentation/devicetree/bindings/dma/renesas,rz-dmac.yaml index d137b9cbaee9..e3311029eb2f 100644 --- a/Documentation/devicetree/bindings/dma/renesas,rz-dmac.yaml +++ b/Documentation/devicetree/bindings/dma/renesas,rz-dmac.yaml @@ -19,6 +19,7 @@ properties: - renesas,r9a07g044-dmac # RZ/G2{L,LC} - renesas,r9a07g054-dmac # RZ/V2L - renesas,r9a08g045-dmac # RZ/G3S + - renesas,r9a08g046-dmac # RZ/G3L - const: renesas,rz-dmac - items: From ff7cbcca2b32c6e079941e577c41c74036861d5a Mon Sep 17 00:00:00 2001 From: Khairul Anuar Romli Date: Sat, 31 Jan 2026 11:28:56 -0600 Subject: [PATCH 05/65] dt-bindings: dma: snps,dw-axi-dmac: add dma-coherent property The Synopsys DesignWare AXI DMA Controller on Agilex5, the controller operates on a cache-coherent AXI interface, where DMA transactions are automatically kept coherent with the CPU caches. In previous generations SoC (Stratix10 and Agilex) the interconnect was non-coherent, hence there is no need for dma-coherent property to be presence. In Agilex 5, the architecture has changed. It introduced a coherent interconnect that supports cache-coherent DMA. Signed-off-by: Khairul Anuar Romli Reviewed-by: Rob Herring (Arm) Signed-off-by: Dinh Nguyen Link: https://patch.msgid.link/20260131172856.29227-1-dinguyen@kernel.org Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml b/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml index 216cda21c538..e12a48a12ea4 100644 --- a/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml +++ b/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml @@ -68,6 +68,8 @@ properties: dma-noncoherent: true + dma-coherent: true + resets: minItems: 1 maxItems: 2 From 6c5883a9ba296d2797437066592d15b2d202de7a Mon Sep 17 00:00:00 2001 From: Khairul Anuar Romli Date: Mon, 2 Feb 2026 14:02:17 +0800 Subject: [PATCH 06/65] dmaengine: dw-axi-dmac: fix Alignment should match open parenthesis checkpatch.pl --strict reports a CHECK warning in dw-axi-dmac-platform.c: CHECK: Alignment should match open parenthesis This warning occurs when multi-line function calls or expressions have continuation lines that don't properly align with the opening parenthesis position. This patch fixes all instances in dw-axi-dmac-platform.c where continuation lines were indented with an inconsistent number of spaces/tabs that neither matched the parenthesis column nor followed a standard indent pattern. Proper alignment improves code readability and maintainability by making parameter lists visually consistent across the kernel codebase. Fixes: 1fe20f1b8454 ("dmaengine: Introduce DW AXI DMAC driver") Fixes: e32634f466a9 ("dma: dw-axi-dmac: support per channel interrupt") Signed-off-by: Khairul Anuar Romli Link: https://patch.msgid.link/20260202060224.12616-2-karom.9560@gmail.com Signed-off-by: Vinod Koul --- drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c index 5d74bc29cf89..b9fbfeb873e7 100644 --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c @@ -342,8 +342,8 @@ static void axi_desc_put(struct axi_dma_desc *desc) kfree(desc); atomic_sub(descs_put, &chan->descs_allocated); dev_vdbg(chan2dev(chan), "%s: %d descs put, %d still allocated\n", - axi_chan_name(chan), descs_put, - atomic_read(&chan->descs_allocated)); + axi_chan_name(chan), descs_put, + atomic_read(&chan->descs_allocated)); } static void vchan_desc_put(struct virt_dma_desc *vdesc) @@ -353,7 +353,7 @@ static void vchan_desc_put(struct virt_dma_desc *vdesc) static enum dma_status dma_chan_tx_status(struct dma_chan *dchan, dma_cookie_t cookie, - struct dma_tx_state *txstate) + struct dma_tx_state *txstate) { struct axi_dma_chan *chan = dchan_to_axi_dma_chan(dchan); struct virt_dma_desc *vdesc; @@ -491,7 +491,7 @@ static void axi_chan_start_first_queued(struct axi_dma_chan *chan) desc = vd_to_axi_desc(vd); dev_vdbg(chan2dev(chan), "%s: started %u\n", axi_chan_name(chan), - vd->tx.cookie); + vd->tx.cookie); axi_chan_block_xfer_start(chan, desc); } @@ -1162,7 +1162,7 @@ static irqreturn_t dw_axi_dma_interrupt(int irq, void *dev_id) axi_chan_irq_clear(chan, status); dev_vdbg(chip->dev, "%s %u IRQ status: 0x%08x\n", - axi_chan_name(chan), i, status); + axi_chan_name(chan), i, status); if (status & DWAXIDMAC_IRQ_ALL_ERR) axi_chan_handle_err(chan, status); @@ -1451,7 +1451,7 @@ static int axi_req_irqs(struct platform_device *pdev, struct axi_dma_chip *chip) if (chip->irq[i] < 0) return chip->irq[i]; ret = devm_request_irq(chip->dev, chip->irq[i], dw_axi_dma_interrupt, - IRQF_SHARED, KBUILD_MODNAME, chip); + IRQF_SHARED, KBUILD_MODNAME, chip); if (ret < 0) return ret; } @@ -1645,7 +1645,7 @@ static void dw_remove(struct platform_device *pdev) of_dma_controller_free(chip->dev->of_node); list_for_each_entry_safe(chan, _chan, &dw->dma.channels, - vc.chan.device_node) { + vc.chan.device_node) { list_del(&chan->vc.chan.device_node); tasklet_kill(&chan->vc.task); } From b6f1d1b08edc406d9f5c140e9eb05d00a23a57b0 Mon Sep 17 00:00:00 2001 From: Khairul Anuar Romli Date: Mon, 2 Feb 2026 14:02:18 +0800 Subject: [PATCH 07/65] dmaengine: dw-axi-dmac: Add blank line after function checkpatch.pl reports a CHECK warning in dw-axi-dmac-platform.c: CHECK: Please use a blank line after function/struct/union/enum declarations drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c: The Linux kernel coding style [Documentation/process/coding-style.rst] requires a blank line after function definitions to provide visual separation between distinct code elements. This patch inserts the required blank line after the closing brace of the function definition after dw_axi_dma_set_byte_halfword(), placing it before the contextual comment that describes the locking requirements. Signed-off-by: Khairul Anuar Romli Link: https://patch.msgid.link/20260202060224.12616-3-karom.9560@gmail.com Signed-off-by: Vinod Koul --- drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c index b9fbfeb873e7..1e536cb979c4 100644 --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c @@ -419,6 +419,7 @@ static void dw_axi_dma_set_byte_halfword(struct axi_dma_chan *chan, bool set) iowrite32(val, chan->chip->apb_regs + offset); } + /* Called in chan locked context */ static void axi_chan_block_xfer_start(struct axi_dma_chan *chan, struct axi_dma_desc *first) From 48278a72fce8a8d30efaedeb206c9c3f05c1eb3f Mon Sep 17 00:00:00 2001 From: Khairul Anuar Romli Date: Mon, 2 Feb 2026 14:02:19 +0800 Subject: [PATCH 08/65] dmaengine: dw-axi-dmac: Remove unnecessary return statement from void function checkpatch.pl --strict reports a WARNING in dw-axi-dmac-platform.c: WARNING: void function return statements are not generally useful FILE: drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c According to Linux kernel coding style [Documentation/process/ coding-style.rst], explicit "return;" statements at the end of void functions are redundant and should be omitted. The function will automatically return upon reaching the closing brace, so the extra statement adds unnecessary clutter without functional benefit. This patch removes the superfluous "return;" statement in dw_axi_dma_set_hw_channel() to comply with kernel coding standards and eliminate the checkpatch warning. Fixes: 32286e279385 ("dmaengine: dw-axi-dmac: Remove free slot check algorithm in dw_axi_dma_set_hw_channel") Signed-off-by: Khairul Anuar Romli Link: https://patch.msgid.link/20260202060224.12616-4-karom.9560@gmail.com Signed-off-by: Vinod Koul --- drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c index 1e536cb979c4..c90a83a3bd2f 100644 --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c @@ -593,8 +593,6 @@ static void dw_axi_dma_set_hw_channel(struct axi_dma_chan *chan, bool set) (chan->id * DMA_APB_HS_SEL_BIT_SIZE)); reg_value |= (val << (chan->id * DMA_APB_HS_SEL_BIT_SIZE)); lo_hi_writeq(reg_value, chip->apb_regs + DMAC_APB_HW_HS_SEL_0); - - return; } /* From be3e2a0419c639b6a192141639259a4d34556dd0 Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Tue, 20 Jan 2026 09:37:03 +0800 Subject: [PATCH 09/65] dt-bindings: dma: snps,dw-axi-dmac: Add CV1800B compatible The DMA controller on CV1800B needs to use the DMA phandle args as the channel number instead of hardware handshake number, so add a new compatible for the DMA controller on CV1800B. Signed-off-by: Inochi Amaoto Acked-by: Conor Dooley Link: https://patch.msgid.link/20260120013706.436742-2-inochiama@gmail.com Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml b/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml index e12a48a12ea4..804514732dbe 100644 --- a/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml +++ b/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml @@ -21,6 +21,7 @@ properties: - enum: - snps,axi-dma-1.01a - intel,kmb-axi-dma + - sophgo,cv1800b-axi-dma - starfive,jh7110-axi-dma - starfive,jh8100-axi-dma - items: From b49c70273801bf2f7c16ac49f403a5c253b46159 Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Tue, 20 Jan 2026 09:37:04 +0800 Subject: [PATCH 10/65] dmaengine: dw-axi-dmac: Add support for CV1800B DMA As the DMA controller on Sophgo CV1800 series SoC only has 8 channels, the SoC provides a dma multiplexer to reuse the DMA channel. However, the dma multiplexer also controls the DMA interrupt multiplexer, which means that the dma multiplexer needs to know the channel number. Allow the driver to use DMA phandle args as the channel number, so the DMA multiplexer can route the DMA interrupt correctly. Signed-off-by: Inochi Amaoto Reviewed-by: Frank Li Link: https://patch.msgid.link/20260120013706.436742-3-inochiama@gmail.com Signed-off-by: Vinod Koul --- .../dma/dw-axi-dmac/dw-axi-dmac-platform.c | 25 ++++++++++++++++--- drivers/dma/dw-axi-dmac/dw-axi-dmac.h | 1 + 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c index c90a83a3bd2f..4d53f077e9d2 100644 --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c @@ -50,6 +50,7 @@ #define AXI_DMA_FLAG_HAS_APB_REGS BIT(0) #define AXI_DMA_FLAG_HAS_RESETS BIT(1) #define AXI_DMA_FLAG_USE_CFG2 BIT(2) +#define AXI_DMA_FLAG_ARG0_AS_CHAN BIT(3) static inline void axi_dma_iowrite32(struct axi_dma_chip *chip, u32 reg, u32 val) @@ -1357,16 +1358,27 @@ static int __maybe_unused axi_dma_runtime_resume(struct device *dev) static struct dma_chan *dw_axi_dma_of_xlate(struct of_phandle_args *dma_spec, struct of_dma *ofdma) { + unsigned int handshake = dma_spec->args[0]; struct dw_axi_dma *dw = ofdma->of_dma_data; - struct axi_dma_chan *chan; + struct axi_dma_chan *chan = NULL; struct dma_chan *dchan; - dchan = dma_get_any_slave_channel(&dw->dma); + if (dw->hdata->use_handshake_as_channel_number) { + if (handshake >= dw->hdata->nr_channels) + return NULL; + + chan = &dw->chan[handshake]; + dchan = dma_get_slave_channel(&chan->vc.chan); + } else { + dchan = dma_get_any_slave_channel(&dw->dma); + } + if (!dchan) return NULL; - chan = dchan_to_axi_dma_chan(dchan); - chan->hw_handshake_num = dma_spec->args[0]; + if (!chan) + chan = dchan_to_axi_dma_chan(dchan); + chan->hw_handshake_num = handshake; return dchan; } @@ -1505,6 +1517,8 @@ static int dw_probe(struct platform_device *pdev) return ret; } + chip->dw->hdata->use_handshake_as_channel_number = !!(flags & AXI_DMA_FLAG_ARG0_AS_CHAN); + chip->dw->hdata->use_cfg2 = !!(flags & AXI_DMA_FLAG_USE_CFG2); chip->core_clk = devm_clk_get(chip->dev, "core-clk"); @@ -1660,6 +1674,9 @@ static const struct of_device_id dw_dma_of_id_table[] = { }, { .compatible = "intel,kmb-axi-dma", .data = (void *)AXI_DMA_FLAG_HAS_APB_REGS, + }, { + .compatible = "sophgo,cv1800b-axi-dma", + .data = (void *)AXI_DMA_FLAG_ARG0_AS_CHAN, }, { .compatible = "starfive,jh7110-axi-dma", .data = (void *)(AXI_DMA_FLAG_HAS_RESETS | AXI_DMA_FLAG_USE_CFG2), diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac.h b/drivers/dma/dw-axi-dmac/dw-axi-dmac.h index b842e6a8d90d..67cc199e24d1 100644 --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac.h +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac.h @@ -34,6 +34,7 @@ struct dw_axi_dma_hcfg { bool reg_map_8_channels; bool restrict_axi_burst_len; bool use_cfg2; + bool use_handshake_as_channel_number; }; struct axi_dma_chan { From f709b38e5bfec5846f3f062e87f86ead0c881028 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 9 Jan 2026 18:35:41 +0100 Subject: [PATCH 11/65] dmaengine: Refactor devm_dma_request_chan() for readability Yes, while it's a bit longer in terms of LoCs, it's more readable when we use the usual patter to check for errors, and not for a success). This eliminates unneeded assignment and moves the needed one closer to its user which is better programming pattern because it allows avoiding potential errors in case the variable is getting reused. Also note that the same pattern have been used already in dmaenginem_async_device_register(). Signed-off-by: Andy Shevchenko Reviewed-by: Dave Jiang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260109173718.3605829-2-andriy.shevchenko@linux.intel.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index 27a8980b03dd..eb54e2b58d44 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -943,12 +943,14 @@ static void dmaenginem_release_channel(void *chan) struct dma_chan *devm_dma_request_chan(struct device *dev, const char *name) { - struct dma_chan *chan = dma_request_chan(dev, name); - int ret = 0; + struct dma_chan *chan; + int ret; - if (!IS_ERR(chan)) - ret = devm_add_action_or_reset(dev, dmaenginem_release_channel, chan); + chan = dma_request_chan(dev, name); + if (IS_ERR(chan)) + return chan; + ret = devm_add_action_or_reset(dev, dmaenginem_release_channel, chan); if (ret) return ERR_PTR(ret); From d42a8378faa35d7958f2d71848f7899e011b829e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 9 Jan 2026 18:35:42 +0100 Subject: [PATCH 12/65] dmaengine: Use device_match_of_node() helper Instead of open coding, use device_match_of_node() helper. Signed-off-by: Andy Shevchenko Reviewed-by: Dave Jiang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260109173718.3605829-3-andriy.shevchenko@linux.intel.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index eb54e2b58d44..ec2a96e3a213 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -765,7 +765,7 @@ struct dma_chan *__dma_request_channel(const dma_cap_mask_t *mask, mutex_lock(&dma_list_mutex); list_for_each_entry_safe(device, _d, &dma_device_list, global_node) { /* Finds a DMA controller with matching device node */ - if (np && device->dev->of_node && np != device->dev->of_node) + if (np && !device_match_of_node(device->dev, np)) continue; chan = find_candidate(device, mask, fn, fn_param); From d1ba2e5f6cfc6ec8786ceeb45b75a080fca313ea Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 9 Jan 2026 18:35:43 +0100 Subject: [PATCH 13/65] dmaengine: Sort headers alphabetically For better maintenance sort headers alphabetically. Signed-off-by: Andy Shevchenko Reviewed-by: Dave Jiang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260109173718.3605829-4-andriy.shevchenko@linux.intel.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index ec2a96e3a213..405bd2fbb4a3 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -31,29 +31,29 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include -#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "dmaengine.h" From c8e9b1d9febc83ee94944695a07cfd40a1b29743 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 25 Feb 2026 21:12:20 -0800 Subject: [PATCH 14/65] dmaengine: fsl-edma: fix all kernel-doc warnings Use the correct kernel-doc format and struct member names to eliminate these kernel-doc warnings: Warning: include/linux/platform_data/dma-mcf-edma.h:35 struct member 'dma_channels' not described in 'mcf_edma_platform_data' Warning: include/linux/platform_data/dma-mcf-edma.h:35 struct member 'slave_map' not described in 'mcf_edma_platform_data' Warning: include/linux/platform_data/dma-mcf-edma.h:35 struct member 'slavecnt' not described in 'mcf_edma_platform_data' Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260226051220.548566-1-rdunlap@infradead.org Signed-off-by: Vinod Koul --- include/linux/platform_data/dma-mcf-edma.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/linux/platform_data/dma-mcf-edma.h b/include/linux/platform_data/dma-mcf-edma.h index d718ccfa3421..0b31af66a1ac 100644 --- a/include/linux/platform_data/dma-mcf-edma.h +++ b/include/linux/platform_data/dma-mcf-edma.h @@ -26,8 +26,9 @@ bool mcf_edma_filter_fn(struct dma_chan *chan, void *param); /** * struct mcf_edma_platform_data - platform specific data for eDMA engine * - * @ver The eDMA module version. - * @dma_channels The number of eDMA channels. + * @dma_channels: The number of eDMA channels. + * @slave_map: Slave device map + * @slavecnt: Number of entries in @slave_map */ struct mcf_edma_platform_data { int dma_channels; From 0124b354a4dbea1f924adb2355db21d29bd2a5fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 4 Mar 2026 22:44:37 +0100 Subject: [PATCH 15/65] dmaengine: ioatdma: make some sysfs structures static MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These structures are only used in sysfs.c, where are defined. Make them static and remove them from the header. Signed-off-by: Thomas Weißschuh Reviewed-by: Frank Li Acked-by: Dave Jiang Link: https://patch.msgid.link/20260304-sysfs-const-ioat-v2-1-b9b82651219b@weissschuh.net Signed-off-by: Vinod Koul --- drivers/dma/ioat/dma.h | 3 --- drivers/dma/ioat/sysfs.c | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/dma/ioat/dma.h b/drivers/dma/ioat/dma.h index 12a4a4860a74..27d2b411853f 100644 --- a/drivers/dma/ioat/dma.h +++ b/drivers/dma/ioat/dma.h @@ -195,9 +195,6 @@ struct ioat_ring_ent { struct ioat_sed_ent *sed; }; -extern const struct sysfs_ops ioat_sysfs_ops; -extern struct ioat_sysfs_entry ioat_version_attr; -extern struct ioat_sysfs_entry ioat_cap_attr; extern int ioat_pending_level; extern struct kobj_type ioat_ktype; extern struct kmem_cache *ioat_cache; diff --git a/drivers/dma/ioat/sysfs.c b/drivers/dma/ioat/sysfs.c index 168adf28c5b1..5da9b0a7b2bb 100644 --- a/drivers/dma/ioat/sysfs.c +++ b/drivers/dma/ioat/sysfs.c @@ -26,7 +26,7 @@ static ssize_t cap_show(struct dma_chan *c, char *page) dma_has_cap(DMA_INTERRUPT, dma->cap_mask) ? " intr" : ""); } -struct ioat_sysfs_entry ioat_cap_attr = __ATTR_RO(cap); +static struct ioat_sysfs_entry ioat_cap_attr = __ATTR_RO(cap); static ssize_t version_show(struct dma_chan *c, char *page) { @@ -36,7 +36,7 @@ static ssize_t version_show(struct dma_chan *c, char *page) return sprintf(page, "%d.%d\n", ioat_dma->version >> 4, ioat_dma->version & 0xf); } -struct ioat_sysfs_entry ioat_version_attr = __ATTR_RO(version); +static struct ioat_sysfs_entry ioat_version_attr = __ATTR_RO(version); static ssize_t ioat_attr_show(struct kobject *kobj, struct attribute *attr, char *page) @@ -67,7 +67,7 @@ const char *page, size_t count) return entry->store(&ioat_chan->dma_chan, page, count); } -const struct sysfs_ops ioat_sysfs_ops = { +static const struct sysfs_ops ioat_sysfs_ops = { .show = ioat_attr_show, .store = ioat_attr_store, }; From bc94ca718f85f1caa40bea31ea63b52278d9d0cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 4 Mar 2026 22:44:38 +0100 Subject: [PATCH 16/65] dmaengine: ioatdma: move sysfs entry definition out of header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move struct ioat_sysfs_entry into sysfs.c because it is only used in it. Signed-off-by: Thomas Weißschuh Reviewed-by: Frank Li Acked-by: Dave Jiang Link: https://patch.msgid.link/20260304-sysfs-const-ioat-v2-2-b9b82651219b@weissschuh.net Signed-off-by: Vinod Koul --- drivers/dma/ioat/dma.h | 6 ------ drivers/dma/ioat/sysfs.c | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/dma/ioat/dma.h b/drivers/dma/ioat/dma.h index 27d2b411853f..e187f3a7e968 100644 --- a/drivers/dma/ioat/dma.h +++ b/drivers/dma/ioat/dma.h @@ -140,12 +140,6 @@ struct ioatdma_chan { int prev_intr_coalesce; }; -struct ioat_sysfs_entry { - struct attribute attr; - ssize_t (*show)(struct dma_chan *, char *); - ssize_t (*store)(struct dma_chan *, const char *, size_t); -}; - /** * struct ioat_sed_ent - wrapper around super extended hardware descriptor * @hw: hardware SED diff --git a/drivers/dma/ioat/sysfs.c b/drivers/dma/ioat/sysfs.c index 5da9b0a7b2bb..709d672bae51 100644 --- a/drivers/dma/ioat/sysfs.c +++ b/drivers/dma/ioat/sysfs.c @@ -14,6 +14,12 @@ #include "../dmaengine.h" +struct ioat_sysfs_entry { + struct attribute attr; + ssize_t (*show)(struct dma_chan *, char *); + ssize_t (*store)(struct dma_chan *, const char *, size_t); +}; + static ssize_t cap_show(struct dma_chan *c, char *page) { struct dma_device *dma = c->device; From 81ca3ad09ba7296daa798b4950097af1591b2809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 4 Mar 2026 22:44:39 +0100 Subject: [PATCH 17/65] dmaengine: ioatdma: make ioat_ktype const MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ioat_ktype is never modified, so make it const. Signed-off-by: Thomas Weißschuh Acked-by: Dave Jiang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260304-sysfs-const-ioat-v2-3-b9b82651219b@weissschuh.net Signed-off-by: Vinod Koul --- drivers/dma/ioat/dma.h | 4 ++-- drivers/dma/ioat/sysfs.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/dma/ioat/dma.h b/drivers/dma/ioat/dma.h index e187f3a7e968..e8a880f338c6 100644 --- a/drivers/dma/ioat/dma.h +++ b/drivers/dma/ioat/dma.h @@ -190,7 +190,7 @@ struct ioat_ring_ent { }; extern int ioat_pending_level; -extern struct kobj_type ioat_ktype; +extern const struct kobj_type ioat_ktype; extern struct kmem_cache *ioat_cache; extern struct kmem_cache *ioat_sed_cache; @@ -393,7 +393,7 @@ void ioat_issue_pending(struct dma_chan *chan); /* IOAT Init functions */ bool is_bwd_ioat(struct pci_dev *pdev); struct dca_provider *ioat_dca_init(struct pci_dev *pdev, void __iomem *iobase); -void ioat_kobject_add(struct ioatdma_device *ioat_dma, struct kobj_type *type); +void ioat_kobject_add(struct ioatdma_device *ioat_dma, const struct kobj_type *type); void ioat_kobject_del(struct ioatdma_device *ioat_dma); int ioat_dma_setup_interrupts(struct ioatdma_device *ioat_dma); void ioat_stop(struct ioatdma_chan *ioat_chan); diff --git a/drivers/dma/ioat/sysfs.c b/drivers/dma/ioat/sysfs.c index 709d672bae51..da616365fef5 100644 --- a/drivers/dma/ioat/sysfs.c +++ b/drivers/dma/ioat/sysfs.c @@ -78,7 +78,7 @@ static const struct sysfs_ops ioat_sysfs_ops = { .store = ioat_attr_store, }; -void ioat_kobject_add(struct ioatdma_device *ioat_dma, struct kobj_type *type) +void ioat_kobject_add(struct ioatdma_device *ioat_dma, const struct kobj_type *type) { struct dma_device *dma = &ioat_dma->dma_dev; struct dma_chan *c; @@ -166,7 +166,7 @@ static struct attribute *ioat_attrs[] = { }; ATTRIBUTE_GROUPS(ioat); -struct kobj_type ioat_ktype = { +const struct kobj_type ioat_ktype = { .sysfs_ops = &ioat_sysfs_ops, .default_groups = ioat_groups, }; From 28c829977f4072b23f2fd8d341c2795eec0d5bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 4 Mar 2026 22:44:40 +0100 Subject: [PATCH 18/65] dmaengine: ioatdma: make sysfs attributes const MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ioat_sysfs_entry structures are never modified, mark them as read-only. Signed-off-by: Thomas Weißschuh Acked-by: Dave Jiang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260304-sysfs-const-ioat-v2-4-b9b82651219b@weissschuh.net Signed-off-by: Vinod Koul --- drivers/dma/ioat/sysfs.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/dma/ioat/sysfs.c b/drivers/dma/ioat/sysfs.c index da616365fef5..e796ddb5383f 100644 --- a/drivers/dma/ioat/sysfs.c +++ b/drivers/dma/ioat/sysfs.c @@ -32,7 +32,7 @@ static ssize_t cap_show(struct dma_chan *c, char *page) dma_has_cap(DMA_INTERRUPT, dma->cap_mask) ? " intr" : ""); } -static struct ioat_sysfs_entry ioat_cap_attr = __ATTR_RO(cap); +static const struct ioat_sysfs_entry ioat_cap_attr = __ATTR_RO(cap); static ssize_t version_show(struct dma_chan *c, char *page) { @@ -42,15 +42,15 @@ static ssize_t version_show(struct dma_chan *c, char *page) return sprintf(page, "%d.%d\n", ioat_dma->version >> 4, ioat_dma->version & 0xf); } -static struct ioat_sysfs_entry ioat_version_attr = __ATTR_RO(version); +static const struct ioat_sysfs_entry ioat_version_attr = __ATTR_RO(version); static ssize_t ioat_attr_show(struct kobject *kobj, struct attribute *attr, char *page) { - struct ioat_sysfs_entry *entry; + const struct ioat_sysfs_entry *entry; struct ioatdma_chan *ioat_chan; - entry = container_of(attr, struct ioat_sysfs_entry, attr); + entry = container_of_const(attr, struct ioat_sysfs_entry, attr); ioat_chan = container_of(kobj, struct ioatdma_chan, kobj); if (!entry->show) @@ -62,10 +62,10 @@ static ssize_t ioat_attr_store(struct kobject *kobj, struct attribute *attr, const char *page, size_t count) { - struct ioat_sysfs_entry *entry; + const struct ioat_sysfs_entry *entry; struct ioatdma_chan *ioat_chan; - entry = container_of(attr, struct ioat_sysfs_entry, attr); + entry = container_of_const(attr, struct ioat_sysfs_entry, attr); ioat_chan = container_of(kobj, struct ioatdma_chan, kobj); if (!entry->store) @@ -120,7 +120,7 @@ static ssize_t ring_size_show(struct dma_chan *c, char *page) return sprintf(page, "%d\n", (1 << ioat_chan->alloc_order) & ~1); } -static struct ioat_sysfs_entry ring_size_attr = __ATTR_RO(ring_size); +static const struct ioat_sysfs_entry ring_size_attr = __ATTR_RO(ring_size); static ssize_t ring_active_show(struct dma_chan *c, char *page) { @@ -129,7 +129,7 @@ static ssize_t ring_active_show(struct dma_chan *c, char *page) /* ...taken outside the lock, no need to be precise */ return sprintf(page, "%d\n", ioat_ring_active(ioat_chan)); } -static struct ioat_sysfs_entry ring_active_attr = __ATTR_RO(ring_active); +static const struct ioat_sysfs_entry ring_active_attr = __ATTR_RO(ring_active); static ssize_t intr_coalesce_show(struct dma_chan *c, char *page) { @@ -154,9 +154,9 @@ size_t count) return count; } -static struct ioat_sysfs_entry intr_coalesce_attr = __ATTR_RW(intr_coalesce); +static const struct ioat_sysfs_entry intr_coalesce_attr = __ATTR_RW(intr_coalesce); -static struct attribute *ioat_attrs[] = { +static const struct attribute *const ioat_attrs[] = { &ring_size_attr.attr, &ring_active_attr.attr, &ioat_cap_attr.attr, From 5f88899ec7531e1680b1003f32584d7da5922902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 3 Mar 2026 10:25:00 +0000 Subject: [PATCH 19/65] dmaengine: Document cyclic transfer for dmaengine_prep_peripheral_dma_vec() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document that the DMA_PREP_REPEAT flag can be used with the dmaengine_prep_peripheral_dma_vec() to mark a transfer as cyclic similar to dmaengine_prep_dma_cyclic(). Reviewed-by: Frank Li Signed-off-by: Nuno Sá Link: https://patch.msgid.link/20260303-axi-dac-cyclic-support-v2-1-0db27b4be95a@analog.com Signed-off-by: Vinod Koul --- include/linux/dmaengine.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h index 99efe2b9b4ea..b3d251c9734e 100644 --- a/include/linux/dmaengine.h +++ b/include/linux/dmaengine.h @@ -996,7 +996,8 @@ static inline struct dma_async_tx_descriptor *dmaengine_prep_slave_single( * @vecs: The array of DMA vectors that should be transferred * @nents: The number of DMA vectors in the array * @dir: Specifies the direction of the data transfer - * @flags: DMA engine flags + * @flags: DMA engine flags - DMA_PREP_REPEAT can be used to mark a cyclic + * DMA transfer */ static inline struct dma_async_tx_descriptor *dmaengine_prep_peripheral_dma_vec( struct dma_chan *chan, const struct dma_vec *vecs, size_t nents, From ac85913ab71e0de9827b7f8f7fccb9f20943c02f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 3 Mar 2026 10:25:01 +0000 Subject: [PATCH 20/65] dmaengine: dma-axi-dmac: Add cyclic transfers in .device_prep_peripheral_dma_vec() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for cyclic transfers by checking the DMA_PREP_REPEAT flag. If the flag is set, close the loop and clear the flag for the last segment (the same done for .device_prep_dma_cyclic(). Reviewed-by: Frank Li Signed-off-by: Nuno Sá Link: https://patch.msgid.link/20260303-axi-dac-cyclic-support-v2-2-0db27b4be95a@analog.com Signed-off-by: Vinod Koul --- drivers/dma/dma-axi-dmac.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c index eb65872c5d5c..79c01b7d9732 100644 --- a/drivers/dma/dma-axi-dmac.c +++ b/drivers/dma/dma-axi-dmac.c @@ -657,7 +657,12 @@ axi_dmac_prep_peripheral_dma_vec(struct dma_chan *c, const struct dma_vec *vecs, vecs[i].len, dsg); } - desc->cyclic = false; + desc->cyclic = flags & DMA_PREP_REPEAT; + if (desc->cyclic) { + /* Chain the last descriptor to the first, and remove its "last" flag */ + desc->sg[num_sgs - 1].hw->flags &= ~AXI_DMAC_HW_FLAG_LAST; + desc->sg[num_sgs - 1].hw->next_sg_addr = desc->sg[0].hw_phys; + } return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags); } From c60990ba1fb2a6c1ff2789e610aa130f3047a2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 3 Mar 2026 10:25:02 +0000 Subject: [PATCH 21/65] dmaengine: dma-axi-dmac: Add helper for getting next desc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new helper for getting the next valid struct axi_dmac_desc. This will be extended in follow up patches to support to gracefully terminate cyclic transfers. Reviewed-by: Frank Li Signed-off-by: Nuno Sá Link: https://patch.msgid.link/20260303-axi-dac-cyclic-support-v2-3-0db27b4be95a@analog.com Signed-off-by: Vinod Koul --- drivers/dma/dma-axi-dmac.c | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c index 79c01b7d9732..a47e08d3dbbc 100644 --- a/drivers/dma/dma-axi-dmac.c +++ b/drivers/dma/dma-axi-dmac.c @@ -227,10 +227,29 @@ static bool axi_dmac_check_addr(struct axi_dmac_chan *chan, dma_addr_t addr) return true; } +static struct axi_dmac_desc *axi_dmac_get_next_desc(struct axi_dmac *dmac, + struct axi_dmac_chan *chan) +{ + struct virt_dma_desc *vdesc; + struct axi_dmac_desc *desc; + + if (chan->next_desc) + return chan->next_desc; + + vdesc = vchan_next_desc(&chan->vchan); + if (!vdesc) + return NULL; + + list_move_tail(&vdesc->node, &chan->active_descs); + desc = to_axi_dmac_desc(vdesc); + chan->next_desc = desc; + + return desc; +} + static void axi_dmac_start_transfer(struct axi_dmac_chan *chan) { struct axi_dmac *dmac = chan_to_axi_dmac(chan); - struct virt_dma_desc *vdesc; struct axi_dmac_desc *desc; struct axi_dmac_sg *sg; unsigned int flags = 0; @@ -240,16 +259,10 @@ static void axi_dmac_start_transfer(struct axi_dmac_chan *chan) if (val) /* Queue is full, wait for the next SOT IRQ */ return; - desc = chan->next_desc; + desc = axi_dmac_get_next_desc(dmac, chan); + if (!desc) + return; - if (!desc) { - vdesc = vchan_next_desc(&chan->vchan); - if (!vdesc) - return; - list_move_tail(&vdesc->node, &chan->active_descs); - desc = to_axi_dmac_desc(vdesc); - chan->next_desc = desc; - } sg = &desc->sg[desc->num_submitted]; /* Already queued in cyclic mode. Wait for it to finish */ From ca3bf200dea50fada92ec371e9e294b18a589676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 3 Mar 2026 10:25:03 +0000 Subject: [PATCH 22/65] dmaengine: dma-axi-dmac: Gracefully terminate SW cyclic transfers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As of now, to terminate a cyclic transfer, one pretty much needs to use brute force and terminate all transfers with .device_terminate_all(). With this change, when a cyclic transfer terminates (and generates an EOT interrupt), look at any new pending transfer with the DMA_PREP_LOAD_EOT flag set. If there is one, the current cyclic transfer is terminated and the next one is enqueued. If the flag is not set, that transfer is ignored. Signed-off-by: Nuno Sá Link: https://patch.msgid.link/20260303-axi-dac-cyclic-support-v2-4-0db27b4be95a@analog.com Signed-off-by: Vinod Koul --- drivers/dma/dma-axi-dmac.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c index a47e08d3dbbc..e9814d725322 100644 --- a/drivers/dma/dma-axi-dmac.c +++ b/drivers/dma/dma-axi-dmac.c @@ -233,6 +233,11 @@ static struct axi_dmac_desc *axi_dmac_get_next_desc(struct axi_dmac *dmac, struct virt_dma_desc *vdesc; struct axi_dmac_desc *desc; + /* + * It means a SW cyclic transfer is in place so we should just return + * the same descriptor. SW cyclic transfer termination is handled + * in axi_dmac_transfer_done(). + */ if (chan->next_desc) return chan->next_desc; @@ -411,6 +416,32 @@ static void axi_dmac_compute_residue(struct axi_dmac_chan *chan, } } +static bool axi_dmac_handle_cyclic_eot(struct axi_dmac_chan *chan, + struct axi_dmac_desc *active) +{ + struct device *dev = chan_to_axi_dmac(chan)->dma_dev.dev; + struct virt_dma_desc *vdesc; + + /* wrap around */ + active->num_completed = 0; + + vdesc = vchan_next_desc(&chan->vchan); + if (!vdesc) + return false; + if (!(vdesc->tx.flags & DMA_PREP_LOAD_EOT)) { + dev_warn(dev, "Discarding non EOT transfer after cyclic\n"); + list_del(&vdesc->node); + return false; + } + + /* then let's end the cyclic transfer */ + chan->next_desc = NULL; + list_del(&active->vdesc.node); + vchan_cookie_complete(&active->vdesc); + + return true; +} + static bool axi_dmac_transfer_done(struct axi_dmac_chan *chan, unsigned int completed_transfers) { @@ -458,7 +489,8 @@ static bool axi_dmac_transfer_done(struct axi_dmac_chan *chan, if (active->num_completed == active->num_sgs || sg->partial_len) { if (active->cyclic) { - active->num_completed = 0; /* wrap around */ + /* keep start_next as is, if already true... */ + start_next |= axi_dmac_handle_cyclic_eot(chan, active); } else { list_del(&active->vdesc.node); vchan_cookie_complete(&active->vdesc); From f1d201e7e4e7646e55ce4946f0adec4b035ffb4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 3 Mar 2026 10:25:04 +0000 Subject: [PATCH 23/65] dmaengine: dma-axi-dmac: Gracefully terminate HW cyclic transfers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for gracefully terminating hardware cyclic DMA transfers when a new transfer is queued and is flagged with DMA_PREP_LOAD_EOT. Without this, cyclic transfers would continue indefinitely until we brute force it with .device_terminate_all(). When a new descriptor is queued while a cyclic transfer is active, mark the cyclic transfer for termination. For hardware with scatter-gather support, modify the last segment flags to trigger end-of-transfer. For non-SG hardware, clear the CYCLIC flag to allow natural completion. Older IP core versions (pre-4.6.a) can prefetch data when clearing the CYCLIC flag, causing corruption in the next transfer. Work around this by disabling and re-enabling the core to flush prefetched data. The cyclic_eot flag tracks transfers marked for termination, preventing new transfers from starting until the cyclic one completes. Non-EOT transfers submitted after cyclic transfers are discarded with a warning. Also note that for hardware cyclic transfers not using SG, we need to make sure that chan->next_desc is also set to NULL (so we can look at possible EOT transfers) and we also need to move the queue check to after axi_dmac_get_next_desc() because with hardware based cyclic transfers we might get the queue marked as full and hence we would not be able to check for cyclic termination. Signed-off-by: Nuno Sá Link: https://patch.msgid.link/20260303-axi-dac-cyclic-support-v2-5-0db27b4be95a@analog.com Signed-off-by: Vinod Koul --- drivers/dma/dma-axi-dmac.c | 104 ++++++++++++++++++++++++++++++++----- 1 file changed, 91 insertions(+), 13 deletions(-) diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c index e9814d725322..45c2c8e4bc45 100644 --- a/drivers/dma/dma-axi-dmac.c +++ b/drivers/dma/dma-axi-dmac.c @@ -134,6 +134,7 @@ struct axi_dmac_desc { struct axi_dmac_chan *chan; bool cyclic; + bool cyclic_eot; bool have_partial_xfer; unsigned int num_submitted; @@ -162,6 +163,7 @@ struct axi_dmac_chan { bool hw_cyclic; bool hw_2d; bool hw_sg; + bool hw_cyclic_hotfix; }; struct axi_dmac { @@ -227,11 +229,26 @@ static bool axi_dmac_check_addr(struct axi_dmac_chan *chan, dma_addr_t addr) return true; } +static struct axi_dmac_desc *axi_dmac_active_desc(struct axi_dmac_chan *chan) +{ + return list_first_entry_or_null(&chan->active_descs, + struct axi_dmac_desc, vdesc.node); +} + static struct axi_dmac_desc *axi_dmac_get_next_desc(struct axi_dmac *dmac, struct axi_dmac_chan *chan) { + struct axi_dmac_desc *active = axi_dmac_active_desc(chan); struct virt_dma_desc *vdesc; struct axi_dmac_desc *desc; + unsigned int val; + + /* + * Just play safe and ignore any SOF if we have an active cyclic transfer + * flagged to end. We'll start it as soon as the current cyclic one ends. + */ + if (active && active->cyclic_eot) + return NULL; /* * It means a SW cyclic transfer is in place so we should just return @@ -245,11 +262,43 @@ static struct axi_dmac_desc *axi_dmac_get_next_desc(struct axi_dmac *dmac, if (!vdesc) return NULL; + if (active && active->cyclic && !(vdesc->tx.flags & DMA_PREP_LOAD_EOT)) { + struct device *dev = chan_to_axi_dmac(chan)->dma_dev.dev; + + dev_warn(dev, "Discarding non EOT transfer after cyclic\n"); + list_del(&vdesc->node); + return NULL; + } + list_move_tail(&vdesc->node, &chan->active_descs); desc = to_axi_dmac_desc(vdesc); chan->next_desc = desc; - return desc; + if (!active || !active->cyclic) + return desc; + + active->cyclic_eot = true; + + if (chan->hw_sg) { + unsigned long flags = AXI_DMAC_HW_FLAG_IRQ | AXI_DMAC_HW_FLAG_LAST; + /* + * Let's then stop the current cyclic transfer by making sure we + * get an EOT interrupt and to open the cyclic loop by marking + * the last segment. + */ + active->sg[active->num_sgs - 1].hw->flags = flags; + return NULL; + } + + /* + * Clear the cyclic bit if there's no Scatter-Gather HW so that we get + * at the end of the transfer. + */ + val = axi_dmac_read(dmac, AXI_DMAC_REG_FLAGS); + val &= ~AXI_DMAC_FLAG_CYCLIC; + axi_dmac_write(dmac, AXI_DMAC_REG_FLAGS, val); + + return NULL; } static void axi_dmac_start_transfer(struct axi_dmac_chan *chan) @@ -260,14 +309,14 @@ static void axi_dmac_start_transfer(struct axi_dmac_chan *chan) unsigned int flags = 0; unsigned int val; - val = axi_dmac_read(dmac, AXI_DMAC_REG_START_TRANSFER); - if (val) /* Queue is full, wait for the next SOT IRQ */ - return; - desc = axi_dmac_get_next_desc(dmac, chan); if (!desc) return; + val = axi_dmac_read(dmac, AXI_DMAC_REG_START_TRANSFER); + if (val) /* Queue is full, wait for the next SOT IRQ */ + return; + sg = &desc->sg[desc->num_submitted]; /* Already queued in cyclic mode. Wait for it to finish */ @@ -309,10 +358,12 @@ static void axi_dmac_start_transfer(struct axi_dmac_chan *chan) * call, enable hw cyclic mode to avoid unnecessary interrupts. */ if (chan->hw_cyclic && desc->cyclic && !desc->vdesc.tx.callback) { - if (chan->hw_sg) + if (chan->hw_sg) { desc->sg[desc->num_sgs - 1].hw->flags &= ~AXI_DMAC_HW_FLAG_IRQ; - else if (desc->num_sgs == 1) + } else if (desc->num_sgs == 1) { + chan->next_desc = NULL; flags |= AXI_DMAC_FLAG_CYCLIC; + } } if (chan->hw_partial_xfer) @@ -330,12 +381,6 @@ static void axi_dmac_start_transfer(struct axi_dmac_chan *chan) axi_dmac_write(dmac, AXI_DMAC_REG_START_TRANSFER, 1); } -static struct axi_dmac_desc *axi_dmac_active_desc(struct axi_dmac_chan *chan) -{ - return list_first_entry_or_null(&chan->active_descs, - struct axi_dmac_desc, vdesc.node); -} - static inline unsigned int axi_dmac_total_sg_bytes(struct axi_dmac_chan *chan, struct axi_dmac_sg *sg) { @@ -425,6 +470,35 @@ static bool axi_dmac_handle_cyclic_eot(struct axi_dmac_chan *chan, /* wrap around */ active->num_completed = 0; + if (active->cyclic_eot) { + /* + * It means an HW cyclic transfer was marked to stop. And we + * know we have something to schedule, so start the next + * transfer now the cyclic one is done. + */ + list_del(&active->vdesc.node); + vchan_cookie_complete(&active->vdesc); + + if (chan->hw_cyclic_hotfix) { + struct axi_dmac *dmac = chan_to_axi_dmac(chan); + /* + * In older IP cores, ending a cyclic transfer by clearing + * the CYCLIC flag does not guarantee a graceful end. + * It can happen that some data (of the next frame) is + * already prefetched and will be wrongly visible in the + * next transfer. To workaround this, we need to reenable + * the core so everything is flushed. Newer cores handles + * this correctly and do not require this "hotfix". The + * SG IP also does not require this. + */ + dev_dbg(dev, "HW cyclic hotfix\n"); + axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0); + axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, AXI_DMAC_CTRL_ENABLE); + } + + return true; + } + vdesc = vchan_next_desc(&chan->vchan); if (!vdesc) return false; @@ -460,6 +534,7 @@ static bool axi_dmac_transfer_done(struct axi_dmac_chan *chan, if (chan->hw_sg) { if (active->cyclic) { vchan_cyclic_callback(&active->vdesc); + start_next = axi_dmac_handle_cyclic_eot(chan, active); } else { list_del(&active->vdesc.node); vchan_cookie_complete(&active->vdesc); @@ -1103,6 +1178,9 @@ static int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version) chan->length_align_mask = chan->address_align_mask; } + if (version < ADI_AXI_PCORE_VER(4, 6, 0) && !chan->hw_sg) + chan->hw_cyclic_hotfix = true; + return 0; } From d9587042b50f69d35a6e05c1b7fc9092e26625a6 Mon Sep 17 00:00:00 2001 From: Kelvin Cao Date: Mon, 2 Mar 2026 14:04:17 -0700 Subject: [PATCH 24/65] dmaengine: switchtec-dma: Introduce Switchtec DMA engine skeleton Some Switchtec Switches can expose DMA engines via extra PCI functions on the upstream ports. At most one such function can be supported on each upstream port. Each function can have one or more DMA channels. This patch is just the core PCI driver skeleton and dma engine registration. Signed-off-by: Kelvin Cao Co-developed-by: George Ge Signed-off-by: George Ge Reviewed-by: Christoph Hellwig Reviewed-by: Frank Li Signed-off-by: Logan Gunthorpe Link: https://patch.msgid.link/20260302210419.3656-2-logang@deltatee.com Signed-off-by: Vinod Koul --- MAINTAINERS | 7 ++ drivers/dma/Kconfig | 9 ++ drivers/dma/Makefile | 1 + drivers/dma/switchtec_dma.c | 209 ++++++++++++++++++++++++++++++++++++ 4 files changed, 226 insertions(+) create mode 100644 drivers/dma/switchtec_dma.c diff --git a/MAINTAINERS b/MAINTAINERS index 55af015174a5..b38452804a2d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25524,6 +25524,13 @@ S: Supported F: include/net/switchdev.h F: net/switchdev/ +SWITCHTEC DMA DRIVER +M: Kelvin Cao +M: Logan Gunthorpe +L: dmaengine@vger.kernel.org +S: Maintained +F: drivers/dma/switchtec_dma.c + SY8106A REGULATOR DRIVER M: Icenowy Zheng S: Maintained diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 66cda7cc9f7a..e249f68423ba 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -610,6 +610,15 @@ config SPRD_DMA help Enable support for the on-chip DMA controller on Spreadtrum platform. +config SWITCHTEC_DMA + tristate "Switchtec PSX/PFX Switch DMA Engine Support" + depends on PCI + select DMA_ENGINE + help + Some Switchtec PSX/PFX PCIe Switches support additional DMA engines. + These are exposed via an extra function on the switch's upstream + port. + config TXX9_DMAC tristate "Toshiba TXx9 SoC DMA support" depends on MACH_TX49XX diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index a54d7688392b..df566c4958b6 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -74,6 +74,7 @@ obj-$(CONFIG_SF_PDMA) += sf-pdma/ obj-$(CONFIG_SOPHGO_CV1800B_DMAMUX) += cv1800b-dmamux.o obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o obj-$(CONFIG_SPRD_DMA) += sprd-dma.o +obj-$(CONFIG_SWITCHTEC_DMA) += switchtec_dma.o obj-$(CONFIG_TXX9_DMAC) += txx9dmac.o obj-$(CONFIG_TEGRA186_GPC_DMA) += tegra186-gpc-dma.o obj-$(CONFIG_TEGRA20_APB_DMA) += tegra20-apb-dma.o diff --git a/drivers/dma/switchtec_dma.c b/drivers/dma/switchtec_dma.c new file mode 100644 index 000000000000..2e7226333107 --- /dev/null +++ b/drivers/dma/switchtec_dma.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Microchip Switchtec(tm) DMA Controller Driver + * Copyright (c) 2025, Kelvin Cao + * Copyright (c) 2025, Microchip Corporation + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "dmaengine.h" + +MODULE_DESCRIPTION("Switchtec PCIe Switch DMA Engine"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kelvin Cao"); + +struct switchtec_dma_dev { + struct dma_device dma_dev; + struct pci_dev __rcu *pdev; + void __iomem *bar; +}; + +static void switchtec_dma_release(struct dma_device *dma_dev) +{ + struct switchtec_dma_dev *swdma_dev = + container_of(dma_dev, struct switchtec_dma_dev, dma_dev); + + put_device(dma_dev->dev); + kfree(swdma_dev); +} + +static int switchtec_dma_create(struct pci_dev *pdev) +{ + struct switchtec_dma_dev *swdma_dev; + struct dma_device *dma; + struct dma_chan *chan; + int nr_vecs, rc; + + /* + * Create the switchtec dma device + */ + swdma_dev = kzalloc_obj(*swdma_dev, GFP_KERNEL); + if (!swdma_dev) + return -ENOMEM; + + swdma_dev->bar = ioremap(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + + RCU_INIT_POINTER(swdma_dev->pdev, pdev); + + nr_vecs = pci_msix_vec_count(pdev); + rc = pci_alloc_irq_vectors(pdev, nr_vecs, nr_vecs, PCI_IRQ_MSIX); + if (rc < 0) + goto err_exit; + + dma = &swdma_dev->dma_dev; + dma->copy_align = DMAENGINE_ALIGN_8_BYTES; + dma->dev = get_device(&pdev->dev); + + dma->device_release = switchtec_dma_release; + + rc = dma_async_device_register(dma); + if (rc) { + pci_err(pdev, "Failed to register dma device: %d\n", rc); + goto err_exit; + } + + list_for_each_entry(chan, &dma->channels, device_node) + pci_dbg(pdev, "%s\n", dma_chan_name(chan)); + + pci_set_drvdata(pdev, swdma_dev); + + return 0; + +err_exit: + iounmap(swdma_dev->bar); + kfree(swdma_dev); + return rc; +} + +static int switchtec_dma_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int rc; + + rc = pci_enable_device(pdev); + if (rc) + return rc; + + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + + rc = pci_request_mem_regions(pdev, KBUILD_MODNAME); + if (rc) + goto err_disable; + + pci_set_master(pdev); + + rc = switchtec_dma_create(pdev); + if (rc) + goto err_free; + + return 0; + +err_free: + pci_free_irq_vectors(pdev); + pci_release_mem_regions(pdev); + +err_disable: + pci_disable_device(pdev); + + return rc; +} + +static void switchtec_dma_remove(struct pci_dev *pdev) +{ + struct switchtec_dma_dev *swdma_dev = pci_get_drvdata(pdev); + + rcu_assign_pointer(swdma_dev->pdev, NULL); + synchronize_rcu(); + + pci_free_irq_vectors(pdev); + + dma_async_device_unregister(&swdma_dev->dma_dev); + + iounmap(swdma_dev->bar); + pci_release_mem_regions(pdev); + pci_disable_device(pdev); +} + +/* + * Also use the class code to identify the devices, as some of the + * device IDs are also used for other devices with other classes by + * Microsemi. + */ +#define SW_ID(vendor_id, device_id) \ + { \ + .vendor = vendor_id, \ + .device = device_id, \ + .subvendor = PCI_ANY_ID, \ + .subdevice = PCI_ANY_ID, \ + .class = PCI_CLASS_SYSTEM_OTHER << 8, \ + .class_mask = 0xffffffff, \ + } + +static const struct pci_device_id switchtec_dma_pci_tbl[] = { + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4000), /* PFX 100XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4084), /* PFX 84XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4068), /* PFX 68XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4052), /* PFX 52XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4036), /* PFX 36XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4028), /* PFX 28XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4100), /* PSX 100XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4184), /* PSX 84XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4168), /* PSX 68XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4152), /* PSX 52XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4136), /* PSX 36XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4128), /* PSX 28XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4352), /* PFXA 52XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4336), /* PFXA 36XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4328), /* PFXA 28XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4452), /* PSXA 52XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4436), /* PSXA 36XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4428), /* PSXA 28XG4 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5000), /* PFX 100XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5084), /* PFX 84XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5068), /* PFX 68XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5052), /* PFX 52XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5036), /* PFX 36XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5028), /* PFX 28XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5100), /* PSX 100XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5184), /* PSX 84XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5168), /* PSX 68XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5152), /* PSX 52XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5136), /* PSX 36XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5128), /* PSX 28XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5300), /* PFXA 100XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5384), /* PFXA 84XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5368), /* PFXA 68XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5352), /* PFXA 52XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5336), /* PFXA 36XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5328), /* PFXA 28XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5400), /* PSXA 100XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5484), /* PSXA 84XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5468), /* PSXA 68XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5452), /* PSXA 52XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5436), /* PSXA 36XG5 */ + SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5428), /* PSXA 28XG5 */ + SW_ID(PCI_VENDOR_ID_EFAR, 0x1001), /* PCI1001 16XG4 */ + SW_ID(PCI_VENDOR_ID_EFAR, 0x1002), /* PCI1002 16XG4 */ + SW_ID(PCI_VENDOR_ID_EFAR, 0x1003), /* PCI1003 16XG4 */ + SW_ID(PCI_VENDOR_ID_EFAR, 0x1004), /* PCI1004 16XG4 */ + SW_ID(PCI_VENDOR_ID_EFAR, 0x1005), /* PCI1005 16XG4 */ + SW_ID(PCI_VENDOR_ID_EFAR, 0x1006), /* PCI1006 16XG4 */ + {0} +}; +MODULE_DEVICE_TABLE(pci, switchtec_dma_pci_tbl); + +static struct pci_driver switchtec_dma_pci_driver = { + .name = KBUILD_MODNAME, + .id_table = switchtec_dma_pci_tbl, + .probe = switchtec_dma_probe, + .remove = switchtec_dma_remove, +}; +module_pci_driver(switchtec_dma_pci_driver); From 30eba9df76adf1294e88214dbf9cea402fa7af37 Mon Sep 17 00:00:00 2001 From: Kelvin Cao Date: Mon, 2 Mar 2026 14:04:18 -0700 Subject: [PATCH 25/65] dmaengine: switchtec-dma: Implement hardware initialization and cleanup Initialize the hardware and create the dma channel queues. Signed-off-by: Kelvin Cao Co-developed-by: George Ge Signed-off-by: George Ge Reviewed-by: Christoph Hellwig Reviewed-by: Frank Li Signed-off-by: Logan Gunthorpe Link: https://patch.msgid.link/20260302210419.3656-3-logang@deltatee.com Signed-off-by: Vinod Koul --- drivers/dma/switchtec_dma.c | 1007 ++++++++++++++++++++++++++++++++++- 1 file changed, 1005 insertions(+), 2 deletions(-) diff --git a/drivers/dma/switchtec_dma.c b/drivers/dma/switchtec_dma.c index 2e7226333107..4c0dacc24608 100644 --- a/drivers/dma/switchtec_dma.c +++ b/drivers/dma/switchtec_dma.c @@ -19,16 +19,976 @@ MODULE_DESCRIPTION("Switchtec PCIe Switch DMA Engine"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Kelvin Cao"); +#define SWITCHTEC_DMAC_CHAN_CTRL_OFFSET 0x1000 +#define SWITCHTEC_DMAC_CHAN_CFG_STS_OFFSET 0x160000 + +#define SWITCHTEC_DMA_CHAN_HW_REGS_SIZE 0x1000 +#define SWITCHTEC_DMA_CHAN_FW_REGS_SIZE 0x80 + +#define SWITCHTEC_REG_CAP 0x80 +#define SWITCHTEC_REG_CHAN_CNT 0x84 +#define SWITCHTEC_REG_TAG_LIMIT 0x90 +#define SWITCHTEC_REG_CHAN_STS_VEC 0x94 +#define SWITCHTEC_REG_SE_BUF_CNT 0x98 +#define SWITCHTEC_REG_SE_BUF_BASE 0x9a + +#define SWITCHTEC_CHAN_CTRL_PAUSE BIT(0) +#define SWITCHTEC_CHAN_CTRL_HALT BIT(1) +#define SWITCHTEC_CHAN_CTRL_RESET BIT(2) +#define SWITCHTEC_CHAN_CTRL_ERR_PAUSE BIT(3) + +#define SWITCHTEC_CHAN_STS_PAUSED BIT(9) +#define SWITCHTEC_CHAN_STS_HALTED BIT(10) +#define SWITCHTEC_CHAN_STS_PAUSED_MASK GENMASK(29, 13) + +#define SWITCHTEC_DMA_SQ_SIZE SZ_32K +#define SWITCHTEC_DMA_CQ_SIZE SZ_32K + +#define SWITCHTEC_DMA_RING_SIZE SZ_32K + +static const char * const channel_status_str[] = { + [13] = "received a VDM with length error status", + [14] = "received a VDM or Cpl with Unsupported Request error status", + [15] = "received a VDM or Cpl with Completion Abort error status", + [16] = "received a VDM with ECRC error status", + [17] = "received a VDM with EP error status", + [18] = "received a VDM with Reserved Cpl error status", + [19] = "received only part of split SE CplD", + [20] = "the ISP_DMAC detected a Completion Time Out", + [21] = "received a Cpl with Unsupported Request status", + [22] = "received a Cpl with Completion Abort status", + [23] = "received a Cpl with a reserved status", + [24] = "received a TLP with ECRC error status in its metadata", + [25] = "received a TLP with the EP bit set in the header", + [26] = "the ISP_DMAC tried to process a SE with an invalid Connection ID", + [27] = "the ISP_DMAC tried to process a SE with an invalid Remote Host interrupt", + [28] = "a reserved opcode was detected in an SE", + [29] = "received a SE Cpl with error status", +}; + +struct chan_hw_regs { + u16 cq_head; + u16 rsvd1; + u16 sq_tail; + u16 rsvd2; + u8 ctrl; + u8 rsvd3[3]; + u16 status; + u16 rsvd4; +}; + +#define PERF_BURST_SCALE_MASK GENMASK_U32(3, 2) +#define PERF_MRRS_MASK GENMASK_U32(6, 4) +#define PERF_INTERVAL_MASK GENMASK_U32(10, 8) +#define PERF_BURST_SIZE_MASK GENMASK_U32(14, 12) +#define PERF_ARB_WEIGHT_MASK GENMASK_U32(31, 24) + +#define SE_BUF_BASE_MASK GENMASK_U32(10, 2) +#define SE_BUF_LEN_MASK GENMASK_U32(20, 12) +#define SE_THRESH_MASK GENMASK_U32(31, 23) + +#define SWITCHTEC_CHAN_ENABLE BIT(1) + +struct chan_fw_regs { + u32 valid_en_se; + u32 cq_base_lo; + u32 cq_base_hi; + u16 cq_size; + u16 rsvd1; + u32 sq_base_lo; + u32 sq_base_hi; + u16 sq_size; + u16 rsvd2; + u32 int_vec; + u32 perf_cfg; + u32 rsvd3; + u32 perf_latency_selector; + u32 perf_fetched_se_cnt_lo; + u32 perf_fetched_se_cnt_hi; + u32 perf_byte_cnt_lo; + u32 perf_byte_cnt_hi; + u32 rsvd4; + u16 perf_se_pending; + u16 perf_se_buf_empty; + u32 perf_chan_idle; + u32 perf_lat_max; + u32 perf_lat_min; + u32 perf_lat_last; + u16 sq_current; + u16 sq_phase; + u16 cq_current; + u16 cq_phase; +}; + +struct switchtec_dma_chan { + struct switchtec_dma_dev *swdma_dev; + struct dma_chan dma_chan; + struct chan_hw_regs __iomem *mmio_chan_hw; + struct chan_fw_regs __iomem *mmio_chan_fw; + + /* Serialize hardware control register access */ + spinlock_t hw_ctrl_lock; + + struct tasklet_struct desc_task; + + /* Serialize descriptor preparation */ + spinlock_t submit_lock; + bool ring_active; + int cid; + + /* Serialize completion processing */ + spinlock_t complete_lock; + bool comp_ring_active; + + /* channel index and irq */ + int index; + int irq; + + /* + * In driver context, head is advanced by producer while + * tail is advanced by consumer. + */ + + /* the head and tail for both desc_ring and hw_sq */ + int head; + int tail; + int phase_tag; + struct switchtec_dma_hw_se_desc *hw_sq; + dma_addr_t dma_addr_sq; + + /* the tail for hw_cq */ + int cq_tail; + struct switchtec_dma_hw_ce *hw_cq; + dma_addr_t dma_addr_cq; + + struct list_head list; + + struct switchtec_dma_desc *desc_ring[SWITCHTEC_DMA_RING_SIZE]; +}; + struct switchtec_dma_dev { struct dma_device dma_dev; struct pci_dev __rcu *pdev; void __iomem *bar; + + struct switchtec_dma_chan **swdma_chans; + int chan_cnt; + int chan_status_irq; }; +enum chan_op { + ENABLE_CHAN, + DISABLE_CHAN, +}; + +enum switchtec_dma_opcode { + SWITCHTEC_DMA_OPC_MEMCPY = 0, + SWITCHTEC_DMA_OPC_RDIMM = 0x1, + SWITCHTEC_DMA_OPC_WRIMM = 0x2, + SWITCHTEC_DMA_OPC_RHI = 0x6, + SWITCHTEC_DMA_OPC_NOP = 0x7, +}; + +struct switchtec_dma_hw_se_desc { + u8 opc; + u8 ctrl; + __le16 tlp_setting; + __le16 rsvd1; + __le16 cid; + __le32 byte_cnt; + __le32 addr_lo; /* SADDR_LO/WIADDR_LO */ + __le32 addr_hi; /* SADDR_HI/WIADDR_HI */ + __le32 daddr_lo; + __le32 daddr_hi; + __le16 dfid; + __le16 sfid; +}; + +#define SWITCHTEC_CE_SC_LEN_ERR BIT(0) +#define SWITCHTEC_CE_SC_UR BIT(1) +#define SWITCHTEC_CE_SC_CA BIT(2) +#define SWITCHTEC_CE_SC_RSVD_CPL BIT(3) +#define SWITCHTEC_CE_SC_ECRC_ERR BIT(4) +#define SWITCHTEC_CE_SC_EP_SET BIT(5) +#define SWITCHTEC_CE_SC_D_RD_CTO BIT(8) +#define SWITCHTEC_CE_SC_D_RIMM_UR BIT(9) +#define SWITCHTEC_CE_SC_D_RIMM_CA BIT(10) +#define SWITCHTEC_CE_SC_D_RIMM_RSVD_CPL BIT(11) +#define SWITCHTEC_CE_SC_D_ECRC BIT(12) +#define SWITCHTEC_CE_SC_D_EP_SET BIT(13) +#define SWITCHTEC_CE_SC_D_BAD_CONNID BIT(14) +#define SWITCHTEC_CE_SC_D_BAD_RHI_ADDR BIT(15) +#define SWITCHTEC_CE_SC_D_INVD_CMD BIT(16) +#define SWITCHTEC_CE_SC_MASK GENMASK(16, 0) + +struct switchtec_dma_hw_ce { + __le32 rdimm_cpl_dw0; + __le32 rdimm_cpl_dw1; + __le32 rsvd1; + __le32 cpl_byte_cnt; + __le16 sq_head; + __le16 rsvd2; + __le32 rsvd3; + __le32 sts_code; + __le16 cid; + __le16 phase_tag; +}; + +struct switchtec_dma_desc { + struct dma_async_tx_descriptor txd; + struct switchtec_dma_hw_se_desc *hw; + u32 orig_size; + bool completed; +}; + +static int wait_for_chan_status(struct chan_hw_regs __iomem *chan_hw, u32 mask, + bool set) +{ + u32 status; + + return readl_poll_timeout_atomic(&chan_hw->status, status, + (set && (status & mask)) || + (!set && !(status & mask)), + 10, 100 * USEC_PER_MSEC); +} + +static int halt_channel(struct switchtec_dma_chan *swdma_chan) +{ + struct chan_hw_regs __iomem *chan_hw = swdma_chan->mmio_chan_hw; + struct pci_dev *pdev; + int ret; + + rcu_read_lock(); + pdev = rcu_dereference(swdma_chan->swdma_dev->pdev); + if (!pdev) { + ret = -ENODEV; + goto unlock_and_exit; + } + + spin_lock(&swdma_chan->hw_ctrl_lock); + writeb(SWITCHTEC_CHAN_CTRL_HALT, &chan_hw->ctrl); + ret = wait_for_chan_status(chan_hw, SWITCHTEC_CHAN_STS_HALTED, true); + spin_unlock(&swdma_chan->hw_ctrl_lock); + +unlock_and_exit: + rcu_read_unlock(); + return ret; +} + +static int unhalt_channel(struct switchtec_dma_chan *swdma_chan) +{ + struct chan_hw_regs __iomem *chan_hw = swdma_chan->mmio_chan_hw; + struct pci_dev *pdev; + u8 ctrl; + int ret; + + rcu_read_lock(); + pdev = rcu_dereference(swdma_chan->swdma_dev->pdev); + if (!pdev) { + ret = -ENODEV; + goto unlock_and_exit; + } + + spin_lock(&swdma_chan->hw_ctrl_lock); + ctrl = readb(&chan_hw->ctrl); + ctrl &= ~SWITCHTEC_CHAN_CTRL_HALT; + writeb(ctrl, &chan_hw->ctrl); + ret = wait_for_chan_status(chan_hw, SWITCHTEC_CHAN_STS_HALTED, false); + spin_unlock(&swdma_chan->hw_ctrl_lock); + +unlock_and_exit: + rcu_read_unlock(); + return ret; +} + +static void flush_pci_write(struct chan_hw_regs __iomem *chan_hw) +{ + readl(&chan_hw->cq_head); +} + +static int reset_channel(struct switchtec_dma_chan *swdma_chan) +{ + struct chan_hw_regs __iomem *chan_hw = swdma_chan->mmio_chan_hw; + struct pci_dev *pdev; + + rcu_read_lock(); + pdev = rcu_dereference(swdma_chan->swdma_dev->pdev); + if (!pdev) { + rcu_read_unlock(); + return -ENODEV; + } + + spin_lock(&swdma_chan->hw_ctrl_lock); + writel(SWITCHTEC_CHAN_CTRL_RESET | SWITCHTEC_CHAN_CTRL_ERR_PAUSE, + &chan_hw->ctrl); + flush_pci_write(chan_hw); + + udelay(1000); + + writel(SWITCHTEC_CHAN_CTRL_ERR_PAUSE, &chan_hw->ctrl); + spin_unlock(&swdma_chan->hw_ctrl_lock); + flush_pci_write(chan_hw); + + rcu_read_unlock(); + return 0; +} + +static int pause_reset_channel(struct switchtec_dma_chan *swdma_chan) +{ + struct chan_hw_regs __iomem *chan_hw = swdma_chan->mmio_chan_hw; + struct pci_dev *pdev; + + rcu_read_lock(); + pdev = rcu_dereference(swdma_chan->swdma_dev->pdev); + if (!pdev) { + rcu_read_unlock(); + return -ENODEV; + } + + spin_lock(&swdma_chan->hw_ctrl_lock); + writeb(SWITCHTEC_CHAN_CTRL_PAUSE, &chan_hw->ctrl); + spin_unlock(&swdma_chan->hw_ctrl_lock); + + flush_pci_write(chan_hw); + + rcu_read_unlock(); + + /* wait 60ms to ensure no pending CEs */ + mdelay(60); + + return reset_channel(swdma_chan); +} + +static int channel_op(struct switchtec_dma_chan *swdma_chan, int op) +{ + struct chan_fw_regs __iomem *chan_fw = swdma_chan->mmio_chan_fw; + struct pci_dev *pdev; + u32 valid_en_se; + + rcu_read_lock(); + pdev = rcu_dereference(swdma_chan->swdma_dev->pdev); + if (!pdev) { + rcu_read_unlock(); + return -ENODEV; + } + + valid_en_se = readl(&chan_fw->valid_en_se); + if (op == ENABLE_CHAN) + valid_en_se |= SWITCHTEC_CHAN_ENABLE; + else + valid_en_se &= ~SWITCHTEC_CHAN_ENABLE; + + writel(valid_en_se, &chan_fw->valid_en_se); + + rcu_read_unlock(); + return 0; +} + +static int enable_channel(struct switchtec_dma_chan *swdma_chan) +{ + return channel_op(swdma_chan, ENABLE_CHAN); +} + +static int disable_channel(struct switchtec_dma_chan *swdma_chan) +{ + return channel_op(swdma_chan, DISABLE_CHAN); +} + +static void +switchtec_dma_cleanup_completed(struct switchtec_dma_chan *swdma_chan) +{ + struct device *chan_dev = &swdma_chan->dma_chan.dev->device; + struct switchtec_dma_desc *desc; + struct switchtec_dma_hw_ce *ce; + struct dmaengine_result res; + int tail, cid, se_idx, i; + __le16 phase_tag; + u32 sts_code; + __le32 *p; + + do { + spin_lock_bh(&swdma_chan->complete_lock); + if (!swdma_chan->comp_ring_active) { + spin_unlock_bh(&swdma_chan->complete_lock); + break; + } + + ce = &swdma_chan->hw_cq[swdma_chan->cq_tail]; + /* + * phase_tag is updated by hardware, ensure the value is + * not from the cache + */ + phase_tag = smp_load_acquire(&ce->phase_tag); + if (le16_to_cpu(phase_tag) == swdma_chan->phase_tag) { + spin_unlock_bh(&swdma_chan->complete_lock); + break; + } + + cid = le16_to_cpu(ce->cid); + se_idx = cid & (SWITCHTEC_DMA_SQ_SIZE - 1); + desc = swdma_chan->desc_ring[se_idx]; + + tail = swdma_chan->tail; + + res.residue = desc->orig_size - le32_to_cpu(ce->cpl_byte_cnt); + + sts_code = le32_to_cpu(ce->sts_code); + + if (!(sts_code & SWITCHTEC_CE_SC_MASK)) { + res.result = DMA_TRANS_NOERROR; + } else { + if (sts_code & SWITCHTEC_CE_SC_D_RD_CTO) + res.result = DMA_TRANS_READ_FAILED; + else + res.result = DMA_TRANS_WRITE_FAILED; + + dev_err(chan_dev, "CID 0x%04x failed, SC 0x%08x\n", cid, + (u32)(sts_code & SWITCHTEC_CE_SC_MASK)); + + p = (__le32 *)ce; + for (i = 0; i < sizeof(*ce) / 4; i++) { + dev_err(chan_dev, "CE DW%d: 0x%08x\n", i, + le32_to_cpu(*p)); + p++; + } + } + + desc->completed = true; + + swdma_chan->cq_tail++; + swdma_chan->cq_tail &= SWITCHTEC_DMA_CQ_SIZE - 1; + + rcu_read_lock(); + if (!rcu_dereference(swdma_chan->swdma_dev->pdev)) { + rcu_read_unlock(); + spin_unlock_bh(&swdma_chan->complete_lock); + return; + } + writew(swdma_chan->cq_tail, &swdma_chan->mmio_chan_hw->cq_head); + rcu_read_unlock(); + + if (swdma_chan->cq_tail == 0) + swdma_chan->phase_tag = !swdma_chan->phase_tag; + + /* Out of order CE */ + if (se_idx != tail) { + spin_unlock_bh(&swdma_chan->complete_lock); + continue; + } + + do { + dma_cookie_complete(&desc->txd); + dma_descriptor_unmap(&desc->txd); + dmaengine_desc_get_callback_invoke(&desc->txd, &res); + desc->txd.callback = NULL; + desc->txd.callback_result = NULL; + desc->completed = false; + + tail++; + tail &= SWITCHTEC_DMA_SQ_SIZE - 1; + + /* + * Ensure the desc updates are visible before updating + * the tail index + */ + smp_store_release(&swdma_chan->tail, tail); + desc = swdma_chan->desc_ring[swdma_chan->tail]; + if (!desc->completed) + break; + } while (CIRC_CNT(READ_ONCE(swdma_chan->head), swdma_chan->tail, + SWITCHTEC_DMA_SQ_SIZE)); + + spin_unlock_bh(&swdma_chan->complete_lock); + } while (1); +} + +static void +switchtec_dma_abort_desc(struct switchtec_dma_chan *swdma_chan, int force) +{ + struct switchtec_dma_desc *desc; + struct dmaengine_result res; + + if (!force) + switchtec_dma_cleanup_completed(swdma_chan); + + spin_lock_bh(&swdma_chan->complete_lock); + + while (CIRC_CNT(swdma_chan->head, swdma_chan->tail, + SWITCHTEC_DMA_SQ_SIZE) >= 1) { + desc = swdma_chan->desc_ring[swdma_chan->tail]; + + res.residue = desc->orig_size; + res.result = DMA_TRANS_ABORTED; + + dma_cookie_complete(&desc->txd); + dma_descriptor_unmap(&desc->txd); + if (!force) + dmaengine_desc_get_callback_invoke(&desc->txd, &res); + desc->txd.callback = NULL; + desc->txd.callback_result = NULL; + + swdma_chan->tail++; + swdma_chan->tail &= SWITCHTEC_DMA_SQ_SIZE - 1; + } + + spin_unlock_bh(&swdma_chan->complete_lock); +} + +static void switchtec_dma_chan_stop(struct switchtec_dma_chan *swdma_chan) +{ + int rc; + + rc = halt_channel(swdma_chan); + if (rc) + return; + + rcu_read_lock(); + if (!rcu_dereference(swdma_chan->swdma_dev->pdev)) { + rcu_read_unlock(); + return; + } + + writel(0, &swdma_chan->mmio_chan_fw->sq_base_lo); + writel(0, &swdma_chan->mmio_chan_fw->sq_base_hi); + writel(0, &swdma_chan->mmio_chan_fw->cq_base_lo); + writel(0, &swdma_chan->mmio_chan_fw->cq_base_hi); + + rcu_read_unlock(); +} + +static int switchtec_dma_terminate_all(struct dma_chan *chan) +{ + struct switchtec_dma_chan *swdma_chan = + container_of(chan, struct switchtec_dma_chan, dma_chan); + + spin_lock_bh(&swdma_chan->complete_lock); + swdma_chan->comp_ring_active = false; + spin_unlock_bh(&swdma_chan->complete_lock); + + return pause_reset_channel(swdma_chan); +} + +static void switchtec_dma_synchronize(struct dma_chan *chan) +{ + struct switchtec_dma_chan *swdma_chan = + container_of(chan, struct switchtec_dma_chan, dma_chan); + + int rc; + + switchtec_dma_abort_desc(swdma_chan, 1); + + rc = enable_channel(swdma_chan); + if (rc) + return; + + rc = reset_channel(swdma_chan); + if (rc) + return; + + rc = unhalt_channel(swdma_chan); + if (rc) + return; + + spin_lock_bh(&swdma_chan->submit_lock); + swdma_chan->head = 0; + spin_unlock_bh(&swdma_chan->submit_lock); + + spin_lock_bh(&swdma_chan->complete_lock); + swdma_chan->comp_ring_active = true; + swdma_chan->phase_tag = 0; + swdma_chan->tail = 0; + swdma_chan->cq_tail = 0; + swdma_chan->cid = 0; + dma_cookie_init(chan); + spin_unlock_bh(&swdma_chan->complete_lock); +} + +static void switchtec_dma_desc_task(unsigned long data) +{ + struct switchtec_dma_chan *swdma_chan = (void *)data; + + switchtec_dma_cleanup_completed(swdma_chan); +} + +static irqreturn_t switchtec_dma_isr(int irq, void *chan) +{ + struct switchtec_dma_chan *swdma_chan = chan; + + if (swdma_chan->comp_ring_active) + tasklet_schedule(&swdma_chan->desc_task); + + return IRQ_HANDLED; +} + +static irqreturn_t switchtec_dma_chan_status_isr(int irq, void *dma) +{ + struct switchtec_dma_dev *swdma_dev = dma; + struct dma_device *dma_dev = &swdma_dev->dma_dev; + struct switchtec_dma_chan *swdma_chan; + struct chan_hw_regs __iomem *chan_hw; + struct device *chan_dev; + struct dma_chan *chan; + u32 chan_status; + int bit; + + list_for_each_entry(chan, &dma_dev->channels, device_node) { + swdma_chan = container_of(chan, struct switchtec_dma_chan, + dma_chan); + chan_dev = &swdma_chan->dma_chan.dev->device; + chan_hw = swdma_chan->mmio_chan_hw; + + rcu_read_lock(); + if (!rcu_dereference(swdma_dev->pdev)) { + rcu_read_unlock(); + goto out; + } + + chan_status = readl(&chan_hw->status); + chan_status &= SWITCHTEC_CHAN_STS_PAUSED_MASK; + rcu_read_unlock(); + + bit = ffs(chan_status); + if (!bit) + dev_dbg(chan_dev, "No pause bit set.\n"); + else + dev_err(chan_dev, "Paused, %s\n", + channel_status_str[bit - 1]); + } + +out: + return IRQ_HANDLED; +} + +static void switchtec_dma_free_desc(struct switchtec_dma_chan *swdma_chan) +{ + struct switchtec_dma_dev *swdma_dev = swdma_chan->swdma_dev; + size_t size; + int i; + + size = SWITCHTEC_DMA_SQ_SIZE * sizeof(*swdma_chan->hw_sq); + if (swdma_chan->hw_sq) + dma_free_coherent(swdma_dev->dma_dev.dev, size, + swdma_chan->hw_sq, swdma_chan->dma_addr_sq); + + size = SWITCHTEC_DMA_CQ_SIZE * sizeof(*swdma_chan->hw_cq); + if (swdma_chan->hw_cq) + dma_free_coherent(swdma_dev->dma_dev.dev, size, + swdma_chan->hw_cq, swdma_chan->dma_addr_cq); + + for (i = 0; i < SWITCHTEC_DMA_RING_SIZE; i++) + kfree(swdma_chan->desc_ring[i]); +} + +static int switchtec_dma_alloc_desc(struct switchtec_dma_chan *swdma_chan) +{ + struct switchtec_dma_dev *swdma_dev = swdma_chan->swdma_dev; + struct chan_fw_regs __iomem *chan_fw = swdma_chan->mmio_chan_fw; + struct switchtec_dma_desc *desc; + struct pci_dev *pdev; + size_t size; + int rc, i; + + swdma_chan->head = 0; + swdma_chan->tail = 0; + swdma_chan->cq_tail = 0; + + size = SWITCHTEC_DMA_SQ_SIZE * sizeof(*swdma_chan->hw_sq); + swdma_chan->hw_sq = dma_alloc_coherent(swdma_dev->dma_dev.dev, size, + &swdma_chan->dma_addr_sq, + GFP_NOWAIT); + if (!swdma_chan->hw_sq) { + rc = -ENOMEM; + goto free_and_exit; + } + + size = SWITCHTEC_DMA_CQ_SIZE * sizeof(*swdma_chan->hw_cq); + swdma_chan->hw_cq = dma_alloc_coherent(swdma_dev->dma_dev.dev, size, + &swdma_chan->dma_addr_cq, + GFP_NOWAIT); + if (!swdma_chan->hw_cq) { + rc = -ENOMEM; + goto free_and_exit; + } + + /* reset host phase tag */ + swdma_chan->phase_tag = 0; + + for (i = 0; i < SWITCHTEC_DMA_RING_SIZE; i++) { + desc = kzalloc_obj(*desc, GFP_NOWAIT); + if (!desc) { + rc = -ENOMEM; + goto free_and_exit; + } + + dma_async_tx_descriptor_init(&desc->txd, &swdma_chan->dma_chan); + desc->hw = &swdma_chan->hw_sq[i]; + desc->completed = true; + + swdma_chan->desc_ring[i] = desc; + } + + rcu_read_lock(); + pdev = rcu_dereference(swdma_dev->pdev); + if (!pdev) { + rcu_read_unlock(); + rc = -ENODEV; + goto free_and_exit; + } + + /* set sq/cq */ + writel(lower_32_bits(swdma_chan->dma_addr_sq), &chan_fw->sq_base_lo); + writel(upper_32_bits(swdma_chan->dma_addr_sq), &chan_fw->sq_base_hi); + writel(lower_32_bits(swdma_chan->dma_addr_cq), &chan_fw->cq_base_lo); + writel(upper_32_bits(swdma_chan->dma_addr_cq), &chan_fw->cq_base_hi); + + writew(SWITCHTEC_DMA_SQ_SIZE, &swdma_chan->mmio_chan_fw->sq_size); + writew(SWITCHTEC_DMA_CQ_SIZE, &swdma_chan->mmio_chan_fw->cq_size); + + rcu_read_unlock(); + return 0; + +free_and_exit: + switchtec_dma_free_desc(swdma_chan); + return rc; +} + +static int switchtec_dma_alloc_chan_resources(struct dma_chan *chan) +{ + struct switchtec_dma_chan *swdma_chan = + container_of(chan, struct switchtec_dma_chan, dma_chan); + struct switchtec_dma_dev *swdma_dev = swdma_chan->swdma_dev; + u32 perf_cfg; + int rc; + + rc = switchtec_dma_alloc_desc(swdma_chan); + if (rc) + return rc; + + rc = enable_channel(swdma_chan); + if (rc) + return rc; + + rc = reset_channel(swdma_chan); + if (rc) + return rc; + + rc = unhalt_channel(swdma_chan); + if (rc) + return rc; + + swdma_chan->ring_active = true; + swdma_chan->comp_ring_active = true; + swdma_chan->cid = 0; + + dma_cookie_init(chan); + + rcu_read_lock(); + if (!rcu_dereference(swdma_dev->pdev)) { + rcu_read_unlock(); + return -ENODEV; + } + + perf_cfg = readl(&swdma_chan->mmio_chan_fw->perf_cfg); + rcu_read_unlock(); + + dev_dbg(&chan->dev->device, "Burst Size: 0x%x\n", + FIELD_GET(PERF_BURST_SIZE_MASK, perf_cfg)); + + dev_dbg(&chan->dev->device, "Burst Scale: 0x%x\n", + FIELD_GET(PERF_BURST_SCALE_MASK, perf_cfg)); + + dev_dbg(&chan->dev->device, "Interval: 0x%x\n", + FIELD_GET(PERF_INTERVAL_MASK, perf_cfg)); + + dev_dbg(&chan->dev->device, "Arb Weight: 0x%x\n", + FIELD_GET(PERF_ARB_WEIGHT_MASK, perf_cfg)); + + dev_dbg(&chan->dev->device, "MRRS: 0x%x\n", + FIELD_GET(PERF_MRRS_MASK, perf_cfg)); + + return SWITCHTEC_DMA_SQ_SIZE; +} + +static void switchtec_dma_free_chan_resources(struct dma_chan *chan) +{ + struct switchtec_dma_chan *swdma_chan = + container_of(chan, struct switchtec_dma_chan, dma_chan); + + spin_lock_bh(&swdma_chan->submit_lock); + swdma_chan->ring_active = false; + spin_unlock_bh(&swdma_chan->submit_lock); + + spin_lock_bh(&swdma_chan->complete_lock); + swdma_chan->comp_ring_active = false; + spin_unlock_bh(&swdma_chan->complete_lock); + + switchtec_dma_chan_stop(swdma_chan); + switchtec_dma_abort_desc(swdma_chan, 0); + switchtec_dma_free_desc(swdma_chan); + + disable_channel(swdma_chan); +} + +static int switchtec_dma_chan_init(struct switchtec_dma_dev *swdma_dev, + struct pci_dev *pdev, int i) +{ + struct dma_device *dma = &swdma_dev->dma_dev; + struct switchtec_dma_chan *swdma_chan; + u32 valid_en_se, thresh; + int se_buf_len, irq, rc; + struct dma_chan *chan; + + swdma_chan = kzalloc_obj(*swdma_chan, GFP_KERNEL); + if (!swdma_chan) + return -ENOMEM; + + swdma_chan->phase_tag = 0; + swdma_chan->index = i; + swdma_chan->swdma_dev = swdma_dev; + + spin_lock_init(&swdma_chan->hw_ctrl_lock); + spin_lock_init(&swdma_chan->submit_lock); + spin_lock_init(&swdma_chan->complete_lock); + tasklet_init(&swdma_chan->desc_task, switchtec_dma_desc_task, + (unsigned long)swdma_chan); + + swdma_chan->mmio_chan_fw = + swdma_dev->bar + SWITCHTEC_DMAC_CHAN_CFG_STS_OFFSET + + i * SWITCHTEC_DMA_CHAN_FW_REGS_SIZE; + swdma_chan->mmio_chan_hw = + swdma_dev->bar + SWITCHTEC_DMAC_CHAN_CTRL_OFFSET + + i * SWITCHTEC_DMA_CHAN_HW_REGS_SIZE; + + swdma_dev->swdma_chans[i] = swdma_chan; + + rc = pause_reset_channel(swdma_chan); + if (rc) + goto free_and_exit; + + /* init perf tuner */ + writel(FIELD_PREP(PERF_BURST_SCALE_MASK, 1) | + FIELD_PREP(PERF_MRRS_MASK, 3) | + FIELD_PREP(PERF_BURST_SIZE_MASK, 6) | + FIELD_PREP(PERF_ARB_WEIGHT_MASK, 1), + &swdma_chan->mmio_chan_fw->perf_cfg); + + valid_en_se = readl(&swdma_chan->mmio_chan_fw->valid_en_se); + + dev_dbg(&pdev->dev, "Channel %d: SE buffer base %d\n", i, + FIELD_GET(SE_BUF_BASE_MASK, valid_en_se)); + + se_buf_len = FIELD_GET(SE_BUF_LEN_MASK, valid_en_se); + dev_dbg(&pdev->dev, "Channel %d: SE buffer count %d\n", i, se_buf_len); + + thresh = se_buf_len / 2; + valid_en_se |= FIELD_GET(SE_THRESH_MASK, thresh); + writel(valid_en_se, &swdma_chan->mmio_chan_fw->valid_en_se); + + /* request irqs */ + irq = readl(&swdma_chan->mmio_chan_fw->int_vec); + dev_dbg(&pdev->dev, "Channel %d: CE irq vector %d\n", i, irq); + + rc = pci_request_irq(pdev, irq, switchtec_dma_isr, NULL, swdma_chan, + KBUILD_MODNAME); + if (rc) + goto free_and_exit; + + swdma_chan->irq = irq; + + chan = &swdma_chan->dma_chan; + chan->device = dma; + dma_cookie_init(chan); + + list_add_tail(&chan->device_node, &dma->channels); + + return 0; + +free_and_exit: + kfree(swdma_chan); + return rc; +} + +static int switchtec_dma_chan_free(struct pci_dev *pdev, + struct switchtec_dma_chan *swdma_chan) +{ + spin_lock_bh(&swdma_chan->submit_lock); + swdma_chan->ring_active = false; + spin_unlock_bh(&swdma_chan->submit_lock); + + spin_lock_bh(&swdma_chan->complete_lock); + swdma_chan->comp_ring_active = false; + spin_unlock_bh(&swdma_chan->complete_lock); + + pci_free_irq(pdev, swdma_chan->irq, swdma_chan); + tasklet_kill(&swdma_chan->desc_task); + + switchtec_dma_chan_stop(swdma_chan); + + return 0; +} + +static int switchtec_dma_chans_release(struct pci_dev *pdev, + struct switchtec_dma_dev *swdma_dev) +{ + int i; + + for (i = 0; i < swdma_dev->chan_cnt; i++) + switchtec_dma_chan_free(pdev, swdma_dev->swdma_chans[i]); + + return 0; +} + +static int switchtec_dma_chans_enumerate(struct switchtec_dma_dev *swdma_dev, + struct pci_dev *pdev, int chan_cnt) +{ + struct dma_device *dma = &swdma_dev->dma_dev; + int base, cnt, rc, i; + + swdma_dev->swdma_chans = kcalloc(chan_cnt, sizeof(*swdma_dev->swdma_chans), + GFP_KERNEL); + + if (!swdma_dev->swdma_chans) + return -ENOMEM; + + base = readw(swdma_dev->bar + SWITCHTEC_REG_SE_BUF_BASE); + cnt = readw(swdma_dev->bar + SWITCHTEC_REG_SE_BUF_CNT); + + dev_dbg(&pdev->dev, "EP SE buffer base %d\n", base); + dev_dbg(&pdev->dev, "EP SE buffer count %d\n", cnt); + + INIT_LIST_HEAD(&dma->channels); + + for (i = 0; i < chan_cnt; i++) { + rc = switchtec_dma_chan_init(swdma_dev, pdev, i); + if (rc) { + dev_err(&pdev->dev, "Channel %d: init channel failed\n", + i); + chan_cnt = i; + goto err_exit; + } + } + + return chan_cnt; + +err_exit: + for (i = 0; i < chan_cnt; i++) + switchtec_dma_chan_free(pdev, swdma_dev->swdma_chans[i]); + + kfree(swdma_dev->swdma_chans); + + return rc; +} + static void switchtec_dma_release(struct dma_device *dma_dev) { struct switchtec_dma_dev *swdma_dev = container_of(dma_dev, struct switchtec_dma_dev, dma_dev); + int i; + + for (i = 0; i < swdma_dev->chan_cnt; i++) + kfree(swdma_dev->swdma_chans[i]); + + kfree(swdma_dev->swdma_chans); put_device(dma_dev->dev); kfree(swdma_dev); @@ -37,9 +997,9 @@ static void switchtec_dma_release(struct dma_device *dma_dev) static int switchtec_dma_create(struct pci_dev *pdev) { struct switchtec_dma_dev *swdma_dev; + int chan_cnt, nr_vecs, irq, rc; struct dma_device *dma; struct dma_chan *chan; - int nr_vecs, rc; /* * Create the switchtec dma device @@ -58,18 +1018,51 @@ static int switchtec_dma_create(struct pci_dev *pdev) if (rc < 0) goto err_exit; + irq = readw(swdma_dev->bar + SWITCHTEC_REG_CHAN_STS_VEC); + pci_dbg(pdev, "Channel pause irq vector %d\n", irq); + + rc = pci_request_irq(pdev, irq, NULL, switchtec_dma_chan_status_isr, + swdma_dev, KBUILD_MODNAME); + if (rc) + goto err_exit; + + swdma_dev->chan_status_irq = irq; + + chan_cnt = readl(swdma_dev->bar + SWITCHTEC_REG_CHAN_CNT); + if (!chan_cnt) { + pci_err(pdev, "No channel configured.\n"); + rc = -ENXIO; + goto err_exit; + } + + chan_cnt = switchtec_dma_chans_enumerate(swdma_dev, pdev, chan_cnt); + if (chan_cnt < 0) { + pci_err(pdev, "Failed to enumerate dma channels: %d\n", + chan_cnt); + rc = -ENXIO; + goto err_exit; + } + + swdma_dev->chan_cnt = chan_cnt; + dma = &swdma_dev->dma_dev; dma->copy_align = DMAENGINE_ALIGN_8_BYTES; dma->dev = get_device(&pdev->dev); + dma->device_alloc_chan_resources = switchtec_dma_alloc_chan_resources; + dma->device_free_chan_resources = switchtec_dma_free_chan_resources; + dma->device_terminate_all = switchtec_dma_terminate_all; + dma->device_synchronize = switchtec_dma_synchronize; dma->device_release = switchtec_dma_release; rc = dma_async_device_register(dma); if (rc) { pci_err(pdev, "Failed to register dma device: %d\n", rc); - goto err_exit; + goto err_chans_release_exit; } + pci_dbg(pdev, "Channel count: %d\n", chan_cnt); + list_for_each_entry(chan, &dma->channels, device_node) pci_dbg(pdev, "%s\n", dma_chan_name(chan)); @@ -77,7 +1070,13 @@ static int switchtec_dma_create(struct pci_dev *pdev) return 0; +err_chans_release_exit: + switchtec_dma_chans_release(pdev, swdma_dev); + err_exit: + if (swdma_dev->chan_status_irq) + free_irq(swdma_dev->chan_status_irq, swdma_dev); + iounmap(swdma_dev->bar); kfree(swdma_dev); return rc; @@ -120,9 +1119,13 @@ static void switchtec_dma_remove(struct pci_dev *pdev) { struct switchtec_dma_dev *swdma_dev = pci_get_drvdata(pdev); + switchtec_dma_chans_release(pdev, swdma_dev); + rcu_assign_pointer(swdma_dev->pdev, NULL); synchronize_rcu(); + pci_free_irq(pdev, swdma_dev->chan_status_irq, swdma_dev); + pci_free_irq_vectors(pdev); dma_async_device_unregister(&swdma_dev->dma_dev); From 3af11daeaeaa6f62494c7cb07265928162b440ab Mon Sep 17 00:00:00 2001 From: Kelvin Cao Date: Mon, 2 Mar 2026 14:04:19 -0700 Subject: [PATCH 26/65] dmaengine: switchtec-dma: Implement descriptor submission On prep, a spin lock is taken and the next entry in the circular buffer is filled. On submit, the spin lock just needs to be released as the requests are already pending. When switchtec_dma_issue_pending() is called, the sq_tail register is written to indicate there are new jobs for the dma engine to start on. Pause and resume operations are implemented by writing to a control register. Signed-off-by: Kelvin Cao Co-developed-by: George Ge Signed-off-by: George Ge Reviewed-by: Christoph Hellwig Signed-off-by: Logan Gunthorpe Link: https://patch.msgid.link/20260302210419.3656-4-logang@deltatee.com Signed-off-by: Vinod Koul --- drivers/dma/switchtec_dma.c | 225 ++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) diff --git a/drivers/dma/switchtec_dma.c b/drivers/dma/switchtec_dma.c index 4c0dacc24608..3ef928640615 100644 --- a/drivers/dma/switchtec_dma.c +++ b/drivers/dma/switchtec_dma.c @@ -32,6 +32,8 @@ MODULE_AUTHOR("Kelvin Cao"); #define SWITCHTEC_REG_SE_BUF_CNT 0x98 #define SWITCHTEC_REG_SE_BUF_BASE 0x9a +#define SWITCHTEC_DESC_MAX_SIZE 0x100000 + #define SWITCHTEC_CHAN_CTRL_PAUSE BIT(0) #define SWITCHTEC_CHAN_CTRL_HALT BIT(1) #define SWITCHTEC_CHAN_CTRL_RESET BIT(2) @@ -41,6 +43,8 @@ MODULE_AUTHOR("Kelvin Cao"); #define SWITCHTEC_CHAN_STS_HALTED BIT(10) #define SWITCHTEC_CHAN_STS_PAUSED_MASK GENMASK(29, 13) +#define SWITCHTEC_INVALID_HFID 0xffff + #define SWITCHTEC_DMA_SQ_SIZE SZ_32K #define SWITCHTEC_DMA_CQ_SIZE SZ_32K @@ -204,6 +208,11 @@ struct switchtec_dma_hw_se_desc { __le16 sfid; }; +#define SWITCHTEC_SE_DFM BIT(5) +#define SWITCHTEC_SE_LIOF BIT(6) +#define SWITCHTEC_SE_BRR BIT(7) +#define SWITCHTEC_SE_CID_MASK GENMASK(15, 0) + #define SWITCHTEC_CE_SC_LEN_ERR BIT(0) #define SWITCHTEC_CE_SC_UR BIT(1) #define SWITCHTEC_CE_SC_CA BIT(2) @@ -603,6 +612,214 @@ static void switchtec_dma_synchronize(struct dma_chan *chan) spin_unlock_bh(&swdma_chan->complete_lock); } +static struct dma_async_tx_descriptor * +switchtec_dma_prep_desc(struct dma_chan *c, u16 dst_fid, dma_addr_t dma_dst, + u16 src_fid, dma_addr_t dma_src, u64 data, + size_t len, unsigned long flags) + __acquires(swdma_chan->submit_lock) +{ + struct switchtec_dma_chan *swdma_chan = + container_of(c, struct switchtec_dma_chan, dma_chan); + struct switchtec_dma_desc *desc; + int head, tail; + + spin_lock_bh(&swdma_chan->submit_lock); + + if (!swdma_chan->ring_active) + goto err_unlock; + + tail = READ_ONCE(swdma_chan->tail); + head = swdma_chan->head; + + if (!CIRC_SPACE(head, tail, SWITCHTEC_DMA_RING_SIZE)) + goto err_unlock; + + desc = swdma_chan->desc_ring[head]; + + if (src_fid != SWITCHTEC_INVALID_HFID && + dst_fid != SWITCHTEC_INVALID_HFID) + desc->hw->ctrl |= SWITCHTEC_SE_DFM; + + if (flags & DMA_PREP_INTERRUPT) + desc->hw->ctrl |= SWITCHTEC_SE_LIOF; + + if (flags & DMA_PREP_FENCE) + desc->hw->ctrl |= SWITCHTEC_SE_BRR; + + desc->txd.flags = flags; + + desc->completed = false; + desc->hw->opc = SWITCHTEC_DMA_OPC_MEMCPY; + desc->hw->addr_lo = cpu_to_le32(lower_32_bits(dma_src)); + desc->hw->addr_hi = cpu_to_le32(upper_32_bits(dma_src)); + desc->hw->daddr_lo = cpu_to_le32(lower_32_bits(dma_dst)); + desc->hw->daddr_hi = cpu_to_le32(upper_32_bits(dma_dst)); + desc->hw->byte_cnt = cpu_to_le32(len); + desc->hw->tlp_setting = 0; + desc->hw->dfid = cpu_to_le16(dst_fid); + desc->hw->sfid = cpu_to_le16(src_fid); + swdma_chan->cid &= SWITCHTEC_SE_CID_MASK; + desc->hw->cid = cpu_to_le16(swdma_chan->cid++); + desc->orig_size = len; + + /* return with the lock held, it will be released in tx_submit */ + + return &desc->txd; + +err_unlock: + /* + * Keep sparse happy by restoring an even lock count on + * this lock. + */ + __acquire(swdma_chan->submit_lock); + + spin_unlock_bh(&swdma_chan->submit_lock); + return NULL; +} + +static struct dma_async_tx_descriptor * +switchtec_dma_prep_memcpy(struct dma_chan *c, dma_addr_t dma_dst, + dma_addr_t dma_src, size_t len, unsigned long flags) + __acquires(swdma_chan->submit_lock) +{ + if (len > SWITCHTEC_DESC_MAX_SIZE) { + /* + * Keep sparse happy by restoring an even lock count on + * this lock. + */ + __acquire(swdma_chan->submit_lock); + return NULL; + } + + return switchtec_dma_prep_desc(c, SWITCHTEC_INVALID_HFID, dma_dst, + SWITCHTEC_INVALID_HFID, dma_src, 0, len, + flags); +} + +static dma_cookie_t +switchtec_dma_tx_submit(struct dma_async_tx_descriptor *desc) + __releases(swdma_chan->submit_lock) +{ + struct switchtec_dma_chan *swdma_chan = + container_of(desc->chan, struct switchtec_dma_chan, dma_chan); + dma_cookie_t cookie; + int head; + + head = swdma_chan->head + 1; + head &= SWITCHTEC_DMA_RING_SIZE - 1; + + /* + * Ensure the desc updates are visible before updating the head index + */ + smp_store_release(&swdma_chan->head, head); + + cookie = dma_cookie_assign(desc); + + spin_unlock_bh(&swdma_chan->submit_lock); + + return cookie; +} + +static enum dma_status switchtec_dma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, struct dma_tx_state *txstate) +{ + struct switchtec_dma_chan *swdma_chan = + container_of(chan, struct switchtec_dma_chan, dma_chan); + enum dma_status ret; + + ret = dma_cookie_status(chan, cookie, txstate); + if (ret == DMA_COMPLETE) + return ret; + + /* + * For jobs where the interrupts are disabled, this is the only place + * to process the completions returned by the hardware. Callers that + * disable interrupts must call tx_status() to determine when a job + * is done, so it is safe to process completions here. If a job has + * interrupts enabled, then the completions will normally be processed + * in the tasklet that is triggered by the interrupt and tx_status() + * does not need to be called. + */ + switchtec_dma_cleanup_completed(swdma_chan); + + return dma_cookie_status(chan, cookie, txstate); +} + +static void switchtec_dma_issue_pending(struct dma_chan *chan) +{ + struct switchtec_dma_chan *swdma_chan = + container_of(chan, struct switchtec_dma_chan, dma_chan); + struct switchtec_dma_dev *swdma_dev = swdma_chan->swdma_dev; + + /* + * The sq_tail register is actually for the head of the + * submisssion queue. Chip has the opposite define of head/tail + * to the Linux kernel. + */ + + rcu_read_lock(); + if (!rcu_dereference(swdma_dev->pdev)) { + rcu_read_unlock(); + return; + } + + spin_lock_bh(&swdma_chan->submit_lock); + writew(swdma_chan->head, &swdma_chan->mmio_chan_hw->sq_tail); + spin_unlock_bh(&swdma_chan->submit_lock); + + rcu_read_unlock(); +} + +static int switchtec_dma_pause(struct dma_chan *chan) +{ + struct switchtec_dma_chan *swdma_chan = + container_of(chan, struct switchtec_dma_chan, dma_chan); + struct chan_hw_regs __iomem *chan_hw = swdma_chan->mmio_chan_hw; + struct pci_dev *pdev; + int ret; + + rcu_read_lock(); + pdev = rcu_dereference(swdma_chan->swdma_dev->pdev); + if (!pdev) { + ret = -ENODEV; + goto unlock_and_exit; + } + + spin_lock(&swdma_chan->hw_ctrl_lock); + writeb(SWITCHTEC_CHAN_CTRL_PAUSE, &chan_hw->ctrl); + ret = wait_for_chan_status(chan_hw, SWITCHTEC_CHAN_STS_PAUSED, true); + spin_unlock(&swdma_chan->hw_ctrl_lock); + +unlock_and_exit: + rcu_read_unlock(); + return ret; +} + +static int switchtec_dma_resume(struct dma_chan *chan) +{ + struct switchtec_dma_chan *swdma_chan = + container_of(chan, struct switchtec_dma_chan, dma_chan); + struct chan_hw_regs __iomem *chan_hw = swdma_chan->mmio_chan_hw; + struct pci_dev *pdev; + int ret; + + rcu_read_lock(); + pdev = rcu_dereference(swdma_chan->swdma_dev->pdev); + if (!pdev) { + ret = -ENODEV; + goto unlock_and_exit; + } + + spin_lock(&swdma_chan->hw_ctrl_lock); + writeb(0, &chan_hw->ctrl); + ret = wait_for_chan_status(chan_hw, SWITCHTEC_CHAN_STS_PAUSED, false); + spin_unlock(&swdma_chan->hw_ctrl_lock); + +unlock_and_exit: + rcu_read_unlock(); + return ret; +} + static void switchtec_dma_desc_task(unsigned long data) { struct switchtec_dma_chan *swdma_chan = (void *)data; @@ -721,6 +938,7 @@ static int switchtec_dma_alloc_desc(struct switchtec_dma_chan *swdma_chan) } dma_async_tx_descriptor_init(&desc->txd, &swdma_chan->dma_chan); + desc->txd.tx_submit = switchtec_dma_tx_submit; desc->hw = &swdma_chan->hw_sq[i]; desc->completed = true; @@ -1047,10 +1265,17 @@ static int switchtec_dma_create(struct pci_dev *pdev) dma = &swdma_dev->dma_dev; dma->copy_align = DMAENGINE_ALIGN_8_BYTES; + dma_cap_set(DMA_MEMCPY, dma->cap_mask); + dma_cap_set(DMA_PRIVATE, dma->cap_mask); dma->dev = get_device(&pdev->dev); dma->device_alloc_chan_resources = switchtec_dma_alloc_chan_resources; dma->device_free_chan_resources = switchtec_dma_free_chan_resources; + dma->device_prep_dma_memcpy = switchtec_dma_prep_memcpy; + dma->device_tx_status = switchtec_dma_tx_status; + dma->device_issue_pending = switchtec_dma_issue_pending; + dma->device_pause = switchtec_dma_pause; + dma->device_resume = switchtec_dma_resume; dma->device_terminate_all = switchtec_dma_terminate_all; dma->device_synchronize = switchtec_dma_synchronize; dma->device_release = switchtec_dma_release; From fe8a56f098fb87dd489666d6e9d6498be73a92e6 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 1 Mar 2026 15:21:59 +0100 Subject: [PATCH 27/65] dmaengine: xilinx: Simplify with scoped for each OF child loop Use scoped for-each loop when iterating over device nodes to make code a bit simpler. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260301142158.90319-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/dma/xilinx/xilinx_dma.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c index b53292e02448..02a05f215614 100644 --- a/drivers/dma/xilinx/xilinx_dma.c +++ b/drivers/dma/xilinx/xilinx_dma.c @@ -3180,7 +3180,7 @@ static int xilinx_dma_probe(struct platform_device *pdev) = axivdma_clk_init; struct device_node *node = pdev->dev.of_node; struct xilinx_dma_device *xdev; - struct device_node *child, *np = pdev->dev.of_node; + struct device_node *np = pdev->dev.of_node; u32 num_frames, addr_width = XILINX_DMA_DFAULT_ADDRWIDTH, len_width; int i, err; @@ -3320,12 +3320,10 @@ static int xilinx_dma_probe(struct platform_device *pdev) platform_set_drvdata(pdev, xdev); /* Initialize the channels */ - for_each_child_of_node(node, child) { + for_each_child_of_node_scoped(node, child) { err = xilinx_dma_child_probe(xdev, child); - if (err < 0) { - of_node_put(child); + if (err < 0) goto error; - } } if (xdev->dma_config->dmatype == XDMA_TYPE_VDMA) { From 70fbea9f1a44d80a4c573c225f119022d6e21360 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 28 Feb 2026 17:12:13 -0800 Subject: [PATCH 28/65] dmaengine: ti-cppi5: fix all kernel-doc warnings Add missing struct member, function parameter, and enum value descriptions. Add missing function Returns: sections. Use correct function name in kernel-doc to avoid mismatched prototypes. These repair all kernel-doc warnings in ti-cppi5.h: Warning: include/linux/dma/ti-cppi5.h:27 struct member 'pkt_info1' not described in 'cppi5_desc_hdr_t' Warning: include/linux/dma/ti-cppi5.h:27 struct member 'pkt_info2' not described in 'cppi5_desc_hdr_t' Warning: include/linux/dma/ti-cppi5.h:50 struct member 'epib' not described in 'cppi5_host_desc_t' Warning: include/linux/dma/ti-cppi5.h:142 struct member 'epib' not described in 'cppi5_monolithic_desc_t' Warning: include/linux/dma/ti-cppi5.h:413 function parameter 'pkt_len' not described in 'cppi5_hdesc_set_pktlen' Warning: include/linux/dma/ti-cppi5.h:436 function parameter 'ps_flags' not described in 'cppi5_hdesc_set_psflags' Warning: include/linux/dma/ti-cppi5.h:509 function parameter 'hbuf_desc' not described in 'cppi5_hdesc_link_hbdesc' Warning: include/linux/dma/ti-cppi5.h:839 struct member 'dicnt3' not described in 'cppi5_tr_type15_t' Warning: include/linux/dma/ti-cppi5.h:970 function parameter 'desc_hdr' not described in 'cppi5_trdesc_init' Warning: include/linux/dma/ti-cppi5.h:184 No description found for return value of 'cppi5_desc_is_tdcm' Warning: include/linux/dma/ti-cppi5.h:198 No description found for return value of 'cppi5_desc_get_type' Warning: include/linux/dma/ti-cppi5.h:210 No description found for return value of 'cppi5_desc_get_errflags' Warning: include/linux/dma/ti-cppi5.h:448 expecting prototype for cppi5_hdesc_get_errflags(). Prototype was for cppi5_hdesc_get_pkttype() instead Warning: include/linux/dma/ti-cppi5.h:460 expecting prototype for cppi5_hdesc_get_errflags(). Prototype was for cppi5_hdesc_set_pkttype() instead Warning: include/linux/dma/ti-cppi5.h:1053 expecting prototype for cppi5_tr_cflag_set(). Prototype was for cppi5_tr_csf_set() instead Warning: include/linux/dma/ti-cppi5.h:651 Enum value 'CPPI5_TR_TYPE_MAX' not described in enum 'cppi5_tr_types' Warning: include/linux/dma/ti-cppi5.h:676 Enum value 'CPPI5_TR_EVENT_SIZE_MAX' not described in enum 'cppi5_tr_event_size' Warning: include/linux/dma/ti-cppi5.h:693 Enum value 'CPPI5_TR_TRIGGER_MAX' not described in enum 'cppi5_tr_trigger' Warning: include/linux/dma/ti-cppi5.h:714 Enum value 'CPPI5_TR_TRIGGER_TYPE_MAX' not described in enum 'cppi5_tr_trigger_type' Warning: include/linux/dma/ti-cppi5.h:890 Enum value 'CPPI5_TR_RESPONSE_STATUS_MAX' not described in enum 'cppi5_tr_resp_status_type' Warning: include/linux/dma/ti-cppi5.h:906 Enum value 'CPPI5_TR_RESPONSE_STATUS_SUBMISSION_MAX' not described in enum 'cppi5_tr_resp_status_submission' Warning: include/linux/dma/ti-cppi5.h:934 Enum value 'CPPI5_TR_RESPONSE_STATUS_UNSUPPORTED_MAX' not described in enum 'cppi5_tr_resp_status_unsupported' Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260301011213.3063688-1-rdunlap@infradead.org Signed-off-by: Vinod Koul --- include/linux/dma/ti-cppi5.h | 53 ++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/include/linux/dma/ti-cppi5.h b/include/linux/dma/ti-cppi5.h index c53c0f6e3b1a..3fe19b75ddf7 100644 --- a/include/linux/dma/ti-cppi5.h +++ b/include/linux/dma/ti-cppi5.h @@ -16,8 +16,8 @@ * struct cppi5_desc_hdr_t - Descriptor header, present in all types of * descriptors * @pkt_info0: Packet info word 0 (n/a in Buffer desc) - * @pkt_info0: Packet info word 1 (n/a in Buffer desc) - * @pkt_info0: Packet info word 2 (n/a in Buffer desc) + * @pkt_info1: Packet info word 1 (n/a in Buffer desc) + * @pkt_info2: Packet info word 2 (n/a in Buffer desc) * @src_dst_tag: Packet info word 3 (n/a in Buffer desc) */ struct cppi5_desc_hdr_t { @@ -35,7 +35,7 @@ struct cppi5_desc_hdr_t { * @buf_info1: word 8: Buffer valid data length * @org_buf_len: word 9: Original buffer length * @org_buf_ptr: word 10/11: Original buffer pointer - * @epib[0]: Extended Packet Info Data (optional, 4 words), and/or + * @epib: Extended Packet Info Data (optional, 4 words), and/or * Protocol Specific Data (optional, 0-128 bytes in * multiples of 4), and/or * Other Software Data (0-N bytes, optional) @@ -132,7 +132,7 @@ struct cppi5_desc_epib_t { /** * struct cppi5_monolithic_desc_t - Monolithic-mode packet descriptor * @hdr: Descriptor header - * @epib[0]: Extended Packet Info Data (optional, 4 words), and/or + * @epib: Extended Packet Info Data (optional, 4 words), and/or * Protocol Specific Data (optional, 0-128 bytes in * multiples of 4), and/or * Other Software Data (0-N bytes, optional) @@ -179,7 +179,7 @@ static inline void cppi5_desc_dump(void *desc, u32 size) * cppi5_desc_is_tdcm - check if the paddr indicates Teardown Complete Message * @paddr: Physical address of the packet popped from the ring * - * Returns true if the address indicates TDCM + * Returns: true if the address indicates TDCM */ static inline bool cppi5_desc_is_tdcm(dma_addr_t paddr) { @@ -190,7 +190,7 @@ static inline bool cppi5_desc_is_tdcm(dma_addr_t paddr) * cppi5_desc_get_type - get descriptor type * @desc_hdr: packet descriptor/TR header * - * Returns descriptor type: + * Returns: descriptor type: * CPPI5_INFO0_DESC_TYPE_VAL_HOST * CPPI5_INFO0_DESC_TYPE_VAL_MONO * CPPI5_INFO0_DESC_TYPE_VAL_TR @@ -205,7 +205,7 @@ static inline u32 cppi5_desc_get_type(struct cppi5_desc_hdr_t *desc_hdr) * cppi5_desc_get_errflags - get Error Flags from Desc * @desc_hdr: packet/TR descriptor header * - * Returns Error Flags from Packet/TR Descriptor + * Returns: Error Flags from Packet/TR Descriptor */ static inline u32 cppi5_desc_get_errflags(struct cppi5_desc_hdr_t *desc_hdr) { @@ -307,7 +307,7 @@ static inline void cppi5_desc_set_tags_ids(struct cppi5_desc_hdr_t *desc_hdr, * @psdata_size: PSDATA size * @sw_data_size: SWDATA size * - * Returns required Host Packet Descriptor size + * Returns: required Host Packet Descriptor size * 0 - if PSDATA > CPPI5_INFO0_HDESC_PSDATA_MAX_SIZE */ static inline u32 cppi5_hdesc_calc_size(bool epib, u32 psdata_size, @@ -381,6 +381,8 @@ cppi5_hdesc_update_psdata_size(struct cppi5_host_desc_t *desc, u32 psdata_size) /** * cppi5_hdesc_get_psdata_size - get PSdata size in bytes * @desc: Host packet descriptor + * + * Returns: PSdata size in bytes */ static inline u32 cppi5_hdesc_get_psdata_size(struct cppi5_host_desc_t *desc) { @@ -398,7 +400,7 @@ static inline u32 cppi5_hdesc_get_psdata_size(struct cppi5_host_desc_t *desc) * cppi5_hdesc_get_pktlen - get Packet Length from HDesc * @desc: Host packet descriptor * - * Returns Packet Length from Host Packet Descriptor + * Returns: Packet Length from Host Packet Descriptor */ static inline u32 cppi5_hdesc_get_pktlen(struct cppi5_host_desc_t *desc) { @@ -408,6 +410,7 @@ static inline u32 cppi5_hdesc_get_pktlen(struct cppi5_host_desc_t *desc) /** * cppi5_hdesc_set_pktlen - set Packet Length in HDesc * @desc: Host packet descriptor + * @pkt_len: Packet length to set */ static inline void cppi5_hdesc_set_pktlen(struct cppi5_host_desc_t *desc, u32 pkt_len) @@ -420,7 +423,7 @@ static inline void cppi5_hdesc_set_pktlen(struct cppi5_host_desc_t *desc, * cppi5_hdesc_get_psflags - get Protocol Specific Flags from HDesc * @desc: Host packet descriptor * - * Returns Protocol Specific Flags from Host Packet Descriptor + * Returns: Protocol Specific Flags from Host Packet Descriptor */ static inline u32 cppi5_hdesc_get_psflags(struct cppi5_host_desc_t *desc) { @@ -431,6 +434,7 @@ static inline u32 cppi5_hdesc_get_psflags(struct cppi5_host_desc_t *desc) /** * cppi5_hdesc_set_psflags - set Protocol Specific Flags in HDesc * @desc: Host packet descriptor + * @ps_flags: Protocol Specific flags to set */ static inline void cppi5_hdesc_set_psflags(struct cppi5_host_desc_t *desc, u32 ps_flags) @@ -442,8 +446,10 @@ static inline void cppi5_hdesc_set_psflags(struct cppi5_host_desc_t *desc, } /** - * cppi5_hdesc_get_errflags - get Packet Type from HDesc + * cppi5_hdesc_get_pkttype - get Packet Type from HDesc * @desc: Host packet descriptor + * + * Returns: Packet type */ static inline u32 cppi5_hdesc_get_pkttype(struct cppi5_host_desc_t *desc) { @@ -452,7 +458,7 @@ static inline u32 cppi5_hdesc_get_pkttype(struct cppi5_host_desc_t *desc) } /** - * cppi5_hdesc_get_errflags - set Packet Type in HDesc + * cppi5_hdesc_set_pkttype - set Packet Type in HDesc * @desc: Host packet descriptor * @pkt_type: Packet Type */ @@ -501,7 +507,7 @@ static inline void cppi5_hdesc_reset_to_original(struct cppi5_host_desc_t *desc) /** * cppi5_hdesc_link_hbdesc - link Host Buffer Descriptor to HDesc * @desc: Host Packet Descriptor - * @buf_desc: Host Buffer Descriptor physical address + * @hbuf_desc: Host Buffer Descriptor physical address * * add and link Host Buffer Descriptor to HDesc */ @@ -527,7 +533,7 @@ static inline void cppi5_hdesc_reset_hbdesc(struct cppi5_host_desc_t *desc) * cppi5_hdesc_epib_present - check if EPIB present * @desc_hdr: packet descriptor/TR header * - * Returns true if EPIB present in the packet + * Returns: true if EPIB present in the packet */ static inline bool cppi5_hdesc_epib_present(struct cppi5_desc_hdr_t *desc_hdr) { @@ -538,7 +544,7 @@ static inline bool cppi5_hdesc_epib_present(struct cppi5_desc_hdr_t *desc_hdr) * cppi5_hdesc_get_psdata - Get pointer on PSDATA * @desc: Host packet descriptor * - * Returns pointer on PSDATA in HDesc. + * Returns: pointer on PSDATA in HDesc. * NULL - if ps_data placed at the start of data buffer. */ static inline void *cppi5_hdesc_get_psdata(struct cppi5_host_desc_t *desc) @@ -568,7 +574,7 @@ static inline void *cppi5_hdesc_get_psdata(struct cppi5_host_desc_t *desc) * cppi5_hdesc_get_swdata - Get pointer on swdata * @desc: Host packet descriptor * - * Returns pointer on SWDATA in HDesc. + * Returns: pointer on SWDATA in HDesc. * NOTE. It's caller responsibility to be sure hdesc actually has swdata. */ static inline void *cppi5_hdesc_get_swdata(struct cppi5_host_desc_t *desc) @@ -648,6 +654,7 @@ enum cppi5_tr_types { CPPI5_TR_TYPE11, /* type12-14: Reserved */ CPPI5_TR_TYPE15 = 15, + /* private: */ CPPI5_TR_TYPE_MAX }; @@ -673,6 +680,7 @@ enum cppi5_tr_event_size { CPPI5_TR_EVENT_SIZE_ICNT1_DEC, CPPI5_TR_EVENT_SIZE_ICNT2_DEC, CPPI5_TR_EVENT_SIZE_ICNT3_DEC, + /* private: */ CPPI5_TR_EVENT_SIZE_MAX }; @@ -690,6 +698,7 @@ enum cppi5_tr_trigger { CPPI5_TR_TRIGGER_GLOBAL0, CPPI5_TR_TRIGGER_GLOBAL1, CPPI5_TR_TRIGGER_LOCAL_EVENT, + /* private: */ CPPI5_TR_TRIGGER_MAX }; @@ -711,6 +720,7 @@ enum cppi5_tr_trigger_type { CPPI5_TR_TRIGGER_TYPE_ICNT2_DEC, CPPI5_TR_TRIGGER_TYPE_ICNT3_DEC, CPPI5_TR_TRIGGER_TYPE_ALL, + /* private: */ CPPI5_TR_TRIGGER_TYPE_MAX }; @@ -815,7 +825,7 @@ struct cppi5_tr_type3_t { * destination * @dicnt1: Total loop iteration count for level 1 for destination * @dicnt2: Total loop iteration count for level 2 for destination - * @sicnt3: Total loop iteration count for level 3 (outermost) for + * @dicnt3: Total loop iteration count for level 3 (outermost) for * destination */ struct cppi5_tr_type15_t { @@ -887,6 +897,7 @@ enum cppi5_tr_resp_status_type { CPPI5_TR_RESPONSE_STATUS_UNSUPPORTED_ERR, CPPI5_TR_RESPONSE_STATUS_TRANSFER_EXCEPTION, CPPI5_TR_RESPONSE_STATUS__TEARDOWN_FLUSH, + /* private: */ CPPI5_TR_RESPONSE_STATUS_MAX }; @@ -903,6 +914,7 @@ enum cppi5_tr_resp_status_submission { CPPI5_TR_RESPONSE_STATUS_SUBMISSION_ICNT0, CPPI5_TR_RESPONSE_STATUS_SUBMISSION_FIFO_FULL, CPPI5_TR_RESPONSE_STATUS_SUBMISSION_OWN, + /* private: */ CPPI5_TR_RESPONSE_STATUS_SUBMISSION_MAX }; @@ -931,6 +943,7 @@ enum cppi5_tr_resp_status_unsupported { CPPI5_TR_RESPONSE_STATUS_UNSUPPORTED_DFMT, CPPI5_TR_RESPONSE_STATUS_UNSUPPORTED_SECTR, CPPI5_TR_RESPONSE_STATUS_UNSUPPORTED_AMODE_SPECIFIC, + /* private: */ CPPI5_TR_RESPONSE_STATUS_UNSUPPORTED_MAX }; @@ -939,7 +952,7 @@ enum cppi5_tr_resp_status_unsupported { * @tr_count: number of TR records * @tr_size: Nominal size of TR record (max) [16, 32, 64, 128] * - * Returns required TR Descriptor size + * Returns: required TR Descriptor size */ static inline size_t cppi5_trdesc_calc_size(u32 tr_count, u32 tr_size) { @@ -955,7 +968,7 @@ static inline size_t cppi5_trdesc_calc_size(u32 tr_count, u32 tr_size) /** * cppi5_trdesc_init - Init TR Descriptor - * @desc: TR Descriptor + * @desc_hdr: TR Descriptor * @tr_count: number of TR records * @tr_size: Nominal size of TR record (max) [16, 32, 64, 128] * @reload_idx: Absolute index to jump to on the 2nd and following passes @@ -1044,7 +1057,7 @@ static inline void cppi5_tr_set_trigger(cppi5_tr_flags_t *flags, } /** - * cppi5_tr_cflag_set - Update the Configuration specific flags + * cppi5_tr_csf_set - Update the Configuration specific flags * @flags: Pointer to the TR's flags * @csf: Configuration specific flags * From 7b84a00dd3528d980f1f35fd2c5015f72dc3f62a Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 28 Feb 2026 17:12:03 -0800 Subject: [PATCH 29/65] dmaengine: qcom: qcom-gpi-dma.h: fix all kernel-doc warnings Add missing enum descriptions and spell one struct member correctly to avoid kernel-doc warnings: Warning: include/linux/dma/qcom-gpi-dma.h:15 Enum value 'SPI_TX' not described in enum 'spi_transfer_cmd' Warning: include/linux/dma/qcom-gpi-dma.h:15 Enum value 'SPI_RX' not described in enum 'spi_transfer_cmd' Warning: include/linux/dma/qcom-gpi-dma.h:15 Enum value 'SPI_DUPLEX' not described in enum 'spi_transfer_cmd' Warning: include/linux/dma/qcom-gpi-dma.h:80 struct member 'multi_msg' not described in 'gpi_i2c_config' Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260301011203.3062658-1-rdunlap@infradead.org Signed-off-by: Vinod Koul --- include/linux/dma/qcom-gpi-dma.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/linux/dma/qcom-gpi-dma.h b/include/linux/dma/qcom-gpi-dma.h index 6680dd1a43c6..332be28427e4 100644 --- a/include/linux/dma/qcom-gpi-dma.h +++ b/include/linux/dma/qcom-gpi-dma.h @@ -8,6 +8,9 @@ /** * enum spi_transfer_cmd - spi transfer commands + * @SPI_TX: SPI peripheral TX command + * @SPI_RX: SPI peripheral RX command + * @SPI_DUPLEX: SPI peripheral Duplex command */ enum spi_transfer_cmd { SPI_TX = 1, @@ -64,7 +67,7 @@ enum i2c_op { * @set_config: set peripheral config * @rx_len: receive length for buffer * @op: i2c cmd - * @muli-msg: is part of multi i2c r-w msgs + * @multi_msg: is part of multi i2c r-w msgs */ struct gpi_i2c_config { u8 set_config; From 65ca1121f7be4cc0391f6d80fa87eac6f7d847a5 Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Fri, 27 Feb 2026 07:59:05 +0530 Subject: [PATCH 30/65] dmaengine: xilinx: Update kernel-doc comments Two members of struct xdma_desc_block are not descibed leading to warnings, document them. Warning: drivers/dma/xilinx/xdma.c:75 struct member 'last_interrupt' not described in 'xdma_chan' Warning: drivers/dma/xilinx/xdma.c:75 struct member 'stop_requested' not described in 'xdma_chan' Link: https://patch.msgid.link/20260227022905.233721-1-vkoul@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/xilinx/xdma.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/dma/xilinx/xdma.c b/drivers/dma/xilinx/xdma.c index d02a4dac2291..35a31e07f0e0 100644 --- a/drivers/dma/xilinx/xdma.c +++ b/drivers/dma/xilinx/xdma.c @@ -61,6 +61,8 @@ struct xdma_desc_block { * @dir: Transferring direction of the channel * @cfg: Transferring config of the channel * @irq: IRQ assigned to the channel + * @last_interrupt: task for comppleting last interrupt + * @stop_requested: stop request flag */ struct xdma_chan { struct virt_dma_chan vchan; From 3a005126c9d7f30093627a6f329656c358e16b3a Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 25 Feb 2026 16:41:37 -0500 Subject: [PATCH 31/65] dmaengine: of_dma: Add devm_of_dma_controller_register() Add a managed API, devm_of_dma_controller_register(), to simplify DMA engine controller registration by automatically handling resource cleanup. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260225-mxsdma-module-v3-1-8f798b13baa6@nxp.com Signed-off-by: Vinod Koul --- include/linux/of_dma.h | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/include/linux/of_dma.h b/include/linux/of_dma.h index fd706cdf255c..16b08234d03b 100644 --- a/include/linux/of_dma.h +++ b/include/linux/of_dma.h @@ -38,6 +38,26 @@ extern int of_dma_controller_register(struct device_node *np, void *data); extern void of_dma_controller_free(struct device_node *np); +static void __of_dma_controller_free(void *np) +{ + of_dma_controller_free(np); +} + +static inline int +devm_of_dma_controller_register(struct device *dev, struct device_node *np, + struct dma_chan *(*of_dma_xlate) + (struct of_phandle_args *, struct of_dma *), + void *data) +{ + int ret; + + ret = of_dma_controller_register(np, of_dma_xlate, data); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, __of_dma_controller_free, np); +} + extern int of_dma_router_register(struct device_node *np, void *(*of_dma_route_allocate) (struct of_phandle_args *, struct of_dma *), @@ -64,6 +84,15 @@ static inline void of_dma_controller_free(struct device_node *np) { } +static inline int +devm_of_dma_controller_register(struct device *dev, struct device_node *np, + struct dma_chan *(*of_dma_xlate) + (struct of_phandle_args *, struct of_dma *), + void *data) +{ + return -ENODEV; +} + static inline int of_dma_router_register(struct device_node *np, void *(*of_dma_route_allocate) (struct of_phandle_args *, struct of_dma *), From ab2bf6d4c0a0152907b18d25c1b118ea5ea779df Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 25 Feb 2026 16:41:38 -0500 Subject: [PATCH 32/65] dmaengine: mxs-dma: Fix missing return value from of_dma_controller_register() Propagate the return value of of_dma_controller_register() in probe() instead of ignoring it. Fixes: a580b8c5429a6 ("dmaengine: mxs-dma: add dma support for i.MX23/28") Signed-off-by: Frank Li Link: https://patch.msgid.link/20260225-mxsdma-module-v3-2-8f798b13baa6@nxp.com Signed-off-by: Vinod Koul --- drivers/dma/mxs-dma.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c index cfb9962417ef..53f572b6b6fc 100644 --- a/drivers/dma/mxs-dma.c +++ b/drivers/dma/mxs-dma.c @@ -824,6 +824,7 @@ static int mxs_dma_probe(struct platform_device *pdev) if (ret) { dev_err(mxs_dma->dma_device.dev, "failed to register controller\n"); + return ret; } dev_info(mxs_dma->dma_device.dev, "initialized\n"); From 96857a90982c2a461520fadc55dda3b8051e8d96 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 25 Feb 2026 16:41:39 -0500 Subject: [PATCH 33/65] dmaengine: mxs-dma: Use local dev variable in probe() Introduce a local dev variable in probe() to avoid repeated use of &pdev->dev throughout the function. No functional change. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260225-mxsdma-module-v3-3-8f798b13baa6@nxp.com Signed-off-by: Vinod Koul --- drivers/dma/mxs-dma.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c index 53f572b6b6fc..5340f831ae9d 100644 --- a/drivers/dma/mxs-dma.c +++ b/drivers/dma/mxs-dma.c @@ -744,20 +744,21 @@ static int mxs_dma_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; const struct mxs_dma_type *dma_type; + struct device *dev = &pdev->dev; struct mxs_dma_engine *mxs_dma; int ret, i; - mxs_dma = devm_kzalloc(&pdev->dev, sizeof(*mxs_dma), GFP_KERNEL); + mxs_dma = devm_kzalloc(dev, sizeof(*mxs_dma), GFP_KERNEL); if (!mxs_dma) return -ENOMEM; ret = of_property_read_u32(np, "dma-channels", &mxs_dma->nr_channels); if (ret) { - dev_err(&pdev->dev, "failed to read dma-channels\n"); + dev_err(dev, "failed to read dma-channels\n"); return ret; } - dma_type = (struct mxs_dma_type *)of_device_get_match_data(&pdev->dev); + dma_type = (struct mxs_dma_type *)of_device_get_match_data(dev); mxs_dma->type = dma_type->type; mxs_dma->dev_id = dma_type->id; @@ -765,7 +766,7 @@ static int mxs_dma_probe(struct platform_device *pdev) if (IS_ERR(mxs_dma->base)) return PTR_ERR(mxs_dma->base); - mxs_dma->clk = devm_clk_get(&pdev->dev, NULL); + mxs_dma->clk = devm_clk_get(dev, NULL); if (IS_ERR(mxs_dma->clk)) return PTR_ERR(mxs_dma->clk); @@ -795,10 +796,10 @@ static int mxs_dma_probe(struct platform_device *pdev) return ret; mxs_dma->pdev = pdev; - mxs_dma->dma_device.dev = &pdev->dev; + mxs_dma->dma_device.dev = dev; /* mxs_dma gets 65535 bytes maximum sg size */ - dma_set_max_seg_size(mxs_dma->dma_device.dev, MAX_XFER_BYTES); + dma_set_max_seg_size(dev, MAX_XFER_BYTES); mxs_dma->dma_device.device_alloc_chan_resources = mxs_dma_alloc_chan_resources; mxs_dma->dma_device.device_free_chan_resources = mxs_dma_free_chan_resources; @@ -816,18 +817,18 @@ static int mxs_dma_probe(struct platform_device *pdev) ret = dmaenginem_async_device_register(&mxs_dma->dma_device); if (ret) { - dev_err(mxs_dma->dma_device.dev, "unable to register\n"); + dev_err(dev, "unable to register\n"); return ret; } ret = of_dma_controller_register(np, mxs_dma_xlate, mxs_dma); if (ret) { - dev_err(mxs_dma->dma_device.dev, + dev_err(dev, "failed to register controller\n"); return ret; } - dev_info(mxs_dma->dma_device.dev, "initialized\n"); + dev_info(dev, "initialized\n"); return 0; } From 4a5b0a728d665b3b7b08fb5bf6b2f69c995e30ec Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 25 Feb 2026 16:41:40 -0500 Subject: [PATCH 34/65] dmaengine: mxs-dma: Use dev_err_probe() to simplify code Use dev_err_probe() simplify code. No functional change. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260225-mxsdma-module-v3-4-8f798b13baa6@nxp.com Signed-off-by: Vinod Koul --- drivers/dma/mxs-dma.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c index 5340f831ae9d..e05bca738d2e 100644 --- a/drivers/dma/mxs-dma.c +++ b/drivers/dma/mxs-dma.c @@ -753,10 +753,8 @@ static int mxs_dma_probe(struct platform_device *pdev) return -ENOMEM; ret = of_property_read_u32(np, "dma-channels", &mxs_dma->nr_channels); - if (ret) { - dev_err(dev, "failed to read dma-channels\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to read dma-channels\n"); dma_type = (struct mxs_dma_type *)of_device_get_match_data(dev); mxs_dma->type = dma_type->type; @@ -816,17 +814,13 @@ static int mxs_dma_probe(struct platform_device *pdev) mxs_dma->dma_device.device_issue_pending = mxs_dma_enable_chan; ret = dmaenginem_async_device_register(&mxs_dma->dma_device); - if (ret) { - dev_err(dev, "unable to register\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "unable to register\n"); ret = of_dma_controller_register(np, mxs_dma_xlate, mxs_dma); - if (ret) { - dev_err(dev, - "failed to register controller\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, + "failed to register controller\n"); dev_info(dev, "initialized\n"); From d11544c674b64beb9948724ba27187238c52b079 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 25 Feb 2026 16:41:41 -0500 Subject: [PATCH 35/65] dmaengine: mxs-dma: Use managed API devm_of_dma_controller_register() Use managed API devm_of_dma_controller_register() to prepare support module remove. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260225-mxsdma-module-v3-5-8f798b13baa6@nxp.com Signed-off-by: Vinod Koul --- drivers/dma/mxs-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c index e05bca738d2e..be35affb9157 100644 --- a/drivers/dma/mxs-dma.c +++ b/drivers/dma/mxs-dma.c @@ -817,7 +817,7 @@ static int mxs_dma_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "unable to register\n"); - ret = of_dma_controller_register(np, mxs_dma_xlate, mxs_dma); + ret = devm_of_dma_controller_register(dev, np, mxs_dma_xlate, mxs_dma); if (ret) return dev_err_probe(dev, ret, "failed to register controller\n"); From e1b712c9352cf74285973462ced8d60ed7a9183c Mon Sep 17 00:00:00 2001 From: Jindong Yue Date: Wed, 25 Feb 2026 16:41:42 -0500 Subject: [PATCH 36/65] dmaengine: mxs-dma: Add module license and description Module license string is required for loading it as a module. Signed-off-by: Jindong Yue Signed-off-by: Frank Li Link: https://patch.msgid.link/20260225-mxsdma-module-v3-6-8f798b13baa6@nxp.com Signed-off-by: Vinod Koul --- drivers/dma/mxs-dma.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c index be35affb9157..7acb3d29dad3 100644 --- a/drivers/dma/mxs-dma.c +++ b/drivers/dma/mxs-dma.c @@ -836,3 +836,6 @@ static struct platform_driver mxs_dma_driver = { }; builtin_platform_driver(mxs_dma_driver); + +MODULE_DESCRIPTION("MXS DMA driver"); +MODULE_LICENSE("GPL"); From 67adf1f6643d75e33509900a2cb35db3a31f0410 Mon Sep 17 00:00:00 2001 From: Jindong Yue Date: Wed, 25 Feb 2026 16:41:43 -0500 Subject: [PATCH 37/65] dmaengine: mxs-dma: Turn MXS_DMA as tristate Use tristate for mxs-dma to support module building. Signed-off-by: Jindong Yue Signed-off-by: Frank Li Link: https://patch.msgid.link/20260225-mxsdma-module-v3-7-8f798b13baa6@nxp.com Signed-off-by: Vinod Koul --- drivers/dma/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index e249f68423ba..e98e3e8c5036 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -505,7 +505,7 @@ config MV_XOR_V2 platforms. config MXS_DMA - bool "MXS DMA support" + tristate "MXS DMA support" depends on ARCH_MXS || ARCH_MXC || COMPILE_TEST select STMP_DEVICE select DMA_ENGINE From 5daee52d7cc87415367fa0051a80998cccbab920 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 25 Feb 2026 16:41:44 -0500 Subject: [PATCH 38/65] dmaengine: imx-sdma: Use devm_clk_get_prepared() to simplify code Use devm_clk_get_prepared() to simplify code. No functional change. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260225-mxsdma-module-v3-8-8f798b13baa6@nxp.com Signed-off-by: Vinod Koul --- drivers/dma/imx-sdma.c | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c index 4c8196d78001..187e8e573fdf 100644 --- a/drivers/dma/imx-sdma.c +++ b/drivers/dma/imx-sdma.c @@ -2265,34 +2265,24 @@ static int sdma_probe(struct platform_device *pdev) if (IS_ERR(sdma->regs)) return PTR_ERR(sdma->regs); - sdma->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); + sdma->clk_ipg = devm_clk_get_prepared(&pdev->dev, "ipg"); if (IS_ERR(sdma->clk_ipg)) return PTR_ERR(sdma->clk_ipg); - sdma->clk_ahb = devm_clk_get(&pdev->dev, "ahb"); + sdma->clk_ahb = devm_clk_get_prepared(&pdev->dev, "ahb"); if (IS_ERR(sdma->clk_ahb)) return PTR_ERR(sdma->clk_ahb); - ret = clk_prepare(sdma->clk_ipg); - if (ret) - return ret; - - ret = clk_prepare(sdma->clk_ahb); - if (ret) - goto err_clk; - ret = devm_request_irq(&pdev->dev, irq, sdma_int_handler, 0, dev_name(&pdev->dev), sdma); if (ret) - goto err_irq; + return ret; sdma->irq = irq; sdma->script_addrs = kzalloc_obj(*sdma->script_addrs); - if (!sdma->script_addrs) { - ret = -ENOMEM; - goto err_irq; - } + if (!sdma->script_addrs) + return -ENOMEM; /* initially no scripts available */ saddr_arr = (s32 *)sdma->script_addrs; @@ -2406,10 +2396,6 @@ err_register: dma_async_device_unregister(&sdma->dma_device); err_init: kfree(sdma->script_addrs); -err_irq: - clk_unprepare(sdma->clk_ahb); -err_clk: - clk_unprepare(sdma->clk_ipg); return ret; } @@ -2421,8 +2407,6 @@ static void sdma_remove(struct platform_device *pdev) devm_free_irq(&pdev->dev, sdma->irq, sdma); dma_async_device_unregister(&sdma->dma_device); kfree(sdma->script_addrs); - clk_unprepare(sdma->clk_ahb); - clk_unprepare(sdma->clk_ipg); /* Kill the tasklet */ for (i = 0; i < MAX_DMA_CHANNELS; i++) { struct sdma_channel *sdmac = &sdma->channel[i]; From 8982cb214a7f29db7d28058a3b4697f436af34d2 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 25 Feb 2026 16:41:45 -0500 Subject: [PATCH 39/65] dmaengine: imx-sdma: Use managed API to simplify code Use managed API devm_kzalloc(), dmaenginem_async_device_register() and devm_of_dma_controller_register() to simple code. No functional change. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260225-mxsdma-module-v3-9-8f798b13baa6@nxp.com Signed-off-by: Vinod Koul --- drivers/dma/imx-sdma.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c index 187e8e573fdf..16b5f60bc748 100644 --- a/drivers/dma/imx-sdma.c +++ b/drivers/dma/imx-sdma.c @@ -2323,11 +2323,11 @@ static int sdma_probe(struct platform_device *pdev) ret = sdma_init(sdma); if (ret) - goto err_init; + return ret; ret = sdma_event_remap(sdma); if (ret) - goto err_init; + return ret; if (sdma->drvdata->script_addrs) sdma_add_scripts(sdma, sdma->drvdata->script_addrs); @@ -2353,17 +2353,18 @@ static int sdma_probe(struct platform_device *pdev) platform_set_drvdata(pdev, sdma); - ret = dma_async_device_register(&sdma->dma_device); + ret = dmaenginem_async_device_register(&sdma->dma_device); if (ret) { dev_err(&pdev->dev, "unable to register\n"); - goto err_init; + return ret; } if (np) { - ret = of_dma_controller_register(np, sdma_xlate, sdma); + ret = devm_of_dma_controller_register(&pdev->dev, np, + sdma_xlate, sdma); if (ret) { dev_err(&pdev->dev, "failed to register controller\n"); - goto err_register; + return ret; } spba_bus = of_find_compatible_node(NULL, NULL, "fsl,spba-bus"); @@ -2391,12 +2392,6 @@ static int sdma_probe(struct platform_device *pdev) } return 0; - -err_register: - dma_async_device_unregister(&sdma->dma_device); -err_init: - kfree(sdma->script_addrs); - return ret; } static void sdma_remove(struct platform_device *pdev) @@ -2405,8 +2400,6 @@ static void sdma_remove(struct platform_device *pdev) int i; devm_free_irq(&pdev->dev, sdma->irq, sdma); - dma_async_device_unregister(&sdma->dma_device); - kfree(sdma->script_addrs); /* Kill the tasklet */ for (i = 0; i < MAX_DMA_CHANNELS; i++) { struct sdma_channel *sdmac = &sdma->channel[i]; From 917edfa57783352cd491cd5759a04d7b60de1714 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 25 Feb 2026 16:41:46 -0500 Subject: [PATCH 40/65] dmaengine: imx-sdma: Use dev_err_probe() to simplify code Use dev_err_probe() to simplify code. No functional change. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260225-mxsdma-module-v3-10-8f798b13baa6@nxp.com Signed-off-by: Vinod Koul --- drivers/dma/imx-sdma.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c index 16b5f60bc748..3d527883776b 100644 --- a/drivers/dma/imx-sdma.c +++ b/drivers/dma/imx-sdma.c @@ -2354,18 +2354,15 @@ static int sdma_probe(struct platform_device *pdev) platform_set_drvdata(pdev, sdma); ret = dmaenginem_async_device_register(&sdma->dma_device); - if (ret) { - dev_err(&pdev->dev, "unable to register\n"); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, "unable to register\n"); if (np) { ret = devm_of_dma_controller_register(&pdev->dev, np, sdma_xlate, sdma); - if (ret) { - dev_err(&pdev->dev, "failed to register controller\n"); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to register controller\n"); spba_bus = of_find_compatible_node(NULL, NULL, "fsl,spba-bus"); ret = of_address_to_resource(spba_bus, 0, &spba_res); From 4035726a6b724ff0f04b4f1429c7b1a935fc2e76 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 25 Feb 2026 16:41:47 -0500 Subject: [PATCH 41/65] dmaengine: fsl-edma: Use managed API dmaenginem_async_device_register() Use managed API dmaenginem_async_device_register() and devm_of_dma_controller_register() to simple code. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260225-mxsdma-module-v3-11-8f798b13baa6@nxp.com Signed-off-by: Vinod Koul --- drivers/dma/fsl-edma-main.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/dma/fsl-edma-main.c b/drivers/dma/fsl-edma-main.c index dbcdd1e68319..57a185bb4076 100644 --- a/drivers/dma/fsl-edma-main.c +++ b/drivers/dma/fsl-edma-main.c @@ -882,20 +882,19 @@ static int fsl_edma_probe(struct platform_device *pdev) platform_set_drvdata(pdev, fsl_edma); - ret = dma_async_device_register(&fsl_edma->dma_dev); + ret = dmaenginem_async_device_register(&fsl_edma->dma_dev); if (ret) { dev_err(&pdev->dev, "Can't register Freescale eDMA engine. (%d)\n", ret); return ret; } - ret = of_dma_controller_register(np, + ret = devm_of_dma_controller_register(&pdev->dev, np, drvdata->dmamuxs ? fsl_edma_xlate : fsl_edma3_xlate, fsl_edma); if (ret) { dev_err(&pdev->dev, "Can't register Freescale eDMA of_dma. (%d)\n", ret); - dma_async_device_unregister(&fsl_edma->dma_dev); return ret; } @@ -908,12 +907,9 @@ static int fsl_edma_probe(struct platform_device *pdev) static void fsl_edma_remove(struct platform_device *pdev) { - struct device_node *np = pdev->dev.of_node; struct fsl_edma_engine *fsl_edma = platform_get_drvdata(pdev); fsl_edma_irq_exit(pdev, fsl_edma); - of_dma_controller_free(np); - dma_async_device_unregister(&fsl_edma->dma_dev); fsl_edma_cleanup_vchan(&fsl_edma->dma_dev); } From 804e18f7da6d29cff7ed4e004bcc05658f51d737 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 25 Feb 2026 16:41:48 -0500 Subject: [PATCH 42/65] dmaengine: fsl-edma: Use dev_err_probe() to simplify code Use dev_err_probe() to simplify code. No functional change. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260225-mxsdma-module-v3-12-8f798b13baa6@nxp.com Signed-off-by: Vinod Koul --- drivers/dma/fsl-edma-main.c | 47 ++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/drivers/dma/fsl-edma-main.c b/drivers/dma/fsl-edma-main.c index 57a185bb4076..950538cc8883 100644 --- a/drivers/dma/fsl-edma-main.c +++ b/drivers/dma/fsl-edma-main.c @@ -709,16 +709,14 @@ static int fsl_edma_probe(struct platform_device *pdev) int ret, i; drvdata = device_get_match_data(&pdev->dev); - if (!drvdata) { - dev_err(&pdev->dev, "unable to find driver data\n"); - return -EINVAL; - } + if (!drvdata) + return dev_err_probe(&pdev->dev, -EINVAL, + "unable to find driver data\n"); ret = of_property_read_u32(np, "dma-channels", &chans); - if (ret) { - dev_err(&pdev->dev, "Can't get dma-channels.\n"); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Can't get dma-channels.\n"); fsl_edma = devm_kzalloc(&pdev->dev, struct_size(fsl_edma, chans, chans), GFP_KERNEL); @@ -742,10 +740,10 @@ static int fsl_edma_probe(struct platform_device *pdev) if (drvdata->flags & FSL_EDMA_DRV_HAS_DMACLK) { fsl_edma->dmaclk = devm_clk_get_enabled(&pdev->dev, "dma"); - if (IS_ERR(fsl_edma->dmaclk)) { - dev_err(&pdev->dev, "Missing DMA block clock.\n"); - return PTR_ERR(fsl_edma->dmaclk); - } + if (IS_ERR(fsl_edma->dmaclk)) + return dev_err_probe(&pdev->dev, + PTR_ERR(fsl_edma->dmaclk), + "Missing DMA block clock.\n"); } ret = of_property_read_variable_u32_array(np, "dma-channel-mask", chan_mask, 1, 2); @@ -769,11 +767,10 @@ static int fsl_edma_probe(struct platform_device *pdev) sprintf(clkname, "dmamux%d", i); fsl_edma->muxclk[i] = devm_clk_get_enabled(&pdev->dev, clkname); - if (IS_ERR(fsl_edma->muxclk[i])) { - dev_err(&pdev->dev, "Missing DMAMUX block clock.\n"); - /* on error: disable all previously enabled clks */ - return PTR_ERR(fsl_edma->muxclk[i]); - } + if (IS_ERR(fsl_edma->muxclk[i])) + return dev_err_probe(&pdev->dev, + PTR_ERR(fsl_edma->muxclk[i]), + "Missing DMAMUX block clock.\n"); } fsl_edma->big_endian = of_property_read_bool(np, "big-endian"); @@ -883,20 +880,16 @@ static int fsl_edma_probe(struct platform_device *pdev) platform_set_drvdata(pdev, fsl_edma); ret = dmaenginem_async_device_register(&fsl_edma->dma_dev); - if (ret) { - dev_err(&pdev->dev, - "Can't register Freescale eDMA engine. (%d)\n", ret); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Can't register Freescale eDMA engine.\n"); ret = devm_of_dma_controller_register(&pdev->dev, np, drvdata->dmamuxs ? fsl_edma_xlate : fsl_edma3_xlate, fsl_edma); - if (ret) { - dev_err(&pdev->dev, - "Can't register Freescale eDMA of_dma. (%d)\n", ret); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Can't register Freescale eDMA of_dma.\n"); /* enable round robin arbitration */ if (!(drvdata->flags & FSL_EDMA_DRV_SPLIT_REG)) From 2438deea9ff82940ebfce67e232d558199ab8a6e Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 25 Feb 2026 16:41:49 -0500 Subject: [PATCH 43/65] dmaengine: fsl-qdma: Use dev_err_probe() to simplify code Use dev_err_probe() to simplify code. No functional change. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260225-mxsdma-module-v3-13-8f798b13baa6@nxp.com Signed-off-by: Vinod Koul --- drivers/dma/fsl-qdma.c | 47 +++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/drivers/dma/fsl-qdma.c b/drivers/dma/fsl-qdma.c index 0bbff9df362f..df843fad0ece 100644 --- a/drivers/dma/fsl-qdma.c +++ b/drivers/dma/fsl-qdma.c @@ -1127,22 +1127,19 @@ static int fsl_qdma_probe(struct platform_device *pdev) struct device_node *np = pdev->dev.of_node; ret = of_property_read_u32(np, "dma-channels", &chans); - if (ret) { - dev_err(&pdev->dev, "Can't get dma-channels.\n"); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Can't get dma-channels.\n"); ret = of_property_read_u32(np, "block-offset", &blk_off); - if (ret) { - dev_err(&pdev->dev, "Can't get block-offset.\n"); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Can't get block-offset.\n"); ret = of_property_read_u32(np, "block-number", &blk_num); - if (ret) { - dev_err(&pdev->dev, "Can't get block-number.\n"); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Can't get block-number.\n"); blk_num = min_t(int, blk_num, num_online_cpus()); @@ -1167,10 +1164,8 @@ static int fsl_qdma_probe(struct platform_device *pdev) return -ENOMEM; ret = of_property_read_u32(np, "fsl,dma-queues", &queues); - if (ret) { - dev_err(&pdev->dev, "Can't get queues.\n"); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, "Can't get queues.\n"); fsl_qdma->desc_allocated = 0; fsl_qdma->n_chans = chans; @@ -1231,28 +1226,24 @@ static int fsl_qdma_probe(struct platform_device *pdev) fsl_qdma->dma_dev.device_terminate_all = fsl_qdma_terminate_all; ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(40)); - if (ret) { - dev_err(&pdev->dev, "dma_set_mask failure.\n"); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, "dma_set_mask failure.\n"); platform_set_drvdata(pdev, fsl_qdma); ret = fsl_qdma_reg_init(fsl_qdma); - if (ret) { - dev_err(&pdev->dev, "Can't Initialize the qDMA engine.\n"); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Can't Initialize the qDMA engine.\n"); ret = fsl_qdma_irq_init(pdev, fsl_qdma); if (ret) return ret; ret = dma_async_device_register(&fsl_qdma->dma_dev); - if (ret) { - dev_err(&pdev->dev, "Can't register NXP Layerscape qDMA engine.\n"); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Can't register NXP Layerscape qDMA engine.\n"); return 0; } From b34f3fcae72a0afdd1a966fd68309b461bf678e6 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 5 Jan 2026 13:44:42 +0200 Subject: [PATCH 44/65] dmaengine: sh: rz_dmac: make error interrupt optional The Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs do not have an error interrupt for the DMACs, and the current driver implementation does not make much use of it. To prepare for adding support for these SoCs, do not error out if the error interrupt is missing. Signed-off-by: Cosmin Tanislav Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260105114445.878262-2-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index d84ca551b2bf..83aaf4054280 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -956,16 +956,15 @@ static int rz_dmac_probe(struct platform_device *pdev) } /* Register interrupt handler for error */ - irq = platform_get_irq_byname(pdev, irqname); - if (irq < 0) - return irq; - - ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0, - irqname, NULL); - if (ret) { - dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n", - irq, ret); - return ret; + irq = platform_get_irq_byname_optional(pdev, irqname); + if (irq > 0) { + ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0, + irqname, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n", + irq, ret); + return ret; + } } /* Initialize the channels. */ From bbb8b402d798f9f211376cee3d649d64dfc17880 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 5 Jan 2026 13:44:43 +0200 Subject: [PATCH 45/65] dmaengine: sh: rz_dmac: make register_dma_req() chip-specific The Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs use a completely different ICU unit compared to RZ/V2H, which requires a separate implementation. To prepare for adding support for these SoCs, add a chip-specific structure and put a pointer to the rzv2h_icu_register_dma_req() function in the .register_dma_req field of the chip-specific structure to allow for other implementations. Do the same for the default request value, RZV2H_ICU_DMAC_REQ_NO_DEFAULT, and place it into .dma_req_no_default. While at it, factor out the logic that calls .register_dma_req() or rz_dmac_set_dmars_register() into a separate function to remove some code duplication. Signed-off-by: Cosmin Tanislav Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260105114445.878262-3-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 65 ++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 83aaf4054280..c6018e03a08c 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -94,9 +94,16 @@ struct rz_dmac_icu { u8 dmac_index; }; +struct rz_dmac_info { + void (*icu_register_dma_req)(struct platform_device *icu_dev, + u8 dmac_index, u8 dmac_channel, u16 req_no); + u16 default_dma_req_no; +}; + struct rz_dmac { struct dma_device engine; struct rz_dmac_icu icu; + const struct rz_dmac_info *info; struct device *dev; struct reset_control *rstc; void __iomem *base; @@ -105,8 +112,6 @@ struct rz_dmac { unsigned int n_channels; struct rz_dmac_chan *channels; - bool has_icu; - DECLARE_BITMAP(modules, 1024); }; @@ -318,6 +323,16 @@ static void rz_dmac_set_dmars_register(struct rz_dmac *dmac, int nr, u32 dmars) rz_dmac_ext_writel(dmac, dmars32, dmars_offset); } +static void rz_dmac_set_dma_req_no(struct rz_dmac *dmac, unsigned int index, + int req_no) +{ + if (dmac->info->icu_register_dma_req) + dmac->info->icu_register_dma_req(dmac->icu.pdev, dmac->icu.dmac_index, + index, req_no); + else + rz_dmac_set_dmars_register(dmac, index, req_no); +} + static void rz_dmac_prepare_desc_for_memcpy(struct rz_dmac_chan *channel) { struct dma_chan *chan = &channel->vc.chan; @@ -335,13 +350,7 @@ static void rz_dmac_prepare_desc_for_memcpy(struct rz_dmac_chan *channel) lmdesc->chext = 0; lmdesc->header = HEADER_LV; - if (dmac->has_icu) { - rzv2h_icu_register_dma_req(dmac->icu.pdev, dmac->icu.dmac_index, - channel->index, - RZV2H_ICU_DMAC_REQ_NO_DEFAULT); - } else { - rz_dmac_set_dmars_register(dmac, channel->index, 0); - } + rz_dmac_set_dma_req_no(dmac, channel->index, dmac->info->default_dma_req_no); channel->chcfg = chcfg; channel->chctrl = CHCTRL_STG | CHCTRL_SETEN; @@ -392,12 +401,7 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel) channel->lmdesc.tail = lmdesc; - if (dmac->has_icu) { - rzv2h_icu_register_dma_req(dmac->icu.pdev, dmac->icu.dmac_index, - channel->index, channel->mid_rid); - } else { - rz_dmac_set_dmars_register(dmac, channel->index, channel->mid_rid); - } + rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid); channel->chctrl = CHCTRL_SETEN; } @@ -675,13 +679,7 @@ static void rz_dmac_device_synchronize(struct dma_chan *chan) if (ret < 0) dev_warn(dmac->dev, "DMA Timeout"); - if (dmac->has_icu) { - rzv2h_icu_register_dma_req(dmac->icu.pdev, dmac->icu.dmac_index, - channel->index, - RZV2H_ICU_DMAC_REQ_NO_DEFAULT); - } else { - rz_dmac_set_dmars_register(dmac, channel->index, 0); - } + rz_dmac_set_dma_req_no(dmac, channel->index, dmac->info->default_dma_req_no); } /* @@ -870,14 +868,13 @@ static int rz_dmac_parse_of_icu(struct device *dev, struct rz_dmac *dmac) uint32_t dmac_index; int ret; - ret = of_parse_phandle_with_fixed_args(np, "renesas,icu", 1, 0, &args); - if (ret == -ENOENT) + if (!dmac->info->icu_register_dma_req) return 0; + + ret = of_parse_phandle_with_fixed_args(np, "renesas,icu", 1, 0, &args); if (ret) return ret; - dmac->has_icu = true; - dmac->icu.pdev = of_find_device_by_node(args.np); of_node_put(args.np); if (!dmac->icu.pdev) { @@ -932,6 +929,7 @@ static int rz_dmac_probe(struct platform_device *pdev) if (!dmac) return -ENOMEM; + dmac->info = device_get_match_data(&pdev->dev); dmac->dev = &pdev->dev; platform_set_drvdata(pdev, dmac); @@ -949,7 +947,7 @@ static int rz_dmac_probe(struct platform_device *pdev) if (IS_ERR(dmac->base)) return PTR_ERR(dmac->base); - if (!dmac->has_icu) { + if (!dmac->info->icu_register_dma_req) { dmac->ext_base = devm_platform_ioremap_resource(pdev, 1); if (IS_ERR(dmac->ext_base)) return PTR_ERR(dmac->ext_base); @@ -1069,9 +1067,18 @@ static void rz_dmac_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); } +static const struct rz_dmac_info rz_dmac_v2h_info = { + .icu_register_dma_req = rzv2h_icu_register_dma_req, + .default_dma_req_no = RZV2H_ICU_DMAC_REQ_NO_DEFAULT, +}; + +static const struct rz_dmac_info rz_dmac_generic_info = { + .default_dma_req_no = 0, +}; + static const struct of_device_id of_rz_dmac_match[] = { - { .compatible = "renesas,r9a09g057-dmac", }, - { .compatible = "renesas,rz-dmac", }, + { .compatible = "renesas,r9a09g057-dmac", .data = &rz_dmac_v2h_info }, + { .compatible = "renesas,rz-dmac", .data = &rz_dmac_generic_info }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(of, of_rz_dmac_match); From 40dd470a95c0674515ca606757ffe174bd7d3f90 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 5 Jan 2026 13:44:44 +0200 Subject: [PATCH 46/65] dt-bindings: dma: renesas,rz-dmac: document RZ/{T2H,N2H} The Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs have three DMAC instances. Compared to the previously supported RZ/V2H, these SoCs are missing the error interrupt line and the reset lines, and they use a different ICU IP. Document them, and use RZ/T2H as a fallback for RZ/N2H as the DMACs are entirely compatible. Signed-off-by: Cosmin Tanislav Reviewed-by: Rob Herring (Arm) Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260105114445.878262-4-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Vinod Koul --- .../bindings/dma/renesas,rz-dmac.yaml | 100 ++++++++++++++---- 1 file changed, 82 insertions(+), 18 deletions(-) diff --git a/Documentation/devicetree/bindings/dma/renesas,rz-dmac.yaml b/Documentation/devicetree/bindings/dma/renesas,rz-dmac.yaml index e3311029eb2f..76d30898b761 100644 --- a/Documentation/devicetree/bindings/dma/renesas,rz-dmac.yaml +++ b/Documentation/devicetree/bindings/dma/renesas,rz-dmac.yaml @@ -30,6 +30,13 @@ properties: - const: renesas,r9a09g057-dmac # RZ/V2H(P) + - const: renesas,r9a09g077-dmac # RZ/T2H + + - items: + - enum: + - renesas,r9a09g087-dmac # RZ/N2H + - const: renesas,r9a09g077-dmac + reg: items: - description: Control and channel register block @@ -37,27 +44,12 @@ properties: minItems: 1 interrupts: + minItems: 16 maxItems: 17 interrupt-names: - items: - - const: error - - const: ch0 - - const: ch1 - - const: ch2 - - const: ch3 - - const: ch4 - - const: ch5 - - const: ch6 - - const: ch7 - - const: ch8 - - const: ch9 - - const: ch10 - - const: ch11 - - const: ch12 - - const: ch13 - - const: ch14 - - const: ch15 + minItems: 16 + maxItems: 17 clocks: items: @@ -123,6 +115,35 @@ required: allOf: - $ref: dma-controller.yaml# + - if: + properties: + compatible: + contains: + enum: + - renesas,rz-dmac + - renesas,r9a09g057-dmac + then: + properties: + interrupt-names: + items: + - const: error + - const: ch0 + - const: ch1 + - const: ch2 + - const: ch3 + - const: ch4 + - const: ch5 + - const: ch6 + - const: ch7 + - const: ch8 + - const: ch9 + - const: ch10 + - const: ch11 + - const: ch12 + - const: ch13 + - const: ch14 + - const: ch15 + - if: properties: compatible: @@ -190,6 +211,49 @@ allOf: - renesas,icu - resets + - if: + properties: + compatible: + contains: + const: renesas,r9a09g077-dmac + then: + properties: + reg: + maxItems: 1 + clocks: + maxItems: 1 + + clock-names: false + resets: false + reset-names: false + + interrupts: + maxItems: 16 + + interrupt-names: + items: + - const: ch0 + - const: ch1 + - const: ch2 + - const: ch3 + - const: ch4 + - const: ch5 + - const: ch6 + - const: ch7 + - const: ch8 + - const: ch9 + - const: ch10 + - const: ch11 + - const: ch12 + - const: ch13 + - const: ch14 + - const: ch15 + + required: + - clocks + - power-domains + - renesas,icu + additionalProperties: false examples: From c03d8b5462bcb0022f9477d09eb37dae66c3a769 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 5 Jan 2026 13:44:45 +0200 Subject: [PATCH 47/65] dmaengine: sh: rz_dmac: add RZ/{T2H,N2H} support The Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs use a completely different ICU unit compared to RZ/V2H, which requires a separate implementation. Add support for them. RZ/N2H will use RZ/T2H as a fallback. Signed-off-by: Cosmin Tanislav Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260105114445.878262-5-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index c6018e03a08c..c75e9202e239 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1072,12 +1073,18 @@ static const struct rz_dmac_info rz_dmac_v2h_info = { .default_dma_req_no = RZV2H_ICU_DMAC_REQ_NO_DEFAULT, }; +static const struct rz_dmac_info rz_dmac_t2h_info = { + .icu_register_dma_req = rzt2h_icu_register_dma_req, + .default_dma_req_no = RZT2H_ICU_DMAC_REQ_NO_DEFAULT, +}; + static const struct rz_dmac_info rz_dmac_generic_info = { .default_dma_req_no = 0, }; static const struct of_device_id of_rz_dmac_match[] = { { .compatible = "renesas,r9a09g057-dmac", .data = &rz_dmac_v2h_info }, + { .compatible = "renesas,r9a09g077-dmac", .data = &rz_dmac_t2h_info }, { .compatible = "renesas,rz-dmac", .data = &rz_dmac_generic_info }, { /* Sentinel */ } }; From be342fb7f2bb5f641419fef3109eaffd469b0d44 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 16 Mar 2026 15:32:47 +0200 Subject: [PATCH 48/65] dmaengine: sh: rz-dmac: Drop read of CHCTRL register The CHCTRL register has 11 bits that can be updated by software. The documentation for all these bits states the following: - A read operation results in 0 being read - Writing zero does not affect the operation All bits in the CHCTRL register accessible by software are set and clear bits. The documentation for the CLREND bit of CHCTRL states: Setting this bit to 1 can clear the END bit of the CHSTAT_n/nS register. Also, the DMA transfer end interrupt is cleared. An attempt to read this bit results in 0 being read. 1: Clears the END bit. 0: Does not affect the operation. Since writing zero to any bit in this register does not affect controller operation and reads always return zero, there is no need to perform read-modify-write accesses to set the CLREND bit. Drop the read of the CHCTRL register. Also, since setting the CLREND bit does not interact with other functionalities exposed through this register and only clears the END interrupt, there is no need to lock around this operation. Add a comment to document this. Reviewed-by: Biju Das Reviewed-by: Frank Li Signed-off-by: Claudiu Beznea Link: https://patch.msgid.link/20260316133252.240348-4-claudiu.beznea.uj@bp.renesas.com Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index e2d506eb8194..29fa2ad07e30 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -696,7 +696,7 @@ static void rz_dmac_irq_handle_channel(struct rz_dmac_chan *channel) { struct dma_chan *chan = &channel->vc.chan; struct rz_dmac *dmac = to_rz_dmac(chan->device); - u32 chstat, chctrl; + u32 chstat; chstat = rz_dmac_ch_readl(channel, CHSTAT, 1); if (chstat & CHSTAT_ER) { @@ -708,8 +708,11 @@ static void rz_dmac_irq_handle_channel(struct rz_dmac_chan *channel) goto done; } - chctrl = rz_dmac_ch_readl(channel, CHCTRL, 1); - rz_dmac_ch_writel(channel, chctrl | CHCTRL_CLREND, CHCTRL, 1); + /* + * No need to lock. This just clears the END interrupt. Writing + * zeros to CHCTRL is just ignored by HW. + */ + rz_dmac_ch_writel(channel, CHCTRL_CLREND, CHCTRL, 1); done: return; } From 7badd294fc82629378b153327c57b8ba453688c7 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 16 Mar 2026 15:32:48 +0200 Subject: [PATCH 49/65] dmaengine: sh: rz-dmac: Drop goto instruction and label There is no need to jump to the done label, so return immediately. Reviewed-by: Frank Li Signed-off-by: Claudiu Beznea Link: https://patch.msgid.link/20260316133252.240348-5-claudiu.beznea.uj@bp.renesas.com Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 29fa2ad07e30..6c9bfe39a11e 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -705,7 +705,7 @@ static void rz_dmac_irq_handle_channel(struct rz_dmac_chan *channel) scoped_guard(spinlock_irqsave, &channel->vc.lock) rz_dmac_ch_writel(channel, CHCTRL_DEFAULT, CHCTRL, 1); - goto done; + return; } /* @@ -713,8 +713,6 @@ static void rz_dmac_irq_handle_channel(struct rz_dmac_chan *channel) * zeros to CHCTRL is just ignored by HW. */ rz_dmac_ch_writel(channel, CHCTRL_CLREND, CHCTRL, 1); -done: - return; } static irqreturn_t rz_dmac_irq_handler(int irq, void *dev_id) From be25945d0ca3ac736c448b530c47e854c82a0343 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 16 Mar 2026 15:32:49 +0200 Subject: [PATCH 50/65] dmaengine: sh: rz-dmac: Drop unnecessary local_irq_save() call rz_dmac_enable_hw() calls local_irq_save()/local_irq_restore(), but this is not needed because the callers of rz_dmac_enable_hw() already protect the critical section using spin_lock_irqsave()/spin_lock_irqrestore(). Remove the local_irq_save()/local_irq_restore() calls. Reviewed-by: Frank Li Signed-off-by: Claudiu Beznea Link: https://patch.msgid.link/20260316133252.240348-6-claudiu.beznea.uj@bp.renesas.com Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 6c9bfe39a11e..eca62d9e9772 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -272,15 +272,12 @@ static void rz_dmac_enable_hw(struct rz_dmac_chan *channel) { struct dma_chan *chan = &channel->vc.chan; struct rz_dmac *dmac = to_rz_dmac(chan->device); - unsigned long flags; u32 nxla; u32 chctrl; u32 chstat; dev_dbg(dmac->dev, "%s channel %d\n", __func__, channel->index); - local_irq_save(flags); - rz_dmac_lmdesc_recycle(channel); nxla = channel->lmdesc.base_dma + @@ -295,8 +292,6 @@ static void rz_dmac_enable_hw(struct rz_dmac_chan *channel) rz_dmac_ch_writel(channel, CHCTRL_SWRST, CHCTRL, 1); rz_dmac_ch_writel(channel, chctrl, CHCTRL, 1); } - - local_irq_restore(flags); } static void rz_dmac_disable_hw(struct rz_dmac_chan *channel) From bfaa60be647842cece968769f208e57fa5dee594 Mon Sep 17 00:00:00 2001 From: John Madieu Date: Mon, 16 Mar 2026 15:32:50 +0200 Subject: [PATCH 51/65] dmaengine: sh: rz-dmac: Use rz_lmdesc_setup() to invalidate descriptors rz_lmdesc_setup() invalidates DMA descriptors more comprehensively. It resets the base, head, and tail pointers of the descriptor list and clears the descriptor headers and their NXLA pointers. Use rz_lmdesc_setup() instead of open-coding parts of its logic. Signed-off-by: John Madieu Signed-off-by: Claudiu Beznea Reviewed-by: Frank Li Link: https://patch.msgid.link/20260316133252.240348-7-claudiu.beznea.uj@bp.renesas.com Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index eca62d9e9772..6bfa77844e02 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -460,15 +460,12 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan) { struct rz_dmac_chan *channel = to_rz_dmac_chan(chan); struct rz_dmac *dmac = to_rz_dmac(chan->device); - struct rz_lmdesc *lmdesc = channel->lmdesc.base; struct rz_dmac_desc *desc, *_desc; unsigned long flags; - unsigned int i; spin_lock_irqsave(&channel->vc.lock, flags); - for (i = 0; i < DMAC_NR_LMDESC; i++) - lmdesc[i].header = 0; + rz_lmdesc_setup(channel, channel->lmdesc.base); rz_dmac_disable_hw(channel); list_splice_tail_init(&channel->ld_active, &channel->ld_free); @@ -560,15 +557,12 @@ rz_dmac_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, static int rz_dmac_terminate_all(struct dma_chan *chan) { struct rz_dmac_chan *channel = to_rz_dmac_chan(chan); - struct rz_lmdesc *lmdesc = channel->lmdesc.base; unsigned long flags; - unsigned int i; LIST_HEAD(head); spin_lock_irqsave(&channel->vc.lock, flags); rz_dmac_disable_hw(channel); - for (i = 0; i < DMAC_NR_LMDESC; i++) - lmdesc[i].header = 0; + rz_lmdesc_setup(channel, channel->lmdesc.base); list_splice_tail_init(&channel->ld_active, &channel->ld_free); list_splice_tail_init(&channel->ld_queue, &channel->ld_free); From 21323b118c16d287355e6497e1098ce1ca348bd6 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Mon, 16 Mar 2026 15:32:51 +0200 Subject: [PATCH 52/65] dmaengine: sh: rz-dmac: Add device_tx_status() callback The RZ/G2L SCIFA driver uses dmaengine_prep_slave_sg() to enqueue DMA transfers and implements a timeout mechanism on RX to handle cases where a DMA transfer does not complete. The timeout is implemented using an hrtimer. In the hrtimer callback, dmaengine_tx_status() is called (along with dmaengine_pause()) to retrieve the transfer residue and handle incomplete DMA transfers. Add support for the device_tx_status() callback. Co-developed-by: Long Luu Signed-off-by: Long Luu Signed-off-by: Biju Das Co-developed-by: Claudiu Beznea Signed-off-by: Claudiu Beznea Reviewed-by: Frank Li Link: https://patch.msgid.link/20260316133252.240348-8-claudiu.beznea.uj@bp.renesas.com Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 144 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 1 deletion(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 6bfa77844e02..4f6f9f4bacca 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -124,10 +124,12 @@ struct rz_dmac { * Registers */ +#define CRTB 0x0020 #define CHSTAT 0x0024 #define CHCTRL 0x0028 #define CHCFG 0x002c #define NXLA 0x0038 +#define CRLA 0x003c #define DCTRL 0x0000 @@ -676,6 +678,145 @@ static void rz_dmac_device_synchronize(struct dma_chan *chan) rz_dmac_set_dma_req_no(dmac, channel->index, dmac->info->default_dma_req_no); } +static struct rz_lmdesc * +rz_dmac_get_next_lmdesc(struct rz_lmdesc *base, struct rz_lmdesc *lmdesc) +{ + struct rz_lmdesc *next = ++lmdesc; + + if (next >= base + DMAC_NR_LMDESC) + next = base; + + return next; +} + +static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel, u32 crla) +{ + struct rz_lmdesc *lmdesc = channel->lmdesc.head; + struct dma_chan *chan = &channel->vc.chan; + struct rz_dmac *dmac = to_rz_dmac(chan->device); + u32 residue = 0, i = 0; + + while (lmdesc->nxla != crla) { + lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc); + if (++i >= DMAC_NR_LMDESC) + return 0; + } + + /* Calculate residue from next lmdesc to end of virtual desc */ + while (lmdesc->chcfg & CHCFG_DEM) { + residue += lmdesc->tb; + lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc); + } + + dev_dbg(dmac->dev, "%s: VD residue is %u\n", __func__, residue); + + return residue; +} + +static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel, + dma_cookie_t cookie) +{ + struct rz_dmac_desc *current_desc, *desc; + enum dma_status status; + u32 crla, crtb, i; + + /* Get current processing virtual descriptor */ + current_desc = list_first_entry(&channel->ld_active, + struct rz_dmac_desc, node); + if (!current_desc) + return 0; + + /* + * If the cookie corresponds to a descriptor that has been completed + * there is no residue. The same check has already been performed by the + * caller but without holding the channel lock, so the descriptor could + * now be complete. + */ + status = dma_cookie_status(&channel->vc.chan, cookie, NULL); + if (status == DMA_COMPLETE) + return 0; + + /* + * If the cookie doesn't correspond to the currently processing virtual + * descriptor then the descriptor hasn't been processed yet, and the + * residue is equal to the full descriptor size. Also, a client driver + * is possible to call this function before rz_dmac_irq_handler_thread() + * runs. In this case, the running descriptor will be the next + * descriptor, and will appear in the done list. So, if the argument + * cookie matches the done list's cookie, we can assume the residue is + * zero. + */ + if (cookie != current_desc->vd.tx.cookie) { + list_for_each_entry(desc, &channel->ld_free, node) { + if (cookie == desc->vd.tx.cookie) + return 0; + } + + list_for_each_entry(desc, &channel->ld_queue, node) { + if (cookie == desc->vd.tx.cookie) + return desc->len; + } + + list_for_each_entry(desc, &channel->ld_active, node) { + if (cookie == desc->vd.tx.cookie) + return desc->len; + } + + /* + * No descriptor found for the cookie, there's thus no residue. + * This shouldn't happen if the calling driver passes a correct + * cookie value. + */ + WARN(1, "No descriptor for cookie!"); + return 0; + } + + /* + * We need to read two registers. Make sure the hardware does not move + * to next lmdesc while reading the current lmdesc. Trying it 3 times + * should be enough: initial read, retry, retry for the paranoid. + */ + for (i = 0; i < 3; i++) { + crla = rz_dmac_ch_readl(channel, CRLA, 1); + crtb = rz_dmac_ch_readl(channel, CRTB, 1); + /* Still the same? */ + if (crla == rz_dmac_ch_readl(channel, CRLA, 1)) + break; + } + + WARN_ONCE(i >= 3, "residue might not be continuous!"); + + /* + * Calculate number of bytes transferred in processing virtual descriptor. + * One virtual descriptor can have many lmdesc. + */ + return crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, crla); +} + +static enum dma_status rz_dmac_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, + struct dma_tx_state *txstate) +{ + struct rz_dmac_chan *channel = to_rz_dmac_chan(chan); + enum dma_status status; + u32 residue; + + status = dma_cookie_status(chan, cookie, txstate); + if (status == DMA_COMPLETE || !txstate) + return status; + + scoped_guard(spinlock_irqsave, &channel->vc.lock) + residue = rz_dmac_chan_get_residue(channel, cookie); + + /* if there's no residue, the cookie is complete */ + if (!residue) + return DMA_COMPLETE; + + dma_set_residue(txstate, residue); + + return status; +} + /* * ----------------------------------------------------------------------------- * IRQ handling @@ -997,6 +1138,7 @@ static int rz_dmac_probe(struct platform_device *pdev) engine = &dmac->engine; dma_cap_set(DMA_SLAVE, engine->cap_mask); dma_cap_set(DMA_MEMCPY, engine->cap_mask); + engine->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_0_7_COMMON_BASE + DCTRL); rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_8_15_COMMON_BASE + DCTRL); @@ -1004,7 +1146,7 @@ static int rz_dmac_probe(struct platform_device *pdev) engine->device_alloc_chan_resources = rz_dmac_alloc_chan_resources; engine->device_free_chan_resources = rz_dmac_free_chan_resources; - engine->device_tx_status = dma_cookie_status; + engine->device_tx_status = rz_dmac_tx_status; engine->device_prep_slave_sg = rz_dmac_prep_slave_sg; engine->device_prep_dma_memcpy = rz_dmac_prep_dma_memcpy; engine->device_config = rz_dmac_config; From 44f991bd6e01bb6a3f78da98eafa6d2a72819a2f Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 16 Mar 2026 15:32:52 +0200 Subject: [PATCH 53/65] dmaengine: sh: rz-dmac: Add device_{pause,resume}() callbacks The RZ/G2L SCIFA driver uses dmaengine_prep_slave_sg() to enqueue DMA transfers and implements a timeout mechanism on RX to handle cases where a DMA transfer does not complete. The timeout is implemented using an hrtimer. In the hrtimer callback, dmaengine_tx_status() is called (along with dmaengine_pause()) to retrieve the transfer residue and handle incomplete DMA transfers. Add support for device_{pause, resume}() callbacks. Signed-off-by: Claudiu Beznea Link: https://patch.msgid.link/20260316133252.240348-9-claudiu.beznea.uj@bp.renesas.com Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 49 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 4f6f9f4bacca..625ff29024de 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -140,10 +140,12 @@ struct rz_dmac { #define CHANNEL_8_15_COMMON_BASE 0x0700 #define CHSTAT_ER BIT(4) +#define CHSTAT_SUS BIT(3) #define CHSTAT_EN BIT(0) #define CHCTRL_CLRINTMSK BIT(17) #define CHCTRL_CLRSUS BIT(9) +#define CHCTRL_SETSUS BIT(8) #define CHCTRL_CLRTC BIT(6) #define CHCTRL_CLREND BIT(5) #define CHCTRL_CLRRQ BIT(4) @@ -805,11 +807,18 @@ static enum dma_status rz_dmac_tx_status(struct dma_chan *chan, if (status == DMA_COMPLETE || !txstate) return status; - scoped_guard(spinlock_irqsave, &channel->vc.lock) + scoped_guard(spinlock_irqsave, &channel->vc.lock) { + u32 val; + residue = rz_dmac_chan_get_residue(channel, cookie); - /* if there's no residue, the cookie is complete */ - if (!residue) + val = rz_dmac_ch_readl(channel, CHSTAT, 1); + if (val & CHSTAT_SUS) + status = DMA_PAUSED; + } + + /* if there's no residue and no paused, the cookie is complete */ + if (!residue && status != DMA_PAUSED) return DMA_COMPLETE; dma_set_residue(txstate, residue); @@ -817,6 +826,38 @@ static enum dma_status rz_dmac_tx_status(struct dma_chan *chan, return status; } +static int rz_dmac_device_pause(struct dma_chan *chan) +{ + struct rz_dmac_chan *channel = to_rz_dmac_chan(chan); + u32 val; + + guard(spinlock_irqsave)(&channel->vc.lock); + + val = rz_dmac_ch_readl(channel, CHSTAT, 1); + if (!(val & CHSTAT_EN)) + return 0; + + rz_dmac_ch_writel(channel, CHCTRL_SETSUS, CHCTRL, 1); + return read_poll_timeout_atomic(rz_dmac_ch_readl, val, + (val & CHSTAT_SUS), 1, 1024, + false, channel, CHSTAT, 1); +} + +static int rz_dmac_device_resume(struct dma_chan *chan) +{ + struct rz_dmac_chan *channel = to_rz_dmac_chan(chan); + u32 val; + + guard(spinlock_irqsave)(&channel->vc.lock); + + /* Do not check CHSTAT_SUS but rely on HW capabilities. */ + + rz_dmac_ch_writel(channel, CHCTRL_CLRSUS, CHCTRL, 1); + return read_poll_timeout_atomic(rz_dmac_ch_readl, val, + !(val & CHSTAT_SUS), 1, 1024, + false, channel, CHSTAT, 1); +} + /* * ----------------------------------------------------------------------------- * IRQ handling @@ -1153,6 +1194,8 @@ static int rz_dmac_probe(struct platform_device *pdev) engine->device_terminate_all = rz_dmac_terminate_all; engine->device_issue_pending = rz_dmac_issue_pending; engine->device_synchronize = rz_dmac_device_synchronize; + engine->device_pause = rz_dmac_device_pause; + engine->device_resume = rz_dmac_device_resume; engine->copy_align = DMAENGINE_ALIGN_1_BYTE; dma_set_max_seg_size(engine->dev, U32_MAX); From dece5b9185ba4c3941f5fffb432f7584138833aa Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 6 Mar 2026 14:58:17 +0000 Subject: [PATCH 54/65] dt-bindings: dma: rz-dmac: Add conditional schema for RZ/G3L The RZ/G3L DMA controller is compatible with RZ/G2L, sharing the same IP. However, the conditional schema logic that enforces RZ/G2L-specific binding constraints was not extended to cover the RZ/G3L compatible string, leaving its bindings without proper validation. Add the RZ/G3L compatible string to the existing RZ/G2L conditional schema so that the same property constraints are applied to both SoCs. Signed-off-by: Biju Das Fixes: e45cf0c7d9b960f1 ("dt-bindings: dma: rz-dmac: Document RZ/G3L SoC") Reviewed-by: Geert Uytterhoeven Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260306145819.897047-1-biju.das.jz@bp.renesas.com Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/dma/renesas,rz-dmac.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/dma/renesas,rz-dmac.yaml b/Documentation/devicetree/bindings/dma/renesas,rz-dmac.yaml index 76d30898b761..0155a15e200b 100644 --- a/Documentation/devicetree/bindings/dma/renesas,rz-dmac.yaml +++ b/Documentation/devicetree/bindings/dma/renesas,rz-dmac.yaml @@ -153,6 +153,7 @@ allOf: - renesas,r9a07g044-dmac - renesas,r9a07g054-dmac - renesas,r9a08g045-dmac + - renesas,r9a08g046-dmac then: properties: reg: From 2d5c2952b972be1cc87c215a2636d208b5e483d4 Mon Sep 17 00:00:00 2001 From: Abin Joseph Date: Mon, 9 Mar 2026 09:04:44 +0530 Subject: [PATCH 55/65] dt-bindings: dma: xlnx,axi-dma: Convert to DT schema Convert the bindings document for Xilinx DMA. No changes to existing binding description. Signed-off-by: Abin Joseph Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260309033444.3472359-1-abin.joseph@amd.com Signed-off-by: Vinod Koul --- .../bindings/dma/xilinx/xilinx_dma.txt | 111 ------- .../bindings/dma/xilinx/xlnx,axi-dma.yaml | 299 ++++++++++++++++++ 2 files changed, 299 insertions(+), 111 deletions(-) delete mode 100644 Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt create mode 100644 Documentation/devicetree/bindings/dma/xilinx/xlnx,axi-dma.yaml diff --git a/Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt b/Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt deleted file mode 100644 index b567107270cb..000000000000 --- a/Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt +++ /dev/null @@ -1,111 +0,0 @@ -Xilinx AXI VDMA engine, it does transfers between memory and video devices. -It can be configured to have one channel or two channels. If configured -as two channels, one is to transmit to the video device and another is -to receive from the video device. - -Xilinx AXI DMA engine, it does transfers between memory and AXI4 stream -target devices. It can be configured to have one channel or two channels. -If configured as two channels, one is to transmit to the device and another -is to receive from the device. - -Xilinx AXI CDMA engine, it does transfers between memory-mapped source -address and a memory-mapped destination address. - -Xilinx AXI MCDMA engine, it does transfer between memory and AXI4 stream -target devices. It can be configured to have up to 16 independent transmit -and receive channels. - -Required properties: -- compatible: Should be one of- - "xlnx,axi-vdma-1.00.a" - "xlnx,axi-dma-1.00.a" - "xlnx,axi-cdma-1.00.a" - "xlnx,axi-mcdma-1.00.a" -- #dma-cells: Should be <1>, see "dmas" property below -- reg: Should contain VDMA registers location and length. -- xlnx,addrwidth: Should be the vdma addressing size in bits(ex: 32 bits). -- dma-ranges: Should be as the following . -- dma-channel child node: Should have at least one channel and can have up to - two channels per device. This node specifies the properties of each - DMA channel (see child node properties below). -- clocks: Input clock specifier. Refer to common clock bindings. -- clock-names: List of input clocks - For VDMA: - Required elements: "s_axi_lite_aclk" - Optional elements: "m_axi_mm2s_aclk" "m_axi_s2mm_aclk", - "m_axis_mm2s_aclk", "s_axis_s2mm_aclk" - For CDMA: - Required elements: "s_axi_lite_aclk", "m_axi_aclk" - For AXIDMA and MCDMA: - Required elements: "s_axi_lite_aclk" - Optional elements: "m_axi_mm2s_aclk", "m_axi_s2mm_aclk", - "m_axi_sg_aclk" - -Required properties for VDMA: -- xlnx,num-fstores: Should be the number of framebuffers as configured in h/w. - -Optional properties for AXI DMA and MCDMA: -- xlnx,sg-length-width: Should be set to the width in bits of the length - register as configured in h/w. Takes values {8...26}. If the property - is missing or invalid then the default value 23 is used. This is the - maximum value that is supported by all IP versions. - -Optional properties for AXI DMA: -- xlnx,axistream-connected: Tells whether DMA is connected to AXI stream IP. -- xlnx,irq-delay: Tells the interrupt delay timeout value. Valid range is from - 0-255. Setting this value to zero disables the delay timer interrupt. - 1 timeout interval = 125 * clock period of SG clock. -Optional properties for VDMA: -- xlnx,flush-fsync: Tells which channel to Flush on Frame sync. - It takes following values: - {1}, flush both channels - {2}, flush mm2s channel - {3}, flush s2mm channel - -Required child node properties: -- compatible: - For VDMA: It should be either "xlnx,axi-vdma-mm2s-channel" or - "xlnx,axi-vdma-s2mm-channel". - For CDMA: It should be "xlnx,axi-cdma-channel". - For AXIDMA and MCDMA: It should be either "xlnx,axi-dma-mm2s-channel" - or "xlnx,axi-dma-s2mm-channel". -- interrupts: Should contain per channel VDMA interrupts. -- xlnx,datawidth: Should contain the stream data width, take values - {32,64...1024}. - -Optional child node properties: -- xlnx,include-dre: Tells hardware is configured for Data - Realignment Engine. -Optional child node properties for VDMA: -- xlnx,genlock-mode: Tells Genlock synchronization is - enabled/disabled in hardware. -- xlnx,enable-vert-flip: Tells vertical flip is - enabled/disabled in hardware(S2MM path). -Optional child node properties for MCDMA: -- dma-channels: Number of dma channels in child node. - -Example: -++++++++ - -axi_vdma_0: axivdma@40030000 { - compatible = "xlnx,axi-vdma-1.00.a"; - #dma_cells = <1>; - reg = < 0x40030000 0x10000 >; - dma-ranges = <0x00000000 0x00000000 0x40000000>; - xlnx,num-fstores = <0x8>; - xlnx,flush-fsync = <0x1>; - xlnx,addrwidth = <0x20>; - clocks = <&clk 0>, <&clk 1>, <&clk 2>, <&clk 3>, <&clk 4>; - clock-names = "s_axi_lite_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk", - "m_axis_mm2s_aclk", "s_axis_s2mm_aclk"; - dma-channel@40030000 { - compatible = "xlnx,axi-vdma-mm2s-channel"; - interrupts = < 0 54 4 >; - xlnx,datawidth = <0x40>; - } ; - dma-channel@40030030 { - compatible = "xlnx,axi-vdma-s2mm-channel"; - interrupts = < 0 53 4 >; - xlnx,datawidth = <0x40>; - } ; -} ; diff --git a/Documentation/devicetree/bindings/dma/xilinx/xlnx,axi-dma.yaml b/Documentation/devicetree/bindings/dma/xilinx/xlnx,axi-dma.yaml new file mode 100644 index 000000000000..340ae9e91cb0 --- /dev/null +++ b/Documentation/devicetree/bindings/dma/xilinx/xlnx,axi-dma.yaml @@ -0,0 +1,299 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/dma/xilinx/xlnx,axi-dma.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Xilinx AXI VDMA, DMA, CDMA and MCDMA IP + +maintainers: + - Radhey Shyam Pandey + - Abin Joseph + +description: > + Xilinx AXI VDMA engine, it does transfers between memory and video devices. + It can be configured to have one channel or two channels. If configured + as two channels, one is to transmit to the video device and another is + to receive from the video device. + + Xilinx AXI DMA engine, it does transfers between memory and AXI4 stream + target devices. It can be configured to have one channel or two channels. + If configured as two channels, one is to transmit to the device and another + is to receive from the device. + + Xilinx AXI CDMA engine, it does transfers between memory-mapped source + address and a memory-mapped destination address. + + Xilinx AXI MCDMA engine, it does transfer between memory and AXI4 stream + target devices. It can be configured to have up to 16 independent transmit + and receive channels. + +properties: + compatible: + enum: + - xlnx,axi-cdma-1.00.a + - xlnx,axi-dma-1.00.a + - xlnx,axi-mcdma-1.00.a + - xlnx,axi-vdma-1.00.a + + reg: + maxItems: 1 + + "#dma-cells": + const: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 1 + + interrupts: + items: + - description: Interrupt for single channel (MM2S or S2MM) + - description: Interrupt for dual channel configuration + minItems: 1 + description: + Interrupt lines for the DMA controller. Only used when + xlnx,axistream-connected is present (DMA connected to AXI Stream + IP). When child dma-channel nodes are present, interrupts are + specified in the child nodes instead. + + clocks: + minItems: 1 + maxItems: 5 + + clock-names: + minItems: 1 + maxItems: 5 + + dma-ranges: true + + xlnx,addrwidth: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [32, 64] + description: The DMA addressing size in bits. + + xlnx,num-fstores: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 32 + description: Should be the number of framebuffers as configured in h/w. + + xlnx,flush-fsync: + type: boolean + description: Tells which channel to Flush on Frame sync. + + xlnx,sg-length-width: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 8 + maximum: 26 + default: 23 + description: + Width in bits of the length register as configured in hardware. + + xlnx,irq-delay: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 255 + description: + Tells the interrupt delay timeout value. Valid range is from 0-255. + Setting this value to zero disables the delay timer interrupt. + 1 timeout interval = 125 * clock period of SG clock. + + xlnx,axistream-connected: + type: boolean + description: Tells whether DMA is connected to AXI stream IP. + +patternProperties: + "^dma-channel(-mm2s|-s2mm)?$": + type: object + description: + Should have at least one channel and can have up to two channels per + device. This node specifies the properties of each DMA channel. + + properties: + compatible: + enum: + - xlnx,axi-vdma-mm2s-channel + - xlnx,axi-vdma-s2mm-channel + - xlnx,axi-cdma-channel + - xlnx,axi-dma-mm2s-channel + - xlnx,axi-dma-s2mm-channel + + interrupts: + maxItems: 1 + + xlnx,datawidth: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [32, 64, 128, 256, 512, 1024] + description: Should contain the stream data width, take values {32,64...1024}. + + xlnx,include-dre: + type: boolean + description: Tells hardware is configured for Data Realignment Engine. + + xlnx,genlock-mode: + type: boolean + description: Tells Genlock synchronization is enabled/disabled in hardware. + + xlnx,enable-vert-flip: + type: boolean + description: + Tells vertical flip is enabled/disabled in hardware(S2MM path). + + dma-channels: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Number of dma channels in child node. + + required: + - compatible + - interrupts + - xlnx,datawidth + + additionalProperties: false + +allOf: + - $ref: ../dma-controller.yaml# + + - if: + properties: + compatible: + contains: + const: xlnx,axi-vdma-1.00.a + then: + properties: + clock-names: + items: + - const: s_axi_lite_aclk + - const: m_axi_mm2s_aclk + - const: m_axi_s2mm_aclk + - const: m_axis_mm2s_aclk + - const: s_axis_s2mm_aclk + minItems: 1 + interrupts: false + patternProperties: + "^dma-channel(-mm2s|-s2mm)?$": + properties: + compatible: + enum: + - xlnx,axi-vdma-mm2s-channel + - xlnx,axi-vdma-s2mm-channel + required: + - xlnx,num-fstores + + - if: + properties: + compatible: + contains: + const: xlnx,axi-cdma-1.00.a + then: + properties: + clock-names: + items: + - const: s_axi_lite_aclk + - const: m_axi_aclk + interrupts: false + patternProperties: + "^dma-channel(-mm2s|-s2mm)?$": + properties: + compatible: + enum: + - xlnx,axi-cdma-channel + + - if: + properties: + compatible: + contains: + enum: + - xlnx,axi-dma-1.00.a + - xlnx,axi-mcdma-1.00.a + then: + properties: + clock-names: + items: + - const: s_axi_lite_aclk + - const: m_axi_mm2s_aclk + - const: m_axi_s2mm_aclk + - const: m_axi_sg_aclk + minItems: 1 + patternProperties: + "^dma-channel(-mm2s|-s2mm)?(@[0-9a-f]+)?$": + properties: + compatible: + enum: + - xlnx,axi-dma-mm2s-channel + - xlnx,axi-dma-s2mm-channel + +required: + - "#dma-cells" + - reg + - xlnx,addrwidth + - dma-ranges + - clocks + - clock-names + +unevaluatedProperties: false + +examples: + - | + #include + + dma-controller@40030000 { + compatible = "xlnx,axi-vdma-1.00.a"; + reg = <0x40030000 0x10000>; + #dma-cells = <1>; + #address-cells = <1>; + #size-cells = <1>; + dma-ranges = <0x0 0x0 0x40000000>; + clocks = <&clk 0>, <&clk 1>, <&clk 2>, <&clk 3>, <&clk 4>; + clock-names = "s_axi_lite_aclk", "m_axi_mm2s_aclk", + "m_axi_s2mm_aclk", "m_axis_mm2s_aclk", + "s_axis_s2mm_aclk"; + xlnx,num-fstores = <8>; + xlnx,flush-fsync; + xlnx,addrwidth = <32>; + + dma-channel-mm2s { + compatible = "xlnx,axi-vdma-mm2s-channel"; + interrupts = ; + xlnx,datawidth = <64>; + }; + + dma-channel-s2mm { + compatible = "xlnx,axi-vdma-s2mm-channel"; + interrupts = ; + xlnx,datawidth = <64>; + }; + }; + + - | + #include + + dma-controller@a4030000 { + compatible = "xlnx,axi-dma-1.00.a"; + reg = <0xa4030000 0x10000>; + #dma-cells = <1>; + #address-cells = <1>; + #size-cells = <1>; + dma-ranges = <0x0 0x0 0x40000000>; + clocks = <&clk 0>, <&clk 1>, <&clk 2>, <&clk 3>; + clock-names = "s_axi_lite_aclk", "m_axi_mm2s_aclk", + "m_axi_s2mm_aclk", "m_axi_sg_aclk"; + xlnx,addrwidth = <32>; + xlnx,sg-length-width = <14>; + + dma-channel-mm2s { + compatible = "xlnx,axi-dma-mm2s-channel"; + interrupts = ; + xlnx,datawidth = <64>; + xlnx,include-dre; + }; + + dma-channel-s2mm { + compatible = "xlnx,axi-dma-s2mm-channel"; + interrupts = ; + xlnx,datawidth = <64>; + xlnx,include-dre; + }; + }; From ffee2dc04e7e06534aaa4fd51ef89645b809b6b8 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Sat, 7 Mar 2026 11:25:10 +0800 Subject: [PATCH 56/65] dmaengine: loongson: New directory for Loongson DMA controllers drivers Gather the Loongson DMA controllers under drivers/dma/loongson/ Reviewed-by: Frank Li Signed-off-by: Binbin Zhou Reviewed-by: Keguang Zhang Reviewed-by: Huacai Chen Link: https://patch.msgid.link/0a0853a85630724741061f6fe08680610e49a06e.1772853681.git.zhoubinbin@loongson.cn Signed-off-by: Vinod Koul --- MAINTAINERS | 3 +- drivers/dma/Kconfig | 25 ++-------------- drivers/dma/Makefile | 3 +- drivers/dma/loongson/Kconfig | 30 +++++++++++++++++++ drivers/dma/loongson/Makefile | 3 ++ .../dma/{ => loongson}/loongson1-apb-dma.c | 4 +-- .../dma/{ => loongson}/loongson2-apb-dma.c | 4 +-- 7 files changed, 42 insertions(+), 30 deletions(-) create mode 100644 drivers/dma/loongson/Kconfig create mode 100644 drivers/dma/loongson/Makefile rename drivers/dma/{ => loongson}/loongson1-apb-dma.c (99%) rename drivers/dma/{ => loongson}/loongson2-apb-dma.c (99%) diff --git a/MAINTAINERS b/MAINTAINERS index b38452804a2d..f0cad0b6ad8f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14953,7 +14953,7 @@ M: Binbin Zhou L: dmaengine@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml -F: drivers/dma/loongson2-apb-dma.c +F: drivers/dma/loongson/loongson2-apb-dma.c LOONGSON LS2X I2C DRIVER M: Binbin Zhou @@ -17721,6 +17721,7 @@ F: arch/mips/boot/dts/loongson/loongson1* F: arch/mips/configs/loongson1_defconfig F: arch/mips/loongson32/ F: drivers/*/*loongson1* +F: drivers/dma/loongson/loongson1-apb-dma.c F: drivers/mtd/nand/raw/loongson-nand-controller.c F: drivers/net/ethernet/stmicro/stmmac/dwmac-loongson1.c F: sound/soc/loongson/loongson1_ac97.c diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index e98e3e8c5036..ae6a682c9f76 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -376,29 +376,6 @@ config K3_DMA Support the DMA engine for Hisilicon K3 platform devices. -config LOONGSON1_APB_DMA - tristate "Loongson1 APB DMA support" - depends on MACH_LOONGSON32 || COMPILE_TEST - select DMA_ENGINE - select DMA_VIRTUAL_CHANNELS - help - This selects support for the APB DMA controller in Loongson1 SoCs, - which is required by Loongson1 NAND and audio support. - -config LOONGSON2_APB_DMA - tristate "Loongson2 APB DMA support" - depends on LOONGARCH || COMPILE_TEST - select DMA_ENGINE - select DMA_VIRTUAL_CHANNELS - help - Support for the Loongson2 APB DMA controller driver. The - DMA controller is having single DMA channel which can be - configured for different peripherals like audio, nand, sdio - etc which is in APB bus. - - This DMA controller transfers data from memory to peripheral fifo. - It does not support memory to memory data transfer. - config LPC18XX_DMAMUX bool "NXP LPC18xx/43xx DMA MUX for PL080" depends on ARCH_LPC18XX || COMPILE_TEST @@ -783,6 +760,8 @@ source "drivers/dma/fsl-dpaa2-qdma/Kconfig" source "drivers/dma/lgm/Kconfig" +source "drivers/dma/loongson/Kconfig" + source "drivers/dma/stm32/Kconfig" # clients diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index df566c4958b6..14aa086629d5 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -49,8 +49,6 @@ obj-$(CONFIG_INTEL_IDMA64) += idma64.o obj-$(CONFIG_INTEL_IOATDMA) += ioat/ obj-y += idxd/ obj-$(CONFIG_K3_DMA) += k3dma.o -obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o -obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o obj-$(CONFIG_LPC18XX_DMAMUX) += lpc18xx-dmamux.o obj-$(CONFIG_LPC32XX_DMAMUX) += lpc32xx-dmamux.o obj-$(CONFIG_MILBEAUT_HDMAC) += milbeaut-hdmac.o @@ -88,6 +86,7 @@ obj-$(CONFIG_FSL_DPAA2_QDMA) += fsl-dpaa2-qdma/ obj-$(CONFIG_INTEL_LDMA) += lgm/ obj-y += amd/ +obj-y += loongson/ obj-y += mediatek/ obj-y += qcom/ obj-y += stm32/ diff --git a/drivers/dma/loongson/Kconfig b/drivers/dma/loongson/Kconfig new file mode 100644 index 000000000000..0a865a8fd3a6 --- /dev/null +++ b/drivers/dma/loongson/Kconfig @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Loongson DMA controllers drivers +# +if MACH_LOONGSON32 || MACH_LOONGSON64 || COMPILE_TEST + +config LOONGSON1_APB_DMA + tristate "Loongson1 APB DMA support" + depends on MACH_LOONGSON32 || COMPILE_TEST + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + help + This selects support for the APB DMA controller in Loongson1 SoCs, + which is required by Loongson1 NAND and audio support. + +config LOONGSON2_APB_DMA + tristate "Loongson2 APB DMA support" + depends on MACH_LOONGSON64 || COMPILE_TEST + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + help + Support for the Loongson2 APB DMA controller driver. The + DMA controller is having single DMA channel which can be + configured for different peripherals like audio, nand, sdio + etc which is in APB bus. + + This DMA controller transfers data from memory to peripheral fifo. + It does not support memory to memory data transfer. + +endif diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile new file mode 100644 index 000000000000..6cdd08065e92 --- /dev/null +++ b/drivers/dma/loongson/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o +obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o diff --git a/drivers/dma/loongson1-apb-dma.c b/drivers/dma/loongson/loongson1-apb-dma.c similarity index 99% rename from drivers/dma/loongson1-apb-dma.c rename to drivers/dma/loongson/loongson1-apb-dma.c index 2e347aba9af8..89786cbd20ab 100644 --- a/drivers/dma/loongson1-apb-dma.c +++ b/drivers/dma/loongson/loongson1-apb-dma.c @@ -16,8 +16,8 @@ #include #include -#include "dmaengine.h" -#include "virt-dma.h" +#include "../dmaengine.h" +#include "../virt-dma.h" /* Loongson-1 DMA Control Register */ #define LS1X_DMA_CTRL 0x0 diff --git a/drivers/dma/loongson2-apb-dma.c b/drivers/dma/loongson/loongson2-apb-dma.c similarity index 99% rename from drivers/dma/loongson2-apb-dma.c rename to drivers/dma/loongson/loongson2-apb-dma.c index b981475e6779..fc7d9f4a96ec 100644 --- a/drivers/dma/loongson2-apb-dma.c +++ b/drivers/dma/loongson/loongson2-apb-dma.c @@ -17,8 +17,8 @@ #include #include -#include "dmaengine.h" -#include "virt-dma.h" +#include "../dmaengine.h" +#include "../virt-dma.h" /* Global Configuration Register */ #define LDMA_ORDER_ERG 0x0 From 7d348227f4961bbf21255281438ee3aebe12830f Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Sat, 7 Mar 2026 11:25:11 +0800 Subject: [PATCH 57/65] dmaengine: loongson: loongson2-apb: Convert to dmaenginem_async_device_register() Use the dmaenginem_async_device_register() helper function to simplify the probe routine. Reviewed-by: Frank Li Signed-off-by: Binbin Zhou Reviewed-by: Huacai Chen Link: https://patch.msgid.link/c56e67ecde38e9a3bda5f88ea3fc20b97a5cba6c.1772853681.git.zhoubinbin@loongson.cn Signed-off-by: Vinod Koul --- drivers/dma/loongson/loongson2-apb-dma.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/dma/loongson/loongson2-apb-dma.c b/drivers/dma/loongson/loongson2-apb-dma.c index fc7d9f4a96ec..2d16842e1658 100644 --- a/drivers/dma/loongson/loongson2-apb-dma.c +++ b/drivers/dma/loongson/loongson2-apb-dma.c @@ -650,21 +650,19 @@ static int ls2x_dma_probe(struct platform_device *pdev) ddev->dst_addr_widths = LDMA_SLAVE_BUSWIDTHS; ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); - ret = dma_async_device_register(&priv->ddev); + ret = dmaenginem_async_device_register(&priv->ddev); if (ret < 0) goto disable_clk; ret = of_dma_controller_register(dev->of_node, of_dma_xlate_by_chan_id, priv); if (ret < 0) - goto unregister_dmac; + goto disable_clk; platform_set_drvdata(pdev, priv); dev_info(dev, "Loongson LS2X APB DMA driver registered successfully.\n"); return 0; -unregister_dmac: - dma_async_device_unregister(&priv->ddev); disable_clk: clk_disable_unprepare(priv->dma_clk); @@ -680,7 +678,6 @@ static void ls2x_dma_remove(struct platform_device *pdev) struct ls2x_dma_priv *priv = platform_get_drvdata(pdev); of_dma_controller_free(pdev->dev.of_node); - dma_async_device_unregister(&priv->ddev); clk_disable_unprepare(priv->dma_clk); } From bdf1621a6a67b6327e2a26a1d47bffcde3be3b26 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Sat, 7 Mar 2026 11:25:12 +0800 Subject: [PATCH 58/65] dmaengine: loongson: loongson2-apb: Convert to devm_clk_get_enabled() Use the devm_clk_get_enabled() helper function to simplify the probe routine. Reviewed-by: Frank Li Signed-off-by: Binbin Zhou Reviewed-by: Huacai Chen Link: https://patch.msgid.link/4f3aad22d14e730cc040ece8b0ced37853d52876.1772853681.git.zhoubinbin@loongson.cn Signed-off-by: Vinod Koul --- drivers/dma/loongson/loongson2-apb-dma.c | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/drivers/dma/loongson/loongson2-apb-dma.c b/drivers/dma/loongson/loongson2-apb-dma.c index 2d16842e1658..adddfafc2f1c 100644 --- a/drivers/dma/loongson/loongson2-apb-dma.c +++ b/drivers/dma/loongson/loongson2-apb-dma.c @@ -616,17 +616,13 @@ static int ls2x_dma_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(priv->regs), "devm_platform_ioremap_resource failed.\n"); - priv->dma_clk = devm_clk_get(&pdev->dev, NULL); + priv->dma_clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(priv->dma_clk)) - return dev_err_probe(dev, PTR_ERR(priv->dma_clk), "devm_clk_get failed.\n"); - - ret = clk_prepare_enable(priv->dma_clk); - if (ret) - return dev_err_probe(dev, ret, "clk_prepare_enable failed.\n"); + return dev_err_probe(dev, PTR_ERR(priv->dma_clk), "Couldn't start the clock.\n"); ret = ls2x_dma_chan_init(pdev, priv); if (ret) - goto disable_clk; + return ret; ddev = &priv->ddev; ddev->dev = dev; @@ -652,21 +648,16 @@ static int ls2x_dma_probe(struct platform_device *pdev) ret = dmaenginem_async_device_register(&priv->ddev); if (ret < 0) - goto disable_clk; + return dev_err_probe(dev, ret, "Failed to register DMA engine device.\n"); ret = of_dma_controller_register(dev->of_node, of_dma_xlate_by_chan_id, priv); if (ret < 0) - goto disable_clk; + return dev_err_probe(dev, ret, "Failed to register dma controller.\n"); platform_set_drvdata(pdev, priv); dev_info(dev, "Loongson LS2X APB DMA driver registered successfully.\n"); return 0; - -disable_clk: - clk_disable_unprepare(priv->dma_clk); - - return ret; } /* @@ -675,10 +666,7 @@ disable_clk: */ static void ls2x_dma_remove(struct platform_device *pdev) { - struct ls2x_dma_priv *priv = platform_get_drvdata(pdev); - of_dma_controller_free(pdev->dev.of_node); - clk_disable_unprepare(priv->dma_clk); } static const struct of_device_id ls2x_dma_of_match_table[] = { From 9de4303fc04977d15b257726a6519caca687c43a Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Sat, 7 Mar 2026 11:25:35 +0800 Subject: [PATCH 59/65] dmaengine: loongson: loongson2-apb: Simplify locking with guard() and scoped_guard() Use guard() and scoped_guard() infrastructure instead of explicitly acquiring and releasing spinlocks to simplify the code and ensure that all locks are released properly. Reviewed-by: Frank Li Signed-off-by: Binbin Zhou Reviewed-by: Huacai Chen Link: https://patch.msgid.link/fb59bb25e5c4fcb84d9aa7b351285fa8d02ea8cb.1772853681.git.zhoubinbin@loongson.cn Signed-off-by: Vinod Koul --- drivers/dma/loongson/loongson2-apb-dma.c | 62 +++++++++++------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/drivers/dma/loongson/loongson2-apb-dma.c b/drivers/dma/loongson/loongson2-apb-dma.c index adddfafc2f1c..aceb069e71fc 100644 --- a/drivers/dma/loongson/loongson2-apb-dma.c +++ b/drivers/dma/loongson/loongson2-apb-dma.c @@ -461,12 +461,11 @@ static int ls2x_dma_slave_config(struct dma_chan *chan, static void ls2x_dma_issue_pending(struct dma_chan *chan) { struct ls2x_dma_chan *lchan = to_ldma_chan(chan); - unsigned long flags; - spin_lock_irqsave(&lchan->vchan.lock, flags); + guard(spinlock_irqsave)(&lchan->vchan.lock); + if (vchan_issue_pending(&lchan->vchan) && !lchan->desc) ls2x_dma_start_transfer(lchan); - spin_unlock_irqrestore(&lchan->vchan.lock, flags); } /* @@ -478,19 +477,18 @@ static void ls2x_dma_issue_pending(struct dma_chan *chan) static int ls2x_dma_terminate_all(struct dma_chan *chan) { struct ls2x_dma_chan *lchan = to_ldma_chan(chan); - unsigned long flags; LIST_HEAD(head); - spin_lock_irqsave(&lchan->vchan.lock, flags); - /* Setting stop cmd */ - ls2x_dma_write_cmd(lchan, LDMA_STOP); - if (lchan->desc) { - vchan_terminate_vdesc(&lchan->desc->vdesc); - lchan->desc = NULL; - } + scoped_guard(spinlock_irqsave, &lchan->vchan.lock) { + /* Setting stop cmd */ + ls2x_dma_write_cmd(lchan, LDMA_STOP); + if (lchan->desc) { + vchan_terminate_vdesc(&lchan->desc->vdesc); + lchan->desc = NULL; + } - vchan_get_all_descriptors(&lchan->vchan, &head); - spin_unlock_irqrestore(&lchan->vchan.lock, flags); + vchan_get_all_descriptors(&lchan->vchan, &head); + } vchan_dma_desc_free_list(&lchan->vchan, &head); return 0; @@ -511,14 +509,13 @@ static void ls2x_dma_synchronize(struct dma_chan *chan) static int ls2x_dma_pause(struct dma_chan *chan) { struct ls2x_dma_chan *lchan = to_ldma_chan(chan); - unsigned long flags; - spin_lock_irqsave(&lchan->vchan.lock, flags); + guard(spinlock_irqsave)(&lchan->vchan.lock); + if (lchan->desc && lchan->desc->status == DMA_IN_PROGRESS) { ls2x_dma_write_cmd(lchan, LDMA_STOP); lchan->desc->status = DMA_PAUSED; } - spin_unlock_irqrestore(&lchan->vchan.lock, flags); return 0; } @@ -526,14 +523,13 @@ static int ls2x_dma_pause(struct dma_chan *chan) static int ls2x_dma_resume(struct dma_chan *chan) { struct ls2x_dma_chan *lchan = to_ldma_chan(chan); - unsigned long flags; - spin_lock_irqsave(&lchan->vchan.lock, flags); + guard(spinlock_irqsave)(&lchan->vchan.lock); + if (lchan->desc && lchan->desc->status == DMA_PAUSED) { lchan->desc->status = DMA_IN_PROGRESS; ls2x_dma_write_cmd(lchan, LDMA_START); } - spin_unlock_irqrestore(&lchan->vchan.lock, flags); return 0; } @@ -550,22 +546,22 @@ static irqreturn_t ls2x_dma_isr(int irq, void *dev_id) struct ls2x_dma_chan *lchan = dev_id; struct ls2x_dma_desc *desc; - spin_lock(&lchan->vchan.lock); - desc = lchan->desc; - if (desc) { - if (desc->cyclic) { - vchan_cyclic_callback(&desc->vdesc); - } else { - desc->status = DMA_COMPLETE; - vchan_cookie_complete(&desc->vdesc); - ls2x_dma_start_transfer(lchan); - } + scoped_guard(spinlock, &lchan->vchan.lock) { + desc = lchan->desc; + if (desc) { + if (desc->cyclic) { + vchan_cyclic_callback(&desc->vdesc); + } else { + desc->status = DMA_COMPLETE; + vchan_cookie_complete(&desc->vdesc); + ls2x_dma_start_transfer(lchan); + } - /* ls2x_dma_start_transfer() updates lchan->desc */ - if (!lchan->desc) - ls2x_dma_write_cmd(lchan, LDMA_STOP); + /* ls2x_dma_start_transfer() updates lchan->desc */ + if (!lchan->desc) + ls2x_dma_write_cmd(lchan, LDMA_STOP); + } } - spin_unlock(&lchan->vchan.lock); return IRQ_HANDLED; } From 7a65e81e8e2e58b0db9f2dedda410ee2b6042859 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Sat, 7 Mar 2026 11:25:36 +0800 Subject: [PATCH 60/65] dt-bindings: dmaengine: Add Loongson Multi-Channel DMA controller The Loongson-2K0300/Loongson-2K3000 have built-in multi-channel DMA controllers, which are similar except for some of the register offsets and number of channels. Obviously, this is quite different from the APB DMA controller used in the Loongson-2K0500/Loongson-2K1000, such as the latter being a single-channel DMA controller. To avoid cluttering a single dt-binding file, add a new yaml file. Reviewed-by: Rob Herring (Arm) Signed-off-by: Binbin Zhou Reviewed-by: Huacai Chen Link: https://patch.msgid.link/135802de72b84f643d0b0624f3f79f13777147a1.1772853681.git.zhoubinbin@loongson.cn Signed-off-by: Vinod Koul --- .../bindings/dma/loongson,ls2k0300-dma.yaml | 81 +++++++++++++++++++ MAINTAINERS | 3 +- 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml diff --git a/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml b/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml new file mode 100644 index 000000000000..c3151d806b55 --- /dev/null +++ b/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/dma/loongson,ls2k0300-dma.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Looongson-2 Multi-Channel DMA controller + +description: + The Loongson-2 Multi-Channel DMA controller is used for transferring data + between system memory and the peripherals on the APB bus. + +maintainers: + - Binbin Zhou + +allOf: + - $ref: dma-controller.yaml# + +properties: + compatible: + enum: + - loongson,ls2k0300-dma + - loongson,ls2k3000-dma + + reg: + maxItems: 1 + + interrupts: + description: + Should contain all of the per-channel DMA interrupts in ascending order + with respect to the DMA channel index. + minItems: 4 + maxItems: 8 + + clocks: + maxItems: 1 + + '#dma-cells': + const: 2 + description: | + DMA request from clients consists of 2 cells: + 1. Channel index + 2. Transfer request factor number, If no transfer factor, use 0. + The number is SoC-specific, and this should be specified with + relation to the device to use the DMA controller. + + dma-channels: + enum: [4, 8] + +required: + - compatible + - reg + - interrupts + - clocks + - '#dma-cells' + - dma-channels + +unevaluatedProperties: false + +examples: + - | + #include + #include + + dma-controller@1612c000 { + compatible = "loongson,ls2k0300-dma"; + reg = <0x1612c000 0xff>; + interrupt-parent = <&liointc0>; + interrupts = <23 IRQ_TYPE_LEVEL_HIGH>, + <24 IRQ_TYPE_LEVEL_HIGH>, + <25 IRQ_TYPE_LEVEL_HIGH>, + <26 IRQ_TYPE_LEVEL_HIGH>, + <27 IRQ_TYPE_LEVEL_HIGH>, + <28 IRQ_TYPE_LEVEL_HIGH>, + <29 IRQ_TYPE_LEVEL_HIGH>, + <30 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk LS2K0300_CLK_APB_GATE>; + #dma-cells = <2>; + dma-channels = <8>; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index f0cad0b6ad8f..3b60dce82e78 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14948,10 +14948,11 @@ S: Maintained F: Documentation/devicetree/bindings/gpio/loongson,ls-gpio.yaml F: drivers/gpio/gpio-loongson-64bit.c -LOONGSON-2 APB DMA DRIVER +LOONGSON-2 DMA DRIVER M: Binbin Zhou L: dmaengine@vger.kernel.org S: Maintained +F: Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml F: drivers/dma/loongson/loongson2-apb-dma.c From 1c0028e725f156ebabe68b0025f9c8e7a6170ffd Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Sat, 7 Mar 2026 11:25:37 +0800 Subject: [PATCH 61/65] dmaengine: loongson: New driver for the Loongson Multi-Channel DMA controller This DMA controller appears in Loongson-2K0300 and Loongson-2K3000. It is a chain multi-channel controller that enables data transfers from memory to memory, device to memory, and memory to device, as well as channel prioritization configurable through the channel configuration registers. In addition, there are slight differences between Loongson-2K0300 and Loongson-2K3000, such as channel register offsets and the number of channels. Reviewed-by: Frank Li Signed-off-by: Binbin Zhou Reviewed-by: Huacai Chen Link: https://patch.msgid.link/73bc32ba6249f1eef94fec9b349bc9efa98278ea.1772853681.git.zhoubinbin@loongson.cn Signed-off-by: Vinod Koul --- MAINTAINERS | 1 + drivers/dma/loongson/Kconfig | 11 + drivers/dma/loongson/Makefile | 1 + drivers/dma/loongson/loongson2-apb-cmc-dma.c | 730 +++++++++++++++++++ 4 files changed, 743 insertions(+) create mode 100644 drivers/dma/loongson/loongson2-apb-cmc-dma.c diff --git a/MAINTAINERS b/MAINTAINERS index 3b60dce82e78..d71c706c9fc2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14954,6 +14954,7 @@ L: dmaengine@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml +F: drivers/dma/loongson/loongson2-apb-cmc-dma.c F: drivers/dma/loongson/loongson2-apb-dma.c LOONGSON LS2X I2C DRIVER diff --git a/drivers/dma/loongson/Kconfig b/drivers/dma/loongson/Kconfig index 0a865a8fd3a6..c4e62dce5d4f 100644 --- a/drivers/dma/loongson/Kconfig +++ b/drivers/dma/loongson/Kconfig @@ -27,4 +27,15 @@ config LOONGSON2_APB_DMA This DMA controller transfers data from memory to peripheral fifo. It does not support memory to memory data transfer. +config LOONGSON2_APB_CMC_DMA + tristate "Loongson2 Chain Multi-Channel DMA support" + depends on MACH_LOONGSON64 || COMPILE_TEST + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + help + Support for the Loongson Chain Multi-Channel DMA controller driver. + It is discovered on the Loongson-2K chip (Loongson-2K0300/Loongson-2K3000), + which has 4/8 channels internally, enabling bidirectional data transfer + between devices and memory. + endif diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile index 6cdd08065e92..48c19781e729 100644 --- a/drivers/dma/loongson/Makefile +++ b/drivers/dma/loongson/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o +obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o diff --git a/drivers/dma/loongson/loongson2-apb-cmc-dma.c b/drivers/dma/loongson/loongson2-apb-cmc-dma.c new file mode 100644 index 000000000000..2f2ef51e41b6 --- /dev/null +++ b/drivers/dma/loongson/loongson2-apb-cmc-dma.c @@ -0,0 +1,730 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Looongson-2 Chain Multi-Channel DMA Controller driver + * + * Copyright (C) 2024-2026 Loongson Technology Corporation Limited + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../dmaengine.h" +#include "../virt-dma.h" + +#define LOONGSON2_CMCDMA_ISR 0x0 /* DMA Interrupt Status Register */ +#define LOONGSON2_CMCDMA_IFCR 0x4 /* DMA Interrupt Flag Clear Register */ +#define LOONGSON2_CMCDMA_CCR 0x8 /* DMA Channel Configuration Register */ +#define LOONGSON2_CMCDMA_CNDTR 0xc /* DMA Channel Transmit Count Register */ +#define LOONGSON2_CMCDMA_CPAR 0x10 /* DMA Channel Peripheral Address Register */ +#define LOONGSON2_CMCDMA_CMAR 0x14 /* DMA Channel Memory Address Register */ + +/* Bitfields of DMA interrupt status register */ +#define LOONGSON2_CMCDMA_TCI BIT(1) /* Transfer Complete Interrupt */ +#define LOONGSON2_CMCDMA_HTI BIT(2) /* Half Transfer Interrupt */ +#define LOONGSON2_CMCDMA_TEI BIT(3) /* Transfer Error Interrupt */ + +#define LOONGSON2_CMCDMA_MASKI \ + (LOONGSON2_CMCDMA_TCI | LOONGSON2_CMCDMA_HTI | LOONGSON2_CMCDMA_TEI) + +/* Bitfields of DMA channel x Configuration Register */ +#define LOONGSON2_CMCDMA_CCR_EN BIT(0) /* Stream Enable */ +#define LOONGSON2_CMCDMA_CCR_TCIE BIT(1) /* Transfer Complete Interrupt Enable */ +#define LOONGSON2_CMCDMA_CCR_HTIE BIT(2) /* Half Transfer Complete Interrupt Enable */ +#define LOONGSON2_CMCDMA_CCR_TEIE BIT(3) /* Transfer Error Interrupt Enable */ +#define LOONGSON2_CMCDMA_CCR_DIR BIT(4) /* Data Transfer Direction */ +#define LOONGSON2_CMCDMA_CCR_CIRC BIT(5) /* Circular mode */ +#define LOONGSON2_CMCDMA_CCR_PINC BIT(6) /* Peripheral increment mode */ +#define LOONGSON2_CMCDMA_CCR_MINC BIT(7) /* Memory increment mode */ +#define LOONGSON2_CMCDMA_CCR_PSIZE_MASK GENMASK(9, 8) +#define LOONGSON2_CMCDMA_CCR_MSIZE_MASK GENMASK(11, 10) +#define LOONGSON2_CMCDMA_CCR_PL_MASK GENMASK(13, 12) +#define LOONGSON2_CMCDMA_CCR_M2M BIT(14) + +#define LOONGSON2_CMCDMA_CCR_CFG_MASK \ + (LOONGSON2_CMCDMA_CCR_PINC | LOONGSON2_CMCDMA_CCR_MINC | LOONGSON2_CMCDMA_CCR_PL_MASK) + +#define LOONGSON2_CMCDMA_CCR_IRQ_MASK \ + (LOONGSON2_CMCDMA_CCR_TCIE | LOONGSON2_CMCDMA_CCR_HTIE | LOONGSON2_CMCDMA_CCR_TEIE) + +#define LOONGSON2_CMCDMA_STREAM_MASK \ + (LOONGSON2_CMCDMA_CCR_CFG_MASK | LOONGSON2_CMCDMA_CCR_IRQ_MASK) + +#define LOONGSON2_CMCDMA_BUSWIDTHS (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \ + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \ + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES)) + +#define LOONSON2_CMCDMA_MAX_DATA_ITEMS SZ_64K + +struct loongson2_cmc_dma_chan_reg { + u32 ccr; + u32 cndtr; + u32 cpar; + u32 cmar; +}; + +struct loongson2_cmc_dma_sg_req { + u32 len; + struct loongson2_cmc_dma_chan_reg chan_reg; +}; + +struct loongson2_cmc_dma_desc { + struct virt_dma_desc vdesc; + bool cyclic; + u32 num_sgs; + struct loongson2_cmc_dma_sg_req sg_req[] __counted_by(num_sgs); +}; + +struct loongson2_cmc_dma_chan { + struct virt_dma_chan vchan; + struct dma_slave_config dma_sconfig; + struct loongson2_cmc_dma_desc *desc; + u32 id; + u32 irq; + u32 next_sg; + struct loongson2_cmc_dma_chan_reg chan_reg; +}; + +struct loongson2_cmc_dma_dev { + struct dma_device ddev; + struct clk *dma_clk; + void __iomem *base; + u32 nr_channels; + u32 chan_reg_offset; + struct loongson2_cmc_dma_chan chan[] __counted_by(nr_channels); +}; + +struct loongson2_cmc_dma_config { + u32 max_channels; + u32 chan_reg_offset; +}; + +static const struct loongson2_cmc_dma_config ls2k0300_cmc_dma_config = { + .max_channels = 8, + .chan_reg_offset = 0x14, +}; + +static const struct loongson2_cmc_dma_config ls2k3000_cmc_dma_config = { + .max_channels = 4, + .chan_reg_offset = 0x18, +}; + +static struct loongson2_cmc_dma_dev *lmdma_get_dev(struct loongson2_cmc_dma_chan *lchan) +{ + return container_of(lchan->vchan.chan.device, struct loongson2_cmc_dma_dev, ddev); +} + +static struct loongson2_cmc_dma_chan *to_lmdma_chan(struct dma_chan *chan) +{ + return container_of(chan, struct loongson2_cmc_dma_chan, vchan.chan); +} + +static struct loongson2_cmc_dma_desc *to_lmdma_desc(struct virt_dma_desc *vdesc) +{ + return container_of(vdesc, struct loongson2_cmc_dma_desc, vdesc); +} + +static struct device *chan2dev(struct loongson2_cmc_dma_chan *lchan) +{ + return &lchan->vchan.chan.dev->device; +} + +static u32 loongson2_cmc_dma_read(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id) +{ + return readl(lddev->base + (reg + lddev->chan_reg_offset * id)); +} + +static void loongson2_cmc_dma_write(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id, u32 val) +{ + writel(val, lddev->base + (reg + lddev->chan_reg_offset * id)); +} + +static int loongson2_cmc_dma_get_width(enum dma_slave_buswidth width) +{ + switch (width) { + case DMA_SLAVE_BUSWIDTH_1_BYTE: + case DMA_SLAVE_BUSWIDTH_2_BYTES: + case DMA_SLAVE_BUSWIDTH_4_BYTES: + return ffs(width) - 1; + default: + return -EINVAL; + } +} + +static int loongson2_cmc_dma_slave_config(struct dma_chan *chan, struct dma_slave_config *config) +{ + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan); + + memcpy(&lchan->dma_sconfig, config, sizeof(*config)); + + return 0; +} + +static void loongson2_cmc_dma_irq_clear(struct loongson2_cmc_dma_chan *lchan, u32 flags) +{ + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan); + u32 ifcr; + + ifcr = flags << (4 * lchan->id); + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_IFCR, 0, ifcr); +} + +static void loongson2_cmc_dma_stop(struct loongson2_cmc_dma_chan *lchan) +{ + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan); + u32 ccr; + + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id); + ccr &= ~(LOONGSON2_CMCDMA_CCR_IRQ_MASK | LOONGSON2_CMCDMA_CCR_EN); + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, ccr); + + loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_MASKI); +} + +static int loongson2_cmc_dma_terminate_all(struct dma_chan *chan) +{ + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan); + + LIST_HEAD(head); + + scoped_guard(spinlock_irqsave, &lchan->vchan.lock) { + if (lchan->desc) { + vchan_terminate_vdesc(&lchan->desc->vdesc); + loongson2_cmc_dma_stop(lchan); + lchan->desc = NULL; + } + vchan_get_all_descriptors(&lchan->vchan, &head); + } + + vchan_dma_desc_free_list(&lchan->vchan, &head); + + return 0; +} + +static void loongson2_cmc_dma_synchronize(struct dma_chan *chan) +{ + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan); + + vchan_synchronize(&lchan->vchan); +} + +static void loongson2_cmc_dma_start_transfer(struct loongson2_cmc_dma_chan *lchan) +{ + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan); + struct loongson2_cmc_dma_sg_req *sg_req; + struct loongson2_cmc_dma_chan_reg *reg; + struct virt_dma_desc *vdesc; + + loongson2_cmc_dma_stop(lchan); + + if (!lchan->desc) { + vdesc = vchan_next_desc(&lchan->vchan); + if (!vdesc) + return; + + list_del(&vdesc->node); + lchan->desc = to_lmdma_desc(vdesc); + lchan->next_sg = 0; + } + + if (lchan->next_sg == lchan->desc->num_sgs) + lchan->next_sg = 0; + + sg_req = &lchan->desc->sg_req[lchan->next_sg]; + reg = &sg_req->chan_reg; + + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, reg->ccr); + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CNDTR, lchan->id, reg->cndtr); + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CPAR, lchan->id, reg->cpar); + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CMAR, lchan->id, reg->cmar); + + lchan->next_sg++; + + /* Start DMA */ + reg->ccr |= LOONGSON2_CMCDMA_CCR_EN; + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, reg->ccr); +} + +static void loongson2_cmc_dma_configure_next_sg(struct loongson2_cmc_dma_chan *lchan) +{ + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan); + struct loongson2_cmc_dma_sg_req *sg_req; + u32 ccr, id = lchan->id; + + if (lchan->next_sg == lchan->desc->num_sgs) + lchan->next_sg = 0; + + /* Stop to update mem addr */ + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, id); + ccr &= ~LOONGSON2_CMCDMA_CCR_EN; + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, id, ccr); + + sg_req = &lchan->desc->sg_req[lchan->next_sg]; + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CMAR, id, sg_req->chan_reg.cmar); + + /* Start transition */ + ccr |= LOONGSON2_CMCDMA_CCR_EN; + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, id, ccr); +} + +static void loongson2_cmc_dma_handle_chan_done(struct loongson2_cmc_dma_chan *lchan) +{ + if (!lchan->desc) + return; + + if (lchan->desc->cyclic) { + vchan_cyclic_callback(&lchan->desc->vdesc); + /* LOONGSON2_CMCDMA_CCR_CIRC mode don't need update register */ + if (lchan->desc->num_sgs == 1) + return; + loongson2_cmc_dma_configure_next_sg(lchan); + lchan->next_sg++; + } else { + if (lchan->next_sg == lchan->desc->num_sgs) { + vchan_cookie_complete(&lchan->desc->vdesc); + lchan->desc = NULL; + } + loongson2_cmc_dma_start_transfer(lchan); + } +} + +static irqreturn_t loongson2_cmc_dma_chan_irq(int irq, void *devid) +{ + struct loongson2_cmc_dma_chan *lchan = devid; + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan); + struct device *dev = chan2dev(lchan); + u32 ists, status, ccr; + + scoped_guard(spinlock, &lchan->vchan.lock) { + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id); + ists = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_ISR, 0); + status = (ists >> (4 * lchan->id)) & LOONGSON2_CMCDMA_MASKI; + + loongson2_cmc_dma_irq_clear(lchan, status); + + if (status & LOONGSON2_CMCDMA_TCI) { + loongson2_cmc_dma_handle_chan_done(lchan); + status &= ~LOONGSON2_CMCDMA_TCI; + } + + if (status & LOONGSON2_CMCDMA_HTI) + status &= ~LOONGSON2_CMCDMA_HTI; + + if (status & LOONGSON2_CMCDMA_TEI) { + dev_err(dev, "DMA Transform Error.\n"); + if (!(ccr & LOONGSON2_CMCDMA_CCR_EN)) + dev_err(dev, "Channel disabled by HW.\n"); + } + } + + return IRQ_HANDLED; +} + +static void loongson2_cmc_dma_issue_pending(struct dma_chan *chan) +{ + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan); + + guard(spinlock_irqsave)(&lchan->vchan.lock); + + if (vchan_issue_pending(&lchan->vchan) && !lchan->desc) { + dev_dbg(chan2dev(lchan), "vchan %pK: issued\n", &lchan->vchan); + loongson2_cmc_dma_start_transfer(lchan); + } +} + +static int loongson2_cmc_dma_set_xfer_param(struct loongson2_cmc_dma_chan *lchan, + enum dma_transfer_direction direction, + enum dma_slave_buswidth *buswidth, u32 buf_len) +{ + struct dma_slave_config sconfig = lchan->dma_sconfig; + struct device *dev = chan2dev(lchan); + int dev_width; + u32 ccr; + + switch (direction) { + case DMA_MEM_TO_DEV: + dev_width = loongson2_cmc_dma_get_width(sconfig.dst_addr_width); + if (dev_width < 0) { + dev_err(dev, "DMA_MEM_TO_DEV bus width not supported\n"); + return dev_width; + } + lchan->chan_reg.cpar = sconfig.dst_addr; + ccr = LOONGSON2_CMCDMA_CCR_DIR; + *buswidth = sconfig.dst_addr_width; + break; + case DMA_DEV_TO_MEM: + dev_width = loongson2_cmc_dma_get_width(sconfig.src_addr_width); + if (dev_width < 0) { + dev_err(dev, "DMA_DEV_TO_MEM bus width not supported\n"); + return dev_width; + } + lchan->chan_reg.cpar = sconfig.src_addr; + ccr = LOONGSON2_CMCDMA_CCR_MINC; + *buswidth = sconfig.src_addr_width; + break; + default: + return -EINVAL; + } + + ccr |= FIELD_PREP(LOONGSON2_CMCDMA_CCR_PSIZE_MASK, dev_width) | + FIELD_PREP(LOONGSON2_CMCDMA_CCR_MSIZE_MASK, dev_width); + + /* Set DMA control register */ + lchan->chan_reg.ccr &= ~(LOONGSON2_CMCDMA_CCR_PSIZE_MASK | LOONGSON2_CMCDMA_CCR_MSIZE_MASK); + lchan->chan_reg.ccr |= ccr; + + return 0; +} + +static struct dma_async_tx_descriptor * +loongson2_cmc_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, u32 sg_len, + enum dma_transfer_direction direction, + unsigned long flags, void *context) +{ + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan); + struct loongson2_cmc_dma_desc *desc; + enum dma_slave_buswidth buswidth; + struct scatterlist *sg; + u32 num_items, i; + int ret; + + desc = kzalloc_flex(*desc, sg_req, sg_len, GFP_NOWAIT); + if (!desc) + return ERR_PTR(-ENOMEM); + + for_each_sg(sgl, sg, sg_len, i) { + ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, sg_dma_len(sg)); + if (ret) + return ERR_PTR(ret); + + num_items = DIV_ROUND_UP(sg_dma_len(sg), buswidth); + if (num_items >= LOONSON2_CMCDMA_MAX_DATA_ITEMS) { + dev_err(chan2dev(lchan), "Number of items not supported\n"); + kfree(desc); + return ERR_PTR(-EINVAL); + } + + desc->sg_req[i].len = sg_dma_len(sg); + desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr; + desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar; + desc->sg_req[i].chan_reg.cmar = sg_dma_address(sg); + desc->sg_req[i].chan_reg.cndtr = num_items; + } + + desc->num_sgs = sg_len; + desc->cyclic = false; + + return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags); +} + +static struct dma_async_tx_descriptor * +loongson2_cmc_dma_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len, + size_t period_len, enum dma_transfer_direction direction, + unsigned long flags) +{ + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan); + struct loongson2_cmc_dma_desc *desc; + enum dma_slave_buswidth buswidth; + u32 num_periods, num_items, i; + int ret; + + if (unlikely(buf_len % period_len)) + return ERR_PTR(-EINVAL); + + ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, period_len); + if (ret) + return ERR_PTR(ret); + + num_items = DIV_ROUND_UP(period_len, buswidth); + if (num_items >= LOONSON2_CMCDMA_MAX_DATA_ITEMS) { + dev_err(chan2dev(lchan), "Number of items not supported\n"); + return ERR_PTR(-EINVAL); + } + + /* Enable Circular mode */ + if (buf_len == period_len) + lchan->chan_reg.ccr |= LOONGSON2_CMCDMA_CCR_CIRC; + + num_periods = DIV_ROUND_UP(buf_len, period_len); + desc = kzalloc_flex(*desc, sg_req, num_periods, GFP_NOWAIT); + if (!desc) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < num_periods; i++) { + desc->sg_req[i].len = period_len; + desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr; + desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar; + desc->sg_req[i].chan_reg.cmar = buf_addr; + desc->sg_req[i].chan_reg.cndtr = num_items; + buf_addr += period_len; + } + + desc->num_sgs = num_periods; + desc->cyclic = true; + + return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags); +} + +static size_t loongson2_cmc_dma_desc_residue(struct loongson2_cmc_dma_chan *lchan, + struct loongson2_cmc_dma_desc *desc, u32 next_sg) +{ + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan); + u32 residue, width, ndtr, ccr, i; + + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id); + width = FIELD_GET(LOONGSON2_CMCDMA_CCR_PSIZE_MASK, ccr); + + ndtr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CNDTR, lchan->id); + residue = ndtr << width; + + if (lchan->desc->cyclic && next_sg == 0) + return residue; + + for (i = next_sg; i < desc->num_sgs; i++) + residue += desc->sg_req[i].len; + + return residue; +} + +static enum dma_status loongson2_cmc_dma_tx_status(struct dma_chan *chan, dma_cookie_t cookie, + struct dma_tx_state *state) +{ + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan); + struct virt_dma_desc *vdesc; + enum dma_status status; + + status = dma_cookie_status(chan, cookie, state); + if (status == DMA_COMPLETE || !state) + return status; + + scoped_guard(spinlock_irqsave, &lchan->vchan.lock) { + vdesc = vchan_find_desc(&lchan->vchan, cookie); + if (lchan->desc && cookie == lchan->desc->vdesc.tx.cookie) + state->residue = loongson2_cmc_dma_desc_residue(lchan, lchan->desc, + lchan->next_sg); + else if (vdesc) + state->residue = loongson2_cmc_dma_desc_residue(lchan, + to_lmdma_desc(vdesc), 0); + } + + return status; +} + +static void loongson2_cmc_dma_free_chan_resources(struct dma_chan *chan) +{ + vchan_free_chan_resources(to_virt_chan(chan)); +} + +static void loongson2_cmc_dma_desc_free(struct virt_dma_desc *vdesc) +{ + kfree(to_lmdma_desc(vdesc)); +} + +static bool loongson2_cmc_dma_acpi_filter(struct dma_chan *chan, void *param) +{ + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan); + struct acpi_dma_spec *dma_spec = param; + + memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg)); + lchan->chan_reg.ccr = dma_spec->chan_id & LOONGSON2_CMCDMA_STREAM_MASK; + + return true; +} + +static int loongson2_cmc_dma_acpi_controller_register(struct loongson2_cmc_dma_dev *lddev) +{ + struct device *dev = lddev->ddev.dev; + struct acpi_dma_filter_info *info; + + if (!is_acpi_node(dev_fwnode(dev))) + return 0; + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + dma_cap_zero(info->dma_cap); + info->dma_cap = lddev->ddev.cap_mask; + info->filter_fn = loongson2_cmc_dma_acpi_filter; + + return devm_acpi_dma_controller_register(dev, acpi_dma_simple_xlate, info); +} + +static struct dma_chan *loongson2_cmc_dma_of_xlate(struct of_phandle_args *dma_spec, + struct of_dma *ofdma) +{ + struct loongson2_cmc_dma_dev *lddev = ofdma->of_dma_data; + struct device *dev = lddev->ddev.dev; + struct loongson2_cmc_dma_chan *lchan; + struct dma_chan *chan; + + if (dma_spec->args_count < 2) + return ERR_PTR(-EINVAL); + + if (dma_spec->args[0] >= lddev->nr_channels) { + dev_err(dev, "Invalid channel id.\n"); + return ERR_PTR(-EINVAL); + } + + lchan = &lddev->chan[dma_spec->args[0]]; + chan = dma_get_slave_channel(&lchan->vchan.chan); + if (!chan) { + dev_err(dev, "No more channels available.\n"); + return ERR_PTR(-EINVAL); + } + + memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg)); + lchan->chan_reg.ccr = dma_spec->args[1] & LOONGSON2_CMCDMA_STREAM_MASK; + + return chan; +} + +static int loongson2_cmc_dma_of_controller_register(struct loongson2_cmc_dma_dev *lddev) +{ + struct device *dev = lddev->ddev.dev; + + if (!is_of_node(dev_fwnode(dev))) + return 0; + + return of_dma_controller_register(dev->of_node, loongson2_cmc_dma_of_xlate, lddev); +} + +static int loongson2_cmc_dma_probe(struct platform_device *pdev) +{ + const struct loongson2_cmc_dma_config *config; + struct loongson2_cmc_dma_chan *lchan; + struct loongson2_cmc_dma_dev *lddev; + struct device *dev = &pdev->dev; + struct dma_device *ddev; + u32 nr_chans, i; + int ret; + + config = (const struct loongson2_cmc_dma_config *)device_get_match_data(dev); + if (!config) + return -EINVAL; + + ret = device_property_read_u32(dev, "dma-channels", &nr_chans); + if (ret || nr_chans > config->max_channels) { + dev_err(dev, "missing or invalid dma-channels property\n"); + nr_chans = config->max_channels; + } + + lddev = devm_kzalloc(dev, struct_size(lddev, chan, nr_chans), GFP_KERNEL); + if (!lddev) + return -ENOMEM; + + lddev->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(lddev->base)) + return PTR_ERR(lddev->base); + + platform_set_drvdata(pdev, lddev); + lddev->nr_channels = nr_chans; + lddev->chan_reg_offset = config->chan_reg_offset; + + lddev->dma_clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(lddev->dma_clk)) + return dev_err_probe(dev, PTR_ERR(lddev->dma_clk), "Failed to get dma clock\n"); + + ddev = &lddev->ddev; + ddev->dev = dev; + + dma_cap_zero(ddev->cap_mask); + dma_cap_set(DMA_SLAVE, ddev->cap_mask); + dma_cap_set(DMA_PRIVATE, ddev->cap_mask); + dma_cap_set(DMA_CYCLIC, ddev->cap_mask); + + ddev->device_free_chan_resources = loongson2_cmc_dma_free_chan_resources; + ddev->device_config = loongson2_cmc_dma_slave_config; + ddev->device_prep_slave_sg = loongson2_cmc_dma_prep_slave_sg; + ddev->device_prep_dma_cyclic = loongson2_cmc_dma_prep_dma_cyclic; + ddev->device_issue_pending = loongson2_cmc_dma_issue_pending; + ddev->device_synchronize = loongson2_cmc_dma_synchronize; + ddev->device_tx_status = loongson2_cmc_dma_tx_status; + ddev->device_terminate_all = loongson2_cmc_dma_terminate_all; + + ddev->max_sg_burst = LOONSON2_CMCDMA_MAX_DATA_ITEMS; + ddev->src_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS; + ddev->dst_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS; + ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); + INIT_LIST_HEAD(&ddev->channels); + + for (i = 0; i < nr_chans; i++) { + lchan = &lddev->chan[i]; + + lchan->id = i; + lchan->vchan.desc_free = loongson2_cmc_dma_desc_free; + vchan_init(&lchan->vchan, ddev); + } + + ret = dmaenginem_async_device_register(ddev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register DMA engine device.\n"); + + for (i = 0; i < nr_chans; i++) { + lchan = &lddev->chan[i]; + + lchan->irq = platform_get_irq(pdev, i); + if (lchan->irq < 0) + return lchan->irq; + + ret = devm_request_irq(dev, lchan->irq, loongson2_cmc_dma_chan_irq, IRQF_SHARED, + dev_name(chan2dev(lchan)), lchan); + if (ret) + return ret; + } + + ret = loongson2_cmc_dma_acpi_controller_register(lddev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register dma controller with ACPI.\n"); + + ret = loongson2_cmc_dma_of_controller_register(lddev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register dma controller with FDT.\n"); + + dev_info(dev, "Loongson-2 Multi-Channel DMA Controller registered successfully.\n"); + + return 0; +} + +static void loongson2_cmc_dma_remove(struct platform_device *pdev) +{ + of_dma_controller_free(pdev->dev.of_node); +} + +static const struct of_device_id loongson2_cmc_dma_of_match[] = { + { .compatible = "loongson,ls2k0300-dma", .data = &ls2k0300_cmc_dma_config }, + { .compatible = "loongson,ls2k3000-dma", .data = &ls2k3000_cmc_dma_config }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, loongson2_cmc_dma_of_match); + +static const struct acpi_device_id loongson2_cmc_dma_acpi_match[] = { + { "LOON0014", .driver_data = (kernel_ulong_t)&ls2k3000_cmc_dma_config }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(acpi, loongson2_cmc_dma_acpi_match); + +static struct platform_driver loongson2_cmc_dma_driver = { + .driver = { + .name = "loongson2-apb-cmc-dma", + .of_match_table = loongson2_cmc_dma_of_match, + .acpi_match_table = loongson2_cmc_dma_acpi_match, + }, + .probe = loongson2_cmc_dma_probe, + .remove = loongson2_cmc_dma_remove, +}; +module_platform_driver(loongson2_cmc_dma_driver); + +MODULE_DESCRIPTION("Looongson-2 Chain Multi-Channel DMA Controller driver"); +MODULE_AUTHOR("Loongson Technology Corporation Limited"); +MODULE_LICENSE("GPL"); From 4a2759a3ae10bb2e6465cfb01c16d0620a1bc7ab Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 17 Mar 2026 20:46:31 +0000 Subject: [PATCH 62/65] dmaengine: loongson: Fix spelling mistake "Looongson" -> "Looogson" There are a couple of spelling mistakes, one in a comment block and one in a module description. Fix them. Signed-off-by: Colin Ian King Link: https://patch.msgid.link/20260317204631.120332-1-colin.i.king@gmail.com Signed-off-by: Vinod Koul --- drivers/dma/loongson/loongson2-apb-cmc-dma.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/dma/loongson/loongson2-apb-cmc-dma.c b/drivers/dma/loongson/loongson2-apb-cmc-dma.c index 2f2ef51e41b6..1c9a542edc85 100644 --- a/drivers/dma/loongson/loongson2-apb-cmc-dma.c +++ b/drivers/dma/loongson/loongson2-apb-cmc-dma.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Looongson-2 Chain Multi-Channel DMA Controller driver + * Loongson-2 Chain Multi-Channel DMA Controller driver * * Copyright (C) 2024-2026 Loongson Technology Corporation Limited */ @@ -725,6 +725,6 @@ static struct platform_driver loongson2_cmc_dma_driver = { }; module_platform_driver(loongson2_cmc_dma_driver); -MODULE_DESCRIPTION("Looongson-2 Chain Multi-Channel DMA Controller driver"); +MODULE_DESCRIPTION("Loongson-2 Chain Multi-Channel DMA Controller driver"); MODULE_AUTHOR("Loongson Technology Corporation Limited"); MODULE_LICENSE("GPL"); From 132e47b783a8057a8eb14484f153b417de00c1cb Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 17 Mar 2026 20:49:38 +0000 Subject: [PATCH 63/65] dt-bindings: dmaengine: Fix spelling mistake "Looongson" -> "Looogson" There is a spelling mistake in the title field. Fix it. Signed-off-by: Colin Ian King Reviewed-by: Binbin Zhou Link: https://patch.msgid.link/20260317204938.120729-1-colin.i.king@gmail.com Signed-off-by: Vinod Koul --- .../devicetree/bindings/dma/loongson,ls2k0300-dma.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml b/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml index c3151d806b55..8095214ccaf7 100644 --- a/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml +++ b/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/dma/loongson,ls2k0300-dma.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Looongson-2 Multi-Channel DMA controller +title: Loongson-2 Multi-Channel DMA controller description: The Loongson-2 Multi-Channel DMA controller is used for transferring data From 14eb9a1d338fdc301a2297af86818ecf716b1539 Mon Sep 17 00:00:00 2001 From: Devendra K Verma Date: Wed, 18 Mar 2026 12:34:02 +0530 Subject: [PATCH 64/65] dmaengine: dw-edma: Add AMD MDB Endpoint Support AMD MDB PCIe endpoint support. For AMD specific support added the following - AMD supported PCIe Device IDs and Vendor ID (Xilinx). - AMD MDB specific driver data - AMD MDB specific VSEC capability to retrieve the device DDR base address. Signed-off-by: Devendra K Verma Reviewed-by: Frank Li Link: https://patch.msgid.link/20260318070403.1634706-2-devendra.verma@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-pcie.c | 190 ++++++++++++++++++++++++++--- 1 file changed, 176 insertions(+), 14 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-pcie.c b/drivers/dma/dw-edma/dw-edma-pcie.c index 83230acaa597..0cb5850ca411 100644 --- a/drivers/dma/dw-edma/dw-edma-pcie.c +++ b/drivers/dma/dw-edma/dw-edma-pcie.c @@ -14,14 +14,35 @@ #include #include #include +#include #include "dw-edma-core.h" -#define DW_PCIE_VSEC_DMA_ID 0x6 -#define DW_PCIE_VSEC_DMA_BAR GENMASK(10, 8) -#define DW_PCIE_VSEC_DMA_MAP GENMASK(2, 0) -#define DW_PCIE_VSEC_DMA_WR_CH GENMASK(9, 0) -#define DW_PCIE_VSEC_DMA_RD_CH GENMASK(25, 16) +/* Synopsys */ +#define DW_PCIE_SYNOPSYS_VSEC_DMA_ID 0x6 +#define DW_PCIE_SYNOPSYS_VSEC_DMA_BAR GENMASK(10, 8) +#define DW_PCIE_SYNOPSYS_VSEC_DMA_MAP GENMASK(2, 0) +#define DW_PCIE_SYNOPSYS_VSEC_DMA_WR_CH GENMASK(9, 0) +#define DW_PCIE_SYNOPSYS_VSEC_DMA_RD_CH GENMASK(25, 16) + +/* AMD MDB (Xilinx) specific defines */ +#define PCI_DEVICE_ID_XILINX_B054 0xb054 + +#define DW_PCIE_XILINX_MDB_VSEC_DMA_ID 0x6 +#define DW_PCIE_XILINX_MDB_VSEC_ID 0x20 +#define DW_PCIE_XILINX_MDB_VSEC_DMA_BAR GENMASK(10, 8) +#define DW_PCIE_XILINX_MDB_VSEC_DMA_MAP GENMASK(2, 0) +#define DW_PCIE_XILINX_MDB_VSEC_DMA_WR_CH GENMASK(9, 0) +#define DW_PCIE_XILINX_MDB_VSEC_DMA_RD_CH GENMASK(25, 16) + +#define DW_PCIE_XILINX_MDB_DEVMEM_OFF_REG_HIGH 0xc +#define DW_PCIE_XILINX_MDB_DEVMEM_OFF_REG_LOW 0x8 +#define DW_PCIE_XILINX_MDB_INVALID_ADDR (~0ULL) + +#define DW_PCIE_XILINX_MDB_LL_OFF_GAP 0x200000 +#define DW_PCIE_XILINX_MDB_LL_SIZE 0x800 +#define DW_PCIE_XILINX_MDB_DT_OFF_GAP 0x100000 +#define DW_PCIE_XILINX_MDB_DT_SIZE 0x800 #define DW_BLOCK(a, b, c) \ { \ @@ -50,6 +71,7 @@ struct dw_edma_pcie_data { u8 irqs; u16 wr_ch_cnt; u16 rd_ch_cnt; + u64 devmem_phys_off; }; static const struct dw_edma_pcie_data snps_edda_data = { @@ -90,6 +112,64 @@ static const struct dw_edma_pcie_data snps_edda_data = { .rd_ch_cnt = 2, }; +static const struct dw_edma_pcie_data xilinx_mdb_data = { + /* MDB registers location */ + .rg.bar = BAR_0, + .rg.off = SZ_4K, /* 4 Kbytes */ + .rg.sz = SZ_8K, /* 8 Kbytes */ + + /* Other */ + .mf = EDMA_MF_HDMA_NATIVE, + .irqs = 1, + .wr_ch_cnt = 8, + .rd_ch_cnt = 8, +}; + +static void dw_edma_set_chan_region_offset(struct dw_edma_pcie_data *pdata, + enum pci_barno bar, off_t start_off, + off_t ll_off_gap, size_t ll_size, + off_t dt_off_gap, size_t dt_size) +{ + u16 wr_ch = pdata->wr_ch_cnt; + u16 rd_ch = pdata->rd_ch_cnt; + off_t off; + u16 i; + + off = start_off; + + /* Write channel LL region */ + for (i = 0; i < wr_ch; i++) { + pdata->ll_wr[i].bar = bar; + pdata->ll_wr[i].off = off; + pdata->ll_wr[i].sz = ll_size; + off += ll_off_gap; + } + + /* Read channel LL region */ + for (i = 0; i < rd_ch; i++) { + pdata->ll_rd[i].bar = bar; + pdata->ll_rd[i].off = off; + pdata->ll_rd[i].sz = ll_size; + off += ll_off_gap; + } + + /* Write channel data region */ + for (i = 0; i < wr_ch; i++) { + pdata->dt_wr[i].bar = bar; + pdata->dt_wr[i].off = off; + pdata->dt_wr[i].sz = dt_size; + off += dt_off_gap; + } + + /* Read channel data region */ + for (i = 0; i < rd_ch; i++) { + pdata->dt_rd[i].bar = bar; + pdata->dt_rd[i].off = off; + pdata->dt_rd[i].sz = dt_size; + off += dt_off_gap; + } +} + static int dw_edma_pcie_irq_vector(struct device *dev, unsigned int nr) { return pci_irq_vector(to_pci_dev(dev), nr); @@ -114,15 +194,15 @@ static const struct dw_edma_plat_ops dw_edma_pcie_plat_ops = { .pci_address = dw_edma_pcie_address, }; -static void dw_edma_pcie_get_vsec_dma_data(struct pci_dev *pdev, - struct dw_edma_pcie_data *pdata) +static void dw_edma_pcie_get_synopsys_dma_data(struct pci_dev *pdev, + struct dw_edma_pcie_data *pdata) { u32 val, map; u16 vsec; u64 off; vsec = pci_find_vsec_capability(pdev, PCI_VENDOR_ID_SYNOPSYS, - DW_PCIE_VSEC_DMA_ID); + DW_PCIE_SYNOPSYS_VSEC_DMA_ID); if (!vsec) return; @@ -131,9 +211,9 @@ static void dw_edma_pcie_get_vsec_dma_data(struct pci_dev *pdev, PCI_VNDR_HEADER_LEN(val) != 0x18) return; - pci_dbg(pdev, "Detected PCIe Vendor-Specific Extended Capability DMA\n"); + pci_dbg(pdev, "Detected Synopsys PCIe Vendor-Specific Extended Capability DMA\n"); pci_read_config_dword(pdev, vsec + 0x8, &val); - map = FIELD_GET(DW_PCIE_VSEC_DMA_MAP, val); + map = FIELD_GET(DW_PCIE_SYNOPSYS_VSEC_DMA_MAP, val); if (map != EDMA_MF_EDMA_LEGACY && map != EDMA_MF_EDMA_UNROLL && map != EDMA_MF_HDMA_COMPAT && @@ -141,13 +221,13 @@ static void dw_edma_pcie_get_vsec_dma_data(struct pci_dev *pdev, return; pdata->mf = map; - pdata->rg.bar = FIELD_GET(DW_PCIE_VSEC_DMA_BAR, val); + pdata->rg.bar = FIELD_GET(DW_PCIE_SYNOPSYS_VSEC_DMA_BAR, val); pci_read_config_dword(pdev, vsec + 0xc, &val); pdata->wr_ch_cnt = min_t(u16, pdata->wr_ch_cnt, - FIELD_GET(DW_PCIE_VSEC_DMA_WR_CH, val)); + FIELD_GET(DW_PCIE_SYNOPSYS_VSEC_DMA_WR_CH, val)); pdata->rd_ch_cnt = min_t(u16, pdata->rd_ch_cnt, - FIELD_GET(DW_PCIE_VSEC_DMA_RD_CH, val)); + FIELD_GET(DW_PCIE_SYNOPSYS_VSEC_DMA_RD_CH, val)); pci_read_config_dword(pdev, vsec + 0x14, &val); off = val; @@ -157,6 +237,64 @@ static void dw_edma_pcie_get_vsec_dma_data(struct pci_dev *pdev, pdata->rg.off = off; } +static void dw_edma_pcie_get_xilinx_dma_data(struct pci_dev *pdev, + struct dw_edma_pcie_data *pdata) +{ + u32 val, map; + u16 vsec; + u64 off; + + pdata->devmem_phys_off = DW_PCIE_XILINX_MDB_INVALID_ADDR; + + vsec = pci_find_vsec_capability(pdev, PCI_VENDOR_ID_XILINX, + DW_PCIE_XILINX_MDB_VSEC_DMA_ID); + if (!vsec) + return; + + pci_read_config_dword(pdev, vsec + PCI_VNDR_HEADER, &val); + if (PCI_VNDR_HEADER_REV(val) != 0x00 || + PCI_VNDR_HEADER_LEN(val) != 0x18) + return; + + pci_dbg(pdev, "Detected Xilinx PCIe Vendor-Specific Extended Capability DMA\n"); + pci_read_config_dword(pdev, vsec + 0x8, &val); + map = FIELD_GET(DW_PCIE_XILINX_MDB_VSEC_DMA_MAP, val); + if (map != EDMA_MF_HDMA_NATIVE) + return; + + pdata->mf = map; + pdata->rg.bar = FIELD_GET(DW_PCIE_XILINX_MDB_VSEC_DMA_BAR, val); + + pci_read_config_dword(pdev, vsec + 0xc, &val); + pdata->wr_ch_cnt = min(pdata->wr_ch_cnt, + FIELD_GET(DW_PCIE_XILINX_MDB_VSEC_DMA_WR_CH, val)); + pdata->rd_ch_cnt = min(pdata->rd_ch_cnt, + FIELD_GET(DW_PCIE_XILINX_MDB_VSEC_DMA_RD_CH, val)); + + pci_read_config_dword(pdev, vsec + 0x14, &val); + off = val; + pci_read_config_dword(pdev, vsec + 0x10, &val); + off <<= 32; + off |= val; + pdata->rg.off = off; + + vsec = pci_find_vsec_capability(pdev, PCI_VENDOR_ID_XILINX, + DW_PCIE_XILINX_MDB_VSEC_ID); + if (!vsec) + return; + + pci_read_config_dword(pdev, + vsec + DW_PCIE_XILINX_MDB_DEVMEM_OFF_REG_HIGH, + &val); + off = val; + pci_read_config_dword(pdev, + vsec + DW_PCIE_XILINX_MDB_DEVMEM_OFF_REG_LOW, + &val); + off <<= 32; + off |= val; + pdata->devmem_phys_off = off; +} + static int dw_edma_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *pid) { @@ -184,7 +322,29 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, * Tries to find if exists a PCIe Vendor-Specific Extended Capability * for the DMA, if one exists, then reconfigures it. */ - dw_edma_pcie_get_vsec_dma_data(pdev, vsec_data); + dw_edma_pcie_get_synopsys_dma_data(pdev, vsec_data); + + if (pdev->vendor == PCI_VENDOR_ID_XILINX) { + dw_edma_pcie_get_xilinx_dma_data(pdev, vsec_data); + + /* + * There is no valid address found for the LL memory + * space on the device side. + */ + if (vsec_data->devmem_phys_off == DW_PCIE_XILINX_MDB_INVALID_ADDR) + return -ENOMEM; + + /* + * Configure the channel LL and data blocks if number of + * channels enabled in VSEC capability are more than the + * channels configured in xilinx_mdb_data. + */ + dw_edma_set_chan_region_offset(vsec_data, BAR_2, 0, + DW_PCIE_XILINX_MDB_LL_OFF_GAP, + DW_PCIE_XILINX_MDB_LL_SIZE, + DW_PCIE_XILINX_MDB_DT_OFF_GAP, + DW_PCIE_XILINX_MDB_DT_SIZE); + } /* Mapping PCI BAR regions */ mask = BIT(vsec_data->rg.bar); @@ -367,6 +527,8 @@ static void dw_edma_pcie_remove(struct pci_dev *pdev) static const struct pci_device_id dw_edma_pcie_id_table[] = { { PCI_DEVICE_DATA(SYNOPSYS, EDDA, &snps_edda_data) }, + { PCI_VDEVICE(XILINX, PCI_DEVICE_ID_XILINX_B054), + (kernel_ulong_t)&xilinx_mdb_data }, { } }; MODULE_DEVICE_TABLE(pci, dw_edma_pcie_id_table); From b7560798466a07d9c3fb011698e92c335ab28baf Mon Sep 17 00:00:00 2001 From: Devendra K Verma Date: Wed, 18 Mar 2026 12:34:03 +0530 Subject: [PATCH 65/65] dmaengine: dw-edma: Add non-LL mode AMD MDB IP supports Linked List (LL) mode as well as non-LL mode. The current code does not have the mechanisms to enable the DMA transactions using the non-LL mode. The following two cases are added with this patch: - For the AMD (Xilinx) only, when a valid physical base address of the device side DDR is not configured, then the IP can still be used in non-LL mode. For all the channels DMA transactions will be using the non-LL mode only. This, the default non-LL mode, is not applicable for Synopsys IP with the current code addition. - If the default mode is LL-mode, for both AMD (Xilinx) and Synosys, and if user wants to use non-LL mode then user can do so via configuring the peripheral_config param of dma_slave_config. Signed-off-by: Devendra K Verma Reviewed-by: Frank Li Link: https://patch.msgid.link/20260318070403.1634706-3-devendra.verma@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 47 +++++++++++++++++++- drivers/dma/dw-edma/dw-edma-core.h | 1 + drivers/dma/dw-edma/dw-edma-pcie.c | 44 ++++++++++++------ drivers/dma/dw-edma/dw-hdma-v0-core.c | 64 ++++++++++++++++++++++++++- drivers/dma/dw-edma/dw-hdma-v0-regs.h | 1 + include/linux/dma/edma.h | 1 + 6 files changed, 143 insertions(+), 15 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index 94bcbd560143..c2feb3adc79f 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -223,6 +223,43 @@ static int dw_edma_device_config(struct dma_chan *dchan, struct dma_slave_config *config) { struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); + bool cfg_non_ll; + int non_ll = 0; + + 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"); + 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; + } memcpy(&chan->config, config, sizeof(*config)); chan->configured = true; @@ -358,6 +395,7 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer) struct dw_edma_desc *desc; u64 src_addr, dst_addr; size_t fsz = 0; + u32 bursts_max; u32 cnt = 0; int i; @@ -415,6 +453,13 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer) return NULL; } + /* + * For non-LL mode, only a single burst can be handled + * in a single chunk unlike LL mode where multiple bursts + * can be configured in a single chunk. + */ + bursts_max = chan->non_ll ? 1 : chan->ll_max; + desc = dw_edma_alloc_desc(chan); if (unlikely(!desc)) goto err_alloc; @@ -450,7 +495,7 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer) if (xfer->type == EDMA_XFER_SCATTER_GATHER && !sg) break; - if (chunk->bursts_alloc == chan->ll_max) { + if (chunk->bursts_alloc == bursts_max) { chunk = dw_edma_alloc_chunk(desc); if (unlikely(!chunk)) goto err_alloc; diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h index 59b24973fa7d..902574b1ba86 100644 --- a/drivers/dma/dw-edma/dw-edma-core.h +++ b/drivers/dma/dw-edma/dw-edma-core.h @@ -86,6 +86,7 @@ struct dw_edma_chan { u8 configured; struct dma_slave_config config; + bool non_ll; }; struct dw_edma_irq { diff --git a/drivers/dma/dw-edma/dw-edma-pcie.c b/drivers/dma/dw-edma/dw-edma-pcie.c index 0cb5850ca411..0b30ce138503 100644 --- a/drivers/dma/dw-edma/dw-edma-pcie.c +++ b/drivers/dma/dw-edma/dw-edma-pcie.c @@ -295,6 +295,15 @@ static void dw_edma_pcie_get_xilinx_dma_data(struct pci_dev *pdev, pdata->devmem_phys_off = off; } +static u64 dw_edma_get_phys_addr(struct pci_dev *pdev, + struct dw_edma_pcie_data *pdata, + enum pci_barno bar) +{ + if (pdev->vendor == PCI_VENDOR_ID_XILINX) + return pdata->devmem_phys_off; + return pci_bus_address(pdev, bar); +} + static int dw_edma_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *pid) { @@ -303,6 +312,7 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, struct dw_edma_chip *chip; int err, nr_irqs; int i, mask; + bool non_ll = false; struct dw_edma_pcie_data *vsec_data __free(kfree) = kmalloc_obj(*vsec_data); @@ -329,21 +339,24 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, /* * There is no valid address found for the LL memory - * space on the device side. + * space on the device side. In the absence of LL base + * address use the non-LL mode or simple mode supported by + * the HDMA IP. */ if (vsec_data->devmem_phys_off == DW_PCIE_XILINX_MDB_INVALID_ADDR) - return -ENOMEM; + non_ll = true; /* * Configure the channel LL and data blocks if number of * channels enabled in VSEC capability are more than the * channels configured in xilinx_mdb_data. */ - dw_edma_set_chan_region_offset(vsec_data, BAR_2, 0, - DW_PCIE_XILINX_MDB_LL_OFF_GAP, - DW_PCIE_XILINX_MDB_LL_SIZE, - DW_PCIE_XILINX_MDB_DT_OFF_GAP, - DW_PCIE_XILINX_MDB_DT_SIZE); + if (!non_ll) + dw_edma_set_chan_region_offset(vsec_data, BAR_2, 0, + DW_PCIE_XILINX_MDB_LL_OFF_GAP, + DW_PCIE_XILINX_MDB_LL_SIZE, + DW_PCIE_XILINX_MDB_DT_OFF_GAP, + DW_PCIE_XILINX_MDB_DT_SIZE); } /* Mapping PCI BAR regions */ @@ -391,6 +404,7 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, chip->mf = vsec_data->mf; chip->nr_irqs = nr_irqs; chip->ops = &dw_edma_pcie_plat_ops; + chip->cfg_non_ll = non_ll; chip->ll_wr_cnt = vsec_data->wr_ch_cnt; chip->ll_rd_cnt = vsec_data->rd_ch_cnt; @@ -399,7 +413,7 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, if (!chip->reg_base) return -ENOMEM; - for (i = 0; i < chip->ll_wr_cnt; i++) { + for (i = 0; i < chip->ll_wr_cnt && !non_ll; i++) { struct dw_edma_region *ll_region = &chip->ll_region_wr[i]; struct dw_edma_region *dt_region = &chip->dt_region_wr[i]; struct dw_edma_block *ll_block = &vsec_data->ll_wr[i]; @@ -410,7 +424,8 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, return -ENOMEM; ll_region->vaddr.io += ll_block->off; - ll_region->paddr = pci_bus_address(pdev, ll_block->bar); + ll_region->paddr = dw_edma_get_phys_addr(pdev, vsec_data, + ll_block->bar); ll_region->paddr += ll_block->off; ll_region->sz = ll_block->sz; @@ -419,12 +434,13 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, return -ENOMEM; dt_region->vaddr.io += dt_block->off; - dt_region->paddr = pci_bus_address(pdev, dt_block->bar); + dt_region->paddr = dw_edma_get_phys_addr(pdev, vsec_data, + dt_block->bar); dt_region->paddr += dt_block->off; dt_region->sz = dt_block->sz; } - for (i = 0; i < chip->ll_rd_cnt; i++) { + for (i = 0; i < chip->ll_rd_cnt && !non_ll; i++) { struct dw_edma_region *ll_region = &chip->ll_region_rd[i]; struct dw_edma_region *dt_region = &chip->dt_region_rd[i]; struct dw_edma_block *ll_block = &vsec_data->ll_rd[i]; @@ -435,7 +451,8 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, return -ENOMEM; ll_region->vaddr.io += ll_block->off; - ll_region->paddr = pci_bus_address(pdev, ll_block->bar); + ll_region->paddr = dw_edma_get_phys_addr(pdev, vsec_data, + ll_block->bar); ll_region->paddr += ll_block->off; ll_region->sz = ll_block->sz; @@ -444,7 +461,8 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, return -ENOMEM; dt_region->vaddr.io += dt_block->off; - dt_region->paddr = pci_bus_address(pdev, dt_block->bar); + dt_region->paddr = dw_edma_get_phys_addr(pdev, vsec_data, + dt_block->bar); dt_region->paddr += dt_block->off; dt_region->sz = dt_block->sz; } diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c index 8f75934e09d0..632abb8b481c 100644 --- a/drivers/dma/dw-edma/dw-hdma-v0-core.c +++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c @@ -225,7 +225,7 @@ static void dw_hdma_v0_sync_ll_data(struct dw_edma_chunk *chunk) readl(chunk->ll_region.vaddr.io); } -static void dw_hdma_v0_core_start(struct dw_edma_chunk *chunk, bool first) +static void dw_hdma_v0_core_ll_start(struct dw_edma_chunk *chunk, bool first) { struct dw_edma_chan *chan = chunk->chan; struct dw_edma *dw = chan->dw; @@ -263,6 +263,68 @@ static void dw_hdma_v0_core_start(struct dw_edma_chunk *chunk, bool first) SET_CH_32(dw, chan->dir, chan->id, doorbell, HDMA_V0_DOORBELL_START); } +static void dw_hdma_v0_core_non_ll_start(struct dw_edma_chunk *chunk) +{ + struct dw_edma_chan *chan = chunk->chan; + struct dw_edma *dw = chan->dw; + struct dw_edma_burst *child; + u32 val; + + child = list_first_entry_or_null(&chunk->burst->list, + struct dw_edma_burst, list); + if (!child) + return; + + SET_CH_32(dw, chan->dir, chan->id, ch_en, HDMA_V0_CH_EN); + + /* Source address */ + SET_CH_32(dw, chan->dir, chan->id, sar.lsb, + lower_32_bits(child->sar)); + SET_CH_32(dw, chan->dir, chan->id, sar.msb, + upper_32_bits(child->sar)); + + /* Destination address */ + SET_CH_32(dw, chan->dir, chan->id, dar.lsb, + lower_32_bits(child->dar)); + SET_CH_32(dw, chan->dir, chan->id, dar.msb, + upper_32_bits(child->dar)); + + /* Transfer size */ + SET_CH_32(dw, chan->dir, chan->id, transfer_size, child->sz); + + /* Interrupt setup */ + val = GET_CH_32(dw, chan->dir, chan->id, int_setup) | + HDMA_V0_STOP_INT_MASK | + HDMA_V0_ABORT_INT_MASK | + HDMA_V0_LOCAL_STOP_INT_EN | + HDMA_V0_LOCAL_ABORT_INT_EN; + + if (!(dw->chip->flags & DW_EDMA_CHIP_LOCAL)) { + val |= HDMA_V0_REMOTE_STOP_INT_EN | + HDMA_V0_REMOTE_ABORT_INT_EN; + } + + SET_CH_32(dw, chan->dir, chan->id, int_setup, val); + + /* Channel control setup */ + val = GET_CH_32(dw, chan->dir, chan->id, control1); + val &= ~HDMA_V0_LINKLIST_EN; + SET_CH_32(dw, chan->dir, chan->id, control1, val); + + SET_CH_32(dw, chan->dir, chan->id, doorbell, + HDMA_V0_DOORBELL_START); +} + +static void dw_hdma_v0_core_start(struct dw_edma_chunk *chunk, bool first) +{ + struct dw_edma_chan *chan = chunk->chan; + + if (chan->non_ll) + dw_hdma_v0_core_non_ll_start(chunk); + else + dw_hdma_v0_core_ll_start(chunk, first); +} + static void dw_hdma_v0_core_ch_config(struct dw_edma_chan *chan) { struct dw_edma *dw = chan->dw; diff --git a/drivers/dma/dw-edma/dw-hdma-v0-regs.h b/drivers/dma/dw-edma/dw-hdma-v0-regs.h index eab5fd7177e5..7759ba9b4850 100644 --- a/drivers/dma/dw-edma/dw-hdma-v0-regs.h +++ b/drivers/dma/dw-edma/dw-hdma-v0-regs.h @@ -12,6 +12,7 @@ #include #define HDMA_V0_MAX_NR_CH 8 +#define HDMA_V0_CH_EN BIT(0) #define HDMA_V0_LOCAL_ABORT_INT_EN BIT(6) #define HDMA_V0_REMOTE_ABORT_INT_EN BIT(5) #define HDMA_V0_LOCAL_STOP_INT_EN BIT(4) diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h index 9da53c75e49b..1fafd5b0e315 100644 --- a/include/linux/dma/edma.h +++ b/include/linux/dma/edma.h @@ -103,6 +103,7 @@ struct dw_edma_chip { enum dw_edma_map_format mf; struct dw_edma *dw; + bool cfg_non_ll; }; /* Export to the platform drivers */