平台:RK3399
使用設備樹描述板級資源;
框架:
linux i2c框架同樣采用分層、分離的模式設計;從上到下分為 app調用層、i2c core層、驅動層;驅動層又分為 cpu平台 i2c控制器相關的驅動層以及 i2c總線上掛接的設備驅動程序;而分離的思想則體現在板級相關的信息放在設備樹上實現,而通用的讀寫、初始化流程、操作流程等則放到驅動里面實現(類似platform總線驅動);
具體分析:
1. cpu i2c控制器驅動(adpter)
static struct platform_driver rk3x_i2c_driver = { .probe = rk3x_i2c_probe, .remove = rk3x_i2c_remove, .driver = { .name = "rk3x-i2c", .of_match_table = rk3x_i2c_match, .pm = &rk3x_i2c_pm_ops, }, };
module_platform_driver(rk3x_i2c_driver)
RK3399采用以上方法向內核定義了一個platform driver,內核啟動的時候,會再設備樹里面I2C節點找到對應的設備節點定義compatible = "rockchip,rk3399-i2c"; ,由於 of_match_table 里面可以找到rockchip,rk3399-i2c,所以緊接着會調用 rk3x_i2c_probe 函數;rk3x_i2c_probe 函數主要是向內核注冊了cpu i2c控制器驅動,並且掃描設備樹里面i2c所有節點的設備信息(包括I2C根節點的信息),然后將添加掃描到的i2c設備信息加入i2c bus總線維護的鏈表里面 (bus->p->klist_devices),然后再用設備的名稱和 i2c設備驅動里面的設備名稱匹配,如果匹配成功,則調用i2c設備的probe函數:
具體分析如下:
ret = i2c_add_adapter(&i2c->adap);
adpter注冊及device注冊調用流程如下:
ret = i2c_add_adapter(&i2c->adap); i2c_register_adapter(adapter); of_i2c_register_devices(adap); //掃描設備樹I2C總線子節點信息;包括掛接的設備名稱及地址! for_each_available_child_of_node(adap->dev.of_node, node) { if (of_node_test_and_set_flag(node, OF_POPULATED)) continue; of_i2c_register_device(adap, node); }
再深入 of_i2c_register_device(adap, node) 函數調用分析:
of_i2c_register_device(adap, node) i2c_new_device(adap, &info); // info里面便包含了i2c總線掛接設備的名稱及地址 status = device_register(&client->dev); // 完成 client 設備的創建 return device_add(dev); error = bus_add_device(dev); // 將設備加入bus總線 bus_probe_device(dev); // 開始設備的匹配
再深入看下 bus_probe_device(dev); // 開始設備的匹配
void bus_probe_device(struct device *dev) { struct bus_type *bus = dev->bus; struct subsys_interface *sif; if (!bus) return; if (bus->p->drivers_autoprobe) // 在i2c_core初始化的時候已經置一 device_initial_probe(dev); mutex_lock(&bus->p->mutex); list_for_each_entry(sif, &bus->p->interfaces, node) if (sif->add_dev) sif->add_dev(dev, sif); mutex_unlock(&bus->p->mutex); }
關注以上代碼里面的 device_initial_probe(dev); 函數,其調用流程如下
device_initial_probe(dev); __device_attach(dev, true); ret = bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
if (!driver_match_device(drv, dev)) // 開始調用 i2c bus總線的match函數,匹配設備和設備驅動
return driver_probe_device(drv, dev); // 匹配成功,則調用設備驅動的probe函數
至此, adpter及設備端的注冊匹配基本結束;
另外還有設備驅動部分,拿聲卡 es8316來分析;
先分配一個 i2c driver結構體
static struct i2c_driver es8316_i2c_driver = { .driver = { .name = "es8316", .of_match_table = es8316_of_match, }, .probe = es8316_i2c_probe, .remove = es8316_i2c_remove, .shutdown = es8316_i2c_shutdown, .id_table = es8316_i2c_id, }; module_i2c_driver(es8316_i2c_driver);
module_i2c_driver(es8316_i2c_driver)向內核注冊i2c driver;主要完成了設備驅動的注冊,及跟設備的匹配,如果匹配成功,則調用probe函數 es8316_i2c_probe, 具體流程如下
i2c_register_driver res = driver_register(&driver->driver); ret = bus_add_driver(drv); klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); error = driver_attach(drv);
從上面流程可以看出,已經完成了把驅動放入 bus 總線維護的 drivers 鏈表 klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
再具體深入下 driver_attach(drv);
driver_attach(drv); return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); if (!driver_match_device(drv, dev)) driver_probe_device(drv, dev);
至此,便完成了設備驅動與設備的匹配和設備驅動probe的調用
至此,i2c驅動基本完成,而其余跟設備相關操作,均可在probe函數里實現,比如聲卡的初始化等;