Linux+I2C匯流排分析(主要是probe的方式)
Linux I2C 匯流排淺析
㈠ Overview
Linux的I2C體系結構分為3個組成部分:
·I2C核心:
I2C核心提供了I2C匯流排驅動和裝置驅動的註冊、登出方法,I2C通訊方法(即“algorithm”)上層的、與具體介面卡無關的程式碼以及探測裝置、檢測裝置地址的上層程式碼等。這部分是與平臺無關的。
·I2C匯流排驅動:
I2C匯流排驅動是對I2C硬體體系結構中介面卡端的實現。I2C匯流排驅動主要包含了I2C介面卡資料結構i2c_adapter、I2C介面卡的algorithm資料結構i2c_algorithm和控制I2C介面卡產生通訊訊號的函式。經由I2C匯流排驅動的程式碼,我們可以控制I2C介面卡以主控方式產生開始位、停止位、讀寫週期,以及以從裝置方式被讀寫、產生ACK等。不同的CPU平臺對應著不同的I2C匯流排驅動。
匯流排驅動的職責,是為系統中每個I2C匯流排增加相應的讀寫方法。但是匯流排驅動本身並不會進行任何的通訊,它只是存在在那裡,等待裝置驅動呼叫其函式。
這部分在MTK 6516中是由MTK已經幫我們實現了的,不需要我們更改。
· I2C裝置驅動:
I2C裝置驅動是對I2C硬體體系結構中裝置端的實現。裝置一般掛接在受CPU控制的I2C介面卡上,通過I2C介面卡與CPU交換資料。I2C裝置驅動主要包含了資料結構i2c_driver和i2c_client,我們需要根據具體裝置實現其中的成員函式。在Linux核心原始碼中的drivers目錄下的i2c_dev.c檔案,實現了I2C介面卡裝置檔案的功能,應用程式通過“i2c-%d”檔名並使用檔案操作介面open()、write()、read()、ioctl()和close()等來訪問這個裝置。應用層可以借用這些介面訪問掛接在介面卡上的I2C裝置的儲存空間或暫存器並控制I2C裝置的工作方式。
裝置驅動則是與掛在I2C總線上的具體的裝置通訊的驅動。通過I2C匯流排驅動提供的函式,裝置驅動可以忽略不同匯流排控制器的差異,不考慮其實現細節地與硬體裝置通訊。
這部分在MTK 6516中是由具體的裝置實現的。(比如camera)
struct i2c_client:
代表一個掛載到i2c總線上的i2c從裝置,該裝置所需要的資料結構,其中包括該i2c從裝置所依附的i2c主裝置 struct i2c_adapter *adapter 該i2c從裝置的驅動程式struct i2c_driver *driver 作為i2c從裝置所通用的成員變數,比如addr, name等 該i2c從裝置驅動所特有的資料,依附於dev->driver_data下
struct i2c_adapter:
代表主晶片所支援的一個i2c主裝置。
struct i2c_algorithm *algo:
是該i2c主裝置傳輸資料的一種演算法,或者說是在i2c總線上完成主從裝置間資料通訊的一種能力。
Linux的i2c子系統新、舊架構並存。主要分為舊架構(Legacy)也有人稱之為adapter方式,和新的架構new-style的方式。
這倆者的區別主要在於設備註冊和驅動註冊的不同。對於Legacy的設備註冊是在驅動執行的時候動態的建立,而新式的new-style則是採用靜態定義的方式。
注:MTK在Android2.1版上用的是Legacy的架構,而在Android2.2版上用的是new-style的架構。(在這裡我就只說明Android2.2的new-style的實現方法)
要完成I2C裝置的驅動,我們可以分三步走:
第一步:完成介面卡的註冊(匯流排);
第二步:完成I2C client的設備註冊(裝置);
第三步:完成I2C client驅動的註冊(驅動);
我們分別給予介紹:(I2C-mt6516.c)
⑴就匯流排而言,其本質只需要我們填充倆個結構體就可以了:
i2c_adapter;i2c_algorithm;
i2c_add_adapter(i2c->adap); 往總線上新增對應的介面卡;
struct i2c_adapter {
struct module *owner;
unsigned int id;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* --- administration stuff. */
int (*client_register)(struct i2c_client *);
int (*client_unregister)(struct i2c_client *);
/* data fields that are valid for all devices */
u8 level; /* nesting level for lockdep */
struct mutex bus_lock;
struct mutex clist_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr; /*該成員描述了匯流排號*/
struct list_head clients; /* i2c_client結構連結串列,該結構包含device,driver和 adapter結構*/
char name[48];
struct completion dev_released;
};
static struct i2c_algorithm mt6516_i2c_algorithm = {
.master_xfer = mt6516_i2c_transfer,
.smbus_xfer = NULL,
.functionality = mt6516_i2c_functionality,
};
2、設備註冊
第一步:
記得以前的i2c裝置驅動,裝置部分喜歡驅動執行的時候動態建立,新式的驅動傾向於向傳統的linux下裝置驅動看齊,採用靜態定義的方式來註冊裝置,使用介面為:
int __init i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
int status;
mutex_lock(&__i2c_board_lock);
/* dynamic bus numbers will be assigned after the last static one */
if (busnum >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = busnum + 1;
for (status = 0; len; len--, info++) {
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);//申請表示i2c裝置的結構體空間
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!\n");
status = -ENOMEM;
break;
}
/* 填寫i2c裝置描述結構 */
devinfo->busnum = busnum;
devinfo->board_info = *info;
list_add_tail(&devinfo->list, &__i2c_board_list);//新增到全域性連結串列__i2c_board_list中
}
mutex_unlock(&__i2c_board_lock);
return status;
}
在系統初始化的過程中,我們可以通過 i2c_register_board_info,將所需要的I2C從裝置加入一個名為__i2c_board_list雙向迴圈連結串列,系統在成功載入I2C主裝置adapt後,就會對這張連結串列裡所有I2C從裝置逐一地完成 i2c_client的註冊。
第二步:
系統初始化的時候,會根據板級i2c裝置配置資訊,建立i2c客戶端裝置(i2c_client),新增到i2c子系統中:
static void i2c_scan_static_board_info (struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
mutex_lock(&__i2c_board_lock);
list_for_each_entry(devinfo, &__i2c_board_list, list) { //遍歷全域性連結串列__i2c_board_list
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info))
printk(KERN_ERR "i2c-core: can't create i2c%d-%04x\n",
i2c_adapter_id(adapter),
devinfo->board_info.addr);
}
mutex_unlock(&__i2c_board_lock);
}
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
int status;
client = kzalloc(sizeof *client, GFP_KERNEL);
if (!client)
return NULL;
client->adapter = adap;
client->dev.platform_data = info->platform_data;
if (info->archdata)
client->dev.archdata = *info->archdata;
client->flags = info->flags;
client->addr = info->addr;
client->irq = info->irq;
strlcpy(client->name, info->type, sizeof(client->name));
/* Check for address business */
status = i2c_check_addr(adap, client->addr);
if (status)
goto out_err;
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
client->addr);
status = device_register(&client->dev);
if (status)
goto out_err;
dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
client->name, dev_name(&client->dev));
return client;
out_err:
dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
"(%d)\n", client->name, client->addr, status);
kfree(client);
return NULL;
}
IDR機制:完成的是裝置ID和結構體的關聯。
__i2c_first_dynamic_bus_num:當前系統允許的動態匯流排的最大值。
i2c_scan_static_board_info(adap);/*完成新型別i2c裝置的註冊,一般只在主機板初始化時*/
此函式為整個I2C子系統的核心,它會去遍歷一個由I2C從裝置組成的雙向迴圈連結串列,並完成所有I2C從裝置的i2c_client的註冊。
struct i2c_devinfo *devinfo; //已經建立好了的I2C從裝置連結串列
status = i2c_check_addr(adap, client->addr);
注:
特別要提一下的是這個“i2c_check_addr”,引用<<i2c 原始碼情景分析>>裡的話:“i2c 裝置的7 位地址是就當前i2c 匯流排而言的,是“相對地址”。不同的i2c 總線上的裝置可以使用相同的7 位地址,但是它們所在的i2c 匯流排不同。所以在系統中一個i2c 裝置的“絕對地址”由二元組(i2c 介面卡的ID 和裝置在該總線上的7 位地址)表示。”,所以這個函式的作用主要是排除同一i2c總線上出現多個地址相同的裝置。
3、I2C驅動註冊:
第一步:
static inline int i2c_add_driver(struct i2c_driver *driver)
{
return i2c_register_driver(THIS_MODULE, driver);
}
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
/* Can't register until after driver model init */
if (unlikely(WARN_ON(!i2c_bus_type.p)))
return -EAGAIN;
/* add the driver to the list of i2c drivers in the driver core */
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
/* When registration returns, the driver core
* will have called probe() for all matching-but-unbound devices.
*/
res = driver_register(&driver->driver);
if (res)
return res;
pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);
INIT_LIST_HEAD(&driver->clients);
/* Walk the adapters that are already present */
mutex_lock(&core_lock);
bus_for_each_dev(&i2c_bus_type, NULL, driver, __attach_adapter);
mutex_unlock(&core_lock);
return 0;
}
裝置和驅動的關聯過程:首先當I2C從裝置和I2C驅動如果處於同一條總線上,那麼其在裝置和驅動註冊之後,將會促使I2C_bus_type中的match獲得呼叫;()如下:
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.suspend = i2c_device_suspend,
.resume = i2c_device_resume,
};
繼續跟進i2c_device_match;
i2c_match_id(driver->id_table, client) != NULL;
我們回到i2c_device_probe;
這個函式的關鍵是:
status = driver->probe(client, i2c_match_id(driver->id_table, client));
它將函式的流程交回到了driver->probe的手中;
流程圖:
過程分享:
1、裝置和驅動的關聯
大家知道,對於一個驅動程式有兩個元素不可或缺,即裝置和驅動,一般驅動都是通過裝置名和驅動名的匹配建立關係的,最開始我從程式碼中只能發現驅動的註冊,卻不見設備註冊的蹤影,令人疑惑,跟蹤發現,在i2c adapter註冊時會遍歷i2c_board_info這樣一個結構,而這個結構在29以前或更早的核心裡是不存在的,它會完成驅動與裝置的匹配問題,
2、名字匹配
一個i2c驅動是可以有多個名字的,即一個驅動程式可以支援多個裝置,該機制是通過 struct i2c_device_id實現的,驅動中建立這麼一個結構體陣列,i2c架構層便會掃描該陣列,與裝置名去匹配,匹配成功的都會進入相應probe函式。
3、進入probe
該過程困惑了我一段時間,其實要進入自己驅動的probe首先需要進入匯流排的probe,而進入匯流排probe的前提是與匯流排的match成功。
待解決的困惑:
1、I2C從裝置名;
Legacy 的相關知識:
(一) Linux的I2C驅動框架中的主要資料結構及其關係
Linux的I2C驅動框架中的主要資料結構包括:i2c_driver、i2c_client、i2c_adapter和i2c_algorithm。
i2c_adapter對應於物理上的一個介面卡,這個介面卡是基於不同的平臺的,一個I2C介面卡需要i2c_algorithm中提供的通訊函式來控制介面卡,因此i2c_adapter中包含其使用的i2c_algorithm的指標。i2c_algorithm中的關鍵函式master_xfer()以i2c_msg為單位產生I2C訪問需要的訊號。不同的平臺所對應的master_xfer()是不同的,開發人員需要根據所用平臺的硬體特性實現自己的XXX_xfer()方法以填充i2c_algorithm的master_xfer指標。
i2c_ driver對應一套驅動方法,不對應於任何的物理實體。i2c_client對應於真實的物理裝置,每個I2C裝置都需要一個i2c_client來描述。i2c_client依附於i2c_adpater,這與I2C硬體體系中介面卡和裝置的關係一致。i2c_driver提供了i2c-client與i2c-adapter產生聯絡的函式。當attach a_dapter()函式探測物理裝置時,如果確定存在一個client,則把該client使用的i2c_client資料結構的adapter指標指向對應的i2e_ adapter,driver指標指向該i2c_driver,並呼叫i2e_adapter的client_register()函式來註冊此裝置。相反的過程發生在i2c_ driver的detach_client()函式被呼叫的時候。
(二) Linux的I2C體系結構中三個組成部分的作用
I2C核心提供了一組不依賴於硬體平臺的介面函式,I2C匯流排驅動和裝置驅動之間依賴於I2C核心作為紐帶。I2C核心提供了i2c_adapter的增加和刪除函式、i2c_driver的增加和刪除函式、i2c_client的依附和脫離函式以及i2c傳輸、傳送和接收函式。i2c傳輸函式i2c_transfer()用於進行I2C介面卡和I2C裝置之間的一組訊息互動i2c_master_send()函式和i2c_master_recv()函式內部會呼叫i2c_ transfer()函式分別完成一條寫訊息和一條讀訊息.
I2C匯流排驅動包括I2C介面卡驅動載入與解除安裝以及I2C匯流排通訊方法。其中I2C介面卡驅動載入(與解除安裝)要完成初始化(釋放)I2C介面卡所使用的硬體資源,申請I/0地址、中斷號、通過i2c_add_ adapter()新增i2c_adapter的資料結構(通過i2c_del_adapter()刪除i2c
_adapter的資料結構)的工作。12C匯流排通訊方法主要對特定的I2C介面卡實現i2c_algorithm的master_xfer()方法來實現i2c_ msg的傳輸。不同的介面卡對應的master_xfer()方法由其處理器的硬體特性決定。
I2C裝置驅動主要用於I2C裝置驅動模組載入與解除安裝以及提供I2C裝置驅動檔案操作介面。I2C裝置驅動的模組載入通用的方法遵循以下流程:首先通過register_chrdev()將I2C設備註冊為一個字元裝置,然後利用I2C核心中的i2c_add_a_dapter()新增i2c_driver。呼叫i2c_add_adapter()過程中會引發i2c_driver結構體中的YYY_attach_adapter()的執行,它通過呼叫I2C核心的i2e_probe()實現物理裝置的探測。i2c_probe()會引發yyy_detect()的呼叫。yyy_detect()中會初始化i2c_ client,然後呼叫核心的i2e_attach_client()通知I2C核心此時系統中包含了一個新的I2C裝置。之後會引發I2C裝置驅動中yyy_init_client()來初始化裝置。解除安裝過程執行相反的操作。
I2C裝置驅動模組載入與解除安裝的流程 如圖2 所示。