3、设备树中的GPIO描述:设备树语法基础、GPIO控制器节点、GPIO客户节点、GPIO属性详解

好,咱们进入正题。设备树这东西,说白了就是一块「硬件配置板」。你想想看,Linux内核要跑在不同硬件上,总不能每换一块板子就重新编译一次内核吧?设备树就是用来解决这个问题的——它把硬件信息从内核代码里抽出来,放到一个文本文件里,内核启动时去解析它。

我个人习惯,拿到一块新板子,第一件事就是看它的设备树。因为这里头藏着所有外设的「户口本」——GPIO、I2C、SPI、UART,全都在这里登记。

设备树语法基础——先看懂再说

设备树的语法其实不复杂。核心就几个概念:节点(node)、属性(property)、值(value)。每个节点代表一个硬件设备或总线,属性描述这个设备的参数。

来看个最简单的例子:

/dts-v1/;

/ {
    model = "My Board v1.0";
    compatible = "vendor,myboard", "vendor,common";

    soc {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;

        gpio0: gpio@ff710000 {
            compatible = "vendor,gpio-controller";
            reg = <0xff710000 0x1000>;
            gpio-controller;
            #gpio-cells = <2>;
        };
    };
};

嗯,这里要注意几个点:

  • /dts-v1/ 是版本声明,必须写在最前面
  • / 是根节点,所有设备都挂在这个下面
  • gpio0: gpio@ff710000 中,gpio0 是标签(label),方便其他地方引用;gpio@ff710000 是节点名,@后面跟的是地址
  • reg 属性描述寄存器地址范围,格式由 #address-cells 和 #size-cells 决定

我曾经遇到一个坑:同事把 #address-cells 写成了 #address-cell,少了个s,结果内核解析reg时直接报错。这种语法错误,编译时不会报,运行时才炸,排查起来特别头疼。

GPIO控制器节点——管脚的总管

GPIO控制器节点,就是芯片内部那个管脚管理单元的「代言人」。它必须声明自己是GPIO控制器,并且告诉内核:我管多少根脚,每个脚需要几个参数来描述。

关键属性就这几个:

属性 含义 示例
gpio-controller 标记这是一个GPIO控制器(布尔属性,不需要值) gpio-controller;
#gpio-cells 描述每个GPIO引用需要几个参数 #gpio-cells = <2>;
ngpios 该控制器管理的GPIO数量(可选) ngpios = <32>;

为什么 #gpio-cells 通常是2?第一个参数是管脚号,第二个参数是标志位(比如是否上拉、是否低电平有效)。我见过有些芯片用3个cell的,多出来的那个用来指定bank号。说白了,这完全取决于芯片设计,没有统一标准。

核心要点:GPIO控制器节点必须同时包含 gpio-controller 和 #gpio-cells 两个属性,缺一不可。否则内核根本认不出这是个GPIO控制器。

GPIO客户节点——谁在用这根脚

客户节点,就是使用GPIO的外设。比如一个LED、一个按键、一个复位信号。它们通过 gpios 或 xxx-gpios 属性来引用GPIO。

看个实际例子:

led-red {
    compatible = "gpio-leds";
    label = "red";
    gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>;
    default-state = "off";
};

button-reset {
    compatible = "gpio-keys";
    label = "reset";
    gpios = <&gpio0 7 GPIO_ACTIVE_LOW>;
    linux,code = <KEY_RESTART>;
};

这里 &gpio0 引用的是之前定义的GPIO控制器标签。15和7是管脚号。GPIO_ACTIVE_HIGH 和 GPIO_ACTIVE_LOW 是标志位,告诉驱动:这个信号是高电平有效还是低电平有效。

我记得有一次调试一个按键,按下后死活检测不到中断。查了半天,发现是设备树里写成了 GPIO_ACTIVE_HIGH,但硬件电路设计的是低电平触发。嗯,这种软硬件不匹配的问题,设备树里特别容易出。

GPIO属性详解——别小看这几个参数

咱们把 gpios 属性拆开来看。它的格式是:

gpios = <&phandle pin_num flags>;
  • &phandle:指向GPIO控制器的引用,用 & 加标签名
  • pin_num:管脚号,从0开始
  • flags:标志位,定义在 include/dt-bindings/gpio/gpio.h 中

常用的标志位:

宏定义 值 含义
GPIO_ACTIVE_HIGH 0 高电平有效
GPIO_ACTIVE_LOW 1 低电平有效
GPIO_OPEN_DRAIN 2 开漏输出
GPIO_OPEN_SOURCE 4 开源输出

你可能会问:为什么要有 GPIO_ACTIVE_LOW 这种标志?直接在驱动里取反不就行了?

原因很简单:设备树描述的是硬件,不是软件逻辑。硬件上这个信号就是低电平有效,那就应该在设备树里如实描述。驱动拿到这个标志后,会自动处理电平反转。这样做的好处是,同样的驱动代码,可以适配不同的硬件设计。

小技巧:如果你不确定某个GPIO的用法,可以在内核启动日志里搜 gpio 关键字。内核会打印出每个GPIO的申请情况,包括哪个驱动、哪个管脚、什么标志。这个比看代码快多了。

知识体系结构图

下面这张图,把设备树中GPIO描述的核心逻辑串起来了。我建议你多看几遍,理解清楚控制器和客户节点之间的关系。

设备树GPIO描述核心结构 / (根节点) gpio0: gpio@ff710000 gpio-controller; #gpio-cells = <2>; GPIO客户节点 led-red compatible = "gpio-leds"; gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>; button-reset compatible = "gpio-keys"; gpios = <&gpio0 7 GPIO_ACTIVE_LOW>; 控制器节点定义管脚资源,客户节点通过 gpios 属性引用具体管脚

警告:设备树中的GPIO管脚号,是芯片内部的GPIO bank偏移,不是物理管脚号。比如芯片有GPIOA0~GPIOA31和GPIOB0~GPIOB31,那GPIOB0的管脚号就是32。这个映射关系,不同芯片厂商定义不同,一定要看数据手册确认。

好了,设备树中GPIO描述的核心内容就这些。说白了就是三件事:控制器声明自己管GPIO、客户节点说我要用哪个管脚、属性告诉驱动怎么用。把这个逻辑理清了,后面看任何芯片的设备树都不会懵。


可可精品资料库