From dfd9538e78f57705e6c656d3ff5660ae7137789c Mon Sep 17 00:00:00 2001 From: Kuldeep Singh Date: Thu, 14 May 2026 00:22:20 +0530 Subject: [PATCH 01/62] dt-bindings: dma: qcom,bam-dma: Document BAM v2.0.0 compatible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document compatible string for bam v2.0.0 version found on kaanapali. BAM v2.0.0 differs from the earlier v1.7.X revision in terms of register layout and offsets, requiring a distinct compatible for correct hardware description. Also add a new example for BAM v2.0.0 to illustrate a more complete configuration than the existing v1.4 example. The new example covers 64-bit address and size cells, IOMMU bindings and execution environment–related properties required on newer platforms. Signed-off-by: Kuldeep Singh Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260514-knp_qce-v2-1-890e3372eef8@oss.qualcomm.com Signed-off-by: Vinod Koul --- .../devicetree/bindings/dma/qcom,bam-dma.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml b/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml index 6493a6968bb4..0923fb189ada 100644 --- a/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml +++ b/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml @@ -23,6 +23,8 @@ properties: - qcom,bam-v1.4.0 # MSM8916, SDM630 - qcom,bam-v1.7.0 + # Kaanapali + - qcom,bam-v2.0.0 - items: - enum: # SDM845, SM6115, SM8150, SM8250 and QCM2290 @@ -118,4 +120,23 @@ examples: #dma-cells = <1>; qcom,ee = <0>; }; + - | + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + dma-controller@1dc4000 { + compatible = "qcom,bam-v2.0.0"; + reg = <0x0 0x01dc4000 0x0 0x22000>; + interrupts = ; + #dma-cells = <1>; + iommus = <&apps_smmu 0xc0 0>, <&apps_smmu 0xc1 0>; + qcom,ee = <0>; + qcom,num-ees = <4>; + num-channels = <20>; + qcom,controlled-remotely; + }; + }; ... From 3331fc194b129582fa21cb3a7e5cc68aaac1081f Mon Sep 17 00:00:00 2001 From: Kuldeep Singh Date: Thu, 14 May 2026 00:22:21 +0530 Subject: [PATCH 02/62] dmaengine: qcom: bam_dma: Add support for BAM v2.0.0 Add register offset table entry for bam v2.0.0 version found on kaanapali. Signed-off-by: Kuldeep Singh Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260514-knp_qce-v2-2-890e3372eef8@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/dma/qcom/bam_dma.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/dma/qcom/bam_dma.c b/drivers/dma/qcom/bam_dma.c index 19116295f832..1bb26af0405f 100644 --- a/drivers/dma/qcom/bam_dma.c +++ b/drivers/dma/qcom/bam_dma.c @@ -199,6 +199,35 @@ static const struct reg_offset_data bam_v1_7_reg_info[] = { [BAM_P_FIFO_SIZES] = { 0x13820, 0x00, 0x1000, 0x00 }, }; +static const struct reg_offset_data bam_v2_0_reg_info[] = { + [BAM_CTRL] = { 0x0000, 0x00, 0x00, 0x00 }, + [BAM_REVISION] = { 0x1000, 0x00, 0x00, 0x00 }, + [BAM_NUM_PIPES] = { 0x1008, 0x00, 0x00, 0x00 }, + [BAM_DESC_CNT_TRSHLD] = { 0x0008, 0x00, 0x00, 0x00 }, + [BAM_IRQ_SRCS] = { 0x3010, 0x00, 0x00, 0x00 }, + [BAM_IRQ_SRCS_MSK] = { 0x3014, 0x00, 0x00, 0x00 }, + [BAM_IRQ_SRCS_UNMASKED] = { 0x3018, 0x00, 0x00, 0x00 }, + [BAM_IRQ_STTS] = { 0x0014, 0x00, 0x00, 0x00 }, + [BAM_IRQ_CLR] = { 0x0018, 0x00, 0x00, 0x00 }, + [BAM_IRQ_EN] = { 0x001C, 0x00, 0x00, 0x00 }, + [BAM_CNFG_BITS] = { 0x007C, 0x00, 0x00, 0x00 }, + [BAM_IRQ_SRCS_EE] = { 0x3000, 0x00, 0x00, 0x1000 }, + [BAM_IRQ_SRCS_MSK_EE] = { 0x3004, 0x00, 0x00, 0x1000 }, + [BAM_P_CTRL] = { 0xC000, 0x1000, 0x00, 0x00 }, + [BAM_P_RST] = { 0xC004, 0x1000, 0x00, 0x00 }, + [BAM_P_HALT] = { 0xC008, 0x1000, 0x00, 0x00 }, + [BAM_P_IRQ_STTS] = { 0xC010, 0x1000, 0x00, 0x00 }, + [BAM_P_IRQ_CLR] = { 0xC014, 0x1000, 0x00, 0x00 }, + [BAM_P_IRQ_EN] = { 0xC018, 0x1000, 0x00, 0x00 }, + [BAM_P_EVNT_DEST_ADDR] = { 0xC82C, 0x00, 0x1000, 0x00 }, + [BAM_P_EVNT_REG] = { 0xC818, 0x00, 0x1000, 0x00 }, + [BAM_P_SW_OFSTS] = { 0xC800, 0x00, 0x1000, 0x00 }, + [BAM_P_DATA_FIFO_ADDR] = { 0xC824, 0x00, 0x1000, 0x00 }, + [BAM_P_DESC_FIFO_ADDR] = { 0xC81C, 0x00, 0x1000, 0x00 }, + [BAM_P_EVNT_GEN_TRSHLD] = { 0xC828, 0x00, 0x1000, 0x00 }, + [BAM_P_FIFO_SIZES] = { 0xC820, 0x00, 0x1000, 0x00 }, +}; + /* BAM CTRL */ #define BAM_SW_RST BIT(0) #define BAM_EN BIT(1) @@ -1208,6 +1237,7 @@ static const struct of_device_id bam_of_match[] = { { .compatible = "qcom,bam-v1.3.0", .data = &bam_v1_3_reg_info }, { .compatible = "qcom,bam-v1.4.0", .data = &bam_v1_4_reg_info }, { .compatible = "qcom,bam-v1.7.0", .data = &bam_v1_7_reg_info }, + { .compatible = "qcom,bam-v2.0.0", .data = &bam_v2_0_reg_info }, {} }; From 998514806998f76d367f935940d0c5e5e5cd12c6 Mon Sep 17 00:00:00 2001 From: Xueyao An Date: Wed, 1 Apr 2026 18:10:28 +0530 Subject: [PATCH 03/62] dt-bindings: dma: qcom,gpi: Document GPI DMA engine for Hawi SoC The Hawi GPI DMA engine follows the same programming model and register interface as previous generation of Qualcomm SoCs like kaanapali, glymur, and is fully compatible with earlier GPI DMA implementations. Reviewed-by: Konrad Dybcio Signed-off-by: Xueyao An Signed-off-by: Mukesh Ojha Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260401124028.589931-1-mukesh.ojha@oss.qualcomm.com Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/dma/qcom,gpi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/dma/qcom,gpi.yaml b/Documentation/devicetree/bindings/dma/qcom,gpi.yaml index fde1df035ad1..caa2ef90d8f2 100644 --- a/Documentation/devicetree/bindings/dma/qcom,gpi.yaml +++ b/Documentation/devicetree/bindings/dma/qcom,gpi.yaml @@ -25,6 +25,7 @@ properties: - items: - enum: - qcom,glymur-gpi-dma + - qcom,hawi-gpi-dma - qcom,kaanapali-gpi-dma - qcom,milos-gpi-dma - qcom,qcm2290-gpi-dma From 55620b11186c81757b05fb8e2df9ddc7127d6fd2 Mon Sep 17 00:00:00 2001 From: Guodong Xu Date: Mon, 18 May 2026 11:32:41 +0800 Subject: [PATCH 04/62] dt-bindings: dmaengine: Add SpacemiT K3 DMA compatible string Add the "spacemit,k3-pdma" compatible string for the SpacemiT K3 SoC. While the K3 PDMA IP reuses most of the design found on the earlier K1 SoC, a new compatible string is required because the DRCMR (DMA Request/Command Register) base address for extended DMA request numbers (>= 64) differs from the K1 implementation. Signed-off-by: Guodong Xu Acked-by: Conor Dooley Signed-off-by: Troy Mitchell Link: https://patch.msgid.link/20260518-k3-pdma-v6-1-67fdf319a8f8@linux.spacemit.com Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/dma/spacemit,k1-pdma.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/dma/spacemit,k1-pdma.yaml b/Documentation/devicetree/bindings/dma/spacemit,k1-pdma.yaml index ec06235baf5c..62ce6d81526b 100644 --- a/Documentation/devicetree/bindings/dma/spacemit,k1-pdma.yaml +++ b/Documentation/devicetree/bindings/dma/spacemit,k1-pdma.yaml @@ -14,7 +14,9 @@ allOf: properties: compatible: - const: spacemit,k1-pdma + enum: + - spacemit,k1-pdma + - spacemit,k3-pdma reg: maxItems: 1 From f46b47623e70dea8b03794a5420ffba060425e85 Mon Sep 17 00:00:00 2001 From: Guodong Xu Date: Mon, 18 May 2026 11:32:42 +0800 Subject: [PATCH 05/62] dmaengine: mmp_pdma: refactor DRCMR access with helper function Refactor the DRCMR macro into a helper function mmp_pdma_get_drcmr() to support variable extended DRCMR base addresses across different PDMA implementations, such as SpacemiT K3. Signed-off-by: Guodong Xu Signed-off-by: Troy Mitchell Link: https://patch.msgid.link/20260518-k3-pdma-v6-2-67fdf319a8f8@linux.spacemit.com Signed-off-by: Vinod Koul --- drivers/dma/mmp_pdma.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c index d12e729ee12c..6112369006ee 100644 --- a/drivers/dma/mmp_pdma.c +++ b/drivers/dma/mmp_pdma.c @@ -51,7 +51,9 @@ #define DCSR_CMPST BIT(10) /* The Descriptor Compare Status */ #define DCSR_EORINTR BIT(9) /* The end of Receive */ -#define DRCMR(n) ((((n) < 64) ? 0x0100 : 0x1100) + (((n) & 0x3f) << 2)) +#define DRCMR_BASE 0x0100 +#define DRCMR_EXT_BASE_DEFAULT 0x1100 +#define DRCMR_REQ_LIMIT 64 #define DRCMR_MAPVLD BIT(7) /* Map Valid (read / write) */ #define DRCMR_CHLNUM 0x1f /* mask for Channel Number (read / write) */ @@ -154,6 +156,7 @@ struct mmp_pdma_phy { * @run_bits: Control bits in DCSR register for channel start/stop * @dma_width: DMA addressing width in bits (32 or 64). Determines the * DMA mask capability of the controller hardware. + * @drcmr_ext_base: Base DRCMR address for extended requests */ struct mmp_pdma_ops { /* Hardware Register Operations */ @@ -174,6 +177,7 @@ struct mmp_pdma_ops { /* Controller Configuration */ u32 run_bits; u32 dma_width; + u32 drcmr_ext_base; }; struct mmp_pdma_device { @@ -195,6 +199,13 @@ struct mmp_pdma_device { #define to_mmp_pdma_dev(dmadev) \ container_of(dmadev, struct mmp_pdma_device, device) +static u32 mmp_pdma_get_drcmr(struct mmp_pdma_device *pdev, u32 drcmr) +{ + if (drcmr < DRCMR_REQ_LIMIT) + return DRCMR_BASE + (drcmr << 2); + return pdev->ops->drcmr_ext_base + ((drcmr - DRCMR_REQ_LIMIT) << 2); +} + /* For 32-bit PDMA */ static void write_next_addr_32(struct mmp_pdma_phy *phy, dma_addr_t addr) { @@ -301,7 +312,7 @@ static void enable_chan(struct mmp_pdma_phy *phy) pdev = to_mmp_pdma_dev(phy->vchan->chan.device); - reg = DRCMR(phy->vchan->drcmr); + reg = mmp_pdma_get_drcmr(pdev, phy->vchan->drcmr); writel(DRCMR_MAPVLD | phy->idx, phy->base + reg); dalgn = readl(phy->base + DALGN); @@ -437,7 +448,7 @@ static void mmp_pdma_free_phy(struct mmp_pdma_chan *pchan) return; /* clear the channel mapping in DRCMR */ - reg = DRCMR(pchan->drcmr); + reg = mmp_pdma_get_drcmr(pdev, pchan->drcmr); writel(0, pchan->phy->base + reg); spin_lock_irqsave(&pdev->phy_lock, flags); @@ -1179,6 +1190,7 @@ static const struct mmp_pdma_ops marvell_pdma_v1_ops = { .get_desc_dst_addr = get_desc_dst_addr_32, .run_bits = (DCSR_RUN), .dma_width = 32, + .drcmr_ext_base = DRCMR_EXT_BASE_DEFAULT, }; static const struct mmp_pdma_ops spacemit_k1_pdma_ops = { @@ -1192,6 +1204,7 @@ static const struct mmp_pdma_ops spacemit_k1_pdma_ops = { .get_desc_dst_addr = get_desc_dst_addr_64, .run_bits = (DCSR_RUN | DCSR_LPAEEN), .dma_width = 64, + .drcmr_ext_base = DRCMR_EXT_BASE_DEFAULT, }; static const struct of_device_id mmp_pdma_dt_ids[] = { From 6587b8661a0b61c2f4b260bfc9f0e9ef9de0ea2e Mon Sep 17 00:00:00 2001 From: Guodong Xu Date: Mon, 18 May 2026 11:32:43 +0800 Subject: [PATCH 06/62] dmaengine: mmp_pdma: add SpacemiT K3 support SpacemiT K3 reuses most of the PDMA IP design found on K1, with one difference being the extended DRCMR base address. Add "spacemit,k3-pdma" compatible string and define a new mmp_pdma_ops for K3 PDMA. Signed-off-by: Guodong Xu Signed-off-by: Troy Mitchell Link: https://patch.msgid.link/20260518-k3-pdma-v6-3-67fdf319a8f8@linux.spacemit.com Signed-off-by: Vinod Koul --- drivers/dma/mmp_pdma.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c index 6112369006ee..386e85cd4882 100644 --- a/drivers/dma/mmp_pdma.c +++ b/drivers/dma/mmp_pdma.c @@ -52,6 +52,7 @@ #define DCSR_EORINTR BIT(9) /* The end of Receive */ #define DRCMR_BASE 0x0100 +#define DRCMR_EXT_BASE_K3 0x1000 #define DRCMR_EXT_BASE_DEFAULT 0x1100 #define DRCMR_REQ_LIMIT 64 #define DRCMR_MAPVLD BIT(7) /* Map Valid (read / write) */ @@ -1207,6 +1208,20 @@ static const struct mmp_pdma_ops spacemit_k1_pdma_ops = { .drcmr_ext_base = DRCMR_EXT_BASE_DEFAULT, }; +static const struct mmp_pdma_ops spacemit_k3_pdma_ops = { + .write_next_addr = write_next_addr_64, + .read_src_addr = read_src_addr_64, + .read_dst_addr = read_dst_addr_64, + .set_desc_next_addr = set_desc_next_addr_64, + .set_desc_src_addr = set_desc_src_addr_64, + .set_desc_dst_addr = set_desc_dst_addr_64, + .get_desc_src_addr = get_desc_src_addr_64, + .get_desc_dst_addr = get_desc_dst_addr_64, + .run_bits = (DCSR_RUN | DCSR_LPAEEN | DCSR_EORIRQEN | DCSR_EORSTOPEN), + .dma_width = 64, + .drcmr_ext_base = DRCMR_EXT_BASE_K3, +}; + static const struct of_device_id mmp_pdma_dt_ids[] = { { .compatible = "marvell,pdma-1.0", @@ -1214,6 +1229,9 @@ static const struct of_device_id mmp_pdma_dt_ids[] = { }, { .compatible = "spacemit,k1-pdma", .data = &spacemit_k1_pdma_ops + }, { + .compatible = "spacemit,k3-pdma", + .data = &spacemit_k3_pdma_ops }, { /* sentinel */ } From 33a6c96b31035fccf6968e2d35e3b727cd42580b Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Fri, 15 May 2026 14:39:36 +0300 Subject: [PATCH 07/62] dt-bindings: dma: qcom,gpi: Document the Eliza GPI DMA engine Document the GPI DMA engine found on the Eliza SoC. It is fully compatible with the GPI DMA engine found on SM6350, thus using qcom,sm6350-gpi-dma as fallback compatible. Acked-by: Krzysztof Kozlowski Signed-off-by: Abel Vesa Reviewed-by: Pankaj Patil Link: https://patch.msgid.link/20260515-eliza-gpi-dma-v2-1-1255b43d5ca9@oss.qualcomm.com Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/dma/qcom,gpi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/dma/qcom,gpi.yaml b/Documentation/devicetree/bindings/dma/qcom,gpi.yaml index caa2ef90d8f2..8f9a552fe30e 100644 --- a/Documentation/devicetree/bindings/dma/qcom,gpi.yaml +++ b/Documentation/devicetree/bindings/dma/qcom,gpi.yaml @@ -24,6 +24,7 @@ properties: - qcom,sm6350-gpi-dma - items: - enum: + - qcom,eliza-gpi-dma - qcom,glymur-gpi-dma - qcom,hawi-gpi-dma - qcom,kaanapali-gpi-dma From 362ee0c0dc522bcf585bde59ceba2038ec583b7d Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 14 May 2026 08:05:26 +0200 Subject: [PATCH 08/62] dmaengine: Move MODULE_DEVICE_TABLE next to the table itself By convention MODULE_DEVICE_TABLE() immediately follows the ID table it exports, because this is easier to read and verify. It also makes more sense since #ifdef for ACPI or OF could hide both of them. Most of the drivers already have this correctly placed, so adjust the missing ones. No functional impact. Reviewed-by: Radhey Shyam Pandey Reviewed-by: Frank Li Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260514060525.9253-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/dma/hisi_dma.c | 2 +- drivers/dma/pch_dma.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/dma/hisi_dma.c b/drivers/dma/hisi_dma.c index 32a0e95c6a20..28bf818f9aa6 100644 --- a/drivers/dma/hisi_dma.c +++ b/drivers/dma/hisi_dma.c @@ -1037,6 +1037,7 @@ static const struct pci_device_id hisi_dma_pci_tbl[] = { { PCI_DEVICE(PCI_VENDOR_ID_HUAWEI, 0xa122) }, { 0, } }; +MODULE_DEVICE_TABLE(pci, hisi_dma_pci_tbl); static struct pci_driver hisi_dma_pci_driver = { .name = "hisi_dma", @@ -1050,4 +1051,3 @@ MODULE_AUTHOR("Zhou Wang "); MODULE_AUTHOR("Zhenfa Qiu "); MODULE_DESCRIPTION("HiSilicon Kunpeng DMA controller driver"); MODULE_LICENSE("GPL v2"); -MODULE_DEVICE_TABLE(pci, hisi_dma_pci_tbl); diff --git a/drivers/dma/pch_dma.c b/drivers/dma/pch_dma.c index e9fbfd5a3d51..bf805f1024f6 100644 --- a/drivers/dma/pch_dma.c +++ b/drivers/dma/pch_dma.c @@ -970,6 +970,7 @@ static const struct pci_device_id pch_dma_id_table[] = { { PCI_VDEVICE(ROHM, PCI_DEVICE_ID_ML7831_DMA2_4CH), 4}, /* SPI */ { 0, }, }; +MODULE_DEVICE_TABLE(pci, pch_dma_id_table); static SIMPLE_DEV_PM_OPS(pch_dma_pm_ops, pch_dma_suspend, pch_dma_resume); @@ -987,4 +988,3 @@ MODULE_DESCRIPTION("Intel EG20T PCH / LAPIS Semicon ML7213/ML7223/ML7831 IOH " "DMA controller driver"); MODULE_AUTHOR("Yong Wang "); MODULE_LICENSE("GPL v2"); -MODULE_DEVICE_TABLE(pci, pch_dma_id_table); From d52d42e2e5d9f13166e81ac837ebb023d1306e61 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Tue, 7 Apr 2026 11:27:55 +0800 Subject: [PATCH 09/62] dmaengine: imx-sdma: Refine spba bus searching in probe There are multi spba-busses for i.MX8M* platforms, if only search for the first spba-bus in DT, the found spba-bus may not the real bus of audio devices, which cause issue for sdma p2p case, as the sdma p2p script presently does not deal with the transactions involving two devices connected to the AIPS bus. Search the SDMA parent node first, which should be the AIPS bus, then search the child node whose compatible string is spba-bus under that AIPS bus for the above multi spba-busses case. Fixes: 8391ecf465ec ("dmaengine: imx-sdma: Add device to device support") Signed-off-by: Shengjiu Wang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260407032755.2758049-1-shengjiu.wang@nxp.com Signed-off-by: Vinod Koul --- drivers/dma/imx-sdma.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c index 3d527883776b..36368835a845 100644 --- a/drivers/dma/imx-sdma.c +++ b/drivers/dma/imx-sdma.c @@ -2364,7 +2364,9 @@ static int sdma_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, ret, "failed to register controller\n"); - spba_bus = of_find_compatible_node(NULL, NULL, "fsl,spba-bus"); + struct device_node *sdma_parent_np __free(device_node) = of_get_parent(np); + + spba_bus = of_get_compatible_child(sdma_parent_np, "fsl,spba-bus"); ret = of_address_to_resource(spba_bus, 0, &spba_res); if (!ret) { sdma->spba_start_addr = spba_res.start; From cc6049bd3fa8501ee27042df469a19ed69cf406d Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 31 Mar 2026 15:52:54 +0530 Subject: [PATCH 10/62] dt-bindings: dma: nvidia,tegra186-gpc-dma: Make reset optional On Tegra264, GPCDMA reset control is not exposed to Linux and is handled by the boot firmware. Although reset was not exposed in Tegra234 as well, the firmware supported a dummy reset which just returns success on reset without doing an actual reset. This is also not supported in Tegra264 BPMP. Therefore mark 'reset' and 'reset-names' properties as required only for devices prior to Tegra264. This also necessitates that the Tegra264 compatible be standalone and cannot have the fallback compatible of Tegra186. Since there is no functional impact, we keep reset as required for Tegra234 to avoid breaking the ABI. Fixes: bb8c97571db5 ("dt-bindings: dma: Add Tegra264 compatible string") Signed-off-by: Akhil R Acked-by: Rob Herring (Arm) Acked-by: Thierry Reding Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260331102303.33181-2-akhilrajeev@nvidia.com Signed-off-by: Vinod Koul --- .../bindings/dma/nvidia,tegra186-gpc-dma.yaml | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml b/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml index 0dabe9bbb219..64f1e9d9896d 100644 --- a/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml +++ b/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml @@ -15,16 +15,14 @@ maintainers: - Jon Hunter - Rajesh Gumasta -allOf: - - $ref: dma-controller.yaml# - properties: compatible: oneOf: - - const: nvidia,tegra186-gpcdma + - enum: + - nvidia,tegra264-gpcdma + - nvidia,tegra186-gpcdma - items: - enum: - - nvidia,tegra264-gpcdma - nvidia,tegra234-gpcdma - nvidia,tegra194-gpcdma - const: nvidia,tegra186-gpcdma @@ -60,12 +58,23 @@ required: - compatible - reg - interrupts - - resets - - reset-names - "#dma-cells" - iommus - dma-channel-mask +allOf: + - $ref: dma-controller.yaml# + - if: + properties: + compatible: + contains: + enum: + - nvidia,tegra186-gpcdma + then: + required: + - resets + - reset-names + additionalProperties: false examples: From d6d7ffb994c676e6414a725d7eb8f208d901b63a Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 31 Mar 2026 15:52:56 +0530 Subject: [PATCH 11/62] dt-bindings: dma: nvidia,tegra186-gpc-dma: Add iommu-map property Add iommu-map property to specify separate stream IDs for each DMA channel. This enables each channel to be in its own IOMMU domain, keeping memory isolated from other devices sharing the same DMA controller. Define the constraints such that if the channel and stream IDs are contiguous, a single entry can map all the channels, but if the channels or stream IDs are non-contiguous support multiple entries. Signed-off-by: Akhil R Acked-by: Rob Herring (Arm) Acked-by: Thierry Reding Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260331102303.33181-4-akhilrajeev@nvidia.com Signed-off-by: Vinod Koul --- .../devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml b/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml index 64f1e9d9896d..bc093c783d98 100644 --- a/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml +++ b/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml @@ -14,6 +14,7 @@ description: | maintainers: - Jon Hunter - Rajesh Gumasta + - Akhil R properties: compatible: @@ -49,6 +50,14 @@ properties: iommus: maxItems: 1 + iommu-map: + description: + Maps DMA channel numbers to IOMMU stream IDs. A single entry can map all + channels when stream IDs are contiguous. In systems where the channels or + stream IDs are not contiguous, multiple entries may be needed. + minItems: 1 + maxItems: 32 + dma-coherent: true dma-channel-mask: From 680e1b928a6adc1b2d95038ffe9c9887ceafd478 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 31 Mar 2026 15:52:57 +0530 Subject: [PATCH 12/62] dmaengine: tegra: Make reset control optional On Tegra264, reset is not available for the driver to control as this is handled by the boot firmware. Hence make the reset control optional and update the error message to reflect the correct error. Signed-off-by: Akhil R Reviewed-by: Frank Li Acked-by: Thierry Reding Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260331102303.33181-5-akhilrajeev@nvidia.com Signed-off-by: Vinod Koul --- drivers/dma/tegra186-gpc-dma.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c index 5948fbf32c21..a0522a992ebc 100644 --- a/drivers/dma/tegra186-gpc-dma.c +++ b/drivers/dma/tegra186-gpc-dma.c @@ -1381,10 +1381,10 @@ static int tegra_dma_probe(struct platform_device *pdev) if (IS_ERR(tdma->base_addr)) return PTR_ERR(tdma->base_addr); - tdma->rst = devm_reset_control_get_exclusive(&pdev->dev, "gpcdma"); + tdma->rst = devm_reset_control_get_optional_exclusive(&pdev->dev, "gpcdma"); if (IS_ERR(tdma->rst)) { return dev_err_probe(&pdev->dev, PTR_ERR(tdma->rst), - "Missing controller reset\n"); + "Failed to get controller reset\n"); } reset_control_reset(tdma->rst); From 5000beabae65310ec81db40dcda181b0a6192ff3 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 31 Mar 2026 15:52:58 +0530 Subject: [PATCH 13/62] dmaengine: tegra: Use struct for register offsets Repurpose the struct tegra_dma_channel_regs to define offsets for all the channel registers. Previously, the struct only held the register values for each transfer and was wrapped within tegra_dma_sg_req. Move the values directly into tegra_dma_sg_req and use channel_regs for storing the register offsets. Update all register reads/writes to use the struct channel_regs. This prepares for the register offset change in Tegra264. Signed-off-by: Akhil R Reviewed-by: Frank Li Acked-by: Thierry Reding Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260331102303.33181-6-akhilrajeev@nvidia.com Signed-off-by: Vinod Koul --- drivers/dma/tegra186-gpc-dma.c | 282 +++++++++++++++++---------------- 1 file changed, 142 insertions(+), 140 deletions(-) diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c index a0522a992ebc..b213c4ae07d2 100644 --- a/drivers/dma/tegra186-gpc-dma.c +++ b/drivers/dma/tegra186-gpc-dma.c @@ -22,7 +22,6 @@ #include "virt-dma.h" /* CSR register */ -#define TEGRA_GPCDMA_CHAN_CSR 0x00 #define TEGRA_GPCDMA_CSR_ENB BIT(31) #define TEGRA_GPCDMA_CSR_IE_EOC BIT(30) #define TEGRA_GPCDMA_CSR_ONCE BIT(27) @@ -58,7 +57,6 @@ #define TEGRA_GPCDMA_CSR_WEIGHT GENMASK(13, 10) /* STATUS register */ -#define TEGRA_GPCDMA_CHAN_STATUS 0x004 #define TEGRA_GPCDMA_STATUS_BUSY BIT(31) #define TEGRA_GPCDMA_STATUS_ISE_EOC BIT(30) #define TEGRA_GPCDMA_STATUS_PING_PONG BIT(28) @@ -70,22 +68,13 @@ #define TEGRA_GPCDMA_STATUS_IRQ_STA BIT(21) #define TEGRA_GPCDMA_STATUS_IRQ_TRIG_STA BIT(20) -#define TEGRA_GPCDMA_CHAN_CSRE 0x008 #define TEGRA_GPCDMA_CHAN_CSRE_PAUSE BIT(31) -/* Source address */ -#define TEGRA_GPCDMA_CHAN_SRC_PTR 0x00C - -/* Destination address */ -#define TEGRA_GPCDMA_CHAN_DST_PTR 0x010 - /* High address pointer */ -#define TEGRA_GPCDMA_CHAN_HIGH_ADDR_PTR 0x014 #define TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR GENMASK(7, 0) #define TEGRA_GPCDMA_HIGH_ADDR_DST_PTR GENMASK(23, 16) /* MC sequence register */ -#define TEGRA_GPCDMA_CHAN_MCSEQ 0x18 #define TEGRA_GPCDMA_MCSEQ_DATA_SWAP BIT(31) #define TEGRA_GPCDMA_MCSEQ_REQ_COUNT GENMASK(30, 25) #define TEGRA_GPCDMA_MCSEQ_BURST GENMASK(24, 23) @@ -101,7 +90,6 @@ #define TEGRA_GPCDMA_MCSEQ_STREAM_ID0_MASK GENMASK(6, 0) /* MMIO sequence register */ -#define TEGRA_GPCDMA_CHAN_MMIOSEQ 0x01c #define TEGRA_GPCDMA_MMIOSEQ_DBL_BUF BIT(31) #define TEGRA_GPCDMA_MMIOSEQ_BUS_WIDTH GENMASK(30, 28) #define TEGRA_GPCDMA_MMIOSEQ_BUS_WIDTH_8 \ @@ -120,17 +108,7 @@ #define TEGRA_GPCDMA_MMIOSEQ_WRAP_WORD GENMASK(18, 16) #define TEGRA_GPCDMA_MMIOSEQ_MMIO_PROT GENMASK(8, 7) -/* Channel WCOUNT */ -#define TEGRA_GPCDMA_CHAN_WCOUNT 0x20 - -/* Transfer count */ -#define TEGRA_GPCDMA_CHAN_XFER_COUNT 0x24 - -/* DMA byte count status */ -#define TEGRA_GPCDMA_CHAN_DMA_BYTE_STATUS 0x28 - /* Error Status Register */ -#define TEGRA_GPCDMA_CHAN_ERR_STATUS 0x30 #define TEGRA_GPCDMA_CHAN_ERR_TYPE_SHIFT 8 #define TEGRA_GPCDMA_CHAN_ERR_TYPE_MASK 0xF #define TEGRA_GPCDMA_CHAN_ERR_TYPE(err) ( \ @@ -143,16 +121,6 @@ #define TEGRA_DMA_MC_SLAVE_ERR 0xB #define TEGRA_DMA_MMIO_SLAVE_ERR 0xA -/* Fixed Pattern */ -#define TEGRA_GPCDMA_CHAN_FIXED_PATTERN 0x34 - -#define TEGRA_GPCDMA_CHAN_TZ 0x38 -#define TEGRA_GPCDMA_CHAN_TZ_MMIO_PROT_1 BIT(0) -#define TEGRA_GPCDMA_CHAN_TZ_MC_PROT_1 BIT(1) - -#define TEGRA_GPCDMA_CHAN_SPARE 0x3c -#define TEGRA_GPCDMA_CHAN_SPARE_EN_LEGACY_FC BIT(16) - /* * If any burst is in flight and DMA paused then this is the time to complete * on-flight burst and update DMA status register. @@ -181,18 +149,24 @@ struct tegra_dma_chip_data { unsigned int nr_channels; unsigned int channel_reg_size; unsigned int max_dma_count; + const struct tegra_dma_channel_regs *channel_regs; int (*terminate)(struct tegra_dma_channel *tdc); }; /* DMA channel registers */ struct tegra_dma_channel_regs { u32 csr; - u32 src_ptr; - u32 dst_ptr; - u32 high_addr_ptr; + u32 status; + u32 csre; + u32 src; + u32 dst; + u32 high_addr; u32 mc_seq; u32 mmio_seq; u32 wcount; + u32 wxfer; + u32 wstatus; + u32 err_status; u32 fixed_pattern; }; @@ -205,7 +179,14 @@ struct tegra_dma_channel_regs { */ struct tegra_dma_sg_req { unsigned int len; - struct tegra_dma_channel_regs ch_regs; + u32 csr; + u32 src; + u32 dst; + u32 high_addr; + u32 mc_seq; + u32 mmio_seq; + u32 wcount; + u32 fixed_pattern; }; /* @@ -228,19 +209,20 @@ struct tegra_dma_desc { * tegra_dma_channel: Channel specific information */ struct tegra_dma_channel { - bool config_init; - char name[30]; - enum dma_transfer_direction sid_dir; - enum dma_status status; - int id; - int irq; - int slave_id; + const struct tegra_dma_channel_regs *regs; struct tegra_dma *tdma; struct virt_dma_chan vc; struct tegra_dma_desc *dma_desc; struct dma_slave_config dma_sconfig; + enum dma_transfer_direction sid_dir; + enum dma_status status; unsigned int stream_id; unsigned long chan_base_offset; + bool config_init; + char name[30]; + int id; + int irq; + int slave_id; }; /* @@ -288,22 +270,22 @@ static void tegra_dma_dump_chan_regs(struct tegra_dma_channel *tdc) { dev_dbg(tdc2dev(tdc), "DMA Channel %d name %s register dump:\n", tdc->id, tdc->name); - dev_dbg(tdc2dev(tdc), "CSR %x STA %x CSRE %x SRC %x DST %x\n", - tdc_read(tdc, TEGRA_GPCDMA_CHAN_CSR), - tdc_read(tdc, TEGRA_GPCDMA_CHAN_STATUS), - tdc_read(tdc, TEGRA_GPCDMA_CHAN_CSRE), - tdc_read(tdc, TEGRA_GPCDMA_CHAN_SRC_PTR), - tdc_read(tdc, TEGRA_GPCDMA_CHAN_DST_PTR) - ); - dev_dbg(tdc2dev(tdc), "MCSEQ %x IOSEQ %x WCNT %x XFER %x BSTA %x\n", - tdc_read(tdc, TEGRA_GPCDMA_CHAN_MCSEQ), - tdc_read(tdc, TEGRA_GPCDMA_CHAN_MMIOSEQ), - tdc_read(tdc, TEGRA_GPCDMA_CHAN_WCOUNT), - tdc_read(tdc, TEGRA_GPCDMA_CHAN_XFER_COUNT), - tdc_read(tdc, TEGRA_GPCDMA_CHAN_DMA_BYTE_STATUS) - ); + dev_dbg(tdc2dev(tdc), "CSR %x STA %x CSRE %x\n", + tdc_read(tdc, tdc->regs->csr), + tdc_read(tdc, tdc->regs->status), + tdc_read(tdc, tdc->regs->csre)); + dev_dbg(tdc2dev(tdc), "SRC %x DST %x HI ADDR %x\n", + tdc_read(tdc, tdc->regs->src), + tdc_read(tdc, tdc->regs->dst), + tdc_read(tdc, tdc->regs->high_addr)); + dev_dbg(tdc2dev(tdc), "MCSEQ %x IOSEQ %x WCNT %x XFER %x WSTA %x\n", + tdc_read(tdc, tdc->regs->mc_seq), + tdc_read(tdc, tdc->regs->mmio_seq), + tdc_read(tdc, tdc->regs->wcount), + tdc_read(tdc, tdc->regs->wxfer), + tdc_read(tdc, tdc->regs->wstatus)); dev_dbg(tdc2dev(tdc), "DMA ERR_STA %x\n", - tdc_read(tdc, TEGRA_GPCDMA_CHAN_ERR_STATUS)); + tdc_read(tdc, tdc->regs->err_status)); } static int tegra_dma_sid_reserve(struct tegra_dma_channel *tdc, @@ -377,13 +359,13 @@ static int tegra_dma_pause(struct tegra_dma_channel *tdc) int ret; u32 val; - val = tdc_read(tdc, TEGRA_GPCDMA_CHAN_CSRE); + val = tdc_read(tdc, tdc->regs->csre); val |= TEGRA_GPCDMA_CHAN_CSRE_PAUSE; - tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSRE, val); + tdc_write(tdc, tdc->regs->csre, val); /* Wait until busy bit is de-asserted */ ret = readl_relaxed_poll_timeout_atomic(tdc->tdma->base_addr + - tdc->chan_base_offset + TEGRA_GPCDMA_CHAN_STATUS, + tdc->chan_base_offset + tdc->regs->status, val, !(val & TEGRA_GPCDMA_STATUS_BUSY), TEGRA_GPCDMA_BURST_COMPLETE_TIME, @@ -419,9 +401,9 @@ static void tegra_dma_resume(struct tegra_dma_channel *tdc) { u32 val; - val = tdc_read(tdc, TEGRA_GPCDMA_CHAN_CSRE); + val = tdc_read(tdc, tdc->regs->csre); val &= ~TEGRA_GPCDMA_CHAN_CSRE_PAUSE; - tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSRE, val); + tdc_write(tdc, tdc->regs->csre, val); tdc->status = DMA_IN_PROGRESS; } @@ -456,27 +438,27 @@ static void tegra_dma_disable(struct tegra_dma_channel *tdc) { u32 csr, status; - csr = tdc_read(tdc, TEGRA_GPCDMA_CHAN_CSR); + csr = tdc_read(tdc, tdc->regs->csr); /* Disable interrupts */ csr &= ~TEGRA_GPCDMA_CSR_IE_EOC; /* Disable DMA */ csr &= ~TEGRA_GPCDMA_CSR_ENB; - tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSR, csr); + tdc_write(tdc, tdc->regs->csr, csr); /* Clear interrupt status if it is there */ - status = tdc_read(tdc, TEGRA_GPCDMA_CHAN_STATUS); + status = tdc_read(tdc, tdc->regs->status); if (status & TEGRA_GPCDMA_STATUS_ISE_EOC) { dev_dbg(tdc2dev(tdc), "%s():clearing interrupt\n", __func__); - tdc_write(tdc, TEGRA_GPCDMA_CHAN_STATUS, status); + tdc_write(tdc, tdc->regs->status, status); } } static void tegra_dma_configure_next_sg(struct tegra_dma_channel *tdc) { struct tegra_dma_desc *dma_desc = tdc->dma_desc; - struct tegra_dma_channel_regs *ch_regs; + struct tegra_dma_sg_req *sg_req; int ret; u32 val; @@ -488,29 +470,29 @@ static void tegra_dma_configure_next_sg(struct tegra_dma_channel *tdc) /* Configure next transfer immediately after DMA is busy */ ret = readl_relaxed_poll_timeout_atomic(tdc->tdma->base_addr + - tdc->chan_base_offset + TEGRA_GPCDMA_CHAN_STATUS, + tdc->chan_base_offset + tdc->regs->status, val, (val & TEGRA_GPCDMA_STATUS_BUSY), 0, TEGRA_GPCDMA_BURST_COMPLETION_TIMEOUT); if (ret) return; - ch_regs = &dma_desc->sg_req[dma_desc->sg_idx].ch_regs; + sg_req = &dma_desc->sg_req[dma_desc->sg_idx]; - tdc_write(tdc, TEGRA_GPCDMA_CHAN_WCOUNT, ch_regs->wcount); - tdc_write(tdc, TEGRA_GPCDMA_CHAN_SRC_PTR, ch_regs->src_ptr); - tdc_write(tdc, TEGRA_GPCDMA_CHAN_DST_PTR, ch_regs->dst_ptr); - tdc_write(tdc, TEGRA_GPCDMA_CHAN_HIGH_ADDR_PTR, ch_regs->high_addr_ptr); + tdc_write(tdc, tdc->regs->wcount, sg_req->wcount); + tdc_write(tdc, tdc->regs->src, sg_req->src); + tdc_write(tdc, tdc->regs->dst, sg_req->dst); + tdc_write(tdc, tdc->regs->high_addr, sg_req->high_addr); /* Start DMA */ - tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSR, - ch_regs->csr | TEGRA_GPCDMA_CSR_ENB); + tdc_write(tdc, tdc->regs->csr, + sg_req->csr | TEGRA_GPCDMA_CSR_ENB); } static void tegra_dma_start(struct tegra_dma_channel *tdc) { struct tegra_dma_desc *dma_desc = tdc->dma_desc; - struct tegra_dma_channel_regs *ch_regs; + struct tegra_dma_sg_req *sg_req; struct virt_dma_desc *vdesc; if (!dma_desc) { @@ -526,21 +508,21 @@ static void tegra_dma_start(struct tegra_dma_channel *tdc) tegra_dma_resume(tdc); } - ch_regs = &dma_desc->sg_req[dma_desc->sg_idx].ch_regs; + sg_req = &dma_desc->sg_req[dma_desc->sg_idx]; - tdc_write(tdc, TEGRA_GPCDMA_CHAN_WCOUNT, ch_regs->wcount); - tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSR, 0); - tdc_write(tdc, TEGRA_GPCDMA_CHAN_SRC_PTR, ch_regs->src_ptr); - tdc_write(tdc, TEGRA_GPCDMA_CHAN_DST_PTR, ch_regs->dst_ptr); - tdc_write(tdc, TEGRA_GPCDMA_CHAN_HIGH_ADDR_PTR, ch_regs->high_addr_ptr); - tdc_write(tdc, TEGRA_GPCDMA_CHAN_FIXED_PATTERN, ch_regs->fixed_pattern); - tdc_write(tdc, TEGRA_GPCDMA_CHAN_MMIOSEQ, ch_regs->mmio_seq); - tdc_write(tdc, TEGRA_GPCDMA_CHAN_MCSEQ, ch_regs->mc_seq); - tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSR, ch_regs->csr); + tdc_write(tdc, tdc->regs->wcount, sg_req->wcount); + tdc_write(tdc, tdc->regs->csr, 0); + tdc_write(tdc, tdc->regs->src, sg_req->src); + tdc_write(tdc, tdc->regs->dst, sg_req->dst); + tdc_write(tdc, tdc->regs->high_addr, sg_req->high_addr); + tdc_write(tdc, tdc->regs->fixed_pattern, sg_req->fixed_pattern); + tdc_write(tdc, tdc->regs->mmio_seq, sg_req->mmio_seq); + tdc_write(tdc, tdc->regs->mc_seq, sg_req->mc_seq); + tdc_write(tdc, tdc->regs->csr, sg_req->csr); /* Start DMA */ - tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSR, - ch_regs->csr | TEGRA_GPCDMA_CSR_ENB); + tdc_write(tdc, tdc->regs->csr, + sg_req->csr | TEGRA_GPCDMA_CSR_ENB); } static void tegra_dma_xfer_complete(struct tegra_dma_channel *tdc) @@ -601,19 +583,19 @@ static irqreturn_t tegra_dma_isr(int irq, void *dev_id) u32 status; /* Check channel error status register */ - status = tdc_read(tdc, TEGRA_GPCDMA_CHAN_ERR_STATUS); + status = tdc_read(tdc, tdc->regs->err_status); if (status) { tegra_dma_chan_decode_error(tdc, status); tegra_dma_dump_chan_regs(tdc); - tdc_write(tdc, TEGRA_GPCDMA_CHAN_ERR_STATUS, 0xFFFFFFFF); + tdc_write(tdc, tdc->regs->err_status, 0xFFFFFFFF); } spin_lock(&tdc->vc.lock); - status = tdc_read(tdc, TEGRA_GPCDMA_CHAN_STATUS); + status = tdc_read(tdc, tdc->regs->status); if (!(status & TEGRA_GPCDMA_STATUS_ISE_EOC)) goto irq_done; - tdc_write(tdc, TEGRA_GPCDMA_CHAN_STATUS, + tdc_write(tdc, tdc->regs->status, TEGRA_GPCDMA_STATUS_ISE_EOC); if (!dma_desc) @@ -673,10 +655,10 @@ static int tegra_dma_stop_client(struct tegra_dma_channel *tdc) * to stop DMA engine from starting any more bursts for * the given client and wait for in flight bursts to complete */ - csr = tdc_read(tdc, TEGRA_GPCDMA_CHAN_CSR); + csr = tdc_read(tdc, tdc->regs->csr); csr &= ~(TEGRA_GPCDMA_CSR_REQ_SEL_MASK); csr |= TEGRA_GPCDMA_CSR_REQ_SEL_UNUSED; - tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSR, csr); + tdc_write(tdc, tdc->regs->csr, csr); /* Wait for in flight data transfer to finish */ udelay(TEGRA_GPCDMA_BURST_COMPLETE_TIME); @@ -687,7 +669,7 @@ static int tegra_dma_stop_client(struct tegra_dma_channel *tdc) ret = readl_relaxed_poll_timeout_atomic(tdc->tdma->base_addr + tdc->chan_base_offset + - TEGRA_GPCDMA_CHAN_STATUS, + tdc->regs->status, status, !(status & (TEGRA_GPCDMA_STATUS_CHANNEL_TX | TEGRA_GPCDMA_STATUS_CHANNEL_RX)), @@ -739,14 +721,14 @@ static int tegra_dma_get_residual(struct tegra_dma_channel *tdc) unsigned int bytes_xfer, residual; u32 wcount = 0, status; - wcount = tdc_read(tdc, TEGRA_GPCDMA_CHAN_XFER_COUNT); + wcount = tdc_read(tdc, tdc->regs->wxfer); /* * Set wcount = 0 if EOC bit is set. The transfer would have * already completed and the CHAN_XFER_COUNT could have updated * for the next transfer, specifically in case of cyclic transfers. */ - status = tdc_read(tdc, TEGRA_GPCDMA_CHAN_STATUS); + status = tdc_read(tdc, tdc->regs->status); if (status & TEGRA_GPCDMA_STATUS_ISE_EOC) wcount = 0; @@ -893,7 +875,7 @@ tegra_dma_prep_dma_memset(struct dma_chan *dc, dma_addr_t dest, int value, /* Configure default priority weight for the channel */ csr |= FIELD_PREP(TEGRA_GPCDMA_CSR_WEIGHT, 1); - mc_seq = tdc_read(tdc, TEGRA_GPCDMA_CHAN_MCSEQ); + mc_seq = tdc_read(tdc, tdc->regs->mc_seq); /* retain stream-id and clean rest */ mc_seq &= TEGRA_GPCDMA_MCSEQ_STREAM_ID0_MASK; @@ -916,16 +898,16 @@ tegra_dma_prep_dma_memset(struct dma_chan *dc, dma_addr_t dest, int value, dma_desc->sg_count = 1; sg_req = dma_desc->sg_req; - sg_req[0].ch_regs.src_ptr = 0; - sg_req[0].ch_regs.dst_ptr = dest; - sg_req[0].ch_regs.high_addr_ptr = + sg_req[0].src = 0; + sg_req[0].dst = dest; + sg_req[0].high_addr = FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (dest >> 32)); - sg_req[0].ch_regs.fixed_pattern = value; + sg_req[0].fixed_pattern = value; /* Word count reg takes value as (N +1) words */ - sg_req[0].ch_regs.wcount = ((len - 4) >> 2); - sg_req[0].ch_regs.csr = csr; - sg_req[0].ch_regs.mmio_seq = 0; - sg_req[0].ch_regs.mc_seq = mc_seq; + sg_req[0].wcount = ((len - 4) >> 2); + sg_req[0].csr = csr; + sg_req[0].mmio_seq = 0; + sg_req[0].mc_seq = mc_seq; sg_req[0].len = len; dma_desc->cyclic = false; @@ -961,7 +943,7 @@ tegra_dma_prep_dma_memcpy(struct dma_chan *dc, dma_addr_t dest, /* Configure default priority weight for the channel */ csr |= FIELD_PREP(TEGRA_GPCDMA_CSR_WEIGHT, 1); - mc_seq = tdc_read(tdc, TEGRA_GPCDMA_CHAN_MCSEQ); + mc_seq = tdc_read(tdc, tdc->regs->mc_seq); /* retain stream-id and clean rest */ mc_seq &= (TEGRA_GPCDMA_MCSEQ_STREAM_ID0_MASK) | (TEGRA_GPCDMA_MCSEQ_STREAM_ID1_MASK); @@ -985,17 +967,17 @@ tegra_dma_prep_dma_memcpy(struct dma_chan *dc, dma_addr_t dest, dma_desc->sg_count = 1; sg_req = dma_desc->sg_req; - sg_req[0].ch_regs.src_ptr = src; - sg_req[0].ch_regs.dst_ptr = dest; - sg_req[0].ch_regs.high_addr_ptr = + sg_req[0].src = src; + sg_req[0].dst = dest; + sg_req[0].high_addr = FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR, (src >> 32)); - sg_req[0].ch_regs.high_addr_ptr |= + sg_req[0].high_addr |= FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (dest >> 32)); /* Word count reg takes value as (N +1) words */ - sg_req[0].ch_regs.wcount = ((len - 4) >> 2); - sg_req[0].ch_regs.csr = csr; - sg_req[0].ch_regs.mmio_seq = 0; - sg_req[0].ch_regs.mc_seq = mc_seq; + sg_req[0].wcount = ((len - 4) >> 2); + sg_req[0].csr = csr; + sg_req[0].mmio_seq = 0; + sg_req[0].mc_seq = mc_seq; sg_req[0].len = len; dma_desc->cyclic = false; @@ -1049,7 +1031,7 @@ tegra_dma_prep_slave_sg(struct dma_chan *dc, struct scatterlist *sgl, if (flags & DMA_PREP_INTERRUPT) csr |= TEGRA_GPCDMA_CSR_IE_EOC; - mc_seq = tdc_read(tdc, TEGRA_GPCDMA_CHAN_MCSEQ); + mc_seq = tdc_read(tdc, tdc->regs->mc_seq); /* retain stream-id and clean rest */ mc_seq &= TEGRA_GPCDMA_MCSEQ_STREAM_ID0_MASK; @@ -1096,14 +1078,14 @@ tegra_dma_prep_slave_sg(struct dma_chan *dc, struct scatterlist *sgl, dma_desc->bytes_req += len; if (direction == DMA_MEM_TO_DEV) { - sg_req[i].ch_regs.src_ptr = mem; - sg_req[i].ch_regs.dst_ptr = apb_ptr; - sg_req[i].ch_regs.high_addr_ptr = + sg_req[i].src = mem; + sg_req[i].dst = apb_ptr; + sg_req[i].high_addr = FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR, (mem >> 32)); } else if (direction == DMA_DEV_TO_MEM) { - sg_req[i].ch_regs.src_ptr = apb_ptr; - sg_req[i].ch_regs.dst_ptr = mem; - sg_req[i].ch_regs.high_addr_ptr = + sg_req[i].src = apb_ptr; + sg_req[i].dst = mem; + sg_req[i].high_addr = FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (mem >> 32)); } @@ -1111,10 +1093,10 @@ tegra_dma_prep_slave_sg(struct dma_chan *dc, struct scatterlist *sgl, * Word count register takes input in words. Writing a value * of N into word count register means a req of (N+1) words. */ - sg_req[i].ch_regs.wcount = ((len - 4) >> 2); - sg_req[i].ch_regs.csr = csr; - sg_req[i].ch_regs.mmio_seq = mmio_seq; - sg_req[i].ch_regs.mc_seq = mc_seq; + sg_req[i].wcount = ((len - 4) >> 2); + sg_req[i].csr = csr; + sg_req[i].mmio_seq = mmio_seq; + sg_req[i].mc_seq = mc_seq; sg_req[i].len = len; } @@ -1186,7 +1168,7 @@ tegra_dma_prep_dma_cyclic(struct dma_chan *dc, dma_addr_t buf_addr, size_t buf_l mmio_seq |= FIELD_PREP(TEGRA_GPCDMA_MMIOSEQ_WRAP_WORD, 1); - mc_seq = tdc_read(tdc, TEGRA_GPCDMA_CHAN_MCSEQ); + mc_seq = tdc_read(tdc, tdc->regs->mc_seq); /* retain stream-id and clean rest */ mc_seq &= TEGRA_GPCDMA_MCSEQ_STREAM_ID0_MASK; @@ -1217,24 +1199,24 @@ tegra_dma_prep_dma_cyclic(struct dma_chan *dc, dma_addr_t buf_addr, size_t buf_l for (i = 0; i < period_count; i++) { mmio_seq |= get_burst_size(tdc, burst_size, slave_bw, len); if (direction == DMA_MEM_TO_DEV) { - sg_req[i].ch_regs.src_ptr = mem; - sg_req[i].ch_regs.dst_ptr = apb_ptr; - sg_req[i].ch_regs.high_addr_ptr = + sg_req[i].src = mem; + sg_req[i].dst = apb_ptr; + sg_req[i].high_addr = FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR, (mem >> 32)); } else if (direction == DMA_DEV_TO_MEM) { - sg_req[i].ch_regs.src_ptr = apb_ptr; - sg_req[i].ch_regs.dst_ptr = mem; - sg_req[i].ch_regs.high_addr_ptr = + sg_req[i].src = apb_ptr; + sg_req[i].dst = mem; + sg_req[i].high_addr = FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (mem >> 32)); } /* * Word count register takes input in words. Writing a value * of N into word count register means a req of (N+1) words. */ - sg_req[i].ch_regs.wcount = ((len - 4) >> 2); - sg_req[i].ch_regs.csr = csr; - sg_req[i].ch_regs.mmio_seq = mmio_seq; - sg_req[i].ch_regs.mc_seq = mc_seq; + sg_req[i].wcount = ((len - 4) >> 2); + sg_req[i].csr = csr; + sg_req[i].mmio_seq = mmio_seq; + sg_req[i].mc_seq = mc_seq; sg_req[i].len = len; mem += len; @@ -1304,11 +1286,28 @@ static struct dma_chan *tegra_dma_of_xlate(struct of_phandle_args *dma_spec, return chan; } +static const struct tegra_dma_channel_regs tegra186_reg_offsets = { + .csr = 0x0, + .status = 0x4, + .csre = 0x8, + .src = 0xc, + .dst = 0x10, + .high_addr = 0x14, + .mc_seq = 0x18, + .mmio_seq = 0x1c, + .wcount = 0x20, + .wxfer = 0x24, + .wstatus = 0x28, + .err_status = 0x30, + .fixed_pattern = 0x34, +}; + static const struct tegra_dma_chip_data tegra186_dma_chip_data = { .nr_channels = 32, .channel_reg_size = SZ_64K, .max_dma_count = SZ_1G, .hw_support_pause = false, + .channel_regs = &tegra186_reg_offsets, .terminate = tegra_dma_stop_client, }; @@ -1317,6 +1316,7 @@ static const struct tegra_dma_chip_data tegra194_dma_chip_data = { .channel_reg_size = SZ_64K, .max_dma_count = SZ_1G, .hw_support_pause = true, + .channel_regs = &tegra186_reg_offsets, .terminate = tegra_dma_pause, }; @@ -1325,6 +1325,7 @@ static const struct tegra_dma_chip_data tegra234_dma_chip_data = { .channel_reg_size = SZ_64K, .max_dma_count = SZ_1G, .hw_support_pause = true, + .channel_regs = &tegra186_reg_offsets, .terminate = tegra_dma_pause_noerr, }; @@ -1345,7 +1346,7 @@ MODULE_DEVICE_TABLE(of, tegra_dma_of_match); static int tegra_dma_program_sid(struct tegra_dma_channel *tdc, int stream_id) { - unsigned int reg_val = tdc_read(tdc, TEGRA_GPCDMA_CHAN_MCSEQ); + unsigned int reg_val = tdc_read(tdc, tdc->regs->mc_seq); reg_val &= ~(TEGRA_GPCDMA_MCSEQ_STREAM_ID0_MASK); reg_val &= ~(TEGRA_GPCDMA_MCSEQ_STREAM_ID1_MASK); @@ -1353,7 +1354,7 @@ static int tegra_dma_program_sid(struct tegra_dma_channel *tdc, int stream_id) reg_val |= FIELD_PREP(TEGRA_GPCDMA_MCSEQ_STREAM_ID0_MASK, stream_id); reg_val |= FIELD_PREP(TEGRA_GPCDMA_MCSEQ_STREAM_ID1_MASK, stream_id); - tdc_write(tdc, TEGRA_GPCDMA_CHAN_MCSEQ, reg_val); + tdc_write(tdc, tdc->regs->mc_seq, reg_val); return 0; } @@ -1419,6 +1420,7 @@ static int tegra_dma_probe(struct platform_device *pdev) tdc->chan_base_offset = TEGRA_GPCDMA_CHANNEL_BASE_ADDR_OFFSET + i * cdata->channel_reg_size; snprintf(tdc->name, sizeof(tdc->name), "gpcdma.%d", i); + tdc->regs = cdata->channel_regs; tdc->tdma = tdma; tdc->id = i; tdc->slave_id = -1; From 286632b9bf1cf239482d54b592cc1d5bbd5ec783 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 31 Mar 2026 15:52:59 +0530 Subject: [PATCH 14/62] dmaengine: tegra: Support address width > 39 bits Tegra264 supports address width of 41 bits. Unlike older SoCs which use a common high_addr register for upper address bits, Tegra264 has separate src_high and dst_high registers to accommodate this wider address space. Add an addr_bits property to the device data structure to specify the number of address bits supported on each device and use that to program the appropriate registers. Update the sg_req struct to remove the high_addr field and use dma_addr_t for src and dst to store the complete addresses. Extract the high address bits only when programming the registers. Signed-off-by: Akhil R Reviewed-by: Frank Li Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260331102303.33181-7-akhilrajeev@nvidia.com Signed-off-by: Vinod Koul --- drivers/dma/tegra186-gpc-dma.c | 83 +++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c index b213c4ae07d2..3ac43ad19ed6 100644 --- a/drivers/dma/tegra186-gpc-dma.c +++ b/drivers/dma/tegra186-gpc-dma.c @@ -146,6 +146,7 @@ struct tegra_dma_channel; */ struct tegra_dma_chip_data { bool hw_support_pause; + unsigned int addr_bits; unsigned int nr_channels; unsigned int channel_reg_size; unsigned int max_dma_count; @@ -161,6 +162,8 @@ struct tegra_dma_channel_regs { u32 src; u32 dst; u32 high_addr; + u32 src_high; + u32 dst_high; u32 mc_seq; u32 mmio_seq; u32 wcount; @@ -179,10 +182,9 @@ struct tegra_dma_channel_regs { */ struct tegra_dma_sg_req { unsigned int len; + dma_addr_t src; + dma_addr_t dst; u32 csr; - u32 src; - u32 dst; - u32 high_addr; u32 mc_seq; u32 mmio_seq; u32 wcount; @@ -266,6 +268,25 @@ static inline struct device *tdc2dev(struct tegra_dma_channel *tdc) return tdc->vc.chan.device->dev; } +static void tegra_dma_program_addr(struct tegra_dma_channel *tdc, + struct tegra_dma_sg_req *sg_req) +{ + tdc_write(tdc, tdc->regs->src, lower_32_bits(sg_req->src)); + tdc_write(tdc, tdc->regs->dst, lower_32_bits(sg_req->dst)); + + if (tdc->tdma->chip_data->addr_bits > 39) { + tdc_write(tdc, tdc->regs->src_high, upper_32_bits(sg_req->src)); + tdc_write(tdc, tdc->regs->dst_high, upper_32_bits(sg_req->dst)); + } else { + u32 src_high = FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR, + upper_32_bits(sg_req->src)); + u32 dst_high = FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, + upper_32_bits(sg_req->dst)); + + tdc_write(tdc, tdc->regs->high_addr, src_high | dst_high); + } +} + static void tegra_dma_dump_chan_regs(struct tegra_dma_channel *tdc) { dev_dbg(tdc2dev(tdc), "DMA Channel %d name %s register dump:\n", @@ -274,10 +295,20 @@ static void tegra_dma_dump_chan_regs(struct tegra_dma_channel *tdc) tdc_read(tdc, tdc->regs->csr), tdc_read(tdc, tdc->regs->status), tdc_read(tdc, tdc->regs->csre)); - dev_dbg(tdc2dev(tdc), "SRC %x DST %x HI ADDR %x\n", - tdc_read(tdc, tdc->regs->src), - tdc_read(tdc, tdc->regs->dst), - tdc_read(tdc, tdc->regs->high_addr)); + + if (tdc->tdma->chip_data->addr_bits > 39) { + dev_dbg(tdc2dev(tdc), "SRC %x SRC HI %x DST %x DST HI %x\n", + tdc_read(tdc, tdc->regs->src), + tdc_read(tdc, tdc->regs->src_high), + tdc_read(tdc, tdc->regs->dst), + tdc_read(tdc, tdc->regs->dst_high)); + } else { + dev_dbg(tdc2dev(tdc), "SRC %x DST %x HI ADDR %x\n", + tdc_read(tdc, tdc->regs->src), + tdc_read(tdc, tdc->regs->dst), + tdc_read(tdc, tdc->regs->high_addr)); + } + dev_dbg(tdc2dev(tdc), "MCSEQ %x IOSEQ %x WCNT %x XFER %x WSTA %x\n", tdc_read(tdc, tdc->regs->mc_seq), tdc_read(tdc, tdc->regs->mmio_seq), @@ -480,9 +511,7 @@ static void tegra_dma_configure_next_sg(struct tegra_dma_channel *tdc) sg_req = &dma_desc->sg_req[dma_desc->sg_idx]; tdc_write(tdc, tdc->regs->wcount, sg_req->wcount); - tdc_write(tdc, tdc->regs->src, sg_req->src); - tdc_write(tdc, tdc->regs->dst, sg_req->dst); - tdc_write(tdc, tdc->regs->high_addr, sg_req->high_addr); + tegra_dma_program_addr(tdc, sg_req); /* Start DMA */ tdc_write(tdc, tdc->regs->csr, @@ -510,11 +539,9 @@ static void tegra_dma_start(struct tegra_dma_channel *tdc) sg_req = &dma_desc->sg_req[dma_desc->sg_idx]; + tegra_dma_program_addr(tdc, sg_req); tdc_write(tdc, tdc->regs->wcount, sg_req->wcount); tdc_write(tdc, tdc->regs->csr, 0); - tdc_write(tdc, tdc->regs->src, sg_req->src); - tdc_write(tdc, tdc->regs->dst, sg_req->dst); - tdc_write(tdc, tdc->regs->high_addr, sg_req->high_addr); tdc_write(tdc, tdc->regs->fixed_pattern, sg_req->fixed_pattern); tdc_write(tdc, tdc->regs->mmio_seq, sg_req->mmio_seq); tdc_write(tdc, tdc->regs->mc_seq, sg_req->mc_seq); @@ -819,7 +846,7 @@ static unsigned int get_burst_size(struct tegra_dma_channel *tdc, static int get_transfer_param(struct tegra_dma_channel *tdc, enum dma_transfer_direction direction, - u32 *apb_addr, + dma_addr_t *apb_addr, u32 *mmio_seq, u32 *csr, unsigned int *burst_size, @@ -897,11 +924,9 @@ tegra_dma_prep_dma_memset(struct dma_chan *dc, dma_addr_t dest, int value, dma_desc->bytes_req = len; dma_desc->sg_count = 1; sg_req = dma_desc->sg_req; - sg_req[0].src = 0; sg_req[0].dst = dest; - sg_req[0].high_addr = - FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (dest >> 32)); + sg_req[0].fixed_pattern = value; /* Word count reg takes value as (N +1) words */ sg_req[0].wcount = ((len - 4) >> 2); @@ -969,10 +994,7 @@ tegra_dma_prep_dma_memcpy(struct dma_chan *dc, dma_addr_t dest, sg_req[0].src = src; sg_req[0].dst = dest; - sg_req[0].high_addr = - FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR, (src >> 32)); - sg_req[0].high_addr |= - FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (dest >> 32)); + /* Word count reg takes value as (N +1) words */ sg_req[0].wcount = ((len - 4) >> 2); sg_req[0].csr = csr; @@ -992,7 +1014,8 @@ tegra_dma_prep_slave_sg(struct dma_chan *dc, struct scatterlist *sgl, struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc); unsigned int max_dma_count = tdc->tdma->chip_data->max_dma_count; enum dma_slave_buswidth slave_bw = DMA_SLAVE_BUSWIDTH_UNDEFINED; - u32 csr, mc_seq, apb_ptr = 0, mmio_seq = 0; + u32 csr, mc_seq, mmio_seq = 0; + dma_addr_t apb_ptr = 0; struct tegra_dma_sg_req *sg_req; struct tegra_dma_desc *dma_desc; struct scatterlist *sg; @@ -1080,13 +1103,9 @@ tegra_dma_prep_slave_sg(struct dma_chan *dc, struct scatterlist *sgl, if (direction == DMA_MEM_TO_DEV) { sg_req[i].src = mem; sg_req[i].dst = apb_ptr; - sg_req[i].high_addr = - FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR, (mem >> 32)); } else if (direction == DMA_DEV_TO_MEM) { sg_req[i].src = apb_ptr; sg_req[i].dst = mem; - sg_req[i].high_addr = - FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (mem >> 32)); } /* @@ -1110,7 +1129,8 @@ tegra_dma_prep_dma_cyclic(struct dma_chan *dc, dma_addr_t buf_addr, size_t buf_l unsigned long flags) { enum dma_slave_buswidth slave_bw = DMA_SLAVE_BUSWIDTH_UNDEFINED; - u32 csr, mc_seq, apb_ptr = 0, mmio_seq = 0, burst_size; + u32 csr, mc_seq, mmio_seq = 0, burst_size; + dma_addr_t apb_ptr = 0; unsigned int max_dma_count, len, period_count, i; struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc); struct tegra_dma_desc *dma_desc; @@ -1201,13 +1221,9 @@ tegra_dma_prep_dma_cyclic(struct dma_chan *dc, dma_addr_t buf_addr, size_t buf_l if (direction == DMA_MEM_TO_DEV) { sg_req[i].src = mem; sg_req[i].dst = apb_ptr; - sg_req[i].high_addr = - FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR, (mem >> 32)); } else if (direction == DMA_DEV_TO_MEM) { sg_req[i].src = apb_ptr; sg_req[i].dst = mem; - sg_req[i].high_addr = - FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (mem >> 32)); } /* * Word count register takes input in words. Writing a value @@ -1304,6 +1320,7 @@ static const struct tegra_dma_channel_regs tegra186_reg_offsets = { static const struct tegra_dma_chip_data tegra186_dma_chip_data = { .nr_channels = 32, + .addr_bits = 39, .channel_reg_size = SZ_64K, .max_dma_count = SZ_1G, .hw_support_pause = false, @@ -1313,6 +1330,7 @@ static const struct tegra_dma_chip_data tegra186_dma_chip_data = { static const struct tegra_dma_chip_data tegra194_dma_chip_data = { .nr_channels = 32, + .addr_bits = 39, .channel_reg_size = SZ_64K, .max_dma_count = SZ_1G, .hw_support_pause = true, @@ -1322,6 +1340,7 @@ static const struct tegra_dma_chip_data tegra194_dma_chip_data = { static const struct tegra_dma_chip_data tegra234_dma_chip_data = { .nr_channels = 32, + .addr_bits = 39, .channel_reg_size = SZ_64K, .max_dma_count = SZ_1G, .hw_support_pause = true, @@ -1433,6 +1452,8 @@ static int tegra_dma_probe(struct platform_device *pdev) tdc->stream_id = stream_id; } + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(cdata->addr_bits)); + dma_cap_set(DMA_SLAVE, tdma->dma_dev.cap_mask); dma_cap_set(DMA_PRIVATE, tdma->dma_dev.cap_mask); dma_cap_set(DMA_MEMCPY, tdma->dma_dev.cap_mask); From 45921a3282d642038d92737fab24107522324bd4 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 31 Mar 2026 15:53:00 +0530 Subject: [PATCH 15/62] dmaengine: tegra: Use managed DMA controller registration Switch to managed registration in probe. This simplifies the error paths in the probe and also removes the requirement of the driver remove function. Signed-off-by: Akhil R Suggested-by: Frank Li Acked-by: Thierry Reding Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260331102303.33181-8-akhilrajeev@nvidia.com Signed-off-by: Vinod Koul --- drivers/dma/tegra186-gpc-dma.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c index 3ac43ad19ed6..9bea2ffb3b9e 100644 --- a/drivers/dma/tegra186-gpc-dma.c +++ b/drivers/dma/tegra186-gpc-dma.c @@ -1483,37 +1483,27 @@ static int tegra_dma_probe(struct platform_device *pdev) tdma->dma_dev.device_synchronize = tegra_dma_chan_synchronize; tdma->dma_dev.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; - ret = dma_async_device_register(&tdma->dma_dev); + ret = dmaenginem_async_device_register(&tdma->dma_dev); if (ret < 0) { dev_err_probe(&pdev->dev, ret, "GPC DMA driver registration failed\n"); return ret; } - ret = of_dma_controller_register(pdev->dev.of_node, - tegra_dma_of_xlate, tdma); + ret = devm_of_dma_controller_register(&pdev->dev, pdev->dev.of_node, + tegra_dma_of_xlate, tdma); if (ret < 0) { dev_err_probe(&pdev->dev, ret, "GPC DMA OF registration failed\n"); - - dma_async_device_unregister(&tdma->dma_dev); return ret; } - dev_info(&pdev->dev, "GPC DMA driver register %lu channels\n", + dev_info(&pdev->dev, "GPC DMA driver registered %lu channels\n", hweight_long(tdma->chan_mask)); return 0; } -static void tegra_dma_remove(struct platform_device *pdev) -{ - struct tegra_dma *tdma = platform_get_drvdata(pdev); - - of_dma_controller_free(pdev->dev.of_node); - dma_async_device_unregister(&tdma->dma_dev); -} - static int __maybe_unused tegra_dma_pm_suspend(struct device *dev) { struct tegra_dma *tdma = dev_get_drvdata(dev); @@ -1564,7 +1554,6 @@ static struct platform_driver tegra_dma_driver = { .of_match_table = tegra_dma_of_match, }, .probe = tegra_dma_probe, - .remove = tegra_dma_remove, }; module_platform_driver(tegra_dma_driver); From 321c0a15f027b83b20ed37717191a2187c9e2eb7 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 31 Mar 2026 15:53:01 +0530 Subject: [PATCH 16/62] dmaengine: tegra: Use iommu-map for stream ID Use 'iommu-map', when provided, to get the stream ID to be programmed for each channel. Iterate over the channels registered and configure each channel device separately using of_dma_configure_id() to allow it to use a separate IOMMU domain for the transfer. However, do this in a second loop since the first loop populates the DMA device channels list and async_device_register() registers the channels. Both are prerequisites for using the channel device in the next loop. Channels will continue to use the same global stream ID if the 'iommu-map' property is not present in the device tree. Signed-off-by: Akhil R Reviewed-by: Frank Li Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260331102303.33181-9-akhilrajeev@nvidia.com Signed-off-by: Vinod Koul --- drivers/dma/tegra186-gpc-dma.c | 53 ++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c index 9bea2ffb3b9e..939eb5462d0a 100644 --- a/drivers/dma/tegra186-gpc-dma.c +++ b/drivers/dma/tegra186-gpc-dma.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1380,9 +1381,13 @@ static int tegra_dma_program_sid(struct tegra_dma_channel *tdc, int stream_id) static int tegra_dma_probe(struct platform_device *pdev) { const struct tegra_dma_chip_data *cdata = NULL; + struct tegra_dma_channel *tdc; + struct tegra_dma *tdma; + struct dma_chan *chan; + struct device *chdev; + bool use_iommu_map = false; unsigned int i; u32 stream_id; - struct tegra_dma *tdma; int ret; cdata = of_device_get_match_data(&pdev->dev); @@ -1410,9 +1415,10 @@ static int tegra_dma_probe(struct platform_device *pdev) tdma->dma_dev.dev = &pdev->dev; - if (!tegra_dev_iommu_get_stream_id(&pdev->dev, &stream_id)) { - dev_err(&pdev->dev, "Missing iommu stream-id\n"); - return -EINVAL; + use_iommu_map = of_property_present(pdev->dev.of_node, "iommu-map"); + if (!use_iommu_map) { + if (!tegra_dev_iommu_get_stream_id(&pdev->dev, &stream_id)) + return dev_err_probe(&pdev->dev, -EINVAL, "Missing iommu stream-id\n"); } ret = device_property_read_u32(&pdev->dev, "dma-channel-mask", @@ -1424,9 +1430,10 @@ static int tegra_dma_probe(struct platform_device *pdev) tdma->chan_mask = TEGRA_GPCDMA_DEFAULT_CHANNEL_MASK; } + /* Initialize vchan for each channel and populate the channels list */ INIT_LIST_HEAD(&tdma->dma_dev.channels); for (i = 0; i < cdata->nr_channels; i++) { - struct tegra_dma_channel *tdc = &tdma->channels[i]; + tdc = &tdma->channels[i]; /* Check for channel mask */ if (!(tdma->chan_mask & BIT(i))) @@ -1446,10 +1453,6 @@ static int tegra_dma_probe(struct platform_device *pdev) vchan_init(&tdc->vc, &tdma->dma_dev); tdc->vc.desc_free = tegra_dma_desc_free; - - /* program stream-id for this channel */ - tegra_dma_program_sid(tdc, stream_id); - tdc->stream_id = stream_id; } dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(cdata->addr_bits)); @@ -1483,6 +1486,7 @@ static int tegra_dma_probe(struct platform_device *pdev) tdma->dma_dev.device_synchronize = tegra_dma_chan_synchronize; tdma->dma_dev.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; + /* Register the DMA device and the channels */ ret = dmaenginem_async_device_register(&tdma->dma_dev); if (ret < 0) { dev_err_probe(&pdev->dev, ret, @@ -1490,6 +1494,37 @@ static int tegra_dma_probe(struct platform_device *pdev) return ret; } + /* + * Configure stream ID for each channel from the channels registered + * above. This is done in a separate iteration to ensure that only + * the channels available and registered for the DMA device are used. + */ + list_for_each_entry(chan, &tdma->dma_dev.channels, device_node) { + chdev = &chan->dev->device; + tdc = to_tegra_dma_chan(chan); + + if (use_iommu_map) { + chdev->bus = pdev->dev.bus; + dma_coerce_mask_and_coherent(chdev, DMA_BIT_MASK(cdata->addr_bits)); + + ret = of_dma_configure_id(chdev, pdev->dev.of_node, + true, &tdc->id); + if (ret) + return dev_err_probe(chdev, ret, + "Failed to configure IOMMU for channel %d\n", tdc->id); + + if (!tegra_dev_iommu_get_stream_id(chdev, &stream_id)) + return dev_err_probe(chdev, -EINVAL, + "Failed to get stream ID for channel %d\n", tdc->id); + + chan->dev->chan_dma_dev = true; + } + + /* program stream-id for this channel */ + tegra_dma_program_sid(tdc, stream_id); + tdc->stream_id = stream_id; + } + ret = devm_of_dma_controller_register(&pdev->dev, pdev->dev.of_node, tegra_dma_of_xlate, tdma); if (ret < 0) { From b236b7973808195fd9c471492bee0041148b823e Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 31 Mar 2026 15:53:02 +0530 Subject: [PATCH 17/62] dmaengine: tegra: Add Tegra264 support Add compatible and chip data to support GPCDMA in Tegra264, which has differences in register layout and address bits compared to previous versions. Signed-off-by: Akhil R Reviewed-by: Frank Li Acked-by: Thierry Reding Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260331102303.33181-10-akhilrajeev@nvidia.com Signed-off-by: Vinod Koul --- drivers/dma/tegra186-gpc-dma.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c index 939eb5462d0a..c364c278a8b0 100644 --- a/drivers/dma/tegra186-gpc-dma.c +++ b/drivers/dma/tegra186-gpc-dma.c @@ -1319,6 +1319,23 @@ static const struct tegra_dma_channel_regs tegra186_reg_offsets = { .fixed_pattern = 0x34, }; +static const struct tegra_dma_channel_regs tegra264_reg_offsets = { + .csr = 0x0, + .status = 0x4, + .csre = 0x8, + .src = 0xc, + .dst = 0x10, + .src_high = 0x14, + .dst_high = 0x18, + .mc_seq = 0x1c, + .mmio_seq = 0x20, + .wcount = 0x24, + .wxfer = 0x28, + .wstatus = 0x2c, + .err_status = 0x34, + .fixed_pattern = 0x38, +}; + static const struct tegra_dma_chip_data tegra186_dma_chip_data = { .nr_channels = 32, .addr_bits = 39, @@ -1349,6 +1366,16 @@ static const struct tegra_dma_chip_data tegra234_dma_chip_data = { .terminate = tegra_dma_pause_noerr, }; +static const struct tegra_dma_chip_data tegra264_dma_chip_data = { + .nr_channels = 32, + .addr_bits = 41, + .channel_reg_size = SZ_64K, + .max_dma_count = SZ_1G, + .hw_support_pause = true, + .channel_regs = &tegra264_reg_offsets, + .terminate = tegra_dma_pause_noerr, +}; + static const struct of_device_id tegra_dma_of_match[] = { { .compatible = "nvidia,tegra186-gpcdma", @@ -1359,6 +1386,9 @@ static const struct of_device_id tegra_dma_of_match[] = { }, { .compatible = "nvidia,tegra234-gpcdma", .data = &tegra234_dma_chip_data, + }, { + .compatible = "nvidia,tegra264-gpcdma", + .data = &tegra264_dma_chip_data, }, { }, }; From 731712403ddb39d1a76a11abf339a0615bc85de7 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:46:53 +0300 Subject: [PATCH 18/62] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up Once the interrupt is requested, the interrupt handler may run immediately. Since the IRQ handler can access channel->ch_base, which is initialized only after requesting the IRQ, this may lead to invalid memory access. Likewise, the IRQ thread may access uninitialized data (the ld_free, ld_queue, and ld_active lists), which may also lead to issues. Request the interrupts only after everything is set up. To keep the error path simpler, use dmam_alloc_coherent() instead of dma_alloc_coherent(). Fixes: 5000d37042a6 ("dmaengine: sh: Add DMAC driver for RZ/G2L SoC") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-2-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 88 +++++++++++++++------------------------- 1 file changed, 33 insertions(+), 55 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 625ff29024de..9f206a33dcc6 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -981,25 +981,6 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, channel->index = index; channel->mid_rid = -EINVAL; - /* Request the channel interrupt. */ - scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index); - irq = platform_get_irq_byname(pdev, pdev_irqname); - if (irq < 0) - return irq; - - irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u", - dev_name(dmac->dev), index); - if (!irqname) - return -ENOMEM; - - ret = devm_request_threaded_irq(dmac->dev, irq, rz_dmac_irq_handler, - rz_dmac_irq_handler_thread, 0, - irqname, channel); - if (ret) { - dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", irq, ret); - return ret; - } - /* Set io base address for each channel */ if (index < 8) { channel->ch_base = dmac->base + CHANNEL_0_7_OFFSET + @@ -1012,9 +993,9 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, } /* Allocate descriptors */ - lmdesc = dma_alloc_coherent(&pdev->dev, - sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, - &channel->lmdesc.base_dma, GFP_KERNEL); + lmdesc = dmam_alloc_coherent(&pdev->dev, + sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, + &channel->lmdesc.base_dma, GFP_KERNEL); if (!lmdesc) { dev_err(&pdev->dev, "Can't allocate memory (lmdesc)\n"); return -ENOMEM; @@ -1030,7 +1011,24 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, INIT_LIST_HEAD(&channel->ld_free); INIT_LIST_HEAD(&channel->ld_active); - return 0; + /* Request the channel interrupt. */ + scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index); + irq = platform_get_irq_byname(pdev, pdev_irqname); + if (irq < 0) + return irq; + + irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u", + dev_name(dmac->dev), index); + if (!irqname) + return -ENOMEM; + + ret = devm_request_threaded_irq(dmac->dev, irq, rz_dmac_irq_handler, + rz_dmac_irq_handler_thread, 0, + irqname, channel); + if (ret) + dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", irq, ret); + + return ret; } static void rz_dmac_put_device(void *_dev) @@ -1099,7 +1097,6 @@ static int rz_dmac_probe(struct platform_device *pdev) const char *irqname = "error"; struct dma_device *engine; struct rz_dmac *dmac; - int channel_num; int ret; int irq; u8 i; @@ -1132,18 +1129,6 @@ static int rz_dmac_probe(struct platform_device *pdev) return PTR_ERR(dmac->ext_base); } - /* Register interrupt handler for error */ - 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. */ INIT_LIST_HEAD(&dmac->engine.channels); @@ -1169,6 +1154,18 @@ static int rz_dmac_probe(struct platform_device *pdev) goto err; } + /* Register interrupt handler for error */ + 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); + goto err; + } + } + /* Register the DMAC as a DMA provider for DT. */ ret = of_dma_controller_register(pdev->dev.of_node, rz_dmac_of_xlate, NULL); @@ -1210,16 +1207,6 @@ static int rz_dmac_probe(struct platform_device *pdev) dma_register_err: of_dma_controller_free(pdev->dev.of_node); err: - channel_num = i ? i - 1 : 0; - for (i = 0; i < channel_num; i++) { - struct rz_dmac_chan *channel = &dmac->channels[i]; - - dma_free_coherent(&pdev->dev, - sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, - channel->lmdesc.base, - channel->lmdesc.base_dma); - } - reset_control_assert(dmac->rstc); err_pm_runtime_put: pm_runtime_put(&pdev->dev); @@ -1232,18 +1219,9 @@ static int rz_dmac_probe(struct platform_device *pdev) static void rz_dmac_remove(struct platform_device *pdev) { struct rz_dmac *dmac = platform_get_drvdata(pdev); - unsigned int i; dma_async_device_unregister(&dmac->engine); of_dma_controller_free(pdev->dev.of_node); - for (i = 0; i < dmac->n_channels; i++) { - struct rz_dmac_chan *channel = &dmac->channels[i]; - - dma_free_coherent(&pdev->dev, - sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, - channel->lmdesc.base, - channel->lmdesc.base_dma); - } reset_control_assert(dmac->rstc); pm_runtime_put(&pdev->dev); pm_runtime_disable(&pdev->dev); From 5fbf3a2a3b96ef5810e6e0fbc601f82067629bc5 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:46:54 +0300 Subject: [PATCH 19/62] dmaengine: sh: rz-dmac: Fix incorrect NULL check for list_first_entry() list_first_entry() does not return NULL when the list is empty, making the existing NULL check invalid. Use list_first_entry_or_null() instead. Fixes: 21323b118c16 ("dmaengine: sh: rz-dmac: Add device_tx_status() callback") Cc: stable@vger.kernel.org Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-3-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 9f206a33dcc6..6d80cb668957 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -723,8 +723,8 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel, u32 crla, crtb, i; /* Get current processing virtual descriptor */ - current_desc = list_first_entry(&channel->ld_active, - struct rz_dmac_desc, node); + current_desc = list_first_entry_or_null(&channel->ld_active, + struct rz_dmac_desc, node); if (!current_desc) return 0; From 89975baaa9ea2490b75d69842561a32ca888b7e5 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:46:55 +0300 Subject: [PATCH 20/62] dmaengine: sh: rz-dmac: Use list_first_entry_or_null() Use list_first_entry_or_null() instead of open-coding it with a list_empty() check and list_first_entry(). This simplifies the code. Reviewed-by: Frank Li Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-4-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 6d80cb668957..1717b407ab9e 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -503,11 +503,10 @@ rz_dmac_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src, __func__, channel->index, &src, &dest, len); scoped_guard(spinlock_irqsave, &channel->vc.lock) { - if (list_empty(&channel->ld_free)) + desc = list_first_entry_or_null(&channel->ld_free, struct rz_dmac_desc, node); + if (!desc) return NULL; - desc = list_first_entry(&channel->ld_free, struct rz_dmac_desc, node); - desc->type = RZ_DMAC_DESC_MEMCPY; desc->src = src; desc->dest = dest; @@ -533,11 +532,10 @@ rz_dmac_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, int i = 0; scoped_guard(spinlock_irqsave, &channel->vc.lock) { - if (list_empty(&channel->ld_free)) + desc = list_first_entry_or_null(&channel->ld_free, struct rz_dmac_desc, node); + if (!desc) return NULL; - desc = list_first_entry(&channel->ld_free, struct rz_dmac_desc, node); - for_each_sg(sgl, sg, sg_len, i) dma_length += sg_dma_len(sg); From 38d4d021228386b8e3fbef2bca5f1e91eacd4fe6 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:46:56 +0300 Subject: [PATCH 21/62] dmaengine: sh: rz-dmac: Use rz_dmac_disable_hw() Use rz_dmac_disable_hw() instead of open coding it. This unifies the code and prepares it for the addition of suspend to RAM and cyclic DMA. The rz_dmac_disable_hw() from rz_dmac_chan_probe() was moved after vchan_init() as it initializes the channel->vc.chan.device used in rz_dmac_disable_hw(). Reviewed-by: Frank Li Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-5-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 1717b407ab9e..40ddf534c094 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -873,7 +873,7 @@ static void rz_dmac_irq_handle_channel(struct rz_dmac_chan *channel) channel->index, chstat); scoped_guard(spinlock_irqsave, &channel->vc.lock) - rz_dmac_ch_writel(channel, CHCTRL_DEFAULT, CHCTRL, 1); + rz_dmac_disable_hw(channel); return; } @@ -1000,15 +1000,15 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, } rz_lmdesc_setup(channel, lmdesc); - /* Initialize register for each channel */ - rz_dmac_ch_writel(channel, CHCTRL_DEFAULT, CHCTRL, 1); - channel->vc.desc_free = rz_dmac_virt_desc_free; vchan_init(&channel->vc, &dmac->engine); INIT_LIST_HEAD(&channel->ld_queue); INIT_LIST_HEAD(&channel->ld_free); INIT_LIST_HEAD(&channel->ld_active); + /* Initialize register for each channel */ + rz_dmac_disable_hw(channel); + /* Request the channel interrupt. */ scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index); irq = platform_get_irq_byname(pdev, pdev_irqname); From 32a69f1487819766d2084ed32b1350b18f971c10 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:46:57 +0300 Subject: [PATCH 22/62] dmaengine: sh: rz-dmac: Add helper to compute the lmdesc address Add a the rz_dmac_lmdesc_addr() helper function to compute the lmdesc address, to make the code easier to understand. The helper will be used in subsequent patches. Reviewed-by: Frank Li Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-6-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 40ddf534c094..c48858b68dee 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -259,6 +259,12 @@ static void rz_lmdesc_setup(struct rz_dmac_chan *channel, * Descriptors preparation */ +static u32 rz_dmac_lmdesc_addr(struct rz_dmac_chan *channel, struct rz_lmdesc *lmdesc) +{ + return channel->lmdesc.base_dma + + (sizeof(struct rz_lmdesc) * (lmdesc - channel->lmdesc.base)); +} + static void rz_dmac_lmdesc_recycle(struct rz_dmac_chan *channel) { struct rz_lmdesc *lmdesc = channel->lmdesc.head; @@ -284,9 +290,7 @@ static void rz_dmac_enable_hw(struct rz_dmac_chan *channel) rz_dmac_lmdesc_recycle(channel); - nxla = channel->lmdesc.base_dma + - (sizeof(struct rz_lmdesc) * (channel->lmdesc.head - - channel->lmdesc.base)); + nxla = rz_dmac_lmdesc_addr(channel, channel->lmdesc.head); chstat = rz_dmac_ch_readl(channel, CHSTAT, 1); if (!(chstat & CHSTAT_EN)) { From e21aa306e82067457f2297ae56af4c91db86c59a Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:46:58 +0300 Subject: [PATCH 23/62] dmaengine: sh: rz-dmac: Save the start LM descriptor Save the start LM descriptor to avoid starting from the beginning of the channel's LM descriptor list in rz_dmac_calculate_residue_bytes_in_vd(). This avoids unnecessary iterations. Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-7-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index c48858b68dee..d3926ecd63ac 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -58,6 +58,7 @@ struct rz_dmac_desc { /* For slave sg */ struct scatterlist *sg; unsigned int sgcount; + struct rz_lmdesc *start_lmdesc; }; #define to_rz_dmac_desc(d) container_of(d, struct rz_dmac_desc, vd) @@ -343,6 +344,8 @@ static void rz_dmac_prepare_desc_for_memcpy(struct rz_dmac_chan *channel) struct rz_dmac_desc *d = channel->desc; u32 chcfg = CHCFG_MEM_COPY; + d->start_lmdesc = lmdesc; + /* prepare descriptor */ lmdesc->sa = d->src; lmdesc->da = d->dest; @@ -377,6 +380,7 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel) } lmdesc = channel->lmdesc.tail; + d->start_lmdesc = lmdesc; for (i = 0, sg = sgl; i < sg_len; i++, sg = sg_next(sg)) { if (d->direction == DMA_DEV_TO_MEM) { @@ -693,9 +697,10 @@ rz_dmac_get_next_lmdesc(struct rz_lmdesc *base, struct rz_lmdesc *lmdesc) return next; } -static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel, u32 crla) +static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel, + struct rz_dmac_desc *desc, u32 crla) { - struct rz_lmdesc *lmdesc = channel->lmdesc.head; + struct rz_lmdesc *lmdesc = desc->start_lmdesc; struct dma_chan *chan = &channel->vc.chan; struct rz_dmac *dmac = to_rz_dmac(chan->device); u32 residue = 0, i = 0; @@ -794,7 +799,7 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel, * 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); + return crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, current_desc, crla); } static enum dma_status rz_dmac_tx_status(struct dma_chan *chan, From 7a94c109a5def4f0f25705a82ed5870f794ff4ed Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:46:59 +0300 Subject: [PATCH 24/62] dmaengine: sh: rz-dmac: Add helper to check if the channel is enabled Add the rz_dmac_chan_is_enabled() helper to check if a channel is enabled. This helper will be reused in subsequent patches. Reviewed-by: Frank Li Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-8-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index d3926ecd63ac..76bac11c217c 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -279,6 +279,13 @@ static void rz_dmac_lmdesc_recycle(struct rz_dmac_chan *channel) channel->lmdesc.head = lmdesc; } +static bool rz_dmac_chan_is_enabled(struct rz_dmac_chan *channel) +{ + u32 val = rz_dmac_ch_readl(channel, CHSTAT, 1); + + return !!(val & CHSTAT_EN); +} + static void rz_dmac_enable_hw(struct rz_dmac_chan *channel) { struct dma_chan *chan = &channel->vc.chan; @@ -840,8 +847,7 @@ static int rz_dmac_device_pause(struct dma_chan *chan) guard(spinlock_irqsave)(&channel->vc.lock); - val = rz_dmac_ch_readl(channel, CHSTAT, 1); - if (!(val & CHSTAT_EN)) + if (!rz_dmac_chan_is_enabled(channel)) return 0; rz_dmac_ch_writel(channel, CHCTRL_SETSUS, CHCTRL, 1); From 1dddc864dfa844efaf36345eb58b121b2cdffa5f Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:47:00 +0300 Subject: [PATCH 25/62] dmaengine: sh: rz-dmac: Add helper to check if the channel is paused Add the rz_dmac_chan_is_paused() helper to check if the channel is paused. This helper will be reused in subsequent patches. Reviewed-by: Frank Li Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-9-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 76bac11c217c..217657513fa7 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -286,6 +286,13 @@ static bool rz_dmac_chan_is_enabled(struct rz_dmac_chan *channel) return !!(val & CHSTAT_EN); } +static bool rz_dmac_chan_is_paused(struct rz_dmac_chan *channel) +{ + u32 val = rz_dmac_ch_readl(channel, CHSTAT, 1); + + return !!(val & CHSTAT_SUS); +} + static void rz_dmac_enable_hw(struct rz_dmac_chan *channel) { struct dma_chan *chan = &channel->vc.chan; @@ -822,12 +829,9 @@ static enum dma_status rz_dmac_tx_status(struct dma_chan *chan, return status; scoped_guard(spinlock_irqsave, &channel->vc.lock) { - u32 val; - residue = rz_dmac_chan_get_residue(channel, cookie); - val = rz_dmac_ch_readl(channel, CHSTAT, 1); - if (val & CHSTAT_SUS) + if (rz_dmac_chan_is_paused(channel)) status = DMA_PAUSED; } From daa6d4617bee722e83f7d8584416e83b709c958a Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:47:01 +0300 Subject: [PATCH 26/62] dmaengine: sh: rz-dmac: Use virt-dma APIs for channel descriptor processing The driver used a mix of virt-dma APIs and driver specific logic to process descriptors. It maintained three internal queues: ld_free, ld_queue, and ld_active as follows: - ld_free: stores the descriptors pre-allocated at probe time - ld_queue: stores descriptors after they are taken from ld_free and prepared. At the same time, vchan_tx_prep() queues them to vc->desc_allocated. The vc->desc_allocated list is then checked in rz_dmac_issue_pending() and rz_dmac_irq_handler_thread() before starting a new transfer via rz_dmac_xfer_desc(). In turn, rz_dmac_xfer_desc() grabs the next descriptor from vc->desc_issued and submits it for transfer - ld_active: stores the descriptors currently being transferred The interrupt handler moved a completed descriptor to ld_free before invoking its completion callback. Once returned to ld_free, the descriptor can be reused to prepare a new transfer. In theory, this means the descriptor could be re-prepared before its completion callback is called. Commit fully back the driver by the virt-dma APIs. With this, only ld_free need to be kept to track how many free descriptors are available. This is now done as follows: - the prepare stage removes the first descriptor from the ld_free and prepares it - the completion calls for it vc->desc_free() (rz_dmac_virt_desc_free()) which re-adds the descriptor at the end of ld_free With this, the critical areas in prepare callbacks were minimized to only getting the descriptor from the ld_free list. Introduce struct rz_dmac_chan::desc to keep track of the currently transferred descriptor. It is cleared in rz_dmac_terminate_all(), referenced from rz_dmac_issue_pending() to determine whether a new transfer can be started, and from rz_dmac_irq_handler_thread() once a descriptor has completed. Finally, the rz_dmac_device_synchronize() was updated with vchan_synchronize() call to ensure the terminated descriptor is freed and the tasklet is killed. With this, residue computation is also simplified, as it can now be handled entirely through the virt-dma APIs. The spin_lock/unlock operations from rz_dmac_irq_handler_thread() were replaced by guard as the final code after rework is simpler this way. As subsequent commits will set the Link End bit on the last descriptor of a transfer, rz_dmac_enable_hw() is also adjusted as part of the full conversion to virt-dma APIs. It no longer checks the channel enable status itself; instead, its callers verify whether the channel is enabled and whether the previous transfer has completed before starting a new one. Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-10-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 237 +++++++++++++++------------------------ 1 file changed, 88 insertions(+), 149 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 217657513fa7..1f884ec101f8 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -79,8 +79,6 @@ struct rz_dmac_chan { int mid_rid; struct list_head ld_free; - struct list_head ld_queue; - struct list_head ld_active; struct { struct rz_lmdesc *base; @@ -299,7 +297,6 @@ static void rz_dmac_enable_hw(struct rz_dmac_chan *channel) struct rz_dmac *dmac = to_rz_dmac(chan->device); u32 nxla; u32 chctrl; - u32 chstat; dev_dbg(dmac->dev, "%s channel %d\n", __func__, channel->index); @@ -307,14 +304,11 @@ static void rz_dmac_enable_hw(struct rz_dmac_chan *channel) nxla = rz_dmac_lmdesc_addr(channel, channel->lmdesc.head); - chstat = rz_dmac_ch_readl(channel, CHSTAT, 1); - if (!(chstat & CHSTAT_EN)) { - chctrl = (channel->chctrl | CHCTRL_SETEN); - rz_dmac_ch_writel(channel, nxla, NXLA, 1); - rz_dmac_ch_writel(channel, channel->chcfg, CHCFG, 1); - rz_dmac_ch_writel(channel, CHCTRL_SWRST, CHCTRL, 1); - rz_dmac_ch_writel(channel, chctrl, CHCTRL, 1); - } + chctrl = (channel->chctrl | CHCTRL_SETEN); + rz_dmac_ch_writel(channel, nxla, NXLA, 1); + rz_dmac_ch_writel(channel, channel->chcfg, CHCFG, 1); + rz_dmac_ch_writel(channel, CHCTRL_SWRST, CHCTRL, 1); + rz_dmac_ch_writel(channel, chctrl, CHCTRL, 1); } static void rz_dmac_disable_hw(struct rz_dmac_chan *channel) @@ -426,18 +420,20 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel) channel->chctrl = CHCTRL_SETEN; } -static int rz_dmac_xfer_desc(struct rz_dmac_chan *chan) +static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan) { - struct rz_dmac_desc *d = chan->desc; struct virt_dma_desc *vd; vd = vchan_next_desc(&chan->vc); - if (!vd) - return 0; + if (!vd) { + chan->desc = NULL; + return; + } list_del(&vd->node); + chan->desc = to_rz_dmac_desc(vd); - switch (d->type) { + switch (chan->desc->type) { case RZ_DMAC_DESC_MEMCPY: rz_dmac_prepare_desc_for_memcpy(chan); break; @@ -445,14 +441,9 @@ static int rz_dmac_xfer_desc(struct rz_dmac_chan *chan) case RZ_DMAC_DESC_SLAVE_SG: rz_dmac_prepare_descs_for_slave_sg(chan); break; - - default: - return -EINVAL; } rz_dmac_enable_hw(chan); - - return 0; } /* @@ -494,8 +485,6 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan) rz_lmdesc_setup(channel, channel->lmdesc.base); rz_dmac_disable_hw(channel); - list_splice_tail_init(&channel->ld_active, &channel->ld_free); - list_splice_tail_init(&channel->ld_queue, &channel->ld_free); if (channel->mid_rid >= 0) { clear_bit(channel->mid_rid, dmac->modules); @@ -504,13 +493,19 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan) spin_unlock_irqrestore(&channel->vc.lock, flags); + vchan_free_chan_resources(&channel->vc); + + spin_lock_irqsave(&channel->vc.lock, flags); + list_for_each_entry_safe(desc, _desc, &channel->ld_free, node) { + list_del(&desc->node); kfree(desc); channel->descs_allocated--; } INIT_LIST_HEAD(&channel->ld_free); - vchan_free_chan_resources(&channel->vc); + + spin_unlock_irqrestore(&channel->vc.lock, flags); } static struct dma_async_tx_descriptor * @@ -529,15 +524,15 @@ rz_dmac_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src, if (!desc) return NULL; - desc->type = RZ_DMAC_DESC_MEMCPY; - desc->src = src; - desc->dest = dest; - desc->len = len; - desc->direction = DMA_MEM_TO_MEM; - - list_move_tail(channel->ld_free.next, &channel->ld_queue); + list_del(&desc->node); } + desc->type = RZ_DMAC_DESC_MEMCPY; + desc->src = src; + desc->dest = dest; + desc->len = len; + desc->direction = DMA_MEM_TO_MEM; + return vchan_tx_prep(&channel->vc, &desc->vd, flags); } @@ -558,23 +553,23 @@ rz_dmac_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, if (!desc) return NULL; - for_each_sg(sgl, sg, sg_len, i) - dma_length += sg_dma_len(sg); - - desc->type = RZ_DMAC_DESC_SLAVE_SG; - desc->sg = sgl; - desc->sgcount = sg_len; - desc->len = dma_length; - desc->direction = direction; - - if (direction == DMA_DEV_TO_MEM) - desc->src = channel->src_per_address; - else - desc->dest = channel->dst_per_address; - - list_move_tail(channel->ld_free.next, &channel->ld_queue); + list_del(&desc->node); } + for_each_sg(sgl, sg, sg_len, i) + dma_length += sg_dma_len(sg); + + desc->type = RZ_DMAC_DESC_SLAVE_SG; + desc->sg = sgl; + desc->sgcount = sg_len; + desc->len = dma_length; + desc->direction = direction; + + if (direction == DMA_DEV_TO_MEM) + desc->src = channel->src_per_address; + else + desc->dest = channel->dst_per_address; + return vchan_tx_prep(&channel->vc, &desc->vd, flags); } @@ -588,8 +583,11 @@ static int rz_dmac_terminate_all(struct dma_chan *chan) rz_dmac_disable_hw(channel); 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); + if (channel->desc) { + vchan_terminate_vdesc(&channel->desc->vd); + channel->desc = NULL; + } + vchan_get_all_descriptors(&channel->vc, &head); spin_unlock_irqrestore(&channel->vc.lock, flags); vchan_dma_desc_free_list(&channel->vc, &head); @@ -600,25 +598,16 @@ static int rz_dmac_terminate_all(struct dma_chan *chan) static void rz_dmac_issue_pending(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_dmac_desc *desc; unsigned long flags; spin_lock_irqsave(&channel->vc.lock, flags); - if (!list_empty(&channel->ld_queue)) { - desc = list_first_entry(&channel->ld_queue, - struct rz_dmac_desc, node); - channel->desc = desc; - if (vchan_issue_pending(&channel->vc)) { - if (rz_dmac_xfer_desc(channel) < 0) - dev_warn(dmac->dev, "ch: %d couldn't issue DMA xfer\n", - channel->index); - else - list_move_tail(channel->ld_queue.next, - &channel->ld_active); - } - } + /* + * Issue the descriptor. If another transfer is already in progress, the + * issued descriptor will be handled after the current transfer finishes. + */ + if (vchan_issue_pending(&channel->vc) && !channel->desc) + rz_dmac_xfer_desc(channel); spin_unlock_irqrestore(&channel->vc.lock, flags); } @@ -676,13 +665,13 @@ static int rz_dmac_config(struct dma_chan *chan, static void rz_dmac_virt_desc_free(struct virt_dma_desc *vd) { - /* - * Place holder - * Descriptor allocation is done during alloc_chan_resources and - * get freed during free_chan_resources. - * list is used to manage the descriptors and avoid any memory - * allocation/free during DMA read/write. - */ + struct rz_dmac_chan *channel = to_rz_dmac_chan(vd->tx.chan); + struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan); + struct rz_dmac_desc *desc = to_rz_dmac_desc(vd); + + guard(spinlock_irqsave)(&vc->lock); + + list_add_tail(&desc->node, &channel->ld_free); } static void rz_dmac_device_synchronize(struct dma_chan *chan) @@ -692,6 +681,8 @@ static void rz_dmac_device_synchronize(struct dma_chan *chan) u32 chstat; int ret; + vchan_synchronize(&channel->vc); + ret = read_poll_timeout(rz_dmac_ch_readl, chstat, !(chstat & CHSTAT_EN), 100, 100000, false, channel, CHSTAT, 1); if (ret < 0) @@ -739,58 +730,22 @@ static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel, 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; + struct rz_dmac_desc *desc = NULL; + struct virt_dma_desc *vd; u32 crla, crtb, i; - /* Get current processing virtual descriptor */ - current_desc = list_first_entry_or_null(&channel->ld_active, - struct rz_dmac_desc, node); - if (!current_desc) - return 0; + vd = vchan_find_desc(&channel->vc, cookie); + if (vd) { + /* Descriptor has been issued but not yet processed. */ + desc = to_rz_dmac_desc(vd); + return desc->len; + } else if (channel->desc && channel->desc->vd.tx.cookie == cookie) { + /* Descriptor is currently processed. */ + desc = channel->desc; + } - /* - * 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!"); + if (!desc) { + /* Descriptor was not found. May be already completed by now. */ return 0; } @@ -813,7 +768,7 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel, * 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, current_desc, crla); + return crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, desc, crla); } static enum dma_status rz_dmac_tx_status(struct dma_chan *chan, @@ -824,21 +779,17 @@ static enum dma_status rz_dmac_tx_status(struct dma_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) { + status = dma_cookie_status(chan, cookie, txstate); + if (status == DMA_COMPLETE || !txstate) + return status; + residue = rz_dmac_chan_get_residue(channel, cookie); - if (rz_dmac_chan_is_paused(channel)) + if (status == DMA_IN_PROGRESS && rz_dmac_chan_is_paused(channel)) 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); return status; @@ -918,28 +869,18 @@ static irqreturn_t rz_dmac_irq_handler(int irq, void *dev_id) static irqreturn_t rz_dmac_irq_handler_thread(int irq, void *dev_id) { struct rz_dmac_chan *channel = dev_id; - struct rz_dmac_desc *desc = NULL; - unsigned long flags; + struct rz_dmac_desc *desc; - spin_lock_irqsave(&channel->vc.lock, flags); + guard(spinlock_irqsave)(&channel->vc.lock); - if (list_empty(&channel->ld_active)) { - /* Someone might have called terminate all */ - goto out; - } + desc = channel->desc; + if (!desc) + return IRQ_HANDLED; - desc = list_first_entry(&channel->ld_active, struct rz_dmac_desc, node); vchan_cookie_complete(&desc->vd); - list_move_tail(channel->ld_active.next, &channel->ld_free); - if (!list_empty(&channel->ld_queue)) { - desc = list_first_entry(&channel->ld_queue, struct rz_dmac_desc, - node); - channel->desc = desc; - if (rz_dmac_xfer_desc(channel) == 0) - list_move_tail(channel->ld_queue.next, &channel->ld_active); - } -out: - spin_unlock_irqrestore(&channel->vc.lock, flags); + channel->desc = NULL; + + rz_dmac_xfer_desc(channel); return IRQ_HANDLED; } @@ -1021,9 +962,7 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, channel->vc.desc_free = rz_dmac_virt_desc_free; vchan_init(&channel->vc, &dmac->engine); - INIT_LIST_HEAD(&channel->ld_queue); INIT_LIST_HEAD(&channel->ld_free); - INIT_LIST_HEAD(&channel->ld_active); /* Initialize register for each channel */ rz_dmac_disable_hw(channel); From dc86e47ca9b1021e258c366a5a9aa15d71c814a5 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:47:02 +0300 Subject: [PATCH 27/62] dmaengine: sh: rz-dmac: Refactor pause/resume code Subsequent patches will add suspend/resume and cyclic DMA support to the rz-dmac driver. This support needs to work on SoCs where power to most components (including DMA) is turned off during system suspend. For this, some channels (for example cyclic ones) may need to be paused and resumed manually by the DMA driver during system suspend/resume. Refactor the pause/resume support so the same code can be reused in the system suspend/resume path. Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-11-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 75 +++++++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 1f884ec101f8..557364443a5f 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -63,6 +64,14 @@ struct rz_dmac_desc { #define to_rz_dmac_desc(d) container_of(d, struct rz_dmac_desc, vd) +/** + * enum rz_dmac_chan_status: RZ DMAC channel status + * @RZ_DMAC_CHAN_STATUS_PAUSED: Channel is paused though DMA engine callbacks + */ +enum rz_dmac_chan_status { + RZ_DMAC_CHAN_STATUS_PAUSED, +}; + struct rz_dmac_chan { struct virt_dma_chan vc; void __iomem *ch_base; @@ -74,6 +83,8 @@ struct rz_dmac_chan { dma_addr_t src_per_address; dma_addr_t dst_per_address; + unsigned long status; + u32 chcfg; u32 chctrl; int mid_rid; @@ -491,6 +502,8 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan) channel->mid_rid = -EINVAL; } + channel->status = 0; + spin_unlock_irqrestore(&channel->vc.lock, flags); vchan_free_chan_resources(&channel->vc); @@ -589,6 +602,9 @@ static int rz_dmac_terminate_all(struct dma_chan *chan) } vchan_get_all_descriptors(&channel->vc, &head); + + channel->status = 0; + spin_unlock_irqrestore(&channel->vc.lock, flags); vchan_dma_desc_free_list(&channel->vc, &head); @@ -795,35 +811,70 @@ static enum dma_status rz_dmac_tx_status(struct dma_chan *chan, return status; } -static int rz_dmac_device_pause(struct dma_chan *chan) +static int rz_dmac_device_pause_set(struct rz_dmac_chan *channel, + unsigned long set_bitmask) { - struct rz_dmac_chan *channel = to_rz_dmac_chan(chan); + int ret = 0; u32 val; - guard(spinlock_irqsave)(&channel->vc.lock); + lockdep_assert_held(&channel->vc.lock); if (!rz_dmac_chan_is_enabled(channel)) return 0; + if (rz_dmac_chan_is_paused(channel)) + goto set_bit; + 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); + ret = read_poll_timeout_atomic(rz_dmac_ch_readl, val, + (val & CHSTAT_SUS), 1, 1024, false, + channel, CHSTAT, 1); + +set_bit: + channel->status |= set_bitmask; + + return ret; +} + +static int rz_dmac_device_pause(struct dma_chan *chan) +{ + struct rz_dmac_chan *channel = to_rz_dmac_chan(chan); + + guard(spinlock_irqsave)(&channel->vc.lock); + + return rz_dmac_device_pause_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED)); +} + +static int rz_dmac_device_resume_set(struct rz_dmac_chan *channel, + unsigned long clear_bitmask) +{ + int ret = 0; + u32 val; + + lockdep_assert_held(&channel->vc.lock); + + /* Do not check CHSTAT_SUS but rely on HW capabilities. */ + + rz_dmac_ch_writel(channel, CHCTRL_CLRSUS, CHCTRL, 1); + ret = read_poll_timeout_atomic(rz_dmac_ch_readl, val, + !(val & CHSTAT_SUS), 1, 1024, false, + channel, CHSTAT, 1); + + channel->status &= ~clear_bitmask; + + return ret; } 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. */ + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED))) + return 0; - 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); + return rz_dmac_device_resume_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED)); } /* From e8baee1d1cddc8e2be7bc362d6dc3fcb2021e873 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:47:03 +0300 Subject: [PATCH 28/62] dmaengine: sh: rz-dmac: Drop the update of channel->chctrl with CHCTRL_SETEN The CHCTRL_SETEN bit is explicitly set in rz_dmac_enable_hw(). Updating struct rz_dmac_chan::chctrl with this bit in rz_dmac_prepare_desc_for_memcpy() and rz_dmac_prepare_descs_for_slave_sg() is unnecessary in the current code base. Moreover, it conflicts with the configuration sequence that will be used for cyclic DMA channels during suspend to RAM. Cyclic DMA support will be introduced in subsequent commits. This is a preparatory commit for cyclic DMA suspend to RAM support. Reviewed-by: Frank Li Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-12-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 557364443a5f..c9c00650ddd5 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -377,7 +377,7 @@ static void rz_dmac_prepare_desc_for_memcpy(struct rz_dmac_chan *channel) rz_dmac_set_dma_req_no(dmac, channel->index, dmac->info->default_dma_req_no); channel->chcfg = chcfg; - channel->chctrl = CHCTRL_STG | CHCTRL_SETEN; + channel->chctrl = CHCTRL_STG; } static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel) @@ -428,7 +428,7 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel) rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid); - channel->chctrl = CHCTRL_SETEN; + channel->chctrl = 0; } static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan) From 172bfb57481c65fcc94ebcae3a730f6df2f953d4 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:47:04 +0300 Subject: [PATCH 29/62] dmaengine: sh: rz-dmac: Add cyclic DMA support Add cyclic DMA support to the RZ DMAC driver. A per-channel status bit is introduced to mark cyclic channels and is set during the DMA prepare callback. The IRQ handler checks this status bit and calls vchan_cyclic_callback() accordingly. Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-13-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 136 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 130 insertions(+), 6 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index c9c00650ddd5..8fd8a4bd9cc9 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -35,6 +35,7 @@ enum rz_dmac_prep_type { RZ_DMAC_DESC_MEMCPY, RZ_DMAC_DESC_SLAVE_SG, + RZ_DMAC_DESC_CYCLIC, }; struct rz_lmdesc { @@ -67,9 +68,11 @@ struct rz_dmac_desc { /** * enum rz_dmac_chan_status: RZ DMAC channel status * @RZ_DMAC_CHAN_STATUS_PAUSED: Channel is paused though DMA engine callbacks + * @RZ_DMAC_CHAN_STATUS_CYCLIC: Channel is cyclic */ enum rz_dmac_chan_status { RZ_DMAC_CHAN_STATUS_PAUSED, + RZ_DMAC_CHAN_STATUS_CYCLIC, }; struct rz_dmac_chan { @@ -191,6 +194,7 @@ struct rz_dmac { /* LINK MODE DESCRIPTOR */ #define HEADER_LV BIT(0) +#define HEADER_WBD BIT(2) #define RZ_DMAC_MAX_CHAN_DESCRIPTORS 16 #define RZ_DMAC_MAX_CHANNELS 16 @@ -431,6 +435,57 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel) channel->chctrl = 0; } +static void rz_dmac_prepare_descs_for_cyclic(struct rz_dmac_chan *channel) +{ + struct dma_chan *chan = &channel->vc.chan; + struct rz_dmac *dmac = to_rz_dmac(chan->device); + struct rz_dmac_desc *d = channel->desc; + size_t period_len = d->sgcount; + struct rz_lmdesc *lmdesc; + size_t buf_len = d->len; + size_t periods = buf_len / period_len; + + lockdep_assert_held(&channel->vc.lock); + + channel->chcfg |= CHCFG_SEL(channel->index) | CHCFG_DMS; + + if (d->direction == DMA_DEV_TO_MEM) { + channel->chcfg |= CHCFG_SAD; + channel->chcfg &= ~CHCFG_REQD; + } else { + channel->chcfg |= CHCFG_DAD | CHCFG_REQD; + } + + lmdesc = channel->lmdesc.tail; + d->start_lmdesc = lmdesc; + + for (size_t i = 0; i < periods; i++) { + if (d->direction == DMA_DEV_TO_MEM) { + lmdesc->sa = d->src; + lmdesc->da = d->dest + (i * period_len); + } else { + lmdesc->sa = d->src + (i * period_len); + lmdesc->da = d->dest; + } + + lmdesc->tb = period_len; + lmdesc->chitvl = 0; + lmdesc->chext = 0; + lmdesc->chcfg = channel->chcfg; + lmdesc->header = HEADER_LV | HEADER_WBD; + + if (i == periods - 1) + lmdesc->nxla = rz_dmac_lmdesc_addr(channel, d->start_lmdesc); + + if (++lmdesc >= (channel->lmdesc.base + DMAC_NR_LMDESC)) + lmdesc = channel->lmdesc.base; + } + + channel->lmdesc.tail = lmdesc; + + rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid); +} + static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan) { struct virt_dma_desc *vd; @@ -452,6 +507,10 @@ static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan) case RZ_DMAC_DESC_SLAVE_SG: rz_dmac_prepare_descs_for_slave_sg(chan); break; + + case RZ_DMAC_DESC_CYCLIC: + rz_dmac_prepare_descs_for_cyclic(chan); + break; } rz_dmac_enable_hw(chan); @@ -586,6 +645,55 @@ rz_dmac_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, return vchan_tx_prep(&channel->vc, &desc->vd, flags); } +static struct dma_async_tx_descriptor * +rz_dmac_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 rz_dmac_chan *channel = to_rz_dmac_chan(chan); + struct rz_dmac_desc *desc; + size_t periods; + + if (!is_slave_direction(direction)) + return NULL; + + if (!period_len || !buf_len) + return NULL; + + periods = buf_len / period_len; + if (!periods || periods > DMAC_NR_LMDESC) + return NULL; + + scoped_guard(spinlock_irqsave, &channel->vc.lock) { + if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)) + return NULL; + + desc = list_first_entry_or_null(&channel->ld_free, struct rz_dmac_desc, node); + if (!desc) + return NULL; + + list_del(&desc->node); + + channel->status |= BIT(RZ_DMAC_CHAN_STATUS_CYCLIC); + } + + desc->type = RZ_DMAC_DESC_CYCLIC; + desc->sgcount = period_len; + desc->len = buf_len; + desc->direction = direction; + + if (direction == DMA_DEV_TO_MEM) { + desc->src = channel->src_per_address; + desc->dest = buf_addr; + } else { + desc->src = buf_addr; + desc->dest = channel->dst_per_address; + } + + return vchan_tx_prep(&channel->vc, &desc->vd, flags); +} + static int rz_dmac_terminate_all(struct dma_chan *chan) { struct rz_dmac_chan *channel = to_rz_dmac_chan(chan); @@ -733,9 +841,18 @@ static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel, } /* 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); + if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)) { + u32 start_lmdesc_addr = rz_dmac_lmdesc_addr(channel, desc->start_lmdesc); + + while (lmdesc->nxla != start_lmdesc_addr) { + residue += lmdesc->tb; + lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc); + } + } else { + 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); @@ -928,10 +1045,14 @@ static irqreturn_t rz_dmac_irq_handler_thread(int irq, void *dev_id) if (!desc) return IRQ_HANDLED; - vchan_cookie_complete(&desc->vd); - channel->desc = NULL; + if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)) { + vchan_cyclic_callback(&desc->vd); + } else { + vchan_cookie_complete(&desc->vd); + channel->desc = NULL; - rz_dmac_xfer_desc(channel); + rz_dmac_xfer_desc(channel); + } return IRQ_HANDLED; } @@ -1183,6 +1304,8 @@ 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); + dma_cap_set(DMA_CYCLIC, engine->cap_mask); + engine->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); 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); @@ -1194,6 +1317,7 @@ static int rz_dmac_probe(struct platform_device *pdev) 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_prep_dma_cyclic = rz_dmac_prep_dma_cyclic; engine->device_config = rz_dmac_config; engine->device_terminate_all = rz_dmac_terminate_all; engine->device_issue_pending = rz_dmac_issue_pending; From 16ba40151b1e6a52b28296a2173457bc6c31f022 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:47:05 +0300 Subject: [PATCH 30/62] dmaengine: sh: rz-dmac: Adjust rz_dmac_chan_get_residue() to return error codes Adjust rz_dmac_chan_get_residue() to return error codes on failure and provide the residue to callers through the residue parameter. This prepares the code for the addition of runtime PM support. Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-14-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 8fd8a4bd9cc9..93394b9934c8 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -860,8 +860,8 @@ static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel, return residue; } -static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel, - dma_cookie_t cookie) +static int rz_dmac_chan_get_residue(struct device *dev, struct rz_dmac_chan *channel, + dma_cookie_t cookie, u32 *residue) { struct rz_dmac_desc *desc = NULL; struct virt_dma_desc *vd; @@ -871,7 +871,8 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel, if (vd) { /* Descriptor has been issued but not yet processed. */ desc = to_rz_dmac_desc(vd); - return desc->len; + *residue = desc->len; + return 0; } else if (channel->desc && channel->desc->vd.tx.cookie == cookie) { /* Descriptor is currently processed. */ desc = channel->desc; @@ -879,6 +880,7 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel, if (!desc) { /* Descriptor was not found. May be already completed by now. */ + *residue = 0; return 0; } @@ -901,7 +903,9 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel, * 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, desc, crla); + *residue = crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, desc, crla); + + return 0; } static enum dma_status rz_dmac_tx_status(struct dma_chan *chan, @@ -909,15 +913,20 @@ static enum dma_status rz_dmac_tx_status(struct dma_chan *chan, struct dma_tx_state *txstate) { struct rz_dmac_chan *channel = to_rz_dmac_chan(chan); + struct rz_dmac *dmac = to_rz_dmac(chan->device); enum dma_status status; u32 residue; scoped_guard(spinlock_irqsave, &channel->vc.lock) { + int ret; + status = dma_cookie_status(chan, cookie, txstate); if (status == DMA_COMPLETE || !txstate) return status; - residue = rz_dmac_chan_get_residue(channel, cookie); + ret = rz_dmac_chan_get_residue(dmac->dev, channel, cookie, &residue); + if (ret) + return DMA_ERROR; if (status == DMA_IN_PROGRESS && rz_dmac_chan_is_paused(channel)) status = DMA_PAUSED; From 7c27a4d54d48d0774518390e4ce6cf3309aac141 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:47:06 +0300 Subject: [PATCH 31/62] dmaengine: sh: rz-dmac: Add runtime PM support Protect the driver exposed APIs with runtime PM suspend/resume calls before accessing HW registers. As the current driver leaves runtime PM enabled in probe, the purpose of the changes in this patch is to avoid accessing HW registers after a failed system suspend leaves the runtime PM state of the device improperly reinitialized. In that case, the driver remains bound to the device, the APIs are still exposed, and any access to HW registers without runtime resuming the device may lead to synchronous aborts. To avoid leaking resources in case of runtime PM failures, save the error code returned by PM_RUNTIME_ACQUIRE_ERR() in rz_dmac_terminate_all() and return it only at the end of the function to allow the cleanup code to run. A similar approach is used in rz_dmac_free_chan_resources(). Because some exposed APIs (e.g. ->device_terminate_all()) may be called from atomic context according to the documentation, mark the DMA device as pm_runtime_irq_safe(). This patch prepares the driver for suspend-to-RAM support. Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-15-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 60 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 93394b9934c8..bd4ca8e939f1 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -549,12 +549,22 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan) struct rz_dmac *dmac = to_rz_dmac(chan->device); struct rz_dmac_desc *desc, *_desc; unsigned long flags; + int ret; + + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(dmac->dev, "RPM resume failed for channel %s, ret=%d\n!", + dma_chan_name(chan), ret); + } spin_lock_irqsave(&channel->vc.lock, flags); rz_lmdesc_setup(channel, channel->lmdesc.base); - rz_dmac_disable_hw(channel); + /* Skip touching HW if RPM resume failed. Let the cleanup do its jobs. */ + if (!ret) + rz_dmac_disable_hw(channel); if (channel->mid_rid >= 0) { clear_bit(channel->mid_rid, dmac->modules); @@ -697,11 +707,22 @@ rz_dmac_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, static int rz_dmac_terminate_all(struct dma_chan *chan) { struct rz_dmac_chan *channel = to_rz_dmac_chan(chan); + struct rz_dmac *dmac = to_rz_dmac(chan->device); unsigned long flags; LIST_HEAD(head); + int ret; + + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(dmac->dev, "RPM resume failed for channel %s, ret=%d\n!", + dma_chan_name(chan), ret); + } spin_lock_irqsave(&channel->vc.lock, flags); - rz_dmac_disable_hw(channel); + /* Don't return if RPM failed. Let the cleanup do its jobs. */ + if (!ret) + rz_dmac_disable_hw(channel); rz_lmdesc_setup(channel, channel->lmdesc.base); if (channel->desc) { @@ -716,13 +737,20 @@ static int rz_dmac_terminate_all(struct dma_chan *chan) spin_unlock_irqrestore(&channel->vc.lock, flags); vchan_dma_desc_free_list(&channel->vc, &head); - return 0; + return ret; } static void rz_dmac_issue_pending(struct dma_chan *chan) { struct rz_dmac_chan *channel = to_rz_dmac_chan(chan); + struct rz_dmac *dmac = to_rz_dmac(chan->device); unsigned long flags; + int ret; + + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return; spin_lock_irqsave(&channel->vc.lock, flags); @@ -807,6 +835,11 @@ static void rz_dmac_device_synchronize(struct dma_chan *chan) vchan_synchronize(&channel->vc); + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return; + ret = read_poll_timeout(rz_dmac_ch_readl, chstat, !(chstat & CHSTAT_EN), 100, 100000, false, channel, CHSTAT, 1); if (ret < 0) @@ -866,6 +899,7 @@ static int rz_dmac_chan_get_residue(struct device *dev, struct rz_dmac_chan *cha struct rz_dmac_desc *desc = NULL; struct virt_dma_desc *vd; u32 crla, crtb, i; + int ret; vd = vchan_find_desc(&channel->vc, cookie); if (vd) { @@ -884,6 +918,11 @@ static int rz_dmac_chan_get_residue(struct device *dev, struct rz_dmac_chan *cha return 0; } + PM_RUNTIME_ACQUIRE_IF_ENABLED(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + /* * 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 @@ -965,6 +1004,13 @@ static int rz_dmac_device_pause_set(struct rz_dmac_chan *channel, static int rz_dmac_device_pause(struct dma_chan *chan) { struct rz_dmac_chan *channel = to_rz_dmac_chan(chan); + struct rz_dmac *dmac = to_rz_dmac(chan->device); + int ret; + + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; guard(spinlock_irqsave)(&channel->vc.lock); @@ -994,6 +1040,13 @@ static int rz_dmac_device_resume_set(struct rz_dmac_chan *channel, static int rz_dmac_device_resume(struct dma_chan *chan) { struct rz_dmac_chan *channel = to_rz_dmac_chan(chan); + struct rz_dmac *dmac = to_rz_dmac(chan->device); + int ret; + + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; guard(spinlock_irqsave)(&channel->vc.lock); @@ -1274,6 +1327,7 @@ static int rz_dmac_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(dmac->rstc), "failed to get resets\n"); + pm_runtime_irq_safe(&pdev->dev); pm_runtime_enable(&pdev->dev); ret = pm_runtime_resume_and_get(&pdev->dev); if (ret < 0) { From c13ce43e70719dead7009e7e708971ba1c447568 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:47:07 +0300 Subject: [PATCH 32/62] dmaengine: sh: rz-dmac: Add suspend to RAM support The Renesas RZ/G3S SoC supports a power saving mode in which power to most of the SoC components is turned off, including the DMA IP. Add suspend to RAM support to save and restore the DMA IP registers. Cyclic DMA channels require special handling. Since they can be paused and resumed during system suspend/resume, the driver restores additional registers for these channels during the system resume phase. If a channel was not explicitly paused during suspend, the driver ensures that it is paused and resumed as part of the system suspend/resume flow. Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-16-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 180 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 175 insertions(+), 5 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index bd4ca8e939f1..2a7124e4aea3 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -69,10 +69,12 @@ struct rz_dmac_desc { * enum rz_dmac_chan_status: RZ DMAC channel status * @RZ_DMAC_CHAN_STATUS_PAUSED: Channel is paused though DMA engine callbacks * @RZ_DMAC_CHAN_STATUS_CYCLIC: Channel is cyclic + * @RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL: Channel is paused through driver internal logic */ enum rz_dmac_chan_status { RZ_DMAC_CHAN_STATUS_PAUSED, RZ_DMAC_CHAN_STATUS_CYCLIC, + RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL, }; struct rz_dmac_chan { @@ -92,6 +94,10 @@ struct rz_dmac_chan { u32 chctrl; int mid_rid; + struct { + u32 nxla; + } pm_state; + struct list_head ld_free; struct { @@ -1017,20 +1023,57 @@ static int rz_dmac_device_pause(struct dma_chan *chan) return rz_dmac_device_pause_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED)); } +static int rz_dmac_device_pause_internal(struct rz_dmac_chan *channel) +{ + lockdep_assert_held(&channel->vc.lock); + + /* Skip channels explicitly paused by consummers or disabled. */ + if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED) || + !rz_dmac_chan_is_enabled(channel)) + return 0; + + return rz_dmac_device_pause_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL)); +} + static int rz_dmac_device_resume_set(struct rz_dmac_chan *channel, unsigned long clear_bitmask) { - int ret = 0; u32 val; + int ret; lockdep_assert_held(&channel->vc.lock); - /* Do not check CHSTAT_SUS but rely on HW capabilities. */ + /* + * We can be: + * + * 1/ after the channel was paused by a consummer and now it + * needs to be resummed + * 2/ after the channel was paused internally (as a result of + * a system suspend with power loss or not) + * 3/ after the channel was paused by a consummer, the system + * went through a system suspend (with power loss or not) + * and the consummer wants to resume the channel + * + * To cover all the above cases we set both CLRSUS and SETEN. + * + * In case 1/ setting SETEN while the channel is still enabled + * is harmless for the controller. + * + * In case 2/ the channel is disabled when calling this function + * and setting CLRSUS is harmless for the controller as the + * channel is disabled anyway. + * + * In case 3/ the channel is disabled/enabled if the system + * went though a suspend with power loss/or not and setting + * CLRSUS/SETEN is harmless for the controller as the channel + * is enabled/disabled anyway. + */ + + rz_dmac_ch_writel(channel, CHCTRL_CLRSUS | CHCTRL_SETEN, CHCTRL, 1); - rz_dmac_ch_writel(channel, CHCTRL_CLRSUS, CHCTRL, 1); ret = read_poll_timeout_atomic(rz_dmac_ch_readl, val, - !(val & CHSTAT_SUS), 1, 1024, false, - channel, CHSTAT, 1); + ((val & (CHSTAT_SUS | CHSTAT_EN)) == CHSTAT_EN), + 1, 1024, false, channel, CHSTAT, 1); channel->status &= ~clear_bitmask; @@ -1056,6 +1099,16 @@ static int rz_dmac_device_resume(struct dma_chan *chan) return rz_dmac_device_resume_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED)); } +static int rz_dmac_device_resume_internal(struct rz_dmac_chan *channel) +{ + lockdep_assert_held(&channel->vc.lock); + + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL))) + return 0; + + return rz_dmac_device_resume_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL)); +} + /* * ----------------------------------------------------------------------------- * IRQ handling @@ -1421,6 +1474,122 @@ static void rz_dmac_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); } +static void rz_dmac_suspend_recover(struct rz_dmac *dmac) +{ + int ret; + + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return; + + for (unsigned int i = 0; i < dmac->n_channels; i++) { + struct rz_dmac_chan *channel = &dmac->channels[i]; + + guard(spinlock_irqsave)(&channel->vc.lock); + + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC))) + continue; + + rz_dmac_device_resume_internal(channel); + } +} + +static int rz_dmac_suspend(struct device *dev) +{ + struct rz_dmac *dmac = dev_get_drvdata(dev); + int ret = 0; + + for (unsigned int i = 0; i < dmac->n_channels; i++) { + struct rz_dmac_chan *channel = &dmac->channels[i]; + + guard(spinlock_irqsave)(&channel->vc.lock); + + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC))) + continue; + + ret = rz_dmac_device_pause_internal(channel); + if (ret) { + dev_err(dev, "Failed to suspend channel %s\n", + dma_chan_name(&channel->vc.chan)); + break; + } + + channel->pm_state.nxla = rz_dmac_ch_readl(channel, NXLA, 1); + } + + if (ret) + goto suspend_recover; + + ret = reset_control_assert(dmac->rstc); + if (ret) + goto suspend_recover; + + ret = pm_runtime_put_sync(dev); + if (ret < 0) + goto reset_deassert; + + return 0; + +reset_deassert: + reset_control_deassert(dmac->rstc); +suspend_recover: + rz_dmac_suspend_recover(dmac); + return ret; +} + +static int rz_dmac_resume(struct device *dev) +{ + struct rz_dmac *dmac = dev_get_drvdata(dev); + int errors = 0, ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = reset_control_deassert(dmac->rstc); + if (ret) { + /* + * Do not put runtime PM here and keep the same state as in + * probe. As subsequent suspend/resume cycles may follow, leave + * the runtime PM as is, here, to avoid imbalances. + */ + return ret; + } + + rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_0_7_COMMON_BASE + DCTRL); + rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_8_15_COMMON_BASE + DCTRL); + + for (unsigned int i = 0; i < dmac->n_channels; i++) { + struct rz_dmac_chan *channel = &dmac->channels[i]; + + guard(spinlock_irqsave)(&channel->vc.lock); + + rz_dmac_disable_hw(&dmac->channels[i]); + + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC))) + continue; + + rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid); + + rz_dmac_ch_writel(channel, channel->pm_state.nxla, NXLA, 1); + rz_dmac_ch_writel(channel, channel->chcfg, CHCFG, 1); + rz_dmac_ch_writel(channel, CHCTRL_SWRST, CHCTRL, 1); + rz_dmac_ch_writel(channel, channel->chctrl, CHCTRL, 1); + + ret = rz_dmac_device_resume_internal(channel); + if (ret) { + errors = ret; + dev_err(dev, "Failed to resume channel %s, ret=%d\n", + dma_chan_name(&channel->vc.chan), ret); + } + } + + return errors ? : 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(rz_dmac_pm_ops, rz_dmac_suspend, rz_dmac_resume); + 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, @@ -1447,6 +1616,7 @@ static struct platform_driver rz_dmac_driver = { .driver = { .name = "rz-dmac", .of_match_table = of_rz_dmac_match, + .pm = pm_ptr(&rz_dmac_pm_ops), }, .probe = rz_dmac_probe, .remove = rz_dmac_remove, From b4d34819a53964648bc53cabaa3ba9890d4fdf9c Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:47:08 +0300 Subject: [PATCH 33/62] ASoC: renesas: rz-ssi: Add pause support Add pause support as a preparatory step to switch to PCM dmaengine APIs. Acked-by: Mark Brown Tested-by: John Madieu Signed-off-by: Claudiu Beznea Link: https://patch.msgid.link/20260526084710.3491480-17-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- sound/soc/renesas/rz-ssi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c index 71e434cfe07b..d4e1dded3a9c 100644 --- a/sound/soc/renesas/rz-ssi.c +++ b/sound/soc/renesas/rz-ssi.c @@ -847,6 +847,7 @@ static int rz_ssi_dai_trigger(struct snd_pcm_substream *substream, int cmd, switch (cmd) { case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ret = rz_ssi_trigger_resume(ssi, strm); if (ret) return ret; @@ -888,6 +889,7 @@ static int rz_ssi_dai_trigger(struct snd_pcm_substream *substream, int cmd, break; case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: rz_ssi_stop(ssi, strm); break; @@ -1054,7 +1056,8 @@ static const struct snd_pcm_hardware rz_ssi_pcm_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_RESUME, + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_PAUSE, .buffer_bytes_max = PREALLOC_BUFFER, .period_bytes_min = 32, .period_bytes_max = 8192, From 9fcaec81ac56c9d2c5d779ffb5a76b622b4d0590 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:47:09 +0300 Subject: [PATCH 34/62] ASoC: renesas: rz-ssi: Use generic PCM dmaengine APIs On Renesas RZ/G2L and RZ/G3S SoCs (where this was tested), captured audio files occasionally contained random spikes when viewed with a tool such as Audacity. These spikes were also audible as popping noises. Using cyclic DMA resolves this issue. The driver was reworked to use the existing support provided by the generic PCM dmaengine APIs. In addition to eliminating the random spikes, the following issues were addressed: - blank periods at the beginning of recorded files, which occurred intermittently, are no longer present - no overruns or underruns were observed when continuously recording short audio files (e.g. 5 seconds long) in a loop - concurrency issues in the SSI driver when enqueuing DMA requests were eliminated; previously, DMA requests could be prepared and submitted both from the DMA completion callback and the interrupt handler, which led to crashes after several hours of testing - the SSI driver logic is simplified - the number of generated interrupts is reduced by approximately 250% In the SSI platform driver probe function, the following changes were made: - the driver-specific DMA configuration was removed in favor of the generic PCM dmaengine APIs. As a result, explicit cleanup goto labels are no longer required and the driver remove callback was dropped, since resource management is now handled via devres helpers - special handling was added for IP variants operating in half-duplex mode, where the DMA channel name in the device tree is "rt"; this DMA channel name is taken into account and passed to the generic PCM dmaengine configuration data All code previously responsible for preparing and completing DMA transfers was removed, as this functionality is now handled entirely by the generic PCM dmaengine APIs. Since DMA channels must be paused and resumed during recovery paths (overruns and underruns reported by the hardware), the DMA channel references are stored in rz_ssi_hw_params(). The logic in rz_ssi_is_dma_enabled() was updated to reflect that the driver no longer manages DMA transfers directly. To avoid software reported underruns (e.g. when running aplay during consecutive suspend/resume cycles, or when the CPU is nearly 100% loaded), rz_ssi_pcm_hardware.buffer_bytes_max was increased to 192K. At the same time, rz_ssi_pcm_hardware.period_bytes_max was set to 48K to reduce interrupt overhead. Finally, rz_ssi_stream_is_play() was removed, as it had only a single remaining user after this rework, and its logic was inlined at the call site. Acked-by: Mark Brown Tested-by: John Madieu Signed-off-by: Claudiu Beznea Link: https://patch.msgid.link/20260526084710.3491480-18-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- sound/soc/renesas/Kconfig | 1 + sound/soc/renesas/rz-ssi.c | 394 +++++++++++++------------------------ 2 files changed, 136 insertions(+), 259 deletions(-) diff --git a/sound/soc/renesas/Kconfig b/sound/soc/renesas/Kconfig index 11c2027c88a7..6520217e7407 100644 --- a/sound/soc/renesas/Kconfig +++ b/sound/soc/renesas/Kconfig @@ -56,6 +56,7 @@ config SND_SOC_MSIOF config SND_SOC_RZ tristate "RZ/G2L series SSIF-2 support" depends on ARCH_RZG2L || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM help This option enables RZ/G2L SSIF-2 sound support. diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c index d4e1dded3a9c..9fe8a639c47c 100644 --- a/sound/soc/renesas/rz-ssi.c +++ b/sound/soc/renesas/rz-ssi.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include @@ -87,8 +89,6 @@ struct rz_ssi_stream { struct rz_ssi_priv *priv; struct snd_pcm_substream *substream; int fifo_sample_size; /* sample capacity of SSI FIFO */ - int dma_buffer_pos; /* The address for the next DMA descriptor */ - int completed_dma_buf_pos; /* The address of the last completed DMA descriptor. */ int period_counter; /* for keeping track of periods transferred */ int buffer_pos; /* current frame position in the buffer */ int running; /* 0=stopped, 1=running */ @@ -96,8 +96,6 @@ struct rz_ssi_stream { int uerr_num; int oerr_num; - struct dma_chan *dma_ch; - int (*transfer)(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm); }; @@ -108,7 +106,6 @@ struct rz_ssi_priv { struct clk *sfr_clk; struct clk *clk; - phys_addr_t phys; int irq_int; int irq_tx; int irq_rx; @@ -148,9 +145,10 @@ struct rz_ssi_priv { unsigned int sample_width; unsigned int sample_bits; } hw_params_cache; -}; -static void rz_ssi_dma_complete(void *data); + struct snd_dmaengine_dai_dma_data dma_dais[SNDRV_PCM_STREAM_LAST + 1]; + struct dma_chan *dmas[SNDRV_PCM_STREAM_LAST + 1]; +}; static void rz_ssi_reg_writel(struct rz_ssi_priv *priv, uint reg, u32 data) { @@ -172,11 +170,6 @@ static void rz_ssi_reg_mask_setl(struct rz_ssi_priv *priv, uint reg, writel(val, (priv->base + reg)); } -static inline bool rz_ssi_stream_is_play(struct snd_pcm_substream *substream) -{ - return substream->stream == SNDRV_PCM_STREAM_PLAYBACK; -} - static inline struct rz_ssi_stream * rz_ssi_stream_get(struct rz_ssi_priv *ssi, struct snd_pcm_substream *substream) { @@ -185,7 +178,7 @@ rz_ssi_stream_get(struct rz_ssi_priv *ssi, struct snd_pcm_substream *substream) static inline bool rz_ssi_is_dma_enabled(struct rz_ssi_priv *ssi) { - return (ssi->playback.dma_ch && (ssi->dma_rt || ssi->capture.dma_ch)); + return !ssi->playback.transfer && !ssi->capture.transfer; } static void rz_ssi_set_substream(struct rz_ssi_stream *strm, @@ -215,8 +208,6 @@ static void rz_ssi_stream_init(struct rz_ssi_stream *strm, struct snd_pcm_substream *substream) { rz_ssi_set_substream(strm, substream); - strm->dma_buffer_pos = 0; - strm->completed_dma_buf_pos = 0; strm->period_counter = 0; strm->buffer_pos = 0; @@ -242,12 +233,13 @@ static void rz_ssi_stream_quit(struct rz_ssi_priv *ssi, dev_info(dev, "underrun = %d\n", strm->uerr_num); } -static int rz_ssi_clk_setup(struct rz_ssi_priv *ssi, unsigned int rate, - unsigned int channels) +static int rz_ssi_clk_setup(struct rz_ssi_priv *ssi, struct snd_pcm_substream *substream, + unsigned int rate, unsigned int channels) { static u8 ckdv[] = { 1, 2, 4, 8, 16, 32, 64, 128, 6, 12, 24, 48, 96 }; unsigned int channel_bits = 32; /* System Word Length */ unsigned long bclk_rate = rate * channels * channel_bits; + struct snd_dmaengine_dai_dma_data *dma_dai; unsigned int div; unsigned int i; u32 ssicr = 0; @@ -290,6 +282,8 @@ static int rz_ssi_clk_setup(struct rz_ssi_priv *ssi, unsigned int rate, return -EINVAL; } + dma_dai = &ssi->dma_dais[substream->stream]; + /* * DWL: Data Word Length = {16, 24, 32} bits * SWL: System Word Length = 32 bits @@ -298,12 +292,15 @@ static int rz_ssi_clk_setup(struct rz_ssi_priv *ssi, unsigned int rate, switch (ssi->hw_params_cache.sample_width) { case 16: ssicr |= SSICR_DWL(1); + dma_dai->addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; break; case 24: ssicr |= SSICR_DWL(5) | SSICR_PDTA; + dma_dai->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; break; case 32: ssicr |= SSICR_DWL(6); + dma_dai->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; break; default: dev_err(ssi->dev, "Not support %u data width", @@ -344,7 +341,7 @@ static void rz_ssi_set_idle(struct rz_ssi_priv *ssi) static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm) { - bool is_play = rz_ssi_stream_is_play(strm->substream); + bool is_play = strm->substream->stream == SNDRV_PCM_STREAM_PLAYBACK; bool is_full_duplex; u32 ssicr, ssifcr; @@ -423,14 +420,6 @@ static int rz_ssi_stop(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm) /* Disable TX/RX */ rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TEN | SSICR_REN, 0); - /* Cancel all remaining DMA transactions */ - if (rz_ssi_is_dma_enabled(ssi)) { - if (ssi->playback.dma_ch) - dmaengine_terminate_async(ssi->playback.dma_ch); - if (ssi->capture.dma_ch) - dmaengine_terminate_async(ssi->capture.dma_ch); - } - rz_ssi_set_idle(ssi); return 0; @@ -458,10 +447,6 @@ static void rz_ssi_pointer_update(struct rz_ssi_stream *strm, int frames) snd_pcm_period_elapsed(strm->substream); strm->period_counter = current_period; } - - strm->completed_dma_buf_pos += runtime->period_size; - if (strm->completed_dma_buf_pos >= runtime->buffer_size) - strm->completed_dma_buf_pos = 0; } static int rz_ssi_pio_recv(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm) @@ -606,12 +591,6 @@ static irqreturn_t rz_ssi_interrupt(int irq, void *data) if (irq == ssi->irq_int) { /* error or idle */ bool is_stopped = !!(ssisr & (SSISR_RUIRQ | SSISR_ROIRQ | SSISR_TUIRQ | SSISR_TOIRQ)); - int i, count; - - if (rz_ssi_is_dma_enabled(ssi)) - count = 4; - else - count = 1; if (ssi->capture.substream && is_stopped) { if (ssisr & SSISR_RUIRQ) @@ -631,19 +610,41 @@ static irqreturn_t rz_ssi_interrupt(int irq, void *data) rz_ssi_stop(ssi, strm_playback); } + if (!rz_ssi_is_stream_running(&ssi->playback) && + !rz_ssi_is_stream_running(&ssi->capture) && + rz_ssi_is_dma_enabled(ssi) && is_stopped) { + if (ssi->playback.substream && + ssi->dmas[SNDRV_PCM_STREAM_PLAYBACK]) + dmaengine_pause(ssi->dmas[SNDRV_PCM_STREAM_PLAYBACK]); + if (ssi->capture.substream && + ssi->dmas[SNDRV_PCM_STREAM_CAPTURE] && + /* Avoid calling pause twice in case of half duplex. */ + ssi->dmas[SNDRV_PCM_STREAM_PLAYBACK] != + ssi->dmas[SNDRV_PCM_STREAM_CAPTURE]) + dmaengine_pause(ssi->dmas[SNDRV_PCM_STREAM_CAPTURE]); + } + /* Clear all flags */ rz_ssi_reg_mask_setl(ssi, SSISR, SSISR_TOIRQ | SSISR_TUIRQ | SSISR_ROIRQ | SSISR_RUIRQ, 0); /* Add/remove more data */ if (ssi->capture.substream && is_stopped) { - for (i = 0; i < count; i++) + if (rz_ssi_is_dma_enabled(ssi)) { + if (ssi->dmas[SNDRV_PCM_STREAM_CAPTURE]) + dmaengine_resume(ssi->dmas[SNDRV_PCM_STREAM_CAPTURE]); + } else { strm_capture->transfer(ssi, strm_capture); + } } if (ssi->playback.substream && is_stopped) { - for (i = 0; i < count; i++) + if (rz_ssi_is_dma_enabled(ssi)) { + if (ssi->dmas[SNDRV_PCM_STREAM_PLAYBACK]) + dmaengine_resume(ssi->dmas[SNDRV_PCM_STREAM_PLAYBACK]); + } else { strm_playback->transfer(ssi, strm_playback); + } } /* Resume */ @@ -679,153 +680,11 @@ static irqreturn_t rz_ssi_interrupt(int irq, void *data) return IRQ_HANDLED; } -static int rz_ssi_dma_slave_config(struct rz_ssi_priv *ssi, - struct dma_chan *dma_ch, bool is_play) -{ - struct dma_slave_config cfg; - - memset(&cfg, 0, sizeof(cfg)); - - cfg.direction = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; - cfg.dst_addr = ssi->phys + SSIFTDR; - cfg.src_addr = ssi->phys + SSIFRDR; - if (ssi->hw_params_cache.sample_width == 16) { - cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; - cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; - } else { - cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - } - - return dmaengine_slave_config(dma_ch, &cfg); -} - -static int rz_ssi_dma_transfer(struct rz_ssi_priv *ssi, - struct rz_ssi_stream *strm) -{ - struct snd_pcm_substream *substream = strm->substream; - struct dma_async_tx_descriptor *desc; - struct snd_pcm_runtime *runtime; - enum dma_transfer_direction dir; - u32 dma_paddr, dma_size; - int amount; - - if (!rz_ssi_stream_is_valid(ssi, strm)) - return -EINVAL; - - runtime = substream->runtime; - if (runtime->state == SNDRV_PCM_STATE_DRAINING) - /* - * Stream is ending, so do not queue up any more DMA - * transfers otherwise we play partial sound clips - * because we can't shut off the DMA quick enough. - */ - return 0; - - dir = rz_ssi_stream_is_play(substream) ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; - - /* Always transfer 1 period */ - amount = runtime->period_size; - - /* DMA physical address and size */ - dma_paddr = runtime->dma_addr + frames_to_bytes(runtime, - strm->dma_buffer_pos); - dma_size = frames_to_bytes(runtime, amount); - desc = dmaengine_prep_slave_single(strm->dma_ch, dma_paddr, dma_size, - dir, - DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - if (!desc) { - dev_err(ssi->dev, "dmaengine_prep_slave_single() fail\n"); - return -ENOMEM; - } - - desc->callback = rz_ssi_dma_complete; - desc->callback_param = strm; - - if (dmaengine_submit(desc) < 0) { - dev_err(ssi->dev, "dmaengine_submit() fail\n"); - return -EIO; - } - - /* Update DMA pointer */ - strm->dma_buffer_pos += amount; - if (strm->dma_buffer_pos >= runtime->buffer_size) - strm->dma_buffer_pos = 0; - - /* Start DMA */ - dma_async_issue_pending(strm->dma_ch); - - return 0; -} - -static void rz_ssi_dma_complete(void *data) -{ - struct rz_ssi_stream *strm = (struct rz_ssi_stream *)data; - - if (!strm->running || !strm->substream || !strm->substream->runtime) - return; - - /* Note that next DMA transaction has probably already started */ - rz_ssi_pointer_update(strm, strm->substream->runtime->period_size); - - /* Queue up another DMA transaction */ - rz_ssi_dma_transfer(strm->priv, strm); -} - -static void rz_ssi_release_dma_channels(struct rz_ssi_priv *ssi) -{ - if (ssi->playback.dma_ch) { - dma_release_channel(ssi->playback.dma_ch); - ssi->playback.dma_ch = NULL; - if (ssi->dma_rt) - ssi->dma_rt = false; - } - - if (ssi->capture.dma_ch) { - dma_release_channel(ssi->capture.dma_ch); - ssi->capture.dma_ch = NULL; - } -} - -static int rz_ssi_dma_request(struct rz_ssi_priv *ssi, struct device *dev) -{ - ssi->playback.dma_ch = dma_request_chan(dev, "tx"); - if (IS_ERR(ssi->playback.dma_ch)) - ssi->playback.dma_ch = NULL; - - ssi->capture.dma_ch = dma_request_chan(dev, "rx"); - if (IS_ERR(ssi->capture.dma_ch)) - ssi->capture.dma_ch = NULL; - - if (!ssi->playback.dma_ch && !ssi->capture.dma_ch) { - ssi->playback.dma_ch = dma_request_chan(dev, "rt"); - if (IS_ERR(ssi->playback.dma_ch)) { - ssi->playback.dma_ch = NULL; - goto no_dma; - } - - ssi->dma_rt = true; - } - - if (!rz_ssi_is_dma_enabled(ssi)) - goto no_dma; - - return 0; - -no_dma: - rz_ssi_release_dma_channels(ssi); - - return -ENODEV; -} - static int rz_ssi_trigger_resume(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm) { struct snd_pcm_substream *substream = strm->substream; - struct snd_pcm_runtime *runtime = substream->runtime; int ret; - strm->dma_buffer_pos = strm->completed_dma_buf_pos + runtime->period_size; - if (rz_ssi_is_stream_running(&ssi->playback) || rz_ssi_is_stream_running(&ssi->capture)) return 0; @@ -834,7 +693,7 @@ static int rz_ssi_trigger_resume(struct rz_ssi_priv *ssi, struct rz_ssi_stream * if (ret) return ret; - return rz_ssi_clk_setup(ssi, ssi->hw_params_cache.rate, + return rz_ssi_clk_setup(ssi, substream, ssi->hw_params_cache.rate, ssi->hw_params_cache.channels); } @@ -843,7 +702,7 @@ static int rz_ssi_dai_trigger(struct snd_pcm_substream *substream, int cmd, { struct rz_ssi_priv *ssi = snd_soc_dai_get_drvdata(dai); struct rz_ssi_stream *strm = rz_ssi_stream_get(ssi, substream); - int ret = 0, i, num_transfer = 1; + int ret = 0; switch (cmd) { case SNDRV_PCM_TRIGGER_RESUME: @@ -858,28 +717,7 @@ static int rz_ssi_dai_trigger(struct snd_pcm_substream *substream, int cmd, if (cmd == SNDRV_PCM_TRIGGER_START) rz_ssi_stream_init(strm, substream); - if (rz_ssi_is_dma_enabled(ssi)) { - bool is_playback = rz_ssi_stream_is_play(substream); - - if (ssi->dma_rt) - ret = rz_ssi_dma_slave_config(ssi, ssi->playback.dma_ch, - is_playback); - else - ret = rz_ssi_dma_slave_config(ssi, strm->dma_ch, - is_playback); - - /* Fallback to pio */ - if (ret < 0) { - ssi->playback.transfer = rz_ssi_pio_send; - ssi->capture.transfer = rz_ssi_pio_recv; - rz_ssi_release_dma_channels(ssi); - } else { - /* For DMA, queue up multiple DMA descriptors */ - num_transfer = 4; - } - } - - for (i = 0; i < num_transfer; i++) { + if (!rz_ssi_is_dma_enabled(ssi)) { ret = strm->transfer(ssi, strm); if (ret) return ret; @@ -975,6 +813,8 @@ static void rz_ssi_shutdown(struct snd_pcm_substream *substream, ssi->dup.tx_active = false; else ssi->dup.rx_active = false; + + ssi->dmas[substream->stream] = NULL; } static bool rz_ssi_is_valid_hw_params(struct rz_ssi_priv *ssi, unsigned int rate, @@ -1026,6 +866,12 @@ static int rz_ssi_dai_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } + /* Save the DMA channels for recovery. */ + if (rz_ssi_is_dma_enabled(ssi)) + ssi->dmas[substream->stream] = snd_dmaengine_pcm_get_chan(substream); + else + ssi->dmas[substream->stream] = NULL; + if (rz_ssi_is_stream_running(&ssi->playback) || rz_ssi_is_stream_running(&ssi->capture)) { if (rz_ssi_is_valid_hw_params(ssi, rate, channels, sample_width, sample_bits)) @@ -1041,10 +887,21 @@ static int rz_ssi_dai_hw_params(struct snd_pcm_substream *substream, if (ret) return ret; - return rz_ssi_clk_setup(ssi, rate, channels); + return rz_ssi_clk_setup(ssi, substream, rate, channels); +} + +static int rz_ssi_dai_probe(struct snd_soc_dai *dai) +{ + struct rz_ssi_priv *ssi = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &ssi->dma_dais[SNDRV_PCM_STREAM_PLAYBACK], + &ssi->dma_dais[SNDRV_PCM_STREAM_CAPTURE]); + + return 0; } static const struct snd_soc_dai_ops rz_ssi_dai_ops = { + .probe = rz_ssi_dai_probe, .startup = rz_ssi_startup, .shutdown = rz_ssi_shutdown, .trigger = rz_ssi_dai_trigger, @@ -1058,9 +915,9 @@ static const struct snd_pcm_hardware rz_ssi_pcm_hardware = { SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_PAUSE, - .buffer_bytes_max = PREALLOC_BUFFER, + .buffer_bytes_max = 192 * 1024, .period_bytes_min = 32, - .period_bytes_max = 8192, + .period_bytes_max = 48 * 1024, .channels_min = SSI_CHAN_MIN, .channels_max = SSI_CHAN_MAX, .periods_min = 1, @@ -1068,8 +925,8 @@ static const struct snd_pcm_hardware rz_ssi_pcm_hardware = { .fifo_size = 32 * 2, }; -static int rz_ssi_pcm_open(struct snd_soc_component *component, - struct snd_pcm_substream *substream) +static int rz_ssi_pcm_open_pio(struct snd_soc_component *component, + struct snd_pcm_substream *substream) { snd_soc_set_runtime_hwparams(substream, &rz_ssi_pcm_hardware); @@ -1077,6 +934,13 @@ static int rz_ssi_pcm_open(struct snd_soc_component *component, SNDRV_PCM_HW_PARAM_PERIODS); } +static int rz_ssi_pcm_open_dma(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); +} + static snd_pcm_uframes_t rz_ssi_pcm_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream) { @@ -1093,7 +957,8 @@ static int rz_ssi_pcm_new(struct snd_soc_component *component, { snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, rtd->card->snd_card->dev, - PREALLOC_BUFFER, PREALLOC_BUFFER_MAX); + rz_ssi_pcm_hardware.buffer_bytes_max, + rz_ssi_pcm_hardware.buffer_bytes_max); return 0; } @@ -1116,16 +981,30 @@ static struct snd_soc_dai_driver rz_ssi_soc_dai[] = { }, }; -static const struct snd_soc_component_driver rz_ssi_soc_component = { +static const struct snd_soc_component_driver rz_ssi_soc_component_pio = { .name = "rz-ssi", - .open = rz_ssi_pcm_open, + .open = rz_ssi_pcm_open_pio, .pointer = rz_ssi_pcm_pointer, .pcm_new = rz_ssi_pcm_new, .legacy_dai_naming = 1, }; +static const struct snd_soc_component_driver rz_ssi_soc_component_dma = { + .name = "rz-ssi", + .open = rz_ssi_pcm_open_dma, + .legacy_dai_naming = 1, +}; + +static const struct snd_dmaengine_pcm_config rz_ssi_dmaengine_pcm_conf = { + .pcm_hardware = &rz_ssi_pcm_hardware, + .prealloc_buffer_size = 192 * 1024, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, +}; + static int rz_ssi_probe(struct platform_device *pdev) { + const struct snd_soc_component_driver *component_driver; + struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; struct rz_ssi_priv *ssi; struct clk *audio_clk; @@ -1141,7 +1020,6 @@ static int rz_ssi_probe(struct platform_device *pdev) if (IS_ERR(ssi->base)) return PTR_ERR(ssi->base); - ssi->phys = res->start; ssi->clk = devm_clk_get(dev, "ssi"); if (IS_ERR(ssi->clk)) return PTR_ERR(ssi->clk); @@ -1165,16 +1043,43 @@ static int rz_ssi_probe(struct platform_device *pdev) ssi->audio_mck = ssi->audio_clk_1 ? ssi->audio_clk_1 : ssi->audio_clk_2; - /* Detect DMA support */ - ret = rz_ssi_dma_request(ssi, dev); - if (ret < 0) { + ssi->dma_dais[SNDRV_PCM_STREAM_PLAYBACK].addr = (dma_addr_t)res->start + SSIFTDR; + ssi->dma_dais[SNDRV_PCM_STREAM_CAPTURE].addr = (dma_addr_t)res->start + SSIFRDR; + + if (of_property_present(np, "dma-names")) { + struct snd_dmaengine_pcm_config *config; + unsigned int flags = 0; + + config = devm_kzalloc(dev, sizeof(*config), GFP_KERNEL); + if (!config) + return -ENOMEM; + + config->pcm_hardware = rz_ssi_dmaengine_pcm_conf.pcm_hardware; + config->prealloc_buffer_size = rz_ssi_dmaengine_pcm_conf.prealloc_buffer_size; + config->prepare_slave_config = rz_ssi_dmaengine_pcm_conf.prepare_slave_config; + + if (of_property_match_string(np, "dma-names", "rt") == 0) { + flags = SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX; + config->chan_names[SNDRV_PCM_STREAM_PLAYBACK] = "rt"; + } else { + config->chan_names[SNDRV_PCM_STREAM_PLAYBACK] = "tx"; + config->chan_names[SNDRV_PCM_STREAM_CAPTURE] = "rx"; + } + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, config, flags); + } else { + ret = -ENODEV; + } + + if (ret == -EPROBE_DEFER) { + return ret; + } else if (ret) { dev_warn(dev, "DMA not available, using PIO\n"); ssi->playback.transfer = rz_ssi_pio_send; ssi->capture.transfer = rz_ssi_pio_recv; + component_driver = &rz_ssi_soc_component_pio; } else { - dev_info(dev, "DMA enabled"); - ssi->playback.transfer = rz_ssi_dma_transfer; - ssi->capture.transfer = rz_ssi_dma_transfer; + dev_info(dev, "DMA enabled\n"); + component_driver = &rz_ssi_soc_component_dma; } ssi->playback.priv = ssi; @@ -1185,17 +1090,13 @@ static int rz_ssi_probe(struct platform_device *pdev) /* Error Interrupt */ ssi->irq_int = platform_get_irq_byname(pdev, "int_req"); - if (ssi->irq_int < 0) { - ret = ssi->irq_int; - goto err_release_dma_chs; - } + if (ssi->irq_int < 0) + return ssi->irq_int; ret = devm_request_irq(dev, ssi->irq_int, rz_ssi_interrupt, 0, dev_name(dev), ssi); - if (ret < 0) { - dev_err_probe(dev, ret, "irq request error (int_req)\n"); - goto err_release_dma_chs; - } + if (ret < 0) + return dev_err_probe(dev, ret, "irq request error (int_req)\n"); if (!rz_ssi_is_dma_enabled(ssi)) { /* Tx and Rx interrupts (pio only) */ @@ -1236,43 +1137,19 @@ static int rz_ssi_probe(struct platform_device *pdev) } ssi->rstc = devm_reset_control_get_exclusive(dev, NULL); - if (IS_ERR(ssi->rstc)) { - ret = PTR_ERR(ssi->rstc); - goto err_release_dma_chs; - } + if (IS_ERR(ssi->rstc)) + return dev_err_probe(dev, PTR_ERR(ssi->rstc), "Failed to get reset\n"); /* Default 0 for power saving. Can be overridden via sysfs. */ pm_runtime_set_autosuspend_delay(dev, 0); pm_runtime_use_autosuspend(dev); ret = devm_pm_runtime_enable(dev); - if (ret < 0) { - dev_err(dev, "Failed to enable runtime PM!\n"); - goto err_release_dma_chs; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to enable runtime PM!\n"); - ret = devm_snd_soc_register_component(dev, &rz_ssi_soc_component, - rz_ssi_soc_dai, - ARRAY_SIZE(rz_ssi_soc_dai)); - if (ret < 0) { - dev_err(dev, "failed to register snd component\n"); - goto err_release_dma_chs; - } - - return 0; - -err_release_dma_chs: - rz_ssi_release_dma_channels(ssi); - - return ret; -} - -static void rz_ssi_remove(struct platform_device *pdev) -{ - struct rz_ssi_priv *ssi = dev_get_drvdata(&pdev->dev); - - rz_ssi_release_dma_channels(ssi); - - reset_control_assert(ssi->rstc); + return devm_snd_soc_register_component(dev, component_driver, + rz_ssi_soc_dai, + ARRAY_SIZE(rz_ssi_soc_dai)); } static const struct of_device_id rz_ssi_of_match[] = { @@ -1307,7 +1184,6 @@ static struct platform_driver rz_ssi_driver = { .pm = pm_ptr(&rz_ssi_pm_ops), }, .probe = rz_ssi_probe, - .remove = rz_ssi_remove, }; module_platform_driver(rz_ssi_driver); From cd2d36e8ae61832aaac3bddf5aafdab72821e6b9 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 26 May 2026 11:47:10 +0300 Subject: [PATCH 35/62] dmaengine: sh: rz-dmac: Set the Link End (LE) bit on the last descriptor On an RZ/G2L-based system, it has been observed that when the DMA channels for all enabled IPs are active (TX and RX for one serial IP, TX and RX for one audio IP, and TX and RX for one SPI IP), shortly after all of them are started, the system can become irrecoverably blocked. In one debug session the system did not block, and the DMA HW registers were inspected. It was found that the DER (Descriptor Error) bit in the CHSTAT register for one of the SPI DMA channels was set. According to the RZ/G2L HW Manual, Rev. 1.30, chapter 14.4.7 Channel Status Register n/nS (CHSTAT_n/nS), description of the DER bit, the DER bit is set when the LV (Link Valid) value loaded with a descriptor in link mode is 0. This means that the DMA engine has loaded an invalid descriptor (as defined in Table 14.14, Header Area, of the same manual). The same chapter states that when a descriptor error occurs, the transfer is stopped, but no DMA error interrupt is generated. Set the LE bit on the last descriptor of a transfer. This informs the DMA engine that this is the final descriptor for the transfer. Tested-by: John Madieu Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260526084710.3491480-19-claudiu.beznea@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 2a7124e4aea3..f1174d25da84 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -200,6 +200,7 @@ struct rz_dmac { /* LINK MODE DESCRIPTOR */ #define HEADER_LV BIT(0) +#define HEADER_LE BIT(1) #define HEADER_WBD BIT(2) #define RZ_DMAC_MAX_CHAN_DESCRIPTORS 16 @@ -382,7 +383,7 @@ static void rz_dmac_prepare_desc_for_memcpy(struct rz_dmac_chan *channel) lmdesc->chcfg = chcfg; lmdesc->chitvl = 0; lmdesc->chext = 0; - lmdesc->header = HEADER_LV; + lmdesc->header = HEADER_LV | HEADER_LE; rz_dmac_set_dma_req_no(dmac, channel->index, dmac->info->default_dma_req_no); @@ -425,7 +426,7 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel) lmdesc->chext = 0; if (i == (sg_len - 1)) { lmdesc->chcfg = (channel->chcfg & ~CHCFG_DEM); - lmdesc->header = HEADER_LV; + lmdesc->header = HEADER_LV | HEADER_LE; } else { lmdesc->chcfg = channel->chcfg; lmdesc->header = HEADER_LV; From c4441b95ae8012a99c6fe22b4f56155e0ddbd042 Mon Sep 17 00:00:00 2001 From: Devendra K Verma Date: Fri, 5 Jun 2026 16:58:29 +0530 Subject: [PATCH 36/62] dmaengine: dw-edma: Add Xilinx CPM6-DMA DeviceID Add Device ID for AMD (Xilinx) CPM6 DMA IP. This IP enables 64 Read and 64 Write Channels. Adding the relevant dw_edma_pcie_data to use 8 Read and 8 Write channels for initial commit. Signed-off-by: Devendra K Verma Reviewed-by: Frank Li Link: https://patch.msgid.link/20260605112829.679697-1-devendra.verma@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-pcie.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/dma/dw-edma/dw-edma-pcie.c b/drivers/dma/dw-edma/dw-edma-pcie.c index 0b30ce138503..2082d0021a8d 100644 --- a/drivers/dma/dw-edma/dw-edma-pcie.c +++ b/drivers/dma/dw-edma/dw-edma-pcie.c @@ -27,6 +27,7 @@ /* AMD MDB (Xilinx) specific defines */ #define PCI_DEVICE_ID_XILINX_B054 0xb054 +#define PCI_DEVICE_ID_XILINX_B00F 0xb00f #define DW_PCIE_XILINX_MDB_VSEC_DMA_ID 0x6 #define DW_PCIE_XILINX_MDB_VSEC_ID 0x20 @@ -125,6 +126,19 @@ static const struct dw_edma_pcie_data xilinx_mdb_data = { .rd_ch_cnt = 8, }; +static const struct dw_edma_pcie_data xilinx_cpm6_dma_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, @@ -547,6 +561,8 @@ 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 }, + { PCI_VDEVICE(XILINX, PCI_DEVICE_ID_XILINX_B00F), + .driver_data = (kernel_ulong_t)&xilinx_cpm6_dma_data }, { } }; MODULE_DEVICE_TABLE(pci, dw_edma_pcie_id_table); From b55bfcc677dd58d808a53173e0574b466dc27b9f Mon Sep 17 00:00:00 2001 From: Sheetal Date: Sun, 17 May 2026 16:30:45 +0000 Subject: [PATCH 37/62] dmaengine: tegra210-adma: Add error logging on failure paths Add dev_err/dev_err_probe logging across failure paths to improve debuggability of DMA errors during runtime and probe. Use return dev_err_probe() pattern where no cleanup is required in the probe function. On error paths that need explicit unwind, store the dev_err_probe() return value in ret before jumping to the cleanup label. Also convert existing dev_err calls in probe to dev_err_probe for consistency, and use dev_err in non-probe functions. Keep explicit runtime PM and DMA registration unwind instead of managed or scoped cleanup. The scoped runtime PM guard releases the usage count with pm_runtime_put(), while this probe error path needs pm_runtime_put_sync() before pm_runtime_disable(). The OF DMA registration failure path also needs to unregister the DMA engine before dropping the runtime PM reference. Signed-off-by: Sheetal Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260517163045.363444-1-sheetal@nvidia.com Signed-off-by: Vinod Koul --- drivers/dma/tegra210-adma.c | 63 ++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/drivers/dma/tegra210-adma.c b/drivers/dma/tegra210-adma.c index 14e0c408ed1e..ceaee1e33e68 100644 --- a/drivers/dma/tegra210-adma.c +++ b/drivers/dma/tegra210-adma.c @@ -335,8 +335,16 @@ static int tegra_adma_request_alloc(struct tegra_adma_chan *tdc, struct tegra_adma *tdma = tdc->tdma; unsigned int sreq_index = tdc->sreq_index; - if (tdc->sreq_reserved) - return tdc->sreq_dir == direction ? 0 : -EINVAL; + if (tdc->sreq_reserved) { + if (tdc->sreq_dir != direction) { + dev_err(tdma->dev, + "DMA request direction mismatch: reserved=%s, requested=%s\n", + dmaengine_get_direction_text(tdc->sreq_dir), + dmaengine_get_direction_text(direction)); + return -EINVAL; + } + return 0; + } if (sreq_index > tdma->cdata->ch_req_max) { dev_err(tdma->dev, "invalid DMA request\n"); @@ -665,8 +673,11 @@ static int tegra_adma_set_xfer_params(struct tegra_adma_chan *tdc, const struct tegra_adma_chip_data *cdata = tdc->tdma->cdata; unsigned int burst_size, adma_dir, fifo_size_shift; - if (desc->num_periods > ADMA_CH_CONFIG_MAX_BUFS) + if (desc->num_periods > ADMA_CH_CONFIG_MAX_BUFS) { + dev_err(tdc2dev(tdc), "invalid DMA periods %zu (max %u)\n", + desc->num_periods, ADMA_CH_CONFIG_MAX_BUFS); return -EINVAL; + } switch (direction) { case DMA_MEM_TO_DEV: @@ -1029,8 +1040,8 @@ static int tegra_adma_probe(struct platform_device *pdev) cdata = of_device_get_match_data(&pdev->dev); if (!cdata) { - dev_err(&pdev->dev, "device match data not found\n"); - return -ENODEV; + return dev_err_probe(&pdev->dev, -ENODEV, + "device match data not found\n"); } tdma = devm_kzalloc(&pdev->dev, @@ -1056,7 +1067,8 @@ static int tegra_adma_probe(struct platform_device *pdev) unsigned int ch_base_offset; if (res_page->start < res_base->start) - return -EINVAL; + return dev_err_probe(&pdev->dev, -EINVAL, + "invalid page/global resource order\n"); page_offset = res_page->start - res_base->start; ch_base_offset = cdata->ch_base_offset; if (!ch_base_offset) @@ -1064,7 +1076,9 @@ static int tegra_adma_probe(struct platform_device *pdev) page_no = div_u64(page_offset, ch_base_offset); if (!page_no || page_no > INT_MAX) - return -EINVAL; + return dev_err_probe(&pdev->dev, -EINVAL, + "invalid page number %llu\n", + (unsigned long long)page_no); tdma->ch_page_no = page_no - 1; tdma->base_addr = devm_ioremap_resource(&pdev->dev, res_base); @@ -1079,7 +1093,8 @@ static int tegra_adma_probe(struct platform_device *pdev) if (IS_ERR(tdma->base_addr)) return PTR_ERR(tdma->base_addr); } else { - return -ENODEV; + return dev_err_probe(&pdev->dev, -ENODEV, + "failed to get memory resource\n"); } tdma->ch_base_addr = tdma->base_addr + cdata->ch_base_offset; @@ -1087,8 +1102,8 @@ static int tegra_adma_probe(struct platform_device *pdev) tdma->ahub_clk = devm_clk_get(&pdev->dev, "d_audio"); if (IS_ERR(tdma->ahub_clk)) { - dev_err(&pdev->dev, "Error: Missing ahub controller clock\n"); - return PTR_ERR(tdma->ahub_clk); + return dev_err_probe(&pdev->dev, PTR_ERR(tdma->ahub_clk), + "failed to get ahub clock\n"); } tdma->dma_chan_mask = devm_kzalloc(&pdev->dev, @@ -1104,8 +1119,8 @@ static int tegra_adma_probe(struct platform_device *pdev) (u32 *)tdma->dma_chan_mask, BITS_TO_U32(tdma->nr_channels)); if (ret < 0 && (ret != -EINVAL)) { - dev_err(&pdev->dev, "dma-channel-mask is not complete.\n"); - return ret; + return dev_err_probe(&pdev->dev, ret, + "dma-channel-mask is not complete.\n"); } INIT_LIST_HEAD(&tdma->dma_dev.channels); @@ -1127,11 +1142,13 @@ static int tegra_adma_probe(struct platform_device *pdev) cdata->global_ch_config_base + (4 * i); } - tdc->irq = of_irq_get(pdev->dev.of_node, i); - if (tdc->irq <= 0) { - ret = tdc->irq ?: -ENXIO; + ret = of_irq_get(pdev->dev.of_node, i); + if (ret <= 0) { + ret = dev_err_probe(&pdev->dev, ret ?: -ENXIO, + "failed to get IRQ for channel %d\n", i); goto irq_dispose; } + tdc->irq = ret; vchan_init(&tdc->vc, &tdma->dma_dev); tdc->vc.desc_free = tegra_adma_desc_free; @@ -1141,12 +1158,18 @@ static int tegra_adma_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); ret = pm_runtime_resume_and_get(&pdev->dev); - if (ret < 0) + if (ret < 0) { + ret = dev_err_probe(&pdev->dev, ret, + "runtime PM resume failed\n"); goto rpm_disable; + } ret = tegra_adma_init(tdma); - if (ret) + if (ret) { + ret = dev_err_probe(&pdev->dev, ret, + "failed to initialize ADMA\n"); goto rpm_put; + } dma_cap_set(DMA_SLAVE, tdma->dma_dev.cap_mask); dma_cap_set(DMA_PRIVATE, tdma->dma_dev.cap_mask); @@ -1172,14 +1195,16 @@ static int tegra_adma_probe(struct platform_device *pdev) ret = dma_async_device_register(&tdma->dma_dev); if (ret < 0) { - dev_err(&pdev->dev, "ADMA registration failed: %d\n", ret); + ret = dev_err_probe(&pdev->dev, ret, + "ADMA registration failed\n"); goto rpm_put; } ret = of_dma_controller_register(pdev->dev.of_node, tegra_dma_of_xlate, tdma); if (ret < 0) { - dev_err(&pdev->dev, "ADMA OF registration failed %d\n", ret); + ret = dev_err_probe(&pdev->dev, ret, + "ADMA OF registration failed\n"); goto dma_remove; } From cc4fea19daeb0460fe3569e0a2d523f427b2bac1 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sat, 30 May 2026 19:08:43 -0700 Subject: [PATCH 38/62] dmaengine: ste_dma40: turn d40_base phy_chans into a flexible array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert the separately-offset phy_chans pointer to a C99 flexible array member at the end of struct d40_base, and switch the allocation to struct_size(). The log_chans and memcpy_chans slots continue to live in the same allocation immediately after phy_chans, indexed via base->log_chans. This removes the hand-rolled pointer fixup that recomputed phy_chans from base + ALIGN(sizeof(struct d40_base), 4). The ALIGN(sizeof(struct d40_base), 4) requirement is met implicitly by the C compiler when using a flexible array member. With struct d40_chan phy_chans[] as the last member, the C standard guarantees sizeof(struct d40_base) includes trailing padding to satisfy the alignment of the flexible array element type (struct d40_chan). Since struct d40_chan contains members like spinlock_t, pointers, and struct dma_chan — all with alignment ≥ 4 — the compiler ensures sizeof(struct d40_base) is already a multiple of _Alignof(struct d40_chan) >= 4. The struct_size() macro then computes sizeof(struct d40_base) + sizeof(struct d40_chan) * num_phy_chans, so phy_chans[0] lands at a properly aligned offset without needing the manual ALIGN. Assisted-by: Claude:Opus-4.7 Signed-off-by: Rosen Penev Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260531020843.594892-1-rosenp@gmail.com Signed-off-by: Vinod Koul --- drivers/dma/ste_dma40.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index 9b803c0aec25..0d9ffa3e2663 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -602,7 +602,6 @@ struct d40_base { struct dma_device dma_both; struct dma_device dma_slave; struct dma_device dma_memcpy; - struct d40_chan *phy_chans; struct d40_chan *log_chans; struct d40_chan **lookup_log_chans; struct d40_chan **lookup_phy_chans; @@ -621,6 +620,7 @@ struct d40_base { u32 *regs_interrupt; u16 gcc_pwr_off_mask; struct d40_gen_dmac gen_dmac; + struct d40_chan phy_chans[]; }; static struct device *chan2dev(struct d40_chan *d40c) @@ -3128,6 +3128,7 @@ static int __init d40_hw_detect_init(struct platform_device *pdev, struct clk *clk; void __iomem *virtbase; struct d40_base *base; + size_t alloc_size; int num_log_chans; int num_phy_chans; int num_memcpy_chans; @@ -3185,22 +3186,24 @@ static int __init d40_hw_detect_init(struct platform_device *pdev, else num_phy_chans = 4 * (readl(virtbase + D40_DREG_ICFG) & 0x7) + 4; + num_phy_chans = min(num_phy_chans, STEDMA40_MAX_PHYS); + /* The number of channels used for memcpy */ if (plat_data->num_of_memcpy_chans) num_memcpy_chans = plat_data->num_of_memcpy_chans; else num_memcpy_chans = ARRAY_SIZE(dma40_memcpy_channels); + num_memcpy_chans = min(num_memcpy_chans, D40_MEMCPY_MAX_CHANS); num_log_chans = num_phy_chans * D40_MAX_LOG_CHAN_PER_PHY; dev_info(dev, "hardware rev: %d with %d physical and %d logical channels\n", rev, num_phy_chans, num_log_chans); - base = devm_kzalloc(dev, - ALIGN(sizeof(struct d40_base), 4) + - (num_phy_chans + num_log_chans + num_memcpy_chans) * - sizeof(struct d40_chan), GFP_KERNEL); + alloc_size = struct_size(base, phy_chans, num_phy_chans); + alloc_size += sizeof(*base->log_chans) * (num_log_chans + num_memcpy_chans); + base = devm_kzalloc(dev, alloc_size, GFP_KERNEL); if (!base) return -ENOMEM; @@ -3213,7 +3216,6 @@ static int __init d40_hw_detect_init(struct platform_device *pdev, base->virtbase = virtbase; base->plat_data = plat_data; base->dev = dev; - base->phy_chans = ((void *)base) + ALIGN(sizeof(struct d40_base), 4); base->log_chans = &base->phy_chans[num_phy_chans]; if (base->plat_data->num_of_phy_chans == 14) { From 4e351f408743354d54ee1af5193fc78234f2044e Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Tue, 2 Jun 2026 15:03:44 +0800 Subject: [PATCH 39/62] dmaengine: qcom: gpi: set DMA_PRIVATE capability The GPI DMA controller is only responsible for QUP peripherals, and cannot work as a general-purpose DMA accelerator. Set DMA_PRIVATE capability for it. This fixes error messages about GPI being shown when an async-tx consumer is loaded. Fixes: 5d0c3533a19f ("dmaengine: qcom: Add GPI dma driver") Signed-off-by: Icenowy Zheng Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260602070344.3707256-1-zhengxingda@iscas.ac.cn Signed-off-by: Vinod Koul --- drivers/dma/qcom/gpi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/dma/qcom/gpi.c b/drivers/dma/qcom/gpi.c index c9a6f610ffd9..a5055a6273af 100644 --- a/drivers/dma/qcom/gpi.c +++ b/drivers/dma/qcom/gpi.c @@ -2260,6 +2260,7 @@ static int gpi_probe(struct platform_device *pdev) /* clear and Set capabilities */ dma_cap_zero(gpi_dev->dma_device.cap_mask); dma_cap_set(DMA_SLAVE, gpi_dev->dma_device.cap_mask); + dma_cap_set(DMA_PRIVATE, gpi_dev->dma_device.cap_mask); /* configure dmaengine apis */ gpi_dev->dma_device.directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); From 87aab0781cb35baa2214d0a95ff01e786e428226 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sat, 30 May 2026 13:03:22 -0700 Subject: [PATCH 40/62] dmaengine: dmatest: split struct dmatest_info from variable declaration Combining the struct definition with its variable initializer confuses the kernel-doc parser because __MUTEX_INITIALIZER() expands to contain braces, breaking brace counting and causing: Warning: drivers/dma/dmatest.c:152 struct member '' not described in 'dmatest_info' Split into separate struct definition and variable declaration, which is the standard kernel pattern. Assisted-by: Opencode:Big-pickle Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260530200322.7584-1-rosenp@gmail.com Signed-off-by: Vinod Koul --- drivers/dma/dmatest.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/dma/dmatest.c b/drivers/dma/dmatest.c index df38681a1ff4..2ae3469397f3 100644 --- a/drivers/dma/dmatest.c +++ b/drivers/dma/dmatest.c @@ -137,7 +137,7 @@ struct dmatest_params { * @did_init: module has been initialized completely * @last_error: test has faced configuration issues */ -static struct dmatest_info { +struct dmatest_info { /* Test parameters */ struct dmatest_params params; @@ -147,7 +147,9 @@ static struct dmatest_info { int last_error; struct mutex lock; bool did_init; -} test_info = { +}; + +static struct dmatest_info test_info = { .channels = LIST_HEAD_INIT(test_info.channels), .lock = __MUTEX_INITIALIZER(test_info.lock), }; From 1d736d76c7d16359bae042b8c9fbb0fdac158721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 28 May 2026 15:50:10 +0200 Subject: [PATCH 41/62] dmaengine: cirrus: Drop left-over from platform probing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 2e7f55ce4302 ("dmaengine: cirrus: Convert to DT for Cirrus EP93xx") the driver cannot probe devices using the traditional platform device way any more. Thus the driver's .id_table serves no purpose any more and can be dropped. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/c3830cb95b0bb939f9cc9543dfa3047e41532c47.1779976024.git.ukleinek@kernel.org Signed-off-by: Vinod Koul --- drivers/dma/ep93xx_dma.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/dma/ep93xx_dma.c b/drivers/dma/ep93xx_dma.c index 8eceb96d058c..a3395cfcf5dd 100644 --- a/drivers/dma/ep93xx_dma.c +++ b/drivers/dma/ep93xx_dma.c @@ -1587,18 +1587,11 @@ static const struct of_device_id ep93xx_dma_of_ids[] = { }; MODULE_DEVICE_TABLE(of, ep93xx_dma_of_ids); -static const struct platform_device_id ep93xx_dma_driver_ids[] = { - { "ep93xx-dma-m2p", 0 }, - { "ep93xx-dma-m2m", 1 }, - { }, -}; - static struct platform_driver ep93xx_dma_driver = { .driver = { .name = "ep93xx-dma", .of_match_table = ep93xx_dma_of_ids, }, - .id_table = ep93xx_dma_driver_ids, .probe = ep93xx_dma_probe, }; From ead262712217296cad7d9db1707e504f16e2cb0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 28 May 2026 12:55:33 +0200 Subject: [PATCH 42/62] dmaengine: nbpfaxi: Drop unused platform_device_id array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dma-nbpf driver only probes devices from device tree and fails to probe devices relying on the traditional platform device probe path. So the platform_device_id array is unused apart from providing misleading module meta data. Drop it. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/5f7380828873e2375e319ef091178d11a277a0ac.1779965563.git.u.kleine-koenig@baylibre.com Signed-off-by: Vinod Koul --- drivers/dma/nbpfaxi.c | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/drivers/dma/nbpfaxi.c b/drivers/dma/nbpfaxi.c index 334425faac00..05d7321629cc 100644 --- a/drivers/dma/nbpfaxi.c +++ b/drivers/dma/nbpfaxi.c @@ -1486,20 +1486,6 @@ static void nbpf_remove(struct platform_device *pdev) clk_disable_unprepare(nbpf->clk); } -static const struct platform_device_id nbpf_ids[] = { - {"nbpfaxi64dmac1b4", (kernel_ulong_t)&nbpf_cfg[NBPF1B4]}, - {"nbpfaxi64dmac1b8", (kernel_ulong_t)&nbpf_cfg[NBPF1B8]}, - {"nbpfaxi64dmac1b16", (kernel_ulong_t)&nbpf_cfg[NBPF1B16]}, - {"nbpfaxi64dmac4b4", (kernel_ulong_t)&nbpf_cfg[NBPF4B4]}, - {"nbpfaxi64dmac4b8", (kernel_ulong_t)&nbpf_cfg[NBPF4B8]}, - {"nbpfaxi64dmac4b16", (kernel_ulong_t)&nbpf_cfg[NBPF4B16]}, - {"nbpfaxi64dmac8b4", (kernel_ulong_t)&nbpf_cfg[NBPF8B4]}, - {"nbpfaxi64dmac8b8", (kernel_ulong_t)&nbpf_cfg[NBPF8B8]}, - {"nbpfaxi64dmac8b16", (kernel_ulong_t)&nbpf_cfg[NBPF8B16]}, - {}, -}; -MODULE_DEVICE_TABLE(platform, nbpf_ids); - static int nbpf_runtime_suspend(struct device *dev) { struct nbpf_device *nbpf = dev_get_drvdata(dev); @@ -1523,7 +1509,6 @@ static struct platform_driver nbpf_driver = { .of_match_table = nbpf_match, .pm = pm_ptr(&nbpf_pm_ops), }, - .id_table = nbpf_ids, .probe = nbpf_probe, .remove = nbpf_remove, }; From 57e766bd3ddb2495d80952ad4fc723fb538e1d43 Mon Sep 17 00:00:00 2001 From: Devendra K Verma Date: Tue, 26 May 2026 11:01:10 +0530 Subject: [PATCH 43/62] dmaengine: dw-edma: Remove dw_edma_add_irq_mask() Function dw_edma_add_irq_mask() sets the mask of the interrupts alloted to read / write channels in a variable. The mask set for read / write channels is niether used nor this function is called else where, making it redundant. The redundant function can be removed safely as it is not affecting anything. Signed-off-by: Devendra K Verma Reviewed-by: Frank Li Link: https://patch.msgid.link/20260526053111.3244488-1-devverma@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index c2feb3adc79f..89a4c498a17b 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -988,20 +988,12 @@ static inline void dw_edma_dec_irq_alloc(int *nr_irqs, u32 *alloc, u16 cnt) } } -static inline void dw_edma_add_irq_mask(u32 *mask, u32 alloc, u16 cnt) -{ - while (*mask * alloc < cnt) - (*mask)++; -} - static int dw_edma_irq_request(struct dw_edma *dw, u32 *wr_alloc, u32 *rd_alloc) { struct dw_edma_chip *chip = dw->chip; struct device *dev = dw->chip->dev; struct msi_desc *msi_desc; - u32 wr_mask = 1; - u32 rd_mask = 1; int i, err = 0; u32 ch_cnt; int irq; @@ -1038,9 +1030,6 @@ static int dw_edma_irq_request(struct dw_edma *dw, dw_edma_dec_irq_alloc(&tmp, rd_alloc, dw->rd_ch_cnt); } - dw_edma_add_irq_mask(&wr_mask, *wr_alloc, dw->wr_ch_cnt); - dw_edma_add_irq_mask(&rd_mask, *rd_alloc, dw->rd_ch_cnt); - for (i = 0; i < (*wr_alloc + *rd_alloc); i++) { irq = chip->ops->irq_vector(dev, i); err = request_irq(irq, From 5d596b9139f59ce412f41283baadaf809936eaf4 Mon Sep 17 00:00:00 2001 From: John Madieu Date: Mon, 25 May 2026 11:07:49 +0000 Subject: [PATCH 44/62] irqchip/renesas-rzv2h: Add DMA ACK signal routing support Some peripherals on RZ/G3E SoCs (SSIU, SPDIF, SCU/SRC, DVC) require explicit ACK signal routing through the ICU via the ICU_DMACKSELk registers for level-based DMA handshaking. Add rzv2h_icu_register_dma_ack() to configure ICU_DMACKSELk, routing a DMAC channel's ACK signal to the specified peripheral. Signed-off-by: John Madieu Acked-by: Thomas Gleixner Link: https://patch.msgid.link/20260525110750.4020112-2-john.madieu.xa@bp.renesas.com Signed-off-by: Vinod Koul --- drivers/irqchip/irq-renesas-rzv2h.c | 40 +++++++++++++++++++++++ include/linux/irqchip/irq-renesas-rzv2h.h | 5 +++ 2 files changed, 45 insertions(+) diff --git a/drivers/irqchip/irq-renesas-rzv2h.c b/drivers/irqchip/irq-renesas-rzv2h.c index 31c543c876b1..971ac83eee90 100644 --- a/drivers/irqchip/irq-renesas-rzv2h.c +++ b/drivers/irqchip/irq-renesas-rzv2h.c @@ -151,6 +151,12 @@ struct rzv2h_hw_info { #define ICU_DMAC_PREP_DMAREQ(sel, up) (FIELD_PREP(ICU_DMAC_DkRQ_SEL_MASK, (sel)) \ << ICU_DMAC_DMAREQ_SHIFT(up)) +/* DMAC ACK routing - 4 x 7-bit fields per 32-bit register, 8-bit spacing */ +#define ICU_DMAC_DACK_SEL_MASK GENMASK(6, 0) +#define ICU_DMAC_DACK_SHIFT(n) ((n) * 8) +#define ICU_DMAC_DACK_FIELD_MASK(n) (ICU_DMAC_DACK_SEL_MASK << ICU_DMAC_DACK_SHIFT(n)) +#define ICU_DMAC_PREP_DACK(val, n) (((val) & ICU_DMAC_DACK_SEL_MASK) << ICU_DMAC_DACK_SHIFT(n)) + /** * struct rzv2h_icu_priv - Interrupt Control Unit controller private data structure. * @base: Controller's base address @@ -188,6 +194,40 @@ void rzv2h_icu_register_dma_req(struct platform_device *icu_dev, u8 dmac_index, } EXPORT_SYMBOL_GPL(rzv2h_icu_register_dma_req); +/** + * rzv2h_icu_register_dma_ack - Configure DMA ACK signal routing + * @icu_dev: ICU platform device + * @dmac_index: DMAC instance index (0-4) + * @dmac_channel: DMAC channel number (0-15), or RZV2H_ICU_DMAC_ACK_NO_DEFAULT + * to disconnect routing for a given ack_no + * @ack_no: Peripheral ACK number (0-88) per RZ/G3E manual Table 4.6-28, + * used as index into ICU_DMACKSELk + * + * Routes the ACK signal of the peripheral identified by @ack_no to DMAC + * channel @dmac_channel of instance @dmac_index. When @dmac_channel is + * RZV2H_ICU_DMAC_ACK_NO_DEFAULT the field is reset, disconnecting any + * previously configured routing for that peripheral. + */ +void rzv2h_icu_register_dma_ack(struct platform_device *icu_dev, u8 dmac_index, + u8 dmac_channel, u16 ack_no) +{ + struct rzv2h_icu_priv *priv = platform_get_drvdata(icu_dev); + u8 reg_idx = ack_no / 4; + u8 field_idx = ack_no & 0x3; + u8 dmac_ack_src = (dmac_channel == RZV2H_ICU_DMAC_ACK_NO_DEFAULT) ? + RZV2H_ICU_DMAC_ACK_NO_DEFAULT : + (dmac_index * 16 + dmac_channel); + u32 val; + + guard(raw_spinlock_irqsave)(&priv->lock); + + val = readl(priv->base + ICU_DMACKSELk(reg_idx)); + val &= ~ICU_DMAC_DACK_FIELD_MASK(field_idx); + val |= ICU_DMAC_PREP_DACK(dmac_ack_src, field_idx); + writel(val, priv->base + ICU_DMACKSELk(reg_idx)); +} +EXPORT_SYMBOL_GPL(rzv2h_icu_register_dma_ack); + static inline struct rzv2h_icu_priv *irq_data_to_priv(struct irq_data *data) { return data->domain->host_data; diff --git a/include/linux/irqchip/irq-renesas-rzv2h.h b/include/linux/irqchip/irq-renesas-rzv2h.h index 618a60d2eac0..4ffa898eaaf2 100644 --- a/include/linux/irqchip/irq-renesas-rzv2h.h +++ b/include/linux/irqchip/irq-renesas-rzv2h.h @@ -11,13 +11,18 @@ #include #define RZV2H_ICU_DMAC_REQ_NO_DEFAULT 0x3ff +#define RZV2H_ICU_DMAC_ACK_NO_DEFAULT 0x7f #ifdef CONFIG_RENESAS_RZV2H_ICU void rzv2h_icu_register_dma_req(struct platform_device *icu_dev, u8 dmac_index, u8 dmac_channel, u16 req_no); +void rzv2h_icu_register_dma_ack(struct platform_device *icu_dev, u8 dmac_index, + u8 dmac_channel, u16 ack_no); #else static inline void rzv2h_icu_register_dma_req(struct platform_device *icu_dev, u8 dmac_index, u8 dmac_channel, u16 req_no) { } +static inline void rzv2h_icu_register_dma_ack(struct platform_device *icu_dev, u8 dmac_index, + u8 dmac_channel, u16 ack_no) { } #endif #endif /* __LINUX_IRQ_RENESAS_RZV2H */ From c0a207898fca8cbb4fad0da1e950d477b6afbf64 Mon Sep 17 00:00:00 2001 From: John Madieu Date: Mon, 25 May 2026 11:07:50 +0000 Subject: [PATCH 45/62] dmaengine: sh: rz-dmac: Add DMA ACK signal routing support Some peripherals on RZ/G3E SoCs (SSIU, SPDIF, SCU/SRC, DVC, PFC) require explicit ACK signal routing through the ICU for level-based DMA handshaking. Rather than extending the DT binding with an optional second #dma-cells (which would require all DMA consumers to supply two cells even when ACK routing is not needed), derive the ACK signal number directly from the MID/RID request number using the linear mapping defined in RZ/G3E hardware manual Table 4.6-28: PFC external DMA pins (DREQ0..DREQ4): req_no 0x000-0x004 -> ACK No. 84-88 SSIU BUSIFs (ssip00..ssip93): req_no 0x161-0x198 -> ACK No. 28-83 SPDIF (CH0..CH2) + SCU SRC (sr0..sr9) + DVC (cmd0..cmd1): req_no 0x199-0x1b4 -> ACK No. 0-27 ACK routing is programmed when a channel is prepared for transfer and cleared when the channel is released or the transfer times out, following the same pattern as MID/RID request routing. Signed-off-by: John Madieu Link: https://patch.msgid.link/20260525110750.4020112-3-john.madieu.xa@bp.renesas.com [fixes subsystem name tag] Signed-off-by: Vinod Koul --- drivers/dma/sh/rz-dmac.c | 69 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index f1174d25da84..ca76f1bb45c4 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -93,6 +93,7 @@ struct rz_dmac_chan { u32 chcfg; u32 chctrl; int mid_rid; + int dmac_ack; struct { u32 nxla; @@ -118,6 +119,9 @@ struct rz_dmac_icu { struct rz_dmac_info { void (*icu_register_dma_req)(struct platform_device *icu_dev, u8 dmac_index, u8 dmac_channel, u16 req_no); + void (*icu_register_dma_ack)(struct platform_device *icu_dev, + u8 dmac_index, u8 dmac_channel, u16 ack_no); + u16 default_dma_ack_no; u16 default_dma_req_no; }; @@ -366,6 +370,60 @@ static void rz_dmac_set_dma_req_no(struct rz_dmac *dmac, unsigned int index, rz_dmac_set_dmars_register(dmac, index, req_no); } +/* + * Map MID/RID request number (bits[0:9] of DMA specifier) to the ICU + * DMA ACK signal number, per RZ/G3E hardware manual Table 4.6-28. + * + * Three peripheral groups cover all ACK-capable peripherals: + * + * PFC external DMA pins (DREQ0..DREQ4): + * req_no 0x000-0x004 -> ACK No. 84-88 (ack = req_no + 84) + * + * SSIU BUSIFs (ssip00..ssip93): + * req_no 0x161-0x198 -> ACK No. 28-83 (ack = req_no - 0x145) + * + * SPDIF (CH0..CH2) + SCU SRC (sr0..sr9) + DVC (cmd0..cmd1): + * req_no 0x199-0x1b4 -> ACK No. 0-27 (ack = req_no - 0x199) + */ +static int rz_dmac_get_ack_no(const struct rz_dmac_info *info, u16 req_no) +{ + if (!info->icu_register_dma_ack) + return -EINVAL; + + switch (req_no) { + case 0x000 ... 0x004: + /* PFC external DMA pins: ACK No. 84-88 */ + return req_no + 84; + case 0x161 ... 0x198: + /* SSIU BUSIFs: ACK No. 28-83 */ + return req_no - 0x145; + case 0x199 ... 0x1b4: + /* SPDIF + SCU SRC + DVC: ACK No. 0-27 */ + return req_no - 0x199; + default: + return -EINVAL; + } +} + +static void rz_dmac_set_dma_ack_no(struct rz_dmac *dmac, unsigned int index, + int ack_no) +{ + if (ack_no < 0 || !dmac->info->icu_register_dma_ack) + return; + + dmac->info->icu_register_dma_ack(dmac->icu.pdev, dmac->icu.dmac_index, + index, ack_no); +} + +static void rz_dmac_reset_dma_ack_no(struct rz_dmac *dmac, int ack_no) +{ + if (ack_no < 0 || !dmac->info->icu_register_dma_ack) + return; + + dmac->info->icu_register_dma_ack(dmac->icu.pdev, dmac->icu.dmac_index, + dmac->info->default_dma_ack_no, ack_no); +} + static void rz_dmac_prepare_desc_for_memcpy(struct rz_dmac_chan *channel) { struct dma_chan *chan = &channel->vc.chan; @@ -438,6 +496,7 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel) channel->lmdesc.tail = lmdesc; rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid); + rz_dmac_set_dma_ack_no(dmac, channel->index, channel->dmac_ack); channel->chctrl = 0; } @@ -491,6 +550,7 @@ static void rz_dmac_prepare_descs_for_cyclic(struct rz_dmac_chan *channel) channel->lmdesc.tail = lmdesc; rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid); + rz_dmac_set_dma_ack_no(dmac, channel->index, channel->dmac_ack); } static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan) @@ -579,6 +639,8 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan) } channel->status = 0; + rz_dmac_reset_dma_ack_no(dmac, channel->dmac_ack); + channel->dmac_ack = -EINVAL; spin_unlock_irqrestore(&channel->vc.lock, flags); @@ -853,6 +915,7 @@ static void rz_dmac_device_synchronize(struct dma_chan *chan) dev_warn(dmac->dev, "DMA Timeout"); rz_dmac_set_dma_req_no(dmac, channel->index, dmac->info->default_dma_req_no); + rz_dmac_reset_dma_ack_no(dmac, channel->dmac_ack); } static struct rz_lmdesc * @@ -1190,6 +1253,8 @@ static bool rz_dmac_chan_filter(struct dma_chan *chan, void *arg) channel->chcfg = CHCFG_FILL_TM(ch_cfg) | CHCFG_FILL_AM(ch_cfg) | CHCFG_FILL_LVL(ch_cfg) | CHCFG_FILL_HIEN(ch_cfg); + channel->dmac_ack = rz_dmac_get_ack_no(dmac->info, channel->mid_rid); + return !test_and_set_bit(channel->mid_rid, dmac->modules); } @@ -1226,6 +1291,7 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, channel->index = index; channel->mid_rid = -EINVAL; + channel->dmac_ack = -EINVAL; /* Set io base address for each channel */ if (index < 8) { @@ -1572,6 +1638,7 @@ static int rz_dmac_resume(struct device *dev) continue; rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid); + rz_dmac_set_dma_ack_no(dmac, channel->index, channel->dmac_ack); rz_dmac_ch_writel(channel, channel->pm_state.nxla, NXLA, 1); rz_dmac_ch_writel(channel, channel->chcfg, CHCFG, 1); @@ -1593,6 +1660,8 @@ static DEFINE_SIMPLE_DEV_PM_OPS(rz_dmac_pm_ops, rz_dmac_suspend, rz_dmac_resume) static const struct rz_dmac_info rz_dmac_v2h_info = { .icu_register_dma_req = rzv2h_icu_register_dma_req, + .icu_register_dma_ack = rzv2h_icu_register_dma_ack, + .default_dma_ack_no = RZV2H_ICU_DMAC_ACK_NO_DEFAULT, .default_dma_req_no = RZV2H_ICU_DMAC_REQ_NO_DEFAULT, }; From 11d7cfe0c119691b2dafbb699bbca90258c678aa Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Thu, 21 May 2026 23:21:51 +0900 Subject: [PATCH 46/62] dmaengine: dw-edma-pcie: Reject devices without driver data dw_edma_pcie_probe() treats the PCI device ID driver_data as the template for the controller layout and copies it unconditionally. A device bound dynamically via sysfs can match the driver without that data, which leads to a NULL pointer dereference. Reject such matches before enabling the device. Fixes: 41aaff2a2ac0 ("dmaengine: Add Synopsys eDMA IP PCIe glue-logic") Cc: stable@vger.kernel.org Signed-off-by: Koichiro Den Reviewed-by: Frank Li Link: https://patch.msgid.link/20260521142153.2957432-3-den@valinux.co.jp Signed-off-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-pcie.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/dma/dw-edma/dw-edma-pcie.c b/drivers/dma/dw-edma/dw-edma-pcie.c index 2082d0021a8d..791c46e8ae4c 100644 --- a/drivers/dma/dw-edma/dw-edma-pcie.c +++ b/drivers/dma/dw-edma/dw-edma-pcie.c @@ -328,6 +328,9 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, int i, mask; bool non_ll = false; + if (!pdata) + return -ENODEV; + struct dw_edma_pcie_data *vsec_data __free(kfree) = kmalloc_obj(*vsec_data); if (!vsec_data) From 8ffba0171c6bbce5f093c6dba5a02c0805b31203 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 21 May 2026 23:21:53 +0900 Subject: [PATCH 47/62] dmaengine: dw-edma: Add spinlock to protect DONE_INT_MASK and ABORT_INT_MASK The DONE_INT_MASK and ABORT_INT_MASK registers are shared by all DMA channels, and modifying them requires a read-modify-write sequence. Because this operation is not atomic, concurrent calls to dw_edma_v0_core_start() can introduce race conditions if two channels update these registers simultaneously. Add a spinlock to serialize access to these registers and prevent race conditions. Fixes: 7e4b8a4fbe2c ("dmaengine: Add Synopsys eDMA IP version 0 support") Cc: stable@vger.kernel.org Signed-off-by: Frank Li [den: update dw_edma.lock comment] Link: https://lore.kernel.org/dmaengine/20260109-edma_ll-v2-1-5c0b27b2c664@nxp.com/ Signed-off-by: Koichiro Den Link: https://patch.msgid.link/20260521142153.2957432-5-den@valinux.co.jp Signed-off-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.h | 2 +- drivers/dma/dw-edma/dw-edma-v0-core.c | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h index 902574b1ba86..6474cacf7195 100644 --- a/drivers/dma/dw-edma/dw-edma-core.h +++ b/drivers/dma/dw-edma/dw-edma-core.h @@ -109,7 +109,7 @@ struct dw_edma { struct dw_edma_chan *chan; - raw_spinlock_t lock; /* Only for legacy */ + raw_spinlock_t lock; /* Protect v0 shared registers */ struct dw_edma_chip *chip; diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c index 69e8279adec8..cfdd6463252e 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-core.c +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c @@ -364,6 +364,7 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first) { struct dw_edma_chan *chan = chunk->chan; struct dw_edma *dw = chan->dw; + unsigned long flags; u32 tmp; dw_edma_v0_core_write_chunk(chunk); @@ -408,6 +409,8 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first) } } /* Interrupt unmask - done, abort */ + raw_spin_lock_irqsave(&dw->lock, flags); + tmp = GET_RW_32(dw, chan->dir, int_mask); tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id)); tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id)); @@ -416,6 +419,9 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first) tmp = GET_RW_32(dw, chan->dir, linked_list_err_en); tmp |= FIELD_PREP(EDMA_V0_LINKED_LIST_ERR_MASK, BIT(chan->id)); SET_RW_32(dw, chan->dir, linked_list_err_en, tmp); + + raw_spin_unlock_irqrestore(&dw->lock, flags); + /* Channel control */ SET_CH_32(dw, chan->dir, chan->id, ch_control1, (DW_EDMA_V0_CCS | DW_EDMA_V0_LLE)); From 92f853f0645aebf1d05d333e97ab7c342ace1892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 24 Apr 2026 18:40:14 +0100 Subject: [PATCH 48/62] dmaengine: Fix possible use after free MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In dma_release_channel(), check chan->device->privatecnt after call dma_chan_put(). However, dma_chan_put() call dma_device_put() which could release the last reference of the device if the DMA provider is already gone and hence free it. Fixes it by moving dma_chan_put() after the check. Fixes: 0f571515c332 ("dmaengine: Add privatecnt to revert DMA_PRIVATE property") Signed-off-by: Nuno Sá Reviewed-by: Frank Li Link: https://patch.msgid.link/20260424-dma-dmac-handle-vunmap-v4-1-90f43412fdc0@analog.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index 405bd2fbb4a3..9049171df857 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -905,11 +905,12 @@ void dma_release_channel(struct dma_chan *chan) mutex_lock(&dma_list_mutex); WARN_ONCE(chan->client_count != 1, "chan reference count %d != 1\n", chan->client_count); - dma_chan_put(chan); /* drop PRIVATE cap enabled by __dma_request_channel() */ if (--chan->device->privatecnt == 0) dma_cap_clear(DMA_PRIVATE, chan->device->cap_mask); + dma_chan_put(chan); + if (chan->slave) { sysfs_remove_link(&chan->dev->device.kobj, DMA_SLAVE_NAME); sysfs_remove_link(&chan->slave->kobj, chan->name); From 4910ce1b3b35687bb2a5e742c4bfbea3c647c980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 24 Apr 2026 18:40:15 +0100 Subject: [PATCH 49/62] dmaengine: dma-axi-dmac: Properly free struct axi_dmac_desc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use axi_dmac_free_desc() to free fully the descriptor at fail path when call axi_dmac_alloc_desc() in axi_dmac_prep_peripheral_dma_vec(). Fixes: 74609e568670 ("dmaengine: dma-axi-dmac: Implement device_prep_peripheral_dma_vec") Signed-off-by: Nuno Sá Reviewed-by: Frank Li Link: https://patch.msgid.link/20260424-dma-dmac-handle-vunmap-v4-2-90f43412fdc0@analog.com Signed-off-by: Vinod Koul --- drivers/dma/dma-axi-dmac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c index 45c2c8e4bc45..127c3cf80a0e 100644 --- a/drivers/dma/dma-axi-dmac.c +++ b/drivers/dma/dma-axi-dmac.c @@ -769,7 +769,7 @@ axi_dmac_prep_peripheral_dma_vec(struct dma_chan *c, const struct dma_vec *vecs, for (i = 0; i < nb; i++) { if (!axi_dmac_check_addr(chan, vecs[i].addr) || !axi_dmac_check_len(chan, vecs[i].len)) { - kfree(desc); + axi_dmac_free_desc(desc); return NULL; } From a725ac2055271fe8123fa854bfeff6e349f7cf0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 24 Apr 2026 18:40:16 +0100 Subject: [PATCH 50/62] dmaengine: dma-axi-dmac: Drop struct clk from main struct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There's no reason to keep struct clk in struct axi_dmac. Hence, use a local clk variable in .probe() and drop it from struct axi_dmac. Signed-off-by: Nuno Sá Reviewed-by: Frank Li Link: https://patch.msgid.link/20260424-dma-dmac-handle-vunmap-v4-3-90f43412fdc0@analog.com Signed-off-by: Vinod Koul --- drivers/dma/dma-axi-dmac.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c index 127c3cf80a0e..41898d594be7 100644 --- a/drivers/dma/dma-axi-dmac.c +++ b/drivers/dma/dma-axi-dmac.c @@ -170,8 +170,6 @@ struct axi_dmac { void __iomem *base; int irq; - struct clk *clk; - struct dma_device dma_dev; struct axi_dmac_chan chan; }; @@ -1198,6 +1196,7 @@ static int axi_dmac_probe(struct platform_device *pdev) { struct dma_device *dma_dev; struct axi_dmac *dmac; + struct clk *clk; struct regmap *regmap; unsigned int version; u32 irq_mask = 0; @@ -1217,9 +1216,9 @@ static int axi_dmac_probe(struct platform_device *pdev) if (IS_ERR(dmac->base)) return PTR_ERR(dmac->base); - dmac->clk = devm_clk_get_enabled(&pdev->dev, NULL); - if (IS_ERR(dmac->clk)) - return PTR_ERR(dmac->clk); + clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); version = axi_dmac_read(dmac, ADI_AXI_REG_VERSION); From 9e942c8579130e62734c14338e9f451780669164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 24 Apr 2026 18:40:17 +0100 Subject: [PATCH 51/62] dmaengine: dma-axi-dmac: use DMA pool to manange DMA descriptor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For architectures like Microblaze or arm64 (where this IP is used), DMA_DIRECT_REMAP is set which means that dma_alloc_coherent() might remap (and hence vmalloc()) some memory. This became visible in a design where dma_direct_use_pool() is not possible. With the above, when calling dma_free_coherent(), vunmap() would be called from softirq context and thus leading to a BUG(). To fix it, use a dma pool that is allocated in .device_alloc_chan_resources() and allocate blocks from it. The key point is that now dma_pool_free() is used in axi_dmac_free_desc() to free the blocks and that just frees the blocks from the pool in the sense they can be used again. In other words, no actual call to dma_free_coherent() happens. That only happens when destroying the pool in axi_dmac_free_chan_resources() which does not happen in any interrupt context. Fixes: 3f8fd25936ee ("dmaengine: axi-dmac: Allocate hardware descriptors") Signed-off-by: Nuno Sá Reviewed-by: Frank Li Link: https://patch.msgid.link/20260424-dma-dmac-handle-vunmap-v4-4-90f43412fdc0@analog.com Signed-off-by: Vinod Koul --- drivers/dma/dma-axi-dmac.c | 66 +++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c index 41898d594be7..d47ff27e1408 100644 --- a/drivers/dma/dma-axi-dmac.c +++ b/drivers/dma/dma-axi-dmac.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -147,6 +148,7 @@ struct axi_dmac_chan { struct virt_dma_chan vchan; struct axi_dmac_desc *next_desc; + void *pool; struct list_head active_descs; enum dma_transfer_direction direction; @@ -648,11 +650,17 @@ static void axi_dmac_issue_pending(struct dma_chan *c) spin_unlock_irqrestore(&chan->vchan.lock, flags); } +static void axi_dmac_free_desc(struct axi_dmac_desc *desc) +{ + for (unsigned int i = 0; i < desc->num_sgs; i++) + dma_pool_free(desc->chan->pool, desc->sg[i].hw, desc->sg[i].hw_phys); + + kfree(desc); +} + static struct axi_dmac_desc * axi_dmac_alloc_desc(struct axi_dmac_chan *chan, unsigned int num_sgs) { - struct axi_dmac *dmac = chan_to_axi_dmac(chan); - struct device *dev = dmac->dma_dev.dev; struct axi_dmac_hw_desc *hws; struct axi_dmac_desc *desc; dma_addr_t hw_phys; @@ -664,22 +672,22 @@ axi_dmac_alloc_desc(struct axi_dmac_chan *chan, unsigned int num_sgs) desc->num_sgs = num_sgs; desc->chan = chan; - hws = dma_alloc_coherent(dev, PAGE_ALIGN(num_sgs * sizeof(*hws)), - &hw_phys, GFP_ATOMIC); - if (!hws) { - kfree(desc); - return NULL; - } - for (i = 0; i < num_sgs; i++) { - desc->sg[i].hw = &hws[i]; - desc->sg[i].hw_phys = hw_phys + i * sizeof(*hws); + hws = dma_pool_zalloc(chan->pool, GFP_NOWAIT, &hw_phys); + if (!hws) { + desc->num_sgs = i; + axi_dmac_free_desc(desc); + return NULL; + } - hws[i].id = AXI_DMAC_SG_UNUSED; - hws[i].flags = 0; + desc->sg[i].hw = hws; + desc->sg[i].hw_phys = hw_phys; + + hws->id = AXI_DMAC_SG_UNUSED; /* Link hardware descriptors */ - hws[i].next_sg_addr = hw_phys + (i + 1) * sizeof(*hws); + if (i) + desc->sg[i - 1].hw->next_sg_addr = hw_phys; } /* The last hardware descriptor will trigger an interrupt */ @@ -688,18 +696,6 @@ axi_dmac_alloc_desc(struct axi_dmac_chan *chan, unsigned int num_sgs) return desc; } -static void axi_dmac_free_desc(struct axi_dmac_desc *desc) -{ - struct axi_dmac *dmac = chan_to_axi_dmac(desc->chan); - struct device *dev = dmac->dma_dev.dev; - struct axi_dmac_hw_desc *hw = desc->sg[0].hw; - dma_addr_t hw_phys = desc->sg[0].hw_phys; - - dma_free_coherent(dev, PAGE_ALIGN(desc->num_sgs * sizeof(*hw)), - hw, hw_phys); - kfree(desc); -} - static struct axi_dmac_sg *axi_dmac_fill_linear_sg(struct axi_dmac_chan *chan, enum dma_transfer_direction direction, dma_addr_t addr, unsigned int num_periods, unsigned int period_len, @@ -933,9 +929,26 @@ static struct dma_async_tx_descriptor *axi_dmac_prep_interleaved( return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags); } +static int axi_dmac_alloc_chan_resources(struct dma_chan *c) +{ + struct axi_dmac_chan *chan = to_axi_dmac_chan(c); + struct device *dev = c->device->dev; + + chan->pool = dma_pool_create(dev_name(dev), dev, + sizeof(struct axi_dmac_hw_desc), + __alignof__(struct axi_dmac_hw_desc), 0); + if (!chan->pool) + return -ENOMEM; + + return 0; +} + static void axi_dmac_free_chan_resources(struct dma_chan *c) { + struct axi_dmac_chan *chan = to_axi_dmac_chan(c); + vchan_free_chan_resources(to_virt_chan(c)); + dma_pool_destroy(chan->pool); } static void axi_dmac_desc_free(struct virt_dma_desc *vdesc) @@ -1238,6 +1251,7 @@ static int axi_dmac_probe(struct platform_device *pdev) dma_cap_set(DMA_SLAVE, dma_dev->cap_mask); dma_cap_set(DMA_CYCLIC, dma_dev->cap_mask); dma_cap_set(DMA_INTERLEAVE, dma_dev->cap_mask); + dma_dev->device_alloc_chan_resources = axi_dmac_alloc_chan_resources; dma_dev->device_free_chan_resources = axi_dmac_free_chan_resources; dma_dev->device_tx_status = dma_cookie_status; dma_dev->device_issue_pending = axi_dmac_issue_pending; From b2d44b3ea95e10315559d8deedd8af2977c7f534 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Wed, 14 Jan 2026 07:14:58 +0200 Subject: [PATCH 52/62] dmaengine: iop32x-adma: Remove a leftover header file The Intel IOPx3xx platform was completely removed in commit b91a69d162aa ("ARM: iop32x: remove the platform"), and it'd be safe to remove an unused and leftover platform data specific header file dma-iop32x.h also. Signed-off-by: Vladimir Zapolskiy Reviewed-by: Frank Li Link: https://patch.msgid.link/20260114051508.3908807-1-vz@mleia.com [vkoul: fixed subsystem tag] Signed-off-by: Vinod Koul --- include/linux/platform_data/dma-iop32x.h | 110 ----------------------- 1 file changed, 110 deletions(-) delete mode 100644 include/linux/platform_data/dma-iop32x.h diff --git a/include/linux/platform_data/dma-iop32x.h b/include/linux/platform_data/dma-iop32x.h deleted file mode 100644 index ac83cff89549..000000000000 --- a/include/linux/platform_data/dma-iop32x.h +++ /dev/null @@ -1,110 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright © 2006, Intel Corporation. - */ -#ifndef IOP_ADMA_H -#define IOP_ADMA_H -#include -#include -#include - -#define IOP_ADMA_SLOT_SIZE 32 -#define IOP_ADMA_THRESHOLD 4 -#ifdef DEBUG -#define IOP_PARANOIA 1 -#else -#define IOP_PARANOIA 0 -#endif -#define iop_paranoia(x) BUG_ON(IOP_PARANOIA && (x)) - -#define DMA0_ID 0 -#define DMA1_ID 1 -#define AAU_ID 2 - -/** - * struct iop_adma_device - internal representation of an ADMA device - * @pdev: Platform device - * @id: HW ADMA Device selector - * @dma_desc_pool: base of DMA descriptor region (DMA address) - * @dma_desc_pool_virt: base of DMA descriptor region (CPU address) - * @common: embedded struct dma_device - */ -struct iop_adma_device { - struct platform_device *pdev; - int id; - dma_addr_t dma_desc_pool; - void *dma_desc_pool_virt; - struct dma_device common; -}; - -/** - * struct iop_adma_chan - internal representation of an ADMA device - * @pending: allows batching of hardware operations - * @lock: serializes enqueue/dequeue operations to the slot pool - * @mmr_base: memory mapped register base - * @chain: device chain view of the descriptors - * @device: parent device - * @common: common dmaengine channel object members - * @last_used: place holder for allocation to continue from where it left off - * @all_slots: complete domain of slots usable by the channel - * @slots_allocated: records the actual size of the descriptor slot pool - * @irq_tasklet: bottom half where iop_adma_slot_cleanup runs - */ -struct iop_adma_chan { - int pending; - spinlock_t lock; /* protects the descriptor slot pool */ - void __iomem *mmr_base; - struct list_head chain; - struct iop_adma_device *device; - struct dma_chan common; - struct iop_adma_desc_slot *last_used; - struct list_head all_slots; - int slots_allocated; - struct tasklet_struct irq_tasklet; -}; - -/** - * struct iop_adma_desc_slot - IOP-ADMA software descriptor - * @slot_node: node on the iop_adma_chan.all_slots list - * @chain_node: node on the op_adma_chan.chain list - * @hw_desc: virtual address of the hardware descriptor chain - * @phys: hardware address of the hardware descriptor chain - * @group_head: first operation in a transaction - * @slot_cnt: total slots used in an transaction (group of operations) - * @slots_per_op: number of slots per operation - * @idx: pool index - * @tx_list: list of descriptors that are associated with one operation - * @async_tx: support for the async_tx api - * @group_list: list of slots that make up a multi-descriptor transaction - * for example transfer lengths larger than the supported hw max - * @xor_check_result: result of zero sum - * @crc32_result: result crc calculation - */ -struct iop_adma_desc_slot { - struct list_head slot_node; - struct list_head chain_node; - void *hw_desc; - struct iop_adma_desc_slot *group_head; - u16 slot_cnt; - u16 slots_per_op; - u16 idx; - struct list_head tx_list; - struct dma_async_tx_descriptor async_tx; - union { - u32 *xor_check_result; - u32 *crc32_result; - u32 *pq_check_result; - }; -}; - -struct iop_adma_platform_data { - int hw_id; - dma_cap_mask_t cap_mask; - size_t pool_size; -}; - -#define to_iop_sw_desc(addr_hw_desc) \ - container_of(addr_hw_desc, struct iop_adma_desc_slot, hw_desc) -#define iop_hw_desc_slot_idx(hw_desc, idx) \ - ( (void *) (((unsigned long) hw_desc) + ((idx) << 5)) ) -#endif From 4651df83b6c796daead3447e8fd874322918ee4f Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Wed, 22 Apr 2026 12:11:34 +0530 Subject: [PATCH 53/62] dmaengine: tegra: Fix burst size calculation Currently, the Tegra GPC DMA hardware requires the transfer length to be a multiple of the max burst size configured for the channel. When a client requests a transfer where the length is not evenly divisible by the configured max burst size, the DMA hangs with partial burst at the end. Fix this by reducing the burst size to the largest power-of-2 value that evenly divides the transfer length. For example, a 40-byte transfer with a 16-byte max burst will now use an 8-byte burst (40 / 8 = 5 complete bursts) instead of causing a hang. This issue was observed with the PL011 UART driver where TX DMA transfers of arbitrary lengths were stuck. Fixes: ee17028009d4 ("dmaengine: tegra: Add tegra gpcdma driver") Cc: stable@vger.kernel.org Signed-off-by: Kartik Rajput Reviewed-by: Frank Li Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260422064134.1323610-1-kkartik@nvidia.com Signed-off-by: Vinod Koul --- drivers/dma/tegra186-gpc-dma.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c index c364c278a8b0..64cedef1050a 100644 --- a/drivers/dma/tegra186-gpc-dma.c +++ b/drivers/dma/tegra186-gpc-dma.c @@ -835,6 +835,13 @@ static unsigned int get_burst_size(struct tegra_dma_channel *tdc, * len to calculate the optimum burst size */ burst_byte = burst_size ? burst_size * slave_bw : len; + + /* + * Find the largest burst size that evenly divides the transfer length. + * The hardware requires the transfer length to be a multiple of the + * burst size - partial bursts are not supported. + */ + burst_byte = min(burst_byte, 1U << __ffs(len)); burst_mmio_width = burst_byte / 4; if (burst_mmio_width < TEGRA_GPCDMA_MMIOSEQ_BURST_MIN) From 75b28d74f90c79e788e0e86caf0173fc0b2e92aa Mon Sep 17 00:00:00 2001 From: Joy Zou Date: Wed, 11 Feb 2026 17:28:24 +0800 Subject: [PATCH 54/62] dt-bindings: dma: fsl-edma: add dma-channel-mask property description Add documentation for the dma-channel-mask property in the fsl-edma binding. This property uses an inverted bit definition: bit value 0 indicates the channel is available, while bit value 1 indicates unavailable. That was already used widely for i.MX8, i.MX9. Correcting the definition will break backward compatibility. This reversal only impacts the eDMA dts node and driver, and doesn't impact DMA consumer. Therefore, keep the inverted definition. Also add a note at the top of the binding to highlight this inverted definition to prevent confusion. Signed-off-by: Joy Zou Reviewed-by: Frank Li Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260211-b4-imx95-v2x-v4-1-10852754b267@nxp.com Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/dma/fsl,edma.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Documentation/devicetree/bindings/dma/fsl,edma.yaml b/Documentation/devicetree/bindings/dma/fsl,edma.yaml index fa4248e2f1b9..f609038e35ae 100644 --- a/Documentation/devicetree/bindings/dma/fsl,edma.yaml +++ b/Documentation/devicetree/bindings/dma/fsl,edma.yaml @@ -12,6 +12,9 @@ description: | DMAMUX0 and DMAMUX1, specific DMA request source can only be multiplexed by any channel of certain group, DMAMUX0 or DMAMUX1, but not both. + This binding has an inverted dma-channel-mask definition compared to + the common DMA binding for historical reasons. + maintainers: - Peng Fan @@ -95,6 +98,12 @@ properties: eDMA are implemented in big endian mode, otherwise in little mode. type: boolean + dma-channel-mask: + description: | + Bitmask of available DMA channels (inverted definition). + Bit semantics: 0 means channel available, 1 means channel unavailable + default: 0 + required: - "#dma-cells" - compatible From 94963138cce29f85605d76c94fa1d43a0335ead9 Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Mon, 25 May 2026 01:37:30 -0700 Subject: [PATCH 55/62] dmaengine: altera-msgdma: Use memcpy_toio for descriptor FIFO writes The descriptor FIFO requires that all words of a descriptor are written in order, with the control word written last to flush it into the DMA engine. Using memcpy() with __force to __iomem is not the correct API and does not guarantee appropriate MMIO access on all architectures. Replace the descriptor body copy with memcpy_toio(), using offsetof(struct msgdma_extended_desc, control) to exclude the control word. This matches the previous sizeof(desc->hw_desc) - sizeof(u32) length only when control is the last struct member; add a static_assert to enforce that layout so a future field after control cannot silently break FIFO ordering. Keep writing the control word separately with write barriers, so it remains the final word pushed into the FIFO. Signed-off-by: Adrian Ng Ho Yin Signed-off-by: Tze Yee Ng Link: https://patch.msgid.link/f6f3b4a2e2eb0eb1a51976de3f5d1ef5bab9bd76.1779697226.git.tze.yee.ng@altera.com Signed-off-by: Vinod Koul --- drivers/dma/altera-msgdma.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/drivers/dma/altera-msgdma.c b/drivers/dma/altera-msgdma.c index b46999c81df0..e23e5b441a24 100644 --- a/drivers/dma/altera-msgdma.c +++ b/drivers/dma/altera-msgdma.c @@ -496,6 +496,11 @@ static void msgdma_copy_one(struct msgdma_device *mdev, { void __iomem *hw_desc = mdev->desc; + /* Ensure control is the last field — required for correct FIFO flush ordering */ + static_assert(offsetof(struct msgdma_extended_desc, control) == + sizeof(struct msgdma_extended_desc) - sizeof(u32), + "control must be the last field in msgdma_extended_desc"); + /* * Check if the DESC FIFO it not full. If its full, we need to wait * for at least one entry to become free again @@ -504,17 +509,18 @@ static void msgdma_copy_one(struct msgdma_device *mdev, MSGDMA_CSR_STAT_DESC_BUF_FULL) mdelay(1); + /* Ensure control is the last field — required for correct FIFO flush ordering */ + static_assert(offsetof(struct msgdma_extended_desc, control) == + sizeof(struct msgdma_extended_desc) - sizeof(u32), + "control must be the last field in msgdma_extended_desc"); + /* - * The descriptor needs to get copied into the descriptor FIFO - * of the DMA controller. The descriptor will get flushed to the - * FIFO, once the last word (control word) is written. Since we - * are not 100% sure that memcpy() writes all word in the "correct" - * order (address from low to high) on all architectures, we make - * sure this control word is written last by single coding it and - * adding some write-barriers here. + * Copy the descriptor into the descriptor FIFO of the DMA controller, + * excluding the control word. The FIFO is flushed and the descriptor + * becomes valid once the control word is written last. */ - memcpy((void __force *)hw_desc, &desc->hw_desc, - sizeof(desc->hw_desc) - sizeof(u32)); + memcpy_toio(hw_desc, &desc->hw_desc, + offsetof(struct msgdma_extended_desc, control)); /* Write control word last to flush this descriptor into the FIFO */ mdev->idle = false; From dc6d681e1571c89cd38145926fb2513d70a633e1 Mon Sep 17 00:00:00 2001 From: Niravkumar L Rabara Date: Mon, 25 May 2026 00:10:21 -0700 Subject: [PATCH 56/62] dmaengine: dw-axi-dmac: drop redundant DMAC enable in block start axi_chan_block_xfer_start() runs after the controller is already enabled, so calling axi_dma_enable() again is unnecessary. Remove the redundant enable call to keep the transfer start path clean and avoid repeated no-op programming. Signed-off-by: Niravkumar L Rabara Signed-off-by: Tze Yee Ng Link: https://patch.msgid.link/060733464e19298f670cd269d4849f2092644923.1779688569.git.tze.yee.ng@altera.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 4d53f077e9d2..f7a50f470461 100644 --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c @@ -437,8 +437,6 @@ static void axi_chan_block_xfer_start(struct axi_dma_chan *chan, return; } - axi_dma_enable(chan->chip); - config.dst_multblk_type = DWAXIDMAC_MBLK_TYPE_LL; config.src_multblk_type = DWAXIDMAC_MBLK_TYPE_LL; config.tt_fc = DWAXIDMAC_TT_FC_MEM_TO_MEM_DMAC; From df0c2dc68770cf43f15df40b184df030b850ea05 Mon Sep 17 00:00:00 2001 From: Tze Yee Ng Date: Mon, 25 May 2026 00:10:22 -0700 Subject: [PATCH 57/62] dmaengine: dw-axi-dmac: fix PM for system sleep and channel alloc The driver only had runtime PM callbacks. If a channel stayed allocated across system suspend/resume, the runtime usage count could remain non-zero while hardware state (DMAC_CFG, clocks) was lost, and axi_dma_runtime_resume() would not run to restore it. Add system-sleep PM ops that use pm_runtime_force_suspend() and pm_runtime_force_resume() so suspend/resume reuses the existing axi_dma_suspend() and axi_dma_resume() paths. Replace pm_runtime_get() with pm_runtime_resume_and_get() in dma_chan_alloc_chan_resources() so clocks are enabled before a client can immediately submit a transfer and touch MMIO. Signed-off-by: Tze Yee Ng Link: https://patch.msgid.link/18bf778a3a1cc2f377ef8eb0d1508d8ac6371896.1779688569.git.tze.yee.ng@altera.com Signed-off-by: Vinod Koul --- drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 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 f7a50f470461..bcefaff03b5c 100644 --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c @@ -516,11 +516,17 @@ static void dw_axi_dma_synchronize(struct dma_chan *dchan) static int dma_chan_alloc_chan_resources(struct dma_chan *dchan) { struct axi_dma_chan *chan = dchan_to_axi_dma_chan(dchan); + int ret; + + ret = pm_runtime_resume_and_get(chan->chip->dev); + if (ret < 0) + return ret; /* ASSERT: channel is idle */ if (axi_chan_is_hw_enable(chan)) { dev_err(chan2dev(chan), "%s is non-idle!\n", axi_chan_name(chan)); + pm_runtime_put(chan->chip->dev); return -EBUSY; } @@ -531,12 +537,11 @@ static int dma_chan_alloc_chan_resources(struct dma_chan *dchan) 64, 0); if (!chan->desc_pool) { dev_err(chan2dev(chan), "No memory for descriptors\n"); + pm_runtime_put(chan->chip->dev); return -ENOMEM; } dev_vdbg(dchan2dev(dchan), "%s: allocating\n", axi_chan_name(chan)); - pm_runtime_get(chan->chip->dev); - return 0; } @@ -1663,6 +1668,8 @@ static void dw_remove(struct platform_device *pdev) } static const struct dev_pm_ops dw_axi_dma_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) SET_RUNTIME_PM_OPS(axi_dma_runtime_suspend, axi_dma_runtime_resume, NULL) }; From 26f926b44dbfc035d5ba0ccfc4387a40aa9947c1 Mon Sep 17 00:00:00 2001 From: Hungyu Lin Date: Sun, 7 Jun 2026 16:31:19 +0000 Subject: [PATCH 58/62] dmaengine: qcom: hidma: use sysfs_emit() in sysfs show callbacks Replace sprintf() and strlen() patterns in sysfs show callbacks with sysfs_emit(). sysfs_emit() is the preferred helper for formatting sysfs output and simplifies the implementation. Signed-off-by: Hungyu Lin Reviewed-by: Frank Li Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260607163119.78717-1-dennylin0707@gmail.com Signed-off-by: Vinod Koul --- drivers/dma/qcom/hidma.c | 6 ++---- drivers/dma/qcom/hidma_mgmt_sys.c | 23 ++++++++++------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/drivers/dma/qcom/hidma.c b/drivers/dma/qcom/hidma.c index 5a8dca8db5ce..7a7f302a9699 100644 --- a/drivers/dma/qcom/hidma.c +++ b/drivers/dma/qcom/hidma.c @@ -624,12 +624,10 @@ static ssize_t hidma_show_values(struct device *dev, { struct hidma_dev *mdev = dev_get_drvdata(dev); - buf[0] = 0; - if (strcmp(attr->attr.name, "chid") == 0) - sprintf(buf, "%d\n", mdev->chidx); + return sysfs_emit(buf, "%d\n", mdev->chidx); - return strlen(buf); + return 0; } static inline void hidma_sysfs_uninit(struct hidma_dev *dev) diff --git a/drivers/dma/qcom/hidma_mgmt_sys.c b/drivers/dma/qcom/hidma_mgmt_sys.c index 930eae0a6257..9672ef9ee8fc 100644 --- a/drivers/dma/qcom/hidma_mgmt_sys.c +++ b/drivers/dma/qcom/hidma_mgmt_sys.c @@ -102,15 +102,12 @@ static ssize_t show_values(struct device *dev, struct device_attribute *attr, struct hidma_mgmt_dev *mdev = dev_get_drvdata(dev); unsigned int i; - buf[0] = 0; - for (i = 0; i < ARRAY_SIZE(hidma_mgmt_files); i++) { - if (strcmp(attr->attr.name, hidma_mgmt_files[i].name) == 0) { - sprintf(buf, "%d\n", hidma_mgmt_files[i].get(mdev)); - break; - } + if (strcmp(attr->attr.name, hidma_mgmt_files[i].name) == 0) + return sysfs_emit(buf, "%d\n", + hidma_mgmt_files[i].get(mdev)); } - return strlen(buf); + return 0; } static ssize_t set_values(struct device *dev, struct device_attribute *attr, @@ -143,15 +140,15 @@ static ssize_t show_values_channel(struct kobject *kobj, struct hidma_chan_attr *chattr; struct hidma_mgmt_dev *mdev; - buf[0] = 0; chattr = container_of(attr, struct hidma_chan_attr, attr); mdev = chattr->mdev; - if (strcmp(attr->attr.name, "priority") == 0) - sprintf(buf, "%d\n", mdev->priority[chattr->index]); - else if (strcmp(attr->attr.name, "weight") == 0) - sprintf(buf, "%d\n", mdev->weight[chattr->index]); - return strlen(buf); + if (strcmp(attr->attr.name, "priority") == 0) + return sysfs_emit(buf, "%d\n", mdev->priority[chattr->index]); + else if (strcmp(attr->attr.name, "weight") == 0) + return sysfs_emit(buf, "%d\n", mdev->weight[chattr->index]); + + return 0; } static ssize_t set_values_channel(struct kobject *kobj, From 0fbf772fabe93b52f1ecd9ea193dbc90a6042c4d Mon Sep 17 00:00:00 2001 From: Xueyao An Date: Mon, 8 Jun 2026 18:40:21 +0530 Subject: [PATCH 59/62] dt-bindings: dma: qcom,gpi: Document GPI DMA engine for Shikra SoC Document the GPI DMA engine on Shikra platform. It is fully compatible with the GPI DMA engine found on SM6350, thus using qcom,sm6350-gpi-dma as fallback compatible. Signed-off-by: Xueyao An Acked-by: Krzysztof Kozlowski Signed-off-by: Komal Bajaj Link: https://patch.msgid.link/20260608-shikra-dt-m1-v4-1-2114300594a6@oss.qualcomm.com Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/dma/qcom,gpi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/dma/qcom,gpi.yaml b/Documentation/devicetree/bindings/dma/qcom,gpi.yaml index 8f9a552fe30e..54dca623223d 100644 --- a/Documentation/devicetree/bindings/dma/qcom,gpi.yaml +++ b/Documentation/devicetree/bindings/dma/qcom,gpi.yaml @@ -37,6 +37,7 @@ properties: - qcom,sc7280-gpi-dma - qcom,sc8280xp-gpi-dma - qcom,sdx75-gpi-dma + - qcom,shikra-gpi-dma - qcom,sm6115-gpi-dma - qcom,sm6375-gpi-dma - qcom,sm8350-gpi-dma From 29d0b2696d4602d36d91dc68a2a5a022ca485a5f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 23 Apr 2026 19:36:03 +0200 Subject: [PATCH 60/62] dmaengine: qcom: Unify user-visible "Qualcomm" name Various names for Qualcomm as a company are used in user-visible config options: QCOM, Qualcomm and Qualcomm Technologies. Switch to unified "Qualcomm" so it will be easier for users to identify the options when for example running menuconfig. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260423173602.92503-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/dma/qcom/Kconfig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/dma/qcom/Kconfig b/drivers/dma/qcom/Kconfig index ace75d7b835a..c71b0b5d536b 100644 --- a/drivers/dma/qcom/Kconfig +++ b/drivers/dma/qcom/Kconfig @@ -11,7 +11,7 @@ config QCOM_ADM and on-chip peripheral devices. config QCOM_BAM_DMA - tristate "QCOM BAM DMA support" + tristate "Qualcomm BAM DMA support" depends on ARCH_QCOM || (COMPILE_TEST && OF && ARM) select DMA_ENGINE select DMA_VIRTUAL_CHANNELS @@ -20,7 +20,7 @@ config QCOM_BAM_DMA provides DMA capabilities for a variety of on-chip devices. config QCOM_GPI_DMA - tristate "Qualcomm Technologies GPI DMA support" + tristate "Qualcomm GPI DMA support" depends on ARCH_QCOM select DMA_ENGINE select DMA_VIRTUAL_CHANNELS @@ -32,7 +32,7 @@ config QCOM_GPI_DMA transfer data between DDR and peripheral. config QCOM_HIDMA_MGMT - tristate "Qualcomm Technologies HIDMA Management support" + tristate "Qualcomm HIDMA Management support" depends on HAS_IOMEM select DMA_ENGINE help @@ -44,7 +44,7 @@ config QCOM_HIDMA_MGMT host would run the QCOM_HIDMA_MGMT management driver. config QCOM_HIDMA - tristate "Qualcomm Technologies HIDMA Channel support" + tristate "Qualcomm HIDMA Channel support" depends on HAS_IOMEM select DMA_ENGINE help From 12933e2bc5e07e1500ee69821ab23ad443c3e649 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 5 May 2026 19:46:05 +0300 Subject: [PATCH 61/62] MAINTAINERS: dmaengine/ti: Remove myself and add Vignesh as maintainer As I cannot spend adequate time to fulfill my role as maintainer for the TI DMA drivers, it is for the better if I resign and hand over the role to Vignesh Raghavendra. Signed-off-by: Peter Ujfalusi Acked-by: Vignesh Raghavendra Link: https://patch.msgid.link/20260505164605.15878-1-peter.ujfalusi@gmail.com Signed-off-by: Vinod Koul --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 2fb1c75afd16..3e7b2d9e9c24 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26349,7 +26349,7 @@ F: sound/soc/codecs/tlv320*.* F: sound/soc/codecs/tpa6130a2.* TEXAS INSTRUMENTS DMA DRIVERS -M: Peter Ujfalusi +M: Vignesh Raghavendra L: dmaengine@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/dma/ti-dma-crossbar.txt From 7524fe142b5a772f8421aeee2132cf7e21a00103 Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Mon, 11 May 2026 14:38:16 +0800 Subject: [PATCH 62/62] dt-bindings: dma: snps,dw-axi-dmac: Add fallback compatible for CV1800B The previous version of the binding change only add compatible string without adding the fallback compatible, this breaks backward compatibility. Add the needed fallback compatible to fix this. Fixes: be3e2a0419c6 ("dt-bindings: dma: snps,dw-axi-dmac: Add CV1800B compatible") Signed-off-by: Inochi Amaoto Acked-by: Conor Dooley Link: https://patch.msgid.link/20260511063818.463877-2-inochiama@gmail.com Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml b/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml index 804514732dbe..0a30a455b0ee 100644 --- a/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml +++ b/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml @@ -21,11 +21,12 @@ properties: - enum: - snps,axi-dma-1.01a - intel,kmb-axi-dma - - sophgo,cv1800b-axi-dma - starfive,jh7110-axi-dma - starfive,jh8100-axi-dma - items: - - const: altr,agilex5-axi-dma + - enum: + - altr,agilex5-axi-dma + - sophgo,cv1800b-axi-dma - const: snps,axi-dma-1.01a reg: