[PATCH] ARM:SAMSUNG: Move S3C DMA driver to drivers/dma

root alim.akhtar at samsung.com
Tue Jun 7 03:48:21 EDT 2011


Signed-off-by: alim.akhtar <alim.akhtar at samsung.com>
---
 arch/arm/configs/exynos4_defconfig |    1 +
 arch/arm/configs/s5p64x0_defconfig |    1 +
 arch/arm/configs/s5pc100_defconfig |    1 +
 arch/arm/configs/s5pv210_defconfig |    1 +
 arch/arm/plat-samsung/Kconfig      |    6 -
 arch/arm/plat-samsung/Makefile     |    2 -
 arch/arm/plat-samsung/s3c-pl330.c  | 1244 ------------------------------------
 drivers/dma/Kconfig                |    8 +
 drivers/dma/Makefile               |    2 +
 drivers/dma/s3c-pl330.c            | 1244 ++++++++++++++++++++++++++++++++++++
 10 files changed, 1258 insertions(+), 1252 deletions(-)
 delete mode 100644 arch/arm/plat-samsung/s3c-pl330.c
 create mode 100644 drivers/dma/s3c-pl330.c

diff --git a/arch/arm/configs/exynos4_defconfig b/arch/arm/configs/exynos4_defconfig
index da53ff3..6421074 100644
--- a/arch/arm/configs/exynos4_defconfig
+++ b/arch/arm/configs/exynos4_defconfig
@@ -37,6 +37,7 @@ CONFIG_SERIAL_SAMSUNG=y
 CONFIG_SERIAL_SAMSUNG_CONSOLE=y
 CONFIG_HW_RANDOM=y
 CONFIG_I2C=y
+CONFIG_DMADEVICES=y
 # CONFIG_HWMON is not set
 # CONFIG_MFD_SUPPORT is not set
 # CONFIG_HID_SUPPORT is not set
diff --git a/arch/arm/configs/s5p64x0_defconfig b/arch/arm/configs/s5p64x0_defconfig
index ad6b61b..9340ffc 100644
--- a/arch/arm/configs/s5p64x0_defconfig
+++ b/arch/arm/configs/s5p64x0_defconfig
@@ -31,6 +31,7 @@ CONFIG_SERIAL_8250_NR_UARTS=3
 CONFIG_SERIAL_SAMSUNG=y
 CONFIG_SERIAL_SAMSUNG_CONSOLE=y
 CONFIG_HW_RANDOM=y
+CONFIG_DMADEVICES=y
 # CONFIG_HWMON is not set
 CONFIG_DISPLAY_SUPPORT=y
 # CONFIG_VGA_CONSOLE is not set
diff --git a/arch/arm/configs/s5pc100_defconfig b/arch/arm/configs/s5pc100_defconfig
index 41bafc9..694ef97 100644
--- a/arch/arm/configs/s5pc100_defconfig
+++ b/arch/arm/configs/s5pc100_defconfig
@@ -20,6 +20,7 @@ CONFIG_SERIAL_SAMSUNG_CONSOLE=y
 CONFIG_HW_RANDOM=y
 CONFIG_I2C=y
 CONFIG_I2C_CHARDEV=y
+CONFIG_DMADEVICES=y
 # CONFIG_VGA_CONSOLE is not set
 CONFIG_MMC=y
 CONFIG_MMC_DEBUG=y
diff --git a/arch/arm/configs/s5pv210_defconfig b/arch/arm/configs/s5pv210_defconfig
index fa98990..0013593 100644
--- a/arch/arm/configs/s5pv210_defconfig
+++ b/arch/arm/configs/s5pv210_defconfig
@@ -37,6 +37,7 @@ CONFIG_SERIAL_8250=y
 CONFIG_SERIAL_SAMSUNG=y
 CONFIG_SERIAL_SAMSUNG_CONSOLE=y
 CONFIG_HW_RANDOM=y
+CONFIG_DMADEVICES=y
 # CONFIG_HWMON is not set
 # CONFIG_VGA_CONSOLE is not set
 # CONFIG_HID_SUPPORT is not set
diff --git a/arch/arm/plat-samsung/Kconfig b/arch/arm/plat-samsung/Kconfig
index 4d79519..9607ac4 100644
--- a/arch/arm/plat-samsung/Kconfig
+++ b/arch/arm/plat-samsung/Kconfig
@@ -294,12 +294,6 @@ config S3C_DMA
 	help
 	  Internal configuration for S3C DMA core
 
-config S3C_PL330_DMA
-	bool
-	select PL330
-	help
-	  S3C DMA API Driver for PL330 DMAC.
-
 comment "Power management"
 
 config SAMSUNG_PM_DEBUG
diff --git a/arch/arm/plat-samsung/Makefile b/arch/arm/plat-samsung/Makefile
index 53eb15b..895c697 100644
--- a/arch/arm/plat-samsung/Makefile
+++ b/arch/arm/plat-samsung/Makefile
@@ -64,8 +64,6 @@ obj-$(CONFIG_SAMSUNG_DEV_PWM)	+= dev-pwm.o
 
 obj-$(CONFIG_S3C_DMA)		+= dma.o
 
-obj-$(CONFIG_S3C_PL330_DMA)	+= s3c-pl330.o
-
 # PM support
 
 obj-$(CONFIG_PM)		+= pm.o
diff --git a/arch/arm/plat-samsung/s3c-pl330.c b/arch/arm/plat-samsung/s3c-pl330.c
deleted file mode 100644
index f85638c..0000000
--- a/arch/arm/plat-samsung/s3c-pl330.c
+++ /dev/null
@@ -1,1244 +0,0 @@
-/* linux/arch/arm/plat-samsung/s3c-pl330.c
- *
- * Copyright (C) 2010 Samsung Electronics Co. Ltd.
- *	Jaswinder Singh <jassi.brar at 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 <linux/clk.h>
-#include <linux/err.h>
-
-#include <asm/hardware/pl330.h>
-
-#include <plat/s3c-pl330-pdata.h>
-
-/**
- * struct s3c_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.
- * @clk: Pointer of DMAC operation clock.
- */
-struct s3c_pl330_dmac {
-	unsigned		busy_chan;
-	enum dma_ch		*peri;
-	struct list_head	node;
-	struct pl330_info	*pi;
-	struct kmem_cache	*kmcache;
-	struct clk		*clk;
-};
-
-/**
- * struct s3c_pl330_xfer - A request submitted by S3C 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 s3c_pl330_xfer {
-	void			*token;
-	struct list_head	node;
-	struct pl330_xfer	px;
-	struct s3c_pl330_chan	*chan;
-};
-
-/**
- * struct s3c_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 s3c2410_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 executed.
- * @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 s3c_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];
-	s3c2410_dma_cbfn_t		callback_fn;
-	struct pl330_reqcfg		rqcfg;
-	struct s3c_pl330_xfer		*xfer_head;
-	struct s3c_pl330_dmac		*dmac;
-	struct s3c2410_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 s3c_pl330_chan *id_to_chan(const enum dma_ch id)
-{
-	struct s3c_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 s3c_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 s3c_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 s3c_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 s3c_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 s3c_pl330_dmac *dmac)
-{
-	enum dma_ch *id = dmac->peri;
-	struct s3c_pl330_dmac *d;
-	struct s3c_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 s3c_pl330_dmac *dmac,
-		struct s3c_pl330_chan *ch)
-{
-	struct pl330_info *pi = dmac->pi;
-	enum dma_ch *id = dmac->peri;
-	struct s3c_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 s3c_pl330_dmac *map_chan_to_dmac(struct s3c_pl330_chan *ch)
-{
-	struct s3c_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 s3c_pl330_chan *chan_acquire(const enum dma_ch id)
-{
-	struct s3c_pl330_chan *ch = id_to_chan(id);
-	struct s3c_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 s3c_pl330_xfer *xfer)
-{
-	struct s3c_pl330_xfer *t;
-	struct s3c_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 s3c_pl330_xfer, node);
-	else
-		t = list_entry(xfer->node.next,
-				struct s3c_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 s3c_pl330_xfer *get_from_queue(struct s3c_pl330_chan *ch,
-		int pluck)
-{
-	struct s3c_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 s3c_pl330_xfer, node);
-	else
-		ch->xfer_head = list_entry(xfer->node.next,
-					struct s3c_pl330_xfer, node);
-
-	if (pluck || !(ch->options & S3C2410_DMAF_CIRCULAR))
-		del_from_queue(xfer);
-
-	return xfer;
-}
-
-static inline void add_to_queue(struct s3c_pl330_chan *ch,
-		struct s3c_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 & S3C2410_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 s3c_pl330_xfer *xfer,
-		enum s3c2410_dma_buffresult res, int ffree)
-{
-	struct s3c_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 & S3C2410_DMAF_CIRCULAR))
-		kmem_cache_free(ch->dmac->kmcache, xfer);
-}
-
-static inline int s3c_pl330_submit(struct s3c_pl330_chan *ch,
-		struct pl330_req *r)
-{
-	struct s3c_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, S3C2410_RES_ERR, 0);
-		}
-	}
-
-	return ret;
-}
-
-static void s3c_pl330_rq(struct s3c_pl330_chan *ch,
-	struct pl330_req *r, enum pl330_op_err err)
-{
-	unsigned long flags;
-	struct s3c_pl330_xfer *xfer;
-	struct pl330_xfer *xl = r->x;
-	enum s3c2410_dma_buffresult res;
-
-	spin_lock_irqsave(&res_lock, flags);
-
-	r->x = NULL;
-
-	s3c_pl330_submit(ch, r);
-
-	spin_unlock_irqrestore(&res_lock, flags);
-
-	/* Map result to S3C DMA API */
-	if (err == PL330_ERR_NONE)
-		res = S3C2410_RES_OK;
-	else if (err == PL330_ERR_ABORT)
-		res = S3C2410_RES_ABORT;
-	else
-		res = S3C2410_RES_ERR;
-
-	/* If last request had some xfer */
-	if (xl) {
-		xfer = container_of(xl, struct s3c_pl330_xfer, px);
-		_finish_off(xfer, res, 0);
-	} else {
-		dev_info(ch->dmac->pi->dev, "%s:%d No Xfer?!\n",
-			__func__, __LINE__);
-	}
-}
-
-static void s3c_pl330_rq0(void *token, enum pl330_op_err err)
-{
-	struct pl330_req *r = token;
-	struct s3c_pl330_chan *ch = container_of(r,
-					struct s3c_pl330_chan, req[0]);
-	s3c_pl330_rq(ch, r, err);
-}
-
-static void s3c_pl330_rq1(void *token, enum pl330_op_err err)
-{
-	struct pl330_req *r = token;
-	struct s3c_pl330_chan *ch = container_of(r,
-					struct s3c_pl330_chan, req[1]);
-	s3c_pl330_rq(ch, r, err);
-}
-
-/* Release an acquired channel */
-static void chan_release(struct s3c_pl330_chan *ch)
-{
-	struct s3c_pl330_dmac *dmac;
-
-	if (chan_free(ch))
-		return;
-
-	dmac = ch->dmac;
-	ch->dmac = NULL;
-	dmac->busy_chan--;
-}
-
-int s3c2410_dma_ctrl(enum dma_ch id, enum s3c2410_chan_op op)
-{
-	struct s3c_pl330_xfer *xfer;
-	enum pl330_chan_op pl330op;
-	struct s3c_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 S3C2410_DMAOP_START:
-		/* Make sure both reqs are enqueued */
-		idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
-		s3c_pl330_submit(ch, &ch->req[idx]);
-		s3c_pl330_submit(ch, &ch->req[1 - idx]);
-		pl330op = PL330_OP_START;
-		break;
-
-	case S3C2410_DMAOP_STOP:
-		pl330op = PL330_OP_ABORT;
-		break;
-
-	case S3C2410_DMAOP_FLUSH:
-		pl330op = PL330_OP_FLUSH;
-		break;
-
-	case S3C2410_DMAOP_PAUSE:
-	case S3C2410_DMAOP_RESUME:
-	case S3C2410_DMAOP_TIMEOUT:
-	case S3C2410_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 s3c_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, S3C2410_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 s3c_pl330_xfer, px);
-
-			del_from_queue(xfer);
-
-			ch->req[1 - idx].x = NULL;
-
-			spin_unlock_irqrestore(&res_lock, flags);
-			_finish_off(xfer, S3C2410_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, S3C2410_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(s3c2410_dma_ctrl);
-
-int s3c2410_dma_enqueue(enum dma_ch id, void *token,
-			dma_addr_t addr, int size)
-{
-	struct s3c_pl330_chan *ch;
-	struct s3c_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 S3C 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)
-		s3c_pl330_submit(ch, &ch->req[idx]);
-	else
-		s3c_pl330_submit(ch, &ch->req[1 - idx]);
-
-	spin_unlock_irqrestore(&res_lock, flags);
-
-	if (ch->options & S3C2410_DMAF_AUTOSTART)
-		s3c2410_dma_ctrl(id, S3C2410_DMAOP_START);
-
-	return 0;
-
-enq_exit:
-	spin_unlock_irqrestore(&res_lock, flags);
-
-	return ret;
-}
-EXPORT_SYMBOL(s3c2410_dma_enqueue);
-
-int s3c2410_dma_request(enum dma_ch id,
-			struct s3c2410_dma_client *client,
-			void *dev)
-{
-	struct s3c_pl330_dmac *dmac;
-	struct s3c_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 = s3c_pl330_rq0;
-	ch->req[1].token = &ch->req[1];
-	ch->req[1].xfer_cb = s3c_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(s3c2410_dma_request);
-
-int s3c2410_dma_free(enum dma_ch id, struct s3c2410_dma_client *client)
-{
-	struct s3c_pl330_chan *ch;
-	struct s3c_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 s3c_pl330_xfer, px);
-
-		ch->req[idx].x = NULL;
-		del_from_queue(xfer);
-
-		spin_unlock_irqrestore(&res_lock, flags);
-		_finish_off(xfer, S3C2410_RES_ABORT, 1);
-		spin_lock_irqsave(&res_lock, flags);
-	}
-
-	if (ch->req[1 - idx].x) {
-		xfer = container_of(ch->req[1 - idx].x,
-				struct s3c_pl330_xfer, px);
-
-		ch->req[1 - idx].x = NULL;
-		del_from_queue(xfer);
-
-		spin_unlock_irqrestore(&res_lock, flags);
-		_finish_off(xfer, S3C2410_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, S3C2410_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(s3c2410_dma_free);
-
-int s3c2410_dma_config(enum dma_ch id, int xferunit)
-{
-	struct s3c_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(s3c2410_dma_config);
-
-/* Options that are supported by this driver */
-#define S3C_PL330_FLAGS (S3C2410_DMAF_CIRCULAR | S3C2410_DMAF_AUTOSTART)
-
-int s3c2410_dma_setflags(enum dma_ch id, unsigned int options)
-{
-	struct s3c_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 & ~(S3C_PL330_FLAGS))
-		ret = -EINVAL;
-	else
-		ch->options = options;
-
-	spin_unlock_irqrestore(&res_lock, flags);
-
-	return 0;
-}
-EXPORT_SYMBOL(s3c2410_dma_setflags);
-
-int s3c2410_dma_set_buffdone_fn(enum dma_ch id, s3c2410_dma_cbfn_t rtn)
-{
-	struct s3c_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(s3c2410_dma_set_buffdone_fn);
-
-int s3c2410_dma_devconfig(enum dma_ch id, enum s3c2410_dmasrc source,
-			  unsigned long address)
-{
-	struct s3c_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 S3C2410_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 S3C2410_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(s3c2410_dma_devconfig);
-
-int s3c2410_dma_getposition(enum dma_ch id, dma_addr_t *src, dma_addr_t *dst)
-{
-	struct s3c_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(s3c2410_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 s3c_pl330_dmac *s3c_pl330_dmac;
-	struct s3c_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;
-
-	/* Allocate a new DMAC */
-	s3c_pl330_dmac = kmalloc(sizeof(*s3c_pl330_dmac), GFP_KERNEL);
-	if (!s3c_pl330_dmac) {
-		ret = -ENOMEM;
-		goto probe_err5;
-	}
-
-	/* Get operation clock and enable it */
-	s3c_pl330_dmac->clk = clk_get(&pdev->dev, "pdma");
-	if (IS_ERR(s3c_pl330_dmac->clk)) {
-		dev_err(&pdev->dev, "Cannot get operation clock.\n");
-		ret = -EINVAL;
-		goto probe_err6;
-	}
-	clk_enable(s3c_pl330_dmac->clk);
-
-	ret = pl330_add(pl330_info);
-	if (ret)
-		goto probe_err7;
-
-	/* Hook the info */
-	s3c_pl330_dmac->pi = pl330_info;
-
-	/* No busy channels */
-	s3c_pl330_dmac->busy_chan = 0;
-
-	s3c_pl330_dmac->kmcache = kmem_cache_create(dev_name(&pdev->dev),
-				sizeof(struct s3c_pl330_xfer), 0, 0, NULL);
-
-	if (!s3c_pl330_dmac->kmcache) {
-		ret = -ENOMEM;
-		goto probe_err8;
-	}
-
-	/* Get the list of peripherals */
-	s3c_pl330_dmac->peri = pl330pd->peri;
-
-	/* Attach to the list of DMACs */
-	list_add_tail(&s3c_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 (s3c_pl330_dmac->peri[i] != DMACH_MAX)
-			chan_add(s3c_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_err8:
-	pl330_del(pl330_info);
-probe_err7:
-	clk_disable(s3c_pl330_dmac->clk);
-	clk_put(s3c_pl330_dmac->clk);
-probe_err6:
-	kfree(s3c_pl330_dmac);
-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 s3c_pl330_dmac *dmac, *d;
-	struct s3c_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);
-			s3c2410_dma_free(ch->id, ch->client);
-			spin_lock_irqsave(&res_lock, flags);
-			list_del(&ch->node);
-			kfree(ch);
-		}
-	}
-
-	/* Disable operation clock */
-	clk_disable(dmac->clk);
-	clk_put(dmac->clk);
-
-	/* 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	= "s3c-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 at samsung.com>");
-MODULE_DESCRIPTION("Driver for PL330 DMA Controller");
-MODULE_LICENSE("GPL");
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 25cf327..9a023e6 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -199,6 +199,14 @@ config PL330_DMA
 	  You need to provide platform specific settings via
 	  platform_data for a dma-pl330 device.
 
+config S3C_PL330_DMA
+	bool "S3C DMA API Driver for PL330 DMAC"
+	select DMA_ENGINE
+	select PL330
+	depends on PLAT_SAMSUNG
+	help
+	  S3C DMA API Driver for PL330 DMAC.
+
 config PCH_DMA
 	tristate "Intel EG20T PCH / OKI Semi IOH(ML7213/ML7223) DMA support"
 	depends on PCI && X86
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 836095a..6e81b5d 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -25,3 +25,4 @@ obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o
 obj-$(CONFIG_PL330_DMA) += pl330.o
 obj-$(CONFIG_PCH_DMA) += pch_dma.o
 obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o
+obj-$(CONFIG_S3C_PL330_DMA)	+= s3c-pl330.o
diff --git a/drivers/dma/s3c-pl330.c b/drivers/dma/s3c-pl330.c
new file mode 100644
index 0000000..f85638c
--- /dev/null
+++ b/drivers/dma/s3c-pl330.c
@@ -0,0 +1,1244 @@
+/* linux/arch/arm/plat-samsung/s3c-pl330.c
+ *
+ * Copyright (C) 2010 Samsung Electronics Co. Ltd.
+ *	Jaswinder Singh <jassi.brar at 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 <linux/clk.h>
+#include <linux/err.h>
+
+#include <asm/hardware/pl330.h>
+
+#include <plat/s3c-pl330-pdata.h>
+
+/**
+ * struct s3c_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.
+ * @clk: Pointer of DMAC operation clock.
+ */
+struct s3c_pl330_dmac {
+	unsigned		busy_chan;
+	enum dma_ch		*peri;
+	struct list_head	node;
+	struct pl330_info	*pi;
+	struct kmem_cache	*kmcache;
+	struct clk		*clk;
+};
+
+/**
+ * struct s3c_pl330_xfer - A request submitted by S3C 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 s3c_pl330_xfer {
+	void			*token;
+	struct list_head	node;
+	struct pl330_xfer	px;
+	struct s3c_pl330_chan	*chan;
+};
+
+/**
+ * struct s3c_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 s3c2410_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 executed.
+ * @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 s3c_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];
+	s3c2410_dma_cbfn_t		callback_fn;
+	struct pl330_reqcfg		rqcfg;
+	struct s3c_pl330_xfer		*xfer_head;
+	struct s3c_pl330_dmac		*dmac;
+	struct s3c2410_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 s3c_pl330_chan *id_to_chan(const enum dma_ch id)
+{
+	struct s3c_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 s3c_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 s3c_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 s3c_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 s3c_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 s3c_pl330_dmac *dmac)
+{
+	enum dma_ch *id = dmac->peri;
+	struct s3c_pl330_dmac *d;
+	struct s3c_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 s3c_pl330_dmac *dmac,
+		struct s3c_pl330_chan *ch)
+{
+	struct pl330_info *pi = dmac->pi;
+	enum dma_ch *id = dmac->peri;
+	struct s3c_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 s3c_pl330_dmac *map_chan_to_dmac(struct s3c_pl330_chan *ch)
+{
+	struct s3c_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 s3c_pl330_chan *chan_acquire(const enum dma_ch id)
+{
+	struct s3c_pl330_chan *ch = id_to_chan(id);
+	struct s3c_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 s3c_pl330_xfer *xfer)
+{
+	struct s3c_pl330_xfer *t;
+	struct s3c_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 s3c_pl330_xfer, node);
+	else
+		t = list_entry(xfer->node.next,
+				struct s3c_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 s3c_pl330_xfer *get_from_queue(struct s3c_pl330_chan *ch,
+		int pluck)
+{
+	struct s3c_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 s3c_pl330_xfer, node);
+	else
+		ch->xfer_head = list_entry(xfer->node.next,
+					struct s3c_pl330_xfer, node);
+
+	if (pluck || !(ch->options & S3C2410_DMAF_CIRCULAR))
+		del_from_queue(xfer);
+
+	return xfer;
+}
+
+static inline void add_to_queue(struct s3c_pl330_chan *ch,
+		struct s3c_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 & S3C2410_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 s3c_pl330_xfer *xfer,
+		enum s3c2410_dma_buffresult res, int ffree)
+{
+	struct s3c_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 & S3C2410_DMAF_CIRCULAR))
+		kmem_cache_free(ch->dmac->kmcache, xfer);
+}
+
+static inline int s3c_pl330_submit(struct s3c_pl330_chan *ch,
+		struct pl330_req *r)
+{
+	struct s3c_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, S3C2410_RES_ERR, 0);
+		}
+	}
+
+	return ret;
+}
+
+static void s3c_pl330_rq(struct s3c_pl330_chan *ch,
+	struct pl330_req *r, enum pl330_op_err err)
+{
+	unsigned long flags;
+	struct s3c_pl330_xfer *xfer;
+	struct pl330_xfer *xl = r->x;
+	enum s3c2410_dma_buffresult res;
+
+	spin_lock_irqsave(&res_lock, flags);
+
+	r->x = NULL;
+
+	s3c_pl330_submit(ch, r);
+
+	spin_unlock_irqrestore(&res_lock, flags);
+
+	/* Map result to S3C DMA API */
+	if (err == PL330_ERR_NONE)
+		res = S3C2410_RES_OK;
+	else if (err == PL330_ERR_ABORT)
+		res = S3C2410_RES_ABORT;
+	else
+		res = S3C2410_RES_ERR;
+
+	/* If last request had some xfer */
+	if (xl) {
+		xfer = container_of(xl, struct s3c_pl330_xfer, px);
+		_finish_off(xfer, res, 0);
+	} else {
+		dev_info(ch->dmac->pi->dev, "%s:%d No Xfer?!\n",
+			__func__, __LINE__);
+	}
+}
+
+static void s3c_pl330_rq0(void *token, enum pl330_op_err err)
+{
+	struct pl330_req *r = token;
+	struct s3c_pl330_chan *ch = container_of(r,
+					struct s3c_pl330_chan, req[0]);
+	s3c_pl330_rq(ch, r, err);
+}
+
+static void s3c_pl330_rq1(void *token, enum pl330_op_err err)
+{
+	struct pl330_req *r = token;
+	struct s3c_pl330_chan *ch = container_of(r,
+					struct s3c_pl330_chan, req[1]);
+	s3c_pl330_rq(ch, r, err);
+}
+
+/* Release an acquired channel */
+static void chan_release(struct s3c_pl330_chan *ch)
+{
+	struct s3c_pl330_dmac *dmac;
+
+	if (chan_free(ch))
+		return;
+
+	dmac = ch->dmac;
+	ch->dmac = NULL;
+	dmac->busy_chan--;
+}
+
+int s3c2410_dma_ctrl(enum dma_ch id, enum s3c2410_chan_op op)
+{
+	struct s3c_pl330_xfer *xfer;
+	enum pl330_chan_op pl330op;
+	struct s3c_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 S3C2410_DMAOP_START:
+		/* Make sure both reqs are enqueued */
+		idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
+		s3c_pl330_submit(ch, &ch->req[idx]);
+		s3c_pl330_submit(ch, &ch->req[1 - idx]);
+		pl330op = PL330_OP_START;
+		break;
+
+	case S3C2410_DMAOP_STOP:
+		pl330op = PL330_OP_ABORT;
+		break;
+
+	case S3C2410_DMAOP_FLUSH:
+		pl330op = PL330_OP_FLUSH;
+		break;
+
+	case S3C2410_DMAOP_PAUSE:
+	case S3C2410_DMAOP_RESUME:
+	case S3C2410_DMAOP_TIMEOUT:
+	case S3C2410_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 s3c_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, S3C2410_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 s3c_pl330_xfer, px);
+
+			del_from_queue(xfer);
+
+			ch->req[1 - idx].x = NULL;
+
+			spin_unlock_irqrestore(&res_lock, flags);
+			_finish_off(xfer, S3C2410_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, S3C2410_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(s3c2410_dma_ctrl);
+
+int s3c2410_dma_enqueue(enum dma_ch id, void *token,
+			dma_addr_t addr, int size)
+{
+	struct s3c_pl330_chan *ch;
+	struct s3c_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 S3C 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)
+		s3c_pl330_submit(ch, &ch->req[idx]);
+	else
+		s3c_pl330_submit(ch, &ch->req[1 - idx]);
+
+	spin_unlock_irqrestore(&res_lock, flags);
+
+	if (ch->options & S3C2410_DMAF_AUTOSTART)
+		s3c2410_dma_ctrl(id, S3C2410_DMAOP_START);
+
+	return 0;
+
+enq_exit:
+	spin_unlock_irqrestore(&res_lock, flags);
+
+	return ret;
+}
+EXPORT_SYMBOL(s3c2410_dma_enqueue);
+
+int s3c2410_dma_request(enum dma_ch id,
+			struct s3c2410_dma_client *client,
+			void *dev)
+{
+	struct s3c_pl330_dmac *dmac;
+	struct s3c_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 = s3c_pl330_rq0;
+	ch->req[1].token = &ch->req[1];
+	ch->req[1].xfer_cb = s3c_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(s3c2410_dma_request);
+
+int s3c2410_dma_free(enum dma_ch id, struct s3c2410_dma_client *client)
+{
+	struct s3c_pl330_chan *ch;
+	struct s3c_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 s3c_pl330_xfer, px);
+
+		ch->req[idx].x = NULL;
+		del_from_queue(xfer);
+
+		spin_unlock_irqrestore(&res_lock, flags);
+		_finish_off(xfer, S3C2410_RES_ABORT, 1);
+		spin_lock_irqsave(&res_lock, flags);
+	}
+
+	if (ch->req[1 - idx].x) {
+		xfer = container_of(ch->req[1 - idx].x,
+				struct s3c_pl330_xfer, px);
+
+		ch->req[1 - idx].x = NULL;
+		del_from_queue(xfer);
+
+		spin_unlock_irqrestore(&res_lock, flags);
+		_finish_off(xfer, S3C2410_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, S3C2410_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(s3c2410_dma_free);
+
+int s3c2410_dma_config(enum dma_ch id, int xferunit)
+{
+	struct s3c_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(s3c2410_dma_config);
+
+/* Options that are supported by this driver */
+#define S3C_PL330_FLAGS (S3C2410_DMAF_CIRCULAR | S3C2410_DMAF_AUTOSTART)
+
+int s3c2410_dma_setflags(enum dma_ch id, unsigned int options)
+{
+	struct s3c_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 & ~(S3C_PL330_FLAGS))
+		ret = -EINVAL;
+	else
+		ch->options = options;
+
+	spin_unlock_irqrestore(&res_lock, flags);
+
+	return 0;
+}
+EXPORT_SYMBOL(s3c2410_dma_setflags);
+
+int s3c2410_dma_set_buffdone_fn(enum dma_ch id, s3c2410_dma_cbfn_t rtn)
+{
+	struct s3c_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(s3c2410_dma_set_buffdone_fn);
+
+int s3c2410_dma_devconfig(enum dma_ch id, enum s3c2410_dmasrc source,
+			  unsigned long address)
+{
+	struct s3c_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 S3C2410_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 S3C2410_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(s3c2410_dma_devconfig);
+
+int s3c2410_dma_getposition(enum dma_ch id, dma_addr_t *src, dma_addr_t *dst)
+{
+	struct s3c_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(s3c2410_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 s3c_pl330_dmac *s3c_pl330_dmac;
+	struct s3c_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;
+
+	/* Allocate a new DMAC */
+	s3c_pl330_dmac = kmalloc(sizeof(*s3c_pl330_dmac), GFP_KERNEL);
+	if (!s3c_pl330_dmac) {
+		ret = -ENOMEM;
+		goto probe_err5;
+	}
+
+	/* Get operation clock and enable it */
+	s3c_pl330_dmac->clk = clk_get(&pdev->dev, "pdma");
+	if (IS_ERR(s3c_pl330_dmac->clk)) {
+		dev_err(&pdev->dev, "Cannot get operation clock.\n");
+		ret = -EINVAL;
+		goto probe_err6;
+	}
+	clk_enable(s3c_pl330_dmac->clk);
+
+	ret = pl330_add(pl330_info);
+	if (ret)
+		goto probe_err7;
+
+	/* Hook the info */
+	s3c_pl330_dmac->pi = pl330_info;
+
+	/* No busy channels */
+	s3c_pl330_dmac->busy_chan = 0;
+
+	s3c_pl330_dmac->kmcache = kmem_cache_create(dev_name(&pdev->dev),
+				sizeof(struct s3c_pl330_xfer), 0, 0, NULL);
+
+	if (!s3c_pl330_dmac->kmcache) {
+		ret = -ENOMEM;
+		goto probe_err8;
+	}
+
+	/* Get the list of peripherals */
+	s3c_pl330_dmac->peri = pl330pd->peri;
+
+	/* Attach to the list of DMACs */
+	list_add_tail(&s3c_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 (s3c_pl330_dmac->peri[i] != DMACH_MAX)
+			chan_add(s3c_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_err8:
+	pl330_del(pl330_info);
+probe_err7:
+	clk_disable(s3c_pl330_dmac->clk);
+	clk_put(s3c_pl330_dmac->clk);
+probe_err6:
+	kfree(s3c_pl330_dmac);
+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 s3c_pl330_dmac *dmac, *d;
+	struct s3c_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);
+			s3c2410_dma_free(ch->id, ch->client);
+			spin_lock_irqsave(&res_lock, flags);
+			list_del(&ch->node);
+			kfree(ch);
+		}
+	}
+
+	/* Disable operation clock */
+	clk_disable(dmac->clk);
+	clk_put(dmac->clk);
+
+	/* 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	= "s3c-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 at samsung.com>");
+MODULE_DESCRIPTION("Driver for PL330 DMA Controller");
+MODULE_LICENSE("GPL");
-- 
1.7.2.3




More information about the linux-arm-kernel mailing list