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配置(原书示例代码 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。自定义外设需新建自己的子节点。
以 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 个 u32(mux_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 = 0x020E0068、SW_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
宏后面紧跟的数值(如 0x17059、0x13058、0x10B0)就是 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
/* 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亮)⚠️ 来源说明:本节不属于《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;
}
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);
与 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 编号。
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;
}
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。
| 特性 | 旧API (gpio_*) | 新API (gpiod_*) |
|---|---|---|
| 资源管理 | 手动gpio_request/free | devm自动管理 |
| 设备树集成 | 需手动解析 | 直接支持 |
| 错误处理 | 简单返回码 | ERR_PTR机制 |
| 多GPIO支持 | 不支持 | 支持GPIO数组 |
| 状态管理 | 无 | 支持active-low等 |
| 推荐程度 | 已废弃 | 推荐使用 |
/* 原书示例代码 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"]
/* 原书示例代码 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_reg与conf_reg保存在info参数中(pin_reg->mux_reg/pin_reg->conf_reg),input_reg、mux_mode、input_val与config保存在grp参数中(pin->...)。
/* 注册 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
};
其中 pctlops、pmxops、confops 三个 _ops 结构体是 PIN 控制器的"工具",分别对应 I.MX6ULL 的 imx_pctrl_ops、imx_pmx_ops 和 imx_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);
本节为原书第四十五章的完整实验流程,例程路径:开发板光盘-> 2、Linux驱动例程-> 5_gpioled。
① 添加 pinctrl 节点(在 iomuxc 的 imx6ul-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 节点是否存在。
#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。
直接复用第四十二章的 ledApp.c,无需修改。
# 编译驱动模块(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驱动开发指南》内容,为扩展知识。
# 查看所有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/
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。
| 错误 | 原因 | 解决方案 |
|---|---|---|
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;
}
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。
答案:
| 方面 | 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控制器引用
*/
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。
| 特性 | 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>;
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.txtDocumentation/devicetree/bindings/gpio/gpio-mxs.txt最后更新: 2026-09-17 | 版本: v2.0
内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第45章 pinctrl和gpio子系统实验(部分实验流程补充自第44章 设备树下的LED驱动实验)