Add PL330 Dma
authorfang <fang@fang-desktop.(none)>
Tue, 26 Oct 2010 07:24:14 +0000 (15:24 +0800)
committerfang <fang@fang-desktop.(none)>
Tue, 26 Oct 2010 07:24:14 +0000 (15:24 +0800)
arch/arm/common/Kconfig
arch/arm/common/Makefile
arch/arm/common/pl330.c [new file with mode: 0644]
arch/arm/include/asm/hardware/pl330.h [new file with mode: 0644]
arch/arm/mach-rk29/Kconfig
arch/arm/mach-rk29/Makefile
arch/arm/mach-rk29/dma.c [new file with mode: 0644]
arch/arm/mach-rk29/include/mach/dma.h [new file with mode: 0644]
arch/arm/mach-rk29/include/mach/irqs.h
arch/arm/mach-rk29/include/mach/rk29-dma-pl330.h [new file with mode: 0644]
arch/arm/mach-rk29/rk29-pl330.c [new file with mode: 0644]

index 4efbb9df0444c6aaa249090b48e6ff7e56d9e259..5cde166b869e693d5cf7a0edec73ab00c38d400d 100644 (file)
@@ -15,6 +15,9 @@ config ARM_VIC_NR
 config ICST525
        bool
 
+config PL330
+       bool
+
 config ICST307
        bool
 
index 76be7ff2a7ca37a55814071da882c0e374a39475..07e32d4ba5370b327abca3d24d8f6ecb881218d3 100644 (file)
@@ -5,6 +5,7 @@
 obj-$(CONFIG_ARM_GIC)          += gic.o
 obj-$(CONFIG_ARM_VIC)          += vic.o
 obj-$(CONFIG_ICST525)          += icst525.o
+obj-$(CONFIG_PL330)            += pl330.o
 obj-$(CONFIG_ICST307)          += icst307.o
 obj-$(CONFIG_SA1111)           += sa1111.o
 obj-$(CONFIG_PCI_HOST_VIA82C505) += via82c505.o
diff --git a/arch/arm/common/pl330.c b/arch/arm/common/pl330.c
new file mode 100644 (file)
index 0000000..5ebbab6
--- /dev/null
@@ -0,0 +1,1966 @@
+/* linux/arch/arm/common/pl330.c
+ *
+ * Copyright (C) 2010 Samsung Electronics Co Ltd.
+ *     Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+
+#include <asm/hardware/pl330.h>
+
+/* 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 PCELL_ID       0xff0
+
+#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
+
+#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
+
+#define        PART            0x330
+#define DESIGNER       0x41
+#define REVISION       0x0
+#define INTEG_CFG      0x0
+#define PERIPH_ID_VAL  ((PART << 0) | (DESIGNER << 12) \
+                         | (REVISION << 20) | (INTEG_CFG << 24))
+
+#define PCELL_ID_VAL   0xb105f00d
+
+#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_STABLE_STATES (PL330_STATE_STOPPED | PL330_STATE_EXECUTING \
+                               | PL330_STATE_WFE | PL330_STATE_FAULTING)
+
+#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
+
+#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
+
+#define BRST_LEN(ccr)  ((((ccr) >> CC_SRCBRSTLEN_SHFT) & 0xf) + 1)
+#define BRST_SIZE(ccr) (1 << (((ccr) >> CC_SRCBRSTSIZE_SHFT) & 0x7))
+
+#define BYTE_TO_BURST(b, ccr)  ((b) / BRST_SIZE(ccr) / BRST_LEN(ccr))
+#define BURST_TO_BYTE(c, ccr)  ((c) * BRST_SIZE(ccr) * BRST_LEN(ccr))
+
+/*
+ * With 256 bytes, we can do more than 2.5MB and 5MB xfers per req
+ * at 1byte/burst for P<->M and M<->M respectively.
+ * For typical scenario, at 1word/burst, 10MB and 20MB xfers per req
+ * should be enough for P<->M and M<->M respectively.
+ */
+#define MCODE_BUFF_PER_REQ     256
+
+/*
+ * Mark a _pl330_req as free.
+ * We do it by writing DMAEND as the first instruction
+ * because no valid request is going to have DMAEND as
+ * its first instruction to execute.
+ */
+#define MARK_FREE(req) do { \
+                               _emit_END(0, (req)->mc_cpu); \
+                               (req)->mc_len = 0; \
+                       } while (0)
+
+/* If the _pl330_req is available to the client */
+#define IS_FREE(req)   (*((u8 *)((req)->mc_cpu)) == CMD_DMAEND)
+
+/* Use this _only_ to wait on transient states */
+#define UNTIL(t, s)    while (!(_state(t) & (s))) cpu_relax();
+
+#ifdef PL330_DEBUG_MCGEN
+static unsigned cmd_line;
+#define PL330_DBGCMD_DUMP(off, x...)   do { \
+                                               printk("%x:", cmd_line); \
+                                               printk(x); \
+                                               cmd_line += off; \
+                                       } while (0)
+#define PL330_DBGMC_START(addr)                (cmd_line = addr)
+#else
+#define PL330_DBGCMD_DUMP(off, x...)   do {} while (0)
+#define PL330_DBGMC_START(addr)                do {} while (0)
+#endif
+
+struct _xfer_spec {
+       u32 ccr;
+       struct pl330_req *r;
+       struct pl330_xfer *x;
+};
+
+enum dmamov_dst {
+       SAR = 0,
+       CCR,
+       DAR,
+};
+
+enum pl330_dst {
+       SRC = 0,
+       DST,
+};
+
+enum pl330_cond {
+       SINGLE,
+       BURST,
+       ALWAYS,
+};
+
+struct _pl330_req {
+       u32 mc_bus;
+       void *mc_cpu;
+       /* Number of bytes taken to setup MC for the req */
+       u32 mc_len;
+       struct pl330_req *r;
+       /* Hook to attach to DMAC's list of reqs with due callback */
+       struct list_head rqd;
+};
+
+/* ToBeDone for tasklet */
+struct _pl330_tbd {
+       bool reset_dmac;
+       bool reset_mngr;
+       u8 reset_chan;
+};
+
+/* A DMAC Thread */
+struct pl330_thread {
+       u8 id;
+       int ev;
+       /* If the channel is not yet acquired by any client */
+       bool free;
+       /* Parent DMAC */
+       struct pl330_dmac *dmac;
+       /* Only two at a time */
+       struct _pl330_req req[2];
+       /* Index of the last submitted request */
+       unsigned lstenq;
+};
+
+enum pl330_dmac_state {
+       UNINIT,
+       INIT,
+       DYING,
+};
+
+/* A DMAC */
+struct pl330_dmac {
+       spinlock_t              lock;
+       /* Holds list of reqs with due callbacks */
+       struct list_head        req_done;
+       /* Pointer to platform specific stuff */
+       struct pl330_info       *pinfo;
+       /* Maximum possible events/irqs */
+       int                     events[32];
+       /* BUS address of MicroCode buffer */
+       u32                     mcode_bus;
+       /* CPU address of MicroCode buffer */
+       void                    *mcode_cpu;
+       /* List of all Channel threads */
+       struct pl330_thread     *channels;
+       /* Pointer to the MANAGER thread */
+       struct pl330_thread     *manager;
+       /* To handle bad news in interrupt */
+       struct tasklet_struct   tasks;
+       struct _pl330_tbd       dmac_tbd;
+       /* State of DMAC operation */
+       enum pl330_dmac_state   state;
+};
+
+static inline void _callback(struct pl330_req *r, enum pl330_op_err err)
+{
+       if (r && r->xfer_cb)
+               r->xfer_cb(r->token, err);
+}
+
+static inline bool _queue_empty(struct pl330_thread *thrd)
+{
+       return (IS_FREE(&thrd->req[0]) && IS_FREE(&thrd->req[1]))
+               ? true : false;
+}
+
+static inline bool _queue_full(struct pl330_thread *thrd)
+{
+       return (IS_FREE(&thrd->req[0]) || IS_FREE(&thrd->req[1]))
+               ? false : true;
+}
+
+static inline bool is_manager(struct pl330_thread *thrd)
+{
+       struct pl330_dmac *pl330 = thrd->dmac;
+
+       /* MANAGER is indexed at the end */
+       if (thrd->id == pl330->pinfo->pcfg.num_chan)
+               return true;
+       else
+               return false;
+}
+
+/* If manager of the thread is in Non-Secure mode */
+static inline bool _manager_ns(struct pl330_thread *thrd)
+{
+       struct pl330_dmac *pl330 = thrd->dmac;
+
+       return (pl330->pinfo->pcfg.mode & DMAC_MODE_NS) ? true : false;
+}
+
+static inline u32 get_id(struct pl330_info *pi, u32 off)
+{
+       void __iomem *regs = pi->base;
+       u32 id = 0;
+
+       id |= (readb(regs + off + 0x0) << 0);
+       id |= (readb(regs + off + 0x4) << 8);
+       id |= (readb(regs + off + 0x8) << 16);
+       id |= (readb(regs + off + 0xc) << 24);
+
+       return id;
+}
+
+static inline u32 _emit_ADDH(unsigned dry_run, u8 buf[],
+               enum pl330_dst da, u16 val)
+{
+       if (dry_run)
+               return SZ_DMAADDH;
+
+       buf[0] = CMD_DMAADDH;
+       buf[0] |= (da << 1);
+       *((u16 *)&buf[1]) = val;
+
+       PL330_DBGCMD_DUMP(SZ_DMAADDH, "\tDMAADDH %s %u\n",
+               da == 1 ? "DA" : "SA", val);
+
+       return SZ_DMAADDH;
+}
+
+static inline u32 _emit_END(unsigned dry_run, u8 buf[])
+{
+       if (dry_run)
+               return SZ_DMAEND;
+
+       buf[0] = CMD_DMAEND;
+
+       PL330_DBGCMD_DUMP(SZ_DMAEND, "\tDMAEND\n");
+
+       return SZ_DMAEND;
+}
+
+static inline u32 _emit_FLUSHP(unsigned dry_run, u8 buf[], u8 peri)
+{
+       if (dry_run)
+               return SZ_DMAFLUSHP;
+
+       buf[0] = CMD_DMAFLUSHP;
+
+       peri &= 0x1f;
+       peri <<= 3;
+       buf[1] = peri;
+
+       PL330_DBGCMD_DUMP(SZ_DMAFLUSHP, "\tDMAFLUSHP %u\n", peri >> 3);
+
+       return SZ_DMAFLUSHP;
+}
+
+static inline u32 _emit_LD(unsigned dry_run, u8 buf[], enum pl330_cond cond)
+{
+       if (dry_run)
+               return SZ_DMALD;
+
+       buf[0] = CMD_DMALD;
+
+       if (cond == SINGLE)
+               buf[0] |= (0 << 1) | (1 << 0);
+       else if (cond == BURST)
+               buf[0] |= (1 << 1) | (1 << 0);
+
+       PL330_DBGCMD_DUMP(SZ_DMALD, "\tDMALD%c\n",
+               cond == SINGLE ? 'S' : (cond == BURST ? 'B' : 'A'));
+
+       return SZ_DMALD;
+}
+
+static inline u32 _emit_LDP(unsigned dry_run, u8 buf[],
+               enum pl330_cond cond, u8 peri)
+{
+       if (dry_run)
+               return SZ_DMALDP;
+
+       buf[0] = CMD_DMALDP;
+
+       if (cond == BURST)
+               buf[0] |= (1 << 1);
+
+       peri &= 0x1f;
+       peri <<= 3;
+       buf[1] = peri;
+
+       PL330_DBGCMD_DUMP(SZ_DMALDP, "\tDMALDP%c %u\n",
+               cond == SINGLE ? 'S' : 'B', peri >> 3);
+
+       return SZ_DMALDP;
+}
+
+static inline u32 _emit_LP(unsigned dry_run, u8 buf[],
+               unsigned loop, u8 cnt)
+{
+       if (dry_run)
+               return SZ_DMALP;
+
+       buf[0] = CMD_DMALP;
+
+       if (loop)
+               buf[0] |= (1 << 1);
+
+       cnt--; /* DMAC increments by 1 internally */
+       buf[1] = cnt;
+
+       PL330_DBGCMD_DUMP(SZ_DMALP, "\tDMALP_%c %u\n", loop ? '1' : '0', cnt);
+
+       return SZ_DMALP;
+}
+
+struct _arg_LPEND {
+       enum pl330_cond cond;
+       bool forever;
+       unsigned loop;
+       u8 bjump;
+};
+
+static inline u32 _emit_LPEND(unsigned dry_run, 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;
+
+       if (dry_run)
+               return SZ_DMALPEND;
+
+       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;
+
+       PL330_DBGCMD_DUMP(SZ_DMALPEND, "\tDMALP%s%c_%c bjmpto_%x\n",
+                       forever ? "FE" : "END",
+                       cond == SINGLE ? 'S' : (cond == BURST ? 'B' : 'A'),
+                       loop ? '1' : '0',
+                       bjump);
+
+       return SZ_DMALPEND;
+}
+
+static inline u32 _emit_KILL(unsigned dry_run, u8 buf[])
+{
+       if (dry_run)
+               return SZ_DMAKILL;
+
+       buf[0] = CMD_DMAKILL;
+
+       return SZ_DMAKILL;
+}
+
+static inline u32 _emit_MOV(unsigned dry_run, u8 buf[],
+               enum dmamov_dst dst, u32 val)
+{
+       if (dry_run)
+               return SZ_DMAMOV;
+
+       buf[0] = CMD_DMAMOV;
+       buf[1] = dst;
+       *((u32 *)&buf[2]) = val;
+
+       PL330_DBGCMD_DUMP(SZ_DMAMOV, "\tDMAMOV %s 0x%x\n",
+               dst == SAR ? "SAR" : (dst == DAR ? "DAR" : "CCR"), val);
+
+       return SZ_DMAMOV;
+}
+
+static inline u32 _emit_NOP(unsigned dry_run, u8 buf[])
+{
+       if (dry_run)
+               return SZ_DMANOP;
+
+       buf[0] = CMD_DMANOP;
+
+       PL330_DBGCMD_DUMP(SZ_DMANOP, "\tDMANOP\n");
+
+       return SZ_DMANOP;
+}
+
+static inline u32 _emit_RMB(unsigned dry_run, u8 buf[])
+{
+       if (dry_run)
+               return SZ_DMARMB;
+
+       buf[0] = CMD_DMARMB;
+
+       PL330_DBGCMD_DUMP(SZ_DMARMB, "\tDMARMB\n");
+
+       return SZ_DMARMB;
+}
+
+static inline u32 _emit_SEV(unsigned dry_run, u8 buf[], u8 ev)
+{
+       if (dry_run)
+               return SZ_DMASEV;
+
+       buf[0] = CMD_DMASEV;
+
+       ev &= 0x1f;
+       ev <<= 3;
+       buf[1] = ev;
+
+       PL330_DBGCMD_DUMP(SZ_DMASEV, "\tDMASEV %u\n", ev >> 3);
+
+       return SZ_DMASEV;
+}
+
+static inline u32 _emit_ST(unsigned dry_run, u8 buf[], enum pl330_cond cond)
+{
+       if (dry_run)
+               return SZ_DMAST;
+
+       buf[0] = CMD_DMAST;
+
+       if (cond == SINGLE)
+               buf[0] |= (0 << 1) | (1 << 0);
+       else if (cond == BURST)
+               buf[0] |= (1 << 1) | (1 << 0);
+
+       PL330_DBGCMD_DUMP(SZ_DMAST, "\tDMAST%c\n",
+               cond == SINGLE ? 'S' : (cond == BURST ? 'B' : 'A'));
+
+       return SZ_DMAST;
+}
+
+static inline u32 _emit_STP(unsigned dry_run, u8 buf[],
+               enum pl330_cond cond, u8 peri)
+{
+       if (dry_run)
+               return SZ_DMASTP;
+
+       buf[0] = CMD_DMASTP;
+
+       if (cond == BURST)
+               buf[0] |= (1 << 1);
+
+       peri &= 0x1f;
+       peri <<= 3;
+       buf[1] = peri;
+
+       PL330_DBGCMD_DUMP(SZ_DMASTP, "\tDMASTP%c %u\n",
+               cond == SINGLE ? 'S' : 'B', peri >> 3);
+
+       return SZ_DMASTP;
+}
+
+static inline u32 _emit_STZ(unsigned dry_run, u8 buf[])
+{
+       if (dry_run)
+               return SZ_DMASTZ;
+
+       buf[0] = CMD_DMASTZ;
+
+       PL330_DBGCMD_DUMP(SZ_DMASTZ, "\tDMASTZ\n");
+
+       return SZ_DMASTZ;
+}
+
+static inline u32 _emit_WFE(unsigned dry_run, u8 buf[], u8 ev,
+               unsigned invalidate)
+{
+       if (dry_run)
+               return SZ_DMAWFE;
+
+       buf[0] = CMD_DMAWFE;
+
+       ev &= 0x1f;
+       ev <<= 3;
+       buf[1] = ev;
+
+       if (invalidate)
+               buf[1] |= (1 << 1);
+
+       PL330_DBGCMD_DUMP(SZ_DMAWFE, "\tDMAWFE %u%s\n",
+               ev >> 3, invalidate ? ", I" : "");
+
+       return SZ_DMAWFE;
+}
+
+static inline u32 _emit_WFP(unsigned dry_run, u8 buf[],
+               enum pl330_cond cond, u8 peri)
+{
+       if (dry_run)
+               return SZ_DMAWFP;
+
+       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;
+
+       PL330_DBGCMD_DUMP(SZ_DMAWFP, "\tDMAWFP%c %u\n",
+               cond == SINGLE ? 'S' : (cond == BURST ? 'B' : 'P'), peri >> 3);
+
+       return SZ_DMAWFP;
+}
+
+static inline u32 _emit_WMB(unsigned dry_run, u8 buf[])
+{
+       if (dry_run)
+               return SZ_DMAWMB;
+
+       buf[0] = CMD_DMAWMB;
+
+       PL330_DBGCMD_DUMP(SZ_DMAWMB, "\tDMAWMB\n");
+
+       return SZ_DMAWMB;
+}
+
+struct _arg_GO {
+       u8 chan;
+       u32 addr;
+       unsigned ns;
+};
+
+static inline u32 _emit_GO(unsigned dry_run, u8 buf[],
+               const struct _arg_GO *arg)
+{
+       u8 chan = arg->chan;
+       u32 addr = arg->addr;
+       unsigned ns = arg->ns;
+
+       if (dry_run)
+               return SZ_DMAGO;
+
+       buf[0] = CMD_DMAGO;
+       buf[0] |= (ns << 1);
+
+       buf[1] = chan & 0x7;
+
+       *((u32 *)&buf[2]) = addr;
+
+       return SZ_DMAGO;
+}
+
+#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
+
+/* Returns Time-Out */
+static bool _until_dmac_idle(struct pl330_thread *thrd)
+{
+       void __iomem *regs = thrd->dmac->pinfo->base;
+       unsigned long loops = msecs_to_loops(5);
+
+       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_thread *thrd,
+               u8 insn[], bool as_manager)
+{
+       void __iomem *regs = thrd->dmac->pinfo->base;
+       u32 val;
+
+       val = (insn[0] << 16) | (insn[1] << 24);
+       if (!as_manager) {
+               val |= (1 << 0);
+               val |= (thrd->id << 8); /* Channel Number */
+       }
+       writel(val, regs + DBGINST0);
+
+       val = *((u32 *)&insn[2]);
+       writel(val, regs + DBGINST1);
+
+       /* If timed out due to halted state-machine */
+       if (_until_dmac_idle(thrd)) {
+               dev_err(thrd->dmac->pinfo->dev, "DMAC halted!\n");
+               return;
+       }
+
+       /* Get going */
+       writel(0, regs + DBGCMD);
+}
+
+static inline u32 _state(struct pl330_thread *thrd)
+{
+       void __iomem *regs = thrd->dmac->pinfo->base;
+       u32 val;
+
+       if (is_manager(thrd))
+               val = readl(regs + DS) & 0xf;
+       else
+               val = readl(regs + CS(thrd->id)) & 0xf;
+
+       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:
+               if (is_manager(thrd))
+                       return PL330_STATE_INVALID;
+               else
+                       return PL330_STATE_ATBARRIER;
+       case DS_ST_QBUSY:
+               if (is_manager(thrd))
+                       return PL330_STATE_INVALID;
+               else
+                       return PL330_STATE_QUEUEBUSY;
+       case DS_ST_WFP:
+               if (is_manager(thrd))
+                       return PL330_STATE_INVALID;
+               else
+                       return PL330_STATE_WFP;
+       case DS_ST_KILL:
+               if (is_manager(thrd))
+                       return PL330_STATE_INVALID;
+               else
+                       return PL330_STATE_KILLING;
+       case DS_ST_CMPLT:
+               if (is_manager(thrd))
+                       return PL330_STATE_INVALID;
+               else
+                       return PL330_STATE_COMPLETING;
+       case DS_ST_FLTCMP:
+               if (is_manager(thrd))
+                       return PL330_STATE_INVALID;
+               else
+                       return PL330_STATE_FAULT_COMPLETING;
+       default:
+               return PL330_STATE_INVALID;
+       }
+}
+
+/* If the request 'req' of thread 'thrd' is currently active */
+static inline bool _req_active(struct pl330_thread *thrd,
+               struct _pl330_req *req)
+{
+       void __iomem *regs = thrd->dmac->pinfo->base;
+       u32 buf = req->mc_bus, pc = readl(regs + CPC(thrd->id));
+
+       if (IS_FREE(req))
+               return false;
+
+       return (pc >= buf && pc <= buf + req->mc_len) ? true : false;
+}
+
+/* Returns 0 if the thread is inactive, ID of active req + 1 otherwise */
+static inline unsigned _thrd_active(struct pl330_thread *thrd)
+{
+       if (_req_active(thrd, &thrd->req[0]))
+               return 1; /* First req active */
+
+       if (_req_active(thrd, &thrd->req[1]))
+               return 2; /* Second req active */
+
+       return 0;
+}
+
+static void _stop(struct pl330_thread *thrd)
+{
+       void __iomem *regs = thrd->dmac->pinfo->base;
+       u8 insn[6] = {0, 0, 0, 0, 0, 0};
+
+       if (_state(thrd) == PL330_STATE_FAULT_COMPLETING)
+               UNTIL(thrd, PL330_STATE_FAULTING | PL330_STATE_KILLING);
+
+       /* Return if nothing needs to be done */
+       if (_state(thrd) == PL330_STATE_COMPLETING
+                 || _state(thrd) == PL330_STATE_KILLING
+                 || _state(thrd) == PL330_STATE_STOPPED)
+               return;
+
+       _emit_KILL(0, insn);
+
+       /* Stop generating interrupts for SEV */
+       writel(readl(regs + INTEN) & ~(1 << thrd->ev), regs + INTEN);
+
+       _execute_DBGINSN(thrd, insn, is_manager(thrd));
+}
+
+/* Start doing req 'idx' of thread 'thrd' */
+static bool _trigger(struct pl330_thread *thrd)
+{
+       void __iomem *regs = thrd->dmac->pinfo->base;
+       struct _pl330_req *req;
+       struct pl330_req *r;
+       struct _arg_GO go;
+       unsigned ns;
+       u8 insn[6] = {0, 0, 0, 0, 0, 0};
+
+       /* Return if already ACTIVE */
+       if (_state(thrd) != PL330_STATE_STOPPED)
+               return true;
+
+       if (!IS_FREE(&thrd->req[1 - thrd->lstenq]))
+               req = &thrd->req[1 - thrd->lstenq];
+       else if (!IS_FREE(&thrd->req[thrd->lstenq]))
+               req = &thrd->req[thrd->lstenq];
+       else
+               req = NULL;
+
+       /* Return if no request */
+       if (!req || !req->r)
+               return true;
+
+       r = req->r;
+
+       if (r->cfg)
+               ns = r->cfg->nonsecure ? 1 : 0;
+       else if (readl(regs + CS(thrd->id)) & CS_CNS)
+               ns = 1;
+       else
+               ns = 0;
+
+       /* See 'Abort Sources' point-4 at Page 2-25 */
+       if (_manager_ns(thrd) && !ns)
+               dev_info(thrd->dmac->pinfo->dev, "%s:%d Recipe for ABORT!\n",
+                       __func__, __LINE__);
+
+       go.chan = thrd->id;
+       go.addr = req->mc_bus;
+       go.ns = ns;
+       _emit_GO(0, insn, &go);
+
+       /* Set to generate interrupts for SEV */
+       writel(readl(regs + INTEN) | (1 << thrd->ev), regs + INTEN);
+
+       /* Only manager can execute GO */
+       _execute_DBGINSN(thrd, insn, true);
+
+       return true;
+}
+
+static bool _start(struct pl330_thread *thrd)
+{
+       switch (_state(thrd)) {
+       case PL330_STATE_FAULT_COMPLETING:
+               UNTIL(thrd, PL330_STATE_FAULTING | PL330_STATE_KILLING);
+
+               if (_state(thrd) == PL330_STATE_KILLING)
+                       UNTIL(thrd, PL330_STATE_STOPPED)
+
+       case PL330_STATE_FAULTING:
+               _stop(thrd);
+
+       case PL330_STATE_KILLING:
+       case PL330_STATE_COMPLETING:
+               UNTIL(thrd, PL330_STATE_STOPPED)
+
+       case PL330_STATE_STOPPED:
+               return _trigger(thrd);
+
+       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;
+       }
+}
+
+static inline int _ldst_memtomem(unsigned dry_run, u8 buf[],
+               const struct _xfer_spec *pxs, int cyc)
+{
+       int off = 0;
+
+       while (cyc--) {
+               off += _emit_LD(dry_run, &buf[off], ALWAYS);
+               off += _emit_RMB(dry_run, &buf[off]);
+               off += _emit_ST(dry_run, &buf[off], ALWAYS);
+               off += _emit_WMB(dry_run, &buf[off]);
+       }
+
+       return off;
+}
+
+static inline int _ldst_devtomem(unsigned dry_run, u8 buf[],
+               const struct _xfer_spec *pxs, int cyc)
+{
+       int off = 0;
+
+       while (cyc--) {
+               off += _emit_WFP(dry_run, &buf[off], SINGLE, pxs->r->peri);
+               off += _emit_LDP(dry_run, &buf[off], SINGLE, pxs->r->peri);
+               off += _emit_ST(dry_run, &buf[off], ALWAYS);
+               off += _emit_FLUSHP(dry_run, &buf[off], pxs->r->peri);
+       }
+
+       return off;
+}
+
+static inline int _ldst_memtodev(unsigned dry_run, u8 buf[],
+               const struct _xfer_spec *pxs, int cyc)
+{
+       int off = 0;
+
+       while (cyc--) {
+               off += _emit_WFP(dry_run, &buf[off], SINGLE, pxs->r->peri);
+               off += _emit_LD(dry_run, &buf[off], ALWAYS);
+               off += _emit_STP(dry_run, &buf[off], SINGLE, pxs->r->peri);
+               off += _emit_FLUSHP(dry_run, &buf[off], pxs->r->peri);
+       }
+
+       return off;
+}
+
+static int _bursts(unsigned dry_run, u8 buf[],
+               const struct _xfer_spec *pxs, int cyc)
+{
+       int off = 0;
+
+       switch (pxs->r->rqtype) {
+       case MEMTODEV:
+               off += _ldst_memtodev(dry_run, &buf[off], pxs, cyc);
+               break;
+       case DEVTOMEM:
+               off += _ldst_devtomem(dry_run, &buf[off], pxs, cyc);
+               break;
+       case MEMTOMEM:
+               off += _ldst_memtomem(dry_run, &buf[off], pxs, cyc);
+               break;
+       default:
+               off += 0x40000000; /* Scare off the Client */
+               break;
+       }
+
+       return off;
+}
+
+/* Returns bytes consumed and updates bursts */
+static inline int _loop(unsigned dry_run, u8 buf[],
+               unsigned long *bursts, const struct _xfer_spec *pxs)
+{
+       int cyc, cycmax, szlp, szlpend, szbrst, off;
+       unsigned lcnt0, lcnt1, ljmp0, ljmp1;
+       struct _arg_LPEND lpend;
+
+       /* Max iterations possibile in DMALP is 256 */
+       if (*bursts >= 256*256) {
+               lcnt1 = 256;
+               lcnt0 = 256;
+               cyc = *bursts / lcnt1 / lcnt0;
+       } else if (*bursts > 256) {
+               lcnt1 = 256;
+               lcnt0 = *bursts / lcnt1;
+               cyc = 1;
+       } else {
+               lcnt1 = *bursts;
+               lcnt0 = 0;
+               cyc = 1;
+       }
+
+       szlp = _emit_LP(1, buf, 0, 0);
+       szbrst = _bursts(1, buf, pxs, 1);
+
+       lpend.cond = ALWAYS;
+       lpend.forever = false;
+       lpend.loop = 0;
+       lpend.bjump = 0;
+       szlpend = _emit_LPEND(1, buf, &lpend);
+
+       if (lcnt0) {
+               szlp *= 2;
+               szlpend *= 2;
+       }
+
+       /*
+        * Max bursts that we can unroll due to limit on the
+        * size of backward jump that can be encoded in DMALPEND
+        * which is 8-bits and hence 255
+        */
+       cycmax = (255 - (szlp + szlpend)) / szbrst;
+
+       cyc = (cycmax < cyc) ? cycmax : cyc;
+
+       off = 0;
+
+       if (lcnt0) {
+               off += _emit_LP(dry_run, &buf[off], 0, lcnt0);
+               ljmp0 = off;
+       }
+
+       off += _emit_LP(dry_run, &buf[off], 1, lcnt1);
+       ljmp1 = off;
+
+       off += _bursts(dry_run, &buf[off], pxs, cyc);
+
+       lpend.cond = ALWAYS;
+       lpend.forever = false;
+       lpend.loop = 1;
+       lpend.bjump = off - ljmp1;
+       off += _emit_LPEND(dry_run, &buf[off], &lpend);
+
+       if (lcnt0) {
+               lpend.cond = ALWAYS;
+               lpend.forever = false;
+               lpend.loop = 0;
+               lpend.bjump = off - ljmp0;
+               off += _emit_LPEND(dry_run, &buf[off], &lpend);
+       }
+
+       *bursts = lcnt1 * cyc;
+       if (lcnt0)
+               *bursts *= lcnt0;
+
+       return off;
+}
+
+static inline int _setup_loops(unsigned dry_run, u8 buf[],
+               const struct _xfer_spec *pxs)
+{
+       struct pl330_xfer *x = pxs->x;
+       u32 ccr = pxs->ccr;
+       unsigned long c, bursts = BYTE_TO_BURST(x->bytes, ccr);
+       int off = 0;
+
+       while (bursts) {
+               c = bursts;
+               off += _loop(dry_run, &buf[off], &c, pxs);
+               bursts -= c;
+       }
+
+       return off;
+}
+
+static inline int _setup_xfer(unsigned dry_run, u8 buf[],
+               const struct _xfer_spec *pxs)
+{
+       struct pl330_xfer *x = pxs->x;
+       int off = 0;
+
+       /* DMAMOV SAR, x->src_addr */
+       off += _emit_MOV(dry_run, &buf[off], SAR, x->src_addr);
+       /* DMAMOV DAR, x->dst_addr */
+       off += _emit_MOV(dry_run, &buf[off], DAR, x->dst_addr);
+
+       /* Setup Loop(s) */
+       off += _setup_loops(dry_run, &buf[off], pxs);
+
+       return off;
+}
+
+/*
+ * A req is a sequence of one or more xfer units.
+ * Returns the number of bytes taken to setup the MC for the req.
+ */
+static int _setup_req(unsigned dry_run, struct pl330_thread *thrd,
+               unsigned index, struct _xfer_spec *pxs)
+{
+       struct _pl330_req *req = &thrd->req[index];
+       struct pl330_xfer *x;
+       u8 *buf = req->mc_cpu;
+       int off = 0;
+
+       PL330_DBGMC_START(req->mc_bus);
+
+       /* DMAMOV CCR, ccr */
+       off += _emit_MOV(dry_run, &buf[off], CCR, pxs->ccr);
+
+       x = pxs->r->x;
+       do {
+               /* Error if xfer length is not aligned at burst size */
+               if (x->bytes % (BRST_SIZE(pxs->ccr) * BRST_LEN(pxs->ccr)))
+                       return -EINVAL;
+
+               pxs->x = x;
+               off += _setup_xfer(dry_run, &buf[off], pxs);
+
+               x = x->next;
+       } while (x);
+
+       /* DMASEV peripheral/event */
+       off += _emit_SEV(dry_run, &buf[off], thrd->ev);
+       /* DMAEND */
+       off += _emit_END(dry_run, &buf[off]);
+
+       return off;
+}
+
+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->dcctl << CC_SRCCCTRL_SHFT);
+       ccr |= (rqc->scctl << CC_DSTCCTRL_SHFT);
+
+       ccr |= (rqc->swap << CC_SWAP_SHFT);
+
+       return ccr;
+}
+
+static inline bool _is_valid(u32 ccr)
+{
+       enum pl330_dstcachectrl dcctl;
+       enum pl330_srccachectrl scctl;
+
+       dcctl = (ccr >> CC_DSTCCTRL_SHFT) & CC_DRCCCTRL_MASK;
+       scctl = (ccr >> CC_SRCCCTRL_SHFT) & CC_SRCCCTRL_MASK;
+
+       if (dcctl == DINVALID1 || dcctl == DINVALID2
+                       || scctl == SINVALID1 || scctl == SINVALID2)
+               return false;
+       else
+               return true;
+}
+
+/*
+ * Submit a list of xfers after which the client wants notification.
+ * Client is not notified after each xfer unit, just once after all
+ * xfer units are done or some error occurs.
+ */
+int pl330_submit_req(void *ch_id, struct pl330_req *r)
+{
+       struct pl330_thread *thrd = ch_id;
+       struct pl330_dmac *pl330;
+       struct pl330_info *pi;
+       struct _xfer_spec xs;
+       unsigned long flags;
+       void __iomem *regs;
+       unsigned idx;
+       u32 ccr;
+       int ret = 0;
+
+       /* No Req or Unacquired Channel or DMAC */
+       if (!r || !thrd || thrd->free)
+               return -EINVAL;
+
+       pl330 = thrd->dmac;
+       pi = pl330->pinfo;
+       regs = pi->base;
+
+       if (pl330->state == DYING
+               || pl330->dmac_tbd.reset_chan & (1 << thrd->id)) {
+               dev_info(thrd->dmac->pinfo->dev, "%s:%d\n",
+                       __func__, __LINE__);
+               return -EAGAIN;
+       }
+
+       /* If request for non-existing peripheral */
+       if (r->rqtype != MEMTOMEM && r->peri >= pi->pcfg.num_peri) {
+               dev_info(thrd->dmac->pinfo->dev,
+                               "%s:%d Invalid peripheral(%u)!\n",
+                               __func__, __LINE__, r->peri);
+               return -EINVAL;
+       }
+
+       spin_lock_irqsave(&pl330->lock, flags);
+
+       if (_queue_full(thrd)) {
+               ret = -EAGAIN;
+               goto xfer_exit;
+       }
+
+       /* Prefer Secure Channel */
+       if (!_manager_ns(thrd))
+               r->cfg->nonsecure = 0;
+       else
+               r->cfg->nonsecure = 1;
+
+       /* Use last settings, if not provided */
+       if (r->cfg)
+               ccr = _prepare_ccr(r->cfg);
+       else
+               ccr = readl(regs + CC(thrd->id));
+
+       /* If this req doesn't have valid xfer settings */
+       if (!_is_valid(ccr)) {
+               ret = -EINVAL;
+               dev_info(thrd->dmac->pinfo->dev, "%s:%d Invalid CCR(%x)!\n",
+                       __func__, __LINE__, ccr);
+               goto xfer_exit;
+       }
+
+       idx = IS_FREE(&thrd->req[0]) ? 0 : 1;
+
+       xs.ccr = ccr;
+       xs.r = r;
+
+       /* First dry run to check if req is acceptable */
+       ret = _setup_req(1, thrd, idx, &xs);
+       if (ret < 0)
+               goto xfer_exit;
+
+       if (ret > pi->mcbufsz / 2) {
+               dev_info(thrd->dmac->pinfo->dev,
+                       "%s:%d Trying increasing mcbufsz\n",
+                               __func__, __LINE__);
+               ret = -ENOMEM;
+               goto xfer_exit;
+       }
+
+       /* Hook the request */
+       thrd->lstenq = idx;
+       thrd->req[idx].mc_len = _setup_req(0, thrd, idx, &xs);
+       thrd->req[idx].r = r;
+
+       ret = 0;
+
+xfer_exit:
+       spin_unlock_irqrestore(&pl330->lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(pl330_submit_req);
+
+static void pl330_dotask(unsigned long data)
+{
+       struct pl330_dmac *pl330 = (struct pl330_dmac *) data;
+       struct pl330_info *pi = pl330->pinfo;
+       unsigned long flags;
+       int i;
+
+       spin_lock_irqsave(&pl330->lock, flags);
+
+       /* The DMAC itself gone nuts */
+       if (pl330->dmac_tbd.reset_dmac) {
+               pl330->state = DYING;
+               /* Reset the manager too */
+               pl330->dmac_tbd.reset_mngr = true;
+               /* Clear the reset flag */
+               pl330->dmac_tbd.reset_dmac = false;
+       }
+
+       if (pl330->dmac_tbd.reset_mngr) {
+               _stop(pl330->manager);
+               /* Reset all channels */
+               pl330->dmac_tbd.reset_chan = (1 << pi->pcfg.num_chan) - 1;
+               /* Clear the reset flag */
+               pl330->dmac_tbd.reset_mngr = false;
+       }
+
+       for (i = 0; i < pi->pcfg.num_chan; i++) {
+
+               if (pl330->dmac_tbd.reset_chan & (1 << i)) {
+                       struct pl330_thread *thrd = &pl330->channels[i];
+                       void __iomem *regs = pi->base;
+                       enum pl330_op_err err;
+
+                       _stop(thrd);
+
+                       if (readl(regs + FSC) & (1 << thrd->id))
+                               err = PL330_ERR_FAIL;
+                       else
+                               err = PL330_ERR_ABORT;
+
+                       spin_unlock_irqrestore(&pl330->lock, flags);
+
+                       _callback(thrd->req[1 - thrd->lstenq].r, err);
+                       _callback(thrd->req[thrd->lstenq].r, err);
+
+                       spin_lock_irqsave(&pl330->lock, flags);
+
+                       thrd->req[0].r = NULL;
+                       thrd->req[1].r = NULL;
+                       MARK_FREE(&thrd->req[0]);
+                       MARK_FREE(&thrd->req[1]);
+
+                       /* Clear the reset flag */
+                       pl330->dmac_tbd.reset_chan &= ~(1 << i);
+               }
+       }
+
+       spin_unlock_irqrestore(&pl330->lock, flags);
+
+       return;
+}
+
+/* Returns 1 if state was updated, 0 otherwise */
+int pl330_update(const struct pl330_info *pi)
+{
+       struct _pl330_req *rqdone;
+       struct pl330_dmac *pl330;
+       unsigned long flags;
+       void __iomem *regs;
+       u32 val;
+       int id, ev, ret = 0;
+
+       if (!pi || !pi->pl330_data)
+               return 0;
+
+       regs = pi->base;
+       pl330 = pi->pl330_data;
+
+       spin_lock_irqsave(&pl330->lock, flags);
+
+       val = readl(regs + FSM) & 0x1;
+       if (val)
+               pl330->dmac_tbd.reset_mngr = true;
+       else
+               pl330->dmac_tbd.reset_mngr = false;
+
+       val = readl(regs + FSC) & ((1 << pi->pcfg.num_chan) - 1);
+       pl330->dmac_tbd.reset_chan |= val;
+       if (val) {
+               int i = 0;
+               while (i < pi->pcfg.num_chan) {
+                       if (val & (1 << i)) {
+                               dev_info(pi->dev,
+                                       "Reset Channel-%d\t CS-%x FTC-%x\n",
+                                               i, readl(regs + CS(i)),
+                                               readl(regs + FTC(i)));
+                               _stop(&pl330->channels[i]);
+                       }
+                       i++;
+               }
+       }
+
+       /* Check which event happened i.e, thread notified */
+       val = readl(regs + ES);
+       if (pi->pcfg.num_events < 32
+                       && val & ~((1 << pi->pcfg.num_events) - 1)) {
+               pl330->dmac_tbd.reset_dmac = true;
+               dev_err(pi->dev, "%s:%d Unexpected!\n", __func__, __LINE__);
+               ret = 1;
+               goto updt_exit;
+       }
+
+       for (ev = 0; ev < pi->pcfg.num_events; ev++) {
+               if (val & (1 << ev)) { /* Event occured */
+                       struct pl330_thread *thrd;
+                       u32 inten = readl(regs + INTEN);
+                       int active;
+
+                       /* Clear the event */
+                       if (inten & (1 << ev))
+                               writel(1 << ev, regs + INTCLR);
+
+                       ret = 1;
+
+                       id = pl330->events[ev];
+
+                       thrd = &pl330->channels[id];
+
+                       active = _thrd_active(thrd);
+                       if (!active) /* Aborted */
+                               continue;
+
+                       active -= 1;
+
+                       rqdone = &thrd->req[active];
+                       MARK_FREE(rqdone);
+
+                       /* Get going again ASAP */
+                       _start(thrd);
+
+                       /* For now, just make a list of callbacks to be done */
+                       list_add_tail(&rqdone->rqd, &pl330->req_done);
+               }
+       }
+
+       /* Now that we are in no hurry, do the callbacks */
+       while (!list_empty(&pl330->req_done)) {
+               rqdone = container_of(pl330->req_done.next,
+                                       struct _pl330_req, rqd);
+
+               list_del_init(&rqdone->rqd);
+
+               spin_unlock_irqrestore(&pl330->lock, flags);
+               _callback(rqdone->r, PL330_ERR_NONE);
+               spin_lock_irqsave(&pl330->lock, flags);
+       }
+
+updt_exit:
+       spin_unlock_irqrestore(&pl330->lock, flags);
+
+       if (pl330->dmac_tbd.reset_dmac
+                       || pl330->dmac_tbd.reset_mngr
+                       || pl330->dmac_tbd.reset_chan) {
+               ret = 1;
+               tasklet_schedule(&pl330->tasks);
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL(pl330_update);
+
+int pl330_chan_ctrl(void *ch_id, enum pl330_chan_op op)
+{
+       struct pl330_thread *thrd = ch_id;
+       struct pl330_dmac *pl330;
+       unsigned long flags;
+       int ret = 0, active;
+
+       if (!thrd || thrd->free || thrd->dmac->state == DYING)
+               return -EINVAL;
+
+       pl330 = thrd->dmac;
+
+       spin_lock_irqsave(&pl330->lock, flags);
+
+       switch (op) {
+       case PL330_OP_FLUSH:
+               /* Make sure the channel is stopped */
+               _stop(thrd);
+
+               thrd->req[0].r = NULL;
+               thrd->req[1].r = NULL;
+               MARK_FREE(&thrd->req[0]);
+               MARK_FREE(&thrd->req[1]);
+               break;
+
+       case PL330_OP_ABORT:
+               active = _thrd_active(thrd);
+
+               /* Make sure the channel is stopped */
+               _stop(thrd);
+
+               /* ABORT is only for the active req */
+               if (!active)
+                       break;
+
+               active--;
+
+               thrd->req[active].r = NULL;
+               MARK_FREE(&thrd->req[active]);
+
+               /* Start the next */
+       case PL330_OP_START:
+               if (!_start(thrd))
+                       ret = -EIO;
+               break;
+
+       default:
+               ret = -EINVAL;
+       }
+
+       spin_unlock_irqrestore(&pl330->lock, flags);
+       return ret;
+}
+EXPORT_SYMBOL(pl330_chan_ctrl);
+
+int pl330_chan_status(void *ch_id, struct pl330_chanstatus *pstatus)
+{
+       struct pl330_thread *thrd = ch_id;
+       struct pl330_dmac *pl330;
+       struct pl330_info *pi;
+       void __iomem *regs;
+       int active;
+       u32 val;
+
+       if (!pstatus || !thrd || thrd->free)
+               return -EINVAL;
+
+       pl330 = thrd->dmac;
+       pi = pl330->pinfo;
+       regs = pi->base;
+
+       /* The client should remove the DMAC and add again */
+       if (pl330->state == DYING)
+               pstatus->dmac_halted = true;
+       else
+               pstatus->dmac_halted = false;
+
+       val = readl(regs + FSC);
+       if (val & (1 << thrd->id))
+               pstatus->faulting = true;
+       else
+               pstatus->faulting = false;
+
+       active = _thrd_active(thrd);
+
+       if (!active) {
+               /* Indicate that the thread is not running */
+               pstatus->top_req = NULL;
+               pstatus->wait_req = NULL;
+       } else {
+               active--;
+               pstatus->top_req = thrd->req[active].r;
+               pstatus->wait_req = !IS_FREE(&thrd->req[1 - active])
+                                       ? thrd->req[1 - active].r : NULL;
+       }
+
+       pstatus->src_addr = readl(regs + SA(thrd->id));
+       pstatus->dst_addr = readl(regs + DA(thrd->id));
+
+       return 0;
+}
+EXPORT_SYMBOL(pl330_chan_status);
+
+/* Reserve an event */
+static inline int _alloc_event(struct pl330_thread *thrd)
+{
+       struct pl330_dmac *pl330 = thrd->dmac;
+       struct pl330_info *pi = pl330->pinfo;
+       int ev;
+
+       for (ev = 0; ev < pi->pcfg.num_events; ev++)
+               if (pl330->events[ev] == -1) {
+                       pl330->events[ev] = thrd->id;
+                       return ev;
+               }
+
+       return -1;
+}
+
+/* Upon success, returns IdentityToken for the
+ * allocated channel, NULL otherwise.
+ */
+void *pl330_request_channel(const struct pl330_info *pi)
+{
+       struct pl330_thread *thrd = NULL;
+       struct pl330_dmac *pl330;
+       unsigned long flags;
+       int chans, i;
+
+       if (!pi || !pi->pl330_data)
+               return NULL;
+
+       pl330 = pi->pl330_data;
+
+       if (pl330->state == DYING)
+               return NULL;
+
+       chans = pi->pcfg.num_chan;
+
+       spin_lock_irqsave(&pl330->lock, flags);
+
+       for (i = 0; i < chans; i++) {
+               thrd = &pl330->channels[i];
+               if (thrd->free) {
+                       thrd->ev = _alloc_event(thrd);
+                       if (thrd->ev >= 0) {
+                               thrd->free = false;
+                               thrd->lstenq = 1;
+                               thrd->req[0].r = NULL;
+                               MARK_FREE(&thrd->req[0]);
+                               thrd->req[1].r = NULL;
+                               MARK_FREE(&thrd->req[1]);
+                               break;
+                       }
+               }
+               thrd = NULL;
+       }
+
+       spin_unlock_irqrestore(&pl330->lock, flags);
+
+       return thrd;
+}
+EXPORT_SYMBOL(pl330_request_channel);
+
+/* Release an event */
+static inline void _free_event(struct pl330_thread *thrd, int ev)
+{
+       struct pl330_dmac *pl330 = thrd->dmac;
+       struct pl330_info *pi = pl330->pinfo;
+
+       /* If the event is valid and was held by the thread */
+       if (ev >= 0 && ev < pi->pcfg.num_events
+                       && pl330->events[ev] == thrd->id)
+               pl330->events[ev] = -1;
+}
+
+void pl330_release_channel(void *ch_id)
+{
+       struct pl330_thread *thrd = ch_id;
+       struct pl330_dmac *pl330;
+       unsigned long flags;
+
+       if (!thrd || thrd->free)
+               return;
+
+       _stop(thrd);
+
+       _callback(thrd->req[1 - thrd->lstenq].r, PL330_ERR_ABORT);
+       _callback(thrd->req[thrd->lstenq].r, PL330_ERR_ABORT);
+
+       pl330 = thrd->dmac;
+
+       spin_lock_irqsave(&pl330->lock, flags);
+       _free_event(thrd, thrd->ev);
+       thrd->free = true;
+       spin_unlock_irqrestore(&pl330->lock, flags);
+}
+EXPORT_SYMBOL(pl330_release_channel);
+
+/* Initialize the structure for PL330 configuration, that can be used
+ * by the client driver the make best use of the DMAC
+ */
+static void read_dmac_config(struct pl330_info *pi)
+{
+       void __iomem *regs = pi->base;
+       u32 val;
+
+       val = readl(regs + CRD) >> CRD_DATA_WIDTH_SHIFT;
+       val &= CRD_DATA_WIDTH_MASK;
+       pi->pcfg.data_bus_width = 8 * (1 << val);
+
+       val = readl(regs + CRD) >> CRD_DATA_BUFF_SHIFT;
+       val &= CRD_DATA_BUFF_MASK;
+       pi->pcfg.data_buf_dep = val + 1;
+
+       val = readl(regs + CR0) >> CR0_NUM_CHANS_SHIFT;
+       val &= CR0_NUM_CHANS_MASK;
+       val += 1;
+       pi->pcfg.num_chan = val;
+
+       val = readl(regs + CR0);
+       if (val & CR0_PERIPH_REQ_SET) {
+               val = (val >> CR0_NUM_PERIPH_SHIFT) & CR0_NUM_PERIPH_MASK;
+               val += 1;
+               pi->pcfg.num_peri = val;
+               pi->pcfg.peri_ns = readl(regs + CR4);
+       } else {
+               pi->pcfg.num_peri = 0;
+       }
+
+       val = readl(regs + CR0);
+       if (val & CR0_BOOT_MAN_NS)
+               pi->pcfg.mode |= DMAC_MODE_NS;
+       else
+               pi->pcfg.mode &= ~DMAC_MODE_NS;
+
+       val = readl(regs + CR0) >> CR0_NUM_EVENTS_SHIFT;
+       val &= CR0_NUM_EVENTS_MASK;
+       val += 1;
+       pi->pcfg.num_events = val;
+
+       pi->pcfg.irq_ns = readl(regs + CR3);
+
+       pi->pcfg.periph_id = get_id(pi, PERIPH_ID);
+       pi->pcfg.pcell_id = get_id(pi, PCELL_ID);
+}
+
+static inline void _reset_thread(struct pl330_thread *thrd)
+{
+       struct pl330_dmac *pl330 = thrd->dmac;
+       struct pl330_info *pi = pl330->pinfo;
+
+       thrd->req[0].mc_cpu = pl330->mcode_cpu
+                               + (thrd->id * pi->mcbufsz);
+       thrd->req[0].mc_bus = pl330->mcode_bus
+                               + (thrd->id * pi->mcbufsz);
+       thrd->req[0].r = NULL;
+       MARK_FREE(&thrd->req[0]);
+
+       thrd->req[1].mc_cpu = thrd->req[0].mc_cpu
+                               + pi->mcbufsz / 2;
+       thrd->req[1].mc_bus = thrd->req[0].mc_bus
+                               + pi->mcbufsz / 2;
+       thrd->req[1].r = NULL;
+       MARK_FREE(&thrd->req[1]);
+}
+
+static int dmac_alloc_threads(struct pl330_dmac *pl330)
+{
+       struct pl330_info *pi = pl330->pinfo;
+       int chans = pi->pcfg.num_chan;
+       struct pl330_thread *thrd;
+       int i;
+
+       /* Allocate 1 Manager and 'chans' Channel threads */
+       pl330->channels = kzalloc((1 + chans) * sizeof(*thrd),
+                                       GFP_KERNEL);
+       if (!pl330->channels)
+               return -ENOMEM;
+
+       /* Init Channel threads */
+       for (i = 0; i < chans; i++) {
+               thrd = &pl330->channels[i];
+               thrd->id = i;
+               thrd->dmac = pl330;
+               _reset_thread(thrd);
+               thrd->free = true;
+       }
+
+       /* MANAGER is indexed at the end */
+       thrd = &pl330->channels[chans];
+       thrd->id = chans;
+       thrd->dmac = pl330;
+       thrd->free = false;
+       pl330->manager = thrd;
+
+       return 0;
+}
+
+static int dmac_alloc_resources(struct pl330_dmac *pl330)
+{
+       struct pl330_info *pi = pl330->pinfo;
+       int chans = pi->pcfg.num_chan;
+       int ret;
+
+       /*
+        * Alloc MicroCode buffer for 'chans' Channel threads.
+        * A channel's buffer offset is (Channel_Id * MCODE_BUFF_PERCHAN)
+        */
+       pl330->mcode_cpu = dma_alloc_coherent(pi->dev,
+                               chans * pi->mcbufsz,
+                               &pl330->mcode_bus, GFP_KERNEL);
+       if (!pl330->mcode_cpu) {
+               dev_err(pi->dev, "%s:%d Can't allocate memory!\n",
+                       __func__, __LINE__);
+               return -ENOMEM;
+       }
+
+       ret = dmac_alloc_threads(pl330);
+       if (ret) {
+               dev_err(pi->dev, "%s:%d Can't to create channels for DMAC!\n",
+                       __func__, __LINE__);
+               dma_free_coherent(pi->dev,
+                               chans * pi->mcbufsz,
+                               pl330->mcode_cpu, pl330->mcode_bus);
+               return ret;
+       }
+
+       return 0;
+}
+
+int pl330_add(struct pl330_info *pi)
+{
+       struct pl330_dmac *pl330;
+       void __iomem *regs;
+       int i, ret;
+
+       if (!pi || !pi->dev)
+               return -EINVAL;
+
+       /* If already added */
+       if (pi->pl330_data)
+               return -EINVAL;
+
+       /*
+        * If the SoC can perform reset on the DMAC, then do it
+        * before reading its configuration.
+        */
+       if (pi->dmac_reset)
+               pi->dmac_reset(pi);
+
+       regs = pi->base;
+
+       /* Check if we can handle this DMAC */
+       if (get_id(pi, PERIPH_ID) != PERIPH_ID_VAL
+          || get_id(pi, PCELL_ID) != PCELL_ID_VAL) {
+               dev_err(pi->dev, "PERIPH_ID 0x%x, PCELL_ID 0x%x !\n",
+                       readl(regs + PERIPH_ID), readl(regs + PCELL_ID));
+               return -EINVAL;
+       }
+
+       /* Read the configuration of the DMAC */
+       read_dmac_config(pi);
+
+       if (pi->pcfg.num_events == 0) {
+               dev_err(pi->dev, "%s:%d Can't work without events!\n",
+                       __func__, __LINE__);
+               return -EINVAL;
+       }
+
+       pl330 = kzalloc(sizeof(*pl330), GFP_KERNEL);
+       if (!pl330) {
+               dev_err(pi->dev, "%s:%d Can't allocate memory!\n",
+                       __func__, __LINE__);
+               return -ENOMEM;
+       }
+
+       /* Assign the info structure and private data */
+       pl330->pinfo = pi;
+       pi->pl330_data = pl330;
+
+       spin_lock_init(&pl330->lock);
+
+       INIT_LIST_HEAD(&pl330->req_done);
+
+       /* Use default MC buffer size if not provided */
+       if (!pi->mcbufsz)
+               pi->mcbufsz = MCODE_BUFF_PER_REQ * 2;
+
+       /* Mark all events as free */
+       for (i = 0; i < pi->pcfg.num_events; i++)
+               pl330->events[i] = -1;
+
+       /* Allocate resources needed by the DMAC */
+       ret = dmac_alloc_resources(pl330);
+       if (ret) {
+               dev_err(pi->dev, "Unable to create channels for DMAC\n");
+               kfree(pl330);
+               return ret;
+       }
+
+       tasklet_init(&pl330->tasks, pl330_dotask, (unsigned long) pl330);
+
+       pl330->state = INIT;
+
+       return 0;
+}
+EXPORT_SYMBOL(pl330_add);
+
+static int dmac_free_threads(struct pl330_dmac *pl330)
+{
+       struct pl330_info *pi = pl330->pinfo;
+       int chans = pi->pcfg.num_chan;
+       struct pl330_thread *thrd;
+       int i;
+
+       /* Release Channel threads */
+       for (i = 0; i < chans; i++) {
+               thrd = &pl330->channels[i];
+               pl330_release_channel((void *)thrd);
+       }
+
+       /* Free memory */
+       kfree(pl330->channels);
+
+       return 0;
+}
+
+static void dmac_free_resources(struct pl330_dmac *pl330)
+{
+       struct pl330_info *pi = pl330->pinfo;
+       int chans = pi->pcfg.num_chan;
+
+       dmac_free_threads(pl330);
+
+       dma_free_coherent(pi->dev, chans * pi->mcbufsz,
+                               pl330->mcode_cpu, pl330->mcode_bus);
+}
+
+void pl330_del(struct pl330_info *pi)
+{
+       struct pl330_dmac *pl330;
+
+       if (!pi || !pi->pl330_data)
+               return;
+
+       pl330 = pi->pl330_data;
+
+       pl330->state = UNINIT;
+
+       tasklet_kill(&pl330->tasks);
+
+       /* Free DMAC resources */
+       dmac_free_resources(pl330);
+
+       kfree(pl330);
+       pi->pl330_data = NULL;
+}
+EXPORT_SYMBOL(pl330_del);
diff --git a/arch/arm/include/asm/hardware/pl330.h b/arch/arm/include/asm/hardware/pl330.h
new file mode 100644 (file)
index 0000000..575fa81
--- /dev/null
@@ -0,0 +1,217 @@
+/* linux/include/asm/hardware/pl330.h
+ *
+ * Copyright (C) 2010 Samsung Electronics Co. Ltd.
+ *     Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __PL330_CORE_H
+#define __PL330_CORE_H
+
+#define PL330_MAX_CHAN         8
+#define PL330_MAX_IRQS         32
+#define PL330_MAX_PERI         32
+
+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 */
+};
+
+/* Populated by the PL330 core driver for DMA API driver's info */
+struct pl330_config {
+       u32     periph_id;
+       u32     pcell_id;
+#define DMAC_MODE_NS   (1 << 0)
+       unsigned int    mode;
+       unsigned int    data_bus_width:10; /* In number of bits */
+       unsigned int    data_buf_dep:10;
+       unsigned int    num_chan:4;
+       unsigned int    num_peri:6;
+       u32             peri_ns;
+       unsigned int    num_events:6;
+       u32             irq_ns;
+};
+
+/* Handle to the DMAC provided to the PL330 core */
+struct pl330_info {
+       /* Owning device */
+       struct device *dev;
+       /* Size of MicroCode buffers for each channel. */
+       unsigned mcbufsz;
+       /* ioremap'ed address of PL330 registers. */
+       void __iomem    *base;
+       /* Client can freely use it. */
+       void    *client_data;
+       /* PL330 core data, Client must not touch it. */
+       void    *pl330_data;
+       /* Populated by the PL330 core driver during pl330_add */
+       struct pl330_config     pcfg;
+       /*
+        * If the DMAC has some reset mechanism, then the
+        * client may want to provide pointer to the method.
+        */
+       void (*dmac_reset)(struct pl330_info *pi);
+};
+
+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.
+        */
+       bool nonsecure;
+       bool privileged;
+       bool 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;
+};
+
+/*
+ * One cycle of DMAC operation.
+ * There may be more than one xfer in a request.
+ */
+struct pl330_xfer {
+       u32 src_addr;
+       u32 dst_addr;
+       /* Size to xfer */
+       u32 bytes;
+       /*
+        * Pointer to next xfer in the list.
+        * The last xfer in the req must point to NULL.
+        */
+       struct pl330_xfer *next;
+};
+
+/* The xfer callbacks are made with one of these arguments. */
+enum pl330_op_err {
+       /* The all xfers in the request were success. */
+       PL330_ERR_NONE,
+       /* If req aborted due to global error. */
+       PL330_ERR_ABORT,
+       /* If req failed due to problem with Channel. */
+       PL330_ERR_FAIL,
+};
+
+enum pl330_reqtype {
+       MEMTOMEM,
+       MEMTODEV,
+       DEVTOMEM,
+       DEVTODEV,
+};
+
+/* A request defining Scatter-Gather List ending with NULL xfer. */
+struct pl330_req {
+       enum pl330_reqtype rqtype;
+       /* Index of peripheral for the xfer. */
+       unsigned peri:5;
+       /* Unique token for this xfer, set by the client. */
+       void *token;
+       /* Callback to be called after xfer. */
+       void (*xfer_cb)(void *token, enum pl330_op_err err);
+       /* If NULL, req will be done at last set parameters. */
+       struct pl330_reqcfg *cfg;
+       /* Pointer to first xfer in the request. */
+       struct pl330_xfer *x;
+};
+
+/*
+ * To know the status of the channel and DMAC, the client
+ * provides a pointer to this structure. The PL330 core
+ * fills it with current information.
+ */
+struct pl330_chanstatus {
+       /*
+        * If the DMAC engine halted due to some error,
+        * the client should remove-add DMAC.
+        */
+       bool dmac_halted;
+       /*
+        * If channel is halted due to some error,
+        * the client should ABORT/FLUSH and START the channel.
+        */
+       bool faulting;
+       /* Location of last load */
+       u32 src_addr;
+       /* Location of last store */
+       u32 dst_addr;
+       /*
+        * Pointer to the currently active req, NULL if channel is
+        * inactive, even though the requests may be present.
+        */
+       struct pl330_req *top_req;
+       /* Pointer to req waiting second in the queue if any. */
+       struct pl330_req *wait_req;
+};
+
+enum pl330_chan_op {
+       /* Start the channel */
+       PL330_OP_START,
+       /* Abort the active xfer */
+       PL330_OP_ABORT,
+       /* Stop xfer and flush queue */
+       PL330_OP_FLUSH,
+};
+
+extern int pl330_add(struct pl330_info *);
+extern void pl330_del(struct pl330_info *pi);
+extern int pl330_update(const struct pl330_info *pi);
+extern void pl330_release_channel(void *ch_id);
+extern void *pl330_request_channel(const struct pl330_info *pi);
+extern int pl330_chan_status(void *ch_id, struct pl330_chanstatus *pstatus);
+extern int pl330_chan_ctrl(void *ch_id, enum pl330_chan_op op);
+extern int pl330_submit_req(void *ch_id, struct pl330_req *r);
+
+#endif /* __PL330_CORE_H */
index 611c1cf8a17ab7cfd44a035e407c29da0ae08d75..f659df860d7ef06b7c53bf060d75e757a7f8100f 100644 (file)
@@ -2,5 +2,6 @@ config MACH_RK29SDK
        depends on ARCH_RK29
        default y
        bool "ROCKCHIP Board Rk29 For Sdk"
-       help
+        select PL330   
+        help
          Support for the ROCKCHIP Board For Rk29 Sdk.
\ No newline at end of file
index 027b83305c67cdac1d7d4c14949ba73043e126fc..70dfd7de6331cd55c375cb926fbecd23226a7161 100644 (file)
@@ -1,2 +1,2 @@
-obj-y += timer.o io.o devices.o iomux.o clock.o
+obj-y += timer.o io.o devices.o iomux.o clock.o rk29-pl330.o dma.o
 obj-$(CONFIG_MACH_RK29SDK) += board-rk29sdk.o
diff --git a/arch/arm/mach-rk29/dma.c b/arch/arm/mach-rk29/dma.c
new file mode 100644 (file)
index 0000000..6b2b667
--- /dev/null
@@ -0,0 +1,114 @@
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+
+#include <mach/rk29_iomap.h>
+#include <mach/irqs.h>
+
+#include <mach/rk29-dma-pl330.h>
+
+static u64 dma_dmamask = DMA_BIT_MASK(16);
+
+static struct resource rk29_dmac0_resource[] = {
+       [0] = {
+               .start  = RK29_DMAC0_PHYS,
+               .end    = RK29_DMAC0_PHYS + RK29_DMAC0_SIZE,
+               .flags  = IORESOURCE_MEM,
+       },
+       [1] = {
+               .start  = IRQ_DMAC0_0,
+               .end    = IRQ_DMAC0_0,
+               .flags  = IORESOURCE_IRQ,
+       },
+};
+
+static struct rk29_pl330_platdata rk29_dmac0_pdata = {
+       .peri = {
+               [0] = DMACH_UART0_TX,
+               [1] = DMACH_UART0_RX,
+               [2] = DMACH_I2S_8CH_TX,
+               [3] = DMACH_I2S_8CH_RX,
+               [4] = DMACH_I2S_2CH_TX,
+               [5] = DMACH_I2S_2CH_RX,
+               [6] = DMACH_SPDIF,
+               [7] = DMACH_MAX,
+               [8] = DMACH_MAX,
+               [9] = DMACH_MAX,
+               [10] = DMACH_MAX,
+               [11] = DMACH_MAX,
+               [12] = DMACH_MAX,
+               [13] = DMACH_MAX,
+               [14] = DMACH_MAX,
+               [15] = DMACH_MAX,
+       },
+};
+
+static struct platform_device rk29_device_dmac0 = {
+       .name           = "rk29-pl330",
+       .id             = 1,
+       .num_resources  = ARRAY_SIZE(rk29_dmac0_resource),
+       .resource       = rk29_dmac0_resource,
+       .dev            = {
+               .dma_mask = &dma_dmamask,
+               .coherent_dma_mask = DMA_BIT_MASK(16),
+               .platform_data = &rk29_dmac0_pdata,
+       },
+};
+
+static struct resource rk29_dmac2_resource[] = {
+       [0] = {
+               .start  = RK29_DMA2_PHYS,
+               .end    = RK29_DMA2_PHYS + RK29_DMA2_SIZE,
+               .flags  = IORESOURCE_MEM,
+       },
+       [1] = {
+               .start  = IRQ_DMAC2_0,
+               .end    = IRQ_DMAC2_0,
+               .flags  = IORESOURCE_IRQ,
+       },
+};
+
+static struct rk29_pl330_platdata rk29_dmac2_pdata = {
+       .peri = {
+               [0] = DMACH_HSADC,
+               [1] = DMACH_SDMMC,
+               [2] = DMACH_SDIO,
+               [3] = DMACH_EMMC,
+               [4] = DMACH_UART1_TX,
+               [5] = DMACH_UART1_RX,
+               [6] = DMACH_UART2_TX,
+               [7] = DMACH_UART2_RX,
+               [8] = DMACH_UART3_TX,
+               [9] = DMACH_UART3_RX,
+               [10] = DMACH_SPI0_TX,
+               [11] = DMACH_SPI0_RX,
+               [12] = DMACH_SPI1_TX,
+               [13] = DMACH_SPI1_RX,
+               [14] = DMACH_PID_FILTER,
+               [15] = DMACH_MAX,
+       },
+};
+
+static struct platform_device rk29_device_dmac2 = {
+       .name           = "rk29-pl330",
+       .id             = 2,
+       .num_resources  = ARRAY_SIZE(rk29_dmac2_resource),
+       .resource       = rk29_dmac2_resource,
+       .dev            = {
+               .dma_mask = &dma_dmamask,
+               .coherent_dma_mask = DMA_BIT_MASK(16),
+               .platform_data = &rk29_dmac2_pdata,
+       },
+};
+
+static struct platform_device *rk29_dmacs[] __initdata = {
+       &rk29_device_dmac0,
+       &rk29_device_dmac2,
+};
+
+static int __init rk29_dma_init(void)
+{
+       platform_add_devices(rk29_dmacs, ARRAY_SIZE(rk29_dmacs));
+
+       return 0;
+}
+arch_initcall(rk29_dma_init);
diff --git a/arch/arm/mach-rk29/include/mach/dma.h b/arch/arm/mach-rk29/include/mach/dma.h
new file mode 100644 (file)
index 0000000..cfd9e14
--- /dev/null
@@ -0,0 +1,127 @@
+/* arch/arm/plat-samsung/include/plat/dma.h
+ *
+ * Copyright (C) 2003-2006 Simtec Electronics
+ *     Ben Dooks <ben@simtec.co.uk>
+ *
+ * Samsung rk29 DMA support
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+enum rk29_dma_buffresult {
+       RK29_RES_OK,
+       RK29_RES_ERR,
+       RK29_RES_ABORT
+};
+
+enum rk29_dmasrc {
+       RK29_DMASRC_HW,         /* source is memory */
+       RK29_DMASRC_MEM         /* source is hardware */
+};
+
+/* enum rk29_chan_op
+ *
+ * operation codes passed to the DMA code by the user, and also used
+ * to inform the current channel owner of any changes to the system state
+*/
+
+enum rk29_chan_op {
+       RK29_DMAOP_START,
+       RK29_DMAOP_STOP,
+       RK29_DMAOP_PAUSE,
+       RK29_DMAOP_RESUME,
+       RK29_DMAOP_FLUSH,
+       RK29_DMAOP_TIMEOUT,             /* internal signal to handler */
+       RK29_DMAOP_STARTED,             /* indicate channel started */
+};
+
+struct rk29_dma_client {
+       char                *name;
+};
+
+struct rk29_dma_chan;
+
+/* rk29_dma_cbfn_t
+ *
+ * buffer callback routine type
+*/
+
+typedef void (*rk29_dma_cbfn_t)(struct rk29_dma_chan *,
+                                  void *buf, int size,
+                                  enum rk29_dma_buffresult result);
+
+typedef int  (*rk29_dma_opfn_t)(struct rk29_dma_chan *,
+                                  enum rk29_chan_op );
+
+
+
+/* rk29_dma_request
+ *
+ * request a dma channel exclusivley
+*/
+
+extern int rk29_dma_request(unsigned int channel,
+                              struct rk29_dma_client *, void *dev);
+
+
+/* rk29_dma_ctrl
+ *
+ * change the state of the dma channel
+*/
+
+extern int rk29_dma_ctrl(unsigned int channel, enum rk29_chan_op op);
+
+/* rk29_dma_setflags
+ *
+ * set the channel's flags to a given state
+*/
+
+extern int rk29_dma_setflags(unsigned int channel,
+                               unsigned int flags);
+
+/* rk29_dma_free
+ *
+ * free the dma channel (will also abort any outstanding operations)
+*/
+
+extern int rk29_dma_free(unsigned int channel, struct rk29_dma_client *);
+
+/* rk29_dma_enqueue
+ *
+ * place the given buffer onto the queue of operations for the channel.
+ * The buffer must be allocated from dma coherent memory, or the Dcache/WB
+ * drained before the buffer is given to the DMA system.
+*/
+
+extern int rk29_dma_enqueue(unsigned int channel, void *id,
+                              dma_addr_t data, int size);
+
+/* rk29_dma_config
+ *
+ * configure the dma channel
+*/
+
+extern int rk29_dma_config(unsigned int channel, int xferunit);
+
+/* rk29_dma_devconfig
+ *
+ * configure the device we're talking to
+*/
+
+extern int rk29_dma_devconfig(unsigned int channel,
+               enum rk29_dmasrc source, unsigned long devaddr);
+
+/* rk29_dma_getposition
+ *
+ * get the position that the dma transfer is currently at
+*/
+
+extern int rk29_dma_getposition(unsigned int channel,
+                                  dma_addr_t *src, dma_addr_t *dest);
+
+extern int rk29_dma_set_opfn(unsigned int, rk29_dma_opfn_t rtn);
+extern int rk29_dma_set_buffdone_fn(unsigned int, rk29_dma_cbfn_t rtn);
+
+
index 8dbb1a084b6571e4595e0985ee92572b5217b201..b456d17523382b77f08c08435ea98e6d42017308 100644 (file)
 
 #define RK29XX_IRQ(x)   (x+32)
 
+#define IRQ_DMAC0_0     RK29XX_IRQ(0)
+#define IRQ_DMAC0_1     RK29XX_IRQ(1)
+#define IRQ_DMAC0_2     RK29XX_IRQ(2)
+#define IRQ_DMAC0_3     RK29XX_IRQ(3)
+
+#define IRQ_DMAC2_0     RK29XX_IRQ(4)
+#define IRQ_DMAC2_1     RK29XX_IRQ(5)
+#define IRQ_DMAC2_2     RK29XX_IRQ(6)
+#define IRQ_DMAC2_3     RK29XX_IRQ(7)
+#define IRQ_DMAC2_4     RK29XX_IRQ(8)
+
 #define IRQ_GPU         RK29XX_IRQ(9)
 #define IRQ_VEPU        RK29XX_IRQ(10)
 #define IRQ_VDPU        RK29XX_IRQ(11)
diff --git a/arch/arm/mach-rk29/include/mach/rk29-dma-pl330.h b/arch/arm/mach-rk29/include/mach/rk29-dma-pl330.h
new file mode 100644 (file)
index 0000000..1fbf972
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 RockChip Electronics Co. Ltd.
+ *     ZhenFu Fang <fzf@rock-chips.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef        __RK29_DMA_PL330_H_
+#define        __RK29_DMA_PL330_H_
+
+#define RK29_DMAF_AUTOSTART            (1 << 0)
+#define RK29_DMAF_CIRCULAR             (1 << 1)
+
+/*
+ * PL330 can assign any channel to communicate with
+ * any of the peripherals attched to the DMAC.
+ * For the sake of consistency across client drivers,
+ * We keep the channel names unchanged and only add
+ * missing peripherals are added.
+ * Order is not important since rk29 PL330 API driver
+ * use these just as IDs.
+ */
+enum dma_ch {
+       DMACH_UART0_TX,
+       DMACH_UART0_RX,
+       DMACH_I2S_8CH_TX,
+       DMACH_I2S_8CH_RX,        
+       DMACH_I2S_2CH_TX,
+       DMACH_I2S_2CH_RX,
+       DMACH_SPDIF,
+       DMACH_HSADC,
+       DMACH_SDMMC,
+       DMACH_SDIO,
+       DMACH_EMMC,
+       DMACH_UART1_TX,
+       DMACH_UART1_RX,
+       DMACH_UART2_TX,
+       DMACH_UART2_RX,
+       DMACH_UART3_TX,
+       DMACH_UART3_RX,
+       DMACH_SPI0_TX,
+       DMACH_SPI0_RX,
+       DMACH_SPI1_TX,
+       DMACH_SPI1_RX,
+       DMACH_PID_FILTER,
+       /* END Marker, also used to denote a reserved channel */
+       DMACH_MAX,
+};
+
+static inline bool rk29_dma_has_circular(void)
+{
+       return true;
+}
+
+/*
+ * Every PL330 DMAC has max 32 peripheral interfaces,
+ * of which some may be not be really used in your
+ * DMAC's configuration.
+ * Populate this array of 32 peri i/fs with relevant
+ * channel IDs for used peri i/f and DMACH_MAX for
+ * those unused.
+ *
+ * The platforms just need to provide this info
+ * to the rk29 DMA API driver for PL330.
+ */
+struct rk29_pl330_platdata {
+       enum dma_ch peri[16];
+};
+
+#include <mach/dma.h>
+
+#endif /* __RK29_DMA_PL330_H_ */
diff --git a/arch/arm/mach-rk29/rk29-pl330.c b/arch/arm/mach-rk29/rk29-pl330.c
new file mode 100644 (file)
index 0000000..44d393d
--- /dev/null
@@ -0,0 +1,1224 @@
+/* linux/arch/arm/plat-samsung/rk29-pl330.c
+ *
+ * Copyright (C) 2010 Samsung Electronics Co. Ltd.
+ *     Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+#include <asm/hardware/pl330.h>
+
+#include <mach/rk29-dma-pl330.h>
+
+/**
+ * struct rk29_pl330_dmac - Logical representation of a PL330 DMAC.
+ * @busy_chan: Number of channels currently busy.
+ * @peri: List of IDs of peripherals this DMAC can work with.
+ * @node: To attach to the global list of DMACs.
+ * @pi: PL330 configuration info for the DMAC.
+ * @kmcache: Pool to quickly allocate xfers for all channels in the dmac.
+ */
+struct rk29_pl330_dmac {
+       unsigned                busy_chan;
+       enum dma_ch             *peri;
+       struct list_head        node;
+       struct pl330_info       *pi;
+       struct kmem_cache       *kmcache;
+};
+
+/**
+ * struct rk29_pl330_xfer - A request submitted by rk29 DMA clients.
+ * @token: Xfer ID provided by the client.
+ * @node: To attach to the list of xfers on a channel.
+ * @px: Xfer for PL330 core.
+ * @chan: Owner channel of this xfer.
+ */
+struct rk29_pl330_xfer {
+       void                    *token;
+       struct list_head        node;
+       struct pl330_xfer       px;
+       struct rk29_pl330_chan  *chan;
+};
+
+/**
+ * struct rk29_pl330_chan - Logical channel to communicate with
+ *     a Physical peripheral.
+ * @pl330_chan_id: Token of a hardware channel thread of PL330 DMAC.
+ *     NULL if the channel is available to be acquired.
+ * @id: ID of the peripheral that this channel can communicate with.
+ * @options: Options specified by the client.
+ * @sdaddr: Address provided via rk29_dma_devconfig.
+ * @node: To attach to the global list of channels.
+ * @lrq: Pointer to the last submitted pl330_req to PL330 core.
+ * @xfer_list: To manage list of xfers enqueued.
+ * @req: Two requests to communicate with the PL330 engine.
+ * @callback_fn: Callback function to the client.
+ * @rqcfg: Channel configuration for the xfers.
+ * @xfer_head: Pointer to the xfer to be next excecuted.
+ * @dmac: Pointer to the DMAC that manages this channel, NULL if the
+ *     channel is available to be acquired.
+ * @client: Client of this channel. NULL if the
+ *     channel is available to be acquired.
+ */
+struct rk29_pl330_chan {
+       void                            *pl330_chan_id;
+       enum dma_ch                     id;
+       unsigned int                    options;
+       unsigned long                   sdaddr;
+       struct list_head                node;
+       struct pl330_req                *lrq;
+       struct list_head                xfer_list;
+       struct pl330_req                req[2];
+       rk29_dma_cbfn_t                 callback_fn;
+       struct pl330_reqcfg             rqcfg;
+       struct rk29_pl330_xfer          *xfer_head;
+       struct rk29_pl330_dmac          *dmac;
+       struct rk29_dma_client  *client;
+};
+
+/* All DMACs in the platform */
+static LIST_HEAD(dmac_list);
+
+/* All channels to peripherals in the platform */
+static LIST_HEAD(chan_list);
+
+/*
+ * Since we add resources(DMACs and Channels) to the global pool,
+ * we need to guard access to the resources using a global lock
+ */
+static DEFINE_SPINLOCK(res_lock);
+
+/* Returns the channel with ID 'id' in the chan_list */
+static struct rk29_pl330_chan *id_to_chan(const enum dma_ch id)
+{
+       struct rk29_pl330_chan *ch;
+
+       list_for_each_entry(ch, &chan_list, node)
+               if (ch->id == id)
+                       return ch;
+
+       return NULL;
+}
+
+/* Allocate a new channel with ID 'id' and add to chan_list */
+static void chan_add(const enum dma_ch id)
+{
+       struct rk29_pl330_chan *ch = id_to_chan(id);
+
+       /* Return if the channel already exists */
+       if (ch)
+               return;
+
+       ch = kmalloc(sizeof(*ch), GFP_KERNEL);
+       /* Return silently to work with other channels */
+       if (!ch)
+               return;
+
+       ch->id = id;
+       ch->dmac = NULL;
+
+       list_add_tail(&ch->node, &chan_list);
+}
+
+/* If the channel is not yet acquired by any client */
+static bool chan_free(struct rk29_pl330_chan *ch)
+{
+       if (!ch)
+               return false;
+
+       /* Channel points to some DMAC only when it's acquired */
+       return ch->dmac ? false : true;
+}
+
+/*
+ * Returns 0 is peripheral i/f is invalid or not present on the dmac.
+ * Index + 1, otherwise.
+ */
+static unsigned iface_of_dmac(struct rk29_pl330_dmac *dmac, enum dma_ch ch_id)
+{
+       enum dma_ch *id = dmac->peri;
+       int i;
+
+       /* Discount invalid markers */
+       if (ch_id == DMACH_MAX)
+               return 0;
+
+       for (i = 0; i < PL330_MAX_PERI; i++)
+               if (id[i] == ch_id)
+                       return i + 1;
+
+       return 0;
+}
+
+/* If all channel threads of the DMAC are busy */
+static inline bool dmac_busy(struct rk29_pl330_dmac *dmac)
+{
+       struct pl330_info *pi = dmac->pi;
+
+       return (dmac->busy_chan < pi->pcfg.num_chan) ? false : true;
+}
+
+/*
+ * Returns the number of free channels that
+ * can be handled by this dmac only.
+ */
+static unsigned ch_onlyby_dmac(struct rk29_pl330_dmac *dmac)
+{
+       enum dma_ch *id = dmac->peri;
+       struct rk29_pl330_dmac *d;
+       struct rk29_pl330_chan *ch;
+       unsigned found, count = 0;
+       enum dma_ch p;
+       int i;
+
+       for (i = 0; i < PL330_MAX_PERI; i++) {
+               p = id[i];
+               ch = id_to_chan(p);
+
+               if (p == DMACH_MAX || !chan_free(ch))
+                       continue;
+
+               found = 0;
+               list_for_each_entry(d, &dmac_list, node) {
+                       if (d != dmac && iface_of_dmac(d, ch->id)) {
+                               found = 1;
+                               break;
+                       }
+               }
+               if (!found)
+                       count++;
+       }
+
+       return count;
+}
+
+/*
+ * Measure of suitability of 'dmac' handling 'ch'
+ *
+ * 0 indicates 'dmac' can not handle 'ch' either
+ * because it is not supported by the hardware or
+ * because all dmac channels are currently busy.
+ *
+ * >0 vlaue indicates 'dmac' has the capability.
+ * The bigger the value the more suitable the dmac.
+ */
+#define MAX_SUIT       UINT_MAX
+#define MIN_SUIT       0
+
+static unsigned suitablility(struct rk29_pl330_dmac *dmac,
+               struct rk29_pl330_chan *ch)
+{
+       struct pl330_info *pi = dmac->pi;
+       enum dma_ch *id = dmac->peri;
+       struct rk29_pl330_dmac *d;
+       unsigned s;
+       int i;
+
+       s = MIN_SUIT;
+       /* If all the DMAC channel threads are busy */
+       if (dmac_busy(dmac))
+               return s;
+
+       for (i = 0; i < PL330_MAX_PERI; i++)
+               if (id[i] == ch->id)
+                       break;
+
+       /* If the 'dmac' can't talk to 'ch' */
+       if (i == PL330_MAX_PERI)
+               return s;
+
+       s = MAX_SUIT;
+       list_for_each_entry(d, &dmac_list, node) {
+               /*
+                * If some other dmac can talk to this
+                * peri and has some channel free.
+                */
+               if (d != dmac && iface_of_dmac(d, ch->id) && !dmac_busy(d)) {
+                       s = 0;
+                       break;
+               }
+       }
+       if (s)
+               return s;
+
+       s = 100;
+
+       /* Good if free chans are more, bad otherwise */
+       s += (pi->pcfg.num_chan - dmac->busy_chan) - ch_onlyby_dmac(dmac);
+
+       return s;
+}
+
+/* More than one DMAC may have capability to transfer data with the
+ * peripheral. This function assigns most suitable DMAC to manage the
+ * channel and hence communicate with the peripheral.
+ */
+static struct rk29_pl330_dmac *map_chan_to_dmac(struct rk29_pl330_chan *ch)
+{
+       struct rk29_pl330_dmac *d, *dmac = NULL;
+       unsigned sn, sl = MIN_SUIT;
+
+       list_for_each_entry(d, &dmac_list, node) {
+               sn = suitablility(d, ch);
+
+               if (sn == MAX_SUIT)
+                       return d;
+
+               if (sn > sl)
+                       dmac = d;
+       }
+
+       return dmac;
+}
+
+/* Acquire the channel for peripheral 'id' */
+static struct rk29_pl330_chan *chan_acquire(const enum dma_ch id)
+{
+       struct rk29_pl330_chan *ch = id_to_chan(id);
+       struct rk29_pl330_dmac *dmac;
+
+       /* If the channel doesn't exist or is already acquired */
+       if (!ch || !chan_free(ch)) {
+               ch = NULL;
+               goto acq_exit;
+       }
+
+       dmac = map_chan_to_dmac(ch);
+       /* If couldn't map */
+       if (!dmac) {
+               ch = NULL;
+               goto acq_exit;
+       }
+
+       dmac->busy_chan++;
+       ch->dmac = dmac;
+
+acq_exit:
+       return ch;
+}
+
+/* Delete xfer from the queue */
+static inline void del_from_queue(struct rk29_pl330_xfer *xfer)
+{
+       struct rk29_pl330_xfer *t;
+       struct rk29_pl330_chan *ch;
+       int found;
+
+       if (!xfer)
+               return;
+
+       ch = xfer->chan;
+
+       /* Make sure xfer is in the queue */
+       found = 0;
+       list_for_each_entry(t, &ch->xfer_list, node)
+               if (t == xfer) {
+                       found = 1;
+                       break;
+               }
+
+       if (!found)
+               return;
+
+       /* If xfer is last entry in the queue */
+       if (xfer->node.next == &ch->xfer_list)
+               t = list_entry(ch->xfer_list.next,
+                               struct rk29_pl330_xfer, node);
+       else
+               t = list_entry(xfer->node.next,
+                               struct rk29_pl330_xfer, node);
+
+       /* If there was only one node left */
+       if (t == xfer)
+               ch->xfer_head = NULL;
+       else if (ch->xfer_head == xfer)
+               ch->xfer_head = t;
+
+       list_del(&xfer->node);
+}
+
+/* Provides pointer to the next xfer in the queue.
+ * If CIRCULAR option is set, the list is left intact,
+ * otherwise the xfer is removed from the list.
+ * Forced delete 'pluck' can be set to override the CIRCULAR option.
+ */
+static struct rk29_pl330_xfer *get_from_queue(struct rk29_pl330_chan *ch,
+               int pluck)
+{
+       struct rk29_pl330_xfer *xfer = ch->xfer_head;
+
+       if (!xfer)
+               return NULL;
+
+       /* If xfer is last entry in the queue */
+       if (xfer->node.next == &ch->xfer_list)
+               ch->xfer_head = list_entry(ch->xfer_list.next,
+                                       struct rk29_pl330_xfer, node);
+       else
+               ch->xfer_head = list_entry(xfer->node.next,
+                                       struct rk29_pl330_xfer, node);
+
+       if (pluck || !(ch->options & RK29_DMAF_CIRCULAR))
+               del_from_queue(xfer);
+
+       return xfer;
+}
+
+static inline void add_to_queue(struct rk29_pl330_chan *ch,
+               struct rk29_pl330_xfer *xfer, int front)
+{
+       struct pl330_xfer *xt;
+
+       /* If queue empty */
+       if (ch->xfer_head == NULL)
+               ch->xfer_head = xfer;
+
+       xt = &ch->xfer_head->px;
+       /* If the head already submitted (CIRCULAR head) */
+       if (ch->options & RK29_DMAF_CIRCULAR &&
+               (xt == ch->req[0].x || xt == ch->req[1].x))
+               ch->xfer_head = xfer;
+
+       /* If this is a resubmission, it should go at the head */
+       if (front) {
+               ch->xfer_head = xfer;
+               list_add(&xfer->node, &ch->xfer_list);
+       } else {
+               list_add_tail(&xfer->node, &ch->xfer_list);
+       }
+}
+
+static inline void _finish_off(struct rk29_pl330_xfer *xfer,
+               enum rk29_dma_buffresult res, int ffree)
+{
+       struct rk29_pl330_chan *ch;
+
+       if (!xfer)
+               return;
+
+       ch = xfer->chan;
+
+       /* Do callback */
+       if (ch->callback_fn)
+               ch->callback_fn(NULL, xfer->token, xfer->px.bytes, res);
+
+       /* Force Free or if buffer is not needed anymore */
+       if (ffree || !(ch->options & RK29_DMAF_CIRCULAR))
+               kmem_cache_free(ch->dmac->kmcache, xfer);
+}
+
+static inline int rk29_pl330_submit(struct rk29_pl330_chan *ch,
+               struct pl330_req *r)
+{
+       struct rk29_pl330_xfer *xfer;
+       int ret = 0;
+
+       /* If already submitted */
+       if (r->x)
+               return 0;
+
+       xfer = get_from_queue(ch, 0);
+       if (xfer) {
+               r->x = &xfer->px;
+
+               /* Use max bandwidth for M<->M xfers */
+               if (r->rqtype == MEMTOMEM) {
+                       struct pl330_info *pi = xfer->chan->dmac->pi;
+                       int burst = 1 << ch->rqcfg.brst_size;
+                       u32 bytes = r->x->bytes;
+                       int bl;
+
+                       bl = pi->pcfg.data_bus_width / 8;
+                       bl *= pi->pcfg.data_buf_dep;
+                       bl /= burst;
+
+                       /* src/dst_burst_len can't be more than 16 */
+                       if (bl > 16)
+                               bl = 16;
+
+                       while (bl > 1) {
+                               if (!(bytes % (bl * burst)))
+                                       break;
+                               bl--;
+                       }
+
+                       ch->rqcfg.brst_len = bl;
+               } else {
+                       ch->rqcfg.brst_len = 1;
+               }
+
+               ret = pl330_submit_req(ch->pl330_chan_id, r);
+
+               /* If submission was successful */
+               if (!ret) {
+                       ch->lrq = r; /* latest submitted req */
+                       return 0;
+               }
+
+               r->x = NULL;
+
+               /* If both of the PL330 ping-pong buffers filled */
+               if (ret == -EAGAIN) {
+                       dev_err(ch->dmac->pi->dev, "%s:%d!\n",
+                               __func__, __LINE__);
+                       /* Queue back again */
+                       add_to_queue(ch, xfer, 1);
+                       ret = 0;
+               } else {
+                       dev_err(ch->dmac->pi->dev, "%s:%d!\n",
+                               __func__, __LINE__);
+                       _finish_off(xfer, RK29_RES_ERR, 0);
+               }
+       }
+
+       return ret;
+}
+
+static void rk29_pl330_rq(struct rk29_pl330_chan *ch,
+       struct pl330_req *r, enum pl330_op_err err)
+{
+       unsigned long flags;
+       struct rk29_pl330_xfer *xfer;
+       struct pl330_xfer *xl = r->x;
+       enum rk29_dma_buffresult res;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       r->x = NULL;
+
+       rk29_pl330_submit(ch, r);
+
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       /* Map result to rk29 DMA API */
+       if (err == PL330_ERR_NONE)
+               res = RK29_RES_OK;
+       else if (err == PL330_ERR_ABORT)
+               res = RK29_RES_ABORT;
+       else
+               res = RK29_RES_ERR;
+
+       /* If last request had some xfer */
+       if (xl) {
+               xfer = container_of(xl, struct rk29_pl330_xfer, px);
+               _finish_off(xfer, res, 0);
+       } else {
+               dev_info(ch->dmac->pi->dev, "%s:%d No Xfer?!\n",
+                       __func__, __LINE__);
+       }
+}
+
+static void rk29_pl330_rq0(void *token, enum pl330_op_err err)
+{
+       struct pl330_req *r = token;
+       struct rk29_pl330_chan *ch = container_of(r,
+                                       struct rk29_pl330_chan, req[0]);
+       rk29_pl330_rq(ch, r, err);
+}
+
+static void rk29_pl330_rq1(void *token, enum pl330_op_err err)
+{
+       struct pl330_req *r = token;
+       struct rk29_pl330_chan *ch = container_of(r,
+                                       struct rk29_pl330_chan, req[1]);
+       rk29_pl330_rq(ch, r, err);
+}
+
+/* Release an acquired channel */
+static void chan_release(struct rk29_pl330_chan *ch)
+{
+       struct rk29_pl330_dmac *dmac;
+
+       if (chan_free(ch))
+               return;
+
+       dmac = ch->dmac;
+       ch->dmac = NULL;
+       dmac->busy_chan--;
+}
+
+int rk29_dma_ctrl(enum dma_ch id, enum rk29_chan_op op)
+{
+       struct rk29_pl330_xfer *xfer;
+       enum pl330_chan_op pl330op;
+       struct rk29_pl330_chan *ch;
+       unsigned long flags;
+       int idx, ret;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = id_to_chan(id);
+
+       if (!ch || chan_free(ch)) {
+               ret = -EINVAL;
+               goto ctrl_exit;
+       }
+
+       switch (op) {
+       case RK29_DMAOP_START:
+               /* Make sure both reqs are enqueued */
+               idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
+               rk29_pl330_submit(ch, &ch->req[idx]);
+               rk29_pl330_submit(ch, &ch->req[1 - idx]);
+               pl330op = PL330_OP_START;
+               break;
+
+       case RK29_DMAOP_STOP:
+               pl330op = PL330_OP_ABORT;
+               break;
+
+       case RK29_DMAOP_FLUSH:
+               pl330op = PL330_OP_FLUSH;
+               break;
+
+       case RK29_DMAOP_PAUSE:
+       case RK29_DMAOP_RESUME:
+       case RK29_DMAOP_TIMEOUT:
+       case RK29_DMAOP_STARTED:
+               spin_unlock_irqrestore(&res_lock, flags);
+               return 0;
+
+       default:
+               spin_unlock_irqrestore(&res_lock, flags);
+               return -EINVAL;
+       }
+
+       ret = pl330_chan_ctrl(ch->pl330_chan_id, pl330op);
+
+       if (pl330op == PL330_OP_START) {
+               spin_unlock_irqrestore(&res_lock, flags);
+               return ret;
+       }
+
+       idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
+
+       /* Abort the current xfer */
+       if (ch->req[idx].x) {
+               xfer = container_of(ch->req[idx].x,
+                               struct rk29_pl330_xfer, px);
+
+               /* Drop xfer during FLUSH */
+               if (pl330op == PL330_OP_FLUSH)
+                       del_from_queue(xfer);
+
+               ch->req[idx].x = NULL;
+
+               spin_unlock_irqrestore(&res_lock, flags);
+               _finish_off(xfer, RK29_RES_ABORT,
+                               pl330op == PL330_OP_FLUSH ? 1 : 0);
+               spin_lock_irqsave(&res_lock, flags);
+       }
+
+       /* Flush the whole queue */
+       if (pl330op == PL330_OP_FLUSH) {
+
+               if (ch->req[1 - idx].x) {
+                       xfer = container_of(ch->req[1 - idx].x,
+                                       struct rk29_pl330_xfer, px);
+
+                       del_from_queue(xfer);
+
+                       ch->req[1 - idx].x = NULL;
+
+                       spin_unlock_irqrestore(&res_lock, flags);
+                       _finish_off(xfer, RK29_RES_ABORT, 1);
+                       spin_lock_irqsave(&res_lock, flags);
+               }
+
+               /* Finish off the remaining in the queue */
+               xfer = ch->xfer_head;
+               while (xfer) {
+
+                       del_from_queue(xfer);
+
+                       spin_unlock_irqrestore(&res_lock, flags);
+                       _finish_off(xfer, RK29_RES_ABORT, 1);
+                       spin_lock_irqsave(&res_lock, flags);
+
+                       xfer = ch->xfer_head;
+               }
+       }
+
+ctrl_exit:
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(rk29_dma_ctrl);
+
+int rk29_dma_enqueue(enum dma_ch id, void *token,
+                       dma_addr_t addr, int size)
+{
+       struct rk29_pl330_chan *ch;
+       struct rk29_pl330_xfer *xfer;
+       unsigned long flags;
+       int idx, ret = 0;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = id_to_chan(id);
+
+       /* Error if invalid or free channel */
+       if (!ch || chan_free(ch)) {
+               ret = -EINVAL;
+               goto enq_exit;
+       }
+
+       /* Error if size is unaligned */
+       if (ch->rqcfg.brst_size && size % (1 << ch->rqcfg.brst_size)) {
+               ret = -EINVAL;
+               goto enq_exit;
+       }
+
+       xfer = kmem_cache_alloc(ch->dmac->kmcache, GFP_ATOMIC);
+       if (!xfer) {
+               ret = -ENOMEM;
+               goto enq_exit;
+       }
+
+       xfer->token = token;
+       xfer->chan = ch;
+       xfer->px.bytes = size;
+       xfer->px.next = NULL; /* Single request */
+
+       /* For rk29 DMA API, direction is always fixed for all xfers */
+       if (ch->req[0].rqtype == MEMTODEV) {
+               xfer->px.src_addr = addr;
+               xfer->px.dst_addr = ch->sdaddr;
+       } else {
+               xfer->px.src_addr = ch->sdaddr;
+               xfer->px.dst_addr = addr;
+       }
+
+       add_to_queue(ch, xfer, 0);
+
+       /* Try submitting on either request */
+       idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
+
+       if (!ch->req[idx].x)
+               rk29_pl330_submit(ch, &ch->req[idx]);
+       else
+               rk29_pl330_submit(ch, &ch->req[1 - idx]);
+
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       if (ch->options & RK29_DMAF_AUTOSTART)
+               rk29_dma_ctrl(id, RK29_DMAOP_START);
+
+       return 0;
+
+enq_exit:
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(rk29_dma_enqueue);
+
+int rk29_dma_request(enum dma_ch id,
+                       struct rk29_dma_client *client,
+                       void *dev)
+{
+       struct rk29_pl330_dmac *dmac;
+       struct rk29_pl330_chan *ch;
+       unsigned long flags;
+       int ret = 0;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = chan_acquire(id);
+       if (!ch) {
+               ret = -EBUSY;
+               goto req_exit;
+       }
+
+       dmac = ch->dmac;
+
+       ch->pl330_chan_id = pl330_request_channel(dmac->pi);
+       if (!ch->pl330_chan_id) {
+               chan_release(ch);
+               ret = -EBUSY;
+               goto req_exit;
+       }
+
+       ch->client = client;
+       ch->options = 0; /* Clear any option */
+       ch->callback_fn = NULL; /* Clear any callback */
+       ch->lrq = NULL;
+
+       ch->rqcfg.brst_size = 2; /* Default word size */
+       ch->rqcfg.swap = SWAP_NO;
+       ch->rqcfg.scctl = SCCTRL0; /* Noncacheable and nonbufferable */
+       ch->rqcfg.dcctl = DCCTRL0; /* Noncacheable and nonbufferable */
+       ch->rqcfg.privileged = 0;
+       ch->rqcfg.insnaccess = 0;
+
+       /* Set invalid direction */
+       ch->req[0].rqtype = DEVTODEV;
+       ch->req[1].rqtype = ch->req[0].rqtype;
+
+       ch->req[0].cfg = &ch->rqcfg;
+       ch->req[1].cfg = ch->req[0].cfg;
+
+       ch->req[0].peri = iface_of_dmac(dmac, id) - 1; /* Original index */
+       ch->req[1].peri = ch->req[0].peri;
+
+       ch->req[0].token = &ch->req[0];
+       ch->req[0].xfer_cb = rk29_pl330_rq0;
+       ch->req[1].token = &ch->req[1];
+       ch->req[1].xfer_cb = rk29_pl330_rq1;
+
+       ch->req[0].x = NULL;
+       ch->req[1].x = NULL;
+
+       /* Reset xfer list */
+       INIT_LIST_HEAD(&ch->xfer_list);
+       ch->xfer_head = NULL;
+
+req_exit:
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(rk29_dma_request);
+
+int rk29_dma_free(enum dma_ch id, struct rk29_dma_client *client)
+{
+       struct rk29_pl330_chan *ch;
+       struct rk29_pl330_xfer *xfer;
+       unsigned long flags;
+       int ret = 0;
+       unsigned idx;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = id_to_chan(id);
+
+       if (!ch || chan_free(ch))
+               goto free_exit;
+
+       /* Refuse if someone else wanted to free the channel */
+       if (ch->client != client) {
+               ret = -EBUSY;
+               goto free_exit;
+       }
+
+       /* Stop any active xfer, Flushe the queue and do callbacks */
+       pl330_chan_ctrl(ch->pl330_chan_id, PL330_OP_FLUSH);
+
+       /* Abort the submitted requests */
+       idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
+
+       if (ch->req[idx].x) {
+               xfer = container_of(ch->req[idx].x,
+                               struct rk29_pl330_xfer, px);
+
+               ch->req[idx].x = NULL;
+               del_from_queue(xfer);
+
+               spin_unlock_irqrestore(&res_lock, flags);
+               _finish_off(xfer, RK29_RES_ABORT, 1);
+               spin_lock_irqsave(&res_lock, flags);
+       }
+
+       if (ch->req[1 - idx].x) {
+               xfer = container_of(ch->req[1 - idx].x,
+                               struct rk29_pl330_xfer, px);
+
+               ch->req[1 - idx].x = NULL;
+               del_from_queue(xfer);
+
+               spin_unlock_irqrestore(&res_lock, flags);
+               _finish_off(xfer, RK29_RES_ABORT, 1);
+               spin_lock_irqsave(&res_lock, flags);
+       }
+
+       /* Pluck and Abort the queued requests in order */
+       do {
+               xfer = get_from_queue(ch, 1);
+
+               spin_unlock_irqrestore(&res_lock, flags);
+               _finish_off(xfer, RK29_RES_ABORT, 1);
+               spin_lock_irqsave(&res_lock, flags);
+       } while (xfer);
+
+       ch->client = NULL;
+
+       pl330_release_channel(ch->pl330_chan_id);
+
+       ch->pl330_chan_id = NULL;
+
+       chan_release(ch);
+
+free_exit:
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(rk29_dma_free);
+
+int rk29_dma_config(enum dma_ch id, int xferunit)
+{
+       struct rk29_pl330_chan *ch;
+       struct pl330_info *pi;
+       unsigned long flags;
+       int i, dbwidth, ret = 0;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = id_to_chan(id);
+
+       if (!ch || chan_free(ch)) {
+               ret = -EINVAL;
+               goto cfg_exit;
+       }
+
+       pi = ch->dmac->pi;
+       dbwidth = pi->pcfg.data_bus_width / 8;
+
+       /* Max size of xfer can be pcfg.data_bus_width */
+       if (xferunit > dbwidth) {
+               ret = -EINVAL;
+               goto cfg_exit;
+       }
+
+       i = 0;
+       while (xferunit != (1 << i))
+               i++;
+
+       /* If valid value */
+       if (xferunit == (1 << i))
+               ch->rqcfg.brst_size = i;
+       else
+               ret = -EINVAL;
+
+cfg_exit:
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(rk29_dma_config);
+
+/* Options that are supported by this driver */
+#define RK29_PL330_FLAGS (RK29_DMAF_CIRCULAR | RK29_DMAF_AUTOSTART)
+
+int rk29_dma_setflags(enum dma_ch id, unsigned int options)
+{
+       struct rk29_pl330_chan *ch;
+       unsigned long flags;
+       int ret = 0;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = id_to_chan(id);
+
+       if (!ch || chan_free(ch) || options & ~(RK29_PL330_FLAGS))
+               ret = -EINVAL;
+       else
+               ch->options = options;
+
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return 0;
+}
+EXPORT_SYMBOL(rk29_dma_setflags);
+
+int rk29_dma_set_buffdone_fn(enum dma_ch id, rk29_dma_cbfn_t rtn)
+{
+       struct rk29_pl330_chan *ch;
+       unsigned long flags;
+       int ret = 0;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = id_to_chan(id);
+
+       if (!ch || chan_free(ch))
+               ret = -EINVAL;
+       else
+               ch->callback_fn = rtn;
+
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(rk29_dma_set_buffdone_fn);
+
+int rk29_dma_devconfig(enum dma_ch id, enum rk29_dmasrc source,
+                         unsigned long address)
+{
+       struct rk29_pl330_chan *ch;
+       unsigned long flags;
+       int ret = 0;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = id_to_chan(id);
+
+       if (!ch || chan_free(ch)) {
+               ret = -EINVAL;
+               goto devcfg_exit;
+       }
+
+       switch (source) {
+       case RK29_DMASRC_HW: /* P->M */
+               ch->req[0].rqtype = DEVTOMEM;
+               ch->req[1].rqtype = DEVTOMEM;
+               ch->rqcfg.src_inc = 0;
+               ch->rqcfg.dst_inc = 1;
+               break;
+       case RK29_DMASRC_MEM: /* M->P */
+               ch->req[0].rqtype = MEMTODEV;
+               ch->req[1].rqtype = MEMTODEV;
+               ch->rqcfg.src_inc = 1;
+               ch->rqcfg.dst_inc = 0;
+               break;
+       default:
+               ret = -EINVAL;
+               goto devcfg_exit;
+       }
+
+       ch->sdaddr = address;
+
+devcfg_exit:
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(rk29_dma_devconfig);
+
+int rk29_dma_getposition(enum dma_ch id, dma_addr_t *src, dma_addr_t *dst)
+{
+       struct rk29_pl330_chan *ch = id_to_chan(id);
+       struct pl330_chanstatus status;
+       int ret;
+
+       if (!ch || chan_free(ch))
+               return -EINVAL;
+
+       ret = pl330_chan_status(ch->pl330_chan_id, &status);
+       if (ret < 0)
+               return ret;
+
+       *src = status.src_addr;
+       *dst = status.dst_addr;
+
+       return 0;
+}
+EXPORT_SYMBOL(rk29_dma_getposition);
+
+static irqreturn_t pl330_irq_handler(int irq, void *data)
+{
+       if (pl330_update(data))
+               return IRQ_HANDLED;
+       else
+               return IRQ_NONE;
+}
+
+static int pl330_probe(struct platform_device *pdev)
+{
+       struct rk29_pl330_dmac *rk29_pl330_dmac;
+       struct rk29_pl330_platdata *pl330pd;
+       struct pl330_info *pl330_info;
+       struct resource *res;
+       int i, ret, irq;
+
+       pl330pd = pdev->dev.platform_data;
+
+       /* Can't do without the list of _32_ peripherals */
+       if (!pl330pd || !pl330pd->peri) {
+               dev_err(&pdev->dev, "platform data missing!\n");
+               return -ENODEV;
+       }
+
+       pl330_info = kzalloc(sizeof(*pl330_info), GFP_KERNEL);
+       if (!pl330_info)
+               return -ENOMEM;
+
+       pl330_info->pl330_data = NULL;
+       pl330_info->dev = &pdev->dev;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res) {
+               ret = -ENODEV;
+               goto probe_err1;
+       }
+
+       request_mem_region(res->start, resource_size(res), pdev->name);
+
+       pl330_info->base = ioremap(res->start, resource_size(res));
+       if (!pl330_info->base) {
+               ret = -ENXIO;
+               goto probe_err2;
+       }
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0) {
+               ret = irq;
+               goto probe_err3;
+       }
+
+       ret = request_irq(irq, pl330_irq_handler, 0,
+                       dev_name(&pdev->dev), pl330_info);
+       if (ret)
+               goto probe_err4;
+
+       ret = pl330_add(pl330_info);
+       if (ret)
+               goto probe_err5;
+
+       /* Allocate a new DMAC */
+       rk29_pl330_dmac = kmalloc(sizeof(*rk29_pl330_dmac), GFP_KERNEL);
+       if (!rk29_pl330_dmac) {
+               ret = -ENOMEM;
+               goto probe_err6;
+       }
+
+       /* Hook the info */
+       rk29_pl330_dmac->pi = pl330_info;
+
+       /* No busy channels */
+       rk29_pl330_dmac->busy_chan = 0;
+
+       rk29_pl330_dmac->kmcache = kmem_cache_create(dev_name(&pdev->dev),
+                               sizeof(struct rk29_pl330_xfer), 0, 0, NULL);
+
+       if (!rk29_pl330_dmac->kmcache) {
+               ret = -ENOMEM;
+               goto probe_err7;
+       }
+
+       /* Get the list of peripherals */
+       rk29_pl330_dmac->peri = pl330pd->peri;
+
+       /* Attach to the list of DMACs */
+       list_add_tail(&rk29_pl330_dmac->node, &dmac_list);
+
+       /* Create a channel for each peripheral in the DMAC
+        * that is, if it doesn't already exist
+        */
+       for (i = 0; i < PL330_MAX_PERI; i++)
+               if (rk29_pl330_dmac->peri[i] != DMACH_MAX)
+                       chan_add(rk29_pl330_dmac->peri[i]);
+
+       printk(KERN_INFO
+               "Loaded driver for PL330 DMAC-%d %s\n", pdev->id, pdev->name);
+       printk(KERN_INFO
+               "\tDBUFF-%ux%ubytes Num_Chans-%u Num_Peri-%u Num_Events-%u\n",
+               pl330_info->pcfg.data_buf_dep,
+               pl330_info->pcfg.data_bus_width / 8, pl330_info->pcfg.num_chan,
+               pl330_info->pcfg.num_peri, pl330_info->pcfg.num_events);
+
+       return 0;
+
+probe_err7:
+       kfree(rk29_pl330_dmac);
+probe_err6:
+       pl330_del(pl330_info);
+probe_err5:
+       free_irq(irq, pl330_info);
+probe_err4:
+probe_err3:
+       iounmap(pl330_info->base);
+probe_err2:
+       release_mem_region(res->start, resource_size(res));
+probe_err1:
+       kfree(pl330_info);
+
+       return ret;
+}
+
+static int pl330_remove(struct platform_device *pdev)
+{
+       struct rk29_pl330_dmac *dmac, *d;
+       struct rk29_pl330_chan *ch;
+       unsigned long flags;
+       int del, found;
+
+       if (!pdev->dev.platform_data)
+               return -EINVAL;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       found = 0;
+       list_for_each_entry(d, &dmac_list, node)
+               if (d->pi->dev == &pdev->dev) {
+                       found = 1;
+                       break;
+               }
+
+       if (!found) {
+               spin_unlock_irqrestore(&res_lock, flags);
+               return 0;
+       }
+
+       dmac = d;
+
+       /* Remove all Channels that are managed only by this DMAC */
+       list_for_each_entry(ch, &chan_list, node) {
+
+               /* Only channels that are handled by this DMAC */
+               if (iface_of_dmac(dmac, ch->id))
+                       del = 1;
+               else
+                       continue;
+
+               /* Don't remove if some other DMAC has it too */
+               list_for_each_entry(d, &dmac_list, node)
+                       if (d != dmac && iface_of_dmac(d, ch->id)) {
+                               del = 0;
+                               break;
+                       }
+
+               if (del) {
+                       spin_unlock_irqrestore(&res_lock, flags);
+                       rk29_dma_free(ch->id, ch->client);
+                       spin_lock_irqsave(&res_lock, flags);
+                       list_del(&ch->node);
+                       kfree(ch);
+               }
+       }
+
+       /* Remove the DMAC */
+       list_del(&dmac->node);
+       kfree(dmac);
+
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return 0;
+}
+
+static struct platform_driver pl330_driver = {
+       .driver         = {
+               .owner  = THIS_MODULE,
+               .name   = "rk29-pl330",
+       },
+       .probe          = pl330_probe,
+       .remove         = pl330_remove,
+};
+
+static int __init pl330_init(void)
+{
+       return platform_driver_register(&pl330_driver);
+}
+module_init(pl330_init);
+
+static void __exit pl330_exit(void)
+{
+       platform_driver_unregister(&pl330_driver);
+       return;
+}
+module_exit(pl330_exit);
+
+MODULE_AUTHOR("Jaswinder Singh <jassi.brar@samsung.com>");
+MODULE_DESCRIPTION("Driver for PL330 DMA Controller");
+MODULE_LICENSE("GPL");