由 總線(bus_type) + 設備(device) + 驅動(device_driver) 組成,在該模型下,全部的設備經過總線鏈接起來,即便有些設備沒有鏈接到一根物理總線上,linux爲其設置了一個內部的、虛擬的platform總線,用以維持總線、驅動、設備的關係。node
所以,對於實現一個linux下的設備驅動,能夠劃分爲兩大步:linux
一、設備註冊;算法
二、驅動註冊。數據結構
固然,其中還有一些細節問題:架構
一、驅動的probe函數函數
二、驅動和設備是怎麼進行綁定的。spa
i2c_adapter:指針
每個i2c_adapter對應一個物理上的i2c控制器,在i2c總線驅動probe函數中動態建立。經過i2c_add_adapter註冊到i2c_core。code
i2c_algorithm:orm
i2c_algorithm中的關鍵函數master_xfer(),以i2c_msg爲單位產生i2c訪問須要的信號。不一樣的平臺所對應的master_xfer()是不一樣的,須要根據所用平臺的硬件特性實現本身的xxx_xfer()方法以填充i2c_algorithm的master_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。
從硬件功能上可劃分爲:i2c控制器和i2c外設(從設備)。每一個i2c控制器總線上均可以掛載多個i2c外設。Linux中對i2c控制器和外設分開管理:經過 i2c-sun6i.c 文件完成了i2c控制器的設備註冊和驅動註冊;經過i2c-core.c爲具體的i2c外設提供了統一的設備註冊接口和驅動註冊接口,它分離了設備驅動device driver和硬件控制的實現細節(如操做i2c的寄存器)。
該文件是與具體硬件平臺相關的,對應於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_type的probe函數,最終會調用設備驅動的probe函數(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 }
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);
至此,probe函數完成。
i2c控制器的中斷服務程序sun6i_i2c_handler調用了sun6i_i2c_core_process,i2c總線的實際傳輸控制也是在該函數裏完成的。
主要流程:
每個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完成的。
i2c從設備的驅動註冊,使用的是i2c-core.c提供的接口:i2c_register_driver;其調用以下:
i2c_register_driver --> driver_register --> bus_add_driver;
對bus_add_driver進行分析:
設備驅動模型是經過kobject對設備驅動進行層次管理的,所以device_driver應該包含kobject成員,linux是將kobject包含在struct driver_private中,再在device_driver中包含struct driver_private;咱們能夠理解driver_private是device_driver的私有數據,由內核進行操做。
struct driver_private 是在驅動註冊的開始,動態申請,並初始化的。
初始化設備鏈表,每個與該驅動匹配的device都會添加到該鏈表下。
指定該驅動所屬的kset;
初始化kobject,並將kobject添加到其對應的kset集合中(即bus->p->drivers_kset)。
該函數最終是調用kobject_add_internal將kobject添加到對應的kset中;須要主要的是,若是kobject的parent若是爲NULL,在此會將其parent設置爲所屬kset集合的kobject:
parent = kobject_get(&kobj->kset->kobj);
接下來是爲kobject建立文件夾:create_dir(kobj);從而能從/sys/目錄下顯示。
將遍歷總線上的設備鏈表,查找能夠匹配的設備,並綁定。
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_type,dev->bus->probe 便是:i2c_device_probe,最終調用驅動的probe函數。
最後是driver_bound,將驅動與設備進行綁定:
其實就是調用klist_add_tail:將設備節點添加到驅動的klist_devices;
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
在sysfs建立drivers目錄
方式一: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實現以下:
方式二: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)
核心內容:
掃描__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_register_board_info,在系統啓動之初靜態地進行i2c設備註冊(axp電源驅動就是這樣作的);
二、實現i2c設備驅動的detect函數,在驅動加載的時候動態檢測建立設備,aw平臺的觸摸屏驅動gt82x.ko就是經過這種方式。
一、i2c_add_adapter
二、i2c_new_device/i2c_register_board_info
三、i2c_add_driver
四、調用i2c bus中註冊的match函數進行匹配
五、調用platform bus中註冊的match函數進行匹配
六、i2cdev_attach_adapter