参考文章
- 《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数据的函数了!