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__);	  
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章