LV09-01-pinctrl和gpio子系统-03-gpio子系统简介
再来看一下gpio子系统?来了解一下。若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 | Windows | Windows11 |
Ubuntu | Ubuntu20.04.2的64位版本 | |
VMware® Workstation 17 Pro | 17.6.0 build-24238078 | |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) | |
Win32DiskImager | Win32DiskImager v1.0 | |
Linux开发板环境 | Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04 | |
linux内核 | linux-4.19.71(NXP官方提供) |
点击查看本文参考资料
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.nxp.com.cn/ | NXP官方网站 | |
https://www.nxpic.org.cn/ | NXP 官方社区 | |
https://u-boot.readthedocs.io/en/latest/ | u-boot官网 | |
https://www.kernel.org/ | linux内核官网 |
点击查看相关文件下载
分类 | 网址 | 说明 |
NXP | https://github.com/nxp-imx | NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库 |
nxp-imx/linux-imx/releases/tag/v4.19.71 | NXP linux内核仓库tags中的v4.19.71 | |
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 | NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0 | |
I.MX6ULL | i.MX 6ULL Applications Processors for Industrial Products | I.MX6ULL 芯片手册(datasheet,可以在线查看) |
i.MX 6ULL Applications ProcessorReference Manual | I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网) | |
Source Code | https://elixir.bootlin.com/linux/latest/source | linux kernel源码 |
kernel/git/stable/linux.git - Linux kernel stable tree | linux kernel源码(官网,tag 4.19.71) | |
https://elixir.bootlin.com/u-boot/latest/source | uboot源码 |
一、gpio子系统简介
1. gpio 子系统有什么用?
pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。
gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。
gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO, Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。
简单来说,就是管理GPIO,既能支持芯片本身的GPIO,也能支持扩展的GPIO。提供统一的、简便的访问接口,实现:输入、输出、中断。
2. GPIO子系统的层次

3. 设备树中的gpio信息
在几乎所有 ARM 芯片中, GPIO 都分为几组,每组中有若干个引脚。所以在使用 GPIO 子系统之前,就要先确定:它是哪组的?组里的哪一个?
在设备树中,“ GPIO 组”就是一个 GPIO Controller,这通常都由芯片厂家设置好。我们要做的是找到它名字,比如“ gpio1”,然后指定要用它里面的哪个引脚,比如<&gpio1 0>
。
3.1 &iomuxc
我们找一个复用为GPIO的实例,我们打开 imx6ul-14x14-evk.dtsi 找到以下内容:
1 | &iomuxc { |
I.MX6ULL-ALPHA 开发板上的 UART1_RTS_B 做为 SD 卡的检测引脚, UART1_RTS_B 复用为 GPIO1_IO19,通过读取这个 GPIO 的高低电平就可以知道 SD 卡有没有插入。首先肯定是将 UART1_RTS_B 这个 PIN 复用为 GPIO1_IO19,并且设置电气属性,也就是前面学习的pinctrl 节点。
pinctrl 配置好以后就是设置 gpio 了, SD 卡驱动程序通过读取 GPIO1_IO19 的值来判断 SD卡有没有插入,但是 SD 卡驱动程序怎么知道 CD 引脚连接的 GPIO1_IO19 呢?肯定是需要设备树告诉驱动啊!
3.2 &usdhc1节点
在设备树中 SD 卡节点下添加一个属性来描述 SD 卡的 CD 引脚就行了, SD卡驱动直接读取这个属性值就知道 SD 卡的 CD 引脚使用的是哪个 GPIO 了。 SD 卡连接在I.MX6ULL 的 usdhc1 接口上,我们打开 imx6ul-14x14-evk.dtsi 找到以下内容:
1 | &usdhc1 { |
第 6 行:属性“cd-gpios”描述了 SD 卡的 CD 引脚使用的哪个 IO。属性值一共有三个,我们来看一下这三个属性值的含义,“&gpio1”表示 CD 引脚所使用的 IO 属于 GPIO1 控制器,“19”表示 GPIO1 控制器的第 19 号 IO,通过这两个值 SD 卡驱动程序就知道 CD 引脚使用了 GPIO1_IO19这 GPIO。“GPIO_ACTIVE_LOW”表示低电平有效,如果改为“GPIO_ACTIVE_HIGH”就表示高电平有效。
根据上面这些信息, SD 卡驱动程序就可以使用 GPIO1_IO19 来检测 SD 卡的 CD 信号了。
3.3 GPIO1 控制器
GPIO控制器的设备树中,有两项是必须的:
- gpio-controller : 表明这是一个GPIO控制器,它下面有很多引脚。
- gpio-cells : 指定使用多少个cell(就是整数)来描述一个引脚。
我们打开imx6ul.dtsi:
1 | gpio1: gpio@209c000 { |
gpio1 节点信息描述了 GPIO1 控制器的所有信息,重点就是 GPIO1 外设寄存器基地址以及兼容属性 。关于 I.MX 系列SOC的GPIO控制器绑定信息可以看fsl-imx-gpio.txt - Documentation/devicetree/bindings/gpio/fsl-imx-gpio.txt。
第 2 行:设置 gpio1 节点的 compatible 属性有两个,分别为“fsl,imx6ul-gpio”和“fsl,imx35- gpio”,在 Linux 内核中搜索这两个字符串就可以找到 I.MX6UL 的 GPIO 驱动程序。
第 3 行:reg 属性设置了 GPIO1 控制器的寄存器基地址为 0X0209C000,可以打开《I.MX6ULL 参考手册》找到“Chapter 28:General Purpose Input/Output(GPIO)”章节第 28.5 小节,有如图所示的寄存器地址表:

第 7 行,“gpio-controller”表示 gpio1 节点是个 GPIO 控制器。
第 8 行,“#gpio-cells”属性和“#address-cells”类似, #gpio-cells 应该为 2,表示一共有两个 cell,第一个 cell 为 GPIO 编号,比如“&gpio1 3”就表示 GPIO1_IO03。第二个 cell 表示GPIO 极性(就是有效电平) , 如果为 0(GPIO_ACTIVE_HIGH) 的话表示高电平有效,如果为1(GPIO_ACTIVE_LOW)的话表示低电平有效。
二、gpio 驱动程序
gpio1 节点的 compatible 属性描述了兼容性,在 Linux 内核中搜索“fsl,imx6ul-gpio”和“fsl,imx35-gpio”这两个字符串,查找 GPIO 驱动文件。gpio-mxc.c - drivers/gpio/gpio-mxc.c就是 I.MX6ULL的 GPIO 驱动文件。
这里就不再详细去分析了,也挺复杂的,感觉会用就行了。
1. 核心数据结构
这里大概了解下重要的3个核心数据结构。记住GPIO Controller的要素,这有助于理解它的驱动程序:
- 一个GPIO Controller里有多少个引脚?有哪些引脚?
- 需要提供函数,设置引脚方向、读取/设置数值
- 需要提供函数,把引脚转换为中断
以Linux面向对象编程的思想,一个GPIO Controller必定会使用一个结构体来表示,这个结构体必定含有这些信息:
- GPIO引脚信息
- 控制引脚的函数
- 中断相关的函数
1.1 gpio_device

每个GPIO Controller用一个gpio_device来表示:
1 | struct gpio_device { |
1.2 gpio_chip
我们并不需要自己创建gpio_device,编写驱动时要创建的是gpio_chip,里面提供了:
- 控制引脚的函数
- 中断相关的函数
- 引脚信息:支持多少个引脚?各个引脚的名字?
1 | struct gpio_chip { |
这一结构体用于描述 GPIO 芯片的属性和操作函数, 可以通过函数指针调用相应的函数来请求、 释放、 设置、 获取 GPIO 的状态和数值等操作, 从而实现对 GPI O 的控制和管理, 需要注意的是这个结构体中的一系列函数都不需要我们来填充, 这些工作都是由芯片原厂工程师来完成的。
1.3 gpio_desc
我们去使用GPIO子系统时,首先是获得某个引脚对应的gpio_desc。gpio_device表示一个GPIO Controller,也就是GPIO控制器,每个控制器里面支持多个GPIO。在gpio_device中有一个gpio_desc数组,每一引脚有一项gpio_desc。
1 | struct gpio_desc { |
2. gpio 子系统驱动
这个驱动就不详细去看了,imx6ull平台的gpio子系统驱动相关的代码在这里:gpio-mxc.c - drivers/gpio/gpio-mxc.c - Linux source code v4.19.71,这个也是平台设备驱动,驱动的匹配表mxc_gpio_dt_ids如下:
1 | static const struct of_device_id mxc_gpio_dt_ids[] = { |
三、gpio 子系统 API 函数
1. 函数版本说明
在目前的 Linux 内核主线中, GPIO(通用输入/输出) 子系统存在两个版本, 这里将两个版本区分为新版本和旧版本。 新版本 GPIO 子系统接口是基于描述符(descriptor-based) 来实现的, 而旧版本的 GPIO 子系统接口是基于整数(integer-based) 来实现的, 在 Linux 内核中为了保持向下的兼容性, 旧版本的接口在最新的内核版本中仍然得到支持, 而随着时间的推移, 新版本的 GPIO 子系统接口会越来越完善, 最终完全取代旧版本, 所以主要还是学习新版本的 GPIO 子系统接口。
新的 GPIO 子系统接口需要与与设备树(Device Tree) 结合使用。 使用设备树和新的 GPIO接口可以更加灵活地配置和管理系统中的 GPIO 资源, 提供了更好的可扩展性和可移植性。 所以如果没有设备树, 就无法使用新的 GPIO 接口。
那要如何对新旧 GPIO 子系统接口进行区分呢, 一个明显的区别是新的 GPIO 子系统接口使用了以 “gpiod_“作为前缀的函数命名约定, 而旧的 GPIO 子系统接口使用了以”gpio_“作为前缀的函数命名约定。
一些区分新旧 GPIO 子系统接口的示例如下所示:
1 | //新接口 |
2. gpio描述符
新的 GPIO 子系统接口是基于描述符(descriptor-based) 来实现的,这个描述符就是gpio_desc结构体:
1 | struct gpio_desc { |
3. 函数都定义在哪?
consumer.h - include/linux/gpio/consumer.h - Linux source code v4.19.71
gpiolib.c - drivers/gpio/gpiolib.c - Linux source code v4.19.71
四、设备树中添加 gpio 节点模板
1. 创建 test 设备节点
在根节点“/”下创建 test 设备子节点,如下所示:
1 | xxx_test { |
2. 添加 pinctrl 信息
我们创建了 pinctrl_test 节点:
1 | // pinctrl_test 节点 |
此节点描述了 test 设备所使用的 GPIO1_IO00 这个 PIN 的信息,我们要将这节点添加到 test 设备节点中,如下所示
1 | test { |
3. 添加 GPIO 属性信息
我们最后需要在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚,添加完成以后如下所示:
1 | xxx_test { |
定义 GPIO Controller 是芯片厂家需要做的事情,我们只需要引用就可以了,在自己的设备节点中使用属性[<name>-]gpios
,不过也不一定,主要还是看厂家怎么写的驱动,例如前面imx6ull的usdhc1中:
1 | cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>; |
我们可以搜一下,就会发现,驱动中是这样写的:
