Bootstrap

DTS 总结

前言

dts 相关学习笔记

参考资料:
MTK + 7.0 源码
http://www.wowotech.net/device_model/why-dt.html wowo 系列
https://blog.csdn.net/21cnbao/article/details/8457546 宋宝华
【非常好】devicetree_org_Device_Tree_Usage.dts 简单示例介绍.pdf
【】devicetree-specification-v0.1-20160524.pdf
【非常好】Power_ePAPR_APPROVED_v1.1【基于这个平台的设备树详细例子】.pdf
【好】linux-DTS基本知识.pptx

概念及示例

DTS 基本知识交流

一.什么是DTS?为什么要引入DTS?

    DTS 即 Device Tree Source 设备树源码, Device Tree 是一种描述硬件的数据结构,它起源于 OpenFirmware (OF)。
在 Linux 2.6 中,ARM 架构的板极硬件细节过多地被硬编码在 arch /arm/plat-xxx和arch/arm/mach-xxx,比如板上的 
platform 设备、resource、i2c_board_info、spi_board_info 以及各种硬件的 platform_data,这些板级细节代码
对内核来讲只不过是垃圾代码。而采用 Device Tree 后,许多硬件的细节可以直接透过它传递给 Linux,而不再需要
在 kernel 中进行大量的冗余编码。   

    每次正式的 linux kernel release 之后都会有两周的 merge window,在这个窗口期间,kernel 各个部分的维护
者都会提交各自的 patch,将自己测试稳定的代码请求并入 kernel main line。每到这个时候,Linus 就会比较繁忙,
他需要从各个内核维护者的分支上取得最新代码并 merge 到自己的 kernel source tree 中。Tony Lindgren,内核
OMAP development tree 的维护者,发送了一个邮件给 Linus,请求提交OMAP平台代码修改,并给出了一些细节描述:
       1)简单介绍本次改动
       2)关于如何解决 merge conficts。有些 git mergetool 就可以处理,不能处理的,给出了详细介绍和解决方案
       一切都很平常,也给出了足够的信息,然而,正是这个 pull request 引发了一场针对 ARM linux 的内核代码
       的争论。我相信 Linus 一定是对 ARM 相关的代码早就不爽了,ARM 的 merge 工作量较大倒在其次,主要是
       他认为 ARM 很多的代码都是垃圾,代码里面有若干愚蠢的 table,而多个人在维护这个 table,从而导致了
       冲突。因此,在处理完 OMAP 的 pull request 之后(Linus 并非针对 OMAP 平台,只是 Tony Lindgren 撞
       在枪口上了),他发出了怒吼: 
            Gaah. Guys, this whole ARM thing is a f*cking pain in the ass.
			
            
    之后经过一些讨论,对 ARM 平台的相关 code 做出如下相关规范调整,这个也正是引入 DTS 的原因:
        1、ARM 的核心代码仍然保存在 arch/arm 目录下
        2、ARM SoC core architecture code 保存在 arch/arm 目录下
        3、ARM SOC 的周边外设模块的驱动保存在 drivers 目录下
        4、ARM SOC 的特定代码在 arch/arm/mach-xxx 目录下
        5、ARM SOC board specific 的代码被移除,由 Device Tree 机制来负责传递硬件拓扑和硬件资源信息。
        
    本质上,Device Tree 改变了原来用 hardcode 方式将 HW 配置信息嵌入到内核代码的方法,改用 bootloader 传
    递一个DB的形式。
        如果我们认为 kernel 是一个 black box,那么其输入参数应该包括:
            a. 识别 platform 的信息   
            b. runtime 的配置参数   
            c. 设备的拓扑结构以及特性
         
        对于嵌入式系统,在系统启动阶段,bootloader 会加载内核并将控制权转交给内核,此外,还需要把上述的三个参
        数信息传递给 kernel,以便 kernel 可以有较大的灵活性。在 linux kernel 中,Device Tree 的设计目标就是
        如此。

二.DTS基本知识

1.DTS 的加载过程

    如果要使用 Device Tree,首先用户要了解自己的硬件配置和系统运行参数,并把这些信息组织成 Device Tree source file。
    通过 DTC(Device Tree Compiler),可以将这些适合人类阅读的 Device Tree source file 变成适合机器处理的 
    Device Tree binary file(有一个更好听的名字,DTB,device tree blob)。在系统启动的时候,boot program
    (例如:firmware、bootloader)可以将保存在 flash 中的 DTB copy 到内存(当然也可以通过其他方式,例如
    可以通过 bootloader 的交互式命令加载 DTB,或者 firmware 可以探测到 device 的信息,组织成 DTB 保存在内存中),
    并把 DTB 的起始地址传递给 client program(例如 OS kernel,bootloader 或者其他特殊功能的程序)。对于计算
    机系统(computer system),一般是 firmware->bootloader->OS,对于嵌入式系统,一般是 bootloader->OS。   

        【DTS】-- DTC -->【DTB】-- Bootloader -->【Kernel】

2.DTS 的描述信息

      Device Tree 由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,
    其实就是成对出现的 name 和 value。在 Device Tree 中,可描述的信息包括(原先这些信息大多被 hard code 
    到 kernel 中):
        CPU的数量和类别
        内存基地址和大小
        总线和桥
        外设连接
        中断控制器和中断使用情况
        GPIO 控制器和 GPIO 使用情况
        Clock 控制器和 Clock 使用情况
        
    它基本上就是画一棵电路板上 CPU、总线、设备组成的树,Bootloader 会将这棵树传递给内核,然后内核可以识
    别这棵树,并根据它展开出 Linux 内核中的 platform_device、i2c_client、spi_device 等设备,而这些设备
    用到的内存、IRQ 等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。
    
    是否 Device Tree 要描述系统中的所有硬件信息?答案是否定的。基本上,那些可以动态探测到的设备是不
    需要描述的,例如 USB device。不过对于 SOC 上的 usb host controller,它是无法动态识别的,需要在 
    device tree 中描述。同样的道理,在 computer system 中,PCI device 可以被动态探测到,不需要在 
    device tree 中描述,但是 PCI bridge 如果不能被探测,那么就需要描述之。
    .dts 文件是一种 ASCII 文本格式的 Device Tree 描述,此文本格式非常人性化,适合人类的阅读习惯。基
    本上,在 ARM Linux 在,一个 .dts 文件对应一个 ARM 的 machine,一般放置在内核的 arch/arm/boot/dts/
    目录。由于一个 SoC 可能对应多个 machine(一个 SoC 可以对应多个产品和电路板),势必这些 .dts 文件需
    包含许多共同的部分,Linux 内核为了简化,把 SoC 公用的部分或者多个 machine 共同的部分一般提炼为 .dtsi,
    类似于 C 语言的头文件。其他的 machine 对应的 .dts 就 include 这个 .dtsi。譬如,对于 RK3288 而言, 
    rk3288.dtsi 就被 rk3288-chrome.dts 所引用,rk3288-chrome.dts 有如下一行:#include “rk3288.dtsi”
    对于 rtd1195, 在 rtd-119x-nas.dts 中就包含了 /include/  "rtd-119x.dtsi" 当然,和 C 语言的头文件
    类似,.dtsi 也可以 include 其他的 .dtsi,譬如几乎所有的 ARM SoC 的 .dtsi 都引用了 skeleton.dtsi,
    即 #include "skeleton.dtsi“ 或者  /include/ "skeleton.dtsi"
    
    skeleton.dtsi。位于linux-3.14\arch\arm\boot\dts目录下,具体该文件的内容如下:
        / {                                                             //
            #address-cells = <1>;                                       //
            #size-cells = <1>;                                          //
            chosen { };                                                 //
            aliases { };                                                //
            memory {                                                    //
                device_type = "memory"; reg = <0 0>;                    // 定义了该 memory 的起始地址和长度
           };                                                           //  
        };                                                              //

      正常情况下所有的 dts 文件以及 dtsi 文件都含有一个根节点”/”,这样 include 之后就会造成有很多个
    根节点? 按理说 device tree 既然是一个树,那么其只能有一个根节点,所有其他的节点都是派生于根节点
    的 child node.
      其实 Device Tree Compiler 会对 DTS 的 node 进行合并,最终生成的 DTB 中只有一个 root  node.   
    device tree 的基本单元是 node。这些 node 被组织成树状结构,除了 root node,每个 node 都只有一个 parent。
    一个 device tree 文件中只能有一个 root node。每个 node 中包含了若干的 property/value 来描述该
    node 的一些特性。每个 node 用节点名字(node name)标识,节点名字的格式是 node-name@unit-address。
    如果该 node 没有 reg 属性(后面会描述这个 property ),那么该节点名字中必须不能包括 @ 和 unit-address。
    
        unit-address 的具体格式是和设备挂在那个 bus 上相关。例如对于 cpu,其 unit-address 就是从 0 开
    始编址,以此加一。而具体的设备,例如以太网控制器,其 unit-address 就是寄存器地址。root node 的
    node name 是确定的,必须是“/”。
     
      在一个树状结构的 device tree 中,如何引用一个 node 呢?要想唯一指定一个 node 必须使用 full path,
    例如 /node-name-1/node-name-2/node-name-N。

3.DTS 的组成结构

    node 的组成结构:
        [label:] node-name[@unit-address] {                             // []: 表示可选 label 为别名,方便在 dts 文件中引用
            [properties definitions]                                    //
            [child nodes]                                               // 子节点
        }                                                               //


	/ {  																//  【1 个  root 结点 /】																
		node1 {  														//  	【子结点 1】	
			a-string-property = "A string";  							//  		"字符串"								
			a-string-list-property = "first string", "second string";  	//  		字符串数组														
			a-byte-data-property = [0x01 0x23 0x34 0x56];  				//  		[二进制数]											
			child-node1 {  												//  	【子子结点 1】			
				first-child-property;  									//  		<Cells: u32 整数数组>						
				second-child-property = <1>;  							//								
				a-string-property = "Hello, world";  					//										
			};  														//	
			child-node2 {  												//  	【子子结点 2】			
			};  														//	
		};  															//
		node2 {  														//  	【子结点   2】	
			an-empty-property;  										//  		空属性					
			a-cell-property = <1 2 3 4>;  								// 			<Cells:     u32 整数数组>						
			child-node1 {  												//			
			};  														//	
		};  															//
	};                                                                           //

/
// 例:s3c2416 芯片的 dtsi     
    #include "skeleton.dtsi"
    / {
        
        //
        //  (A)在描述compatible属性之前要先描述model属性。model属性指明了该设备属于哪个设备生产商的哪一个model。
        //  一般而言,我们会给model赋值“manufacturer,model”。例如model = "samsung,s3c24xx"。samsung是生产商,
        //  s3c24xx是model类型,指明了具体的是哪一个系列的SOC。OK,现在我们回到compatible属性,该属性的值是string
        //  list,定义了一系列的modle(每个string是一个model)。这些字符串列表被操作系统用来选择用哪一个driver来驱动该
        //  设备。假设定义该属性:compatible = “aaaaaa”, “bbbbb"。那么操作操作系统可能首先使用aaaaaa来匹配适合的
        //  driver,如果没有匹配到,那么使用字符串bbbbb来继续寻找适合的driver,对于本例,compatible =
        //  "samsung,s3c24xx",这里只定义了一个modle而不是一个list。对于root node,compatible属性是用来匹配machine
        //  type的(在device tree代码分析文章中会给出更细致的描述)。对于普通的HW block的节点,例如interrupt-controller,
        //  compatible属性是用来匹配适合的driver的
        compatible = "samsung,s3c24xx"; -------------------(A)
        
        //
        //  (B)具体各个HW block的interrupt source是如何物理的连接到interruptcontroller的呢?在dts文件中是用interruptparent
        //  这个属性来标识的。且慢,这里定义interrupt-parent属性的是root node,难道root node会产生中断到interrupt
        //  controller吗?当然不会,只不过如果一个能够产生中断的device node没有定义interrupt-parent的话,其interrupt-parent
        //  属性就是跟随parent node。因此,与其在所有的下游设备中定义interrupt-parent,不如统一在root node中定义了。
        //  intc是一个lable,标识了一个device node(在本例中是标识了interrupt-controller@4a000000 这个device node)。实际
        //  上,interrupt-parent属性值应该是是一个u32的整数值(这个整数值在Device Tree的范围内唯一识别了一个device
        //  node,也就是phandle),不过,在dts文件中中,可以使用类似c语言的Labels and References机制。定义一个lable,
        //  唯一标识一个node或者property,后续可以使用&来引用这个lable。DTC会将lable转换成u32的整数值放入到DTB中,用
        //  户层面就不再关心具体转换的整数值了。
        interrupt-parent = <&intc>; ----------------------(B)
        
        //
        //(C)pinctrl0是一个缩写,他是/pinctrl@56000000的别名。这里同样也是使用了Labels and References机制。
        aliases {
            pinctrl0 = &pinctrl_0; ------------------------(C)
        };
        
        //
        //  (D)intc(node name是interrupt-controller@4a000000 ,我这里直接使用lable)是描述interrupt controller的device
        //  node。根据S3C24xx的datasheet,我们知道interrupt controller的寄存器地址从0x4a000000开始,长度为0x100(实际
        //  2451的interrupt的寄存器地址空间没有那么长,0x4a000074是最后一个寄存器),也就是reg属性定义的内容。
        //  interrupt-controller属性为空,只是用来标识该node是一个interrupt controller而不是interrupt nexus(interrupt nexus需
        //  要在不同的interrupt domains之间进行翻译,需要定义interrupt-map的属性,本文不涉及这部分的内容)。#interruptcells 
        //  和#address-cells概念是类似的,也就是说,用多少个u32来标识一个interrupt source。我们可以看到,在具体 HW
        block的interrupt定义中都是用了4个u32来表示,例如串口的中断是这样定义的:
                interrupts = <1 0 4 28>, <1 1 4 28>;
        intc:interrupt-controller@4a000000 { ------------------(D)
            compatible = "samsung,s3c2410-irq";
            reg = <0x4a000000 0x100>;
            interrupt-controller;
            #interrupt-cells = <4>;
        };
        
        //
        //  (E) 从reg属性可以serial controller寄存器地址从0x50000000 开始,长度为0x4000。对于一个能产生中断的设备,必
        //  须定义interrupts这个属性。也可以定义interrupt-parent这个属性,如果不定义,则继承其parent node的interrupt-parent
        //  属性。 对于interrupt属性值,各个interrupt controller定义是不一样的,有的用3个u32表示,有的用4个。具体上面的各
        //  个数字的解释权归相关的interrupt controller所有。对于中断属性的具体值的描述我们会在device tree的第三份文档-代
        //  码分析中描述。
        serial@50000000 { ----------------------(E)
            compatible = "samsung,s3c2410-uart";
            reg = <0x50000000 0x4000>;
            interrupts = <1 0 4 28>, <1 1 4 28>;
            status = "disabled";
        };
        
        //
        // (F)这个node是描述GPIO控制的。这个节点定义了一个wakeup-interrupt-controller 的子节点,用来描述有唤醒功能
        // 的中断源。
        pinctrl_0: pinctrl@56000000 {------------------(F)
            reg = <0x56000000 0x1000>;
            wakeup-interrupt-controller {
                compatible = "samsung,s3c2410-wakeup-eint";
                interrupts = <0 0 0 3>,
                <0 0 1 3>,
                <0 0 2 3>,
                <0 0 3 3>,
                <0 0 4 4>,
                <0 0 5 4>;
            };
        };
        ……
    }   
    
    #include "s3c24xx.dtsi"
    #include "s3c2416-pinctrl.dtsi"
    / {
        

        model = "Samsung S3C2416 SoC";
       
        //
        //  (A)在s3c24xx.dtsi文件中已经定义了compatible这个属性,在s3c2416.dtsi中重复定义了这个属性,一个node不可能
        //  有相同名字的属性,具体如何处理就交给DTC了。经过反编译,可以看出,DTC是丢弃掉了前一个定义。因此,到目前
        //  为止,compatible = samsung,s3c2416。在s3c24xx.dtsi文件中定义了compatible的属性值被覆盖了。
        compatible = "samsung,s3c2416"; ---------------A
        
        //
        //  (B)对于根节点,必须有一个cpus的child node来描述系统中的CPU信息。对于CPU的编址我们用一个u32整数就可以
        //  描述了,因此,对于cpus node,#address-cells 是 1,而#size-cells是0。其实CPU的node可以定义很多属性,例如
        //  TLB,cache、频率信息什么的,不过对于 ARM,这里只是定义了 compatible 属性就 OK 了,arm926ejs 包括了所有的
        //  processor 相关的信息。
        cpus { ----------------------------B
            #address-cells = <1>;
            #size-cells = <0>;
            cpu {
                compatible = "arm,arm926ejs";
            };
        };
        
        //
        // (C)s3c24xx.dtsi文件和s3c2416.dtsi中都有interrupt-controller@4a000000这个node,DTC会对这两个node进行合并
        interrupt-controller@4a000000 { -----------------C
            compatible = "samsung,s3c2416-irq";
        };
        ……
    };
    
    

/
// 【例:】machine 配置如下:
    1个双核 ARM Cortex-A9 32 位处理器;
    ARM 的 local bus 上的内存映射区域分布了:
        2 个串口(分别位于 0x101F1000 和 0x101F2000)、
        GPIO 控制器(位于 0x101F3000)、
        SPI 控制器(位于 0x10115000)、
        中断控制器(位于 0x10140000)和
        一个 external bus 桥;External bus 桥上又连接了: 
            SMC SMC91111 Ethernet(位于 0x10100000)、
            64MB NOR Flash(位于 0x30000000)、
            I2C 控制器(位于 0x10160000),I2C 总线上又连接了:
                Maxim DS1338 实时钟(I2C 地址为 0x58)

//
// 对应 .dts 文件对应如下:
	/{  
        /
        // 板级全局配置:
        //      【compatible = <manufacturer>,<model>】 
        //    
        //    内核通过 / 根结点的此属性,可以判断它启动的是哪种 machine                                   
        // 在 .dts 文件的每个设备,都有一个 compatible 属性,compatible                                       
        // 属性是一个字符串列表,列表中的第一个字符串表征了结点代表                                      
        // 的确切设备,形式为 <manufacturer>,<model>                                       
        // 其后的字符串表征了可兼容的其他设备,前面是特指,后面则是                                       
        // 涵盖更广的范围,如在 arch/arm/boot/dts/vexpress-v2m.dtsi 中的 Flash 结点:                             
        //      flash@0,00000000{                       
        //           compatible = "arm,vexpress-flash", "cfi-flash";                                     
        //           reg = <0 0x00000000 0x04000000>,                               
        //                  <1 0x00000000 0x04000000>;                              
        //           bank-width = <4>;                                   
        //       };                                     
        //    compatible 属性的第 2 个字符串 "cfi-flash" 明显比第1个字符串
        // "arm,vexpress-flash" 涵盖的范围更广。

		compatible = "acme,coyotes-revenge";        // 定义了系统的名称:<manufacturer>,<model>
        
        
        // 指定下辖结点 reg 的 address 为 1 length 为 1
		#address-cells = <1>;  
		#size-cells = <1>;  
        
        
        // 设备结点通过此行来指定所依附的中断控制器的 phandle? 地址指针?
        // 当结点没有指定 interrupt-parent 时,则从父级结点继承。对于本
        // 例而言:
        //     root 结点指定了 interrupt-parent = <&intc>,其对应于 
        //  intc: interrupt-controller@10140000, 而 root 结点的子结点 
        //  并未指定 interrupt-parent, 因此它们继承 intc, 即位于 0x10140000
        //  的中断控制器 
        // 
		interrupt-parent = <&intc>;  
	  
        /
        // cpus 结点:描述此 machine 上的 2 个 CPU
        // 结点的通用格式:
        //      【<name>[@<unit-address>]】
        //  
        //    name: ASCII 字符串,用于描述对应设备的类型 
        //    unit-address: 可选,一般是设备的地址,如寄存器地址,设备的地址也可在其对应的 reg 属性给出 
        //                  【注】:他的字节数,受父结点的 #addres-cells 影响 
        //  多个设备可以使用相同的 name, 但是他们的 unit-address 则必须不同,如 cpu@0 cpu@1 
        //  
        //
        //  reg 的组织形式: 
        //      【reg = <address1 length1 [address2 length2] [address3 lentgh3]】
        //
        //    [address length]: 每组表示了设备使用的一个地址范围
        //          address: 为 1 个或多个 32 位的整型(即 cell)
        //          length: 则为 cell 的列表或者为空(若 #size-cells = 0)
        //      address 和 length 的字段是可变长的,由【最近父节点属性】决定:
        //          #addres-cells : 决定了 reg 属性的 address 字段的长度  
        //          #size-cells   : 决定了 reg 属性的 length 字段的长度
        //      
        
		cpus{  
            
            // 指定下辖结点 reg 的 address 为 1 length 为 0
			#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 设备结点通过它指定中断号,触发方法等,具体这个属性含有多少个 cell,由它 
            // 依附的中断控制器结点 #interrupt-cells 属性决定,而具体的每个 cell 才是什么含义,一
            // 般【由驱动的实现决定】 ,而且也会在 Device Tree 的 binding 文档中说明 
			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 >;  
		};  
	  
        ///
        // 【中断控制器设置】
        // 如果一个能够产生中断的 device node 没有定义 interrupt-parent 的话,其 interrupt-parent
        // 属性就是跟随 parent node
		intc: interrupt-controller@10140000{  
			compatible = "arm,pl190";  
			reg = <0x10140000 0x1000 >;  
			interrupt-controller;             // 中断控制器加上此属性表明自己身份  
			#interrupt-cells = <2>;           // 与 #addres-cells 和 #size_cells 相似,表示连接此中断控制
                                              // 器的设备的 interrupts 属性的 cell 大小 
		};  
	  
		spi@10115000{  
			compatible = "arm,pl022";  
			reg = <0x10115000 0x1000 >;  
			interrupts = < 4 0 >;  
		};  
        
		external-bus{  
                
                // 指定下辖结点 reg 的 address 为 2 length 为 1
				#address-cells = <2>  
				#size-cells = <1>;  
                

                //
                //  root 结点的子结点描述的是 CPU 的视图,因此 root 子结点的 address 区域就直接 
                // 位于 CPU 的 memory 区域,但是经过总线桥后的 address 往往需要经过转换才能对应
                // 的 CPU 的 memory 映射,external-bus 的 rangs 属性定义了经过 external-bus 桥后 
                // 的地址范围如何映射到 CPU 的 memory 区域:
                //      【ranges = <子地址 address length> <父地址 address length>】
                //
                //  address、length 宽度取决:   
                //                  子地址:#addres-cells    父地址:#addres-cells
                // 本例中:
                //       子地址 external-bus 的 #addres-cells = 2 
                //       父地址 /  的 #addres-cells = 1
                //
                // 因此, ranges 前两列描述的是子地址空间地址,第三列为父地址空间地址 
                // 如第一行: 
                //   0 0  0x10100000   0x10000
                //     0 0:为 external-bus 上的片选 0 上的偏移 0 
                //     0x10100000: 父地址内存空间的地址 
                //     0x10000: 地址长度 
                // 这句意思就是:
                //    external-bus 片选 0 上的偏移 0,映射到 CPU 的 0x10100000 位置,映射大小 0x10000 
                // 后面含义依次类推。
				ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet  
                          1 0  0x10160000   0x10000     // Chipselect 2, i2c controller  
                          2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash  
		  
          
                /
                // 一列称为一个 cell
                //      cell 1: 第一个 cell(0,1,2) 是对应的片选  
                //      cell 2: 第二个 cell(0,0,0) 是相对该片选的基地址 
                //      cell 3: 第三个 cell(0x100, 0x100, 0x4000000) 为 length 
				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>;  
                    
					rtc@58 {  
						compatible = "maxim,ds1338";  
						reg = <58>;  
						interrupts = < 7 3 >;  
					};  
				};  
		  
				flash@2,0{  
					compatible = "samsung,k8f1315ebm", "cfi-flash";  
					reg = <2 0 0x4000000>;  
				};  
		};  
	};  

4. DTB 整体结构

    Device Tree Blob:
    
            ----------------------------|
            |        DTB header         |
            | (struct boot_param_header)|
            |---------------------------| alignment gap
            |---------------------------|
            |    memory reserve map     |
            |---------------------------|alignment gap
            |---------------------------|
            |   device-tree structure   |
            |---------------------------|alignment gap
            |---------------------------|
            |   device-tree strings     |
            |---------------------------|
   
    DTB header,其各个成员解释如下:
            header field name               description
            magic                           用来识别DTB的。通过这个magic,kernel可以确定bootloader传递的参数
                                            block是一个DTB还是tag list。
            totalsize                       DTB的total size
            off_dt_struct                   device tree structure block的offset
            off_dt_strings                  device tree strings block的offset
            off_mem_rsvmap                  offset to memory reserve map。有些系统,我们也许会保留一些memory有
                                            特殊用途(例如DTB或者initrd image),或者在有些DSP+ARM的SOC
                                            platform上,有写memory被保留用于ARM和DSP进行信息交互。这些保留
                                            内存不会进入内存管理系统。
            version                         该DTB的版本。
            last_comp_version               兼容版本信息
            boot_cpuid_phys                 我们在哪一个CPU(用ID标识)上booting
            dt_strings_size                 device tree strings block的size。和off_dt_strings一起确定了strings block
                                            在内存中的位置
            dt_struct_size                  device tree structure block的size。和和off_dt_struct一起确定了device tree
                                            structure block在内存中的位置
   memory reserve map 的格式描述:
            这个区域包括了若干的reserve memory描述符。每个reserve memory描述符是由address和size组成。其中address和
            size都是用U64来描述

   device tree structure block 的格式描述:
            device tree structure block 区域是由若干的分片组成,每个分片开始位置都是保存了 token,以此来描述该分片的属性和
            内容。共计有5种token:
                (1)FDT_BEGIN_NODE (0x00000001)。该token描述了一个node的开始位置,紧挨着该token的就是node name(包
                        括unit address)
                (2)FDT_END_NODE (0x00000002)。该token描述了一个node的结束位置。
                (3)FDT_PROP (0x00000003)。该token描述了一个property的开始位置,该token之后是两个u32的数据,分别是
                        length和name offset。length表示该property value data的size。name offset表示该属性字符串在device tree strings
                        block的偏移值。length和name offset之后就是长度为length具体的属性值数据。
                (4)FDT_NOP (0x00000004)。
                (5)FDT_END (0x00000009)。该token标识了一个DTB的结束位置。

           一个可能的DTB的结构如下:
            (1)若干个FDT_NOP(可选)
            (2)FDT_BEGIN_NODE
                node name
                paddings
            (3)若干属性定义。
            (4)若干子节点定义。(被FDT_BEGIN_NODE和FDT_END_NODE包围)
            (5)若干个FDT_NOP(可选)
            (6)FDT_END_NODE
            (7)FDT_END     
                
    device tree strings bloc 的格式描述:
            device tree strings bloc 定义了各个 node 中使用的属性的字符串表。由于很多属性会出现在多个 node 中,因此,所有的
            属性字符串组成了一个 string block。这样可以压缩 DTB 的 size。

5. dts 引起 BSP 和 driver 的变更

    
    // 【没有使用 dts 之前的 BSP 和 driver】
    /
       【引入 DTS 之前】:
            
            // 【平台设备】
            static struct platform_deivce usbcp_key_dev = {
                .name = "usbcopy_key",
                .id   = -1,
                .dev  = {
                    .release = usbcp_key_release,
                },
            };
            
            //【平台驱动】 
            static struct platform_driver usbcp_key_driver = {
                .driver = {
                    .name  = "usbcopy_key",
                    .owner = THIS_MODULE,
                },
                .probe  = usbcp_key_probe,
                .remove = usbcp_key_remove,
            };
        
       【引入 DTS 之后】:
            //
            // 【匹配结点信息】
            static const struct of_deivce_id usbcp_key_table[] = {
                {
                    .compatible = "Realtek,rtk-gpio-ctl-irq-mux"
                },
                {},
            };
            /
            // 【平台驱动】
            static struct platform_driver usbcp_key_driver = {
                .driver = {
                    .name = "usbcopy_key",
                    .of_match_table = usb_key_table,
                    .owner = THIS_MODULE,
                },
                .probe = usbcp_key_probe,
                .remove = usbcp_key_remove,
            }
            /
            // 【dts 配置】
            rtk_gpio_ctl_mlk{
                compatible = "Realtek,rtk-gpio-ctl-irq-mux";
                gpios = <&rtk_iso_gpio 8 0 1>;
            };
        //
        // 针对上面 dts 的解释:
            1. rtk_gpio_ctl_mlk 这个是 node 的名字,自己可以随便定义,当然最好是见名知意,可以通过
                驱动程序打印当前使用的设备树节点 :
                    printk("now dts node name is %s\n",pdev->dev.of_node->name);
            2. compatible 选项是用来和驱动程序中的 of_match_table 指针所指向的 of_deivce_id 结构里的
                compatible 字段来匹配的,只有 dts 里的 compatible 字段的名字和驱动程序中 of_deivce_id 
                里的 compatible 字段的名字一样,驱动程序才能进入 probe 函数 
            3. 对于  gpios 这个字段:
                  首先 &rtk_iso_gpio 指明了这个 gpio 是连接到 rtk_iso_gpio,
                  接着那个 8 是 gpio_number 偏移量,它是以 rtk_iso_gpio base 为基准的
                  紧接着的那个 0 说明目前配置的 gpio number 是设置成输入 input
                        如果是 1 就是设置成输出 output
                  最后一个字段 1 是指定这个 gpio 默认为高电平
                        如果是 0,则是指定这个 gpio 默认为低电平
            4. 如果驱动里面只是利用 compatible 字段进行匹配进入 probe 函数,那么 gpios 可以不需要,但是 
                如果驱动程序里面是采用设备树相关的方法进行操作获取 gpio number, 那么 gpios 这个字段必须
                使用。
                    gpios 这个字段是由 of_get_gpio_flags() 函数默认指定的 name.
                获取 gpio number 的函数如下:
                    of_get_named_gpio_flags() 
                    of_get_gpio_flags() 
                    
    /
    // 【注册 i2c_board_info】: 指定 IRQ 等板级信息 
    /
       【引入 DTS 之前】:
            static struct i2c_board_info __initdata afeb9260_i2c_devices[] = {
                {    
                    I2C_BOARD_INFO("tlv320aic23",0x1a),
                },
                {
                    I2C_BOARD_INFO("fm3130",0x68),
                },
                {
                    I2C_BOARD_INFO("24c64",0x68),
                }
            };
       【引入 DTS 之后】:     
            之类的 i2c_board_info() 代码,目前不再需要出现,现在只需要把 tlv320aic23、fm3130、24c64 这些 
            设备结点填充作为相应的 I2C controller 结点的子结点即可,类似于前面的:
                i2c@1,0{
                    compatible = "acme,a1234-i2c-bus";
                    ...
                    rtc@58{
                        compatible = "wlf,wm8753";
                        reg = <58>;
                        interrupt = <7 3>;
                    };
                };
            
            Device Tree 中的 I2C client 会通过 I2C host 驱动的 probe() 函数中调用 of_i2c_register_devices(&i2c_dev->adapter)
            被自动展开。
            驱动中使用: 
                static const struct of_device_id wm8753_of_match[] = {  
                        { .compatible = "wlf,wm8753", },  
                        { }  
                };  
                MODULE_DEVICE_TABLE(of, wm8753_of_match);  
                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,  
                };                  
    /
    // 【注册 spi_board_info】: 指定 IRQ 等板级信息 
    /
        【引入 DTS 之前】:
            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,  
                    },  
            };  
            
  
        【引入 DTS 之后】:
            之类的 spi_board_info 代码,目前不再需要出现,与 I2C 类似,现在只需要把 mtd_dataflash 之类的结
            点,作为 SPI 控制器的子结点即可,SPI host 驱动的 probe 函数透过 spi_register_master() 注册
            master 的时候,会自动展开依附于它的 slave。      
                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,  
                };  

        //
        // 关于【I2C/SPI 别名问题】:
         I2C 和 SPI 外设驱动和 Device Tree 中设备结点的 compatible 属性还有一种弱式匹配方法,就是别名
         匹配。compatible 属性的组织形式为 <manufacturer>,<model>,别名其实就是去掉 compatible 属性中
         逗号前的 manufacturer 前缀。关于这一点,可查看 drivers/spi/spi.c 的源代码,函数 
         spi_match_device() 暴露了更多的细节,如果别名出现在设备 spi_driver 的 id_table 里面,或者别
         名与 spi_driver 的 name 字段相同,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;  
            }               
    ///
    // 【 多个针对不同电路板的 machine,以及相关的 callback 】 
    /
       【引入 DTS 之前】:
            过去,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              
            
            这些不同的 machine 会有不同的 MACHINE ID,Uboot 在启动 Linux 内核时会将 MACHINE ID 存放在 r1
        寄存器,Linux 启动时会匹配 Bootloader 传递的 MACHINE ID 和 MACHINE_START 声明的 MACHINE ID,然后
        执行相应 machine 的一系列初始化函数。
        
      【引入 DTS 之后】:
            引入 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 的一系列初始化函数被执行。
                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,              // 【倡导多个 Soc、电路板共用】
                        .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 属性字符串。之后,如果的电路板的初始化序列不一样,可以透过
            int of_machine_is_compatible(const char *compat) 
        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",               // 针对三星 exynos5250 板子
                        "samsung,exynos5440",               // 针对三星 exynos5440 板子 
                        NULL  
                };  
                 
                DT_MACHINE_START(EXYNOS5_DT, "SAMSUNG EXYNOS5 (Flattened Device Tree)")  
                        // Maintainer: Kukjin Kim <[email protected]>  
                        .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);  
                }          

6. 常见的 DTS 函数

    
    Linux 内核中目前 DTS 相关的函数都是以 of_ 前缀开头的,它们的实现位于内核源码的 drivers/of 下面
     
        void __iomem *of_iomap(struct device_node *node,int index) 
      
      通过设备结点直接进行设备内存区间的 ioremap() index 是内存段的索引,若设备结点的 reg 属性有多段,
    可通过 index 标示要 ioremap 的是哪一段,只有 1 段的情况下,index 为 0。采用 Device Tree 后,大量的
    设备驱动通过 of_iomap() 进行映射,而不再通过传统的 ioremap。
    
        int of_get_named_gpio_flags(struct device_node *np, const char *propname, int index,enum_of_gpio_flags *flag)
        
        static inline int of_get_gpio_flags(struct device_node *np, int index, enum of_gpio_flags *flags)
        {
            return of_get_named_gpio_flags(np, "gpios", index, flags);
        }
        
    从设备树中读取相关 GPIO 的配置编号和标志,返回值为 gpio number 
    /
    在 Linux 的 BSP 和驱动代码中,还经常会使用到 Linux 中一组 Device Tree 的 API,这些 API 通常被冠以 
    of_ 前缀,它们的实现代码位于内核的 drivers/of 目录。这些常用的API包括:
    
    int of_device_is_compatible(const struct device_node *device,const char *compat);
        
        判断设备结点的 compatible 属性是否包含 compat 指定的字符串。当一个驱动支持 2 个或多个设备的时候,
        这些不同 .dts 文件中设备的 compatible 属性都会进入驱动 OF 匹配表。因此驱动可以透过 Bootloader 
        传递给内核的 Device Tree 中的真正结点的 compatible 属性以确定究竟是哪一种设备,从而根据不同的
        设备类型进行不同的处理。如 drivers/pinctrl/pinctrl-sirf.c 即兼容于 "sirf,prima2-pinctrl",又
        兼容于 "sirf,prima2-pinctrl",在驱动中就有相应分支处理:
        if (of_device_is_compatible(np, "sirf,marco-pinctrl"))is_marco = 1; 
        
    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()。如在 arch/arm/mm/cache-l2x0.c 中,透过如下语
        句读取 L2 cache 的 "arm,data-latency" 属性:
            of_property_read_u32_array(np, "arm,data-latency", data, ARRAY_SIZE(data));
            
        在arch/arm/boot/dts/vexpress-v2p-ca9.dts中,含有"arm,data-latency"属性的L2 cache结点如下:
            L2: cache-controller@1e00a000 {  
                    compatible = "arm,pl310-cache";  
                    reg = <0x1e00a000 0x1000>;  
                    interrupts = <0 43 4>;  
                    cache-level = <2>;  
                    arm,data-latency = <1 1 1>;  
                    arm,tag-latency = <1 1 1>;  
            }  
        
        有些情况下,整形属性的长度可能为 1,于是内核为了方便调用者,又在上述 API 的基础上封装出了更
        加简单的读单一整形属性的 API,它们为 int of_property_read_u8()、of_property_read_u16() 等,
        实现于 include/linux/of.h:
            static inline int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value)
            static inline int of_property_read_u16(const struct device_node *np,const char *propname,u16 *out_value)
            static inline int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value)
            
    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 个字符串。如 drivers/clk/clk.c 中的
        of_clk_get_parent_name()透过 of_property_read_string_index() 遍历 clkspec 结点的所有 
        "clock-output-names" 字符串数组属性。
            const char *of_clk_get_parent_name(struct device_node *np, int index)  
            {  
                    struct of_phandle_args clkspec;  
                    const char *clk_name;  
                    int rc;  
             
                    if (index < 0)  
                            return NULL;  
             
                    rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,  
                                                    &clkspec);  
                    if (rc)  
                            return NULL;  
             
                    if (of_property_read_string_index(clkspec.np, "clock-output-names",  
                                              clkspec.args_count ? clkspec.args[0] : 0,  
                                                      &clk_name) < 0)  
                            clk_name = clkspec.np->name;  
             
                    of_node_put(clkspec.np);  
                    return clk_name;  
            }  
            EXPORT_SYMBOL_GPL(of_clk_get_parent_name);  

    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。采用 Device Tree
        后,大量的设备驱动通过 of_iomap() 进行映射,而不再通过传统的 ioremap。
    
    unsigned int irq_of_parse_and_map(struct device_node *dev, int index);
        透过 Device Tree 或者设备的中断号,实际上是从 .dts 中的 interrupts 属性解析出中断号。若设备
        使用了多个中断,index 指定中断的索引号。
    
    /
   【还有一些 OF API,这里不一一列举,具体可参考include/linux/of.h头文件】
    /

7. DTC(device tree complier)

       将 .dts 编译为 .dtb 的工具。DTC 的源代码位于内核的 scripts/dtc 目录,在 Linux 内核使能了 
    Device Tree 的情况下,编译内核的时候主机工具 dtc 会被编译出来,对应 scripts/dtc/Makefile 中
    的 “hostprogs-y := dtc” 这一 hostprogs 编译 target。
      在 Linux 内核的 arch/arm/boot/dts/Makefile 中,描述了当某种 SoC 被选中后,哪些 .dtb 文件会
    被编译出来,如与VEXPRESS对应的.dtb包括:
        dtb-$(CONFIG_ARCH_VEXPRESS) += vexpress-v2p-ca5s.dtb \  
              vexpress-v2p-ca9.dtb \  
              vexpress-v2p-ca15-tc1.dtb \  
              vexpress-v2p-ca15_a7.dtb \  
              xenvm-4.2.dtb  
              
      在 Linux 下,我们可以单独编译 Device Tree 文件。当我们在 Linux 内核下运行 make dtbs 时,若
    我们之前选择了 ARCH_VEXPRESS,上述 .dtb 都会由对应的 .dts 编译出来。因为 arch/arm/Makefile 中
    含有一个 dtbs 编译 target 项目。
    
    反编译 dtb 文件:
        编译生成 dtb 位置: 
            alps\out\target\product\$(proj)\obj\KERNEL_OBJ\arch\arm(xx)\boot\dts\$(proj).dtb
        反编译工具位置:
            alps\out\target\product\$(proj)\obj\KERNEL_OBJ\scripts\dtc\dtc
        反编译命令行:
            ./dtc –I dtb –O dts –o k35v1_64_op01.dts k35v1_64_op01.dtb

8. DTS 的用户接口位置:

    位置:proc/device-tree/
        # hexdump -C '/proc/device-tree/#size-cells'
        00000000 00 00 00 01 |....|
        00000004
        # hexdump -C '/proc/device-tree/axi@0/compatible'
        00000000 78 6c 6e 78 2c 70 73 37 2d 61 78 69 2d 69 6e 74 |xlnx,ps7-axi-int|
        00000010 65 72 63 6f 6e 6e 65 63 74 2d 31 2e 30 30 2e 61 |erconnect-1.00.a|
        00000020 00 73 69 6d 70 6c 65 2d 62 75 73 00 |.simple-bus.|
        0000002c

        或直接:
        # cat '/proc/device-tree/axi@0/compatible'
        xlnx,ps7-axi-interconnect-1.00.asimple-bus

基于 MTK 平台分析

加载流程

【初始化段数组的添加】:
	// Core.c (kernel-3.18\arch\arm\mach-mediatek)
    static const char *mt6755_dt_match[] __initconst = {
        "mediatek,MT6755",
        NULL
    };
    DT_MACHINE_START(MT6755_DT, "MT6755")
        .dt_compat	= mt6755_dt_match,
    MACHINE_END
            /
            // Arch.h (z:\work\e266l_cmcc\kernel-3.18\arch\arm\include\asm\mach)
            // 宏定义:放入 arch.info.init 段中
            #define DT_MACHINE_START(_name, _namestr)		\
            static const struct machine_desc __mach_desc_##_name	\
             __used							\
             __attribute__((__section__(".arch.info.init"))) = {	\
                .nr		= ~0,				\
                .name		= _namestr,

                /
                // vmlinux.lds.S (z:\work\e266l_cmcc\kernel-3.18\arch\arm\kernel)
                .init.arch.info : {
                    __arch_info_begin = .;          // 段开始
                    *(.arch.info.init)
                    __arch_info_end = .;            // 段结束 
                }
                    
                    // C 中引用:Arch.h (z:\work\e266l_cmcc\kernel-3.18\arch\arm\include\asm\mach)
                    extern const struct machine_desc __arch_info_begin[], __arch_info_end[];
 

【初始化数组的调用】: 
///
// Main.c (z:\work\e266l_cmcc\kernel-3.18\init)
start_kernel(void)
    setup_arch(&command_line);
            ///
            //Setup.c (z:\work\e266l_cmcc\kernel-3.18\arch\arm\kernel)
            setup_arch(char **cmdline_p)
                setup_processor();
                //
                // 获得设备树
                mdesc = setup_machine_fdt(__atags_pointer);
                            if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys)))
                                            early_init_dt_scan(void *params)
                                                status = early_init_dt_verify(params);
                                                            // Setup flat device-tree pointer
                                                            initial_boot_params = params;
                                                early_init_dt_scan_nodes();
                                                    // Retrieve various information from the /chosen node
                                                    of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
                                                    // Initialize {size,address}-cells info 
                                                    of_scan_flat_dt(early_init_dt_scan_root, NULL);
                                                    // Setup memory, calling early_init_dt_add_memory_arch
                                                    of_scan_flat_dt(early_init_dt_scan_memory, NULL);
                /
                // 根据 nr 匹配平台架构
                if (!mdesc)
                    mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
                            
                            //Atags_parse.c (z:\work\e266l_cmcc\kernel-3.18\arch\arm\kernel)
                            setup_machine_tags(phys_addr_t __atags_pointer, unsigned int machine_nr)
                                for_each_machine_desc(p)
                                    if (machine_nr == p->nr)
                                        mdesc = p;
                                        /
                                        // Arch.h (z:\work\e266l_cmcc\kernel-3.18\arch\arm\include\asm\mach)
                                        #define for_each_machine_desc(p)			\
                                            for (p = __arch_info_begin; p < __arch_info_end; p++)
                
                ///
                // 获得设备树中的平台名称
                machine_name = of_flat_dt_get_machine_name();  
                            unsigned long dt_root = of_get_flat_dt_root();
                            name = of_get_flat_dt_prop(dt_root, "model", NULL);
                            name = of_get_flat_dt_prop(dt_root, "compatible", NULL);
                            
                
                // 解析设备树:
                unflatten_device_tree();
                        __unflatten_device_tree(initial_boot_params, &of_allnodes,early_init_dt_alloc_memory_arch);
                        
                        // 获得 dts 中的 /chosen 节点和 /aliases 别名节点
                        of_alias_scan(early_init_dt_alloc_memory_arch);
                        
                arm_dt_init_cpu_maps();
                psci_init();
                if (mdesc->init_early)
                    mdesc->init_early();
                                    
    init_IRQ(); 
            /
            // 初始化中断控制器
            //Irq.c (z:\work\e266l_cmcc\kernel-3.18\arch\arm64\kernel)
            init_IRQ(void)
                irqchip_init();
                        
                        // Irqchip.c (z:\work\e266l_cmcc\kernel-3.18\drivers\irqchip)
                        irqchip_init(void)
                            of_irq_init(__irqchip_of_table);
                                    /
                                    // Irq.c (z:\work\e266l_cmcc\kernel-3.18\drivers\of)
                                    of_irq_init(const struct of_device_id *matches)
                                        of_find_property(np, "interrupt-controller", NULL)
                                        ///
                                        // 初始化函数指针,并调用函数指针进行初始化
                                        irq_init_cb = (of_irq_init_cb_t)match->data;
                                        ret = irq_init_cb(desc->dev, desc->interrupt_parent);

【非常好】引脚 Dts 相关 clock 时钟示例

/{    
    // mt6755.dtsi 
    clocks {
		clk_null: clk_null {
			compatible = "fixed-clock";
			#clock-cells = <0>;
			clock-frequency = <0>;
		};

		clk26m: clk26m {
			compatible = "fixed-clock";
			#clock-cells = <0>;
			clock-frequency = <26000000>;
		};

		clk32k: clk32k {
			compatible = "fixed-clock";
			#clock-cells = <0>;
			clock-frequency = <32000>;
		};
	};
    
    #######################################################
    # dts 引用 clk 
    /* Do not put any bus before mtk-msdc, because it should be mtk-msdc.0 for partition device node usage */
	mtk-msdc.0 {
		compatible = "simple-bus";
		#address-cells = <1>;
		#size-cells = <1>;
		ranges = <0 0 0 0xffffffff>;

		mmc0: msdc0@11230000 {
			compatible = "mediatek,mt6755-mmc";
			reg = <0x11230000 0x10000>;
			interrupts = <GIC_SPI 79 IRQ_TYPE_LEVEL_LOW>;
			clocks = <&infrasys INFRA_MSDC0>;
			clock-names = "MSDC0-CLOCK";
		};

		mmc1: msdc1@11240000 {
			compatible = "mediatek,mt6755-mmc";
			reg = <0x11240000 0x10000>;
			interrupts = <GIC_SPI 80 IRQ_TYPE_LEVEL_LOW>;
			clocks = <&infrasys INFRA_MSDC1>;
			clock-names = "MSDC1-CLOCK";
		};

		mmc2: msdc2@11250000 {
			compatible = "mediatek,mt6755-mmc";
			reg = <0x11250000 0x10000>;
			interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_LOW>;
			clocks = <&infrasys INFRA_MSDC2>;
			clock-names = "MSDC2-CLOCK";
		};

		/*
		mmc3: msdc3@11260000 {
			compatible = "mediatek,mt6755-mmc";
			reg = <0x11260000 0x10000>;
			interrupts = <GIC_SPI 82 IRQ_TYPE_LEVEL_LOW>;
			clocks = <&infrasys INFRA_MSDC3>;
			clock-names = "MSDC3-CLOCK";
		};*/
	};
    
    
}

#######################################################
# 代码中使用位置
    //  #define MSDC0_CLK_NAME          "MSDC0-CLOCK"
    //  #define MSDC1_CLK_NAME          "MSDC1-CLOCK"
    //  #define MSDC2_CLK_NAME          "MSDC2-CLOCK"
    //  #define MSDC3_CLK_NAME          "MSDC3-CLOCK"
    static char const * const clk_names[] = {
        MSDC0_CLK_NAME, MSDC1_CLK_NAME, MSDC2_CLK_NAME, MSDC3_CLK_NAME
    };
    host->clock_control = devm_clk_get(&pdev->dev, clk_names[pdev->id]);
    // 进行时钟准备 
    clk_prepare(host->clock_control)   
	clk_enable(clk);
    clk_unprepare(host->clock_control);

【非常好】dts 配置使用 LDO

	///
    // 获得 dts 配置 
         Mt6353.dtsi (kernel-3.18\arch\arm64\boot\dts)
          / {
              mt_pmic_regulator {
                  compatible = "mediatek,mt_pmic";
                  buck_regulators {
                      compatible = "mediatek,mt_pmic_buck_regulators";
                      mt_pmic_vproc_buck_reg: buck_vproc {
                          regulator-name = "vproc";
                          regulator-min-microvolt = <600000>;
                          regulator-max-microvolt = <1393750>;
                          regulator-ramp-delay = <6250>;
                          regulator-enable-ramp-delay = <30>;
                          regulator-always-on;
                          regulator-boot-on;
                      };
                      。。。
                  }
                  ldo_regulators {
                      compatible = "mediatek,mt_pmic_ldo_regulators";
                      mt_pmic_vtcxo24_ldo_reg: ldo_vtcxo24 {
                          regulator-name = "vtcxo24";
                          regulator-min-microvolt = <1800000>;
                          regulator-max-microvolt = <2800000>;
                          regulator-enable-ramp-delay = <100>;
                          regulator-boot-on;
                      };
                      。。。
                      mt_pmic_vldo28_ldo_reg: ldo_vldo28 {
                          regulator-name = "vldo28";              // vldo28 的供电能力,在 PMIC 上,所以不需要配置引脚
                          regulator-min-microvolt = <1200000>;
                          regulator-max-microvolt = <3300000>;
                          regulator-enable-ramp-delay = <240>;
                      };
                  }
                  regulators_supply {
                      compatible = "mediatek,mt_pmic_regulator_supply";
                      vtcxo24-supply = <&mt_pmic_vtcxo24_ldo_reg>;
                      vtcxo28-supply = <&mt_pmic_vtcxo28_ldo_reg>;
                      vsim1-supply = <&mt_pmic_vsim1_ldo_reg>;
                      vsim2-supply = <&mt_pmic_vsim2_ldo_reg>;
                      vemc-supply = <&mt_pmic_vemc_ldo_reg>;
                      vmch-supply = <&mt_pmic_vmch_ldo_reg>;
                      vmc-supply = <&mt_pmic_vmc_ldo_reg>;
                      vio28-supply = <&mt_pmic_vio28_ldo_reg>;
                      vibr-supply = <&mt_pmic_vibr_ldo_reg>;
                      vrf18-supply = <&mt_pmic_vrf18_ldo_reg>;
                      vio18-supply = <&mt_pmic_vio18_ldo_reg>;
                      vsram_proc-supply = <&mt_pmic_vsram_proc_ldo_reg>;
                      vxo22-supply = <&mt_pmic_vxo22_ldo_reg>;
                      vrf12-supply = <&mt_pmic_vrf12_ldo_reg>;
                      vdram-supply = <&mt_pmic_vdram_ldo_reg>;
                  };
              }
          }

    ##############################################################
    # 硬件相关配置
        struct mtk_regulator mtk_ldos[] = {
            PMIC_LDO_GEN1(va18, PMIC_RG_VA18_EN, PMIC_RG_VA18_VOSEL,
            mt6351_VA18_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vtcxo24, PMIC_RG_VTCXO24_EN, PMIC_RG_VTCXO24_VOSEL,
            mt6351_VA18_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vtcxo28, PMIC_RG_VTCXO28_EN,
            PMIC_RG_VTCXO28_VOSEL, mt6351_VA18_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vcn28, PMIC_RG_VCN28_EN, PMIC_RG_VCN28_VOSEL,
            mt6351_VA18_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vcama, PMIC_RG_VCAMA_EN, PMIC_RG_VCAMA_VOSEL,
                      mt6351_VCAMA_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vusb33, PMIC_RG_VUSB33_EN, NULL, mt6351_3v3_voltages, 1, PMIC_EN),
            PMIC_LDO_GEN1(vsim1, PMIC_RG_VSIM1_EN, PMIC_RG_VSIM1_VOSEL,
                      mt6351_VSIM1_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vsim2, PMIC_RG_VSIM2_EN, PMIC_RG_VSIM2_VOSEL,
                      mt6351_VSIM1_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vemc, PMIC_RG_VEMC_EN, PMIC_RG_VEMC_VOSEL,
                      mt6351_VEMC33_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vmch, PMIC_RG_VMCH_EN, PMIC_RG_VMCH_VOSEL,
                      mt6351_VMCH_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vio28, PMIC_RG_VIO28_EN, NULL, mt6351_2v8_voltages, 1, PMIC_EN),
            PMIC_LDO_GEN1(vibr, PMIC_RG_VIBR_EN, PMIC_RG_VIBR_VOSEL,
                      mt6351_VCAM_AF_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vcamd, PMIC_RG_VCAMD_EN, PMIC_RG_VCAMD_VOSEL,
                      mt6351_VCAMD_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vrf18, PMIC_RG_VRF18_EN, PMIC_RG_VRF18_VOSEL,
            mt6351_rf18_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vio18, PMIC_RG_VIO18_EN, NULL, mt6351_1v8_voltages, 1, PMIC_EN),
            PMIC_LDO_GEN1(vcn18, PMIC_RG_VCN18_EN, PMIC_RG_VCN18_VOSEL,
            mt6351_VCAM_IO_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vcamio, PMIC_RG_VCAMIO_EN, PMIC_RG_VCAMIO_VOSEL,
                      mt6351_VCAM_IO_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vsram_proc, PMIC_RG_VSRAM_PROC_EN, PMIC_BUCK_VSRAM_PROC_VOSEL,
                      mt6351_2v8_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vxo22, PMIC_RG_VXO22_EN, PMIC_RG_VXO22_VOSEL,
                      mt6351_VA18_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vrf12, PMIC_RG_VRF12_EN, PMIC_RG_VRF12_VOSEL,
                      mt6351_VCAM_IO_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(va10, PMIC_RG_VA10_EN, PMIC_RG_VA10_VOSEL,
                      mt6351_VA10_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vdram, PMIC_RG_VDRAM_EN, PMIC_RG_VDRAM_VOSEL,
                      mt6351_VCAMD_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vmipi, PMIC_RG_VMIPI_EN, PMIC_RG_VMIPI_VOSEL,
                      mt6351_VA10_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vgp3, PMIC_RG_VGP3_EN, PMIC_RG_VGP3_VOSEL,
                      mt6351_VGP3_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vbif28, PMIC_RG_VBIF28_EN, PMIC_RG_VBIF28_VOSEL,
                      mt6351_VA18_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vefuse, PMIC_RG_VEFUSE_EN, PMIC_RG_VEFUSE_VOSEL,
                      mt6351_VEFUSE_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vcn33_bt, PMIC_RG_VCN33_EN_BT, PMIC_RG_VCN33_VOSEL,
                      mt6351_VCN33_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vcn33_wifi, PMIC_RG_VCN33_EN_WIFI, PMIC_RG_VCN33_VOSEL,
                      mt6351_VCN33_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vldo28, PMIC_RG_VLDO28_EN_0, NULL, mt6351_2v8_voltages, 1, PMIC_EN),
            PMIC_LDO_GEN1(vmc, PMIC_RG_VMC_EN, PMIC_RG_VMC_VOSEL,
            mt6351_VMC_voltages, 1, PMIC_EN_VOL),
            PMIC_LDO_GEN1(vldo28_0, PMIC_RG_VLDO28_EN_0, NULL, mt6351_2v8_voltages, 1, PMIC_EN),
            PMIC_LDO_GEN1(vldo28_1, PMIC_RG_VLDO28_EN_1, NULL, mt6351_2v8_voltages, 1, PMIC_EN),
        };

    ########################################################################
    # 进行匹配的地方
    ########################################################################
    # 匹配的地方:
    #       matched = of_regulator_match(&pdev->dev, regulators,
	#       			     pmic_regulator_matches,
	#       			     pmic_regulator_matches_size);
    #                 
    static struct of_regulator_match pmic_regulator_matches[] = {
        PMIC_REGULATOR_OF_MATCH(ldo_va18, VA18),
        PMIC_REGULATOR_OF_MATCH(ldo_vtcxo24, VTCXO24),
        PMIC_REGULATOR_OF_MATCH(ldo_vtcxo28, VTCXO28),
        PMIC_REGULATOR_OF_MATCH(ldo_vcn28, VCN28),
        PMIC_REGULATOR_OF_MATCH(ldo_vcama, VCAMA),
        PMIC_REGULATOR_OF_MATCH(ldo_vusb33, VUSB33),
        PMIC_REGULATOR_OF_MATCH(ldo_vsim1, VSIM1),
        PMIC_REGULATOR_OF_MATCH(ldo_vsim2, VSIM2),
        PMIC_REGULATOR_OF_MATCH(ldo_vemc, VEMC),
        PMIC_REGULATOR_OF_MATCH(ldo_vmch, VMCH),
        PMIC_REGULATOR_OF_MATCH(ldo_vio28, VIO28),
        PMIC_REGULATOR_OF_MATCH(ldo_vibr, VIBR),
        PMIC_REGULATOR_OF_MATCH(ldo_vcamd, VCAMD),
        PMIC_REGULATOR_OF_MATCH(ldo_vrf18, VRF18),
        PMIC_REGULATOR_OF_MATCH(ldo_vio18, VIO18),
        PMIC_REGULATOR_OF_MATCH(ldo_vcn18, VCN18),
        PMIC_REGULATOR_OF_MATCH(ldo_vcamio, VCAMIO),
        PMIC_REGULATOR_OF_MATCH(ldo_vsram_proc, VSRAM_PROC),
        PMIC_REGULATOR_OF_MATCH(ldo_vxo22, VXO22),
        PMIC_REGULATOR_OF_MATCH(ldo_vrf12, VRF12),
        PMIC_REGULATOR_OF_MATCH(ldo_va10, VA10),
        PMIC_REGULATOR_OF_MATCH(ldo_vdram, VDRAM),
        PMIC_REGULATOR_OF_MATCH(ldo_vmipi, VMIPI),
        PMIC_REGULATOR_OF_MATCH(ldo_vgp3, VGP3),
        PMIC_REGULATOR_OF_MATCH(ldo_vbif28, VBIF28),
        PMIC_REGULATOR_OF_MATCH(ldo_vefuse, VEFUSE),
        PMIC_REGULATOR_OF_MATCH(ldo_vcn33_bt, VCN33_BT),
        PMIC_REGULATOR_OF_MATCH(ldo_vcn33_wifi, VCN33_WIFI),
        PMIC_REGULATOR_OF_MATCH(ldo_vldo28, VLDO28),
        PMIC_REGULATOR_OF_MATCH(ldo_vmc, VMC),
        PMIC_REGULATOR_OF_MATCH(ldo_vldo28_0, VLDO28),
        PMIC_REGULATOR_OF_MATCH(ldo_vldo28_1, VLDO28),
    };

##########################################################################
# 使用 dts 配置:
        &touch {
            vtouch-supply = <&mt_pmic_vldo28_ldo_reg>;
            status = "okay";
        };

##########################################################################
# 代码中使用:
    // 注册平台设备,然后获得供电引脚 
    tpd->reg = regulator_get(tpd->tpd_dev, "vtouch");

驱动获取 dts 配置的 reg 地址映射

########################################################
# 相关 dts 配置     
    apuart0: apuart0@11002000 {
        cell-index = <0>;
        compatible = "mediatek,mt6755-uart";
        reg = <0x11002000 0x1000>, /* UART base */
              <0x11000380 0x1000>, /* DMA Tx base */
              <0x11000400 0x80>; /* DMA Rx base */
        interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_LOW>, /* UART IRQ */
                     <GIC_SPI 103 IRQ_TYPE_LEVEL_LOW>, /* DMA Tx IRQ */
                     <GIC_SPI 104 IRQ_TYPE_LEVEL_LOW>; /* DMA Rx IRQ */

        clock-frequency = <26000000>;
        clock-div = <1>;
        clocks = <&infrasys INFRA_UART0>, <&infrasys INFRA_AP_DMA>;
        clock-names = "uart0-main", "uart-apdma";
    };
    
#######################################################
# 代码中使用:
    node = of_find_node_by_name(NULL, "apuart0");
	base = of_iomap(node, 1);

【非常好】驱动引用 dts 的 GPIO 引脚配置 pinctrl

	// 相关 dtsi 添加:

	lcmbias: lcmbias {
		compatible = "mediatek,lcmbias";
	};
    &lcmbias {
        pinctrl-names = "default", "lcd_bias_enp0_gpio", "lcd_bias_enp1_gpio";
        pinctrl-0 = <&lcmbias_pins_default>;
        pinctrl-1 = <&lcmbias_pins_lcd_bias_enp0>;
        pinctrl-2 = <&lcmbias_pins_lcd_bias_enp1>;
        status = "okay";
    };
    &pio {

        lcmbias_pins_default: default {
        };

        lcmbias_pins_lcd_bias_enp0: lcd_bias_enp0_gpio {
            pins_cmd_dat {
                pins = <PINMUX_GPIO12__FUNC_GPIO12>;
                slew-rate = <1>;
                output-low;
            };
        };

        lcmbias_pins_lcd_bias_enp1: lcd_bias_enp1_gpio {
            pins_cmd_dat {
                pins = <PINMUX_GPIO12__FUNC_GPIO12>;
                slew-rate = <1>;
                output-high;
            };
        };

    };


########################################################
// 相关驱动中使用:
########################################################
    static struct platform_device * pltfm_dev ;
    struct pinctrl *lcmbiasctrl = NULL;
    struct pinctrl_state *lcmbias_enable= NULL;
    struct pinctrl_state *lcmbias_disable= NULL;

    static int lcmbias_probe(struct platform_device *dev)
    {
        pr_debug("[lcm]lcmbias_probe begin!\n");
        pltfm_dev = dev;
        
        lcmbiasctrl = devm_pinctrl_get(&pltfm_dev->dev);
        
        if (IS_ERR(lcmbiasctrl)) {
            dev_err(&pltfm_dev->dev, "Cannot find  lcmbias pinctrl!");
        }

        lcmbias_enable = pinctrl_lookup_state(lcmbiasctrl, "lcd_bias_enp1_gpio");
        if (IS_ERR(lcmbias_enable)) {
            pr_debug("%s : pinctrl err, lcmbias_enable\n", __func__);
        }

        lcmbias_disable = pinctrl_lookup_state(lcmbiasctrl, "lcd_bias_enp0_gpio");
        if (IS_ERR(lcmbias_disable)) {
            pr_debug("%s : pinctrl err, lcmbias_disable\n", __func__);
        }
        pr_debug("[lcm]lcmbias_probe done!\n");
        return 0;
    }
    
    static int lcmbias_remove(struct platform_device *dev)
    {

        return 0;
    }

    struct of_device_id lcmbias_of_match[] = {
        { .compatible = "mediatek,lcmbias", },
        {},
    };

    static struct platform_driver lcmbias_driver = {
        .probe = lcmbias_probe,
        .remove = lcmbias_remove,
        .driver = {
                .name = "lcmbias_drv",
                .of_match_table = lcmbias_of_match,
               },
    };
    
    static int lcmbias_mod_init(void)
    {
        int ret = 0;
        pr_debug("[lcmbias]lcmbias_mod_init begin!\n");
        ret = platform_driver_register(&lcmbias_driver);
        if (ret)
            pr_debug("[lcmbias]platform_driver_register error:(%d)\n", ret);
        else
            pr_debug("[lcmbias]platform_driver_register done!\n");

            pr_debug("[lcmbias]lcmbias_mod_init done!\n");
        return ret;

    }

    static void lcmbias_mod_exit(void)
    {
        pr_debug("[lcmdet]lcmbias_mod_exit\n");
        platform_driver_unregister(&lcmbias_driver);
        pr_debug("[lcmdet]lcmbias_mod_exit Done!\n");
    }

    module_init(lcmbias_mod_init);
    module_exit(lcmbias_mod_exit);

#############################################################
// 第三方驱动使用操作 GPIO: pinctrl_select_state()
static void lcm_suspend(void) 
{
	// when phone sleep , config output low, disable backlight drv chip  
	push_table(lcm_deep_sleep_mode_in_setting, sizeof(lcm_deep_sleep_mode_in_setting) / sizeof(struct LCM_setting_table), 1);

#ifdef GPIO_LCD_BIAS_ENP_PIN
	mt_set_gpio_mode(GPIO_LCD_BIAS_ENP_PIN, GPIO_MODE_00);
	mt_set_gpio_dir(GPIO_LCD_BIAS_ENP_PIN, GPIO_DIR_OUT);
	mt_set_gpio_out(GPIO_LCD_BIAS_ENP_PIN, GPIO_OUT_ZERO);
#endif
	pinctrl_select_state(lcmbiasctrl, lcmbias_disable); 
	MDELAY(10);

	printk("lcm ili9881_hd720_dsi_vdo_yassy %s \n",__func__);
	MDELAY(10);
}

static void lcm_init(void)
{
#ifdef GPIO_LCD_BIAS_ENP_PIN
	mt_set_gpio_mode(GPIO_LCD_BIAS_ENP_PIN, GPIO_MODE_00);
	mt_set_gpio_dir(GPIO_LCD_BIAS_ENP_PIN, GPIO_DIR_OUT);
	mt_set_gpio_out(GPIO_LCD_BIAS_ENP_PIN, GPIO_OUT_ONE);
#endif
	pinctrl_select_state(lcmbiasctrl, lcmbias_enable); 

	MDELAY(10);

	SET_RESET_PIN(1);
	MDELAY(1);
	SET_RESET_PIN(0);
	MDELAY(10);
	SET_RESET_PIN(1);
	MDELAY(120);

	push_table(lcm_initialization_setting1, sizeof(lcm_initialization_setting1) / sizeof(struct LCM_setting_table), 1); 

	printk("lcm ili9881_hd720_dsi_vdo_yassy %s \n",__func__);	  
}
;