1. 程式人生 > >ARM Linux裝置樹

ARM Linux裝置樹

1 ARM裝置樹

DT: Device Tree

FDT: Flattened DeviceTree

OF: Open Firmware(開啟韌體,這個字首在後面的api中會用到)

DTS : device tree souke

DTSI: device tree source include

DTB: device tree blob

DTC:device tree compiler

 

2 裝置樹的組成和結構

整個裝置樹牽涉面比較廣,既增加了新的用於描述裝置硬體資訊的文字格式,又增加了編譯這個文字的工具,同時Bootloader也需要支援將編譯後的裝置樹傳遞給Linux核心

2.1 DTS、 DTC和DTB等

1.DTS

一般放置在核心的arch/arm/boot/dts/目錄中。也會在arch/powerpc/boot/dts、 arch/powerpc/boot/dts、
arch/c6x/boot/dts、 arch/openrisc/boot/dts等目錄中。


由於一個SoC可能對應多個裝置(一個SoC可以對應多個產品和電路板),這些.dts檔案勢必須包含許多共同的部分,Linux核心為了簡化,把SoC公用的部分或者多個裝置共同的部分一般提煉為.dtsi,類似於C語言的標頭檔案。其他的裝置對應的.dts就包括這個.dtsi。譬如,對於VEXPRESS而言, vexpressv2m.dtsi就被vexpress-v2p-ca9.dts所引用, vexpress-v2p-ca9.dts
有如下一行程式碼:
/include/ "vexpress-v2m.dtsi"


當然,和C語言的標頭檔案類似, .dtsi也可以包括其他的.dtsi,譬如幾乎所有的ARM SoC的.dtsi都引用了skeleton.dtsi。
檔案.dts(或者其包括的.dtsi)的基本元素即為前文所述的節點和屬性,程式碼清單18.1給出了一個裝置樹結構的模版。

 

下面以一個最簡單的裝置為例來看如何寫一個.dts檔案。如圖18.1所示,假設此裝置的配置如下

  • 1個雙核ARM Cortex-A932位處理器;
  • ARM本地總線上的記憶體對映區域分佈有兩個串列埠(分別位於0x101F1000和0x101F2000)、 GPIO控制器(位於0x101F3000)、 SPI控制器(位於0x10170000)、中斷控制器(位於0x10140000)和一個外部匯流排橋;
  • 外部匯流排橋上又連線了SMC SMC91111乙太網(位於0x10100000)、 I2C控制器(位於0x10160000)、 64MBNOR Flash(位於0x30000000);
  • 外部匯流排橋上連線的I2C控制器所對應的I2C總線上又連線了Maxim DS1338實時鐘(I2C地址為0x58)。

對於圖18.1所示硬體結構圖,如果用“.dts”描述,則其對應的“.dts”檔案如程式碼清單18.2所示。

/ {
	compatible = "acme,coyotes-revenge";
	#address-cells = <1>;
	#size-cells = <1>;
	interrupt-parent = <&intc>;

	cpus {
		#address-cells = <1>;
		#size-cells = <0>;
		[email protected] {
			compatible = "arm,cortex-a9";
			reg = <0>;
		};
		[email protected] {
			compatible = "arm,cortex-a9";
			reg = <1>;
		};
	};

	[email protected] {
		compatible = "arm,pl011";
		reg = <0x101f0000 0x1000 >;
		interrupts = < 1 0 >;
	};

	[email protected] {
		compatible = "arm,pl011";
		reg = <0x101f2000 0x1000 >;
		interrupts = < 2 0 >;
	};

	[email protected] {
		compatible = "arm,pl061";
		reg = <0x101f3000 0x1000
				0x101f4000 0x0010>;
		interrupts = < 3 0 >;
	};

	intc: [email protected] {
		compatible = "arm,pl190";
		reg = <0x10140000 0x1000 >;
		interrupt-controller;
		#interrupt-cells = <2>;
	};

	[email protected] {
		compatible = "arm,pl022";
		reg = <0x10115000 0x1000 >;
		interrupts = < 4 0 >;
	};

	external-bus {
		#address-cells = <2>
		#size-cells = <1>;
		ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethe
		        1 0 0x10160000 0x10000 // Chipselect 2, i
		        2 0 0x30000000 0x1000000>; // Chipselect 3, NOR

		[email protected],0 {
			compatible = "smc,smc91c111";
			reg = <0 0 0x1000>;
			interrupts = < 5 2 >;
		};

		[email protected],0 {
			compatible = "acme,a1234-i2c-bus";
			#address-cells = <1>;
			#size-cells = <0>;
			reg = <1 0 0x1000>;
			interrupts = < 6 2 >;
			[email protected] {
				compatible = "maxim,ds1338";
				reg = <58>;
				interrupts = < 7 3 >;
			};
		};

		[email protected],0 {
			compatible = "samsung,k8f1315ebm", "cfi-flash";
			reg = <2 0 0x4000000>;
		};
	};
};

2.DTC(Device Tree Compiler)

DTC是將.dts編譯為.dtb的工具。 DTC的原始碼位於核心的scripts/dtc目錄中,在Linux核心使能了裝置樹的情況下,編譯核心的時候主機工具DTC會被編譯出來,對應於scripts/dtc/Makefile中“hostprogs -y: =dtc”這一hostprogs的編譯目標。
當然, DTC也可以在Ubuntu中單獨安裝,命令如下:
sudo apt-get install device-tree-compiler

在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下,我們可以單獨編譯裝置樹檔案。當我們在Linux核心下執行make dtbs時,若我們之前選擇了ARCH_VEXPRESS,上述.dtb都會由對應的.dts編譯出來,因為arch/arm/Makefile中含有一個.dtbs編譯目標專案。DTC除了可以編譯.dts檔案以外,其實也可以“反彙編”.dtb檔案為.dts檔案,其指令格式為:
./scripts/dtc/dtc -I dtb -O dts -o xxx.dts arch/arm/boot/dts/xxx.dtb


3.DTB(Device Tree Blob)

檔案.dtb是.dts被DTC編譯後的二進位制格式的裝置樹描述,可由Linux核心解析,當然U-Boot這樣的bootloader也是可以識別.dtb的。
通常在我們為電路板製作NAND、 SD啟動映像時,會為.dtb檔案單獨留下一個很小的區域以存放之,之後bootloader在引導核心的過程中,會先讀取該.dtb到記憶體。
Linux核心也支援一種變通的模式,可以不把.dtb檔案單獨存放,而是直接和zImage繫結在一起做成一個映像檔案,類似

cat zImage xxx.dtb>zImage_with_dtb的效果。當然核心編譯時候要使能CONFIG_ARM_APPENDED_DTB這個選項,以支援“Use appended device tree blob to zImage”(見Linux核心中的選單)。

4.繫結(Binding)

對於裝置樹中的節點和屬性具體是如何來描述裝置的硬體細節的,一般需要文件來進行講解,文件的字尾名一般為.txt。在這個.txt檔案中,需要描述對應節點的相容性、必需的屬性和可選的屬性。這些文件位於核心的Documentation/devicetree/bindings
錄下,其下又分為很多子目錄。譬如,
Documentation/devicetree/bindings/i2c/i2c-xiic.txt描述了Xilinx的I2C控制器,其內容如下

Xilinx IIC controller:
Required properties:
- compatible : Must be "xlnx,xps-iic-2.00.a"
- reg : IIC register location and length  //IIC暫存器的位置和長度
- interrupts : IIC controller unterrupt
- #address-cells = <1>
- #size-cells = <0>
Optional properties:
- Child nodes conforming to i2c bus binding
Example:
	axi_iic_0: [email protected] {
		compatible = "xlnx,xps-iic-2.00.a";
		interrupts = < 1 2 >;
		reg = < 0x40800000 0x10000 >;
		#size-cells = <0>;
		#address-cells = <1>;
	};

基本可以看出,裝置樹繫結文件的主要內容包括:

  • 關於該模組最基本的描述。
  • 必需屬性(Required Properties)的描述。
  • 可選屬性(Optional Properties)的描述。
  • 一個例項。

Linux核心下的scripts/checkpatch.pl會執行一個檢查,如果有人在裝置樹中新添加了compatible字串,而沒有新增相應的文件進行解釋, checkpatch程式會報出警告:
UNDOCUMENTED_DT_STRINGDT compatible string xxxappears un-documented

因此程式設計師要養成及時寫DT Binding文件的習慣。


5.Bootloader

設定啟動引數

bootargs=root=/dev/nfs nfsroot=192.168.43.30:/home/liu/ARM/rootfs/rootfs2 ip=192.168.43.10:192.168.43.30:192.168.43.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC2,115200
bootcmd=tftp 30008000 zImage;     tftp 30008020 test.dtb;     bootm 30008000 - 30008020

 

2.2 根節點相容性

根節點"/"的相容屬性compatible="acme, coyotes-revenge";它的組織形式為: <manufacturer>, <model>。
Linux核心通過根節點"/"的相容屬性即可判斷它啟動的是什麼裝置。在真實專案中,這個頂層裝置的相容屬性一般包括兩個或者兩個以上的相容性字串,

  1. 第一個相容性字串是板子級別的名字
  2. 第二個相容性是晶片級別(或者晶片系列級別)的名字

譬如板子arch/arm/boot/dts/vexpress-v2p-ca9.dts兼容於

arm, vexpress, v2p-ca9和“arm, vexpress”:
compatible = "arm,vexpress,v2p-ca9", "arm,vexpress";

板子arch/arm/boot/dts/vexpress-v2p-ca5s.dts的相容性則為:

 compatible = "arm,vexpress,v2p-ca5s", "arm,vexpress";

板子arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts的相容性為:

compatible = "arm,vexpress,v2p-ca15_a7", "arm,vexpress";

可以看出,上述各個電路板的共性是兼容於arm,vexpress,而特性是分別兼容於arm, vexpress, v2p-ca9、
arm, vexpress, v2p-ca5s和arm, vexpress, v2p-ca15_a7。
進一步地看, arch/arm/boot/dts/exynos4210-origen.dts的相容性欄位如下:

compatible = "insignal,origen", "samsung,exynos4210", "samsung,exynos4";
  1. 第1個字串是板子名字(很特定)
  2. 第2個字串是晶片名字(比較特定)
  3. 第3個欄位是晶片系列的名字(比較通用)

作為類比, arch/arm/boot/dts/exynos4210-universal_c210.dts的相容性欄位則如下:

compatible = "samsung,universal_c210", "samsung,exynos4210", "samsung,ex";

由此可見,它與exynos4210-origen.dts的區別只在於第1個字串(特定的板子名字)不一樣,後面晶片名和晶片系列的名字都一樣。

在Linux 2.6核心中, ARM Linux針對不同的電路板會建立由MACHINE_START和MACHINE_END包圍起來的針對這個裝置的一系列回撥函式,如mach-x210.c中

#ifdef CONFIG_MACH_SMDKC110
MACHINE_START(SMDKC110, "SMDKC110")
#elif CONFIG_MACH_SMDKV210
MACHINE_START(SMDKV210, "SMDKV210")
#endif
	/* Maintainer: Kukjin Kim <[email protected]> */
	.phys_io	= S3C_PA_UART & 0xfff00000,
	.io_pg_offst	= (((u32)S3C_VA_UART) >> 18) & 0xfffc,
	.boot_params	= S5P_PA_SDRAM + 0x100,
	//.fixup		= smdkv210_fixup,
	.init_irq	= s5pv210_init_irq,
	.map_io		= smdkc110_map_io,
	.init_machine	= smdkc110_machine_init,
	.timer		= &s5p_systimer,
MACHINE_END

這些不同的裝置會有不同的MACHINE ID, Uboot在啟動Linux核心時會將MACHINE ID存放在r1暫存器, Linux啟動時會匹配Bootloader傳遞的MACHINE ID和MACHINE_START宣告的MACHINE ID,然後執行相應裝置的一系列初始化函式。

ARM Linux 3.x在引入裝置樹之後, MACHINE_START變更為DT_MACHINE_START,其中含有一個.dt_compat成員,用於表明相關的裝置與.dts中根節點的相容屬性相容關係。

如果Bootloader傳遞給核心的裝置樹中根節點的相容屬性出現在某裝置的.dt_compat表中,相關的裝置就與對應的相容匹配,從而引發這一裝置的一系列初始化函式被執行。一個典型的DT_MACHINE如程式碼清單18.4所示。

static const char * const v2m_dt_match[] __initconst = {
	"arm,vexpress",
	"xen,xenvm",
	NULL,
};
DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express")
	.dt_compat 		= v2m_dt_match,
	.smp 			= smp_ops(vexpress_smp_ops),
	.map_io 		= v2m_dt_map_io,
	.init_early	 	= v2m_dt_init_early,
	.init_irq 		= v2m_dt_init_irq,
	.timer 			= &v2m_dt_timer,
	.init_machine           = v2m_dt_init,
	.handle_irq             = gic_handle_irq,
	.restart                = vexpress_restart,
MACHINE_END

Linux倡導針對多個SoC、多個電路板的通用DT裝置,即一個DT裝置的.dt_compat包含多個電路板.dts檔案的根節點相容屬性字串。之後,如果這多個電路板的初始化序列不一樣,可以通過下面的API判斷具體的電路板是什麼。

int of_machine_is_compatible(const char *compat);

此API判斷目前執行的板子或者SoC的相容性,它匹配的是裝置樹根節點下的相容屬性。

例如drivers/cpufreq/exynoscpufreq.c中就有判斷執行的CPU型別是exynos4210、exynos4212、 exynos4412還是exynos5250的程式碼,進而分別處理,

程式碼5 of_machine_is_compatible()的案例

static int exynos_cpufreq_probe(struct platform_device *pdev)
{
	int ret = -EINVAL;
	exynos_info = kzalloc(sizeof(*exynos_info), GFP_KERNEL);
	if (!exynos_info)
	return -ENOMEM;

	exynos_info->dev = &pdev->dev;
	
	if (of_machine_is_compatible("samsung,exynos4210")) {
		exynos_info->type = EXYNOS_SOC_4210;
		ret = exynos4210_cpufreq_init(exynos_info);
		
	} else if (of_machine_is_compatible("samsung,exynos4212")) {
		exynos_info->type = EXYNOS_SOC_4212;
		ret = exynos4x12_cpufreq_init(exynos_info);
		
	} else if (of_machine_is_compatible("samsung,exynos4412")) {
		exynos_info->type = EXYNOS_SOC_4412;
		ret = exynos4x12_cpufreq_init(exynos_info);
		
	} else if (of_machine_is_compatible("samsung,exynos5250")) {
		exynos_info->type = EXYNOS_SOC_5250;
		ret = exynos5250_cpufreq_init(exynos_info);
		
	} else {
		pr_err("%s: Unknown SoC type\n", __func__);
	return -ENODEV;
	}
	//...
}

如果一個相容包含多個字串,譬如對於前面介紹的根節點相容compatible="samsung, universal_c210", "samsung,exynos4210", "samsung, exynos4"的情況,如下的3個表示式都為真。

of_machine_is_compatible("samsung,universal_c210")
of_machine_is_compatible("samsung,exynos4210")
of_machine_is_compatible("samsung,exynos4")

2.3 裝置節點相容性

在.dts檔案的每個裝置節點中,都有一個相容屬性,相容屬性用於驅動和裝置的繫結。

相容屬性是一個字串的列表,列表中的第一個字串表徵了節點代表的確切裝置,形式為"<manufacturer>, <model>",其後的字串表徵可相容的其他裝置。可以說前面的是特指,後面的則涵蓋更廣的範圍。如在vexpress-v2m.dtsi中的Flash節點如下:

[email protected],00000000 {
    compatible = "arm,vexpress-flash", "cfi-flash";
    reg = <0 0x00000000 0x04000000>,
    <1 0x00000000 0x04000000>;
    bank-width = <4>;
};

 

int of_device_is_compatible(const struct device_node *device,const char *compat);

此函式用於判斷裝置節點的相容屬性是否包含compat指定的字串。這個API多用於一個驅動支援兩個以上裝置的時候。

當一個驅動支援兩個或多個裝置的時候,這些不同.dts檔案中裝置的相容屬性都會寫入驅動OF匹配表。因此驅動可以通過Bootloader傳遞給核心裝置樹中的真正節點的相容屬性以確定究竟是哪一種裝置,從而根據不同的裝置型別進行不同的處理。

如arch/powerpc/platforms/83xx/usb.c中的mpc831x_usb_cfg()就進行了類似處理:

if (immr_node && (of_device_is_compatible(immr_node, "fsl,mpc8315-immr") ||
		of_device_is_compatible(immr_node, "fsl,mpc8308-immr")))
	clrsetbits_be32(immap + MPC83XX_SCCR_OFFS,
					MPC8315_SCCR_USB_MASK,
					MPC8315_SCCR_USB_DRCM_01);
else
	clrsetbits_be32(immap + MPC83XX_SCCR_OFFS,
					MPC83XX_SCCR_USB_MASK,
					MPC83XX_SCCR_USB_DRCM_11);
/* Configure pin mux for ULPI. There is no pin mux for UTMI */
if (prop && !strcmp(prop, "ulpi")) {
	if (of_device_is_compatible(immr_node, "fsl,mpc8308-immr")) {
		clrsetbits_be32(immap + MPC83XX_SICRH_OFFS,
		MPC8308_SICRH_USB_MASK,
		MPC8308_SICRH_USB_ULPI);
	} else if (of_device_is_compatible(immr_node, "fsl,mpc8315-immr")){
		clrsetbits_be32(immap + MPC83XX_SICRL_OFFS,
		MPC8315_SICRL_USB_MASK,
		MPC8315_SICRL_USB_ULPI);
		clrsetbits_be32(immap + MPC83XX_SICRH_OFFS,
		MPC8315_SICRH_USB_MASK,
		MPC8315_SICRH_USB_ULPI);
	} else {
		clrsetbits_be32(immap + MPC83XX_SICRL_OFFS,
		MPC831X_SICRL_USB_MASK,
		MPC831X_SICRL_USB_ULPI);
		clrsetbits_be32(immap + MPC83XX_SICRH_OFFS,
		MPC831X_SICRH_USB_MASK,
		MPC831X_SICRH_USB_ULPI);
	}
}

它根據具體的裝置是fsl, mpc8315-immr和fsl, mpc8308-immr、中的哪一種來進行不同的處理。

當一個驅動可以相容多種裝置的時候,除了of_device_is_compatible()這種判斷方法以外,還可以採用在驅動的of_device_id表中填充.data成員的形式。譬如,arch/arm/mm/cache-l2x0.c支援“arm, l210-cache”“arm, pl310-cache”“arm, l220-cache”等多種裝置,其of_device_id表如程式碼清單18.9所示。
程式碼清單18.9 支援多個相容性以及.data成員的of_device_id表

#define L2C_ID(name, fns) 
{
	.compatible = name,
	.data = (void *)&fns,
}
static const struct of_device_id l2x0_ids[] __initconst = {
	L2C_ID("arm,l210-cache", of_l2c210_data),
	L2C_ID("arm,l220-cache", of_l2c220_data),
	L2C_ID("arm,pl310-cache", of_l2c310_data),
	L2C_ID("brcm,bcm11351-a2-pl310-cache", of_bcm_l2x0_data),
	L2C_ID("marvell,aurora-outer-cache", of_aurora_with_outer_data),
	L2C_ID("marvell,aurora-system-cache", of_aurora_no_outer_data),
	L2C_ID("marvell,tauros3-cache", of_tauros3_data),
	/* Deprecated IDs */
	L2C_ID("bcm,bcm11351-a2-pl310-cache", of_bcm_l2x0_data),
	{}
};

在驅動中,通過下面的程式碼的方法拿到了對應於L2快取型別的.data成員,其中主要用到了of_match_node()這個API。

int __init l2x0_of_init(u32 aux_val, u32 aux_mask)
{
	const struct l2c_init_data *data;
	struct device_node *np;

	np = of_find_matching_node(NULL, l2x0_ids);
	if (!np)
	return -ENODEV;
	//...
/*10*/	data = of_match_node(l2x0_ids, np)->data;
}

如果電路板的.dts檔案中L2快取是arm, pl310-cache,那麼上述程式碼第10行找到的data就是of_l2c310_data,它是l2c_init_data結構體的一個例項。 l2c_init_data是一個由L2快取驅動自定義的資料結構,在其定義中既可以保護資料成員,又可以包含函式指標,如下面所示。

與相容對應的特定data例項

struct l2c_init_data {
	const char *type;
	unsigned way_size_0;
	unsigned num_lock;
	void (*of_parse)(const struct device_node *, u32 *, u32 *);
	void (*enable)(void __iomem *, u32, unsigned);
	void (*fixup)(void __iomem *, u32, struct outer_cache_fns *);
	void (*save)(void __iomem *);
	struct outer_cache_fns outer_cache;
};

通過這種方法,驅動可以把與某個裝置相容的私有資料尋找出來,如此體現了一種面向物件的設計思想,避免了大量的if, else或者switch, case語句。

2.4 裝置節點及label的命名  @@@@@@

程式碼2的.dts檔案中,根節點“/”的cpus子節點下面又包含兩個cpu子節點,描述了此裝置上的兩個CPU,並且兩者
的相容屬性為: "arm, cortex-a9"。

cpus,cpus的兩個cpu子節點的命名形式為<name>[@<unit-address>]

<>中的內容是必選項

[]中的則為可選項

name是一個ASCII字串,用於描述節點對應的裝置型別,如3com Ethernet介面卡對應的節點name宜為ethernet,而不3com509。

裝置的unit-address地址也經常在其對應節點的reg屬性中給出。

對於掛在記憶體空間的裝置而言, @字元後跟的一般就是該裝置在記憶體空間的基地址,譬如arch/arm/boot/dts/exynos4210.dtsi中存在的:

[email protected] {
    compatible = "mmio-sram";
    reg = <0x02020000 0x20000>;
}

但是也可以和reg中的地址不一樣,對於掛在I2C總線上的外設而言, @後面一般跟的是從裝置的I2C地址(不是實體地址),譬如arch/arm/boot/dts/exynos4210-trats.dts中的mms114-touchscreen:

[email protected] {
    [email protected] {  //I2C地址為0x48
        compatible = "melfas,mms114";
        reg = <0x48>;
    };
};

具體的節點命名規範可見ePAPR(embedded PowerArchitecture Platform Reference)標準,在https://www.power.org中可下載該標準。

我們還可以給一個裝置節點新增label,之後可以通過&label的形式訪問這個label,這種引用是通過phandle(pointer handle)進行的。

例如,第3組GPIO有gpio3這個label

程式碼12 在裝置樹中定義label

//在裝置樹中定義labe
gpio3: [email protected] {   //lable 為 gpio3
	compatible = "ti,omap4-gpio";
	reg = <0x48057000 0x200>;
	interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;
	ti,hwmods = "gpio3";
	gpio-controller;
	#gpio-cells = <2>;
	interrupt-controller;
	#interrupt-cells = <2>;
};

而hsusb2_phy這個USB的PHY復位GPIO用的是這組GPIO中的一個,所以它通過phandle引用了“gpio3”,如下面所示。

程式碼13:通過phandle引用其他節點

/* HS USB Host PHY on PORT 2 */
hsusb2_phy: hsusb2_phy {
	compatible = "usb-nop-xceiv";
	reset-gpios = <&gpio3 12 GPIO_ACTIVE_LOW>; /* gpio3_76 HUB_RESET*/
};

上面程式碼12的gpio3是[email protected]節點的label,而程式碼13的hsusb2_phy則通過&gpio3引用了這個節點,表明自己要使用這一組GPIO中的第12個GPIO。很顯然,這種phandle引用其實表明硬體之間的一種關聯性。

再舉一例,從下面例項可以看出, label習慣以<裝置型別><index>進行命名:

i2c1: [email protected] {
}
i2c2: [email protected] {
}
i2c3: [email protected] {
}

讀者也許發現了一個奇怪的現象,就是程式碼13中居然引用了GPIO_ACTIVE_LOW這個類似C語言的巨集。檔案.dts的編譯過程確實支援C的預處理,相應的.dts檔案也包括了包含GPIO_ACTIVE_LOW這個巨集定義的標頭檔案

#include <dt-bindings/gpio/gpio.h>

 

狀態:status

'status'屬性用來表示節點的狀態的,其實就是硬體的狀態,用字串表示。

  • ”okay“          表示硬體正常工作
  • “disabled”   表示硬體當前不可用
  • “fail”            表示因為出錯不可用
  • “fail-sss”     表示因為某種原因出錯不可用
  • “sss”           表示具體的出錯原因

實際中,基本只用'okay'和'disabled'。

2.5 地址編碼    @@@@@@@

父節點的#address-cells和#size-cells分別決定了子節點reg屬性中address和length佔多少單元。

  • 如:
  • #address-cells=<2> :用來表示地址的數佔用一個單元,則reg()中前兩個數表示地址
  • #sizecells=<1>         :用來表示地址的範圍的數佔用1個單元

第一種:#address-cells=<1>和#size-cells=<0>

如reg=<0>, reg=<1>

這裡的reg僅僅是編號

第二種:#address-cells=<1>和#size-cells=<1>

第一個單元:相對該片選的基地址

第二個單元:為length

第三種:#address-cells=<2>和#size-cells=<1>;

如reg=<0 0 0x1000>;、 reg=<1 0 0x1000>;和reg=<2 0 0x4000000>;。

每個reg共有三個單元:

第一個單元:對應的片選

第二個單元:相對該片選的基地址

第三個單元:為length

第四種:

#address-cells = <1>;

#size-cells = <0>;

          [email protected],0{

               reg = <0x48>;

          };

這裡是i2c裝置,reg的屬性值表示該i2c裝置地址

注意:地址編碼屬性只能對子節點起作用

根節點的直接子書點描述的是CPU的檢視,因此根子節點的address區域就直接位於CPU的記憶體區域。但是,經過匯流排橋後的address往往需要經過轉換才能對應CPU的記憶體對映。external-bus的ranges屬性定義了經過external-bus橋後的地址範圍如何對映到CPU的記憶體區域。

ranges = <0 0 0x10100000 0x10000     // Chipselect 1, Ethernet
          1 0 0x10160000 0x10000     // Chipselect 2, i2c controller
          2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash

ranges是地址轉換表,由一個子地址、父地址以及在子地址空間的大小的對映。

0 0 0x10100000 0x10000

第一個:片選0

第二個:偏移0

第三個:表示external-bus上片選0偏移0的地址空間被對映到CPU的本地匯流排的0x10100000位置

第四個:表示對映的大小為0x10000
 

2.6 中斷連線   @@@@@@

  • interrupt-controller
  • 一個空屬性用來宣告這個node接收中斷訊號,即這個node是一箇中斷控制器。
  • #interrupt-cells
  • 用來標識這個控制器需要幾個單位做中斷描述符,用來描述子節點中"interrupts"屬性使用了父節點中的interrupts屬性的具體的哪個值
  • 如果父節點的該屬性的值是3,則子節點的interrupts分別為:<中斷域 中斷 觸發方式>
  • 如果父節點的該屬性是2,則是<中斷 觸發方式>
  • interrupt-parent
  • 標識此裝置節點屬於哪一個中斷控制器,如果沒有設定這個屬性,會自動依附父節點的
  • interrupts
  • 一箇中斷識別符號列表,表示每一箇中斷輸出訊號

例項:

/ {
	compatible = "acme,coyotes-revenge";
	#address-cells = <1>;
	#size-cells = <1>;
	interrupt-parent = <&intc>;  //標識此裝置節點屬於中斷控制器“intc”
	[email protected] {
		compatible = "arm,pl061";
		reg = <0x101f3000 0x1000
		0x101f4000 0x0010>;
		interrupts = <0 3 1 >;
	};

	[email protected] {
		compatible = "arm,pl022";
		reg = <0x10115000 0x1000 >;
		interrupts = <0 4 2 >;
	};
};

GIC:中斷控制器

關於這幾個屬性的具體含義在  Documentation/devicetree/bindings/arm/gic.txt   中有詳細說明:

The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
interrupts.
The 2nd cell contains the interrupt number for the interrupt type.
SPI interrupts are in the range [0-987]. PPI interrupts are in the
range [0-15].
The 3rd cell is the flags, encoded as follows:
bits[3:0] trigger type and level flags.
        1 = low-to-high edge triggered
        2 = high-to-low edge triggered
        4 = active high level-sensitive
        8 = active low level-sensitive
bits[15:8] PPI interrupt cpu mask. Each bit corresponds to each of
the 8 possible cpus attached to the GIC. A bit set to '1' indicated
the interrupt is wired to that CPU. Only valid for PPI interrup

第一個單元格是中斷型別;SPI中斷為0,PPI為1中斷。
第二個單元格包含中斷型別的中斷號。SPI中斷在範圍內[0-987]。PPI中斷在範圍(0-15)。
第三個單元格是標誌,編碼如下:
bits[3:0] 觸發型別和等級標誌
        1 = 上升沿觸發
        2 = 下降沿觸發
        4 = 高電平觸發
        8 = 低電平觸發
位[15:8]PPI中斷cpu掩碼。每個位對應於每個位
連線到GIC的8個可能的cpu。位設定為“1”表示
中斷連線到那個CPU。僅對PPI互動有效
Example:

	[email protected] {
		compatible = "arm,cortex-a15-gic";
		#interrupt-cells = <3>;
		interrupt-controller;
		reg = <0x2c001000 0x1000>,
		      <0x2c002000 0x1000>,
		      <0x2c004000 0x2000>,
		      <0x2c006000 0x2000>;
		interrupts = <1 9 0xf04>;
	};

一個裝置可以有多箇中斷號,如:定義為interrupts=<0   168   4>, <0   169   4>;

由於系統的中斷號不夠用,於是核心發明了核心域的概覽,有中斷程序分層(父子層)

於是中斷源--interrupt parent-->GPIO--interrupt parent-->GIC1--interrupt parent-->GIC2--...-->CPU

詳細參考:https://www.cnblogs.com/xiaojiang1025/p/6131381.html

 

對於平臺裝置:

簡單的通過如下API就可以指定想取哪一個中斷,其中的引數num就是中斷的index。

int platform_get_irq(struct platform_device *dev, unsigned int num);

當然在.dts檔案中可以對中斷進行命名,而後在驅動中通過platform_get_irq_byname()來獲取對應的中斷號。譬如程式碼14演示了在drivers/dma/fsl-edma.c中通過platform_get_irq_byname()獲取IRQ,以及arch/arm/boot/dts/vf610.dtsi與fsl-edma驅動對應節點的中斷描述。
程式碼14 裝置樹中的中斷名稱以及驅動獲取中斷

static int
fsl_edma_irq_init(struct platform_device *pdev,struct fsl_edma_engine
{
	fsl_edma->txirq = platform_get_irq_byname(pdev, "edma-tx");
	fsl_edma->errirq = platform_get_irq_byname(pdev, "edma-err");
}

edma0: [email protected] {
	#dma-cells = <2>;
	compatible = "fsl,vf610-edma";
	reg = <0x40018000 0x2000>,
		<0x40024000 0x1000>,
		<0x40025000 0x1000>;
	interrupts = <0 8 IRQ_TYPE_LEVEL_HIGH>,
		<0 9 IRQ_TYPE_LEVEL_HIGH>;
	interrupt-names = "edma-tx", "edma-err";
	dma-channels = <32>;
	clock-names = "dmamux0", "dmamux1";
	clocks = <&clks VF610_CLK_DMAMUX0>,
		<&clks VF610_CLK_DMAMUX1>;
};

第4行、第5行的platform_get_irq_byname()的第2個引數與.dts中的interrupt-names是一致的。
 

2.7 GPIO、時鐘、 pinmux連線  @@@@@

1.GPIO

GPIO控制器:對應的裝置節點需宣告gpio-controller屬性,並設定#gpio-cells的大小,如下。

[email protected] {
	compatible = "fsl,imx28-pinctrl", "simple-bus";
	reg = <0x80018000 2000>;
	gpio0: [email protected] {
		compatible = "fsl,imx28-gpio";
		interrupts = <127>;
		gpio-controller;
		#gpio-cells = <2>;
		interrupt-controller;
		#interrupt-cells = <2>;
	};
	gpio1: [email protected] {
		compatible = "fsl,imx28-gpio";
		interrupts = <126>;
		gpio-controller;
		#gpio-cells = <2>;
		interrupt-controller;
		#interrupt-cells = <2>;
	};
};

GPIO:

  1. 命名:通過定義命名xxx-gpios屬性來引用GPIO控制器的裝置節點
  2. 第1個cell:GPIO號
  3. 第2個cell:GPIO的極性,為0的時候是高電平有效,為1的時候則是低電平有效。
[email protected] {
	status = "okay";
	cd-gpios = <&gpio01 0>;  //gpio1_0
	wp-gpios = <&gpio02 0>;  //gpio2_0
	power-gpios = <&gpio03 0>;  //gpio3_0
	bus-width = <4>;
};

而具體的裝置驅動則通過類似如下的方法來獲取GPIO:

cd_gpio = of_get_named_gpio(np, "cd-gpios", 0);
wp_gpio = of_get_named_gpio(np, "wp-gpios", 0);
power_gpio = of_get_named_gpio(np, "power-gpios", 0);

of_get_named_gpio()這個API的原型如下:

static inline int of_get_named_gpio(struct device_node *np,
                        const char *propname, int index);

在.dts和裝置驅動不關心GPIO名字的情況下,也可以直接通過of_get_gpio()獲取GPIO,此函式原型為:

static inline int of_get_gpio(struct device_node *np, int index);

如對於compatible="gpio-control-nand"的基於GPIO的NAND控制器而言,在.dts中會定義多個gpio屬性:

[email protected],0 {
	compatible = "gpio-control-nand";
	reg = <1 0x0000 0x2>;
	#address-cells = <1>;
	#size-cells = <1>;
	gpios = <&banka 1 0 /* rdy */
	&banka 2 0 /* nce */
	&banka 3 0 /* ale */
	&banka 4 0 /* cle */
	0 			/* nwp */>;
	[email protected] {
		//...
	};
};

在相應的驅動程式碼drivers/mtd/nand/gpio.c中是這樣獲取這些GPIO的:

plat->gpio_rdy = of_get_gpio(dev->of_node, 0);
plat->gpio_nce = of_get_gpio(dev->of_node, 1);
plat->gpio_ale = of_get_gpio(dev->of_node, 2);
plat->gpio_cle = of_get_gpio(dev->of_node, 3);
plat->gpio_nwp = of_get_gpio(dev->of_node, 4);

2.時鐘

時鐘和GPIO也是類似的,時鐘控制器的節點被使用時鐘的模組引用:

clocks = <&clks 138>, <&clks 140>, <&clks 141>;
clock-names = "uart", "general", "noc";

而驅動中則使用上述的clock-names屬性作為clk_get()或devm_clk_get()的第二個引數來申請時鐘,譬如獲取第2個時鐘:

devm_clk_get(&pdev->dev, "general");

<&clks 138>裡的138這個index是與相應時鐘驅動中clk的表的順序對應的,很多開發者也認為這種數字出現在裝置樹中不太好,因此他們把clk的index作為巨集定義到了arch/arm/boot/dts/include/dt-bindings/clock中。譬如include/dt-bindings/clock/imx6qdl-clock.h中存在這樣的巨集:
 

#define IMX6QDL_CLK_STEP 16
#define IMX6QDL_CLK_PLL1_SW 17
#define IMX6QDL_CLK_ARM 104

而arch/arm/boot/dts/imx6q.dtsi則是這樣引用它們的:

clocks = <&clks IMX6QDL_CLK_ARM>,
<&clks IMX6QDL_CLK_PLL2_PFD2_396M>,
<&clks IMX6QDL_CLK_STEP>,
<&clks IMX6QDL_CLK_PLL1_SW>,
<&clks IMX6QDL_CLK_PLL1_SYS>;

3.pinmux(引出線

在裝置樹中,某個裝置節點使用的pinmux的引腳群是通過phandle來指定的。譬如在arch/arm/boot/dts/atlas6.dtsi的pinctrl節點中包含所有引腳群的描述,如程式碼清單18.15所示。

程式碼15 裝置樹中pinctrl控制器的引腳群

gpio: [email protected] {
	#gpio-cells = <2>;
	#interrupt-cells = <2>;
	compatible = "sirf,atlas6-pinctrl";
	lcd_16pins_a: [email protected] {
		lcd {
			sirf,pins = "lcd_16bitsgrp";
			sirf,function = "lcd_16bits";
		};
	};

	spi0_pins_a: [email protected] {
		spi {
			sirf,pins = "spi0grp";
			sirf,function = "spi0";
		};
	};
	spi1_pins_a: [email protected] {
		spi {
			sirf,pins = "spi1grp";
			sirf,function = "spi1";
		};
	};
};

而SPI0這個硬體實際上需要用到spi0_pins_a對應的spi0grp這一組引腳,因此在atlas6-evb.dts中通過pinctrl-0引用了它,如下面所示。

[email protected] {
	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&spi0_pins_a>;
};

到目前為止,我們可以勾勒出一個裝置樹的全域性檢視,圖18.2顯示了裝置樹中的節點、屬性、 label以及phandle等資訊。

 

3 由裝置樹引發的BSP和驅動變更   ########

有了裝置樹後,不再需要大量的板級資訊,譬如過去經常在arch/arm/plat-xxx和arch/arm/mach-xxx中實施如下事情。

1.註冊platform_device,繫結resource,即記憶體、 IRQ等板級資訊

通過裝置樹後,形如:

static struct resource xxx_resources[] = {
	[0] = {
		.start = …,
		.end = …,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = …,
		.end = …,
		.flags = IORESOURCE_IRQ,
	},
};
static struct platform_device xxx_device = {
	.name = "xxx",
	.id = -1,
	.dev = {
		.platform_data = &xxx_data,
	},
	.resource = xxx_resources,
	.num_resources = ARRAY_SIZE(xxx_resources),
};

之類的platform_device程式碼都不再需要,其中platform_device會由核心自動展開。

而這些resource實際來源於.dts中裝置節點的reg、 interrupts屬性。

典型的,大多數匯流排都與“simple_bus”相容,而在與SoC對應的裝置的.init_machine成員函式中,呼叫

of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);

即可自動展開所有的platform_device。


2.註冊i2c_board_info,指定IRQ等板級資訊

形如:

static struct i2c_board_info __initdata afeb9260_i2c_devices[] = {
	{
		I2C_BOARD_INFO("tlv320aic23", 0x1a),
	}, {
		I2C_BOARD_INFO("fm3130", 0x68),
	}, {
		I2C_BOARD_INFO("24c64", 0x50),
	},
};

之類的i2c_board_info程式碼目前不再需要出現,現在只需要把tlv320aic23、 fm3130、 24c64這些裝置節點填充作為相應的控制器節點的子節點即可,類似於前面的程式碼:

[email protected],0 {
	compatible = "acme,a1234-i2c-bus";
	[email protected] {
		compatible = "maxim,ds1338";
		reg = <58>;
		interrupts = < 7 3 >;
	};
};

裝置樹中的I2C客戶端會通過在I2C host驅動的probe()函式中呼叫的of_i2c_register_devices(&i2c_dev->adapter);被自動展開。


3.註冊spi_board_info,指定IRQ等板級資訊

形如:

static struct spi_board_info afeb9260_spi_devices[] = {
	{ /* DataFlash chip */
		.modalias = "mtd_dataflash",
		.chip_select = 1,
		.max_speed_hz = 15 * 1000 * 1000,
		.bus_num = 0,
	},
};

之類的spi_board_info程式碼目前不再需要出現,與I2C類似,現在只需要把mtd_dataflash之類的節點作為SPI控制器的子節點即可, SPI host驅動的probe()函式通過spi_register_master()註冊主機的時候,會自動展開依附於它的從機, spear1310-evb.dts中的st, m25p80SPI介面的NOR Flash節點如下:

spi0: [email protected] {
	status = "okay";
	num-cs = <3>;
	[email protected] {
		compatible = "st,m25p80";
	};
}

4.多個針對不同電路板的裝置,以及相關的回撥函式

在過去, ARM Linux針對不同的電路板會建立由MACHINE_START和MACHINE_END包圍的裝置,引入裝置樹之後, MACHINE_START變更為DT_MACHINE_START,其中含有一個.dt_compat成員,用於表明相關的裝置與.dts中根節點的相容屬性的相容關係。

這樣可以顯著改善程式碼的結構並減少冗餘的程式碼,在不支援裝置樹的情況下,光是一個S3C24xx就存在多個板檔案,

譬如mach-amlm5900.c、 mach-gta02.c、 mach-smdk2410.c、 machqt2410.c、 mach-rx3715.c等,其累計的程式碼量是相當大的,板級資訊都用C語言來實現。而採用裝置樹後,我們可以對多個SoC和板子使用同一個DT_MACHINE和板檔案,板子和板子之間的差異更多隻是通過不同的.dts檔案來體現。


5.裝置與驅動的匹配方式

使用裝置樹後,驅動需要與在.dts中描述的裝置節點進行匹配,從而使驅動的probe()函式執行。新的驅動、裝置的匹配變成了裝置樹節點的相容屬性和裝置驅動中的OF匹配表的匹配。


6.裝置的平臺數據屬性化

在Linux 2.6下,驅動習慣自定義platform_data,在arch/arm/mach-xxx註冊platform_device、 i2c_board_info、spi_board_info等的時候繫結platform_data,而後驅動通過標準API獲取平臺數據。譬如,在arch/arm/mach-at91/board-sam9263ek.c下用如下程式碼註冊gpio_keys裝置,它通過gpio_keys_platform_data結構體來定義platform_data。

static struct gpio_keys_button ek_buttons[] = {
	{ /* BP1, "leftclic" */
		.code = BTN_LEFT,
		.gpio = AT91_PIN_PC5,
		.active_low = 1,
		.desc = "left_click",
		.wakeup = 1,
	},
	{ /* BP2, "rightclic" */
		//...
	}
};
static struct gpio_keys_platform_data ek_button_data = {
	.buttons = ek_buttons,
	.nbuttons = ARRAY_SIZE(ek_buttons),
};
static struct platform_device ek_button_device = {
	.name = "gpio-keys",
	.id = -1,
	.num_resources = 0,
	.dev = {
		.platform_data= &ek_button_data,
	}
};

裝置驅動drivers/input/keyboard/gpio_keys.c則通過如下簡單方法取得這個資訊。

static int gpio_keys_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
	//...
}

在轉移到裝置樹後, platform_data便不再喜歡放在arch/arm/mach-xxx中了,它需要從裝置樹的屬性中獲取,比如一個電路板上有gpio_keys,則只需要在裝置樹中新增類似arch/arm/boot/dts/exynos4210-origen.dts中的如程式碼17所示的資訊則可。
程式碼17 在裝置樹中新增GPIO按鍵資訊

gpio_keys {
	compatible = "gpio-keys";
	#address-cells = <1>;
	#size-cells = <0>;

	up {
		label = "Up";
		gpios = <&gpx2 0 1>;
		linux,code = <KEY_UP>;
		gpio-key,wakeup;
	};

	down {
		label = "Down";
		gpios = <&gpx2 1 1>;
		linux,code = <KEY_DOWN>;
		gpio-key,wakeup;
	};
	//...
};

而drivers/input/keyboard/gpio_keys.c則通過以of_開頭的讀屬性的API來讀取這些資訊,並組織出gpio_keys_platform_data結構體,如程式碼18所示。
程式碼18 在GPIO按鍵驅動中獲取.dts中的鍵描述

static struct gpio_keys_platform_data *gpio_keys_get_devtree_pdata(struct device *dev)
{
	struct device_node *node, *pp;
	struct gpio_keys_platform_data *pdata;
	struct gpio_keys_button *button;
	int error;
	int nbuttons;
	int i;

	node = dev->of_node;
	if (!node)
		return ERR_PTR(-ENODEV);

	nbuttons = of_get_child_count(node);
	if (nbuttons == 0)
		return ERR_PTR(-ENODEV);

	pdata = devm_kzalloc(dev, sizeof(*pdata) + nbuttons * sizeof(*butt), GFP_KERNEL);
	if (!pdata)
		return ERR_PTR(-ENOMEM);

	pdata->buttons = (struct gpio_keys_button *)(pdata + 1);
	pdata->nbuttons = nbuttons;

	pdata->rep = !!of_get_property(node, "autorepeat", NULL);

	i = 0;
	for_each_child_of_node(node, pp) {
		int gpio;
		enum of_gpio_flags flags;
		if (!of_find_property(pp, "gpios", NULL)) {
			pdata->nbuttons--;
			dev_warn(dev, "Found button without gpios\n");
			continue;
		}

		gpio = of_get_gpio_flags(pp, 0, &flags);
		if (gpio < 0) {
			error = gpio;
			if (error != -EPROBE_DEFER){
				dev_err(dev,"Failed to get gpio flags", ererror);
			}
			return ERR_PTR(error);
		}

		button = &pdata->buttons[i++];

		button->gpio = gpio;
		button->active_low = flags & OF_GPIO_ACTIVE_LOW;

		if (of_property_read_u32(pp, "linux,code", &button->code)){
			dev_err(dev, "Button without keycode: 0x%x\n", button->gpio);
			return ERR_PTR(-EINVAL);
		}

		button->desc = of_get_property(pp, "label", NULL);

		if (of_property_read_u32(pp, "linux,input-type", &button->type)
			button->type = EV_KEY;

		button->wakeup = !!of_get_property(pp, "gpio-key,wakeup", NULL);

		if (of_property_read_u32(pp, "debounce-interval",&button->debounce_interval))
			button->debounce_interval = 5;
	}

	if (pdata->nbuttons == 0)
		return ERR_PTR(-EINVAL);

	return pdata;
}

上述程式碼通過第31行的for_each_child_of_node()遍歷gpio_keys節點下的所有子節點,並通過of_get_gpio_flags()、of_property_read_u32()等API讀取出來與各個子節點對應的GPIO、與每個GPIO對應的鍵盤鍵值等。

4 常用的OF API

關於裝置樹的API通常被冠以of_字首,它們的實現程式碼位於核心的drivers/of/目錄下。

1.尋找節點

struct device_node *of_find_compatible_node(struct device_node *from,
                          const char *type, const char *compatible);

根據相容屬性,獲得裝置節點。遍歷裝置樹中的裝置節點,看看哪個節點的型別、相容屬性與本函式的輸入引數匹配,在大多數情況下, from、 type為NULL,則表示遍歷了所有節點。


2.讀取屬性

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中,通過如下語句可讀取
L2cache的"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"屬性的L2cache節點如下:

L2: [email protected] {
	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中,如代
碼19所示。
程式碼19 裝置樹中整型屬性的讀取API

static inline int of_property_read_u8(const struct device_node *np,
	const char *propname, u8 *out_value)
{
	return of_property_read_u8_array(np, propname, out_value, 1);
}

static inline int of_property_read_u16(const struct device_node *np,
	const char *propname, u16 *out_value)
{
	return of_property_read_u16_array(np, propname, out_value, 1)
}

static inline int of_property_read_u32(const struct device_node *np,
	const char *propname, u32 *out_value)
{
	return of_property_read_u32_array(np, propname, out_value, 1)
}

除了整型屬性外,字串屬性也比較常用,其對應的API
包括:

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);

如:test_list_string = "red fish", "fly fish", "blue, fish";

首先系統會把這幾個字串合在一起,對應節點屬性的value成員,這個  value  是  void指標

前者是把value直接賦值給   *out_string,而裡面是多個字串組合而成,還要自己去分開

後者是把value分成單個字串,你要第index個就給你第index個的地址

如drivers/clk/clk.c中的

of_clk_get_parent_name()函式就通過
of_property_read_string_index()遍歷clkspec節點的所有"clock-output-names"字串陣列屬性。

程式碼20 在驅動中讀取第index個字串的例子

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);

3.記憶體對映

void __iomem *of_iomap(struct device_node *node, int index);

上述API可以直接通過裝置節點進行裝置記憶體區間的ioremap(), index是記憶體段的索引。若裝置節點的reg屬性有多段,可通過index標示要ioremap()的是哪一段,在只有1段的情況, index為0。採用裝置樹後,一些裝置驅動通過of_iomap()而不再通過傳統的ioremap()進行對映,當然,傳統的ioremap()的使用者也不少。

int of_address_to_resource(struct device_node *dev, int index,
    struct resource *r);

上述API通過裝置節點獲取與它對應的記憶體資源的resource結構體。其本質是分析reg屬性以獲取記憶體基地址、大小等資訊並填充到struct resource*r引數指向的結構體中。
 

4.解析中斷

unsigned int irq_of_parse_and_map(struct device_node *dev, int index);

通過裝置樹獲得裝置的中斷號,實際上是從.dts中的interrupts屬性裡解析出中斷號。若裝置使用了多箇中斷, index指定中斷的索引號。


5.獲取與節點對應的platform_device

struct platform_device *of_find_device_by_node(struct device_node *np);

在可以拿到device_node的情況下,如果想反向獲取對應的platform_device,可使用上述API。當然,在已知platform_device的情況下,想獲取device_node則易如反掌,例如:

static int sirfsoc_dma_probe(struct platform_device *op)
{
    struct device_node *dn = op->dev.of_node;
}

5 裝置和驅動之間如何匹配

當我們在DTS檔案中寫了一個裝置節點,如

[email protected]{
		compatible = "farsight,tst";
		reg = <0x12345678 0x12
		       0x87654321 0x34>;
		tesprop,mytest;
		test_list_string = "red fish", "fly fish", "blue, fish";
		interrupts = <&gpx1 2 3>,  //對應gpx1_2 ,上升沿和下降沿觸發
			     <&gpx1 3 3>, //對應gpx1_3 ,上升沿和下降沿觸發
			     <&gpx3 2 3>; //對應gpx3_2 ,上升沿和下降沿觸發
}

就會生成一個platform_device,最後用compatible進行匹配

使用裝置樹後,驅動需要與.dts中描述的裝置節點進行匹配,從而使驅動的probe()函式執行。對於platform_driver而言,需要新增一個OF匹配表,如前文的.dts檔案的"acme,a1234-i2c-bus"相容I2C控制器節點的OF匹配表,

struct of_device_id
{
	char	name[32];
	char	type[32];
	char	compatible[128];
#ifdef __KERNEL__
	void	*data;
#else
	kernel_ulong_t data;
#endif
};

static const struct of_device_id a1234_i2c_of_match[] = {
	{
		.compatible = "acme,a1234-i2c-bus", 
	},
	{},
};
MODULE_DEVICE_TABLE(of, a1234_i2c_of_match);

static struct platform_driver i2c_a1234_driver = {
	.driver = {
		.name = "a1234-i2c-bus",
		.owner = THIS_MODULE,
		.of_match_table = a1234_i2c_of_match,
	},
	.probe = i2c_a1234_probe,
	.remove = i2c_a1234_remove,
};
module_platform_driver(i2c_a1234_driver);

對於I2C和SPI從裝置而言,同樣也可以通過of_match_table新增匹配的.dts中的相關節點的相容屬性,如sound/soc/codecs/wm8753.c中的針對WolfsonWM8753的of_match_table,具體如程式碼清單18.7所示。

static const struct of_device_id wm8753_of_match[] = {
	{ .compatible = "wlf,wm8753", },
	{ }
};
MODULE_DEVICE_TABLE(of, wm8753_of_match);
static struct spi_driver wm8753_spi_driver = {
	.driver = {
		.name = "wm8753",
		.owner = THIS_MODULE,
		.of_match_table = wm8753_of_match,
	},
	.probe = wm8753_spi_probe,
	.remove = wm8753_spi_remove,
};
static struct i2c_driver wm8753_i2c_driver = {
	.driver = {
		.name = "wm8753",
		.owner = THIS_MODULE,
		.of_match_table = wm8753_of_match,
	},
	.probe = wm8753_i2c_probe,
	.remove = wm8753_i2c_remove,
	.id_table = wm8753_i2c_id,
};

第2行顯示WM8753的供應商是“wlf”,它其實是對應於Wolfson Microe-lectronics的字首。

裝置樹繫結供應商字首登錄檔

這不是一個詳盡的列表,但是您應該在它之前新增新的字首使用它們來避免名稱空間衝突。

位於核心文件: Documentation/devicetree/bindings/vendorprefixes.txt

注意: I2C和SPI外設驅動和裝置樹中裝置節點的相容屬性還有一種弱式匹配方法,就是“別名”匹配。