[U-Boot] [RFC 1/4] drivers: dma: Add the ARM PL330 DMA driver

Chin Liang See
2016-10-12T16:04:09Z

On Mon, 2016-10-10 at 10:52 -0500, Dinh Nguyen wrote:
> 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
> +

Not sure this is true for other platform. If not, this would need goto
include/configs header files.

[..]

>
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>
> +
>

[..]

> +
> +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;

This state and below would yield difference between channel and
manager.

[..]

>
> +/*
> + * 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;
> + }
> +

Good to check the src_addr too. If unaligned, the microcode would not
be applicable.

> + /* 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;
> + }

We won't need setup based on transfer type as the microcode is setup to
write zero to mem only.

Thanks
Chin Liang

> +
> + 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;
> +}
> +
>

[..]