第10章:设备树常用属性详解
设备树里的属性,说白了就是硬件和驱动之间的「沟通语言」。我刚开始接触设备树时,总觉得这些属性名看着眼熟,但真要自己写起来,又拿不准该用哪个、怎么配。今天咱们就把最常用的几个属性掰开揉碎,一个一个讲清楚。
10.1 compatible:你是谁家的孩子?
compatible 属性,是驱动匹配硬件的「身份证」。内核通过它来找到对应的驱动程序。
// 标准写法
compatible = "vendor,device";
// 实际例子
compatible = "ti,am335x-uart", "ns16550";
这里有个细节:越具体的字符串越靠前。内核匹配时,会从左到右依次尝试。我在项目中遇到过,有人把通用型号写在前面,结果驱动加载了通用驱动,专用功能没使上。嗯,这个顺序很重要。
匹配规则:
- 先匹配厂商+设备名(如 "ti,am335x-uart")
- 匹配不上再试通用名(如 "ns16550")
- 都匹配不上,驱动就不加载
10.2 reg:你的地盘在哪里?
reg 属性描述设备的地址空间。说白了,就是告诉驱动:「你的寄存器从哪个地址开始,占多大地方」。
// 格式:reg = <地址 长度>;
reg = <0x48020000 0x1000>;
这里 0x48020000 是基地址,0x1000 是地址范围。我建议你写 reg 时,长度一定要比实际需要的多留一点。为什么?因为有些芯片的寄存器会跨页,长度写少了,驱动访问后面的寄存器就会报错。我曾经因为这个查了整整两天。
| 场景 | reg 写法 | 说明 |
|---|---|---|
| 单地址段 | reg = <0x1000 0x200>; | 一个设备只有一个地址段 |
| 多地址段 | reg = <0x1000 0x200>, <0x2000 0x100>; | 比如控制寄存器+数据缓冲区 |
| 带片选 | reg = <0 0x1000 0x200>; | 第一个数字是片选号 |
10.3 interrupts:有情况就喊我
interrupts 属性定义设备的中断号。你想想看,设备总不能一直轮询吧?有中断了,让 CPU 来处理,这才是正经做法。
// 格式:interrupts = <中断号 触发方式>;
interrupts = <29 2>;
触发方式常用的有:
- 0:上升沿触发
- 1:下降沿触发
- 2:高电平触发
- 3:低电平触发
- 4:边沿触发(上升和下降都触发)
我的经验:电平触发容易丢中断,边沿触发容易重复触发。我个人习惯用边沿触发,然后在驱动里做防抖处理。当然,具体用哪种,得看硬件设计。
10.4 clocks:给设备上发条
clocks 属性描述设备需要的时钟源。现在的 SoC 越来越复杂,一个设备可能有好几个时钟输入。
clocks = <&clkc 16>, <&clkc 17>;
clock-names = "uart_clk", "baud_clk";
clock-names 是给每个时钟起个名字,驱动里通过名字来获取时钟。我建议你一定要写 clock-names。为什么?因为驱动里用名字找时钟,比用索引号靠谱多了。索引号一变,驱动就崩了。
注意:时钟频率不对,设备可能不工作,也可能工作不稳定。我曾经遇到过,UART 波特率配好了,但时钟源频率不对,结果收发数据全是乱码。查了半天才发现是时钟树配错了。
10.5 gpios:管脚怎么用?
gpios 属性定义 GPIO 的用法。比如复位脚、使能脚、中断脚等等。
// 格式:gpios = <&gpioX 管脚号 标志>;
reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
enable-gpios = <&gpio2 3 GPIO_ACTIVE_HIGH>;
标志位常用:
- GPIO_ACTIVE_HIGH:高电平有效
- GPIO_ACTIVE_LOW:低电平有效
- GPIO_OPEN_DRAIN:开漏输出
- GPIO_OPEN_SOURCE:开源输出
避坑指南:我曾经在项目里,把复位脚的标志写反了。本来应该是低电平复位,我写成了 GPIO_ACTIVE_HIGH。结果设备上电后一直处于复位状态,根本起不来。查了三天,最后发现是这里的问题。所以,GPIO 的有效电平一定要和硬件设计一致。
10.6 status:这个设备用不用?
status 属性控制设备是否启用。说白了,就是个开关。
status = "okay"; // 启用
status = "disabled"; // 禁用
常用的值:
- "okay":设备正常工作
- "disabled":设备被禁用
- "reserved":保留,不用于操作系统
- "fail":设备检测失败
我建议你在调试时,先用 "disabled" 把不相关的设备关掉,减少干扰。等核心功能调通了,再逐个打开。这样排查问题会快很多。
10.7 综合示例:一个完整的 I2C 设备节点
说了这么多,咱们来看个完整的例子。这是一个 I2C 温度传感器的设备树节点:
temp_sensor: tmp102@48 {
compatible = "ti,tmp102";
reg = <0x48>;
interrupt-parent = <&gpio1>;
interrupts = <7 IRQ_TYPE_EDGE_FALLING>;
status = "okay";
// 时钟
clocks = <&i2c_clk>;
clock-names = "i2c_clk";
// GPIO 控制
alert-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>;
};
你看,一个完整的设备节点,就是把上面这些属性组合起来。每个属性都有自己的作用,缺一不可。
我的习惯:写设备树时,我会先写 compatible 和 reg,这两个是必须的。然后根据硬件手册,把 interrupts、clocks、gpios 逐个加上。最后别忘了 status。这样一步步来,不容易漏掉。
好了,这章的内容就到这儿。下一章咱们聊聊设备树的调试技巧,到时候我会分享一些实战中遇到的坑和解决方法。