GPIO(通用输入输出)是嵌入式开发里最常用到的硬件资源,无论是点亮LED、读取按键状态,还是控制外部传感器,都离不开它。在Linux内核中,GPIO子系统(gpiolib)屏蔽了不同SoC、不同扩展芯片的硬件差异,给上层驱动提供了统一的调用接口。
GPIO子系统采用经典的三层分层架构,每层职责清晰,解耦度很高:

理解GPIO子系统,首先要搞懂三个核心数据结构的职责和关系:
struct gpio_desc 定义在 drivers/gpio/gpiolib.h(gpiolib内部私有头文件)
/**
* struct gpio_desc - 每个GPIO引脚的描述符
*
* 每个物理GPIO引脚对应一个gpio_desc实例,
* 记录该引脚的完整状态信息。
*
* 消费层驱动拿到的 struct gpio_desc * 是不透明指针,
* 只能通过 gpiod_*() API间接操作。
*/
structgpio_desc {
structgpio_device *gdev;// 所属的GPIO控制器设备
unsignedlong *flags; // 引脚状态标志位图
/* 以下字段在CONFIG_GPIOLIB_IRQCHIP开启时可用 */
#ifdef CONFIG_GPIOLIB_IRQCHIP
structirq_data *irq_data;// 关联的中断数据(可选)
#endif
};
flags 是一个位图,每个bit代表引脚的一种状态:
/* drivers/gpio/gpiolib.h */
#define FLAG_REQUESTED 0 // 已被申请(gpiod_get成功)
#define FLAG_IS_OUT 1 // 当前方向为输出(0=输入)
#define FLAG_EXPORT 2 // 已导出到sysfs
#define FLAG_SYSFS 3 // 通过sysfs接口申请的
#define FLAG_IS_HOGGED 4 // 被GPIO hog机制自动占用
#define FLAG_IS_HW_TIMESTAMP 5 // 硬件时间戳已启用
#define FLAG_USED_AS_IRQ 6 // 被用作中断源(已锁定)
#define FLAG_ACTIVE_LOW 7 // 低电平有效标志
#define FLAG_OPEN_DRAIN 8 // 开漏输出模式
#define FLAG_OPEN_SOURCE 9 // 开源输出模式
#define FLAG_BIAS_PULL_UP 10 // 内部上拉已使能
#define FLAG_BIAS_PULL_DOWN 11 // 内部下拉已使能
#define FLAG_BIAS_DISABLE 12 // 偏置已禁用
#define FLAG_TRANSITORY 13 // 瞬态标志(系统挂起时不保持状态)
#define FLAG_IS_IN 14 // 当前方向为输入
这些标志位由gpiolib核心层自动管理,消费层驱动通过 gpiod_*() API间接设置或查询。
核心字段详解
gdev — 所属控制器
structgpio_device *gdev;
指向该引脚所属的 gpio_device。通过这个指针,gpiolib可以从一个引脚描述符追溯到整个控制器:
// gpiolib内部通过desc找到对应的gpio_chip
structgpio_chip *gc = desc->gdev->chip;
flags — 状态标志位图
unsignedlong *flags;
记录引脚的当前状态。gpiolib在每次操作前都会检查相关标志位,例如:
这是gpiolib内部用来管理每个GPIO控制器的结构,每个注册的GPIO控制器都对应一个gpio_device实例。struct gpio_device 定义在内核源码 drivers/gpio/gpiolib.h 中
/*
* drivers/gpio/gpiolib.h
*
* struct gpio_device - 内部数据结构,表示一个 GPIO 控制器
*
* 注意: 此结构体是 gpiolib 内部私有的,外部驱动不可直接使用
*/
structgpio_device {
int id; // 控制器编号,对应 /dev/gpiochipN 中的 N
structdevicedev;// 内嵌的设备结构体
structcdevchrdev;// 字符设备,提供 /dev/gpiochipN 节点
structdevice *mockdev;// 模拟设备,用于 sysfs 展示
structmodule *owner;// 拥有此控制器的内核模块
structgpio_chip *chip;// 指向控制器驱动注册的 gpio_chip
structgpio_desc *descs;// 指向该控制器下所有 GPIO 描述符数组
int base; // 该控制器的起始 GPIO 全局编号
u16 ngpio; // 该控制器管理的 GPIO 数量
char *label; // 控制器标签名(用于调试)
void *data; // 控制器驱动的私有数据
structlist_headlist;// 链表节点,挂入全局 gpio_devices 链表
#ifdef CONFIG_PINCTRL
/*
* 如果启用了 CONFIG_PINCTRL,GPIO 控制器可以描述
* 它在 SoC 中服务的实际引脚范围,pinctrl 子系统
* 会利用此信息将对应引脚配置为 GPIO 功能
*/
structlist_headpin_ranges;// pinctrl 引脚范围链表
#endif
};
这是控制器驱动的核心结构,定义在<linux/gpio/driver.h>中,控制器驱动需要填充它的回调函数来实现具体硬件操作:
/**
* struct gpio_chip - abstract a GPIO controller
*
* 抽象一个GPIO控制器,每个物理GPIO控制器(SoC内置或外部扩展芯片)
* 都对应一个 gpio_chip 实例。
*/
structgpio_chip {
/* ===== 基本标识字段 ===== */
constchar *label; // 控制器标签名
structgpio_device *gpiodev;// 内部状态持有者(不透明)
structdevice *parent;// 可选的父设备
structfwnode_handle *fwnode;// 提供此控制器属性的可选fwnode
structmodule *owner;// 模块所有者
/* ===== 核心操作回调函数 ===== */
int (*request)(struct gpio_chip *gc, unsignedint offset);
void (*free)(struct gpio_chip *gc, unsignedint offset);
int (*get_direction)(struct gpio_chip *gc, unsignedint offset);
int (*direction_input)(struct gpio_chip *gc, unsignedint offset);
int (*direction_output)(struct gpio_chip *gc, unsignedint offset, int value);
int (*get)(struct gpio_chip *gc, unsignedint offset);
int (*get_multiple)(struct gpio_chip *gc, unsignedlong *mask, unsignedlong *bits);
int (*set)(struct gpio_chip *gc, unsignedint offset, int value);
int (*set_multiple)(struct gpio_chip *gc, unsignedlong *mask, unsignedlong *bits);
int (*set_config)(struct gpio_chip *gc, unsignedint offset, unsignedlong config);
int (*to_irq)(struct gpio_chip *gc, unsignedint offset);
void (*dbg_show)(struct seq_file *s, struct gpio_chip *gc);
/* ===== 可选回调 ===== */
int (*init_valid_mask)(struct gpio_chip *gc, unsignedlong *valid_mask, unsignedint ngpios);
int (*add_pin_ranges)(struct gpio_chip *gc);
int (*en_hw_timestamp)(struct gpio_chip *gc, u32 offset, unsignedlong flags);
int (*dis_hw_timestamp)(struct gpio_chip *gc, u32 offset, unsignedlong flags);
/* ===== 属性字段 ===== */
int base; // 起始GPIO编号(-1表示动态分配)
u16 ngpio; // 管理的GPIO数量
u16 offset; // 偏移量
constchar *const *names; // 可选的引脚名称数组
bool can_sleep; // 操作是否可能睡眠
/* ===== 中断支持(CONFIG_GPIOLIB_IRQCHIP) ===== */
#ifdef CONFIG_GPIOLIB_IRQCHIP
structgpio_irq_chipirq;
#endif
/* ===== 设备树支持(CONFIG_OF_GPIO) ===== */
#if defined(CONFIG_OF_GPIO)
unsignedint of_gpio_n_cells;
bool (*of_node_instance_match)(struct gpio_chip *gc, unsignedint i);
int (*of_xlate)(struct gpio_chip *gc, const struct of_phandle_args *gpiospec, u32 *flags);
#endif
};
基本标识字段详解
一个字符串,用于标识这个GPIO控制器的功能名称,通常是部件号或SoC IP模块名称(如 "gpio-imx6ul-1"、"pca953x")。这个标签会显示在 /sys/kernel/debug/gpio 的调试输出中,方便开发者识别。
指向gpiolib内部分配的 gpio_device 结构体,这是一个不透明的内部结构,控制器驱动不应该直接访问它。当控制器通过 gpiochip_add_data() 注册时,gpiolib会自动创建对应的 gpio_device 并回填此指针。
指向提供这些GPIO的父设备,通常是注册此控制器的 platform_device。设置此字段后,gpiolib会自动创建 /dev/gpiochipN 字符设备节点并挂到该父设备下。
提供此控制器属性的可选固件节点句柄。在设备树系统中通常由gpiolib自动从 parent->fwnode 获取,大多数驱动无需手动设置。
核心操作回调函数详解
回调函数是 gpio_chip 的灵魂,控制器驱动通过实现这些函数来告诉gpiolib如何操作硬件。gpiolib在收到上层调用时,会转发到对应的回调函数。
request — GPIO申请回调
int (*request)(struct gpio_chip *gc, unsignedint offset);
调用时机:
典型用途:
参数:
实际实现示例
staticintrockchip_gpio_request(struct gpio_chip *chip, unsigned offset)
{
/* 通知pinctrl子系统将此引脚配置为GPIO功能 */
return pinctrl_request_gpio(chip->base + offset);
}
free — GPIO释放回调
void (*free)(struct gpio_chip *gc, unsigned int offset);
调用时机:
典型用途:
实际实现示例
staticvoidrockchip_gpio_free(struct gpio_chip *chip, unsigned offset)
{
pinctrl_free_gpio(chip->base + offset);
}
get_direction — 查询方向回调
int (*get_direction)(struct gpio_chip *gc, unsignedint offset);
调用时机:
返回值:
direction_input — 配置为输入
int (*direction_input)(struct gpio_chip *gc, unsignedint offset);
调用时机:
典型实现(SoC内置GPIO):
staticintsamsung_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
{
structsamsung_pin_bank *bank = gpiochip_get_data(gc);
unsignedlong flags;
int ret;
spin_lock_irqsave(&bank->slock, flags);
ret = samsung_gpio_set_direction(gc, offset, true); /* true = 输入 */
spin_unlock_irqrestore(&bank->slock, flags);
return ret;
}
direction_output — 配置为输出
int (*direction_output)(struct gpio_chip *gc, unsignedint offset, int value);
调用时机:
当上层调用 gpiod_direction_output(desc, value) 时调用。
参数:
value 是逻辑值(gpiolib已经处理了Active Low转换),回调函数只需设置物理输出值。
staticintsamsung_gpio_direction_output(struct gpio_chip *gc, unsigned offset, int value)
{
structsamsung_pin_bank *bank = gpiochip_get_data(gc);
unsignedlong flags;
int ret;
spin_lock_irqsave(&bank->slock, flags);
samsung_gpio_set_value(gc, offset, value); /* 先设置输出值 */
ret = samsung_gpio_set_direction(gc, offset, false); /* 再设为输出方向 */
spin_unlock_irqrestore(&bank->slock, flags);
return ret;
}
get — 读取电平
int (*get)(struct gpio_chip *gc, unsignedint offset);
调用时机:
返回值:
staticintrockchip_gpio_get(struct gpio_chip *gc, unsigned offset)
{
structrockchip_pin_bank *bank = gc_to_pin_bank(gc);
u32 data;
/* 读取EXT_PORT寄存器,获取引脚实际输入电平 */
data = readl(bank->reg_base + GPIO_EXT_PORT);
data >>= offset;
data &= 1;
return data;
}
set — 设置输出电平
int (*set)(struct gpio_chip *gc, unsignedint offset, int value);
调用时机:
参数:
staticvoidrockchip_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
{
structrockchip_pin_bank *bank = gc_to_pin_bank(gc);
void __iomem *reg = bank->reg_base + GPIO_SWPORT_DR;
unsignedlong flags;
u32 data;
spin_lock_irqsave(&bank->slock, flags);
data = readl(reg); /* 读-改-写,不影响其他引脚 */
data &= ~BIT(offset);
if (value)
data |= BIT(offset);
writel(data, reg);
spin_unlock_irqrestore(&bank->slock, flags);
}
注意:set 回调中通常需要加自旋锁保护,因为读-改-写操作不是原子的,多个上下文可能同时操作同一个寄存器的不同位。
set_multiple — 批量设置输出电平
int (*set_multiple)(struct gpio_chip *gc, unsignedlong *mask, unsignedlong *bits);
调用时机:
参数说明:
mask:位掩码,标记要设置哪些引脚
bits:输入位图,存放要设置的值优势:
可以一次性修改整个寄存器,避免多次读-改-写操作。
是否必须:
set_config — 通用电气配置
int (*set_config)(struct gpio_chip *gc, unsignedint offset, unsignedlong config);
调用时机:
支持:典型实现:
很多控制器驱动直接将此回调指向 gpiochip_generic_config(),由pinctrl子系统处理。
/* Samsung驱动的实现 */
staticconststructgpio_chipsamsung_gpiolib_chip = {
.set_config = gpiochip_generic_config, /* 转发到pinctrl */
...
};
to_irq — GPIO转中断
int (*to_irq)(struct gpio_chip *gc, unsignedint offset);
staticintrockchip_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
{
structrockchip_pin_bank *bank = gc_to_pin_bank(gc);
unsignedint virq;
if (!bank->domain)
return -ENXIO;
/* 通过irq_domain创建GPIO到IRQ的映射 */
virq = irq_create_mapping(bank->domain, offset);
return (virq) ? : -ENXIO;
}
dbg_show — 自定义debugfs输出
void (*dbg_show)(struct seq_file *s, struct gpio_chip *gc);
调用时机:
典型用途:
一切从设备树(.dts文件)开始。SoC的GPIO控制器在设备树中被描述为一个节点:
/* 以 i.MX6UL 的 GPIO1 为例 */
gpio1: gpio@0209c000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x0209c0000x4000>; /* 寄存器物理基地址和大小 */
interrupts = <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_GPIO1>;
gpio-controller; /* 标记:我是一个GPIO控制器 */
#gpio-cells = <2>; /* 每个GPIO用2个cell描述(引脚号+标志) */
interrupt-controller; /* 同时也是一个中断控制器 */
#interrupt-cells = <2>;
status = "okay";
};
内核启动时,设备树解析器(drivers/of/fdt.c)会遍历所有节点,提取每个节点的 compatible 属性。
GPIO控制器挂在 platform 总线上。总线匹配逻辑(drivers/base/platform.c)会用设备树节点的 compatible 字符串,去匹配所有已注册的 platform_driver 的 of_match_table:
/* drivers/gpio/gpio-mxc.c — i.MX系列GPIO控制器驱动 */
staticconststructof_device_idmxc_gpio_dt_ids[] = {
{ .compatible = "fsl,imx1-gpio", .data = &mxc_gpio_devdata[IMX1_GPIO] },
{ .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devdata[IMX21_GPIO] },
{ .compatible = "fsl,imx35-gpio", .data = &mxc_gpio_devdata[IMX35_GPIO] },
{ .compatible = "fsl,imx6ul-gpio", .data = &mxc_gpio_devdata[IMX6UL_GPIO] },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mxc_gpio_dt_ids);
staticstructplatform_drivermxc_gpio_driver = {
.driver = {
.name = "gpio-mxc",
.of_match_table = mxc_gpio_dt_ids, /* ← 匹配表 */
},
.probe = mxc_gpio_probe, /* ← 匹配成功后的入口 */
};
设备树节点: compatible = "fsl,imx6ul-gpio"
│
▼
platform总线匹配逻辑
│
│ 遍历所有 platform_driver 的 of_match_table
│ mxc_gpio_driver.of_match_table 中包含 "fsl,imx6ul-gpio"
│
▼
匹配成功 → 内核自动调用 mxc_gpio_probe(pdev)
三、probe函数:填充 gpio_chip 并触发注册
mxc_gpio_probe() 是三个结构体串联的触发点。在这个函数中,控制器驱动工程师做两件事:填充 gpio_chip(控制层),然后调用注册接口。
staticintmxc_gpio_probe(struct platform_device *pdev)
{
structmxc_gpio_port *port;
port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
/* 从设备树获取硬件资源 */
port->base = devm_platform_ioremap_resource(pdev, 0); /* 映射寄存器 */
port->irq = platform_get_irq(pdev, 0); /* 获取中断号 */
/* ★ 填充 gpio_chip —— 告诉gpiolib"怎么操作我的硬件" */
port->gc.label = dev_name(&pdev->dev);
port->gc.parent = &pdev->dev;
port->gc.owner = THIS_MODULE;
port->gc.base = -1; /* 动态分配全局编号 */
port->gc.ngpio = 32; /* 本控制器有32个引脚 */
/* 核心操作回调 —— 直接操作硬件寄存器的函数 */
port->gc.request = gpiochip_generic_request;
port->gc.free = gpiochip_generic_free;
port->gc.get_direction = mxc_gpio_get_direction;
port->gc.direction_input = mxc_gpio_direction_input;
port->gc.direction_output = mxc_gpio_direction_output;
port->gc.get = mxc_gpio_get;
port->gc.set = mxc_gpio_set;
port->gc.to_irq = mxc_gpio_to_irq;
/* ★★ 注册!触发三个结构体的串联 ★★ */
return devm_gpiochip_add_data(&pdev->dev, &port->gc, port);
}
此时,只有 gpio_chip 存在,它还是"孤立的"——没有被任何内部结构持有。gpio_chip 只是控制器驱动在栈/堆上填充好的一个结构体,等待gpiolib核心层来"收容"它。
devm_gpiochip_add_data() 只是一个资源管理包装,最终调用链为:
devm_gpiochip_add_data()
→ gpiochip_add_data() (宏展开)
→ gpiochip_add_data_with_key() (真正的核心函数)
gpiochip_add_data_with_key() 定义在 drivers/gpio/gpiolib.c,是整个GPIO子系统的注册核心。
第1步:分配 gpio_device
gdev = kzalloc(sizeof(*gdev), GFP_KERNEL);
gpiolib核心层为这个GPIO控制器分配一个 gpio_device 实例。此时 gpio_device 是一个空壳。
第2步:建立 gpio_device ↔ gpio_chip 双向指针
gdev->chip = chip; /* gpio_device → gpio_chip */
chip->gpiodev = gdev; /* gpio_chip → gpio_device(反向指针) */
这是第一个关键连接。 gpio_device 持有 gpio_chip,gpio_chip 也反向持有 gpio_device。两者从此绑定。
第3步:初始化 gpio_device 的设备模型字段
gdev->dev.bus = &gpio_bus_type;
dev_set_name(&gdev->dev, "gpiochip%d", gdev->id);
device_initialize(&gdev->dev);
if (chip->parent) {
gdev->dev.parent = chip->parent;
gdev->dev.of_node = chip->parent->of_node;
}
给 gpio_device 内嵌的 struct device 设置名称(如 gpiochip0)、父设备、设备树节点。这就是为什么在 /sys/bus/gpio/devices/ 下能看到 gpiochip0 等节点。
第4步:分配 gpio_desc 数组
gdev->descs = kcalloc(chip->ngpio, sizeof(gdev->descs[0]), GFP_KERNEL);
gdev->ngpio = chip->ngpio;
根据 gpio_chip.ngpio(本例为32),一次性分配32个 gpio_desc,挂在 gpio_device.descs 下。这是第二个关键连接
第5步:分配全局GPIO编号并挂入全局链表
if (base < 0) { /* chip->base == -1,动态分配 */
base = gpiochip_find_base(chip->ngpio);
chip->base = base;
}
gdev->base = base;
gpiodev_add_to_list(gdev); /* 挂入全局链表 gpio_devices */
在全局GPIO编号空间中分配一段连续编号(如0~31),然后将 gpio_device 通过其 list 字段挂入全局链表 gpio_devices。
第6步:初始化每个 gpio_desc
for (i = 0; i < chip->ngpio; i++) {
structgpio_desc *desc = &gdev->descs[i];
desc->gdev = gdev; /* ★ 第三个关键连接:每个desc指回所属的gpio_device */
/* 通过 gpio_chip 的 get_direction 回调读取硬件初始方向 */
if (chip->get_direction) {
int dir = chip->get_direction(chip, i);
if (!dir)
set_bit(FLAG_IS_OUT, &desc->flags);
}
}
这是第三个关键连接。 每个 gpio_desc 都通过 gdev 指针指回所属的 gpio_device,而 gpio_device 又持有 gpio_chip。至此,三个结构体形成了完整的指针链。
第7步:设备树解析与字符设备创建
of_gpiochip_add(chip); /* 解析设备树中引用本控制器的GPIO属性 */
gpiochip_setup_dev(gdev); /* 创建 /dev/gpiochipN 字符设备 */
of_gpiochip_add() 会设置 of_xlate 回调,使gpiolib能正确翻译设备树中 <&gpio1 5 GPIO_ACTIVE_HIGH> 这样的GPIO说明符。
全局链表 gpio_devices
│
├──▶ gpio_device (gpiochip0)
│ │
│ ├── chip ──────────▶ gpio_chip
│ │ │
│ │ ├── .get() → 读寄存器
│ │ ├── .set() → 写寄存器
│ │ ├── .direction_*() → 改方向寄存器
│ │ ├── .to_irq() → 创建IRQ映射
│ │ ├── base = 0
│ │ ├── ngpio = 32
│ │ └── gpiodev ──────────┐ (反向指针)
│ │ │
│ ├── descs ──▶ gpio_desc[0] │
│ │ ├── gdev ────────────────────┘ (指回gdev)
│ │ ├── flags = 0
│ │ └── (未申请, 未使用)
│ │
│ │ gpio_desc[1]
│ │ ├── gdev ──→ (同上)
│ │ └── ...
│ │
│ │ ... (共32个desc)
│ │
│ │ gpio_desc[31]
│ │ ├── gdev ──→ (同上)
│ │ └── ...
│ │
│ ├── ngpio = 32
│ ├── base = 0
│ └── list ──→ 下一个 gpio_device
│
├──▶ gpio_device (gpiochip1)
│ ├── chip ──→ gpio_chip (另一个控制器)
│ ├── descs ──→ gpio_desc[0] ... gpio_desc[31]
│ └── ...
│
└──▶ ...
当消费层驱动(如LED驱动)通过设备树引用一个GPIO时:
/* 设备树中引用GPIO */
leds {
compatible = "gpio-leds";
led0 {
gpios = <&gpio1 5 GPIO_ACTIVE_LOW>; /* 引用gpio1的第5号引脚 */
};
};
消费层驱动调用 gpiod_get() 获取描述符:
/* LED驱动 probe 中 */
structgpio_desc *led_gpio = gpiod_get(&pdev->dev, NULL, GPIOD_OUT_LOW);
gpiolib内部通过设备树解析,找到 gpio1 对应的 gpio_device,再定位到 descs[5],返回这个 gpio_desc 的不透明指针给消费层。 之后消费层调用 gpiod_set_value(led_gpio, 1) 时,gpiolib内部正是沿着三个结构体的指针链完成操作:
/* gpiolib内部实现(简化) */
voidgpiod_set_value(struct gpio_desc *desc, int value)
{
/* 第1步:desc → gpio_device */
structgpio_device *gdev = desc->gdev;
/* 第2步:gpio_device → gpio_chip */
structgpio_chip *gc = gdev->chip;
/* 第3步:计算引脚在控制器内的硬件偏移 */
unsignedint offset = gpio_chip_hwgpio(desc);
/* 第4步:处理 Active Low */
if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
value = !value;
/* 第5步:调用 gpio_chip 的 set 回调,直达硬件 */
gc->set(gc, offset, value);
}
两次指针解引用(desc->gdev->chip),就从一个不透明的引脚描述符直达硬件操作函数。 这就是三个结构体串联的核心价值。
时间线 ──────────────────────────────────────────────────────────→
① 设备树解析
内核解析 .dtb,发现 compatible = "fsl,imx6ul-gpio" 的节点
创建 platform_device
│
② 总线匹配
platform总线用 compatible 匹配 of_match_table
匹配成功 → 调用 mxc_gpio_probe(pdev)
│
③ probe 函数执行
驱动从设备树获取寄存器地址、中断号、时钟
填充 gpio_chip(get/set/direction_* 等回调)
调用 devm_gpiochip_add_data()
│
④ gpiochip_add_data_with_key() 内部
┌─ kzalloc gpio_device ← gpio_device 诞生
├─ gdev->chip = chip ← gpio_device 持有 gpio_chip
├─ chip->gpiodev = gdev ← gpio_chip 反向持有 gpio_device
├─ kcalloc gpio_desc[32] ← gpio_desc 数组诞生
├─ for: desc[i].gdev = gdev ← 每个 gpio_desc 指回 gpio_device
├─ gpiodev_add_to_list(gdev) ← 挂入全局链表
├─ of_gpiochip_add(chip) ← 设备树GPIO翻译初始化
└─ gpiochip_setup_dev(gdev) ← 创建 /dev/gpiochipN
│
⑤ 注册完成
三个结构体串联就绪
消费层驱动可以开始使用 gpiod_*() API
│
⑥ 消费层使用
gpiod_get() → 拿到 gpio_desc *
gpiod_set_value(desc, 1)
→ desc->gdev->chip->set() → 操作硬件寄存器
设计一个完整的综合示例项目,场景是一个自定义平台设备 myboard,板上包含:
/*
* 文件: myboard-gpio.dtsi
* 说明: 自定义板级设备树片段,演示GPIO子系统新API的设备树配置
*
* 假设平台: 基于NXP i.MX6ULL或类似SoC
* gpio1: 控制器1, 引脚0~31
* gpio2: 控制器2, 引脚0~31
*/
/ {
/*
* 自定义平台设备节点
* 驱动通过 compatible = "myboard,gpio-demo" 匹配
*/
myboard_gpio_demo {
compatible = "myboard,gpio-demo";
/*
* LED GPIO配置:
* - 属性名 led-gpios, 驱动中 con_id = "led"
* - 红色LED: gpio1引脚5, 高电平点亮 (GPIO_ACTIVE_HIGH)
* - 绿色LED: gpio1引脚6, 高电平点亮
* - 蓝色LED: gpio1引脚7, 低电平点亮 (GPIO_ACTIVE_LOW)
*
* 演示API: devm_gpiod_get_index(), gpiod_set_value() (自动处理Active Low)
*/
led-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>, /* 红色LED */
<&gpio1 6 GPIO_ACTIVE_HIGH>, /* 绿色LED */
<&gpio1 7 GPIO_ACTIVE_LOW>; /* 蓝色LED (低电平有效) */
/*
* 按键GPIO配置:
* - 属性名 button-gpios, 驱动中 con_id = "button"
* - gpio2引脚3, 低电平有效 (按键按下接地)
* - 支持中断
*
* 演示API: devm_gpiod_get(), gpiod_to_irq(), gpiod_set_debounce(),
* gpiod_direction_input(), gpiod_is_active_low()
*/
button-gpios = <&gpio2 3 GPIO_ACTIVE_LOW>;
/*
* 可选蜂鸣器GPIO配置:
* - 属性名 buzzer-gpios, 驱动中 con_id = "buzzer"
* - gpio1引脚12, 高电平有效
* - 使用 _optional 变体, 如果设备树中删除此属性, 驱动不会报错
*
* 演示API: devm_gpiod_get_optional()
*/
buzzer-gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>;
/*
* 测试GPIO数组:
* - 属性名 test-gpios, 驱动中 con_id = "test"
* - 4个GPIO, 用于演示批量操作
*
* 演示API: devm_gpiod_get_array(), gpiod_set_array_value()
*/
test-gpios = <&gpio1 20 GPIO_ACTIVE_HIGH>,
<&gpio1 21 GPIO_ACTIVE_HIGH>,
<&gpio1 22 GPIO_ACTIVE_HIGH>,
<&gpio1 23 GPIO_ACTIVE_HIGH>;
};
};
/*
* GPIO控制器节点 (以i.MX6ULL为例, 通常已在SoC dtsi中定义)
* 这里仅展示控制器端需要的关键属性
*/
&gpio1 {
gpio-controller;
#gpio-cells = <2>; /* 第一个cell=引脚号, 第二个cell=标志 */
interrupt-controller;
#interrupt-cells = <2>;
};
&gpio2 {
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
/*
* 文件: myboard_gpio_demo.c
* 说明: GPIO子系统新API综合演示驱动
*
* 覆盖的消费端API:
* devm_gpiod_get / devm_gpiod_get_index / devm_gpiod_get_optional
* devm_gpiod_get_array / gpiod_count
* gpiod_direction_input / gpiod_direction_output
* gpiod_get_value / gpiod_set_value / gpiod_get_direction
* gpiod_get_value_cansleep / gpiod_set_value_cansleep
* gpiod_set_array_value
* gpiod_to_irq / gpiod_cansleep / gpiod_is_active_low
* gpiod_set_debounce / gpiod_set_consumer_name
* gpiod_toggle_active_low / gpiod_set_config
*
* 覆盖的提供端API:
* gpiochip_add_data / gpiochip_remove
* struct gpio_chip 各回调函数
*/
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/platform_device.h>
#include<linux/gpio/consumer.h>
#include<linux/gpio/driver.h>
#include<linux/interrupt.h>
#include<linux/cdev.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/delay.h>
#include<linux/of.h>
#include<linux/of_device.h>
#define DEVICE_NAME "myboard_gpio"
#define CLASS_NAME "myboard_gpio_class"
#define MAX_LEDS 3
#define MAX_TEST_GPIOS 8
/* ============================================================
* 用户空间与驱动之间的ioctl命令定义
* ============================================================ */
#define MYBOARD_IOC_MAGIC 'M'
#define MYBOARD_LED_ON _IOW(MYBOARD_IOC_MAGIC, 1, int) /* 点亮指定LED */
#define MYBOARD_LED_OFF _IOW(MYBOARD_IOC_MAGIC, 2, int) /* 熄灭指定LED */
#define MYBOARD_LED_TOGGLE _IOW(MYBOARD_IOC_MAGIC, 3, int) /* 翻转指定LED */
#define MYBOARD_BTN_READ _IOR(MYBOARD_IOC_MAGIC, 4, int) /* 读取按键状态 */
#define MYBOARD_BUZZER_ON _IO(MYBOARD_IOC_MAGIC, 5) /* 打开蜂鸣器 */
#define MYBOARD_BUZZER_OFF _IO(MYBOARD_IOC_MAGIC, 6) /* 关闭蜂鸣器 */
#define MYBOARD_TEST_SET _IOW(MYBOARD_IOC_MAGIC, 7, int) /* 批量设置测试GPIO */
#define MYBOARD_INFO _IOR(MYBOARD_IOC_MAGIC, 8, int) /* 获取GPIO信息 */
/* ============================================================
* GPIO信息结构体, 用于传递给用户空间
* ============================================================ */
structmyboard_gpio_info {
int led_count; /* LED数量 */
int button_present; /* 按键是否存在 */
int button_active_low; /* 按键是否低电平有效 */
int buzzer_present; /* 蜂鸣器是否存在 */
int buzzer_active_low; /* 蜂鸣器是否低电平有效 */
int test_gpio_count; /* 测试GPIO数量 */
int button_irq; /* 按键中断号 */
int button_cansleep; /* 按键GPIO是否可睡眠 */
};
/* ============================================================
* 驱动私有数据结构体
* ============================================================ */
structmyboard_gpio_data {
structdevice *dev;
/* LED相关 */
structgpio_desc *led_gpios[MAX_LEDS];
int led_count;
/* 按键相关 */
structgpio_desc *button_gpio;
int button_irq;
unsignedlong button_press_count;
/* 蜂鸣器 (可选) */
structgpio_desc *buzzer_gpio;
/* 测试GPIO数组 */
structgpio_descs *test_gpios;
/* 字符设备 */
dev_t devt;
structcdevcdev;
structclass *cls;
structdevice *char_dev;
/* 虚拟GPIO控制器 (演示提供端API) */
structgpio_chipvchip;
int vchip_base;
unsignedlong vchip_state; /* 虚拟GPIO的状态位图 */
};
staticstructmyboard_gpio_data *g_data;/* 全局指针, 供中断处理使用 */
/* ============================================================
* 虚拟GPIO控制器回调函数 (演示提供端API)
*
* 这个虚拟控制器模拟了4个GPIO引脚, 实际并不操作硬件,
* 仅用于演示 gpio_chip 的各回调函数如何实现
* ============================================================ */
/*
* request回调: GPIO被申请时调用
* 典型用途: 使能时钟、配置引脚复用等
*/
staticintvchip_request(struct gpio_chip *gc, unsignedint offset)
{
pr_info("myboard: vchip request gpio %u\n", offset);
return0;
}
/*
* free回调: GPIO被释放时调用
* 典型用途: 禁用时钟、恢复引脚默认状态等
*/
staticvoidvchip_free(struct gpio_chip *gc, unsignedint offset)
{
pr_info("myboard: vchip free gpio %u\n", offset);
}
/*
* get_direction回调: 查询引脚当前方向
* 返回: 0 = 输出 (GPIO_LINE_DIRECTION_OUT)
* 1 = 输入 (GPIO_LINE_DIRECTION_IN)
*
* 强烈推荐实现此回调, debugfs依赖它显示正确的方向信息
*/
staticintvchip_get_direction(struct gpio_chip *gc, unsignedint offset)
{
/* 虚拟控制器中所有引脚都是输出 */
return GPIO_LINE_DIRECTION_OUT;
}
/*
* direction_input回调: 配置引脚为输入
* 虚拟控制器不支持输入, 返回错误
*/
staticintvchip_direction_input(struct gpio_chip *gc, unsignedint offset)
{
pr_warn("myboard: vchip does not support input direction\n");
return -ENOTSUPP;
}
/*
* direction_output回调: 配置引脚为输出并设置初始值
* 参数: offset - 引脚编号, value - 初始逻辑值
*/
staticintvchip_direction_output(struct gpio_chip *gc, unsignedint offset, int value)
{
structmyboard_gpio_data *data = gpiochip_get_data(gc);
if (value)
set_bit(offset, &data->vchip_state);
else
clear_bit(offset, &data->vchip_state);
pr_info("myboard: vchip set output gpio %u = %d\n", offset, value);
return0;
}
/*
* get回调: 读取引脚电平
* 返回: 0 = 低电平, 1 = 高电平
*/
staticintvchip_get(struct gpio_chip *gc, unsignedint offset)
{
structmyboard_gpio_data *data = gpiochip_get_data(gc);
return test_bit(offset, &data->vchip_state) ? 1 : 0;
}
/*
* get_multiple回调: 批量读取多个引脚电平
* mask: 位掩码, 标记要读取哪些引脚
* bits: 输出位图, 存放读取结果
*
* 批量操作比逐个调用get()效率更高
*/
staticintvchip_get_multiple(struct gpio_chip *gc, unsignedlong *mask,
unsignedlong *bits)
{
structmyboard_gpio_data *data = gpiochip_get_data(gc);
int i;
for_each_set_bit(i, mask, gc->ngpio) {
if (test_bit(i, &data->vchip_state))
set_bit(i, bits);
else
clear_bit(i, bits);
}
return0;
}
/*
* set回调: 设置输出引脚的电平值
* 参数: offset - 引脚编号, value - 逻辑值
*/
staticvoidvchip_set(struct gpio_chip *gc, unsignedint offset, int value)
{
structmyboard_gpio_data *data = gpiochip_get_data(gc);
if (value)
set_bit(offset, &data->vchip_state);
else
clear_bit(offset, &data->vchip_state);
pr_info("myboard: vchip set gpio %u = %d\n", offset, value);
}
/*
* set_multiple回调: 批量设置多个输出引脚的电平
* mask: 位掩码, 标记要设置哪些引脚
* bits: 输入位图, 存放要设置的值
*
* 批量操作可以一次性写入硬件寄存器, 避免多次总线访问
*/
staticvoidvchip_set_multiple(struct gpio_chip *gc, unsignedlong *mask,
unsignedlong *bits)
{
structmyboard_gpio_data *data = gpiochip_get_data(gc);
int i;
for_each_set_bit(i, mask, gc->ngpio) {
if (test_bit(i, bits))
set_bit(i, &data->vchip_state);
else
clear_bit(i, &data->vchip_state);
}
}
/*
* set_config回调: 配置GPIO的电气属性
* config: 使用pinconf打包格式的配置参数
*
* 支持: 消抖、开漏/开源、上下拉等
* 不支持时返回 -ENOTSUPP
*/
staticintvchip_set_config(struct gpio_chip *gc, unsignedint offset,
unsignedlong config)
{
/* 虚拟控制器不支持任何电气配置 */
return -ENOTSUPP;
}
/*
* dbg_show回调: 自定义debugfs输出
* 在 /sys/kernel/debug/gpio 中显示额外信息
*/
staticvoidvchip_dbg_show(struct seq_file *s, struct gpio_chip *gc)
{
structmyboard_gpio_data *data = gpiochip_get_data(gc);
seq_printf(s, "virtual state: 0x%04lx\n", data->vchip_state);
}
/* ============================================================
* 按键中断处理函数
*
* 演示API:
* gpiod_get_value_cansleep() - 在中断线程中读取按键状态
* gpiod_is_active_low() - 判断有效电平
* ============================================================ */
staticirqreturn_tmyboard_button_isr(int irq, void *dev_id)
{
structmyboard_gpio_data *data = dev_id;
int val;
/*
* 在线程化中断上下文中, 可以使用cansleep版本的读取函数
* 对于I2C/SPI总线上的GPIO扩展芯片, 必须使用cansleep版本
*/
val = gpiod_get_value_cansleep(data->button_gpio);
/*
* 注意: gpiod_get_value_cansleep() 返回的是逻辑值
* 如果GPIO配置为GPIO_ACTIVE_LOW, 按下时返回1 (逻辑有效)
* 无需手动处理Active Low转换
*/
data->button_press_count++;
dev_info(data->dev, "Button pressed! count=%lu, logical_val=%d\n",
data->button_press_count, val);
return IRQ_HANDLED;
}
/* ============================================================
* 字符设备文件操作
* ============================================================ */
/*
* ioctl处理函数: 用户空间通过ioctl控制GPIO
*
* 演示API:
* gpiod_set_value() - 设置LED
* gpiod_get_value() - 读取按键
* gpiod_set_array_value() - 批量设置测试GPIO
* gpiod_is_active_low() - 查询Active Low属性
* gpiod_cansleep() - 查询是否可睡眠
* gpiod_get_direction() - 查询方向
* gpiod_toggle_active_low() - 切换Active Low
*/
staticlongmyboard_ioctl(struct file *file, unsignedint cmd, unsignedlong arg)
{
structmyboard_gpio_data *data = file->private_data;
int ret = 0;
int val;
switch (cmd) {
/* ---- LED控制 ---- */
case MYBOARD_LED_ON:
if (get_user(val, (int __user *)arg))
return -EFAULT;
if (val < 0 || val >= data->led_count)
return -EINVAL;
/*
* gpiod_set_value: 设置逻辑值
* 如果LED配置为GPIO_ACTIVE_LOW, val=1会自动驱动物理低电平
* 驱动无需关心硬件极性
*/
gpiod_set_value(data->led_gpios[val], 1);
dev_info(data->dev, "LED %d ON (logical)\n", val);
break;
case MYBOARD_LED_OFF:
if (get_user(val, (int __user *)arg))
return -EFAULT;
if (val < 0 || val >= data->led_count)
return -EINVAL;
gpiod_set_value(data->led_gpios[val], 0);
dev_info(data->dev, "LED %d OFF (logical)\n", val);
break;
case MYBOARD_LED_TOGGLE:
if (get_user(val, (int __user *)arg))
return -EFAULT;
if (val < 0 || val >= data->led_count)
return -EINVAL;
{
/*
* gpiod_get_value: 读取当前逻辑值
* 返回值已考虑Active Low属性
*/
int cur = gpiod_get_value(data->led_gpios[val]);
gpiod_set_value(data->led_gpios[val], !cur);
dev_info(data->dev, "LED %d toggled: %d -> %d\n", val, cur, !cur);
}
break;
/* ---- 按键读取 ---- */
case MYBOARD_BTN_READ:
if (!data->button_gpio)
return -ENODEV;
{
/*
* gpiod_cansleep: 判断GPIO操作是否可能睡眠
* 对于I2C/SPI扩展GPIO返回非零值, 必须使用cansleep版本
*/
int cansleep = gpiod_cansleep(data->button_gpio);
int direction = gpiod_get_direction(data->button_gpio);
if (cansleep)
val = gpiod_get_value_cansleep(data->button_gpio);
else
val = gpiod_get_value(data->button_gpio);
dev_info(data->dev, "Button: val=%d, cansleep=%d, direction=%s\n",
val, cansleep,
direction == GPIO_LINE_DIRECTION_IN ? "in" : "out");
}
if (put_user(val, (int __user *)arg))
return -EFAULT;
break;
/* ---- 蜂鸣器控制 ---- */
case MYBOARD_BUZZER_ON:
if (!data->buzzer_gpio)
return -ENODEV;
gpiod_set_value(data->buzzer_gpio, 1);
dev_info(data->dev, "Buzzer ON\n");
break;
case MYBOARD_BUZZER_OFF:
if (!data->buzzer_gpio)
return -ENODEV;
gpiod_set_value(data->buzzer_gpio, 0);
dev_info(data->dev, "Buzzer OFF\n");
break;
/* ---- 测试GPIO批量操作 ---- */
case MYBOARD_TEST_SET:
if (!data->test_gpios)
return -ENODEV;
if (get_user(val, (int __user *)arg))
return -EFAULT;
{
/*
* gpiod_set_array_value: 批量设置多个GPIO
* 参数:
* array_size - GPIO数量
* desc_array - 描述符数组
* array_info - 可选的优化信息 (NULL表示不使用)
* value_bitmap - 值位图, 每个bit对应一个GPIO的逻辑值
*
* 如果所有GPIO属于同一控制器, 底层会合并为一次寄存器写入
*/
unsignedlong bitmap = val;
ret = gpiod_set_array_value(data->test_gpios->ndescs,
data->test_gpios->desc,
NULL, /* array_info */
&bitmap);
dev_info(data->dev, "Test GPIOs set: bitmap=0x%lx, ret=%d\n",
bitmap, ret);
}
break;
/* ---- 获取GPIO信息 ---- */
case MYBOARD_INFO:
{
structmyboard_gpio_infoinfo = {0};
info.led_count = data->led_count;
info.button_present = (data->button_gpio != NULL) ? 1 : 0;
info.buzzer_present = (data->buzzer_gpio != NULL) ? 1 : 0;
info.test_gpio_count = data->test_gpios ? data->test_gpios->ndescs : 0;
info.button_irq = data->button_irq;
if (data->button_gpio) {
/*
* gpiod_is_active_low: 判断GPIO是否低电平有效
* 返回1表示Active Low, 0表示Active High
*/
info.button_active_low = gpiod_is_active_low(data->button_gpio);
info.button_cansleep = gpiod_cansleep(data->button_gpio);
}
if (data->buzzer_gpio)
info.buzzer_active_low = gpiod_is_active_low(data->buzzer_gpio);
if (copy_to_user((void __user *)arg, &info, sizeof(info)))
return -EFAULT;
}
break;
default:
ret = -ENOTTY;
break;
}
return ret;
}
staticintmyboard_open(struct inode *inode, struct file *file)
{
file->private_data = g_data;
return0;
}
staticintmyboard_release(struct inode *inode, struct file *file)
{
return0;
}
staticconststructfile_operationsmyboard_fops = {
.owner = THIS_MODULE,
.open = myboard_open,
.release = myboard_release,
.unlocked_ioctl = myboard_ioctl,
};
/* ============================================================
* 平台驱动probe函数
*
* 设备匹配后由内核自动调用, 完成所有GPIO的获取和初始化
* ============================================================ */
staticintmyboard_gpio_probe(struct platform_device *pdev)
{
structmyboard_gpio_data *data;
structdevice *dev = &pdev->dev;
int ret, i;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->dev = dev;
g_data = data;
/* ==========================================================
* 第一步: 获取LED GPIO (演示 devm_gpiod_get_index)
*
* 设备树属性: led-gpios = <&gpio1 5 ...>, <&gpio1 6 ...>, <&gpio1 7 ...>
* con_id = "led", 通过idx区分不同的LED
*
* devm_gpiod_get_index 参数说明:
* dev - 设备指针, 用于关联设备树节点
* con_id - 功能名称, 对应设备树中 "led-gpios" 的前缀
* idx - 索引, 从0开始
* flags - 初始方向和电气属性
*
* GPIOD_OUT_LOW: 配置为输出, 初始逻辑值低 (LED熄灭)
* 如果设备树中标记为GPIO_ACTIVE_LOW, 初始物理电平为高
* ========================================================== */
data->led_count = 0;
for (i = 0; i < MAX_LEDS; i++) {
/*
* 使用 _optional 变体: 如果索引超出设备树定义的数量,
* 返回NULL而不是错误, 方便灵活处理不同硬件配置
*/
data->led_gpios[i] = devm_gpiod_get_index(dev, "led", i, GPIOD_OUT_LOW);
if (IS_ERR(data->led_gpios[i])) {
ret = PTR_ERR(data->led_gpios[i]);
if (ret == -ENOENT)
break; /* 没有更多LED, 正常退出循环 */
dev_err(dev, "Failed to get LED GPIO %d: %d\n", i, ret);
return ret;
}
/*
* gpiod_set_consumer_name: 设置消费者标签
* 在 /sys/kernel/debug/gpio 中显示, 方便调试追踪
*/
{
char name[16];
snprintf(name, sizeof(name), "led:%d", i);
gpiod_set_consumer_name(data->led_gpios[i], name);
}
data->led_count++;
dev_info(dev, "LED %d: active_low=%d\n", i,
gpiod_is_active_low(data->led_gpios[i]));
}
dev_info(dev, "Found %d LEDs\n", data->led_count);
/* ==========================================================
* 第二步: 获取按键GPIO (演示 devm_gpiod_get)
*
* 设备树属性: button-gpios = <&gpio2 3 GPIO_ACTIVE_LOW>
* con_id = "button"
*
* GPIOD_IN: 配置为输入模式
* 由于是GPIO_ACTIVE_LOW, 按键按下时 gpiod_get_value() 返回1
* ========================================================== */
data->button_gpio = devm_gpiod_get(dev, "button", GPIOD_IN);
if (IS_ERR(data->button_gpio)) {
ret = PTR_ERR(data->button_gpio);
dev_err(dev, "Failed to get button GPIO: %d\n", ret);
return ret;
}
/*
* gpiod_set_consumer_name: 标记按键GPIO的用途
*/
gpiod_set_consumer_name(data->button_gpio, "user-button");
/*
* gpiod_set_debounce: 设置消抖时间 (单位: 微秒)
* 20ms消抖, 防止机械按键抖动产生多次中断
* 如果控制器不支持消抖, 返回 -ENOTSUPP, 不影响后续功能
*/
ret = gpiod_set_debounce(data->button_gpio, 20000);
if (ret)
dev_warn(dev, "Debounce not supported: %d (will use software debounce)\n", ret);
/*
* gpiod_set_config: 通用配置接口
* 可以配置消抖、开漏、上下拉等, 使用pinconf打包格式
* 这里演示配置10K上拉 (如果控制器支持)
*
* 注意: 实际使用中需要包含 <linux/pinctrl/pinconf-generic.h>
* 并使用 PIN_CONFIG_BIAS_PULL_UP 等宏来打包配置参数
*/
/*
* unsigned long config = pinconf_to_config_packed(PIN_CONFIG_BIAS_PULL_UP, 10000);
* ret = gpiod_set_config(data->button_gpio, config);
*/
/*
* gpiod_to_irq: 将GPIO描述符转换为IRQ编号
* 返回正数IRQ编号表示成功
* 返回负错误码表示不支持中断
*/
data->button_irq = gpiod_to_irq(data->button_gpio);
if (data->button_irq < 0) {
dev_warn(dev, "Button GPIO does not support IRQ: %d\n", data->button_irq);
data->button_irq = 0;
} else {
/*
* request_threaded_irq: 申请线程化中断
* - handler=NULL: 不使用硬中断处理函数
* - thread_fn=myboard_button_isr: 中断线程处理函数
* - IRQF_TRIGGER_FALLING: 下降沿触发 (按键按下)
* - IRQF_ONESHOT: 保证线程执行期间中断保持屏蔽
*/
ret = request_threaded_irq(data->button_irq,
NULL, /* 硬中断处理 */
myboard_button_isr, /* 线程化处理 */
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"myboard-button",
data);
if (ret) {
dev_err(dev, "Failed to request IRQ %d: %d\n", data->button_irq, ret);
data->button_irq = 0;
} else {
dev_info(dev, "Button IRQ %d registered\n", data->button_irq);
}
}
/* ==========================================================
* 第三步: 获取可选蜂鸣器GPIO (演示 devm_gpiod_get_optional)
*
* 设备树属性: buzzer-gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>
*
* _optional 变体的特点:
* - 如果设备树中定义了该属性, 行为与 devm_gpiod_get() 完全一致
* - 如果设备树中没有定义该属性, 返回NULL (而不是-ENOENT错误)
* - 其他错误 (如-ENOMEM) 仍然正常返回
*
* 适用场景: 某些硬件版本有蜂鸣器, 某些没有
* ========================================================== */
data->buzzer_gpio = devm_gpiod_get_optional(dev, "buzzer", GPIOD_OUT_LOW);
if (IS_ERR(data->buzzer_gpio)) {
ret = PTR_ERR(data->buzzer_gpio);
dev_err(dev, "Failed to get buzzer GPIO: %d\n", ret);
return ret;
}
if (data->buzzer_gpio) {
gpiod_set_consumer_name(data->buzzer_gpio, "buzzer");
dev_info(dev, "Buzzer GPIO found, active_low=%d\n",
gpiod_is_active_low(data->buzzer_gpio));
} else {
dev_info(dev, "No buzzer GPIO defined in device tree\n");
}
/* ==========================================================
* 第四步: 获取测试GPIO数组 (演示 devm_gpiod_get_array)
*
* 设备树属性: test-gpios = <&gpio1 20 ...>, <&gpio1 21 ...>, ...
*
* devm_gpiod_get_array 一次性获取所有GPIO, 返回:
* struct gpio_descs {
* unsigned int ndescs; // GPIO数量
* struct gpio_desc *desc[]; // 描述符柔性数组
* };
*
* 批量操作优势: 如果所有GPIO属于同一控制器,
* gpiod_set_array_value() 可以合并为一次寄存器写入
* ========================================================== */
data->test_gpios = devm_gpiod_get_array(dev, "test", GPIOD_OUT_LOW);
if (IS_ERR(data->test_gpios)) {
ret = PTR_ERR(data->test_gpios);
dev_warn(dev, "Failed to get test GPIO array: %d (non-fatal)\n", ret);
data->test_gpios = NULL;
} elseif (data->test_gpios) {
dev_info(dev, "Test GPIO array: %d GPIOs\n", data->test_gpios->ndescs);
/*
* gpiod_count: 查询设备关联的某个功能的GPIO数量
* 与 data->test_gpios->ndescs 结果一致
*/
int count = gpiod_count(dev, "test");
dev_info(dev, "gpiod_count(\"test\") = %d\n", count);
}
/* ==========================================================
* 第五步: 注册虚拟GPIO控制器 (演示提供端API)
*
* 这个虚拟控制器模拟4个GPIO引脚, 用于演示:
* - gpio_chip 结构体的填充
* - gpiochip_add_data() 注册
* - 各回调函数的实现
*
* 实际项目中, 提供端API通常用于:
* - SoC内部的GPIO控制器驱动
* - I2C/SPI GPIO扩展芯片驱动 (如PCA953x, MCP23S17)
* - FPGA中的软核GPIO控制器
* ========================================================== */
data->vchip.label = "myboard-vchip";
data->vchip.parent = dev;
data->vchip.owner = THIS_MODULE;
/* 核心回调函数 */
data->vchip.request = vchip_request;
data->vchip.free = vchip_free;
data->vchip.get_direction = vchip_get_direction;
data->vchip.direction_input = vchip_direction_input;
data->vchip.direction_output = vchip_direction_output;
data->vchip.get = vchip_get;
data->vchip.get_multiple = vchip_get_multiple;
data->vchip.set = vchip_set;
data->vchip.set_multiple = vchip_set_multiple;
data->vchip.set_config = vchip_set_config;
data->vchip.dbg_show = vchip_dbg_show;
/* 属性配置 */
data->vchip.base = -1; /* -1表示动态分配GPIO编号 */
data->vchip.ngpio = 4; /* 4个虚拟GPIO */
data->vchip.can_sleep = false; /* 纯内存操作, 不会睡眠 */
/*
* gpiochip_add_data: 向gpiolib注册GPIO控制器
* data指针会在回调函数中通过 gpiochip_get_data() 取回
*
* 注册成功后, 在 /sys/kernel/debug/gpio 中可以看到:
* gpiochipN: GPIOs xxx-xxx, parent: platform/myboard_gpio_demo, myboard-vchip:
*
* 推荐使用 devm_gpiochip_add_data() 自动管理生命周期
*/
ret = devm_gpiochip_add_data(dev, &data->vchip, data);
if (ret) {
dev_err(dev, "Failed to register virtual gpiochip: %d\n", ret);
return ret;
}
dev_info(dev, "Virtual GPIO chip registered (4 GPIOs)\n");
/* ==========================================================
* 第六步: 创建字符设备, 暴露用户空间接口
* ========================================================== */
ret = alloc_chrdev_region(&data->devt, 0, 1, DEVICE_NAME);
if (ret) {
dev_err(dev, "Failed to alloc chrdev: %d\n", ret);
return ret;
}
cdev_init(&data->cdev, &myboard_fops);
data->cdev.owner = THIS_MODULE;
ret = cdev_add(&data->cdev, data->devt, 1);
if (ret) {
dev_err(dev, "Failed to add cdev: %d\n", ret);
goto err_unreg_chrdev;
}
data->cls = class_create(CLASS_NAME);
if (IS_ERR(data->cls)) {
ret = PTR_ERR(data->cls);
goto err_del_cdev;
}
data->char_dev = device_create(data->cls, dev, data->devt, NULL, DEVICE_NAME);
if (IS_ERR(data->char_dev)) {
ret = PTR_ERR(data->char_dev);
goto err_del_class;
}
dev_info(dev, "Character device /dev/%s created\n", DEVICE_NAME);
/* ==========================================================
* 第七步: 初始状态演示
* ========================================================== */
/* 逐个点亮LED, 演示 gpiod_set_value 的Active Low自动处理 */
for (i = 0; i < data->led_count; i++) {
gpiod_set_value(data->led_gpios[i], 1); /* 逻辑高 = 点亮 */
dev_info(dev, "LED %d: logical=1, active_low=%d\n",
i, gpiod_is_active_low(data->led_gpios[i]));
}
/* 批量设置测试GPIO, 演示 gpiod_set_array_value */
if (data->test_gpios && data->test_gpios->ndescs > 0) {
unsignedlong bitmap = 0x0A; /* 二进制: 1010, 即GPIO 1和3为高 */
ret = gpiod_set_array_value(data->test_gpios->ndescs,
data->test_gpios->desc,
NULL,
&bitmap);
dev_info(dev, "Test GPIOs initial set: bitmap=0x%lx, ret=%d\n",
bitmap, ret);
}
platform_set_drvdata(pdev, data);
dev_info(dev, "Probe completed successfully\n");
return0;
err_del_class:
class_destroy(data->cls);
err_del_cdev:
cdev_del(&data->cdev);
err_unreg_chrdev:
unregister_chrdev_region(data->devt, 1);
return ret;
}
/* ============================================================
* 平台驱动remove函数
*
* devm_系列API会自动释放大部分资源, 这里只需处理
* 非devm管理的资源
* ============================================================ */
staticintmyboard_gpio_remove(struct platform_device *pdev)
{
structmyboard_gpio_data *data = platform_get_drvdata(pdev);
/* 释放中断 */
if (data->button_irq)
free_irq(data->button_irq, data);
/*
* 注意: 以下资源由devm_自动管理, 无需手动释放:
* - GPIO描述符 (devm_gpiod_get系列)
* - GPIO控制器 (devm_gpiochip_add_data)
* - 内存 (devm_kzalloc)
*
* 但字符设备需要手动清理 (未使用devm版本)
*/
device_destroy(data->cls, data->devt);
class_destroy(data->cls);
cdev_del(&data->cdev);
unregister_chrdev_region(data->devt, 1);
dev_info(data->dev, "Driver removed, button was pressed %lu times\n",
data->button_press_count);
return0;
}
/* ============================================================
* 设备树匹配表
* ============================================================ */
staticconststructof_device_idmyboard_gpio_of_match[] = {
{ .compatible = "myboard,gpio-demo" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, myboard_gpio_of_match);
/* ============================================================
* 平台驱动结构体
* ============================================================ */
staticstructplatform_drivermyboard_gpio_driver = {
.driver = {
.name = "myboard-gpio-demo",
.of_match_table = myboard_gpio_of_match,
.owner = THIS_MODULE,
},
.probe = myboard_gpio_probe,
.remove = myboard_gpio_remove,
};
module_platform_driver(myboard_gpio_driver);
MODULE_AUTHOR("Your Name <your.email@example.com>");
MODULE_DESCRIPTION("GPIO Subsystem New API Demo Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:myboard-gpio-demo");
/*
* 文件: myboard_gpio_app.c
* 说明: 用户空间GPIO控制应用程序
*
* 编译: gcc -o myboard_gpio_app myboard_gpio_app.c
* 运行: ./myboard_gpio_app
*
* 同时演示libgpiod库的使用 (可选)
* 编译libgpiod版本: gcc -o myboard_gpio_app myboard_gpio_app.c -lgpiod
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/ioctl.h>
#include<signal.h>
/* ioctl命令定义, 必须与驱动中一致 */
#define MYBOARD_IOC_MAGIC 'M'
#define MYBOARD_LED_ON _IOW(MYBOARD_IOC_MAGIC, 1, int)
#define MYBOARD_LED_OFF _IOW(MYBOARD_IOC_MAGIC, 2, int)
#define MYBOARD_LED_TOGGLE _IOW(MYBOARD_IOC_MAGIC, 3, int)
#define MYBOARD_BTN_READ _IOR(MYBOARD_IOC_MAGIC, 4, int)
#define MYBOARD_BUZZER_ON _IO(MYBOARD_IOC_MAGIC, 5)
#define MYBOARD_BUZZER_OFF _IO(MYBOARD_IOC_MAGIC, 6)
#define MYBOARD_TEST_SET _IOW(MYBOARD_IOC_MAGIC, 7, int)
#define MYBOARD_INFO _IOR(MYBOARD_IOC_MAGIC, 8, int)
/* GPIO信息结构体, 必须与驱动中一致 */
structmyboard_gpio_info {
int led_count;
int button_present;
int button_active_low;
int buzzer_present;
int buzzer_active_low;
int test_gpio_count;
int button_irq;
int button_cansleep;
};
staticint fd = -1;
staticvolatileint running = 1;
/* 信号处理: Ctrl+C优雅退出 */
staticvoidsignal_handler(int sig)
{
running = 0;
}
/* ============================================================
* 方式一: 通过ioctl接口控制GPIO (使用自定义驱动)
* ============================================================ */
staticvoiddemo_ioctl_interface(void)
{
int ret, val;
structmyboard_gpio_infoinfo;
printf("\n========== ioctl接口演示 ==========\n\n");
/* 获取GPIO信息 */
ret = ioctl(fd, MYBOARD_INFO, &info);
if (ret < 0) {
perror("ioctl MYBOARD_INFO");
return;
}
printf("GPIO信息:\n");
printf(" LED数量: %d\n", info.led_count);
printf(" 按键存在: %s\n", info.button_present ? "是" : "否");
printf(" 按键Active Low: %s\n", info.button_active_low ? "是" : "否");
printf(" 按键可睡眠: %s\n", info.button_cansleep ? "是" : "否");
printf(" 按键中断号: %d\n", info.button_irq);
printf(" 蜂鸣器存在: %s\n", info.buzzer_present ? "是" : "否");
printf(" 蜂鸣器Active Low: %s\n", info.buzzer_active_low ? "是" : "否");
printf(" 测试GPIO数量: %d\n", info.test_gpio_count);
printf("\n");
/* LED逐个点亮演示 */
printf("--- LED逐个点亮 ---\n");
for (int i = 0; i < info.led_count; i++) {
printf(" 点亮LED %d...\n", i);
ioctl(fd, MYBOARD_LED_ON, &i);
usleep(500000); /* 500ms */
printf(" 熄灭LED %d...\n", i);
ioctl(fd, MYBOARD_LED_OFF, &i);
}
/* LED流水灯效果 */
printf("\n--- LED流水灯 (3轮) ---\n");
for (int round = 0; round < 3; round++) {
for (int i = 0; i < info.led_count; i++) {
ioctl(fd, MYBOARD_LED_ON, &i);
usleep(200000); /* 200ms */
ioctl(fd, MYBOARD_LED_OFF, &i);
}
}
/* LED翻转演示 */
printf("\n--- LED翻转 ---\n");
for (int i = 0; i < info.led_count; i++) {
printf(" 翻转LED %d\n", i);
ioctl(fd, MYBOARD_LED_TOGGLE, &i);
}
usleep(500000);
for (int i = 0; i < info.led_count; i++) {
ioctl(fd, MYBOARD_LED_TOGGLE, &i); /* 恢复 */
}
/* 按键读取演示 */
if (info.button_present) {
printf("\n--- 按键读取 ---\n");
ioctl(fd, MYBOARD_BTN_READ, &val);
printf(" 按键当前状态: %d (1=按下, 0=释放)\n", val);
}
/* 蜂鸣器演示 */
if (info.buzzer_present) {
printf("\n--- 蜂鸣器演示 ---\n");
printf(" 蜂鸣器开启 500ms...\n");
ioctl(fd, MYBOARD_BUZZER_ON);
usleep(500000);
ioctl(fd, MYBOARD_BUZZER_OFF);
printf(" 蜂鸣器关闭\n");
}
/* 测试GPIO批量操作演示 */
if (info.test_gpio_count > 0) {
printf("\n--- 测试GPIO批量操作 ---\n");
for (int pattern = 0; pattern < (1 << info.test_gpio_count); pattern++) {
ioctl(fd, MYBOARD_TEST_SET, &pattern);
printf(" 设置bitmap=0x%X\n", pattern);
usleep(200000);
}
}
}
/* ============================================================
* 方式二: 通过libgpiod库直接操作GPIO (不依赖自定义驱动)
*
* libgpiod是Linux推荐的用户空间GPIO操作库,
* 通过 /dev/gpiochipN 字符设备接口访问GPIO
*
* 注意: 使用libgpiod时需要先关闭自定义驱动, 避免GPIO冲突
* ============================================================ */
#ifdef USE_LIBGPIOD
#include<gpiod.h>
staticvoiddemo_libgpiod_interface(void)
{
structgpiod_chip *chip;
structgpiod_line *line;
structgpiod_line_bulkbulk;
constchar *chip_name = "gpiochip0";
int ret;
printf("\n========== libgpiod接口演示 ==========\n\n");
/*
* gpiod_chip_open: 打开GPIO控制器字符设备
* 对应内核中的 /dev/gpiochipN
*/
chip = gpiod_chip_open_by_name(chip_name);
if (!chip) {
perror("gpiod_chip_open_by_name");
return;
}
printf("已打开 %s\n", chip_name);
/*
* gpiod_chip_get_line: 获取指定偏移的GPIO线
* 对应设备树中 &gpio1 5 的偏移5
*/
line = gpiod_chip_get_line(chip, 5);
if (!line) {
perror("gpiod_chip_get_line");
goto close_chip;
}
/*
* gpiod_line_request_output: 请求GPIO为输出模式
* 参数: 消费者名称, 初始值
*/
ret = gpiod_line_request_output(line, "myboard_app", 0);
if (ret < 0) {
perror("gpiod_line_request_output");
goto close_chip;
}
/* 闪烁LED */
printf("通过libgpiod控制GPIO 5闪烁...\n");
for (int i = 0; i < 5 && running; i++) {
gpiod_line_set_value(line, 1);
printf(" GPIO 5 = HIGH\n");
usleep(500000);
gpiod_line_set_value(line, 0);
printf(" GPIO 5 = LOW\n");
usleep(500000);
}
/* 释放GPIO */
gpiod_line_release(line);
/*
* 批量操作演示: 请求多个GPIO
*/
printf("\n--- 批量操作演示 ---\n");
{
structgpiod_line *lines[4];
int values[4] = {1, 0, 1, 0};
unsignedint offsets[4] = {20, 21, 22, 23};
for (int i = 0; i < 4; i++) {
lines[i] = gpiod_chip_get_line(chip, offsets[i]);
if (!lines[i]) {
perror("gpiod_chip_get_line (bulk)");
goto close_chip;
}
}
/* 批量请求为输出 */
ret = gpiod_line_request_bulk_output(lines, "myboard_bulk", values);
if (ret < 0) {
perror("gpiod_line_request_bulk_output");
goto close_chip;
}
printf("批量设置: GPIO 20=1, 21=0, 22=1, 23=0\n");
usleep(1000000);
/* 批量释放 */
gpiod_line_release_bulk(lines);
}
/*
* 中断/事件监听演示: 监听按键GPIO的边沿事件
*/
printf("\n--- 中断事件监听演示 (监听GPIO 3, 按Ctrl+C退出) ---\n");
line = gpiod_chip_get_line(chip, 3);
if (line) {
/*
* gpiod_line_request_falling_edge_events: 请求下降沿事件
* 对应内核中的 gpiod_to_irq() + request_irq()
*/
ret = gpiod_line_request_falling_edge_events(line, "myboard_event");
if (ret == 0) {
structgpiod_line_eventevent;
while (running) {
/*
* gpiod_line_event_wait: 等待事件, 超时1秒
* 阻塞式等待, 类似内核中的中断等待
*/
ret = gpiod_line_event_wait(line,
&(struct timespec){.tv_sec = 1, .tv_nsec = 0},
&event);
if (ret == 0) {
printf(" 检测到下降沿事件! sec=%ld nsec=%ld\n",
event.ts.tv_sec, event.ts.tv_nsec);
}
}
gpiod_line_release(line);
}
}
close_chip:
gpiod_chip_close(chip);
printf("已关闭 %s\n", chip_name);
}
#endif/* USE_LIBGPIOD */
/* ============================================================
* 主函数
* ============================================================ */
intmain(int argc, char *argv[])
{
signal(SIGINT, signal_handler);
printf("====================================\n");
printf(" GPIO子系统新API用户空间演示程序\n");
printf("====================================\n");
/* 打开字符设备 */
fd = open("/dev/myboard_gpio", O_RDWR);
if (fd < 0) {
perror("open /dev/myboard_gpio");
printf("提示: 请确保已加载myboard_gpio_demo内核模块\n");
return-1;
}
printf("已打开 /dev/myboard_gpio (fd=%d)\n", fd);
/* 方式一: 通过ioctl接口 */
demo_ioctl_interface();
#ifdef USE_LIBGPIOD
/* 方式二: 通过libgpiod库 (编译时加 -DUSE_LIBGPIOD -lgpiod) */
printf("\n注意: 使用libgpiod前请先卸载自定义驱动 (rmmod myboard_gpio_demo)\n");
printf("是否继续libgpiod演示? (y/n): ");
char c = getchar();
if (c == 'y' || c == 'Y') {
close(fd);
fd = -1;
demo_libgpiod_interface();
}
#endif
if (fd >= 0)
close(fd);
printf("\n程序退出\n");
return0;
}
devm_gpiod_get() | ||
devm_gpiod_get_index() | ||
devm_gpiod_get_optional() | ||
devm_gpiod_get_array() | ||
gpiod_count() | ||
gpiod_direction_input() | ||
gpiod_direction_output() | ||
gpiod_get_value() | ||
gpiod_set_value() | ||
gpiod_get_value_cansleep() | ||
gpiod_set_value_cansleep() | ||
gpiod_set_array_value() | ||
gpiod_to_irq() | ||
gpiod_cansleep() | ||
gpiod_is_active_low() | ||
gpiod_set_debounce() | ||
gpiod_set_consumer_name() | ||
gpiod_get_direction() | ||
gpiod_toggle_active_low() | ||
gpiod_set_config() |
devm_gpiochip_add_data() | ||
gpiochip_get_data() | ||
struct gpio_chip | ||
.request | ||
.free | ||
.get_direction | ||
.direction_input | ||
.direction_output | ||
.get | ||
.get_multiple | ||
.set | ||
.set_multiple | ||
.set_config | ||
.dbg_show |
与GPIO子系统不同,pinctrl与GPIO的协作从设备树中两个独立的控制器节点开始——一个描述引脚控制器(pinctrl),一个描述GPIO控制器(gpio),它们通过 gpio-ranges 属性建立映射关系。
/* 以 Rockchip RK3568 为例 */
/* ① Pinctrl控制器节点 —— 描述所有引脚的复用和电气配置能力 */
pinctrl: pinctrl {
compatible = "rockchip,rk3568-pinctrl";
rockchip,grf = <&grf>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
/* 引脚配置子节点(供其他设备引用) */
i2c3_pins: i2c3-pins {
rockchip,pins =
<3 RK_PB6 1 &pcfg_pull_up>, /* GPIO3_PB6 → I2C3_SDA */
<3 RK_PB7 1 &pcfg_pull_up>; /* GPIO3_PB7 → I2C3_SCL */
};
uart1_pins: uart1-pins {
rockchip,pins =
<1 RK_PB0 1 &pcfg_pull_up>, /* GPIO1_PB0 → UART1_TX */
<1 RK_PB1 1 &pcfg_pull_up>; /* GPIO1_PB1 → UART1_RX */
};
/* ② GPIO控制器节点 —— 作为pinctrl的子节点存在 */
gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x00xfdd600000x00x100>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_GPIO0>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
/* ★★★ 关键:建立GPIO编号到Pinctrl引脚编号的映射 ★★★ */
gpio-ranges = <&pinctrl 0032>;
/* ↑ ↑ ↑ ↑
* │ │ │ └── 数量:32个引脚
* │ │ └───── pinctrl侧起始引脚编号
* │ └──────── GPIO侧起始偏移
* └───────────── pinctrl控制器引用
*
* 含义:本GPIO控制器的第0~31号引脚,
* 对应pinctrl中第0~31号引脚
*/
};
gpio1: gpio@fe740000 {
compatible = "rockchip,gpio-bank";
reg = <0x00xfe7400000x00x100>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 03232>;
/* 本GPIO控制器的第0~31号引脚,
* 对应pinctrl中第32~63号引脚 */
};
};
设备树中的关键信息:
gpio-controller | ||
#gpio-cells = <2> | ||
gpio-ranges | ||
compatible = "rockchip,rk3568-pinctrl" |
Pinctrl控制器和GPIO控制器是两个独立的平台设备,各自有独立的驱动和probe函数,各自注册到各自的子系统。但它们必须在GPIO被使用前完成协作准备。
时间线 ──────────────────────────────────────────────────────────→
① Pinctrl控制器 probe 先执行
pinctrl_probe()
│
├── 填充 pinctrl_desc
│ ├── pins[] — 所有物理引脚描述
│ ├── pctlops — 引脚组枚举操作
│ ├── pmxops — 复用选择操作(含gpio_request_enable)
│ └── confops — 电气配置操作
│
├── devm_pinctrl_register()
│ │
│ ├── 分配 pinctrl_dev
│ ├── 注册到全局pinctrl_dev_list链表
│ └── 解析设备树中的引脚配置节点
│
└── pinctrl注册完成 ✓
│
│ (pinctrl必须先于GPIO注册,否则GPIO申请会失败)
▼
② GPIO控制器 probe 后执行
gpio_probe()
│
├── 填充 gpio_chip
│ ├── .request = gpiochip_generic_request ← 关键!
│ ├── .free = gpiochip_generic_free ← 关键!
│ ├── .get / .set / .direction_*
│ └── ngpio = 32
│
├── devm_gpiochip_add_data()
│ │
│ ├── 分配 gpio_device
│ ├── 分配 gpio_desc 数组
│ ├── 串联三个结构体
│ │
│ ├── of_gpiochip_add_pin_range() ← 关键!
│ │ │
│ │ ├── 解析设备树中的 gpio-ranges 属性
│ │ ├── 找到对应的 pinctrl_dev
│ │ └── 调用 gpiochip_add_pin_range()
│ │ │
│ │ └── 在pinctrl_dev中创建
│ │ pinctrl_gpio_range 结构体
│ │ 挂入 pinctrl_dev->gpio_ranges 链表
│ │
│ └── 创建 /dev/gpiochipN
│
└── GPIO注册完成 ✓
│
│ 此时两条链路已汇合:
│ GPIO编号 ←→ Pinctrl引脚编号 映射已建立
▼
③ 消费层驱动可以安全地使用GPIO
gpio-ranges 是连接两个子系统的唯一纽带。当GPIO控制器注册时,gpiolib会自动解析它:
设备树中的声明
gpio-ranges = <&pinctrl 03232>;
四个参数的含义:
<&pinctrl 03232>
│ │ │ │
│ │ │ └── npins: 映射的引脚数量(32个)
│ │ └────── pin_base: pinctrl侧的起始引脚编号(32号)
│ └────────── base: GPIO侧的起始偏移(0号)
└────────────────── pinctrl控制器引用
这个GPIO控制器的第0~31号引脚,对应pinctrl中第32~63号引脚。
内核解析调用链
/* 注册GPIO控制器时自动触发 */
devm_gpiochip_add_data()
→ gpiochip_add_data_with_key()
→ of_gpiochip_add()
→ of_gpiochip_add_pin_range()
of_gpiochip_add_pin_range() 的核心逻辑:
/* drivers/gpio/gpiolib-of.c */
staticintof_gpiochip_add_pin_range(struct gpio_chip *chip)
{
structdevice_node *np = chip->of_node;
int index = 0;
/* 循环解析 gpio-ranges 属性中的每一组映射 */
for (;;) {
structof_phandle_argspinspec;
structpinctrl_dev *pctldev;
int ret;
/* 从设备树读取一组 gpio-ranges */
ret = of_parse_phandle_with_fixed_args(np, "gpio-ranges",
3, index, &pinspec);
if (ret)
break; /* 没有更多映射了 */
/* 根据 &pinctrl 引用找到对应的 pinctrl_dev */
pctldev = of_pinctrl_get(pinspec.np);
/* 建立映射关系:
* chip->base + pinspec.args[0] → GPIO侧起始编号
* pinspec.args[1] → pinctrl侧起始引脚号
* pinspec.args[2] → 引脚数量
*/
gpiochip_add_pin_range(chip,
pinctrl_dev_get_devname(pctldev),
pinspec.args[0], /* GPIO侧偏移 */
pinspec.args[1], /* pinctrl侧引脚号 */
pinspec.args[2]); /* 数量 */
index++;
}
}
解析完成后,pinctrl核心层会创建一个 pinctrl_gpio_range 结构体,挂入 pinctrl_dev 的 gpio_ranges 链表:
/* include/linux/pinctrl/pinctrl.h */
structpinctrl_gpio_range {
structlist_headnode;/* 链表节点,挂入pinctrl_dev->gpio_ranges */
constchar *name; /* 范围名称 */
unsignedint id; /* 唯一ID */
unsignedint base; /* GPIO侧起始编号 */
unsignedint pin_base; /* Pinctrl侧起始引脚编号 */
unsignedint npins; /* 映射的引脚数量 */
unsignedconst *pins; /* 可选:逐个引脚映射表(非连续时使用) */
structgpio_chip *gc;/* 反向指向GPIO控制器 */
};
以RK3568的gpio1为例,注册完成后:
pinctrl_dev (rk3568-pinctrl)
│
├── gpio_ranges 链表
│ │
│ ├── range[0]:
│ │ base = 0 ← gpio0的第0号引脚
│ │ pin_base = 0 ← pinctrl的第0号引脚
│ │ npins = 32 ← 共32个
│ │ gc → gpio_chip (gpio0)
│ │
│ └── range[1]:
│ base = 0 ← gpio1的第0号引脚
│ pin_base = 32 ← pinctrl的第32号引脚
│ npins = 32 ← 共32个
│ gc → gpio_chip (gpio1)
│
└── pin_desc_tree
├── pin[0] → "GPIO0_A0"
├── pin[1] → "GPIO0_A1"
├── ...
├── pin[32] → "GPIO1_A0"
├── pin[33] → "GPIO1_A1"
└── ...
这个映射表的作用: 当gpiolib收到一个GPIO操作请求时,可以通过这个映射表将GPIO编号转换为pinctrl引脚编号,从而调用pinctrl的相应接口。
两个子系统协作最关键的场景发生在消费层驱动申请GPIO时。gpiolib会回调pinctrl,确保该引脚没有被复用为其他功能。 完整调用链
消费层驱动调用 gpiod_get()
│
│ 设备树中: gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
│ 含义:使用gpio1控制器的第5号引脚
▼
gpiod_get()
→ gpiod_request()
→ gpiod_request_commit()
│
│ 设置 FLAG_REQUESTED
│
├── chip->request(chip, offset)
│ │
│ │ chip->request 指向 gpiochip_generic_request
│ ▼
│ gpiochip_generic_request(chip, offset)
│ │
│ │ 检查是否有pin_ranges
│ │ 如果有,说明pinctrl存在,需要通知pinctrl
│ ▼
│ pinctrl_gpio_request(chip->gpiodev->base + offset)
│ │
│ │ 参数是GPIO全局编号
│ ▼
│ pinctrl_gpio_request()
│ │
│ ├── ① 查表:遍历 gpio_ranges 链表
│ │ 找到包含该GPIO编号的 range
│ │ 将GPIO编号转换为pinctrl引脚编号
│ │
│ │ pin = gpio_to_pin(range, gpio)
│ │ 例如:gpio1的第5号引脚
│ │ → 全局GPIO编号 = 32+5 = 37
│ │ → pinctrl引脚号 = 32+5 = 37
│ │
│ ├── ② 冲突检查:该引脚是否已被复用?
│ │ 调用 pinmux_request_gpio()
│ │ 检查该引脚当前是否被I2C/UART/SPI占用
│ │
│ ├── ③ 如果空闲:调用 pmxops->gpio_request_enable()
│ │ 通知pinctrl驱动:"这个引脚现在作为GPIO使用了"
│ │ pinctrl驱动将引脚复用寄存器切换到GPIO模式
│ │
│ └── ④ 如果被占用:返回 -EBUSY
│ gpiod_request 失败
│ 消费层驱动收到错误
│
└── 申请成功,返回 gpio_desc *
关键函数源码
/* drivers/gpio/gpiolib.c */
intgpiochip_generic_request(struct gpio_chip *chip, unsigned offset)
{
#ifdef CONFIG_PINCTRL
/* 如果该控制器没有注册pin_ranges,说明不依赖pinctrl,直接返回成功 */
if (list_empty(&chip->gpiodev->pin_ranges))
return0;
#endif
/* 有pinctrl,通知pinctrl子系统 */
return pinctrl_gpio_request(chip->gpiodev->base + offset);
}
/* drivers/pinctrl/core.c */
intpinctrl_gpio_request(unsigned gpio)
{
structpinctrl_dev *pctldev;
structpinctrl_gpio_range *range;
int pin;
/* ① 通过gpio_ranges链表,找到对应的pinctrl_dev和range */
ret = pinctrl_get_device_gpio_range(gpio, &pctldev, &range);
if (ret)
return ret;
mutex_lock(&pctldev->mutex);
/* ② GPIO编号 → pinctrl引脚编号 */
pin = gpio_to_pin(range, gpio);
/* ③ 冲突检查 + 切换到GPIO模式 */
ret = pinmux_request_gpio(pctldev, range, pin, gpio);
mutex_unlock(&pctldev->mutex);
return ret;
}
/* drivers/pinctrl/pinmux.c */
intpinmux_request_gpio(struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned pin, unsigned gpio)
{
/* 检查该引脚是否已被其他功能占用 */
if (desc->mux_usecount && !desc->gpio) {
/* 已被I2C/UART/SPI等占用,拒绝GPIO申请 */
return -EBUSY;
}
/* 调用pinctrl驱动的 gpio_request_enable 回调 */
if (pctldev->desc->pmxops->gpio_request_enable) {
ret = pctldev->desc->pmxops->gpio_request_enable(pctldev, range, pin);
}
/* 标记该引脚现在被GPIO占用 */
desc->mux_usecount = 1;
desc->gpio = gpio;
return0;
}
当消费层驱动调用 gpiod_direction_input() 或 gpiod_direction_output() 时,gpiolib同样会通知pinctrl:
/* gpiolib内部实现(简化) */
intgpiod_direction_input(struct gpio_desc *desc)
{
structgpio_chip *gc = desc->gdev->chip;
unsignedint offset = gpio_chip_hwgpio(desc);
/* ① 通知pinctrl:这个引脚要切换为输入方向 */
if (gc->request) {
/* 部分驱动在direction_input中也会调用pinctrl */
}
pinctrl_gpio_direction_input(gc->gpiodev->base + offset);
/* ② 调用gpio_chip的direction_input回调,操作硬件寄存器 */
return gc->direction_input(gc, offset);
}
/* drivers/pinctrl/core.c */
intpinctrl_gpio_direction_input(unsigned gpio)
{
structpinctrl_dev *pctldev;
structpinctrl_gpio_range *range;
int pin;
/* GPIO编号 → pinctrl引脚编号 */
pinctrl_get_device_gpio_range(gpio, &pctldev, &range);
pin = gpio_to_pin(range, gpio);
/* 调用pinctrl驱动的 gpio_set_direction 回调 */
return pctldev->desc->pmxops->gpio_set_direction(pctldev, range, pin, true);
/* ↑
* true = 输入方向
*/
}
pinctrl驱动的 gpio_set_direction 回调会修改SoC的方向寄存器,确保引脚的电气方向与GPIO子系统的逻辑方向一致。
当引脚不是作为GPIO使用,而是作为I2C/UART/SPI等外设功能时,pinctrl独立工作,完全不经过GPIO子系统:
/* 设备树中I2C控制器引用pinctrl配置 */
&i2c3 {
status = "okay";
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&i2c3_pins>; /* 引用pinctrl中的引脚配置 */
};
调用链:
I2C控制器 probe()
│
│ 内核自动调用(驱动框架层面,在probe之前)
▼
pinctrl_select_default_state(dev)
│
├── 解析设备树中 pinctrl-0 引用的 i2c3_pins 节点
│
├── 调用 pinctrl_ops->dt_node_to_map()
│ 将设备树节点转换为 pinctrl_map 数组
│
├── 调用 pinmux_ops->set_mux()
│ 将引脚复用寄存器切换到I2C功能
│ (此时这些引脚不再是GPIO模式)
│
└── 调用 pinconf_ops->pin_config_set()
配置上拉电阻、驱动强度等电气特性
此时这些引脚被I2C功能独占,如果GPIO子系统尝试申请这些引脚,pinctrl会返回 -EBUSY。
┌─────────────────────────────────────────────────────────────────────┐
│ Pinctrl 子系统 │
│ │
│ 全局链表 pinctrl_dev_list │
│ │ │
│ ├──▶ pinctrl_dev (rk3568-pinctrl) │
│ │ │ │
│ │ ├── desc ──▶ pinctrl_desc │
│ │ │ ├── pins[] ── 所有物理引脚描述 │
│ │ │ ├── pctlops ── 引脚组枚举 │
│ │ │ ├── pmxops ── 复用选择 │
│ │ │ │ ├── set_mux() │
│ │ │ │ ├── gpio_request_enable() ◀──┼── GPIO申请时回调
│ │ │ │ ├── gpio_disable_free() ◀──┼── GPIO释放时回调
│ │ │ │ └── gpio_set_direction() ◀──┼── 方向切换时回调
│ │ │ └── confops ── 电气配置 │
│ │ │ │
│ │ ├── gpio_ranges 链表 ◀─────────────────────────────── │ ── 映射表
│ │ │ │ │ GPIO编号
│ │ │ ├── range[0]: base=0, pin_base=0, npins=32 │ ←→
│ │ │ │ gc → gpio_chip(gpio0) │ Pinctrl引脚号
│ │ │ │ │
│ │ │ └── range[1]: base=0, pin_base=32, npins=32 │
│ │ │ gc → gpio_chip(gpio1) │
│ │ │ │
│ │ └── pin_desc_tree │
│ │ ├── pin[0] → "GPIO0_A0" (mux_usecount, gpio) │
│ │ ├── pin[1] → "GPIO0_A1" │
│ │ ├── ... │
│ │ └── pin[63] → "GPIO1_D7" │
│ │ │
└──────┼──────────────────────────────────────────────────────────────┘
│
│ gpio_ranges 映射表
│ 将GPIO编号转换为Pinctrl引脚编号
│
┌──────┼──────────────────────────────────────────────────────────────┐
│ │ GPIO 子系统 │
│ ▼ │
│ 全局链表 gpio_devices │
│ │ │
│ ├──▶ gpio_device (gpiochip0) │
│ │ ├── chip ──▶ gpio_chip │
│ │ │ ├── .request = gpiochip_generic_request │
│ │ │ │ ↓ 内部调用 │
│ │ │ │ pinctrl_gpio_request() ─────────────┼──→ 查gpio_ranges
│ │ │ │ │ → 冲突检查
│ │ │ ├── .free = gpiochip_generic_free │ → gpio_request_enable
│ │ │ │ ↓ 内部调用 │
│ │ │ │ pinctrl_gpio_free() ────────────────┼──→ gpio_disable_free
│ │ │ │ │
│ │ │ ├── .get / .set / .direction_* │
│ │ │ └── base=0, ngpio=32 │
│ │ │ │
│ │ └── descs ──▶ gpio_desc[0..31] │
│ │ │
│ ├──▶ gpio_device (gpiochip1) │
│ │ ├── chip ──▶ gpio_chip │
│ │ │ ├── .request = gpiochip_generic_request │
│ │ │ └── base=32, ngpio=32 │
│ │ └── descs ──▶ gpio_desc[0..31] │
│ │ │
└─────────────────────────────────────────────────────────────────────┘
Pinctrl管"引脚的物理状态"(连到哪个外设、电气特性),GPIO管"引脚的逻辑功能"(高低电平、输入输出方向)。GPIO是Pinctrl的一种复用功能(function = gpio),两者通过 gpio-ranges 映射表和 gpiochip_generic_request 回调实现协作。
时间线 ──────────────────────────────────────────────────────────→
① 设备树解析
内核解析 .dtb,发现两个控制器节点:
├── pinctrl: compatible = "rockchip,rk3568-pinctrl"
└── gpio0: compatible = "rockchip,gpio-bank"
gpio-ranges = <&pinctrl 0032>
│
② Pinctrl控制器 probe(先执行)
pinctrl_probe()
├── 填充 pinctrl_desc(pins[]、pctlops、pmxops、confops)
├── devm_pinctrl_register()
│ ├── 分配 pinctrl_dev
│ ├── 注册到全局链表
│ └── 解析设备树中的引脚配置子节点(i2c3_pins、uart1_pins等)
└── pinctrl就绪 ✓
│
③ GPIO控制器 probe(后执行)
gpio_probe()
├── 填充 gpio_chip
│ ├── .request = gpiochip_generic_request ← 桥接pinctrl
│ ├── .free = gpiochip_generic_free
│ └── .get/.set/.direction_*
├── devm_gpiochip_add_data()
│ ├── 分配 gpio_device、gpio_desc 数组
│ ├── 串联三个结构体
│ │
│ ├── of_gpiochip_add_pin_range() ← 解析 gpio-ranges
│ │ ├── 找到 pinctrl_dev
│ │ └── 创建 pinctrl_gpio_range,挂入 gpio_ranges 链表
│ │
│ └── 创建 /dev/gpiochipN
└── GPIO就绪 ✓
│
│ ★ 两条链路在此汇合 ★
│ GPIO编号 ←→ Pinctrl引脚编号 映射已建立
│
④ 消费层驱动使用GPIO
gpiod_get()
→ gpiod_request()
→ chip->request()
→ gpiochip_generic_request()
→ pinctrl_gpio_request()
→ 查 gpio_ranges 映射表
→ GPIO编号转换为pinctrl引脚编号
→ 冲突检查(引脚是否被I2C/UART占用?)
→ 调用 gpio_request_enable() 切换到GPIO模式
→ 返回成功
│
⑤ 消费层操作GPIO
gpiod_set_value(desc, 1)
→ desc->gdev->chip->set() → 操作GPIO数据寄存器
(此时pinctrl不参与,GPIO子系统独立操作硬件)
│
⑥ 消费层释放GPIO
gpiod_put(desc)
→ gpiod_free()
→ chip->free()
→ gpiochip_generic_free()
→ pinctrl_gpio_free()
→ 调用 gpio_disable_free()
→ 释放引脚的GPIO占用标记
