设计说明
-
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 接口定义。
如 DMA 驱动的软件架构图 所示,DMA 驱动不依赖于任何 RTOS 的设备驱动模型,因此无论是在 RTOS 还是 baremetal 环境下,DMA 驱动都可以直接调用 DMA Engine API。

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

在 DMA Client 调用流程中:
-
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);
}