Linux I2C驅動是嵌入式Linux驅動開發人員常常須要編寫的一種驅動,由於凡是系統中使用到的I2C設備,幾乎都須要編寫相應的I2C驅動去配置和控制它,例如 RTC實時時鐘芯片、音視頻採集芯片、音視頻輸出芯片、EEROM芯片、AD/DA轉換芯片等等。linux
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設備探測和註銷的一些基本方法和信息,示例以下:學習
static struct i2c_driver tvp5158_i2c_driver = { .driver = { .name = "tvp5158_i2c_driver", }, .attach_adapter = &tvp5158_attach_adapter, .detach_client = &tvp5158_detach_client, .command = NULL, };
其中,name字段標識本驅動的名稱(不要超過31個字符),attach_adapter和detach_client字段爲函數指針,這兩個函數在I2C設備註冊的時候會自動調用,須要本身實現這兩個函數,後面將詳細講述。.net
2. i2c_client 結構體對象指針
上面定義的i2c_driver對象,抽象爲一個i2c的驅動模型,提供對i2C設備的探測和註銷方法,而i2c_client結構體則是表明着一個具體的i2c設備,該結構體有一個data指針,能夠指向任何私有的設備數據,在複雜點的驅動中可能會用到。示例以下: orm
struct tvp5158_obj{ struct i2c_client client; int users; // how many users using the driver }; struct tvp5158_obj* g_tvp5158_obj;
其中,users爲示例,用戶能夠本身在tvp5158_obj這個結構體裏面添加感興趣的字段,可是i2c_client字段不可少。具體用法後面再詳細講。視頻
3. 設備註冊及探測功能
這一步很關鍵,按照標準的要求來寫,則Linux系統會自動調用相關的代碼去探測你的I2C設備,而且添加到系統的I2C設備列表中以供後面訪問。
咱們知道,每個I2C設備芯片,都經過硬件鏈接設定好了該設備的I2C設備地址。所以,I2C設備的探測通常是靠設備地址來完成的。那麼,首先要在驅動代碼中聲明你要探測的I2C設備地址列表,以及一個宏。示例以下:
static unsigned short normal_i2c[] = { 0xbc >> 1, 0xbe >> 1, I2C_CLIENT_END }; I2C_CLIENT_INSMOD;
normal_i2c 數組包含了你須要探測的I2C設備地址列表,而且必須以I2C_CLIENT_END做爲結尾,注意,上述代碼中的0xbc和0xbe是我在硬件上爲個人tvp5158分配的地址,硬件上我支持經過跳線將該地址設置爲 0xbc 或者 0xbe,因此把這兩個地址均寫入到探測列表中,讓系統進行探測。若是你的I2C設備的地址是固定的,那麼,這裏能夠只寫你本身的I2C設備地址,注意必須向右移位1。
宏 I2C_CLIENT_INSMOD 的做用網上有許多文章進行了詳細的講解,這裏我就不詳細描述了,記得加上就行,咱們重點關注實現。
下一步就應該編寫第1步中的兩個回調函數,一個用於註冊設備,一個用於註銷設備。探測函數示例以下:
static int tvp5158_attach_adapter(struct i2c_adapter *adapter) { return i2c_probe(adapter, &addr_data, &tvp5158_detect_client); }
這個回調函數系統會自動調用,咱們只須要按照上述代碼形式寫好就行,這裏調用了系統的I2C設備探測函數,i2c_probe(),第三個參數爲具體的設備探測回調函數,系統會在探測設備的時候調用這個函數,須要本身實現。示例以下:
static int tvp5158_detect_client(struct i2c_adapter *adapter,int address,int kind) { struct tvp5158_obj *pObj; int err = 0; printk(KERN_INFO "I2C: tvp5158_detect_client at address %x ...\n", address); if( g_tvp5158_obj != NULL ) { //already allocated,inc user count, and return the allocated handle g_tvp5158_obj->users++; return 0; } /* alloc obj */ pObj = kmalloc(sizeof(struct tvp5158_obj), GFP_KERNEL); if (pObj==0){ return -ENOMEM; } memset(pObj, 0, sizeof(struct tvp5158_obj)); pObj->client.addr = address; pObj->client.adapter = adapter; pObj->client.driver = &tvp5158_i2c_driver; pObj->client.flags = I2C_CLIENT_ALLOW_USE; pObj->users++; /* attach i2c client to sys i2c clients list */ if((err = i2c_attach_client(&pObj->client))){ printk( KERN_ERR "I2C: ERROR: i2c_attach_client fail! address=%x\n",address); return err; } // store the pObj g_tvp5158_obj = pObj; printk( KERN_ERR "I2C: i2c_attach_client ok! address=%x\n",address); return 0; }
到此爲止,探測而且註冊設備的代碼已經完成,之後對該 I2C 設備的訪問都可以經過 g_tvp5158_obj 這個全局的指針進行了。
4. 註銷I2C設備
同理,設備註銷的回調函數也會自動被系統調用,只須要按照模板寫好設備註銷代碼,示例以下:
static int tvp5158_detach_client(struct i2c_client *client) { int err; if( ! client->adapter ){ return -ENODEV; } if( (err = i2c_detach_client(client)) ) { printk( KERN_ERR "Client deregistration failed (address=%x), client not detached.\n", client->addr); return err; } client->adapter = NULL; if( g_tvp5158_obj ){ kfree(g_tvp5158_obj); } return 0; }
到此爲止,設備的註冊和註銷代碼已經所有完成,下面要作的就是提供讀寫I2C設備的方法。
5. I2C設備的讀寫
對I2C設備的讀寫,Linux系統提供了多種接口,能夠在內核的 i2c.h 中找到,這裏簡單介紹其中的兩種接口。
【接口一】:
extern int i2c_master_send(struct i2c_client *,const char* ,int); extern int i2c_master_recv(struct i2c_client *,char* ,int);
第一個參數是 i2c_client 對象指針,第二個參數是要傳輸的數據buffer指針,第三個參數爲buffer的大小。
【接口二】:
extern int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msg, int num);
這個接口支持一次向I2C設備發送多個消息,每個消息能夠是讀也能夠是寫,讀或者寫以及讀寫的目標地址(寄存器地址)均包含在msg消息參數裏面。
這些接口僅僅是最底層的讀寫方法,關於具體怎麼與I2C設備交互,好比具體怎麼讀芯片的某個特定寄存器的值,這須要看具體的芯片手冊,每一個I2C芯片都會有具體的I2C寄存器讀寫時序圖。所以,爲了在驅動中提供更好的訪問接口,還須要根據具體的時序要求對這些讀寫函數進行進一步封裝,這些內容將在後面的文章中講述。
6. 模塊初始化及其餘
下一步就是整個模塊的初始化代碼和逆初始化代碼,以及模塊聲明瞭。
static int __init tvp5158_i2c_init(void) { g_tvp5158_obj = NULL; return i2c_add_driver(&tvp5158_i2c_driver); } static void __exit tvp5158_i2c_exit(void) { i2c_del_driver(&tvp5158_i2c_driver); } module_init(tvp5158_i2c_init); module_exit(tvp5158_i2c_exit); MODULE_DESCRIPTION("TVP5158 i2c driver"); MODULE_AUTHOR("Lujun @hust"); MODULE_LICENSE("GPL");
在初始化的代碼裏面,添加本模塊的 i2c driver 對象,在逆初始化代碼裏面,刪除本模塊的 i2c driver 對象。
7. 總結
到此爲止,算是從應用的角度把編寫一個I2C的設備驅動代碼講完了,不少原理性的東西我都沒有具體分析(其實我也瞭解的不深),之後會慢慢更深刻地學習和了解,文中有什麼講述不正確的地方,歡迎留言或者來信lujun.hust@gmail.com交流。
讀到最後,你們可能還有一個疑問,這個驅動寫完了怎麼在用戶空間(應用層)去使用它呢?因爲本文不想把代碼弄得太多太複雜,怕提升理解的難度,因此就沒有講,其實要想在用戶空間使用該I2C設備驅動,則還須要藉助字符設備驅動來完成,即爲這個I2C設備驅動封裝一層字符設備驅動,這樣,用戶空間就能夠經過對字符設備驅動的訪問來訪問I2C設備,這個方法我會在後面的文章中講述。
總結和說明
免費學習更多精品課程,登陸樂搏學院官網http://www.learnbo.com/
或關注咱們的官方微博微信,還有更多驚喜哦~
本文出自 「Jhuster的專欄」 博客,請務必保留此出處http://ticktick.blog.51cto.com/823160/760020