I2C是一种接口,数据通过该接口需遵循I2C协议,对于驱动开发者相当于I2C从设备(硬件芯片)开发。包括:
I2C是多主控总线,所以任何一个设备都能像主控器一样工作,并控制总线。总线上每一个设备都有一个独一无二的地址,根据设备它们自己的能力,它们可以作为发射器或接收器工作。多路微控制器能在同一个I2C总线上共存。
有两条线式串行总线,一条数据线SDA,一条SCL时钟线,均接上拉电阻,发送接收端均采用三极管的基电极连接,可降低外接干扰,保证高低电平的准确输出,同一时刻只能发送或接受数据,对发送或接收的数据字节数是没有限制的。
SCL:时钟节拍器,每一个周期对应SDA上的一个信号或数据位。
SDA:数据传输,半双工通信。
开始信号:在SCL为高电平时,SDA由高变低
传输数据:每一个SCL周期传递一个数据,在SCL为高电平时数据才有效,并且SCL高电平时数据位需保持稳定,电平不能发送改变。
ACK信号:在数据传输完毕后,释放SDA线恢复高电平,从设备收到数据将SDA线又重新拉低,回应主设备,作为应答位ACK。
停止位:在SCL为高电平时,SDA由低变高
特点:可以发现在SCL为高电平时,SDA电平稳定表示数据传输,SDA电平发生变化表示起始信号/结束信号。
写过程:
主机发送一个起始信号S→发送7位从设备地址和1位写方向位→从设备匹配成功回应ACK→主设备传输8位数据对从设备进行连续写操作→从设备接收成功回应ACK→若写结束主机发出停止信号。
读过程:
主机发送一个起始信号S→发送7位从设备地址和写方向位→从设备匹配成功回应ACK→重新发送起始信号S→从设备寄存器地址和读方向位→从设备接收成功回应ACK→从机占用SDA线,用SDA传输8位数据给主机→主机连续读数据,从机释放SDA线,拉低SDA线作为ACK应答→若读结束主机发出停止信号
应用层:通过驱动层io接口对从设备进行读写。
I2C驱动层driver:对从设备进行发送数据,提供文件I/O操作接口,与应用层交互
I2C核心层core:维护I2C总线上的两条链表,用于设备与驱动的注册与匹配,adapter也加入到device链表中,其中设备的描述为I2C_client(名字、从设备信息)由adapter层创建,驱动的描述为I2C_driver(名字等)由我们创建。
I2C控制器层adapter:初始化i2c控制器,控制I2C控制器发送数据,包含alogritm算法,通过master_xfer对数据转为遵循I2C控制器协议的格式,传输给硬件层
硬件层:连接在I2C总线上的设备
在内核中将adapter做成pdrv,加入到平台总线,平台总线特点是在系统升级时只需修改设备信息,驱动可不变,因此对于4412-A9系列的,还是采用s3c-2410.c的驱动,对于老版本来说,平台总线设备由pdev描述,由于对pdev修改十分麻烦,后面采用设备树模型,将设备树中个设备结点注册到平台总线中进行匹配。i2c_client实质上也是从设备树中获取。
应用层对I2C控制器对应的设备节点进行io读写操作,从用户态转到内核态,驱动层中将数据封装为I2C_MSG对象,通过i2c_tranfer对该数据对象进行转发。
i2c_client由adapter进行创建,并注册到i2c核心层device链表中进行维护,i2c_driver由驱动工程师创建,也注册到i2c核心层driver链表中进行维护,当匹配成功后,i2c_client传到驱动层中的probe方法,驱动层遍可以获取I2c_client的设备信息,对从设备进行读写操作,由于i2c_client由adapter创建,通过i2c_client遍可以获得i2c_adapter对象,从而可以调用到adapter对象中的master_xfer方法对控制i2c控制器发送数据给从设备。
我们只需要完成对驱动层和从设备信息的编写,对于从设备信息的编写,对于非设备树需通过math-xx.c文件中的结构体i2c_board_info进行配置,对于设备树需在设备树文件中进行配置。
以我的开发板 讯为4412的手册摄像头模块为例:
接的是I2C控制器7,并且在核心板原理图中找到对应引脚
SDA7----GPD0-2
SCL7----GPD0-3
在datashett中找到对应寄存器地址
对应第7组控制器,序号从0开始,则地址为0x138D0000
OK!总结一下找到的信息:
SDA7----GPD0-2
SCL7----GPD0-3
I2C控制器7对应寄存器地址:0x138D0000
在设备树文件中,通过层层头文件封装查找到,在arch/arm/boot/dts/exynos4.dtsi文件下
然后在查找i2c7,得到对应控制器7的结点信息
i2c_7: i2c@138D0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x138D0000 0x100>;
interrupts = ;
clocks = <&clock CLK_I2C7>;
clock-names = "i2c";
pinctrl-names = "default";
pinctrl-0 = <&i2c7_bus>;
status = "disabled";
};
对于从设备结点一般都是在最顶层设备树目录中,打开exynos4412-itop-elite.dts文件,仿照其他I2C结点添加相应设备结点
&i2c_7 { /* 相当于i2c_adapter的信息 */
#address-cells = <1>;
#size-cells = <0>;
samsung,i2c-sda-delay = <100>;
samsung,i2c-max-bus-freq = <100000>;
pinctrl-0 = <&i2c7_bus>; //继承与上面的i2c7
pinctrl-names = "default";
status = "okay";//开启状态
codec: camera@1a { /*相当于i2c_client的设备信息*/
compatible = "itop,camera";//出产商、设备名
reg = <0x..>; //从设备地址,需查对应硬件手册获取
};
};
添加完毕后,回到顶级目录下重新编译设备树,make dtbs
对于核心层和控制器层,可以内核make menuconfig中配置编译获取,由内核完成。可以通过/sys/bus/i2c查看是否配置,控制器adatper文件记录在device目录下。
i2c-core.c -----核心层文件
i2c-对于平台.c ------控制器层文件
几个重要对象:
描述驱动信息 -----由驱动工程师创建
struct i2c_driver{
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
struct device_driver driver;//继承于父类 device_driver
-- const struct of_device_id *of_match_table; //设备树匹配从设备
const struct i2c_device_id *id_table;//非设备树匹配从设备
}
注册:i2c_add_driver
注销:i2c_del_driver
描述从设备信息 -----由i2c_adapter创建
struct i2c_client{
unsigned short addr; //从设备地址,来自于设备树中的
char name[I2C_NAME_SIZE]; //用于和i2c driver匹配,来自于设备树中的compatible
struct i2c_adapter *adapter; //指向当前从设备所存在的I2c adapter
struct i2c_driver *driver; //匹配到的驱动
struct device dev; //继承父类 device
int irq; //来自于设备树中的
}
创建:i2c_new_device
描述主控器信息 -------由源厂代码构建
struct i2c_adapter{
const struct i2c_algorithm *algo; //算法
-- int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, //转发数据
struct device dev; //继承于父类 device
int nr; //i2c控制器编号
}
注册:i2c_add_adapter
注销:i2c_del_adapter
描述一个从设备要发送的数据的数据包
struct i2c_msg{
__u16 addr; //从设备地址
__u16 flags; //读1写0
__u16 len; //发送消息的长度
__u8 *buf; //指针指向要发送的数据
}
写从设备:i2c_master_send
读从设备:i2c_master_recv
以上两个函数都调用了i2c_transfer
写:
int mpu6050_write_bytes(struct i2c_client *client, char *buf, int count)
{
int ret;
struct i2c_adapter *adapter = client->adapter;
struct i2c_msg msg;
msg.addr = client->addr;
msg.flags = 0;
msg.len = count;
msg.buf = buf;
ret = i2c_transfer(adapter, &msg, 1);
return ret==1?count:ret;
}
读:
int mpu6050_read_bytes(struct i2c_client *client, char *buf, int count)
{
int ret;
struct i2c_adapter *adapter = client->adapter;
struct i2c_msg msg;
msg.addr = client->addr;
msg.flags = I2C_M_RD;
msg.len = count;
msg.buf = buf;
ret = i2c_transfer(adapter, &msg, 1);
return ret==1?count:ret;
}
读取某个特定寄存器的地址上的值:
int mpu6050_read_reg_byte(struct i2c_client *client, char reg)
{
//写寄存器地址,在读寄存器的值
int ret;
struct i2c_adapter *adapter = client->adapter;
struct i2c_msg msg[2];
char rxbuf[1];
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = ®
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = 1;
msg[1].buf = rxbuf;
ret = i2c_transfer(adapter, msg, 1);
if(ret < 0){
printk("i2c_transfer read err\n");
return ret;
}
return rxbuf[0];
}