4、GPIO子系统框架:Linux内核GPIO子系统架构、gpiolib、pinctrl子系统、设备树中GPIO描述方法

在全志T113平台上实现GPIO驱动控制LED与蜂鸣器中断响应,必须深入理解Linux内核的GPIO子系统框架。该框架是连接硬件引脚与上层应用的核心桥梁,主要由三大部分构成:gpiolib(通用GPIO库)、pinctrl子系统(引脚控制子系统)以及设备树(硬件描述机制)。本章将系统性地剖析这三者的架构、协作关系及在全志T113上的具体应用。

核心概念:GPIO子系统是Linux内核中用于管理和控制通用输入输出引脚的标准化框架。它将硬件细节抽象化,为驱动开发者提供统一的API接口,同时通过pinctrl子系统实现引脚复用和电气属性的精细控制。

4.1 Linux内核GPIO子系统整体架构

Linux内核的GPIO子系统采用分层设计,从上到下依次为:GPIO消费者(Consumer,如LED驱动、按键驱动)、GPIO核心层(gpiolib核心)、GPIO控制器驱动(Provider,如全志T113的GPIO控制器驱动)。这种分层架构实现了接口与实现的解耦。

Linux内核GPIO子系统分层架构 GPIO消费者(Consumer) LED驱动 / 蜂鸣器驱动 / 按键驱动 / 外部中断服务 GPIO核心层(gpiolib) gpiochip_add / gpio_request / gpiod_get / 中断映射 sysfs接口 / cdev接口 / 描述符管理 GPIO控制器驱动(Provider) 全志T113 GPIO控制器 / 寄存器操作 / 中断控制器 pinctrl集成 / 引脚复用配置 / 电气属性设置 pin ctr l 分层设计优势 消费者无需关心底层寄存器细节,控制器驱动无需关心上层使用场景

在全志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(所属控制器)
开发提示:在编写全志T113 GPIO驱动时,优先使用基于描述符的API(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决定了引脚当前工作在哪种模式。

pinctrl子系统与GPIO子系统协作关系 pinctrl核心层 引脚复用配置 电气属性设置 引脚状态管理 GPIO核心层 电平读写 方向控制 中断映射 全志T113物理引脚 PA0 / PB1 / PC2 ... 复用寄存器 / 配置寄存器 配置复用功能 读写电平/中断 协作模式 pinctrl决定引脚功能(GPIO/UART/I2C),GPIO子系统在“GPIO模式”下控制电平

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";
    };
};
常见陷阱:在全志T113上,如果一个引脚被pinctrl配置为UART功能,那么通过GPIO子系统对该引脚进行读写操作将无效或产生不可预知的结果。务必确保在设备树中正确配置引脚的function为"gpio_in"或"gpio_out"后,才能使用GPIO API。

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>;
};
全志T113特殊说明:全志T113的GPIO中断控制器支持多种触发类型: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和响应蜂鸣器中断)的标准流程如下:

全志T113 GPIO驱动开发标准流程 1. 设备树描述硬件资源 2. 解析设备树获取GPIO描述符 3. 请求GPIO并设置方向 4a. 输出:控制LED电平 4b. 输入:注册中断处理 5a. 定时/事件触发电平翻转 5b. 中断到来执行回调函数 6. 驱动退出时释放GPIO资源

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