应用程序和驱动中使用I2C的流程
编写I2C设备驱动有两种方法。一种是利用系统给我们提供的i2c-dev.c来实现一个i2c适配器的设备文件。然后通过在应用层操作i2c适配器来控制i2c设备。另一种是为i2c设备,独立编写一个设备驱动。注意:在后一种情况下,是不需要使用i2c-dev.c的。
前一种方法也就是说只要系统实现了I2C适配器的驱动并生成了设备文件, 那么挂在其上面的I2C设备也可以在应用层直接通过操作这个设备文件来间接控制这个I2C设备, 这样就省去了专门为这个设备编写驱动的麻烦。本文也详细描述在应用层和驱动层分别控制I2C设备的流程。
一应用层操作I2C设备
即利用i2c-dev.c操作适配器,进而控制i2c设备, i2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read()、write()和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的i2c设备的存储空间或寄存器,并控制I2C设备的工作方式。
需要特别注意的是:i2c-dev.c的read()、write()方法都只适合于如下方式的数据格式(可查看内核相关源码):
所以不具有太强的通用性,如下面这种情况就不适用(通常出现在读目标时)。
而且read()、write()方法只适用用于适配器支持i2c算法的情况,如:
static const struct i2c_algorithms3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
而不适合适配器只支持smbus算法的情况,如:
static const struct i2c_algorithmsmbus_algorithm = {
.smbus_xfer = i801_access,
.functionality = i801_func,
};
基于上面几个原因,所以一般都不会使用i2c-dev.c的read()、write()方法。最常用的是ioctl()方法。ioctl()方法可以实现上面所有的情况(两种数据格式、以及I2C算法和smbus算法)。
针对i2c的算法,需要熟悉struct i2c_rdwr_ioctl_data 、struct i2c_msg。使用的命令是I2C_RDWR。
struct i2c_rdwr_ioctl_data {
struct i2c_msg __user *msgs; /*pointers to i2c_msgs */
__u32 nmsgs; /* number of i2c_msgs*/
};
struct i2c_msg {
_ _u16 addr; /* slave address */
_ _u16 flags; /* 标志(读、写) */
_ _u16 len; /* msg length */
_ _u8 *buf; /* pointer to msg data*/
};
针对smbus算法,需要熟悉struct i2c_smbus_ioctl_data。使用的命令是I2C_SMBUS。对于smbus算法,不需要考虑“多开始信号时序”问题。
struct i2c_smbus_ioctl_data {
__u8 read_write; //读、写
__u8 command; //命令
__u32 size; //数据长度标识
union i2c_smbus_data __user *data;//数据
};
下面以本人工作中的一个实例讲解操作的具体过程。通过imx6q操作tlv320aic3204(一颗音频codec)。实现对tlv320aic3204的初始化工作。
首先在内核中已经包含了对imx6q中的i2c控制器驱动的支持。提供了i2c算法(非smbus类型的,所以后面的ioctl的命令是I2C_RDWR)
我们所用的I2C适配器的设备文件为:/dev/i2c-0, 接下来是使用流程:
1. 打开适配器文件, 并做相应的设置:
/*iccard device*/
fd_codec= open(TLV320AIC3204_FILE, O_RDWR); /*打开适配器文件*/
if(fd_codec< 0) {
LOGE("can'topen codec device file");
return0;
}
ioctl(fd_codec,I2C_SLAVE, TLV320AIC3204_I2C_ADDR); /*配置slave地址*/
ioctl(fd_codec,I2C_TIMEOUT, 10); /*配置超时时间*/
ioctl(fd_codec,I2C_RETRIES, 2); /*配置重试次数*/
/*initializei2c_data*/ /*初始化I2C通信的核心数据结构*/
i2c_data.nmsgs= 2; /*max 2 message*/
i2c_data.msgs= malloc(i2c_data.nmsgs * sizeof(struct i2c_msg));
if(i2c_data.msgs== NULL) {
LOGE("cant'tmalloc memory");
gotofailed1;
}
msgs[0]= i2c_data.msgs;
msgs[1]= i2c_data.msgs + 1;
msgs[0]->buf= malloc(2); /*根据要传输的字节数来分配*/
if(!msgs[0]->buf){
LOGE("can'tmalloc memory");
gotofailed2;
}
msgs[1]->buf= malloc(2); /*根据要传输的字节数来分配*/
if(!msgs[1]->buf){
LOGE("can'tmalloc memory");
gotofailed3;
}
2. 写寄存器流程如下:
structi2c_msg *p_msg = i2c_data.msgs;
if(fd_codec< 0)
return-1;
/*justset i2c_data*/
i2c_data.nmsgs= 1; /*写的话只要一个msg即可*/
p_msg->len= 2;
p_msg->addr= TLV320AIC3204_I2C_ADDR;
p_msg->flags= 0; /*write*/
p_msg->buf[0]= reg; /*register*/
p_msg->buf[1]= val; /*data*/
ret =ioctl(fd_codec, I2C_RDWR, (unsigned long) &i2c_data); /*发送命令给适配器*/
if(ret< 0) {
LOGE("ioctlfailed! :%d", ret);
}
3. 读寄存器的流程:
int ret= 0;
structi2c_msg *msgs[2]; /*读需要两个msg, 1个用来发读register的地址, 一个读数据*/
if(fd_codec< 0)
return-1;
msgs[0]= i2c_data.msgs;
msgs[1]= i2c_data.msgs + 1;
/*justset i2c_data*/
i2c_data.nmsgs= 2;
/*指定要读的寄存器地址的msg*/
msgs[0]->len= 1;
msgs[0]->addr= TLV320AIC3204_I2C_ADDR;
msgs[0]->flags= 0; /*write*/
msgs[0]->buf[0]= reg; /*register*/
/*读数据*/
msgs[1]->len= 1;
msgs[1]->addr= TLV320AIC3204_I2C_ADDR;
msgs[1]->flags= 1; /*read*/
msgs[1]->buf[0]= 0;
ret =ioctl(fd_codec, I2C_RDWR, (unsigned long) &i2c_data); /*发送请求命令*/
if(ret< 0) {
LOGE("ioctlfailed! :%d", ret);
returnret;
}
else
return msgs[1]->buf[0];
以上讲述了一种比较常用的利用i2c-dev.c操作i2c设备的方法,这种方法可以说是在应用层完成了对具体i2c设备的驱动工作。
二驱动层操作I2C设备
即为I2C设备编写驱动程序, 首先要确保I2C适配器的驱动都正常, 因为我们需要它来实现正真的I2C传输, 下面将以本人项目中的一颗RTC的I2C设备:pcf8563为例来讲解大体的I2C驱动流程。
1. 定义一个i2c_board_info的数据块:
static structi2c_board_info mxc_i2c1_board_info[] __initdata = {
…
{
I2C_BOARD_INFO("pcf8563", 0x51), /*名字要跟驱动里的同名, 0x51为I2C的设备地址*/
},
…
};
2. 注册到系统中去:
i2c_register_board_info(1,mxc_i2c1_board_info,
ARRAY_SIZE(mxc_i2c1_board_info));
3. 编写 PCF8563的I2C驱动, 这个驱动在内核里已经实现了, 在driver/rtc/rtc-pcf8563.c, 我们将描述这个驱动的框架, 及怎么跟上面注册的i2c设备绑定在一起。
3.1 I2C驱动都有一个i2c_driver的类型, 这里是:
static struct i2c_driver pcf8563_driver = {
.driver = {
.name = "rtc-pcf8563",
},
.probe = pcf8563_probe,
.remove = pcf8563_remove,
.id_table = pcf8563_id,
};
pcf8563_id 内容如下:
static const struct i2c_device_idpcf8563_id[] = {
{"pcf8563", 0 }, /*可以看到名称和上面的设备名称相同*/
{"rtc8564", 0 },
{}
};
3.2 把i2c_driver注册到系统中去:
static int __init pcf8563_init(void)
{
returni2c_add_driver(&pcf8563_driver);
}
注册进去后, I2C总线会去查找哪些已经挂在总线上, 但还没有找到驱动的设备, 然后,根据设备名称和驱动支持的id列表中去匹配, 如果匹配的话, 就调用驱动的probe函数, 显然,这边我们找到了pcf8563的设备,pcf8563_probe会被调用。
3.3 pcf8563_probe函数会去向rtc子系统注册一个RTC设备, 这边不详细解说了, 我们着重看看在这个I2C驱动里是如何跟I2C设备通信的,
3.4 读写设备:
读设备如下:
static int pcf8563_get_datetime(structi2c_client *client, struct rtc_time *tm)
{
structpcf8563 *pcf8563 = i2c_get_clientdata(client);
unsignedchar buf[13] = { PCF8563_REG_ST1 };
structi2c_msg msgs[] = {
{client->addr, 0, 1, buf }, /* setupread ptr */
{client->addr, I2C_M_RD, 13, buf }, /*read status + date */
};
/*read registers */
if((i2c_transfer(client->adapter, msgs, 2)) != 2) {
dev_err(&client->dev,"%s: read error\n", __func__);
return-EIO;
}
….
}
写设备如下:
static int pcf8563_set_datetime(structi2c_client *client, struct rtc_time *tm)
{
structpcf8563 *pcf8563 = i2c_get_clientdata(client);
inti, err;
unsignedchar buf[9];
…….
/*hours, minutes and seconds */
buf[PCF8563_REG_SC]= bin2bcd(tm->tm_sec);
buf[PCF8563_REG_MN]= bin2bcd(tm->tm_min);
buf[PCF8563_REG_HR]= bin2bcd(tm->tm_hour);
buf[PCF8563_REG_DM]= bin2bcd(tm->tm_mday);
/*month, 1 - 12 */
buf[PCF8563_REG_MO]= bin2bcd(tm->tm_mon + 1);
/*year and century */
buf[PCF8563_REG_YR]= bin2bcd(tm->tm_year % 100);
if(pcf8563->c_polarity ? (tm->tm_year >= 100) : (tm->tm_year <100))
buf[PCF8563_REG_MO]|= PCF8563_MO_C;
buf[PCF8563_REG_DW]= tm->tm_wday & 0x07;
/*write register's data */
for(i = 0; i < 7; i++) {
unsignedchar data[2] = { PCF8563_REG_SC + i,
buf[PCF8563_REG_SC+ i] };
err= i2c_master_send(client, data, sizeof(data));
if(err != sizeof(data)) {
dev_err(&client->dev,
"%s:err=%d addr=%02x, data=%02x\n",
__func__,err, data[0], data[1]);
return-EIO;
}
};
return0;
}
部分内容来自刘洪涛老师, 请参考:
http://blog.csdn.net/hongtao_liu/article/details/4964244
http://blog.csdn.net/hongtao_liu/article/details/5260739