[ARM] 3796/1: S3C24XX: Add per-cpu DMA channel mapper

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>
This commit is contained in:
Ben Dooks 2006-09-15 23:42:24 +01:00 committed by Russell King
parent fd88edd20f
commit 505788cccb
4 changed files with 257 additions and 69 deletions

View 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

View File

@ -1,34 +1,15 @@
/* 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
*/
@ -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);
chan = s3c2410_dma_map_channel(channel);
if (chan == NULL) {
local_irq_restore(flags);
return -EBUSY;
} else {
printk(KERN_ERR "dma%d: client already has channel\n", channel);
}
}
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;
}

View File

@ -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);

View File

@ -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 */