應用程序和驅動中使用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