4、设备树绑定文档:如何阅读内核Documentation/devicetree/bindings、常见外设绑定规范。

设备树绑定文档,说白了就是芯片厂商写给驱动开发者的“说明书”。

我刚入行那会儿,总觉得设备树就是一堆看不懂的属性和数字。直到有一次调一个I2C触摸屏,死活唤不醒,折腾了两天。后来一个老同事过来看了一眼,说:“你查过绑定文档吗?”我这才去翻 Documentation/devicetree/bindings 目录。结果发现,有个 wakeup-source 属性我没配。加上去,问题秒解。

从那以后,我养成了一个习惯:写驱动之前,先读绑定文档。今天我就带你系统地过一遍,怎么高效地读这些文档,以及常见外设的绑定规范长什么样。

4.1 绑定文档的目录结构

内核源码树下的 Documentation/devicetree/bindings/ 目录,是按外设类型分门别类整理的。你想想看,一个成熟的嵌入式Linux系统,可能涉及几十种外设,如果没有清晰的目录结构,找起来会非常痛苦。

我习惯先看顶层目录,快速定位目标外设的类型。下面这张表列出了最常用的几个子目录:

子目录 包含的外设类型 典型文件举例
arm/ ARM架构相关的SoC和板级绑定 arm,vexpress.yaml
clock/ 时钟控制器、PLL、时钟源 clk-imx.yaml
gpio/ GPIO控制器 gpio-controller.yaml
i2c/ I2C控制器及I2C设备 i2c-imx.yaml
pinctrl/ 引脚复用和引脚配置 pinctrl-bindings.txt
regulator/ 电压调节器 regulator.yaml
serial/ UART串口控制器 fsl-imx-uart.yaml
spi/ SPI控制器及SPI设备 spi-controller.yaml
display/ 显示接口、LCD控制器、HDMI panel-simple.yaml
sound/ 音频编解码器、DAI链路 simple-audio-card.yaml
我的习惯: 拿到一块新板子,我会先 grep -r "compatible" arch/arm/boot/dts/ 找到所有用到的 compatible 字符串,然后去 bindings 目录下反向查找对应的文档。这样比漫无目的地翻目录快得多。

4.2 如何阅读一个绑定文档

现在的绑定文档,大部分已经迁移到了 YAML 格式(以 .yaml 结尾)。相比以前的纯文本格式,YAML 更结构化,也更容易被工具校验。但不管什么格式,核心内容都围绕这几个问题:

  • 这个设备是什么? —— 通过 compatible 属性来匹配驱动。
  • 它需要哪些资源? —— 寄存器地址、中断号、时钟、DMA通道等。
  • 它有哪些可选功能? —— 比如是否支持唤醒、是否使能某个特性。
  • 子节点怎么配置? —— 对于I2C/SPI总线控制器,子节点代表挂载的具体设备。

我拿一个实际的 I2C 控制器绑定文档片段来举例。假设我们看 i2c-imx.yaml:

# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/i2c/i2c-imx.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#

title: Freescale i.MX I2C Controller

maintainers:
  - Oleksij Rempel <o.rempel@pengutronix.de>

properties:
  compatible:
    oneOf:
      - items:
          - enum:
              - fsl,imx1-i2c
              - fsl,imx21-i2c
              - fsl,imx6q-i2c
      - items:
          - const: fsl,imx6ul-i2c
          - const: fsl,imx6q-i2c

  reg:
    maxItems: 1

  interrupts:
    maxItems: 1

  clocks:
    maxItems: 1

  clock-frequency:
    description: I2C bus clock frequency in Hz
    default: 100000
    enum: [100000, 400000]

required:
  - compatible
  - reg
  - interrupts
  - clocks

你看,这个文档清晰地告诉我们:

  • compatible 可以写 fsl,imx6ul-i2c,后面再跟一个 fsl,imx6q-i2c 作为 fallback。
  • reg 和 interrupts 是必填项,各一个。
  • clock-frequency 是可选的,默认 100KHz,可以配成 400KHz。
我曾经踩过的坑: 有一次我配 SPI 设备,照着另一个平台的设备树抄了 spi-max-frequency,结果 SPI 控制器死活不工作。后来查绑定文档才发现,那个控制器要求频率必须写在子节点的 spi-max-frequency 属性里,而不是控制器节点里。文档里写的是 maxItems: 1,但我没注意看描述。所以,一定要看属性的描述和约束条件,不要想当然。

4.3 常见外设绑定规范速览

下面我挑几个最常用的外设,把它们的绑定要点列出来。这些是我在实际项目中反复用到的。

4.3.1 GPIO 控制器

GPIO 控制器的绑定,核心是定义 gpio-controller 和 #gpio-cells。

gpio0: gpio@2090000 {
    compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
    reg = <0x0209c000 0x4000>;
    interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
                 <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
};

这里 #gpio-cells = <2> 表示引用这个 GPIO 时,需要两个参数:GPIO 编号和标志位(比如高电平有效还是低电平有效)。

4.3.2 I2C 设备

I2C 子节点的绑定,通常需要 compatible、reg(I2C 地址),以及一些厂商自定义的属性。

&i2c1 {
    clock-frequency = <400000>;
    status = "okay";

    touch: touchscreen@38 {
        compatible = "edt,edt-ft5406";
        reg = <0x38>;
        interrupt-parent = <&gpio1>;
        interrupts = <9 IRQ_TYPE_EDGE_FALLING>;
        wakeup-source;
    };
};

注意那个 wakeup-source,就是我在开头提到的那个属性。没有它,触摸屏在系统休眠后无法唤醒。

4.3.3 引脚复用 (pinctrl)

引脚复用是设备树里最繁琐的部分之一。绑定文档通常会定义一组 pinctrl-<n> 属性,每个属性指向一个引脚配置节点。

&iomuxc {
    pinctrl_uart1: uart1grp {
        fsl,pins = <
            MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
            MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1
        >;
    };
};

&uart1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_uart1>;
    status = "okay";
};

这里的 0x1b0b1 是引脚配置的 pad 控制值,包括上下拉、驱动能力、速度等。这些值怎么算?嗯,你得去看芯片的参考手册,绑定文档里一般不会写那么细。

4.4 绑定文档的“潜规则”

除了文档里明写的,还有一些不成文的规则。我总结了几条:

  • compatible 字符串的命名: 一般是 厂商,设备型号,比如 fsl,imx6ul-uart。如果设备兼容某个更通用的型号,可以追加一个 fallback,比如 "fsl,imx6ul-uart", "fsl,imx6q-uart"。
  • reg 属性的顺序: 通常第一个是寄存器基地址和长度,第二个可能是某些特殊功能区的地址。具体顺序要看文档。
  • status 属性: 默认是 "disabled",需要显式设为 "okay" 才能启用。我见过有人忘了设这个,查了半天。
  • 中断类型: 尽量用 IRQ_TYPE_LEVEL_HIGH 或 IRQ_TYPE_EDGE_RISING 这样的宏,不要直接写数字。可读性差,而且容易写错。
核心原则: 绑定文档是“契约”。驱动开发者按文档写驱动,设备树编写者按文档配设备树。两边都对得上,系统才能跑起来。如果发现驱动和设备树对不上,先查文档,别急着改代码。

4.5 知识结构图:绑定文档阅读流程

下面这张图,是我自己总结的阅读绑定文档的流程。每次拿到一个新外设,我都会按这个步骤走一遍。

绑定文档阅读流程 1. 定位文档 2. 确认 compatible 3. 检查 required 属性 4. 配置可选属性 5. 验证与测试 常见 required 属性 • reg (寄存器地址) • interrupts (中断号) • clocks (时钟源) • resets (复位信号) 验证手段: dt-validate / 内核启动log

这个流程看起来简单,但每一步都有细节。比如第一步“定位文档”,如果你不知道外设的 compatible 字符串,可以先去内核源码里搜 of_match_table,找到驱动对应的 compatible,再反向查文档。

4.6 避坑指南

最后,分享几个我亲身经历的教训:

  • 不要完全相信示例代码: 绑定文档里的示例,有时候是简化版,或者来自老版本内核。我遇到过示例里用了 interrupts = <0 66 4>,但实际内核已经改用 GIC_SPI 宏了。所以,示例只能参考,最终要以文档的 properties 定义为准。
  • 注意 YAML 的缩进: YAML 对缩进极其敏感。我见过有人把 required 列表缩进错了,导致整个绑定校验失败。如果你手写设备树,建议用 dt-validate 工具检查一下。
  • 多看看 maintainers 字段: 如果文档里写了维护者的邮箱,遇到问题可以直接发邮件问。我试过几次,大部分维护者都很乐意解答。当然,前提是你已经仔细读过文档了。
一个小技巧: 在 Documentation/devicetree/bindings/ 目录下,用 grep -r "pattern" *.yaml 可以快速搜索某个属性或 compatible 字符串。比如我想查所有支持 wakeup-source 的设备,直接 grep -r "wakeup-source" *.yaml 就行。

好了,关于绑定文档的阅读方法,我就讲到这里。记住,文档是死的,但硬件是活的。遇到问题,多查文档,多动手验证,慢慢你就能形成自己的“套路”。