Linux驱动开发(12)------- IIC子系统


一,IIC协议简介


【1】IIC( Intel-Integrated Circuit)是由飞利浦(现在叫恩智浦)公司开发的一种慢速两线制总线协议。

【2】最初总线的速率定为100KHz,经过发展,速率出现了400KHz、34MHz、1MHz和5MHz的不同等级,目前大多数器件都支持400KHz。不 过速率是可变的,比如一个支持400KHz的器件,完全可以工作在299KHz这个速率上,只要不超过400KHz即可。

【3】I2C总线为那些不需要经常访问、低带宽的设备提供了一种廉价的互联方式,被广泛地应用在嵌入式系统设备当中。

【4】不过12C总线协议有商标上的一些限制,有些厂家为了避免这种限制,使用了另外一种总线协议 SMBus( System Management Bus)。 SMBus总线协议其实是2C总线协议的一个子集,绝大多数12C设备都能够在 SMBus总线上工作。现在的计算机主板通常使用 SMBus总线,最常见的就是内存条上的 EEPROM配置芯片。

【5】I2C总线由SCL(串行时钟线)和SDA(串行数据线)两条线组成,其上连接有主机控制器( Master,在 Linux驱动中称为 Adapter)和从设备( Slave,在 Linux驱动中称为 Client)。

【6】所有的访问操作都是由主机控制器发起的,在同一条总线上可以有多个主机控制器,当多个主机控制器同时对设备发起访问时,由协议的冲突检测和仲裁机制来保证只有一个主机控制器对从设备进行访间。

【7】多个从设备是通过从设备的地址来区分的。地址分为7位地址和10位地址两种,常见的是7位地址。

在这里插入图片描述

二,IIC时序分析

【1】IIC时序图
在这里插入图片描述

【2】开始位:

  • 当SCL为高电平时,SDA由高电平变为低电平的期间,如上图中最左边的(START condition)所标记的区域,这表示主机控制器要开始对从机发起访问了。

【3】地址位:

  • 接下来的7个时钟周期,主机控制器将会发送从机的7位地址(如果是10位地址需要分两次发送),如如上图中 (ADDRESS )所标记的区域。

【3】读/写位:

  • 在第8个时钟周期,如果SDA为高电平则表示接下来要读取从机的数据,如果是低电平则表示主机要写数据到从机,如如上图中(R/W)所标记的区域。

【4】应答位:

  • 在第9个时钟周期由从机进行应答,低电平为ACK,高电平为NACK,如果从机响应,应该发ACK。

【5】数据位:

  • 在接下来的若干个周期内,主机可以持续读取数据(如果读/写位为读),或写数据(如果读/写位为写),每次数据传输完成(如如上图中的 DATA 所标记的区域)也要进行应答,是读则由主机控制器来应答,是写则由从机来应答,只是在主机读完最后一个字节的数据后应该以NACK来应答。

【6】停止位:
当SCL为高电平时,SDA由低电平变为高电平的期间,如上图中最边的(STOP condition)所标记的区域,这表示主机控制器结束了对从机的访问。

【7】I2C从设备内部通常有若干个寄存器,每个寄存器都有一个地址,对这些从设备的访问通常是顺序访问的。比如上次从寄存器2开始连续访问了4个寄存器,那么下次访问将从寄存器6开始。不过按照下表的时序,可以实现随机的读和写访问。

  • 随机写访问
MasterSAD+WRADATADATAP
SlaveACKACKACKACK
  • 随机写访问是在主机发送开始信号(S),主机继续发送完从机地址(AD)和写标志(W)后,从机应答,主机继续写寄存器地址(RA)。当从机对写入的寄存器地址进行应答后,主机才写入真正的内容。

  • 随机读访问

MasterSAD+WRASAD+RACKNACKP
SlaveACKACKACKDATADATA
  • 对随机读访问则要复杂一点,在寄存器地址写入,从机应答后,主机需要考虑再次发送发送开始位,然后发送从地址和读取标志位,之后才是读操作。

三,Linux的IIC子系统框架


【1】IIC子系统框图

在这里插入图片描述

  • I2C主机驱动:I2C主机控制器的驱动,一般由SoC芯片厂商负责设计实现,用于控制II2C主机控制器发出时序信号。
  • I2C Core:为上层提供统一的API接口和对其他模块进行注册和注销等管理等。
  • I2C Core为屏蔽不同的12C主机控制器驱动提供了可能,可以使I2C 设备驱动仅关心如何操作I2C 设备,而不需要了解I2C主机控制器的细节,从而使12C设备驱动可以独立存在,适用于不同的硬件平台。
  • I2C设备驱动:调用I2C Core提供的统一API,根据I2C 设备的访问规范,控制I2C主机控制器发出不同的时序信号,对I2C设备进行访问。该驱动称为内核层12C设备驱动。
  • I2C 驱动和我们之前接触到的平台总线设备驱动非常类似,都有总线、设备和驱这三者。

【2】linux内核的I2C驱动框架总览

  • I2C驱动框架的主要目标是:让驱动开发者可以在内核中方便的添加自己的I2C设备的驱动程序,从而可以更容易的在linux下驱动自己的I2C接口硬件
  • 源码中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。这种是我们后续分析的重点。

四,linux内核的I2C子系统源代码分析


【1】I2C子系统的4个关键结构体

  • struct i2c_adapter : I2C适配器
  • struct i2c_algorithm : I2C算法
  • struct i2c_client :I2C(从机)设备信息
  • struct i2c_driver : I2C(从机)设备驱动
struct i2c_adapter {
	struct module *owner;
	unsigned int id;
	unsigned int class;		          /* classes to allow probing for */
	const struct i2c_algorithm *algo; /* the algorithm to access the bus */
	void *algo_data;

	/* data fields that are valid for all devices	*/
	struct rt_mutex bus_lock;
	int timeout;		         	 /* in jiffies */
	int retries;
	struct device dev;		         /* the adapter device */

	int nr;
	char name[48];
	struct completion dev_released;
	struct list_head userspace_clients;
};
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,  int num);
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 *);
};
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;
};
struct i2c_driver {
	unsigned int class;
	int (*attach_adapter)(struct i2c_adapter *);
	int (*detach_adapter)(struct i2c_adapter *);

	/* Standard driver model interfaces */
	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 *);
	int (*suspend)(struct i2c_client *, pm_message_t mesg);
	int (*resume)(struct i2c_client *);
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;
	const struct i2c_device_id *id_table;

	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
};


1.i2c-core.c分析


  • smbus代码略过

  • 模块加载和卸载:bus_register(&i2c_bus_type);

在这里插入图片描述

  • match函数
  • 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函数会对设备进行硬件初始化和后续工作。

【2】核心层开放给其他部分的注册接口

i2c_add_adapter/i2c_add_numbered_adapter		注册adapter的
i2c_add_driver							    	注册driver的
i2c_new_device								    注册client的

2.i2c_s3c2410.c分析


【1】 adapter模块的注册

  • 平台总线方式注册
  • 找到driver和device,并且确认其配对过程
    在这里插入图片描述

【2】probe函数分析

  • 填充一个i2c_adapter结构体,并且调用接口去注册之
  • 从platform_device接收硬件信息,做必要的处理(request_mem_region & ioremap、request_irq等)
  • 对硬件做初始化(直接操作210内部I2C控制器的寄存器)

在这里插入图片描述
【3】i2c_algorithm

  • i2c->adap.algo = &s3c24xx_i2c_algorithm;
  • functionality
  • s3c24xx_i2c_doxfer
  • i2c_msg
    在这里插入图片描述
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	     /* read data, from slave to master */
#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	 /* length will be first received byte */
	__u16 len;		                 /* msg length				*/
	__u8 *buf;		                 /* pointer to msg data			*/
};

3.gslX680的驱动为例-----i2c_driver 和 i2c_client


【1】i2c_driver的基本分析:(gslx680.c)

在这里插入图片描述

【2】i2c_client从哪里来

  • 直接来源:
mach-x210.c
	smdkc110_machine_init
		i2c_register_board_info

在这里插入图片描述

【3】i2c_board_info

  • i2c_board_info用于描述某些信息已知的IIC设备,通常用I2C_BOARD_INFO宏来描述基本信息,包括驱动的名字和从设备的地址。
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
};

【4】i2c_client来源的最终实现原理

  • 内核维护一个链表 __i2c_board_list,这个链表上链接的是I2C总线上挂接的所有硬件设备的信息结构体。也就是说这个链表维护的是一个struct i2c_board_info结构体链表。
  • 真正的需要的struct i2c_client在别的地方由__i2c_board_list链表中的各个节点内容来另外构建生成。

在这里插入图片描述

  • 函数调用层次
i2c_add_adapter/i2c_add_numbered_adapter
	i2c_register_adapter
		i2c_scan_static_board_info
			i2c_new_device
				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));生成之后就能与i2c_drivert通过i2c_driver的id_table里面的名字匹配。

  • 自此,i2c_driver 和 i2c_client在i2c总线中完成匹配,驱动框架分析结束。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是CLRC66301HN的IIC版本开发例程: 1. 确认硬件连接 首先,需要确认CLRC66301HN芯片的硬件连接是否正确。在IIC版本中,通常使用两个引脚SCL和SDA连接到主控芯片的IIC总线上。请检查这两个引脚是否正确连接。 2. 初始化IIC总线 在开始使用CLRC66301HN芯片之前,需要初始化IIC总线。以下是一个简单的例程,可以初始化IIC总线: ```c void i2c_init(void) { // 初始化IIC总线 // 设置IIC时钟频率为100kHz TWBR = 72; // 打开IIC总线 TWCR = (1 << TWEN); } ``` 3. 写入寄存器 在使用CLRC66301HN芯片之前,需要将一些寄存器设置为正确的值。以下是一个写入寄存器的例程: ```c void write_register(uint8_t reg, uint8_t value) { // 发送起始信号 TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); // 检查状态码 if ((TWSR & 0xF8) != TW_START) return; // 发送从地址和写入位 TWDR = CLRC66301HN_I2C_ADDR << 1; TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); // 检查状态码 if ((TWSR & 0xF8) != TW_MT_SLA_ACK) return; // 发送寄存器地址 TWDR = reg; TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); // 检查状态码 if ((TWSR & 0xF8) != TW_MT_DATA_ACK) return; // 发送数据 TWDR = value; TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); // 检查状态码 if ((TWSR & 0xF8) != TW_MT_DATA_ACK) return; // 发送停止信号 TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN); } ``` 这个例程将一个字节写入指定的寄存器中。 4. 读取寄存器 如果需要读取CLRC66301HN芯片中的某个寄存器的值,可以使用以下例程: ```c uint8_t read_register(uint8_t reg) { uint8_t value = 0; // 发送起始信号 TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); // 检查状态码 if ((TWSR & 0xF8) != TW_START) return 0; // 发送从地址和写入位 TWDR = CLRC66301HN_I2C_ADDR << 1; TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); // 检查状态码 if ((TWSR & 0xF8) != TW_MT_SLA_ACK) return 0; // 发送寄存器地址 TWDR = reg; TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); // 检查状态码 if ((TWSR & 0xF8) != TW_MT_DATA_ACK) return 0; // 发送重启信号 TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); // 检查状态码 if ((TWSR & 0xF8) != TW_REP_START) return 0; // 发送从地址和读取位 TWDR = (CLRC66301HN_I2C_ADDR << 1) | 0x01; TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); // 检查状态码 if ((TWSR & 0xF8) != TW_MR_SLA_ACK) return 0; // 读取数据 TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); value = TWDR; // 发送停止信号 TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN); return value; } ``` 这个例程将返回指定寄存器中存储的一个字节的值。 5. 总结 这是一个非常简单的CLRC66301HN IIC版本的开发例程。您可以将其用作基础,以构建更复杂的应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值