3. GPIO子系统介绍:Linux GPIO子系统架构、gpiolib库、GPIO编号与控制器、设备树中的GPIO描述
GPIO(General Purpose Input/Output,通用输入输出)是嵌入式系统中最基础、最常用的外设接口之一。在Linux内核中,GPIO子系统提供了一套统一的框架,用于管理和操作SoC或外部GPIO扩展芯片的引脚。理解GPIO子系统的架构、核心库、编号机制以及设备树描述,是编写高质量GPIO驱动的基础。
3.1 Linux GPIO子系统整体架构
Linux GPIO子系统采用分层设计,从上到下依次为:GPIO消费者(Consumer)、GPIO核心层(gpiolib)、GPIO控制器驱动(Provider)。这种分层架构使得驱动开发者无需关心底层硬件细节,只需通过标准API即可操作GPIO。
3.2 gpiolib库:核心功能与API
gpiolib是GPIO子系统的核心实现,位于内核源码的drivers/gpio/gpiolib.c中。它提供了以下关键功能:
- GPIO编号管理: 维护全局的GPIO编号空间,支持动态或静态分配。
- 消费者API: 提供
gpiod_get()、gpiod_direction_output()、gpiod_set_value()等标准接口。 - 中断支持: 将GPIO中断映射到内核中断系统,支持边沿触发和电平触发。
- sysfs接口: 通过
/sys/class/gpio/暴露给用户空间(旧接口,新设备树推荐使用libgpiod)。 - ACPI/设备树支持: 解析设备树或ACPI表,自动映射GPIO资源。
gpiod_*系列),而不是已废弃的整数编号API(gpio_*系列)。描述符API更安全、更灵活,且与设备树深度集成。
下表对比了新旧两套API的关键差异:
| 特性 | 旧API(gpio_*) | 新API(gpiod_*) |
|---|---|---|
| 核心数据类型 | int(GPIO编号) | struct gpio_desc *(描述符指针) |
| 请求GPIO | gpio_request() | gpiod_get() / devm_gpiod_get() |
| 设置方向 | gpio_direction_output() | gpiod_direction_output() |
| 读取值 | gpio_get_value() | gpiod_get_value() |
| 设备树支持 | 需手动解析of_get_named_gpio() | 自动解析,支持GPIO消费节点 |
| 资源管理 | 手动释放 | 支持devm_自动释放 |
3.3 GPIO编号与控制器
在Linux内核中,每个GPIO引脚都有一个全局唯一的GPIO编号。这个编号是系统级的,由内核在启动时根据注册的GPIO控制器动态分配。每个GPIO控制器对应一个struct gpio_chip结构体实例。
3.3.1 GPIO编号分配机制
GPIO编号的分配遵循以下规则:
- 每个
gpio_chip在注册时,内核会为其分配一段连续的编号范围。 - 编号从0开始,依次递增,但不同控制器的编号范围可能不连续。
- 通过
/sys/kernel/debug/gpio可以查看当前系统的GPIO编号映射。
3.3.2 gpio_chip结构体
每个GPIO控制器驱动必须实现并注册一个gpio_chip结构体,该结构体定义了控制器的属性和操作函数:
struct gpio_chip {
const char *label;
struct device *parent;
struct module *owner;
int base; // 起始编号,-1表示动态分配
int ngpio; // 本控制器管理的GPIO数量
// 核心回调函数
int (*direction_input)(struct gpio_chip *chip, unsigned offset);
int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
int (*get)(struct gpio_chip *chip, unsigned offset);
void (*set)(struct gpio_chip *chip, unsigned offset, int value);
// 中断相关
int (*to_irq)(struct gpio_chip *chip, unsigned offset);
// 其他可选回调
int (*request)(struct gpio_chip *chip, unsigned offset);
void (*free)(struct gpio_chip *chip, unsigned offset);
};
3.4 设备树中的GPIO描述
设备树(Device Tree)是描述硬件拓扑的标准化方式,在ARM/ARM64等嵌入式平台中广泛使用。GPIO子系统的设备树描述主要涉及两个方面:GPIO控制器节点和GPIO消费节点。
3.4.1 GPIO控制器节点
GPIO控制器节点定义了SoC或外部芯片的GPIO控制器属性,包括引脚数量、编号基址、中断控制器关联等。以下是一个典型的GPIO控制器节点示例:
// SoC内置GPIO控制器
gpio0: gpio@ff710000 {
compatible = "vendor,soc-gpio";
reg = <0xff710000 0x1000>;
gpio-controller; // 标记为GPIO控制器
#gpio-cells = <2>; // 每个GPIO描述需要2个cell
interrupt-controller; // 同时也是中断控制器
#interrupt-cells = <2>; // 中断描述需要2个cell
ngpios = <32>; // 本控制器有32个引脚
};
关键属性说明:
| 属性 | 含义 | 值示例 |
|---|---|---|
gpio-controller |
标记节点为GPIO控制器 | 空属性(布尔值) |
#gpio-cells |
GPIO描述所需的cell数量 | 2(通常为引脚号和标志位) |
ngpios |
控制器管理的GPIO数量 | 32 |
gpio-ranges |
GPIO与pinctrl引脚的映射关系 | <0 0 32> |
3.4.2 GPIO消费节点
外设驱动通过消费节点中的gpios或xxx-gpios属性来引用GPIO引脚。以下是一个按键设备使用GPIO中断的典型描述:
// 按键设备节点,使用GPIO中断
key-interrupt {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&key_pins>;
button@0 {
label = "KEY_POWER";
linux,code = <KEY_POWER>;
gpios = <&gpio0 15 GPIO_ACTIVE_LOW>; // GPIO0控制器,引脚15,低电平有效
interrupts-extended = <&gpio0 15 IRQ_TYPE_EDGE_FALLING>; // 下降沿触发中断
wakeup-source; // 支持唤醒系统
};
};
<&控制器节点 引脚号 标志位>。标志位常用值包括GPIO_ACTIVE_HIGH(0)和GPIO_ACTIVE_LOW(1),用于指示引脚的电气有效电平。
3.4.3 在驱动中解析设备树GPIO
内核驱动通过gpiod_get()系列函数从设备树中获取GPIO描述符。以下是一个按键中断驱动的设备树解析示例:
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
struct my_device {
struct gpio_desc *key_gpio;
int irq;
};
static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
int val = gpiod_get_value(dev->key_gpio);
pr_info("Key interrupt! GPIO value: %d\n", val);
return IRQ_HANDLED;
}
static int my_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct my_device *mydev;
int ret;
mydev = devm_kzalloc(dev, sizeof(*mydev), GFP_KERNEL);
if (!mydev)
return -ENOMEM;
// 从设备树获取名为"gpios"的GPIO描述符
mydev->key_gpio = devm_gpiod_get(dev, NULL, GPIOD_IN);
if (IS_ERR(mydev->key_gpio)) {
dev_err(dev, "Failed to get GPIO\n");
return PTR_ERR(mydev->key_gpio);
}
// 将GPIO转换为中断号
mydev->irq = gpiod_to_irq(mydev->key_gpio);
if (mydev->irq < 0) {
dev_err(dev, "Failed to get IRQ from GPIO\n");
return mydev->irq;
}
// 注册中断处理函数
ret = devm_request_irq(dev, mydev->irq, key_irq_handler,
IRQF_TRIGGER_FALLING, "key_irq", mydev);
if (ret) {
dev_err(dev, "Failed to request IRQ\n");
return ret;
}
platform_set_drvdata(pdev, mydev);
return 0;
}
3.5 本章小结
本章深入介绍了Linux GPIO子系统的核心知识:
- 分层架构: 消费者层、核心层(gpiolib)、控制器驱动层、硬件层,层间通过标准接口交互。
- gpiolib库: 提供统一的消费者API和控制器注册机制,推荐使用基于描述符的新API。
- GPIO编号与控制器: 每个gpio_chip管理一段连续编号,全局编号空间由内核动态维护。
- 设备树描述: 通过gpio-controller和#gpio-cells定义控制器,消费节点使用gpios属性引用引脚。