4、GPIO子系统框架:Linux内核GPIO子系统架构、gpiolib、pinctrl子系统、设备树中GPIO描述方法
在全志T113平台上实现GPIO驱动控制LED与蜂鸣器中断响应,必须深入理解Linux内核的GPIO子系统框架。该框架是连接硬件引脚与上层应用的核心桥梁,主要由三大部分构成:gpiolib(通用GPIO库)、pinctrl子系统(引脚控制子系统)以及设备树(硬件描述机制)。本章将系统性地剖析这三者的架构、协作关系及在全志T113上的具体应用。
4.1 Linux内核GPIO子系统整体架构
Linux内核的GPIO子系统采用分层设计,从上到下依次为:GPIO消费者(Consumer,如LED驱动、按键驱动)、GPIO核心层(gpiolib核心)、GPIO控制器驱动(Provider,如全志T113的GPIO控制器驱动)。这种分层架构实现了接口与实现的解耦。
在全志T113平台上,GPIO控制器驱动位于底层,直接操作硬件寄存器;gpiolib核心层提供标准化的API接口,如gpiod_get_value()、gpiod_set_value()等;消费者层则是我们编写的LED、蜂鸣器及中断响应驱动程序。
4.2 gpiolib:通用GPIO库核心机制
gpiolib是GPIO子系统的核心实现,负责管理系统中所有的GPIO引脚。它提供了从内核空间到用户空间的完整访问路径。
4.2.1 gpiolib核心数据结构
gpiolib围绕两个核心数据结构展开:
| 数据结构 | 作用 | 关键字段 |
|---|---|---|
struct gpio_chip |
描述一个GPIO控制器,包含操作函数集 | request、free、get、set、direction_input、direction_output、to_irq |
struct gpio_desc |
描述一个具体的GPIO引脚描述符 | flags(方向、状态)、label(使用者标签)、chip(所属控制器) |
gpiod_*系列),而非传统的整数编号API(gpio_*系列)。描述符API更安全、更符合设备树模型,且支持内核的引用计数管理。
4.2.2 gpiolib API层次
gpiolib提供了三个层次的API,适应不同的使用场景:
- 消费者API(驱动开发者常用):
gpiod_get()/gpiod_get_index():从设备树获取GPIO描述符gpiod_direction_output()/gpiod_direction_input():设置方向gpiod_set_value()/gpiod_get_value():读写电平gpiod_to_irq():获取GPIO对应的中断号gpiod_put():释放GPIO描述符
- 控制器驱动API(芯片厂商使用):
gpiochip_add_data():注册GPIO控制器gpiochip_remove():注销GPIO控制器gpiochip_request_own_desc():控制器内部请求引脚
- 用户空间API:
/sys/class/gpio/:sysfs接口(旧,逐渐弃用)/dev/gpiochip*:cdev字符设备接口(新,推荐)
4.2.3 全志T113 gpiolib注册流程
全志T113的GPIO控制器驱动在初始化时,会为每个GPIO bank(如PA、PB、PC等)注册一个gpio_chip实例。以下是简化的注册流程:
// 全志T113 GPIO控制器驱动注册伪代码
static struct gpio_chip sunxi_gpio_chip = {
.label = "sunxi-gpio",
.request = sunxi_gpio_request,
.free = sunxi_gpio_free,
.set_direction = sunxi_gpio_set_direction,
.direction_input = sunxi_gpio_direction_input,
.direction_output = sunxi_gpio_direction_output,
.get = sunxi_gpio_get,
.set = sunxi_gpio_set,
.to_irq = sunxi_gpio_to_irq,
.base = -1, // 动态分配基编号
.ngpio = 32, // 每个bank 32个引脚
.can_sleep = false,
};
static int sunxi_gpio_probe(struct platform_device *pdev)
{
struct sunxi_gpio_chip *sgc;
int ret;
// 1. 分配并初始化私有数据结构
sgc = devm_kzalloc(&pdev->dev, sizeof(*sgc), GFP_KERNEL);
// 2. 映射GPIO寄存器基地址
sgc->regs = devm_platform_ioremap_resource(pdev, 0);
// 3. 设置gpio_chip的私有数据
sgc->chip = sunxi_gpio_chip;
sgc->chip.parent = &pdev->dev;
sgc->chip.of_node = pdev->dev.of_node;
// 4. 注册gpio_chip到内核
ret = gpiochip_add_data(&sgc->chip, sgc);
// 5. 注册pinctrl相关功能
sunxi_pinctrl_register(pdev, sgc);
return ret;
}
4.3 pinctrl子系统:引脚复用与控制
pinctrl子系统是GPIO子系统的“搭档”,负责管理引脚的复用功能(function)和电气属性(如上下拉、驱动能力)。在全志T113上,一个物理引脚可能同时作为GPIO、UART TX、I2C SCL等,pinctrl决定了引脚当前工作在哪种模式。
4.3.1 pinctrl核心概念
| 概念 | 说明 | 全志T113示例 |
|---|---|---|
| pin | 物理引脚,芯片上的一个焊盘 | PA1、PB3、PC5 |
| function | 引脚可切换的功能模式 | GPIO、UART2_TX、SPI1_MOSI |
| group | 一组具有相同功能配置的引脚集合 | uart2-pins(PB0/PB1) |
| state | 设备在特定场景下的引脚配置状态 | default、sleep、active |
| config | 引脚的电气属性配置 | pull-up、pull-down、drive-strength |
4.3.2 全志T113 pinctrl驱动实现
全志T113的pinctrl驱动通常与GPIO控制器驱动集成在一起,形成“pinctrl+gpio”复合驱动。驱动通过解析设备树中的pinctrl-*属性,配置引脚的复用寄存器。
// 全志T113 pinctrl设备树节点示例(sun8i-t113.dtsi)
pio: pinctrl@2000000 {
compatible = "allwinner,sun8i-t113-pinctrl";
reg = <0x02000000 0x400>;
interrupts = <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <3>;
interrupt-controller;
#interrupt-cells = <3>;
// 定义引脚复用组
uart2_pins: uart2-pins {
pins = "PB0", "PB1";
function = "uart2";
};
i2c0_pins: i2c0-pins {
pins = "PA11", "PA12";
function = "i2c0";
bias-pull-up;
};
led_pins: led-pins {
pins = "PD22";
function = "gpio_out";
};
};
4.4 设备树中GPIO描述方法
设备树(Device Tree)是描述硬件拓扑和配置的机制,在嵌入式Linux中广泛使用。全志T113的GPIO资源完全通过设备树来描述和分配。
4.4.1 GPIO控制器节点
GPIO控制器节点在设备树中通过gpio-controller属性标识,并定义#gpio-cells指定GPIO描述符的格式。
// 全志T113 GPIO控制器节点(pinctrl节点内嵌)
pio: pinctrl@2000000 {
compatible = "allwinner,sun8i-t113-pinctrl";
reg = <0x02000000 0x400>;
interrupts = <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller; // 标识为GPIO控制器
#gpio-cells = <3>; // 3个cell:bank、offset、flags
interrupt-controller; // 标识为中断控制器
#interrupt-cells = <3>; // 3个cell:bank、offset、type
};
4.4.2 GPIO consumer节点
使用GPIO的设备节点通过gpios或xxx-gpios属性引用GPIO引脚。
// LED设备节点示例
leds {
compatible = "gpio-leds";
led0: led-0 {
label = "status-led";
gpios = <&pio PD 22 GPIO_ACTIVE_HIGH>; // PD22,高电平点亮
default-state = "off";
};
};
// 蜂鸣器设备节点示例
buzzer {
compatible = "pwm-beeper";
pwms = <&pwm 0 1000000 0>; // PWM控制蜂鸣器
};
// 按键中断设备节点示例
keys {
compatible = "gpio-keys";
key0: key-0 {
label = "USER_KEY";
gpios = <&pio PA 10 GPIO_ACTIVE_LOW>; // PA10,低电平触发
linux,code = <KEY_ENTER>;
gpio-key,wakeup;
};
};
4.4.3 GPIO描述符格式详解
全志T113使用3个cell来描述一个GPIO:
| Cell | 含义 | 示例值 | 说明 |
|---|---|---|---|
| 第1个cell | GPIO控制器phandle | &pio |
引用pinctrl节点 |
| 第2个cell | Bank编号 | PD(值为3) |
PA=0, PB=1, PC=2, PD=3, PE=4, PF=5, PG=6 |
| 第3个cell | 引脚偏移 | 22 |
该bank内的引脚序号(0~31) |
| 第4个cell | 标志位 | GPIO_ACTIVE_HIGH |
有效电平:0=低电平有效,1=高电平有效 |
4.4.4 中断描述方法
当GPIO用于中断时,需要在设备树中描述中断属性:
// 外部中断设备节点示例
external-interrupt {
compatible = "my-device,ext-int";
interrupt-parent = <&pio>; // 中断控制器为pio
interrupts = <PA 10 IRQ_TYPE_EDGE_FALLING>; // PA10,下降沿触发
// 或者使用gpios + gpio-to-irq方式
gpios = <&pio PA 10 GPIO_ACTIVE_LOW>;
};
IRQ_TYPE_EDGE_RISING(上升沿)、IRQ_TYPE_EDGE_FALLING(下降沿)、IRQ_TYPE_EDGE_BOTH(双边沿)、IRQ_TYPE_LEVEL_HIGH(高电平)、IRQ_TYPE_LEVEL_LOW(低电平)。在编写中断响应驱动时,需根据硬件设计选择合适的触发类型。
4.5 驱动开发中的GPIO操作流程
基于上述框架,在全志T113上开发GPIO驱动(如控制LED和响应蜂鸣器中断)的标准流程如下:
4.5.1 驱动代码示例:LED控制
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
struct led_device {
struct gpio_desc *led_gpio;
};
static int led_probe(struct platform_device *pdev)
{
struct led_device *led;
struct device *dev = &pdev->dev;
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
// 从设备树获取GPIO描述符(对应"gpios"属性)
led->led_gpio = devm_gpiod_get(dev, NULL, GPIOD_OUT_LOW);
if (IS_ERR(led->led_gpio)) {
dev_err(dev, "Failed to get GPIO\n");
return PTR_ERR(led->led_gpio);
}
// 设置GPIO方向为输出,初始低电平
gpiod_direction_output(led->led_gpio, 0);
// 点亮LED(设置高电平)
gpiod_set_value(led->led_gpio, 1);
platform_set_drvdata(pdev, led);
dev_info(dev, "LED driver initialized\n");
return 0;
}
static int led_remove(struct platform_device *pdev)
{
struct led_device *led = platform_get_drvdata(pdev);
// 关闭LED
gpiod_set_value(led->led_gpio, 0);
// devm_gpiod_put 由 devm 自动管理,无需手动释放
dev_info(&pdev->dev, "LED driver removed\n");
return 0;
}
static const struct of_device_id led_of_match[] = {
{ .compatible = "my-device,gpio-led" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "t113-gpio-led",
.of_match_table = led_of_match,
},
};
module_platform_driver(led_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedded Developer");
MODULE_DESCRIPTION("T113 GPIO LED Driver");
4.5.2 驱动代码示例:中断响应
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/of.h>
struct irq_device {
struct gpio_desc *irq_gpio;
int irq_num;
};
static irqreturn_t irq_handler(int irq, void *dev_id)
{
struct irq_device *irq_dev = dev_id;
pr_info("GPIO interrupt triggered! IRQ: %d\n", irq);
// 在这里添加蜂鸣器控制逻辑
// 例如:gpiod_set_value(buzzer_gpio, 1); mdelay(100); gpiod_set_value(buzzer_gpio, 0);
return IRQ_HANDLED;
}
static int irq_probe(struct platform_device *pdev)
{
struct irq_device *irq_dev;
struct device *dev = &pdev->dev;
int ret;
irq_dev = devm