加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
  • 推荐器件
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

技术 | I2C 子系统(十)

2022/06/15
4759
阅读需 30 分钟
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

4、I2C Data Transfer

I2C 数据传输主要有三个 API

int i2c_master_send(const struct i2c_client *client,const char *buf,int count)

client:I2C 设备对应的 i2c_client。
buf:要发送的数据。
count:要发送的数据字节数,要小于 64KB,以为 i2c_msg 的 len 成员变量是一个 u16(无符号 16 位)类型的数据。
返回值:负值,失败,其他非负值,发送的字节数。

int i2c_master_recv(const struct i2c_client *client,char *buf,int count)
client:I2C 设备对应的 i2c_client。
buf:要接收的数据。
count:要接收的数据字节数,要小于 64KB,以为 i2c_msg 的 len 成员变量是一个 u16(无符号 16 位)类型的数据。
返回值:负值,失败,其他非负值,发送的字节数。

int i2c_transfer(struct i2c_adapter *adap,struct i2c_msg *msgs,int num)

adap:所使用的 I2C 适配器,i2c_client 会保存其对应的 i2c_adapter。
msgs:I2C 要发送的一个或多个消息。
num:消息数量,也就是 msgs 的数量。
返回值:负值,失败,其他非负值,发送的 msgs 数量。

i2c_master_send 和 i2c_master_recv 都是对 i2c_transfer 的封装。因此我们重点研究 i2c_transfer。

其中,adap->algo->master_xfer 由芯片原厂提供。在 MTK 平台,是 mtk_i2c_transfer 函数,不同平台命名不同。

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;

  //初始化硬件
 mtk_i2c_init_hw(i2c);

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

 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)
  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--;
   }
  }

  /* always 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;
}

mtk_i2c_transfer 再调用 mtk_i2c_do_transfer,这一步就是最底层的 I2C 传输了,大量操作寄存器

该函数和芯片底层强相关,是最底层的实现,一般情况下不会修改。重要的地方博主放了注释。

static int mtk_i2c_do_transfer(struct mtk_i2c *i2c, struct i2c_msg *msgs,int num, int left_num)
{
 u16 addr_reg;
 u16 start_reg;
 u16 control_reg;
 u16 restart_flag = 0;
 u32 reg_4g_mode;
 u8 *w_buf = NULL;
 u8 *r_buf = NULL;
 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);

 /* 设置开始条件 */
 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 = i2c_8bit_addr_from_msg(msgs);
 writew(addr_reg, i2c->base + OFFSET_SLAVE_ADDR);

 /* 清中断 */
 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);

 /* 使能中断 */
 writew(restart_flag | I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP, i2c->base + OFFSET_INTR_MASK);

 /* 设置 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);
 }

 /* 准备 buffer data to start transfer */
 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);

  r_buf = kzalloc(msgs->len, GFP_KERNEL);
  if (r_buf == NULL)
   return -ENOMEM;

  rpaddr = dma_map_single(i2c->dev, r_buf,
     msgs->len, DMA_FROM_DEVICE);
  if (dma_mapping_error(i2c->dev, rpaddr)) {
   kfree(r_buf);
   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);

  w_buf = kzalloc(msgs->len, GFP_KERNEL);
  if (w_buf == NULL)
   return -ENOMEM;

  memcpy(w_buf, msgs->buf, msgs->len);

  wpaddr = dma_map_single(i2c->dev, w_buf,
     msgs->len, DMA_TO_DEVICE);
  if (dma_mapping_error(i2c->dev, wpaddr)) {
   kfree(w_buf);
   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);

  w_buf = kzalloc(msgs->len, GFP_KERNEL);
  if (w_buf == NULL)
   return -ENOMEM;
  r_buf = kzalloc((msgs + 1)->len, GFP_KERNEL);
  if (r_buf == NULL) {
   kfree(w_buf);
   return -ENOMEM;
  }

  memcpy(w_buf, msgs->buf, msgs->len);

  wpaddr = dma_map_single(i2c->dev, w_buf,
     msgs->len, DMA_TO_DEVICE);
  if (dma_mapping_error(i2c->dev, wpaddr)) {
   kfree(w_buf);
   kfree(r_buf);
   return -ENOMEM;
  }
  rpaddr = dma_map_single(i2c->dev, r_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);
   kfree(w_buf);
   kfree(r_buf);
   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);
 }

 writel(I2C_DMA_START_EN, i2c->pdmabase + OFFSET_EN);

 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);

 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 (i2c->op == I2C_MASTER_WR) {
  dma_unmap_single(i2c->dev, wpaddr,
     msgs->len, DMA_TO_DEVICE);
  kfree(w_buf);
 } else if (i2c->op == I2C_MASTER_RD) {
  dma_unmap_single(i2c->dev, rpaddr,
     msgs->len, DMA_FROM_DEVICE);
  memcpy(msgs->buf, r_buf, msgs->len);
  kfree(r_buf);
 } else {
  dma_unmap_single(i2c->dev, wpaddr, msgs->len,
     DMA_TO_DEVICE);
  dma_unmap_single(i2c->dev, rpaddr, (msgs + 1)->len,
     DMA_FROM_DEVICE);
  memcpy((msgs + 1)->buf, r_buf, (msgs + 1)->len);
  kfree(w_buf);
  kfree(r_buf);
 }

 if (ret == 0) {
  dev_dbg(i2c->dev, "addr: %x, transfer timeoutn", 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 errorn", msgs->addr);
  mtk_i2c_init_hw(i2c);
  return -ENXIO;
 }

 return 0;
}

mtk_i2c_irq:中断处理函数,在 I2C 传输 ACKERR 和传输 STOP 时触发。

static irqreturn_t mtk_i2c_irq(int irqno, void *dev_id)
{
 struct mtk_i2c *i2c = dev_id;
 u16 restart_flag = 0;
 u16 intr_stat;

 if (i2c->auto_restart)
  restart_flag = I2C_RS_TRANSFER;

 intr_stat = readw(i2c->base + OFFSET_INTR_STAT);
 writew(intr_stat, i2c->base + OFFSET_INTR_STAT);

 i2c->irq_stat |= intr_stat;

 if (i2c->ignore_restart_irq && (i2c->irq_stat & restart_flag)) {
  i2c->ignore_restart_irq = false;
  i2c->irq_stat = 0;
  writew(I2C_RS_MUL_CNFG | I2C_RS_MUL_TRIG | I2C_TRANSAC_START,
         i2c->base + OFFSET_START);
 } else {
  if (i2c->irq_stat & (I2C_TRANSAC_COMP | restart_flag))
   complete(&i2c->msg_complete);
 }

 return IRQ_HANDLED;
}

 

优先级翻转与优先级继承

优先级翻转在可剥夺内核中是非常常见的,例子如下(H:High、M:Middle、L:Low)

 

  1. 任务 H 和任务 M 处于挂起状态,等待某一事件的发生,任务 L 正在运行。某一时刻任务 L 想要访问共享资源,在此之前它必须先获得对应该资源的信号量。任务 L 获得信号量并开始使用该共享资源。由于任务 H 优先级高,它等待的事件发生后便剥夺了任务 L 的 CPU 使用权。
  2. 任务 H 开始运行。任务 H 运行过程中也要使用任务 L 正在使用着的资源,由于该资源的信号量还被任务L 占用着,任务 H 只能进入挂起状态,等待任务 L 释放该信号量。
  3. 任务 L 继续运行。由于任务 M 的优先级高于任务 L,当任务 M 等待的事件发生后,任务 M 剥夺了任务L 的 CPU 使用权。任务 M 处理该处理的事。任务 M 执行完毕后,将 CPU 使用权归还给任务 L。任务 L 继续运行。最终任务 L 完成所有的工作并释放了信号量,到此为止,由于实时内核知道有个高优先级的任务在等待这个信号量,故内核做任务切换。任务 H 得到该信号量并接着运行。

在这种情况下,任务 H 的优先级实际上降到了任务 L 的优先级水平。因为任务 H 要一直等待直到任务 L 释放其占用的那个共享资源。由于任务 M 剥夺了任务 L 的 CPU 使用权,使得任务 H 的情况更加恶化,这样就相当于任务 M 的优先级高于任务 H,导致优先级翻转。

Linux 用 rt_mutex 来解决该问题,rt_mutex 是带优先级继承的互斥锁

当一个 rt_mutex 正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个 rt_mutex 的话就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这就是优先级继承。优先级继承尽可能的降低了高优先级任务处于阻塞态的时间,并且将已经出现的“优先级翻转”的影响降到最低。

优先级继承并不能完全的消除优先级翻转,它只是尽可能的降低优先级翻转带来的影响。

rt_mutex 不能用于中断服务函数中,原因如下:

  1. rt_mutex 有优先级继承的机制,所以只能用在任务中,不能用于中断服务函数。中断服务函数中不能因为要等待 rt_mutex 而设置阻塞时间进入阻塞态。

在 i2c_transfer 调用 __i2c_transfer 之前,就加了 rt_mutex,保证 I2C 传输尽快执行。

推荐器件

更多器件
器件型号 数量 器件厂商 器件描述 数据手册 ECAD模型 风险等级 参考价格 更多信息
CSTNE8M00G520000R0 1 Murata Manufacturing Co Ltd Ceramic Resonator,

ECAD模型

下载ECAD模型
$0.65 查看
LFXTAL003000REEL 1 IQD Frequency Products QUARTZ CRYSTAL RESONATOR, 0.032768 MHz, ROHS COMPLIANT, SMD, 4 PIN
$0.53 查看
TLP185(TPR,SE 1 Toshiba America Electronic Components Transistor Output Optocoupler

ECAD模型

下载ECAD模型
$1.89 查看

相关推荐

电子产业图谱

研究生在读,熟悉硬件、STM32单片机、嵌入式Linux。已收获小米、联发科、浙江大华、上能电气、英威腾、汇川技术、格力、富士康等大厂offer。在这里分享求职经验、嵌入式学习规划、考研、嵌入式Linux技术文章等。