sylixos下spi总线驱动框架

下面以imx6q处理器自带spi总线驱动为例,讲解sylixos下spi总线驱动框架。

1. 驱动入口位置

要为系统添加一个spi总线,可以在bsp工程中,直接集成到系统镜像中,也可以通过内核模块动态添加至系统。
如果是集成到系统镜像中,则代码应位于bspInit.c文件的halBusInit函数中开始调用。如果是通过内核模块实现,则自然是在module_init函数中调用。

2. 初始化 spi 组件库

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);
    }
}

3.创建各个通道的spi总线

初始化 spi 组件库后,就可以创建各个通道的spi总线了,这里调用的是spiBusCreate函数,其有一个参数可以指定是要创建哪个通道的总线。spiBusCreate函数是在driver/ecspi/ecspi.c文件中实现的。如果SOC中只有一个通道的spi,该函数也可以设计为无参数的。

4.创建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总线对象子类的父类。

5.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传输。它有三个参数:

  • PLW_SPI_ADAPTER pSpiAdapter :SPI总线适配器指针,所有的spi总线设备信息都从这里获取。
  • PLW_SPI_MESSAGE pspimsg :SPI设备需要传输的消息结构体首地址指针,描述spi传输信息,是一个结构体数组的首地址,至于数组有多少个成员,也就是有多少个spi传输帧由iNum 给出。
  • INT iNum :为传输的消息个数。
  • 执行失败时返回错误号(为负值),执行成功返回实际完成的消息个数。

基于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);
}

6.定义spi总线对象子类

往往一款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总线的实现步骤只包含以下几步:

  1. 调用API_SpiLibInit 函数,初始化 spi 组件库。
  2. 调用API_SpiAdapterCreate函数,创建spi总线适配器,需要传入总线名称和LW_SPI_FUNCS 结构体对象。
  3. 需要提前实例化LW_SPI_FUNCS 结构体对象,其中SPIFUNC_pfuncMasterXfer 函数必须实现。
  4. SPIFUNC_pfuncMasterXfer 函数的基本框架如下,其中TODO部分是单次spi传输需要实现的寄存器操作。注意参数pSpiAdapter->SPIADAPTER_pspifunc指向的地址就是调用API_SpiAdapterCreate数传入的pspifunc,所以可以将其强转为实际的子类对象。

你可能感兴趣的:(#,SylixOS中的SPI模块)