天天看点

实现RTEMS Beaglebone Black I2C驱动

RTEMS的beaglebone black BSP并不包含I2C的驱动,而很多传感器模块的通信方式都只支持I2C,因此限制了RTEMS在IOT物联网方面的应用。

本文对I2C驱动的实现思想进行详细描述。参考代码是RTEMS源码中ATSAM的i2C实现。

首先要知道,RTEMS中I2C bus的代码已经存在,因此我们只关心具体实现代码即可,也就是BSP部分的i2C驱动部分/

RTEMS对于I2C设备的管理方式是采用nexus总线,因此首先要在总线上对I2C设备进行注册。

注册函数如下:

int am335x_i2c_bus_register(
  const char *bus_path,
  uintptr_t register_base,
  uint32_t input_clock,
  rtems_vector_number irq
)
{
  
  bbb_i2c_bus *bus;
  rtems_status_code sc;
  int err;
  /*check bus number is >0 & <MAX*/


  bus = (bbb_i2c_bus *) i2c_bus_alloc_and_init(sizeof(*bus));
  
  if (bus == NULL) {
    return -1;
  }


  bus->regs = (volatile bbb_i2c_regs *) register_base;
 
// 1. Enable clock for I2CX
  I2C0ModuleClkConfig();
// 2. pinmux setup
  am335x_i2c0_pinmux(bus);
// 3. RESET : Disable Master, autoideal 
  am335x_i2c_reset(bus);
// 4. configure bus speed  
  bus->input_clock = input_clock; // By default 100KHz. Normally pass 100KHz as argument 
 
  
  err = am335x_i2c_set_clock(&bus->base, I2C_BUS_CLOCK_DEFAULT);
 
  if (err != 0) {
    (*bus->base.destroy)(&bus->base);
    
    rtems_set_errno_and_return_minus_one(-err);
  }
   bus->irq = irq;
  
  //bring I2C out of reset


   udelay(1000);
  flush_fifo(&bus->base);
  writew(0xFFFF, &bus->regs->BBB_I2C_IRQSTATUS);




 
  // 5. Start interrupt service routine & one interrupt at a time 
  sc  = rtems_interrupt_handler_install(
    irq,
    "BBB I2C",
    RTEMS_INTERRUPT_UNIQUE,
    am335x_i2c_interrupt,
    bus
   );
  
  if (sc != RTEMS_SUCCESSFUL) {
    (*bus->base.destroy)(&bus->base);
 
    rtems_set_errno_and_return_minus_one(EIO);
  }
  // 6. start transfer for reading and writing 
  bus->base.transfer = am335x_i2c_transfer;
  bus->base.set_clock = am335x_i2c_set_clock;
  bus->base.destroy = am335x_i2c_destroy;
  
  return i2c_bus_register(&bus->base,bus_path);
}
           

首先是调用i2c_bus_alloc_and_init函数,对i2c bus进行资源分配和初始化,该函数具体实现在i2c-bus.c 文件中,属于rtems设备驱动系统函数,不做过多分析。然后进行判断,若函数返回值为null,表示分配资源未成功,返回-1,表示失败,并跳出注册函数。

接下来给出I2C寄存器的基础地址:

bus->regs = (volatile bbb_i2c_regs *) register_base;
           

这行代码非常重要,它决定了后面的读写寄存器的地址是否正确。参数register_base是注册函数的输入参数,该参数定义在i2c.h文件中,依据不同的i2c设备进行选择,比如i2c0也就是EEPROM占用的通道,该基础地址是0x44E0_B000。

接下来为i2c使能时钟,设置引脚复用类型,以及重置i2c,这里具体分析reset i2c实现:

static void am335x_i2c_reset(bbb_i2c_bus *bus)
{
  volatile bbb_i2c_regs *regs = bus->regs;
   int timeout = I2C_TIMEOUT; 
 
   if (readw(&regs->BBB_I2C_CON) & I2C_CON_EN) {
    writew(0, &regs->BBB_I2C_CON);
    udelay(50000);
  }

  writew(0x2, &regs->BBB_I2C_SYSC); /* for ES2 after soft reset */
  udelay(1000);

  writew(I2C_CON_EN, &regs->BBB_I2C_CON);
  while (!(readw(&regs->BBB_I2C_SYSS) & I2C_SYSS_RDONE) && timeout--) {
    if (timeout <= 0) {
      puts("ERROR: Timeout in soft-reset\n");
      return;
    }
    udelay(1000);
  }


}
           

主要是利用寄存器进行软件复位,寄存器是i2c的BBB_I2C_SYSC,第二位置1表示进行软件复位,然后利用while读取BBB_I2C_SYSS寄存器进行判断,如果I2C_SYSS_RDONE为1,说明复位完成,跳出该函数。

然后进行中断句柄安装:

sc  = rtems_interrupt_handler_install(
    irq,
    "BBB I2C",
    RTEMS_INTERRUPT_UNIQUE,
    am335x_i2c_interrupt,
    bus
   );
           

其中am335x_i2c_interrupt就是中断处理函数,非常重要,将在后面详细介绍。

最后设置bus的各种接口函数,transfer、destroy等,并返回i2c_bus_register函数,该函数也属于i2c系统总线函数,因此不做分析。

接下来介绍transfer函数,该函数统一处理i2c的读和写操作,是整个i2c驱动的关键部分:

static int am335x_i2c_transfer(i2c_bus *base, i2c_msg *msgs, uint32_t msg_count)
{
  rtems_status_code sc;
  bbb_i2c_bus *bus = (bbb_i2c_bus *)base;
  volatile bbb_i2c_regs *regs;
  uint32_t i;
  rtems_task_wake_after(1);
  

  if (msg_count < 1){
    return 1;
  }
 
  for (i=0; i<msg_count;++i) {
      if ((msgs[i].flags & I2C_M_RECV_LEN) != 0) {
        return -EINVAL;
      }
  }
  
  bus->msgs = &msgs[0];
  bus->msg_todo = msg_count;
 
  
  bus->current_msg_todo = msgs[0].len;// current data size
  bus->current_msg_byte = msgs[0].buf;// current data
  
  bus->task_id = rtems_task_self();

  regs = bus->regs;
  am335x_i2c_setup_transfer(bus,regs);
  REG(&regs->BBB_I2C_IRQENABLE_SET) = BBB_I2C_IRQ_USED;

  sc = rtems_event_transient_receive(RTEMS_WAIT, bus->base.timeout);
  // If timeout then return timeout error
  if (sc != RTEMS_SUCCESSFUL) {
    am335x_i2c_reset(bus);

    rtems_event_transient_clear();

    return -ETIMEDOUT;
  }
  return 0;
}
           

首先将要传输的数据的信息结构体赋给bus结构体中的各个变量,然后进入到am335x_i2c_setup_transfer函数进行设置。该函数管理传输和发送函数。

static void am335x_i2c_setup_transfer(
  bbb_i2c_bus           *bus,
  volatile bbb_i2c_regs *regs
)
{
  const i2c_msg *msgs = bus->msgs;
  uint32_t       msg_todo = bus->msg_todo;
  bool           send_stop = false;
  uint32_t       i;

  bus->current_todo = msgs[ 0 ].len;

  for ( i = 1; i < msg_todo && ( msgs[ i ].flags & I2C_M_NOSTART ) != 0;
        ++i ) {
    bus->current_todo += msgs[ i ].len;
  }

  regs = bus->regs;
  REG( &bus->regs->BBB_I2C_BUF ) |= AM335X_I2C_BUF_TXFIFO_CLR;
  REG( &bus->regs->BBB_I2C_BUF ) |= AM335X_I2C_BUF_RXFIFO_CLR;
  am335x_i2c_set_address_size( msgs, regs );
  bus->read = ( msgs->flags & I2C_M_RD ) != 0;
  bus->already_transferred = ( bus->read == true ) ? 0 : 1;

  if ( bus->read ) {
    if ( bus->current_msg_todo == 1 ) {
      send_stop = true;
    }

    am335x_i2c_setup_read_transfer( bus, regs, msgs, send_stop );
  } else {
    am335x_i2c_setup_write_transfer( bus, regs, msgs );
  }
}
           

该函数就是通过 bus->read判断是要进行读操作还是写操作。然后进入对应的函数处理。

接下来分析中断处理函数:

static void am335x_i2c_interrupt(void *arg)
{
  bbb_i2c_bus *bus = arg;
  volatile bbb_i2c_regs *regs = bus->regs;
  /* get status of enabled interrupts */
  uint32_t irqstatus = REG(&regs->BBB_I2C_IRQSTATUS);
  bool done = false;
  /* Clear all enabled interrupt except receive ready and transmit ready interrupt in status register */ 
  REG(&regs->BBB_I2C_IRQSTATUS) = (irqstatus & ~( AM335X_I2C_IRQSTATUS_RRDY | AM335X_I2C_IRQSTATUS_XRDY));

  if (irqstatus & AM335X_I2C_INT_RECV_READY) {
   delay_bbb_i2c

    am335x_i2c_continue_read_transfer(bus, regs);
  }
 
  if (irqstatus & AM335X_I2C_IRQSTATUS_XRDY) {

    am335x_i2c_continue_write(bus,regs);
  }
 
  if (irqstatus & AM335X_I2C_IRQSTATUS_NACK) {
    done = true;
   
    am335x_i2c_masterint_disable(regs,AM335X_I2C_IRQSTATUS_NACK);
  }

  if (irqstatus & AM335X_I2C_IRQSTATUS_ARDY) {
   done = true;
  writew(I2C_STAT_ARDY, &regs->BBB_I2C_IRQSTATUS);
  }


  if (irqstatus & AM335X_I2C_IRQSTATUS_BF) {
    done = true;
   
  }

  if (done) {
    uint32_t err = irqstatus & BBB_I2C_IRQ_ERROR;

    am335x_i2c_next_byte(bus);

    if (bus->msg_todo == 0 ) {
    rtems_status_code sc;

    am335x_i2c_masterint_disable(regs, (AM335X_I2C_IRQSTATUS_RRDY | AM335X_I2C_IRQSTATUS_XRDY | AM335X_I2C_IRQSTATUS_BF));

    REG(&regs->BBB_I2C_IRQSTATUS) = err;
  
    sc = rtems_event_transient_send(bus->task_id);
    _Assert(sc == RTEMS_SUCCESSFUL);
    (void) sc;
    } else {
    
      am335x_i2c_setup_transfer(bus, regs);
    }
  }
}
           

首先读取中断状态寄存器,然后对各种状态进行判断,如果判断寄存器是AM335X_I2C_INT_RECV_READY,那么进入am335x_i2c_continue_read_transfer函数,继续读取数据。以此类推。然后判断done,如果done不是true,说明这一个byte还未传输完成,因此进入am335x_i2c_setup_transfer函数,进行下一步的工作,继续发送或者接收。如果done是true,那么进入到下一个byte的数据传输中,如果done为真的同时,bus->msg_todo为0,也就是要做的数据为0,表明传输完成,那么就调用am335x_i2c_masterint_disable函数禁止掉中断,并调用rtems_event_transient_send函数,参数为事件的任务id。

am335x_i2c_continue_write函数

static void am335x_i2c_continue_write(
  bbb_i2c_bus           *bus,
  volatile bbb_i2c_regs *regs
)
{
  if ( bus->already_transferred == bus->msg_todo ) {
    REG( &regs->BBB_I2C_DATA ) =
      bus->current_msg_byte[ bus->already_transferred ];
    REG( &regs->BBB_I2C_IRQSTATUS ) = AM335X_I2C_IRQSTATUS_XRDY;
    am335x_i2c_masterint_disable( regs, AM335X_I2C_IRQSTATUS_XRDY );
    REG( &regs->BBB_I2C_CON ) |= AM335X_I2C_CON_STOP;
  } else {
    writeb( bus->current_msg_byte[ bus->already_transferred ],
      &regs->BBB_I2C_DATA );
    REG( &regs->BBB_I2C_IRQSTATUS ) = AM335X_I2C_IRQSTATUS_XRDY;
    bus->already_transferred++;
  }
}
           

这里要注意的是,在对BBB_I2C_DATA寄存器写入数据时,要用writeb函数,也就是8位数据写入,因为 BBB_I2C_DATA寄存器就是八位的:

#define writeb(v,c) ({ unsigned char  __v = v; __arch_putb(__v,c); __v; })
           

继续阅读