源码说明
源代码位于 bsp/artinchip/
:
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 控制器的寄存器定义
模块架构
RTOS 系统中并没有提供了一个类似 Linux 的 DMA Engine 子系统,为了方便DMA使用者的代码兼容,DMA驱动提供了类似Linux的DMA Engine接口定义。
DMA 驱动的软件框架如下图:
图中可以看到,DMA 驱动不依赖任何 RTOS 的设备驱动模型,所以无论是在 RTOS 环境、还是 baremetal 环境,都是可以直接调用 DMA Engine API 的。
关键流程设计
初始化流程
DMA 驱动的初始化接口通过 INIT_BOARD_EXPORT(drv_dma_init)
完成注册,其中主要步骤有:
初始化模块的clk
初始化DMA通道、任务描述符管理信息
注册中断
DMA Client 的调用流程
作为DMA用户,调用流程和Linux的DMA Engine完全相同,如下:
其中有两个操作的概念需要注意:
submit,是指传输请求提交给了 DMA 驱动的缓存中,还没有开始传输数据
issue pending,才会真正启动数据传输动作
中断处理流程
中断处理流程主要是:
逐个DMA通道的查看完成状态;
如果通道有任务传输完成,就调用相应DMA client注册的回调函数。
数据结构设计
struct aic_dma_dev
属于 HAL 层接口,记录DMA控制器的配置信息:
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;
};
2
3
4
5
6
7
8
9
struct aic_dma_chan¶
属于 HAL 层接口,记录了一个 DMA 物理通道对应的通道号、寄存器基地址等信息:
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;
};
2
3
4
5
6
7
8
9
10
11
12
13
struct aic_dma_task¶
DMA 控制器支持散列(Scatter Gather)的描述符参数形式,需要提前将参数分组(一个Buffer对应一组散列参数)打包到多个描述符中,这些描述符会组成一个链表,然后将这个链表的第一个描述符的物理地址传给DMA控制器。描述符组成的链表结构如下图:
TIP
End Flag 是DMA控制器硬件预先定义好的一个数值:0xfffff800。
DMA 描述符的数据结构定义如下:
struct aic_dma_task {
u32 cfg; /* dma transfer configuration */
u32 src; /* source address of one transfer package */
u32 dst; /* distination address of one transfer package */
u32 len; /* data length of one transfer package */
u32 delay; /* time delay for period transfer */
u32 p_next; /* next package for dma controller */
u32 mode; /* the negotiation mode, not used by phsical task list */
#if (CACHE_LINE_SIZE == 64)
u32 pad[7];
#endif
/*
* virtual list for cpu maintain package list,
* not used by dma controller
*/
struct aic_dma_task *v_next;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct dma_slave_config
DAM 通道的配置信息,和 Linux DMA Engine 接口的定义保持一致。
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;
};
2
3
4
5
6
7
8
9
10
Driver 层接口设计
以下接口是遵循 Linux DMA Engine 子系统的标准接口。
dmaengine_slave_config
函数原型 | int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config) |
---|---|
功能说明 | 配置指定的DMA物理通道 |
参数定义 | chan - 指向一个DMA物理通道 ; config - 保存了需要的配置信息 |
返回值 | 0,成功 |
注意事项 |
dmaengine_pause
函数原型 | int dmaengine_pause(struct dma_chan *chan) |
---|---|
功能说明 | 暂停指定通道的传输操作 |
参数定义 | chan - 指向一个DMA物理通道 |
返回值 | 0,成功 |
注意事项 |
dmaengine_resume
函数原型 | int dmaengine_resume(struct dma_chan *chan) |
---|---|
功能说明 | 暂停指定通道的传输操作 |
参数定义 | chan - 指向一个DMA物理通道 |
返回值 | 0,成功 |
注意事项 |
dmaengine_prep_dma_memcpy
函数原型 | 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, 失败 |
注意事项 |
dmaengine_prep_dma_device
函数原型 | 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, 失败 |
注意事项 |
dmaengine_prep_dma_cyclic
函数原型 | 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, 失败 |
注意事项 |
dma_async_issue_pending
函数原型 | void dma_async_issue_pending(struct dma_chan *chan) |
---|---|
功能说明 | 启动指定通道的数据传输 |
参数定义 | chan - 指向一个DMA物理通道 |
返回值 | 无 |
注意事项 |
dmaengine_terminate_async
函数原型 | int dmaengine_terminate_async(struct dma_chan *chan) |
---|---|
功能说明 | 终止指定通道的数据传输 |
参数定义 | chan - 指向一个DMA物理通道 |
返回值 | 无 |
注意事项 |
dmaengine_tx_status
函数原型 | enum dma_status dmaengine_tx_status(struct dma_chan* chan, uint32_t* residue) |
---|---|
功能说明 | 获取指定通道的传输状态 |
参数定义 | chan - 指向一个DMA物理通道 residue - 还没有传输完成的剩余长度, 单位: 字节 |
返回值 | DMA_COMPLETE, 传输完成; DMA_IN_PROGRESS, 传输中 |
注意事项 |
HAL 层接口设计
HAL 层的函数接口声明存放在 hal_dma.h 中,主要接口有:
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);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Demo
Mem to Device
SPI 驱动(详见 bsp/artinchip/hal/qspi/aic_hal_qspi.c)中调用了DMA进行数据传输,其使用过程可以当作Demo参考:
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
Mem to Mem
本Demo是 test_dma_memcpy 的源码(bsp/examples/test-dma/test_dma.c),完成一次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);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96