手把手教你寫Linux I2C裝置驅動

來源:互聯網
上載者:User
原創作品,允許轉載,轉載時請務必以超連結形式標明文章
原始出處 、作者資訊和本聲明。否則將追究法律責任。http://ticktick.blog.51cto.com/823160/760020

    Linux I2C驅動是嵌入式Linux驅動開發人員經常需要編寫的一種驅動,因為凡是系統中使用到的I2C裝置,幾乎都需要編寫相應的I2C驅動去配置和控制它,例如 RTC系統時鐘晶片、音視頻採集晶片、音視頻輸出晶片、EEROM晶片、AD/DA轉換晶片等等。

    Linux I2C驅動涉及的知識點還是挺多的,主要分為Linux I2C的匯流排驅動(I2C BUS Driver)和裝置驅動(I2C Clients Driver),本文主要關注如何快速地完成一個具體的I2C裝置驅動(I2C Clients Driver)。關於Linux I2C驅動的整體架構、核心原理等可以在網上搜尋其他相關文章學習。

    注意:本系列文章的I2C裝置驅動是基於Linux 2.6.18核心。

    本文主要參考了Linux核心源碼目錄下的 ./Documentation/i2c/writing-clients 文檔。以手頭的一款視頻採集晶片TVP5158為驅動目標,編寫Linux I2C裝置驅動。

1.   i2c_driver結構體對象     

    每一個I2C裝置驅動,必須首先創造一個i2c_driver結構體對象,該結構體包含了I2C裝置探測和登出的一些基本方法和資訊,樣本如下:

 
  1. static struct i2c_driver tvp5158_i2c_driver = {  
  2.         .driver = {  
  3.             .name = "tvp5158_i2c_driver",  
  4.         },   
  5.         .attach_adapter = &tvp5158_attach_adapter,  
  6.         .detach_client  = &tvp5158_detach_client,  
  7.         .command        = NULL,  
  8. };  

    其中,name欄位標識本驅動的名稱(不要超過31個字元),attach_adapter和detach_client欄位為函數指標,這兩個函數在I2C裝置註冊的時候會自動調用,需要自己實現這兩個函數,後面將詳細講述。

2.   i2c_client 結構體對象

    上面定義的i2c_driver對象,抽象為一個i2c的驅動模型,提供對i2C裝置的探測和登出方法,而i2c_client結構體則是代表著一個具體的i2c裝置,該結構體有一個data指標,可以指向任何私人的裝置資料,在複雜點的驅動中可能會用到。樣本如下:   

 
  1. struct tvp5158_obj{        
  2.     struct i2c_client client;        
  3.     int users; // how many users using the driver    
  4. };  
  5.  
  6. struct tvp5158_obj* g_tvp5158_obj;  

    其中,users為樣本,使用者可以自己在tvp5158_obj這個結構體裡面添加感興趣的欄位,但是i2c_client欄位不可少。具體用法後面再詳細講。

3.   裝置註冊及探測功能

    這一步很關鍵,按照標準的要求來寫,則Linux系統會自動調用相關的代碼去探測你的I2C裝置,並且添加到系統的I2C裝置列表中以供後面訪問。

    我們知道,每一個I2C裝置晶片,都通過硬體串連設定好了該裝置的I2C裝置地址。因此,I2C裝置的探測一般是靠裝置地址來完成的。那麼,首先要在驅動代碼中聲明你要探測的I2C裝置地址清單,以及一個宏。樣本如下:

 
  1. static unsigned short normal_i2c[] = {  
  2.         0xbc >> 1,  
  3.         0xbe >> 1,  
  4.         I2C_CLIENT_END  
  5. };  
  6. I2C_CLIENT_INSMOD;  

    normal_i2c 數組包含了你需要探測的I2C裝置地址清單,並且必須以I2C_CLIENT_END作為結尾,注意,上述代碼中的0xbc和0xbe是我在硬體上為我的tvp5158分配的地址,硬體上我支援通過跳線將該地址設定為 0xbc 或者 0xbe,所以把這兩個地址均寫入到探測列表中,讓系統進行探測。如果你的I2C裝置的地址是固定的,那麼,這裡可以唯寫你自己的I2C裝置地址,注意必須向右移位1。

    宏 I2C_CLIENT_INSMOD 的作用網上有許多文章進行了詳細的講解,這裡我就不詳細描述了,記得加上就行,我們重點關注實現。

    下一步就應該編寫第1步中的兩個回呼函數,一個用於註冊裝置,一個用於登出裝置。探測函數樣本如下:

 
  1. static int tvp5158_attach_adapter(struct i2c_adapter *adapter)  
  2. {  
  3.     return i2c_probe(adapter, &addr_data, &tvp5158_detect_client);  

    這個回呼函數系統會自動調用,我們只需要按照上述代碼形式寫好就行,這裡調用了系統的I2C裝置探測函數,i2c_probe(),第三個參數為具體的裝置探測回呼函數,系統會在探測裝置的時候調用這個函數,需要自己實現。樣本如下:

 
  1. static int tvp5158_detect_client(struct i2c_adapter *adapter,int address,int kind)  
  2. {  
  3.     struct tvp5158_obj *pObj;  
  4.     int err = 0;  
  5.  
  6.     printk(KERN_INFO "I2C: tvp5158_detect_client at address %x ...\n", address);  
  7.  
  8.     if( g_tvp5158_obj != NULL  ) {  
  9.         //already allocated,inc user count, and return the allocated handle  
  10.         g_tvp5158_obj->users++;  
  11.         return 0;  
  12.     }  
  13.  
  14.     /* alloc obj */ 
  15.     pObj = kmalloc(sizeof(struct tvp5158_obj), GFP_KERNEL);  
  16.     if (pObj==0){  
  17.         return -ENOMEM;  
  18.     }  
  19.     memset(pObj, 0, sizeof(struct tvp5158_obj));  
  20.     pObj->client.addr    = address;  
  21.     pObj->client.adapter = adapter;  
  22.     pObj->client.driver  = &tvp5158_i2c_driver;  
  23.     pObj->client.flags   = I2C_CLIENT_ALLOW_USE;  
  24.     pObj->users++;  
  25.  
  26.     /* attach i2c client to sys i2c clients list */ 
  27.     if((err = i2c_attach_client(&pObj->client))){  
  28.         printk( KERN_ERR "I2C: ERROR: i2c_attach_client fail! address=%x\n",address);  
  29.         return err;  
  30.     }  
  31.  
  32.     // store the pObj  
  33.     g_tvp5158_obj = pObj;  
  34.  
  35.     printk( KERN_ERR "I2C: i2c_attach_client ok! address=%x\n",address);  
  36.  
  37.     return 0;  

    到此為止,探測並且註冊裝置的代碼已經完成,以後對該  I2C 裝置的訪問均可以通過 g_tvp5158_obj 這個全域的指標進行了。

4.    登出I2C裝置 

    同理,裝置登出的回呼函數也會自動被系統調用,只需要按照模板寫好裝置登出代碼,樣本如下:    

 
  1. static int tvp5158_detach_client(struct i2c_client *client)  
  2. {  
  3.     int err;  
  4.  
  5.     if( ! client->adapter ){  
  6.         return -ENODEV;  
  7.     }  
  8.  
  9.     if( (err = i2c_detach_client(client)) ) {  
  10.         printk( KERN_ERR "Client deregistration failed (address=%x), client not detached.\n", client->addr);  
  11.         return err;  
  12.     }  
  13.  
  14.     client->adapter = NULL;  
  15.  
  16.     if( g_tvp5158_obj ){  
  17.         kfree(g_tvp5158_obj);  
  18.     }  
  19.  
  20.     return 0;  

    到此為止,裝置的註冊和登出代碼已經全部完成,下面要做的就是提供讀寫I2C裝置的方法。

 5.   I2C裝置的讀寫      

    對I2C裝置的讀寫,Linux系統提供了多種介面,可以在核心的 i2c.h 中找到,這裡簡單介紹其中的兩種介面。

   【介面一】:

 
  1. extern int i2c_master_send(struct i2c_client *,const char* ,int);  
  2.  
  3. extern int i2c_master_recv(struct i2c_client *,char* ,int);  

    第一個參數是 i2c_client 對象指標,第二個參數是要傳輸的資料buffer指標,第三個參數為buffer的大小。

   【介面二】:

 
  1. extern int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msg, int num); 

    這個介面支援一次向I2C裝置發送多個訊息,每一個訊息可以是讀也可以是寫,讀或者寫以及讀寫的目標地址(寄存器地址)均包含在msg訊息參數裡面。

    這些介面僅僅是最底層的讀寫方法,關於具體怎麼與I2C裝置互動,比如具體怎麼讀晶片的某個特定寄存器的值,這需要看具體的晶片手冊,每個I2C晶片都會有具體的I2C寄存器讀寫時序圖。因此,為了在驅動中提供更好的提供者,還需要根據具體的時序要求對這些讀寫函數進行進一步封裝,這些內容將在後面的文章中講述。

6.  模組初始化及其他

    下一步就是整個模組的初始化代碼和逆初始化代碼,以及模組聲明了。    

 
  1. static int __init tvp5158_i2c_init(void) 
  2.     g_tvp5158_obj = NULL; 
  3.      
  4.     return i2c_add_driver(&tvp5158_i2c_driver); 
  5.  
  6. static void __exit tvp5158_i2c_exit(void) 
  7.     i2c_del_driver(&tvp5158_i2c_driver); 
  8.  
  9. module_init(tvp5158_i2c_init); 
  10. module_exit(tvp5158_i2c_exit); 
  11.  
  12. MODULE_DESCRIPTION("TVP5158 i2c driver"); 
  13. MODULE_AUTHOR("Lujun @hust"); 
  14. MODULE_LICENSE("GPL"); 

    在初始化的代碼裡面,添加本模組的 i2c driver 對象,在逆初始化代碼裡面,刪除本模組的 i2c driver 對象。

7.   總結

    到此為止,算是從應用的角度把編寫一個I2C的裝置驅動代碼講完了,很多原理性的東西我都沒有具體分析(其實我也瞭解的不深),以後會慢慢更深入地學習和瞭解,文中有什麼講述不正確的地方,歡迎留言或者來信lujun.hust@gmail.com交流。

    讀到最後,大家可能還有一個疑問,這個驅動寫完了怎麼在使用者空間(應用程式層)去使用它呢?由於本文不想把代碼弄得太多太複雜,怕提高理解的難度,所以就沒有講,其實要想在使用者空間使用該I2C裝置驅動,則還需要藉助字元裝置驅動來完成,即為這個I2C裝置驅動封裝一層字元裝置驅動,這樣,使用者空間就可以通過對字元裝置驅動的訪問來訪問I2C裝置,這個方法我會在後面的文章中講述。

本文出自 “對影成三人” 部落格,請務必保留此出處http://ticktick.blog.51cto.com/823160/760020

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.