Linux I2C驱动完全分析(一)

http://blog.csdn.net/ypoflyer/article/details/6376545

博主按:其实老早就想写这个I2C的了,期间有各种各样的事情给耽误了。借着五一放假的时间把这个写出来,供同志们参考。以后会花一些时间深入研究下内核,虽然以前对内核也有所了解,但是还不系统。I2C的硬件结构并不复杂,一个适配器加几个设备而已。Linux下驱动的体系结构看着挺复杂,实际也是比较简单的。在本文中我还是使用实际的例子,结合硬件和软件两个方面来介绍。希望能给初学的同志们一些帮助,另外抛砖引玉,希望高手能给一些指点。话不多说,开整!~

本文用到的一些资源:

1. Source Insight软件

2. mini2440原理图。 下载地址http://wenku.baidu.com/view/0521ab8da0116c175f0e48fe.html

3.S3C2440 datasheet

4. AT24C08 datasheet

5. Bq27200 datasheet

6. kernel 2.6.31中的At24.c ,Bq27x00_battery.c和i2c-s3c2410.c

7. mini2440的板文件mach-mini2440.c

8. 参考资料:《linux设备驱动开发详解(第2版)》 by 宋宝华

本文的结构:

第一部分:At24C08驱动

1. mini2440中at24c08的电气连接

2. Linux中I2C驱动框架分析

3. I2C总线驱动代码分析

4. at24c08驱动代码分析

第二部分:Bq27200驱动

1. Bq27200的典型应用电路

2. 主要分析一下ba27x00的代码,对比at24c08来加深理解。

———————我是分割线———————-

第一部分

1. mini2440中at24c08的电气连接及其板文件

如下图。

24C08的I2C接口是与2440的IICSCL/IICSDA直接相连的。在2440内部集成了一个I2C控制器,可以通过寄存器来控制它。先来和这四个寄存器混个脸熟吧,后面分析时还会经常用到这四个寄存器。

在mini2440的板文件中可以找到关于at24c08的内容,如下:

[c-sharp]view plaincopy

    /**I2Cdevices*/staticstructat24_platform_dataat24c08={.byte_len=SZ_8K/8,.page_size=16,};staticstructi2c_board_infomini2440_i2c_devs[]__initdata={{I2C_BOARD_INFO("24c08",0x50),.platform_data=&at24c08,},};staticvoid__initmini2440_init(void){……i2c_register_board_info(0,mini2440_i2c_devs,ARRAY_SIZE(mini2440_i2c_devs));……}

可以看出,在mini2440的init函数中注册了一个i2c的设备,这个设备我们使用了一个结构体i2c_board_info来描述。这个结构体定义在i2c.h文件中。如下:

[c-sharp]view plaincopy

    structi2c_board_info{chartype[I2C_NAME_SIZE];unsignedshortflags;unsignedshortaddr;void*platform_data;structdev_archdata*archdata;intirq;};

其中的platform_data又指向一个at24_platform_data结构体。

以上只是at24c08的部分,在板文件中还可以看到关于2440内部i2c控制器的部分,如下:

[c-sharp]view plaincopy

    staticstructplatform_device*mini2440_devices[]__initdata={……&s3c_device_i2c0,……};staticvoid__initmini2440_init(void){……platform_add_devices(mini2440_devices,ARRAY_SIZE(mini2440_devices));……}

其中s2c_device_i2c0定义在arch/arm/plat-s3c/Dev-i2c0.c中(在同一目录下还可以看到很多Dev-开头的c文件,都是2440内部集成的各种设备),仔细看下面的代码再对比2440的datasheet就可以很清楚的知道:

* 控制器的IO起始地址为S3C_PA_IIC =0x54000000,大小是4K,中断号是43 = IRQ_IIC S3C2410_IRQ(27)

* 控制器名是"s3c2410-i2c"

[c-sharp]view plaincopy

    staticstructresources3c_i2c_resource[]={[0]={.start=S3C_PA_IIC,.end=S3C_PA_IIC+SZ_4K-1,.flags=IORESOURCE_MEM,},[1]={.start=IRQ_IIC,.end=IRQ_IIC,.flags=IORESOURCE_IRQ,},};structplatform_devices3c_device_i2c0={.name="s3c2410-i2c",#ifdefCONFIG_S3C_DEV_I2C1.id=0,#else.id=-1,#endif.num_resources=ARRAY_SIZE(s3c_i2c_resource),.resource=s3c_i2c_resource,};

2. Linux中I2C驱动框架分析

这部分是本文的重点部分。根据上面的电气连接关系我们可以看出,我们要想操作24c08,必须要做两方面的驱动。

第一方面: 2440中I2C控制器的驱动,有了这部分驱动,我们才可以操作控制器来产生I2C的时序信号,来发送数据和接收数据。

第二方面: 24C08的驱动,有了这部分驱动,才能使用控制器正确操作芯片,来读取和存放数据。

在Linux系统中,对上边第一方面的实现叫做I2C总线驱动,对第二方面的实现叫做I2C设备驱动。一般来说,如果CPU中集成了I2C控制器并且Linux内核支持这个CPU,那么总线驱动方面就不用我们操心了,内核已经做好了。但如果CPU中没有I2C控制器,而是外接的话,那么就要我们自己实现总线驱动了。对于设备驱动来说,一般常用的驱动也都包含在内核中了,如果我们用了一个内核中没有的芯片,那么就要自己来写了。

Linux中I2C体系结构如下图所示(图片来源于网络)。图中用分割线分成了三个层次:用户空间(也就是应用程序),内核(也就是驱动部分)和硬件(也就是实际物理设备,这里就是2440中的i2c控制器和at24c08)。这个够清晰了吧?我们现在就是要研究中间那一层。

由上图我们还可以看出哪些信息呢?

1). 可以看到几个重要的组成部分,它们是:Driver,Client,i2c-dev,i2c-core,Algorithm,Adapter。这几个部分在内核中都有相应的数据结构,定义在i2c.h文件中,尽量避免粘贴打断代码来凑数,就不贴出来了。简要概括一下每个结构体的意义。

Driver –> struct i2c_driver

这个结构体对应了驱动方法,重要成员函数有probe,remove,suspend,resume。

还包括一个重要的数据结构: struct i2c_device_id *id_table; 如果驱动可以支持好几个设备,那么这里面就要包含这些设备的ID

Client –> struct i2c_client

应用程序是选择性失明的,它只能看到抽象的设备文件,其他部分都是看不见的。图中只有Client与应用程序有联系,所以我们可以大胆得出结论:这个Client是对应于真实的物理设备,在本文就是at24c08。所以很显然这个结构体中的内容应该是描述设备的。包含了芯片地址,设备名称,设备使用的中断号,设备所依附的控制器,设备所依附的驱动等内容。

Algorithm –>struct i2c_algorithm

Algorithm就是算法的意思。在这个结构体中定义了一套控制器使用的通信方法。其中关键函数是master_xfer()。我们实际工作中的重要一点就是要实现这个函数。

[c-sharp]view plaincopy

    int(*master_xfer)(structi2c_adapter*adap,structi2c_msg*msgs,intnum);

Adapter –> struct i2c_adapter

这个结构体对应一个控制器。其中包含了控制器名称,algorithm数据,控制器设备等。

2). 可以看出,i2c-core起到了关键的承上启下的作用。事实上也是这样,我们将从这里展开来分析。源代码位于drivers/i2c/i2c-core.c中。在这个文件中可以看到几个重要的函数。

*增加/删除i2c控制器的函数

[c-sharp]view plaincopy

    inti2c_add_adapter(structi2c_adapter*adapter)inti2c_del_adapter(structi2c_adapter*adap)

*增加/删除设备驱动的函数

[c-sharp]view plaincopy

    inti2c_register_driver(structmodule*owner,structi2c_driver*driver)voidi2c_del_driver(structi2c_driver*driver)

*增加/删除i2c设备的函数

[c-sharp]view plaincopy

    structi2c_client*i2c_new_device(structi2c_adapter*adap,structi2c_board_infoconst*info);voidi2c_unregister_device(structi2c_client*client)

注:在2.6.30版本之前使用的是i2c_attach_client()和i2c_detach_client()函数。之后attach被merge到了i2c_new_device中,而detach直接被unresister取代。实际上这两个函数内部都是调用了device_register()和device_unregister()。源码如下:

*I2C传输、发送和接收函数

[c-sharp]view plaincopy

    inti2c_transfer(structi2c_adapter*adap,structi2c_msg*msgs,intnum);inti2c_master_send(structi2c_client*client,constchar*buf,intcount);inti2c_master_recv(structi2c_client*client,char*buf,intcount);

其中send和receive分别都调用了transfer函数,而transfer也不是直接和硬件交互,而是调用algorithm中的master_xfer()函数,所以我们要想进行数据传输,必须自己来实现这个master_xfer()函数,这是总线驱动开发的重点之一。下面以read()系统调用的流程来简单梳理一下:

(待续)

如同磁铁吸引四周的铁粉,热情也能吸引周围的人,改变周围的情况。

Linux I2C驱动完全分析(一)

相关文章:

你感兴趣的文章:

标签云: