【Linux驱动开发】第22天:SPI 设备树 + spi_driver

📅 2026/7/4 4:11:50
【Linux驱动开发】第22天:SPI 设备树 + spi_driver
一、SPI设备树标准规范与片选机制SPI设备树结构与I2C高度同源采用控制器节点为父、从设备节点为子的层级结构核心差异在于reg属性的含义与片选配置逻辑。1. 整体层级结构SPI控制器节点SoC级dtsi预定义如SPI0 ├── 通用硬件属性reg基地址、中断、时钟、pinctrl ├── #address-cells 1 // 子节点reg占1个cell ├── #size-cells 0 // 子节点无地址空间 └── 从设备节点1板级dts添加如Flash0 ├── compatible与驱动匹配 ├── reg片选号0/1/2... ├── spi-max-frequency最大时钟 └── 时序模式属性spi-cpol/spi-cpha2. 控制器节点核心属性SoC级dtsi定义RK3568的SPI控制器统一在rk3568.dtsi中预定义板级DTS仅需启用并追加从设备。属性必选说明compatible是固定为rockchip,rk3568-spi与SPI控制器驱动匹配reg是控制器寄存器基地址地址空间大小interrupts是SPI控制器中断号clocks/clock-names是外设总线时钟与功能时钟名称为pclk、spi#address-cells是固定为1片选号用1个cell表示#size-cells是固定为0SPI从设备无独立地址空间cs-gpios否GPIO模拟片选时使用数组形式索引对应从设备reg值3. 从设备节点核心属性板级DTS添加属性必选说明compatible是与spi驱动的of_match_table匹配reg是片选通道号非设备地址对应控制器的第n个片选从0开始。这是与I2C最核心的区别spi-max-frequency是设备支持的最大SPI时钟频率单位Hz实际运行不会超过该值spi-cpol否布尔属性存在则CPOL1空闲时钟高电平默认CPOL0spi-cpha否布尔属性存在则CPHA1第二个边沿采样默认CPHA0spi-cs-high否布尔属性存在则片选高电平有效默认低电平有效spi-3wire否布尔属性使用3线模式MOSI/MISO复用一根线与I2C关键对比I2C的reg是7位从设备物理地址SPI的reg是片选通道索引仅用于选择哪个CS引脚。4. 两种片选实现方式方式1硬件原生片选推荐使用SPI控制器自带的CS引脚由控制器硬件自动控制片选电平时序精准、CPU开销小。RK3568每个SPI控制器支持2路硬件片选CS0、CS1。配置方式从设备reg填0/1pinctrl配置对应CS引脚无需额外属性。方式2GPIO模拟片选当硬件片选数量不足时使用普通GPIO引脚模拟片选由内核SPI核心层控制GPIO电平。配置方式控制器节点添加cs-gpios属性指定用哪些GPIO做片选从设备reg对应数组索引。示例spi0 { cs-gpios gpio0 RK_PA3 GPIO_ACTIVE_LOW, // 第0路片选GPIO0_A3 gpio0 RK_PB0 GPIO_ACTIVE_LOW; // 第1路片选GPIO0_B0 status okay; flash0 { reg 0; // 对应cs-gpios数组第0个GPIO // ...其他属性 }; };二、RK3568 SPI设备树通用模板前置说明RK3568共4路SPI控制器SPI0SPI3每路原生支持2个硬件片选默认时钟源为APB总线时钟。以下模板以SPI0为例可直接复用至SPI1SPI3。模板1硬件原生片选标准推荐包含pinctrl配置、控制器启用、W25Q系列Flash从设备可直接复制到板级DTS使用。#include dt-bindings/gpio/gpio.h #include dt-bindings/pinctrl/rockchip.h #include dt-bindings/interrupt-controller/irq.h pinctrl { spi0 { /omit-if-no-ref/ spi0_clk: spi0-clk { rockchip,pins 0 RK_PA0 RK_FUNC_1 pcfg_pull_none; }; spi0_mosi: spi0-mosi { rockchip,pins 0 RK_PA1 RK_FUNC_1 pcfg_pull_none; }; spi0_miso: spi0-miso { rockchip,pins 0 RK_PA2 RK_FUNC_1 pcfg_pull_up; }; spi0_cs0: spi0-cs0 { rockchip,pins 0 RK_PA3 RK_FUNC_1 pcfg_pull_none; }; }; }; spi0 { status okay; pinctrl-names default; pinctrl-0 spi0_clk spi0_mosi spi0_miso spi0_cs0; /* W25Q128 SPI Flash 示例 */ flash0 { compatible winbond,w25q128, jedec,spi-nor; reg 0; // 硬件片选0 spi-max-frequency 80000000; // 最大时钟80MHz spi-cpol; // CPOL1 spi-cpha; // CPHA1 → SPI模式3 status okay; }; /* 可追加第二个从设备使用CS1 sensor1 { compatible vendor,xxx-sensor; reg 1; spi-max-frequency 10000000; status okay; }; */ };模板2GPIO模拟片选当原生CS引脚被占用时使用GPIO模拟片选的配置模板spi0 { status okay; pinctrl-names default; pinctrl-0 spi0_clk spi0_mosi spi0_miso; // 不配置硬件CS引脚 // GPIO模拟片选2路片选分别用GPIO0_A3、GPIO0_B0 cs-gpios gpio0 RK_PA3 GPIO_ACTIVE_LOW, gpio0 RK_PB0 GPIO_ACTIVE_LOW; oled0 { compatible solomon,ssd1306; reg 0; // 对应第0路GPIO片选 spi-max-frequency 10000000; // 10MHz // 模式0无spi-cpol、spi-cpha dc-gpios gpio0 RK_PB1 GPIO_ACTIVE_HIGH; reset-gpios gpio0 RK_PB2 GPIO_ACTIVE_LOW; status okay; }; };三、spi_driver最简可运行驱动代码对应上述W25Q Flash设备实现最简spi_driver框架设备树匹配、probe初始化、SPI读写测试、remove清理。1. 完整驱动代码spi_demo.c#includelinux/module.h#includelinux/spi/spi.h#includelinux/of.h/* 设备树匹配表 */staticconststructof_device_idspi_demo_of_match[]{{.compatiblewinbond,w25q128},{/* 哨兵 */}};MODULE_DEVICE_TABLE(of,spi_demo_of_match);/* 读取Flash JEDEC ID的测试函数 */staticintspi_demo_read_id(structspi_device*spi){intret;u8 tx_buf[4]{0x9f,0,0,0};// 0x9f是JEDEC ID读取指令u8 rx_buf[4]{0};/* 最简API先写后读内部自动封装spi_message */retspi_write_then_read(spi,tx_buf,1,rx_buf,3);if(ret0){dev_err(spi-dev,read JEDEC ID failed, ret%d\n,ret);returnret;}dev_info(spi-dev,JEDEC ID: 0x%02x 0x%02x 0x%02x\n,rx_buf[0],rx_buf[1],rx_buf[2]);return0;}/* 设备匹配成功后执行 */staticintspi_demo_probe(structspi_device*spi){intret;dev_info(spi-dev,spi demo probe, max_freq%dHz, mode0x%x\n,spi-max_speed_hz,spi-mode);/* 可选重新配置SPI参数模式、位宽等一般设备树已配置 */spi-modeSPI_MODE_3;spi-bits_per_word8;retspi_setup(spi);if(ret0){dev_err(spi-dev,spi setup failed\n);returnret;}/* 测试读取Flash ID */spi_demo_read_id(spi);return0;}/* 驱动卸载时执行 */staticvoidspi_demo_remove(structspi_device*spi){dev_info(spi-dev,spi demo remove\n);}/* SPI驱动结构体 */staticstructspi_driverspi_demo_driver{.driver{.namespi-demo,.ownerTHIS_MODULE,.of_match_tablespi_demo_of_match,},.probespi_demo_probe,.removespi_demo_remove,};/* 模块注册与注销 */module_spi_driver(spi_demo_driver);MODULE_LICENSE(GPL);MODULE_AUTHOR(Demo);MODULE_DESCRIPTION(RK3568 SPI minimal driver demo);2. 配套MakefileKERNELDIR ? /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) obj-m spi_demo.o all: make -C $(KERNELDIR) M$(PWD) modules clean: make -C $(KERNELDIR) M$(PWD) clean3. 代码核心说明驱动匹配通过of_match_table的compatible与设备树节点匹配匹配成功后执行probe。struct spi_device对应一个SPI从设备包含片选号、模式、时钟、所属控制器等全部信息对应I2C的i2c_client。spi_setup配置SPI控制器的工作参数模式、位宽、时钟参数变更后必须调用。spi_write_then_read最常用的封装API自动构造spi_transfer和spi_message完成一次先写后读事务对应I2C的组合读写。module_spi_driver一键注册/注销SPI驱动的宏对应I2C的module_i2c_driver。四、核心传输API与验证方法1. 常用API分级API类型函数适用场景便捷封装spi_write_then_read()先写指令/地址、再读数据90%外设场景适用便捷封装spi_write()/spi_read()纯写/纯读单段数据标准接口spi_sync()自定义多段传输需手动构造spi_message spi_transfer异步接口spi_async()非阻塞传输通过回调通知完成2. 验证步骤编译设备树与驱动模块烧录到开发板加载驱动insmod spi_demo.ko查看内核日志dmesg | grep spi正常应打印probe信息与JEDEC ID查看SPI总线设备ls /sys/bus/spi/devices/应出现对应从设备节点