应用层操作I2C(MTK)

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:
应用层操作I2C(MTK)_第1张图片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;
}

你可能感兴趣的:(MTK)