轉載:http://zqwt.012.blog.163.com/blog/static/1204468420128291156143/
Linux裝置驅動的設計一般可以遵循以下幾個步驟。
一、datasheet和原理圖
閱讀和深刻了解裝置晶片的datasheet對第一個步驟裡面最重要的組成部分,裝置驅動就是datasheet的最直覺表達,隻有在深刻了解的datasheet的基礎之上,才有可能設計和開發出優秀的裝置驅動。另外,在一定程度上,datasheet是你做驅動軟體的需求文檔。你做軟體總該有個需求吧,那麼這裡談到的datasheet就是驅動軟體的最好的、最清楚的需求分析。是以,在沒有了解datasheet之前就着手做驅動,那結果是可想而知的。
二、搭建起裝置驅動的架構
了解了 datasheet 之後,先不要急着去編寫代碼,你首先應該做的就是給你将要寫的驅動程式設計一個架構。那麼這裡的架構應該依據什麼搭建呢?具體怎麼搭建呢?
一般的,從USB驅動到I2C驅動,從SPI驅動到序列槽驅動,從PCI驅動到DMA驅動,等等,不管是什麼類型的驅動,它總有一種或者幾種基本固定的套路供你選擇。如果你打算寫一個touch驅動,而這個touch挂接在I2C上,那麼你就依據I2C裝置編寫的一一種固定套路先搭建架構。具體如何搭建,這裡就給出I2C裝置驅動編寫的一種固定套路給大家。
1、建立一個i2c_driver
static struct i2c_driver XXXX_driver = {
.driver = {
.name= "XXXX",
},
.probe = XXXX_probe,
.remove = XXXX_remove,
.id_table = XXXX_id,
};
2、注冊i2c_driver
static int __init XXXX_init(void)
{
return i2c_add_driver(&XXXX_driver);
}
module_init(XXXX_init);
注冊i2c_driver的過程,實際上是将driver注冊到i2c_bus_type的總線上。此總線的比對規則是:
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
const struct i2c_client *client)
{
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0)
return id;
id++;
}
return NULL;
}
上面代碼實際上是利用i2c_client的名稱和id_table中的名稱做比對的。這裡的id_table為:
static const struct i2c_device_id XXXX_id[] = {
{ "XXXX346", 8, },
{ "XXXX353", 16, },
{ "XXXX388", 8, },
{ "XXXX399", 16, },
{ "XXXX542", 8, },
{ "XXXX551", 16, },
{ "XXXX572", 8, },
{ }
};
3 、注冊i2c_board_info
對于Probe方法,都要在平台代碼中要完成i2c_board_info的注冊。辦法如下:
static struct i2c_board_info __initdata tmp_i2c_devices[] = {
{
I2C_BOARD_INFO("XXXX9555", 0x27),//XXXX9555為晶片名稱,0x27為晶片位址
.platform_data = &XXXX9555_data,
},
{
I2C_BOARD_INFO("mt9v022", 0x48),
.platform_data = &iclink[0],
},
{
I2C_BOARD_INFO("mt9m001", 0x5d),
.platform_data = &iclink[0],
},
};
i2c_register_board_info(0,tmp_i2c_devices,
ARRAY_SIZE(tmp_i2c_devices));
i2c_client是在注冊過程中建立的。
4 、調用i2c擴充卡來完成資料傳輸
int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,int num);
master_xfer要通過調用i2c_transfer來完成傳輸。
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);
5 、 字元驅動注冊
I2C裝置驅動的Probe方法,字元驅動的添加位置在XXXX_probe中。
static int __devinit XXXX_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
if(XXXX_major){
Result = register_chrdev_region(XXXX_dev,1,"XXXX");
} else {
result = alloc_chrdev_region(&XXXX_dev,0,1,"XXXX");
XXXX_major=MAJOR(XXXX_dev);
}
if (result < 0) {
printk(KERN_NOTICE "Unable to get XXXX region, error %d\n", result);
return result;
}
XXXX_setup_cdev(chip,0);
}
6 、字元裝置驅動的建構
struct file_operations XXXX_fops = {
.owner = THIS_MODULE,
.ioctl = XXXX_ioctl,
.open = XXXX_open,
.release = XXXX_release,
};
7 、删除i2c_driver
static void __exit XXXX_exit(void)
{
i2c_del_driver(&XXXX_driver);
}
module_exit(XXXX_exit);
8 、 删除字元裝置驅動
static int __devinit XXXX_remove (struct i2c_client *client)
{
}
// 注:總之,你要編寫什麼類型的驅動,首先根據實際情況搭建好架構。
三、填充架構
搭建好裝置驅動程式的架構之後,你應該做的就是根據實際情況往架構裡面填充内容了。填充内容可以分為兩個層次的填充,分别是:
1、和上層應用打交道的内容
主要包括:
1) 基于devfs實作檔案操作結構體(file_operations)中的函數,目的是給上層提供系統調用(system call)
2) 通過procfs或者sysfs給上層應用提供不同種類的接口
3) 向虛拟檔案系統(VFS)注冊或者登出(添加或者删除)裝置驅動,等等。
2、和主要以及裝置晶片打交道的内容
1) 依據主要、裝置晶片的datasheet以及開發闆原理圖,做以下填充:
2) 定義裝置驅動的頭檔案
3) 填充系統調用中和裝置晶片直接打交道的部分
4) 編寫裝置驅動程式電源管理子產品
5) 配置好I/O口的功能,等等。
四、開始裝置驅動的調試
一般的,裝置驅動的調試都會經曆如下幾個步驟:
1、把裝置驅動的代碼添加進kernel (注意:不同的方案提供商給出的添加方法是不同的)。傳統架構的話,就很簡單了,一般都會涉及到以下幾步:
(1)建立裝置驅動目錄 ; (2)添加頭檔案 ;(3)建立目前目錄的Makefile
(4)建立上層目錄的Makefile ;(5)在上層目錄添加Kconfig選項 ; (6)在闆檔案添加對應項。
(7)如果不想每次都用menuconfig配置,就在kernel/arch/arm/configs/XXX_defconfig裡面添加編譯項
2、使得添加進去的裝置驅動可以編譯通過
裝置驅動添加進來之後,一般的,都需要你添加、删除、修改一些内容,才能保證其編譯通過。如果是調試,這裡可能改動不大,因為
驅動檔案一般是由晶片原廠提供的,他們已經修改的基本可以編譯通過了。但如果是移植,在這裡花費的精力一般是比較大的,這會在
後文中的附加說明裡面提及到。
3、檢查裝置驅動是否正常初始化
進行到這一步,你需要檢查裝置驅動是否可以順利初始化,這個很簡單,你隻需要在裝置驅動的probe函數或者init函數裡面添加一條打
印資訊即可在序列槽觀察是否有列印,如果沒有列印,說明驅動根本就沒有得到初始化。此時,你應該做兩件事情:
(1)首先,确定你的闆子以及貼了你要調試的裝置驅動所對應的晶片。
(2)其次,如果晶片确實貼上了,馬上檢查裝置驅動的添加是否有什麼問題,是否是闆檔案沒有修改好,是否驅動本身就沒有注冊好等等。
4、檢查應用程式是否可以正常使用
如果驅動得到了正常的初始化,這時候你開始檢查該裝置驅動對應的應用程式是否可以正常使用。如果可以正常使用,那你太幸運了,這說明晶片
原廠為你考慮的很周到,你可以節省N多時間幹别的事兒了。但遺憾的是,往往事情不會這麼順利,當你檢查應用程式的時候,你發現應用程式沒
有做出應有的反應。
5、裝置驅動的“調”和“試”
所謂“調”,就是你依據晶片datasheet和觀察與測量的結果,不斷地對裝置驅動 code進行添加、删除和修改。所謂“試”,就是你在添加、删除和
修改的同時要不斷地通過序列槽對回報資訊進行觀察,通過萬用表對各電源引腳電壓進行測量,通過示波器對晶片的時鐘(輸入的和輸出的)頻率、數
據信号的波形、某種總線信号的波形,等各個引腳進行測量(當然這裡測量的參考标準是晶片的datasheet了)。這裡應該養成一個好習慣,那就是
在測量各個引腳的同時,你最好建立一份excel表格,把各個引腳的電壓、波形等情況記錄下來,目的是在N多次修改之間作比較,也更有備忘的作用。
這裡的“調”和“試”的過程是最耗時的,也是裝置驅動調試的最關鍵的步驟,是以你應該非常有耐心的走好這一步。還應該注意的是,裝置驅動的
調試不光是你一個人的事情,有時候你悶頭苦幹一個星期都不一定能搞定。是以,進行調試的同時,必須有一個硬體工程師配合你,因為驅動不能正常
運作也很有可能是硬體的設計纰漏導緻的。此外,不要閉門造車,畢竟你在一個大的開發團隊裡面,你需要及時和同僚進行有效的交流,避免走彎路。
最後,如果還是存在問題,你就得邀請晶片原廠的FAE過來喽。
6、自己對裝置驅動的測試
測試不光是測試員的事情,你必須保證自己調試的驅動可以運作穩定才可以送出代碼,發給測試員進行大量測試。測試自己調試的裝置驅動有以下幾種:
(1)利用系統裡面現有的應用程式進行測試;這個是最直接的測試,也省去了你自己編寫測試程式的時間。
(2)自己編寫linux應用程式來測試驅動;如果你對系統現有的應用程式不放心,或者不滿意,就自己編寫測試驅動吧。
(3)利用proc或者sys檔案系統的讀和寫函數
如果你在裝置驅動裡面添加了proc或者sys檔案系統的讀和寫函數,你就可以在指令行通過cat或者echo指令來對裝置驅動的各項參數進行手動測試。