MT8665 Android 5.1 I2C驱动,非DMA方式,无法读写超过8个字节的问题的修改

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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值