aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/mach-s3c2410/Makefile6
-rw-r--r--arch/arm/mach-s3c2410/dma.c241
-rw-r--r--arch/arm/mach-s3c2410/dma.h45
-rw-r--r--include/asm-arm/arch-s3c2410/dma.h34
4 files changed, 257 insertions, 69 deletions
diff --git a/arch/arm/mach-s3c2410/Makefile b/arch/arm/mach-s3c2410/Makefile
index 0eadec91621..e4c32f6ffb7 100644
--- a/arch/arm/mach-s3c2410/Makefile
+++ b/arch/arm/mach-s3c2410/Makefile
@@ -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
diff --git a/arch/arm/mach-s3c2410/dma.c b/arch/arm/mach-s3c2410/dma.c
index cc92a7b2db8..d264bbbd8be 100644
--- a/arch/arm/mach-s3c2410/dma.c
+++ b/arch/arm/mach-s3c2410/dma.c
@@ -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
@@ -55,10 +36,14 @@
#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
index 00000000000..0ebfe0aab80
--- /dev/null
+++ b/arch/arm/mach-s3c2410/dma.h
@@ -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);
diff --git a/include/asm-arm/arch-s3c2410/dma.h b/include/asm-arm/arch-s3c2410/dma.h
index 3661e465b0a..166fc89d62d 100644
--- a/include/asm-arm/arch-s3c2410/dma.h
+++ b/include/asm-arm/arch-s3c2410/dma.h
@@ -23,6 +23,36 @@
#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 */