LV09-01-pinctrl和gpio子系统-01-pinctrl子系统简介
前面学习I2C子系统,会发现有一些地方出现了pinctrl,这是什么?来简单了解一下,不做深入了解,这里涉及的内容实在是多。若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 | Windows | Windows11 |
Ubuntu | Ubuntu20.04.2的64位版本 | |
VMware® Workstation 17 Pro | 17.6.0 build-24238078 | |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) | |
Win32DiskImager | Win32DiskImager v1.0 | |
Linux开发板环境 | Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04 | |
linux内核 | linux-4.19.71(NXP官方提供) |
点击查看本文参考资料
分类 | 网址 | 说明 |
官方网站 | 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内核的仓库 |
nxp-imx/linux-imx/releases/tag/v4.19.71 | NXP linux内核仓库tags中的v4.19.71 | |
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 | NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0 | |
I.MX6ULL | i.MX 6ULL Applications Processors for Industrial Products | I.MX6ULL 芯片手册(datasheet,可以在线查看) |
i.MX 6ULL Applications ProcessorReference Manual | I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网) | |
Source Code | https://elixir.bootlin.com/linux/latest/source | linux kernel源码 |
kernel/git/stable/linux.git - Linux kernel stable tree | linux kernel源码(官网,tag 4.19.71) | |
https://elixir.bootlin.com/u-boot/latest/source | uboot源码 |
一、pinctrl 子系统
1. GPIO的复用
以imx6ull为例,这个芯片拥有众多的片上外设, 大多数外设需要通过芯片的引脚与外部设备(器件)相连实现相对应的控制,例如我们熟悉的I2C、SPI、LCD、USDHC等等。
我们知道芯片的可用引脚(除去电源引脚和特定功能引脚)数量是有限的,芯片的设计厂商为了提高硬件设计的灵活性, 一个芯片引脚往往可以做为多个片上外设的功能引脚,以GPIO1_IO03所对应的引脚为例,如下图所示。

这个引脚不仅仅可以作为I2C1的SDA,也可以作为多个外设的功能引脚,如普通的GPIO引脚,USB_OTG2_OC等, 在设计硬件时我们可以根据需要灵活的选择其中的一个。这个就是之前经常多说GPIO的复用。
复用(Multiplexing):就是将单一物理引脚通过配置,动态分配给多个功能模块使用。这样做的目的就是在有限的引脚资源下,支持更多外设功能(如UART、SPI、I2C、PWM等),避免芯片引脚数量膨胀。所以其实无论是哪种芯片,基本都有类似下图的结构:

要想让 pinA、 B 用于 GPIO,需要设置 IOMUX 让它们连接到 GPIO 模块;
要想让 pinA、 B 用于 I2C,需要设置 IOMUX 让它们连接到 I2C 模块。
以 GPIO、 I2C 应该是并列的关系,它们能够使用之前,需要设置 IOMUX。有时候并不仅仅是设置 IOMUX,还要配置引脚,比如上拉、下拉、开漏等等。
在编程过程中,无论是裸机还是驱动, 一般首先要设置引脚的复用功能并且设置引脚的PAD属性(驱动能力、上下拉等等)。
2. pinctrl子系统简介
在驱动程序中我们需要手动设置每个引脚的复用功能,不仅增加了工作量,编写的驱动程序不方便移植, 可重用性差等。更糟糕的是缺乏对引脚的统一管理,容易出现引脚的重复定义。
假设我们在I2C1的驱动中将UART4_RX_DATA 引脚和 UART4_TX_DATA 引脚复用为SCL和SDA, 而这个时候恰好在编写UART4驱动驱动时没有注意到UART4_RX_DATA引脚和UART4_TX_DATA引脚已经被使用, 在驱动中又将其初始化为UART4_RX和UART4_TX,这样I2C1驱动将不能正常工作,并且这种错误很难被发现。pinctrl 子系统就是为了解决这个问题而引入的, pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能。
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
pinctrl子系统是由芯片厂商来实现的,简单来说用于帮助我们管理芯片引脚并自动完成引脚的初始化, 而我们要做的只是在设备树中按照规定的格式写出想要的配置参数即可。
总的来说,Linux 中的 pinctrl 子系统(Pin Control Subsystem) 是一个用于管理和配置通用输入/输出(GPIO) 引脚的框架。 它提供了一种标准化的方法, 以在 Linux 内核中对 GPIO 引脚进行配置、分配和控制, 从而适应不同的硬件平台和设备。
3. 一些基本概念
linux内核中提供了相关的文档,如:pinctrl-bindings.txt - Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt - Linux source code v4.19.71
这会涉及 2 个对象: pin controller、 client device。前者提供服务:可以用它来复用引脚、配置引脚。后者使用服务:声明自己要使用哪些引脚的哪些功能,怎么配置它们。
3.1 pin controller
它是一个软件上的概念,可以认为它对应 IOMUX——用来复用引脚,还可以配置引脚(比如上下拉电阻等)。
注意, pin controller 和 GPIO Controller 不是一回事,前者控制的引脚可用于 GPIO 功能、 I2C 功能;后者只是把引脚配置为输入、输出等简单的功能。即先用 pin controller 把引脚配置为 GPIO,再用 GPIO Controler 把引 脚配置为输入或输出。
3.2 client device
“客户设备”,谁的客户? pinctrl 系统的客户,那就是使用 pinctrl 系统的设备,使用引脚的设备。它在设备树里会被定义为一个节点,在节点里声明要用哪些引脚。我们可以看一下这个Example:

上图中,左边是 pin controller 节点,右边是 client device 节点:
3.2.1 pin state
对于一个“client device”来说,比如对于一个 UART 设备,它有多个“状态”: default、 sleep 等,那对应的引脚也有这些状态。怎么理解?
比如默认状态下, UART 设备是工作的,那么所用的引脚就要复用为 UART 功能。在休眠状态下,为了省电,可以把这些引脚复用为 GPIO 功能;或者直接把它们配置输出高电平。
上图中, pinctrl-names 里定义了 2 种状态: default、 sleep。第0种状态用到的引脚在pinctrl-0中定义,它是state_0_node_a,位于 pincontroller 节点中。第1种状态用到的引脚在pinctrl-1中定义,它是state_1_node_a,位于 pincontroller 节点中。当这个设备处于 default 状态时, pinctrl 子系统会自动根据上述信息把所用引脚复用为 uart0 功能。当这这个设备处于 sleep 状态时, pinctrl 子系统会自动根据上述信息把所用引脚配置为高电平。
3.2.2 groups 和 function
引脚组groups是一组具有相似功能、 约束条件或共同工作的引脚的集合。 每个引脚组通常与特定的硬件功能或外设相关联。 例如, 一个引脚组可以用于控制串行通信接口(如 UART 或 SPI) ,另一个引脚组可以用于驱动 GPIO。
功能(function)定义了芯片上具有外设功能的功能。 每个功能节点对应于一个或多个 IO 组(group) 的配置信息。 这些功能可以是串口、 SPI、 I2C 等外设功能。
举个例子:rk3568-pinctrl.dtsi - arch/arm64/boot/dts/rockchip/rk3568-pinctrl.dtsi - Linux source code v5.19.17,这里会有can0 和 can1 对应两个不同的 function, 分别为 CAN0 控制器和 CAN 1 控制器。 每个控制器中又都有两个不同的 groups 引脚组。
- CAN0 控制器 :rk3568-pinctrl.dtsi - can0
1 | can0 { |
引脚组 can0m0-pins: 这是 CAN0 控制器的第一个引脚组, 用于配置 CAN0 的引脚。 它定义了两个引脚: RK_PB4 和 RK_PB3。 其中, RK_PB4 用于 CAN0 的接收引脚(can0_rxm0) , RK_PB3 用于 CAN0 的发送引脚(can0_txm0) 。
引脚组 can0m1-pins: 这是 CAN0 控制器的第二个引脚组, 也用于配置 CAN0 的引脚。 它定义了两个引脚: RK_PA2 和 RK_PA1。 其中, RK_PA2 用于 CAN0 的接收引脚(can0_rxm1) , RK_PA1 用于 CAN0 的发送引脚(can0_txm1) 。
1 | can1 { |
引脚组 can1m0-pins: 这是 CAN1 控制器的第一个引脚组, 用于配置 CAN1 的引脚。 它定义了两个引脚: RK_PA0 和 RK_PA1。 其中, RK_PA0 用于 CAN1 的接收引脚(can1_rxm0) , RK_ PA1 用于 CAN1 的发送引脚(can1_txm0) 。
引脚组 can1m1-pins: 这是 CAN1 控制器的第二个引脚组, 也用于配置 CAN1 的引脚。 它定义了两个引脚: RK_PC2 和 RK_PC3。 其中, RK_PC2 用于 CAN1 的接收引脚(can1_rxm1) , RK_PC3 用于 CAN1 的发送引脚(can1_txm1) 。
3.2.3 Generic pin multiplexing node 和 Generic pin configuration node
在上图左边的 pin controller 节点中,有子节点或孙节点,它们是给client device 使用的。可以用来描述复用信息:哪组 (group) 引脚复用为哪个功能(function);可以用来描述配置信息:哪组(group)引脚配置为哪个设置功能(setting),比如上拉、下拉等。
注意: pin controller 节点的格式, 没有统一的标准!!!每家芯片都不一样。甚至上面的 group、 function 关键字也不一定有,但是概念是有的。
3.3 示例
我们看一个示例,打开imx6ul-14x14-evk.dtsi,有如下内容:
1 | &uart1 { |
这个就相当于client device。它对应的pin controller是什么?我们打开imx6ul-14x14-evk.dtsi,会有如下内容:
1 | pinctrl_uart1: uart1grp { |
二、PIN配置信息
上面大概了解了一些基本概念,我们以I.MX6ULL为例,看一下设备树中的pin配置信息。
要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要根据我们提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信息。
1. iomuxc节点
打开 imx6ul.dtsi 文件,找到一个叫做 iomuxc 的节点:
1 | soc { |
iomuxc 节点就是 I.MX6ULL 的 IOMUXC 外设对应的节点,看起来内容很少,没看出什么跟 PIN 的配置有关的内容啊,这里其实只是指定了这个IOMUXC 外设,我们可以看一下reg属性,往前翻一下,就会发现,这个节点的父节点配置了:
1 |
所以这里的reg属性中,它的地址是0x020e0000,大小是0x4000。我们看一下《 i.MX 6ULL Applications Processor Reference Manual》—— Table 2-2. AIPS-1 memory map :

会发现,这个外设的起始地址就是0x020e0000,大小是16KB,就是0x4000。
2. &iomuxc
我们打开 imx6ul-14x14-evk.dtsi 找到以下内容:
1 | &iomuxc { |
这个部分就是向 iomuxc 节点追加数据,不同的外设使用的 PIN 不同、其配置也不同,因此一个萝卜一个坑,将某个外设所使用的所有 PIN 都组织在一个子节点里面。例如上面的 pinctrl_i2c1就是I2C1外设所用的pin引脚信息。和前面的结合起来就是:
1 | soc { |
第 9 行:compatible 属性值为“fsl,imx6ul-iomuxc”,前面学习设备树的时候说过, Linux 内核会根据 compatbile 属性值来查找对应的驱动文件,所以我们在 Linux 内核源码中全局搜索字符串“fsl,imx6ul-iomuxc”就会找到 I.MX6ULL 这颗 SOC 的 pinctrl 驱动文件。稍后我们会学习这个 pinctrl 驱动文件。
2.1 fsl,pins
第 14 - 18 行:pinctrl_i2c1子节点所使用的 PIN 配置信息 。我们可以看一下《 i.MX 6ULL Applications Processor Reference Manual》——Table 31-1. I2C External Signals :

会发现,I2C1的SCL和SDA可以通过UART4的两个引脚复用而来。可以看到这个pin配置信息分为俩个部分,接下来来看一下这两个部分是什么含义:
1 | MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0 |
对于一个 PIN 的配置主要包括两方面,一个是设置这个 PIN 的复用功能,另一个就是设置这个 PIN 的电气特性。所以我们可以大胆的猜测 MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 的这两部分配置信息一个是设置 UART4_TX_DATA 的复用功能,一个是用来设置 UART4_TX_DATA 的电气特性。
2.1.1 MX6UL_PAD_UART4_TX_DATA__I2C1_SCL的含义
我们接下来就来看一下这个宏是什么意思,MX6UL_PAD_UART4_TX_DATA__I2C1_SCL定义在imx6ul-pinfunc.h,其实关于这个UART4_TX_DATA引脚一共有8个相关的宏定义:
1 |
我们看一下《 i.MX 6ULL Applications Processor Reference Manual》—— 32.6.29 这一部分:

观察一下就会发现,上面的宏以及展开后对应的第三个值和下面图片中的刚好对应。这个时候我们再来看这个:
1 |
这 5 个值的含义如下所示:
1 |
|
- 0x00b4: mux_reg 寄存器偏移地址,在这里就是IOMUXC_SW_MUX_CTL_PAD_UART4_TX_DATA
前面分析过了,设备树中的 iomuxc 节点就是 IOMUXC 外设对应的节点 根据其reg 属性可知 IOMUXC 外设寄存器起始地址为0x020e0000 。 因 此0x020e0000+0x00b4=0x020e00b4,因此可知, 0x020e0000+mux_reg 就是 PIN 的复用寄存器IOMUXC_SW_MUX_CTL_PAD_UART4_TX_DATA的地址:

- 0x0340:conf_reg 寄存器偏移地址,在这里就是IOMUXC_SW_PAD_CTL_PAD_UART4_TX_DATA
0x020e0000+0x0340=0x020e0340,这个就是寄存器IOMUXC_SW_PAD_CTL_PAD_UART4_TX_DATA 的地址。

- 0x05a4:input_reg 寄存器偏移地址,在这里就是IOMUXC_I2C1_SCL_SELECT_INPUT
有些外设有 input_reg 寄存器,有 input_reg 寄存器的外设需要配置 input_reg 寄存器。这个引脚用于I2C1,这个有input_reg,对应了I2C1_SCL_SELECT_INPUT DAISY Register:IOMUXC_I2C1_SCL_SELECT_INPUT:

- 2:mux_reg 寄存器值 , 在这里就相当于设置 IOMUXC_SW_MUX_CTL_PAD_UART4_TX_DATA 寄存器为 0x2,也即是设置 UART4_TX_DATA 这个 PIN 复用为I2C1_SCL。

- 1: input_reg 寄存器值,这里就是设置IOMUXC_I2C1_SCL_SELECT_INPUT寄存器低2位为1:

2.1.2 0x4001b8b0
上面已经设置了IOMUXC_SW_MUX_CTL_PAD_UART4_TX_DATA复用寄存器的值,那么还有电气属性没有设置,那么IOMUXC_SW_PAD_CTL_PAD_UART4_TX_DATA寄存器的值是怎么设置的?前面的设备树中不是还有个值吗?
1 | fsl,pins = < |
接下来看 0x4001b8b0 就是 conf_reg 寄存器值!此值由用户自行设置,通过此值来设置一个 IO 的上/下拉、驱动能力和速度等。这里 0x4001b8b0 换算成二进制就是 0100 0000 0000 0001 1011 1000 1011 0000
:

具体含义可以看参考手册对应寄存器的说明。
2.2 &i2c1
怎么使用上面的pinctrl信息?我们来看一下i2c1这个节点,这个就相当于上面说的client device,我们打开imx6ul-14x14-evk.dtsi
1 | &i2c1 { |
3. 设备树添加pinctrl节点
关于 I.MX 系列 SOC 的 pinctrl 设备树绑定信息可以参考文档:
这里我们虚拟一个名为“test”的设备, test 使用了 GPIO1_IO00 这个 PIN 的 GPIO 功能, pinctrl 节点添加过程如下:
3.1 创建对应的节点
同一个外设的 PIN 都放到一个节点里面,打开imx6ul-14x14-evk.dtsi ,在 &iomuxc 节点中添加“pinctrl_test”节点,注意!节点前缀一定要为“pinctrl_”。添加完成以后如下所示:
1 | &iomuxc { |
3.2 添加“fsl,pins”属性
设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为“fsl,pins”,因为对于 I.MX 系列 SOC 而言, pinctrl 驱动程序是通过读取“fsl,pins”属性值来获取 PIN 的配置信息,完成以后如下所示:
1 | // pinctrl_test 节点 |
3.3 在“fsl,pins”属性中添加 PIN 配置信息
最后在“fsl,pins”属性中添加具体的 PIN 配置信息,完成以后如下所示:
1 | // pinctrl_test 节点 |
4. 生成pincontroller设备树信息
pincontroller设备树的信息是怎么生成的?生成pincontroller设备树信息,有3种方法:
- 有些芯片有图形化的工具,可以点点鼠标就可以配置引脚信息,得到pincontroller中的信息。例如imx6ull,NXP官方就提供了这样的工具:Search | NXP Semiconductors,或者直接点这里下载i.MX Pins Tool, Windows 64bit package

- 有些芯片,只能看厂家给的设备树文档或者参考设备树的例子
- 最差的就是需要阅读驱动代码才能构造设备树信息。
三、I.MX6ULL 的 pinctrl 子系统驱动
所有的东西都已经准备好了,包括寄存器地址和寄存器值, Linux 内核相应的驱动文件就会根据这些值来做相应的初始化。
iomuxc 节点中 compatible 属性的值为“fsl,imx6ul-iomuxc”,在 Linux 内核中全局搜索“fsl,imx6ul-iomuxc”字符串就会找到对应的驱动文件。
1.pincontroller的数据结构
1.1 pinctrl_dev
1 | struct pinctrl_dev { |
这里我们主要关注这个desc指针,它是struct pinctrl_desc 类型的。pincontroller虽然是一个软件的概念,但是它背后是有硬件支持的,所以可以使用一个结构体来表示它:pinctrl_dev。怎么使用pinctrl_desc、pinctrl_dev来描述一个pin controller?这两个结构体定义如下:

1.2 pinctrl_desc
linux中使用结构体 struct pinctrl_desc 来描述一个引脚控制器(pinctrl) 的属性和操作:
1 | struct pinctrl_desc { |
引脚控制器是硬件系统中的一个组件, 用于管理和控制引脚的功能和状态。 struct pinctrl_desc 结构体的作用是提供一个统一的接口, 用于配置和管理引脚控制器的行为。
name:引脚控制器的名称, 用于标识引脚控制器的唯一性。
pins:引脚描述符数组, 是一个struct pinctrl_pin_desc类型的指针,用于描述引脚的属性和配置。 每个引脚描述符包含了引脚的名称、 编号、 模式等信息。
npins:表示引脚描述符数组中元素的数量, 用于确定引脚描述符数组的长度。
pctlops :指向引脚控制操作函数的指针, struct pinctrl_ops类型,用于定义引脚控制器的操作接口。 通过这些操作函数, 可以对引脚进行配置、 使能、 禁用等操作。
pmxops:指向引脚复用操作函数的指针, struct pinmux_ops类型,用于定义引脚的复用功能。 复用功能允许将引脚的功能切换为不同的模式, 以适应不同的设备需求。
pinconf_ops:指向引脚配置操作函数的指针, struct pinconf_ops类型,用于定义引脚的其他配置选项。 这些配置选项可以包括引脚的上拉、 下拉配置、 电气特性等。
owner:指向拥有该引脚控制器结构体的模块的指针。 这个字段用于跟踪引脚控制器结构体的所有者。
num_custom_params: 表示自定义配置参数的数量, 用于描述引脚控制器的自定义配置参数。
custom_params: 指向自定义配置参数的指针,用于描述引脚控制器的自定义配置参数的属性。 自定义配置参数可以根据具体需求定义, 用于扩展引脚控制器的配置选项。
custom_conf_items: 指向自定义配置项的指针, 用于描述引脚控制器的自定义配置项的属性。 自定义配置项可以根据具体需求定义, 用于扩展引脚控制器的配置选项。
1.2.1 pinctrl_desc.pinctrl_pin_desc
1 | struct pinctrl_pin_desc { |
1.2.2 pinctrl_desc.pinctrl_ops
1 | struct pinctrl_ops { |
重点是函数 pinctrl_desc.pinctrl_ops.dt_node_to_map,这个函数用于处理设备树中pin controller中的某个节点,可以通过该函数将device_node转换为一系列的pinctrl_map。
1.2.3 pinctrl_desc.pinmux_ops
1 | struct pinmux_ops { |
pinctrl_desc.pinmux_ops中需要关注的是 pinctrl_desc.pinmux_ops.set_mux函数,这个函数用于配置引脚的复用。
1.2.4 pinctrl_desc.pinconf_ops
1 | struct pinconf_ops { |
pinctrl_desc.pinconf_ops中的函数是用于对引脚进行配置。
1.3 使用pinctrl_desc注册得到pinctrl_dev
调用devm_pinctrl_register()或pinctrl_register(),就可以根据pinctrl_desc构造出pinctrl_dev,并且把pinctrl_dev放入链表。其实devm_pinctrl_register()最后也是调用的pinctrl_register(),我们来看一下pinctrl_register():
1 | struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc, |
内部会再调用pinctrl_init_controller(),下面的删去了暂时不需要关心的内容。
1 | static struct pinctrl_dev * |
2. client的数据结构
2.1 device
在设备树中,使用pinctrl时格式如下:
1 | /* For a client device requiring named states */ |
设备节点要么被转换为platform_device,或者其他结构体(比如i2c_client),但是里面都会有一个device结构体,
比如:

2.2 device.dev_pin_info
每个device结构体里都有一个dev_pin_info结构体,用来保存设备的pinctrl信息:
1 | struct dev_pin_info { |
(1) struct pinctrl *p;: 引脚控制器指针。 该指针指向设备所使用的引脚控制器对象, 用于对设备的引脚进行控制和配置。
(2) struct pinctrl_state *default_state;: 默认状态指针。 该指针指向设备的默认引脚配置状态, 表示设备在正常操作时的引脚配置。
(3) struct pinctrl_state *init_state;: 初始化状态指针。 该指针指向设备的初始化引脚配置状态, 表示设备在初始化阶段的引脚配置。
(4) struct pinctrl_state *sleep_state;: 睡眠状态指针(仅在支持电源管理时可用) 。 该指针指向设备的引脚配置状态, 表示设备在进入睡眠状态时的引脚配置。
(5) struct pinctrl_state *idle_state;: 空闲状态指针(仅在支持电源管理时可用) 。 该指针指向设备的引脚配置状态, 表示设备在空闲状态时的引脚配置。
这个后面学习 pinctrl_bind_pins() 的时候会继续学习。

2.2.1 device.dev_pin_info.pinctrl
假设芯片上有多个pin controller,那么这个设备使用哪个pin controller?这需要通过设备树来确定。
分析设备树,找到pin controller,对于每个状态,比如default、init,去分析pin controller中的设备树节点。然后使用pin controller的 pinctrl_desc.pinctrl_ops.dt_node_to_map来处理设备树的pinctrl节点信息,得到一系列的pinctrl_map,这些pinctrl_map放在device.dev_pin_info.pinctrl.dt_maps链表中,每个pinctrl_map都被转换为pinctrl_setting,放在对应的pinctrl_state.settings链表中。

2.2.2 pinctrl_map和pinctrl_setting
设备引用pin controller中的某个节点时,这个节点会被转换为一系列的pinctrl_map,转换为多少个pinctrl_map,完全由具体的驱动决定。每个pinctrl_map,又被转换为一个pinctrl_setting。
例如,设备节点里有:pinctrl-0 = <&state_0_node_a>
,pinctrl-0对应一个状态,会得到一个pinctrl_state,state_0_node_a节点被解析为一系列的pinctrl_map,这一系列的pinctrl_map被转换为一系列的pinctrl_setting,这些pinctrl_setting被放入pinctrl_state的settings链表

2.2.4 使用pinctrl_setting
函数调用过程如下:
1 | really_probe() |
1 | /* Apply all the settings for the new state */ |

3. 驱动源码分析
3.1 驱动入口与匹配表
3.1.1 imx6ul_pinctrl_init()
在 pinctrl-imx6ul.c - drivers/pinctrl/freescale/pinctrl-imx6ul.c 中有如下内容:
1 | static int __init imx6ul_pinctrl_init(void) |
会发现,这也都是平台设备驱动。平台设备驱动结构体为 imx6ul_pinctrl_driver:
1 | static struct platform_driver imx6ul_pinctrl_driver = { |
3.1.2 imx6ul_pinctrl_driver.of_match_table
接下来看一下这个imx6ul_pinctrl_driver.of_match_table,它指向的是数组imx6ul_pinctrl_of_match
1 | static const struct of_device_id imx6ul_pinctrl_of_match[] = { |
之前学习设备树的时候说过了, 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 配置工作。
1 | iomuxc: iomuxc@20e0000 { |
3.2 imx6ul_pinctrl_driver.imx6ul_pinctrl_probe()
当驱动匹配上之后,就会执行.probe函数,在这里就是 imx6ul_pinctrl_probe() :
1 | static int imx6ul_pinctrl_probe(struct platform_device *pdev) |
后续就是我们就是要分析这个imx_pinctrl_probe()函数了。

3.3.1 imx_pinctrl_parse_groups()

设备树中的 mux_reg 和 conf_reg 值会保存在 info 参数中, input_reg、mux_mode、 input_val 和 config 值会保存在 grp 参数中。

- 第 527 行:获取 config 值。

3.3.2 pinctrl_register()
1 | struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc, |
这个我们主要看一下结构体 struct pinctrl_desc ,它被用来描述一个引脚控制器(pinctrl) 的属性和操作:
1 | struct pinctrl_desc { |
这三个“_ops”结构体指针非常重要!!!因为这三个结构体就是 PIN 控制器的“工具”,这三个结构体里面包含了很多操作函数,通过这些操作函数就可以完成对某一个PIN 的配置。struct pinctrl_desc 结构体需要由用户提供,结构体里面的成员变量也是用户提供的。但是这个用户并不是我们这些使用芯片的程序员,而是半导体厂商,半导体厂商发布的 Linux 内核源码中已经把这些工作做完了。比如在 imx_pinctrl_probe() 函数中可以找到如下所示代码 :
1 | int imx_pinctrl_probe(struct platform_device *pdev, |
其中用到了imx_pctrl_ops、 imx_pmx_ops 和 imx_pinconf_ops 这三个结构体定义。这里就不再继续分析了,这个系统的内容好多。这里一些函数我们后面再开一个小节学习。
3.3 imx_pctrl_ops.dt_node_to_map()
设备树(Device Tree) 中存放的是对硬件设备信息的描述, 包含了硬件设备的配置和连接信息, 例如在 pinctrl 节点中的引脚的配置和映射关系。imx_pctrl_ops.dt_node_to_map()函数的作用就是根据设备树中的节点信息, 生成对应的引脚映射数组。 这个映射数组将描述硬件功能(如复用功能和配置信息) 与设备树中的引脚信息进行绑定。
3.4.1 pinctrl_map
先看一下pinctrl_map:
1 | struct pinctrl_map { |
该结构体用于在引脚控制器中定义引脚的映射关系。 通过映射类型的不同, 可以将引脚与具体的复用功能或配置信息关联起来,从而实现引脚的配置和控制 。
3.4.2 dt_node_to_map()
1 | static int imx_dt_node_to_map(struct pinctrl_dev *pctldev, |
这个函数根据设备节点的信息创建引脚映射, 包括复用映射和配置映射。 复用映射用于将引脚组的功能与父节点的功能关联起来, 而配置映射用于将引脚的配置信息与引脚的名称关联起来。 这些映射将用于配置引脚控制器, 以确保引脚在系统中正确地配置和使用。 这个函数在设备树解析过程中被调用, 以便为每个设备节点创建相应的引脚映射。
3.4 imx_pmx_ops.imx_pmx_set()
1 | static int imx_pmx_set(struct pinctrl_dev *pctldev, unsigned selector, |
这个函数会对引脚进行配置。这里就不详细去分析了。
3.5 pinctrl是怎么被调用的?
我们的驱动基本不用管。当设备切换状态时,对应的pinctrl就会被调用。比如在platform_device和platform_driver的枚举过程中,流程如下:
3.5.1 really_probe()
前面学习platform_driver的时候,我们知道当驱动和设备树匹配上后,就会调用probe函数,这个probe函数是这样被调用的:LV06-04-linux设备模型-09-设备与驱动的匹配 | 苏木,这里学习linux设备模型的时候有了解过,是通过really_probe()最终调用了上面的.probe函数,我们看下really_probe():
1 | static int really_probe(struct device *dev, struct device_driver *drv) |
如果使用了 pinctrl 就会调用 第 9 行 的 pinctrl_bind_pins() 函数进行设备引脚的绑定。
3.5.2 pinctrl_bind_pins()
- 先看一下 device.dev_pin_info。每个device结构体里都有一个dev_pin_info结构体,用来保存设备的pinctrl信息:
1 | struct dev_pin_info { |
前面介绍了每个成员的含义,这里举个例子:
1 | rk_485_ctl: rk-485-ctl { |
其中第 4 行的 pinctrl-names 属性指定了设备所使用的引脚控制器为 default, 即第 5 行的 p inctrl-0, 而 pinctrl-0 的值为 pinctrl 节点中的 rk_485_gpio, 所以 struct pinctrl_state *default_st ate 这一默认状态结构体指针会用来存放 11 行的引脚复用信息, 设备树中的 pinctrl 节点会转换为 pinctrl_map 结构体, 那么 struct pinctrl_state *default_state 必然会跟 pinctrl_map 结构体建立联系, 那这个联系是如何建立的呢?
根据线索跳转到 pinctrl_bind_pins() 函数, 该函数定义如下:
1 | int pinctrl_bind_pins(struct device *dev) |
会用就行,这里后面有机会再详细分析。
3.6 总结
下面这个图是讯为的rk3568开发板文档中的一张图,都差不多,帮助理解。

4. 总结
pinctrl的三大作用,有助于理解所涉及的数据结构:
(1)引脚枚举与命名(Enumerating and naming):包括单个引脚和各组引脚
(2)引脚复用(Multiplexing):比如用作GPIO、I2C或其他功能
(3)引脚配置(Configuration):比如上拉、下拉、open drain、驱动强度等
pinctrl驱动程序的核心是构造一个pinctrl_desc结构体:

4.1 作用1:描述、获得引脚
分为2部分:
- 描述、获得单个引脚的信息
- 描述、获得某组引脚的信息

4.2 作用2:引脚复用
用来把某组引脚(group)复用为某个功能(function)。

4.3 作用3:引脚配置
用来配置:某个引脚(pin)或某组引脚(group)。

参考资料:
linux内核驱动-pinctrl子系统 - fuzidage - 博客园
NXP(恩智浦)官方提供的配置i.MX系列处理器引脚复用和功能的工具“i.MX Pins Tool”的安装及使用说明【我主要用于生成设备树节点描述语句】_imx pin tool-CSDN博客