系列文章目錄
第一章 Linux 中核心與驅動程式
第二章 Linux 裝置驅動編寫 (misc)
第三章 Linux 裝置驅動編寫及裝置節點自動生成 (cdev)
第四章 Linux 平台總線platform與裝置樹
第五章 Linux 裝置樹中pinctrl與gpio(lichee nano pi)
文章目錄
- 系列文章目錄
- 前言
- 一、平台總線
-
- 1.1 平台總線簡介
- 1.2 以平台總線模型設計的驅動
-
- 1.2.1 注冊device
- 1.2.2 注冊driver
- 二、裝置樹
-
- 1.基本文法
- 2.執行個體講解
前言
如果你在學習Linux 裝置驅動程式設計過程中,如果你一上來直接就去看裝置樹,或者去看别人寫的一些驅動代碼,你可能會迷惑:什麼是prob函數?device.c和driver.c的差別和聯系?為什麼要用裝置樹?
一、平台總線
1.1 平台總線簡介
1.平台總線定義
平台總線模型也叫platform總線模型。是 Linux核心虛拟出來的一條總線,不是真實的導線。
平台總線模型就是把原來的驅動C檔案給分成了倆個C檔案,一個是device.c,一個是driver.c
把穩定不變的放在driver.c裡面,需要變得就放在了device.c裡面。
2.為什麼會有平台總線模型?
(1)可以提高代碼的重用性(2)減少重複性代碼。
裝置 總線 I驅動
device.c driver.c
3.怎麼編寫以平台總線模型設計的驅動?
一個是device.c,一個是driver.c,然後分别注冊device.c和driver.c。平台總線是以名字來比對的,實際上就是字元串比較。
1.2 以平台總線模型設計的驅動
1.2.1 注冊device
device.c裡面寫的是硬體資源,這裡的硬體資源是指寄存器的位址,中斷号,時鐘等硬體資源。
在Linux 核心裡面,我們是用一個結構體來描述硬體資源的。
struct platform_device {
const char *name; //平台總線進行比對的時候用到的name,/sys/bus..
int id; //裝置id,一般寫-1
struct device dev; //内嵌的device結構體
{
u32 num_resources; //資源的個數
struct resource *resource; //device裡面的硬體資源。
};
};
現在給出一個例子,當中的描述的是蜂鳴器的資源,根據每個開發闆的具體情況進行更改。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
struct resource beep_res = {
[0]={
.start = 0x2OACO00,
.end = 0x2OAC083,
.flags = IORESOURCE_MEM,
.name = "GPIO5_DR",
};
};
struct platform_device beep_device = {;
.name = "beep_test",
.id = -1,
.resource =beep_res,
.num_resources =ARRAY_SIZE(beep_res),
}
static int device_init(void)
{
printk(KERN_ALERT "device_init");
return platform_device_register (&beep_device);
}
static void device_exit(void)
{
printk(KERN_ALERT "device_exit");
return platform_device_unregister (&beep_device);
}
module_init( device_init );
module_exit( device_exit );
MODULE_LICENSE("Dual BSD/GPL");
1.2.2 注冊driver
現在來編寫driver.c
struct platform_driver {
int (*probe)(struct platform_device *);//當driver和device 比對成功的時候,就會執行probe函數
int (*remove)(struct platform_device *);//當driver和device任意一個remove的時候,就會執行這個函數
void (*shutdown)(struct platform_device *);//當裝置收到shutdown指令的時候,就會執行這個函數
int (*suspend)(struct platform_device *. pm_message_t state);//當裝置收到suspend指令的時候,就會執行這個函數
int (*resume)(struct platform_device *);//當裝置收到resume指令的時候,就會執行這個函數
struct device_driver driver;
conststruct patrorm_device_id *id_table;
bool prevent_deferred_probe;
};
struct device_driver {
const char *name;//這個是我們比對裝置時候用到的名字
struct module *owner;
};
代碼如下
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
int beep_probe(struct platform_device *pdev)
{
printk( "beep_probe\n");
return 0;
}
int beep_remove(struct platform_device *pdev)
{
printk( "beep_remove\n");
return 0;
}
struct platform_driver beep_driver{
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.owner = THIS_MODULE,
.name = "beep_test"
}
//const struct platform_device_id *id_table;
};
static int driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&beep_driver);
if(ret < 0){
printk( "platform_driver_register error \n");
return ret;
}
printk(KERN_ALERT "driver_init");
return 0;
}
}
static void device_exit(void)
{
platform_driver_unregister(&beep_driver);
printk(KERN_ALERT "driver_exit");
}
module_init( driver_init );
module_exit( driver_exit );
MODULE_LICENSE("Dual BSD/GPL");
device.c和driver.c是通過name來比對的,當兩者都insmod之後,如果兩者比對的話,會進入driver.c當中的prob函數。重點變成都在prob函數中。我們可以通過of系列函數獲得device中的硬體資源,具體的prob函數編寫現在不在詳細講解。
以上就将硬體資訊和相同的驅動代碼分割開了。
平台總線platfrom解決了問題,當我們在另一個開發闆上要運作相同的驅動,隻需要改改引腳就可以了。
但是又有個問題出現了,一個開發闆上肯定不止有一個硬體、一個驅動。一個驅動寫一個device.c去描寫其硬體資源,那麼好多就顯得很備援,因為device.c和driver.c不同,它的結構很固定。那麼有沒有一種方法将開發闆的你需要的硬體資源都彙總到一個檔案?
裝置樹就出現了。
二、裝置樹
1.基本文法
1節點
什麼是節點呢?節點就好比一顆大樹,從樹的主幹開始,然後有一節一節的樹枝,這個就叫節點。在代碼中的節點是什麼樣子的呢。我們把上面模闆中的根節點摘出來,如下圖所示,這個就是根節點。相當于大樹的樹幹。
/{
};//分号
而樹枝就相當于裝置樹的子節點,同樣我們把子節點摘出來就是根節點裡面的node1和node2,如下圖所示:
/{ //根節點
node1//子節點node1
{
}
node2//子節點node2
{
};
};//分号
一個樹枝是不是也可以繼續分成好幾個樹枝呢,也就是說子節點裡面可以包含子子節點。是以child-node1和child-node2是node1和node1的子節點,如下圖所示:
{//根節點
node1//子節點node1
{
child-node1//子子節點
{
};
};
};
node2//子節點node2
{
};
看裝置樹檔案要從上往下看,從左往右看
2節點名稱
節點的命名有一個固定的格式。
格式:<名稱>[@<裝置位址>]
<名稱>節點的名稱也不是任意起的,一般要展現裝置的類型而不是特點的型号,比如網口,應該命名為ethernet,而不是随意起一個,比如111。
<裝置位址>就是用來通路該裝置的基位址。但并不是說在操作過程中來描述一個位址,他主要用來區分用。
注意事項:
<1>同一級的節點隻要位址不一樣,名字是可以不唯一的。
<2>裝置位址是一個可選選項,可以不寫。但為了容易區分和了解,一般是都寫的。
3節點别名
當我們找一個節點的時候,我們必須書寫完整的節點路徑,如果我們的節點名很長,那麼我們在引用的時候就十分不友善,是以,裝置樹允許我們用下面的形式為節點标注引用(起别名)。
pio: [email protected]
4節點引用
要對子節點進行更改時我們一般不在根節點裡面進行更改,而是在外面。通過引用的方式對其進行更改。
/ {
pio:[email protected]{
};
};
&pio{
//通過&引用,在這裡進行更改
};
5
一些屬性的講解在此不做贅述。連結: link
2.執行個體講解
例1
Q:有的父節點有compatible屬性,位元組點也有自己的compatible,那麼該比對那個呢?A:用你那個節點就在驅動中與之比對。
Q:這樣看來父子節點之間好像沒什麼關系,A:我的了解是:父節點中定義#address-cells 與#size-cells這樣就可以完成一類的位元組點的編寫,有共性。
例2裝置樹中不僅要看該裝置樹中的根節點,還要從頭檔案中找到根節點,這兩個檔案中的根節點運作的時候會合并起來。就像下面的例子中,
例3
//根節點
/ {
#address-cells = <1>; //帶有#一般都是描述子節點的屬性,
#size-cells = <1>; //這個與上一行的經常一塊使用,描述位元組點的位址大小相關
interrupt-parent = <&intc>;
//子節點
clocks {
#address-cells = <1>;
#size-cells = <1>;
ranges;
//子節點下的chirld節點
osc24M: clk-24M {
#clock-cells = <0>;
compatible = "fixed-clock"; //compatible ,與driver中的name比對,會進入driver.c的prob函數
clock-frequency = <24000000>;
clock-output-names = "osc24M";
};
...
}