I2C驱动框架

I2C驱动开发

I2C是一种接口,数据通过该接口需遵循I2C协议,对于驱动开发者相当于I2C从设备(硬件芯片)开发。包括:

  1. cmos camera摄像头(前置、后置)
  2. ts触摸屏
  3. gsenor重力传感器
  4. eeprom存储设备
  5. HDMI高清输出接口(机顶盒与显示屏连接)。另外,主机与显示屏是通过VGA接口。

接口

I2C是多主控总线,所以任何一个设备都能像主控器一样工作,并控制总线。总线上每一个设备都有一个独一无二的地址,根据设备它们自己的能力,它们可以作为发射器或接收器工作。多路微控制器能在同一个I2C总线上共存。
有两条线式串行总线,一条数据线SDA,一条SCL时钟线,均接上拉电阻,发送接收端均采用三极管的基电极连接,可降低外接干扰,保证高低电平的准确输出,同一时刻只能发送或接受数据,对发送或接收的数据字节数是没有限制的。

SCL:时钟节拍器,每一个周期对应SDA上的一个信号或数据位。
SDA:数据传输,半双工通信。

I2C协议

开始信号:在SCL为高电平时,SDA由高变低
传输数据:每一个SCL周期传递一个数据,在SCL为高电平时数据才有效,并且SCL高电平时数据位需保持稳定,电平不能发送改变。
ACK信号:在数据传输完毕后,释放SDA线恢复高电平,从设备收到数据将SDA线又重新拉低,回应主设备,作为应答位ACK。
停止位:在SCL为高电平时,SDA由低变高

特点:可以发现在SCL为高电平时,SDA电平稳定表示数据传输,SDA电平发生变化表示起始信号/结束信号。

I2C读写过程

写过程:
主机发送一个起始信号S→发送7位从设备地址和1位写方向位→从设备匹配成功回应ACK→主设备传输8位数据对从设备进行连续写操作→从设备接收成功回应ACK→若写结束主机发出停止信号。

读过程:
主机发送一个起始信号S→发送7位从设备地址和写方向位→从设备匹配成功回应ACK→重新发送起始信号S→从设备寄存器地址和读方向位→从设备接收成功回应ACK→从机占用SDA线,用SDA传输8位数据给主机→主机连续读数据,从机释放SDA线,拉低SDA线作为ACK应答→若读结束主机发出停止信号

I2C子系统框架

应用层:通过驱动层io接口对从设备进行读写。

I2C驱动层driver:对从设备进行发送数据,提供文件I/O操作接口,与应用层交互

I2C核心层core:维护I2C总线上的两条链表,用于设备与驱动的注册与匹配,adapter也加入到device链表中,其中设备的描述为I2C_client(名字、从设备信息)由adapter层创建,驱动的描述为I2C_driver(名字等)由我们创建。

I2C控制器层adapter:初始化i2c控制器,控制I2C控制器发送数据,包含alogritm算法,通过master_xfer对数据转为遵循I2C控制器协议的格式,传输给硬件层

硬件层:连接在I2C总线上的设备

I2C控制器层adapter

在内核中将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驱动框架_第1张图片
接的是I2C控制器7,并且在核心板原理图中找到对应引脚
在这里插入图片描述
SDA7----GPD0-2
SCL7----GPD0-3
在datashett中找到对应寄存器地址
I2C驱动框架_第2张图片
对应第7组控制器,序号从0开始,则地址为0x138D0000

OK!总结一下找到的信息:
SDA7----GPD0-2
SCL7----GPD0-3
I2C控制器7对应寄存器地址:0x138D0000

在设备树文件中,通过层层头文件封装查找到,在arch/arm/boot/dts/exynos4.dtsi文件下
I2C驱动框架_第3张图片
然后在查找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 ------控制器层文件

I2C驱动层框架的编写

  1. 获取i2c_client对象,由设备树中创建了,在/sys/bus/i2c中,传递到驱动probe中。
  2. 编写i2c_driver,构建对象并注册到i2c driver总线上,实现probe、remove与创建设备结点,通过I2c接口去初始化I2c从设备。
  3. 完成文件io操作提供给应用层

几个重要对象:

描述驱动信息  -----由驱动工程师创建
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];
}

你可能感兴趣的:(linux驱动)