1. 程式人生 > >I2C匯流排驅動框架詳解

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