/* --COPYRIGHT--,BSD
 * Copyright (c) 2012, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * --/COPYRIGHT--*/
//
// Include files
//
#include "deviceSpecifics.h"
#include "i2cMaster.h"

// 
// Definitions
//
#define SLAVE_ADDRESS           0x40        /*! Address of EEPROM   */
#define MASTER_ADDRESS          0x48        /*! I2C own address     */

//
//  Local variables
//
static uint8_t *PTxData;            /*! Pointer to TX data  */
static int16_t TXByteCtr;           /*! TX Data counter     */
static uint8_t *PRxData;            /*! Pointer to RX data  */
static int16_t RXByteCtr;           /*! RX Data counter     */
static uint8_t RxBuffer[256];       /*! RX Buffer           */

static uint8_t nack_from_slave;     /*! Checks if NACK was received   */
static uint8_t Timeout;             /*! Checks if timeout has expired */

//
//  Local function prototypes
//  
static uint8_t i2cTimeouthasExpired(void);
static void i2cStartTimeout(void);
static void i2cTimeoutStop(void);


/******************************************************************************
 * @brief  Initialize the UCB1 I2C module as I2C Master
 *         Slave address defined by SLAVE_ADDRESS
 *
 * @return none
 *****************************************************************************/
void i2cInit(void)
{
    
  I2C_SDA_PORT_SEL |= I2C_SDA_PIN;          // UCB1SDA Function Selected
  I2C_SCL_PORT_SEL |= I2C_SCL_PIN;          // UCB1SCL Function Selected
  I2C_SDA_POUT |= I2C_SDA_PIN;              // Enable Pull-ups
  I2C_SCL_POUT |= I2C_SCL_PIN;          
  I2C_SDA_REN |= I2C_SDA_PIN;              // Enable Pull-ups
  I2C_SCL_REN |= I2C_SCL_PIN;          
  
  UCB1CTL1 = UCSWRST;                       // Enable SW reset
  UCB1CTL0 = UCMST + UCMODE_3 + UCSYNC;     // I2C Master, synchronous mode
  UCB1CTL1 = UCSSEL_2 + UCSWRST;            // Use SMCLK, keep SW reset
  UCB1BR0 = 160;                           // fSCL = 16MHz/160 = 100kHz
  //UCB1BR0 = 40;                           // fSCL = 16MHz/40 = 400kHz
  UCB1BR1 = 0;
  UCB1I2CSA = SLAVE_ADDRESS;                // Slave Address is 048h
  UCB1CTL1 &= ~UCSWRST;                     // Clear SW reset, resume operation

  PRxData = RxBuffer;                       // Incase no receive buffer is assigned
                                            // before RX interrupt occurs.

  UCB1IE |= UCTXIE;                         // Enable TX interrupt
  UCB1IE |= UCRXIE;

}

/******************************************************************************
 * @brief  Send a message, I2CMessage, to I2C slave
 *
 * @param  I2CMessage       I2C message being sent
 *         messageLength    Lenght of message in bytes
 *
 * @return RET_I2C_OK       No error
 *         RET_I2C_ERROR    Error (NACK or timeout)
 *****************************************************************************/
int8_t i2cSendMessage(uint8_t* I2cMessage, uint16_t messageLength)
{
    int8_t ret;

    UCB1IE |= UCTXIE;                       // Enable TX interrupt
    UCB1IE |= UCRXIE|UCNACKIE;

    nack_from_slave = 0;

    PTxData = (unsigned char *)I2cMessage;  // TX array start address
    TXByteCtr = messageLength ;             // Load TX byte counter

    P10DIR |= BIT0;
    P10OUT |= BIT0;
    i2cStartTimeout();

    UCB1CTL1 |= UCTR + UCTXSTT;             // I2C TX, start condition
  __bis_SR_register(LPM0_bits + GIE);       // Enter LPM0, enable interrupts
    
    P10OUT &= ~BIT0;

    if ((i2cTimeouthasExpired() == 1) || (nack_from_slave == 1))
        ret = RET_I2C_ERROR;
    else
        ret = RET_I2C_OK;

    i2cTimeoutStop();
    return ret;
}



/******************************************************************************
 * @brief  Send a message not waiting for timeout
 *
 * @param  I2CMessage       I2C message being sent
 *         messageLength    Lenght of message in bytes
 *
 * @return RET_I2C_OK       No error
 *         RET_I2C_ERROR    Error (NACK or timeout)
 *****************************************************************************/
int8_t i2cSendMessagenoTimeout(uint8_t * I2cMessage, uint16_t messageLength)
{
    int ret;

    UCB1IE |= UCTXIE;                         // Enable TX interrupt
    UCB1IE |= UCRXIE|UCNACKIE;

    nack_from_slave = 0;
    PTxData = (unsigned char *)I2cMessage;        // TX array start address
    TXByteCtr = messageLength ;               // Load TX byte counter

    P10DIR |= BIT0;
    P10OUT |= BIT0;
    UCB1CTL1 |= UCTR + UCTXSTT;               // I2C TX, start condition
    __bis_SR_register(LPM0_bits + GIE);       // Enter LPM0, enable interrupts
    P10OUT &= ~BIT0;

    if (nack_from_slave == 1)
        ret = RET_I2C_ERROR;
    else
        ret = RET_I2C_OK;

    return ret;
}


/******************************************************************************
 * @brief  Receive a message from the I2C slave 
 *
 * @param  I2CMessage       Pointer to array where data will be stored
 * @param  messageLength    Number of bytes being requested
 *
 * @return RET_I2C_OK       No error
 *         RET_I2C_ERROR    Error (NACK or timeout)
 *****************************************************************************/
int8_t i2cReceiveMessage(uint8_t * I2cMessage, uint16_t messageLength)
{
    int8_t  ret;

    UCB1CTL1 &= ~UCTR;
    UCB1IE |= UCRXIE|UCNACKIE;                         // Enable RX interrupt
    nack_from_slave = 0;

    PRxData = (unsigned char *)I2cMessage;    // Start of RX buffer
    RXByteCtr = messageLength;                // Load RX byte counter
    P10DIR |= BIT1;
    P10OUT |= BIT1;

    i2cStartTimeout();
    UCB1CTL1 |= UCTXSTT;
    __bis_SR_register(LPM0_bits + GIE);     // Enter LPM0, enable interrupts
                                            // Remain in LPM0 until all data
                                            // is RX'd

    while ( (i2cTimeouthasExpired() == 0) && 
            ((UCB1CTL1 & UCTXSTP) != 0x00) && 
            (nack_from_slave == 0) )
        ;

    P10OUT &= ~BIT1;

    if ((i2cTimeouthasExpired() == 1) || (nack_from_slave == 1))
        ret = RET_I2C_ERROR;
    else
        ret = RET_I2C_OK;

    i2cTimeoutStop();
    return ret;
}

/******************************************************************************
 * @brief  Forces a timeout on I2C 
 *
 * @return RET_I2C_OK       I2C timeout sent
 *         RET_I2C_ERROR    Error (NACK)
 *****************************************************************************/
int8_t i2cForceTimeout(void)
{
    uint32_t counter = 200000;
    
    UCB1IE &= ~(UCTXIE|UCRXIE|UCNACKIE);    // Disable USCI interrupts
    
    UCB1CTL1 &= ~UCTR;
    UCB1CTL1 |= UCTXSTT;             // I2C RX, start condition
  
    while (counter-- != 0x00)
    {
        if (UCB1IFG & UCNACKIFG)
        {
            UCB1IFG &= ~UCNACKIFG;
            i2cSendStop();
            return RET_I2C_ERROR;
        }
    }
    i2cSendStop();
    return RET_I2C_OK;
}

/******************************************************************************
 * @brief  Send a Repeated Start to the I2C bus
 *
 * @return none
 *****************************************************************************/
void i2cSendRestart()
{
  UCB1CTL1 |= UCTXSTT;
}

/******************************************************************************
 * @brief  Send an Stop to the I2C bus
 *
 * @return none
 *****************************************************************************/
void i2cSendStop()
{
  UCB1CTL1 |= UCTXSTP;
}

/******************************************************************************
 * @brief  I2C Interrupt service routine
 *
 * @return none
 *****************************************************************************/
#pragma vector = USCI_B1_VECTOR
__interrupt void USCI_B1_ISR(void)
{
  switch(__even_in_range(UCB1IV,12))
  {
  case  0: break;                           // Vector  0: No interrupts
  case  2: break;                           // Vector  2: ALIFG
  case  4:
    nack_from_slave = 1;
    UCB1IFG &= ~UCNACKIFG;
    __bic_SR_register_on_exit(LPM0_bits); // Exit active CPU
    break;                           // Vector  4: NACKIFG
  case  6: break;                           // Vector  6: STTIFG
  case  8: break;                           // Vector  8: STPIFG
  case 10:

    P10DIR |= BIT2;
    P10OUT |= BIT2;
    RXByteCtr--;                            // Decrement RX byte counter
    if (RXByteCtr)
    {
      *PRxData++ = UCB1RXBUF;               // Move RX data to address PRxData
      if (RXByteCtr == 1)                   // Only one byte left?
        UCB1CTL1 |= UCTXSTP;                // Generate I2C stop condition
    }
    else
    {
      *PRxData = UCB1RXBUF;                 // Move final RX data to PRxData
      __bic_SR_register_on_exit(LPM0_bits); // Exit active CPU
    }


    P10OUT &= ~BIT2;

    break;                                  // Vector 10: RXIFG
  case 12:                                  // Vector 12: TXIFG
    P10DIR |= BIT3;
    P10OUT |= BIT3;
    if (TXByteCtr)                          // Check TX byte counter
    {
      UCB1TXBUF = *PTxData++;               // Load TX buffer
      TXByteCtr--;                          // Decrement TX byte counter
    }
    else
    {
      UCB0CTL1 |= UCTXSTP;                  // I2C stop condition
      UCB0IFG &= ~UCTXIFG;                  // Clear USCI_B0 TX int flag
      __bic_SR_register_on_exit(LPM0_bits); // Exit LPM0
    }
    P10OUT &= ~BIT3;

  default: break;
  }
}

/******************************************************************************
 * @brief  Start a timeout timer
 *
 * @return none
 *****************************************************************************/
static void i2cStartTimeout(void)
{
    Timeout =0;
    TA1CCTL0 = CCIE;                          // CCR0 interrupt enabled
    TA1CCR0 = 1000;
    TA1CTL = TASSEL_1 + MC_1 + TACLR;         // ACLK, upmode, clear TAR
}

/******************************************************************************
 * @brief  Checks if timeout has expired
 *
* @return 1: Timeout expired  0: Timeout not expired
 *****************************************************************************/
static uint8_t i2cTimeouthasExpired(void)
{
    return Timeout;
}

/******************************************************************************
 * @brief  Stop the timeout timer
 *
 * @return none
 *****************************************************************************/
static void i2cTimeoutStop(void)
{
    TA1CTL = 0;
    Timeout = 0;
}


/******************************************************************************
 * @brief  Timeout Timer Interrupt Service Routine
 *
 * @return none
 *****************************************************************************/
#pragma vector=TIMER1_A0_VECTOR
__interrupt void TIMER1_A0_ISR(void)
{
    Timeout = 1;
    __bic_SR_register_on_exit(LPM0_bits);
}
