LV06-02-字符设备驱动-01-pinctrl与gpio子系统-01-pinctrl
本文主要是pinctrl与gpio子系统——pinctrl子系统相关笔记。若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
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官网) |
一、概述
我们先来回顾一下之前是怎么初始化 LED 灯所使用的 GPIO,步骤如下:
(1)修改设备树, 添加相应的节点,节点里面重点是设置 reg 属性, reg 属性包括了 GPIO相关寄存器。
(2)获 取 reg 属 性 中 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 和IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 这两个寄存器地址,并且初始化这两个寄存器,这两个寄存器用于设置 GPIO1_IO03 这个 PIN 的复用功能、上下拉、速度等。
(3)在(2)里面将 GPIO1_IO03 这个 PIN 复用为了 GPIO 功能,因此需要设置 GPIO1_IO03这个 GPIO 相关的寄存器,也就是 GPIO1_DR 和 GPIO1_GDIR 这两个寄存器。
总结一下,(2)中完成对 GPIO1_IO03 这个 PIN 的初始化,设置这个 PIN 的复用功能、上下拉等,比如将 GPIO_IO03 这个 PIN 设置为 GPIO 功能。(3)中完成对 GPIO 的初始化,设置 GPIO为输入/输出等。其实对于大多数的 32 位 SOC 而言,引脚的设置基本都是这两方面,因此 Linux 内核针对 PIN 的配置推出了 pinctrl 子系统,对于 GPIO的配置推出了 gpio 子系统。
大多数 SOC 的 pin 都是支持复用的,比如 I.MX6ULL 的 GPIO1_IO03 既可以作为普通的GPIO 使用,也可以作为 I2C1 的 SDA 等等。此外我们还需要配置 pin 的电气特性,比如上/下拉、速度、驱动能力等等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。 pinctrl 子系统就是为了解决这个问题而引入的, pinctrl 子系统主要工作内容如下:
(1)获取设备树中 pin 信息。
(2)根据获取到的 pin 信息来设置 pin 的复用功能
(3)根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成, pinctrl 子系统源码目录为 drivers/pinctrl。
二、pinctrl 子系统驱动
1. PIN 配置信息
要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要根据我们提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信息。打开 imx6ull.dtsi 文件,找到一个叫做 iomuxc 的节点,如下所示 :
iomuxc 节点就是 I.MX6ULL 的 IOMUXC 外设对应的节点,看起来内容很少,没看出什么跟 PIN 的配置有关的内容啊,我们打开 imx6ull-alpha-emmc.dts,找到如下所示内容:
这就是就是向 iomuxc 节点追加数据,不同的外设使用的 PIN 不同、其配置也不同,因此一个萝卜一个坑,将某个外设所使用的所有 PIN 都组织在一个子节点里面。上图 pinctrl_hog_1 子节点就是和热插拔有关的 PIN 集合,比如 USB OTG 的 ID 引脚。如果需要在 iomuxc 中添加我们自定义外设的 PIN,那么需要新建一个子节点,然后将这个自定义外设的所有 PIN 配置信息都放到这个子节点中。
将其与上图结合起来就可以得到完成的 iomuxc 节点,如下所示:
1 | iomuxc: iomuxc@020e0000 { |
第 2 行, compatible 属性值为“fsl,imx6ul-iomuxc”,前面讲解设备树的时候说过, Linux 内核会根据 compatbile 属性值来查找对应的驱动文件,所以我们在 Linux 内核源码中全局搜索字符串“fsl,imx6ul-iomuxc”就会找到 I.MX6ULL 这颗 SOC 的 pinctrl 驱动文件。
第 9~12 行, pinctrl_hog_1 子节点所使用的 PIN 配置信息,我们就以第 9 行的 UART1_RTS_B这个 PIN 为例,学习一下如何添加 PIN 的配置信息, UART1_RTS_B 的配置信息如下:
1 | 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
的宏定义内容如下:
中一共有 8 个以“MX6UL_PAD_UART1_RTS_B”开头的宏定义,仔细观察应该就能发现,这 8 个宏定义分别对应 UART1_RTS_B 这个 PIN 的 8 个复用 IO。查阅《I.MX6ULL 参考手册》可以知 UART1_RTS_B 的可选复用 IO 如图
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 表 示 将UART1_RTS_B 这个 IO 复用为 GPIO1_IO19。此宏定义后面跟着 5 个数字,也就是这个宏定义的具体值,如下所示:
1 | 0x0090 0x031C 0x0000 0x5 0x0 |
这5个值的含义如下:
1 | <mux_reg conf_reg input_reg mux_mode input_val> |
- (0x0090)mux_reg:这里取值为0x0090,寄存器偏移地址,设备树中的 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: 寄存器偏移地址,这里取值为0x0000,有些外设有 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 的电气特性的,这么重要的寄存器怎么没有值呢?在imx6ull-alpha-emmc.dts中其实有的:
如图中的:
1 | 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 内核相应的驱动文件就会根据这些值来做相应的初始化。接下来就找一下哪个驱动文件来做这一件事情, iomuxc 节点中 compatible 属性的值为“fsl,imx6ul-iomuxc”,在 Linux 内核中全局搜索“fsl,imx6ul-iomuxc”字符串就会找到对应的驱动文件。在文件drivers/pinctrl/freescale/pinctrl-imx6ul.c中有如下内容
第 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 配置入口函数。以此为入口,可以看到如下图的函数调用路径 :
在图中函数 imx_pinctrl_parse_groups 负责获取设备树中关于 PIN 的配置信息,也就是我们前面分析的那 6 个 u32 类型的值。我们搜索一下就可以找到这个函数定义在 drivers/pinctrl/freescale/pinctrl-imx.c 中。
接下来看一下函数 pinctrl_register,此函数用于向 Linux 内核注册一个 PIN 控制器,此函数原型如下 :
1 | struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc, |
参数 pctldesc 非常重要,因为此参数就是要注册的 PIN 控制器, PIN 控制器用于配置 SOC的 PIN 复用功能和电气特性。参数 pctldesc 是 pinctrl_desc 结构体类型指针, pinctrl_desc 结构体(定义在include/linux/pinctrl/pinctrl.h)如下所示:
第 132~124 行,这三个“_ops”结构体指针非常重要!因为这三个结构体就是 PIN 控制器的“工具”,这三个结构体里面包含了很多操作函数,通过这些操作函数就可以完成对某一个PIN 的配置。 pinctrl_desc 结构体需要由用户提供,结构体里面的成员变量也是用户提供的。但是这个用户并不是我们这些使用芯片的程序员,而是半导体厂商,半导体厂商发布的 Linux 内核源码中已经把这些工作做完了。这里就不再详细写了。
三、设备树中添加 pinctrl 节点模板
关 于 I.MX 系 列 SOC 的 pinctrl 设备树绑定信息可以参考文档Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt。这里我们虚拟一个名为“test”的设备, test 使用了 GPIO1_IO00 这个 PIN 的 GPIO 功能, pinctrl 节点添加过程如下.
1. 创建对应的节点
同一个外设的 PIN 都放到一个节点里面,打开 imx6ull-alpha-emmc.dts,在 iomuxc 节点中的“imx6ul-evk”子节点下添加 “ pinctrl_test ”节点,注意!节点前缀一定要为“pinctrl_”。添加完成以后如下所示:
1 | pinctrl_test: testgrp { |
2. 添加“fsl,pins”属性
设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为“fsl,pins”,因为对于 I.MX 系列 SOC 而言, pinctrl 驱动程序是通过读取“fsl,pins”属性值来获取 PIN 的配置信息,完成以后如下所示:
1 | pinctrl_test: testgrp { |
3. 在“fsl,pins”属性中添加 PIN 配置信息
最后在“fsl,pins”属性中添加具体的 PIN 配置信息,完成以后如下所示:
1 | pinctrl_test: testgrp { |
至此我们已经在 imx6ull-alpha-emmc.dts 文件中添加好了 test 设备所使用的 PIN 配置信息