aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/plat-samsung/s3c-pl330.c
diff options
context:
space:
mode:
authorJassi Brar <jassi.brar@samsung.com>2010-05-17 22:59:06 -0400
committerBen Dooks <ben-linux@fluff.org>2010-05-18 05:00:12 -0400
commitd800edebe33cac300ab1ff3b95e8a74d53e842d2 (patch)
tree58b34218288bae45714cbe4f0a7743b558f05e4a /arch/arm/plat-samsung/s3c-pl330.c
parent7ebd467551ed6ae200d7835a84bbda0dcadaa511 (diff)
S3C: DMA: Add api driver for PL330
Latest Samsung SoCs have one or more PL330 as their DMACs. This patch implements the S3C DMA API for PL330 core driver. The design has been kept as generic as possible while keeping effort to add support for new SoCs to the minimum possible level. Some of the salient features of this driver are:- o Automatic scheduling of client requests onto DMAC if more than one DMAC can reach the peripheral. Factors, such as current load and number of exclusive but inactive peripherals that are supported by the DMAC, are used to decide suitability of a DMAC for a particular client. o CIRCULAR buffer option is supported. o The driver scales transparently with the number of DMACs and total peripherals in the platform, since all peripherals are added to the peripheral pool and DMACs to the controller pool. For most conservative use of memory, smallest driver size and best performance, we don't employ legacy data structures of the S3C DMA API. That should not have any affect since those data structures are completely invisible to the DMA clients. Signed-off-by: Jassi Brar <jassi.brar@samsung.com> Signed-off-by: Ben Dooks <ben-linux@fluff.org>
Diffstat (limited to 'arch/arm/plat-samsung/s3c-pl330.c')
-rw-r--r--arch/arm/plat-samsung/s3c-pl330.c1224
1 files changed, 1224 insertions, 0 deletions
diff --git a/arch/arm/plat-samsung/s3c-pl330.c b/arch/arm/plat-samsung/s3c-pl330.c
new file mode 100644
index 000000000000..a91305a60aed
--- /dev/null
+++ b/arch/arm/plat-samsung/s3c-pl330.c
@@ -0,0 +1,1224 @@
1/* linux/arch/arm/plat-samsung/s3c-pl330.c
2 *
3 * Copyright (C) 2010 Samsung Electronics Co. Ltd.
4 * Jaswinder Singh <jassi.brar@samsung.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 */
11
12#include <linux/init.h>
13#include <linux/module.h>
14#include <linux/interrupt.h>
15#include <linux/io.h>
16#include <linux/slab.h>
17#include <linux/platform_device.h>
18
19#include <asm/hardware/pl330.h>
20
21#include <plat/s3c-pl330-pdata.h>
22
23/**
24 * struct s3c_pl330_dmac - Logical representation of a PL330 DMAC.
25 * @busy_chan: Number of channels currently busy.
26 * @peri: List of IDs of peripherals this DMAC can work with.
27 * @node: To attach to the global list of DMACs.
28 * @pi: PL330 configuration info for the DMAC.
29 * @kmcache: Pool to quickly allocate xfers for all channels in the dmac.
30 */
31struct s3c_pl330_dmac {
32 unsigned busy_chan;
33 enum dma_ch *peri;
34 struct list_head node;
35 struct pl330_info *pi;
36 struct kmem_cache *kmcache;
37};
38
39/**
40 * struct s3c_pl330_xfer - A request submitted by S3C DMA clients.
41 * @token: Xfer ID provided by the client.
42 * @node: To attach to the list of xfers on a channel.
43 * @px: Xfer for PL330 core.
44 * @chan: Owner channel of this xfer.
45 */
46struct s3c_pl330_xfer {
47 void *token;
48 struct list_head node;
49 struct pl330_xfer px;
50 struct s3c_pl330_chan *chan;
51};
52
53/**
54 * struct s3c_pl330_chan - Logical channel to communicate with
55 * a Physical peripheral.
56 * @pl330_chan_id: Token of a hardware channel thread of PL330 DMAC.
57 * NULL if the channel is available to be acquired.
58 * @id: ID of the peripheral that this channel can communicate with.
59 * @options: Options specified by the client.
60 * @sdaddr: Address provided via s3c2410_dma_devconfig.
61 * @node: To attach to the global list of channels.
62 * @lrq: Pointer to the last submitted pl330_req to PL330 core.
63 * @xfer_list: To manage list of xfers enqueued.
64 * @req: Two requests to communicate with the PL330 engine.
65 * @callback_fn: Callback function to the client.
66 * @rqcfg: Channel configuration for the xfers.
67 * @xfer_head: Pointer to the xfer to be next excecuted.
68 * @dmac: Pointer to the DMAC that manages this channel, NULL if the
69 * channel is available to be acquired.
70 * @client: Client of this channel. NULL if the
71 * channel is available to be acquired.
72 */
73struct s3c_pl330_chan {
74 void *pl330_chan_id;
75 enum dma_ch id;
76 unsigned int options;
77 unsigned long sdaddr;
78 struct list_head node;
79 struct pl330_req *lrq;
80 struct list_head xfer_list;
81 struct pl330_req req[2];
82 s3c2410_dma_cbfn_t callback_fn;
83 struct pl330_reqcfg rqcfg;
84 struct s3c_pl330_xfer *xfer_head;
85 struct s3c_pl330_dmac *dmac;
86 struct s3c2410_dma_client *client;
87};
88
89/* All DMACs in the platform */
90static LIST_HEAD(dmac_list);
91
92/* All channels to peripherals in the platform */
93static LIST_HEAD(chan_list);
94
95/*
96 * Since we add resources(DMACs and Channels) to the global pool,
97 * we need to guard access to the resources using a global lock
98 */
99static DEFINE_SPINLOCK(res_lock);
100
101/* Returns the channel with ID 'id' in the chan_list */
102static struct s3c_pl330_chan *id_to_chan(const enum dma_ch id)
103{
104 struct s3c_pl330_chan *ch;
105
106 list_for_each_entry(ch, &chan_list, node)
107 if (ch->id == id)
108 return ch;
109
110 return NULL;
111}
112
113/* Allocate a new channel with ID 'id' and add to chan_list */
114static void chan_add(const enum dma_ch id)
115{
116 struct s3c_pl330_chan *ch = id_to_chan(id);
117
118 /* Return if the channel already exists */
119 if (ch)
120 return;
121
122 ch = kmalloc(sizeof(*ch), GFP_KERNEL);
123 /* Return silently to work with other channels */
124 if (!ch)
125 return;
126
127 ch->id = id;
128 ch->dmac = NULL;
129
130 list_add_tail(&ch->node, &chan_list);
131}
132
133/* If the channel is not yet acquired by any client */
134static bool chan_free(struct s3c_pl330_chan *ch)
135{
136 if (!ch)
137 return false;
138
139 /* Channel points to some DMAC only when it's acquired */
140 return ch->dmac ? false : true;
141}
142
143/*
144 * Returns 0 is peripheral i/f is invalid or not present on the dmac.
145 * Index + 1, otherwise.
146 */
147static unsigned iface_of_dmac(struct s3c_pl330_dmac *dmac, enum dma_ch ch_id)
148{
149 enum dma_ch *id = dmac->peri;
150 int i;
151
152 /* Discount invalid markers */
153 if (ch_id == DMACH_MAX)
154 return 0;
155
156 for (i = 0; i < PL330_MAX_PERI; i++)
157 if (id[i] == ch_id)
158 return i + 1;
159
160 return 0;
161}
162
163/* If all channel threads of the DMAC are busy */
164static inline bool dmac_busy(struct s3c_pl330_dmac *dmac)
165{
166 struct pl330_info *pi = dmac->pi;
167
168 return (dmac->busy_chan < pi->pcfg.num_chan) ? false : true;
169}
170
171/*
172 * Returns the number of free channels that
173 * can be handled by this dmac only.
174 */
175static unsigned ch_onlyby_dmac(struct s3c_pl330_dmac *dmac)
176{
177 enum dma_ch *id = dmac->peri;
178 struct s3c_pl330_dmac *d;
179 struct s3c_pl330_chan *ch;
180 unsigned found, count = 0;
181 enum dma_ch p;
182 int i;
183
184 for (i = 0; i < PL330_MAX_PERI; i++) {
185 p = id[i];
186 ch = id_to_chan(p);
187
188 if (p == DMACH_MAX || !chan_free(ch))
189 continue;
190
191 found = 0;
192 list_for_each_entry(d, &dmac_list, node) {
193 if (d != dmac && iface_of_dmac(d, ch->id)) {
194 found = 1;
195 break;
196 }
197 }
198 if (!found)
199 count++;
200 }
201
202 return count;
203}
204
205/*
206 * Measure of suitability of 'dmac' handling 'ch'
207 *
208 * 0 indicates 'dmac' can not handle 'ch' either
209 * because it is not supported by the hardware or
210 * because all dmac channels are currently busy.
211 *
212 * >0 vlaue indicates 'dmac' has the capability.
213 * The bigger the value the more suitable the dmac.
214 */
215#define MAX_SUIT UINT_MAX
216#define MIN_SUIT 0
217
218static unsigned suitablility(struct s3c_pl330_dmac *dmac,
219 struct s3c_pl330_chan *ch)
220{
221 struct pl330_info *pi = dmac->pi;
222 enum dma_ch *id = dmac->peri;
223 struct s3c_pl330_dmac *d;
224 unsigned s;
225 int i;
226
227 s = MIN_SUIT;
228 /* If all the DMAC channel threads are busy */
229 if (dmac_busy(dmac))
230 return s;
231
232 for (i = 0; i < PL330_MAX_PERI; i++)
233 if (id[i] == ch->id)
234 break;
235
236 /* If the 'dmac' can't talk to 'ch' */
237 if (i == PL330_MAX_PERI)
238 return s;
239
240 s = MAX_SUIT;
241 list_for_each_entry(d, &dmac_list, node) {
242 /*
243 * If some other dmac can talk to this
244 * peri and has some channel free.
245 */
246 if (d != dmac && iface_of_dmac(d, ch->id) && !dmac_busy(d)) {
247 s = 0;
248 break;
249 }
250 }
251 if (s)
252 return s;
253
254 s = 100;
255
256 /* Good if free chans are more, bad otherwise */
257 s += (pi->pcfg.num_chan - dmac->busy_chan) - ch_onlyby_dmac(dmac);
258
259 return s;
260}
261
262/* More than one DMAC may have capability to transfer data with the
263 * peripheral. This function assigns most suitable DMAC to manage the
264 * channel and hence communicate with the peripheral.
265 */
266static struct s3c_pl330_dmac *map_chan_to_dmac(struct s3c_pl330_chan *ch)
267{
268 struct s3c_pl330_dmac *d, *dmac = NULL;
269 unsigned sn, sl = MIN_SUIT;
270
271 list_for_each_entry(d, &dmac_list, node) {
272 sn = suitablility(d, ch);
273
274 if (sn == MAX_SUIT)
275 return d;
276
277 if (sn > sl)
278 dmac = d;
279 }
280
281 return dmac;
282}
283
284/* Acquire the channel for peripheral 'id' */
285static struct s3c_pl330_chan *chan_acquire(const enum dma_ch id)
286{
287 struct s3c_pl330_chan *ch = id_to_chan(id);
288 struct s3c_pl330_dmac *dmac;
289
290 /* If the channel doesn't exist or is already acquired */
291 if (!ch || !chan_free(ch)) {
292 ch = NULL;
293 goto acq_exit;
294 }
295
296 dmac = map_chan_to_dmac(ch);
297 /* If couldn't map */
298 if (!dmac) {
299 ch = NULL;
300 goto acq_exit;
301 }
302
303 dmac->busy_chan++;
304 ch->dmac = dmac;
305
306acq_exit:
307 return ch;
308}
309
310/* Delete xfer from the queue */
311static inline void del_from_queue(struct s3c_pl330_xfer *xfer)
312{
313 struct s3c_pl330_xfer *t;
314 struct s3c_pl330_chan *ch;
315 int found;
316
317 if (!xfer)
318 return;
319
320 ch = xfer->chan;
321
322 /* Make sure xfer is in the queue */
323 found = 0;
324 list_for_each_entry(t, &ch->xfer_list, node)
325 if (t == xfer) {
326 found = 1;
327 break;
328 }
329
330 if (!found)
331 return;
332
333 /* If xfer is last entry in the queue */
334 if (xfer->node.next == &ch->xfer_list)
335 t = list_entry(ch->xfer_list.next,
336 struct s3c_pl330_xfer, node);
337 else
338 t = list_entry(xfer->node.next,
339 struct s3c_pl330_xfer, node);
340
341 /* If there was only one node left */
342 if (t == xfer)
343 ch->xfer_head = NULL;
344 else if (ch->xfer_head == xfer)
345 ch->xfer_head = t;
346
347 list_del(&xfer->node);
348}
349
350/* Provides pointer to the next xfer in the queue.
351 * If CIRCULAR option is set, the list is left intact,
352 * otherwise the xfer is removed from the list.
353 * Forced delete 'pluck' can be set to override the CIRCULAR option.
354 */
355static struct s3c_pl330_xfer *get_from_queue(struct s3c_pl330_chan *ch,
356 int pluck)
357{
358 struct s3c_pl330_xfer *xfer = ch->xfer_head;
359
360 if (!xfer)
361 return NULL;
362
363 /* If xfer is last entry in the queue */
364 if (xfer->node.next == &ch->xfer_list)
365 ch->xfer_head = list_entry(ch->xfer_list.next,
366 struct s3c_pl330_xfer, node);
367 else
368 ch->xfer_head = list_entry(xfer->node.next,
369 struct s3c_pl330_xfer, node);
370
371 if (pluck || !(ch->options & S3C2410_DMAF_CIRCULAR))
372 del_from_queue(xfer);
373
374 return xfer;
375}
376
377static inline void add_to_queue(struct s3c_pl330_chan *ch,
378 struct s3c_pl330_xfer *xfer, int front)
379{
380 struct pl330_xfer *xt;
381
382 /* If queue empty */
383 if (ch->xfer_head == NULL)
384 ch->xfer_head = xfer;
385
386 xt = &ch->xfer_head->px;
387 /* If the head already submitted (CIRCULAR head) */
388 if (ch->options & S3C2410_DMAF_CIRCULAR &&
389 (xt == ch->req[0].x || xt == ch->req[1].x))
390 ch->xfer_head = xfer;
391
392 /* If this is a resubmission, it should go at the head */
393 if (front) {
394 ch->xfer_head = xfer;
395 list_add(&xfer->node, &ch->xfer_list);
396 } else {
397 list_add_tail(&xfer->node, &ch->xfer_list);
398 }
399}
400
401static inline void _finish_off(struct s3c_pl330_xfer *xfer,
402 enum s3c2410_dma_buffresult res, int ffree)
403{
404 struct s3c_pl330_chan *ch;
405
406 if (!xfer)
407 return;
408
409 ch = xfer->chan;
410
411 /* Do callback */
412 if (ch->callback_fn)
413 ch->callback_fn(NULL, xfer->token, xfer->px.bytes, res);
414
415 /* Force Free or if buffer is not needed anymore */
416 if (ffree || !(ch->options & S3C2410_DMAF_CIRCULAR))
417 kmem_cache_free(ch->dmac->kmcache, xfer);
418}
419
420static inline int s3c_pl330_submit(struct s3c_pl330_chan *ch,
421 struct pl330_req *r)
422{
423 struct s3c_pl330_xfer *xfer;
424 int ret = 0;
425
426 /* If already submitted */
427 if (r->x)
428 return 0;
429
430 xfer = get_from_queue(ch, 0);
431 if (xfer) {
432 r->x = &xfer->px;
433
434 /* Use max bandwidth for M<->M xfers */
435 if (r->rqtype == MEMTOMEM) {
436 struct pl330_info *pi = xfer->chan->dmac->pi;
437 int burst = 1 << ch->rqcfg.brst_size;
438 u32 bytes = r->x->bytes;
439 int bl;
440
441 bl = pi->pcfg.data_bus_width / 8;
442 bl *= pi->pcfg.data_buf_dep;
443 bl /= burst;
444
445 /* src/dst_burst_len can't be more than 16 */
446 if (bl > 16)
447 bl = 16;
448
449 while (bl > 1) {
450 if (!(bytes % (bl * burst)))
451 break;
452 bl--;
453 }
454
455 ch->rqcfg.brst_len = bl;
456 } else {
457 ch->rqcfg.brst_len = 1;
458 }
459
460 ret = pl330_submit_req(ch->pl330_chan_id, r);
461
462 /* If submission was successful */
463 if (!ret) {
464 ch->lrq = r; /* latest submitted req */
465 return 0;
466 }
467
468 r->x = NULL;
469
470 /* If both of the PL330 ping-pong buffers filled */
471 if (ret == -EAGAIN) {
472 dev_err(ch->dmac->pi->dev, "%s:%d!\n",
473 __func__, __LINE__);
474 /* Queue back again */
475 add_to_queue(ch, xfer, 1);
476 ret = 0;
477 } else {
478 dev_err(ch->dmac->pi->dev, "%s:%d!\n",
479 __func__, __LINE__);
480 _finish_off(xfer, S3C2410_RES_ERR, 0);
481 }
482 }
483
484 return ret;
485}
486
487static void s3c_pl330_rq(struct s3c_pl330_chan *ch,
488 struct pl330_req *r, enum pl330_op_err err)
489{
490 unsigned long flags;
491 struct s3c_pl330_xfer *xfer;
492 struct pl330_xfer *xl = r->x;
493 enum s3c2410_dma_buffresult res;
494
495 spin_lock_irqsave(&res_lock, flags);
496
497 r->x = NULL;
498
499 s3c_pl330_submit(ch, r);
500
501 spin_unlock_irqrestore(&res_lock, flags);
502
503 /* Map result to S3C DMA API */
504 if (err == PL330_ERR_NONE)
505 res = S3C2410_RES_OK;
506 else if (err == PL330_ERR_ABORT)
507 res = S3C2410_RES_ABORT;
508 else
509 res = S3C2410_RES_ERR;
510
511 /* If last request had some xfer */
512 if (xl) {
513 xfer = container_of(xl, struct s3c_pl330_xfer, px);
514 _finish_off(xfer, res, 0);
515 } else {
516 dev_info(ch->dmac->pi->dev, "%s:%d No Xfer?!\n",
517 __func__, __LINE__);
518 }
519}
520
521static void s3c_pl330_rq0(void *token, enum pl330_op_err err)
522{
523 struct pl330_req *r = token;
524 struct s3c_pl330_chan *ch = container_of(r,
525 struct s3c_pl330_chan, req[0]);
526 s3c_pl330_rq(ch, r, err);
527}
528
529static void s3c_pl330_rq1(void *token, enum pl330_op_err err)
530{
531 struct pl330_req *r = token;
532 struct s3c_pl330_chan *ch = container_of(r,
533 struct s3c_pl330_chan, req[1]);
534 s3c_pl330_rq(ch, r, err);
535}
536
537/* Release an acquired channel */
538static void chan_release(struct s3c_pl330_chan *ch)
539{
540 struct s3c_pl330_dmac *dmac;
541
542 if (chan_free(ch))
543 return;
544
545 dmac = ch->dmac;
546 ch->dmac = NULL;
547 dmac->busy_chan--;
548}
549
550int s3c2410_dma_ctrl(enum dma_ch id, enum s3c2410_chan_op op)
551{
552 struct s3c_pl330_xfer *xfer;
553 enum pl330_chan_op pl330op;
554 struct s3c_pl330_chan *ch;
555 unsigned long flags;
556 int idx, ret;
557
558 spin_lock_irqsave(&res_lock, flags);
559
560 ch = id_to_chan(id);
561
562 if (!ch || chan_free(ch)) {
563 ret = -EINVAL;
564 goto ctrl_exit;
565 }
566
567 switch (op) {
568 case S3C2410_DMAOP_START:
569 /* Make sure both reqs are enqueued */
570 idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
571 s3c_pl330_submit(ch, &ch->req[idx]);
572 s3c_pl330_submit(ch, &ch->req[1 - idx]);
573 pl330op = PL330_OP_START;
574 break;
575
576 case S3C2410_DMAOP_STOP:
577 pl330op = PL330_OP_ABORT;
578 break;
579
580 case S3C2410_DMAOP_FLUSH:
581 pl330op = PL330_OP_FLUSH;
582 break;
583
584 case S3C2410_DMAOP_PAUSE:
585 case S3C2410_DMAOP_RESUME:
586 case S3C2410_DMAOP_TIMEOUT:
587 case S3C2410_DMAOP_STARTED:
588 spin_unlock_irqrestore(&res_lock, flags);
589 return 0;
590
591 default:
592 spin_unlock_irqrestore(&res_lock, flags);
593 return -EINVAL;
594 }
595
596 ret = pl330_chan_ctrl(ch->pl330_chan_id, pl330op);
597
598 if (pl330op == PL330_OP_START) {
599 spin_unlock_irqrestore(&res_lock, flags);
600 return ret;
601 }
602
603 idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
604
605 /* Abort the current xfer */
606 if (ch->req[idx].x) {
607 xfer = container_of(ch->req[idx].x,
608 struct s3c_pl330_xfer, px);
609
610 /* Drop xfer during FLUSH */
611 if (pl330op == PL330_OP_FLUSH)
612 del_from_queue(xfer);
613
614 ch->req[idx].x = NULL;
615
616 spin_unlock_irqrestore(&res_lock, flags);
617 _finish_off(xfer, S3C2410_RES_ABORT,
618 pl330op == PL330_OP_FLUSH ? 1 : 0);
619 spin_lock_irqsave(&res_lock, flags);
620 }
621
622 /* Flush the whole queue */
623 if (pl330op == PL330_OP_FLUSH) {
624
625 if (ch->req[1 - idx].x) {
626 xfer = container_of(ch->req[1 - idx].x,
627 struct s3c_pl330_xfer, px);
628
629 del_from_queue(xfer);
630
631 ch->req[1 - idx].x = NULL;
632
633 spin_unlock_irqrestore(&res_lock, flags);
634 _finish_off(xfer, S3C2410_RES_ABORT, 1);
635 spin_lock_irqsave(&res_lock, flags);
636 }
637
638 /* Finish off the remaining in the queue */
639 xfer = ch->xfer_head;
640 while (xfer) {
641
642 del_from_queue(xfer);
643
644 spin_unlock_irqrestore(&res_lock, flags);
645 _finish_off(xfer, S3C2410_RES_ABORT, 1);
646 spin_lock_irqsave(&res_lock, flags);
647
648 xfer = ch->xfer_head;
649 }
650 }
651
652ctrl_exit:
653 spin_unlock_irqrestore(&res_lock, flags);
654
655 return ret;
656}
657EXPORT_SYMBOL(s3c2410_dma_ctrl);
658
659int s3c2410_dma_enqueue(enum dma_ch id, void *token,
660 dma_addr_t addr, int size)
661{
662 struct s3c_pl330_chan *ch;
663 struct s3c_pl330_xfer *xfer;
664 unsigned long flags;
665 int idx, ret = 0;
666
667 spin_lock_irqsave(&res_lock, flags);
668
669 ch = id_to_chan(id);
670
671 /* Error if invalid or free channel */
672 if (!ch || chan_free(ch)) {
673 ret = -EINVAL;
674 goto enq_exit;
675 }
676
677 /* Error if size is unaligned */
678 if (ch->rqcfg.brst_size && size % (1 << ch->rqcfg.brst_size)) {
679 ret = -EINVAL;
680 goto enq_exit;
681 }
682
683 xfer = kmem_cache_alloc(ch->dmac->kmcache, GFP_ATOMIC);
684 if (!xfer) {
685 ret = -ENOMEM;
686 goto enq_exit;
687 }
688
689 xfer->token = token;
690 xfer->chan = ch;
691 xfer->px.bytes = size;
692 xfer->px.next = NULL; /* Single request */
693
694 /* For S3C DMA API, direction is always fixed for all xfers */
695 if (ch->req[0].rqtype == MEMTODEV) {
696 xfer->px.src_addr = addr;
697 xfer->px.dst_addr = ch->sdaddr;
698 } else {
699 xfer->px.src_addr = ch->sdaddr;
700 xfer->px.dst_addr = addr;
701 }
702
703 add_to_queue(ch, xfer, 0);
704
705 /* Try submitting on either request */
706 idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
707
708 if (!ch->req[idx].x)
709 s3c_pl330_submit(ch, &ch->req[idx]);
710 else
711 s3c_pl330_submit(ch, &ch->req[1 - idx]);
712
713 spin_unlock_irqrestore(&res_lock, flags);
714
715 if (ch->options & S3C2410_DMAF_AUTOSTART)
716 s3c2410_dma_ctrl(id, S3C2410_DMAOP_START);
717
718 return 0;
719
720enq_exit:
721 spin_unlock_irqrestore(&res_lock, flags);
722
723 return ret;
724}
725EXPORT_SYMBOL(s3c2410_dma_enqueue);
726
727int s3c2410_dma_request(enum dma_ch id,
728 struct s3c2410_dma_client *client,
729 void *dev)
730{
731 struct s3c_pl330_dmac *dmac;
732 struct s3c_pl330_chan *ch;
733 unsigned long flags;
734 int ret = 0;
735
736 spin_lock_irqsave(&res_lock, flags);
737
738 ch = chan_acquire(id);
739 if (!ch) {
740 ret = -EBUSY;
741 goto req_exit;
742 }
743
744 dmac = ch->dmac;
745
746 ch->pl330_chan_id = pl330_request_channel(dmac->pi);
747 if (!ch->pl330_chan_id) {
748 chan_release(ch);
749 ret = -EBUSY;
750 goto req_exit;
751 }
752
753 ch->client = client;
754 ch->options = 0; /* Clear any option */
755 ch->callback_fn = NULL; /* Clear any callback */
756 ch->lrq = NULL;
757
758 ch->rqcfg.brst_size = 2; /* Default word size */
759 ch->rqcfg.swap = SWAP_NO;
760 ch->rqcfg.scctl = SCCTRL0; /* Noncacheable and nonbufferable */
761 ch->rqcfg.dcctl = DCCTRL0; /* Noncacheable and nonbufferable */
762 ch->rqcfg.privileged = 0;
763 ch->rqcfg.insnaccess = 0;
764
765 /* Set invalid direction */
766 ch->req[0].rqtype = DEVTODEV;
767 ch->req[1].rqtype = ch->req[0].rqtype;
768
769 ch->req[0].cfg = &ch->rqcfg;
770 ch->req[1].cfg = ch->req[0].cfg;
771
772 ch->req[0].peri = iface_of_dmac(dmac, id) - 1; /* Original index */
773 ch->req[1].peri = ch->req[0].peri;
774
775 ch->req[0].token = &ch->req[0];
776 ch->req[0].xfer_cb = s3c_pl330_rq0;
777 ch->req[1].token = &ch->req[1];
778 ch->req[1].xfer_cb = s3c_pl330_rq1;
779
780 ch->req[0].x = NULL;
781 ch->req[1].x = NULL;
782
783 /* Reset xfer list */
784 INIT_LIST_HEAD(&ch->xfer_list);
785 ch->xfer_head = NULL;
786
787req_exit:
788 spin_unlock_irqrestore(&res_lock, flags);
789
790 return ret;
791}
792EXPORT_SYMBOL(s3c2410_dma_request);
793
794int s3c2410_dma_free(enum dma_ch id, struct s3c2410_dma_client *client)
795{
796 struct s3c_pl330_chan *ch;
797 struct s3c_pl330_xfer *xfer;
798 unsigned long flags;
799 int ret = 0;
800 unsigned idx;
801
802 spin_lock_irqsave(&res_lock, flags);
803
804 ch = id_to_chan(id);
805
806 if (!ch || chan_free(ch))
807 goto free_exit;
808
809 /* Refuse if someone else wanted to free the channel */
810 if (ch->client != client) {
811 ret = -EBUSY;
812 goto free_exit;
813 }
814
815 /* Stop any active xfer, Flushe the queue and do callbacks */
816 pl330_chan_ctrl(ch->pl330_chan_id, PL330_OP_FLUSH);
817
818 /* Abort the submitted requests */
819 idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
820
821 if (ch->req[idx].x) {
822 xfer = container_of(ch->req[idx].x,
823 struct s3c_pl330_xfer, px);
824
825 ch->req[idx].x = NULL;
826 del_from_queue(xfer);
827
828 spin_unlock_irqrestore(&res_lock, flags);
829 _finish_off(xfer, S3C2410_RES_ABORT, 1);
830 spin_lock_irqsave(&res_lock, flags);
831 }
832
833 if (ch->req[1 - idx].x) {
834 xfer = container_of(ch->req[1 - idx].x,
835 struct s3c_pl330_xfer, px);
836
837 ch->req[1 - idx].x = NULL;
838 del_from_queue(xfer);
839
840 spin_unlock_irqrestore(&res_lock, flags);
841 _finish_off(xfer, S3C2410_RES_ABORT, 1);
842 spin_lock_irqsave(&res_lock, flags);
843 }
844
845 /* Pluck and Abort the queued requests in order */
846 do {
847 xfer = get_from_queue(ch, 1);
848
849 spin_unlock_irqrestore(&res_lock, flags);
850 _finish_off(xfer, S3C2410_RES_ABORT, 1);
851 spin_lock_irqsave(&res_lock, flags);
852 } while (xfer);
853
854 ch->client = NULL;
855
856 pl330_release_channel(ch->pl330_chan_id);
857
858 ch->pl330_chan_id = NULL;
859
860 chan_release(ch);
861
862free_exit:
863 spin_unlock_irqrestore(&res_lock, flags);
864
865 return ret;
866}
867EXPORT_SYMBOL(s3c2410_dma_free);
868
869int s3c2410_dma_config(enum dma_ch id, int xferunit)
870{
871 struct s3c_pl330_chan *ch;
872 struct pl330_info *pi;
873 unsigned long flags;
874 int i, dbwidth, ret = 0;
875
876 spin_lock_irqsave(&res_lock, flags);
877
878 ch = id_to_chan(id);
879
880 if (!ch || chan_free(ch)) {
881 ret = -EINVAL;
882 goto cfg_exit;
883 }
884
885 pi = ch->dmac->pi;
886 dbwidth = pi->pcfg.data_bus_width / 8;
887
888 /* Max size of xfer can be pcfg.data_bus_width */
889 if (xferunit > dbwidth) {
890 ret = -EINVAL;
891 goto cfg_exit;
892 }
893
894 i = 0;
895 while (xferunit != (1 << i))
896 i++;
897
898 /* If valid value */
899 if (xferunit == (1 << i))
900 ch->rqcfg.brst_size = i;
901 else
902 ret = -EINVAL;
903
904cfg_exit:
905 spin_unlock_irqrestore(&res_lock, flags);
906
907 return ret;
908}
909EXPORT_SYMBOL(s3c2410_dma_config);
910
911/* Options that are supported by this driver */
912#define S3C_PL330_FLAGS (S3C2410_DMAF_CIRCULAR | S3C2410_DMAF_AUTOSTART)
913
914int s3c2410_dma_setflags(enum dma_ch id, unsigned int options)
915{
916 struct s3c_pl330_chan *ch;
917 unsigned long flags;
918 int ret = 0;
919
920 spin_lock_irqsave(&res_lock, flags);
921
922 ch = id_to_chan(id);
923
924 if (!ch || chan_free(ch) || options & ~(S3C_PL330_FLAGS))
925 ret = -EINVAL;
926 else
927 ch->options = options;
928
929 spin_unlock_irqrestore(&res_lock, flags);
930
931 return 0;
932}
933EXPORT_SYMBOL(s3c2410_dma_setflags);
934
935int s3c2410_dma_set_buffdone_fn(enum dma_ch id, s3c2410_dma_cbfn_t rtn)
936{
937 struct s3c_pl330_chan *ch;
938 unsigned long flags;
939 int ret = 0;
940
941 spin_lock_irqsave(&res_lock, flags);
942
943 ch = id_to_chan(id);
944
945 if (!ch || chan_free(ch))
946 ret = -EINVAL;
947 else
948 ch->callback_fn = rtn;
949
950 spin_unlock_irqrestore(&res_lock, flags);
951
952 return ret;
953}
954EXPORT_SYMBOL(s3c2410_dma_set_buffdone_fn);
955
956int s3c2410_dma_devconfig(enum dma_ch id, enum s3c2410_dmasrc source,
957 unsigned long address)
958{
959 struct s3c_pl330_chan *ch;
960 unsigned long flags;
961 int ret = 0;
962
963 spin_lock_irqsave(&res_lock, flags);
964
965 ch = id_to_chan(id);
966
967 if (!ch || chan_free(ch)) {
968 ret = -EINVAL;
969 goto devcfg_exit;
970 }
971
972 switch (source) {
973 case S3C2410_DMASRC_HW: /* P->M */
974 ch->req[0].rqtype = DEVTOMEM;
975 ch->req[1].rqtype = DEVTOMEM;
976 ch->rqcfg.src_inc = 0;
977 ch->rqcfg.dst_inc = 1;
978 break;
979 case S3C2410_DMASRC_MEM: /* M->P */
980 ch->req[0].rqtype = MEMTODEV;
981 ch->req[1].rqtype = MEMTODEV;
982 ch->rqcfg.src_inc = 1;
983 ch->rqcfg.dst_inc = 0;
984 break;
985 default:
986 ret = -EINVAL;
987 goto devcfg_exit;
988 }
989
990 ch->sdaddr = address;
991
992devcfg_exit:
993 spin_unlock_irqrestore(&res_lock, flags);
994
995 return ret;
996}
997EXPORT_SYMBOL(s3c2410_dma_devconfig);
998
999int s3c2410_dma_getposition(enum dma_ch id, dma_addr_t *src, dma_addr_t *dst)
1000{
1001 struct s3c_pl330_chan *ch = id_to_chan(id);
1002 struct pl330_chanstatus status;
1003 int ret;
1004
1005 if (!ch || chan_free(ch))
1006 return -EINVAL;
1007
1008 ret = pl330_chan_status(ch->pl330_chan_id, &status);
1009 if (ret < 0)
1010 return ret;
1011
1012 *src = status.src_addr;
1013 *dst = status.dst_addr;
1014
1015 return 0;
1016}
1017EXPORT_SYMBOL(s3c2410_dma_getposition);
1018
1019static irqreturn_t pl330_irq_handler(int irq, void *data)
1020{
1021 if (pl330_update(data))
1022 return IRQ_HANDLED;
1023 else
1024 return IRQ_NONE;
1025}
1026
1027static int pl330_probe(struct platform_device *pdev)
1028{
1029 struct s3c_pl330_dmac *s3c_pl330_dmac;
1030 struct s3c_pl330_platdata *pl330pd;
1031 struct pl330_info *pl330_info;
1032 struct resource *res;
1033 int i, ret, irq;
1034
1035 pl330pd = pdev->dev.platform_data;
1036
1037 /* Can't do without the list of _32_ peripherals */
1038 if (!pl330pd || !pl330pd->peri) {
1039 dev_err(&pdev->dev, "platform data missing!\n");
1040 return -ENODEV;
1041 }
1042
1043 pl330_info = kzalloc(sizeof(*pl330_info), GFP_KERNEL);
1044 if (!pl330_info)
1045 return -ENOMEM;
1046
1047 pl330_info->pl330_data = NULL;
1048 pl330_info->dev = &pdev->dev;
1049
1050 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1051 if (!res) {
1052 ret = -ENODEV;
1053 goto probe_err1;
1054 }
1055
1056 request_mem_region(res->start, resource_size(res), pdev->name);
1057
1058 pl330_info->base = ioremap(res->start, resource_size(res));
1059 if (!pl330_info->base) {
1060 ret = -ENXIO;
1061 goto probe_err2;
1062 }
1063
1064 irq = platform_get_irq(pdev, 0);
1065 if (irq < 0) {
1066 ret = irq;
1067 goto probe_err3;
1068 }
1069
1070 ret = request_irq(irq, pl330_irq_handler, 0,
1071 dev_name(&pdev->dev), pl330_info);
1072 if (ret)
1073 goto probe_err4;
1074
1075 ret = pl330_add(pl330_info);
1076 if (ret)
1077 goto probe_err5;
1078
1079 /* Allocate a new DMAC */
1080 s3c_pl330_dmac = kmalloc(sizeof(*s3c_pl330_dmac), GFP_KERNEL);
1081 if (!s3c_pl330_dmac) {
1082 ret = -ENOMEM;
1083 goto probe_err6;
1084 }
1085
1086 /* Hook the info */
1087 s3c_pl330_dmac->pi = pl330_info;
1088
1089 /* No busy channels */
1090 s3c_pl330_dmac->busy_chan = 0;
1091
1092 s3c_pl330_dmac->kmcache = kmem_cache_create(dev_name(&pdev->dev),
1093 sizeof(struct s3c_pl330_xfer), 0, 0, NULL);
1094
1095 if (!s3c_pl330_dmac->kmcache) {
1096 ret = -ENOMEM;
1097 goto probe_err7;
1098 }
1099
1100 /* Get the list of peripherals */
1101 s3c_pl330_dmac->peri = pl330pd->peri;
1102
1103 /* Attach to the list of DMACs */
1104 list_add_tail(&s3c_pl330_dmac->node, &dmac_list);
1105
1106 /* Create a channel for each peripheral in the DMAC
1107 * that is, if it doesn't already exist
1108 */
1109 for (i = 0; i < PL330_MAX_PERI; i++)
1110 if (s3c_pl330_dmac->peri[i] != DMACH_MAX)
1111 chan_add(s3c_pl330_dmac->peri[i]);
1112
1113 printk(KERN_INFO
1114 "Loaded driver for PL330 DMAC-%d %s\n", pdev->id, pdev->name);
1115 printk(KERN_INFO
1116 "\tDBUFF-%ux%ubytes Num_Chans-%u Num_Peri-%u Num_Events-%u\n",
1117 pl330_info->pcfg.data_buf_dep,
1118 pl330_info->pcfg.data_bus_width / 8, pl330_info->pcfg.num_chan,
1119 pl330_info->pcfg.num_peri, pl330_info->pcfg.num_events);
1120
1121 return 0;
1122
1123probe_err7:
1124 kfree(s3c_pl330_dmac);
1125probe_err6:
1126 pl330_del(pl330_info);
1127probe_err5:
1128 free_irq(irq, pl330_info);
1129probe_err4:
1130probe_err3:
1131 iounmap(pl330_info->base);
1132probe_err2:
1133 release_mem_region(res->start, resource_size(res));
1134probe_err1:
1135 kfree(pl330_info);
1136
1137 return ret;
1138}
1139
1140static int pl330_remove(struct platform_device *pdev)
1141{
1142 struct s3c_pl330_dmac *dmac, *d;
1143 struct s3c_pl330_chan *ch;
1144 unsigned long flags;
1145 int del, found;
1146
1147 if (!pdev->dev.platform_data)
1148 return -EINVAL;
1149
1150 spin_lock_irqsave(&res_lock, flags);
1151
1152 found = 0;
1153 list_for_each_entry(d, &dmac_list, node)
1154 if (d->pi->dev == &pdev->dev) {
1155 found = 1;
1156 break;
1157 }
1158
1159 if (!found) {
1160 spin_unlock_irqrestore(&res_lock, flags);
1161 return 0;
1162 }
1163
1164 dmac = d;
1165
1166 /* Remove all Channels that are managed only by this DMAC */
1167 list_for_each_entry(ch, &chan_list, node) {
1168
1169 /* Only channels that are handled by this DMAC */
1170 if (iface_of_dmac(dmac, ch->id))
1171 del = 1;
1172 else
1173 continue;
1174
1175 /* Don't remove if some other DMAC has it too */
1176 list_for_each_entry(d, &dmac_list, node)
1177 if (d != dmac && iface_of_dmac(d, ch->id)) {
1178 del = 0;
1179 break;
1180 }
1181
1182 if (del) {
1183 spin_unlock_irqrestore(&res_lock, flags);
1184 s3c2410_dma_free(ch->id, ch->client);
1185 spin_lock_irqsave(&res_lock, flags);
1186 list_del(&ch->node);
1187 kfree(ch);
1188 }
1189 }
1190
1191 /* Remove the DMAC */
1192 list_del(&dmac->node);
1193 kfree(dmac);
1194
1195 spin_unlock_irqrestore(&res_lock, flags);
1196
1197 return 0;
1198}
1199
1200static struct platform_driver pl330_driver = {
1201 .driver = {
1202 .owner = THIS_MODULE,
1203 .name = "s3c-pl330",
1204 },
1205 .probe = pl330_probe,
1206 .remove = pl330_remove,
1207};
1208
1209static int __init pl330_init(void)
1210{
1211 return platform_driver_register(&pl330_driver);
1212}
1213module_init(pl330_init);
1214
1215static void __exit pl330_exit(void)
1216{
1217 platform_driver_unregister(&pl330_driver);
1218 return;
1219}
1220module_exit(pl330_exit);
1221
1222MODULE_AUTHOR("Jaswinder Singh <jassi.brar@samsung.com>");
1223MODULE_DESCRIPTION("Driver for PL330 DMA Controller");
1224MODULE_LICENSE("GPL");