/***************************************************************************/
/*  Name             : P.H. Seerden                                        */
/*  Description      : Driver for the byte I2C hardware interface on       */
/*                     the NXP LPC13xx / LPC11xx microcontrollers.         */
/*                     A transfer consists of one or more linked messages  */
/*                     separated by repeated STARTs.                       */
/*                     To start a transfer call function "I2C_Transfer".   */
/*                                                                         */
/*                  (C) Copyright 2010 NXP Semiconductors                  */
/*                                                                         */
/***************************************************************************/

#include <LPC13Uxx.h>                              // LPC1347 definitions
#include "i2c.h"

static I2C_MSG  *msg;                              // ptr to active message block
static BYTE cnt;                                   // bytes send/received counter
static BYTE state;                                 // driver state


void I2C_IRQHandler(void) 
{
  BYTE status;

    status = LPC_I2C->STAT;
    LPC_I2C->CONCLR = 0x2C;                        // clear flags AA, SI and STA

    switch(status >> 3)
    {
      case 0x00:                                   // bus Error has occured
        LPC_I2C->CONSET = 0x10;	                   // Set STO, generate STOP
        break;
      case 0x01:                                   // (rep) Start condition transmitted
      case 0x02:                                   // Slave address + R/W are transmitted
        LPC_I2C->DAT = msg->address; 
        LPC_I2C->CONSET = 0x04;	                   // Set AA (ack)
        break;
      case 0x03:                                   // SLA+W or DATA transmitted, ACK received
      case 0x05:                                   // DATA or STOP will be transmitted
        if (cnt < msg->nrBytes)
        {
            LPC_I2C->DAT = msg->buf[cnt++];        // sent first byte
            LPC_I2C->CONSET = 0x04;                // Set AA (ack)
        }
        else if (msg->next != 0)                   // any more messages to send
        {
            cnt = 0;
            msg = (I2C_MSG *) msg->next;           // next message
            LPC_I2C->CONSET = 0x20;                // set STA, generate (rep)START
        }
        else
        {
            LPC_I2C->CONSET = 0x10;                // set STO, generate STOP
            state = I2C_OK;
        }
        break;
      case 0x04:
      case 0x09:                                   // SLA+W/R transmitted, NOT ACK received
        state = I2C_NACK_ON_ADDRESS;               // driver finished
		    LPC_I2C->CONSET = 0x10;	                   // Set STO, generate STOP
        break;
      case 0x06:                                   // DATA transmitted, NOT ACK received
        state = I2C_NACK_ON_DATA;
        LPC_I2C->CONSET = 0x10;	                   // Set STO, generate STOP
        break;
      case 0x07:                                   // Arbitration lost in SLA+W or DATA
        LPC_I2C->CONSET = 0x20;                    // set STA, generate (rep)START
        break;
      case 0x08:                                   // SLA+R transmitted, ACK received
        if (msg->nrBytes != 1)
	          LPC_I2C->CONSET = 0x04;                // Set AA (ack next byte)
        break;
      case 0x0A:                                   // DATA received, ACK has been returned
        msg->buf[cnt++] = LPC_I2C->DAT;            // read next data
        if (cnt + 1 != msg->nrBytes)               // next byte the last ?
	          LPC_I2C->CONSET = 0x04;                // Set AA (return ack)
        break;
      case 0x0B:                                   // DATA received, NOT ACK has been returned
        msg->buf[cnt] = LPC_I2C->DAT;              // read last data
        if (msg->next != 0)                        // any more messages ?
        {
            cnt = 0;
            msg = (I2C_MSG *) msg->next;           // next message
            LPC_I2C->CONSET = 0x20;                // set STA, generate (rep)START
        }
        else
        {
            LPC_I2C->CONSET = 0x10;                // Set STO, generate STOP
            state = I2C_OK;
        }
        break;
      default:
        break;
    }
}

void I2C_Init(void)
/*****************/
{
    LPC_SYSCON->PRESETCTRL    |= (1<<1);           // de-assert I2C reset
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<5);           // I2C clock enable

    LPC_IOCON->PIO0_4 = 0x01;                      // I2C SCL
    LPC_IOCON->PIO0_5 = 0x01;                      // I2C SDA

    LPC_I2C->CONCLR = 0x6C;                        // clear flags AA, SI, STA and I2EN

    LPC_I2C->SCLL = SystemCoreClock / 100000;
    LPC_I2C->SCLH = SystemCoreClock / 100000;

    NVIC_EnableIRQ(I2C_IRQn);                      // enable the I2C Interrupt

    LPC_I2C->CONSET = 0x40;                        // enable I2C, set I2EN
}

BYTE I2C_Transfer(I2C_MSG *p)
/********************************
 * Start an I2C transfer, containing 1 or more LINKED messages.
 * The application must leave the message parameter block untouched.
 * The first I2C message is started with sending a START condition.
 ***************************************************************************/
{
  LONG timeOut;

    msg = p;
    cnt = 0;
    state = I2C_BUSY;                              // state of the I2C driver

    LPC_I2C->CONSET = 0x20;                        // generate START

    timeOut = 0;
    while (state == I2C_BUSY)
    {
        if (++timeOut > 40000)
            state = I2C_TIME_OUT;
    }

    return state;
}
