赞
踩
本人学生一枚,之前没有详细的接触过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的内容。
发现里面都是乱码,然后用echo命令把字符串“test”输入给eeprom文件,然后再cat出来。
就会发现字符串test已经存在eeprom里面了,我们知道sysfs文件系统断电后就没了,也无法对数据进行保存,为了验证确实把“test”字符串存储在了eeprom,可以把系统断电重启,然后cat eeprom,会发现test还是存在的,证明确实对eeprom进行了写入操作。
当然,因为eeprom已经映射为一个文件了,我们还可以通过文件I/O写应用程序对其进行简单的访问测试。比如以下程序对特定地址(0x40)写入特定数据(Hi,this is an eepromtest!),然后再把写入的数据在此地址上读出来。
- #include<stdio.h>
- #include<stdlib.h>
- #include<sys/types.h>
- #include<sys/stat.h>
- #include<fcntl.h>
- #include<string.h>
-
- 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 <stdio.h>
- #include <linux/types.h>
- #include <stdlib.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/ioctl.h>
- #include <errno.h>
- #include <linux/i2c.h>
- #include <linux/i2c-dev.h>
-
- 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。