linux 驱动中I2C设备驱动有两种,其一为用户模式设备驱动,依赖i2c子系统中的i2c-dev驱动,在用户空间去读写i2c设备,另一种就是普通的设备驱动。本文主要讨论第一种:在用户空间读写I2C设备。
首先关于用户空间读写I2C的基本操作, 《i2c驱动之调用ioctl函数进行读写at24c08》 这篇文章看一下就好,不重复说了。然后是MTK平台,I2C驱动的分析,可以看一下 《MTK I2C驱动代码分析》。
下面开始干活:
首先打开文件
fd=open("/dev/i2c-2",O_RDWR);
发现 /dev 路径下并没有i2c-2这个节点,查看 (project)/kernel-3.18/drivers/i2c 下的Makefile
obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o
而在 (project)/kernel-3.18/arch/arm64/configs/(project)_defconfig中默认情况下,CONFIG_I2C_CHARDEV 这个宏是没有打开的。
# CONFIG_I2C_CHARDEV is not set
把这个宏打开就可以了。这时候又会引出一个权限问题,这个根据SEAndroid的规则,申请相应的权限就好,否则第三方应用无法操作 /dev/i2c-2 这个节点。
然后继续,设置i2c_rdwr_ioctl_data.msgs参数
vcnl4200_i2c_data.nmsgs = 2;
vcnl4200_i2c_data.msgs[0].len = 1;
vcnl4200_i2c_data.msgs[0].flags = 0; //write
vcnl4200_i2c_data.msgs[0].addr = 0x6B;
data[0][0]=0x00;
vcnl4200_i2c_data.msgs[0].buf=data[0];
vcnl4200_i2c_data.msgs[1].len = 2;
vcnl4200_i2c_data.msgs[1].flags = I2C_M_RD; //read
vcnl4200_i2c_data.msgs[1].addr = 0x6B;
data[1][0]=0;
data[1][1]=0;
vcnl4200_i2c_data.msgs[1].buf=data[1];
结果发现无论怎样设置都没有数据输出,即I2C没有波形输出,但是抓log又没有看到报错。最后发现,MTK的内核i2c_msg结构体与linux标准的不一样:
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#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_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#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 */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
#ifdef CONFIG_MTK_I2C_EXTENSION
__u32 timing; /* parameters of timings */
__u32 ext_flag;
#endif
};
mtk做的i2c_msg结构体多了 timing和ext_flag这两个成员变量。按照上面的参数设置,会出现参数校验失败,而这个参数校验失败,系统不会报错。
修改应用的i2c_msg结构体定义,与内核一致。
完整的I2C读数据函数:
unsigned char _i2c_read ( unsigned char device_addr, unsigned char sub_addr, unsigned char * buff, short int ByteNo )
{
unsigned int fd, ret;
struct i2c_rdwr_ioctl_data vcnl4200_i2c_data;
const char * i2c_dev = "/dev/i2c-2";
unsigned char data[2][10];
fd = open ( i2c_dev, O_RDWR );
if ( fd < 0 )
{
LOGE("error (errno=%d)", errno);
LOGE ( "no /dev/i2c-2\r\n" );
return FAIL;
}
ioctl(fd, I2C_TIMEOUT, 2);
ioctl(fd, I2C_RETRIES, 1);
vcnl4200_i2c_data.nmsgs = 2;
vcnl4200_i2c_data.msgs = (struct i2c_msg *)malloc(vcnl4200_i2c_data.nmsgs * sizeof(vcnl4200_i2c_data.msgs));
if(!vcnl4200_i2c_data.msgs)
{
LOGE ( "malloc error" );
close ( fd );
return FAIL;
}
vcnl4200_i2c_data.nmsgs = 2;
vcnl4200_i2c_data.msgs[0].len = 1;
vcnl4200_i2c_data.msgs[0].flags = 0; //write
vcnl4200_i2c_data.msgs[0].addr = 0x6B;
data[0][0]=0x00;
vcnl4200_i2c_data.msgs[0].buf=data[0];
vcnl4200_i2c_data.msgs[1].len = 2;
vcnl4200_i2c_data.msgs[1].flags = I2C_M_RD; //read
vcnl4200_i2c_data.msgs[1].addr = 0x6B;
data[1][0]=0;
data[1][1]=0;
vcnl4200_i2c_data.msgs[1].buf=data[1];
LOGE ( "read data %x %x error\r\n", device_addr, sub_addr );
ret = ioctl(fd, I2C_RDWR, (unsigned long)&vcnl4200_i2c_data);
if(ret < 0)
{
LOGE ( "read data %x %x error\r\n", device_addr, sub_addr );
LOGE("error (errno=%d)", errno);
close ( fd );
free ( vcnl4200_i2c_data.msgs );
return FAIL;
}
int i;
for ( i = 0; i < ByteNo; i++ )
{
LOGE ( " 0x%02x", vcnl4200_i2c_data.msgs[1].buf[i] );
}
close(fd);
free ( vcnl4200_i2c_data.msgs );
return SUCCESS;
}
这个,一般的I2C设备应该都是可以正常读取数据的,但是客户反馈依旧无法读取数据,通过示波器抓取I2C波形可以看到,i2c_msgs的两条message(一个msgs为一条I2C信息,一条写地址,一条读数据)之间存在stop信号,即第一条msgs写完地址后,会发出stop信号,延时后再重新开始第二条msgs读数据。这其中涉及到I2C的repeated start的问题,MTK平台的这个问题早有前人进行了研究分析:《MTK Aandroid 底层驱动-----I2C读写函数分析》
跟代码,发现:
(project)/kernel-3.18/drivers/misc/mediatek/i2c/mt6735/i2c.c:
static void _i2c_translate_msg(struct mt_i2c_t *i2c, struct mt_i2c_msg *msg)
{
/*-------------compatible with 77/75 driver------*/
if (msg->addr & 0xFF00)
msg->ext_flag |= msg->addr & 0xFF00;
I2CINFO(I2C_T_TRANSFERFLOW, "Before i2c transfer .....\n");
i2c->msg_buf = msg->buf;
i2c->msg_len = msg->len;
if (msg->ext_flag & I2C_RS_FLAG)
i2c->st_rs = I2C_TRANS_REPEATED_START;
else
i2c->st_rs = I2C_TRANS_STOP;
if (msg->ext_flag & I2C_DMA_FLAG)
i2c->dma_en = true;
else
i2c->dma_en = false;
if (msg->ext_flag & I2C_WR_FLAG)
i2c->op = I2C_MASTER_WRRD;
else {
if (msg->flags & I2C_M_RD)
i2c->op = I2C_MASTER_RD;
else
i2c->op = I2C_MASTER_WR;
}
if (msg->ext_flag & I2C_POLLING_FLAG)
i2c->poll_en = true;
else
i2c->poll_en = false;
if (msg->ext_flag & I2C_A_FILTER_MSG)
i2c->filter_msg = true;
else
i2c->filter_msg = false;
i2c->delay_len = (msg->timing & 0xff0000) >> 16;
/* Set device speed,set it before set_control register */
if (0 == (msg->timing & 0xFFFF)) {
i2c->mode = ST_MODE;
i2c->speed = MAX_ST_MODE_SPEED;
} else {
if (msg->ext_flag & I2C_HS_FLAG)
i2c->mode = HS_MODE;
else
i2c->mode = FS_MODE;
i2c->speed = msg->timing & 0xFFFF;
}
/*Set ioconfig */
if (msg->ext_flag & I2C_PUSHPULL_FLAG)
i2c->pushpull = true;
else
i2c->pushpull = false;
if (msg->ext_flag & I2C_3DCAMERA_FLAG)
i2c->i2c_3dcamera_flag = true;
else
i2c->i2c_3dcamera_flag = false;
}
看到这里,一开始我以为我知道了MTK为什么会在i2c_msg结构体中引入ext_flag这个成员变量了。附上:《I2C ext_flag解析》。
注意:
▪I2C_WR_FLAG
–Enable write and read tranfer mode, Must set I2C_RS_FLAG ,Don’t support in DMA mode
很好,那就加上ext_flag的配置:
vcnl4200_i2c_data.msgs[0].ext_flag = I2C_WR_FLAG | I2C_RS_FLAG ;
结果发现卵用没有,没记错还报错:
ERROR,961: mt-i2c2: Current I2C Adapter is busy.
然后重新研读了《MTK Aandroid 底层驱动-----I2C读写函数分析》,发现:
g_pstI2Cclient->addr = (i2cId >> 1) & I2C_MASK_FLAG | I2C_WR_FLAG | I2C_RS_FLAG;//加入I2C_RS_FLAG,实现restart模式
他是在地址那里用或操作加上flag的,这就很奇怪了,先不深究这个。在代码中加上:
vcnl4200_i2c_data.msgs[0].addr = 0x6B | I2C_WR_FLAG | I2C_RS_FLAG ;
然后又出现了一个问题,报错:
ERROR,707: WRRD transfer length is not right. trans_len=1,
[I2C]tans_num=1, trans_auxlen=0
跟代码,去到报错代码处:
(project)/kernel-3.18/drivers/misc/mediatek/i2c/mt6735/i2c.c:
static s32 _i2c_get_transfer_len(struct mt_i2c_t *i2c)
{
``````
if (false == i2c->dma_en) { /*non-DMA mode */
if (I2C_MASTER_WRRD != i2c->op) {
``````
} else {
trans_len = (i2c->msg_len) & 0xFF;
trans_auxlen = (i2c->msg_len >> 8) & 0xFF;
trans_num = 2;
data_size = trans_len;
if (!trans_len || !trans_auxlen || trans_len > 8 || trans_auxlen > 8) {
I2CERR(" WRRD transfer length is not right. trans_len=%x,\n"
I2CTAG "tans_num=%x, trans_auxlen=%x\n",
trans_len, trans_num, trans_auxlen);
I2C_BUG_ON(!trans_len || !trans_auxlen || trans_len > 8
|| trans_auxlen > 8);
ret = -EINVAL_I2C;
}
}
} else { /*DMA mode */
``````
}
``````
return ret;
}
trans_auxlen,这个是什么鬼东西?干嘛用的?为什么是从msg_len中取高8位的?继续跟代码:
(project)/kernel-3.18/drivers/misc/mediatek/i2c/mt6735/i2c.c:
static void _i2c_write_reg(struct mt_i2c_t *i2c)
{
``````
i2c_writel(i2c, OFFSET_TRANSFER_LEN, i2c->trans_data.trans_len & 0xFFFF);
i2c_writel(i2c, OFFSET_TRANSFER_LEN_AUX, i2c->trans_data.trans_auxlen & 0xFFFF);
/*Set transaction len */
i2c_writel(i2c, OFFSET_TRANSAC_LEN, i2c->trans_data.trans_num & 0xFF);
``````
}
(project)/kernel-3.18/drivers/misc/mediatek/i2c/mt6735/mt_i2c.h:
enum I2C_REGS_OFFSET {
OFFSET_DATA_PORT = 0x0,
``````
OFFSET_DEBUGCTRL = 0x68,
OFFSET_TRANSFER_LEN_AUX = 0x6C,
};
???什么鬼?写寄存器?0x6c?去查
MT6737_LTE_Smartphone_Application_Processor_Software_Register_Table_V1.0:
must be set to be bigger than 1? 很好
修改代码:
vcnl4200_i2c_data.msgs[0].len = 0x0201;
``````
vcnl4200_i2c_data.msgs[1].len = 0x0202;
完整代码:
unsigned char _i2c_read ( unsigned char device_addr, unsigned char sub_addr, unsigned char * buff, short int ByteNo )
{
unsigned int fd, ret;
struct i2c_rdwr_ioctl_data vcnl4200_i2c_data;
const char * i2c_dev = "/dev/i2c-2";
unsigned char data[2][10];
fd = open ( i2c_dev, O_RDWR );
if ( fd < 0 )
{
LOGE("error (errno=%d)", errno);
LOGE ( "no /dev/i2c-2\r\n" );
return FAIL;
}
ioctl(fd, I2C_TIMEOUT, 2);
ioctl(fd, I2C_RETRIES, 1);
vcnl4200_i2c_data.nmsgs = 2;
vcnl4200_i2c_data.msgs = (struct i2c_msg *)malloc(vcnl4200_i2c_data.nmsgs * sizeof(vcnl4200_i2c_data.msgs));
if(!vcnl4200_i2c_data.msgs)
{
LOGE ( "malloc error" );
close ( fd );
return FAIL;
}
vcnl4200_i2c_data.nmsgs = 2;
vcnl4200_i2c_data.msgs[0].len = 0x0201;
vcnl4200_i2c_data.msgs[0].flags = 0; //write
vcnl4200_i2c_data.msgs[0].addr = 0x6B | I2C_WR_FLAG | I2C_RS_FLAG ;
data[0][0]=0x00;
vcnl4200_i2c_data.msgs[0].buf=data[0];
vcnl4200_i2c_data.msgs[0].ext_flag = I2C_WR_FLAG | I2C_RS_FLAG ;
vcnl4200_i2c_data.msgs[1].len = 0x0202;
vcnl4200_i2c_data.msgs[1].flags = I2C_M_RD; //read
vcnl4200_i2c_data.msgs[1].addr = 0x6B | I2C_WR_FLAG | I2C_RS_FLAG ;
data[1][0]=0;
data[1][1]=0;
vcnl4200_i2c_data.msgs[1].buf=data[1];
vcnl4200_i2c_data.msgs[1].ext_flag = I2C_WR_FLAG | I2C_RS_FLAG ;
LOGE ( "read data %x %x error\r\n", device_addr, sub_addr );
ret = ioctl(fd, I2C_RDWR, (unsigned long)&vcnl4200_i2c_data);
if(ret < 0)
{
LOGE ( "read data %x %x error\r\n", device_addr, sub_addr );
LOGE("error (errno=%d)", errno);
close ( fd );
free ( vcnl4200_i2c_data.msgs );
return FAIL;
}
int i;
for ( i = 0; i < ByteNo; i++ )
{
LOGE ( " 0x%02x", vcnl4200_i2c_data.msgs[1].buf[i] );
}
close(fd);
free ( vcnl4200_i2c_data.msgs );
return SUCCESS;
}
到此,示波器看到的波形是连续的,第一个msgs后是一个repeated start 信号紧接着第二个msgs。
本以为就此结束,任务完成的,可是log显示,最终应用获得的数据与实际i2c返回的数据不一致,根据i2c波形读出的数据应该为0x5a 0x3b,结果到了应用显示却是0x20 0x11,what???什么鬼?
在源码上加了一堆log打印信息,到最后发现,数据没有出错,只是存储的地方变了,当i2c的模式为非restart的时候,读数据时,数据存储在第二条msgs的buf中,当i2c的模式为restart的时候,数据直接就存储在了第一条msgs的buf中,这其中的逻辑问题还需要继续分析。目前的处理方法是直接将msgs[0].buf的数据返回给应用,即:
(project)/kernel-3.18/drivers/i2c/i2c-dev.c:
static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client,
unsigned long arg)
{
``````
res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs-1);
while (i-- > 0) {
if (res >= 0 && (rdwr_pa[i].flags & I2C_M_RD)) {
if (copy_to_user(data_ptrs[i-1], rdwr_pa[i-1].buf,
(rdwr_pa[i].len & 0xff)))
res = -EFAULT;
}
``````
return res;
}