Linux 内核提供了 pinctrl 和 gpio 子系统用于GPIO 驱动,
本章我们就来学习一下如何借助 pinctrl 和 gpio 子系统来简化 GPIO 驱动开发。
45.1 pinctrl 子系统
45.1.1 pinctrl 子系统简介
Linux 驱动讲究驱动分离与分层,pinctrl 和 gpio 子系统就是驱动分离与分层思想下的产物,
驱动分离与分层其实就是按照面向对象编程的设计思想而设计的设备驱动框架,关于驱动的分
离与分层我们后面会讲。本来 pinctrl 和 gpio 子系统应该放到驱动分离与分层章节后面讲解,但
是不管什么外设驱动, GPIO 驱动基本都是必须的,而 pinctrl 和 gpio 子系统又是 GPIO 驱动必
须使用的,所以就将 pintrcl 和 gpio 子系统这一章节提前了。
我们先来回顾一下上一章是怎么初始化 LED 灯所使用的 GPIO,步骤如下:
①、修改设备树,添加相应的节点,节点里面重点是设置 reg 属性, reg 属性包括了 GPIO
相关寄存器。
② 、 获 取 reg 属 性 中 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 和
(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 这两个寄存器地址,并且初始化这两个寄存器,
这两个寄存器用于设置 GPIO1_IO03 这个 PIN 的复用功能、上下拉、速度等。
③、在②里面将 GPIO1_IO03 这个 PIN 复用为了 GPIO 功能,因此需要设置 GPIO1_IO03
这个 GPIO 相关的寄存器,也就是 GPIO1_DR 和 GPIO1_GDIR 这两个寄存器。
总结一下,②中完成对 GPIO1_IO03 这个 PIN 的初始化,设置这个 PIN 的复用功能、上下
拉等,比如将 GPIO_IO03 这个 PIN 设置为 GPIO 功能。③中完成对 GPIO 的初始化,设置 GPIO
为输入/输出等。如果使用过 STM32 的话应该都记得, STM32 也是要先设置某个 PIN 的复用功
能、速度、上下拉等,然后再设置 PIN 所对应的 GPIO。其实对于大多数的 32 位 SOC 而言,引
脚的设置基本都是这两方面,因此 Linux 内核针对 PIN 的配置推出了 pinctrl 子系统,对于 GPIO
的配置推出了 gpio 子系统。本节我们来学习 pinctrl 子系统,下一节再学习 gpio 子系统。
大多数 SOC 的 pin 都是支持复用的,比如 I.MX6ULL 的 GPIO1_IO03 既可以作为普通的
GPIO 使用,也可以作为 I2C1 的 SDA 等等。此外我们还需要配置 pin 的电气特性,比如上/下
拉、速度、驱动能力等等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置
方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。 pinctrl 子系统就是为了解决这个问题而引
入的, pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始
化工作均由 pinctrl 子系统来完成, pinctrl 子系统源码目录为 drivers/pinctrl。
45.1.2 I.MX6ULL 的 pinctrl 子系统驱动
1、 PIN 配置信息详解
要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要
根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信
息。打开 imx6ull.dtsi 文件,找到一个叫做 iomuxc 的节点,如下所示:
示例代码 45.1.2.1 iomuxc 节点内容 1
756 iomuxc: iomuxc@020e0000 {
757 compatible = "fsl,imx6ul-iomuxc";
758 reg = <0x020e0000 0x4000>;
759 };
iomuxc 节点就是 I.MX6ULL 的 IOMUXC 外设对应的节点,看起来内容很少,没看出什么
跟 PIN 的配置有关的内容啊,别急!打开 imx6ull-alientek-emmc.dts,找到如下所示内容:
iomuxc 节点就是 I.MX6ULL 的 IOMUXC 外设对应的节点,看起来内容很少,没看出什么
跟 PIN 的配置有关的内容啊,别急!打开 imx6ull-alientek-emmc.dts,找到如下所示内容:
示例代码 45.1.2.2 iomuxc 节点内容 2
311 &iomuxc {
312 pinctrl-names = "default";
313 pinctrl-0 = <&pinctrl_hog_1>;
314 imx6ul-evk {
315 pinctrl_hog_1: hoggrp-1 {
316 fsl,pins = <
317 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
318 MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
319 MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
320 MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
321 >;
322 };
......
371 pinctrl_flexcan1: flexcan1grp{
372 fsl,pins = <
373 MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020
374 MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020
375 >;
376 };
......
587 pinctrl_wdog: wdoggrp {
588 fsl,pins = <
589 MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY 0x30b0
590 >;
591 };
592 };
593 };
示例代码 45.1.2.2 就是向 iomuxc 节点追加数据,不同的外设使用的 PIN 不同、其配置也不
同,因此一个萝卜一个坑,将某个外设所使用的所有 PIN 都组织在一个子节点里面。示例代码
45.1.2.2 中 pinctrl_hog_1 子节点就是和热插拔有关的 PIN 集合,比如 USB OTG 的 ID 引脚。
pinctrl_flexcan1 子节点是 flexcan1 这个外设所使用的 PIN, pinctrl_wdog 子节点是 wdog 外设所
使用的 PIN。如果需要在 iomuxc 中添加我们自定义外设的 PIN,那么需要新建一个子节点,然
后将这个自定义外设的所有 PIN 配置信息都放到这个子节点中。
将其与示例代码 45.1.2.1 结合起来就可以得到完成的 iomuxc 节点,如下所示:
示例代码 45.1.2.3 完整的 iomuxc 节点
1 iomuxc: iomuxc@020e0000 {
2 compatible = "fsl,imx6ul-iomuxc";
3 reg = <0x020e0000 0x4000>;
4 pinctrl-names = "default";
5 pinctrl-0 = <&pinctrl_hog_1>;
6 imx6ul-evk {
7 pinctrl_hog_1: hoggrp-1 {
8 fsl,pins = <
9 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
10 MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
11 MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
12 MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
13 >;
......
16 };
17 };
18 };
第 2 行, compatible 属性值为“ fsl,imx6ul-iomuxc”,前面讲解设备树的时候说过, Linux 内
核会根据 compatbile 属性值来查找对应的驱动文件,所以我们在 Linux 内核源码中全局搜索字
符串“ fsl,imx6ul-iomuxc”就会找到 I.MX6ULL 这颗 SOC 的 pinctrl 驱动文件。稍后我们会讲解
这个 pinctrl 驱动文件。
第 9~12 行,pinctrl_hog_1 子节点所使用的 PIN 配置信息,我们就以第 9 行的 UART1_RTS_B
这个 PIN 为例,讲解一下如何添加 PIN 的配置信息, UART1_RTS_B 的配置信息如下:
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
首先说明一下, UART1_RTS_B 这个 PIN 是作为 SD 卡的检测引脚,也就是通过此 PIN 就
可 以 检 测 到 SD 卡 是 否 有 插 入 。 UART1_RTS_B 的 配 置 信 息 分 为 两 部 分 :
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 和 0x17059
我们重点来看一下这两部分是什么含义,前面说了,对于一个 PIN 的配置主要包括两方面,
一个是设置这个 PIN 的复用功能,另一个就是设置这个 PIN 的电气特性。所以我们可以大胆的
猜测 UART1_RTS_B 的这两部分配置信息一个是设置 UART1_RTS_B 的复用功能,一个是用来
设置 UART1_RTS_B 的电气特性。
首先来看一下 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19,这是一个宏定义,定义在文件
arch/arm/boot/dts/imx6ul-pinfunc.h 中, imx6ull.dtsi 会引用 imx6ull-pinfunc.h 这个头文件,而
imx6ull-pinfunc.h 又会引用 imx6ul-pinfunc.h 这个头文件(绕啊绕!)。从这里可以看出,可以在
设备树中引用 C 语言中.h 文件中的内容。 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 的宏定
义内容如下:
示例代码 45.1.2.4 UART1_RTS_B 引脚定义
190 #define MX6UL_PAD_UART1_RTS_B__UART1_DCE_RTS 0x0090 0x031C 0x0620 0x0 0x3
191 #define MX6UL_PAD_UART1_RTS_B__UART1_DTE_CTS 0x0090 0x031C 0x0000 0x0 0x0
192 #define MX6UL_PAD_UART1_RTS_B__ENET1_TX_ER 0x0090 0x031C 0x0000 0x1 0x0
193 #define MX6UL_PAD_UART1_RTS_B__USDHC1_CD_B 0x0090 0x031C 0x0668 0x2 0x1
194 #define MX6UL_PAD_UART1_RTS_B__CSI_DATA05 0x0090 0x031C 0x04CC 0x3 0x1
195 #define MX6UL_PAD_UART1_RTS_B__ENET2_1588_EVENT1_OUT 0x0090 0x031C 0x0000 0x4 0x0
196 #define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
197 #define MX6UL_PAD_UART1_RTS_B__USDHC2_CD_B 0x0090 0x031C 0x0674 0x8 0x2
示例代码 45.1.2.4 中一共有 8 个以“ MX6UL_PAD_UART1_RTS_B”开头的宏定义,大家
仔细观察应该就能发现,这 8 个宏定义分别对应 UART1_RTS_B 这个 PIN 的 8 个复用 IO。查
阅《 I.MX6ULL 参考手册》可以知 UART1_RTS_B 的可选复用 IO。
示 例 代 码 196 行 的 宏 定 义 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 表 示 将
UART1_RTS_B 这个 IO 复用为 GPIO1_IO19。此宏定义后面跟着 5 个数字,也就是这个宏定义
的具体值,如下所示:
0x0090 0x031C 0x0000 0x5 0x0
这 5 个值的含义如下所示:
<mux_reg conf_reg input_reg mux_mode input_val>
综上所述可知:
0x0090: mux_reg 寄存器偏移地址,设备树中的 iomuxc 节点就是 IOMUXC 外设对应的节
点 , 根 据 其 reg 属 性 可 知 IOMUXC 外 设 寄 存 器 起 始 地 址 为 0x020e0000 。 因 此
0x020e0000+0x0090=0x020e0090, IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 寄存器地址
正 好 是 0x020e0090 , 大 家 可 以 在 《 IMX6ULL 参 考 手 册 》 中 找 到
IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 这个寄存器的位域图。
因此可知, 0x020e0000+mux_reg 就是 PIN 的复用寄存器地址。
0x031C: conf_reg 寄存器偏移地址,和 mux_reg 一样, 0x020e0000+0x031c=0x020e031c,
这个就是寄存器 IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的地址。
0x0000: input_reg 寄存器偏移地址,有些外设有 input_reg 寄存器,有 input_reg 寄存器的
外设需要配置 input_reg 寄存器。没有的话就不需要设置, UART1_RTS_B 这个 PIN 在做
GPIO1_IO19 的时候是没有 input_reg 寄存器,因此这里 intput_reg 是无效的。
0x5 : mux_reg 寄 存 器 值 , 在 这 里 就 相 当 于 设 置
IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 寄存器为 0x5,也即是设置 UART1_RTS_B 这
个 PIN 复用为 GPIO1_IO19。
0x0: input_reg 寄存器值,在这里无效。
这就是宏 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 的含义,看的比较仔细的同学应该
会发现并没有 conf_reg 寄存器的值, config_reg 寄存器是设置一个 PIN 的电气特性的,这么重
要的寄存器怎么没有值呢?回到示例代码 45.1.2.3 中,第 9 行的内容如下所示:
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 我们上面已经分析了,就剩下了一个 0x17059,
反应快的同学应该已经猜出来了, 0x17059 就是 conf_reg 寄存器值!此值由用户自行设置,通
过此值来设置一个 IO 的上 /下拉、驱动能力和速度等。在这里就相当于设置寄存器
IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的值为 0x17059。
2、 PIN 驱动程序讲解
本小节会涉及到 Linux 驱动分层与分离、 平台设备驱动等还未讲解的知识,所以本小节教
程可以不用看,不会影响后续的实验。如果对 Linux 内核的 pinctrl 子系统实现原理感兴趣的话
可以看本小节。
所有的东西都已经准备好了,包括寄存器地址和寄存器值, Linux 内核相应的驱动文件就
会根据这些值来做相应的初始化。接下来就找一下哪个驱动文件来做这一件事情, iomuxc 节点
中 compatible 属性的值为“ fsl,imx6ul-iomuxc”,在 Linux 内核中全局搜索“ fsl,imx6ul-iomuxc”
字符串就会找到对应的驱动文件。在文件 drivers/pinctrl/freescale/pinctrl-imx6ul.c中有如下内容:
示例代码 45.1.2.5 pinctrl-imx6ul.c 文件代码段
326 static struct of_device_id imx6ul_pinctrl_of_match[] = {
327 {
.compatible = "fsl,imx6ul-iomuxc", .data = &imx6ul_pinctrl_info, },
328 {
.compatible = "fsl,imx6ull-iomuxc-snvs", .data = &imx6ull_snvs_pinctrl_info, },
329 {
}
330 };
331
332 static int imx6ul_pinctrl_probe(struct platform_device *pdev)
333 {
334 const struct of_device_id *match;
335 struct imx_pinctrl_soc_info *pinctrl_info;
337 match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);
339 if (!match)
340 return -ENODEV;
342 pinctrl_info = (struct imx_pinctrl_soc_info *) match->data;
344 return imx_pinctrl_probe(pdev, pinctrl_info);
345 }
346
347 static struct platform_driver imx6ul_pinctrl_driver = {
348 .driver = {
349 .name = "imx6ul-pinctrl",
350 .owner = THIS_MODULE,
351 .of_match_table = of_match_ptr(imx6ul_pinctrl_of_match),
352 },
353 .probe = imx6ul_pinctrl_probe,
354 .remove = imx_pinctrl_remove,
355 };
第 326~330 行,of_device_id 结构体数组,第四十三章讲解设备树的时候说过了,of_device_id
里面保存着这个驱动文件的兼容性值,设备树中的 compatible 属性值会和 of_device_id 中的所
有兼容性字符串比较,查看是否可以使用此驱动。 imx6ul_pinctrl_of_match 结构体数组一共有两
个兼容性字符串, 分别为“ fsl,imx6ul-iomuxc”和“ fsl,imx6ull-iomuxc-snvs”,因此 iomuxc 节点
与此驱动匹配,所以 pinctrl-imx6ul.c 会完成 I.MX6ULL 的 PIN 配置工作。
第 347~355 行, platform_driver 是平台设备驱动,这个是我们后