Developed the I2C driver for Beaglebone Black BSP

My first part of GSOC project is finish the I2C driver left by GSOC 2016 student Punit Vara.
I reference the U-Boot and FreeBSD i2c code for develop.

1. We need register the i2c bus:

a) i2c_bus *i2c_bus_alloc_and_init(size_t size)

this function is use for alloc and init a new i2c bus.

i2c_bus *i2c_bus_alloc_and_init(size_t size)
{
  i2c_bus *bus = NULL;

  if (size >= sizeof(*bus)) {
    bus = calloc(1, size);
    if (bus != NULL) {
      int rv;

      rv = i2c_bus_do_init(bus, i2c_bus_destroy_and_free);
      if (rv != 0) {
        return NULL;
      }
    }
  }

  return bus;
}

b) configure the I2C clock and pinimux

there two pin we need to configure:

static void am335x_i2c0_pinmux(bbb_i2c_bus *bus)
{
  REG(bus->regs + AM335X_CONF_I2C0_SDA) =
  (BBB_RXACTIVE | BBB_SLEWCTRL | BBB_PU_EN);

  REG(bus->regs + AM335X_CONF_I2C0_SCL) =
  (BBB_RXACTIVE | BBB_SLEWCTRL | BBB_PU_EN);
}

the pin we need configure to be pull and rx enable


c) install the i2c interrupt handle


rtems_status_code rtems_interrupt_handler_install(
  rtems_vector_number vector,
  const char *info,
  rtems_option options,
  rtems_interrupt_handler handler,
  void *arg
)
{
  return bsp_interrupt_handler_install(vector, info, options, handler, arg);
}


 sc  = rtems_interrupt_handler_install(
    irq,
    "BBB_I2C",
    RTEMS_INTERRUPT_UNIQUE,
    (rtems_interrupt_handler)am335x_i2c_interrupt,
    bus
   );
  
irq is the interrupt vector

BBB_i2c is the intrrupt info.

RTEMS_INTERRUPT_UNIQUE is the option, it prevents other handler from using the same interrupt vector.

am335x_i2c_interrupt is the ISR


d) Setting the interface function for bus API

  bus->base.transfer = am335x_i2c_transfer;
  bus->base.set_clock = am335x_i2c_set_clock;
  bus->base.destroy = am335x_i2c_destroy;



2. i2c interrupt ISR

There are many interrupt state in i2c, we can read the AM335x Manual for i2c interrupt register

 
When a interrupt occur, we need to judge which interrupt state change.

So when enter the ISR , we need read the interrput state register.

uint32_t irqstatus = REG(&regs->BBB_I2C_IRQSTATUS);
issue
We judge it and do some corresponding processing. 

In fact what we need pay attention is the AARY RRDY and XRDY,


XRDY: Transmit data ready IRQ status.
Set to '1' by core when transmitter and when new data is requested. When set to '1' by core, an interrupt is signaled to MPUSS.

RRDY: This read/clear only RRDY is set to 1 when the RX FIFO level is above the configured threshold(RXTRSH).

ARDY: This read/clear only bit, when set to 1, indicates that the previously programmed data and command (receive or transmit, master or slave) has been performed and status bit has been updated.

So the i2c work flow should be : 
when need to write data , we start to transmit, when occur the XRDY, we need write data to the data register. when the write transmit is over, occur the ARDY, which indicate that the transmit is over and is ready for next transmit. 
when need to read data, such as read the EEPROM, we need start write address offset to EEPROM, and wait the ARDY, then start to read transfer, when the RRDY occur, we read the data register, and when the transfer is over, the ARDY occur again, then we can stop reading data.


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

  if (irqstatus & AM335X_I2C_IRQSTATUS_XRDY) {
    am335x_i2c_continue_write(bus,regs);
  }
 
   if (irqstatus & AM335X_I2C_IRQSTATUS_ARDY) {
  done = true;
  REG(&regs->BBB_I2C_IRQSTATUS) = I2C_STAT_ARDY;
  }



3. write data register
There a issue we need to know, when we write data to the I2C data register, we need use the byte access, or we may get stuck at somewhere, i stuck at the ISR for almost a week, and finaly find the problem, So be careful about the data register access.


4. read data
How to start to read data, first we need tell the i2c count register how many data we need to read:
 
 REG(&regs->BBB_I2C_CNT) = bus->current_msg_todo;

Then we need to configure the control register:

  REG(&regs->BBB_I2C_CON) = AM335X_I2C_CFG_MST_RX | AM335X_I2C_CON_I2C_EN;
 
enable the rx mode

Then set the start bit in the control register:

 REG(&regs->BBB_I2C_CON) |=  AM335X_I2C_CON_START;


Last step we need enable the IRQ:

 am335x_i2c_masterint_enable(regs,AM335X_I2C_INT_RECV_READY
                                  | AM335X_I2C_IRQSTATUS_ARDY);

There is something we need to know,  we must enable the IRQ after set the start bit of control register, or we may get error in interrupt register, for example, i enable the IRQ before set start bit, then i got the ROVR bit of IRQ register, which mean receive overrun status. 


These issue is my experience during the I2C development.


评论

此博客中的热门博文

How to generate image file and dd to SD card

Compiling RTEMS for Beaglebone Black BSP