之前已经介绍过关于设备树的一些基本概念,相信对设备数已经有了一些了解。我们知道在驱动开发过程中设备树是非常重要的,但是在使用pinctrl和gpio子系统之前,其实我们写驱动的方式,就拿点亮一个led来说,还是和裸机开发非常类似。因为我们仍然相当于直接操作的寄存器,和不使用设备树相比无疑只是将寄存器的地址写到了节点属性里,然后我们去读取而已。
其实对于大多数的 32 位 SOC 而言,引脚的设置基本都是先设置PIN的复用功能、然后设置上下拉、速度等电气属性;如果复用为GPIO的话,我们还需要读取或者写入指定的值。因此 Linux 内核针对 PIN 的配置推出了 pinctrl 子系统,对于 GPIO的配置推出了 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。
要使用 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文件中,因为我们知道dts文件是可以对dtsi文件中的节点进行引用以补充内容的,如下所示:
以上dts文件中的内容就是在向 iomuxc 节点追加数据,不同的外设使用的 PIN 不同、其配置也不同,因此一个萝卜一个坑,将某个外设所使用的所有 PIN 都组织在一个子节点里面。示例代码中 pinctrl_hog_1 子节点就是和热插拔有关的 PIN 集合,比如 USB OTG 的 ID 引脚。pinctrl_flexcan1 子节点是 flexcan1 这个外设所使用的 PIN,pinctrl_wdog 子节点是 wdog 外设所使用的 PIN。如果需要在 iomuxc 中添加我们自定义外设的 PIN,那么需要新建一个子节点,然后将这个自定义外设的所有 PIN 配置信息都放到这个子节点中。
其实将两个文件里关于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 };
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
宏 定 义 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。就这样,我们得到了需要使用的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 的电气特性的,这么重要的寄存器怎么可能没有值呢?回到示例代码中,第 9 行的内容如下所示:`
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 我们上面已经分析了,就剩下了一个 0x17059,0x17059 就是 conf_reg 寄存器值!此值由用户自行设置,也就是说上面所说的那些值都是一个PIN固定的值,这一个值才是我们可以自行设置的,通过此值来设置一个 IO 的上/下拉、驱动能力和速度等。在这里就相当于设置寄存器IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的值为 0x17059。
有一点需要注意的是,不同的开发板,肯定寄存器的名字以及地址有所不同,但是你可以进行类比。比如你可以去用户手册根据代码中提到的地址去查找对应的寄存器,然后就可以看到它应该如何配置,我们按照需要配置其值即可,也即设置PIN的电气属性。
上面我们已经说了,不同的外设使用的 PIN 不同、其配置也不同,因此一个萝卜一个坑,将某个外设所使用的所有 PIN 都组织在一个子节点里面。而这些节点应该在设备树的哪一级进行添加相信很容易能看出来,在这个示例中,肯定是在和pinctrl_hog_1节点同一级进行添加,因为这一级中每一个节点就代表一个外设在pinctrl 子系统中的对应的属于自己的那一部分。也即这个外设需要使用到哪些PIN我们在这里面进行添加即可,后面在gpio子系统中会引用到这个节点。而且我们其实只需要设置conf_reg 寄存器的值就好,其他的一些值,比如我们上面提到的宏定义后面的五个值其实都是已经写好的,它们和PIN一一对应,我们只需要选择所需的PIN即可,也即使用该PIN对应的宏定义,然后在其后面跟上conf_reg 的值以完成电气属性的设置。
这里我们虚拟一个名为“test”的设备,test 使用了 GPIO1_IO00 这个 PIN 的 GPIO 功能,pinctrl 节点添加过程如下:
同一个外设的 PIN 都放到一个节点里面,打开 imx6ull-alientek-emmc.dts,在 iomuxc 节点中的“imx6ul-evk”子节点下添加“pinctrl_test”节点,注意!节点前缀一定要为“pinctrl_”。添加完成以后如下所示:
1 pinctrl_test: testgrp {
2 /* 具体的 PIN 信息 */
3 };
注意上面已经说了,它是和pinctrl_hog_1节点在同一级,不要搞错了。
设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为“fsl,pins”,因为对于 I.MX 系列 SOC 而言,pinctrl 驱动程序是通过读取“fsl,pins”属性值来获取 PIN 的配置信息,完成以后如下所示:
1 pinctrl_test: testgrp {
2 fsl,pins = <
3 /* 设备所使用的 PIN 配置信息 */
4 >;
5 };
这里需要注意的是,不同的板子前面这个属性值肯定是不一样的,但是我们只需要看一下已经写好的节点里这个值是啥就行,因为我们并不是从零开始写的,肯定是已经有很多的节点被添加进来。
最后在“fsl,pins”属性中添加具体的 PIN 配置信息,完成以后如下所示:
1 pinctrl_test: testgrp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值*/
4 >;
最后总结一下就是,不同的板子寄存器的名字和地址可能不太一样,我们选择PIN的时候需要根据原理图和用户手册来确定应该使用哪些PIN、设置这些PIN的时候应该使用哪些寄存器及其如何赋值、这些PIN(包括复用)在头文件中对应的宏定义是哪一个等信息。