瑞芯微Rockchip开发者社区
直播中

王斌

7年用户 1249经验值
私信 关注
[问答]

一文详解RK3399 设备树

什么是设备树?设备树是由哪些部分组成的?设备树有哪些优点呢?




回帖(1)

张荣

2022-3-8 11:45:16
1. 什么是设备树


1.1 背景


  嵌入式底层,一般是用汇编或者C语言进行编程,如内存访问、寄存器访问、外设控制等。在linux 3.x之前,linux内核与硬件抽象层相关采用的是C语言的方式描述板级设备信息,一般位于“kernel/arch/arm/mach-xxx”下,这样的方式有个严重弊端就是板级源码与内核耦合在一起,同一个CPU更改PCB或者调整底层设备时,就得修改板级C源码,然后重新编译内核,导致内核存在大量与内核无关的冗余代码。因此,从linux 3.x后引入独立于内核的设备树(Device Tree),来描述板级信息,存放于“/kernel/arch/arm(arm64)/boot/dts”目录下。



1.2 设备树


  设备树,顾名思义,分为设备和树,以树的形式描述设备,所有设备都是“挂”在树上,,设备树不限于传统板级描述。设备树以ASCII字符的形式描述板级信息,类似XML、JSON的易读性格式,方便编写和阅读。





  • 处理信息,CPU核数和类型、内存地址和大小、内存总线
  • 连接板级信息,GPIO、中断
  • 连接外设,固定设备、热插拔设备





1.3 设备树组成


  设备树包括DTC(device tree compiler)编译器,DTS(device tree source)源文件和DTB(device tree blob)目标文件。DTS与C语言一样,包括源文件DTS和头文件DTSI。DTB是二进制目标文件,DTS和DTSI通过编译后输出,在系统起来后加载使用。



DTS
  DTS是设备树源文件,文件为“.dts”后缀,对设备信息的描述。一个系统对应一个DTS,可能存在多个DTS,但最终通过编译都得出一个DTB文件。DTS可以“include”DTS和DTSI,甚至C语言头文件。

#include "rk3399-firefly-core.dtsi"
#include



DTSI
  DTS头文件,文件为“.dtsi”后缀。一个CPU可能用于多个产品上(板级信息),不同产品存在共同的设备信息,那么可以将这些共同的设备描述保存为一个DTSI文件,通过“include”方式实现复用,减少DTS冗余。类似的,DTSI支持include DTS、DTSI、C语言头文件。



DTC
  设备树编译工具,将DTS和DTSI编译为目标DTB文件。



DTB
  二进制文件,文件为“.dtb”后缀,存放于板级固定存储地址空间,bootloader在引导内核时,首先读取DTB到内存,由内核解析,执行板级相关操作。



1.4 设备树优点




  • 对于程序员,设备树采用ASII字符描述,适合程序员阅读和修改。
  • 对于开发效率,板级相关改动只需更改设备树文件,然后编译即可,无需更改驱动源码。
  • 对于linux内核管理,释放linux内核板级冗余代码,内核不需经常变动。
  • 对于产品开发,便于产品衍生(同一CPU多个产品),只需修改设备树即可满足。




2. 设备树语法




2.1 设备树节点


  设备树采用树型结构来描述板级设备信号,每个设备都是一个节点,每个节点都有自己的属性来描述设备,节点和属性就是一对“键值”。因此设备树是由节点和节点属性组成,一个节点可以嵌套多字子节点,子节点还可以包含孙子节点。

【1】节点表示形式

#源自rk3399.dtsi
/ {
        compatible = "rockchip,rk3399";

        interrupt-parent = <&gic>;
        #address-cells = <2>;
        #size-cells = <2>;

        aliases {
                dsi0 = &dsi;
                dsi1 = &dsi1;
                ethernet0 = &gmac;
                i2c0 = &i2c0;
                i2c1 = &i2c1;
                i2c2 = &i2c2;
                i2c3 = &i2c3;
                i2c4 = &i2c4;
                i2c5 = &i2c5;
                i2c6 = &i2c6;
                i2c7 = &i2c7;
                i2c8 = &i2c8;
                serial0 = &uart0;
                serial1 = &uart1;
                serial2 = &uart2;
                serial3 = &uart3;
                serial4 = &uart4;
        };

        cpus {
                #address-cells = <2>;
                #size-cells = <0>;

                cpu-map {
                        cluster0 {
                                core0 {
                                        cpu = <&cpu_l0>;
                                };
                                core1 {
                                        cpu = <&cpu_l1>;
                                };
                                core2 {
                                        cpu = <&cpu_l2>;
                                };
                                core3 {
                                        cpu = <&cpu_l3>;
                                };
                        };

                        cluster1 {
                                core0 {
                                        cpu = <&cpu_b0>;
                                };
                                core1 {
                                        cpu = <&cpu_b1>;
                                };
                        };
                };

                uart0: serial@ff180000 {
                compatible = "rockchip,rk3399-uart", "snps,dw-apb-uart";
                reg = <0x0 0xff180000 0x0 0x100>;
                clocks = <&cru SCLK_UART0>, <&cru PCLK_UART0>;
                clock-names = "baudclk", "apb_pclk";
                interrupts = ;
                reg-shift = <2>;
                reg-io-width = <4>;
                pinctrl-names = "default";
                pinctrl-0 = <&uart0_xfer &uart0_cts &uart0_rts>;
                status = "disabled";
        };
      
        .......



  • 根节点,设备树采用树状结构,因此每个设备树文件只有一个根节点(root树根)。根节点以“/”表示,如果一个板级系统由多个设备树组成时,编译后最终也会合并为一个根节点。
  • 子节点,子节点命名格式一般为:


node-label:node-name@node-address



注:
node-label,节点标签,可以通过标签索引该节点,可以不填写标签名称
node-name,节点名称,用于描述节点,如i2c、spi、bmp180(具体器件名称)
node-address,表示设备地址、寄存器首地址等,如i2c器件则表示该设备的i2c从地址




2.2 节点属性


  设备属性一般是以“属性名+属性值”的键值对存在,属性值的表示方式是多元的,可以为空、数字、字符、字符串等。

[tr]值类型举例[/tr]
32位无符号整数reg = <0x0 >
32位无符号整型数组reg = <0x0 0x2>
字符串compatible = “gpio-leds”
字符串列表compatible = “rockchip,rk3399-firefly-core”, “rockchip,rk3399”
二进制数组local-mac-address = [00 01 12 0a ab ff]



【1】状态

status ,键值类型为, 设备状态属性,可以支持动态更改,如设备热插拔。

[tr]键值描述[/tr]
“okay”设备正常状态
“disable”设备当前不可用,设备可能改变为“okay”状态
“fail”设备不可用,设备出现错误,不可重新改变为“okay”状态
“fail-sss”与“fail”相同,“sss”表示错误的具体内容



【2】兼容属性

compatible ,键值类型为,兼容属性,用于和设备驱动程序匹配,一般格式为“厂商,器件型号”。如果是根据节点的“compatible”属性,linux内核则匹配该内核是否支持该CPU,如支持才启动内核执。兼容属性键值可以是一组列表,内核匹配时,从左至右边匹配。

compatible = "invensense,mpu6500","mpu65xx";        #先匹配“invensense,mpu6500”,如失败则继续匹配“mpu65xx”



【3】设备信息

model,键值类型为,与“compatible”类似,描述设备模块信息。

model= "invensense,mpu6500";



【4】 地址



  • reg ,键值类型为
    ,用于描述设备地址空间资源信息,如外设的寄存器地址范围,字节点名称接着的十六进制地址就是reg属性列表的首地址。reg属性由父节点的“#address-cells”和“#size-cells”的值决定数目列表,可以存在多个列表。特殊情况下,reg也描述器件地址,不需带长度信息,如i2c器件从地址。
  • #address-cells和#size-cells,键值类型为,用于描述子节点地址(reg属性)信息,#address-cells描述子节点reg属性地址信息的字长,#size-cells描述子节点reg属性地址长度信息所占的字长。


#源自rk3399.dtsi
i2c4: i2c@ff3d0000 {
                compatible = "rockchip,rk3399-i2c";
                reg = <0x0 0xff3d0000 0x0 0x1000>;
                clocks = <&pmucru SCLK_I2C4_PMU>, <&pmucru PCLK_I2C4_PMU>;
                clock-names = "i2c", "pclk";
                interrupts = ;
                pinctrl-names = "default";
                pinctrl-0 = <&i2c4_xfer>;
                #address-cells = <1>;
                #size-cells = <0>;
                status = "disabled";
        };
      
#源自rk3399-firefly-port.dtsi
mpu6500: mpu@68 {
                status = "disabled";
                compatible = "invensense,mpu6500";
                reg = <0x68>;        #父节点是i2c4
......



【5】 地址映射

ranges,子、父地址映射表,键值类型为,或者为空。



  • child-bus-address,子地址物理空间起始地址。
  • parent-bus-address,父地址物理空间起始地址。
  • length,字地址空间长度大小,由父节点“#size-cells”属性决定该地址所占的字长。








  • ranges为空,表示子地址空间和父地址空间相同,不需要进行地址转换。
  • ranges不为空,表示把字地址空间起始映射到父地址空间起始,大小为length。
  • 含有ranges属性的节点的子节点,其reg都是基于子地址。
  • 对于没有ranges属性的节点,代表不是memory map区域。



  rk3399设备树文件“range”属性基本为空,表示不需要子、父地址转换,我们单独举个例子说明。

Test: Test@0x0 {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges=<0x0 0x10 0x100>        #把字地址空间(0x0——(x0+0x100))映射到父地址空间(0x10——(0x10+0x100))
        ....



【6】 GPIO



  • gpio-controller,空属性,声明该节点为GPIO控制器
  • #gpio-cells,键值类型为,表示需要描述GPIO属性的单位数目,如一个普通GOPIO需要管脚序号和工作模式,则#gpio-cells值为2。


#源自rk3399.dtsi
gpio0: gpio0@ff720000 {
                        compatible = "rockchip,gpio-bank";
                        reg = <0x0 0xff720000 0x0 0x100>;
                        clocks = <&pmucru PCLK_GPIO0_PMU>;
                        interrupts = ;

                        gpio-controller;
                        #gpio-cells = <0x2>;        #2 cell个描述

                        interrupt-controller;
                        #interrupt-cells = <0x2>;
                };
               
#源自rk3399firefly-port.dtsi               
leds {
                compatible = "gpio-leds";

                work {
                        label = "firefly:blue:power";
                        linux,default-trigger = "ir-power-click";
                        gpios = <&gpio2 27 GPIO_ACTIVE_HIGH>;#与“#gpio-cells”对应,27为管脚序号,GPIO_ACTIVE_HIGH为工作模式
                        pinctrl-names = "default";
                        pinctrl-0 = <&led_power>;
                        default-state = "on";
                };



【7】中断



  • interrupt-controller ,空属性,声明该节点支持接收中断信息。
  • interrupt-parent,表示该节点属于哪一个中断控制器,如果不设置该属性,则默认继承父节点中断控制器。
  • #interrupt-cells,键值类型为,表示需要描述中断属性(interrupts)的单位数目,用来描述子节点中"interrupts"属性使用了父节点中的“interrupts”属性的具体的哪个值。通常,如果父节点interrupt-cells的值为3,则子节点的interrupts的键值分别为<中断域 中断 触发方式> ;如果父节点interrupt-cells的值2,则键值是<中断 触发方式>。
  • interrupts,中断标识符列表,与“#interrupt-cells”关联。
  • #interrupts-extended,如果设备连接多个中断控制器,则列出这些中断。


###rk3399电源管理设备(rk808)设备节点,父节点为i2c0。###
#源自rk3399-firefly-core.dtsi
rk808: pmic@1b {      
                compatible = "rockchip,rk808";
                reg = <0x1b>;
                interrupt-parent = <&gpio1>;                #继承GPIO1中断
                interrupts = <21 IRQ_TYPE_LEVEL_LOW>;#2个单位中断描述,<中断 触发方式>
                .....
               
#源自rk3399.dtsi
gpio1: gpio1@ff730000 {
                        compatible = "rockchip,gpio-bank";
                        reg = <0x0 0xff730000 0x0 0x100>;
                        clocks = <&pmucru PCLK_GPIO1_PMU>;
                        interrupts = ;

                        gpio-controller;
                        #gpio-cells = <0x2>;

                        interrupt-controller;
                        #interrupt-cells = <0x2>;        #子节点需2个单位中断描述
                };
举报

更多回帖

发帖
×
20
完善资料,
赚取积分