在嵌入式开发领域,SPI(Serial Peripheral Interface)总线因其全双工、高速、简单的硬件连接特性,成为传感器、存储芯片、显示模块等外设的优选通信方案。我在实际项目中遇到过这样的场景:当我们需要为一块定制开发板移植触摸屏驱动时,发现内核已有该型号IC的驱动源码,却始终无法正常加载。经过三天排查,最终发现问题出在设备树匹配环节。这个经历让我深刻意识到理解SPI子系统匹配机制的重要性。
SPI子系统作为Linux内核中结构清晰的框架,其设计遵循"分离思想"——将控制器驱动(controller driver)、协议驱动(protocol driver)和设备匹配机制解耦。这种架构带来的直接好处是:
由spi_controller结构体描述,包含硬件相关的传输函数集(如spi_transfer、spi_message等)。在RK3568平台的实际案例中,我们需要特别注意dma_alignment参数的设置,错误的配置会导致DMA传输失败。典型实现如下:
c复制static const struct spi_controller_mem_ops rk_spi_mem_ops = {
.exec_op = rk_spi_exec_op_mem,
.dma_map_mem_op_data = rk_spi_dma_map_mem_op_data,
};
static int rk_spi_probe(struct platform_device *pdev) {
ctlr->bus_num = pdev->id;
ctlr->mem_ops = &rk_spi_mem_ops;
ctlr->dma_alignment = 4; // 必须与硬件DMA对齐要求一致
}
提供spi_device和spi_driver两个核心结构体。其中spi_device代表具体的物理设备,包含时钟极性(CPOL)、时钟相位(CPHA)等关键参数。在调试某款气压传感器时,我曾因误设CPHA=1导致数据采样错位,正确的配置应参考设备手册的时序图:
code复制/* 典型SPI模式配置 */
spi->mode = SPI_MODE_0; // CPOL=0, CPHA=0
spi->max_speed_hz = 10 * 1000 * 1000; // 10MHz
包含具体设备驱动(如spidev、mtd等)。开发flash驱动时需要注意,不同容量的NOR Flash可能使用相同协议指令集,但需要单独实现size检测:
c复制static const struct spi_device_id flash_ids[] = {
{ "mx25l8005", .driver_data = (kernel_ulong_t)&mx25l8005_info },
{ "mx25l1606e", .driver_data = (kernel_ulong_t)&mx25l1606e_info },
{}
};
以STM32MP157开发板为例,设备树中SPI节点需要严格遵循以下结构:
dts复制&spi2 {
pinctrl-names = "default";
pinctrl-0 = <&spi2_pins_a>;
cs-gpios = <&gpioz 3 GPIO_ACTIVE_LOW>;
status = "okay";
touchscreen@0 {
compatible = "ti,ads7846";
reg = <0>; // CS0
spi-max-frequency = <2000000>;
interrupt-parent = <&gpiof>;
interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
};
};
匹配过程的关键路径:
踩坑记录:某次调试中发现驱动加载失败,原因是设备树中compatible字符串末尾多了空格,这种隐蔽错误需要特别关注。
对于不支持设备树的旧系统,需要注册spi_board_info结构体。在AM335x平台移植加速度计时,动态注册示例如下:
c复制static struct spi_board_info am335x_spi_devices[] = {
{
.modalias = "bma250",
.platform_data = &bma250_data,
.irq = gpio_to_irq(53),
.max_speed_hz = 5 * 1000 * 1000,
.bus_num = 1,
.chip_select = 0,
.mode = SPI_MODE_3,
},
};
x86平台常见ACPI匹配表配置:
c复制static const struct acpi_device_id acpi_spi_match[] = {
{ "INT33C2", (kernel_ulong_t)&intel_spi_data },
{ "INT33C3", (kernel_ulong_t)&intel_spi_data },
{},
};
MODULE_DEVICE_TABLE(acpi, acpi_spi_match);
c复制static const struct spi_controller_mem_ops my_qspi_ops = {
.adjust_op_size = my_adjust_op_size,
.exec_op = my_exec_op,
};
当单SPI总线挂载多个设备时,需要特别注意:
通过spidev接口实现用户层控制:
bash复制# 查看可用SPI设备
ls /dev/spidev*
典型ioctl操作示例:
c复制int fd = open("/dev/spidev1.0", O_RDWR);
ioctl(fd, SPI_IOC_WR_MODE, SPI_MODE_0);
ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, 8);
在工业控制场景中,需要:
c复制static int my_spi_probe(struct platform_device *pdev) {
ctlr->rt = 1; // 启用实时模式
ctlr->use_gpio_descriptors = true;
}
对于Flash设备,推荐使用spi-nor框架而非直接实现:
c复制static const struct flash_info my_flash_parts[] = {
{ "mx25l12805d", INFO(0xc22018, 0, 64*1024, 256) },
};
static const struct spi_device_id *spi_nor_match_id(const char *name) {
/* 厂商ID自动匹配逻辑 */
}
在开发阶段可以使用动态覆盖:
dts复制// overlay.dts
/dts-v1/;
/plugin/;
&spi1 {
new_device@1 {
compatible = "vendor,new-chip";
reg = <1>;
};
};
加载命令:
bash复制fdtoverlay -i main.dtb -o new.dtb overlay.dtbo
在多年SPI驱动开发中,最深刻的体会是:匹配机制看似简单,但细节决定成败。建议在开发初期就建立完整的检查清单,包括设备树节点验证、时钟配置检查、DMA对齐确认等关键项。当遇到问题时,从硬件信号层(用示波器测量CLK/MOSI)、内核框架层(dmesg日志)、驱动实现层(kprobe跟踪)三个维度进行系统性排查,往往能快速定位问题根源。