首先先看SPI的整体应用-内核-硬件的框架:

在硬件层我们可以看到存在SPI控制器和SPI外设,而我们一般操作的是外设,所以写的驱动也是外设的驱动,而SPI控制器连接的是SPI适配器的驱动层,这部分驱动是由芯片原厂工程师进行编写的。
我们本篇文章描述的是设备树的写法,我们知道设备树描述的是**硬件设备与CPU无法自动发现的硬件连接关系,**由图可知,对于SPI来说,或者说对于我们要编写一个SPI外设来说,具有两个硬件设备,一个是SPI控制器,一个是SPI外设,一般情况下SPI控制器的设备树是原厂工程师写好了的,所以我们一般只需要自己写SPI外设的设备树即可。
**GPIO:**GPIO也是如此,分为GPIO控制器和GPIO"外设"(即使用GPIO的设备,如led),我们需要编写的也是GPIO"外设"的设备树。、
接下里我们对比两种外设的写法:
GPIO控制器及其GPIO使用者设备树
GPIO控制器:
bash
gpio1: gpio@0209c000 {
compatible = "fsl,imx6ul-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
可知,这个节点可以作为GPIO控制器,也可以作为中断控制器。
GPIO"外设":
bash
led {
compatible = "mycompany,myled";
led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
};
SPI控制器及其外设设备树
SPI控制器:

spi控制器节点一般在芯片级.dtsi文件中。
SPI外设:
bash
&spi0 {
status = "okay"; // 启用 SPI0 控制器
pinctrl-names = "default";
pinctrl-0 = <&spi0m1_pins>; // 确保引脚复用配置正确
/* 你的外设子节点 */
my_sensor@0 {
compatible = "vendor,my-sensor-model"; // 必须与驱动中的 of_match_table 一致
reg = <0>; // 使用 CS0 (片选0)
spi-max-frequency = <10000000>; // 最大时钟频率 10MHz
/* 可选属性 */
spi-cpol; // 如果外设需要 CPOL=1
spi-cpha; // 如果外设需要 CPHA=1
/* 中断引脚配置(如果外设带中断) */
interrupt-parent = <&gpio1>;
interrupts = <RK_PA1 IRQ_TYPE_EDGE_FALLING>;
};
};
写法分析
我们可以发现 GPIO 和 SPI 都有"控制器"和"外设",但它们在设备树里的写法不同,根本原因是:
SPI 是总线,外设是挂在 SPI 控制器下面的"子设备";GPIO 不是总线,外设只是"使用了某几根 GPIO 线"。
设备树既可以用父子节点 表示总线拓扑,也可以用 phandle 引用表示资源连接关系。Linux 设备树文档也说明,DT 本质是描述硬件的数据结构,除了树形父子关系外,还允许节点之间建立任意引用关系;常见 binding 会描述 data bus、GPIO connection、interrupt line、peripheral device 等硬件特征。
SPI 是典型总线结构:
bash
SPI 控制器
├── CS0 外设
├── CS1 外设
└── CS2 外设
而GPIO不是父子结构,GPIO 更像是一组"可借用的引脚资源"。
| 对比 | SPI | GPIO |
|---|---|---|
| 本质 | 总线 | 引脚资源 |
| 外设和控制器关系 | 外设挂在 SPI 控制器下面 | 外设使用 GPIO 控制器的一根线 |
| 设备树表示 | 父子节点 | phandle 引用 |
| 外设身份 | spi_device |
通常是 platform device / input device / led device 等 |
| 地址含义 | reg = <CS号> |
<&gpiox pin flags> |
| 内核解析 | SPI core 扫描子节点 | GPIO consumer API 解析 xxx-gpios |