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管脚号,是芯片内部的GPIO bank偏移,不是物理管脚号。比如芯片有GPIOA0~GPIOA31和GPIOB0~GPIOB31,那GPIOB0的管脚号就是32。这个映射关系,不同芯片厂商定义不同,一定要看数据手册确认。
好了,设备树中GPIO描述的核心内容就这些。说白了就是三件事:控制器声明自己管GPIO、客户节点说我要用哪个管脚、属性告诉驱动怎么用。把这个逻辑理清了,后面看任何芯片的设备树都不会懵。