/* 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");