一般情况下,我们不会从头编写一个完整的dts文件,SOC厂商一般会直接提供一个有着基本框架的dts文件,当需要添加自己的板子设备树文件时,基于厂商提供的dts文件修改即可。所以我们要了解dts设备树文件的语法,这样我们才清楚如何添加我们自己的设备。
在本章节中,我们以topeet_emmc_4_3.dts设备树文件为例,来具体讲解一下dts文件的语法结构,如何添加一个设备硬件信息。
由于一个SOC可能对应多个ARM设备,这些dts文件势必包含许多共同的部分,Linux内核为了简化,把SOC公用的部分或者多个设备共同的部分提炼为.dtsi文件,类似于C语言的头文件。.dtsi文件也可以包含其他的.dtsi。在topeet_emmc_4_3.dts文件中有如下内容:
#include
#include "imx6ull.dtsi"
用“#include”关键字来引用了input.h和imx6ull.dtsi文件,
在imx6ull-14x14-evk-gpmi-weim.dts文件中有如下内容:
#include “imx6ull-14x14-evk.dts”
用“#include”关键字来引用了imx6ull-14x14-evk.dts文件,由此可以看出在.dts文件中可以通过“#include”来引用.h、.dtsi和.dts文件。
一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如 UART、IIC 等等。比如 imx6ull.dtsi 就是描述 I.MX6ULL 这个 SOC 内部外设情况信息的,内容如下:
10 #include <dt-bindings/clock/imx6ul-clock.h>
11 #include <dt-bindings/gpio/gpio.h>
12 #include <dt-bindings/interrupt-controller/arm-gic.h>
13 #include "imx6ull-pinfunc.h"
14 #include "imx6ull-pinfunc-snvs.h"
15 #include "skeleton.dtsi"
16
17 / {
18 aliases {
19 can0 = &flexcan1;
......
48 };
49
50 cpus {
51 #address-cells = <1>;
52 #size-cells = <0>;
53
54 cpu0: cpu@0 {
55 compatible = "arm,cortex-a7";
56 device_type = "cpu";
......
89 };
90 };
91
92 intc: interrupt-controller@00a01000 {
93 compatible = "arm,cortex-a7-gic";
94 #interrupt-cells = <3>;
95 interrupt-controller;
96 reg = <0x00a01000 0x1000>,
97 <0x00a02000 0x100>;
98 };
99
100 clocks {
101 #address-cells = <1>;
102 #size-cells = <0>;
103
104 ckil: clock@0 {
105 compatible = "fixed-clock";
106 reg = <0>;
107 #clock-cells = <0>;
108 clock-frequency = <32768>;
109 clock-output-names = "ckil";
110 };
......
135 };
136
137 soc {
138 #address-cells = <1>;
139 #size-cells = <1>;
140 compatible = "simple-bus";
141 interrupt-parent = <&gpc>;
142 ranges;
143
144 busfreq {
145 compatible = "fsl,imx_busfreq";
......
162 };
197
198 gpmi: gpmi-nand@01806000{
199 compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpminand";
200 #address-cells = <1>;
201 #size-cells = <1>;
202 reg = <0x01806000 0x2000>, <0x01808000 0x4000>;
......
216 };
......
1177 };
1178 };
第 54~89 行就是 cpu0 这个设备节点信息,这个节点信息描述了I.MX6ULL 这颗 SOC 所使用的 CPU 信息,比如架构是 cortex-A7,频率支持 996MHz、792MHz、528MHz、396MHz 和 198MHz 等等。
在imx6ull.dtsi 文件中不仅仅描述了 cpu0 这一个节点信息, I.MX6ULL 这颗 SOC 所有的外设都描述的清清楚楚,比如 ecspi14、uart18、usbphy12、i2c14等等。下面我们就来介绍一下设备节点的具体信息。
设备树是一个包含节点和属性的简单树状结构。属性就是键-值对,而节点可以同时包含属性和子节点。下面先来看一个设备树结构模板:
1/ {
2 node1 {
3 a-string-property = "A string";
4 a-string-list-property = "first string", "second string";
5 a-byte-data-property = [0x01 0x23 0x34 0x56];
6 child-node1 {
7 first-child-property;
8 second-child-property = <1>;
9 a-string-property = "Hello, world";
10 };
11 child-node2 {
12 };
13 };
14 node2 {
15 an-empty-property;
16 a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
17 child-node1 {
18 };
19 };
20}
上面的dts文件内容并没有实际的用途,只是基本表示了一个设备树源文件的结构。但是这里面体现了一些属性:
① 一个单独的根节点:“/”
② 两个子节点:“node1”和“node2”
③ 两个node1的子节点:“child-node1”和“child-node2”
④ 一些分散在树里的属性,属性是最简单的键-值对,它的值可以为空或者包含一个任意的字节流。虽然数据类型并没有编码进数据结构,但是设备树源文件中仍有几个基本的数据表示形式:
1)文本字符串(无结束符),可以用双引号表示:
a-string-property = “A string”;
2)“cells”是32位无符号整数,用尖括号限定:
cell-property = <0xbeef 123 0xabcd1234>;
3)二进制数据用方括号限定:
binary-property = [0x01 0x23 0x45 0x67];
4)不同表示形式的数据可以用逗号连在一起:
mixed-property = “a string”, [0x01 0x23 0x45 0x67], <0x12345678>;
5)逗号也可以用于创建字符串列表:
string-list = “red fish”, “blue fish”;
下面我们看一下简化之后的imx6ull.dtsi文件中结构:
1 / {
2 aliases {
3 can0 = &flexcan1;
4 };
5
6 cpus {
7 #address-cells = <1>;
8 #size-cells = <0>;
9
10 cpu0: cpu@0 {
11 compatible = "arm,cortex-a7";
12 device_type = "cpu";
13 reg = <0>;
14 };
15 };
16
17 intc: interrupt-controller@00a01000 {
18 compatible = "arm,cortex-a7-gic";
19 #interrupt-cells = <3>;
20 interrupt-controller;
21 reg = <0x00a01000 0x1000>,
22 <0x00a02000 0x100>;
23 };
24 }
第1行,“/”是根节点。
第2、6和17行,aliases、cpus和intc是三个子节点。
第10行,cpu0是cpus的子节点。
在上述代码中我们看到了许多节点中的属性,例如:compatible、reg等,它们的具体作用是什么,还有发现节点的命名格式不有所不同,那么节点应该如何命名?在下面的内容中来一一说明。
在前面的代码中,我们注意到节点和子节点之间的命名有所不同,它们都遵循了下面的命名格式:
node-name@unit-address
其中node-name是必选项,而unit-address则是可选项。name是一个ASCII字符串,用于描述节点对应的设备类型,如:“uart1”,可以清晰的描述出设备的功能。如果一个节点描述的设备有地址,则应该给出@unit-address。多个相同类型设备节点的name可以一样,只要unit-address不同即可,如cpu@0、cpu@1以及serial@101f0000与serial@101f2000这样的同名节点。
在之前的代码中还有另一种命名格式:
label: node-name@unit-address
如:“cpu0:cpu@0”,和上面的命名格式差不多,前面多了一个label,称作节点标签。label的作用就是为了方便访问节点,可以直接通过&label来访问这个节点,比如通过“&cpu0”就可以访问“cpu@0”这个节点,而不用输入完整的节点名字。在例如节点“intc: interrupt-controller@00a01000”,通过&intc来访问比节点名字方便很多。
例如在imx6ull.dtsi文件中定义i2c1设备节点信息:
....
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
};
.....
在topeet_emmc_4_3.dts文件中通过label引用向其中添加其他设备信息:
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = “default”;
pinctrl-0 = <&pinctrl_i2c1>;
status = “okay”;
mag3110@0e {
compatible = "fsl,mag3110";
reg = <0x0e>;
position = <2>;
};
};
节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以自定义属性。除了用户自定义属性,有很多属性是标准属性,Linux 下的很多外设驱动都会使用这些标准属性,本节我们就来学习一下几个常用的标准属性。
1、compatible属性
设备树中的每个表示一个设备的节点都需要一个compatible属性,compatible属性是操作系统用来决定设备和驱动绑定的关键因素。compatible属性也叫做兼容性属性,属性的值是一个字符串列表,用于表示是何种设备。具体结构如下:
“manufacturer,model”
第一个字符串表示厂商,后面的字符串表示确切的设备名称。比如在topeet_emmc_4_3.dts文件中sound节点表示开发板的音频设备节点,i.MX6UL终结者开发板上的音频芯片是欧胜(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 内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查找,直到找到或者查找完整个 Linux 内核也没有找到对应的驱动。
一般驱动程序文件都会有一个 OF 匹配表,此 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 };
第632~635行,是imx_wm8960这个设备的compatible属性匹配表,会和设备中相同的compatible属性相匹配,当匹配成功后,设备和驱动就绑定了,设备就会使用这个驱动文件。在这个匹配表中只有一个匹配值,也有其他设备的匹配表中有多个匹配值。
第642行,wm8960采用了platform总线,将匹配表结构体imx_wm8960_dt_ids赋值给platform_driver结构体中相应的成员变量。也就是设置platform_driver所使用的的OF匹配表。
2、model属性
model属性值用于描述设备模块信息,也是一个字符串,使用的较少,以wm8960为例:
model = “wm8960-audio”;
3、status属性
status属性用来表示节点的状态,其实就是硬件的状态,用字符串表示。
“okay”表示硬件正常工作
“disable”表示当前硬件不可用
“fail”表示因为出错不可用
“fail-sss”表示某种原因出错不可用,sss表示具体出错的原因。
实际中,基本只用“okay”和“disabl”。
4、#address-cells和#size-cells属性
不同的平台,不同的总线,地址位长度可能不同,有32位地址,有64位地址,为了适应这个,规范规定一个32位的长度为一个cell。"#address-cells"属性用来表示总线地址需要几个cell表示,该属性本身是u32类型的。"#size-cells"属性用来表示子总线地址空间的长度需要几个cell表示,属性本身的类型也是u32。可以这么理解父节点表示总线,总线上每个设备的地址长度以及地址范围是总线的一个特性,用"#address-cells","#size-cells"属性表示,比如总线是32位,那么"#address-cells"设置成1就可以了。这两个属性不可以继承,就是说在未定义这两个属性的时候,不会继承更高一级父节点的设置,如果没有设置的话,内核默认认为"#address-cells"为2,"#size-cells"为1。
1 spi4 {
2 compatible = "spi-gpio";
3 #address-cells = <1>;
4 #size-cells = <0>;
5
6 gpio_spi: gpio_spi@0 {
7 compatible = "fairchild,74hc595";
8 reg = <0>;
9 };
10 };
11
12 aips3: aips-bus@02200000 {
13 compatible = "fsl,aips-bus", "simple-bus";
14 #address-cells = <1>;
15 #size-cells = <1>;
16
17 dcp: dcp@02280000 {
18 compatible = "fsl,imx6sl-dcp";
19 reg = <0x02280000 0x4000>;
20 };
21 };
第 3,4 行,节点 spi4 的#address-cells = <1>,#size-cells = <0>,说明 spi4 的子节点 reg 属性中起始地址所占用的字长为 1,地址长度所占用的字长为 0。因此第8行reg 属性值为 <0>,相当于设置了起始地址,而没有设置地址长度。
第 14,15 行,设置 aips3: aips-bus@02200000 节点#address-cells = <1>,#size-cells = <1>,说明 aips3: aips-bus@02200000 节点起始地址长度所占用的字长为 1,地址长度所占用的字长也为 1。因此第 19 行,子节点 dcp: dcp@02280000 的 reg 属性值为<0x02280000 0x4000>,相当于设置了起始地址为 0x02280000,地址长度为 0x40000。
5、reg属性
“reg"属性用来表示节点地址资源的,比如常见的就是寄存器的起始地址及大小。要想表示一块连续地址,必须包含起始地址和空间大小两个参数,如果有多块地址,那么就需要多组这样的值表示。对于’reg’属性,每个元素是一个二元组,包含起始地址和大小。还有另外一个问题,地址和大小用几个u32表示呢?这个就由父节点的”#address-cells","#size-cells"属性确定。
比如在 imx6ull.dtsi 中有如下内容:
323 uart1: serial@02020000 {
324 compatible = "fsl,imx6ul-uart",
325 "fsl,imx6q-uart", "fsl,imx21-uart";
326 reg = <0x02020000 0x4000>;
327 interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
328 clocks = <&clks IMX6UL_CLK_UART1_IPG>,
329 <&clks IMX6UL_CLK_UART1_SERIAL>;
330 clock-names = "ipg", "per";
331 status = "disabled";
332 };
上述代码是节点 uart1,uart1 节点描述了 I.MX6ULL 的 UART1 相关信息,重点是第 326 行的 reg 属性。其中 uart1 的父节点 aips1: aips-bus@02000000 设置了#address-cells = <1>、#size-cells = <1>,因此 reg 属性中 address=0x02020000,length=0x4000。
6、ranges属性
总线上设备在总线地址和总线本身的地址可能不同,"ranges"属性用来表示如何转换。和’reg’属性类似,不同的是’ranges’属性的每个元素是三元组,按照前后顺序分别是(子总线地址,父总线地址,大小)。子总线地址需要几个u32表示由’ranges’属性所在节点的’#address-cells’属性决定,父总线地址需要几个u32表示由上一级节点的’#address-cells’属性决定,大小需要几个u32表示由当前节点的’#size-cells’属性确定。
如果 ranges 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换,对于我们所使用的 I.MX6ULL 来说,子地址空间和父地址空间完全相同,因此会在 imx6ull.dtsi中找到大量的值为空的 ranges 属性,如下所示:
137 soc {
138 #address-cells = <1>;
139 #size-cells = <1>;
140 compatible = "simple-bus";
141 interrupt-parent = <&gpc>;
142 ranges;
......
ranges 属性不为空的示例代码如下所示:
1 soc {
2 compatible = "simple-bus";
3 #address-cells = <1>;
4 #size-cells = <1>;
5 ranges = <0x0 0xe0000000 0x00100000>;
6
7 serial {
8 device_type = "serial";
9 compatible = "ns16550";
10 reg = <0x4600 0x100>;
11 clock-frequency = <0>;
12 interrupts = <0xA 0x8>;
13 interrupt-parent = <&ipic>;
14 };
15 };
第 5 行,节点 soc 定义的 ranges 属性,值为<0x0 0xe0000000 0x00100000>,此属性值指定了一个 1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为 0x0,父地址空间的物理起始地址为 0xe0000000。
第 10 行,serial 是串口设备节点,reg 属性定义了 serial 设备寄存器的起始地址为 0x4600,寄存器长度为 0x100。经过地址转换,serial 设备可以从 0xe0004600 开始进行读写操作,0xe0004600=0x4600+0xe0000000。
在设备树文件中,每个节点都有compatible属性,根节点“/”也不例外,根节点compatible属性定义了整个系统的名称。在topeet_emmc_4_3.dts设备树文件中根节点compatible属性定义如下:
/ {
model = "Freescale i.MX6 ULL 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
......
}
在上述代码中,根节点“/”compatible 属性有两个值:"fsl,imx6ull-14x14-evk"和 “fsl,imx6ull”,Linux内核通过根节点“/”的compatible属性即可判断它启动的是什么设备。,一般第一个值描述了所使用的硬件设备名字,比如这里使用的是“imx6ull-14x14-evk”这个设备,第二个值描述了设备所使用的 SOC,比如这里使用的是“imx6ull”这颗 SOC。Linux 内核会通过根节点的 compoatible 属性查看是否支持此设备,如果支持的话设备就会启动 Linux 内核。接下来我们就来学习一下 Linux 内核在使用设备树前后是如何判断是否支持某款设备的。
1、使用设备树之前设备匹配方法
在没有使用设备树之前,uboot启动会向Linux内核传递一个machine id的值,不同的设备有不同的machine id值,然后Linux内核根据不同的machine id值执行相应设备的一系列初始化函数。
针对每一个设备,Linux内核用MACHINE_START和MACHINE_END来定义一个 machine_desc 结构体来描述这个设备。比如在文件arch/arm/mach-imx/mach-mx21ads.c中有如下定义:
324 MACHINE_START(MX21ADS, "Freescale i.MX21ADS")
325 /* maintainer: Freescale Semiconductor, Inc. */
326 .atag_offset = 0x100,
327 .map_io = mx21_map_io,
328 .init_early = imx21_init_early,
329 .init_irq = mx21_init_irq,
330 .init_time = mx21ads_timer_init,
331 .init_machine = mx21ads_board_init,
332 .restart = mxc_restart,
333 MACHINE_END
上述代码定义了“Freescale i.MX21ADS”这个设备,其中 MACHINE_START 和
MACHINE_END 定义在文件 arch/arm/include/asm/mach/arch.h 中,内容如下:
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
根据 MACHINE_START 和 MACHINE_END 的宏定义,将machine_desc 结构体定义展开后如下所示:
1 static const struct machine_desc __mach_desc_MX21ADS \
2 __used \
3 __attribute__((__section__(".arch.info.init"))) = {
4 .nr = MACH_TYPE_MX21ADS,
5 .name = "Freescale i.MX21ADS",
6 /* Maintainer: Freescale Semiconductor, Inc */
7 .atag_offset = 0x100,
8 .map_io = mx21_map_io,
9 .init_early = imx21_init_early,
10 .init_irq = mx21_init_irq,
11 .init_time = mx21ads_timer_init,
12 .init_machine = mx21ads_board_init,
13 .restart = mxc_restart,
14 };
上述代码显示,这里定义了一个 machine_desc 类型的结构体变量__mach_desc_MX21ADS ,这个变量存储在 “ .arch.info.init ”段中。第 4 行的 MACH_TYPE_MX21ADS就是“ Freescale i.MX21ADS ”这个板子的 machine id 。MACH_TYPE_MX21ADS 定义在文件 include/generated/mach-types.h 中,此文件定义了大量的machine id,内容如下所示:
173 #define MACH_TYPE_AT91SAM9261EK 848
174 #define MACH_TYPE_LOFT 849
175 #define MACH_TYPE_MX21ADS 851
176 #define MACH_TYPE_AMS_DELTA 862
177 #define MACH_TYPE_NAS100D 865
可以看出MACH_TYPE_MX21ADS的值为851。
uboot启动Linux内核后,会传递machine id这个参数,然后Linux内核检查machine id是否支持。也就是和宏定义的实际值做比较,如果相等的话就表示 Linux 内核支持这个设备,如果不支持的话那么这个设备就没法启动 Linux 内核。
2、使用设备树之后的设备匹配方法
在Linux内核引入设备树之后,MACHINE_START变更为DT_MACHINE_START,其中含有一个.dt_compat成员,用于表明相关的设备与.dts中根节点的兼容属性兼容关系。如果Bootloader传递给内核的设备树中根节点的兼容属性出现在某设备的.dt_compat表中,相关的设备就与对应的兼容匹配,从而引发这一设备的一系列初始化函数被执行。
DT_MACHINE_START 也定义在文件 arch/arm/include/asm/mach/arch.h里面,定义如下:
#define DT_MACHINE_START(_name, _namestr) \
static const struct machine_desc __mach_desc_##_name \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = ~0, \
.name = _namestr,
可以看出,DT_MACHINE_START 和 MACHINE_START 基本相同,只是.nr 的设置不同,在 DT_MACHINE_START 里面直接将.nr 设置为~0。说明引入设备树以后不会再根据 machine id 来检查 Linux 内核是否支持某个设备了。
打开文件 arch/arm/mach-imx/mach-imx6ul.c,有如下所示内容:
static const char *imx6ul_dt_compat[] __initconst = {
"fsl,imx6ul",
"fsl,imx6ull",
NULL,
};
DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
.map_io = imx6ul_map_io,
.init_irq = imx6ul_init_irq,
.init_machine = imx6ul_init_machine,
.init_late = imx6ul_init_late,
.dt_compat = imx6ul_dt_compat,
MACHINE_END
在DT_MACHINE_START中多了一个.dt_compat成员变量,此变量用于保存设备的兼容属性,在上述代码中将imx6ul_dt_compat结构体赋值给.dt_compat成员变量,因此只要某个板子的设备树文件下根节点“/”compatible属性的值和imx6ul_dt_compat结构体中的任何一个值相等,那么就表示Linux内核支持此设备。在topeet_emmc_4_3.dts文件中根节点“/”compatible属性如下所示:
compatible = “fsl,imx6ull-14x14-evk”, “fsl,imx6ull”;
其中"fsl,imx6ull"与imx6ul_dt_compat 中的“fsl,imx6ull”相匹配,表示支持此开发板,Linux内核可以正常启动。
接下来简单看一下Linux内核是如何将设备树根节点的compatible属性和Linux内核中machine_desc结构体进行匹配的。Linux 内核调用 start_kernel 函数来启动内核,start_kernel 函数会调用setup_arch 函数来匹配 machine_desc,setup_arch 函数定义在文件 arch/arm/kernel/setup.c 中,函数内容如下(有缩减):
913 void __init setup_arch(char **cmdline_p)
914 {
915 const struct machine_desc *mdesc;
916
917 setup_processor();
918 mdesc = setup_machine_fdt(__atags_pointer);
919 if (!mdesc)
920 mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
921 machine_desc = mdesc;
922 machine_name = mdesc->name;
......
986 }
第 918 行,调用 setup_machine_fdt 函数来获取匹配的 machine_desc,参数就是 atags 的首地址,也就是 uboot 传递给 Linux 内核的 dtb 文件首地址,setup_machine_fdt 函数的返回值就是找到的最匹配的 machine_desc。函数 setup_machine_fdt 定义在文件 arch/arm/kernel/devtree.c 中,内容如下(有缩减):
204 const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
205 {
206 const struct machine_desc *mdesc, *mdesc_best = NULL;
......
214
215 if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
216 return NULL;
217
218 mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
219
......
247 __machine_arch_type = mdesc->nr;
248 return mdesc;
249 }
第 218 行,调用函数 of_flat_dt_match_machine 来获取匹配的 machine_desc,参数 mdesc_best是默认的 machine_desc ,参数 arch_get_next_mach 是个函数,此函数定义在定义在arch/arm/kernel/devtree.c 文件中。找到匹配的 machine_desc 的过程就是用设备树根节点的compatible 属性值和 Linux 内核中保存的所有 machine_desc 结构的. dt_compat 中的值比较,看看哪个相等,如果相等的话就表示找到匹配的 machine_desc,arch_get_next_mach 函数的工作就获取 Linux 内核中下一个 machine_desc 结构体。
最后在来看一下 of_flat_dt_match_machine 函数,此函数定义在文件 drivers/of/fdt.c 中,内容如下(有缩减):
705 const void * __init of_flat_dt_match_machine(const void *default_match,
706 const void * (*get_next_compat)(const char * const**))
707 {
708 const void *data = NULL;
709 const void *best_data = default_match;
710 const char *const *compat;
711 unsigned long dt_root;
712 unsigned int best_score = ~1, score = 0;
713
714 dt_root = of_get_flat_dt_root();
715 while ((data = get_next_compat(&compat))) {
716 score = of_flat_dt_match(dt_root, compat);
717 if (score > 0 && score < best_score) {
718 best_data = data;
719 best_score = score;
720 }
721 }
......
739
740 pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());
741
742 return best_data;
743 }
第 714 行,通过函数 of_get_flat_dt_root 获取设备树根节点。
第 715~720 行,此循环就是查找匹配的 machine_desc 过程,第 716 行的 of_flat_dt_match 函数会将根节点 compatible 属性的值和每个 machine_desc 结构体中. dt_compat 的值进行比较,直至找到匹配的那个 machine_desc。
当我们开发一个新的开发板时,需要不断的向设备树设备节点下添加和修改内容。比如我们的开发板需要一个音频设备wm8960,wm8960是一个基于i2c2的设备,所以需要在i2c2的节点下添加一个wm8960子节点。下面先看一下i2c2节点信息,在imx6ull.dtsi文件中:
947 i2c2: i2c@021a4000 {
948 #address-cells = <1>;
949 #size-cells = <0>;
950 compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
951 reg = <0x021a4000 0x4000>;
952 interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
953 clocks = <&clks IMX6UL_CLK_I2C2>;
954 status = "disabled";
955 };
上述代码是定义在imx6ull.dtst文件中的i2c2节点信息,如果我们想要添加一个wm8960音频设备的硬件信息,最简单的就是在i2c2的节点下创建一个wm8960子节点,如下所示:
947 i2c2: i2c@021a4000 {
948 #address-cells = <1>;
949 #size-cells = <0>;
950 compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
951 reg = <0x021a4000 0x4000>;
952 interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
953 clocks = <&clks IMX6UL_CLK_I2C2>;
954 status = "disabled";
955
956 wm8960@1a {
957 compatible = "wlf,wm8960";
958 reg = <0x1a>;
959 clocks = <&clks IMX6UL_CLK_SAI2>;
960 clock-names = "mclk";
961 wlf,shared-lrclk;
962 };
963
964 };
第956~962行就是添加的wm8960设备对应的子节点信息。这样添加是没有问题的,但是会有个问题,i2c2节点定义在imx6ull.dtsi文件中,而imx6ull.dtsi 是设备树头文件,其他所有使用到 I.MX6ULL这颗 SOC 的板子都会引用 imx6ull.dtsi 这个文件。直接在 i2c1 节点中添加 wm8960 就相当于在其他的所有板子上都添加了wm8960 这个设备,但是其他的板子可能并没有这个设备。所以这样添加语法上是没有问题,但是添加的位置不太合适。我们需要在添加wm8960设备节点的同时,不影响其他使用imx6ull的板子。
topeet_emmc_4_3.dts文件是专门对应i.MX6UL终结者开发板的设备树文件,只对本开发板有效,因此需要在topeet_emmc_4_3.dts文件中添加wm8960设备节点信息。内容如下:
306 &i2c2 {
307 clock_frequency = <100000>;
308 pinctrl-names = "default";
309 pinctrl-0 = <&pinctrl_i2c2>;
310 status = "okay";
311
312 codec: wm8960@1a {
313 compatible = "wlf,wm8960";
314 reg = <0x1a>;
315 clocks = <&clks IMX6UL_CLK_SAI2>;
316 clock-names = "mclk";
317 wlf,shared-lrclk;
318 };
......
380 }
第306行,通过&i2c2来引用i2c2节点。
第307行,“clock-frequency”属性就表示 i2c2 时钟为 100KHz。“clock-frequency”就是新添加的属性。
第310行,修改i2c2节点中的status属性,修改为okay。
第312~318行,添加wm8960设备子节点。
以上代码添加在topeet_emmc_4_3.dts文件中,所以不会对其他使用imx6ull.dtsi文件的设备造成影响。重点就是通过&label来访问节点,并添加或修改内容。