gpio子系统
一、GPIO子系统的作用
GPIO子系统既能支持芯片本身的GPIO,也能支持扩展的GPIO。提供统一、简便的访问接口,实现:输入、输出、中断。
芯片内部有很多引脚,这些引脚可以接到GPIO模块,也可以接到I2C等模块。通过Pinctrl子系统来选择引脚的功能(mux function)、配置引脚:
当一个引脚被复用为GPIO功能时,我们可以去设置它的方向、设置/读取它的值。GPIO名为”General Purpose Input/Output”,通用目的输入/输出,就是常用的引脚。GPIO可能是芯片自带的,也可能通过I2C、SPI接口扩展:
GPIO有一些通用功能、通用属性。
通用功能:
- 可以设为输出:让它输出高低电平;
- 可以设为输入,读取引脚当前电平;
- 可以用来触发中断
对于芯片自带的GPIO,它的访问时很快的,可以在获得spinlocks的情况下操作它。
但是,对于通过I2C、SPI等接口扩展的GPIO,访问它们时可能导致休眠,所以这些”GPIO Expander”就不能在获得spinlocks的情况下使用。
通用属性:
Active-High and Active-Low
以LED为例,需要设置GPIO电平。但是有些电路可能是高电平点亮LED,有些是低电平点亮LED。可以使用如下代码:
1 | gpiod_set_value(gpio, 1); // 输出高电平点亮LED |
对应同一个目标:点亮LED,对于不同的LED,就需要不同的代码,原因在于上面的代码中1、0表示的是”物理值”。如果能使用”逻辑值”,同样的逻辑值在不同的配置下输出对应的物理值,就可以保持代码一致,比如:
1 | gpiod_set_value(gpio, 1); // 输出逻辑1 |
Open Drain and Open Source
有多个GPIO驱动同时驱动一个电路时,就需要设置Open Drain或Open Source。
- Open Drain:引脚被设置为低电平时才会驱动电路,典型场景是I2C接口。
- Open Source:引脚被设置为高电平时才会驱动电路
二、GPIO子系统层次
2.1 整体层次
2.2 GPIOLIB向上提供的接口
| descriptor-based | legacy | 说明 |
|---|---|---|
| 获得GPIO | ||
| gpiod_get | gpio_request | |
| gpiod_get_index | ||
| gpiod_get_array | gpio_request_array | |
| devm_gpiod_get | ||
| devm_gpiod_get_index | ||
| devm_gpiod_get_array | ||
| 设置方向 | ||
| gpiod_direction_input | gpio_direction_input | |
| gpiod_direction_output | gpio_direction_output | |
| 读值、写值 | ||
| gpiod_get_value | gpio_get_value | |
| gpiod_set_value | gpio_set_value | |
| 释放GPIO | ||
| gpio_free | gpio_free | |
| gpiod_put | gpio_free_array | |
| gpiod_put_array | ||
| devm_gpiod_put | ||
| devm_gpiod_put_array |
2.3 GPIOLIB向下提供的接口
三、核心数据结构
整体框架:
记住GPIO Controller的要素,这有助于理解它的驱动程序:
- 一个GPIO Controller里有多少个引脚?有哪些引脚?
- 需要提供函数,设置引脚方向、读取/设置数值
- 需要提供函数,把引脚转换为中断
以Linux面向对象编程的思想,一个GPIO Controller必定会使用一个结构体来表示,这个结构体必定含有这些信息:
- GPIO引脚信息
- 控制引脚的函数
- 中断相关的函数
3.1 gpio_device
每个GPIO Controller用一个gpio_device来表示:
- 里面每一个gpio引脚用一个gpio_desc来表示
- gpio引脚的函数(引脚控制、中断相关),都放在gpio_chip里
3.2 gpio_chip
我们并不需要自己创建gpio_device,编写BSP驱动时要创建的是gpio_chip,里面提供了:
- 控制引脚的函数
- 中断相关的函数
- 引脚信息:支持多少个引脚?各个引脚的名字?
3.3 gpio_desc
我们去使用GPIO子系统时,首先是获得某个引脚对应的gpio_desc。
gpio_device表示一个GPIO Controller,里面支持多个GPIO引脚。
gpio_device中有一个gpio_desc数组,每个引脚都被抽象成gpio_desc。
3.4 gpio_chip相关函数的实现机制
偷一张图:
四、 GPIO子系统驱动程序流程
分配、设置、注册gpioc_chip结构体,示例:drivers\gpio\gpio-74x164.c
imx6ull的gpio子系统的整体流程:
五、 GPIO子系统与Pinctrl子系统的交互
5.1 使用GPIO前应先设置Pinctrl
假设使用这个虚拟的GPIO Controller的pinA来控制LED:
要使用pinA来控制LED,首先要通过Pinctrl子系统把它设置为GPIO功能,然后才能使用GPIO子系统设置它为输出引脚、设置它的输出值。所以在设备树文件里,应该添加Pinctrl的内容:
1 | virtual_pincontroller { |
但是很多芯片,并不要求在设备树中把把引脚复用为GPIO功能。
比如STM32MP157,在它的设备树工具STM32CubeMX即使把引脚配置为GPIO功能,它也不会在设备树中出现。
原因在于:GPIO走了后门。
现实的芯片中,并没有Pinctrl这样的硬件,它的功能大部分是在GPIO模块中实现的。
Pinctrl是一个软件虚拟处理的概念,它的实现本来就跟GPIO密切相关。
甚至一些引脚默认就是GPIO功能。
按理说:
一个引脚可能被用作GPIO,也可能被用作I2C,GPIO和I2C这些功能时相同低位的。
要用作GPIO,需要先通过Pinctrl把引脚复用为GPIO功能。
但是Pinctrl和GPIO关系密切,当你使用gpiod_get获得GPIO引脚时,它就偷偷地通过Pinctrl把引脚复用为GPIO功能了。
5.2 GPIO和Pinctrl的映射关系
从上图可知:
左边的Pinctrl支持8个引脚,在Pinctrl的内部编号为0~7
图中有2个GPIO控制器
- GPIO0内部引脚编号为0~3,假设在GPIO子系统中全局编号为100 ~ 103
- GPIO1内部引脚编号为0~3,假设在GPIO子系统中全局编号为104 ~ 107
假设我们要使用pin1_1,应该这样做:
- 根据GPIO1的内部编号1,可以换算为Pinctrl子系统中的编号5
- 使用Pinctrl的函数,把第5个引脚配置为GPIO功能
对应数据结构:
5.3 GPIO间接调用Pinctrl的过程
GPIO子系统中的request函数,用来申请某个GPIO引脚,
它会导致Pinctrl子系统中的这2个函数之一被调用:pmxops->gpio_request_enable或pmxops->request
调用关系如下:
1 | gpiod_get |
我们编写GPIO controller驱动程序时,所设置chip->request的函数,一般直接赋值为gpiochip_generic_request,它导致Pinctrl把引脚复用为GPIO功能。
1 | gpiochip_generic_request(struct gpio_chip *chip, unsigned offset) |
Pinctrl子系统中的pin_request函数就会把引脚配置为GPIO功能:
1 | static int pin_request(struct pinctrl_dev *pctldev, int pin, const char *owner, struct pinctrl_gpio_range *gpio_range) |
5.4 如何实现GPIO间接调用Pinctrl
如果不想在使用GPIO引脚时在设备树中设置Pinctrl信息,就要让GPIO和Pinctrl之间建立联系,我们需要做这些事情:
5.4.1 表明GPIO和Pinctrl间的联系
在GPIO controller设备树中使用gpio-ranges来描述它们之间的联系:
GPIO系统中有引脚号
Pinctrl子系统中也有自己的引脚号
2个号码要建立映射关系
在GPIO设备树中使用如下代码建立映射关系
1 | // 当前GPIOA控制器的0号引脚, 对应pinctrlA中的128号引脚, 数量为12 |
5.4.2 解析这些联系
在GPIO驱动程序中,解析跟Pinctrl之间的联系:处理gpio-ranges:
这不需要我们自己写代码
注册gpio_chip时会自动调用
1 | int gpiochip_add_data(struct gpio_chip *chip, void *data) |
5.4.2 编程
在GPIO驱动程序中, 提供:
gpio_chip->request在Pinctrl驱动程序中,提供:
pmxops->gpio_request_enable或pmxops->requestgpiod_get->gpio_chip->request->pmxops->request
1 | pinctrl_virt: virtual_pincontroller { |


























