* [PATCH v2 0/4] dmaengine: Add Loongson Multi-Channel DMA controller support
@ 2026-02-09 3:04 Binbin Zhou
2026-02-09 3:04 ` [PATCH v2 1/4] dmaengine: loongson: New directory for Loongson DMA controllers drivers Binbin Zhou
` (3 more replies)
0 siblings, 4 replies; 16+ messages in thread
From: Binbin Zhou @ 2026-02-09 3:04 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Vinod Koul, dmaengine
Cc: Xiaochuang Mao, Huacai Chen, Xuerui Wang, loongarch, devicetree,
Keguang Zhang, linux-mips, jeffbai, Binbin Zhou
Hi all:
This patchset introduces the Loongson multi-channel DMA controller,
which is present in the Loongson-2K0300 and Loongson-2K3000 processors.
It is a multi-channel controller that enables data transfers from memory
to memory, device to memory, and memory to device, as well as channel
prioritization configurable through the channel configuration registers.
Additionally, since multiple distinct types of DMA controllers exist on
the Loongson platform, I have attempted to consolidate all Loongson DMA
drivers into a new directory named `Loongson` for easier management.
Thanks.
Binbin
===========
V2:
patch(1/4):
- Update loongson1-apb-dma.c entry in MAINTAINERS.
patch(2/4):
- New patch, use dmaenginem_async_device_register() helper.
patch(3/4):
- `additionalProperties: false` replaced by
`unevaluatedProperties: false`.
patch(4/4):
- Rename filename as loongson2-apb-cmc-dma.c;
- Rename Kconfig item as LOONGSON2_APB_CMC_DMA;
- Rename the variable prefix as `loongson2_cmc_dma`;
- Use dmaenginem_async_device_register() helper;
- Drop 'dma_' prefix in struct loongson2_mdma_chan_reg;
- Use struct_size();
Link to V1:
https://lore.kernel.org/all/cover.1770119693.git.zhoubinbin@loongson.cn/
Binbin Zhou (4):
dmaengine: loongson: New directory for Loongson DMA controllers
drivers
dmaengine: loongson: loongson2-apb: Convert to
dmaenginem_async_device_register()
dt-bindings: dmaengine: Add Loongson Multi-Channel DMA controller
dmaengine: loongson: New driver for the Loongson Multi-Channel DMA
controller
.../bindings/dma/loongson,ls2k0300-dma.yaml | 78 ++
MAINTAINERS | 7 +-
drivers/dma/Kconfig | 25 +-
drivers/dma/Makefile | 3 +-
drivers/dma/loongson/Kconfig | 38 +
drivers/dma/loongson/Makefile | 4 +
.../dma/{ => loongson}/loongson1-apb-dma.c | 4 +-
drivers/dma/loongson/loongson2-apb-cmc-dma.c | 736 ++++++++++++++++++
.../dma/{ => loongson}/loongson2-apb-dma.c | 11 +-
9 files changed, 870 insertions(+), 36 deletions(-)
create mode 100644 Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
create mode 100644 drivers/dma/loongson/Kconfig
create mode 100644 drivers/dma/loongson/Makefile
rename drivers/dma/{ => loongson}/loongson1-apb-dma.c (99%)
create mode 100644 drivers/dma/loongson/loongson2-apb-cmc-dma.c
rename drivers/dma/{ => loongson}/loongson2-apb-dma.c (98%)
base-commit: ab736ed52e3409b58a4888715e4425b6e8ac444f
--
2.52.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 1/4] dmaengine: loongson: New directory for Loongson DMA controllers drivers
2026-02-09 3:04 [PATCH v2 0/4] dmaengine: Add Loongson Multi-Channel DMA controller support Binbin Zhou
@ 2026-02-09 3:04 ` Binbin Zhou
2026-02-09 16:48 ` Frank Li
2026-02-09 3:04 ` [PATCH v2 2/4] dmaengine: loongson: loongson2-apb: Convert to dmaenginem_async_device_register() Binbin Zhou
` (2 subsequent siblings)
3 siblings, 1 reply; 16+ messages in thread
From: Binbin Zhou @ 2026-02-09 3:04 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Vinod Koul, dmaengine
Cc: Xiaochuang Mao, Huacai Chen, Xuerui Wang, loongarch, devicetree,
Keguang Zhang, linux-mips, jeffbai, Binbin Zhou
Gather the Loongson DMA controllers under drivers/dma/loongson/
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
MAINTAINERS | 3 +-
drivers/dma/Kconfig | 25 ++---------------
drivers/dma/Makefile | 3 +-
drivers/dma/loongson/Kconfig | 28 +++++++++++++++++++
drivers/dma/loongson/Makefile | 3 ++
.../dma/{ => loongson}/loongson1-apb-dma.c | 4 +--
.../dma/{ => loongson}/loongson2-apb-dma.c | 4 +--
7 files changed, 40 insertions(+), 30 deletions(-)
create mode 100644 drivers/dma/loongson/Kconfig
create mode 100644 drivers/dma/loongson/Makefile
rename drivers/dma/{ => loongson}/loongson1-apb-dma.c (99%)
rename drivers/dma/{ => loongson}/loongson2-apb-dma.c (99%)
diff --git a/MAINTAINERS b/MAINTAINERS
index f630328ca6ae..27f77b68d596 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14777,7 +14777,7 @@ M: Binbin Zhou <zhoubinbin@loongson.cn>
L: dmaengine@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
-F: drivers/dma/loongson2-apb-dma.c
+F: drivers/dma/loongson/loongson2-apb-dma.c
LOONGSON LS2X I2C DRIVER
M: Binbin Zhou <zhoubinbin@loongson.cn>
@@ -17515,6 +17515,7 @@ F: arch/mips/boot/dts/loongson/loongson1*
F: arch/mips/configs/loongson1_defconfig
F: arch/mips/loongson32/
F: drivers/*/*loongson1*
+F: drivers/dma/loongson/loongson1-apb-dma.c
F: drivers/mtd/nand/raw/loongson-nand-controller.c
F: drivers/net/ethernet/stmicro/stmmac/dwmac-loongson1.c
F: sound/soc/loongson/loongson1_ac97.c
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 66cda7cc9f7a..1b84c5b11654 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -376,29 +376,6 @@ config K3_DMA
Support the DMA engine for Hisilicon K3 platform
devices.
-config LOONGSON1_APB_DMA
- tristate "Loongson1 APB DMA support"
- depends on MACH_LOONGSON32 || COMPILE_TEST
- select DMA_ENGINE
- select DMA_VIRTUAL_CHANNELS
- help
- This selects support for the APB DMA controller in Loongson1 SoCs,
- which is required by Loongson1 NAND and audio support.
-
-config LOONGSON2_APB_DMA
- tristate "Loongson2 APB DMA support"
- depends on LOONGARCH || COMPILE_TEST
- select DMA_ENGINE
- select DMA_VIRTUAL_CHANNELS
- help
- Support for the Loongson2 APB DMA controller driver. The
- DMA controller is having single DMA channel which can be
- configured for different peripherals like audio, nand, sdio
- etc which is in APB bus.
-
- This DMA controller transfers data from memory to peripheral fifo.
- It does not support memory to memory data transfer.
-
config LPC18XX_DMAMUX
bool "NXP LPC18xx/43xx DMA MUX for PL080"
depends on ARCH_LPC18XX || COMPILE_TEST
@@ -774,6 +751,8 @@ source "drivers/dma/fsl-dpaa2-qdma/Kconfig"
source "drivers/dma/lgm/Kconfig"
+source "drivers/dma/loongson/Kconfig"
+
source "drivers/dma/stm32/Kconfig"
# clients
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index a54d7688392b..a1c73415b79f 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -49,8 +49,6 @@ obj-$(CONFIG_INTEL_IDMA64) += idma64.o
obj-$(CONFIG_INTEL_IOATDMA) += ioat/
obj-y += idxd/
obj-$(CONFIG_K3_DMA) += k3dma.o
-obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
-obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
obj-$(CONFIG_LPC18XX_DMAMUX) += lpc18xx-dmamux.o
obj-$(CONFIG_LPC32XX_DMAMUX) += lpc32xx-dmamux.o
obj-$(CONFIG_MILBEAUT_HDMAC) += milbeaut-hdmac.o
@@ -88,6 +86,7 @@ obj-$(CONFIG_INTEL_LDMA) += lgm/
obj-y += amd/
obj-y += mediatek/
+obj-y += loongson/
obj-y += qcom/
obj-y += stm32/
obj-y += ti/
diff --git a/drivers/dma/loongson/Kconfig b/drivers/dma/loongson/Kconfig
new file mode 100644
index 000000000000..9dbdaef5a59f
--- /dev/null
+++ b/drivers/dma/loongson/Kconfig
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Loongson DMA controllers drivers
+#
+if MACH_LOONGSON32 || MACH_LOONGSON64 || COMPILE_TEST
+
+config LOONGSON1_APB_DMA
+ tristate "Loongson1 APB DMA support"
+ select DMA_ENGINE
+ select DMA_VIRTUAL_CHANNELS
+ help
+ This selects support for the APB DMA controller in Loongson1 SoCs,
+ which is required by Loongson1 NAND and audio support.
+
+config LOONGSON2_APB_DMA
+ tristate "Loongson2 APB DMA support"
+ select DMA_ENGINE
+ select DMA_VIRTUAL_CHANNELS
+ help
+ Support for the Loongson2 APB DMA controller driver. The
+ DMA controller is having single DMA channel which can be
+ configured for different peripherals like audio, nand, sdio
+ etc which is in APB bus.
+
+ This DMA controller transfers data from memory to peripheral fifo.
+ It does not support memory to memory data transfer.
+
+endif
diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
new file mode 100644
index 000000000000..6cdd08065e92
--- /dev/null
+++ b/drivers/dma/loongson/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
+obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
diff --git a/drivers/dma/loongson1-apb-dma.c b/drivers/dma/loongson/loongson1-apb-dma.c
similarity index 99%
rename from drivers/dma/loongson1-apb-dma.c
rename to drivers/dma/loongson/loongson1-apb-dma.c
index 255fe7eca212..e99247cf90c1 100644
--- a/drivers/dma/loongson1-apb-dma.c
+++ b/drivers/dma/loongson/loongson1-apb-dma.c
@@ -16,8 +16,8 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
-#include "dmaengine.h"
-#include "virt-dma.h"
+#include "../dmaengine.h"
+#include "../virt-dma.h"
/* Loongson-1 DMA Control Register */
#define LS1X_DMA_CTRL 0x0
diff --git a/drivers/dma/loongson2-apb-dma.c b/drivers/dma/loongson/loongson2-apb-dma.c
similarity index 99%
rename from drivers/dma/loongson2-apb-dma.c
rename to drivers/dma/loongson/loongson2-apb-dma.c
index c528f02b9f84..0cb607595d04 100644
--- a/drivers/dma/loongson2-apb-dma.c
+++ b/drivers/dma/loongson/loongson2-apb-dma.c
@@ -17,8 +17,8 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
-#include "dmaengine.h"
-#include "virt-dma.h"
+#include "../dmaengine.h"
+#include "../virt-dma.h"
/* Global Configuration Register */
#define LDMA_ORDER_ERG 0x0
--
2.52.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 2/4] dmaengine: loongson: loongson2-apb: Convert to dmaenginem_async_device_register()
2026-02-09 3:04 [PATCH v2 0/4] dmaengine: Add Loongson Multi-Channel DMA controller support Binbin Zhou
2026-02-09 3:04 ` [PATCH v2 1/4] dmaengine: loongson: New directory for Loongson DMA controllers drivers Binbin Zhou
@ 2026-02-09 3:04 ` Binbin Zhou
2026-02-09 16:51 ` Frank Li
2026-02-09 3:04 ` [PATCH v2 3/4] dt-bindings: dmaengine: Add Loongson Multi-Channel DMA controller Binbin Zhou
2026-02-09 3:04 ` [PATCH v2 4/4] dmaengine: loongson: New driver for the " Binbin Zhou
3 siblings, 1 reply; 16+ messages in thread
From: Binbin Zhou @ 2026-02-09 3:04 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Vinod Koul, dmaengine
Cc: Xiaochuang Mao, Huacai Chen, Xuerui Wang, loongarch, devicetree,
Keguang Zhang, linux-mips, jeffbai, Binbin Zhou
Use new dmaenginem_async_device_register() helper to simplify the code.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/dma/loongson/loongson2-apb-dma.c | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/drivers/dma/loongson/loongson2-apb-dma.c b/drivers/dma/loongson/loongson2-apb-dma.c
index 0cb607595d04..5e9a13ad9aa2 100644
--- a/drivers/dma/loongson/loongson2-apb-dma.c
+++ b/drivers/dma/loongson/loongson2-apb-dma.c
@@ -650,21 +650,19 @@ static int ls2x_dma_probe(struct platform_device *pdev)
ddev->dst_addr_widths = LDMA_SLAVE_BUSWIDTHS;
ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
- ret = dma_async_device_register(&priv->ddev);
+ ret = dmaenginem_async_device_register(&priv->ddev);
if (ret < 0)
goto disable_clk;
ret = of_dma_controller_register(dev->of_node, of_dma_xlate_by_chan_id, priv);
if (ret < 0)
- goto unregister_dmac;
+ goto disable_clk;
platform_set_drvdata(pdev, priv);
dev_info(dev, "Loongson LS2X APB DMA driver registered successfully.\n");
return 0;
-unregister_dmac:
- dma_async_device_unregister(&priv->ddev);
disable_clk:
clk_disable_unprepare(priv->dma_clk);
@@ -680,7 +678,6 @@ static void ls2x_dma_remove(struct platform_device *pdev)
struct ls2x_dma_priv *priv = platform_get_drvdata(pdev);
of_dma_controller_free(pdev->dev.of_node);
- dma_async_device_unregister(&priv->ddev);
clk_disable_unprepare(priv->dma_clk);
}
--
2.52.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 3/4] dt-bindings: dmaengine: Add Loongson Multi-Channel DMA controller
2026-02-09 3:04 [PATCH v2 0/4] dmaengine: Add Loongson Multi-Channel DMA controller support Binbin Zhou
2026-02-09 3:04 ` [PATCH v2 1/4] dmaengine: loongson: New directory for Loongson DMA controllers drivers Binbin Zhou
2026-02-09 3:04 ` [PATCH v2 2/4] dmaengine: loongson: loongson2-apb: Convert to dmaenginem_async_device_register() Binbin Zhou
@ 2026-02-09 3:04 ` Binbin Zhou
2026-02-10 3:03 ` Rob Herring
2026-02-09 3:04 ` [PATCH v2 4/4] dmaengine: loongson: New driver for the " Binbin Zhou
3 siblings, 1 reply; 16+ messages in thread
From: Binbin Zhou @ 2026-02-09 3:04 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Vinod Koul, dmaengine
Cc: Xiaochuang Mao, Huacai Chen, Xuerui Wang, loongarch, devicetree,
Keguang Zhang, linux-mips, jeffbai, Binbin Zhou
The Loongson-2K0300/Loongson-2K3000 have built-in multi-channel DMA
controllers, which are similar except for some of the register offsets
and number of channels.
Obviously, this is quite different from the APB DMA controller used in
the Loongson-2K0500/Loongson-2K1000, such as the latter being a
single-channel DMA controller.
To avoid cluttering a single dt-binding file, add a new yaml file.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
.../bindings/dma/loongson,ls2k0300-dma.yaml | 78 +++++++++++++++++++
MAINTAINERS | 3 +-
2 files changed, 80 insertions(+), 1 deletion(-)
create mode 100644 Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
diff --git a/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml b/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
new file mode 100644
index 000000000000..77e5df47ec01
--- /dev/null
+++ b/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
@@ -0,0 +1,78 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/dma/loongson,ls2k0300-dma.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Looongson-2 Multi-Channel DMA controller
+
+description:
+ The Loongson-2 Multi-Channel DMA controller is used for transferring data
+ between system memory and the peripherals on the APB bus.
+
+maintainers:
+ - Binbin Zhou <zhoubinbin@loongson.cn>
+
+allOf:
+ - $ref: dma-controller.yaml#
+
+properties:
+ compatible:
+ enum:
+ - loongson,ls2k0300-dma
+ - loongson,ls2k3000-dma
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ minItems: 4
+ maxItems: 8
+
+ clocks:
+ maxItems: 1
+
+ '#dma-cells':
+ const: 2
+ description: |
+ DMA request from clients consists of 2 cells:
+ 1. Channel index
+ 2. Transfer request factor number, If no transfer factor, use 0.
+ The number is SoC-specific, and this should be specified with
+ relation to the device to use the DMA controller.
+
+ dma-channels:
+ enum: [4, 8]
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - '#dma-cells'
+ - dma-channels
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/clock/loongson,ls2k-clk.h>
+
+ dma-controller@1612c000 {
+ compatible = "loongson,ls2k0300-dma";
+ reg = <0x1612c000 0xff>;
+ interrupt-parent = <&liointc0>;
+ interrupts = <23 IRQ_TYPE_LEVEL_HIGH>,
+ <24 IRQ_TYPE_LEVEL_HIGH>,
+ <25 IRQ_TYPE_LEVEL_HIGH>,
+ <26 IRQ_TYPE_LEVEL_HIGH>,
+ <27 IRQ_TYPE_LEVEL_HIGH>,
+ <28 IRQ_TYPE_LEVEL_HIGH>,
+ <29 IRQ_TYPE_LEVEL_HIGH>,
+ <30 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk LS2K0300_CLK_APB_GATE>;
+ #dma-cells = <2>;
+ dma-channels = <8>;
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 27f77b68d596..d3cb541aee2a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14772,10 +14772,11 @@ S: Maintained
F: Documentation/devicetree/bindings/gpio/loongson,ls-gpio.yaml
F: drivers/gpio/gpio-loongson-64bit.c
-LOONGSON-2 APB DMA DRIVER
+LOONGSON-2 DMA DRIVER
M: Binbin Zhou <zhoubinbin@loongson.cn>
L: dmaengine@vger.kernel.org
S: Maintained
+F: Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
F: drivers/dma/loongson/loongson2-apb-dma.c
--
2.52.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 4/4] dmaengine: loongson: New driver for the Loongson Multi-Channel DMA controller
2026-02-09 3:04 [PATCH v2 0/4] dmaengine: Add Loongson Multi-Channel DMA controller support Binbin Zhou
` (2 preceding siblings ...)
2026-02-09 3:04 ` [PATCH v2 3/4] dt-bindings: dmaengine: Add Loongson Multi-Channel DMA controller Binbin Zhou
@ 2026-02-09 3:04 ` Binbin Zhou
2026-02-09 17:04 ` Frank Li
3 siblings, 1 reply; 16+ messages in thread
From: Binbin Zhou @ 2026-02-09 3:04 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Vinod Koul, dmaengine
Cc: Xiaochuang Mao, Huacai Chen, Xuerui Wang, loongarch, devicetree,
Keguang Zhang, linux-mips, jeffbai, Binbin Zhou
This DMA controller appears in Loongson-2K0300 and Loongson-2K3000.
It is a chain multi-channel controller that enables data transfers from
memory to memory, device to memory, and memory to device, as well as
channel prioritization configurable through the channel configuration
registers.
In addition, there are slight differences between Loongson-2K0300 and
Loongson-2K3000, such as channel register offsets and the number of
channels.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
MAINTAINERS | 1 +
drivers/dma/loongson/Kconfig | 10 +
drivers/dma/loongson/Makefile | 1 +
drivers/dma/loongson/loongson2-apb-cmc-dma.c | 736 +++++++++++++++++++
4 files changed, 748 insertions(+)
create mode 100644 drivers/dma/loongson/loongson2-apb-cmc-dma.c
diff --git a/MAINTAINERS b/MAINTAINERS
index d3cb541aee2a..61a39070d7a0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14778,6 +14778,7 @@ L: dmaengine@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
+F: drivers/dma/loongson/loongson2-apb-cmc-dma.c
F: drivers/dma/loongson/loongson2-apb-dma.c
LOONGSON LS2X I2C DRIVER
diff --git a/drivers/dma/loongson/Kconfig b/drivers/dma/loongson/Kconfig
index 9dbdaef5a59f..28b3daeed4e3 100644
--- a/drivers/dma/loongson/Kconfig
+++ b/drivers/dma/loongson/Kconfig
@@ -25,4 +25,14 @@ config LOONGSON2_APB_DMA
This DMA controller transfers data from memory to peripheral fifo.
It does not support memory to memory data transfer.
+config LOONGSON2_APB_CMC_DMA
+ tristate "Loongson2 Chain Multi-Channel DMA support"
+ select DMA_ENGINE
+ select DMA_VIRTUAL_CHANNELS
+ help
+ Support for the Loongson Chain Multi-Channel DMA controller driver.
+ It is discovered on the Loongson-2K chip (Loongson-2K0300/Loongson-2K3000),
+ which has 4/8 channels internally, enabling bidirectional data transfer
+ between devices and memory.
+
endif
diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
index 6cdd08065e92..48c19781e729 100644
--- a/drivers/dma/loongson/Makefile
+++ b/drivers/dma/loongson/Makefile
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
+obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
diff --git a/drivers/dma/loongson/loongson2-apb-cmc-dma.c b/drivers/dma/loongson/loongson2-apb-cmc-dma.c
new file mode 100644
index 000000000000..f598ad095686
--- /dev/null
+++ b/drivers/dma/loongson/loongson2-apb-cmc-dma.c
@@ -0,0 +1,736 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Looongson-2 Multi-Channel DMA Controller driver
+ *
+ * Copyright (C) 2024-2026 Loongson Technology Corporation Limited
+ */
+
+#include <linux/acpi.h>
+#include <linux/acpi_dma.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_dma.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include "../dmaengine.h"
+#include "../virt-dma.h"
+
+#define LOONGSON2_CMCDMA_ISR 0x0 /* DMA Interrupt Status Register */
+#define LOONGSON2_CMCDMA_IFCR 0x4 /* DMA Interrupt Flag Clear Register */
+#define LOONGSON2_CMCDMA_CCR 0x8 /* DMA Channel Configuration Register */
+#define LOONGSON2_CMCDMA_CNDTR 0xc /* DMA Channel Transmit Count Register */
+#define LOONGSON2_CMCDMA_CPAR 0x10 /* DMA Channel Peripheral Address Register */
+#define LOONGSON2_CMCDMA_CMAR 0x14 /* DMA Channel Memory Address Register */
+
+/* Bitfields of DMA interrupt status register */
+#define LOONGSON2_CMCDMA_TCI BIT(1) /* Transfer Complete Interrupt */
+#define LOONGSON2_CMCDMA_HTI BIT(2) /* Half Transfer Interrupt */
+#define LOONGSON2_CMCDMA_TEI BIT(3) /* Transfer Error Interrupt */
+
+#define LOONGSON2_CMCDMA_MASKI \
+ (LOONGSON2_CMCDMA_TCI | LOONGSON2_CMCDMA_HTI | LOONGSON2_CMCDMA_TEI)
+
+/* Bitfields of DMA channel x Configuration Register */
+#define LOONGSON2_CMCDMA_CCR_EN BIT(0) /* Stream Enable */
+#define LOONGSON2_CMCDMA_CCR_TCIE BIT(1) /* Transfer Complete Interrupt Enable */
+#define LOONGSON2_CMCDMA_CCR_HTIE BIT(2) /* Half Transfer Complete Interrupt Enable */
+#define LOONGSON2_CMCDMA_CCR_TEIE BIT(3) /* Transfer Error Interrupt Enable */
+#define LOONGSON2_CMCDMA_CCR_DIR BIT(4) /* Data Transfer Direction */
+#define LOONGSON2_CMCDMA_CCR_CIRC BIT(5) /* Circular mode */
+#define LOONGSON2_CMCDMA_CCR_PINC BIT(6) /* Peripheral increment mode */
+#define LOONGSON2_CMCDMA_CCR_MINC BIT(7) /* Memory increment mode */
+#define LOONGSON2_CMCDMA_CCR_PSIZE_MASK GENMASK(9, 8)
+#define LOONGSON2_CMCDMA_CCR_MSIZE_MASK GENMASK(11, 10)
+#define LOONGSON2_CMCDMA_CCR_PL_MASK GENMASK(13, 12)
+#define LOONGSON2_CMCDMA_CCR_M2M BIT(14)
+
+#define LOONGSON2_CMCDMA_CCR_CFG_MASK \
+ (LOONGSON2_CMCDMA_CCR_PINC | LOONGSON2_CMCDMA_CCR_MINC | LOONGSON2_CMCDMA_CCR_PL_MASK)
+
+#define LOONGSON2_CMCDMA_CCR_IRQ_MASK \
+ (LOONGSON2_CMCDMA_CCR_TCIE | LOONGSON2_CMCDMA_CCR_HTIE | LOONGSON2_CMCDMA_CCR_TEIE)
+
+#define LOONGSON2_CMCDMA_STREAM_MASK \
+ (LOONGSON2_CMCDMA_CCR_CFG_MASK | LOONGSON2_CMCDMA_CCR_IRQ_MASK)
+
+#define LOONGSON2_CMCDMA_BUSWIDTHS (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \
+ BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \
+ BIT(DMA_SLAVE_BUSWIDTH_4_BYTES))
+
+enum loongson2_cmc_dma_width {
+ LOONGSON2_CMCDMA_BYTE,
+ LOONGSON2_CMCDMA_HALF_WORD,
+ LOONGSON2_CMCDMA_WORD,
+};
+
+struct loongson2_cmc_dma_chan_reg {
+ u32 ccr;
+ u32 cndtr;
+ u32 cpar;
+ u32 cmar;
+};
+
+struct loongson2_cmc_dma_sg_req {
+ u32 len;
+ struct loongson2_cmc_dma_chan_reg chan_reg;
+};
+
+struct loongson2_cmc_dma_desc {
+ struct virt_dma_desc vdesc;
+ bool cyclic;
+ u32 num_sgs;
+ struct loongson2_cmc_dma_sg_req sg_req[] __counted_by(num_sgs);
+};
+
+struct loongson2_cmc_dma_chan {
+ struct virt_dma_chan vchan;
+ struct dma_slave_config dma_sconfig;
+ struct loongson2_cmc_dma_desc *desc;
+ u32 id;
+ u32 irq;
+ u32 next_sg;
+ struct loongson2_cmc_dma_chan_reg chan_reg;
+};
+
+struct loongson2_cmc_dma_config {
+ u32 max_channels;
+ u32 chan_reg_offset;
+};
+
+struct loongson2_cmc_dma_dev {
+ struct dma_device ddev;
+ struct clk *dma_clk;
+ void __iomem *base;
+ u32 nr_channels;
+ u32 chan_reg_offset;
+ struct loongson2_cmc_dma_chan chan[] __counted_by(nr_channels);
+};
+
+static const struct loongson2_cmc_dma_config ls2k0300_cmc_dma_config = {
+ .max_channels = 8,
+ .chan_reg_offset = 0x14,
+};
+
+static const struct loongson2_cmc_dma_config ls2k3000_cmc_dma_config = {
+ .max_channels = 4,
+ .chan_reg_offset = 0x18,
+};
+
+static struct loongson2_cmc_dma_dev *lmdma_get_dev(struct loongson2_cmc_dma_chan *lchan)
+{
+ return container_of(lchan->vchan.chan.device, struct loongson2_cmc_dma_dev, ddev);
+}
+
+static struct loongson2_cmc_dma_chan *to_lmdma_chan(struct dma_chan *chan)
+{
+ return container_of(chan, struct loongson2_cmc_dma_chan, vchan.chan);
+}
+
+static struct loongson2_cmc_dma_desc *to_lmdma_desc(struct virt_dma_desc *vdesc)
+{
+ return container_of(vdesc, struct loongson2_cmc_dma_desc, vdesc);
+}
+
+static struct device *chan2dev(struct loongson2_cmc_dma_chan *lchan)
+{
+ return &lchan->vchan.chan.dev->device;
+}
+
+static u32 loongson2_cmc_dma_read(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id)
+{
+ return readl(lddev->base + (reg + lddev->chan_reg_offset * id));
+}
+
+static void loongson2_cmc_dma_write(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id, u32 val)
+{
+ writel(val, lddev->base + (reg + lddev->chan_reg_offset * id));
+}
+
+static int loongson2_cmc_dma_get_width(struct loongson2_cmc_dma_chan *lchan,
+ enum dma_slave_buswidth width)
+{
+ switch (width) {
+ case DMA_SLAVE_BUSWIDTH_1_BYTE:
+ return LOONGSON2_CMCDMA_BYTE;
+ case DMA_SLAVE_BUSWIDTH_2_BYTES:
+ return LOONGSON2_CMCDMA_HALF_WORD;
+ case DMA_SLAVE_BUSWIDTH_4_BYTES:
+ return LOONGSON2_CMCDMA_WORD;
+ default:
+ dev_err(chan2dev(lchan), "Dma bus width not supported\n");
+ return -EINVAL;
+ }
+}
+
+static int loongson2_cmc_dma_slave_config(struct dma_chan *chan, struct dma_slave_config *config)
+{
+ struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
+
+ memcpy(&lchan->dma_sconfig, config, sizeof(*config));
+
+ return 0;
+}
+
+static void loongson2_cmc_dma_irq_clear(struct loongson2_cmc_dma_chan *lchan, u32 flags)
+{
+ struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
+ u32 ifcr;
+
+ ifcr = flags << (4 * lchan->id);
+ loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_IFCR, 0, ifcr);
+}
+
+static void loongson2_cmc_dma_stop(struct loongson2_cmc_dma_chan *lchan)
+{
+ struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
+ u32 ccr;
+
+ ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
+ ccr &= ~(LOONGSON2_CMCDMA_CCR_IRQ_MASK | LOONGSON2_CMCDMA_CCR_EN);
+ loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, ccr);
+
+ loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_MASKI);
+}
+
+static int loongson2_cmc_dma_terminate_all(struct dma_chan *chan)
+{
+ struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
+ unsigned long flags;
+
+ LIST_HEAD(head);
+
+ spin_lock_irqsave(&lchan->vchan.lock, flags);
+ if (lchan->desc) {
+ vchan_terminate_vdesc(&lchan->desc->vdesc);
+ loongson2_cmc_dma_stop(lchan);
+ lchan->desc = NULL;
+ }
+ vchan_get_all_descriptors(&lchan->vchan, &head);
+ spin_unlock_irqrestore(&lchan->vchan.lock, flags);
+
+ vchan_dma_desc_free_list(&lchan->vchan, &head);
+
+ return 0;
+}
+
+static void loongson2_cmc_dma_synchronize(struct dma_chan *chan)
+{
+ struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
+
+ vchan_synchronize(&lchan->vchan);
+}
+
+static void loongson2_cmc_dma_start_transfer(struct loongson2_cmc_dma_chan *lchan)
+{
+ struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
+ struct loongson2_cmc_dma_sg_req *sg_req;
+ struct loongson2_cmc_dma_chan_reg *reg;
+ struct virt_dma_desc *vdesc;
+
+ loongson2_cmc_dma_stop(lchan);
+
+ if (!lchan->desc) {
+ vdesc = vchan_next_desc(&lchan->vchan);
+ if (!vdesc)
+ return;
+
+ list_del(&vdesc->node);
+ lchan->desc = to_lmdma_desc(vdesc);
+ lchan->next_sg = 0;
+ }
+
+ if (lchan->next_sg == lchan->desc->num_sgs)
+ lchan->next_sg = 0;
+
+ sg_req = &lchan->desc->sg_req[lchan->next_sg];
+ reg = &sg_req->chan_reg;
+
+ loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, reg->ccr);
+ loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CNDTR, lchan->id, reg->cndtr);
+ loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CPAR, lchan->id, reg->cpar);
+ loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CMAR, lchan->id, reg->cmar);
+
+ lchan->next_sg++;
+
+ /* Start DMA */
+ reg->ccr |= LOONGSON2_CMCDMA_CCR_EN;
+ loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, reg->ccr);
+}
+
+static void loongson2_cmc_dma_configure_next_sg(struct loongson2_cmc_dma_chan *lchan)
+{
+ struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
+ struct loongson2_cmc_dma_sg_req *sg_req;
+ u32 ccr, id = lchan->id;
+
+ if (lchan->next_sg == lchan->desc->num_sgs)
+ lchan->next_sg = 0;
+
+ /* stop to update mem addr */
+ ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, id);
+ ccr &= ~LOONGSON2_CMCDMA_CCR_EN;
+ loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, id, ccr);
+
+ sg_req = &lchan->desc->sg_req[lchan->next_sg];
+ loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CMAR, id, sg_req->chan_reg.cmar);
+
+ /* start transition */
+ ccr |= LOONGSON2_CMCDMA_CCR_EN;
+ loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, id, ccr);
+}
+
+static void loongson2_cmc_dma_handle_chan_done(struct loongson2_cmc_dma_chan *lchan)
+{
+ if (!lchan->desc)
+ return;
+
+ if (lchan->desc->cyclic) {
+ vchan_cyclic_callback(&lchan->desc->vdesc);
+ /* LOONGSON2_CMCDMA_CCR_CIRC mode don't need update register */
+ if (lchan->desc->num_sgs == 1)
+ return;
+ loongson2_cmc_dma_configure_next_sg(lchan);
+ lchan->next_sg++;
+ } else {
+ if (lchan->next_sg == lchan->desc->num_sgs) {
+ vchan_cookie_complete(&lchan->desc->vdesc);
+ lchan->desc = NULL;
+ }
+ loongson2_cmc_dma_start_transfer(lchan);
+ }
+}
+
+static irqreturn_t loongson2_cmc_dma_chan_irq(int irq, void *devid)
+{
+ struct loongson2_cmc_dma_chan *lchan = devid;
+ struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
+ u32 ists, status, scr;
+
+ spin_lock(&lchan->vchan.lock);
+
+ ists = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_ISR, 0);
+ scr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
+
+ status = (ists >> (4 * lchan->id)) & LOONGSON2_CMCDMA_MASKI;
+ status &= scr;
+
+ if (status & LOONGSON2_CMCDMA_TCI)
+ loongson2_cmc_dma_handle_chan_done(lchan);
+
+ if (status & LOONGSON2_CMCDMA_HTI)
+ loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
+
+ if (status & LOONGSON2_CMCDMA_TEI)
+ dev_err(chan2dev(lchan), "DMA Transform Error\n");
+
+ loongson2_cmc_dma_irq_clear(lchan, status);
+
+ spin_unlock(&lchan->vchan.lock);
+
+ return IRQ_HANDLED;
+}
+
+static void loongson2_cmc_dma_issue_pending(struct dma_chan *chan)
+{
+ struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
+ unsigned long flags;
+
+ spin_lock_irqsave(&lchan->vchan.lock, flags);
+ if (vchan_issue_pending(&lchan->vchan) && !lchan->desc) {
+ dev_dbg(chan2dev(lchan), "vchan %pK: issued\n", &lchan->vchan);
+ loongson2_cmc_dma_start_transfer(lchan);
+ }
+ spin_unlock_irqrestore(&lchan->vchan.lock, flags);
+}
+
+static int loongson2_cmc_dma_set_xfer_param(struct loongson2_cmc_dma_chan *lchan,
+ enum dma_transfer_direction direction,
+ enum dma_slave_buswidth *buswidth, u32 buf_len)
+{
+ struct dma_slave_config sconfig = lchan->dma_sconfig;
+ int dev_width;
+ u32 ccr;
+
+ switch (direction) {
+ case DMA_MEM_TO_DEV:
+ dev_width = loongson2_cmc_dma_get_width(lchan, sconfig.dst_addr_width);
+ if (dev_width < 0)
+ return dev_width;
+ lchan->chan_reg.cpar = sconfig.dst_addr;
+ ccr = LOONGSON2_CMCDMA_CCR_DIR;
+ *buswidth = sconfig.dst_addr_width;
+ break;
+ case DMA_DEV_TO_MEM:
+ dev_width = loongson2_cmc_dma_get_width(lchan, sconfig.src_addr_width);
+ if (dev_width < 0)
+ return dev_width;
+ lchan->chan_reg.cpar = sconfig.src_addr;
+ ccr = LOONGSON2_CMCDMA_CCR_MINC;
+ *buswidth = sconfig.src_addr_width;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ccr |= FIELD_PREP(LOONGSON2_CMCDMA_CCR_PSIZE_MASK, dev_width) |
+ FIELD_PREP(LOONGSON2_CMCDMA_CCR_MSIZE_MASK, dev_width);
+
+ /* Set DMA control register */
+ lchan->chan_reg.ccr &= ~(LOONGSON2_CMCDMA_CCR_PSIZE_MASK | LOONGSON2_CMCDMA_CCR_MSIZE_MASK);
+ lchan->chan_reg.ccr |= ccr;
+
+ return 0;
+}
+
+static struct dma_async_tx_descriptor *
+loongson2_cmc_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, u32 sg_len,
+ enum dma_transfer_direction direction,
+ unsigned long flags, void *context)
+{
+ struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
+ struct loongson2_cmc_dma_desc *desc;
+ enum dma_slave_buswidth buswidth;
+ struct scatterlist *sg;
+ u32 num_items, i;
+ int ret;
+
+ desc = kzalloc(struct_size(desc, sg_req, sg_len), GFP_NOWAIT);
+ if (!desc)
+ return NULL;
+
+ for_each_sg(sgl, sg, sg_len, i) {
+ ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, sg_dma_len(sg));
+ if (ret)
+ return NULL;
+
+ desc->sg_req[i].len = sg_dma_len(sg);
+
+ num_items = desc->sg_req[i].len / buswidth;
+ if (num_items >= SZ_64K) {
+ dev_err(chan2dev(lchan), "Number of items not supported\n");
+ kfree(desc);
+ return NULL;
+ }
+ desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr;
+ desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar;
+ desc->sg_req[i].chan_reg.cmar = sg_dma_address(sg);
+ desc->sg_req[i].chan_reg.cndtr = num_items;
+ }
+
+ desc->num_sgs = sg_len;
+ desc->cyclic = false;
+
+ return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags);
+}
+
+static struct dma_async_tx_descriptor *
+loongson2_cmc_dma_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
+ size_t period_len, enum dma_transfer_direction direction,
+ unsigned long flags)
+{
+ struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
+ struct loongson2_cmc_dma_desc *desc;
+ enum dma_slave_buswidth buswidth;
+ u32 num_periods, num_items, i;
+ int ret;
+
+ if (unlikely(buf_len % period_len))
+ return NULL;
+
+ ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, period_len);
+ if (ret)
+ return NULL;
+
+ num_items = period_len / buswidth;
+ if (num_items >= SZ_64K) {
+ dev_err(chan2dev(lchan), "Number of items not supported\n");
+ return NULL;
+ }
+
+ /* Enable Circular mode */
+ if (buf_len == period_len)
+ lchan->chan_reg.ccr |= LOONGSON2_CMCDMA_CCR_CIRC;
+
+ num_periods = buf_len / period_len;
+ desc = kzalloc(struct_size(desc, sg_req, num_periods), GFP_NOWAIT);
+ if (!desc)
+ return NULL;
+
+ for (i = 0; i < num_periods; i++) {
+ desc->sg_req[i].len = period_len;
+ desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr;
+ desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar;
+ desc->sg_req[i].chan_reg.cmar = buf_addr;
+ desc->sg_req[i].chan_reg.cndtr = num_items;
+ buf_addr += period_len;
+ }
+
+ desc->num_sgs = num_periods;
+ desc->cyclic = true;
+
+ return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags);
+}
+
+static size_t loongson2_cmc_dma_desc_residue(struct loongson2_cmc_dma_chan *lchan,
+ struct loongson2_cmc_dma_desc *desc, u32 next_sg)
+{
+ struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
+ u32 residue, width, ndtr, ccr, i;
+
+ ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
+ width = FIELD_GET(LOONGSON2_CMCDMA_CCR_PSIZE_MASK, ccr);
+
+ ndtr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CNDTR, lchan->id);
+ residue = ndtr << width;
+
+ if (lchan->desc->cyclic && next_sg == 0)
+ return residue;
+
+ for (i = next_sg; i < desc->num_sgs; i++)
+ residue += desc->sg_req[i].len;
+
+ return residue;
+}
+
+static enum dma_status loongson2_cmc_dma_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
+ struct dma_tx_state *state)
+{
+ struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
+ struct virt_dma_desc *vdesc;
+ enum dma_status status;
+ unsigned long flags;
+
+ status = dma_cookie_status(chan, cookie, state);
+ if (status == DMA_COMPLETE || !state)
+ return status;
+
+ spin_lock_irqsave(&lchan->vchan.lock, flags);
+ vdesc = vchan_find_desc(&lchan->vchan, cookie);
+ if (lchan->desc && cookie == lchan->desc->vdesc.tx.cookie)
+ state->residue = loongson2_cmc_dma_desc_residue(lchan, lchan->desc, lchan->next_sg);
+ else if (vdesc)
+ state->residue = loongson2_cmc_dma_desc_residue(lchan, to_lmdma_desc(vdesc), 0);
+
+ spin_unlock_irqrestore(&lchan->vchan.lock, flags);
+
+ return status;
+}
+
+static void loongson2_cmc_dma_free_chan_resources(struct dma_chan *chan)
+{
+ vchan_free_chan_resources(to_virt_chan(chan));
+}
+
+static void loongson2_cmc_dma_desc_free(struct virt_dma_desc *vdesc)
+{
+ kfree(to_lmdma_desc(vdesc));
+}
+
+static bool loongson2_cmc_dma_acpi_filter(struct dma_chan *chan, void *param)
+{
+ struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
+ struct acpi_dma_spec *dma_spec = param;
+
+ memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg));
+ lchan->chan_reg.ccr = dma_spec->chan_id & LOONGSON2_CMCDMA_STREAM_MASK;
+
+ return true;
+}
+
+static int loongson2_cmc_dma_acpi_controller_register(struct loongson2_cmc_dma_dev *lddev)
+{
+ struct device *dev = lddev->ddev.dev;
+ struct acpi_dma_filter_info *info;
+ int ret;
+
+ if (!has_acpi_companion(dev))
+ return 0;
+
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ dma_cap_zero(info->dma_cap);
+ info->dma_cap = lddev->ddev.cap_mask;
+ info->filter_fn = loongson2_cmc_dma_acpi_filter;
+
+ ret = devm_acpi_dma_controller_register(dev, acpi_dma_simple_xlate, info);
+ if (ret)
+ dev_err(dev, "could not register acpi_dma_controller\n");
+
+ return ret;
+}
+
+static struct dma_chan *loongson2_cmc_dma_of_xlate(struct of_phandle_args *dma_spec,
+ struct of_dma *ofdma)
+{
+ struct loongson2_cmc_dma_dev *lddev = ofdma->of_dma_data;
+ struct device *dev = lddev->ddev.dev;
+ struct loongson2_cmc_dma_chan *lchan;
+ struct dma_chan *chan;
+
+ if (dma_spec->args_count < 2)
+ return NULL;
+
+ if (dma_spec->args[0] >= lddev->nr_channels) {
+ dev_err(dev, "Invalid channel id\n");
+ return NULL;
+ }
+
+ lchan = &lddev->chan[dma_spec->args[0]];
+ chan = dma_get_slave_channel(&lchan->vchan.chan);
+ if (!chan) {
+ dev_err(dev, "No more channels available\n");
+ return NULL;
+ }
+
+ memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg));
+ lchan->chan_reg.ccr = dma_spec->args[1] & LOONGSON2_CMCDMA_STREAM_MASK;
+
+ return chan;
+}
+
+static int loongson2_cmc_dma_of_controller_register(struct loongson2_cmc_dma_dev *lddev)
+{
+ struct device *dev = lddev->ddev.dev;
+ int ret;
+
+ if (!dev->of_node)
+ return 0;
+
+ ret = of_dma_controller_register(dev->of_node, loongson2_cmc_dma_of_xlate, lddev);
+ if (ret)
+ dev_err(dev, "could not register of_dma_controller\n");
+
+ return ret;
+}
+
+static int loongson2_cmc_dma_probe(struct platform_device *pdev)
+{
+ const struct loongson2_cmc_dma_config *config;
+ struct loongson2_cmc_dma_chan *lchan;
+ struct loongson2_cmc_dma_dev *lddev;
+ struct device *dev = &pdev->dev;
+ struct dma_device *ddev;
+ u32 nr_chans, i;
+ int ret;
+
+ config = (const struct loongson2_cmc_dma_config *)device_get_match_data(dev);
+ if (!config)
+ return -EINVAL;
+
+ ret = device_property_read_u32(dev, "dma-channels", &nr_chans);
+ if (ret || nr_chans > config->max_channels) {
+ dev_err(dev, "missing or invalid dma-channels property\n");
+ nr_chans = config->max_channels;
+ }
+
+ lddev = devm_kzalloc(dev, struct_size(lddev, chan, nr_chans), GFP_KERNEL);
+ if (!lddev)
+ return -ENOMEM;
+
+ lddev->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(lddev->base))
+ return PTR_ERR(lddev->base);
+
+ platform_set_drvdata(pdev, lddev);
+ lddev->nr_channels = nr_chans;
+ lddev->chan_reg_offset = config->chan_reg_offset;
+
+ lddev->dma_clk = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(lddev->dma_clk))
+ return dev_err_probe(dev, PTR_ERR(lddev->dma_clk), "Failed to get dma clock\n");
+
+ ddev = &lddev->ddev;
+ ddev->dev = dev;
+
+ dma_cap_zero(ddev->cap_mask);
+ dma_cap_set(DMA_SLAVE, ddev->cap_mask);
+ dma_cap_set(DMA_PRIVATE, ddev->cap_mask);
+ dma_cap_set(DMA_CYCLIC, ddev->cap_mask);
+
+ ddev->device_free_chan_resources = loongson2_cmc_dma_free_chan_resources;
+ ddev->device_config = loongson2_cmc_dma_slave_config;
+ ddev->device_prep_slave_sg = loongson2_cmc_dma_prep_slave_sg;
+ ddev->device_prep_dma_cyclic = loongson2_cmc_dma_prep_dma_cyclic;
+ ddev->device_issue_pending = loongson2_cmc_dma_issue_pending;
+ ddev->device_synchronize = loongson2_cmc_dma_synchronize;
+ ddev->device_tx_status = loongson2_cmc_dma_tx_status;
+ ddev->device_terminate_all = loongson2_cmc_dma_terminate_all;
+
+ ddev->src_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS;
+ ddev->dst_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS;
+ ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
+ INIT_LIST_HEAD(&ddev->channels);
+
+ for (i = 0; i < nr_chans; i++) {
+ lchan = &lddev->chan[i];
+
+ lchan->id = i;
+ lchan->vchan.desc_free = loongson2_cmc_dma_desc_free;
+ vchan_init(&lchan->vchan, ddev);
+ }
+
+ ret = dmaenginem_async_device_register(ddev);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < nr_chans; i++) {
+ lchan = &lddev->chan[i];
+
+ lchan->irq = platform_get_irq(pdev, i);
+ if (lchan->irq < 0)
+ return lchan->irq;
+
+ ret = devm_request_irq(dev, lchan->irq, loongson2_cmc_dma_chan_irq, IRQF_SHARED,
+ dev_name(chan2dev(lchan)), lchan);
+ if (ret)
+ return ret;
+ }
+
+ ret = loongson2_cmc_dma_acpi_controller_register(lddev);
+ if (ret)
+ return ret;
+
+ return loongson2_cmc_dma_of_controller_register(lddev);
+}
+
+static void loongson2_cmc_dma_remove(struct platform_device *pdev)
+{
+ of_dma_controller_free(pdev->dev.of_node);
+}
+
+static const struct of_device_id loongson2_cmc_dma_of_match[] = {
+ { .compatible = "loongson,ls2k0300-dma", .data = &ls2k0300_cmc_dma_config },
+ { .compatible = "loongson,ls2k3000-dma", .data = &ls2k3000_cmc_dma_config },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, loongson2_cmc_dma_of_match);
+
+static const struct acpi_device_id loongson2_cmc_dma_acpi_match[] = {
+ { "LOON0014", .driver_data = (kernel_ulong_t)&ls2k3000_cmc_dma_config },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(acpi, loongson2_cmc_dma_acpi_match);
+
+static struct platform_driver loongson2_cmc_dma_driver = {
+ .driver = {
+ .name = "loongson2-apb-cmc-dma",
+ .of_match_table = loongson2_cmc_dma_of_match,
+ .acpi_match_table = loongson2_cmc_dma_acpi_match,
+ },
+ .probe = loongson2_cmc_dma_probe,
+ .remove = loongson2_cmc_dma_remove,
+};
+module_platform_driver(loongson2_cmc_dma_driver);
+
+MODULE_DESCRIPTION("Looongson-2 Multi-Channel DMA Controller driver");
+MODULE_AUTHOR("Loongson Technology Corporation Limited");
+MODULE_LICENSE("GPL");
--
2.52.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/4] dmaengine: loongson: New directory for Loongson DMA controllers drivers
2026-02-09 3:04 ` [PATCH v2 1/4] dmaengine: loongson: New directory for Loongson DMA controllers drivers Binbin Zhou
@ 2026-02-09 16:48 ` Frank Li
2026-02-10 1:27 ` Binbin Zhou
0 siblings, 1 reply; 16+ messages in thread
From: Frank Li @ 2026-02-09 16:48 UTC (permalink / raw)
To: Binbin Zhou
Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Vinod Koul, dmaengine, Xiaochuang Mao, Huacai Chen,
Xuerui Wang, loongarch, devicetree, Keguang Zhang, linux-mips,
jeffbai
On Mon, Feb 09, 2026 at 11:04:18AM +0800, Binbin Zhou wrote:
> Gather the Loongson DMA controllers under drivers/dma/loongson/
>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
> MAINTAINERS | 3 +-
> drivers/dma/Kconfig | 25 ++---------------
> drivers/dma/Makefile | 3 +-
> drivers/dma/loongson/Kconfig | 28 +++++++++++++++++++
> drivers/dma/loongson/Makefile | 3 ++
> .../dma/{ => loongson}/loongson1-apb-dma.c | 4 +--
> .../dma/{ => loongson}/loongson2-apb-dma.c | 4 +--
> 7 files changed, 40 insertions(+), 30 deletions(-)
> create mode 100644 drivers/dma/loongson/Kconfig
> create mode 100644 drivers/dma/loongson/Makefile
> rename drivers/dma/{ => loongson}/loongson1-apb-dma.c (99%)
> rename drivers/dma/{ => loongson}/loongson2-apb-dma.c (99%)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f630328ca6ae..27f77b68d596 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14777,7 +14777,7 @@ M: Binbin Zhou <zhoubinbin@loongson.cn>
> L: dmaengine@vger.kernel.org
> S: Maintained
> F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
> -F: drivers/dma/loongson2-apb-dma.c
> +F: drivers/dma/loongson/loongson2-apb-dma.c
>
> LOONGSON LS2X I2C DRIVER
> M: Binbin Zhou <zhoubinbin@loongson.cn>
> @@ -17515,6 +17515,7 @@ F: arch/mips/boot/dts/loongson/loongson1*
> F: arch/mips/configs/loongson1_defconfig
> F: arch/mips/loongson32/
> F: drivers/*/*loongson1*
> +F: drivers/dma/loongson/loongson1-apb-dma.c
> F: drivers/mtd/nand/raw/loongson-nand-controller.c
> F: drivers/net/ethernet/stmicro/stmmac/dwmac-loongson1.c
> F: sound/soc/loongson/loongson1_ac97.c
> diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
> index 66cda7cc9f7a..1b84c5b11654 100644
> --- a/drivers/dma/Kconfig
> +++ b/drivers/dma/Kconfig
> @@ -376,29 +376,6 @@ config K3_DMA
> Support the DMA engine for Hisilicon K3 platform
> devices.
>
> -config LOONGSON1_APB_DMA
> - tristate "Loongson1 APB DMA support"
> - depends on MACH_LOONGSON32 || COMPILE_TEST
> - select DMA_ENGINE
> - select DMA_VIRTUAL_CHANNELS
> - help
> - This selects support for the APB DMA controller in Loongson1 SoCs,
> - which is required by Loongson1 NAND and audio support.
> -
> -config LOONGSON2_APB_DMA
> - tristate "Loongson2 APB DMA support"
> - depends on LOONGARCH || COMPILE_TEST
> - select DMA_ENGINE
> - select DMA_VIRTUAL_CHANNELS
> - help
> - Support for the Loongson2 APB DMA controller driver. The
> - DMA controller is having single DMA channel which can be
> - configured for different peripherals like audio, nand, sdio
> - etc which is in APB bus.
> -
> - This DMA controller transfers data from memory to peripheral fifo.
> - It does not support memory to memory data transfer.
> -
> config LPC18XX_DMAMUX
> bool "NXP LPC18xx/43xx DMA MUX for PL080"
> depends on ARCH_LPC18XX || COMPILE_TEST
> @@ -774,6 +751,8 @@ source "drivers/dma/fsl-dpaa2-qdma/Kconfig"
>
> source "drivers/dma/lgm/Kconfig"
>
> +source "drivers/dma/loongson/Kconfig"
> +
> source "drivers/dma/stm32/Kconfig"
>
> # clients
> diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
> index a54d7688392b..a1c73415b79f 100644
> --- a/drivers/dma/Makefile
> +++ b/drivers/dma/Makefile
> @@ -49,8 +49,6 @@ obj-$(CONFIG_INTEL_IDMA64) += idma64.o
> obj-$(CONFIG_INTEL_IOATDMA) += ioat/
> obj-y += idxd/
> obj-$(CONFIG_K3_DMA) += k3dma.o
> -obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> -obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> obj-$(CONFIG_LPC18XX_DMAMUX) += lpc18xx-dmamux.o
> obj-$(CONFIG_LPC32XX_DMAMUX) += lpc32xx-dmamux.o
> obj-$(CONFIG_MILBEAUT_HDMAC) += milbeaut-hdmac.o
> @@ -88,6 +86,7 @@ obj-$(CONFIG_INTEL_LDMA) += lgm/
>
> obj-y += amd/
> obj-y += mediatek/
> +obj-y += loongson/
keep alphabet order
Frank
> obj-y += qcom/
> obj-y += stm32/
> obj-y += ti/
> diff --git a/drivers/dma/loongson/Kconfig b/drivers/dma/loongson/Kconfig
> new file mode 100644
> index 000000000000..9dbdaef5a59f
> --- /dev/null
> +++ b/drivers/dma/loongson/Kconfig
> @@ -0,0 +1,28 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Loongson DMA controllers drivers
> +#
> +if MACH_LOONGSON32 || MACH_LOONGSON64 || COMPILE_TEST
> +
> +config LOONGSON1_APB_DMA
> + tristate "Loongson1 APB DMA support"
> + select DMA_ENGINE
> + select DMA_VIRTUAL_CHANNELS
> + help
> + This selects support for the APB DMA controller in Loongson1 SoCs,
> + which is required by Loongson1 NAND and audio support.
> +
> +config LOONGSON2_APB_DMA
> + tristate "Loongson2 APB DMA support"
> + select DMA_ENGINE
> + select DMA_VIRTUAL_CHANNELS
> + help
> + Support for the Loongson2 APB DMA controller driver. The
> + DMA controller is having single DMA channel which can be
> + configured for different peripherals like audio, nand, sdio
> + etc which is in APB bus.
> +
> + This DMA controller transfers data from memory to peripheral fifo.
> + It does not support memory to memory data transfer.
> +
> +endif
> diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
> new file mode 100644
> index 000000000000..6cdd08065e92
> --- /dev/null
> +++ b/drivers/dma/loongson/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> +obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> diff --git a/drivers/dma/loongson1-apb-dma.c b/drivers/dma/loongson/loongson1-apb-dma.c
> similarity index 99%
> rename from drivers/dma/loongson1-apb-dma.c
> rename to drivers/dma/loongson/loongson1-apb-dma.c
> index 255fe7eca212..e99247cf90c1 100644
> --- a/drivers/dma/loongson1-apb-dma.c
> +++ b/drivers/dma/loongson/loongson1-apb-dma.c
> @@ -16,8 +16,8 @@
> #include <linux/platform_device.h>
> #include <linux/slab.h>
>
> -#include "dmaengine.h"
> -#include "virt-dma.h"
> +#include "../dmaengine.h"
> +#include "../virt-dma.h"
>
> /* Loongson-1 DMA Control Register */
> #define LS1X_DMA_CTRL 0x0
> diff --git a/drivers/dma/loongson2-apb-dma.c b/drivers/dma/loongson/loongson2-apb-dma.c
> similarity index 99%
> rename from drivers/dma/loongson2-apb-dma.c
> rename to drivers/dma/loongson/loongson2-apb-dma.c
> index c528f02b9f84..0cb607595d04 100644
> --- a/drivers/dma/loongson2-apb-dma.c
> +++ b/drivers/dma/loongson/loongson2-apb-dma.c
> @@ -17,8 +17,8 @@
> #include <linux/platform_device.h>
> #include <linux/slab.h>
>
> -#include "dmaengine.h"
> -#include "virt-dma.h"
> +#include "../dmaengine.h"
> +#include "../virt-dma.h"
>
> /* Global Configuration Register */
> #define LDMA_ORDER_ERG 0x0
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 2/4] dmaengine: loongson: loongson2-apb: Convert to dmaenginem_async_device_register()
2026-02-09 3:04 ` [PATCH v2 2/4] dmaengine: loongson: loongson2-apb: Convert to dmaenginem_async_device_register() Binbin Zhou
@ 2026-02-09 16:51 ` Frank Li
0 siblings, 0 replies; 16+ messages in thread
From: Frank Li @ 2026-02-09 16:51 UTC (permalink / raw)
To: Binbin Zhou
Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Vinod Koul, dmaengine, Xiaochuang Mao, Huacai Chen,
Xuerui Wang, loongarch, devicetree, Keguang Zhang, linux-mips,
jeffbai
On Mon, Feb 09, 2026 at 11:04:19AM +0800, Binbin Zhou wrote:
> Use new dmaenginem_async_device_register() helper to simplify the code.
>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
> drivers/dma/loongson/loongson2-apb-dma.c | 7 ++-----
> 1 file changed, 2 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/dma/loongson/loongson2-apb-dma.c b/drivers/dma/loongson/loongson2-apb-dma.c
> index 0cb607595d04..5e9a13ad9aa2 100644
> --- a/drivers/dma/loongson/loongson2-apb-dma.c
> +++ b/drivers/dma/loongson/loongson2-apb-dma.c
> @@ -650,21 +650,19 @@ static int ls2x_dma_probe(struct platform_device *pdev)
> ddev->dst_addr_widths = LDMA_SLAVE_BUSWIDTHS;
> ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
>
> - ret = dma_async_device_register(&priv->ddev);
> + ret = dmaenginem_async_device_register(&priv->ddev);
> if (ret < 0)
> goto disable_clk;
>
> ret = of_dma_controller_register(dev->of_node, of_dma_xlate_by_chan_id, priv);
> if (ret < 0)
> - goto unregister_dmac;
> + goto disable_clk;
This patch is okay.
You can use devm_clk_get_enabled() to simple probe also.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Frank
>
> platform_set_drvdata(pdev, priv);
>
> dev_info(dev, "Loongson LS2X APB DMA driver registered successfully.\n");
> return 0;
>
> -unregister_dmac:
> - dma_async_device_unregister(&priv->ddev);
> disable_clk:
> clk_disable_unprepare(priv->dma_clk);
>
> @@ -680,7 +678,6 @@ static void ls2x_dma_remove(struct platform_device *pdev)
> struct ls2x_dma_priv *priv = platform_get_drvdata(pdev);
>
> of_dma_controller_free(pdev->dev.of_node);
> - dma_async_device_unregister(&priv->ddev);
> clk_disable_unprepare(priv->dma_clk);
> }
>
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/4] dmaengine: loongson: New driver for the Loongson Multi-Channel DMA controller
2026-02-09 3:04 ` [PATCH v2 4/4] dmaengine: loongson: New driver for the " Binbin Zhou
@ 2026-02-09 17:04 ` Frank Li
2026-02-10 7:41 ` Binbin Zhou
0 siblings, 1 reply; 16+ messages in thread
From: Frank Li @ 2026-02-09 17:04 UTC (permalink / raw)
To: Binbin Zhou
Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Vinod Koul, dmaengine, Xiaochuang Mao, Huacai Chen,
Xuerui Wang, loongarch, devicetree, Keguang Zhang, linux-mips,
jeffbai
On Mon, Feb 09, 2026 at 11:04:55AM +0800, Binbin Zhou wrote:
> This DMA controller appears in Loongson-2K0300 and Loongson-2K3000.
>
> It is a chain multi-channel controller that enables data transfers from
> memory to memory, device to memory, and memory to device, as well as
> channel prioritization configurable through the channel configuration
> registers.
>
> In addition, there are slight differences between Loongson-2K0300 and
> Loongson-2K3000, such as channel register offsets and the number of
> channels.
>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
> MAINTAINERS | 1 +
> drivers/dma/loongson/Kconfig | 10 +
> drivers/dma/loongson/Makefile | 1 +
> drivers/dma/loongson/loongson2-apb-cmc-dma.c | 736 +++++++++++++++++++
> 4 files changed, 748 insertions(+)
> create mode 100644 drivers/dma/loongson/loongson2-apb-cmc-dma.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d3cb541aee2a..61a39070d7a0 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14778,6 +14778,7 @@ L: dmaengine@vger.kernel.org
> S: Maintained
> F: Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
> F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
> +F: drivers/dma/loongson/loongson2-apb-cmc-dma.c
> F: drivers/dma/loongson/loongson2-apb-dma.c
>
> LOONGSON LS2X I2C DRIVER
> diff --git a/drivers/dma/loongson/Kconfig b/drivers/dma/loongson/Kconfig
> index 9dbdaef5a59f..28b3daeed4e3 100644
> --- a/drivers/dma/loongson/Kconfig
> +++ b/drivers/dma/loongson/Kconfig
> @@ -25,4 +25,14 @@ config LOONGSON2_APB_DMA
> This DMA controller transfers data from memory to peripheral fifo.
> It does not support memory to memory data transfer.
>
> +config LOONGSON2_APB_CMC_DMA
> + tristate "Loongson2 Chain Multi-Channel DMA support"
> + select DMA_ENGINE
> + select DMA_VIRTUAL_CHANNELS
> + help
> + Support for the Loongson Chain Multi-Channel DMA controller driver.
> + It is discovered on the Loongson-2K chip (Loongson-2K0300/Loongson-2K3000),
> + which has 4/8 channels internally, enabling bidirectional data transfer
> + between devices and memory.
> +
> endif
> diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
> index 6cdd08065e92..48c19781e729 100644
> --- a/drivers/dma/loongson/Makefile
> +++ b/drivers/dma/loongson/Makefile
> @@ -1,3 +1,4 @@
> # SPDX-License-Identifier: GPL-2.0-only
> obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> +obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
> diff --git a/drivers/dma/loongson/loongson2-apb-cmc-dma.c b/drivers/dma/loongson/loongson2-apb-cmc-dma.c
> new file mode 100644
> index 000000000000..f598ad095686
> --- /dev/null
> +++ b/drivers/dma/loongson/loongson2-apb-cmc-dma.c
> @@ -0,0 +1,736 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Looongson-2 Multi-Channel DMA Controller driver
> + *
> + * Copyright (C) 2024-2026 Loongson Technology Corporation Limited
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/acpi_dma.h>
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/dmapool.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_dma.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#include "../dmaengine.h"
> +#include "../virt-dma.h"
> +
> +#define LOONGSON2_CMCDMA_ISR 0x0 /* DMA Interrupt Status Register */
> +#define LOONGSON2_CMCDMA_IFCR 0x4 /* DMA Interrupt Flag Clear Register */
> +#define LOONGSON2_CMCDMA_CCR 0x8 /* DMA Channel Configuration Register */
> +#define LOONGSON2_CMCDMA_CNDTR 0xc /* DMA Channel Transmit Count Register */
> +#define LOONGSON2_CMCDMA_CPAR 0x10 /* DMA Channel Peripheral Address Register */
> +#define LOONGSON2_CMCDMA_CMAR 0x14 /* DMA Channel Memory Address Register */
> +
> +/* Bitfields of DMA interrupt status register */
> +#define LOONGSON2_CMCDMA_TCI BIT(1) /* Transfer Complete Interrupt */
> +#define LOONGSON2_CMCDMA_HTI BIT(2) /* Half Transfer Interrupt */
> +#define LOONGSON2_CMCDMA_TEI BIT(3) /* Transfer Error Interrupt */
> +
> +#define LOONGSON2_CMCDMA_MASKI \
> + (LOONGSON2_CMCDMA_TCI | LOONGSON2_CMCDMA_HTI | LOONGSON2_CMCDMA_TEI)
> +
> +/* Bitfields of DMA channel x Configuration Register */
> +#define LOONGSON2_CMCDMA_CCR_EN BIT(0) /* Stream Enable */
> +#define LOONGSON2_CMCDMA_CCR_TCIE BIT(1) /* Transfer Complete Interrupt Enable */
> +#define LOONGSON2_CMCDMA_CCR_HTIE BIT(2) /* Half Transfer Complete Interrupt Enable */
> +#define LOONGSON2_CMCDMA_CCR_TEIE BIT(3) /* Transfer Error Interrupt Enable */
> +#define LOONGSON2_CMCDMA_CCR_DIR BIT(4) /* Data Transfer Direction */
> +#define LOONGSON2_CMCDMA_CCR_CIRC BIT(5) /* Circular mode */
> +#define LOONGSON2_CMCDMA_CCR_PINC BIT(6) /* Peripheral increment mode */
> +#define LOONGSON2_CMCDMA_CCR_MINC BIT(7) /* Memory increment mode */
> +#define LOONGSON2_CMCDMA_CCR_PSIZE_MASK GENMASK(9, 8)
> +#define LOONGSON2_CMCDMA_CCR_MSIZE_MASK GENMASK(11, 10)
> +#define LOONGSON2_CMCDMA_CCR_PL_MASK GENMASK(13, 12)
> +#define LOONGSON2_CMCDMA_CCR_M2M BIT(14)
> +
> +#define LOONGSON2_CMCDMA_CCR_CFG_MASK \
> + (LOONGSON2_CMCDMA_CCR_PINC | LOONGSON2_CMCDMA_CCR_MINC | LOONGSON2_CMCDMA_CCR_PL_MASK)
> +
> +#define LOONGSON2_CMCDMA_CCR_IRQ_MASK \
> + (LOONGSON2_CMCDMA_CCR_TCIE | LOONGSON2_CMCDMA_CCR_HTIE | LOONGSON2_CMCDMA_CCR_TEIE)
> +
> +#define LOONGSON2_CMCDMA_STREAM_MASK \
> + (LOONGSON2_CMCDMA_CCR_CFG_MASK | LOONGSON2_CMCDMA_CCR_IRQ_MASK)
> +
> +#define LOONGSON2_CMCDMA_BUSWIDTHS (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \
> + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \
> + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES))
> +
> +enum loongson2_cmc_dma_width {
> + LOONGSON2_CMCDMA_BYTE,
> + LOONGSON2_CMCDMA_HALF_WORD,
> + LOONGSON2_CMCDMA_WORD,
> +};
> +
> +struct loongson2_cmc_dma_chan_reg {
> + u32 ccr;
> + u32 cndtr;
> + u32 cpar;
> + u32 cmar;
> +};
> +
> +struct loongson2_cmc_dma_sg_req {
> + u32 len;
> + struct loongson2_cmc_dma_chan_reg chan_reg;
> +};
> +
> +struct loongson2_cmc_dma_desc {
> + struct virt_dma_desc vdesc;
> + bool cyclic;
> + u32 num_sgs;
> + struct loongson2_cmc_dma_sg_req sg_req[] __counted_by(num_sgs);
> +};
> +
> +struct loongson2_cmc_dma_chan {
> + struct virt_dma_chan vchan;
> + struct dma_slave_config dma_sconfig;
> + struct loongson2_cmc_dma_desc *desc;
> + u32 id;
> + u32 irq;
> + u32 next_sg;
> + struct loongson2_cmc_dma_chan_reg chan_reg;
> +};
> +
> +struct loongson2_cmc_dma_config {
> + u32 max_channels;
> + u32 chan_reg_offset;
> +};
> +
> +struct loongson2_cmc_dma_dev {
> + struct dma_device ddev;
> + struct clk *dma_clk;
> + void __iomem *base;
> + u32 nr_channels;
> + u32 chan_reg_offset;
> + struct loongson2_cmc_dma_chan chan[] __counted_by(nr_channels);
> +};
> +
> +static const struct loongson2_cmc_dma_config ls2k0300_cmc_dma_config = {
> + .max_channels = 8,
> + .chan_reg_offset = 0x14,
> +};
> +
> +static const struct loongson2_cmc_dma_config ls2k3000_cmc_dma_config = {
> + .max_channels = 4,
> + .chan_reg_offset = 0x18,
> +};
> +
> +static struct loongson2_cmc_dma_dev *lmdma_get_dev(struct loongson2_cmc_dma_chan *lchan)
> +{
> + return container_of(lchan->vchan.chan.device, struct loongson2_cmc_dma_dev, ddev);
> +}
> +
> +static struct loongson2_cmc_dma_chan *to_lmdma_chan(struct dma_chan *chan)
> +{
> + return container_of(chan, struct loongson2_cmc_dma_chan, vchan.chan);
> +}
> +
> +static struct loongson2_cmc_dma_desc *to_lmdma_desc(struct virt_dma_desc *vdesc)
> +{
> + return container_of(vdesc, struct loongson2_cmc_dma_desc, vdesc);
> +}
> +
> +static struct device *chan2dev(struct loongson2_cmc_dma_chan *lchan)
> +{
> + return &lchan->vchan.chan.dev->device;
> +}
> +
> +static u32 loongson2_cmc_dma_read(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id)
> +{
> + return readl(lddev->base + (reg + lddev->chan_reg_offset * id));
> +}
> +
> +static void loongson2_cmc_dma_write(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id, u32 val)
> +{
> + writel(val, lddev->base + (reg + lddev->chan_reg_offset * id));
> +}
> +
> +static int loongson2_cmc_dma_get_width(struct loongson2_cmc_dma_chan *lchan,
> + enum dma_slave_buswidth width)
> +{
> + switch (width) {
> + case DMA_SLAVE_BUSWIDTH_1_BYTE:
> + return LOONGSON2_CMCDMA_BYTE;
> + case DMA_SLAVE_BUSWIDTH_2_BYTES:
> + return LOONGSON2_CMCDMA_HALF_WORD;
> + case DMA_SLAVE_BUSWIDTH_4_BYTES:
> + return LOONGSON2_CMCDMA_WORD;
is ffs() helper in case your hardware support more buswidth in future?
> + default:
> + dev_err(chan2dev(lchan), "Dma bus width not supported\n");
> + return -EINVAL;
> + }
> +}
> +
> +static int loongson2_cmc_dma_slave_config(struct dma_chan *chan, struct dma_slave_config *config)
> +{
> + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> +
> + memcpy(&lchan->dma_sconfig, config, sizeof(*config));
> +
> + return 0;
> +}
> +
> +static void loongson2_cmc_dma_irq_clear(struct loongson2_cmc_dma_chan *lchan, u32 flags)
> +{
> + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> + u32 ifcr;
> +
> + ifcr = flags << (4 * lchan->id);
> + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_IFCR, 0, ifcr);
> +}
> +
> +static void loongson2_cmc_dma_stop(struct loongson2_cmc_dma_chan *lchan)
> +{
> + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> + u32 ccr;
> +
> + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
> + ccr &= ~(LOONGSON2_CMCDMA_CCR_IRQ_MASK | LOONGSON2_CMCDMA_CCR_EN);
> + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, ccr);
> +
> + loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_MASKI);
> +}
> +
> +static int loongson2_cmc_dma_terminate_all(struct dma_chan *chan)
> +{
> + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> + unsigned long flags;
> +
> + LIST_HEAD(head);
> +
> + spin_lock_irqsave(&lchan->vchan.lock, flags);
> + if (lchan->desc) {
> + vchan_terminate_vdesc(&lchan->desc->vdesc);
> + loongson2_cmc_dma_stop(lchan);
> + lchan->desc = NULL;
> + }
> + vchan_get_all_descriptors(&lchan->vchan, &head);
> + spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> +
> + vchan_dma_desc_free_list(&lchan->vchan, &head);
> +
> + return 0;
> +}
> +
> +static void loongson2_cmc_dma_synchronize(struct dma_chan *chan)
> +{
> + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> +
> + vchan_synchronize(&lchan->vchan);
> +}
> +
> +static void loongson2_cmc_dma_start_transfer(struct loongson2_cmc_dma_chan *lchan)
> +{
> + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> + struct loongson2_cmc_dma_sg_req *sg_req;
> + struct loongson2_cmc_dma_chan_reg *reg;
> + struct virt_dma_desc *vdesc;
> +
> + loongson2_cmc_dma_stop(lchan);
> +
> + if (!lchan->desc) {
> + vdesc = vchan_next_desc(&lchan->vchan);
> + if (!vdesc)
> + return;
> +
> + list_del(&vdesc->node);
> + lchan->desc = to_lmdma_desc(vdesc);
> + lchan->next_sg = 0;
> + }
> +
> + if (lchan->next_sg == lchan->desc->num_sgs)
> + lchan->next_sg = 0;
> +
> + sg_req = &lchan->desc->sg_req[lchan->next_sg];
> + reg = &sg_req->chan_reg;
> +
> + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, reg->ccr);
> + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CNDTR, lchan->id, reg->cndtr);
> + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CPAR, lchan->id, reg->cpar);
> + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CMAR, lchan->id, reg->cmar);
> +
> + lchan->next_sg++;
> +
> + /* Start DMA */
> + reg->ccr |= LOONGSON2_CMCDMA_CCR_EN;
> + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, reg->ccr);
> +}
> +
> +static void loongson2_cmc_dma_configure_next_sg(struct loongson2_cmc_dma_chan *lchan)
> +{
> + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> + struct loongson2_cmc_dma_sg_req *sg_req;
> + u32 ccr, id = lchan->id;
> +
> + if (lchan->next_sg == lchan->desc->num_sgs)
> + lchan->next_sg = 0;
> +
> + /* stop to update mem addr */
> + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, id);
> + ccr &= ~LOONGSON2_CMCDMA_CCR_EN;
> + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, id, ccr);
> +
> + sg_req = &lchan->desc->sg_req[lchan->next_sg];
> + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CMAR, id, sg_req->chan_reg.cmar);
> +
> + /* start transition */
> + ccr |= LOONGSON2_CMCDMA_CCR_EN;
> + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, id, ccr);
> +}
> +
> +static void loongson2_cmc_dma_handle_chan_done(struct loongson2_cmc_dma_chan *lchan)
> +{
> + if (!lchan->desc)
> + return;
> +
> + if (lchan->desc->cyclic) {
> + vchan_cyclic_callback(&lchan->desc->vdesc);
> + /* LOONGSON2_CMCDMA_CCR_CIRC mode don't need update register */
> + if (lchan->desc->num_sgs == 1)
> + return;
> + loongson2_cmc_dma_configure_next_sg(lchan);
> + lchan->next_sg++;
> + } else {
> + if (lchan->next_sg == lchan->desc->num_sgs) {
> + vchan_cookie_complete(&lchan->desc->vdesc);
> + lchan->desc = NULL;
> + }
> + loongson2_cmc_dma_start_transfer(lchan);
> + }
> +}
> +
> +static irqreturn_t loongson2_cmc_dma_chan_irq(int irq, void *devid)
> +{
> + struct loongson2_cmc_dma_chan *lchan = devid;
> + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> + u32 ists, status, scr;
> +
> + spin_lock(&lchan->vchan.lock);
> +
> + ists = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_ISR, 0);
> + scr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
> +
> + status = (ists >> (4 * lchan->id)) & LOONGSON2_CMCDMA_MASKI;
> + status &= scr;
> +
> + if (status & LOONGSON2_CMCDMA_TCI)
> + loongson2_cmc_dma_handle_chan_done(lchan);
> +
> + if (status & LOONGSON2_CMCDMA_HTI)
> + loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
> +
> + if (status & LOONGSON2_CMCDMA_TEI)
> + dev_err(chan2dev(lchan), "DMA Transform Error\n");
> +
> + loongson2_cmc_dma_irq_clear(lchan, status);
irq clear should before loongson2_cmc_dma_handle_chan_done() incase you
missed irq, if loongson2_cmc_dma_handle_chan_done() trigger new irq before
your call irq_cler().
> +
> + spin_unlock(&lchan->vchan.lock);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void loongson2_cmc_dma_issue_pending(struct dma_chan *chan)
> +{
> + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&lchan->vchan.lock, flags);
> + if (vchan_issue_pending(&lchan->vchan) && !lchan->desc) {
> + dev_dbg(chan2dev(lchan), "vchan %pK: issued\n", &lchan->vchan);
> + loongson2_cmc_dma_start_transfer(lchan);
> + }
> + spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> +}
> +
> +static int loongson2_cmc_dma_set_xfer_param(struct loongson2_cmc_dma_chan *lchan,
> + enum dma_transfer_direction direction,
> + enum dma_slave_buswidth *buswidth, u32 buf_len)
> +{
> + struct dma_slave_config sconfig = lchan->dma_sconfig;
> + int dev_width;
> + u32 ccr;
> +
> + switch (direction) {
> + case DMA_MEM_TO_DEV:
> + dev_width = loongson2_cmc_dma_get_width(lchan, sconfig.dst_addr_width);
> + if (dev_width < 0)
> + return dev_width;
> + lchan->chan_reg.cpar = sconfig.dst_addr;
> + ccr = LOONGSON2_CMCDMA_CCR_DIR;
> + *buswidth = sconfig.dst_addr_width;
> + break;
> + case DMA_DEV_TO_MEM:
> + dev_width = loongson2_cmc_dma_get_width(lchan, sconfig.src_addr_width);
> + if (dev_width < 0)
> + return dev_width;
> + lchan->chan_reg.cpar = sconfig.src_addr;
> + ccr = LOONGSON2_CMCDMA_CCR_MINC;
> + *buswidth = sconfig.src_addr_width;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + ccr |= FIELD_PREP(LOONGSON2_CMCDMA_CCR_PSIZE_MASK, dev_width) |
> + FIELD_PREP(LOONGSON2_CMCDMA_CCR_MSIZE_MASK, dev_width);
> +
> + /* Set DMA control register */
> + lchan->chan_reg.ccr &= ~(LOONGSON2_CMCDMA_CCR_PSIZE_MASK | LOONGSON2_CMCDMA_CCR_MSIZE_MASK);
> + lchan->chan_reg.ccr |= ccr;
> +
> + return 0;
> +}
> +
> +static struct dma_async_tx_descriptor *
> +loongson2_cmc_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, u32 sg_len,
> + enum dma_transfer_direction direction,
> + unsigned long flags, void *context)
> +{
> + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> + struct loongson2_cmc_dma_desc *desc;
> + enum dma_slave_buswidth buswidth;
> + struct scatterlist *sg;
> + u32 num_items, i;
> + int ret;
> +
> + desc = kzalloc(struct_size(desc, sg_req, sg_len), GFP_NOWAIT);
> + if (!desc)
> + return NULL;
> +
> + for_each_sg(sgl, sg, sg_len, i) {
> + ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, sg_dma_len(sg));
> + if (ret)
> + return NULL;
> +
> + desc->sg_req[i].len = sg_dma_len(sg);
> +
> + num_items = desc->sg_req[i].len / buswidth;
> + if (num_items >= SZ_64K) {
> + dev_err(chan2dev(lchan), "Number of items not supported\n");
> + kfree(desc);
> + return NULL;
if use sg_nents_for_dma(), you can use multi sg to trasfer more than 64K
data.
> + }
> + desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr;
> + desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar;
> + desc->sg_req[i].chan_reg.cmar = sg_dma_address(sg);
> + desc->sg_req[i].chan_reg.cndtr = num_items;
> + }
> +
> + desc->num_sgs = sg_len;
> + desc->cyclic = false;
> +
> + return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags);
> +}
> +
> +static struct dma_async_tx_descriptor *
> +loongson2_cmc_dma_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
> + size_t period_len, enum dma_transfer_direction direction,
> + unsigned long flags)
> +{
> + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> + struct loongson2_cmc_dma_desc *desc;
> + enum dma_slave_buswidth buswidth;
> + u32 num_periods, num_items, i;
> + int ret;
> +
> + if (unlikely(buf_len % period_len))
> + return NULL;
> +
> + ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, period_len);
> + if (ret)
> + return NULL;
> +
> + num_items = period_len / buswidth;
> + if (num_items >= SZ_64K) {
> + dev_err(chan2dev(lchan), "Number of items not supported\n");
> + return NULL;
> + }
> +
> + /* Enable Circular mode */
> + if (buf_len == period_len)
> + lchan->chan_reg.ccr |= LOONGSON2_CMCDMA_CCR_CIRC;
> +
> + num_periods = buf_len / period_len;
> + desc = kzalloc(struct_size(desc, sg_req, num_periods), GFP_NOWAIT);
> + if (!desc)
> + return NULL;
> +
> + for (i = 0; i < num_periods; i++) {
> + desc->sg_req[i].len = period_len;
> + desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr;
> + desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar;
> + desc->sg_req[i].chan_reg.cmar = buf_addr;
> + desc->sg_req[i].chan_reg.cndtr = num_items;
> + buf_addr += period_len;
> + }
> +
> + desc->num_sgs = num_periods;
> + desc->cyclic = true;
> +
> + return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags);
> +}
> +
> +static size_t loongson2_cmc_dma_desc_residue(struct loongson2_cmc_dma_chan *lchan,
> + struct loongson2_cmc_dma_desc *desc, u32 next_sg)
> +{
> + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> + u32 residue, width, ndtr, ccr, i;
> +
> + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
> + width = FIELD_GET(LOONGSON2_CMCDMA_CCR_PSIZE_MASK, ccr);
> +
> + ndtr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CNDTR, lchan->id);
> + residue = ndtr << width;
> +
> + if (lchan->desc->cyclic && next_sg == 0)
> + return residue;
> +
> + for (i = next_sg; i < desc->num_sgs; i++)
> + residue += desc->sg_req[i].len;
> +
> + return residue;
> +}
> +
> +static enum dma_status loongson2_cmc_dma_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
> + struct dma_tx_state *state)
> +{
> + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> + struct virt_dma_desc *vdesc;
> + enum dma_status status;
> + unsigned long flags;
> +
> + status = dma_cookie_status(chan, cookie, state);
> + if (status == DMA_COMPLETE || !state)
> + return status;
> +
> + spin_lock_irqsave(&lchan->vchan.lock, flags);
> + vdesc = vchan_find_desc(&lchan->vchan, cookie);
> + if (lchan->desc && cookie == lchan->desc->vdesc.tx.cookie)
> + state->residue = loongson2_cmc_dma_desc_residue(lchan, lchan->desc, lchan->next_sg);
> + else if (vdesc)
> + state->residue = loongson2_cmc_dma_desc_residue(lchan, to_lmdma_desc(vdesc), 0);
> +
> + spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> +
> + return status;
> +}
> +
> +static void loongson2_cmc_dma_free_chan_resources(struct dma_chan *chan)
> +{
> + vchan_free_chan_resources(to_virt_chan(chan));
> +}
> +
> +static void loongson2_cmc_dma_desc_free(struct virt_dma_desc *vdesc)
> +{
> + kfree(to_lmdma_desc(vdesc));
> +}
> +
> +static bool loongson2_cmc_dma_acpi_filter(struct dma_chan *chan, void *param)
> +{
> + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> + struct acpi_dma_spec *dma_spec = param;
> +
> + memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg));
> + lchan->chan_reg.ccr = dma_spec->chan_id & LOONGSON2_CMCDMA_STREAM_MASK;
> +
> + return true;
> +}
> +
> +static int loongson2_cmc_dma_acpi_controller_register(struct loongson2_cmc_dma_dev *lddev)
> +{
> + struct device *dev = lddev->ddev.dev;
> + struct acpi_dma_filter_info *info;
> + int ret;
> +
> + if (!has_acpi_companion(dev))
> + return 0;
> +
> + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
> + if (!info)
> + return -ENOMEM;
> +
> + dma_cap_zero(info->dma_cap);
> + info->dma_cap = lddev->ddev.cap_mask;
> + info->filter_fn = loongson2_cmc_dma_acpi_filter;
> +
> + ret = devm_acpi_dma_controller_register(dev, acpi_dma_simple_xlate, info);
> + if (ret)
> + dev_err(dev, "could not register acpi_dma_controller\n");
> +
> + return ret;
> +}
> +
> +static struct dma_chan *loongson2_cmc_dma_of_xlate(struct of_phandle_args *dma_spec,
> + struct of_dma *ofdma)
> +{
> + struct loongson2_cmc_dma_dev *lddev = ofdma->of_dma_data;
> + struct device *dev = lddev->ddev.dev;
> + struct loongson2_cmc_dma_chan *lchan;
> + struct dma_chan *chan;
> +
> + if (dma_spec->args_count < 2)
> + return NULL;
> +
> + if (dma_spec->args[0] >= lddev->nr_channels) {
> + dev_err(dev, "Invalid channel id\n");
> + return NULL;
> + }
> +
> + lchan = &lddev->chan[dma_spec->args[0]];
> + chan = dma_get_slave_channel(&lchan->vchan.chan);
> + if (!chan) {
> + dev_err(dev, "No more channels available\n");
> + return NULL;
> + }
> +
> + memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg));
> + lchan->chan_reg.ccr = dma_spec->args[1] & LOONGSON2_CMCDMA_STREAM_MASK;
> +
> + return chan;
> +}
> +
> +static int loongson2_cmc_dma_of_controller_register(struct loongson2_cmc_dma_dev *lddev)
> +{
> + struct device *dev = lddev->ddev.dev;
> + int ret;
> +
> + if (!dev->of_node)
> + return 0;
> +
> + ret = of_dma_controller_register(dev->of_node, loongson2_cmc_dma_of_xlate, lddev);
> + if (ret)
> + dev_err(dev, "could not register of_dma_controller\n");
> +
> + return ret;
> +}
> +
> +static int loongson2_cmc_dma_probe(struct platform_device *pdev)
> +{
> + const struct loongson2_cmc_dma_config *config;
> + struct loongson2_cmc_dma_chan *lchan;
> + struct loongson2_cmc_dma_dev *lddev;
> + struct device *dev = &pdev->dev;
> + struct dma_device *ddev;
> + u32 nr_chans, i;
> + int ret;
> +
> + config = (const struct loongson2_cmc_dma_config *)device_get_match_data(dev);
> + if (!config)
> + return -EINVAL;
> +
> + ret = device_property_read_u32(dev, "dma-channels", &nr_chans);
> + if (ret || nr_chans > config->max_channels) {
> + dev_err(dev, "missing or invalid dma-channels property\n");
> + nr_chans = config->max_channels;
> + }
> +
> + lddev = devm_kzalloc(dev, struct_size(lddev, chan, nr_chans), GFP_KERNEL);
> + if (!lddev)
> + return -ENOMEM;
> +
> + lddev->base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(lddev->base))
> + return PTR_ERR(lddev->base);
> +
> + platform_set_drvdata(pdev, lddev);
> + lddev->nr_channels = nr_chans;
> + lddev->chan_reg_offset = config->chan_reg_offset;
> +
> + lddev->dma_clk = devm_clk_get_optional_enabled(dev, NULL);
> + if (IS_ERR(lddev->dma_clk))
> + return dev_err_probe(dev, PTR_ERR(lddev->dma_clk), "Failed to get dma clock\n");
> +
> + ddev = &lddev->ddev;
> + ddev->dev = dev;
> +
> + dma_cap_zero(ddev->cap_mask);
> + dma_cap_set(DMA_SLAVE, ddev->cap_mask);
> + dma_cap_set(DMA_PRIVATE, ddev->cap_mask);
> + dma_cap_set(DMA_CYCLIC, ddev->cap_mask);
> +
> + ddev->device_free_chan_resources = loongson2_cmc_dma_free_chan_resources;
> + ddev->device_config = loongson2_cmc_dma_slave_config;
> + ddev->device_prep_slave_sg = loongson2_cmc_dma_prep_slave_sg;
> + ddev->device_prep_dma_cyclic = loongson2_cmc_dma_prep_dma_cyclic;
> + ddev->device_issue_pending = loongson2_cmc_dma_issue_pending;
> + ddev->device_synchronize = loongson2_cmc_dma_synchronize;
> + ddev->device_tx_status = loongson2_cmc_dma_tx_status;
> + ddev->device_terminate_all = loongson2_cmc_dma_terminate_all;
> +
> + ddev->src_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS;
> + ddev->dst_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS;
> + ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
> + INIT_LIST_HEAD(&ddev->channels);
where use this 'channels' ?
Frank
> +
> + for (i = 0; i < nr_chans; i++) {
> + lchan = &lddev->chan[i];
> +
> + lchan->id = i;
> + lchan->vchan.desc_free = loongson2_cmc_dma_desc_free;
> + vchan_init(&lchan->vchan, ddev);
> + }
> +
> + ret = dmaenginem_async_device_register(ddev);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < nr_chans; i++) {
> + lchan = &lddev->chan[i];
> +
> + lchan->irq = platform_get_irq(pdev, i);
> + if (lchan->irq < 0)
> + return lchan->irq;
> +
> + ret = devm_request_irq(dev, lchan->irq, loongson2_cmc_dma_chan_irq, IRQF_SHARED,
> + dev_name(chan2dev(lchan)), lchan);
> + if (ret)
> + return ret;
> + }
> +
> + ret = loongson2_cmc_dma_acpi_controller_register(lddev);
> + if (ret)
> + return ret;
> +
> + return loongson2_cmc_dma_of_controller_register(lddev);
> +}
> +
> +static void loongson2_cmc_dma_remove(struct platform_device *pdev)
> +{
> + of_dma_controller_free(pdev->dev.of_node);
> +}
> +
> +static const struct of_device_id loongson2_cmc_dma_of_match[] = {
> + { .compatible = "loongson,ls2k0300-dma", .data = &ls2k0300_cmc_dma_config },
> + { .compatible = "loongson,ls2k3000-dma", .data = &ls2k3000_cmc_dma_config },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, loongson2_cmc_dma_of_match);
> +
> +static const struct acpi_device_id loongson2_cmc_dma_acpi_match[] = {
> + { "LOON0014", .driver_data = (kernel_ulong_t)&ls2k3000_cmc_dma_config },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(acpi, loongson2_cmc_dma_acpi_match);
> +
> +static struct platform_driver loongson2_cmc_dma_driver = {
> + .driver = {
> + .name = "loongson2-apb-cmc-dma",
> + .of_match_table = loongson2_cmc_dma_of_match,
> + .acpi_match_table = loongson2_cmc_dma_acpi_match,
> + },
> + .probe = loongson2_cmc_dma_probe,
> + .remove = loongson2_cmc_dma_remove,
> +};
> +module_platform_driver(loongson2_cmc_dma_driver);
> +
> +MODULE_DESCRIPTION("Looongson-2 Multi-Channel DMA Controller driver");
> +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> +MODULE_LICENSE("GPL");
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/4] dmaengine: loongson: New directory for Loongson DMA controllers drivers
2026-02-09 16:48 ` Frank Li
@ 2026-02-10 1:27 ` Binbin Zhou
2026-02-26 8:07 ` Huacai Chen
0 siblings, 1 reply; 16+ messages in thread
From: Binbin Zhou @ 2026-02-10 1:27 UTC (permalink / raw)
To: Frank Li
Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Vinod Koul, dmaengine, Xiaochuang Mao, Huacai Chen,
Xuerui Wang, loongarch, devicetree, Keguang Zhang, linux-mips,
jeffbai
Hi Frank:
On Tue, Feb 10, 2026 at 12:48 AM Frank Li <Frank.li@nxp.com> wrote:
>
> On Mon, Feb 09, 2026 at 11:04:18AM +0800, Binbin Zhou wrote:
> > Gather the Loongson DMA controllers under drivers/dma/loongson/
> >
> > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > ---
> > MAINTAINERS | 3 +-
> > drivers/dma/Kconfig | 25 ++---------------
> > drivers/dma/Makefile | 3 +-
> > drivers/dma/loongson/Kconfig | 28 +++++++++++++++++++
> > drivers/dma/loongson/Makefile | 3 ++
> > .../dma/{ => loongson}/loongson1-apb-dma.c | 4 +--
> > .../dma/{ => loongson}/loongson2-apb-dma.c | 4 +--
> > 7 files changed, 40 insertions(+), 30 deletions(-)
> > create mode 100644 drivers/dma/loongson/Kconfig
> > create mode 100644 drivers/dma/loongson/Makefile
> > rename drivers/dma/{ => loongson}/loongson1-apb-dma.c (99%)
> > rename drivers/dma/{ => loongson}/loongson2-apb-dma.c (99%)
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index f630328ca6ae..27f77b68d596 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -14777,7 +14777,7 @@ M: Binbin Zhou <zhoubinbin@loongson.cn>
> > L: dmaengine@vger.kernel.org
> > S: Maintained
> > F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
> > -F: drivers/dma/loongson2-apb-dma.c
> > +F: drivers/dma/loongson/loongson2-apb-dma.c
> >
> > LOONGSON LS2X I2C DRIVER
> > M: Binbin Zhou <zhoubinbin@loongson.cn>
> > @@ -17515,6 +17515,7 @@ F: arch/mips/boot/dts/loongson/loongson1*
> > F: arch/mips/configs/loongson1_defconfig
> > F: arch/mips/loongson32/
> > F: drivers/*/*loongson1*
> > +F: drivers/dma/loongson/loongson1-apb-dma.c
> > F: drivers/mtd/nand/raw/loongson-nand-controller.c
> > F: drivers/net/ethernet/stmicro/stmmac/dwmac-loongson1.c
> > F: sound/soc/loongson/loongson1_ac97.c
> > diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
> > index 66cda7cc9f7a..1b84c5b11654 100644
> > --- a/drivers/dma/Kconfig
> > +++ b/drivers/dma/Kconfig
> > @@ -376,29 +376,6 @@ config K3_DMA
> > Support the DMA engine for Hisilicon K3 platform
> > devices.
> >
> > -config LOONGSON1_APB_DMA
> > - tristate "Loongson1 APB DMA support"
> > - depends on MACH_LOONGSON32 || COMPILE_TEST
> > - select DMA_ENGINE
> > - select DMA_VIRTUAL_CHANNELS
> > - help
> > - This selects support for the APB DMA controller in Loongson1 SoCs,
> > - which is required by Loongson1 NAND and audio support.
> > -
> > -config LOONGSON2_APB_DMA
> > - tristate "Loongson2 APB DMA support"
> > - depends on LOONGARCH || COMPILE_TEST
> > - select DMA_ENGINE
> > - select DMA_VIRTUAL_CHANNELS
> > - help
> > - Support for the Loongson2 APB DMA controller driver. The
> > - DMA controller is having single DMA channel which can be
> > - configured for different peripherals like audio, nand, sdio
> > - etc which is in APB bus.
> > -
> > - This DMA controller transfers data from memory to peripheral fifo.
> > - It does not support memory to memory data transfer.
> > -
> > config LPC18XX_DMAMUX
> > bool "NXP LPC18xx/43xx DMA MUX for PL080"
> > depends on ARCH_LPC18XX || COMPILE_TEST
> > @@ -774,6 +751,8 @@ source "drivers/dma/fsl-dpaa2-qdma/Kconfig"
> >
> > source "drivers/dma/lgm/Kconfig"
> >
> > +source "drivers/dma/loongson/Kconfig"
> > +
> > source "drivers/dma/stm32/Kconfig"
> >
> > # clients
> > diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
> > index a54d7688392b..a1c73415b79f 100644
> > --- a/drivers/dma/Makefile
> > +++ b/drivers/dma/Makefile
> > @@ -49,8 +49,6 @@ obj-$(CONFIG_INTEL_IDMA64) += idma64.o
> > obj-$(CONFIG_INTEL_IOATDMA) += ioat/
> > obj-y += idxd/
> > obj-$(CONFIG_K3_DMA) += k3dma.o
> > -obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> > -obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> > obj-$(CONFIG_LPC18XX_DMAMUX) += lpc18xx-dmamux.o
> > obj-$(CONFIG_LPC32XX_DMAMUX) += lpc32xx-dmamux.o
> > obj-$(CONFIG_MILBEAUT_HDMAC) += milbeaut-hdmac.o
> > @@ -88,6 +86,7 @@ obj-$(CONFIG_INTEL_LDMA) += lgm/
> >
> > obj-y += amd/
> > obj-y += mediatek/
> > +obj-y += loongson/
>
> keep alphabet order
Sorry, I'll fix it in the next version.
>
> Frank
> > obj-y += qcom/
> > obj-y += stm32/
> > obj-y += ti/
> > diff --git a/drivers/dma/loongson/Kconfig b/drivers/dma/loongson/Kconfig
> > new file mode 100644
> > index 000000000000..9dbdaef5a59f
> > --- /dev/null
> > +++ b/drivers/dma/loongson/Kconfig
> > @@ -0,0 +1,28 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +#
> > +# Loongson DMA controllers drivers
> > +#
> > +if MACH_LOONGSON32 || MACH_LOONGSON64 || COMPILE_TEST
> > +
> > +config LOONGSON1_APB_DMA
> > + tristate "Loongson1 APB DMA support"
> > + select DMA_ENGINE
> > + select DMA_VIRTUAL_CHANNELS
> > + help
> > + This selects support for the APB DMA controller in Loongson1 SoCs,
> > + which is required by Loongson1 NAND and audio support.
> > +
> > +config LOONGSON2_APB_DMA
> > + tristate "Loongson2 APB DMA support"
> > + select DMA_ENGINE
> > + select DMA_VIRTUAL_CHANNELS
> > + help
> > + Support for the Loongson2 APB DMA controller driver. The
> > + DMA controller is having single DMA channel which can be
> > + configured for different peripherals like audio, nand, sdio
> > + etc which is in APB bus.
> > +
> > + This DMA controller transfers data from memory to peripheral fifo.
> > + It does not support memory to memory data transfer.
> > +
> > +endif
> > diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
> > new file mode 100644
> > index 000000000000..6cdd08065e92
> > --- /dev/null
> > +++ b/drivers/dma/loongson/Makefile
> > @@ -0,0 +1,3 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> > +obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> > diff --git a/drivers/dma/loongson1-apb-dma.c b/drivers/dma/loongson/loongson1-apb-dma.c
> > similarity index 99%
> > rename from drivers/dma/loongson1-apb-dma.c
> > rename to drivers/dma/loongson/loongson1-apb-dma.c
> > index 255fe7eca212..e99247cf90c1 100644
> > --- a/drivers/dma/loongson1-apb-dma.c
> > +++ b/drivers/dma/loongson/loongson1-apb-dma.c
> > @@ -16,8 +16,8 @@
> > #include <linux/platform_device.h>
> > #include <linux/slab.h>
> >
> > -#include "dmaengine.h"
> > -#include "virt-dma.h"
> > +#include "../dmaengine.h"
> > +#include "../virt-dma.h"
> >
> > /* Loongson-1 DMA Control Register */
> > #define LS1X_DMA_CTRL 0x0
> > diff --git a/drivers/dma/loongson2-apb-dma.c b/drivers/dma/loongson/loongson2-apb-dma.c
> > similarity index 99%
> > rename from drivers/dma/loongson2-apb-dma.c
> > rename to drivers/dma/loongson/loongson2-apb-dma.c
> > index c528f02b9f84..0cb607595d04 100644
> > --- a/drivers/dma/loongson2-apb-dma.c
> > +++ b/drivers/dma/loongson/loongson2-apb-dma.c
> > @@ -17,8 +17,8 @@
> > #include <linux/platform_device.h>
> > #include <linux/slab.h>
> >
> > -#include "dmaengine.h"
> > -#include "virt-dma.h"
> > +#include "../dmaengine.h"
> > +#include "../virt-dma.h"
> >
> > /* Global Configuration Register */
> > #define LDMA_ORDER_ERG 0x0
> > --
> > 2.52.0
> >
--
Thanks.
Binbin
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 3/4] dt-bindings: dmaengine: Add Loongson Multi-Channel DMA controller
2026-02-09 3:04 ` [PATCH v2 3/4] dt-bindings: dmaengine: Add Loongson Multi-Channel DMA controller Binbin Zhou
@ 2026-02-10 3:03 ` Rob Herring
2026-02-10 6:02 ` Binbin Zhou
0 siblings, 1 reply; 16+ messages in thread
From: Rob Herring @ 2026-02-10 3:03 UTC (permalink / raw)
To: Binbin Zhou
Cc: Binbin Zhou, Huacai Chen, Krzysztof Kozlowski, Conor Dooley,
Vinod Koul, dmaengine, Xiaochuang Mao, Huacai Chen, Xuerui Wang,
loongarch, devicetree, Keguang Zhang, linux-mips, jeffbai
On Mon, Feb 09, 2026 at 11:04:20AM +0800, Binbin Zhou wrote:
> The Loongson-2K0300/Loongson-2K3000 have built-in multi-channel DMA
> controllers, which are similar except for some of the register offsets
> and number of channels.
>
> Obviously, this is quite different from the APB DMA controller used in
> the Loongson-2K0500/Loongson-2K1000, such as the latter being a
> single-channel DMA controller.
>
> To avoid cluttering a single dt-binding file, add a new yaml file.
>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
> .../bindings/dma/loongson,ls2k0300-dma.yaml | 78 +++++++++++++++++++
> MAINTAINERS | 3 +-
> 2 files changed, 80 insertions(+), 1 deletion(-)
> create mode 100644 Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
>
> diff --git a/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml b/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
> new file mode 100644
> index 000000000000..77e5df47ec01
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
> @@ -0,0 +1,78 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/dma/loongson,ls2k0300-dma.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Looongson-2 Multi-Channel DMA controller
> +
> +description:
> + The Loongson-2 Multi-Channel DMA controller is used for transferring data
> + between system memory and the peripherals on the APB bus.
> +
> +maintainers:
> + - Binbin Zhou <zhoubinbin@loongson.cn>
> +
> +allOf:
> + - $ref: dma-controller.yaml#
> +
> +properties:
> + compatible:
> + enum:
> + - loongson,ls2k0300-dma
> + - loongson,ls2k3000-dma
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + minItems: 4
> + maxItems: 8
I'm assuming this is 1 interrupt per channel? If so, add a description
saying that.
> +
> + clocks:
> + maxItems: 1
> +
> + '#dma-cells':
> + const: 2
> + description: |
> + DMA request from clients consists of 2 cells:
> + 1. Channel index
> + 2. Transfer request factor number, If no transfer factor, use 0.
> + The number is SoC-specific, and this should be specified with
> + relation to the device to use the DMA controller.
> +
> + dma-channels:
> + enum: [4, 8]
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - clocks
> + - '#dma-cells'
> + - dma-channels
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/irq.h>
> + #include <dt-bindings/clock/loongson,ls2k-clk.h>
> +
> + dma-controller@1612c000 {
> + compatible = "loongson,ls2k0300-dma";
> + reg = <0x1612c000 0xff>;
> + interrupt-parent = <&liointc0>;
> + interrupts = <23 IRQ_TYPE_LEVEL_HIGH>,
> + <24 IRQ_TYPE_LEVEL_HIGH>,
> + <25 IRQ_TYPE_LEVEL_HIGH>,
> + <26 IRQ_TYPE_LEVEL_HIGH>,
> + <27 IRQ_TYPE_LEVEL_HIGH>,
> + <28 IRQ_TYPE_LEVEL_HIGH>,
> + <29 IRQ_TYPE_LEVEL_HIGH>,
> + <30 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&clk LS2K0300_CLK_APB_GATE>;
> + #dma-cells = <2>;
> + dma-channels = <8>;
> + };
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 27f77b68d596..d3cb541aee2a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14772,10 +14772,11 @@ S: Maintained
> F: Documentation/devicetree/bindings/gpio/loongson,ls-gpio.yaml
> F: drivers/gpio/gpio-loongson-64bit.c
>
> -LOONGSON-2 APB DMA DRIVER
> +LOONGSON-2 DMA DRIVER
> M: Binbin Zhou <zhoubinbin@loongson.cn>
> L: dmaengine@vger.kernel.org
> S: Maintained
> +F: Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
> F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
> F: drivers/dma/loongson/loongson2-apb-dma.c
>
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 3/4] dt-bindings: dmaengine: Add Loongson Multi-Channel DMA controller
2026-02-10 3:03 ` Rob Herring
@ 2026-02-10 6:02 ` Binbin Zhou
0 siblings, 0 replies; 16+ messages in thread
From: Binbin Zhou @ 2026-02-10 6:02 UTC (permalink / raw)
To: Rob Herring
Cc: Binbin Zhou, Huacai Chen, Krzysztof Kozlowski, Conor Dooley,
Vinod Koul, dmaengine, Xiaochuang Mao, Huacai Chen, Xuerui Wang,
loongarch, devicetree, Keguang Zhang, linux-mips, jeffbai
Hi Rob:
Thanks for your reply.
On Tue, Feb 10, 2026 at 11:03 AM Rob Herring <robh@kernel.org> wrote:
>
> On Mon, Feb 09, 2026 at 11:04:20AM +0800, Binbin Zhou wrote:
> > The Loongson-2K0300/Loongson-2K3000 have built-in multi-channel DMA
> > controllers, which are similar except for some of the register offsets
> > and number of channels.
> >
> > Obviously, this is quite different from the APB DMA controller used in
> > the Loongson-2K0500/Loongson-2K1000, such as the latter being a
> > single-channel DMA controller.
> >
> > To avoid cluttering a single dt-binding file, add a new yaml file.
> >
> > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > ---
> > .../bindings/dma/loongson,ls2k0300-dma.yaml | 78 +++++++++++++++++++
> > MAINTAINERS | 3 +-
> > 2 files changed, 80 insertions(+), 1 deletion(-)
> > create mode 100644 Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
> >
> > diff --git a/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml b/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
> > new file mode 100644
> > index 000000000000..77e5df47ec01
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
> > @@ -0,0 +1,78 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/dma/loongson,ls2k0300-dma.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Looongson-2 Multi-Channel DMA controller
> > +
> > +description:
> > + The Loongson-2 Multi-Channel DMA controller is used for transferring data
> > + between system memory and the peripherals on the APB bus.
> > +
> > +maintainers:
> > + - Binbin Zhou <zhoubinbin@loongson.cn>
> > +
> > +allOf:
> > + - $ref: dma-controller.yaml#
> > +
> > +properties:
> > + compatible:
> > + enum:
> > + - loongson,ls2k0300-dma
> > + - loongson,ls2k3000-dma
> > +
> > + reg:
> > + maxItems: 1
> > +
> > + interrupts:
> > + minItems: 4
> > + maxItems: 8
>
> I'm assuming this is 1 interrupt per channel? If so, add a description
> saying that.
Yes. this part will be rewritten as:
interrupts:
description:
Should contain all of the per-channel DMA interrupts in ascending order
with respect to the DMA channel index.
minItems: 4
maxItems: 8
>
> > +
> > + clocks:
> > + maxItems: 1
> > +
> > + '#dma-cells':
> > + const: 2
> > + description: |
> > + DMA request from clients consists of 2 cells:
> > + 1. Channel index
> > + 2. Transfer request factor number, If no transfer factor, use 0.
> > + The number is SoC-specific, and this should be specified with
> > + relation to the device to use the DMA controller.
> > +
> > + dma-channels:
> > + enum: [4, 8]
> > +
> > +required:
> > + - compatible
> > + - reg
> > + - interrupts
> > + - clocks
> > + - '#dma-cells'
> > + - dma-channels
> > +
> > +unevaluatedProperties: false
> > +
> > +examples:
> > + - |
> > + #include <dt-bindings/interrupt-controller/irq.h>
> > + #include <dt-bindings/clock/loongson,ls2k-clk.h>
> > +
> > + dma-controller@1612c000 {
> > + compatible = "loongson,ls2k0300-dma";
> > + reg = <0x1612c000 0xff>;
> > + interrupt-parent = <&liointc0>;
> > + interrupts = <23 IRQ_TYPE_LEVEL_HIGH>,
> > + <24 IRQ_TYPE_LEVEL_HIGH>,
> > + <25 IRQ_TYPE_LEVEL_HIGH>,
> > + <26 IRQ_TYPE_LEVEL_HIGH>,
> > + <27 IRQ_TYPE_LEVEL_HIGH>,
> > + <28 IRQ_TYPE_LEVEL_HIGH>,
> > + <29 IRQ_TYPE_LEVEL_HIGH>,
> > + <30 IRQ_TYPE_LEVEL_HIGH>;
> > + clocks = <&clk LS2K0300_CLK_APB_GATE>;
> > + #dma-cells = <2>;
> > + dma-channels = <8>;
> > + };
> > +...
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 27f77b68d596..d3cb541aee2a 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -14772,10 +14772,11 @@ S: Maintained
> > F: Documentation/devicetree/bindings/gpio/loongson,ls-gpio.yaml
> > F: drivers/gpio/gpio-loongson-64bit.c
> >
> > -LOONGSON-2 APB DMA DRIVER
> > +LOONGSON-2 DMA DRIVER
> > M: Binbin Zhou <zhoubinbin@loongson.cn>
> > L: dmaengine@vger.kernel.org
> > S: Maintained
> > +F: Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
> > F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
> > F: drivers/dma/loongson/loongson2-apb-dma.c
> >
> > --
> > 2.52.0
> >
--
Thanks.
Binbin
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/4] dmaengine: loongson: New driver for the Loongson Multi-Channel DMA controller
2026-02-09 17:04 ` Frank Li
@ 2026-02-10 7:41 ` Binbin Zhou
2026-02-10 12:02 ` Binbin Zhou
0 siblings, 1 reply; 16+ messages in thread
From: Binbin Zhou @ 2026-02-10 7:41 UTC (permalink / raw)
To: Frank Li
Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Vinod Koul, dmaengine, Xiaochuang Mao, Huacai Chen,
Xuerui Wang, loongarch, devicetree, Keguang Zhang, linux-mips,
jeffbai
Hi Frank:
Thanks for your reply.
On Tue, Feb 10, 2026 at 1:05 AM Frank Li <Frank.li@nxp.com> wrote:
>
> On Mon, Feb 09, 2026 at 11:04:55AM +0800, Binbin Zhou wrote:
> > This DMA controller appears in Loongson-2K0300 and Loongson-2K3000.
> >
> > It is a chain multi-channel controller that enables data transfers from
> > memory to memory, device to memory, and memory to device, as well as
> > channel prioritization configurable through the channel configuration
> > registers.
> >
> > In addition, there are slight differences between Loongson-2K0300 and
> > Loongson-2K3000, such as channel register offsets and the number of
> > channels.
> >
> > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > ---
> > MAINTAINERS | 1 +
> > drivers/dma/loongson/Kconfig | 10 +
> > drivers/dma/loongson/Makefile | 1 +
> > drivers/dma/loongson/loongson2-apb-cmc-dma.c | 736 +++++++++++++++++++
> > 4 files changed, 748 insertions(+)
> > create mode 100644 drivers/dma/loongson/loongson2-apb-cmc-dma.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index d3cb541aee2a..61a39070d7a0 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -14778,6 +14778,7 @@ L: dmaengine@vger.kernel.org
> > S: Maintained
> > F: Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
> > F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
> > +F: drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > F: drivers/dma/loongson/loongson2-apb-dma.c
> >
> > LOONGSON LS2X I2C DRIVER
> > diff --git a/drivers/dma/loongson/Kconfig b/drivers/dma/loongson/Kconfig
> > index 9dbdaef5a59f..28b3daeed4e3 100644
> > --- a/drivers/dma/loongson/Kconfig
> > +++ b/drivers/dma/loongson/Kconfig
> > @@ -25,4 +25,14 @@ config LOONGSON2_APB_DMA
> > This DMA controller transfers data from memory to peripheral fifo.
> > It does not support memory to memory data transfer.
> >
> > +config LOONGSON2_APB_CMC_DMA
> > + tristate "Loongson2 Chain Multi-Channel DMA support"
> > + select DMA_ENGINE
> > + select DMA_VIRTUAL_CHANNELS
> > + help
> > + Support for the Loongson Chain Multi-Channel DMA controller driver.
> > + It is discovered on the Loongson-2K chip (Loongson-2K0300/Loongson-2K3000),
> > + which has 4/8 channels internally, enabling bidirectional data transfer
> > + between devices and memory.
> > +
> > endif
> > diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
> > index 6cdd08065e92..48c19781e729 100644
> > --- a/drivers/dma/loongson/Makefile
> > +++ b/drivers/dma/loongson/Makefile
> > @@ -1,3 +1,4 @@
> > # SPDX-License-Identifier: GPL-2.0-only
> > obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> > obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> > +obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
> > diff --git a/drivers/dma/loongson/loongson2-apb-cmc-dma.c b/drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > new file mode 100644
> > index 000000000000..f598ad095686
> > --- /dev/null
> > +++ b/drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > @@ -0,0 +1,736 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Looongson-2 Multi-Channel DMA Controller driver
> > + *
> > + * Copyright (C) 2024-2026 Loongson Technology Corporation Limited
> > + */
> > +
> > +#include <linux/acpi.h>
> > +#include <linux/acpi_dma.h>
> > +#include <linux/bitfield.h>
> > +#include <linux/clk.h>
> > +#include <linux/dma-mapping.h>
> > +#include <linux/dmapool.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/io.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/of_dma.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/slab.h>
> > +
> > +#include "../dmaengine.h"
> > +#include "../virt-dma.h"
> > +
> > +#define LOONGSON2_CMCDMA_ISR 0x0 /* DMA Interrupt Status Register */
> > +#define LOONGSON2_CMCDMA_IFCR 0x4 /* DMA Interrupt Flag Clear Register */
> > +#define LOONGSON2_CMCDMA_CCR 0x8 /* DMA Channel Configuration Register */
> > +#define LOONGSON2_CMCDMA_CNDTR 0xc /* DMA Channel Transmit Count Register */
> > +#define LOONGSON2_CMCDMA_CPAR 0x10 /* DMA Channel Peripheral Address Register */
> > +#define LOONGSON2_CMCDMA_CMAR 0x14 /* DMA Channel Memory Address Register */
> > +
> > +/* Bitfields of DMA interrupt status register */
> > +#define LOONGSON2_CMCDMA_TCI BIT(1) /* Transfer Complete Interrupt */
> > +#define LOONGSON2_CMCDMA_HTI BIT(2) /* Half Transfer Interrupt */
> > +#define LOONGSON2_CMCDMA_TEI BIT(3) /* Transfer Error Interrupt */
> > +
> > +#define LOONGSON2_CMCDMA_MASKI \
> > + (LOONGSON2_CMCDMA_TCI | LOONGSON2_CMCDMA_HTI | LOONGSON2_CMCDMA_TEI)
> > +
> > +/* Bitfields of DMA channel x Configuration Register */
> > +#define LOONGSON2_CMCDMA_CCR_EN BIT(0) /* Stream Enable */
> > +#define LOONGSON2_CMCDMA_CCR_TCIE BIT(1) /* Transfer Complete Interrupt Enable */
> > +#define LOONGSON2_CMCDMA_CCR_HTIE BIT(2) /* Half Transfer Complete Interrupt Enable */
> > +#define LOONGSON2_CMCDMA_CCR_TEIE BIT(3) /* Transfer Error Interrupt Enable */
> > +#define LOONGSON2_CMCDMA_CCR_DIR BIT(4) /* Data Transfer Direction */
> > +#define LOONGSON2_CMCDMA_CCR_CIRC BIT(5) /* Circular mode */
> > +#define LOONGSON2_CMCDMA_CCR_PINC BIT(6) /* Peripheral increment mode */
> > +#define LOONGSON2_CMCDMA_CCR_MINC BIT(7) /* Memory increment mode */
> > +#define LOONGSON2_CMCDMA_CCR_PSIZE_MASK GENMASK(9, 8)
> > +#define LOONGSON2_CMCDMA_CCR_MSIZE_MASK GENMASK(11, 10)
> > +#define LOONGSON2_CMCDMA_CCR_PL_MASK GENMASK(13, 12)
> > +#define LOONGSON2_CMCDMA_CCR_M2M BIT(14)
> > +
> > +#define LOONGSON2_CMCDMA_CCR_CFG_MASK \
> > + (LOONGSON2_CMCDMA_CCR_PINC | LOONGSON2_CMCDMA_CCR_MINC | LOONGSON2_CMCDMA_CCR_PL_MASK)
> > +
> > +#define LOONGSON2_CMCDMA_CCR_IRQ_MASK \
> > + (LOONGSON2_CMCDMA_CCR_TCIE | LOONGSON2_CMCDMA_CCR_HTIE | LOONGSON2_CMCDMA_CCR_TEIE)
> > +
> > +#define LOONGSON2_CMCDMA_STREAM_MASK \
> > + (LOONGSON2_CMCDMA_CCR_CFG_MASK | LOONGSON2_CMCDMA_CCR_IRQ_MASK)
> > +
> > +#define LOONGSON2_CMCDMA_BUSWIDTHS (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \
> > + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \
> > + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES))
> > +
> > +enum loongson2_cmc_dma_width {
> > + LOONGSON2_CMCDMA_BYTE,
> > + LOONGSON2_CMCDMA_HALF_WORD,
> > + LOONGSON2_CMCDMA_WORD,
> > +};
> > +
> > +struct loongson2_cmc_dma_chan_reg {
> > + u32 ccr;
> > + u32 cndtr;
> > + u32 cpar;
> > + u32 cmar;
> > +};
> > +
> > +struct loongson2_cmc_dma_sg_req {
> > + u32 len;
> > + struct loongson2_cmc_dma_chan_reg chan_reg;
> > +};
> > +
> > +struct loongson2_cmc_dma_desc {
> > + struct virt_dma_desc vdesc;
> > + bool cyclic;
> > + u32 num_sgs;
> > + struct loongson2_cmc_dma_sg_req sg_req[] __counted_by(num_sgs);
> > +};
> > +
> > +struct loongson2_cmc_dma_chan {
> > + struct virt_dma_chan vchan;
> > + struct dma_slave_config dma_sconfig;
> > + struct loongson2_cmc_dma_desc *desc;
> > + u32 id;
> > + u32 irq;
> > + u32 next_sg;
> > + struct loongson2_cmc_dma_chan_reg chan_reg;
> > +};
> > +
> > +struct loongson2_cmc_dma_config {
> > + u32 max_channels;
> > + u32 chan_reg_offset;
> > +};
> > +
> > +struct loongson2_cmc_dma_dev {
> > + struct dma_device ddev;
> > + struct clk *dma_clk;
> > + void __iomem *base;
> > + u32 nr_channels;
> > + u32 chan_reg_offset;
> > + struct loongson2_cmc_dma_chan chan[] __counted_by(nr_channels);
> > +};
> > +
> > +static const struct loongson2_cmc_dma_config ls2k0300_cmc_dma_config = {
> > + .max_channels = 8,
> > + .chan_reg_offset = 0x14,
> > +};
> > +
> > +static const struct loongson2_cmc_dma_config ls2k3000_cmc_dma_config = {
> > + .max_channels = 4,
> > + .chan_reg_offset = 0x18,
> > +};
> > +
> > +static struct loongson2_cmc_dma_dev *lmdma_get_dev(struct loongson2_cmc_dma_chan *lchan)
> > +{
> > + return container_of(lchan->vchan.chan.device, struct loongson2_cmc_dma_dev, ddev);
> > +}
> > +
> > +static struct loongson2_cmc_dma_chan *to_lmdma_chan(struct dma_chan *chan)
> > +{
> > + return container_of(chan, struct loongson2_cmc_dma_chan, vchan.chan);
> > +}
> > +
> > +static struct loongson2_cmc_dma_desc *to_lmdma_desc(struct virt_dma_desc *vdesc)
> > +{
> > + return container_of(vdesc, struct loongson2_cmc_dma_desc, vdesc);
> > +}
> > +
> > +static struct device *chan2dev(struct loongson2_cmc_dma_chan *lchan)
> > +{
> > + return &lchan->vchan.chan.dev->device;
> > +}
> > +
> > +static u32 loongson2_cmc_dma_read(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id)
> > +{
> > + return readl(lddev->base + (reg + lddev->chan_reg_offset * id));
> > +}
> > +
> > +static void loongson2_cmc_dma_write(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id, u32 val)
> > +{
> > + writel(val, lddev->base + (reg + lddev->chan_reg_offset * id));
> > +}
> > +
> > +static int loongson2_cmc_dma_get_width(struct loongson2_cmc_dma_chan *lchan,
> > + enum dma_slave_buswidth width)
> > +{
> > + switch (width) {
> > + case DMA_SLAVE_BUSWIDTH_1_BYTE:
> > + return LOONGSON2_CMCDMA_BYTE;
> > + case DMA_SLAVE_BUSWIDTH_2_BYTES:
> > + return LOONGSON2_CMCDMA_HALF_WORD;
> > + case DMA_SLAVE_BUSWIDTH_4_BYTES:
> > + return LOONGSON2_CMCDMA_WORD;
>
> is ffs() helper in case your hardware support more buswidth in future?
It seems there's no need for us to do this.
The data width setting bit in the DMA channel configuration register
only has two bits (LOONGSON2_CMCDMA_CCR_PSIZE_MASK). The bitmask
values are: 8-bit/16-bit/32-bit/reserved.
>
> > + default:
> > + dev_err(chan2dev(lchan), "Dma bus width not supported\n");
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int loongson2_cmc_dma_slave_config(struct dma_chan *chan, struct dma_slave_config *config)
> > +{
> > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > +
> > + memcpy(&lchan->dma_sconfig, config, sizeof(*config));
> > +
> > + return 0;
> > +}
> > +
> > +static void loongson2_cmc_dma_irq_clear(struct loongson2_cmc_dma_chan *lchan, u32 flags)
> > +{
> > + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > + u32 ifcr;
> > +
> > + ifcr = flags << (4 * lchan->id);
> > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_IFCR, 0, ifcr);
> > +}
> > +
> > +static void loongson2_cmc_dma_stop(struct loongson2_cmc_dma_chan *lchan)
> > +{
> > + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > + u32 ccr;
> > +
> > + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
> > + ccr &= ~(LOONGSON2_CMCDMA_CCR_IRQ_MASK | LOONGSON2_CMCDMA_CCR_EN);
> > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, ccr);
> > +
> > + loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_MASKI);
> > +}
> > +
> > +static int loongson2_cmc_dma_terminate_all(struct dma_chan *chan)
> > +{
> > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > + unsigned long flags;
> > +
> > + LIST_HEAD(head);
> > +
> > + spin_lock_irqsave(&lchan->vchan.lock, flags);
> > + if (lchan->desc) {
> > + vchan_terminate_vdesc(&lchan->desc->vdesc);
> > + loongson2_cmc_dma_stop(lchan);
> > + lchan->desc = NULL;
> > + }
> > + vchan_get_all_descriptors(&lchan->vchan, &head);
> > + spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> > +
> > + vchan_dma_desc_free_list(&lchan->vchan, &head);
> > +
> > + return 0;
> > +}
> > +
> > +static void loongson2_cmc_dma_synchronize(struct dma_chan *chan)
> > +{
> > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > +
> > + vchan_synchronize(&lchan->vchan);
> > +}
> > +
> > +static void loongson2_cmc_dma_start_transfer(struct loongson2_cmc_dma_chan *lchan)
> > +{
> > + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > + struct loongson2_cmc_dma_sg_req *sg_req;
> > + struct loongson2_cmc_dma_chan_reg *reg;
> > + struct virt_dma_desc *vdesc;
> > +
> > + loongson2_cmc_dma_stop(lchan);
> > +
> > + if (!lchan->desc) {
> > + vdesc = vchan_next_desc(&lchan->vchan);
> > + if (!vdesc)
> > + return;
> > +
> > + list_del(&vdesc->node);
> > + lchan->desc = to_lmdma_desc(vdesc);
> > + lchan->next_sg = 0;
> > + }
> > +
> > + if (lchan->next_sg == lchan->desc->num_sgs)
> > + lchan->next_sg = 0;
> > +
> > + sg_req = &lchan->desc->sg_req[lchan->next_sg];
> > + reg = &sg_req->chan_reg;
> > +
> > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, reg->ccr);
> > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CNDTR, lchan->id, reg->cndtr);
> > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CPAR, lchan->id, reg->cpar);
> > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CMAR, lchan->id, reg->cmar);
> > +
> > + lchan->next_sg++;
> > +
> > + /* Start DMA */
> > + reg->ccr |= LOONGSON2_CMCDMA_CCR_EN;
> > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, reg->ccr);
> > +}
> > +
> > +static void loongson2_cmc_dma_configure_next_sg(struct loongson2_cmc_dma_chan *lchan)
> > +{
> > + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > + struct loongson2_cmc_dma_sg_req *sg_req;
> > + u32 ccr, id = lchan->id;
> > +
> > + if (lchan->next_sg == lchan->desc->num_sgs)
> > + lchan->next_sg = 0;
> > +
> > + /* stop to update mem addr */
> > + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, id);
> > + ccr &= ~LOONGSON2_CMCDMA_CCR_EN;
> > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, id, ccr);
> > +
> > + sg_req = &lchan->desc->sg_req[lchan->next_sg];
> > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CMAR, id, sg_req->chan_reg.cmar);
> > +
> > + /* start transition */
> > + ccr |= LOONGSON2_CMCDMA_CCR_EN;
> > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, id, ccr);
> > +}
> > +
> > +static void loongson2_cmc_dma_handle_chan_done(struct loongson2_cmc_dma_chan *lchan)
> > +{
> > + if (!lchan->desc)
> > + return;
> > +
> > + if (lchan->desc->cyclic) {
> > + vchan_cyclic_callback(&lchan->desc->vdesc);
> > + /* LOONGSON2_CMCDMA_CCR_CIRC mode don't need update register */
> > + if (lchan->desc->num_sgs == 1)
> > + return;
> > + loongson2_cmc_dma_configure_next_sg(lchan);
> > + lchan->next_sg++;
> > + } else {
> > + if (lchan->next_sg == lchan->desc->num_sgs) {
> > + vchan_cookie_complete(&lchan->desc->vdesc);
> > + lchan->desc = NULL;
> > + }
> > + loongson2_cmc_dma_start_transfer(lchan);
> > + }
> > +}
> > +
> > +static irqreturn_t loongson2_cmc_dma_chan_irq(int irq, void *devid)
> > +{
> > + struct loongson2_cmc_dma_chan *lchan = devid;
> > + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > + u32 ists, status, scr;
> > +
> > + spin_lock(&lchan->vchan.lock);
> > +
> > + ists = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_ISR, 0);
> > + scr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
> > +
> > + status = (ists >> (4 * lchan->id)) & LOONGSON2_CMCDMA_MASKI;
> > + status &= scr;
> > +
> > + if (status & LOONGSON2_CMCDMA_TCI)
> > + loongson2_cmc_dma_handle_chan_done(lchan);
> > +
> > + if (status & LOONGSON2_CMCDMA_HTI)
> > + loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
> > +
> > + if (status & LOONGSON2_CMCDMA_TEI)
> > + dev_err(chan2dev(lchan), "DMA Transform Error\n");
> > +
> > + loongson2_cmc_dma_irq_clear(lchan, status);
>
> irq clear should before loongson2_cmc_dma_handle_chan_done() incase you
> missed irq, if loongson2_cmc_dma_handle_chan_done() trigger new irq before
> your call irq_cler().
>
> > +
> > + spin_unlock(&lchan->vchan.lock);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static void loongson2_cmc_dma_issue_pending(struct dma_chan *chan)
> > +{
> > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > + unsigned long flags;
> > +
> > + spin_lock_irqsave(&lchan->vchan.lock, flags);
> > + if (vchan_issue_pending(&lchan->vchan) && !lchan->desc) {
> > + dev_dbg(chan2dev(lchan), "vchan %pK: issued\n", &lchan->vchan);
> > + loongson2_cmc_dma_start_transfer(lchan);
> > + }
> > + spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> > +}
> > +
> > +static int loongson2_cmc_dma_set_xfer_param(struct loongson2_cmc_dma_chan *lchan,
> > + enum dma_transfer_direction direction,
> > + enum dma_slave_buswidth *buswidth, u32 buf_len)
> > +{
> > + struct dma_slave_config sconfig = lchan->dma_sconfig;
> > + int dev_width;
> > + u32 ccr;
> > +
> > + switch (direction) {
> > + case DMA_MEM_TO_DEV:
> > + dev_width = loongson2_cmc_dma_get_width(lchan, sconfig.dst_addr_width);
> > + if (dev_width < 0)
> > + return dev_width;
> > + lchan->chan_reg.cpar = sconfig.dst_addr;
> > + ccr = LOONGSON2_CMCDMA_CCR_DIR;
> > + *buswidth = sconfig.dst_addr_width;
> > + break;
> > + case DMA_DEV_TO_MEM:
> > + dev_width = loongson2_cmc_dma_get_width(lchan, sconfig.src_addr_width);
> > + if (dev_width < 0)
> > + return dev_width;
> > + lchan->chan_reg.cpar = sconfig.src_addr;
> > + ccr = LOONGSON2_CMCDMA_CCR_MINC;
> > + *buswidth = sconfig.src_addr_width;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + ccr |= FIELD_PREP(LOONGSON2_CMCDMA_CCR_PSIZE_MASK, dev_width) |
> > + FIELD_PREP(LOONGSON2_CMCDMA_CCR_MSIZE_MASK, dev_width);
> > +
> > + /* Set DMA control register */
> > + lchan->chan_reg.ccr &= ~(LOONGSON2_CMCDMA_CCR_PSIZE_MASK | LOONGSON2_CMCDMA_CCR_MSIZE_MASK);
> > + lchan->chan_reg.ccr |= ccr;
> > +
> > + return 0;
> > +}
> > +
> > +static struct dma_async_tx_descriptor *
> > +loongson2_cmc_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, u32 sg_len,
> > + enum dma_transfer_direction direction,
> > + unsigned long flags, void *context)
> > +{
> > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > + struct loongson2_cmc_dma_desc *desc;
> > + enum dma_slave_buswidth buswidth;
> > + struct scatterlist *sg;
> > + u32 num_items, i;
> > + int ret;
> > +
> > + desc = kzalloc(struct_size(desc, sg_req, sg_len), GFP_NOWAIT);
> > + if (!desc)
> > + return NULL;
> > +
> > + for_each_sg(sgl, sg, sg_len, i) {
> > + ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, sg_dma_len(sg));
> > + if (ret)
> > + return NULL;
> > +
> > + desc->sg_req[i].len = sg_dma_len(sg);
> > +
> > + num_items = desc->sg_req[i].len / buswidth;
> > + if (num_items >= SZ_64K) {
> > + dev_err(chan2dev(lchan), "Number of items not supported\n");
> > + kfree(desc);
> > + return NULL;
>
> if use sg_nents_for_dma(), you can use multi sg to trasfer more than 64K
> data.
Sorry, are you referring to sg_nents_for_len()?
64K is a hardware limitation of the controller, so it seems impossible
to resolve it using that function, right?
>
> > + }
> > + desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr;
> > + desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar;
> > + desc->sg_req[i].chan_reg.cmar = sg_dma_address(sg);
> > + desc->sg_req[i].chan_reg.cndtr = num_items;
> > + }
> > +
> > + desc->num_sgs = sg_len;
> > + desc->cyclic = false;
> > +
> > + return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags);
> > +}
> > +
> > +static struct dma_async_tx_descriptor *
> > +loongson2_cmc_dma_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
> > + size_t period_len, enum dma_transfer_direction direction,
> > + unsigned long flags)
> > +{
> > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > + struct loongson2_cmc_dma_desc *desc;
> > + enum dma_slave_buswidth buswidth;
> > + u32 num_periods, num_items, i;
> > + int ret;
> > +
> > + if (unlikely(buf_len % period_len))
> > + return NULL;
> > +
> > + ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, period_len);
> > + if (ret)
> > + return NULL;
> > +
> > + num_items = period_len / buswidth;
> > + if (num_items >= SZ_64K) {
> > + dev_err(chan2dev(lchan), "Number of items not supported\n");
> > + return NULL;
> > + }
> > +
> > + /* Enable Circular mode */
> > + if (buf_len == period_len)
> > + lchan->chan_reg.ccr |= LOONGSON2_CMCDMA_CCR_CIRC;
> > +
> > + num_periods = buf_len / period_len;
> > + desc = kzalloc(struct_size(desc, sg_req, num_periods), GFP_NOWAIT);
> > + if (!desc)
> > + return NULL;
> > +
> > + for (i = 0; i < num_periods; i++) {
> > + desc->sg_req[i].len = period_len;
> > + desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr;
> > + desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar;
> > + desc->sg_req[i].chan_reg.cmar = buf_addr;
> > + desc->sg_req[i].chan_reg.cndtr = num_items;
> > + buf_addr += period_len;
> > + }
> > +
> > + desc->num_sgs = num_periods;
> > + desc->cyclic = true;
> > +
> > + return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags);
> > +}
> > +
> > +static size_t loongson2_cmc_dma_desc_residue(struct loongson2_cmc_dma_chan *lchan,
> > + struct loongson2_cmc_dma_desc *desc, u32 next_sg)
> > +{
> > + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > + u32 residue, width, ndtr, ccr, i;
> > +
> > + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
> > + width = FIELD_GET(LOONGSON2_CMCDMA_CCR_PSIZE_MASK, ccr);
> > +
> > + ndtr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CNDTR, lchan->id);
> > + residue = ndtr << width;
> > +
> > + if (lchan->desc->cyclic && next_sg == 0)
> > + return residue;
> > +
> > + for (i = next_sg; i < desc->num_sgs; i++)
> > + residue += desc->sg_req[i].len;
> > +
> > + return residue;
> > +}
> > +
> > +static enum dma_status loongson2_cmc_dma_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
> > + struct dma_tx_state *state)
> > +{
> > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > + struct virt_dma_desc *vdesc;
> > + enum dma_status status;
> > + unsigned long flags;
> > +
> > + status = dma_cookie_status(chan, cookie, state);
> > + if (status == DMA_COMPLETE || !state)
> > + return status;
> > +
> > + spin_lock_irqsave(&lchan->vchan.lock, flags);
> > + vdesc = vchan_find_desc(&lchan->vchan, cookie);
> > + if (lchan->desc && cookie == lchan->desc->vdesc.tx.cookie)
> > + state->residue = loongson2_cmc_dma_desc_residue(lchan, lchan->desc, lchan->next_sg);
> > + else if (vdesc)
> > + state->residue = loongson2_cmc_dma_desc_residue(lchan, to_lmdma_desc(vdesc), 0);
> > +
> > + spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> > +
> > + return status;
> > +}
> > +
> > +static void loongson2_cmc_dma_free_chan_resources(struct dma_chan *chan)
> > +{
> > + vchan_free_chan_resources(to_virt_chan(chan));
> > +}
> > +
> > +static void loongson2_cmc_dma_desc_free(struct virt_dma_desc *vdesc)
> > +{
> > + kfree(to_lmdma_desc(vdesc));
> > +}
> > +
> > +static bool loongson2_cmc_dma_acpi_filter(struct dma_chan *chan, void *param)
> > +{
> > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > + struct acpi_dma_spec *dma_spec = param;
> > +
> > + memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg));
> > + lchan->chan_reg.ccr = dma_spec->chan_id & LOONGSON2_CMCDMA_STREAM_MASK;
> > +
> > + return true;
> > +}
> > +
> > +static int loongson2_cmc_dma_acpi_controller_register(struct loongson2_cmc_dma_dev *lddev)
> > +{
> > + struct device *dev = lddev->ddev.dev;
> > + struct acpi_dma_filter_info *info;
> > + int ret;
> > +
> > + if (!has_acpi_companion(dev))
> > + return 0;
> > +
> > + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
> > + if (!info)
> > + return -ENOMEM;
> > +
> > + dma_cap_zero(info->dma_cap);
> > + info->dma_cap = lddev->ddev.cap_mask;
> > + info->filter_fn = loongson2_cmc_dma_acpi_filter;
> > +
> > + ret = devm_acpi_dma_controller_register(dev, acpi_dma_simple_xlate, info);
> > + if (ret)
> > + dev_err(dev, "could not register acpi_dma_controller\n");
> > +
> > + return ret;
> > +}
> > +
> > +static struct dma_chan *loongson2_cmc_dma_of_xlate(struct of_phandle_args *dma_spec,
> > + struct of_dma *ofdma)
> > +{
> > + struct loongson2_cmc_dma_dev *lddev = ofdma->of_dma_data;
> > + struct device *dev = lddev->ddev.dev;
> > + struct loongson2_cmc_dma_chan *lchan;
> > + struct dma_chan *chan;
> > +
> > + if (dma_spec->args_count < 2)
> > + return NULL;
> > +
> > + if (dma_spec->args[0] >= lddev->nr_channels) {
> > + dev_err(dev, "Invalid channel id\n");
> > + return NULL;
> > + }
> > +
> > + lchan = &lddev->chan[dma_spec->args[0]];
> > + chan = dma_get_slave_channel(&lchan->vchan.chan);
> > + if (!chan) {
> > + dev_err(dev, "No more channels available\n");
> > + return NULL;
> > + }
> > +
> > + memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg));
> > + lchan->chan_reg.ccr = dma_spec->args[1] & LOONGSON2_CMCDMA_STREAM_MASK;
> > +
> > + return chan;
> > +}
> > +
> > +static int loongson2_cmc_dma_of_controller_register(struct loongson2_cmc_dma_dev *lddev)
> > +{
> > + struct device *dev = lddev->ddev.dev;
> > + int ret;
> > +
> > + if (!dev->of_node)
> > + return 0;
> > +
> > + ret = of_dma_controller_register(dev->of_node, loongson2_cmc_dma_of_xlate, lddev);
> > + if (ret)
> > + dev_err(dev, "could not register of_dma_controller\n");
> > +
> > + return ret;
> > +}
> > +
> > +static int loongson2_cmc_dma_probe(struct platform_device *pdev)
> > +{
> > + const struct loongson2_cmc_dma_config *config;
> > + struct loongson2_cmc_dma_chan *lchan;
> > + struct loongson2_cmc_dma_dev *lddev;
> > + struct device *dev = &pdev->dev;
> > + struct dma_device *ddev;
> > + u32 nr_chans, i;
> > + int ret;
> > +
> > + config = (const struct loongson2_cmc_dma_config *)device_get_match_data(dev);
> > + if (!config)
> > + return -EINVAL;
> > +
> > + ret = device_property_read_u32(dev, "dma-channels", &nr_chans);
> > + if (ret || nr_chans > config->max_channels) {
> > + dev_err(dev, "missing or invalid dma-channels property\n");
> > + nr_chans = config->max_channels;
> > + }
> > +
> > + lddev = devm_kzalloc(dev, struct_size(lddev, chan, nr_chans), GFP_KERNEL);
> > + if (!lddev)
> > + return -ENOMEM;
> > +
> > + lddev->base = devm_platform_ioremap_resource(pdev, 0);
> > + if (IS_ERR(lddev->base))
> > + return PTR_ERR(lddev->base);
> > +
> > + platform_set_drvdata(pdev, lddev);
> > + lddev->nr_channels = nr_chans;
> > + lddev->chan_reg_offset = config->chan_reg_offset;
> > +
> > + lddev->dma_clk = devm_clk_get_optional_enabled(dev, NULL);
> > + if (IS_ERR(lddev->dma_clk))
> > + return dev_err_probe(dev, PTR_ERR(lddev->dma_clk), "Failed to get dma clock\n");
> > +
> > + ddev = &lddev->ddev;
> > + ddev->dev = dev;
> > +
> > + dma_cap_zero(ddev->cap_mask);
> > + dma_cap_set(DMA_SLAVE, ddev->cap_mask);
> > + dma_cap_set(DMA_PRIVATE, ddev->cap_mask);
> > + dma_cap_set(DMA_CYCLIC, ddev->cap_mask);
> > +
> > + ddev->device_free_chan_resources = loongson2_cmc_dma_free_chan_resources;
> > + ddev->device_config = loongson2_cmc_dma_slave_config;
> > + ddev->device_prep_slave_sg = loongson2_cmc_dma_prep_slave_sg;
> > + ddev->device_prep_dma_cyclic = loongson2_cmc_dma_prep_dma_cyclic;
> > + ddev->device_issue_pending = loongson2_cmc_dma_issue_pending;
> > + ddev->device_synchronize = loongson2_cmc_dma_synchronize;
> > + ddev->device_tx_status = loongson2_cmc_dma_tx_status;
> > + ddev->device_terminate_all = loongson2_cmc_dma_terminate_all;
> > +
> > + ddev->src_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS;
> > + ddev->dst_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS;
> > + ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
> > + INIT_LIST_HEAD(&ddev->channels);
>
> where use this 'channels' ?
It will be used by global functions such as `dma_async_device_register()`.
>
> Frank
> > +
> > + for (i = 0; i < nr_chans; i++) {
> > + lchan = &lddev->chan[i];
> > +
> > + lchan->id = i;
> > + lchan->vchan.desc_free = loongson2_cmc_dma_desc_free;
> > + vchan_init(&lchan->vchan, ddev);
> > + }
> > +
> > + ret = dmaenginem_async_device_register(ddev);
> > + if (ret)
> > + return ret;
> > +
> > + for (i = 0; i < nr_chans; i++) {
> > + lchan = &lddev->chan[i];
> > +
> > + lchan->irq = platform_get_irq(pdev, i);
> > + if (lchan->irq < 0)
> > + return lchan->irq;
> > +
> > + ret = devm_request_irq(dev, lchan->irq, loongson2_cmc_dma_chan_irq, IRQF_SHARED,
> > + dev_name(chan2dev(lchan)), lchan);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + ret = loongson2_cmc_dma_acpi_controller_register(lddev);
> > + if (ret)
> > + return ret;
> > +
> > + return loongson2_cmc_dma_of_controller_register(lddev);
> > +}
> > +
> > +static void loongson2_cmc_dma_remove(struct platform_device *pdev)
> > +{
> > + of_dma_controller_free(pdev->dev.of_node);
> > +}
> > +
> > +static const struct of_device_id loongson2_cmc_dma_of_match[] = {
> > + { .compatible = "loongson,ls2k0300-dma", .data = &ls2k0300_cmc_dma_config },
> > + { .compatible = "loongson,ls2k3000-dma", .data = &ls2k3000_cmc_dma_config },
> > + { /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, loongson2_cmc_dma_of_match);
> > +
> > +static const struct acpi_device_id loongson2_cmc_dma_acpi_match[] = {
> > + { "LOON0014", .driver_data = (kernel_ulong_t)&ls2k3000_cmc_dma_config },
> > + { /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(acpi, loongson2_cmc_dma_acpi_match);
> > +
> > +static struct platform_driver loongson2_cmc_dma_driver = {
> > + .driver = {
> > + .name = "loongson2-apb-cmc-dma",
> > + .of_match_table = loongson2_cmc_dma_of_match,
> > + .acpi_match_table = loongson2_cmc_dma_acpi_match,
> > + },
> > + .probe = loongson2_cmc_dma_probe,
> > + .remove = loongson2_cmc_dma_remove,
> > +};
> > +module_platform_driver(loongson2_cmc_dma_driver);
> > +
> > +MODULE_DESCRIPTION("Looongson-2 Multi-Channel DMA Controller driver");
> > +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> > +MODULE_LICENSE("GPL");
> > --
> > 2.52.0
> >
--
Thanks.
Binbin
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/4] dmaengine: loongson: New driver for the Loongson Multi-Channel DMA controller
2026-02-10 7:41 ` Binbin Zhou
@ 2026-02-10 12:02 ` Binbin Zhou
2026-02-10 15:27 ` Frank Li
0 siblings, 1 reply; 16+ messages in thread
From: Binbin Zhou @ 2026-02-10 12:02 UTC (permalink / raw)
To: Frank Li
Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Vinod Koul, dmaengine, Xiaochuang Mao, Huacai Chen,
Xuerui Wang, loongarch, devicetree, Keguang Zhang, linux-mips,
jeffbai
Hi Frank:
On Tue, Feb 10, 2026 at 3:41 PM Binbin Zhou <zhoubb.aaron@gmail.com> wrote:
>
> Hi Frank:
>
> Thanks for your reply.
>
> On Tue, Feb 10, 2026 at 1:05 AM Frank Li <Frank.li@nxp.com> wrote:
> >
> > On Mon, Feb 09, 2026 at 11:04:55AM +0800, Binbin Zhou wrote:
> > > This DMA controller appears in Loongson-2K0300 and Loongson-2K3000.
> > >
> > > It is a chain multi-channel controller that enables data transfers from
> > > memory to memory, device to memory, and memory to device, as well as
> > > channel prioritization configurable through the channel configuration
> > > registers.
> > >
> > > In addition, there are slight differences between Loongson-2K0300 and
> > > Loongson-2K3000, such as channel register offsets and the number of
> > > channels.
> > >
> > > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > > ---
> > > MAINTAINERS | 1 +
> > > drivers/dma/loongson/Kconfig | 10 +
> > > drivers/dma/loongson/Makefile | 1 +
> > > drivers/dma/loongson/loongson2-apb-cmc-dma.c | 736 +++++++++++++++++++
> > > 4 files changed, 748 insertions(+)
> > > create mode 100644 drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index d3cb541aee2a..61a39070d7a0 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -14778,6 +14778,7 @@ L: dmaengine@vger.kernel.org
> > > S: Maintained
> > > F: Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
> > > F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
> > > +F: drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > F: drivers/dma/loongson/loongson2-apb-dma.c
> > >
> > > LOONGSON LS2X I2C DRIVER
> > > diff --git a/drivers/dma/loongson/Kconfig b/drivers/dma/loongson/Kconfig
> > > index 9dbdaef5a59f..28b3daeed4e3 100644
> > > --- a/drivers/dma/loongson/Kconfig
> > > +++ b/drivers/dma/loongson/Kconfig
> > > @@ -25,4 +25,14 @@ config LOONGSON2_APB_DMA
> > > This DMA controller transfers data from memory to peripheral fifo.
> > > It does not support memory to memory data transfer.
> > >
> > > +config LOONGSON2_APB_CMC_DMA
> > > + tristate "Loongson2 Chain Multi-Channel DMA support"
> > > + select DMA_ENGINE
> > > + select DMA_VIRTUAL_CHANNELS
> > > + help
> > > + Support for the Loongson Chain Multi-Channel DMA controller driver.
> > > + It is discovered on the Loongson-2K chip (Loongson-2K0300/Loongson-2K3000),
> > > + which has 4/8 channels internally, enabling bidirectional data transfer
> > > + between devices and memory.
> > > +
> > > endif
> > > diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
> > > index 6cdd08065e92..48c19781e729 100644
> > > --- a/drivers/dma/loongson/Makefile
> > > +++ b/drivers/dma/loongson/Makefile
> > > @@ -1,3 +1,4 @@
> > > # SPDX-License-Identifier: GPL-2.0-only
> > > obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> > > obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> > > +obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
> > > diff --git a/drivers/dma/loongson/loongson2-apb-cmc-dma.c b/drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > new file mode 100644
> > > index 000000000000..f598ad095686
> > > --- /dev/null
> > > +++ b/drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > @@ -0,0 +1,736 @@
> > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > +/*
> > > + * Looongson-2 Multi-Channel DMA Controller driver
> > > + *
> > > + * Copyright (C) 2024-2026 Loongson Technology Corporation Limited
> > > + */
> > > +
> > > +#include <linux/acpi.h>
> > > +#include <linux/acpi_dma.h>
> > > +#include <linux/bitfield.h>
> > > +#include <linux/clk.h>
> > > +#include <linux/dma-mapping.h>
> > > +#include <linux/dmapool.h>
> > > +#include <linux/interrupt.h>
> > > +#include <linux/io.h>
> > > +#include <linux/module.h>
> > > +#include <linux/of.h>
> > > +#include <linux/of_dma.h>
> > > +#include <linux/platform_device.h>
> > > +#include <linux/slab.h>
> > > +
> > > +#include "../dmaengine.h"
> > > +#include "../virt-dma.h"
> > > +
> > > +#define LOONGSON2_CMCDMA_ISR 0x0 /* DMA Interrupt Status Register */
> > > +#define LOONGSON2_CMCDMA_IFCR 0x4 /* DMA Interrupt Flag Clear Register */
> > > +#define LOONGSON2_CMCDMA_CCR 0x8 /* DMA Channel Configuration Register */
> > > +#define LOONGSON2_CMCDMA_CNDTR 0xc /* DMA Channel Transmit Count Register */
> > > +#define LOONGSON2_CMCDMA_CPAR 0x10 /* DMA Channel Peripheral Address Register */
> > > +#define LOONGSON2_CMCDMA_CMAR 0x14 /* DMA Channel Memory Address Register */
> > > +
> > > +/* Bitfields of DMA interrupt status register */
> > > +#define LOONGSON2_CMCDMA_TCI BIT(1) /* Transfer Complete Interrupt */
> > > +#define LOONGSON2_CMCDMA_HTI BIT(2) /* Half Transfer Interrupt */
> > > +#define LOONGSON2_CMCDMA_TEI BIT(3) /* Transfer Error Interrupt */
> > > +
> > > +#define LOONGSON2_CMCDMA_MASKI \
> > > + (LOONGSON2_CMCDMA_TCI | LOONGSON2_CMCDMA_HTI | LOONGSON2_CMCDMA_TEI)
> > > +
> > > +/* Bitfields of DMA channel x Configuration Register */
> > > +#define LOONGSON2_CMCDMA_CCR_EN BIT(0) /* Stream Enable */
> > > +#define LOONGSON2_CMCDMA_CCR_TCIE BIT(1) /* Transfer Complete Interrupt Enable */
> > > +#define LOONGSON2_CMCDMA_CCR_HTIE BIT(2) /* Half Transfer Complete Interrupt Enable */
> > > +#define LOONGSON2_CMCDMA_CCR_TEIE BIT(3) /* Transfer Error Interrupt Enable */
> > > +#define LOONGSON2_CMCDMA_CCR_DIR BIT(4) /* Data Transfer Direction */
> > > +#define LOONGSON2_CMCDMA_CCR_CIRC BIT(5) /* Circular mode */
> > > +#define LOONGSON2_CMCDMA_CCR_PINC BIT(6) /* Peripheral increment mode */
> > > +#define LOONGSON2_CMCDMA_CCR_MINC BIT(7) /* Memory increment mode */
> > > +#define LOONGSON2_CMCDMA_CCR_PSIZE_MASK GENMASK(9, 8)
> > > +#define LOONGSON2_CMCDMA_CCR_MSIZE_MASK GENMASK(11, 10)
> > > +#define LOONGSON2_CMCDMA_CCR_PL_MASK GENMASK(13, 12)
> > > +#define LOONGSON2_CMCDMA_CCR_M2M BIT(14)
> > > +
> > > +#define LOONGSON2_CMCDMA_CCR_CFG_MASK \
> > > + (LOONGSON2_CMCDMA_CCR_PINC | LOONGSON2_CMCDMA_CCR_MINC | LOONGSON2_CMCDMA_CCR_PL_MASK)
> > > +
> > > +#define LOONGSON2_CMCDMA_CCR_IRQ_MASK \
> > > + (LOONGSON2_CMCDMA_CCR_TCIE | LOONGSON2_CMCDMA_CCR_HTIE | LOONGSON2_CMCDMA_CCR_TEIE)
> > > +
> > > +#define LOONGSON2_CMCDMA_STREAM_MASK \
> > > + (LOONGSON2_CMCDMA_CCR_CFG_MASK | LOONGSON2_CMCDMA_CCR_IRQ_MASK)
> > > +
> > > +#define LOONGSON2_CMCDMA_BUSWIDTHS (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \
> > > + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \
> > > + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES))
> > > +
> > > +enum loongson2_cmc_dma_width {
> > > + LOONGSON2_CMCDMA_BYTE,
> > > + LOONGSON2_CMCDMA_HALF_WORD,
> > > + LOONGSON2_CMCDMA_WORD,
> > > +};
> > > +
> > > +struct loongson2_cmc_dma_chan_reg {
> > > + u32 ccr;
> > > + u32 cndtr;
> > > + u32 cpar;
> > > + u32 cmar;
> > > +};
> > > +
> > > +struct loongson2_cmc_dma_sg_req {
> > > + u32 len;
> > > + struct loongson2_cmc_dma_chan_reg chan_reg;
> > > +};
> > > +
> > > +struct loongson2_cmc_dma_desc {
> > > + struct virt_dma_desc vdesc;
> > > + bool cyclic;
> > > + u32 num_sgs;
> > > + struct loongson2_cmc_dma_sg_req sg_req[] __counted_by(num_sgs);
> > > +};
> > > +
> > > +struct loongson2_cmc_dma_chan {
> > > + struct virt_dma_chan vchan;
> > > + struct dma_slave_config dma_sconfig;
> > > + struct loongson2_cmc_dma_desc *desc;
> > > + u32 id;
> > > + u32 irq;
> > > + u32 next_sg;
> > > + struct loongson2_cmc_dma_chan_reg chan_reg;
> > > +};
> > > +
> > > +struct loongson2_cmc_dma_config {
> > > + u32 max_channels;
> > > + u32 chan_reg_offset;
> > > +};
> > > +
> > > +struct loongson2_cmc_dma_dev {
> > > + struct dma_device ddev;
> > > + struct clk *dma_clk;
> > > + void __iomem *base;
> > > + u32 nr_channels;
> > > + u32 chan_reg_offset;
> > > + struct loongson2_cmc_dma_chan chan[] __counted_by(nr_channels);
> > > +};
> > > +
> > > +static const struct loongson2_cmc_dma_config ls2k0300_cmc_dma_config = {
> > > + .max_channels = 8,
> > > + .chan_reg_offset = 0x14,
> > > +};
> > > +
> > > +static const struct loongson2_cmc_dma_config ls2k3000_cmc_dma_config = {
> > > + .max_channels = 4,
> > > + .chan_reg_offset = 0x18,
> > > +};
> > > +
> > > +static struct loongson2_cmc_dma_dev *lmdma_get_dev(struct loongson2_cmc_dma_chan *lchan)
> > > +{
> > > + return container_of(lchan->vchan.chan.device, struct loongson2_cmc_dma_dev, ddev);
> > > +}
> > > +
> > > +static struct loongson2_cmc_dma_chan *to_lmdma_chan(struct dma_chan *chan)
> > > +{
> > > + return container_of(chan, struct loongson2_cmc_dma_chan, vchan.chan);
> > > +}
> > > +
> > > +static struct loongson2_cmc_dma_desc *to_lmdma_desc(struct virt_dma_desc *vdesc)
> > > +{
> > > + return container_of(vdesc, struct loongson2_cmc_dma_desc, vdesc);
> > > +}
> > > +
> > > +static struct device *chan2dev(struct loongson2_cmc_dma_chan *lchan)
> > > +{
> > > + return &lchan->vchan.chan.dev->device;
> > > +}
> > > +
> > > +static u32 loongson2_cmc_dma_read(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id)
> > > +{
> > > + return readl(lddev->base + (reg + lddev->chan_reg_offset * id));
> > > +}
> > > +
> > > +static void loongson2_cmc_dma_write(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id, u32 val)
> > > +{
> > > + writel(val, lddev->base + (reg + lddev->chan_reg_offset * id));
> > > +}
> > > +
> > > +static int loongson2_cmc_dma_get_width(struct loongson2_cmc_dma_chan *lchan,
> > > + enum dma_slave_buswidth width)
> > > +{
> > > + switch (width) {
> > > + case DMA_SLAVE_BUSWIDTH_1_BYTE:
> > > + return LOONGSON2_CMCDMA_BYTE;
> > > + case DMA_SLAVE_BUSWIDTH_2_BYTES:
> > > + return LOONGSON2_CMCDMA_HALF_WORD;
> > > + case DMA_SLAVE_BUSWIDTH_4_BYTES:
> > > + return LOONGSON2_CMCDMA_WORD;
> >
> > is ffs() helper in case your hardware support more buswidth in future?
>
> It seems there's no need for us to do this.
> The data width setting bit in the DMA channel configuration register
> only has two bits (LOONGSON2_CMCDMA_CCR_PSIZE_MASK). The bitmask
> values are: 8-bit/16-bit/32-bit/reserved.
Sorry, I checked again, the ffs() helper can make the code cleaner:
static int loongson2_cmc_dma_get_width(enum dma_slave_buswidth width)
{
switch (width) {
case DMA_SLAVE_BUSWIDTH_1_BYTE:
case DMA_SLAVE_BUSWIDTH_2_BYTES:
case DMA_SLAVE_BUSWIDTH_4_BYTES:
return ffs(width) - 1;
default:
return -EINVAL;
}
}
And the enum loongson2_cmc_dma_width{ } can be dropped.
> >
> > > + default:
> > > + dev_err(chan2dev(lchan), "Dma bus width not supported\n");
> > > + return -EINVAL;
> > > + }
> > > +}
> > > +
> > > +static int loongson2_cmc_dma_slave_config(struct dma_chan *chan, struct dma_slave_config *config)
> > > +{
> > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > +
> > > + memcpy(&lchan->dma_sconfig, config, sizeof(*config));
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void loongson2_cmc_dma_irq_clear(struct loongson2_cmc_dma_chan *lchan, u32 flags)
> > > +{
> > > + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > > + u32 ifcr;
> > > +
> > > + ifcr = flags << (4 * lchan->id);
> > > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_IFCR, 0, ifcr);
> > > +}
> > > +
> > > +static void loongson2_cmc_dma_stop(struct loongson2_cmc_dma_chan *lchan)
> > > +{
> > > + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > > + u32 ccr;
> > > +
> > > + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
> > > + ccr &= ~(LOONGSON2_CMCDMA_CCR_IRQ_MASK | LOONGSON2_CMCDMA_CCR_EN);
> > > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, ccr);
> > > +
> > > + loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_MASKI);
> > > +}
> > > +
> > > +static int loongson2_cmc_dma_terminate_all(struct dma_chan *chan)
> > > +{
> > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > + unsigned long flags;
> > > +
> > > + LIST_HEAD(head);
> > > +
> > > + spin_lock_irqsave(&lchan->vchan.lock, flags);
> > > + if (lchan->desc) {
> > > + vchan_terminate_vdesc(&lchan->desc->vdesc);
> > > + loongson2_cmc_dma_stop(lchan);
> > > + lchan->desc = NULL;
> > > + }
> > > + vchan_get_all_descriptors(&lchan->vchan, &head);
> > > + spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> > > +
> > > + vchan_dma_desc_free_list(&lchan->vchan, &head);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void loongson2_cmc_dma_synchronize(struct dma_chan *chan)
> > > +{
> > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > +
> > > + vchan_synchronize(&lchan->vchan);
> > > +}
> > > +
> > > +static void loongson2_cmc_dma_start_transfer(struct loongson2_cmc_dma_chan *lchan)
> > > +{
> > > + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > > + struct loongson2_cmc_dma_sg_req *sg_req;
> > > + struct loongson2_cmc_dma_chan_reg *reg;
> > > + struct virt_dma_desc *vdesc;
> > > +
> > > + loongson2_cmc_dma_stop(lchan);
> > > +
> > > + if (!lchan->desc) {
> > > + vdesc = vchan_next_desc(&lchan->vchan);
> > > + if (!vdesc)
> > > + return;
> > > +
> > > + list_del(&vdesc->node);
> > > + lchan->desc = to_lmdma_desc(vdesc);
> > > + lchan->next_sg = 0;
> > > + }
> > > +
> > > + if (lchan->next_sg == lchan->desc->num_sgs)
> > > + lchan->next_sg = 0;
> > > +
> > > + sg_req = &lchan->desc->sg_req[lchan->next_sg];
> > > + reg = &sg_req->chan_reg;
> > > +
> > > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, reg->ccr);
> > > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CNDTR, lchan->id, reg->cndtr);
> > > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CPAR, lchan->id, reg->cpar);
> > > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CMAR, lchan->id, reg->cmar);
> > > +
> > > + lchan->next_sg++;
> > > +
> > > + /* Start DMA */
> > > + reg->ccr |= LOONGSON2_CMCDMA_CCR_EN;
> > > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, lchan->id, reg->ccr);
> > > +}
> > > +
> > > +static void loongson2_cmc_dma_configure_next_sg(struct loongson2_cmc_dma_chan *lchan)
> > > +{
> > > + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > > + struct loongson2_cmc_dma_sg_req *sg_req;
> > > + u32 ccr, id = lchan->id;
> > > +
> > > + if (lchan->next_sg == lchan->desc->num_sgs)
> > > + lchan->next_sg = 0;
> > > +
> > > + /* stop to update mem addr */
> > > + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, id);
> > > + ccr &= ~LOONGSON2_CMCDMA_CCR_EN;
> > > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, id, ccr);
> > > +
> > > + sg_req = &lchan->desc->sg_req[lchan->next_sg];
> > > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CMAR, id, sg_req->chan_reg.cmar);
> > > +
> > > + /* start transition */
> > > + ccr |= LOONGSON2_CMCDMA_CCR_EN;
> > > + loongson2_cmc_dma_write(lddev, LOONGSON2_CMCDMA_CCR, id, ccr);
> > > +}
> > > +
> > > +static void loongson2_cmc_dma_handle_chan_done(struct loongson2_cmc_dma_chan *lchan)
> > > +{
> > > + if (!lchan->desc)
> > > + return;
> > > +
> > > + if (lchan->desc->cyclic) {
> > > + vchan_cyclic_callback(&lchan->desc->vdesc);
> > > + /* LOONGSON2_CMCDMA_CCR_CIRC mode don't need update register */
> > > + if (lchan->desc->num_sgs == 1)
> > > + return;
> > > + loongson2_cmc_dma_configure_next_sg(lchan);
> > > + lchan->next_sg++;
> > > + } else {
> > > + if (lchan->next_sg == lchan->desc->num_sgs) {
> > > + vchan_cookie_complete(&lchan->desc->vdesc);
> > > + lchan->desc = NULL;
> > > + }
> > > + loongson2_cmc_dma_start_transfer(lchan);
> > > + }
> > > +}
> > > +
> > > +static irqreturn_t loongson2_cmc_dma_chan_irq(int irq, void *devid)
> > > +{
> > > + struct loongson2_cmc_dma_chan *lchan = devid;
> > > + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > > + u32 ists, status, scr;
> > > +
> > > + spin_lock(&lchan->vchan.lock);
> > > +
> > > + ists = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_ISR, 0);
> > > + scr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
> > > +
> > > + status = (ists >> (4 * lchan->id)) & LOONGSON2_CMCDMA_MASKI;
> > > + status &= scr;
> > > +
> > > + if (status & LOONGSON2_CMCDMA_TCI)
> > > + loongson2_cmc_dma_handle_chan_done(lchan);
> > > +
> > > + if (status & LOONGSON2_CMCDMA_HTI)
> > > + loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
> > > +
> > > + if (status & LOONGSON2_CMCDMA_TEI)
> > > + dev_err(chan2dev(lchan), "DMA Transform Error\n");
> > > +
> > > + loongson2_cmc_dma_irq_clear(lchan, status);
> >
> > irq clear should before loongson2_cmc_dma_handle_chan_done() incase you
> > missed irq, if loongson2_cmc_dma_handle_chan_done() trigger new irq before
> > your call irq_cler().
Yes, this part should be refracted, how about the following code:
spin_lock(&lchan->vchan.lock);
ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
ists = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_ISR, 0);
status = (ists >> (4 * lchan->id)) & LOONGSON2_CMCDMA_MASKI;
if (status & LOONGSON2_CMCDMA_TCI) {
loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_TCI);
if (ccr & LOONGSON2_CMCDMA_CCR_TCIE)
loongson2_cmc_dma_handle_chan_done(lchan);
status &= ~LOONGSON2_CMCDMA_TCI;
}
if (status & LOONGSON2_CMCDMA_HTI) {
loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
status &= ~LOONGSON2_CMCDMA_HTI;
}
if (status & LOONGSON2_CMCDMA_TEI) {
loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
dev_err(chan2dev(lchan), "DMA Transform Error\n");
if (!(ccr & LOONGSON2_CMCDMA_CCR_EN))
dev_err(chan2dev(lchan), "chan disabled by HW\n");
}
spin_unlock(&lchan->vchan.lock);
> >
> > > +
> > > + spin_unlock(&lchan->vchan.lock);
> > > +
> > > + return IRQ_HANDLED;
> > > +}
> > > +
> > > +static void loongson2_cmc_dma_issue_pending(struct dma_chan *chan)
> > > +{
> > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > + unsigned long flags;
> > > +
> > > + spin_lock_irqsave(&lchan->vchan.lock, flags);
> > > + if (vchan_issue_pending(&lchan->vchan) && !lchan->desc) {
> > > + dev_dbg(chan2dev(lchan), "vchan %pK: issued\n", &lchan->vchan);
> > > + loongson2_cmc_dma_start_transfer(lchan);
> > > + }
> > > + spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> > > +}
> > > +
> > > +static int loongson2_cmc_dma_set_xfer_param(struct loongson2_cmc_dma_chan *lchan,
> > > + enum dma_transfer_direction direction,
> > > + enum dma_slave_buswidth *buswidth, u32 buf_len)
> > > +{
> > > + struct dma_slave_config sconfig = lchan->dma_sconfig;
> > > + int dev_width;
> > > + u32 ccr;
> > > +
> > > + switch (direction) {
> > > + case DMA_MEM_TO_DEV:
> > > + dev_width = loongson2_cmc_dma_get_width(lchan, sconfig.dst_addr_width);
> > > + if (dev_width < 0)
> > > + return dev_width;
> > > + lchan->chan_reg.cpar = sconfig.dst_addr;
> > > + ccr = LOONGSON2_CMCDMA_CCR_DIR;
> > > + *buswidth = sconfig.dst_addr_width;
> > > + break;
> > > + case DMA_DEV_TO_MEM:
> > > + dev_width = loongson2_cmc_dma_get_width(lchan, sconfig.src_addr_width);
> > > + if (dev_width < 0)
> > > + return dev_width;
> > > + lchan->chan_reg.cpar = sconfig.src_addr;
> > > + ccr = LOONGSON2_CMCDMA_CCR_MINC;
> > > + *buswidth = sconfig.src_addr_width;
> > > + break;
> > > + default:
> > > + return -EINVAL;
> > > + }
> > > +
> > > + ccr |= FIELD_PREP(LOONGSON2_CMCDMA_CCR_PSIZE_MASK, dev_width) |
> > > + FIELD_PREP(LOONGSON2_CMCDMA_CCR_MSIZE_MASK, dev_width);
> > > +
> > > + /* Set DMA control register */
> > > + lchan->chan_reg.ccr &= ~(LOONGSON2_CMCDMA_CCR_PSIZE_MASK | LOONGSON2_CMCDMA_CCR_MSIZE_MASK);
> > > + lchan->chan_reg.ccr |= ccr;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static struct dma_async_tx_descriptor *
> > > +loongson2_cmc_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, u32 sg_len,
> > > + enum dma_transfer_direction direction,
> > > + unsigned long flags, void *context)
> > > +{
> > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > + struct loongson2_cmc_dma_desc *desc;
> > > + enum dma_slave_buswidth buswidth;
> > > + struct scatterlist *sg;
> > > + u32 num_items, i;
> > > + int ret;
> > > +
> > > + desc = kzalloc(struct_size(desc, sg_req, sg_len), GFP_NOWAIT);
> > > + if (!desc)
> > > + return NULL;
> > > +
> > > + for_each_sg(sgl, sg, sg_len, i) {
> > > + ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, sg_dma_len(sg));
> > > + if (ret)
> > > + return NULL;
> > > +
> > > + desc->sg_req[i].len = sg_dma_len(sg);
> > > +
> > > + num_items = desc->sg_req[i].len / buswidth;
> > > + if (num_items >= SZ_64K) {
> > > + dev_err(chan2dev(lchan), "Number of items not supported\n");
> > > + kfree(desc);
> > > + return NULL;
> >
> > if use sg_nents_for_dma(), you can use multi sg to trasfer more than 64K
> > data.
>
> Sorry, are you referring to sg_nents_for_len()?
> 64K is a hardware limitation of the controller, so it seems impossible
> to resolve it using that function, right?
>
> >
> > > + }
> > > + desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr;
> > > + desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar;
> > > + desc->sg_req[i].chan_reg.cmar = sg_dma_address(sg);
> > > + desc->sg_req[i].chan_reg.cndtr = num_items;
> > > + }
> > > +
> > > + desc->num_sgs = sg_len;
> > > + desc->cyclic = false;
> > > +
> > > + return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags);
> > > +}
> > > +
> > > +static struct dma_async_tx_descriptor *
> > > +loongson2_cmc_dma_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
> > > + size_t period_len, enum dma_transfer_direction direction,
> > > + unsigned long flags)
> > > +{
> > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > + struct loongson2_cmc_dma_desc *desc;
> > > + enum dma_slave_buswidth buswidth;
> > > + u32 num_periods, num_items, i;
> > > + int ret;
> > > +
> > > + if (unlikely(buf_len % period_len))
> > > + return NULL;
> > > +
> > > + ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, period_len);
> > > + if (ret)
> > > + return NULL;
> > > +
> > > + num_items = period_len / buswidth;
> > > + if (num_items >= SZ_64K) {
> > > + dev_err(chan2dev(lchan), "Number of items not supported\n");
> > > + return NULL;
> > > + }
> > > +
> > > + /* Enable Circular mode */
> > > + if (buf_len == period_len)
> > > + lchan->chan_reg.ccr |= LOONGSON2_CMCDMA_CCR_CIRC;
> > > +
> > > + num_periods = buf_len / period_len;
> > > + desc = kzalloc(struct_size(desc, sg_req, num_periods), GFP_NOWAIT);
> > > + if (!desc)
> > > + return NULL;
> > > +
> > > + for (i = 0; i < num_periods; i++) {
> > > + desc->sg_req[i].len = period_len;
> > > + desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr;
> > > + desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar;
> > > + desc->sg_req[i].chan_reg.cmar = buf_addr;
> > > + desc->sg_req[i].chan_reg.cndtr = num_items;
> > > + buf_addr += period_len;
> > > + }
> > > +
> > > + desc->num_sgs = num_periods;
> > > + desc->cyclic = true;
> > > +
> > > + return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags);
> > > +}
> > > +
> > > +static size_t loongson2_cmc_dma_desc_residue(struct loongson2_cmc_dma_chan *lchan,
> > > + struct loongson2_cmc_dma_desc *desc, u32 next_sg)
> > > +{
> > > + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > > + u32 residue, width, ndtr, ccr, i;
> > > +
> > > + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
> > > + width = FIELD_GET(LOONGSON2_CMCDMA_CCR_PSIZE_MASK, ccr);
> > > +
> > > + ndtr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CNDTR, lchan->id);
> > > + residue = ndtr << width;
> > > +
> > > + if (lchan->desc->cyclic && next_sg == 0)
> > > + return residue;
> > > +
> > > + for (i = next_sg; i < desc->num_sgs; i++)
> > > + residue += desc->sg_req[i].len;
> > > +
> > > + return residue;
> > > +}
> > > +
> > > +static enum dma_status loongson2_cmc_dma_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
> > > + struct dma_tx_state *state)
> > > +{
> > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > + struct virt_dma_desc *vdesc;
> > > + enum dma_status status;
> > > + unsigned long flags;
> > > +
> > > + status = dma_cookie_status(chan, cookie, state);
> > > + if (status == DMA_COMPLETE || !state)
> > > + return status;
> > > +
> > > + spin_lock_irqsave(&lchan->vchan.lock, flags);
> > > + vdesc = vchan_find_desc(&lchan->vchan, cookie);
> > > + if (lchan->desc && cookie == lchan->desc->vdesc.tx.cookie)
> > > + state->residue = loongson2_cmc_dma_desc_residue(lchan, lchan->desc, lchan->next_sg);
> > > + else if (vdesc)
> > > + state->residue = loongson2_cmc_dma_desc_residue(lchan, to_lmdma_desc(vdesc), 0);
> > > +
> > > + spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> > > +
> > > + return status;
> > > +}
> > > +
> > > +static void loongson2_cmc_dma_free_chan_resources(struct dma_chan *chan)
> > > +{
> > > + vchan_free_chan_resources(to_virt_chan(chan));
> > > +}
> > > +
> > > +static void loongson2_cmc_dma_desc_free(struct virt_dma_desc *vdesc)
> > > +{
> > > + kfree(to_lmdma_desc(vdesc));
> > > +}
> > > +
> > > +static bool loongson2_cmc_dma_acpi_filter(struct dma_chan *chan, void *param)
> > > +{
> > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > + struct acpi_dma_spec *dma_spec = param;
> > > +
> > > + memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg));
> > > + lchan->chan_reg.ccr = dma_spec->chan_id & LOONGSON2_CMCDMA_STREAM_MASK;
> > > +
> > > + return true;
> > > +}
> > > +
> > > +static int loongson2_cmc_dma_acpi_controller_register(struct loongson2_cmc_dma_dev *lddev)
> > > +{
> > > + struct device *dev = lddev->ddev.dev;
> > > + struct acpi_dma_filter_info *info;
> > > + int ret;
> > > +
> > > + if (!has_acpi_companion(dev))
> > > + return 0;
> > > +
> > > + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
> > > + if (!info)
> > > + return -ENOMEM;
> > > +
> > > + dma_cap_zero(info->dma_cap);
> > > + info->dma_cap = lddev->ddev.cap_mask;
> > > + info->filter_fn = loongson2_cmc_dma_acpi_filter;
> > > +
> > > + ret = devm_acpi_dma_controller_register(dev, acpi_dma_simple_xlate, info);
> > > + if (ret)
> > > + dev_err(dev, "could not register acpi_dma_controller\n");
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static struct dma_chan *loongson2_cmc_dma_of_xlate(struct of_phandle_args *dma_spec,
> > > + struct of_dma *ofdma)
> > > +{
> > > + struct loongson2_cmc_dma_dev *lddev = ofdma->of_dma_data;
> > > + struct device *dev = lddev->ddev.dev;
> > > + struct loongson2_cmc_dma_chan *lchan;
> > > + struct dma_chan *chan;
> > > +
> > > + if (dma_spec->args_count < 2)
> > > + return NULL;
> > > +
> > > + if (dma_spec->args[0] >= lddev->nr_channels) {
> > > + dev_err(dev, "Invalid channel id\n");
> > > + return NULL;
> > > + }
> > > +
> > > + lchan = &lddev->chan[dma_spec->args[0]];
> > > + chan = dma_get_slave_channel(&lchan->vchan.chan);
> > > + if (!chan) {
> > > + dev_err(dev, "No more channels available\n");
> > > + return NULL;
> > > + }
> > > +
> > > + memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg));
> > > + lchan->chan_reg.ccr = dma_spec->args[1] & LOONGSON2_CMCDMA_STREAM_MASK;
> > > +
> > > + return chan;
> > > +}
> > > +
> > > +static int loongson2_cmc_dma_of_controller_register(struct loongson2_cmc_dma_dev *lddev)
> > > +{
> > > + struct device *dev = lddev->ddev.dev;
> > > + int ret;
> > > +
> > > + if (!dev->of_node)
> > > + return 0;
> > > +
> > > + ret = of_dma_controller_register(dev->of_node, loongson2_cmc_dma_of_xlate, lddev);
> > > + if (ret)
> > > + dev_err(dev, "could not register of_dma_controller\n");
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static int loongson2_cmc_dma_probe(struct platform_device *pdev)
> > > +{
> > > + const struct loongson2_cmc_dma_config *config;
> > > + struct loongson2_cmc_dma_chan *lchan;
> > > + struct loongson2_cmc_dma_dev *lddev;
> > > + struct device *dev = &pdev->dev;
> > > + struct dma_device *ddev;
> > > + u32 nr_chans, i;
> > > + int ret;
> > > +
> > > + config = (const struct loongson2_cmc_dma_config *)device_get_match_data(dev);
> > > + if (!config)
> > > + return -EINVAL;
> > > +
> > > + ret = device_property_read_u32(dev, "dma-channels", &nr_chans);
> > > + if (ret || nr_chans > config->max_channels) {
> > > + dev_err(dev, "missing or invalid dma-channels property\n");
> > > + nr_chans = config->max_channels;
> > > + }
> > > +
> > > + lddev = devm_kzalloc(dev, struct_size(lddev, chan, nr_chans), GFP_KERNEL);
> > > + if (!lddev)
> > > + return -ENOMEM;
> > > +
> > > + lddev->base = devm_platform_ioremap_resource(pdev, 0);
> > > + if (IS_ERR(lddev->base))
> > > + return PTR_ERR(lddev->base);
> > > +
> > > + platform_set_drvdata(pdev, lddev);
> > > + lddev->nr_channels = nr_chans;
> > > + lddev->chan_reg_offset = config->chan_reg_offset;
> > > +
> > > + lddev->dma_clk = devm_clk_get_optional_enabled(dev, NULL);
> > > + if (IS_ERR(lddev->dma_clk))
> > > + return dev_err_probe(dev, PTR_ERR(lddev->dma_clk), "Failed to get dma clock\n");
> > > +
> > > + ddev = &lddev->ddev;
> > > + ddev->dev = dev;
> > > +
> > > + dma_cap_zero(ddev->cap_mask);
> > > + dma_cap_set(DMA_SLAVE, ddev->cap_mask);
> > > + dma_cap_set(DMA_PRIVATE, ddev->cap_mask);
> > > + dma_cap_set(DMA_CYCLIC, ddev->cap_mask);
> > > +
> > > + ddev->device_free_chan_resources = loongson2_cmc_dma_free_chan_resources;
> > > + ddev->device_config = loongson2_cmc_dma_slave_config;
> > > + ddev->device_prep_slave_sg = loongson2_cmc_dma_prep_slave_sg;
> > > + ddev->device_prep_dma_cyclic = loongson2_cmc_dma_prep_dma_cyclic;
> > > + ddev->device_issue_pending = loongson2_cmc_dma_issue_pending;
> > > + ddev->device_synchronize = loongson2_cmc_dma_synchronize;
> > > + ddev->device_tx_status = loongson2_cmc_dma_tx_status;
> > > + ddev->device_terminate_all = loongson2_cmc_dma_terminate_all;
> > > +
> > > + ddev->src_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS;
> > > + ddev->dst_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS;
> > > + ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
> > > + INIT_LIST_HEAD(&ddev->channels);
> >
> > where use this 'channels' ?
>
> It will be used by global functions such as `dma_async_device_register()`.
> >
> > Frank
> > > +
> > > + for (i = 0; i < nr_chans; i++) {
> > > + lchan = &lddev->chan[i];
> > > +
> > > + lchan->id = i;
> > > + lchan->vchan.desc_free = loongson2_cmc_dma_desc_free;
> > > + vchan_init(&lchan->vchan, ddev);
> > > + }
> > > +
> > > + ret = dmaenginem_async_device_register(ddev);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + for (i = 0; i < nr_chans; i++) {
> > > + lchan = &lddev->chan[i];
> > > +
> > > + lchan->irq = platform_get_irq(pdev, i);
> > > + if (lchan->irq < 0)
> > > + return lchan->irq;
> > > +
> > > + ret = devm_request_irq(dev, lchan->irq, loongson2_cmc_dma_chan_irq, IRQF_SHARED,
> > > + dev_name(chan2dev(lchan)), lchan);
> > > + if (ret)
> > > + return ret;
> > > + }
> > > +
> > > + ret = loongson2_cmc_dma_acpi_controller_register(lddev);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return loongson2_cmc_dma_of_controller_register(lddev);
> > > +}
> > > +
> > > +static void loongson2_cmc_dma_remove(struct platform_device *pdev)
> > > +{
> > > + of_dma_controller_free(pdev->dev.of_node);
> > > +}
> > > +
> > > +static const struct of_device_id loongson2_cmc_dma_of_match[] = {
> > > + { .compatible = "loongson,ls2k0300-dma", .data = &ls2k0300_cmc_dma_config },
> > > + { .compatible = "loongson,ls2k3000-dma", .data = &ls2k3000_cmc_dma_config },
> > > + { /* sentinel */ }
> > > +};
> > > +MODULE_DEVICE_TABLE(of, loongson2_cmc_dma_of_match);
> > > +
> > > +static const struct acpi_device_id loongson2_cmc_dma_acpi_match[] = {
> > > + { "LOON0014", .driver_data = (kernel_ulong_t)&ls2k3000_cmc_dma_config },
> > > + { /* sentinel */ }
> > > +};
> > > +MODULE_DEVICE_TABLE(acpi, loongson2_cmc_dma_acpi_match);
> > > +
> > > +static struct platform_driver loongson2_cmc_dma_driver = {
> > > + .driver = {
> > > + .name = "loongson2-apb-cmc-dma",
> > > + .of_match_table = loongson2_cmc_dma_of_match,
> > > + .acpi_match_table = loongson2_cmc_dma_acpi_match,
> > > + },
> > > + .probe = loongson2_cmc_dma_probe,
> > > + .remove = loongson2_cmc_dma_remove,
> > > +};
> > > +module_platform_driver(loongson2_cmc_dma_driver);
> > > +
> > > +MODULE_DESCRIPTION("Looongson-2 Multi-Channel DMA Controller driver");
> > > +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> > > +MODULE_LICENSE("GPL");
> > > --
> > > 2.52.0
> > >
>
> --
> Thanks.
> Binbin
--
Thanks.
Binbin
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/4] dmaengine: loongson: New driver for the Loongson Multi-Channel DMA controller
2026-02-10 12:02 ` Binbin Zhou
@ 2026-02-10 15:27 ` Frank Li
2026-02-24 12:59 ` Binbin Zhou
0 siblings, 1 reply; 16+ messages in thread
From: Frank Li @ 2026-02-10 15:27 UTC (permalink / raw)
To: Binbin Zhou
Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Vinod Koul, dmaengine, Xiaochuang Mao, Huacai Chen,
Xuerui Wang, loongarch, devicetree, Keguang Zhang, linux-mips,
jeffbai
On Tue, Feb 10, 2026 at 08:02:21PM +0800, Binbin Zhou wrote:
> Hi Frank:
>
> On Tue, Feb 10, 2026 at 3:41 PM Binbin Zhou <zhoubb.aaron@gmail.com> wrote:
> >
> > Hi Frank:
> >
> > Thanks for your reply.
> >
> > On Tue, Feb 10, 2026 at 1:05 AM Frank Li <Frank.li@nxp.com> wrote:
> > >
> > > On Mon, Feb 09, 2026 at 11:04:55AM +0800, Binbin Zhou wrote:
> > > > This DMA controller appears in Loongson-2K0300 and Loongson-2K3000.
> > > >
> > > > It is a chain multi-channel controller that enables data transfers from
> > > > memory to memory, device to memory, and memory to device, as well as
> > > > channel prioritization configurable through the channel configuration
> > > > registers.
> > > >
> > > > In addition, there are slight differences between Loongson-2K0300 and
> > > > Loongson-2K3000, such as channel register offsets and the number of
> > > > channels.
> > > >
> > > > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > > > ---
> > > > MAINTAINERS | 1 +
> > > > drivers/dma/loongson/Kconfig | 10 +
> > > > drivers/dma/loongson/Makefile | 1 +
> > > > drivers/dma/loongson/loongson2-apb-cmc-dma.c | 736 +++++++++++++++++++
> > > > 4 files changed, 748 insertions(+)
> > > > create mode 100644 drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > >
> > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > index d3cb541aee2a..61a39070d7a0 100644
> > > > --- a/MAINTAINERS
> > > > +++ b/MAINTAINERS
> > > > @@ -14778,6 +14778,7 @@ L: dmaengine@vger.kernel.org
> > > > S: Maintained
> > > > F: Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
> > > > F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
> > > > +F: drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > > F: drivers/dma/loongson/loongson2-apb-dma.c
> > > >
> > > > LOONGSON LS2X I2C DRIVER
> > > > diff --git a/drivers/dma/loongson/Kconfig b/drivers/dma/loongson/Kconfig
> > > > index 9dbdaef5a59f..28b3daeed4e3 100644
> > > > --- a/drivers/dma/loongson/Kconfig
> > > > +++ b/drivers/dma/loongson/Kconfig
> > > > @@ -25,4 +25,14 @@ config LOONGSON2_APB_DMA
> > > > This DMA controller transfers data from memory to peripheral fifo.
> > > > It does not support memory to memory data transfer.
> > > >
> > > > +config LOONGSON2_APB_CMC_DMA
> > > > + tristate "Loongson2 Chain Multi-Channel DMA support"
> > > > + select DMA_ENGINE
> > > > + select DMA_VIRTUAL_CHANNELS
> > > > + help
> > > > + Support for the Loongson Chain Multi-Channel DMA controller driver.
> > > > + It is discovered on the Loongson-2K chip (Loongson-2K0300/Loongson-2K3000),
> > > > + which has 4/8 channels internally, enabling bidirectional data transfer
> > > > + between devices and memory.
> > > > +
> > > > endif
> > > > diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
> > > > index 6cdd08065e92..48c19781e729 100644
> > > > --- a/drivers/dma/loongson/Makefile
> > > > +++ b/drivers/dma/loongson/Makefile
> > > > @@ -1,3 +1,4 @@
> > > > # SPDX-License-Identifier: GPL-2.0-only
> > > > obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> > > > obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> > > > +obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
> > > > diff --git a/drivers/dma/loongson/loongson2-apb-cmc-dma.c b/drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > > new file mode 100644
> > > > index 000000000000..f598ad095686
> > > > --- /dev/null
> > > > +++ b/drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > > @@ -0,0 +1,736 @@
> > > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > > +/*
> > > > + * Looongson-2 Multi-Channel DMA Controller driver
> > > > + *
> > > > + * Copyright (C) 2024-2026 Loongson Technology Corporation Limited
> > > > + */
> > > > +
> > > > +#include <linux/acpi.h>
> > > > +#include <linux/acpi_dma.h>
> > > > +#include <linux/bitfield.h>
> > > > +#include <linux/clk.h>
> > > > +#include <linux/dma-mapping.h>
> > > > +#include <linux/dmapool.h>
> > > > +#include <linux/interrupt.h>
> > > > +#include <linux/io.h>
> > > > +#include <linux/module.h>
> > > > +#include <linux/of.h>
> > > > +#include <linux/of_dma.h>
> > > > +#include <linux/platform_device.h>
> > > > +#include <linux/slab.h>
> > > > +
> > > > +#include "../dmaengine.h"
> > > > +#include "../virt-dma.h"
> > > > +
> > > > +#define LOONGSON2_CMCDMA_ISR 0x0 /* DMA Interrupt Status Register */
> > > > +#define LOONGSON2_CMCDMA_IFCR 0x4 /* DMA Interrupt Flag Clear Register */
> > > > +#define LOONGSON2_CMCDMA_CCR 0x8 /* DMA Channel Configuration Register */
> > > > +#define LOONGSON2_CMCDMA_CNDTR 0xc /* DMA Channel Transmit Count Register */
> > > > +#define LOONGSON2_CMCDMA_CPAR 0x10 /* DMA Channel Peripheral Address Register */
> > > > +#define LOONGSON2_CMCDMA_CMAR 0x14 /* DMA Channel Memory Address Register */
> > > > +
> > > > +/* Bitfields of DMA interrupt status register */
> > > > +#define LOONGSON2_CMCDMA_TCI BIT(1) /* Transfer Complete Interrupt */
> > > > +#define LOONGSON2_CMCDMA_HTI BIT(2) /* Half Transfer Interrupt */
> > > > +#define LOONGSON2_CMCDMA_TEI BIT(3) /* Transfer Error Interrupt */
> > > > +
> > > > +#define LOONGSON2_CMCDMA_MASKI \
> > > > + (LOONGSON2_CMCDMA_TCI | LOONGSON2_CMCDMA_HTI | LOONGSON2_CMCDMA_TEI)
> > > > +
> > > > +/* Bitfields of DMA channel x Configuration Register */
> > > > +#define LOONGSON2_CMCDMA_CCR_EN BIT(0) /* Stream Enable */
> > > > +#define LOONGSON2_CMCDMA_CCR_TCIE BIT(1) /* Transfer Complete Interrupt Enable */
> > > > +#define LOONGSON2_CMCDMA_CCR_HTIE BIT(2) /* Half Transfer Complete Interrupt Enable */
> > > > +#define LOONGSON2_CMCDMA_CCR_TEIE BIT(3) /* Transfer Error Interrupt Enable */
> > > > +#define LOONGSON2_CMCDMA_CCR_DIR BIT(4) /* Data Transfer Direction */
> > > > +#define LOONGSON2_CMCDMA_CCR_CIRC BIT(5) /* Circular mode */
> > > > +#define LOONGSON2_CMCDMA_CCR_PINC BIT(6) /* Peripheral increment mode */
> > > > +#define LOONGSON2_CMCDMA_CCR_MINC BIT(7) /* Memory increment mode */
> > > > +#define LOONGSON2_CMCDMA_CCR_PSIZE_MASK GENMASK(9, 8)
> > > > +#define LOONGSON2_CMCDMA_CCR_MSIZE_MASK GENMASK(11, 10)
> > > > +#define LOONGSON2_CMCDMA_CCR_PL_MASK GENMASK(13, 12)
> > > > +#define LOONGSON2_CMCDMA_CCR_M2M BIT(14)
> > > > +
> > > > +#define LOONGSON2_CMCDMA_CCR_CFG_MASK \
> > > > + (LOONGSON2_CMCDMA_CCR_PINC | LOONGSON2_CMCDMA_CCR_MINC | LOONGSON2_CMCDMA_CCR_PL_MASK)
> > > > +
> > > > +#define LOONGSON2_CMCDMA_CCR_IRQ_MASK \
> > > > + (LOONGSON2_CMCDMA_CCR_TCIE | LOONGSON2_CMCDMA_CCR_HTIE | LOONGSON2_CMCDMA_CCR_TEIE)
> > > > +
> > > > +#define LOONGSON2_CMCDMA_STREAM_MASK \
> > > > + (LOONGSON2_CMCDMA_CCR_CFG_MASK | LOONGSON2_CMCDMA_CCR_IRQ_MASK)
> > > > +
> > > > +#define LOONGSON2_CMCDMA_BUSWIDTHS (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \
> > > > + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \
> > > > + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES))
> > > > +
> > > > +enum loongson2_cmc_dma_width {
> > > > + LOONGSON2_CMCDMA_BYTE,
> > > > + LOONGSON2_CMCDMA_HALF_WORD,
> > > > + LOONGSON2_CMCDMA_WORD,
> > > > +};
> > > > +
> > > > +struct loongson2_cmc_dma_chan_reg {
> > > > + u32 ccr;
> > > > + u32 cndtr;
> > > > + u32 cpar;
> > > > + u32 cmar;
> > > > +};
> > > > +
> > > > +struct loongson2_cmc_dma_sg_req {
> > > > + u32 len;
> > > > + struct loongson2_cmc_dma_chan_reg chan_reg;
> > > > +};
> > > > +
> > > > +struct loongson2_cmc_dma_desc {
> > > > + struct virt_dma_desc vdesc;
> > > > + bool cyclic;
> > > > + u32 num_sgs;
> > > > + struct loongson2_cmc_dma_sg_req sg_req[] __counted_by(num_sgs);
> > > > +};
> > > > +
> > > > +struct loongson2_cmc_dma_chan {
> > > > + struct virt_dma_chan vchan;
> > > > + struct dma_slave_config dma_sconfig;
> > > > + struct loongson2_cmc_dma_desc *desc;
> > > > + u32 id;
> > > > + u32 irq;
> > > > + u32 next_sg;
> > > > + struct loongson2_cmc_dma_chan_reg chan_reg;
> > > > +};
> > > > +
> > > > +struct loongson2_cmc_dma_config {
> > > > + u32 max_channels;
> > > > + u32 chan_reg_offset;
> > > > +};
> > > > +
> > > > +struct loongson2_cmc_dma_dev {
> > > > + struct dma_device ddev;
> > > > + struct clk *dma_clk;
> > > > + void __iomem *base;
> > > > + u32 nr_channels;
> > > > + u32 chan_reg_offset;
> > > > + struct loongson2_cmc_dma_chan chan[] __counted_by(nr_channels);
> > > > +};
> > > > +
> > > > +static const struct loongson2_cmc_dma_config ls2k0300_cmc_dma_config = {
> > > > + .max_channels = 8,
> > > > + .chan_reg_offset = 0x14,
> > > > +};
> > > > +
> > > > +static const struct loongson2_cmc_dma_config ls2k3000_cmc_dma_config = {
> > > > + .max_channels = 4,
> > > > + .chan_reg_offset = 0x18,
> > > > +};
> > > > +
> > > > +static struct loongson2_cmc_dma_dev *lmdma_get_dev(struct loongson2_cmc_dma_chan *lchan)
> > > > +{
> > > > + return container_of(lchan->vchan.chan.device, struct loongson2_cmc_dma_dev, ddev);
> > > > +}
> > > > +
> > > > +static struct loongson2_cmc_dma_chan *to_lmdma_chan(struct dma_chan *chan)
> > > > +{
> > > > + return container_of(chan, struct loongson2_cmc_dma_chan, vchan.chan);
> > > > +}
> > > > +
> > > > +static struct loongson2_cmc_dma_desc *to_lmdma_desc(struct virt_dma_desc *vdesc)
> > > > +{
> > > > + return container_of(vdesc, struct loongson2_cmc_dma_desc, vdesc);
> > > > +}
> > > > +
> > > > +static struct device *chan2dev(struct loongson2_cmc_dma_chan *lchan)
> > > > +{
> > > > + return &lchan->vchan.chan.dev->device;
> > > > +}
> > > > +
> > > > +static u32 loongson2_cmc_dma_read(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id)
> > > > +{
> > > > + return readl(lddev->base + (reg + lddev->chan_reg_offset * id));
> > > > +}
> > > > +
> > > > +static void loongson2_cmc_dma_write(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id, u32 val)
> > > > +{
> > > > + writel(val, lddev->base + (reg + lddev->chan_reg_offset * id));
> > > > +}
> > > > +
> > > > +static int loongson2_cmc_dma_get_width(struct loongson2_cmc_dma_chan *lchan,
> > > > + enum dma_slave_buswidth width)
> > > > +{
> > > > + switch (width) {
> > > > + case DMA_SLAVE_BUSWIDTH_1_BYTE:
> > > > + return LOONGSON2_CMCDMA_BYTE;
> > > > + case DMA_SLAVE_BUSWIDTH_2_BYTES:
> > > > + return LOONGSON2_CMCDMA_HALF_WORD;
> > > > + case DMA_SLAVE_BUSWIDTH_4_BYTES:
> > > > + return LOONGSON2_CMCDMA_WORD;
> > >
> > > is ffs() helper in case your hardware support more buswidth in future?
> >
> > It seems there's no need for us to do this.
> > The data width setting bit in the DMA channel configuration register
> > only has two bits (LOONGSON2_CMCDMA_CCR_PSIZE_MASK). The bitmask
> > values are: 8-bit/16-bit/32-bit/reserved.
>
> Sorry, I checked again, the ffs() helper can make the code cleaner:
>
> static int loongson2_cmc_dma_get_width(enum dma_slave_buswidth width)
> {
> switch (width) {
> case DMA_SLAVE_BUSWIDTH_1_BYTE:
> case DMA_SLAVE_BUSWIDTH_2_BYTES:
> case DMA_SLAVE_BUSWIDTH_4_BYTES:
> return ffs(width) - 1;
> default:
> return -EINVAL;
> }
> }
if (width >= DMA_SLAVE_BUSWIDTH_4_BYTES)
return -EINVAL;
return ffs(width) - 1;
>
> And the enum loongson2_cmc_dma_width{ } can be dropped.
>
> > >
> > > > + default:
> > > > + dev_err(chan2dev(lchan), "Dma bus width not supported\n");
> > > > + return -EINVAL;
> > > > + }
> > > > +}
> > > > +
...
> > > > + if (status & LOONGSON2_CMCDMA_TCI)
> > > > + loongson2_cmc_dma_handle_chan_done(lchan);
> > > > +
> > > > + if (status & LOONGSON2_CMCDMA_HTI)
> > > > + loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
> > > > +
> > > > + if (status & LOONGSON2_CMCDMA_TEI)
> > > > + dev_err(chan2dev(lchan), "DMA Transform Error\n");
> > > > +
> > > > + loongson2_cmc_dma_irq_clear(lchan, status);
> > >
> > > irq clear should before loongson2_cmc_dma_handle_chan_done() incase you
> > > missed irq, if loongson2_cmc_dma_handle_chan_done() trigger new irq before
> > > your call irq_cler().
>
> Yes, this part should be refracted, how about the following code:
>
> spin_lock(&lchan->vchan.lock);
>
> ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
> ists = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_ISR, 0);
> status = (ists >> (4 * lchan->id)) & LOONGSON2_CMCDMA_MASKI;
>
> if (status & LOONGSON2_CMCDMA_TCI) {
> loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_TCI);
if status is w1c, you can clean it unconditional.
> if (ccr & LOONGSON2_CMCDMA_CCR_TCIE)
Not sure your hardware, generally irq status register will not set if
enable bit have not set.
> loongson2_cmc_dma_handle_chan_done(lchan);
> status &= ~LOONGSON2_CMCDMA_TCI;
> }
>
> if (status & LOONGSON2_CMCDMA_HTI) {
> loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
> status &= ~LOONGSON2_CMCDMA_HTI;
> }
>
> if (status & LOONGSON2_CMCDMA_TEI) {
> loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
> dev_err(chan2dev(lchan), "DMA Transform Error\n");
> if (!(ccr & LOONGSON2_CMCDMA_CCR_EN))
> dev_err(chan2dev(lchan), "chan disabled by HW\n");
> }
>
> spin_unlock(&lchan->vchan.lock);
>
> > >
> > > > +
> > > > + spin_unlock(&lchan->vchan.lock);
> > > > +
> > > > + return IRQ_HANDLED;
> > > > +}
> > > > +
> > > > +static void loongson2_cmc_dma_issue_pending(struct dma_chan *chan)
> > > > +{
> > > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > + unsigned long flags;
> > > > +
> > > > + spin_lock_irqsave(&lchan->vchan.lock, flags);
> > > > + if (vchan_issue_pending(&lchan->vchan) && !lchan->desc) {
> > > > + dev_dbg(chan2dev(lchan), "vchan %pK: issued\n", &lchan->vchan);
> > > > + loongson2_cmc_dma_start_transfer(lchan);
> > > > + }
> > > > + spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> > > > +}
> > > > +
...
> > > > +static struct dma_async_tx_descriptor *
> > > > +loongson2_cmc_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, u32 sg_len,
> > > > + enum dma_transfer_direction direction,
> > > > + unsigned long flags, void *context)
> > > > +{
> > > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > + struct loongson2_cmc_dma_desc *desc;
> > > > + enum dma_slave_buswidth buswidth;
> > > > + struct scatterlist *sg;
> > > > + u32 num_items, i;
> > > > + int ret;
> > > > +
> > > > + desc = kzalloc(struct_size(desc, sg_req, sg_len), GFP_NOWAIT);
> > > > + if (!desc)
> > > > + return NULL;
> > > > +
> > > > + for_each_sg(sgl, sg, sg_len, i) {
> > > > + ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, sg_dma_len(sg));
> > > > + if (ret)
> > > > + return NULL;
> > > > +
> > > > + desc->sg_req[i].len = sg_dma_len(sg);
> > > > +
> > > > + num_items = desc->sg_req[i].len / buswidth;
> > > > + if (num_items >= SZ_64K) {
> > > > + dev_err(chan2dev(lchan), "Number of items not supported\n");
> > > > + kfree(desc);
> > > > + return NULL;
> > >
> > > if use sg_nents_for_dma(), you can use multi sg to trasfer more than 64K
> > > data.
> >
> > Sorry, are you referring to sg_nents_for_len()?
> > 64K is a hardware limitation of the controller, so it seems impossible
> > to resolve it using that function, right?
you can use multi sg_req to implement it, which max 64K.
sg_reqp[i + 0] -> 1st 64k
sg_reqp[i + 1] -> 2nd 64k
...
Only need allocate more at kzalloc with sg_nents_for_len(), in stead of
sg_len.
Frank
> >
> > >
> > > > + }
> > > > + desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr;
> > > > + desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar;
> > > > + desc->sg_req[i].chan_reg.cmar = sg_dma_address(sg);
> > > > + desc->sg_req[i].chan_reg.cndtr = num_items;
> > > > + }
> > > > +
> > > > + desc->num_sgs = sg_len;
> > > > + desc->cyclic = false;
> > > > +
> > > > + return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags);
> > > > +}
> > > > +
> > > > +static struct dma_async_tx_descriptor *
> > > > +loongson2_cmc_dma_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
> > > > + size_t period_len, enum dma_transfer_direction direction,
> > > > + unsigned long flags)
> > > > +{
> > > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > + struct loongson2_cmc_dma_desc *desc;
> > > > + enum dma_slave_buswidth buswidth;
> > > > + u32 num_periods, num_items, i;
> > > > + int ret;
> > > > +
> > > > + if (unlikely(buf_len % period_len))
> > > > + return NULL;
> > > > +
> > > > + ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, period_len);
> > > > + if (ret)
> > > > + return NULL;
> > > > +
> > > > + num_items = period_len / buswidth;
> > > > + if (num_items >= SZ_64K) {
> > > > + dev_err(chan2dev(lchan), "Number of items not supported\n");
> > > > + return NULL;
> > > > + }
> > > > +
> > > > + /* Enable Circular mode */
> > > > + if (buf_len == period_len)
> > > > + lchan->chan_reg.ccr |= LOONGSON2_CMCDMA_CCR_CIRC;
> > > > +
> > > > + num_periods = buf_len / period_len;
> > > > + desc = kzalloc(struct_size(desc, sg_req, num_periods), GFP_NOWAIT);
> > > > + if (!desc)
> > > > + return NULL;
> > > > +
> > > > + for (i = 0; i < num_periods; i++) {
> > > > + desc->sg_req[i].len = period_len;
> > > > + desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr;
> > > > + desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar;
> > > > + desc->sg_req[i].chan_reg.cmar = buf_addr;
> > > > + desc->sg_req[i].chan_reg.cndtr = num_items;
> > > > + buf_addr += period_len;
> > > > + }
> > > > +
> > > > + desc->num_sgs = num_periods;
> > > > + desc->cyclic = true;
> > > > +
> > > > + return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags);
> > > > +}
> > > > +
> > > > +static size_t loongson2_cmc_dma_desc_residue(struct loongson2_cmc_dma_chan *lchan,
> > > > + struct loongson2_cmc_dma_desc *desc, u32 next_sg)
> > > > +{
> > > > + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > > > + u32 residue, width, ndtr, ccr, i;
> > > > +
> > > > + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
> > > > + width = FIELD_GET(LOONGSON2_CMCDMA_CCR_PSIZE_MASK, ccr);
> > > > +
> > > > + ndtr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CNDTR, lchan->id);
> > > > + residue = ndtr << width;
> > > > +
> > > > + if (lchan->desc->cyclic && next_sg == 0)
> > > > + return residue;
> > > > +
> > > > + for (i = next_sg; i < desc->num_sgs; i++)
> > > > + residue += desc->sg_req[i].len;
> > > > +
> > > > + return residue;
> > > > +}
> > > > +
> > > > +static enum dma_status loongson2_cmc_dma_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
> > > > + struct dma_tx_state *state)
> > > > +{
> > > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > + struct virt_dma_desc *vdesc;
> > > > + enum dma_status status;
> > > > + unsigned long flags;
> > > > +
> > > > + status = dma_cookie_status(chan, cookie, state);
> > > > + if (status == DMA_COMPLETE || !state)
> > > > + return status;
> > > > +
> > > > + spin_lock_irqsave(&lchan->vchan.lock, flags);
> > > > + vdesc = vchan_find_desc(&lchan->vchan, cookie);
> > > > + if (lchan->desc && cookie == lchan->desc->vdesc.tx.cookie)
> > > > + state->residue = loongson2_cmc_dma_desc_residue(lchan, lchan->desc, lchan->next_sg);
> > > > + else if (vdesc)
> > > > + state->residue = loongson2_cmc_dma_desc_residue(lchan, to_lmdma_desc(vdesc), 0);
> > > > +
> > > > + spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> > > > +
> > > > + return status;
> > > > +}
> > > > +
> > > > +static void loongson2_cmc_dma_free_chan_resources(struct dma_chan *chan)
> > > > +{
> > > > + vchan_free_chan_resources(to_virt_chan(chan));
> > > > +}
> > > > +
> > > > +static void loongson2_cmc_dma_desc_free(struct virt_dma_desc *vdesc)
> > > > +{
> > > > + kfree(to_lmdma_desc(vdesc));
> > > > +}
> > > > +
> > > > +static bool loongson2_cmc_dma_acpi_filter(struct dma_chan *chan, void *param)
> > > > +{
> > > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > + struct acpi_dma_spec *dma_spec = param;
> > > > +
> > > > + memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg));
> > > > + lchan->chan_reg.ccr = dma_spec->chan_id & LOONGSON2_CMCDMA_STREAM_MASK;
> > > > +
> > > > + return true;
> > > > +}
> > > > +
> > > > +static int loongson2_cmc_dma_acpi_controller_register(struct loongson2_cmc_dma_dev *lddev)
> > > > +{
> > > > + struct device *dev = lddev->ddev.dev;
> > > > + struct acpi_dma_filter_info *info;
> > > > + int ret;
> > > > +
> > > > + if (!has_acpi_companion(dev))
> > > > + return 0;
> > > > +
> > > > + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
> > > > + if (!info)
> > > > + return -ENOMEM;
> > > > +
> > > > + dma_cap_zero(info->dma_cap);
> > > > + info->dma_cap = lddev->ddev.cap_mask;
> > > > + info->filter_fn = loongson2_cmc_dma_acpi_filter;
> > > > +
> > > > + ret = devm_acpi_dma_controller_register(dev, acpi_dma_simple_xlate, info);
> > > > + if (ret)
> > > > + dev_err(dev, "could not register acpi_dma_controller\n");
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static struct dma_chan *loongson2_cmc_dma_of_xlate(struct of_phandle_args *dma_spec,
> > > > + struct of_dma *ofdma)
> > > > +{
> > > > + struct loongson2_cmc_dma_dev *lddev = ofdma->of_dma_data;
> > > > + struct device *dev = lddev->ddev.dev;
> > > > + struct loongson2_cmc_dma_chan *lchan;
> > > > + struct dma_chan *chan;
> > > > +
> > > > + if (dma_spec->args_count < 2)
> > > > + return NULL;
> > > > +
> > > > + if (dma_spec->args[0] >= lddev->nr_channels) {
> > > > + dev_err(dev, "Invalid channel id\n");
> > > > + return NULL;
> > > > + }
> > > > +
> > > > + lchan = &lddev->chan[dma_spec->args[0]];
> > > > + chan = dma_get_slave_channel(&lchan->vchan.chan);
> > > > + if (!chan) {
> > > > + dev_err(dev, "No more channels available\n");
> > > > + return NULL;
> > > > + }
> > > > +
> > > > + memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg));
> > > > + lchan->chan_reg.ccr = dma_spec->args[1] & LOONGSON2_CMCDMA_STREAM_MASK;
> > > > +
> > > > + return chan;
> > > > +}
> > > > +
> > > > +static int loongson2_cmc_dma_of_controller_register(struct loongson2_cmc_dma_dev *lddev)
> > > > +{
> > > > + struct device *dev = lddev->ddev.dev;
> > > > + int ret;
> > > > +
> > > > + if (!dev->of_node)
> > > > + return 0;
> > > > +
> > > > + ret = of_dma_controller_register(dev->of_node, loongson2_cmc_dma_of_xlate, lddev);
> > > > + if (ret)
> > > > + dev_err(dev, "could not register of_dma_controller\n");
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static int loongson2_cmc_dma_probe(struct platform_device *pdev)
> > > > +{
> > > > + const struct loongson2_cmc_dma_config *config;
> > > > + struct loongson2_cmc_dma_chan *lchan;
> > > > + struct loongson2_cmc_dma_dev *lddev;
> > > > + struct device *dev = &pdev->dev;
> > > > + struct dma_device *ddev;
> > > > + u32 nr_chans, i;
> > > > + int ret;
> > > > +
> > > > + config = (const struct loongson2_cmc_dma_config *)device_get_match_data(dev);
> > > > + if (!config)
> > > > + return -EINVAL;
> > > > +
> > > > + ret = device_property_read_u32(dev, "dma-channels", &nr_chans);
> > > > + if (ret || nr_chans > config->max_channels) {
> > > > + dev_err(dev, "missing or invalid dma-channels property\n");
> > > > + nr_chans = config->max_channels;
> > > > + }
> > > > +
> > > > + lddev = devm_kzalloc(dev, struct_size(lddev, chan, nr_chans), GFP_KERNEL);
> > > > + if (!lddev)
> > > > + return -ENOMEM;
> > > > +
> > > > + lddev->base = devm_platform_ioremap_resource(pdev, 0);
> > > > + if (IS_ERR(lddev->base))
> > > > + return PTR_ERR(lddev->base);
> > > > +
> > > > + platform_set_drvdata(pdev, lddev);
> > > > + lddev->nr_channels = nr_chans;
> > > > + lddev->chan_reg_offset = config->chan_reg_offset;
> > > > +
> > > > + lddev->dma_clk = devm_clk_get_optional_enabled(dev, NULL);
> > > > + if (IS_ERR(lddev->dma_clk))
> > > > + return dev_err_probe(dev, PTR_ERR(lddev->dma_clk), "Failed to get dma clock\n");
> > > > +
> > > > + ddev = &lddev->ddev;
> > > > + ddev->dev = dev;
> > > > +
> > > > + dma_cap_zero(ddev->cap_mask);
> > > > + dma_cap_set(DMA_SLAVE, ddev->cap_mask);
> > > > + dma_cap_set(DMA_PRIVATE, ddev->cap_mask);
> > > > + dma_cap_set(DMA_CYCLIC, ddev->cap_mask);
> > > > +
> > > > + ddev->device_free_chan_resources = loongson2_cmc_dma_free_chan_resources;
> > > > + ddev->device_config = loongson2_cmc_dma_slave_config;
> > > > + ddev->device_prep_slave_sg = loongson2_cmc_dma_prep_slave_sg;
> > > > + ddev->device_prep_dma_cyclic = loongson2_cmc_dma_prep_dma_cyclic;
> > > > + ddev->device_issue_pending = loongson2_cmc_dma_issue_pending;
> > > > + ddev->device_synchronize = loongson2_cmc_dma_synchronize;
> > > > + ddev->device_tx_status = loongson2_cmc_dma_tx_status;
> > > > + ddev->device_terminate_all = loongson2_cmc_dma_terminate_all;
> > > > +
> > > > + ddev->src_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS;
> > > > + ddev->dst_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS;
> > > > + ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
> > > > + INIT_LIST_HEAD(&ddev->channels);
> > >
> > > where use this 'channels' ?
> >
> > It will be used by global functions such as `dma_async_device_register()`.
Okay, supposed it sould be done in dma_async_device_register().
Frank
> > >
> > > Frank
> > > > +
> > > > + for (i = 0; i < nr_chans; i++) {
> > > > + lchan = &lddev->chan[i];
> > > > +
> > > > + lchan->id = i;
> > > > + lchan->vchan.desc_free = loongson2_cmc_dma_desc_free;
> > > > + vchan_init(&lchan->vchan, ddev);
> > > > + }
> > > > +
> > > > + ret = dmaenginem_async_device_register(ddev);
> > > > + if (ret)
> > > > + return ret;
> > > > +
> > > > + for (i = 0; i < nr_chans; i++) {
> > > > + lchan = &lddev->chan[i];
> > > > +
> > > > + lchan->irq = platform_get_irq(pdev, i);
> > > > + if (lchan->irq < 0)
> > > > + return lchan->irq;
> > > > +
> > > > + ret = devm_request_irq(dev, lchan->irq, loongson2_cmc_dma_chan_irq, IRQF_SHARED,
> > > > + dev_name(chan2dev(lchan)), lchan);
> > > > + if (ret)
> > > > + return ret;
> > > > + }
> > > > +
> > > > + ret = loongson2_cmc_dma_acpi_controller_register(lddev);
> > > > + if (ret)
> > > > + return ret;
> > > > +
> > > > + return loongson2_cmc_dma_of_controller_register(lddev);
> > > > +}
> > > > +
> > > > +static void loongson2_cmc_dma_remove(struct platform_device *pdev)
> > > > +{
> > > > + of_dma_controller_free(pdev->dev.of_node);
> > > > +}
> > > > +
> > > > +static const struct of_device_id loongson2_cmc_dma_of_match[] = {
> > > > + { .compatible = "loongson,ls2k0300-dma", .data = &ls2k0300_cmc_dma_config },
> > > > + { .compatible = "loongson,ls2k3000-dma", .data = &ls2k3000_cmc_dma_config },
> > > > + { /* sentinel */ }
> > > > +};
> > > > +MODULE_DEVICE_TABLE(of, loongson2_cmc_dma_of_match);
> > > > +
> > > > +static const struct acpi_device_id loongson2_cmc_dma_acpi_match[] = {
> > > > + { "LOON0014", .driver_data = (kernel_ulong_t)&ls2k3000_cmc_dma_config },
> > > > + { /* sentinel */ }
> > > > +};
> > > > +MODULE_DEVICE_TABLE(acpi, loongson2_cmc_dma_acpi_match);
> > > > +
> > > > +static struct platform_driver loongson2_cmc_dma_driver = {
> > > > + .driver = {
> > > > + .name = "loongson2-apb-cmc-dma",
> > > > + .of_match_table = loongson2_cmc_dma_of_match,
> > > > + .acpi_match_table = loongson2_cmc_dma_acpi_match,
> > > > + },
> > > > + .probe = loongson2_cmc_dma_probe,
> > > > + .remove = loongson2_cmc_dma_remove,
> > > > +};
> > > > +module_platform_driver(loongson2_cmc_dma_driver);
> > > > +
> > > > +MODULE_DESCRIPTION("Looongson-2 Multi-Channel DMA Controller driver");
> > > > +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> > > > +MODULE_LICENSE("GPL");
> > > > --
> > > > 2.52.0
> > > >
> >
> > --
> > Thanks.
> > Binbin
>
> --
> Thanks.
> Binbin
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/4] dmaengine: loongson: New driver for the Loongson Multi-Channel DMA controller
2026-02-10 15:27 ` Frank Li
@ 2026-02-24 12:59 ` Binbin Zhou
0 siblings, 0 replies; 16+ messages in thread
From: Binbin Zhou @ 2026-02-24 12:59 UTC (permalink / raw)
To: Frank Li
Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Vinod Koul, dmaengine, Xiaochuang Mao, Huacai Chen,
Xuerui Wang, loongarch, devicetree, Keguang Zhang, linux-mips,
jeffbai
Hi Frank:
Sorry for the late reply.
On Tue, Feb 10, 2026 at 11:27 PM Frank Li <Frank.li@nxp.com> wrote:
>
> On Tue, Feb 10, 2026 at 08:02:21PM +0800, Binbin Zhou wrote:
> > Hi Frank:
> >
> > On Tue, Feb 10, 2026 at 3:41 PM Binbin Zhou <zhoubb.aaron@gmail.com> wrote:
> > >
> > > Hi Frank:
> > >
> > > Thanks for your reply.
> > >
> > > On Tue, Feb 10, 2026 at 1:05 AM Frank Li <Frank.li@nxp.com> wrote:
> > > >
> > > > On Mon, Feb 09, 2026 at 11:04:55AM +0800, Binbin Zhou wrote:
> > > > > This DMA controller appears in Loongson-2K0300 and Loongson-2K3000.
> > > > >
> > > > > It is a chain multi-channel controller that enables data transfers from
> > > > > memory to memory, device to memory, and memory to device, as well as
> > > > > channel prioritization configurable through the channel configuration
> > > > > registers.
> > > > >
> > > > > In addition, there are slight differences between Loongson-2K0300 and
> > > > > Loongson-2K3000, such as channel register offsets and the number of
> > > > > channels.
> > > > >
> > > > > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > > > > ---
> > > > > MAINTAINERS | 1 +
> > > > > drivers/dma/loongson/Kconfig | 10 +
> > > > > drivers/dma/loongson/Makefile | 1 +
> > > > > drivers/dma/loongson/loongson2-apb-cmc-dma.c | 736 +++++++++++++++++++
> > > > > 4 files changed, 748 insertions(+)
> > > > > create mode 100644 drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > > >
> > > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > > index d3cb541aee2a..61a39070d7a0 100644
> > > > > --- a/MAINTAINERS
> > > > > +++ b/MAINTAINERS
> > > > > @@ -14778,6 +14778,7 @@ L: dmaengine@vger.kernel.org
> > > > > S: Maintained
> > > > > F: Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
> > > > > F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
> > > > > +F: drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > > > F: drivers/dma/loongson/loongson2-apb-dma.c
> > > > >
> > > > > LOONGSON LS2X I2C DRIVER
> > > > > diff --git a/drivers/dma/loongson/Kconfig b/drivers/dma/loongson/Kconfig
> > > > > index 9dbdaef5a59f..28b3daeed4e3 100644
> > > > > --- a/drivers/dma/loongson/Kconfig
> > > > > +++ b/drivers/dma/loongson/Kconfig
> > > > > @@ -25,4 +25,14 @@ config LOONGSON2_APB_DMA
> > > > > This DMA controller transfers data from memory to peripheral fifo.
> > > > > It does not support memory to memory data transfer.
> > > > >
> > > > > +config LOONGSON2_APB_CMC_DMA
> > > > > + tristate "Loongson2 Chain Multi-Channel DMA support"
> > > > > + select DMA_ENGINE
> > > > > + select DMA_VIRTUAL_CHANNELS
> > > > > + help
> > > > > + Support for the Loongson Chain Multi-Channel DMA controller driver.
> > > > > + It is discovered on the Loongson-2K chip (Loongson-2K0300/Loongson-2K3000),
> > > > > + which has 4/8 channels internally, enabling bidirectional data transfer
> > > > > + between devices and memory.
> > > > > +
> > > > > endif
> > > > > diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
> > > > > index 6cdd08065e92..48c19781e729 100644
> > > > > --- a/drivers/dma/loongson/Makefile
> > > > > +++ b/drivers/dma/loongson/Makefile
> > > > > @@ -1,3 +1,4 @@
> > > > > # SPDX-License-Identifier: GPL-2.0-only
> > > > > obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> > > > > obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> > > > > +obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
> > > > > diff --git a/drivers/dma/loongson/loongson2-apb-cmc-dma.c b/drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > > > new file mode 100644
> > > > > index 000000000000..f598ad095686
> > > > > --- /dev/null
> > > > > +++ b/drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > > > @@ -0,0 +1,736 @@
> > > > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > > > +/*
> > > > > + * Looongson-2 Multi-Channel DMA Controller driver
> > > > > + *
> > > > > + * Copyright (C) 2024-2026 Loongson Technology Corporation Limited
> > > > > + */
> > > > > +
> > > > > +#include <linux/acpi.h>
> > > > > +#include <linux/acpi_dma.h>
> > > > > +#include <linux/bitfield.h>
> > > > > +#include <linux/clk.h>
> > > > > +#include <linux/dma-mapping.h>
> > > > > +#include <linux/dmapool.h>
> > > > > +#include <linux/interrupt.h>
> > > > > +#include <linux/io.h>
> > > > > +#include <linux/module.h>
> > > > > +#include <linux/of.h>
> > > > > +#include <linux/of_dma.h>
> > > > > +#include <linux/platform_device.h>
> > > > > +#include <linux/slab.h>
> > > > > +
> > > > > +#include "../dmaengine.h"
> > > > > +#include "../virt-dma.h"
> > > > > +
> > > > > +#define LOONGSON2_CMCDMA_ISR 0x0 /* DMA Interrupt Status Register */
> > > > > +#define LOONGSON2_CMCDMA_IFCR 0x4 /* DMA Interrupt Flag Clear Register */
> > > > > +#define LOONGSON2_CMCDMA_CCR 0x8 /* DMA Channel Configuration Register */
> > > > > +#define LOONGSON2_CMCDMA_CNDTR 0xc /* DMA Channel Transmit Count Register */
> > > > > +#define LOONGSON2_CMCDMA_CPAR 0x10 /* DMA Channel Peripheral Address Register */
> > > > > +#define LOONGSON2_CMCDMA_CMAR 0x14 /* DMA Channel Memory Address Register */
> > > > > +
> > > > > +/* Bitfields of DMA interrupt status register */
> > > > > +#define LOONGSON2_CMCDMA_TCI BIT(1) /* Transfer Complete Interrupt */
> > > > > +#define LOONGSON2_CMCDMA_HTI BIT(2) /* Half Transfer Interrupt */
> > > > > +#define LOONGSON2_CMCDMA_TEI BIT(3) /* Transfer Error Interrupt */
> > > > > +
> > > > > +#define LOONGSON2_CMCDMA_MASKI \
> > > > > + (LOONGSON2_CMCDMA_TCI | LOONGSON2_CMCDMA_HTI | LOONGSON2_CMCDMA_TEI)
> > > > > +
> > > > > +/* Bitfields of DMA channel x Configuration Register */
> > > > > +#define LOONGSON2_CMCDMA_CCR_EN BIT(0) /* Stream Enable */
> > > > > +#define LOONGSON2_CMCDMA_CCR_TCIE BIT(1) /* Transfer Complete Interrupt Enable */
> > > > > +#define LOONGSON2_CMCDMA_CCR_HTIE BIT(2) /* Half Transfer Complete Interrupt Enable */
> > > > > +#define LOONGSON2_CMCDMA_CCR_TEIE BIT(3) /* Transfer Error Interrupt Enable */
> > > > > +#define LOONGSON2_CMCDMA_CCR_DIR BIT(4) /* Data Transfer Direction */
> > > > > +#define LOONGSON2_CMCDMA_CCR_CIRC BIT(5) /* Circular mode */
> > > > > +#define LOONGSON2_CMCDMA_CCR_PINC BIT(6) /* Peripheral increment mode */
> > > > > +#define LOONGSON2_CMCDMA_CCR_MINC BIT(7) /* Memory increment mode */
> > > > > +#define LOONGSON2_CMCDMA_CCR_PSIZE_MASK GENMASK(9, 8)
> > > > > +#define LOONGSON2_CMCDMA_CCR_MSIZE_MASK GENMASK(11, 10)
> > > > > +#define LOONGSON2_CMCDMA_CCR_PL_MASK GENMASK(13, 12)
> > > > > +#define LOONGSON2_CMCDMA_CCR_M2M BIT(14)
> > > > > +
> > > > > +#define LOONGSON2_CMCDMA_CCR_CFG_MASK \
> > > > > + (LOONGSON2_CMCDMA_CCR_PINC | LOONGSON2_CMCDMA_CCR_MINC | LOONGSON2_CMCDMA_CCR_PL_MASK)
> > > > > +
> > > > > +#define LOONGSON2_CMCDMA_CCR_IRQ_MASK \
> > > > > + (LOONGSON2_CMCDMA_CCR_TCIE | LOONGSON2_CMCDMA_CCR_HTIE | LOONGSON2_CMCDMA_CCR_TEIE)
> > > > > +
> > > > > +#define LOONGSON2_CMCDMA_STREAM_MASK \
> > > > > + (LOONGSON2_CMCDMA_CCR_CFG_MASK | LOONGSON2_CMCDMA_CCR_IRQ_MASK)
> > > > > +
> > > > > +#define LOONGSON2_CMCDMA_BUSWIDTHS (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \
> > > > > + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \
> > > > > + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES))
> > > > > +
> > > > > +enum loongson2_cmc_dma_width {
> > > > > + LOONGSON2_CMCDMA_BYTE,
> > > > > + LOONGSON2_CMCDMA_HALF_WORD,
> > > > > + LOONGSON2_CMCDMA_WORD,
> > > > > +};
> > > > > +
> > > > > +struct loongson2_cmc_dma_chan_reg {
> > > > > + u32 ccr;
> > > > > + u32 cndtr;
> > > > > + u32 cpar;
> > > > > + u32 cmar;
> > > > > +};
> > > > > +
> > > > > +struct loongson2_cmc_dma_sg_req {
> > > > > + u32 len;
> > > > > + struct loongson2_cmc_dma_chan_reg chan_reg;
> > > > > +};
> > > > > +
> > > > > +struct loongson2_cmc_dma_desc {
> > > > > + struct virt_dma_desc vdesc;
> > > > > + bool cyclic;
> > > > > + u32 num_sgs;
> > > > > + struct loongson2_cmc_dma_sg_req sg_req[] __counted_by(num_sgs);
> > > > > +};
> > > > > +
> > > > > +struct loongson2_cmc_dma_chan {
> > > > > + struct virt_dma_chan vchan;
> > > > > + struct dma_slave_config dma_sconfig;
> > > > > + struct loongson2_cmc_dma_desc *desc;
> > > > > + u32 id;
> > > > > + u32 irq;
> > > > > + u32 next_sg;
> > > > > + struct loongson2_cmc_dma_chan_reg chan_reg;
> > > > > +};
> > > > > +
> > > > > +struct loongson2_cmc_dma_config {
> > > > > + u32 max_channels;
> > > > > + u32 chan_reg_offset;
> > > > > +};
> > > > > +
> > > > > +struct loongson2_cmc_dma_dev {
> > > > > + struct dma_device ddev;
> > > > > + struct clk *dma_clk;
> > > > > + void __iomem *base;
> > > > > + u32 nr_channels;
> > > > > + u32 chan_reg_offset;
> > > > > + struct loongson2_cmc_dma_chan chan[] __counted_by(nr_channels);
> > > > > +};
> > > > > +
> > > > > +static const struct loongson2_cmc_dma_config ls2k0300_cmc_dma_config = {
> > > > > + .max_channels = 8,
> > > > > + .chan_reg_offset = 0x14,
> > > > > +};
> > > > > +
> > > > > +static const struct loongson2_cmc_dma_config ls2k3000_cmc_dma_config = {
> > > > > + .max_channels = 4,
> > > > > + .chan_reg_offset = 0x18,
> > > > > +};
> > > > > +
> > > > > +static struct loongson2_cmc_dma_dev *lmdma_get_dev(struct loongson2_cmc_dma_chan *lchan)
> > > > > +{
> > > > > + return container_of(lchan->vchan.chan.device, struct loongson2_cmc_dma_dev, ddev);
> > > > > +}
> > > > > +
> > > > > +static struct loongson2_cmc_dma_chan *to_lmdma_chan(struct dma_chan *chan)
> > > > > +{
> > > > > + return container_of(chan, struct loongson2_cmc_dma_chan, vchan.chan);
> > > > > +}
> > > > > +
> > > > > +static struct loongson2_cmc_dma_desc *to_lmdma_desc(struct virt_dma_desc *vdesc)
> > > > > +{
> > > > > + return container_of(vdesc, struct loongson2_cmc_dma_desc, vdesc);
> > > > > +}
> > > > > +
> > > > > +static struct device *chan2dev(struct loongson2_cmc_dma_chan *lchan)
> > > > > +{
> > > > > + return &lchan->vchan.chan.dev->device;
> > > > > +}
> > > > > +
> > > > > +static u32 loongson2_cmc_dma_read(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id)
> > > > > +{
> > > > > + return readl(lddev->base + (reg + lddev->chan_reg_offset * id));
> > > > > +}
> > > > > +
> > > > > +static void loongson2_cmc_dma_write(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id, u32 val)
> > > > > +{
> > > > > + writel(val, lddev->base + (reg + lddev->chan_reg_offset * id));
> > > > > +}
> > > > > +
> > > > > +static int loongson2_cmc_dma_get_width(struct loongson2_cmc_dma_chan *lchan,
> > > > > + enum dma_slave_buswidth width)
> > > > > +{
> > > > > + switch (width) {
> > > > > + case DMA_SLAVE_BUSWIDTH_1_BYTE:
> > > > > + return LOONGSON2_CMCDMA_BYTE;
> > > > > + case DMA_SLAVE_BUSWIDTH_2_BYTES:
> > > > > + return LOONGSON2_CMCDMA_HALF_WORD;
> > > > > + case DMA_SLAVE_BUSWIDTH_4_BYTES:
> > > > > + return LOONGSON2_CMCDMA_WORD;
> > > >
> > > > is ffs() helper in case your hardware support more buswidth in future?
> > >
> > > It seems there's no need for us to do this.
> > > The data width setting bit in the DMA channel configuration register
> > > only has two bits (LOONGSON2_CMCDMA_CCR_PSIZE_MASK). The bitmask
> > > values are: 8-bit/16-bit/32-bit/reserved.
> >
> > Sorry, I checked again, the ffs() helper can make the code cleaner:
> >
> > static int loongson2_cmc_dma_get_width(enum dma_slave_buswidth width)
> > {
> > switch (width) {
> > case DMA_SLAVE_BUSWIDTH_1_BYTE:
> > case DMA_SLAVE_BUSWIDTH_2_BYTES:
> > case DMA_SLAVE_BUSWIDTH_4_BYTES:
> > return ffs(width) - 1;
> > default:
> > return -EINVAL;
> > }
> > }
>
> if (width >= DMA_SLAVE_BUSWIDTH_4_BYTES)
> return -EINVAL;
>
> return ffs(width) - 1;
Yes, this looks better.
>
> >
> > And the enum loongson2_cmc_dma_width{ } can be dropped.
> >
> > > >
> > > > > + default:
> > > > > + dev_err(chan2dev(lchan), "Dma bus width not supported\n");
> > > > > + return -EINVAL;
> > > > > + }
> > > > > +}
> > > > > +
> ...
> > > > > + if (status & LOONGSON2_CMCDMA_TCI)
> > > > > + loongson2_cmc_dma_handle_chan_done(lchan);
> > > > > +
> > > > > + if (status & LOONGSON2_CMCDMA_HTI)
> > > > > + loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
> > > > > +
> > > > > + if (status & LOONGSON2_CMCDMA_TEI)
> > > > > + dev_err(chan2dev(lchan), "DMA Transform Error\n");
> > > > > +
> > > > > + loongson2_cmc_dma_irq_clear(lchan, status);
> > > >
> > > > irq clear should before loongson2_cmc_dma_handle_chan_done() incase you
> > > > missed irq, if loongson2_cmc_dma_handle_chan_done() trigger new irq before
> > > > your call irq_cler().
> >
> > Yes, this part should be refracted, how about the following code:
> >
> > spin_lock(&lchan->vchan.lock);
> >
> > ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
> > ists = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_ISR, 0);
> > status = (ists >> (4 * lchan->id)) & LOONGSON2_CMCDMA_MASKI;
> >
> > if (status & LOONGSON2_CMCDMA_TCI) {
> > loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_TCI);
>
> if status is w1c, you can clean it unconditional.
Emm, it can be moved outside.
>
> > if (ccr & LOONGSON2_CMCDMA_CCR_TCIE)
>
> Not sure your hardware, generally irq status register will not set if
> enable bit have not set.
>
> > loongson2_cmc_dma_handle_chan_done(lchan);
> > status &= ~LOONGSON2_CMCDMA_TCI;
> > }
> >
> > if (status & LOONGSON2_CMCDMA_HTI) {
> > loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
> > status &= ~LOONGSON2_CMCDMA_HTI;
> > }
> >
> > if (status & LOONGSON2_CMCDMA_TEI) {
> > loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
> > dev_err(chan2dev(lchan), "DMA Transform Error\n");
> > if (!(ccr & LOONGSON2_CMCDMA_CCR_EN))
> > dev_err(chan2dev(lchan), "chan disabled by HW\n");
> > }
> >
> > spin_unlock(&lchan->vchan.lock);
> >
> > > >
> > > > > +
> > > > > + spin_unlock(&lchan->vchan.lock);
> > > > > +
> > > > > + return IRQ_HANDLED;
> > > > > +}
> > > > > +
> > > > > +static void loongson2_cmc_dma_issue_pending(struct dma_chan *chan)
> > > > > +{
> > > > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > > + unsigned long flags;
> > > > > +
> > > > > + spin_lock_irqsave(&lchan->vchan.lock, flags);
> > > > > + if (vchan_issue_pending(&lchan->vchan) && !lchan->desc) {
> > > > > + dev_dbg(chan2dev(lchan), "vchan %pK: issued\n", &lchan->vchan);
> > > > > + loongson2_cmc_dma_start_transfer(lchan);
> > > > > + }
> > > > > + spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> > > > > +}
> > > > > +
> ...
> > > > > +static struct dma_async_tx_descriptor *
> > > > > +loongson2_cmc_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, u32 sg_len,
> > > > > + enum dma_transfer_direction direction,
> > > > > + unsigned long flags, void *context)
> > > > > +{
> > > > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > > + struct loongson2_cmc_dma_desc *desc;
> > > > > + enum dma_slave_buswidth buswidth;
> > > > > + struct scatterlist *sg;
> > > > > + u32 num_items, i;
> > > > > + int ret;
> > > > > +
> > > > > + desc = kzalloc(struct_size(desc, sg_req, sg_len), GFP_NOWAIT);
> > > > > + if (!desc)
> > > > > + return NULL;
> > > > > +
> > > > > + for_each_sg(sgl, sg, sg_len, i) {
> > > > > + ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, sg_dma_len(sg));
> > > > > + if (ret)
> > > > > + return NULL;
> > > > > +
> > > > > + desc->sg_req[i].len = sg_dma_len(sg);
> > > > > +
> > > > > + num_items = desc->sg_req[i].len / buswidth;
> > > > > + if (num_items >= SZ_64K) {
> > > > > + dev_err(chan2dev(lchan), "Number of items not supported\n");
> > > > > + kfree(desc);
> > > > > + return NULL;
> > > >
> > > > if use sg_nents_for_dma(), you can use multi sg to trasfer more than 64K
> > > > data.
> > >
> > > Sorry, are you referring to sg_nents_for_len()?
> > > 64K is a hardware limitation of the controller, so it seems impossible
> > > to resolve it using that function, right?
>
> you can use multi sg_req to implement it, which max 64K.
>
> sg_reqp[i + 0] -> 1st 64k
> sg_reqp[i + 1] -> 2nd 64k
> ...
>
> Only need allocate more at kzalloc with sg_nents_for_len(), in stead of
> sg_len.
I double-checked, and 64K is actually `max_burst`, which represents
the maximum value for a single transmission. It seems unsuitable for
this purpose.
I should set `dma_device->max_burst` to this value and define it as a
macro: LOONSON2_CMCDMA_MAX_DATA_ITEMS.
>
> Frank
>
>
> > >
> > > >
> > > > > + }
> > > > > + desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr;
> > > > > + desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar;
> > > > > + desc->sg_req[i].chan_reg.cmar = sg_dma_address(sg);
> > > > > + desc->sg_req[i].chan_reg.cndtr = num_items;
> > > > > + }
> > > > > +
> > > > > + desc->num_sgs = sg_len;
> > > > > + desc->cyclic = false;
> > > > > +
> > > > > + return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags);
> > > > > +}
> > > > > +
> > > > > +static struct dma_async_tx_descriptor *
> > > > > +loongson2_cmc_dma_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
> > > > > + size_t period_len, enum dma_transfer_direction direction,
> > > > > + unsigned long flags)
> > > > > +{
> > > > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > > + struct loongson2_cmc_dma_desc *desc;
> > > > > + enum dma_slave_buswidth buswidth;
> > > > > + u32 num_periods, num_items, i;
> > > > > + int ret;
> > > > > +
> > > > > + if (unlikely(buf_len % period_len))
> > > > > + return NULL;
> > > > > +
> > > > > + ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, period_len);
> > > > > + if (ret)
> > > > > + return NULL;
> > > > > +
> > > > > + num_items = period_len / buswidth;
> > > > > + if (num_items >= SZ_64K) {
> > > > > + dev_err(chan2dev(lchan), "Number of items not supported\n");
> > > > > + return NULL;
> > > > > + }
> > > > > +
> > > > > + /* Enable Circular mode */
> > > > > + if (buf_len == period_len)
> > > > > + lchan->chan_reg.ccr |= LOONGSON2_CMCDMA_CCR_CIRC;
> > > > > +
> > > > > + num_periods = buf_len / period_len;
> > > > > + desc = kzalloc(struct_size(desc, sg_req, num_periods), GFP_NOWAIT);
> > > > > + if (!desc)
> > > > > + return NULL;
> > > > > +
> > > > > + for (i = 0; i < num_periods; i++) {
> > > > > + desc->sg_req[i].len = period_len;
> > > > > + desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr;
> > > > > + desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar;
> > > > > + desc->sg_req[i].chan_reg.cmar = buf_addr;
> > > > > + desc->sg_req[i].chan_reg.cndtr = num_items;
> > > > > + buf_addr += period_len;
> > > > > + }
> > > > > +
> > > > > + desc->num_sgs = num_periods;
> > > > > + desc->cyclic = true;
> > > > > +
> > > > > + return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags);
> > > > > +}
> > > > > +
> > > > > +static size_t loongson2_cmc_dma_desc_residue(struct loongson2_cmc_dma_chan *lchan,
> > > > > + struct loongson2_cmc_dma_desc *desc, u32 next_sg)
> > > > > +{
> > > > > + struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > > > > + u32 residue, width, ndtr, ccr, i;
> > > > > +
> > > > > + ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
> > > > > + width = FIELD_GET(LOONGSON2_CMCDMA_CCR_PSIZE_MASK, ccr);
> > > > > +
> > > > > + ndtr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CNDTR, lchan->id);
> > > > > + residue = ndtr << width;
> > > > > +
> > > > > + if (lchan->desc->cyclic && next_sg == 0)
> > > > > + return residue;
> > > > > +
> > > > > + for (i = next_sg; i < desc->num_sgs; i++)
> > > > > + residue += desc->sg_req[i].len;
> > > > > +
> > > > > + return residue;
> > > > > +}
> > > > > +
> > > > > +static enum dma_status loongson2_cmc_dma_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
> > > > > + struct dma_tx_state *state)
> > > > > +{
> > > > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > > + struct virt_dma_desc *vdesc;
> > > > > + enum dma_status status;
> > > > > + unsigned long flags;
> > > > > +
> > > > > + status = dma_cookie_status(chan, cookie, state);
> > > > > + if (status == DMA_COMPLETE || !state)
> > > > > + return status;
> > > > > +
> > > > > + spin_lock_irqsave(&lchan->vchan.lock, flags);
> > > > > + vdesc = vchan_find_desc(&lchan->vchan, cookie);
> > > > > + if (lchan->desc && cookie == lchan->desc->vdesc.tx.cookie)
> > > > > + state->residue = loongson2_cmc_dma_desc_residue(lchan, lchan->desc, lchan->next_sg);
> > > > > + else if (vdesc)
> > > > > + state->residue = loongson2_cmc_dma_desc_residue(lchan, to_lmdma_desc(vdesc), 0);
> > > > > +
> > > > > + spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> > > > > +
> > > > > + return status;
> > > > > +}
> > > > > +
> > > > > +static void loongson2_cmc_dma_free_chan_resources(struct dma_chan *chan)
> > > > > +{
> > > > > + vchan_free_chan_resources(to_virt_chan(chan));
> > > > > +}
> > > > > +
> > > > > +static void loongson2_cmc_dma_desc_free(struct virt_dma_desc *vdesc)
> > > > > +{
> > > > > + kfree(to_lmdma_desc(vdesc));
> > > > > +}
> > > > > +
> > > > > +static bool loongson2_cmc_dma_acpi_filter(struct dma_chan *chan, void *param)
> > > > > +{
> > > > > + struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > > + struct acpi_dma_spec *dma_spec = param;
> > > > > +
> > > > > + memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg));
> > > > > + lchan->chan_reg.ccr = dma_spec->chan_id & LOONGSON2_CMCDMA_STREAM_MASK;
> > > > > +
> > > > > + return true;
> > > > > +}
> > > > > +
> > > > > +static int loongson2_cmc_dma_acpi_controller_register(struct loongson2_cmc_dma_dev *lddev)
> > > > > +{
> > > > > + struct device *dev = lddev->ddev.dev;
> > > > > + struct acpi_dma_filter_info *info;
> > > > > + int ret;
> > > > > +
> > > > > + if (!has_acpi_companion(dev))
> > > > > + return 0;
> > > > > +
> > > > > + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
> > > > > + if (!info)
> > > > > + return -ENOMEM;
> > > > > +
> > > > > + dma_cap_zero(info->dma_cap);
> > > > > + info->dma_cap = lddev->ddev.cap_mask;
> > > > > + info->filter_fn = loongson2_cmc_dma_acpi_filter;
> > > > > +
> > > > > + ret = devm_acpi_dma_controller_register(dev, acpi_dma_simple_xlate, info);
> > > > > + if (ret)
> > > > > + dev_err(dev, "could not register acpi_dma_controller\n");
> > > > > +
> > > > > + return ret;
> > > > > +}
> > > > > +
> > > > > +static struct dma_chan *loongson2_cmc_dma_of_xlate(struct of_phandle_args *dma_spec,
> > > > > + struct of_dma *ofdma)
> > > > > +{
> > > > > + struct loongson2_cmc_dma_dev *lddev = ofdma->of_dma_data;
> > > > > + struct device *dev = lddev->ddev.dev;
> > > > > + struct loongson2_cmc_dma_chan *lchan;
> > > > > + struct dma_chan *chan;
> > > > > +
> > > > > + if (dma_spec->args_count < 2)
> > > > > + return NULL;
> > > > > +
> > > > > + if (dma_spec->args[0] >= lddev->nr_channels) {
> > > > > + dev_err(dev, "Invalid channel id\n");
> > > > > + return NULL;
> > > > > + }
> > > > > +
> > > > > + lchan = &lddev->chan[dma_spec->args[0]];
> > > > > + chan = dma_get_slave_channel(&lchan->vchan.chan);
> > > > > + if (!chan) {
> > > > > + dev_err(dev, "No more channels available\n");
> > > > > + return NULL;
> > > > > + }
> > > > > +
> > > > > + memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg));
> > > > > + lchan->chan_reg.ccr = dma_spec->args[1] & LOONGSON2_CMCDMA_STREAM_MASK;
> > > > > +
> > > > > + return chan;
> > > > > +}
> > > > > +
> > > > > +static int loongson2_cmc_dma_of_controller_register(struct loongson2_cmc_dma_dev *lddev)
> > > > > +{
> > > > > + struct device *dev = lddev->ddev.dev;
> > > > > + int ret;
> > > > > +
> > > > > + if (!dev->of_node)
> > > > > + return 0;
> > > > > +
> > > > > + ret = of_dma_controller_register(dev->of_node, loongson2_cmc_dma_of_xlate, lddev);
> > > > > + if (ret)
> > > > > + dev_err(dev, "could not register of_dma_controller\n");
> > > > > +
> > > > > + return ret;
> > > > > +}
> > > > > +
> > > > > +static int loongson2_cmc_dma_probe(struct platform_device *pdev)
> > > > > +{
> > > > > + const struct loongson2_cmc_dma_config *config;
> > > > > + struct loongson2_cmc_dma_chan *lchan;
> > > > > + struct loongson2_cmc_dma_dev *lddev;
> > > > > + struct device *dev = &pdev->dev;
> > > > > + struct dma_device *ddev;
> > > > > + u32 nr_chans, i;
> > > > > + int ret;
> > > > > +
> > > > > + config = (const struct loongson2_cmc_dma_config *)device_get_match_data(dev);
> > > > > + if (!config)
> > > > > + return -EINVAL;
> > > > > +
> > > > > + ret = device_property_read_u32(dev, "dma-channels", &nr_chans);
> > > > > + if (ret || nr_chans > config->max_channels) {
> > > > > + dev_err(dev, "missing or invalid dma-channels property\n");
> > > > > + nr_chans = config->max_channels;
> > > > > + }
> > > > > +
> > > > > + lddev = devm_kzalloc(dev, struct_size(lddev, chan, nr_chans), GFP_KERNEL);
> > > > > + if (!lddev)
> > > > > + return -ENOMEM;
> > > > > +
> > > > > + lddev->base = devm_platform_ioremap_resource(pdev, 0);
> > > > > + if (IS_ERR(lddev->base))
> > > > > + return PTR_ERR(lddev->base);
> > > > > +
> > > > > + platform_set_drvdata(pdev, lddev);
> > > > > + lddev->nr_channels = nr_chans;
> > > > > + lddev->chan_reg_offset = config->chan_reg_offset;
> > > > > +
> > > > > + lddev->dma_clk = devm_clk_get_optional_enabled(dev, NULL);
> > > > > + if (IS_ERR(lddev->dma_clk))
> > > > > + return dev_err_probe(dev, PTR_ERR(lddev->dma_clk), "Failed to get dma clock\n");
> > > > > +
> > > > > + ddev = &lddev->ddev;
> > > > > + ddev->dev = dev;
> > > > > +
> > > > > + dma_cap_zero(ddev->cap_mask);
> > > > > + dma_cap_set(DMA_SLAVE, ddev->cap_mask);
> > > > > + dma_cap_set(DMA_PRIVATE, ddev->cap_mask);
> > > > > + dma_cap_set(DMA_CYCLIC, ddev->cap_mask);
> > > > > +
> > > > > + ddev->device_free_chan_resources = loongson2_cmc_dma_free_chan_resources;
> > > > > + ddev->device_config = loongson2_cmc_dma_slave_config;
> > > > > + ddev->device_prep_slave_sg = loongson2_cmc_dma_prep_slave_sg;
> > > > > + ddev->device_prep_dma_cyclic = loongson2_cmc_dma_prep_dma_cyclic;
> > > > > + ddev->device_issue_pending = loongson2_cmc_dma_issue_pending;
> > > > > + ddev->device_synchronize = loongson2_cmc_dma_synchronize;
> > > > > + ddev->device_tx_status = loongson2_cmc_dma_tx_status;
> > > > > + ddev->device_terminate_all = loongson2_cmc_dma_terminate_all;
> > > > > +
> > > > > + ddev->src_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS;
> > > > > + ddev->dst_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS;
> > > > > + ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
> > > > > + INIT_LIST_HEAD(&ddev->channels);
> > > >
> > > > where use this 'channels' ?
> > >
> > > It will be used by global functions such as `dma_async_device_register()`.
>
> Okay, supposed it sould be done in dma_async_device_register().
>
> Frank
> > > >
> > > > Frank
> > > > > +
> > > > > + for (i = 0; i < nr_chans; i++) {
> > > > > + lchan = &lddev->chan[i];
> > > > > +
> > > > > + lchan->id = i;
> > > > > + lchan->vchan.desc_free = loongson2_cmc_dma_desc_free;
> > > > > + vchan_init(&lchan->vchan, ddev);
> > > > > + }
> > > > > +
> > > > > + ret = dmaenginem_async_device_register(ddev);
> > > > > + if (ret)
> > > > > + return ret;
> > > > > +
> > > > > + for (i = 0; i < nr_chans; i++) {
> > > > > + lchan = &lddev->chan[i];
> > > > > +
> > > > > + lchan->irq = platform_get_irq(pdev, i);
> > > > > + if (lchan->irq < 0)
> > > > > + return lchan->irq;
> > > > > +
> > > > > + ret = devm_request_irq(dev, lchan->irq, loongson2_cmc_dma_chan_irq, IRQF_SHARED,
> > > > > + dev_name(chan2dev(lchan)), lchan);
> > > > > + if (ret)
> > > > > + return ret;
> > > > > + }
> > > > > +
> > > > > + ret = loongson2_cmc_dma_acpi_controller_register(lddev);
> > > > > + if (ret)
> > > > > + return ret;
> > > > > +
> > > > > + return loongson2_cmc_dma_of_controller_register(lddev);
> > > > > +}
> > > > > +
> > > > > +static void loongson2_cmc_dma_remove(struct platform_device *pdev)
> > > > > +{
> > > > > + of_dma_controller_free(pdev->dev.of_node);
> > > > > +}
> > > > > +
> > > > > +static const struct of_device_id loongson2_cmc_dma_of_match[] = {
> > > > > + { .compatible = "loongson,ls2k0300-dma", .data = &ls2k0300_cmc_dma_config },
> > > > > + { .compatible = "loongson,ls2k3000-dma", .data = &ls2k3000_cmc_dma_config },
> > > > > + { /* sentinel */ }
> > > > > +};
> > > > > +MODULE_DEVICE_TABLE(of, loongson2_cmc_dma_of_match);
> > > > > +
> > > > > +static const struct acpi_device_id loongson2_cmc_dma_acpi_match[] = {
> > > > > + { "LOON0014", .driver_data = (kernel_ulong_t)&ls2k3000_cmc_dma_config },
> > > > > + { /* sentinel */ }
> > > > > +};
> > > > > +MODULE_DEVICE_TABLE(acpi, loongson2_cmc_dma_acpi_match);
> > > > > +
> > > > > +static struct platform_driver loongson2_cmc_dma_driver = {
> > > > > + .driver = {
> > > > > + .name = "loongson2-apb-cmc-dma",
> > > > > + .of_match_table = loongson2_cmc_dma_of_match,
> > > > > + .acpi_match_table = loongson2_cmc_dma_acpi_match,
> > > > > + },
> > > > > + .probe = loongson2_cmc_dma_probe,
> > > > > + .remove = loongson2_cmc_dma_remove,
> > > > > +};
> > > > > +module_platform_driver(loongson2_cmc_dma_driver);
> > > > > +
> > > > > +MODULE_DESCRIPTION("Looongson-2 Multi-Channel DMA Controller driver");
> > > > > +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> > > > > +MODULE_LICENSE("GPL");
> > > > > --
> > > > > 2.52.0
> > > > >
> > >
> > > --
> > > Thanks.
> > > Binbin
> >
> > --
> > Thanks.
> > Binbin
--
Thanks.
Binbin
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/4] dmaengine: loongson: New directory for Loongson DMA controllers drivers
2026-02-10 1:27 ` Binbin Zhou
@ 2026-02-26 8:07 ` Huacai Chen
0 siblings, 0 replies; 16+ messages in thread
From: Huacai Chen @ 2026-02-26 8:07 UTC (permalink / raw)
To: Binbin Zhou
Cc: Frank Li, Binbin Zhou, Huacai Chen, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Vinod Koul, dmaengine,
Xiaochuang Mao, Xuerui Wang, loongarch, devicetree, Keguang Zhang,
linux-mips, jeffbai
Hi, Binbin.
On Tue, Feb 10, 2026 at 9:27 AM Binbin Zhou <zhoubb.aaron@gmail.com> wrote:
>
> Hi Frank:
>
> On Tue, Feb 10, 2026 at 12:48 AM Frank Li <Frank.li@nxp.com> wrote:
> >
> > On Mon, Feb 09, 2026 at 11:04:18AM +0800, Binbin Zhou wrote:
> > > Gather the Loongson DMA controllers under drivers/dma/loongson/
> > >
> > > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > > ---
> > > MAINTAINERS | 3 +-
> > > drivers/dma/Kconfig | 25 ++---------------
> > > drivers/dma/Makefile | 3 +-
> > > drivers/dma/loongson/Kconfig | 28 +++++++++++++++++++
> > > drivers/dma/loongson/Makefile | 3 ++
> > > .../dma/{ => loongson}/loongson1-apb-dma.c | 4 +--
> > > .../dma/{ => loongson}/loongson2-apb-dma.c | 4 +--
> > > 7 files changed, 40 insertions(+), 30 deletions(-)
> > > create mode 100644 drivers/dma/loongson/Kconfig
> > > create mode 100644 drivers/dma/loongson/Makefile
> > > rename drivers/dma/{ => loongson}/loongson1-apb-dma.c (99%)
> > > rename drivers/dma/{ => loongson}/loongson2-apb-dma.c (99%)
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index f630328ca6ae..27f77b68d596 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -14777,7 +14777,7 @@ M: Binbin Zhou <zhoubinbin@loongson.cn>
> > > L: dmaengine@vger.kernel.org
> > > S: Maintained
> > > F: Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
> > > -F: drivers/dma/loongson2-apb-dma.c
> > > +F: drivers/dma/loongson/loongson2-apb-dma.c
> > >
> > > LOONGSON LS2X I2C DRIVER
> > > M: Binbin Zhou <zhoubinbin@loongson.cn>
> > > @@ -17515,6 +17515,7 @@ F: arch/mips/boot/dts/loongson/loongson1*
> > > F: arch/mips/configs/loongson1_defconfig
> > > F: arch/mips/loongson32/
> > > F: drivers/*/*loongson1*
> > > +F: drivers/dma/loongson/loongson1-apb-dma.c
> > > F: drivers/mtd/nand/raw/loongson-nand-controller.c
> > > F: drivers/net/ethernet/stmicro/stmmac/dwmac-loongson1.c
> > > F: sound/soc/loongson/loongson1_ac97.c
> > > diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
> > > index 66cda7cc9f7a..1b84c5b11654 100644
> > > --- a/drivers/dma/Kconfig
> > > +++ b/drivers/dma/Kconfig
> > > @@ -376,29 +376,6 @@ config K3_DMA
> > > Support the DMA engine for Hisilicon K3 platform
> > > devices.
> > >
> > > -config LOONGSON1_APB_DMA
> > > - tristate "Loongson1 APB DMA support"
> > > - depends on MACH_LOONGSON32 || COMPILE_TEST
> > > - select DMA_ENGINE
> > > - select DMA_VIRTUAL_CHANNELS
> > > - help
> > > - This selects support for the APB DMA controller in Loongson1 SoCs,
> > > - which is required by Loongson1 NAND and audio support.
> > > -
> > > -config LOONGSON2_APB_DMA
> > > - tristate "Loongson2 APB DMA support"
> > > - depends on LOONGARCH || COMPILE_TEST
> > > - select DMA_ENGINE
> > > - select DMA_VIRTUAL_CHANNELS
> > > - help
> > > - Support for the Loongson2 APB DMA controller driver. The
> > > - DMA controller is having single DMA channel which can be
> > > - configured for different peripherals like audio, nand, sdio
> > > - etc which is in APB bus.
> > > -
> > > - This DMA controller transfers data from memory to peripheral fifo.
> > > - It does not support memory to memory data transfer.
> > > -
> > > config LPC18XX_DMAMUX
> > > bool "NXP LPC18xx/43xx DMA MUX for PL080"
> > > depends on ARCH_LPC18XX || COMPILE_TEST
> > > @@ -774,6 +751,8 @@ source "drivers/dma/fsl-dpaa2-qdma/Kconfig"
> > >
> > > source "drivers/dma/lgm/Kconfig"
> > >
> > > +source "drivers/dma/loongson/Kconfig"
> > > +
> > > source "drivers/dma/stm32/Kconfig"
> > >
> > > # clients
> > > diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
> > > index a54d7688392b..a1c73415b79f 100644
> > > --- a/drivers/dma/Makefile
> > > +++ b/drivers/dma/Makefile
> > > @@ -49,8 +49,6 @@ obj-$(CONFIG_INTEL_IDMA64) += idma64.o
> > > obj-$(CONFIG_INTEL_IOATDMA) += ioat/
> > > obj-y += idxd/
> > > obj-$(CONFIG_K3_DMA) += k3dma.o
> > > -obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> > > -obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> > > obj-$(CONFIG_LPC18XX_DMAMUX) += lpc18xx-dmamux.o
> > > obj-$(CONFIG_LPC32XX_DMAMUX) += lpc32xx-dmamux.o
> > > obj-$(CONFIG_MILBEAUT_HDMAC) += milbeaut-hdmac.o
> > > @@ -88,6 +86,7 @@ obj-$(CONFIG_INTEL_LDMA) += lgm/
> > >
> > > obj-y += amd/
> > > obj-y += mediatek/
> > > +obj-y += loongson/
> >
> > keep alphabet order
>
> Sorry, I'll fix it in the next version.
> >
> > Frank
> > > obj-y += qcom/
> > > obj-y += stm32/
> > > obj-y += ti/
> > > diff --git a/drivers/dma/loongson/Kconfig b/drivers/dma/loongson/Kconfig
> > > new file mode 100644
> > > index 000000000000..9dbdaef5a59f
> > > --- /dev/null
> > > +++ b/drivers/dma/loongson/Kconfig
> > > @@ -0,0 +1,28 @@
> > > +# SPDX-License-Identifier: GPL-2.0-only
> > > +#
> > > +# Loongson DMA controllers drivers
> > > +#
> > > +if MACH_LOONGSON32 || MACH_LOONGSON64 || COMPILE_TEST
> > > +
> > > +config LOONGSON1_APB_DMA
> > > + tristate "Loongson1 APB DMA support"
> > > + select DMA_ENGINE
> > > + select DMA_VIRTUAL_CHANNELS
I think "depends on MACH_LOONGSON32 || COMPILE_TEST" is still needed,
otherwise it can be compiled for MACH_LOONGSON64.
> > > + help
> > > + This selects support for the APB DMA controller in Loongson1 SoCs,
> > > + which is required by Loongson1 NAND and audio support.
> > > +
> > > +config LOONGSON2_APB_DMA
> > > + tristate "Loongson2 APB DMA support"
> > > + select DMA_ENGINE
> > > + select DMA_VIRTUAL_CHANNELS
The same, "depends on MACH_LOONGSON64 || COMPILE_TEST" is needed.
Huacai
> > > + help
> > > + Support for the Loongson2 APB DMA controller driver. The
> > > + DMA controller is having single DMA channel which can be
> > > + configured for different peripherals like audio, nand, sdio
> > > + etc which is in APB bus.
> > > +
> > > + This DMA controller transfers data from memory to peripheral fifo.
> > > + It does not support memory to memory data transfer.
> > > +
> > > +endif
> > > diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
> > > new file mode 100644
> > > index 000000000000..6cdd08065e92
> > > --- /dev/null
> > > +++ b/drivers/dma/loongson/Makefile
> > > @@ -0,0 +1,3 @@
> > > +# SPDX-License-Identifier: GPL-2.0-only
> > > +obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> > > +obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> > > diff --git a/drivers/dma/loongson1-apb-dma.c b/drivers/dma/loongson/loongson1-apb-dma.c
> > > similarity index 99%
> > > rename from drivers/dma/loongson1-apb-dma.c
> > > rename to drivers/dma/loongson/loongson1-apb-dma.c
> > > index 255fe7eca212..e99247cf90c1 100644
> > > --- a/drivers/dma/loongson1-apb-dma.c
> > > +++ b/drivers/dma/loongson/loongson1-apb-dma.c
> > > @@ -16,8 +16,8 @@
> > > #include <linux/platform_device.h>
> > > #include <linux/slab.h>
> > >
> > > -#include "dmaengine.h"
> > > -#include "virt-dma.h"
> > > +#include "../dmaengine.h"
> > > +#include "../virt-dma.h"
> > >
> > > /* Loongson-1 DMA Control Register */
> > > #define LS1X_DMA_CTRL 0x0
> > > diff --git a/drivers/dma/loongson2-apb-dma.c b/drivers/dma/loongson/loongson2-apb-dma.c
> > > similarity index 99%
> > > rename from drivers/dma/loongson2-apb-dma.c
> > > rename to drivers/dma/loongson/loongson2-apb-dma.c
> > > index c528f02b9f84..0cb607595d04 100644
> > > --- a/drivers/dma/loongson2-apb-dma.c
> > > +++ b/drivers/dma/loongson/loongson2-apb-dma.c
> > > @@ -17,8 +17,8 @@
> > > #include <linux/platform_device.h>
> > > #include <linux/slab.h>
> > >
> > > -#include "dmaengine.h"
> > > -#include "virt-dma.h"
> > > +#include "../dmaengine.h"
> > > +#include "../virt-dma.h"
> > >
> > > /* Global Configuration Register */
> > > #define LDMA_ORDER_ERG 0x0
> > > --
> > > 2.52.0
> > >
>
> --
> Thanks.
> Binbin
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-02-26 8:07 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-09 3:04 [PATCH v2 0/4] dmaengine: Add Loongson Multi-Channel DMA controller support Binbin Zhou
2026-02-09 3:04 ` [PATCH v2 1/4] dmaengine: loongson: New directory for Loongson DMA controllers drivers Binbin Zhou
2026-02-09 16:48 ` Frank Li
2026-02-10 1:27 ` Binbin Zhou
2026-02-26 8:07 ` Huacai Chen
2026-02-09 3:04 ` [PATCH v2 2/4] dmaengine: loongson: loongson2-apb: Convert to dmaenginem_async_device_register() Binbin Zhou
2026-02-09 16:51 ` Frank Li
2026-02-09 3:04 ` [PATCH v2 3/4] dt-bindings: dmaengine: Add Loongson Multi-Channel DMA controller Binbin Zhou
2026-02-10 3:03 ` Rob Herring
2026-02-10 6:02 ` Binbin Zhou
2026-02-09 3:04 ` [PATCH v2 4/4] dmaengine: loongson: New driver for the " Binbin Zhou
2026-02-09 17:04 ` Frank Li
2026-02-10 7:41 ` Binbin Zhou
2026-02-10 12:02 ` Binbin Zhou
2026-02-10 15:27 ` Frank Li
2026-02-24 12:59 ` Binbin Zhou
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox