Linux pinctrl 子系统
一、Pinctrl的使用
大多数 SOC 的 pin 都是支持复用的,例如某个SOC的GPIO3_D4 既可以作为普通的 GPIO 使用,也可以作为 PWM1_M0 引脚、GPU_AVS 引脚、UART0_RX 引脚。此外我们还需要配置 pin 的电气特性,比如上/下拉、驱动能力等等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。pinctrl 子系统就是为了解决这个问题而引入的,pinctrl 子系统主要工作内容如下:
- 获取设备树中 pin 信息。
- 根据获取到的 pin 信息来设置 pin 的复用功能
- 根据获取到的 pin 信息来设置 pin 的电气特性,如驱动能力。
对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成,pinctrl 子系统源码目录为 drivers/pinctrl。
1.PIN 配置信息详解
要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信息。
C
//imx6ull.dtsi
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};
//imx6ull-14x14-evk.dts
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>; //hog pin是系统默认的状态,在 pinctrl 初始化阶段立即生效
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /* SD1 VSELECT */
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 /* SD1 RESET */
>;
};
pinctrl_csi1: csi1grp {
fsl,pins = <
MX6UL_PAD_CSI_MCLK__CSI_MCLK 0x1b088
MX6UL_PAD_CSI_PIXCLK__CSI_PIXCLK 0x1b088
...
>;
};
...
}
iomuxc是 IOMUX 控制器,负责 pin 的复用和 pad 控制compatible决定使用哪个 pinctrl 驱动(drivers/pinctrl/freescale/)reg指向 IOMUXC 寄存器基地址
所有 pin 的复用和电气配置,最终都会通过该控制器完成。
这里要说明一下,PAD、Pin、GPIO是三个东西:
-
PAD SoC 内部 负责复用、电气特性、信号缓冲
-
Pin / Ball 芯片封装 焊球 / 引脚编号
-
GPIO 逻辑功能 数字输入输出控制器
PAD 是 pinctrl / IOMUX 直接操作的对象,多个内部信号共享同一个 PAD,通过复用选择其一。
通常在 &iomuxc 节点下定义多个 pinctrl 子节点,每个子节点描述一组功能相关的 pin,例如上面的pinctrl_hog_1、pinctrl_csi1。
c
pinctrl_xxx: xxxgrp {
fsl,pins = <
PAD_MUX_CONFIG PAD_CTRL_CONFIG
...
>;
};
pinctrl_xxx:label,用于被其他设备节点引用
xxxgrp:节点名,无实际功能意义,仅用于区分
fsl,pins:平台私有属性,描述具体 pin 配置
fsl,pins 属性格式说明
该属性由 若干组 <PAD MUX + PAD CTRL> 组成,每一组代表一个 pin 的配置。举例:
C
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
>;
PAD MUX :宏MX6UL_PAD_UART1_RTS_B__GPIO1_IO19包含三部分信息,MX6UL_PAD_UART1_RTS_B表示物理 PAD 名称,__表示分隔符,GPIO1_IO19表示复用后的功能,表示将 UART1_RTS_B 这个 PAD 复用为 GPIO1_IO19 功能。这些宏定义位于arch/arm/boot/dts/imx6ull-pinfunc.h。
PAD CTRL :0x17059用于配置 pin 的电气特性,对应 IOMUXC_PAD 控制寄存器。常见控制项包括:上拉 / 下拉、驱动能力、速率、施密特触发、推挽开漏。
2.设备节点中引用 pinctrl
pinctrl 配置并不会自动生效,必须被设备节点引用。
基本引用方式
c
uart1: serial@02020000 {
compatible = "fsl,imx6ul-uart";
reg = <0x02020000 0x4000>;
interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>; //引用pinctrl_uart1
status = "okay";
};
//对应的pinctrl定义
pinctrl_uart1: uart1grp {
fsl,pins = <
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1
>;
};
多状态 pinctrl
ini
pinctrl-names = "default", "sleep";
pinctrl-0 = <&pinctrl_uart1_default>;
pinctrl-1 = <&pinctrl_uart1_sleep>;
这里有两个状态:
default:工作状态
sleep:低功耗状态(关闭上下拉、降低驱动)
是否切换由驱动调用:pinctrl_select_state();
二、内部原理
核心思路 (方便回忆) pinctrl 是 Linux 内核中负责管理引脚复用与引脚配置的子系统。
它通过Pin Controller 驱动和 设备驱动两层模型,实现了从设备树引脚定义到硬件寄存器配置的完整映射。
简单流程版:
- pinctrl 子系统的工作流程从 Pin Controller 驱动开始。首先,内核通过设备树中的 compatible 属性匹配到对应的 pinctrl 驱动,当匹配成功后,会调用该驱动的 probe 函数。在 probe 阶段,驱动会根据 SoC 的特性填充一个 pinctrl_desc 结构体,其中包含引脚描述信息(pins/npins)、操作函数(pctlops、pmxops、confops)等内容,并通过 devm_pinctrl_register() 将该 pin controller 注册到内核中,完成引脚控制器的初始化与功能注册。
- 当具体使用pinctrl的设备驱动在 probe 阶段加载时,内核会根据设备树中定义的引脚配置调用 pinctrl_bind_pins() 函数,为该设备建立引脚控制上下文。该函数首先为设备创建一个 dev_pin_info 结构体,用于保存设备的引脚控制信息。其中,函数会调用 devm_pinctrl_get() 创建一个新的 pinctrl 结构体,并将其赋值给
dev_pin_info的p成员。 - 随后,内核通过 pinctrl_dt_to_map() 从设备树中读取该设备的引脚配置,并调用 pinctrl 驱动提供的 imx_dt_node_to_map() 等函数来解析
pinctrl-0、pinctrl-1等节点。解析过程会生成一系列 pinctrl_map 结构体,每个 map 对应一组引脚功能或配置。这些 map 会被进一步转换为 pinctrl_setting ,并挂入对应状态的 pinctrl_state.settings 链表中,同时所有 map 信息也会保存在pinctrl的dt_maps链表中。 - 在此基础上,内核根据不同的 pinctrl 节点(如 default、sleep 等)创建多个 pinctrl_state ,每个状态对应设备树中定义的一个引脚状态。这些状态被添加到
pinctrl的states链表中,而dev_pin_info结构中的default_state、sleep_state等指针则会被设置为指向这些状态结构。 - 当设备运行过程中需要切换引脚配置(例如进入睡眠或恢复默认状态)时,驱动可调用 pinctrl_select_state() 函数来切换到对应的状态。该函数会遍历当前状态下的所有 pinctrl_setting,并根据每个 setting 的类型(复用或配置)调用 pin controller 驱动提供的操作接口(ops),最终通过底层寄存器配置完成硬件引脚功能与电气属性的切换。
这一整套机制实现了从设备树定义到硬件引脚配置的完整自动化映射流程。
1.Provider 端
Pin Controller 驱动注册流程
(1)设备树匹配
在设备树中定义一个 pin controller 节点:
ini
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};
对应驱动中的匹配表:
arduino
static const struct of_device_id imx6ul_pinctrl_of_match[] = {
{ .compatible = "fsl,imx6ul-iomuxc", .data = &imx6ul_pinctrl_info },
{ /* sentinel */ }
};
当 compatible 匹配成功后,调用:
arduino
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{
return imx_pinctrl_probe(pdev, pinctrl_info);
}
(2)probe 阶段
构造并注册 pinctrl_desc
核心函数 imx_pinctrl_probe():
ini
int imx_pinctrl_probe(struct platform_device *pdev,
struct imx_pinctrl_soc_info *info)
{
struct pinctrl_desc *desc;
desc->name = dev_name(&pdev->dev);
desc->pins = info->pins; // 引脚数组
desc->npins = info->npins; // 引脚数量
desc->pctlops = &imx_pctrl_ops; // 分组操作
desc->pmxops = &imx_pmx_ops; // 复用操作
desc->confops = &imx_pinconf_ops; // 引脚配置操作
desc->owner = THIS_MODULE;
// 注册 pin controller
ipctl->pctl = devm_pinctrl_register(&pdev->dev, desc, ipctl);
}
devm_pinctrl_register() 会在内核中注册一个 pinctrl_dev 实例:
arduino
struct pinctrl_dev {
struct pinctrl_desc *desc;
struct device *dev;
void *driver_data;
};
(3)三大操作函数
pinctrl 框架通过以下三类 ops 完成抽象:
| 操作集 | 功能 | 示例函数 |
|---|---|---|
| pinctrl_ops | 引脚组管理与设备树解析 | dt_node_to_map() |
| pinmux_ops | 控制引脚复用 | set_mux() |
| pinconf_ops | 控制引脚电气属性 | pin_config_set() |
示例:
ini
static const struct pinctrl_ops imx_pctrl_ops = {
.get_groups_count = imx_get_groups_count,
.get_group_name = imx_get_group_name,
.dt_node_to_map = imx_dt_node_to_map, // 关键:设备树转 pinctrl_map
};
2.Consumer 端
设备驱动的引脚绑定与状态创建
(1)设备树中定义引脚配置
ini
&iomuxc {
imx6ul-evk {
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART1_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART1_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
};
};
i2c1: i2c@021a0000 {
compatible = "fsl,imx6ul-i2c";
pinctrl-names = "default", "sleep";
pinctrl-0 = <&pinctrl_i2c1>;
pinctrl-1 = <&pinctrl_i2c1_sleep>;
};
pinctrl-names 与 pinctrl-0, pinctrl-1 等属性定义了设备的多种引脚状态。
(2)pinctrl_bind_pins()
设备 probe 时的引脚绑定入口:pinctrl_bind_pins()
当设备驱动被加载时,really_probe() 调用:
scss
pinctrl_bind_pins(dev);
该函数负责为设备创建引脚控制信息,是 Consumer 端的核心入口。
(3)pinctrl_bind_pins
pinctrl_bind_pins 内部关键步骤
step1.创建 dev_pin_info 结构
ini
dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
struct dev_pin_info 用于描述设备与 pinctrl 子系统的绑定关系:
arduino
struct dev_pin_info {
struct pinctrl *p; // 指向该设备的 pinctrl 句柄
struct pinctrl_state *default_state; // 默认状态
struct pinctrl_state *init_state; // 初始化状态
struct pinctrl_state *sleep_state; // 睡眠状态
struct pinctrl_state *idle_state; // 空闲状态
};
dev_pin_info 是设备和 pinctrl 框架之间的"桥梁":
p:指向设备对应的struct pinctrl- 其他成员指向不同命名状态的
pinctrl_state
step2.创建 pinctrl 结构
ini
dev->pins->p = devm_pinctrl_get(dev);
devm_pinctrl_get() 内部执行:
- 创建
struct pinctrl; - 调用
pinctrl_dt_to_map()解析设备树; - 将解析结果保存到
pinctrl->dt_maps; - 为每个状态创建
pinctrl_state并加入pinctrl->states链表。
step3.设备树解析 → pinctrl_map 构建
pinctrl_dt_to_map() 通过 pinctrl 驱动提供的 dt_node_to_map() 来解析:
scss
imx_dt_node_to_map(np_config, &map, &num_maps);
每个节点(如 pinctrl-0)会生成若干个 pinctrl_map:
arduino
struct pinctrl_map {
const char *dev_name;
const char *name;
enum pinctrl_map_type type;
union {
struct { const char *group; const char *function; } mux;
struct { const char *group_or_pin; unsigned long *configs; unsigned num_configs; } configs;
} data;
};
这些 pinctrl_map 描述了设备、组、功能与配置之间的对应关系,并被挂入:
rust
pinctrl->dt_maps
step4.pinctrl_map → pinctrl_setting → pinctrl_state
每个 pinctrl_map 会被转换为 pinctrl_setting,挂入对应的 pinctrl_state.settings 链表。
结构关系如下:
scss
dev_pin_info
└── pinctrl
├── states (链表)
│ ├── pinctrl_state("default")
│ │ └── settings → pinctrl_setting(多个)
│ └── pinctrl_state("sleep")
└── dt_maps (映射链表)
此时,dev_pin_info 中的:
arduino
default_state → 指向 states 中名为 "default" 的状态
sleep_state → 指向 states 中名为 "sleep" 的状态
3.状态切换
当驱动需要切换引脚状态时(如进入休眠),会调用:
scss
pinctrl_select_state(pinctrl, sleep_state);
执行过程:
- 遍历该状态下所有的
pinctrl_setting - 判断 setting 类型并调用相应 ops:
go
switch (setting->type) {
case PIN_MAP_TYPE_MUX_GROUP:
pinmux_enable_setting(setting);
// 实际调用 -> ops->set_mux()
break;
case PIN_MAP_TYPE_CONFIGS_GROUP:
case PIN_MAP_TYPE_CONFIGS_PIN:
pinconf_apply_setting(setting);
// 实际调用 -> ops->pin_config_group_set()
break;
}
最终由 pin controller 驱动的底层函数(imx_pmx_ops、imx_pinconf_ops)将配置写入寄存器,完成硬件层设置。
4.调试
在 /sys/kernel/debug/pinctrl/<controller>/ 下可以查看 pinctrl 的内部状态:
| 文件名 | 含义 |
|---|---|
pins |
已注册引脚信息 |
pingroups |
引脚组信息 |
pinmux-pins |
每个引脚的复用状态 |
pinmux-functions |
功能与组的对应 |
pinconf-pins |
引脚配置参数 |
pinconf-config |
可动态修改配置 |
示例:
bash
cat /sys/kernel/debug/pinctrl/20e0000.iomuxc/pingroups
cat /sys/kernel/debug/pinctrl/20e0000.iomuxc/pinmux-functions
5.执行链总结
| 阶段 | 主体 | 操作 | 核心结构 / 函数 |
|---|---|---|---|
| 1. 匹配驱动 | 内核 → pinctrl 驱动 | 匹配 compatible | of_match_table |
| 2. 控制器注册 | pinctrl 驱动 | 注册 pin controller | devm_pinctrl_register |
| 3. 设备树解析 | 内核框架 | 解析 pinctrl-0 节点 | pinctrl_dt_to_map / dt_node_to_map |
| 4. 状态建立 | 内核框架 | 创建 map → setting → state | pinctrl_add_setting |
| 5. 设备绑定 | 内核框架 | 创建 dev_pin_info 与 pinctrl |
pinctrl_bind_pins |
| 6. 状态切换 | 设备驱动 | 应用状态 | pinctrl_select_state |
| 7. 硬件配置 | pinctrl 驱动 | 调用 ops 写寄存器 | set_mux / pin_config_set |
6.总结
pinctrl 驱动:描述并注册硬件引脚能力;
pinctrl 核心:提供统一的数据结构与调用机制;
设备驱动:声明状态,动态切换引脚配置;
dev_pin_info:连接设备与 pinctrl 框架的中枢,管理状态切换。