[TOC]
drivers/gpio GPIO子系统(General Purpose Input/Output) 内核与硬件I/O引脚交互的通用框架
历史与背景
这项技术是为了解决什么特定问题而诞生的?
GPIO(通用输入/输出)子系统是为了在Linux内核中创建一个统一、抽象、可移植的框架来管理和控制硬件的GPIO引脚而诞生的。 在此框架出现之前,对GPIO的操作是混乱且与平台高度绑定的。每个SoC(片上系统)或主板都有自己独特的GPIO控制方式,驱动程序必须编写大量特定于硬件的代码才能操作一个引脚。
该子系统的诞生解决了以下核心问题:
- 消除平台特定代码:为内核提供一个标准的API,使得驱动程序(称为“消费者”)可以不用关心底层GPIO控制器(称为“提供者”或
gpio_chip)的具体实现,就能请求、配置和读写一个GPIO引脚。 - 资源管理与冲突避免:一个GPIO引脚在系统中是独占性资源。 该框架提供了一套请求(
request)和释放(free)机制,确保一个引脚在同一时间只能被一个驱动程序使用,从而避免了硬件冲突。 - 抽象硬件差异:不同的GPIO控制器功能各异(例如,有些支持中断,有些支持开漏/开源配置等)。GPIO子系统通过统一的接口抽象了这些差异,为上层驱动提供了一致的行为。
- 与设备树集成:随着设备树(Device Tree)的普及,GPIO子系统需要一种方法来解析设备树中描述的GPIO连接关系,使得驱动程序能够动态地获取其所需的引脚,而不是在代码中硬编码。
它的发展经历了哪些重要的里程碑或版本迭代?
GPIO子系统的发展经历了从简单的数字命名空间到更安全、更强大的描述符模型的演进。
- 早期的整数命名空间(Legacy API):最初,所有GPIO引脚都被映射到一个全局的、从0开始的整数命名空间中。 驱动通过一个整数(如
gpio_request(23, ...))来请求引脚。这种方式的主要缺点是:- 脆弱性:GPIO编号不是稳定的,它会因为内核配置或硬件平台的改变而改变,导致代码不可移植。
- 不安全:任何驱动都可以伪造一个整数来尝试控制一个它不拥有的引-脚。
- 描述符接口的引入(Descriptor-based API):这是一个决定性的里程碑。新的API不再使用不稳定的全局整数,而是引入了一个不透明的句柄——
struct gpio_desc *(GPIO描述符)。- 驱动程序必须通过
gpiod_get()等函数从设备(struct device)或通过设备树获取描述符。 - 这种方式更安全,因为描述符不能被伪造,并且其生命周期由内核管理。
- 它还更好地处理了“低电平有效”(active-low)的逻辑,驱动可以设置或读取逻辑值(0/1),而由框架来处理物理电平的翻转。
- 所有新的内核驱动都被强烈推荐使用这个以
gpiod_*为前缀的新接口。
- 驱动程序必须通过
- 用户空间接口的演进:
- Sysfs接口(已废弃):早期提供了一个通过
/sys/class/gpio的接口,允许用户空间通过读写文件来控制GPIO。该接口因其效率低下、设计缺陷以及与旧的整数模型绑定等原因,从Linux 4.8版本开始被废弃。 - 字符设备接口(Chardev ABI):为了取代sysfs,内核引入了一个基于字符设备(
/dev/gpiochipN)的新用户空间接口。 它更高效(可以通过一次ioctl调用操作多个引脚)、更安全(资源生命周期与文件句柄绑定),并由libgpiod库提供了易于使用的封装。
- Sysfs接口(已废弃):早期提供了一个通过
目前该技术的社区活跃度和主流应用情况如何?
GPIO子系统是所有嵌入式Linux系统和许多服务器硬件平台的核心基础组件。它非常稳定,但仍在积极维护以支持新的硬件特性。
- 主流应用:几乎所有的嵌入式设备驱动都会用到GPIO。内核中已经包含了大量使用GPIO的子系统级驱动,例如
gpio-keys(按键输入)、leds-gpio(LED控制)、gpio-fan(风扇控制)等,这些驱动为常见的硬件模式提供了标准化的内核接口。 - 社区规范:社区强烈推荐所有新代码使用基于描述符的
gpiod_*内核API,并使用libgpiod库与字符设备进行用户空间交互。
核心原理与设计
它的核心工作原理是什么?
GPIO子系统的核心是生产者/消费者模型,由一个通用的中间层(称为gpiolib)进行协调。
生产者(Provider /
gpio_chip):- 硬件GPIO控制器的驱动程序(例如,某个SoC的GPIO模块驱动)会实现一个
struct gpio_chip。 - 这个结构体包含了一组函数指针,用于实现具体的操作,如设置方向(
.direction_input/.direction_output)、读值(.get)、写值(.set)以及请求中断等。 - 驱动通过
gpiochip_add_data()将其实例注册到gpiolib中。
- 硬件GPIO控制器的驱动程序(例如,某个SoC的GPIO模块驱动)会实现一个
消费者(Consumer):
- 需要使用GPIO的设备驱动程序(例如,一个Wi-Fi模块驱动需要控制电源使能引脚)被称为消费者。
- 消费者通过调用
gpiod_get()或devm_gpiod_get(),并提供设备指针和在设备树中定义的名字(如"enable-gpios"),来向gpiolib请求一个GPIO描述符。 gpiolib根据设备树的连接关系,找到对应的gpio_chip,并返回一个有效的描述符。
gpiolib(核心层):
- 位于
drivers/gpio/gpiolib.c等文件中,是连接生产者和消费者的桥梁。 - 它管理着所有注册的
gpio_chip,维护一个GPIO描述符池,并处理资源的请求和释放。 - 当消费者的驱动调用
gpiod_set_value()时,gpiolib会找到该描述符对应的gpio_chip,并调用其.set()回调函数,从而最终操作硬件。
- 位于
它的主要优势体现在哪些方面?
- 抽象和解耦:完全将设备驱动与具体的GPIO控制器硬件分离。
- 安全性:基于描述符的API防止了资源的误用,并确保了所有权。
- 标准化:为内核和用户空间提供了稳定且功能丰富的API。
- 可扩展性:新的GPIO控制器可以很容易地通过实现
gpio_chip接口接入到系统中。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 性能:对于需要极高速率(MHz级别)进行位操作(Bit-banging)的场景,通过
gpiolib的函数调用路径可能会引入不可接受的延迟。这种场景通常应使用硬件SPI或I2C控制器,而不是用GPIO模拟。 - 非通用功能:GPIO子系统只处理通用的数字输入/输出。 对于引脚的复用(Pin Muxing)、电气特性配置(如上/下拉、驱动强度)等更复杂的功能,则由Pinctrl子系统负责。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。
GPIO子系统是任何需要直接控制或读取硬件数字信号线的标准解决方案。
- 硬件使能/复位:一个MMC/SD卡驱动,通过GPIO来检测卡槽中是否有卡插入(输入),以及控制SD卡的电源使能(输出)。
- LED状态指示:系统通过一个
leds-gpio驱动来控制多个LED灯的亮灭,以指示网络活动、存储状态等。 - 按键输入:使用
gpio-keys驱动,将连接到GPIO的物理按键转换为标准的内核输入事件,这样用户空间的程序就能像读取键盘一样读取按键。 - 中断触发:一个触摸屏控制器,通过一个GPIO引脚在有触摸事件发生时向CPU发送中断信号。
gpiolib能够将GPIO中断转换为标准的Linux IRQ号。
是否有不推荐使用该技术的场景?为什么?
- 引脚复用配置:当一个引脚需要被配置为特定硬件模块(如UART、I2C控制器)的功能引脚时,不应使用GPIO子系统。这项工作必须由Pinctrl子系统完成,它负责将引脚从“GPIO模式”切换到“UART_TX模式”等。
- 高速串行总线模拟:如上所述,模拟SPI或I2C总线对于
gpiolib来说开销太大,性能很差。应优先使用专用的硬件控制器。 - 模拟PWM或音频信号:虽然可以通过软件快速翻转GPIO来模拟PWM,但其精度和稳定性远不如硬件PWM控制器。
对比分析
请将其 与 其他相似技术 进行详细对比。
在Linux内核中,与GPIO子系统关系最密切、最容易混淆的是Pinctrl(Pin Control)子系统。
| 特性 | GPIO Subsystem | Pinctrl Subsystem |
|---|---|---|
| 核心功能 | 控制引脚的逻辑状态:设置为输入或输出,读取高/低电平,写入高/低电平,处理中断。 | 配置引脚的“身份”和电气特性:决定一个引脚是作为GPIO、UART、I2C还是其他功能,以及配置其上/下拉、驱动强度、转换速率等。 |
| 操作对象 | 已经处于GPIO模式下的引脚。 | 系统中的所有物理引脚(Pins)。 |
| 抽象层次 | 较高层,面向逻辑功能。一个GPIO是一个可以读写的数字信号线。 | 较低层,面向硬件物理属性。一个Pin是一个需要被复用和配置的物理实体。 |
| 交互关系 | 依赖于Pinctrl。一个引脚必须首先被Pinctrl子系统配置为GPIO模式,然后才能被GPIO子系统使用。 | 为GPIO提供基础。Pinctrl负责“搭台”,GPIO负责“唱戏”。 |
| 典型API | 内核:gpiod_get(), gpiod_direction_output(), gpiod_set_value() |
内核:pinctrl_get(), pinctrl_lookup_state(), pinctrl_select_state() |
| 总结 | 回答“这个信号线是高电平还是低电平?” | 回答“这个物理引脚现在应该做什么用?(是当GPIO还是当UART用?)” |
include/linux/gpio/driver.h
for_each_gpiochip_node 遍历GPIO控制器节点
1 |
|
drivers/gpio/gpiolib-of.c
of_get_named_gpiod_flags & of_gpio_flags_quirks
这两个函数是Linux内核中从设备树(Device Tree)解析GPIO属性的最终执行者。of_get_named_gpiod_flags是负责处理单个、具名GPIO属性的核心工作引擎, 而of_gpio_flags_quirks则是一个专门的辅助函数, 用于处理各种历史遗留的、非标准的设备树绑定(“怪癖”)。
of_get_named_gpiod_flags: The Core Device Tree GPIO Property Parser
此函数是of_find_gpio在确定了要查找的具体属性名称(例如, "enable-gpios")后调用的底层核心引擎。它的原理是执行一个精确的、分阶段的流程, 将设备树中一个GPIO描述符(phandle + specifier)完全转化为一个内核可以使用的、包含了正确标志的gpio_desc句柄。
工作流程详解:
解析Phandle和参数 (
of_parse_phandle_with_args_map): 这是第一步, 也是最关键的一步。此函数负责解析设备树属性值, 例如<&gpioA 5 GPIO_ACTIVE_LOW>。- 它从属性中提取出
phandle(指向GPIO控制器节点, 如&gpioA)。 - 它根据GPIO控制器节点中的
#gpio-cells属性, 确定后面有多少个参数(specifier)。 - 它将
phandle和所有参数(5,GPIO_ACTIVE_LOW)打包到一个of_phandle_args结构体(gpiospec)中。
- 它从属性中提取出
查找提供者驱动 (
of_find_gpio_device_by_xlate): 有了指向GPIO控制器节点的phandle, 此函数会在内核中查找是否已经有驱动程序为该节点注册了一个gpio_device。- 这是处理驱动依赖关系的核心点: 如果GPIO控制器驱动(例如STM32的pinctrl/gpio驱动)尚未初始化, 查找就会失败。在这种情况下, 此函数会正确地返回
-EPROBE_DEFER, 从而安全地推迟当前消费者驱动的探测, 等待依赖就绪。
- 这是处理驱动依赖关系的核心点: 如果GPIO控制器驱动(例如STM32的pinctrl/gpio驱动)尚未初始化, 查找就会失败。在这种情况下, 此函数会正确地返回
翻译与获取 (
of_xlate_and_get_gpiod_flags): 找到GPIO控制器驱动后, 此函数会调用该驱动的of_xlate回调函数。xlate(translate) 的作用是将设备树中特定于硬件的参数 (如5 GPIO_ACTIVE_LOW) “翻译”成驱动内部可以理解的本地硬件引脚号, 并解析出标准的内核GPIO标志。- 翻译完成后, 它就从该GPIO控制器驱动管理的
gpio_chip中获取代表该特定引脚的gpio_desc句柄。
应用”怪癖” (
of_gpio_flags_quirks): 在基本标志解析完成后, 它会调用of_gpio_flags_quirks函数, 对标志进行可能的修正, 以处理各种非标准的历史遗留绑定。资源管理: 在函数结束前, 它必须调用
of_node_put来释放对GPIO控制器节点的引用计数, 这是Linux内核中标准的资源管理实践。
of_gpio_flags_quirks: The Legacy Binding Compatibility Handler
此函数本身不执行任何标准的查找, 它的唯一作用是充当一个”兼容性补丁”集合。它包含了许多针对特定设备或旧版设备树绑定的特殊处理逻辑, 用于修正或补充从设备树中解析出来的GPIO标志。
原理:
它是一个大型的if/else if条件判断集合, 每一个条件块都对应一个已知的”怪癖”。
- 通用修正: 它首先调用一些通用的修复函数, 如
of_gpio_try_fixup_polarity, 来处理一些常见的极性定义问题。 - 固定电压调节器怪癖: 它检查设备是否是一个
"reg-fixed-voltage"。如果是, 它会去查找一个额外的、非标准的布尔属性"gpio-open-drain"。在现代设备树中, “open-drain”标志应该直接写在gpios属性的参数里, 这个检查是为了兼容那些将此标志放在别处的旧设备树。 - SPI芯片选择怪癖: 这是一个更复杂的例子。对于SPI的
"cs-gpios"属性, 其”高电平有效”或”低电平有效”的极性, 按照旧的绑定规范, 不是定义在cs-gpios属性本身, 而是定义在SPI master节点的子节点中的一个"spi-cs-high"布尔属性里。此函数包含了遍历子节点、匹配片选索引、并根据spi-cs-high属性来修正极性标志的完整逻辑。 - STMMAC以太网怪癖: 它为STMMAC驱动的复位引脚
"snps,reset-gpio"检查一个名为"snps,reset-active-low"的独立布尔属性, 以此来确定复位信号的极性。
在STM32H750的现代设备树文件中, 这些怪癖通常不会被触发。然而, of_gpio_flags_quirks的存在是Linux内核能够无缝支持横跨十几年、数千种不同硬件设计的关键因素之一, 它将处理历史遗留问题的代码集中在一个地方, 使得核心的解析逻辑(of_get_named_gpiod_flags)能够保持干净和标准化。
1 | /* |
of_find_gpio: The Device Tree GPIO Lookup Engine with Quirk Support
此函数是Linux内核gpiod子系统中专门负责解析设备树(Device Tree)以查找GPIO的后端核心引擎。它的主要作用是根据消费者驱动提供的功能名称(con_id), 在设备树节点(np)中寻找匹配的GPIO属性, 并返回一个代表该GPIO的内核句柄(struct gpio_desc)。
该函数的核心原理是一种健壮且可扩展的两阶段查找策略, 旨在同时支持标准的设备树绑定规范和各种非标准的、特定于硬件的”怪癖”(Quirks)。
工作流程详解:
阶段一: 标准化查找 (The Standard Path)
- 此阶段遵循Linux设备树绑定的官方规范。它使用
for_each_gpio_property_name宏, 这是一个巧妙的工具, 它会根据输入的功能名con_id自动生成两个标准的属性名进行尝试。例如, 如果con_id是"enable", 这个宏会依次生成:"enable-gpios"(复数形式, 用于可能包含多个GPIO的功能)"enable-gpio"(单数形式, 向后兼容)
- 对于每一个生成的属性名, 它会调用
of_get_named_gpiod_flags。这个底层函数负责执行实际的设备树解析工作: 它在设备树节点中查找该属性, 读取其值(通常是一个指向GPIO控制器节点的phandle和一些参数), 并返回一个初始的GPIO描述符。 - 如果
of_get_named_gpiod_flags成功找到了GPIO(或者返回了除-ENOENT之外的任何”真实”错误, 如-EBUSY), 查找过程就会立即停止并进入最后阶段。
- 此阶段遵循Linux设备树绑定的官方规范。它使用
阶段二: “怪癖”查找 (The Quirk Path)
- 只有当第一阶段完全没有找到任何匹配的属性时, 才会进入此阶段。这体现了”标准优先”的原则。
- 它会遍历一个名为
of_find_gpio_quirks的全局函数指针数组。数组中的每一个函数都是一个专门的”怪癖处理器”, 用于解决某个特定硬件平台或旧版设备树绑定不遵循标准规范的问题。 - 例如:
of_find_gpio_rename: 可能用于处理那些使用了非标准属性名的旧绑定。of_find_mt2701_gpio: 这是一个非常具体的例子, 专门用于处理联发科(MediaTek) MT2701 SoC上的一种特殊GPIO绑定。
- 函数会依次调用数组中的每一个怪癖处理器, 让它们尝试用自己的特殊逻辑去查找GPIO。只要其中任何一个怪癖处理器成功找到, 查找就会停止。
最后阶段: 标志转换与返回
- 在通过标准或怪癖路径成功找到GPIO描述符后, 它会调用
of_convert_gpio_flags。这是一个重要的翻译步骤, 它将设备树中定义的标志(如OF_GPIO_ACTIVE_LOW)转换为gpiod子系统内部使用的通用标志(如GPIO_ACTIVE_LOW)。 - 最终, 它返回一个包含了正确硬件信息和标志的、可供上层函数使用的
gpio_desc指针。
- 在通过标准或怪癖路径成功找到GPIO描述符后, 它会调用
在STM32H750这样的现代嵌入式平台上, 其设备树绑定通常遵循官方标准。因此, 在绝大多数情况下, of_find_gpio函数会在第一阶段就成功找到所需的GPIO, 而不会进入第二阶段的怪癖处理流程。然而, 这个怪癖处理机制是Linux内核保持对大量不同硬件(包括那些有历史遗留问题的硬件)的广泛兼容性的关键所在, 体现了内核设计的灵活性和向后兼容性。
1 | /* |
drivers/gpio/gpio-stmpe.c
STMPE GPIO 操作回调函数集
stmpe_gpio_get: 获取GPIO引脚的输入电平
- 作用: 当内核或用户空间需要读取一个GPIO引脚的当前逻辑状态(高电平或低电平)时,
gpiolib框架会调用此函数。 - 原理:
- 获取上下文:
gpiochip_get_data(chip)获取到指向本驱动私有数据stmpe_gpio的指针, 从而可以访问到与父设备通信的stmpe句柄。 - 定位寄存器:
stmpe->regs是一个数组, 存储了不同功能寄存器的基地址。STMPE_IDX_GPMR_LSB是”GPIO Pin Monitor Register”(GPIO引脚监视寄存器)的基地址索引。offset / 8计算出该引脚属于哪个8位的寄存器(哪个bank)。 - 定位比特位:
BIT(offset % 8)计算出该引脚在8位寄存器中所对应的比特位掩码(例如,offset为10, 掩码为BIT(2), 即0x04)。 - 硬件交互:
stmpe_reg_read通过I2C/SPI总线读取目标寄存器的值。 - 解析结果:
ret & mask从读取到的8位值中分离出我们关心的那一位。!!是一个C语言技巧, 用于将任何非零值转换为1, 0值保持为0, 确保返回值是标准的逻辑值。
- 获取上下文:
1 | static int stmpe_gpio_get(struct gpio_chip *chip, unsigned offset) |
stmpe_gpio_set: 设置GPIO引脚的输出电平
- 作用: 当需要将一个配置为输出的GPIO引脚设置为高电平或低电平时,
gpiolib会调用此函数。 - 原理:
- 选择操作: STMPE芯片通常有独立的”GPIO Pin Set Register”(GPSR, 写1置位)和”GPIO Pin Clear Register”(GPCR, 写1清零)。函数根据传入的
val值(1或0)来选择which寄存器基址。 - 定位: 与
get函数一样, 计算出确切的寄存器地址和比特位掩码。 - 特殊处理: 某些STMPE芯片型号可能只有一个寄存器用于置位/清零。代码通过比较GPSR和GPCR的地址是否相同来检测这种情况。如果是, 它会调用
stmpe_set_bits(一个读-修改-写操作)来设置或清除相应的位。 - 标准操作: 对于有独立置位/清零寄存器的型号, 只需向选定的寄存器写入掩码即可触发硬件操作。
stmpe_reg_write完成这个总线写操作。
- 选择操作: STMPE芯片通常有独立的”GPIO Pin Set Register”(GPSR, 写1置位)和”GPIO Pin Clear Register”(GPCR, 写1清零)。函数根据传入的
1 | static int stmpe_gpio_set(struct gpio_chip *chip, unsigned int offset, int val) |
stmpe_gpio_get_direction: 获取GPIO引脚的方向
- 作用: 查询一个GPIO引脚当前是被配置为输入还是输出。
- 原理: 此操作围绕”GPIO Pin Direction Register”(GPDR)进行。
- 定位: 计算GPDR寄存器的地址和比特位掩码。注意这里有一个
- (offset / 8)的笔误, 应该是+, 假设在父驱动的寄存器地址表中已做了修正。 - 硬件交互: 读取GPDR寄存器的值。
- 解析: 检查对应的比特位。根据STMPE的数据手册, 如果该位是1, 表示输出; 如果是0, 表示输入。函数返回
gpiolib定义的标准方向常量。
- 定位: 计算GPDR寄存器的地址和比特位掩码。注意这里有一个
1 | static int stmpe_gpio_get_direction(struct gpio_chip *chip, |
stmpe_gpio_direction_output / stmpe_gpio_direction_input: 设置GPIO引脚的方向
- 作用: 将一个GPIO引脚配置为输入或输出模式。
- 原理:
direction_output: 这是一个两步操作, 以避免引脚在方向切换瞬间产生不确定的电平(毛刺)。- 首先调用
stmpe_gpio_set预先设置好期望的输出电平val。此时方向尚未改变, 但硬件内部状态已准备好。 - 然后调用
stmpe_set_bits(一个安全的读-修改-写函数), 将GPDR寄存器中对应的比特位置为1, 正式将引脚切换为输出模式。
- 首先调用
direction_input: 这是一个单步操作。只需调用stmpe_set_bits将GPDR寄存器中对应的比特位清零即可。
1 | static int stmpe_gpio_direction_output(struct gpio_chip *chip, |
stmpe_gpio_request: 请求使用一个GPIO
- 作用: 当一个驱动程序首次通过
gpio_request()或gpiod_get()请求使用某个GPIO时,gpiolib会调用此函数。这是执行一次性初始化配置的理想位置。 - 原理:
- 检查保留位: 首先检查该引脚
offset是否在norequest_mask(从设备树读取)中被标记为保留。如果是, 则返回-EINVAL拒绝请求。 - 设置复用功能: STMPE芯片的引脚通常是多功能的(例如, GPIO, ADC, PWM等)。
stmpe_set_altfunc是一个父驱动提供的函数, 它会通过总线向芯片发送命令, 确保该引脚的复用功能被正确地设置为GPIO模式。这是确保引脚能作为通用IO使用的关键一步。
- 检查保留位: 首先检查该引脚
1 | static int stmpe_gpio_request(struct gpio_chip *chip, unsigned offset) |
STMPE GPIO驱动的核心实现
此代码片段展示了stmpe-gpio驱动程序的核心部分: 它定义了驱动的私有数据结构, 并提供了所有必要的回调函数, 以将STMPE IO扩展器的硬件操作与Linux内核通用的gpiolib框架连接起来。其核心原理是将gpiolib的抽象请求(如”设置引脚5为高电平”)转换为对STMPE芯片的具体、面向寄存器的I2C/SPI总线操作。
struct stmpe_gpio: 驱动私有数据结构
这个结构体是stmpe-gpio驱动实例的”大脑”, 它包含了驱动运行时所需的所有状态和信息。
1 | /* |
template_chip: gpiolib接口的”蓝图”
这个静态常量结构体将上面定义的所有回调函数打包在一起, 作为注册到gpiolib的模板。
1 | /* |
stmpe_gpio_probe: 初始化STMPE IO扩展器的GPIO功能
此函数是stmpe-gpio平台驱动程序的探测函数, 它是驱动程序的核心入口点。当内核的设备模型发现一个与此驱动匹配的设备时(通常是通过设备树), 就会调用此函数。它的核心作用是将一个物理上的STMPE芯片(通过I2C或SPI连接的外部IO扩展器)的GPIO功能, 初始化并注册到Linux内核通用的gpiolib子系统中, 使这些外部IO引脚能像处理器片上GPIO一样被系统标准地访问和控制。
其工作原理可以分解为以下几个关键步骤:
- 获取父设备数据与内存分配: 此驱动是一个”多功能设备”(MFD, Multi-Function Device)的客户端。它首先从其父设备(即主
stmpe驱动, 负责I2C/SPI通信)获取一个指向stmpe核心数据结构的句柄。然后, 它使用devm_kzalloc为自身的状态结构stmpe_gpio分配内存, 这种内存分配方式确保了在驱动卸载时资源能被自动释放, 极大地简化了错误处理。 - 配置
gpio_chip结构体:gpio_chip是gpiolib框架的核心, 它像一个”驱动蓝图”, 包含了一系列函数指针, 用于定义如何对GPIO进行读、写、设置方向等操作。此函数使用一个预定义的template_chip作为模板, 并根据从父设备获取的信息(如GPIO数量)对其进行定制。将base设置为-1, 是让gpiolib动态地为这些GPIO分配一个未被占用的编号区间。 - 硬件使能与中断处理: 它调用父驱动提供的
stmpe_enable函数, 向物理芯片发送命令以开启GPIO功能块。这是实际的硬件交互。接着, 它处理中断:- 它获取一个由STMPE芯片产生的中断号。这个中断是”共享的”, 即芯片上任何一个GPIO产生中断, 都会触发这一根物理中断线。
- 它注册一个线程化中断处理程序(
stmpe_gpio_irq)。当物理中断发生时, 内核会唤醒一个专门的内核线程来执行这个处理函数, 这对于通过较慢总线(如I2C)连接的设备是最佳实践, 避免在中断上下文中进行耗时操作。 - 它配置
gpio_irq_chip结构。这是将gpiolib和irqchip(中断控制器)框架连接起来的”胶水”。它定义了如何为单个GPIO使能/屏蔽中断、设置触发类型等操作。当上层请求某个GPIO的中断时,gpiolib会通过gpio_irq_chip中的函数指针, 调用本驱动的相应函数来操作硬件。
- 注册到GPIOLIB: 最后, 它调用
devm_gpiochip_add_data, 将完全配置好的gpio_chip注册到gpiolib中。一旦注册成功, 系统中其他任何部分(包括用户空间)就可以通过标准的GPIO接口(例如,gpio_request,gpiod_get,/sys/class/gpio等)来使用这些来自STMPE芯片的GPIO引脚了。
1 | /* |
stmpe_gpio: STMPE GPIO驱动的定义与注册
此代码片段的核心作用是定义一个针对”STMPE”系列芯片(通常是I2C/SPI接口的IO扩展器、触摸屏控制器等)上GPIO功能的平台驱动程序, 并通过内核的初始化调用机制, 在系统启动的适当时机将其注册到内核中。
- 驱动定义 (
stmpe_gpio_driver):struct platform_driver是Linux平台总线模型中用来描述一个驱动程序的核心数据结构。它告诉内核这个驱动的名字、关键的回调函数(如probe), 以及一些行为属性。一旦注册, 内核就会用它的名字("stmpe-gpio")去匹配设备树或板级文件中定义的设备。 - 注册函数 (
stmpe_gpio_init): 这个函数是驱动注册的入口点。它只做一件事: 调用platform_driver_register将stmpe_gpio_driver这个”驱动蓝图”提交给内核的平台总线核心, 使其变为一个”活”的、可用于设备匹配的驱动程序。 - 初始化调用 (
subsys_initcall):subsys_initcall是一个宏, 它将stmpe_gpio_init函数的地址放入一个特殊的内存段中。在内核启动过程中, 会有一个专门的阶段来执行这个段里的所有函数指针。这确保了该GPIO驱动在核心子系统初始化之后、依赖它的具体设备驱动(device drivers)初始化之前被注册, 从而保证了正确的初始化顺序。
1 | /* |









