/*
 * Simple I2C driver:
 *  1. Assume you have multiple masters and slave devices using the bus.
 *   [dev] Two cases need to check:
 *    a. Must check bus busy before any transfer because another master may take bus
 *    b. Must check each transfer status to make sure this master not lost arbitration
 *       (2 master may start same time, and which master send more 0 will take bus)
*        --> re-init the transfer if bus not busy 
 *
 *  2. It should have 3 basic functions – Detect, Read and Write.
 *   [dev] For simple: probe (detect device exist), read/write data by loop RW 1byte API
 *
 *  3. Be able to handle any error conditions.
 *   [dev] Timeout, bus busy, error lost arbitration and fail transfer
 *
 *  4. Write some test cases.
 *   [dev] interract with user input, and pseudo read/write/taking bus
 */

/* Include */
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

/* Define */
#define I2C_POLL_TIMEOUT      10     /* 10ms. FIXME: change it in real HW */
#define I2C_WAIT_TIMEOUT      10000  /* 10s. Wait time to take bus in multi-master */

#define WAIT_BUS_TIMEOUT_ERR  -1     /* Error return when waiting bus timeout */

#define I2C_LOST_ARBIT_ERR    5      /* Error transfer because of arbitration lost in multi master */
#define I2C_LOST_NAK_ERR      4      /* NAK */

/* Define for test case */
#define I2C_DEFAULT_BUS_NUM   0     /* Port 0 */
#define I2C_DEFAULT_SPEED     100   /* 100KHz */

#define VERBOSE               1
#define i2c_err(...)          printf("ERR: " __VA_ARGS__)
#define i2c_warn(...)         printf("WARN: " __VA_ARGS__)
#if VERBOSE >= 1
  #define i2c_verbose(...)    printf(__VA_ARGS__)
#else
  #define i2c_verbose(...)
#endif

/* Define for test case some pseudo return */
int pseudo_return_bus_busy();
int pseudo_return_status();
int pseudo_return_done();

int pseudo_bus_busy = 0;      /* 0: idle, 1: busy */
int pseudo_err_status = 0;    /* I2C_LOST_ARBIT_ERR, >0: other err */
int pseudo_transer_done = 1;  /* 1: done, 0: not done */

/* API I2C */
/* I2C initialization: HW early setup */
void i2c_init(unsigned int bus, unsigned int speed)
{
  i2c_verbose("I2C HW init\n");

  /* Configure I2C, base address base on bus number */

  /* Configure I2C mode, speed, timeout, timing */

}

/* I2C start: Setup before each transfer */
void i2c_start(char slave_add, unsigned int address_len)
{
  i2c_verbose("I2C Transfer setup\n");

  /* Configure slave address */

  /* TODO: some specific config may come along with this slave add from input */
}

/* I2C check bus busy. Return 1: busy, 0: idle */
int i2c_bus_busy()
{
  /* Check bus busy or not from HW reg */
  /* FIXME: this pseudo is using for manual test case only */
  return pseudo_return_bus_busy();
}

/* I2C get err status after each transfer */
int i2c_get_status()
{
  /* Get status register */
  /* FIXME: this pseudo is using for manual test case only */
  return pseudo_return_status();
}

/* I2C check bus busy. Return 1: done, 0: timeout */
int i2c_poll_done()
{
  int time_out = I2C_POLL_TIMEOUT;

  while (time_out--) {
    /* FIXME: this pseudo is using for manual test case only */
    if (pseudo_return_done() == 1)
      return 1;

    /* FIXME: add delay here, maybe 1ms in real HW */
  }

  return 0;
}

/* TODO: I2C Master write buffer */

/* TODO: I2C Master read buffer */

/*
 * I2C Master write 1 byte:
 * Input:
 *  slave_add: Target device I2C address.
 *  address: address to access for transfer.
 *  address_len: number of byte address (1 or 2 byte mode)
 *  data: 1 byte data write.  
 *
 * Return: 0 - write OK, -1: timeout, > 0: Write fail with status from I2C reg
 */
int i2c_master_write_byte(char slave_add, unsigned int address,
                          unsigned int address_len, char data)
{
  int ret, time_out = I2C_WAIT_TIMEOUT;

  i2c_verbose("I2C write slave 0x%x addr 0x%x (%d) data 0x%x\n",
              slave_add, address, address_len, data);

  /* Start config for a transaction write */
  i2c_start(slave_add, address_len);

  /* Because of multi-master, must wait until bus ready */
  while (i2c_bus_busy() == 1 && time_out) {
    time_out--;

    /* FIXME: add delay here, maybe 1ms in real HW */
  }

  /* If wait time over timeout, must stop */
  if (time_out <= 0) {
    i2c_err("Waiting bus timeout !!\n");
    return WAIT_BUS_TIMEOUT_ERR;
  }

  /* Write data input to register buffer for sending out */

  /* Trigger write */

  /* Polling status */
  ret = i2c_poll_done();
  if (ret == 0) { /* not done */
    return i2c_get_status();
  }

  return 0;
}

/*
 * I2C Master read 1 byte
 * Input:
 *  slave_add: Target device I2C address.
 *  address: address to access for transfer.
 *  address_len: number of byte address (1 or 2 byte mode)
 *  data: pointer to location stored data read out.  
 *
 * Return: 0 - Read OK, -1: timeout, > 0: Read fail with status from I2C reg
 */
int i2c_master_read_byte(char slave_add, unsigned int address,
                         unsigned int address_len, char *data)
{
  int ret, time_out = I2C_WAIT_TIMEOUT;

  i2c_verbose("I2C read slave 0x%x addr 0x%x (%d)\n",
              slave_add, address, address_len);

  /* Start config for a transaction write */
  i2c_start(slave_add, address_len);

  /* Because of multi-master, must wait until bus ready */
  while (i2c_bus_busy() == 1 && time_out) {
    time_out--;

    /* FIXME: add delay here, maybe 1ms in real HW */
  }

  /* If wait time over timeout, must stop */
  if (time_out <= 0) {
    i2c_err("Waiting bus timeout !!\n");
    return WAIT_BUS_TIMEOUT_ERR;
  }

  /* Trigger read data */
 
  /* Polling status */
  ret = i2c_poll_done();
  if (ret == 0) { /* not done */
    return i2c_get_status();
  }

  /* Copy data from reg to buffer input */

  return 0;
}

/*
 * I2C probe/detect: send read command at first address, if received ACK means
 * slave device address is exist.
 *
 * Return 0: device exist, != 0: no exist.
 */
/* FIXME: some special I2C device may not response this probe command */
int i2c_detect(char slave_add)
{
  char data;

  return i2c_master_read_byte(slave_add, 0, 0, &data);
}

/*
 * I2C Write:
 * Return: 0 - Write OK, -1: timeout, > 0: Write fail with status from I2C reg.
 */
int i2c_master_write(char slave_add, unsigned int address,
                     unsigned int address_len, char *data, unsigned int len)
{
  int i, ret;

  for (i = 0; i < len; i++) {
    ret = i2c_master_write_byte(slave_add, address, address_len, data[i]);

    /* If lost arbitration must re-init this transfer */
    if (ret == I2C_LOST_ARBIT_ERR) {
      i2c_verbose("Lost arbitration -> retry\n");
      i = i - 1;

      /* FIXME: Create pseudo to remove arbitration lost in next times, just for test case */
      pseudo_transer_done = 1;
      pseudo_err_status = 0;

      continue;
    
    /* If wait bus timeout, must stop */
    } else if (ret == WAIT_BUS_TIMEOUT_ERR) {
      return ret;

    /* TODO: If other error may stop or diff. process */
    } else if (ret > 0) {
      i2c_err("Read fail, error code 0x%x !!\n", ret);
      return ret;
    }
  }

  return 0;
}

/*
 * I2C read:
 * Return: 0 - Read OK, -1: timeout, > 0: Read fail with status from I2C reg.
 */
int i2c_master_read(char slave_add, unsigned int address,
                    unsigned int address_len, char *data, unsigned int len)
{
  int i, ret;

  for (i = 0; i < len; i++) {
    ret = i2c_master_read_byte(slave_add, address, address_len, &data[i]);

    /* If lost arbitration must re-init this transfer */
    if (ret == I2C_LOST_ARBIT_ERR) {
      i = i - 1;
      continue;
    
    /* If wait bus timeout, must stop */
    } else if (ret == WAIT_BUS_TIMEOUT_ERR) {
      return ret;

    /* TODO: If other error may stop or diff. process */
    } else if (ret > 0) {
      i2c_err("Read fail, error code 0x%x !!\n", ret);
      return ret;
    }
  }

  return 0;
}

/* API Test case: create some pseudo data return */
int pseudo_return_bus_busy()
{
  return pseudo_bus_busy;
}
int pseudo_return_status()
{
  return pseudo_err_status;
}
int pseudo_return_done()
{
  return pseudo_transer_done;
}

/* Main func interract with user */
int main(void)
{
  int bus = I2C_DEFAULT_BUS_NUM, speed = I2C_DEFAULT_SPEED;
  int sel, slv_add;

  char data_wr[4] = { 0x00, 0x01, 0x02, 0x03 };
  char data_rd[4] = { 0 };

  /* Config I2C: HW setup register */
  i2c_init(bus, speed);

  /* User select option */
  while (1) {
    printf("Selection:\n");
    printf("1. Probe all device form 0x50 to 0x55\n");
    printf("2. Write input slave (format: hex number without '0x')\n");
    printf("3. Read input slave (format: hex number without '0x')\n");
    printf("4. Write input slave but bus not ready\n");
    printf("5. Write input slave but lost abritration & retry\n");
    printf("\nSelect: ");

    /* User input */
    scanf("%d", &sel);

    /* Process */
    switch (sel) {
    case 1:
      for (slv_add = 0x50; slv_add < 0x55; slv_add++) {
        /* Create pseudo available for even slv addr, not available for odd slv addr */
        pseudo_transer_done = (slv_add % 2) ? 0 : 1;
        pseudo_err_status = I2C_LOST_NAK_ERR;

        if (i2c_detect(slv_add)) {
          printf("0x%x: Not available\n\n", slv_add);
        } else {
          printf("0x%x: Available\n\n", slv_add);
        }
      }

      /* Restore pseudo */
      pseudo_transer_done = 1;
      pseudo_err_status = 0;
      break;
    case 2:
      printf("\nSlave address: ");
      scanf("%x", &slv_add);

      /* Create pseudo available for even slv addr, not available for odd slv addr */
      pseudo_transer_done = (slv_add % 2) ? 0 : 1;
      pseudo_err_status = I2C_LOST_NAK_ERR;

      if (i2c_detect(slv_add)) {
        printf("0x%x: Not available\n\n", slv_add);
      } else {
        if (i2c_master_write(slv_add, 0, 1, &data_wr[0], 4) == 0)
          printf("\nWRITE PASS\n\n");
      }

      /* Restore pseudo */
      pseudo_transer_done = 1;
      pseudo_err_status = 0;
      break;
    case 3:
      printf("\nSlave address: ");
      scanf("%x", &slv_add);

      /* Create pseudo available for even slv addr, not available for odd slv addr */
      pseudo_transer_done = (slv_add % 2) ? 0 : 1;
      pseudo_err_status = I2C_LOST_NAK_ERR;

      if (i2c_detect(slv_add)) {
        printf("0x%x: Not available\n\n", slv_add);
      } else {
        if (i2c_master_read(slv_add, 0, 1, &data_rd[0], 4) == 0)
          printf("\nREAD PASS\n\n");
      }

      /* Restore pseudo */
      pseudo_transer_done = 1;
      pseudo_err_status = 0;
      break;
    case 4:
      printf("\nSlave address: ");
      scanf("%x", &slv_add);

      if (i2c_detect(slv_add)) {
        printf("0x%x: Not available\n\n", slv_add);
      } else {
        /* Create bus busy always */
        pseudo_bus_busy = 1;
        
        if (i2c_master_write(slv_add, 0, 1, &data_wr[0], 4) == 0)
          printf("\nWRITE PASS\n");
        printf("\n");
      }

      /* Restore pseudo */
      pseudo_bus_busy = 0;
      break;
    case 5:
      printf("\nSlave address: ");
      scanf("%x", &slv_add);

      if (i2c_detect(slv_add)) {
        printf("0x%x: Not available\n", slv_add);
      } else {
        /* Create bus busy and lost arbitration */
        pseudo_transer_done = 0;
        pseudo_err_status = I2C_LOST_ARBIT_ERR;

        if (i2c_master_write(slv_add, 0, 1, &data_wr[0], 4) == 0)
          printf("\nWRITE PASS\n");
        printf("\n");
      }

      /* Restore pseudo */
      pseudo_transer_done = 1;
      pseudo_err_status = 0;
      break;
    default:
      printf("Not available selection %d !!\n", sel);
      break;
    }

  }

  return 0;
}

Embed on website

To embed this project on your website, copy the following code and paste it into your website's HTML: