From: Dinh Nguyen <dinguyen@kernel.org>
To: u-boot@lists.denx.de
Subject: [U-Boot] [RFC 1/4] drivers: dma: Add the ARM PL330 DMA driver
Date: Mon, 10 Oct 2016 10:52:20 -0500 [thread overview]
Message-ID: <20161010155223.23751-2-dinguyen@kernel.org> (raw)
In-Reply-To: <20161010155223.23751-1-dinguyen@kernel.org>
From: Dinh Nguyen <dinguyen@opensource.altera.com>
Adopted from the Linux kernel PL330 DMA driver.
Signed-off-by: Dinh Nguyen <dinguyen@opensource.altera.com>
---
arch/arm/include/asm/pl330.h | 105 +++++
drivers/dma/pl330.c | 942 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 1047 insertions(+)
create mode 100644 arch/arm/include/asm/pl330.h
create mode 100644 drivers/dma/pl330.c
diff --git a/arch/arm/include/asm/pl330.h b/arch/arm/include/asm/pl330.h
new file mode 100644
index 0000000..dd19b4c
--- /dev/null
+++ b/arch/arm/include/asm/pl330.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 Samsung Electronics Co. Ltd.
+ * Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * adapted from linux kernel pl330.h
+ */
+
+#ifndef __PL330_H_
+#define __PL330_H_
+
+#define PL330_STATE_STOPPED (1 << 0)
+#define PL330_STATE_EXECUTING (1 << 1)
+#define PL330_STATE_WFE (1 << 2)
+#define PL330_STATE_FAULTING (1 << 3)
+#define PL330_STATE_COMPLETING (1 << 4)
+#define PL330_STATE_WFP (1 << 5)
+#define PL330_STATE_KILLING (1 << 6)
+#define PL330_STATE_FAULT_COMPLETING (1 << 7)
+#define PL330_STATE_CACHEMISS (1 << 8)
+#define PL330_STATE_UPDTPC (1 << 9)
+#define PL330_STATE_ATBARRIER (1 << 10)
+#define PL330_STATE_QUEUEBUSY (1 << 11)
+#define PL330_STATE_INVALID (1 << 15)
+
+#define PL330_DMA_MAX_BURST_SIZE 3
+
+/* structure to be passed in for pl330_transfer_x */
+struct pl330_transfer_struct {
+ void __iomem *reg_base;
+ u32 channel_num;
+ u32 src_addr;
+ u32 dst_addr;
+ u32 len;
+ u32 brst_size;
+ u32 single_brst_size;
+ u32 brst_len;
+ u32 peripheral_id;
+ u32 transfer_type;
+ u32 enable_cache1;
+ u32 buf_size;
+ u8 *buf;
+};
+
+enum pl330_srccachectrl {
+ SCCTRL0 = 0, /* Noncacheable and nonbufferable */
+ SCCTRL1, /* Bufferable only */
+ SCCTRL2, /* Cacheable, but do not allocate */
+ SCCTRL3, /* Cacheable and bufferable, but do not allocate */
+ SINVALID1,
+ SINVALID2,
+ SCCTRL6, /* Cacheable write-through, allocate on reads only */
+ SCCTRL7, /* Cacheable write-back, allocate on reads only */
+};
+
+enum pl330_dstcachectrl {
+ DCCTRL0 = 0, /* Noncacheable and nonbufferable */
+ DCCTRL1, /* Bufferable only */
+ DCCTRL2, /* Cacheable, but do not allocate */
+ DCCTRL3, /* Cacheable and bufferable, but do not allocate */
+ DINVALID1 = 8,
+ DINVALID2,
+ DCCTRL6, /* Cacheable write-through, allocate on writes only */
+ DCCTRL7, /* Cacheable write-back, allocate on writes only */
+};
+
+enum pl330_byteswap {
+ SWAP_NO = 0,
+ SWAP_2,
+ SWAP_4,
+ SWAP_8,
+ SWAP_16,
+};
+
+/*
+ * Request Configuration.
+ * The PL330 core does not modify this and uses the last
+ * working configuration if the request doesn't provide any.
+ *
+ * The Client may want to provide this info only for the
+ * first request and a request with new settings.
+ */
+struct pl330_reqcfg {
+ /* Address Incrementing */
+ unsigned dst_inc:1;
+ unsigned src_inc:1;
+
+ /*
+ * For now, the SRC & DST protection levels
+ * and burst size/length are assumed same.
+ */
+ int nonsecure;
+ int privileged;
+ int insnaccess;
+ unsigned brst_len:5;
+ unsigned brst_size:3; /* in power of 2 */
+
+ enum pl330_dstcachectrl dcctl;
+ enum pl330_srccachectrl scctl;
+ enum pl330_byteswap swap;
+};
+
+void arm_pl330_transfer(struct pl330_transfer_struct *pl330);
+#endif /* __PL330_H_ */
diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c
new file mode 100644
index 0000000..a97cd9f
--- /dev/null
+++ b/drivers/dma/pl330.c
@@ -0,0 +1,942 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Copyright (C) 2010 Samsung Electronics Co. Ltd.
+ * Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <asm/io.h>
+#include <common.h>
+#include <dma.h>
+#include <dm/device.h>
+#include <asm/pl330.h>
+#include <asm/processor.h>
+
+struct dma_pl330_platdata {
+ u32 base;
+};
+
+/* Register and Bit field Definitions */
+#define DS 0x0
+#define DS_ST_STOP 0x0
+#define DS_ST_EXEC 0x1
+#define DS_ST_CMISS 0x2
+#define DS_ST_UPDTPC 0x3
+#define DS_ST_WFE 0x4
+#define DS_ST_ATBRR 0x5
+#define DS_ST_QBUSY 0x6
+#define DS_ST_WFP 0x7
+#define DS_ST_KILL 0x8
+#define DS_ST_CMPLT 0x9
+#define DS_ST_FLTCMP 0xe
+#define DS_ST_FAULT 0xf
+
+#define DPC 0x4
+#define INTEN 0x20
+#define ES 0x24
+#define INTSTATUS 0x28
+#define INTCLR 0x2c
+#define FSM 0x30
+#define FSC 0x34
+#define FTM 0x38
+
+#define _FTC 0x40
+#define FTC(n) (_FTC + (n)*0x4)
+
+#define _CS 0x100
+#define CS(n) (_CS + (n)*0x8)
+#define CS_CNS (1 << 21)
+
+#define _CPC 0x104
+#define CPC(n) (_CPC + (n)*0x8)
+
+#define _SA 0x400
+#define SA(n) (_SA + (n)*0x20)
+
+#define _DA 0x404
+#define DA(n) (_DA + (n)*0x20)
+
+#define _CC 0x408
+#define CC(n) (_CC + (n)*0x20)
+
+#define CC_SRCINC (1 << 0)
+#define CC_DSTINC (1 << 14)
+#define CC_SRCPRI (1 << 8)
+#define CC_DSTPRI (1 << 22)
+#define CC_SRCNS (1 << 9)
+#define CC_DSTNS (1 << 23)
+#define CC_SRCIA (1 << 10)
+#define CC_DSTIA (1 << 24)
+#define CC_SRCBRSTLEN_SHFT 4
+#define CC_DSTBRSTLEN_SHFT 18
+#define CC_SRCBRSTSIZE_SHFT 1
+#define CC_DSTBRSTSIZE_SHFT 15
+#define CC_SRCCCTRL_SHFT 11
+#define CC_SRCCCTRL_MASK 0x7
+#define CC_DSTCCTRL_SHFT 25
+#define CC_DRCCCTRL_MASK 0x7
+#define CC_SWAP_SHFT 28
+
+#define _LC0 0x40c
+#define LC0(n) (_LC0 + (n)*0x20)
+
+#define _LC1 0x410
+#define LC1(n) (_LC1 + (n)*0x20)
+
+#define DBGSTATUS 0xd00
+#define DBG_BUSY (1 << 0)
+
+#define DBGCMD 0xd04
+#define DBGINST0 0xd08
+#define DBGINST1 0xd0c
+
+#define CR0 0xe00
+#define CR1 0xe04
+#define CR2 0xe08
+#define CR3 0xe0c
+#define CR4 0xe10
+#define CRD 0xe14
+
+#define PERIPH_ID 0xfe0
+#define PERIPH_REV_SHIFT 20
+#define PERIPH_REV_MASK 0xf
+#define PERIPH_REV_R0P0 0
+#define PERIPH_REV_R1P0 1
+#define PERIPH_REV_R1P1 2
+
+#define CR0_PERIPH_REQ_SET (1 << 0)
+#define CR0_BOOT_EN_SET (1 << 1)
+#define CR0_BOOT_MAN_NS (1 << 2)
+#define CR0_NUM_CHANS_SHIFT 4
+#define CR0_NUM_CHANS_MASK 0x7
+#define CR0_NUM_PERIPH_SHIFT 12
+#define CR0_NUM_PERIPH_MASK 0x1f
+#define CR0_NUM_EVENTS_SHIFT 17
+#define CR0_NUM_EVENTS_MASK 0x1f
+
+#define CR1_ICACHE_LEN_SHIFT 0
+#define CR1_ICACHE_LEN_MASK 0x7
+#define CR1_NUM_ICACHELINES_SHIFT 4
+#define CR1_NUM_ICACHELINES_MASK 0xf
+
+/* Configuration register value */
+#define CRD_DATA_WIDTH_SHIFT 0
+#define CRD_DATA_WIDTH_MASK 0x7
+#define CRD_WR_CAP_SHIFT 4
+#define CRD_WR_CAP_MASK 0x7
+#define CRD_WR_Q_DEP_SHIFT 8
+#define CRD_WR_Q_DEP_MASK 0xf
+#define CRD_RD_CAP_SHIFT 12
+#define CRD_RD_CAP_MASK 0x7
+#define CRD_RD_Q_DEP_SHIFT 16
+#define CRD_RD_Q_DEP_MASK 0xf
+#define CRD_DATA_BUFF_SHIFT 20
+#define CRD_DATA_BUFF_MASK 0x3ff
+
+/* Microcode opcode value */
+#define CMD_DMAADDH 0x54
+#define CMD_DMAEND 0x00
+#define CMD_DMAFLUSHP 0x35
+#define CMD_DMAGO 0xa0
+#define CMD_DMALD 0x04
+#define CMD_DMALDP 0x25
+#define CMD_DMALP 0x20
+#define CMD_DMALPEND 0x28
+#define CMD_DMAKILL 0x01
+#define CMD_DMAMOV 0xbc
+#define CMD_DMANOP 0x18
+#define CMD_DMARMB 0x12
+#define CMD_DMASEV 0x34
+#define CMD_DMAST 0x08
+#define CMD_DMASTP 0x29
+#define CMD_DMASTZ 0x0c
+#define CMD_DMAWFE 0x36
+#define CMD_DMAWFP 0x30
+#define CMD_DMAWMB 0x13
+
+/* the size of opcode plus opcode required settings */
+#define SZ_DMAADDH 3
+#define SZ_DMAEND 1
+#define SZ_DMAFLUSHP 2
+#define SZ_DMALD 1
+#define SZ_DMALDP 2
+#define SZ_DMALP 2
+#define SZ_DMALPEND 2
+#define SZ_DMAKILL 1
+#define SZ_DMAMOV 6
+#define SZ_DMANOP 1
+#define SZ_DMARMB 1
+#define SZ_DMASEV 2
+#define SZ_DMAST 1
+#define SZ_DMASTP 2
+#define SZ_DMASTZ 1
+#define SZ_DMAWFE 2
+#define SZ_DMAWFP 2
+#define SZ_DMAWMB 1
+#define SZ_DMAGO 6
+
+/* Use this _only_ to wait on transient states */
+#define UNTIL(t, s) while (!(_state(t) & (s))) cpu_relax();
+
+/* Enum declaration */
+enum dmamov_dst {
+ SAR = 0,
+ CCR,
+ DAR,
+};
+
+enum pl330_dst {
+ SRC = 0,
+ DST,
+};
+
+enum pl330_cond {
+ SINGLE,
+ BURST,
+ ALWAYS,
+};
+
+/* Structure will be used by _emit_LPEND function */
+struct _arg_LPEND {
+ enum pl330_cond cond;
+ int forever;
+ unsigned loop;
+ u8 bjump;
+};
+
+/* Structure will be used by _emit_GO function */
+struct _arg_GO {
+ u8 chan;
+ u32 addr;
+ unsigned ns;
+};
+
+/*
+ * Function: add opcode DMAEND into microcode (end)
+ * Return: size of opcode
+ * Parameter: buf -> the buffer which stored the microcode program
+ */
+static inline u32 _emit_END(u8 buf[])
+{
+ buf[0] = CMD_DMAEND;
+
+ return SZ_DMAEND;
+}
+
+static inline u32 _emit_FLUSHP(u8 buf[], u8 peri)
+{
+ buf[0] = CMD_DMAFLUSHP;
+
+ peri &= 0x1f;
+ peri <<= 3;
+ buf[1] = peri;
+
+ return SZ_DMAFLUSHP;
+}
+
+static inline u32 _emit_LD(u8 buf[], enum pl330_cond cond)
+{
+ buf[0] = CMD_DMALD;
+
+ if (cond == SINGLE)
+ buf[0] |= (0 << 1) | (1 << 0);
+ else if (cond == BURST)
+ buf[0] |= (1 << 1) | (1 << 0);
+
+ return SZ_DMALD;
+}
+
+static inline u32 _emit_LDP(u8 buf[], enum pl330_cond cond, u8 peri)
+{
+ buf[0] = CMD_DMALDP;
+
+ if (cond == BURST)
+ buf[0] |= (1 << 1);
+
+ peri &= 0x1f;
+ peri <<= 3;
+ buf[1] = peri;
+
+ return SZ_DMALDP;
+}
+
+static inline u32 _emit_LP(u8 buf[], unsigned loop, u8 cnt)
+{
+ buf[0] = CMD_DMALP;
+
+ if (loop)
+ buf[0] |= (1 << 1);
+
+ cnt--; /* DMAC increments by 1 internally */
+ buf[1] = cnt;
+
+ return SZ_DMALP;
+}
+
+static inline u32 _emit_LPEND(u8 buf[], const struct _arg_LPEND *arg)
+{
+ enum pl330_cond cond = arg->cond;
+ bool forever = arg->forever;
+ unsigned loop = arg->loop;
+ u8 bjump = arg->bjump;
+
+ buf[0] = CMD_DMALPEND;
+
+ if (loop)
+ buf[0] |= (1 << 2);
+
+ if (!forever)
+ buf[0] |= (1 << 4);
+
+ if (cond == SINGLE)
+ buf[0] |= (0 << 1) | (1 << 0);
+ else if (cond == BURST)
+ buf[0] |= (1 << 1) | (1 << 0);
+
+ buf[1] = bjump;
+
+ return SZ_DMALPEND;
+}
+
+static inline u32 _emit_KILL(u8 buf[])
+{
+ buf[0] = CMD_DMAKILL;
+
+ return SZ_DMAKILL;
+}
+
+static inline u32 _emit_MOV(u8 buf[], enum dmamov_dst dst, u32 val)
+{
+ buf[0] = CMD_DMAMOV;
+ buf[1] = dst;
+
+ buf[2] = val & 0xFF;
+ buf[3] = (val >> 8) & 0xFF;
+ buf[4] = (val >> 16) & 0xFF;
+ buf[5] = (val >> 24) & 0xFF;
+
+ return SZ_DMAMOV;
+}
+
+static inline u32 _emit_NOP(u8 buf[])
+{
+ buf[0] = CMD_DMANOP;
+
+ return SZ_DMANOP;
+}
+
+static inline u32 _emit_RMB(u8 buf[])
+{
+ buf[0] = CMD_DMARMB;
+
+ return SZ_DMARMB;
+}
+
+static inline u32 _emit_SEV(u8 buf[], u8 ev)
+{
+ buf[0] = CMD_DMASEV;
+
+ ev &= 0x1f;
+ ev <<= 3;
+ buf[1] = ev;
+
+ return SZ_DMASEV;
+}
+
+static inline u32 _emit_ST(u8 buf[], enum pl330_cond cond)
+{
+ buf[0] = CMD_DMAST;
+
+ if (cond == SINGLE)
+ buf[0] |= (0 << 1) | (1 << 0);
+ else if (cond == BURST)
+ buf[0] |= (1 << 1) | (1 << 0);
+
+ return SZ_DMAST;
+}
+
+static inline u32 _emit_STP(u8 buf[], enum pl330_cond cond, u8 peri)
+{
+ buf[0] = CMD_DMASTP;
+
+ if (cond == BURST)
+ buf[0] |= (1 << 1);
+
+ peri &= 0x1f;
+ peri <<= 3;
+ buf[1] = peri;
+
+ return SZ_DMASTP;
+}
+
+static inline u32 _emit_STZ(u8 buf[])
+{
+ buf[0] = CMD_DMASTZ;
+
+ return SZ_DMASTZ;
+}
+
+static inline u32 _emit_WFE(u8 buf[], u8 ev, unsigned invalidate)
+{
+ buf[0] = CMD_DMAWFE;
+
+ ev &= 0x1f;
+ ev <<= 3;
+ buf[1] = ev;
+
+ if (invalidate)
+ buf[1] |= (1 << 1);
+
+ return SZ_DMAWFE;
+}
+
+static inline u32 _emit_WFP(u8 buf[], enum pl330_cond cond, u8 peri)
+{
+ buf[0] = CMD_DMAWFP;
+
+ if (cond == SINGLE)
+ buf[0] |= (0 << 1) | (0 << 0);
+ else if (cond == BURST)
+ buf[0] |= (1 << 1) | (0 << 0);
+ else
+ buf[0] |= (0 << 1) | (1 << 0);
+
+ peri &= 0x1f;
+ peri <<= 3;
+ buf[1] = peri;
+
+ return SZ_DMAWFP;
+}
+
+static inline u32 _emit_WMB(u8 buf[])
+{
+ buf[0] = CMD_DMAWMB;
+
+ return SZ_DMAWMB;
+}
+
+static inline u32 _emit_GO(u8 buf[],
+ const struct _arg_GO *arg)
+{
+ u8 chan = arg->chan;
+ u32 addr = arg->addr;
+ unsigned ns = arg->ns;
+
+ buf[0] = CMD_DMAGO;
+ buf[0] |= (ns << 1);
+
+ buf[1] = chan & 0x7;
+ buf[2] = addr & 0xFF;
+ buf[3] = (addr >> 8) & 0xFF;
+ buf[4] = (addr >> 16) & 0xFF;
+ buf[5] = (addr >> 24) & 0xFF;
+ return SZ_DMAGO;
+}
+
+/*
+ * Function: Populate the CCR register
+ * Parameter: rqc -> Request Configuration.
+ */
+static inline u32 _prepare_ccr(const struct pl330_reqcfg *rqc)
+{
+ u32 ccr = 0;
+
+ if (rqc->src_inc)
+ ccr |= CC_SRCINC;
+ if (rqc->dst_inc)
+ ccr |= CC_DSTINC;
+
+ /* We set same protection levels for Src and DST for now */
+ if (rqc->privileged)
+ ccr |= CC_SRCPRI | CC_DSTPRI;
+ if (rqc->nonsecure)
+ ccr |= CC_SRCNS | CC_DSTNS;
+ if (rqc->insnaccess)
+ ccr |= CC_SRCIA | CC_DSTIA;
+
+ ccr |= (((rqc->brst_len - 1) & 0xf) << CC_SRCBRSTLEN_SHFT);
+ ccr |= (((rqc->brst_len - 1) & 0xf) << CC_DSTBRSTLEN_SHFT);
+
+ ccr |= (rqc->brst_size << CC_SRCBRSTSIZE_SHFT);
+ ccr |= (rqc->brst_size << CC_DSTBRSTSIZE_SHFT);
+
+ ccr |= (rqc->scctl << CC_SRCCCTRL_SHFT);
+ ccr |= (rqc->dcctl << CC_DSTCCTRL_SHFT);
+
+ ccr |= (rqc->swap << CC_SWAP_SHFT);
+ return ccr;
+}
+
+/*
+ * Function: wait until DMA Manager is idle
+ * Return: 1 = error / timeout ocurred before idle
+ * Parameter: loop -> number of loop before timeout ocurred
+ */
+static int _until_dmac_idle(struct pl330_transfer_struct *pl330, int loops)
+{
+ void __iomem *regs = pl330->reg_base;
+
+ do {
+ /* Until Manager is Idle */
+ if (!(readl(regs + DBGSTATUS) & DBG_BUSY))
+ break;
+
+ cpu_relax();
+ } while (--loops);
+
+ if (!loops)
+ return true;
+
+ return false;
+}
+
+static inline void _execute_DBGINSN(struct pl330_transfer_struct *pl330,
+ u8 insn[], bool as_manager, int timeout_loops)
+{
+ void __iomem *regs = pl330->reg_base;
+ u32 val;
+
+ val = (insn[0] << 16) | (insn[1] << 24);
+ if (!as_manager) {
+ val |= (1 << 0);
+ val |= (pl330->channel_num << 8); /* Channel Number */
+ }
+ writel(val, regs + DBGINST0);
+
+ val = insn[2];
+ val = val | (insn[3] << 8);
+ val = val | (insn[4] << 16);
+ val = val | (insn[5] << 24);
+ writel(val, regs + DBGINST1);
+
+ /* If timed out due to halted state-machine */
+ if (_until_dmac_idle(pl330, timeout_loops)) {
+ printf("DMAC halted!\n");
+ return;
+ }
+
+ /* Get going */
+ writel(0, regs + DBGCMD);
+}
+
+static inline u32 _state(struct pl330_transfer_struct *pl330)
+{
+ void __iomem *regs = pl330->reg_base;
+ u32 val;
+
+ val = readl(regs + CS(pl330->channel_num)) & 0xf;
+
+ udelay(1);
+
+ switch (val) {
+ case DS_ST_STOP:
+ return PL330_STATE_STOPPED;
+ case DS_ST_EXEC:
+ return PL330_STATE_EXECUTING;
+ case DS_ST_CMISS:
+ return PL330_STATE_CACHEMISS;
+ case DS_ST_UPDTPC:
+ return PL330_STATE_UPDTPC;
+ case DS_ST_WFE:
+ return PL330_STATE_WFE;
+ case DS_ST_FAULT:
+ return PL330_STATE_FAULTING;
+ case DS_ST_ATBRR:
+ return PL330_STATE_ATBARRIER;
+ case DS_ST_QBUSY:
+ return PL330_STATE_QUEUEBUSY;
+ case DS_ST_WFP:
+ return PL330_STATE_WFP;
+ case DS_ST_KILL:
+ return PL330_STATE_KILLING;
+ case DS_ST_CMPLT:
+ return PL330_STATE_COMPLETING;
+ case DS_ST_FLTCMP:
+ return PL330_STATE_FAULT_COMPLETING;
+ default:
+ return PL330_STATE_INVALID;
+ }
+}
+
+static void _stop(struct pl330_transfer_struct *pl330, int timeout_loops)
+{
+ u8 insn[6] = {0, 0, 0, 0, 0, 0};
+
+ if (_state(pl330) == PL330_STATE_FAULT_COMPLETING)
+ UNTIL(pl330, PL330_STATE_FAULTING | PL330_STATE_KILLING);
+
+ /* Return if nothing needs to be done */
+ if (_state(pl330) == PL330_STATE_COMPLETING
+ || _state(pl330) == PL330_STATE_KILLING
+ || _state(pl330) == PL330_STATE_STOPPED)
+ return;
+
+ _emit_KILL(insn);
+
+ _execute_DBGINSN(pl330, insn, 0, timeout_loops);
+}
+
+static bool _trigger(struct pl330_transfer_struct *pl330, u8 *buffer,
+ int timeout_loops)
+{
+ void __iomem *regs = pl330->reg_base;
+ struct _arg_GO go;
+ u8 insn[6] = {0, 0, 0, 0, 0, 0};
+
+ /* Return if already ACTIVE */
+ if (_state(pl330) != PL330_STATE_STOPPED)
+ return true;
+
+ go.chan = pl330->channel_num;
+ go.addr = (u32)buffer;
+
+ /* determine security. Assume secure */
+ if (readl(regs + CS(go.chan)) & CS_CNS)
+ go.ns = 1;
+ else
+ go.ns = 0;
+ _emit_GO(insn, &go);
+
+ /* Only manager can execute GO */
+ _execute_DBGINSN(pl330, insn, true, timeout_loops);
+
+ return false;
+}
+
+static bool _start(struct pl330_transfer_struct *pl330, int timeout_loops)
+{
+ switch (_state(pl330)) {
+ case PL330_STATE_FAULT_COMPLETING:
+ UNTIL(pl330, PL330_STATE_FAULTING | PL330_STATE_KILLING);
+
+ if (_state(pl330) == PL330_STATE_KILLING)
+ UNTIL(pl330, PL330_STATE_STOPPED)
+
+ case PL330_STATE_FAULTING:
+ _stop(pl330, timeout_loops);
+
+ case PL330_STATE_KILLING:
+ case PL330_STATE_COMPLETING:
+ UNTIL(pl330, PL330_STATE_STOPPED)
+
+ case PL330_STATE_STOPPED:
+ return _trigger(pl330, pl330->buf, timeout_loops);
+
+ case PL330_STATE_WFP:
+ case PL330_STATE_QUEUEBUSY:
+ case PL330_STATE_ATBARRIER:
+ case PL330_STATE_UPDTPC:
+ case PL330_STATE_CACHEMISS:
+ case PL330_STATE_EXECUTING:
+ return true;
+
+ case PL330_STATE_WFE: /* For RESUME, nothing yet */
+ default:
+ return false;
+ }
+}
+
+/*
+ * DMA run or start
+ * Return: 1 for error or not successful
+ */
+static int pl330_transfer_start(struct pl330_transfer_struct *pl330)
+{
+ /* Timeout loop */
+ int timeout_loops = 10000;
+
+ /* Execute the command list */
+ return _start(pl330, timeout_loops);
+}
+
+/*
+ * DMA poll until finish or error
+ * Return: 1 for error or not successful
+ * channel_num - channel number assigned, valid from 0 to 7
+ */
+static int pl330_transfer_finish(struct pl330_transfer_struct *pl330)
+{
+ /* Wait until finish execution to ensure we compared correct result*/
+ UNTIL(pl330, PL330_STATE_STOPPED | PL330_STATE_FAULTING);
+
+ /* check the state */
+ if (_state(pl330) == PL330_STATE_FAULTING) {
+ printf("FAULT Mode: Channel %u Faulting, FTR = 0x%08x, "
+ "CPC = 0x%08x\n", pl330->channel_num,
+ readl(pl330->reg_base + FTC(pl330->channel_num)),
+ ((u32)readl(pl330->reg_base + CPC(pl330->channel_num))
+ - (u32)pl330->buf));
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * DMA transfer setup (DMA_SUPPORTS_MEM_TO_MEM, DMA_SUPPORTS_MEM_TO_DEV or
+ DMA_SUPPORTS_DEV_TO_MEM)
+ * For Peripheral transfer, the FIFO threshold value is expected at
+ * 2 ^ pl330->brst_size * pl330->brst_len.
+ * Return: 1 for error or not successful
+ *
+ * channel_num - channel number assigned, valid from 0 to 7
+ * src_addr - address to transfer from / source
+ * dst_addr - address to transfer to / destination
+ * len - number of bytes to be transferred
+ * brst_size - valid from 0 - 3
+ * where 0 = 1 (2 ^ 0) bytes and 3 = 8 bytes (2 ^ 3)
+ * single_brst_size - single transfer size (from 0 - 3)
+ * brst_len - valid from 1 - 16 where each burst can trasfer 1 - 16
+ * data chunk (each chunk size equivalent to brst_size)
+ * peripheral_id assigned peripheral_id, valid from 0 to 31
+ * transfer_type DMA_SUPPORTS_MEM_TO_MEM, DMA_SUPPORTS_MEM_TO_DEV or
+ * DMA_SUPPORTS_DEV_TO_MEM
+ * buf_size - sizeof(buf)
+ * buf - buffer handler which will point to the memory
+ * allocated for dma microcode
+ */
+static int pl330_transfer_setup(struct pl330_transfer_struct *pl330)
+{
+ /* Variable declaration */
+ int off = 0; /* buffer offset clear to 0 */
+ int ret = 0;
+ unsigned loopjmp0, loopjmp1; /* for DMALPEND */
+ unsigned lcnt0 = 0; /* loop count 0 */
+ unsigned lcnt1 = 0; /* loop count 1 */
+ unsigned burst_size = 0;
+ unsigned len = pl330->len;
+ u32 ccr = 0; /* Channel Control Register */
+ struct pl330_reqcfg reqcfg;
+
+ /* for burst, always use the maximum burst size and length */
+ pl330->brst_size = PL330_DMA_MAX_BURST_SIZE;
+ pl330->brst_len = 16;
+ pl330->single_brst_size = 1;
+
+ /* burst_size = 2 ^ brst_size */
+ burst_size = 1 << pl330->brst_size;
+
+ pl330->src_addr = (u32)&pl330->buf;
+ if (pl330->dst_addr & (burst_size - 1)) {
+ puts("ERROR PL330 : destination address unaligned\n");
+ return 1;
+ }
+
+ /* DMAMOV DAR, x->dst_addr */
+ off += _emit_MOV(&pl330->buf[off], DAR, pl330->dst_addr);
+ /* DMAFLUSHP P(periheral_id) */
+ if (pl330->transfer_type != DMA_SUPPORTS_MEM_TO_MEM)
+ off += _emit_FLUSHP(&pl330->buf[off], pl330->peripheral_id);
+
+ /* Preparing the CCR value */
+ if (pl330->transfer_type == DMA_SUPPORTS_MEM_TO_DEV) {
+ reqcfg.dst_inc = 0; /* disable auto increment */
+ reqcfg.src_inc = 1; /* enable auto increment */
+ } else if (pl330->transfer_type == DMA_SUPPORTS_DEV_TO_MEM) {
+ reqcfg.dst_inc = 1;
+ reqcfg.src_inc = 0;
+ } else {
+ /* DMA_SUPPORTS_MEM_TO_MEM */
+ reqcfg.dst_inc = 1;
+ reqcfg.src_inc = 1;
+ }
+
+ reqcfg.nonsecure = 0; /* Secure mode */
+ reqcfg.dcctl = 0x1; /* noncacheable but bufferable */
+ reqcfg.scctl = 0x1;
+ reqcfg.privileged = 1; /* 1 - Priviledge */
+ reqcfg.insnaccess = 0; /* 0 - data access */
+ reqcfg.swap = 0; /* 0 - no endian swap */
+ reqcfg.brst_len = pl330->brst_len; /* DMA burst length */
+ reqcfg.brst_size = pl330->brst_size; /* DMA burst size */
+ /* Preparing the CCR value */
+ ccr = _prepare_ccr(&reqcfg);
+ /* DMAMOV CCR, ccr */
+ off += _emit_MOV(&pl330->buf[off], CCR, ccr);
+
+ /* BURST */
+ /* Can initiate a burst? */
+ while (len >= burst_size * pl330->brst_len) {
+ lcnt0 = len / (burst_size * pl330->brst_len);
+ lcnt1 = 0;
+ if (lcnt0 >= 256 * 256)
+ lcnt0 = lcnt1 = 256;
+ else if (lcnt0 >= 256) {
+ lcnt1 = lcnt0 / 256;
+ lcnt0 = 256;
+ }
+ len = len -
+ (burst_size * pl330->brst_len * lcnt0 * lcnt1);
+
+ if (lcnt1) {
+ /* DMALP1 */
+ off += _emit_LP(&pl330->buf[off], 1, lcnt1);
+ loopjmp1 = off;
+ }
+ /* DMALP0 */
+ off += _emit_LP(&pl330->buf[off], 0, lcnt0);
+ loopjmp0 = off;
+
+ off += _emit_STZ(&pl330->buf[off]);
+ /* DMALP0END */
+ struct _arg_LPEND lpend;
+ lpend.cond = ALWAYS;
+ lpend.forever = 0;
+ lpend.loop = 0; /* loop cnt 0 */
+ lpend.bjump = off - loopjmp0;
+ off += _emit_LPEND(&pl330->buf[off], &lpend);
+ /* DMALP1END */
+ if (lcnt1) {
+ struct _arg_LPEND lpend;
+ lpend.cond = ALWAYS;
+ lpend.forever = 0;
+ lpend.loop = 1; /* loop cnt 1*/
+ lpend.bjump = off - loopjmp1;
+ off += _emit_LPEND(&pl330->buf[off], &lpend);
+ }
+ /* ensure the microcode don't exceed buffer size */
+ if (off > pl330->buf_size) {
+ puts("ERROR PL330 : Exceed buffer size\n");
+ return 1;
+ }
+ }
+
+ /* SINGLE */
+ pl330->brst_size = pl330->single_brst_size;
+ pl330->brst_len = 1;
+ /* burst_size = 2 ^ brst_size */
+ burst_size = (1 << pl330->brst_size);
+ lcnt0 = len / (burst_size * pl330->brst_len);
+
+ /* ensure all data will be transfered */
+ len = len -
+ (burst_size * pl330->brst_len * lcnt0);
+ if (len)
+ puts("ERROR PL330 : Detected the possibility of untransfered"
+ "data. Please ensure correct single burst size\n");
+
+ if (lcnt0) {
+ /* Preparing the CCR value */
+ reqcfg.brst_len = pl330->brst_len; /* DMA burst length */
+ reqcfg.brst_size = pl330->brst_size; /* DMA burst size */
+ ccr = _prepare_ccr(&reqcfg);
+ /* DMAMOV CCR, ccr */
+ off += _emit_MOV(&pl330->buf[off], CCR, ccr);
+
+ /* DMALP0 */
+ off += _emit_LP(&pl330->buf[off], 0, lcnt0);
+ loopjmp0 = off;
+
+ off += _emit_STZ(&pl330->buf[off]);
+ struct _arg_LPEND lpend1;
+ lpend1.cond = ALWAYS;
+ lpend1.forever = 0;
+ lpend1.loop = 0; /* loop cnt 0 */
+ lpend1.bjump = off - loopjmp0;
+ off += _emit_LPEND(&pl330->buf[off], &lpend1);
+ /* ensure the microcode don't exceed buffer size */
+ if (off > pl330->buf_size) {
+ puts("ERROR PL330 : Exceed buffer size\n");
+ return 1;
+ }
+ }
+
+ /* DMAEND */
+ off += _emit_END(&pl330->buf[off]);
+
+ ret = pl330_transfer_start(pl330);
+ if (ret)
+ return ret;
+
+ ret = pl330_transfer_finish(pl330);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+#ifndef CONFIG_DMA
+void arm_pl330_transfer(struct pl330_transfer_struct *pl330)
+{
+ pl330_transfer_setup(pl330);
+}
+
+#else
+static int pl330_transfer(struct udevice *dev, int direction, void *dst,
+ void *src, size_t len)
+{
+ int ret = 0;
+ struct dma_pl330_platdata *priv = dev_get_priv(dev);
+ struct pl330_transfer_struct *pl330;
+
+ /* Allocate a new DMAC and its Channels */
+ pl330 = devm_kzalloc(dev, sizeof(*pl330), GFP_KERNEL);
+ if (!pl330)
+ return -ENOMEM;
+
+ pl330->reg_base = priv->base;
+
+ pl330->dst_addr = (unsigned int) (dst);
+ pl330->src_addr = (unsigned int) (src);
+ pl330->len = len;
+
+ /* channel 1 */
+ pl330->channel_num = 1;
+
+ switch(direction) {
+ case DMA_MEM_TO_MEM:
+ pl330->transfer_type = DMA_SUPPORTS_MEM_TO_MEM;
+ break;
+ case DMA_MEM_TO_DEV:
+ pl330->transfer_type = DMA_SUPPORTS_MEM_TO_DEV;
+ break;
+ case DMA_DEV_TO_MEM:
+ pl330->transfer_type = DMA_SUPPORTS_DEV_TO_MEM;
+ break;
+ }
+
+ ret = pl330_transfer_setup(pl330);
+
+ return ret;
+}
+
+static int pl330_ofdata_to_platdata(struct udevice *dev)
+{
+ struct dma_pl330_platdata *priv = dev_get_priv(dev);
+
+ priv->base = dev_get_addr(dev);
+
+ return 0;
+}
+
+static int pl330_probe(struct udevice *adev)
+{
+ struct dma_dev_priv *uc_priv = dev_get_uclass_priv(adev);
+
+ uc_priv->supported = (DMA_SUPPORTS_MEM_TO_MEM |
+ DMA_SUPPORTS_MEM_TO_DEV |
+ DMA_SUPPORTS_DEV_TO_MEM);
+ return 0;
+}
+
+static const struct dma_ops pl330_ops = {
+ .transfer = pl330_transfer,
+};
+
+static const struct udevice_id pl330_ids[] = {
+ { .compatible = "arm,pl330" },
+ { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(dma_pl330) = {
+ .name = "dma_pl330",
+ .id = UCLASS_DMA,
+ .of_match = pl330_ids,
+ .ops = &pl330_ops,
+ .ofdata_to_platdata = pl330_ofdata_to_platdata,
+ .probe = pl330_probe,
+ .priv_auto_alloc_size = sizeof(struct dma_pl330_platdata),
+};
+#endif /* CONFIG_DMA */
--
2.8.3
next prev parent reply other threads:[~2016-10-10 15:52 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-10-10 15:52 [U-Boot] [RFC] 0/4] Add Pl330 DMA support Dinh Nguyen
2016-10-10 15:52 ` Dinh Nguyen [this message]
2016-10-12 16:04 ` [U-Boot] [RFC 1/4] drivers: dma: Add the ARM PL330 DMA driver Chin Liang See
2016-10-10 15:52 ` [U-Boot] [RFC 2/4] dma: Kconfig: Add CONFIG_PL330_DMA entry Dinh Nguyen
2016-10-12 16:05 ` Chin Liang See
2016-10-14 15:17 ` Marek Vasut
2016-10-10 15:52 ` [U-Boot] [RFC 3/4] dm: add DMA_SUPPORTS_DEV_TO_MEM type to DMA_UCLASS Dinh Nguyen
2016-10-13 0:03 ` Simon Glass
2016-10-14 15:20 ` Marek Vasut
2016-10-10 15:52 ` [U-Boot] [RFC 4/4] arm: socfpga: scrub the SDRAM to properly enable ECC support Dinh Nguyen
2016-10-12 16:13 ` Chin Liang See
2016-10-16 16:03 ` Marek Vasut
2016-10-14 7:23 ` [U-Boot] [RFC] 0/4] Add Pl330 DMA support Marek Vasut
2016-10-14 13:08 ` Dinh Nguyen
2016-10-14 15:09 ` Marek Vasut
2016-10-14 22:13 ` Dinh Nguyen
2016-10-15 16:43 ` Marek Vasut
2016-10-14 22:10 ` Dinh Nguyen
2016-10-16 15:59 ` Marek Vasut
2016-10-18 15:50 ` Dinh Nguyen
2016-10-18 18:36 ` Marek Vasut
2016-10-29 19:59 ` Marek Vasut
2016-11-01 15:43 ` Dinh Nguyen
2016-11-01 20:09 ` Marek Vasut
2016-11-01 20:39 ` Dinh Nguyen
2016-11-01 20:42 ` Marek Vasut
2016-11-01 20:43 ` Dinh Nguyen
2016-11-02 16:30 ` Dinh Nguyen
2016-11-02 20:47 ` Marek Vasut
2016-11-03 14:21 ` Dinh Nguyen
2016-11-04 20:56 ` Marek Vasut
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20161010155223.23751-2-dinguyen@kernel.org \
--to=dinguyen@kernel.org \
--cc=u-boot@lists.denx.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.