文章目录

  • 1 dtsi头文件
  • 2 设备节点信息
  • 3 设备节点及label的命名
  • 4 标准属性
  • 5 根节点compatible属性
  • 6 在设备节点中添加或修改内容

一般情况下,我们不会从头编写一个完整的dts文件,SOC厂商一般会直接提供一个有着基本框架的dts文件,当需要添加自己的板子设备树文件时,基于厂商提供的dts文件修改即可。所以我们要了解dts设备树文件的语法,这样我们才清楚如何添加我们自己的设备。
在本章节中,我们以topeet_emmc_4_3.dts设备树文件为例,来具体讲解一下dts文件的语法结构,如何添加一个设备硬件信息。

1 dtsi头文件

由于一个SOC可能对应多个ARM设备,这些dts文件势必包含许多共同的部分,Linux内核为了简化,把SOC公用的部分或者多个设备共同的部分提炼为.dtsi文件,类似于C语言的头文件。.dtsi文件也可以包含其他的.dtsi。在topeet_emmc_4_3.dts文件中有如下内容:

#include     <dt-bindings/input/input.h>
#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 所有的外设都描述的清清楚楚,比如 ecspi1~4uart1~8usbphy1~2i2c1~4等等。下面我们就来介绍一下设备节点的具体信息。

2 设备节点信息

设备树是一个包含节点和属性的简单树状结构。属性就是键-值对,而节点可以同时包含属性和子节点。下面先来看一个设备树结构模板:

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等,它们的具体作用是什么,还有发现节点的命名格式不有所不同,那么节点应该如何命名?在下面的内容中来一一说明。

3 设备节点及label的命名

在前面的代码中,我们注意到节点和子节点之间的命名有所不同,它们都遵循了下面的命名格式:
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>;};
};

4 标准属性

节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以自定义属性。除了用户自定义属性,有很多属性是标准属性,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。

5 根节点compatible属性

在设备树文件中,每个节点都有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        848174   #define MACH_TYPE_LOFT                 849175   #define MACH_TYPE_MX21ADS              851176   #define MACH_TYPE_AMS_DELTA            862177    #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。

6 在设备节点中添加或修改内容

当我们开发一个新的开发板时,需要不断的向设备树设备节点下添加和修改内容。比如我们的开发板需要一个音频设备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来访问节点,并添加或修改内容。

i.MX6ULL终结者Linux设备树DTS设备树语法结构相关推荐

  1. Linux加载DTS设备节点的过程(以高通8974平台为例)

    DTS是Device Tree Source的缩写,用来描述设备的硬件细节.在过去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码, ...

  2. i.MX6ULL终结者Linux RS232/485驱动实验i.MX6UL UART驱动分析

    文章目录 1 uart的platform驱动框架 2 uart_driver初始化 3 uart_port初始化和注册 4 imx_pops结构体 1 uart的platform驱动框架 首先看一下在 ...

  3. i.MX6ULL终结者Linux文件系统开发制作最小文件系统镜像

    文章目录 1 BusyBox工具简介 2 Busybox 制作最小文件系统 2.1 准备源码 2.2 编译环境 2.3 支持中文 2.4 配置Busybox 2.5 编译Busybox 2.6 整理最 ...

  4. i.MX6ULL终结者Linux I2C驱动实验IMX6ULL的I2C总线驱动分析

    在上一节中我们了解了I2C框架分为I2C核心.I2C总线驱动和I2C设备驱动三部分.其中I2C总线驱动就是SOC的I2C控制器驱动,一般来说都是SOC厂家实现好的.而I2C设备驱动是用户根据自己不同的 ...

  5. i.MX6ULL终结者Linux异步通知实验编写实验程序

    文章目录 1 驱动程序编写 2 应用测试程序 3 运行测试 1 驱动程序编写 本实验例程路径:i.MX6UL终结者光盘资料/06_Linux驱动例程/13_key_signal 驱动程序在key_po ...

  6. i.MX6ULL终结者Linux阻塞和非阻塞IO实验非阻塞IO实验

    文章目录 1 编写驱动程序 2 编写应用测试程序 3 运行测试 1 编写驱动程序 本实验例程路径:i.MX6UL终结者光盘资料/06_Linux驱动例程/12_key_poll 创建key_poll. ...

  7. i.MX6ULL终结者Linux 4G通信实验EC20 4G模块配置

    文章目录 1 添加USB设备信息 2 Linux内核配置 1 添加USB设备信息 要使用EC20 4G模块首先需要在Linux内核中添加EC20的USB信息,这样内核启动后,才能正确识别EC20 4G ...

  8. 迅为i.MX6ULL终结者Linux中断实验运行测试

    文章目录 1 编译驱动程序 2 编译应用测试程序 3 运行测试 1 编译驱动程序 和前面章节中驱动测试程序一样需要一个Makefile文件,只是将obj-m的值改为key_irq.o,Makefile ...

  9. i.MX6ULL终结者Linux 电容触摸屏实验硬件原理图

    在本实验中使用迅为的7寸屏为例,使用的是FT5426触摸芯片. 图 1 从原理图中得知,7寸屏使用I2C2,触摸屏复位引脚为SNVS_TAMPER9,中断引脚为GPIO_9.

最新文章

  1. 自己看着视频的理解:设计模式之abstractfactory模式(2)
  2. C#Excel文件读取问题及解决办法
  3. 【自动驾驶】16.计算机视觉:相机成像原理:世界坐标系、相机坐标系、图像坐标系、像素坐标系之间的转换
  4. Jmeter获取性能指标
  5. PHP cURL应用实现模拟登录与采集使用方法详解
  6. 下载丨Oracle 12c最佳参数实践
  7. Python 基础知识学习笔记——NumPy
  8. 基于Linux和MiniGUI的嵌入式系统软件开发指南(五)
  9. 【Redis】redis 持久化 RDB 和 AOF
  10. stl之list双向链表容器应用基础
  11. 【OFDM通信】基于matlab OFDM通信系统仿真【含Matlab源码 1005期】
  12. STM32F103C8t6程序下载
  13. 符合 Qi 规范的移动设备无线充电解决方案
  14. 国庆专属头像、国旗专属头像一键生成源代码
  15. 华钜同创:拯救你的亚马逊销量!你需要知道这些技巧
  16. 传时珍医药伟业谱本草科学新篇——访李时珍医药集团董事长林朝辉
  17. (转)腾讯区块链方案白皮书:底层技术平台及五大场景解决方案
  18. asp.net core web 解决方案多项目模板制作打包总结
  19. 如何用javaweb实现网上招聘系统、基于SSM+mysql的校园大学生兼职招聘平台
  20. 解决canon LBP 6200只能打印单面的问题

热门文章

  1. 极客时间 DDD 学习笔记03
  2. 基于RGB-D的场景实时三维重建综述
  3. 架构师的软实力之架构透视
  4. 中电信披露十一大电话骗术 提醒勿轻易汇款
  5. 腾讯乐固的加固和多渠道打包客户端配置
  6. 计算机网络应用和ps的实训报告,大学计算机网络实验报告(合集).doc
  7. matlab绘制步进频率信号,雷达信号处理MATALB模拟---频率步进信号SFWC
  8. 艺术设计和计算机科学技术的相互影响与关系,电脑与艺术设计的关系
  9. 人工智能 Java 坦克机器人系列: 遗传算法
  10. CVE漏洞攻击链案例描述