1. 程式人生 > 其它 >Linux驅動 | 從0寫一個裝置樹節點例項

Linux驅動 | 從0寫一個裝置樹節點例項

一、前言

裝置樹是每一個Linux驅動工程師都必須掌握的一個知識點,有很多之前做微控制器的朋友剛接觸Linux驅動時,會一臉懵!

其實裝置樹的使用並沒有大家想像的那麼複雜,對於大部分工程師來說,只要會修改即可。

很多粉絲留言說,希望彭老師提供一個裝置樹到驅動解析的例項。

必須安排!

在學習裝置樹之前,大家一定要搞清楚什麼是platform匯流排,請詳細學習下面這篇文章:

手把手教Linux驅動10-platform匯流排詳解

關於裝置樹理論部分內容請學習下面這篇文章:

手把手教linux驅動11-linux裝置驅動統一模型

關於驅動基礎文章,可以去B站學習一口君的入門視訊:

從學Linux驅動入門視訊

https://www.bilibili.com/video/BV1d5411A7VJ?spm_id_from=333.999.0.0

有了這些基礎知識後,我們就可以來編寫一個裝置樹的例項,

下面彭老師就給大家講解如何自己新增一個裝置樹節點,並如何在驅動中提取出裝置樹的資訊。

老規矩,程式碼從0開始編寫,並且全部驗證通過,並分享給大家。

二、測試平臺

本次測試在開發板上操作,操作環境如下:

1. 編譯環境

ubuntu 16.04

2. 交叉編譯工具

root@ubuntu:/home/peng/linux-3.14# arm-none-linux-gnueabi-gcc -v
Using built-in specs.
COLLECT_GCC=arm-none-linux-gnueabi-gcc
COLLECT_LTO_WRAPPER=/home/peng/toolchain/gcc-4.6.4/bin/../libexec/gcc/arm-arm1176jzfssf-linux-gnueabi/4.6.4/lto-wrapper
Target: arm-arm1176jzfssf-linux-gnueabi
………………
gcc version 4.6.4 (crosstool-NG hg+default-2685dfa9de14 - tc0002)

3. 開發板

開發板:fs4412
soc:exynos4412

4. 核心版本

Linux kernel 3.14.0

三、核心解析裝置樹一般過程

  1. 系統啟動後,uboot會從網路或者flash、sd卡中讀取裝置樹檔案(具體由uboot命令給出),

  2. 引導linux核心啟動後,會把裝置樹映象儲存到的記憶體地址傳遞給Linux核心,Linux核心會解析裝置樹映象,從裝置樹中提取硬體資訊並逐一初始化。

  3. 其中裝置樹資訊會被轉換成struct platform_device型別變數。

  4. 而驅動要解析裝置樹,必須定義 struct platform_driver型別結構體變數,並通過函式platform_driver_register()

    註冊。

  5. 這兩者都會註冊到platform匯流排,當驅動和裝置樹節點匹配成功後,就呼叫 struct platform_driver.probe方法。

其中裝置樹節點會封裝在struct device_node 結構體變數中
各個屬性資訊會封裝在 struct property結構體變數中,
他們與struct platform_device結構體之間關係如下:

四、驅動架構

以下是一口君編寫的驅動架構,

我們只需要將測試程式碼填充到hello_probe()中即可:

static int hello_probe(struct platform_device *pdev)
{
	printk("match ok \n");
	
//解析程式碼編寫
	return 0;
}
static 	int hello_remove(struct platform_device *pdev)
{
	printk("hello_remove \n");
	return 0;
}
static struct of_device_id beep_table[] = {
		{.compatible = "yikoulinux"},
};
static struct platform_driver hello_driver =
{
	.probe = hello_probe,
	.driver.name = "duang",
	.remove = hello_remove,
	.driver = {
		.name = "yikoupeng",
		.of_match_table = beep_table,
	},
};
static int hello_init(void)
{
	printk("hello_init \n");
	return platform_driver_register(&hello_driver);
}
static void hello_exit(void)
{
	printk("hello_exit \n");
	platform_driver_unregister(&hello_driver);
	return;
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);

五、裝置樹節點

下面是給出的裝置樹資訊:

	yikou_node{
        compatible = "yikoulinux";
        reg = <0x114000a0 0x4 0x139D0000 0x20>;
        reg-names = "peng";
        interrupt-parent=<&gpx1>;
        interrupts =<1 2>,<2  2>;
    
        csm_gpios=<&gpx2 3 0 &gpx2 4 0 &gpx2 5 0 &gpx2 6 0>;
        
        crl0_gpio=<&gpx0 5 0>;
        crl1_gpio=<&gpx0 6 0>;
        rst_gpio=<&gpx0 7 0>;
        cfg_gpio=<&gpx0 4 0>;
		
		phy_ref_freq = <26000>;  /* kHz */		
		suspend_poweroff;
		
		clock-names = "xusbxti",
			"otg";
		yikou_node {
			compatible = "leadcore,dsi-panel";
			panel_name = "lcd_rd_rm67295";
			refresh_en = <1>;
			bits-per-pixel = <32>;	
		};
    };

其中包括常見reg、中斷、整型值、bool值、字串、子節點、時鐘等屬性。

一定要注意,很多屬性的給出會因為使用的SOC平臺的不同有所差異,
下面介紹下GPIO和中斷編寫原理:

1. GPIO

gpio資訊的給出有以下兩種方法:

 	csm_gpios=<&gpx2 3 0 &gpx2 4 0 &gpx2 5 0 &gpx2 6 0>;
	crl0_gpio=<&gpx0 5 0>;
	crl1_gpio=<&gpx0 6 0>;
	rst_gpio=<&gpx0 7 0>;
	cfg_gpio=<&gpx0 4 0>;

第1種是公用同一個名字,第2種是每一個gpio單獨使用1個名字。

gpio需要指明父節點,關於gpio父節點的說明下說明文件(通常linux-3.14\Documentation下有關於該核心版本的一些模組說明,很重要):

linux-3.14\Documentation\devicetree\bindings\gpio.txt
For example, the following could be used to describe gpios pins to use
as chip select lines; with chip selects 0, 1 and 3 populated, and chip
select 2 left empty:

	gpio1: gpio1 {
		gpio-controller
		 #gpio-cells = <2>;
	};
	gpio2: gpio2 {
		gpio-controller
		 #gpio-cells = <1>;
	};
	[...]
	 chipsel-gpios = <&gpio1 12 0>,
			 <&gpio1 13 0>,
			 <0>, /* holes are permitted, means no GPIO 2 */
			 <&gpio2 2>;
Note that gpio-specifier length is controller dependent.  In the
above example, &gpio1 uses 2 cells to specify a gpio, while &gpio2
only uses one.

gpio-specifier may encode: bank, pin position inside the bank,
whether pin is open-drain and whether pin is logically inverted.
Exact meaning of each specifier cell is controller specific, and must
be documented in the device tree binding for the device.

Example of the node using GPIOs:

	node {
		gpios = <&qe_pio_e 18 0>;
	};

In this example gpio-specifier is "18 0" and encodes GPIO pin number,
and empty GPIO flags as accepted by the "qe_pio_e" gpio-controller.

翻譯總結成如下幾點:

  1. gpio父節點需要包含屬性
	gpio-controller、    表示是gpi控制器
	#gpio-cells = <2>;    表示子節點包括2個屬性
  1. 對於子節點是2個屬性的函式
    比如:
	gpios = <&qe_pio_e 18 0>;

父節點是qe_pio_e
其中18表示GPIO pin值,就是gpio下面管理的pin腳序號,該pin值一般就需要查詢使用者手冊&電路圖。

2. 中斷

中斷屬性節點如下:

        interrupt-parent=<&gpx1>;
        interrupts =<1 2>,<2  2>;

其中

interrupt-parent=<&gpx1>;: 該中斷訊號所述的中斷控制器
interrupts =<1 2>,<2  2>;  :描述中斷屬性,其中<>中第一個值表示該中斷所述中斷控制器index,第二個值表示中斷觸發方式

中斷子節點格式如下:

linux-3.14\Documentation\devicetree\bindings\gpio.txt
Example of a peripheral using the GPIO module as an IRQ controller:

	funkyfpga@0 {
		compatible = "funky-fpga";
		...
		interrupt-parent = <&gpio1>;   #父節點
		interrupts = <4 3>;     #節點屬性
	};

中斷子節點說明文件如下:

linux-3.14\Documentation\devicetree\bindings\interrupt-controller\interrupts.txt
  b) two cells
  ------------
  The #interrupt-cells property is set to 2 and the first cell defines the
  index of the interrupt within the controller, while the second cell is used
  to specify any of the following flags:
    - 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          低電平有效

我們所填寫的中斷父節點gpx1定義如下(該檔案由三星廠家出廠定製好):

linux-3.14\arch\arm\boot\dts\exynos4x12-pinctrl.dtsi
		gpx1: gpx1 {
			gpio-controller;        #gpio控制器
			#gpio-cells = <2>;      #子節點有2個屬性

			interrupt-controller;  #中斷控制器
			interrupt-parent = <&gic>;    #父節點gic
			interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,   #子節點屬性約束
				     <0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;
			#interrupt-cells = <2>;
		};

可見三星的exynos4412平臺中gpx1,既可以做gpio控制器又可以做中斷控制器,而gpx1作為中斷控制器則路由到gic上。
其中interrupts 屬性說明如下:

linux-3.14\Documentation\devicetree\bindings\arm\gic.txt
Main node required properties:

- compatible : should be one of:
	"arm,gic-400"
	"arm,cortex-a15-gic"
	"arm,cortex-a9-gic"
	"arm,cortex-a7-gic"
	"arm,arm11mp-gic"
- interrupt-controller : Identifies the node as an interrupt controller
- #interrupt-cells : Specifies the number of cells needed to encode an
  interrupt source.  The type shall be a <u32> and the value shall be 3.

  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 interrupts.

翻譯總結:

interrupts = <0 24 0>
  1. 第1個0 表示該中斷是SPI型別中斷,如果是1表示PPI型別中斷
  2. 24表示中斷號(通過查詢電路圖和datasheet獲得)
  3. 第三個0表示中斷觸發方式

再強調一遍
不同的平臺gpio、中斷控制器管理可能不一樣,所以填寫方法可能會有差異,不可教條

六、驅動提取裝置樹資訊方法

驅動解析程式碼與裝置樹節點之間關係如下,程式碼與屬性用相同顏色框出:
of開頭的函式請參考《手把手教linux驅動11-linux裝置驅動統一模型》

七、編譯(ubuntu中操作)

驅動編譯:
注意,核心必須提前編譯好

裝置樹編譯:

編譯裝置樹命令,各個廠家的SDK都不盡相同,本例製作參考。

除此之外驅動模組檔案、裝置樹檔案如何匯入給開發板,差別也比較大,本文不再給出步驟。

八、載入模組(開發板上操作)

載入模組後執行結果如下:

[root@peng test]# insmod driver.ko 
[   26.880000] hello_init 
[   26.880000] match ok 
[   26.880000] mem_res1 : [0x114000a0]  mem_res2:[0x139d0000] 
[   26.885000] irq_res1 : [168]  irq_res2:[169] 
[   26.890000] mem_resp:[114000a0]
[   26.890000] 
[   26.895000] phy_ref_freq:26000
[   26.900000] suspend_poweroff [true]
[   26.900000] suspend_poweroff_test [false]
[   26.900000] 
[   26.905000] csm_gpios :[231][232][233][234]
[   26.910000] CTL0:[217] CTL1:[218] RST:[219] CFG:[216]
[   26.915000] bits_per_pixel:32
[   26.920000] panel_name:lcd_rd_rm67295
[   26.925000] refresh_en [true]

其中列印的資訊就是最終我們解析出的裝置樹裡的硬體資訊,
我們就可以根據這些資訊進行相關資源申請、初始化。

同時裝置樹中的資訊,會以檔案節點形式建立在一下目錄中:

完整程式碼請關注公眾號:一口Linux,後臺回覆裝置樹dt

歡迎關注公眾號:一口Linux