pinctrl 和gpio 子系统实验

Posted 行稳方能走远

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pinctrl 和gpio 子系统实验相关的知识,希望对你有一定的参考价值。

上一章我们编写了基于设备树的LED 驱动,但是驱动的本质还是没变,都是配置LED 灯所使用的GPIO 寄存器,驱动开发方式和裸机基本没啥区别。Linux 是一个庞大而完善的系统,尤其是驱动框架,像GPIO 这种最基本的驱动不可能采用“原始”的裸机驱动开发方式,否则就相当于你买了一辆车,结果每天推着车去上班。Linux 内核提供了pinctrl 和gpio 子系统用于GPIO 驱动,本章我们就来学习一下如何借助pinctrl 和gpio 子系统来简化GPIO 驱动开发。

pinctrl 子系统

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。

I.MX6ULL 的pinctrl 子系统驱动

1、PIN 配置信息详解
要使用pinctrl 子系统,我们需要在设备树里面设置PIN 的配置信息,毕竟pinctrl 子系统要根据你提供的信息来配置PIN 功能,一般会在设备树里面创建一个节点来描述PIN 的配置信息。打开imx6ull.dtsi 文件,找到一个叫做iomuxc 的节点,如下所示:

756 iomuxc: iomuxc@020e0000 {
757 compatible = "fsl,imx6ul-iomuxc";
758 reg = <0x020e0000 0x4000>;
759 };

iomuxc 节点就是I.MX6ULL 的IOMUXC 外设对应的节点,看起来内容很少,没看出什么跟PIN 的配置有关的内容啊,别急!打开imx6ull-alientek-emmc.dts,找到如下所示内容:

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

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 的宏定义内容如下:

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 如图45.1.2.1 所示:

示例代码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 这个寄存器的位域图,如图45.1.2.2 所示:

因此可知,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 中有如下内容:

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 { /* sentinel */ }
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;
336
337 match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);
338
339 if (!match)

第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 是平台设备驱动,这个是我们后面章节要讲解的内容,platform_driver 是个结构体,有个probe 成员变量。在这里大家只需要知道,当设备和驱动匹配成功以后platform_driver 的probe 成员变量所代表的函数就会执行,在353 行设置probe 成员变量为imx6ul_pinctrl_probe 函数,因此在本章实验中imx6ul_pinctrl_probe 这个函数就会执行,可以认为imx6ul_pinctrl_probe 函数就是I.MX6ULL 这个SOC 的PIN 配置入口函数。以此为入口,如图45.1.2.3 所示的函数调用路径:

在图45.1.2.3 中函数imx_pinctrl_parse_groups 负责获取设备树中关于PIN 的配置信息,也就是我们前面分析的那6 个u32 类型的值。处理过程如下所示:

488 /*
489 * Each pin represented in fsl,pins consists of 5 u32 PIN_FUNC_ID
490 * and 1 u32 CONFIG, so 24 types in total for each pin.
491 */

第496 和497 行,设备树中的mux_reg 和conf_reg 值会保存在info 参数中,input_reg、mux_mode、input_val 和config 值会保存在grp 参数中。
第560~564 行,获取mux_reg、conf_reg、input_reg、mux_mode 和input_val 值。
第570 行,获取config 值。
接下来看一下函数pinctrl_register,此函数用于向Linux 内核注册一个PIN 控制器,此函数原型如下:

struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
struct device *dev,
void *driver_data)

参数pctldesc 非常重要,因为此参数就是要注册的PIN 控制器,PIN 控制器用于配置SOC的PIN 复用功能和电气特性。参数pctldesc 是pinctrl_desc 结构体类型指针,pinctrl_desc 结构体如下所示:

128 struct pinctrl_desc {
129 const char *name;
130 struct pinctrl_pin_desc const *pins;
131 unsigned int npins;
132 const struct pinctrl_ops *pctlops;
133 const struct pinmux_ops *pmxops;
134 const struct pinconf_ops *confops;
135 struct module *owner;
136 #ifdef CONFIG_GENERIC_PINCONF
137 unsigned int num_custom_params;
138 const struct pinconf_generic_params *custom_params;
139 const struct pin_config_item *custom_conf_items;
140 #endif
141 };

第132~124 行,这三个“_ops”结构体指针非常重要!!!因为这三个结构体就是PIN 控制器的“工具”,这三个结构体里面包含了很多操作函数,通过这些操作函数就可以完成对某一个PIN 的配置。pinctrl_desc 结构体需要由用户提供,结构体里面的成员变量也是用户提供的。但是这个用户并不是我们这些使用芯片的程序员,而是半导体厂商,半导体厂商发布的Linux 内核源码中已经把这些工作做完了。比如在imx_pinctrl_probe 函数中可以找到如下所示代码:

648 int imx_pinctrl_probe(struct platform_device *pdev,
649 struct imx_pinctrl_soc_info *info)
650 {
651 struct device_node *dev_np = pdev->dev.of_node;
652 struct device_node *np;
653 struct imx_pinctrl *ipctl;
654 struct resource *res;
655 struct pinctrl_desc *imx_pinctrl_desc;
......
663
664 imx_pinctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*imx_pinctrl_desc),
665 GFP_KERNEL);
666 if (!imx_pinctrl_desc)
667 return -ENOMEM;
......
705
706 imx_pinctrl_desc->name = dev_name(&pdev->dev);
707 imx_pinctrl_desc->pins = info->pins;
708 imx_pinctrl_desc->npins = info->npins;
709 imx_pinctrl_desc->pctlops = &imx_pctrl_ops;
710 imx_pinctrl_desc->pmxops = &imx_pmx_ops;
711 imx_pinctrl_desc->confops = &imx_pinconf_ops;
712 imx_pinctrl_desc->owner = THIS_MODULE;
......
723 ipctl->pctl = pinctrl_register(imx_pinctrl_desc, &pdev->dev, ipctl);
......
732 }

第655 行,定义结构体指针变量imx_pinctrl_desc。
第664 行,向指针变量imx_pinctrl_desc 分配内存。
第706~712 行,初始化imx_pinctrl_desc 结构体指针变量,重点是pctlops、pmxops 和confops这三个成员变量,分别对应imx_pctrl_ops、imx_pmx_ops 和imx_pinconf_ops 这三个结构体。
第723 行,调用函数pinctrl_register 向Linux 内核注册imx_pinctrl_desc,注册以后Linux 内核就有了对I.MX6ULL 的PIN 进行配置的工具。
imx_pctrl_ops、imx_pmx_ops 和imx_pinconf_ops 这三个结构体定义如下:

174 static const struct pinctrl_ops imx_pctrl_ops = {
175 .get_groups_count = imx_get_groups_count,
176 .get_group_name = imx_get_group_name,
177 .get_group_pins = imx_get_group_pins,
178 .pin_dbg_show = imx_pin_dbg_show,
179 .dt_node_to_map = imx_dt_node_to_map,
180 .dt_free_map = imx_dt_free_map,
181
182 };
......
374 static const struct pinmux_ops imx_pmx_ops = {
375 .get_functions_count = imx_pmx_get_funcs_count,
376 .get_function_name = imx_pmx_get_func_name,
377 .get_function_groups = imx_pmx_get_groups,
378 .set_mux = imx_pmx_set,
379 .gpio_request_enable = imx_pmx_gpio_request_enable,
380 .gpio_set_direction = imx_pmx_gpio_set_direction,
381 };
......
481 static const struct pinconf_ops imx_pinconf_ops = {
482 .pin_config_get = imx_pinconf_get,
483 .pin_config_set = imx_pinconf_set,
484 .pin_config_dbg_show = imx_pinconf_dbg_show,
485 .pin_config_group_dbg_show = imx_pinconf_group_dbg_show,
486 };

示例代码45.1.2.9 中这三个结构体下的所有函数就是I.MX6ULL 的PIN 配置函数,我们就此打住,不再去分析这些函数了,否则本章就没完没了了,有兴趣的可以去看一下。

设备树中添加pinctrl 节点模板

我们已经对pinctrl 有了比较深入的了解,接下来我们学习一下如何在设备树中添加某个外设的PIN 信息。关于I.MX 系列SOC 的pinctrl 设备树绑定信息可以参考文档
Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt。这里我们虚拟一个名为“test”的设备,test 使用了GPIO1_IO00 这个PIN 的GPIO 功能,pinctrl 节点添加过程如下:

1、创建对应的节点
同一个外设的PIN 都放到一个节点里面,打开imx6ull-alientek-emmc.dts,在iomuxc 节点中的“imx6ul-evk”子节点下添加“pinctrl_test”节点,注意!节点前缀一定要为“pinctrl_”。添加完成以后如下所示:

1 pinctrl_test: testgrp {
2 /* 具体的PIN信息*/
3 };

2、添加“fsl,pins”属性
设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为“fsl,pins”,因为对于I.MX 系列SOC 而言,pinctrl 驱动程序是通过读取“fsl,pins”属性值来获取PIN 的配置信息,完成以后如下所示:

1 pinctrl_test: testgrp {
2 fsl,pins = <
3 /* 设备所使用的PIN配置信息*/
4 >;
5 };

3、在“fsl,pins”属性中添加PIN 配置信息
最后在“fsl,pins”属性中添加具体的PIN 配置信息,完成以后如下所示:

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


至此,我们已经在imx6ull-alientek-emmc.dts 文件中添加好了test 设备所使用的PIN 配置信息。

gpio 子系统

gpio 子系统简介

上一小节讲解了pinctrl 子系统,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-alientek-emmc.dts,UART1_RTS_B 这个PIN 的pincrtl 设置如下:

316 pinctrl_hog_1: hoggrp-1 {
317 fsl,pins = <
318 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
......
322 >;
323 };

第318 行,设置UART1_RTS_B 这个PIN 为GPIO1_IO19。

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

760 &usdhc1 {
761 pinctrl-names = "default", "state_100mhz", "state_200mhz";
762 pinctrl-0 = <&pinctrl_usdhc1>;
763 pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
764 pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
765 /* pinctrl-3 = <&pinctrl_hog_1>; */
766 cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
767 keep-power-in-suspend;
768 enable-sdio-wakeup;
769 vmmc-supply = <&reg_sd1_vmmc>;
770 status = "okay";
771 };

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

第766 行,属性“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,在里面找到如下所示内容:

504 gpio1: gpio@0209c000 {
505 compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
506 reg = <0x0209c000 0x4000>;
507 interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
508 <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
509 gpio-controller;
510 #gpio-cells = <2>;
511 interrupt-controller;
512 #interrupt-cells = <2>;
513 };

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

从图45.2.2.1 可以看出,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 驱动程序简介
本小节会涉及到Linux 驱动分层与分离、平台设备驱动等还未讲解的知识,所以本小节教程可以不用看,不会影响后续的实验。如果对Linux 内核的GPIO 子系统实现原理感兴趣的话可以看本小节。

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

152 static const struct of_device_id mxc_gpio_dt_ids[] = {
153 { .compatible = "fsl,imx1-gpio", .data = &mxc_gpio_devtype[IMX1_GPIO], },
154 { .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devtype[IMX21_GPIO], },
155 { .compatible = "fsl,imx31-gpio", .data = &mxc_gpio_devtype[IMX31_GPIO], },
156 { .compatible = "fsl,imx35-gpio", .data = &mxc_gpio_devtype[IMX35_GPIO], },
157 { /* sentinel */ }
158 };

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

以上是关于pinctrl 和gpio 子系统实验的主要内容,如果未能解决你的问题,请参考以下文章

嵌入式Linux开发21——pinctrl 和 gpio 子系统

pinctrl 和 gpio 子系统

Linux驱动开发pinctrl子系统和gpio子系统

Linux驱动开发pinctrl子系统和gpio子系统

使用pinctrl和gpio子系统开发GPIO驱动正点原子IMX6ULL阿尔法板的LED灯

pinctl和gpio子系统-23