驱动i2c控制器归根到底是对IIC控制器的寄存器进行读写,因此,理解了linux中是怎样通过层层调用来操作IIC的寄存器,便理解了整个IIC子系统的轮廓。
下面以公司使用的重力传感器(bma250)驱动为例来描述这个轮廓。
首先介绍三个比较重要的驱动文件:
bma250.c(Drivers/Gsensor/),I2c_core.c(Drivers/I2c/),I2c-sunxi.c(/Drivers/I2c/busses)。
bma250.c与外接的传感器bma250有关(即client端),I2c_sunxi.c与主控的IIC控制器有关(即adapter端,是对IIC寄存器操作的所在文件),I2c_core.c则起到为前面两个文件进行关联的作用(实际上就是总线管理的作用,而这里是IIC总线)。区分开的一个很大的好处便是移植性强,比如bma250.c可以用于一切支持linux的硬件平台,而I2c_sunxi.c可用于A10的所有IIC外围器件,硬件变动下,I2c_core.c则基本不用改变。
在I2c_sunxi.c的模块初始化中定义了一个平台设备,用于获取A10的IIC寄存器地址和中断向量,而在平台设备匹配成功后调用的i2c_sunxi_probe函数中,初始化了一个i2c_adapter结构体,这个结构体用于描述A10的IIC控制器,并通过i2c_add_numbered_adapter函数向IIC总线添加了添加了这个adapter。i2c_adapter结构体有个很重要的成员,即i2c_algorithm结构,该结构下又有一个很重要的成员:master_xfer,I2c_sunxi.c在初始化i2c_adapter时将master_xfer初始化为i2c_sunxi_xfer,而i2c_sunxi_xfer正是对A10的IIC寄存器进行操作的开始。在此函数中,通过直接操作IIC寄存器,使能了IIC中断,发出了IIC的开始信号,并通过wait_event_timeout函数让进程进入休眠,接下来通过层层中断(比如IIC开始信号发出完成后会发生中断,发送完一个字节也会发生中断)将i2c结构体中的msg数据发送出去,此处i2c是i2c_adapter结构体中的algo_data成员,要发送的数据都放在这个msg结构体里。
因此,只需要注册并调用i2c_adapter->algo->smbus_xfer,便可以让IIC控制器按要求发出或接收数据。
I2c_core.c在完成建设IIC总线的同时,还完成了对i2c_adapter->algo->smbus_xfer的封装,即i2c_transfer,而i2c_smbus_read_i2c_block_data之类的函数又对i2c_transfer进行了封装。因而只需调用i2c_smbus_read_i2c_block_data等函数,并传入对应的i2c_adapter,便可以让控制器按要求发出或接收数据。
在bma250.c的模块初始化中,注册了一个i2c_driver:bma250_driver,该步骤引发了bma250(client)与A10的IIC接口(adapter)的匹配。匹配中一个非常关键的因素是bma250_driver中的address_list,即bma250的IIC地址,新版本的内核隐藏了这一步的实现,变得自动化,只需要给出的address_list正确,硬件电路正确,linux的IIC总线上挂有对应的adapter,变可以匹配成功,匹配成功后,linux新建了一个client结构,并将结构里的adapter初始化为匹配成功的那个adapter,至此,总线上,bma250便与A10的IIC接口配对,bma250.c中的函数所用到的参数adapter,都是配对成功的那个adapter.
这里说一下旧版本(2.6和2.6之前)内核client和adapter的匹配过程。在调用IIC驱动注册函数时(i2c_add_driver),引发了i2c_probe函数的调用,在该函数中,把注册在IIC总线上的adapter逐个拿出来做实验,实验过程如下:将IIC有可能的地址0~0x7F,逐个与address_list里的地址进行对比,如果相同则调用i2c_smbus_xfer,该函数实际操作了目前这个被实验中的adatper的寄存器,发出信号与bma250通讯。若bma250发出了应答信号,则匹配成功。匹配的实现算法与硬件无关,并且算法应该是比较成熟了,所以新内核将其直接拿来用,并将其隐藏起来,不再需要驱动开发者重写过匹配算法。
到这里总结一下:I2c_core.c建设了一条IIC总线,I2c-sunxi.c向总线添加了一个adapter,而bma250.c通过向总线注册一个IIC驱动,探测总线上适合(硬件上连接)的adapter,探测成功后bma250便与A10建立联系,bma250.c通过调用I2c_core.c的函数从而调用I2c-sunxi.c里的函数,完成了IIC硬件上的实现。
下面以bma250.c为例,简单说一下上层到下层的调用过程。向总线注册完IIC驱动后,将匹配到的adapter作为后续函数的参数。Bma250驱动通过一工作队列定时调用函数bma250_read_accel_xyz,该函数读出了bma250里的三轴数据,通过输入子系统,将数据上报给系统。整个调用路线如下:
bma250_read_accel_xyz → bma250_smbus_read_byte_block →i2c_smbus_read_i2c_block_data(进入I2c_core.c)→ i2c_smbus_xfer → adapter->algo->smbus_xfer(i2c_sunxi_xfer) → i2c_sunxi_do_xfer(引发中断)→ i2c_sunxi_handler(IIC中断函数)→i2c_sunxi_core_process→aw_twi_put_byte/aw_twi_get_byte等 →readl/ writel(此函数正是操作A20寄存器的函数)
这便是整个IIC子系统的实现轮廓。
以下总结编写IIC驱动所需做的工作:
1、通过平台设备或其他手段获取主控IIC控制器的资源(寄存器地址和中断向量号),申请中断并实现中断函数。
2、向IIC总线添加一个初始化好的i2c_adapter结构体。而初始化一个i2c_adapter结构体时最重要的一步是实现i2c_adapter中的algo成员,在algo的成员函数中通过操作寄存器,发出IIC起始信号引发后续数据的发送与接收(通过中断,里面也是通过操作寄存器实现)。
3、向IIC总线添加一个初始化好的i2c_driver结构体。初始化时比较重要的是给出IIC设备的地址。在其probe函数中完成向应用层接口的初始化,如初始化一个输入子系统,用于上报数据。
阅读(3336) | 评论(0) | 转发(0) |