4、设备树绑定文档:如何阅读内核文档与属性定义
设备树绑定文档,说白了就是内核给设备树写的一份「说明书」。你写设备树节点时,哪些属性必须写、哪些可选、值是什么格式,全看它。我刚开始接触设备树时,总觉得这玩意儿可有可无,直接抄别的板子改改就行。结果有一次,一个GPIO按键死活不触发中断,查了两天才发现——我漏了一个 interrupt-names 属性。
嗯,从那以后,我再也不敢跳过绑定文档了。
4.1 绑定文档在哪?长什么样?
所有官方绑定文档都在内核源码树的这个目录下:
Documentation/devicetree/bindings/
里面按子系统分了子目录。比如:
gpio/— GPIO控制器和按键i2c/— I2C控制器和外设pinctrl/— 引脚复用配置clock/— 时钟控制器regulator/— 电源管理sound/— 音频设备
每个绑定文档通常是一个 .yaml 文件(新格式)或 .txt 文件(老格式)。我个人更推荐看 .yaml 格式的,结构清晰,字段定义一目了然。
Documentation/devicetree/bindings/ 目录下搜索。用 grep -r 配合设备型号或驱动名,比翻网页快得多。
4.2 标准属性:每个节点都可能用到
设备树规范里定义了一批通用属性,不管什么设备都可能用到。我把最常用的几个列出来:
| 属性名 | 作用 | 格式 | 是否必须 |
|---|---|---|---|
compatible |
匹配驱动,告诉内核「我是谁」 | 字符串,优先用厂商前缀+设备名 | 必须 |
reg |
寄存器地址/大小,或总线地址 | <address length> 数组 |
通常必须 |
interrupts |
中断号、触发类型 | <irq_num flags> 数组 |
有中断则必须 |
clocks |
引用时钟源 | phandle + 索引 | 有时必须 |
status |
启用/禁用设备 | "okay" 或 "disabled" |
可选 |
pinctrl-* |
引脚复用配置 | phandle 引用 | 有引脚则推荐 |
举个例子,一个典型的I2C外设节点:
&i2c1 {
status = "okay";
temperature_sensor: tmp102@48 {
compatible = "ti,tmp102";
reg = <0x48>;
interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
};
};
这里 compatible 告诉内核用 ti,tmp102 驱动;reg 是I2C地址;interrupts 指定了中断引脚和触发方式。你想想看,如果没有绑定文档,你怎么知道 reg 是7位地址还是10位地址?
4.3 厂商自定义属性:别乱造,有规矩
标准属性不够用时,芯片厂商会定义自己的属性。这些属性必须以厂商前缀开头,比如:
fsl,— 飞思卡尔/恩智浦ti,— 德州仪器allwinner,— 全志rockchip,— 瑞芯微brcm,— 博通
我在项目中遇到过最典型的例子,是全志的 allwinner,pin-function 属性。它用来配置引脚复用功能,标准属性里没有直接对应的,所以厂商自己定义了一个。
my-company,super-feature 就往上写,内核驱动根本不认。
看一个瑞芯微的例子:
vcc_sd: regulator@0 {
compatible = "regulator-fixed";
regulator-name = "vcc_sd";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
gpio = <&gpio0 RK_PA5 GPIO_ACTIVE_HIGH>;
enable-active-high;
regulator-boot-on;
};
这里的 enable-active-high 和 regulator-boot-on 就是标准属性,定义在 regulator 子系统的绑定文档里。而 gpio 属性虽然看起来像自定义,其实也是标准用法——它引用了一个GPIO phandle。
4.4 怎么快速读懂一份绑定文档?
我个人的阅读习惯是三步走:
- 看 compatible 列表 — 文档开头会列出所有支持的设备型号,确认你的芯片在不在里面。
- 看 required 属性 — 哪些属性是必须写的,少一个驱动就加载不了。
- 看例子 — 文档末尾通常有一个完整示例节点,直接抄过来改。
举个例子,打开 Documentation/devicetree/bindings/gpio/gpio.txt,你会看到:
Required properties:
- compatible: "ti,omap4-gpio" or "ti,omap3-gpio"
- reg: 物理地址和长度
- interrupts: 中断号
- gpio-controller: 空属性,标记为GPIO控制器
- #gpio-cells: 2 // 每个GPIO引用需要两个cell
Optional properties:
- ti,gpio-always-on: 保持GPIO模块始终供电
看到 #gpio-cells = <2> 你就知道,引用这个控制器的GPIO时,要写两个值:GPIO号和标志位。
4.5 知识体系:绑定文档的核心逻辑
下面这张图,是我自己总结的绑定文档阅读框架。你照着这个思路走,基本不会迷路。
4.6 避坑指南:我踩过的几个坑
最后分享几个实战中容易翻车的地方:
- compatible 写反了顺序 — 内核匹配驱动时,会按compatible列表从前到后尝试。最具体的型号要写在最前面。比如
"ti,tmp102", "tmp102",而不是反过来。 - reg 的地址长度单位搞错 — 有些控制器用32位地址,有些用64位。绑定文档里会写
#address-cells和#size-cells,一定要看仔细。 - interrupts 的触发类型不匹配 — 我曾经把
IRQ_TYPE_EDGE_RISING写成了IRQ_TYPE_LEVEL_HIGH,结果中断触发一次就卡死了。查绑定文档才发现,这个设备只支持边沿触发。 - 忘了加
status = "okay"— 很多SoC默认把外设节点设为disabled,你如果不显式启用,驱动根本不会加载。