3、SPI控制器驱动模型

SPI控制器驱动是VxWorks下SPI子系统与硬件SPI外设之间的桥梁。本章聚焦struct spi_ctlr核心结构体、控制器初始化标准流程以及注册/注销机制,帮助开发者建立从“硬件寄存器”到“内核对象”的完整映射。

3.1 SPI控制器结构体:struct spi_ctlr

VxWorks使用struct spi_ctlr描述一个SPI控制器实例,该结构体定义在spiLib.h中,包含硬件资源、总线参数、回调函数和DMA配置等字段。

字段类型说明
namechar *控制器名称,用于调试和绑定
busNumUINT32总线编号,系统内唯一
modeUINT32SPI模式(CPOL/CPHA组合)
maxSpeedHzUINT32最大SCK频率,单位Hz
bitsPerWordUINT8每字位数,通常8/16/32
chipSelectUINT32当前片选线编号
pLockSEM_ID互斥信号量,保护并发访问
pRegBasevolatile UINT8 *寄存器基地址(MMIO映射)
pDmaChanDMA_CHAN *关联DMA通道(可选)
transferSTATUS (*)(struct spi_ctlr *, struct spi_msg *)底层传输函数指针
configSTATUS (*)(struct spi_ctlr *, struct spi_config *)控制器配置回调
设计要点: transfer和config是驱动开发者必须实现的两个回调。前者负责实际的SPI数据收发,后者用于动态调整时钟、极性和相位。

3.2 控制器初始化流程

初始化一个SPI控制器驱动通常分为5个步骤,以下流程图展示了从硬件探测到内核注册的完整路径。

SPI控制器初始化流程 1. 硬件资源探测 2. 分配并清零spi_ctlr 3. 填充结构体字段 4. 创建同步信号量 5. 注册到SPI核心层 说明:步骤1-4在驱动入口函数中完成,步骤5调用spiCtlrRegister()将控制器加入全局链表

3.3 初始化代码示例

以下代码展示了典型的SPI控制器初始化实现,基于i.MX6 ECSPI外设进行适配:

STATUS spiEimxInit
    (
    struct spi_ctlr *pCtlr,
    UINT32 busNum,
    UINT32 regBase,
    UINT32 maxClk
    )
    {
    /* 1. 清零结构体 */
    bzero (pCtlr, sizeof (struct spi_ctlr));

    /* 2. 填充基础信息 */
    pCtlr->name        = "ecspi";
    pCtlr->busNum      = busNum;
    pCtlr->maxSpeedHz  = maxClk;
    pCtlr->bitsPerWord = 8;
    pCtlr->mode        = SPI_MODE_0;   /* CPOL=0, CPHA=0 */

    /* 3. 映射寄存器地址 */
    pCtlr->pRegBase = (volatile UINT8 *)regBase;

    /* 4. 创建互斥信号量 */
    pCtlr->pLock = semMCreate (SEM_Q_PRIORITY | SEM_INVERSION_SAFE);
    if (pCtlr->pLock == NULL)
        return (ERROR);

    /* 5. 绑定传输与配置回调 */
    pCtlr->transfer = spiEimxTransfer;
    pCtlr->config   = spiEimxConfig;

    /* 6. 硬件级复位 */
    REG_WRITE32 (pCtlr->pRegBase + ECSPI_CONREG, 0);

    return (OK);
    }
常见错误: 忘记调用semMCreate会导致并发传输时数据竞争;寄存器基地址必须使用volatile修饰,否则编译器优化可能跳过硬件访问。

3.4 注册与注销

VxWorks SPI核心层提供两个标准API用于管理控制器生命周期:

API原型说明
spiCtlrRegisterSTATUS spiCtlrRegister(struct spi_ctlr *)将控制器加入全局链表,使能总线访问
spiCtlrUnregisterSTATUS spiCtlrUnregister(struct spi_ctlr *)从全局链表移除,释放资源

3.5 注册与注销流程

下图展示了控制器注册和注销在内核中的完整路径:

SPI控制器注册与注销流程 spiCtlrRegister() 校验pCtlr非空 插入全局链表spiCtlrList spiCtlrUnregister() 检查是否有从设备占用 从链表移除并释放信号量 注意:注销前必须确保所有从设备已解绑,否则可能导致悬空指针或内核崩溃

3.6 注册与注销代码示例

/* 注册控制器 */
STATUS spiEimxRegister (struct spi_ctlr *pCtlr)
    {
    if (pCtlr == NULL)
        return (ERROR);

    /* 调用核心层注册函数 */
    if (spiCtlrRegister (pCtlr) != OK)
        {
        logMsg ("spiCtlrRegister failed for bus %d\n",
                pCtlr->busNum, 0, 0, 0, 0, 0);
        return (ERROR);
        }

    logMsg ("SPI controller bus %d registered\n",
            pCtlr->busNum, 0, 0, 0, 0, 0);
    return (OK);
    }

/* 注销控制器 */
STATUS spiEimxUnregister (struct spi_ctlr *pCtlr)
    {
    if (pCtlr == NULL)
        return (ERROR);

    /* 调用核心层注销函数 */
    if (spiCtlrUnregister (pCtlr) != OK)
        {
        logMsg ("spiCtlrUnregister failed for bus %d\n",
                pCtlr->busNum, 0, 0, 0, 0, 0);
        return (ERROR);
        }

    /* 释放驱动私有资源 */
    semDelete (pCtlr->pLock);
    bzero (pCtlr, sizeof (struct spi_ctlr));

    return (OK);
    }
最佳实践: 在系统启动脚本中按总线编号顺序注册控制器;注销时先调用spiCtlrUnregister再释放私有内存,避免内核访问已释放的结构体。

3.7 控制器生命周期总结

阶段关键操作注意事项
探测读取设备树/硬件寄存器获取基地址、中断号确认MMIO地址范围不与其他外设冲突
初始化分配结构体、填充字段、创建信号量信号量类型建议使用SEM_Q_PRIORITY
注册调用spiCtlrRegister加入全局链表注册后立即生效,从设备可发起通信
运行处理transfer回调,与从设备交互确保中断处理函数不阻塞信号量
注销调用spiCtlrUnregister,释放资源必须在所有从设备解绑后执行