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

Flutter 组件 calendar_time 的适配 鸿蒙Harmony 深度进阶 - 驾驭时间段语义隔离、实现鸿蒙端动态工作日排除与高并发列表动态刷新方案

Flutter 组件 calendar_time 的适配 鸿蒙Harmony 深度进阶 - 驾驭时间段语义隔离、实现鸿蒙端动态工作日排除与高并发列表动态刷新方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 calendar_time 的适配 鸿蒙Harmony 深度进阶 - 驾驭时间段语义隔离、实现鸿蒙端动态工作日排除与高并发列表动态刷新方案 前言 在前文中,我们利用 calendar_time 实现了基础的相对时间(如“刚才”、“昨天”)展示。但在真正的“金融级对账系统”、“政务排班大盘”或“高频社交动态”场景中。简单的相对描述远远不够。面对需要根据“当前业务时间”判定是否属于“法定工作时间”、针对包含上万条消息的列表如何实现高效的“每秒分钟数自增更新”。 如果处理不当,不仅会产生业务逻辑上的“时差错觉”。更会在鸿蒙(OpenHarmony)端引发严重的渲染性能雪崩。 本文将作为 calendar_time 适配的进阶篇。带你深入探讨其在鸿蒙端的逻辑时序对其、复杂区间判别(

By Ne0inhk

WSL needs updatingYour version of Windows Subsystem for Linux (WSL) is too old.如何解决

安装 Docker Desktop 时出现该问题,核心原因是:Docker Desktop 运行依赖 Windows Subsystem for Linux (WSL) 2 提供的轻量级虚拟化环境,而你的系统当前的 WSL 环境不符合运行要求。具体诱因主要有这三点: 1. WSL 功能未安装 / 版本过低:系统未启用 WSL 功能,或仅安装了旧版 WSL 1(Docker Desktop 硬性要求为 WSL 2 版本); 2. WSL 2 内核未更新:即便已安装 WSL 2,其内核组件未升级至最新版本,无法适配 Docker 运行需求; 3. 系统虚拟化功能未开启:Windows 未启用

By Ne0inhk
Flutter 组件 native_shuttle 的适配 鸿蒙Harmony 实战 - 驾驭极致原生通讯性能、实现鸿蒙端 Dart 与 ArkTS 之间的高频底层穿梭方案

Flutter 组件 native_shuttle 的适配 鸿蒙Harmony 实战 - 驾驭极致原生通讯性能、实现鸿蒙端 Dart 与 ArkTS 之间的高频底层穿梭方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 native_shuttle 的适配 鸿蒙Harmony 实战 - 驾驭极致原生通讯性能、实现鸿蒙端 Dart 与 ArkTS 之间的高频底层穿梭方案 前言 在鸿蒙(OpenHarmony)生态的极速动态图形交互、需要频繁调用系统底层多媒体能力以及对跨引擎数据同步有“毫秒级延时门禁”的各类专业级应用开发中,“宿主语言(ArkTS)与业务语言(Dart)之间的交互效率”是决定应用能否在大规模、高并发工况下保持流畅的终极技术壁垒。面对需要每秒传递 60 帧以上的高精度传感器数据流、复杂的 0307 批次资产二进制 Blob 同步或者是在鸿蒙平板与折叠屏之间执行频繁的逻辑状态投影。如果仅仅依靠传统的 MethodChannel 这种基于 JSON 或序列化编码的慢速异步通道。不仅会导致在数据转换(Serialization)过程中产生巨大的 CPU

By Ne0inhk

Flutter 三方库 persistent_cache_simple 的鸿蒙化适配指南 - 实现具备磁盘溢出淘汰与极简 API 的本地持久化缓存、支持端侧资源异步落地与状态秒开实战

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 persistent_cache_simple 的鸿蒙化适配指南 - 实现具备磁盘溢出淘汰与极简 API 的本地持久化缓存、支持端侧资源异步落地与状态秒开实战 前言 在进行 Flutter for OpenHarmony 应用开发时,如何高效、持久地缓存一些网络 JSON、配置片段或临时计算结果?传统的 shared_preferences 在处理大段字符串时性能受限,且缺乏生命周期淘汰机制。persistent_cache_simple 是一款功能专一、基于文件系统的轻量级缓存库。本文将探讨如何在鸿蒙端构建极致、稳健的二级缓存体系。 一、原直观解析 / 概念介绍 1.1 基础原理 该库建立在“键值映射至文件(Key-to-File)”的简易架构之上。它利用鸿蒙应用的沙箱存储目录,将每一个缓存项序列化为独立的文件。

By Ne0inhk