現在的位置: 首頁 > 技術文章 > Tiny4412專區 > linux > 正文

設備樹詳解(轉)

2018年08月11日 linux ⁄ 共 7236字 ⁄ 字號 設備樹詳解(轉)已關閉評論

在Linux3.x版本后,arch/arm/plat-xxx和arch/arm/mach-xxx中,描述板級細節的代碼(比如platform_device、i2c_board_info等)被大量取消,取而代之的是設備樹,其目錄位于arch/arm/boot/dts.

1.設備樹的組成

1個dts文件+n個dtsi文件,它們編譯而成的dtb文件就是真正的設備樹

  • soc廠商會把soc公共的特性和多塊開發板公用的特性提煉為dtsi,而dts則負責描述某個具體的產品(開發板)的特性。dts直接或間接的包含多個dtsi(類似于c語言的頭文件),就體現了一個完整的產品(開發板)所有的特性。以solidrun公司的hummingboard為例,其組成為

 

dts

  • 此外,dts/dtsi兼容c語言的一些語法,能使用宏定義,也能包含.h文件

2.設備樹的結構

下面分別是是imx6dl-hummingboard.dts以及imx6dl.dtsi文件,我們以它們為例來分析,不難發現dts文件內容很少,只有一些板級的特征,大部分公共的硬件描述都在dtsi文件中

  • imx6dl-hummingboard.dts 文件節選
/dts-v1/;
#include "imx6dl.dtsi"
#include "imx6qdl-microsom.dtsi"
#include "imx6qdl-microsom-ar8035.dtsi"

/ {
model = "SolidRun HummingBoard DL/Solo";
compatible = "solidrun,hummingboard", "fsl,imx6dl";

ir_recv: ir-receiver {
compatible = "gpio-ir-receiver";
gpios = <&gpio1 2 1>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hummingboard_gpio1_2>;
};

regulators {
compatible = "simple-bus";

reg_3p3v: 3p3v {
compatible = "regulator-fixed";
regulator-name = "3P3V";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
}

&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hummingboard_i2c1>;

rtc: [email protected] {
compatible = "nxp,pcf8523";
reg = <0x68>;
};
};

  • imx6dl.dtsi文件節選
/ {
aliases {

/*省略無關代碼*/
}
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
interrupt-parent = <&intc>;
ranges;

/*省略無關代碼*/

[email protected] {
compatible = "arm,cortex-a9-twd-timer";
reg = <0x00a00600 0x20>;
interrupts = <1 13 0xf01>;
clocks = <&clks IMX6QDL_CLK_TWD>;
};

[email protected] { /* AIPS1 */
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02000000 0x100000>;
ranges;

/*省略無關代碼*/

gpio1: [email protected] {
compatible = "fsl,imx6q-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>,
<0 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};

/*省略無關代碼*/

i2c1: [email protected] {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <0 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6QDL_CLK_I2C1>;
status = "disabled";
};

};
/*省略無關代碼*/ 
}; 
};

基本構造

{}包圍起來的結構稱之為節點,dts中最開頭的/ {},稱為根節點。節點的標準結構是[email protected]{…},xxx是節點的名字,yyy則不是必須的,其值為節點的地址(寄存器地址或其他地址),比如i2c1: [email protected]中的就是一個i2c控制器的寄存器基地址,rtc: [email protected]中的就是這個rtc設備的i2c地址

屬性:地址

有關節點的地址,比如[email protected],雖然它在名字后面跟了地址,但是正式的設置是在reg屬性中設置的比如:reg = <0x021a0000 0x4000>; reg的格式通常為<address length>,0x021a0000是寄存器基地址,0x4000是長度。address 和length的個數是可變的,由父節點的屬性#address-cells 和#size-cells 決定,比如節點[email protected]的父節點是[email protected],其#address-cells 和#size-cells均為1,所以下面的i2c節點的reg屬性就有一個address 和length,而i2c節點本身#address-cells 和#size-cells 分別為1和0,所以其下的rtc: [email protected] 的reg屬性就只有一個0x68(i2c地址)了

屬性:兼容性

如果一個節點是設備節點,那么它一定要有compatible(兼容性),因為這將作為驅動和設備(設備節點)的匹配依據,compatible(兼容性)的值可以有不止一個字符串以滿足不同的需求,詳見下一節。而根節點的compatible也是非常重要的,也就是"fsl,imx6dl"這個字符串,因為系統啟動后,將根據根節點的compatible來判斷cpu信息,并由此進行初始化

屬性設置的套路

一般來說,每一種設備的節點屬性設置都會有一些套路,比如可以設置哪些屬性?屬性值怎么設置?那怎么知道這些套路呢,有兩種思路

第一種是抄類似的dts,比如我們自己項目的平臺是4412,那么就可以抄exynos4412-tiny4412.dts、exynos4412-smdk4412.dts這類相近的dts

第二種是查詢內核中的文檔,比如Documentation/devicetree/bindings/i2c/i2c-imx.txt就描述了imx平臺的i2c屬性設置方法;Documentation/devicetree/bindings/fb就描述了lcd、lvds這類屬性設置方法

節點之間的聯系

節點與節點之間的關聯,通常通過“標號引用”和“包含”來實現

所謂標號引用,就是在節點名稱前加上標號,這樣設備樹的其他位置就能夠通過&符號來調用/訪問該節點,比如上面代碼ir_recv節點中的gpio屬性,就引用了gpio1標號處的節點

包含則是最基本的方式,比如我們要在i2c1接口添加一個i2c外設,那么就必須要在i2c1下面添加一個節點,比如上面代碼中的rtc: [email protected] {}

標號引用常常還作為節點的重寫方式,比如下面代碼是imx6qdl.dtsi中定義的i2c節點,而前面imx6dl-hummingboard.dts中的&i2c1,就是對i2c1標號處節點的一次重寫,在其內部添加了一個rtc設備

如果一個節點是屬性節點(即僅僅是作為屬性被其他節點調用),那么它定義在哪里其實無所謂,重要的是調用的位置,比如lcd屏幕的時序,其實我們完全可以把它定義在其他犄角旮旯,然后在lcd節點下用&來調用它,這也是可以的。這有點類似于函數:在哪定義不重要,重要的是在哪調用

3.內核(驅動)與節點的匹配

首先,內核必須要知道dtb文件的地址,這由U-boot來告訴內核。只要內核知曉了dtb文件的地址,那么驅動就可以通過一些API任意獲取設備樹的內部信息

對于3.x版本之后的內核,platform、i2c、spi等設備不再需要在mach-xxx中注冊,驅動程序將直接和設備樹里的設備節點進行配對,是通過設備節點中的compatible(兼容性)來與設備節點進行配對的

這里以pcf8523驅動為例,只要驅動中的of_match_table 中的compatible 值和設備節點中的compatible 相匹配,那么probe函數就會被觸發。不僅i2c是這樣,platform、spi等都是這個原理

/*定義的of_match_table*/
static const struct of_device_id pcf8523_of_match[] = {
{ .compatible = "nxp,pcf8523" },
{ }
};

/*driver 結構體中的of_match_table*/
static struct i2c_driver pcf8523_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(pcf8523_of_match),
},
.probe = pcf8523_probe,
.id_table = pcf8523_id,
};

i2c和spi驅動還支持一種“別名匹配”的機制,就以pcf8523為例,假設某程序員在設備樹中的pcf8523設備節點中寫了compatible = "pcf8523";,顯然相對于驅動id_table中的"nxp,pcf8523",他遺漏了nxp字段,但是驅動卻仍然可以匹配上,因為別名匹配對compatible中字符串里第二個字段敏感

4.常見屬性的設置與獲取

當修改或編寫驅動時,常常需要修改gpio、時鐘、中斷等等參數,以前都是在mach-xxx中的device設置的,現在則要在節點里設置,然后驅動用特殊的API來獲取

屬性的獲取常常在probe函數中進行,但是獲取屬性之前,最重要的是,確定哪個節點觸發了驅動。如果一個驅動對應多個節點,那驅動可以通過int of_device_is_compatible(const struct device_node *device, const char *name)來判斷當前節點是否包含指定的compatible(兼容性)

gpio的設置與獲取

/*imx6dl.dtsi中gpio1控制器的定義節點*/
gpio1: [email protected] {
compatible = "fsl,imx6q-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>,
<0 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};

/*imx6qdl-sabreauto.dtsi中某個設備節點*/
max7310_reset: max7310-reset {
compatible = "gpio-reset";
reset-gpios = <&gpio1 15 1>;
reset-delay-us = <1>;
#reset-cells = <0>;
};

一般來說,我們把gpio屬性的名字起為xxx-gpios(xxx我們可以隨便起),這樣驅動才能通過特定API從識別該屬性,并轉換成具體的gpio號

該設備節點中設置了reset-gpios = <&gpio1 15 1>;這格式是什么意思呢?&gpio1 15引用了gpio1節點,故此處含義為gpio1_15這個引腳;最后一個參數1則代表低電平有效,0則為高電平有效。至于gpio1_15具體對應哪個引腳,在imx6的手冊上都有詳細描述

其實最后一個參數(高低電平有效)不是必須的,因為gpio1節點中設置了#gpio-cells = <2>;,所以才有兩個參數;某些soc的gpio節點中會設置為#gpio-cells = <1>;,那么可以不寫最后一個參數

驅動一般通過以下接口獲取上面節點中gpio的屬性。該函數第一個參數是節點,一般可以在傳入probe的參數中間接獲得;第二個參數是gpio屬性的名字,一定要和節點屬性中的xxx-gpios相同;最后一個是編號index,當節點中有n個同名的xxx-gpios時,可以通過它來獲取特定的那個gpio,同一節點中gpio同名情況很少存在,所以我們都把index設為0

gpio = of_get_named_gpio(node, "reset-gpios", index);

在dts和驅動都不關心gpio名字的情況下,也可直接通過以下接口來獲取gpio號,這個時候編號index就十分重要了,可以指定拿取節點的第index個gpio屬性

gpio = of_get_gpio(node, index);

中斷的設置與獲取

假設某設備節點需要一個gpio中斷

/*先確定中斷所在的組*/
interrupt-parent = <&gpio6>;

/*表示中斷,GPIO6中的第8個IO,2為觸發類型,下降沿觸發*/
interrupts = <8 2>;

而在驅動中使用 中斷號 =irq_of_parse_and_map(node, index)函數返回值來得到中斷號

自定義屬性的設置與獲取

所謂的自定義屬性,有點類似于老內核中的platform_data,我們在設備節點中可以隨意添加自定義屬性,比如下面這個節點里面的屬性都是我們自己定義的

reg_3p3v: 3p3v {
compatible = "regulator-fixed";
regulator-name = "3P3V";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};

針對32位整形的屬性,比如上面的regulator-min-microvolt,可以利用下面這個API來獲取屬性值,第一個參數是節點,第二個參數是屬性名字,第三個是輸出型參數(把讀出來的值放進去)

of_property_read_u32(node, "regulator-min-microvolt", &microvolt);

類似的讀取數值的API還有:

int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value)

int of_property_read_u16(const struct device_node *np, const char *propname, u16 *out_value)

下列API可檢查節點中某個屬性是否存在,存在則返回true,不存在則返回false

bool of_property_read_bool(const struct device_node *np, const char *propname)

當節點中存在字符串時,可以像下面那樣讀取,比如我們讀取前面reg_3p3v節點中的字符串

of_property_read_string(node, "regulator-name", &string)

當節點中存在數組時,可以像下面那樣讀取

/*帶有數組的某個節點*/
L2: [email protected] {
compatible = "arm,pl310-cache";
arm,data-latency = <1 1 1>;
arm,tag-latency = <1 1 1>;
};

/*驅動中使用API來讀取數組, &data為輸出型參數*/
of_property_read_u32_array(node, "arm,pl310-cache", &data, ARRAY_SIZE(data));

轉自 : https://blog.csdn.net/qq_28992301/article/details/53321610

二八杠讨论心得
×