【from 一只嵌入式爱好者】Linux字符设备驱动详解七(“插件“设备树实现RGB灯驱动)

原文地址:https://blog.csdn.net/weixin_45905650/article/details/121597351


前言

本文主要来自正点原子、野火Linux教程及本人理解,若有侵权请及时联系本人删除。

正文

Device Tree Overlays:"插件"设备树

传统设备树

批量管理硬件资源,机制僵化

"插件"设备树

模块化管理硬件资源,灵活定制

使用前提

  • 内核配置
    • CONFIG_OF_OVERLAY = y
    • CONFIG_OF_CONFIGFS = y
  • 挂载ConfigFS
mount x /sys/kernel/config -t configfs

案例说明

设备树:foo.dts

/ {
	compatible = "corp,foo";
	
	/* On chip peripherals */
	ocp: ocp {
		/* peripherals that are always instantiated */
		peripheral1 { ... };
	}
};

“插件”设备树:bar.dts

/dts-v1/;
/plugin/;
/ {
	....	
	fragment@0 {
		target = <&ocp>;
		__overlay__ {
			/* bar peripheral */
			bar {
				compatible = "corp,bar";
				... /* various properties and child nodes */
			}
		};
	};
};
  • /dts-v1/ :指定dts版本号
  • /plugin/:允许设备树中引用未定义的节点
  • target = <&xxx>:指定"插件"设备树的父节点
  • target-path = “xxx”:指定"插件"设备树的父节点路径

设备树+“插件设备树”:foo.dts + bar.dts

/ {
		compatible = "corp,foo";

		/* shared resources */
		res: res {
		};

		/* On chip peripherals */
		ocp: ocp {
			/* peripherals that are always instantiated */
			peripheral1 { ... };

			/* bar peripheral */
			bar {
				compatible = "corp,bar";
				... /* various properties and child nodes */
			}
		}
	};

编译方式

./scripts/dtc/dtc -I dts -O dtb -o xxx.dtbo arch/arm/boot/dts/xxx.dts // 编译 dts 为 dtbo
./scripts/dtc/dtc -I dtb -O dts -o xxx.dts arch/arm/boot/dts/xxx.dtbo // 反编译 dtbo 为 dts

APT下载dtc工具

sudo apt install device-tree-compiler

使用方式

内核运行状态加载(通用)

  1. 在/sys/kernel/config/device-tree/overlays/下创建一个新目录:
mkdir /sys/kernel/config/device-tree/overlays/xxx
  1. 将dtbo固件echo到path属性文件中(第一种方法)
echo xxx.dtbo >/sys/kernel/config/device-tree/overlays/xxx/path

或者将dtbo的内容cat到dtbo属性文件(第二种方法)

cat xxx.dtbo >/sys/kernel/config/device-tree/overlays/xxx/dtbo
  1. 节点将被创建,查看内核设备树
ls /proc/device-tree
  1. 删除"插件"设备树
rmdir /sys/kernel/config/device-tree/overlays/xxx

uboot加载(野火linux开发板)

修改/boot/uEnv.txt

"插件"设备树实现RGB灯驱动

设备树添加节点信息

RGB灯的相关寄存器

/*
*CCM_CCGR1                         0x020C406C
*IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04  0x020E006C
*IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04  0x020E02F8
*GPIO1_GD                          0x0209C000
*GPIO1_GDIR                        0x0209C004
*/


/*
*CCM_CCGR3                         0x020C4074
*IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC   0x020E01E0
*IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC   0x020E046C
*GPIO4_GD                          0x020A8000
*GPIO4_GDIR                        0x020A8004
*/


/*
*CCM_CCGR3                         0x020C4074
*IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC   0x020E01DC
*IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC   0x020E0468
*GPIO4_GD                          0x020A8000
*GPIO4_GDIR                        0x020A8004
*/
	/*添加led节点*/
	rgb_led{
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "fire,rgb_led";

		/*红灯节点*/
		ranges;
		rgb_led_red@0x020C406C{
			reg = <0x020C406C 0x00000004
			       0x020E006C 0x00000004
			       0x020E02F8 0x00000004
				   0x0209C000 0x00000004
			       0x0209C004 0x00000004>;
			status = "okay";
		};

		/*绿灯节点*/
		rgb_led_green@0x020C4074{
			reg = <0x020C4074 0x00000004
			       0x020E01E0 0x00000004
			       0x020E046C 0x00000004
				   0x020A8000 0x00000004
			       0x020A8004 0x00000004>;
			status = "okay";
		};

		/*蓝灯节点*/
		rgb_led_blue@0x020C4074{
			reg = <0x020C4074 0x00000004
			       0x020E01DC 0x00000004
			       0x020E0468 0x00000004
				   0x020A8000 0x00000004
			       0x020A8004 0x00000004>;
			status = "okay";
		};
	};

reg属性内存映射

of_iomap()函数
将reg属性值的物理地址转化为虚拟地址

void __iomem *of_iomap(struct device_node *np, int index)

参数:

  • np:device_node表示的节点
  • index:通常情况下reg属性包含多段,index 用于指定映射那一段,标号从0开始。

代码示例

以野火代码为例
led.dts

/dts-v1/;
/plugin/;
/ {
	fragment@0 {
		target-path = "/";
		__overlay__ {
			/* bar peripheral */
			rgb_led{
            #address-cells = <1>;
            #size-cells = <1>;
            compatible = "fire,rgb_led";

            /*红灯节点*/
            ranges;
            rgb_led_red@0x020C406C{
                reg = <0x020C406C 0x00000004
                    0x020E006C 0x00000004
                    0x020E02F8 0x00000004
                    0x0209C000 0x00000004
                    0x0209C004 0x00000004>;
                status = "okay";
            };

            /*绿灯节点*/
            rgb_led_green@0x020C4074{
                reg = <0x020C4074 0x00000004
                    0x020E01E0 0x00000004
                    0x020E046C 0x00000004
                    0x020A8000 0x00000004
                    0x020A8004 0x00000004>;
                status = "okay";
            };

            /*蓝灯节点*/
            rgb_led_blue@0x020C4074{
                reg = <0x020C4074 0x00000004
                    0x020E01DC 0x00000004
                    0x020E0468 0x00000004
                    0x020A8000 0x00000004
                    0x020A8004 0x00000004>;
                status = "okay";
            };
        };
		};
	};
};

dts_led.c,该代码与上一篇一样

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/string.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 *virtual_CCM_CCGR;
	void __iomem *virtual_IOMUXC_SW_MUX_CTL_PAD;
	void __iomem *virtual_IOMUXC_SW_PAD_CTL_PAD;
	void __iomem *virtual_DR;
	void __iomem *virtual_GDIR;
};

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)
{

	int ret,error;
	unsigned int register_data = 0; //暂存读取得到的寄存器数据
	unsigned char receive_data[10]; //用于保存接收到的数据
	unsigned int write_data; //用于保存接收到的数据

	if(cnt>10)
			cnt =10;

	error = copy_from_user(receive_data, buf, cnt);
	if (error < 0)
	{
		return -1;
	}

	ret = kstrtoint(receive_data, 16, &write_data);
	if (ret) {
		return -1;
        }

	/*设置 GPIO1_04 输出电平*/
	if (write_data & 0x04)
	{
		register_data = ioread32(led_red.virtual_DR);
		register_data &= ~(0x01 << 4);
		iowrite32(register_data, led_red.virtual_DR); // GPIO1_04引脚输出低电平,红灯亮
	}
	else
	{
		register_data = ioread32(led_red.virtual_DR);
		register_data |= (0x01 << 4);
		iowrite32(register_data, led_red.virtual_DR); // GPIO1_04引脚输出高电平,红灯灭
	}

	/*设置 GPIO4_20 输出电平*/
	if (write_data & 0x02)
	{
		register_data = ioread32(led_green.virtual_DR);
		register_data &= ~(0x01 << 20);
		iowrite32(register_data, led_green.virtual_DR); // GPIO4_20引脚输出低电平,绿灯亮
	}
	else
	{
		register_data = ioread32(led_green.virtual_DR);
		register_data |= (0x01 << 20);
		iowrite32(register_data, led_green.virtual_DR); // GPIO4_20引脚输出高电平,绿灯灭
	}

	/*设置 GPIO4_19 输出电平*/
	if (write_data & 0x01)
	{
		register_data = ioread32(led_blue.virtual_DR);
		register_data &= ~(0x01 << 19);
		iowrite32(register_data, led_blue.virtual_DR); //GPIO4_19引脚输出低电平,蓝灯亮
	}
	else
	{
		register_data = ioread32(led_blue.virtual_DR);
		register_data |= (0x01 << 19);
		iowrite32(register_data, led_blue.virtual_DR); //GPIO4_19引脚输出高电平,蓝灯灭
	}

	return cnt;
}

/*字符设备操作函数集*/
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_ALERT "\t  match successed  \n");

	/*获取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.virtual_CCM_CCGR = of_iomap(led_red.device_node, 0);
	led_red.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_red.device_node, 1);
	led_red.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_red.device_node, 2);
	led_red.virtual_DR = of_iomap(led_red.device_node, 3);
	led_red.virtual_GDIR = of_iomap(led_red.device_node, 4);

	/*初始化红灯*/
	register_data = ioread32(led_red.virtual_CCM_CCGR);
	register_data |= (0x03 << 26);
	iowrite32(register_data, led_red.virtual_CCM_CCGR); //开启时钟

	register_data = ioread32(led_red.virtual_IOMUXC_SW_MUX_CTL_PAD);
	register_data &= ~(0xf << 0);
	register_data |= (0x05 << 0);
	iowrite32(register_data, led_red.virtual_IOMUXC_SW_MUX_CTL_PAD); //设置复用功能

	register_data = ioread32(led_red.virtual_IOMUXC_SW_PAD_CTL_PAD);
	register_data = (0x10B0);
	iowrite32(register_data, led_red.virtual_IOMUXC_SW_PAD_CTL_PAD); //设置PAD 属性

	register_data = ioread32(led_red.virtual_GDIR);
	register_data |= (0x01 << 4);
	iowrite32(register_data, led_red.virtual_GDIR); //设置GPIO1_04 为输出模式

	register_data = ioread32(led_red.virtual_DR);
	register_data |= (0x01 << 4);
	iowrite32(register_data, led_red.virtual_DR); //设置 GPIO1_04 默认输出高电平






	/*获取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.virtual_CCM_CCGR = of_iomap(led_green.device_node, 0);
	led_green.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_green.device_node, 1);
	led_green.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_green.device_node, 2);
	led_green.virtual_DR = of_iomap(led_green.device_node, 3);
	led_green.virtual_GDIR = of_iomap(led_green.device_node, 4);

	/*初始化绿灯*/
	register_data = ioread32(led_green.virtual_CCM_CCGR);
	register_data |= (0x03 << 12);
	iowrite32(register_data, led_green.virtual_CCM_CCGR); //开启时钟

	register_data = ioread32(led_green.virtual_IOMUXC_SW_MUX_CTL_PAD);
	register_data &= ~(0xf << 0);
	register_data |= (0x05 << 0);
	iowrite32(register_data, led_green.virtual_IOMUXC_SW_MUX_CTL_PAD); //设置复用功能

	register_data = ioread32(led_green.virtual_IOMUXC_SW_PAD_CTL_PAD);
	register_data = (0x10B0);
	iowrite32(register_data, led_green.virtual_IOMUXC_SW_PAD_CTL_PAD); //设置PAD 属性

	register_data = ioread32(led_green.virtual_GDIR);
	register_data |= (0x01 << 20);
	iowrite32(register_data, led_green.virtual_GDIR); //设置GPIO4_IO20 为输出模式

	register_data = ioread32(led_green.virtual_DR);
	register_data |= (0x01 << 20);
	iowrite32(register_data, led_green.virtual_DR); //设置 GPIO4_IO20 默认输出高电平






	/*获取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.virtual_CCM_CCGR = of_iomap(led_blue.device_node, 0);
	led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_blue.device_node, 1);
	led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_blue.device_node, 2);
	led_blue.virtual_DR = of_iomap(led_blue.device_node, 3);
	led_blue.virtual_GDIR = of_iomap(led_blue.device_node, 4);

	/*初始化蓝灯*/
	register_data = ioread32(led_blue.virtual_CCM_CCGR);
	register_data |= (0x03 << 12);
	iowrite32(register_data, led_blue.virtual_CCM_CCGR); //开启时钟

	register_data = ioread32(led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD);
	register_data &= ~(0xf << 0);
	register_data |= (0x05 << 0);
	iowrite32(register_data, led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD); //设置复用功能

	register_data = ioread32(led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD);
	register_data = (0x10B0);
	iowrite32(register_data, led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD); //设置PAD 属性

	register_data = ioread32(led_blue.virtual_GDIR);
	register_data |= (0x01 << 19);
	iowrite32(register_data, led_blue.virtual_GDIR); //设置GPIO4_IO19 为输出模式

	register_data = ioread32(led_blue.virtual_DR);
	register_data |= (0x01 << 19);
	iowrite32(register_data, led_blue.virtual_DR); //设置 GPIO4_IO19 默认输出高电平








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

	//第一步
	//采用动态分配的方式,获取设备编号,次设备号为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_ALERT "\tDriverState is %d\n", DriverState);
	return 0;
}

/*
*驱动注销函数
*/
static void __exit led_platform_driver_exit(void)
{
	/*取消物理地址映射到虚拟地址*/
	iounmap(led_green.virtual_CCM_CCGR);
	iounmap(led_green.virtual_IOMUXC_SW_MUX_CTL_PAD);
	iounmap(led_green.virtual_IOMUXC_SW_PAD_CTL_PAD);
	iounmap(led_green.virtual_DR);
	iounmap(led_green.virtual_GDIR);

	iounmap(led_red.virtual_CCM_CCGR);
	iounmap(led_red.virtual_IOMUXC_SW_MUX_CTL_PAD);
	iounmap(led_red.virtual_IOMUXC_SW_PAD_CTL_PAD);
	iounmap(led_red.virtual_DR);
	iounmap(led_red.virtual_GDIR);

	iounmap(led_blue.virtual_CCM_CCGR);
	iounmap(led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD);
	iounmap(led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD);
	iounmap(led_blue.virtual_DR);
	iounmap(led_blue.virtual_GDIR);

	/*删除设备*/
	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_ALERT "led_platform_driver exit!\n");
}

module_init(led_platform_driver_init);
module_exit(led_platform_driver_exit);

MODULE_LICENSE("GPL");

/**/

总结

将led.dts编译为rgb.dtbo插件设备树,将传统设备树和插件设备树都加载到内核中,加载完成后就使用以下命令查看设备树,此时可以在设备数中看到新增了rgb_led节点。

ls /sys/firmware/devicetree/base
或者
ls /proc/device-tree

再编译dts_led.c源文件为dto.led.ko内核模块并加载进内核。这时就有了/dev/rgb_led节点,最后向/dev/rgb_led节点写入数据就能控制rgb灯了。

sudo sh -c "ecoh '1' >/dev/rgb_led"
亮蓝灯
sudo sh -c "ecoh '2' >/dev/rgb_led"
亮绿灯
sudo sh -c "ecoh '4' >/dev/rgb_led"
亮红灯
sudo sh -c "ecoh '7' >/dev/rgb_led"
全亮
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嵌入式Linux下,设备树(device tree)用来描述硬件平台的各种资源,Linux内核在启动过程中,会解析设备树,获取各种硬件资源来初始化硬件。设备树的overlay功能是指可以在系统运行期间动态修改设备树。一般情况下,如上图所示,设备树经过DTC编译器编译为二进制的hello.dtb文件,加载到内存,随Linux内核一起启动后,一般就无法更改了。如果我们想修改设备树,需要修改hello.dts文件文件,重新编译成二进制文件:hello.dtb,然后重新启动内核,重新解析。有了设备树的overlay功能,省去了设备树的重新编译和内核重启,我们可以直接编写一个设备树插件:overlay.dts,编译成overlay.dtbo后,直接给设备树“打补丁”,在运行期间就可以动态添加节点、修改节点...设备树的overlay功能,在很多场合都会用得到,会让我们的开发更加方便:外界插拔设备,无法在设备树中预先描述:耳机树莓派 + FPGA开发板基于I2C的温度传感器管脚的重新配置:PIN multiplexing修改bootcmd、分区...设备树的overlay功能,目前还没有加入到内核mainline(linux-5.10.x),但目前有些开发板和配套的BSP已经支持了,支持在系统运行期间动态修改设备树文件。如果你手头的开发板或内核平台还没有支持device tree overlay,可以学习本期课程,学习内核中设备树overlay的实现原理,如何给内核打补丁,使内核支持设备树的overlay功能。有了本期课程的学习基础,明白了设备树overlay的实现原理和运行机制,你就可以尝试在自己的开发板平台上实现这个功能了。本期课程的主要内容如下:在开发板上如何实现设备树的overlay功能Configfs文件系统的配置与挂载Configfs编程接口如何编写设备树 overlay插件设备树 overlay的编译和运行设备树overlay运行机制分析本期课程适合哪些人学习:嵌入式驱动工程师嵌入式BSP工程师嵌入式软件工程师想从事嵌入式开发的同学全网首家讲解设备树overlay的视频教程。   

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值