From mboxrd@z Thu Jan 1 00:00:00 1970 From: kmpark@infradead.org (Kyungmin Park) Date: Tue, 7 Jun 2011 17:00:38 +0900 Subject: [PATCH] ARM:SAMSUNG: Move S3C DMA driver to drivers/dma In-Reply-To: <1307432901-22781-1-git-send-email-alim.akhtar@samsung.com> References: <1307432901-22781-1-git-send-email-alim.akhtar@samsung.com> Message-ID: To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org Hi, As I know there's are PL330 DMA implementation by MODULE_AUTHOR("Jaswinder Singh "); Doesn't it better to use the generic PL330 instead of Samsung specific PL330 implementation? As I remember Jassi has a plan to use generic one? Thank you, Kyungmin Park On Tue, Jun 7, 2011 at 4:48 PM, root wrote: > 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"); > -- > 1.7.2.3 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in > the body of a message to majordomo at vger.kernel.org > More majordomo info at ?http://vger.kernel.org/majordomo-info.html >