首页 理论教育深入解析Linux内核I2C总线驱动

深入解析Linux内核I2C总线驱动

【摘要】:I2C总线框架主要就是对这两类设备及操作进行管理,整体的框架如图7-3所示。由图7-3可见,I2C核心管理的实体就是i2c_client、i2c_adapter与总线设备相关的驱动i2c_driver。通过标准的I2C操作可以模拟smbus操作,I2C总线框架提供函数i2c_sm-bus_xfer_emulated来模拟相应的操作。

1.总线相关以及核心框架

在介绍设备模型的时候,已经介绍了设备模型中的总线抽象管理,通常总线都有自动probe功能,即可以在向设备模型注册设备或者驱动的时候,进行绑定操作,由具体总线实例化相关操作。了解总线相关操作对于了解整个总线框架非常有帮助。

在介绍系统初始化的时候,已经看到整个I2C的初始化函数i2c_init定义为postcore_init-call,这样就在I2C设备或者驱动注册之前进行注册,从而保证总线的功能。而初始化中包含如下语句:

978-7-111-49426-3-Chapter07-3.jpg

这里注册了总线类型,其中bus_register会设置总线自动probe功能。而i2c_bus_type可以作为了解整个总线框架的入口,详细定义如下:

978-7-111-49426-3-Chapter07-4.jpg

978-7-111-49426-3-Chapter07-5.jpg

这里重点的接口是match以及匹配之后的probe。下面详细分析这两个接口:

978-7-111-49426-3-Chapter07-6.jpg

978-7-111-49426-3-Chapter07-7.jpg

从分析中可见,除了基本的操作之外,两个接口函数都通过i2c_verify_client进行了设备的验证。下面来看看具体的内容。

978-7-111-49426-3-Chapter07-8.jpg

这里dev->type可以是一个特殊的设备类型i2c_client_type。在设备模型中已经介绍了,device中的type属性在功能类型和总线类型等大的类型中对设备进行细分,并进行相应操作,这里就是实际应用。

进行type的划分说明不止一种设备,下面来看看I2C总线的不同设备类型:

978-7-111-49426-3-Chapter07-9.jpg

其中有两种类型的设备,分别是i2c_client类型和i2c_adapter类型。进行这样的分类是由于从抽象的角度考虑,总线控制器也是设备,而作为总线的控制接口,其物理上代表的是总线,而不是总线上连接的设备,所以在类型上加以区分。而在进行总线级别的匹配时,需要进行匹配的是总线上连接的设备和其相应的驱动,这就要求确保只有总线设备进行匹配操作,所以才有之前进行检查的代码。

I2C总线框架主要就是对这两类设备及操作进行管理,整体的框架如图7-3所示。

由图7-3可见,I2C核心管理的实体就是i2c_client、i2c_adapter与总线设备相关的驱动i2c_driver。为了管理这些实体,提供了接口函数,后面在相关部分会对其进行介绍。另外除了以上的两类设备之外,系统还提供了针对应用层的操作接口管理实体i2c_dev。下面来看看细节:

978-7-111-49426-3-Chapter07-10.jpg

图7-3 I2C总线内核框架

978-7-111-49426-3-Chapter07-11.jpg

其中提供了i2c_adapter的指针,这是由于对应用层开放的设备文件主要提供I2C应用层驱动的功能,作为驱动主要是对总线设备的操作,通过总线控制器进行总线操作,所以这里只要提供相应的总线控制器即可,而具体与哪个I2C从设备交互及相关的地址等信息都是通过ioctl命令设置的。这样对下层的操作都是相同的,只是在应用层接口部分实现i2c_client作为操作的接口即可。下面来看看I2C设备文件操作接口i2cdev_fops中的open函数:

978-7-111-49426-3-Chapter07-12.jpg

从代码中可见,设置了一个特殊的driver即i2cdev_driver,下面来查看一下细节:

978-7-111-49426-3-Chapter07-13.jpg

这里只有attach_adapter和detach_adapter两个操作接口,这两个接口是系统用于处理新的总线控制器加入或者退出系统时的相关操作,在此情况下驱动可以通过相应接口进行相关操作。应用接口层利用该功能进行相关的操作,通过检测总线控制器的状态变化,进行i2c_dev以及设备文件的创建和销毁。

I2C设备更多的是对内核的各种功能框架提供设备的控制接口,这样就要求将功能框架设备与I2C设备关联。以视频框架为例,相应的关联是在v4l2_i2c_subdev_init中实现的,具体细节如下:

978-7-111-49426-3-Chapter07-14.jpg

这样V4L2子设备就可以在操作中通过v4l2_get_subdevdata来获得I2C设备进行相应的操作,而I2C设备移除时也可以通过i2c_get_clientdata来获得V4L2子设备进行释放操作。

这样I2C框架就分别给内核功能模块与应用层提供了操作接口,在该设计中可以保证共用的部分尽量多。

2.总线控制器相关

I2C总线框架对总线控制器的管理主要是通过i2c_adapter来实现的,其详细分析如下:

978-7-111-49426-3-Chapter07-15.jpg

978-7-111-49426-3-Chapter07-16.jpg

从中可见,除了属性信息外最重要的就是i2c_algorithm了。i2c_algorithm中定义了操作接口,通过这些操作接口可以实现标准的I2C总线操作。由于不同设备的操作方法不同,该接口实际是实现具体设备总线控制器的总线操作逻辑。下面来看看具体细节:

978-7-111-49426-3-Chapter07-17.jpg

从中可见,有两个操作接口,但是两个并不都需要提供,通常只要提供标准I2C接口master_xfer即可。通过标准的I2C操作可以模拟smbus操作,I2C总线框架提供函数i2c_sm-bus_xfer_emulated来模拟相应的操作。在一次传输中可以批量做多次处理,每个处理都是一次i2c_msg的交互操作,具体i2c_msg的个数由参数num指定。

为了管理总线控制器,I2C总线框架提供了以下接口:

978-7-111-49426-3-Chapter07-18.jpg

具体设备的总线控制器在调用以上接口进行管理前,相关的属性(包括algo)都需要初始化,也就是说框架并没有提供总线控制器的操作algo与管理实体adapter进行绑定的接口。这是由于adapter的很多属性都需要初始化,而与哪个操作算法绑定开发者最清楚,所以直接作为初始化的一部分设置即可。(www.chuimin.cn)

另外还提供了adapter设备特殊管理数据的设置和获取接口,如i2c_set_adapdata和i2c_get_adapdata。

当一个adapter加入系统后,说明增加了一个I2C总线连接,而通常I2C总线是在硬件板上直接进行连接,这就需要进行总线的遍历以及总线设备与相应驱动的绑定。这个流程是在i2c_register_adapter中完成的,而该函数会在i2c_add_adapter和i2c_add_numbered_adapter中进行调用。i2c_register_adapter中主要工作就是进行adapter设备模型的注册,另外通过如下代码进行总线遍历:

978-7-111-49426-3-Chapter07-19.jpg

这里对每个I2C设备驱动都执行__process_new_adapter,在__process_new_adapter中会通过i2c_detect调用i2c_detect_address来由驱动detect匹配的设备。如果detect成功则注册新设备。其中设备模型和总线会负责绑定驱动,从而完成加载操作,这就实现了总线设备发现相关功能。

总线的主要功能(包括总线传输事务以及总线设备发现)就都在I2C总线控制器部分有了完整的支持。

3.总线设备相关

I2C总线框架提供了i2c_client来完成总线设备管理功能,详细内容如下:

978-7-111-49426-3-Chapter07-20.jpg

978-7-111-49426-3-Chapter07-21.jpg

从相关属性来看,主要的属性是addr、adapter和driver。下面来看看与之相关联的driv-er的详细信息:

978-7-111-49426-3-Chapter07-22.jpg

978-7-111-49426-3-Chapter07-23.jpg

从i2c_driver的内容分析可见,其主要负责总线相关以及电源管理相关的操作,操作接口中并不涉及实际的信息传输,可以说该驱动主要是处理总线和设备模型相关的事务,并不涉及功能的事务。功能型的事务涉及操作的具体内容,是在功能型驱动中通过I2C的接口实现的。而功能型的事务需要有总线设备的信息,这就要求功能型驱动管理实体中能够包含i2c_client的信息。这个桥梁是与I2C总线事务相关的,所以该工作自然就由i2c_driver来完成,具体的接口就是probe,这样整体上各种实体就关联起来了。具体例子见mt9t111_probe:

978-7-111-49426-3-Chapter07-24.jpg

只有总线设备与功能管理实体关联后才能完成功能性的工作,而总线设备对应的驱动主要工作是建立总线设备与功能管理实体的关联;对物理总线以及电源管理等事件响应并进行相应的操作。

以上无论是i2c_client还是i2c_driver都属于I2C总线框架管理的运行时的实体,而I2C总线设备通常直接焊接在板子上面,这些固定的信息同样需要进行表示,在创建动态管理的设备实体时需要这些信息。在I2C总线框架中相应的实体是i2c_board_info,内容如下:

978-7-111-49426-3-Chapter07-25.jpg

978-7-111-49426-3-Chapter07-26.jpg

基本的信息就是type和addr,可以通过宏I2C_BOARD_INFO来产生。

对I2C总线设备的创建,框架提供的函数接口如下:

978-7-111-49426-3-Chapter07-27.jpg

从中可见,创建总线设备需要i2c_adapter和i2c_board_info,对于特定总线设备有i2c_board_info信息,这就需要明确与i2c_adapter关联。

有两种方法来实现:方法一通过structi2c_adapter *i2c_get_adapter(intid)获得指定的i2c_adapter,然后调用i2c_new_device来创建总线设备。方法二是在初始化阶段调用如下函数,注册某个I2C总线上的所有设备信息:i2c_register_board_info(int busnum,struct i2c_board_info const *info,unsigned len),总线控制器i2c_adapter会通过i2c_scan_static_board_info来遍历这些信息,为总线上的设备创建i2c_client。

方法一主要用于总线设备信息初始化晚于i2c_adapter初始化的情况,如camer sensor的v4l2子设备;而方法二则是用于较早初始化的情况。

4.总线传输接口

总线传输是完成功能型事务的基础,之前已经了解到,一次总线传输可以进行多个i2c_msg的交互,i2c_msg是传输的基本信息。下面来看一下详细内容:

978-7-111-49426-3-Chapter07-28.jpg

978-7-111-49426-3-Chapter07-29.jpg

标准I2C传输的接口函数是i2c_transfer,具体内容如下:

978-7-111-49426-3-Chapter07-30.jpg

978-7-111-49426-3-Chapter07-31.jpg

从代码中可见,实现一定的重传和超时机制主要还是通过adapter中操作接口master_xfer来完成的。

具体的操作通常由功能型驱动完成。下面还是以mt9t111 sensor为例:

978-7-111-49426-3-Chapter07-32.jpg

978-7-111-49426-3-Chapter07-33.jpg

以上分别是读和写的例子,可见主要的工作就是通过i2c_client的信息构建i2c_msg,然后调用i2c_transfer来完成传输。

为了免去驱动构建i2c_msg,框架提供了以下接口以方便驱动调用:

978-7-111-49426-3-Chapter07-34.jpg

这些函数都是对i2c_transfer的封装。Linux的I2C框架主要提供的是主设备的功能,而从设备功能并没有涉及,这与使用Linux设备通常都通过I2C总线连接从设备相关。