解决一个I2C读写问题

嵌入式Linux

共 12759字,需浏览 26分钟

 · 2021-01-29

之前写关于I2C相关的文章

排查一个触摸屏驱动问题

MTK 平台TP调试遇坑

1、问题

今天遇到一个问题,我们有一个芯片,I2C读写失败,导致录音有问题,而且是偶现的。

log提示看到是返回 -6

<3>[  730.336308]  (3)[2085:tinycap]es7243_read error2, ret = -6.

下面流程找一下这个 -6 是从哪里来的

2、I2c 发送流程

i2c-core.c

调用发送函数

/**
 * __i2c_transfer - unlocked flavor of i2c_transfer
 * @adap: Handle to I2C bus
 * @msgs: One or more messages to execute before STOP is issued to
 * terminate the operation; each message begins with a START.
 * @num: Number of messages to be executed.
 *
 * Returns negative errno, else the number of messages executed.
 *
 * Adapter lock must be held when calling this function. No debug logging
 * takes place. adap->algo->master_xfer existence isn't checked.
 */

int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
 unsigned long orig_jiffies;
 int ret, try;

 if (adap->quirks && i2c_check_for_quirks(adap, msgs, num))
  return -EOPNOTSUPP;

 /* i2c_trace_msg gets enabled when tracepoint i2c_transfer gets
  * enabled.  This is an efficient way of keeping the for-loop from
  * being executed when not needed.
  */

 if (static_key_false(&i2c_trace_msg)) {
  int i;
  for (i = 0; i < num; i++)
   if (msgs[i].flags & I2C_M_RD)
    trace_i2c_read(adap, &msgs[i], i);
   else
    trace_i2c_write(adap, &msgs[i], i);
 }

 /* Retry automatically on arbitration loss */
 orig_jiffies = jiffies;
 for (ret = 0try = 0try <= adap->retries; try++) {
  ret = adap->algo->master_xfer(adap, msgs, num);
  if (ret != -EAGAIN)
   break;
  if (time_after(jiffies, orig_jiffies + adap->timeout))
   break;
 }

 if (static_key_false(&i2c_trace_msg)) {
  int i;
  for (i = 0; i < ret; i++)
   if (msgs[i].flags & I2C_M_RD)
    trace_i2c_reply(adap, &msgs[i], i);
  trace_i2c_result(adap, i, ret);
 }

 return ret;
}
EXPORT_SYMBOL(__i2c_transfer);

里面有一句

ret = adap->algo->master_xfer(adap, msgs, num);

这里需要找到初始化的位置,这种指针初始化的位置,一般都是和平台设备相关的,这里也是一个技巧,初学者看到这类指针一般都会比较懵。

所以,就开始去找吧。

因为我用的是MTK平台,所以需要去找到MTK平台相关里面初始化的这个指针

i2c-mt65xx.c (D:\source\kernel-4.4\drivers\i2c\busses) line 755 :  .master_xfer = mtk_i2c_transfer,

在文件

i2c-mt65xx.c

这里初始化了

static const struct i2c_algorithm mtk_i2c_algorithm = {
 .master_xfer = mtk_i2c_transfer,
 .functionality = mtk_i2c_functionality,
};

然后再调用发送函数

static int mtk_i2c_transfer(struct i2c_adapter *adap,
       struct i2c_msg msgs[], int num)

{
 int ret;
 int left_num = num;
 struct mtk_i2c *i2c = i2c_get_adapdata(adap);

 ret = mtk_i2c_clock_enable(i2c);
 if (ret)
  return ret;

 i2c->auto_restart = i2c->dev_comp->auto_restart;

 /* checking if we can skip restart and optimize using WRRD mode */
 if (i2c->auto_restart && num == 2) {
  if (!(msgs[0].flags & I2C_M_RD) && (msgs[1].flags & I2C_M_RD) &&
      msgs[0].addr == msgs[1].addr) {
   i2c->auto_restart = 0;
  }
 }

 if (i2c->auto_restart && num >= 2 && i2c->speed_hz > MAX_FS_MODE_SPEED)
  /* ignore the first restart irq after the master code,
   * otherwise the first transfer will be discarded.
   */

  i2c->ignore_restart_irq = true;
 else
  i2c->ignore_restart_irq = false;

 while (left_num--) {
  if (!msgs->buf) {
   dev_dbg(i2c->dev, "data buffer is NULL.\n");
   ret = -EINVAL;
   goto err_exit;
  }

  if (msgs->flags & I2C_M_RD)
   i2c->op = I2C_MASTER_RD;
  else
   i2c->op = I2C_MASTER_WR;

  if (!i2c->auto_restart) {
   if (num > 1) {
    /* combined two messages into one transaction */
    i2c->op = I2C_MASTER_WRRD;
    left_num--;
   }
  }

  /* len <= 8: use fifo mode, len > 8: use DMA mode. */
  ret = mtk_i2c_do_transfer(i2c, msgs, num, left_num);
  if (ret < 0)
   goto err_exit;

  msgs++;
 }
 /* the return value is number of executed messages */
 ret = num;

err_exit:
 mtk_i2c_clock_disable(i2c);
 return ret;
}

3、返回 值的代码位置

在文件

i2c-mt65xx.c

中的函数

static int mtk_i2c_do_transfer(struct mtk_i2c *i2c, struct i2c_msg *msgs,
          int num, int left_num)

{
 bool dma_en;
 u8 *data_buf;
 u16 data_len;
 u16 read_len;
 u16 addr_reg;
 u16 start_reg;
 u16 control_reg;
 u16 restart_flag = 0;
 u32 reg_4g_mode;
 dma_addr_t rpaddr = 0;
 dma_addr_t wpaddr = 0;
 int ret;

 i2c->irq_stat = 0;

 if (i2c->auto_restart)
  restart_flag = I2C_RS_TRANSFER;

 reinit_completion(&i2c->msg_complete);

 control_reg = readw(i2c->base + OFFSET_CONTROL) &
   ~(I2C_CONTROL_DIR_CHANGE | I2C_CONTROL_RS);
 if ((i2c->speed_hz > 400000) || (left_num >= 1))
  control_reg |= I2C_CONTROL_RS;

 if (i2c->op == I2C_MASTER_WRRD)
  control_reg |= I2C_CONTROL_DIR_CHANGE | I2C_CONTROL_RS;

 writew(control_reg, i2c->base + OFFSET_CONTROL);

 /* set start condition */
 if (i2c->speed_hz <= 100000)
  writew(I2C_ST_START_CON, i2c->base + OFFSET_EXT_CONF);
 else
  writew(I2C_FS_START_CON, i2c->base + OFFSET_EXT_CONF);

 addr_reg = msgs->addr << 1;
 if (i2c->op == I2C_MASTER_RD)
  addr_reg |= 0x1;

 writew(addr_reg, i2c->base + OFFSET_SLAVE_ADDR);

 /* Clear interrupt status */
 writew(restart_flag | I2C_HS_NACKERR | I2C_ACKERR |
        I2C_TRANSAC_COMP, i2c->base + OFFSET_INTR_STAT);
 writew(I2C_FIFO_ADDR_CLR, i2c->base + OFFSET_FIFO_ADDR_CLR);

 /* Enable interrupt */
 writew(restart_flag | I2C_HS_NACKERR | I2C_ACKERR |
        I2C_TRANSAC_COMP, i2c->base + OFFSET_INTR_MASK);

 /* Set transfer and transaction len */
 if (i2c->op == I2C_MASTER_WRRD) {
  if (i2c->dev_comp->aux_len_reg) {
   writew(msgs->len, i2c->base + OFFSET_TRANSFER_LEN);
   writew((msgs + 1)->len, i2c->base +
          OFFSET_TRANSFER_LEN_AUX);
  } else {
   writew(msgs->len | ((msgs + 1)->len) << 8,
          i2c->base + OFFSET_TRANSFER_LEN);
  }
  writew(I2C_WRRD_TRANAC_VALUE, i2c->base + OFFSET_TRANSAC_LEN);
 } else {
  writew(msgs->len, i2c->base + OFFSET_TRANSFER_LEN);
  writew(num, i2c->base + OFFSET_TRANSAC_LEN);
 }

 /* Prepare buffer data to start transfer */
 if (msgs->len > I2C_FIFO_SIZE || (i2c->op == I2C_MASTER_WRRD &&
     (msgs + 1)->len > I2C_FIFO_SIZE)) {
  dma_en = true;

  if (i2c->op == I2C_MASTER_RD) {
   writel(I2C_DMA_INT_FLAG_NONE, i2c->pdmabase + OFFSET_INT_FLAG);
   writel(I2C_DMA_CON_RX, i2c->pdmabase + OFFSET_CON);
   rpaddr = dma_map_single(i2c->dev, msgs->buf,
      msgs->len, DMA_FROM_DEVICE);
   if (dma_mapping_error(i2c->dev, rpaddr))
    return -ENOMEM;

   if (i2c->dev_comp->support_33bits) {
    reg_4g_mode = mtk_i2c_set_4g_mode(rpaddr);
    writel(reg_4g_mode, i2c->pdmabase + OFFSET_RX_4G_MODE);
   }

   writel((u32)rpaddr, i2c->pdmabase + OFFSET_RX_MEM_ADDR);
   writel(msgs->len, i2c->pdmabase + OFFSET_RX_LEN);
  } else if (i2c->op == I2C_MASTER_WR) {
   writel(I2C_DMA_INT_FLAG_NONE, i2c->pdmabase + OFFSET_INT_FLAG);
   writel(I2C_DMA_CON_TX, i2c->pdmabase + OFFSET_CON);
   wpaddr = dma_map_single(i2c->dev, msgs->buf,
      msgs->len, DMA_TO_DEVICE);
   if (dma_mapping_error(i2c->dev, wpaddr))
    return -ENOMEM;

   if (i2c->dev_comp->support_33bits) {
    reg_4g_mode = mtk_i2c_set_4g_mode(wpaddr);
    writel(reg_4g_mode, i2c->pdmabase + OFFSET_TX_4G_MODE);
   }

   writel((u32)wpaddr, i2c->pdmabase + OFFSET_TX_MEM_ADDR);
   writel(msgs->len, i2c->pdmabase + OFFSET_TX_LEN);
  } else {
   writel(I2C_DMA_CLR_FLAG, i2c->pdmabase + OFFSET_INT_FLAG);
   writel(I2C_DMA_CLR_FLAG, i2c->pdmabase + OFFSET_CON);
   wpaddr = dma_map_single(i2c->dev, msgs->buf,
      msgs->len, DMA_TO_DEVICE);
   if (dma_mapping_error(i2c->dev, wpaddr))
    return -ENOMEM;
   rpaddr = dma_map_single(i2c->dev, (msgs + 1)->buf,
      (msgs + 1)->len, DMA_FROM_DEVICE);
   if (dma_mapping_error(i2c->dev, rpaddr)) {
    dma_unmap_single(i2c->dev, wpaddr,
       msgs->len, DMA_TO_DEVICE);
    return -ENOMEM;
   }

   if (i2c->dev_comp->support_33bits) {
    reg_4g_mode = mtk_i2c_set_4g_mode(wpaddr);
    writel(reg_4g_mode, i2c->pdmabase + OFFSET_TX_4G_MODE);

    reg_4g_mode = mtk_i2c_set_4g_mode(rpaddr);
    writel(reg_4g_mode, i2c->pdmabase + OFFSET_RX_4G_MODE);
   }

   writel((u32)wpaddr, i2c->pdmabase + OFFSET_TX_MEM_ADDR);
   writel((u32)rpaddr, i2c->pdmabase + OFFSET_RX_MEM_ADDR);
   writel(msgs->len, i2c->pdmabase + OFFSET_TX_LEN);
   writel((msgs + 1)->len, i2c->pdmabase + OFFSET_RX_LEN);
  }

  if (i2c->op != I2C_MASTER_RD)
   writel(I2C_DMA_START_EN, i2c->pdmabase + OFFSET_EN);
 } else {
  dma_en = false;

  if (i2c->op != I2C_MASTER_RD) {
   data_buf = msgs->buf;
   data_len = msgs->len;

   while (data_len--)
    writew_relaxed(*(data_buf++), i2c->base + OFFSET_DATA_PORT);
  }
 }

 if (!i2c->auto_restart) {
  start_reg = I2C_TRANSAC_START;
 } else {
  start_reg = I2C_TRANSAC_START | I2C_RS_MUL_TRIG;
  if (left_num >= 1)
   start_reg |= I2C_RS_MUL_CNFG;
 }
 writew(start_reg, i2c->base + OFFSET_START);

 if (dma_en && i2c->op == I2C_MASTER_RD)
  writel(I2C_DMA_START_EN, i2c->pdmabase + OFFSET_EN);

 ret = wait_for_completion_timeout(&i2c->msg_complete,
       i2c->adap.timeout);

 /* Clear interrupt mask */
 writew(~(restart_flag | I2C_HS_NACKERR | I2C_ACKERR |
        I2C_TRANSAC_COMP), i2c->base + OFFSET_INTR_MASK);

 if (dma_en) {
  if (i2c->op == I2C_MASTER_WR) {
   dma_unmap_single(i2c->dev, wpaddr,
      msgs->len, DMA_TO_DEVICE);
  } else if (i2c->op == I2C_MASTER_RD) {
   dma_unmap_single(i2c->dev, rpaddr,
      msgs->len, DMA_FROM_DEVICE);
  } else {
   dma_unmap_single(i2c->dev, wpaddr, msgs->len,
      DMA_TO_DEVICE);
   dma_unmap_single(i2c->dev, rpaddr, (msgs + 1)->len,
      DMA_FROM_DEVICE);
  }
 }

 if (ret == 0) {
  dev_dbg(i2c->dev, "addr: %x, transfer timeout\n", msgs->addr);
  mtk_i2c_init_hw(i2c);
  return -ETIMEDOUT;
 }

 completion_done(&i2c->msg_complete);

 if (i2c->irq_stat & (I2C_HS_NACKERR | I2C_ACKERR)) {
  dev_dbg(i2c->dev, "addr: %x, transfer ACK error\n", msgs->addr);
  mtk_i2c_init_hw(i2c);
  return -ENXIO;
 }

 if (!dma_en && i2c->op != I2C_MASTER_WR) {
  data_buf = (i2c->op == I2C_MASTER_RD) ? msgs->buf :
      (msgs + 1)->buf;
  data_len = (i2c->op == I2C_MASTER_RD) ? msgs->len :
      (msgs + 1)->len;
  read_len = (readw(i2c->base + OFFSET_FIFO_STAT) >> 4) & 0xf;

  if (read_len == data_len) {
   while (data_len--)
    *(data_buf++) = readw_relaxed(i2c->base + OFFSET_DATA_PORT);
  } else {
   dev_dbg(i2c->dev, "data_len %x, read_len %x, fifo read len error\n",
    data_len, read_len);
   mtk_i2c_init_hw(i2c);
   return -EIO;
  }
 }

 return 0;
}

下面这段代码

 if (i2c->irq_stat & (I2C_HS_NACKERR | I2C_ACKERR)) {
  dev_dbg(i2c->dev, "addr: %x, transfer ACK error\n", msgs->addr);
  mtk_i2c_init_hw(i2c);
  return -ENXIO;
 }

宏定义

errno-base.h


#define ENXIO   6 /* No such device or address */

表示就是找不到设备,没有应答

我用逻辑分析仪抓波形,确实存在没有应答的情况下打印了上面的错误日志。


解决

问题日志

<6>[ 1594.422871]  (3)[1663:AudioInMic][ES7243]: es7243_pcm_startup() line: 444 start.
<6>[ 1594.432778]  (3)[1663:AudioInMic][ES7243]: es7243_pcm_startup() line: 444 start.
<6>[ 1594.432902]  (3)[1663:AudioInMic][ES7243]: es7243_pcm_hw_params() line: 472 es7243_pcm_hw_params
<6>[ 1594.432918]  (3)[1663:AudioInMic][ES7243]: es7243_set_bias_level() line: 568 level:1
<6>[ 1594.433367]  (3)[1663:AudioInMic][ES7243]: es7243_write() line: 161 es7243_write reg:0x00 val:0x09 ok
<6>[ 1594.433741]  (0)[1663:AudioInMic][ES7243]: es7243_write() line: 161 es7243_write reg:0x06 val:0x18 ok
<6>[ 1594.434121]  (2)[1663:AudioInMic][ES7243]: es7243_write() line: 161 es7243_write reg:0x07 val:0x80 ok
<6>[ 1594.434506]  (0)[1663:AudioInMic][ES7243]: es7243_write() line: 161 es7243_write reg:0x01 val:0x0c ok
<6>[ 1594.434886]  (0)[1663:AudioInMic][ES7243]: es7243_write() line: 161 es7243_write reg:0x02 val:0x10 ok
<6>[ 1594.435288]  (2)[1663:AudioInMic][ES7243]: es7243_write() line: 161 es7243_write reg:0x03 val:0x04 ok
<6>[ 1594.435682]  (3)[1663:AudioInMic][ES7243]: es7243_write() line: 161 es7243_write reg:0x04 val:0x02 ok
<6>[ 1594.436136]  (1)[1663:AudioInMic][ES7243]: es7243_write() line: 161 es7243_write reg:0x05 val:0x1a ok
<6>[ 1594.436574]  (3)[1663:AudioInMic][ES7243]: es7243_write() line: 161 es7243_write reg:0x09 val:0x3f ok
<6>[ 1594.437004]  (0)[1663:AudioInMic][ES7243]: es7243_write() line: 161 es7243_write reg:0x06 val:0x00 ok
<6>[ 1594.437424]  (3)[1663:AudioInMic][ES7243]: es7243_write() line: 161 es7243_write reg:0x05 val:0x36 ok
<3>[ 1594.437676]  (3)[1663:AudioInMic]es7243_write 13 error->[REG-0x08,val-0x07]
<3>[ 1594.437907]  (3)[1663:AudioInMic]es7243_read error1
<3>[ 1594.438220]  (3)[1663:AudioInMic]es7243_write 13 error->[REG-0x01,val-0x8c]
<6>[ 1594.438246]  (3)[1663:AudioInMic][ES7243]: es7243_update_bits() line: 175 value:12
<3>[ 1594.438951]  (3)[1663:AudioInMic]es7243_read error2, ret = -6.
<6>[ 1594.438990]  (3)[1663:AudioInMic][ES7243]: es7243_pcm_hw_params() line: 538 es7243_pcm_hw_params538 -- Reg 08 ----> 0x0!
<3>[ 1594.439477]  (3)[1663:AudioInMic]es7243_read error2, ret = -6.
<6>[ 1594.439510]  (3)[1663:AudioInMic][ES7243]: es7243_pcm_hw_params() line: 540 es7243_pcm_hw_params540 -- Reg 05 ----> 0x0!

复现了十几把,发现每次都是刚开始I2C可以读写,后面再读写的时候,就容易出现I2C操作失败的情况。

FAE给的答复是

  • I2C走线是否有其他干扰
  • I2C有没有加RC滤波电路

硬件已经确定了,想再修改硬件可能性比较小,也比较麻烦,硬件测量了I2C波形,也没有发现异常。故此,还是想在软件上做规避。

我猜测可能是芯片操作过快,芯片没有来得及反应,而且每次操作开始I2C都是可以读写的,所以就想在读写函数中阻塞一会会来规避这个问题。

patch


diff --git a/kernel-4.4/sound/soc/codecs/es7243.c b/kernel-4.4/sound/soc/codecs/es7243.c
index 272f5dbf88..0e6a1e6bcc 100755
--- a/kernel-4.4/sound/soc/codecs/es7243.c
+++ b/kernel-4.4/sound/soc/codecs/es7243.c
@@ -32,6 +32,7 @@
 #include 
 #include 
 #include 
+#include 
 #ifdef CONFIG_OF
 #include 
 #include 
@@ -127,6 +128,7 @@ static int es7243_read(u8 reg, u8 *rt_value, struct i2c_client *client)
        read_cmd[0] = reg;
        cmd_len = 1;

+       udelay(2000);
        if (client->adapter == NULL)
                pr_err("es7243_read client->adapter==NULL\n");

@@ -141,6 +143,7 @@ static int es7243_read(u8 reg, u8 *rt_value, struct i2c_client *client)
                pr_err("es7243_read error2, ret = %d.\n", ret);
                return -1;
        }
+
        ES7243_DEBUG("es7243_read OK\n");
        return 0;
 }
@@ -153,12 +156,14 @@ static int es7243_write(u8 reg, unsigned char value, struct i2c_client *client)
        write_cmd[0] = reg;
        write_cmd[1] = value;

+       udelay(2000);
        ret = i2c_master_send(client, write_cmd, 2);
        if (ret != 2) {
                pr_err("es7243_write %x error->[REG-0x%02x,val-0x%02x]\n",client->addr,reg,value);
                return -1;
        }
        ES7243_DEBUG("es7243_write reg:0x%.2x val:0x%.2x ok \n",reg,value);
+
        return 0;
 }

修改后,验证了十几把,确实没有再发现问题,如果再有问题的话,再分析。




推荐阅读:
专辑|Linux文章汇总
专辑|程序人生
专辑|C语言
我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎转发,在看,评论~



浏览 140
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报