/*
 * SMBus.c
 *
 *  Created on: Sep 4, 2014
 *      Author: msolecki
 *
 *      We use TA1 for timeout check for the SMBus
 */

#include <msp430.h>
#include "include/include.h"
#include "include/SMBus.h"

#define BQ78350_ADDR		(0x16>>1)
#define MFR_BLK_ACCESS		0x44
#define MAX_QUEUE			75
#define MAX_RX_LEN			50
#define MAX_TX_LEN			10
#define CRC8_POLY			0x07
#define CRC8_INIT_REM		0x00

//SMBus maximum cumulative clock stretching from start to stop is 25ms.  Give a little slack to avoid useless errors.
#define TIMERSMBUS_TIMEOUT	((TIMERSMB_CLK_FREQ / (TIMERSMB_PDIV * TIMERSMB_IDIV)) * 26/1000)

#define TIMERSMBUS_2MS		(TIMERSMB_CLK_FREQ / (500 * TIMERSMB_PDIV * TIMERSMB_IDIV))

typedef struct message_t {
	unsigned char address;
	read_write mess_dir;
	unsigned char is_block;
	unsigned char tx_data[MAX_TX_LEN];
	unsigned int tx_cnt;
	unsigned char *rx_data;
	unsigned int rx_cnt;
	unsigned char nack;
	void (*rx_done_callback)(unsigned char);
	unsigned char id;
} message;

typedef struct message_queue_t {
	message mess_q[MAX_QUEUE];
	unsigned int mess_q_cnt;
	unsigned int mess_q_in;
	unsigned int mess_q_out;
} message_queue;

static unsigned char crc8MakeBitwise(unsigned char CRC, unsigned char Poly,
		unsigned char *Pmsg, unsigned int Msg_Size);

smbus_state cur_state = smbus_idle;
unsigned int error_flag;
unsigned int tx_done_flag;
unsigned int mess_done_flag;

unsigned int timer_delay_over;

unsigned long nack_cntr;

volatile message *cur_mess;
message_queue smbus_queue;

volatile unsigned char *PTxData;
volatile unsigned char *PRxData;
volatile unsigned char TXByteCtr;
volatile unsigned char RXByteCtr;

void smbus_q_init(void) {
	smbus_queue.mess_q_cnt = 0;
	smbus_queue.mess_q_in = 0;
	smbus_queue.mess_q_out = 0;
}

// This function assumes the SMBus module is already properly configured.
void smbus_reset(void) {

	UCB1CTL1 |= UCTXSTP;	//Force a stop right away
	TA1CTL = MC__STOP;		//Stop the timeout counter
	UCB1CTL1 |= UCSWRST;		//Reset SMBus module

	cur_state = smbus_down;
}

void smbus_resume(void) {
	UCB1CTL1 &= ~UCSWRST;	//Enable SMBus module
}

void smbus_init(void) {

	UCB1CTL1 = UCSWRST;	//place UCB in reset state

	cur_state = smbus_idle;
	error_flag = 0;
	timer_delay_over = 0;
	tx_done_flag = 0;
	mess_done_flag = 0;
	nack_cntr = 0;
	smbus_q_init();

	PSEL_MODE_MODULE(P8SEL, BIT5 | BIT6);
	//P8SEL |= BIT5 | BIT6;		//set IO for SMBus

	// set UCB in I2C mode
	// multiple masters | master | I2C mode | synchronous for I2C | Clk source: AUXILIARY CLK (ACLK)
	UCB1CTL0 = UCMST | UCMODE_3 | UCSYNC; //UCMM |
	UCB1CTL1 |= SMBUS_CLK_SRC;

	UCB1I2COA = 0x08;	//set own address

	//set frequency to 100kHz
	UCB1BR0 = ((SMBUS_CLK_FREQ / SMBUS_FREQ) & 0xFF);
	UCB1BR1 = (((SMBUS_CLK_FREQ / SMBUS_FREQ) >> 8) & 0xFF);

	UCB1IFG &= 0xFF;	//Clear all flags

	UCB1I2CSA = BQ78350_ADDR;			// Slave Address is 1st byte of PayLoad
	UCB1CTL1 &= ~UCSWRST;					// Clear SW reset, resume operation
	UCB1IE |= UCTXIE + UCRXIE + UCNACKIE;	// Enable TX, RX, NACK interrupt

	TA1CCR0 = TIMERSMBUS_TIMEOUT;		// Set top value for CCR0
	TA1CCR1 = TIMERSMBUS_2MS;// Set timeout value for CCR1 (only used to add delay between frames)
}

unsigned char smbus_is_queue_full(void) {

	if(smbus_queue.mess_q_cnt == MAX_QUEUE)
		return 1;

	return 0;
}

smbus_state smbus_get_state(void) {
	return cur_state;
}

void smbus_requestWord(unsigned char addr, unsigned char id, unsigned char *return_data, void (*rx_done_callback)(unsigned char)) {
	message *new_mess;

	if (smbus_queue.mess_q_cnt < MAX_QUEUE) {
		new_mess = &smbus_queue.mess_q[smbus_queue.mess_q_in];

		//add data to message queue
		new_mess->address = BQ78350_ADDR;
		new_mess->mess_dir = read_mess;
		new_mess->is_block = 0;
		new_mess->tx_data[0] = addr;
		new_mess->tx_cnt = 1;
		new_mess->rx_data = return_data;
		new_mess->rx_cnt = 3;
		new_mess->nack = 0;
		new_mess->rx_done_callback = rx_done_callback;
		new_mess->id = id;

		//update queue pointers
		smbus_queue.mess_q_in++;
		if (smbus_queue.mess_q_in == MAX_QUEUE) smbus_queue.mess_q_in = 0;
		smbus_queue.mess_q_cnt++;
	}
}

void smbus_requestMfrAccessReg(unsigned int acc_addr, unsigned char id, unsigned char *return_data, void (*rx_done_callback)(unsigned char)) {
	unsigned int i;
	message *new_mess;
	unsigned char stream[MAX_TX_LEN + 1];
	if (smbus_queue.mess_q_cnt < MAX_QUEUE - 1) { //queue must have 2 spaces left to request
		new_mess = &smbus_queue.mess_q[smbus_queue.mess_q_in];

		// Add data request to queue (1st message)
		new_mess->address = BQ78350_ADDR;
		new_mess->mess_dir = write_mess;
		new_mess->is_block = 0;
		stream[0] = BQ78350_ADDR << 1;
		stream[1] = MFR_BLK_ACCESS; // SBS command: 0x44 Manufacturer Block Access function
		stream[2] = 2; 						// Tx block length : 2
		stream[3] = (unsigned char) acc_addr & 0xFF;	//Mfr address LSB
		stream[4] = (unsigned char) (acc_addr >> 8) & 0xFF;	//Mfr address MSB

		for (i = 0; i < 4; i++) {
			new_mess->tx_data[i] = stream[i + 1];
		}

		new_mess->tx_data[4] = crc8MakeBitwise(CRC8_INIT_REM, CRC8_POLY,
				&stream[0], 5);
		new_mess->tx_cnt = 5;
		new_mess->rx_data = 0;
		new_mess->rx_cnt = 0;
		new_mess->nack = 0;

		//update queue pointers
		smbus_queue.mess_q_in++;
		if (smbus_queue.mess_q_in == MAX_QUEUE) smbus_queue.mess_q_in = 0;
		smbus_queue.mess_q_cnt++;

		// Add message to fetch data (2nd message)
		new_mess = &smbus_queue.mess_q[smbus_queue.mess_q_in];
		new_mess->address = BQ78350_ADDR;
		new_mess->mess_dir = read_mess;
		new_mess->is_block = 1;
		new_mess->tx_data[0] = MFR_BLK_ACCESS;// SBS command: 0x44 Manufacturer Block Access function
		new_mess->tx_cnt = 1;
		new_mess->rx_data = return_data;
		new_mess->rx_cnt = 2;
		new_mess->nack = 0;
		new_mess->id = id;
		new_mess->rx_done_callback = rx_done_callback;

		//update queue pointers
		smbus_queue.mess_q_in++;
		if (smbus_queue.mess_q_in == MAX_QUEUE) smbus_queue.mess_q_in = 0;
		smbus_queue.mess_q_cnt++;
	}
}

void smbus_writeWord(unsigned char addr, unsigned char *data) {
	message *new_mess;

	if (smbus_queue.mess_q_cnt < MAX_QUEUE) {
		new_mess = &smbus_queue.mess_q[smbus_queue.mess_q_in];

		new_mess->address = BQ78350_ADDR;
		new_mess->mess_dir = write_mess;
		new_mess->is_block = 0;
		new_mess->tx_data[0] = addr;
		new_mess->tx_data[1] = *data;
		new_mess->tx_data[2] = *(data + 1);

		//update queue pointers
		smbus_queue.mess_q_in++;
		if (smbus_queue.mess_q_in == MAX_QUEUE) smbus_queue.mess_q_in = 0;
		smbus_queue.mess_q_cnt++;
	}
}

void smbus_writeBlock(unsigned char *data, unsigned int length) {
	message *new_mess;
	unsigned char stream[MAX_TX_LEN + 4];
	unsigned int i;

	if (smbus_queue.mess_q_cnt < MAX_QUEUE) {
		new_mess = &smbus_queue.mess_q[smbus_queue.mess_q_in];

		new_mess->address = BQ78350_ADDR;
		new_mess->mess_dir = write_mess;
		new_mess->is_block = 0;
		new_mess->tx_cnt = length + 3;
		new_mess->tx_data[0] = MFR_BLK_ACCESS;
		new_mess->tx_data[1] = length;

		stream[0] = (new_mess->address << 1) & ~0x01;
		stream[1] = new_mess->tx_data[0];
		stream[2] = new_mess->tx_data[1];

		for (i = 0; i < length; i++) {
			new_mess->tx_data[i + 2] = *data;
			stream[i + 3] = *data;
			data++;
		}
		new_mess->tx_data[length + 2] = crc8MakeBitwise(CRC8_INIT_REM,
		CRC8_POLY, &stream[0], length + 3);

		//update queue pointers
		smbus_queue.mess_q_in++;
		if (smbus_queue.mess_q_in == MAX_QUEUE) smbus_queue.mess_q_in = 0;
		smbus_queue.mess_q_cnt++;
	}
}

void smbus_writeToFlash(unsigned char *data, unsigned int length) {
	smbus_writeBlock(data, length);
}

void smbus_read_nack(void) {
	__no_operation();
}
//Return 1 if there is a message being processed.  Otherwise, returns 0.
unsigned char smbus_process(void) {
	//unsigned char stream[MAX_RX_LEN + MAX_TX_LEN];

	if (error_flag == 1) {
		cur_state = smbus_no_resp;
	}
	else if (error_flag == 2) {
		cur_state = smbus_failure;
	}

	cur_mess = &smbus_queue.mess_q[smbus_queue.mess_q_out];

	switch (cur_state) {
	case smbus_idle:

	case smbus_wait:
		if (smbus_queue.mess_q_cnt > 0) {

			if (UCB1STAT & UCBBUSY) {
				cur_state = smbus_wait;
			}
			else {
				error_flag = 0;
				mess_done_flag = 0;
				tx_done_flag = 0;
				timer_delay_over = 1;
				cur_state = smbus_delay;
			}
		}
		else {
			cur_state = smbus_idle;

			return 0;
		}
		//break;

	case smbus_delay:
		if (timer_delay_over == 1) {
			PTxData = (unsigned char *) (&cur_mess->tx_data[0]);
			PRxData = (unsigned char *) (&cur_mess->rx_data[0]);
			TXByteCtr = cur_mess->tx_cnt;
			RXByteCtr = cur_mess->rx_cnt;

			if (UCB1CTL1 & UCTXSTP) {				// Stop condition sent?
				break;
			}
			cur_state = smbus_tx;				// Change state to transmit
			TA1CTL = TIMERSMB_CLK_SRC | TACLR | TIMERSMB_IDIV_BITS | MC__UP | TAIE; // Start delay

			UCB1CTL1 |= UCTR + UCTXSTT;					// Go!
		}
		break;

	case smbus_done:
		/*
		 * PEC is ignored for now
		 if (cur_mess->mess_dir == read_mess) {

		 stream[0] = (UCB1I2CSA << 1) & ~0x01;
		 stream[1] = cur_mess->address;
		 for (i = 0; i < cur_mess->tx_cnt; i++) {
		 stream[i + 1] = cur_mess->tx_data[i];
		 }

		 stream[cur_mess->tx_cnt] = cur_mess->address | 0x01;
		 for (i = 0; i < cur_mess->rx_cnt; i++) {
		 stream[i + cur_mess->tx_cnt + 1] = cur_mess->rx_data[i];
		 }
		 PEC = crc8MakeBitwise(CRC8_INIT_REM, CRC8_POLY, &(stream[0]),
		 cur_mess->tx_cnt + cur_mess->rx_cnt + 2);
		 if (PEC == *PRxData) {
		 //update BQ78350 data for state machine
		 //P1OUT ^= 0x01;
		 }
		 // else { //error in PEC }
		 }
		 */
		//else { we have a tx only message }
		if (smbus_queue.mess_q_cnt > 0) {	// Remove last message queue

			if(cur_mess->mess_dir == read_mess) {
				if(cur_mess->nack)
					nack_cntr++;
				//Read completed without error, jump to the callback, if there is any.
				else if(cur_mess->rx_done_callback)
					(cur_mess->rx_done_callback)(cur_mess->id);
			}
			else if(cur_mess->nack) {
				nack_cntr++;
			}

			smbus_queue.mess_q_out++;
			if (smbus_queue.mess_q_out == MAX_QUEUE) smbus_queue.mess_q_out = 0;
			smbus_queue.mess_q_cnt--;

		}
		cur_state = smbus_wait;
		break;

	case smbus_tx:
		if (tx_done_flag) cur_state = smbus_rx;
		//drop through
	case smbus_rx:
		if (mess_done_flag) cur_state = smbus_done;
		break;
	case smbus_down:
	case smbus_failure:
	case smbus_no_resp:
		//Do nothing
		break;
	default:
		cur_state = smbus_wait;
		break;
	}

	return 1;
}

unsigned int smbus_is_running(void) {
	return (smbus_queue.mess_q_cnt > 0);
}

unsigned int smbus_get_error(void) {
	return error_flag;
}

/*-----------------------------------------------------------------------------+
 |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 char 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;					// ckeck 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);
}

/*----------------------------------------------------------------------------+
 | I2C/SMBus interrupt (USCI B1)
 +----------------------------------------------------------------------------*/
#pragma vector = USCI_B1_VECTOR
__interrupt
void USCI_B1_ISR(void) {
	switch (__even_in_range(UCB1IV, 12)) {
	case 0:
		break;
	case 2:									// ALIFG
		break;
	case 4:									// NACKIFG
		UCB1CTL1 |= UCTXSTP;				// Transmit a stop
		UCB1IFG &= ~UCNACKIFG;				// Clear NACK flag
		TA1CTL = MC__STOP;					// Turn off timeout timer
		cur_mess->nack = 1;					//Transaction failed.  No need to wake up after ISR servicing.
		mess_done_flag = 1;
		break;
	case 6:									// STTIFG
		break;
	case 8:									// STPIFG
		break;
	case 10:								// RXIFG
		TA1CTL = MC__STOP;					// Turn off timeout timer
		RXByteCtr--;						// Decrement RX byte counter

		if (RXByteCtr) {
			*PRxData = UCB1RXBUF;			// Multiple bytes left to receive
			PRxData++;

			//if block access, 1st byte is block length
			RXByteCtr = (cur_mess->is_block) ? (UCB1RXBUF + 1) : RXByteCtr;
			//reset is_block flag so length is only updated once
			cur_mess->is_block = 0;

			TA1CTL = TIMERSMB_CLK_SRC | TACLR | TIMERSMB_IDIV_BITS | MC__UP | TAIE; // Start delay
		}
		else {
			*PRxData = UCB1RXBUF;				// Last byte to be received
			mess_done_flag = 1;					// Flag to show read completed
			__bic_SR_register_on_exit(LPM3_bits);	// Exit LPM3 since there is some processing to do after a read
		}

		if (RXByteCtr == 1)					// Only one byte left?
		UCB1CTL1 |= UCTXSTP;				// Generate I2C stop condition

											// If reading only 1 byte, stop condition
											// should have already been sent
		break;
	case 12:								// TXIFG
											// Set when UCB1TXBUF is empty

		TA1CTL = MC__STOP;					// Turn off timeout timer
		if (TXByteCtr)						// If there's something to transmit
		{
			UCB1TXBUF = *PTxData++;			// Load TX buffer
			TXByteCtr--;					// Decrement TX byte counter
			TA1CTL = TIMERSMB_CLK_SRC | TACLR | TIMERSMB_IDIV_BITS | MC__UP | TAIE; // Start delay
		}
		// Nothing more to transmit
		else if (cur_mess->mess_dir == read_mess) {	//change to receive mode
			UCB1CTL1 &= ~UCTR;				// Receiver mode
			UCB1IFG &= ~UCTXIFG;			// Clear USCI_B0 TX int flag
			UCB1CTL1 |= UCTXSTT;			// I2C restart (SMBus protocol)
			tx_done_flag = 1;

			TA1CTL = TIMERSMB_CLK_SRC | TACLR | TIMERSMB_IDIV_BITS | MC__UP | TAIE; // Start delay
		}
		else {								// Transmit only, stop transmission
			//UCB1CTL1 &= ~UCTR;
			UCB1IFG &= ~UCTXIFG;			// Clear USCI_B0 TX int flag
			UCB1CTL1 |= UCTXSTP;			// Generate I2C stop condition

			//cur_state = smbus_done;			// Flag to show read completed
			mess_done_flag = 1;
		}
		break;
	default:
		break;
	}
}

#pragma vector=TIMER1_A1_VECTOR
__interrupt
void TIMER1_A1_ISR(void) {

	switch (__even_in_range(TA1IV, 14)) {
	case 0:
		break;                          // No interrupt
	case 2:
		TA1CTL = MC__STOP;				// Turn off timeout timer
		TA1CCTL1 = 0;					// Disable this compare point
		timer_delay_over = 1;			// Set flag to end delay
		break;							// CCR1 not used
	case 4:
		break;							// CCR2 not used
	case 6:
		break;							// reserved
	case 8:
		break;							// reserved
	case 10:
		break;							// reserved
	case 12:
		break;							// reserved
	case 14:							// overflow
		TA1CTL = MC__STOP;				// Turn off timeout timer
		UCB1CTL1 |= UCTXSTP;			// Send stop on SDA line

		UCB1CTL1 |= UCSWRST;			// Enable SW reset
		UCB1CTL1 &= ~UCSWRST;
		UCB1IE |= UCTXIE + UCRXIE + UCNACKIE;	// Enable TX, RX, NACK interrupt
		error_flag = 2;					// Timeout flag

		break;
	default:
		break;
	}
	//__bic_SR_register_on_exit(LPM3_bits);
}
