* [PATCH 0/2] dmaengine: ma35d1-pdma: Add MA35D1 PDMA driver
@ 2026-11-03 2:22 Shan-Chun Hung
2026-11-03 2:22 ` [PATCH 1/2] dt-bindings: dma: nuvoton,ma35d1-pdma: Document MA35D1 PDMA controller Shan-Chun Hung
2026-11-03 2:22 ` [PATCH 2/2] dmaengine: ma35d1-pdma: Add MA35D1 PDMA driver Shan-Chun Hung
0 siblings, 2 replies; 3+ messages in thread
From: Shan-Chun Hung @ 2026-11-03 2:22 UTC (permalink / raw)
To: ychuang3, schung, vkoul, Frank.Li, robh, krzk+dt, conor+dt, kees,
gustavoars, linux-arm-kernel, dmaengine, devicetree,
linux-kernel, linux-hardening
Cc: Shan-Chun Hung
This series adds the PDMA controller driver and DT binding for the
Nuvoton MA35D1 SoC.
The driver supports memcpy, slave scatter-gather and cyclic transfers,
and a per-channel timeout configured through
dma_slave_config.peripheral_config. The timeout is needed by the
MA35D1 UART driver, to be submitted separately.
Tested on the MA35D1 SOM board with Linux v7.4-rc1, using local
(not yet submitted) QSPI and UART patches that use this PDMA driver.
Signed-off-by: Shan-Chun Hung <shanchun1218@gmail.com>
Shan-Chun Hung (2):
dt-bindings: dma: nuvoton,ma35d1-pdma: Document MA35D1 PDMA controller
dmaengine: ma35d1-pdma: Add MA35D1 PDMA driver
.../bindings/dma/nuvoton,ma35d1-pdma.yaml | 72 ++
drivers/dma/Kconfig | 12 +
drivers/dma/Makefile | 1 +
drivers/dma/ma35d1-pdma.c | 780 ++++++++++++++++++
include/dt-bindings/dma/ma35d1-pdma.h | 113 +++
include/linux/dma/ma35d1-pdma.h | 24 +
6 files changed, 1002 insertions(+)
create mode 100644 Documentation/devicetree/bindings/dma/nuvoton,ma35d1-pdma.yaml
create mode 100644 drivers/dma/ma35d1-pdma.c
create mode 100644 include/dt-bindings/dma/ma35d1-pdma.h
create mode 100644 include/linux/dma/ma35d1-pdma.h
--
2.25.1
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH 1/2] dt-bindings: dma: nuvoton,ma35d1-pdma: Document MA35D1 PDMA controller
2026-11-03 2:22 [PATCH 0/2] dmaengine: ma35d1-pdma: Add MA35D1 PDMA driver Shan-Chun Hung
@ 2026-11-03 2:22 ` Shan-Chun Hung
2026-11-03 2:22 ` [PATCH 2/2] dmaengine: ma35d1-pdma: Add MA35D1 PDMA driver Shan-Chun Hung
1 sibling, 0 replies; 3+ messages in thread
From: Shan-Chun Hung @ 2026-11-03 2:22 UTC (permalink / raw)
To: ychuang3, schung, vkoul, Frank.Li, robh, krzk+dt, conor+dt, kees,
gustavoars, linux-arm-kernel, dmaengine, devicetree,
linux-kernel, linux-hardening
Cc: Shan-Chun Hung
Add binding for Nuvoton MA35D1 PDMA controller.
Signed-off-by: Shan-Chun Hung <shanchun1218@gmail.com>
---
.../bindings/dma/nuvoton,ma35d1-pdma.yaml | 72 +++++++++++
include/dt-bindings/dma/ma35d1-pdma.h | 113 ++++++++++++++++++
2 files changed, 185 insertions(+)
create mode 100644 Documentation/devicetree/bindings/dma/nuvoton,ma35d1-pdma.yaml
create mode 100644 include/dt-bindings/dma/ma35d1-pdma.h
diff --git a/Documentation/devicetree/bindings/dma/nuvoton,ma35d1-pdma.yaml b/Documentation/devicetree/bindings/dma/nuvoton,ma35d1-pdma.yaml
new file mode 100644
index 000000000000..cd752571242c
--- /dev/null
+++ b/Documentation/devicetree/bindings/dma/nuvoton,ma35d1-pdma.yaml
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/dma/nuvoton,ma35d1-pdma.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Nuvoton MA35D1 PDMA controller
+
+description:
+ This describes the devicetree bindings for four Peripheral DMA engines to perform
+ memory-to-memory, memory-to-peripheral, or peripheral-to-memory data transfer
+ capable of supporting 10 channels, implemented in Nuvoton MA35D1 SoCs.
+
+maintainers:
+ - Shan-Chun Hung <shanchun1218@gmail.com>
+
+allOf:
+ - $ref: dma-controller.yaml#
+
+properties:
+ compatible:
+ const: nuvoton,ma35d1-pdma
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ "#dma-cells":
+ const: 2
+ description:
+ The first cell is the channel number. The second cell is the
+ peripheral request source (REQSRC) selection, which defines which
+ peripheral is connected to that PDMA channel.
+
+ dma-channels:
+ const: 10
+
+additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - interrupts
+ - "#dma-cells"
+ - dma-channels
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
+
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ dma-controller@40080000 {
+ compatible = "nuvoton,ma35d1-pdma";
+ reg = <0x0 0x40080000 0x0 0x2000>;
+ interrupts = <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk PDMA0_GATE>;
+ #dma-cells = <2>;
+ dma-channels = <10>;
+ };
+ };
+
+...
diff --git a/include/dt-bindings/dma/ma35d1-pdma.h b/include/dt-bindings/dma/ma35d1-pdma.h
new file mode 100644
index 000000000000..0ddc99d29661
--- /dev/null
+++ b/include/dt-bindings/dma/ma35d1-pdma.h
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ */
+
+#ifndef __DT_BINDINGS_DMA_MA35D1_PDMA_H__
+#define __DT_BINDINGS_DMA_MA35D1_PDMA_H__
+
+#define MA35D1_PDMA_MEM 0
+#define MA35D1_PDMA_UART0_TX 4
+#define MA35D1_PDMA_UART0_RX 5
+#define MA35D1_PDMA_UART1_TX 6
+#define MA35D1_PDMA_UART1_RX 7
+#define MA35D1_PDMA_UART2_TX 8
+#define MA35D1_PDMA_UART2_RX 9
+#define MA35D1_PDMA_UART3_TX 10
+#define MA35D1_PDMA_UART3_RX 11
+#define MA35D1_PDMA_UART4_TX 12
+#define MA35D1_PDMA_UART4_RX 13
+#define MA35D1_PDMA_UART5_TX 14
+#define MA35D1_PDMA_UART5_RX 15
+#define MA35D1_PDMA_UART6_TX 16
+#define MA35D1_PDMA_UART6_RX 17
+#define MA35D1_PDMA_UART7_TX 18
+#define MA35D1_PDMA_UART7_RX 19
+#define MA35D1_PDMA_UART8_TX 20
+#define MA35D1_PDMA_UART8_RX 21
+#define MA35D1_PDMA_UART9_TX 22
+#define MA35D1_PDMA_UART9_RX 23
+#define MA35D1_PDMA_UART10_TX 24
+#define MA35D1_PDMA_UART10_RX 25
+#define MA35D1_PDMA_UART11_TX 26
+#define MA35D1_PDMA_UART11_RX 27
+#define MA35D1_PDMA_UART12_TX 28
+#define MA35D1_PDMA_UART12_RX 29
+#define MA35D1_PDMA_UART13_TX 30
+#define MA35D1_PDMA_UART13_RX 31
+#define MA35D1_PDMA_UART14_TX 32
+#define MA35D1_PDMA_UART14_RX 33
+#define MA35D1_PDMA_UART15_TX 34
+#define MA35D1_PDMA_UART15_RX 35
+#define MA35D1_PDMA_UART16_TX 36
+#define MA35D1_PDMA_UART16_RX 37
+#define MA35D1_PDMA_QSPI0_TX 54
+#define MA35D1_PDMA_QSPI0_RX 55
+#define MA35D1_PDMA_QSPI1_TX 56
+#define MA35D1_PDMA_QSPI1_RX 57
+#define MA35D1_PDMA_SPI0_TX 60
+#define MA35D1_PDMA_SPI0_RX 61
+#define MA35D1_PDMA_SPI1_TX 62
+#define MA35D1_PDMA_SPI1_RX 63
+#define MA35D1_PDMA_SPI2_TX 64
+#define MA35D1_PDMA_SPI2_RX 65
+#define MA35D1_PDMA_SPI3_TX 66
+#define MA35D1_PDMA_SPI3_RX 67
+#define MA35D1_PDMA_I2C0_TX 70
+#define MA35D1_PDMA_I2C0_RX 71
+#define MA35D1_PDMA_I2C1_TX 72
+#define MA35D1_PDMA_I2C1_RX 73
+#define MA35D1_PDMA_I2C2_TX 74
+#define MA35D1_PDMA_I2C2_RX 75
+#define MA35D1_PDMA_I2C3_TX 76
+#define MA35D1_PDMA_I2C3_RX 77
+#define MA35D1_PDMA_I2C4_TX 78
+#define MA35D1_PDMA_I2C4_RX 79
+#define MA35D1_PDMA_I2C5_TX 80
+#define MA35D1_PDMA_I2C5_RX 81
+#define MA35D1_PDMA_I2S0_TX 86
+#define MA35D1_PDMA_I2S0_RX 87
+#define MA35D1_PDMA_I2S1_TX 88
+#define MA35D1_PDMA_I2S1_RX 89
+#define MA35D1_PDMA_TMR0 94
+#define MA35D1_PDMA_TMR1 95
+#define MA35D1_PDMA_TMR2 96
+#define MA35D1_PDMA_TMR3 97
+#define MA35D1_PDMA_TMR4 98
+#define MA35D1_PDMA_TMR5 99
+#define MA35D1_PDMA_TMR6 100
+#define MA35D1_PDMA_TMR7 101
+#define MA35D1_PDMA_TMR8 102
+#define MA35D1_PDMA_TMR9 103
+#define MA35D1_PDMA_TMR10 104
+#define MA35D1_PDMA_TMR11 105
+#define MA35D1_PDMA_EPWM0_P1_RX 110
+#define MA35D1_PDMA_EPWM0_P2_RX 111
+#define MA35D1_PDMA_EPWM0_P3_RX 112
+#define MA35D1_PDMA_EPWM1_P1_RX 113
+#define MA35D1_PDMA_EPWM1_P2_RX 114
+#define MA35D1_PDMA_EPWM1_P3_RX 115
+#define MA35D1_PDMA_EPWM2_P1_RX 116
+#define MA35D1_PDMA_EPWM2_P2_RX 117
+#define MA35D1_PDMA_EPWM2_P3_RX 118
+#define MA35D1_PDMA_EPWM0_CH0_TX 122
+#define MA35D1_PDMA_EPWM0_CH1_TX 123
+#define MA35D1_PDMA_EPWM0_CH2_TX 124
+#define MA35D1_PDMA_EPWM0_CH3_TX 125
+#define MA35D1_PDMA_EPWM0_CH4_TX 126
+#define MA35D1_PDMA_EPWM0_CH5_TX 127
+#define MA35D1_PDMA_EPWM1_CH0_TX 128
+#define MA35D1_PDMA_EPWM1_CH1_TX 129
+#define MA35D1_PDMA_EPWM1_CH2_TX 130
+#define MA35D1_PDMA_EPWM1_CH3_TX 131
+#define MA35D1_PDMA_EPWM1_CH4_TX 132
+#define MA35D1_PDMA_EPWM1_CH5_TX 133
+#define MA35D1_PDMA_EPWM2_CH0_TX 134
+#define MA35D1_PDMA_EPWM2_CH1_TX 135
+#define MA35D1_PDMA_EPWM2_CH2_TX 136
+#define MA35D1_PDMA_EPWM2_CH3_TX 137
+#define MA35D1_PDMA_EPWM2_CH4_TX 138
+#define MA35D1_PDMA_EPWM2_CH5_TX 139
+#define MA35D1_PDMA_EADC0_RX 146
+
+#endif /* __DT_BINDINGS_DMA_MA35D1_PDMA_H__ */
--
2.25.1
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH 2/2] dmaengine: ma35d1-pdma: Add MA35D1 PDMA driver
2026-11-03 2:22 [PATCH 0/2] dmaengine: ma35d1-pdma: Add MA35D1 PDMA driver Shan-Chun Hung
2026-11-03 2:22 ` [PATCH 1/2] dt-bindings: dma: nuvoton,ma35d1-pdma: Document MA35D1 PDMA controller Shan-Chun Hung
@ 2026-11-03 2:22 ` Shan-Chun Hung
1 sibling, 0 replies; 3+ messages in thread
From: Shan-Chun Hung @ 2026-11-03 2:22 UTC (permalink / raw)
To: ychuang3, schung, vkoul, Frank.Li, robh, krzk+dt, conor+dt, kees,
gustavoars, linux-arm-kernel, dmaengine, devicetree,
linux-kernel, linux-hardening
Cc: Shan-Chun Hung
Add a driver for the PDMA controller found in the Nuvoton MA35D1 SoC.
It supports up to 10 channels with memory-to-memory, slave scatter-gather
and cyclic transfers, and a per-channel timeout for variable-length
peripheral RX.
Signed-off-by: Shan-Chun Hung <shanchun1218@gmail.com>
---
drivers/dma/Kconfig | 12 +
drivers/dma/Makefile | 1 +
drivers/dma/ma35d1-pdma.c | 780 ++++++++++++++++++++++++++++++++
include/linux/dma/ma35d1-pdma.h | 24 +
4 files changed, 817 insertions(+)
create mode 100644 drivers/dma/ma35d1-pdma.c
create mode 100644 include/linux/dma/ma35d1-pdma.h
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 01f96a8257e5..3d4d09e459f1 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -404,6 +404,18 @@ config LPC32XX_DMAMUX
Support for PL080 multiplexed DMA request lines on
LPC32XX platrofm.
+config MA35D1_PDMA
+ tristate "Nuvoton MA35D1 PDMA Engine"
+ depends on ARCH_MA35 || COMPILE_TEST
+ select DMA_ENGINE
+ select DMA_VIRTUAL_CHANNELS
+ help
+ Support the Nuvoton MA35D1 PDMA engine. This engine is integrated
+ into Nuvoton MA35D1 chips. This DMA controller can transfer data
+ from memory to memory, memory to peripheral and peripheral to memory.
+ If you have a board based on such a SoC and wish to use DMA, say Y
+ or M here.
+
config MCF_EDMA
tristate "Freescale eDMA engine support, ColdFire mcf5441x SoCs"
depends on M5441x || (COMPILE_TEST && FSL_EDMA=n)
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index f62d12b08e15..dd99f7d12d4d 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -52,6 +52,7 @@ obj-y += idxd/
obj-$(CONFIG_K3_DMA) += k3dma.o
obj-$(CONFIG_LPC18XX_DMAMUX) += lpc18xx-dmamux.o
obj-$(CONFIG_LPC32XX_DMAMUX) += lpc32xx-dmamux.o
+obj-$(CONFIG_MA35D1_PDMA) += ma35d1-pdma.o
obj-$(CONFIG_MILBEAUT_HDMAC) += milbeaut-hdmac.o
obj-$(CONFIG_MILBEAUT_XDMAC) += milbeaut-xdmac.o
obj-$(CONFIG_MMP_PDMA) += mmp_pdma.o
diff --git a/drivers/dma/ma35d1-pdma.c b/drivers/dma/ma35d1-pdma.c
new file mode 100644
index 000000000000..176bc56c8063
--- /dev/null
+++ b/drivers/dma/ma35d1-pdma.c
@@ -0,0 +1,780 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Nuvoton MA35D1 PDMA controller driver
+ *
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Author: Shan-Chun Hung <shanchun1218@gmail.com>
+ */
+
+#include <linux/align.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/compiler_types.h>
+#include <linux/container_of.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/dma-direction.h>
+#include <linux/dma-mapping.h>
+#include <linux/dma/ma35d1-pdma.h>
+#include <linux/dmaengine.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/gfp_types.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irqreturn.h>
+#include <linux/limits.h>
+#include <linux/list.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_dma.h>
+#include <linux/overflow.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/spinlock_types.h>
+#include <linux/stddef.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include "dmaengine.h"
+#include "virt-dma.h"
+
+/* PDMA registers */
+#define PDMA_DSCT_CTL 0x000
+#define PDMA_DSCT_SA 0x004
+#define PDMA_DSCT_DA 0x008
+#define PDMA_DSCT_NEXT 0x00c
+#define PDMA_CHCTL 0x400
+#define PDMA_SWREQ 0x408
+#define PDMA_INTEN 0x418
+#define PDMA_INTSTS 0x41C
+#define PDMA_ABTSTS 0x420
+#define PDMA_TDSTS 0x424
+#define PDMA_TOUTEN 0x434
+#define PDMA_TOUTIEN 0x438
+#define PDMA_TOC 0x440
+#define PDMA_CHRST 0x460
+#define PDMA_TOUTPSC 0x470
+#define PDMA_REQSEL 0x480
+
+/* Bitfields in PDMA registers */
+#define PDMA_OP_BASIC BIT(0)
+#define PDMA_OP_SCATTER BIT(1)
+#define PDMA_OP_MSK GENMASK(1, 0)
+#define PDMA_TXTYPE BIT(2)
+#define PDMA_TBINTDIS BIT(7)
+#define PDMA_SAFIX GENMASK(9, 8)
+#define PDMA_DAFIX GENMASK(11, 10)
+
+#define PDMA_MAX_CHANS 10
+#define PDMA_MAX_CHAN_BEATS (U16_MAX + 1)
+
+#define PDMA_WIDTH(w) FIELD_PREP(GENMASK(13, 12), (w) >> 1)
+#define PDMA_TXCNT(cnt) FIELD_PREP(GENMASK(31, 16), (cnt) - 1)
+#define PDMA_GET_TXCNT(ctrl) FIELD_GET(GENMASK(31, 16), readl(ctrl))
+#define PDMA_CH_OFFSET(ch) ((ch) * 0x10)
+
+#define PDMA_SLAVE_BUSWIDTHS (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \
+ BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \
+ BIT(DMA_SLAVE_BUSWIDTH_4_BYTES))
+
+struct ma35d1_sg {
+ u32 ctl;
+ u32 src;
+ u32 dst;
+ u32 next;
+};
+
+struct ma35d1_desc {
+ struct ma35d1_chan *ch;
+ bool cyclic;
+ /* bytes per beat */
+ u32 width;
+ struct virt_dma_desc vd;
+ dma_addr_t dma_addr;
+ size_t dma_len;
+ u32 sg_len;
+ struct ma35d1_sg sg[] __counted_by(sg_len);
+};
+
+struct ma35d1_chan {
+ struct device *dev;
+ struct ma35d1_pdmadev *pdmadev;
+ struct virt_dma_chan vc;
+ void __iomem *base;
+ int id;
+ struct ma35d1_desc *desc;
+ struct dma_slave_config cfg;
+ struct ma35d1_peripheral pcfg;
+ /* second #dma-cells argument from devicetree */
+ u32 request;
+ /* set when the current descriptor completed with an abort */
+ bool error;
+ /* bytes left after a timeout */
+ u32 remain;
+};
+
+struct ma35d1_pdmadev {
+ struct dma_device ddev;
+ /* protects read-modify-write of shared controller registers */
+ spinlock_t lock;
+ int nr_chans;
+ struct ma35d1_chan channels[] __counted_by(nr_chans);
+};
+
+static inline struct ma35d1_chan *to_ma35d1_pdma_chan(struct dma_chan *c)
+{
+ return container_of(c, struct ma35d1_chan, vc.chan);
+}
+
+static inline struct ma35d1_desc *to_ma35d1_pdma_desc(struct virt_dma_desc *vd)
+{
+ return container_of(vd, struct ma35d1_desc, vd);
+}
+
+static inline void ma35d1_pdma_reg_set(struct ma35d1_chan *ch, u32 offset, u32 mask)
+{
+ writel(readl(ch->base + offset) | mask, ch->base + offset);
+}
+
+static inline void ma35d1_pdma_reg_clear(struct ma35d1_chan *ch, u32 offset, u32 mask)
+{
+ writel(readl(ch->base + offset) & ~mask, ch->base + offset);
+}
+
+static void ma35d1_pdma_desc_free(struct virt_dma_desc *vd)
+{
+ struct ma35d1_desc *d = to_ma35d1_pdma_desc(vd);
+
+ dma_unmap_single(d->ch->dev, d->dma_addr, d->dma_len, DMA_TO_DEVICE);
+ kfree(d);
+}
+
+static int ma35d1_terminate_all(struct dma_chan *chan)
+{
+ LIST_HEAD(head);
+ struct ma35d1_chan *ch = to_ma35d1_pdma_chan(chan);
+ unsigned long flags, pdmadev_flags;
+
+ spin_lock_irqsave(&ch->vc.lock, flags);
+
+ spin_lock_irqsave(&ch->pdmadev->lock, pdmadev_flags);
+ ma35d1_pdma_reg_clear(ch, PDMA_CHCTL, BIT(ch->id));
+ spin_unlock_irqrestore(&ch->pdmadev->lock, pdmadev_flags);
+
+ if (ch->desc) {
+ vchan_terminate_vdesc(&ch->desc->vd);
+ ch->desc = NULL;
+ }
+
+ vchan_get_all_descriptors(&ch->vc, &head);
+ spin_unlock_irqrestore(&ch->vc.lock, flags);
+
+ vchan_dma_desc_free_list(&ch->vc, &head);
+
+ return 0;
+}
+
+static void ma35d1_synchronize(struct dma_chan *chan)
+{
+ struct ma35d1_chan *ch = to_ma35d1_pdma_chan(chan);
+
+ vchan_synchronize(&ch->vc);
+}
+
+static void ma35d1_pdma_timeout(struct ma35d1_chan *ch)
+{
+ struct ma35d1_peripheral *pcfg = ch->cfg.peripheral_config;
+ u32 offset, shift;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ch->pdmadev->lock, flags);
+ if (!pcfg || (pcfg->prescaler == 0 && pcfg->counter == 0)) {
+ ma35d1_pdma_reg_clear(ch, PDMA_TOUTIEN, BIT(ch->id));
+ ma35d1_pdma_reg_clear(ch, PDMA_TOUTEN, BIT(ch->id));
+ goto spin_unlock;
+ }
+
+ /*
+ * Calculate the positions of the timeout prescaler's bit fields and registers,
+ * then write them.
+ */
+ offset = PDMA_TOUTPSC + 4 * (ch->id >> 3);
+ shift = (ch->id & GENMASK(2, 0)) * 4;
+ ma35d1_pdma_reg_clear(ch, offset, GENMASK(2, 0) << shift);
+ ma35d1_pdma_reg_set(ch, offset, pcfg->prescaler << shift);
+
+ /*
+ * Calculate the positions of the timeout counter's bit fields and registers,
+ * then write them.
+ */
+ offset = PDMA_TOC + 4 * (ch->id >> 1);
+ shift = (ch->id & GENMASK(0, 0)) * 16;
+ ma35d1_pdma_reg_clear(ch, offset, GENMASK(15, 0) << shift);
+ ma35d1_pdma_reg_set(ch, offset, pcfg->counter << shift);
+
+ ma35d1_pdma_reg_set(ch, PDMA_TOUTEN, BIT(ch->id));
+ ma35d1_pdma_reg_set(ch, PDMA_TOUTIEN, BIT(ch->id));
+
+spin_unlock:
+ spin_unlock_irqrestore(&ch->pdmadev->lock, flags);
+}
+
+static void ma35d1_pdma_setup_channel(struct ma35d1_chan *ch)
+{
+ u32 offset, reg, shift;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ch->pdmadev->lock, flags);
+
+ if ((readl(ch->base + PDMA_CH_OFFSET(ch->id)) & GENMASK(1, 0)) != 0) {
+ reg = readl(ch->base + PDMA_CHCTL);
+ writel(0, ch->base + PDMA_CH_OFFSET(ch->id));
+ ma35d1_pdma_reg_set(ch, PDMA_CHRST, BIT(ch->id));
+ writel(reg | BIT(ch->id), ch->base + PDMA_CHCTL);
+ } else {
+ writel(0, ch->base + PDMA_CH_OFFSET(ch->id));
+ ma35d1_pdma_reg_set(ch, PDMA_CHCTL, BIT(ch->id));
+ }
+ ma35d1_pdma_reg_set(ch, PDMA_INTEN, BIT(ch->id));
+
+ offset = PDMA_REQSEL + (ch->id & ~GENMASK(1, 0));
+ shift = (ch->id & GENMASK(1, 0)) * 8;
+ ma35d1_pdma_reg_clear(ch, offset, GENMASK(7, 0) << shift);
+ ma35d1_pdma_reg_set(ch, offset, ch->request << shift);
+
+ spin_unlock_irqrestore(&ch->pdmadev->lock, flags);
+}
+
+static int ma35d1_slave_config(struct dma_chan *chan, struct dma_slave_config *cfg)
+{
+ struct ma35d1_chan *ch = to_ma35d1_pdma_chan(chan);
+ struct ma35d1_peripheral *pcfg = cfg->peripheral_config;
+
+ memcpy(&ch->cfg, cfg, sizeof(*cfg));
+ ch->cfg.peripheral_config = NULL;
+
+ if (pcfg) {
+ if (cfg->peripheral_size != sizeof(*pcfg)) {
+ dev_err(ch->dev, "Invalid peripheral size %zu, expected %zu\n",
+ cfg->peripheral_size, sizeof(*pcfg));
+ return -EINVAL;
+ }
+
+ if (pcfg->counter > GENMASK(15, 0) || pcfg->prescaler > GENMASK(2, 0)) {
+ dev_err(ch->dev, "Maximum prescaler or counter exceeded\n");
+ return -EINVAL;
+ }
+
+ memcpy(&ch->pcfg, pcfg, sizeof(ch->pcfg));
+ ch->cfg.peripheral_config = &ch->pcfg;
+ }
+
+ return 0;
+}
+
+static int ma35d1_slave_width(struct ma35d1_chan *ch, enum dma_transfer_direction dir)
+{
+ enum dma_slave_buswidth width;
+
+ if (dir == DMA_MEM_TO_DEV) {
+ width = ch->cfg.dst_addr_width;
+ } else if (dir == DMA_DEV_TO_MEM) {
+ width = ch->cfg.src_addr_width;
+ } else {
+ dev_err(ch->dev, "invalid DMA direction %d\n", dir);
+ return -EINVAL;
+ }
+
+ if (width != DMA_SLAVE_BUSWIDTH_1_BYTE && width != DMA_SLAVE_BUSWIDTH_2_BYTES &&
+ width != DMA_SLAVE_BUSWIDTH_4_BYTES) {
+ dev_err(ch->dev, "invalid DMA width %d\n", width);
+ return -EINVAL;
+ }
+
+ return width;
+}
+
+static struct ma35d1_desc *ma35d1_desc_alloc(struct ma35d1_chan *ch, u32 sg_len)
+{
+ struct ma35d1_desc *d;
+
+ d = kzalloc(struct_size(d, sg, sg_len), GFP_ATOMIC);
+ if (!d)
+ return NULL;
+
+ d->sg_len = sg_len;
+ d->dma_len = sizeof(*d->sg) * sg_len;
+ d->dma_addr = dma_map_single(ch->dev, d->sg, d->dma_len, DMA_TO_DEVICE);
+ if (dma_mapping_error(ch->dev, d->dma_addr)) {
+ dev_err(ch->dev, "dma mapping error for channel\n");
+ kfree(d);
+ return NULL;
+ }
+
+ writel(d->dma_addr, ch->base + PDMA_CH_OFFSET(ch->id) + PDMA_DSCT_NEXT);
+
+ return d;
+}
+
+static struct dma_async_tx_descriptor *
+ma35d1_desc_submit(struct ma35d1_chan *ch, struct ma35d1_desc *d, bool cyclic,
+ unsigned long tx_flags)
+{
+ dma_sync_single_for_device(ch->dev, d->dma_addr, d->dma_len, DMA_TO_DEVICE);
+ d->cyclic = cyclic;
+ d->ch = ch;
+ ch->error = false;
+
+ return vchan_tx_prep(&ch->vc, &d->vd, tx_flags);
+}
+
+static struct dma_async_tx_descriptor *
+ma35d1_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dst, dma_addr_t src,
+ size_t len, unsigned long tx_flags)
+{
+ int i;
+ size_t tlen, max_chunk;
+ struct ma35d1_chan *ch = to_ma35d1_pdma_chan(chan);
+ struct ma35d1_desc *d;
+ u32 sg_len, width = 1;
+
+ if (ch->request != 0) {
+ dev_err(ch->dev, "%s: channel is bound to a peripheral request\n", __func__);
+ return NULL;
+ }
+
+ if (!len) {
+ dev_err(ch->dev, "invalid memcpy length\n");
+ return NULL;
+ }
+
+ if (IS_ALIGNED(src, 4) && IS_ALIGNED(dst, 4) && IS_ALIGNED(len, 4))
+ width = 4;
+
+ max_chunk = (size_t)PDMA_MAX_CHAN_BEATS * width;
+
+ sg_len = DIV_ROUND_UP_ULL(len, max_chunk);
+ d = ma35d1_desc_alloc(ch, sg_len);
+ if (!d)
+ return NULL;
+ d->width = width;
+
+ ma35d1_pdma_setup_channel(ch);
+
+ for (i = 0; i < sg_len; i++) {
+ tlen = min_t(size_t, len, max_chunk);
+ d->sg[i].src = src;
+ d->sg[i].dst = dst;
+ d->sg[i].ctl = PDMA_WIDTH(width) | PDMA_TXCNT(tlen / width) | PDMA_OP_SCATTER;
+ d->sg[i].next = d->dma_addr + PDMA_CH_OFFSET(i + 1);
+ src += tlen;
+ dst += tlen;
+ len -= tlen;
+ }
+ d->sg[sg_len - 1].ctl &= ~(PDMA_OP_MSK | PDMA_TBINTDIS);
+ d->sg[sg_len - 1].ctl |= PDMA_OP_BASIC;
+
+ return ma35d1_desc_submit(ch, d, false, tx_flags);
+}
+
+static struct dma_async_tx_descriptor *
+ma35d1_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
+ u32 sg_len, enum dma_transfer_direction dir,
+ unsigned long tx_flags, void *context)
+{
+ int i, width;
+ struct ma35d1_chan *ch = to_ma35d1_pdma_chan(chan);
+ struct ma35d1_desc *d;
+ struct scatterlist *sgent;
+
+ width = ma35d1_slave_width(ch, dir);
+ if (width < 0)
+ return NULL;
+
+ if (!sg_len) {
+ dev_err(ch->dev, "SG entry length is zero\n");
+ return NULL;
+ }
+
+ d = ma35d1_desc_alloc(ch, sg_len);
+ if (!d)
+ return NULL;
+ d->width = width;
+
+ ma35d1_pdma_setup_channel(ch);
+
+ for_each_sg(sgl, sgent, sg_len, i) {
+ if (dir == DMA_MEM_TO_DEV) {
+ d->sg[i].ctl = PDMA_WIDTH(width) | PDMA_DAFIX;
+ d->sg[i].src = sg_dma_address(sgent);
+ d->sg[i].dst = ch->cfg.dst_addr;
+ } else {
+ d->sg[i].ctl = PDMA_WIDTH(width) | PDMA_SAFIX;
+ d->sg[i].src = ch->cfg.src_addr;
+ d->sg[i].dst = sg_dma_address(sgent);
+ }
+ d->sg[i].ctl |= PDMA_TXCNT(sg_dma_len(sgent) / width) | PDMA_TBINTDIS |
+ PDMA_TXTYPE | PDMA_OP_SCATTER;
+ d->sg[i].next = d->dma_addr + PDMA_CH_OFFSET(i + 1);
+ }
+
+ d->sg[sg_len - 1].ctl &= ~(PDMA_OP_MSK | PDMA_TBINTDIS);
+ d->sg[sg_len - 1].ctl |= PDMA_OP_BASIC;
+
+ return ma35d1_desc_submit(ch, d, false, tx_flags);
+}
+
+static struct dma_async_tx_descriptor *
+ma35d1_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t dma_addr, size_t buf_len,
+ size_t period_len, enum dma_transfer_direction dir,
+ unsigned long flags)
+{
+ int i, sg_len, width;
+ size_t offset = 0;
+ struct ma35d1_chan *ch = to_ma35d1_pdma_chan(chan);
+ struct ma35d1_desc *d;
+
+ width = ma35d1_slave_width(ch, dir);
+ if (width < 0)
+ return NULL;
+
+ if (!buf_len || !period_len || period_len / width > PDMA_MAX_CHAN_BEATS) {
+ dev_err(ch->dev, "Invalid buffer/period length\n");
+ return NULL;
+ }
+
+ if (buf_len % period_len) {
+ dev_err(ch->dev, "Buffer length not multiple of period length\n");
+ return NULL;
+ }
+
+ if (period_len % width) {
+ dev_err(ch->dev, "Period length must be a multiple of DMA width\n");
+ return NULL;
+ }
+
+ sg_len = buf_len / period_len;
+ d = ma35d1_desc_alloc(ch, sg_len);
+ if (!d)
+ return NULL;
+ d->width = width;
+
+ ma35d1_pdma_setup_channel(ch);
+
+ /* Split the buffer into period size chunks. */
+ for (offset = 0, i = 0; offset < buf_len; offset += period_len, i++) {
+ if (dir == DMA_MEM_TO_DEV) {
+ d->sg[i].ctl = PDMA_WIDTH(width) | PDMA_TXCNT(period_len / width) |
+ PDMA_DAFIX;
+ d->sg[i].src = dma_addr + offset;
+ d->sg[i].dst = ch->cfg.dst_addr;
+ } else {
+ d->sg[i].ctl = PDMA_WIDTH(width) | PDMA_TXCNT(period_len / width) |
+ PDMA_SAFIX;
+ d->sg[i].src = ch->cfg.src_addr;
+ d->sg[i].dst = dma_addr + offset;
+ }
+ d->sg[i].ctl |= PDMA_TXTYPE | PDMA_OP_SCATTER;
+ d->sg[i].next = d->dma_addr + PDMA_CH_OFFSET(i + 1);
+ }
+ d->sg[sg_len - 1].next = d->dma_addr;
+
+ return ma35d1_desc_submit(ch, d, true, flags);
+}
+
+static struct dma_chan *ma35d1_of_xlate(struct of_phandle_args *dma_spec, struct of_dma *of)
+{
+ struct ma35d1_pdmadev *pdmadev = of->of_dma_data;
+ unsigned int chan_id, reqsel;
+
+ if (dma_spec->args_count != 2)
+ return NULL;
+
+ chan_id = dma_spec->args[0];
+ reqsel = dma_spec->args[1];
+ if (chan_id >= pdmadev->nr_chans || reqsel > GENMASK(7, 0))
+ return NULL;
+
+ pdmadev->channels[chan_id].request = reqsel;
+
+ return dma_get_slave_channel(&pdmadev->channels[chan_id].vc.chan);
+}
+
+static void ma35d1_free_chan_resources(struct dma_chan *chan)
+{
+ struct ma35d1_chan *ch = to_ma35d1_pdma_chan(chan);
+
+ vchan_free_chan_resources(&ch->vc);
+ ch->request = 0;
+}
+
+static void ma35d1_pdma_start(struct ma35d1_chan *ch)
+{
+ struct ma35d1_desc *d = ch->desc;
+
+ if (d->sg_len == 1 && !d->cyclic) {
+ /* Trigger basic mode. */
+ writel(d->sg[0].src, ch->base + PDMA_CH_OFFSET(ch->id) + PDMA_DSCT_SA);
+ writel(d->sg[0].dst, ch->base + PDMA_CH_OFFSET(ch->id) + PDMA_DSCT_DA);
+ writel(d->sg[0].ctl, ch->base + PDMA_CH_OFFSET(ch->id) + PDMA_DSCT_CTL);
+ } else {
+ /* Trigger scatter-gather mode. */
+ ma35d1_pdma_reg_clear(ch, PDMA_CH_OFFSET(ch->id) + PDMA_DSCT_CTL, PDMA_OP_MSK);
+ ma35d1_pdma_reg_set(ch, PDMA_CH_OFFSET(ch->id) + PDMA_DSCT_CTL, PDMA_OP_SCATTER);
+ }
+
+ /* Memory-to-memory has no peripheral request, so trigger it by software. */
+ if (ch->request == 0)
+ writel(BIT(ch->id), ch->base + PDMA_SWREQ);
+}
+
+static void ma35d1_pdma_start_desc(struct dma_chan *chan)
+{
+ struct ma35d1_chan *ch = to_ma35d1_pdma_chan(chan);
+ struct virt_dma_desc *vd;
+
+ vd = vchan_next_desc(&ch->vc);
+ if (!vd) {
+ ch->desc = NULL;
+ return;
+ }
+
+ list_del(&vd->node);
+ ch->desc = to_ma35d1_pdma_desc(vd);
+ ma35d1_pdma_timeout(ch);
+ ma35d1_pdma_start(ch);
+}
+
+static void ma35d1_issue_pending(struct dma_chan *chan)
+{
+ struct ma35d1_chan *ch = to_ma35d1_pdma_chan(chan);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ch->vc.lock, flags);
+
+ if (vchan_issue_pending(&ch->vc) && !ch->desc)
+ ma35d1_pdma_start_desc(chan);
+
+ spin_unlock_irqrestore(&ch->vc.lock, flags);
+}
+
+static enum dma_status ma35d1_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
+ struct dma_tx_state *txstate)
+{
+ enum dma_status ret;
+ struct ma35d1_chan *ch = to_ma35d1_pdma_chan(chan);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ch->vc.lock, flags);
+
+ ret = dma_cookie_status(chan, cookie, txstate);
+ if (ret == DMA_COMPLETE) {
+ if (ch->error)
+ ret = DMA_ERROR;
+ else
+ dma_set_residue(txstate, ch->remain);
+ }
+
+ spin_unlock_irqrestore(&ch->vc.lock, flags);
+
+ return ret;
+}
+
+static void ma35d1_pdma_complete(struct ma35d1_chan *ch)
+{
+ if (!ch->desc)
+ return;
+
+ vchan_cookie_complete(&ch->desc->vd);
+ ma35d1_pdma_start_desc(&ch->vc.chan);
+}
+
+static irqreturn_t ma35d1_pdma_interrupt(int irq, void *devid)
+{
+ int i;
+ struct ma35d1_pdmadev *pdmadev = devid;
+ struct ma35d1_chan *ch = &pdmadev->channels[pdmadev->nr_chans - 1];
+ unsigned int timeout_sts, done_sts, abort_sts;
+ unsigned long pending;
+
+ /*
+ * Read the interrupt flags. Normally, only one is set per channel.
+ * Check and clear each flag separately.
+ */
+ done_sts = readl(ch->base + PDMA_TDSTS);
+ timeout_sts = readl(ch->base + PDMA_INTSTS);
+ abort_sts = readl(ch->base + PDMA_ABTSTS);
+
+ pending = (done_sts | abort_sts | (timeout_sts >> 8)) &
+ GENMASK(pdmadev->nr_chans - 1, 0);
+
+ for_each_set_bit(i, &pending, pdmadev->nr_chans) {
+ unsigned long flags;
+
+ ch = &pdmadev->channels[i];
+
+ spin_lock_irqsave(&ch->vc.lock, flags);
+
+ if (done_sts & BIT(i)) {
+ writel(BIT(i), ch->base + PDMA_TDSTS);
+ if (ch->desc) {
+ ch->remain = 0;
+ if (ch->desc->cyclic)
+ vchan_cyclic_callback(&ch->desc->vd);
+ else
+ ma35d1_pdma_complete(ch);
+ }
+ }
+
+ if (timeout_sts & BIT(i + 8)) {
+ if (ch->desc)
+ ch->remain = PDMA_GET_TXCNT(ch->base + PDMA_CH_OFFSET(ch->id)) *
+ ch->desc->width;
+ ma35d1_pdma_reg_clear(ch, PDMA_TOUTEN, BIT(i));
+ writel(BIT(i + 8), ch->base + PDMA_INTSTS);
+ ma35d1_pdma_complete(ch);
+ }
+
+ if (abort_sts & BIT(i)) {
+ writel(BIT(i), ch->base + PDMA_ABTSTS);
+ ch->error = true;
+ ma35d1_pdma_complete(ch);
+ }
+
+ spin_unlock_irqrestore(&ch->vc.lock, flags);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int ma35d1_pdma_probe(struct platform_device *pdev)
+{
+ int i, irq, nr_chans, ret;
+ struct clk *pdma_clk;
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node;
+ struct ma35d1_chan *ch;
+ struct ma35d1_pdmadev *pdmadev;
+ void __iomem *reg_base;
+
+ pdma_clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(pdma_clk))
+ return dev_err_probe(dev, PTR_ERR(pdma_clk), "Failed to get clk\n");
+
+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = of_property_read_u32(node, "dma-channels", &nr_chans);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get dma-channels\n");
+
+ if (nr_chans > PDMA_MAX_CHANS) {
+ dev_warn(dev, "dma-channels %d exceeds max %d, clamping\n",
+ nr_chans, PDMA_MAX_CHANS);
+ nr_chans = PDMA_MAX_CHANS;
+ }
+
+ pdmadev = devm_kzalloc(dev, struct_size(pdmadev, channels, nr_chans), GFP_KERNEL);
+ if (!pdmadev)
+ return -ENOMEM;
+
+ pdmadev->nr_chans = nr_chans;
+
+ reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(reg_base))
+ return dev_err_probe(dev, PTR_ERR(reg_base),
+ "Failed to map pdma global registers\n");
+
+ dma_cap_zero(pdmadev->ddev.cap_mask);
+ dma_cap_set(DMA_SLAVE, pdmadev->ddev.cap_mask);
+ dma_cap_set(DMA_PRIVATE, pdmadev->ddev.cap_mask);
+ dma_cap_set(DMA_MEMCPY, pdmadev->ddev.cap_mask);
+ dma_cap_set(DMA_CYCLIC, pdmadev->ddev.cap_mask);
+
+ pdmadev->ddev.dev = dev;
+ pdmadev->ddev.src_addr_widths = PDMA_SLAVE_BUSWIDTHS;
+ pdmadev->ddev.dst_addr_widths = PDMA_SLAVE_BUSWIDTHS;
+ pdmadev->ddev.directions = BIT(DMA_MEM_TO_DEV) | BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_MEM);
+ pdmadev->ddev.device_prep_dma_memcpy = ma35d1_prep_dma_memcpy;
+ pdmadev->ddev.device_prep_slave_sg = ma35d1_prep_slave_sg;
+ pdmadev->ddev.device_prep_dma_cyclic = ma35d1_prep_dma_cyclic;
+ pdmadev->ddev.device_free_chan_resources = ma35d1_free_chan_resources;
+ pdmadev->ddev.device_issue_pending = ma35d1_issue_pending;
+ pdmadev->ddev.device_tx_status = ma35d1_tx_status;
+ pdmadev->ddev.device_config = ma35d1_slave_config;
+ pdmadev->ddev.device_terminate_all = ma35d1_terminate_all;
+ pdmadev->ddev.device_synchronize = ma35d1_synchronize;
+
+ spin_lock_init(&pdmadev->lock);
+ INIT_LIST_HEAD(&pdmadev->ddev.channels);
+ for (i = 0; i < nr_chans; i++) {
+ ch = &pdmadev->channels[i];
+ ch->dev = dev;
+ ch->pdmadev = pdmadev;
+ ch->id = i;
+ ch->base = reg_base;
+ ch->vc.desc_free = ma35d1_pdma_desc_free;
+ vchan_init(&ch->vc, &pdmadev->ddev);
+ }
+
+ ret = devm_request_irq(dev, irq, ma35d1_pdma_interrupt, 0, pdev->name, pdmadev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request IRQ\n");
+
+ ret = dmaenginem_async_device_register(&pdmadev->ddev);
+ if (ret)
+ return ret;
+
+ ret = of_dma_controller_register(node, ma35d1_of_xlate, pdmadev);
+ if (ret)
+ return dev_err_probe(dev, ret, "of_dma_controller_register failed\n");
+
+ platform_set_drvdata(pdev, pdmadev);
+
+ return 0;
+}
+
+static void ma35d1_pdma_remove(struct platform_device *pdev)
+{
+ struct ma35d1_pdmadev *pdmadev = platform_get_drvdata(pdev);
+ int i;
+
+ of_dma_controller_free(pdev->dev.of_node);
+ writel(0, pdmadev->channels[0].base + PDMA_INTEN);
+
+ for (i = 0; i < pdmadev->nr_chans; i++)
+ tasklet_kill(&pdmadev->channels[i].vc.task);
+}
+
+static const struct of_device_id ma35d1_pdma_match[] = {
+ { .compatible = "nuvoton,ma35d1-pdma" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ma35d1_pdma_match);
+
+static struct platform_driver ma35d1_pdma_driver = {
+ .probe = ma35d1_pdma_probe,
+ .remove = ma35d1_pdma_remove,
+ .driver = {
+ .name = "ma35d1-pdma",
+ .of_match_table = ma35d1_pdma_match,
+ },
+};
+module_platform_driver(ma35d1_pdma_driver);
+
+MODULE_DESCRIPTION("PDMA controller driver for Nuvoton MA35D1");
+MODULE_AUTHOR("Shan-Chun Hung <shanchun1218@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/dma/ma35d1-pdma.h b/include/linux/dma/ma35d1-pdma.h
new file mode 100644
index 000000000000..13f1410d74da
--- /dev/null
+++ b/include/linux/dma/ma35d1-pdma.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Header file for the MA35D1 PDMA Controller driver
+ *
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ */
+#ifndef _MA35D1_PDMA_H
+#define _MA35D1_PDMA_H
+
+/**
+ * struct ma35d1_peripheral - PDMA peripheral configuration
+ * @prescaler: timeout prescaler value
+ * @counter: timeout counter value
+ *
+ * Slave device drivers fill in this structure and pass it via
+ * dma_slave_config.peripheral_config/peripheral_size to configure
+ * the MA35D1 PDMA engine's per-channel timeout detection block.
+ */
+struct ma35d1_peripheral {
+ unsigned int prescaler;
+ unsigned int counter;
+};
+
+#endif /* _MA35D1_PDMA_H */
--
2.25.1
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-11-03 2:22 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-03 2:22 [PATCH 0/2] dmaengine: ma35d1-pdma: Add MA35D1 PDMA driver Shan-Chun Hung
2026-11-03 2:22 ` [PATCH 1/2] dt-bindings: dma: nuvoton,ma35d1-pdma: Document MA35D1 PDMA controller Shan-Chun Hung
2026-11-03 2:22 ` [PATCH 2/2] dmaengine: ma35d1-pdma: Add MA35D1 PDMA driver Shan-Chun Hung
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®