因此,對於實現一個linux下的設備驅動,可以劃分為兩大步:
1、設備注冊;
2、驅動注冊。
當然,其中還有一些細節問題:
1、驅動的probe函數
2、驅動和設備是怎麼進行綁定的。
每一個i2c_adapter對應一個物理上的i2c控制器,在i2c總線驅動probe函數中動態創建。通過i2c_add_adapter注冊到i2c_core。
i2c_algorithm:
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。
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個設備共享的是同一套驅動。
設備與驅動匹配
1.match過程
i2c_add_driver-->i2c_register_driver-->i2c_bus_type-->.match->i2c_device_match-->of_driver_match_device/i2c_match_id(比較i2c_driver->id_table->name和client->name,如果相同,則匹配上,匹配上之後,運行driver_register調用driver_probe_device進行設備與驅動綁定。),
2.probe過程
初始化.probe和.remove函數,然後調用i2c_add_driver進行驅動注冊。主要函數調用流程:
i2c_add_driver-->i2c_register_driver --> driver_register --> bus_add_driver --> driver_attach-->driver_probe_device-->really_probe(裡面講設備的驅動指針指向驅動,如果匹配成功,執行dev->bus->probe即設備驅動裡的probe),-->driver_bound(綁定)
需要注意的是driver_attach,這個函數遍歷了總線上(platform_bus_type)的所有設備,尋找與驅動匹配的設備,並把滿足條件的設備結構體上的驅動指針指向驅動,從而完成了驅動和設備的匹配(__driver_attach函數完成)。
如果匹配到設備,這時就需要執行platform_bus_type的probe函數,最終會調用設備驅動的probe函數(sun6i_i2c_probe)。
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)) {
1280return -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_core:i2c_add_numbered_adapter。
至此,probe函數完成。
主要流程:
讀取i2c控制器當前狀態,twi_query_irq_status,保留在state中;
根據state的值進行分支跳轉,控制i2c的工作狀態;
傳輸完成,調用sun6i_i2c_xfer_complete,喚醒工作隊列。
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_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_private是device_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_internal將kobject添加到對應的kset中;需要主要的是,如果kobject的parent如果為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_type,dev->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目錄
在i2c_register_driver的最後:
INIT_LIST_HEAD(&driver->clients); 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 __initi2c_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,創建clienti2c_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_attach和bus_probe_device);因此不管是先注冊驅動還是先注冊設備,最後都能夠將合適的驅動和設備進行綁定。
有兩種方式進行設備注冊:
1、通過i2c_register_board_info,在系統啟動之初靜態地進行i2c設備注冊(axp電源驅動就是這樣做的);
2、實現i2c設備驅動的detect函數,在驅動加載的時候動態檢測創建設備,aw平台的觸摸屏驅動gt82x.ko就是通過這種方式。
Linux是通過在驅動數據結構中內嵌kobject、kset,完成了設備驅動的層次管理的,理解kobject、kset對理解設備驅動模型很重要。
1、i2c_add_adapter
2、i2c_new_device/i2c_register_board_info
3、i2c_add_driver
4、調用i2c bus中注冊的match函數進行匹配
5、調用platform bus中注冊的match函數進行匹配
6、i2cdev_attach_adapter