ARM Linux 驱动开发篇--- pinctrl 子系统详解-- Ubuntu20.04

ARM Linux 驱动开发篇--- pinctrl 子系统详解-- Ubuntu20.04
🎬 渡水无言个人主页渡水无言

专栏传送门: 《linux专栏》   《嵌入式linux驱动开发》《freertos专栏
⭐️流水不争先,争的是滔滔不绝

 📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生

| 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生

在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

文章目录


前言

在上一期的实战中,我们完成了基于设备树的 LED 驱动开发,但从底层逻辑来看,核心依旧是直接配置 LED 对应的 GPIO 寄存器 —— 这种开发方式,本质上和裸机驱动开发并无二致。

Linux 作为一套成熟、庞大的操作系统,其驱动框架的设计核心就是复用与简化。对于 GPIO 这类最基础、最常用的外设驱动,内核绝不会让开发者一直沿用 “直接操作寄存器” 的 “原始” 方式。这就好比你买了一辆性能完备的汽车,却每天靠推着车去上班,完全浪费了系统本身的 “动力总成”。

为了彻底摆脱寄存器级别的繁琐操作,实现 GPIO 驱动的标准化、模块化开发,Linux 内核专门提供了pinctrl 子系统gpio 子系统。前者负责 GPIO 的引脚复用、电气属性配置,后者则封装了 GPIO 的输入输出、高低电平控制等核心逻辑。接下来,我们就将正式学习如何借助这两大子系统,高效、规范地完成 GPIO 驱动开发。本期博客主要介绍pinctrl 子系统。


一、pinctrl 子系统简介

在上一期的 LED 驱动实战中,我们通过设备树 + 寄存器操作完成了 GPIO 初始化,核心步骤可归纳为三步:

  1. 设备树配置:在设备树中添加节点,通过reg属性定义 GPIO 相关寄存器的物理地址;
  2. PIN 属性配置:读取reg属性,映射并初始化IOMUXC_SW_MUX(复用)和IOMUXC_SW_PAD(电气属性)寄存器,完成引脚复用、上下拉、速度等配置;
  3. 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 子系统的核心目标,就是接管所有引脚的初始化工作,让驱动开发者无需再直接操作寄存器。其核心工作流程可概括为三步:

  1. 解析设备树:从设备树节点中读取引脚的相关配置信息;
  2. 配置复用功能:根据设备树信息,设置引脚的复用模式(如 GPIO、I2C、SPI 等);
  3. 配置电气属性:根据设备树信息,设置引脚的上下拉、速度、驱动能力等参数。

对于开发者而言,使用 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 >; }; /* 其他外设引脚组配置... */ }; };

核心配置逻辑解析

  1. 节点追加语法&iomuxc表示对.dtsi中同名节点进行追加,而非重新定义,完美实现 “通用 + 定制” 的配置分离;
  2. 默认引脚组指定pinctrl-names = "default"定义状态名称。                                    pinctrl-0 = <&pinctrl_hog_1>指定默认状态下启用的引脚组;
  3. 外设引脚组化:采用 “一个外设一个子节点” 的设计,将同一外设的所有引脚组织在一个子节点中(如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 个核心参数,如下表所示:

参数值参数名底层含义实际地址 / 作用
0x0090mux_reg复用寄存器偏移地址IOMUXC 基地址 (0x020e0000) + 0x0090 = 0x020e0090(对应寄存器IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B
0x031Cconf_reg电气属性寄存器偏移地址0x020e0000 + 0x031C = 0x020e031C(对应寄存器IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B
0x0000input_reg输入选择寄存器偏移地址UART1_RTS_B 复用为 GPIO1_IO19 时无此寄存器,值无效
0x5mux_mode复用模式值写入IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B寄存器,将 PIN 复用为 GPIO1_IO19(参考 IMX6ULL 手册,该寄存器的 bit0~3 配置复用模式,0x5 对应 GPIO1_IO19)
0x0input_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_regconf_reginput_regmux_modeinput_valconfig):

// 核心逻辑:逐个解析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 子系统的原理及应用。

Read more

【优选算法 | 位运算】位运算基础:深入理解二进制操作

【优选算法 | 位运算】位运算基础:深入理解二进制操作

算法相关知识点可以通过点击以下链接进行学习一起加油!双指针滑动窗口二分查找前缀和 在本篇文章中,我们将全面解析位运算的基本原理与实际应用。位运算通过直接操作数字的二进制表示,能够在许多计算中提供极大的效率提升。无论是用于加速数学运算、优化算法,还是解决特定的技术问题,位运算都扮演着至关重要的角色。 🌈个人主页:是店小二呀 🌈C/C++专栏:C语言\ C++ 🌈初/高阶数据结构专栏: 初阶数据结构\ 高阶数据结构 🌈Linux专栏: Linux 🌈算法专栏:算法 🌈Mysql专栏:Mysql 🌈你可知:无人扶我青云志 我自踏雪至山巅 文章目录 * 一、常见位运算总结 * 二、入门题目 * 面试题 01.01. 判定字符是否唯一 * 268.丢失的数字 * 371.两整数之和 * 137.只出现一次的数字 II * 面试题 17.19. 消失的两个数字

By Ne0inhk
斐波那契数列模型:在动态规划的丝绸之路上追寻斐波那契的足迹(上)

斐波那契数列模型:在动态规划的丝绸之路上追寻斐波那契的足迹(上)

文章目录 * 引言 * 递归与动态规划的对比 * 递归解法的初探 * 动态规划的优雅与高效 * 自顶向下的记忆化搜索 * 自底向上的迭代法 * 性能分析与比较 * 小结 引言 斐波那契数列,这一数列如同一条无形的丝线,穿越千年时光,悄然延续其魅力。其定义简单而优美: F(0)=0,F(1)=1 F(n)=F(n−1)+F(n−2), n>1 这看似简单的递归公式,却蕴含着深刻的数学结构,成为计算机科学中的经典问题之一。斐波那契数列不仅仅出现在数学课本上,它在自然界、计算机算法、金融模型等领域中无处不在。对于程序员而言,斐波那契数列不仅是一个练习递归的好题目,更是一个优化算法的标杆。 在这篇文章中,我们将通过动态规划的技术来探讨如何高效地求解斐波那契数列,从而避免传统递归方法中低效的冗余计算。我们将以 C 语言为例,展示动态规划方法如何一步步揭开这一问题的面纱。 递归与动态规划的对比

By Ne0inhk
2026 前端 / 后端 / 算法岗 AI 技能清单,直接对标大厂

2026 前端 / 后端 / 算法岗 AI 技能清单,直接对标大厂

2026 大厂前端岗 AI 技能清单 核心基础技能 * 大模型前端适配能力:掌握大模型上下文管理,实现对话历史的高效存储与加载,适配流式输出的前端渲染逻辑。 * AI 组件开发:熟练开发基于大模型的智能组件,如代码补全、智能问答、内容生成类组件,支持参数化配置与多模型切换。 * 向量数据库集成:掌握 Pinecone、Weaviate 等向量数据库的前端调用方法,实现语义搜索、相似内容推荐等功能。 进阶实践技能 * 大模型微调适配:理解大模型微调原理,能够基于前端业务场景,将微调后的模型部署至前端环境,实现模型轻量化调用。 * 多模态交互开发:支持文本、图像、音频等多模态输入的前端处理,对接多模态大模型 API 实现智能交互。 * AI 性能优化:实现大模型请求的批量处理、缓存复用与增量更新,降低前端请求延迟与资源消耗。 实战代码示例 以下为基于 OpenAI API 实现的流式对话前端组件,使用 React 18 开发:

By Ne0inhk
《算法闯关指南:优选算法--前缀和》--29.和为k的子数组,30.和可被k整除的子数组

《算法闯关指南:优选算法--前缀和》--29.和为k的子数组,30.和可被k整除的子数组

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 29. 和为k的子数组 * 解法(前缀和+哈希表): * 算法思路: * C++算法代码: * 算法总结&&笔记展示: * 30. 和可被k整除的子数组 * 解法(前缀和+哈希表): * 前置知识补充: * 算法思路: * C++算法代码: * 算法总结&&笔记展示: * 结尾: 前言: 聚焦算法题实战,系统讲解三大核心板块:优选算法:剖析动态规划、二分法等高效策略,学会寻找“最优解”。 递归与回溯:掌握问题分解与状态回退,攻克组合、

By Ne0inhk