title: pinctrl与gpio子系统 tags: [Linux驱动, pinctrl, gpio, 嵌入式, IMX6ULL] created: 2026-09-16 updated: 2026-09-17
💡 关联知识: [[03-Linux驱动开发核心/02-设备树语法与实战]] | [[03-Linux驱动开发核心/07-platform总线模型]] | [[STM32学习笔记/02-GPIO寄存器详解与三步进化法]]
在早期的LED驱动实验中,我们直接通过设备树节点的 reg 属性配置GPIO:
/* 早期方式:直接配置寄存器 */
mydevice {
reg = <0x020e0000 0x4000>; /* IOMUXC寄存器基地址 */
/* ... */
};
问题分析:
| 问题 | 说明 |
|---|---|
| 可移植性差 | 不同芯片的寄存器地址完全不同 |
| 维护困难 | 硬编码寄存器值,无法动态管理 |
| 复用冲突 | 无法检测引脚是否被其他设备占用 |
| 电气属性缺失 | 无法配置上下拉、驱动强度等 |
Linux内核提供了两个子系统来统一管理GPIO:
graph TB
subgraph "Linux GPIO管理架构"
A[用户空间] -->|ioctl/read/write| B[设备驱动]
B -->|GPIO API| C[GPIO子系统]
B -->|Pin API| D[Pinctrl子系统]
C -->|操作GPIO| E[GPIO控制器驱动]
D -->|配置引脚| F[IOMUX控制器驱动]
E -->|寄存器操作| G[硬件GPIO控制器]
F -->|寄存器操作| H[硬件IOMUXC]
end
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#e8f5e8
style D fill:#fff3e0
pinctrl(Pin Control)子系统负责:
/* 在iomuxc节点下定义pinctrl配置 */
&iomuxc {
pinctrl-names = "default"; /* 定义状态名称列表 */
pinctrl-0 = <&pinctrl_hog_1>; /* default状态使用的配置组 */
imx6ul-evk {
/* 定义一个pinctrl配置组 */
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_UART1_RTS_B__GPIO1_IO19 为例:
/* 定义在 imx6ull-pinfunc.h 中 */
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
宏的5个组成部分:
| 序号 | 字段 | 值 | 含义 |
|---|---|---|---|
| 1 | mux_reg | 0x0090 | IOMUXC_SW_MUX_CTL_PAD寄存器偏移地址 |
| 2 | conf_reg | 0x031C | IOMUXC_SW_PAD_CTL_PAD寄存器偏移地址 |
| 3 | input_reg | 0x0000 | 输入选择寄存器偏移(0表示无) |
| 4 | mux_mode | 0x5 | 复用模式(5=GPIO功能) |
| 5 | input_val | 0x0 | 输入选择值 |
寄存器地址计算:
基地址 = 0x020E0000 (IOMUXC基地址)
mux_reg地址 = 0x020E0000 + 0x0090 = 0x020E0090
→ 对应寄存器: IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B
conf_reg地址 = 0x020E0000 + 0x031C = 0x020E031C
→ 对应寄存器: IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B
0x17059 是pad配置寄存器的值,每个bit的含义:
/* IOMUXC_SW_PAD_CTL_PAD寄存器位定义 */
bit 0: SRE - 压摆率控制 (0: 低速, 1: 高速)
bit 1-2: DSE - 驱动强度 (00: 输出电阻最大, 11: 输出电阻最小)
bit 3-4: SPEED - 速度等级 (00: 50MHz, 01: 100MHz, 10: 100MHz, 11: 200MHz)
bit 5: ODE - 开漏使能 (0: 关闭, 1: 使能)
bit 6: PKE - 上下拉使能 (0: 关闭, 1: 使能)
bit 7: PUE - 上下拉选择 (0: 下拉, 1: 上拉)
bit 8-9: LVS - 电压等级
bit 10: HYS - 滞后使能 (0: 关闭, 1: 使能)
0x17059的二进制分解:
0x17059 = 0001 0111 0000 0101 1001
bit[0] = 1 → SRE=1 (高速压摆率)
bit[1:2] = 00 → DSE=00 (输出电阻最大)
bit[3:4] = 10 → SPEED=10 (100MHz)
bit[5] = 0 → ODE=0 (推挽输出)
bit[6] = 1 → PKE=1 (上下拉使能)
bit[7] = 0 → PUE=0 (下拉)
bit[10] = 1 → HYS=1 (滞后使能)
/* LED设备节点 */
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-gpioled";
/* 1. 关联Pinctrl配置 */
pinctrl-names = "default"; /* 状态名称 */
pinctrl-0 = <&pinctrl_led>; /* 引用pinctrl配置组 */
/* 2. 指定GPIO属性 */
/* 格式: <&控制器 引脚号 标志位> */
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
属性详解:
| 属性 | 含义 | 示例 |
|---|---|---|
| pinctrl-names | 定义状态名称列表 | "default", "sleep" |
| pinctrl-0 | 对应"default"状态的配置 | <&pinctrl_led> |
| pinctrl-1 | 对应第二个状态的配置 | <&pinctrl_sleep> |
| led-gpio | GPIO属性,供驱动读取 | <&gpio1 3 GPIO_ACTIVE_LOW> |
GPIO标志位:
GPIO_ACTIVE_LOW:低电平有效(输出0时LED亮)GPIO_ACTIVE_HIGH:高电平有效(输出1时LED亮)#include <linux/pinctrl/consumer.h>
/* 获取设备的pinctrl句柄 */
struct pinctrl *devm_pinctrl_get(struct device *dev);
/* 释放pinctrl句柄(通常使用devm版本自动释放) */
void pinctrl_put(struct pinctrl *pctl);
/* 根据名字获取特定的状态 */
struct pinctrl_state *pinctrl_lookup_state(
struct pinctrl *pctl,
const char *name);
/* 应用某个状态 */
int pinctrl_select_state(
struct pinctrl *pctl,
struct pinctrl_state *state);
使用示例:
static int my_driver_probe(struct platform_device *pdev)
{
struct pinctrl *pinctrl;
struct pinctrl_state *state_default;
int ret;
/* 1. 获取pinctrl句柄 */
pinctrl = devm_pinctrl_get(&pdev->dev);
if (IS_ERR(pinctrl)) {
dev_err(&pdev->dev, "Failed to get pinctrl\n");
return PTR_ERR(pinctrl);
}
/* 2. 获取default状态 */
state_default = pinctrl_lookup_state(pinctrl, "default");
if (IS_ERR(state_default)) {
dev_err(&pdev->dev, "Failed to lookup default state\n");
return PTR_ERR(state_default);
}
/* 3. 应用default状态 */
ret = pinctrl_select_state(pinctrl, state_default);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to select default state\n");
return ret;
}
return 0;
}
pinctrl支持为设备定义多种引脚状态,适用于不同功耗场景:
/* 多状态pinctrl配置 */
usdhc1 {
/* 定义三种状态名称 */
pinctrl-names = "default", "state_100mhz", "state_200mhz";
/* 为每种状态指定具体的引脚配置 */
pinctrl-0 = <&pinctrl_usdhc1>; /* 对应"default" */
pinctrl-1 = <&pinctrl_usdhc1_100mhz>; /* 对应"state_100mhz" */
pinctrl-2 = <&pinctrl_usdhc1_200mhz>; /* 对应"state_200mhz" */
status = "okay";
};
状态切换流程:
sequenceDiagram
participant D as 设备驱动
participant P as pinctrl子系统
participant M as IOMUX控制器
D->>P: devm_pinctrl_get()
P-->>D: 返回pinctrl句柄
D->>P: pinctrl_lookup_state("state_100mhz")
P-->>D: 返回state句柄
D->>P: pinctrl_select_state(state)
P->>M: 写入新的引脚配置
M-->>P: 配置完成
P-->>D: 返回成功
gpio子系统负责:
/* GPIO属性的标准格式 */
mydevice {
/* 单个GPIO */
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
/* 多个GPIO */
reset-gpios = <&gpio1 2 GPIO_ACTIVE_LOW>,
<&gpio2 3 GPIO_ACTIVE_LOW>;
/* 带enable/disable的GPIO */
power-gpios = <&gpio3 4 GPIO_ACTIVE_HIGH>;
};
GPIO属性解析:
| 字段 | 含义 | 示例 |
|---|---|---|
| &gpio1 | GPIO控制器引用 | 使用GPIO1控制器 |
| 3 | GPIO引脚号 | 第3号引脚(GPIO1_IO03) |
| GPIO_ACTIVE_LOW | 有效电平 | 低电平有效 |
#include <linux/gpio/consumer.h>
/* 获取GPIO(设备树方式) */
struct gpio_desc *devm_gpiod_get(
struct device *dev,
const char *con_id,
enum gpiod_flags flags);
/* 获取GPIO(旧方式,已废弃) */
int gpio_request(unsigned gpio, const char *label);
void gpio_free(unsigned gpio);
/* 设置GPIO方向 */
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
/* 读取GPIO电平 */
int gpio_get_value(unsigned gpio);
/* 设置GPIO电平 */
void gpio_set_value(unsigned gpio, int value);
/* 获取GPIO编号(从设备树) */
int of_get_named_gpio(struct device_node *np,
const char *propname,
int index);
static int led_driver_probe(struct platform_device *pdev)
{
struct device_node *nd;
int led_gpio;
int ret;
/* 1. 获取设备节点 */
nd = of_find_node_by_path("/gpioled");
if (nd == NULL) {
return -EINVAL;
}
/* 2. 获取GPIO编号 */
led_gpio = of_get_named_gpio(nd, "led-gpio", 0);
if (led_gpio < 0) {
printk("Can't get led-gpio\n");
return -EINVAL;
}
printk("LED GPIO Num = %d\n", led_gpio);
/* 3. 申请GPIO */
ret = gpio_request(led_gpio, "LED_PIN");
if (ret < 0) {
printk("GPIO Request failed\n");
return -EINVAL;
}
/* 4. 设置为输出,默认高电平 */
ret = gpio_direction_output(led_gpio, 1);
if (ret < 0) {
printk("GPIO Direction Set failed\n");
return -EINVAL;
}
/* 5. 控制电平 */
gpio_set_value(led_gpio, 0); // 输出低电平,点亮LED
return 0;
}
static int led_driver_probe(struct platform_device *pdev)
{
struct gpio_desc *led_gpio;
/* 使用devm_gpiod_get获取GPIO描述符 */
led_gpio = devm_gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW);
if (IS_ERR(led_gpio)) {
dev_err(&pdev->dev, "Failed to get led gpio\n");
return PTR_ERR(led_gpio);
}
/* 直接使用gpiod接口操作 */
gpiod_set_value(led_gpio, 1); // 点亮LED
return 0;
}
| 特性 | 旧API (gpio_*) | 新API (gpiod_*) |
|---|---|---|
| 资源管理 | 手动gpio_request/free | devm自动管理 |
| 设备树集成 | 需手动解析 | 直接支持 |
| 错误处理 | 简单返回码 | ERR_PTR机制 |
| 多GPIO支持 | 不支持 | 支持GPIO数组 |
| 状态管理 | 无 | 支持active-low等 |
| 推荐程度 | 已废弃 | 推荐使用 |
/* imx6ull pinctrl驱动入口 */
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{
struct imx_pinctrl_soc_info *pinctrl_info;
/* 获取匹配的私有数据 */
pinctrl_info = (struct imx_pinctrl_soc_info *) match->data;
/* 调用通用探测函数 */
return imx_pinctrl_probe(pdev, pinctrl_info);
}
/* 平台驱动结构体 */
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,
};
/* 解析pinctrl groups */
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;
/* 读取fsl,pins属性 */
list = of_property_read_var_array(np, "fsl,pins", &size);
/* 解析每个pin的配置 */
for (i = 0; i < size; i += pin_size) {
/* 提取mux_reg, conf_reg, input_reg, mux_mode, input_val */
grp->pins[i] = be32_to_cpu(list[i]); /* mux_reg */
grp->pins[i+1] = be32_to_cpu(list[i+1]); /* conf_reg */
grp->pins[i+2] = be32_to_cpu(list[i+2]); /* input_reg */
grp->pins[i+3] = be32_to_cpu(list[i+3]); /* mux_mode */
grp->pins[i+4] = be32_to_cpu(list[i+4]); /* input_val */
/* 读取config值(电气属性) */
config = be32_to_cpu(list[i+5]);
grp->configs[i/6] = config;
}
return 0;
}
/* 注册pinctrl控制器 */
int pinctrl_register(struct pinctrl_desc *pctldesc,
struct device *dev,
struct pinctrl_handle *handle)
{
struct pinctrl_dev *pctldev;
/* 分配pinctrl_dev结构体 */
pctldev = kzalloc(sizeof(*pctldev), GFP_KERNEL);
/* 初始化 */
pctldev->desc = pctldesc;
pctldev->dev = dev;
/* 添加到全局链表 */
list_add_tail(&pctldev->node, &pinctrldev_list);
/* 创建sysfs属性 */
ret = device_register(&pctldev->dev);
return 0;
}
# 查看所有GPIO状态
cat /sys/kernel/debug/gpio
# 输出示例:
# gpiochip0: GPIOs 0-31, parent: platform/20a0000.gpio, gpio0:
# gpio-0 ( |led-green ) out hi
# gpio-3 ( |reset ) out lo
# 查看特定GPIO的方向和电平
cat /sys/class/gpio/gpio3/direction
cat /sys/class/gpio/gpio3/value
# 查看设备的pinctrl状态
cat /sys/kernel/debug/pinctrl/*/pins
# 查看pinctrl控制器信息
cat /sys/kernel/debug/pinctrl/pinctrl-maps
# 编译设备树
dtc -I dts -O dtb -o mydevice.dtb mydevice.dts
# 反编译查看
dtc -I dtb -O dts -o mydevice.dts mydevice.dtb
# 检查节点是否存在
ls /proc/device-tree/gpioled/
| 错误 | 原因 | 解决方案 |
|---|---|---|
gpio_request: already requested |
GPIO被其他驱动占用 | 检查设备树,注释掉冲突配置 |
pinctrl_lookup_state: failed |
状态名不存在 | 检查pinctrl-names属性 |
of_get_named_gpio: failed |
属性名错误或节点不存在 | 检查设备树节点路径和属性名 |
| GPIO输出电平不对 | active-low配置错误 | 检查GPIO_ACTIVE_LOW标志 |
# 方法1:搜索设备树中所有使用该引脚的节点
grep -r "GPIO1_IO03" /proc/device-tree/
# 方法2:查看iomuxc节点
cat /proc/device-tree/soc/aips-bus@02000000/iomuxc@020e0000/pinctrl_hog_1/fsl,pins
# 方法3:使用devmem2直接读取寄存器
devmem2 0x020E0090 w
/* 在驱动中添加调试信息 */
static int my_driver_probe(struct platform_device *pdev)
{
struct device_node *nd = pdev->dev.of_node;
int gpio_num;
/* 打印设备节点路径 */
dev_info(&pdev->dev, "Device node: %pOF\n", nd);
/* 打印GPIO编号 */
gpio_num = of_get_named_gpio(nd, "led-gpio", 0);
dev_info(&pdev->dev, "GPIO number: %d\n", gpio_num);
/* 打印GPIO控制器信息 */
struct gpio_chip *chip = gpio_to_chip(gpio_num);
dev_info(&pdev->dev, "GPIO chip: %s\n", chip->label);
return 0;
}
答案:
| 方面 | pinctrl子系统 | gpio子系统 |
|---|---|---|
| 职责 | 配置引脚复用和电气属性 | 控制GPIO电平和方向 |
| 操作对象 | IOMUXC寄存器 | GPIO数据寄存器 |
| 使用场景 | 初始化时配置引脚功能 | 运行时控制IO状态 |
| API特点 | 状态管理,支持多状态 | 直接读写,简单直接 |
GPIO_ACTIVE_LOW 的作用是什么?答案:
驱动代码:gpio_set_value(led_gpio, 1) 实际输出低电平
/* 驱动代码 */
gpiod_set_value(led_gpio, 1); // 期望点亮LED
/* 内核处理 */
if (gpiod_is_active_low(led_gpio))
value = !value; // 自动取反
/* 实际硬件输出 */
gpio_set_value(gpio_num, 0); // 输出低电平,LED亮
答案:
/sys/kernel/debug/gpio 确认占用情况答案:
| 特性 | 旧API | 新API |
|---|---|---|
| 资源管理 | 手动request/free | devm自动管理 |
| 错误处理 | 简单返回码 | ERR_PTR机制 |
| 设备树集成 | 需手动解析 | 直接支持 |
| 代码简洁性 | 较繁琐 | 更简洁 |
推荐原因:
#gpio-cells属性是什么意思?答案:
#gpio-cells属性定义了引用GPIO时需要的参数数量。对于I.MX6ULL:
#gpio-cells = <2> 表示需要2个参数3表示GPIO1_IO03)第二个参数:标志位(GPIO_ACTIVE_LOW或GPIO_ACTIVE_HIGH)
/* GPIO控制器定义 */
gpio1: gpio@0209c000 {
compatible = "fsl,imx6ul-gpio";
reg = <0x0209c000 0x4000>;
gpio-controller;
#gpio-cells = <2>; /* 需要2个参数 */
interrupt-controller;
#interrupt-cells = <2>;
};
/* 使用GPIO */
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
/* ^ ^ ^
* | | └── 第二个cell:极性标志
* | └────── 第一个cell:引脚编号
* └────────── GPIO控制器引用
*/
| 特性 | IMX6ULL | STM32 | RK3568 |
|---|---|---|---|
| IOMUX控制器 | IOMUXC | AFIO+GPIO | PMU+GPIO |
| 配置方式 | 6个32位值 | 4位MODER+AFR | 32位寄存器组 |
| pinctrl支持 | 完善 | 部分支持 | 完善 |
| GPIO控制器 | GPIO1-GPIO5 | GPIOA-GPIOI | GPIO0-GPIO4 |
| 每组GPIO数 | 32个 | 16个 | 32个 |
| 设备树配置 | fsl,pins | st,pins | rockchip,pins |
/* IMX6ULL */
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
pinctrl-0 = <&pinctrl_led>;
/* STM32 */
led-gpio = <&gpioa 5 GPIO_ACTIVE_HIGH>;
// 无独立pinctrl,通过GPIO寄存器配置
/* RK3568 */
led-gpio = <&gpio0 12 GPIO_ACTIVE_LOW>;
pinctrl-0 = <&led_pin>;
drivers/pinctrl/imx/pinctrl-imx6ul.c - IMX6ULL pinctrl驱动drivers/gpio/gpio-mxc.c - IMX6ULL GPIO驱动include/dt-bindings/gpio/gpio.h - GPIO标志定义Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txtDocumentation/devicetree/bindings/gpio/gpio-mxs.txt最后更新: 2026-09-17 | 版本: v2.0