9-Linux驱动开发-设备树=>设备树插件实现 RGB 灯驱动

维度 主设备树 (Device Tree) 设备树插件 (DT Overlay)
类似 原始蓝图 插件查到蓝图里面
文件后缀 .dts (源码) -> .dtb (二进制) .dts (源码) -> .dtbo (二进制)
修改方式 直接修改内核源码 新建一个独立的文件。即插即用。
编译成本 慢。通常需要重新编译所有关联的 DTB。 。只编译这一个小文件。
生效逻辑 覆盖。必须替换启动分区的系统文件,风险大。 叠加。在原系统基础上"打补丁",风险小。
依赖关系 是基础。定义了 &gpioa 是什么。 是上层。引用基础,"要用到 &gpioa"。

设备树插件,顾名思义新建插入。我们不用改设备树源码只需要在内核源码的arch/arm/boot/dts/overlays/ 目录下新建一个.dts作为插件,编译后内核能够动态加载它。

设备树插件格式:

c 复制代码
/* * 文件名: fire-stm32mp157-overlay.dts
 * 描述: STM32MP157 设备树插件模板 (现代语法)
 */

/* 1. 头部声明 (必须有) */
/* 告诉编译器版本 */
/dts-v1/;
/* 告诉编译器这是一个"插件",不是完整设备树 */
/plugin/;

/* * 2. 场景一:在根节点下"无中生有"添加新设备 
 * 语法:&{/} 代表引用根节点
 */
&{/} {
    /* 自定义的 RGB 灯节点 */
    fire_rgb_led {
        /* 匹配暗号,驱动程序靠这个找到我 */
        compatible = "rgb-led";
        
        /* 硬件资源描述 */
        /* 注意: &gpioa 需要主设备树里有这个标签 */
        /* 如果报错找不到 &gpioa,请往下看注意事项 */
        red-gpios   = <&gpioa 13 0>; 
        green-gpios = <&gpioa 14 0>;
        
        status = "okay";
    };
    
    /* 另一个测试节点 */
    my_test_node {
        compatible = "test";
        my-id = <100>;
    };
};

/* * 3. 场景二:修改/开启板子上已有的设备 
 * 语法:直接引用标签 (例如 &uart4, &i2c1)
 */
&uart4 {
    /* 原本可能是 disabled,我们把它打开 */
    status = "okay";
    
    /* 我们还可以修改它的属性,比如波特率 */
    /* current-speed = <115200>; */
};

&i2c1 {
    status = "okay";
    
    /* 在 I2C 总线上挂载一个从设备 */
    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
    };
};

如果您编译插件时报错:Reference to non-existent node or label "gpioa"(找不到标签 gpioa)。要使用绝对路径引用法

c 复制代码
&gpioa { ... };//修改为以下内容
/* 使用 fragment 语法 + 绝对路径 target-path */
fragment@0 {
    /* 去查手册或源码,找到 gpioa 的绝对路径 */
    target-path = "/soc/pin-controller/gpio@50002000"; 
    __overlay__ {
        /* 这里写内容 */
    };
};
驱动设备树插件编译使用流程

概览流程

html 复制代码
1.准备环境: 打开两个终端窗口。
	窗口 A:进内核源码目录(编译插件用)。/arch/arm/boot/dts/overlays
	窗口 B:进驱动代码目录(编译驱动用)。/linux_driver/dynamic_device_tree/overlay_rgb_led
2.搞定硬件(设备树插件):
	在窗口 B 写好 .dts。
	把它复制到窗口 A 的 arch/arm/boot/dts/overlays/,并修改Makfile文件-添加设备树插件文件。
	在窗口 A 根运行 make dtbs。
	一般情况乱下生成的.dtbo 位于内核根目录下的"/arch/arm/boot/dts/overlays"目录下。
	我们为了不污染源吗我们设定O=... 参数,保持源码目录干净。
	我们采用如下方式:
		make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- O=build_image/build -j4 dtbs
	把生成的 .dtbo 扔到板子上重启。
3.搞定软件(驱动):
	在窗口 B 写好 .c。
	在窗口 B 运行 make。
	把生成的 .ko 扔到板子上加载。

设备树代码文件fire-rgb-led-overlay.dts

c 复制代码
/*
 * 野火 STM32MP1 RGB LED 设备树插件 (现代语法版)
 */

/dts-v1/;
/plugin/;

/* 引用必要的头文件 */
#include <dt-bindings/pinctrl/stm32-pinfunc.h>
#include <dt-bindings/input/input.h>
#include <dt-bindings/mfd/st,stpmic1.h>
#include <dt-bindings/gpio/gpio.h>

/* * 现代写法核心:
 * 使用 &{/} 直接引用根节点,替代 fragment@0 + target-path + __overlay__
 */
&{/} {
    /* 添加 led 父节点 */
    rgb_led {//节点名
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "rgb_led";
        ranges; /* 允许子节点继承父节点的地址空间 */

        /* -------------------------------------------------
         * 红灯节点 (GPIOA 13)
         * 包含 GPIOA 的 5 个寄存器 + RCC 时钟寄存器,顺序根据驱动中的顺序改变的
         * ------------------------------------------------- */
        rgb_led_red@0x50002000 {//节点名
            compatible = "fire,rgb_led_red";//匹配名
            reg = <
                0x50002000 0x4  /* GPIOA_MODER */
                0x50002004 0x4  /* GPIOA_OTYPER */
                0x50002008 0x4  /* GPIOA_OSPEEDR */
                0x5000200C 0x4  /* GPIOA_PUPDR */
                0x50002018 0x4  /* GPIOA_BSRR */
                0x50000A28 0x4  /* RCC_MP_AHB4ENSETR (时钟) */
            >;
            status = "okay";
        };

        /* -------------------------------------------------
         * 绿灯节点 (GPIOG 2)
         * 包含 GPIOG 的 5 个寄存器 + RCC 时钟寄存器
         * ------------------------------------------------- */
        rgb_led_green@0x50008000 {
            compatible = "fire,rgb_led_green";
            reg = <
                0x50008000 0x4  /* GPIOG_MODER */
                0x50008004 0x4  /* GPIOG_OTYPER */
                0x50008008 0x4  /* GPIOG_OSPEEDR */
                0x5000800C 0x4  /* GPIOG_PUPDR */
                0x50008018 0x4  /* GPIOG_BSRR */
                0x50000A28 0x4  /* RCC_MP_AHB4ENSETR */
            >;
            status = "okay";
        };

        /* -------------------------------------------------
         * 蓝灯节点 (GPIOB 5)
         * 包含 GPIOB 的 5 个寄存器 + RCC 时钟寄存器
         * ------------------------------------------------- */
        rgb_led_blue@0x50003000 {
            compatible = "fire,rgb_led_blue";
            reg = <
                0x50003000 0x4  /* GPIOB_MODER */
                0x50003004 0x4  /* GPIOB_OTYPER */
                0x50003008 0x4  /* GPIOB_OSPEEDR */
                0x5000300C 0x4  /* GPIOB_PUPDR */
                0x50003018 0x4  /* GPIOB_BSRR */
                0x50000A28 0x4  /* RCC_MP_AHB4ENSETR */
            >;
            status = "okay";
        };
    };
};

驱动代码文件rgb_led.c

c 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

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

#include <linux/platform_device.h>

/*------------------字符设备内容----------------------*/
#define DEV_NAME "rgb_led"
#define DEV_CNT (1)

/*定义 led 资源结构体,保存获取得到的节点信息以及转换后的虚拟寄存器地址*/
struct led_resource
{
	struct device_node *device_node; //rgb_led_red的设备树节点
	void __iomem *va_MODER;
	void __iomem *va_OTYPER;
	void __iomem *va_OSPEEDR;
	void __iomem *va_PUPDR;
	void __iomem *va_BSRR;
};

static void __iomem *va_clkaddr;

static dev_t led_devno;					 //定义字符设备的设备号
static struct cdev led_chr_dev;			 //定义字符设备结构体chr_dev
struct class *class_led;				 //保存创建的类
struct device *device;					 // 保存创建的设备
struct device_node *rgb_led_device_node; //rgb_led的设备树节点结构体

/*定义 R G B 三个灯的led_resource 结构体,保存获取得到的节点信息*/
struct led_resource led_red;
struct led_resource led_green;
struct led_resource led_blue;

/*字符设备操作函数集,open函数*/
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{
	printk("\n open form driver \n");
	return 0;
}

/*字符设备操作函数集,write函数*/
static ssize_t led_chr_dev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{

	unsigned int register_data = 0; //暂存读取得到的寄存器数据
	unsigned char write_data; //用于保存接收到的数据

	int error = copy_from_user(&write_data, buf, cnt);
	if (error < 0)
	{
		return -1;
	}
	// 开启a、b、g的时钟 
	writel(0x43, va_clkaddr);
	//write_data & 0x04 找到对应的GPIO引脚
	/*设置 GPIOA13 输出电平*/
	//高 16 位 写入 1 (低电平)电压差 ---> 导致 灯开。 低 16 位 写入 1 (高电平)两端高电平 ---> 导致 灯关。(对应pin脚)
	if (write_data & 0x04)
	{
		register_data |= (0x01 << (13+16));
		writel(register_data, led_red.va_BSRR); // GPIOA13引脚输出低电平,红灯亮
	}
	else
	{
		register_data |= (0x01 << (13));
		writel(register_data, led_red.va_BSRR); // GPIOA13引脚输出高电平,红灯灭
	}

	/*设置 GPIOG2 输出电平*/
	if (write_data & 0x02)
	{
		register_data |= (0x01 << (2+16));
		writel(register_data, led_green.va_BSRR); // GPIOG2引脚输出低电平,绿灯亮
	}
	else
	{
		register_data |= (0x01 << (2));
		writel(register_data, led_green.va_BSRR); // GPIOG2引脚输出高电平,绿灯灭
	}

	/*设置 GPIOB5 输出电平*/
	if (write_data & 0x01)
	{
		register_data |= (0x01 << (5+16));
		writel(register_data, led_blue.va_BSRR); //GPIOB5引脚输出低电平,蓝灯亮
	}
	else
	{
		register_data |= (0x01 << (5));
		writel(register_data, led_blue.va_BSRR); //GPIOB5引脚输出高电平,蓝灯灭
	}

	return 0;
}

/*字符设备操作函数集*/
static struct file_operations led_chr_dev_fops =
	{
		.owner = THIS_MODULE,
		.open = led_chr_dev_open,
		.write = led_chr_dev_write,
};

/*----------------平台驱动函数集-----------------*/
static int led_probe(struct platform_device *pdv)
{

	int ret = -1; //保存错误状态码
	unsigned int register_data = 0;

	printk(KERN_EMERG "\t  match successed  \n");
	
	//设备树编写部分 就是先设备树插件的rgb_led节点
	//然后在rgb_led节点下编写rgb_led_red、rgb_led_green、rgb_led_blue子节点
	//最后在每个子节点下编写reg属性以及时钟属性
	//驱动程序部分 先获取rgb_led节点,然后分别获取rgb_led_red、rgb_led_green、rgb_led_blue子节点
	//最后获取每个子节点下的reg属性并转换为虚拟地址
	/*获取rgb_led的设备树节点*/
	rgb_led_device_node = of_find_node_by_path("/rgb_led");
	if (rgb_led_device_node == NULL)
	{
		printk(KERN_ERR "\t  get rgb_led failed!  \n");
		return -1;
	}

	/*获取rgb_led节点的红灯子节点*/
	led_red.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_red");
	if (led_red.device_node == NULL)
	{
		printk(KERN_ERR "\n get rgb_led_red_device_node failed ! \n");
		return -1;
	}


	/*获取 reg 属性并转化为虚拟地址*/
	led_red.va_MODER = of_iomap(led_red.device_node, 0);
	led_red.va_OTYPER = of_iomap(led_red.device_node, 1);
	led_red.va_OSPEEDR = of_iomap(led_red.device_node, 2);
	led_red.va_PUPDR = of_iomap(led_red.device_node, 3);
	led_red.va_BSRR = of_iomap(led_red.device_node, 4);
	va_clkaddr = of_iomap(led_red.device_node, 5);

	register_data = readl(va_clkaddr);
	// 开启a、b、g的时钟 
	register_data |= (0x43); // 开启a、b、g的时钟 
	writel(register_data, va_clkaddr);

	// 设置模式寄存器:输出模式
	register_data = readl(led_red.va_MODER);
	register_data &= ~((unsigned int)0X3 << (2 * 13));
	register_data |= ((unsigned int)0X1 << (2 * 13));
	writel(register_data,led_red.va_MODER);
	// 设置输出类型寄存器:推挽模式
	register_data = readl(led_red.va_OTYPER);
	register_data &= ~((unsigned int)0X1 << 13);
	writel(register_data, led_red.va_OTYPER);
	// 设置输出速度寄存器:高速
	register_data = readl(led_red.va_OSPEEDR);
	register_data &= ~((unsigned int)0X3 << (2 * 13));
	register_data |= ((unsigned int)0x2 << (2 * 13));
	writel(register_data, led_red.va_OSPEEDR);
	// 设置上下拉寄存器:上拉
	register_data = readl(led_red.va_PUPDR);
	register_data &= ~((unsigned int)0X3 << (2*13));
	register_data |= ((unsigned int)0x1 << (2*13));
	writel(register_data,led_red.va_PUPDR);
	// 设置置位寄存器:默认输出高电平
	register_data = readl(led_red.va_BSRR);
	register_data |= ((unsigned int)0x1 << (13));
	writel(register_data, led_red.va_BSRR);

	/*获取rgb_led节点的绿灯子节点*/
	led_green.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_green");
	if (led_green.device_node == NULL)
	{
		printk(KERN_ERR "\n get rgb_led_green_device_node failed ! \n");
		return -1;
	}

	/*获取 reg 属性并转化为虚拟地址*/
	led_green.va_MODER = of_iomap(led_green.device_node, 0);
	led_green.va_OTYPER = of_iomap(led_green.device_node, 1);
	led_green.va_OSPEEDR = of_iomap(led_green.device_node, 2);
	led_green.va_PUPDR = of_iomap(led_green.device_node, 3);
	led_green.va_BSRR = of_iomap(led_green.device_node, 4);

	// 设置模式寄存器:输出模式
	register_data = readl(led_green.va_MODER);
	register_data &= ~((unsigned int)0X3 << (2 * 2));
	register_data |= ((unsigned int)0X1 << (2 * 2));
	writel(register_data,led_green.va_MODER);
	// 设置输出类型寄存器:推挽模式
	register_data = readl(led_green.va_OTYPER);
	register_data &= ~((unsigned int)0X1 << 2);
	writel(register_data, led_green.va_OTYPER);
	// 设置输出速度寄存器:高速
	register_data = readl(led_green.va_OSPEEDR);
	register_data &= ~((unsigned int)0X3 << (2 * 2));
	register_data |= ((unsigned int)0x2 << (2 * 2));
	writel(register_data, led_green.va_OSPEEDR);
	// 设置上下拉寄存器:上拉
	register_data = readl(led_green.va_PUPDR);
	register_data &= ~((unsigned int)0X3 << (2*2));
	register_data |= ((unsigned int)0x1 << (2*2));
	writel(register_data,led_green.va_PUPDR);
	// 设置置位寄存器:默认输出高电平
	register_data = readl(led_green.va_BSRR);
	register_data |= ((unsigned int)0x1 << (2));
	writel(register_data, led_green.va_BSRR);

	/*获取rgb_led节点的蓝灯子节点*/
	led_blue.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_blue");
	if (led_blue.device_node == NULL)
	{
		printk(KERN_ERR "\n get rgb_led_blue_device_node failed ! \n");
		return -1;
	}

	/*获取 reg 属性并转化为虚拟地址*/
	led_blue.va_MODER = of_iomap(led_blue.device_node, 0);
	led_blue.va_OTYPER = of_iomap(led_blue.device_node, 1);
	led_blue.va_OSPEEDR = of_iomap(led_blue.device_node, 2);
	led_blue.va_PUPDR = of_iomap(led_blue.device_node, 3);
	led_blue.va_BSRR = of_iomap(led_blue.device_node, 4);

	// 设置模式寄存器:输出模式
	register_data = readl(led_blue.va_MODER);
	register_data &= ~((unsigned int)0X3 << (2 * 5));
	register_data |= ((unsigned int)0X1 << (2 * 5));
	writel(register_data,led_blue.va_MODER);
	// 设置输出类型寄存器:推挽模式
	register_data = readl(led_blue.va_OTYPER);
	register_data &= ~((unsigned int)0X1 << 5);
	writel(register_data, led_blue.va_OTYPER);
	// 设置输出速度寄存器:高速
	register_data = readl(led_blue.va_OSPEEDR);
	register_data &= ~((unsigned int)0X3 << (2 * 5));
	register_data |= ((unsigned int)0x2 << (2 * 5));
	writel(register_data, led_blue.va_OSPEEDR);
	// 设置上下拉寄存器:上拉
	register_data = readl(led_blue.va_PUPDR);
	register_data &= ~((unsigned int)0X3 << (2*5));
	register_data |= ((unsigned int)0x1 << (2*5));
	writel(register_data,led_blue.va_PUPDR);
	// 设置置位寄存器:默认输出高电平
	register_data = readl(led_blue.va_BSRR);
	register_data |= ((unsigned int)0x1 << (5));
	writel(register_data, led_blue.va_BSRR);

	/*---------------------注册 字符设备部分-----------------*/

	//第一步
	//采用动态分配的方式,获取设备编号,次设备号为0,
	//设备名称为rgb-leds,可通过命令cat  /proc/devices查看
	//DEV_CNT为1,当前只申请一个设备编号
	ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
	if (ret < 0)
	{
		printk("fail to alloc led_devno\n");
		goto alloc_err;
	}
	//第二步
	//关联字符设备结构体cdev与文件操作结构体file_operations
	led_chr_dev.owner = THIS_MODULE;
	cdev_init(&led_chr_dev, &led_chr_dev_fops);
	//第三步
	//添加设备至cdev_map散列表中
	ret = cdev_add(&led_chr_dev, led_devno, DEV_CNT);
	if (ret < 0)
	{
		printk("fail to add cdev\n");
		goto add_err;
	}

	//第四步
	/*创建类 */
	class_led = class_create(THIS_MODULE, DEV_NAME);

	/*创建设备*/
	device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);

	return 0;

add_err:
	//添加设备失败时,需要注销设备号
	unregister_chrdev_region(led_devno, DEV_CNT);
	printk("\n error! \n");
alloc_err:

	return -1;
}

static const struct of_device_id rgb_led[] = {
	{.compatible = "fire,rgb_led"},
	{/* sentinel */}};

/*定义平台设备结构体*/
struct platform_driver led_platform_driver = {
	.probe = led_probe,
	.driver = {
		.name = "rgb-leds-platform",
		.owner = THIS_MODULE,
		.of_match_table = rgb_led,
	}};

/*
*驱动初始化函数
*/
static int __init led_platform_driver_init(void)
{
	int DriverState;
	DriverState = platform_driver_register(&led_platform_driver);
	printk(KERN_EMERG "\tDriverState is %d\n", DriverState);
	return 0;
}

/*
*驱动注销函数
*/
static void __exit led_platform_driver_exit(void)
{
	/*取消物理地址映射到虚拟地址*/
	iounmap(va_clkaddr);

	iounmap(led_green.va_MODER);
	iounmap(led_green.va_OTYPER);
	iounmap(led_green.va_OSPEEDR);
	iounmap(led_green.va_PUPDR);
	iounmap(led_green.va_BSRR);

	iounmap(led_red.va_MODER);
	iounmap(led_red.va_OTYPER);
	iounmap(led_red.va_OSPEEDR);
	iounmap(led_red.va_PUPDR);
	iounmap(led_red.va_BSRR);

	iounmap(led_blue.va_MODER);
	iounmap(led_blue.va_OTYPER);
	iounmap(led_blue.va_OSPEEDR);
	iounmap(led_blue.va_PUPDR);
	iounmap(led_blue.va_BSRR);

	/*删除设备*/
	device_destroy(class_led, led_devno);		  //清除设备
	class_destroy(class_led);					  //清除类
	cdev_del(&led_chr_dev);						  //清除设备号
	unregister_chrdev_region(led_devno, DEV_CNT); //取消注册字符设备

	/*注销字符设备*/
	platform_driver_unregister(&led_platform_driver);

	printk(KERN_EMERG "HELLO WORLD exit!\n");
}

module_init(led_platform_driver_init);
module_exit(led_platform_driver_exit);

MODULE_LICENSE("GPL");

由于板子引脚可能被占用,因此引脚被占用后,设备树可能无法再加载或驱动中无法再申请对应的资源。到boot下的uEnv.txt中注销对应led插件加#,之后重启

编译好的文件同理放到板子/usr/lib/linux-image-4.19.94-stm-r1/overlays/目录下,然后uEnv.txt中进行注册

整体流程串联

html 复制代码
设备树插件、驱动、总线工作流程
1.设备树插件加载 (Overlay Loaded):
	把 .dtbo 加载到系统时,内核会解析它,发现里面描述了一个新硬件( fire,rgb-led)。
	内核会自动在 平台总线 上创建一个新的 "平台设备" (Platform Device)。
	compatible和具体硬件信息
2.驱动加载 (Driver Loaded):
	当insmod rgb_led.ko 时,驱动程序向 平台总线 注册自己。
	总线上多了一个驱动,里面有compatible信息
3.总线匹配 (Bus Match):
	平台总线发现新来的设备和驱动的Compatible一致。
	总线把它们俩"绑"在一起,并触发驱动的 probe 函数。
相关推荐
码农小韩10 小时前
基于Linux的C++学习——指针
linux·开发语言·c++·学习·算法
早日退休!!!11 小时前
ARM Cortex-M核 【保存上下文&恢复上下文】
arm开发·单片机·嵌入式硬件
wdfk_prog11 小时前
[Linux]学习笔记系列 -- [fs]seq_file
linux·笔记·学习
来自晴朗的明天11 小时前
差分控多少Ω阻抗
单片机·嵌入式硬件·硬件工程
Jay Chou why did11 小时前
wsl安装完无法进入wsl
linux
石头53012 小时前
Rocky Linux 9.6 docker k8s v1.23.17 kubeadm 高可用部署文档
linux
SystickInt12 小时前
mosbus复习总结(20260110)
stm32
点灯小铭12 小时前
基于单片机的多功能智能婴儿车设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
RisunJan12 小时前
Linux命令-ipcs命令(报告进程间通信(IPC)设施状态的实用工具)
linux·运维·服务器