From mboxrd@z Thu Jan 1 00:00:00 1970 From: ben-linux@fluff.org (Ben Dooks) Date: Mon, 17 May 2010 09:40:21 +0100 Subject: [PATCH 2/6] S3C: DMA: Add api driver for PL330 In-Reply-To: <1274060139-14290-1-git-send-email-jassi.brar@samsung.com> References: <1274060104-14224-1-git-send-email-jassi.brar@samsung.com> <1274060139-14290-1-git-send-email-jassi.brar@samsung.com> Message-ID: <20100517084021.GC6684@trinity.fluff.org> To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org On Mon, May 17, 2010 at 10:35:39AM +0900, Jassi Brar wrote: > Latest Samsung SoCs have one or more PL330 as their DMACs. This patch > implements the S3C DMA API for PL330 core driver. > > The design has been kept as generic as possible while keeping effort to > add support for new SoCs to the minimum possible level. > > Some of the salient features of this driver are:- > o Automatic scheduling of client requests onto DMAC if more than > one DMAC can reach the peripheral. Factors, such as current load > and number of exclusive but inactive peripherals that are > supported by the DMAC, are used to decide suitability of a DMAC > for a particular client. > o CIRCULAR buffer option is supported. > o The driver scales transparently with the number of DMACs and total > peripherals in the platform, since all peripherals are added to > the peripheral pool and DMACs to the controller pool. > > For most conservative use of memory, smallest driver size and best > performance, we don't employ legacy data structures of the S3C DMA API. > That should not have any affect since those data structures are completely > invisible to the DMA clients. > > Signed-off-by: Jassi Brar > --- > arch/arm/plat-samsung/Kconfig | 6 + > arch/arm/plat-samsung/Makefile | 2 + > arch/arm/plat-samsung/include/plat/s3c-dma-pl330.h | 87 ++ > .../plat-samsung/include/plat/s3c-pl330-pdata.h | 34 + > arch/arm/plat-samsung/s3c-pl330.c | 1229 ++++++++++++++++++++ > 5 files changed, 1358 insertions(+), 0 deletions(-) > create mode 100644 arch/arm/plat-samsung/include/plat/s3c-dma-pl330.h > create mode 100644 arch/arm/plat-samsung/include/plat/s3c-pl330-pdata.h > create mode 100644 arch/arm/plat-samsung/s3c-pl330.c > > diff --git a/arch/arm/plat-samsung/Kconfig b/arch/arm/plat-samsung/Kconfig > index d552c65..78b8b89 100644 > --- a/arch/arm/plat-samsung/Kconfig > +++ b/arch/arm/plat-samsung/Kconfig > @@ -198,6 +198,12 @@ 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 22c89d0..05718a3 100644 > --- a/arch/arm/plat-samsung/Makefile > +++ b/arch/arm/plat-samsung/Makefile > @@ -45,6 +45,8 @@ obj-$(CONFIG_S3C_DEV_NAND) += dev-nand.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/include/plat/s3c-dma-pl330.h b/arch/arm/plat-samsung/include/plat/s3c-dma-pl330.h > new file mode 100644 > index 0000000..7af9054 > --- /dev/null > +++ b/arch/arm/plat-samsung/include/plat/s3c-dma-pl330.h > @@ -0,0 +1,87 @@ > +/* > + * 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. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. > + */ > + > +#ifndef __S3C_DMA_PL330_H_ > +#define __S3C_DMA_PL330_H_ > + > +#define S3C2410_DMAF_AUTOSTART (1 << 0) > +#define S3C2410_DMAF_CIRCULAR (1 << 1) > + > +/* > + * PL330 can assign any channel to communicate with > + * any of the peripherals attched to the DMAC. > + * For the sake of consistency across client drivers, > + * We keep the channel names unchanged and only add > + * missing peripherals are added. > + * Order is not important since S3C PL330 API driver > + * use these just as IDs. > + */ > +enum dma_ch { > + DMACH_UART0_RX, > + DMACH_UART0_TX, > + DMACH_UART1_RX, > + DMACH_UART1_TX, > + DMACH_UART2_RX, > + DMACH_UART2_TX, > + DMACH_UART3_RX, > + DMACH_UART3_TX, > + DMACH_IRDA, > + DMACH_I2S0_RX, > + DMACH_I2S0_TX, > + DMACH_I2S0S_TX, > + DMACH_I2S1_RX, > + DMACH_I2S1_TX, > + DMACH_I2S2_RX, > + DMACH_I2S2_TX, > + DMACH_SPI0_RX, > + DMACH_SPI0_TX, > + DMACH_SPI1_RX, > + DMACH_SPI1_TX, > + DMACH_SPI2_RX, > + DMACH_SPI2_TX, > + DMACH_AC97_MICIN, > + DMACH_AC97_PCMIN, > + DMACH_AC97_PCMOUT, > + DMACH_EXTERNAL, > + DMACH_PWM, > + DMACH_SPDIF, > + DMACH_HSI_RX, > + DMACH_HSI_TX, > + DMACH_PCM0_TX, > + DMACH_PCM0_RX, > + DMACH_PCM1_TX, > + DMACH_PCM1_RX, > + DMACH_PCM2_TX, > + DMACH_PCM2_RX, > + DMACH_MSM_REQ3, > + DMACH_MSM_REQ2, > + DMACH_MSM_REQ1, > + DMACH_MSM_REQ0, > + /* END Marker, also used to denote a reserved channel */ > + DMACH_MAX, > +}; > + > +static inline bool s3c_dma_has_circular(void) > +{ > + return true; > +} > + > +#include > + > +#endif /* __S3C_DMA_PL330_H_ */ > diff --git a/arch/arm/plat-samsung/include/plat/s3c-pl330-pdata.h b/arch/arm/plat-samsung/include/plat/s3c-pl330-pdata.h > new file mode 100644 > index 0000000..ae4d683 > --- /dev/null > +++ b/arch/arm/plat-samsung/include/plat/s3c-pl330-pdata.h > @@ -0,0 +1,34 @@ > +/* linux/arch/arm/plat-samsung/include/plat/s3c-pl330-pdata.h > + * > + * 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. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. > + */ > + > +#ifndef __S3C_PL330_PDATA_H > +#define __S3C_PL330_PDATA_H > + > +#include > + > +/* > + * The platforms just need to provide this info > + * to the S3C DMA API driver for PL330. would be heklpful to describe what this information actually means, including how it helps to configure the hardware appropriately. > + */ > +struct s3c_pl330_platdata { > + enum dma_ch peri[32]; > +}; > + > +#endif /* __S3C_PL330_PDATA_H */ > diff --git a/arch/arm/plat-samsung/s3c-pl330.c b/arch/arm/plat-samsung/s3c-pl330.c > new file mode 100644 > index 0000000..f88690b > --- /dev/null > +++ b/arch/arm/plat-samsung/s3c-pl330.c > @@ -0,0 +1,1229 @@ > +/* 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. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > + > +#include > + > +struct s3c_pl330_dmac { > + struct pl330_info *pi; > + /* Number of channels currently busy */ > + unsigned busy_chan; > + /* To attach to the global list of DMACs */ > + struct list_head node; > + /* List of IDs of peripherals this DMAC can work with */ > + enum dma_ch *peri; > + /* Pool to quickly allocate xfers for all channels in the dmac */ > + struct kmem_cache *kmcache; > +}; I would prefer to see kerneldoc style documentation for this if possible. > +struct s3c_pl330_xfer { > + /* To attach to the list of xfers on a channel */ > + struct list_head node; > + /* Owner channel of this xfer */ > + struct s3c_pl330_chan *chan; > + /* Xfer ID provided by the client */ > + void *token; > + > + /* Xfer for PL330 core */ > + struct pl330_xfer px; > +}; > + > +struct s3c_pl330_chan { > + /* ID of the peripheral that this channel can communicate with */ > + enum dma_ch id; > + /* To attach to the global list of channels */ > + struct list_head node; > + /* Pointer to the last submitted pl330_req to PL330 core */ > + struct pl330_req *lrq; > + > + /* To manage list of xfers enqueued */ > + struct list_head xfer_list; > + /* Always points to the xfer at the front of queue */ > + struct s3c_pl330_xfer *xfer_head; > + > + /* Pointer to the DMAC that manages this channel, > + * NULL if the channel is available to be acquired. > + */ > + struct s3c_pl330_dmac *dmac; > + > + /* Token of a hardware channel thread of PL330 DMAC > + * NULL if the channel is available to be acquired. > + */ > + void *pl330_chan_id; > + > + /* Two requests to communicate with the PL330 engine */ > + struct pl330_req req[2]; > + > + /* Client of this channel > + * NULL if the channel is available to be acquired. > + */ > + struct s3c2410_dma_client *client; > + /* Callback function to the client */ > + s3c2410_dma_cbfn_t callback_fn; > + /* Options specified by the client */ > + unsigned int options; > + struct pl330_reqcfg rqcfg; > + /* Address provided via s3c2410_dma_devconfig */ > + unsigned long sdaddr; > +}; > + > +/* 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; > + should you init the list entry before adding it? > + 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. > + */ > +typedef u8 suit; > +#define MAX_SUIT 255 > +#define MIN_SUIT 0 hmm, would oprfer to see this as a simple integer. > +static suit 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; > + suit 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; > + suit 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); > +} couldn't xfer_head be infered from the transfer list? > +/* 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; > + > + ret = pl330_add(pl330_info); > + if (ret) > + goto probe_err5; > + > + /* Allocate a new DMAC */ > + s3c_pl330_dmac = kmalloc(sizeof(*s3c_pl330_dmac), GFP_KERNEL); > + if (!s3c_pl330_dmac) { > + ret = -ENOMEM; > + goto probe_err6; > + } > + > + /* 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(pdev->name, > + sizeof(struct s3c_pl330_xfer), 0, 0, NULL); > + > + if (!s3c_pl330_dmac->kmcache) { > + ret = -ENOMEM; > + goto probe_err7; > + } > + > + /* 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_err7: > + kfree(s3c_pl330_dmac); > +probe_err6: > + pl330_del(pl330_info); > +probe_err5: > + free_irq(irq, pl330_info); > +probe_err4: > +probe_err3: > + iounmap(pl330_info->base); > +probe_err2: > + release_mem_region(res->start, resource_size(res)); > +probe_err1: > + kfree(pl330_info); > + > + return ret; > +} > + > +static int pl330_remove(struct platform_device *pdev) > +{ > + struct 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); > + } > + } > + > + /* 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"); > -- > 1.6.2.5 > > > _______________________________________________ > linux-arm-kernel mailing list > linux-arm-kernel at lists.infradead.org > http://lists.infradead.org/mailman/listinfo/linux-arm-kernel -- -- Ben Q: What's a light-year? A: One-third less calories than a regular year.