本文将从以下三个方面进行:
- 概述i2c协议;
- linux i2c的软件框架与核心实现;
- 怎样编写i2c驱动;
- 用户态编程使用i2c驱动。
概述
I2C协议是嵌入式系统中广泛使用的一类通信协议,主要用于CPU和各种外设之间的低速数据通信,如camera、eeprom、HDMI控制线等。
Linux kernel使用I2C framework抽象、管理相应的资源,并以各种形式,向各类使用者提供API。作为总线(bus)的一种,I2C framework的实现体现了linux设备模型的精髓,值得研究与学习。
I2C是一个能够支持多个设备的总线,包含一条双向串行数据线SDA,一条串行时钟线SCL。i2c支持三种速率模式, 普通模式:100kHz;快速模式:400kHz;高速模式:3.4MHz。
i2c协议
-
空闲状态
i2c总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。 -
起始位与停止位的定义
起始信号:当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号。
停止信号:当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。
-
数据发送与应答
在起始信号后,发送端发送8bit数据,接收端在第9个时钟发送ACK应答信号。
在SCL低电平时,SDA上的电平进行改变;在SCL高电平时,SDA上的电平状态必须保持稳定,接收端在SCL高电平时读取数据。 -
i2c读写时序
- i2c写数据时序
在起始信号后,主机发送一个7bit的从机地址和一个读写位(0:写,1:读),对应地址的从机发送一个ACK,主机开始写入8bit数据,从机ACK,结束信号。
- i2c读数据时序
在起始信号后,主机发送一个7bit的从机地址和一个读写位(0:写,1:读),对应地址的从机发送一个ACK,从机发送8bit数据,主机发送ACK,结束信号。
RTC-RX8010协议
rx8010指定寄存器写入时序:
- 主机发送起始信号
- 主机发送从机rx8010地址和读标志位
- rx8010发送ACK
- 主机发送欲写入的寄存器地址
- rx8010发送ACK
- 主机发送写入数据
- rx8010发送ACK
- [可选]继续写入数据可重复6、7步骤,寄存器地址会自动增加
- 主机发送停止信号
rx8010指定寄存器读取时序:
- 主机发送起始信号
- 主机发送从机rx8010地址和写标志位
- rx8010发送ACK
- 主机发送欲读取的寄存器地址
- rx8010发送ACK
- 主机发送起始信号(restart)
- 主机发送从机rx8010地址和读标志位
- rx8010发送寄存器数据
- 主机发送ACK则rx8010继续发送数据(寄存器自动增加),主机发送NACK则rx8010停止发送数据
- 主机发送停止信号
linux i2c驱动框架分析
linux i2c驱动是一种基于总线的驱动,主要分为三个部分:设备驱动、核心层(总线驱动)、适配器驱动(设备)。
设备驱动:实现在driver/i2c/i2c-dev.c
中,主要完成对应用提供write、read等接口,绑定client并传递给核心层。
核心层:又可称为总线层,定义在driver/i2c-core.c
中,主要完成设备驱动与设备的绑定,提供通用的数据传输接口。linux系统提供,无需修改。
适配器层:又称控制器驱动层,定义在driver/busses
,具体完成i2c控制器的驱动,实现发送数据的方法,创建client。SOC厂家提供,一般可无需修改,但是创建client需要设备信息,可通过mach-xxx.c或设备树提供。
i2c适配器层platform总线:
函数调用结构:
i2c驱动开发
将i2c核心层和适配器层编译进内核
在编译linux的.config文件或menuconfig中使能i2c总线编译。
make menuconfig
Device Drivers
I2C support ##--->i2c-core.c
I2C Hardware Bus support
xxx I2C Controller ##--->适配器层驱动
查看i2c总线是否编译加载:
ls /sys/bus/i2c
devices drivers ##--->说明核心层(bus)已被加载
cat /sys/bus/i2c/devices/i2c-0/name
xxxxx ##--->对应平台的i2c控制器platform设备,说明适配器层已被加载
ls /sys/bus/platform/drivers
xxxx_i2c ###--->对应i2c控制器platform驱动
重要数据结构与函数
数据结构
struct i2c_driver
表示是一个从设备的驱动。
{
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
struct device_driver driver; ///<继承了父类
const struct of_device_id *of_match_table; ///<用于做对比,设备树
const struct i2c_device_id *idtable; ///<用于做比对,非设备树
}
对应函数:
/*注册设备*/
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
/*注销设备*/
void i2c_del_driver(struct i2c_driver *driver);
2. `struct i2c_client`
表示一个从设备
```c
{
unsigned short addr; ///<从设备地址,来自于设备树中<reg>
char name[I2C_NAME_SIZE]; ///<用于和i2c driver进行匹配,来自于设备树中的compatible
struct i2c_adapter *adapter; ///<指向所属的适配器
struct device dev; ///<继承了父类
}
struct i2c_adapter
表示一个i2c控制器
{
const struct i2c_algorithm *algo; ///<算法
int (*master_xfer) (struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
struct device dev; ///<继承了父类,也会被加入i2c bus
int nr; ///<编号,如i2c-2 中的2
}
struct i2c_msg
表示一个i2c消息
{
struct addr; ///<从设备地址
struct flag; ///<读、写标志,发送数据类型标志,如10bit地址、16bit寄存器模式等
__u16 len; ///<消息长度
__u8 *buf;///< 消息数据地址
}
发送\接收信息函数:
int i2c_master_send(const struct i2c_client *client, const char *buf, int count);
int i2c_master_recv(const struct i2c_client *client, char *buf, int count);
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
i2c子系统开发-设备驱动设计的重要函数(linux/i2c.h)
数据接收与发送
int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned n);
适配器增加与删除
int i2c_add_adapter(struct i2c_adapter *);
void i2c_del_adapter(struct i2c_adapter *);
int i2c_add_numbered_adapter(struct i2c_adapter *);
驱动注册与删除
int i2c_register_driver(struct module *, struct i2c_driver *);
void i2c_del_driver(struct i2c_driver *);
设备增加与注销
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);
void i2c_unregister_device(struct i2c_client *);
i2c总线驱动层