Linux pinctrl 子系统

Linux pinctrl 子系统

一、Pinctrl的使用

大多数 SOC 的 pin 都是支持复用的,例如某个SOC的GPIO3_D4 既可以作为普通的 GPIO 使用,也可以作为 PWM1_M0 引脚、GPU_AVS 引脚、UART0_RX 引脚。此外我们还需要配置 pin 的电气特性,比如上/下拉、驱动能力等等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。pinctrl 子系统就是为了解决这个问题而引入的,pinctrl 子系统主要工作内容如下:

  • 获取设备树中 pin 信息。
  • 根据获取到的 pin 信息来设置 pin 的复用功能
  • 根据获取到的 pin 信息来设置 pin 的电气特性,如驱动能力。

对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成,pinctrl 子系统源码目录为 drivers/pinctrl。

1.PIN 配置信息详解

要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信息。

C 复制代码
//imx6ull.dtsi
iomuxc: iomuxc@020e0000 {
				compatible = "fsl,imx6ul-iomuxc";
				reg = <0x020e0000 0x4000>;
			};
//imx6ull-14x14-evk.dts
&iomuxc {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_hog_1>; //hog pin是系统默认的状态,在 pinctrl 初始化阶段立即生效
	pinctrl_hog_1: hoggrp-1 {
		fsl,pins = <
			MX6UL_PAD_UART1_RTS_B__GPIO1_IO19	0x17059 /* SD1 CD */
			MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT	0x17059 /* SD1 VSELECT */
			MX6UL_PAD_GPIO1_IO09__GPIO1_IO09        0x17059 /* SD1 RESET */
		>;
	};

	pinctrl_csi1: csi1grp {
		fsl,pins = <
			MX6UL_PAD_CSI_MCLK__CSI_MCLK		0x1b088
			MX6UL_PAD_CSI_PIXCLK__CSI_PIXCLK	0x1b088
			...
		>;
	};
    ...
}
  • iomuxc 是 IOMUX 控制器,负责 pin 的复用和 pad 控制
  • compatible 决定使用哪个 pinctrl 驱动(drivers/pinctrl/freescale/
  • reg 指向 IOMUXC 寄存器基地址

所有 pin 的复用和电气配置,最终都会通过该控制器完成。

这里要说明一下,PAD、Pin、GPIO是三个东西:

  • PAD SoC 内部 负责复用、电气特性、信号缓冲

  • Pin / Ball 芯片封装 焊球 / 引脚编号

  • GPIO 逻辑功能 数字输入输出控制器

PAD 是 pinctrl / IOMUX 直接操作的对象,多个内部信号共享同一个 PAD,通过复用选择其一。

通常在 &iomuxc 节点下定义多个 pinctrl 子节点,每个子节点描述一组功能相关的 pin,例如上面的pinctrl_hog_1pinctrl_csi1

c 复制代码
pinctrl_xxx: xxxgrp {
	fsl,pins = <
		PAD_MUX_CONFIG  PAD_CTRL_CONFIG
		...
	>;
};

pinctrl_xxx:label,用于被其他设备节点引用

xxxgrp:节点名,无实际功能意义,仅用于区分

fsl,pins:平台私有属性,描述具体 pin 配置

fsl,pins 属性格式说明

该属性由 若干组 <PAD MUX + PAD CTRL> 组成,每一组代表一个 pin 的配置。举例:

C 复制代码
fsl,pins = <
	MX6UL_PAD_UART1_RTS_B__GPIO1_IO19   0x17059
>;

PAD MUX :宏MX6UL_PAD_UART1_RTS_B__GPIO1_IO19包含三部分信息,MX6UL_PAD_UART1_RTS_B表示物理 PAD 名称,__表示分隔符,GPIO1_IO19表示复用后的功能,表示将 UART1_RTS_B 这个 PAD 复用为 GPIO1_IO19 功能。这些宏定义位于arch/arm/boot/dts/imx6ull-pinfunc.h

PAD CTRL0x17059用于配置 pin 的电气特性,对应 IOMUXC_PAD 控制寄存器。常见控制项包括:上拉 / 下拉、驱动能力、速率、施密特触发、推挽开漏。

2.设备节点中引用 pinctrl

pinctrl 配置并不会自动生效,必须被设备节点引用。

基本引用方式

c 复制代码
uart1: serial@02020000 {
	compatible = "fsl,imx6ul-uart";
	reg = <0x02020000 0x4000>;
	interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;

	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_uart1>; //引用pinctrl_uart1

	status = "okay";
};
//对应的pinctrl定义
pinctrl_uart1: uart1grp {
	fsl,pins = <
		MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX  0x1b0b1
		MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX  0x1b0b1
	>;
};

多状态 pinctrl

ini 复制代码
pinctrl-names = "default", "sleep";
pinctrl-0 = <&pinctrl_uart1_default>;
pinctrl-1 = <&pinctrl_uart1_sleep>;

这里有两个状态:

default:工作状态

sleep:低功耗状态(关闭上下拉、降低驱动)

是否切换由驱动调用:pinctrl_select_state();

二、内部原理

核心思路 (方便回忆) pinctrl 是 Linux 内核中负责管理引脚复用与引脚配置的子系统。

它通过Pin Controller 驱动和 设备驱动两层模型,实现了从设备树引脚定义到硬件寄存器配置的完整映射。

简单流程版:

  • pinctrl 子系统的工作流程从 Pin Controller 驱动开始。首先,内核通过设备树中的 compatible 属性匹配到对应的 pinctrl 驱动,当匹配成功后,会调用该驱动的 probe 函数。在 probe 阶段,驱动会根据 SoC 的特性填充一个 pinctrl_desc 结构体,其中包含引脚描述信息(pins/npins)、操作函数(pctlops、pmxops、confops)等内容,并通过 devm_pinctrl_register() 将该 pin controller 注册到内核中,完成引脚控制器的初始化与功能注册。
  • 当具体使用pinctrl的设备驱动在 probe 阶段加载时,内核会根据设备树中定义的引脚配置调用 pinctrl_bind_pins() 函数,为该设备建立引脚控制上下文。该函数首先为设备创建一个 dev_pin_info 结构体,用于保存设备的引脚控制信息。其中,函数会调用 devm_pinctrl_get() 创建一个新的 pinctrl 结构体,并将其赋值给 dev_pin_infop 成员。
  • 随后,内核通过 pinctrl_dt_to_map() 从设备树中读取该设备的引脚配置,并调用 pinctrl 驱动提供的 imx_dt_node_to_map() 等函数来解析 pinctrl-0pinctrl-1 等节点。解析过程会生成一系列 pinctrl_map 结构体,每个 map 对应一组引脚功能或配置。这些 map 会被进一步转换为 pinctrl_setting ,并挂入对应状态的 pinctrl_state.settings 链表中,同时所有 map 信息也会保存在 pinctrldt_maps 链表中。
  • 在此基础上,内核根据不同的 pinctrl 节点(如 default、sleep 等)创建多个 pinctrl_state ,每个状态对应设备树中定义的一个引脚状态。这些状态被添加到 pinctrlstates 链表中,而 dev_pin_info 结构中的 default_statesleep_state 等指针则会被设置为指向这些状态结构。
  • 当设备运行过程中需要切换引脚配置(例如进入睡眠或恢复默认状态)时,驱动可调用 pinctrl_select_state() 函数来切换到对应的状态。该函数会遍历当前状态下的所有 pinctrl_setting,并根据每个 setting 的类型(复用或配置)调用 pin controller 驱动提供的操作接口(ops),最终通过底层寄存器配置完成硬件引脚功能与电气属性的切换。

这一整套机制实现了从设备树定义到硬件引脚配置的完整自动化映射流程。


1.Provider 端

Pin Controller 驱动注册流程

(1)设备树匹配

在设备树中定义一个 pin controller 节点:

ini 复制代码
iomuxc: iomuxc@020e0000 {
    compatible = "fsl,imx6ul-iomuxc";
    reg = <0x020e0000 0x4000>;
};

对应驱动中的匹配表:

arduino 复制代码
static const struct of_device_id imx6ul_pinctrl_of_match[] = {
    { .compatible = "fsl,imx6ul-iomuxc", .data = &imx6ul_pinctrl_info },
    { /* sentinel */ }
};

当 compatible 匹配成功后,调用:

arduino 复制代码
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{
    return imx_pinctrl_probe(pdev, pinctrl_info);
}

(2)probe 阶段

构造并注册 pinctrl_desc

核心函数 imx_pinctrl_probe()

ini 复制代码
int imx_pinctrl_probe(struct platform_device *pdev,
                      struct imx_pinctrl_soc_info *info)
{
    struct pinctrl_desc *desc;

    desc->name = dev_name(&pdev->dev);
    desc->pins = info->pins;           // 引脚数组
    desc->npins = info->npins;         // 引脚数量
    desc->pctlops = &imx_pctrl_ops;    // 分组操作
    desc->pmxops = &imx_pmx_ops;       // 复用操作
    desc->confops = &imx_pinconf_ops;  // 引脚配置操作
    desc->owner = THIS_MODULE;

    // 注册 pin controller
    ipctl->pctl = devm_pinctrl_register(&pdev->dev, desc, ipctl);
}

devm_pinctrl_register() 会在内核中注册一个 pinctrl_dev 实例:

arduino 复制代码
struct pinctrl_dev {
    struct pinctrl_desc *desc;
    struct device *dev;
    void *driver_data;
};

(3)三大操作函数

pinctrl 框架通过以下三类 ops 完成抽象:

操作集 功能 示例函数
pinctrl_ops 引脚组管理与设备树解析 dt_node_to_map()
pinmux_ops 控制引脚复用 set_mux()
pinconf_ops 控制引脚电气属性 pin_config_set()

示例:

ini 复制代码
static const struct pinctrl_ops imx_pctrl_ops = {
    .get_groups_count = imx_get_groups_count,
    .get_group_name = imx_get_group_name,
    .dt_node_to_map = imx_dt_node_to_map, // 关键:设备树转 pinctrl_map
};

2.Consumer 端

设备驱动的引脚绑定与状态创建

(1)设备树中定义引脚配置
ini 复制代码
&iomuxc {
    imx6ul-evk {
        pinctrl_i2c1: i2c1grp {
            fsl,pins = <
                MX6UL_PAD_UART1_TX_DATA__I2C1_SCL 0x4001b8b0
                MX6UL_PAD_UART1_RX_DATA__I2C1_SDA 0x4001b8b0
            >;
        };
    };
};

i2c1: i2c@021a0000 {
    compatible = "fsl,imx6ul-i2c";
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&pinctrl_i2c1>;
    pinctrl-1 = <&pinctrl_i2c1_sleep>;
};

pinctrl-namespinctrl-0, pinctrl-1 等属性定义了设备的多种引脚状态。


(2)pinctrl_bind_pins()

设备 probe 时的引脚绑定入口:pinctrl_bind_pins()

当设备驱动被加载时,really_probe() 调用:

scss 复制代码
pinctrl_bind_pins(dev);

该函数负责为设备创建引脚控制信息,是 Consumer 端的核心入口。


(3)pinctrl_bind_pins

pinctrl_bind_pins 内部关键步骤

step1.创建 dev_pin_info 结构

ini 复制代码
dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);

struct dev_pin_info 用于描述设备与 pinctrl 子系统的绑定关系:

arduino 复制代码
struct dev_pin_info {
    struct pinctrl *p;                    // 指向该设备的 pinctrl 句柄
    struct pinctrl_state *default_state;  // 默认状态
    struct pinctrl_state *init_state;     // 初始化状态
    struct pinctrl_state *sleep_state;    // 睡眠状态
    struct pinctrl_state *idle_state;     // 空闲状态
};

dev_pin_info 是设备和 pinctrl 框架之间的"桥梁":

  • p:指向设备对应的 struct pinctrl
  • 其他成员指向不同命名状态的 pinctrl_state

step2.创建 pinctrl 结构

ini 复制代码
dev->pins->p = devm_pinctrl_get(dev);

devm_pinctrl_get() 内部执行:

  1. 创建 struct pinctrl
  2. 调用 pinctrl_dt_to_map() 解析设备树;
  3. 将解析结果保存到 pinctrl->dt_maps
  4. 为每个状态创建 pinctrl_state 并加入 pinctrl->states 链表。

step3.设备树解析 → pinctrl_map 构建

pinctrl_dt_to_map() 通过 pinctrl 驱动提供的 dt_node_to_map() 来解析:

scss 复制代码
imx_dt_node_to_map(np_config, &map, &num_maps);

每个节点(如 pinctrl-0)会生成若干个 pinctrl_map

arduino 复制代码
struct pinctrl_map {
    const char *dev_name;
    const char *name;
    enum pinctrl_map_type type;
    union {
        struct { const char *group; const char *function; } mux;
        struct { const char *group_or_pin; unsigned long *configs; unsigned num_configs; } configs;
    } data;
};

这些 pinctrl_map 描述了设备、组、功能与配置之间的对应关系,并被挂入:

rust 复制代码
pinctrl->dt_maps

step4.pinctrl_mappinctrl_settingpinctrl_state

每个 pinctrl_map 会被转换为 pinctrl_setting,挂入对应的 pinctrl_state.settings 链表。

结构关系如下:

scss 复制代码
dev_pin_info
   └── pinctrl
        ├── states (链表)
        │     ├── pinctrl_state("default")
        │     │       └── settings → pinctrl_setting(多个)
        │     └── pinctrl_state("sleep")
        └── dt_maps (映射链表)

此时,dev_pin_info 中的:

arduino 复制代码
default_state → 指向 states 中名为 "default" 的状态
sleep_state   → 指向 states 中名为 "sleep" 的状态

3.状态切换

当驱动需要切换引脚状态时(如进入休眠),会调用:

scss 复制代码
pinctrl_select_state(pinctrl, sleep_state);

执行过程:

  1. 遍历该状态下所有的 pinctrl_setting
  2. 判断 setting 类型并调用相应 ops:
go 复制代码
switch (setting->type) {
case PIN_MAP_TYPE_MUX_GROUP:
    pinmux_enable_setting(setting);
    // 实际调用 -> ops->set_mux()
    break;

case PIN_MAP_TYPE_CONFIGS_GROUP:
case PIN_MAP_TYPE_CONFIGS_PIN:
    pinconf_apply_setting(setting);
    // 实际调用 -> ops->pin_config_group_set()
    break;
}

最终由 pin controller 驱动的底层函数(imx_pmx_opsimx_pinconf_ops)将配置写入寄存器,完成硬件层设置。


4.调试

/sys/kernel/debug/pinctrl/<controller>/ 下可以查看 pinctrl 的内部状态:

文件名 含义
pins 已注册引脚信息
pingroups 引脚组信息
pinmux-pins 每个引脚的复用状态
pinmux-functions 功能与组的对应
pinconf-pins 引脚配置参数
pinconf-config 可动态修改配置

示例:

bash 复制代码
cat /sys/kernel/debug/pinctrl/20e0000.iomuxc/pingroups
cat /sys/kernel/debug/pinctrl/20e0000.iomuxc/pinmux-functions

5.执行链总结

阶段 主体 操作 核心结构 / 函数
1. 匹配驱动 内核 → pinctrl 驱动 匹配 compatible of_match_table
2. 控制器注册 pinctrl 驱动 注册 pin controller devm_pinctrl_register
3. 设备树解析 内核框架 解析 pinctrl-0 节点 pinctrl_dt_to_map / dt_node_to_map
4. 状态建立 内核框架 创建 map → setting → state pinctrl_add_setting
5. 设备绑定 内核框架 创建 dev_pin_infopinctrl pinctrl_bind_pins
6. 状态切换 设备驱动 应用状态 pinctrl_select_state
7. 硬件配置 pinctrl 驱动 调用 ops 写寄存器 set_mux / pin_config_set

6.总结

pinctrl 驱动:描述并注册硬件引脚能力;

pinctrl 核心:提供统一的数据结构与调用机制;

设备驱动:声明状态,动态切换引脚配置;

dev_pin_info:连接设备与 pinctrl 框架的中枢,管理状态切换。

相关推荐
lvbinemail2 小时前
添加zabbix-agentd.service
linux·运维·服务器·zabbix·监控
天骄t2 小时前
CS与BS模型对比:协议、功能、资源全解析
linux
代码游侠2 小时前
应用——UDP Socket 编程笔记
linux·运维·网络·笔记·网络协议·学习·udp
是阿威啊2 小时前
【第六站】测试本地项目连接虚拟机上的大数据集群
大数据·linux·hive·hadoop·spark·yarn
知识分享小能手2 小时前
Ubuntu入门学习教程,从入门到精通, Ubuntu 22.04 文件和目录管理完全指南(7)
linux·学习·ubuntu
Mr-Wanter2 小时前
麒麟V10x86 系统 curl报错SSLv3符号缺失问题解决
linux·服务器·github
VekiSon2 小时前
Linux系统编程——网络:TCP 协议与通信实战
linux·网络·tcp/ip
苦逼IT运维2 小时前
VMware Horizon 与 Docker 冲突排错记录
linux·运维·docker·容器·自动化
阿拉伯柠檬3 小时前
应用层协议HTTP
linux·网络·c++·网络协议·http