Linux驱动开发实战(十一):GPIO子系统深度解析与RGB LED驱动实践
文章目录
- [Linux驱动开发实战(十一):GPIO子系统深度解析与RGB LED驱动实践](#Linux驱动开发实战(十一):GPIO子系统深度解析与RGB LED驱动实践)
- 前言
- 一、GPIO控制器设备树剖析
-
- [1.1 核心节点定义解析](#1.1 核心节点定义解析)
- [1.2 GPIO编号计算(当拓展看看即可)](#1.2 GPIO编号计算(当拓展看看即可))
- [二、RGB LED设备节点实现](#二、RGB LED设备节点实现)
-
- [2.1 设备树配置实例](#2.1 设备树配置实例)
- [2.2 关键解析点:](#2.2 关键解析点:)
- 三、驱动开发
-
- [3.1 关键API深度解析](#3.1 关键API深度解析)
- [3.2 代码解析](#3.2 代码解析)
- [3.3 关键代码解析:](#3.3 关键代码解析:)
- 四、实验
- 总结
前言
本文讲:
- 如何通过设备树(Device Tree)优雅地描述硬件资源
- 平台设备驱动(Platform Driver)的注册与匹配机制
- GPIO资源的申请、配置与原子化操作
一、GPIO控制器设备树剖析
1.1 核心节点定义解析
以gpio1节点为例:
dts:arch/arm/boot/dts/imx6ull-mmc-npi.dtsi
c
gpio1: gpio@209c000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x209c000 0x4000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_GPIO1>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-ranges = <&iomuxc 0 23 10>,
<&iomuxc 10 17 6>,
<&iomuxc 16 33 16>;
};
关键属性解析:
- reg:0x209c000为GPIO1的物理基地址,0x4000表示地址范围
- gpio-ranges :实现GPIO编号到物理引脚的映射,格式为:
<&pinctrl 起始GPIO 起始PIN 数量>
第一个映射:<&iomuxc 0 23 10>
表示: - GPIO1_IO0 对应物理引脚IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00(基地址+0x14)
1.2 GPIO编号计算(当拓展看看即可)
系统GPIO编号计算公式:
bash
全局编号 = bank编号 * 32 + pin号
i.MX6ULL各GPIO控制器对应bank:
- GPIO1:0*32 = 0
- GPIO2:1*32 = 32
- GPIO5:4*32 = 128(注意中间有保留bank)
示例:GPIO4_IO19的全局编号:
bash
(4-1)*32 + 19 = 3*32 +19 = 115
二、RGB LED设备节点实现
2.1 设备树配置实例
c
rgb_led {
#address-cells = <1>; // 子节点地址用1个u32表示
#size-cells = <1>; // 子节点大小用1个u32表示
compatible = "fire,rgb_led"; // 驱动匹配标识符
pinctrl-names = "default"; // 引脚控制状态名称
pinctrl-0 = <&pinctrl_rgb_led>; // 关联的引脚配置组
// 红色LED使用两个GPIO(阵列格式)
rgb_led_red = <&gpio1 4 GPIO_ACTIVE_LOW // GPIO1_IO04
&gpio1 10 GPIO_ACTIVE_LOW>; // GPIO1_IO10
// 绿色LED配置(单GPIO)
rgb_led_green = <&gpio4 20 GPIO_ACTIVE_LOW>; // GPIO4_IO20
// 蓝色LED配置(单GPIO)
rgb_led_blue = <&gpio4 19 GPIO_ACTIVE_LOW>; // GPIO4_IO19
status = "okay"; // 启用设备
};
};
2.2 关键解析点:
- 引脚控制组:
c
pinctrl-0 = <&pinctrl_rgb_led>;
需要配套的引脚定义(通常在iomuxc节点中)前文有讲:
c
pinctrl_rgb_led: rgbledgrp {
fsl,pins = <
MX6ULL_PAD_GPIO1_IO04__GPIO1_IO04 0x10B0
MX6ULL_PAD_GPIO1_IO10__GPIO1_IO10 0x10B0
MX6ULL_PAD_CSI_HSYNC__GPIO4_IO20 0x10B0
MX6ULL_PAD_CSI_VSYNC__GPIO4_IO19 0x10B0
>;
};
- GPIO编号计算:
&gpio1 4
= (1-1)*32 + 4 = 4&gpio4 20
= (4-1)*32 +20 = 116
c
// &gpioX Y → 全局编号 = (X-1)*32 + Y
rgb_led_red = <&gpio1 4 GPIO_ACTIVE_LOW // 全局编号 = (1-1)*32 +4 = 4
&gpio1 10 GPIO_ACTIVE_LOW>; // 全局编号 = (1-1)*32 +10 = 10
rgb_led_green = <&gpio4 20 GPIO_ACTIVE_LOW>; // (4-1)*32 +20 = 116
rgb_led_blue = <&gpio4 19 GPIO_ACTIVE_LOW>; // (4-1)*32 +19 = 115
将物理引脚(如GPIO1_IO04)转换为Linux GPIO子系统使用的统一编号
- 电平极性:
GPIO_ACTIVE_LOW
表示低电平点亮LED- 在驱动中需要取反电平设置:
c
gpio_set_value(gpio_num, !status);
三、驱动开发
3.1 关键API深度解析
of_get_named_gpio()
:
- 自动计算全局GPIO编号
- 支持多索引值(如rgb_led_red的两个GPIO)
gpio_direction_output()
:
c
static inline int gpio_direction_output(unsigned gpio, int value)
{
return gpio_direction_output_raw(gpio, value);
}
- 内部会操作GPIO_GDIR寄存器(方向寄存器)
- 对于i.MX6ULL,设置方向寄存器的对应bit为1表示输出
3.2 代码解析
c
// ... 头文件包含部分 ...
/*------------------GPIO获取关键部分----------------------*/
int rgb_led_red;
int rgb_led_green;
int rgb_led_blue;
static int led_probe(struct platform_device *pdv)
{
// 1. 查找设备树节点(核心操作)
rgb_led_device_node = of_find_node_by_path("/rgb_led");
if (!rgb_led_device_node) {
printk(KERN_ERR "Failed to find rgb_led node\n");
return -ENODEV;
}
// 2. 获取GPIO编号(设备树解析核心)
rgb_led_red = of_get_named_gpio(rgb_led_device_node, "rgb_led_red", 0);
if (gpio_is_valid(rgb_led_red)) {
printk(KERN_INFO "Red GPIO: %d\n", rgb_led_red);
} else {
printk(KERN_ERR "Invalid red GPIO\n");
return -EINVAL;
}
// 3. GPIO请求与配置(硬件操作)
ret = gpio_request(rgb_led_red, "led-red");
if (ret) {
printk(KERN_ERR "Red GPIO request failed: %d\n", ret);
return ret;
}
gpio_direction_output(rgb_led_red, 1); // 初始化为高电平
// ... 绿色和蓝色GPIO的相同处理流程 ...
}
3.3 关键代码解析:
- 设备树节点查找
c
of_find_node_by_path("/rgb_led")
通过绝对路径查找设备树节点
对应设备树中的:
c
rgb_led {
compatible = "fire,rgb_led";
// ...
};
- GPIO编号获取:
c
of_get_named_gpio(np, "rgb_led_red", 0)
- 解析设备树属性
rgb_led_red
的第一个GPIO - 对应设备树中的:
c
rgb_led_red = <&gpio1 4 GPIO_ACTIVE_LOW>;
- 返回值是全局GPIO编号,计算方式:
c
global_gpio = (gpio_bank-1)*32 + pin
例如:gpio1_4 → (1-1)*32 +4 =4
- GPIO硬件操作:
c
gpio_request(rgb_led_red, "led-red");
gpio_direction_output(rgb_led_red, 1);
gpio_request
:标记GPIO占用,防止冲突gpio_direction_output
:配置为输出模式,初始电平为高
- 绿色:驱动注册入口
- 蓝色:核心探测函数
- 黄色:设备树操作
- 紫色:GPIO硬件操作
四、实验
更改根节点的信息
编译设备树

替换原来的设备树
加载驱动

点灯成功!!!

总结
- 设备树配置
- 使用gpio-leds节点声明LED硬件参数
- 通过of_find_node_by_path()精准定位设备节点
- 利用of_get_named_gpio()解析GPIO编号
- 平台驱动架构
- 实现platform_driver结构体注册
- 构建led_probe()初始化流程
- 完善led_remove()资源释放机制