2. 内核SPI子系统架构:核心层、控制器驱动层、设备驱动层的关系与数据流

做SPI驱动调试,第一件事不是翻芯片手册,而是搞懂Linux内核的SPI子系统长什么样。我见过太多人一上来就对着寄存器猛写,结果数据死活出不来——其实就是没搞清楚数据是怎么从用户态流到硬件管脚的。

Linux SPI子系统分三层,说白了就是:核心层管规矩,控制器驱动层管硬件,设备驱动层管业务。这三层各司其职,数据流就像接力棒一样一层层传下去。

2.1 三层架构总览

先看一张整体结构图,我习惯把这三层想象成一个"数据管道":

Linux SPI 子系统三层架构与数据流 用户空间 (User Space) 设备驱动层 (SPI Device Driver) spidev / 传感器驱动 / 显示驱动 SPI 核心层 (SPI Core) spi.c / spi-master.c / 消息队列管理 控制器驱动层 (SPI Controller Driver) sunxi-spi.c / 寄存器操作 / DMA传输 硬件层 (SPI Controller + Device) 数据流方向:用户空间 → 设备驱动 → SPI Core → 控制器驱动 → 硬件

这张图我建议你存下来。每次调SPI驱动,脑子里过一遍这个流程,定位问题会快很多。

2.2 SPI核心层(SPI Core)

核心层在drivers/spi/spi.c里,它不碰任何硬件寄存器。它的工作就三件:

  • 管理总线:注册/注销SPI控制器,维护总线上的设备列表
  • 消息队列调度:把上层发来的spi_message排好队,按顺序发给控制器
  • 提供API:给设备驱动和控制器驱动提供标准接口

核心层最重要的数据结构是struct spi_controller(老内核叫spi_master)。我调全志T113时,经常要打印这个结构体里的字段来排查问题。

struct spi_controller {
    struct device   dev;
    struct list_head list;        // 挂在总线上的设备链表
    s16             bus_num;      // 总线编号
    u16             num_chipselect; // 片选数量
    u32             mode_bits;    // 支持的SPI模式位
    u32             bits_per_word_mask;
    u32             min_speed_hz;
    u32             max_speed_hz;
    int             (*transfer_one)(struct spi_controller *ctlr,
                                    struct spi_device *spi,
                                    struct spi_transfer *transfer);
    int             (*prepare_message)(struct spi_controller *ctlr,
                                       struct spi_message *msg);
    int             (*unprepare_message)(struct spi_controller *ctlr,
                                         struct spi_message *msg);
    /* ... 还有很多 */
};

关键点:核心层不关心你的SPI控制器是哪个厂家的,也不关心挂的是温度传感器还是LCD屏。它只负责搭桥。

2.3 控制器驱动层(SPI Controller Driver)

这一层才是真正跟硬件打交道的。全志T113的SPI控制器驱动在drivers/spi/spi-sunxi.c里(不同内核版本文件名可能略有差异)。

控制器驱动要做的事:

  1. 初始化硬件:配置时钟、引脚复用、中断、DMA通道
  2. 实现传输函数:transfer_one是核心,负责把spi_transfer里的数据发出去
  3. 中断处理:处理传输完成、FIFO空/满等中断
  4. DMA传输:大数据量时用DMA减轻CPU负担

我记得第一次调T113的SPI时,发现数据总是错位。折腾了两天,最后发现是set_cs回调没实现——片选信号时序不对。嗯,这种坑踩过一次就记住了。

static int sunxi_spi_transfer_one(struct spi_controller *ctlr,
                                  struct spi_device *spi,
                                  struct spi_transfer *tfr)
{
    struct sunxi_spi *sspi = spi_controller_get_devdata(ctlr);
    unsigned int mclk_rate, div;
    u32 reg;

    /* 设置时钟分频 */
    mclk_rate = clk_get_rate(sspi->mclk);
    div = DIV_ROUND_UP(mclk_rate, tfr->speed_hz);
    /* ... 配置寄存器 ... */

    /* 设置传输模式 */
    reg = sunxi_spi_read(sspi, SUNXI_SPI_CTL_REG);
    /* ... 根据spi->mode设置CPOL/CPHA ... */

    /* 启动传输 */
    sunxi_spi_write(sspi, SUNXI_SPI_TXD_REG, *(u8 *)tfr->tx_buf);
    /* ... */
}

注意:全志T113的SPI控制器有8级FIFO,每次传输前一定要清空FIFO。我曾经因为没清FIFO,导致上一次传输的残留数据混进了新数据里,排查了整整一个下午。

2.4 设备驱动层(SPI Device Driver)

设备驱动是离业务最近的一层。它通过核心层提供的API,跟具体的SPI设备通信。

典型的设备驱动要做这些事:

  • probe:创建设备节点、分配缓冲区、初始化设备
  • 构造spi_message:把要发送/接收的数据打包成spi_transfer链表
  • 发起同步/异步传输:调用spi_sync或spi_async
  • 解析数据:把收到的原始数据转换成业务含义

举个例子,挂一个SPI温度传感器:

static int temp_sensor_probe(struct spi_device *spi)
{
    struct temp_data *priv;
    u8 tx_buf[2] = {0x01, 0x00};  // 读寄存器命令
    u8 rx_buf[2] = {0};
    struct spi_transfer tfr = {
        .tx_buf = tx_buf,
        .rx_buf = rx_buf,
        .len = 2,
        .speed_hz = 1000000,
        .bits_per_word = 8,
    };
    struct spi_message msg;

    spi_message_init(&msg);
    spi_message_add_tail(&tfr, &msg);
    spi_sync(spi, &msg);  // 同步传输

    /* 解析rx_buf得到温度值 */
    /* ... */
}

经验之谈:调试设备驱动时,先用spidev用户态工具验证硬件通路。我在T113上都是先跑spidev_test,确认收发正常了,再写内核驱动。这样能快速区分是硬件问题还是驱动问题。

2.5 数据流全景

数据从用户态到硬件,到底经历了什么?我画了个流程图:

SPI数据流:从用户态到硬件 用户态应用 write/ioctl 设备驱动层 构造spi_message SPI Core 消息入队/调度 控制器驱动 transfer_one 硬件寄存器 FIFO/GPIO 同步 vs 异步 spi_sync:阻塞等待 spi_async:立即返回 中断回调通知完成 传输模式 PIO:小数据量 DMA:大数据量 全志T113支持两种

数据流的关键路径:

步骤 发生位置 关键操作 常见问题
1 用户态 通过设备文件发起读写 权限不足、设备节点未创建
2 设备驱动 构造spi_message,调用spi_sync 消息构造错误、缓冲区对齐问题
3 SPI Core 消息排队,调用控制器transfer_one 队列满、调度延迟
4 控制器驱动 配置寄存器,启动传输 时钟配置错、片选时序错
5 硬件 FIFO移位,SCLK产生 电气问题、信号干扰

2.6 调试时的切入点

搞清楚了架构,调试就有方向了。我总结了几条经验:

  • 数据没出来:先查控制器驱动层,用示波器看SCLK和MOSI有没有波形
  • 数据错位:查设备驱动的消息构造,特别是bits_per_word和len对不对
  • 速度上不去:查控制器驱动的时钟树配置,全志T113的SPI时钟源可以选
  • 中断风暴:查FIFO阈值设置,或者考虑切DMA模式

重要提醒:全志T113的SPI控制器在devicetree里配dmas属性时,一定要核对DMA通道号。我见过有人把SPI0的DMA通道配成了SPI1的,结果两个设备互相干扰,数据全乱套了。

嗯,三层架构和数据流就讲到这里。你想想看,搞懂了这些,后面调驱动的时候,遇到问题至少知道该去翻哪一层的代码,对吧?