From patchwork Tue Jun 7 07:48:21 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alim Akhtar X-Patchwork-Id: 855372 Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) by demeter1.kernel.org (8.14.4/8.14.3) with ESMTP id p577pGbA031488 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Tue, 7 Jun 2011 07:51:37 GMT Received: from canuck.infradead.org ([2001:4978:20e::1]) by merlin.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1QTr3z-0005wu-TM; Tue, 07 Jun 2011 07:51:00 +0000 Received: from localhost ([127.0.0.1] helo=canuck.infradead.org) by canuck.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1QTr3z-000451-GD; Tue, 07 Jun 2011 07:50:59 +0000 Received: from mailout3.samsung.com ([203.254.224.33]) by canuck.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1QTr3q-00044Y-8A for linux-arm-kernel@lists.infradead.org; Tue, 07 Jun 2011 07:50:57 +0000 Received: from epcpsbgm2.samsung.com (mailout3.samsung.com [203.254.224.33]) by mailout3.samsung.com (Oracle Communications Messaging Exchange Server 7u4-19.01 64bit (built Sep 7 2010)) with ESMTP id <0LME00KU2TSB1N50@mailout3.samsung.com> for linux-arm-kernel@lists.infradead.org; Tue, 07 Jun 2011 16:50:44 +0900 (KST) X-AuditID: cbfee61b-b7c62ae0000056ed-3c-4dedd854f6be Received: from epmmp1.local.host ( [203.254.227.16]) by epcpsbgm2.samsung.com (MMPCPMTA) with SMTP id D5.F5.22253.458DDED4; Tue, 07 Jun 2011 16:50:44 +0900 (KST) Received: from alim.sisodomain.com (unknown [107.108.73.28]) by mmp1.samsung.com (Oracle Communications Messaging Exchange Server 7u4-19.01 64bit (built Sep 7 2010)) with ESMTPA id <0LME008DRTSC4000@mmp1.samsung.com> for linux-arm-kernel@lists.infradead.org; Tue, 07 Jun 2011 16:50:44 +0900 (KST) From: root To: linux-arm-kernel@lists.infradead.org Subject: [PATCH] ARM:SAMSUNG: Move S3C DMA driver to drivers/dma Date: Tue, 07 Jun 2011 13:18:21 +0530 Message-id: <1307432901-22781-1-git-send-email-alim.akhtar@samsung.com> X-Mailer: git-send-email 1.7.2.3 X-Brightmail-Tracker: AAAAAA== X-CRM114-Version: 20090807-BlameThorstenAndJenny ( TRE 0.7.6 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20110607_035051_377828_F3BBBAC2 X-CRM114-Status: GOOD ( 34.52 ) X-Spam-Score: -2.3 (--) X-Spam-Report: SpamAssassin version 3.3.1 on canuck.infradead.org summary: Content analysis details: (-2.3 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at http://www.dnswl.org/, medium trust [203.254.224.33 listed in list.dnswl.org] 0.0 T_FRT_FREE BODY: ReplaceTags: Free 0.0 RFC_ABUSE_POST Both abuse and postmaster missing on sender domain Cc: linux-samsung-soc@vger.kernel.org, kgene.kim@samsung.com X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: linux-arm-kernel-bounces@lists.infradead.org Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.6 (demeter1.kernel.org [140.211.167.41]); Tue, 07 Jun 2011 07:51:38 +0000 (UTC) Signed-off-by: alim.akhtar --- 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 - * - * 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 -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -/** - * 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 "); -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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +/** + * 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 "); +MODULE_DESCRIPTION("Driver for PL330 DMA Controller"); +MODULE_LICENSE("GPL");