DMA(Direct Memory Access,直接内存访问)是一种不经过CPU而直接从内存存取数据的数据交换模式。在需要进行大量数据交换的场合,用好DMA,可以大大提高系统的性能,因为DMA操作几乎不占用CPU资源。s3c2440提供了4个通道的DMA
下面的DMA驱动程序使用的是第四种,内存属于AHB总线上的,我们打算在内存中开辟两个连续空间,分别作为源和目的。我们用两个方法将源中的数据写到目的中,一种方法是让cpu去做,另外一种方法是让DMA去做:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#include <linux/dma-mapping.h>
#define MEM_CPY_NO_DMA 0
#define MEM_CPY_DMA 1
#define BUF_SIZE (512*1024)
#define DMA0_BASE_ADDR 0x4B000000
#define DMA1_BASE_ADDR 0x4B000040
#define DMA2_BASE_ADDR 0x4B000080
#define DMA3_BASE_ADDR 0x4B0000C0
struct s3c_dma_regs {
unsigned long disrc;
unsigned long disrcc;
unsigned long didst;
unsigned long didstc;
unsigned long dcon;
unsigned long dstat;
unsigned long dcsrc;
unsigned long dcdst;
unsigned long dmasktrig;
};
static int major = 0;
static char *src;
static u32 src_phys;
static char *dst;
static u32 dst_phys;
static struct class *cls;
static volatile struct s3c_dma_regs *dma_regs;
static DECLARE_WAIT_QUEUE_HEAD(dma_waitq);
/* 中断事件标志, 中断服务程序将它置1,ioctl将它清0 */
static volatile int ev_dma = 0;
static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int i;
memset(src, 0xAA, BUF_SIZE);
memset(dst, 0x55, BUF_SIZE);
switch (cmd)
{
//这是非DMA模式
case MEM_CPY_NO_DMA :
{
for (i = 0; i < BUF_SIZE; i++)
dst[i] = src[i]; //CPU直接将源拷贝到目的
if (memcmp(src, dst, BUF_SIZE) == 0)
{
printk("MEM_CPY_NO_DMA OK\n");
}
else
{
printk("MEM_CPY_DMA ERROR\n");
}
break;
}
//这是DMA模式
case MEM_CPY_DMA :
{
ev_dma = 0;
/* 把源,目的,长度告诉DMA */
/* 关于下面寄存器的具体情况,我们在注释3里面来详细讲一下 */
dma_regs->disrc = src_phys; /* 源的物理地址 */
dma_regs->disrcc = (0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */
dma_regs->didst = dst_phys; /* 目的的物理地址 */
dma_regs->didstc = (0<<2) | (0<<1) | (0<<0); /* 目的位于AHB总线, 目的地址递增 */
dma_regs->dcon = (1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(BUF_SIZE<<0); /* AHP总线同步,使能中断,全速传输(BIT27),软件触发(BIT23), */
/* 启动DMA */
dma_regs->dmasktrig = (1<<1) | (1<<0); //打开通道,软件启动
/* 如何知道DMA什么时候完成? */
/* 休眠 */
wait_event_interruptible(dma_waitq, ev_dma);
if (memcmp(src, dst, BUF_SIZE) == 0)
{
printk("MEM_CPY_DMA OK\n");
}
else
{
printk("MEM_CPY_DMA ERROR\n");
}
break;
}
}
return 0;
}
static struct file_operations dma_fops = {
.owner = THIS_MODULE,
.ioctl = s3c_dma_ioctl,
};
static irqreturn_t s3c_dma_irq(int irq, void *devid)
{
/* 唤醒 */
ev_dma = 1;
wake_up_interruptible(&dma_waitq); /* 唤醒休眠的进程 */
return IRQ_HANDLED;
}
static int s3c_dma_init(void)
{
/* 这里注册一个中断,当DMA数据传输完毕之后会发生此中断 */
if (request_irq(IRQ_DMA3, s3c_dma_irq, 0, "s3c_dma", 1))
{
printk("can't request_irq for DMA\n");
return -EBUSY;
}
/* 分配SRC, DST对应的缓冲区:之前我们知道在内核中开辟空间可以用kmalloc函数,这里却用了dma_alloc_writecombine,这是为什么呢?这是因为kmalloc开辟的空间其逻辑地址虽然是连续的,但是其实际的物理地址可能不是连续的。而DMA传输数据时,要求物理地址是连续的,dma_alloc_writecombine就满足这一点,这个函数的原型是:dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)其中size代表开辟的空间的大小,handle代表开辟的空间的物理地址,返回值是开辟的空间的逻辑地址*/
src = dma_alloc_writecombine(NULL, BUF_SIZE, &src_phys, GFP_KERNEL);//源
if (NULL == src)
{
printk("can't alloc buffer for src\n");
free_irq(IRQ_DMA3, 1);
return -ENOMEM;
}
dst = dma_alloc_writecombine(NULL, BUF_SIZE, &dst_phys, GFP_KERNEL);//目的
if (NULL == dst)
{
free_irq(IRQ_DMA3, 1);
dma_free_writecombine(NULL, BUF_SIZE, src, src_phys);
printk("can't alloc buffer for dst\n");
return -ENOMEM;
}
major = register_chrdev(0, "s3c_dma", &dma_fops);//注册字符设备
/* 为了自动创建设备节点 */
cls = class_create(THIS_MODULE, "s3c_dma");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "dma"); /* /dev/dma */
dma_regs = ioremap(DMA3_BASE_ADDR, sizeof(struct s3c_dma_regs));//这边是将DMA控制寄存器映射到内核空间
return 0;
}
static void s3c_dma_exit(void)
{
iounmap(dma_regs);
class_device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "s3c_dma");
dma_free_writecombine(NULL, BUF_SIZE, src, src_phys);
dma_free_writecombine(NULL, BUF_SIZE, dst, dst_phys);
free_irq(IRQ_DMA3, 1);
}
module_init(s3c_dma_init);
module_exit(s3c_dma_exit);
MODULE_LICENSE("GPL");
测试程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
/* ./dma_test nodma
* ./dma_test dma
*/
#define MEM_CPY_NO_DMA 0
#define MEM_CPY_DMA 1
void print_usage(char *name)
{
printf("Usage:\n");
printf("%s <nodma | dma>\n", name);
}
int main(int argc, char **argv)
{
int fd;
if (argc != 2)
{
print_usage(argv[0]);
return -1;
}
fd = open("/dev/dma", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/dma\n");
return -1;
}
if (strcmp(argv[1], "nodma") == 0)
{
while (1)
{
ioctl(fd, MEM_CPY_NO_DMA);
}
}
else if (strcmp(argv[1], "dma") == 0)
{
while (1)
{
ioctl(fd, MEM_CPY_DMA);
}
}
else
{
print_usage(argv[0]);
return -1;
}
return 0;
}
测试方法:
MEM_CPY_NO_DMA
MEM_CPY_NO_DMA
MEM_CPY_NO_DMA
————————————————————————————————————————————————————————————————————————————
熟悉了DMA与寄存器配置后, 下面来看下裸奔S3C2440的时候如何使用DMA来播放音频:
转自:http://blog.youkuaiyun.com/zhaocj/article/details/5583935
下面我们就用DMA的方式来实现音频的播放。由于是用DMA的方式,因此在播放的过程中不占用系统资源,我们可以很容易的实现声音的各种操作而丝毫不影响播放的效果,如音量的提高和降低、静音、暂停等。在这里,还需要强调一点,利用DMA传输数据,一次最多可以传输的字节大小为:DSZ×TSZ×TC,DSZ表示的是数据大小(字节、半字还是字,即是1、2还是4),TSZ表示的是传输大小(单元传输还是突发传输,即1还是4),TC表示传输计数值(即寄存器DCONn的低20位存放的数据),因此如果需要传输的字节大小超出了这三个参数乘积的大小,则还要进一步处理,在我们给出的程序中,我们就考虑了这方面的问题。下面就是具体的程序,其中我们是利用UART来实现音频信号的播出、停止、暂停、静音、音量的提高和降低的。
…… ……
//一段纯音频数据数组
unsigned char music[] = {
0xF9, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xFF, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF,
0xF6, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF,
…… ……
};
int result;
int remainder;
char flag;
char cmd;
char play_state;
void __irq uartISR(void)
{
char ch;
rSUBSRCPND |= 0x1;
rSRCPND |= 0x1<<28;
rINTPND |= 0x1<<28;
ch=rURXH0;
switch(ch)
{
case 0x55: //播放
cmd = 1;
break;
case 0x1: //静音
cmd = 0x11;
break;
case 0x2: //音量提高
cmd = 0x12;
break;
case 0x3: //音量降低
cmd = 0x13;
break;
case 0x66: //停止
cmd = 0x2;
break;
case 0x77: //暂停
cmd = 0x3;
break;
}
rUTXH0=ch;
}
//放音子程序
void playsound(unsigned char *buffer,int length)
{
//用于计算音频数据的长度是否超过DMA所能传输的字节数范围
//这里音频数据的通道位数为16位,因此需要length除以2
remainder = (length>>1) & 0xfffff; //余数
result = (length>>1) / 0x100000; //商
play_state = 1; //置播放标志
rGPBDAT = rGPBDAT & ~(L3M|L3C|L3D) |(L3M|L3C);
//配置1341,详细讲解请看上一篇文章
WriteL3(0x14 + 2,1);
WriteL3(0x60,0);
WriteL3(0x14 + 2,1);
WriteL3(0x10,0);
WriteL3(0x14 + 2,1);
WriteL3(0xc1,0);
//配置IIS
rIISPSR = 3<<5|3;
rIISCON = (1<<5)|(0<<4)|(0<<3)|(1<<2)|(1<<1); //发送IIS的DMA使能
rIISMOD = (0<<9)|(0<<8)|(2<<6)|(0<<5)|(0<<4)|(1<<3)|(1<<2)|(1<<0);
rIISFCON = (1<<15)|(1<<13); //发送FIFO为DMA
//配置DMA
rDISRC2 = (U32)buffer; //DMA的源基址为音频数据数组的首地址
rDISRCC2 = (0<<1)|(0<<0); //AHB,源地址递增
rDIDST2 = (U32)IISFIFO; //DMA的目的基址为IIS的FIFO
rDIDSTC2 = (0<<2)| (1<<1)|(1<<0); //当传输计数值为0时中断,APB,目的地址不变
if (result == 0) //所传输的字节数没有超出DMA的最大传输范围
{
flag = 0; //清标志,表示没有超出范围,进入DMA中断后结束DMA操作
//握手模式,PCLK同步,传输计数中断,单元传输,单步服务模式,IISSDO,
//硬件请求模式,非自动重载,半字,
rDCON2 = (1<<31) | (0<<30) | (1<<29) | (0<<28) | (0<<27) | (0<<24) | (1<<23) | (1<<22) | (1<<20) | (remainder);
}
else //所传输的字节数超出了DMA的最大传输范围
{
flag = 1; //置标志,表示超出范围
rDCON2 = (1<<31) | (0<<30) | (1<<29) | (0<<28) | (0<<27) | (0<<24) | (1<<23) | (1<<22) | (1<<20) | (0xfffff);
}
rDMASKTRIG2=(0<<2)|(1<<1)|0; //不停止DMA,DMA通道开启,非软件触发
//启动IIS
rIISCON |= 0x1;
}
void __irq DMA_end(void)
{
rSRCPND |= 0x1<<19;
rINTPND |= 0x1<<19;
if (flag == 0) //DMA传输完毕
{
rIISCON = 0x0; //关闭IIS
rIISFCON = 0x0; //清IIS的FIFO
rDMASKTRIG2=1<<2; //停止DMA
play_state = 0; //清播放标志
}
else //DMA没有传输完毕,继续传输
{
result --; //商递减
rDISRC2 += 0x200000; //DMA源基址递增。因为传输的数据是半字,所以这里递增0x200000
if (result == 0 ) //只剩下余数部分需要传输
{
rDCON2=(rDCON2&(~0xfffff))|(remainder); //需要重新设置传输计数值
flag=0; //清标志
}
rDMASKTRIG2=(0<<2)|(1<<1)|0; //需要重新设置DMA通道的开启
}
}
void Main(void)
{
char mute;
char volume;
…… ……
rSRCPND = (0x1<<19)|(0x1<<28);
rSUBSRCPND = 0x1;
rINTPND = (0x1<<19)|(0x1<<28);
rINTSUBMSK = ~(0x1);
rINTMSK = ~((0x1<<19)|(0x1<<28)); //开启DMA2中断屏蔽
pISR_UART0 = (U32)uartISR;
pISR_DMA2=(U32)DMA_end;
result=0;
remainder=0;
flag=0;
cmd=0;
play_state =0;
while(1)
{
switch(cmd)
{
case 0x1: //播放
if (play_state==0)
{
volume = 0; //音量清零
mute=0xa0; //初始化静音
playsound(music,sizeof(music));
}
else
{
while(!(rUTRSTAT0 & 0x2));
rUTXH0=0xff;
}
cmd = 0;
break;
case 0x2: //停止
if (play_state==1)
{
rIISCON = 0x0; //停止IIS
rIISFCON = 0x0; //清IIS的FIFO
rDMASKTRIG2=1<<2; //终止DMA2
flag = 0;
play_state = 0;
}
else
{
while(!(rUTRSTAT0 & 0x2));
rUTXH0=0xff;
}
cmd = 0;
break;
case 0x3: //暂停,
if(play_state == 1)
{
rIISCON ^= 0x1; //异或,
}
else
{
while(!(rUTRSTAT0 & 0x2));
rUTXH0=0xff;
}
cmd = 0;
break;
case 0x11: //静音
if (play_state==1)
{
mute ^= 0x4;
WriteL3(0x14 + 0,1); //DATA0 (000101xx+00)
WriteL3(mute,0); //10,1,00,x,00:x,静音
}
else
{
while(!(rUTRSTAT0 & 0x2));
rUTXH0=0xff;
}
cmd = 0;
break;
case 0x12: //音量递增
if (play_state==1)
{
if(volume>0)
{
volume --;
WriteL3(0x14 + 0,1); //DATA0 (000101xx+00)
WriteL3(volume,0); //音量提高
}
}
else
{
while(!(rUTRSTAT0 & 0x2));
rUTXH0=0xff;
}
cmd = 0;
break;
case 0x13: //音量递减
if (play_state==1)
{
if(volume<61)
{
volume++;
WriteL3(0x14 + 0,1); //DATA0 (000101xx+00)
WriteL3(volume,0); //音量降低
}
}
else
{
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=0xff;
}
cmd = 0;
break;
}
}
}