×
单片机 > 单片机程序设计 > 详情

S3C2440 DMA驱动程序编写及测试(三十二)

发布时间:2024-05-10 发布时间:
|

DMA(Direct Memory Access)


即直接存储访问,DMA传输方式无需CPU直接控制传输,通过硬件为RAM、I/O设备开辟一条直接传输数据的通路,能使CPU的效率大为提高。


学了这么多驱动,不难退出DMA的编写套路:


1)注册DMA中断,分配缓冲区

2)注册字符设备,并提供文件操作集合fops

-> 2.1)file_operations里设置DMA硬件相关操作,来启动DMA

由于我们是用字符设备的测试方式测试的,而本例子只是用两个地址之间的拷贝来演示DMA的作用,所以采用字符设备方式编写


1、驱动编写之前,先来讲如何分配释放缓冲区、DMA相关寄存器、使用DMA中断


1.1 在linux中,分配释放DMA缓冲区,常用以下几个函数


1)dma_alloc_writecombine()函数:


/* 该函数只禁止cache缓冲,保持写缓冲区,也就是对注册的物理区写入数据,也会更新到对应的虚拟缓冲区上 */

void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)

//分配DMA缓存区

//返回值为:申请到的DMA缓冲区的虚拟地址,若为NULL,表示分配失败,需要释放,避免内存泄漏

//参数如下:

//*dev:指针,这里填0,表示这个申请的缓冲区里没有内容

//size:分配的地址大小(字节单位)

//*handle:申请到的物理起始地址

//gfp:分配出来的内存参数,标志定义在,常用标志如下:

//GFP_ATOMIC 用来从中断处理和进程上下文之外的其他代码中分配内存,从不休眠

//GFP_KERNEL 内核内存的正常分配,可能睡眠

//GFP_USER 用来为用户空间页来分配内存;它可能睡眠

2)dma_alloc_coherent()函数:


/*该函数禁止cache缓存以及禁止写入缓冲区,从而使CPU读写的地址和DMA读写的地址内容一致*/

void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);

//分配DMA缓存区,返回值和参数和上面的函数一直

3)dma_free_writecombine()函数:


dma_free_writecombine(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle); //释放DMA缓存,与dma_alloc_writecombine()对应

//size:释放长度

//cpu_addr:虚拟地址,

//handle:物理地址

4)dma_free_coherent()函数:


dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle) //释放DMA缓存,与dma_alloc_coherent ()对应

//size:释放长度

//cpu_addr:虚拟地址,

//handle:物理地址

(PS:dma_free_writecombine()其实就是dma_free_coherent(),只不过是用了#define重命名而已。)


而我们之前用的内存分配kmalloc()函数,是不能用在DMA上,因为分配出来的内存可能在物理地址上不连续的,因为DMA没有那么智能,不知道一会跑去那里,一会跑去那里。


1.2 那么2440开发板如何来启动DMA,先来看一下2440的DMA寄存器


(PS:实际这些DMA相关的寄存器,在linux内核中三星公司已封装好了,可以直接调用,不过非常麻烦,还不如直接设置寄存器,可以参考:/zixunimg/eeworldimg/blog.csdn.net/mirkerson/article/details/6632273)


1.2.1 2440支持4个通道的DMA控制器


其中4个DMA外设请求源,如下图所示(通过DCONn寄存器的[26:24]来设置)


(PS:如果请求源是在系统总线上的,就只需要设置DCONn寄存器的[23]=0即可)

1.2.2 且每个通道都可以处理以下4种情况

1)源和目标都在系统总线上(比如:两个物理内存地址)


2)当目标在外设总线上时,源在系统总线上(外设指:串口,定时器,I2C,I2S等)


3)当目标在系统总线上时,源在外设总线上


4)源和目标都在外设总线上


1.2.3 DMA有两种工作模式(通过DCONn寄存器的[31]来设置)


查询模式:


当DMA请求XnXDREQ为低电平时,则DMA会一直传输数据,直到DMA请求拉高,才停止


握手模式:


当DMA请求XnXDREQ有下降沿触发时,则DMA会传输一次数据


1.2.4 DMA有两种传输模式(通过DCONn寄存器的[28]来设置)


单元传输:


指传输过程中,每执行一次,则读1次,写1次(如上图所示)


突发4传输:


指传输过程中,每执行一次,则读4次,然后写4次(如下图所示)


1.2.5 2440中的DMA寄存器如下图所示:


共有4个通道的寄存器,且每个通道的寄存器内容都一致,所以我们以DMA通道0为例:


1)DISR0 初始源寄存器(DMA Initial Source Register)


bit[30:0]:存放DMA源的基地址(物理地址)


2)DISRCC0 初始源控制寄存器(DMA Initial Source Control Register)


bit[1]:源位置选择, 0:源在系统总线上 1:源在外设总线上


bit[0]:源地址增加的选择,0:传输时,源地址自动增加 1:源地址固定


3)DIDST0 初始目标寄存器(DMA Initial Destination Register)


bit[30:0]:设置DMA目的的基地址(物理地址)


4)DIDSTC0 初始目标控制寄存器(DMA Initial Destination Control Register)


bit[2]:中断时间选择, 0:当DMA传输计数=0,立即发生中断 1:执行完自动加载后再发送中断(也就是计数为0,然后重新加载计数值)


bit[1]:目的位置选择, 0:目的在系统总线上 1:目的在外设总线上


bit[0]:目的地址选择, 0:传输时目的地址自动增加 1:目的地址固定


5)DCON0 控制寄存器(DMA Control Register)


bit[31]:工作模式选择,0:查询模式 1:握手模式(当源出于外设时,尽量选择握手模式)


bit[30]:中断请求(DREQ)/中断回应(DACK)的同步时钟选择,0:PCLK同步 1:HCLK同步


(PS:如果有设备在HCLK上,该为应当设为1,比如:(SDRAM)内存数组,反之当这些设备在PCLK上,应当设为0,比如:ADC,IIS,I2C,UART)


bit[29]:DMA传输计数中断使能/禁止 0:禁止中断 1:当传输完成后,产生中断


bit[28]:传输模式选择, 0:单元传输 1:突发4传输


bit[27]:传输服务模式


0:单服务模式,比如:有2个DMA请求,它们会被顺序执行一次(单元传输/突发4传输)后停止,然后知道下一次DMA请求,再重新开始另一次循环。


1:全服务模式,指DMA若有请求,则会占用DMA总线,一直传输,期间若有其他DMA请求,只有等待传输计数TC为0,才会执行其他DMA请求


bit[26:24]:DMA外设请求源选择(我们是用软件触发,没有用到)


bit[23]:软件/硬件请求源选择, 0:软件请求 1:硬件请求(还需要设置[26:24]来选择外设源)


bit[22]:重新加载开关选项,不需要,为0即可


bit[21:20]:传输数据大小,为了简单,一个字节(8位)即可


bit[19:0]:设置DMA传输的计数TC(BUF_SIZE 2k大小)


6)DSTAT0 状态寄存器(DMA Status Register)


bit[21:20]:DMA状态,00:空闲,01:忙


bit[19:0]:传输计数当前值CURR_TC 为0表示传输结束


7)DCSRC0 当前源寄存器(DMA Current Source Register)


bit[30:0]:存放DMA当前的源基地址


8)DCDST0 当前目标寄存器(Current Destination Register)


bit[30:0]:存放DMA当前的目的基地址


9)DMASKTRIG0 触发屏蔽寄存器


bit[2]:停止STOP 该为写1,立刻停止DMA当前的传输


bit[1]:DMA通道使能 0:关闭DMA的通道0(禁止DMA请求) 1:开启DMA的通道0(开启DMA请求)


bit[0]:软件请求触发 1:表示启动一次软件请求DMA,只有DCONn[23]=0和DMASKTRIGn[1]=1才有效,DMA传输时,该位自动清0


1.3 接下来就开始讲linux注册DMA中断


首先,DMA的每个通道只能只有一个源->目的,所以输入命令cat /proc/interrupts,找到DMA3中断未被使用

所以在linux中使用:


request_irq(IRQ_DMA3, s3c_dma_irq, NULL, "s3c_dma", 1);// s3c_dma_irq:中断服务函数,这里注册DMA3中断服务函数

//NULL:中断产生类型, 不需要,所以填NULL

//1:表示中断时,传入中断函数的参数,本节不需要所以填1,切记不能填0,否则注册失败

2、接下来,我们便来写一个DMA的字符设备驱动


步骤如下:


1)注册DMA中断,分配两个DMA缓冲区(源、目的)


2)注册字符设备,并提供文件操作集合fops


2.1)通过ioctl的cmd来判断是使用DMA启动两个地址之间的拷贝,还是直接两个地址之间的拷贝


2.2)若是DMA启动,则设置DMA的相关硬件,并启动DMA传输


2.1 所以,驱动代码如下所示:dma.c


#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define MEM_CPY_NO_DMA 0 //复制内存不使用DMA

#define MEM_CPY_DMA 1 //复制内存使用DMA

#define BUF_SIZE (512*1024) //大小512k

//DMA各通道的起始地址

#define DMA0_BASE_ADDR 0x4B000000

#define DMA1_BASE_ADDR 0x4B000040

#define DMA2_BASE_ADDR 0x4B000080

#define DMA3_BASE_ADDR 0x4B0000C0

//DMA通道寄存器

[1] [2]
S3C2440DMA驱动程序

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
STM32 USB HID 键盘