DMA定义:
DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU的干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作。
DMA传输方式:
DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:
- 外设到内存
- 内存到外设
- 内存到内存
- 外设到外设
DW_AHB_DMA 主要组件:
- DMA Hardware Requeset Interface
- AHB Master Interface
- AHB Slave Interface
- Arbiter(仲裁器)
- DMA Channel x
- FIFO of DMA Channel x
- DMA Controller
DMA的应用
DMA主适用于一些高速、大数据量的I/O设备。对于这类高速I/O设备,如果采用输入输出指令或采用中断的方式传输信息,会占用大量CPU的时间,同时也容易造成数据的丢失。DMA方式能使I/O设备直接和存储器进行成批数据的快速传输。
常见应用如下:
- FLASH
- AUDIO
- VIDEO
DMA相关的基本定义
Source/Destination Peripheral
Source Peripheral :DMAC从AHB层读取数据的设备,然后DMAC将数据存储在通道FIFO中。
Source Peripheral 可以挂在AHB总线或者APB总线上。
Destination Peripheral DMAC 从FIFO中存储的数据的设备(从Source Peripheral读取的)
Destination Peripheral 可以挂在AHB总线或者APB总线上。
Memory/Non-Memory Peripheral
始终“准备就绪”进行DMA传输的Source Peripheral 或者 Destination Peripheral,不需要握手接口即可与DMA进行交互,仅当外围设备插入的等待状态不超过16个时,才应当将其定义为Memory Peripheral。反之,则应当定义为Non-Memory Peripheral并使用握手接口,以便在其准备好接收/发送数据时发出信号通知DMA。
Master/Slave Interface
Master Interface:DW_ahb_dmac是通过AHB总线上的一个master interface,DW_ahb_dmac通过AHB总线从source读取数据并写入数据到destination
Slave Interface:CPU编程DW_ahb_dmac的AHB接口。主要是作为从设备接口部分,供AHB总线上的其他主设备来访问自身控制寄存器。
Handshaking Interface
一组信号或软件寄存器,他们符合DMAC与Source Peripheral/Destination Peripheral之间的握手协议,以控制他们之间的Single/Brust 事务。
该接口用于请求、确认、控制DMAC事务。一般情况DMAC可以通过以下3种方式接收请求:
- **Hardware handshaking interface: **使用硬件信号来控制DMAC与源或目标外围设备之间的Single/Brust 事务。
- Software handshaking interface: 使用一组软件寄存器DMAC与源或目标外围设备之间的Single/Brust 事务。
- Peripheral interrupt handshaking interface: 在这种模式下,来自外围设备的中断线被绑定到硬件握手接口的dma_req输入;忽略其他接口信号。
Channel
通道-在一个配置的AHB层上的源外设和同一或不同AHB层上的目标外设之间通过通道FIFO进行的读/写数据路径。
如果源外设不是内存,则将源握手接口分配给通道。
如果目标外设不是内存,则将目标握手接口分配给通道。源和目标握手接口可以通过编程通道寄存器来动态分配
。
通道可以理解成一个小型的程序,它是一个简单的IO控制程序,只能处理对应外设,mem之间的数据传输,工作之前需要配置DMA里使用哪个通道。
每一个通道都有一个相应的FIFO,该FIFO的缓冲长度可以编程配置
DMA 传输层级结构
Non-Memory Peripherals
Memory
解释:
Block:
Block- DW_ahb_dmac数据的块,其数量即块长,由流量控制器决定。对于DW_ahb_dmac和内存之间的传输,块被直接分解为突发序列和单个传输。对于DW_ahb_dmac和非内存外设之间的传输,块被分解为DW_ahb_dmac事务序列(单个事务和突发事务)。这些又被分解成AHB传输序列。
Transaction:
事务——DW_ahb_dmac传输的基本单元,由硬件或软件握手接口决定。
如果外设是非内存设备,则事务仅与DW_ahb_dmac与源外设或目标外设之间的传输相关。
有两种类型的事务:
- 单个事务—单个事务的长度始终为1,并转换为单个AHB转移。
- 突发事务—突发事务的长度被编程到DW_ahb_dmac中。突发事务被转换为一系列突发和AHB单传输。突发事务长度由程序控制,通常与dmac和源/目的外设中的FIFO大小有一定关系。
DMA传输方式
软件控制DMAC传输的块数量,在DMA传输完成时DW_AHB_DMAC硬件关闭通道并产生一个DMA传输完毕的中断信号,程序员可以为一个新的DMA传输重编程通道
根据传输块的分布情况可以分为两种
第一种:Single-block DMA transfer
一片连续的组合块
第二种:Multi-block DMA transfer
Multi-block DMA transfer可能由多个DW_ahb_dmac块组成。对多块DMA传输通过以下方式支持:
- 块链(链表指针) linked list pointer
- 自动重新加载通道寄存器
- 连续块
源和目标可以独立地选择要使用的方法。
Scatter-gather DMA方式是与block DMA方式相对应的一种DMA方式。
在DMA传输数据的过程中,要求源物理地址和目标物理地址必须是连续的。但是在某些计算机体系中,如IA架构,连续的存储器地址在物理上不一定是连续的,所以DMA传输要分成多次完成。
如果在传输完一块物理上连续的数据后引起一次中断,然后再由主机进行下一块物理上连续的数据传输,那么这种方式就是block DMA方式。
Scatter-gather DMA方式则不同,它使用一个链表描述物理上不连续的存储空间(这个链表),然后把链表首地址告诉DMA master。DMA master在传输完一块物理连续的数据后,不用发起中断,而是根据链表来传输下一块物理上连续的数据,直到传输完毕后再发起一次中断。
感觉对于物理地址不连续离散的使用block dma和sg dma是不一样,sg dma效率更高,可是如果是连续的物理地址,感觉block dma和sg dma效率其实是差不多的。
S-G传输的两种场景:
将一片连续内存数据搬运到一片不连续的的内存空间(且间隔是相等的)
源内存:连续
目的内存:离散
这个时候就可以用到DMA的 Destination Scatter Transfer
场景2:
将一片内存区域中等间隔的多段数据拷贝到一段连续内存中(常见的2D矩形抠图就是这种场景的典型应用)
源内存:分散
目的内存:聚合
这种场景就可以使用DMA的Source Gather transfer
DMA传输相关设置
传输类型
- Memory to Memory
- Memory to Peripheral
- Peripheral to Memory
- Peripheral to Peripheral
传输控制器 — 流控制器 Flow Controller
流控制器:确定DMA块传输长度并终止它的设备
流控制器一般有以下选择:
- DMAC
- Source Peripheral
- Destination Peripheral
流控制器设置规则:
如果在传输之前知道块大小,则应将DMAC设为流控。
如果在传输之前不知道块大小,则Source Peripheral/Destination Peripheral必须为流控
传输宽度
多数通用DMA是挂在AHB总线上的,即AHB总线的位宽则为DMA的传输宽度的上限。
以STM32单片机为例,DMA的传输宽度 <= 32。则常见有Byte-8bit、HalfWord-16bit、Word-32bit方式
DT53的AHB总线位宽为CPU位宽,同STM32一样都是32位,可以使用Byte-8bit、HalfWord-16bit、Word-32bit方式
传输时地址变化方式
DMA传输过程是没有CPU干预的,DMAC怎么知道下一个数据去哪里搬运,也需要事先告诉DMA地址的变化规律。
即三种地址方式:
- 递增
- 递减
- 不变
通道优先级
对于独立的通用DMA,都会有多条通道,每条通道有独立的FIFO、配置寄存器、中断等硬件。但是DMA的Master Interface仅有一个,故多个通道会竞争该Master Interface资源。
值得注意的是,高优先级通道可以随时提出对主总线接口的请求,但是仅在当前AHB传输(突发或单次)完成后才被授予。因此,当Master Interface正在为底优先级通道传输数据而高优先级通道请求服务时,它会在切换为高优先级通道传输数据之前完成低优先级通道当前的AHB传输(Single 或 Brust)。另外,当优先级相同时(寄存器配置),DMAC一般会根据通道号来选择优先运行的通道。
FIFO的作用
启用DMA的FIFO可以最大程度地避免数据传输过程中的溢出问题,可以减少DMA对内存的访问次数从而减少总线访问竞争
每个DMA stream都有4个字的FIFO可用。它用来暂存来自DMA源端的数据,每当FIFO里存放的数据达到设定的阈值后,数据就会被移走。阈值可以设置为从1个字到4个字的深度
突发模式和循环挪用模式
DMA和CPU共用总线,所以这两种模式是为解决如何共用总线提供了三种方法。
官方文档:
突发模式[编辑]以一个连续的顺序传输整个数据块。
一旦CPU授予DMA控制器对系统总线的访问权限,它就会在释放对系统总线的控制返回给CPU之前传输数据块中的所有字节数据,但会使CPU在相对较长的时间内处于非活动状态。该模式也称为“块传输模式”。它还用于停止不必要的数据。
周期挪用模式[编辑]周期挪用模式用于在突发传输模式所需的时间长度内不应禁用CPU的系统。在周期窃取模式中,DMA控制器以与突发模式相同的方式获得对系统总线的访问,使用BR(总线请求)和BG(总线授权)信号,这两个信号控制CPU和DMA控制器之间的接口。然而,在周期挪用模式下,在一个字节的数据传输之后,系统总线的控制通过BG解除对CPU的断言。然后通过BR不断地再次请求,每次请求传输一个字节的数据,直到整个数据块传输完毕。
通过不断地获得和释放对系统总线的控制,DMA控制器实质上交替的完成了指令和数据传输。CPU处理一条指令,然后DMA控制器传输一个数据值,以此类推。一方面,数据块在周期窃取模式中的传输速度不如在突发模式中那么快,但另一方面,CPU的空闲时间并不像突发模式中的那样长。
周期窃取模式对于实时监控数据的控制器很有用。
突发模式:
当DMA申请总线成功后会连续传送数据,而不给cpu使用总线的机会,直到数据传送完毕。比如stm32设置了4个节拍的突发传输,传输宽度位8位,则一个dma请求会连续传送4个字节,是单次传输的4倍。
Burst操作还是要通过CPU的参与的,与单独的一次读写操作相比,burst只需要提供一个其实地址就行了,以后的地址依次加1,而非burst操作每次都要给出地址,以及需要中间的一些应答、等待状态等等。如果是对地址连续的读取,burst效率高得多,但如果地址是跳跃的,则无法采用burst操作
dma实际上是一次一次的申请总线,把要传的数据总量分成一个一个小的数据块。比如要传64个字节,那么dma内部可能分为2次,一次传64/2=32个字节,这个2(a)次呢,就叫做burst。这个burst是可以设置的。这32个字节又可以分为32位 8或者16位16来传输。
循环挪用模式:
这应该是常用的dma模式,就是一个dma请求就申请一次总线,传输1个字节。
什么时候使用突发模式:
在封装/解封数据的过程中,如果在数据完全封装/解封前中断操作,则有数据损坏的危险。因此,为了确保数据一致性,可将数据流配置成生成突发传输:在这种情况下,属于一个突发的每组传输不可分割。
为什么封装/解封会有数据损坏的危险呢?举个例子,有个外设的一组寄存器是实时联动的,也就是随着时间的变化,这组寄存器会改变。如果我们第一时刻取前一半寄存器的值,后一时刻区取后后一半寄存器的值,将两者封装起来就会损坏数据。所以我们需要在同一时刻取出所有值,这时就要用到突发传输,因为在传输时只有dma用总线,寄存器不会改变。
DMA Relative Register
DW_AHB_DMAC的所有寄存器都是64位对齐的,都是64位宽,但是一般高32位都是Reserved。
DT53一共有两个DMAC,两条物理通道,每个DMAC有3条Channel。
通道配置寄存器
AHB有4个独立的AHB主接口,4个接口可以同时传输,可以在不同的AHB上存在主接口(multi-layer)。
源与目的可以在不同的AHB总线上
每个AHB主接口可配置的数据总线宽度(256bits)
通过APBbridge与APB外设通信
DMA编程示例
DMA传输可以由单个或多个块传输组成。
在多块传输的连续块上,DW_ahb_dmac中的SARx/DARx寄存器使用以下方法之一重新编程:
- 使用链表的块链
- Auto-reloading
- 块之间的连续地址
在多块传输的连续块上,DW_ahb_dmac中的CTLx寄存器使用以下方法之一重新编程:
- 使用链表的块链
- Auto-reloading
当块链时,使用链表是多块方法的选择。在连续的块上,DW_ahb_dmac中的LLPx寄存器使用带链表的块链重新编程。
块描述符由六个寄存器组成:SARx、DARx、LLPx、CTLx、SSTATx和DSTATx。DW_ahb_dmac使用前四个寄存器和CFGx寄存器来设置和描述块传输。
配置一个通道
-
读取ChEnReg通道使能寄存器,寻找当前空闲的通道,然后按照优先级使能该通道
-
通过向interrupt clear registers寄存器组中写值去清除上次DMA传输的通道中任何pending interrupts。读取中断原始状态和中断状态寄存器确认所有中断都已清除。
-
编程以下通道寄存器
a. 设置源起始地址,即配置SARx寄存器,x为通道号
b. 设置目的起始地址,即配置DARx寄存器,x为通道号
c. 配置CTLx=0和CFGx=1,配置LLPx寄存器为0(DMA传输时不使用链表)(根据表格的ROW1)
d. 配置CTLx寄存器以为DMA传输设置控制信息
i. 设置传输类型(memroy or non-memory peripheral for source and destination) 和流控制器设备
通过编程CTLx寄存器的TT_FC域
参考下表
ii. 设置传输特性,例如:
★配置源端传输宽度,SRC_TR_WIDTH域
★配置目的端传输宽度,DST_TR_WIDTH域
★配置source master layer, SMS域
★配置destination master layer,DMS域
★配置源递增/递减/固定地址,SINC域
★配置目的递增/递减/固定地址,DINC域
iii. 写入通道配置信息到CFGx寄存器
为源和目标外设指定握手接口类型(硬件或软件);这不是内存所必需的。这一步需要分别编程HS_SEL_SRC/HS_SEL_DST位。
写入0可以激活硬件握手接口来处理源/目标请求。写入1激活软件握手接口来处理源和目标请求。
如果源或目标外设的硬件握手接口被激活,向源和目标外设提供握手接口:这是必需的分别编程SRC_PER和DEST_PER位。
f. 如果启用了gather(参数DMAH_CHx_SRC_GAT_EN = True, CTLx. log = True)。SRC_GATHER_EN已启用),为通道x编程SGRx寄存器。
g. 如果启用了scatter(参数DMAH CHx DST SCA EN-True和CTLx.DST_SCATTER_EN),则为通道x编程DSRxregister。
- 在写入ChEnReg之前,确保DmaCfgReg寄存器的第0位是启用的。
- 源和目标请求单个和突发DMA事务,以便传输数据块(假设是非内存外设)。DW_ahb_dmac在块中的每个事务(突发事务和单个事务)完成时确认,并执行块传输。
- 传输完成后,硬件设置中断并禁用通道。此时,您可以响应Block Complete或Transfer Complete中断,或者轮询传输完成原始中断状态寄存器(RawTfr[n], n=通道号),直到硬件设置完毕,以便检测传输何时完成。注意,如果使用这种轮询,软件必须确保在通道启用之前,通过写入中断清除寄存器ClearTfr[n]来清除传输完成中断。
需要与设计沟通的地方
每个芯片的DMA设计都不同,如SRC_Master 与DST_Master的选择。它们在代码中以idx的形式给出,设计对每个芯片选择的Master是不同的idx。(0/1/2/3)
handshaking也需要沟通,handshaking是一组信号或软件寄存器,他们符合DMAC与Source Peripheral/Destination Peripheral之间的握手协议,以控制他们之间的Single/Brust 事务。每款芯片的设计,其各个外设,不管是源外设还是目的外设,他们要控制DMAC都可以看作与DMAC的一个连线,这个连线是需要标记出来并设置到寄存器中的,每款芯片的设计不同,这些连线的标记也会不同。比如I2C要作为Source Peripheral去请求DMA,它和DMAC的连线可能在DT56中标记为1,在DT53中的标记为2。开发不同的芯片的DMAC驱动时一定要注意修改这一点。
裸板代码分析
在这里主要分析裸板代码的内存到内存的single传输:
代码4-1:
void dw_ahb_dma_mem2mem_single(u32 idx, u32 ch, u32 cfg)
{
struct xfer_buff *tr_buff = NULL;
tr_buff = dw_ahb_dmac_get_buff(idx);
/* 1.init src buffer once */
if ((idx == 0) && (cfg == DW_AHB_CHANNEL_CFG_0)) {
for (u32 i = 0; i < tr_buff->buff_size; i++) {
tr_buff->src[i] = i & 0xff;
}
}
/* 2.clear dest buffer */
memset(tr_buff->dst, 0x0, tr_buff->buff_size);
if (dma_cache_on) {
cleanDCache();
}
/* 3.config dmac and start transferring */
qw_cpu_dmac_cfg_t *cpu_dmac_cfg = &cpu_dmac_config[idx][ch];
dw_ahb_dmac_handle_t dmac_handle;
dw_ahb_dmac_priv_t *dmac_priv;
dmac_handle = dw_ahb_dmac_initialize(idx, dw_ahb_dmac_test_callback);
dmac_priv = (dw_ahb_dmac_priv_t *)dmac_handle;
qw_cpu_dmac_reg_t *cpu_dmac = (qw_cpu_dmac_reg_t *)(uintptr_t)(dmac_priv->base);
cpu_dmac_cfg->src_addr = (u32)tr_buff->src + ADDR_OFFSET;
cpu_dmac_cfg->dst_addr = (u32)tr_buff->dst + ADDR_OFFSET;
cpu_dmac_cfg->src_master = 1;//idx;
cpu_dmac_cfg->dst_master = 1;//idx;
if (cfg == DW_AHB_CHANNEL_CFG_0) {
cpu_dmac_cfg->src_bus_width = CPU_DMAC_TR_WIDTH_8BITS;
cpu_dmac_cfg->dst_bus_width = CPU_DMAC_TR_WIDTH_8BITS;
cpu_dmac_cfg->src_burst_size = CPU_DMAC_MSIZE_8;
cpu_dmac_cfg->dst_burst_size = CPU_DMAC_MSIZE_8;
cpu_dmac_cfg->block_size = BLOCK_SIZE0;
} else if (cfg == DW_AHB_CHANNEL_CFG_1) {
cpu_dmac_cfg->src_bus_width = CPU_DMAC_TR_WIDTH_32BITS;
cpu_dmac_cfg->dst_bus_width = CPU_DMAC_TR_WIDTH_32BITS;
cpu_dmac_cfg->src_burst_size = CPU_DMAC_MSIZE_4;
cpu_dmac_cfg->dst_burst_size = CPU_DMAC_MSIZE_4;
cpu_dmac_cfg->block_size = BLOCK_SIZE1;
} else if (cfg == DW_AHB_CHANNEL_CFG_2) {
cpu_dmac_cfg->src_bus_width = CPU_DMAC_TR_WIDTH_32BITS;
cpu_dmac_cfg->dst_bus_width = CPU_DMAC_TR_WIDTH_16BITS;
cpu_dmac_cfg->src_burst_size = CPU_DMAC_MSIZE_4;
cpu_dmac_cfg->dst_burst_size = CPU_DMAC_MSIZE_8;
cpu_dmac_cfg->block_size = BLOCK_SIZE2;
}
dw_ahb_dmac_init_chn_stat(idx, ch,
COUNT_MAX >> cpu_dmac_cfg->src_bus_width,
cpu_dmac_cfg->block_size);
cpu_dmac_cfg->src_addr_change_mode = CPU_DMAC_ADDR_INCREASE;
cpu_dmac_cfg->dst_addr_change_mode = CPU_DMAC_ADDR_INCREASE;
cpu_dmac_cfg->trans_mode = CPU_DMAC_CTL_TT_MEM2MEM;
cpu_dmac_cfg->fifo_mode = CPU_DMAC_FIFO_MODE_EN;
drv_cpu_dmac_init(cpu_dmac, ch, cpu_dmac_cfg);
//pr_info("---dma%d DMAC_CTL0 0x%llx\r\n", idx, cpu_dmac->DMAC_CTL0);
DMA_DBG("---src_bus_width 0x%x, src_burst_size 0x%x\r\n",
cpu_dmac_cfg->src_bus_width, cpu_dmac_cfg->src_burst_size);
DMA_DBG("---src_master 0x%x, dst_master 0x%x\r\n",
cpu_dmac_cfg->src_master, cpu_dmac_cfg->dst_master);
/* enable selected channel's interrupt */
drv_cpu_dmac_enable_intr(cpu_dmac, ch, CPU_DMAC_BLOCK_INTR, CPU_DMAC_INTR_EN);
/* enable dmac */
drv_cpu_dmac_enable_dmac(cpu_dmac, CPU_DMAC_EN);
/* enable selected channel */
drv_cpu_dmac_enable_ch(cpu_dmac, ch, CPU_DMAC_CH_EN);
}
dw_ahb_dmac_initialize初始化一个DMAC控制器dw_ahb_dmac_priv_t* dmac_priv后,就要利用dmac_priv->base开始配置qw_cpu_dmac_reg_t *cpu_dmac
cpu_dmac是寄存器组,前面定义的qw_cpu_dmac_cfg_t *cpu_dmac_cfg是damc配置信息的对象。
DMA驱动的一大任务就是封装对dmac配置信息设置寄存器的函数。这些函数从dmac配置信息来具体地设置DMA寄存器。
drv_cpu_dmac_init就是做这件事:
代码4-2:
s32 drv_cpu_dmac_init(qw_cpu_dmac_reg_t *addr, u32 channel, qw_cpu_dmac_cfg_t *cpu_dmac_cfg)
{
if (channel > CPU_DMAC_MAX_CHANNEL)
return ERROR;
/* set selected channel's source address */
drv_cpu_dmac_set_src_addr(addr, channel, cpu_dmac_cfg->src_addr);
/* set selected channel's destination address */
drv_cpu_dmac_set_dst_addr(addr, channel, cpu_dmac_cfg->dst_addr);
/* set selected channel's block size */
drv_cpu_dmac_set_block_size(addr, channel, cpu_dmac_cfg->block_size);
drv_cpu_dmac_set_src_master(addr, channel, cpu_dmac_cfg->src_master);
drv_cpu_dmac_set_dst_master(addr, channel, cpu_dmac_cfg->dst_master);
/* set selected channel's transfer mode */
drv_cpu_dmac_set_trans_mode(addr, channel, cpu_dmac_cfg->trans_mode);
/* set selected channel's source transfer bus width */
drv_cpu_dmac_set_src_trans_bus_width(addr, channel, cpu_dmac_cfg->src_bus_width);
/* set selected channel's destination transfer bus width */
drv_cpu_dmac_set_dst_trans_bus_width(addr, channel, cpu_dmac_cfg->dst_bus_width);
/* set selected channel's source burst transfer size */
drv_cpu_dmac_set_src_burst_size(addr, channel, cpu_dmac_cfg->src_burst_size);
/* set selected channel's destination burst transfer size */
drv_cpu_dmac_set_dst_burst_size(addr, channel, cpu_dmac_cfg->dst_burst_size);
/* set selected channel's source address change mode */
drv_cpu_dmac_set_src_addr_change_mode(addr, channel, cpu_dmac_cfg->src_addr_change_mode);
/* set selected channel's destination address change mode */
drv_cpu_dmac_set_dst_addr_change_mode(addr, channel, cpu_dmac_cfg->dst_addr_change_mode);
/* set selected channel's source handshake mode, soft or hard */
drv_cpu_dmac_set_src_hs_mode(addr, channel, cpu_dmac_cfg->src_hs_mode);
/* set selected channel's destination handshake mode, soft or hard */
drv_cpu_dmac_set_dst_hs_mode(addr, channel, cpu_dmac_cfg->dst_hs_mode);
/* set selected channel's source hard handshake number */
drv_cpu_dmac_set_src_hs_num(addr, channel, cpu_dmac_cfg->src_hs_num);
/* set selected channel's destination hard handshake number */
drv_cpu_dmac_set_dst_hs_num(addr, channel, cpu_dmac_cfg->dst_hs_num);
/* set selected channel's block size */
drv_cpu_dmac_set_fifo_mode(addr, channel, cpu_dmac_cfg->fifo_mode);
return OK;
}
先不具体看这些设置寄存器的函数,而继续回到代码4-1的dw_ahb_dma_mem2mem_single看它是如何配置cpu_dmac_cfg配置信息对象的。
首先是31-32行 配置源地址与目的地址,其中tr_buff对象是xfer_buff类型,保存了一次传输的基本信息,它由dw_ahb_dmac_get_buff(idx)函数配置,为传输准备源/目的内存空间。
struct xfer_buff {
u8 *src;
u8 *dst;
u32 buff_size;
} g_xfer_buff[DW_AHB_DMAC_MAX];
34-46行 是根据AHB传输通道的不同选择来配置以下两点
-
SRC_Master 与DST_Master,这里需要与芯片设计沟通,看mem2mem的传输应该具体如何选择。
-
设置传输特性,源端/目的端的传输宽度以及burst大小,以及每次传输的block大小
73行 需要使能被选择通道的中断,这里选择的是Block Complete中断
76和79行 前后使能DMAC和Channel
关于AHB传输通道的传输特性,各个参数之间的关系还需要进一步理解:
数据宽度设置为8bit,也就是transfer size=8bits = 1byte(SRC_TR_WIDTH)
一次传8个transfer size,即burst size=8 (SRC_MSIZE = Source Burst Transaction Length:Number of data items each of width CTLx.SRC_TR_WIDTH to be read from the source every time a burst transaferred request is made from either the corresponding hardware or software handshaking interface)
传输块大小设置为4092,也就是Block transfer size=4092(the total number of single transaction to perform for every block transfer)
意味着一次传输,实际DMA 将会从源端传输4092个8bit也就是4092bytes
一次burst请求能传送 8bit*8 = 8个字节
4092字节在burst事务会分为4092字节/8字节=511…4 即进行512次burst事务传完一个block
基于FIFO的burst模式的DMA传输时,BURST的大小乘以数据宽度不得超过设置的FIFO阈值大小,否则会出错。
总的来说dw_ahb_dma_mem2mem_single的配置过程和前面DMA编程示例中给出的“配置一个通道”中的示例类似。