【北京迅为】i.MX6ULL终结者DTS设备树语法结构

一般情况下,我们不会从头编写一个完整的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 	
#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等等。下面我们就来介绍一下设备节点的具体信息。

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        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。

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终结者DTS设备树语法结构_第1张图片

你可能感兴趣的:(#,第四部分,Linux驱动开发,linux,开发平台,嵌入式)