dts从uboot加载到kernel使用案例的分析

来源:互联网 发布:淘宝店铺没销量怎么办 编辑:程序博客网 时间:2024/04/30 07:14

以下凡是涉及代码分析的地方,可能不同平台的处理方式有所区别,具体情况是以自己手头上的平台代码为准。

曾经在空间里面转载过一篇《ARM Linux 3.x的设备树(Device Tree》的文章,当时看了几遍,仍然不知所云。后来在工作中才慢慢地对dts有所领悟。所以,在这里想用简单的词语,描述一下自己对dts的理解。

首先,dts是什么?很简单,一句话:为了瘦简内核、去掉部分冗余的代码,而用一种简单的方式(语言)把硬件设备相关信息描述出来,这就是dts。

既然命名为“device tree”(本文用dts来简称),顾名思义,它是以一种树的形态存在:树干,分支,叶子。而且这种“树”的结构形式是dts特有的。下面会对大家进行分析。

在dts里面,结点(node)和属性(property)的概念十分重要,这也是组成dts模型最重要的东西。结点,不用多说大家都明白。而属性,说的就是设备相关的name,value等信息。

在讲dts之前,先来讲讲uboot是怎样去加载dtb的。

dtb是dts编译出来的二进制文件,以“.dtb”结尾,dtb在启动阶段,会由加载到某一内存地址。当启动内核,驱动里面调用dts相关的api的时候,会到这个地址里面去寻找匹配的字符串,如果符合,则读取相关的配置信息。

这样说起来,好像跟全志代码里面配置文件的处理方式有点相似。这里没有打广告的意思,只是接触过全志代码,做个比较罢了。

好,接下来我们看到uboot代码里面:

首先是boards.cfg 里面定义 DEFAULT_FDT_FILE 的名称,这个名词必须和内核编译出来的dtb的名称一样。这样才能保证dtb被load到内存里面。

[html] view plaincopy
  1. DEFAULT_FDT_FILE="xxx.dtb",DDR_MB=1024,SYS_USE_SPINOR  

接下来,根据DEFAULT_FDT_FILE 来定义 fdt_file 的变量。

include/configs/xxxcommon.h

[html] view plaincopy
  1. "fdt_file=" CONFIG_DEFAULT_FDT_FILE "\0" \  

和相关环节变量的定义:

[html] view plaincopy
  1. 208     "loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}\0" \  
  2. 209     "mmcboot=echo Booting from mmc ...; " \  
  3. 210         "run mmcargs; " \  
  4. 211         "if test ${boot_fdt} = yes || test ${boot_fdt} = try; then " \  
  5. 212             "if run loadfdt; then " \  

在board.c 里面设置环境变量,在这过程中,会调用上一步骤的run loadfdt 动作,把dtb 装载到内存中。

arch/arm/lib/board.c

[html] view plaincopy
  1. sprintf(buf, "fatload sata 0:1 ${fdt_addr} ${fdt_file}");  
  2. •••  
  3. sprintf(buf, "dcache off; sata init; run loaduimage; run loadbootscript; run sataargs; run loadfdt; bootm ${loadaddr} - ${fdt_addr}");  
  4. setenv("bootcmd", buf);  

在这里,uboot里面的代码不详细分析,有兴趣的童鞋可以去看看。

接下来是dts结点(node)和属性(property)的分析,以一个i2c的例子为参考。

dts源码在scripts/dtc目录下

参考文档在Documentation/devicetree目录下

如在i2c上挂的一个设备:

[html] view plaincopy
  1. &i2c2{  
  2. •••  
  3.     #address-cells = <1>;  
  4.     #size-cells = <0>;  
  5.   
  6.    I2c@1{   
  7.     reg=<1>;    
  8.   
  9.     #address-cells = <1>;   
  10.     #size-cells = <0>;   
  11.     •••  
  12.         ec@30 {   
  13.         compatible = “fsl , ec”;   
  14.         reg=<0x30>;   
  15.               };  
  16.         };  
  17. };  

由上到下:

I2c@1 :

         reg = <address1 length1 [address2 length2][address3 length3]

       address为1个或多个32位的整型(即cell),而length则为cell的列表或者为空(若#size-cells = 0)

reg=<1> :

         这个reg是被上面的#address-cells 和#size-cells决定的

#address-cells =<1> :

       决定了,以下包含的节点@后面只能接一个参数,即地址

#size-cells =<0> :

       决定了,以下包含的节点里面reg= < , length> , length为空(不填)

       以下包含的节点里面reg=<,, length>, length 前面的参数即为@后面的参数

       如果@后面只有一个参数,则为地址

       如果@后面有两个参数,则为“片选+相对该片选的基地址”

       如果为address-cells 为1 ,则@直接跟地址,length一般为空,即size-cells为0

       当address-cells为 ,则length一般不为空,即size-cells不为0

ec@30 :

         名字@器件地址

Compatible :

         厂家名字,模块名字

         跟i2c_device_id里面描述的名字要一致

reg=<0x30> :

         器件地址,对应 ”@30”

以上是对一个i2c节点的说明,也就是i2c节点在dts里面的结构,上面的内容说到了,dts是有自己独特的结构的,具体不同的设备节点有所差异。我们在dts里面添加这些节点的时候,需要安照dts里面的规则来添加。

如果分析过程有什么错漏,大家可以提出。互相交流。这里不再多说,下面来看一个具体的应用实例。


此例子是linux里面一个控制背光的实例。首先,我们先看到dts里面描述的设备信息:

[html] view plaincopy
  1. backlight {  
  2.     pinctrl-names = "default";  
  3.     pinctrl-0 = <&pinctrl_lvds_bkl_1 &pinctrl_lvds_vcc_1>;   
  4.     compatible = "pwm-backlight";   
  5.     lvds-bkl-enable = <&gpio4 6 0>;   
  6.     lvds-vcc-enable = <&gpio4 7 0>;  
  7.     pwms = <&pwm1 0 5000000>;   
  8.     brightness-levels = <0 4 8 16 32 64 128 255>;   
  9.     default-brightness-level = <7>;   
  10. };  

pinctrl-0 :从上面lvds里面获取io脚

compatible :驱动里面会找这个名字进行匹配

lvds-bkl-enable :第四组GPIO的第六个管教,默认值为0

lvds-vcc-enable :对于gpio的描述,在include的dts里面有描述

pwms :是取寄存器的值

brightness-levels :backlight 的level

default-brightness-level :level分为7个等级

接下来,我们看看代码里面是怎样对dts节点信息进行匹配的:

driver里面会对要match的dts的分支的名字写在device_idtable里面。当遍历dtb这棵树的时候,能找到与下面“compatible”名字相同的节点的名字,就匹配成功。

[html] view plaincopy
  1. static struct of_device_id pwm_backlight_of_match[] = {  
  2.     { .compatible = "pwm-backlight" },  
  3.     { }  
  4. };  

在backlight的驱动里面,当执行到probe函数的时候,会对dts里面几个管脚进行匹对,取值。其中,要注意以下几个函数的用法:

of_get_named_gpio

of_find_property

of_property_read_u32_array

of_property_read_u32

其实这几个函数的作用,做的事情都是这样一个过程:在匹配成功的节点里面寻找到函数参数里面匹配的字符串,然后读取后面的数值。至于这几个函数的源码,可以到“scripts/dtc”里面查看。

[html] view plaincopy
  1. lvds_vcc_enable = of_get_named_gpio(node, "lvds-vcc-enable", 0);  
  2. if (lvds_vcc_enable > 0)  
  3. {  
  4.    ret = gpio_request(lvds_vcc_enable,"lvds_vcc_enable");  
  5.    if (ret < 0) {  
  6.       printk("request lvds_vcc_enable failed: %d\n", ret);  
  7.       return ret;  
  8.    }  
  9.     gpio_direction_output(lvds_vcc_enable, 1);  
  10. }  
  11. lvds_bkl_enable = of_get_named_gpio(node, "lvds-bkl-enable", 0);  
  12. if (lvds_bkl_enable > 0)  
  13. {  
  14.    ret = gpio_request(lvds_bkl_enable,"lvds_bkl_enable");  
  15.    if (ret < 0) {  
  16.        printk("request lvds_bkl_enable failed: %d\n", ret);  
  17.          return ret;  
  18.     }  
  19.      gpio_direction_output(lvds_bkl_enable, 1);  
  20. }  
  21.   
  22. /* determine the number of brightness levels */  
  23. prop = of_find_property(node, "brightness-levels", &length);  
  24. if (!prop)  
  25.     return -EINVAL;  
  26. data->max_brightness = length / sizeof(u32);  
  27. /* read brightness levels from DT property */  
  28. if (data->max_brightness > 0) {  
  29.     size_t size = sizeof(*data->levels) * data->max_brightness;  
  30.     data->levels = devm_kzalloc(dev, size, GFP_KERNEL);  
  31.     if (!data->levels)  
  32.         return -ENOMEM;  
  33.     ret = of_property_read_u32_array(node, "brightness-levels",  
  34.                      data->levels,  
  35.                      data->max_brightness);  
  36.      if (ret < 0)  
  37.          return ret;  
  38.      ret = of_property_read_u32(node, "default-brightness-level",  
  39.                      &value);  

上面一段代码就是读取dts数据,初始化管教,或数组的过程。

当然,dts还有include,定义变量,引用等一些用法。具体可以参考“arch/arm/boot/dts/”下的文件进行配置。

0 0
原创粉丝点击