Linux设备驱动简析—基于I2C的E2PROM驱动

 /*
*By Neil Chiao (
neilchiao at gmail.com
)
*转载请注明出处:
neilengineer.cublog.cn

*/


1、I2C总线原理
I2C是一种常用的串行总线,由串行数据线SDA 和串线时钟线SCL组成。
       系统的I2C模块分为I2C总线控制器和I2C设备。I2C总线控制器是CPU提供的控制I2C总线接口,它控制I2C总线的协议、仲裁、时序。I2C设备是指通过I2C总线与CPU相连的设备,如EEPROM。 使用I2C通信时必须指定主从设备。 一般来说,.I2C总线控制器被配置成主设备,与总线相连的I2C设备如AT24C02作为从设备。
1.1、IIC读写EEPROM原理
IIC总线的开始/停止信号如图1所示。开始信号为:时钟信号线SCL为高电平,数据线SDA从高变低。停止信号为:时钟信号线SCL为高电平,数据线SDA从低变高。



图1 IIC Start-Stop Signal
1.1.1 IIC总线Byte Write
IIC总线写数据分几种格式,如字节写和页写。
字节写传送格式如图2所示。开始信号之后,总线开始发数据,第一个Byte是IIC的设备地址,第二个Byte是设备内的地址(如EEPROM中具体的某个物理地址),然后就是要传送的真正的数据DATA。
NOTE:IIC总线在传送每个Byte后,都会从IIC总线上的接收设备得到一个ACK信号来确认接收到了数据。其中,第一个Byte的设备地址中,前7位是地址码,第8位是方向位(“0”为发送,“1”为接收)。IIC的中断信号有:ACK,Start,Stop。



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


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

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




图4 IIC Random Read Operation

1.2 EEPROM的寻址
Device Address格式如下:
1
0
1
0
A2
A1
A0
R/W
对于A0~A2是EEPROM芯片上的引脚,按照引脚硬件上的连接方法,可以给此芯片指定具体的地址。EEPROM的寻址由device address的低3位(A2A1A0)+word address(8位)组成13位的寻址方式。

2、Linux中的I2C驱动结构
2.1 I2C驱动概述
Linux的I2C驱动结构可分为3个部分:
•  I2C核心
I2C 核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(即“algorithm”),与具体适配器无关的代码以及探测设备、检测设备地址等。i2c-core.c中的核心驱动程序可管理多个I2C总线适配器(控制器)和多个I2C从设备。每个I2C从设备驱动都能找到和它相连的I2C总线适配器。
•  I2C总线驱动
I2C总线驱动主要包括I2C适配器结构i2c_adapter和I2C适配器的algorithm数据结构。
通过I2C总线驱动的代码,可控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。
•  I2C设备驱动
I2C设备驱动是对I2C设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。I2C设备驱动主要包括数据结构i2c_driver和i2c_client。





内核中对于I2C定义了4种结构:
1)i2c_adapter—I2C总线适配器。 即为CPU中的I2C总线控制器。
2)i2c_algorithm—I2C总线通信传输算法,管理I2C总线控制器,实现I2C总线上数据的发送和接收等操作。
3)i2c_client—挂载在I2C总线上的I2C设备的驱动程序。
4)i2c_driver—用于管理I2C的驱动程序,它对应I2C的设备节点。
这4种结构的定义见include/linux/i2c.h文件
对于i2c_driver和i2c_client,i2c_driver对应一套驱动方法,是纯粹的用于辅助作用的数据结构,它不对应于任何的物理实体。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_client一般被包含在i2c字符设备的私有信息结构体中。 i2c_driver 与i2c_client发生关联的时刻在i2c_driver的attach_adapter()函数被运行时。attach_adapter()会探测物理设备,当确定一个client存在时,把该client使用的i2c_client数据结构的adapter指针指向对应的i2c_adapter, driver指针指向该i2c_driver,并会调用i2c_adapter的client_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,数据长度和实际的数据。

2.2内核中的I2C驱动
Linux内核源码的drivers目录下有个i2c目录,其中包含如下文件和文件夹:
•  i2c-core.c
这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。
•  i2c-dev.c
实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访问设备时的主设备号都为89,次设备号为0~255。应用程序通过 “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总线的驱动,如S3C2410的I2C控制器驱动为i2c-s3c2410.c。
•  algos文件夹
实现了一些I2C总线适配器的algorithm。

i2c-core.c文件不需要修改,其主要实现的函数有:
1)adapter和client相关操作
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);
2)I2C传输,发送和接收
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_algorithm的master_xfer()函数和functionality()函数。
2.3 I2C驱动实例简析
下面以使用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
              at24->client = i2c_new_dummy(client->adapter,
                                   client->addr + i);
              if (!at24->client) {
                     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
              if (at24->client)
                     i2c_unregister_device(at24->client);
。。。。。。
}
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
                     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);
。。。。。。
}

       到这里要涉及到具体CPU的I2C控制器驱动了,对于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 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 IICCON,start
       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.c(e2prom_write,eeprom_i2c_write函数)à
I2C core驱动的core层i2c-core.c(i2c_transfer函数)à
drivers/i2c/busses/i2c-s3c2410.c(s3c24xx_i2c_xfer函数)à
drivers/i2c/busses/i2c-s3c2410.c(s3c24xx_i2c_doxfer函数)à
drivers/i2c/busses/i2c-s3c2410.c(s3c24xx_i2c_message_start函数)à
实际发送数据在drivers/i2c/busses/i2c-s3c2410.c中实现(i2s_s3c_irq_nextbyte函数)。


本文来自ChinaUnix博客,如果查看原文请点:
http://blog.chinaunix.net/u3/91522/showart_1886369.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值