下面以imx6q处理器自带spi总线驱动为例,讲解sylixos下spi总线驱动框架。
要为系统添加一个spi总线,可以在bsp工程中,直接集成到系统镜像中,也可以通过内核模块动态添加至系统。
如果是集成到系统镜像中,则代码应位于bspInit.c文件的halBusInit函数中开始调用。如果是通过内核模块实现,则自然是在module_init函数中调用。
spi总线驱动需要调用的第一个函数应当是系统api函数 API_SpiLibInit,该函数初始化 spi 组件库,为spi总线驱动和spi设备驱动做支持。
static VOID halBusInit (VOID)
{
/*
* SPI 总线初始化
*/
API_SpiLibInit(); /* 初始化 spi 组件库 */
spiBusCreate(0);
spiBusCreate(1);
}
查看API_SpiLibInit函数源码,我们可以看到,里面只是创建了一个二值信号量,这个信号量会保护每次spi总线的调用不被重入。
/*********************************************************************************************************
** 函数名称: API_SpiLibInit
** 功能描述: 初始化 spi 组件库
** 输 入 : NONE
** 输 出 : ERROR CODE
*********************************************************************************************************/
LW_API
INT API_SpiLibInit (VOID)
{
if (_G_hSpiListLock == LW_OBJECT_HANDLE_INVALID) {
_G_hSpiListLock = API_SemaphoreBCreate("spi_listlock",
LW_TRUE, LW_OPTION_WAIT_FIFO | LW_OPTION_OBJECT_GLOBAL,
LW_NULL);
}
if (_G_hSpiListLock) {
return (ERROR_NONE);
} else {
return (PX_ERROR);
}
}
初始化 spi 组件库后,就可以创建各个通道的spi总线了,这里调用的是spiBusCreate函数,其有一个参数可以指定是要创建哪个通道的总线。spiBusCreate函数是在driver/ecspi/ecspi.c文件中实现的。如果SOC中只有一个通道的spi,该函数也可以设计为无参数的。
接下来看spiBusCreate函数的具体实现。
/*********************************************************************************************************
** 函数名称: spiBusCreate
** 功能描述: 创建 spi 总线并获取驱动程序
** 输 入: iCh 通道号
** 输 出: 错误码
*********************************************************************************************************/
INT spiBusCreate (INT iCh)
{
CHAR spiBusName[64];
LW_SPI_FUNCS *hSpiFunc;
static SPI_CHANNEL_ST spiChannels[ECSPI_CHAN_COUNT] = {
[0] = {
.funcs = {
.SPIFUNC_pfuncMasterXfer = spiMasterXfer,
.SPIFUNC_pfuncMasterCtl = spiMasterCtl,
},
.iCh = 0,
.uPhyBase = ECSPI1_BASE_ADDR,
.uVmmBase = ECSPI1_BASE_ADDR,
.uVector = IMX_INT_ECSPI1,
.uBaudRate = CFG_DEFAULT_BAUDRATE,
.uOption = LW_SPI_M_CPHA_0 | LW_SPI_M_CPOL_1,
},
[1] = {
.funcs = {
.SPIFUNC_pfuncMasterXfer = spiMasterXfer,
.SPIFUNC_pfuncMasterCtl = spiMasterCtl,
},
.iCh = 1,
.uPhyBase = ECSPI2_BASE_ADDR,
.uVmmBase = ECSPI2_BASE_ADDR,
.uVector = IMX_INT_ECSPI2,
.uBaudRate = CFG_DEFAULT_BAUDRATE,
.uOption = LW_SPI_M_CPHA_0 | LW_SPI_M_CPOL_1,
},
[2] = {
.funcs = {
.SPIFUNC_pfuncMasterXfer = spiMasterXfer,
.SPIFUNC_pfuncMasterCtl = spiMasterCtl,
},
.iCh = 2,
.uPhyBase = ECSPI3_BASE_ADDR,
.uVmmBase = ECSPI3_BASE_ADDR,
.uVector = IMX_INT_ECSPI3,
.uBaudRate = CFG_DEFAULT_BAUDRATE,
.uOption = LW_SPI_M_CPHA_0 | LW_SPI_M_CPOL_1,
},
[3] = {
.funcs = {
.SPIFUNC_pfuncMasterXfer = spiMasterXfer,
.SPIFUNC_pfuncMasterCtl = spiMasterCtl,
},
.iCh = 3,
.uPhyBase = ECSPI4_BASE_ADDR,
.uVmmBase = ECSPI4_BASE_ADDR,
.uVector = IMX_INT_ECSPI4,
.uBaudRate = CFG_DEFAULT_BAUDRATE,
.uOption = LW_SPI_M_CPHA_0 | LW_SPI_M_CPOL_1,
},
[4] = {
.funcs = {
.SPIFUNC_pfuncMasterXfer = spiMasterXfer,
.SPIFUNC_pfuncMasterCtl = spiMasterCtl,
},
.iCh = 4,
.uPhyBase = ECSPI5_BASE_ADDR,
.uVmmBase = ECSPI5_BASE_ADDR,
.uVector = IMX_INT_ECSPI5,
.uBaudRate = CFG_DEFAULT_BAUDRATE,
.uOption = LW_SPI_M_CPHA_0 | LW_SPI_M_CPOL_1,
},
};
if (iCh >= ECSPI_CHAN_COUNT) {
return (PX_ERROR);
}
spiInit(&spiChannels[iCh]);
hSpiFunc = (LW_SPI_FUNCS *)&spiChannels[iCh];
sprintf(spiBusName, "/bus/spi/%d", iCh);
return (API_SpiAdapterCreate(spiBusName, hSpiFunc));
}
spiBusCreate函数中定义了spi总线子类实体集合,对相应通道进行了初始化操作,最后调用系统接口创建spi总线。
创建spi总线的真正接口其实是系统api函数API_SpiAdapterCreate,它需要两个参数:
CPCHAR pcName
:spi总线名称,即shell命令buss显示的名称,也是spi设备驱动创建时需要制定的总线名称。PLW_SPI_FUNCS pspifunc
:spi操作函数组,是SPI总线底层操作的函数集合,也是spi总线对象子类的父类。sylixos的spi驱动框架定义了SPI总线底层操作的函数集合,也就是PLW_SPI_FUNCS 结构体,该结构体定义如下:
/*********************************************************************************************************
SPI 总线传输函数集
*********************************************************************************************************/
typedef struct lw_spi_funcs {
INT (*SPIFUNC_pfuncMasterXfer)(PLW_SPI_ADAPTER pspiadapter,
PLW_SPI_MESSAGE pspimsg,
INT iNum);
/* 适配器数据传输 */
INT (*SPIFUNC_pfuncMasterCtl)(PLW_SPI_ADAPTER pspiadapter,
INT iCmd,
LONG lArg);
/* 适配器控制 */
} LW_SPI_FUNCS;
typedef LW_SPI_FUNCS *PLW_SPI_FUNCS;
sylixos驱动层对spi总线api接口的调用,最终都会间接调用到PLW_SPI_FUNCS 里面注册的函数。
SPIFUNC_pfuncMasterXfer回调函数是必须要实现的,它负责控制硬件来完成spi传输。它有三个参数:
基于SPIFUNC_pfuncMasterXfer的功能和输入输出参数,该函数的大体结构也是固定的,如下:
/*********************************************************************************************************
** 函数名称: spiMasterXfer
** 功能描述: spi 传输函数
** 输 入: pSpiAdapter spi 适配器
** pSpiMsg spi 传输消息
** iNum 消息数量
** 输 出: 完成传输的消息数量
*********************************************************************************************************/
static INT spiMasterXfer (PLW_SPI_ADAPTER pSpiAdapter, PLW_SPI_MESSAGE pSpiMsg, INT iNum)
{
INT i;
SPI_CHANNEL_ST *pSpi = (SPI_CHANNEL_ST *)pSpiAdapter->SPIADAPTER_pspifunc;
for (i = 0; i < iNum; i++) {
//TODO 此处是进行spi传输的实际寄存器操作
if (pSpiMsg->SPIMSG_pfuncComplete) { /* 传输完成上层回调函数 */
pSpiMsg->SPIMSG_pfuncComplete(pSpiMsg->SPIMSG_pvContext);
}
pSpiMsg++;
}
return (iNum);
}
SPIFUNC_pfuncMasterCtl回调函数是用于操作一些spi属性的,可以不实现,注册为空函数或空指针。
调用格式和一般的ioctrl函数相似,操作对象是SPI总线适配器。系统已经定义了设置波特率的命令(LW_SPI_CTL_BAUDRATE),驱动开发者根据具体情况也可以定义自己需要要的命令。
/*********************************************************************************************************
** 函数名称: spiMasterCtl
** 功能描述: spi 控制函数
** 输 入: pSpiAdapter spi 适配器
** iCmd spi 命令
** lArg spi 参数
** 输 出: 错误号
*********************************************************************************************************/
static INT spiMasterCtl (PLW_SPI_ADAPTER pSpiAdapter, INT iCmd, LONG lArg)
{
SPI_CHANNEL_ST *pSpi = (SPI_CHANNEL_ST *)pSpiAdapter->SPIADAPTER_pspifunc;
switch (iCmd) {
case LW_SPI_CTL_BAUDRATE://设置波特率
pSpi->uBaudRate = lArg;
spiConfig(pSpi);
break;
default:
break;
}
return (ERROR_NONE);
}
往往一款SOC芯片中的spi总线控制器会有相同的多个,他们的寄存器和硬件特性是完全一样的,只有寄存器基址,向量号,使用的管脚等不一样,具体应用时波特率和总线模式等也可能不一样,但他们肯定都使用同一个驱动。相同的驱动程序在服务不同的通道操作时就需要基于不同的spi总线对象了。
定义spi总线对象其实就是把驱动中需要记录的,各通道间差异化的数据或方法集中到一个结构体的,具体都要有哪些成员是要看具体的硬件特性和驱动实现方式的。需要注意的是,第一项必须是LW_ SPI_ FUNCS 结构体对象,即要以LW_SPI_FUNCS作为具体spi总线对象的父类。
因为在spiBusCreate函数在注册spi总线时,要把spi总线对象作为父类(LW_SPI_FUNCS)传入驱动框架,后面SPIFUNC_pfuncMasterXfer 函数被调用时传入的LW_SPI_FUNCS对象其实就是注册时转入的。所以定义spi总线对象要修饰为静态变量,SPIFUNC_pfuncMasterXfer 和SPIFUNC_pfuncMasterCtl 函数中也可以将LW_SPI_FUNCS强转为具体的spi总线对象来使用。
本驱动中定义的spi总线对象如下:
/*********************************************************************************************************
spi 通道结构定义
*********************************************************************************************************/
typedef struct {
LW_SPI_FUNCS funcs; /* 必须为第一个 */
INT iCh; /* 通道号 */
UINT uPhyBase; /* 物理基地址 */
UINT uVmmBase; /* 虚拟基地址 */
UINT uVector; /* 中断向量号 */
UINT uClock; /* 模块时钟频率 */
UINT uBaudRate; /* 目标波特率 */
UINT uOption; /* 硬件选项 */
UINT8 *pTxBuf; /* 当前发送缓存 */
UINT8 *pRxBuf; /* 当前接收缓存 */
UINT uiLength; /* 收发字节长度 */
UINT uiIndex; /* 当前收发字节索引 */
spinlock_t slLock; /* 自旋锁 */
LW_OBJECT_HANDLE hSemBTx; /* 发送完成同步信号量 */
} SPI_CHANNEL_ST;
对于spi如何进行初始化,如何进行传输操作,是否使用中断这都是spi硬件特性和操作方式的问题,每个应用情景都不一样,这里就不再祥说了。
抛去所有可能不需要的操作,spi总线的实现步骤只包含以下几步: