s3c2440中DMA的一般操作步骤分七步:
s3c2410_dma_client 的定义为:
struct s3c2410_dma_client {
char *name;
};
void* dev一般设置为NULL。channel是通道号。
根据xferunit以及dcon设置通道的控制寄存器DCONx
xferunit 为每次传输的数据大小:0:byte 1:half word 2:word
设置相应的dma通道完成一次dma传输后的回调函数,也即是s3c2410_dma_enqueue完成后会调用的函数
回调函数应具有一下格式:
typedef void (*s3c2410_dma_cbfn_t)(struct s3c2410_dma_chan *,
void *buf, int size,
enum s3c2410_dma_buffresult result);
buf可以传递一些有用的数据。
source: S3C2410_DMASRC_HW(外设),或者S3C2410_DMASRC_MEM(内存)。
hwcfg: the value for xxxSTCn register,
bit 0: 0=increment pointer, 1=leave pointer
bit 1: 0=soucre is AHB, 1=soucre is APB
devaddr:source 的物理地址。
建立一致性DMA映射函数,
该函数实际获得两个地址,
1、函数的返回值是一个(rc),代表缓冲区的内核虚拟地址
2、相关的总线地址,保存在dma_handle中
发起一次dma传输
参数意义:
* id the device driver's id information for this buffer
* data the physical address of the buffer data
* size the size of the buffer in bytes
将dma_alloc_coherent中得到的 dmaphys传递给s3c2410_dma_enqueue. s3c2410_dma_enqueue提交一次dma请求,当dma通道可用的时候通过s3c2410_dma_loadbuffer开始一次传输,传输完成后会产生irq中断。其dma的中断服务函数中会继续启动dma请求队列中的请求,传输剩下的数据。
DMA的使用有了相关的认识。那么关于DMA的体系架构是怎么建立起来的。接下了看看:
DMA先有arch_initcall(s3c2440_dma_init);core_initcall(s3c24xx_dma_sysclass_init);然后late_initcall(s3c24xx_dma_sysdev_register);
先看看段arch_initcall(s3c2440_dam_init)做了些什么。
它主要是驱动注册
static int __init s3c2440_dma_init(void)
{
return sysdev_driver_register(&s3c2440_sysclass, &s3c2440_dma_driver);
}
&s3c2440_sysclass,s3c2440_dma_driver定义如下
struct sysdev_class s3c2440_sysclass = {
.name = "s3c2440-core",
.suspend = s3c244x_suspend,
.resume = s3c244x_resume
};
static struct sysdev_driver s3c2440_dma_driver = {
.add = s3c2440_dma_add,
};
这里我们只关心.add=s3c2440_dam_add
在函数里s3c2440_dam_add做了什么呢?
static int __init s3c2440_dma_add(struct sys_device *sysdev)
{
s3c2410_dma_init();
s3c24xx_dma_order_set(&s3c2440_dma_order);
return s3c24xx_dma_init_map(&s3c2440_dma_sel);
}
它调用了三个函数s3c2410_dma_init(),s3c24xx_dma_order_set(&s3c2440_dma_order),s3c24xx_dma_init_map(&s3c2440_dma_sel);
s3c2410_dma_init()完成s3c2410_chans[]的初始化,并完成地址映射。s3c2410_chans[]是s3c2410_dma_chan结构体类型数组。s3c2410_dma_chan描述了一个通道的属性,下面是这个结构体得定义:
struct s3c2410_dma_chan {
unsigned char number; 代表第几个DMA通道
unsigned char in_use; 是否使用,0:为被使用1:已被占用
unsigned char irq_claimed;
unsigned char irq_enabled;
//irq相关标识,用于判断
unsigned char xfer_unit; 一次传输大小
enum s3c2410_dma_state state;
enum s3c2410_dma_loadst load_state;
struct s3c2410_dma_client *client;
enum s3c2410_dmasrc source;//有两个值,外设和内存。具体宏看源代码
enum dma_ch req_ch;
unsigned long dev_addr;
unsigned long load_timeout;
unsigned int flags;
struct s3c24xx_dma_map *map; 通道实映射
void __iomem *regs;
void __iomem *addr_reg;
unsigned int irq;
unsigned long dcon; 寄存器DCONx值
s3c2410_dma_cbfn_t callback_fn; 回调函数
s3c2410_dma_opfn_t op_fn;
struct s3c2410_dma_stats *stats;
struct s3c2410_dma_stats stats_store;
struct s3c2410_dma_buf *curr;
struct s3c2410_dma_buf *next;
struct s3c2410_dma_buf *end;
struct sys_device dev;
};
下面是s3c2410_dma_init()函数的源代码:
int __init s3c2410_dma_init(void)
{
return s3c24xx_dma_init(4, IRQ_DMA0, 0x40);
}
int __init s3c24xx_dma_init(unsigned int channels, unsigned int irq,
unsigned int stride)
{
struct s3c2410_dma_chan *cp;
int channel;
int ret;
printk("S3C24XX DMA Driver, (c) 2003-2004,2006 Simtec Electronics\n");
dma_channels = channels;
dma_base = ioremap(S3C24XX_PA_DMA, stride * channels);
if (dma_base == NULL) {
printk(KERN_ERR "dma failed to remap register block\n");
return -ENOMEM;
}
dma_kmem = kmem_cache_create("dma_desc",
sizeof(struct s3c2410_dma_buf), 0,
SLAB_HWCACHE_ALIGN,
s3c2410_dma_cache_ctor);
if (dma_kmem == NULL) {
printk(KERN_ERR "dma failed to make kmem cache\n");
ret = -ENOMEM;
goto err;
}
for (channel = 0; channel < channels; channel++) {
cp = &s3c2410_chans[channel];
memset(cp, 0, sizeof(struct s3c2410_dma_chan));
cp->number = channel;
cp->irq = channel + irq;
cp->regs = dma_base + (channel * stride);
cp->stats = &cp->stats_store;
cp->stats_store.timeout_shortest = LONG_MAX;
cp->load_timeout = 1<<18;
printk("DMA channel %d at %p, irq %d\n",
cp->number, cp->regs, cp->irq);
}
return 0;
err:
kmem_cache_destroy(dma_kmem);
iounmap(dma_base);
dma_base = NULL;
return ret;
}
有几个全局变量需要注意:
static void __iomem *dma_base;
static struct kmem_cache *dma_kmem;
static int dma_channels;通道数
struct s3c2410_dma_chan s3c2410_chans[S3C_DMA_CHANNELS];
kmem_cache_create()创建struct s3c2410_dma_buf对象cache。dma_kmen是这个cache的引用。主要是为了提高速度的作用。dma_channels被初始化为4,dam_base ioremap()获得。在for语句中,初始化了全局变量s3c4210_chans[0],s3c4210_chans[1],s3c4210_chans[2],s3c4210_chans[3].
接下来倒回到s3c24xx_dma_order_set(&s3c2440_dma_order)预定目标板要用的DMA通道。
&s3c2440_dma_order定义如下:
struct s3c24xx_dma_order {
struct s3c24xx_dma_order_ch channels[DMACH_MAX];
};
static struct s3c24xx_dma_order __initdata s3c2440_dma_order = {
.channels = {
[DMACH_SDI] = {
.list = {
[0] = 3 | DMA_CH_VALID,
[1] = 2 | DMA_CH_VALID,
[2] = 1 | DMA_CH_VALID,
[3] = 0 | DMA_CH_VALID,
},
},
[DMACH_I2S_IN] = {
.list = {
[0] = 1 | DMA_CH_VALID,
[1] = 2 | DMA_CH_VALID,
},
},
[DMACH_I2S_OUT] = {
.list = {
[0] = 2 | DMA_CH_VALID,
[1] = 1 | DMA_CH_VALID,
},
},
[DMACH_PCM_IN] = {
.list = {
[0] = 2 | DMA_CH_VALID,
[1] = 1 | DMA_CH_VALID,
},
},
[DMACH_PCM_OUT] = {
.list = {
[0] = 1 | DMA_CH_VALID,
[1] = 3 | DMA_CH_VALID,
},
},
[DMACH_MIC_IN] = {
.list = {
[0] = 3 | DMA_CH_VALID,
[1] = 2 | DMA_CH_VALID,
},
},
},
};
int __init s3c24xx_dma_order_set(struct s3c24xx_dma_order *ord)
{
struct s3c24xx_dma_order *nord = dma_order;
if (nord == NULL)
nord = kmalloc(sizeof(struct s3c24xx_dma_order), GFP_KERNEL);
if (nord == NULL) {
printk(KERN_ERR "no memory to store dma channel order\n");
return -ENOMEM;
}
dma_order = nord;
memcpy(nord, ord, sizeof(struct s3c24xx_dma_order));
return 0;
}
dam_order是一个全局变量
static struct s3c24xx_dma_order *dma_order;函数中使用kmalloc获得nord,然后memcpy s3c2440_dma_order 给nord。这里就有两个值相同的内存块。而dma_order指针指向nord.完成全局变量赋值。
s3c24xx_dma_init_map(&s3c2440_dma_sel);
看这个函数想看下相关的东西
static struct s3c24xx_dma_selection __initdata s3c2440_dma_sel = {
.select = s3c2440_dma_select,
.dcon_mask = 7 << 24,
.map = s3c2440_dma_mappings,
.map_size = ARRAY_SIZE(s3c2440_dma_mappings),
};
static struct s3c24xx_dma_map __initdata s3c2440_dma_mappings[] = {
[DMACH_XD0] = {
.name = "xdreq0",
.channels[0] = S3C2410_DCON_CH0_XDREQ0 | DMA_CH_VALID,
},
[DMACH_XD1] = {
.name = "xdreq1",
.channels[1] = S3C2410_DCON_CH1_XDREQ1 | DMA_CH_VALID,
},
[DMACH_SDI] = {
.name = "sdi",
.channels[0] = S3C2410_DCON_CH0_SDI | DMA_CH_VALID,
.channels[1] = S3C2440_DCON_CH1_SDI | DMA_CH_VALID,
.channels[2] = S3C2410_DCON_CH2_SDI | DMA_CH_VALID,
.channels[3] = S3C2410_DCON_CH3_SDI | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_IIS + S3C2410_IISFIFO,
.hw_addr.from = S3C2410_PA_IIS + S3C2410_IISFIFO,
},
[DMACH_SPI0] = {
.name = "spi0",
.channels[1] = S3C2410_DCON_CH1_SPI | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_SPI + S3C2410_SPTDAT,
.hw_addr.from = S3C2410_PA_SPI + S3C2410_SPRDAT,
},
[DMACH_SPI1] = {
.name = "spi1",
.channels[3] = S3C2410_DCON_CH3_SPI | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_SPI + 0x20 + S3C2410_SPTDAT,
.hw_addr.from = S3C2410_PA_SPI + 0x20 + S3C2410_SPRDAT,
},
[DMACH_UART0] = {
.name = "uart0",
.channels[0] = S3C2410_DCON_CH0_UART0 | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_UART0 + S3C2410_UTXH,
.hw_addr.from = S3C2410_PA_UART0 + S3C2410_URXH,
},
[DMACH_UART1] = {
.name = "uart1",
.channels[1] = S3C2410_DCON_CH1_UART1 | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_UART1 + S3C2410_UTXH,
.hw_addr.from = S3C2410_PA_UART1 + S3C2410_URXH,
},
[DMACH_UART2] = {
.name = "uart2",
.channels[3] = S3C2410_DCON_CH3_UART2 | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_UART2 + S3C2410_UTXH,
.hw_addr.from = S3C2410_PA_UART2 + S3C2410_URXH,
},
[DMACH_TIMER] = {
.name = "timer",
.channels[0] = S3C2410_DCON_CH0_TIMER | DMA_CH_VALID,
.channels[2] = S3C2410_DCON_CH2_TIMER | DMA_CH_VALID,
.channels[3] = S3C2410_DCON_CH3_TIMER | DMA_CH_VALID,
},
[DMACH_I2S_IN] = {
.name = "i2s-sdi",
.channels[1] = S3C2410_DCON_CH1_I2SSDI | DMA_CH_VALID,
.channels[2] = S3C2410_DCON_CH2_I2SSDI | DMA_CH_VALID,
.hw_addr.from = S3C2410_PA_IIS + S3C2410_IISFIFO,
},
[DMACH_I2S_OUT] = {
.name = "i2s-sdo",
.channels[0] = S3C2440_DCON_CH0_I2SSDO | DMA_CH_VALID,
.channels[2] = S3C2410_DCON_CH2_I2SSDO | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_IIS + S3C2410_IISFIFO,
},
[DMACH_PCM_IN] = {
.name = "pcm-in",
.channels[0] = S3C2440_DCON_CH0_PCMIN | DMA_CH_VALID,
.channels[2] = S3C2440_DCON_CH2_PCMIN | DMA_CH_VALID,
.hw_addr.from = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA,
},
[DMACH_PCM_OUT] = {
.name = "pcm-out",
.channels[1] = S3C2440_DCON_CH1_PCMOUT | DMA_CH_VALID,
.channels[3] = S3C2440_DCON_CH3_PCMOUT | DMA_CH_VALID,
.hw_addr.to = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA,
},
[DMACH_MIC_IN] = {
.name = "mic-in",
.channels[2] = S3C2440_DCON_CH2_MICIN | DMA_CH_VALID,
.channels[3] = S3C2440_DCON_CH3_MICIN | DMA_CH_VALID,
.hw_addr.from = S3C2440_PA_AC97 + S3C_AC97_MIC_DATA,
},
[DMACH_USB_EP1] = {
.name = "usb-ep1",
.channels[0] = S3C2410_DCON_CH0_USBEP1 | DMA_CH_VALID,
},
[DMACH_USB_EP2] = {
.name = "usb-ep2",
.channels[1] = S3C2410_DCON_CH1_USBEP2 | DMA_CH_VALID,
},
[DMACH_USB_EP3] = {
.name = "usb-ep3",
.channels[2] = S3C2410_DCON_CH2_USBEP3 | DMA_CH_VALID,
},
[DMACH_USB_EP4] = {
.name = "usb-ep4",
.channels[3] = S3C2410_DCON_CH3_USBEP4 | DMA_CH_VALID,
},
};
s3c2440_dma_sel实现对S3C24XX下DMA资源的统一管理。
struct s3c24xx_dma_selection {
struct s3c24xx_dma_map *map;记录struct s3c24xx_dma_map的首地址
unsigned long map_size;//struct s3c24xx_dma_map数组成员个数
unsigned long dcon_mask;//dcon控制器掩码
void (*select)(struct s3c2410_dma_chan *chan,
struct s3c24xx_dma_map *map);//
void (*direction)(struct s3c2410_dma_chan *chan,
struct s3c24xx_dma_map *map,
enum s3c2410_dmasrc dir);
};
struct s3c24xx_dma_map {
const char *name;//虚拟通道名
struct s3c24xx_dma_addr hw_addr;
unsigned long channels[S3C_DMA_CHANNELS];//实际DAM通道信息
unsigned long channels_rx[S3C_DMA_CHANNELS];
};
struct s3c24xx_dma_map提供了DAM虚拟通道与实际的DMA通道的直接关联。
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;
}
函数初始化了全局变量static struct s3c24xx_dma_selection dma_sel;
s3c24xx_dma_check_entry()是空函数。
plat-s3c24xx的dma.c中封装了一系列的API。以后慢慢看。