Chinaunix首页 | 论坛 | 博客
  • 博客访问: 268094
  • 博文数量: 60
  • 博客积分: 2010
  • 博客等级: 大尉
  • 技术积分: 820
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-18 00:28
文章分类

全部博文(60)

文章存档

2010年(60)

我的朋友

分类:

2010-04-14 18:03:36

/*

*By Neil Chiao ()

*转载请注明出处:neilengineer.cublog.cn

*欢迎到“新星湾()”指导

*/

 

 

I2C总线原理

I2C是一种常用的串行总线,由串行数据线SDA 和串线时钟线SCL组成。

       系统的I2C模块分为I2C总线控制器和I2C设备。I2C总线控制器是CPU提供的控制I2C总线接口,它控制I2C总线的协议、仲裁、时序。I2C设备是指通过I2C总线与CPU相连的设备,如EEPROM 使用I2C通信时必须指定主从设备。 一般来说,.I2C总线控制器被配置成主设备,与总线相连的I2C设备如AT24C02作为从设备。

IIC读写EEPROM原理

IIC总线的开始/停止信号如图1所示。开始信号为:时钟信号线SCL为高电平,数据线SDA从高变低。停止信号为:时钟信号线SCL为高电平,数据线SDA从低变高。

 

1 IIC Start-Stop Signal

1.1.1 IIC总线Byte Write

IIC总线写数据分几种格式,如字节写和页写。

字节写传送格式如图2所示。开始信号之后,总线开始发数据,第一个ByteIIC的设备地址,第二个Byte是设备内的地址(如EEPROM中具体的某个物理地址),然后就是要传送的真正的数据DATA

NOTEIIC总线在传送每个Byte后,都会从IIC总线上的接收设备得到一个ACK信号来确认接收到了数据。其中,第一个Byte的设备地址中,前7位是地址码,第8位是方向位(“0为发送,“1为接收)。IIC的中断信号有:ACKStartStop

 

2 IIC Byte Write

 

       Write功能的实际实现原理如图3所示:

(1)设置GPIO的相关引脚为IIC输出;

(2)设置IIC(打开ACK,打开IIC中断,设置CLK等);

(3)设备地址赋给IICDS ,并设置IICSTAT,启动IIC发送设备地址出去;从而找到相应的设备即IIC总线上的EEPROM

(4)第一个Byte的设备地址发送后,从EEPROM得到ACK信号,此信号触发中断;

(5)在中断处理函数中把第二个Byte(设备内地址)发送出去;发送之后,接收到ACK又触发中断;

(6)中断处理函数把第三个Byte(真正的数据)发送到EEPROM中;

(7)发送之后同样接收到ACK并触发中断,中断处理函数判断,发现数据传送完毕。

(8)IIC Stop信号,关IIC中断,置位各寄存器。

 

3 IIC Write Operation

NOTE:对于EEPROMIICDS寄存器发送的数据会先放在Ring buffer中,当其收到stop信号时,开始实际写入EEPROM中。在实际写的过程中,EEPROM不响应从CPU来的信号,直到写完才会响应,因而有一段延迟代码。在page write时,注意一定要有延时!

 

 

NOTE:数据先写到EEPROMring buffer中,收到Stop信号时,开始实际地把数据写入EEPROM,这时不响应任何输入。即这时Write函数中后面的延时中,向其发slvaddr时,不会得到ACK,直到数据写完时,才会收到ACK

 

1.1.2 IIC总线Random Read

IIC总线读数据为Current Address ReadRandom ReadSequential Read

IIC总线Random Read传送格式如图4所示。开始信号后,CPU开始写第一个Byte(IIC的设备地址),第二个Byte是设备内的地址(此地址保存在EEPROM)。然后,开始读过程:发送设备地址找到IIC设备,然后就开始读数据。类似写过程,CPU读一个byte的实际数据后,CPUIICEEPROMACKACK触发中断。读数据也在中断程序中进行。

 

 

4 IIC Random Read Operation

 

的寻址

Device Address格式如下:

1

0

1

0

A2

A1

A0

R/W

对于A0~A2EEPROM芯片上的引脚,按照引脚硬件上的连接方法,可以给此芯片指定具体的地址。EEPROM的寻址由device address的低3(A2A1A0)+word address(8)组成13位的寻址方式。

 

Linux中的I2C驱动结构

2C驱动概述

LinuxI2C驱动结构可分为3个部分:

  I2C核心

I2C 核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(即“algorithm”),与具体适配器无关的代码以及探测设备、检测设备地址等。i2c-core.c中的核心驱动程序可管理多个I2C总线适配器(控制器)和多个I2C从设备。每个I2C从设备驱动都能找到和它相连的I2C总线适配器。

  I2C总线驱动

I2C总线驱动主要包括I2C适配器结构i2c_adapterI2C适配器的algorithm数据结构。

通过I2C总线驱动的代码,可控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。

  I2C设备驱动

I2C设备驱动是对I2C设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。I2C设备驱动主要包括数据结构i2c_driveri2c_client

 

 

 

 

内核中对于I2C定义了4种结构:

1i2c_adapter—I2C总线适配器。 即为CPU中的I2C总线控制器。

2i2c_algorithm—I2C总线通信传输算法,管理I2C总线控制器,实现I2C总线上数据的发送和接收等操作。

3i2c_client—挂载在I2C总线上的I2C设备的驱动程序。

4i2c_driver—用于管理I2C的驱动程序,它对应I2C的设备节点。

4种结构的定义见include/linux/i2c.h文件。

对于i2c_driveri2c_clienti2c_driver对应一套驱动方法,是纯粹的用于辅助作用的数据结构,它不对应于任何的物理实体。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_client一般被包含在i2c字符设备的私有信息结构体中。 i2c_driver i2c_client发生关联的时刻在i2c_driverattach_adapter()函数被运行时。attach_adapter()会探测物理设备,当确定一个client存在时,把该client使用的i2c_client数据结构的adapter指针指向对应的i2c_adapter driver指针指向该i2c_driver,并会调用i2c_adapterclient_register()函数。相反的过程发生在 i2c_driver detach_client()函数被调用的时候。

对于i2c_adpater i2c_client,与I2C硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adpater。由于一个适配器上可以连接多个I2C设备,所以一个i2c_adpater也可以被多个i2c_client依附,i2c_adpater中包括依附于它的i2c_client的链表。

i2c.h文件中除定义上述4个重要结构之外,还定义了一个非常重要的结构体:i2c_msg其定义如下:

struct i2c_msg {

       __u16 addr;    /* slave address*/

       __u16 flags;

#define I2C_M_TEN            0x0010    /* this is a ten bit chip address */

#define I2C_M_RD              0x0001    /* read data, from slave to master */

#define I2C_M_NOSTART           0x4000    /* if I2C_FUNC_PROTOCOL_MANGLING */

#define I2C_M_REV_DIR_ADDR 0x2000    /* if I2C_FUNC_PROTOCOL_MANGLING */

#define I2C_M_IGNORE_NAK    0x1000    /* if I2C_FUNC_PROTOCOL_MANGLING */

#define I2C_M_NO_RD_ACK             0x0800    /* if I2C_FUNC_PROTOCOL_MANGLING */

#define I2C_M_RECV_LEN         0x0400    /* length will be first received byte */

       __u16 len;             /* msg length                       */

       __u8 *buf;             /* pointer to msg data                  */

};

它是实际传输的数据,其中包括了slave address,数据长度和实际的数据。

 

内核中的I2C驱动

Linux内核源码的drivers目录下有个i2c目录,其中包含如下文件和文件夹:

  i2c-core.c

这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。

  i2c-dev.c

实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访问设备时的主设备号都为89,次设备号为0255。应用程序通过 i2c-%d (i2c-0, i2c-1, ..., i2c-10, ...)文件名并使用文件操作接口open()write()read()ioctl()close()等来访问这个设备。

i2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read()write()ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上I2C设备的存储空间或寄存器并控制I2C设备的工作方式。

  chips文件夹

此目录中包含了一些特定的I2C设备驱动,如RTC实时钟芯片驱动和I2C接口的EEPROM驱动等。

  busses文件夹

此目录中包含了一些I2C总线的驱动,如S3C2410I2C控制器驱动为i2c-s3c2410.c

  algos文件夹

实现了一些I2C总线适配器的algorithm

 

i2c-core.c文件不需要修改,其主要实现的函数有:

1adapterclient相关操作

int i2c_add_adapter(struct i2c_adapter *adap); //增加adapter

int i2c_del_adapter(struct i2c_adapter *adap);

int i2c_register_driver(struct module *, struct i2c_driver *); //增加驱动 (i2c_add_driver)

int i2c_del_driver(struct i2c_driver *driver);

int i2c_attach_client(struct i2c_client *client); //增加client

int i2c_detach_client(struct i2c_client *client);

2I2C传输,发送和接收

int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);
int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
int i2c_master_recv(struct i2c_client *client, char *buf ,int count);

i2c_transfer函数用于进行I2C适配器和I2C设备之间的一组消息交互;i2c_master_send函数和i2c_master_recv函数调用i2c_transfer函数分别完成一条写消息和一条读消息。而i2c_transfer函数实现中使用这句话adap->algo->master_xfer(adap,msgs,num);来调用i2c_algorithm中注册的master_xfer函数。

 

i2c_algorithm如下定义:

struct i2c_algorithm {

       int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,

                        int num);

       int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,

                        unsigned short flags, char read_write,

                        u8 command, int size, union i2c_smbus_data *data);

       u32 (*functionality) (struct i2c_adapter *);

};

       根据定义主要要实现i2c_algorithmmaster_xfer()函数和functionality()函数。

2C驱动实例简析

下面以使用S3C2410 I2C控制器的at24系列E2PROM驱动为例,从设备驱动开始,向上层具体分析写E2PROM的整个调用过程。

at24系列设备驱动程序在文件drivers/i2c/chips/at24.c中:

static struct i2c_driver at24_driver = {

       .driver = {

              .name = "at24",

              .owner = THIS_MODULE,

       },

       .probe = at24_probe,     //在此函数中注册write, read等函数

       .remove = __devexit_p(at24_remove),

       .id_table = at24_ids,

};

 

static int __init at24_init(void)

{

       io_limit = rounddown_pow_of_two(io_limit);

       return i2c_add_driver(&at24_driver);

}

module_init(at24_init);          //at24设备驱动初始化

       at24_probe函数如下:

       static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)

{

       struct at24_platform_data chip;

。。。。。。

       mutex_init(&at24->lock);

       at24->use_smbus = use_smbus;

。。。。。。

       at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;

       at24->bin.read = at24_bin_read;           //指定read函数

。。。。。。

if (writable) {

              if (!use_smbus || i2c_check_functionality(client->adapter,

                            I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {

                     unsigned write_max = chip.page_size;

                     at24->bin.write = at24_bin_write; //指定write函数

                     at24->bin.attr.mode |= S_IWUSR;

                     if (write_max > io_limit)

                            write_max = io_limit;

                     if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX)

                            write_max = I2C_SMBUS_BLOCK_MAX;

                     at24->write_max = write_max;

。。。。。。

       }

。。。。。。

       at24->client[0] = client;

       for (i = 1; i < num_addresses; i++) {

              at24->client[i] = i2c_new_dummy(client->adapter,

                                   client->addr + i);

              if (!at24->client[i]) {

                     dev_err(&client->dev, "address 0x%02x unavailable\n",

                                   client->addr + i);

                     err = -EADDRINUSE;

                     goto err_clients;

              }

       }

 

       err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);

。。。。。。

       i2c_set_clientdata(client, at24);

。。。。。。

err_clients:

       for (i = 1; i < num_addresses; i++)

              if (at24->client[i])

                     i2c_unregister_device(at24->client[i]);

。。。。。。

}

at24_bin_write函数à调用at24_eeprom_write函数如下:

mutex_lock(&at24->lock);

       while (count) {

              ssize_t     status;

              status = at24_eeprom_write(at24, buf, off, count);

              if (status <= 0) {

                     if (retval == 0)

                            retval = status;

                     break;

              }

              buf += status;

              off += status;

              count -= status;

              retval += status;

       }

       mutex_unlock(&at24->lock);

 

       at24_eeprom_write函数如下:

static ssize_t at24_eeprom_write(struct at24_data *at24, char *buf,

              unsigned offset, size_t count)

{

       struct i2c_client *client;

       struct i2c_msg msg;

。。。。。。

 

       timeout = jiffies + msecs_to_jiffies(write_timeout);

       do {

              write_time = jiffies;

              if (at24->use_smbus) {

                     status = i2c_smbus_write_i2c_block_data(client,

                                   offset, count, buf);

                     if (status == 0)

                            status = count;

              } else {

                     status = i2c_transfer(client->adapter, &msg, 1);

                     if (status == 1)

                            status = count;

              }

。。。。。。

              /* REVISIT: at HZ=100, this is sloooow */

              msleep(1);

       } while (time_before(write_time, timeout));

。。。。。。

}

 

i2c_transfer函数是I2C核心封装的一个函数,其实现见i2c-core.c文件:

int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)

{

。。。。。。

       if (adap->algo->master_xfer) {

。。。。。。

              if (in_atomic() || irqs_disabled()) {

                     ret = mutex_trylock(&adap->bus_lock);

                     if (!ret)

                            return -EAGAIN;

              } else {

                     mutex_lock_nested(&adap->bus_lock, adap->level);

              }

              ret = adap->algo->master_xfer(adap,msgs,num);

              mutex_unlock(&adap->bus_lock);

。。。。。。

}

 

       到这里要涉及到具体CPUI2C控制器驱动了,对于s3c2410其赋值和实现见drivers/i2c/busses/i2c-s3c2410.c文件:

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {

       .master_xfer          = s3c24xx_i2c_xfer,

       .functionality          = s3c24xx_i2c_func,

};

       s3c24xx_i2c_xfer函数实现:

static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,

                     struct i2c_msg *msgs, int num)

{

。。。。。。

       for (retry = 0; retry < adap->retries; retry++) {

              ret = s3c24xx_i2c_doxfer(i2c, msgs, num);

              if (ret != -EAGAIN)

                     return ret;

。。。。。。

}

       s3c24xx_i2c_xfer函数调用了s3c24xx_i2c_doxfer函数:

static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c, struct i2c_msg *msgs, int num)

{

。。。。。

       spin_lock_irq(&i2c->lock);

       i2c->msg     = msgs;

       i2c->msg_num = num;

       i2c->msg_ptr = 0;

       i2c->msg_idx = 0;

       i2c->state   = STATE_START;

       s3c24xx_i2c_enable_irq(i2c);

       s3c24xx_i2c_message_start(i2c, msgs);

       spin_unlock_irq(&i2c->lock);

。。。。。

}

 

       s3c24xx_i2c_doxfer函数又调用s3c24xx_i2c_message_start函数来开始传输数据。

static void s3c24xx_i2c_message_start (struct s3c24xx_i2c *i2c,

                                  struct i2c_msg *msg)

{

。。。。。。

       writeb(addr, i2c->regs + S3C2410_IICDS);

       //write slave addr to IICDS

       ndelay(i2c->tx_setup);

       writel(iiccon, i2c->regs + S3C2410_IICCON);

       //write to IICCONstart

       stat |=  S3C2410_IICSTAT_START;

       writel(stat, i2c->regs + S3C2410_IICSTAT);

}

       上述s3c24xx_i2c_message_start函数发出slave addr和开始信号,但实际传输数据在中断处理函数s3c24xx_i2c_irq 调用的函数i2s_s3c_irq_nextbyte中实现。

       s3c24xx_i2c_message_start函数发送start信号,收到从设备的ACK时发生中断,触发这里的中断处理函数。i2s_s3c_irq_nextbyte函数分析暂略。

 

 

概括

       对于I2C总线的E2PROM写函数调用如下:

E2PROM驱动drivers/i2c/chips/at24.ce2prom_writeeeprom_i2c_write函数)à

I2C core驱动的corei2c-core.ci2c_transfer函数)à

drivers/i2c/busses/i2c-s3c2410.cs3c24xx_i2c_xfer函数)à

drivers/i2c/busses/i2c-s3c2410.cs3c24xx_i2c_doxfer函数)à

drivers/i2c/busses/i2c-s3c2410.cs3c24xx_i2c_message_start函数)à

实际发送数据在drivers/i2c/busses/i2c-s3c2410.c中实现(i2s_s3c_irq_nextbyte函数)。

阅读(1934) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~