4. SPI 设备驱动注册流程:从设备树到设备节点的完整调用链

SPI 设备驱动的注册并非简单的函数调用,而是一条从 设备树解析 到 内核驱动模型挂载 的完整链路。理解这条链路,是进行 SPI 驱动开发与调试的基础。本章将拆解 spi_register_driver、spi_alloc_master、spi_add_device 等核心 API 的调用关系与数据流。

核心要点: SPI 子系统采用“主机驱动 + 设备驱动”分离架构。主机控制器驱动负责注册 master,设备驱动负责匹配设备树节点并绑定。整个流程分为三个阶段:主机侧初始化、设备树解析与设备创建、驱动注册与匹配。

4.1 整体调用链全景图

下图展示了从 SPI 主机控制器驱动加载到设备驱动 probe 的完整调用链。注意数据流从设备树 (DTB) 开始,经过内核 OF 解析层,最终到达 SPI 核心层和设备驱动层。

SPI 设备驱动注册完整调用链 阶段一:主机控制器初始化 阶段二:设备树解析与设备创建 阶段三:驱动注册与匹配 platform_driver_register() spi_alloc_master() spi_master_set_devdata() spi_register_master() of_register_spi_devices() of_spi_parse_dt() 解析 reg / spi-max-frequency 解析 spi-cpha / spi-cpol spi_add_device() device_register() spi_register_driver() driver_register() spi_bus_type.match of_driver_match_device() spi_drv_probe() 说明:spi_add_device 在设备树节点解析完成后被调用,将 SPI 设备注册到内核设备模型。

4.2 主机控制器驱动初始化

SPI 主机控制器驱动通常以 platform driver 的形式存在。在 probe 函数中,驱动需要完成 master 的分配、初始化与注册。

4.2.1 spi_alloc_master:分配 master 结构体

spi_alloc_master 是 SPI 核心层提供的分配函数,用于分配 struct spi_master 及其关联的私有数据。

struct spi_master *spi_alloc_master(struct device *dev, unsigned int size)
{
    struct spi_master *master;

    // 分配 master 结构体 + 私有数据区(连续内存)
    master = kzalloc(sizeof(*master) + size, GFP_KERNEL);
    if (!master)
        return NULL;

    // 初始化设备结构体,关联父设备
    device_initialize(&master->dev);
    master->dev.class = &spi_master_class;
    master->dev.parent = dev;
    master->bus_num = -1;  // 由 SPI 核心动态分配或设备树指定

    // 初始化传输队列与锁
    spin_lock_init(&master->bus_lock_spinlock);
    mutex_init(&master->bus_lock_mutex);
    INIT_LIST_HEAD(&master->queue);
    INIT_LIST_HEAD(&master->list);

    return master;
}
EXPORT_SYMBOL_GPL(spi_alloc_master);
关键参数: size 参数用于为驱动私有数据预留空间。通过 spi_master_get_devdata(master) 获取该区域指针,避免额外分配。

4.2.2 spi_register_master:注册 master 到核心层

在完成 master 的初始化(设置传输函数、模式位、DMA 掩码等)后,调用 spi_register_master 将 master 注册到 SPI 核心。该函数内部会触发设备树解析。

int spi_register_master(struct spi_master *master)
{
    int status = -ENODEV;
    int dynamic = 0;

    // 1. 分配 bus_num(若未指定)
    if (!master->bus_num)
        status = spi_register_master_id(master);
    else
        dynamic = 0;

    // 2. 创建设备文件(sysfs)
    status = device_add(&master->dev);

    // 3. 解析设备树,创建子设备
    of_register_spi_devices(master);

    // 4. 处理 ACPI 或 board_info 方式注册的设备
    // ...

    return status;
}
EXPORT_SYMBOL_GPL(spi_register_master);

4.3 设备树节点解析与设备创建

设备树解析是 SPI 设备注册的核心环节。内核通过 of_register_spi_devices 遍历 master 节点下的所有子节点,为每个子节点创建一个 spi_device。

4.3.1 of_register_spi_devices 内部流程

static void of_register_spi_devices(struct spi_master *master)
{
    struct device_node *nc;

    // 遍历 master 设备树节点的所有子节点
    for_each_available_child_of_node(master->dev.of_node, nc) {
        struct spi_device *spi;

        // 解析设备树属性,填充 spi_board_info
        spi = of_register_spi_device(master, nc);
        if (IS_ERR(spi))
            dev_warn(&master->dev, "Failed to create SPI device for %pOF\n", nc);
    }
}

of_register_spi_device 内部调用 of_spi_parse_dt 解析节点属性,然后调用 spi_add_device 创建设备。

4.3.2 设备树属性解析对照表

设备树属性 对应 spi_device 字段 说明
reg chip_select 片选编号,决定使用哪条 CS 线
spi-max-frequency max_speed_hz 最大 SPI 时钟频率,单位 Hz
spi-cpha mode 中的 SPI_CPHA 时钟相位,数据采样沿
spi-cpol mode 中的 SPI_CPOL 时钟极性,空闲电平
spi-cs-high mode 中的 SPI_CS_HIGH 片选高电平有效
spi-3wire mode 中的 SPI_3WIRE 三线制模式(无 MISO/MOSI 区分)
spi-rx-delay-us rx_delay 接收数据后的延迟(微秒)
spi-tx-delay-us tx_delay 发送数据前的延迟(微秒)

4.3.3 spi_add_device:创建设备实例

spi_add_device 是 SPI 核心层将 spi_board_info 或设备树解析结果转换为 spi_device 实例的关键函数。

struct spi_device *spi_add_device(struct spi_master *master,
                                  struct spi_board_info *bi)
{
    struct spi_device *spi;
    int status;

    // 1. 分配 spi_device 结构体
    spi = spi_alloc_device(master);
    if (!spi)
        return ERR_PTR(-ENOMEM);

    // 2. 填充 spi_device 字段
    spi->chip_select = bi->chip_select;
    spi->max_speed_hz = bi->max_speed_hz;
    spi->mode = bi->mode;
    spi->irq = bi->irq;
    strlcpy(spi->modalias, bi->modalias, sizeof(spi->modalias));

    // 3. 设置设备名、父设备、总线类型
    dev_set_name(&spi->dev, "%s.%u", dev_name(&master->dev), bi->chip_select);
    spi->dev.parent = &master->dev;
    spi->dev.bus = &spi_bus_type;
    spi->dev.type = &spi_dev_type;

    // 4. 注册到内核设备模型
    status = device_register(&spi->dev);
    if (status < 0) {
        spi_dev_put(spi);
        return ERR_PTR(status);
    }

    return spi;
}
EXPORT_SYMBOL_GPL(spi_add_device);
注意事项: spi_add_device 调用 device_register 后,设备会立即出现在 sysfs 中。如果此时已有匹配的驱动,probe 函数会被同步调用。因此,确保在调用前 master 已完全初始化。

4.4 驱动注册与匹配机制

设备驱动侧通过 spi_register_driver 注册到 SPI 总线。匹配过程由 SPI 总线类型 spi_bus_type 的 .match 方法完成。

4.4.1 spi_register_driver 内部实现

int spi_register_driver(struct spi_driver *sdrv)
{
    // 设置驱动所属总线为 spi_bus_type
    sdrv->driver.bus = &spi_bus_type;

    // 如果驱动提供了 of_match_table,则关联
    if (sdrv->probe)
        sdrv->driver.probe = spi_drv_probe;
    if (sdrv->remove)
        sdrv->driver.remove = spi_drv_remove;

    // 注册到内核驱动模型
    return driver_register(&sdrv->driver);
}
EXPORT_SYMBOL_GPL(spi_register_driver);

4.4.2 匹配流程详解

SPI 总线的匹配策略优先使用 设备树 compatible 匹配,其次使用 ID table 匹配。

static int spi_match_device(struct device *dev, struct device_driver *drv)
{
    const struct spi_device *spi = to_spi_device(dev);
    const struct spi_driver *sdrv = to_spi_driver(drv);

    // 1. 设备树匹配(优先)
    if (of_driver_match_device(dev, drv))
        return 1;

    // 2. ID table 匹配(次优先)
    if (sdrv->id_table)
        return !!spi_match_id(sdrv->id_table, spi);

    // 3. 驱动名匹配(兜底)
    return strcmp(spi->modalias, drv->name) == 0;
}

4.4.3 匹配优先级对比

匹配方式 匹配依据 优先级 典型场景
设备树 compatible of_device_id.compatible 最高 设备树平台,兼容性最强
SPI ID table spi_device_id.name 中 非设备树平台或兼容旧驱动
驱动名 spi_driver.driver.name 低 简单设备或调试

4.5 完整调用链时序图

以下时序图展示了从主机驱动 probe 到设备驱动 probe 的完整调用顺序。

SPI 设备驱动注册时序图 主机驱动 SPI 核心 设备模型 设备驱动 spi_alloc_master() spi_register_master() of_register_spi_devices() spi_add_device() device_register() spi_register_driver() spi_bus_type.match spi_drv_probe() 关键时序说明: 1. 主机驱动 probe 时完成 master 注册和设备树解析,设备先于驱动出现在总线上。 2. 驱动注册后,内核自动触发匹配,匹配成功则调用驱动的 probe 函数。

4.6 常见问题与调试技巧

调试建议: 当 SPI 设备驱动 probe 不执行时,按以下步骤排查:
  1. 检查设备树节点是否在正确的 SPI 总线节点下(父子关系)。
  2. 检查 compatible 属性是否与驱动中的 of_match_table 完全一致(包括大小写)。
  3. 检查 reg 属性值是否超出 master 支持的片选数量。
  4. 查看 /sys/bus/spi/devices/ 目录下是否有对应设备出现。
  5. 启用 SPI 核心调试:echo 'file drivers/spi/spi.c +p' > /sys/kernel/debug/dynamic_debug/control

4.6.1 设备树节点示例

&spi0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_spi0>;

    spidev@0 {
        compatible = "rohm,dh2228fv";   // 与驱动 of_match_table 匹配
        reg = <0>;                       // 片选 0
        spi-max-frequency = <10000000>;  // 10 MHz
        spi-cpha;                        // 时钟相位模式 1
        spi-cpol;                        // 时钟极性模式 1
    };

    temperature@1 {
        compatible = "ti,ads7846";
        reg = <1>;                       // 片选 1
        spi-max-frequency = <2000000>;   // 2 MHz
        interrupt-parent = <&gpio1>;
        interrupts = <17 2>;
    };
};

4.6.2 驱动模板代码

static const struct of_device_id my_spi_of_match[] = {
    { .compatible = "rohm,dh2228fv" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_spi_of_match);

static const struct spi_device_id my_spi_id[] = {
    { "dh2228fv", 0 },
    { }
};
MODULE_DEVICE_TABLE(spi, my_spi_id);

static int my_spi_probe(struct spi_device *spi)
{
    dev_info(&spi->dev, "Probe: CS=%d, max_freq=%d Hz, mode=0x%x\n",
             spi->chip_select, spi->max_speed_hz, spi->mode);
    // 初始化设备...
    return 0;
}

static struct spi_driver my_spi_driver = {
    .driver = {
        .name = "my_spi_device",
        .of_match_table = my_spi_of_match,
    },
    .probe = my_spi_probe,
    .id_table = my_spi_id,
};

module_spi_driver(my_spi_driver);
最佳实践: 同时提供 of_match_table 和 id_table,确保驱动在设备树和非设备树平台上都能正常工作。module_spi_driver 宏封装了 spi_register_driver 和 spi_unregister_driver 的调用。

4.7 本章小结

SPI 设备驱动注册流程可归纳为三条主线:

  • 主机侧: spi_alloc_master → spi_register_master → of_register_spi_devices
  • 设备侧: of_spi_parse_dt → spi_add_device → device_register
  • 驱动侧: spi_register_driver → spi_bus_type.match → spi_drv_probe