參考文章
- 《linux裝置驅動開發詳解》第15章
- 【linux iic子系統】i2c整體框圖【精髓部分】(五)_bus_for_each_drv-CSDN部落格
- linux裝置驅動程式——i2c匯流排的新增與實現_如何填充i2c_client-CSDN部落格
- 【linux iic子系統】i2c裝置與驅動匹配過程(三)_i2c_add_driver 如何和clent匹配-CSDN部落格
驅動和裝置樹以及各個結構體總的關係圖如下所示:
下面根據圖具體解釋。
重要的幾個結構體的關係
比較重要的是 i2c_adapter
i2c_client
i2c_driver
i2c_algorithm
i2c_adapter
對應的一個物理上的介面卡,以tegra為例,在裝置樹中compitable = "nvidia,tegra210-i2c"
來指示介面卡該怎麼配置。並且在其對應的probe函式中會同時註冊 i2c_algorithm
的方法用來控制如何傳送i2c資料,以及傳送i2c資料的函式
i2c_client
會透過對應裝置樹中的節點資訊填充,然後掛到bus上統一被管理。client其實就是裝置樹資訊。所以一個adapter可能會有多個client(=一個i2c匯流排上掛載多個裝置)
i2c_driver
是使用者所寫的驅動程式,也會被統一掛載到bus上。由bus統一管理。
i2c bus部分
bus提供了註冊和probe方法。並由於很多裝置都依賴i2c,所以i2c bus的註冊優先順序很高
//i2c-core.c
postcore_initcall(i2c_init); // 2
在 i2c_init
中,會用bus_register
註冊一個 i2c_bus_type
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
在這個匯流排型別中,會提供match方法和probe方法。match就是用來匹配匯流排上的裝置樹資訊client
,和驅動裝置檔案driver
的。probe用來在driver的probe(就是"xx.ko"驅動檔案裡的probe!!)執行前做一些初始化,然後再去呼叫driver提供的probe函式。
adapter部分
adapter是控制實際的物理i2c介面卡與外界裝置通訊的。
物理上的i2c1的裝置樹描述
i2c@7000c000 {
compatible = "nvidia,tegra210-i2c", "nvidia,tegra114-i2c";
reg = <0x0 0x7000c000 0x0 0x100>;
interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
clocks = <&tegra_car TEGRA210_CLK_I2C1>;
clock-names = "div-clk";
resets = <&tegra_car 12>;
reset-names = "i2c";
dmas = <&apbdma 21>, <&apbdma 21>;
dma-names = "rx", "tx";
status = "disabled";
};
在tegra的廠商提供的,會將 nvidia,tegra210-i2c
節點認為是adapter。
//i2c-tegra.c
subsys_initcall(tegra_i2c_init_driver);//優先順序是小於bus的
static const struct of_device_id tegra_i2c_of_match[] = {
...
{ .compatible = "nvidia,tegra210-i2c", .data = &tegra210_i2c_hw, },
...
{},
};
在對應的probe函式中
- 註冊對應的傳送函式
- 透過
i2c_add_numbered_adapter
註冊到系統中
probe中還會 i2c_add_driver(&dummy_driver)
註冊一個空的驅動
在 i2c-dev.c
中提供了adapter的檔案功能和一些檔案操作的介面
在i2c-dev.c
的initcall優先順序很低只有6
所以如果有已經註冊的adapter會依次註冊
//i2c-dev.c
/* Bind to already existing adapters right away */
i2c_for_each_dev(NULL, i2cdev_attach_adapter);
通訊方法
透過在probe中註冊的 algorithm
控制。
如果adapter沒有algorithm提供的傳送函式,那麼就無法通訊。最後實際通訊會落腳到algorithm
//i2c-tegra.c
tegra_i2c_probe{
...
i2c_dev->adapter.algo = &tegra_i2c_algo;
...
}
static const struct i2c_algorithm tegra_i2c_algo = {
.master_xfer = tegra_i2c_xfer, //實際的傳送函式
.functionality = tegra_i2c_func, //控制訪問週期
};
tegra的實際的傳送函式tegra_i2c_xfer
是比較複雜的,會涉及到dma的方式,並且最後是透過傳送完後的中斷來最後傳送停止訊號。這部分將在後續的文章中詳細分析。
client部分
用來描述掛載在i2c1下的i2c裝置
i2c@7000c400{
motor@60{
compatible = "nxp,pca9685-pwm";
#pwm-cells = <2>;
reg = <0x60>;
invert;
};
};
在註冊的時候會透過 i2c_new_device
將裝置樹的內容填充到 i2c_client
中,然後設定好 i2c_client_type
用來指示是i2c裝置。最後透過 device_register
將裝置註冊到系統中。
device_register
會呼叫 device_add
,這個函式會建立好檔案,以及匹配對應的匯流排,並且通知連結串列來監聽匯流排裝置,有新的裝置加入。同時會產生一個uevent,有裝置加入,最後會去匹配匯流排上驅動。
這一部分也是比較複雜,後續可能會專門有分析的文章。
可以參考下面的文章【linux iic子系統】i2c裝置與驅動匹配過程(三)_i2c_add_driver 如何和clent匹配-CSDN部落格
driver部分
driver就是使用者寫的驅動程式,透過id_table或者另外的方式透過bus匹配到client後,會先呼叫bus的probe函式然後,將client傳入driver的probe函式中供driver使用,(這也就是driver的probe函式都會有client結構的原因!)
並且client是依賴於adapter的所以,透過client就可以呼叫到傳送i2c資料的函式了!