|
@@ -0,0 +1,198 @@
|
|
|
+/* linux/arch/arm/plat-s3c24xx/dma.c
|
|
|
+ *
|
|
|
+ * Copyright 2003-2006 Simtec Electronics
|
|
|
+ * Ben Dooks <ben@simtec.co.uk>
|
|
|
+ *
|
|
|
+ * S3C2410 DMA core
|
|
|
+ *
|
|
|
+ * http://armlinux.simtec.co.uk/
|
|
|
+ *
|
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
|
+ * it under the terms of the GNU General Public License version 2 as
|
|
|
+ * published by the Free Software Foundation.
|
|
|
+*/
|
|
|
+
|
|
|
+
|
|
|
+#ifdef CONFIG_S3C2410_DMA_DEBUG
|
|
|
+#define DEBUG
|
|
|
+#endif
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/init.h>
|
|
|
+#include <linux/sched.h>
|
|
|
+#include <linux/spinlock.h>
|
|
|
+#include <linux/interrupt.h>
|
|
|
+#include <linux/syscore_ops.h>
|
|
|
+#include <linux/slab.h>
|
|
|
+#include <linux/errno.h>
|
|
|
+#include <linux/io.h>
|
|
|
+
|
|
|
+#include <asm/irq.h>
|
|
|
+#include <mach/hardware.h>
|
|
|
+#include <mach/dma.h>
|
|
|
+#include <mach/map.h>
|
|
|
+
|
|
|
+#include <plat/dma-s3c24xx.h>
|
|
|
+#include <plat/regs-dma.h>
|
|
|
+
|
|
|
+/* io map for dma */
|
|
|
+static void __iomem *dma_base;
|
|
|
+static struct kmem_cache *dma_kmem;
|
|
|
+
|
|
|
+static int dma_channels;
|
|
|
+
|
|
|
+static struct s3c24xx_dma_selection dma_sel;
|
|
|
+
|
|
|
+
|
|
|
+/* debugging functions */
|
|
|
+
|
|
|
+#define BUF_MAGIC (0xcafebabe)
|
|
|
+
|
|
|
+#define dmawarn(fmt...) printk(KERN_DEBUG fmt)
|
|
|
+
|
|
|
+#define dma_regaddr(chan, reg) ((chan)->regs + (reg))
|
|
|
+
|
|
|
+#if 1
|
|
|
+#define dma_wrreg(chan, reg, val) writel((val), (chan)->regs + (reg))
|
|
|
+#else
|
|
|
+static inline void
|
|
|
+dma_wrreg(struct s3c2410_dma_chan *chan, int reg, unsigned long val)
|
|
|
+{
|
|
|
+ pr_debug("writing %08x to register %08x\n",(unsigned int)val,reg);
|
|
|
+ writel(val, dma_regaddr(chan, reg));
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+#define dma_rdreg(chan, reg) readl((chan)->regs + (reg))
|
|
|
+
|
|
|
+/* captured register state for debug */
|
|
|
+
|
|
|
+struct s3c2410_dma_regstate {
|
|
|
+ unsigned long dcsrc;
|
|
|
+ unsigned long disrc;
|
|
|
+ unsigned long dstat;
|
|
|
+ unsigned long dcon;
|
|
|
+ unsigned long dmsktrig;
|
|
|
+};
|
|
|
+
|
|
|
+#ifdef CONFIG_S3C2410_DMA_DEBUG
|
|
|
+
|
|
|
+/* dmadbg_showregs
|
|
|
+ *
|
|
|
+ * simple debug routine to print the current state of the dma registers
|
|
|
+*/
|
|
|
+
|
|
|
+static void
|
|
|
+dmadbg_capture(struct s3c2410_dma_chan *chan, struct s3c2410_dma_regstate *regs)
|
|
|
+{
|
|
|
+ regs->dcsrc = dma_rdreg(chan, S3C2410_DMA_DCSRC);
|
|
|
+ regs->disrc = dma_rdreg(chan, S3C2410_DMA_DISRC);
|
|
|
+ regs->dstat = dma_rdreg(chan, S3C2410_DMA_DSTAT);
|
|
|
+ regs->dcon = dma_rdreg(chan, S3C2410_DMA_DCON);
|
|
|
+ regs->dmsktrig = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG);
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+dmadbg_dumpregs(const char *fname, int line, struct s3c2410_dma_chan *chan,
|
|
|
+ struct s3c2410_dma_regstate *regs)
|
|
|
+{
|
|
|
+ printk(KERN_DEBUG "dma%d: %s:%d: DCSRC=%08lx, DISRC=%08lx, DSTAT=%08lx DMT=%02lx, DCON=%08lx\n",
|
|
|
+ chan->number, fname, line,
|
|
|
+ regs->dcsrc, regs->disrc, regs->dstat, regs->dmsktrig,
|
|
|
+ regs->dcon);
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+dmadbg_showchan(const char *fname, int line, struct s3c2410_dma_chan *chan)
|
|
|
+{
|
|
|
+ struct s3c2410_dma_regstate state;
|
|
|
+
|
|
|
+ dmadbg_capture(chan, &state);
|
|
|
+
|
|
|
+ printk(KERN_DEBUG "dma%d: %s:%d: ls=%d, cur=%p, %p %p\n",
|
|
|
+ chan->number, fname, line, chan->load_state,
|
|
|
+ chan->curr, chan->next, chan->end);
|
|
|
+
|
|
|
+ dmadbg_dumpregs(fname, line, chan, &state);
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+dmadbg_showregs(const char *fname, int line, struct s3c2410_dma_chan *chan)
|
|
|
+{
|
|
|
+ struct s3c2410_dma_regstate state;
|
|
|
+
|
|
|
+ dmadbg_capture(chan, &state);
|
|
|
+ dmadbg_dumpregs(fname, line, chan, &state);
|
|
|
+}
|
|
|
+
|
|
|
+#define dbg_showregs(chan) dmadbg_showregs(__func__, __LINE__, (chan))
|
|
|
+#define dbg_showchan(chan) dmadbg_showchan(__func__, __LINE__, (chan))
|
|
|
+#else
|
|
|
+#define dbg_showregs(chan) do { } while(0)
|
|
|
+#define dbg_showchan(chan) do { } while(0)
|
|
|
+#endif /* CONFIG_S3C2410_DMA_DEBUG */
|
|
|
+
|
|
|
+/* s3c2410_dma_stats_timeout
|
|
|
+ *
|
|
|
+ * Update DMA stats from timeout info
|
|
|
+*/
|
|
|
+
|
|
|
+static void
|
|
|
+s3c2410_dma_stats_timeout(struct s3c2410_dma_stats *stats, int val)
|
|
|
+{
|
|
|
+ if (stats == NULL)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (val > stats->timeout_longest)
|
|
|
+ stats->timeout_longest = val;
|
|
|
+ if (val < stats->timeout_shortest)
|
|
|
+ stats->timeout_shortest = val;
|
|
|
+
|
|
|
+ stats->timeout_avg += val;
|
|
|
+}
|
|
|
+
|
|
|
+/* s3c2410_dma_waitforload
|
|
|
+ *
|
|
|
+ * wait for the DMA engine to load a buffer, and update the state accordingly
|
|
|
+*/
|
|
|
+
|
|
|
+static int
|
|
|
+s3c2410_dma_waitforload(struct s3c2410_dma_chan *chan, int line)
|
|
|
+{
|
|
|
+ int timeout = chan->load_timeout;
|
|
|
+ int took;
|
|
|
+
|
|
|
+ if (chan->load_state != S3C2410_DMALOAD_1LOADED) {
|
|
|
+ printk(KERN_ERR "dma%d: s3c2410_dma_waitforload() called in loadstate %d from line %d\n", chan->number, chan->load_state, line);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (chan->stats != NULL)
|
|
|
+ chan->stats->loads++;
|
|
|
+
|
|
|
+ while (--timeout > 0) {
|
|
|
+ if ((dma_rdreg(chan, S3C2410_DMA_DSTAT) << (32-20)) != 0) {
|
|
|
+ took = chan->load_timeout - timeout;
|
|
|
+
|
|
|
+ s3c2410_dma_stats_timeout(chan->stats, took);
|
|
|
+
|
|
|
+ switch (chan->load_state) {
|
|
|
+ case S3C2410_DMALOAD_1LOADED:
|
|
|
+ chan->load_state = S3C2410_DMALOAD_1RUNNING;
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ printk(KERN_ERR "dma%d: unknown load_state in s3c2410_dma_waitforload() %d\n", chan->number, chan->load_state);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (chan->stats != NULL) {
|
|
|
+ chan->stats->timeout_failed++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|