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子系统的层次

image-20210527103908743

3. 设备树中的gpio信息

在几乎所有 ARM 芯片中, GPIO 都分为几组,每组中有若干个引脚。所以在使用 GPIO 子系统之前,就要先确定:它是哪组的?组里的哪一个?

在设备树中,“ GPIO 组”就是一个 GPIO Controller,这通常都由芯片厂家设置好。我们要做的是找到它名字,比如“ gpio1”,然后指定要用它里面的哪个引脚,比如<&gpio1 0>

3.1 &iomuxc

我们找一个复用为GPIO的实例,我们打开 imx6ul-14x14-evk.dtsi 找到以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
&iomuxc {

//......
pinctrl_usdhc1: usdhc1grp {
fsl,pins = <
MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x17059
MX6UL_PAD_SD1_CLK__USDHC1_CLK 0x10059
MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x17059
MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x17059
MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x17059
MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x17059
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /* SD1 VSELECT */
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 /* SD1 RESET */
>;
};
//......
};

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
2
3
4
5
6
7
8
9
10
11
&usdhc1 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc1>;
pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
keep-power-in-suspend;
wakeup-source;
vmmc-supply = <&reg_sd1_vmmc>;
status = "okay";
};

第 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
2
3
4
5
6
7
8
9
10
11
12
13
gpio1: gpio@209c000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_GPIO1>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-ranges = <&iomuxc 0 23 10>, <&iomuxc 10 17 6>,
<&iomuxc 16 33 16>;
};

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 小节,有如图所示的寄存器地址表:

image-20250330062617339

第 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

image-20250401115131806

每个GPIO Controller用一个gpio_device来表示:

  • 里面每一个gpio引脚用一个gpio_desc来表示。
  • gpio引脚的函数(引脚控制、中断相关),都放在gpio_chip里。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct gpio_device {
int id; // 它是系统中第几个GPIO Controller
struct device dev;
struct cdev chrdev;
struct device *mockdev;
struct module *owner;
struct gpio_chip *chip; // 含有各类操作函数
struct gpio_desc *descs; // 用来描述引脚,每个引脚对应一个 gpio_desc
int base; // 这些GPIO的号码基值
u16 ngpio; // 这个GPIO Controller支持多少个gpio
const char *label; // 标签,名字
void *data;
struct list_head list;

#ifdef CONFIG_PINCTRL
/*
* If CONFIG_PINCTRL is enabled, then gpio controllers can optionally
* describe the actual pin range which they serve in an SoC. This
* information would be used by pinctrl subsystem to configure
* corresponding pins for gpio usage.
*/
struct list_head pin_ranges;
#endif
};

1.2 gpio_chip

我们并不需要自己创建gpio_device,编写驱动时要创建的是gpio_chip,里面提供了:

  • 控制引脚的函数
  • 中断相关的函数
  • 引脚信息:支持多少个引脚?各个引脚的名字?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct gpio_chip {
const char *label;
struct gpio_device *gpiodev;
struct device *parent;
struct module *owner;
// 一些函数
int (*request)(struct gpio_chip *chip, unsigned offset);
void (*free)(struct gpio_chip *chip, unsigned offset);
int (*get_direction)(struct gpio_chip *chip, unsigned offset);
int (*direction_input)(struct gpio_chip *chip, unsigned offset);
int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
int (*get)(struct gpio_chip *chip, unsigned offset);
int (*get_multiple)(struct gpio_chip *chip, unsigned long *mask, unsigned long *bits);
void (*set)(struct gpio_chip *chip, unsigned offset, int value);
//......
int base; // GPIO Controller中引脚号码的基值,个数
u16 ngpio;
const char *const *names;// 每个引脚的名字
bool can_sleep;
//......
};

这一结构体用于描述 GPIO 芯片的属性和操作函数, 可以通过函数指针调用相应的函数来请求、 释放、 设置、 获取 GPIO 的状态和数值等操作, 从而实现对 GPI O 的控制和管理, 需要注意的是这个结构体中的一系列函数都不需要我们来填充, 这些工作都是由芯片原厂工程师来完成的。

1.3 gpio_desc

我们去使用GPIO子系统时,首先是获得某个引脚对应的gpio_descgpio_device表示一个GPIO Controller,也就是GPIO控制器,每个控制器里面支持多个GPIO。在gpio_device中有一个gpio_desc数组,每一引脚有一项gpio_desc

1
2
3
4
5
6
7
8
9
10
11
struct gpio_desc {
struct gpio_device *gdev; // 属于哪个GPIO Controller
unsigned long flags;
/* flag symbols are bit numbers */
#define FLAG_REQUESTED 0
//......
/* Connection label */
const char *label; // 一般等于gpio_chip的label
/* Name of the GPIO */
const char *name; // 引脚名
};

2. gpio 子系统驱动

这个驱动就不详细去看了,imx6ull平台的gpio子系统驱动相关的代码在这里:gpio-mxc.c - drivers/gpio/gpio-mxc.c - Linux source code v4.19.71,这个也是平台设备驱动,驱动的匹配表mxc_gpio_dt_ids如下:

1
2
3
4
5
6
7
8
static const struct of_device_id mxc_gpio_dt_ids[] = {
{ .compatible = "fsl,imx1-gpio", .data = &mxc_gpio_devtype[IMX1_GPIO], },
{ .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devtype[IMX21_GPIO], },
{ .compatible = "fsl,imx31-gpio", .data = &mxc_gpio_devtype[IMX31_GPIO], },
{ .compatible = "fsl,imx35-gpio", .data = &mxc_gpio_devtype[IMX35_GPIO], },
{ .compatible = "fsl,imx7d-gpio", .data = &mxc_gpio_devtype[IMX35_GPIO], },
{ /* sentinel */ }
};

三、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
3
4
5
6
7
8
9
10
11
12
//新接口
gpiod_set_value()
gpiod_direction_input()
gpiod_direction_output()
gpiod_get_value()
gpiod_request()
//旧接口
gpio_set_value()
gpio_direction_input()
gpio_direction_output()
gpio_get_value()
gpio_request()

2. gpio描述符

新的 GPIO 子系统接口是基于描述符(descriptor-based) 来实现的,这个描述符就是gpio_desc结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct gpio_desc {
struct gpio_device gdev; // GPIO 设备结构体
unsigned long flags; // 标志位, 用于表示不同的属性
/* 标志位符号对应的位号 */
#define FLAG_REQUESTED 0 // GPIO 已请求
#define FLAG_IS_OUT 1 // GPIO 用作输出
#define FLAG_EXPORT 2 // 受 sysfs_lock 保护的导出标志
#define FLAG_SYSFS 3 // 通过/sys/class/gpio/control 导出的标志
#define FLAG_ACTIVE_LOW 6 // GPIO 值为低电平时激活
#define FLAG_OPEN_DRAIN 7 // GPIO 为开漏类型
#define FLAG_OPEN_SOURCE 8 // GPIO 为开源类型
#define FLAG_USED_AS_IRQ 9 // GPIO 连接到中断请求(IRQ)
#define FLAG_IS_HOGGED 11 // GPIO 被独占占用
#define FLAG_TRANSITORY 12 // GPIO 在休眠或复位时可能失去值
/* 连接标签 */
const char *label; // GPIO 的名称
const char *name; // GPIO 的名称
};

3. 函数都定义在哪?

四、设备树中添加 gpio 节点模板

1. 创建 test 设备节点

在根节点“/”下创建 test 设备子节点,如下所示:

1
2
3
xxx_test {
/* 节点内容 */
};

2. 添加 pinctrl 信息

我们创建了 pinctrl_test 节点:

1
2
3
4
5
6
// pinctrl_test 节点
pinctrl_test: testgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /* config 是具体设置值 */
>;
};

此节点描述了 test 设备所使用的 GPIO1_IO00 这个 PIN 的信息,我们要将这节点添加到 test 设备节点中,如下所示

1
2
3
4
5
test {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_test>;
/* 其他节点内容 */
};

3. 添加 GPIO 属性信息

我们最后需要在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚,添加完成以后如下所示:

1
2
3
4
5
xxx_test {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_test>;
xxx-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>;
};

定义 GPIO Controller 是芯片厂家需要做的事情,我们只需要引用就可以了,在自己的设备节点中使用属性[<name>-]gpios,不过也不一定,主要还是看厂家怎么写的驱动,例如前面imx6ull的usdhc1中:

1
cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;

我们可以搜一下,就会发现,驱动中是这样写的:

image-20250401151638242