目录
- 需求
- 一、设备树的概念
- 1、设备树的后缀名:
- 2、设备树的语法格式
- 3、设备树的属性(重要)
- 4、设备树格式举例
- 二、设备树所用函数
- 1、如何在内核层种获取设备树节点:
- 2、从设备树上获取 gpio 口的属性
- 3、获取节点上的属性只针对于字符串属性的
- 4、函数读取 np 结点中的 propname 属性的值,并将读取到的 u32 类型的值保存在 out_value 指向的内存中,函数的返回值表示读取到的 u32 类型的数据的个数。
- 三、设备树的编译与烧写
- 四、代码
- 五、现象
需求
利用设备树和字符设备驱动实现对LED等的控制。
一、设备树的概念
设备号:这里用到的是linux2.6动态创建的设备号。他是一个完整的设备号,这个完整的设备号里包含了主设备号和次设备号,这里他是一个 32 位的设备号,32 位里前 12 位是主设备号,后 20 位是次设备号,所以他的这个设备号的取值范围是比较大的。
设备树:他是描述硬件设备资源的文本文件,因为设备树里边的内容架构像树形结构,因此就被称为设备树。以前是没有设备树,之所以有设备树,是因为 linux 之父在 Linux 社区公开开骂,是因为内核太过于臃肿了,内核里的内容太过于复杂,大部分都是垃圾代码,Linux 本身就是开源免费的,其实他就提供一个 ARM 架构的框架,因为当今电子产品跟新换代太快了,所有的厂商都可以使用 linux 内核架构,他们各大厂商都会把自己公司代码,统统往内核塞,所以这些 Linux 社区里的大神看到 Linux 之父的愤怒,因此就搞了一个新
的内容出来,他就是设备树,设备树他就是一个文件,只不过这个文件是用来描述硬件设备资源的。
目前设备树的使用在驱动里是非常受欢迎的因为其使用简单,它大大的简化了你写驱动代码的架构。
其实驱动代码他在平台设备总线里的分为了两部分。
设备端:他就只提供硬件设备的资源 比如:中断资源 GPIO 资源等。
驱动端:就是驱动代码的框架使用杂项或 Linux2.6 创建设备号。他会和设备端进行 匹配,然后从设备端获取硬件的资源,来驱动对应的硬件,但是有了设备树之后就把设备端给替代了。
1、设备树的后缀名:
设备树他有自己的语法规则,就类似于一门语言,你编写设备树的时候需要遵循设备树的语法规则。
dts:就是咱们编写修改的文件 — 有点像你 C 语言的.c 文件
dtsi:他是设备的原始文件 — 一般是有厂商写的 — 有点像你 C 语言的.h 文
件
dtb:就是设备树编译生成的二进制文件 — 有点像你 C 语言的.o 文件
dtc:编译设备树的工具 — 有点像你 C 语言的 gcc 文件
2、设备树的语法格式
/dts-v1/; — 设备树的版本 — 一般是必须要有的
/ ---- 这个设备树文件的根 ,也就是起始的位置
他下边可以保护很多的子节点,子节点里可以包含在的子节点
/ {
属性名=属性值;
属性名=属性值;
子节点名 {
属性名=属性值;
属性名=属性值;
子节点名 {
属性名=属性值;
属性名=属性值;
};
};
};
3、设备树的属性(重要)
model:他是描述开发板信息的属性 ,主要是公司芯片的型号 它是一个字符串。
compatible:他是设备树属性里最重要的一个属性。他就是用来做匹配的,因为设备树是描述设备信息的,而设备信息是驱动代码使用的。驱动代码怎么找到设备树里的具体某一个设备 节点的信息,设备树里可以包含很多个节点信息,这里就通过 compatible 去找到这个节点他也是一个字符串,他也可以是多个字符串如compatible = “xyd-led”, “rk-led”;查找顺序是现按照 xyd-led,如果找不到就在往后找 rk-led。几乎每一个节点里都包含了此节点。
status:设备当前的一个状态 — 他的属性值也是一个字符串。
okay — 他是可以使用的
disabled — 禁用/失能
就是这个设备默认是否可以使用。
4、设备树格式举例
设备树有以下三种格式。
/ {
model = “xyd rk3588s leds gpios”
xydled1 {
compatible = “xyd-led1”;
led-gpios = <&gpio1 RK_PB4 GPIO_ACTIVE_LOW>;
status = “okay”;
};
xydled2: myled {
compatible = “xyd-led2”;
led-gpios = <&gpio0 RK_PB5 GPIO_ACTIVE_LOW>;
status = “okay”;
};
xydled3: myled@ff690000 {
compatible = “xyd-led2”;
led-gpios = <&gpio0 RK_PB5 GPIO_ACTIVE_LOW>;
status = “okay”;
};
};
二、设备树所用函数
1、如何在内核层种获取设备树节点:
一共有三种方法:用的比较多的是用属性进行查找。
struct device_node *of_find_node_by_name(struct device_node
*from, const char *name);
**struct device_node *of_find_compatible_node(struct device_node
*from, const char type, const char compatible)
struct device_node *of_find_node_by_path(const char *path)
函数功能:在设备树上寻找你指定的设备节点
函数原型:struct device_node of_find_compatible_node(struct device_nodefrom, const char *type,const char *compatible)
函数头文件:#include <linux/of.h>
函数参数:device_node:写 NULL 代表从头开始
type:写 NULL
compatible:设备节点上的 compatible 的属性的值
函数返回值:成功返回指向一个 struct device_node 指针 失败 NULL
2、从设备树上获取 gpio 口的属性
函数功能:从设备树上获取 gpio 口的属性
函数原型:int of_get_named_gpio(struct device_node *np,const char *propname, int index)
函数头文件:#include <linux/of_gpio.h>
函数参数:np:设备树节点的信息
propname:获取 gpio 口属性的名字
index:获取 gpio 口资源的编号 — 你要获取第几个资源
函数返回值:成功获取得到的 gpio 口的编号 失败返回负数
3、获取节点上的属性只针对于字符串属性的
函数功能:获取节点上的属性 — 针对于字符串属性的
函数原型:int of_property_read_string(struct device_node np,const charpropname, const char **out_string)
函数头文件:#include <linux/of.h>
函数参数:np:设备节点
propname:获取的节点属性的名字
out_string:保存获取得到的属性的值
函数返回值:成功返回 0 失败返回负数
4、函数读取 np 结点中的 propname 属性的值,并将读取到的 u32 类型的值保存在 out_value 指向的内存中,函数的返回值表示读取到的 u32 类型的数据的个数。
函数功能:函数读取 np 结点中的 propname 属性的值,并将读取到的 u32 类型的值保存在 out_value 指向的内存中,函数的返回值表示读取到的 u32 类型的数据的个数。
函数原型:int of_property_read_u32_index(const struct device_node *np,
const char *propname,u32 index,u32 *out_value)
函数头文件:#include <linux/of.h>
函数参数:np:设备节点
propname:节点里的那一个属性 — 属性的名字
index:获取这个属性里的一个值 — 如果一个默认就写 0
out_value:保存获取得到信息
函数返回值:成功返回 0 失败返回负数
三、设备树的编译与烧写
这里我使用瑞芯微编写好的脚本去编译的。
你在设备树里写好自己的设备节点信息之后返回 RK3588S 的顶层目录执行
./build.sh kernel ,就会默认就编译设备树文件了,并且他把编译生成的设备树的 dtb 文件给继承了到 boot.img 镜像里使用烧录工具单独去烧写 boot.img 即可里边就包括了你自己写的设备节点。
开发板设备树节点的位置
/sys/firmware/devicetree/base/xyd_led
/proc/device-tree/xyd_led
内核中的设备树节点:
dev下的设备节点:
四、代码
内核底层驱动:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/fs.h>
dev_t dev;
int all;
struct cdev mydev;
struct class *myclass=NULL;
int gpio_values[2]={0};
const char *out_string = NULL;
ssize_t myled_read (struct file *file, char __user *user, size_t size, loff_t *fd)
{
printk("myled read ok\n");
printk("读取数据成功\n");
return 0;
}
ssize_t myled_write (struct file *file, const char __user *user, size_t size, loff_t *fd)
{
printk("myled write ok\n");
printk("写入数据成功\n");
return 0;
}
int myled_open (struct inode *inode, struct file *fp)
{
gpio_set_value(gpio_values[0],1);
gpio_set_value(gpio_values[1],1);
printk("myled open ok\n");
printk("myled open 正确打开\n");
return 0;
}
int myled_close (struct inode *inode, struct file *fp)
{
gpio_set_value(gpio_values[0],0);
gpio_set_value(gpio_values[1],0);
printk("myled close ok\n");
printk("myled close 关闭正确\n");
return 0;
}
struct file_operations myfops={
.owner = THIS_MODULE,
.open = myled_open,
.release = myled_close,
.read=myled_read,
.write=myled_write,
};
static int __init myled_init(void)
{
struct device_node *node=of_find_compatible_node(NULL,NULL,"myqxj_led");
printk("节点的名字:%s\n",node->name);
gpio_values[0]=of_get_named_gpio(node,"led-gpios", 0);
gpio_values[1]=of_get_named_gpio(node,"led-gpios", 1);
printk("gpio_values[0]:%d\n",gpio_values[0]);
printk("gpio_values[1]:%d\n",gpio_values[1]);
of_property_read_string(node,"status", &out_string);
printk("out_string:%s\n",out_string);
if(strcmp("okay",out_string)!=0)
{
printk("设备节点信息不可用\n");
return -1;
}
gpio_request(gpio_values[0], "led1");
gpio_request(gpio_values[1], "led2");
gpio_direction_output(gpio_values[0], 1);
gpio_direction_output(gpio_values[1], 1);
all=alloc_chrdev_region(&dev,0, 1,"led");
if(all<0)
{
printk("alloc_chrdev_region error\n");
printk("动态创建失败\n");
return -1;
}
printk("主设备号:%d\n",MAJOR(dev));
printk("次设备号:%d\n",MINOR(dev));
cdev_init(&mydev,&myfops);
cdev_add(&mydev,dev,1);
myclass=class_create(THIS_MODULE,"class_led");//创建类
if(myclass == NULL)
{
printk("class_create error\n");
printk("class_create 类创建失败\n");
return -1;
}
device_create(myclass,NULL,dev,NULL,"myqxjled");
return 0;
}
static void __exit myled_exit(void)
{
device_destroy(myclass,dev);
class_destroy(myclass);
cdev_del(&mydev);
unregister_chrdev_region(dev,1);
gpio_free(gpio_values[0]);
gpio_free(gpio_values[1]);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
应用层:
五、现象
代码运行状态:
灯现象:灯亮灭交替闪烁。