4. 设备树GPIO绑定:设备树语法基础、GPIO控制器节点、gpios属性、引脚复用配置
设备树(Device Tree)是嵌入式Linux中描述硬件拓扑的核心机制。对于GPIO驱动开发而言,设备树承担着“硬件描述语言”的角色——它告诉内核:哪个芯片负责GPIO控制、哪些引脚被用作输入/输出、中断触发方式是什么、引脚复用功能如何切换。本章从语法基础出发,逐步深入到GPIO控制器节点、gpios属性及引脚复用配置,为后续中断注册与驱动编写打下坚实的硬件描述基础。
4.1 设备树语法基础
设备树使用节点(node)和属性(property)来描述硬件。每个节点代表一个设备或总线,属性则描述设备的配置参数。以下是必须掌握的核心语法要素:
| 语法元素 | 格式 | 说明 |
|---|---|---|
| 节点定义 | node-name@unit-address { ... }; |
节点名称+寄存器地址(可选) |
| 属性赋值 | property-name = <value>; |
支持整数、字符串、数组、二进制数据 |
| 标签引用 | &label { ... }; |
通过标签扩展或覆盖已有节点 |
| 包含文件 | #include "filename.dtsi" |
引入公共设备树头文件 |
| 宏定义 | #define GPIO_ACTIVE_HIGH 0 |
配合#include使用,增强可读性 |
关键约定:设备树中
#address-cells和#size-cells决定子节点reg属性的地址和长度字段位数。GPIO控制器通常设置#address-cells = <1>、#size-cells = <0>。
以下是一个典型的设备树片段,展示节点层级与属性写法:
/dts-v1/;
#include <dt-bindings/gpio/gpio.h>
/ {
model = "Embedded GPIO Demo Board";
compatible = "vendor,board-rev1";
soc {
#address-cells = <1>;
#size-cells = <1>;
gpio0: gpio@ff710000 {
compatible = "vendor,gpio-controller";
reg = <0xff710000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
interrupts = <0 32 4>;
};
};
};
提示:
#gpio-cells = <2>表示每个GPIO引用需要2个参数:引脚号(0~31)和标志位(如GPIO_ACTIVE_HIGH或GPIO_ACTIVE_LOW)。
4.2 GPIO控制器节点
GPIO控制器节点是设备树中描述GPIO硬件模块的核心节点。它必须包含以下关键属性:
gpio-controller;—— 标记该节点为GPIO控制器,是必须的布尔属性。#gpio-cells—— 指定引用GPIO时需要的参数个数,通常为2(引脚号+标志位)。compatible—— 匹配驱动字符串,如"vendor,gpio-controller"。reg—— 控制器寄存器基地址和地址范围。interrupts—— 控制器本身的中断号,用于支持引脚中断功能。
常见错误:忘记添加
gpio-controller;属性会导致内核无法将该节点识别为GPIO控制器,其他设备通过gpios引用时会返回-EPROBE_DEFER或-EINVAL。
以下是一个完整的GPIO控制器节点示例,包含中断控制器功能:
gpio2: gpio@ff720000 {
compatible = "vendor,gpio-controller";
reg = <0xff720000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <0 33 4>;
clocks = <&clk_gate GPIO2_CLK>;
ngpios = <32>;
};
| 属性 | 值示例 | 作用 |
|---|---|---|
interrupt-controller; |
布尔 | 声明该节点也是中断控制器 |
#interrupt-cells |
<2> |
中断描述参数:<引脚号 触发类型> |
ngpios |
<32> |
该控制器管理的GPIO引脚总数 |
clocks |
<&clk_gate GPIO2_CLK> |
GPIO模块的时钟源 |
4.3 gpios属性:如何引用GPIO引脚
当外设设备需要使用GPIO引脚时,通过gpios属性(或cs-gpios、reset-gpios等命名变体)引用GPIO控制器中的具体引脚。引用格式为:
gpios = <&gpio_controller pin_number flags>;
其中flags常用宏定义(定义在dt-bindings/gpio/gpio.h中):
| 宏名称 | 值 | 含义 |
|---|---|---|
GPIO_ACTIVE_HIGH |
0 | 高电平有效(默认) |
GPIO_ACTIVE_LOW |
1 | 低电平有效 |
GPIO_OPEN_DRAIN |
16 | 开漏输出 |
GPIO_OPEN_SOURCE |
32 | 开源输出 |
GPIO_PULL_UP |
64 | 内部上拉 |
GPIO_PULL_DOWN |
128 | 内部下拉 |
实际设备树中的使用示例:
#include <dt-bindings/gpio/gpio.h>
led-red {
compatible = "gpio-leds";
gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
button-reset {
compatible = "gpio-keys";
gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
label = "reset_button";
linux,code = <KEY_RESTART>;
};
最佳实践:始终使用
#include <dt-bindings/gpio/gpio.h>中的宏定义,而不是直接写数字0或1。这能显著提高设备树的可读性和可维护性。
4.4 引脚复用配置
引脚复用(Pin Mux)是嵌入式系统中常见的需求——同一个物理引脚可能被配置为GPIO、UART、I2C、SPI等功能。设备树通过pinctrl子系统描述引脚复用状态。核心概念包括:
pinctrl-names—— 定义状态名称列表,如"default"、"sleep"、"active"。pinctrl-0、pinctrl-1—— 对应每个状态的引脚配置句柄。- pinmux节点 —— 在SoC设备树中预定义,描述引脚功能选择寄存器配置。
典型的引脚复用配置示例:
&uart1 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&uart1_tx_pins &uart1_rx_pins>;
pinctrl-1 = <&uart1_sleep_pins>;
status = "okay";
};
/* 在SoC dtsi中预定义的引脚组 */
uart1_tx_pins: uart1_tx_pins {
pins = "GPIO1_12";
function = "uart1_tx";
bias-pull-up;
drive-strength = <4>;
};
uart1_rx_pins: uart1_rx_pins {
pins = "GPIO1_13";
function = "uart1_rx";
bias-pull-up;
};
| pinctrl属性 | 作用 | 典型值 |
|---|---|---|
pins |
指定引脚名称或编号 | "GPIO1_12"、"PAD_CTRL0" |
function |
引脚功能选择 | "uart1_tx"、"gpio" |
bias-pull-up |
内部上拉 | 布尔属性 |
bias-pull-down |
内部下拉 | 布尔属性 |
bias-disable |
禁用上下拉 | 布尔属性 |
drive-strength |
驱动能力(mA) | <4>、<8> |
input-enable |
使能输入缓冲 | 布尔属性 |
output-high |
初始输出高电平 | 布尔属性 |
驱动加载时序:当设备驱动probe时,内核pinctrl子系统自动将引脚切换到
pinctrl-names中名为"default"的状态。如果定义了"sleep"状态,系统休眠时会自动切换。
4.5 综合案例:带中断的按键设备树配置
将以上知识点整合,展示一个完整的带中断功能的按键设备树配置:
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
/ {
gpio-keys {
compatible = "gpio-keys";
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default";
pinctrl-0 = <&key_pins>;
button@0 {
label = "USER_KEY";
gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
interrupts = <15 IRQ_TYPE_EDGE_FALLING>;
linux,code = <KEY_ENTER>;
debounce-interval = <50>;
};
};
};
&pinctrl {
key_pins: key_pins {
pins = "GPIO0_15";
function = "gpio";
bias-pull-up;
input-enable;
};
};
注意:当使用
gpios属性时,如果GPIO控制器也支持中断,可以在同一节点中同时使用gpios和interrupts属性。但interrupts属性中的第一个参数是引脚号,第二个参数是触发类型,与gpios的标志位含义不同,不要混淆。