DMA 驱动程序
驱动框架
DMA 内核相关代码位于“SylixOS/system/device/dma/”目录下,其中包含 DMA 中断、注册等。驱动仅需配置源端口地址、目的端口地址、DMA 通道号等参数即可完成驱动配置(本章以 mini2440 开发板为例,介绍 DMA 驱动程序)。
DMA 驱动程序需要实现 LW_DMA_FUNCS 结构体中 3 个回调函数的填充和一个中断处理函数。
硬件芯片如果支持多个 DMA 通道,则需要多次调用驱动加载函数来加载对应通道的 DMA 驱动。
DMA 通道结构体
__DMA_PHY_CHANNEL 是重要的结构体,通用 DMA 的硬件特性的配置都需要依赖此结构体,如下所示:
#include <SylixOS.h>
typedef struct
{
volatile unsigned int uiSrcAddr; /* 源端地址 */
volatile unsigned int uiSrcCtl; /* 源端地址控制 */
volatile unsigned int uiDstAddr; /* 目的端地址 */
volatile unsigned int uiDstCtl; /* 目的端地址控制 */
volatile unsigned int uiDMACtl; /* DMA通道控制 */
volatile unsigned int uiDMAStat; /* DMA通道状态 */
volatile unsigned int uiCurScr; /* DMA当前源端地址 */
volatile unsigned int uiCurDst; /* DMA当前目的端地址 */
volatile unsigned int uiMaskTigger; /* 触发控制寄存器 */
} __DMA_PHY_CHANNEL;
- 参数 uiSrcAddr 是 DMA 搬运源端地址。
- 参数 uiSrcCtl 是 DMA 搬运源端地址控制。
- 参数 uiDstAddr 是 DMA 搬运目的端地址。
- 参数 uiDstCtl 是 DMA 搬运目的端地址控制。
- 参数 uiDMACtl 是 DMA 通道控制。
- 参数 uiDMAStat 是 DMA 通道状态。
- 参数 uiCurScr 是 DMA 当前源端地址。
- 参数 uiCurDst 是 DMA 当前目的端地址。
- 参数 uiMaskTigger 是触发控制寄存器。
此结构体中的成员是根据硬件平台的通用 DMA 的使用原理来定义的。在用户使用过程中需要根据实际使用的开发板的硬件信息来配置相关的参数。
硬件地址定义
下面的介绍是根据开发板的寄存器地址定义的宏或数组。实际编写驱动过程中需参考下述结构,根据实际使用的开发板的硬件信息来填充结构。
#include <SylixOS.h>
#define __2440_DMA_PHY_ADDR {(__DMA_PHY_CHANNEL *)0x4b000000, \
(__DMA_PHY_CHANNEL *)0x4b000040, \
(__DMA_PHY_CHANNEL *)0x4b000080, \
(__DMA_PHY_CHANNEL *)0x4b0000c0}
中断处理函数
DMA 中断触发之后,只需在相关通道的中断内清除中断标志位即可。如下所示:
#include <SylixOS.h>
static irqreturn_t __dmaHwIsr (int iChannel)
{
switch (iChannel) { /* 清除指定中断标志 */
case LW_DMA_CHANNEL0:
INTER_CLR_PNDING(BIT_DMA0);
break;
case LW_DMA_CHANNEL1:
INTER_CLR_PNDING(BIT_DMA1);
break;
case LW_DMA_CHANNEL2:
INTER_CLR_PNDING(BIT_DMA2);
break;
case LW_DMA_CHANNEL3:
INTER_CLR_PNDING(BIT_DMA3);
break;
default:
return (LW_IRQ_HANDLED);
}
API_DmaContext(iChannel); /* 调用DMA处理函数 */
return (LW_IRQ_HANDLED);
}
DMA 传输完成之后,会触发一次 DMA 中断。在中断服务函数中清除中断标志位,再调用系统函数 API_DmaContext 函数,进行相关操作。
初始化 DMA
DMA 驱动加载或 DMA 复位过程中会调用 __dmaHwReset 函数,此函数时需按照如下步骤。第一步填充对应 DMA 通道的初始化操作,第二步清除对应通道 DMA 驱动的悬挂中断标志位,第三步向系统内核中注册相应的中断处理函数,使能对应中断即可。__dmaHwReset 函数如下:
#include <SylixOS.h>
static void __dmaHwReset (UINT uiChannel, PLW_DMA_FUNCS pdmafuncs)
{
__DMA_PHY_CHANNEL *pdmaphychanCtl = __GpdmaphychanTbl[uiChannel];
if (uiChannel >= __2440_DMA_CHAN_NUM) {
return; /* 通道出错 */
}
pdmaphychanCtl->uiMaskTigger = (__DMA_PHY_STOP | __DMA_PHY_ON);
/* 停止 DMA */
/*
* 安装中断处理例程
*/
switch (uiChannel) {
case LW_DMA_CHANNEL0:
INTER_CLR_PNDING(BIT_DMA0); /* 清除悬挂中断标志 */
API_InterVectorConnect(VIC_CHANNEL_DMA0,
(PINT_SVR_ROUTINE)__dmaHwIsr,
(PVOID)uiChannel,
"dma0_isr"); /* 安装中断处理例程 */
INTER_CLR_MSK((1u << VIC_CHANNEL_DMA0));
break;
case LW_DMA_CHANNEL1:
INTER_CLR_PNDING(BIT_DMA1); /* 清除悬挂中断标志 */
API_InterVectorConnect(VIC_CHANNEL_DMA1,
(PINT_SVR_ROUTINE)__dmaHwIsr,
(PVOID)uiChannel,
"dma1_isr"); /* 安装中断处理例程 */
INTER_CLR_MSK((1u << VIC_CHANNEL_DMA1));
break;
case LW_DMA_CHANNEL2:
INTER_CLR_PNDING(BIT_DMA2); /* 清除悬挂中断标志 */
API_InterVectorConnect(VIC_CHANNEL_DMA2,
(PINT_SVR_ROUTINE)__dmaHwIsr,
(PVOID)uiChannel,
"dma2_isr"); /* 安装中断处理例程 */
INTER_CLR_MSK((1u << VIC_CHANNEL_DMA2));
break;
case LW_DMA_CHANNEL3:
INTER_CLR_PNDING(BIT_DMA3); /* 清除悬挂中断标志 */
API_InterVectorConnect(VIC_CHANNEL_DMA3,
(PINT_SVR_ROUTINE)__dmaHwIsr,
(PVOID)uiChannel,
"dma3_isr"); /* 安装中断处理例程 */
INTER_CLR_MSK((1u << VIC_CHANNEL_DMA3));
break;
}
}
获取 DMA 状态函数
系统调用 DMA 驱动的时候,有时会需要判断当前通道 DMA 工作状态来判断是否能执行相关操作。__dmaHwGetStatus 函数的功能是根据传入的通道号来判断当前通道的 DMA 的状态。
#include <SylixOS.h>
static int __dmaHwGetStatus (UINT uiChannel, PLW_DMA_FUNCS pdmafuncs)
{
__DMA_PHY_CHANNEL *pdmaphychanCtl = __GpdmaphychanTbl[uiChannel];
if (uiChannel >= __2440_DMA_CHAN_NUM) {
return (PX_ERROR); /* 通道出错 */
}
/*
* 检测状态
*/
if (pdmaphychanCtl->uiDMAStat & __DMA_PHY_STAT) {
return (LW_DMA_STATUS_BUSY);
} else {
return (LW_DMA_STATUS_IDLE);
}
}
函数成功返回 LW_DMA_STATUS_IDLE ,失败返回 LW_DMA_STATUS_BUSY 。
初始化一次 DMA 传输
DMA 驱动安装完毕之后,系统如需启动 DMA 进行数据搬运工作可调用 __dmaHwTransact 函数配置一次传输。DMA 驱动根据 CPU 传参来选择源地址是 APB 或 AHB 总线,DMA 拷贝的方向,目的端总线,最后进行 DMA 控制器相关配置,如选择触发方式是硬件触发或软件触发。完成配置之后会在对应驱动中启动 DMA 来进行一次 DMA 搬运工作,__dmaHwTransact 函数如下:
#include <SylixOS.h>
static int __dmaHwTransact (UINT uiChannel,
PLW_DMA_FUNCS pdmafuncs,
PLW_DMA_TRANSACTION pdmatMsg)
{
__DMA_PHY_CHANNEL *pdmaphychanCtl = __GpdmaphychanTbl[uiChannel];
if (uiChannel >= __2440_DMA_CHAN_NUM) {
return (-1); /* 通道出错 */
}
if (!pdmatMsg) {
return (-1); /* 消息指针错误 */
}
/*
* 保存地址信息
*/
pdmaphychanCtl->uiSrcAddr = (unsigned int)pdmatMsg->DMAT_pucSrcAddress;
pdmaphychanCtl->uiDstAddr = (unsigned int)pdmatMsg->DMAT_pucDestAddress;
/*
* 设置源端地址控制信息
*/
switch (pdmatMsg->DMAT_iSrcAddrCtl) {
case LW_DMA_ADDR_INC:
if (pdmatMsg->DMAT_ulOption & DMA_OPTION_SRCBUS_APB) {
/* 源端为 APB 总线 */
pdmaphychanCtl->uiSrcCtl = (1 << 1);
} else { /* 源端为 AHB 总线 */
pdmaphychanCtl->uiSrcCtl = 0;
}
break;
case LW_DMA_ADDR_FIX:
if (pdmatMsg->DMAT_ulOption & DMA_OPTION_SRCBUS_APB) {
/* 源端为 APB 总线 */
pdmaphychanCtl->uiSrcCtl = ((1 << 1) | 1);
} else { /* 源端为 AHB 总线 */
pdmaphychanCtl->uiSrcCtl = 1;
}
break;
default:
return (PX_ERROR); /* 不支持 */
}
/*
* 设置目的端地址控制信息
*/
switch (pdmatMsg->DMAT_iDestAddrCtl) {
case LW_DMA_ADDR_INC:
if (pdmatMsg->DMAT_ulOption & DMA_OPTION_DSTBUS_APB) {
/* 目的端为 APB 总线 */
pdmaphychanCtl->uiDstCtl = (1 << 1);
} else { /* 目的端为 AHB 总线 */
pdmaphychanCtl->uiDstCtl = 0;
}
break;
case LW_DMA_ADDR_FIX:
if (pdmatMsg->DMAT_ulOption & DMA_OPTION_DSTBUS_APB) {
/* 目的端为 APB 总线 */
pdmaphychanCtl->uiDstCtl = ((1 << 1) | 1);
} else { /* 目的端为 AHB 总线 */
pdmaphychanCtl->uiDstCtl = 1;
}
break;
default:
return (PX_ERROR); /* 不支持 */
}
/*
* 设置DMA址控制信息
*/
{
int iHandshake = 0; /* 非握手模式,测试用 */
int iSyncClk = 0; /* 请求段同步时钟 */
int iInterEn = 1; /* 允许中断 */
int iTransferMode = 0; /* 卒发或单字传输 */
int iServiceMode = 0; /* 完全 or 单次传输模式 */
int iReqScr = 0; /* 请求源 */
int iSwOrHwReg = 0; /* 请求启动方式 */
int iAutoReloadDis = 1; /* 是否禁能自动加载 */
int iDataSizeOnce = 0; /* 一次传输的数据宽度 */
int iLength; /* 传输的长度 */
if (pdmatMsg->DMAT_bHwHandshakeEn) {
iHandshake = 1; /* 使用硬件握手 */
}
if (pdmatMsg->DMAT_iTransMode & DMA_TRANSMODE_WHOLE) {
iServiceMode = 1; /* 完全传输模式 */
}
if (pdmatMsg->DMAT_iTransMode & DMA_TRANSMODE_CLKAHB) {
iSyncClk = 1; /* AHB 时钟源 */
}
if (pdmatMsg->DMAT_iTransMode & DMA_TRANSMODE_BURST) {
iTransferMode = 1; /* 卒发模式 */
}
iReqScr = pdmatMsg->DMAT_iHwReqNum; /* 请求源编号 */
if (pdmatMsg->DMAT_bHwReqEn) {
iSwOrHwReg = 1; /* 硬件请求启动 */
}
if (pdmatMsg->DMAT_iTransMode & DMA_TRANSMODE_DBYTE) {
iDataSizeOnce = 1; /* 半字传输 */
} else if (pdmatMsg->DMAT_iTransMode & DMA_TRANSMODE_4BYTE) {
iDataSizeOnce = 2; /* 字传输 */
}
switch (iDataSizeOnce) { /* 确定传输长度 */
case 0:
iLength = (INT)pdmatMsg->DMAT_stDataBytes;
break;
case 1:
iLength = (INT)pdmatMsg->DMAT_stDataBytes / 2;
break;
case 2:
iLength = (INT)pdmatMsg->DMAT_stDataBytes / 4;
break;
}
pdmaphychanCtl->uiDMACtl = ((unsigned)iHandshake << 31)
| (iSyncClk << 30)
| (iInterEn << 29)
| (iTransferMode << 28)
| (iServiceMode << 27)
| (iReqScr << 24)
| (iSwOrHwReg << 23)
| (iAutoReloadDis << 22)
| (iDataSizeOnce << 20)
| (iLength); /* 设置控制寄存器 */
/*
* 启动DMA
*/
if (iSwOrHwReg == 0) { /* 选择软件启动方式 */
pdmaphychanCtl->uiMaskTigger = (__DMA_PHY_ON | __DMA_SW_TRIGGER);
/* 软件启动传输 */
} else {
pdmaphychanCtl->uiMaskTigger = __DMA_PHY_ON;
/* 进打开通道,等待硬件触发 */
}
}
return (0);
}
- 参数 LW_DMA_ADDR_INC 是内核宏定义 DMA 以地址增长方式工作。
- 参数 LW_DMA_ADDR_FIX 是内核宏定义 DMA 以地址不变工作。
- 参数 LW_DMA_ADDR_DEC 是内核宏定义 DMA 以地址减少方式工作。
- 参数 DMA_OPTION_SRCBUS_AHB 是宏定义源端 AHB 总线通道。
- 参数 DMA_OPTION_SRCBUS_APB 是宏定义源端 APB 总线通道。
- 参数 DMA_OPTION_DSTBUS_AHB 是宏定义目标 AHB 总线通道。
- 参数 DMA_OPTION_DSTBUS_APB 是宏定义目标 APB 总线通道。
- 参数 DMA_TRANSMODE_SINGLE 是宏定义单次传输。
- 参数 DMA_TRANSMODE_WHOLE 是宏定义完全传输。
- 参数 DMA_TRANSMODE_CLKAPB 是宏定义 APB 时钟源; 。
- 参数 DMA_TRANSMODE_CLKAHB 是宏定义 AHB 时钟源。
- 参数 DMA_TRANSMODE_NORMAL 是宏定义正常方式传输。
- 参数 DMA_TRANSMODE_BURST 是宏定义突发模式传输; 。
- 参数 DMA_TRANSMODE_ONESHOT 是宏定义传输完成后, 不进行重载; 。
- 参数 DMA_TRANSMODE_RELOAD 是宏定义传输完成后, 自动重载。
- 参数 DMA_TRANSMODE_BYTE 是宏定义字节传输; 。
- 参数 DMA_TRANSMODE_DBYTE 是宏定义半字传输。
- 参数 DMA_TRANSMODE_4BYTE 是宏定义字传输。
DMA 驱动注册
dmaGetFuncs 函数的主要工作是注册填充相关的回调函数供内核使用。具体函数的实现在上文中已经做了具体介绍。
完成上述函数的填充就可以完成一个完整的 DMA 驱动。
#include <SylixOS.h>
PLW_DMA_FUNCS dmaGetFuncs (int iChannel, ULONG *pulMaxBytes)
{
static LW_DMA_FUNCS pdmafuncsS3c2440a;
if (pdmafuncsS3c2440a.DMAF_pfuncReset == LW_NULL) {
pdmafuncsS3c2440a.DMAF_pfuncReset = __dmaHwReset;
pdmafuncsS3c2440a.DMAF_pfuncTrans = __dmaHwTransact;
pdmafuncsS3c2440a.DMAF_pfuncStatus = __dmaHwGetStatus;
}
if (pulMaxBytes) {
*pulMaxBytes = (1 * LW_CFG_MB_SIZE);
}
return (&pdmafuncsS3c2440a);
}