第4章 设备树基础:从dtsi到中断绑定
设备树,说白了就是Linux内核用来描述硬件的一门"方言"。你在R-Car上做驱动移植,绕不开它。我刚开始接触设备树时也觉得这东西有点玄乎,后来调了几个板子才明白——它就是个硬件配置清单,告诉内核"你有哪些外设、它们怎么连、地址在哪"。
这一章我们聚焦四个核心点:dtsi/dts文件结构、节点与属性、中断与GPIO绑定、reg和ranges的用法。掌握了这些,你在R-Car上改设备树就不会抓瞎。
4.1 dtsi与dts:继承与覆盖
R-Car的BSP里,设备树文件分两类:.dtsi是"头文件",.dts是"板级文件"。你可以把.dtsi理解成芯片原厂提供的通用配置,.dts则是你针对具体板子做的定制。
关键区别
- dtsi:描述SoC内部模块,比如SCIF、I2C、MMC控制器。由瑞萨提供,一般不改。
- dts:描述板级外设,比如PHY地址、GPIO按键、LED。由板卡厂商维护。
举个例子,R-Car H3的通用文件叫r8a7795.dtsi,里面定义了所有内置外设的节点。你的板子文件my-board.dts通过#include把它拉进来,然后覆盖或追加节点。
// r8a7795.dtsi(原厂提供)
scif0: serial@e6e60000 {
compatible = "renesas,scif-r8a7795", "renesas,scif";
reg = <0 0xe6e60000 0 0x40>;
interrupts = <GIC_SPI 152 IRQ_TYPE_LEVEL_HIGH>;
status = "disabled";
};
// my-board.dts(板级文件)
#include "r8a7795.dtsi"
&scif0 {
status = "okay";
clocks = <&cpg CPG_MOD 310>;
clock-names = "fck";
};
看到没?my-board.dts里用&scif0引用原节点,只改status和clocks。这种做法我特别喜欢——原厂升级dtsi时,你的板级改动不受影响。
我的习惯:在R-Car上做移植,我通常把板级特有的GPIO、中断、电源管理相关属性全放在dts里,dtsi只动SoC级别的定义。这样BSP升级时冲突最少。
4.2 节点与属性:设备树的"零件"
设备树的每个节点代表一个硬件设备。节点名一般用名称@地址的格式,比如i2c@e6500000。属性就是键值对,描述设备的特征。
常用的属性类型有这些:
| 属性类型 | 写法示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "renesas,rcar-gen3-i2c"; |
驱动匹配的关键 |
| 32位整数 | #address-cells = <1>; |
地址宽度描述 |
| 整数数组 | reg = <0 0xe6500000 0 0x1000>; |
地址+长度对 |
| 布尔值 | status = "disabled"; |
存在即true |
| phandle | clocks = <&cpg CPG_MOD 310>; |
引用另一个节点 |
这里有个坑——compatible的优先级。内核匹配驱动时,会从左到右尝试。我在R-Car上遇到过一个问题:写"renesas,scif-r8a7795"和"renesas,scif"的顺序反了,结果驱动加载了通用版本,没走优化路径。嗯,顺序很重要。
4.3 中断与GPIO绑定
中断绑定是设备树里最容易出错的环节。R-Car使用GIC(通用中断控制器),中断描述格式是固定的。
interrupts = <GIC_SPI 152 IRQ_TYPE_LEVEL_HIGH>;
三个字段的含义:
- GIC_SPI:共享外设中断,值为0。还有GIC_PPI(私有外设中断,值为1)。
- 152:中断号,这是硬件线号,不是Linux的IRQ号。
- IRQ_TYPE_LEVEL_HIGH:触发类型,高电平触发。
我曾经踩过的坑:在R-Car M3上移植一个触摸屏驱动,中断号写成了Linux的IRQ号(比如152+32=184),结果中断死活不触发。后来查手册才发现,设备树里必须用硬件中断号,内核会自动映射。记住:设备树里写的是GIC侧的编号,不是CPU侧的。
GPIO绑定相对简单,但要注意gpio-hog的用法:
gpio-keys {
compatible = "gpio-keys";
power-key {
gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
linux,code = <KEY_POWER>;
label = "Power Key";
};
};
这里&gpio6 11表示GPIO bank 6的第11脚。R-Car的GPIO bank编号从0开始,每个bank有32个引脚。我建议你在调试时先确认bank号和引脚号是否对应原理图。
4.4 reg与ranges:地址映射的"翻译官"
reg描述设备的物理地址范围,ranges描述父总线到子总线的地址映射。这两个属性在R-Car的PCIe、Display Controller等复杂外设中特别重要。
先看reg的格式:
reg = <address1 length1 [address2 length2]...>;
地址和长度的宽度由#address-cells和#size-cells决定。R-Car gen3通常用<0 0x...>这种64位地址写法:
#address-cells = <2>;
#size-cells = <2>;
reg = <0 0xe6500000 0 0x1000>;
// 地址:0x0e6500000,长度:0x1000
ranges用于桥接不同地址空间。比如PCIe控制器:
pciec0: pcie@fe000000 {
compatible = "renesas,pcie-r8a7795";
reg = <0 0xfe000000 0 0x80000>;
ranges = <0x01000000 0 0x00000000 0 0xfe100000 0 0x00100000
0x02000000 0 0x60000000 0 0x60000000 0 0x20000000>;
// 子地址(PCI) -> 父地址(CPU)
};
这里ranges的每一行包含:子地址类型、子地址、父地址、映射长度。第一行是IO空间,第二行是32位非预取内存空间。
我的调试技巧:当设备树里的reg和实际硬件对不上时,用devmem2直接读物理地址验证。比如devmem2 0xe6500000,如果读到全0或全F,说明地址错了或者外设没使能。
4.5 知识结构总览
下面这张图把本章的核心逻辑串起来了。你看一遍,心里就有谱了。
4.6 实战要点总结
最后,我把R-Car设备树移植的几个关键点列出来,你写代码时可以对照检查:
- compatible:优先使用SoC精确匹配,比如
"renesas,scif-r8a7795",再写通用版本。 - reg:R-Car gen3用2个cell表示地址,别写成1个cell。长度也要用2个cell。
- interrupts:中断号是GIC硬件号,不是Linux IRQ号。触发类型用宏定义,别写数字。
- status:原厂dtsi里外设默认
disabled,板级文件里改成okay。别忘了这一步。 - pinctrl:R-Car的引脚复用通过pinctrl节点配置,记得在设备节点里加
pinctrl-0和pinctrl-names。
特别提醒:R-Car的某些外设(比如HS-USB、PCIe)在dtsi里可能定义了多个reg区域。你移植时一定要对照硬件手册,确认每个reg段对应哪个功能模块。我曾经因为漏了一个reg段,导致DMA传输失败,查了两天才找到原因。
设备树这东西,说白了就是"写对了就通,写错了就黑"。你只要把dtsi/dts的关系理清,reg和interrupts的格式搞对,R-Car上的驱动移植就成功了一半。剩下的,就是调试和验证了。