赞
踩
2.1 LinuxI2C子系统架构
在内核中已经提供I2C子系统,所以在做I2C驱动之前,就必须要熟悉该子系统。
2.2 三大组成部分
1、I2C核心(i2c-core)
I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(algorithm)上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
2、I2C总线驱动(I2Cadapter/Algo driver)
I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力。
I2C总线驱动由i2c_adapter和i2c_algorithm来描述
3、I2C客户驱动程序(I2Cclient driver)
I2C客户驱动是对I2C从设备的软件实现,一个具体的I2C客户驱动包括两个部分:一部分是i2c_driver,用于将设备挂接于i2c总线;另一部分是设备本身的驱动。
I2C客户驱动程序由i2c_driver和i2c_client来描述
2.3 所有的I2C驱动代码位于drivers/i2c目录下
I2c-core.c 实现I2C核心的功能
I2c-dev.c 通用的从设备驱动
Chips 特定的I2C设备驱动
Busses I2C适配器的驱动
Algos 实现了一些I2C总线适配器的algorithm
2.4 I2C驱动编写的两种方法
从上面的图我们可以看到两种编写驱动方法,一种是利用系统提供的i2c-dev.c来实现一个i2c适配器的设备文件,然后通过在应用层操作I2C适配器来控制I2C设备;另一种是为I2C从设备独立编写一个设备驱动,不需要i2c-dev.c文件。
2.5 重要的数据结构
每次分析子系统免不了分析它的数据结构,OK我们先来分析一下。
I2c_adapter结构体代表I2C总线控制器
I2c_algorithm对应一套通信方法
[cpp] view plaincopy
Functionality 函数用于返回algorithm所支持的通信协议,比如I2C_FUNC_I2C,I2C_FUNC_10BIT_ADDR等。
Master_xfer 函数实现总线上数据传输,与具体的适配器有关
Master_xfer函数实现模板
[cpp] view plaincopy
上面调用的函数用于完成适配器的底层硬件操作,与I2C适配器和CPU的具体硬件直接相关,需要由工程师根据芯片的数据手册来实现。在内核源码中,针对不同的I2C适配器都有master_xfer的实现,风格与模板不尽相同,但是可以用该模板作为参考来看源代码,受益匪浅。
I2c_driver代表I2C从设备驱动
[cpp] view plaincopy
在新内核中,attach_adapter和detach_adapter已经被probe和remove取代
Id_table用于i2c_driver和i2c_client的匹配
I2c_client代表I2C从设备
[cpp] view plaincopy
2.6 核心层提供的接口函数
1、 增加/删除I2C适配器
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_del_adapter(struct i2c_adapter *adap)
[cpp] view plaincopy
Device_register(&adap->dev) 向I2C总线注册一个adapter设备
i2c_scan_static_board_info(adap) 注册所有已知的i2c_client
2、 增加/删除I2C从设备驱动
[cpp] view plaincopy
driver_register(&driver->driver) 向I2C总线注册一个i2c_driver
3、 i2c传输,发送和接收
[cpp] view plaincopy
最终会调用到适配器实现的master_xfer函数来完成数据传输工作
3.1 概述
之前在介绍I2C子系统时,提到过使用i2c-dev.c文件在应用程序中实现我们的I2C从设备驱动。不过,它实现的是一个虚拟,临时的i2c_client,随着设备文件的打开而产生,并随着设备文件的关闭而撤销。I2c-dev.c针对每个I2C适配器生成一个主设备号为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口,所以i2c-dev.c的主题是”i2c_driver成员函数+字符设备驱动”。
3.2 i2c-dev.c源码分析
初始化模块
[cpp] view plaincopy
I2c-dev初始化函数主要做了注册名为”i2c”的字符设备文件和”i2c-dev”的类
i2cdev_read和i2cdev_write
I2c-dev.c中实现的i2cdev_read和i2cdev_write函数不具有太强的通用性,只适合下面这种单开始信号情况:
而不适合多开始信号的情况:
所以我们经常会使用i2cdev_ioctl函数的I2C_RDWR,在分析i2cdev_ioctl函数之前,我们需要了解一个结构体:
[cpp] view plaincopy
Msgs 表示单个开始信号传递的数据;
Nmsgs 表示有多少个msgs,比如上图,单开始信号时,nmsgs等于1;多开始信号时,nmsgs等于2
[cpp] view plaincopy
使用i2cdev_ioctl函数的I2C_RDWR指令会调用到i2cdev_ioctl_rdrw函数:
[cpp] view plaincopy
咋一看,还挺复杂,其实主要做了一件事情:把用户空间传递过来的i2c_rdwr_ioctl_data数据进行错误检查,然后调用i2c_transfer函数与适配器进行通信,如果是接收数据,代码会将访问到的数据传回i2c_rdwr_ioctl_data的buf中。I2c_transfer最终会调用到I2C适配器具体实现的master_xfer函数来与硬件进行通信。
3.3 eeprom实例
预备知识
使用的mini2440开发板,eeprom的地址为0x50,实验完成一个数据的读写,先看下读写时序
AT24C08任意地址字节写的时序:
AT24C08任意地址字节写的时序:
下面的代码可以按照上面的两个图来阅读:
[cpp] view plaincopy
4.1 概述
I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力,比如起始,停止,应答信号和master_xfer的实现函数。
I2C总线驱动由i2c_adapter和i2c_algorithm来描述
4.2 S3c2440I2C控制器的硬件描述
S3c2440处理器内部集成了一个I2C控制器,通过四个寄存器来进行控制:
IICCON I2C控制寄存器
IICSTAT I2C状态寄存器
IICDS I2C收发数据移位寄存器
IICADD I2C地址寄存器
通过IICCON,IICDS,IICADD寄存器操作,可在I2C总线上产生开始位、停止位、数据和地址,而传输的状态则通过IICSTAT寄存器来获取。
4.3 i2c-s3c2410总线驱动分析(platform_driver)
I2C总线驱动代码在drivers/i2c/busses/i2c-s3c2410.c,这个代码同样支持s3c2410,s3c6410,s5pc110等Samsung 系列的芯片。
初始化模块和卸载模块
[cpp] view plaincopy
总线驱动是基于platform来实现的,很符合设备驱动模型的思想。
[cpp] view plaincopy
s3c24xx_i2c_probe函数
当调用platform_driver_register函数注册platform_driver结构体时,如果platformdevice 和 platform driver匹配成功后,会调用probe函数,来初始化适配器硬件。
[cpp] view plaincopy
Probe主要工作是时能硬件并申请I2C适配器使用的IO地址,中断号等,然后向I2C核心添加这个适配器。I2c_adapter注册过程i2c_add_numbered_adapter->i2c_register_adapter
I2C总线通信方法
[cpp] view plaincopy
s3c24xx_i2c_xfer函数是总线通信方式的具体实现,依赖于s3c24xx_i2c_doxfer和s3c24xx_i2c_message_start两个函数;
[cpp] view plaincopy
首先设置s3c I2C设备器为主设备,然后调用s3c24xx_i2c_message_start函数启动I2C消息传输。
s3c24xx_i2c_func函数返回适配器所支持的通信功能。
4.4 适配器的设备资源(platform_device)
S3c2440的I2C总线驱动是基于platform来实现,前面我们分析了platformdriver部分,再来看下platform device部分。
在arch/arm/plat-samsung/dev-i2c0.c文件中定义了platform_device结构体以及I2C控制器的资源信息:
[cpp] view plaincopy
在板文件中把platform_device注册进内核:
[cpp] view plaincopy
调用s3c_i2c0_set_platdata 函数把适配器具体的数据赋值给dev.platform_data:
[cpp] view plaincopy
I2C总线驱动就分析到这里。
5 客户驱动
5.1 概述
I2C客户驱动是对I2C从设备的实现,一个具体的I2C客户驱动包括两个部分:一部分是i2c_driver,用于将设备挂接于i2c总线;另一部分是设备本身的驱动。
I2C客户驱动程序主要由i2c_driver和i2c_client来描述。
5.2 实例源码分析
好了,我们来深入了解客户驱动代码的实现,drivers/misc/eeprom/at24.c文件支持大多数I2C接口的eeprom
I2c_driver实现
[cpp] view plaincopy
初始化和卸载
[cpp] view plaincopy
At24_Probe函数
[cpp] view plaincopy
Probe函数主要的工作是在sys目录下创建bin节点文件,用户可以同此节点文件来操作eeprom,并提供操作方法(read,write)
5.3 I2c_client实现
At24c不依赖于具体的CPU和I2C控制器硬件特性,因此如果电路板包含该外设,只需要添加对应的i2c_board_info,下面是at24c08 i2c_client在板文件中的实现:
[cpp] view plaincopy
I2c_register_board_info函数会把I2C从设备硬件特性信息注册到全局链表__i2c_board_list,在调用i2c_add_adapter函数时,会遍历__i2c_board_list获得从设备信息来构造i2c_client。
I2c_client的构建
我们调用I2c_register_board_info函数会把I2C从设备硬件特性信息注册到全局链表__i2c_board_list,但是还没有构建出一个i2c_client结构体,也没有注册进I2C总线。我们来分析一下构造的过程,调用i2c_add_adapter函数时,会遍历__i2c_board_list获得从设备信息来构造i2c_client:i2c_register_adapter()->i2c_scan_static_board_info()->i2c_new_device()->device_register()。
5.4 I2c_driver和i2c_client的match
在调用i2c_add_driver注册i2c_driver和构建i2c_client时,都会调用i2c bus中注册的i2c_device_match()->i2c_match_id()函数通过i2c_driver->id_table->name和client->name来匹配
[cpp] view plaincopy
5.5 测试
已在mini2440上实验成功,在/sys/bus/i2c/devices/0-0050/目录下(50代表从设备地址)会产生一个eeprom文件,这个文件相当于是硬件设备eeprom的映射,我们可以像普通文件一样对eeprom文件进行操作,实质上就是就硬件eeprom的操作。重启开发板,你会发现对eeprom文件修改过的内容不会改变,这就证明实验成功了,要知道sys文件系统是无法对数据保存的。
6 总结
下图根据之前的分析丰富的架构图
Tips:I2C适配器驱动不一定是基于platform实现,这里是以s3c-i2c为例。
I2c_driver、i2c_client与i2c_adapter
I2c_driver与i2c_client是一对多的关系,一个i2c_driver上可以支持多个同等类型的i2c_client。调用i2c_add_driver函数将I2c_driver注册到I2C总线上,调用i2c_register_board_info函数将i2c_client注册到全局链表__i2c_board_list。当调用i2c_add_adapter注册适配器时,遍历__i2c_board_list链表,i2c_register_adapter()->i2c_scan_static_board_info()->i2c_new_device()会构建i2c_client结构。当调用i2c_add_driver时,会先注册i2c_driver到I2C总线上,然后调用I2C BUS注册的match函数进行匹配,如果匹配成功,则先调用I2C BUS中注册的probe函数,在调用i2c_driver中实现的probe函数,完成相应的工作。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。