/********************************************************************************
Two Wire Interface (TWI)
********************************************************************************/
#include <avr/io.h>
#include <stdbool.h>
#include <util/twi.h>
#include "twi.h"

//-------------------------------------------------------------------
//Global Variables
//-------------------------------------------------------------------
volatile unsigned char toCounter;  // time-out counter
unsigned char toValue;             // starting time-out counter value

//-------------------------------------------------------------------
//Macros and Defines
//-------------------------------------------------------------------
#define WAIT_FOR_TWINT \
  for ( \
       toCounter = toValue; \
       ( toCounter != 0 ) && ( ( TWCR & _BV( TWINT ) ) == 0 ); \
  )

#define WAIT_FOR_STOP_GEN \
  for ( \
       toCounter = toValue; \
       ( toCounter != 0 ) && ( ( TWCR & _BV( TWSTO ) ) != 0 ); \
  )

#define TWI_READY ( ( TWCR & _BV( TWINT ) ) != 0 )

/* define CPU frequency in Mhz here if not defined in Makefile */
#ifndef F_CPU
#define F_CPU 8000000UL
#endif

#define SCL_FREQ 100000UL

//-------------------------------------------------------------------
//generateStop
//	returns zero of no error; non-zero if error
//-------------------------------------------------------------------
int TWI_GenerateStop(void)
{

  // generate a STOP condition
  TWCR = _BV( TWINT ) | _BV( TWSTO ) | _BV( TWEN );

  // wait for STOP generated
  WAIT_FOR_STOP_GEN;

  // check for timeout
  if ( ( TWCR & _BV( TWSTO ) ) != 0 ) return 1;

  return 0;

}

//-------------------------------------------------------------------
//sendSla (send SLA - Slave Address)
//	returns zero of no error; non-zero if error
//		slave address (0..127)
//		read (TW_READ) or write (TW_WRITE)
//-------------------------------------------------------------------
int TWI_SendSla(unsigned char sla, unsigned char rw)
{
  // send START condition
  TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);

  // wait for start condition transmitted
  WAIT_FOR_TWINT;

  // clear TWI START condition bit
  TWCR = _BV(TWEN);

  // check for time-out and check status code
  if(!TWI_READY) return 2;
  if(TW_STATUS != TW_START) return 3;

  // load SLA+R or SLA+W
  TWDR = (sla << 1) | (rw & 0x01);

  // transmit SLA
  TWCR = _BV(TWINT) | _BV(TWEN);

  // wait for SLA transmitted
  WAIT_FOR_TWINT;

  // check for time-out and check status code
  if(!TWI_READY) return 4;
  if(rw == TW_READ)
  {
    // receive mode
    if(TW_STATUS != TW_MR_SLA_ACK)
    {
      if(TW_STATUS == TW_MR_SLA_NACK) TWI_GenerateStop();
      return 5;
    }
  }
  else
  {
    // transmit mode
    if(TW_STATUS != TW_MT_SLA_ACK)
    {
      if(TW_STATUS == TW_MT_SLA_NACK) TWI_GenerateStop();
      return 6;
    }
  }

  return 0;

}

//-------------------------------------------------------------------
//External Functions
//-------------------------------------------------------------------
//initialize TWI
//-------------------------------------------------------------------
void TWI_Init(unsigned char timeout)
{
  // save starting time-out counter value
  toValue = timeout;

  // initialize TWI Bit Rate Prescaler
  TWSR = 0;	//(((TWPS & _BV(1)) != 0) ? _BV(TWPS1) : 0) | (((TWPS & _BV(0)) != 0) ? _BV(TWPS0) : 0);

  // initialize TWI Bit Rate
  TWBR = ((F_CPU/SCL_FREQ)-16)/2;	//TWBR_VALUE;
}

//-------------------------------------------------------------------
//Master Transmist
//-------------------------------------------------------------------
unsigned char TWI_Write(unsigned char sla, unsigned char* data, unsigned char count)
{

  unsigned char rc;

  // send SLA+W
  rc = TWI_SendSla(sla, TW_WRITE);
  if ( rc != 0 ) return rc;

  // send data byte(s)
  do
  {
    // send next data byte
    TWDR = *data++;
    TWCR = _BV(TWINT) | _BV(TWEN);

    // wait for data byte transmitted
    WAIT_FOR_TWINT;

    // check for timeout and check status code
    if (!TWI_READY) return 7;
    if (TW_STATUS != TW_MT_DATA_ACK)
    {
      if (TW_STATUS == TW_MT_DATA_NACK) TWI_GenerateStop();
      return 8;
    } // end if

  } while (--count != 0);

  // generate STOP
  rc = TWI_GenerateStop();
  if (rc != 0) return rc;

  return 0;

}

//-------------------------------------------------------------------
//Master Receive
//-------------------------------------------------------------------
unsigned char TWI_Read(unsigned char sla, unsigned char* data, unsigned char count)
{
  unsigned char rc;

  // send SLA+R
  rc = TWI_SendSla(sla, TW_READ);
  if (rc != 0) return rc;

  // read data byte(s)
  do
  {
    // set up to receive next data byte
    TWCR = _BV(TWINT) | _BV(TWEN) | ((count != 1) ? _BV(TWEA) : 0);

    // wait for data byte
    WAIT_FOR_TWINT;

    // check for timeout and check status
    if (!TWI_READY) return 9;
    if (TW_STATUS != ((count != 1) ? TW_MR_DATA_ACK : TW_MR_DATA_NACK))
    {
      TWI_GenerateStop();
      return 10;
    }

    // read data byte
    *(data++) = TWDR;

  } while (--count != 0);

  // generate STOP
  rc = TWI_GenerateStop( );

  if (rc != 0) return rc;

  return 0;

}

//-------------------------------------------------------------------
//decrement time-out counter
//-------------------------------------------------------------------
void TWI_DecTo(void)
{
  if ( toCounter != 0 ) toCounter--;
}
