天天看點

設計和編寫裝置驅動的一般方法

轉載: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指令來對裝置驅動的各項參數進行手動測試。