LV06-02-字符设备驱动-01-pinctrl与gpio子系统-02-gpio

本文主要是pinctrl与gpio子系统——gpio子系统相关笔记。若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows版本 windows11
Ubuntu版本 Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
Win32DiskImager Win32DiskImager v1.0
buildroot 2023.05.1版本
点击查看本文参考资料
分类 网址 说明
官方网站 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内核的仓库
https://elixir.bootlin.com/linux/latest/source 在线阅读linux kernel源码
nxp-imx/linux-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga NXP linux内核仓库tags中的rel_imx_4.1.15_2.1.0_ga
nxp-imx/uboot-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga NXP u-boot仓库tags中的rel_imx_4.1.15_2.1.0_ga
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)

一、概述

pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。

gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。 gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO, Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。

二、I.MX6ULL 的 gpio 子系统驱动

1. 设备树中的 gpio 信息

I.MX6ULL-ALPHA 开发板上的 UART1_RTS_B 做为 SD 卡的检测引脚, UART1_RTS_B 复用为 GPIO1_IO19,通过读取这个 GPIO 的高低电平就可以知道 SD 卡有没有插入。首先肯定是将 UART1_RTS_B 这个 PIN 复用为 GPIO1_IO19,并且设置电气属性,也就是上一小节学习的pinctrl 节点。打开 imx6ull-alpha-emmc.dts, UART1_RTS_B 这个 PIN 的 pincrtl 设置如下:

image-20230826160108109

pinctrl 配置好以后就是设置 gpio 了, SD 卡驱动程序通过读取 GPIO1_IO19 的值来判断 SD卡有没有插入,但是 SD 卡驱动程序怎么知道 CD 引脚连接的 GPIO1_IO19 呢?肯定是需要设备树告诉驱动啊!在设备树中 SD 卡节点下添加一个属性来描述 SD 卡的 CD 引脚就行了, SD卡驱动直接读取这个属性值就知道 SD 卡的 CD 引脚使用的是哪个 GPIO 了。 SD 卡连接在I.MX6ULL 的 usdhc1 接口上,在 imx6ull-alpha-emmc.dts 中找到名为“usdhc1”的节点,这个节点就是 SD 卡设备节点,如下所示

image-20230826160435363

第 757 行,此行本来没有,是添加的注释信息, usdhc1 节点作为 SD 卡设备总节点, usdhc1 节点需要描述 SD 卡所有的信息,因为驱动要使用。本行就是描述 SD 卡的 CD 引脚 pinctrl 信息所在的子节点,因为 SD 卡驱动需要根据 pincrtl 节点信息来设置 CD 引脚的复用功能等。754~756行的 pinctrl-0~2 都是 SD 卡其他 PIN 的 pincrtl 节点信息。但是会发现,其实在 usdhc1 节点中并没有“pinctrl-3 = <&pinctrl_hog_1>”这一行,也就是说并没有指定CD 引脚的 pinctrl 信息,那么 SD 卡驱动就没法设置 CD 引脚的复用功能啊?这个不用担心,因为在“iomuxc”节点下引用了 pinctrl_hog_1 这个节点,所以Linux 内核中的 iomuxc 驱动就会自动初始化 pinctrl_hog_1节点下的所有 PIN。

第 758 行,属性“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 信号了,打开 imx6ull.dtsi,在里面找到如下所示内容 :

image-20230826160832206

gpio1 节点信息描述了 GPIO1 控制器的所有信息,重点就是 GPIO1 外设寄存器基地址以及兼容属性 。关于I.MX 系列 SOC 的GPIO控制器绑定信息可以查看 文档 Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt。

第 505 行,设置 gpio1 节点的 compatible 属性有两个,分别为“fsl,imx6ul-gpio”和“fsl,imx35-gpio”,在 Linux 内核中搜索这两个字符串就可以找到 I.MX6UL 的 GPIO 驱动程序。

第 506 行, reg 属性设置了 GPIO1 控制器的寄存器基地址为 0X0209C000,可以打开《I.MX6ULL 参考手册》找到“Chapter 28:General Purpose Input/Output(GPIO)”章节第 28.5 小节,有如图所示的寄存器地址表:

image-20230826161019216

可以看出, GPIO1 控制器的基地址就是 0X0209C000。

第 509 行,“gpio-controller”表示 gpio1 节点是个 GPIO 控制器。

第 510 行,“#gpio-cells”属性和“#address-cells”类似, #gpio-cells 应该为 2,表示一共有两个 cell,第一个 cell 为 GPIO 编号,比如“&gpio1 3”就表示 GPIO1_IO03。第二个 cell 表示GPIO 极 性 , 如 果 为 0(GPIO_ACTIVE_HIGH) 的 话表示高电平有效,如果为1(GPIO_ACTIVE_LOW)的话表示低电平有效。

2. GPIO 驱动程序

gpio1 节点的 compatible 属性描述了兼容性,在 Linux 内核中搜索“fsl,imx6ul-gpio”和“fsl,imx35-gpio”这两个字符串,查找 GPIO 驱动文件。 drivers/gpio/gpio-mxc.c 就是 I.MX6ULL的 GPIO 驱动文件,在此文件中有如下所示 of_device_id 匹配表:

image-20230826161256859

第 156 行的 compatible 值为“fsl,imx35-gpio”,和 gpio1 的 compatible 属性匹配,因此 gpiomxc.c 就是 I.MX6ULL 的 GPIO 控制器驱动文件。 gpio-mxc.c 所在的目录为 drivers/gpio,打开这个目录可以看到很多芯片的 gpio 驱动文件, “gpiolib”开始的文件是 gpio 驱动的核心文件:

image-20230826161420932

我们重点来看一下 gpio-mxc.c 这个文件,在 gpio-mxc.c 文件中有如下所示内容 :

image-20230826161554787

可以看出 GPIO 驱动也是个平台设备驱动,因此当设备树中的设备节点与驱动的of_device_id 匹配以后 probe 函数就会执行,在这里就是 mxc_gpio_probe 函数,这个函数就是I.MX6ULL 的 GPIO 驱动入口函数。 这里就不详细分析了,流程都跟platform平台总线驱动类似。

三、gpio 子系统 API 函数

1. gpio_request()

1
2
/* 函数声明 */
int gpio_request(unsigned gpio, const char *label);

【函数说明】该函数用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_request进行申请 。

【函数参数】

  • gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信息,此函数会返回这个 GPIO 的标号。
  • label:给 gpio 设置个名字 。

【返回值】 int 类型,0,申请成功;其他值,申请失败。

【使用格式】none

【注意事项】 none

2. gpio_free()

1
2
/* 函数声明 */
void gpio_free(unsigned gpio);

【函数说明】如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放

【函数参数】

  • gpio:要释放的 gpio 标号。

【返回值】 none

【使用格式】none

【注意事项】 none

3. gpio_direction_input ()

1
2
/* 函数声明 */
int gpio_direction_input(unsigned gpio);

【函数说明】该函数用于设置某个 GPIO 为输入 。

【函数参数】

  • gpio:要设置为输入的 GPIO 标号。

【返回值】 int 类型,0,设置成功;负值,设置失败。

【使用格式】none

【注意事项】 none

4. gpio_direction_output ()

1
2
/* 函数声明 */
int gpio_direction_output(unsigned gpio, int value);

【函数说明】该函数用于设置某个 GPIO 为输出,并且设置默认输出值。

【函数参数】

  • gpio:要设置为输出的 GPIO 标号。
  • value: GPIO 默认输出值。

【返回值】 int 类型,0,设置成功;负值,设置失败。

【使用格式】none

【注意事项】 none

5. gpio_get_value ()

1
2
3
/* 函数声明 */
#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio);

【函数说明】该函数用于获取某个 GPIO 的值(0 或 1),此函数是个宏。

【函数参数】

  • gpio:要获取的 GPIO 标号。

【返回值】 int 类型,非负值,得到的 GPIO 值;负值,获取失败。

【使用格式】none

【注意事项】 none

6. gpio_set_value ()

1
2
3
/* 函数声明 */
#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value);

【函数说明】该函数用于设置某个 GPIO 的值,此函数是个宏。

【函数参数】

  • gpio:要设置的 GPIO 标号。
  • value: 要设置的值。

【返回值】 none

【使用格式】none

【注意事项】 none

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

前边我们了解了如何创建 test 设备的 pinctrl 节点。现在我们来学习一下如何创建 test 设备的 GPIO 节点。

1. 创建 test 设备节点

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

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

2. 添加 pinctrl 信息

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

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

第 2 行,添加 pinctrl-names 属性,此属性描述 pinctrl 名字为“default”。

第 3 行,添加 pinctrl-0 节点,此节点引用之前创建的 pinctrl_test 节点,表示 tset 设备的所使用的 PIN 信息保存在 pinctrl_test 节点中。

3. 添加 GPIO 属性信息

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

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

第 4 行, test 设备所使用的 gpio。

4. 与 gpio 相关的 OF 函数

4.1 of_gpio_named_count 函数

of_gpio_named_count 函数用于获取设备树某个属性里面定义了几个 GPIO 信息,要注意的是空的 GPIO 信息也会被统计到,比如 :

1
2
3
4
gpios = <0
&gpio1 1 2
0 &
gpio2 3 4>;

上述代码的“gpios”节点一共定义了 4 个 GPIO,但是有 2 个是空的,没有实际的含义。通过 of_gpio_named_count 函数统计出来的 GPIO 数量就是 4 个,此函数原型如下:

1
2
/* 函数声明 */
int of_gpio_named_count(struct device_node *np, const char *propname);

【函数说明】该函数用于获取设备树某个属性里面定义了几个 GPIO 信息。

【函数参数】

  • np:设备节点。
  • propname:要统计的 GPIO 属性。

【返回值】 int类型,正值,统计到的 GPIO 数量;负值,失败。

【使用格式】none

【注意事项】 none

4.2 of_gpio_count 函数

1
2
/* 函数声明 */
int of_gpio_count(struct device_node *np);

【函数说明】该函数和 of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“gpios”这个属性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息。

【函数参数】

  • np:设备节点。

【返回值】 int类型,正值,统计到的 GPIO 数量;负值,失败。

【使用格式】none

【注意事项】 none

4.3 of_get_named_gpio 函数

1
2
3
4
/* 函数声明 */
int of_get_named_gpio(struct device_node *np,
const char *propname,
int index);

【函数说明】该函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编号。

【函数参数】

  • np:设备节点。
  • propname:包含要获取 GPIO 信息的属性名。
  • index: GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO的编号,如果只有一个 GPIO 信息的话此参数为 0。

【返回值】 int类型,正值,获取到的 GPIO 编号;负值,失败。

【使用格式】none

【注意事项】 none