目录
[pinctrl 子系统](#pinctrl 子系统)
[PIN 配置信息详解](#PIN 配置信息详解)
[电气属性值 0x17059](#电气属性值 0x17059)
[PIN 驱动程序讲解](#PIN 驱动程序讲解)
[PIN 配置](#PIN 配置)
[设备树中添加 pinctrl 节点模板](#设备树中添加 pinctrl 节点模板)
[添加 PIN 配置信息](#添加 PIN 配置信息)
前言
在上一讲内容里,设备树下的 LED 驱动实验,我们配置 LED 灯所使用的 GPIO 寄存器,驱动开发方式和裸机基本没啥区别。
Linux 内核提供了 pinctrl 和 gpio 子系统用于GPIO 驱动,借助 pinctrl 和 gpio 子系统来简化 GPIO 驱动开发。本讲实验,我们主要学习pinctrl子系统的配置信息、理解驱动代码、学会添加pinctrl节点模板。
以控制一个 LED为例:

pinctrl 子系统
Linux 驱动讲究驱动分离与分层, pinctrl 和 gpio 子系统就是驱动分离与分层思想下的产物。
-
分离:将硬件相关的代码(如寄存器操作)与业务逻辑(如设备功能)解耦。
-
分层:通过子系统抽象硬件共性,提供统一接口,避免重复造轮子。
pinctrl简介
pinctrl 子系统:引脚复用与电气属性管理
功能
-
控制引脚的 复用功能(如 GPIO、UART、I2C 等)
-
配置引脚的 电气属性(上下拉、驱动强度、斜率等)
对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成。
pinctrl 子系统源码目录为 drivers/pinctrl。
接下来我们学习以下I.MX6ULL 的 pinctrl 子系统驱动。
PIN 配置信息详解
要使用 pinctrl 子系统,一般会在设备树里面创建一个节点来描述 PIN 的配置信息。
打开 imx6ull.dtsi 文件,找到一个叫做 iomuxc 的节点,如下所示:
cpp
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc"; // 兼容性字符串,匹配i.MX6ULL的IOMUXC驱动
reg = <0x020e0000 0x4000>; // 寄存器基地址0x020e0000,地址空间长度16KB
};
iomuxc 节点就是 I.MX6ULL 的 IOMUXC 外设对应的节点。
再打开 imx6ull-alientek-emmc.dts,找到**iomuxc 节点,**如下所示内容:
cpp
&iomuxc {
pinctrl-names = "default"; // 默认引脚状态名称
pinctrl-0 = <&pinctrl_hog_1>; // 默认使用的引脚配置组
/* 开发板特定引脚配置 */
imx6ul-evk {
/* GPIO默认配置组 */
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
/* GPIO1_IO19配置:复用为GPIO,电气属性0x17059 */
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
/* GPIO1_IO05配置:复用为USDHC1_VSELECT */
MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
/* GPIO1_IO09配置:复用为GPIO */
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
/* GPIO1_IO00配置:复用为OTG ID检测,电气属性0x13058 */
MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
>;
};
......
/* FlexCAN1控制器引脚配置 */
pinctrl_flexcan1: flexcan1grp {
fsl,pins = <
/* CAN_RX信号:复用为FLEXCAN1_RX,电气属性0x1b020 */
MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020
/* CAN_TX信号:复用为FLEXCAN1_TX,电气属性0x1b020 */
MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020
>;
};
......
/* 看门狗引脚配置 */
pinctrl_wdog: wdoggrp {
fsl,pins = <
/* 看门狗信号:复用为WDOG1_WDOG_ANY,电气属性0x30b0 */
MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY 0x30b0
>;
};
};
};
这段代码向 iomuxc 节点追加数据,不同的外设使用的 PIN 不同、其配置也不同,将某个外设所使用的所有 PIN 都组织在一个子节点里面。
以GPIO1_IO19为例:
cpp
/* GPIO1_IO19配置:复用为GPIO,电气属性0x17059 */
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
cpp
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
引脚标识
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19
- 前半部分:物理引脚名称,MX6UL_PAD_UART1_RTS_B物理引脚名称,对应芯片手册中的 UART1_RTS_B 引脚
- 后半部分:配置该引脚,复用功能(GPIO1_IO19)

寄存器值
宏定义后面跟着一串数字:
cpp
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
对应参数顺序:
<mux_reg,conf_reg,input_reg,mux_mode,input_val>
参数 | 值 | 说明 |
---|---|---|
mux_reg |
0x0090 |
复用功能寄存器偏移地址 (相对于IOMUXC基地址0x020E0000 ) |
conf_reg |
0x031C |
电气属性寄存器偏移地址(相对于IOMUXC基地址) |
input_reg |
0x0000 |
输入寄存器偏移地址(此引脚无输入功能,故为0) |
mux_mode |
0x5 |
复用模式值(ALT5模式对应GPIO1_IO19功能) |
input_val |
0x0 |
输入功能选择值(未使用) |
以mux_reg来看寄存器偏移,如图:

其它寄存器的具体细节,可以参考芯片手册来分析。
电气属性值 0x17059
cpp
/* GPIO1_IO19配置:复用为GPIO,电气属性0x17059 */
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
i.MX6UL 的引脚控制寄存器(IOMUXC_SW_PAD_CTL_PAD_*)按以下位域定义:
位域 | 名称 | 值 | 功能说明 |
---|---|---|---|
16:13 | HYS | 0x1 | 输入滞后使能(抗抖动) |
12 | PUS | 0x7 | 上拉/下拉选择(0=无, 1=下拉, 2=上拉) |
11:10 | PUE | 0x0 | 上下拉使能(0=关闭, 3=保持器模式) |
9:6 | PKE | 0x1 | 输入保持器使能 |
5:3 | ODE | 0x0 | 开漏输出禁用(推挽模式) |
2:1 | SPEED | 0x2 | 驱动强度(00=低, 11=高) |
0 | SRE | 0x1 | 压摆率(0=慢, 1=快) |
0x17059的二进制展开:0001_0111_0000_0101_1001
对应配置:
- HYS=1:启用输入滞后
- PUS=7 (0111):47KΩ 上拉
- PUE=0:关闭上下拉(PUS 优先)
- PKE=1:启用输入保持器
- ODE=0:推挽输出
- SPEED=2:中速驱动
- SRE=1:快速压摆率
PIN 驱动程序讲解
PIN 配置
在文件drivers/pinctrl/freescale/pinctrl-imx6ul.c中,有如下内容:
cpp
/**
* imx6ul_pinctrl_of_match - 设备树兼容性匹配表
*
* 用于匹配设备树中的iomuxc节点与对应的驱动数据
*/
static struct of_device_id imx6ul_pinctrl_of_match[] = {
{
.compatible = "fsl,imx6ul-iomuxc", // 标准IOMUX控制器
.data = &imx6ul_pinctrl_info, // 关联i.MX6UL的引脚控制信息
},
{
.compatible = "fsl,imx6ull-iomuxc-snvs", // SNVS域IOMUX控制器
.data = &imx6ull_snvs_pinctrl_info, // 关联i.MX6ULL SNVS引脚信息
},
{ /* sentinel */ } // 结束标记
};
/**
* imx6ul_pinctrl_probe - 驱动探测函数
* @pdev: 平台设备结构体指针
*
* 1. 匹配设备树节点
* 2. 获取SoC特定引脚信息
* 3. 调用通用pinctrl初始化
*/
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
struct imx_pinctrl_soc_info *pinctrl_info;
// 通过设备树匹配表查找对应设备
match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);
if (!match)
return -ENODEV; // 未找到匹配项
// 获取匹配项中存储的SoC特定数据
pinctrl_info = (struct imx_pinctrl_soc_info *)match->data;
// 调用i.MX系列通用pinctrl初始化函数
return imx_pinctrl_probe(pdev, pinctrl_info);
}
/**
* imx6ul_pinctrl_driver - pinctrl平台驱动结构
*
* 注册驱动到内核平台驱动框架
*/
static struct platform_driver imx6ul_pinctrl_driver = {
.driver = {
.name = "imx6ul-pinctrl", // 驱动名称
.owner = THIS_MODULE, // 模块所有者
.of_match_table = of_match_ptr(imx6ul_pinctrl_of_match), // 设备树匹配表
},
.probe = imx6ul_pinctrl_probe, // 设备探测回调
.remove = imx_pinctrl_remove, // 设备移除回调
};
让我们分析这段代码:
of_device_id
of_device_id结构体数组,保存着这个驱动文件的兼容性值,设备树中的 compatible 属性值会和 of_device_id 中的所有兼容性字符串比较,查看是否可以使用此驱动。
cpp
static struct of_device_id imx6ul_pinctrl_of_match[] = {
{
.compatible = "fsl,imx6ul-iomuxc", // 标准IOMUX控制器
.data = &imx6ul_pinctrl_info, // 关联i.MX6UL的引脚控制信息
},
{
.compatible = "fsl,imx6ull-iomuxc-snvs", // SNVS域IOMUX控制器
.data = &imx6ull_snvs_pinctrl_info, // 关联i.MX6ULL SNVS引脚信息
},
{ /* sentinel */ } // 结束标记
};
imx6ul_pinctrl_of_match 结构体数组一共有两个兼容性字符串, 分别为"fsl,imx6ul-iomuxc"和"fsl,imx6ull-iomuxc-snvs",因此 iomuxc 节点与此驱动匹配,所以 pinctrl-imx6ul.c 会完成 I.MX6ULL 的 PIN 配置工作。
驱动探测函数
imx6ul_pinctrl_probe 函数就是 I.MX6ULL 这个 SOC 的 PIN 配置入口函数:
cpp
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
struct imx_pinctrl_soc_info *pinctrl_info;
// 通过设备树匹配表查找对应设备
match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);
if (!match)
return -ENODEV; // 未找到匹配项
// 获取匹配项中存储的SoC特定数据
pinctrl_info = (struct imx_pinctrl_soc_info *)match->data;
// 调用i.MX系列通用pinctrl初始化函数
return imx_pinctrl_probe(pdev, pinctrl_info);
}

imx6ul_pinctrl_probe 函数调用路径如下:

注册驱动
platform_driver结构体 是平台设备驱动,有个 probe 成员变量。当设备和驱动匹配成功以后, platform_driver 的 probe 成员变量所代表的函数就会执行。
cpp
static struct platform_driver imx6ul_pinctrl_driver = {
.driver = {
.name = "imx6ul-pinctrl", // 驱动名称
.owner = THIS_MODULE, // 模块所有者
.of_match_table = of_match_ptr(imx6ul_pinctrl_of_match), // 设备树匹配表
},
.probe = imx6ul_pinctrl_probe, // 设备探测回调
.remove = imx_pinctrl_remove, // 设备移除回调
};
获取PIN配置
函数 imx_pinctrl_parse_groups 负责获取设备树中关于 PIN 的配置信息:
cpp
/*
* fsl,pins 属性中每个引脚由5个u32(PIN_FUNC_ID)和1个u32(CONFIG)组成,
* 因此每个引脚总共占用24字节。
*/
#define FSL_PIN_SIZE 24 // 标准引脚描述符的字节大小
#define SHARE_FSL_PIN_SIZE 20 // 共享引脚描述符的字节大小(无input_reg时)
/**
* imx_pinctrl_parse_groups - 解析设备树中的引脚组配置
* @np: 设备树节点指针
* @grp: 存储解析结果的引脚组结构
* @info: SoC特定的引脚控制器信息
* @index: 引脚组索引
*
* 从设备树的fsl,pins属性中提取引脚复用和配置信息,
* 填充到imx_pin_group结构中。
*/
static int imx_pinctrl_parse_groups(struct device_node *np,
struct imx_pin_group *grp,
struct imx_pinctrl_soc_info *info,
u32 index)
{
int size, pin_size;
const __be32 *list; // 指向设备树属性数据的指针
int i;
u32 config;
......
/* 遍历组内所有引脚 */
for (i = 0; i < grp->npins; i++) {
u32 mux_reg = be32_to_cpu(*list++); // 复用寄存器地址
u32 conf_reg; // 配置寄存器地址
unsigned int pin_id; // 计算得到的引脚ID
struct imx_pin_reg *pin_reg; // 引脚寄存器信息指针
struct imx_pin *pin = &grp->pins[i]; // 当前引脚结构
......
/* 计算引脚ID(根据复用或配置寄存器地址) */
pin_id = (mux_reg != -1) ? mux_reg / 4 : conf_reg / 4;
/* 存储寄存器映射信息 */
pin_reg = &info->pin_regs[pin_id];
pin->pin = pin_id; // 记录引脚ID
grp->pin_ids[i] = pin_id; // 存储到组ID数组
pin_reg->mux_reg = mux_reg; // 复用寄存器地址
pin_reg->conf_reg = conf_reg; // 配置寄存器地址
/* 提取引脚功能配置 */
pin->input_reg = be32_to_cpu(*list++); // 输入选择寄存器
pin->mux_mode = be32_to_cpu(*list++); // 复用模式(ALTx)
pin->input_val = be32_to_cpu(*list++); // 输入功能值
......
/* 处理SION位(特殊功能位,位于复用寄存器中) */
config = be32_to_cpu(*list++); // 原始配置值
if (config & IMX_PAD_SION)
pin->mux_mode |= IOMUXC_CONFIG_SION; // 设置SION标志位
pin->config = config & ~IMX_PAD_SION; // 存储清理后的配置值
}
return 0;
}
设备树中的 mux_reg 和 conf_reg 值会保存在 info 参数中, input_reg、mux_mode、 input_val 和 config 值会保存在 grp 参数中。
cpp
static int imx_pinctrl_parse_groups(struct device_node *np,
struct imx_pin_group *grp,
struct imx_pinctrl_soc_info *info,
u32 index)
获取 mux_reg、 conf_reg、 input_reg、 mux_mode 和 input_val 值。
cpp
pin_reg->mux_reg = mux_reg; // 复用寄存器地址
pin_reg->conf_reg = conf_reg; // 配置寄存器地址
/* 提取引脚功能配置 */
pin->input_reg = be32_to_cpu(*list++); // 输入选择寄存器
pin->mux_mode = be32_to_cpu(*list++); // 复用模式(ALTx)
pin->input_val = be32_to_cpu(*list++); // 输入功能值
获取 config 值。
cpp
pin->config = config & ~IMX_PAD_SION; // 存储清理后的配置值

注册PIN控制器
函数 pinctrl_register,用于向 Linux 内核注册一个 PIN 控制器,原型如下:
cpp
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc, // 描述引脚控制器的结构
struct device *dev, // 关联的硬件设备
void *driver_data // 驱动私有数据
);
参数 | 类型 | 说明 |
---|---|---|
pctldesc |
struct pinctrl_desc* |
描述引脚控制器的结构体,包含操作函数集、引脚范围等关键信息 |
dev |
struct device* |
关联的硬件设备(通常为platform_device ) |
driver_data |
void* |
驱动私有数据,可通过pinctrl_dev_get_drvdata() 获取 |
参数 pctldesc ,就是要注册的 PIN 控制器, PIN 控制器用于配置 SOC的 PIN 复用功能和电气特性。参数 pctldesc 是 pinctrl_desc 结构体类型指针。
pinctrl_desc结构体如下所示:
cpp
/*
* pinctrl_desc - 引脚控制器描述符
* 该结构体描述一个引脚控制器及其功能,用于向pinctrl子系统注册引脚控制器
*/
struct pinctrl_desc {
const char *name; /* 引脚控制器设备名称 */
struct pinctrl_pin_desc const *pins; /* 描述每个引脚的引脚描述符数组 */
unsigned int npins; /* pins数组中的引脚数量 */
const struct pinctrl_ops *pctlops; /* 引脚控制操作(引脚控制核心操作) */
const struct pinmux_ops *pmxops; /* 引脚复用操作(多路复用功能配置) */
const struct pinconf_ops *confops; /* 引脚配置操作(电气特性配置) */
struct module *owner; /* 该引脚控制器的模块所有者 */
#ifdef CONFIG_GENERIC_PINCONF
unsigned int num_custom_params; /* 自定义配置参数的数量 */
const struct pinconf_generic_params *custom_params; /* 自定义配置参数数组 */
const struct pin_config_item *custom_conf_items; /* 自定义参数的配置项 */
#endif
};
其中,有三个重要的结构体指针:
cpp
const struct pinctrl_ops *pctlops; /* 引脚控制操作(引脚控制核心操作) */
const struct pinmux_ops *pmxops; /* 引脚复用操作(多路复用功能配置) */
const struct pinconf_ops *confops; /* 引脚配置操作(电气特性配置) */
这三个结构体就是 PIN 控制器的"工具",这三个结构体里面包含了很多操作函数,通过这些操作函数就可以完成对某一个PIN 的配置。
pinctrl_desc 结构体需要由用户提供,结构体里面的成员变量也是用户提供的。但是这个用户并不是我们这些使用芯片的程序员,而是半导体厂商,半导体厂商发布的 Linux 内核源码中已经把这些工作做完了。
比如在 imx_pinctrl_probe 函数中,有以下代码:
cpp
int imx_pinctrl_probe(struct platform_device *pdev,
struct imx_pinctrl_soc_info *info)
{
struct device_node *dev_np = pdev->dev.of_node; // 获取设备树节点
struct device_node *np; // 临时设备树节点指针
struct imx_pinctrl *ipctl; // i.MX引脚控制器数据结构
struct resource *res; // 资源指针
struct pinctrl_desc *imx_pinctrl_desc; // 引脚控制器描述符
......
// 为引脚控制器描述符分配内存
imx_pinctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*imx_pinctrl_desc),
GFP_KERNEL);
if (!imx_pinctrl_desc)
return -ENOMEM; // 内存分配失败返回错误
......
// 初始化引脚控制器描述符
imx_pinctrl_desc->name = dev_name(&pdev->dev); // 设置控制器名称
imx_pinctrl_desc->pins = info->pins; // 设置引脚描述数组
imx_pinctrl_desc->npins = info->npins; // 设置引脚数量
imx_pinctrl_desc->pctlops = &imx_pctrl_ops; // 设置控制操作函数集
imx_pinctrl_desc->pmxops = &imx_pmx_ops; // 设置复用操作函数集
imx_pinctrl_desc->confops = &imx_pinconf_ops; // 设置配置操作函数集
imx_pinctrl_desc->owner = THIS_MODULE; // 设置所属模块
......
// 注册引脚控制器
ipctl->pctl = pinctrl_register(imx_pinctrl_desc, &pdev->dev, ipctl);
// ...其他代码...
}
- 定义结构体指针变量 imx_pinctrl_desc。
- 向指针变量 imx_pinctrl_desc 分配内存。
- 初始化 imx_pinctrl_desc 结构体指针变量,重点是 pctlops、 pmxops 和 confops这三个成员变量,分别对应 imx_pctrl_ops、 imx_pmx_ops 和 imx_pinconf_ops 这三个结构体。
- 调用函数 pinctrl_register 向 Linux 内核注册 imx_pinctrl_desc,注册以后 Linux 内核就有了对 I.MX6ULL 的 PIN 进行配置的工具。
imx_pctrl_ops、 imx_pmx_ops 和 imx_pinconf_ops 这三个结构体定义如下:
cpp
/* i.MX 引脚控制操作集合 */
static const struct pinctrl_ops imx_pctrl_ops = {
.get_groups_count = imx_get_groups_count, /* 获取引脚组数量 */
.get_group_name = imx_get_group_name, /* 获取引脚组名称 */
.get_group_pins = imx_get_group_pins, /* 获取引脚组中的引脚列表 */
.pin_dbg_show = imx_pin_dbg_show, /* 调试接口:显示引脚信息 */
.dt_node_to_map = imx_dt_node_to_map, /* 从设备树节点生成引脚映射 */
.dt_free_map = imx_dt_free_map, /* 释放引脚映射资源 */
};
......
/* i.MX 引脚复用操作集合 */
static const struct pinmux_ops imx_pmx_ops = {
.get_functions_count = imx_pmx_get_funcs_count, /* 获取复用功能数量 */
.get_function_name = imx_pmx_get_func_name, /* 获取复用功能名称 */
.get_function_groups = imx_pmx_get_groups, /* 获取支持某功能的引脚组 */
.set_mux = imx_pmx_set, /* 设置引脚复用功能 */
.gpio_request_enable = imx_pmx_gpio_request_enable, /* GPIO请求使能 */
.gpio_set_direction = imx_pmx_gpio_set_direction, /* 设置GPIO方向 */
};
......
/* i.MX 引脚配置操作集合 */
static const struct pinconf_ops imx_pinconf_ops = {
.pin_config_get = imx_pinconf_get, /* 获取引脚配置参数 */
.pin_config_set = imx_pinconf_set, /* 设置引脚配置参数 */
.pin_config_dbg_show = imx_pinconf_dbg_show, /* 调试接口:显示引脚配置 */
.pin_config_group_dbg_show = imx_pinconf_group_dbg_show, /* 调试接口:显示引脚组配置 */
};
......
这三个结构体下的所有函数就是 I.MX6ULL 的 PIN 配置函数。
设备树中添加 pinctrl 节点模板
我们学习一下如何在设备树中添加某个外设的PIN信息 。
关 于 I.MX 系 列 SOC 的 pinctrl 设 备 树 绑 定 信 息 可 以 参 考 文 档Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt。
这里我们虚拟一个名为"test"的设备, test 使用了GPIO1_IO00这个 PIN的GPIO 功能,
pinctrl 节点添加过程如下:
创建对应的节点
同一个外设的 PIN 都放到一个节点里面,打开 imx6ull-alientek-emmc.dts,在 iomuxc 节点中的"imx6ul-evk"子节点下添加"pinctrl_test"节点。
添加完成以后如下所示:
cpp
pinctrl_test: testgrp {
/* 具体的 PIN 信息 */
};
添加"fsl,pins"属性
设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为"fsl,pins"。
因为对于 I.MX 系列 SOC 而言, pinctrl 驱动程序是通过读取"fsl,pins"属性值来获取 PIN 的配置信息。
完成以后如下所示:
cpp
pinctrl_test: testgrp {
fsl,pins = <
/* 设备所使用的 PIN 配置信息 */
>;
};
添加 PIN 配置信息
最后在"fsl,pins"属性中添加具体的 PIN 配置信息,完成以后如下所示:
cpp
pinctrl_test: testgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值*/
>;
};
至此,我们已经在 imx6ull-alientek-emmc.dts 文件中,添加好了 test 设备所使用的 PIN 配置信息。