「正点原子Linux连载」第四十三章Linux设备树(二)

1)实验平台:正点原子Linux开发板

2)

摘自《正点原子I.MX6U嵌入式Linux驱动开发指南

「正点原子Linux连载」第四十三章Linux设备树(二)

第四十三章Linux设备树


43.3.5 向节点追加或修改内容

产品开发过程中可能面临着频繁的需求更改,比如第一版硬件上有一个IIC接口的六轴芯片MPU6050,第二版硬件又要把这个MPU6050更换为MPU9250等。一旦硬件修改了,我们就要同步的修改设备树文件,毕竟设备树是描述板子硬件信息的文件。假设现在有个六轴芯片fxls8471,fxls8471要接到I.MX6U-ALPHA开发板的I2C1接口上,那么相当于需要在i2c1这个节点上添加一个fxls8471子节点。先看一下I2C1接口对应的节点,打开文件imx6ull.dtsi文件,找到如下所示内容:

示例代码43.3.5.1 i2c1节点

937 i2c1: i2c@021a0000 {

938 #address-cells =<1>;

939 #size-cells =<0>;

940 compatible ="fsl,imx6ul-i2c","fsl,imx21-i2c";

941 reg =<0x021a00000x4000>;

942 interrupts =<GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;

943 clocks =clks IMX6UL_CLK_I2C1>;

944 status ="disabled";

945};

示例代码43.3.5.1就是I.MX6ULL的I2C1节点,现在要在i2c1节点下创建一个子节点,这个子节点就是fxls8471,最简单的方法就是在i2c1下直接添加一个名为fxls8471的子节点,如下所示:

示例代码43.3.5.2 添加fxls8471子节点

937 i2c1: i2c@021a0000 {

938 #address-cells =<1>;

939 #size-cells =<0>;

940 compatible ="fsl,imx6ul-i2c","fsl,imx21-i2c";

941 reg =<0x021a00000x4000>;

942 interrupts =<GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;

943 clocks =clks IMX6UL_CLK_I2C1>;

944 status ="disabled";

945

946 //fxls8471子节点

947 fxls8471@1e {

948 compatible ="fsl,fxls8471";

949 reg =<0x1e>;

950 };

951};

第947~950行就是添加的fxls8471这个芯片对应的子节点。但是这样会有个问题!i2c1节点是定义在imx6ull.dtsi文件中的,而imx6ull.dtsi是设备树头文件,其他所有使用到I.MX6ULL这颗SOC的板子都会引用imx6ull.dtsi这个文件。直接在i2c1节点中添加fxls8471就相当于在其他的所有板子上都添加了fxls8471这个设备,但是其他的板子并没有这个设备啊!因此,按照示例代码43.3.5.2这样写肯定是不行的。

这里就要引入另外一个内容,那就是如何向节点追加数据,我们现在要解决的就是如何向i2c1节点追加一个名为fxls8471的子节点,而且不能影响到其他使用到I.MX6ULL的板子。I.MX6U-ALPHA开发板使用的设备树文件为imx6ull-alientek-emmc.dts,因此我们需要在imx6ull-alientek-emmc.dts文件中完成数据追加的内容,方式如下:

示例代码43.3.5.3 节点追加数据方法

1&i2c1 {

2 /* 要追加或修改的内容 */

3};

第1行,&i2c1表示要访问i2c1这个label所对应的节点,也就是imx6ull.dtsi中的“i2c1: i2c@021a0000”。

第2行,花括号内就是要向i2c1这个节点添加的内容,包括修改某些属性的值。

打开imx6ull-alientek-emmc.dts,找到如下所示内容:

示例代码43.3.5.4 向i2c1节点追加数据

224&i2c1 {

225 clock-frequency =<100000>;

226 pinctrl-names

="default";

227 pinctrl-0=pinctrl_i2c1>;

228 status ="okay";

229

230 mag3110@0e {

231 compatible ="fsl,mag3110";

232 reg =<0x0e>;

233 position =<2>;

234};

235

236 fxls8471@1e

{

237 compatible ="fsl,fxls8471";

238 reg =<0x1e>;

239 position =<0>;

240 interrupt-parent =gpio5>;

241 interrupts =<08>;

242};

243};

示例代码43.3.5.4就是向i2c1节点添加/修改数据,比如第225行的属性“clock-frequency”就表示i2c1时钟为100KHz。“clock-frequency”就是新添加的属性。

第228行,将status属性的值由原来的disabled改为okay。

第230~234行,i2c1子节点mag3110,因为NXP官方开发板在I2C1上接了一个磁力计芯片mag3110,正点原子的I.MX6U-ALPHA开发板并没有使用mag3110。

第236~242行,i2c1子节点fxls8471,同样是因为NXP官方开发板在I2C1上接了fxls8471这颗六轴芯片。

因为示例代码43.3.5.4中的内容是imx6ull-alientek-emmc.dts这个文件内的,所以不会对使用I.MX6ULL这颗SOC的其他板子造成任何影响。这个就是向节点追加或修改内容,重点就是通过&label来访问节点,然后直接在里面编写要追加或者修改的内容。

43.4 创建小型模板设备树

上一节已经对DTS的语法做了比较详细的讲解,本节我们就根据前面讲解的语法,从头到尾编写一个小型的设备树文件。当然了,这个小型设备树没有实际的意义,做这个对的目的是为了掌握设备树的语法。在实际产品开发中,我们是不需要完完全全的重写一个.dts设备树文件,一般都是使用SOC厂商提供好的.dts文件,我们只需要在上面根据自己的实际情况做相应的修改即可。在编写设备树之前要先定义一个设备,我们就以I.MX6ULL这个SOC为例,我们需要在设备树里面描述的内容如下:

①、I.MX6ULL这个Cortex-A7架构的32位CPU。

②、I.MX6ULL内部ocram,起始地址0x00900000,大小为128KB(0x20000)。

③、I.MX6ULL内部aips1域下的ecspi1外设控制器,寄存器起始地址为0x02008000,大小为0x4000。

④、I.MX6ULL内部aips2域下的usbotg1外设控制器,寄存器起始地址为0x02184000,大小为0x4000。

⑤、I.MX6ULL内部aips3域下的rngb外设控制器,寄存器起始地址为0x02284000,大小为0x4000。

为了简单起见,我们就在设备树里面就实现这些内容即可,首先,搭建一个仅含有根节点“/”的基础的框架,新建一个名为myfirst.dts文件,在里面输入如下所示内容:

示例代码43.4.1 设备树基础框架

1/{

2 compatible ="fsl,imx6ull-alientek-evk","fsl,imx6ull";

3}

设备树框架很简单,就一个根节点“/”,根节点里面只有一个compatible属性。我们就在这个基础框架上面将上面列出的内容一点点添加进来。

1、添加cpus节点

首先添加CPU节点,I.MX6ULL采用Cortex-A7架构,而且只有一个CPU,因此只有一个cpu0节点,完成以后如下所示:

示例代码43.4.2 添加CPU0节点

1/{

2 compatible ="fsl,imx6ull-alientek-evk","fsl,imx6ull";

3

4 cpus {

5 #address-cells =<1>;

6 #size-cells =<0>;

7

8 //CPU0节点

9 cpu0: cpu@0 {

10 compatible ="arm,cortex-a7";

11 device_type ="cpu";

12 reg =<0>;

13 };

14 };

15}

第4~14行,cpus节点,此节点用于描述SOC内部的所有CPU,因为I.MX6ULL只有一个CPU,因此只有一个cpu0子节点。

2、添加soc节点

像uart,iic控制器等等这些都属于SOC内部外设,因此一般会创建一个叫做soc的父节点来管理这些SOC内部外设的子节点,添加soc节点以后的myfirst.dts文件内容如下所示:

示例代码43.4.3 添加soc节点

1/{

2 compatible ="fsl,imx6ull-alientek-evk","fsl,imx6ull";

3

4 cpus {

5 #address-cells =<1>;

6 #size-cells =<0>;

7

8//CPU0节点

9 cpu0: cpu@0 {

10 compatible ="arm,cortex-a7";

11 device_type ="cpu"

;

12 reg =<0>;

13};

14};

15

16//soc节点

17 soc {

18 #address-cells =<1>;

19 #size-cells =<1>;

20 compatible ="simple-bus";

21 ranges;

22}

23}

第17~22行,soc节点,soc节点设置#address-cells = <1>,#size-cells = <1>,这样soc子节点的reg属性中起始地占用一个字长,地址空间长度也占用一个字长。

第21行,ranges属性,ranges属性为空,说明子空间和父空间地址范围相同。

3、添加ocram节点

根据第②点的要求,添加ocram节点,ocram是I.MX6ULL内部RAM,因此ocram节点应该是soc节点的子节点。ocram起始地址为0x00900000,大小为128KB(0x20000),添加ocram节点以后myfirst.dts文件内容如下所示:

示例代码43.4.4 添加ocram节点

1/{

2 compatible ="fsl,imx6ull-alientek-evk","fsl,imx6ull";

3

4 cpus {

5 #address-cells =<1>;

6 #size-cells =<

0>;

7

8//CPU0节点

9 cpu0: cpu@0 {

10 compatible ="arm,cortex-a7";

11 device_type ="cpu";

12 reg =<0>;

13};

14};

15

16//soc节点

17 soc {

18 #address-

cells =<1>;

19 #size-cells =<1>;

20 compatible ="simple-bus";

21 ranges;

22

23//ocram节点

24 ocram: sram@00900000 {

25 compatible ="fsl,lpm-sram";

26 reg =<0x009000000x20000>;

27};

28}

29}

第24~27行,ocram节点,第24行节点名字@后面的0x00900000就是ocram的起始地址。第26行的reg属性也指明了ocram内存的起始地址为0x00900000,大小为0x20000。

4、添加aips1、aips2和aips3这三个子节点

I.MX6ULL内部分为三个域:aips1~3,这三个域分管不同的外设控制器,aips1~3这三个域对应的内存范围如表43.4.1所示:


「正点原子Linux连载」第四十三章Linux设备树(二)


表43.4.1 aips1~3地址范围

我们先在设备树中添加这三个域对应的子节点。aips1~3这三个域都属于soc节点的子节点,完成以后的myfirst.dts文件内容如下所示:

示例代码43.4.5 添加aips1~3节点

1/{

2 compatible ="fsl,imx6ull-alientek-evk","fsl,imx6ull";

3

4 cpus {

5 #address-cells =<1>;

6 #size-cells =<0>;

7

8//CPU0节点

9 cpu0: cpu@0 {

10 compatible ="arm,cortex-a7";

11 device_type ="cpu";

12 reg =<0>;

13};

14};

15

16//soc节点

17 soc {

18 #address-cells =<1

>;

19 #size-cells =<1>;

20 compatible ="simple-bus";

21 ranges;

22

23//ocram节点

24 ocram: sram@00900000 {

25 compatible ="fsl,lpm-sram";

26 reg =<0x009000000x20000>;

27};

28

29 //aips1节点

30 aips1: aips-bus@02000000 {

31 compatible ="fsl,aips-bus","simple-bus";

32 #address-cells =<1>;

33 #size-cells =<1>;

34 reg =<0x020000000x100000>;

35 ranges;

36}

37

38//aips2节点

39 aips2: aips-bus@02100000 {

40 compatible ="fsl,aips-bus","simple-bus";

41 #address-cells =<1>;

42 #size-cells =<1>;

43 reg =<0x021000000x100000>;

44 ranges;

45}

46

47//aips3节点

48 aips3: aips-bus@02200000 {

49 compatible ="fsl,aips-bus","simple-bus";

50 #address-cells =<1>;

51 #size-cells =<1>;

52 reg =<0x022000000x100000>;

53 ranges;

54}

55}

56}

第30~36行,aips1节点。

第39~45行,aips2节点。

第48~54行,aips3节点。

5、添加ecspi1、usbotg1和rngb这三个外设控制器节点

最后我们在myfirst.dts文件中加入ecspi1,usbotg1和rngb这三个外设控制器对应的节点,其中ecspi1属于aips1的子节点,usbotg1属于aips2的子节点,rngb属于aips3的子节点。最终的myfirst.dts文件内容如下:

示例代码43.4.6 添加ecspi1、usbotg1和rngb这三个节点

1/{

2 compatible ="fsl,imx6ull-alientek-evk","fsl,imx6ull";

3

4 cpus

{

5 #address-cells =<1>;

6 #size-cells =<0>;

7

8//CPU0节点

9 cpu0: cpu@0 {

10 compatible ="arm,cortex-a7";

11 device_type ="cpu";

12 reg =<0>;

13};

14};

15

16//soc节点

17 soc {

18 #address-cells =<1>;

19 #size-cells =<1>;

20 compatible ="simple-bus";

21 ranges;

22

23//ocram节点

24 ocram: sram@00900000 {

25 compatible

="fsl,lpm-sram";

26 reg =<0x009000000x20000>;

27};

28

29//aips1节点

30 aips1: aips-bus@02000000 {

31 compatible ="fsl,aips-bus","simple-bus";

32 #address-cells =<1>;

33 #size-cells =<1>;

34 reg =<0x020000000x100000>;

35 ranges;

36

37//ecspi1节点

38 ecspi1: ecspi@02008000 {

39 #address-cells =<1>;

40 #size-cells =<0>;

41 compatible ="fsl,imx6ul-ecspi","fsl,imx51-ecspi";

42 reg =<0x020080000x4000>;

43 status ="disabled";

44};

45}

46

47//aips2节点

48 aips2: aips-bus@02100000 {

49 compatible ="fsl,aips-bus","simple-bus";

50 #address-cells =<1>;

51 #size-cells =<1>;

52 reg =<0x021000000x100000>;

53 ranges;

54

55//usbotg1节点

56 usbotg1: usb@02184000 {

57 compatible ="fsl,imx6ul-usb","fsl,imx27-usb";

58 reg =<0x021840000x200>;

59 status ="disabled";

60};

61}

62

63//aips3节点

64 aips3: aips-bus@02200000 {

65 compatible ="fsl,aips-bus","simple-bus";

66 #address-cells =<1>;

67 #size

-cells =<1>;

68 reg =<0x022000000x100000>;

69 ranges;

70

71//rngb节点

72 rngb: rngb@02284000 {

73 compatible ="fsl,imx6sl-rng","fsl,imx-rng","imx-rng";

74 reg =<0x022840000x4000>;

75};

76}

77}

78}

第38~44行,ecspi1外设控制器节点。

第56~60行,usbotg1外设控制器节点。

第72~75行,rngb外设控制器节点。

至此,myfirst.dts这个小型的模板设备树就编写好了,基本和imx6ull.dtsi很像,可以看做是imx6ull.dtsi的缩小版。在myfirst.dts里面我们仅仅是编写了I.MX6ULL的外设控制器节点,像IIC接口,SPI接口下所连接的具体设备我们并没有写,因为具体的设备其设备树属性内容不同,这个等到具体的实验在详细讲解。

43.5 设备树在系统中的体现

Linux内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/device-tree目录下根据节点名字创建不同文件夹,如图43.5.1所示:


「正点原子Linux连载」第四十三章Linux设备树(二)


图43.5.1 根节点“/”的属性以及子节点

图43.5.1就是目录/proc/device-tree目录下的内容,/proc/device-tree目录下是根节点“/”的所有属性和子节点,我们依次来看一下这些属性和子节点。

1、根节点“/”各个属性

在图43.5.1中,根节点属性属性表现为一个个的文件(图中细字体文件),比如图43.5.1中的“#address-cells”、“#size-cells”、“compatible”、“model”和“name”这5个文件,它们在设备树中就是根节点的5个属性。既然是文件那么肯定可以查看其内容,输入cat命令来查看model和compatible这两个文件的内容,结果如图43.5.2所示:


「正点原子Linux连载」第四十三章Linux设备树(二)


图43.5.2 model和compatible文件内容

从图43.5.2可以看出,文件model的内容是“Freescale i.MX6 ULL 14x14 EVK Board”,文件compatible的内容为“fsl,imx6ull-14x14-evkfsl,imx6ull”。打开文件imx6ull-alientek-emmc.dts查看一下,这不正是根节点“/”的model和compatible属性值吗!

2、根节点“/”各子节点

图43.5.1中各个文件夹(途中粗字体文件夹)就是根节点“/”的各个子节点,比如“aliases”、“backlight”、“chosen”和“clocks”等等。大家可以查看一下imx6ull-alientek-emmc.dts和imx6ull.dtsi这两个文件,看看根节点的子节点都有哪些,看看是否和图43.5.1中的一致。

/proc/device-tree目录就是设备树在根文件系统中的体现,同样是按照树形结构组织的,进入/proc/device-tree/soc目录中就可以看到soc节点的所有子节点,如图43.5.3所示:


「正点原子Linux连载」第四十三章Linux设备树(二)


图43.5.3 soc节点的所有属性和子节点

和根节点“/”一样,图43.5.3中的所有文件分别为soc节点的属性文件和子节点文件夹。大家可以自行查看一下这些属性文件的内容是否和imx6ull.dtsi中soc节点的属性值相同,也可以进入“busfreq”这样的文件夹里面查看soc节点的子节点信息。

43.6 特殊节点

在根节点“/”中有两个特殊的子节点:aliases和chosen,我们接下来看一下这两个特殊的子节点。

43.6.1 aliases子节点

打开imx6ull.dtsi文件,aliases节点内容如下所示:

示例代码43.6.1.1 aliases子节点

18 aliases {

19 can0 =&flexcan1;

20 can1

=&flexcan2;

21 ethernet0 =&fec1;

22 ethernet1 =&fec2;

23 gpio0 =&gpio1;

24 gpio1 =&gpio2;

......

42 spi0 =&ecspi1;

43 spi1 =&ecspi2;

44 spi2 =&ecspi3;

45 spi3 =&ecspi4;

46 usbphy0 =&usbphy1;

47 usbphy1 =&usbphy2;

48};

单词aliases的意思是“别名”,因此aliases节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。不过我们一般会在节点命名的时候会加上label,然后通过&label来访问节点,这样也很方便,而且设备树里面大量的使用&label的形式来访问节点。

43.6.2 chosen子节点

chosen并不是一个真实的设备,chosen节点主要是为了uboot向Linux内核传递数据,重点是bootargs参数。一般.dts文件中chosen节点通常为空或者内容很少,imx6ull-alientek-emmc.dts中chosen节点内容如下所示:

示例代码43.6.2.1 chosen子节点

18 chosen {

19 stdout-path =&uart1;

20};

从示例代码43.6.2.1中可以看出,chosen节点仅仅设置了属性“stdout-path”,表示标准输出使用uart1。但是当我们进入到/proc/device-tree/chosen目录里面,会发现多了bootargs这个属性,如图43.6.2.1所示:


「正点原子Linux连载」第四十三章Linux设备树(二)


图43.6.2.1 chosen节点目录

输入cat命令查看bootargs这个文件的内容,结果如图43.6.2.2所示:


「正点原子Linux连载」第四十三章Linux设备树(二)


图43.6.2.2 bootargs文件内容

从图43.6.2.2可以看出,bootargs这个文件的内容为“console=ttymxc0,115200……”,这个不就是我们在uboot中设置的bootargs环境变量的值吗?现在有两个疑点:

①、我们并没有在设备树中设置chosen节点的bootargs属性,那么图43.6.2.1中bootargs这个属性是怎么产生的?

②、为何bootargs文件的内容和uboot中bootargs环境变量的值一样?它们之间有什么关系?

前面讲解uboot的时候说过,uboot在启动Linux内核的时候会将bootargs的值传递给Linux内核,bootargs会作为Linux内核的命令行参数,Linux内核启动的时候会打印出命令行参数(也就是uboot传递进来的bootargs的值),如图43.6.2.3所示:


「正点原子Linux连载」第四十三章Linux设备树(二)


图43.6.2.3 命令行参数

既然chosen节点的bootargs属性不是我们在设备树里面设置的,那么只有一种可能,那就是uboot自己在chosen节点里面添加了bootargs属性!并且设置bootargs属性的值为bootargs环境变量的值。因为在启动Linux内核之前,只有uboot知道bootargs环境变量的值,并且uboot也知道.dtb设备树文件在DRAM中的位置,因此uboot的“作案”嫌疑最大。在uboot源码中全局搜索“chosen”这个字符串,看看能不能找到一些蛛丝马迹。果然不出所料,在common/fdt_support.c文件中发现了“chosen”的身影,fdt_support.c文件中有个fdt_chosen函数,此函数内容如下所示:

示例代码43.6.2.2 uboot源码中的fdt_chosen函数

275int fdt_chosen(void*fdt)

276{

277int nodeoffset;

278int err;

279char*str;/* used to set string properties */

280

281 err = fdt_check_header(fdt);

282if(err <0){

283 printf("fdt_chosen: %s\\n", fdt_strerror(err));

284return err;

285}

286

287/* find or create "/chosen" node. */

288 nodeoffset =

fdt_find_or_add_subnode(fdt,0,"chosen");

289if(nodeoffset <0)

290return nodeoffset;

291

292 str = getenv("bootargs");

293if(str){

294 err = fdt_setprop(fdt, nodeoffset,"bootargs",

str,

295 strlen(str)+1);

296if(err <0){

297 printf("WARNING: could not set bootargs %s.\\n",

298 fdt_strerror(err));

299return err;

300}

301}

302

303return

fdt_fixup_stdout(fdt, nodeoffset);

304}

第288行,调用函数fdt_find_or_add_subnode从设备树(.dtb)中找到chosen节点,如果没有找到的话就会自己创建一个chosen节点。

第292行,读取uboot中bootargs环境变量的内容。

第294行,调用函数fdt_setprop向chosen节点添加bootargs属性,并且bootargs属性的值就是环境变量bootargs的内容。

证据“石锤”了,就是uboot中的fdt_chosen函数在设备树的chosen节点中加入了bootargs属性,并且还设置了bootargs属性值。接下来我们顺着fdt_chosen函数一点点的抽丝剥茧,看看都有哪些函数调用了fdt_chosen,一直找到最终的源头。这里我就不卖关子了,直接告诉大家整个流程是怎么样的,见图43.6.2.4:


「正点原子Linux连载」第四十三章Linux设备树(二)


图43.6.2.4 fdt_chosen函数调用流程

图43.6.2.4中框起来的部分就是函数do_bootm_linux函数的执行流程,也就是说do_bootm_linux函数会通过一系列复杂的调用,最终通过fdt_chosen函数在chosen节点中加入了bootargs属性。而我们通过bootz命令启动Linux内核的时候会运行do_bootm_linux函数,至此,真相大白,一切事情的源头都源于如下命令:

bootz 80800000 – 83000000

当我们输入上述命令并执行以后,do_bootz函数就会执行,然后一切就按照图43.6.2.4中所示的流程开始运行。

43.7 Linux内核解析DTB文件

Linux内核在启动的时候会解析DTB文件,然后在/proc/device-tree目录下生成相应的设备树节点文件。接下来我们简单分析一下Linux内核是如何解析DTB文件的,流程如图43.7.1所示:


「正点原子Linux连载」第四十三章Linux设备树(二)


图43.7.1 设备树节点解析流程。

从图43.7.1中可以看出,在start_kernel函数中完成了设备树节点解析的工作,最终实际工作的函数为unflatten_dt_node。

43.8 绑定信息文档

设备树是用来描述板子上的设备信息的,不同的设备其信息不同,反映到设备树中就是属性不同。那么我们在设备树中添加一个硬件对应的节点的时候从哪里查阅相关的说明呢?在Linux内核源码中有详细的.txt文档描述了如何添加节点,这些.txt文档叫做绑定文档,路径为:Linux源码目录/Documentation/devicetree/bindings,如图43.8.1所示:


「正点原子Linux连载」第四十三章Linux设备树(二)


图43.8.1 绑定文档

比如我们现在要想在I.MX6ULL这颗SOC的I2C下添加一个节点,那么就可以查看Documentation/devicetree/bindings/i2c/i2c-imx.txt,此文档详细的描述了I.MX系列的SOC如何在设备树中添加I2C设备节点,文档内容如下所示:

* Freescale Inter IC (I2C) and High Speed Inter IC (HS-I2C) for i.MX


Required properties:

- compatible :

- "fsl,imx1-i2c" for I2C compatible with the one integrated on i.MX1 SoC

- "fsl,imx21-i2c" for I2C compatible with the one integrated on i.MX21 SoC

- "fsl,vf610-i2c" for I2C compatible with the one integrated on Vybrid vf610 SoC

- reg : Should contain I2C/HS-I2C registers location and length

- interrupts : Should contain I2C/HS-I2C interrupt

- clocks : Should contain the I2C/HS-I2C clock specifier


Optional properties:

- clock-frequency : Constains desired I2C/HS-I2C bus clock frequency in Hz.

The absence of the propoerty indicates the default frequency 100 kHz.

- dmas: A list of two dma specifiers, one for each entry in dma-names.

- dma-names: should contain "tx" and "rx".


Examples:


i2c@83fc4000 { /* I2C2 on i.MX51 */

compatible = "fsl,imx51-i2c", "fsl,imx21-i2c";

reg = <0x83fc4000 0x4000>;

interrupts = <63>;

};


i2c@70038000 { /* HS-I2C on i.MX51 */

compatible = "fsl,imx51-i2c", "fsl,imx21-i2c";

reg = <0x70038000 0x4000>;

interrupts = <64>;

clock-frequency = <400000>;

};


i2c0: i2c@40066000 { /* i2c0 on vf610 */

compatible = "fsl,vf610-i2c";

reg = <0x40066000 0x1000>;

interrupts =<0 71 0x04>;

dmas = ,

;

dma-names = "rx","tx";

};

有时候使用的一些芯片在Documentation/devicetree/bindings目录下找不到对应的文档,这个时候就要咨询芯片的提供商,让他们给你提供参考的设备树文件。

43.9 设备树常用OF操作函数

设备树描述了设备的详细信息,这些信息包括数字类型的、字符串类型的、数组类型的,我们在编写驱动的时候需要获取到这些信息。比如设备树使用reg属性描述了某个外设的寄存器地址为0X02005482,长度为0X400,我们在编写驱动的时候需要获取到reg属性的0X02005482和0X400这两个值,然后初始化外设。Linux内核给我们提供了一系列的函数来获取设备树中的节点或者属性信息,这一系列的函数都有一个统一的前缀“of_”,所以在很多资料里面也被叫做OF函数。这些OF函数原型都定义在include/linux/of.h文件中。

43.9.1 查找节点的OF函数

设备都是以节点的形式“挂”到设备树上的,因此要想获取这个设备的其他属性信息,必须先获取到这个设备的节点。Linux内核使用device_node结构体来描述一个节点,此结构体定义在文件include/linux/of.h中,定义如下:

示例代码43.3.9.1 device_node节点

49struct device_node {

50 constchar*name; /* 节点名字 */

51 constchar*type; /* 设备类型 */

52 phandle phandle;

53 constchar*full_name; /* 节点全名 */

54 struct fwnode_handle fwnode;

55

56 struct property *properties; /* 属性 */

57 struct property *deadprops; /* removed属性 */

58 struct device_node *

parent; /* 父节点 */

59 struct device_node *child; /* 子节点 */

60 struct device_node *sibling;

61 struct kobject kobj;

62 unsignedlong _flags;

63 void*data;

64 #if defined(CONFIG_SPARC)

65 constchar*path_component_name;

66 unsignedint unique_id;

67 struct of_irq_controller *

irq_trans;

68 #endif

69};

与查找节点有关的OF函数有5个,我们依次来看一下。

1、of_find_node_by_name函数

of_find_node_by_name函数通过节点名字查找指定的节点,函数原型如下:

struct device_node *of_find_node_by_name(struct device_node *from,

const char *name);

函数参数和返回值含义如下:

from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。

name:要查找的节点名字。

返回值:找到的节点,如果为NULL表示查找失败。

2、of_find_node_by_type函数

of_find_node_by_type函数通过device_type属性查找指定的节点,函数原型如下:

struct device_node *of_find_node_by_type(struct device_node *from,const char *type)

函数参数和返回值含义如下:

from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。

type:要查找的节点对应的type字符串,也就是device_type属性值。

返回值:找到的节点,如果为NULL表示查找失败。

3、of_find_compatible_node函数

of_find_compatible_node函数根据device_type和compatible这两个属性查找指定的节点,函数原型如下:

struct device_node *of_find_compatible_node(struct device_node *from,

const char *type,

const char *compatible)

函数参数和返回值含义如下:

from

:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。

type:要查找的节点对应的type字符串,也就是device_type属性值,可以为NULL,表示忽略掉device_type属性。

compatible:要查找的节点所对应的compatible属性列表。

返回值:找到的节点,如果为NULL表示查找失败

4、of_find_matching_node_and_match函数

of_find_matching_node_and_match函数通过of_device_id匹配表来查找指定的节点,函数原型如下:

struct device_node *of_find_matching_node_and_match(struct device_node *from,

const struct of_device_id *matches,

const struct of_device_id **match)

函数参数和返回值含义如下:

from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。

matches

:of_device_id匹配表,也就是在此匹配表里面查找节点。

match:找到的匹配的of_device_id。

返回值:找到的节点,如果为NULL表示查找失败

5、of_find_node_by_path函数

of_find_node_by_path函数通过路径来查找指定的节点,函数原型如下:

inline struct device_node *of_find_node_by_path(const char *path)

函数参数和返回值含义如下:

path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是backlight这个节点的全路径。

返回值:找到的节点,如果为NULL表示查找失败

43.9.2 查找父/子节点的OF函数

Linux内核提供了几个查找节点对应的父节点或子节点的OF函数,我们依次来看一下。

1、of_get_parent函数

of_get_parent函数用于获取指定节点的父节点(如果有父节点的话),函数原型如下:

struct device_node *of_get_parent(const struct device_node *node)

函数参数和返回值含义如下:

node:要查找的父节点的节点。

返回值:找到的父节点。

2、of_get_next_child函数

of_get_next_child函数用迭代的查找子节点,函数原型如下:

struct device_node *of_get_next_child(const struct device_node *node,

struct device_node *prev)

函数参数和返回值含义如下:

node:父节点。

prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始。

返回值:找到的下一个子节点。

43.9.3 提取属性值的OF函数

节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要,Linux内核中使用结构体property表示属性,此结构体同样定义在文件include/linux/of.h中,内容如下:

示例代码43.9.3.1 property结构体

35struct property {

36 char*name; /* 属性名字 */

37 int length; /* 属性长度 */

38 void*value; /* 属性值 */

39 struct property *next; /* 下一个属性 */

40 unsignedlong _flags;

41 unsignedint unique_id;

42 struct bin_attribute attr;

43};

Linux内核也提供了提取属性值的OF函数,我们依次来看一下。

1、of_find_property函数

of_find_property函数用于查找指定的属性,函数原型如下:

property *of_find_property(const struct device_node *np,

const char *name,

int *lenp)

函数参数和返回值含义如下:

np:设备节点。

name:属性名字。

lenp:属性值的字节数

返回值:找到的属性。

2、of_property_count_elems_of_size函数

of_property_count_elems_of_size函数用于获取属性中元素的数量,比如reg属性值是一个数组,那么使用此函数可以获取到这个数组的大小,此函数原型如下:

int of_property_count_elems_of_size(const struct device_node *np,

const char *propname,

int elem_size)

函数参数和返回值含义如下:

np:设备节点。

proname:需要统计元素数量的属性名字。

elem_size:元素长度。

返回值:得到的属性元素数量。

3、of_property_read_u32_index函数

of_property_read_u32_index函数用于从属性中获取指定标号的u32类型数据值(无符号32位),比如某个属性有多个u32类型的值,那么就可以使用此函数来获取指定标号的数据值,此函数原型如下:

int of_property_read_u32_index(const struct device_node *np,

const char *propname,

u32 index,

u32 *out_value)

函数参数和返回值含义如下:

np:设备节点。

proname:要读取的属性名字。

index:要读取的值标号。

out_value:读取到的值

返回值:0读取成功,负值,读取失败,-EINVAL表示属性不存在,-ENODATA表示没有要读取的数据,-EOVERFLOW表示属性值列表太小。

4、 of_property_read_u8_array函数

of_property_read_u16_array函数

of_property_read_u32_array函数

of_property_read_u64_array函数

这4个函数分别是读取属性中u8、u16、u32和u64类型的数组数据,比如大多数的reg属性都是数组数据,可以使用这4个函数一次读取出reg属性中的所有数据。这四个函数的原型如下:

int of_property_read_u8_array(const struct device_node *np,

const char *propname,

u8 *out_values,

size_t sz)

int of_property_read_u16_array(const struct device_node *np,

const char *propname,

u16 *out_values,

size_t sz)

int of_property_read_u32_array(const struct device_node *np,

const char *propname,

u32 *out_values,

size_t sz)

int of_property_read_u64_array(const struct device_node *np,

const char *propname,

u64 *out_values,

size_t sz)

函数参数和返回值含义如下:

np:设备节点。

proname:要读取的属性名字。

out_value:读取到的数组值,分别为u8、u16、u32和u64。

sz:要读取的数组元素数量。

返回值:0,读取成功,负值,读取失败,-EINVAL表示属性不存在,-ENODATA表示没有要读取的数据,-EOVERFLOW表示属性值列表太小。

5、of_property_read_u8函数

of_property_read_u16函数

of_property_read_u32函数

of_property_read_u64函数

有些属性只有一个整形值,这四个函数就是用于读取这种只有一个整形值的属性,分别用于读取u8、u16、u32和u64类型属性值,函数原型如下:

int of_property_read_u8(const struct device_node *np,

const char *propname,

u8 *out_value)

int of_property_read_u16(const struct device_node *np,

const char *propname,

u16 *out_value)

int of_property_read_u32(const struct device_node *np,

const char *propname,

u32 *out_value)

int of_property_read_u64(const struct device_node *np,

const char *propname,

u64 *out_value)

函数参数和返回值含义如下:

np:设备节点。

proname:要读取的属性名字。

out_value

:读取到的数组值。

返回值:0,读取成功,负值,读取失败,-EINVAL表示属性不存在,-ENODATA表示没有要读取的数据,-EOVERFLOW表示属性值列表太小。

6、of_property_read_string函数

of_property_read_string函数用于读取属性中字符串值,函数原型如下:

int of_property_read_string(struct device_node *np,

const char *propname,

const char **out_string)

函数参数和返回值含义如下:

np:设备节点。

proname:要读取的属性名字。

out_string:读取到的字符串值。

返回值:0,读取成功,负值,读取失败。

7、of_n_addr_cells函数

of_n_addr_cells函数用于获取#address-cells属性值,函数原型如下:

int of_n_addr_cells(struct device_node *np)

函数参数和返回值含义如下:

np:设备节点。

返回值:获取到的#address-cells属性值。

8、of_n_size_cells函数

of_size_cells函数用于获取#size-cells属性值,函数原型如下:

int of_n_size_cells(struct device_node *np)

函数参数和返回值含义如下:

np:设备节点。

返回值:获取到的#size-cells属性值。

43.9.4 其他常用的OF函数

1、of_device_is_compatible函数

of_device_is_compatible函数用于查看节点的compatible属性是否有包含compat指定的字符串,也就是检查设备节点的兼容性,函数原型如下:

int of_device_is_compatible(const struct device_node *device,

const char *compat)

函数参数和返回值含义如下:

device:设备节点。

compat:要查看的字符串。

返回值:0,节点的compatible属性中包含compat指定的字符串;其他值,节点的compatible属性中不包含compat指定的字符串。

2、of_get_address函数

of_get_address函数用于获取地址相关属性,主要是“reg”或者“assigned-addresses”属性值,函数属性如下:

const __be32 *of_get_address(struct device_node *dev,

int index,

u64 *size,

unsigned int *flags)

函数参数和返回值含义如下:

dev:设备节点。

index:要读取的地址标号。

size:地址长度。

flags:参数,比如IORESOURCE_IO、IORESOURCE_MEM等

返回值:读取到的地址数据首地址,为NULL的话表示读取失败。

3、of_translate_address函数

of_translate_address函数负责将从设备树读取到的地址转换为物理地址,函数原型如下:

u64 of_translate_address(struct device_node *dev,

const __be32 *in_addr)

函数参数和返回值含义如下:

dev:设备节点。

in_addr:要转换的地址。

返回值:得到的物理地址,如果为OF_BAD_ADDR的话表示转换失败。

4、of_address_to_resource函数

IIC、SPI、GPIO等这些外设都有对应的寄存器,这些寄存器其实就是一组内存空间,Linux内核使用resource结构体来描述一段内存空间,“resource”翻译出来就是“资源”,因此用resource结构体描述的都是设备资源信息,resource结构体定义在文件include/linux/ioport.h中,定义如下:

示例代码43.9.4.1 resource结构体

18struct resource {

19 resource_size_t start;

20 resource_size_t end;

21 constchar*name;

22 unsignedlong flags;

23 struct resource *parent,*sibling,*child;

24};

对于32位的SOC来说,resource_size_t是u32类型的。其中start表示开始地址,end表示结束地址,name是这个资源的名字,flags是资源标志位,一般表示资源类型,可选的资源标志定义在文件include/linux/ioport.h中,如下所示:

示例代码43.9.4.2 资源标志

1 #define IORESOURCE_BITS 0x000000ff

2 #define IORESOURCE_TYPE_BITS 0x00001f00

3 #define IORESOURCE_IO 0x00000100

4 #define IORESOURCE_MEM 0x00000200

5 #define IORESOURCE_REG 0x00000300

6 #define IORESOURCE_IRQ 0x00000400

7 #define IORESOURCE_DMA 0x00000800

8 #define IORESOURCE_BUS 0x00001000

9 #define IORESOURCE_PREFETCH 0x00002000

10 #define IORESOURCE_READONLY 0x00004000

11 #define IORESOURCE_CACHEABLE 0x00008000

12 #define IORESOURCE_RANGELENGTH 0x00010000

13 #define IORESOURCE_SHADOWABLE 0x00020000

14 #define IORESOURCE_SIZEALIGN 0x00040000

15 #define IORESOURCE_STARTALIGN 0x00080000

16 #define IORESOURCE_MEM_64 0x00100000

17 #define IORESOURCE_WINDOW 0x00200000

18 #define IORESOURCE_MUXED 0x00400000

19 #define IORESOURCE_EXCLUSIVE 0x08000000

20 #define IORESOURCE_DISABLED 0x10000000

21 #define IORESOURCE_UNSET 0x20000000

22 #define IORESOURCE_AUTO 0x40000000

23 #define IORESOURCE_BUSY 0x80000000

大家一般最常见的资源标志就是IORESOURCE_MEM、IORESOURCE_REG和IORESOURCE_IRQ等。接下来我们回到of_address_to_resource函数,此函数看名字像是从设备树里面提取资源值,但是本质上就是将reg属性值,然后将其转换为resource结构体类型,函数原型如下所示

int of_address_to_resource(struct device_node *dev,

int index,

struct resource *r)

函数参数和返回值含义如下:

dev:设备节点。

index

:地址资源标号。

r:得到的resource类型的资源值。

返回值:0,成功;负值,失败。

5、of_iomap函数

of_iomap函数用于直接内存映射,以前我们会通过ioremap函数来完成物理地址到虚拟地址的映射,采用设备树以后就可以直接通过of_iomap函数来获取内存地址所对应的虚拟地址,不需要使用ioremap函数了。当然了,你也可以使用ioremap函数来完成物理地址到虚拟地址的内存映射,只是在采用设备树以后,大部分的驱动都使用of_iomap函数了。of_iomap函数本质上也是将reg属性中地址信息转换为虚拟地址,如果reg属性有多段的话,可以通过index参数指定要完成内存映射的是那一段,of_iomap函数原型如下:

void __iomem *of_iomap(struct device_node *np,

int index)

函数参数和返回值含义如下:

np:设备节点。

index:reg属性中要完成内存映射的段,如果reg属性只有一段的话index就设置为0。

返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话表示内存映射失败。

关于设备树常用的OF函数就先讲解到这里,Linux内核中关于设备树的OF函数不仅仅只有前面讲的这几个,还有很多OF函数我们并没有讲解,这些没有讲解的OF函数要结合具体的驱动,比如获取中断号的OF函数、获取GPIO的OF函数等等,这些OF函数我们在后面的驱动实验中再详细的讲解。

关于设备树就讲解到这里,关于设备树我们重点要了解一下几点内容:

①、DTS、DTB和DTC之间的区别,如何将.dts文件编译为.dtb文件。

②、设备树语法,这个是重点,因为在实际工作中我们是需要修改设备树的。

③、设备树的几个特殊子节点。

④、关于设备树的OF操作函数,也是重点,因为设备树最终是被驱动文件所使用的,而驱动文件必须要读取设备树中的属性信息,比如内存信息、GPIO信息、中断信息等等。要想在驱动中读取设备树的属性值,那么就必须使用Linux内核提供的众多的OF函数。

从下一章开始所以的Linux驱动实验都将采用设备树,从最基本的点灯,到复杂的音频、网络或块设备等驱动。将会带领大家由简入深,深度剖析设备树,最终掌握基于设备树的驱动开发技能。


分享到:


相關文章: