最近因為工作需要涉及到了I2C總線。雖然我過去用過I2c,但看了 Linux kernel 後才發現,一個 layer 能被做到這樣完善。
1. Linux的I2C驅動架構
Linux中I2C總線的驅動分為兩個部分,總線驅動(BUS)和設備驅動(DEVICE)。其中總線驅動的職責,是為系統中每個I2C總線增加相應的讀寫方法。但是總線驅動本身並不會進行任何的通訊,它只是存在在那裡,等待設備驅動調用其函數。
設備驅動則是與掛在I2C總線上的具體的設備通訊的驅動。通過I2C總線驅動提供的函數,設備驅動可以忽略不同總線控制器的差異,不考慮其實現細節地與硬件設備通訊。
1.1 總線驅動
在系統開機時,首先裝載的是I2C總線驅動。一個總線驅動用於支持一條特定的I2C總線的讀寫。一個總線驅動通常需要兩個模塊,一個struct i2c_adapter和一個struct i2c_algorithm來描述:
static struct i2c_adapter pb1550_board_adapter = {
name: "pb1550 adapter",
id: I2C_HW_AU1550_PSC,
algo: NULL,
algo_data: &pb1550_i2c_info,
inc_use: pb1550_inc_use,
dec_use: pb1550_dec_use,
client_register: pb1550_reg,
client_unregister: pb1550_unreg,
client_count: 0,
};
這個樣例掛接了一個叫做“pb1550 adapter”的驅動。但這個模塊並未提供讀寫函數,具體的讀寫方法由第二個模塊,struct i2c_algorithm提供。
static struct i2c_algorithm au1550_algo = {
.name = "Au1550 algorithm",
.id = I2C_ALGO_AU1550,
.master_xfer = au1550_xfer,
.functionality = au1550_func,
};
i2c_adap->algo = &au1550_algo;
這個樣例給上述總線驅動增加了讀寫“算法”。通常情況下每個I2C總線驅動都定義一個自己的讀寫算法,但鑒於有些總線使用相同的算法,因而可以共用同一套讀寫函數。本例中的驅動定義了自己的讀寫算法模塊,起名叫“Au1550 algorithm”。
全部填妥後,通過調用:
i2c_add_adapter(i2c_adap);
將這兩個模塊注冊到操作系統裡,總線驅動就算裝上了。對於AMD au1550,這部分已經由AMD提供了。
1.2 設備驅動
如前所述,總線驅動只是提供了對一條總線的讀寫機制,本身並不會去做通信。通信是由I2C設備驅動來做的,設備驅動透過I2C總線同具體的設備進行通訊。一個設備驅動有兩個模塊來描述,struct i2c_driver和struct i2c_client。
當系統開機、I2C總線驅動裝入完成後,就可以裝入設備驅動了。首先裝入如下結構:
static struct i2c_driver driver = {
.name = "i2c TV tuner driver",
.id = I2C_DRIVERID_TUNER,
.flags = I2C_DF_NOTIFY,
.attach_adapter = tuner_probe,
.detach_client = tuner_detach,
.command = tuner_command,
};
i2c_add_driver(&driver);
這個i2c_driver一旦裝入完成,其中的attach_adapter函數就會被調用。在其中可以遍歷系統中的每個i2c總線驅動,探測想要訪問的設備:
static int tuner_probe(struct i2c_adapter *adap)
{
return i2c_probe(adap, &addr_data, tuner_attach);
}
注意探測可能會找到多個設備,因而不僅一個I2C總線可以掛多個不同類型的設備,一個設備驅動也可以同時為掛在多個不同I2C總線上的設備服務。
每當設備驅動探測到了一個它能支持的設備,它就創建一個struct i2c_client來標識這個設備:
new_client->addr = address;
new_client->adapter = adapter;
new_client->driver = &driver;
/* Tell the I2C layer a new client has arrived */
err = i2c_attach_client(new_client);
if (err)
goto error;
可見,一個i2c_client代表著位於adapter總線上,地址為address,使用driver來驅動的一個設備。它將總線驅動與設備驅動,以及設備地址綁定在了一起。一個i2c_client就代表著一個I2C設備。
當得到I2C設備後,就可以直接對此設備進行讀寫:
/*
* The master routines are the ones normally used to transmit data to devices
* on a bus (or read from them). Apart from two basic transfer functions to
* transmit one message at a time, a more complex version can be used to
* transmit an arbitrary number of messages without interruption.
*/
extern int i2c_master_send(struct i2c_client *,const char* ,int);
extern int i2c_master_recv(struct i2c_client *,char* ,int);
與通常意義上的讀寫函數一樣,這兩個函數對i2c_client指針指定的設備,讀寫int個char。返回值為讀寫的字節數。對於我們現有的SLIC的驅動,只要將最後要往總線上進行讀寫的數據引出傳輸到這兩個函數中,移植工作就算完成了,我們將得到一個Linux版的I2C設備驅動。