您的位置:首页 > 其它

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

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

曾经在空间里面转载过一篇《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

DEFAULT_FDT_FILE="xxx.dtb",DDR_MB=1024,SYS_USE_SPINOR

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

include/configs/xxxcommon.h

[html]
view plaincopy

"fdt_file=" CONFIG_DEFAULT_FDT_FILE "\0" \

和相关环节变量的定义:

[html]
view plaincopy

208 "loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}\0" \
209 "mmcboot=echo Booting from mmc ...; " \
210 "run mmcargs; " \
211 "if test ${boot_fdt} = yes || test ${boot_fdt} = try; then " \
212 "if run loadfdt; then " \

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

arch/arm/lib/board.c

[html]
view plaincopy

sprintf(buf, "fatload sata 0:1 ${fdt_addr} ${fdt_file}");
•••
sprintf(buf, "dcache off; sata init; run loaduimage; run loadbootscript; run sataargs; run loadfdt; bootm ${loadaddr} - ${fdt_addr}");
setenv("bootcmd", buf);

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

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

dts源码在scripts/dtc目录下

参考文档在Documentation/devicetree目录下

如在i2c上挂的一个设备:

[html]
view plaincopy

&i2c2{
•••
#address-cells = <1>;
#size-cells = <0>;

I2c@1{
reg=<1>;

#address-cells = <1>;
#size-cells = <0>;
•••
ec@30 {
compatible = “fsl , ec”;
reg=<0x30>;
};
};
};

由上到下:

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

backlight {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lvds_bkl_1 &pinctrl_lvds_vcc_1>;
compatible = "pwm-backlight";
lvds-bkl-enable = <&gpio4 6 0>;
lvds-vcc-enable = <&gpio4 7 0>;
pwms = <&pwm1 0 5000000>;
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <7>;
};

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

static struct of_device_id pwm_backlight_of_match[] = {
{ .compatible = "pwm-backlight" },
{ }
};

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

of_get_named_gpio

of_find_property

of_property_read_u32_array

of_property_read_u32

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

[html]
view plaincopy

lvds_vcc_enable = of_get_named_gpio(node, "lvds-vcc-enable", 0);
if (lvds_vcc_enable > 0)
{
ret = gpio_request(lvds_vcc_enable,"lvds_vcc_enable");
if (ret < 0) {
printk("request lvds_vcc_enable failed: %d\n", ret);
return ret;
}
gpio_direction_output(lvds_vcc_enable, 1);
}
lvds_bkl_enable = of_get_named_gpio(node, "lvds-bkl-enable", 0);
if (lvds_bkl_enable > 0)
{
ret = gpio_request(lvds_bkl_enable,"lvds_bkl_enable");
if (ret < 0) {
printk("request lvds_bkl_enable failed: %d\n", ret);
return ret;
}
gpio_direction_output(lvds_bkl_enable, 1);
}

/* determine the number of brightness levels */
prop = of_find_property(node, "brightness-levels", &length);
if (!prop)
return -EINVAL;
data->max_brightness = length / sizeof(u32);
/* read brightness levels from DT property */
if (data->max_brightness > 0) {
size_t size = sizeof(*data->levels) * data->max_brightness;
data->levels = devm_kzalloc(dev, size, GFP_KERNEL);
if (!data->levels)
return -ENOMEM;
ret = of_property_read_u32_array(node, "brightness-levels",
data->levels,
data->max_brightness);
if (ret < 0)
return ret;
ret = of_property_read_u32(node, "default-brightness-level",
&value);

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

当然,dts还有include,定义变量,引用等一些用法。具体可以参考“arch/arm/boot/dts/”下的文件进行配置。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: