原文来自
https://blog.csdn.net/eastmoon502136/article/details/8502087
DMA(Direct Memory Access,直接存储器访问) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用
在实现DMA传输时,是由DMA控制器直接掌管总线,因此,存在着一个总线控制权转移问题。即DMA传输前,CPU要把总线控制权交给DMA控制器,而在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU。一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。
DMA工作过程
对于嵌入式中的DMA,其实是在写数据寄存器的时候用dma的传输来代替。就像i2c设备,在发送和接收数据的时候都是要往数据寄存器中写数据的。比如那个寄存器是I2C_DATA,如果用cpu来传输的话就是writel(data, I2C_DATA);而用dma传输就是配置好要传输的buf长度,然后源地址就是buf的地址,目标地址就是I2C_DATA。
这里还要注意经过cpu的是虚拟地址,而dma传输的是物理地址。
其实dma传输就是dma控制在两个物理地址之间传输数据。
Linux下用dma传输主要调用下面这些函数就可以实现外部的dma了。
1、初始化DMA
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE,mask); /*1. Init rx channel */
dws->rxchan= dma_request_channel(mask, dma_chan_filter, params);
主要就是申请DMA通道。
dma_chan_filter这个函数主要是查找你的dma传输的设备的请求信号线,其具体是在注册时填写的。
这里会根据这个函数返回的真假来判断已经注册在总线上的dma slave的。
buf =kmalloc(DMA_BUFFER_SIZE, GFP_KERNEL);
//申请一块地址,用来DMA传输的数据就放在这里
sg_init_one(&dma_dev->dmatx.sg, buf, DMA_BUFFER_SIZE);
//初始化,其主要为了发送时虚拟地址和物理地址的映射。
2、启动DMA
struct dma_async_tx_descriptor *txdesc = NULL;
struct dma_chan *txchan,;
struct dma_slave_config txconf;
txchan= dws->txchan;
/*2. Prepare the TX dma transfer */
txconf.direction= DMA_TO_DEVICE; //表示dma传输方向为发送
txconf.dst_addr= dws->dma_addr; //目标地址,物理地址
txconf.dst_maxburst= LNW_DMA_MSIZE_16; //最大传输的字节数
txconf.dst_addr_width= DMA_SLAVE_BUSWIDTH_2_BYTES; //数据的位宽
txchan->device->device_control(txchan,DMA_SLAVE_CONFIG,
(unsigned long) &txconf);
dws->tx_sgl.length= dws->len; //要传输的数据的长度
dma_map_sg(dma_dev->dev,&dmatx->sg, 1, DMA_TO_DEVICE);
//通过这个函数来实现虚拟地址和物理地址的映射。
txdesc= txchan->device->device_prep_slave_sg(txchan,
&dws->tx_sgl,
1,
DMA_TO_DEVICE,
DMA_PREP_INTERRUPT| DMA_COMPL_SKIP_DEST_UNMAP);
txdesc->callback= dw_spi_dma_done; //传输完成后的回调函数
txdesc->callback_param= params; //回调函数中的参数
dmaengine_submit(txdesc);
dma_dev->device_issue_pending(txchan); // 启动dma传输了
配置好后,这样DMA就会开始传输了,然后传输完了以后就会有调用callback函数