linux下i2c驅動筆記 轉

1. 幾個基本概念

1.1. 設備模型

由 總線(bus_type) 設備(device) 驅動(device_driver) 組成,在該模型下,全部的設備經過總線鏈接起來,即便有些設備沒有鏈接到一根物理總線上,linux爲其設置了一個內部的、虛擬的platform總線,用以維持總線、驅動、設備的關係。node

所以,對於實現一個linux下的設備驅動,能夠劃分爲兩大步:linux

一、設備註冊;算法

二、驅動註冊。數據結構

 

固然,其中還有一些細節問題:架構

一、驅動的probe函數函數

二、驅動和設備是怎麼進行綁定的。spa

1.2. i2c設備驅動的幾個數據結構

 

i2c_adapter指針

每個i2c_adapter對應一個物理上的i2c控制器,在i2c總線驅動probe函數中動態建立。經過i2c_add_adapter註冊到i2c_corecode

 

i2c_algorithmorm

i2c_algorithm中的關鍵函數master_xfer(),以i2c_msg爲單位產生i2c訪問須要的信號。不一樣的平臺所對應的master_xfer()是不一樣的,須要根據所用平臺的硬件特性實現本身的xxx_xfer()方法以填充i2c_algorithmmaster_xfer指針;在A31上便是sun6i_i2c_algorithm函數。

 

i2c_client

表明一個掛載到i2c總線上的i2c從設備,包含該設備所須要的數據:

i2c從設備所依附的i2c控制器 struct i2c_adapter *adapter

i2c從設備的驅動程序struct i2c_driver *driver

i2c從設備的訪問地址addr, name

i2c從設備的名稱name

2. i2c總線驅動

2.1. 功能劃分

從硬件功能上可劃分爲:i2c控制器和i2c外設(從設備)。每一個i2c控制器總線上均可以掛載多個i2c外設。Linux中對i2c控制器和外設分開管理:經過 i2c-sun6i.c 文件完成了i2c控制器的設備註冊和驅動註冊;經過i2c-core.c爲具體的i2c外設提供了統一的設備註冊接口和驅動註冊接口,它分離了設備驅動device driver和硬件控制的實現細節(如操做i2c的寄存器)。

2.2. i2c-sun6i.c

該文件是與具體硬件平臺相關的,對應於A3x系列芯片。該文件其實是i2c總線驅動的實現,本質上就是向內核註冊i2c總線設備、註冊總線驅動、實現總線傳輸的時序控制算法。i2c控制器被註冊爲Platform設備,以下:

複製代碼
    if (twi_used_mask & TWI0_USED_MASK)
        platform_device_register(&sun6i_twi0_device);
 
    if (twi_used_mask & TWI1_USED_MASK)
        platform_device_register(&sun6i_twi1_device);
 
    if (twi_used_mask & TWI2_USED_MASK)
        platform_device_register(&sun6i_twi2_device);
 
    if (twi_used_mask & TWI3_USED_MASK)
        platform_device_register(&sun6i_twi3_device);
 
    if (twi_used_mask)
        return platform_driver_register(&sun6i_i2c_driver);
複製代碼

 

須要注意的是:設備與驅動的對應關係是多對一的;即若是設備類型是同樣的,會共用同一套驅動,所以上面代碼只是註冊了一次驅動platform_driver_register(&sun6i_i2c_driver) 

 

設備註冊:

i2c控制器設備註冊爲platform設備,爲每個控制器定義一個struct platform_device數據結構,而且把.name都設置爲"sun6i-i2c"(後面會經過名字進行匹配驅動的),而後是調用platform_device_register()將設備註冊到platform bus上。

 

設備註冊完成後其直觀的表現就是在文件系統下出現:/sys/bus/platform/devices/sun6i-i2c.0

 

經過platform_device_register()進行的註冊過程,說到底就是對struct platform_device這個數據結構的更改,逐步完成.dev.parent.dev.kobj.dev.bus的賦值,而後將.dev.kobj加入到platform_bus->kobj的鏈表上。

 

驅動註冊:

步驟和設備註冊的步驟相似,也是爲驅動定義了一個數據結構:

struct platform_driver sun6i_i2c_driver;

 

由於一個驅動是能夠對應多個設備的,而在系統裏的3個控制器基本上是一致的(區別就是寄存器的地址不同),因此上面註冊的3個設備共享的是同一套驅動。

 

初始化.probe.remove函數,而後調用platform_driver_register進行驅動註冊。主要函數調用流程:

platform_driver_register --> driver_register --> bus_add_driver --> driver_attach

 

須要注意的是driver_attach,這個函數遍歷了總線上(platform_bus_type)的全部設備,尋找與驅動匹配的設備,並把知足條件的設備結構體上的驅動指針指向驅動,從而完成了驅動和設備的匹配(__driver_attach函數完成)。

 

若是匹配到設備,這時就須要執行platform_bus_typeprobe函數,最終會調用設備驅動的probe函數(sun6i_i2c_probe)。

2.2.1  sun6i_i2c_probe

sun6i_i2c_probe函數中完成了大量的工做,包括硬件初始化、中斷註冊、爲每一個i2c控制器建立i2c_adapter等。

複製代碼
1268         pdata = pdev->dev.platform_data;
1269         if (pdata == NULL) {
1270                 return -ENODEV;
1271         }
1272
1273         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1274         irq = platform_get_irq(pdev, 0);
1275         if (res == NULL || irq < 0) {
1276                 return -ENODEV;
1277         }
1278
1279        if (!request_mem_region(res->start, resource_size(res), res->name)) {
1280                 return -ENOMEM;
1281         }
複製代碼

 

  • 首先獲得當前設備的私有數據指針,並將其保留在pdata;進而經過platform_get_resource獲得該設備佔用的內存資源,並申請:request_mem_region。同時將irq資源也保留下來。 
複製代碼
1288
1289         strlcpy(i2c->adap.name, "sun6i-i2c", sizeof(i2c->adap.name));
1290         i2c->adap.owner   = THIS_MODULE;
1291         i2c->adap.nr      = pdata->bus_num;
1292         i2c->adap.retries = 3;
1293         i2c->adap.timeout = 5*HZ;
1294         i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;
1295         i2c->bus_freq     = pdata->frequency;
1296         i2c->irq                  = irq;
1297         i2c->bus_num      = pdata->bus_num;
1298         i2c->status       = I2C_XFER_IDLE;
1299         i2c->suspended = 0;
1300         spin_lock_init(&i2c->lock);
1301         init_waitqueue_head(&i2c->wait);
複製代碼
  • 初始化i2c_adapter,並初始化一個工做隊列 init_waitqueue_head 
  • 經過ioremap申請IO資源;
  • 經過request_irq申請irq資源,中斷的處理服務函數是:sun6i_i2c_handler
  • sun6i_i2c_hw_init,對i2c控制進行硬件初始化;
  • i2c->adap.algo = &sun6i_i2c_algorithm,初始化控制器的總線傳輸算法,設備驅動調用;
  • 將初始化好的i2c_adapter註冊到i2c_corei2c_add_numbered_adapter

至此,probe函數完成。

2.2.2  sun6i_i2c_core_process

i2c控制器的中斷服務程序sun6i_i2c_handler調用了sun6i_i2c_core_processi2c總線的實際傳輸控制也是在該函數裏完成的。

主要流程:

  1. 讀取i2c控制器當前狀態,twi_query_irq_status,保留在state中;
  2. 根據state的值進行分支跳轉,控制i2c的工做狀態;
  3. 傳輸完成,調用sun6i_i2c_xfer_complete,喚醒工做隊列。

 

2.2.3  sun6i_i2c_xfer

每個i2c控制器設備,在驅動綁定後,都會建立一個i2c_adapter,用以描述該控制器,i2c_adapter的創建與初始化是在驅動probe的時候創建的。每個i2c_adapter包含了一個i2c_algorithm結構體的指針,i2c_algorithm是用來對外提供操做i2c控制器的函數接口的,主要是master_xfer函數,對應於i2c-sun6i.c,實際就是:

 

static int sun6i_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

該函數的功能是通知i2c_adapter須要對外設進行數據交換,須要交換的信息經過struct i2c_msg *msgs傳入。sun6i_i2c_xfer其實是調用了sun6i_i2c_do_xfer進行傳輸。

由於i2c總線讀寫速率有限,sun6i_i2c_do_xfer啓動i2c傳輸後,經過wait_event_timeout進入休眠,直到中斷喚醒或者超時;中斷喚醒是由sun6i_i2c_xfer_complete完成的。

3. i2c設備驅動

3.1. 驅動註冊

i2c從設備的驅動註冊,使用的是i2c-core.c提供的接口:i2c_register_driver;其調用以下:

 

i2c_register_driver --> driver_register --> bus_add_driver

 

bus_add_driver進行分析:

  • 關於device_driver數據結構的 struct driver_private *p

設備驅動模型是經過kobject對設備驅動進行層次管理的,所以device_driver應該包含kobject成員,linux是將kobject包含在struct driver_private中,再在device_driver中包含struct driver_private;咱們能夠理解driver_privatedevice_driver的私有數據,由內核進行操做。

struct driver_private 是在驅動註冊的開始,動態申請,並初始化的。

  • klist_init(&priv->klist_devices, NULL, NULL);

初始化設備鏈表,每個與該驅動匹配的device都會添加到該鏈表下。

  • priv->kobj.kset = bus->p->drivers_kset;

指定該驅動所屬的kset

  • kobject_init_and_add

初始化kobject,並將kobject添加到其對應的kset集合中(即bus->p->drivers_kset)。

該函數最終是調用kobject_add_internalkobject添加到對應的kset中;須要主要的是,若是kobjectparent若是爲NULL,在此會將其parent設置爲所屬kset集合的kobject

parent = kobject_get(&kobj->kset->kobj)

 

接下來是爲kobject建立文件夾:create_dir(kobj);從而能從/sys/目錄下顯示。

  • driver_attach,將驅動和設備進行綁定

將遍歷總線上的設備鏈表,查找能夠匹配的設備,並綁定。

driver_attach -->  bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

將函數指針__driver_attach傳入bus_for_each_dev,將每一個查找獲得的device進行驅動匹配。

 

bus_for_each_dev

遍歷總線上的全部設備,由於總線上的設備都是bus->p->klist_devices鏈表上的一個節點,所以該函數其實就是對鏈表的遍歷,具體能夠參考klist

 

__driver_attach(源碼位置drivers/base/dd.c):

進行設備和驅動匹配,若是匹配成功,嘗試進行綁定。

 

1. 首先進行匹配確認:driver_match_device(drv,  dev)

調用關係: --> drv->bus->match --> i2c_device_match 

-->  of_driver_match_device

  i2c_match_id

 

能夠看出,最終有兩種方式進行驅動匹配查詢:

方法一:經過of_driver_match_device對比of_device_id

方法二:經過i2c_match_id對比id_table

方法二實際上就是對比

i2c_driver->id_table->name client->name是否一致。

 

2. 若是匹配確認,進行驅動與設備綁定:driver_probe_device

調用關係: driver_probe_device --> really_probe

 --> dev->bus->probe

    driver_bound

 

really_probe中,首先將設備的驅動指針指向該驅動:dev->driver = drv

對應於i2c_bus_typedev->bus->probe 便是:i2c_device_probe,最終調用驅動的probe函數。

 

最後是driver_bound,將驅動與設備進行綁定:

其實就是調用klist_add_tail:將設備節點添加到驅動的klist_devices;

 

  • 調用klist_add_tail,將被註冊的驅動添加到總線的klist_drivers上;

klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);

 

  •  module_add_driver(drv->owner,  drv)

sysfs建立drivers目錄

3.2. 設備註冊

方式一:i2c設備動態發現註冊

i2c_register_driver的最後:

        INIT_LIST_HEAD(&driver->clients);
        /* Walk the adapters that are already present */
        i2c_for_each_dev(driver, __process_new_driver);

 

觀察i2c_for_each_dev 

複製代碼
int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *))
{
        int res;
 
        mutex_lock(&core_lock);
        res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);
        mutex_unlock(&core_lock);
 
        return res;
}
複製代碼

 

其實就是遍歷i2c總線上的klist_devices鏈表,對獲得的每個device,執行__process_new_driver 

跟蹤 __process_new_driver --> i2c_do_add_adapter --> i2c_detect

i2c_detect實現了i2c設備發現:在註冊驅動後,經過i2c_detect檢測是否有適合的設備鏈接在總線上。i2c_detect實現以下:

  • 在每個adapter上遍歷驅動給出的地址列表(address_list),由i2c_detect_address函數完成;最終會調用driver->detect(即設備驅動提供的設備發現函數);
  • 若是發現知足條件的設備,執行i2c_new_device,爲設備創建i2c_client;而且將設備添加到i2c_bus_type->p->klist_devices鏈表上(device_register),經過bus_add_device函數完成,最後調用bus_probe_device,嘗試綁定驅動。
  • client添加到驅動的設備鏈表上:list_add_tail(&client->detected, &driver->clients)

 

方式二:i2c設備之靜態註冊

Linux 3.3 提供了靜態定義的方式來註冊設備,接口原型:linux-3.3/drivers/i2c/i2c-boardinfo.c

int __init
i2c_register_board_info(int busnum,
        struct i2c_board_info const *info, unsigned len)

核心內容: 

  • 申請struct i2c_devinfo,用以描述一個i2c外設;
  • list_add_tail(&devinfo->list, &__i2c_board_list),將devinfo加入鏈表__i2c_board_list,以供後續查找;

掃描__i2c_board_list,建立client

i2c_register_board_info只是把設備描述符加入到了__i2c_board_list,並無建立client,當調用i2c_register_adapter註冊adapter時,會掃描__i2c_board_list,建立client;具體調用:

i2c_register_adapter 

--> i2c_scan_static_board_info 

--> i2c_new_device 

--> device_register

 

在 i2c_new_device完成了client建立,以及設備註冊device_register

 

PS

由上面的註冊流程可知,i2c_register_board_info應該在i2c_register_adapter以前完成,不然__i2c_board_list中的節點不會被掃描到。

 

總結:

  • 由上述分析可知,i2c設備驅動是經過i2c_register_driver註冊的,i2c設備是經過i2c_new_device註冊的,在最後,這兩個函數都嘗試進行驅動和設備綁定(driver_attachbus_probe_device);所以不論是先註冊驅動仍是先註冊設備,最後都可以將合適的驅動和設備進行綁定。
  • 有兩種方式進行設備註冊:

一、經過i2c_register_board_info,在系統啓動之初靜態地進行i2c設備註冊(axp電源驅動就是這樣作的);

二、實現i2c設備驅動的detect函數,在驅動加載的時候動態檢測建立設備,aw平臺的觸摸屏驅動gt82x.ko就是經過這種方式。

  • Linux是經過在驅動數據結構中內嵌kobjectkset,完成了設備驅動的層次管理的,理解kobjectkset對理解設備驅動模型很重要。

4. i2c驅動架構圖

 

 

一、i2c_add_adapter

二、i2c_new_device/i2c_register_board_info

三、i2c_add_driver

四、調用i2c bus中註冊的match函數進行匹配

五、調用platform bus中註冊的match函數進行匹配

六、i2cdev_attach_adapter

相關文章
相關標籤/搜索