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 函数。
相关推荐
梁正雄3 小时前
linux服务-Nginx+Tomcat+Redis之Session 共享 - 容器compose
linux·nginx·tomcat
fengyehongWorld3 小时前
Linux rsync命令
linux
明月清了个风4 小时前
工作笔记-----EEPROM偶发性读取错误
arm开发·笔记·单片机·嵌入式硬件
XINVRY-FPGA4 小时前
XCVU9P-2FLGC2104I Xilinx AMD Virtex UltraScale+ FPGA
嵌入式硬件·机器学习·计算机视觉·fpga开发·硬件工程·dsp开发·fpga
干啥都是小小白4 小时前
Linux C编程
linux·运维·服务器
hazy1k4 小时前
ESP32基础-Socket通信 (TCP/UDP)
c语言·单片机·嵌入式硬件·网络协议·tcp/ip·udp·esp32
赖small强4 小时前
【Linux 网络基础】WebSockets 技术指南
linux·网络·https·websockets·ping/pong
司铭鸿4 小时前
化学式解析的算法之美:从原子计数到栈的巧妙运用
linux·运维·服务器·算法·动态规划·代理模式·哈希算法
沟通QQ:276998854 小时前
虚拟同步机控制结构图](https://app.sxlcdn.com/upfile/2264/phi_153621_20_lyxqk2vj.png
驱动开发