4、设备树绑定文档:如何阅读内核文档与属性定义

设备树绑定文档,说白了就是内核给设备树写的一份「说明书」。你写设备树节点时,哪些属性必须写、哪些可选、值是什么格式,全看它。我刚开始接触设备树时,总觉得这玩意儿可有可无,直接抄别的板子改改就行。结果有一次,一个GPIO按键死活不触发中断,查了两天才发现——我漏了一个 interrupt-names 属性。

嗯,从那以后,我再也不敢跳过绑定文档了。

4.1 绑定文档在哪?长什么样?

所有官方绑定文档都在内核源码树的这个目录下:

Documentation/devicetree/bindings/

里面按子系统分了子目录。比如:

  • gpio/ — GPIO控制器和按键
  • i2c/ — I2C控制器和外设
  • pinctrl/ — 引脚复用配置
  • clock/ — 时钟控制器
  • regulator/ — 电源管理
  • sound/ — 音频设备

每个绑定文档通常是一个 .yaml 文件(新格式)或 .txt 文件(老格式)。我个人更推荐看 .yaml 格式的,结构清晰,字段定义一目了然。

小技巧: 在Buildroot里,你可以直接解压内核源码包,然后进到 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 属性。它用来配置引脚复用功能,标准属性里没有直接对应的,所以厂商自己定义了一个。

注意: 自定义属性必须写在绑定文档里,并且经过内核社区review。你不能自己随便造一个 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 怎么快速读懂一份绑定文档?

我个人的阅读习惯是三步走:

  1. 看 compatible 列表 — 文档开头会列出所有支持的设备型号,确认你的芯片在不在里面。
  2. 看 required 属性 — 哪些属性是必须写的,少一个驱动就加载不了。
  3. 看例子 — 文档末尾通常有一个完整示例节点,直接抄过来改。

举个例子,打开 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 知识体系:绑定文档的核心逻辑

下面这张图,是我自己总结的绑定文档阅读框架。你照着这个思路走,基本不会迷路。

设备树绑定文档阅读框架 绑定文档 标准属性 compatible / reg interrupts / clocks status / pinctrl-* 厂商自定义属性 前缀:ti, fsl, rockchip 必须写在绑定文档中 需内核社区review 文档结构 compatible 列表 required / optional 示例节点 最终目标:写出驱动能正确解析的设备树节点 设备树 ↔ 绑定文档 ↔ 驱动,三者对齐

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,你如果不显式启用,驱动根本不会加载。
我的习惯: 每次写完设备树,我都会去绑定文档里逐条核对属性。特别是从别的板子移植过来的节点,compatible、reg、interrupts这三项最容易出错。花10分钟核对,能省下半天调试时间。