MT8665 Android 5.1 I2C驱动,非DMA方式,无法读写超过8个字节的问题的修改
先说问题原因:
是因为MTK驱动,在非DMA方式下,使用FIFO作为数据缓冲,但是,只使用了1次!这个FIFO刚好8字节,只使用1次就是,写满了之后发,或者收满了之后读,所以读写都被限定在8字节。
修改方法:
循环使用FIFO,这也是FIFO设计的初衷,一般硬件上的FIFO大都是loop方式的,我们可以循环往里写数据,或者循环读数据,不受FIFO本身物理大小的限制。
正常来说,循环读写FIFO也是可以使用中断模式去读写的,FIFO满或者空的时候,产生中断,然后去读或者写,但是由于没有MTK芯片资料,不知道对应中断的标志位等信息,所以,下面修改主要是用polling的方式,和中断方式相比,会多占用一些CPU时间!
MTK原始驱动代码分析
MTK的“i2c_adapter”驱动在如下路径:
kernel-3.10\drivers\misc\mediatek\i2c\mt6735\i2c.c
其adapter中的“i2c_algorithm”如下:
static struct i2c_algorithm mt_i2c_algorithm = {
#ifdef USE_I2C_MTK_EXT
#ifdef COMPATIBLE_WITH_AOSP
.master_xfer = standard_i2c_transfer,
#else
.master_xfer = mtk_i2c_transfer,
#endif
#else
.master_xfer = standard_i2c_transfer,
#endif
.smbus_xfer = NULL,
.functionality = mt_i2c_functionality,
};
这里我们代码使用的是“mtk_i2c_transfer”这个函数来做最终的数据传输,追踪这个函数,得到最终执行i2c读写的相关函数如下:
static S32 _i2c_transfer_interface(mt_i2c *i2c)
{
......
return_value=_i2c_get_transfer_len(i2c); //获取需要传输的数据长度等信息
if ( return_value < 0 ){
I2CERR("_i2c_get_transfer_len fail,return_value=%d\n",return_value);
ret =-EINVAL_I2C;
goto err;
}
......
return_value=i2c_set_speed(i2c); //设置i2c速度,100K,400K等
if ( return_value < 0 ){
I2CERR("i2c_set_speed fail,return_value=%d\n",return_value);
ret =-EINVAL_I2C;
goto err;
}
......
spin_lock(&i2c->lock);
_i2c_write_reg(i2c); //设置i2c外设寄存器,准备传输数据
/*All register must be prepared before setting the start bit [SMP]*/
......
/*Start the transfer*/
i2c_writel(i2c, OFFSET_START, 0x0001); //发送起始位,开始数据传输
spin_unlock(&i2c->lock);
ret = _i2c_deal_result(i2c); //处理传输结果
I2CINFO(I2C_T_TRANSFERFLOW, "After i2c transfer .....\n");
err:
end:
return ret;
}
在这里我们主要关注下面3个函数:
return_value=_i2c_get_transfer_len(i2c); //获取需要传输的数据长度等信息
_i2c_write_reg(i2c); //设置i2c外设寄存器,准备传输数据
ret = _i2c_deal_result(i2c); //处理传输结果
代码分析如下:
static S32 _i2c_get_transfer_len(mt_i2c *i2c)
{
S32 ret = I2C_OK;
u16 trans_num = 0;
u16 data_size = 0;
u16 trans_len = 0;
u16 trans_auxlen = 0;
/*Get Transfer len and transaux len*/
if(FALSE == i2c->dma_en)
{ /*non-DMA mode*/
if(I2C_MASTER_WRRD != i2c->op)
{
trans_len = (i2c->msg_len) & 0xFFFF;
trans_num = (i2c->msg_len >> 16) & 0xFF;
if(0 == trans_num)
trans_num = 1;
trans_auxlen = 0;
data_size = trans_len*trans_num;
//在WR或者RD的时候,trans_num会一直为1,所以“trans_len*trans_num”就是要传输的数据长度
//可以看到,这里做了最大8字节限制
if(!trans_len || !trans_num || trans_len*trans_num > 8)
{
I2CERR(" non-WRRD transfer length is not right. trans_len=%x, tans_num=%x, trans_auxlen=%x\n", trans_len, trans_num, trans_auxlen);
I2C_BUG_ON(!trans_len || !trans_num || trans_len*trans_num > 8);
ret = -EINVAL_I2C;
}
} else
{
......
}
}
else
{ /*DMA mode*/
......
}
//这里把获取到的数据长度保存起来
i2c->trans_data.trans_num = trans_num;
i2c->trans_data.trans_len = trans_len;
i2c->trans_data.data_size = data_size;
i2c->trans_data.trans_auxlen = trans_auxlen;
return ret;
}
static void _i2c_write_reg(mt_i2c *i2c)
{
U8 *ptr = i2c->msg_buf;
U32 data_size=i2c->trans_data.data_size;
U32 addr_reg=0;
......
/*Set slave address*/
addr_reg = i2c->read_flag ? ((i2c->addr << 1) | 0x1) : ((i2c->addr << 1) & ~0x1);
i2c_writel(i2c, OFFSET_SLAVE_ADDR, addr_reg);
/*Clear interrupt status*/
i2c_writel(i2c, OFFSET_INTR_STAT, (I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP));
/*Clear fifo address*/
i2c_writel(i2c, OFFSET_FIFO_ADDR_CLR, 0x0001);
/*Setup the interrupt mask flag*/
if(i2c->poll_en) //如果使用polling模式,则disable掉中断
i2c_writel(i2c, OFFSET_INTR_MASK, i2c_readl(i2c, OFFSET_INTR_MASK) & ~(I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP)); /*Disable interrupt*/
else
i2c_writel(i2c, OFFSET_INTR_MASK, i2c_readl(i2c, OFFSET_INTR_MASK) | (I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP)); /*Enable interrupt*/
/*Set transfer len */ //设置一下要传输的数据长度
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);
/*Prepare buffer data to start transfer*/
if(i2c->dma_en){
......
}
else
{
/*Set fifo mode data*/
if (I2C_MASTER_RD == i2c->op)
{
/*do not need set fifo data*/ //RD的话,这里不对fifo操作,等下从fifo读数据就可以
}else
{ /*both write && write_read mode*/ //WR的话,这里往fifo写数据,先写的数据,会先发送
while (data_size--) //因为前面做了8字节限制,所以这里data_size不会超8字节
{ //这里也是为什么只能写8字节的原因,因为对fifo的写入,只在这里做一次,后面就只等着传输完成了
i2c_writel(i2c, OFFSET_DATA_PORT, *ptr);
ptr++;
}
}
}
/*Set trans_data*/
i2c->trans_data.data_size = data_size;
......
}
static S32 _i2c_deal_result(mt_i2c *i2c)
{
#ifdef I2C_DRIVER_IN_KERNEL
long tmo = i2c->adap.timeout;
#else
long tmo = 1;
#endif
U16 data_size = 0;
U8 *ptr = i2c->msg_buf;
S32 ret = i2c->msg_len;
long tmo_poll = 0xffff;
int dma_err=0;
if(i2c->poll_en)
{/*master read && poll mode*/
for (;;)
{ /*check the interrupt status register*/
i2c->irq_stat = i2c_readl(i2c, OFFSET_INTR_STAT);
//如果使用polling模式,就循环读状态寄存器,如果出错,或者传输完成,则退出读状态寄存器
if(i2c->irq_stat & (I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP))
{
atomic_set(&i2c->trans_stop, 1);
spin_lock(&i2c->lock);
/*Clear interrupt status,write 1 clear*/
i2c_writel(i2c, OFFSET_INTR_STAT, (I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP));
spin_unlock(&i2c->lock);
break;
}
tmo_poll --;
if(tmo_poll == 0) { //如果超时还没有传输完成,也退出读状态
tmo = 0;
break;
}
}
} else { /*Interrupt mode,wait for interrupt wake up*/
//如果是中断模式,则在这里等待中断事件(出错,或者传输完成)wake_up进程
tmo = wait_event_timeout(i2c->wait,atomic_read(&i2c->trans_stop), tmo);
}
//不管是polling模式,还是中断模式,运行到这里,肯定出现了如下几种状态:超时,传输完成,传输出错
/*Save status register status to i2c struct*/
#ifdef I2C_DRIVER_IN_KERNEL
if (i2c->irq_stat & I2C_TRANSAC_COMP) {
atomic_set(&i2c->trans_err, 0);
atomic_set(&i2c->trans_comp, 1);
}
atomic_set(&i2c->trans_err, i2c->irq_stat & (I2C_HS_NACKERR | I2C_ACKERR));
#endif
/*Check the transfer status*/
if (!(tmo == 0 || atomic_read(&i2c->trans_err)) )
{//这里处理正常传输完成的情况,如果是RD,则读fifo,如果是WR,则不用做任何事情
/*Transfer success ,we need to get data from fifo*/
if((!i2c->dma_en) && (i2c->op == I2C_MASTER_RD || i2c->op == I2C_MASTER_WRRD))
{ /*only read mode or write_read mode and fifo mode need to get data*/
data_size = (i2c_readl(i2c, OFFSET_FIFO_STAT) >> 4) & 0x000F;
while (data_size--) //这里去读fifo收到的数据,先收到的数据,会先读出来,同样data_size大小不会超过8字节
{//这里也是为什么读不能超过8字节的原因,因为是在传输完成后,只读一次,如果传输多与8字节,就丢数据了
*ptr = i2c_readl(i2c, OFFSET_DATA_PORT);
ptr++;
}
}
if(i2c->dma_en)
{
......
}
}else
{//这里处理超时或传输出错的情况
/*Timeout or ACKERR*/
if ( tmo == 0 ){
I2CERR("id=%d,addr: %x, transfer timeout\n",i2c->id, i2c->addr);
ret = -ETIMEDOUT_I2C;
} else
{
I2CERR("id=%d,addr: %x, transfer error\n",i2c->id,i2c->addr);
ret = -EREMOTEIO_I2C;
}
if (i2c->irq_stat & I2C_HS_NACKERR)
I2CERR("I2C_HS_NACKERR\n");
if (i2c->irq_stat & I2C_ACKERR)
I2CERR("I2C_ACKERR\n");
if (i2c->filter_msg==FALSE) //TEST
{
_i2c_dump_info(i2c);
}
......
}
return ret;
}
修改之后驱动代码
static S32 _i2c_get_transfer_len(mt_i2c *i2c)
{
......
/*Get Transfer len and transaux len*/
if(FALSE == i2c->dma_en)
{ /*non-DMA mode*/
if(I2C_MASTER_WRRD != i2c->op)
{
......
//**********************************************************************
//修改这里的判断条件,polling模式,不去限制要传输的字节大小
//if(!trans_len || !trans_num || trans_len*trans_num > 8)
if( (!trans_len || !trans_num || trans_len*trans_num > 8) && (!i2c->poll_en) )
//**********************************************************************
{
......
}
} else
{
......
}
}
else
{ /*DMA mode*/
......
}
......
return ret;
}
static void _i2c_write_reg(mt_i2c *i2c)
{
......
/*Prepare buffer data to start transfer*/
if(i2c->dma_en){
......
}
else
{
/*Set fifo mode data*/
if (I2C_MASTER_RD == i2c->op)
{
/*do not need set fifo data*/ //RD的话,这里不对fifo操作,等下再从fifo读数据
}else
{
//**********************************************************************
//如果是WR,并且是polling模式,则在这里先给fifo写一个字节,后面再去写剩余字节
//避免start之后,在下一个写fifo操作之间,间隔时间过长,fifo为空的情况
if(i2c->poll_en && (I2C_MASTER_WR == i2c->op))
{
i2c_writel(i2c, OFFSET_DATA_PORT, *ptr);
}
//如果是非polling模式,或者是WRRD,则保持之前逻辑
else
{
/*both write && write_read mode*/
while (data_size--)
{
i2c_writel(i2c, OFFSET_DATA_PORT, *ptr);
ptr++;
}
}
//**********************************************************************
}
}
/*Set trans_data*/
i2c->trans_data.data_size = data_size;
......
}
static S32 _i2c_deal_result(mt_i2c *i2c)
{
......
U8 *ptr = i2c->msg_buf;
S32 ret = i2c->msg_len;
if(i2c->poll_en)
{/*master read && poll mode*/
for (;;)
{ /*check the interrupt status register*/
i2c->irq_stat = i2c_readl(i2c, OFFSET_INTR_STAT);
//**********************************************************************
//如果剩余数据长度大于0,才去执行操作
if(ret > 0)
{
//查看fifo目前已被写入的字节数
//当fifo被写入数据时,这个寄存器自加,当fifo数据被读取时,这个寄存器自减
//这里的“写入”,意思是接收数据时I2C控制器硬件写入收到的数据,或者发送数据时从msg_buf写入要发送的数据
//这里的“读取”,意思是接收数据时将fifo数据读取到msg_buf,或者发送数据时I2C控制器硬件从fifo读取要发送的数据
data_size = (i2c_readl(i2c, OFFSET_FIFO_STAT) >> 4) & 0x000F;
//如果是RD,并且fifo内部有数据,则读取data_size个字节
if((data_size > 0) && (i2c->op == I2C_MASTER_RD))
{
while (data_size--)
{
*ptr = i2c_readl(i2c, OFFSET_DATA_PORT);
ptr++; //每读一个字节,msg_buf的指针ptr自加
ret--; //每读一个字节,剩余数据长度ret自减
if(ret == 0) //如果数据读完了,则自动退出
break;
}
}
//如果是WR,并且fifo内部数据不满8个,则写入(8-data_size)个字节
else if((data_size < 8) && (i2c->op == I2C_MASTER_WR))
{
data_size = 8 - data_size;
while (data_size--)
{
ret--; //先减一,因为之前函数已经先写入了一个字节
if(ret == 0) //如果数据发完了,则自动退出
break;
ptr++; //先加一,因为之前函数已经先写入了一个字节
i2c_writel(i2c, OFFSET_DATA_PORT, *ptr);
}
}
}
//**********************************************************************
//如果使用polling模式,就循环读状态寄存器,如果出错,或者传输完成,则退出读状态寄存器
if(i2c->irq_stat & (I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP))
{
atomic_set(&i2c->trans_stop, 1);
spin_lock(&i2c->lock);
/*Clear interrupt status,write 1 clear*/
i2c_writel(i2c, OFFSET_INTR_STAT, (I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP));
spin_unlock(&i2c->lock);
break;
}
tmo_poll --;
if(tmo_poll == 0) { //如果超时还没有传输完成,也退出读状态
tmo = 0;
break;
}
}
} else { /*Interrupt mode,wait for interrupt wake up*/
//如果是中断模式,则在这里等待中断事件(出错,或者传输完成)wake_up进程
tmo = wait_event_timeout(i2c->wait,atomic_read(&i2c->trans_stop), tmo);
}
......
/*Check the transfer status*/
if (!(tmo == 0 || atomic_read(&i2c->trans_err)) )
{
//**********************************************************************
//由于polling模式在上面已经读完了数据,所以如果是RD,只有在非polling模式才再去读数据
//如果是WR这里本来也没有做其他操作,不用关心,其他WRRD依然在这里读数据
/*Transfer success ,we need to get data from fifo*/
//if((!i2c->dma_en) && (i2c->op == I2C_MASTER_RD || i2c->op == I2C_MASTER_WRRD))
if((!i2c->dma_en) && ((i2c->op == I2C_MASTER_RD && (!i2c->poll_en)) || i2c->op == I2C_MASTER_WRRD))
//**********************************************************************
{ /*only read mode or write_read mode and fifo mode need to get data*/
data_size = (i2c_readl(i2c, OFFSET_FIFO_STAT) >> 4) & 0x000F;
while (data_size--)
{
*ptr = i2c_readl(i2c, OFFSET_DATA_PORT);
ptr++;
}
}
if(i2c->dma_en)
{
......
}
}else
{//这里处理超时或传输出错的情况
/*Timeout or ACKERR*/
......
}
return ret;
}