4. SPI 设备驱动注册流程:从设备树到设备节点的完整调用链
SPI 设备驱动的注册并非简单的函数调用,而是一条从 设备树解析 到 内核驱动模型挂载 的完整链路。理解这条链路,是进行 SPI 驱动开发与调试的基础。本章将拆解 spi_register_driver、spi_alloc_master、spi_add_device 等核心 API 的调用关系与数据流。
4.1 整体调用链全景图
下图展示了从 SPI 主机控制器驱动加载到设备驱动 probe 的完整调用链。注意数据流从设备树 (DTB) 开始,经过内核 OF 解析层,最终到达 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 的完整调用顺序。
4.6 常见问题与调试技巧
- 检查设备树节点是否在正确的 SPI 总线节点下(父子关系)。
- 检查
compatible属性是否与驱动中的of_match_table完全一致(包括大小写)。 - 检查
reg属性值是否超出 master 支持的片选数量。 - 查看
/sys/bus/spi/devices/目录下是否有对应设备出现。 - 启用 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