学习Linux Kernel DT(Device Tree)总结

  之前在使用的3.0.8版本内核还没有使用上DT,而最近在研发使用的3.10.37版本内核已使用上了DT,瞬间感觉自己的知识体系更新慢了,查了资料发现3.x版本的内核已经支持DT了,为何ARM也要使用上DT呢?
    在旧版本的ARM Linux内核里,我们习惯上会去arch/arm/mach-XXX/目录下进行一些板载级设备配置,尤其在board-YYY.c文件里使用platform_add_devices()等函数去注册一堆硬件设备以及板级初始化操作,还有如下宏:
MACHINE_START(project name, "board name")
        .boot_params    = PLAT_PHYS_OFFSET + 0x800,
        .fixup          = XXX_fixup,
        .reserve        = &XXX_reserve,
        .map_io         = XXX_map_io,
        .init_irq       = XXX_init_irq,
        .timer          = &XXX_timer,
        .init_machine   = XXX_init,
MACHINE_END
    其中的XXX_init函数里就会调用platform_add_devices()
    以及arch/arm/plat-XXX目录下也有一堆平台级的操作,一般在进行移植工作的时候,就是修改了上面的board-YYY.c文件,调试好了各种Clock之后,剩下的就是设备驱动程序了。这些处理往往在所有ARM平台里有很多的相同操作和共同定义,而这些往往存在了大量的重复编码工作,故而Linux内核的开发人员和ARM的相关人员引入了DT来改善该问题,相关的历史有如下引用内容:
Linus Torvalds 2011 3 17 日的ARM Linux 邮件列表宣称“this whole ARM thing is a f*cking pain in the ass”,引发ARM Linux 社区的地震,随后ARM 社区进行了一系列的重大修正。在过去的ARM Linux 中,arch/arm/plat-xxx arch/arm/mach-xxx 中充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不过是垃圾,如板上的platform 设备、resourcei2c_board_infospi_board_info 以及各种硬件的platform_data。读者有兴趣可以统计下常见的s3c2410s3c6410 等板级目录,代码量在数万行。
社区必须改变这种局面,于是PowerPC 等其他体系架构下已经使用的Flattened Device TreeFDT)进入ARM 社区的视野。Device Tree 是一种描述硬件的数据结构,它起源于
OpenFirmware (OF)。在Linux 2.6 中,ARM 架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx arch/arm/mach-xxx,采用Device Tree 后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel 中进行大量的冗余编码。
Device Tree 由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name value。在Device Tree 中,可描述的信
息包括(原先这些信息大多被hard code kernel 中):
§ CPU 的数量和类别
§ 内存基地址和大小
§ 总线和桥
§ 外设连接
§ 中断控制器和中断使用情况
§ GPIO 控制器和GPIO 使用情况
§ Clock 控制器和Clock 使用情况
它基本上就是画一棵电路板上CPU、总线、设备组成的树,Bootloader 会将这棵树传递给内核,然后内核可以识别这棵树,并根据它展开出Linux 内核中的platform_devicei2c_clientspi_device 等设备,而这些设备用到的内存、IRQ 等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。
    了解完相关历史后,我们接下来分如下几个方面了解ARM Linux中的DT
使用和不使用DTbootloaderkernel的影响
关于DT
kernel中的DT

.使用和不使用DTbootloaderkernel的影响
1.不使用DT
    不使用DT时,kernel包含了硬件的完整描述信息,bootloader加载单独的一个二进制文件(kernel镜像文件uImagezImage)并执行它,bootloader通过寄存器r2传递ATAGS(为一些附加信息,如RAM大小和地址、cmdline等)给kernel,通过寄存器r1传递一个机器类型(machine type,用于告诉内核将启动哪一款板卡)整数给kernel。有如下映射:
    
    这时候,在U-boot命令行里执行bootm <kernel img addr>命令可以启动kernel
2.使用DT
   使用DT时,kernel包含的硬件完整打桩信息被提取为一个二进制文件DTBdevice tree blob)文件,bootloader则需要加载kernel镜像(uImagezImage)以及DTBarch/arm/boot/dts/目录下的DTS文件<一个板卡一个dts文件>通过DTC编译成DTB文件),bootloader通过寄存器r2传递DTB文件(该文件也包含了RAM信息、cmdline等信息)所在地址给kernel,而原先传递板卡类型整数的r1则不需要再关注了,相应的映射如下:
    

    这时候,在U-boot里使用命令bootm <kernel img addr> - <dtb addr>来启动kernel
3.bootloaderDT的支持
    Uboot的主线代码从v1.1.3开始就支持DT了,其对ARM的支持和kernelDevice Tree的支持是同期完成的,在Uboot中需要在config文件中加入#define CONFIG_OF_LIBFDT配置项即可,当我们将DTB文件在Uboot里加载到内存中后,通过fdt addr 0xnnnnnnnn命令来设置DTB文件对应地址,这样就可以使用fdt resizefdt print等命令对DTB文件进行操作了。对于ARM,使用bootz kernel_addr initrd_addr dtb_addr命令来启动kerneldtb_addr作为bootzbootm最后一个参数,第一个参数为内核镜像的地址,第二个参数为initrd的地址,如不存在,使用-代替(看完这句话,一的2中的命令就能理解了)。
4.kernelDT兼容引导模式
    在实际情况下,存在部分平台过旧,方案厂无法提供新版的bootloader,而原有的bootloader不支持DT时,还好kernel有兼容机制:
     当设置CONFIG_ARM_APPENDED_DTBy时,它表示我们使用kernel时,需要在kernel镜像后面查找DTB信息(即kernel镜像后紧挨着DTB),而Makefile也没有相应的规则去生成相应格式的kernel镜像,此时,我们需要执行如下命令手动制作相应的镜像:
    cat arch/arm/boot/zImage arch/arm/boot/dts/myboard.dtb > my-zImage
    mkimage ... d my-zImage my-uImage
    而当设置CONFIG_ARM_ATAG_DTB_COMPATy时,它表示kernel将从bootloader获取到ATAGS信息,并更新DT文件使用这些信息。
.关于DT
     首先,先看下一个基本的DT语法格式图示:
     

    上图中所保存成的一个.dts文件并不会有实质的功能,仅仅是一个Device Tree源文件结构的呈现,由图可见,一个.dts文件包含一个root结点"/"root结点下面有一系列子结点,上图中有node@0node@1,其中node@0下面还有两个子结点child-node@0child-node@1node@1下面有child-node@0子结点,而结点中又有一系列的属性,如属性来空:an-empty-property,为字符串:a-string-property,为字符串数组:a-string-list-property,为二进制:a-byte-data-property,为Cells(由u32整数组成):a-cell-propertysecond-child-property,为引用:a-reference-to-something。还有别名node1,实际上是node@1结点,如果引用时,则需要完整路径/node@1,而使用别名可以省掉这绝对路径的一长串字符。
    接下来,拿一个实例来说明下一个平台于DTS的配置:
    假如我们有如下配置的一台机器:
· 1个双核ARM Cortex-A9 32位处理器
· ARMlocal bus上的内在映射区域分布如下控制器:
·   2个串口(分别位于0x101F10000x101F2000
·   GPIO控制器(0x101F3000
·   SPI控制器(0x10170000
·   中断控制器(0x10160000
·   一个external bus桥,桥上连接如下设备:
·     SMC SMC91111 Ethernet0x10100000
·     I2C控制器(0x10160000),上面接了如下设备:
·       Maxim DS1338 RTCI2C地址0x58
·     64MB Nor Flash0x30000000
     上述配置对应的.dts文件内容如下:
     / {
          compatible = "acme,coyotes-revenge";
          #address-cells = <1>;
          #size-cells = <1>;
          interrupt-parent = <&intc>;
          cpus {
               #address-cells = <1>;
               #size-cells = <0>;
               cpu@0 {
                    compatible = "arm,cortex-a9";
                    reg = <0>;
               };
               cpu@1 {
                    compatible = "arm,cortex-a9";
                    reg = <1>;
               };
          };
          serial@101f0000 {
               compatible = "arm,pl011";
               reg = <0x101f0000 0x1000 >;
               interrupts = < 1 0 >;
          };
          serial@101f2000 {
               compatible = "arm,pl011";
               reg = <0x101f2000 0x1000 >;
               interrupts = < 2 0 >;
          };
          gpio@101f3000 {
               compatible = "arm,pl061";
               reg = <0x101f3000 0x1000
                         0x101f4000 0x0010>;
               interrupts = < 3 0 >;
          };
          intc: interrupt-controller@10140000 {
               compatible = "arm,pl190";
               reg = <0x10140000 0x1000 >;
               interrupt-controller;
               #interrupt-cells = <2>;
          };
          spi@10115000 {
               compatible = "arm,pl022";
               reg = <0x10115000 0x1000 >;
               interrupts = < 4 0 >;
          };
          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 {
                    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>;
                    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>;
               };
          };
     };
    上面的.dts文件中,root结点"/"compatible属性(compatible = "acme,coyotes-revenge";)定义了系统的名字,其组织形式为:<manufacturer>,<model>Linux kernel通过root结点的该属性就可以判断需要启动的是什么machine
    .dts文件中的每个设备,都有一个compatible属性,用于驱动与设备间的绑定。compatible属性是一个字符串列表,该列表第一个字符串表示了结点所代表的确切设备,其后的字符串代表可兼容的设备,在上面的.dts文件中有如下内容:
    flash@2,0 {
        compatible = "samsung,k8f1315ebm", "cfi-flash";
        reg = <2 0 0x4000000>;
    };
    其中,compatible属性的第一个字符串"samsung,k8f1315ebm"表示了该配置项是对samsungk8f1315ebm这款Nor Flash的支持,第二个字符串"cfi-flash"表示可被兼容的设备型号(省略了厂商信息,表示可用的范围更广)。
    接下来root结点"/"cpus子结点下包含了2cpu子结点,描述了该machine上的双核CPU,并且这两个子结点的compatible属性都为"arm,cortex-a9"。注意这两个子结点的命名都遵循的组织形式为:<name>[@<unit-address>],其中<>中的内容是必选项,而[]中的则为可选项。name是一个ASCII字符串,用于描述结点对应的设备类型,如3com Ethernet PHY对应的结点nameethernet,而不是3com509。如果一结点描述的设备有地址,则应给出相应的@unit-address。多个相同类型的设备结点的name可以一样,只需相应的unit-address不同即可,如上述.dts文件中所示。设备的unit-address地址通常也在其对应结点的reg属性中给出。
    可寻址的设备使用reg#address-cells#size-cells三属性来确定其在DT中的编址信息,其中reg的组织形式为:reg=<address1 length1 [address2 length2] [address3 length3] >,这里每一组address length表明了该设备使用的一个地址范围。addresslength字段是可变长的,address1个或多个cell32位的整形),而length则为cell列表或空,父结点的#address-cells#size-cells决定了子结点的reg属性的addresslength字段的长度。在上面的.dts中,root结点的#address-cells=<1>;#size-cells=<1>;决定了serialgpiospi等结点的addresslength字段的长度都为1cpus结点的#address-cells=<1>;#size-cells=<0>;决定了2cpu子结点的address长度为1,而length为空,即2cpureg分别为reg=<0>;reg=<1>;external-bus结点的#address-cells=<2>;#size-cells=<1>;决定了其子结点etherneti2cflashreg字段为reg=<0 0 0x1000>;reg=<1 0 0x1000>;reg=<2 0 0x4000000>;,这三个reg的第一个值分别为012,是对应的片选,第二个值都为0,表示相应片选的基地址,第三个值分别为0x10000x10000x4000000length。特别要注意的是i2c结点中定义的#address-cells=<1>;#size-cells=<0>;作用到相应总结上的RTC设备的address字段则为0x58,是该设备的i2c地址。
    对于root结点的子结点,其address区域直接位于CPUmemory区域,均为CPU的视图范围,但是,经过总线桥后的address一般需要进行转换才能被CPUmemory区域映射到,从上面的.dts文件中可看到,external-busranges属性定义了经过external-bus桥后的地址范围如何映射到CPUmemory区域,如下所示:
    ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet
                  1 0 0x10160000 0x10000 // Chipselect 2, i2c controller
                  2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash
    ranges是地址转换表,其中第个项目是一个子地址、父地址及其空间大小的映射。映射表中的子地址、父地址分别采用子地址空间的#address-cells和父地址空间的#address-cells大小。对于这儿的ranges,子地址空间的#address-cells2,父地址空间的#address-cells1,故而0 0 0x10100000 0x10000的前两项(cell)为external-bus片选0上偏移0,第3项为external-bus片选0上偏移0的地址空间被映射到CPU0x10100000位置上,第4项为映射的大小为0x10000。后面的两组片选同样理解。
    对于中断控制器,DT提供了如下属性:
    interrupt-controller——这个属性为空,中断控制器使用该属性表明身份;
    #interrupt-cells——和#address-cells#size-cells相似,表明使用该中断控制器的设备的interrupts属性的cell大小;
    interrupt-parent——设备结点通过它来指定其所依附的中断控制器的phandle,当结点没指定interrupt-parent时,则从父结点继续。上面的.dts中,root结点指定了interrupt-parent=<&intc>;其对应于intc:interrupt-controller@10140000,而root结点的子结点没有指定interrupt-parent,故而都继续了intc,即位于0x10140000的中断控制器。
    interrupts——用到了中断的设备结点通过它来指定中断号、触发方式等,具体该属性含有多少个cell,由其所依附的中断控制器结点的#interrupt-cells属性决定,而具体每个cell的含义,一般由驱动的实现决定,其会在DTbinding文档中说明,如ARM GIC中断控制器,#interrupt-cells33cell的具体含义可查阅Documentation/devicetree/bindings/arm/gic.txt文件,有如下说明:
  The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI interrupts.
  The 2nd cell contains the interrupt number for the interrupt type.
  SPI interrupts are in the range [0-987].  PPI interrupts are in the range [0-15].
  The 3rd cell is the flags, encoded as follows:
        bits[3:0] trigger type and level flags.
                1 = low-to-high edge triggered
                2 = high-to-low edge triggered
                4 = active high level-sensitive
                8 = active low level-sensitive
        bits[15:8] PPI interrupt cpu mask.  Each bit corresponds to each of the 8 possible cpus attached to the GIC.  A bit set to '1' indicated the interrupt is wired to that CPU.  Only valid for PPI interrupts.
    还需注意的是,一个设备可能使用多个中断号。对于ARM GIC来说,若某设备使用SPI168169两个中断,并且都是高电平触发,那么该设备结点的interrupts属性可定义为:interrupts=<0 168 4>,<0 169 4>;。除了中断外,ARM LinuxclockGPIOpinmux都可通过.dts来描述,而pinmux/pinctl与对应的平台关系较大。
.kernel中的DT
1.DTBDTSDTC
    ARM Linux Kernel中,所有的DTSDevice Tree Source)文件存放在arch/arm/boot/dts目录下,该目录下后缀为.dts的文件为板级定义,而.dtsiSOC级定义,是被包含的文件,在.dts文件中会使用/include/ "XXX.dtsi"#include "XXX.dtsi"这样的语句放在文件最开始位置,将相关文件包含进来。既然.dts可以包含.dtsi,如果碰到两文件中有定义了同一结点,那么会合并两文件中的所有属性,而当为同一属性时,则以.dts文件中的为准,有如下例图所示:
    

    对于DTCDevice Tree Compiler)会将.dts文件编译为.dtb文件,该工具的源码在scripts/dtc目录下,在该目录下的Makefile文件中有hostprogs-y     := dtc一句,确保在编译时会将该dtc编译为主机工具。
    对于DTBDevice Tree Blob)文件由DTC编译生成,是在bootloader中被加载和在kernel引导时被解析的二进制文件。
2.kernelDT支持做的改变
    使用DT后,以往所使用的大量板级信息都不再需要的,例如在arch/arm/plat-xxxarch/arm/mach-xxx曾经经常实施的操作:
    a.注册platform_device、绑定resource,即内存和IRQ等板级信息
        使用DT后,形如
        static struct resource xxx_resources[] = {
            [0] = {
                .start = ,
                .end = ,
                .flags = IORESOURCE_MEM,
            },
            [1] = {
                .start = ,
                .end = ,
                .flags = IORESOURCE_IRQ,
            },
        };
        static struct platform_device xxx_device = {
            .name = "xxx",
            .id = -1,
            .dev = {
                .platform_data = &xxx_data,
           },
           .resource = xxx_resources,
            .num_resources = ARRAY_SIZE(xxx_resources),
        };
        之类的platform_device代码都不再需要了,其中platform_device 会由kernel自动展开,其中这些resource来自.dts的设备结点reginterruptes属性。
        比较典型的,大多数总结与simple_bus”都兼容,而在SoC对应machine.init_machine成员函数中,调用of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);来展开所有的platform_device。例如,有XXX SoC,在arch/arm/mach-xxx/的板级文件中有如下的展开.dts中设备结点对就的platform_device
        static struct of_device_id xxx_of_bus_ids[] __initdata = {
            { .compatible = "simple-bus", },
            {},
        };
        void __init xxx_mach_init(void){
            of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);
        }
        #ifdef CONFIG_ARCH_XXX
        DT_MACHINE_START(XXX_DT, "Generic XXX (Flattened Device Tree)")
            …
            .init_machine = xxx_mach_init,
            …
        MACHINE_END
       #endif
    b.注册i2c_board_info,指定IRQ等板级信息
        形如:
        static struct i2c_board_info __initdata afeb9260_i2c_devices[] = {
            {
                I2C_BOARD_INFO("tlv320aic23", 0x1a),
            }, {
                I2C_BOARD_INFO("fm3130", 0x68),
            }, {
                I2C_BOARD_INFO("24c64", 0x50),
            },
        };
        之类的代码,现在也不需要了,只需要将tlv320aic23fm313024c64之类的设备结点填充到相应的I2C Controller结点的子结点中,如上面的.dts中所示。DTI2C Client会通过I2C Host驱动的probe()函数中调用of_i2c_register_devices(&i2c_dev->adapter);被自动展开使用。
    c.注册spi_board_info,指定IRQ等板级信息
        形如:
        static struct spi_board_info afeb9260_spi_devices[] = {
            { /* DataFlash chip */
                .modalias = "mtd_dataflash",
                .chip_select = 1,
                .max_speed_hz = 15 * 1000 * 1000,
                .bus_num = 0,
            },
        };
        之类的代码也不再需要了,只需将mtd_dataflash之类结点作为SPI控制器的子结点即可,SPI Host驱动的probe函数通过spi_register_master()注册master的时候,会自动展开其slave
    d.指定电路板的machine及相关callback
        以前,ARM Linux 针对不同的电路板会建立由MACHINE_START MACHINE_END 包围起来的针对这个machine 的一系列callback,如:
        MACHINE_START(VEXPRESS, "ARM-Versatile Express")
                .atag_offset = 0x100,
                .smp = smp_ops(vexpress_smp_ops),
                .map_io = v2m_map_io,
                .init_early = v2m_init_early,
                .init_irq = v2m_init_irq,
                .timer = &v2m_timer,
                .handle_irq = gic_handle_irq,
                .init_machine = v2m_init,
                .restart = vexpress_restart,
        MACHINE_END
        Uboot在启动kernel时会将MACHINE_ID放在r1中,Linux Kernel启动时会匹配传过来的MACHINE_ID,将该值与MACHINE_START处声明的MACHINE_ID比对,如匹配则会执行相应machine的一系列初始化函数。
        使用DT后,MACHINE_START变为DT_MACHINE_START,其中多了一个.dt_compat成员,用于指定该machine.dtsroot结点compatible属性兼容关系。一旦bootloader传递给kernelDTBroot结点compatible属性出现在某machine.dt_compat表中,则匹配上了,从而执行相应的初始化函数。如:
        static const char * const v2m_dt_match[] __initconst = {
                "arm,vexpress",
                "xen,xenvm",
                NULL,
        };
        DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express")
                .dt_compat = v2m_dt_match,
                .smp = smp_ops(vexpress_smp_ops),
                .map_io = v2m_dt_map_io,
                .init_early = v2m_dt_init_early,
                .init_irq = v2m_dt_init_irq,
                .timer = &v2m_dt_timer,
                .init_machine = v2m_dt_init,
                .handle_irq = gic_handle_irq,
                .restart = vexpress_restart,
        MACHINE_END
        Linux中倡导对多个SoC、多个电路板的通用DT machine,即一个DT machine.dt_compat表含有多个电路板.dts文件的root结点compatible属性字符串,之后,如果不同的电路板的初始化序列不一样时,可通过of_machine_is_compatible() API来判断。例如arch/arm/mach-exynos/mach-exynos5-dt.c EXYNOS5_DT machine 同时兼容"samsung,exynos5250""samsung,exynos5440"
        static char const *exynos5_dt_compat[] __initdata = {
                "samsung,exynos5250",
                "samsung,exynos5440",
                NULL
        };
        DT_MACHINE_START(EXYNOS5_DT, "SAMSUNG EXYNOS5 (Flattened Device Tree)")
                /* Maintainer: Kukjin Kim <kgene.kim@samsung.com> */
                .init_irq = exynos5_init_irq,
                .smp = smp_ops(exynos_smp_ops),
                .map_io = exynos5_dt_map_io,
                .handle_irq = gic_handle_irq,
                .init_machine = exynos5_dt_machine_init,
                .init_late = exynos_init_late,
                .timer = &exynos4_timer,
                .dt_compat = exynos5_dt_compat,
                .restart = exynos5_restart,
                .reserve = exynos5_reserve,
        MACHINE_END
        .init_machine成员函数中有针对不同machine进行分支处理:
        static void __init exynos5_dt_machine_init(void)
        {
                …
                if (of_machine_is_compatible("samsung,exynos5250"))
                        of_platform_populate(NULL, of_default_bus_match_table, exynos5250_auxdata_lookup, NULL);
                else if (of_machine_is_compatible("samsung,exynos5440"))
                        of_platform_populate(NULL, of_default_bus_match_table, exynos5440_auxdata_lookup, NULL);
        }
        使用DT后,驱动需要与.dts中描述的设备结点匹配,才会引发驱动的probe()函数执行。对于platform_driver,需要添加一个OF匹配表,如上面的.dts文件的"acme,a1234-i2c-bus"兼容I2C 控制器结点的OF 匹配表为:
        static const struct of_device_id a1234_i2c_of_match[] = {
                { .compatible = "acme,a1234-i2c-bus ", },
                {},
        };
        MODULE_DEVICE_TABLE(of, a1234_i2c_of_match);

        static struct platform_driver i2c_a1234_driver = {
                .driver = {
                        .name = "a1234-i2c-bus ",
                        .owner = THIS_MODULE,
                        .of_match_table = a1234_i2c_of_match,
                },
                .probe = i2c_a1234_probe,
                .remove = i2c_a1234_remove,
        };
        module_platform_driver(i2c_a1234_driver);
        对于I2CSPI从设备,同样也可以通过of_match_table添加匹配的.dts中相关结点的compatible属性,如sound/soc/codecs/wm8753.c中有:
        static const struct of_device_id wm8753_of_match[] = {
                { .compatible = "wlf,wm8753", },
                { }
        };
        MODULE_DEVICE_TABLE(of, wm8753_of_match);
        static struct spi_driver wm8753_spi_driver = {
                .driver = {
                        .name = "wm8753",
                        .owner = THIS_MODULE,
                        .of_match_table = wm8753_of_match,
                },
                .probe = wm8753_spi_probe,
                .remove = wm8753_spi_remove,
        };
        static struct i2c_driver wm8753_i2c_driver = {
                .driver = {
                        .name = "wm8753",
                        .owner = THIS_MODULE,
                        .of_match_table = wm8753_of_match,
                },
                .probe = wm8753_i2c_probe,
                .remove = wm8753_i2c_remove,
                .id_table = wm8753_i2c_id,
        };
        这需要注意的是,I2CSPI外设驱动和DT中的compatible属性还有一种弱匹配方法,即别名匹配。compatible属性的组织形式为:<manufacturer>,<model>,别名为去掉manufacturer所剩下的model。对此,在drivers/spi/spi.c文件中的spi_match_device()函数中有相应处理,如果别名在设备spi_driverid_table里或与spi_drivername字段相同,SPI设备都可以和驱动匹配上:
        static int spi_match_device(struct device *dev, struct device_driver *drv)
        {
                const struct spi_device *spi = to_spi_device(dev);
                const struct spi_driver *sdrv = to_spi_driver(drv);

                /* Attempt an OF style match */
                if (of_driver_match_device(dev, drv))
                        return 1;

                /* Then try ACPI */
                if (acpi_driver_match_device(dev, drv))
                        return 1;

                if (sdrv->id_table)
                        return !!spi_match_id(sdrv->id_table, spi);

                return strcmp(spi->modalias, drv->name) == 0;
        }
        static const struct spi_device_id *spi_match_id(const struct spi_device_id *id, const struct spi_device *sdev)
        {
                while (id->name[0]) {
                        if (!strcmp(sdev->modalias, id->name))
                                return id;
                        id++;
                }
                return NULL;
        }
3.kernel中获取DT内容的API
    Linux Kernel中,会使用到与DT相关的API,通常以of_为前缀,其实现在drivers/of目录下,常用的API有:
    int of_device_is_compatible(const struct device_node *device,const char *compat);
    用于判断设备结点的compatible属性是否包含compat指定的字符串。当一个驱动支持2个以上设备时,这些不同的.dts文件中设备的compatible属性都会在驱动OF匹配表中。
    struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible);
    根据compatible属性获得设备结点。遍历DT中所有设备结点,确认哪个结点的类型、compatible属性与本函数的输入参数匹配,很多时候,fromtypeNULL
    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位整型数组的属性。有时候整型属性的长度可能为1,故而双有如下API,其在include/linux/of.h文件中定义如下:
    static inline int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value)
    {
        return of_property_read_u8_array(np, propname, out_value, 1);
    }
    static inline int of_property_read_u16(const struct device_node *np, const char *propname, u16 *out_value)
    {
        return of_property_read_u16_array(np, propname, out_value, 1);
    }
    static inline int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value)
    {
        return of_property_read_u32_array(np, propname, out_value, 1);
    }

    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个字符串。
    static inline bool of_property_read_bool(const struct device_node *np, const char *propname);
    如果设备结点np含有propname属性,则返回true,否用返回false。一般用于检查是否存在空属性。
    void __iomem *of_iomap(struct device_node *node, int index);
    通过设备结点直接进行设备内存区间的ioremap()index是内存段的索引。如果设备结点的reg属性有多段,可通过index来标示取ioremap的哪一段,只为1段的情况下,index才为0。使用DT后,设备驱动使用of_iomap()替代ioremap()来进行映射。
    unsigned int irq_of_parse_and_map(struct device_node *dev, int index);
    通过DT或设备的中断号,实际是从.dts中的interrupts属性解析出中断号,如设备使用多个中断,index指定中断的索引号。
    还有一些驱动端在使用DT后获取属性时的API范例:
    a.获取时钟的引用
        clocks属性
        s->clk=clk_get(&pdev->dev, NULL);
    b.获取I/O寄存器资源
        reg属性
        r=platform_get_resource(pdev, IORESOURCE_MEM, 0);
    c.获取中断
        interrupts属性
        s->irq=platform_get_irq(pdev, 0);
    d.获取DMA channel
        dmas属性
        s->rx_dma_chan=dma_request_slave_channel(s->dev, "rx");
        s->tx_dma_chan=dma_request_slave_channel(s->dev, "tx");
.参考资料
    http://devicetree.org/Device_Tree_Usage

评论

此博客中的热门博文

I/O映射之I/O端口

使用seq_file

Shell命令控制蜂鸣器发声