四、(正点原子)Linux设备树

一、概念

        1、什么是设备树?

        设备树(Device Tree),就是具有树形结构的设备统一管理。描述设备树的文件叫做DTS(Device Tree Source)这个DTS文件采用树形结构描述板级设备,也就是开发板上的设备信息。例如:

         树的主干就是系统总线, IIC 控制器、 GPIO 控制器、 SPI 控制器等都是接到系统主线上的分支。IIC 控制器有分为 IIC1IIC2 两种,其中 IIC1 上接了 FT5206 AT24C02这两个 IIC 设备, IIC2 上只接了 MPU6050 这个设备。

        2、设备树的由来        

        DTS 文件的主要功能就是按照上图所示的结构来描述板子上的设备信息, DTS 文件描述设备信息是有相应的语法规则要求的,在3.x版本以前的Linux内核中ARM架构并没有采用设备树。以前要描述ARM架构的板级信息是在Linux内核源码中arch/arm/mach-xxxarch/arm/plat-xxx 文件夹创建一个.c的文件存放板级的信息。

        但是一个芯片能制作成很多的板子,比如我们所知道的st公司的F4系列的芯片就拓展了很多的板子。一个板子对应一个.c文件。导致Linux内核下的板级信息非常多,导致Linux内核冗余。Linux之父linus就发火了,从此之后ARM社区就引入了设备树来描述板级信息,将这些内容从Linux内核中分离开,用一个专属的文件格式来描述:设备树(文件扩展名.dts)。

        一个SOC能制作出很多不同的板子,但是不同的板子肯定也有共同的信息,将这些共同的信息提取出来作为一个通用的文件,其他的设备树(.dts)文件直接引用这个通用的文件即可,这个通用的文件就是.dtsi文件,类似于C语言中的头文件。

二、DTS、DTB和DTC

        1、概念

        DTS:设备树的源码文件。       

        DTB:DTS编译后得到的二进制文件。

        DTC:DTS要编译从DTB所使用到的编辑器。

        可以类比于C语言,.c文件相当于设备树的源文件(DTS),DTB文件相当于.c文件编译链接后的二进制文件,而.c文件编译链接需要编辑器,比如gcc编辑器。DTC就相当于gcc编辑器。

注:如果只编辑设备树的话建议使用命令:make dtsb

        2、DTS语法

        DTS语法就是用来编写.dts的规定,比如C语言的编写规定一样。DTS语法非常人性化,是一种ASCII文本文件,不管是阅读还是修改都挺方便。参考《Power_ePAPR_APPROVED_v1.12

        ①、.dtsi头文件

        我们打开前面我们移植的NXP官方的Linux内核的源码,找到我们添加的阿尔法开发板的.dts文件:

        .dtsi头文件和C语言中的.h文件一样,可以使用#include来包含,不仅可以应用.dtsi文件,还可以引用.dts文件。

        一般.dtsi文件描述的是SOC外部的信息,比如:CPU架构、主频、外设寄存器地址范围,中断等等,而.dts文件就具体到一个外设下的具体的设备或者传感器。

        ②、设备节点

        设备树采用属性结构来描述板子上的设备信息的文件,每个设备都是一个节点,每个节点都通过属性信息来描述结点信息,比如说我们前面移植添加的imx6ull-alientek-emmc.dts文件中:

        backlight节点,它描述LCD背光的信息, 它的前面还有"/"表示根节点。compatiblepwms灯就是backlight节点的属性信息,来描述这个节点的信息。

        在imx6ull-alientek-emmc.dts中引用头文件imx6ull.dtsi,在imx6ull.dtsi中也有根节点"/",那么这两个根节点设备树同一个呢?会不会有冲突呢?,其实这两个节点都是同一个节点,最终这个"/"根节点会合并成一个,里面的属性信息也会合并。

        设备树节点命名:

node-name@unit-address

         节点名字的格式:node-name@unit-address,"node-name"是节点名字,是ASCII字符串,能够清晰描述设备的功能。"unit-address"一般表示设备的地址或者寄存器首地址,如果某个节点没有地址或者寄存器,"unit-address"可以不要。如:cpu@0、interruptcontroller@00a01000

        其中,有一些节点如图:

         这就是另一种节点的命名方式,":"前面表示节点标签(label)":"后面才是节点名字。

label: node-name@unit-address

        引入 label 的目的就是为了方便访问节点,可以直接通过&label 来访问这个节点,比如通过
&cpu0 就可以访问“cpu@0”这个节点,而不需要输入完整的节点名字。

        ③、标准属性

        属性用来描述节点信息,每个节点都有不同的属性,用户可以自定义属性,除了用户自己定义的属性,有很多的标准属性。

                1、compatible属性

                compatible属性也叫兼容性属性,非常重要!compatible属性的值是一个字符串列表,compatible 属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序, compatible 属性的值格式如下所示:

"manufacturer,model"

                manufacturer:表示厂商。

                model:表示模块对应驱动的名字。

                根节点的compatible属性可以知道我们所使用的设备,一般第一个值描述了所使用的硬件设备名字。第二个值描述了设备所使用的 SOC。Linux 内核会通过根节点的 compoatible 属性查看是否支持此设备,如果支持的话设备就会启动 Linux 内核。否者不启动。

                比如:

                sound节点 表示开发板的音频设备节点,fsl表示厂商为飞思卡尔,imx6ul-evk-wm8960imx-audio-wm8960表示驱动模块的名字。sound这个设备首先使用第一个兼容值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查。
                一般驱动程序文件都会有一个 OF 匹配表(of_device_id),此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。比如在文件 imx-wm8960.c 中有如下内容:

632 static const struct of_device_id imx_wm8960_dt_ids[] = {
633     { .compatible = "fsl,imx-audio-wm8960", },
634     { /* sentinel */ }
635 };
636 MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);
637
638 static struct platform_driver imx_wm8960_driver = {
639     .driver = {
640         .name = "imx-wm8960",
641         .pm = &snd_soc_pm_ops,
642         .of_match_table = imx_wm8960_dt_ids,
643     },
644     .probe = imx_wm8960_probe,
645     .remove = imx_wm8960_remove,
646 };

                 数组 imx_wm8960_dt_ids 就是 imx-wm8960.c 这个驱动文件的匹配表,此匹配表只有一个匹配值“fsl,imx-audio-wm8960”。如果在设备树中有哪个节点的 compatible 属性值与此相等,那么这个节点就会使用此驱动文件。

                2、model属性

                model 属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的,比如前面的sound节点:model = "wm8960-audio"

                3、status属性

                用来表示设备的状态,属性值也是字符串。

                 4、#address-cells和#size-cells属性

                这两个属性的值都是无符号32位整型,#address-cells#size-cells 这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。 #address-cells 属性值决定了子节点​​​​​​​ reg 属性中地址信息所占用的字长(32 位), #size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位)。 #address-cells#size-cells 表明了子节点应该如何编写 reg 属性值,一般 reg 属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度, reg 属性的格式一为:

reg = <address1 length1 address2 length2 address3 length3……>

        每个“address length”组合表示一个地址范围,其中 address 是起始地址, length 是地址长度, #address-cells 表明 address 这个数据所占用的字长, #size-cells 表明 length 这个数据所占用的字长,比如:
      

        在regulators这个节点中,#adddress-cells为1,表示regulator的子节点的reg起始地址数据所占的字长为1个字长。#size-cells表示reg的地址长度为0个字长。所以在子结点:reg_can_3v3中,reg <0> ,0表示这个起始地址为0,没有设置地址长度。

        再例如:

        在aips1这个节点中#adddress-cells为1 子节点的起始地址所占的长度为1.#size-cells为1表示子节点的地址长度为1字长。所以在spba-bus@02000000节点,reg<0x02000000 0x4000>因为父节点设置了#address-cells = <1>, #size-cells = <1>, 起始地址= 0x02280000, 地址长度= 0x4000,相当于设置了起始地址为 0x02280000,地址长度为 0x40000。

                5、reg属性

                前面也说过了reg属性的值一般是(起始地址,地址长度)。reg属性一般用来描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。如果 ranges 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换

                6、ranges属性

                ranges是一个地址映射/转换表,ranges属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵。

                child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长。

                parent-bus-address:父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长。

                length:子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长。

                7、name属性

              name 属性值为字符串, name 属性用于记录节点名字, name 属性已经被弃用,不推荐使用name 属性,一些老的设备树文件可能会使用此属性。

三、设备树常用OF操作函数

        设备树描述了设备的详细信息,这些信息包括数字类型的、字符串类型的、数组类型的,
我们在编写驱动的时候需要获取到这些信息。比如设备树使用 reg 属性描述了某个外设的寄存
器地址为 0X02005482,长度为 0X400,我们在编写驱动的时候需要获取到 reg 属性的0X02005482 和 0X400 这两个值,然后初始化外设。 Linux 内核给我们提供了一系列的函数来获
取设备树中的节点或者属性信息,这一系列的函数都有一个统一的前缀“of_”,所以在很多资料里面也被叫做 OF 函数。这些 OF 函数原型都定义在 include/linux/of.h 文件

        1、查找结点的OF函数

        设备都是以节点的形式“挂”到设备树上的,因此要想获取这个设备的其他属性信息,必须先获取到这个设备的节点。 Linux 内核使用 device_node 结构体来描述一个节点,此结构体定义在文件 include/linux/of.h 中定义:

        通过节点的结构体,于查找相关的OF函数有5个 :

                ①、of_find_node_by_name函数

                通过节点名字查找指定节点,函数原型:

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

                from: 开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。

                name:要查找的节点名字。

                返回值:找到的节点,如果为NULL表示查找失败。

                使用方法:

                ②、of_find_node_by_type函数

                通过 device_type 属性查找指定的节点,函数原型:

struct device_node *of_find_node_by_type(struct device_node *from,
	                                        const char *type);

                from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。

                type:要查找的节点对应的type字符串,也就是device_type属性值。

                返回值:找到的节点,如果为NULL表示查找失败。

                使用方法:

                ③、of_find_compatible_node函数

                根据device_typecompatible这两个属性值查找指定的节点,函数原型

static inline struct device_node *of_find_compatible_node(
						struct device_node *from,
						const char *type,
						const char *compat)

                 from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。

                 type:要查找的节点对应的type字符串,也就是device_type属性值。可以为 NULL,表示忽略掉 device_type 属性。

                compat:要查找的节点所对应的 compatible 属性列表。

                返回值:找到的节点,如果为NULL表示查找失败。

                使用方法:

                ④、of_find_matching_node_and_match函数 

                通过 OF匹配表中of_device_id 来查找指定的结点。函数原型:

struct device_node *of_find_matching_node_and_match(
	                                    struct device_node *from,
	                                    const struct of_device_id *matches,
	                                    const struct of_device_id **match);

                 from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。

                matches:of_device_id匹配表,也就是在这个匹配表里面查找节点。

                match:找到的匹配的of_device_id

                返回值:找到的节点,如果为NULL表示查找失败。

                使用方法:

                 ⑤、of_find_node_by_path函数

                通过路径来查找指定的节点。函数原型:

static inline struct device_node *of_find_node_by_path(const char *path)

                path:带有全路径的节点名,一般从/根节点开始 

                返回值:找到的节点,如果为NULL表示查找失败。

                使用方法:

        2、查找父/子节点的OF函数

                  ①、of_get_parent函数

                用于获取指定节点的父节点(如果有父节点的话),函数原型:

struct device_node *of_get_parent(const struct device_node *node);

                 node:要查找的父节点的节点。

                返回值:找到的父节点,如果没有找到返回NULL。

                ②、of_get_next_child函数

                用迭代的方式查找子节点,函数原型:

struct device_node *of_get_next_child(const struct device_node *node,
					                   struct device_node *prev);

                node:要查找的节点的父节点。

                prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始。
                返回值: 找到的下一个子节点,如果没有找到返回NULL。
                使用方法:

        3、提取属性值的OF函数

                节点的属性信息里面保存了驱动所需要的内容,因此对属性值的提取非常重要,Linux 内
核中使用结构体 property 表示属性,此结构体同样定义在文件 include/linux/of.h 中:

                 ①、of_find_property函数

                用于查找指定的属性,函数原型:

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

                np:设备节点。

                name:要查找的属性的名字。

                lenp:属性值的字节数。

                返回值:找到的属性。

                使用方法:

                ②、of_property_count_elems_of_size函数

                用于获取属性中元素的数量,比如 reg 属性值是一个数组,那么使用此函数可以获取到这个数组的大小,函数原型:

int of_property_count_elems_of_size(const struct device_node *np,
				                    const char *propname, 
                                    int elem_size);

                np: 设备节点。

                propname:需要统计元素数量的属性名字。

                elem_size:元素长度,一般使用sizeof函数求元素长度。

                返回值:得到的元素数量。

                使用方法:

                ③、 of_property_read_u32_index函数

                用于从属性中获取指定标号的 u32 类型数据值(无符号 32位),比如某个属性有多个 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 读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。

                ④、 of_property_read_u8_array 函数
                        of_property_read_u16_array 函数
                        of_property_read_u32_array 函数
                        of_property_read_u64_array 函数

                读取属性中 u8u16 u32u64 类型的数组数据,比如大多数的 reg 属性都是数组数据,可以使用这 4 个函数一次读取出 reg 属性中的所有数据。但是,一般属性值都是u32类型的,所以我们通常都是使用u32函数。四个函数原型:

int of_property_read_u8_array(const struct device_node *np,
			                  const char *propname, 
                              u8 *out_values, 
                              size_t sz);

int of_property_read_u16_array(const struct device_node *np,
			                   const char *propname, 
                               u16 *out_values,
                               size_t sz);

int of_property_read_u32_array(const struct device_node *np,
				               const char *propname,
				               u32 *out_values,
				               size_t sz);

int of_property_read_u64_array(const struct device_node *np,
				               const char *propname,
				               u64 *out_values,
				               size_t sz);

                 np: 设备节点。

                propname:要读取的属性名字。

                out_values:读取到的数组值。(类型不同)

                sz:要读取的数组元素的数量。

                返回值:0,读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。

                 ⑤、of_property_read_u8 函数
                        of_property_read_u16 函数
                        of_property_read_u32 函数
                        of_property_read_u64 函数

                用于读取这种只有一个整形值的属性,分别用于读取 u8 u16u32 u64 类型属性值,函数原型:

int of_property_read_u8(const struct device_node *np,
                        const char *propname,
                        u8 *out_value)

int of_property_read_u16(const struct device_node *np,
                         const char *propname,
                         u16 *out_value)

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

int of_property_read_u64(const struct device_node *np,
                         const char *propname,
                         u64 *out_value)

                 np: 设备节点。

                 propname:要读取的属性名字。

                out_value:读取到的数值。(类型不同)。

                返回值:0,读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。

                使用方法:

                 ⑥、of_property_read_string函数

                用于读取属性中字符串值,函数原型:

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

                  np: 设备节点。

                 propname:要读取的属性名字。

                out_string:读取到的字符串值。

                 返回值:0,读取成功,负值,读取失败。

                使用方法:

                 ⑦、of_n_addr_cells函数

                         of_n_size_cells函数

                用于获取#address-cells#size-cells属性值,函数原型:

int of_n_addr_cells(struct device_node *np)

int of_n_size_cells(struct device_node *np)

                 np: 设备节点。

                返回值:获取的#address-cells#size-cells属性值。

        4、其他常用的OF函数

                ①、of_device_is_compatible函数

                用于查看节点的 compatible 属性是否有包含 compat 指定的字符串,也就是检查设备节点的兼容性,函数原型:

int of_device_is_compatible(const struct device_node *device,
                            const char *compat)

                device:设备节点。

                compat:要查看的字符串。

                返回值: 0,节点的 compatible 属性中不包含 compat 指定的字符串; 正数,节点的 compatible属性中包含 compat 指定的字符串。

                使用方法:

                 ②、of_get_address函数

                用于获取地址相关属性,主要是“reg”或者“assigned-addresses”属性值,函数原型:

const __be32 *of_get_address(struct device_node *dev,
                             int index,
                             u64 *size,
                             unsigned int *flags)

                dev:设备节点。

                index:要读取的地址标号。

                size:地址长度。

                flags: 参数,比如 IORESOURCE_IOIORESOURCE_MEM 等。。

                返回值:读取到的地址数据首地址,为 NULL 的话表示读取失败。

                使用方法:

                 ③、of_translate_address函数

                将从设备树读取到的地址转换为物理地址,函数原型:

u64 of_translate_address(struct device_node *dev,
                         const __be32 *in_addr)

                dev:设备节点。

                in_addr:要转换的地址。

                返回值:得到的物理地址,如果为 OF_BAD_ADDR 的话表示转换失败。
 

                 ④、of_address_to_resource函数

                IIC、 SPI、 GPIO 等这些外设都有对应的寄存器,这些寄存器其实就是一组内存空间, Linux内核使用 resource 结构体来描述一段内存空间,“resource”翻译出来就是“资源”,因此用 resource结构体描述的都是设备资源信息, resource 结构体定义在文件 include/linux/ioport.h 中定义:

                resource_size_tu32类型。start开始地址。end:结束地址。name:这个资源的名字。 flags 是资源标志位,一般表示资源类型,可选的资源标志定义在文件 include/linux/ioport.h

                将 reg 属性值,将其转换为 resource 结构体类型。函数原型:

int of_address_to_resource(struct device_node *dev,
                           int index,
                           struct resource *r)

                dev:设备节点。

                index:地址资源标号。

                r:得到的resource类型的资源值。

                返回值: 0,成功;负值,失败。

                ⑤、of_iomap函数

                of_iomap 函数用于直接内存映射,以前我们会通过 ioremap 函数来完成物理地址到虚拟地址的映射,采用设备树以后就可以直接通过 of_iomap 函数来获取内存地址所对应的虚拟地址,不需要使用 ioremap 函数了。of_iomap 函数本质上也是将 reg 属性中地址信息转换为虚拟地址,如果 reg 属性有多段的话,可以通过 index 参数指定要完成内存映射的是哪一段,函数原型:

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

                np:设备节点。

                index:reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0。
                返回值:经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败。

                使用方法:

      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tofu_Cabbage

你的打赏是我的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值