|
|
@@ -0,0 +1,1045 @@
|
|
|
+/*
|
|
|
+ * 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
|
|
|
+ */
|
|
|
+
|
|
|
+#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>
|
|
|
+
|
|
|
+#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
|
|
|
+
|
|
|
+#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
|
|
|
+
|
|
|
+#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
|
|
|
+
|
|
|
+#define PL330_DIR_SAR 0
|
|
|
+#define PL330_DIR_CCR 1
|
|
|
+#define PL330_DIR_DAR 2
|
|
|
+
|
|
|
+#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
|
|
|
+
|
|
|
+#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
|
|
|
+
|
|
|
+struct pl330_chan
|
|
|
+{
|
|
|
+ struct rt_dma_chan parent;
|
|
|
+
|
|
|
+ rt_bool_t enabled;
|
|
|
+ rt_size_t size;
|
|
|
+
|
|
|
+ void *microcode;
|
|
|
+ rt_size_t microcode_len;
|
|
|
+ rt_ubase_t microcode_dma;
|
|
|
+ rt_uint8_t microcode_raw[PL330_MICROCODE_SIZE + 4]; /* For align */
|
|
|
+};
|
|
|
+
|
|
|
+struct pl330
|
|
|
+{
|
|
|
+ struct rt_dma_controller parent;
|
|
|
+
|
|
|
+ void *regs;
|
|
|
+ int irqs_nr;
|
|
|
+ int irqs[AMBA_NR_IRQS];
|
|
|
+
|
|
|
+#define PL330_QUIRK_BROKEN_NO_FLUSHP RT_BIT(0)
|
|
|
+#define PL330_QUIRK_PERIPH_BURST RT_BIT(1)
|
|
|
+ rt_uint32_t quirk;
|
|
|
+
|
|
|
+ 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;
|
|
|
+
|
|
|
+ struct pl330_chan *chans;
|
|
|
+
|
|
|
+ struct rt_clk *pclk;
|
|
|
+ struct rt_reset_control *rstc;
|
|
|
+ struct rt_reset_control *rstc_ocp;
|
|
|
+};
|
|
|
+#define raw_to_pl330(raw) rt_container_of(raw, struct pl330, parent)
|
|
|
+
|
|
|
+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));
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+static rt_uint32_t pl330_chan_id(struct pl330 *pl330, struct pl330_chan *pc)
|
|
|
+{
|
|
|
+ return pc - pl330->chans;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+static rt_err_t pl330_dma_config(struct rt_dma_chan *chan,
|
|
|
+ struct rt_dma_slave_config *conf)
|
|
|
+{
|
|
|
+ return RT_EOK;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+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,
|
|
|
+};
|
|
|
+
|
|
|
+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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+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);
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+static const struct rt_ofw_node_id pl330_ofw_ids[] =
|
|
|
+{
|
|
|
+ { .compatible = "arm,pl330" },
|
|
|
+ { /* sentinel */ }
|
|
|
+};
|
|
|
+
|
|
|
+static struct rt_platform_driver pl330_driver =
|
|
|
+{
|
|
|
+ .name = "dma-pl330",
|
|
|
+ .ids = pl330_ofw_ids,
|
|
|
+
|
|
|
+ .probe = pl330_probe,
|
|
|
+ .remove = pl330_remove,
|
|
|
+};
|
|
|
+
|
|
|
+static int pl330_drv_register(void)
|
|
|
+{
|
|
|
+ rt_platform_driver_register(&pl330_driver);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+INIT_SUBSYS_EXPORT(pl330_drv_register);
|