Linux i2c system
I2C總線是由PHILIPS公司開發(fā)的兩線式串行總線,每個連接到總線的器件都可以通過唯一的地址和主機主機進(jìn)行通訊,主機可以作為主機發(fā)送器或主機接收器;串行的8位雙向數(shù)據(jù)傳輸位速率在標(biāo)準(zhǔn)模式下可達(dá)100kbit/s,快速模式下可達(dá)400kbit/s,高速模式下可達(dá)3.4Mbit/s。
1.Linux下I2c的驅(qū)動架構(gòu)
如下:
從圖中可以觀察到I2C系統(tǒng)的整個框架,從下往上;
Hardware:
所連接的外界設(shè)備i2c-device,可以連接多個device,但掛載在同一條總線上的設(shè)備其地址必須不一樣。Kernel:
Kernel主要分為三個模塊,adapter、core、client,driver;Adapter:
CPU與I2C的接口,簡單解釋就是CPU要控制一個i2c-device,那你CPU自身要能支持i2c總線功能,所以adapter主要的工作就是初始化i2c,向cpu申請i2c總線,一條i2c總線對于一個adapter;adapter會根據(jù)cpu的變化有一些變化,所以需要用戶根據(jù)cpu體現(xiàn)自行實現(xiàn),一般放在driver/i2c/buses/文件夾下面(例如:i2c-s3c2410.c)。由于adapter屬于片上資源,所以驅(qū)動的實現(xiàn)采用platform驅(qū)動模型。Core:
core為i2c驅(qū)動的核心,位于driver/i2c/i2c-core.c中;core上要跟client打好關(guān)系,為其提供client的注冊申請,下要為adapter提供總線的申請注冊,不過這一部分kernel基本已經(jīng)為我們所實現(xiàn),我們要做的就是有選擇性的調(diào)用其中的函數(shù)。Client:
client即為外界設(shè)備i2c-device,一個device即是一個client,不同的i2c-device注冊client方式基本一致,一般在平臺設(shè)備arch/arm/mach-xxx里面添加info信息,也可以通過dts來進(jìn)行設(shè)置。需要注意的是在同一個總線上的多個設(shè)備,其設(shè)備地址肯定不一樣,設(shè)備reg的讀寫等也需要根據(jù)設(shè)備來定制。Driver:
driver可以看成是client的驅(qū)動,一個client對應(yīng)一個driver,這部分的驅(qū)動由用戶來實現(xiàn),一般放在driver/i2c/或driver/i2c/chips/文件夾下實現(xiàn)。User-space:
即應(yīng)用層,我們做這么多驅(qū)動的工作,主要就是要用它,所以要有特定的應(yīng)用程序來控制。
上面將i2c driver的整體框架做了一個介紹,熟悉i2cdriver的框架,下面我們就將需要由用戶自行實現(xiàn)的adapter、client和driver進(jìn)行簡單說明,里面會交叉講解一些i2c-core中的內(nèi)容。
2.I2c adapter and client
adapter的主要目的就是通過platform總線將片上i2c設(shè)備和i2c驅(qū)動綁定在一起,當(dāng)i2c的platform驅(qū)動成功后,會執(zhí)行platform_driver
結(jié)構(gòu)下對應(yīng)的probe函數(shù),對于如何實現(xiàn)i2c的platform設(shè)備驅(qū)動,查看Linux platform system。
probe函件里面會調(diào)用adapter的添加,在i2c-core.c中,為我們提供了i2c_add_adapter()
和i2c_add_numbered_adapter()
兩個函數(shù)來實現(xiàn)adapter接口注冊。
這兩個函數(shù)有什么區(qū)別呢,來具體探究下,下面為這兩個函數(shù)的源碼,位于i2c-core.c中:
int i2c_add_adapter(struct i2c_adapter *adapter)
{
int id, res = 0;
retry:
if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
return -ENOMEM;
mutex_lock(&core_lock);
/* "above" here means "above or equal to", sigh */
res = idr_get_new_above(&i2c_adapter_idr, adapter,
__i2c_first_dynamic_bus_num, &id);
mutex_unlock(&core_lock);
if (res < 0) {
if (res == -EAGAIN)
goto retry;
return res;
}
adapter->nr = id;
return i2c_register_adapter(adapter);
}
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
int id;
int status;
if (adap->nr == -1) /* -1 means dynamically assign bus id */
return i2c_add_adapter(adap);
if (adap->nr & ~MAX_IDR_MASK)
return -EINVAL;
retry:
if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
return -ENOMEM;
mutex_lock(&core_lock);
/* "above" here means "above or equal to", sigh;
* we need the "equal to" result to force the result
*/
status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);
if (status == 0 && id != adap->nr) {
status = -EBUSY;
idr_remove(&i2c_adapter_idr, id);
}
mutex_unlock(&core_lock);
if (status == -EAGAIN)
goto retry;
if (status == 0)
status = i2c_register_adapter(adap);
return status;
}
可以觀察到,不管兩個函數(shù)上面在執(zhí)行什么操作,都是為了得到最終的i2c_adapter
結(jié)構(gòu)體,最后通過調(diào)用i2c_register_adapter()
函數(shù)來實現(xiàn)adapter的注冊的。
其實函數(shù)的上部分是為了處理i2c的總線號,對于i2c_add_adapter()
而言,它使用的是動態(tài)總線號,即由系統(tǒng)給其自動分配一個總線號,而i2c_add_numbered_adapter()
則是自己指定總線號,如果這個總線號非法或者是被占用,就會注冊失敗。
既然有動態(tài)總線和指定總線,那就去尋找總線號的指定在哪邊實現(xiàn),總線號的執(zhí)行是在平臺設(shè)備里面指定的,在平臺設(shè)備arch/arm/mach-xxx
里面我們會調(diào)用i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
函數(shù)來提供總線信息和client信息。
第一個參數(shù)busnum即總線號。
第二個參數(shù)info為client的name和id,這邊client的name和id要和driver下面的id、name一致。
上面所提到的使用i2c_register_board_info
來提供總線號和注冊client,在Linux3.14以后的版本也可以有dts來實現(xiàn)。
3.I2c client driver
一個client一般對應(yīng)一個driver,driver比較簡單,一般就是通過i2c_add_driver
來進(jìn)行添加,i2c_del_driver
進(jìn)行卸載,當(dāng)i2c_driver.id_table
信息與i2c_board_info
所指向的設(shè)備(或者設(shè)備樹中的節(jié)點)匹配成功,則執(zhí)行i2c_driver.probe()
,從而獲得對應(yīng)的i2c client。
如下例子:
static const struct i2c_device_id pca9555_id[] = {
{"pca9555", 0x27},
{}
};
static struct i2c_driver pca9555_driver = {
.driver = {
.name = "pca9555",
},
.probe = pca9555_probe,
.remove = pca9555_remove,
.id_table = pca9555_id,
.suspend = pca9555_suspend,
.resume = pca9555_resume,
.shutdown = pca9555_shutdown,
};
static int __init pca9555_init(void)
{
return i2c_add_driver(&pca9555_driver);
}
static void __exit pca9555_exit(void)
{
i2c_del_driver(&pca9555_driver);
}
module_init(pca9555_init);
module_exit(pca9555_exit);
獲得i2c client后,就是進(jìn)行實現(xiàn)client的讀寫函數(shù)了,在i2c-core.c
里面有提供例如i2c_smbus_read_byte_data
、i2c_smbus_write_byte_data
這類的函數(shù),需要查看芯片手冊的時序找對應(yīng)的實現(xiàn)函數(shù)即可。
調(diào)試驅(qū)動的時候最常用的方法就是使用printk來進(jìn)行交互,進(jìn)行定位、驗證,但是要在哪邊進(jìn)行printk呢,個人覺得調(diào)試i2c驅(qū)動有一個地方一定要進(jìn)行printk,那就是位于i2c-core.c
下的i2c_match_id()
函數(shù),如下:
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
const struct i2c_client *client)
{
while (id->name[0]) {
printk("client->name:%s\n",client->name);
printk("id->name:%s\n",id->name);
if (strcmp(client->name, id->name) == 0)
return id;
id++;
}
return NULL;
}
看函數(shù)名稱就知道,該函數(shù)用來匹配device和client的name是否一致。通過在此處打印信息,我們可以觀察到,這個函數(shù)會被調(diào)用到兩次,一次是進(jìn)行注冊adapter時,另一次就是尋找驅(qū)動的時候,如果遇到問題是,我們只有查看打印的信息大概就能發(fā)現(xiàn)問題的存在點了。
Linux i2c system的分析就到這邊,有感悟時會持續(xù)會更新。
注:以上內(nèi)容都是本人在學(xué)習(xí)過程積累的一些心得,難免會有參考到其他文章的一些知識,如有侵權(quán),請及時通知我,我將及時刪除或標(biāo)注內(nèi)容出處,如有錯誤之處也請指出,進(jìn)行探討學(xué)習(xí)。文章只是起一個引導(dǎo)作用,詳細(xì)的數(shù)據(jù)解析內(nèi)容還請查看Linux相關(guān)教程,感謝您的查閱。