基于i2c子系统的驱动分析
和i2c有关的代码都在源码drivers/i2c目录下。内核提供了两种i2c的实现方法:
- 第一种叫i2c_dev,对应drivers/i2c/i2c-dev.c,这种方法仅仅封装了soc的i2c控制器操作,并向应用层提供操作接口。其本质是为应用层提供了一个库,驱动功能由应用层实现,不是主流的做法
- 第二种是驱动层实现所有驱动功能,是比较主流的做法
第二种可以认为是正统的i2c驱动,其本质是:工程师任意选用input子系统、misc框架、普通字符驱动等方式实现i2c驱动,i2c子系统的意义仅仅是为硬件操作提供接口(库)
1.i2c子系统的结构
如图
可以看出,i2c子系统基本机制和platform很类似,都是设备和驱动两者匹配来工作。i2c驱动只需调用核心层提供的接口(相当于核心层提供了库),即可方便地操作i2c
2.i2c总线核心分析
i2c总线核心提供了设备驱动和设备(client)的注册、注销方法, 还提供了一组不依赖于硬件平台的接口函数,I2C 总线驱动和设备驱动之间依赖于 I2C 核心作为纽带
3.i2c适配器(adapter)驱动分析
所谓的i2c适配器驱动,就是soc内部的i2c控制器的驱动,由原厂移植内核时提供,一般位于driver/i2c/busses内。而i2c适配器设备的注册,在3.x后的kernel中采用了设备树节点的方式,故这里需要分类讨论
老内核下的i2c适配器
我们这里用的是i2c-s3c2410.c,该驱动兼容三星大部分的soc,包括210。该驱动由platform总线实现,该驱动probe函数中主要做了:
- 填充了一个i2c_adapter结构体,并调用接口注册之,i2c_adapter 对应于SOC上的一个适配器
- 从platform_data(自留地)接收硬件信息,做必要的处理(为寄存器申请虚拟地址映射、申请中断等)
- 通过操作寄存器,对soc内的i2c适配器做初始化,比如把i2c速率设置为默认的100k。这一套设置基本通吃大部分器件,一般情况不用改动的
新内核下的i2c适配器
在新内核下,i2c适配器的驱动倒是没有变化,而i2c适配器设备体的注册,却采用了设备树的方式
- 下面是imx6qdl.dtsi中对i2c1适配器设备的定义和注册,里面定义了很多参数,一般来说我们是根本不用去修改这个节点的。假设我们要修改其中的参数(比如频率),只需在项目的dts中引用该节点,并重写即可
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <0 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6QDL_CLK_I2C1>;
status = "disabled";
};
4.i2c设备(client)注册分析
所谓的i2c设备(client),就是挂在i2c上的外设(比如各种传感器),这个需要我们自己注册,在3.x后的kernel中采用了设备树节点的方式,故这里需要分类讨论
老内核下的i2c设备(client)
对于老版本的内核,首先应该进入mach-xxx.c完成i2c设备(client)的注册。如何注册?这方面i2c和platform有较大不同,主要是soc上有多个i2c,所以是分开注册的
- 在mach-xxx.c中的xxx_machine_init函数中,发现由i2c_register_board_info来注册三个i2c上各自的设备。以i2c_devs0为例,i2c_devs0是一个数组,里面是i2c0上所有的设备
i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));
i2c_register_board_info(2, i2c_devs2, ARRAY_SIZE(i2c_devs2));
- 查看i2c_devs0的定义,我们发现该数组内部都是i2c_board_info结构体,如果要添加设备到i2c0,只需在该数组中使用I2C_BOARD_INFO这个宏即可,第一个参数是名字,第二个参数是设备在i2c上的地址,此宏的本质就是填充一个struct i2c_board_info,这一步作用是把wm8580以i2c设备的身份被注册,并且绑定i2c0这个适配器<