学习笔记 --- S3C2440 DMA操作原理

本文介绍S3C2440处理器中DMA控制器的使用方法,包括通过DMA进行内存拷贝的驱动程序设计及利用DMA进行音频播放的技术细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


DMA(Direct Memory Access,直接内存访问)是一种不经过CPU而直接从内存存取数据的数据交换模式。在需要进行大量数据交换的场合,用好DMA,可以大大提高系统的性能,因为DMA操作几乎不占用CPU资源。s3c2440提供了4个通道的DMA

每个DMA通道能处理下面四种情况的数据传输:
(1)源器件和目的器件都在系统总线 APB
(2)源器件在系统总线,目的器件在外设总线
(3)源器件在外设总线,目的器件在系统总线
(4)源器件和目的器件都在外设总线 AHB

下面的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;  
}
测试方法:

# insmod dma.ko      //加载驱动
# cat /proc/interrupts //查看中断
           CPU0
 30:      52318         s3c  S3C2410 Timer Tick
 33:          0         s3c  s3c-mci
 34:          0         s3c  I2SSDI
 35:          0         s3c  I2SSDO
 36:          0         s3c  s3c_dma
 37:         12         s3c  s3c-mci
 42:          0         s3c  ohci_hcd:usb1
 43:          0         s3c  s3c2440-i2c
 51:       2725     s3c-ext  eth0
 60:          0     s3c-ext  s3c-mci
 70:         97   s3c-uart0  s3c2440-uart
 71:        100   s3c-uart0  s3c2440-uart
 83:          0           -  s3c2410-wdt
Err:          0

# ls /dev/dma     //查看设备
/dev/dma

# ./dmatest       //如此就会打印用法
Usage:
./dmatest <nodma | dma>

# ./dmatest dma     //以DMA方式拷贝,CPU可以做其他事情
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK

# ./dmatest nodma     //CPU拷贝,各种竞争CPU

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×TCDSZ表示的是数据大小(字节、半字还是字,即是12还是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;
              }           
}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值