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

🎬 渡水无言个人主页渡水无言

专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《freertos专栏

⭐️流水不争先,争的是滔滔不绝

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

| 省级优秀毕业生获得者 | csdn新星杯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.MX6ULLpinctrl****子系统驱动

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 个核心参数,如下表所示:

参数值 参数名 底层含义 实际地址 / 作用
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_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 子系统的原理及应用。

相关推荐
济6171 小时前
FreeRTOS基础知识---为什么使用FreeRTOS以及其核心功能
嵌入式·freertos
云飞云共享云桌面2 小时前
10人SolidWorks设计团队如何提升SolidWorks软件利用率
大数据·linux·运维·服务器·网络·人工智能
czxyvX5 小时前
019-Linux-Socket编程-TCP
linux·tcp/ip
A.A呐10 小时前
【Linux第六章】进程状态和优先级
linux
iambooo11 小时前
Shell在日志分析与故障排查中的实战应用
linux·服务器·网络
一路往蓝-Anbo11 小时前
第 9 章:Linux 设备树 (DTS) ——屏蔽与独占外设
linux·运维·服务器·人工智能·stm32·嵌入式硬件
钛态12 小时前
Flutter for OpenHarmony:dio_cookie_manager 让 Dio 发挥会话管理能力,像浏览器一样自动处理 Cookie 深度解析与鸿蒙适配指南
android·linux·运维·flutter·ui·华为·harmonyos
王码码203512 小时前
Flutter for OpenHarmony:Flutter 三方库 bluez 玩转 Linux 风格的蓝牙操作(蓝牙底层互操作)
linux·运维·服务器·前端·flutter·云原生·harmonyos
A.A呐12 小时前
【Linux第七章】进程切换和命令行参数
linux