
🎬 渡水无言 :个人主页渡水无言
❄专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《freertos专栏》
⭐️流水不争先,争的是滔滔不绝
📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生
| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生
在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

文章目录
前言
在上一期的实战中,我们完成了基于设备树的 LED 驱动开发,但从底层逻辑来看,核心依旧是直接配置 LED 对应的 GPIO 寄存器 ------ 这种开发方式,本质上和裸机驱动开发并无二致。
Linux 作为一套成熟、庞大的操作系统,其驱动框架的设计核心就是复用与简化。对于 GPIO 这类最基础、最常用的外设驱动,内核绝不会让开发者一直沿用 "直接操作寄存器" 的 "原始" 方式。这就好比你买了一辆性能完备的汽车,却每天靠推着车去上班,完全浪费了系统本身的 "动力总成"。
为了彻底摆脱寄存器级别的繁琐操作,实现 GPIO 驱动的标准化、模块化开发,Linux 内核专门提供了pinctrl 子系统 和gpio 子系统 。前者负责 GPIO 的引脚复用、电气属性配置,后者则封装了 GPIO 的输入输出、高低电平控制等核心逻辑。接下来,我们就将正式学习如何借助这两大子系统,高效、规范地完成 GPIO 驱动开发。本期博客主要介绍pinctrl 子系统。
一、pinctrl****子系统简介
在上一期的 LED 驱动实战中,我们通过设备树 + 寄存器操作完成了 GPIO 初始化,核心步骤可归纳为三步:
-
设备树配置 :在设备树中添加节点,通过
reg属性定义 GPIO 相关寄存器的物理地址; -
PIN 属性配置 :读取
reg属性,映射并初始化IOMUXC_SW_MUX(复用)和IOMUXC_SW_PAD(电气属性)寄存器,完成引脚复用、上下拉、速度等配置; -
GPIO 功能配置 :映射并初始化
GPIO1_DR(数据)、GPIO1_GDIR(方向)寄存器,将引脚设置为 GPIO 功能并配置输入 / 输出模式。
本质上,这一过程和 STM32 等裸机开发完全一致:先配置引脚的复用与电气属性,再配置 GPIO 本身的功能。

如上图所示,在芯片内部通常存在一个管理引脚功能复用的模块IOMUX:
如果我们想让pinA和pinB用于SPI功能,需要设置IOMUX,配置这两个引脚连接到SPI模块。
如果我们想让pinA和pinB用于GPIO功能,需要设置IOMUX,配置这两个引脚连接到GPIO模块。
注意:此处的GPIO模块与SPI、IIC、UART等为并列关系,它与pinctrl子系统的关系是,使用pinctrl子系统将pinA、pinB等复用至GPIO模块,然后才能将pinA、pinB用于GPIO功能。
除了将引脚复用为某种功能外,有时候还需要配置引脚的电气属性,如上拉、下拉、开漏等等。
对于大多数 32 位 SOC 而言,引脚配置都绕不开这两步。但这种直接操作寄存器的方式存在明显弊端 ------ 配置繁琐、易出现引脚功能冲突,且代码复用性极低。
为解决这些问题,Linux 内核专门推出了pinctrl 子系统,将引脚的配置逻辑从驱动代码中抽离,实现标准化管理。
pinctrl 子系统的核心目标,就是接管所有引脚的初始化工作,让驱动开发者无需再直接操作寄存器。其核心工作流程可概括为三步:
-
解析设备树:从设备树节点中读取引脚的相关配置信息;
-
配置复用功能:根据设备树信息,设置引脚的复用模式(如 GPIO、I2C、SPI 等);
-
配置电气属性:根据设备树信息,设置引脚的上下拉、速度、驱动能力等参数。
对于开发者而言,使用 pinctrl 子系统的方式极其简单:只需在设备树中按规范写好引脚的属性配置,剩下的初始化工作会由 pinctrl 子系统自动完成,无需在驱动代码中编写任何寄存器操作逻辑。
pinctrl 子系统的内核源码目录为:drivers/pinctrl。
二、I.MX6ULL的pinctrl****子系统驱动
2.1、PIN****配置信息详解
要使用 Linux 内核的pinctrl子系统完成引脚配置,核心是在设备树中提供精准的 PIN 配置信息。------ 毕竟 pinctrl 子系统需要依据这些信息,自动完成引脚的复用功能和电气属性初始化。
在 IMX6ULL 平台上,所有引脚的配置都围绕iomuxc节点展开,这个节点是 IOMUXC(输入输出多路复用控制器)外设在设备树中的专属映射。接下来我们结合内核源码中的.dtsi和开发板属.dts文件,彻底讲清iomuxc节点的组成逻辑与配置方法。
2.1.1、基础框架:imx6ull.dtsi 中的 iomuxc 根节点
首先打开芯片通用设备树文件imx6ull.dtsi,找到iomuxc节点的基础定义,这是所有引脚配置的 "根容器":
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};
这段代码仅定义了核心基础信息:
compatible = "fsl,imx6ul-iomuxc":内核通过该属性匹配 IMX6ULL 的 pinctrl 驱动,是驱动与硬件对接的关键;
reg = <0x020e0000 0x4000>:指定 IOMUXC 外设的物理基地址(0x020e0000)和寄存器长度(0x4000)。
可以看到,这里并没有任何引脚的具体配置 ------ 这是因为.dtsi文件只存放 SOC 的通用信息,具体开发板的引脚配置,会在专属.dts文件中通过 "节点追加" 的方式补充。
2.1.2、节点追加:imx6ull-alientek-emmc.dts 中的引脚配置
打开我们使用的开发板的专属设备树imx6ull-alientek-emmc.dts,找到对iomuxc节点的扩展配置,这才是 pinctrl 子系统的核心配置部分:
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-evk {
/* 热插拔相关引脚组(如USB OTG ID、SD卡检测) */
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
>;
};
/* CAN1外设引脚组 */
pinctrl_flexcan1: flexcan1grp{
fsl,pins = <
MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020
MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020
>;
};
/* 看门狗外设引脚组 */
pinctrl_wdog: wdoggrp {
fsl,pins = <
MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY 0x30b0
>;
};
/* 其他外设引脚组配置... */
};
};
核心配置逻辑解析
-
节点追加语法 :
&iomuxc表示对.dtsi中同名节点进行追加,而非重新定义,完美实现 "通用 + 定制" 的配置分离; -
默认引脚组指定 :
pinctrl-names = "default"定义状态名称。pinctrl-0 = <&pinctrl_hog_1>指定默认状态下启用的引脚组; -
外设引脚组化 :采用 "一个外设一个子节点 " 的设计,将同一外设的所有引脚组织在一个子节点中(如
pinctrl_flexcan1对应 CAN1,pinctrl_wdog对应看门狗),结构清晰、便于维护。
若我们要为自定义外设(如 LED)添加引脚配置,只需在imx6ul-evk节点下,新建一个专属子节点,将所有相关引脚配置放入其中即可。
2.1.3、完整的 iomuxc 节点结构
结合.dtsi的基础定义和.dts的扩展配置,最终生成的完整iomuxc节点如下,这也是内核实际解析的结构:
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-evk {
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
>;
};
/* 其他外设引脚组... */
};
};
PIN 配置的核心格式:复用 + 电气属性
以pinctrl_hog_1节点中的UART1_RTS_B引脚为例,其配置项为:
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
| 配置部分 | 含义 | 核心作用 |
|---|---|---|
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 |
引脚复用宏 | 定义引脚的功能方向 ------ 将原本的 UART1_RTS_B 引脚,复用为 GPIO1_IO19(用作 SD 卡检测引脚) |
0x17059 |
电气属性值 | 配置引脚的上下拉、速度、驱动能力、摆率等参数 |
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19是一个宏定义,位于内核文件arch/arm/boot/dts/imx6ul-pinfunc.h中(imx6ull.dtsi会间接引用该头文件,实现设备树对 C 语言宏的复用)
完整定义如下:
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
这 5 个十六进制数值,对应 PIN 复用配置的 5 个核心参数,如下表所示:
| 参数值 | 参数名 | 底层含义 | 实际地址 / 作用 |
|---|---|---|---|
| 0x0090 | mux_reg | 复用寄存器偏移地址 | IOMUXC 基地址 (0x020e0000) + 0x0090 = 0x020e0090(对应寄存器IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B) |
| 0x031C | conf_reg | 电气属性寄存器偏移地址 | 0x020e0000 + 0x031C = 0x020e031C(对应寄存器IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B) |
| 0x0000 | input_reg | 输入选择寄存器偏移地址 | UART1_RTS_B 复用为 GPIO1_IO19 时无此寄存器,值无效 |
| 0x5 | mux_mode | 复用模式值 | 写入IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B寄存器,将 PIN 复用为 GPIO1_IO19(参考 IMX6ULL 手册,该寄存器的 bit0~3 配置复用模式,0x5 对应 GPIO1_IO19) |
| 0x0 | input_val | 输入选择寄存器值 | 因 input_reg 无效,此值无意义 |
pinctrl 子系统会自动解析这两部分信息,替代开发者完成底层寄存器的读写操作。
三、IMX6ULL pinctrl 内核驱动核心解析(原理篇)
前文我们已在设备树中完成了 PIN 的配置,而真正将这些配置转化为硬件寄存器操作的,是 Linux 内核中的 pinctrl 驱动。本小节仅梳理核心执行流程,帮助大家理解 "设备树配置如何被内核析"。
3.1 驱动匹配:compatible 属性的关键作用
设备树中iomuxc节点的compatible = "fsl,imx6ul-iomuxc",是驱动与硬件匹配的核心。在 Linux 内核源码drivers/pinctrl/freescale/pinctrl-imx6ul.c中,通过of_device_id结构体数组完成匹配:
static 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, },
{ /* 哨兵节点,结束标记 */ }
};
当设备树与驱动的 compatible 属性匹配成功后,平台设备驱动的probe函数会被触发执行。
3.2 驱动入口:imx6ul_pinctrl_probe 函数
IMX6ULL 的 pinctrl 驱动是典型的平台设备驱动 ,其入口函数为imx6ul_pinctrl_probe:
static int imx6ul_pinctrl_probe(struct platform_device *pdev) {
const struct of_device_id *match;
struct imx_pinctrl_soc_info *pinctrl_info;
// 匹配设备树与驱动的compatible属性
match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);
if (!match)
return -ENODEV;
pinctrl_info = (struct imx_pinctrl_soc_info *) match->data;
// 调用通用的IMX pinctrl探测函数
return imx_pinctrl_probe(pdev, pinctrl_info);
}
该函数的核心作用是完成匹配校验,并将流程移交至 IMX 系列通用的imx_pinctrl_probe函数。
3.3 核心执行流程:从解析设备树到注册控制器
imx6ul_pinctrl_probe触发后,内核会按照以下关键路径完成 PIN 配置的底层实现:
解析设备树配置:imx_pinctrl_parse_groups
此函数是设备树与驱动的 "桥梁",负责解析设备树中fsl,pins属性里的 6 个 u32 类型配置值(mux_reg、conf_reg、input_reg、mux_mode、input_val、config):
// 核心逻辑:逐个解析PIN配置参数并保存
for (i = 0; i < grp->npins; i++) {
pin->mux_reg = be32_to_cpu(*list++);
pin->conf_reg = be32_to_cpu(*list++);
pin->input_reg = be32_to_cpu(*list++);
pin->mux_mode = be32_to_cpu(*list++);
pin->input_val = be32_to_cpu(*list++);
pin->config = be32_to_cpu(*list++);
// 后续处理SION位等细节...
}
解析后的数据会分别保存到imx_pinctrl_soc_info(寄存器地址)和imx_pin_group(配置值)结构体中.
注册 PIN 控制器:pinctrl_register
这是 pinctrl 驱动的最终步骤,通过pinctrl_register向内核注册一个完整的 PIN 控制器。核心是初始化pinctrl_desc结构体,该结构体包含了 PIN 配置的 "核心工具"------ 三组操作函数集:
struct pinctrl_desc imx_pinctrl_desc = {
.name = dev_name(&pdev->dev),
.pins = info->pins,
.npins = info->npins,
.pctlops = &imx_pctrl_ops, // PIN控制器通用操作
.pmxops = &imx_pmx_ops, // PIN复用操作(核心)
.confops = &imx_pinconf_ops, // PIN电气属性配置(核心)
.owner = THIS_MODULE,
};
pinctrl_register(imx_pinctrl_desc, &pdev->dev, ipctl);
其中imx_pmx_ops(复用配置)和imx_pinconf_ops(电气属性配置)包含了直接操作硬件寄存器的函数,我们无需关心具体细节。
四、设备树中添加pinctrl节点模板
理解了 pinctrl 的核心原理后,实操的重点是在设备树中为自定义外设添加 PIN 配置节点。
本节以虚拟的 test 设备 为例,该设备使用GPIO1_IO00的 GPIO 功能,演示完整的 pinctrl 节点添加流程。
步骤 1:打开开发板专属设备树文件
打开正点原子 Emmc 版开发板的设备树imx6ull-alientek-emmc.dts,找到&iomuxc节点下的imx6ul-evk子节点 ------ 所有外设的 pinctrl 子节点都需定义在这里。
步骤 2:创建专属 pinctrl 子节点
为 test 设备创建独立的 pinctrl 子节点,命名遵循pinctrl_<设备名>的规范,标签名建议与节点名对应:
pinctrl_test: testgrp {
fsl,pins = <
/* 此处添加具体的PIN配置项 */
>;
};
步骤 3:添加 fsl,pins 核心属性
对于 IMX 系列 SOC,pinctrl 驱动固定通过fsl,pins属性读取 PIN 配置信息,因此必须添加该属性:
pinctrl_test: testgrp {
fsl,pins = <
/* 此处添加具体的PIN配置项 */
>;
};
步骤 4:写入具体的 PIN 配置信息
在fsl,pins属性中,按复用宏 + 电气属性值的格式,写入GPIO1_IO00的配置信息。其中复用宏选用MX6UL_PAD_GPIO1_IO00__GPIO1_IO00(复用为 GPIO 功能),config为具体的电气属性值(如0x17059,需根据硬件需求设置):
pinctrl_test: testgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 0x17059
>;
};
最终完整配置
添加完成后,&iomuxc节点中的相关部分如下:
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-evk {
// 原有节点...
pinctrl_hog_1: hoggrp-1 { ... };
// 新增test设备的pinctrl节点
pinctrl_test: testgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 0x17059
>;
};
};
};
至此,我们已完成自定义外设 test 的 pinctrl 设备树配置。后续开发 test 设备的驱动时,只需通过设备树绑定该 pinctrl 节点,即可让 pinctrl 子系统自动完成GPIO1_IO00的复用和电气属性初始化,彻底告别手动操作寄存器的繁琐方式。
总结
本期博客主要介绍pinctrl 子系统的原理及应用。