Linus Torvalds在2011年3月17日的ARM Linux邮件列表宣称“this whole ARM thing is a f*cking pain in the ass”,引发ARM Linux社区的地震,随后ARM社区进行了一系列的重大修正, 在Linux 2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx,采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。Device Tree 由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的 name 和 value。在 Device Tree 中。.dts 文件是一种 ASCII 文本格式的 Device Tree 描述,
&i2c0{ //挂载 i2c0 总线上
sensor@1d { //sensor,i2c 地址
compatible = "gs_mma8452"; //设备 ID 号
reg = <0x1d>; //设备地址
type =
irq-gpio = <&gpio0 GPIO_B7 IRQ_TYPE_EDGE_FALLING>; //中断 GPIO
irq_enable = <1>; //使用中断
poll_delay_ms = <30>; //轮询时间
layout = <4>; //方向矩阵,1-8
};
};
static struct of_device_id sensor_dt_ids[] = {
{ .compatible = "gs_mma8452" }, //这边定义必须和 dts 中定义的一样。
};
static struct i2c_driver sensor_driver = {
.probe = sensor_probe,
.remove = sensor_remove,
.shutdown = sensor_shut_down,
.id_table = sensor_id,
.driver = {
.owner = THIS_MODULE,
.name = "sensors",
.of_match_table = of_match_ptr(sensor_dt_ids), //3.10 中增加的,必须要有
}
Probe()
{ of_property_read_u32(np,"type",&(pdata->type));
}
struct device_node *of_find_compatible_node(struct device_node *from,
const char *type, const char *compatible);
根据 compatible 属性,获得设备结点。遍历 Device Tree 中所有的设备结点,看看哪个结
点的类型、compatible 属性与本函数的输入参数匹配,大多数情况下,from、type 为 NULL。
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(const struct device_node *np, const char
*propname, u64 *out_value);
读取设备结点 np 的属性名为 propname,类型为8、16、32、64位整型数组的属
性。对于32位处理器来讲,最常用的是 of_property_read_u32_array()。
有些情况下,整形属性的长度可能为1,于是内核为了方便调用者,又在上述 API
的基础上封装出了更加简单的读单一整形属性的 API,它们为
int of_property_read_u8();
Int of_property_read_u16();
Int of_property_read_u30();
int of_property_read_string(struct device_node *np, const char
*propname, const char **out_string);
int of_property_read_string_index(struct device_node *np, const char
*propname, int index, const char **output);
前者读取字符串属性,后者读取字符串数组属性中的第 index 个字符串。
of_get_named_gpio_flags
device-node-name {
定义该device自己的属性
pinctrl-names = "sleep", "active";------(1)
pinctrl-0 =
pinctrl-1 =
};
pinctrl-names定义了一个state列表,当设备active的时候,我们需要pin controller将相关的一组pin设定为具体的设备功能,而当设备进入sleep状态的时候,需要pin controller将相关的一组pin设定为普通GPIO,>>>> state有两种标识,一种就是pinctrl-names定义的字符串列表,另外一种就是ID,这里当state ID等于0(名字是active)的state对应pinctrl-0属性,state ID等于1(名字是idle)的state对应pinctrl-1属性。
上面的如在active的时候,I2C功能有两种配置,一种是从pin ID{7,8}引出,另外一个是从pin ID{69,103}引出。
例如
/ {
compatible = "rockchip,rk312x";
rockchip,sram = <&sram>;
interrupt-parent = <&gic>;
aliases {
serial0 = &uart0;
serial1 = &uart1;
serial2 = &uart2;
i2c0 = &i2c0;
i2c1 = &i2c1;
i2c2 = &i2c2;
i2c3 = &i2c3;
lcdc = &lcdc;
spi0 = &spi0;
};
uart0: serial@20060000 {
compatible = "rockchip,serial";
reg = <0x20060000 0x100>;
interrupts =
clock-frequency = <24000000>;
clocks = <&clk_uart0>, <&clk_gates8 0>;
clock-names = "sclk_uart", "pclk_uart";
reg-shift = <2>;
reg-io-width = <4>;
dmas = <&pdma 2>, <&pdma 3>;
#dma-cells = <2>;
pinctrl-names = "default";
pinctrl-0 = <&uart0_xfer &uart0_cts &uart0_rts>;
status = "okay";
};
i2c0: i2c@20072000 {
compatible = "rockchip,rk30-i2c";
reg = <0x20072000 0x1000>;
interrupts =
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default", "gpio";
pinctrl-0 = <&i2c0_sda &i2c0_scl>;
pinctrl-1 = <&i2c0_gpio>;
gpios = <&gpio0 GPIO_A1 GPIO_ACTIVE_LOW>, <&gpio0 GPIO_A0
GPIO_ACTIVE_LOW>;
clocks = <&clk_gates8 4>;
rockchip,check-idle = <1>;
status = "disabled";
};
spi0: spi@20074000 {
compatible = "rockchip,rockchip-spi";
reg = <0x20074000 0x1000>;
interrupts =
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default";
pinctrl-0 = <&spi0_txd_mux0 &spi0_rxd_mux0 &spi0_clk_mux0 &spi0_cs0_mux0
&spi0_cs1_mux0>;
//pinctrl-0 = <&spi0_txd_mux1 &spi0_rxd_mux1 &spi0_clk_mux1 &spi0_cs0_mux1
&spi0_cs1_mux1>;
//pinctrl-0 = <&spi0_txd_mux2 &spi0_rxd_mux2 &spi0_clk_mux2 &spi0_cs0_mux2>;
rockchip,spi-src-clk = <0>;
num-cs = <2>;
clocks =<&clk_spi0>, <&clk_gates7 12>;
clock-names = "spi","pclk_spi0";
dmas = <&pdma 8>, <&pdma 9>;
#dma-cells = <2>;
dma-names = "tx", "rx";
status = "disabled";
};
fb: fb{
compatible = "rockchip,rk-fb";
rockchip,disp-mode =
};
rk_screen: rk_screen{
compatible = "rockchip,screen";
};
lvds: lvds@20038000 {
compatible = "rockchip,rk31xx-lvds";
reg = <0x20038000 0x4000>, <0x101100b0 0x01>;
reg-names = "mipi_lvds_phy", "mipi_lvds_ctl";
clocks = <&clk_gates5 0>, <&clk_gates9 6>, <&clk_gates9 5>;
clock-names = "pclk_lvds", "pclk_lvds_ctl", "hclk_vio_h2p";
status = "disabled";
};
lcdc: lcdc@1010e000 {
compatible = "rockchip,rk312x-lcdc";
rockchip,prop =
reg = <0x1010e000 0x1000>;
interrupts =
clocks = <&clk_gates6 0>, <&dclk_lcdc0>, <&clk_gates6 1>, <&sclk_lcdc0>, <&pd_vop>,
<&clk_cpll>;
clock-names = "aclk_lcdc", "dclk_lcdc", "hclk_lcdc", "sclk_lcdc", "pd_lcdc", "sclk_pll";
rockchip,iommu-enabled = <1>;
status = "disabled";
};
lvds: lvds@20038000 {}
lcdc: lcdc@1010e000 {}
方括号 表示二进制:a binary-property = [0x01 0x23 0x45 0x67];
a mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
a string-list = "red fish", "blue fish";
/ { # 这个是根节点
compatible = "acme,coyotes-revenge"; #定义了厂家和型号
#address-cells = <1>;//决定了serial、gpio、spi等结点的address字段的长度为1
#size-cells = <1>;//决定了serial、gpio、spi等结点的length字段的长度为1
/*
上面这两行表示父结点的reg属性,它们分别决定了子结点的reg属性的address和length字段的长度。如果<0>:address 和 length 字段是可变长的,
reg的组织形式为reg =
*/
interrupt-parent = <&intc>;
cpus { //定义了cpu,这里是双核cpu0和cpu1, cortex-a9 32位处理器
#address-cells = <1>;
#size-cells = <0>;
cpu@0 { //子节点
compatible = "arm,cortex-a9";
reg = <0>;
};
cpu@1 { //子节点
compatible = "arm,cortex-a9";
reg = <1>;
};
};
serial@101f0000 { //串口1,地址在101f0000
compatible = "arm,pl011";
reg = <0x101f0000 0x1000 >;
interrupts = < 1 0 >;
};
serial@101f2000 { //串口2,地址在101f2000
compatible = "arm,pl011";
reg = <0x101f2000 0x1000 >;
interrupts = < 2 0 >;
};
gpio@101f3000 { //gpio控制器,地址101f3000
compatible = "arm,pl061";
reg = <0x101f3000 0x1000
0x101f4000 0x0010>;
interrupts = < 3 0 >;
};
intc: interrupt-controller@10140000 { //中断控制器(位于0x10140000)
compatible = "arm,pl190";
reg = <0x10140000 0x1000 >;
interrupt-controller;
#interrupt-cells = <2>;
/*#interrupt-cell-这是中断控制器节点的一个属性。它代表此中断控制器的interrupt specifier有多少cells*/
};
spi@10115000 { //SPI控制器(位于0x10170000)
compatible = "arm,pl022";
reg = <0x10115000 0x1000 >;
interrupts = < 4 0 >;
};
external-bus { //external bus桥,它分别接了1) Ethernet, 2) I2C控制器, 3) 64MB NOR Flash
#address-cells = <2>
#size-cells = <1>;
/*
上面这两个决定了下面1) Ethernet, 2) I2C控制器,3)flash 的reg属性。
将下面的reg记录如下:
reg = <0 0 0x1000>;
reg = <1 0 0x1000>;
reg = <2 0 0x4000000>;
0.1.2 address绿色表示的是片选,0.0.0表示的是该片选的基地址。褐色0x1000,0x1000,0x4000000表示的是长度。
总结:reg =
父类address-cells和size-cells决定了子类的相关属性要包含多少个cell,如果子节点有特殊需求的话,可以自己再定义,这样就可以摆脱父节点的控制。
address-cells决定了address1/2/3包含几个cell,size-cells决定了length1/2/3包含了几个cell。
由父节点#address-cells = <2> + #size-cells = <1>; 可知,现在公有3个节点,分别表示片选序号,偏移量,地址空间长度,
*/
ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet
1 0 0x10160000 0x10000 // Chipselect 2, i2c controller
2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash
ethernet@0,0 {
compatible = "smc,smc91c111";
reg = <0 0 0x1000>;
interrupts = < 5 2 >;
};
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
#address-cells = <1>;
#size-cells = <0>;
reg = <1 0 0x1000>;
/*特别要留意的是i2c结点中定义的 #address-cells = <1>;和#size-cells = <0>;又作用到了I2C总线上连接的RTC,因为RTC设备只是被分配在一个地址上,不需要其他任何空间,所以只需要一个address的cell就可以描述完整,不需要size-cells。它的address字段为0x58,是设备的I2C地址。*/
interrupts = < 6 2 >;
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
interrupts = < 7 3 >;
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
reg = <2 0 0x4000000>;
};
};
};
//因root结点的子结点描述的是CPU的视图,因此root子结点的address区域就直接位于CPU的memory区域,但是,经过总线桥后的address往往需要经过转换才能对应的CPU的memory映射。external-bus的ranges属性定义了经过external-bus桥后的地址范围如何映射到CPU的memory区域。见上面的:
/ {
compatible = "acme,coyotes-revenge";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;
..................
external-bus {
#address-cells = <2>
#size-cells = <1>;
ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet
1 0 0x10160000 0x10000 // Chipselect 2, i2c controller
2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash
ethernet@0,0 {
......
}
i2c@1,0 {
.....
}
flash@2,0 {
....
}
}
}
ranges是地址转换表,每个项目是一个子地址、父地址以及在子地址空间的大小的映射。
父节点:#address-cells = <1> #size-cells = <1>;
子节点:#address-cells = <2> #size-cells = <1>;
对于本例而言,子地址空间的#address-cells为2,父地址空间的#address-cells值为1,因此0 0 0x10100000 0x10000的前2个cell为external-bus后片选0上偏移0,第3个cell表示external-bus后片选0上偏移0的地址空间被映射到CPU的0x10100000位置,第4个cell表示映射的大小为0x10000。ranges的后面2个项目的含义可以类推。
.dts文件的每个设备,都有一个compatible 属性,compatible属性用户驱动和设备的绑定。
将.dts编译为.dtb的工具。
.dtb是.dts被DTC编译后的二进制格式的Device Tree描述,可由Linux内核解析。通常在我们制作NAND、SD启动image时,会为.dtb文件单独留下一个很小的区域以存放之,之后bootloader在引导kernel的过程中,会先读取该.dtb到内存。
对于Device Tree中的结点和属性具体是如何来描述设备的硬件细节的,一般需要文档来进行讲解,文档的后缀名一般为.txt。这些文档位于内核的Documentation/devicetree/bindings目录,其下又分为很多子目录。
Uboot mainline 从 v1.1.3开始支持Device Tree,其对ARM的支持则是和ARM内核支持Device Tree同期完成。为了使能Device Tree,需要编译Uboot的时候在config文件中加入
#define CONFIG_OF_LIBFDT
在Uboot中,可以从NAND、SD或者TFTP等任意介质将.dtb读入内存,假设.dtb放入的内存地址为0x71000000,之后可在Uboot运行命令fdt addr命令设置.dtb的地址,如:
U-Boot> fdt addr 0x71000000
bootz kernel_addr initrd_address dtb_address的命令。第一个参数为内核映像的地址,第二个参数为initrd的地址,若不存在initrd,可以用 -代替,第三个dtb_address作为bootz或者bootm的最后一次参数。
过去,ARM Linux针对不同的电路板会建立由MACHINE_START和MACHINE_END包围起来的针对这个machine的一系列callback
1. 373 MACHINE_START(VEXPRESS, "ARM-Versatile Express")
2. 374 .atag_offset = 0x100,
3. 375 .smp = smp_ops(vexpress_smp_ops),
4. 376 .map_io = v2m_map_io,
5. 377 .init_early = v2m_init_early,
6. 378 .init_irq = v2m_init_irq,
7. 379 .timer = &v2m_timer,
8. 380 .handle_irq = gic_handle_irq,
9. 381 .init_machine = v2m_init,
10. 382 .restart = vexpress_restart,
11. 383 MACHINE_END
在旧版的kerenl里,不同的machine会有不同的MACHINE ID,Uboot在启动Linux内核时会将MACHINE ID存放在r1寄存器,Linux启动时会匹配Bootloader传递的MACHINE ID和MACHINE_START声明的MACHINE ID,然后执行相应machine的一系列初始化函数.
新版kernel在引入Device Tree之后,MACHINE_START变更为DT_MACHINE_START,其中含有一个.dt_compat成员,用于表明相关的machine与.dts中root结点的compatible属性兼容关系。如果Bootloader传递给内核的Device Tree中root结点的compatible属性出现在某machine的.dt_compat表中,相关的machine就与对应的Device Tree匹配,从而引发这一machine的一系列初始化函数被执行
1. static const char * const v2m_dt_match[] __initconst = {
2. 490 "arm,vexpress",
3. 491 "xen,xenvm",
4. 492 NULL,
5. 493 };
6. 495 DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express")
7. 496 .dt_compat = v2m_dt_match,
8. 497 .smp = smp_ops(vexpress_smp_ops),
9. 498 .map_io = v2m_dt_map_io,
10. 499 .init_early = v2m_dt_init_early,
11. 500 .init_irq = v2m_dt_init_irq,
12. 501 .timer = &v2m_dt_timer,
13. 502 .init_machine = v2m_dt_init,
14. 503 .handle_irq = gic_handle_irq,
15. 504 .restart = vexpress_restart,
16. 505 MACHINE_END
如:瑞芯微的rk3128:
DT_MACHINE_START(RK3128_DT, "Rockchip RK3128")
.smp = smp_ops(rockchip_smp_ops),
.map_io = rk3128_dt_map_io,
.init_time = rk312x_dt_init_timer,
.dt_compat = rk3128_dt_compat,
.init_late = rk312x_init_late,
.reserve = rk312x_reserve,
.restart = rk312x_restart,
MACHINE_END
static const char * const rk3128_dt_compat[] __initconst = {
"rockchip,rk3128",
NULL,
};
这里刚好对应了:arch/arm/boot/dts/rk3128-86v.dts
/ {
compatible = "rockchip,rk3128";
}
Linux倡导针对多个SoC、多个电路板的通用DT machine,即一个DT machine的.dt_compat表含多个电路板.dts文件的root结点compatible属性字符串.
比如上面的这个可以设计成这样:
static const char * const rk3128_dt_compat[] __initconst = {
"rockchip,rk3128",
"rockchip,rk3126",
NULL,
};
它的.init_machine成员函数就针对不同的machine进行了不同的分支处理:
1. 126 static void __init exynos5_dt_machine_init(void)
2. 127 {
3. 128 …
4. 149
5. 150 if (of_machine_is_compatible("samsung,exynos5250"))
6. 151 of_platform_populate(NULL, of_default_bus_match_table,
7. 152 exynos5250_auxdata_lookup, NULL);
8. 153 else if (of_machine_is_compatible("samsung,exynos5440"))
9. 154 of_platform_populate(NULL, of_default_bus_match_table,
10. 155 exynos5440_auxdata_lookup, NULL);
11. 156 }
使用Device Tree后,驱动需要与.dts中描述的设备结点进行匹配,从而引发驱动的probe()函数执行。对于platform_driver而言,需要添加一个OF匹配表,如前文的.dts文件的"acme,a1234-i2c-bus"兼容I2C控制器结点的OF匹配表.
例如:i2c匹配:
旧版在board需要 定义为:
1. static struct i2c_board_info __initdata afeb9260_i2c_devices[] = {
2. 146 {
3. 147 I2C_BOARD_INFO("tlv320aic23", 0x1a),
4. 148 }, {
5. 149 I2C_BOARD_INFO("fm3130", 0x68),
6. 150 }, {
7. 151 I2C_BOARD_INFO("24c64", 0x50),
8. 152 },
9. 153 };
而新版采样dts:
1. i2c@1,0 {
2. compatible = "acme,a1234-i2c-bus";
3. …
4. rtc@58 {
5. compatible = "maxim,ds1338";
6. reg = <58>;
7. interrupts = < 7 3 >;
8. };
9. };
驱动里面OF匹配表.
1. static const struct of_device_id a1234_i2c_of_match[] = {
2. 437 { .compatible = "acme,a1234-i2c-bus ", },
3. 438 {},
4. 439 };
5. 440 MODULE_DEVICE_TABLE(of, a1234_i2c_of_match);
6. 441
7. 442 static struct platform_driver i2c_a1234_driver = {
8. 443 .driver = {
9. 444 .name = "a1234-i2c-bus ",
10. 445 .owner = THIS_MODULE,
11. 449 .of_match_table = a1234_i2c_of_match,
12. 450 },
13. 451 .probe = i2c_a1234_probe,
14. 452 .remove = i2c_a1234_remove,
15. 453 };
16. 454 module_platform_driver(i2c_a1234_driver);
名字manufacturer可以忽略: I2C和SPI外设驱动和Device Tree中设备结点的compatible 属性还有一种弱式匹配方法,就是别名匹配。compatible 属性的组织形式为
1. static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
2. 72 const struct spi_device *sdev)
3. 73 {
4. 74 while (id->name[0]) {
5. 75 if (!strcmp(sdev->modalias, id->name))
6. 76 return id;
7. 77 id++;
8. 78 }
9. 79 return NULL;
10. 80 }
在Linux的BSP和驱动代码中,还经常会使用到Linux中一组Device Tree的API,这些API通常被冠以of_前缀,它们的实现代码位于内核的drivers/of目录。这些常用的API包括:
struct device_node {
const char *name;
const char *type;
phandle phandle;
const char *full_name;
struct property *properties;
struct property *deadprops; /* removed properties */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
struct device_node *next; /* next device of same type */
struct device_node *allnext; /* next in list of all nodes */
struct proc_dir_entry *pde; /* this node's proc directory */
struct kref kref;
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
驱动可以透过Bootloader传递给内核的Device Tree中的真正结点的compatible 属性以确定究竟是哪一种设备
compatible = "rockchip,rk3188-div-con";
rockchip,bits = <0 5>;
clocks = <&clk_core>;
clock-output-names = "clk_core";
rockchip,div-type =
#clock-cells = <0>;
rockchip,clkops-idx =
rockchip,flags = <(CLK_GET_RATE_NOCACHE |CLK_SET_RATE_NO_REPARENT)>;
};
of_property_read_string (np, "clock-output-names",&divinfo->clk_name);
of_parse_phandle_with_args(np, "clocks", "#clock-cells", index, &clkspec);
of_property_read_string_index(clkspec.np,"clock-output-names",clkspec.args_count?clkspec.args[0] : 0, &clk_name) < 0)
of_property_read_u32(np, "rockchip,clkops-idx",&divinfo->clkops_idx);
ARM社区一贯充斥的大量垃圾代码导致Linus盛怒,因此社区在2011年到2012年进行了大量的工作。ARM Linux开始围绕Device Tree展开,Device Tree有自己的独立的语法,它的源文件为.dts,编译后得到.dtb,Bootloader在引导Linux内核的时候会将.dtb地址告知内核。之后内核会展开Device Tree并创建和注册相关的设备,因此arch/arm/mach-xxx和arch/arm/plat-xxx中大量的用于注册platform、I2C、SPI板级信息的代码被删除,而驱动也以新的方式和.dts中定义的设备结点进行匹配。
gpio1: gpio1@20080000 {
compatible = "rockchip,gpio-bank";
reg = <0x20080000 0x100>;
interrupts =
clocks = <&clk_gates8 10>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
..........//下面的是另一个复用的功能。
..........
gpio0_uart0 {
uart0_xfer: uart0-xfer {
rockchip,pins =
rockchip,pull =
};
uart0_cts: uart0-cts {
rockchip,pins =
rockchip,pull =
};
uart0_rts: uart0-rts {
rockchip,pins =
rockchip,pull =
};
uart0_rts_gpio: uart0-rts-gpio {
rockchip,pins =
rockchip,pull =
};
};
..........
.........
}
node = fdt_node_offset_by_compatible(blob, 0, "rockchip,rk-fb");
/* arch/arm/boot/dts/rk312x.dtsi: 管脚复用等很多都在这里定义
fb: fb{
compatible = "rockchip,rk-fb";
rockchip,disp-mode =
};
*/
}