4. 设备树基础与SPI节点
设备树(Device Tree)这东西,刚接触时觉得挺玄乎。说白了,它就是一套描述硬件信息的“配置文件”。内核通过它才知道你板子上接了哪些外设、地址是多少、中断挂在哪条线上。
我刚开始做移植那会儿,还遇到过直接改板级文件(board-*.c)的老代码。后来设备树统一了江湖,所有平台都得按这套规矩来。全志T113也不例外,它的SPI控制器信息全写在dts里。
4.1 设备树语法速览
设备树的语法其实不复杂。一个节点就是一个设备,节点里放属性和值。看个最简单的例子:
/dts-v1/;
/ {
model = "Allwinner T113 S3 Board";
compatible = "allwinner,sun8i-t113";
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0x0>;
};
};
这里有几个关键点:
- /dts-v1/ — 版本声明,必须写在最前面
- / — 根节点,整棵树从这里开始
- model — 板子名称,字符串
- compatible — 兼容性标识,内核靠它匹配驱动
- reg — 地址信息,通常是基地址+长度
节点可以嵌套,可以引用。你想想看,一个SoC里有几十个外设,每个外设一个节点,树状结构管理起来确实清晰。
4.2 T113设备树文件结构
全志T113的设备树文件分三层:
| 层级 | 文件名 | 作用 |
|---|---|---|
| SoC级 | sun8i-t113.dtsi | 定义所有内置控制器,SPI、I2C、UART都在这里 |
| 板级 | sun8i-t113-mangopi.dts | 继承SoC级,使能具体外设、配置引脚 |
| 头文件 | sun8i-t113.h / sun8i-t113.dtsi | 宏定义、时钟索引、中断号 |
实际编译时,dtc会把.dts和.dtsi合并成一个完整的设备树。我在项目中遇到过有人直接在板级文件里重写SoC级节点,结果覆盖了关键配置,SPI死活不工作。嗯,这里要注意:板级文件只做“使能”和“引脚配置”,不要动SoC级的基础地址和中断。
4.3 SPI控制器节点详解
全志T113有4个SPI控制器。在sun8i-t113.dtsi里,SPI节点的定义长这样:
spi0: spi@04025000 {
compatible = "allwinner,sun8i-t113-spi";
reg = <0x04025000 0x1000>;
interrupts = <GIC_SPI 50 IRQ_TYPE_LEVEL_HIGH>;
clocks = <>;
clock-names = "ahb", "mod";
resets = <>;
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
};
我来拆开讲几个关键属性:
- compatible — 驱动匹配的关键字。内核里
sun8i-t113-spi这个驱动就是靠它绑定的 - reg — 基地址0x04025000,长度0x1000(4KB)。这就是SPI控制器的寄存器空间
- interrupts — 中断号50,触发类型高电平。GIC_SPI表示这是共享外设中断
- clocks — 两个时钟:AHB总线时钟和模块时钟。缺一个SPI都转不起来
- status — 默认disabled,板级文件里改成okay才能用
4.4 reg属性与interrupts属性
这两个属性是设备树里最常用的,我单独拿出来说。
reg属性描述设备的地址空间。格式是:
reg = <address1 length1 address2 length2 ...>;
对于SPI控制器,通常只有一段地址空间。但有些设备有多个地址段,比如DMA控制器有描述符地址和配置地址。每个地址段由#address-cells和#size-cells决定宽度。T113里这两个值分别是1和1,所以每个地址段占两个32位数。
interrupts属性描述中断信息。T113使用GIC(通用中断控制器),格式是:
interrupts = <GIC_SPI 50 IRQ_TYPE_LEVEL_HIGH>;
三个字段的含义:
| 字段 | 值 | 说明 |
|---|---|---|
| 中断类型 | GIC_SPI (0) | 共享外设中断,多个CPU核都能响应 |
| 中断号 | 50 | SPI0的中断号,硬件手册里查到的 |
| 触发方式 | IRQ_TYPE_LEVEL_HIGH | 高电平触发。SPI通常用电平触发,边沿触发容易丢中断 |
你想想看,如果中断号写错了,驱动注册中断时就会失败。我见过有人把SPI0的中断号写成SPI1的,结果两个设备抢同一个中断线,系统跑起来就死机。
4.5 在设备树中使能SPI节点
SoC级dtsi里SPI节点默认是disabled的。要在板级dts里使能它,需要做两件事:
- 引用节点 — 用
&spi0引用SoC级定义的节点 - 覆盖属性 — 把status改成okay,配置引脚和片选
实际代码长这样:
&spi0 {
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins>;
status = "okay";
spidev@0 {
compatible = "rohm,dh2228fv";
reg = <0>;
spi-max-frequency = <50000000>;
};
};
这里有几个细节:
- pinctrl — 引脚复用配置,告诉SoC这些引脚要当SPI用,不是GPIO
- spidev@0 — 子节点,代表挂在SPI总线上的设备。@0表示片选CS0
- spi-max-frequency — 最大时钟频率,50MHz是T113 SPI控制器能跑的上限
我习惯在使能SPI节点后,先编译dts生成dtb,再用dtc -I dtb -O dts反编译看看最终结果。这样能确认所有&spi0引用都正确展开了,避免编译时没报错但实际节点没生效的情况。
这张图把T113 SPI设备树的层级关系画清楚了。从上到下依次是根节点、soc节点、spi0节点,再到属性和板级覆盖。你写dts时脑子里要有这个结构,知道每个属性该放在哪一层。