9.2 Linux I2C核心、总线与设备驱动

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_driveri2c_client,我们需要根据具体设备实现其中的成员函数。

I2C体系结构在Linux中的实现复杂。

当工程师拿到实际的电路板时,面对复杂的Linux I2C子系统,应该如何下手写驱动呢?究竟有哪些是需要亲自做的,哪些是内核已经提供
的呢?理清这个问题非常有意义,可以使我们在面对具体问题时迅速抓住重点。

  1. 适配器驱动可能是Linux内核本身还不包含的;
  2. 挂接在适配器上的具体设备驱动可能也是Linux内核还不包含的

因此,工程师要实现的主要工作如下。

  1. 提供
  2. I2C适配器的硬件驱动,
  3. 探测、初始化I2C适配器(如申请I2C的I/O地址和中断号)、
  4. 驱动CPU控制的I2C适配器从硬件上产生各种信号以及处理I2C中断等。
  5. 提供
  6. 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和设备地址组成。

注意事项:

  1. 地址的定义
  2. i2c client数据结构是描述1IC设备的“模板” ,驱动程序的设备结构体中应该包含该结构。
  3. adapter指向设备连接的总线适配器,系统中可能有多个总线适配器。内核中静态指针数组adapters记录所有已经注册的总线适配器设备。
  4. 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总线层 和设备层的关系 @@@@@@@@@@@

总线层

  1. struct i2c_adapter            I2C适配器(主机CPU)
  2. struct i2c_algorithm            I2C算法(时序控制)

设备层

  1. struct i2c_client            I2C(从机)设备信息
  2. 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)
  1. 参数idp是之前通过idr init初始化的idr指针,或者DEFINE IDR宏定义的IDR的指针。
  2. 参数ptr是和ID号相关联的指针。
  3. 参数id由内核自动分配的ID号。
  4. 参数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);
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值