这里具体DMA CONTROL寄存器(DCON)的配置说明,进而引出DMA的各种工作方式。
Atomic transfer:指的是DMA的单次原子操作,它可以是Unit模式(传输1个data size),也可以是burst模式(传输4个data size),具体对应DCON[28]。
Data Size:指的是单次原子操作的数据位宽,8、16、32,具体对应DCON[21:20]。
Request Source:DMA请求的来源有两种,软件&硬件模块,由DCON[23]控制;当为前者时,由软件对DMASKTRIG寄存器的位0置位触发一次DMA 操作。当为后者时,具体来源由DCON[26:24]控制,不同硬件模块的某时间触发一次DMA操作,具体要见不同的硬件模块。
DMA service mode:DMA的工作模式有两种,单一服务模式&整体服务模式。前一模式下,一次DMA请求完成一项原子操作,并且transfer count的值减1。后一模式下,一次DMA请求完成一批原子操作,直到transfer count等于0表示完成一次整体服务。具体对应DCON[27]。
RELOAD:在reload模式下,当transfer count的值变为零时,将自动加src、dst、TC的值加载到CURR_DST、CURR_SRC、CURR_TC,并开始一次新的DMA传输。该模式一般和整体服务模式一起使用,也就是说当一次整体服务开始后,src、dst、TC的值都已经被加载,因此可以更改为下一次
服务的地址,2410说明文档中建议加入以下语句来判断当前的服务开始,src、dst、TC的值可以被更改了:while((rDSTATn & 0xfffff) == 0) ;
Req&Ack:DMA请求和应答的协议有两种,Demard mode 和 Handshake mode。两者对Request和Ack的时序定义有所不同:在Demard模式下,如果
DMA完成一次请求如果Request仍然有效,那么DMA就认为这是下一次DMA请求;在Handshake模式下,DMA完成一次请求后等待Request信号无效,然后把ACK也置无效,再等待下一次Request。这个设计外部DMA请求时可能要用到。
传输总长度:DMA一次整体服务传输的总长度为:
Data Size × Atomic transfer size × TC(字节)。
2410的DMA支持四类DMA传输:系统总线到系统总线(ASB/AHB to ASB/AHB),系统总线到外设总线(ASB/AHB to APB),外设总线到系统总线(APB to ASB/AHB),外设总线到外设总线(APB to APB)。
2410共有四条DMA通道,每条通道5个请求源。
Ch0:nXDREQ0,UART0,SDI,Timer,USB EP1
Ch1: nXDREQ1,UART1,I2SSDI,SPI0,USB EP2
Ch2:I2SSDO,I2SSDI,SDI,Timer, USB EP3
Ch3:UART1,SDI,SPI1,Timer, USB EP4
2410 DMA 三个比较重要的信号,引用2410官方文档:
DMA REQ: In the Single service mode, these three states of main FSM are performed and then stops, and waits for another DMA REQ. And if DMA REQ comes in, all three states are repeated.
DMA ACK: DMA ACK is asserted and then deasserted for each atomic transfer. In contrast, in the Whole service mode, main FSM waits at state-3 until CURR_TC becomes 0. Therefore, DMA ACK is asserted during all the transfers and then deasserted when TC reaches 0.
INT REQ: INT REQ is asserted only if CURR_TC becomes 0 regardless of the service mode (Single service mode or Whole service mode).
2410 DMA状态机:
2410 的DMA使用一个具有三个状态的有限状态机进行DMA 传输的流程控制,引用2410官方文档:
State-1. As an initial state, the DMA waits for a DMA request. If it comes, it goes to state-2. At this state, DMA ACK and INT REQ are 0.
State-2. In this state, DMA ACK becomes 1 and the counter (CURR_TC) is loaded from DCON[19:0] register. Note that the DMA ACK remains 1 until it is cleared later.
State-3. In this state, sub-FSM handling the atomic operation of DMA is initiated. The sub-FSM reads the data from the source address and then writes it to destination address. In this operation, data size and transfer size(single or burst) are considered.
每当一次DMA操作结束,不管是使用什么服务模式,DMA状态机都会自动地从状态三回到状态一,开始另一次操作。注意这里信号是DMA REQ 和 DMA ACK,而最终引脚信号是nXDREQ 和 nXDACK,所以最后实际输出的电平与这里的描述是相反的。
2410 DMA 的服务模式:
共有两种服务模式,一种是单一服务模式(single service),另外一种是整体服务模式(whole service)。
在单一服务模式下,不使用传统的DMA计数器,三个DMA状态被顺序执行一次后停止,等待DMA 请求再一次来临后再重新开始另一次循环。
在整体服务模式下,使用传统的DMA 计数器,状态机会停留在状态三,直到DMA计数器的值减为零,再回到状态一,等待下一次DMA请求。
2410 DMA 数据传输模式:
共有两种数据传输模式:
单位数据传输模式:执行一次读操作和一次写操作。
并发数据传输模式:执行四次读操作和四次写操作。
2410 DMA 的基本时序:
nXDREQ请求生效并经过2CLK周期同步后,nXDACK响应并开始生效,但至少还要经过3CLK的周期延迟,DMA控制器才可获得总线的控制权,并开始数据传输。
2410 DMA 的两种协议模式:
请求模式:If XnXDREQ remains asserted, the next transfer starts immediately. Otherwise it waits for XnXDREQ to be asserted.
握手模式:If XnXDREQ is deasserted, DMA deasserts XnXDACK in 2cycles. Otherwise it waits until XnXDREQ is deasserted.
2410 DMA REQ与ACK 协议类型:
共有三种协议类型:
单一服务请求:
单一服务握手:
整体服务握手:
根据上面所说的服务模式和协议模式,很容易推知这三种协议的时序分别是什么。
//=================================================
以下分配了四个DMA通道:
s3c2410_dma_t dma_chan[MAX_S3C2410_DMA_CHANNELS];
每个DMA通道维护着一个多缓冲区组成的单链表等待队列,执行DMA操作时先更新DMA通道控制寄存器内容,再依次摘取当前缓冲区投入使用,缓冲区头指针顺次前移;需要插入新的缓冲区时,可从head或tail插入;
图A详细分析了数据结构关系:
- DMA驱动主要函数功能分析(linux/arch/arm/mach-s3c2410/dma.c)
写一个DMA驱动的主要工作包括:DMA通道申请、DMA中断申请、控制寄存器设置、挂入DMA等待队列、清除DMA中断、释放DMA通道。Dma.c中对这些工作作了很好的实现,以下具体分析关键函数:
- int s3c2410_request_dma(const char *device_id, dmach_t channel,
dma_callback_t write_cb, dma_callback_t read_cb) (s3c2410_dma_queue_buffer);
函数描述:申请某通道的DMA资源,填充s3c2410_dma_t 数据结构的内容,申请DMA中断。
输入参数:device_id DMA 设备名;channel 通道号;
write_cb DMA写操作完成的回调函数;read_cb DMA读操作完成的回调函数
输出参数:若channel通道已使用,出错返回;否则,返回0
- int s3c2410_dma_queue_buffer(dmach_t channel, void *buf_id,
dma_addr_t data, int size, int write) (s3c2410_dma_stop);
函数描述:这是DMA操作最关键的函数,它完成了一系列动作:分配并初始化一个DMA内核缓冲区控制结构,并将它插入DMA等待队列,设置DMA控制寄存器内容,等待DMA操作触发
输入参数: channel 通道号;buf_id,缓冲区标识
dma_addr_t data DMA数据缓冲区起始物理地址;size DMA数据缓冲区大小;write 是写还是读操作
输出参数:操作成功,返回0;否则,返回错误号
- int s3c2410_dma_stop(dmach_t channel)
函数描述:停止DMA操作。
- int s3c2410_dma_flush_all(dmach_t channel)
函数描述:释放DMA通道所申请的所有内存资源
- void s3c2410_free_dma(dmach_t channel)
函数描述:释放DMA通道
因为各函数功能强大,一个完整的DMA驱动程序中一般只需调用以上3个函数即可。可在驱动初始化中调用s3c2410_request_dma,开始DMA传输前调用s3c2410_dma_queue_buffer,释放驱动模块时调用s3c2410_free_dma。
- 具体的DMA实例分析
Linux下的IIS音频驱动主要都在/kernel/drivers/sound/s3c2410-uda1341.c文件中。它定义了2个重要的数据结构audio_bufer_t, 管理audio缓冲区的数据结构;audio_stream_t 管理多缓冲区的数据结构,它为音频流数据组成了一个环形缓冲区。
我们先看一下加载驱动模块时的初始化函数:int __init s3c2410_uda1341_init(void),该函数先初始化IO和UDA341芯片,然后语句s3c2410_request_dma("I2SSDO", s->dma_ch, audio_dmaout_done_callback, NULL);申请了一个DMA通道用于输出音频数据;
smdk2410_audio_write是音频驱动最关键的函数,它从用户进程中拷贝音频数据流至DMA内核缓冲区,然后适用DMA通道2把音频数据发送出去,从而输出声音。我们可以在smdk2410_audio_write 中发现语句s3c2410_dma_queue_buffer(s->dma_ch, (void *) b,b->dma_addr, b->size, DMA_BUF_WR);就是它为DMA写操作作好了一切准备,当I2SSDO中断到来,DMA2通道执行一次写操作(从DMA缓冲写往IO地址0x55000010)。
smdk2410_audio_release函数中先后调用了s3c2410_dma_flush_all、s3c2410_free_dma释放DMA2占用的内存资源、和释放DMA2通道.
//=========================================
前天完成了UDA1341的驱动,程序是网上下载的,仅仅小改一下而已,昨晚上
分析了其中的DMA部分,有些小小的感想,如果不正确希望读者能够告诉我。谢谢。
写的很乱,没有整理。
DMA的基础就不介绍了,可以参考http://blog.chinaunix.net/u1/58640/showart_483567.html
和规格书。我主要根据audio的使用,介绍一下DMA的使用。
1.首先对DMA进行初始化。
如对outstream的初始化
channel = 2;
source = S3C2410_DMASRC_MEM;//源地址为Memory
hwcfg = 3;
devaddr = 0x55000010;
dcon = 0xa0800000;
flags = S3C2410_DMAF_AUTOSTART;
/* s3c2410_dma_devconfig
*
* configure the dma source/destination hardware type and address
* 配置DMA的源和目的的硬件类型和地址
* source: S3C2410_DMASRC_HW: source is hardware 源是硬件
* S3C2410_DMASRC_MEM: source is memory 源是内存
*
* 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: physical address of the source
*/
因为我们的DMA 源是内存,目的是硬件IIS的固定地址,所以hwcfg应该
设定为APB FIX_ADDR 即:3
int s3c2410_dma_devconfig(int channel,
s3c2410_dmasrc_t source,
int hwcfg,
unsigned long devaddr)
{
s3c2410_dma_chan_t *chan = &s3c2410_chans[channel];
check_channel(channel);
pr_debug("%s: source=%d, hwcfg=%08x, devaddr=%08lx/n",
__FUNCTION__, (int)source, hwcfg, devaddr);
chan->source = source;
chan->dev_addr = devaddr;
switch (source) {
case S3C2410_DMASRC_HW:
/* source is hardware */
pr_debug("%s: hw source, devaddr=%08lx, hwcfg=%d/n",
__FUNCTION__, devaddr, hwcfg);
dma_wrreg(chan, S3C2410_DMA_DISRCC, hwcfg & 3);
dma_wrreg(chan, S3C2410_DMA_DISRC, devaddr);
dma_wrreg(chan, S3C2410_DMA_DIDSTC, (0<<1) | (0<<0));
chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DIDST);
return 0;
case S3C2410_DMASRC_MEM:
/* source is memory */
pr_debug( "%s: mem source, devaddr=%08lx, hwcfg=%d/n",
__FUNCTION__, devaddr, hwcfg);
dma_wrreg(chan, S3C2410_DMA_DISRCC, (0<<1) | (0<<0));
dma_wrreg(chan, S3C2410_DMA_DIDST, devaddr);
dma_wrreg(chan, S3C2410_DMA_DIDSTC, hwcfg & 3);
chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DISRC);
return 0;
}
printk(KERN_ERR "dma%d: invalid source type (%d)/n", channel, source);
return -EINVAL;
}
/* s3c2410_dma_config
*
* xfersize: size of unit in bytes (1,2,4)
* dcon: base value of the DCONx register
*/
dcon = 0xa0800000;
因为IIS是16bit的,所以unit为2.
s3c2410_dma_config(channel, 2, dcon);
//设定DMA传输结束后的回调函数
s3c2410_dma_set_buffdone_fn(channel, audio_dmaout_done_callback);
//设定S3C2410_DMAF_AUTOSTART
s3c2410_dma_setflags(channel, flags);
//申请DMA通道
ret = s3c2410_dma_request(s->dma_ch, &s3c2410iis_dma_out, NULL);
以上是初始化部分,之后通过dmabuf = dma_alloc_coherent(NULL, dmasize, &dmaphys, GFP_KERNEL);
来申请缓冲,dmabuf为虚拟地址,dmaphys是物理地址,虚拟地址用来让驱动写buf用,dmaphys用来传给dma。
dma关心的是实际的物理地址。
然后通过s3c2410_dma_enqueue(s->dma_ch, (void *) b, b->dma_addr, b->size);来启动dma传输,
这里面的参数就是dmaphys,它为实际地址。
如果dma传输结束,就会调用:
static void audio_dmaout_done_callback(s3c2410_dma_chan_t *ch, void *buf, int size,
s3c2410_dma_buffresult_t result)
{
audio_buf_t *b = (audio_buf_t *) buf;
DPRINTK("audio_dmaout_done_callback/n");
up(&b->sem);
wake_up(&b->sem.wait);
}
来启动下一个buf的传输。