在Linux3.x版本后,arch/arm/plat-xxx和arch/arm/mach-xxx中,描述板級(jí)細(xì)節(jié)的代碼(比如platform_device、i2c_board_info等)被大量取消,取而代之的是設(shè)備樹,其目錄位于arch/arm/boot/dts
1.設(shè)備樹的組成
1個(gè)dts文件+n個(gè)dtsi文件,它們編譯而成的dtb文件就是真正的設(shè)備樹
soc廠商會(huì)把soc公共的特性和多塊開發(fā)板公用的特性提煉為dtsi,而dts則負(fù)責(zé)描述某個(gè)具體的產(chǎn)品(開發(fā)板)的特性。dts直接或間接的包含多個(gè)dtsi(類似于c語言的頭文件),就體現(xiàn)了一個(gè)完整的產(chǎn)品(開發(fā)板)所有的特性。以solidrun公司的hummingboard為例,其組成為
imx6dl-hummingboard.dts |_imx6dl.dtsi | |_imx6qdl.dtsi |_imx6qdl-microsom.dtsi |_imx6qdl-microsom-ar8035.dtsi
1
2
3
4
5
此外,dts/dtsi兼容c語言的一些語法,能使用宏定義,也能包含.h文件
2.設(shè)備樹的結(jié)構(gòu)
下面分別是是imx6dl-hummingboard.dts以及imx6dl.dtsi文件,我們以它們?yōu)槔齺矸治?,不難發(fā)現(xiàn)dts文件內(nèi)容很少,只有一些板級(jí)的特征,大部分公共的硬件描述都在dtsi文件中
imx6dl-hummingboard.dts 文件節(jié)選
/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: pcf8523@68 { compatible = "nxp,pcf8523"; reg = <0x68>; };};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
imx6dl.dtsi文件節(jié)選
/ { aliases { /*省略無關(guān)代碼*/ } soc { #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; interrupt-parent = <&intc>; ranges; /*省略無關(guān)代碼*/ timer@00a00600 { compatible = "arm,cortex-a9-twd-timer"; reg = <0x00a00600 0x20>; interrupts = <1 13 0xf01>; clocks = <&clks IMX6QDL_CLK_TWD>; }; aips-bus@02000000 { /* AIPS1 */ compatible = "fsl,aips-bus", "simple-bus"; #address-cells = <1>; #size-cells = <1>; reg = <0x02000000 0x100000>; ranges; /*省略無關(guān)代碼*/ gpio1: gpio@0209c000 { 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>; }; /*省略無關(guān)代碼*/ i2c1: i2c@021a0000 { #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"; }; }; /*省略無關(guān)代碼*/ }; };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
基本構(gòu)造
{}包圍起來的結(jié)構(gòu)稱之為節(jié)點(diǎn),dts中最開頭的/ {},稱為根節(jié)點(diǎn)。節(jié)點(diǎn)的標(biāo)準(zhǔn)結(jié)構(gòu)是xxx@yyy{…},xxx是節(jié)點(diǎn)的名字,yyy則不是必須的,其值為節(jié)點(diǎn)的地址(寄存器地址或其他地址),比如i2c1: i2c@021a0000中的就是一個(gè)i2c控制器的寄存器基地址,rtc: pcf8523@68中的就是這個(gè)rtc設(shè)備的i2c地址
屬性:地址
有關(guān)節(jié)點(diǎn)的地址,比如i2c@021a0000,雖然它在名字后面跟了地址,但是正式的設(shè)置是在reg屬性中設(shè)置的比如:reg = <0x021a0000 0x4000>;?reg的格式通常為 ,0x021a0000是寄存器基地址,0x4000是長度。address 和length的個(gè)數(shù)是可變的,由父節(jié)點(diǎn)的屬性#address-cells?和#size-cells?決定,比如節(jié)點(diǎn)i2c@021a0000的父節(jié)點(diǎn)是aips-bus@02000000,其#address-cells?和#size-cells均為1,所以下面的i2c節(jié)點(diǎn)的reg屬性就有一個(gè)address 和length,而i2c節(jié)點(diǎn)本身#address-cells?和#size-cells?分別為1和0,所以其下的rtc: pcf8523@68?的reg屬性就只有一個(gè)0x68(i2c地址)了
屬性:兼容性
如果一個(gè)節(jié)點(diǎn)是設(shè)備節(jié)點(diǎn),那么它一定要有compatible(兼容性),因?yàn)檫@將作為驅(qū)動(dòng)和設(shè)備(設(shè)備節(jié)點(diǎn))的匹配依據(jù),compatible(兼容性)的值可以有不止一個(gè)字符串以滿足不同的需求,詳見下一節(jié)。而根節(jié)點(diǎn)的compatible也是非常重要的,也就是"fsl,imx6dl"這個(gè)字符串,因?yàn)橄到y(tǒng)啟動(dòng)后,將根據(jù)根節(jié)點(diǎn)的compatible來判斷cpu信息,并由此進(jìn)行初始化
屬性設(shè)置的套路
一般來說,每一種設(shè)備的節(jié)點(diǎn)屬性設(shè)置都會(huì)有一些套路,比如可以設(shè)置哪些屬性?屬性值怎么設(shè)置?那怎么知道這些套路呢,有兩種思路
第一種是抄類似的dts,比如我們自己項(xiàng)目的平臺(tái)是4412,那么就可以抄exynos4412-tiny4412.dts、exynos4412-smdk4412.dts這類相近的dts
第二種是查詢內(nèi)核中的文檔,比如Documentation/devicetree/bindings/i2c/i2c-imx.txt就描述了imx平臺(tái)的i2c屬性設(shè)置方法;Documentation/devicetree/bindings/fb就描述了lcd、lvds這類屬性設(shè)置方法
節(jié)點(diǎn)之間的聯(lián)系
節(jié)點(diǎn)與節(jié)點(diǎn)之間的關(guān)聯(lián),通常通過“標(biāo)號(hào)引用”和“包含”來實(shí)現(xiàn)
所謂標(biāo)號(hào)引用,就是在節(jié)點(diǎn)名稱前加上標(biāo)號(hào),這樣設(shè)備樹的其他位置就能夠通過&符號(hào)來調(diào)用/訪問該節(jié)點(diǎn),比如上面代碼ir_recv節(jié)點(diǎn)中的gpio屬性,就引用了gpio1標(biāo)號(hào)處的節(jié)點(diǎn)
包含則是最基本的方式,比如我們要在i2c1接口添加一個(gè)i2c外設(shè),那么就必須要在i2c1下面添加一個(gè)節(jié)點(diǎn),比如上面代碼中的rtc: pcf8523@68 {}
標(biāo)號(hào)引用常常還作為節(jié)點(diǎn)的重寫方式,比如下面代碼是imx6qdl.dtsi中定義的i2c節(jié)點(diǎn),而前面imx6dl-hummingboard.dts中的&i2c1,就是對(duì)i2c1標(biāo)號(hào)處節(jié)點(diǎn)的一次重寫,在其內(nèi)部添加了一個(gè)rtc設(shè)備
如果一個(gè)節(jié)點(diǎn)是屬性節(jié)點(diǎn)(即僅僅是作為屬性被其他節(jié)點(diǎn)調(diào)用),那么它定義在哪里其實(shí)無所謂,重要的是調(diào)用的位置,比如lcd屏幕的時(shí)序,其實(shí)我們完全可以把它定義在其他犄角旮旯,然后在lcd節(jié)點(diǎn)下用&來調(diào)用它,這也是可以的。這有點(diǎn)類似于函數(shù):在哪定義不重要,重要的是在哪調(diào)用
3.內(nèi)核(驅(qū)動(dòng))與節(jié)點(diǎn)的匹配
首先,內(nèi)核必須要知道dtb文件的地址,這由U-boot來告訴內(nèi)核,詳見U-boot引導(dǎo)內(nèi)核流程分析?第6節(jié)。只要內(nèi)核知曉了dtb文件的地址,那么驅(qū)動(dòng)就可以通過一些API任意獲取設(shè)備樹的內(nèi)部信息
對(duì)于3.x版本之后的內(nèi)核,platform、i2c、spi等設(shè)備不再需要在mach-xxx中注冊(cè),驅(qū)動(dòng)程序?qū)⒅苯雍驮O(shè)備樹里的設(shè)備節(jié)點(diǎn)進(jìn)行配對(duì),是通過設(shè)備節(jié)點(diǎn)中的compatible(兼容性)來與設(shè)備節(jié)點(diǎn)進(jìn)行配對(duì)的,這里只做簡單介紹,具體的應(yīng)用詳見?基于i2c子系統(tǒng)的驅(qū)動(dòng)分析、?基于platform總線的驅(qū)動(dòng)分析
這里以pcf8523驅(qū)動(dòng)為例,只要驅(qū)動(dòng)中的of_match_table 中的compatible 值和設(shè)備節(jié)點(diǎn)中的compatible 相匹配,那么probe函數(shù)就會(huì)被觸發(fā)。不僅i2c是這樣,platform、spi等都是這個(gè)原理
/*定義的of_match_table*/static const struct of_device_id pcf8523_of_match[] = { { .compatible = "nxp,pcf8523" }, { }};/*driver 結(jié)構(gòu)體中的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,};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
i2c和spi驅(qū)動(dòng)還支持一種“別名匹配”的機(jī)制,就以pcf8523為例,假設(shè)某程序員在設(shè)備樹中的pcf8523設(shè)備節(jié)點(diǎn)中寫了compatible = "pcf8523";,顯然相對(duì)于驅(qū)動(dòng)id_table中的"nxp,pcf8523",他遺漏了nxp字段,但是驅(qū)動(dòng)卻仍然可以匹配上,因?yàn)閯e名匹配對(duì)compatible中字符串里第二個(gè)字段敏感
4.常見屬性的設(shè)置與獲取
當(dāng)修改或編寫驅(qū)動(dòng)時(shí),常常需要修改gpio、時(shí)鐘、中斷等等參數(shù),以前都是在mach-xxx中的device設(shè)置的,現(xiàn)在則要在節(jié)點(diǎn)里設(shè)置,然后驅(qū)動(dòng)用特殊的API來獲取
屬性的獲取常常在probe函數(shù)中進(jìn)行,但是獲取屬性之前,最重要的是,確定哪個(gè)節(jié)點(diǎn)觸發(fā)了驅(qū)動(dòng)。如果一個(gè)驅(qū)動(dòng)對(duì)應(yīng)多個(gè)節(jié)點(diǎn),那驅(qū)動(dòng)可以通過int of_device_is_compatible(const struct device_node *device, const char *name)來判斷當(dāng)前節(jié)點(diǎn)是否包含指定的compatible(兼容性)
gpio的設(shè)置與獲取
/*imx6dl.dtsi中g(shù)pio1控制器的定義節(jié)點(diǎn)*/gpio1: gpio@0209c000 { 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中某個(gè)設(shè)備節(jié)點(diǎn)*/max7310_reset: max7310-reset { compatible = "gpio-reset"; reset-gpios = <&gpio1 15 1>; reset-delay-us = <1>; #reset-cells = <0>;};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
一般來說,我們把gpio屬性的名字起為xxx-gpios(xxx我們可以隨便起),這樣驅(qū)動(dòng)才能通過特定API從識(shí)別該屬性,并轉(zhuǎn)換成具體的gpio號(hào)
該設(shè)備節(jié)點(diǎn)中設(shè)置了reset-gpios = <&gpio1 15 1>;這格式是什么意思呢?&gpio1 15引用了gpio1節(jié)點(diǎn),故此處含義為gpio1_15這個(gè)引腳;最后一個(gè)參數(shù)1則代表低電平有效,0則為高電平有效。至于gpio1_15具體對(duì)應(yīng)哪個(gè)引腳,在imx6的手冊(cè)上都有詳細(xì)描述
其實(shí)最后一個(gè)參數(shù)(高低電平有效)不是必須的,因?yàn)間pio1節(jié)點(diǎn)中設(shè)置了#gpio-cells = <2>;,所以才有兩個(gè)參數(shù);某些soc的gpio節(jié)點(diǎn)中會(huì)設(shè)置為#gpio-cells = <1>;,那么可以不寫最后一個(gè)參數(shù)
驅(qū)動(dòng)一般通過以下接口獲取上面節(jié)點(diǎn)中g(shù)pio的屬性。該函數(shù)第一個(gè)參數(shù)是節(jié)點(diǎn),一般可以在傳入probe的參數(shù)中間接獲得;第二個(gè)參數(shù)是gpio屬性的名字,一定要和節(jié)點(diǎn)屬性中的xxx-gpios相同;最后一個(gè)是編號(hào)index,當(dāng)節(jié)點(diǎn)中有n個(gè)同名的xxx-gpios時(shí),可以通過它來獲取特定的那個(gè)gpio,同一節(jié)點(diǎn)中g(shù)pio同名情況很少存在,所以我們都把index設(shè)為0
gpio = of_get_named_gpio(node, "reset-gpios", index);
1
在dts和驅(qū)動(dòng)都不關(guān)心gpio名字的情況下,也可直接通過以下接口來獲取gpio號(hào),這個(gè)時(shí)候編號(hào)index就十分重要了,可以指定拿取節(jié)點(diǎn)的第index個(gè)gpio屬性
gpio = of_get_gpio(node, index);
1
中斷的設(shè)置與獲取
假設(shè)某設(shè)備節(jié)點(diǎn)需要一個(gè)gpio中斷
/*先確定中斷所在的組*/interrupt-parent = <&gpio6>;/*表示中斷,GPIO6中的第8個(gè)IO,2為觸發(fā)類型,下降沿觸發(fā)*/interrupts = <8 2>;
1
2
3
4
5
6
而在驅(qū)動(dòng)中使用?中斷號(hào) =irq_of_parse_and_map(node, index)函數(shù)返回值來得到中斷號(hào)
自定義屬性的設(shè)置與獲取
所謂的自定義屬性,有點(diǎn)類似于老內(nèi)核中的platform_data,我們?cè)谠O(shè)備節(jié)點(diǎn)中可以隨意添加自定義屬性,比如下面這個(gè)節(jié)點(diǎn)里面的屬性都是我們自己定義的
reg_3p3v: 3p3v { compatible = "regulator-fixed"; regulator-name = "3P3V"; regulator-min-microvolt = <3300000>; regulator-max-microvolt = <3300000>; regulator-always-on;};
1
2
3
4
5
6
7
針對(duì)32位整形的屬性,比如上面的regulator-min-microvolt,可以利用下面這個(gè)API來獲取屬性值,第一個(gè)參數(shù)是節(jié)點(diǎn),第二個(gè)參數(shù)是屬性名字,第三個(gè)是輸出型參數(shù)(把讀出來的值放進(jìn)去)
of_property_read_u32(node, "regulator-min-microvolt", μvolt);
1
類似的讀取數(shù)值的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)
1
2
3
4
下列API可檢查節(jié)點(diǎn)中某個(gè)屬性是否存在,存在則返回true,不存在則返回false
bool of_property_read_bool(const struct device_node *np, const char *propname)
1
當(dāng)節(jié)點(diǎn)中存在字符串時(shí),可以像下面那樣讀取,比如我們讀取前面reg_3p3v節(jié)點(diǎn)中的字符串
of_property_read_string(node, "regulator-name", &string)
1
當(dāng)節(jié)點(diǎn)中存在數(shù)組時(shí),可以像下面那樣讀取
/*帶有數(shù)組的某個(gè)節(jié)點(diǎn)*/L2: cache-controller@1e00a000 { compatible = "arm,pl310-cache"; arm,data-latency = <1 1 1>; arm,tag-latency = <1 1 1>;};/*驅(qū)動(dòng)中使用API來讀取數(shù)組, &data為輸出型參數(shù)*/of_property_read_u32_array(node, "arm,pl310-cache", &data, ARRAY_SIZE(data));
?
評(píng)論
查看更多