I2C总线仅仅使用SCL、 SDA这两根信号线就实现了设备之间的数据交互,极大地简化了对硬件资源和PCB板布线空间的占用。因此, I2C总线非常广泛地应用在EEPROM、实时钟、小型LCD等设备与CPU的接口中
I2C子系统详解###########################
I2C总线汇总概览
(1)三根通信线:SCL、SDA、GND
(2)同步、串行、电平、低速、近距离
(3)总线式结构,支持多个设备挂接在同一条总线上
(4)主从式结构,通信双方必须一个为主(master)一个为从(slave),主设备掌握每次通信的主动权,从设备按照主设备的节奏被动响应。每个从设备在总线中有唯一的地址(slave address),主设备通过从地址找到自己要通信的从设备(本质是广播)。
(5)I2C主要用途就是主SoC和外围设备之间的通信,最大优势是可以在总线上扩展多个外围设备的支持。常见的各种物联网传感器芯片(如gsensor、温度、湿度、光强度、酸碱度、烟雾浓度、压力等)均使用I2C接口和主SoC进行连接。
(6)电容触摸屏芯片的多个引脚构成2个接口。一个接口是I2C的,负责和主SoC连接(本身作为从设备),主SoC通过该接口初始化及控制电容触摸屏芯片、芯片通过该接口向SoC汇报触摸事件的信息(触摸坐标等),我们使用电容触摸屏时重点关注的是这个接口;另一个接口是电容触摸板的管理接口,电容触摸屏芯片通过该接口来控制触摸板硬件。该接口是电容触摸屏公司关心的,他们的触摸屏芯片内部固件编程要处理这部分,我们使用电容触摸屏的人并不关心这里。
linux内核的I2C驱动框架总览
(1)I2C驱动框架的主要目标是:让驱动开发者可以在内核中方便的添加自己的I2C设备的驱动程序,从而可以更容易的在linux下驱动自己的I2C接口硬件
(2)源码中I2C相关的驱动均位于:drivers/i2c目录下。
linux系统提供2种I2C驱动实现方法:
第一种叫i2c-dev,对应drivers/i2c/i2c-dev.c,这种方法只是封装了主机(I2C master,一般是SoC中内置的I2C控制器)的I2C基本操作,并且向应用层提供相应的操作接口,应用层代码需要自己去实现对slave的控制和操作,所以这种I2C驱动相当于只是提供给应用层可以访问slave硬件设备的接口,本身并未对硬件做任何操作,应用需要实现对硬件的操作,因此写应用的人必须对硬件非常了解,其实相当于传统的驱动中干的活儿丢给应用去做了,所以这种I2C驱动又叫做“应用层驱动”,这种方式并不主流,它的优势是把差异化都放在应用中,这样在设备比较难缠(尤其是slave是非标准I2C时)时不用动驱动,而只需要修改应用就可以实现对各种设备的驱动。这种驱动在驱动层很简单(就是i2c-dev.c)我们就不分析了。
第二种I2C驱动是所有的代码都放在驱动层实现,直接向应用层提供最终结果。应用层甚至不需要知道这里面有I2C存在,譬如电容式触摸屏驱动,直接向应用层提供/dev/input/event1的操作接口,应用层编程的人根本不知道event1中涉及到了I2C。这种是我们后续分析的重点。
I2C子系统的4个关键结构体
(1)struct i2c_adapter I2C适配器(主机)
(2)struct i2c_algorithm I2C算法(时序控制)
(3)struct i2c_client I2C(从机)设备信息
(4)struct i2c_driver I2C(从机)设备驱动
i2c-core.c初步分析
(1)smbus代码略过
(2)模块加载和卸载:bus_register(&i2c_bus_type);
I2C总线的匹配机制
(1)match函数
(2)probe函数
总结:I2C总线上有2条分支:i2c_client链和i2c_driver链,当任何一个driver或者client去注册时,I2C总线都会调用match函数去对client.name和driver.id_table.name进行循环匹配。如果driver.id_table中所有的id都匹配不上则说明client并没有找到一个对应的driver,没了;如果匹配上了则标明client和driver是适用的,那么I2C总线会调用自身的probe函数,自身的probe函数又会调用driver中提供的probe函数,driver中的probe函数会对设备进行硬件初始化和后续工作。
核心层开放给其他部分的注册接口
(1)i2c_add_adapter/i2c_add_numbered_adapter 注册adapter的
(2)i2c_add_driver 注册driver的
(3)i2c_new_device 注册client的
Linux的I2C体系结构分为3个组成部分。
(1) I2C核心
I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法, I2C通信方法(即Algorithm)上层的与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等,如图15.1所示。
(2) I2C总线驱动
I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。I2C总线驱动主要包含I2C适配器数据结构i2c_adapter、 I2C适配器的Algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。
经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。
(3) I2C设备驱动
I2C设备驱动(也称为客户驱动)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。I2C设备驱动主要包含数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。
I2C体系结构在Linux中的实现复杂。
当工程师拿到实际的电路板时,面对复杂的Linux I2C子系统,应该如何下手写驱动呢?究竟有哪些是需要亲自做的,哪些是内核已经提供
的呢?理清这个问题非常有意义,可以使我们在面对具体问题时迅速抓住重点。
- 适配器驱动可能是Linux内核本身还不包含的;
- 挂接在适配器上的具体设备驱动可能也是Linux内核还不包含的
因此,工程师要实现的主要工作如下。
- 提供
- I2C适配器的硬件驱动,
- 探测、初始化I2C适配器(如申请I2C的I/O地址和中断号)、
- 驱动CPU控制的I2C适配器从硬件上产生各种信号以及处理I2C中断等。
- 提供
- I2C适配器的Algorithm,用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋值给i2c_adapter的algo指针。
总结:
不管是平台总线还是IIC总线都都有这样的调用路线:
当系统发现了新设备或者新驱动就会掉用相应总线的Match()进行匹配,当找到后就会掉用相对应的总线的Probe函数,最后Probe函数再调用驱动自己的Probe函数
IIC总线是先调用自己核心的Probe函数,再调用驱动的Probe函数
如下:
//platform 总线
int platform_driver_register(struct platform_driver *drv)
{
if (drv->probe)
drv->driver.probe = platform_drv_probe;
return driver_register(&drv->driver);
}
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return drv->probe(dev);
}
// IIC总线
static int i2c_device_probe(struct device *dev)
{
status = driver->probe(client, i2c_match_id(driver->id_table, client));
}
1 重要结构体 ###############
1.1 设备层 @@@@@@@@@@@
1.1.1 IIC设备(i2c_client)---------------
由IIC总线规范可知, IIC总线由两条物理线路组成,这两条物理线路是SDA和SCL。只要连接到SDA和SCL总线上的设备都可以叫做IIC设备。一个IIC设备由i2c client数据结构进行描述。
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;
};
设备地址(注意每种设备的设备地址定义可能不同)
i2c_client中的addr的低8位表示设备地址。设备地址由读写位、器件类型和自定义地址组成,如图所示。
以下是AT24C08芯片的定义
第7位是R/w位,"0”表示写,“1”表示读(通常读写信号中写上面有一横线,表示低电平) ,所以I2C设备通常有两个地址,即读地址和写地址。
类型器件由中间4位组成,这是由半导公司生产时就已固化的了,也就是说这4位已是固定的。
自定义地址码由低3位组成。这是由用户自己设置的,通常的作法如EEPROM这些器件是由外部1芯片的3个引脚所组合电平决定的(用常用的名字如(A0,Al、 A2) 。A0,A1,A2就是自定义地址码。自定义地址码只能表示8个地址,所以同一IIC总线上同一型号的芯片最多只能挂接8个。AT24C08的自定义地址码如图所示,A0, Al和A2接低电平,所以自定义地址码为0.
如果在两条不同的IIC总线上挂接了两块类型和地址相同的芯片,那么这两块芯片的,地址相同。这显然是地址冲突的,解决的办法是为总线适配器指定一个ID号,那么新的芯片地址就由总线适配器的ID和设备地址组成。
注意事项:
- 地址的定义
- i2c client数据结构是描述1IC设备的“模板” ,驱动程序的设备结构体中应该包含该结构。
- adapter指向设备连接的总线适配器,系统中可能有多个总线适配器。内核中静态指针数组adapters记录所有已经注册的总线适配器设备。
- driver是指向设备对应的驱动,这个驱动程序是在系统检测到设备存在时赋值的。
1.1.2 IIC设备驱动(i2c_driver)----------------
每一个IIC设备都应该对应一个驱动,也就是每一个i2c client结构都应该对应一个 i2c driver结构。它们之间通过指针相互连接。i2c driver结构体的代码如下:
struct i2c_driver {
int id; //驱动id
unsigned int class; //驱动类型
int (*attach_adapter)(struct i2c_adapter *);//当检测到适配器调用的函数
int (*detach_adapter)(struct i2c_adapter *); //卸载适配器的函数
/* 以下是新类型驱动需要的函数 */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);//新型设备的探测函数
int (*remove)(struct i2c_client *);//移除函数
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *); //关闭IIC设备
int (*suspend)(struct i2c_client *, pm_message_t mesg);//挂起IIC设备
int (*resume)(struct i2c_client *);//恢复设备
void (*alert)(struct i2c_client *, unsigned int data);
//使用命令使设备完成特殊的功能,类似ioctl函数
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver; //设备驱动结构体
const struct i2c_device_id *id_table; //设备ID表
/* 设备检测回调,用于自动创建设备 */
int (*detect)(struct i2c_client *, struct i2c_board_info *);//自动探测设备的回调函数
const unsigned short *address_list;
struct list_head clients;//指向支持的设备
};
第08~12行,定义了新类型的驱动程序函数,这些函数支持IIC设备的动态插入和拔出。如果IIC设备不可以动态插入和拔出,那么就应该实现第04~06行的驱动函数。注意要么只定义04~06行的传统函数,要么只定义08~12行的新类型设备的驱动函数。如果同时定义这些函数,将出现"i2c-core: driver driver-name is confused"的警告。
第14行,类似于字符设备的ioctl()函数,用来控制设备的状态。
第15行,是IIC设备内嵌的设备驱动结果体。
第16行,是一个设备ID表,表示这个设备驱动程序支持哪些设备。
第17行, detect()是自动探测设备的回调函数,这个函数一般不会执行。
第18行,表示设备映射到虚拟内存的地址范围。
第19行,使用一个list head类型的clients链表连接这个驱动支持的所有IIC设备。
1.1.3 设备和驱动的关系
1.2 IIC总线层 @@@@@@@@@@@
1.2.1 IIC总线适配器(i2c_adapter)-----------
struct i2c_adapter {
struct module *owner; //模块计数
unsigned int id; //alogorithm的类型,定义与i2c_id.h中
unsigned int class; /* 允许探测的驱动的类型 */
const struct i2c_algorithm *algo; /* 指向适配器的驱动程序 */
void *algo_data; //指向适配器的私有数据,根据不同情况使用的方法不同
/* data fields that are valid for all devices */
struct rt_mutex bus_lock;
int timeout; /* 超时 */
int retries; //重试次数
struct device dev; /* 指向适配器设备结构体 */
int nr;
char name[48]; //名字
struct completion dev_released; //用于同步的完成量
struct list_head userspace_clients;//链接总线上设备的链表
};
第05行,定义了一个i2c algorithm结构体。一个1IC适配器上的1IC总线通信方法由其驱动程序i2c algorithm结构体描述,该结构体由algo指针指向。对于不同的适配器有不同的i2c algorithm结构体。
第12、13行, timeout和retries用于超时重传,总线传输数据并不是每次都成功,所以需要超时重传机制。
第16行的clients连接该适配器上的所有IIC设备(i2c client) 。
clist lock互斥锁用于实现对1IC总线的互斥访问:在访问1IC总线上的任一设备期间当前进程必须首先获得该信号量。
第18行,定义了一个完成量用于表示适配器是否在被其他进程使用。
1.2.2 IIC总线驱动程序(i2c_algorithm)-----------
struct i2c_algorithm {
//传输函数
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
//smbus方式传输函数指针
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 *); //返回适配器支持的功能
};
第03行,定义了一个master xfer()函数,其指向实现IIC总线通信协议的函数。
第05行,定义了一个smbus xfer()函数,其指向实现SMBus总线通信协议的函数。SMBus协议基于1IC协议的原理,也是由2条总线组成(1个时钟线,1个数据线)。SMBus和1IC之间可以通过软件方式兼容,所以这里提供了一个函数,但是一般都赋值为NULL,没有使用,这里可以忽略。
第08行定义了一个functionality ()函数,主要用来确定适配器支持哪些传输类型。
1.3 IIC总线层 和设备层的关系 @@@@@@@@@@@
总线层
- struct i2c_adapter I2C适配器(主机CPU)
- struct i2c_algorithm I2C算法(时序控制)
设备层
- struct i2c_client I2C(从机)设备信息
- struct i2c_driver I2C(从机)设备驱动
1.4 IIC设备驱动开发步骤 @@@@@@@@@@@
2 IIC子系统的初始化 ###############
在启动系统时,需要对1IC子系统进行初始化。这些初始化函数包含在i2c-core.c文件中。该文件中包含1IC子系统中的公用代码,驱动开发人员只需要用它,而不需要修改它。下面对这些公用代码的主要部分进行介绍。
IIC初始化:
static int __init i2c_init(void)
{
int retval;
retval = bus_register(&i2c_bus_type); //注册一条“i2c”总线
if (retval)
return retval;
retval = i2c_add_driver(&dummy_driver);//将一个空驱动注册到IIC总线中
if (retval)
goto class_err;
return 0;
class_err:
bus_unregister(&i2c_bus_type); //总线销毁
return retval;
}
第04行,调用设备模型中的bus register()函数在系统中注册一条新的总线,该总线的名称是i2c。适配器设备、IIC设备和IIC设备驱动程序都会连接到这条总线上。
第10行,调用i2c add driver()函数向i2c总线注册一个空的IIC设备驱动程序,用于特殊用途。驱动开发人员不用关心该空驱动程序。
第14-17行,用来错误处理。
IIC退出:
static void __exit i2c_exit(void)
{
i2c_del_driver(&dummy_driver);//去掉注销iic设备驱动程序
bus_unregister(&i2c_bus_type);//注销IIC总线
}
5 适配器驱动程序 ###################
适配器驱动程序是1IC设备驱动程序需要实现的主要驱动程序,这个驱动程序需要根据具体的适配器硬件来编写,本节将对适配器驱动程序进行详细的讲解。
1 适配器结构体 @@@@@@@@@
i2c adapter结构体为描述各种1IC适配器提供了通用“模板” ,它定义了注册总线上所有设备的clients链表、指向具体1IC适配器的总线通信方法i2c algorithm的algo指针、实现i2c总线操作原子性的lock信号量。但i2c adapter结构体只是所有适配器的共有属性,并不能代表所有类型的适配器。
struct s3c24xx_i2c {
spinlock_t lock;
wait_queue_head_t wait;
unsigned int suspended:1; //表示设备是否挂起,只有一位确定
struct i2c_msg *msg;
unsigned int msg_num;
unsigned int msg_idx;
unsigned int msg_ptr;
unsigned int tx_setup;
unsigned int irq; //设配器的中断号
enum s3c24xx_i2c_state state;
unsigned long clkrate;
void __iomem *regs;
struct clk *clk; //对应的时钟
struct device *dev; //适配器对应的设备结构体
struct resource *ioarea; //适配器对应的资源
struct i2c_adapter adap; //适配器主体结构体
};
第02行的lock自旋锁。
第03行的wait表示等待队列头。由于IIC设备是低速设备,所以可以采用“阻寒,一中断”的驱动模型,即读写i2c设备的用户进程在1IC设备操作期间进入阻塞状,态,待1IC操作完成后总线适配器将引发中断,再相应地在中断处理程序中唤醒受,阻的用户进程。所以在s3c24xx i2c结构体中设计了等待队列首部wait成员,用来将阻塞的进程放入等待队列中。
第05行的i2c msg表示从适配器到设备一次传输的单位,用这个结构体将数据包装起来便于操作,在后面的内容中将详细说明。
第06行的msg num表示消息的个数。
第07行的msg idx表示第几个消息。当完成一个消息后,该值增加。
第08行的msg-ptr总是指向当前交互中要传送、接收的下一个字节,在i2c msg.buf中的偏移位置。
第09行,表示写IIC设备寄存器的一个时间,这里被设置成50毫秒。
第10行,代表IIC设备申请的中断号。
第11行,表示1IC设备目前的状态。这个状态结构体s3c24xx i2c state将在下文详细讲述。
第12行,表示时钟速率。
第13行,表示IIC设备的寄存器地址。
第14行,表示IIC设备对应的时钟。
第16行, ioarea指针指向了适配器申请的资源。
第17行, adap表示内嵌的适配器结构体。
IIC消息
s3c24xx i2c适配器结构体中有一个i2c msg的消息指针。该结构体是从适配器到1C设备传输数据的基本单位,代码如下:
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RD 0x0001 /* 从从机到主机读数据 */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400 /* 第一次接收的字节长度 */
__u16 len; /* 消息字节长度 */
__u8 *buf; /* 指向消息字节缓冲区 */
};
其中addr为IIC设备的地址。这个字段说明一个适配器在获得总线控制权后,可以与多个1IC设备进行交互。
buf指向与IC设备交互的数据缓冲区,其长度为len.
flags中的,标志位描述该消息的属性,这些属性由一系列12C M *宏表示。
2 适配器加载函数 @@@@@@@@@
当驱动开发人员拿到一块新的电路板,并研究了响应的IIC适配器之后,就应该使用内核提供的框架函数向1IC子系统中添加一个新的适配器。
这个过程如下所示:
(1)分配一个1IC适配器,并初始化相应的变量。
(2)使用i2c add adapter()函数向IIC子系统添加适配器结构体i2c adapter,这个结构体已经在第一步初始化了。
i2c add adapter()函数的代码如下:
int i2c_add_adapter(struct i2c_adapter *adapter)
{
int id, res = 0;
retry:
if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
return -ENOMEM;//分配内存失败
mutex_lock(&core_lock);//上锁
/* "above" here means "above or equal to", sigh */
res = idr_get_new_above(&i2c_adapter_idr, adapter,
__i2c_first_dynamic_bus_num, &id);//id分配
mutex_unlock(&core_lock);//解锁
if (res < 0) {
if (res == -EAGAIN)
goto retry;
return res;
}
adapter->nr = id;
return i2c_register_adapter(adapter);//注册适配器设备
}
该代码的第17行是向内核注册一个适配器设备。第03~16行涉及一个陌生的IDR机制,由于IDR机制较为复杂,下面单独列一节内容加以说明。当了解了IDR机制后,将对 i2c-add -adapte()函数进行进一步解释。
3 IDR机制 @@@@@@@@@
IDR机制在Linux内核中指的就是整数ID管理机制。从实质上来讲,这就是一种将一个整数ID号和一个指针关联在一起的机制。
3.1 IDR机制原理 ------------
IDR机制适用在那些需要把某个整数和特定指针关联在一起的地方。例如,在1IC总线中,每个设备都有自己的地址,要想在总线上找到特定的设备,就必须要先发送该设备的地址。当适配器要访问总线上的IIC设备时,首先要知道它们的ID号,同时要在内核中建立一个用于描述该设备的结构体和驱动程序。
怎么才能将该设备的ID号和它的设备结构体联系起来呢?
数组:进行索引,但如果ID号的范围很大(比如32位的ID号) ,则用数组索引会占据大量的内存空间,这显然不可能
链表:但如果总线中实际存在的设备较多,则链表的查询效率会很低。
这种情况下,就可以采用IDR机制,该机制内部采用红黑树(radix,类似于二分数)实现,可以很方便地将整数和指针关联起来,并且具有很高的搜索效率。IDR机制的主要代码在/include/linux/idr.h实现,下面对其主要函数进行说明。
3.2 结构体
struct idr {
struct idr_layer *top;
struct idr_layer *id_free;
int layers; /* only valid without concurrent changes */
int id_free_cnt;
spinlock_t lock;
};
#define IDR_INIT(name) \
{ \
.top = NULL, \
.id_free = NULL, \
.layers = 0, \
.id_free_cnt = 0, \
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
}
#define DEFINE_IDR(name) struct idr name = IDR_INIT(name) //定义一个idr结构体
3.3 初始化
void idr_init(struct idr *idp)
{
memset(idp, 0, sizeof(struct idr)); //初始化为0
spin_lock_init(&idp->lock); //初始化自旋锁
}
3.4 分配内存存放ID号的内存
每次通过IDR获得ID号之前,需要为ID号先分配内存。分配内存的函数是, idr_preget()。
成功1 ,错误0
int idr_pre_get(struct idr *idp, gfp_t gfp_mask)
{
while (idp->id_free_cnt < IDR_FREE_MAX) {
struct idr_layer *new;
new = kmem_cache_zalloc(idr_layer_cache, gfp_mask);
if (new == NULL)
return (0);
move_to_free_list(idp, new);
}
return 1;
}
该函数的第一个参数是指向IDR结构体的指针:第二个参数是内存分配标志,与 kmalloc()函数的标志相同。
3.5 分配ID号并将ID号和指针关联
int idr_get_new(struct idr *idp, void *ptr, int *id)
int idr_get_new_above(struct idr *idp, void *ptr, int starting_id, int *id)
- 参数idp是之前通过idr init初始化的idr指针,或者DEFINE IDR宏定义的IDR的指针。
- 参数ptr是和ID号相关联的指针。
- 参数id由内核自动分配的ID号。
- 参数start id是起始ID号。
内核在分配ID号时,会从start id开始。函数调用成功时返回0,如果没有ID可以分配,则返回负数,
3.6 通过ID号查询对应的指针
如果知道了ID号,需要查询对应的指针,可以使用idr find()函数。
void *idr_find(struct idr *idp, int id)
参数idp是之前通过idr init初始化的IDR指针,或者DEFINE IDR宏定义IDR的指针。
参数id是要查询的ID号。如果成功返回,则给定ID相关联的指针,如果没有,则返回NULL.
3.7 删除ID
void idr_remove(struct idr *idp, int id) //删除一个
void idr_remove_all(struct idr *idp) //删除所有
3.8 通过ID获得适配器指针
struct i2c_adapter *i2c_get_adapter(int id)
{
struct i2c_adapter *adapter;//适配器指针
mutex_lock(&core_lock);
adapter = idr_find(&i2c_adapter_idr, id);//查询适配器
if (adapter && !try_module_get(adapter->owner))
adapter = NULL;//适配器模块引用加一
mutex_unlock(&core_lock);
return adapter;
}
3.9 实例代码
retry:
if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
return -ENOMEM;//分配内存失败
mutex_lock(&core_lock);//上锁
/* 为适配器分配ID号,__i2c_first_dynamic_bus_num是动态分配的最小值 */
res = idr_get_new_above(&i2c_adapter_idr, adapter,
__i2c_first_dynamic_bus_num, &id);//id分配
mutex_unlock(&core_lock);//解锁
if (res < 0) {
if (res == -EAGAIN)
goto retry;
return res;
}
4 适配器卸载函数 @@@@@@@@@
i2c del adapter()函数用于注销适配器的数据结构,删除其总线上所有设备的i2c client数据结构和对应的i2c driver驱动程序,并减少其代表总线上所有设:备的相应驱动程序数据结构的引用计数(如果到达0,则卸载设备驱动程序)。该函数的原型如下:
int i2c_del_adapter(struct i2c_adapter *adap)
5 IIC总线通信方法s3c24xx_i2c_algorithm @@@@@@@@@
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
消息怎样发送的?
注意:主机只负责前面的开始为S和地址,以后的消息处理由中断函数进行,也就是传输数据要靠中断
s3c24xx_i2c_xfer
s3c24xx_i2c_doxfer
s3c24xx_i2c_message_start//开始发送
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
//协议支持函数
static u32 s3c24xx_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
}
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num) //传输函数,主要使调用这个函数开始发送信息
{
/**/s3c24xx_i2c_doxfer(i2c, msgs, num); //传输到IIC设备的具体函数
}
static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
struct i2c_msg *msgs, int num)
{
ret = s3c24xx_i2c_set_master(i2c);//将适配器设置为主机状态
if (ret != 0) {//总线忙,要重试
dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);
ret = -EAGAIN;
goto out;
}
/* 锁上后,每次允许一个进程传输数据,其他进程无法获得总线 */
spin_lock_irq(&i2c->lock);
i2c->msg = msgs; //传输的消息
i2c->msg_num = num; //消息个数
i2c->msg_ptr = 0; //当前要传输的字节在消息中的偏移
i2c->msg_idx = 0; //消息数组的索引
i2c->state = STATE_START;
s3c24xx_i2c_enable_irq(i2c);//中断
/**/s3c24xx_i2c_message_start(i2c, msgs); //开始传输数据
spin_unlock_irq(&i2c->lock);
timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);//等待5秒
}
static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
struct i2c_msg *msg)
{
unsigned int addr = (msg->addr & 0x7f) << 1;//取第七位,并左移
unsigned long stat;//缓存IICSTAT 寄存器的值
unsigned long iiccon; //缓存IICCON 寄存器的值
stat = 0;
stat |= S3C2410_IICSTAT_TXRXEN;//使能接收和发送功能,使适配器能发送数据
if (msg->flags & I2C_M_RD) {//判断是读还是写
stat |= S3C2410_IICSTAT_MASTER_RX;//将适配器设置为主机接收
addr |= 1;//最低位 写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);//写
dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);
writeb(addr, i2c->regs + S3C2410_IICDS);//写地址到地址寄存器
/* 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);//发送 "S" 开始信号
//到这里就开始发送消息了
}
6 中断处理函数 @@@@@@@@@@
顺着通信函数s3c24xx i2c xfer()的执行流分析,函数最终会返回,但并没有传输数据。传输数据的过程被交到了中断处理函数中。这是因为IIC设备的读写是非常慢的,需要使用中断的方法提高处理器的效率
6.1 数据通信方法的调用关系 ----------
驱动端:通过调用下面这个函数开始了通信
static u32 gsl_write_interface(struct i2c_client *client, const u8 reg, u8 *buf, u32 num)
{
return i2c_transfer(client->adapter, xfer_msg, 1) == 1 ? 0 : -EFAULT;
}
适配器端:
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
ret = adap->algo->master_xfer(adap, msgs, num);
}
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
(1)传输数据时,调用s3c24xx i2c algorithm结构体中的数据传输函数s3c24xx_i2c_xfer
(2) s3c24xx i2c xfer()中会调用s3c24xx i2c doxfer()进行数据的传输。
(3) s3c24xx i2c doxfer()中向总线发送 IIC设备地址 和 开始信号s 后,便会调用 wait event timeout()函数进入等待状态。
设备端:
(4)将数据准备好发送时,将产生中断,并调用事先注册的中断处理函数s3c24xx i2c irq()
(5) s3c24xx i2c irg()调用下一个字节传输函数i2ss3c irq nextbyte()来传输数据。
(6)当数据传输完成后,会调用s3c24xx i2c stop()
(7)最后调用wake up()唤醒等待队列,完成数据的传输过程。
当s3c2440的IIC适配器处于主机模式时, IIC操作的第一步总是向1IC总线写入设备,的地址及开始信号,这步由上面介绍的函数s3c24xx i2c set master()和 s3c24xx i2c message start()完成。而收发数据的后继操作都是在1IC中断处理函数ts3c24xxi2c irq()中完成的。
5. linux内核的I2C子系统详解##########################
5.1、i2c_driver的注册
(1)以gslX680的驱动为例
gslX680的驱动没啥要注意的,就是一些IIC的驱动注册,但是如果要深入就是触摸屏的知识,这里不想研究
主要注意:driver name和probe
5.2、i2c_client从哪里来
(1)直接来源:i2c_register_board_info
smdkc110_machine_init (mach-x210.c)
i2c_register_board_info
list_add_tail(&devinfo->list, &__i2c_board_list); //添加到内核的IIC设备链表中
i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));
i2c_register_board_info(2, i2c_devs2, ARRAY_SIZE(i2c_devs2));
struct i2c_board_info {
char type[I2C_NAME_SIZE]; // 设备名
unsigned short flags; // 属性
unsigned short addr; // 设备从地址
void *platform_data; // 设备私有数据
struct dev_archdata *archdata;
#ifdef CONFIG_OF
struct device_node *of_node;
#endif
int irq; // 设备使用的IRQ号,对应CPU的EINT
};
// mach-x210.c 1654行
static struct i2c_board_info i2c_devs1[] __initdata = {
#ifdef CONFIG_VIDEO_TV20
{
I2C_BOARD_INFO("s5p_ddc", (0x74>>1)),
},
#endif
#ifdef CONFIG_TOUCHSCREEN_GSLX680
{
I2C_BOARD_INFO("gslX680", 0x40),
},
#endif
};
(2)实现原理分析
内核维护一个链表 __i2c_board_list,这个链表上链接的是I2C总线上挂接的所有硬件设备的信息结构体。也就是说这个链表维护的是一个struct i2c_board_info结构体链表。
真正的需要的struct i2c_client在别的地方由__i2c_board_list链表中的各个节点内容来另外构建生成。
函数调用层次:
s3c24xx_i2c_probe
i2c_add_adapter/i2c_add_numbered_adapter
i2c_register_adapter
i2c_scan_static_board_info
list_for_each_entry(devinfo, &__i2c_board_list, list) {
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info))
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
device_register
总结:I2C总线的i2c_client的提供是内核通过i2c_add_adapter/i2c_add_numbered_adapter接口调用时自动生成的,生成的原料是mach-x210.c中的i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));
至于s3c2440-i2c适配器的平台设备在哪注册的有下面可以得到,s5pv210_map_io的调用路线不想了解
void __init s5pv210_map_io(void)
{
/* initialise device information early */
s5pv210_default_sdhci0();
s5pv210_default_sdhci1();
s5pv210_default_sdhci2();
/* the i2c devices are directly compatible with s3c2440 */
s3c_i2c0_setname("s3c2440-i2c");
s3c_i2c1_setname("s3c2440-i2c");
s3c_i2c2_setname("s3c2440-i2c");
}
static inline void s3c_i2c0_setname(char *name)
{
s3c_device_i2c0.name = name;
}
static inline void s3c_i2c1_setname(char *name)
{
s3c_device_i2c1.name = name;
}
static inline void s3c_i2c2_setname(char *name)
{
s3c_device_i2c2.name = name;
}
/* dev-i2c0.c ********/
struct platform_device s3c_device_i2c0 = {
.name = "s3c2410-i2c",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
/* dev-i2c1.c ********/
struct platform_device s3c_device_i2c1 = {
.name = "s3c2410-i2c",
.id = 1,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
/* dev-i2c3.c ********/
struct platform_device s3c_device_i2c2 = {
.name = "s3c2410-i2c",
.id = 2,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
这样就得到了IIC设备的注册线路:
platform_driver s3c24xx_i2c_driver 和 platform_device s3c_device_i2c0的注册调用了s3c24xx_i2c_probe
s3c24xx_i2c_probe的一些列步骤生成了IIC 设备
而IIC驱动,它自己调用i2c_add_driver(&gsl_ts_driver);进行生成
6.2 主要函数 --------------
6.3 辅助函数 -----------
判断当前处理的消息是否为最后一个消息
static inline int is_lastmsg(struct s3c24xx_i2c *i2c)
{
return i2c->msg_idx >= (i2c->msg_num - 1);
}
如果这是当前消息中的最后一个字节,则返回TRUE
static inline int is_msglast(struct s3c24xx_i2c *i2c)
{
return i2c->msg_ptr == i2c->msg->len-1;
}
用来判断当前消息是否已经传输完所有字节
static inline int is_msgend(struct s3c24xx_i2c *i2c)
{
return i2c->msg_ptr >= i2c->msg->len;
}
开启应答和禁止应当
static inline void s3c24xx_i2c_disable_ack(struct s3c24xx_i2c *i2c)
{
unsigned long tmp;
tmp = readl(i2c->regs + S3C2410_IICCON);
writel(tmp & ~S3C2410_IICCON_ACKEN, i2c->regs + S3C2410_IICCON);
}
static inline void s3c24xx_i2c_enable_ack(struct s3c24xx_i2c *i2c)
{
unsigned long tmp;
tmp = readl(i2c->regs + S3C2410_IICCON);
writel(tmp | S3C2410_IICCON_ACKEN, i2c->regs + S3C2410_IICCON);
}