Files
rt-thread/components/drivers/dma/dma-pl330.c
T
Rbb666 7071ba35cb
ToolsCI / Tools (push) Has been cancelled
RT-Thread BSP Static Build Check / 🔍 Summary of Git Diff Changes (push) Has been cancelled
RT-Thread BSP Static Build Check / ${{ matrix.legs.RTT_BSP }} (push) Has been cancelled
RT-Thread BSP Static Build Check / collect-artifacts (push) Has been cancelled
pkgs_test / change (push) Has been cancelled
utest_auto_run / A9 :components/dfs.cfg (push) Has been cancelled
utest_auto_run / A9 :components/lwip.cfg (push) Has been cancelled
utest_auto_run / A9 :components/netdev.cfg (push) Has been cancelled
utest_auto_run / A9 :components/sal.cfg (push) Has been cancelled
utest_auto_run / A9 :cpp11/cpp11.cfg (push) Has been cancelled
utest_auto_run / AARCH64-rtsmart :default.cfg (push) Has been cancelled
utest_auto_run / A9-rtsmart :default.cfg (push) Has been cancelled
utest_auto_run / RISCV-rtsmart :default.cfg (push) Has been cancelled
utest_auto_run / XUANTIE-rtsmart :default.cfg (push) Has been cancelled
utest_auto_run / AARCH64 :default.cfg (push) Has been cancelled
utest_auto_run / AARCH64-smp :default.cfg (push) Has been cancelled
utest_auto_run / A9 :default.cfg (push) Has been cancelled
utest_auto_run / A9-smp :default.cfg (push) Has been cancelled
utest_auto_run / RISCV :default.cfg (push) Has been cancelled
utest_auto_run / RISCV-smp :default.cfg (push) Has been cancelled
utest_auto_run / A9 :kernel/atomic_c11.cfg (push) Has been cancelled
utest_auto_run / RISCV :kernel/atomic_c11.cfg (push) Has been cancelled
utest_auto_run / A9-rtsmart :kernel/ipc.cfg (push) Has been cancelled
utest_auto_run / RISCV-rtsmart :kernel/ipc.cfg (push) Has been cancelled
utest_auto_run / XUANTIE-rtsmart :kernel/ipc.cfg (push) Has been cancelled
utest_auto_run / A9 :kernel/ipc.cfg (push) Has been cancelled
utest_auto_run / A9-smp :kernel/ipc.cfg (push) Has been cancelled
utest_auto_run / RISCV :kernel/ipc.cfg (push) Has been cancelled
utest_auto_run / A9-rtsmart :kernel/kernel_basic.cfg (push) Has been cancelled
utest_auto_run / RISCV-rtsmart :kernel/kernel_basic.cfg (push) Has been cancelled
utest_auto_run / XUANTIE-rtsmart :kernel/kernel_basic.cfg (push) Has been cancelled
utest_auto_run / A9 :kernel/kernel_basic.cfg (push) Has been cancelled
utest_auto_run / A9-smp :kernel/kernel_basic.cfg (push) Has been cancelled
utest_auto_run / RISCV :kernel/kernel_basic.cfg (push) Has been cancelled
utest_auto_run / AARCH64-rtsmart :kernel/mem.cfg (push) Has been cancelled
utest_auto_run / A9-rtsmart :kernel/mem.cfg (push) Has been cancelled
utest_auto_run / RISCV-rtsmart :kernel/mem.cfg (push) Has been cancelled
utest_auto_run / XUANTIE-rtsmart :kernel/mem.cfg (push) Has been cancelled
utest_auto_run / AARCH64 :kernel/mem.cfg (push) Has been cancelled
utest_auto_run / AARCH64-smp :kernel/mem.cfg (push) Has been cancelled
utest_auto_run / A9 :kernel/mem.cfg (push) Has been cancelled
utest_auto_run / A9-smp :kernel/mem.cfg (push) Has been cancelled
utest_auto_run / RISCV :kernel/mem.cfg (push) Has been cancelled
utest_auto_run / RISCV-smp :kernel/mem.cfg (push) Has been cancelled
MemBrowse Memory Report / load-targets (push) Has been cancelled
MemBrowse Memory Report / analyze (push) Has been cancelled
[bsp/rockchip]Add CI compilation checks for RK3500 BSP.
2026-06-21 17:08:53 +08:00

1453 lines
41 KiB
C

/*
* Copyright (c) 2006-2023, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 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>
#include <bitmap.h>
#define DBG_TAG "dma.pl330"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
/** @brief Extract a bit field from a register value */
#define _FIELD_READ(h, l, x) ((RT_GENMASK(h, l) & (x)) >> l)
/** @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
/** @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
/** @brief Microcode instruction sizes */
#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
/** @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
/** @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
/** @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; /**< Generic DMA channel */
rt_bool_t enabled; /**< Channel is currently in use */
rt_size_t size; /**< Transfer size for callback reporting */
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; /**< Generic DMA controller */
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) /**< 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; /**< 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; /**< Array of channel descriptors */
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 */
};
/** @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;
void *regs = pl330->regs;
value = HWREG32(regs + PL330_REG_CR(0));
pl330->mode_ns = !!PL330_CR0_NUM_EVENTS(value);
pl330->num_chan = PL330_CR0_NUM_CHNLS(value) + 1;
pl330->num_events = PL330_CR0_NUM_EVENTS(value) + 1;
if (value & PL330_CR0_PERIPH_REQ_SET)
{
pl330->num_peri = PL330_CR0_NUM_PERIPH(value) + 1;
}
value = HWREG32(regs + PL330_REG_CRD);
pl330->data_width = 8 * (1 << PL330_CRD_DATA_WIDTH(value));
pl330->data_buffer_dep = PL330_CRD_DATA_BUFFER_DEP(value) + 1;
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;
*ccr |= conf->src_maxburst << PL330_SRC_BURST_LEN_SHIFT;
*ccr |= conf->dst_maxburst << PL330_DST_BURST_LEN_SHIFT;
switch (conf->src_addr_width)
{
case RT_DMA_SLAVE_BUSWIDTH_1_BYTE:
*ccr |= 0 << PL330_SRC_BURST_SIZE_SHIFT;
break;
case RT_DMA_SLAVE_BUSWIDTH_2_BYTES:
*ccr |= 1 << PL330_SRC_BURST_SIZE_SHIFT;
break;
case RT_DMA_SLAVE_BUSWIDTH_4_BYTES:
*ccr |= 2 << PL330_SRC_BURST_SIZE_SHIFT;
break;
default:
return -RT_EINVAL;
}
switch (conf->dst_addr_width)
{
case RT_DMA_SLAVE_BUSWIDTH_1_BYTE:
*ccr |= 0 << PL330_DST_BURST_SIZE_SHIFT;
break;
case RT_DMA_SLAVE_BUSWIDTH_2_BYTES:
*ccr |= 1 << PL330_DST_BURST_SIZE_SHIFT;
break;
case RT_DMA_SLAVE_BUSWIDTH_4_BYTES:
*ccr |= 2 << PL330_DST_BURST_SIZE_SHIFT;
break;
default:
return -RT_EINVAL;
}
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)
{
/*
* DMAMOV encoding
* 15 4 3 2 1 10 ... 8 7 6 5 4 3 2 1 0
* 0 0 0 0 0 |rd[2:0]|1 0 1 1 1 1 0 0
*
* 47 ... 16
* imm[32:0]
*
* rd: b000 for SAR, b001 CCR, b010 DAR
*/
*microcode++ = PL330_CMD_DMAMOV;
*microcode++ = rd;
*microcode++ = imm;
*microcode++ = imm >> 8;
*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);
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)
{
/*
* DMALD encoding
* 7 6 5 4 3 2 1 0
* 0 0 0 0 0 1 bs x
*/
*microcode = PL330_CMD_DMALD;
if (cond == PL330_COND_SINGLE)
{
*microcode |= (0 << 1) | (1 << 0);
}
else if (cond == PL330_COND_BURST)
{
*microcode |= (1 << 1) | (1 << 0);
}
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)
{
/*
* DMAST encoding
* 7 6 5 4 3 2 1 0
* 0 0 0 0 1 0 bs x
*/
*microcode = PL330_CMD_DMAST;
if (cond == PL330_COND_SINGLE)
{
*microcode |= (0 << 1) | (1 << 0);
}
else if (cond == PL330_COND_BURST)
{
*microcode |= (1 << 1) | (1 << 0);
}
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)
{
/*
* DMALP encoding
* 15 ... 8 7 6 5 4 3 2 1 0
* | iter[7:0] |0 0 1 0 0 0 lc 0
*/
*microcode++ = PL330_CMD_DMALP | ((lc & 1) << 1);
*microcode |= loops - 1;
LOG_D("DMALP %u (lc: %c)", loops - 1, lc ? '1' : '0');
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)
{
/*
* DMALPEND encoding
* 15 ... 8 7 6 5 4 3 2 1 0
* | backward_jump[7:0] |0 0 1 nf 1 lc bs x
*/
*microcode = PL330_CMD_DMALPEND;
if (loop)
{
*microcode |= (1 << 2);
}
if (!forever)
{
*microcode |= (1 << 4);
}
if (cond == PL330_COND_SINGLE)
{
*microcode |= (0 << 1) | (1 << 0);
}
else if (cond == PL330_COND_BURST)
{
*microcode |= (1 << 1) | (1 << 0);
}
++microcode;
*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);
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;
*microcode++ = (event & 0x1f) << 3;
LOG_D("DMASEV %u", event & 0x1f);
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)
{
/*
* DMAEND encoding:
* 7 6 5 4 3 2 1 0
* 0 0 0 0 0 0 0 0
*/
*microcode = PL330_CMD_DMAEND;
LOG_D("DMAEND");
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)
{
int idx = -1;
struct pl330_chan *pc;
struct pl330 *pl330 = raw_to_pl330(ctrl);
struct rt_ofw_cell_args *args = fw_data;
if (args && (idx = args->args[0]) >= pl330->num_chan)
{
return rt_err_ptr(-RT_EINVAL);
}
if (idx >= 0)
{
pc = &pl330->chans[idx];
if (pc->enabled)
{
return rt_err_ptr(-RT_EBUSY);
}
}
else
{
/*
* Memory to Memory is often called after after DM has been initialized,
* So just return unused chan
*/
for (int i = 0; i < pl330->num_chan; ++i)
{
pc = &pl330->chans[i];
if (!pc->enabled)
{
goto _found;
}
}
return RT_NULL;
}
_found:
pc->enabled = RT_TRUE;
HWREG32(pl330->regs + PL330_REG_INTEN) |= RT_BIT(pl330_chan_id(pl330, pc));
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);
pc->enabled = RT_FALSE;
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);
struct pl330 *pl330 = raw_to_pl330(chan->ctrl);
while (HWREG32(pl330->regs + PL330_REG_DBGSTATUS) & PL330_DBGSTATUS_BUSY)
{
rt_hw_cpu_relax();
}
rt_hw_cpu_dcache_ops(RT_HW_CACHE_FLUSH, pc->microcode, pc->microcode_len);
HWREG32(pl330->regs + PL330_REG_DBGINST0) = ((pl330_chan_id(pl330, pc) + 1) << 8) | (PL330_CMD_DMAGO << 16);
HWREG32(pl330->regs + PL330_REG_DBGINST1) = pc->microcode_dma;
HWREG32(pl330->regs + PL330_REG_DBGCMD) = 0;
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);
struct pl330 *pl330 = raw_to_pl330(chan->ctrl);
while (HWREG32(pl330->regs + PL330_REG_DBGSTATUS) & PL330_DBGSTATUS_BUSY)
{
rt_hw_cpu_relax();
}
HWREG32(pl330->regs + PL330_REG_DBGINST0) = ((pl330_chan_id(pl330, pc) + 1) << 8) | PL330_CMD_DMAKILL;
HWREG32(pl330->regs + PL330_REG_DBGINST1) = 0;
HWREG32(pl330->regs + PL330_REG_DBGCMD) = 0;
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)
{
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)
{
void *mc;
rt_err_t err;
rt_uint32_t value;
rt_size_t burst_bytes;
rt_uint8_t ljmp_inner, ljmp_outer;
rt_uint16_t loop, outer, rem, inner_first;
struct pl330_chan *pc = rt_container_of(chan, struct pl330_chan, parent);
struct pl330 *pl330 = raw_to_pl330(chan->ctrl);
struct rt_dma_slave_config *conf = &chan->conf;
mc = pc->microcode;
if ((err = pl330_ccr_config(conf, &value)))
{
return err;
}
mc += pl330_cmd_dmamov(mc, PL330_DIR_CCR, value);
mc += pl330_cmd_dmamov(mc, PL330_DIR_SAR, dma_addr_src);
mc += pl330_cmd_dmamov(mc, PL330_DIR_DAR, dma_addr_dst);
burst_bytes = conf->src_addr_width * conf->src_maxburst;
if (!burst_bytes || !len)
{
return -RT_EINVAL;
}
if (len % burst_bytes)
{
LOG_E("Memcpy len(%lu) must be multiple of burst(%lu)", len, burst_bytes);
return -RT_EINVAL;
}
/* Total iterations */
loop = len / burst_bytes;
if (loop == 0)
{
return -RT_EINVAL;
}
outer = loop / 256;
rem = loop % 256;
if (outer == 0)
{
outer = 1;
}
inner_first = (loop < 256) ? loop : 256;
mc += pl330_cmd_dmalp(mc, 1, outer);
ljmp_outer = mc - pc->microcode;
mc += pl330_cmd_dmalp(mc, 0, inner_first);
ljmp_inner = mc - pc->microcode;
mc += pl330_cmd_dmald(mc, PL330_COND_ALWAYS);
mc += pl330_cmd_dmast(mc, PL330_COND_ALWAYS);
mc += pl330_cmd_dmalpend(mc, PL330_COND_ALWAYS, RT_FALSE, 0,
mc - pc->microcode - ljmp_inner);
if (rem && loop > 256)
{
rt_uint8_t ljmp_inner2;
mc += pl330_cmd_dmalp(mc, 0, rem);
ljmp_inner2 = mc - pc->microcode;
mc += pl330_cmd_dmald(mc, PL330_COND_ALWAYS);
mc += pl330_cmd_dmast(mc, PL330_COND_ALWAYS);
mc += pl330_cmd_dmalpend(mc, PL330_COND_ALWAYS, RT_FALSE, 0,
mc - pc->microcode - ljmp_inner2);
}
mc += pl330_cmd_dmalpend(mc, PL330_COND_ALWAYS, RT_FALSE, 1,
mc - pc->microcode - ljmp_outer);
mc += pl330_cmd_dmasev(mc, pl330_chan_id(pl330, pc));
mc += pl330_cmd_dmaend(mc);
pc->size = len;
pc->microcode_len = mc - pc->microcode;
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)
{
void *mc;
rt_err_t err;
rt_uint32_t ccr_val;
rt_size_t burst_bytes;
rt_uint16_t period_loop, total_periods;
struct pl330_chan *pc = rt_container_of(chan, struct pl330_chan, parent);
struct pl330 *pl330 = raw_to_pl330(chan->ctrl);
struct rt_dma_slave_config *conf = &chan->conf;
mc = pc->microcode;
if ((err = pl330_ccr_config(conf, &ccr_val)))
{
return err;
}
mc += pl330_cmd_dmamov(mc, PL330_DIR_CCR, ccr_val);
if (dir == RT_DMA_MEM_TO_DEV)
{
mc += pl330_cmd_dmamov(mc, PL330_DIR_SAR, dma_buf_addr);
mc += pl330_cmd_dmamov(mc, PL330_DIR_DAR, conf->dst_addr);
}
else if (dir == RT_DMA_DEV_TO_MEM)
{
mc += pl330_cmd_dmamov(mc, PL330_DIR_SAR, conf->src_addr);
mc += pl330_cmd_dmamov(mc, PL330_DIR_DAR, dma_buf_addr);
}
burst_bytes = conf->src_addr_width * conf->src_maxburst;
if (!burst_bytes)
{
return -RT_EINVAL;
}
if (period_len % burst_bytes)
{
return -RT_EINVAL;
}
if (buf_len % period_len)
{
return -RT_EINVAL;
}
period_loop = period_len / burst_bytes;
if (period_loop == 0 || period_loop > 255)
{
return -RT_EINVAL;
}
total_periods = buf_len / period_len;
if (total_periods == 0)
{
return -RT_EINVAL;
}
/* Outer Loop */
mc += pl330_cmd_dmalp(mc, 1 /* LC1 */, 0 /* Infinite loop */);
rt_uint8_t outer_ljmp = mc - pc->microcode;
/* Inner Loop */
mc += pl330_cmd_dmalp(mc, 0 /* LC0 */, period_loop);
rt_uint8_t inner_ljmp = mc - pc->microcode;
mc += pl330_cmd_dmald(mc, PL330_COND_ALWAYS);
mc += pl330_cmd_dmast(mc, PL330_COND_ALWAYS);
mc += pl330_cmd_dmalpend(mc, PL330_COND_ALWAYS,
RT_FALSE, 0,
mc - pc->microcode - inner_ljmp);
mc += pl330_cmd_dmasev(mc, pl330_chan_id(pl330, pc));
/* Outer loop end */
mc += pl330_cmd_dmalpend(mc, PL330_COND_ALWAYS,
RT_FALSE, 1, /* LC1 */
mc - pc->microcode - outer_ljmp);
pc->microcode_len = mc - pc->microcode;
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)
{
void *mc;
rt_err_t err;
rt_uint16_t loop;
rt_uint32_t ccr_val;
rt_size_t burst_bytes;
struct pl330_chan *pc = rt_container_of(chan, struct pl330_chan, parent);
struct pl330 *pl330 = raw_to_pl330(chan->ctrl);
struct rt_dma_slave_config *conf = &chan->conf;
mc = pc->microcode;
if ((err = pl330_ccr_config(conf, &ccr_val)))
{
return err;
}
mc += pl330_cmd_dmamov(mc, PL330_DIR_CCR, ccr_val);
if (dir == RT_DMA_MEM_TO_DEV)
{
mc += pl330_cmd_dmamov(mc, PL330_DIR_SAR, dma_buf_addr);
mc += pl330_cmd_dmamov(mc, PL330_DIR_DAR, conf->dst_addr);
}
else if (dir == RT_DMA_DEV_TO_MEM)
{
mc += pl330_cmd_dmamov(mc, PL330_DIR_SAR, conf->src_addr);
mc += pl330_cmd_dmamov(mc, PL330_DIR_DAR, dma_buf_addr);
}
burst_bytes = conf->src_addr_width * conf->src_maxburst;
if (!burst_bytes || !buf_len)
{
return -RT_EINVAL;
}
if (buf_len % burst_bytes)
{
return -RT_EINVAL;
}
loop = buf_len / burst_bytes;
if (loop == 0 || loop > 255)
{
return -RT_EINVAL;
}
mc += pl330_cmd_dmalp(mc, 0 /* LC0 */, loop);
rt_uint8_t ljmp = mc - pc->microcode;
mc += pl330_cmd_dmald(mc, PL330_COND_ALWAYS);
mc += pl330_cmd_dmast(mc, PL330_COND_ALWAYS);
mc += pl330_cmd_dmalpend(mc,
PL330_COND_ALWAYS,
RT_FALSE,
0, /* LC0 */
mc - pc->microcode - ljmp);
mc += pl330_cmd_dmasev(mc, pl330_chan_id(pl330, pc));
mc += pl330_cmd_dmaend(mc);
pc->microcode_len = mc - pc->microcode;
pc->size = buf_len;
return RT_EOK;
}
/** @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,
.stop = pl330_dma_stop,
.config = pl330_dma_config,
.prep_memcpy = pl330_dma_prep_memcpy,
.prep_cyclic = pl330_dma_prep_cyclic,
.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;
struct pl330_chan *pc;
struct pl330 *pl330 = params;
isr = HWREG32(pl330->regs + PL330_REG_INTMIS);
for (int i = 0; i < pl330->num_chan; ++i)
{
if (!(isr & RT_BIT(i)))
{
continue;
}
pc = &pl330->chans[i];
HWREG32(pl330->regs + PL330_REG_INTCLR) = RT_BIT(i);
csr = HWREG32(pl330->regs + PL330_REG_CSR(i)) & 0xf;
switch (csr)
{
case PL330_CS_CMPLT:
case PL330_CS_STOP:
rt_dma_chan_done(&pc->parent, pc->size);
break;
case PL330_CS_FAULT:
case PL330_CS_FLTCMP:
LOG_E("Channel[%d] fault", i);
rt_dma_chan_done(&pc->parent, 0);
break;
default:
LOG_E("Unhandle CSR = %x", csr);
break;
}
}
}
/**
* @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)
{
rt_iounmap(pl330->regs);
}
if (!rt_is_err_or_null(pl330->pclk))
{
rt_clk_disable_unprepare(pl330->pclk);
rt_clk_put(pl330->pclk);
}
if (!rt_is_err_or_null(pl330->rstc))
{
rt_reset_control_assert(pl330->rstc);
rt_reset_control_put(pl330->rstc);
}
if (!rt_is_err_or_null(pl330->rstc_ocp))
{
rt_reset_control_assert(pl330->rstc_ocp);
rt_reset_control_put(pl330->rstc_ocp);
}
if (pl330->chans)
{
rt_free(pl330->chans);
}
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;
char isr_name[RT_NAME_MAX];
struct rt_device *dev = &pdev->parent;
struct pl330 *pl330 = rt_calloc(1, sizeof(*pl330));
if (!pl330)
{
return -RT_ENOMEM;
}
pl330->regs = rt_dm_dev_iomap(dev, 0);
if (!pl330->regs)
{
err = -RT_EIO;
goto _fail;
}
pl330->irqs_nr = rt_dm_dev_get_irq_count(dev);
if (pl330->irqs_nr < 0)
{
err = pl330->irqs_nr;
goto _fail;
}
else if (pl330->irqs_nr == 0)
{
err = -RT_EINVAL;
goto _fail;
}
for (int i = 0; i < pl330->irqs_nr; ++i)
{
int irq = rt_dm_dev_get_irq(dev, i);
if (irq < 0)
{
err = irq;
goto _fail;
}
pl330->irqs[i] = irq;
}
pl330->pclk = rt_clk_get_by_name(dev, "apb_pclk");
if (rt_is_err(pl330->pclk))
{
err = rt_ptr_err(pl330->pclk);
goto _fail;
}
if ((err = rt_clk_prepare_enable(pl330->pclk)))
{
goto _fail;
}
pl330->rstc = rt_reset_control_get_by_name(dev, "dma");
if (rt_is_err(pl330->rstc))
{
err = rt_ptr_err(pl330->rstc);
goto _fail;
}
if (pl330->rstc && (err = rt_reset_control_deassert(pl330->rstc)))
{
goto _fail;
}
pl330->rstc_ocp = rt_reset_control_get_by_name(dev, "dma-ocp");
if (rt_is_err(pl330->rstc_ocp))
{
err = rt_ptr_err(pl330->rstc_ocp);
goto _fail;
}
if (pl330->rstc_ocp && (err = rt_reset_control_deassert(pl330->rstc_ocp)))
{
goto _fail;
}
if (rt_dm_dev_prop_read_bool(dev, "arm,pl330-broken-no-flushp"))
{
pl330->quirk |= PL330_QUIRK_BROKEN_NO_FLUSHP;
}
if (rt_dm_dev_prop_read_bool(dev, "arm,pl330-periph-burst"))
{
pl330->quirk |= PL330_QUIRK_PERIPH_BURST;
}
pl330_read_config(pl330);
pl330->chans = rt_calloc(pl330->num_chan, sizeof(pl330->chans[0]));
if (!pl330->chans)
{
err = -RT_ENOMEM;
goto _fail;
}
for (int i = 0; i < pl330->num_chan; ++i)
{
int offset = 0;
struct pl330_chan *chan = &pl330->chans[i];
chan->microcode_dma = (rt_ubase_t)rt_kmem_v2p(chan->microcode_raw);
offset = RT_ALIGN(chan->microcode_dma, 4) - chan->microcode_dma;
chan->microcode = chan->microcode_raw + offset;
chan->microcode_dma += offset;
}
pl330->parent.dev = dev;
pl330->parent.ops = &pl330_dma_ops;
rt_dma_controller_add_direction(&pl330->parent, RT_DMA_MEM_TO_MEM);
rt_dma_controller_add_direction(&pl330->parent, RT_DMA_MEM_TO_DEV);
rt_dma_controller_add_direction(&pl330->parent, RT_DMA_DEV_TO_MEM);
rt_dma_controller_set_addr_mask(&pl330->parent, RT_DMA_ADDR_MASK(32));
if ((err = rt_dma_controller_register(&pl330->parent)))
{
goto _fail;
}
dev->user_data = pl330;
for (int i = 0; i < pl330->irqs_nr; ++i)
{
rt_snprintf(isr_name, RT_NAME_MAX, "%s-%u", rt_dm_dev_get_name(dev), i);
rt_hw_interrupt_install(pl330->irqs[i], pl330_isr, pl330, isr_name);
rt_hw_interrupt_umask(pl330->irqs[i]);
}
return RT_EOK;
_fail:
pl330_free(pl330);
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;
for (int i = 0; i < pl330->irqs_nr; ++i)
{
rt_hw_interrupt_mask(pl330->irqs[i]);
rt_pic_detach_irq(pl330->irqs[i], pl330);
}
rt_dma_controller_unregister(&pl330->parent);
pl330_free(pl330);
return RT_EOK;
}
/** @brief Device tree compatible IDs for the PL330 driver */
static const struct rt_ofw_node_id pl330_ofw_ids[] = {
{ .compatible = "arm,pl330" },
{ /* sentinel */ }
};
/** @brief PL330 platform driver descriptor */
static struct rt_platform_driver pl330_driver = {
.name = "dma-pl330",
.ids = pl330_ofw_ids,
.probe = pl330_probe,
.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);
return 0;
}
INIT_SUBSYS_EXPORT(pl330_drv_register);