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所对应的引脚为例,如下图所示。

image-20250329192152317

这个引脚不仅仅可以作为I2C1的SDA,也可以作为多个外设的功能引脚,如普通的GPIO引脚,USB_OTG2_OC等, 在设计硬件时我们可以根据需要灵活的选择其中的一个。这个就是之前经常多说GPIO的复用。

复用(Multiplexing):就是将单一物理引脚通过配置,动态分配给多个功能模块使用。这样做的目的就是在有限的引脚资源下,支持更多外设功能(如UART、SPI、I2C、PWM等),避免芯片引脚数量膨胀。所以其实无论是哪种芯片,基本都有类似下图的结构:

image-20250329190307094
  • 要想让 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:

image-20250329193141579

上图中,左边是 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 引脚组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
can0 {
/omit-if-no-ref/
can0m0_pins: can0m0-pins {
rockchip,pins =
/* can0_rxm0 */
<0 RK_PB4 2 &pcfg_pull_none>,
/* can0_txm0 */
<0 RK_PB3 2 &pcfg_pull_none>;
};

/omit-if-no-ref/
can0m1_pins: can0m1-pins {
rockchip,pins =
/* can0_rxm1 */
<2 RK_PA2 4 &pcfg_pull_none>,
/* can0_txm1 */
<2 RK_PA1 4 &pcfg_pull_none>;
};
};

引脚组 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
can1 {
/omit-if-no-ref/
can1m0_pins: can1m0-pins {
rockchip,pins =
/* can1_rxm0 */
<1 RK_PA0 3 &pcfg_pull_none>,
/* can1_txm0 */
<1 RK_PA1 3 &pcfg_pull_none>;
};

/omit-if-no-ref/
can1m1_pins: can1m1-pins {
rockchip,pins =
/* can1_rxm1 */
<4 RK_PC2 3 &pcfg_pull_none>,
/* can1_txm1 */
<4 RK_PC3 3 &pcfg_pull_none>;
};
};

引脚组 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
2
3
4
5
&uart1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
status = "okay";
};

这个就相当于client device。它对应的pin controller是什么?我们打开imx6ul-14x14-evk.dtsi,会有如下内容:

1
2
3
4
5
6
pinctrl_uart1: uart1grp {
fsl,pins = <
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1
>;
};

二、PIN配置信息

上面大概了解了一些基本概念,我们以I.MX6ULL为例,看一下设备树中的pin配置信息。

要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要根据我们提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信息。

1. iomuxc节点

打开 imx6ul.dtsi 文件,找到一个叫做 iomuxc 的节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
interrupt-parent = <&gpc>;
ranges;
//......
iomuxc: iomuxc@20e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};
//......
};

iomuxc 节点就是 I.MX6ULL 的 IOMUXC 外设对应的节点,看起来内容很少,没看出什么跟 PIN 的配置有关的内容啊,这里其实只是指定了这个IOMUXC 外设,我们可以看一下reg属性,往前翻一下,就会发现,这个节点的父节点配置了:

1
2
#address-cells = <1>; // 它指定了设备树中地址单元的位数。告诉解析设备树的软件在解释设备地址时应该使用多少位来表示一个地址单元。
#size-cells = <1>; // 告诉解析设备树的软件在解释设备大小时应该使用多少位来表示一个大小单元。

所以这里的reg属性中,它的地址是0x020e0000,大小是0x4000。我们看一下《 i.MX 6ULL Applications Processor Reference Manual》—— Table 2-2. AIPS-1 memory map :

image-20250329195740385

会发现,这个外设的起始地址就是0x020e0000,大小是16KB,就是0x4000。

2. &iomuxc

我们打开 imx6ul-14x14-evk.dtsi 找到以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
&iomuxc {
pinctrl-names = "default";
//......
pinctrl_flexcan1: flexcan1grp{
fsl,pins = <
MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020
MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020
>;
};
//......
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
//......
pinctrl_wdog: wdoggrp {
fsl,pins = <
MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY 0x30b0
>;
};
};

这个部分就是向 iomuxc 节点追加数据,不同的外设使用的 PIN 不同、其配置也不同,因此一个萝卜一个坑,将某个外设所使用的所有 PIN 都组织在一个子节点里面。例如上面的 pinctrl_i2c1就是I2C1外设所用的pin引脚信息。和前面的结合起来就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
interrupt-parent = <&gpc>;
ranges;
//......
iomuxc: iomuxc@20e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;

pinctrl-names = "default";

pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
};
//......
};

第 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 :

image-20250329200416047

会发现,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
2
3
4
5
6
7
8
#define MX6UL_PAD_UART4_TX_DATA__UART4_DCE_TX		    0x00b4 0x0340 0x0000 0 0
#define MX6UL_PAD_UART4_TX_DATA__UART4_DTE_RX 0x00b4 0x0340 0x063c 0 0
#define MX6UL_PAD_UART4_TX_DATA__ENET2_TDATA02 0x00b4 0x0340 0x0000 1 0
#define MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x00b4 0x0340 0x05a4 2 1
#define MX6UL_PAD_UART4_TX_DATA__CSI_DATA12 0x00b4 0x0340 0x04f4 3 0
#define MX6UL_PAD_UART4_TX_DATA__CSU_CSU_ALARM_AUT02 0x00b4 0x0340 0x0000 4 0
#define MX6UL_PAD_UART4_TX_DATA__GPIO1_IO28 0x00b4 0x0340 0x0000 5 0
#define MX6UL_PAD_UART4_TX_DATA__ECSPI2_SCLK 0x00b4 0x0340 0x0544 8 1

我们看一下《 i.MX 6ULL Applications Processor Reference Manual》—— 32.6.29 这一部分:

image-20250329201411264

观察一下就会发现,上面的宏以及展开后对应的第三个值和下面图片中的刚好对应。这个时候我们再来看这个:

1
#define MX6UL_PAD_UART4_TX_DATA__I2C1_SCL		0x00b4 0x0340 0x05a4 2 1

这 5 个值的含义如下所示:

1
2
3
#define MX6UL_PAD_UART4_TX_DATA__I2C1_SCL		\
0x00b4 0x0340 0x05a4 2 1
// <mux_reg conf_reg input_reg mux_mode input_val>
  • 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的地址:

image-20250330010848309
  • 0x0340:conf_reg 寄存器偏移地址,在这里就是IOMUXC_SW_PAD_CTL_PAD_UART4_TX_DATA

0x020e0000+0x0340=0x020e0340,这个就是寄存器IOMUXC_SW_PAD_CTL_PAD_UART4_TX_DATA 的地址。

image-20250330011035677
  • 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:

image-20250329203121557
  • 2:mux_reg 寄存器值 , 在这里就相当于设置 IOMUXC_SW_MUX_CTL_PAD_UART4_TX_DATA 寄存器为 0x2,也即是设置 UART4_TX_DATA 这个 PIN 复用为I2C1_SCL。
image-20250330011415656
  • 1: input_reg 寄存器值,这里就是设置IOMUXC_I2C1_SCL_SELECT_INPUT寄存器低2位为1:
image-20250329203519277

2.1.2 0x4001b8b0

上面已经设置了IOMUXC_SW_MUX_CTL_PAD_UART4_TX_DATA复用寄存器的值,那么还有电气属性没有设置,那么IOMUXC_SW_PAD_CTL_PAD_UART4_TX_DATA寄存器的值是怎么设置的?前面的设备树中不是还有个值吗?

1
2
3
4
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;

接下来看 0x4001b8b0 就是 conf_reg 寄存器值!此值由用户自行设置,通过此值来设置一个 IO 的上/下拉、驱动能力和速度等。这里 0x4001b8b0 换算成二进制就是 0100 0000 0000 0001 1011 1000 1011 0000:

image-20250330011940162

具体含义可以看参考手册对应寄存器的说明。

2.2 &i2c1

怎么使用上面的pinctrl信息?我们来看一下i2c1这个节点,这个就相当于上面说的client device,我们打开imx6ul-14x14-evk.dtsi

1
2
3
4
5
6
7
8
9
10
11
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";

mag3110@e {
compatible = "fsl,mag3110";
reg = <0x0e>;
};
};

3. 设备树添加pinctrl节点

关于 I.MX 系列 SOC 的 pinctrl 设备树绑定信息可以参考文档:

fsl,imx-pinctrl.txt - Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt - Linux source code v4.19.71

这里我们虚拟一个名为“test”的设备, test 使用了 GPIO1_IO00 这个 PIN 的 GPIO 功能, pinctrl 节点添加过程如下:

3.1 创建对应的节点

同一个外设的 PIN 都放到一个节点里面,打开imx6ul-14x14-evk.dtsi ,在 &iomuxc 节点中添加“pinctrl_test”节点,注意!节点前缀一定要为“pinctrl_”。添加完成以后如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
&iomuxc {
pinctrl-names = "default";
//......
pinctrl_wdog: wdoggrp {
fsl,pins = <
MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY 0x30b0
>;
};
// pinctrl_test 节点
pinctrl_test: testgrp {
/* 具体的 PIN 信息 */
};
};

3.2 添加“fsl,pins”属性

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

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

3.3 在“fsl,pins”属性中添加 PIN 配置信息

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

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

4. 生成pincontroller设备树信息

pincontroller设备树的信息是怎么生成的?生成pincontroller设备树信息,有3种方法:

image-20250401171331179
  • 有些芯片,只能看厂家给的设备树文档或者参考设备树的例子
  • 最差的就是需要阅读驱动代码才能构造设备树信息。

三、I.MX6ULL 的 pinctrl 子系统驱动

所有的东西都已经准备好了,包括寄存器地址和寄存器值, Linux 内核相应的驱动文件就会根据这些值来做相应的初始化。

iomuxc 节点中 compatible 属性的值为“fsl,imx6ul-iomuxc”,在 Linux 内核中全局搜索“fsl,imx6ul-iomuxc”字符串就会找到对应的驱动文件。

1.pincontroller的数据结构

1.1 pinctrl_dev

1
2
3
4
5
6
struct pinctrl_dev {
struct list_head node;
struct pinctrl_desc *desc;
struct radix_tree_root pin_desc_tree;
//......
};

这里我们主要关注这个desc指针,它是struct pinctrl_desc 类型的。pincontroller虽然是一个软件的概念,但是它背后是有硬件支持的,所以可以使用一个结构体来表示它:pinctrl_dev。怎么使用pinctrl_descpinctrl_dev来描述一个pin controller?这两个结构体定义如下:

image-20210505153958014

1.2 pinctrl_desc

linux中使用结构体 struct pinctrl_desc 来描述一个引脚控制器(pinctrl) 的属性和操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct pinctrl_desc {
const char *name; // 引脚控制器的名称
const struct pinctrl_pin_desc *pins;// 引脚描述符数组
unsigned int npins; // 引脚描述符数组的大小
const struct pinctrl_ops *pctlops; // 引脚控制操作函数指针
const struct pinmux_ops *pmxops; // 引脚复用操作函数指针
const struct pinconf_ops *confops; // 引脚配置操作函数指针
struct module *owner; // 拥有该结构体的模块
#ifdef CONFIG_GENERIC_PINCONF
unsigned int num_custom_params; // 自定义参数数量
const struct pinconf_generic_params *custom_params; // 自定义参数数组
const struct pin_config_item *custom_conf_items; // 自定义配置项数组
#endif
};

引脚控制器是硬件系统中的一个组件, 用于管理和控制引脚的功能和状态。 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
2
3
4
5
struct pinctrl_pin_desc {
unsigned number;
const char *name;
void *drv_data;
};

1.2.2 pinctrl_desc.pinctrl_ops

1
2
3
4
5
6
7
struct pinctrl_ops {
//......
int (*dt_node_to_map) (struct pinctrl_dev *pctldev,
struct device_node *np_config,
struct pinctrl_map **map, unsigned *num_maps);
//......
};

重点是函数 pinctrl_desc.pinctrl_ops.dt_node_to_map,这个函数用于处理设备树中pin controller中的某个节点,可以通过该函数将device_node转换为一系列的pinctrl_map

1.2.3 pinctrl_desc.pinmux_ops

1
2
3
4
5
6
struct pinmux_ops {
//......
int (*set_mux) (struct pinctrl_dev *pctldev, unsigned func_selector,
unsigned group_selector);
//......
};

pinctrl_desc.pinmux_ops中需要关注的是 pinctrl_desc.pinmux_ops.set_mux函数,这个函数用于配置引脚的复用。

1.2.4 pinctrl_desc.pinconf_ops

1
2
3
4
5
6
7
8
9
10
11
12
13
struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONF
bool is_generic;
#endif
int (*pin_config_get) (struct pinctrl_dev *pctldev, unsigned pin, unsigned long *config);
int (*pin_config_set) (struct pinctrl_dev *pctldev, unsigned pin, unsigned long *configs, unsigned num_configs);
int (*pin_config_group_get) (struct pinctrl_dev *pctldev, unsigned selector, unsigned long *config);
int (*pin_config_group_set) (struct pinctrl_dev *pctldev, unsigned selector, unsigned long *configs, unsigned num_configs);
int (*pin_config_dbg_parse_modify) (struct pinctrl_dev *pctldev, const char *arg, unsigned long *config);
void (*pin_config_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s, unsigned offset);
void (*pin_config_group_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s, unsigned selector);
void (*pin_config_config_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s, unsigned long config);
};

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
2
3
4
5
6
7
8
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
struct device *dev, void *driver_data)
{
//......
pctldev = pinctrl_init_controller(pctldesc, dev, driver_data);
//......

}

内部会再调用pinctrl_init_controller(),下面的删去了暂时不需要关心的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static struct pinctrl_dev *
pinctrl_init_controller(struct pinctrl_desc *pctldesc, struct device *dev,
void *driver_data)
{
struct pinctrl_dev *pctldev;

pctldev = kzalloc(sizeof(*pctldev), GFP_KERNEL);
if (!pctldev)
return ERR_PTR(-ENOMEM);

/* Initialize pin control device struct */
pctldev->owner = pctldesc->owner;
pctldev->desc = pctldesc;
pctldev->driver_data = driver_data;

/* check core ops for sanity */
ret = pinctrl_check_ops(pctldev);

/* If we're implementing pinmuxing, check the ops for sanity */
ret = pinmux_check_ops(pctldev);

/* If we're implementing pinconfig, check the ops for sanity */
ret = pinconf_check_ops(pctldev);

/* Register all the pins */
ret = pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins);

}

2. client的数据结构

2.1 device

在设备树中,使用pinctrl时格式如下:

1
2
3
4
5
6
/* For a client device requiring named states */
device {
pinctrl-names = "active", "idle";
pinctrl-0 = <&state_0_node_a>;
pinctrl-1 = <&state_1_node_a &state_1_node_b>;
};

设备节点要么被转换为platform_device,或者其他结构体(比如i2c_client),但是里面都会有一个device结构体,

比如:

image-20210505171819747

2.2 device.dev_pin_info

每个device结构体里都有一个dev_pin_info结构体,用来保存设备的pinctrl信息:

1
2
3
4
5
6
7
8
9
struct dev_pin_info {
struct pinctrl *p;// 引脚控制器指针
struct pinctrl_state *default_state;// 默认状态指针
struct pinctrl_state *init_state; // 初始化状态指针
#ifdef CONFIG_PM
struct pinctrl_state *sleep_state;// 睡眠状态指针(仅在支持电源管理时可用)
struct pinctrl_state *idle_state; // 空闲状态指针(仅在支持电源管理时可用)
#endif
};

(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() 的时候会继续学习。

image-20210505173004090

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链表中。

image-20210505182828324

2.2.2 pinctrl_mappinctrl_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链表

image-20210505182324076

2.2.4 使用pinctrl_setting

函数调用过程如下:

1
2
3
4
really_probe()
pinctrl_bind_pins()
pinctrl_select_state()
pinctrl_commit_state()

pinctrl_commit_state()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Apply all the settings for the new state */
list_for_each_entry(setting, &state->settings, node) {
switch (setting->type) {
case PIN_MAP_TYPE_MUX_GROUP:
ret = pinmux_enable_setting(setting);
ret = ops->set_mux(...);
break;
case PIN_MAP_TYPE_CONFIGS_PIN:
case PIN_MAP_TYPE_CONFIGS_GROUP:
ret = pinconf_apply_setting(setting);
ret = ops->pin_config_group_set(...);
break;
default:
ret = -EINVAL;
break;
}
image-20210505183256914

3. 驱动源码分析

3.1 驱动入口与匹配表

3.1.1 imx6ul_pinctrl_init()

pinctrl-imx6ul.c - drivers/pinctrl/freescale/pinctrl-imx6ul.c 中有如下内容:

1
2
3
4
5
static int __init imx6ul_pinctrl_init(void)
{
return platform_driver_register(&imx6ul_pinctrl_driver);
}
arch_initcall(imx6ul_pinctrl_init);

会发现,这也都是平台设备驱动。平台设备驱动结构体为 imx6ul_pinctrl_driver

1
2
3
4
5
6
7
static struct platform_driver imx6ul_pinctrl_driver = {
.driver = {
.name = "imx6ul-pinctrl",
.of_match_table = of_match_ptr(imx6ul_pinctrl_of_match),
},
.probe = imx6ul_pinctrl_probe,
};

3.1.2 imx6ul_pinctrl_driver.of_match_table

接下来看一下这个imx6ul_pinctrl_driver.of_match_table,它指向的是数组imx6ul_pinctrl_of_match

1
2
3
4
5
static const struct of_device_id imx6ul_pinctrl_of_match[] = {
{ .compatible = "fsl,imx6ul-iomuxc", .data = &imx6ul_pinctrl_info, },
{ .compatible = "fsl,imx6ull-iomuxc-snvs", .data = &imx6ull_snvs_pinctrl_info, },
{ /* sentinel */ }
};

之前学习设备树的时候说过了, 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
2
3
4
iomuxc: iomuxc@20e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};

3.2 imx6ul_pinctrl_driver.imx6ul_pinctrl_probe()

当驱动匹配上之后,就会执行.probe函数,在这里就是 imx6ul_pinctrl_probe()

1
2
3
4
5
6
7
8
9
10
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{
const struct imx_pinctrl_soc_info *pinctrl_info;
//进行驱动和设备树的匹配,匹配后获取相关信息
pinctrl_info = of_device_get_match_data(&pdev->dev);
if (!pinctrl_info)
return -ENODEV;
// 完成pinctrl子系统初始化
return imx_pinctrl_probe(pdev, pinctrl_info);
}

后续就是我们就是要分析这个imx_pinctrl_probe()函数了。

image-20250330051104962

3.3.1 imx_pinctrl_parse_groups()

image-20250330051435182

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

  • 511 - 515 行:获取 mux_reg、 conf_reg、 input_reg、 mux_mode 和 input_val 值。
image-20250330051538198
  • 527 行:获取 config 值。
image-20250330051729390

3.3.2 pinctrl_register()

1
2
3
4
5
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
struct device *dev, void *driver_data)
{
//......
}

这个我们主要看一下结构体 struct pinctrl_desc ,它被用来描述一个引脚控制器(pinctrl) 的属性和操作:

1
2
3
4
5
6
7
struct pinctrl_desc {
//......
const struct pinctrl_ops *pctlops; // 引脚控制操作函数指针
const struct pinmux_ops *pmxops; // 引脚复用操作函数指针
const struct pinconf_ops *confops; // 引脚配置操作函数指针
//......
};

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int imx_pinctrl_probe(struct platform_device *pdev,
const struct imx_pinctrl_soc_info *info)
{
//......
imx_pinctrl_desc->name = dev_name(&pdev->dev);
imx_pinctrl_desc->pins = info->pins;
imx_pinctrl_desc->npins = info->npins;
imx_pinctrl_desc->pctlops = &imx_pctrl_ops;
imx_pinctrl_desc->pmxops = &imx_pmx_ops;
imx_pinctrl_desc->confops = &imx_pinconf_ops;
imx_pinctrl_desc->owner = THIS_MODULE;
//......
platform_set_drvdata(pdev, ipctl);
ret = devm_pinctrl_register_and_init(&pdev->dev,
imx_pinctrl_desc, ipctl,
&ipctl->pctl);
}

其中用到了imx_pctrl_opsimx_pmx_opsimx_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
2
3
4
5
6
7
8
9
10
struct pinctrl_map {
const char *dev_name; // 设备名称
const char *name; // 映射名称
enum pinctrl_map_type type; // 映射类型
const char *ctrl_dev_name; // 控制设备名称
union {
struct pinctrl_map_mux mux; // 复用映射数据
struct pinctrl_map_configs configs; // 配置映射数据
} data;
};

该结构体用于在引脚控制器中定义引脚的映射关系。 通过映射类型的不同, 可以将引脚与具体的复用功能或配置信息关联起来,从而实现引脚的配置和控制 。

3.4.2 dt_node_to_map()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
static int imx_dt_node_to_map(struct pinctrl_dev *pctldev,
struct device_node *np,
struct pinctrl_map **map, unsigned *num_maps)
{
struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);// 获取引脚控制器的私有数据指针
const struct group_desc *grp;// 引脚组指针
struct pinctrl_map *new_map; // 新的引脚映射数组
struct device_node *parent; // 父节点指针
int map_num = 1; // 映射数量, 默认为 1
int i, j;

/*
* first find the group of this node and check if we need create
* config maps for pins
*/
/* 查找引脚组 */
grp = imx_pinctrl_find_group_by_name(pctldev, np->name); // 根据节点名称查找对应的引脚组
if (!grp) {
dev_err(ipctl->dev, "unable to find group for node %s\n",
np->name);
return -EINVAL;
}

for (i = 0; i < grp->num_pins; i++) {
struct imx_pin *pin = &((struct imx_pin *)(grp->data))[i];

if (!(pin->config & IMX_NO_PAD_CTL))
map_num++;
}

new_map = kmalloc_array(map_num, sizeof(struct pinctrl_map),
GFP_KERNEL);
if (!new_map)
return -ENOMEM;

*map = new_map;
*num_maps = map_num;

/* create mux map */
/* 创建复用映射 */
parent = of_get_parent(np);// 获取节点的父节点
if (!parent) {
kfree(new_map);
return -EINVAL;
}
new_map[0].type = PIN_MAP_TYPE_MUX_GROUP; // 设置映射类型为复用映射
new_map[0].data.mux.function = parent->name;// 复用功能名称为父节点的名称
new_map[0].data.mux.group = np->name;// 引脚组名称为节点的名称
of_node_put(parent);// 释放父节点的引用计数

/* create config map */
/* 创建配置映射 */
new_map++;
for (i = j = 0; i < grp->num_pins; i++) {
struct imx_pin *pin = &((struct imx_pin *)(grp->data))[i];

if (!(pin->config & IMX_NO_PAD_CTL)) {
new_map[j].type = PIN_MAP_TYPE_CONFIGS_PIN;// 设置映射类型为配置映射
new_map[j].data.configs.group_or_pin =
pin_get_name(pctldev, pin->pin);// 引脚组或引脚名称为引脚组中的引脚名称
new_map[j].data.configs.configs = &pin->config; // 配置信息数组为引脚组中该引脚的配置信息
new_map[j].data.configs.num_configs = 1;
j++;
}
}

dev_dbg(pctldev->dev, "maps: function %s group %s num %d\n",
(*map)->data.mux.function, (*map)->data.mux.group, map_num);

return 0;
}

这个函数根据设备节点的信息创建引脚映射, 包括复用映射和配置映射。 复用映射用于将引脚组的功能与父节点的功能关联起来, 而配置映射用于将引脚的配置信息与引脚的名称关联起来。 这些映射将用于配置引脚控制器, 以确保引脚在系统中正确地配置和使用。 这个函数在设备树解析过程中被调用, 以便为每个设备节点创建相应的引脚映射。

3.4 imx_pmx_ops.imx_pmx_set()

1
2
3
4
5
static int imx_pmx_set(struct pinctrl_dev *pctldev, unsigned selector,
unsigned group)
{
//......
}

这个函数会对引脚进行配置。这里就不详细去分析了。

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static int really_probe(struct device *dev, struct device_driver *drv)
{
//......
re_probe:
dev->driver = drv;// 将设备的驱动程序指针设置为当前驱动

/* If using pinctrl, bind pins now before probing */
// 如果使用了 pinctrl, 在探测之前绑定引脚
ret = pinctrl_bind_pins(dev);
if (ret)
goto pinctrl_bind_failed;

ret = dma_configure(dev);// 绑定设备的引脚
if (ret)
goto probe_failed;

if (driver_sysfs_add(dev)) { // 添加驱动程序的 sysfs 接口
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
//......
if (dev->bus->probe) {// 如果总线的探测函数存在, 则调用总线的探测函数
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
//......
}

如果使用了 pinctrl 就会调用 第 9 行pinctrl_bind_pins() 函数进行设备引脚的绑定。

3.5.2 pinctrl_bind_pins()

1
2
3
4
5
6
7
8
9
struct dev_pin_info {
struct pinctrl *p;// 引脚控制器指针
struct pinctrl_state *default_state;// 默认状态指针
struct pinctrl_state *init_state; // 初始化状态指针
#ifdef CONFIG_PM
struct pinctrl_state *sleep_state;// 睡眠状态指针(仅在支持电源管理时可用)
struct pinctrl_state *idle_state; // 空闲状态指针(仅在支持电源管理时可用)
#endif
};

前面介绍了每个成员的含义,这里举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
rk_485_ctl: rk-485-ctl {
compatible = "topeet,rs485_ctl";
gpios = <&gpio0 22 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&rk_485_gpio>;
};

&pinctrl {
rk_485{
rk_485_gpio:rk-485-gpio {
rockchip,pins = <3 13 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
};

其中第 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
int pinctrl_bind_pins(struct device *dev)
{
int ret;

if (dev->of_node_reused)// 检查设备是否重用了节点
return 0;
// 为设备的引脚分配内存空间
dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
if (!dev->pins)
return -ENOMEM;
// 获取设备的 pinctrl 句柄
dev->pins->p = devm_pinctrl_get(dev);
if (IS_ERR(dev->pins->p)) {
dev_dbg(dev, "no pinctrl handle\n");
ret = PTR_ERR(dev->pins->p);
goto cleanup_alloc;
}
// 查找设备的默认 pinctrl 状态
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_DEFAULT);
if (IS_ERR(dev->pins->default_state)) {
dev_dbg(dev, "no default pinctrl state\n");
ret = 0;
goto cleanup_get;
}
// 查找设备的初始化 pinctrl 状态
dev->pins->init_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_INIT);
if (IS_ERR(dev->pins->init_state)) {
/* Not supplying this state is perfectly legal */
dev_dbg(dev, "no init pinctrl state\n");

ret = pinctrl_select_state(dev->pins->p,
dev->pins->default_state);
} else {
ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state);
}

if (ret) {
dev_dbg(dev, "failed to activate initial pinctrl state\n");
goto cleanup_get;
}

#ifdef CONFIG_PM
/*
* If power management is enabled, we also look for the optional
* sleep and idle pin states, with semantics as defined in
* <linux/pinctrl/pinctrl-state.h>
*/
dev->pins->sleep_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_SLEEP);
if (IS_ERR(dev->pins->sleep_state))
/* Not supplying this state is perfectly legal */
dev_dbg(dev, "no sleep pinctrl state\n");

dev->pins->idle_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_IDLE);
if (IS_ERR(dev->pins->idle_state))
/* Not supplying this state is perfectly legal */
dev_dbg(dev, "no idle pinctrl state\n");
#endif

return 0;

/*
* If no pinctrl handle or default state was found for this device,
* let's explicitly free the pin container in the device, there is
* no point in keeping it around.
*/
cleanup_get:
devm_pinctrl_put(dev->pins->p);
cleanup_alloc:
devm_kfree(dev, dev->pins);
dev->pins = NULL;

/* Return deferrals */
if (ret == -EPROBE_DEFER)
return ret;
/* Return serious errors */
if (ret == -EINVAL)
return ret;
/* We ignore errors like -ENOENT meaning no pinctrl state */

return 0;
}

会用就行,这里后面有机会再详细分析。

3.6 总结

下面这个图是讯为的rk3568开发板文档中的一张图,都差不多,帮助理解。

image-20250403100549998

4. 总结

pinctrl的三大作用,有助于理解所涉及的数据结构:

  • (1)引脚枚举与命名(Enumerating and naming):包括单个引脚和各组引脚

  • (2)引脚复用(Multiplexing):比如用作GPIO、I2C或其他功能

  • (3)引脚配置(Configuration):比如上拉、下拉、open drain、驱动强度等

pinctrl驱动程序的核心是构造一个pinctrl_desc结构体:

image-20210505153958014

4.1 作用1:描述、获得引脚

分为2部分:

  • 描述、获得单个引脚的信息
  • 描述、获得某组引脚的信息
image-20210513162517514

4.2 作用2:引脚复用

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

image-20210513162922762

4.3 作用3:引脚配置

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

image-20210513163832306

参考资料:

linux内核驱动-pinctrl子系统 - fuzidage - 博客园

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