/**
 * @email support.ele@noritake.com
 * @website www.noritake-elec.com
 * @version v2.3
 * @ide Code Composer Studio
 * @brief SPI library for MSP430 (MSP-EXP430F5529LP) and Noritake GT-CP modules.
 *
 * @verbatim
 * Provide the SPI interface between MSP430 and Noritake GT-CP modules.
 * Compatible hosts:
 * - MSP430F5529
 * - MSP430FR6989
 * @endverbatim
 *
 *
 * Wiring Reference | MSP430F5529:
 *   CS(5)    -> P2.6
 *   MBUSY(4) -> P4.0
 *   MOSI(3)  -> P3.0
 *   MISO(6)  -> P3.1
 *   SCK(2)   -> P3.2
 *   RESET(1) -> P1.5
 *
 * Wiring Reference | MSP430FR6989:
 *   CS(5)    -> P2.5
 *   MBUSY(4) -> P2.7
 *   MOSI(3)  -> P1.6
 *   MISO(6)  -> P1.7
 *   SCK(2)   -> P1.4
 *   RESET(1) -> P2.2
 */

#include "GTCP_SPI.h"

/**
 * Initializes the SPI interface for MSP430 to communicate with a Noritake
 * GTCP series module.
 *
 * @return none
 */
void initSPI()
{
	WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer

	initRESET();
	configureSPIGPIO();

	resetUSCIStateMachine_SPI();
	configureSPI();
	initUSCIStateMachine_SPI();

}

/**
 * Put USCI in reset state.
 *
 * @return none
 */
void resetUSCIStateMachine_SPI()
{
#ifdef __MSP430F5529__
    UCB0CTL1 |= UCSWRST; // Hold USCI logic in reset state
#elif defined __MSP430FR6989
    UCB0CTLW0 |= UCSWRST; // Enter reset state
#endif
}

/**
 * Release USCI reset state.
 * Can also be seen as USCI initialize.
 *
 * @return none
 */
void initUSCIStateMachine_SPI()
{
#ifdef __MSP430F5529__
    UCB0CTL1 &= ~UCSWRST; //Release USCI logic for use
#elif defined __MSP430FR6989
    UCB0CTLW0 &= ~UCSWRST; // Release USCI logic for use.
#endif
}

void configureSPIGPIO()
{
#ifdef __MSP430F5529__
    P2DIR |= BIT6; //Set P2.6 for CS output
    setCS();
    P4DIR &= ~BIT0; //Set P4.0 for MBUSY input
    P3SEL |= BIT0 + BIT1 + BIT2; // Select P3.0 (MOSI), P3.1(MISO), P3.2(SCLK) for SPI

#elif defined __MSP430FR6989
    P2DIR |= BIT5; // Configure P2.5 for CS output
    setCS();
    P2DIR &= ~BIT7; // Configure P2.7 for MBUSY input

    /**
     * Set SPI pins to UCB0:
     * P1.4 (SCLK)
     * P1.6 (MOSI)
     * P1.7 (MISO)
     */
    P1SEL0 |= BIT4 | BIT6 | BIT7;
    P1SEL1 &= ~(BIT4 | BIT6 | BIT7);

    // Disable the GPIO power-on default high-impedance mode to activate
    // previously configured port settings
    PM5CTL0 &= ~LOCKLPM5;
#endif
}

void configureSPI()
{
#ifdef __MSP430F5529__
    UCB0CTL0 |= UCMST; // Master mode
    UCB0CTL0 |= UCSYNC; // Synchronous mode
    UCB0CTL0 |= UCCKPL; // High clock polarity
    UCB0CTL0 |= UCMSB; // MSB first

    UCB0CTL1 |= UCSSEL_3; // Use the SMCLK

    UCB0BR0 = 0x00; // Bit clock prescaler low byte
    UCB0BR1 = 0x00; // Bit clock prescaler high byte

#elif defined __MSP430FR6989
    // UCMODE kept at 0x00 for 3-pin mode
    // UCSTEM = 0 : CS not automatically toggled.
    UCB0CTLW0 |= UCSWRST; // Enter reset state
    UCB0CTLW0 |= UCSYNC; // Synchronous mode
    UCB0CTLW0 |= UCCKPL; // High clock polarity
    UCB0CTLW0 |= UCMSB; // MSB first
    UCB0CTLW0 |= UCMST; // Master mode
    UCB0CTLW0 |= UCSSEL_2; // use SMCLK in master mode

    UCB0BRW = 4; // Set clock prescaler to 4.
#endif
}

/**
 * Set chip select (CS) HIGH.
 *
 * @return none
 */
void setCS()
{
#ifdef __MSP430F5529__
    P2OUT |= BIT6; // Set CS
#elif defined __MSP430FR6989
    P2OUT |= BIT5; // Set CS
#endif
}

/**
 * Clear chip select (CS).
 *
 * @return none
 */
void clearCS()
{
#ifdef __MSP430F5529__
    P2OUT &= ~BIT6; // Lower CS
#elif defined __MSP430FR6989
    P2OUT &= ~BIT5; // Lower CS
#endif
}

/**
 * Wait until MBUSY is cleared.
 *
 * @return none
 */
void checkMBUSY()
{
#ifdef __MSP430F5529__
    while(P4IN & BIT0);
#elif defined __MSP430FR6989
    while(P2IN & BIT7);
#endif
}

/**
 * Wait until SPI transmit buffer is empty.
 *
 * @return none
 */
void checkTXBufEmpty()
{
    while(!(UCB0IFG & UCTXIFG));
}

/**
 * Wait until SPI receive buffer is empty.
 *
 * @return none
 */
void checkRXBufEmpty()
{
    while(!(UCB0IFG & UCRXIFG));
}

/**
 * Starts SPI write communication.
 *
 * @return none
 */
void beginWriteSPI()
{
	clearCS();
	checkMBUSY();
	putTXBUF(WRITE_IDENTIFIER);
}

/**
 * Put one byte of data into the transmit buffer.
 *
 * @param data Data to put in transmit buffer.
 * @return none
 */
void putTXBUF(uint8_t data)
{
    checkTXBufEmpty();
    UCB0TXBUF = data;
}

/**
 * Read the current receive buffer data byte.
 *
 * @return Receive buffer data byte.
 */
uint8_t readRXBUF()
{
    __delay_cycles(READ_DELAY);
    checkRXBufEmpty();
    __delay_cycles(READ_DELAY);
    return UCB0RXBUF;
}

/**
 * Ends SPI communication
 *
 * @return none
 */
void endSPI()
{
	__delay_cycles(CS_DELAY);
	setCS();
}

/**
 * Writes a character to the destination device.
 * This method must be used after the SPI interface has started.
 *
 * @param  data The character to be transmitted.
 * @return none
 */
void writeSPI(char data)
{
	checkMBUSY();
	putTXBUF(data);
	checkRXBufEmpty();

}

/**
 * Writes a string of characters to the destination device.
 * This function must be used after the SPI interface has started.
 *
 * @param  data The data to be transmitted.
 * @return none
 */
void writeStringSPI(unsigned char* data)
{
	uint8_t pos = 0;

	while(data[pos] != '\0')
	{
		putTXBUF(data[pos]);
		pos++;
	}
}

/**
 * Writes an array of data to the destination device.
 * This function must be used after the SPI interface has started.
 *
 * @param data Data to write.
 * @param size Number of bytes to write.
 * @return none
 */
void writeArraySPI(uint8_t* data, int size)
{
	for(unsigned int i = 0; i < size; i++)
	{
		putTXBUF(data[i]);
	}
}

/**
 * Start the read sequence for SPI.
 *
 * @return none
 */
void startSPIRead()
{
	clearCS();
	checkMBUSY();
	putTXBUF(DATAREAD_IDENTIFIER);
}

/**
 * Read bytesToRead number of bytes and store them
 * in the passed-in pointer location.
 *
 * @param data Receive data array pointer.
 * @return none
 */
void readSPI(unsigned char* data) // break into startRead, Read and endRead
{
	if(bytesToRead <= MAX_BYTES_TO_READ && bytesToRead > 0)
	{
		clearCS();
		checkMBUSY();
		putTXBUF(DATAREAD_IDENTIFIER);
		__delay_cycles(READ_DELAY);
		for(int i = 0; i < bytesToRead + 1; i++)
		{
			putTXBUF(0x00);
			uint8_t rxdata = readRXBUF();

			if((rxdata == 0xFF) && (i == 0))
			{
				i = -1;
			} else if ((rxdata == 0x00) && (i == 0))
			{
				i--;
				bytesToRead--;
			}
			else{
				data[i] = rxdata;
			}
		}
		setCS();
	}
}

/**
 * Reads the status byte from the device.
 *
 * @return The number of bytes to be read from the device.
 */
int readSPIStatus()
{
	int incomingByte = 0;
	bytesToRead = 0;

	checkMBUSY();

	clearCS();
	__delay_cycles(READ_DELAY);
	putTXBUF(STATUSREAD_IDENTIFIER);

	__delay_cycles(READ_DELAY);
	putTXBUF(0x00);

	incomingByte = readRXBUF(); // Read data from receive buffer

	if((incomingByte & 0x40) == 0)
	{
		bytesToRead = incomingByte & 0x3F;
	}
	setCS();

	return bytesToRead;
}
