I2C總線驅動框架詳解


一、I2C子系統總體架構

1、三大組成部分

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

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

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

    I2C適配器是SoC中內置i2c控制器的軟件抽象,可以理解為他所代表的是一個I2C主機。

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

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

3、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。

 

4、相關的結構體

(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 };

 

5、關鍵文件(drivers\i2c)

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

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

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

 

二、I2C核心層源碼分析

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

1、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 };

 

2、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總線下的設備與設備驅動

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

 

3、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函數。

 

4、核心層開放給其他部分的注冊接口

(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適配器與設備之間的匹配(通過適配器編號)

 

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

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

1、入口函數:

從上面可以知道,adapter的注冊實現為模塊的方式,可以在內核配置的時候進行動態的加載和卸載,並且是基於platform平台總線,本文件提供的是platform平台設備驅動

的注冊,那么他的platform平台設備注冊在mach文件中,我這里是mach-x210.c文件。

 

2、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...)

 

四、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

1、I2C總線設備驅動的注冊

 

2、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******************************************************

 

     

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

          

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM