A. 如何使用linux的Documentation來寫驅動
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驅動的整體架構、核心原理等可以在網上搜索其他相關文章學習。
本文主要參考了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設備注冊的時候會自動調用,需要自己實現這兩個函數,後面將詳細講述。
2. i2c_client 結構體對象
上面定義的i2c_driver對象,抽象為一個i2c的驅動模型,提供對i2C設備的探測和注銷方法,而i2c_client結構體則是代表著一個具體的i2c設備,該結構體有一個data指針,可以指向任何私有的設備數據,在復雜點的驅動中可能會用到。示例如下:
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 這個全局的指針進行了。
B. 在linux上怎樣增加一個i2c設備
假設手上有一塊從淘寶上買來的開發板,我要在開發板的I2C匯流排上增加一個從設備(如at24c08),那麼我要怎樣寫這個「I2C設備驅動」,讓
應用程序可以訪問at24c08呢?
先來看一個最簡單的i2c設備驅動:
static struct i2c_board_info at24cxx_info = { //所支持的i2c設備的列表
I2C_BOARD_INFO("at24c08", 0x50), //一項代表一個支持的設備,它的名字叫做「at24c08」,器件地址是0x50
};
static struct i2c_client *at24cxx_client;
static int at24cxx_dev_init(void)
{
struct i2c_adapter *i2c_adap; //分配一個適配器的指針
i2c_adap = i2c_get_adapter(0); //調用core層的函數,獲得一個i2c匯流排。這里我們已經知道新增的器件掛接在編號為0的i2c匯流排上
at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info); // 把i2c適配器和新增的I2C器件關聯起來,這個用了i2c匯流排0,地址是0x50。這就組成了一個客戶端
at24cxx_client i2c_put_adapter(i2c_adap);
return 0;
}
static void at24cxx_dev_exit(void)
{
i2c_unregister_device(at24cxx_client);
}
mole_init(at24cxx_dev_init);
mole_exit(at24cxx_dev_exit);
從上面的程序可以看到,寫一個i2c設備驅動程序,與寫普通的字元驅動基本一樣。特別之處是它調用了i2c的core層的函數,以獲得對i2c匯流排的控制。因為用的是開發板,板上的與soc晶元(一般來說就是arm的晶元)i2c匯流排驅動一般都做好了,直接調用core層的函數就可以控制soc的i2c模塊了。也就是說,寫i2c設備驅動不需要關注arm內部的i2c模塊的寄存器,我們需要關注的是設備(at24c08)的寄存器以及它的datasheet對時序的要求。
其實,添加i2c設備的方法很靈活。根據Linux的官方文檔《linux-3.4.2\Documentation\i2c\instantiating-devices》,添加i2c設備的方法總結有4種:
1. i2c_register_board_info:根據匯流排編號、設備名字(「at24c08」)、設備地址(0x50)注冊一個字元驅動。這種方法最簡單、最粗暴,最貼近平時在開片機上開發i2c器件的。
2. i2c_new_device:根據i2c匯流排的編號,聲明一個i2c設備:這種方法就是上面例子用的方法。這種方法也簡單,但是需要事先知道器件掛接在哪條匯流排上。對於設備,還實現知道了設備地址0x50,匯流排適配器也支持名字為「at24c08」的設備
3. i2c_new_probed_device:
4.從用戶空間實例化一個器件:這個方法相當智能快速,如下輸入指令,即可增加一個i2c設備,同時增加了對應的設備文件。
# echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device
根據英文文檔的標題,添加i2c設備有稱之為「i2c設備的實例化」。
從上述可以知道,在實例化一個i2c設備之前,除了有對應的驅動支持匯流排外(這里是匯流排0),還需要有一個驅動使用了匯流排0發送時序,支持名字為"at24c08"的器件。這個驅動用匯流排驅動的函數,配置了at24c08的寄存器。
C. Linux內核開發與Linux驅動開發有什麼關系
驅動裝在系統上,有的會跟內核有交互,但是驅動一般是針對設備
D. 用linux 調用內核中的統一I2C驅動 i2c總是 busy,求大神支招,謝謝! 程序很短
最近我也遇到這個問題了,糾結了一天,在網友的支持下解決了,這個天嵌的版本中,i2c和他的攝像頭驅動(OV9650驅動)相沖突,你在編譯內核之前,將攝像頭的驅動全部去掉,這樣子重新編譯之後,i2c就可以正常測試使用了。
E. 如何在Linux中讓I2C驅動支持Sub Address的兩種方法
【目的】
AS3527有一個模擬部分,稱作AFE,其與數字部分通過i2c通信,此處AFE部分有很多寄存器供外界操作訪問,如果想要訪問這些寄存器,就要用到Sub Address,所以,要實現讓i2c 驅動支持Sub Address的模式。
i2C本身的架構中,沒有支持sub address,所以,我們只能想辦法,讓其I2C支持(方法1)或者用smbus的架構(方法2).
【方法】
方法1:
在i2c的message中傳遞一個2個位元組的buffer,分別存放Sub Address和data
比如,對於讀操作,就可以這么實現:
int afe_read_reg(int addr, u8 *pdata)
{
u8 msgbuf[2];
struct i2c_msg msg =
{
.addr = save_client->addr | ( << 8),
.flags = I2C_M_RD ,
.len = 2,
.buf = msgbuf,
};
msgbuf[0] = addr; //存放Sub Address,此處的Addr是寄存器地址,也就是Sub Address
msgbuf[1] = 0; //初始化
if (i2c_transfer(save_client->adapter, &msg, 1) < 0) {
dev_warn(&save_client->dev,
"can't read from afe /n");
return -ENOMEM;
}
*pdata = msgbuf[1];
return 0;
}
方法2:
使用SMBUS的框架,其支持Sub Address
在i2c讀操作中,直接調用SMBUS架構中的函數i2c_smbus_read_byte_data:
int afe_read_reg(int addr, u8 *pdata)
{
int ret;
ret = i2c_smbus_read_byte_data(save_client, addr);
if (ret < 0)
return ret;
else {
*pdata = (u8)ret;
return 0;
}
}
然後函數調用順序是
i2c_smbus_read_byte_data -> i2c_smbus_xfer ->
adapter->algo->smbus_xfer 或 i2c_smbus_xfer_emulated
(1)此處如果你自己的I2C驅動中沒有實現
adapter->algo->smbus_xfer
那麼就會去調用i2c_smbus_xfer_emulated,其會把I2C的讀一個位元組的操作,
分成2個message,然後
i2c_smbus_xfer_emulated -> i2c_transfer -> adap->algo->master_xfer(adap,msgs,num)
去調用底層自己的i2c傳輸的函數master_xfer去實現兩個message的傳輸。
此處要注意的是,如果你的i2C的控制器和i2c設備,支持將此I2C的讀一個位元組操作分兩個message傳輸,
那麼此處此方法也是可以的。
而你的底層的master_xfer函數,只要負責將對應的message發送出去也就可以實現對應的功能了。
否則,就像我此處遇到的,我這里的AFE的i2c控制器,不支持讀操作分成兩次message,只支持一個I2C message的傳輸,
所以,只能是在底層特殊處理,將2個message自己整理成一個message,或者是用下面的辦法。
(2)自己實現了adapter->algo->smbus_xfer
自己仿照i2c_smbus_xfer_emulated,在具體實現的時候,對於讀和寫都只是發送一個message,然後讓底層代碼
adap->algo->master_xfer去處理這個message,實現對應的讀和寫。
【注意】
1.以上,不論是1還是2,都是在實現了自己I2C驅動底層message傳輸的基本函數之後,才可以工作的。
而對於這個基本函數,即adap->algo->master_xfer,
都是要在實現的時候,注意上層傳遞過來的buffer的第一個位元組是sub address,第二個位元組才是要用於寫入或讀取的buffer。
2.對於方法2(2),在模擬i2c_smbus_xfer_emulated實現自己的xfer函數的時候,
不能直接調用i2c_transfer,因為i2c_transfer裡面,去獲得adapter->bus_lock,而i2c_smbus_xfer中,調用adapter->algo->smbus_xfer之前,已經進行了對於adapter->bus_lock鎖定,而因此會形成死鎖的的,辦法是不要再去獲得鎖,而直接調用adapter->algo->master_xfer即可。