原文地址: http://hello2mao.github.io/2015/12/02/Linux_I2C_driver.html

目录

  • 一、LinuxI2C驱动--概述
  • 1.1 写在前面
  • 1.2 I2C
  • 1.3 硬件
  • 1.4 软件
  • 1.5 参考
  • 二、LinuxI2C驱动--I2C总线
  • 2.1 I2C总线物理结构
  • 2.2 I2C总线特性
  • 2.3 开始和停止条件
  • 2.4 数据传输格式
  • 2.5 响应
  • 2.6 总线仲裁
  • 三、LinuxI2C驱动--解析EEPROM的读写
  • 3.1 概述
  • 3.2 设备地址
  • 3.3 读eeprom
  • 3.4 写eeprom
  • 四、LinuxI2C驱动--从两个访问eeprom的例子开始
  • 4.1 通过sysfs文件系统访问I2C设备
  • 4.2 通过devfs访问I2C设备
  • 4.3 总结
  • 五、LinuxI2C驱动--浅谈LinuxI2C驱动架构
  • 5.1 I2C体系结构
  • 5.2 I2C重要数据结构
  • 六、LinuxI2C驱动--I2C设备驱动
  • 6.1 eeprom板级设备资源
  • 6.2 AT24C01A EEPROM 的I2C设备驱动
  • 6.2.1 at24_driver
  • 6.2.2 at24probe() / at24remove()
  • 6.2.3 at24binread()
  • 6.2.4 at24binwrite()
  • 6.3 总结
  • 七、LinuxI2C驱动--I2C总线驱动
  • 7.1 三星S5PV210 i2c适配器的硬件描述
  • 7.2 i2c总线驱动的加载/卸载
  • 7.3 i2c总线驱动的probe
  • 7.4 启动i2c传输
  • 7.5 通过中断来推进i2c的传输
  • 7.6 总结

一、LinuxI2C驱动--概述

1.1 写在前面

本人学生一枚,之前没有详细的接触过linux驱动,只是读过宋宝华的《Linux设备驱动开发详解》,这段时间想静下心来学习下linux i2c驱动,在网上找了很多资料,前辈们写的文章让我受益匪浅,但是一开始上手真的很痛苦,基本上大家都是从linux i2c体系结构的三大组成谈起:i2c核心,i2c总线驱动,i2c设备驱动,好抽象。所以我才想写这个文章,从一个新人的角度分享下我学习linux i2c驱动的心得,写的不对的地方欢迎大家批评指正。

因为对Linux设备模型还不是很熟悉,所以我按照如何去实现一个i2c传输来讲述,对于平台总线、设备与总线如何去匹配等暂时忽略。

当然很多东西都是我从网上搜刮而来的,也请大家原谅。我会把一些有用的博文链接放在后面,希望对大家有用。

1.2 I2C

I2C总线是由Philips公司开发的两线式串行总线,这两根线为时钟线(SCL)和双向数据线(SDA)。由于I2C总线仅需要两根线,因此在电路板上占用的空间更少,带来的问题是带宽较窄。I2C在标准模式下传输速率最高100Kb/s,在快速模式下最高可达400kb/s。属于半双工。

在嵌入式系统中,I2C应用非常广泛,大多数微控制器中集成了I2C总线,一般用于和RTC,EEPROM,智能电池电路,传感器,LCD以及其他类似设备之间的通信。

1.3 硬件

开发板:飞凌OK210

CPU型号:Samsung S5PV210

EEPROM型号:AT24C01A

linux-i2c-1.3.png

1.4 软件

linux版本:Linux 2.6.35.7

I2C总线驱动:drivers/i2c/busses/i2c-s3c2410.c

eeprom驱动:drivers/misc/eeprom/at24.c

1.5 参考

  • 《Linux设备驱动开发详解》 宋宝华
  • http://blog.csdn.net/liaozc/article/details/6655015 Zechin的专栏
  • http://www.linxh.blog.chinaunix.net/uid/25445243/sid-179653-list-1.html 辉辉308
  • http://www.linuxidc.com/Linux/2011-11/47651.htm Linux社区 作者:cjok376240497
  • http://www.embedu.org/Column/Column190.htm 作者:刘老师,华清远见嵌入式学院讲师。
  • http://www.linuxidc.com/Linux/2013-10/91993p14.htm Linux社区 作者:赵春江

二、LinuxI2C驱动--I2C总线

本节分析下I2C总线协议,因为我的开发板是三星s5pv210芯片,所以就以此为例。

2.1 I2C总线物理结构

linux-i2c-2.1.png

I2C总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。

2.2 I2C总线特性

  • 每个连接到总线的器件都可以通过唯一的地址和一直存在的简单的主机/从机关系来软件设定地址
  • 多主机总线,如果两个或者更多的主机同时初始化数据传输,可以通过仲裁防止数据被破坏。
  • 串行8位双向数据传输
  • 标准模式传输速率为100kbits/s
  • 快速模式传输速率为400kbits/s
  • 7位地址模
  • 支持主机发、主机收,从机发、从机收

2.3 开始和停止条件

当SCL是高电平时,SDA线由高电平向低电平切换,表示开始;当SCL是高电平时,SDA线由低电平向高电平切换,表示停止。如下图所示。

linux-i2c-2.3.png

2.4 数据传输格式

发送到SDA线上的每个字节必须为8位,每次传输可以发送的字节数不受限制,但是每个字节后面必须跟一个响应位。

linux-i2c-2.4.1.png

linux-i2c-2.4.2.png

2.5 响应

数据传输必须带响应,响应时钟脉冲由主机产生,在SCL的第9个时钟脉冲上,前8个时钟脉冲用来传输8位即1byte的数据。

当发送端收到响应时钟脉冲的时候就会拉高SDA从而释放SDA线,而接收端通过拉低SDA先来表示收到数据,即SDA在响应期间保持低电平。

linux-i2c-2.5.png

2.6 总线仲裁

当两个主机在总线上产生竞争时就需要仲裁。

SDA线低电平的优先级高于高电平。当一个主机首先产生低电平,而紧接着另一个主机产生高电平,但是由于低电平的优先级高于高电平,所以总线成低电平,也就是发低电平的主机占有总线而发高电平的主机不占有总线。如果两个主机都是发送低电平,那么继续比较下一个时钟周期的电平来决定谁占有总线,以此类推。

linux-i2c-2.6.png

三、LinuxI2C驱动--解析EEPROM的读写

本节介绍eeprom的读写时序,参考的是AT24C01A的datasheet。

3.1 概述

AT24C01A的存储大小是1K,页大小是8个字节。

linux-i2c-3.1.png

3.2 设备地址

linux-i2c-3.2.png

7位地址,前四位是1010,后三位由芯片引脚决定,由原理图可知后三位是000,也就是设备地址为0x50,因为数据传输是8位的,最后一位决定是读还是写。

3.3 读eeprom

linux-i2c-3.3.png

读任意地址eeprom的数据,首先第一个字节得先在SDA上发出eeprom的设备地址,也就是0x50,并且8位数据的最后一位是低电平表示写设备,然后第二个字节是要读的数据在eeprom内的地址,这样以后再产生开始条件,第三个字节在SDA上发出设备地址,此时的最后一位是高电平,表示读设备,第四个字节的数据就是读eeprom的对应地址的数据。

可以看到,读eeprom需要两个开始条件,也就是2条消息,第一条消息写eeprom确定读的位置,大小为2个字节,第二条消息才是真正的读eeprom。

3.4 写eeprom

linux-i2c-3.4.png

写eeprom就相对简单,只需一个开始条件,第一个字节发出设备地址和置最低位为低电平表示写eeprom,第二个字节发出要读数据在eerpom的地址,第三个字节读到的数据就对应地址在eeprom上的数据

四、LinuxI2C驱动--从两个访问eeprom的例子开始

本小节介绍两个在linux应用层访问eeprom的方法,并给出示例代码方便大家理解。第一个方法是通过sysfs文件系统对eeprom进行访问,第二个方法是通过eeprom的设备文件进行访问。这两个方法分别对应了i2c设备驱动的两个不同的实现,在后面的小结会详细的分析。

4.1 通过sysfs文件系统访问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<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<string.h>intmain(void){intfd,size,len,i;charbuf[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);//定位地址,地址是0x40if((size=write(fd,bufw,len))<0)//写入数据{printf("write error\n");return1;}printf("writeok\n");//读操作lseek(fd,0x40,SEEK_SET);//准备读,首先定位地址,因为前面写入的时候更新了当前文件偏移量,所以这边需要重新定位到0x40.if((size=read(fd,buf,len))<0)//读数据{printf("readerror\n");return1;}printf("readok\n");for(i=0;i<len;i++)printf("buff[%d]=%x\n",i,buf[i]);//打印数据close(fd);return0;}

4.2 通过devfs访问I2C设备

linux的i2c驱动会针对每个i2c适配器在/dev/目录下生成一个主设备号为89的设备文件,简单的来说,对于本例的eeprom驱动,/dev/i2c/0就是它的设备文件,因此接下来的eeprom的访问就变为了对此设备文件的访问。

我们需要用到两个结构体i2cmsg和i2crdwrioctldata。

structi2c_msg{//i2c消息结构体,每个i2c消息对应一个结构体__u16 addr;/* 从设备地址,此处就是eeprom地址,即0x50 */__u16 flags;/* 一些标志,比如i2c读等*/__u16 len;/* i2c消息的长度 */__u8*buf;/* 指向i2c消息中的数据 */};structi2c_rdwr_ioctl_data{structi2c_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>intmain(){intfd,ret;structi2c_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=(structi2c_msg*)malloc(e2prom_data.nmsgs*sizeof(structi2c_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=(unsignedchar*)malloc(2);(e2prom_data.msgs[0]).buf[0]=0x10;// e2prom 写入目标的地址(e2prom_data.msgs[0]).buf[1]=0x58;//写入的数据ret=ioctl(fd,I2C_RDWR,(unsignedlong)&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=(unsignedchar*)malloc(1);//存放返回值的地址。(e2prom_data.msgs[1]).buf[0]=0;//初始化读缓冲,读到的数据放到此缓冲区ret=ioctl(fd,I2C_RDWR,(unsignedlong)&e2prom_data);//通过ioctl进行实际的读操作if(ret<0){perror("ioctl error2");}printf("buff[0]=%x\n",(e2prom_data.msgs[1]).buf[0]);/***打印读出的值,没错的话,就应该是前面写的0x58了***/close(fd);return0;}

4.3 总结

本小节介绍了两种在linux应用层访问eeprom的方法,并且给出了示例程序,通过sysfs文件系统访问eeprom操作简单,无需了解eeprom的硬件特性以及访问时序,而通过devfs访问eeprom的方法则需要了解eeprom的读写时序。

后面分析后会发现,第一种通过sysfs文件系统的二进制结点访问eeprom的方法是由eeprom的设备驱动实现的,是一种专有的方法;而第二种通过devfs访问eeprom的方法是linux i2c提供的一种通用的方法,访问设备的能力有限。

五、LinuxI2C驱动--浅谈LinuxI2C驱动架构

前面几个小结介绍了i2c总线的协议,又介绍了我们关注的eeprom的读写访问时序,还给出了两个访问eeprom的例子,我的目的是为了能更好的理解后面解析Linux下i2c驱动。

网上介绍Linux I2C驱动架构的文章非常的多,我把这些内容做了个归纳与简化,但是在搬出这些非常抽象的内容之前,我想先谈下我的理解。

如下图

linux-i2c-5.0.png

图中画了一个三星的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。

接下来开始搬运,,

5.1 I2C体系结构

linux的i2c体系结构分为三个组成部分。放张图加深理解。

linux-i2c-5.0.png

(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

5.2 I2C重要数据结构

在include/linux/i2c.h中定义四个I2C驱动中重要的数据结构:i2cadapter,i2calgorithm,i2cdriver,i2cclient.

i2c_adapter对应物理上的一个i2c适配器

structi2c_adapter{structmodule*owner;//所属模块unsignedintid;unsignedintclass;/* classes to allow probing for */conststructi2c_algorithm*algo;/* 总线通讯方法指针,需要其产生特定的访问周期信号 */void*algo_data;/* data fields that are valid for all devices   */structrt_mutex bus_lock;inttimeout;/* in jiffies */intretries;/* 重复次数 */structdevice dev;/* the adapter device */intnr;charname[48];structcompletion dev_released;structlist_head userspace_clients;};

i2c_algorithm对应一套通讯方法

structi2c_algorithm{int(*master_xfer)(structi2c_adapter*adap,structi2c_msg*msgs,intnum);//产生i2c访问周期说需要的信号int(*smbus_xfer)(structi2c_adapter*adap,u16 addr,unsignedshortflags,charread_write,u8 command,intsize,unioni2c_smbus_data*data);/* To determine what the adapter supports */u32(*functionality)(structi2c_adapter*);//返回说支持的通讯协议};

i2c_driver对应一套驱动方法

structi2c_driver{unsignedintclass;int(*probe)(structi2c_client*,conststructi2c_device_id*);int(*remove)(structi2c_client*);void(*shutdown)(structi2c_client*);int(*suspend)(structi2c_client*,pm_message_tmesg);int(*resume)(structi2c_client*);void(*alert)(structi2c_client*,unsignedintdata);int(*command)(structi2c_client*client,unsignedintcmd,void*arg);structdevice_driver driver;conststructi2c_device_id*id_table;//该驱动所支持的i2c设备的ID表int(*detect)(structi2c_client*,structi2c_board_info*);constunsignedshort*address_list;structlist_head clients;};

i2cclient对应真实的物理设备,每个i2c设备都需要一个i2cclient来描述

structi2c_client{unsignedshortflags;/* div., see below      */unsignedshortaddr;/* chip address - NOTE: 7bit    *//* addresses are stored in the  *//* _LOWER_ 7 bits       */charname[I2C_NAME_SIZE];structi2c_adapter*adapter;/* the adapter we sit on    */structi2c_driver*driver;/* and our access routines  */structdevice dev;/* the device structure     */intirq;/* irq issued by device     */structlist_head detected;};

(1)i2cadapter与i2calgorithm 一个I2C适配器需要i2calgorithm中提供的通信函数来控制适配器上产生特定的访问周期。i2calgorithm中的关键函数masterxfer()用于产生I2C访问周期需要的信号,以i2cmsg为单位。

structi2c_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上。

六、LinuxI2C驱动--I2C设备驱动

本节主要分析eeprom的所属的i2c设备驱动,此驱动主要实现了能够通过sysfs文件系统访问eeprom。

6.1 eeprom板级设备资源

因为原开发板的eeprom驱动还没调试好,板级资源还没写好,所以需要自己加进去。 修改arch/arm/mach-s5pv210/mach-smdkc110.c文件。

staticstructat24_platform_data at24c01={.byte_len=SZ_8K/8,/*eeprom大小*/.page_size=8,/*页大小*/};/* I2C0 */staticstructi2c_board_info i2c_devs0[]__initdata={{I2C_BOARD_INFO("24c01",0x50),//0x50是eeprom的设备地址.platform_data=&at24c01,},}

这样以后后面调用smdkc110machineinit就会把资源注册进去。

staticvoid__init smdkc110_machine_init(void){….i2c_register_board_info(0,i2c_devs0,ARRAY_SIZE(i2c_devs0));….}

6.2 AT24C01A EEPROM 的I2C设备驱动

6.2.1 at24_driver

前面讲i2c驱动架构的时候,说到I2C设备驱动主要由i2c_driver来描述。

在drivers/misc/eeprom/at24.c中可以看到eeprom驱动对i2c_driver结构的实例化。

staticstructi2c_driver at24_driver={.driver={.name="at24",.owner=THIS_MODULE,},.probe=at24_probe,.remove=__devexit_p(at24_remove),.id_table=at24_ids,};

其中probe和remove会在模块初始化和卸载的时候被调用。

staticint__init at24_init(void)//模块初始化{io_limit=rounddown_pow_of_two(io_limit);//io_limit是写eeprom时允许一次写入的最大字节,默认128Byte,是驱动模块参数。returni2c_add_driver(&at24_driver);//添加i2c_driver,在i2c核心中实现,会调用at24_probe.}module_init(at24_init);staticvoid__exit at24_exit(void)//模块卸载{i2c_del_driver(&at24_driver);//删除i2c_driver,会调用at24_remove}module_exit(at24_exit);

6.2.2 at24_probe() / at24_remove()

staticintat24_probe(structi2c_client*client,conststructi2c_device_id*id){structat24_platform_data chip;boolwritable;intuse_smbus=0;structat24_data*at24;interr;unsignedi,num_addresses;kernel_ulong_tmagic;//获取板级设备信息if(client->dev.platform_data){chip=*(structat24_platform_data*)client->dev.platform_data;}else{if(!id->driver_data){err=-ENODEV;gotoerr_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协议,如果不支持则检查是否支持SMBUSif(!i2c_check_functionality(client->adapter,I2C_FUNC_I2C)){if(chip.flags&AT24_FLAG_ADDR16){err=-EPFNOSUPPORT;gotoerr_out;}if(i2c_check_functionality(client->adapter,I2C_FUNC_SMBUS_READ_I2C_BLOCK)){use_smbus=I2C_SMBUS_I2C_BLOCK_DATA;}elseif(i2c_check_functionality(client->adapter,I2C_FUNC_SMBUS_READ_WORD_DATA)){use_smbus=I2C_SMBUS_WORD_DATA;}elseif(i2c_check_functionality(client->adapter,I2C_FUNC_SMBUS_READ_BYTE_DATA)){use_smbus=I2C_SMBUS_BYTE_DATA;}else{err=-EPFNOSUPPORT;gotoerr_out;}}if(chip.flags&AT24_FLAG_TAKE8ADDR)//检查时候使用8个地址num_addresses=8;elsenum_addresses=DIV_ROUND_UP(chip.byte_len,//AT24C01使用一个地址(chip.flags&AT24_FLAG_ADDR16)?65536:256);at24=kzalloc(sizeof(structat24_data)+num_addresses*sizeof(structi2c_client*),GFP_KERNEL);//为at24_data分配内存,同时根据地址个数分配i2c_clientif(!at24){err=-ENOMEM;gotoerr_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)){unsignedwrite_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;gotoerr_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;gotoerr_clients;}}//向sysfs文件系统注册二进制结点err=sysfs_create_bin_file(&client->dev.kobj,&at24->bin);if(err)gotoerr_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);return0;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);returnerr;}

at24probe()函数主要的工作是在sys目录在创建bin结点文件,也就是前面通过sysfs文件系统访问i2c设备中提到的/sys/bus/i2c/devices/0-0050/eeprom文件,用户可以用此文件来操作eeprom,提供读/写操作方法,在probe里面读写操作已经与二进制结点绑定,读操作函数是at24binread(),写操作函数是at24bin_write()。

其中有个重要的结构体:

structat24_data{structat24_platform_data chip;structmemory_accessor macc;intuse_smbus;/** Lock protects against activities from other Linux tasks,* but not from changes by other I2C masters.*/structmutexlock;structbin_attribute bin;//二进制结点u8*writebuf;//写缓冲区unsignedwrite_max;unsignednum_addresses;/* * Some chips tie up multiple I2C addresses; dummy devices reserve* them for us, and we'll use them with SMBus calls.*/structi2c_client*client[];};

at24_data是此驱动的一些私有数据的封装,包括二进制结点,以及写缓冲区。

staticint__devexit at24_remove(structi2c_client*client){structat24_data*at24;inti;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);return0;}

at24remove()基本就是at24probe()的反操作。

6.2.3 at24_bin_read()

staticssize_tat24_bin_read(structfile*filp,structkobject*kobj,structbin_attribute*attr,char*buf,loff_toff,size_tcount){structat24_data*at24;//通过kobj获得device,再获取driver_dataat24=dev_get_drvdata(container_of(kobj,structdevice,kobj));returnat24_read(at24,buf,off,count);//调用at24_read()}

at24binread()通过devgetdrvdata()获取at24data结构体数据。然后调用at24read()。

staticssize_tat24_read(structat24_data*at24,char*buf,loff_toff,size_tcount){ssize_tretval=0;if(unlikely(!count))returncount;/** Read data from chip, protecting against concurrent updates* from this host, but not from other I2C masters.*/mutex_lock(&at24->lock);//访问设备前加锁while(count){ssize_tstatus;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);//访问结束后解锁returnretval;}

at24read()传入的参数,at24是驱动私有数据结构体at24data,buf是读eeprom后读到的数据存储的缓冲区,off是数据的偏移地址,count是要读数据的大小。at24read()主要调用at24eeprom_read()去读,但是此函数读eeprom能读到的数据个数有限制,不一定一次就把count个数据都读到,所以用while来读,并且读到status个数据后更新count,表示还剩多少个数据没读到,同时也要更新数据偏移off,和读入缓冲buf。

staticssize_tat24_eeprom_read(structat24_data*at24,char*buf,unsignedoffset,size_tcount){structi2c_msg msg[2];u8 msgbuf[2];structi2c_client*client;unsignedlongtimeout,read_time;intstatus,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);//获得clientif(count>io_limit)count=io_limit;switch(at24->use_smbus){//如果使用SMBUScaseI2C_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;caseI2C_SMBUS_WORD_DATA:count=2;break;caseI2C_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条消息,第一条消息是写eeprommsg[0].addr=client->addr;//设备地址,即0x50msg[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){caseI2C_SMBUS_I2C_BLOCK_DATA:status=i2c_smbus_read_i2c_block_data(client,offset,count,buf);break;caseI2C_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;caseI2C_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)//已经全部读取,则返回returncount;/* 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进行交互。

6.2.4 at24_bin_write()

staticssize_tat24_bin_write(structfile*filp,structkobject*kobj,structbin_attribute*attr,char*buf,loff_toff,size_tcount){structat24_data*at24;at24=dev_get_drvdata(container_of(kobj,structdevice,kobj));returnat24_write(at24,buf,off,count);}

at24binwrite()与at24binread()一样操作,获得at24data后调用at24write().

staticssize_tat24_write(structat24_data*at24,constchar*buf,loff_toff,size_tcount){ssize_tretval=0;if(unlikely(!count))returncount;/** Write data to chip, protecting against concurrent updates* from this host, but not from other I2C masters.*/mutex_lock(&at24->lock);while(count){ssize_tstatus;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);returnretval;}

at24write()的操作也是类似的,通过调用at24eeprom_write()来实现。

staticssize_tat24_eeprom_write(structat24_data*at24,constchar*buf,unsignedoffset,size_tcount){structi2c_client*client;structi2c_msg msg;ssize_tstatus;unsignedlongtimeout,write_time;unsignednext_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消息结构体inti=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);//超时时间,为驱动模块参数,默认25msdo{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)//已经全部写入,返回returncount;/* REVISIT: at HZ=100, this is sloooow */msleep(1);}while(time_before(write_time,timeout));return-ETIMEDOUT;}

与at24eepromread()类似,at24eepromwrite()因为写eeprom需要1条i2c消息,最后实际的传输也是通过i2c_transfer()实现。

6.3 总结

由上面简单的分析可知,通过sysfs文件系统访问eeprom,对/sys/bus/i2c/devices/0-0050/eeprom的读写是通过at24binread()/at24binwrite() ==> at24eepromread()/at24eepromwrite() ==>i2c_transfer()来实现的。

七、LinuxI2C驱动--I2C总线驱动

前面分析了i2c设备驱动如何实现通过sysfs文件系统访问eeprom,对于读写eeprom,最后都是调用了i2c_transfer(),此函数的实现在i2c核心中。

inti2c_transfer(structi2c_adapter*adap,structi2c_msg*msgs,intnum){unsignedlongorig_jiffies;intret,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){#ifdefDEBUGfor(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)?"+":"");}#endifif(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);returnret;}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总线驱动中我们就可以找到答案。

7.1 三星S5PV210 i2c适配器的硬件描述

s5pv210处理器内部集成了一个i2c控制器,通过4个主要的寄存器就可以对其进行控制。 在arch/arm/plat-samsung/include/plat/regs-iic.h中列出了这几个寄存器。

#defineS3C2410_IICREG(x)(x)#defineS3C2410_IICCON    S3C2410_IICREG(0x00)//i2c控制寄存器#defineS3C2410_IICSTAT   S3C2410_IICREG(0x04)//i2c状态寄存器#defineS3C2410_IICADD    S3C2410_IICREG(0x08)//i2c地址寄存器#defineS3C2410_IICDS     S3C2410_IICREG(0x0C)//i2c收发数据移位寄存器

i2c寄存器支持收发两种模式,我们主要使用主模式,通过对IICCON、IICDS和IICADD寄存器的操作,可以在i2c总线上产生开始位,停止位,数据和地址,而传输的状态则是通过IICSTAT寄存器获取。

在三星的i2c总线说明文档中给出了i2c总线进行传输的整个流程。

linux-i2c-7.1.png

以通过i2c总线写eeprom为例,具体的流程如下:

  1. 设置GPIO的相关引脚为IIC输出;
  2. 设置IIC(打开ACK,打开IIC中断,设置CLK等);
  3. 设备地址赋给IICDS ,并设置IICSTAT,启动IIC发送设备地址出去;从而找到相应的设备即IIC总线上的设备。
  4. 第一个Byte的设备地址发送后,从EEPROM得到ACK信号,此信号触发中断;
  5. 在中断处理函数中把第二个Byte(设备内地址)发送出去;发送之后,接收到ACK又触发中断;
  6. 中断处理函数把第三个Byte(真正的数据)发送到设备中。
  7. 发送之后同样接收到ACK并触发中断,中断处理函数判断,发现数据传送完毕。
  8. IIC Stop信号,关IIC中断,置位各寄存器。

在下面的小节中将结合代码来分析i2c总线对上面流程的具体实现。

7.2 i2c总线驱动的加载/卸载

i2c总线驱动被作为一个单独的模块加载,下面首先分析它的加载/卸载函数。

staticint__init i2c_adap_s3c_init(void){returnplatform_driver_register(&s3c24xx_i2c_driver);//注册为平台驱动}subsys_initcall(i2c_adap_s3c_init);staticvoid__exit i2c_adap_s3c_exit(void){platform_driver_unregister(&s3c24xx_i2c_driver);}module_exit(i2c_adap_s3c_exit);

三星s5pv210的i2c总线驱动是作为平台驱动来实现的,其中传入的结构体s3c24xxi2cdriver就是platform_driver。

staticstructplatform_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,},};

7.3 i2c总线驱动的probe

i2c总线驱动的probe函数会在一个合适的设备被发现的时候由总线驱动调用。

/* s3c24xx_i2c_probe
*
* called by the bus driver when a suitable device is found
*/staticints3c24xx_i2c_probe(structplatform_device*pdev){structs3c24xx_i2c*i2c;//封装i2c适配器的信息structs3c2410_platform_i2c*pdata;//i2c平台数据structresource*res;//平台资源intret;pdata=pdev->dev.platform_data;//找到平台数据if(!pdata){dev_err(&pdev->dev,"no platform data\n");return-EINVAL;}i2c=kzalloc(sizeof(structs3c24xx_i2c),GFP_KERNEL);//为i2c适配器私有数据结构体分配内存空间,并且初始化为0if(!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;gotoerr_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;gotoerr_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;gotoerr_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;gotoerr_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)gotoerr_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");gotoerr_iomap;}//申请中断,指定了中断处理函数s3c24xx_i2c_irqret=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);gotoerr_iomap;}//动态变频,忽略ret=s3c24xx_i2c_register_cpufreq(i2c);if(ret<0){dev_err(&pdev->dev,"failed to register cpufreq notifier\n");gotoerr_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");gotoerr_cpufreq;}platform_set_drvdata(pdev,i2c);clk_disable(i2c->clk);dev_info(&pdev->dev,"%s: S3C I2C adapter\n",dev_name(&i2c->adap.dev));return0;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);returnret;}

可以看到,i2c24xxi2cprobe()的主要工作有:使能硬件,申请i2c适配器使用的io地址、中断号,然后向i2c核心添加了这个适配器。

s3c24xx_i2c是i2c适配器的私有数据结构体,封装了适配器的所有信息。

structs3c24xx_i2c{spinlock_tlock;//用于防止并发访问的锁wait_queue_head_twait;//等待队列unsignedintsuspended:1;structi2c_msg*msg;//i2c消息unsignedintmsg_num;//i2c消息的数量unsignedintmsg_idx;//当前消息中的一个指针unsignedintmsg_ptr;//消息索引unsignedinttx_setup;//等待数据发送到总线上的一个建立时间unsignedintirq;//中断enums3c24xx_i2c_state  state;//i2c状态unsignedlongclkrate;void__iomem*regs;structclk*clk;structdevice*dev;structresource*ioarea;structi2c_adapter  adap;//i2c_adapter#ifdefCONFIG_CPU_FREQstructnotifier_block   freq_transition;#endif};

初始化i2c控制器函数s3c24xxi2cinit()如下

staticints3c24xx_i2c_init(structs3c24xx_i2c*i2c){unsignedlongiicon=S3C2410_IICCON_IRQEN|S3C2410_IICCON_ACKEN;//中断使能,ACK使能structs3c2410_platform_i2c*pdata;unsignedintfreq;/* 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);return0;}

s3c24xxi2cinit()中完成了前面所说的通过i2c总线写eeprom流程的(1)(2)两步。


在浅谈LinuxI2C驱动架构这一小节中提到了,i2c总线驱动是对I2C硬件体系结构中适配器端的实现,主要是实现了两个结构i2cadapter和i2calgorithm,从而控制i2c适配器产生通讯信号。

在i2c24xxi2cprobe()中就填充了i2cadapter,并且通过i2c->adap.algo = &s3c24xxi2calgorithm给i2cadapter绑定了i2c_algorithm。

其中s3c24xxi2calgorithm为

staticconststructi2c_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(),后面都会详细分析。

7.4 启动i2c传输

接下来就是分析负责启动i2c传输任务的s3c24xxi2cxfer()。

staticints3c24xx_i2c_xfer(structi2c_adapter*adap,structi2c_msg*msgs,intnum){structs3c24xx_i2c*i2c=(structs3c24xx_i2c*)adap->algo_data;//获得i2c适配器私有数据结构intretry;intret;clk_enable(i2c->clk);//使能时钟for(retry=0;retry<adap->retries;retry++){//传输不成功,则重试,retries为重试次数。ret=s3c24xx_i2c_doxfer(i2c,msgs,num);//启动一次i2c传输if(ret!=-EAGAIN)gotoout;dev_dbg(i2c->dev,"Retrying transmission (%d)\n",retry);udelay(100);}ret=-EREMOTEIO;out:clk_disable(i2c->clk);returnret;}

可以看到s3c24xxi2cxfer()是调用了s3c24xxi2cdoxfer()来启动传输的。

staticints3c24xx_i2c_doxfer(structs3c24xx_i2c*i2c,structi2c_msg*msgs,intnum){unsignedlongtimeout;intret;if(i2c->suspended)return-EIO;ret=s3c24xx_i2c_set_master(i2c);//检查i2c总线状态,总线不忙返回0if(ret!=0){dev_err(i2c->dev,"cannot get bus (error %d)\n",ret);ret=-EAGAIN;gotoout;}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_STARTs3c24xx_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()的实现如下

staticints3c24xx_i2c_set_master(structs3c24xx_i2c*i2c){unsignedlongiicstat;inttimeout=400;while(timeout-->0){iicstat=readl(i2c->regs+S3C2410_IICSTAT);//读i2c状态寄存器if(!(iicstat&S3C2410_IICSTAT_BUSBUSY))//总线不忙,则返回0;否则直到超时return0;msleep(1);}writel(iicstat&~S3C2410_IICSTAT_TXRXEN,i2c->regs+S3C2410_IICSTAT);if(!(readl(i2c->regs+S3C2410_IICSTAT)&S3C2410_IICSTAT_BUSBUSY))return0;return-ETIMEDOUT;}

在获知总线不忙后,把要消息写入i2c适配器私有数据结构,并且把状态改为STATESTART。 然后使能中断,通过s3c24xxi2cmessagestart()发送第一个byte,这样在获取ACK后就会触发中断来推进i2c的传输。

staticvoids3c24xx_i2c_message_start(structs3c24xx_i2c*i2c,structi2c_msg*msg){unsignedintaddr=(msg->addr&0x7f)<<1;//从设备地址,7位地址,最低位用来表示读或者写,1为读,0为写。unsignedlongstat;unsignedlongiiccon;stat=0;stat|=S3C2410_IICSTAT_TXRXEN;//使能RxTxif(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);//使能ACKiiccon=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的传输。

7.5 通过中断来推进i2c的传输

发送完第一个byte,收到ACK信号后就会进入中断,并且以后只要收到ACK信号就都会进入中断。中断在probe中已经注册,它的实现 如下

staticirqreturn_ts3c24xx_i2c_irq(intirqno,void*dev_id){structs3c24xx_i2c*i2c=dev_id;unsignedlongstatus;unsignedlongtmp;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);gotoout;}/* 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);//推进传输,传输下一个byteout:returnIRQ_HANDLED;}

i2c总线驱动的中断处理函数s3c24xxi2cirq()是调用i2cs3cirq_nextbyte()来推进i2c的传输的。

staticinti2c_s3c_irq_nextbyte(structs3c24xx_i2c*i2c,unsignedlongiicstat{unsignedlongtmp;unsignedcharbyte;intret=0;switch(i2c->state){//根据i2c的状态选择caseSTATE_IDLE://空闲dev_err(i2c->dev,"%s: called in STATE_IDLE\n",__func__);gotoout;break;caseSTATE_STOP://停止dev_err(i2c->dev,"%s: called in STATE_STOP\n",__func__);s3c24xx_i2c_disable_irq(i2c);//禁止中断gotoout_ack;caseSTATE_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传输gotoout_ack;}if(i2c->msg->flags&I2C_M_RD)//如果当前i2c消息的标志为i2c读i2c->state=STATE_READ;//则修改状态为i2c读elsei2c->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);gotoout_ack;}if(i2c->state==STATE_READ)//如果i2c状态为读,就跳到读,否则,,就会跳到写,,,因为没有breakgotoprepare_read;/* fall through to the write state, as we will need to* send a byte as well */caseSTATE_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);gotoout_ack;}}retry_write://继续写if(!is_msgend(i2c)){//不是一条消息的最后1Bytebyte=i2c->msg->buf[i2c->msg_ptr++];//取出此消息的下一个bytewriteb(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);//延迟,等待数据发送}elseif(!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);//错误,返回}gotoretry_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;caseSTATE_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}elseif(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:returnret;}

i2cs3cirq_nextbyte()推进了i2c的传输,以写eeprom为例,第一个Byte的设备地址发送后,从EEPROM得到ACK信号,此信号触发中断,在中断处理函数中把第二个Byte(设备内地址)发送出去;发送之后,接收到ACK又触发中断,中断处理函数把第三个Byte(真正的数据)发送到设备中,发送之后同样接收到ACK并触发中断,中断处理函数判断,发现数据传送完毕,就发送IIC Stop信号,关IIC中断,置位各寄存器。这样就把通过i2c总线写eeprom的整个流程都实现了。

7.6 总结

i2c总线驱动控制i2c适配器产生通信信号,通过master_xfer()启动一个i2c传输,然后通过中断推进i2c传输。

linux I2C 驱动相关推荐

  1. linux下I2C驱动发送IO时序,Linux I2C 驱动阅读的碰到的一些网上没有提到的东西

    # re: Linux I2C 驱动阅读的碰到的一些网上没有提到的东西  回复  更多评论 2009-04-11 13:39 by 初学都 楼主,你好.我看了你那篇<Linux I2C核心.总线 ...

  2. 【正点原子MP157连载】第四十章 Linux I2C驱动实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  3. Linux I2C驱动框架(超详细)

    Linux I2C驱动框架 文章目录 Linux I2C驱动框架 一.几个重要的对象 1.I2C总线 2.I2C驱动 3.I2C设备 4.I2C设配器 小结 二.内核源码分析 1.注册I2C驱动 2. ...

  4. linux i2c 内核初始io,linux I2C驱动移植

    linux I2C驱动移植 I²C总线仅使用SCL,SDA两根信号线实现设备间的数据交互,被广泛应用于微控制领域芯片与芯片之间的通信,如EEPROM,实时时钟,小型LCD等与CPU之间的通信. I2C ...

  5. 《linux设备驱动开发详解》笔记——15 linux i2c驱动

    <linux设备驱动开发详解>笔记--15 linux i2c驱动 15.1 总体结构 如下图,i2c驱动分为如下几个重要模块 核心层core,完成i2c总线.设备.驱动模型,对用户提供s ...

  6. 【正点原子Linux连载】第六十一章 Linux I2C驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  7. STM32MP157驱动开发——Linux I2C驱动

    相关文章:正点原子教程第四十章--Linux I2C驱动实验 0.前言   为了简化笔记的编写以及降低工作量,本节开始相关的基础知识部分通过引入原子哥的教材链接来完成,有兴趣的可以进入学习.   上一 ...

  8. linux I2C驱动架构解析

    I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL ,使用SCL,SDA这两根信号线就实现了设备之间的数据交互,它方便了工程师的布线. ...

  9. Linux i2c驱动框架分析 (二)

    Linux i2c驱动框架分析 (一) Linux i2c驱动框架分析 (二) Linux i2c驱动框架分析 (三) 通用i2c设备驱动分析 i2c core i2c核心(drivers/i2c/i ...

最新文章

  1. opencv学习笔记(六)---图像梯度
  2. Pytorch详解NLLLoss和CrossEntropyLoss、以及softmax和log_softmax
  3. ejb 2.0 3.0_EJB 3.0注入和查找简介
  4. Java的jdk在win10安装配置环境变量
  5. 系统调用功能模块的初始化
  6. 【2011-2012 ACM-ICPC Northeastern European Regional Contest (NEERC 11) G】GCD Guessing Game【数论思维题】
  7. 计算机考研数据结构用哪本书,计算机考研数据结构该用哪本参考书?
  8. PDF迅捷转换器html网址,迅捷pdf转换成HTML转换器
  9. 稳定同位素示踪技术在内源性物质代谢调控中的应用
  10. android迅雷下载搭建开发环境
  11. STM32+LCD实现简单的贪吃蛇小游戏
  12. 怎么用python表白_如何正确使用Python进行表白
  13. 武汉大学计算机音乐,名家论坛丨预告:聆听音乐
  14. 《计算机网络 自顶向下》第一章==计算机网络和因特网==随堂笔记
  15. java ocx调用_Java调用ocx控件以及dll
  16. 动态规划求解最大子段和
  17. vue实现一个日历切换功能
  18. Allegro如何导入第三方网表操作指导
  19. Django使用pip安装
  20. word2007 表格一点击就变蓝

热门文章

  1. 使用curl创建HTTP请求 Using curl To Make HTTP Requests--用Enki学Linux系列(3)
  2. windows环境下安装RabbitMQ(超详细)
  3. 在Firefox中以电影院风格观看YouTube视频
  4. markdown 目录一键生成和转为 word 格式
  5. 批量识别PDF/OFD/PNG/JPG电子发票到EXCEL
  6. 大厂程序员必备的一套浏览器书签,我帮你整理好了。[下载导入浏览器]
  7. ftp服务器文件复制文件路径,FTP将文件复制到同一FTP的另一个位置
  8. Spring JMS CLIENT_ACKNOWLEDGE
  9. 【离线语音专题②】安信可语音开放平台的使用——VC系列SDK的获取
  10. 抽奖随机滚动_原来抽奖不是凭运气!两个技巧,让你在抽奖环节独占鳌头