/*******************************************************************************
* Name: smbus.c 
* 
* Description: SMBus communication functions. Use the SMBus_Access functions to
* configure the MSP430 as a Master and communicate with the bq20zxx within the
* battery. To use Packet-Error-Checking (PEC), use the function SMBus_Access_PEC.
* 
* Texas Instruments, Inc
* 
* Version: 1.0
*******************************************************************************/

/*
 * Copyright (C) {2011} Texas Instruments Incorporated - http://www.ti.com/ 
 * 
 * 
 *  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.
 *
*/

// Include Header Files
#include <string.h>

#include "device.h"
#include "smbus.h"
#include "init.h"
#include "misc.h"

// Structure definition for SMBus port lines
typedef struct								
{
	unsigned int PortChanDir;		// Which Port Direction Address for Battery Channel 1-to-2 demux?
	unsigned int PortChanOut;		// Which Port Output Address for Battery Channel 1-to-2 demux?
	unsigned int PortChanBit;		// Which Bit on Port for Battery Channel selection?
	unsigned int PortComAddr;		// Which Port for Communication?
	unsigned int PortComBitData;	// Which Bit on the Communication Port for SDA?
	unsigned int PortComBitClock;	// Which Bit on the Communication Port for SCL?
} SMBusDescription_t;

// Initialize SMBus structure for this board
SMBusDescription_t SMBus = SMBUS_DEFAULT_STATE;

// Global Variables
unsigned char SMBus_Data_To_Slave[SMBUS_DATA_TO_SLAVE];
unsigned char SMBus_Data_From_Slave[SMBUS_DATA_FROM_SLAVE];
unsigned char *pTXData = 0x0;                     	// Pointer to TX data
unsigned char TXByteCounter = 0;
unsigned char *pRXData = 0x0;                     	// Pointer to RX data
unsigned char RXByteCounter = 0;
unsigned char RXFlag = FLAG_FAIL;					// Data Received Flag
unsigned char TXFlag = FLAG_FAIL;					// Data Transmitted Flag
unsigned char RWFlag = SMBUS_MASTER_MODE_READ;		// Read/Write Flag
unsigned char SMBus_Start_Flag = SMBUS_START_FLAG_RESET;	// Has a transaction been started?

/* ****************************************************************************
 * Function Name: SMBus_Initialize
 * 
 * Description: Initialize the MSP430 USCI I2C module as a Master for SMBus 
 * operation. Uses SMCLK as synchronous clock source to operate SMBus 
 * clock at approx 100 kHz. Slave address is defined in the header file.   
 * ***************************************************************************/
void SMBus_Initialize(unsigned char SMBus_Mode)
{
	// Zero out the arrays
	memset(SMBus_Data_To_Slave, '\0', SMBUS_DATA_TO_SLAVE);
	memset(SMBus_Data_From_Slave, '\0', SMBUS_DATA_FROM_SLAVE);
	
	// Select I2C/SMBus SDA/SCL as port functions
	*(unsigned int *) (SMBus.PortComAddr) |= (SMBus.PortComBitClock + SMBus.PortComBitData);								
	
	if(SMBus_Mode == SMBUS_MASTER_MODE) 
	{				
		UCB1CTL1 |= UCSWRST;                      		// Enable SW reset
		UCB1CTL0 = UCMST + UCMODE_3 + UCSYNC;     		// I2C Master, synchronous mode
		UCB1CTL1 = UCSSEL_2 + UCSWRST;            		// Use SMCLK, keep SW reset
		UCB1BR0 = 0;                             		// fSCL = SMCLK/256 ~ 100kHz
		UCB1BR1 = 1;									// Buad Rate Prescalar = UCB1BR1 * 256 + UCB1BR0 
		UCB1I2CSA = SMBUS_SLAVE_ADDRESS;                // Assign Slave Address 
		UCB1CTL1 &= ~UCSWRST;                    		// Clear SW reset, resume operation
		UCB1IE |= UCTXIE + UCRXIE + UCNACKIE;     		// Enable TX, RX, NACK interrupt	
	}
	else if(SMBus_Mode == SMBUS_SLAVE_MODE)
	{
		UCB1CTL1 |= UCSWRST;                      		// Enable SW reset
		UCB1CTL0 = UCMODE_3 + UCSYNC;     				// I2C Slave, synchronous mode
		UCB1CTL1 = UCSSEL_2 + UCSWRST;            		// Use SMCLK, keep SW reset
		UCB1I2COA = SMBUS_MSP430_ADDRESS;               // Assign MSP430 Address 
		UCB1CTL1 &= ~UCSWRST;                    		// Clear SW reset, resume operation
		UCB1IE |= UCRXIE + UCSTTIE + UCSTPIE;			// Enable RX, Start, Stop interrupts
	}		
}

/* ****************************************************************************
 * Function Name: SMBus_Access
 * 
 * Description: Generic SMBus access function to be used when the MSP430 is
 * in Master Transmitter/Receiver Mode. Useful for one-way communication 
 * between the MSP430 and the slave device where the slave device only responds
 * when asked to. 
 * 
 * Inputs:
 * o smbus_command: One byte of SMBus command (0x00 - 0x7F). Please refer
 * to the SMBus reference guide for more details. 
 * Notation: CELL_VOLTAGE_1, TEMPERATURE, etc. 
 * o read_write: Notation: SMBUS_MASTER_MODE_READ, SMBUS_MASTER_MODE_WRITE
 * o size_in_bytes: Based on the SMBus command. Notation: 1, 2, 4, ... 32
 * 
 * Outputs:
 * o smbus_access_status: Returns the status of whether the command was
 * successful or not. 
 * ***************************************************************************/
unsigned char SMBus_Access(unsigned char smbus_command, unsigned char \
	read_write, unsigned char size_in_bytes)
{
	unsigned char smbus_access_status = 0x0;	
	
	while(SMBus_NotReady());		// Wait for the SMBus to become idle	
	
	if (read_write == SMBUS_MASTER_MODE_READ) 
	{
		TXByteCounter = 1;								// TX 1 byte of SMBus Command for now
		pTXData = &(smbus_command);
		
		// RX the number of bytes as per the SBS command specification
		RXByteCounter = size_in_bytes;				
			
		// Store the RX bytes in the global array
		pRXData = (unsigned char *) SMBus_Data_From_Slave;
				
		// Initialize Flag to FAIL unless it succeeds 		
		RXFlag = FLAG_FAIL;								
		RWFlag = SMBUS_MASTER_MODE_READ;				// Read Mode
		
		while (UCB1CTL1 & UCTXSTP);              		// Stop condition sent?
	    
	    TA1CTL = TASSEL__ACLK + TACLR + MC__UP;			// Begin Timer A to measure time out
	    SMBus_Start_Flag = SMBUS_START_FLAG_SET;
	    UCB1CTL1 |= UCTR + UCTXSTT;             		// Go!
	    while (UCB1CTL1 & UCTXSTT);             		// Ensure START condition got sent
	    
	    // Did the Slave Device acknowledge it's own address? If yes, then proceed 
	    // with the command and data packets.
	    if((RXFlag != FLAG_NACK) && (RXFlag != FLAG_FAIL)) {						
	    	__bis_SR_register(LPM0_bits + GIE);         // Enter LPM0 w/ interrupts
	    }	
	    
	    smbus_access_status = RXFlag;					// Return status flag
	}
	else if (read_write == SMBUS_MASTER_MODE_WRITE) 
	{
		// Combine the SMBus Command with the data in the Transmit Buffer
		// into one char array 
		unsigned char data_to_transmit[sizeof(smbus_command) + sizeof(SMBus_Data_To_Slave)];
		unsigned int element = 0;
		
		data_to_transmit[0] = smbus_command;			// First element is the SMBus command byte
		
		// Remaining elements are data to be written to the device
		for (element = 1; element < sizeof(SMBus_Data_To_Slave); element++) 
		{
			data_to_transmit[element] = SMBus_Data_To_Slave[element - 1];
		}
		
		// Size of data transfer in bytes + 1 byte for the command
		TXByteCounter = size_in_bytes + 1;	
		
		// Set the pointer to the super-array created above			
		pTXData = (unsigned char *) data_to_transmit;
		
		// Initialize Flag to FAIL unless it succeeds 	
		TXFlag = FLAG_FAIL;
		RWFlag = SMBUS_MASTER_MODE_WRITE;				// Write Mode
		
	    while(UCB1CTL1 & UCTXSTP);              		// Stop condition sent?
	    
	    TA1CTL = TASSEL__ACLK + TACLR + MC__UP;			// Begin Timer A to measure time out
	    SMBus_Start_Flag = SMBUS_START_FLAG_SET;
	    UCB1CTL1 |= UCTR + UCTXSTT;             		// Go!
        while (UCB1CTL1 & UCTXSTT);             		// Ensure START condition got sent
        
	    // Did the Slave Device acknowledge it's own address? If yes, then proceed 
	    // with the command and data packets.
	    if((TXFlag != FLAG_NACK) && (TXFlag != FLAG_FAIL)) {
			__bis_SR_register(LPM0_bits + GIE);			// Enter LPM0 w/ interrupts
	    }	
	    while (UCB1CTL1 & UCTXSTP);              		// Stop condition sent?
	    
	    smbus_access_status = TXFlag;					// Return status flag
	}

	// Return status flag to the calling function
	return smbus_access_status;							
}

/* ****************************************************************************
 * Function Name: SMBus_Access_PEC
 * 
 * Description: Generic SMBus access function with Packet Error Checking to be 
 * used when the MSP430 is in Master Transmitter/Receiver Mode. The device that
 * transmits the last data byte also transmits the PEC byte. 
 * 
 * Inputs:
 * o smbus_command: One byte of SMBus command (0x00 - 0x7F). Please refer
 * to the SMBus reference guide for more details. 
 * Notation: CELL_VOLTAGE_1, TEMPERATURE, etc. 
 * o read_write: Notation: SMBUS_MASTER_MODE_READ, SMBUS_MASTER_MODE_WRITE
 * o size_in_bytes: Based on the SMBus command. Notation: 1, 2, 4, ... 32
 * 
 * Outputs:
 * o smbus_access_status: Returns the status of whether the command was
 * successful or not. 
 * ***************************************************************************/
unsigned char SMBus_Access_PEC(unsigned char smbus_command, unsigned char \
	read_write, unsigned char size_in_bytes)
{
	unsigned char smbus_access_status = 0x0;	
	unsigned char crc_msg_size = 0;
	unsigned char crc_msg[5];
	unsigned char crc_master_generated = 0;
	unsigned char crc_slave_generated = 0;	
	
	while(SMBus_NotReady());		// Wait for the SMBus to become idle
	
	if (read_write == SMBUS_MASTER_MODE_READ) 
	{
		TXByteCounter = 1;								// TX 1 byte of SMBus Command for now
		pTXData = &(smbus_command);
		
		// RX the number of bytes as per the SBS command specification
		// PLUS an additional byte for PEC
		RXByteCounter = size_in_bytes + 1;				
			
		// Store the RX bytes in the global array
		pRXData = (unsigned char *) SMBus_Data_From_Slave;
				
		// Initialize Flag to FAIL unless it succeeds 		
		RXFlag = FLAG_FAIL;								
		RWFlag = SMBUS_MASTER_MODE_READ;				// Read Mode
		
		while (UCB1CTL1 & UCTXSTP);              		// Stop condition sent?
	    
	    TA1CTL = TASSEL__ACLK + TACLR + MC__UP;			// Begin Timer A to measure time out
	    SMBus_Start_Flag = SMBUS_START_FLAG_SET;
	    UCB1CTL1 |= UCTR + UCTXSTT;             		// Go!
	    while (UCB1CTL1 & UCTXSTT);             		// Ensure START condition got sent
	    
	    // Did the Slave Device acknowledge it's own address? If yes, then proceed 
	    // with the command and data packets.
	    if((RXFlag != FLAG_NACK) && (RXFlag != FLAG_FAIL)) {						
	    	__bis_SR_register(LPM0_bits + GIE);         // Enter LPM0 w/ interrupts
	    }	
	    
	    // PEC Byte Processing
	    crc_msg[0] = SMBUS_SLAVE_ADDRESS << 1;			// Slave Address with R/W bit low
	    crc_msg[1] = smbus_command;						// Command byte
	    crc_msg[2] = (SMBUS_SLAVE_ADDRESS << 1) + 1;	// Slave Address with R/W bit high
	    crc_msg[3] = SMBus_Data_From_Slave[0];			// First byte RX
	    crc_msg[4] = SMBus_Data_From_Slave[1];			// Second byte RX
	    crc_slave_generated = SMBus_Data_From_Slave[2];	// Store PEC byte from slave device
	    
	    crc_msg_size = 5;								// # of bytes
		/* CRC function call, generate CRC byte to compare with slave CRC*/
		crc_master_generated = crc8MakeBitwise(CRC8_INIT_REM, CRC8_POLY, crc_msg, crc_msg_size);
		
		// PEC Byte Validation
		if(crc_master_generated == crc_slave_generated)
		{
			smbus_access_status = SMBUS_PEC_PASS;		// PEC byte validated
		}
		else
		{
			smbus_access_status = SMBUS_PEC_FAIL;		// Failed PEC test
		}										    
	   					
	}
	else if (read_write == SMBUS_MASTER_MODE_WRITE) 
	{
		// Combine the SMBus Command with the data in the Transmit Buffer
		// into one char array 
		unsigned char data_to_transmit[sizeof(smbus_command) + sizeof(SMBus_Data_To_Slave)];
		unsigned int element = 0;
		
		// PEC Byte Processing
		crc_msg[0] = SMBUS_SLAVE_ADDRESS << 1;			// Slave Address with R/W bit low
		crc_msg[1] = smbus_command;						// Command byte
		crc_msg[2] = SMBus_Data_To_Slave[0];			// First byte TX
		crc_msg[3] = SMBus_Data_To_Slave[1];			// Second byte TX
		
		crc_msg_size = 4;								// # of bytes
		
		/* CRC function call, generate CRC byte to transmit to slave device*/
		crc_master_generated = crc8MakeBitwise(CRC8_INIT_REM, CRC8_POLY, crc_msg, crc_msg_size);
		
		data_to_transmit[0] = smbus_command;			// First element is the SMBus command byte
		// Remaining elements are data to be written to the device
		for (element = 1; element < size_in_bytes; element++) 
		{
			data_to_transmit[element] = SMBus_Data_To_Slave[element - 1];
		}
		data_to_transmit[element + 1] = crc_master_generated;			// Last element is the PEC byte
		
		// Size of data transfer in bytes + 1 byte for the command + 1 byte for PEC
		TXByteCounter = size_in_bytes + 1 + 1;	
		
		// Set the pointer to the super-array created above			
		pTXData = (unsigned char *) data_to_transmit;
		
		// Initialize Flag to FAIL unless it succeeds 	
		TXFlag = FLAG_FAIL;
		RWFlag = SMBUS_MASTER_MODE_WRITE;				// Write Mode
		
	    while(UCB1CTL1 & UCTXSTP);              		// Stop condition sent?
	    
	    TA1CTL = TASSEL__ACLK + TACLR + MC__UP;			// Begin Timer A to measure time out
	    SMBus_Start_Flag = SMBUS_START_FLAG_SET;
	    UCB1CTL1 |= UCTR + UCTXSTT;             		// Go!
        while (UCB1CTL1 & UCTXSTT);             		// Ensure START condition got sent
        
	    // Did the Slave Device acknowledge it's own address? If yes, then proceed 
	    // with the command and data packets.
	    if((TXFlag != FLAG_NACK) && (TXFlag != FLAG_FAIL)) {
			__bis_SR_register(LPM0_bits + GIE);			// Enter LPM0 w/ interrupts
	    }	
	    while (UCB1CTL1 & UCTXSTP);              		// Stop condition sent?
	    
	    // PEC Byte Validation
	    if(TXFlag == FLAG_SUCCESS)
	    {
	    	smbus_access_status = SMBUS_PEC_PASS;				// Slave ACK'ed the PEC byte
	    }
	    else
	    {
	    	smbus_access_status = SMBUS_PEC_FAIL;				// Slave NACK'ed the PEC byte
	    }
	    	
	}

	// Return status flag to the calling function
	return smbus_access_status;							
}

/* ****************************************************************************
 * Function Name: SMBus_Monitor_Slave_Broadcast
 * 
 * Description: With the MSP430 USCI I2C/SMBus module in Slave mode, it 
 * monitors the bus for a broadcast from the bq device and records the
 * charging voltage, charging current and status of the alarms. 
 * ***************************************************************************/
unsigned char SMBus_Monitor_Slave_Broadcast(void)
{
	while(SMBus_NotReady());		// Wait for the SMBus to become idle	
	
	// Store the RX bytes in the global array
	pRXData = (unsigned char *) SMBus_Data_To_Slave;
			
	// Initialize Flag to FAIL unless it succeeds 		
	RXFlag = FLAG_FAIL;
		
	// Write Mode - The bq device takes over the bus as a MASTER, the 
	// MSP430 becomes a SLAVE and the bq devices WRITEs data to the MSP430.						
	RWFlag = SMBUS_SLAVE_MODE_WRITE;
	SMBus_Start_Flag = SMBUS_START_FLAG_RESET;				
			
   	__bis_SR_register(LPM0_bits + GIE);         // Enter LPM0 w/ interrupts
	
	// Return status flag to the calling function
	return RXFlag;					
}

/*------------------------------------------------------------------------------
 * Function Name: SMBus_NotReady
 * 
 * Description: Checks the status of the I2C/SMBus to see if it is busy.
 *----------------------------------------------------------------------------*/
unsigned char SMBus_NotReady()
{
  return ((UCB1STAT & UCBBUSY) || (UCB1STAT & UCSCLLOW));
}

/*------------------------------------------------------------------------------
 * Function Name: SMBus_Reset
 * 
 * Description: Reset the MSP430 I2C/SMBus USCI module.
 *----------------------------------------------------------------------------*/
void SMBus_Reset(unsigned char SMBus_Start_Flag)
{

  if(SMBus_Start_Flag == SMBUS_START_FLAG_SET) 
  {
  
	  // If the I2C/SMBus has been held up for an extended period
	  // of time, then reset the bus with a STOP condition. 
	  UCB1CTL1 |= UCTXSTP;                      // Send stop on SDA line
	  UCB1IFG = 0x00;							// Clear all interrupt flags to cease
	  											// any more transactions
	  UCB1CTL1 |= UCSWRST;                      // Enable SW reset
	  UCB1CTL1 &= ~UCTXSTT;						// Clear Start Bit
	  UCB1CTL1 &= ~UCTXSTP;						// Clear Stop Bit	  
	  UCB1CTL1 &= ~UCSWRST;
	  UCB1IE = 0x00;							// Disable all interrupts
	  UCB1I2COA = 0x00;							// Reset own address to 0x00
	  TXFlag = FLAG_FAIL;						// Reset the flag to indicate error
	  RXFlag = FLAG_FAIL;						// Reset the flag to indicate error
	  
	  SMBus_Start_Flag = SMBUS_START_FLAG_RESET;
	  
  }

}

/*------------------------------------------------------------------------------
unsigned short crc8MakeBitwise(...)
IN: unsigned char CRC           => Initial crc value
    unsigned char Poly          => CRC polynomial 
    unsigned int *Pmsg          => Pointer to input data stream
    unsigned char Msg_Size      => No. of bytes
OUT: unsigned short CRC         => Result of CRC function
    
------------------------------------------------------------------------------*/
static unsigned short crc8MakeBitwise(unsigned char CRC, unsigned char Poly, unsigned char *Pmsg, unsigned int Msg_Size)
{
    unsigned int i, j, carry;
    unsigned char msg;

    CRC = *Pmsg++;                          // first byte loaded in "crc"		
    for(i = 0 ; i < Msg_Size-1 ; i ++)
    {
        msg = *Pmsg++;                      // next byte loaded in "msg"
        
	for(j = 0 ; j < 8 ; j++)
        {
	  carry = CRC & 0x80;               	// check if MSB=1			
          CRC = (CRC << 1) | (msg >> 7);    // Shift 1 bit of next byte into crc
                if(carry) CRC ^= Poly;      // If MSB = 1, perform XOR
		msg <<= 1;                  		// Shift left msg byte by 1
        }
    }
	// The previous loop computes the CRC of the input bit stream. To this, 
        // 8 trailing zeros are padded and the CRC of the resultant value is 
        // computed. This gives the final CRC of the input bit stream.
    	for(j = 0 ; j < 8 ; j++)
        {
			carry = CRC & 0x80;
			CRC <<= 1;
			if(carry) CRC ^= Poly;
        }	
    
    return(CRC);
}

/* ****************************************************************************
 * Function Name: SMBus_Select
 * 
 * Description: Selects the active slave bus. This function controls a
 * 1-to-2 demux to route the SDA/SCL lines between the MSP430 to two identical
 * slave devices with the same slave address. 
 * ***************************************************************************/
void SMBus_Select(unsigned char batt_num)
{
	// SMBus Channel Select
	*(unsigned int *) SMBus.PortChanDir |= SMBus.PortChanBit;			// Set the port bit as output				
	
	if(batt_num == BATT_1)
	{
		*(unsigned int *) SMBus.PortChanOut &= ~(SMBus.PortChanBit);	// Select Battery 1
	}
	else if(batt_num == BATT_2)
	{
		*(unsigned int *) SMBus.PortChanOut |= SMBus.PortChanBit;		// Select Battery 2
	}						
	
}
