4、SPI驱动框架:Linux SPI子系统架构

Linux SPI 子系统采用主机-设备-驱动三层分离模型,将 SPI 控制器的硬件差异、设备连接拓扑与协议逻辑解耦。理解这一架构是编写可移植、高性能 SPI 驱动的基石,也是后续将 DMA 与内存映射技术融入 SPI 传输的前提。

4.1 三层核心对象:spi_master / spi_device / spi_driver

SPI 子系统的核心数据结构定义在 include/linux/spi/spi.h 中,三者通过内核设备模型(driver model)串联:

对象 数据结构 职责 生命周期
spi_master struct spi_master 代表 SPI 控制器硬件,管理 FIFO、时钟、片选、DMA 通道 由平台/ACPI/设备树触发,probe 时分配,remove 时释放
spi_device struct spi_device 代表挂接在 SPI 总线上的从设备,包含模式、频率、字长等参数 由设备树节点或 board info 注册时创建,与 spi_master 绑定
spi_driver struct spi_driver 实现从设备的具体协议逻辑(如 ADC 采集、传感器读取) 模块加载时注册,卸载时注销
架构要点:spi_master 负责物理传输,spi_device 描述拓扑与配置,spi_driver 处理协议语义。三者通过总线类型 spi_bus_type 完成匹配与绑定。

4.2 设备树绑定:从硬件拓扑到内核对象

在 ARM/ARM64/RISC-V 等嵌入式平台,设备树(Device Tree)是描述 SPI 拓扑的标准方式。内核通过 of_spi_register_master() 解析设备树子节点,自动创建 spi_device 实例。

4.2.1 典型设备树节点结构

/* SPI 控制器节点 */
spi@ff110000 {
    compatible = "vendor,spi-controller";
    reg = <0x0 0xff110000 0x0 0x1000>;
    #address-cells = <1>;
    #size-cells = <0>;
    interrupts = <0 56 4>;
    clocks = <&clk SPI_CLK>;

    /* 从设备子节点 */
    adc@0 {
        compatible = "vendor,adc-device";
        reg = <0>;                 /* 片选编号 0 */
        spi-max-frequency = <10000000>;  /* 10 MHz */
        spi-cpol;                  /* 时钟极性 = 1 */
        spi-cpha;                  /* 时钟相位 = 1 */
        spi-cs-high;               /* 片选高有效 */
    };

    sensor@1 {
        compatible = "vendor,temperature-sensor";
        reg = <1>;                 /* 片选编号 1 */
        spi-max-frequency = <5000000>;
    };
};

4.2.2 设备树属性与 spi_device 字段映射

设备树属性 spi_device 字段 说明
reg chip_select 片选编号,对应 spi_master 的 cs_gpio 数组索引
spi-max-frequency max_speed_hz 从设备支持的最大 SPI 时钟频率
spi-cpol mode 置位 SPI_CPOL 时钟极性:空闲时时钟电平
spi-cpha mode 置位 SPI_CPHA 时钟相位:数据采样沿
spi-cs-high mode 置位 SPI_CS_HIGH 片选信号高电平有效
spi-3wire mode 置位 SPI_3WIRE 三线制模式(MISO/MOSI 合并)
实战建议:设备树中 reg 的值必须与 SPI 控制器硬件片选线一一对应。若使用 GPIO 模拟片选,需在控制器节点中定义 cs-gpios 属性。

4.3 probe/remove 回调:驱动与设备的握手时刻

当 spi_driver 与 spi_device 通过 compatible 匹配成功后,内核调用 probe 回调;设备移除或驱动卸载时调用 remove。这两个回调是驱动开发者最常接触的入口。

4.3.1 probe 回调的标准流程

  1. 获取设备私有数据:通过 devm_kzalloc() 分配驱动私有结构体。
  2. 读取设备树配置:使用 device_property_read_* 系列 API 获取频率、模式、GPIO 等参数。
  3. 初始化硬件:配置 SPI 控制器时钟、DMA 通道、中断。
  4. 注册内核框架:如 IIO、Input、HWMON 等子系统。
  5. 设置 SPI 传输参数:调用 spi_setup() 使配置生效。
static int adc_probe(struct spi_device *spi)
{
    struct adc_priv *priv;
    int ret;

    /* 1. 分配私有数据 */
    priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;
    spi_set_drvdata(spi, priv);

    /* 2. 读取设备树属性 */
    device_property_read_u32(&spi->dev, "vref-mv", &priv->vref_mv);

    /* 3. 配置 SPI 参数 */
    spi->mode = SPI_MODE_0;
    spi->max_speed_hz = 10 * 1000 * 1000;  /* 10 MHz */
    ret = spi_setup(spi);
    if (ret)
        return dev_err_probe(&spi->dev, ret, "spi_setup failed\n");

    /* 4. 注册 IIO 设备 */
    priv->indio_dev = devm_iio_device_alloc(&spi->dev, 0);
    if (!priv->indio_dev)
        return -ENOMEM;
    /* ... 配置 iio_info、channels ... */

    return devm_iio_device_register(&spi->dev, priv->indio_dev);
}

static void adc_remove(struct spi_device *spi)
{
    struct adc_priv *priv = spi_get_drvdata(spi);

    /* 清理硬件状态:停止 DMA、关闭时钟、复位 FIFO */
    adc_hardware_reset(priv);
    dev_info(&spi->dev, "ADC device removed\n");
}

4.3.2 probe 失败常见原因

错误场景 典型错误码 排查方向
设备树 compatible 不匹配 -ENODEV 检查 dts 与驱动中 of_match_table 是否一致
spi_setup() 参数无效 -EINVAL 确认 mode 位合法、频率不超过控制器上限
GPIO 片选申请失败 -EBUSY 检查 cs-gpios 引脚是否被其他驱动占用
DMA 通道获取失败 -EPROBE_DEFER DMA 控制器驱动未就绪,内核会自动重试 probe
注意:probe 中若使用 devm_* 系列资源管理 API,通常不需要在 remove 中手动释放内存,但硬件状态(如 DMA 描述符、中断屏蔽)必须显式清理。

4.4 SPI 子系统架构总览

下图展示了从设备树解析到驱动 probe 完成的完整数据流与对象关系:

SPI 子系统架构:设备树 → 内核对象 → 驱动绑定 设备树(Device Tree) spi@ff110000 → adc@0 { compatible = "vendor,adc"; reg = <0>; spi-max-frequency = <10000000>; } sensor@1 { compatible = "vendor,temp"; reg = <1>; spi-max-frequency = <5000000>; } SPI 核心层(spi.c / spi-mem.c) of_spi_register_master() 解析子节点 → 创建 spi_device spi_bus_type 匹配 spi_driver → 调用 probe spi_master • 控制器硬件抽象 • FIFO / DMA / 时钟 • 片选管理 spi_device • 从设备描述 • mode / max_speed_hz • chip_select spi_driver • probe / remove • 协议逻辑实现 • 与 IIO/Input 交互 驱动实例:adc_probe() → spi_setup() → 注册 IIO 设备 私有数据分配 | 设备树属性读取 | 硬件初始化 | 子系统注册 说明:设备树节点通过 SPI 核心层解析为 spi_device,再与 spi_driver 匹配触发 probe,最终生成驱动实例。

4.5 关键数据结构关系

以下代码片段展示了 spi_master 与 spi_device 之间的核心关联:

/* spi_master 关键字段(精简) */
struct spi_master {
    struct device   dev;             /* 内核设备结构 */
    s16             bus_num;         /* 总线编号 */
    u16             num_chipselect;  /* 片选数量 */
    u16             mode_bits;       /* 支持的 SPI 模式位掩码 */
    u32             min_speed_hz;
    u32             max_speed_hz;
    int             (*transfer_one)(struct spi_master *master,
                                    struct spi_device *spi,
                                    struct spi_transfer *transfer);
    void            (*set_cs)(struct spi_device *spi, bool enable);
    /* DMA 相关 */
    struct dma_chan *dma_tx;
    struct dma_chan *dma_rx;
};

/* spi_device 关键字段 */
struct spi_device {
    struct device       dev;             /* 内核设备结构 */
    struct spi_master   *master;         /* 所属控制器 */
    u32                 max_speed_hz;    /* 最大时钟频率 */
    u8                  chip_select;     /* 片选编号 */
    u8                  mode;            /* SPI_MODE_0 ~ SPI_MODE_3 */
    u8                  bits_per_word;   /* 每字位数,通常 8 */
    int                 irq;             /* 从设备中断号(可选) */
    void                *controller_data; /* 控制器私有数据 */
};
性能关注点:transfer_one 回调是 SPI 传输性能的核心路径。在后续章节中,我们将通过 DMA 传输和内存映射技术重写该回调,消除 CPU 逐字节搬运的瓶颈。

4.6 本章小结

  • 三层架构:spi_master 管理硬件,spi_device 描述拓扑,spi_driver 实现协议,三者通过内核设备模型协作。
  • 设备树绑定:通过 compatible 匹配、reg 指定片选、spi-max-frequency 等属性完成硬件描述,内核自动创建 spi_device。
  • probe/remove 流程:probe 中完成资源分配、参数配置、子系统注册;remove 中清理硬件状态。
  • 架构图:从设备树到驱动实例的完整数据流,为后续 DMA 和内存映射优化提供上下文。