03-pinctrl与gpio子系统.md 35 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配置(原书示例代码 45.1.2.2 / 45.1.2.3) */
&iomuxc {
    pinctrl-names = "default";  /* 定义状态名称列表 */
    pinctrl-0 = <&pinctrl_hog_1>;  /* default状态使用的配置组 */

    imx6ul-evk {
        /* 定义一个pinctrl配置组:热插拔相关PIN */
        pinctrl_hog_1: hoggrp-1 {
            fsl,pins = <
                /* 宏定义                                电气属性配置值 */
                MX6UL_PAD_UART1_RTS_B__GPIO1_IO19      0x17059  /* SD1 CD */
                MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT   0x17059
                MX6UL_PAD_GPIO1_IO09__GPIO1_IO09       0x17059
                MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID   0x13058
            >;
        };

        /* flexcan1 外设所使用的 PIN */
        pinctrl_flexcan1: flexcan1grp {
            fsl,pins = <
                MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX     0x1b020
                MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX     0x1b020
            >;
        };

        /* wdog 外设所使用的 PIN */
        pinctrl_wdog: wdoggrp {
            fsl,pins = <
                MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY    0x30b0
            >;
        };
    };
};

同一个外设的 PIN 都组织在 iomuxc 下的一个子节点里:pinctrl_hog_1 是热插拔相关 PIN,pinctrl_flexcan1 是 flexcan1 的 PIN,pinctrl_wdog 是看门狗的 PIN。自定义外设需新建自己的子节点。

2.2.2 宏定义详解

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 为例:

/* 定义在 imx6ull-pinfunc.h(由 imx6ul-pinfunc.h 引用)中 */
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19  0x0090 0x031C 0x0000 0x5 0x0

原书示例代码 45.1.2.4 中,以 MX6UL_PAD_UART1_RTS_B 开头的宏一共有 8 个,分别对应 UART1_RTS_B 这个 PIN 的 8 种复用 IO(UART1_DCE_RTS、UART1_DTE_CTS、ENET1_TX_ER、USDHC1_CD_B、CSI_DATA05、ENET2_1588_EVENT1_OUT、GPIO1_IO19、USDHC2_CD_B)。

宏的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 输入选择值

关于"6 个整数":宏本身只展开为 5 个 u32mux_reg conf_reg input_reg mux_mode input_val);而在设备树的 fsl,pins 属性中,每个引脚条目总计是 6 个 u32——在宏的 5 个值之后,紧跟一个由用户自行设置的 config(电气属性)值:

< mux_reg  conf_reg  input_reg  mux_mode  input_val  config >

这正是原书示例代码 45.1.2.6 的注释所说的:Each pin represented in fsl,pins consists of 5 u32 PIN_FUNC_ID and 1 u32 CONFIG, so 24 types in total for each pin.#define FSL_PIN_SIZE 24,即 6×4 字节)。

以本章 LED 实验使用的引脚为例(mux_reg/conf_reg 偏移来自原书第四十四章使用的 SW_MUX_GPIO1_IO03_BASE = 0x020E0068SW_PAD_GPIO1_IO03_BASE = 0x020E02F4):

#define MX6UL_PAD_GPIO1_IO03__GPIO1_IO03  0x0068 0x02F4 0x0000 0x5 0x0
/*                                        mux_reg conf_reg input  mode input */

寄存器地址计算

基地址 = 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 电气属性值详解

宏后面紧跟的数值(如 0x170590x130580x10B0)就是 conf_reg 的值,最终写入 IOMUXC_SW_PAD_CTL_PAD_xxx 寄存器,由用户自行设置,用于配置该 IO 的上/下拉、驱动能力和速度等。原书本章只说明该值的用途,未展开位域。

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

/* IOMUXC_SW_PAD_CTL_PAD 寄存器位定义(i.MX6ULL) */
bit 0      : SRE   - 压摆率控制 (0: 低速, 1: 高速)
bit 5~3    : DSE   - 驱动强度 (3 位,000~111,值越大驱动越强)
bit 7~6    : SPEED - 速度等级 (00: 50MHz, 01: 100MHz, 10: 100MHz, 11: 200MHz)
bit 11     : ODE   - 开漏使能 (0: 关闭, 1: 使能)
bit 12     : PKE   - 上下拉/保持使能 (0: 关闭, 1: 使能)
bit 13     : PUE   - 上下拉/保持选择 (1: 上下拉, 0: 保持)
bit 15~14  : PUS   - 上下拉阻值 (00: 100K下拉, 01: 47K上拉, 10: 100K上拉, 11: 22K上拉)
bit 16     : HYS   - 滞回比较器使能 (0: 关闭, 1: 使能)

0x17059 的二进制分解

0x17059 = 0001 0111 0000 0101 1001

bit0     = 1   → SRE=1
bit5~3   = 011 → DSE=3
bit7~6   = 01  → SPEED=1
bit11    = 0   → ODE=0
bit12    = 1   → PKE=1
bit13    = 1   → PUE=1
bit15~14 = 01  → PUS=1 (47K 上拉)
bit16    = 1   → HYS=1

0x10B0(本章 LED 使用的电气属性值,原书示例代码 45.4.1.1)的二进制分解

0x10B0 = 0001 0000 1011 0000

bit0     = 0   → SRE=0
bit5~3   = 110 → DSE=6
bit7~6   = 10  → SPEED=2
bit11    = 0   → ODE=0
bit12    = 1   → PKE=1
bit13    = 0   → PUE=0 (保持)
bit16    = 0   → HYS=0

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

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

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

与 GPIO 相关的 OF 函数(原书 45.2.5 逐项核对)

函数 原型 说明
of_gpio_named_count int of_gpio_named_count(struct device_node *np, const char *propname) 统计某个属性里定义了几个 GPIO;空的 GPIO 也会被统计到
of_gpio_count int of_gpio_count(struct device_node *np) 统计 gpios 属性里的 GPIO 数量(只能统计 "gpios")
of_get_named_gpio int of_get_named_gpio(struct device_node *np, const char *propname, int index) <&gpio5 7 GPIO_ACTIVE_LOW> 这类属性值转换为内核 GPIO 编号

原书说明:gpio_get_value / gpio_set_value 在书中是宏定义,分别展开为 int __gpio_get_value(unsigned gpio)void __gpio_set_value(unsigned gpio, int value),使用时传的是 GPIO 编号。

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对比

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

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

四、内核源码分析

4.1 pinctrl驱动框架

/* 原书示例代码 45.1.2.5 pinctrl-imx6ul.c 片段 */
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, },
    { /* sentinel */ }
};

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;

    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,
    .remove = imx_pinctrl_remove,
};

函数调用链(原书图 45.1.2.3)

graph TD
    A[imx6ul_pinctrl_probe] --> B[imx_pinctrl_probe]
    B --> C[imx_pinctrl_probe_dt]
    C --> D[imx_pinctrl_parse_functions]
    D --> E[imx_pinctrl_parse_groups]
    E --> F["解析设备树PIN信息<br/>mux_reg/conf_reg/input_reg/mux_mode/input_val/config"]
    B --> G["pinctrl_register()<br/>向内核注册pinctrl"]

4.2 设备树解析流程

/* 原书示例代码 45.1.2.6 imx_pinctrl_parse_groups 函数代码段 */
/*
 * Each pin represented in fsl,pins consists of 5 u32 PIN_FUNC_ID
 * and 1 u32 CONFIG, so 24 types in total for each pin.
 */
#define FSL_PIN_SIZE 24
#define SHARE_FSL_PIN_SIZE 20

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;
        struct imx_pin_reg *pin_reg;
        struct imx_pin *pin = &grp->pins[i];
        ......
        pin_id = (mux_reg != -1) ? mux_reg / 4 : conf_reg / 4;
        pin_reg = &info->pin_regs[pin_id];
        pin->pin = pin_id;
        grp->pin_ids[i] = pin_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++);
        pin->input_val = be32_to_cpu(*list++);

        /* SION bit is in mux register */
        config = be32_to_cpu(*list++);
        if (config & IMX_PAD_SION)
            pin->mux_mode |= IOMUXC_CONFIG_SION;
        pin->config = config & ~IMX_PAD_SION;
        ......
    }

    return 0;
}

原书说明:mux_regconf_reg 保存在 info 参数中(pin_reg->mux_reg / pin_reg->conf_reg),input_regmux_modeinput_valconfig 保存在 grp 参数中(pin->...)。

4.3 pinctrl注册流程

/* 注册 pinctrl 控制器(原书给出的原型) */
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
                                     struct device *dev,
                                     void *driver_data);

pctldesc 非常重要,它就是要注册的 PIN 控制器描述符,用于配置 SOC 的 PIN 复用功能和电气特性(原书示例代码 45.1.2.7):

struct pinctrl_desc {
    const char *name;
    struct pinctrl_pin_desc const *pins;
    unsigned int npins;
    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
};

其中 pctlopspmxopsconfops 三个 _ops 结构体是 PIN 控制器的"工具",分别对应 I.MX6ULL 的 imx_pctrl_opsimx_pmx_opsimx_pinconf_ops(原书示例代码 45.1.2.9),由半导体厂商在内核源码中提供:

/* 原书示例代码 45.1.2.8 imx_pinctrl_probe 函数代码段 */
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);

五、实验:用pinctrl和gpio子系统驱动LED(原书 45.1.3、45.2.4、45.4、45.5)

本节为原书第四十五章的完整实验流程,例程路径:开发板光盘-> 2、Linux驱动例程-> 5_gpioled

5.1 修改设备树

① 添加 pinctrl 节点(在 iomuxcimx6ul-evk 子节点下,原书示例代码 45.4.1.1):

pinctrl_led: ledgrp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0x10B0 /* LED0 */
    >;
};

② 添加 LED 设备节点(在根节点 / 下,原书示例代码 45.4.1.2):

gpioled {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "atkalpha-gpioled";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_led>;
    led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
    status = "okay";
};

③ 检查 PIN 是否被其他外设占用(原书一再强调的重点):

  • pinctrl_tsc 节点中默认把 GPIO1_IO03 配成了 TSC 的 PIN,需要注释掉:

    pinctrl_tsc: tscgrp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO01__GPIO1_IO01    0xb0
        MX6UL_PAD_GPIO1_IO02__GPIO1_IO02    0xb0
        MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0xb0   /* 屏蔽掉 */
        MX6UL_PAD_GPIO1_IO04__GPIO1_IO04    0xb0
    >;
    };
    
  • 再搜索 gpio1 3,把其他使用该 GPIO 的外设也屏蔽掉(例如 tsc 节点的 xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;)。

修改完成后 make dtbs 重新编译设备树,用新的 imx6ull-alientek-emmc.dtb 启动,进入 /proc/device-tree 检查 gpioled 节点是否存在。

5.2 驱动程序 gpioled.c(原书示例代码 45.4.2.1)

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPIOLED_CNT      1            /* 设备号个数 */
#define GPIOLED_NAME     "gpioled"   /* 名字 */
#define LEDOFF           0           /* 关灯 */
#define LEDON            1           /* 开灯 */

/* gpioled 设备结构体 */
struct gpioled_dev {
    dev_t devid;              /* 设备号 */
    struct cdev cdev;         /* cdev */
    struct class *class;      /* 类 */
    struct device *device;    /* 设备 */
    int major;                /* 主设备号 */
    int minor;                /* 次设备号 */
    struct device_node *nd;   /* 设备节点 */
    int led_gpio;             /* led 所使用的 GPIO 编号 */
};

struct gpioled_dev gpioled; /* led 设备 */

static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &gpioled; /* 设置私有数据 */
    return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf,
                        size_t cnt, loff_t *offt)
{
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
                         size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    struct gpioled_dev *dev = filp->private_data;

    retvalue = copy_from_user(databuf, buf, cnt);
    if (retvalue < 0) {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0];           /* 获取状态值 */

    if (ledstat == LEDON) {
        gpio_set_value(dev->led_gpio, 0);   /* 打开 LED 灯 */
    } else if (ledstat == LEDOFF) {
        gpio_set_value(dev->led_gpio, 1);   /* 关闭 LED 灯 */
    }
    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 设备操作函数 */
static struct file_operations gpioled_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};

/* 驱动入口函数 */
static int __init led_init(void)
{
    int ret = 0;

    /* 1、获取设备节点:gpioled */
    gpioled.nd = of_find_node_by_path("/gpioled");
    if (gpioled.nd == NULL) {
        printk("gpioled node cant not found!\r\n");
        return -EINVAL;
    } else {
        printk("gpioled node has been found!\r\n");
    }

    /* 2、获取设备树中的 gpio 属性,得到 LED 所使用的 GPIO 编号 */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    if (gpioled.led_gpio < 0) {
        printk("can't get led-gpio");
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n", gpioled.led_gpio);

    /* 3、设置 GPIO1_IO03 为输出,默认高电平,默认关闭 LED 灯 */
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if (ret < 0) {
        printk("can't set gpio!\r\n");
    }

    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (gpioled.major) {            /* 定义了设备号 */
        gpioled.devid = MKDEV(gpioled.major, 0);
        register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    } else {                        /* 没有定义设备号 */
        alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }
    printk("gpioled major=%d,minor=%d\r\n", gpioled.major, gpioled.minor);

    /* 2、初始化 cdev */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);

    /* 3、添加一个 cdev */
    cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

    /* 4、创建类 */
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class)) {
        return PTR_ERR(gpioled.class);
    }

    /* 5、创建设备 */
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid,
                                   NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device)) {
        return PTR_ERR(gpioled.device);
    }
    return 0;
}

/* 驱动出口函数 */
static void __exit led_exit(void)
{
    cdev_del(&gpioled.cdev);                              /* 删除 cdev */
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销 */

    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

与原书第四十四章的 dtsled.c 相比,这里取消了直接配置寄存器的过程,改用 of_get_named_gpio + gpio_direction_output + gpio_set_value 等内核 API。

5.3 测试 APP

直接复用第四十二章的 ledApp.c,无需修改。

5.4 编译与运行测试

# 编译驱动模块(Makefile 中 obj-m := gpioled.o)
make -j32                      # 生成 gpioled.ko

# 编译测试 APP
arm-linux-gnueabihf-gcc ledApp.c -o ledApp

# 拷贝到开发板 rootfs/lib/modules/4.1.15/ 后,在开发板上:
depmod                         # 第一次加载驱动时需要执行
modprobe gpioled.ko            # 加载驱动,打印 led-gpio num = 3

./ledApp /dev/gpioled 1        # 打开 LED 灯
./ledApp /dev/gpioled 0        # 关闭 LED 灯

rmmod gpioled.ko               # 卸载驱动

六、调试方法

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

6.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

6.2 查看pinctrl状态

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

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

6.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/

七、避坑指南

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

7.1 常见错误及解决方案

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

7.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

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

八、面试精选

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

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控制器引用
    */
    

九、跨平台对比

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

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-1193)
  • 第四十四章 设备树下的LED驱动实验 (P1131-1161,SW_MUX_GPIO1_IO03_BASE / SW_PAD_GPIO1_IO03_BASE)

内核源码

  • 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

内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第45章 pinctrl和gpio子系统实验(部分实验流程补充自第44章 设备树下的LED驱动实验)