72、IMX6ULL驱动实战:设备树(DTS/DTB)+ GPIO子系统+Platform总线
IMX6ULL驱动实战:设备树(DTS/DTB)+ GPIO子系统+Platform总线
一、核心技术原理铺垫
1.1 设备树(DTS/DTB)核心概念
设备树(Device Tree)是描述硬件资源的结构化文件,替代传统驱动中的硬编码硬件信息,核心目标是“硬件与驱动分离”:
- DTS(Device Tree Source):设备树源文件,人类可读,包含硬件节点、资源配置(如GPIO、寄存器地址);
- DTB(Device Tree Blob):DTS编译后的二进制文件,内核可直接解析;
- 编译命令:
make dtbs编译所有设备树,make xxx.dtb编译指定DTS; - 核心属性:
compatible:兼容性属性,驱动通过该属性匹配设备节点(优先级高于name匹配);reg:寄存器地址范围;xxx-gpio:GPIO编号及配置。
1.2 Platform总线与设备树匹配机制
Platform总线支持两种匹配方式(优先级:设备树匹配 > name匹配):
- 设备树匹配:驱动中定义
of_device_id结构体(包含compatible属性),与DTS节点的compatible一致则匹配; - name匹配:驱动
platform_driver的name与DTS节点name一致则匹配。
匹配成功后,总线自动执行驱动的probe函数,完成驱动初始化。
1.3 GPIO子系统API(简化硬件操作)
内核提供的GPIO子系统封装了底层寄存器操作,无需手动配置引脚复用、方向,直接调用API即可:
| API函数 | 作用说明 |
|---|---|
of_find_node_by_path | 通过路径查找DTS设备节点 |
of_get_named_gpio | 从设备节点获取GPIO编号 |
gpio_request | 申请GPIO(避免资源冲突) |
gpio_direction_output | 配置GPIO为输出模式并设置初始值 |
gpio_set_value | 设置GPIO电平(1=高,0=低) |
gpio_free | 释放GPIO资源 |
1.4 硬件基础:GPIO控制LED
本文通过IMX6ULL的GPIO引脚控制LED,核心逻辑:
- DTS中定义LED对应的GPIO节点(如
ptled_sub),指定compatible和ptled-gpio属性; - 驱动通过GPIO子系统API读取DTS中的GPIO信息,申请并配置GPIO;
- 通过
gpio_set_value控制GPIO电平,实现LED亮灭。
二、设备树(DTS)编写示例
根据驱动代码中提到的/ptled和/ptled_sub节点,编写对应的DTS片段(需添加到IMX6ULL的设备树文件,如imx6ull-14x14-evk.dts):
2.1 节点1:/ptled(寄存器地址描述,适配第二个驱动)
ptled {
compatible = "pt-led"; // 兼容性属性,驱动匹配用
name1 = "imx6ull-led"; // 自定义名称属性
reg = <0x020E0068 0x04 // IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03(4字节)
0x020E02F4 0x04 // IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03(4字节)
0x0209C004 0x04 // GPIO1_GDIR(方向寄存器)
0x0209C000 0x04>; // GPIO1_DR(数据寄存器)
};
2.2 节点2:/ptled_sub(GPIO描述,适配第一个驱动)
ptled_sub {
compatible = "pt-led-sub"; // 与驱动of_device_id一致
ptled-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; // GPIO1_IO03,低电平有效
};
&gpio1:引用GPIO1控制器节点;3:GPIO1的第3个引脚(GPIO1_IO03);GPIO_ACTIVE_LOW:低电平激活(LED亮)。
三、核心驱动代码深度解析
3.1 驱动1:GPIO子系统+Platform总线+设备树匹配(推荐)
该驱动通过GPIO子系统操作硬件,无需手动配置寄存器,可移植性强,核心代码解析:
3.1.1 设备树匹配结构体
// 设备树匹配表(compatible属性匹配)
static struct of_device_id led_table[] = {
{.compatible = "pt-led-sub"}, // 与DTS节点compatible一致
{} // 结束标志
};
// Platform驱动结构体
static struct platform_driver pdrv = {
.probe = probe,
.remove = remove,
.driver = {
.name = DEV_NAME, // name匹配(备用)
.of_match_table = led_table // 设备树匹配(优先)
}
};
3.1.2 probe函数(核心初始化)
static int probe(struct platform_device *pdev) {
struct device_node *pdts; // 设备树节点指针
int ret = misc_register(&misc_dev); // 注册杂项设备
if(ret) goto err_misc_register;
// 1. 通过路径查找DTS节点(/ptled_sub)
pdts = of_find_node_by_path("/ptled_sub");
if(NULL == pdts) {
ret = PTR_ERR(pdts);
goto err_of_find;
}
// 2. 从节点获取GPIO编号(属性名:ptled-gpio)
led_gpio = of_get_named_gpio(pdts, "ptled-gpio", 0);
if(led_gpio < 0) {
ret = led_gpio;
goto err_of_find;
}
// 3. 申请GPIO资源(避免冲突)
ret = gpio_request(led_gpio, "led");
if(ret < 0) goto err_gpio_request;
// 4. 配置GPIO为输出模式,初始值为LED_OFF(灭)
gpio_direction_output(led_gpio, LED_OFF);
printk("驱动匹配成功,probe执行完成!
");
return 0;
// 错误处理:释放已申请资源
err_gpio_request:
err_of_find:
misc_deregister(&misc_dev);
err_misc_register:
printk("初始化失败!ret=%d
", ret);
return ret;
}
3.1.3 LED控制逻辑(ioctl/write)
static void led_on(void) {
gpio_set_value(led_gpio, LED_ON); // GPIO输出低电平,LED亮
}
static void led_off(void) {
gpio_set_value(led_gpio, LED_OFF); // GPIO输出高电平,LED灭
}
// ioctl命令处理(核心控制接口)
static long ioctl(struct file *file, unsigned int cmd, unsigned long args) {
long ret = 0;
switch(cmd) {
case CMD_LED_ON: led_on(); break;
case CMD_LED_OFF: led_off(); break;
default: ret = -EINVAL;
}
return ret;
}
3.2 驱动2:设备树读取寄存器+直接操作(传统方式)
该驱动通过DTS读取寄存器地址,手动ioremap映射后操作硬件,适用于无GPIO子系统的场景,核心区别:
3.2.1 从DTS读取寄存器地址
static int __init led_init(void) {
struct device_node *pdts;
unsigned int led_array[8] = {0}; // 存储reg属性的地址和长度
int ret = misc_register(&misc_dev);
if(ret) goto err_misc_register;
// 1. 查找DTS节点/ptled
pdts = of_find_node_by_path("/ptled");
if(NULL == pdts) {
ret = PTR_ERR(pdts);
goto err_find_node;
}
// 2. 读取reg属性(8个值:4组地址+长度)
ret = of_property_read_u32_array(pdts, "reg", led_array, 8);
if(ret < 0) goto err_of_property_read;
// 3. 映射寄存器地址(虚拟地址操作硬件)
iomuxc_mux_ctl = ioremap(led_array[0], led_array[1]); // 复用寄存器
iomuxc_pad_ctl = ioremap(led_array[2], led_array[3]); // 电气特性寄存器
gpio1_gdir = ioremap(led_array[4], led_array[5]); // 方向寄存器
gpio1_dr = ioremap(led_array[6], led_array[7]); // 数据寄存器
printk("杂项驱动初始化完成!
");
return 0;
// 错误处理(省略)
}
3.2.2 手动配置GPIO(替代GPIO子系统)
static void led1_init(void) {
*iomuxc_mux_ctl = 0x05; // 引脚复用为GPIO
*iomuxc_pad_ctl = 0x10B0;// 电气特性配置(上拉+100MHz)
*gpio1_gdir |= (1 << 3); // 配置为输出模式
}
static void led_on(void) {
*gpio1_dr &= ~(1 << 3); // 低电平亮灯
}
四、应用程序编写(复用之前的ioctl控制)
#include
#include
#include
#include
#define MAGIC_NUM 'x'
#define CMD_LED_ON _IO(MAGIC_NUM, 0)
#define CMD_LED_OFF _IO(MAGIC_NUM, 1)
int main(int argc, char *argv[]) {
int fd = open("/dev/led", O_RDWR);
if(fd < 0) {
perror("open失败");
return 1;
}
// 循环控制LED1秒闪烁
while(1) {
ioctl(fd, CMD_LED_ON);
sleep(1);
ioctl(fd, CMD_LED_OFF);
sleep(1);
}
close(fd);
return 0;
}
五、实操验证流程(IMX6ULL开发板)
5.1 编译准备
(1)编译设备树(DTB)
- 将编写的DTS片段添加到IMX6ULL的设备树文件(如
imx6ull-14x14-evk.dts); - 进入内核源码目录,编译设备树:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs - 编译完成后,在
arch/arm/boot/dts/目录下生成更新后的imx6ull-14x14-evk.dtb。
(2)编译驱动模块
创建Makefile:
# 驱动1(GPIO子系统+Platform)
obj-m += led_platform_gpio.o
# 驱动2(寄存器操作+杂项设备)
# obj-m += led_misc_reg.o
KERNELDIR ?= /home/linux/IMX6ULL/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
执行编译:
make # 生成.ko模块文件
(3)编译应用程序
arm-linux-gnueabihf-gcc led_app.c -o led_app
5.2 驱动加载与验证(推荐驱动1:GPIO子系统方式)
- 拷贝
imx6ull-14x14-evk.dtb、led_platform_gpio.ko、led_app到开发板; - 开发板上电,通过TFTP加载新的DTB(或烧录到SD卡);
- 加载驱动模块:
insmod led_platform_gpio.ko ls /dev/led # 杂项设备自动创建节点 - 运行应用程序:
./led_app # LED开始1秒闪烁 - 查看内核打印:
预期输出:dmesg | grep -E "probe|led"led platform_driver_register success ######################### led_driver probe led open cmd = 2147483648 args = 0 cmd = 2147483649 args = 0 - 卸载驱动:
rmmod led_platform_gpio.ko
5.3 驱动2验证(寄存器操作方式)
insmod led_misc_reg.ko
ls /dev/led
./led_app
六、关键技术对比与优势总结
6.1 驱动1(GPIO子系统)vs 驱动2(寄存器操作)
| 对比维度 | 驱动1(GPIO子系统+设备树) | 驱动2(寄存器操作+设备树) |
|---|---|---|
| 代码复杂度 | 低(调用API,无需手动配置寄存器) | 高(手动配置复用、电气特性) |
| 可移植性 | 强(适配不同芯片,仅需修改DTS) | 弱(寄存器地址硬编码,移植需改驱动) |
| 安全性 | 高(gpio_request避免资源冲突) | 低(无资源申请,可能冲突) |
| 适用场景 | 大多数GPIO外设(LED、按键、继电器) | 无GPIO子系统的老内核或特殊外设 |
6.2 设备树的核心价值
- 硬件与驱动分离:修改硬件时无需改驱动,仅需修改DTS;
- 统一硬件描述:所有硬件资源集中在DTS,便于维护;
- 内核自动解析:内核通过DTS识别硬件,无需驱动硬编码。
七、常见问题排查
7.1 驱动匹配失败(probe不执行)
compatible不匹配:驱动of_device_id的compatible与DTS节点一致;- DTS节点路径错误:
of_find_node_by_path的路径需与DTS中完全一致; - 设备树未更新:确保烧录的是编译后的新DTB。
7.2 GPIO申请失败(ret<0)
- GPIO被占用:通过
cat /sys/kernel/debug/gpio查看已占用的GPIO; - DTS中GPIO配置错误:确认
ptled-gpio的GPIO控制器(如&gpio1)和引脚号正确。
7.3 LED不闪烁但应用无报错
- GPIO方向配置错误:确认调用
gpio_direction_output配置为输出模式; - 电平极性错误:DTS中
GPIO_ACTIVE_LOW表示低电平亮,需与硬件电路一致。
八、总结
本文通过两个LED驱动案例,完整展现了IMX6ULL的设备树+GPIO子系统+Platform总线的驱动开发流程:
- 设备树(DTS)描述硬件资源(GPIO、寄存器地址);
- 驱动通过of函数解析DTS,获取硬件信息;
- 优先使用GPIO子系统API操作硬件,提升可移植性;
- Platform总线通过
compatible属性实现驱动与硬件的自动匹配。










