03-pinctrl与gpio子系统.md 21 KB


title: pinctrl与gpio子系统 tags: [Linux驱动, pinctrl, gpio, 嵌入式, IMX6ULL] created: 2026-09-16 updated: 2026-09-17

pdf_ref: "第四十五章 pinctrl和gpio子系统实验 (P1162-1194, 32页)"

pinctrl与gpio子系统

💡 关联知识: [[03-Linux驱动开发核心/02-设备树语法与实战]] | [[03-Linux驱动开发核心/07-platform总线模型]] | [[STM32学习笔记/02-GPIO寄存器详解与三步进化法]]

一、问题背景:为什么需要pinctrl和gpio子系统

1.1 传统方式的问题

在早期的LED驱动实验中,我们直接通过设备树节点的 reg 属性配置GPIO:

/* 早期方式:直接配置寄存器 */
mydevice {
    reg = <0x020e0000 0x4000>;  /* IOMUXC寄存器基地址 */
    /* ... */
};

问题分析

问题 说明
可移植性差 不同芯片的寄存器地址完全不同
维护困难 硬编码寄存器值,无法动态管理
复用冲突 无法检测引脚是否被其他设备占用
电气属性缺失 无法配置上下拉、驱动强度等

1.2 Linux的解决方案

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子系统详解

2.1 pinctrl子系统的作用

pinctrl(Pin Control)子系统负责:

  1. 引脚复用:将物理引脚配置为GPIO、UART、SPI等功能
  2. 电气属性:配置上下拉、驱动强度、开漏/推挽等
  3. 状态管理:支持default、sleep等多种状态

2.2 设备树配置详解

2.2.1 pinctrl节点语法

/* 在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
            >;
        };
    };
};

2.2.2 宏定义详解

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

2.2.3 电气属性值详解

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 (滞后使能)

2.3 设备节点引用pinctrl

/* 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亮)

2.4 pinctrl驱动API

#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;
}

2.5 pinctrl多状态支持

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子系统详解

3.1 gpio子系统的作用

gpio子系统负责:

  1. GPIO编号管理:将设备树中的GPIO属性转换为全局GPIO编号
  2. 方向控制:设置GPIO为输入或输出
  3. 电平读写:读取或设置GPIO电平
  4. 资源管理:防止多个驱动同时使用同一个GPIO

3.2 设备树中的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 有效电平 低电平有效

3.3 GPIO API详解

#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);

3.4 GPIO使用示例

方式一:传统方式(已废弃)

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;
}

3.5 新旧API对比

特性 旧API (gpio_*) 新API (gpiod_*)
资源管理 手动gpio_request/free devm自动管理
设备树集成 需手动解析 直接支持
错误处理 简单返回码 ERR_PTR机制
多GPIO支持 不支持 支持GPIO数组
状态管理 支持active-low等
推荐程度 已废弃 推荐使用

四、内核源码分析

4.1 pinctrl驱动框架

/* 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,
};

4.2 设备树解析流程

/* 解析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;
}

4.3 pinctrl注册流程

/* 注册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;
}

五、调试方法

5.1 查看GPIO状态

# 查看所有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

5.2 查看pinctrl状态

# 查看设备的pinctrl状态
cat /sys/kernel/debug/pinctrl/*/pins

# 查看pinctrl控制器信息
cat /sys/kernel/debug/pinctrl/pinctrl-maps

5.3 设备树验证

# 编译设备树
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/

六、避坑指南

6.1 常见错误及解决方案

错误 原因 解决方案
gpio_request: already requested GPIO被其他驱动占用 检查设备树,注释掉冲突配置
pinctrl_lookup_state: failed 状态名不存在 检查pinctrl-names属性
of_get_named_gpio: failed 属性名错误或节点不存在 检查设备树节点路径和属性名
GPIO输出电平不对 active-low配置错误 检查GPIO_ACTIVE_LOW标志

6.2 引脚冲突排查

# 方法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

6.3 调试技巧

/* 在驱动中添加调试信息 */
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;
}

七、面试精选

Q1: pinctrl和gpio子系统的区别是什么?

答案

方面 pinctrl子系统 gpio子系统
职责 配置引脚复用和电气属性 控制GPIO电平和方向
操作对象 IOMUXC寄存器 GPIO数据寄存器
使用场景 初始化时配置引脚功能 运行时控制IO状态
API特点 状态管理,支持多状态 直接读写,简单直接

Q2: 设备树中 GPIO_ACTIVE_LOW 的作用是什么?

答案

  • 物理层面:LED通过三极管反接,低电平点亮
  • 软件层面:GPIO子系统会自动处理电平反转
  • 驱动代码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亮
    

Q3: 如何解决GPIO被占用的问题?

答案

  1. 检查设备树:搜索整个dts文件,找到所有使用该GPIO的节点
  2. 注释冲突配置:将不需要的配置注释掉
  3. 使用pinctrl状态:定义sleep状态,释放GPIO
  4. 查看debug信息:通过 /sys/kernel/debug/gpio 确认占用情况

Q4: 新旧GPIO API有什么区别?为什么推荐使用新API?

答案

特性 旧API 新API
资源管理 手动request/free devm自动管理
错误处理 简单返回码 ERR_PTR机制
设备树集成 需手动解析 直接支持
代码简洁性 较繁琐 更简洁

推荐原因

  • 避免资源泄漏
  • 支持设备树原生集成
  • 错误处理更规范
  • 是Linux内核的发展方向

Q5: 设备树中GPIO控制器的#gpio-cells属性是什么意思?

答案#gpio-cells属性定义了引用GPIO时需要的参数数量。对于I.MX6ULL:

  • #gpio-cells = <2> 表示需要2个参数
  • 第一个参数:GPIO编号(如3表示GPIO1_IO03)
  • 第二个参数:标志位(GPIO_ACTIVE_LOWGPIO_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 vs STM32 vs RK3568

特性 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>;

九、参考资料

PDF原文

  • 第四十五章 pinctrl和gpio子系统实验 (P1162-1194)

内核源码

  • 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.txt
  • Documentation/devicetree/bindings/gpio/gpio-mxs.txt

最后更新: 2026-09-17 | 版本: v2.0