linux驱动——设备树

1:初识设备树

1.1 什么是设备树,设备树的意义

        设备树(Device Tree)是 Linux 内核中用于描述硬件设备的一种数据结构。它为操作系统提供了一种抽象的方法,使其能够识别和配置硬件设备,而无需将硬件细节硬编码到内核中。设备树最常用于嵌入式系统和其他复杂硬件平台上,尤其是在 ARM 架构下。

        使用设备树的目的就是去精华、优化内核的重复代码。

设备树文件存放的位置:
        统一放在内核架构文件夹里面/boot/dts 里面
        以 32bit ARM 芯片 内核/arch/arm/boot/dts
        以 64bit ARM 芯片 内核/arch/arm64/boot/dts/rockchip
        我所使用的开发板为rk3588s所对应的设备文件:
                ls *dtb 当前设备树谁被编译成了 二进制文件
                确定我使用的设备文件:
                        rk3588s-yyt.dts
设备树的意义:
        降低了内核的重复性和臃肿性。

2:设备树的模型

        因为有了设备树之后,几乎芯片的所有的驱动,甚至是 CPU 的频率 时钟 电压都采用了这
种方式。写一个通用的驱动,通过设备树传参,带来不同的效果
设备树的模型类似 树状结构体 最开始处就是 根节点 ,往下延申各种节点信息

3:常见节点

在设备树文件中主要由分为三类文件:

dts:

        用户所修改裁剪的设备树文件

dtsi:

        相当于头文件->一般被dtsi包含

dtb:

        会被 DTC工具编译成二进制文件,编译完成后的文件名称一般和设备树一样。

设备树内部的内容分为两个关键的点:

       3. 1 设备树的所有的信息都被归纳为节点(node)

                以一个LED灯的为例:

led: led{
    compatible = "led";
    gpios =<&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>,
    <&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>;
    status = "okay";
};

节点一般用{}引用,节点都属于根节点内部的子节点。

        3.2 设备节点里面的内容

                节点里面的内容被称之为属性。

                compatible = "led";
                status = "okay";

        规范为:

                1、节点的属性结尾用;

                2、一个属性的多个属性之间用,分隔

                3、属性一般有两种可能:字符串 数字

        3.3 特殊节点  

设备树里面常见的特殊节点:
        / :
                所有的设备树的起始一定是 / 根节点
        aliases:
                定义一些节点的别名
        chosen:
                这个大部分情况下无用
        可以当作给内核的传参->类似 uboot 的 bootargs

4:常见属性

节点里面常见属性:
        name:(已被弃用)
                就是个标签作用,基本没有其他的作用了
        model:
                真标签,为了给别人看你写的设备的作用
                里面会写厂商的名字 芯片 型号....
        compatible:(最重要属性没有之一)
                他是节点的真标识,他也是驱动匹配的方法,表示、名字
                驱动怎么找到设备树从而获取设备树信息
                全靠 compatible ->字符串
        status: (重点之一)
                表示设备树的状态
                        "disalbed"->该信息失效/失能
                        "okay

5:查找节点

在内核中查找节点有三种接口函数:

函数的功能:获取设备树节点 通过 name
函数头文件: <linux/of.h>
函数的原型:
        

struct device_node *of_find_node_by_name(
                struct device_node *from,
                const char *name
        );


函数的参数:
        from:
                你要从哪个节点往下搜寻
                一般传入 NULL ->代表从 根节点往下搜索!
        name:
                你要搜寻的节点的名字
        举个节点为例子:
        hehehe {
                compatible = "hehe","test_hehe";
                status = "disabled";
                testnum = <0xFFFF>;
                };
节点名字 "hehehe"
函数返回值:
        返回的就是在内核的节点抽象的结构体

函数的功能:获取设备的节点->通过 compatible 获取节点
函数头文件:同上
函数的原型:
       

 struct device_node *of_find_compatible_node(
                struct device_node *from,
                const char *type,
                const char *compatible
        )


函数的参数:
        from:
                从哪个节点往下查找 /->NULL
        type:
                NULL
        compatible:
        你想查找的节点对应 compatible 属性值
函数返回值:
        返回值即为你要的节点


函数的功能:通过路径寻找节点
函数头文件:同上
函数的原型:
        

struct device_node *of_find_node_by_path(const char *path)


函数的参数:
        path:
                提供节点的绝对路径->/开始
举个例子:
        hehehe 节点为例路径: "/hehehe"
函数返回值:返回的即为节点

6:获取节点属性

函数的功能:通用获取节点属性的信息的函数
函数头文件:同上
函数的原型:
       

 property *of_find_property(
                const struct device_node *np,
                const char *name,
                int *lenp
                )


函数的参数:
        np:
                获取的属性来自哪个节点
        name:
                获取属性的属性名
        lenp:
                获取属性的长度->不提供
函数返回值:
                内核封装的属性返回值的结构体

函数的功能:读取一个 U32 类型属性值
函数头文件:同上
函数的原型:
       

 int of_property_read_u32_index(
                const struct device_node *np,
                const char *propname,
                u32 index,
                u32 *out_value
                )


函数的参数:
        np:
                设备节点
        propname:
                你要查找的属性名
        index:
                索引号,下标号
        out_value:
                把查找到属性值给他放入里面
        函数返回值:
                成功返回 0
                失败返回 非 0

函数的功能:获取属性的字符串信息
函数头文件:同上
函数的原型:
       

 int of_property_read_string(
                struct device_node *np,
                const char *propname,
                const char **out_string
        )


函数的参数:
        np:
                节点
        propname:
                属性名字
        out_string:
                就会把返回的字符串传入其中
函数返回值:
        成功返回 0
        失败返回 非 0

简单举例:用设备树写一个LED灯的驱动

#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/of.h"
#include "linux/cdev.h"
#include "linux/fs.h"
#include "linux/gpio.h"
#include "linux/of_gpio.h"
#include "linux/device/class.h"
#include "linux/device.h"
struct class  cls;
struct device_node * node;
dev_t devnum;
struct cdev  xydledcdev;
const struct file_operations ops;
struct xyd_led_gpio{
int gpio_num;
int gpio_flag;
char name[32];
};
struct xyd_led_gpio myxyd_led_info;
int led_open (struct inode *i, struct file *f)
{
	gpio_set_value(myxyd_led_info.gpio_num,myxyd_led_info.gpio_flag);
	return 0;
}
int led_close (struct inode *i, struct file *f)
{
	gpio_set_value(myxyd_led_info.gpio_num,!myxyd_led_info.gpio_flag);
	return 0;
}
static int +__init tree_led_init(void)
{
	//1:先确定设备树LED灯设备信息是否存在
	node = of_find_compatible_node(NULL, NULL,"xyd_led");
	if(node == NULL)
	{
		printk("未成功设置设备树信息\r\n");
		return -EINVAL;
	}
	//2:判断status状态
	const char *  out_string;
	int ret = of_property_read_string(node, "status", &out_string);
	if(ret!=0 || strcmp("okay",out_string))
	{
		printk("信息不全,或者未使能! \r\n");
		return -EINVAL;
	}
	//3.获取设备树的GPIO信息
	myxyd_led_info.gpio_num = of_get_named_gpio(node, "xyd-gpios", 0);
	if(myxyd_led_info.gpio_num < 0)
	{
		printk("该设备树未提供 GPIO 信息! \r\n");
		return -EINVAL;
	}
	//4.获取GPIO的有效点平
	enum of_gpio_flags flags =0;
	of_get_named_gpio_flags(node,"xyd-gpios" , 0,&flags);
	if(flags == OF_GPIO_ACTIVE_LOW)
	{
		myxyd_led_info.gpio_flag=0;	
	}
	else
	{
		myxyd_led_info.gpio_flag=1;
	}
	//5:封装设别名
	sprintf(myxyd_led_info.name,"xyd_led_%d",myxyd_led_info.gpio_num);
	//6:初始化GPIO
	gpio_request(myxyd_led_info.gpio_num,myxyd_led_info.name);
	gpio_direction_output(myxyd_led_info.gpio_num,!myxyd_led_info.gpio_flag);
	//7:申请一个设备号
	alloc_chrdev_region(&devnum, 0,1,myxyd_led_info.name);
	//8:初始化cdev
	ops.open = led_open;
	ops.release = led_close;
	ops.owner= THIS_MODULE;
	cdev_init(&xydledcdev,&ops );
	//9:添加到内核
	cdev_add(&xydledcdev,devnum,1);
	//10:生成类结构体
	cls = class_create(THIS_MODULE,myxyd_led_info.name);
	//11:生成设备文件
	device_create(cls, NULL,devname,NULL,myxyd_led_info.name);
	
	return 0;
}
static void __exit tree_led_exit(void)
{
	//根据倒序的思想
	//1:销毁设备文件
	device_destroy(cls, myxyd_led_info.gpio_num);
	//2:销毁类文件
	class_destroy(cls);
	//3:从内核中删除
	cdev_del(&xydledcdev);
	//4:释放设备号
	unregister_chrdev_region(devnum, 1);
	//5:注销GPIO设备
	gpio_free(myxyd_led_info.gpio_num);
	
	
}
module_init(tree_led_init);
module_exit(tree_led_exit);
MODULE_LICENSE("GPL");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值