1、I2C子系統整體架構html

一、三大組成部分前端

(1)I2C核心(i2c-core):I2C核心提供了I2C總線驅動(適配器)和設備驅動的註冊、註銷方法,I2C通訊方法(」algorithm」)上層的,與具體硬件無關的代碼以及探測設備node

    檢測設備地址的上層代碼等。。
linux

(2)I2C總線驅動(I2Cadapter):I2C總線驅動是I2C適配器的軟件實現,提供I2C適配器與從設備間完成數據通訊的能力。I2C總線驅動由i2c_adapter和i2c_algorithm來描述git

    I2C適配器是SoC中內置i2c控制器的軟件抽象,能夠理解爲他所表明的是一個I2C主機。程序員

(3)I2C設備驅動(I2Cclient driver):包括兩部分:設備的註冊和設備驅動的註冊算法

二、I2C子系統的主要目標是:讓驅動開發者能夠在內核中方便的添加本身的I2C設備的驅動程序,讓內核統一管理I2C設備,從而能夠更容易的在linux下驅動本身的I2C接口硬件。編程

三、I2C子系統提供的兩種驅動實現方法(源碼中I2C相關的驅動均位於:drivers/i2c目錄下)數組

(1)第一種叫i2c-dev,對應drivers/i2c/i2c-dev.c,這種方法只是封裝了主機(I2Cmaster,通常是SoC中內置的I2C控制器)的I2C基本操做,而且嚮應用層提供相應的操做瀏覽器

接口,應用層代碼須要本身去實現對slave的控制和操做,因此這種I2C驅動至關於只是提供給應用層能夠訪問slave硬件設備的接口,自己並未對硬件作任何操做,應用須要實

現對硬件的操做,所以寫應用的人必須對硬件很是瞭解,其實至關於傳統的驅動中乾的活兒丟給應用去作了,因此這種I2C驅動又叫作「應用層驅動」,這種方式並不主流,它的優點是

把差別化都放在應用中,這樣在設備比較難纏(尤爲是slave是非標準I2C時)時不用動驅動,而只須要修改應用就能夠實現對各類設備的驅動。

(2)第二種I2C驅動是全部的代碼都放在驅動層實現,直接嚮應用層提供最終結果。應用層甚至不須要知道這裏面有I2C存在,譬如電容式觸摸屏驅動,直接嚮應用層提供/dev/input/event1

的操做接口,應用層編程的人根本不知道event1中涉及到了I2C。

 

四、相關的結構體

(1)struct i2c_adapter(I2C適配器)

struct i2c_adapter是用來描述一個I2C適配器,在SoC中的指的就是內部外設I2C控制器,當向I2C核心層註冊一個I2C適配器時就須要提供這樣的一個結構體變量。

複製代碼
 1 struct i2c_adapter {  2 struct module *owner; // 全部者  3 unsigned int id;  4 unsigned int class; // 該適配器支持的從設備的類型  5 const struct i2c_algorithm *algo; // 該適配器與從設備的通訊算法  6 void *algo_data;  7  8 /* data fields that are valid for all devices */  9 struct rt_mutex bus_lock; 10 11 int timeout; // 超時時間 12 int retries; 13 struct device dev; // 該適配器設備對應的device 14 15 int nr; // 適配器的編號 16 char name[48]; // 適配器的名字 17 struct completion dev_released; 18 19 struct list_head userspace_clients; // 用來掛接與適配器匹配成功的從設備i2c_client的一個鏈表頭 20 };
複製代碼

 

(2)struct i2c_algorithm(I2C算法)

struct i2c_algorithm結構體表明的是適配器的通訊算法,在構建i2c_adapter結構體變量的時候會去填充這個元素。

複製代碼
 1 struct i2c_algorithm {  2 /* If an adapter algorithm can't do I2C-level access, set master_xfer  3  to NULL. If an adapter algorithm can do SMBus access, set  4  smbus_xfer. If set to NULL, the SMBus protocol is simulated  5  using common I2C messages */  6 /* master_xfer should return the number of messages successfully  7  processed, or a negative value on error */  8 int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,  9 int num); 10 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, 11 unsigned short flags, char read_write, 12 u8 command, int size, union i2c_smbus_data *data); 13 14 /* To determine what the adapter supports */ 15 u32 (*functionality) (struct i2c_adapter *); 16 };
複製代碼

注意:smbus協議是從I2C協議的基礎上發展而來的,他們之間有很大的類似度,SMBus與I2C總線之間在時序特性上存在一些差異,應用於移動PC和桌面PC系統中的低速率通信。

 

(3)struct i2c_client

複製代碼
 1 struct i2c_client { // 用來描述一個i2c次設備  2 unsigned short flags; // 描述i2c次設備特性的標誌位  3 unsigned short addr; // i2c 次設備的地址  4  5 char name[I2C_NAME_SIZE]; // i2c次設備的名字  6 struct i2c_adapter *adapter; // 指向與次設備匹配成功的適配器  7 struct i2c_driver *driver; // 指向與次設備匹配成功的設備驅動  8 struct device dev; // 該次設備對應的device  9 int irq; // 次設備的中斷引腳 10 struct list_head detected; // 做爲一個鏈表節點掛接到與他匹配成功的i2c_driver 相應的鏈表頭上 11 };
複製代碼

 

(4)struct device_driver

複製代碼
 1 struct i2c_driver { // 表明一個i2c設備驅動  2 unsigned int class; // i2c設備驅動所支持的i2c設備的類型  3  4 /* Notifies the driver that a new bus has appeared or is about to be  5  * removed. You should avoid using this if you can, it will probably  6  * be removed in a near future.  7 */  8 int (*attach_adapter)(struct i2c_adapter *); // 用來匹配適配器的函數 adapter  9 int (*detach_adapter)(struct i2c_adapter *); 10 11 /* Standard driver model interfaces */ 12 int (*probe)(struct i2c_client *, const struct i2c_device_id *); // 設備驅動層的probe函數 13 int (*remove)(struct i2c_client *); // 設備驅動層卸載函數 14 15 /* driver model interfaces that don't relate to enumeration */ 16 void (*shutdown)(struct i2c_client *); 17 int (*suspend)(struct i2c_client *, pm_message_t mesg); 18 int (*resume)(struct i2c_client *); 19 20 /* Alert callback, for example for the SMBus alert protocol. 21  * The format and meaning of the data value depends on the protocol. 22  * For the SMBus alert protocol, there is a single bit of data passed 23  * as the alert response's low bit ("event flag"). 24 */ 25 void (*alert)(struct i2c_client *, unsigned int data); 26 27 /* a ioctl like command that can be used to perform specific functions 28  * with the device. 29 */ 30 int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); 31 32 struct device_driver driver; // 該i2c設備驅動所對應的device_driver 33 const struct i2c_device_id *id_table; // 設備驅動層用來匹配設備的id_table 34 35 /* Device detection callback for automatic device creation */ 36 int (*detect)(struct i2c_client *, struct i2c_board_info *); 37 const unsigned short *address_list; // 該設備驅動支持的全部次設備的地址數組 38 struct list_head clients; // 用來掛接與該i2c_driver匹配成功的i2c_client (次設備)的一個鏈表頭 39 };
複製代碼

 

複製代碼
 1 struct i2c_board_info { // 這個結構體是用來描述板子上的一個i2c設備的信息  2 char type[I2C_NAME_SIZE]; // i2c 設備的名字,用來初始化i2c_client.name  3 unsigned short flags; // 用來初始化i2c_client.flags  4 unsigned short addr; // 用來初始化 i2c_client.addr  5 void *platform_data; // 用來初始化 i2c_client.dev.platform_data  6 struct dev_archdata *archdata; // 用來初始化i2c_client.dev.archdata  7 #ifdef CONFIG_OF  8 struct device_node *of_node;  9 #endif 10 int irq; // 用來初始化i2c_client.irq 11 }; 12 13 14 15 struct i2c_devinfo { 16 struct list_head list; // 做爲一個鏈表節點掛接到__i2c_board_list 鏈表上去 17 int busnum; // 適配器的編號 18 struct i2c_board_info board_info; // 內置的i2c_board_info 結構體 19 };
複製代碼

 

五、關鍵文件(drivers\i2c)

(1)i2c-core.c:  i2c核心層

(2)busses目錄:這個文件中是已經編寫好的各類向i2c核心層註冊的適配器

(3)algos目錄:這個目錄裏面是一些I2C通訊算法

 

2、I2C核心層源碼分析

跟之前的分析同樣,i2c子系統源代碼一樣是實現爲一個模塊,在內核配置的時候能夠進行動態的加載和卸載。

一、I2C子系統註冊函數:i2c_init

複製代碼
 1 static int __init i2c_init(void)  2 {  3 int retval;  4  5 retval = bus_register(&i2c_bus_type); // 註冊i2c總線 /sys/bus/i2c  6 if (retval)  7 return retval;  8 #ifdef CONFIG_I2C_COMPAT  9 i2c_adapter_compat_class = class_compat_register("i2c-adapter"); 10 if (!i2c_adapter_compat_class) { 11 retval = -ENOMEM; 12 goto bus_err; 13  } 14 #endif 15 retval = i2c_add_driver(&dummy_driver); // 註冊一個空設備驅動 /sys/bus/i2c/driver/dummy 16 if (retval) 17 goto class_err; 18 return 0; 19 20 class_err: 21 #ifdef CONFIG_I2C_COMPAT 22  class_compat_unregister(i2c_adapter_compat_class); 23 bus_err: 24 #endif 25 bus_unregister(&i2c_bus_type); 26 return retval; 27 }
複製代碼

 

複製代碼
1 struct bus_type i2c_bus_type = { 2 .name = "i2c", // 總線的名字 3 .match = i2c_device_match, // 總線下設備與設備驅動的匹配函數 4 .probe = i2c_device_probe, // 總線層的probr函數 5 .remove = i2c_device_remove, // 總線卸載時執行的函數 6 .shutdown = i2c_device_shutdown, 7 .pm = &i2c_device_pm_ops, // 電源管理 8 };
複製代碼

 

二、i2c_device_match函數分析:

複製代碼
 1 static int i2c_device_match(struct device *dev, struct device_driver *drv)  2 {  3 struct i2c_client *client = i2c_verify_client(dev); // 經過device指針獲取到對應的i2c_client指針  4 struct i2c_driver *driver; // 定義一個i2c_driver 指針  5  6 if (!client)  7 return 0;  8  9 driver = to_i2c_driver(drv); // 經過device_driver指針獲取到對應的i2c_driver指針 10 /* match on an id table if there is one */ 11 if (driver->id_table) // 若是設備驅動中存在id_table表,則經過這個來進行與設備的匹配 12 return i2c_match_id(driver->id_table, client) != NULL; // 匹配的方式就是比較 id_table指向的i2c_device_id數組中各個元素的名字 13 // 若是匹配成功就會返回這個 i2c_device_id 元素項地址 14 return 0; // 匹配失敗返回 0 15 }
複製代碼

由i2c_match_id函數分析能夠知道,在i2c總線下的設備與設備驅動的匹配是經過設備的名字和設備驅動的i2c_device_id表中的各個表項中的依次進行匹配的,只要有一個匹配成功

則設備和設備驅動就匹配成功了,若是都沒有匹配成功則代表匹配失敗;由此能夠對比platform平臺總線的匹配原則,他們基本是一致的,可是platform總線下的設備與設備驅動

匹配過程當中還會將設備的名字和設備驅動的名字進行一個匹配,若是這個也沒有成功才說明設備與設備驅動匹配過程失敗。

 

三、i2c_device_probe函數分析:

複製代碼
 1 static int i2c_device_probe(struct device *dev)  2 {  3 struct i2c_client *client = i2c_verify_client(dev); // 經過device指針獲取到對應的i2c_client指針  4 struct i2c_driver *driver;  5 int status;  6  7 if (!client)  8 return 0;  9 10 driver = to_i2c_driver(dev->driver); // 經過device->driver指針獲取到對應的i2c_driver指針 11 if (!driver->probe || !driver->id_table) 12 return -ENODEV; 13 client->driver = driver; // i2c設備經過i2c_client->driver指針去指向與他匹配成功的設備驅動i2c_driver 14 if (!device_can_wakeup(&client->dev)) 15 device_init_wakeup(&client->dev, 16 client->flags & I2C_CLIENT_WAKE); 17 dev_dbg(dev, "probe\n"); 18 19 status = driver->probe(client, i2c_match_id(driver->id_table, client)); // 調用設備驅動層的probe函數 20 if (status) { 21 client->driver = NULL; 22  i2c_set_clientdata(client, NULL); 23  } 24 return status; 25 }
複製代碼

從上面的分析能夠知道,總線層的probe函數最終仍是會去調用設備驅動層的probe函數。

 

四、核心層開放給其餘部分的註冊接口

(1)i2c_add_adapter/i2c_add_numbered_adapter(註冊adapter)

/******************************************************************/

i2c_add_adapter

     i2c_register_adapter

 

i2c_add_numbered_adapter

     i2c_register_adapter

/******************************************************************/

i2c_register_adapter函數是I2C子系統核心層提供給I2C總線驅動層的用來向核心層註冊一個adapter(適配器)的接口函數。

從上能夠知道這兩個函數最終都是調用i2c_register_adapter函數去註冊adapter,他們的區別在於:i2c_add_adapter函數是自動分配適配器編號,而i2c_add_numbered_adapter

是須要本身手動指定一個適配器編號。這個編號的做用第一是爲了i2c子系統方便管理系統中的adapter。

i2c_register_adapter函數分析:

複製代碼
 1 static int i2c_register_adapter(struct i2c_adapter *adap) // 向i2c總線註冊適配器adapter  2 {  3 int res = 0, dummy;  4  5 /* Can't register until after driver model init */  6 if (unlikely(WARN_ON(!i2c_bus_type.p))) {  7 res = -EAGAIN;  8 goto out_list;  9  } 10 11 rt_mutex_init(&adap->bus_lock); 12 INIT_LIST_HEAD(&adap->userspace_clients); // 初始化i2c_adapter->userspace_clients鏈表 13 14 /* Set default timeout to 1 second if not already set */ 15 if (adap->timeout == 0) 16 adap->timeout = HZ; 17 18 dev_set_name(&adap->dev, "i2c-%d", adap->nr); // 設置適配器設備的名字 i2c-%d(nr) 19 adap->dev.bus = &i2c_bus_type; // 設置設備的總線類型 20 adap->dev.type = &i2c_adapter_type; // 設置設備的設備類型 21 res = device_register(&adap->dev); // 註冊設備 若是前面沒有指定父設備那麼建立的設備文件是: /sys/devices/i2c-%d 22 if (res) // samsung在註冊適配器的時候是指定了父設備的,因此他建立的設備是: /sys/devices/platform/s3c2410-i2cn/i2c-%d 23 goto out_list; // 爲何是這個會在後面說到 24 25 dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name); 26 27 #ifdef CONFIG_I2C_COMPAT 28 res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev, 29 adap->dev.parent); 30 if (res) 31 dev_warn(&adap->dev, 32 "Failed to create compatibility class link\n"); 33 #endif 34 35 /* create pre-declared device nodes */ 36 if (adap->nr < __i2c_first_dynamic_bus_num) 37 i2c_scan_static_board_info(adap); // 掃描__i2c_board_list鏈表上掛接的全部的i2c次設備信息並與適配器進行匹配,匹配成功建立i2c次設備 38 39 /* Notify drivers */ 40 mutex_lock(&core_lock); 41 dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap, 42  __process_new_adapter); 43 mutex_unlock(&core_lock); 44 45 return 0; 46 47 out_list: 48 mutex_lock(&core_lock); 49 idr_remove(&i2c_adapter_idr, adap->nr); 50 mutex_unlock(&core_lock); 51 return res; 52 }
複製代碼

 

i2c_scan_static_board_info函數分析:

複製代碼
 1 static void i2c_scan_static_board_info(struct i2c_adapter *adapter)  2 {  3 struct i2c_devinfo *devinfo; // 定義一個i2c_devinfo 結構體指針  4  5 down_read(&__i2c_board_lock);  6 list_for_each_entry(devinfo, &__i2c_board_list, list) { // 遍歷 __i2c_board_list 鏈表上的全部i2c_devinfo 結構體  7 if (devinfo->busnum == adapter->nr // 比較 i2c_devinfo->busnum 與 適配器的編號是否匹配  8 && !i2c_new_device(adapter, // 若是匹配就會調用 i2c_new_device 函數進行註冊添加新的次設備 i2c_client  9 &devinfo->board_info)) 10 dev_err(&adapter->dev, 11 "Can't create device at 0x%02x\n", 12 devinfo->board_info.addr); 13  } 14 up_read(&__i2c_board_lock); 15 }
複製代碼

 

i2c_new_device函數分析:

複製代碼
 1 struct i2c_client *  2 i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)  3 {  4 struct i2c_client *client; // 定義一個 i2c_client 指針  5 int status;  6  7 client = kzalloc(sizeof *client, GFP_KERNEL); // 申請分配  8 if (!client)  9 return NULL; 10 11 // 對i2c_client結構體變量進行填充 12 client->adapter = adap; // i2c次設備經過i2c_client->adapter指針去指向與它匹配成功的適配器i2c_adapter 13 client->dev.platform_data = info->platform_data; // 將傳進來的i2c_board_info結構體做爲i2c次設備的platform平臺數據 14 15 if (info->archdata) 16 client->dev.archdata = *info->archdata; 17 18 client->flags = info->flags; // 標誌位 19 client->addr = info->addr; // i2c次設備的地址 20 client->irq = info->irq; // 中斷號 21 22 strlcpy(client->name, info->type, sizeof(client->name)); // 名字 23 24 /* Check for address validity */ 25 status = i2c_check_client_addr_validity(client); // 次設備地址校驗 26 if (status) { 27 dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n", 28 client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr); 29 goto out_err_silent; 30  } 31 32 /* Check for address business */ 33 status = i2c_check_addr_busy(adap, client->addr); 34 if (status) 35 goto out_err; 36 37 client->dev.parent = &client->adapter->dev; // 指定i2c 次設備的父設備是與它匹配成功的適配器對應的設備 38 client->dev.bus = &i2c_bus_type; // 指定次設備的總線類型 39 client->dev.type = &i2c_client_type; // 指定次設備的設備類型 40 #ifdef CONFIG_OF 41 client->dev.of_node = info->of_node; 42 #endif 43 44 dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap), // 設置次設備的名字 %d-%04x 45 client->addr); 46 status = device_register(&client->dev); // 註冊次設備: /sys/devices/platform/s3c2410-i2cn/i2c-%d/%d-%04x 47 if (status) 48 goto out_err; 49 50 dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n", 51 client->name, dev_name(&client->dev)); 52 53 return client; 54 55 out_err: 56 dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x " 57 "(%d)\n", client->name, client->addr, status); 58 out_err_silent: 59  kfree(client); 60 return NULL; 61 }
複製代碼

i2c_new_device是I2C核心層提供給設備層註冊i2c設備時使用的一個接口函數,咱們能夠在註冊i2c設備時直接調用這個函數進行註冊,在這裏i2c核心層還提供了另外一種

機制去實現制動註冊,他的原理就是: 咱們在系統啓動的時候在他的硬件機器初始化函數中(例如: smdkc110_machine_init)去註冊板子上的i2c次設備

(實際上就是構建i2c_devinfo結構體變量掛接到__i2c_board_list鏈表上),當咱們去向i2c總線核心層註冊適配器的時候就會去掃描該鏈表,而後根據

相關的信息去註冊i2c次設備,也就是上面的那個函數的意義了。  

/***********************************************************************************************************/

smdkc110_machine_init

     platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices));    

     s3c_i2c1_set_platdata(NULL);              /* 這個是設置的是SoC中的i2c控制器(適配器)做爲平臺設備的私有數據 */

     i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs0));   // 經過這個函數註冊板子上的i2c次設備的信息

/*********************************************************************************************************/

 

i2c_add_driver函數分析:

i2c_add_driver函數是定義在 include\linux\i2c.h 頭文件中的一個靜態內斂函數,可是函數內部是直接調用了I2C核心層的i2c_register_driver函數來進行正真的工做。

該函數是提供用來註冊一個I2C總線下的設備驅動的接口。

複製代碼
 1 int i2c_register_driver(struct module *owner, struct i2c_driver *driver)  2 {  3 int res;  4  5 /* Can't register until after driver model init */  6 if (unlikely(WARN_ON(!i2c_bus_type.p)))  7 return -EAGAIN;  8  9 /* add the driver to the list of i2c drivers in the driver core */ 10 driver->driver.owner = owner; 11 driver->driver.bus = &i2c_bus_type; // 指定該設備驅動的總線類型 i2c 12 13 /* When registration returns, the driver core 14  * will have called probe() for all matching-but-unbound devices. 15 */ 16 res = driver_register(&driver->driver); // 註冊設備驅動 /sys/bus/i2c/drivers/dummy dummy就是一個設備驅動文件 17 if (res) 18 return res; 19 20 pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name); 21 22 INIT_LIST_HEAD(&driver->clients); // 初始化i2c_driver -> clients 鏈表 23 /* Walk the adapters that are already present */ 24 mutex_lock(&core_lock); 25 bus_for_each_dev(&i2c_bus_type, NULL, driver, __process_new_driver); // 能夠忽略這條語句的執行效果 26 mutex_unlock(&core_lock); 27 28 return 0; 29 }
複製代碼

總結:從上面的分析其實能夠知道:i2c子系統內部存在着2個匹配過程:

(1)i2c總線下的設備與設備驅動之間的匹配(經過設備驅動的id_table)

(2)adapter適配器與設備之間的匹配(經過適配器編號)

 

3、I2C總線驅動層代碼分析(drivers\i2c\busses\i2c-s3c2410.c)

I2C總線驅動是I2C適配器的軟件實現,提供I2C適配器與從設備間完成數據通訊的能力,I2C總線驅動由i2c_adapter和i2c_algorithm來描述。

一、入口函數:

從上面能夠知道,adapter的註冊實現爲模塊的方式,能夠在內核配置的時候進行動態的加載和卸載,而且是基於platform平臺總線,本文件提供的是platform平臺設備驅動

的註冊,那麼他的platform平臺設備註冊在mach文件中,我這裏是mach-x210.c文件。

 

二、probe函數分析:

複製代碼
  1 static int s3c24xx_i2c_probe(struct platform_device *pdev)  2 {  3 struct s3c24xx_i2c *i2c; // 次結構體是三星對本SoC中的i2c控制器的一個描述,是一個用來在多文件中進行數據傳遞的全局結構體  4 struct s3c2410_platform_i2c *pdata; // 此結構體用來表示平臺設備的私有數據  5 struct resource *res;  6 int ret;  7  8 pdata = pdev->dev.platform_data; // 獲取到平臺設備層中的平臺數據  9 if (!pdata) {  10 dev_err(&pdev->dev, "no platform data\n");  11 return -EINVAL;  12  }  13  14 i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL); // 給s3c24xx_i2c類型指針申請分配內存空間  15 if (!i2c) {  16 dev_err(&pdev->dev, "no memory for state\n");  17 return -ENOMEM;  18  }  19  20 // 如下主要是對s3c24xx_i2c 結構體中的i2c_adapter變量的一個填充  21 strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); // 設置適配器的名字 s3c2410-i2c  22 i2c->adap.owner = THIS_MODULE;  23 i2c->adap.algo = &s3c24xx_i2c_algorithm; // 通訊算法  24 i2c->adap.retries = 2;  25 i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; // 該適配器所支持的次設備類有哪些  26 i2c->tx_setup = 50;  27  28 spin_lock_init(&i2c->lock); // 初始化互斥鎖  29 init_waitqueue_head(&i2c->wait); // 初始化工做隊列  30  31 /* find the clock and enable it */  32  33 i2c->dev = &pdev->dev; // 經過s3c24xx_i2c->dev 指針指向平臺設備的device結構體  34 i2c->clk = clk_get(&pdev->dev, "i2c");  35  36 if (IS_ERR(i2c->clk)) {  37 dev_err(&pdev->dev, "cannot get clock\n");  38 ret = -ENOENT;  39 goto err_noclk;  40  }  41  42 dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);  43  44 clk_enable(i2c->clk); // 使能時鐘  45  46 /* map the registers */  47  48 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 獲取平臺設備資源  49 if (res == NULL) {  50 dev_err(&pdev->dev, "cannot find IO resource\n");  51 ret = -ENOENT;  52 goto err_clk;  53  }  54  55 i2c->ioarea = request_mem_region(res->start, resource_size(res), // 物理地址到虛擬地址的映射請求  56 pdev->name);  57  58 if (i2c->ioarea == NULL) {  59 dev_err(&pdev->dev, "cannot request IO\n");  60 ret = -ENXIO;  61 goto err_clk;  62  }  63  64 i2c->regs = ioremap(res->start, resource_size(res)); // 地址映射  65  66 if (i2c->regs == NULL) { 67 dev_err(&pdev->dev, "cannot map IO\n"); 68 ret = -ENXIO; 69 goto err_ioarea; 70 } 71 72 dev_dbg(&pdev->dev, "registers %p (%p, %p)\n", 73 i2c->regs, i2c->ioarea, res); 74 75 /* setup info block for the i2c core */ 76 77 i2c->adap.algo_data = i2c; // 將s3c24xx_i2c 結構體變量做爲s3c24xx_i2c中內置的i2c_adapter適配器中的私有數據 78 i2c->adap.dev.parent = &pdev->dev; // 指定適配器設備的父設備是平臺設備device : /sys/devices/platform/s3c2410-i2cn這個目錄下 79 80 /* initialise the i2c controller */ 81 82 ret = s3c24xx_i2c_init(i2c); // i2c控制器(適配器) 寄存器相關的配置 83 if (ret != 0) 84 goto err_iomap; 85 86 /* find the IRQ for this unit (note, this relies on the init call to 87 * ensure no current IRQs pending 88 */ 89 90 i2c->irq = ret = platform_get_irq(pdev, 0); // 獲取平臺設備中的i2c中斷號(這個中斷是I2C控制器產生的中斷) 91 if (ret <= 0) { 92 dev_err(&pdev->dev, "cannot find IRQ\n"); 93 goto err_iomap; 94 } 95 96 ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED, // 申請中斷 97 dev_name(&pdev->dev), i2c); 98 99 if (ret != 0) { 100 dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq); 101 goto err_iomap; 102 } 103 104 ret = s3c24xx_i2c_register_cpufreq(i2c); // 這個不清楚 105 if (ret < 0) { 106 dev_err(&pdev->dev, "failed to register cpufreq notifier\n"); 107 goto err_irq; 108 } 109 110 /* Note, previous versions of the driver used i2c_add_adapter() 111 * to add the bus at any number. We now pass the bus number via 112 * the platform data, so if unset it will now default to always 113 * being bus 0. 114 */ 115 116 i2c->adap.nr = pdata->bus_num; // 肯定i2c主機(適配器)的編號 117 118 ret = i2c_add_numbered_adapter(&i2c->adap); // 向i2c核心註冊i2c適配器 /sys/devices/platform/s3c2410-i2cn/s3c2410-i2c 由於在函數內會將 i2c-%d做爲適配器的名字 119 if (ret < 0) { 120 dev_err(&pdev->dev, "failed to add bus to i2c core\n"); 121 goto err_cpufreq; 122 } 123 124 platform_set_drvdata(pdev, i2c); // 將s3c24xx_i2c變量做爲平臺設備私有數據中的設備驅動私有數據 dev->p->driver_data 125 // 由於這個變量還會在本文件中其餘函數中會用到了 126 clk_disable(i2c->clk); 127 128 dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev)); 129 return 0; 130 131 err_cpufreq: 132 s3c24xx_i2c_deregister_cpufreq(i2c); 133 134 err_irq: 135 free_irq(i2c->irq, i2c); 136 137 err_iomap: 138 iounmap(i2c->regs); 139 140 err_ioarea: 141 release_resource(i2c->ioarea); 142 kfree(i2c->ioarea); 143 144 err_clk: 145 clk_disable(i2c->clk); 146 clk_put(i2c->clk); 147 148 err_noclk: 149 kfree(i2c); 150 return ret; 151 }
複製代碼

 

struct i2c_msg:

複製代碼
 1 struct i2c_msg {  2 __u16 addr; /* slave address 設備地址 */  3 __u16 flags; /* 本次消息的標誌位,就是下面的這些 */  4 #define I2C_M_TEN 0x0010 /* 設置了這個標誌位表示從設備的地址是10bit */  5 #define I2C_M_RD 0x0001 /* 設置了這個標誌位表示本次通訊i2c控制器是處於接收方,不然就是發送方 */  6 #define I2C_M_NOSTART 0x4000  7 #define I2C_M_REV_DIR_ADDR 0x2000 /* 設置這個標誌位表示須要將讀寫標誌位反轉過來 */  8 #define I2C_M_IGNORE_NAK 0x1000 /* 設置這個標誌意味當前i2c_msg忽略I2C器件的ack和nack信號 */  9 #define I2C_M_NO_RD_ACK 0x0800 /* 設置這個標誌位表示在讀操做中主機不用ACK */ 10 #define I2C_M_RECV_LEN 0x0400 11 __u16 len; /* 數據長度 */ 12 __u8 *buf; /* 數據緩衝區指針 */ 13 };
複製代碼

這個結構體就是用來表示一個通訊週期的數據相關的結構體,包括通訊從設備的信息,通訊數據長度等等。函數中填充的通訊算法會在i2c主設備與從設備

通訊的時候調用到。

平臺設備的註冊會隨着smdkc110_machine_init函數中的platform_add_devices函數的執行被註冊(s3c_device_i2c0、s3c_device_i2c1...)

 

4、I2C設備驅動層代碼分析(drivers\input\touchscreen\gslX680.c)

同platform平臺總線同樣,I2C總線下也是分爲i2c總線設備層(struct i2c_client)和i2c總線設備驅動層(struct i2c_driver),這兩個結構體已經在上面分析過了

由於個人板子上使用的是一款I2C接口的電容觸摸屏:gslx680

因此就以這個I2C設備爲例進行分析(源代碼文件: gslX680.c),代碼是由觸摸品IC原廠工程師提供的,代碼中涉及到不少的觸摸屏專業方面的知識,這個就不用去管了。

一樣的gslX680.c文件提供的是I2C設備驅動的註冊,相應的I2C設備註冊是在mach文件中,我這裏一樣仍是:mach-x210.c

一、I2C總線設備驅動的註冊

 

二、gsl_ts_probe函數分析

複製代碼
 1 static int __devinit gsl_ts_probe(struct i2c_client *client,  2 const struct i2c_device_id *id)  3 {  4 struct gsl_ts *ts; // 設備驅動層封裝的一個全局結構體  5 int rc;  6  7 print_info("GSLX680 Enter %s\n", __func__);  8 if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {  9 dev_err(&client->dev, "I2C functionality not supported\n"); 10 return -ENODEV; 11  } 12 13 ts = kzalloc(sizeof(*ts), GFP_KERNEL); // 給 gsl_ts類型的指針申請分配內存 14 if (!ts) 15 return -ENOMEM; 16 print_info("==kzalloc success=\n"); 17 18 ts->client = client; // 經過gsl_ts->client指針去指向傳進來的i2c次設備i2c_client 19 i2c_set_clientdata(client, ts); // 將gsl_ts做爲i2c次設備的私有數據區中的設備驅動私有數據 20 ts->device_id = id->driver_data; 21 22 rc = gslX680_ts_init(client, ts); // 初始化操做 23 if (rc < 0) { 24 dev_err(&client->dev, "GSLX680 init failed\n"); 25 goto error_mutex_destroy; 26  } 27 28 gsl_client = client; // 經過一個全局的i2c_client指針gsl_client去指向傳進來的i2c次設備i2c_client 29 30 gslX680_init(); // gslX680 觸摸屏相關的gpio初始化操做 31 init_chip(ts->client); // gslX680觸摸屏芯片相關的初始化操做 32 check_mem_data(ts->client); 33 34 rc= request_irq(client->irq, gsl_ts_irq, IRQF_TRIGGER_RISING, client->name, ts); // 申請中斷,這個中斷是接在SoC的一個外部中斷引腳上的 35 if (rc < 0) { // 當發生中斷的時候表示有數據能夠進行讀取了,那麼就會通知 36 print_info( "gsl_probe: request irq failed\n"); // I2C主機去去讀數據 37 goto error_req_irq_fail; 38  } 39 40 /* create debug attribute */ 41 //rc = device_create_file(&ts->input->dev, &dev_attr_debug_enable); 42 43 #ifdef CONFIG_HAS_EARLYSUSPEND 44 ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; 45 //ts->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 1; 46 ts->early_suspend.suspend = gsl_ts_early_suspend; 47 ts->early_suspend.resume = gsl_ts_late_resume; 48 register_early_suspend(&ts->early_suspend); 49 #endif 50 51 52 #ifdef GSL_MONITOR 53 print_info( "gsl_ts_probe () : queue gsl_monitor_workqueue\n"); 54 55 INIT_DELAYED_WORK(&gsl_monitor_work, gsl_monitor_worker); 56 gsl_monitor_workqueue = create_singlethread_workqueue("gsl_monitor_workqueue"); 57 queue_delayed_work(gsl_monitor_workqueue, &gsl_monitor_work, 1000); 58 #endif 59 60 print_info("[GSLX680] End %s\n", __func__); 61 62 return 0; 63 64 //exit_set_irq_mode: 65 error_req_irq_fail: 66 free_irq(ts->irq, ts); 67 68 error_mutex_destroy: 69 input_free_device(ts->input); 70 kfree(ts); 71 return rc; 72 }
複製代碼

 

gslX680_ts_init函數分析:

複製代碼
 1 static int gslX680_ts_init(struct i2c_client *client, struct gsl_ts *ts)  2 {  3 struct input_dev *input_device; // 定義一個 input_dev 結構體指針  4 int rc = 0;  5  6 printk("[GSLX680] Enter %s\n", __func__);  7  8 ts->dd = &devices[ts->device_id];  9 10 if (ts->device_id == 0) { 11 ts->dd->data_size = MAX_FINGERS * ts->dd->touch_bytes + ts->dd->touch_meta_data; 12 ts->dd->touch_index = 0; 13  } 14 15 ts->touch_data = kzalloc(ts->dd->data_size, GFP_KERNEL); 16 if (!ts->touch_data) { 17 pr_err("%s: Unable to allocate memory\n", __func__); 18 return -ENOMEM; 19  } 20 21 input_device = input_allocate_device(); // 給input_device指針申請分配內存 22 if (!input_device) { 23 rc = -ENOMEM; 24 goto error_alloc_dev; 25  } 26 27 ts->input = input_device; // 經過gsl_ts->input指針去指向input輸入設備 28 input_device->name = GSLX680_I2C_NAME; // 設置input設備的名字 29 input_device->id.bustype = BUS_I2C; // 設置input設備的總線類型 30 input_device->dev.parent = &client->dev; // 設置input設備的父設備: /sys/devices/platform/s3c2410-i2cn/i2c-%d/%d-%04x 31 // 可是經過後面的分析可知,最終不是這個父設備 32 input_set_drvdata(input_device, ts); // 將gsl_ts結構體做爲input設備的私有數據區中的設備驅動數據 33 34 // 如下是對input_dev 輸入設備的一些設置 設置input設備能夠上報的事件類型 35 set_bit(EV_ABS, input_device->evbit); 36 set_bit(BTN_TOUCH, input_device->keybit); 37 set_bit(EV_ABS, input_device->evbit); 38 set_bit(EV_KEY, input_device->evbit); 39 input_set_abs_params(input_device, ABS_X, 0, SCREEN_MAX_X, 0, 0); 40 input_set_abs_params(input_device, ABS_Y, 0, SCREEN_MAX_Y, 0, 0); 41 input_set_abs_params(input_device, ABS_PRESSURE, 0, 1, 0, 0); 42 #ifdef HAVE_TOUCH_KEY 43 input_device->evbit[0] = BIT_MASK(EV_KEY); 44 //input_device->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); 45 for (i = 0; i < MAX_KEY_NUM; i++) 46 set_bit(key_array[i], input_device->keybit); 47 #endif 48 49 client->irq = IRQ_PORT; // 觸摸屏使用到的中斷號 50 ts->irq = client->irq; // 51 52 ts->wq = create_singlethread_workqueue("kworkqueue_ts"); 53 if (!ts->wq) { 54 dev_err(&client->dev, "Could not create workqueue\n"); 55 goto error_wq_create; 56  } 57 flush_workqueue(ts->wq); 58 59 INIT_WORK(&ts->work, gslX680_ts_worker); // 初始化工做隊列 當發生中斷的時候在在中斷處理函數中就會調用這個工做隊列 60 // 做爲中斷的下半部 61 rc = input_register_device(input_device); // 註冊input設備 62 if (rc) 63 goto error_unreg_device; 64 65 return 0; 66 67 error_unreg_device: 68 destroy_workqueue(ts->wq); 69 error_wq_create: 70 input_free_device(input_device); 71 error_alloc_dev: 72 kfree(ts->touch_data); 73 return rc; 74 }
複製代碼

/*******************************************************************************************/

由於在中斷中須要作的事情不少,因此在這裏採用了中斷上下半部的方式來處理,上半部就是probe函數中的申請中斷時綁定的函數gsl_ts_irq

在這個函數中作了一些最必要作的事情,而後開啓下半部,在下半部中繼續執行未處理完的事情gslX680_ts_worker。

須要注意的是這裏的中斷指的是觸摸屏方的中斷信號,他的思想是這樣的:當有人按下電容觸摸屏的時候,在觸摸屏IC就會將產生的模擬量轉化爲數字量,當轉換完成以後

因爲I2C協議自己的限制,全部的通訊週期都是由主機方發起,從機只能被動的相應。I2c控制器這邊如何知道數據已經能夠讀取了呢?這就得經過觸摸屏接口這邊引出一個

中斷信號引腳來告知I2C主機控制器讀取數據,而後I2C主機就會發起一次通訊來讀取數據。

因此由此能夠推知在gslX680_ts_worker(中斷下半部)函數中須要作兩件事: 讀取觸摸屏數據、向input核心層上報數據

/*****************************************************************************/

gslX680_ts_worker

     gsl_ts_read(I2C總線設備驅動層提供的函數)

          i2c_master_recv (I2C子系統核心層提供的函數)

               i2c_transfer(I2C子系統核心層提供的函數)

                    adap->algo->master_xfer(adap, msgs, num)(I2C子系統總線驅動層提供的函數)       

/****************************************************************************/

gsl_ts_write(I2C總線設備驅動層提供的函數)

     i2c_master_send(I2C子系統核心層提供的函數)

          i2c_transfer(I2C子系統核心層提供的函數)

               adap->algo->master_xfer(adap, msgs, num)(I2C子系統總線驅動層提供的函數)   

/******************************************************/

如今來分析一下整個的工做流程:

當觸摸屏有人按下而且在觸摸屏IC中已經完成了AD轉換以後就會產生一箇中斷信號給I2C控制器,就會觸發I2C設備驅動層的中斷函數。在設備驅動層的中斷函數中會調用

核心層提供的讀寫函數來讀取觸摸屏的數據,而後核心層最終調用的是I2C總線驅動層(適配器)中註冊的通訊算法函數來發起一個起始信號來開啓一個接收數據的通訊週期,

以後的事情就都交給I2C總線驅動層的中斷函數來讀取數據了,當數據讀取完成以後就會將數據存放在緩衝區中。因此咱們最終在設備驅動層的中斷函數中將讀取到的數據進

行一些處理,而後上報給input核心層。

 

 

************************************************end******************************************************

 

     

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

          

 

分類: Linux驅動
好文要頂 關注我 收藏該文
0
0
 
« 上一篇: Linux內核中斷學習
» 下一篇: gslX680驅動的移植實踐