linux下IIC驱动开发分析

1.  IIC规范
IIC(InterIntegrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。IIC总线产生于在80年代,最初为音频和视频设备开发,如今主要在服务器管理中使用,其中包括单个组件状态的通信。例如管理员可对各个组件进行查询,以管理系统的配置或掌握组件的功能状态,如电源和系统风扇。可随时监控内存、硬盘、网络、系统温度等多个参数,增加了系统的安全性,方便了管理。
2.1      IIC总线的特点
IIC总线最主要的优点是其简单性和有效性。由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。IIC总线的另一个优点是,它支持多主控(multimastering) 其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。
2.2      IIC总线工作原理
2.2.1              总线构成及信号类型
IIC总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、ICIC之间进行双向传送,最高传送速率100kbps。各种被控制电路均并联在这条总线上,但就像电话机一样只有拨通各自的号码才能工作,所以每个电路和模块都有唯一的地址,在信息的传输过程中,IIC总线上并接的每一模块电路既是主控器(或被控器),又是发送器(或接收器),这取决于它所要完成的功能。
CPU发出的控制信号分为地址码和控制量两部分:
?        地址码用来选址,即接通需要控制的电路,确定控制的种类;
?        控制量决定该调整的类别(如对比度、亮度等)及需要调整的量。
这样,各控制电路虽然挂在同一条总线上,却彼此独立,互不相关。
IIC总线在传送数据过程中共有三种类型信号:
?        开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
?        结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
?        数据传输信号:在开始条件以后,时钟信号SCL的高电平周期期问,当数据线稳定时,数据线SDA的状态表示数据有效,即数据可以被读走,开始进行读操作。在时钟信号SCL的低电平周期期间,数据线上数据才允许改变。每位数据需要一个时钟脉冲。
?        应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
目前有很多半导体集成电路上都集成了IIC接口。带有IIC接口的单片机有:CYGNAL C8051F0XX系列,PHILIPSP87LPC7XX系列,MICROCHIPPIC16C6XX系列等。很多外围器件如存储器、监控芯片等也提供IIC接口。
2.3       总线基本操作
IIC规程运用主/从双向通讯。器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。 总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。SDA线上的数据状态仅在SCL为低电平的期间才能改变,SCL为高电平的期间,SDA状态的改变被用来表示起始和停止条件。
2.3.1 控制字节
在起始条件之后,必须是器件的控制字节,其中高四位为器件类型识别符(不同的芯片类型有不同的定义,EEPROM一般应为1010),接着三位为片选,最后一位为读写位,当为1时为读操作,为0时为写操作。
2.3.2 写操作
写操作分为字节写和页面写两种操作,对于页面写根据芯片的一次装载的字节不同有所不同。关于页面写的地址、应答和数据传送的时序。
2.3.3 读操作
读操作有三种基本操作:当前地址读、随机读和顺序读。图4给出的是顺序读的时序图。应当注意的是:最后一个读操作的第9个时钟周期不是“不关心”。为了结束读操作,主机必须在第9个周期间发出停止条件或者在第9个时钟周期内保持SDA为高电平、然后发出停止条件。
2.3.4 总线仲裁
主机只能在总线空闲的时候启动传输。两个或多个主机可能在起始条件的最小持续内产生一个起始条件,结果在总线上产生一个规定的起始条件。
SCL线是高电平时,仲裁在SDA线发生:这样,在其他主机发送低电平时,发送高电平的主机将断开它的数据输出级,因为总线上的电平和它自己的电平不同。
仲裁可以持续多位。从地址位开始,同一个器件的话接着就是数据位(如果主机-发送器),或者比较相应位(如果主机-接收器)。IIC总线的地址和数据信息由赢得仲裁的主机决定,在这个过程中不会丢失信息。
仲裁不能在下面情况之间进行:
?        重复起始条件和数据位;
?        停止条件和数据位;
?        重复起始条件和停止条件。
2.4       特性总结
?        IIC肯定是2线的(不算地线)IIC协议确实很科学,3/4线的SPI要好,当然线多通讯速率相对就快了
?        IIC的原则是
l        SCL=1(高电平),SDA千万别忽悠!!!否则,SDA下跳则"判罚""起始信号S",SDA上跳则"判罚""停止信号P".
l        SCL=0(低电平),SDA随便忽悠!!!(可别忽悠过火到SCL跳高)
?        每个字节后应该由对方回送一个应答信号ACK做为对方在线的标志.非应答信号一般在所有字节的最后一个字节后.一般要由双方协议签定.
?        SCL必须由主机发送,否则天下大乱
?        首字节是"片选信号",7位从机地址加1位方向(读写)控制.从机收到(听到)自己的地址才能发送应答信号(必须应答!!!)表示自己在线.其他地址的从机不允许忽悠!!!(当然群呼可以忽悠但只能听不许说话)
?        读写是站在主机的立场上定义的.""是主机接收从机数据,""是主机发送数据给从机.
?        重复位主要用于主机从发送模式到接收模式的转换"信号",由于只有2线,所以收发转换肯定要比SPI复杂,因为SPI可用不同的边沿来收发数据,IIC不行.
?        在硬件IIC模块,特别是MCU/ARM/DSP等每个阶段都会得到一个准确的状态码,根据这个状态码可以很容易知道现在在什么状态和什么出错信息.
?        7IIC总线可以挂接127个不同地址的IIC设备,0"设备"作为群呼地址.10IIC总线可以挂接更多的10IIC设备.

原文地址:linux下IIC驱动开发分析(2) 作者:putiancaijunyu
3.1       LinuxIIC驱动架构
Linux定义了系统的IIC驱动体系结构,在Linux系统中,IIC驱动由3部分组成,即IIC核心、IIC总线驱动和IIC设备驱动。这3部分相互协作,形成了非常通用、可适应性很强的IIC框架。
3.1.1 IIC核心
IIC 核心提供了IIC总线驱动和设备驱动的注册、注销方法,IIC通信方法(即“algorithm”,笔者认为直译为运算方法并不合适,为免引起误解, 下文将直接使用“algorithm”)上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
在我们的Linux驱动的i2c文件夹下有algosbusseschips三个文件夹,另外还有i2c-core.ci2c-dev.c两个文件。
i2c-core.c文件实现了I2Ccore框架,是Linux内核用来维护和管理的I2C的核心部分,其中维护了两个静态的List,分别记录系统中的I2Cdriver结构和I2Cadapter结构。I2Ccore提供接口函数,允许一个I2CadatperI2CdriverI2Cclient初始化时在I2Ccore中进行注册,以及退出时进行注销。同时还提供了I2C总线读写访问的一般接口,主要应用在I2C设备驱动中。
3.1.2 IIC总线驱动
IIC总线驱动是对IIC硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至直接集成在CPU内部。总线驱动的职责,是为系统中每个I2C总线增加相应的读写方法。但是总线驱动本身并不会进行任何的通讯,它只是存在那里,等待设备驱动调用其函数。
IIC总线驱动主要包含了IIC适配器数据结构i2c_adapterIIC适配器的algorithm数据结构i2c_algorithm和控制IIC适配器产生通信信号的函数。经由IIC总线驱动的代码,我们可以控制IIC适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。
 Busses文件夹下的i2c-mpc.c文件实现了PowerPCI2C总线适配器驱动,定义描述了具体的I2C总线适配器的i2c_adapter数据结构,实现比较底层的对I2C总线访问的具体方法。I2Cadapter 构造一个对I2Ccore层接口的数据结构,并通过接口函数向I2Ccore注册一个控制器。I2Cadapter主要实现对I2C总线访问的算法,iic_xfer() 函数就是I2Cadapter底层对I2C总线读写方法的实现。同时I2Cadpter 中还实现了对I2C控制器中断的处理函数。
3.1.3 IIC设备驱动
IIC设备驱动是对IIC硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的IIC适配器上,通过IIC适配器与CPU交换数据。设备驱动则是与挂在I2C总线上的具体的设备通讯的驱动。通过I2C总线驱动提供的函数,设备驱动可以忽略不同总线控制器的差异,不考虑其实现细节地与硬件设备通讯。
IIC设备驱动主要包含了数据结构i2c_driveri2c_client,我们需要根据具体设备实现其中的成员函数。
i2c-dev.c文件中实现了I2Cdriver,提供了一个通用的I2C设备的驱动程序,实现了字符类型设备的访问接口,实现了对用户应用层的接口,提供用户程序访问I2C设备的接口,包括实现openreleasereadwrite以及最重要的ioctl等标准文件操作的接口函数。我们可以通过open函数打开 I2C的设备文件,通过ioctl函数设定要访问从设备的地址,然后就可以通过 readwrite函数完成对I2C设备的读写操作。
通过I2Cdriver提供的通用方法可以访问任何一个I2C的设备,但是其中实现的readwriteioctl等功能完全是基于一般设备的实现,所有的操作数据都是基于字节流,没有明确的格式和意义。为了更方便和有效地使用I2C设备,我们可以为一个具体的I2C设备开发特定的I2C设备驱动程序,在驱动中完成对特定的数据格式的解释以及实现一些专用的功能。



原文地址:linux下IIC驱动开发分析(3) 作者:putiancaijunyu
3.1       LinuxIIC驱动开发
3.2.1 IIC总线驱动
在系统开机时,首先装载的是I2C总线驱动。一个总线驱动用于支持一条特定的I2C总线的读写。一个总线驱动通常需要两个模块,一个struct i2c_adapter和一个struct i2c_algorithm来描述:

static struct i2c_adapter pb1550_board_adapter =
{
  name:              "pb1550 adapter",
  id:                I2C_HW_AU1550_PSC,
  algo:              NULL,
  algo_data:         &pb1550_i2c_info,
  inc_use:           pb1550_inc_use,
  dec_use:           pb1550_dec_use,
  client_register:   pb1550_reg,
  client_unregister: pb1550_unreg,
  client_count:      0,
};

这个样例挂接了一个叫做 “pb1550 adapter” 的驱动。但这个模块并未提供读写函数,具体的读写方法由第二个模块, struct i2c_algorithm 提供。
static struct i2c_algorithm au1550_algo =
{
 .name         = "Au1550 algorithm",
 .id      = I2C_ALGO_AU1550,
 .master_xfer = au1550_xfer,
 .functionality     = au1550_func,
};
i2c_adap->algo = &au1550_algo;

这个样例给上述总线驱动增加了读写 算法 。通常情况下每个 I2C 总线驱动都定义一个自己的读写算法,但鉴于有些总线使用相同的算法,因而可以共用同一套读写函数。本例中的驱动定义了自己的读写算法模块,起名叫 “Au1550 algorithm”
全部填妥后,通过调用:
i2c_add_adapter(i2c_adap);
将这两个模块注册到操作系统里,总线驱动就算装上了。对于 AMD au1550 ,这部分已经由 AMD 提供了。
3.2.2 IIC 设备驱动
I2C 的设备驱动是通过 i2c_add_driver(&my_driver) i2c-core 注册的, my_driver 中的核心是 detach_client  attach_adapter 函数,在 attach_adapter 中通过 probe 探测到总线上的设备并把设备和驱动建立连接以完成设备的初始化。
设备驱动透过 I2C 总线同具体的设备进行通讯。一个设备驱动有两个模块来描述, struct i2c_driver struct i2c_client
当系统开机、 I2C 总线驱动装入完成后,就可以装入设备驱动了。首先装入如下结构:
static struct i2c_driver zlg7290_driver = {
     .owner           = THIS_MODULE,
     .name            = DEV_NAME,
//   .id          = I2C_DRIVERID_ZLG7290,
     .flags             = I2C_DF_NOTIFY,
     .attach_adapter     = zlg7290_attach_adapter,
     .detach_client       = zlg7290_detach_client,
};
 
i2c_add_driver(&zlg7290_driver);
这个 i2c_driver 一旦装入完成,其中的 attach_adapter 函数就会被调用。
如何探测总线上的设备、 Linux i2c 设备地址如何标识并引用?
I2C 典型的 attach_adapter 如下所示:
static int my_attach(struct i2c_adapter *adapter)
{
return i2c_probe(adapter, &addr_data, zlg7290_detect);
}
static int zlg7290_detect (struct i2c_adapter *adapter, int address, int kind)
{
    ..........
}

3.2.2.1             IIC 设备探测
注意探测可能会找到多个设备,因而不仅一个 I2C 总线可以挂多个不同类型的设备,一个设备驱动也可以同时为挂在多个不同 I2C 总线上的设备服务。
每当设备驱动探测到了一个它能支持的设备,它就创建一个 struct i2c_client 来标识这个设备:
struct i2c_client *new_client;
……
    new_client->addr = address;
    new_client->adapter = adapter;
    new_client->driver = &zlg7290_driver;
    new_client->flags = 0;
    /* Fill in the remaining client fields */
    strlcpy(new_client->name, DEV_NAME, I2C_NAME_SIZE);
    /* Tell the I2Clayer a new client has arrived */
    if ((err = i2c_attach_client(new_client))){
        printk("i2cattach client error.\n");
        goto exit_kfree;
    }

可见,一个 i2c_client 代表着位于 adapter 总线上,地址为 address ,使用 zlg7290_driver来驱动的一个设备。它将总线驱动与设备驱动,以及设备地址绑定在了一起。一个 i2c_client 就代表着一个 I2C 设备。
当得到 I2C 设备后,就可以直接对此设备进行读写:
/*
  * The master routines are the ones normally used to transmit data to devices
  * on a bus (or read from them). Apart from two basic transfer functions to
  * transmit one message at a time, a more complex version can be used to
  * transmit an arbitrary number of messages without interruption.
  */
extern int i2c_master_send(struct i2c_client *,const char* ,int);
extern int i2c_master_recv(struct i2c_client *,char* ,int);
3.2.2.2            linux下IIC设备地址
addr_data是在 include/linux/i2c.h 中定义的或自己在自己驱动程序中定义的一个i2c_client_address_data结构:
static struct i2c_client_address_data addr_data = {            \
    .normal_i2c    = normal_i2c,                    \
    .probe        = probe,                    \
    .ignore        = ignore,                    \
    .forces        = forces,                    \
}

若自己不定义,则用 i2c.h 中的默认定义。
/* i2c_client_address_data is the struct for holding default client addresses for a driver and for the parameters supplied on the command line */
struct i2c_client_address_data {
    unsigned short *normal_i2c;
    unsigned short *probe;
    unsigned short *ignore;
    unsigned short **forces;
};

根据作者自行定义设备地址与否,有两种情形:
a.  采用默认定义,一般是不会 work, 毕竟大多数 i2c-core 中是不可能提前知道所接设备地址的,这样通过 i2c_probe() 探测肯定不可能找到,也不可能建立两者之间的联系;况且, i2c_probe() 属于 i2c-core 中的函数, i2c-core 中管理着所有注册过的设备和驱动列表, i2c_probe() 中也不能随意传入地址,否则容易导致系统混乱或有潜在的风险,所以 i2c-core 也不允许这么做!
b.  作者自行定义地址结构
典型例子如下:
若自行定义,则参考如下:
/* Addresses to scan */
static unsigned short normal_i2c[] = {I2C_KS0127_ADDON>>1,
                       I2C_KS0127_ONBOARD>>1, I2C_CLIENT_END};/// 实际设备的地址List
static unsigned short probe[2] =    {I2C_CLIENT_END, I2C_CLIENT_END};
static unsigned short ignore[2] =     {I2C_CLIENT_END, I2C_CLIENT_END};
static struct i2c_client_address_data addr_data = {
    normal_i2c,
    probe,
    ignore,
};

或者根本就不定义完整的 i2c_client_address_data 结构,只根据需要定义 normal_i2c[],probe[],ignore[] forces[][], 然后调用 i2c_probe(adapter,&addr_data,   &my_probe)  即可。
my_probe() 中把实际的地址赋于 i2c_client ,调用 i2c_set_clientdata() 设置 i2c_client->dev->drv_data, 并调用 i2c_attach_client(client) 向系统注册设备。
最后, i2c_probe() 中探测时的地址优先级:
forces[][], probe[], normal_i2c[]( 其中忽略 ignore[] 中的项 )
I2c 设备在实际使用中比较广泛, sensor,rtc,audio, codec,etc.  因设备复杂性不同, Linux 中有些驱动中对地址的定义不在同一文件,这时多数情况都在 arch 中对设备作为 platform_device 进行初始化并注册的代码中。

原文地址: linux下IIC驱动开发分析(4)  作者: putiancaijunyu
3.2.1  具体开发实例
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define DEV_NAME "zlg7290"
/* Addresses to scan */
static unsigned short normal_i2c[] = {0x38, I2C_CLIENT_END };
// Insmod parameters
I2C_CLIENT_INSMOD_1(zlg7290);
//I2C_CLIENT_INSMOD;
/* Each client has this additional data */
struct zlg7290_device {
       struct i2c_client    client;
       struct cdev            dev;
};
static int zlg7290_attach_adapter(struct i2c_adapter *adapter);
static int zlg7290_detect(struct i2c_adapter *adapter, int address, int kind);
static int zlg7290_detach_client(struct i2c_client *client);
static ssize_t zlg7290_key_read(struct file *filp, char *buff, size_t count, loff_t *offp);
static ssize_t zlg7290_led_write(struct file *filp, const char *buff, size_t count, loff_t *offp);
static int zlg7290_open(struct inode *inode, struct file *filp);
/* This is the driver that will be inserted */
static struct i2c_driver zlg7290_driver = {
       .owner           = THIS_MODULE,
       .name            = DEV_NAME,
//     .id          = I2C_DRIVERID_ZLG7290,
       .flags             = I2C_DF_NOTIFY,
       .attach_adapter     = zlg7290_attach_adapter,
       .detach_client       = zlg7290_detach_client,
};
static struct file_operations zlg7290_fops = {
       .owner    =    THIS_MODULE,
       .open    =    zlg7290_open,
//     .release =     led_release,
       .write      =     zlg7290_led_write,
       .read      =    zlg7290_key_read,
};
static int zlg7290_attach_adapter(struct i2c_adapter *adapter)
{
//     printk("In function %s\n", __FUNCTION__);
       return i2c_probe(adapter, &addr_data, zlg7290_detect);
}
/* This function is called by i2c_probe */
static int zlg7290_detect(struct i2c_adapter *adapter, int address, int kind)
{
       struct i2c_client *new_client;
       struct zlg7290_device *pdev;
       dev_t dev_no;
       int err = 0;
       /* There are three ways we can read the EEPROM data:
          (1) I2Cblock reads (faster, but unsupported by most adapters)
          (2) Consecutive byte reads (100% overhead)
          (3) Regular byte data reads (200% overhead)
          The third method is not implemented by this driver because all
          known adapters support at least the second. */
//     printk("In function %s\n", __FUNCTION__);
       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA
                                       | I2C_FUNC_SMBUS_BYTE)){
              printk("i2ccheck functionality error.\n");
              goto exit;
       }
       if (!(pdev = kmalloc(sizeof(struct zlg7290_device), GFP_KERNEL))) {
              printk("Kmalloc error.\n");
              err = -ENOMEM;
              goto exit;
       }
      
       memset(pdev, 0, sizeof(struct zlg7290_device));
      
      
       new_client = &pdev->client;
       i2c_set_clientdata(new_client, pdev);             
      
       new_client->addr = address;
       new_client->adapter = adapter;
       new_client->driver = &zlg7290_driver;
       new_client->flags = 0;
       /* Fill in the remaining client fields */
       strlcpy(new_client->name, DEV_NAME, I2C_NAME_SIZE);
       /* Tell the I2Clayer a new client has arrived */
       if ((err = i


http://blogimg.chinaunix.net/blog/upfile2/090807155927.rar

你可能感兴趣的:(初学linux内核,linux驱动,iic)