设计说明
-
bsp/artinchip/drv/dma/drv_dma.c:DMA Driver 层实现
-
bsp/artinchip/include/drv/drv_dma.h:DMA Driver 层接口,提供了类似 Linux 的 DMA Engine 接口
-
bsp/artinchip/hal/dma/hal_dma.c:DMA HAL 层实现
-
bsp/artinchip/include/hal/hal_dma.h:DMA HAL 层接口头文件
-
bsp/artinchip/hal/dma/hal_dma_reg.h:DMA 控制器的寄存器定义文件
DMA 驱动的软件架构
RTOS 系统中未提供一个类似 Linux 的 DMA Engine 子系统,但为了方便 DMA 使用者的代码兼容,DMA 驱动提供了类似 Linux 的 DMA Engine 接口定义。
如 图 1 所示,DMA 驱动不依赖于任何 RTOS 的设备驱动模型,因此无论是在 RTOS 还是 baremetal 环境下,DMA 驱动都可以直接调用 DMA Engine API。

关键流程设计
本节介绍了 DMA 模块的关键流程。
初始化流程
DMA 驱动的初始化接口通过 INIT_BOARD_EXPORT(drv_dma_init) 完成注册,其中主要步骤有:
-
初始化模块的 clk。
-
初始化 DMA 通道、任务描述符管理信息。
-
注册中断。
DMA Client 的调用流程
作为 DMA 用户,调用流程如下:

在 图 2中:
-
dmaengine_submit():将传输请求提交到 DMA Engine 的缓存中,但不开始传输数据。
-
dma_async_issue pending():将传输请求加入到 DMA Device 的请求队列中,接下来才会启动数据传输。
中断处理流程
DMA 中断处理流程可以确保数据高效传输并合理配置系统资源。当 DMA 传输完成或遇到错误时,中断机制会触发相应的处理程序。中断处理流程如下所示:
-
读取 DMA 完成寄存器,获取已完成的传输数量信息,并逐个查看每个 DMA 通道的完成状态。
在处理完中断后,需要清除相应通道的中断标志位,以免再次触发相同的中断。
-
如果通道有任务传输完成,就调用相应 DMA client 注册的回调函数,进行后续数据处理或其它操作。
每个 DMA 通道在初始化时都会注册一个回调函数,当该通道的传输任务完成时,就会调用这个回调函数。
数据结构设计
本节介绍了 DMA 数据类型及其结构描述。
struct aic_dma_dev
struct aic_dma_dev { struct aic_dma_task task[TASK_MAX_NUM]; s32 inited; unsigned long base; u32 burst_length; /* burst length capacity */ u32 addr_widths; /* address width support capacity */ struct aic_dma_chan dma_chan[AIC_DMA_CH_NUM]; struct aic_dma_task *freetask; };
struct aic_dma_chan
struct aic_dma_chan { u8 ch_nr; /* drq port number */ u8 used; u8 irq_type; /* irq types */ bool cyclic; /* flag to mark if cyclic transfer one package */ bool memset; unsigned long base; struct dma_slave_config cfg; volatile int lock; dma_async_callback callback; void *callback_param; struct aic_dma_task * desc; };
struct aic_dma_task
DMA 控制器支持散列 (Scatter Gather) 的描述符参数形式,需要提前将参数分组打包到多个描述符中,一个 Buffer 对应一组散列参数。这些描述符会组成一个链表,然后将这个链表的第一个描述符的物理地址传给 DMA 控制器。描述符组成的链表结构如下图:

struct aic_dma_task { u32 cfg; /* DMA transfer configuration */ u32 src; /* source address of one transfer package */ u32 dst; /* destination address of one transfer package */ u32 len; /* data length of one transfer package */ u32 delay; /* time delay for period transfer */ u32 p_next; /* next task node for DMA controller */ u32 mode; /* the negotiation mode */ /* * virtual list for driver maintain package list, * not used by DMA controller */ struct aic_dma_task *v_next; };
struct dma_slave_config
struct dma_slave_config { enum dma_transfer_direction direction; unsigned long src_addr; unsigned long dst_addr; enum dma_slave_buswidth src_addr_width; enum dma_slave_buswidth dst_addr_width; uint32_t src_maxburst; uint32_t dst_maxburst; uint32_t slave_id; };
接口设计
Driver 层接口设计
以下接口是遵循 Linux DMA Engine 子系统的标准接口。
函数原型 |
int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config) |
---|---|
功能说明 |
配置指定的 DMA 物理通道 |
参数定义 |
chan - 指向一个 DMA 物理通道
config - 保存了需要的配置信息
|
返回值 |
0,成功 |
注意事项 |
- |
函数原型 |
int dmaengine_pause(struct dma_chan *chan) |
---|---|
功能说明 |
暂停指定通道的传输操作 |
参数定义 |
chan - 指向一个 DMA 物理通道 |
返回值 |
0,成功 |
注意事项 |
- |
函数原型 |
int dmaengine_resume(struct dma_chan *chan) |
---|---|
功能说明 |
恢复指定通道的传输操作 |
参数定义 |
chan - 指向一个 DMA 物理通道 |
返回值 |
0,成功 |
注意事项 |
- |
函数原型 |
int dmaengine_prep_dma_memcpy(struct dma_chan *chan,
uint32_t dest, uint32_t src, uint32_t len)
|
---|---|
功能说明 |
memcpy 操作的预处理 |
参数定义 |
chan - 指向一个 DMA 物理通道
dest - 目标 Buffer 的物理地址
src - 源 Buffer 的物理地址
len - 数据长度
|
返回值 |
0,成功;<0,失败 |
注意事项 |
- |
函数原型 |
int dmaengine_prep_dma_device(struct dma_chan *chan,
uint32_t dest, uint32_t src, uint32_t len,
enum dma_transfer_direction dir)
|
---|---|
功能说明 |
设备与内存之间传输操作的预处理 |
参数定义 |
chan - 指向一个 DMA 物理通道
dest - 目标 Buffer 的物理地址
src - 源 Buffer 的物理地址
len - 数据长度
dir - 传输方向,是 Dev to Mem,还是 Mem to Dev
|
返回值 |
0,成功;<0,失败 |
注意事项 |
- |
函数原型 |
int dmaengine_prep_dma_cyclic(struct dma_chan *chan,
uint32_t buf_addr, uint32_t buf_len, uint32_t
period_len,
enum dma_transfer_direction dir)
|
---|---|
功能说明 |
(设备与内存之间)循环传输操作的预处理 |
参数定义 |
chan - 指向一个 DMA 物理通道
buf_addr - 循环 Buffer 的起始物理地址
buf_len - 循环 Buffer 的总长度
period_len - 循环的 Buffer 片段长度
dir - 传输方向,是 Dev to Mem,还是 Mem to Dev
|
返回值 |
0,成功;<0,失败 |
注意事项 |
- |
函数原型 |
void dma_async_issue_pending(struct dma_chan *chan) |
---|---|
功能说明 |
启动指定通道的数据传输 |
参数定义 |
chan - 指向一个 DMA 物理通道 |
返回值 |
无 |
注意事项 |
- |
函数原型 |
int dmaengine_terminate_async(struct dma_chan *chan) |
---|---|
功能说明 |
终止指定通道的数据传输 |
参数定义 |
chan - 指向一个 DMA 物理通道 |
返回值 |
0,成功 |
注意事项 |
- |
函数原型 |
enum dma_status dmaengine_tx_status(struct dma_chan *chan, uint32_t *residue) |
---|---|
功能说明 |
获取指定通道的传输状态 |
参数定义 |
chan - 指向一个 DMA 物理通道
residue - 还没有传输完成的剩余长度,单位:字节
|
返回值 |
DMA_COMPLETE,传输完成;DMA_IN_PROGRESS,传输中 |
注意事项 |
- |
HAL 层接口设计
int hal_dma_chan_prep_memset(struct aic_dma_chan *chan, uint32_t p_dest, uint32_t value, uint32_t len); int hal_dma_chan_prep_memcpy(struct aic_dma_chan *chan, uint32_t p_dest, uint32_t p_src, uint32_t len); int hal_dma_chan_prep_device(struct aic_dma_chan *chan, uint32_t p_dest, uint32_t p_src, uint32_t len, enum dma_transfer_direction dir); int hal_dma_chan_prep_cyclic(struct aic_dma_chan *chan, uint32_t p_buf_addr, uint32_t buf_len, uint32_t period_len, enum dma_transfer_direction dir); int hal_dma_chan_tx_status(struct aic_dma_chan *chan, uint32_t *left_size); int hal_dma_chan_start(struct aic_dma_chan *chan); int hal_dma_chan_stop(struct aic_dma_chan *chan); int hal_dma_chan_pause(struct aic_dma_chan *chan); int hal_dma_chan_resume(struct aic_dma_chan *chan); int hal_dma_chan_terminate_all(struct aic_dma_chan *chan); int hal_dma_chan_register_cb(struct aic_dma_chan *chan, dma_async_callback callback, void *callback_param); int hal_dma_chan_config(struct aic_dma_chan *chan, struct dma_slave_config *config); int hal_release_dma_chan(struct aic_dma_chan *chan); struct aic_dma_chan * hal_request_dma_chan(void); int hal_dma_init(void); int hal_dma_deinit(void); int hal_dma_chan_dump(int ch_nr); irqreturn_t hal_dma_irq(int irq, void *arg);
Demo
Mem to Device
static s32 qspi_tx_rx_dma(u32 base, u8 *tx, u32 txlen, u8 *rx, u32 rxlen) { u32 poll_time, single_len; s32 ret = 0; single_len = 0; struct aic_dma_chan *rx_dma, *tx_dma; struct dma_slave_config dmacfg; qspi_reset_fifo(base); tx_dma = NULL; rx_dma = NULL; if (tx) { spi_setbits(FCR_BIT_TX_DMA_EN, SPI_REG_FCR(base)); if (qspi_in_single_mode(base)) single_len = txlen; qspi_set_xfer_cnt(base, txlen, 0, single_len, 0); tx_dma = hal_request_dma_chan(); if (!tx_dma) goto out; dmacfg.direction = DMA_MEM_TO_DEV; dmacfg.src_addr = (unsigned long)tx; dmacfg.dst_addr = (unsigned long)SPI_REG_TXD(base); dmacfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; dmacfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; dmacfg.src_maxburst = 1; dmacfg.dst_maxburst = 1; dmacfg.slave_id = 10; // FIXME: should set id according spi id ret = hal_dma_chan_config(tx_dma, &dmacfg); if (ret) goto out; ret = hal_dma_chan_prep_device( tx_dma, (uint32_t)(unsigned long)SPI_REG_TXD(base), (uint32_t)(unsigned long)tx, txlen, DMA_MEM_TO_DEV); if (ret) goto out; ret = hal_dma_chan_start(tx_dma); if (ret) goto out; /* Start transfer */ spi_setbits(TCR_BIT_XCH, SPI_REG_TCR(base)); poll_time = 0x7FFFFFFF; while (!(readl(SPI_REG_ISR(base)) & ISR_BIT_TC)) { poll_time--; if (poll_time == 0) { ret = -1; spi_clrbits(FCR_BIT_TX_DMA_EN, SPI_REG_FCR(base)); hal_log_err("TX Transfer complete timeout at the end.\n"); goto out; } } spi_setbits(ISR_BIT_TX_EMP, SPI_REG_ISR(base)); spi_setbits(ISR_BIT_TX_FULL, SPI_REG_ISR(base)); spi_setbits(ISR_BIT_TX_RDY, SPI_REG_ISR(base)); spi_setbits(ISR_BIT_TC, SPI_REG_ISR(base)); spi_clrbits(FCR_BIT_TX_DMA_EN, SPI_REG_FCR(base)); } if (rx) { spi_setbits(FCR_BIT_RX_DMA_EN, SPI_REG_FCR(base)); qspi_set_xfer_cnt(base, 0, rxlen, 0, 0); rx_dma = hal_request_dma_chan(); if (!rx_dma) goto out; dmacfg.direction = DMA_DEV_TO_MEM; dmacfg.src_addr = (unsigned long)SPI_REG_RXD(base); dmacfg.dst_addr = (unsigned long)rx; dmacfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; dmacfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; dmacfg.src_maxburst = 1; dmacfg.dst_maxburst = 1; dmacfg.slave_id = 10; // FIXME: should set id according spi id ret = hal_dma_chan_config(rx_dma, &dmacfg); if (ret) goto out; ret = hal_dma_chan_prep_device(rx_dma, (uint32_t)(unsigned long)rx, (uint32_t)(unsigned long)SPI_REG_RXD(base), rxlen, DMA_DEV_TO_MEM); if (ret) goto out; ret = hal_dma_chan_start(rx_dma); if (ret) goto out; /* Start transfer */ spi_setbits(TCR_BIT_XCH, SPI_REG_TCR(base)); poll_time = 0x7FFFFFFF; while (!(readl(SPI_REG_ISR(base)) & ISR_BIT_TC)) { poll_time--; if (poll_time == 0) { ret = -1; spi_clrbits(FCR_BIT_TX_DMA_EN, SPI_REG_FCR(base)); hal_log_err("RX Transfer complete timeout at the end.\n"); goto out; } } spi_setbits(ISR_BIT_TC, SPI_REG_ISR(base)); spi_setbits(ISR_BIT_RX_EMP, SPI_REG_ISR(base)); spi_setbits(ISR_BIT_RX_FULL, SPI_REG_ISR(base)); spi_setbits(ISR_BIT_RX_RDY, SPI_REG_ISR(base)); spi_clrbits(FCR_BIT_TX_DMA_EN, SPI_REG_FCR(base)); } out: if (tx_dma) { hal_dma_chan_stop(tx_dma); hal_release_dma_chan(tx_dma); } if (rx_dma) { hal_dma_chan_stop(rx_dma); hal_release_dma_chan(rx_dma); } return ret; }
Mem to Mem
static void dma_test_cb(void *param) { printf("DMA complete, callback....\n"); } static void cmd_test_dma_memcpy(int argc, char **argv) { struct dma_chan *chan = NULL; uint32_t test_len = 0, align_len = 0; char *src = NULL, *dest = NULL; int ret, i; uint32_t size = 0; #ifdef RT_USING_POSIX_CLOCK struct timespec start, end; #endif if (argc != 2) { pr_err("Invalid parameter\n"); return; } sscanf((char *)argv[1], "%u", &test_len); test_len = roundup(test_len, 8); align_len = roundup(test_len, CACHE_LINE_SIZE); src = aicos_malloc_align(0, align_len, CACHE_LINE_SIZE); dest = aicos_malloc_align(0, align_len, CACHE_LINE_SIZE); if ((src == NULL) || (dest == NULL)){ pr_err("Alloc %d mem fail!\n ", align_len); goto free_mem; } printf("DMA memcpy test: src = 0x%lx, dest = 0x%lx, len = 0x%x\n", (unsigned long)src, (unsigned long)dest, test_len); for (i = 0;i < test_len; i++) src[i] = i & 0xff; #ifdef RT_USING_POSIX_CLOCK clock_gettime(CLOCK_REALTIME, &start); #endif chan = dma_request_channel(); if (chan == NULL){ pr_err("Alloc dma chan fail!\n "); goto free_mem; } ret = dmaengine_prep_dma_memcpy(chan, (unsigned long)dest, (unsigned long)src, test_len); if (ret){ pr_err("dmaengine_prep_dma_memcpy fail! ret = %d\n ", ret); goto free_chan; } ret = dmaengine_submit(chan, dma_test_cb, chan); if (ret){ pr_err("dmaengine_submit fail! ret = %d\n ", ret); goto free_chan; } dma_async_issue_pending(chan); while (dmaengine_tx_status(chan, &size) != DMA_COMPLETE); aicos_dcache_invalid_range((unsigned long *)src, align_len); aicos_dcache_invalid_range((unsigned long *)dest, align_len); #ifdef RT_USING_POSIX_CLOCK clock_gettime(CLOCK_REALTIME, &end); #endif for (i = 0;i < test_len; i++){ if (dest[i] != src[i]){ printf("addr 0x%x err: src - 0x%x, dest - 0x%x\n", i, src[i], dest[i]); ret = -1; } } if (ret) printf("DMA test fail!\n"); else printf("DMA test succeed!\n"); #ifdef RT_USING_POSIX_CLOCK printf("DMA memcpy %u bytes, speed %.2f MB/s\n", align_len, (float)align_len / 1024 / 1024 / time_diff(&start, &end)); #endif free_chan: if (chan) dma_release_channel(chan); free_mem: if (src) aicos_free_align(0, src); if (dest) aicos_free_align(0, dest); }