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

Ubuntu新手必看:如何快速更换国内源(阿里/清华/中科大源对比)

Ubuntu 新手的第一道“加速”关:国内镜像源深度解析与实战指南 刚装好 Ubuntu,那种清爽的桌面和开箱即用的感觉确实不错。但当你兴冲冲地打开终端,准备用 apt install 装点东西时,进度条那慢如蜗牛的爬行速度,是不是瞬间浇灭了一半的热情?别急着怀疑自己的网络,这几乎是每个国内 Ubuntu 用户都会遇到的“新手墙”。问题的核心,往往不在于你的宽带,而在于系统默认连接的软件仓库服务器远在海外,网络延迟和带宽限制成了最大的瓶颈。 解决这个问题的方法,就是“换源”——将系统的软件源地址,更换为位于国内的镜像服务器。这听起来像是个简单的操作,但背后其实有不少门道:国内有哪些可靠的镜像站?阿里云、清华大学、中国科学技术大学(USTC)的源有什么区别?为什么别人的源换上去飞快,你的却报了一堆错?今天,我们就来彻底拆解这个问题。这不仅仅是复制粘贴几行命令,而是帮你理解原理、掌握选择、并能在遇到问题时自己动手排查。无论你是刚接触 Linux 的开发新手,还是希望优化工作流效率的资深用户,一个配置得当的软件源,

By Ne0inhk
玩转ClaudeCode:ClaudeCode安装教程(Windows+Linux+MacOS)

玩转ClaudeCode:ClaudeCode安装教程(Windows+Linux+MacOS)

本文介绍如何安装 AI 编码界一骑绝尘的最强工具 ——— Claude Code。安装不同的操作系统环境,本文会从 Windows、Linux、Mac 三个不同的系统环境依次介绍安装方法。 其中,Windows 系统作为大家最主流的操作系统,提供了两种安装方式,一种方式是直接在 Windows 的终端里安装,另一种是在 Windows 的子系统(WSL)内完成安装。其中,通过 WSL 安装,我们又可以分为,WSL 环境的直装和基于 WSL 的容器化安装(Docker),几种方法各有利弊,但均可正常使用。 Windows 环境直装 Claude Code 1. 获取 Claude Code 账号 访问 Claude Code 中国镜像站,完成账户注册。 输入邀请码

By Ne0inhk
Flutter 三方库 swagger_parser 自动化打通鸿蒙 API 通信(一键将 Swagger 转化为 Dart 模型)

Flutter 三方库 swagger_parser 自动化打通鸿蒙 API 通信(一键将 Swagger 转化为 Dart 模型)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在进行 OpenHarmony 项目开发时,最枯燥的工作莫过于根据后端提供的 Swagger (OpenAPI) 文档手动编写一个个的 Request 类、Response 类和 API Client。这不仅低效,而且极易因文档更新没对齐而导致 Bug。 swagger_parser 是一个强大的命令行工具,它能直接读取本地或网络上的 Swagger JSON/YAML 文件,自动为你生成完整的 Dart 数据类和 Dio/Chopper API 控制器。 一、核心工作流 Swagger JSON / YAML Swagger Parser Dart 数据模型 (JSON Serialized) Dio / Chopper

By Ne0inhk
0x80070035找不到网络路径怎么办?win10/win11的6种有效解决方法

0x80070035找不到网络路径怎么办?win10/win11的6种有效解决方法

在win10或win11电脑访问局域网共享文件、连接其他设备时,不少人会遇到 “0x80070035 找不到网络路径” 报错,导致无法读取共享数据、协作受阻。据某技术社区2025年用户反馈统计,该错误在局域网场景占比超65%。下面我们将深入分析这一问题,并提供多种经过验证的解决方案。 一、0x80070035错误是什么 0x80070035是Windows系统访问网络资源时常见的错误代码,意味着系统无法定位到指定的网络路。当用户尝试访问局域网中的另一台计算机的共享文件夹或打印机时,系统会尝试通过网络路径(如\计算机名\共享名)建立连接,而这一过程因各种原因可能中断。这一错误在Windows 7、Windows 10和Windows 11系统中均有出现,成因多样但解决方案相似。 二、为什么会出现0x80070035错误? 0x80070035错误的产生涉及多个层面的因素,主要可归纳为以下四类: 1️⃣网络配置问题:网络发现功能未启用是最常见的原因之一。同一局域网内的计算机若想相互访问,必须确保所有设备开启了网络发现和文件共享功能。 2️⃣服务运行异常:多个Windows服

By Ne0inhk