]> pilppa.org Git - linux-2.6-omap-h63xx.git/commitdiff
[ARM] 3796/1: S3C24XX: Add per-cpu DMA channel mapper
authorBen Dooks <ben-linux@fluff.org>
Fri, 15 Sep 2006 22:42:24 +0000 (23:42 +0100)
committerRussell King <rmk+kernel@arm.linux.org.uk>
Mon, 25 Sep 2006 09:25:15 +0000 (10:25 +0100)
Allow each CPU type in the S3C24XX range to
select the DMA channel mapping it supports.

We change the DMA registration to use an
virtual channel number that the DMA system
will allocate to a hardware channel at
request time.

Signed-off-by: Ben Dooks <ben-linux@fluff.org>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
arch/arm/mach-s3c2410/Makefile
arch/arm/mach-s3c2410/dma.c
arch/arm/mach-s3c2410/dma.h [new file with mode: 0644]
include/asm-arm/arch-s3c2410/dma.h

index 0eadec916214ecde32fa0ed66532d4ee33732276..e4c32f6ffb71ee54ba98ff8df2ffe288c92f429f 100644 (file)
@@ -9,6 +9,8 @@ obj-y                   := cpu.o irq.o time.o gpio.o clock.o devs.o
 obj-m                  :=
 obj-n                  :=
 obj-                   :=
+obj-dma-y              :=
+obj-dma-n              :=
 
 # DMA
 obj-$(CONFIG_S3C2410_DMA)      += dma.o
@@ -57,6 +59,10 @@ obj-$(CONFIG_CPU_S3C2442)    += s3c2442-clock.o
 
 obj-$(CONFIG_BAST_PC104_IRQ)   += bast-irq.o
 
+# merge in dma objects
+
+obj-y                          += $(obj-dma-y)
+
 # machine specific support
 
 obj-$(CONFIG_MACH_ANUBIS)      += mach-anubis.o
index cc92a7b2db889e7cbc5ec151e61a888718325046..d264bbbd8bef893fa660255fafbb88876f290b52 100644 (file)
@@ -1,35 +1,16 @@
-/* linux/arch/arm/mach-bast/dma.c
+/* linux/arch/arm/mach-s3c2410/dma.c
  *
- * (c) 2003-2005 Simtec Electronics
+ * (c) 2003-2005,2006 Simtec Electronics
  *     Ben Dooks <ben@simtec.co.uk>
  *
  * S3C2410 DMA core
  *
- * http://www.simtec.co.uk/products/EB2410ITX/
+ * 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.
- *
- * Changelog:
- *  27-Feb-2005 BJD  Added kmem cache for dma descriptors
- *  18-Nov-2004 BJD  Removed error for loading onto stopped channel
- *  10-Nov-2004 BJD  Ensure all external symbols exported for modules
- *  10-Nov-2004 BJD  Use sys_device and sysdev_class for power management
- *  08-Aug-2004 BJD  Apply rmk's suggestions
- *  21-Jul-2004 BJD  Ported to linux 2.6
- *  12-Jul-2004 BJD  Finished re-write and change of API
- *  06-Jul-2004 BJD  Rewrote dma code to try and cope with various problems
- *  23-May-2003 BJD  Created file
- *  19-Aug-2003 BJD  Cleanup, header fix, added URL
- *
- * This file is based on the Sangwook Lee/Samsung patches, re-written due
- * to various ommisions from the code (such as flexible dma configuration)
- * for use with the BAST system board.
- *
- * The re-write is pretty much complete, and should be good enough for any
- * possible DMA function
- */
+*/
 
 
 #ifdef CONFIG_S3C2410_DMA_DEBUG
 #include <asm/mach/dma.h>
 #include <asm/arch/map.h>
 
+#include "dma.h"
+
 /* io map for dma */
 static void __iomem *dma_base;
 static kmem_cache_t *dma_kmem;
 
+struct s3c24xx_dma_selection dma_sel;
+
 /* dma channel state information */
 struct s3c2410_dma_chan s3c2410_chans[S3C2410_DMA_CHANNELS];
 
@@ -79,7 +64,6 @@ 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))
@@ -151,12 +135,20 @@ dmadbg_showregs(const char *fname, int line, struct s3c2410_dma_chan *chan)
 #define dbg_showchan(chan) do { } while(0)
 #endif /* CONFIG_S3C2410_DMA_DEBUG */
 
-#define check_channel(chan) \
-  do { if ((chan) >= S3C2410_DMA_CHANNELS) { \
-    printk(KERN_ERR "%s: invalid channel %d\n", __FUNCTION__, (chan)); \
-    return -EINVAL; \
-  } } while(0)
+static struct s3c2410_dma_chan *dma_chan_map[DMACH_MAX];
 
+/* lookup_dma_channel
+ *
+ * change the dma channel number given into a real dma channel id
+*/
+
+static struct s3c2410_dma_chan *lookup_dma_channel(unsigned int channel)
+{
+       if (channel & DMACH_LOW_LEVEL)
+               return &s3c2410_chans[channel & ~DMACH_LOW_LEVEL];
+       else
+               return dma_chan_map[channel];
+}
 
 /* s3c2410_dma_stats_timeout
  *
@@ -321,8 +313,10 @@ static inline void
 s3c2410_dma_buffdone(struct s3c2410_dma_chan *chan, struct s3c2410_dma_buf *buf,
                     enum s3c2410_dma_buffresult result)
 {
+#if 0
        pr_debug("callback_fn=%p, buf=%p, id=%p, size=%d, result=%d\n",
                 chan->callback_fn, buf, buf->id, buf->size, result);
+#endif
 
        if (chan->callback_fn != NULL) {
                (chan->callback_fn)(chan, buf->id, buf->size, result);
@@ -439,7 +433,6 @@ s3c2410_dma_canload(struct s3c2410_dma_chan *chan)
        return 0;
 }
 
-
 /* s3c2410_dma_enqueue
  *
  * queue an given buffer for dma transfer.
@@ -460,11 +453,12 @@ s3c2410_dma_canload(struct s3c2410_dma_chan *chan)
 int s3c2410_dma_enqueue(unsigned int channel, void *id,
                        dma_addr_t data, int size)
 {
-       struct s3c2410_dma_chan *chan = &s3c2410_chans[channel];
+       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
        struct s3c2410_dma_buf *buf;
        unsigned long flags;
 
-       check_channel(channel);
+       if (chan == NULL)
+               return -EINVAL;
 
        pr_debug("%s: id=%p, data=%08x, size=%d\n",
                 __FUNCTION__, id, (unsigned int)data, size);
@@ -562,8 +556,10 @@ s3c2410_dma_freebuf(struct s3c2410_dma_buf *buf)
 static inline void
 s3c2410_dma_lastxfer(struct s3c2410_dma_chan *chan)
 {
+#if 0
        pr_debug("dma%d: s3c2410_dma_lastxfer: load_state %d\n",
                 chan->number, chan->load_state);
+#endif
 
        switch (chan->load_state) {
        case S3C2410_DMALOAD_NONE:
@@ -718,7 +714,8 @@ s3c2410_dma_irq(int irq, void *devpw, struct pt_regs *regs)
                if (chan->load_state == S3C2410_DMALOAD_NONE) {
                        pr_debug("dma%d: end of transfer, stopping channel (%ld)\n",
                                 chan->number, jiffies);
-                       s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_STOP);
+                       s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL,
+                                        S3C2410_DMAOP_STOP);
                }
        }
 
@@ -726,37 +723,34 @@ s3c2410_dma_irq(int irq, void *devpw, struct pt_regs *regs)
        return IRQ_HANDLED;
 }
 
+static struct s3c2410_dma_chan *s3c2410_dma_map_channel(int channel);
+
 /* s3c2410_request_dma
  *
  * get control of an dma channel
 */
 
-int s3c2410_dma_request(unsigned int channel, struct s3c2410_dma_client *client,
+int s3c2410_dma_request(unsigned int channel,
+                       struct s3c2410_dma_client *client,
                        void *dev)
 {
-       struct s3c2410_dma_chan *chan = &s3c2410_chans[channel];
+       struct s3c2410_dma_chan *chan;
        unsigned long flags;
        int err;
 
        pr_debug("dma%d: s3c2410_request_dma: client=%s, dev=%p\n",
                 channel, client->name, dev);
 
-       check_channel(channel);
-
        local_irq_save(flags);
 
-       dbg_showchan(chan);
-
-       if (chan->in_use) {
-               if (client != chan->client) {
-                       printk(KERN_ERR "dma%d: already in use\n", channel);
-                       local_irq_restore(flags);
-                       return -EBUSY;
-               } else {
-                       printk(KERN_ERR "dma%d: client already has channel\n", channel);
-               }
+       chan = s3c2410_dma_map_channel(channel);
+       if (chan == NULL) {
+               local_irq_restore(flags);
+               return -EBUSY;
        }
 
+       dbg_showchan(chan);
+
        chan->client = client;
        chan->in_use = 1;
 
@@ -809,14 +803,14 @@ EXPORT_SYMBOL(s3c2410_dma_request);
 
 int s3c2410_dma_free(dmach_t channel, struct s3c2410_dma_client *client)
 {
-       struct s3c2410_dma_chan *chan = &s3c2410_chans[channel];
+       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
        unsigned long flags;
 
-       check_channel(channel);
+       if (chan == NULL)
+               return -EINVAL;
 
        local_irq_save(flags);
 
-
        if (chan->client != client) {
                printk(KERN_WARNING "dma%d: possible free from different client (channel %p, passed %p)\n",
                       channel, chan->client, client);
@@ -837,8 +831,12 @@ int s3c2410_dma_free(dmach_t channel, struct s3c2410_dma_client *client)
 
        if (chan->irq_claimed)
                free_irq(chan->irq, (void *)chan);
+
        chan->irq_claimed = 0;
 
+       if (!(channel & DMACH_LOW_LEVEL))
+               dma_chan_map[channel] = NULL;
+
        local_irq_restore(flags);
 
        return 0;
@@ -848,8 +846,8 @@ EXPORT_SYMBOL(s3c2410_dma_free);
 
 static int s3c2410_dma_dostop(struct s3c2410_dma_chan *chan)
 {
-       unsigned long tmp;
        unsigned long flags;
+       unsigned long tmp;
 
        pr_debug("%s:\n", __FUNCTION__);
 
@@ -997,9 +995,10 @@ s3c2410_dma_started(struct s3c2410_dma_chan *chan)
 int
 s3c2410_dma_ctrl(dmach_t channel, enum s3c2410_chan_op op)
 {
-       struct s3c2410_dma_chan *chan = &s3c2410_chans[channel];
+       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
 
-       check_channel(channel);
+       if (chan == NULL)
+               return -EINVAL;
 
        switch (op) {
        case S3C2410_DMAOP_START:
@@ -1046,12 +1045,19 @@ int s3c2410_dma_config(dmach_t channel,
                       int xferunit,
                       int dcon)
 {
-       struct s3c2410_dma_chan *chan = &s3c2410_chans[channel];
+       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
 
        pr_debug("%s: chan=%d, xfer_unit=%d, dcon=%08x\n",
                 __FUNCTION__, channel, xferunit, dcon);
 
-       check_channel(channel);
+       if (chan == NULL)
+               return -EINVAL;
+
+       printk("Initial dcon is %08x\n", dcon);
+
+       dcon |= chan->dcon & dma_sel.dcon_mask;
+
+       printk("New dcon is %08x\n", dcon);
 
        switch (xferunit) {
        case 1:
@@ -1086,9 +1092,10 @@ EXPORT_SYMBOL(s3c2410_dma_config);
 
 int s3c2410_dma_setflags(dmach_t channel, unsigned int flags)
 {
-       struct s3c2410_dma_chan *chan = &s3c2410_chans[channel];
+       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
 
-       check_channel(channel);
+       if (chan == NULL)
+               return -EINVAL;
 
        pr_debug("%s: chan=%p, flags=%08x\n", __FUNCTION__, chan, flags);
 
@@ -1106,9 +1113,10 @@ EXPORT_SYMBOL(s3c2410_dma_setflags);
 
 int s3c2410_dma_set_opfn(dmach_t channel, s3c2410_dma_opfn_t rtn)
 {
-       struct s3c2410_dma_chan *chan = &s3c2410_chans[channel];
+       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
 
-       check_channel(channel);
+       if (chan == NULL)
+               return -EINVAL;
 
        pr_debug("%s: chan=%p, op rtn=%p\n", __FUNCTION__, chan, rtn);
 
@@ -1121,9 +1129,10 @@ EXPORT_SYMBOL(s3c2410_dma_set_opfn);
 
 int s3c2410_dma_set_buffdone_fn(dmach_t channel, s3c2410_dma_cbfn_t rtn)
 {
-       struct s3c2410_dma_chan *chan = &s3c2410_chans[channel];
+       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
 
-       check_channel(channel);
+       if (chan == NULL)
+               return -EINVAL;
 
        pr_debug("%s: chan=%p, callback rtn=%p\n", __FUNCTION__, chan, rtn);
 
@@ -1153,9 +1162,10 @@ int s3c2410_dma_devconfig(int channel,
                          int hwcfg,
                          unsigned long devaddr)
 {
-       struct s3c2410_dma_chan *chan = &s3c2410_chans[channel];
+       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
 
-       check_channel(channel);
+       if (chan == NULL)
+               return -EINVAL;
 
        pr_debug("%s: source=%d, hwcfg=%08x, devaddr=%08lx\n",
                 __FUNCTION__, (int)source, hwcfg, devaddr);
@@ -1200,9 +1210,10 @@ EXPORT_SYMBOL(s3c2410_dma_devconfig);
 
 int s3c2410_dma_getposition(dmach_t channel, dma_addr_t *src, dma_addr_t *dst)
 {
-       struct s3c2410_dma_chan *chan = &s3c2410_chans[channel];
+       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
 
-       check_channel(channel);
+       if (chan == NULL)
+               return -EINVAL;
 
        if (src != NULL)
                *src = dma_rdreg(chan, S3C2410_DMA_DCSRC);
@@ -1252,7 +1263,7 @@ static int s3c2410_dma_resume(struct sys_device *dev)
 #define s3c2410_dma_resume  NULL
 #endif /* CONFIG_PM */
 
-static struct sysdev_class dma_sysclass = {
+struct sysdev_class dma_sysclass = {
        set_kset_name("s3c24xx-dma"),
        .suspend        = s3c2410_dma_suspend,
        .resume         = s3c2410_dma_resume,
@@ -1265,7 +1276,6 @@ static void s3c2410_dma_cache_ctor(void *p, kmem_cache_t *c, unsigned long f)
        memset(p, 0, sizeof(struct s3c2410_dma_buf));
 }
 
-
 /* initialisation code */
 
 static int __init s3c2410_init_dma(void)
@@ -1274,7 +1284,7 @@ static int __init s3c2410_init_dma(void)
        int channel;
        int ret;
 
-       printk("S3C2410 DMA Driver, (c) 2003-2004 Simtec Electronics\n");
+       printk("S3C24XX DMA Driver, (c) 2003-2004,2006 Simtec Electronics\n");
 
        dma_base = ioremap(S3C24XX_PA_DMA, 0x200);
        if (dma_base == NULL) {
@@ -1282,6 +1292,8 @@ static int __init s3c2410_init_dma(void)
                return -ENOMEM;
        }
 
+       printk("Registering sysclass\n");
+
        ret = sysdev_class_register(&dma_sysclass);
        if (ret != 0) {
                printk(KERN_ERR "dma sysclass registration failed\n");
@@ -1335,4 +1347,95 @@ static int __init s3c2410_init_dma(void)
        return ret;
 }
 
-__initcall(s3c2410_init_dma);
+core_initcall(s3c2410_init_dma);
+
+static inline int is_channel_valid(unsigned int channel)
+{
+       return (channel & DMA_CH_VALID);
+}
+
+/* s3c2410_dma_map_channel()
+ *
+ * turn the virtual channel number into a real, and un-used hardware
+ * channel.
+ *
+ * currently this code uses first-free channel from the specified harware
+ * map, not taking into account anything that the board setup code may
+ * have to say about the likely peripheral set to be in use.
+*/
+
+struct s3c2410_dma_chan *s3c2410_dma_map_channel(int channel)
+{
+       struct s3c24xx_dma_map *ch_map;
+       struct s3c2410_dma_chan *dmach;
+       int ch;
+
+       if (dma_sel.map == NULL || channel > dma_sel.map_size)
+               return NULL;
+
+       ch_map = dma_sel.map + channel;
+
+       for (ch = 0; ch < S3C2410_DMA_CHANNELS; ch++) {
+               if (!is_channel_valid(ch_map->channels[ch]))
+                       continue;
+
+               if (s3c2410_chans[ch].in_use == 0) {
+                       printk("mapped channel %d to %d\n", channel, ch);
+                       break;
+               }
+       }
+
+       if (ch >= S3C2410_DMA_CHANNELS)
+               return NULL;
+
+       /* update our channel mapping */
+
+       dmach = &s3c2410_chans[ch];
+       dma_chan_map[channel] = dmach;
+
+       /* select the channel */
+
+       (dma_sel.select)(dmach, ch_map);
+
+       return dmach;
+}
+
+static void s3c24xx_dma_show_ch(struct s3c24xx_dma_map *map, int ch)
+{
+       /* show the channel configuration */
+
+       printk("%2d: %20s, channels %c%c%c%c\n", ch, map->name,
+              (is_channel_valid(map->channels[0]) ? '0' : '-'),
+              (is_channel_valid(map->channels[1]) ? '1' : '-'),
+              (is_channel_valid(map->channels[2]) ? '2' : '-'),
+              (is_channel_valid(map->channels[3]) ? '3' : '-'));
+}
+
+static int s3c24xx_dma_check_entry(struct s3c24xx_dma_map *map, int ch)
+{
+       if (1)
+               s3c24xx_dma_show_ch(map, ch);
+
+       return 0;
+}
+
+int __init s3c24xx_dma_init_map(struct s3c24xx_dma_selection *sel)
+{
+       struct s3c24xx_dma_map *nmap;
+       size_t map_sz = sizeof(*nmap) * sel->map_size;
+       int ptr;
+
+       nmap = kmalloc(map_sz, GFP_KERNEL);
+       if (nmap == NULL)
+               return -ENOMEM;
+
+       memcpy(nmap, sel->map, map_sz);
+       memcpy(&dma_sel, sel, sizeof(*sel));
+
+       dma_sel.map = nmap;
+
+       for (ptr = 0; ptr < sel->map_size; ptr++)
+               s3c24xx_dma_check_entry(nmap+ptr, ptr);
+
+       return 0;
+}
diff --git a/arch/arm/mach-s3c2410/dma.h b/arch/arm/mach-s3c2410/dma.h
new file mode 100644 (file)
index 0000000..0ebfe0a
--- /dev/null
@@ -0,0 +1,45 @@
+/* arch/arm/mach-s3c2410/dma.h
+ *
+ * Copyright (C) 2006 Simtec Electronics
+ *     Ben Dooks <ben@simtec.co.uk>
+ *
+ * Samsung S3C24XX DMA support
+ *
+ * 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.
+*/
+
+extern struct sysdev_class dma_sysclass;
+extern struct s3c2410_dma_chan s3c2410_chans[S3C2410_DMA_CHANNELS];
+
+#define DMA_CH_VALID           (1<<31)
+
+struct s3c24xx_dma_addr {
+       unsigned long           from;
+       unsigned long           to;
+};
+
+/* struct s3c24xx_dma_map
+ *
+ * this holds the mapping information for the channel selected
+ * to be connected to the specified device
+*/
+
+struct s3c24xx_dma_map {
+       const char              *name;
+       struct s3c24xx_dma_addr  hw_addr;
+
+       unsigned long            channels[S3C2410_DMA_CHANNELS];
+};
+
+struct s3c24xx_dma_selection {
+       struct s3c24xx_dma_map  *map;
+       unsigned long            map_size;
+       unsigned long            dcon_mask;
+
+       void    (*select)(struct s3c2410_dma_chan *chan,
+                         struct s3c24xx_dma_map *map);
+};
+
+extern int s3c24xx_dma_init_map(struct s3c24xx_dma_selection *sel);
index 3661e465b0a5a51efe1e874e02ca826d9f3f8d77..166fc89d62d71c39638bf38f4ac3c4837cbe10b5 100644 (file)
 #define MAX_DMA_ADDRESS                0x40000000
 #define MAX_DMA_TRANSFER_SIZE   0x100000 /* Data Unit is half word  */
 
+/* We use `virtual` dma channels to hide the fact we have only a limited
+ * number of DMA channels, and not of all of them (dependant on the device)
+ * can be attached to any DMA source. We therefore let the DMA core handle
+ * the allocation of hardware channels to clients.
+*/
+
+enum dma_ch {
+       DMACH_XD0,
+       DMACH_XD1,
+       DMACH_SDI,
+       DMACH_SPI0,
+       DMACH_SPI1,
+       DMACH_UART0,
+       DMACH_UART1,
+       DMACH_UART2,
+       DMACH_TIMER,
+       DMACH_I2S_IN,
+       DMACH_I2S_OUT,
+       DMACH_PCM_IN,
+       DMACH_PCM_OUT,
+       DMACH_MIC_IN,
+       DMACH_USB_EP1,
+       DMACH_USB_EP2,
+       DMACH_USB_EP3,
+       DMACH_USB_EP4,
+       DMACH_MAX,              /* the end entry */
+};
+
+#define DMACH_LOW_LEVEL        (1<<28) /* use this to specifiy hardware ch no */
+
 /* we have 4 dma channels */
 #define S3C2410_DMA_CHANNELS        (4)
 
@@ -149,6 +179,8 @@ struct s3c2410_dma_stats {
        unsigned long           timeout_failed;
 };
 
+struct s3c2410_dma_map;
+
 /* struct s3c2410_dma_chan
  *
  * full state information for each DMA channel
@@ -174,6 +206,8 @@ struct s3c2410_dma_chan {
        unsigned long            load_timeout;
        unsigned int             flags;         /* channel flags */
 
+       struct s3c24xx_dma_map  *map;           /* channel hw maps */
+
        /* channel's hardware position and configuration */
        void __iomem            *regs;          /* channels registers */
        void __iomem            *addr_reg;      /* data address register */