[U-Boot] [RFC v2 0/3] Add PL330 DMA support on SoCFPGA

Hello,
I'm seeking advice on debugging on what is happening when I use the PL330 DMA controller to transfer zero's to the SDRAM and the effect of memory barriers.
Here are my current observations:
1) With commit "a78cd8613204 ARM: Rework and correct barrier definitions", the correct memory barrier calls are in place. With this patch in place, this version of the PL330 DMA driver can correctly transfer zero's to the SDRAM.
However, after the transfer is done, I'm encountering this error on SPL boot up: Trying to boot from MMC1 spl: could not find mmc device. error: -19 SPL: failed to boot from all boot devices ### ERROR ### Please RESET the board ###
The above error can be narrowed down to the list_emtpy(&uc->dev_head) in uclass_find_device() being true. A simple revert of patch a78cd8613204 make this error go away.
2) I've tried to replace all the readl/writel with raw_readl/raw_writel in the pl330 driver. Same error.
3) Removing the __iormb and __iowmb in readl/writel, effectively, removing the barriers. Error goes away.
I don't know enough what affect the barriers are having on the DMA memory transfer, so any guidance would be greatly appreciated.
Thanks, Dinh
Dinh Nguyen (3): drivers: dma: Add the ARM PL330 DMA driver arm: socfpga: scrub the SDRAM to properly enable ECC support arm: socfpga: Add reset manager defines for reset status
arch/arm/include/asm/pl330.h | 105 +++ arch/arm/mach-socfpga/include/mach/reset_manager.h | 21 + arch/arm/mach-socfpga/include/mach/sdram.h | 2 + arch/arm/mach-socfpga/spl.c | 8 + arch/arm/mach-socfpga/wrap_sdram_config.c | 32 + configs/socfpga_cyclone5_defconfig | 2 + drivers/dma/Kconfig | 4 + drivers/dma/Makefile | 1 + drivers/dma/pl330.c | 942 +++++++++++++++++++++ 9 files changed, 1117 insertions(+) create mode 100644 arch/arm/include/asm/pl330.h create mode 100644 drivers/dma/pl330.c

From: Dinh Nguyen dinguyen@opensource.altera.com
Adopted from the Linux kernel PL330 DMA driver.
Signed-off-by: Dinh Nguyen dinguyen@kernel.org --- v2: Add Kconfig CONFIG_PL330_DMA entry --- arch/arm/include/asm/pl330.h | 105 +++++ drivers/dma/Kconfig | 4 + drivers/dma/Makefile | 1 + drivers/dma/pl330.c | 942 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1052 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/Kconfig b/drivers/dma/Kconfig index 1b92c77..d95f959 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -19,4 +19,8 @@ config TI_EDMA3 This driver support data transfer between memory regions.
+config PL330_DMA + bool "PL330 DMA driver" + help + Enable the ARM PL330 DMA driver. endmenu # menu "DMA Support" diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 39b78b2..d287512 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_FSL_DMA) += fsl_dma.o obj-$(CONFIG_TI_KSNAV) += keystone_nav.o keystone_nav_cfg.o obj-$(CONFIG_TI_EDMA3) += ti-edma3.o obj-$(CONFIG_DMA_LPC32XX) += lpc32xx_dma.o +obj-$(CONFIG_PL330_DMA) += pl330.o 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 */

Hi Dinh,
On 9 December 2016 at 12:03, Dinh Nguyen dinguyen@kernel.org wrote:
From: Dinh Nguyen dinguyen@opensource.altera.com
Adopted from the Linux kernel PL330 DMA driver.
Signed-off-by: Dinh Nguyen dinguyen@kernel.org
v2: Add Kconfig CONFIG_PL330_DMA entry
arch/arm/include/asm/pl330.h | 105 +++++ drivers/dma/Kconfig | 4 + drivers/dma/Makefile | 1 + drivers/dma/pl330.c | 942 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1052 insertions(+) create mode 100644 arch/arm/include/asm/pl330.h create mode 100644 drivers/dma/pl330.c
Reviewed-by: Simon Glass sjg@chromium.org
nit below.
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 {
Is this used anywhere? Is this describing hardware registers?
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 */
You might consider naming these with their meaning instead of the number/
+};
+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;
Is this mapping a hardware register? The use of bitfields worries me. If it is not mapping hardware, can you please add struct member comments?
/*
* 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/Kconfig b/drivers/dma/Kconfig index 1b92c77..d95f959 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -19,4 +19,8 @@ config TI_EDMA3 This driver support data transfer between memory regions.
+config PL330_DMA
bool "PL330 DMA driver"
help
Enable the ARM PL330 DMA driver.
Please expand a bit - what is the PL330 - where is it found, what can it do?
endmenu # menu "DMA Support" diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 39b78b2..d287512 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_FSL_DMA) += fsl_dma.o obj-$(CONFIG_TI_KSNAV) += keystone_nav.o keystone_nav_cfg.o obj-$(CONFIG_TI_EDMA3) += ti-edma3.o obj-$(CONFIG_DMA_LPC32XX) += lpc32xx_dma.o +obj-$(CONFIG_PL330_DMA) += pl330.o 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)
Can you add a space either side of * ?
+#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
I think it is better to have the mask defined as
#define CR0_NUM_CHANS_MASK (0x7 << CR0_NUM_CHANS_SHIFT)
Then you can mask without shifting, or before shifting. That said, I cannot see where this is used in this patch.
+#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();
Could this be a static function instead? It looks like you could pass in the flags to check and the required value of those flags as two separate parameters.
+/* 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)
Consider using unsigned instead of u8. Non-native arguments do bloat the code.
+{
buf[0] = CMD_DMASEV;
ev &= 0x1f;
Or at least drop this mask.
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;
Does put_unaligned_le32() help here?
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)
Please comment this function, its args and return value.
+{
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)
Can you rename this function? We use 'start' as the start of U-Boot so I think it might be confusing to see this in the symbol list. Although it may be inlined...
+{
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;
Is this intended to wait for a specific number of loops or a specific time? Please add a comment as to how you chose this value.
/* 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
It would be good to use the standard format for these comments, e.g.
@channel_num: desc @return ...
- 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;
I don't think this needs to be assigned here. Perhaps some others are the same.
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");
printf() - we use that instead of puts() these days, except perhaps in tiny SPL areas.
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;
checkpatch / patman should warn about missing {} here?
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;
blank line here (between decls and code)
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);
Join those two lines?
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
Does this means it supports both driver model and not? Is it necessary for this to work without driver model?
+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.7.4
Regards, Simon

In order for SDRAM ECC to work correctly, the SDRAM needs to get zero'd which enables the ECC bit. By using the PL330 DMA to fill the SDRAM with zeroes, the operation is completed in ~1.2 seconds, versus ~14 seconds with a memset.
Signed-off-by: Dinh Nguyen dinguyen@opensource.altera.com --- v2: explanation for size of pl330_buf remove reset manager defines --- arch/arm/mach-socfpga/include/mach/sdram.h | 2 ++ arch/arm/mach-socfpga/spl.c | 8 ++++++++ arch/arm/mach-socfpga/wrap_sdram_config.c | 32 ++++++++++++++++++++++++++++++ configs/socfpga_cyclone5_defconfig | 2 ++ 4 files changed, 44 insertions(+)
diff --git a/arch/arm/mach-socfpga/include/mach/sdram.h b/arch/arm/mach-socfpga/include/mach/sdram.h index b11228f..a70d5c6 100644 --- a/arch/arm/mach-socfpga/include/mach/sdram.h +++ b/arch/arm/mach-socfpga/include/mach/sdram.h @@ -20,6 +20,8 @@ const struct socfpga_sdram_rw_mgr_config *socfpga_get_sdram_rwmgr_config(void); const struct socfpga_sdram_io_config *socfpga_get_sdram_io_config(void); const struct socfpga_sdram_misc_config *socfpga_get_sdram_misc_config(void);
+void sdram_ecc_init(void); + #define SDR_CTRLGRP_ADDRESS (SOCFPGA_SDR_ADDRESS | 0x5000)
struct socfpga_sdr_ctrl { diff --git a/arch/arm/mach-socfpga/spl.c b/arch/arm/mach-socfpga/spl.c index fec4c7a..26688ad 100644 --- a/arch/arm/mach-socfpga/spl.c +++ b/arch/arm/mach-socfpga/spl.c @@ -30,6 +30,8 @@ static struct nic301_registers *nic301_regs = (struct nic301_registers *)SOCFPGA_L3REGS_ADDRESS; static struct socfpga_system_manager *sysmgr_regs = (struct socfpga_system_manager *)SOCFPGA_SYSMGR_ADDRESS; +static const struct socfpga_reset_manager *reset_manager_base = + (void *)SOCFPGA_RSTMGR_ADDRESS;
u32 spl_boot_device(void) { @@ -85,6 +87,9 @@ void board_init_f(ulong dummy) #endif unsigned long sdram_size; unsigned long reg; + u32 rst_mgr_status; + + rst_mgr_status = readl(&reset_manager_base->status);
/* * First C code to run. Clear fake OCRAM ECC first as SBE @@ -179,6 +184,9 @@ void board_init_f(ulong dummy)
socfpga_bridges_reset(1);
+ if ((rst_mgr_status & RSTMGR_WARMRST_MASK) == 0) + sdram_ecc_init(); + /* Configure simple malloc base pointer into RAM. */ gd->malloc_base = CONFIG_SYS_TEXT_BASE + (1024 * 1024); } diff --git a/arch/arm/mach-socfpga/wrap_sdram_config.c b/arch/arm/mach-socfpga/wrap_sdram_config.c index 8fb808a..1509cba 100644 --- a/arch/arm/mach-socfpga/wrap_sdram_config.c +++ b/arch/arm/mach-socfpga/wrap_sdram_config.c @@ -5,8 +5,10 @@ */
#include <common.h> +#include <dma.h> #include <errno.h> #include <asm/arch/sdram.h> +#include <asm/pl330.h>
/* Board-specific header. */ #include <qts/sdram_config.h> @@ -317,3 +319,33 @@ const struct socfpga_sdram_misc_config *socfpga_get_sdram_misc_config(void) { return &misc_config; } + +#if (CONFIG_HPS_SDR_CTRLCFG_CTRLCFG_ECCEN == 1) +/* init the whole SDRAM ECC bit */ +void sdram_ecc_init(void) +{ + struct pl330_transfer_struct pl330; + /* + * 1200 bytes is the buffer size needed to handle the microcode for + * transferring 1GB of zero's. + */ + u8 pl330_buf[1200]; + + pl330.dst_addr = CONFIG_SYS_SDRAM_BASE; + pl330.len = sdram_calculate_size(); + pl330.channel_num = 0; + pl330.buf_size = sizeof(pl330_buf); + pl330.buf = pl330_buf; + + pl330.transfer_type = DMA_SUPPORTS_DEV_TO_MEM; + pl330.reg_base = (void __iomem *)SOCFPGA_DMASECURE_ADDRESS; + + puts("SDRAM: Initializing SDRAM ECC\n"); + + arm_pl330_transfer(&pl330); + + printf("SDRAM: ECC initialized successfully\n"); +} +#else +void sdram_ecc_init(void) {} +#endif diff --git a/configs/socfpga_cyclone5_defconfig b/configs/socfpga_cyclone5_defconfig index 905f766..8a8c71b 100644 --- a/configs/socfpga_cyclone5_defconfig +++ b/configs/socfpga_cyclone5_defconfig @@ -63,3 +63,5 @@ CONFIG_G_DNL_MANUFACTURER="altera" CONFIG_G_DNL_VENDOR_NUM=0x0525 CONFIG_G_DNL_PRODUCT_NUM=0xa4a5 CONFIG_USE_TINY_PRINTF=y +CONFIG_SPL_DMA_SUPPORT=y +CONFIG_PL330_DMA=y

Add additional defines for the reset manager for different reset status bits.
Signed-off-by: Dinh Nguyen dinguyen@kernel.org --- arch/arm/mach-socfpga/include/mach/reset_manager.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+)
diff --git a/arch/arm/mach-socfpga/include/mach/reset_manager.h b/arch/arm/mach-socfpga/include/mach/reset_manager.h index 2f070f2..9750026 100644 --- a/arch/arm/mach-socfpga/include/mach/reset_manager.h +++ b/arch/arm/mach-socfpga/include/mach/reset_manager.h @@ -79,4 +79,25 @@ struct socfpga_reset_manager { /* Create a human-readable reference to SoCFPGA reset. */ #define SOCFPGA_RESET(_name) RSTMGR_##_name
+#define RSTMGR_STAT_L4WD1RST_MASK 0x00008000 +#define RSTMGR_STAT_L4WD0RST_MASK 0x00004000 +#define RSTMGR_STAT_MPUWD1RST_MASK 0x00002000 +#define RSTMGR_STAT_MPUWD0RST_MASK 0x00001000 +#define RSTMGR_STAT_SWWARMRST_MASK 0x00000400 +#define RSTMGR_STAT_FPGAWARMRST_MASK 0x00000200 +#define RSTMGR_STAT_NRSTPINRST_MASK 0x00000100 +#define RSTMGR_STAT_SWCOLDRST_MASK 0x00000010 +#define RSTMGR_STAT_CONFIGIOCOLDRST_MASK 0x00000008 +#define RSTMGR_STAT_FPGACOLDRST_MASK 0x00000004 +#define RSTMGR_STAT_NPORPINRST_MASK 0x00000002 +#define RSTMGR_STAT_PORVOLTRST_MASK 0x00000001 + +#define RSTMGR_WARMRST_MASK (RSTMGR_STAT_SWWARMRST_MASK | \ + RSTMGR_STAT_L4WD0RST_MASK | \ + RSTMGR_STAT_L4WD1RST_MASK | \ + RSTMGR_STAT_MPUWD1RST_MASK | \ + RSTMGR_STAT_MPUWD0RST_MASK | \ + RSTMGR_STAT_FPGAWARMRST_MASK | \ + RSTMGR_STAT_NRSTPINRST_MASK) + #endif /* _RESET_MANAGER_H_ */
participants (2)
-
Dinh Nguyen
-
Simon Glass