3. 设备树实例:GPIO控制器
本章通过一个完整的GPIO控制器设备树节点实例,深入讲解地址资源、中断资源、GPIO范围等核心属性的编写方法与驱动匹配机制。GPIO控制器是嵌入式系统中最基础、最常用的外设之一,掌握其设备树节点的编写规范,对于理解整个设备树体系具有举足轻重的作用。
#gpio-cells、gpio-controller、interrupt-controller等关键标志位的作用;学会在驱动中通过of_match_table匹配自定义节点。
3.1 GPIO控制器节点结构总览
一个完整的GPIO控制器设备树节点通常包含以下五类属性:兼容性、地址资源、中断资源、GPIO控制标志、其他辅助属性。下面是一个基于ARM架构、挂载在AXI总线上的GPIO控制器节点示例:
gpio0: gpio@ff240000 {
compatible = "vendor,gpio-controller", "generic-gpio";
reg = <0x0 0xff240000 0x0 0x1000>;
interrupts = <0 45 4>;
interrupt-parent = <&gic>;
#interrupt-cells = <2>;
interrupt-controller;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl1 0 32 16>;
ngpios = <16>;
clocks = <&clkc 15>;
status = "okay";
};
gpio@基地址 格式,gpio0 是标签(label),用于在其它节点中通过 &gpio0 引用。标签名应具有唯一性,建议按序号递增。
3.2 核心属性详解
下表列出了GPIO控制器节点中每个核心属性的含义、格式与典型值:
| 属性名 | 格式 | 含义 | 典型值 |
|---|---|---|---|
compatible |
字符串列表 | 驱动匹配标识,优先使用厂商特定字符串 | "vendor,gpio-controller" |
reg |
<地址 长度> |
寄存器物理地址范围,支持多段 | <0x0 0xff240000 0x0 0x1000> |
interrupts |
<中断号 触发类型> |
控制器本身的中断线描述 | <0 45 4> |
gpio-controller |
空标志 | 标记该节点为GPIO控制器 | 无值 |
#gpio-cells |
整数 | GPIO specifier 中单元格数量 | 2 |
gpio-ranges |
<pin控制器 本地偏移 全局偏移 数量> |
GPIO与引脚控制器的映射关系 | <&pinctrl1 0 32 16> |
ngpios |
整数 | 该控制器管理的GPIO引脚总数 | 16 |
gpio-controller; 标志会导致其它节点无法通过 &gpio0 引用该控制器;#gpio-cells 的值必须与驱动解析逻辑一致,通常为2(引脚号 + 标志位)。
3.3 GPIO specifier 与客户节点引用
当一个外设节点需要使用GPIO时,通过 gpios 或 xxx-gpios 属性引用控制器。每个GPIO引用称为一个 GPIO specifier,其单元格数量由控制器的 #gpio-cells 决定。
/* 客户节点:使用 gpio0 的第5号引脚,低电平有效 */
led-red {
compatible = "gpio-leds";
gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
};
/* 客户节点:使用 gpio0 的第3号引脚,高电平有效,用于按键中断 */
button-reset {
compatible = "gpio-keys";
gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
interrupts = <3 IRQ_TYPE_EDGE_RISING>;
};
在驱动代码中,通过 of_get_named_gpio_flags() 或 gpiod_get() 系列API解析这些引用,无需手动计算寄存器偏移。
3.4 中断子节点与层级结构
当GPIO控制器同时作为中断控制器时(即同时声明 interrupt-controller; 和 #interrupt-cells),其内部可以包含中断子节点来描述每个引脚的触发方式。这种层级结构在GPIO扩展芯片(如PCA9535、MCP23017)中尤为常见。
gpio_expander: gpio@20 {
compatible = "nxp,pca9535";
reg = <0x20>;
gpio-controller;
#gpio-cells = <2>;
interrupt-parent = <&gpio0>;
interrupts = <10 IRQ_TYPE_EDGE_FALLING>;
interrupt-controller;
#interrupt-cells = <2>;
/* 中断子节点:定义每个引脚的默认中断配置 */
irq_pins {
interrupt-parent = <&gpio_expander>;
interrupts = <0 IRQ_TYPE_LEVEL_LOW>,
<1 IRQ_TYPE_LEVEL_LOW>;
};
};
gpio-controller 和 interrupt-controller,否则内核无法建立从GPIO到中断控制器的完整映射,导致 gpio_to_irq() 调用失败。
3.5 GPIO控制器驱动匹配流程
下面这张SVG流程图展示了从设备树节点到驱动 probe 函数的完整匹配链路:
3.6 驱动端解析设备树资源的典型代码
在GPIO控制器的驱动 probe 函数中,通常需要解析以下四类资源。下面给出一个经过简化的、可直接参考的代码框架:
static int my_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct resource *res;
void __iomem *base;
int irq, ngpios, ret;
/* 1. 解析 reg 地址资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
/* 2. 解析中断资源 */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
/* 3. 解析 ngpios 属性(可选,默认16) */
ret = of_property_read_u32(np, "ngpios", &ngpios);
if (ret)
ngpios = 16;
/* 4. 注册 gpio_chip 结构体 */
struct gpio_chip *chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
chip->label = dev_name(dev);
chip->parent = dev;
chip->owner = THIS_MODULE;
chip->base = -1; /* 动态分配基号 */
chip->ngpio = ngpios;
chip->of_node = np;
chip->request = my_gpio_request;
chip->free = my_gpio_free;
chip->direction_input = my_gpio_direction_input;
chip->direction_output = my_gpio_direction_output;
chip->get = my_gpio_get;
chip->set = my_gpio_set;
ret = devm_gpiochip_add_data(dev, chip, NULL);
if (ret)
return ret;
dev_info(dev, "GPIO controller probed: %d pins, irq %d\n", ngpios, irq);
return 0;
}
devm_gpiochip_add_data() 是推荐使用的注册接口,它自动管理生命周期;of_property_read_u32() 用于读取设备树中的整数属性;platform_get_irq() 自动解析 interrupts 属性并返回Linux中断号。
3.7 多bank GPIO控制器的设备树设计
对于包含多个bank(每组32个引脚)的复杂GPIO控制器,设备树节点需要为每个bank独立描述地址范围和中断线。下面是一个双bank控制器的节点示例:
gpio1: gpio@ff250000 {
compatible = "vendor,dual-bank-gpio";
reg = <0x0 0xff250000 0x0 0x1000>, /* Bank0 寄存器 */
<0x0 0xff251000 0x0 0x1000>; /* Bank1 寄存器 */
reg-names = "bank0", "bank1";
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&gic>;
interrupts = <0 46 4>, /* Bank0 中断 */
<0 47 4>; /* Bank1 中断 */
interrupt-names = "bank0_irq", "bank1_irq";
gpio-controller;
#gpio-cells = <2>;
ngpios = <64>; /* 两个bank共64个引脚 */
gpio-ranges = <&pinctrl1 0 0 32>,
<&pinctrl1 32 64 32>;
};
驱动端通过 platform_get_resource_byname(pdev, IORESOURCE_MEM, "bank0") 和 platform_get_irq_byname(pdev, "bank0_irq") 分别获取每个bank的资源,避免硬编码索引。
3.8 调试与验证方法
设备树节点编写完成后,建议按以下步骤进行验证:
- 编译检查: 使用
dtc -I dts -O dtb -o test.dtb test.dts确认语法正确。 - 内核启动日志: 检查
dmesg中是否出现gpiochip_add相关打印,确认引脚数量与中断号正确。 - 用户空间验证: 通过
/sys/kernel/debug/gpio查看已注册的GPIO控制器列表。 - 中断测试: 使用
cat /proc/interrupts观察GPIO中断号是否注册成功。 - 回读测试: 通过
libgpiod工具(gpioinfo、gpioget、gpioset)验证引脚读写功能。
probe 未被调用,首先检查 compatible 字符串是否与驱动中的 of_match_table 完全一致(包括大小写和逗号);其次确认节点 status 属性不是 "disabled"。
3.9 本章小结
本章通过一个完整的GPIO控制器设备树节点实例,系统讲解了地址资源、中断资源、GPIO范围、控制器标志等核心属性的编写规范。重点强调了 gpio-controller 与 #gpio-cells 的配对使用、中断子节点的层级设计、以及驱动端解析设备树资源的标准代码框架。掌握这些内容后,读者可以独立为任意GPIO控制器编写正确的设备树节点,并快速定位驱动匹配失败的问题。