本人学生一枚,之前没有详细的接触过linux驱动,只是读过宋宝华的《Linux设备驱动开发详解》,这段时间想静下心来学习下linux i2c驱动,在网上找了很多资料,前辈们写的文章让我受益匪浅,但是一开始上手真的很痛苦,基本上大家都是从linux i2c体系结构的三大组成谈起:i2c核心,i2c总线驱动,i2c设备驱动,好抽象。所以我才想写这个文章,从一个新人的角度分享下我学习linux i2c驱动的心得,写的不对的地方欢迎大家批评指正。
因为对Linux设备模型还不是很熟悉,所以我按照如何去实现一个i2c传输来讲述,对于平台总线、设备与总线如何去匹配等暂时忽略。
当然很多东西都是我从网上搜刮而来的,也请大家原谅。我会把一些有用的博文链接放在后面,希望对大家有用。
I2C总线是由Philips公司开发的两线式串行总线,这两根线为时钟线(SCL)和双向数据线(SDA)。由于I2C总线仅需要两根线,因此在电路板上占用的空间更少,带来的问题是带宽较窄。I2C在标准模式下传输速率最高100Kb/s,在快速模式下最高可达400kb/s。属于半双工。
在嵌入式系统中,I2C应用非常广泛,大多数微控制器中集成了I2C总线,一般用于和RTC,EEPROM,智能电池电路,传感器,LCD以及其他类似设备之间的通信。
开发板:飞凌OK210
CPU型号:Samsung S5PV210
EEPROM型号:AT24C01A
linux版本:Linux 2.6.35.7
I2C总线驱动:drivers/i2c/busses/i2c-s3c2410.c
eeprom驱动:drivers/misc/eeprom/at24.c
本节分析下I2C总线协议,因为我的开发板是三星s5pv210芯片,所以就以此为例。
I2C总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
当SCL是高电平时,SDA线由高电平向低电平切换,表示开始;当SCL是高电平时,SDA线由低电平向高电平切换,表示停止。如下图所示。
发送到SDA线上的每个字节必须为8位,每次传输可以发送的字节数不受限制,但是每个字节后面必须跟一个响应位。
数据传输必须带响应,响应时钟脉冲由主机产生,在SCL的第9个时钟脉冲上,前8个时钟脉冲用来传输8位即1byte的数据。
当发送端收到响应时钟脉冲的时候就会拉高SDA从而释放SDA线,而接收端通过拉低SDA先来表示收到数据,即SDA在响应期间保持低电平。
当两个主机在总线上产生竞争时就需要仲裁。
SDA线低电平的优先级高于高电平。当一个主机首先产生低电平,而紧接着另一个主机产生高电平,但是由于低电平的优先级高于高电平,所以总线成低电平,也就是发低电平的主机占有总线而发高电平的主机不占有总线。如果两个主机都是发送低电平,那么继续比较下一个时钟周期的电平来决定谁占有总线,以此类推。
本节介绍eeprom的读写时序,参考的是AT24C01A的datasheet。
AT24C01A的存储大小是1K,页大小是8个字节。
7位地址,前四位是1010,后三位由芯片引脚决定,由原理图可知后三位是000,也就是设备地址为0x50,因为数据传输是8位的,最后一位决定是读还是写。
读任意地址eeprom的数据,首先第一个字节得先在SDA上发出eeprom的设备地址,也就是0x50,并且8位数据的最后一位是低电平表示写设备,然后第二个字节是要读的数据在eeprom内的地址,这样以后再产生开始条件,第三个字节在SDA上发出设备地址,此时的最后一位是高电平,表示读设备,第四个字节的数据就是读eeprom的对应地址的数据。
可以看到,读eeprom需要两个开始条件,也就是2条消息,第一条消息写eeprom确定读的位置,大小为2个字节,第二条消息才是真正的读eeprom。
写eeprom就相对简单,只需一个开始条件,第一个字节发出设备地址和置最低位为低电平表示写eeprom,第二个字节发出要读数据在eerpom的地址,第三个字节读到的数据就对应地址在eeprom上的数据
本小节介绍两个在linux应用层访问eeprom的方法,并给出示例代码方便大家理解。第一个方法是通过sysfs文件系统对eeprom进行访问,第二个方法是通过eeprom的设备文件进行访问。这两个方法分别对应了i2c设备驱动的两个不同的实现,在后面的小结会详细的分析。
eeprom的设备驱动在/sys/bus/i2c/devices/0-0050/目录下把eeprom设备映射为一个二进制节点,文件名为eeprom。对这个eeprom文件的读写就是对eeprom进行读写。
我们可以先用cat命令来看下eeprom的内容。
[root@FORLINX210]# cat eeprom
�����������X�����������������������������������������������
发现里面都是乱码,然后用echo命令把字符串“test”输入给eeprom文件,然后再cat出来。
就会发现字符串test已经存在eeprom里面了,我们知道sysfs文件系统断电后就没了,也无法对数据进行保存,为了验证确实把“test”字符串存储在了eeprom,可以把系统断电重启,然后cat eeprom,会发现test还是存在的,证明确实对eeprom进行了写入操作。
当然,因为eeprom已经映射为一个文件了,我们还可以通过文件I/O写应用程序对其进行简单的访问测试。比如以下程序对特定地址(0x40)写入特定数据(Hi,this is an eepromtest!),然后再把写入的数据在此地址上读出来。
#include
#include
#include
#include
#include
#include
int main(void){
int fd, size, len, i;
char buf[50]= {0};
char *bufw="Hi,this is an eepromtest!";//要写入的数据
len=strlen(bufw);//数据长度
fd= open("/sys/bus/i2c/devices/0-0050/eeprom",O_RDWR);//打开文件
if(fd< 0)
{
printf("####i2c test device open failed####/n");
return(-1);
}
//写操作
lseek(fd,0x40,SEEK_SET); //定位地址,地址是0x40
if((size=write(fd,bufw, len))<0)//写入数据
{
printf("write error\n");
return 1;
}
printf("writeok\n");
//读操作
lseek(fd,0x40, SEEK_SET);//准备读,首先定位地址,因为前面写入的时候更新了当前文件偏移量,所以这边需要重新定位到0x40.
if((size=read(fd,buf,len))<0)//读数据
{
printf("readerror\n");
return 1;
}
printf("readok\n");
for(i=0; i< len; i++)
printf("buff[%d]=%x\n",i, buf[i]);//打印数据
close(fd);
return 0;
}
linux的i2c驱动会针对每个i2c适配器在/dev/目录下生成一个主设备号为89的设备文件,简单的来说,对于本例的eeprom驱动,/dev/i2c/0就是它的设备文件,因此接下来的eeprom的访问就变为了对此设备文件的访问。
我们需要用到两个结构体i2cmsg和i2crdwrioctldata。
struct i2c_msg { //i2c消息结构体,每个i2c消息对应一个结构体
__u16 addr; /* 从设备地址,此处就是eeprom地址,即0x50 */
__u16 flags; /* 一些标志,比如i2c读等*/
__u16 len; /* i2c消息的长度 */
__u8 *buf; /* 指向i2c消息中的数据 */
};
struct i2c_rdwr_ioctl_data {
struct i2c_msg __user *msgs; /* 指向一个i2c消息 */
__u32 nmsgs; /* i2c消息的数量 */
};
对一个eeprom上的特定地址(0x10)写入特定数据(0x58)并在从此地址读出写入数据的示例程序如下所示。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int fd,ret;
struct i2c_rdwr_ioctl_data e2prom_data;
fd=open("/dev/i2c/0",O_RDWR);//打开eeprom设备文件结点
if(fd<0)
{
perror("open error");
}
e2prom_data.nmsgs=2;
e2prom_data.msgs=(struct i2c_msg*)malloc(e2prom_data.nmsgs*sizeof(struct i2c_msg));//分配空间
if(!e2prom_data.msgs)
{
perror("malloc error");
exit(1);
}
ioctl(fd,I2C_TIMEOUT,1);/*超时时间*/
ioctl(fd,I2C_RETRIES,2);/*重复次数*/
/*写eeprom*/
e2prom_data.nmsgs=1;//由前面eeprom读写分析可知,写eeprom需要一条消息
(e2prom_data.msgs[0]).len=2; //此消息的长度为2个字节,第一个字节是要写入数据的地址,第二个字节是要写入的数据
(e2prom_data.msgs[0]).addr=0x50;//e2prom 设备地址
(e2prom_data.msgs[0]).flags=0; //写
(e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);
(e2prom_data.msgs[0]).buf[0]=0x10;// e2prom 写入目标的地址
(e2prom_data.msgs[0]).buf[1]=0x58;//写入的数据
ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);//通过ioctl进行实际写入操作,后面会详细分析
if(ret<0)
{
perror("ioctl error1");
}
sleep(1);
/*读eeprom*/
e2prom_data.nmsgs=2;//读eeprom需要两条消息
(e2prom_data.msgs[0]).len=1; //第一条消息实际是写eeprom,需要告诉eeprom需要读数据的地址,因此长度为1个字节
(e2prom_data.msgs[0]).addr=0x50; // e2prom 设备地址
(e2prom_data.msgs[0]).flags=0;//先是写
(e2prom_data.msgs[0]).buf[0]=0x10;//e2prom上需要读的数据的地址
(e2prom_data.msgs[1]).len=1;//第二条消息才是读eeprom,
(e2prom_data.msgs[1]).addr=0x50;// e2prom 设备地址
(e2prom_data.msgs[1]).flags=I2C_M_RD;//然后是读
(e2prom_data.msgs[1]).buf=(unsigned char*)malloc(1);//存放返回值的地址。
(e2prom_data.msgs[1]).buf[0]=0;//初始化读缓冲,读到的数据放到此缓冲区
ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);//通过ioctl进行实际的读操作
if(ret<0)
{
perror("ioctl error2");
}
printf("buff[0]=%x\n",(e2prom_data.msgs[1]).buf[0]);
/***打印读出的值,没错的话,就应该是前面写的0x58了***/
close(fd);
return 0;
}
本小节介绍了两种在linux应用层访问eeprom的方法,并且给出了示例程序,通过sysfs文件系统访问eeprom操作简单,无需了解eeprom的硬件特性以及访问时序,而通过devfs访问eeprom的方法则需要了解eeprom的读写时序。
后面分析后会发现,第一种通过sysfs文件系统的二进制结点访问eeprom的方法是由eeprom的设备驱动实现的,是一种专有的方法;而第二种通过devfs访问eeprom的方法是linux i2c提供的一种通用的方法,访问设备的能力有限。
前面几个小结介绍了i2c总线的协议,又介绍了我们关注的eeprom的读写访问时序,还给出了两个访问eeprom的例子,我的目的是为了能更好的理解后面解析Linux下i2c驱动。
网上介绍Linux I2C驱动架构的文章非常的多,我把这些内容做了个归纳与简化,但是在搬出这些非常抽象的内容之前,我想先谈下我的理解。
如下图
图中画了一个三星的s5pv210处理器,在处理器的里面集成了一个I2C适配器,外面有一个eeprom,通过SDA、SCL连接到cpu内集成的i2c适配器上。这样cpu就可以控制i2c适配器与外部的eeprom进行交互,也就是i2c适配器产生符合i2c协议的信号与eeprom进行通信。
所以对应到linux驱动下,控制i2c适配器有一套驱动代码,叫做i2c总线驱动,是用来产生i2c时序信号的,可以发送和接受数据;控制eeprom有一套驱动代码,叫做i2c设备驱动,这套驱动代码才是真正的对硬件eeprom控制。这也符合linux设备驱动分层的思想。而两套驱动代码之间有一个i2c核心,用来起到承上启下的作用。
以一个写eeprom为例,应用层发出写eeprom消息,i2c设备驱动接到消息,把消息封装成一个前文提到的i2c消息结构体,然后经i2c核心的调度把消息传给i2c适配器,i2c适配器就根据当前cpu的i2c总线协议把消息通过SDA和SCL发给了eeprom。
接下来开始搬运,,
linux的i2c体系结构分为三个组成部分。放张图加深理解。
(1)i2c核心
提供了I2C总线驱动的注册、注销方法 提供了I2C设备驱动的注册、注销方法 提供了I2C通信方法(algorithm) 对应代码:drivers/i2c/i2c-core.c
(2)i2c总线驱动
I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至集成在CPU内部(大多数微控制器都这么做)。适配器就是我们经常所说的控制器。
经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位,停止位,读写周期,以及以从设备方式被读写,产生ACK等。
I2C总线驱动由i2cadapter和i2calgorithm来描述 对应代码:drivers/i2c/busses/i2c-s3c2410.c
(3)i2c设备驱动
I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在收CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。
I2C设备驱动程序由i2c_driver来描述
对应代码:drivers/misc/eeprom/at24.c
在include/linux/i2c.h中定义四个I2C驱动中重要的数据结构:i2cadapter,i2calgorithm,i2cdriver,i2cclient.
i2c_adapter对应物理上的一个i2c适配器
struct i2c_adapter {
struct module *owner;//所属模块
unsigned int id;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* 总线通讯方法指针,需要其产生特定的访问周期信号 */
void *algo_data;
/* data fields that are valid for all devices */
struct rt_mutex bus_lock;
int timeout; /* in jiffies */
int retries;/* 重复次数 */
struct device dev; /* the adapter device */
int nr;
char name[48];
struct completion dev_released;
struct list_head userspace_clients;
};
i2c_algorithm对应一套通讯方法
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);//产生i2c访问周期说需要的信号
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);//返回说支持的通讯协议
};
i2c_driver对应一套驱动方法
struct i2c_driver {
unsigned int class;
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
void (*alert)(struct i2c_client *, unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;//该驱动所支持的i2c设备的ID表
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
i2cclient对应真实的物理设备,每个i2c设备都需要一个i2cclient来描述
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct i2c_driver *driver; /* and our access routines */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};
(1)i2cadapter与i2calgorithm 一个I2C适配器需要i2calgorithm中提供的通信函数来控制适配器上产生特定的访问周期。i2calgorithm中的关键函数masterxfer()用于产生I2C访问周期需要的信号,以i2cmsg为单位。
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
(2)i2cadapter与i2cclient i2cdriver与i2cclient是一对多的关系,一个i2cdriver上可以支持多个同等类型的i2cclient。
(3)i2cadapter与i2cclient i2cadapter与i2cclient的关系与I2C硬件体系中适配器和从设备的关系一致,i2cclient依附在i2cadapter上。
本节主要分析eeprom的所属的i2c设备驱动,此驱动主要实现了能够通过sysfs文件系统访问eeprom。
因为原开发板的eeprom驱动还没调试好,板级资源还没写好,所以需要自己加进去。 修改arch/arm/mach-s5pv210/mach-smdkc110.c文件。
static struct at24_platform_data at24c01 = {
.byte_len = SZ_8K / 8,/*eeprom大小*/
.page_size = 8,/*页大小*/
};
/* I2C0 */
static struct i2c_board_info i2c_devs0[] __initdata = {
{
I2C_BOARD_INFO("24c01",0x50),//0x50是eeprom的设备地址
.platform_data=&at24c01,
},
}
这样以后后面调用smdkc110machineinit就会把资源注册进去。
static void __init smdkc110_machine_init(void)
{
….
i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
….
}
前面讲i2c驱动架构的时候,说到I2C设备驱动主要由i2c_driver来描述。
在drivers/misc/eeprom/at24.c中可以看到eeprom驱动对i2c_driver结构的实例化。
static struct i2c_driver at24_driver = {
.driver = {
.name = "at24",
.owner = THIS_MODULE,
},
.probe = at24_probe,
.remove = __devexit_p(at24_remove),
.id_table = at24_ids,
};
其中probe和remove会在模块初始化和卸载的时候被调用。
static int __init at24_init(void)//模块初始化
{
io_limit = rounddown_pow_of_two(io_limit);//io_limit是写eeprom时允许一次写入的最大字节,默认128Byte,是驱动模块参数。
return i2c_add_driver(&at24_driver);//添加i2c_driver,在i2c核心中实现,会调用at24_probe.
}
module_init(at24_init);
static void __exit at24_exit(void)//模块卸载
{
i2c_del_driver(&at24_driver);//删除i2c_driver,会调用at24_remove
}
module_exit(at24_exit);
static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct at24_platform_data chip;
bool writable;
int use_smbus = 0;
struct at24_data *at24;
int err;
unsigned i, num_addresses;
kernel_ulong_t magic;
//获取板级设备信息
if (client->dev.platform_data) {
chip = *(struct at24_platform_data *)client->dev.platform_data;
} else {
if (!id->driver_data) {
err = -ENODEV;
goto err_out;
}
magic = id->driver_data;
chip.byte_len = BIT(magic & AT24_BITMASK(AT24_SIZE_BYTELEN));
magic >>= AT24_SIZE_BYTELEN;
chip.flags = magic & AT24_BITMASK(AT24_SIZE_FLAGS);
/*
* This is slow, but we can't know all eeproms, so we better
* play safe. Specifying custom eeprom-types via platform_data
* is recommended anyhow.
*/
chip.page_size = 1;
chip.setup = NULL;
chip.context = NULL;
}
//检查参数,必须为2的幂
if (!is_power_of_2(chip.byte_len))
dev_warn(&client->dev,
"byte_len looks suspicious (no power of 2)!\n");
if (!is_power_of_2(chip.page_size))
dev_warn(&client->dev,
"page_size looks suspicious (no power of 2)!\n");
/* Use I2C operations unless we're stuck with SMBus extensions. */
//检查是否支持I2C协议,如果不支持则检查是否支持SMBUS
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
if (chip.flags & AT24_FLAG_ADDR16) {
err = -EPFNOSUPPORT;
goto err_out;
}
if (i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
use_smbus = I2C_SMBUS_I2C_BLOCK_DATA;
} else if (i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_WORD_DATA)) {
use_smbus = I2C_SMBUS_WORD_DATA;
} else if (i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
use_smbus = I2C_SMBUS_BYTE_DATA;
} else {
err = -EPFNOSUPPORT;
goto err_out;
}
}
if (chip.flags & AT24_FLAG_TAKE8ADDR)//检查时候使用8个地址
num_addresses = 8;
else
num_addresses = DIV_ROUND_UP(chip.byte_len,//AT24C01使用一个地址
(chip.flags & AT24_FLAG_ADDR16) ? 65536 : 256);
at24 = kzalloc(sizeof(struct at24_data) +
num_addresses * sizeof(struct i2c_client *), GFP_KERNEL);//为at24_data分配内存,同时根据地址个数分配i2c_client
if (!at24) {
err = -ENOMEM;
goto err_out;
}
mutex_init(&at24->lock);
//初始化at24_data,也就是填充此结构体
at24->use_smbus = use_smbus;
at24->chip = chip;
at24->num_addresses = num_addresses;
/*
* Export the EEPROM bytes through sysfs, since that's convenient.
* By default, only root should see the data (maybe passwords etc)
*/
//以二进制结点的形式呈现eeprom的数据
sysfs_bin_attr_init(&at24->bin);
at24->bin.attr.name = "eeprom";//结点名字
at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;
at24->bin.read = at24_bin_read;//绑定读函数
at24->bin.size = chip.byte_len;
at24->macc.read = at24_macc_read;
//判断是否可写
writable = !(chip.flags & AT24_FLAG_READONLY);
if (writable) {//如果可写
if (!use_smbus || i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {
unsigned write_max = chip.page_size;
at24->macc.write = at24_macc_write;
at24->bin.write = at24_bin_write;//绑定写函数
at24->bin.attr.mode |= S_IWUSR;//文件拥有者可写
if (write_max > io_limit)//一次最多写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;
/* buffer (data + address at the beginning) */
at24->writebuf = kmalloc(write_max + 2, GFP_KERNEL);//分配缓冲区,多余两个字节用于保存寄存器地址
if (!at24->writebuf) {
err = -ENOMEM;
goto err_struct;
}
} else {
dev_warn(&client->dev,
"cannot write due to controller restrictions.");
}
}
at24->client[0] = client;
/* use dummy devices for multiple-address chips */
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;
}
}
//向sysfs文件系统注册二进制结点
err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);
if (err)
goto err_clients;
//保存驱动数据
i2c_set_clientdata(client, at24);
dev_info(&client->dev, "%zu byte %s EEPROM %s\n",
at24->bin.size, client->name,
writable ? "(writable)" : "(read-only)");
if (use_smbus == I2C_SMBUS_WORD_DATA ||
use_smbus == I2C_SMBUS_BYTE_DATA) {
dev_notice(&client->dev, "Falling back to %s reads, "
"performance will suffer\n", use_smbus ==
I2C_SMBUS_WORD_DATA ? "word" : "byte");
}
dev_dbg(&client->dev,
"page_size %d, num_addresses %d, write_max %d, use_smbus %d\n",
chip.page_size, num_addresses,
at24->write_max, use_smbus);
/* export data to kernel code */
if (chip.setup)
chip.setup(&at24->macc, chip.context);
return 0;
err_clients:
for (i = 1; i < num_addresses; i++)
if (at24->client[i])
i2c_unregister_device(at24->client[i]);
kfree(at24->writebuf);
err_struct:
kfree(at24);
err_out:
dev_dbg(&client->dev, "probe error %d\n", err);
return err;
}
at24probe()函数主要的工作是在sys目录在创建bin结点文件,也就是前面通过sysfs文件系统访问i2c设备中提到的/sys/bus/i2c/devices/0-0050/eeprom文件,用户可以用此文件来操作eeprom,提供读/写操作方法,在probe里面读写操作已经与二进制结点绑定,读操作函数是at24binread(),写操作函数是at24bin_write()。
其中有个重要的结构体:
struct at24_data {
struct at24_platform_data chip;
struct memory_accessor macc;
int use_smbus;
/*
* Lock protects against activities from other Linux tasks,
* but not from changes by other I2C masters.
*/
struct mutex lock;
struct bin_attribute bin;//二进制结点
u8 *writebuf;//写缓冲区
unsigned write_max;
unsigned num_addresses;
/*
* Some chips tie up multiple I2C addresses; dummy devices reserve
* them for us, and we'll use them with SMBus calls.
*/
struct i2c_client *client[];
};
at24_data是此驱动的一些私有数据的封装,包括二进制结点,以及写缓冲区。
static int __devexit at24_remove(struct i2c_client *client)
{
struct at24_data *at24;
int i;
at24 = i2c_get_clientdata(client);
sysfs_remove_bin_file(&client->dev.kobj, &at24->bin);
for (i = 1; i < at24->num_addresses; i++)
i2c_unregister_device(at24->client[i]);
kfree(at24->writebuf);
kfree(at24);
return 0;
}
at24remove()基本就是at24probe()的反操作。
static ssize_t at24_bin_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct at24_data *at24;
//通过kobj获得device,再获取driver_data
at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
return at24_read(at24, buf, off, count);//调用at24_read()
}
at24binread()通过devgetdrvdata()获取at24data结构体数据。然后调用at24read()。
static ssize_t at24_read(struct at24_data *at24,
char *buf, loff_t off, size_t count)
{
ssize_t retval = 0;
if (unlikely(!count))
return count;
/*
* Read data from chip, protecting against concurrent updates
* from this host, but not from other I2C masters.
*/
mutex_lock(&at24->lock);//访问设备前加锁
while (count) {
ssize_t status;
status = at24_eeprom_read(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);//访问结束后解锁
return retval;
}
at24read()传入的参数,at24是驱动私有数据结构体at24data,buf是读eeprom后读到的数据存储的缓冲区,off是数据的偏移地址,count是要读数据的大小。at24read()主要调用at24eeprom_read()去读,但是此函数读eeprom能读到的数据个数有限制,不一定一次就把count个数据都读到,所以用while来读,并且读到status个数据后更新count,表示还剩多少个数据没读到,同时也要更新数据偏移off,和读入缓冲buf。
static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,
unsigned offset, size_t count)
{
struct i2c_msg msg[2];
u8 msgbuf[2];
struct i2c_client *client;
unsigned long timeout, read_time;
int status, i;
memset(msg, 0, sizeof(msg));
/*
* REVISIT some multi-address chips don't rollover page reads to
* the next slave address, so we may need to truncate the count.
* Those chips might need another quirk flag.
*
* If the real hardware used four adjacent 24c02 chips and that
* were misconfigured as one 24c08, that would be a similar effect:
* one "eeprom" file not four, but larger reads would fail when
* they crossed certain pages.
*/
/*
* Slave address and byte offset derive from the offset. Always
* set the byte address; on a multi-master board, another master
* may have changed the chip's "current" address pointer.
*/
client = at24_translate_offset(at24, &offset);//获得client
if (count > io_limit)
count = io_limit;
switch (at24->use_smbus) {//如果使用SMBUS
case I2C_SMBUS_I2C_BLOCK_DATA:
/* Smaller eeproms can work given some SMBus extension calls */
if (count > I2C_SMBUS_BLOCK_MAX)
count = I2C_SMBUS_BLOCK_MAX;
break;
case I2C_SMBUS_WORD_DATA:
count = 2;
break;
case I2C_SMBUS_BYTE_DATA:
count = 1;
break;
default://使用I2C协议
/*
* When we have a better choice than SMBus calls, use a
* combined I2C message. Write address; then read up to
* io_limit data bytes. Note that read page rollover helps us
* here (unlike writes). msgbuf is u8 and will cast to our
* needs.
*/
i = 0;
if (at24->chip.flags & AT24_FLAG_ADDR16)
msgbuf[i++] = offset >> 8;
msgbuf[i++] = offset;
//由前小节读eeprom的时序可知,需要2条消息,第一条消息是写eeprom
msg[0].addr = client->addr;//设备地址,即0x50
msg[0].buf = msgbuf;
msg[0].len = i;
//第二条消息才是读eeprom,读到的数据存储在buf中。
msg[1].addr = client->addr;//设备地址
msg[1].flags = I2C_M_RD;//读
msg[1].buf = buf;//读缓冲区
msg[1].len = count;//要读数据的长度
}
/*
* Reads fail if the previous write didn't complete yet. We may
* loop a few times until this one succeeds, waiting at least
* long enough for one entire page write to work.
*/
timeout = jiffies + msecs_to_jiffies(write_timeout);
do {
read_time = jiffies;
switch (at24->use_smbus) {
case I2C_SMBUS_I2C_BLOCK_DATA:
status = i2c_smbus_read_i2c_block_data(client, offset,
count, buf);
break;
case I2C_SMBUS_WORD_DATA:
status = i2c_smbus_read_word_data(client, offset);
if (status >= 0) {
buf[0] = status & 0xff;
buf[1] = status >> 8;
status = count;
}
break;
case I2C_SMBUS_BYTE_DATA:
status = i2c_smbus_read_byte_data(client, offset);
if (status >= 0) {
buf[0] = status;
status = count;
}
break;
default://使用I2C协议去读
status = i2c_transfer(client->adapter, msg, 2);//实际的数据传输,
if (status == 2)
status = count;
}
dev_dbg(&client->dev, "read %zu@%d --> %d (%ld)\n",
count, offset, status, jiffies);
if (status == count)//已经全部读取,则返回
return count;
/* REVISIT: at HZ=100, this is sloooow */
msleep(1);
} while (time_before(read_time, timeout));
return -ETIMEDOUT;
}
at24eepromread()根据读eeprom所需要的时序,填充两个i2c消息结构体,第一个i2c消息结构体是写eeprom,告诉eeprom要读的数据是哪个,第二个i2c消息才是真正的读eeprom。最后把这两个i2c消息结构体传给i2ctransfer()进行实际的消息传输。i2ctransfer()是i2c核心的函数,用于i2c设备与i2c适配器直接的消息传递,后面会分析。这里我们看到了i2c设备驱动通过i2c核心向i2c总线驱动传递消息的主要途径,i2c总线驱动接收到i2c消息后就会控制i2c适配器根据传入的i2c消息,通过SDA和SCL与eeprom进行交互。
static ssize_t at24_bin_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct at24_data *at24;
at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
return at24_write(at24, buf, off, count);
}
at24binwrite()与at24binread()一样操作,获得at24data后调用at24write().
static ssize_t at24_write(struct at24_data *at24, const char *buf, loff_t off,
size_t count)
{
ssize_t retval = 0;
if (unlikely(!count))
return count;
/*
* Write data to chip, protecting against concurrent updates
* from this host, but not from other I2C masters.
*/
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);
return retval;
}
at24write()的操作也是类似的,通过调用at24eeprom_write()来实现。
static ssize_t at24_eeprom_write(struct at24_data *at24, const char *buf,
unsigned offset, size_t count)
{
struct i2c_client *client;
struct i2c_msg msg;
ssize_t status;
unsigned long timeout, write_time;
unsigned next_page;
/* Get corresponding I2C address and adjust offset */
client = at24_translate_offset(at24, &offset);//获得对应的client
/* write_max is at most a page */
//检查写入字数
if (count > at24->write_max)
count = at24->write_max;
/* Never roll over backwards, to the start of this page */
//写入不会越过页边界(下一页)
next_page = roundup(offset + 1, at24->chip.page_size);
if (offset + count > next_page)
count = next_page - offset;
/* If we'll use I2C calls for I/O, set up the message */
if (!at24->use_smbus) {//使用i2c协议,则填充i2c消息结构体
int i = 0;
//由前小节分析,写eeprom只需一条i2c消息
msg.addr = client->addr;//设备地址
msg.flags = 0;//写eeprom
/* msg.buf is u8 and casts will mask the values */
msg.buf = at24->writebuf;//写缓冲区
if (at24->chip.flags & AT24_FLAG_ADDR16)
msg.buf[i++] = offset >> 8;
msg.buf[i++] = offset;
memcpy(&msg.buf[i], buf, count);//复制需要发送的数据
msg.len = i + count;//发送传读为要发送的数据长度,加上地址长度
}
/*
* Writes fail if the previous one didn't complete yet. We may
* loop a few times until this one succeeds, waiting at least
* long enough for one entire page write to work.
*/
timeout = jiffies + msecs_to_jiffies(write_timeout);//超时时间,为驱动模块参数,默认25ms
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 {//i2c传输
status = i2c_transfer(client->adapter, &msg, 1);//实际传输
if (status == 1)
status = count;
}
dev_dbg(&client->dev, "write %zu@%d --> %zd (%ld)\n",
count, offset, status, jiffies);
if (status == count)//已经全部写入,返回
return count;
/* REVISIT: at HZ=100, this is sloooow */
msleep(1);
} while (time_before(write_time, timeout));
return -ETIMEDOUT;
}
与at24eepromread()类似,at24eepromwrite()因为写eeprom需要1条i2c消息,最后实际的传输也是通过i2c_transfer()实现。
由上面简单的分析可知,通过sysfs文件系统访问eeprom,对/sys/bus/i2c/devices/0-0050/eeprom的读写是通过at24binread()/at24binwrite() ==> at24eepromread()/at24eepromwrite() ==>i2c_transfer()来实现的。
前面分析了i2c设备驱动如何实现通过sysfs文件系统访问eeprom,对于读写eeprom,最后都是调用了i2c_transfer(),此函数的实现在i2c核心中。
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
unsigned long orig_jiffies;
int ret, try;
/* REVISIT the fault reporting model here is weak:
*
* - When we get an error after receiving N bytes from a slave,
* there is no way to report "N".
*
* - When we get a NAK after transmitting N bytes to a slave,
* there is no way to report "N" ... or to let the master
* continue executing the rest of this combined message, if
* that's the appropriate response.
*
* - When for example "num" is two and we successfully complete
* the first message but get an error part way through the
* second, it's unclear whether that should be reported as
* one (discarding status on the second message) or errno
* (discarding status on the first one).
*/
if (adap->algo->master_xfer) {
#ifdef DEBUG
for (ret = 0; ret < num; ret++) {
dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
"len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)
? 'R' : 'W', msgs[ret].addr, msgs[ret].len,
(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
}
#endif
if (in_atomic() || irqs_disabled()) {
ret = rt_mutex_trylock(&adap->bus_lock);
if (!ret)
/* I2C activity is ongoing. */
return -EAGAIN;
} else {
rt_mutex_lock(&adap->bus_lock);
}
/* Retry automatically on arbitration loss */
orig_jiffies = jiffies;
for (ret = 0, try = 0; try <= adap->retries; try++) {
ret = adap->algo->master_xfer(adap, msgs, num);//i2c总线驱动的入口
if (ret != -EAGAIN)
break;
if (time_after(jiffies, orig_jiffies + adap->timeout))
break;
}
rt_mutex_unlock(&adap->bus_lock);
return ret;
} else {
dev_dbg(&adap->dev, "I2C level transfers not supported\n");
return -EOPNOTSUPP;
}
}
可以看到,语句ret = adap->algo->masterxfer(adap, msgs, num)就是i2c总线驱动的入口,此语句是寻找i2cadapter对应的i2calgorithm后,使用masterxfer()驱动硬件流程来进行实际的传输。
那么i2cadapter是在哪里绑定了i2calgorithm呢?master_xfer()又是如何来启动i2c传输的呢?在i2c总线驱动中我们就可以找到答案。
s5pv210处理器内部集成了一个i2c控制器,通过4个主要的寄存器就可以对其进行控制。 在arch/arm/plat-samsung/include/plat/regs-iic.h中列出了这几个寄存器。
#define S3C2410_IICREG(x) (x)
#define S3C2410_IICCON S3C2410_IICREG(0x00)//i2c控制寄存器
#define S3C2410_IICSTAT S3C2410_IICREG(0x04)//i2c状态寄存器
#define S3C2410_IICADD S3C2410_IICREG(0x08)//i2c地址寄存器
#define S3C2410_IICDS S3C2410_IICREG(0x0C)//i2c收发数据移位寄存器
i2c寄存器支持收发两种模式,我们主要使用主模式,通过对IICCON、IICDS和IICADD寄存器的操作,可以在i2c总线上产生开始位,停止位,数据和地址,而传输的状态则是通过IICSTAT寄存器获取。
在三星的i2c总线说明文档中给出了i2c总线进行传输的整个流程。
以通过i2c总线写eeprom为例,具体的流程如下:
在下面的小节中将结合代码来分析i2c总线对上面流程的具体实现。
i2c总线驱动被作为一个单独的模块加载,下面首先分析它的加载/卸载函数。
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);//注册为平台驱动
}
subsys_initcall(i2c_adap_s3c_init);
static void __exit i2c_adap_s3c_exit(void)
{
platform_driver_unregister(&s3c24xx_i2c_driver);
}
module_exit(i2c_adap_s3c_exit);
三星s5pv210的i2c总线驱动是作为平台驱动来实现的,其中传入的结构体s3c24xxi2cdriver就是platform_driver。
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
},
};
i2c总线驱动的probe函数会在一个合适的设备被发现的时候由总线驱动调用。
/* s3c24xx_i2c_probe
*
* called by the bus driver when a suitable device is found
*/
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;//封装i2c适配器的信息
struct s3c2410_platform_i2c *pdata;//i2c平台数据
struct resource *res;//平台资源
int ret;
pdata = pdev->dev.platform_data;//找到平台数据
if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
}
i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);//为i2c适配器私有数据结构体分配内存空间,并且初始化为0
if (!i2c) {
dev_err(&pdev->dev, "no memory for state\n");
return -ENOMEM;
}
//填充i2c适配器私有数据结构体
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));//名字
i2c->adap.owner = THIS_MODULE;//模块拥有者
i2c->adap.algo = &s3c24xx_i2c_algorithm;//总线通讯方法
i2c->adap.retries = 2;//重试次数
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = 50;
spin_lock_init(&i2c->lock);//i2c适配器私有数据的锁进行初始化
init_waitqueue_head(&i2c->wait);//初始化等待队列
/* find the clock and enable it */
//找到时钟,并且使能
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev, "i2c");//找到时钟
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
ret = -ENOENT;
goto err_noclk;
}
dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
clk_enable(i2c->clk);//使能
/* map the registers */
//映射寄存器
//获取平台设备资源,对于IORESOURSE_MEM类型的资源,start,end表示platform_device占据的内存的开始地址和结束地址
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "cannot find IO resource\n");
ret = -ENOENT;
goto err_clk;
}
//申请io内存资源
i2c->ioarea = request_mem_region(res->start, resource_size(res),
pdev->name);
if (i2c->ioarea == NULL) {
dev_err(&pdev->dev, "cannot request IO\n");
ret = -ENXIO;
goto err_clk;
}
//映射io
//I/O端口空间映射到内存的虚拟地址
i2c->regs = ioremap(res->start, resource_size(res));
if (i2c->regs == NULL) {
dev_err(&pdev->dev, "cannot map IO\n");
ret = -ENXIO;
goto err_ioarea;
}
dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",
i2c->regs, i2c->ioarea, res);
/* setup info block for the i2c core */
//设置i2c核心所需数据
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
/* initialise the i2c controller */
//i2c适配器私有数据结构提填充完了,就初始化i2c控制器
ret = s3c24xx_i2c_init(i2c);
if (ret != 0)
goto err_iomap;
/* find the IRQ for this unit (note, this relies on the init call to
* ensure no current IRQs pending
*/
//找到要申请的中断号
i2c->irq = ret = platform_get_irq(pdev, 0);
if (ret <= 0) {
dev_err(&pdev->dev, "cannot find IRQ\n");
goto err_iomap;
}
//申请中断,指定了中断处理函数s3c24xx_i2c_irq
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
dev_name(&pdev->dev), i2c);
if (ret != 0) {
dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq);
goto err_iomap;
}
//动态变频,忽略
ret = s3c24xx_i2c_register_cpufreq(i2c);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register cpufreq notifier\n");
goto err_irq;
}
/* Note, previous versions of the driver used i2c_add_adapter()
* to add the bus at any number. We now pass the bus number via
* the platform data, so if unset it will now default to always
* being bus 0.
*/
i2c->adap.nr = pdata->bus_num;
//添加i2c适配器(cpu内部集成)
ret = i2c_add_numbered_adapter(&i2c->adap);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add bus to i2c core\n");
goto err_cpufreq;
}
platform_set_drvdata(pdev, i2c);
clk_disable(i2c->clk);
dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
return 0;
err_cpufreq:
s3c24xx_i2c_deregister_cpufreq(i2c);
err_irq:
free_irq(i2c->irq, i2c);
err_iomap:
iounmap(i2c->regs);
err_ioarea:
release_resource(i2c->ioarea);
kfree(i2c->ioarea);
err_clk:
clk_disable(i2c->clk);
clk_put(i2c->clk);
err_noclk:
kfree(i2c);
return ret;
}
可以看到,i2c24xxi2cprobe()的主要工作有:使能硬件,申请i2c适配器使用的io地址、中断号,然后向i2c核心添加了这个适配器。
s3c24xx_i2c是i2c适配器的私有数据结构体,封装了适配器的所有信息。
struct s3c24xx_i2c {
spinlock_t lock;//用于防止并发访问的锁
wait_queue_head_t wait;//等待队列
unsigned int suspended:1;
struct i2c_msg *msg;//i2c消息
unsigned int msg_num;//i2c消息的数量
unsigned int msg_idx;//当前消息中的一个指针
unsigned int msg_ptr;//消息索引
unsigned int tx_setup;//等待数据发送到总线上的一个建立时间
unsigned int irq;//中断
enum s3c24xx_i2c_state state;//i2c状态
unsigned long clkrate;
void __iomem *regs;
struct clk *clk;
struct device *dev;
struct resource *ioarea;
struct i2c_adapter adap;//i2c_adapter
#ifdef CONFIG_CPU_FREQ
struct notifier_block freq_transition;
#endif
};
初始化i2c控制器函数s3c24xxi2cinit()如下
static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
{
unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;//中断使能,ACK使能
struct s3c2410_platform_i2c *pdata;
unsigned int freq;
/* get the plafrom data */
pdata = i2c->dev->platform_data;//获取平台数据
/* inititalise the gpio */
if (pdata->cfg_gpio)//初始化gpio ,流程(1)
pdata->cfg_gpio(to_platform_device(i2c->dev));
/* write slave address */
writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);//写从设备地址
dev_dbg(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);
writel(iicon, i2c->regs + S3C2410_IICCON);//写控制寄存器,也就是使能中断和使能ACK,流程(2)
/* we need to work out the divisors for the clock... */
if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {//计算时钟分频
writel(0, i2c->regs + S3C2410_IICCON);
dev_err(i2c->dev, "cannot meet bus frequency required\n");
return -EINVAL;
}
/* todo - check that the i2c lines aren't being dragged anywhere */
dev_dbg(i2c->dev, "bus frequency set to %d KHz\n", freq);
dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);
dev_dbg(i2c->dev, "S3C2440_IICLC=%08x\n", pdata->sda_delay);
writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC);
return 0;
}
s3c24xxi2cinit()中完成了前面所说的通过i2c总线写eeprom流程的(1)(2)两步。
在浅谈LinuxI2C驱动架构这一小节中提到了,i2c总线驱动是对I2C硬件体系结构中适配器端的实现,主要是实现了两个结构i2cadapter和i2calgorithm,从而控制i2c适配器产生通讯信号。
在i2c24xxi2cprobe()中就填充了i2cadapter,并且通过i2c->adap.algo = &s3c24xxi2calgorithm给i2cadapter绑定了i2c_algorithm。
其中s3c24xxi2calgorithm为
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
其中s3c24xxi2cxfer()用来启动i2c传输,s3c24xxi2cfunc()返回所支持的通讯协议。
所以说,i2c设备通过i2ctransfer()进行实际传输,在i2c核心中我们已经看到,i2ctransfer实际是调用了i2cadapter对应的masterxfer(),此处,在i2c总线驱动中,把masterxfer()指定为了s3c24xxi2cxfer(),所以说此时,传输任务交给了s3c24xxi2c_xfer()。
通过后面分析我们会看到,s3c24xxi2cxfer()只是启动了i2c传输,把i2c传输这个任务进行推进并且完成还需要靠我们在probe中注册的中断来完成,对应的中断处理函数是s3c24xxi2cirq(),后面都会详细分析。
接下来就是分析负责启动i2c传输任务的s3c24xxi2cxfer()。
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;//获得i2c适配器私有数据结构
int retry;
int ret;
clk_enable(i2c->clk);//使能时钟
for (retry = 0; retry < adap->retries; retry++) {//传输不成功,则重试,retries为重试次数。
ret = s3c24xx_i2c_doxfer(i2c, msgs, num);//启动一次i2c传输
if (ret != -EAGAIN)
goto out;
dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);
udelay(100);
}
ret = -EREMOTEIO;
out:
clk_disable(i2c->clk);
return ret;
}
可以看到s3c24xxi2cxfer()是调用了s3c24xxi2cdoxfer()来启动传输的。
static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
struct i2c_msg *msgs, int num)
{
unsigned long timeout;
int ret;
if (i2c->suspended)
return -EIO;
ret = s3c24xx_i2c_set_master(i2c);//检查i2c总线状态,总线不忙返回0
if (ret != 0) {
dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);
ret = -EAGAIN;
goto out;
}
spin_lock_irq(&i2c->lock);
//把消息写入i2c适配器的私有数据结构体中
i2c->msg = msgs;//i2c消息
i2c->msg_num = num;//消息数量
i2c->msg_ptr = 0;//消息指针,指向当前消息未发送部分的开始
i2c->msg_idx = 0;//消息索引
i2c->state = STATE_START;//将状态改为STATE_START
s3c24xx_i2c_enable_irq(i2c);//使能中断
s3c24xx_i2c_message_start(i2c, msgs);//发送第一个byte,获得ACK后触发中断。
spin_unlock_irq(&i2c->lock);
timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);//等待消息传输完成,否则超时
s3c24xxi2cdoxfer()首先调用s3c24xxi2csetmaster()来检查总线状态,s3c24xxi2csetmaster()的实现如下
static int s3c24xx_i2c_set_master(struct s3c24xx_i2c *i2c)
{
unsigned long iicstat;
int timeout = 400;
while (timeout-- > 0) {
iicstat = readl(i2c->regs + S3C2410_IICSTAT);//读i2c状态寄存器
if (!(iicstat & S3C2410_IICSTAT_BUSBUSY))//总线不忙,则返回0;否则直到超时
return 0;
msleep(1);
}
writel(iicstat & ~S3C2410_IICSTAT_TXRXEN, i2c->regs + S3C2410_IICSTAT);
if (!(readl(i2c->regs + S3C2410_IICSTAT) & S3C2410_IICSTAT_BUSBUSY))
return 0;
return -ETIMEDOUT;
}
在获知总线不忙后,把要消息写入i2c适配器私有数据结构,并且把状态改为STATESTART。 然后使能中断,通过s3c24xxi2cmessagestart()发送第一个byte,这样在获取ACK后就会触发中断来推进i2c的传输。
static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
struct i2c_msg *msg)
{
unsigned int addr = (msg->addr & 0x7f) << 1;//从设备地址,7位地址,最低位用来表示读或者写,1为读,0为写。
unsigned long stat;
unsigned long iiccon;
stat = 0;
stat |= S3C2410_IICSTAT_TXRXEN;//使能RxTx
if (msg->flags & I2C_M_RD) {//从i2c消息判断,如果是读
stat |= S3C2410_IICSTAT_MASTER_RX;//把状态设为主模式读
addr |= 1;//别且设置第一byte最低位为1,表示读
} else//否则是写
stat |= S3C2410_IICSTAT_MASTER_TX;//把状态设为主模式写
if (msg->flags & I2C_M_REV_DIR_ADDR)//如果是读写反转
addr ^= 1;//读写交换
/* todo - check for wether ack wanted or not */
s3c24xx_i2c_enable_ack(i2c);//使能ACK
iiccon = readl(i2c->regs + S3C2410_IICCON);
writel(stat, i2c->regs + S3C2410_IICSTAT);//根据前面的设置来配置控制寄存器,流程(3)
dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);
writeb(addr, i2c->regs + S3C2410_IICDS);//把第一个byte写入i2c收发数据移位寄存器,流程(3)
/* delay here to ensure the data byte has gotten onto the bus
* before the transaction is started */
ndelay(i2c->tx_setup);
dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);
writel(iiccon, i2c->regs + S3C2410_IICCON);
stat |= S3C2410_IICSTAT_START;
writel(stat, i2c->regs + S3C2410_IICSTAT);//修改状态,流程(3)
}
s3c24xxi2cmessage_start()在i2c总线上发送了一个开始信号,即完成了通过i2c总线写eeprom中的流程(3)的工作,设备地址赋给IICDS ,并设置IICSTAT,启动IIC发送设备地址出去,当从设备收到此数据并且回复ACK后,i2c适配器收到ACK后就会触发中断来推进i2c的传输。
发送完第一个byte,收到ACK信号后就会进入中断,并且以后只要收到ACK信号就都会进入中断。中断在probe中已经注册,它的实现 如下
static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)
{
struct s3c24xx_i2c *i2c = dev_id;
unsigned long status;
unsigned long tmp;
status = readl(i2c->regs + S3C2410_IICSTAT);//获得i2c状态寄存器的值
if (status & S3C2410_IICSTAT_ARBITR) {//需要仲裁
/* deal with arbitration loss */
dev_err(i2c->dev, "deal with arbitration loss\n");
}
if (i2c->state == STATE_IDLE) {//空闲状态
dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n");
tmp = readl(i2c->regs + S3C2410_IICCON);
tmp &= ~S3C2410_IICCON_IRQPEND;
writel(tmp, i2c->regs + S3C2410_IICCON);
goto out;
}
/* pretty much this leaves us with the fact that we've
* transmitted or received whatever byte we last sent */
i2c_s3c_irq_nextbyte(i2c, status);//推进传输,传输下一个byte
out:
return IRQ_HANDLED;
}
i2c总线驱动的中断处理函数s3c24xxi2cirq()是调用i2cs3cirq_nextbyte()来推进i2c的传输的。
static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat
{
unsigned long tmp;
unsigned char byte;
int ret = 0;
switch (i2c->state) {//根据i2c的状态选择
case STATE_IDLE://空闲
dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__);
goto out;
break;
case STATE_STOP://停止
dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__);
s3c24xx_i2c_disable_irq(i2c);//禁止中断
goto out_ack;
case STATE_START://开始
/* last thing we did was send a start condition on the
* bus, or started a new i2c message
*/
//切换为开始状态之前,刚发送了第一个byte,也就是设备地址
//首先检查下state时候与硬件寄存器的状态一致
if (iicstat & S3C2410_IICSTAT_LASTBIT &&
!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
/* ack was not received... */
dev_dbg(i2c->dev, "ack was not received\n");
s3c24xx_i2c_stop(i2c, -ENXIO);//停止i2c传输
goto out_ack;
}
if (i2c->msg->flags & I2C_M_RD)//如果当前i2c消息的标志为i2c读
i2c->state = STATE_READ;//则修改状态为i2c读
else
i2c->state = STATE_WRITE;//否则修改为i2c写
/* terminate the transfer if there is nothing to do
* as this is used by the i2c probe to find devices. */
if (is_lastmsg(i2c) && i2c->msg->len == 0) {//如果是最后一条消息则停止i2c传输。
s3c24xx_i2c_stop(i2c, 0);
goto out_ack;
}
if (i2c->state == STATE_READ)//如果i2c状态为读,就跳到读,否则,,就会跳到写,,,因为没有break
goto prepare_read;
/* fall through to the write state, as we will need to
* send a byte as well */
case STATE_WRITE://第一次开始写i2c
/* we are writing data to the device... check for the
* end of the message, and if so, work out what to do
*/
if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
if (iicstat & S3C2410_IICSTAT_LASTBIT) {
dev_dbg(i2c->dev, "WRITE: No Ack\n");
s3c24xx_i2c_stop(i2c, -ECONNREFUSED);
goto out_ack;
}
}
retry_write://继续写
if (!is_msgend(i2c)) {//不是一条消息的最后1Byte
byte = i2c->msg->buf[i2c->msg_ptr++];//取出此消息的下一个byte
writeb(byte, i2c->regs + S3C2410_IICDS);//写入收发数据移位寄存器。
/* delay after writing the byte to allow the
* data setup time on the bus, as writing the
* data to the register causes the first bit
* to appear on SDA, and SCL will change as
* soon as the interrupt is acknowledged */
ndelay(i2c->tx_setup);//延迟,等待数据发送
} else if (!is_lastmsg(i2c)) {//是一条消息的最后一个byte,不是最后一条消息
/* we need to go to the next i2c message */
dev_dbg(i2c->dev, "WRITE: Next Message\n");
i2c->msg_ptr = 0;//当前消息未发数据开始指针复位
i2c->msg_idx++;//消息索引++
i2c->msg++;//下一条消息
/* check to see if we need to do another message */
if (i2c->msg->flags & I2C_M_NOSTART) {//在发送下个消息之前,检查是否需要一个新的开始信号,如果不需要
if (i2c->msg->flags & I2C_M_RD) {//如果是读
/* cannot do this, the controller
* forces us to send a new START
* when we change direction */
s3c24xx_i2c_stop(i2c, -EINVAL);//错误,返回
}
goto retry_write;//继续写
} else {//如果需要一个新的开始信号
/* send the new start */
s3c24xx_i2c_message_start(i2c, i2c->msg);//发送一个新的开始信号
i2c->state = STATE_START;//并且修改状态
}
} else {//是一条消息的最后
/* send stop */
s3c24xx_i2c_stop(i2c, 0);//停止发送
}
break;
case STATE_READ://开始读
/* we have a byte of data in the data register, do
* something with it, and then work out wether we are
* going to do any more read/write
*/
byte = readb(i2c->regs + S3C2410_IICDS);//先获取读到的消息,后面再决定时候有用
i2c->msg->buf[i2c->msg_ptr++] = byte;//把消息存入读缓冲
prepare_read://如果第一个byte是读,则跳到此处。
if (is_msglast(i2c)) {//是当前消息的最后一byte,也就是当前消息只剩1Byte的空余
/* last byte of buffer */
if (is_lastmsg(i2c))//如果也是最后一条消息
s3c24xx_i2c_disable_ack(i2c);//那么就禁止ACK
} else if (is_msgend(i2c)) {//否则如果是当前消息已经用完读缓冲
/* ok, we've read the entire buffer, see if there
* is anything else we need to do */
if (is_lastmsg(i2c)) {//如果是最后一条消息了
/* last message, send stop and complete */
dev_dbg(i2c->dev, "READ: Send Stop\n");
s3c24xx_i2c_stop(i2c, 0);//停止i2c传输
} else {//否则进入下一条i2c传输
/* go to the next transfer */
dev_dbg(i2c->dev, "READ: Next Transfer\n");
i2c->msg_ptr = 0;
i2c->msg_idx++;
i2c->msg++;//下一条i2c消息
}
}
break;
}
/* acknowlegde the IRQ and get back on with the work */
out_ack:
tmp = readl(i2c->regs + S3C2410_IICCON);
tmp &= ~S3C2410_IICCON_IRQPEND;//清中断标志位
writel(tmp, i2c->regs + S3C2410_IICCON);
out:
return ret;
}
i2cs3cirq_nextbyte()推进了i2c的传输,以写eeprom为例,第一个Byte的设备地址发送后,从EEPROM得到ACK信号,此信号触发中断,在中断处理函数中把第二个Byte(设备内地址)发送出去;发送之后,接收到ACK又触发中断,中断处理函数把第三个Byte(真正的数据)发送到设备中,发送之后同样接收到ACK并触发中断,中断处理函数判断,发现数据传送完毕,就发送IIC Stop信号,关IIC中断,置位各寄存器。这样就把通过i2c总线写eeprom的整个流程都实现了。
i2c总线驱动控制i2c适配器产生通信信号,通过master_xfer()启动一个i2c传输,然后通过中断推进i2c传输。
原文地址: http://hello2mao.github.io/2015/12/02/Linux_I2C_driver.html