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匹配):

  1. 设备树匹配 :驱动中定义of_device_id结构体(包含compatible属性),与DTS节点的compatible一致则匹配;
  2. name匹配 :驱动platform_drivername与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),指定compatibleptled-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(寄存器地址描述,适配第二个驱动)

dts 复制代码
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描述,适配第一个驱动)

dts 复制代码
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 设备树匹配结构体
c 复制代码
// 设备树匹配表(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函数(核心初始化)
c 复制代码
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执行完成!\n");
    return 0;

    // 错误处理:释放已申请资源
err_gpio_request:
err_of_find:
    misc_deregister(&misc_dev);
err_misc_register:
    printk("初始化失败!ret=%d\n", ret);
    return ret;
}
3.1.3 LED控制逻辑(ioctl/write)
c 复制代码
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读取寄存器地址
c 复制代码
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("杂项驱动初始化完成!\n");
    return 0;
    // 错误处理(省略)
}
3.2.2 手动配置GPIO(替代GPIO子系统)
c 复制代码
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控制)

c 复制代码
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>

#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)
  1. 将编写的DTS片段添加到IMX6ULL的设备树文件(如imx6ull-14x14-evk.dts);

  2. 进入内核源码目录,编译设备树:

    bash 复制代码
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
  3. 编译完成后,在arch/arm/boot/dts/目录下生成更新后的imx6ull-14x14-evk.dtb

(2)编译驱动模块

创建Makefile

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

执行编译:

bash 复制代码
make  # 生成.ko模块文件
(3)编译应用程序
bash 复制代码
arm-linux-gnueabihf-gcc led_app.c -o led_app

5.2 驱动加载与验证(推荐驱动1:GPIO子系统方式)

  1. 拷贝imx6ull-14x14-evk.dtbled_platform_gpio.koled_app到开发板;

  2. 开发板上电,通过TFTP加载新的DTB(或烧录到SD卡);

  3. 加载驱动模块:

    bash 复制代码
    insmod led_platform_gpio.ko
    ls /dev/led  # 杂项设备自动创建节点
  4. 运行应用程序:

    bash 复制代码
    ./led_app  # LED开始1秒闪烁
  5. 查看内核打印:

    bash 复制代码
    dmesg | grep -E "probe|led"

    预期输出:

    复制代码
    led platform_driver_register success
    #########################  led_driver  probe
    led  open
    cmd = 2147483648  args = 0
    cmd = 2147483649  args = 0
  6. 卸载驱动:

    bash 复制代码
    rmmod led_platform_gpio.ko

5.3 驱动2验证(寄存器操作方式)

bash 复制代码
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_idcompatible与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总线的驱动开发流程:

  1. 设备树(DTS)描述硬件资源(GPIO、寄存器地址);
  2. 驱动通过of函数解析DTS,获取硬件信息;
  3. 优先使用GPIO子系统API操作硬件,提升可移植性;
  4. Platform总线通过compatible属性实现驱动与硬件的自动匹配。
相关推荐
likangbinlxa6 小时前
【Oracle11g SQL详解】UPDATE 和 DELETE 操作的正确使用
数据库·sql
简单中的复杂6 小时前
【避坑指南】RK3576 Linux SDK 编译:解决 Buildroot 卡死在 host-gcc-final 的终极方案
linux·嵌入式硬件
r i c k6 小时前
数据库系统学习笔记
数据库·笔记·学习
wVelpro6 小时前
如何在Pycharm 2025.3 版本实现虚拟环境“Make available to all projects”
linux·ide·pycharm
上海合宙LuatOS6 小时前
LuatOS核心库API——【audio 】
java·网络·单片机·嵌入式硬件·物联网·音视频·硬件工程
野犬寒鸦7 小时前
从零起步学习JVM || 第一章:类加载器与双亲委派机制模型详解
java·jvm·数据库·后端·学习
Hhh __灏7 小时前
stm32的SRAM内存不足如何分析和优化?堆栈空间如何优化?
单片机
程序员老舅7 小时前
C++高并发精髓:无锁队列深度解析
linux·c++·内存管理·c/c++·原子操作·无锁队列
点灯小铭7 小时前
基于51单片机的双档交流电压表设计与实现
单片机·嵌入式硬件·毕业设计·51单片机·课程设计·期末大作业