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

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

 

I2C总线仅仅使用SCL、 SDA这两根信号线就实现了设备之间的数据交互,极大地简化了对硬件资源和PCB板布线空间的占用。因此, I2C总线非常广泛地应用在EEPROM、实时钟、小型LCD等设备与CPU的接口中
 

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);
}				  

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 数据通信方法的调用关系   ----------

(1)传输数据时,调用s3c24xx i2c algorithm结构体中的数据传输函数s3c24xx_i2c_xfer

(2) s3c24xx i2c xfer()中会调用s3c24xx i2c doxfer()进行数据的传输。

(3) s3c24xx i2c doxfer()中向总线发送1IC设备地址和开始信号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()中完成的。

 

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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值