Blame view

drivers/dma/pl330.c 23.4 KB
b3040e406   Jassi Brar   DMA: PL330: Add d...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  /* linux/drivers/dma/pl330.c
   *
   * Copyright (C) 2010 Samsung Electronics Co. Ltd.
   *	Jaswinder Singh <jassi.brar@samsung.com>
   *
   * 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 <linux/io.h>
  #include <linux/init.h>
  #include <linux/slab.h>
  #include <linux/module.h>
  #include <linux/dmaengine.h>
  #include <linux/interrupt.h>
  #include <linux/amba/bus.h>
  #include <linux/amba/pl330.h>
a2f5203fe   Boojin Kim   DMA: PL330: Add s...
20
  #include <linux/pm_runtime.h>
1b9bb715e   Boojin Kim   DMA: PL330: Updat...
21
  #include <linux/scatterlist.h>
93ed55441   Thomas Abraham   DMA: PL330: Add d...
22
  #include <linux/of.h>
b3040e406   Jassi Brar   DMA: PL330: Add d...
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
  
  #define NR_DEFAULT_DESC	16
  
  enum desc_status {
  	/* In the DMAC pool */
  	FREE,
  	/*
  	 * Allocted to some channel during prep_xxx
  	 * Also may be sitting on the work_list.
  	 */
  	PREP,
  	/*
  	 * Sitting on the work_list and already submitted
  	 * to the PL330 core. Not more than two descriptors
  	 * of a channel can be BUSY at any time.
  	 */
  	BUSY,
  	/*
  	 * Sitting on the channel work_list but xfer done
  	 * by PL330 core
  	 */
  	DONE,
  };
  
  struct dma_pl330_chan {
  	/* Schedule desc completion */
  	struct tasklet_struct task;
  
  	/* DMA-Engine Channel */
  	struct dma_chan chan;
  
  	/* Last completed cookie */
  	dma_cookie_t completed;
  
  	/* List of to be xfered descriptors */
  	struct list_head work_list;
  
  	/* Pointer to the DMAC that manages this channel,
  	 * NULL if the channel is available to be acquired.
  	 * As the parent, this DMAC also provides descriptors
  	 * to the channel.
  	 */
  	struct dma_pl330_dmac *dmac;
  
  	/* To protect channel manipulation */
  	spinlock_t lock;
  
  	/* Token of a hardware channel thread of PL330 DMAC
  	 * NULL if the channel is available to be acquired.
  	 */
  	void *pl330_chid;
1b9bb715e   Boojin Kim   DMA: PL330: Updat...
74
75
76
  
  	/* For D-to-M and M-to-D channels */
  	int burst_sz; /* the peripheral fifo width */
1d0c1d606   Boojin Kim   DMA: PL330: Suppo...
77
  	int burst_len; /* the number of burst */
1b9bb715e   Boojin Kim   DMA: PL330: Updat...
78
  	dma_addr_t fifo_addr;
42bc9cf45   Boojin Kim   DMA: PL330: Add D...
79
80
81
  
  	/* for cyclic capability */
  	bool cyclic;
b3040e406   Jassi Brar   DMA: PL330: Add d...
82
83
84
85
86
87
88
89
90
91
92
93
94
95
  };
  
  struct dma_pl330_dmac {
  	struct pl330_info pif;
  
  	/* DMA-Engine Device */
  	struct dma_device ddma;
  
  	/* Pool of descriptors available for the DMAC's channels */
  	struct list_head desc_pool;
  	/* To protect desc_pool manipulation */
  	spinlock_t pool_lock;
  
  	/* Peripheral channels connected to this DMAC */
4e0e6109a   Rob Herring   dmaengine: pl330:...
96
  	struct dma_pl330_chan *peripherals; /* keep at end */
a2f5203fe   Boojin Kim   DMA: PL330: Add s...
97
98
  
  	struct clk *clk;
b3040e406   Jassi Brar   DMA: PL330: Add d...
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
  };
  
  struct dma_pl330_desc {
  	/* To attach to a queue as child */
  	struct list_head node;
  
  	/* Descriptor for the DMA Engine API */
  	struct dma_async_tx_descriptor txd;
  
  	/* Xfer for PL330 core */
  	struct pl330_xfer px;
  
  	struct pl330_reqcfg rqcfg;
  	struct pl330_req req;
  
  	enum desc_status status;
  
  	/* The channel which currently holds this desc */
  	struct dma_pl330_chan *pchan;
  };
3e2ec13a8   Thomas Abraham   DMA: PL330: move ...
119
120
  /* forward declaration */
  static struct amba_driver pl330_driver;
b3040e406   Jassi Brar   DMA: PL330: Add d...
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
  static inline struct dma_pl330_chan *
  to_pchan(struct dma_chan *ch)
  {
  	if (!ch)
  		return NULL;
  
  	return container_of(ch, struct dma_pl330_chan, chan);
  }
  
  static inline struct dma_pl330_desc *
  to_desc(struct dma_async_tx_descriptor *tx)
  {
  	return container_of(tx, struct dma_pl330_desc, txd);
  }
  
  static inline void free_desc_list(struct list_head *list)
  {
  	struct dma_pl330_dmac *pdmac;
  	struct dma_pl330_desc *desc;
  	struct dma_pl330_chan *pch;
  	unsigned long flags;
  
  	if (list_empty(list))
  		return;
  
  	/* Finish off the work list */
  	list_for_each_entry(desc, list, node) {
  		dma_async_tx_callback callback;
  		void *param;
  
  		/* All desc in a list belong to same channel */
  		pch = desc->pchan;
  		callback = desc->txd.callback;
  		param = desc->txd.callback_param;
  
  		if (callback)
  			callback(param);
  
  		desc->pchan = NULL;
  	}
  
  	pdmac = pch->dmac;
  
  	spin_lock_irqsave(&pdmac->pool_lock, flags);
  	list_splice_tail_init(list, &pdmac->desc_pool);
  	spin_unlock_irqrestore(&pdmac->pool_lock, flags);
  }
42bc9cf45   Boojin Kim   DMA: PL330: Add D...
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
  static inline void handle_cyclic_desc_list(struct list_head *list)
  {
  	struct dma_pl330_desc *desc;
  	struct dma_pl330_chan *pch;
  	unsigned long flags;
  
  	if (list_empty(list))
  		return;
  
  	list_for_each_entry(desc, list, node) {
  		dma_async_tx_callback callback;
  
  		/* Change status to reload it */
  		desc->status = PREP;
  		pch = desc->pchan;
  		callback = desc->txd.callback;
  		if (callback)
  			callback(desc->txd.callback_param);
  	}
  
  	spin_lock_irqsave(&pch->lock, flags);
  	list_splice_tail_init(list, &pch->work_list);
  	spin_unlock_irqrestore(&pch->lock, flags);
  }
b3040e406   Jassi Brar   DMA: PL330: Add d...
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
  static inline void fill_queue(struct dma_pl330_chan *pch)
  {
  	struct dma_pl330_desc *desc;
  	int ret;
  
  	list_for_each_entry(desc, &pch->work_list, node) {
  
  		/* If already submitted */
  		if (desc->status == BUSY)
  			break;
  
  		ret = pl330_submit_req(pch->pl330_chid,
  						&desc->req);
  		if (!ret) {
  			desc->status = BUSY;
  			break;
  		} else if (ret == -EAGAIN) {
  			/* QFull or DMAC Dying */
  			break;
  		} else {
  			/* Unacceptable request */
  			desc->status = DONE;
  			dev_err(pch->dmac->pif.dev, "%s:%d Bad Desc(%d)
  ",
  					__func__, __LINE__, desc->txd.cookie);
  			tasklet_schedule(&pch->task);
  		}
  	}
  }
  
  static void pl330_tasklet(unsigned long data)
  {
  	struct dma_pl330_chan *pch = (struct dma_pl330_chan *)data;
  	struct dma_pl330_desc *desc, *_dt;
  	unsigned long flags;
  	LIST_HEAD(list);
  
  	spin_lock_irqsave(&pch->lock, flags);
  
  	/* Pick up ripe tomatoes */
  	list_for_each_entry_safe(desc, _dt, &pch->work_list, node)
  		if (desc->status == DONE) {
  			pch->completed = desc->txd.cookie;
  			list_move_tail(&desc->node, &list);
  		}
  
  	/* Try to submit a req imm. next to the last completed cookie */
  	fill_queue(pch);
  
  	/* Make sure the PL330 Channel thread is active */
  	pl330_chan_ctrl(pch->pl330_chid, PL330_OP_START);
  
  	spin_unlock_irqrestore(&pch->lock, flags);
42bc9cf45   Boojin Kim   DMA: PL330: Add D...
245
246
247
248
  	if (pch->cyclic)
  		handle_cyclic_desc_list(&list);
  	else
  		free_desc_list(&list);
b3040e406   Jassi Brar   DMA: PL330: Add d...
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
  }
  
  static void dma_pl330_rqcb(void *token, enum pl330_op_err err)
  {
  	struct dma_pl330_desc *desc = token;
  	struct dma_pl330_chan *pch = desc->pchan;
  	unsigned long flags;
  
  	/* If desc aborted */
  	if (!pch)
  		return;
  
  	spin_lock_irqsave(&pch->lock, flags);
  
  	desc->status = DONE;
  
  	spin_unlock_irqrestore(&pch->lock, flags);
  
  	tasklet_schedule(&pch->task);
  }
3e2ec13a8   Thomas Abraham   DMA: PL330: move ...
269
270
  bool pl330_filter(struct dma_chan *chan, void *param)
  {
cd0725152   Thomas Abraham   DMA: PL330: Infer...
271
  	u8 *peri_id;
3e2ec13a8   Thomas Abraham   DMA: PL330: move ...
272
273
274
  
  	if (chan->device->dev->driver != &pl330_driver.drv)
  		return false;
93ed55441   Thomas Abraham   DMA: PL330: Add d...
275
276
277
278
279
280
281
282
283
284
285
286
287
  #ifdef CONFIG_OF
  	if (chan->device->dev->of_node) {
  		const __be32 *prop_value;
  		phandle phandle;
  		struct device_node *node;
  
  		prop_value = ((struct property *)param)->value;
  		phandle = be32_to_cpup(prop_value++);
  		node = of_find_node_by_phandle(phandle);
  		return ((chan->private == node) &&
  				(chan->chan_id == be32_to_cpup(prop_value)));
  	}
  #endif
cd0725152   Thomas Abraham   DMA: PL330: Infer...
288
289
  	peri_id = chan->private;
  	return *peri_id == (unsigned)param;
3e2ec13a8   Thomas Abraham   DMA: PL330: move ...
290
291
  }
  EXPORT_SYMBOL(pl330_filter);
b3040e406   Jassi Brar   DMA: PL330: Add d...
292
293
294
295
296
297
298
299
300
  static int pl330_alloc_chan_resources(struct dma_chan *chan)
  {
  	struct dma_pl330_chan *pch = to_pchan(chan);
  	struct dma_pl330_dmac *pdmac = pch->dmac;
  	unsigned long flags;
  
  	spin_lock_irqsave(&pch->lock, flags);
  
  	pch->completed = chan->cookie = 1;
42bc9cf45   Boojin Kim   DMA: PL330: Add D...
301
  	pch->cyclic = false;
b3040e406   Jassi Brar   DMA: PL330: Add d...
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
  
  	pch->pl330_chid = pl330_request_channel(&pdmac->pif);
  	if (!pch->pl330_chid) {
  		spin_unlock_irqrestore(&pch->lock, flags);
  		return 0;
  	}
  
  	tasklet_init(&pch->task, pl330_tasklet, (unsigned long) pch);
  
  	spin_unlock_irqrestore(&pch->lock, flags);
  
  	return 1;
  }
  
  static int pl330_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, unsigned long arg)
  {
  	struct dma_pl330_chan *pch = to_pchan(chan);
ae43b886f   Boojin Kim   DMA: PL330: Remov...
319
  	struct dma_pl330_desc *desc, *_dt;
b3040e406   Jassi Brar   DMA: PL330: Add d...
320
  	unsigned long flags;
1d0c1d606   Boojin Kim   DMA: PL330: Suppo...
321
322
  	struct dma_pl330_dmac *pdmac = pch->dmac;
  	struct dma_slave_config *slave_config;
ae43b886f   Boojin Kim   DMA: PL330: Remov...
323
  	LIST_HEAD(list);
b3040e406   Jassi Brar   DMA: PL330: Add d...
324

1d0c1d606   Boojin Kim   DMA: PL330: Suppo...
325
326
327
  	switch (cmd) {
  	case DMA_TERMINATE_ALL:
  		spin_lock_irqsave(&pch->lock, flags);
b3040e406   Jassi Brar   DMA: PL330: Add d...
328

1d0c1d606   Boojin Kim   DMA: PL330: Suppo...
329
330
  		/* FLUSH the PL330 Channel thread */
  		pl330_chan_ctrl(pch->pl330_chid, PL330_OP_FLUSH);
b3040e406   Jassi Brar   DMA: PL330: Add d...
331

1d0c1d606   Boojin Kim   DMA: PL330: Suppo...
332
  		/* Mark all desc done */
ae43b886f   Boojin Kim   DMA: PL330: Remov...
333
  		list_for_each_entry_safe(desc, _dt, &pch->work_list , node) {
1d0c1d606   Boojin Kim   DMA: PL330: Suppo...
334
  			desc->status = DONE;
ae43b886f   Boojin Kim   DMA: PL330: Remov...
335
336
337
  			pch->completed = desc->txd.cookie;
  			list_move_tail(&desc->node, &list);
  		}
b3040e406   Jassi Brar   DMA: PL330: Add d...
338

ae43b886f   Boojin Kim   DMA: PL330: Remov...
339
  		list_splice_tail_init(&list, &pdmac->desc_pool);
1d0c1d606   Boojin Kim   DMA: PL330: Suppo...
340
  		spin_unlock_irqrestore(&pch->lock, flags);
1d0c1d606   Boojin Kim   DMA: PL330: Suppo...
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
  		break;
  	case DMA_SLAVE_CONFIG:
  		slave_config = (struct dma_slave_config *)arg;
  
  		if (slave_config->direction == DMA_TO_DEVICE) {
  			if (slave_config->dst_addr)
  				pch->fifo_addr = slave_config->dst_addr;
  			if (slave_config->dst_addr_width)
  				pch->burst_sz = __ffs(slave_config->dst_addr_width);
  			if (slave_config->dst_maxburst)
  				pch->burst_len = slave_config->dst_maxburst;
  		} else if (slave_config->direction == DMA_FROM_DEVICE) {
  			if (slave_config->src_addr)
  				pch->fifo_addr = slave_config->src_addr;
  			if (slave_config->src_addr_width)
  				pch->burst_sz = __ffs(slave_config->src_addr_width);
  			if (slave_config->src_maxburst)
  				pch->burst_len = slave_config->src_maxburst;
  		}
  		break;
  	default:
  		dev_err(pch->dmac->pif.dev, "Not supported command.
  ");
  		return -ENXIO;
  	}
b3040e406   Jassi Brar   DMA: PL330: Add d...
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
  
  	return 0;
  }
  
  static void pl330_free_chan_resources(struct dma_chan *chan)
  {
  	struct dma_pl330_chan *pch = to_pchan(chan);
  	unsigned long flags;
  
  	spin_lock_irqsave(&pch->lock, flags);
  
  	tasklet_kill(&pch->task);
  
  	pl330_release_channel(pch->pl330_chid);
  	pch->pl330_chid = NULL;
42bc9cf45   Boojin Kim   DMA: PL330: Add D...
381
382
  	if (pch->cyclic)
  		list_splice_tail_init(&pch->work_list, &pch->dmac->desc_pool);
b3040e406   Jassi Brar   DMA: PL330: Add d...
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
  	spin_unlock_irqrestore(&pch->lock, flags);
  }
  
  static enum dma_status
  pl330_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
  		 struct dma_tx_state *txstate)
  {
  	struct dma_pl330_chan *pch = to_pchan(chan);
  	dma_cookie_t last_done, last_used;
  	int ret;
  
  	last_done = pch->completed;
  	last_used = chan->cookie;
  
  	ret = dma_async_is_complete(cookie, last_done, last_used);
  
  	dma_set_tx_state(txstate, last_done, last_used, 0);
  
  	return ret;
  }
  
  static void pl330_issue_pending(struct dma_chan *chan)
  {
  	pl330_tasklet((unsigned long) to_pchan(chan));
  }
  
  /*
   * We returned the last one of the circular list of descriptor(s)
   * from prep_xxx, so the argument to submit corresponds to the last
   * descriptor of the list.
   */
  static dma_cookie_t pl330_tx_submit(struct dma_async_tx_descriptor *tx)
  {
  	struct dma_pl330_desc *desc, *last = to_desc(tx);
  	struct dma_pl330_chan *pch = to_pchan(tx->chan);
  	dma_cookie_t cookie;
  	unsigned long flags;
  
  	spin_lock_irqsave(&pch->lock, flags);
  
  	/* Assign cookies to all nodes */
  	cookie = tx->chan->cookie;
  
  	while (!list_empty(&last->node)) {
  		desc = list_entry(last->node.next, struct dma_pl330_desc, node);
  
  		if (++cookie < 0)
  			cookie = 1;
  		desc->txd.cookie = cookie;
  
  		list_move_tail(&desc->node, &pch->work_list);
  	}
  
  	if (++cookie < 0)
  		cookie = 1;
  	last->txd.cookie = cookie;
  
  	list_add_tail(&last->node, &pch->work_list);
  
  	tx->chan->cookie = cookie;
  
  	spin_unlock_irqrestore(&pch->lock, flags);
  
  	return cookie;
  }
  
  static inline void _init_desc(struct dma_pl330_desc *desc)
  {
  	desc->pchan = NULL;
  	desc->req.x = &desc->px;
  	desc->req.token = desc;
  	desc->rqcfg.swap = SWAP_NO;
  	desc->rqcfg.privileged = 0;
  	desc->rqcfg.insnaccess = 0;
  	desc->rqcfg.scctl = SCCTRL0;
  	desc->rqcfg.dcctl = DCCTRL0;
  	desc->req.cfg = &desc->rqcfg;
  	desc->req.xfer_cb = dma_pl330_rqcb;
  	desc->txd.tx_submit = pl330_tx_submit;
  
  	INIT_LIST_HEAD(&desc->node);
  }
  
  /* Returns the number of descriptors added to the DMAC pool */
  int add_desc(struct dma_pl330_dmac *pdmac, gfp_t flg, int count)
  {
  	struct dma_pl330_desc *desc;
  	unsigned long flags;
  	int i;
  
  	if (!pdmac)
  		return 0;
  
  	desc = kmalloc(count * sizeof(*desc), flg);
  	if (!desc)
  		return 0;
  
  	spin_lock_irqsave(&pdmac->pool_lock, flags);
  
  	for (i = 0; i < count; i++) {
  		_init_desc(&desc[i]);
  		list_add_tail(&desc[i].node, &pdmac->desc_pool);
  	}
  
  	spin_unlock_irqrestore(&pdmac->pool_lock, flags);
  
  	return count;
  }
  
  static struct dma_pl330_desc *
  pluck_desc(struct dma_pl330_dmac *pdmac)
  {
  	struct dma_pl330_desc *desc = NULL;
  	unsigned long flags;
  
  	if (!pdmac)
  		return NULL;
  
  	spin_lock_irqsave(&pdmac->pool_lock, flags);
  
  	if (!list_empty(&pdmac->desc_pool)) {
  		desc = list_entry(pdmac->desc_pool.next,
  				struct dma_pl330_desc, node);
  
  		list_del_init(&desc->node);
  
  		desc->status = PREP;
  		desc->txd.callback = NULL;
  	}
  
  	spin_unlock_irqrestore(&pdmac->pool_lock, flags);
  
  	return desc;
  }
  
  static struct dma_pl330_desc *pl330_get_desc(struct dma_pl330_chan *pch)
  {
  	struct dma_pl330_dmac *pdmac = pch->dmac;
cd0725152   Thomas Abraham   DMA: PL330: Infer...
521
  	u8 *peri_id = pch->chan.private;
b3040e406   Jassi Brar   DMA: PL330: Add d...
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
  	struct dma_pl330_desc *desc;
  
  	/* Pluck one desc from the pool of DMAC */
  	desc = pluck_desc(pdmac);
  
  	/* If the DMAC pool is empty, alloc new */
  	if (!desc) {
  		if (!add_desc(pdmac, GFP_ATOMIC, 1))
  			return NULL;
  
  		/* Try again */
  		desc = pluck_desc(pdmac);
  		if (!desc) {
  			dev_err(pch->dmac->pif.dev,
  				"%s:%d ALERT!
  ", __func__, __LINE__);
  			return NULL;
  		}
  	}
  
  	/* Initialize the descriptor */
  	desc->pchan = pch;
  	desc->txd.cookie = 0;
  	async_tx_ack(&desc->txd);
cd0725152   Thomas Abraham   DMA: PL330: Infer...
546
  	desc->req.peri = peri_id ? pch->chan.chan_id : 0;
b3040e406   Jassi Brar   DMA: PL330: Add d...
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
  
  	dma_async_tx_descriptor_init(&desc->txd, &pch->chan);
  
  	return desc;
  }
  
  static inline void fill_px(struct pl330_xfer *px,
  		dma_addr_t dst, dma_addr_t src, size_t len)
  {
  	px->next = NULL;
  	px->bytes = len;
  	px->dst_addr = dst;
  	px->src_addr = src;
  }
  
  static struct dma_pl330_desc *
  __pl330_prep_dma_memcpy(struct dma_pl330_chan *pch, dma_addr_t dst,
  		dma_addr_t src, size_t len)
  {
  	struct dma_pl330_desc *desc = pl330_get_desc(pch);
  
  	if (!desc) {
  		dev_err(pch->dmac->pif.dev, "%s:%d Unable to fetch desc
  ",
  			__func__, __LINE__);
  		return NULL;
  	}
  
  	/*
  	 * Ideally we should lookout for reqs bigger than
  	 * those that can be programmed with 256 bytes of
  	 * MC buffer, but considering a req size is seldom
  	 * going to be word-unaligned and more than 200MB,
  	 * we take it easy.
  	 * Also, should the limit is reached we'd rather
  	 * have the platform increase MC buffer size than
  	 * complicating this API driver.
  	 */
  	fill_px(&desc->px, dst, src, len);
  
  	return desc;
  }
  
  /* Call after fixing burst size */
  static inline int get_burst_len(struct dma_pl330_desc *desc, size_t len)
  {
  	struct dma_pl330_chan *pch = desc->pchan;
  	struct pl330_info *pi = &pch->dmac->pif;
  	int burst_len;
  
  	burst_len = pi->pcfg.data_bus_width / 8;
  	burst_len *= pi->pcfg.data_buf_dep;
  	burst_len >>= desc->rqcfg.brst_size;
  
  	/* src/dst_burst_len can't be more than 16 */
  	if (burst_len > 16)
  		burst_len = 16;
  
  	while (burst_len > 1) {
  		if (!(len % (burst_len << desc->rqcfg.brst_size)))
  			break;
  		burst_len--;
  	}
  
  	return burst_len;
  }
42bc9cf45   Boojin Kim   DMA: PL330: Add D...
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
  static struct dma_async_tx_descriptor *pl330_prep_dma_cyclic(
  		struct dma_chan *chan, dma_addr_t dma_addr, size_t len,
  		size_t period_len, enum dma_data_direction direction)
  {
  	struct dma_pl330_desc *desc;
  	struct dma_pl330_chan *pch = to_pchan(chan);
  	dma_addr_t dst;
  	dma_addr_t src;
  
  	desc = pl330_get_desc(pch);
  	if (!desc) {
  		dev_err(pch->dmac->pif.dev, "%s:%d Unable to fetch desc
  ",
  			__func__, __LINE__);
  		return NULL;
  	}
  
  	switch (direction) {
  	case DMA_TO_DEVICE:
  		desc->rqcfg.src_inc = 1;
  		desc->rqcfg.dst_inc = 0;
cd0725152   Thomas Abraham   DMA: PL330: Infer...
634
  		desc->req.rqtype = MEMTODEV;
42bc9cf45   Boojin Kim   DMA: PL330: Add D...
635
636
637
638
639
640
  		src = dma_addr;
  		dst = pch->fifo_addr;
  		break;
  	case DMA_FROM_DEVICE:
  		desc->rqcfg.src_inc = 0;
  		desc->rqcfg.dst_inc = 1;
cd0725152   Thomas Abraham   DMA: PL330: Infer...
641
  		desc->req.rqtype = DEVTOMEM;
42bc9cf45   Boojin Kim   DMA: PL330: Add D...
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
  		src = pch->fifo_addr;
  		dst = dma_addr;
  		break;
  	default:
  		dev_err(pch->dmac->pif.dev, "%s:%d Invalid dma direction
  ",
  		__func__, __LINE__);
  		return NULL;
  	}
  
  	desc->rqcfg.brst_size = pch->burst_sz;
  	desc->rqcfg.brst_len = 1;
  
  	pch->cyclic = true;
  
  	fill_px(&desc->px, dst, src, period_len);
  
  	return &desc->txd;
  }
b3040e406   Jassi Brar   DMA: PL330: Add d...
661
662
663
664
665
666
  static struct dma_async_tx_descriptor *
  pl330_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dst,
  		dma_addr_t src, size_t len, unsigned long flags)
  {
  	struct dma_pl330_desc *desc;
  	struct dma_pl330_chan *pch = to_pchan(chan);
b3040e406   Jassi Brar   DMA: PL330: Add d...
667
668
  	struct pl330_info *pi;
  	int burst;
4e0e6109a   Rob Herring   dmaengine: pl330:...
669
  	if (unlikely(!pch || !len))
b3040e406   Jassi Brar   DMA: PL330: Add d...
670
  		return NULL;
b3040e406   Jassi Brar   DMA: PL330: Add d...
671
672
673
674
675
676
677
678
  	pi = &pch->dmac->pif;
  
  	desc = __pl330_prep_dma_memcpy(pch, dst, src, len);
  	if (!desc)
  		return NULL;
  
  	desc->rqcfg.src_inc = 1;
  	desc->rqcfg.dst_inc = 1;
cd0725152   Thomas Abraham   DMA: PL330: Infer...
679
  	desc->req.rqtype = MEMTOMEM;
b3040e406   Jassi Brar   DMA: PL330: Add d...
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
  
  	/* Select max possible burst size */
  	burst = pi->pcfg.data_bus_width / 8;
  
  	while (burst > 1) {
  		if (!(len % burst))
  			break;
  		burst /= 2;
  	}
  
  	desc->rqcfg.brst_size = 0;
  	while (burst != (1 << desc->rqcfg.brst_size))
  		desc->rqcfg.brst_size++;
  
  	desc->rqcfg.brst_len = get_burst_len(desc, len);
  
  	desc->txd.flags = flags;
  
  	return &desc->txd;
  }
  
  static struct dma_async_tx_descriptor *
  pl330_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
  		unsigned int sg_len, enum dma_data_direction direction,
  		unsigned long flg)
  {
  	struct dma_pl330_desc *first, *desc = NULL;
  	struct dma_pl330_chan *pch = to_pchan(chan);
b3040e406   Jassi Brar   DMA: PL330: Add d...
708
709
  	struct scatterlist *sg;
  	unsigned long flags;
1b9bb715e   Boojin Kim   DMA: PL330: Updat...
710
  	int i;
b3040e406   Jassi Brar   DMA: PL330: Add d...
711
  	dma_addr_t addr;
cd0725152   Thomas Abraham   DMA: PL330: Infer...
712
  	if (unlikely(!pch || !sgl || !sg_len))
b3040e406   Jassi Brar   DMA: PL330: Add d...
713
  		return NULL;
1b9bb715e   Boojin Kim   DMA: PL330: Updat...
714
  	addr = pch->fifo_addr;
b3040e406   Jassi Brar   DMA: PL330: Add d...
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
  
  	first = NULL;
  
  	for_each_sg(sgl, sg, sg_len, i) {
  
  		desc = pl330_get_desc(pch);
  		if (!desc) {
  			struct dma_pl330_dmac *pdmac = pch->dmac;
  
  			dev_err(pch->dmac->pif.dev,
  				"%s:%d Unable to fetch desc
  ",
  				__func__, __LINE__);
  			if (!first)
  				return NULL;
  
  			spin_lock_irqsave(&pdmac->pool_lock, flags);
  
  			while (!list_empty(&first->node)) {
  				desc = list_entry(first->node.next,
  						struct dma_pl330_desc, node);
  				list_move_tail(&desc->node, &pdmac->desc_pool);
  			}
  
  			list_move_tail(&first->node, &pdmac->desc_pool);
  
  			spin_unlock_irqrestore(&pdmac->pool_lock, flags);
  
  			return NULL;
  		}
  
  		if (!first)
  			first = desc;
  		else
  			list_add_tail(&desc->node, &first->node);
  
  		if (direction == DMA_TO_DEVICE) {
  			desc->rqcfg.src_inc = 1;
  			desc->rqcfg.dst_inc = 0;
cd0725152   Thomas Abraham   DMA: PL330: Infer...
754
  			desc->req.rqtype = MEMTODEV;
b3040e406   Jassi Brar   DMA: PL330: Add d...
755
756
757
758
759
  			fill_px(&desc->px,
  				addr, sg_dma_address(sg), sg_dma_len(sg));
  		} else {
  			desc->rqcfg.src_inc = 0;
  			desc->rqcfg.dst_inc = 1;
cd0725152   Thomas Abraham   DMA: PL330: Infer...
760
  			desc->req.rqtype = DEVTOMEM;
b3040e406   Jassi Brar   DMA: PL330: Add d...
761
762
763
  			fill_px(&desc->px,
  				sg_dma_address(sg), addr, sg_dma_len(sg));
  		}
1b9bb715e   Boojin Kim   DMA: PL330: Updat...
764
  		desc->rqcfg.brst_size = pch->burst_sz;
b3040e406   Jassi Brar   DMA: PL330: Add d...
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
  		desc->rqcfg.brst_len = 1;
  	}
  
  	/* Return the last desc in the chain */
  	desc->txd.flags = flg;
  	return &desc->txd;
  }
  
  static irqreturn_t pl330_irq_handler(int irq, void *data)
  {
  	if (pl330_update(data))
  		return IRQ_HANDLED;
  	else
  		return IRQ_NONE;
  }
  
  static int __devinit
aa25afad2   Russell King   ARM: amba: make p...
782
  pl330_probe(struct amba_device *adev, const struct amba_id *id)
b3040e406   Jassi Brar   DMA: PL330: Add d...
783
784
785
786
787
788
789
790
  {
  	struct dma_pl330_platdata *pdat;
  	struct dma_pl330_dmac *pdmac;
  	struct dma_pl330_chan *pch;
  	struct pl330_info *pi;
  	struct dma_device *pd;
  	struct resource *res;
  	int i, ret, irq;
4e0e6109a   Rob Herring   dmaengine: pl330:...
791
  	int num_chan;
b3040e406   Jassi Brar   DMA: PL330: Add d...
792
793
  
  	pdat = adev->dev.platform_data;
b3040e406   Jassi Brar   DMA: PL330: Add d...
794
  	/* Allocate a new DMAC and its Channels */
4e0e6109a   Rob Herring   dmaengine: pl330:...
795
  	pdmac = kzalloc(sizeof(*pdmac), GFP_KERNEL);
b3040e406   Jassi Brar   DMA: PL330: Add d...
796
797
798
799
800
801
802
803
804
  	if (!pdmac) {
  		dev_err(&adev->dev, "unable to allocate mem
  ");
  		return -ENOMEM;
  	}
  
  	pi = &pdmac->pif;
  	pi->dev = &adev->dev;
  	pi->pl330_data = NULL;
4e0e6109a   Rob Herring   dmaengine: pl330:...
805
  	pi->mcbufsz = pdat ? pdat->mcbuf_sz : 0;
b3040e406   Jassi Brar   DMA: PL330: Add d...
806
807
808
809
810
811
812
813
814
  
  	res = &adev->res;
  	request_mem_region(res->start, resource_size(res), "dma-pl330");
  
  	pi->base = ioremap(res->start, resource_size(res));
  	if (!pi->base) {
  		ret = -ENXIO;
  		goto probe_err1;
  	}
a2f5203fe   Boojin Kim   DMA: PL330: Add s...
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
  	pdmac->clk = clk_get(&adev->dev, "dma");
  	if (IS_ERR(pdmac->clk)) {
  		dev_err(&adev->dev, "Cannot get operation clock.
  ");
  		ret = -EINVAL;
  		goto probe_err1;
  	}
  
  	amba_set_drvdata(adev, pdmac);
  
  #ifdef CONFIG_PM_RUNTIME
  	/* to use the runtime PM helper functions */
  	pm_runtime_enable(&adev->dev);
  
  	/* enable the power domain */
  	if (pm_runtime_get_sync(&adev->dev)) {
  		dev_err(&adev->dev, "failed to get runtime pm
  ");
  		ret = -ENODEV;
  		goto probe_err1;
  	}
  #else
  	/* enable dma clk */
  	clk_enable(pdmac->clk);
  #endif
b3040e406   Jassi Brar   DMA: PL330: Add d...
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
  	irq = adev->irq[0];
  	ret = request_irq(irq, pl330_irq_handler, 0,
  			dev_name(&adev->dev), pi);
  	if (ret)
  		goto probe_err2;
  
  	ret = pl330_add(pi);
  	if (ret)
  		goto probe_err3;
  
  	INIT_LIST_HEAD(&pdmac->desc_pool);
  	spin_lock_init(&pdmac->pool_lock);
  
  	/* Create a descriptor pool of default size */
  	if (!add_desc(pdmac, GFP_KERNEL, NR_DEFAULT_DESC))
  		dev_warn(&adev->dev, "unable to allocate desc
  ");
  
  	pd = &pdmac->ddma;
  	INIT_LIST_HEAD(&pd->channels);
  
  	/* Initialize channel parameters */
93ed55441   Thomas Abraham   DMA: PL330: Add d...
862
863
  	num_chan = max(pdat ? pdat->nr_valid_peri : (u8)pi->pcfg.num_peri,
  			(u8)pi->pcfg.num_chan);
4e0e6109a   Rob Herring   dmaengine: pl330:...
864
  	pdmac->peripherals = kzalloc(num_chan * sizeof(*pch), GFP_KERNEL);
b3040e406   Jassi Brar   DMA: PL330: Add d...
865

4e0e6109a   Rob Herring   dmaengine: pl330:...
866
867
  	for (i = 0; i < num_chan; i++) {
  		pch = &pdmac->peripherals[i];
93ed55441   Thomas Abraham   DMA: PL330: Add d...
868
869
870
871
  		if (!adev->dev.of_node)
  			pch->chan.private = pdat ? &pdat->peri_id[i] : NULL;
  		else
  			pch->chan.private = adev->dev.of_node;
b3040e406   Jassi Brar   DMA: PL330: Add d...
872
873
874
875
  
  		INIT_LIST_HEAD(&pch->work_list);
  		spin_lock_init(&pch->lock);
  		pch->pl330_chid = NULL;
b3040e406   Jassi Brar   DMA: PL330: Add d...
876
  		pch->chan.device = pd;
b3040e406   Jassi Brar   DMA: PL330: Add d...
877
878
879
  		pch->dmac = pdmac;
  
  		/* Add the channel to the DMAC list */
b3040e406   Jassi Brar   DMA: PL330: Add d...
880
881
882
883
  		list_add_tail(&pch->chan.device_node, &pd->channels);
  	}
  
  	pd->dev = &adev->dev;
93ed55441   Thomas Abraham   DMA: PL330: Add d...
884
  	if (pdat) {
cd0725152   Thomas Abraham   DMA: PL330: Infer...
885
  		pd->cap_mask = pdat->cap_mask;
93ed55441   Thomas Abraham   DMA: PL330: Add d...
886
  	} else {
cd0725152   Thomas Abraham   DMA: PL330: Infer...
887
  		dma_cap_set(DMA_MEMCPY, pd->cap_mask);
93ed55441   Thomas Abraham   DMA: PL330: Add d...
888
889
890
891
892
  		if (pi->pcfg.num_peri) {
  			dma_cap_set(DMA_SLAVE, pd->cap_mask);
  			dma_cap_set(DMA_CYCLIC, pd->cap_mask);
  		}
  	}
b3040e406   Jassi Brar   DMA: PL330: Add d...
893
894
895
896
  
  	pd->device_alloc_chan_resources = pl330_alloc_chan_resources;
  	pd->device_free_chan_resources = pl330_free_chan_resources;
  	pd->device_prep_dma_memcpy = pl330_prep_dma_memcpy;
42bc9cf45   Boojin Kim   DMA: PL330: Add D...
897
  	pd->device_prep_dma_cyclic = pl330_prep_dma_cyclic;
b3040e406   Jassi Brar   DMA: PL330: Add d...
898
899
900
901
902
903
904
905
906
907
908
  	pd->device_tx_status = pl330_tx_status;
  	pd->device_prep_slave_sg = pl330_prep_slave_sg;
  	pd->device_control = pl330_control;
  	pd->device_issue_pending = pl330_issue_pending;
  
  	ret = dma_async_device_register(pd);
  	if (ret) {
  		dev_err(&adev->dev, "unable to register DMAC
  ");
  		goto probe_err4;
  	}
b3040e406   Jassi Brar   DMA: PL330: Add d...
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
  	dev_info(&adev->dev,
  		"Loaded driver for PL330 DMAC-%d
  ", adev->periphid);
  	dev_info(&adev->dev,
  		"\tDBUFF-%ux%ubytes Num_Chans-%u Num_Peri-%u Num_Events-%u
  ",
  		pi->pcfg.data_buf_dep,
  		pi->pcfg.data_bus_width / 8, pi->pcfg.num_chan,
  		pi->pcfg.num_peri, pi->pcfg.num_events);
  
  	return 0;
  
  probe_err4:
  	pl330_del(pi);
  probe_err3:
  	free_irq(irq, pi);
  probe_err2:
  	iounmap(pi->base);
  probe_err1:
  	release_mem_region(res->start, resource_size(res));
  	kfree(pdmac);
  
  	return ret;
  }
  
  static int __devexit pl330_remove(struct amba_device *adev)
  {
  	struct dma_pl330_dmac *pdmac = amba_get_drvdata(adev);
  	struct dma_pl330_chan *pch, *_p;
  	struct pl330_info *pi;
  	struct resource *res;
  	int irq;
  
  	if (!pdmac)
  		return 0;
  
  	amba_set_drvdata(adev, NULL);
  
  	/* Idle the DMAC */
  	list_for_each_entry_safe(pch, _p, &pdmac->ddma.channels,
  			chan.device_node) {
  
  		/* Remove the channel */
  		list_del(&pch->chan.device_node);
  
  		/* Flush the channel */
  		pl330_control(&pch->chan, DMA_TERMINATE_ALL, 0);
  		pl330_free_chan_resources(&pch->chan);
  	}
  
  	pi = &pdmac->pif;
  
  	pl330_del(pi);
  
  	irq = adev->irq[0];
  	free_irq(irq, pi);
  
  	iounmap(pi->base);
  
  	res = &adev->res;
  	release_mem_region(res->start, resource_size(res));
a2f5203fe   Boojin Kim   DMA: PL330: Add s...
970
971
972
973
974
975
  #ifdef CONFIG_PM_RUNTIME
  	pm_runtime_put(&adev->dev);
  	pm_runtime_disable(&adev->dev);
  #else
  	clk_disable(pdmac->clk);
  #endif
b3040e406   Jassi Brar   DMA: PL330: Add d...
976
977
978
979
980
981
982
983
984
985
986
987
  	kfree(pdmac);
  
  	return 0;
  }
  
  static struct amba_id pl330_ids[] = {
  	{
  		.id	= 0x00041330,
  		.mask	= 0x000fffff,
  	},
  	{ 0, 0 },
  };
e8fa516a4   Dave Martin   dmaengine: pl330:...
988
  MODULE_DEVICE_TABLE(amba, pl330_ids);
a2f5203fe   Boojin Kim   DMA: PL330: Add s...
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
  #ifdef CONFIG_PM_RUNTIME
  static int pl330_runtime_suspend(struct device *dev)
  {
  	struct dma_pl330_dmac *pdmac = dev_get_drvdata(dev);
  
  	if (!pdmac) {
  		dev_err(dev, "failed to get dmac
  ");
  		return -ENODEV;
  	}
  
  	clk_disable(pdmac->clk);
  
  	return 0;
  }
  
  static int pl330_runtime_resume(struct device *dev)
  {
  	struct dma_pl330_dmac *pdmac = dev_get_drvdata(dev);
  
  	if (!pdmac) {
  		dev_err(dev, "failed to get dmac
  ");
  		return -ENODEV;
  	}
  
  	clk_enable(pdmac->clk);
  
  	return 0;
  }
  #else
  #define pl330_runtime_suspend	NULL
  #define pl330_runtime_resume	NULL
  #endif /* CONFIG_PM_RUNTIME */
  
  static const struct dev_pm_ops pl330_pm_ops = {
  	.runtime_suspend = pl330_runtime_suspend,
  	.runtime_resume = pl330_runtime_resume,
  };
b3040e406   Jassi Brar   DMA: PL330: Add d...
1028
1029
1030
1031
  static struct amba_driver pl330_driver = {
  	.drv = {
  		.owner = THIS_MODULE,
  		.name = "dma-pl330",
a2f5203fe   Boojin Kim   DMA: PL330: Add s...
1032
  		.pm = &pl330_pm_ops,
b3040e406   Jassi Brar   DMA: PL330: Add d...
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
  	},
  	.id_table = pl330_ids,
  	.probe = pl330_probe,
  	.remove = pl330_remove,
  };
  
  static int __init pl330_init(void)
  {
  	return amba_driver_register(&pl330_driver);
  }
  module_init(pl330_init);
  
  static void __exit pl330_exit(void)
  {
  	amba_driver_unregister(&pl330_driver);
  	return;
  }
  module_exit(pl330_exit);
  
  MODULE_AUTHOR("Jaswinder Singh <jassi.brar@samsung.com>");
  MODULE_DESCRIPTION("API Driver for PL330 DMAC");
  MODULE_LICENSE("GPL");