diff options
Diffstat (limited to 'arch/arm/mach-s3c64xx/dma.c')
-rw-r--r-- | arch/arm/mach-s3c64xx/dma.c | 749 |
1 files changed, 749 insertions, 0 deletions
diff --git a/arch/arm/mach-s3c64xx/dma.c b/arch/arm/mach-s3c64xx/dma.c new file mode 100644 index 000000000000..b62bdf18dca4 --- /dev/null +++ b/arch/arm/mach-s3c64xx/dma.c | |||
@@ -0,0 +1,749 @@ | |||
1 | /* linux/arch/arm/plat-s3c64xx/dma.c | ||
2 | * | ||
3 | * Copyright 2009 Openmoko, Inc. | ||
4 | * Copyright 2009 Simtec Electronics | ||
5 | * Ben Dooks <ben@simtec.co.uk> | ||
6 | * http://armlinux.simtec.co.uk/ | ||
7 | * | ||
8 | * S3C64XX DMA core | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU General Public License version 2 as | ||
12 | * published by the Free Software Foundation. | ||
13 | */ | ||
14 | |||
15 | #include <linux/kernel.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/interrupt.h> | ||
18 | #include <linux/dmapool.h> | ||
19 | #include <linux/sysdev.h> | ||
20 | #include <linux/errno.h> | ||
21 | #include <linux/delay.h> | ||
22 | #include <linux/clk.h> | ||
23 | #include <linux/err.h> | ||
24 | #include <linux/io.h> | ||
25 | |||
26 | #include <mach/dma.h> | ||
27 | #include <mach/map.h> | ||
28 | #include <mach/irqs.h> | ||
29 | |||
30 | #include <mach/regs-sys.h> | ||
31 | |||
32 | #include <asm/hardware/pl080.h> | ||
33 | |||
34 | /* dma channel state information */ | ||
35 | |||
36 | struct s3c64xx_dmac { | ||
37 | struct sys_device sysdev; | ||
38 | struct clk *clk; | ||
39 | void __iomem *regs; | ||
40 | struct s3c2410_dma_chan *channels; | ||
41 | enum dma_ch chanbase; | ||
42 | }; | ||
43 | |||
44 | /* pool to provide LLI buffers */ | ||
45 | static struct dma_pool *dma_pool; | ||
46 | |||
47 | /* Debug configuration and code */ | ||
48 | |||
49 | static unsigned char debug_show_buffs = 0; | ||
50 | |||
51 | static void dbg_showchan(struct s3c2410_dma_chan *chan) | ||
52 | { | ||
53 | pr_debug("DMA%d: %08x->%08x L %08x C %08x,%08x S %08x\n", | ||
54 | chan->number, | ||
55 | readl(chan->regs + PL080_CH_SRC_ADDR), | ||
56 | readl(chan->regs + PL080_CH_DST_ADDR), | ||
57 | readl(chan->regs + PL080_CH_LLI), | ||
58 | readl(chan->regs + PL080_CH_CONTROL), | ||
59 | readl(chan->regs + PL080S_CH_CONTROL2), | ||
60 | readl(chan->regs + PL080S_CH_CONFIG)); | ||
61 | } | ||
62 | |||
63 | static void show_lli(struct pl080s_lli *lli) | ||
64 | { | ||
65 | pr_debug("LLI[%p] %08x->%08x, NL %08x C %08x,%08x\n", | ||
66 | lli, lli->src_addr, lli->dst_addr, lli->next_lli, | ||
67 | lli->control0, lli->control1); | ||
68 | } | ||
69 | |||
70 | static void dbg_showbuffs(struct s3c2410_dma_chan *chan) | ||
71 | { | ||
72 | struct s3c64xx_dma_buff *ptr; | ||
73 | struct s3c64xx_dma_buff *end; | ||
74 | |||
75 | pr_debug("DMA%d: buffs next %p, curr %p, end %p\n", | ||
76 | chan->number, chan->next, chan->curr, chan->end); | ||
77 | |||
78 | ptr = chan->next; | ||
79 | end = chan->end; | ||
80 | |||
81 | if (debug_show_buffs) { | ||
82 | for (; ptr != NULL; ptr = ptr->next) { | ||
83 | pr_debug("DMA%d: %08x ", | ||
84 | chan->number, ptr->lli_dma); | ||
85 | show_lli(ptr->lli); | ||
86 | } | ||
87 | } | ||
88 | } | ||
89 | |||
90 | /* End of Debug */ | ||
91 | |||
92 | static struct s3c2410_dma_chan *s3c64xx_dma_map_channel(unsigned int channel) | ||
93 | { | ||
94 | struct s3c2410_dma_chan *chan; | ||
95 | unsigned int start, offs; | ||
96 | |||
97 | start = 0; | ||
98 | |||
99 | if (channel >= DMACH_PCM1_TX) | ||
100 | start = 8; | ||
101 | |||
102 | for (offs = 0; offs < 8; offs++) { | ||
103 | chan = &s3c2410_chans[start + offs]; | ||
104 | if (!chan->in_use) | ||
105 | goto found; | ||
106 | } | ||
107 | |||
108 | return NULL; | ||
109 | |||
110 | found: | ||
111 | s3c_dma_chan_map[channel] = chan; | ||
112 | return chan; | ||
113 | } | ||
114 | |||
115 | int s3c2410_dma_config(unsigned int channel, int xferunit) | ||
116 | { | ||
117 | struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); | ||
118 | |||
119 | if (chan == NULL) | ||
120 | return -EINVAL; | ||
121 | |||
122 | switch (xferunit) { | ||
123 | case 1: | ||
124 | chan->hw_width = 0; | ||
125 | break; | ||
126 | case 2: | ||
127 | chan->hw_width = 1; | ||
128 | break; | ||
129 | case 4: | ||
130 | chan->hw_width = 2; | ||
131 | break; | ||
132 | default: | ||
133 | printk(KERN_ERR "%s: illegal width %d\n", __func__, xferunit); | ||
134 | return -EINVAL; | ||
135 | } | ||
136 | |||
137 | return 0; | ||
138 | } | ||
139 | EXPORT_SYMBOL(s3c2410_dma_config); | ||
140 | |||
141 | static void s3c64xx_dma_fill_lli(struct s3c2410_dma_chan *chan, | ||
142 | struct pl080s_lli *lli, | ||
143 | dma_addr_t data, int size) | ||
144 | { | ||
145 | dma_addr_t src, dst; | ||
146 | u32 control0, control1; | ||
147 | |||
148 | switch (chan->source) { | ||
149 | case S3C2410_DMASRC_HW: | ||
150 | src = chan->dev_addr; | ||
151 | dst = data; | ||
152 | control0 = PL080_CONTROL_SRC_AHB2; | ||
153 | control0 |= PL080_CONTROL_DST_INCR; | ||
154 | break; | ||
155 | |||
156 | case S3C2410_DMASRC_MEM: | ||
157 | src = data; | ||
158 | dst = chan->dev_addr; | ||
159 | control0 = PL080_CONTROL_DST_AHB2; | ||
160 | control0 |= PL080_CONTROL_SRC_INCR; | ||
161 | break; | ||
162 | default: | ||
163 | BUG(); | ||
164 | } | ||
165 | |||
166 | /* note, we do not currently setup any of the burst controls */ | ||
167 | |||
168 | control1 = size >> chan->hw_width; /* size in no of xfers */ | ||
169 | control0 |= PL080_CONTROL_PROT_SYS; /* always in priv. mode */ | ||
170 | control0 |= PL080_CONTROL_TC_IRQ_EN; /* always fire IRQ */ | ||
171 | control0 |= (u32)chan->hw_width << PL080_CONTROL_DWIDTH_SHIFT; | ||
172 | control0 |= (u32)chan->hw_width << PL080_CONTROL_SWIDTH_SHIFT; | ||
173 | |||
174 | lli->src_addr = src; | ||
175 | lli->dst_addr = dst; | ||
176 | lli->next_lli = 0; | ||
177 | lli->control0 = control0; | ||
178 | lli->control1 = control1; | ||
179 | } | ||
180 | |||
181 | static void s3c64xx_lli_to_regs(struct s3c2410_dma_chan *chan, | ||
182 | struct pl080s_lli *lli) | ||
183 | { | ||
184 | void __iomem *regs = chan->regs; | ||
185 | |||
186 | pr_debug("%s: LLI %p => regs\n", __func__, lli); | ||
187 | show_lli(lli); | ||
188 | |||
189 | writel(lli->src_addr, regs + PL080_CH_SRC_ADDR); | ||
190 | writel(lli->dst_addr, regs + PL080_CH_DST_ADDR); | ||
191 | writel(lli->next_lli, regs + PL080_CH_LLI); | ||
192 | writel(lli->control0, regs + PL080_CH_CONTROL); | ||
193 | writel(lli->control1, regs + PL080S_CH_CONTROL2); | ||
194 | } | ||
195 | |||
196 | static int s3c64xx_dma_start(struct s3c2410_dma_chan *chan) | ||
197 | { | ||
198 | struct s3c64xx_dmac *dmac = chan->dmac; | ||
199 | u32 config; | ||
200 | u32 bit = chan->bit; | ||
201 | |||
202 | dbg_showchan(chan); | ||
203 | |||
204 | pr_debug("%s: clearing interrupts\n", __func__); | ||
205 | |||
206 | /* clear interrupts */ | ||
207 | writel(bit, dmac->regs + PL080_TC_CLEAR); | ||
208 | writel(bit, dmac->regs + PL080_ERR_CLEAR); | ||
209 | |||
210 | pr_debug("%s: starting channel\n", __func__); | ||
211 | |||
212 | config = readl(chan->regs + PL080S_CH_CONFIG); | ||
213 | config |= PL080_CONFIG_ENABLE; | ||
214 | |||
215 | pr_debug("%s: writing config %08x\n", __func__, config); | ||
216 | writel(config, chan->regs + PL080S_CH_CONFIG); | ||
217 | |||
218 | return 0; | ||
219 | } | ||
220 | |||
221 | static int s3c64xx_dma_stop(struct s3c2410_dma_chan *chan) | ||
222 | { | ||
223 | u32 config; | ||
224 | int timeout; | ||
225 | |||
226 | pr_debug("%s: stopping channel\n", __func__); | ||
227 | |||
228 | dbg_showchan(chan); | ||
229 | |||
230 | config = readl(chan->regs + PL080S_CH_CONFIG); | ||
231 | config |= PL080_CONFIG_HALT; | ||
232 | writel(config, chan->regs + PL080S_CH_CONFIG); | ||
233 | |||
234 | timeout = 1000; | ||
235 | do { | ||
236 | config = readl(chan->regs + PL080S_CH_CONFIG); | ||
237 | pr_debug("%s: %d - config %08x\n", __func__, timeout, config); | ||
238 | if (config & PL080_CONFIG_ACTIVE) | ||
239 | udelay(10); | ||
240 | else | ||
241 | break; | ||
242 | } while (--timeout > 0); | ||
243 | |||
244 | if (config & PL080_CONFIG_ACTIVE) { | ||
245 | printk(KERN_ERR "%s: channel still active\n", __func__); | ||
246 | return -EFAULT; | ||
247 | } | ||
248 | |||
249 | config = readl(chan->regs + PL080S_CH_CONFIG); | ||
250 | config &= ~PL080_CONFIG_ENABLE; | ||
251 | writel(config, chan->regs + PL080S_CH_CONFIG); | ||
252 | |||
253 | return 0; | ||
254 | } | ||
255 | |||
256 | static inline void s3c64xx_dma_bufffdone(struct s3c2410_dma_chan *chan, | ||
257 | struct s3c64xx_dma_buff *buf, | ||
258 | enum s3c2410_dma_buffresult result) | ||
259 | { | ||
260 | if (chan->callback_fn != NULL) | ||
261 | (chan->callback_fn)(chan, buf->pw, 0, result); | ||
262 | } | ||
263 | |||
264 | static void s3c64xx_dma_freebuff(struct s3c64xx_dma_buff *buff) | ||
265 | { | ||
266 | dma_pool_free(dma_pool, buff->lli, buff->lli_dma); | ||
267 | kfree(buff); | ||
268 | } | ||
269 | |||
270 | static int s3c64xx_dma_flush(struct s3c2410_dma_chan *chan) | ||
271 | { | ||
272 | struct s3c64xx_dma_buff *buff, *next; | ||
273 | u32 config; | ||
274 | |||
275 | dbg_showchan(chan); | ||
276 | |||
277 | pr_debug("%s: flushing channel\n", __func__); | ||
278 | |||
279 | config = readl(chan->regs + PL080S_CH_CONFIG); | ||
280 | config &= ~PL080_CONFIG_ENABLE; | ||
281 | writel(config, chan->regs + PL080S_CH_CONFIG); | ||
282 | |||
283 | /* dump all the buffers associated with this channel */ | ||
284 | |||
285 | for (buff = chan->curr; buff != NULL; buff = next) { | ||
286 | next = buff->next; | ||
287 | pr_debug("%s: buff %p (next %p)\n", __func__, buff, buff->next); | ||
288 | |||
289 | s3c64xx_dma_bufffdone(chan, buff, S3C2410_RES_ABORT); | ||
290 | s3c64xx_dma_freebuff(buff); | ||
291 | } | ||
292 | |||
293 | chan->curr = chan->next = chan->end = NULL; | ||
294 | |||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | int s3c2410_dma_ctrl(unsigned int channel, enum s3c2410_chan_op op) | ||
299 | { | ||
300 | struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); | ||
301 | |||
302 | WARN_ON(!chan); | ||
303 | if (!chan) | ||
304 | return -EINVAL; | ||
305 | |||
306 | switch (op) { | ||
307 | case S3C2410_DMAOP_START: | ||
308 | return s3c64xx_dma_start(chan); | ||
309 | |||
310 | case S3C2410_DMAOP_STOP: | ||
311 | return s3c64xx_dma_stop(chan); | ||
312 | |||
313 | case S3C2410_DMAOP_FLUSH: | ||
314 | return s3c64xx_dma_flush(chan); | ||
315 | |||
316 | /* belive PAUSE/RESUME are no-ops */ | ||
317 | case S3C2410_DMAOP_PAUSE: | ||
318 | case S3C2410_DMAOP_RESUME: | ||
319 | case S3C2410_DMAOP_STARTED: | ||
320 | case S3C2410_DMAOP_TIMEOUT: | ||
321 | return 0; | ||
322 | } | ||
323 | |||
324 | return -ENOENT; | ||
325 | } | ||
326 | EXPORT_SYMBOL(s3c2410_dma_ctrl); | ||
327 | |||
328 | /* s3c2410_dma_enque | ||
329 | * | ||
330 | */ | ||
331 | |||
332 | int s3c2410_dma_enqueue(unsigned int channel, void *id, | ||
333 | dma_addr_t data, int size) | ||
334 | { | ||
335 | struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); | ||
336 | struct s3c64xx_dma_buff *next; | ||
337 | struct s3c64xx_dma_buff *buff; | ||
338 | struct pl080s_lli *lli; | ||
339 | unsigned long flags; | ||
340 | int ret; | ||
341 | |||
342 | WARN_ON(!chan); | ||
343 | if (!chan) | ||
344 | return -EINVAL; | ||
345 | |||
346 | buff = kzalloc(sizeof(struct s3c64xx_dma_buff), GFP_ATOMIC); | ||
347 | if (!buff) { | ||
348 | printk(KERN_ERR "%s: no memory for buffer\n", __func__); | ||
349 | return -ENOMEM; | ||
350 | } | ||
351 | |||
352 | lli = dma_pool_alloc(dma_pool, GFP_ATOMIC, &buff->lli_dma); | ||
353 | if (!lli) { | ||
354 | printk(KERN_ERR "%s: no memory for lli\n", __func__); | ||
355 | ret = -ENOMEM; | ||
356 | goto err_buff; | ||
357 | } | ||
358 | |||
359 | pr_debug("%s: buff %p, dp %08x lli (%p, %08x) %d\n", | ||
360 | __func__, buff, data, lli, (u32)buff->lli_dma, size); | ||
361 | |||
362 | buff->lli = lli; | ||
363 | buff->pw = id; | ||
364 | |||
365 | s3c64xx_dma_fill_lli(chan, lli, data, size); | ||
366 | |||
367 | local_irq_save(flags); | ||
368 | |||
369 | if ((next = chan->next) != NULL) { | ||
370 | struct s3c64xx_dma_buff *end = chan->end; | ||
371 | struct pl080s_lli *endlli = end->lli; | ||
372 | |||
373 | pr_debug("enquing onto channel\n"); | ||
374 | |||
375 | end->next = buff; | ||
376 | endlli->next_lli = buff->lli_dma; | ||
377 | |||
378 | if (chan->flags & S3C2410_DMAF_CIRCULAR) { | ||
379 | struct s3c64xx_dma_buff *curr = chan->curr; | ||
380 | lli->next_lli = curr->lli_dma; | ||
381 | } | ||
382 | |||
383 | if (next == chan->curr) { | ||
384 | writel(buff->lli_dma, chan->regs + PL080_CH_LLI); | ||
385 | chan->next = buff; | ||
386 | } | ||
387 | |||
388 | show_lli(endlli); | ||
389 | chan->end = buff; | ||
390 | } else { | ||
391 | pr_debug("enquing onto empty channel\n"); | ||
392 | |||
393 | chan->curr = buff; | ||
394 | chan->next = buff; | ||
395 | chan->end = buff; | ||
396 | |||
397 | s3c64xx_lli_to_regs(chan, lli); | ||
398 | } | ||
399 | |||
400 | local_irq_restore(flags); | ||
401 | |||
402 | show_lli(lli); | ||
403 | |||
404 | dbg_showchan(chan); | ||
405 | dbg_showbuffs(chan); | ||
406 | return 0; | ||
407 | |||
408 | err_buff: | ||
409 | kfree(buff); | ||
410 | return ret; | ||
411 | } | ||
412 | |||
413 | EXPORT_SYMBOL(s3c2410_dma_enqueue); | ||
414 | |||
415 | |||
416 | int s3c2410_dma_devconfig(int channel, | ||
417 | enum s3c2410_dmasrc source, | ||
418 | unsigned long devaddr) | ||
419 | { | ||
420 | struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); | ||
421 | u32 peripheral; | ||
422 | u32 config = 0; | ||
423 | |||
424 | pr_debug("%s: channel %d, source %d, dev %08lx, chan %p\n", | ||
425 | __func__, channel, source, devaddr, chan); | ||
426 | |||
427 | WARN_ON(!chan); | ||
428 | if (!chan) | ||
429 | return -EINVAL; | ||
430 | |||
431 | peripheral = (chan->peripheral & 0xf); | ||
432 | chan->source = source; | ||
433 | chan->dev_addr = devaddr; | ||
434 | |||
435 | pr_debug("%s: peripheral %d\n", __func__, peripheral); | ||
436 | |||
437 | switch (source) { | ||
438 | case S3C2410_DMASRC_HW: | ||
439 | config = 2 << PL080_CONFIG_FLOW_CONTROL_SHIFT; | ||
440 | config |= peripheral << PL080_CONFIG_SRC_SEL_SHIFT; | ||
441 | break; | ||
442 | case S3C2410_DMASRC_MEM: | ||
443 | config = 1 << PL080_CONFIG_FLOW_CONTROL_SHIFT; | ||
444 | config |= peripheral << PL080_CONFIG_DST_SEL_SHIFT; | ||
445 | break; | ||
446 | default: | ||
447 | printk(KERN_ERR "%s: bad source\n", __func__); | ||
448 | return -EINVAL; | ||
449 | } | ||
450 | |||
451 | /* allow TC and ERR interrupts */ | ||
452 | config |= PL080_CONFIG_TC_IRQ_MASK; | ||
453 | config |= PL080_CONFIG_ERR_IRQ_MASK; | ||
454 | |||
455 | pr_debug("%s: config %08x\n", __func__, config); | ||
456 | |||
457 | writel(config, chan->regs + PL080S_CH_CONFIG); | ||
458 | |||
459 | return 0; | ||
460 | } | ||
461 | EXPORT_SYMBOL(s3c2410_dma_devconfig); | ||
462 | |||
463 | |||
464 | int s3c2410_dma_getposition(unsigned int channel, | ||
465 | dma_addr_t *src, dma_addr_t *dst) | ||
466 | { | ||
467 | struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); | ||
468 | |||
469 | WARN_ON(!chan); | ||
470 | if (!chan) | ||
471 | return -EINVAL; | ||
472 | |||
473 | if (src != NULL) | ||
474 | *src = readl(chan->regs + PL080_CH_SRC_ADDR); | ||
475 | |||
476 | if (dst != NULL) | ||
477 | *dst = readl(chan->regs + PL080_CH_DST_ADDR); | ||
478 | |||
479 | return 0; | ||
480 | } | ||
481 | EXPORT_SYMBOL(s3c2410_dma_getposition); | ||
482 | |||
483 | /* s3c2410_request_dma | ||
484 | * | ||
485 | * get control of an dma channel | ||
486 | */ | ||
487 | |||
488 | int s3c2410_dma_request(unsigned int channel, | ||
489 | struct s3c2410_dma_client *client, | ||
490 | void *dev) | ||
491 | { | ||
492 | struct s3c2410_dma_chan *chan; | ||
493 | unsigned long flags; | ||
494 | |||
495 | pr_debug("dma%d: s3c2410_request_dma: client=%s, dev=%p\n", | ||
496 | channel, client->name, dev); | ||
497 | |||
498 | local_irq_save(flags); | ||
499 | |||
500 | chan = s3c64xx_dma_map_channel(channel); | ||
501 | if (chan == NULL) { | ||
502 | local_irq_restore(flags); | ||
503 | return -EBUSY; | ||
504 | } | ||
505 | |||
506 | dbg_showchan(chan); | ||
507 | |||
508 | chan->client = client; | ||
509 | chan->in_use = 1; | ||
510 | chan->peripheral = channel; | ||
511 | |||
512 | local_irq_restore(flags); | ||
513 | |||
514 | /* need to setup */ | ||
515 | |||
516 | pr_debug("%s: channel initialised, %p\n", __func__, chan); | ||
517 | |||
518 | return chan->number | DMACH_LOW_LEVEL; | ||
519 | } | ||
520 | |||
521 | EXPORT_SYMBOL(s3c2410_dma_request); | ||
522 | |||
523 | /* s3c2410_dma_free | ||
524 | * | ||
525 | * release the given channel back to the system, will stop and flush | ||
526 | * any outstanding transfers, and ensure the channel is ready for the | ||
527 | * next claimant. | ||
528 | * | ||
529 | * Note, although a warning is currently printed if the freeing client | ||
530 | * info is not the same as the registrant's client info, the free is still | ||
531 | * allowed to go through. | ||
532 | */ | ||
533 | |||
534 | int s3c2410_dma_free(unsigned int channel, struct s3c2410_dma_client *client) | ||
535 | { | ||
536 | struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); | ||
537 | unsigned long flags; | ||
538 | |||
539 | if (chan == NULL) | ||
540 | return -EINVAL; | ||
541 | |||
542 | local_irq_save(flags); | ||
543 | |||
544 | if (chan->client != client) { | ||
545 | printk(KERN_WARNING "dma%d: possible free from different client (channel %p, passed %p)\n", | ||
546 | channel, chan->client, client); | ||
547 | } | ||
548 | |||
549 | /* sort out stopping and freeing the channel */ | ||
550 | |||
551 | |||
552 | chan->client = NULL; | ||
553 | chan->in_use = 0; | ||
554 | |||
555 | if (!(channel & DMACH_LOW_LEVEL)) | ||
556 | s3c_dma_chan_map[channel] = NULL; | ||
557 | |||
558 | local_irq_restore(flags); | ||
559 | |||
560 | return 0; | ||
561 | } | ||
562 | |||
563 | EXPORT_SYMBOL(s3c2410_dma_free); | ||
564 | |||
565 | static irqreturn_t s3c64xx_dma_irq(int irq, void *pw) | ||
566 | { | ||
567 | struct s3c64xx_dmac *dmac = pw; | ||
568 | struct s3c2410_dma_chan *chan; | ||
569 | enum s3c2410_dma_buffresult res; | ||
570 | u32 tcstat, errstat; | ||
571 | u32 bit; | ||
572 | int offs; | ||
573 | |||
574 | tcstat = readl(dmac->regs + PL080_TC_STATUS); | ||
575 | errstat = readl(dmac->regs + PL080_ERR_STATUS); | ||
576 | |||
577 | for (offs = 0, bit = 1; offs < 8; offs++, bit <<= 1) { | ||
578 | struct s3c64xx_dma_buff *buff; | ||
579 | |||
580 | if (!(errstat & bit) && !(tcstat & bit)) | ||
581 | continue; | ||
582 | |||
583 | chan = dmac->channels + offs; | ||
584 | res = S3C2410_RES_ERR; | ||
585 | |||
586 | if (tcstat & bit) { | ||
587 | writel(bit, dmac->regs + PL080_TC_CLEAR); | ||
588 | res = S3C2410_RES_OK; | ||
589 | } | ||
590 | |||
591 | if (errstat & bit) | ||
592 | writel(bit, dmac->regs + PL080_ERR_CLEAR); | ||
593 | |||
594 | /* 'next' points to the buffer that is next to the | ||
595 | * currently active buffer. | ||
596 | * For CIRCULAR queues, 'next' will be same as 'curr' | ||
597 | * when 'end' is the active buffer. | ||
598 | */ | ||
599 | buff = chan->curr; | ||
600 | while (buff && buff != chan->next | ||
601 | && buff->next != chan->next) | ||
602 | buff = buff->next; | ||
603 | |||
604 | if (!buff) | ||
605 | BUG(); | ||
606 | |||
607 | if (buff == chan->next) | ||
608 | buff = chan->end; | ||
609 | |||
610 | s3c64xx_dma_bufffdone(chan, buff, res); | ||
611 | |||
612 | /* Free the node and update curr, if non-circular queue */ | ||
613 | if (!(chan->flags & S3C2410_DMAF_CIRCULAR)) { | ||
614 | chan->curr = buff->next; | ||
615 | s3c64xx_dma_freebuff(buff); | ||
616 | } | ||
617 | |||
618 | /* Update 'next' */ | ||
619 | buff = chan->next; | ||
620 | if (chan->next == chan->end) { | ||
621 | chan->next = chan->curr; | ||
622 | if (!(chan->flags & S3C2410_DMAF_CIRCULAR)) | ||
623 | chan->end = NULL; | ||
624 | } else { | ||
625 | chan->next = buff->next; | ||
626 | } | ||
627 | } | ||
628 | |||
629 | return IRQ_HANDLED; | ||
630 | } | ||
631 | |||
632 | static struct sysdev_class dma_sysclass = { | ||
633 | .name = "s3c64xx-dma", | ||
634 | }; | ||
635 | |||
636 | static int s3c64xx_dma_init1(int chno, enum dma_ch chbase, | ||
637 | int irq, unsigned int base) | ||
638 | { | ||
639 | struct s3c2410_dma_chan *chptr = &s3c2410_chans[chno]; | ||
640 | struct s3c64xx_dmac *dmac; | ||
641 | char clkname[16]; | ||
642 | void __iomem *regs; | ||
643 | void __iomem *regptr; | ||
644 | int err, ch; | ||
645 | |||
646 | dmac = kzalloc(sizeof(struct s3c64xx_dmac), GFP_KERNEL); | ||
647 | if (!dmac) { | ||
648 | printk(KERN_ERR "%s: failed to alloc mem\n", __func__); | ||
649 | return -ENOMEM; | ||
650 | } | ||
651 | |||
652 | dmac->sysdev.id = chno / 8; | ||
653 | dmac->sysdev.cls = &dma_sysclass; | ||
654 | |||
655 | err = sysdev_register(&dmac->sysdev); | ||
656 | if (err) { | ||
657 | printk(KERN_ERR "%s: failed to register sysdevice\n", __func__); | ||
658 | goto err_alloc; | ||
659 | } | ||
660 | |||
661 | regs = ioremap(base, 0x200); | ||
662 | if (!regs) { | ||
663 | printk(KERN_ERR "%s: failed to ioremap()\n", __func__); | ||
664 | err = -ENXIO; | ||
665 | goto err_dev; | ||
666 | } | ||
667 | |||
668 | snprintf(clkname, sizeof(clkname), "dma%d", dmac->sysdev.id); | ||
669 | |||
670 | dmac->clk = clk_get(NULL, clkname); | ||
671 | if (IS_ERR(dmac->clk)) { | ||
672 | printk(KERN_ERR "%s: failed to get clock %s\n", __func__, clkname); | ||
673 | err = PTR_ERR(dmac->clk); | ||
674 | goto err_map; | ||
675 | } | ||
676 | |||
677 | clk_enable(dmac->clk); | ||
678 | |||
679 | dmac->regs = regs; | ||
680 | dmac->chanbase = chbase; | ||
681 | dmac->channels = chptr; | ||
682 | |||
683 | err = request_irq(irq, s3c64xx_dma_irq, 0, "DMA", dmac); | ||
684 | if (err < 0) { | ||
685 | printk(KERN_ERR "%s: failed to get irq\n", __func__); | ||
686 | goto err_clk; | ||
687 | } | ||
688 | |||
689 | regptr = regs + PL080_Cx_BASE(0); | ||
690 | |||
691 | for (ch = 0; ch < 8; ch++, chno++, chptr++) { | ||
692 | printk(KERN_INFO "%s: registering DMA %d (%p)\n", | ||
693 | __func__, chno, regptr); | ||
694 | |||
695 | chptr->bit = 1 << ch; | ||
696 | chptr->number = chno; | ||
697 | chptr->dmac = dmac; | ||
698 | chptr->regs = regptr; | ||
699 | regptr += PL008_Cx_STRIDE; | ||
700 | } | ||
701 | |||
702 | /* for the moment, permanently enable the controller */ | ||
703 | writel(PL080_CONFIG_ENABLE, regs + PL080_CONFIG); | ||
704 | |||
705 | printk(KERN_INFO "PL080: IRQ %d, at %p\n", irq, regs); | ||
706 | |||
707 | return 0; | ||
708 | |||
709 | err_clk: | ||
710 | clk_disable(dmac->clk); | ||
711 | clk_put(dmac->clk); | ||
712 | err_map: | ||
713 | iounmap(regs); | ||
714 | err_dev: | ||
715 | sysdev_unregister(&dmac->sysdev); | ||
716 | err_alloc: | ||
717 | kfree(dmac); | ||
718 | return err; | ||
719 | } | ||
720 | |||
721 | static int __init s3c64xx_dma_init(void) | ||
722 | { | ||
723 | int ret; | ||
724 | |||
725 | printk(KERN_INFO "%s: Registering DMA channels\n", __func__); | ||
726 | |||
727 | dma_pool = dma_pool_create("DMA-LLI", NULL, sizeof(struct pl080s_lli), 16, 0); | ||
728 | if (!dma_pool) { | ||
729 | printk(KERN_ERR "%s: failed to create pool\n", __func__); | ||
730 | return -ENOMEM; | ||
731 | } | ||
732 | |||
733 | ret = sysdev_class_register(&dma_sysclass); | ||
734 | if (ret) { | ||
735 | printk(KERN_ERR "%s: failed to create sysclass\n", __func__); | ||
736 | return -ENOMEM; | ||
737 | } | ||
738 | |||
739 | /* Set all DMA configuration to be DMA, not SDMA */ | ||
740 | writel(0xffffff, S3C_SYSREG(0x110)); | ||
741 | |||
742 | /* Register standard DMA controlers */ | ||
743 | s3c64xx_dma_init1(0, DMACH_UART0, IRQ_DMA0, 0x75000000); | ||
744 | s3c64xx_dma_init1(8, DMACH_PCM1_TX, IRQ_DMA1, 0x75100000); | ||
745 | |||
746 | return 0; | ||
747 | } | ||
748 | |||
749 | arch_initcall(s3c64xx_dma_init); | ||