/*
*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
*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