3 i2c-dev
3.1 概述
之前在介紹I2C子系統時,提到過使用i2c-dev.c檔案在應用程式中實現我們的I2C從裝置驅動。不過,它實現的是一個虛擬,臨時的i2c_client,隨著裝置檔案的開啟而產生,並隨著裝置檔案的關閉而撤銷。I2c-dev.c針對每個I2C適配器產生一個主裝置號為89的裝置檔案,實現了i2c_driver的成員函數以及檔案操作介面,所以i2c-dev.c的主題是”i2c_driver成員函數+字元裝置驅動”。
3.2 i2c-dev.c源碼分析
初始化模組
static int __init i2c_dev_init(void){ res= register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops); i2c_dev_class= class_create(THIS_MODULE, "i2c-dev"); /*Keep track of adapters which will be added or removed later */ res= bus_register_notifier(&i2c_bus_type, &i2cdev_notifier); /*綁定已經存在的適配器 */ i2c_for_each_dev(NULL,i2cdev_attach_adapter);}
I2c-dev初始化函數主要做了註冊名為”i2c”的字元裝置檔案和”i2c-dev”的類
i2cdev_read和i2cdev_write
I2c-dev.c中實現的i2cdev_read和i2cdev_write函數不具有太強的通用性,只適合下面這種單開始訊號情況:
而不適合多開始訊號的情況:
所以我們經常會使用i2cdev_ioctl函數的I2C_RDWR,在分析i2cdev_ioctl函數之前,我們需要瞭解一個結構體:
/* This is the structure as used in theI2C_RDWR ioctl call */struct i2c_rdwr_ioctl_data { structi2c_msg __user *msgs; /* pointersto i2c_msgs */ __u32nmsgs; /* number ofi2c_msgs */};
Msgs 表示單個開始訊號傳遞的資料;
Nmsgs 表示有多少個msgs,比如,單開始訊號時,nmsgs等於1;多開始訊號時,nmsgs等於2
struct i2c_msg { __u16addr; /* slave address */ __u16flags; /* 預設為寫入 */#define I2C_M_TEN 0x0010 /*this is a ten bit chip address */#define I2C_M_RD 0x0001 /* read data,from slave to master */#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */#define I2C_M_REV_DIR_ADDR 0x2000 /*if I2C_FUNC_PROTOCOL_MANGLING */#define I2C_M_IGNORE_NAK 0x1000 /*if I2C_FUNC_PROTOCOL_MANGLING */#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */ __u16len; /* msg length */ __u8*buf; /* pointer to msgdata */};
使用i2cdev_ioctl函數的I2C_RDWR指令會調用到i2cdev_ioctl_rdrw函數:
static noinline inti2cdev_ioctl_rdrw(struct i2c_client *client, unsignedlong arg){ structi2c_rdwr_ioctl_data rdwr_arg; structi2c_msg *rdwr_pa; u8__user **data_ptrs; inti, res; if(copy_from_user(&rdwr_arg, (struct i2c_rdwr_ioctl_data __user *)arg, sizeof(rdwr_arg))) return-EFAULT; if(rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS) return-EINVAL; rdwr_pa= kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg), GFP_KERNEL); if(copy_from_user(rdwr_pa, rdwr_arg.msgs, rdwr_arg.nmsgs * sizeof(struct i2c_msg))) { kfree(rdwr_pa); return-EFAULT; } res= i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs); while(i-- > 0) { if(res >= 0 && (rdwr_pa[i].flags & I2C_M_RD)) { if(copy_to_user(data_ptrs[i], rdwr_pa[i].buf, rdwr_pa[i].len)) res= -EFAULT; } kfree(rdwr_pa[i].buf); }}
咋一看,還挺複雜,其實主要做了一件事情:把使用者空間傳遞過來的i2c_rdwr_ioctl_data資料進行錯誤檢查,然後調用i2c_transfer函數與適配器進行通訊,如果是接收資料,代碼會將訪問到的資料傳回i2c_rdwr_ioctl_data的buf中。I2c_transfer最終會調用到I2C適配器具體實現的master_xfer函數來與硬體進行通訊。
3.3 eeprom執行個體
預備知識
使用的mini2440開發板,eeprom的地址為0x50,實驗完成一個資料的讀寫,先看下讀寫時序
AT24C08任意地址位元組寫的時序:
AT24C08任意地址位元組寫的時序:
下面的代碼可以按照上面的兩個圖來閱讀:
#include <stdio.h>#include <linux/types.h>#include <fcntl.h>#include <unistd.h>#include <stdlib.h>#include <sys/types.h>#include <sys/ioctl.h>#include <errno.h>#include <assert.h>#include <string.h>#include <linux/i2c.h>#include <linux/i2c-dev.h> int main(){ intfd, ret; unsignedchar rdwr_addr = 0x42; /* e2prom 讀寫地址 */ unsignedchar device_addr = 0x50; /* e2prom 裝置地址 */ unsignedchar data = 0x12; /* 向e2prom寫的資料 */ structi2c_rdwr_ioctl_data e2prom_data; fd= open("/dev/i2c/0", O_RDWR); if(fd < 0) { perror("openerror"); exit(1); } e2prom_data.msgs= (struct i2c_msg *)malloc(e2prom_data.nmsgs * \ sizeof(structi2c_msg)); if(e2prom_data.msgs == NULL) { perror("mallocerror"); exit(1); } ioctl(fd,I2C_TIMEOUT, 1); /* 設定逾時 */ ioctl(fd,I2C_RETRIES, 2); /* 設定重試次數 */ /*向e2prom的rdwr_addr地址寫入資料data*/ e2prom_data.nmsgs= 1; e2prom_data.msgs[0].len= 2; e2prom_data.msgs[0].addr= device_addr; e2prom_data.msgs[0].flags= 0; /* write */ e2prom_data.msgs[0].buf= (unsigned char *)malloc(2); e2prom_data.msgs[0].buf[0]= rdwr_addr; /* write address */ e2prom_data.msgs[0].buf[1]= data; /* write data */ ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data); if(ret < 0) { perror("writedata error"); exit(1); } printf("writedata: %d to address: %#x\n", data, rdwr_addr); data= 0; /* be zero*/ /*從e2prom的rdwr_addr地址讀取資料存入buf*/ e2prom_data.nmsgs= 2; e2prom_data.msgs[0].len= 1; e2prom_data.msgs[0].addr= device_addr;// e2prom_data.msgs[0].flags= 0; /* write */ e2prom_data.msgs[0].buf= &rdwr_addr; e2prom_data.msgs[1].len= 1; e2prom_data.msgs[1].addr= device_addr; e2prom_data.msgs[1].flags= 1; /* read */ e2prom_data.msgs[1].buf= &data; ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data); if(ret < 0) { perror("readerror"); exit(1); } printf("read data: %d from address: %#x\n", data,rdwr_addr); free(e2prom_data.msgs); close(fd); return0;}
在mini2440開發板上已經實驗成功。