----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------
一、修改设备树s3c2440.dtsi
s3c2440.dtsi设备树存放的是s3c2440这个SoC跟其他s3c24xx系列不同的一些硬件信息,如clock控制器、串口等等;
修改arch/arm/boot/dts/s3c2440.dtsi文件,将该文件中的2416全部替换成2440,替换后文件如下:
#include <dt-bindings/clock/s3c2443.h> /* 注意 */ #include "s3c24xx.dtsi" #include "s3c2440-pinctrl.dtsi" / { model = "Samsung S3C2440 SoC"; compatible = "samsung,s3c2440","samsung,mini2440"; aliases { serial3 = &uart_3; }; cpus { cpu { compatible = "arm,arm926ej-s"; }; }; interrupt-controller@4a000000 { compatible = "samsung,s3c2440-irq"; }; clocks: clock-controller@4c000000 { compatible = "samsung,s3c2440-clock"; reg = <0x4c000000 0x40>; #clock-cells = <1>; }; pinctrl@56000000 { compatible = "samsung,s3c2440-pinctrl"; }; timer@51000000 { clocks = <&clocks PCLK_PWM>; clock-names = "timers"; }; uart_0: serial@50000000 { compatible = "samsung,s3c2440-uart"; clock-names = "uart", "clk_uart_baud2", "clk_uart_baud3"; clocks = <&clocks PCLK_UART0>, <&clocks PCLK_UART0>, <&clocks SCLK_UART>; }; uart_1: serial@50004000 { compatible = "samsung,s3c2440-uart"; clock-names = "uart", "clk_uart_baud2", "clk_uart_baud3"; clocks = <&clocks PCLK_UART1>, <&clocks PCLK_UART1>, <&clocks SCLK_UART>; }; uart_2: serial@50008000 { compatible = "samsung,s3c2440-uart"; clock-names = "uart", "clk_uart_baud2", "clk_uart_baud3"; clocks = <&clocks PCLK_UART2>, <&clocks PCLK_UART2>, <&clocks SCLK_UART>; }; uart_3: serial@5000c000 { compatible = "samsung,s3c2440-uart"; reg = <0x5000C000 0x4000>; interrupts = <1 18 24 4>, <1 18 25 4>; clock-names = "uart", "clk_uart_baud2", "clk_uart_baud3"; clocks = <&clocks PCLK_UART3>, <&clocks PCLK_UART3>, <&clocks SCLK_UART>; status = "disabled"; }; sdhci_1: sdhci@4ac00000 { compatible = "samsung,s3c6410-sdhci"; reg = <0x4AC00000 0x100>; interrupts = <0 0 21 3>; clock-names = "hsmmc", "mmc_busclk.0", "mmc_busclk.2"; clocks = <&clocks HCLK_HSMMC0>, <&clocks HCLK_HSMMC0>, <&clocks MUX_HSMMC0>; status = "disabled"; }; sdhci_0: sdhci@4a800000 { compatible = "samsung,s3c6410-sdhci"; reg = <0x4A800000 0x100>; interrupts = <0 0 20 3>; clock-names = "hsmmc", "mmc_busclk.0", "mmc_busclk.2"; clocks = <&clocks HCLK_HSMMC1>, <&clocks HCLK_HSMMC1>, <&clocks MUX_HSMMC1>; status = "disabled"; }; watchdog: watchdog@53000000 { interrupts = <1 9 27 3>; clocks = <&clocks PCLK_WDT>; clock-names = "watchdog"; }; rtc: rtc@57000000 { compatible = "samsung,s3c2440-rtc"; clocks = <&clocks PCLK_RTC>; clock-names = "rtc"; }; i2c@54000000 { compatible = "samsung,s3c2440-i2c"; clocks = <&clocks PCLK_I2C0>; clock-names = "i2c"; }; };
接着我们需要对该文件进行修改来适配s3c2440。
1.1 修改头文件
修改时钟编号相关宏头文件:
#include <dt-bindings/clock/s3c2443.h>
修改为:
#include <dt-bindings/clock/s3c2410.h>
1.2 修改根节点compatible
compatible需要与mach-smdk2440-dt.c文件dt_compat数组数组的compatible匹配,修改为:
model = "Samsung S3C2440 SoC"; compatible = "samsung,s3c2440","samsung,mini2440";
1.3 cpus节点
由于s3c2440内核为arm920t,因此修改cpus为:
cpus { cpu { compatible = "arm,arm920t"; }; };
1.4 中断控制器节点
移除s3c2440.dtsi中中断控制器节点,在父级设备树s3c24xx.dtsi文件中定义有:
intc:interrupt-controller@4a000000 { compatible = "samsung,s3c2410-irq"; reg = <0x4a000000 0x100>; interrupt-controller; #interrupt-cells = <4>; };
中断控制器节点在上一片博客已经介绍过了,这里不重复介绍了。
关于中断控制器这部分可以参考内核文档:
- Documentation/devicetree/bindings/interrupt-controller/samsung,s3c24xx-irq.txt;
- Documentation/devicetree/bindings/interrupt-controller/arm,gic.yaml;
- Documentation/devicetree/bindings/interrupt-controller/interrupts.txt;
1.4.1 未使用设备树
在内核移植不使用设备树的时候,在arch/arm/mach-s3c24xx/mach-smdk2440.c文件:
MACHINE_START(S3C2440, "SMDK2440") /* Maintainer: Ben Dooks <ben-linux@fluff.org> */ .atag_offset = 0x100, .init_irq = s3c2440_init_irq, .map_io = smdk2440_map_io, .init_machine = smdk2440_machine_init, .init_time = smdk2440_init_time, MACHINE_END void __init s3c2440_init_irq(void) { pr_info("S3C2440: IRQ Support\n"); #ifdef CONFIG_FIQ init_FIQ(FIQ_START); #endif s3c_intc[0] = s3c24xx_init_intc(NULL, &init_s3c2440base[0], NULL, // 初始化32个主中断源相关的中断 0x4a000000); if (IS_ERR(s3c_intc[0])) { pr_err("irq: could not create main interrupt controller\n"); return; } s3c24xx_init_intc(NULL, &init_eint[0], s3c_intc[0], 0x560000a4); // 初始化外部中断相关的中断、外部中断4~7、8~23分别对应主中断源中的的EINT4~7、EINT8~23 s3c_intc[1] = s3c24xx_init_intc(NULL, &init_s3c2440subint[0], // 初始化带有子中断的内部中断相关的中断 s3c_intc[0], 0x4a000018); }
这里直接调用s3c24xx_init_intc进行中断控制器初始化,函数位于drivers/irqchip/irq-s3c24xx.c,具体可以参考linux驱动移植-中断子系统执行流程。
1.4.2 使用设备树
在linux内核根路径下搜索samsung,s3c2410-irq:
root@zhengyang:/work/sambashare/linux-5.2.8-dt# grep "samsung,s3c2410-irq" * -nR drivers/irqchip/irq-s3c24xx.c:1307:IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);
定位到drivers/irqchip/irq-s3c24xx.c文件:
static struct s3c24xx_irq_of_ctrl s3c2410_ctrl[] = { { .name = "intc", .offset = 0, }, { .name = "subintc", .offset = 0x18, .parent = &s3c_intc[0], } }; int __init s3c2410_init_intc_of(struct device_node *np, struct device_node *interrupt_parent) { return s3c_init_intc_of(np, interrupt_parent, s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl)); } IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);
IRQCHIP_DECLARE的作用如下所述:用IRQCHIP_DECLARE声明兼容的中断控制器驱动,并将其与初始化函数关联。
其中IRQCHIP_DECLARE宏定义在include/linux/irqchip.h中:
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn) #define OF_DECLARE_2(table, name, compat, fn) \ _OF_DECLARE(table, name, compat, fn, of_init_fn_2) #define _OF_DECLARE(table, name, compat, fn, fn_type) \ static const struct of_device_id __of_table_##name \ __used __section(__##table##_of_table) \ = { .compatible = compat, \ .data = (fn == (fn_type)NULL) ? fn : fn }
这一段需要借助内核编译的lds文件来解读,其中传入了参数给编译器来确定变量的存放位置,其实就是定义了:
static const struct of_device_id __clk_of_table_s3c2410_irq \ __used__section(__irqchip_of_table) \ = { .compatible = "samsung,s3c2410-irq", .data = s3c2410_init_intc_of, };
通过IRQCHIP_DECLARE来定义相应的of_device_id,并且要把相应的驱动初始化函数fn的地址(即s3c2410_init_intc_of)传给data。
这样当设备树定义有compatible = "samsung,s3c2410-irq"时,匹配到相应的设备时就会直接调用驱动初始化函数s3c2410_init_intc_of了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
static int __init s3c_init_intc_of(struct device_node *np, struct device_node *interrupt_parent, struct s3c24xx_irq_of_ctrl *s3c_ctrl, int num_ctrl) { struct s3c_irq_intc *intc; struct s3c24xx_irq_of_ctrl *ctrl; struct irq_domain *domain; void __iomem *reg_base; int i; reg_base = of_iomap(np, 0); if (!reg_base) { pr_err("irq-s3c24xx: could not map irq registers\n"); return -EINVAL; } domain = irq_domain_add_linear(np, num_ctrl * 32, &s3c24xx_irq_ops_of, NULL); if (!domain) { pr_err("irq: could not create irq-domain\n"); return -EINVAL; } for (i = 0; i < num_ctrl; i++) { ctrl = &s3c_ctrl[i]; pr_debug("irq: found controller %s\n", ctrl->name); intc = kzalloc(sizeof(struct s3c_irq_intc), GFP_KERNEL); if (!intc) return -ENOMEM; intc->domain = domain; intc->irqs = kcalloc(32, sizeof(struct s3c_irq_data), GFP_KERNEL); if (!intc->irqs) { kfree(intc); return -ENOMEM; } if (ctrl->parent) { intc->reg_pending = reg_base + ctrl->offset; intc->reg_mask = reg_base + ctrl->offset + 0x4; if (*(ctrl->parent)) { intc->parent = *(ctrl->parent); } else { pr_warn("irq: parent of %s missing\n", ctrl->name); kfree(intc->irqs); kfree(intc); continue; } } else { intc->reg_pending = reg_base + ctrl->offset; intc->reg_mask = reg_base + ctrl->offset + 0x08; intc->reg_intpnd = reg_base + ctrl->offset + 0x10; } s3c24xx_clear_intc(intc); s3c_intc[i] = intc; } set_handle_irq(s3c24xx_handle_irq); return 0; }
1.5 时钟控制器节点
s3c2440.dtsi中定义了时钟控制器节点,在内核文档中称之为"Clock providers":
clocks: clock-controller@4c000000 { compatible = "samsung,s3c2440-clock"; reg = <0x4c000000 0x40>; #clock-cells = <1>; };
时钟提供者节点必须有#clock-cells属性,说明该节点是clock provider。它有2种取值:
- #clock-cells = <0>,只有一个时钟输出;
- #clock-cells = <1>,有多个时钟输出;
设备需要时钟时,它是"Clock consumers"。它描述了使用哪一个"Clock providers"中的哪一个时钟;
- 每个时钟都分配了一个标识符,设备节点可以使用此标识符来指定它们使用的时钟。其中一些时钟仅在特定的SoC上可用;
- 所有可用的时钟都定义为预处理器宏并位于dt-bindings/clock/s3c2410.h头文件中,可以在设备树源代码中使用;
比如使用时钟控制器生成的时钟的UART控制器节点:
serial@50004000 { compatible = "samsung,s3c2440-uart"; reg = <0x50004000 0x4000>; interrupts = <1 23 3 4>, <1 23 4 4>; clock-names = "uart", "clk_uart_baud2"; /*在s3c24xx_serial_probe中,调用clk_get(&platdev->dev, "uart")根据clock-names来查找并获取对时钟生产者的引用 */ clocks = <&clocks PCLK_UART0>, <&clocks PCLK_UART0>; /* 使用clocks即clock-controller@4c000000中的PCLK_UART0、
其中PCLK_UART0参考include/dt-bindings/clock/s3c2410.h或者这个clock控制器驱动的实现。 */ };
时钟使用者(clock consumer)的node节点必须有clocks属性,说明该节点是clock consumer。clocks属性由2部分组成:phandle、clock-specifier。
- phandle是@节点名称(例如上个示例中的@clokcks);
- clock-specifier怎么理解呢?一个时钟控制器可以控制很多时钟硬件(例如5种基本时钟:fixed-rate、fixed-factor、gate、mux、divider),每种时钟硬件都有对应的ID号。clock-specifier就是这个ID号。例如,s3c2440的时钟硬件编号已经在include/dt-bindings/clock/s3c2410.h中声明了;
另外,注意:如果时钟提供者将#clock-cells指定为“0”,则仅会出现该对中的phandle部分。
关于clock这部分可以参考内核文档:
- Documentation/devicetree/bindings/clock/samsung,s3c2410-clock.txt;
- Documentation/devicetree/bindings/clock/clock-bindings.txt;
1.5.1 未使用设备树
在内核移植不使用设备树的时候,在arch/arm/mach-s3c24xx/mach-smdk2440.c文件:
MACHINE_START(S3C2440, "SMDK2440") /* Maintainer: Ben Dooks <ben-linux@fluff.org> */ .atag_offset = 0x100, .init_irq = s3c2440_init_irq, .map_io = smdk2440_map_io, .init_machine = smdk2440_machine_init, .init_time = smdk2440_init_time, MACHINE_END static void __init smdk2440_init_time(void) { s3c2440_init_clocks(12000000); samsung_timer_init(); }
直接调用s3c2440_init_clocks初始化linux内核的时钟,晶振频率为12MHz;
void __init s3c2440_init_clocks(int xtal) { s3c2410_common_clk_init(NULL, xtal, 1, S3C24XX_VA_CLKPWR); // 1对应的枚举变量S3C2440 }
这里直接调用s3c2410_common_clk_init进行s3c2440时钟的初始化,具体可以参考linux驱动移植-通用时钟框架子系统。
1.5.2 使用设备树
在linux内核根路径下搜索samsung,s3c2440-clock:
root@zhengyang:/work/sambashare/linux-5.2.8-dt# grep "s3c2440-clock" * -nR Documentation/devicetree/bindings/clock/samsung,s3c2410-clock.txt:11: - "samsung,s3c2440-clock" - controller compatible with S3C2440 SoC. drivers/clk/samsung/clk-s3c2410.c:437:CLK_OF_DECLARE(s3c2440_clk, "samsung,s3c2440-clock", s3c2440_clk_init);
定位到drivers/clk/samsung/clk-s3c2410.c文件:
static void __init s3c2440_clk_init(struct device_node *np) { s3c2410_common_clk_init(np, 0, S3C2440, NULL); } CLK_OF_DECLARE(s3c2440_clk, "samsung,s3c2440-clock", s3c2440_clk_init);
CLK_OF_DECLARE的作用如下所述:用CLK_OF_DECLARE声明兼容的时钟驱动,并将其与初始化函数关联。
其中CLK_OF_DECLARE宏定义在include/linux/clk-provider.h中:
#define CLK_OF_DECLARE(name, compat, fn) \ static const struct of_device_id __clk_of_table_##name \ __used __section(__clk_of_table) \ = { .compatible = compat, .data = fn };
这一段需要借助内核编译的lds文件来解读,其中传入了参数给编译器来确定变量的存放位置,其实就是定义了:
static const struct of_device_id __clk_of_table_s3c2440_clk \ __used__section(__clk_of_table) \ = { .compatible = "samsung,s3c2440-clock", .data = s3c2440_clk_init, };
通过CLK_OF_DECLARE来定义相应的of_device_id,并且要把相应的驱动初始化函数fn的地址(即s3c2440_clk_init)传给data。
这样当设备树定义有compatible = "samsung,s3c2440-clock"时,匹配到相应的设备时就会直接调用驱动初始化函数s3c2440_clk_init了。
s3c2440_clk_init函数其内部调用s3c2410_common_clk_init进行进行s3c2440时钟的初始化。
1.6 串口节点
s3c2440只有三个串口,因此需要修改s3c2440.dtsi文件中定义的串口节点uart_0、uart_1、uart_2,同时删除uart_3节点:
uart_0: serial@50000000 { compatible = "samsung,s3c2440-uart"; // 对应驱动定义在drivers/tty/serial/samsung.c clock-names = "uart"; clocks = <&clocks PCLK_UART0>; }; uart_1: serial@50004000 { compatible = "samsung,s3c2440-uart"; clock-names = "uart"; clocks = <&clocks PCLK_UART1>; }; uart_2: serial@50008000 { compatible = "samsung,s3c2440-uart"; clock-names = "uart"; clocks = <&clocks PCLK_UART2>; };
在父级设备树s3c24xx.dtsi文件中定义有:
uart0: serial@50000000 { compatible = "samsung,s3c2410-uart"; reg = <0x50000000 0x4000>; interrupts = <1 28 0 4>, <1 28 1 4>; status = "disabled"; }; uart1: serial@50004000 { compatible = "samsung,s3c2410-uart"; reg = <0x50004000 0x4000>; interrupts = <1 23 3 4>, <1 23 4 4>; status = "disabled"; }; uart2: serial@50008000 { compatible = "samsung,s3c2410-uart"; reg = <0x50008000 0x4000>; interrupts = <1 15 6 4>, <1 15 7 4>; status = "disabled"; };
串口节点属性在上一片博客已经介绍过了,这里不重复介绍了。
在drivers/tty/serial/samsung.c中可以匹配关键字 “samsung,s3c2440-uart”;当platform设备和驱动匹配后,s3c24xx_serial_probe函数被调用。
在s3c24xx_serial_probe函数中,调用clk_get(&platdev->dev, "uart")根据设备名称/时钟别名来查找并获取对时钟生产者的引用,获取时钟生产者的clk结构体。其中:
- dev是时钟使用者设备(device for clock consumer);
- "uart"是串口时钟别名;
关于串口这部分可以参考内核文档:
- Documentation/devicetree/bindings/serial/samsung_uart.txt;
1.7 看门狗节点
修改s3c2440.dtsi中看门狗节点信息:
watchdog: watchdog@53000000 { interrupts = <1 9 27 3>; clocks = <&clocks PCLK>; /* 修改为PCLK */ clock-names = "watchdog"; };
在父级设备树s3c24xx.dtsi文件中定义有:
watchdog@53000000 { compatible = "samsung,s3c2410-wdt"; reg = <0x53000000 0x100>; interrupts = <0 0 9 3>; status = "disabled"; };
从S3C2440芯片手册上看,看门狗的时钟直接接到PCLK上,没有加任何开关,所以这里clocks修改为PCLK。
关于watchdog这部分可以参考内核文档:
- Documentation/devicetree/bindings/watchdog/samsung-wdt.txt;
1.8 rtc节点
修改s3c2440.dtsi中rtc节点信息:
rtc: rtc@57000000 { compatible = "samsung,s3c2140-rtc"; // 对应驱动对应在 drivers/rtc/rtc-s3c.c clocks = <&clocks PCLK_RTC>; clock-names = "rtc"; };
在父级设备树s3c24xx.dtsi文件中定义有:
rtc@57000000 { compatible = "samsung,s3c2410-rtc"; reg = <0x57000000 0x100>; interrupts = <0 0 30 3>, <0 0 8 3>; status = "disabled"; };
在drivers/rtc/rtc-s3c.c中可以匹配关键字 “samsung,s3c2410-rtc”;当platform设备和驱动匹配后,s3c_rtc_probe函数被调用。
在s3c_rtc_probe函数中,调用devm_clk_get(&pdev->dev, "rtc")根据设备名称/时钟别名来查找并获取对时钟生产者的引用,获取时钟生产者的clk结构体。其中:
- dev是时钟使用者设备(device for clock consumer);
- "rtc"是rtc时钟别名;
关于rtc这部分可以参考内核文档:
- Documentation/devicetree/bindings/rtc/rtc.txt;
- Documentation/devicetree/bindings/rtc/s3c-rtc.txt;
1.9 i2c节点
修改s3c2440.dtsi中i2c节点信息:
i2c@54000000 { compatible = "samsung,s3c2440-i2c"; clocks = <&clocks PCLK_I2C0>; clock-names = "i2c"; };
在父级设备树s3c24xx.dtsi文件中定义有:
i2c@54000000 { compatible = "samsung,s3c2410-i2c"; reg = <0x54000000 0x100>; interrupts = <0 0 27 3>; #address-cells = <1>; #size-cells = <0>; status = "disabled"; };
关于i2c这部分可以参考内核文档:
- Documentation/devicetree/bindings/i2c/i2c-s3c2410.txt;
二、修改设备树s3c2440-smdk2440.dts
修改s3c2440-smdk2440.dts设备树,同时将该文件中的2416全部替换成2440:
/dts-v1/; #include "s3c2440.dtsi" / { model = "SMDK2440"; compatible = "samsung,s3c2440","samsung,mini2440"; memory@30000000 { device_type = "memory"; reg = <0x30000000 0x4000000>; }; clocks { compatible = "simple-bus"; #address-cells = <1>; #size-cells = <0>; xti: xti@0 { compatible = "fixed-clock"; reg = <0>; clock-frequency = <12000000>; clock-output-names = "xti"; #clock-cells = <0>; }; }; }; &rtc { status = "okay"; }; &sdhci_0 { pinctrl-names = "default"; pinctrl-0 = <&sd1_clk>, <&sd1_cmd>, <&sd1_bus1>, <&sd1_bus4>; bus-width = <4>; broken-cd; status = "okay"; }; &sdhci_1 { pinctrl-names = "default"; pinctrl-0 = <&sd0_clk>, <&sd0_cmd>, <&sd0_bus1>, <&sd0_bus4>; bus-width = <4>; cd-gpios = <&gpf 1 0>; cd-inverted; status = "okay"; }; &uart_0 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart0_data>, <&uart0_fctl>; }; &uart_1 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart1_data>, <&uart1_fctl>; }; &uart_2 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart2_data>; }; &uart_3 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart3_data>; }; &watchdog { status = "okay"; };
2.1 fixed-clock时钟配置
晶振设备节点xti定义如下:
clocks { compatible = "simple-bus"; #address-cells = <1>; #size-cells = <0>; xti: xti@0 { compatible = "fixed-clock"; reg = <0>; clock-frequency = <12000000>; clock-output-names = "xti"; #clock-cells = <0>; }; };
根据compatible可以找到对应的驱动,驱动程序将晶振的频率记录下来,以后作为计算的基准。
三、uboot FDT命令
uboot提供了fdt的相关命令:
"addr [-c] <addr> [<length>] - Set the [control] fdt location to <addr>\n" "fdt move <fdt> <newaddr> <length> - Copy the fdt to <addr> and make it active\n" "fdt resize [<extrasize>] - Resize fdt to size + padding to 4k addr + some optional <extrasize> if needed\n" "fdt print <path> [<prop>] - Recursive print starting at <path>\n" "fdt list <path> [<prop>] - Print one level starting at <path>\n" "fdt get value <var> <path> <prop> - Get <property> and store in <var>\n" "fdt get name <var> <path> <index> - Get name of node <index> and store in <var>\n" "fdt get addr <var> <path> <prop> - Get start address of <property> and store in <var>\n" "fdt get size <var> <path> [<prop>] - Get size of [<property>] or num nodes and store in <var>\n" "fdt set <path> <prop> [<val>] - Set <property> [to <val>]\n" "fdt mknode <path> <node> - Create a new node after <path>\n" "fdt rm <path> [<prop>] - Delete the node or <property>\n" "fdt header - Display header info\n" "fdt bootcpu <id> - Set boot cpuid\n" "fdt memory <addr> <size> - Add/Update memory node\n" "fdt rsvmem print - Show current mem reserves\n" "fdt rsvmem add <addr> <size> - Add a mem reserve\n" "fdt rsvmem delete <index> - Delete a mem reserves\n" "fdt chosen [<start> <end>] - Add/update the /chosen branch in the tree\n" " <start>/<end> - initrd start/end addr\n"
参考文章
[1]linux设备驱动(19)设备树详解3-u-boot传输dts
[2]linux设备驱动(20)设备树详解4-kernel解析dts
[9]TQ2440设备树