Skip to content

LV505-gpio子系统简介

再来看一下 gpio 子系统?来了解一下。若笔记中有错误或者不合适的地方,欢迎批评指正 😃。

一、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 找到以下内容:

c
&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 找到以下内容:

c
&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

c
			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-20260121155327390

第 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 子系统 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 子系统接口的示例如下所示:

c
//新接口
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 结构体:

c
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 设备子节点,如下所示:

c
xxx_test {
	/* 节点内容 */
};

2. 添加 pinctrl 信息

我们创建了 pinctrl_test 节点:

c
    // pinctrl_test 节点
    pinctrl_test: testgrp {
        fsl,pins = <
            MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /* config 是具体设置值 */
        >;
    };

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

c
test {
	pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_test>;
    /* 其他节点内容 */
};

3. 添加 GPIO 属性信息

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

c
xxx_test {
	pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_test>;
    xxx-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>;
};

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

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

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

image-20250401151638242