以下凡是涉及代码分析的地方,可能不同平台的处理方式有所区别,具体情况是以自己手头上的平台代码为准。
曾经在空间里面转载过一篇《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到内存里面。
DEFAULT_FDT_FILE="xxx.dtb",DDR_MB=1024,SYS_USE_SPINOR
接下来,根据DEFAULT_FDT_FILE 来定义 fdt_file 的变量。
include/configs/xxxcommon.h
"fdt_file=" CONFIG_DEFAULT_FDT_FILE "\0" \
和相关环节变量的定义:
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
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上挂的一个设备:
&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里面描述的设备信息:
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”名字相同的节点的名字,就匹配成功。
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”里面查看。
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/”下的文件进行配置。