RK3399—设备树

夙愿已清 提交于 2020-01-07 10:05:11

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 <dt-bindings/input/input.h>

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 = <GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH 0>;
		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 节点属性

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

值类型 举例
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 ,键值类型为<string>, 设备状态属性,可以支持动态更改,如设备热插拔。

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

【2】兼容属性

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

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

【3】设备信息

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

model= "invensense,mpu6500";

【4】 地址

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

  • #address-cells和#size-cells,键值类型为<u32>,用于描述子节点地址(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 = <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH 0>;
		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>,或者为空。

  • 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,键值类型为<u32>,表示需要描述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 = <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH 0>;

			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,键值类型为<u32>,表示需要描述中断属性(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 = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH 0>;

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

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

3. 参考

【1】https://www.cnblogs.com/pengdonglin137/p/7401049.html

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!