i2c总线是一种十分常见的板级总线,本文以linux3.14.0为参考, 讨论Linux中的i2c驱动模型并利用这个模型写一个mpu6050的驱动, 最后在应用层将mpu6050中的原始数据读取出来
i2c子系统框架
下图就是我理解的i2c驱动框架示意图, 类似中断子系统, i2c子系统中也使用一个对象来描述一个物理实体, 设备对象与驱动分离, 驱动结合设备对象对硬件设备的描述才可以驱动一个具体的物理设备, 体现了分离的设计思想, 实现了代码的复用, 比如:
- 一个i2c控制器就对应一个i2c_board_info, 它驱动就是s3c2410_i2c_driver, 他们通过platform_bus_type协调工作。
- 一个i2c总线上的设备就对应内核中的一个i2c_client类型的对象, 它的驱动就是的i2c_driver, 二者通过i2c_bus_type协调工作。
- 同样是抽象的思路, 对于i2c总线本身, 内核也使用i2c_bus_type来描述。
事实上, 对于任何一种总线, 内核都有一个bus_type类型的对象与之对应, 但是platform_bus_type并没有对应的实际的物理总线, 这也就是platform总线也叫虚拟总线的原因.
除了分离,i2c子系统也体现的软件分层的设计思想, 整个i2c子系统由3层构成:设备驱动层--i2c核心--控制器驱动
除了经典的分层与分离模型,我们也可以看到一个有意思的现象——Linux 的应用程序不但可以通过设备驱动来访问i2c从设备,还可以通过一个并没有直接挂接到i2c_bus_type的i2c_cleint来找到主机控制器进而访问任意一个i2c设备, 这是怎么回事呢? 我会在下一篇说-
核心结构和方法简述
核心结构
- i2c_adapter对象实现了一组通过一个i2c控制器发送消息的所有信息, 包括时序, 地址等等, 即封装了i2c控制器的"控制信息"。它被i2c主机驱动创建, 通过clien域和i2c_client和i2c_driver相连, 这样设备端驱动就可以通过其中的方法以及i2c物理控制器来和一个i2c总线的物理设备进行交互
- i2c_algorithm描述一个i2c主机的发送时序的信息,该类的对象algo是i2c_adapter的一个域,其中的master_xfer()注册的函数最终被设备驱动端的i2c_transfer()回调。
- i2c_client描述一个挂接在硬件i2c总线上的设备的设备信息,即i2c设备的设备对象,与i2c_driver对象匹配成功后通过detected和i2c_driver以及i2c_adapter相连,在控制器驱动与控制器设备匹配成功后被控制器驱动通过i2c_new_device()创建。
- i2c_driver描述一个挂接在硬件i2c总线上的设备的驱动方法,即i2c设备的驱动对象,通过i2c_bus_type和设备信息i2c_client匹配,匹配成功后通过clients和i2c_client对象以及i2c_adapter对象相连
- i2c_msg描述一个在设备端和主机端之间进行流动的数据, 在设备驱动中打包并通过i2c_transfer()发送。相当于skbuf之于网络设备,urb之于USB设备。
核心方法
- i2c_transfer()是i2c核心提供给设备驱动的发送方法, 通过它发送的数据需要被打包成i2c_msg, 这个函数最终会回调相应i2c_adapter->i2c_algorithm->master_xfer()接口将i2c_msg对象发送到i2c物理控制器
核心结构与方法详述
i2c_adapter
我首先说i2c_adapter, 并不是编写一个i2c设备驱动需要它, 通常我们在配置内核的时候已经将i2c控制器的设备信息和驱动已经编译进内核了, 就是这个adapter对象已经创建好了, 但是了解其中的成员对于理解i2c驱动框架非常重要, 所有的设备驱动都要经过这个对象的处理才能和物理设备通信
//include/linux/i2c.h
425 struct i2c_adapter {
426 struct module *owner;
427 unsigned int class; /* classes to allow probing for */
428 const struct i2c_algorithm *algo; /* the algorithm to access the bus */
429 void *algo_data;
430
431 /* data fields that are valid for all devices */
432 struct rt_mutex bus_lock;
433
434 int timeout; /* in jiffies */
435 int retries;
436 struct device dev; /* the adapter device */
437
438 int nr;
439 char name[48];
440 struct completion dev_released;
441
442 struct mutex userspace_clients_lock;
443 struct list_head userspace_clients;
444
445 struct i2c_bus_recovery_info *bus_recovery_info;
446 };
struct i2c_adapter
--428-->这个i2c控制器需要的控制算法, 其中最重要的成员是master_xfer()接口, 这个接口是硬件相关的, 里面的操作都是基于具体的SoCi2c寄存器的, 它将完成将数据发送到物理i2c控制器的"最后一公里"
--436-->表示这个一个device, 会挂接到内核中的链表中来管理, 其中的
--443-->这个节点将一个 i2c_adapter对象和它所属的 i2c_client对象以及相应的 i2c_driver对象连接到一起
下面是2个i2c-core.c提供的i2c_adapter直接相关的操作API, 通常也不需要设备驱动开发中使用,
i2c_add_adapter
这个API可以将一个i2c_adapter类型的对象注册到内核中, 源码我就不贴了, 下面是他们的调用关系,我们可以从中看到一个adapter对象和系统中的i2c_driver对象以及i2c_client对象的匹配流程。
首先,我们在驱动中构造一个i2c_adapter对象的时候,对其中的相关域进行初始化,这里我们最关心它的父设备
//drivers/i2c/buses/i2c-s3c2410.c
1072 static int s3c24xx_i2c_probe(struct platform_device *pdev)
1073 {
1140 i2c->adap.dev.parent = &pdev->dev;
1210 }
得到了这样一个i2c_adapter对象,我们就可以调用这个API将它注册到内核,调用关系如下:
i2c_add_adapter()
1 └──i2c_register_adapter(adapter)
2 ├──adap-&g