summaryrefslogtreecommitdiff
path: root/components/drivers
diff options
context:
space:
mode:
authorGuorui Li <[email protected]>2026-06-16 10:21:41 +0800
committerGitHub <[email protected]>2026-06-16 10:21:41 +0800
commit09334ce7719d13af4f4c4e781cf4e0a33bc75dd3 (patch)
tree461e166b7913899be02d3110998ae19ce12e02be /components/drivers
parent98d507785cd0bd5fc4370297766f25835afc551f (diff)
doc: add Doxygen comments to DMA driver subsystem #11477
Diffstat (limited to 'components/drivers')
-rw-r--r--components/drivers/dma/dma-pl330.c756
-rw-r--r--components/drivers/dma/dma.c243
-rw-r--r--components/drivers/dma/dma_pool.c378
-rw-r--r--components/drivers/include/drivers/dma.h463
4 files changed, 1509 insertions, 331 deletions
diff --git a/components/drivers/dma/dma-pl330.c b/components/drivers/dma/dma-pl330.c
index f91d2ea8a7..b0f4bbaa10 100644
--- a/components/drivers/dma/dma-pl330.c
+++ b/components/drivers/dma/dma-pl330.c
@@ -8,6 +8,24 @@
* 2023-02-25 GuEe-GUI the first version
*/
+/**
+ * @file dma-pl330.c
+ * @brief ARM PL330 DMA Controller (DMA-330) driver
+ *
+ * Implements a DMA controller driver for the ARM PrimeCell PL330
+ * (also known as DMA-330). The PL330 uses a programmable microcode
+ * engine to execute DMA transfers. This driver generates microcode
+ * sequences for memcpy, cyclic, and single transfers on-the-fly.
+ *
+ * Key features:
+ * - Microcode generation for DMAMOV, DMALD, DMAST, DMALP, DMAEND
+ * DMAWFE, DMASEV instructions
+ * - Busrt size and width configuration from DMA slave config
+ * - Nested loop support for transfers exceeding 256 burst iterations
+ * - Cyclic (infinite loop) transfers with periodic callbacks
+ * - Debug instruction interface for starting and killing channels
+ */
+
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
@@ -17,175 +35,275 @@
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
-#define _FIELD_READ(h, l, x) ((RT_GENMASK(h, l) & (x)) >> l)
+/** @brief Extract a bit field from a register value */
+#define _FIELD_READ(h, l, x) ((RT_GENMASK(h, l) & (x)) >> l)
-#define PL330_REG_DSR 0x000 /* DMA Manager Status Register */
-#define PL330_REG_DPC 0x004 /* DMA Program Counter Register */
-#define PL330_REG_INTEN 0x020 /* Interrupt Enable Register */
-#define PL330_REG_INT_EVENT_RIS 0x024 /* Event-Interrupt Raw Status Register */
-#define PL330_REG_INTMIS 0x028 /* Interrupt Status Register */
-#define PL330_REG_INTCLR 0x02c /* Interrupt Clear Register */
-#define PL330_REG_FSRD 0x030 /* Fault Status DMA Manager Register */
-#define PL330_REG_FSRC 0x034 /* Fault Status DMA Channel Register */
-#define PL330_REG_FTRD 0x038 /* Fault Type DMA Manager Register */
-#define PL330_REG_FTR(n) (0x040 + (n) * 0x4) /* Fault Type DMA Channel [n] */
-#define PL330_FT_UNDEF_INSTR RT_BIT(0)
-#define PL330_FT_OPERAND_INVALID RT_BIT(1)
-#define PL330_FT_DMAGO_ERR RT_BIT(4)
-#define PL330_FT_EVENT_ERR RT_BIT(5)
-#define PL330_FT_CH_PERIPH_ERR RT_BIT(6)
-#define PL330_FT_CH_RDWR_ERR RT_BIT(7)
-#define PL330_FT_ST_DATA_UNAVAILABLE RT_BIT(12)
-#define PL330_FT_FIFOEMPTY_ERR RT_BIT(13)
-#define PL330_FT_INSTR_FETCH_ERR RT_BIT(16)
-#define PL330_FT_DATA_WRITE_ERR RT_BIT(17)
-#define PL330_FT_DATA_READ_ERR RT_BIT(18)
-#define PL330_FT_DBG_INSTR RT_BIT(30)
-#define PL330_FT_LOCKUP_ERR RT_BIT(31)
-#define PL330_REG_CSR(n) (0x100 + (n) * 0x8) /* Channel [n] Status Register */
-#define PL330_CS_STOP 0x0
-#define PL330_CS_EXEC 0x1
-#define PL330_CS_CMISS 0x2
-#define PL330_CS_UPDTPC 0x3
-#define PL330_CS_WFE 0x4
-#define PL330_CS_ATBRR 0x5
-#define PL330_CS_QBUSY 0x6
-#define PL330_CS_WFP 0x7
-#define PL330_CS_KILL 0x8
-#define PL330_CS_CMPLT 0x9
-#define PL330_CS_FLTCMP 0xe
-#define PL330_CS_FAULT 0xf
-#define PL330_REG_CPC(n) (0x104 + (n) * 0x8) /* Channel [n] Program Counter Register */
-#define pl330_REG_SAR(n) (0x0400 + (n) * 0x20) /* Channel [n] Source Address Register */
-#define pl330_REG_DAR(n) (0x0404 + (n) * 0x20) /* Channel [n] Destination Address Register */
-#define pl330_REG_CCR(n) (0x0408 + (n) * 0x20) /* Channel [n] Channel Control Register */
-#define pl330_REG_LC0(n) (0x040c + (n) * 0x20) /* Channel [n] Loop Counter 0 Register */
-#define pl330_REG_LC1(n) (0x0410 + (n) * 0x20) /* Channel [n] Loop Counter 1 Register */
-#define PL330_REG_DBGSTATUS 0xd00 /* Debug Status Register */
-#define PL330_DBGSTATUS_IDLE 0
-#define PL330_DBGSTATUS_BUSY RT_BIT(0)
-#define PL330_REG_DBGCMD 0xd04 /* Debug Command Register */
-#define PL330_REG_DBGINST0 0xd08 /* Debug Instruction-0 Register */
-#define PL330_REG_DBGINST1 0xd0c /* Debug Instruction-1 Register */
-#define PL330_REG_CR(n) (0xe00 + (n) * 0x4) /* Configuration Register [n] */
-#define PL330_CR0_PERIPH_REQ_SET RT_BIT(0)
-#define PL330_CR0_MGR_NS_AT_RST(x) _FIELD_READ(2, 2, x)
-#define PL330_CR0_NUM_CHNLS(x) _FIELD_READ(6, 4, x)
-#define PL330_CR0_NUM_PERIPH(x) _FIELD_READ(16, 12, x)
-#define PL330_CR0_NUM_EVENTS(x) _FIELD_READ(21, 17, x)
-#define PL330_REG_CRD 0x0e14 /* Configuration Register */
-#define PL330_CRD_DATA_WIDTH(x) _FIELD_READ(2, 0, x)
-#define PL330_CRD_WR_CAP(x) _FIELD_READ(6, 4, x)
-#define PL330_CRD_WR_Q_DEP(x) _FIELD_READ(11, 8, x)
-#define PL330_CRD_RD_CAP(x) _FIELD_READ(14, 12, x)
-#define PL330_CRD_RD_Q_DEP(x) _FIELD_READ(19, 16, x)
-#define PL330_CRD_DATA_BUFFER_DEP(x) _FIELD_READ(29, 20, x)
-#define PL330_REG_WDT 0x0e80 /* DMA Watchdog Register */
-#define PL330_REG_PERIPH_ID 0x0fe0 /* Periph ID Register */
-#define PL330_PERIPH_REV(x) _FIELD_READ(23, 20, x)
-#define PL330_PERIPH_REV_R0P0 0
-#define PL330_PERIPH_REV_R1P0 1
-#define PL330_PERIPH_REV_R1P1 2
+/** @brief DMA Manager Status Register */
+#define PL330_REG_DSR 0x000
+/** @brief DMA Program Counter Register */
+#define PL330_REG_DPC 0x004
+/** @brief Interrupt Enable Register */
+#define PL330_REG_INTEN 0x020
+/** @brief Event-Interrupt Raw Status Register */
+#define PL330_REG_INT_EVENT_RIS 0x024
+/** @brief Interrupt Status Register */
+#define PL330_REG_INTMIS 0x028
+/** @brief Interrupt Clear Register */
+#define PL330_REG_INTCLR 0x02c
+/** @brief Fault Status DMA Manager Register */
+#define PL330_REG_FSRD 0x030
+/** @brief Fault Status DMA Channel Register */
+#define PL330_REG_FSRC 0x034
+/** @brief Fault Type DMA Manager Register */
+#define PL330_REG_FTRD 0x038
+/** @brief Fault Type DMA Channel [n] Register */
+#define PL330_REG_FTR(n) (0x040 + (n) * 0x4)
+/** @brief Undefined instruction fault flag */
+#define PL330_FT_UNDEF_INSTR RT_BIT(0)
+/** @brief Invalid operand fault flag */
+#define PL330_FT_OPERAND_INVALID RT_BIT(1)
+/** @brief DMAGO error fault flag */
+#define PL330_FT_DMAGO_ERR RT_BIT(4)
+/** @brief Event error fault flag */
+#define PL330_FT_EVENT_ERR RT_BIT(5)
+/** @brief Channel peripheral error fault flag */
+#define PL330_FT_CH_PERIPH_ERR RT_BIT(6)
+/** @brief Channel read/write error fault flag */
+#define PL330_FT_CH_RDWR_ERR RT_BIT(7)
+/** @brief Store data unavailable fault flag */
+#define PL330_FT_ST_DATA_UNAVAILABLE RT_BIT(12)
+/** @brief FIFO empty error fault flag */
+#define PL330_FT_FIFOEMPTY_ERR RT_BIT(13)
+/** @brief Instruction fetch error fault flag */
+#define PL330_FT_INSTR_FETCH_ERR RT_BIT(16)
+/** @brief Data write error fault flag */
+#define PL330_FT_DATA_WRITE_ERR RT_BIT(17)
+/** @brief Data read error fault flag */
+#define PL330_FT_DATA_READ_ERR RT_BIT(18)
+/** @brief Debug instruction fault flag */
+#define PL330_FT_DBG_INSTR RT_BIT(30)
+/** @brief Lockup error fault flag */
+#define PL330_FT_LOCKUP_ERR RT_BIT(31)
+/** @brief Channel [n] Status Register */
+#define PL330_REG_CSR(n) (0x100 + (n) * 0x8)
+/** @brief Channel stopped */
+#define PL330_CS_STOP 0x0
+/** @brief Channel executing */
+#define PL330_CS_EXEC 0x1
+/** @brief Channel cache miss */
+#define PL330_CS_CMISS 0x2
+/** @brief Channel updating PC */
+#define PL330_CS_UPDTPC 0x3
+/** @brief Channel waiting for event */
+#define PL330_CS_WFE 0x4
+/** @brief Channel at barrier */
+#define PL330_CS_ATBRR 0x5
+/** @brief Channel queue busy */
+#define PL330_CS_QBUSY 0x6
+/** @brief Channel waiting for peripheral */
+#define PL330_CS_WFP 0x7
+/** @brief Channel killed */
+#define PL330_CS_KILL 0x8
+/** @brief Channel completed */
+#define PL330_CS_CMPLT 0x9
+/** @brief Channel fault completed */
+#define PL330_CS_FLTCMP 0xe
+/** @brief Channel fault */
+#define PL330_CS_FAULT 0xf
+/** @brief Channel [n] Program Counter Register */
+#define PL330_REG_CPC(n) (0x104 + (n) * 0x8)
+/** @brief Channel [n] Source Address Register */
+#define pl330_REG_SAR(n) (0x0400 + (n) * 0x20)
+/** @brief Channel [n] Destination Address Register */
+#define pl330_REG_DAR(n) (0x0404 + (n) * 0x20)
+/** @brief Channel [n] Channel Control Register */
+#define pl330_REG_CCR(n) (0x0408 + (n) * 0x20)
+/** @brief Channel [n] Loop Counter 0 Register */
+#define pl330_REG_LC0(n) (0x040c + (n) * 0x20)
+/** @brief Channel [n] Loop Counter 1 Register */
+#define pl330_REG_LC1(n) (0x0410 + (n) * 0x20)
+/** @brief Debug Status Register */
+#define PL330_REG_DBGSTATUS 0xd00
+/** @brief Debug unit idle */
+#define PL330_DBGSTATUS_IDLE 0
+/** @brief Debug unit busy */
+#define PL330_DBGSTATUS_BUSY RT_BIT(0)
+/** @brief Debug Command Register */
+#define PL330_REG_DBGCMD 0xd04
+/** @brief Debug Instruction-0 Register */
+#define PL330_REG_DBGINST0 0xd08
+/** @brief Debug Instruction-1 Register */
+#define PL330_REG_DBGINST1 0xd0c
+/** @brief Configuration Register [n] */
+#define PL330_REG_CR(n) (0xe00 + (n) * 0x4)
+/** @brief Peripheral request set at reset */
+#define PL330_CR0_PERIPH_REQ_SET RT_BIT(0)
+/** @brief Manager non-secure at reset flag */
+#define PL330_CR0_MGR_NS_AT_RST(x) _FIELD_READ(2, 2, x)
+/** @brief Number of channels field */
+#define PL330_CR0_NUM_CHNLS(x) _FIELD_READ(6, 4, x)
+/** @brief Number of peripherals field */
+#define PL330_CR0_NUM_PERIPH(x) _FIELD_READ(16, 12, x)
+/** @brief Number of events field */
+#define PL330_CR0_NUM_EVENTS(x) _FIELD_READ(21, 17, x)
+/** @brief Configuration Data Register */
+#define PL330_REG_CRD 0x0e14
+/** @brief Data width field */
+#define PL330_CRD_DATA_WIDTH(x) _FIELD_READ(2, 0, x)
+/** @brief Write capability field */
+#define PL330_CRD_WR_CAP(x) _FIELD_READ(6, 4, x)
+/** @brief Write queue depth field */
+#define PL330_CRD_WR_Q_DEP(x) _FIELD_READ(11, 8, x)
+/** @brief Read capability field */
+#define PL330_CRD_RD_CAP(x) _FIELD_READ(14, 12, x)
+/** @brief Read queue depth field */
+#define PL330_CRD_RD_Q_DEP(x) _FIELD_READ(19, 16, x)
+/** @brief Data buffer depth field */
+#define PL330_CRD_DATA_BUFFER_DEP(x) _FIELD_READ(29, 20, x)
+/** @brief DMA Watchdog Register */
+#define PL330_REG_WDT 0x0e80
+/** @brief Peripheral ID Register */
+#define PL330_REG_PERIPH_ID 0x0fe0
+/** @brief Peripheral revision field */
+#define PL330_PERIPH_REV(x) _FIELD_READ(23, 20, x)
+/** @brief PL330 revision r0p0 */
+#define PL330_PERIPH_REV_R0P0 0
+/** @brief PL330 revision r1p0 */
+#define PL330_PERIPH_REV_R1P0 1
+/** @brief PL330 revision r1p1 */
+#define PL330_PERIPH_REV_R1P1 2
-#define PL330_CMD_DMAADDH 0x54
-#define PL330_CMD_DMAEND 0x00
-#define PL330_CMD_DMAFLUSHP 0x35
-#define PL330_CMD_DMAGO 0xa0
-#define PL330_CMD_DMALD 0x04
-#define PL330_CMD_DMALDP 0x25
-#define PL330_CMD_DMALP 0x20
-#define PL330_CMD_DMALPEND 0x28
-#define PL330_CMD_DMAKILL 0x01
-#define PL330_CMD_DMAMOV 0xbc
-#define PL330_CMD_DMANOP 0x18
-#define PL330_CMD_DMARMB 0x12
-#define PL330_CMD_DMASEV 0x34
-#define PL330_CMD_DMAST 0x08
-#define PL330_CMD_DMASTP 0x29
-#define PL330_CMD_DMASTZ 0x0c
-#define PL330_CMD_DMAWFE 0x36
-#define PL330_CMD_DMAWFP 0x30
-#define PL330_CMD_DMAWMB 0x13
+/** @brief Microcode instruction: add halfword to address */
+#define PL330_CMD_DMAADDH 0x54
+/** @brief Microcode instruction: end program */
+#define PL330_CMD_DMAEND 0x00
+/** @brief Microcode instruction: flush pipeline */
+#define PL330_CMD_DMAFLUSHP 0x35
+/** @brief Microcode instruction: go (execute channel) */
+#define PL330_CMD_DMAGO 0xa0
+/** @brief Microcode instruction: load (read from source) */
+#define PL330_CMD_DMALD 0x04
+/** @brief Microcode instruction: load peripheral */
+#define PL330_CMD_DMALDP 0x25
+/** @brief Microcode instruction: loop */
+#define PL330_CMD_DMALP 0x20
+/** @brief Microcode instruction: loop end */
+#define PL330_CMD_DMALPEND 0x28
+/** @brief Microcode instruction: kill */
+#define PL330_CMD_DMAKILL 0x01
+/** @brief Microcode instruction: move immediate */
+#define PL330_CMD_DMAMOV 0xbc
+/** @brief Microcode instruction: no operation */
+#define PL330_CMD_DMANOP 0x18
+/** @brief Microcode instruction: memory barrier */
+#define PL330_CMD_DMARMB 0x12
+/** @brief Microcode instruction: send event */
+#define PL330_CMD_DMASEV 0x34
+/** @brief Microcode instruction: store (write to dest) */
+#define PL330_CMD_DMAST 0x08
+/** @brief Microcode instruction: store peripheral */
+#define PL330_CMD_DMASTP 0x29
+/** @brief Microcode instruction: store zero */
+#define PL330_CMD_DMASTZ 0x0c
+/** @brief Microcode instruction: wait for event */
+#define PL330_CMD_DMAWFE 0x36
+/** @brief Microcode instruction: wait for peripheral */
+#define PL330_CMD_DMAWFP 0x30
+/** @brief Microcode instruction: write memory barrier */
+#define PL330_CMD_DMAWMB 0x13
-#define PL330_SIZE_DMAADDH 3
-#define PL330_SIZE_DMAEND 1
-#define PL330_SIZE_DMAFLUSHP 2
-#define PL330_SIZE_DMALD 1
-#define PL330_SIZE_DMALDP 2
-#define PL330_SIZE_DMALP 2
-#define PL330_SIZE_DMALPEND 2
-#define PL330_SIZE_DMAKILL 1
-#define PL330_SIZE_DMAMOV 6
-#define PL330_SIZE_DMANOP 1
-#define PL330_SIZE_DMARMB 1
-#define PL330_SIZE_DMASEV 2
-#define PL330_SIZE_DMAST 1
-#define PL330_SIZE_DMASTP 2
-#define PL330_SIZE_DMASTZ 1
-#define PL330_SIZE_DMAWFE 2
-#define PL330_SIZE_DMAWFP 2
-#define PL330_SIZE_DMAWMB 1
-#define PL330_SIZE_DMAGO 6
+/** @brief DMAMOV to Source Address Register */
+#define PL330_DIR_SAR 0
+/** @brief DMAMOV to Channel Control Register */
+#define PL330_DIR_CCR 1
+/** @brief DMAMOV to Destination Address Register */
+#define PL330_DIR_DAR 2
-#define PL330_DIR_SAR 0
-#define PL330_DIR_CCR 1
-#define PL330_DIR_DAR 2
+/** @brief Source address increment flag in CCR */
+#define PL330_SRC_INC RT_BIT(0)
+/** @brief Source burst size shift in CCR */
+#define PL330_SRC_BURST_SIZE_SHIFT 1
+/** @brief Source burst length shift in CCR */
+#define PL330_SRC_BURST_LEN_SHIFT 4
+/** @brief Destination address increment flag in CCR */
+#define PL330_DST_INC RT_BIT(14)
+/** @brief Destination burst size shift in CCR */
+#define PL330_DST_BURST_SIZE_SHIFT 15
+/** @brief Destination burst length shift in CCR */
+#define PL330_DST_BURST_LEN_SHIFT 18
-#define PL330_SRC_INC RT_BIT(0)
-#define PL330_SRC_BURST_SIZE_SHIFT 1
-#define PL330_SRC_BURST_LEN_SHIFT 4
-#define PL330_DST_INC RT_BIT(14)
-#define PL330_DST_BURST_SIZE_SHIFT 15
-#define PL330_DST_BURST_LEN_SHIFT 18
+/** @brief Single transfer condition */
+#define PL330_COND_SINGLE 0
+/** @brief Burst transfer condition */
+#define PL330_COND_BURST 1
+/** @brief Always execute (unconditional) */
+#define PL330_COND_ALWAYS 2
-#define PL330_COND_SINGLE 0
-#define PL330_COND_BURST 1
-#define PL330_COND_ALWAYS 2
-
-#define PL330_MICROCODE_SIZE 128
-#define AMBA_NR_IRQS 9
+/** @brief Maximum microcode program size in bytes (128 instructions) */
+#define PL330_MICROCODE_SIZE 128
+/** @brief Maximum number of AMBA IRQs */
+#define AMBA_NR_IRQS 9
+/**
+ * @brief PL330 DMA channel descriptor
+ *
+ * Extends the generic DMA channel with microcode buffer management.
+ * The microcode is generated at prep time and stored in microcode_raw
+ * with alignment padding.
+ */
struct pl330_chan
{
- struct rt_dma_chan parent;
+ struct rt_dma_chan parent; /**< Generic DMA channel */
- rt_bool_t enabled;
- rt_size_t size;
+ rt_bool_t enabled; /**< Channel is currently in use */
+ rt_size_t size; /**< Transfer size for callback reporting */
- void *microcode;
- rt_size_t microcode_len;
- rt_ubase_t microcode_dma;
- rt_uint8_t microcode_raw[PL330_MICROCODE_SIZE + 4]; /* For align */
+ void *microcode; /**< Aligned microcode buffer pointer */
+ rt_size_t microcode_len; /**< Microcode length in bytes */
+ rt_ubase_t microcode_dma; /**< Physical address of microcode buffer */
+ rt_uint8_t microcode_raw[PL330_MICROCODE_SIZE + 4]; /**< Raw microcode storage (+4 for alignment) */
};
+/**
+ * @brief PL330 DMA controller private data
+ */
struct pl330
{
- struct rt_dma_controller parent;
+ struct rt_dma_controller parent; /**< Generic DMA controller */
- void *regs;
- int irqs_nr;
- int irqs[AMBA_NR_IRQS];
+ void *regs; /**< MMIO register base */
+ int irqs_nr; /**< Number of IRQ lines */
+ int irqs[AMBA_NR_IRQS]; /**< IRQ numbers */
-#define PL330_QUIRK_BROKEN_NO_FLUSHP RT_BIT(0)
-#define PL330_QUIRK_PERIPH_BURST RT_BIT(1)
- rt_uint32_t quirk;
+#define PL330_QUIRK_BROKEN_NO_FLUSHP RT_BIT(0) /**< Quirk: broken FLUSHP instruction */
+#define PL330_QUIRK_PERIPH_BURST RT_BIT(1) /**< Quirk: peripheral burst support */
+ rt_uint32_t quirk; /**< Hardware quirk flags */
- rt_uint32_t mode_ns;
- rt_uint32_t num_chan;
- rt_uint32_t num_peri;
- rt_uint32_t num_events;
- rt_uint32_t data_width;
- rt_uint32_t data_buffer_dep;
- rt_uint32_t ins;
+ rt_uint32_t mode_ns; /**< Non-secure mode flag */
+ rt_uint32_t num_chan; /**< Number of DMA channels */
+ rt_uint32_t num_peri; /**< Number of peripheral interfaces */
+ rt_uint32_t num_events; /**< Number of event interfaces */
+ rt_uint32_t data_width; /**< Data bus width in bits */
+ rt_uint32_t data_buffer_dep; /**< Data buffer depth in entries */
+ rt_uint32_t ins; /**< Instruction space configuration */
- struct pl330_chan *chans;
+ struct pl330_chan *chans; /**< Array of channel descriptors */
- struct rt_clk *pclk;
- struct rt_reset_control *rstc;
- struct rt_reset_control *rstc_ocp;
+ struct rt_clk *pclk; /**< APB peripheral clock */
+ struct rt_reset_control *rstc; /**< DMA reset control */
+ struct rt_reset_control *rstc_ocp; /**< OCP (Open Core Protocol) reset control */
};
-#define raw_to_pl330(raw) rt_container_of(raw, struct pl330, parent)
+/** @brief Cast from rt_dma_controller to pl330 */
+#define raw_to_pl330(raw) rt_container_of(raw, struct pl330, parent)
+/**
+ * @brief Read PL330 configuration from hardware registers
+ *
+ * Extracts channel count, event count, peripheral count, data width,
+ * and data buffer depth from the configuration registers (CR0, CRD).
+ *
+ * @param[in] pl330 PL330 controller instance (updated in place)
+ */
static void pl330_read_config(struct pl330 *pl330)
{
rt_uint32_t value;
@@ -208,6 +326,17 @@ static void pl330_read_config(struct pl330 *pl330)
pl330->ins = HWREG32(regs + PL330_REG_CR(3));
}
+/**
+ * @brief Configure the PL330 Channel Control Register value from a slave config
+ *
+ * Sets source/destination increment, burst sizes, and address widths
+ * based on the DMA slave configuration.
+ *
+ * @param[in] conf DMA slave configuration
+ * @param[out] ccr Computed CCR value
+ *
+ * @return RT_EOK on success, -RT_EINVAL if address width is unsupported
+ */
static rt_err_t pl330_ccr_config(struct rt_dma_slave_config *conf, rt_uint32_t *ccr)
{
*ccr = PL330_SRC_INC | PL330_DST_INC;
@@ -249,6 +378,18 @@ static rt_err_t pl330_ccr_config(struct rt_dma_slave_config *conf, rt_uint32_t *
return RT_EOK;
}
+/**
+ * @brief Emit a DMAMOV microcode instruction
+ *
+ * DMAMOV writes an immediate 32-bit value to a register (SAR, CCR, or DAR).
+ * Encoding: 6 bytes.
+ *
+ * @param[in] microcode Output buffer (advanced by 6 bytes)
+ * @param[in] rd Destination register (PL330_DIR_SAR/CCR/DAR)
+ * @param[in] imm 32-bit immediate value
+ *
+ * @return Number of bytes written (always 6)
+ */
static int pl330_cmd_dmamov(rt_uint8_t *microcode, rt_uint8_t rd, rt_uint32_t imm)
{
/*
@@ -269,15 +410,24 @@ static int pl330_cmd_dmamov(rt_uint8_t *microcode, rt_uint8_t rd, rt_uint32_t im
*microcode++ = imm >> 16;
*microcode++ = imm >> 24;
- LOG_D("DMAMOV %s, %#x", ((const char *const []){
- [PL330_DIR_SAR] = "SAR",
- [PL330_DIR_CCR] = "CCR",
- [PL330_DIR_DAR] = "DAR"
- })[rd], imm);
+ LOG_D("DMAMOV %s, %#x", ((const char * const[]){ [PL330_DIR_SAR] = "SAR", [PL330_DIR_CCR] = "CCR", [PL330_DIR_DAR] = "DAR" })[rd], imm);
return PL330_SIZE_DMAMOV;
}
+/**
+ * @brief Emit a DMALD microcode instruction
+ *
+ * DMALD loads data from the source address. Condition codes:
+ * - SINGLE: single transfer, end if last
+ * - BURST: burst transfer, end if last
+ * - ALWAYS: unconditional (no end check)
+ *
+ * @param[in] microcode Output buffer (advanced by 1 byte)
+ * @param[in] cond Transfer condition (PL330_COND_*)
+ *
+ * @return Number of bytes written (always 1)
+ */
static int pl330_cmd_dmald(rt_uint8_t *microcode, rt_uint32_t cond)
{
/*
@@ -296,15 +446,25 @@ static int pl330_cmd_dmald(rt_uint8_t *microcode, rt_uint32_t cond)
*microcode |= (1 << 1) | (1 << 0);
}
- LOG_D("DMALD %c", ((char []){
- [PL330_COND_SINGLE] = 'S',
- [PL330_COND_BURST] = 'B',
- [PL330_COND_ALWAYS] = 'A'
- })[cond]);
+ LOG_D("DMALD %c", ((char[]){
+ [PL330_COND_SINGLE] = 'S',
+ [PL330_COND_BURST] = 'B',
+ [PL330_COND_ALWAYS] = 'A' })[cond]);
return PL330_SIZE_DMALD;
}
+/**
+ * @brief Emit a DMAST microcode instruction
+ *
+ * DMAST stores data to the destination address. Same condition
+ * semantics as DMALD.
+ *
+ * @param[in] microcode Output buffer (advanced by 1 byte)
+ * @param[in] cond Transfer condition
+ *
+ * @return Number of bytes written (always 1)
+ */
static int pl330_cmd_dmast(rt_uint8_t *microcode, rt_uint32_t cond)
{
/*
@@ -323,15 +483,26 @@ static int pl330_cmd_dmast(rt_uint8_t *microcode, rt_uint32_t cond)
*microcode |= (1 << 1) | (1 << 0);
}
- LOG_D("DMAST %c", ((char []){
- [PL330_COND_SINGLE] = 'S',
- [PL330_COND_BURST] = 'B',
- [PL330_COND_ALWAYS] = 'A'
- })[cond]);
+ LOG_D("DMAST %c", ((char[]){
+ [PL330_COND_SINGLE] = 'S',
+ [PL330_COND_BURST] = 'B',
+ [PL330_COND_ALWAYS] = 'A' })[cond]);
return PL330_SIZE_DMAST;
}
+/**
+ * @brief Emit a DMALP (loop begin) microcode instruction
+ *
+ * Sets up a counted loop using one of the two loop counters (LC0 or LC1).
+ * The loop body follows immediately after.
+ *
+ * @param[in] microcode Output buffer (advanced by 2 bytes)
+ * @param[in] lc Loop counter index (0 or 1)
+ * @param[in] loops Number of iterations (1-256, stored as loops-1)
+ *
+ * @return Number of bytes written (always 2)
+ */
static int pl330_cmd_dmalp(rt_uint8_t *microcode, rt_uint8_t lc, rt_uint16_t loops)
{
/*
@@ -347,8 +518,24 @@ static int pl330_cmd_dmalp(rt_uint8_t *microcode, rt_uint8_t lc, rt_uint16_t loo
return PL330_SIZE_DMALP;
}
+/**
+ * @brief Emit a DMALPEND (loop end) microcode instruction
+ *
+ * Closes a loop by branching back to the DMALP instruction. Supports:
+ * - Forever loops (nf=0): never terminates
+ * - Conditional termination based on loop counter
+ * - Non-forever loops (nf=1): terminates when counter reaches 0
+ *
+ * @param[in] microcode Output buffer (advanced by 2 bytes)
+ * @param[in] cond Loop termination condition
+ * @param[in] forever RT_TRUE for infinite loop, RT_FALSE for counted
+ * @param[in] loop Which loop counter to test (0=LC0, 1=LC1)
+ * @param[in] bjump Backward jump offset in bytes
+ *
+ * @return Number of bytes written (always 2)
+ */
static int pl330_cmd_dmalpend(rt_uint8_t *microcode, rt_uint32_t cond,
- rt_bool_t forever, rt_uint32_t loop, rt_uint8_t bjump)
+ rt_bool_t forever, rt_uint32_t loop, rt_uint8_t bjump)
{
/*
* DMALPEND encoding
@@ -380,15 +567,22 @@ static int pl330_cmd_dmalpend(rt_uint8_t *microcode, rt_uint32_t cond,
*microcode = bjump;
- LOG_D("DMALPEND %c (%sloop: %c bjump: %d)", ((char []){
- [PL330_COND_SINGLE] = 'S',
- [PL330_COND_BURST] = 'B',
- [PL330_COND_ALWAYS] = 'A'
- })[cond], forever ? "FE, " : "", loop ? 'Y' : 'N', bjump);
+ LOG_D("DMALPEND %c (%sloop: %c bjump: %d)", ((char[]){ [PL330_COND_SINGLE] = 'S', [PL330_COND_BURST] = 'B', [PL330_COND_ALWAYS] = 'A' })[cond], forever ? "FE, " : "", loop ? 'Y' : 'N', bjump);
return PL330_SIZE_DMALPEND;
}
+/**
+ * @brief Emit a DMASEV microcode instruction
+ *
+ * Sends an event to the specified event interface, triggering an
+ * interrupt on the IRQ line corresponding to the channel.
+ *
+ * @param[in] microcode Output buffer (advanced by 2 bytes)
+ * @param[in] event Event number (0-31)
+ *
+ * @return Number of bytes written (always 2)
+ */
static int pl330_cmd_dmasev(rt_uint8_t *microcode, int event)
{
*microcode++ = PL330_CMD_DMASEV;
@@ -399,6 +593,15 @@ static int pl330_cmd_dmasev(rt_uint8_t *microcode, int event)
return PL330_SIZE_DMASEV;
}
+/**
+ * @brief Emit a DMAEND microcode instruction
+ *
+ * Terminates the microcode program and signals completion.
+ *
+ * @param[in] microcode Output buffer (advanced by 1 byte)
+ *
+ * @return Number of bytes written (always 1)
+ */
static int pl330_cmd_dmaend(rt_uint8_t *microcode)
{
/*
@@ -413,13 +616,34 @@ static int pl330_cmd_dmaend(rt_uint8_t *microcode)
return PL330_SIZE_DMAEND;
}
+/**
+ * @brief Get the hardware channel ID from a pl330_chan pointer
+ *
+ * @param[in] pl330 PL330 controller
+ * @param[in] pc Channel descriptor
+ *
+ * @return Zero-based channel index
+ */
static rt_uint32_t pl330_chan_id(struct pl330 *pl330, struct pl330_chan *pc)
{
return pc - pl330->chans;
}
+/**
+ * @brief Request a DMA channel from the PL330 controller
+ *
+ * If fw_data (device tree args) specifies a channel number, that
+ * specific channel is requested. Otherwise any unallocated channel
+ * is used. Marks the channel as enabled and unmasks its IRQ.
+ *
+ * @param[in] ctrl DMA controller
+ * @param[in] slave Requesting device
+ * @param[in] fw_data Device tree cell args (may be NULL for mem-to-mem)
+ *
+ * @return DMA channel pointer, or RT_NULL/error pointer on failure
+ */
static struct rt_dma_chan *pl330_dma_request_chan(struct rt_dma_controller *ctrl,
- struct rt_device *slave, void *fw_data)
+ struct rt_device *slave, void *fw_data)
{
int idx = -1;
struct pl330_chan *pc;
@@ -466,6 +690,13 @@ _found:
return &pc->parent;
}
+/**
+ * @brief Release a previously requested PL330 DMA channel
+ *
+ * @param[in] chan DMA channel to release
+ *
+ * @return RT_EOK
+ */
static rt_err_t pl330_dma_release_chan(struct rt_dma_chan *chan)
{
struct pl330_chan *pc = rt_container_of(chan, struct pl330_chan, parent);
@@ -475,6 +706,17 @@ static rt_err_t pl330_dma_release_chan(struct rt_dma_chan *chan)
return RT_EOK;
}
+/**
+ * @brief Start a DMA transfer on a PL330 channel
+ *
+ * Waits for the debug unit to become idle, flushes the microcode
+ * buffer from cache, and issues a DMAGO instruction via the debug
+ * registers.
+ *
+ * @param[in] chan DMA channel (must be prepared)
+ *
+ * @return RT_EOK
+ */
static rt_err_t pl330_dma_start(struct rt_dma_chan *chan)
{
struct pl330_chan *pc = rt_container_of(chan, struct pl330_chan, parent);
@@ -494,6 +736,16 @@ static rt_err_t pl330_dma_start(struct rt_dma_chan *chan)
return RT_EOK;
}
+/**
+ * @brief Stop a DMA transfer on a PL330 channel
+ *
+ * Issues a DMAKILL instruction via the debug registers to abort
+ * the channel's current transfer.
+ *
+ * @param[in] chan DMA channel to stop
+ *
+ * @return RT_EOK
+ */
static rt_err_t pl330_dma_stop(struct rt_dma_chan *chan)
{
struct pl330_chan *pc = rt_container_of(chan, struct pl330_chan, parent);
@@ -511,14 +763,49 @@ static rt_err_t pl330_dma_stop(struct rt_dma_chan *chan)
return RT_EOK;
}
+/**
+ * @brief Configure a PL330 DMA channel (no-op for PL330)
+ *
+ * The PL330 applies configuration at prep time via CCR values
+ * embedded in the microcode. No separate config step is needed.
+ *
+ * @param[in] chan DMA channel
+ * @param[in] conf Slave configuration (unused)
+ *
+ * @return RT_EOK
+ */
static rt_err_t pl330_dma_config(struct rt_dma_chan *chan,
- struct rt_dma_slave_config *conf)
+ struct rt_dma_slave_config *conf)
{
return RT_EOK;
}
+/**
+ * @brief Prepare a memory-to-memory transfer on a PL330 channel
+ *
+ * Generates microcode:
+ * DMAMOV CCR, <ccr_value>
+ * DMAMOV SAR, <src_addr>
+ * DMAMOV DAR, <dst_addr>
+ * DMALP 1, <outer> ; outer loop
+ * DMALP 0, <inner_first> ; inner loop (up to 256 bursts)
+ * DMALD A ; load burst from source
+ * DMAST A ; store burst to dest
+ * DMALPEND ; end inner loop
+ * [remainder loop if len > 256 bursts]
+ * DMALPEND ; end outer loop
+ * DMASEV <channel_id> ; signal completion interrupt
+ * DMAEND
+ *
+ * @param[in] chan DMA channel
+ * @param[in] dma_addr_src Source physical address
+ * @param[in] dma_addr_dst Destination physical address
+ * @param[in] len Transfer length in bytes
+ *
+ * @return RT_EOK on success, -RT_EINVAL if parameters are invalid
+ */
static rt_err_t pl330_dma_prep_memcpy(struct rt_dma_chan *chan,
- rt_ubase_t dma_addr_src, rt_ubase_t dma_addr_dst, rt_size_t len)
+ rt_ubase_t dma_addr_src, rt_ubase_t dma_addr_dst, rt_size_t len)
{
void *mc;
rt_err_t err;
@@ -611,9 +898,34 @@ static rt_err_t pl330_dma_prep_memcpy(struct rt_dma_chan *chan,
return RT_EOK;
}
+/**
+ * @brief Prepare a cyclic (infinite loop) transfer on a PL330 channel
+ *
+ * Generates microcode with an infinite outer loop and a finite inner loop
+ * per period. Each period completion triggers a DMASEV interrupt.
+ *
+ * Microcode structure:
+ * DMAMOV CCR, ...
+ * DMAMOV SAR/DAR, ...
+ * DMALP 1, 0 ; infinite outer loop
+ * DMALP 0, <period_loop> ; inner loop per period
+ * DMALD A
+ * DMAST A
+ * DMALPEND ; end inner loop
+ * DMASEV <channel_id> ; signal period done
+ * DMALPEND ; end outer loop (forever)
+ *
+ * @param[in] chan DMA channel
+ * @param[in] dma_buf_addr Buffer address for device-side data
+ * @param[in] buf_len Total buffer length in bytes
+ * @param[in] period_len Bytes per period
+ * @param[in] dir Transfer direction (MEM_TO_DEV or DEV_TO_MEM)
+ *
+ * @return RT_EOK on success, -RT_EINVAL on invalid parameters
+ */
static rt_err_t pl330_dma_prep_cyclic(struct rt_dma_chan *chan,
- rt_ubase_t dma_buf_addr, rt_size_t buf_len, rt_size_t period_len,
- enum rt_dma_transfer_direction dir)
+ rt_ubase_t dma_buf_addr, rt_size_t buf_len, rt_size_t period_len,
+ enum rt_dma_transfer_direction dir)
{
void *mc;
rt_err_t err;
@@ -700,9 +1012,29 @@ static rt_err_t pl330_dma_prep_cyclic(struct rt_dma_chan *chan,
return RT_EOK;
}
+/**
+ * @brief Prepare a single (one-shot) transfer on a PL330 channel
+ *
+ * Generates a simple counted loop microcode program:
+ * DMAMOV CCR, ...
+ * DMAMOV SAR/DAR, ...
+ * DMALP 0, <loop>
+ * DMALD A
+ * DMAST A
+ * DMALPEND
+ * DMASEV <channel_id>
+ * DMAEND
+ *
+ * @param[in] chan DMA channel
+ * @param[in] dma_buf_addr Buffer address for device-side data
+ * @param[in] buf_len Transfer length in bytes
+ * @param[in] dir Transfer direction
+ *
+ * @return RT_EOK on success, -RT_EINVAL on invalid parameters
+ */
static rt_err_t pl330_dma_prep_single(struct rt_dma_chan *chan,
- rt_ubase_t dma_buf_addr, rt_size_t buf_len,
- enum rt_dma_transfer_direction dir)
+ rt_ubase_t dma_buf_addr, rt_size_t buf_len,
+ enum rt_dma_transfer_direction dir)
{
void *mc;
rt_err_t err;
@@ -757,10 +1089,10 @@ static rt_err_t pl330_dma_prep_single(struct rt_dma_chan *chan,
mc += pl330_cmd_dmast(mc, PL330_COND_ALWAYS);
mc += pl330_cmd_dmalpend(mc,
- PL330_COND_ALWAYS,
- RT_FALSE,
- 0, /* LC0 */
- mc - pc->microcode - ljmp);
+ PL330_COND_ALWAYS,
+ RT_FALSE,
+ 0, /* LC0 */
+ mc - pc->microcode - ljmp);
mc += pl330_cmd_dmasev(mc, pl330_chan_id(pl330, pc));
@@ -772,8 +1104,8 @@ static rt_err_t pl330_dma_prep_single(struct rt_dma_chan *chan,
return RT_EOK;
}
-static const struct rt_dma_controller_ops pl330_dma_ops =
-{
+/** @brief PL330 DMA controller operations vtable */
+static const struct rt_dma_controller_ops pl330_dma_ops = {
.request_chan = pl330_dma_request_chan,
.release_chan = pl330_dma_release_chan,
.start = pl330_dma_start,
@@ -784,6 +1116,18 @@ static const struct rt_dma_controller_ops pl330_dma_ops =
.prep_single = pl330_dma_prep_single,
};
+/**
+ * @brief PL330 interrupt service routine
+ *
+ * Handles all channel interrupts. Reads the Interrupt Status Register
+ * to identify which channel triggered, then checks the Channel Status
+ * Register:
+ * - PL330_CS_CMPLT / PL330_CS_STOP: transfer completed successfully
+ * - PL330_CS_FAULT / PL330_CS_FLTCMP: transfer faulted
+ *
+ * @param[in] irqno Hardware IRQ number
+ * @param[in] params Controller instance pointer
+ */
static void pl330_isr(int irqno, void *params)
{
rt_uint32_t isr, csr;
@@ -825,6 +1169,14 @@ static void pl330_isr(int irqno, void *params)
}
}
+/**
+ * @brief Free all resources allocated by the PL330 driver
+ *
+ * Unmaps registers, disables the clock, asserts reset controls,
+ * and frees the channel array and controller structure.
+ *
+ * @param[in] pl330 PL330 controller instance
+ */
static void pl330_free(struct pl330 *pl330)
{
if (pl330->regs)
@@ -858,6 +1210,25 @@ static void pl330_free(struct pl330 *pl330)
rt_free(pl330);
}
+/**
+ * @brief Probe a PL330 DMA controller platform device
+ *
+ * Initialization sequence:
+ * 1. Allocate pl330 structure
+ * 2. Map MMIO registers
+ * 3. Get IRQ lines
+ * 4. Enable APB clock
+ * 5. Deassert reset controls (dma, dma-ocp)
+ * 6. Read hardware quirks from device tree
+ * 7. Read hardware configuration (channel count, data width, etc.)
+ * 8. Allocate channel descriptors with microcode buffer alignment
+ * 9. Register with the DMA framework
+ * 10. Install and unmask IRQ handlers
+ *
+ * @param[in] pdev Platform device matching "arm,pl330"
+ *
+ * @return RT_EOK on success, error code otherwise
+ */
static rt_err_t pl330_probe(struct rt_platform_device *pdev)
{
rt_err_t err;
@@ -1004,6 +1375,16 @@ _fail:
return err;
}
+/**
+ * @brief Remove a PL330 DMA controller platform device
+ *
+ * Masks and detaches all IRQs, unregisters from the DMA framework,
+ * and frees all resources.
+ *
+ * @param[in] pdev Platform device
+ *
+ * @return RT_EOK
+ */
static rt_err_t pl330_remove(struct rt_platform_device *pdev)
{
struct pl330 *pl330 = pdev->parent.user_data;
@@ -1021,14 +1402,14 @@ static rt_err_t pl330_remove(struct rt_platform_device *pdev)
return RT_EOK;
}
-static const struct rt_ofw_node_id pl330_ofw_ids[] =
-{
+/** @brief Device tree compatible IDs for the PL330 driver */
+static const struct rt_ofw_node_id pl330_ofw_ids[] = {
{ .compatible = "arm,pl330" },
{ /* sentinel */ }
};
-static struct rt_platform_driver pl330_driver =
-{
+/** @brief PL330 platform driver descriptor */
+static struct rt_platform_driver pl330_driver = {
.name = "dma-pl330",
.ids = pl330_ofw_ids,
@@ -1036,6 +1417,11 @@ static struct rt_platform_driver pl330_driver =
.remove = pl330_remove,
};
+/**
+ * @brief Register the PL330 platform driver at boot
+ *
+ * @return 0
+ */
static int pl330_drv_register(void)
{
rt_platform_driver_register(&pl330_driver);
diff --git a/components/drivers/dma/dma.c b/components/drivers/dma/dma.c
index 76ec8d391b..cf98f75b69 100644
--- a/components/drivers/dma/dma.c
+++ b/components/drivers/dma/dma.c
@@ -8,6 +8,17 @@
* 2023-02-25 GuEe-GUI the first version
*/
+/**
+ * @file dma.c
+ * @brief DMA (Direct Memory Access) controller framework core API
+ *
+ * Provides a unified interface for DMA controller management, channel
+ * lifecycle, transfer configuration, and transfer preparation (memcpy,
+ * cyclic, single). Controllers register themselves via
+ * rt_dma_controller_register(), and slave devices request channels
+ * via rt_dma_chan_request().
+ */
+
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
@@ -16,9 +27,18 @@
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
+/** @brief Global list of registered DMA controllers */
static rt_list_t dmac_nodes = RT_LIST_OBJECT_INIT(dmac_nodes);
static RT_DEFINE_SPINLOCK(dmac_nodes_lock);
+/**
+ * @brief Acquire the DMA controller mutex lock
+ *
+ * Only acquires the lock in thread context (not ISR) since mutex
+ * operations are not ISR-safe.
+ *
+ * @param[in] ctrl DMA controller to lock
+ */
static void dma_lock(struct rt_dma_controller *ctrl)
{
if (rt_thread_self())
@@ -27,6 +47,11 @@ static void dma_lock(struct rt_dma_controller *ctrl)
}
}
+/**
+ * @brief Release the DMA controller mutex lock
+ *
+ * @param[in] ctrl DMA controller to unlock
+ */
static void dma_unlock(struct rt_dma_controller *ctrl)
{
if (rt_thread_self())
@@ -35,6 +60,19 @@ static void dma_unlock(struct rt_dma_controller *ctrl)
}
}
+/**
+ * @brief Register a DMA controller with the framework
+ *
+ * Validates that the controller has valid dev and ops pointers, and
+ * that at least one direction capability is set. The controller is
+ * added to the global dmac_nodes list for later channel allocation.
+ * If the device has a device tree node, firmware data is bound.
+ *
+ * @param[in] ctrl DMA controller to register
+ *
+ * @return RT_EOK on success, -RT_EINVAL if ctrl, dev, or ops are NULL
+ * or no direction capability is set
+ */
rt_err_t rt_dma_controller_register(struct rt_dma_controller *ctrl)
{
const char *dev_name;
@@ -73,6 +111,18 @@ rt_err_t rt_dma_controller_register(struct rt_dma_controller *ctrl)
return RT_EOK;
}
+/**
+ * @brief Unregister a DMA controller from the framework
+ *
+ * Fails with -RT_EBUSY if any channels are still active.
+ * Unbinds firmware data if device tree was used, detaches the mutex,
+ * and removes the controller from the global list.
+ *
+ * @param[in] ctrl DMA controller to unregister
+ *
+ * @return RT_EOK on success, -RT_EINVAL if ctrl is NULL,
+ * -RT_EBUSY if channels are still open
+ */
rt_err_t rt_dma_controller_unregister(struct rt_dma_controller *ctrl)
{
if (!ctrl)
@@ -103,6 +153,16 @@ rt_err_t rt_dma_controller_unregister(struct rt_dma_controller *ctrl)
return RT_EOK;
}
+/**
+ * @brief Start a DMA transfer on a channel
+ *
+ * The channel must have been successfully prepared (no prep_err).
+ *
+ * @param[in] chan DMA channel to start
+ *
+ * @return RT_EOK on success, -RT_EINVAL if chan is NULL,
+ * or the last preparation error code
+ */
rt_err_t rt_dma_chan_start(struct rt_dma_chan *chan)
{
rt_err_t err;
@@ -131,6 +191,16 @@ rt_err_t rt_dma_chan_start(struct rt_dma_chan *chan)
return err;
}
+/**
+ * @brief Pause a DMA transfer on a channel
+ *
+ * If the controller does not support pause, falls back to stop.
+ *
+ * @param[in] chan DMA channel to pause
+ *
+ * @return RT_EOK on success, -RT_EINVAL if chan is NULL,
+ * or the last preparation error code
+ */
rt_err_t rt_dma_chan_pause(struct rt_dma_chan *chan)
{
rt_err_t err;
@@ -165,6 +235,14 @@ rt_err_t rt_dma_chan_pause(struct rt_dma_chan *chan)
return err;
}
+/**
+ * @brief Stop a DMA transfer on a channel
+ *
+ * @param[in] chan DMA channel to stop
+ *
+ * @return RT_EOK on success, -RT_EINVAL if chan is NULL,
+ * or the last preparation error code
+ */
rt_err_t rt_dma_chan_stop(struct rt_dma_chan *chan)
{
rt_err_t err;
@@ -193,8 +271,21 @@ rt_err_t rt_dma_chan_stop(struct rt_dma_chan *chan)
return err;
}
+/**
+ * @brief Configure a DMA channel with slave-specific settings
+ *
+ * Validates direction, address width, and that the controller supports
+ * the requested direction. Saves the configuration to chan->conf on success.
+ *
+ * @param[in] chan DMA channel to configure
+ * @param[in] conf Slave configuration (direction, address widths, maxburst,
+ * source/destination addresses)
+ *
+ * @return RT_EOK on success, -RT_EINVAL on invalid parameters,
+ * -RT_ENOSYS if direction not supported
+ */
rt_err_t rt_dma_chan_config(struct rt_dma_chan *chan,
- struct rt_dma_slave_config *conf)
+ struct rt_dma_slave_config *conf)
{
rt_err_t err;
struct rt_dma_controller *ctrl;
@@ -232,7 +323,7 @@ rt_err_t rt_dma_chan_config(struct rt_dma_chan *chan,
if (!chan->name && dir != RT_DMA_MEM_TO_MEM)
{
LOG_E("%s: illegal config for uname channels",
- rt_dm_dev_get_name(ctrl->dev));
+ rt_dm_dev_get_name(ctrl->dev));
err = -RT_EINVAL;
goto _end;
@@ -255,6 +346,17 @@ _end:
return err;
}
+/**
+ * @brief Signal transfer completion on a DMA channel
+ *
+ * Calls the channel's registered callback with the transfer size.
+ * The controller driver calls this from its ISR when a transfer completes.
+ *
+ * @param[in] chan DMA channel that completed
+ * @param[in] size Number of bytes transferred (0 indicates an error)
+ *
+ * @return RT_EOK on success, -RT_EINVAL if chan is NULL
+ */
rt_err_t rt_dma_chan_done(struct rt_dma_chan *chan, rt_size_t size)
{
if (!chan)
@@ -270,8 +372,18 @@ rt_err_t rt_dma_chan_done(struct rt_dma_chan *chan, rt_size_t size)
return RT_EOK;
}
+/**
+ * @brief Check if an address falls below the configured boundary
+ *
+ * @param[in] name Controller name for error logging
+ * @param[in] desc Description of the address (e.g., "source", "dest")
+ * @param[in] addr0 Configured address
+ * @param[in] addr1 Transfer address to validate
+ *
+ * @return RT_TRUE if addr0 is below addr1 (illegal), RT_FALSE otherwise
+ */
static rt_bool_t range_is_illegal(const char *name, const char *desc,
- rt_ubase_t addr0, rt_ubase_t addr1)
+ rt_ubase_t addr0, rt_ubase_t addr1)
{
rt_bool_t illegal = addr0 < addr1;
@@ -283,8 +395,18 @@ static rt_bool_t range_is_illegal(const char *name, const char *desc,
return illegal;
}
+/**
+ * @brief Check if an address is within the controller's address mask
+ *
+ * @param[in] name Controller name for error logging
+ * @param[in] desc Description of the address (e.g., "source", "dest")
+ * @param[in] mask Controller's address mask
+ * @param[in] addr Address to validate
+ *
+ * @return RT_TRUE if any bits outside the mask are set (unsupported), RT_FALSE otherwise
+ */
static rt_bool_t addr_is_supported(const char *name, const char *desc,
- rt_uint64_t mask, rt_ubase_t addr)
+ rt_uint64_t mask, rt_ubase_t addr)
{
rt_bool_t illegal = !!(addr & ~mask);
@@ -296,8 +418,20 @@ static rt_bool_t addr_is_supported(const char *name, const char *desc,
return illegal;
}
+/**
+ * @brief Prepare a memory-to-memory DMA transfer
+ *
+ * Validates source and destination addresses against the controller's
+ * address mask and the configured boundary ranges.
+ *
+ * @param[in] chan DMA channel (must be configured for RT_DMA_MEM_TO_MEM)
+ * @param[in] transfer Transfer descriptor (src_addr, dst_addr, buffer_len)
+ *
+ * @return RT_EOK on success, -RT_EINVAL on invalid parameters or address
+ * range violations, -RT_ENOSYS if prep_memcpy not supported
+ */
rt_err_t rt_dma_prep_memcpy(struct rt_dma_chan *chan,
- struct rt_dma_slave_transfer *transfer)
+ struct rt_dma_slave_transfer *transfer)
{
rt_err_t err;
rt_size_t len;
@@ -326,25 +460,25 @@ rt_err_t rt_dma_prep_memcpy(struct rt_dma_chan *chan,
len = transfer->buffer_len;
if (addr_is_supported(rt_dm_dev_get_name(ctrl->dev), "source",
- ctrl->addr_mask, conf->src_addr))
+ ctrl->addr_mask, conf->src_addr))
{
return -RT_ENOSYS;
}
if (addr_is_supported(rt_dm_dev_get_name(ctrl->dev), "dest",
- ctrl->addr_mask, conf->dst_addr))
+ ctrl->addr_mask, conf->dst_addr))
{
return -RT_ENOSYS;
}
if (range_is_illegal(rt_dm_dev_get_name(ctrl->dev), "source",
- dma_addr_src, conf->src_addr))
+ dma_addr_src, conf->src_addr))
{
return -RT_EINVAL;
}
if (range_is_illegal(rt_dm_dev_get_name(ctrl->dev), "dest",
- dma_addr_dst, conf->dst_addr))
+ dma_addr_dst, conf->dst_addr))
{
return -RT_EINVAL;
}
@@ -372,8 +506,21 @@ rt_err_t rt_dma_prep_memcpy(struct rt_dma_chan *chan,
return err;
}
+/**
+ * @brief Prepare a cyclic (repeating) DMA transfer
+ *
+ * Cyclic transfers repeat automatically, useful for audio, ADC/DAC
+ * streaming, and other periodic data transfers. The buffer is divided
+ * into periods; each period completion triggers a callback.
+ *
+ * @param[in] chan DMA channel
+ * @param[in] transfer Transfer descriptor with buffer details and period_len
+ *
+ * @return RT_EOK on success, -RT_EINVAL on invalid parameters,
+ * -RT_ENOSYS if prep_cyclic not supported
+ */
rt_err_t rt_dma_prep_cyclic(struct rt_dma_chan *chan,
- struct rt_dma_slave_transfer *transfer)
+ struct rt_dma_slave_transfer *transfer)
{
rt_err_t err;
rt_ubase_t dma_buf_addr;
@@ -403,13 +550,13 @@ rt_err_t rt_dma_prep_cyclic(struct rt_dma_chan *chan,
dma_buf_addr = transfer->src_addr;
if (addr_is_supported(rt_dm_dev_get_name(ctrl->dev), "source",
- ctrl->addr_mask, conf->src_addr))
+ ctrl->addr_mask, conf->src_addr))
{
return -RT_ENOSYS;
}
if (range_is_illegal(rt_dm_dev_get_name(ctrl->dev), "source",
- dma_buf_addr, conf->src_addr))
+ dma_buf_addr, conf->src_addr))
{
return -RT_EINVAL;
}
@@ -419,13 +566,13 @@ rt_err_t rt_dma_prep_cyclic(struct rt_dma_chan *chan,
dma_buf_addr = transfer->dst_addr;
if (addr_is_supported(rt_dm_dev_get_name(ctrl->dev), "dest",
- ctrl->addr_mask, conf->dst_addr))
+ ctrl->addr_mask, conf->dst_addr))
{
return -RT_ENOSYS;
}
if (range_is_illegal(rt_dm_dev_get_name(ctrl->dev), "dest",
- dma_buf_addr, conf->dst_addr))
+ dma_buf_addr, conf->dst_addr))
{
return -RT_EINVAL;
}
@@ -440,7 +587,7 @@ rt_err_t rt_dma_prep_cyclic(struct rt_dma_chan *chan,
dma_lock(ctrl);
err = ctrl->ops->prep_cyclic(chan, dma_buf_addr,
- transfer->buffer_len, transfer->period_len, dir);
+ transfer->buffer_len, transfer->period_len, dir);
dma_unlock(ctrl);
}
@@ -459,8 +606,20 @@ rt_err_t rt_dma_prep_cyclic(struct rt_dma_chan *chan,
return err;
}
+/**
+ * @brief Prepare a single (one-shot) DMA transfer
+ *
+ * Used for simple device-to-memory or memory-to-device transfers
+ * that are not repeating.
+ *
+ * @param[in] chan DMA channel
+ * @param[in] transfer Transfer descriptor (src_addr or dst_addr, buffer_len)
+ *
+ * @return RT_EOK on success, -RT_EINVAL on invalid parameters,
+ * -RT_ENOSYS if prep_single not supported
+ */
rt_err_t rt_dma_prep_single(struct rt_dma_chan *chan,
- struct rt_dma_slave_transfer *transfer)
+ struct rt_dma_slave_transfer *transfer)
{
rt_err_t err;
rt_ubase_t dma_buf_addr;
@@ -490,13 +649,13 @@ rt_err_t rt_dma_prep_single(struct rt_dma_chan *chan,
dma_buf_addr = transfer->src_addr;
if (addr_is_supported(rt_dm_dev_get_name(ctrl->dev), "source",
- ctrl->addr_mask, conf->src_addr))
+ ctrl->addr_mask, conf->src_addr))
{
return -RT_ENOSYS;
}
if (range_is_illegal(rt_dm_dev_get_name(ctrl->dev), "source",
- dma_buf_addr, conf->src_addr))
+ dma_buf_addr, conf->src_addr))
{
return -RT_EINVAL;
}
@@ -506,13 +665,13 @@ rt_err_t rt_dma_prep_single(struct rt_dma_chan *chan,
dma_buf_addr = transfer->dst_addr;
if (addr_is_supported(rt_dm_dev_get_name(ctrl->dev), "dest",
- ctrl->addr_mask, conf->dst_addr))
+ ctrl->addr_mask, conf->dst_addr))
{
return -RT_ENOSYS;
}
if (range_is_illegal(rt_dm_dev_get_name(ctrl->dev), "dest",
- dma_buf_addr, conf->dst_addr))
+ dma_buf_addr, conf->dst_addr))
{
return -RT_EINVAL;
}
@@ -527,7 +686,7 @@ rt_err_t rt_dma_prep_single(struct rt_dma_chan *chan,
dma_lock(ctrl);
err = ctrl->ops->prep_single(chan, dma_buf_addr,
- transfer->buffer_len, dir);
+ transfer->buffer_len, dir);
dma_unlock(ctrl);
}
@@ -546,8 +705,21 @@ rt_err_t rt_dma_prep_single(struct rt_dma_chan *chan,
return err;
}
+/**
+ * @brief Find a DMA controller from device tree by name
+ *
+ * Looks up the "dmas" and "dma-names" properties in the device's
+ * device tree node. The controller must have been probed and registered
+ * via rt_dma_controller_register().
+ *
+ * @param[in] dev Slave device requesting a DMA channel
+ * @param[in] name DMA channel name (matches dma-names entry in DT)
+ * @param[out] args Parsed DMA specifier from the dmas property
+ *
+ * @return DMA controller pointer on success, RT_NULL if not found
+ */
static struct rt_dma_controller *ofw_find_dma_controller(struct rt_device *dev,
- const char *name, struct rt_ofw_cell_args *args)
+ const char *name, struct rt_ofw_cell_args *args)
{
struct rt_dma_controller *ctrl = RT_NULL;
#ifdef RT_USING_OFW
@@ -582,6 +754,22 @@ static struct rt_dma_controller *ofw_find_dma_controller(struct rt_device *dev,
return ctrl;
}
+/**
+ * @brief Request a DMA channel for a slave device
+ *
+ * If a name is provided, the channel is looked up from device tree
+ * via the "dmas" property. If no name is provided, any available
+ * memory-to-memory capable controller is selected.
+ *
+ * The channel is added to the controller's channels_nodes list.
+ *
+ * @param[in] dev Slave device requesting DMA service
+ * @param[in] name Channel name (from dma-names in DT), or NULL for
+ * unnamed memory-to-memory allocation
+ *
+ * @return Pointer to the DMA channel on success, or an error pointer
+ * (rt_err_ptr(-RT_ENOMEM), rt_err_ptr(-RT_ENOSYS), etc.)
+ */
struct rt_dma_chan *rt_dma_chan_request(struct rt_device *dev, const char *name)
{
void *fw_data = RT_NULL;
@@ -662,6 +850,17 @@ struct rt_dma_chan *rt_dma_chan_request(struct rt_device *dev, const char *name)
return chan;
}
+/**
+ * @brief Release a DMA channel back to the controller
+ *
+ * Removes the channel from the controller's list. If the controller
+ * provides a release_chan operation, it is called; otherwise the
+ * channel memory is simply freed.
+ *
+ * @param[in] chan DMA channel to release
+ *
+ * @return RT_EOK on success, -RT_EINVAL if chan is NULL
+ */
rt_err_t rt_dma_chan_release(struct rt_dma_chan *chan)
{
rt_err_t err = RT_EOK;
diff --git a/components/drivers/dma/dma_pool.c b/components/drivers/dma/dma_pool.c
index 50652e6486..79b89e39a2 100644
--- a/components/drivers/dma/dma_pool.c
+++ b/components/drivers/dma/dma_pool.c
@@ -8,6 +8,21 @@
* 2023-02-25 GuEe-GUI the first version
*/
+/**
+ * @file dma_pool.c
+ * @brief DMA memory pool and buffer allocation framework
+ *
+ * Manages DMA-capable memory regions (coherent pool and CMA) for
+ * allocating DMA buffers. Provides coherent and non-coherent buffer
+ * allocation with optional device tree "memory-region" integration,
+ * cache synchronization via the rt_dma_map_ops interface, and a
+ * bitmap-based page allocator within DMA pools.
+ *
+ * Pools are organized as:
+ * - coherent-pool: smaller pool for cache-coherent DMA (ioremap_cached)
+ * - CMA (Contiguous Memory Allocator): larger pool for general DMA
+ */
+
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
@@ -26,22 +41,38 @@ static rt_list_t dma_pool_nodes = RT_LIST_OBJECT_INIT(dma_pool_nodes);
static struct rt_dma_pool *dma_pool_install(rt_region_t *region);
static void *dma_alloc(struct rt_device *dev, rt_size_t size,
- rt_ubase_t *dma_handle, rt_ubase_t flags);
+ rt_ubase_t *dma_handle, rt_ubase_t flags);
static void dma_free(struct rt_device *dev, rt_size_t size,
- void *cpu_addr, rt_ubase_t dma_handle, rt_ubase_t flags);
+ void *cpu_addr, rt_ubase_t dma_handle, rt_ubase_t flags);
+/** @brief Acquire the DMA pool spinlock */
rt_inline void region_pool_lock(void)
{
rt_hw_spin_lock(&dma_pools_lock.lock);
}
+/** @brief Release the DMA pool spinlock */
rt_inline void region_pool_unlock(void)
{
rt_hw_spin_unlock(&dma_pools_lock.lock);
}
+/**
+ * @brief Synchronize output data for a coherent DMA device
+ *
+ * Converts the virtual address to a physical DMA handle and flushes
+ * the CPU data cache to ensure the DMA engine sees the latest data.
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] data Virtual address of the data buffer
+ * @param[in] size Buffer size in bytes
+ * @param[out] dma_handle Physical DMA address (filled if non-NULL)
+ * @param[in] flags Allocation flags (unused)
+ *
+ * @return RT_EOK
+ */
static rt_err_t dma_map_coherent_sync_out_data(struct rt_device *dev,
- void *data, rt_size_t size, rt_ubase_t *dma_handle, rt_ubase_t flags)
+ void *data, rt_size_t size, rt_ubase_t *dma_handle, rt_ubase_t flags)
{
if (dma_handle)
{
@@ -52,22 +83,50 @@ static rt_err_t dma_map_coherent_sync_out_data(struct rt_device *dev,
return RT_EOK;
}
+/**
+ * @brief Synchronize input data for a coherent DMA device
+ *
+ * Invalidates the CPU data cache so the CPU sees the data written
+ * by the DMA engine.
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] out_data Virtual address of the data buffer
+ * @param[in] size Buffer size in bytes
+ * @param[in] dma_handle Physical DMA address (unused)
+ * @param[in] flags Allocation flags (unused)
+ *
+ * @return RT_EOK
+ */
static rt_err_t dma_map_coherent_sync_in_data(struct rt_device *dev,
- void *out_data, rt_size_t size, rt_ubase_t dma_handle, rt_ubase_t flags)
+ void *out_data, rt_size_t size, rt_ubase_t dma_handle, rt_ubase_t flags)
{
rt_hw_cpu_dcache_ops(RT_HW_CACHE_INVALIDATE, out_data, size);
return RT_EOK;
}
-static const struct rt_dma_map_ops dma_map_coherent_ops =
-{
+/** @brief DMA map operations for cache-coherent devices */
+static const struct rt_dma_map_ops dma_map_coherent_ops = {
.sync_out_data = dma_map_coherent_sync_out_data,
.sync_in_data = dma_map_coherent_sync_in_data,
};
+/**
+ * @brief Synchronize output data for a non-coherent DMA device
+ *
+ * Converts virtual address to physical DMA handle. No cache flush
+ * is performed since the memory is non-cacheable.
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] data Virtual address of the data buffer
+ * @param[in] size Buffer size in bytes
+ * @param[out] dma_handle Physical DMA address (filled if non-NULL)
+ * @param[in] flags Allocation flags (unused)
+ *
+ * @return RT_EOK
+ */
static rt_err_t dma_map_nocoherent_sync_out_data(struct rt_device *dev,
- void *data, rt_size_t size, rt_ubase_t *dma_handle, rt_ubase_t flags)
+ void *data, rt_size_t size, rt_ubase_t *dma_handle, rt_ubase_t flags)
{
if (dma_handle)
{
@@ -77,31 +136,71 @@ static rt_err_t dma_map_nocoherent_sync_out_data(struct rt_device *dev,
return RT_EOK;
}
+/**
+ * @brief Synchronize input data for a non-coherent DMA device
+ *
+ * No cache operations needed for non-cacheable memory.
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] out_data Virtual address of the data buffer
+ * @param[in] size Buffer size in bytes
+ * @param[in] dma_handle Physical DMA address (unused)
+ * @param[in] flags Allocation flags (unused)
+ *
+ * @return RT_EOK
+ */
static rt_err_t dma_map_nocoherent_sync_in_data(struct rt_device *dev,
- void *out_data, rt_size_t size, rt_ubase_t dma_handle, rt_ubase_t flags)
+ void *out_data, rt_size_t size, rt_ubase_t dma_handle, rt_ubase_t flags)
{
return RT_EOK;
}
-static const struct rt_dma_map_ops dma_map_nocoherent_ops =
-{
+/** @brief DMA map operations for non-cache-coherent devices */
+static const struct rt_dma_map_ops dma_map_nocoherent_ops = {
.sync_out_data = dma_map_nocoherent_sync_out_data,
.sync_in_data = dma_map_nocoherent_sync_in_data,
};
#ifdef RT_USING_OFW
+
+/**
+ * @brief Translate a CPU address to a DMA address via device tree
+ *
+ * @param[in] dev Device with an ofw_node
+ * @param[in] addr CPU physical address
+ *
+ * @return DMA address
+ */
rt_inline rt_ubase_t ofw_addr_cpu2dma(struct rt_device *dev, rt_ubase_t addr)
{
return (rt_ubase_t)rt_ofw_translate_cpu2dma(dev->ofw_node, addr);
}
+/**
+ * @brief Translate a DMA address to a CPU address via device tree
+ *
+ * @param[in] dev Device with an ofw_node
+ * @param[in] addr DMA address
+ *
+ * @return CPU physical address
+ */
rt_inline rt_ubase_t ofw_addr_dma2cpu(struct rt_device *dev, rt_ubase_t addr)
{
return (rt_ubase_t)rt_ofw_translate_dma2cpu(dev->ofw_node, addr);
}
+/**
+ * @brief Allocate a DMA buffer and translate to DMA address space
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] size Requested size in bytes
+ * @param[out] dma_handle DMA address (device tree translated)
+ * @param[in] flags Allocation flags
+ *
+ * @return Virtual address of the buffer, or RT_NULL on failure
+ */
static void *ofw_dma_map_alloc(struct rt_device *dev, rt_size_t size,
- rt_ubase_t *dma_handle, rt_ubase_t flags)
+ rt_ubase_t *dma_handle, rt_ubase_t flags)
{
void *cpu_addr = dma_alloc(dev, size, dma_handle, flags);
@@ -113,17 +212,40 @@ static void *ofw_dma_map_alloc(struct rt_device *dev, rt_size_t size,
return cpu_addr;
}
+/**
+ * @brief Free a DMA buffer, translating DMA address back to CPU space
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] size Buffer size in bytes
+ * @param[in] cpu_addr Virtual address of the buffer
+ * @param[in] dma_handle DMA address (will be translated back to CPU)
+ * @param[in] flags Allocation flags
+ */
static void ofw_dma_map_free(struct rt_device *dev, rt_size_t size,
- void *cpu_addr, rt_ubase_t dma_handle, rt_ubase_t flags)
+ void *cpu_addr, rt_ubase_t dma_handle, rt_ubase_t flags)
{
dma_handle = ofw_addr_dma2cpu(dev, dma_handle);
dma_free(dev, size, cpu_addr, dma_handle, flags);
}
+/**
+ * @brief Sync output data with device tree DMA address translation
+ *
+ * Chooses coherent or non-coherent sync based on RT_DMA_F_NOCACHE flag,
+ * then translates the DMA handle to device bus address space.
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] data Virtual address of the data buffer
+ * @param[in] size Buffer size in bytes
+ * @param[out] dma_handle DMA address (translated)
+ * @param[in] flags Sync flags
+ *
+ * @return RT_EOK on success
+ */
static rt_err_t ofw_dma_map_sync_out_data(struct rt_device *dev,
- void *data, rt_size_t size,
- rt_ubase_t *dma_handle, rt_ubase_t flags)
+ void *data, rt_size_t size,
+ rt_ubase_t *dma_handle, rt_ubase_t flags)
{
rt_err_t err;
@@ -144,9 +266,23 @@ static rt_err_t ofw_dma_map_sync_out_data(struct rt_device *dev,
return err;
}
+/**
+ * @brief Sync input data with device tree DMA address back-translation
+ *
+ * Translates the DMA handle from bus address space back to CPU space,
+ * then performs coherent or non-coherent cache invalidation.
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] out_data Virtual address of the data buffer
+ * @param[in] size Buffer size in bytes
+ * @param[in] dma_handle DMA address (translated back from bus space)
+ * @param[in] flags Sync flags
+ *
+ * @return RT_EOK on success
+ */
static rt_err_t ofw_dma_map_sync_in_data(struct rt_device *dev,
- void *out_data, rt_size_t size,
- rt_ubase_t dma_handle, rt_ubase_t flags)
+ void *out_data, rt_size_t size,
+ rt_ubase_t dma_handle, rt_ubase_t flags)
{
dma_handle = ofw_addr_dma2cpu(dev, dma_handle);
@@ -158,14 +294,26 @@ static rt_err_t ofw_dma_map_sync_in_data(struct rt_device *dev,
return dma_map_coherent_sync_in_data(dev, out_data, size, dma_handle, flags);
}
-static const struct rt_dma_map_ops ofw_dma_map_ops =
-{
+/** @brief DMA map operations with device tree address translation */
+static const struct rt_dma_map_ops ofw_dma_map_ops = {
.alloc = ofw_dma_map_alloc,
.free = ofw_dma_map_free,
.sync_out_data = ofw_dma_map_sync_out_data,
.sync_in_data = ofw_dma_map_sync_in_data,
};
+/**
+ * @brief Determine DMA operations for a device tree device with memory-region
+ *
+ * Parses the "memory-region" property to install DMA pools from
+ * reserved-memory nodes. Sets RT_DMA_F_NOMAP if "no-map" is set,
+ * and RT_DMA_F_NOCACHE if the device is not DMA-coherent.
+ *
+ * @param[in] dev Device to configure DMA operations for
+ *
+ * @return Pointer to DMA map ops (ofw_dma_map_ops) if regions were found,
+ * RT_NULL otherwise
+ */
static const struct rt_dma_map_ops *ofw_device_dma_ops(struct rt_device *dev)
{
rt_err_t err;
@@ -195,7 +343,7 @@ static const struct rt_dma_map_ops *ofw_device_dma_ops(struct rt_device *dev)
if ((err = rt_ofw_get_address(mem_np, 0, &addr, &size)))
{
LOG_E("%s: Read '%s' error = %s", rt_ofw_node_full_name(mem_np),
- "memory-region", rt_strerror(err));
+ "memory-region", rt_strerror(err));
rt_ofw_node_put(mem_np);
continue;
@@ -235,6 +383,20 @@ static const struct rt_dma_map_ops *ofw_device_dma_ops(struct rt_device *dev)
}
#endif /* RT_USING_OFW */
+/**
+ * @brief Select DMA operations for a device
+ *
+ * Priority:
+ * 1. Device-specific dma_ops (if already set)
+ * 2. Device tree memory-region ops (if device has ofw_node)
+ * 3. Coherent or non-coherent fallback based on device property
+ *
+ * The result is cached in dev->dma_ops for subsequent calls.
+ *
+ * @param[in] dev Device to query
+ *
+ * @return DMA map operations for this device
+ */
static const struct rt_dma_map_ops *device_dma_ops(struct rt_device *dev)
{
const struct rt_dma_map_ops *ops = dev->dma_ops;
@@ -265,6 +427,17 @@ static const struct rt_dma_map_ops *device_dma_ops(struct rt_device *dev)
return ops;
}
+/**
+ * @brief Allocate a contiguous range of pages from a DMA pool
+ *
+ * Uses a first-fit bitmap scan to find a contiguous block of free pages.
+ * Pages are tracked by individual bits; ARCH_PAGE_SIZE is the granularity.
+ *
+ * @param[in] pool DMA memory pool
+ * @param[in] size Requested size in bytes
+ *
+ * @return Physical offset within the pool, or RT_NULL on failure
+ */
static rt_ubase_t dma_pool_alloc(struct rt_dma_pool *pool, rt_size_t size)
{
rt_size_t bit, next_bit, end_bit, max_bits;
@@ -287,23 +460,29 @@ static rt_ubase_t dma_pool_alloc(struct rt_dma_pool *pool, rt_size_t size)
if (next_bit == end_bit)
{
- while (next_bit --> bit)
+ while (next_bit-- > bit)
{
rt_bitmap_set_bit(pool->map, next_bit);
}
LOG_D("%s offset = %p, pages = %d", "Alloc",
- pool->start + bit * ARCH_PAGE_SIZE, size);
+ pool->start + bit * ARCH_PAGE_SIZE, size);
return pool->start + bit * ARCH_PAGE_SIZE;
}
- _next:
- ;
+ _next:;
}
return RT_NULL;
}
+/**
+ * @brief Free a range of pages back to the DMA pool
+ *
+ * @param[in] pool DMA memory pool
+ * @param[in] offset Physical offset within the pool (from dma_pool_alloc)
+ * @param[in] size Size in bytes
+ */
static void dma_pool_free(struct rt_dma_pool *pool, rt_ubase_t offset, rt_size_t size)
{
rt_size_t bit = (offset - pool->start) / ARCH_PAGE_SIZE, end_bit;
@@ -319,8 +498,23 @@ static void dma_pool_free(struct rt_dma_pool *pool, rt_ubase_t offset, rt_size_t
LOG_D("%s offset = %p, pages = %d", "Free", offset, size);
}
+/**
+ * @brief Internal DMA buffer allocator (page-based from pools)
+ *
+ * Iterates through all registered DMA pools, matching the requested flags
+ * against pool capabilities (RT_DMA_F_DEVICE, RT_DMA_F_NOMAP, RT_DMA_F_32BITS).
+ * On success, remaps the physical pages to a virtual address with the
+ * appropriate cache attributes.
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] size Requested size in bytes
+ * @param[out] dma_handle Physical DMA address of the allocation
+ * @param[in] flags Allocation flags (RT_DMA_F_*)
+ *
+ * @return Kernel virtual address, or RT_NULL on failure
+ */
static void *dma_alloc(struct rt_device *dev, rt_size_t size,
- rt_ubase_t *dma_handle, rt_ubase_t flags)
+ rt_ubase_t *dma_handle, rt_ubase_t flags)
{
void *dma_buffer = RT_NULL;
struct rt_dma_pool *pool;
@@ -390,8 +584,19 @@ static void *dma_alloc(struct rt_device *dev, rt_size_t size,
return dma_buffer;
}
+/**
+ * @brief Internal DMA buffer free (returns pages to the pool)
+ *
+ * Finds the pool containing the DMA handle and frees the pages.
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] size Buffer size in bytes
+ * @param[in] cpu_addr Kernel virtual address (unmapped)
+ * @param[in] dma_handle Physical DMA address
+ * @param[in] flags Allocation flags
+ */
static void dma_free(struct rt_device *dev, rt_size_t size,
- void *cpu_addr, rt_ubase_t dma_handle, rt_ubase_t flags)
+ void *cpu_addr, rt_ubase_t dma_handle, rt_ubase_t flags)
{
struct rt_dma_pool *pool;
@@ -413,8 +618,21 @@ static void dma_free(struct rt_device *dev, rt_size_t size,
region_pool_unlock();
}
+/**
+ * @brief Allocate a DMA-capable buffer
+ *
+ * Public API for allocating memory suitable for DMA transfers.
+ * Uses device-specific DMA operations when available.
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] size Requested buffer size in bytes
+ * @param[out] dma_handle Physical DMA address (can be NULL if not needed)
+ * @param[in] flags Allocation flags (RT_DMA_F_NOCACHE, RT_DMA_F_NOMAP, etc.)
+ *
+ * @return Kernel virtual address of the buffer, or RT_NULL on failure
+ */
void *rt_dma_alloc(struct rt_device *dev, rt_size_t size,
- rt_ubase_t *dma_handle, rt_ubase_t flags)
+ rt_ubase_t *dma_handle, rt_ubase_t flags)
{
void *dma_buffer = RT_NULL;
rt_ubase_t dma_handle_s = 0;
@@ -449,8 +667,17 @@ void *rt_dma_alloc(struct rt_device *dev, rt_size_t size,
return dma_buffer;
}
+/**
+ * @brief Free a DMA buffer previously allocated with rt_dma_alloc()
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] size Buffer size in bytes
+ * @param[in] cpu_addr Kernel virtual address of the buffer
+ * @param[in] dma_handle Physical DMA address from rt_dma_alloc()
+ * @param[in] flags Allocation flags used when allocating
+ */
void rt_dma_free(struct rt_device *dev, rt_size_t size,
- void *cpu_addr, rt_ubase_t dma_handle, rt_ubase_t flags)
+ void *cpu_addr, rt_ubase_t dma_handle, rt_ubase_t flags)
{
const struct rt_dma_map_ops *ops;
@@ -471,8 +698,22 @@ void rt_dma_free(struct rt_device *dev, rt_size_t size,
}
}
+/**
+ * @brief Synchronize data for a DMA output transfer (CPU→device)
+ *
+ * Ensures the DMA engine sees the latest CPU-written data by performing
+ * any necessary cache flushes and returning the DMA bus address.
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] data Virtual address of the data buffer
+ * @param[in] size Buffer size in bytes
+ * @param[out] dma_handle DMA bus address (can be NULL)
+ * @param[in] flags Sync flags (RT_DMA_F_NOCACHE, etc.)
+ *
+ * @return RT_EOK on success, -RT_EINVAL if data or size is 0
+ */
rt_err_t rt_dma_sync_out_data(struct rt_device *dev, void *data, rt_size_t size,
- rt_ubase_t *dma_handle, rt_ubase_t flags)
+ rt_ubase_t *dma_handle, rt_ubase_t flags)
{
rt_err_t err;
rt_ubase_t dma_handle_s = 0;
@@ -494,8 +735,22 @@ rt_err_t rt_dma_sync_out_data(struct rt_device *dev, void *data, rt_size_t size,
return err;
}
+/**
+ * @brief Synchronize data for a DMA input transfer (device→CPU)
+ *
+ * Ensures the CPU sees the latest DMA engine data by performing
+ * any necessary cache invalidations.
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] out_data Virtual address of the data buffer
+ * @param[in] size Buffer size in bytes
+ * @param[in] dma_handle DMA bus address from the transfer
+ * @param[in] flags Sync flags
+ *
+ * @return RT_EOK on success, -RT_EINVAL if out_data or size is 0
+ */
rt_err_t rt_dma_sync_in_data(struct rt_device *dev, void *out_data, rt_size_t size,
- rt_ubase_t dma_handle, rt_ubase_t flags)
+ rt_ubase_t dma_handle, rt_ubase_t flags)
{
rt_err_t err;
const struct rt_dma_map_ops *ops;
@@ -511,6 +766,19 @@ rt_err_t rt_dma_sync_in_data(struct rt_device *dev, void *out_data, rt_size_t si
return err;
}
+/**
+ * @brief Install a DMA memory pool from a region descriptor
+ *
+ * Creates a new DMA pool covering the given memory region. Allocates
+ * a bitmap for page tracking. Pools under 4GB are marked RT_DMA_F_32BITS.
+ * Pools are automatically marked RT_DMA_F_LINEAR.
+ *
+ * Must be called with the DMA pools lock NOT held.
+ *
+ * @param[in] region Memory region descriptor (start, end, name)
+ *
+ * @return New DMA pool pointer, or RT_NULL on failure
+ */
static struct rt_dma_pool *dma_pool_install(rt_region_t *region)
{
rt_err_t err;
@@ -519,7 +787,7 @@ static struct rt_dma_pool *dma_pool_install(rt_region_t *region)
if (!(pool = rt_calloc(1, sizeof(*pool))))
{
LOG_E("Install pool[%p, %p] error = %s",
- region->start, region->end, rt_strerror(-RT_ENOMEM));
+ region->start, region->end, rt_strerror(-RT_ENOMEM));
return RT_NULL;
}
@@ -562,11 +830,20 @@ _fail:
rt_free(pool);
LOG_E("Install pool[%p, %p] error = %s",
- region->start, region->end, rt_strerror(err));
+ region->start, region->end, rt_strerror(err));
return RT_NULL;
}
+/**
+ * @brief Public API: install a DMA memory pool
+ *
+ * Wrapper around dma_pool_install() with logging of the reserved region.
+ *
+ * @param[in] region Memory region descriptor
+ *
+ * @return New DMA pool pointer, or RT_NULL on failure
+ */
struct rt_dma_pool *rt_dma_pool_install(rt_region_t *region)
{
struct rt_dma_pool *pool;
@@ -581,15 +858,32 @@ struct rt_dma_pool *rt_dma_pool_install(rt_region_t *region)
region = &pool->region;
LOG_I("%s: Reserved %u.%u MiB at %p",
- region->name,
- (region->end - region->start) / SIZE_MB,
- (region->end - region->start) / SIZE_KB & (SIZE_KB - 1),
- region->start);
+ region->name,
+ (region->end - region->start) / SIZE_MB,
+ (region->end - region->start) / SIZE_KB & (SIZE_KB - 1),
+ region->start);
}
return pool;
}
+/**
+ * @brief Extract DMA pools from memblock reserved memory
+ *
+ * Creates two pools from the "dma-pool" reserved memory region:
+ * 1. coherent-pool: for coherent DMA allocations
+ * 2. CMA (Contiguous Memory Allocator): remaining space for general DMA
+ *
+ * Prefers regions below 4GB for 32-bit DMA compatibility.
+ *
+ * @param[in] cma_size Total CMA region size in bytes
+ * @param[in] coherent_pool_size Size of the coherent pool in bytes (must
+ * be <= cma_size)
+ *
+ * @return RT_EOK on success, -RT_EINVAL if sizes are invalid,
+ * -RT_ENOSYS if memblock is not available,
+ * -RT_EEMPTY if no suitable dma-pool region found
+ */
rt_err_t rt_dma_pool_extract(rt_size_t cma_size, rt_size_t coherent_pool_size)
{
struct rt_dma_pool *pool;
@@ -681,7 +975,17 @@ _found:
}
#if defined(RT_USING_CONSOLE) && defined(RT_USING_MSH)
-static int list_dma_pool(int argc, char**argv)
+/**
+ * @brief MSH command: list all DMA memory pools
+ *
+ * Displays each pool's name, start, and end address.
+ *
+ * @param[in] argc Argument count (unused)
+ * @param[in] argv Argument values (unused)
+ *
+ * @return 0
+ */
+static int list_dma_pool(int argc, char **argv)
{
int count = 0;
rt_region_t *region;
@@ -696,7 +1000,7 @@ static int list_dma_pool(int argc, char**argv)
region = &pool->region;
rt_kprintf("%-*.s [%p, %p]\n", RT_NAME_MAX, region->name,
- region->start, region->end);
+ region->start, region->end);
++count;
}
diff --git a/components/drivers/include/drivers/dma.h b/components/drivers/include/drivers/dma.h
index 1bcf552b44..040ead1bc0 100644
--- a/components/drivers/include/drivers/dma.h
+++ b/components/drivers/include/drivers/dma.h
@@ -8,6 +8,16 @@
* 2023-02-25 GuEe-GUI the first version
*/
+/**
+ * @file dma.h
+ * @brief DMA (Direct Memory Access) controller framework public API
+ *
+ * Defines the DMA controller, channel, memory pool, and mapping operations
+ * interfaces. Provides APIs for controller registration, channel lifecycle
+ * management, transfer preparation (memcpy, cyclic, single), DMA buffer
+ * allocation with cache coherence management, and device tree integration.
+ */
+
#ifndef __DMA_H__
#define __DMA_H__
@@ -23,143 +33,343 @@
struct rt_dma_chan;
struct rt_dma_controller_ops;
+/**
+ * @brief DMA transfer direction
+ *
+ * Defines the data flow direction for a DMA transfer. The controller's
+ * dir_cap bitmap indicates which directions are supported.
+ */
enum rt_dma_transfer_direction
{
- RT_DMA_MEM_TO_MEM,
- RT_DMA_MEM_TO_DEV,
- RT_DMA_DEV_TO_MEM,
- RT_DMA_DEV_TO_DEV,
+ RT_DMA_MEM_TO_MEM, /**< Memory-to-memory copy (no peripheral involved) */
+ RT_DMA_MEM_TO_DEV, /**< Memory to peripheral (TX / playback) */
+ RT_DMA_DEV_TO_MEM, /**< Peripheral to memory (RX / capture) */
+ RT_DMA_DEV_TO_DEV, /**< Device-to-device (rare, may not be supported) */
- RT_DMA_DIR_MAX,
+ RT_DMA_DIR_MAX, /**< Sentinel: total number of direction types */
};
+/**
+ * @brief DMA slave bus width options
+ *
+ * Specifies the transfer width for source and destination on the
+ * DMA bus. Must match the peripheral's FIFO width for correct operation.
+ */
enum rt_dma_slave_buswidth
{
- RT_DMA_SLAVE_BUSWIDTH_UNDEFINED = 0,
- RT_DMA_SLAVE_BUSWIDTH_1_BYTE = 1,
- RT_DMA_SLAVE_BUSWIDTH_2_BYTES = 2,
- RT_DMA_SLAVE_BUSWIDTH_3_BYTES = 3,
- RT_DMA_SLAVE_BUSWIDTH_4_BYTES = 4,
- RT_DMA_SLAVE_BUSWIDTH_8_BYTES = 8,
- RT_DMA_SLAVE_BUSWIDTH_16_BYTES = 16,
- RT_DMA_SLAVE_BUSWIDTH_32_BYTES = 32,
- RT_DMA_SLAVE_BUSWIDTH_64_BYTES = 64,
- RT_DMA_SLAVE_BUSWIDTH_128_BYTES = 128,
+ RT_DMA_SLAVE_BUSWIDTH_UNDEFINED = 0, /**< Undefined / not configured */
+ RT_DMA_SLAVE_BUSWIDTH_1_BYTE = 1, /**< 8-bit transfers */
+ RT_DMA_SLAVE_BUSWIDTH_2_BYTES = 2, /**< 16-bit transfers */
+ RT_DMA_SLAVE_BUSWIDTH_3_BYTES = 3, /**< 24-bit transfers */
+ RT_DMA_SLAVE_BUSWIDTH_4_BYTES = 4, /**< 32-bit transfers */
+ RT_DMA_SLAVE_BUSWIDTH_8_BYTES = 8, /**< 64-bit transfers */
+ RT_DMA_SLAVE_BUSWIDTH_16_BYTES = 16, /**< 128-bit transfers */
+ RT_DMA_SLAVE_BUSWIDTH_32_BYTES = 32, /**< 256-bit transfers */
+ RT_DMA_SLAVE_BUSWIDTH_64_BYTES = 64, /**< 512-bit transfers */
+ RT_DMA_SLAVE_BUSWIDTH_128_BYTES = 128, /**< 1024-bit transfers */
- RT_DMA_SLAVE_BUSWIDTH_BYTES_MAX,
+ RT_DMA_SLAVE_BUSWIDTH_BYTES_MAX, /**< Sentinel: maximum bus width enum value */
};
+/**
+ * @brief DMA slave channel configuration
+ *
+ * Passed to rt_dma_chan_config() to set up a channel before starting
+ * transfers. Defines transfer direction, address widths, fixed source/
+ * destination addresses, and burst sizes.
+ */
struct rt_dma_slave_config
{
- enum rt_dma_transfer_direction direction;
- enum rt_dma_slave_buswidth src_addr_width;
- enum rt_dma_slave_buswidth dst_addr_width;
+ enum rt_dma_transfer_direction direction; /**< Transfer direction (MEM_TO_DEV, DEV_TO_MEM, etc.) */
+ enum rt_dma_slave_buswidth src_addr_width; /**< Source bus width in bytes */
+ enum rt_dma_slave_buswidth dst_addr_width; /**< Destination bus width in bytes */
- rt_ubase_t src_addr;
- rt_ubase_t dst_addr;
+ rt_ubase_t src_addr; /**< Fixed source address (peripheral FIFO register) */
+ rt_ubase_t dst_addr; /**< Fixed destination address (peripheral FIFO register) */
- rt_uint32_t src_maxburst;
- rt_uint32_t dst_maxburst;
- rt_uint32_t src_port_window_size;
- rt_uint32_t dst_port_window_size;
+ rt_uint32_t src_maxburst; /**< Maximum source burst size in transfer-width units */
+ rt_uint32_t dst_maxburst; /**< Maximum destination burst size in transfer-width units */
+ rt_uint32_t src_port_window_size; /**< Source port window size (for scatter-gather) */
+ rt_uint32_t dst_port_window_size; /**< Destination port window size (for scatter-gather) */
};
+/**
+ * @brief DMA slave transfer descriptor
+ *
+ * Describes a single DMA transfer: source/destination buffer addresses,
+ * total buffer length, and period length for cyclic transfers. Passed
+ * to rt_dma_prep_memcpy(), rt_dma_prep_cyclic(), or rt_dma_prep_single().
+ */
struct rt_dma_slave_transfer
{
- rt_ubase_t src_addr;
- rt_ubase_t dst_addr;
+ rt_ubase_t src_addr; /**< Source buffer physical address */
+ rt_ubase_t dst_addr; /**< Destination buffer physical address */
- void *buffer;
- rt_ubase_t dma_handle;
- rt_size_t buffer_len;
- rt_size_t period_len;
+ void *buffer; /**< Virtual address of the DMA buffer (if mapped) */
+ rt_ubase_t dma_handle; /**< DMA bus address of the buffer */
+ rt_size_t buffer_len; /**< Total buffer length in bytes */
+ rt_size_t period_len; /**< Period length in bytes (for cyclic transfers only) */
};
+/**
+ * @brief DMA controller descriptor
+ *
+ * Represents a single DMA controller instance. Each controller has
+ * a set of operations (ops), supported transfer directions (dir_cap),
+ * an address mask for validating transfer addresses, and a list of
+ * currently allocated channels.
+ */
struct rt_dma_controller
{
- rt_list_t list;
+ rt_list_t list; /**< Node in the global controller list */
- struct rt_device *dev;
+ struct rt_device *dev; /**< Owning platform device */
#define RT_DMA_ADDR_MASK(n) (((n) == 64) ? ~0ULL : ((1ULL << (n)) - 1))
- rt_uint64_t addr_mask;
- RT_BITMAP_DECLARE(dir_cap, RT_DMA_DIR_MAX);
- const struct rt_dma_controller_ops *ops;
+ /** @brief Compute DMA address mask from bit width (e.g., RT_DMA_ADDR_MASK(32) = 0xFFFFFFFF) */
+ rt_uint64_t addr_mask; /**< Address mask: bits outside this mask are invalid for DMA */
+ RT_BITMAP_DECLARE(dir_cap, RT_DMA_DIR_MAX); /**< Bitmap of supported transfer directions */
+ const struct rt_dma_controller_ops *ops; /**< Controller-specific operations */
- rt_list_t channels_nodes;
- struct rt_mutex mutex;
+ rt_list_t channels_nodes; /**< List of currently allocated channels */
+ struct rt_mutex mutex; /**< Mutex for controller-level operations */
};
+/**
+ * @brief DMA controller operations vtable
+ *
+ * Each DMA controller driver provides implementations of these operations.
+ * start, stop, and config are mandatory; the rest are optional.
+ */
struct rt_dma_controller_ops
{
+ /**
+ * @brief Request a DMA channel (optional)
+ *
+ * @param[in] ctrl DMA controller
+ * @param[in] slave Device requesting the channel
+ * @param[in] fw_data Device tree cell args (NULL for mem-to-mem)
+ *
+ * @return Channel pointer, RT_NULL, or error pointer
+ */
struct rt_dma_chan *(*request_chan)(struct rt_dma_controller *ctrl,
- struct rt_device *slave, void *fw_data);
+ struct rt_device *slave, void *fw_data);
+
+ /**
+ * @brief Release a DMA channel (optional)
+ *
+ * @param[in] chan Channel to release
+ *
+ * @return RT_EOK on success
+ */
rt_err_t (*release_chan)(struct rt_dma_chan *chan);
+ /**
+ * @brief Start a prepared transfer (mandatory)
+ *
+ * @param[in] chan Channel with a successfully prepared transfer
+ *
+ * @return RT_EOK on success
+ */
rt_err_t (*start)(struct rt_dma_chan *chan);
+
+ /**
+ * @brief Pause a running transfer (optional)
+ *
+ * If not implemented, the framework falls back to stop().
+ *
+ * @param[in] chan Channel to pause
+ *
+ * @return RT_EOK on success
+ */
rt_err_t (*pause)(struct rt_dma_chan *chan);
+
+ /**
+ * @brief Stop a running transfer (mandatory)
+ *
+ * @param[in] chan Channel to stop
+ *
+ * @return RT_EOK on success
+ */
rt_err_t (*stop)(struct rt_dma_chan *chan);
+
+ /**
+ * @brief Configure a DMA channel (mandatory)
+ *
+ * @param[in] chan Channel to configure
+ * @param[in] conf Slave configuration
+ *
+ * @return RT_EOK on success
+ */
rt_err_t (*config)(struct rt_dma_chan *chan, struct rt_dma_slave_config *conf);
+ /**
+ * @brief Prepare a memory-to-memory transfer (optional)
+ *
+ * @param[in] chan DMA channel
+ * @param[in] dma_addr_src Source physical address
+ * @param[in] dma_addr_dst Destination physical address
+ * @param[in] len Transfer length in bytes
+ *
+ * @return RT_EOK on success
+ */
rt_err_t (*prep_memcpy)(struct rt_dma_chan *chan,
- rt_ubase_t dma_addr_src, rt_ubase_t dma_addr_dst, rt_size_t len);
+ rt_ubase_t dma_addr_src, rt_ubase_t dma_addr_dst, rt_size_t len);
+ /**
+ * @brief Prepare a cyclic (repeating) transfer (optional)
+ *
+ * @param[in] chan DMA channel
+ * @param[in] dma_buf_addr Buffer physical address
+ * @param[in] buf_len Total buffer length in bytes
+ * @param[in] period_len Bytes per period (callback interval)
+ * @param[in] dir Transfer direction
+ *
+ * @return RT_EOK on success
+ */
rt_err_t (*prep_cyclic)(struct rt_dma_chan *chan,
- rt_ubase_t dma_buf_addr, rt_size_t buf_len, rt_size_t period_len,
- enum rt_dma_transfer_direction dir);
+ rt_ubase_t dma_buf_addr, rt_size_t buf_len, rt_size_t period_len,
+ enum rt_dma_transfer_direction dir);
+ /**
+ * @brief Prepare a single (one-shot) transfer (optional)
+ *
+ * @param[in] chan DMA channel
+ * @param[in] dma_buf_addr Buffer physical address
+ * @param[in] buf_len Transfer length in bytes
+ * @param[in] dir Transfer direction
+ *
+ * @return RT_EOK on success
+ */
rt_err_t (*prep_single)(struct rt_dma_chan *chan,
- rt_ubase_t dma_buf_addr, rt_size_t buf_len,
- enum rt_dma_transfer_direction dir);
+ rt_ubase_t dma_buf_addr, rt_size_t buf_len,
+ enum rt_dma_transfer_direction dir);
};
+/**
+ * @brief DMA channel descriptor
+ *
+ * Represents an allocated DMA channel bound to a specific slave device.
+ * Tracks configuration and preparation errors to prevent operations on
+ * improperly set up channels.
+ */
struct rt_dma_chan
{
- const char *name;
+ const char *name; /**< Channel name (from dma-names in device tree) */
- struct rt_dma_controller *ctrl;
- struct rt_device *slave;
+ struct rt_dma_controller *ctrl; /**< Parent DMA controller */
+ struct rt_device *slave; /**< Slave device that requested this channel */
- rt_list_t list;
- rt_err_t conf_err;
- rt_err_t prep_err;
- struct rt_dma_slave_config conf;
- struct rt_dma_slave_transfer transfer;
+ rt_list_t list; /**< Node in the controller's channels list */
+ rt_err_t conf_err; /**< Last configuration error (-RT_ERROR = not configured) */
+ rt_err_t prep_err; /**< Last preparation error (-RT_ERROR = not prepared) */
+ struct rt_dma_slave_config conf; /**< Saved slave configuration */
+ struct rt_dma_slave_transfer transfer; /**< Saved transfer descriptor */
- void (*callback)(struct rt_dma_chan *chan, rt_size_t size);
+ void (*callback)(struct rt_dma_chan *chan, rt_size_t size); /**< Transfer completion callback */
- void *priv;
+ void *priv; /**< Controller driver private data */
};
+/**
+ * @brief DMA memory pool descriptor
+ *
+ * Manages a contiguous physical memory region for DMA allocations.
+ * Uses a bitmap to track free/used pages. Pools are organized in
+ * a global linked list.
+ */
struct rt_dma_pool
{
- rt_region_t region;
+ rt_region_t region; /**< Memory region (name, start, end) */
- rt_list_t list;
+ rt_list_t list; /**< Node in the global pool list */
- rt_ubase_t flags;
+ rt_ubase_t flags; /**< Pool flags (RT_DMA_F_LINEAR, RT_DMA_F_32BITS, etc.) */
- rt_bitmap_t *map;
- rt_size_t bits;
- rt_ubase_t start;
+ rt_bitmap_t *map; /**< Page allocation bitmap (1 bit per page) */
+ rt_size_t bits; /**< Total number of pages in this pool */
+ rt_ubase_t start; /**< Page-aligned start address */
- struct rt_device *dev;
+ struct rt_device *dev; /**< Device that owns this pool (for RT_DMA_F_DEVICE) */
};
+/**
+ * @brief DMA memory mapping operations vtable
+ *
+ * Abstracts device-specific DMA memory management: allocation, freeing,
+ * and cache synchronization for coherent and non-coherent devices.
+ * Devices may override these via dev->dma_ops or the device tree.
+ */
struct rt_dma_map_ops
{
+ /**
+ * @brief Allocate a DMA buffer (optional, may fall back to default)
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] size Requested size in bytes
+ * @param[out] dma_handle Physical DMA address
+ * @param[in] flags Allocation flags (RT_DMA_F_*)
+ *
+ * @return Kernel virtual address, or RT_NULL on failure
+ */
void *(*alloc)(struct rt_device *dev, rt_size_t size,
- rt_ubase_t *dma_handle, rt_ubase_t flags);
+ rt_ubase_t *dma_handle, rt_ubase_t flags);
+
+ /**
+ * @brief Free a DMA buffer (optional)
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] size Buffer size in bytes
+ * @param[in] cpu_addr Kernel virtual address
+ * @param[in] dma_handle Physical DMA address
+ * @param[in] flags Allocation flags
+ */
void (*free)(struct rt_device *dev, rt_size_t size,
- void *cpu_addr, rt_ubase_t dma_handle, rt_ubase_t flags);
+ void *cpu_addr, rt_ubase_t dma_handle, rt_ubase_t flags);
+
+ /**
+ * @brief Synchronize data for DMA output (CPU → device)
+ *
+ * Performs any necessary cache flush and returns the DMA bus address.
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] data Virtual address of data buffer
+ * @param[in] size Buffer size in bytes
+ * @param[out] dma_handle DMA bus address (may be NULL)
+ * @param[in] flags Sync flags
+ *
+ * @return RT_EOK on success
+ */
rt_err_t (*sync_out_data)(struct rt_device *dev, void *data, rt_size_t size,
- rt_ubase_t *dma_handle, rt_ubase_t flags);
+ rt_ubase_t *dma_handle, rt_ubase_t flags);
+
+ /**
+ * @brief Synchronize data for DMA input (device → CPU)
+ *
+ * Performs any necessary cache invalidation.
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] out_data Virtual address of data buffer
+ * @param[in] size Buffer size in bytes
+ * @param[in] dma_handle DMA bus address
+ * @param[in] flags Sync flags
+ *
+ * @return RT_EOK on success
+ */
rt_err_t (*sync_in_data)(struct rt_device *dev, void *out_data, rt_size_t size,
- rt_ubase_t dma_handle, rt_ubase_t flags);
+ rt_ubase_t dma_handle, rt_ubase_t flags);
};
+/**
+ * @brief Add a supported transfer direction to a DMA controller
+ *
+ * Controllers call this during probe to declare which directions they support.
+ *
+ * @param[in] ctrl DMA controller
+ * @param[in] dir Transfer direction to add
+ */
rt_inline void rt_dma_controller_add_direction(struct rt_dma_controller *ctrl,
- enum rt_dma_transfer_direction dir)
+ enum rt_dma_transfer_direction dir)
{
RT_ASSERT(ctrl != RT_NULL);
RT_ASSERT(dir < RT_DMA_DIR_MAX);
@@ -167,80 +377,159 @@ rt_inline void rt_dma_controller_add_direction(struct rt_dma_controller *ctrl,
rt_bitmap_set_bit(ctrl->dir_cap, dir);
}
+/**
+ * @brief Set the address mask for a DMA controller
+ *
+ * The mask defines the valid address range for DMA transfers.
+ * For example, RT_DMA_ADDR_MASK(32) limits transfers to the lower 4GB.
+ *
+ * @param[in] ctrl DMA controller
+ * @param[in] mask Address bit mask
+ */
rt_inline void rt_dma_controller_set_addr_mask(struct rt_dma_controller *ctrl,
- rt_uint64_t mask)
+ rt_uint64_t mask)
{
RT_ASSERT(ctrl != RT_NULL);
ctrl->addr_mask = mask;
}
+/** @brief Register a DMA controller with the framework */
rt_err_t rt_dma_controller_register(struct rt_dma_controller *ctrl);
+
+/** @brief Unregister a DMA controller (fails if channels are in use) */
rt_err_t rt_dma_controller_unregister(struct rt_dma_controller *ctrl);
+/** @brief Start a prepared DMA transfer on a channel */
rt_err_t rt_dma_chan_start(struct rt_dma_chan *chan);
+
+/** @brief Pause a running DMA transfer (falls back to stop if not supported) */
rt_err_t rt_dma_chan_pause(struct rt_dma_chan *chan);
+
+/** @brief Stop a running DMA transfer */
rt_err_t rt_dma_chan_stop(struct rt_dma_chan *chan);
+
+/** @brief Configure a DMA channel with slave-specific parameters */
rt_err_t rt_dma_chan_config(struct rt_dma_chan *chan,
- struct rt_dma_slave_config *conf);
+ struct rt_dma_slave_config *conf);
+
+/** @brief Signal transfer completion (called by controller ISR) */
rt_err_t rt_dma_chan_done(struct rt_dma_chan *chan, rt_size_t size);
+/** @brief Prepare a memory-to-memory DMA transfer */
rt_err_t rt_dma_prep_memcpy(struct rt_dma_chan *chan,
- struct rt_dma_slave_transfer *transfer);
+ struct rt_dma_slave_transfer *transfer);
+
+/** @brief Prepare a cyclic (repeating) DMA transfer */
rt_err_t rt_dma_prep_cyclic(struct rt_dma_chan *chan,
- struct rt_dma_slave_transfer *transfer);
+ struct rt_dma_slave_transfer *transfer);
+
+/** @brief Prepare a single (one-shot) DMA transfer */
rt_err_t rt_dma_prep_single(struct rt_dma_chan *chan,
- struct rt_dma_slave_transfer *transfer);
+ struct rt_dma_slave_transfer *transfer);
+/** @brief Request a DMA channel for a slave device (by DT name or auto) */
struct rt_dma_chan *rt_dma_chan_request(struct rt_device *dev, const char *name);
+
+/** @brief Release a DMA channel back to the controller */
rt_err_t rt_dma_chan_release(struct rt_dma_chan *chan);
-#define RT_DMA_F_LINEAR RT_BIT(0)
-#define RT_DMA_F_32BITS RT_BIT(1)
-#define RT_DMA_F_NOCACHE RT_BIT(2)
-#define RT_DMA_F_DEVICE RT_BIT(3)
-#define RT_DMA_F_NOMAP RT_BIT(4)
-#define RT_DMA_F_WT RT_BIT(5)
+/** @brief DMA pool: linear address space (all DMA pools have this flag) */
+#define RT_DMA_F_LINEAR RT_BIT(0)
+/** @brief DMA pool: limited to 32-bit addressable memory (below 4GB) */
+#define RT_DMA_F_32BITS RT_BIT(1)
+/** @brief DMA allocation: use non-cacheable memory mapping */
+#define RT_DMA_F_NOCACHE RT_BIT(2)
+/** @brief DMA pool: device-private pool (matches pool->dev) */
+#define RT_DMA_F_DEVICE RT_BIT(3)
+/** @brief DMA allocation: do not map virtual address, return physical */
+#define RT_DMA_F_NOMAP RT_BIT(4)
+/** @brief DMA allocation: use write-through cache mapping */
+#define RT_DMA_F_WT RT_BIT(5)
-#define RT_DMA_PAGE_SIZE ARCH_PAGE_SIZE
+/** @brief DMA pool page size matches architecture page size */
+#define RT_DMA_PAGE_SIZE ARCH_PAGE_SIZE
+/** @brief Allocate a DMA-capable memory buffer */
void *rt_dma_alloc(struct rt_device *dev, rt_size_t size,
- rt_ubase_t *dma_handle, rt_ubase_t flags);
+ rt_ubase_t *dma_handle, rt_ubase_t flags);
+/** @brief Free a DMA buffer previously allocated with rt_dma_alloc() */
void rt_dma_free(struct rt_device *dev, rt_size_t size,
- void *cpu_addr, rt_ubase_t dma_handle, rt_ubase_t flags);
+ void *cpu_addr, rt_ubase_t dma_handle, rt_ubase_t flags);
+/**
+ * @brief Allocate a coherent (non-cacheable) DMA buffer
+ *
+ * Convenience wrapper around rt_dma_alloc() with RT_DMA_F_NOCACHE | RT_DMA_F_LINEAR.
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] size Requested size in bytes
+ * @param[out] dma_handle Physical DMA address
+ *
+ * @return Kernel virtual address, or RT_NULL on failure
+ */
rt_inline void *rt_dma_alloc_coherent(struct rt_device *dev, rt_size_t size,
- rt_ubase_t *dma_handle)
+ rt_ubase_t *dma_handle)
{
return rt_dma_alloc(dev, size, dma_handle,
- RT_DMA_F_NOCACHE | RT_DMA_F_LINEAR);
+ RT_DMA_F_NOCACHE | RT_DMA_F_LINEAR);
}
+/**
+ * @brief Free a coherent DMA buffer
+ *
+ * @param[in] dev DMA-capable device
+ * @param[in] size Buffer size in bytes
+ * @param[in] cpu_addr Kernel virtual address
+ * @param[in] dma_handle Physical DMA address
+ */
rt_inline void rt_dma_free_coherent(struct rt_device *dev, rt_size_t size,
- void *cpu_addr, rt_ubase_t dma_handle)
+ void *cpu_addr, rt_ubase_t dma_handle)
{
rt_dma_free(dev, size, cpu_addr, dma_handle,
- RT_DMA_F_NOCACHE | RT_DMA_F_LINEAR);
+ RT_DMA_F_NOCACHE | RT_DMA_F_LINEAR);
}
+/** @brief Synchronize CPU cache for DMA output (CPU → device) */
rt_err_t rt_dma_sync_out_data(struct rt_device *dev, void *data, rt_size_t size,
- rt_ubase_t *dma_handle, rt_ubase_t flags);
+ rt_ubase_t *dma_handle, rt_ubase_t flags);
+
+/** @brief Synchronize CPU cache for DMA input (device → CPU) */
rt_err_t rt_dma_sync_in_data(struct rt_device *dev, void *out_data, rt_size_t size,
- rt_ubase_t dma_handle, rt_ubase_t flags);
+ rt_ubase_t dma_handle, rt_ubase_t flags);
+/**
+ * @brief Check if a device is DMA-coherent
+ *
+ * Reads the "dma-coherent" boolean property from the device's
+ * device tree node (if present).
+ *
+ * @param[in] dev Device to check
+ *
+ * @return RT_TRUE if the device is DMA-coherent
+ */
rt_inline rt_bool_t rt_dma_device_is_coherent(struct rt_device *dev)
{
return rt_dm_dev_prop_read_bool(dev, "dma-coherent");
}
+/**
+ * @brief Override the DMA mapping operations for a device
+ *
+ * @param[in] dev Device to configure
+ * @param[in] ops Custom DMA map operations (or NULL to reset)
+ */
rt_inline void rt_dma_device_set_ops(struct rt_device *dev,
- const struct rt_dma_map_ops *ops)
+ const struct rt_dma_map_ops *ops)
{
dev->dma_ops = ops;
}
+/** @brief Install a DMA memory pool from a region descriptor */
struct rt_dma_pool *rt_dma_pool_install(rt_region_t *region);
+
+/** @brief Extract coherent pool and CMA from memblock reserved memory */
rt_err_t rt_dma_pool_extract(rt_size_t cma_size, rt_size_t coherent_pool_size);
#endif /* __DMA_H__ */