0%

Linux驱动学习日记(11) DTS 设备树学习

什么是设备树

  设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(D evice Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息, 比如 CPU 数量、 内存基地址、IIC 接口上接了哪些设备、SPI 接口上接了哪些设备等等。

图 1 设备树结构图

  在上图中,树的主干就是系统总线,IIC控制器、GPIO控制器、SPI控制器等都是接到系统主线上的分支。IIC控制器有分为IIC1和IIC2两种,其中IIC1上接了FT5206和AT24C02这两个IIC设备,IIC2上只接了MPU6050这个设备。DTS文件的主要功能就是按照上图所示的结构来描述板子上的设备信息。

DTS、DTB 和 DTC

  上面说了,设备树源文件的扩展名是.dts,但是我们在前面移植 linux 的时候一直在使用.dtb文件,DTB 是 DTS 编译后生成的文件,而这次编译用到的就是 DTC 工具。

DTS 语法

  本节我们以 imx6ull-hcw-emmc.dts 这个文件为例来讲解一下DTS语法。

dtsi 头文件

  和 C 语言一样,设备树也支持头文件,设备树的头文件扩展名是 .dtsi。

1
2
#include "imx6ull.dtsi"
#include "imx6ull-hcw-emmc.dtsi"

  一般.dtsi文件用于描述SOC的内部外设信息,比如CPU架构、主频、外设寄存器地址范围,比如 UART、IC等等。比如imx6ull.dtsi就是描述I.MX6ULL这颗SOC内部外设情况信息的。

设备节点

  设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键一值对,示例代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/ {
···
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>;
};

spi-4 {
compatible = "spi-gpio";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_spi4>;
status = "okay";
gpio-sck = <&gpio5 11 0>;
gpio-mosi = <&gpio5 10 0>;
cs-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>;
num-chipselects = <1>;
#address-cells = <1>;
#size-cells = <0>;

gpio_spi: gpio@0 {
compatible = "fairchild,74hc595";
gpio-controller;
#gpio-cells = <2>;
reg = <0>;
registers-number = <1>;
spi-max-frequency = <100000>;

};
};
···
};

  “/” 是根节点,每个设备树文件只有一个根节点,多个 dtsi 文件中的根节点会合并为一个根节点。

  memory 和 spi-4 是两个子节点,在设备树中子节点的命名格式如下

node-name@unit-address

  其中 node-name 是节点名字,为 ASCII 字符串,节点名字应该清晰的描述出节点功能。unit-address 一般表示设备的地址或寄存器首地址,如果某个节点没有地址或寄存器的话可以不写。有些节点命名如下:

cpu0:cpu@0

上述命令并不是 “node-name@unit-address” 这样的格式,而是用“:”隔开成了两部分,“:”前面的是节点标签(label),“:”后面的才是节点名字,格式如下所示:

label: node-name@unit-address

引入label 的目的就是为了方便访问节点,可以直接通过&label 来访问这个节点,比如通过 &cpu0 就可以访问“cpu@0”这个节点,而不需要输入完整的节点名字。再比如节点 “intc:interrupt-controller@00a01000”,节点 label 是 intc,而节点名字就很长了,为“interrupt-controller@00a01000”。很明显通过&intc 来访问“interrupt-controller@00a01000”这个节点要方便很多。

  每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流。设备树源码中常用的几种数据形式如下所示:

标准属性

  节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以 自定义属性。除了用户自定义属性,有很多属性是标准属性,Linux 下的很多外设驱动都会使用 这些标准属性,本节我们就来学习一下几个常用的标准属性。

compatible 属性

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

"manufacturer,model"

  其中 manufacturer 表示厂商,model 一般是模块对应的驱动名字,比如imx6ull-hcw-emmc.dts中sound节点是I.MX6U-ALPHA开发板的音频设备节点,I.MX6U-ALPHA开发板上的音频芯片采用的欧胜(WOLFSON)出品的WM8960,sound节点的compatible属性值如下:

compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";

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

model 属性

  model属性值也是一个字符串,一般model属性描述设备模块信息,比如名字什么的,比如:

model ="wm8960-audio";

status 属性

  status 属性看名字就知道是和设备状态有关的,status 属性值也是字符串,字符串是设备的状态信息,可选的状态如下表所示

描述
“okay” 表明当前设备可操作
“disabeled” 表明设备当前是不可操作的,但是在未来可以变为可操作的,比如热插拔设备插入以后。至于 disabled 的具体含义还要看设备的绑定文档。
“fail” 表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可操作。
“fail-sss” fail”相同,后面的 sss 部分是检测到的错误内容。

#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……>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spi4 {
compatible = "spi-gpio";
#address-cells = <1>
#size-cells = <0>;
gpio_spi: gpio_spi@0 {
compatible = "fairchild,74hc595";
reg = <0>;
};
}
aips3: aips-bus@02200000 {
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;

dcp: dcp@02280000 {
compatible = "fsl,imx6sl-dcp";
reg = <0x02280000 0x4000>;
};
};

  在spi4这个节点中,#address-cells 为1,#size-cells为0,所以 reg 中只有一个值为0,表示在总设备中的片选号。在 aips3 中,#address-cells 和 #size-cells 都被设置成1,reg 中的定义相当于设置了起始地址为 0x02280000,地址长度为 0x4000。

reg 属性

  刚刚说过,reg 属性的值一般是(address,length)对。reg 属性一般用于描 述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。

ranges 属性

  ranges 属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数 字矩阵,ranges 是一个地址映射/转换表,ranges 属性每个项目由子地址、父地址和地址空间长度这三部分组成:

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

根节点 compatible 属性

  每个节点都有 compatible 属性,根节点“/”也不例外,imx6ull-hcw-emmc.dts 文件中根节点的 compatible 属性内容如下所示:

1
2
3
4
/ {
model = "Freescale i.MX6 UltraLiteLite 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
};

通过根节点的 compatible 属性可以知道我们所使用的设备,一般第一个值描述了所使用的硬件设备名字,比如这里使用的是“imx6ull-14x14-evk”这个设备,第二个值描述了设备所使用的 SOC,比如这里使用的是“imx6ull”这颗SOC。Linux内核会通过根节点的 compatible 属性查看是否支持此设备,如果支持就会启动 Linux 内核。