/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <joerg@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.        Joerg Wunsch
 * ----------------------------------------------------------------------------
 * Updated to handle larger devices having 16-bit addresses
 *                                                 (2007-09-05) Ruwan Jayanetti
 * ----------------------------------------------------------------------------
 */

/*
 * Simple demo program that talks to a 24Cxx I2C EEPROM using the
 * builtin TWI interface of an ATmega device.
 */

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <avr/io.h>
#include <util/twi.h>           // Note [1]

#define DEBUG 1

/*
 * System clock in Hz.
 */
#define F_CPU 14745600UL        // Note [2]

/*
 * Compatibility defines.  This should work on ATmega8, ATmega16,
 * ATmega163, ATmega323 and ATmega128 (IOW: on all devices that
 * provide a builtin TWI interface).
 *
 * On the ATmega128, it defaults to USART 1.
 */
#ifndef UCSRB
# ifdef UCSR1A          /* ATmega128 */
#  define UCSRA UCSR1A
#  define UCSRB UCSR1B
#  define UBRR UBRR1L
#  define UDR UDR1
# else /* ATmega8 */
#  define UCSRA USR
#  define UCSRB UCR
# endif
#endif
#ifndef UBRR
#  define UBRR UBRRL
#endif

/*
 * Note [3]
 * TWI address for 24Cxx EEPROM:
 *
 * 1 0 1 0 E2 E1 E0 R/~W        24C01/24C02
 * 1 0 1 0 E2 E1 A8 R/~W        24C04
 * 1 0 1 0 E2 A9 A8 R/~W        24C08
 * 1 0 1 0 A10 A9 A8 R/~W       24C16
 */
#define TWI_SLA_24CXX   0xa0    /* E2 E1 E0 = 0 0 0 */

/*
 * Note [3a]
 * Device word address length for 24Cxx EEPROM
 * Larger EEPROM devices (from 24C32) have 16-bit address
 * Define or undefine according to the used device
 */
//#define WORD_ADDRESS_16BIT

/*
 * Maximal number of iterations to wait for a device to respond for a
 * selection.  Should be large enough to allow for a pending write to
 * complete, but low enough to properly abort an infinite loop in case
 * a slave is broken or not present at all.  With 100 kHz TWI clock,
 * transferring the start condition and SLA+R/W packet takes about 10
 * microseconds.  The longest write period is supposed to not exceed ~ 10 ms.
 * Thus, normal operation should not require more than 100 iterations
 * to get the device to respond to a selection.
 */
#define MAX_ITER        200

/*
 * Number of bytes that can be written in a row, see comments for
 * ee24xx_write_page() below.  Some vendor's devices would accept 16,
 * but 8 seems to be the lowest common denominator.
 *
 * Note that the page size must be a power of two, this simplifies the
 * page boundary calculations below.
 */
#define PAGE_SIZE 8

/*
 * Saved TWI status register, for error messages only.  We need to
 * save it in a variable, since the datasheet only guarantees the TWSR
 * register to have valid contents while the TWINT bit in TWCR is set.
 */
uint8_t twst;

/*
 * Do all the startup-time peripheral initializations: UART (for our
 * debug/test output), and TWI clock.
 */
void
ioinit(void)
{

#if F_CPU <= 1000000UL
  /*
   * Note [4]
   * Slow system clock, double Baud rate to improve rate error.
   */
  UCSRA = _BV(U2X);
  // 9600 Bd
  UBRR = (F_CPU / (8 * 9600UL)) - 1;
#else
  // 9600 Bd
  UBRR = (F_CPU / (16 * 9600UL)) - 1;
#endif

  // Tx enable
  UCSRB = _BV(TXEN);

  // initialize TWI clock: 100 kHz clock, TWPS = 0 => prescaler = 1
#if defined(TWPS0)
  // has prescaler (mega128 & newer)
  TWSR = 0;
#endif

#if F_CPU < 3600000UL
  // smallest TWBR value, see note [5]
  TWBR = 10;
#else
  TWBR = (F_CPU / 100000UL - 16) / 2;
#endif
}

/*
 * Note [6]
 * Send character c down the UART Tx, wait until tx holding register
 * is empty.
 */
int
uart_putchar(char c, FILE *stream)
{
  (void) stream;
  if (c == '\n')
    uart_putchar('\r', 0);
  loop_until_bit_is_set(UCSRA, UDRE);
  UDR = c;
  return 0;
}


static inline void
wait_for_transmission (void)
{
  while ((TWCR & _BV(TWINT)) == 0)
    {
      // wait for transmission
    }
}

typedef uint16_t eeaddr_t;

/*
 * Note [7]
 *
 * Read "len" bytes from EEPROM starting at "eeaddr" into "buf".
 *
 * This requires two bus cycles: during the first cycle, the device
 * will be selected (master transmitter mode), and the address
 * transferred.
 * Address bits exceeding 256 are transferred in the
 * E2/E1/E0 bits (subaddress bits) of the device selector.
 * Address is sent in two dedicated 8 bit transfers
 * for 16 bit address devices (larger EEPROM devices)
 *
 * The second bus cycle will reselect the device (repeated start
 * condition, going into master receiver mode), and transfer the data
 * from the device to the TWI master.  Multiple bytes can be
 * transferred by ACKing the client's transfer.  The last transfer will
 * be NACKed, which the client will take as an indication to not
 * initiate further transfers.
 */
int
ee24xx_read_bytes(eeaddr_t eeaddr, int len, void *vbuf)
{
  uint8_t *buf = (uint8_t*) vbuf;
  uint8_t sla, twcr, n = 0;
  int rv = 0;

#ifndef WORD_ADDRESS_16BIT
  // patch high bits of EEPROM address into SLA
  sla = TWI_SLA_24CXX | (((eeaddr >> 8) & 0x07) << 1);
#else
  // 16-bit address devices need only TWI Device Address
  sla = TWI_SLA_24CXX;
#endif

  /*
   * Note [8]
   * First cycle: master transmitter mode
   */
restart:
  if (n++ >= MAX_ITER)
    return -1;

begin:

  // send start condition
  TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);

  wait_for_transmission();

  switch ((twst = TW_STATUS))
    {
    case TW_REP_START:          // OK, but should not happen
    case TW_START:
      break;

    case TW_MT_ARB_LOST:
      // Note [9]
      goto begin;

    default:
      // error: not in start condition
      // NB: do /not/ send stop condition
      return -1;
    }

  // Note [10]
  // send SLA+W
  TWDR = sla | TW_WRITE;
  // clear interrupt to start transmission
  TWCR = _BV(TWINT) | _BV(TWEN);

  wait_for_transmission();

  switch ((twst = TW_STATUS))
    {
    case TW_MT_SLA_ACK:
      break;

    case TW_MT_SLA_NACK:
      // nack during select: device busy writing
      // Note [11]
      goto restart;

    case TW_MT_ARB_LOST:
      // re-arbitrate
      goto begin;

    default:
      // must send stop condition
      goto error;
    }

#ifdef WORD_ADDRESS_16BIT
  // 16-bit word address device, send high 8 bits of addr
  TWDR = eeaddr >> 8;
  // clear interrupt to start transmission
  TWCR = _BV(TWINT) | _BV(TWEN);

  wait_for_transmission();

  switch ((twst = TW_STATUS))
    {
    case TW_MT_DATA_ACK:
      break;

    case TW_MT_DATA_NACK:
      goto quit;

    case TW_MT_ARB_LOST:
      goto begin;

    default:
      // must send stop condition
      goto error;
    }
#endif

  // low 8 bits of addr
  TWDR = eeaddr;
  // clear interrupt to start transmission
  TWCR = _BV(TWINT) | _BV(TWEN);

  wait_for_transmission();

  switch ((twst = TW_STATUS))
    {
    case TW_MT_DATA_ACK:
      break;

    case TW_MT_DATA_NACK:
      goto quit;

    case TW_MT_ARB_LOST:
      goto begin;

    default:
      // must send stop condition
      goto error;
    }

  /*
   * Note [12]
   * Next cycle(s): master receiver mode
   */
  // send (rep.) start condition
  TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);

  wait_for_transmission();

  switch ((twst = TW_STATUS))
    {
    case TW_START:              // OK, but should not happen
    case TW_REP_START:
      break;

    case TW_MT_ARB_LOST:
      goto begin;

    default:
      goto error;
    }

  // send SLA+R
  TWDR = sla | TW_READ;
  // clear interrupt to start transmission
  TWCR = _BV(TWINT) | _BV(TWEN);

  wait_for_transmission();

  switch ((twst = TW_STATUS))
    {
    case TW_MR_SLA_ACK:
      break;

    case TW_MR_SLA_NACK:
      goto quit;

    case TW_MR_ARB_LOST:
      goto begin;

    default:
      goto error;
    }

  // Note [13]
  for (twcr = _BV(TWINT) | _BV(TWEN) | _BV(TWEA); len > 0; len--)
    {
      if (len == 1)
        // send NAK this time
        twcr = _BV(TWINT) | _BV(TWEN);

      // clear int to start transmission
      TWCR = twcr;

      wait_for_transmission();

      switch ((twst = TW_STATUS))
        {
        case TW_MR_DATA_NACK:
          // force end of loop
          len = 0;
          // FALLTHROUGH

        case TW_MR_DATA_ACK:
          *buf++ = TWDR;
          rv++;
          if (twst == TW_MR_DATA_NACK)
            goto quit;
          break;

        default:
          goto error;
        }
    }
quit:
  // Note [14]
  // send stop condition
  TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN);

  return rv;

error:
  rv = -1;
  goto quit;
}

/*
 * Write "len" bytes into EEPROM starting at "eeaddr" from "buf".
 *
 * This is a bit simpler than the previous function since both, the
 * address and the data bytes will be transferred in master transmitter
 * mode, thus no reselection of the device is necessary.  However, the
 * EEPROMs are only capable of writing one "page" simultaneously, so
 * care must be taken to not cross a page boundary within one write
 * cycle.  The amount of data one page consists of varies from
 * manufacturer to manufacturer: some vendors only use 8-byte pages
 * for the smaller devices, and 16-byte pages for the larger devices,
 * while other vendors generally use 16-byte pages.  We thus use the
 * smallest common denominator of 8 bytes per page, declared by the
 * macro PAGE_SIZE above.
 *
 * The function simply returns after writing one page, returning the
 * actual number of data byte written.  It is up to the caller to
 * re-invoke it in order to write further data.
 */
int
ee24xx_write_page(eeaddr_t eeaddr, int len, const void *vbuf)
{
  const uint8_t *buf = (const uint8_t*) vbuf;
  uint8_t sla, n = 0;
  int rv = 0;
  eeaddr_t endaddr;

  if (eeaddr + len <= (eeaddr | (PAGE_SIZE - 1)))
    endaddr = eeaddr + len;
  else
    endaddr = (eeaddr | (PAGE_SIZE - 1)) + 1;
  len = endaddr - eeaddr;

#ifndef WORD_ADDRESS_16BIT
  // patch high bits of EEPROM address into SLA
  sla = TWI_SLA_24CXX | (((eeaddr >> 8) & 0x07) << 1);
#else
  // 16-bit address devices need only TWI Device Address
  sla = TWI_SLA_24CXX;
#endif

restart:
  if (n++ >= MAX_ITER)
    return -1;

begin:

  // Note [15]
  // send start condition */
  TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);

  wait_for_transmission();

  switch ((twst = TW_STATUS))
    {
    case TW_REP_START:          // OK, but should not happen
    case TW_START:
      break;

    case TW_MT_ARB_LOST:
      goto begin;

    default:
      // error: not in start condition
      // NB: do /not/ send stop condition
      return -1;
    }

  // send SLA+W
  TWDR = sla | TW_WRITE;
  // clear interrupt to start transmission
  TWCR = _BV(TWINT) | _BV(TWEN);

  wait_for_transmission();

  switch ((twst = TW_STATUS))
    {
    case TW_MT_SLA_ACK:
      break;

    case TW_MT_SLA_NACK:        /* nack during select: device busy writing */
      goto restart;

    case TW_MT_ARB_LOST:        /* re-arbitrate */
      goto begin;

    default:
      goto error;               /* must send stop condition */
    }

#ifdef WORD_ADDRESS_16BIT
  // 16 bit word address device, send high 8 bits of addr.
  TWDR = eeaddr >> 8;

  // clear interrupt to start transmission.
  TWCR = _BV(TWINT) | _BV(TWEN);

  wait_for_transmission();

  switch ((twst = TW_STATUS))
    {
    case TW_MT_DATA_ACK:
      break;

    case TW_MT_DATA_NACK:
      goto quit;

    case TW_MT_ARB_LOST:
      goto begin;

    default:
      // must send stop condition
      goto error;
    }
#endif

  TWDR = eeaddr;                /* low 8 bits of addr */
  TWCR = _BV(TWINT) | _BV(TWEN); /* clear interrupt to start transmission */

  wait_for_transmission();

  switch ((twst = TW_STATUS))
    {
    case TW_MT_DATA_ACK:
      break;

    case TW_MT_DATA_NACK:
      goto quit;

    case TW_MT_ARB_LOST:
      goto begin;

    default:
      goto error;               /* must send stop condition */
    }

  for (; len > 0; len--)
    {
      TWDR = *buf++;
      // start transmission */
      TWCR = _BV(TWINT) | _BV(TWEN);

      wait_for_transmission();

      switch ((twst = TW_STATUS))
        {
        case TW_MT_DATA_NACK:
          // device write protected -- Note [16]
          goto error;

        case TW_MT_DATA_ACK:
          rv++;
          break;

        default:
          goto error;
        }
    }
quit:
  // send stop condition
  TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN);

  return rv;

error:
  rv = -1;
  goto quit;
}

/*
 * Wrapper around ee24xx_write_page() that repeats calling this
 * function until either an error has been returned, or all bytes
 * have been written.
 */
int
ee24xx_write_bytes(eeaddr_t eeaddr, size_t len, const void *vbuf)
{
  const uint8_t *buf = (const uint8_t*) vbuf;
  int total = 0;
  do
    {
#if DEBUG
      printf("Calling ee24xx_write_page(%d, %d, %p)", eeaddr, len, buf);
#endif
      int rv = ee24xx_write_page(eeaddr, len, buf);
#if DEBUG
      printf(" => %d\n", rv);
#endif
      if (rv == -1)
        return -1;
      eeaddr += rv;
      len -= rv;
      buf += rv;
      total += rv;
    }
  while (len > 0);

  return total;
}

void
error(void)
{
  printf("error: TWI status %#x\n", twst);
  exit(0);
}

static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

int
main(void)
{
  int rv;
  uint8_t buf[16];

  ioinit();

  stdout = &mystdout;

  for (eeaddr_t a = 0; a < 256;)
    {
      printf("%#04x: ", a);
      rv = ee24xx_read_bytes(a, sizeof(buf), buf);
      if (rv <= 0)
        error();
      if (rv < 16)
        printf("warning: short read %d\n", rv);
      a += rv;
      for (uint8_t x = 0; x < rv; x++)
        printf("%02x ", buf[x]);
      putchar('\n');
    }

#define EE_WRITE_STR(addr, str) \
  ee24xx_write_bytes(addr, 1 + strlen(str), str)

  rv = EE_WRITE_STR(55, "The quick brown fox jumps over the lazy dog.");
  if (rv < 0)
    error();
  printf("Wrote %d bytes.\n", rv);
  for (eeaddr_t a = 0; a < 256;)
    {
      printf("%#04x: ", a);
      rv = ee24xx_read_bytes(a, sizeof(buf), buf);
      if (rv <= 0)
        error();
      if (rv < 16)
        printf("warning: short read %d\n", rv);
      a += rv;
      for (uint8_t x = 0; x < rv; x++)
        printf("%02x ", buf[x]);
      putchar('\n');
    }

  printf("done.\n");

  return 0;
}
