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 所示。