/** 
 * \file spi.c
 * \brief spi source file
 * \details File contains functions definitions for SPI module
 *
 * \author Juraj Golej
 * \version 1.0
 */

#include "spi.h"

static struct SPI_module* gSPI_registered[SPI_INST_NUM] = { NULL };		///< List of registered modules

/**
 * \brief General SPI interrupt handler function
 * \param[in] perID module ID
 */
static inline void _General_irq(uint8_t perID)
{
	Assert(gSPI_registered[perID]);
	struct SPI_module* module = gSPI_registered[perID];
	/*uint32_t SRreg = */module->periphery->SPI_SR;

	uint16_t readData = (uint16_t)module->periphery->SPI_RDR;
	if(module->read.index < module->read.len) {
		module->read.buffer[module->read.index] = readData;
		++module->read.index;
	}

	if(module->write.index < module->write.len) {
		module->periphery->SPI_TDR = module->write.buffer[module->write.index];
		++module->write.index;
	}
	else if(module->read.index < module->read.len) {
		module->periphery->SPI_TDR = 0;
	}
	else {
		module->periphery->SPI_CR = SPI_CR_SPIDIS_Msk;
	}

	if(module->read.buffer && !(module->periphery->SPI_SR == SPI_SR_SPIENS_Msk) && module->read.callback) {
		module->read.callback(module, module->read.buffer, module->read.index);
		module->read.buffer = NULL;
	}
	if(module->write.buffer && !(module->periphery->SPI_SR == SPI_SR_SPIENS_Msk) && module->write.callback) {
		module->write.callback(module, module->write.buffer, module->write.index);
		module->write.buffer = NULL;
	}
}

/**
 * \brief SPI0 Interrupt handler function
 */
void SPI0_Handler()
{
	_General_irq(0);
}

/**
 * \brief SPI1 Interrupt handler function
 */
void SPI1_Handler()
{
	_General_irq(1);
}

enum retCode SPI_init(struct SPI_module* const module, Spi* periphery)
{
	/* Check NULL pointers */
	Assert(module && periphery);

	/* Get peripheral hardware ID */
	int16_t tmpPerID =  SPI_getPeripheralHWID(periphery);
	Try(tmpPerID < 0, EInvalidParameter);

	/* Register SPI module */
	Spi* tmpInsta[SPI_INST_NUM] = SPI_INSTS;
	for(uint8_t i = 0; i < SPI_INST_NUM; ++i) {
		if(tmpInsta[i] == periphery) {
			if(!gSPI_registered[i]) {
				gSPI_registered[i] = module;
				module->ID = i + 1;
				break;
			}
			else {
				return EDenied;
			}
		}
	}

	/* Init SPI module */
	module->periphery = periphery;
	module->write.buffer = NULL;
	module->write.len = 0;
	module->write.index = 0;
	module->write.callback = NULL;
	module->read.buffer = NULL;
	module->read.len = 0;
	module->read.index = 0;
	module->read.callback = NULL;

	/* Enable peripheral clock and interrupt */
	PMC_peripheral_clock_enable(tmpPerID);
	NVIC_ClearPendingIRQ(tmpPerID);
	NVIC_EnableIRQ(tmpPerID);

	/* Reset SPI periphery to default state */
	periphery->SPI_CR = SPI_CR_SWRST_Msk;

	/* Disable mode fault detection and enable master mode */
	periphery->SPI_MR = SPI_MR_MODFDIS_Msk | SPI_MR_MSTR_Msk;

	/* Enable interrupt when received data */
	periphery->SPI_IER = SPI_IER_RDRF_Msk;

	return ENone;
}

enum retCode SPI_deinit(struct SPI_module* const module)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Reset SPI periphery to default state */
	module->periphery->SPI_CR = SPI_CR_SWRST_Msk;

	/* Disable peripheral clock and interrupt */
	int16_t tmpPerID =  SPI_getPeripheralHWID(module->periphery);
	PMC_peripheral_clock_disable(tmpPerID);
	NVIC_DisableIRQ(tmpPerID);
	NVIC_ClearPendingIRQ(tmpPerID);

	/* Remove module from registered modules */
	gSPI_registered[module->ID - 1] = NULL;
	module->ID = 0;

	return ENone;
}

enum retCode SPI_configureChannel(struct SPI_module* const module, const enum SPI_CHANNEL channel, const uint32_t freq, const enum SPI_MODE mode, const enum SPI_BITS bits)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Calculate SPCK bit rate */
	uint32_t SCBRdata = PMC_get_mck_freq() / freq;
	Try((SCBRdata > 0xFFUL) || (SCBRdata == 0), EInvalidParameter);

	/* Configure */
	module->periphery->SPI_CSR[(uint8_t)channel] = SPI_CSR_SCBR((uint8_t)SCBRdata) | SPI_CSR_BITS((uint8_t)bits) | bits;

	return ENone;
}

enum retCode SPI_setBetweenDelay(struct SPI_module* const module, const uint8_t channel, const uint8_t delay)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Set delay */
	module->periphery->SPI_CSR[(uint8_t)channel] = SPI_CSR_DLYBCT(delay);

	return ENone;
}

enum retCode SPI_setBeforeClockDelay(struct SPI_module* const module, const uint8_t channel, const uint8_t delay)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Set delay */
	module->periphery->SPI_CSR[(uint8_t)channel] = SPI_CSR_DLYBS(delay);

	return ENone;
}

enum retCode SPI_setChipSelectDelay(struct SPI_module* const module, const uint8_t delay)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Set delay */
	module->periphery->SPI_MR = (module->periphery->SPI_MR & ~SPI_MR_DLYBCS_Msk) | SPI_MR_DLYBCS(delay);

	return ENone;
}

enum retCode SPI_setLocalLoopback(struct SPI_module* const module, const bool enable)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Enable/disable local loopback */
	if(enable) {
		module->periphery->SPI_MR |= SPI_MR_LLB_Msk; 
	}
	else {
		module->periphery->SPI_MR &= ~SPI_MR_LLB_Msk; 
	}

	return ENone;
}

enum retCode SPI_setExtendedSlaves(struct SPI_module* const module, const bool enable)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Enable/disable extended slave numbers (0 to 15)*/
	if(enable) {
		module->periphery->SPI_MR |= SPI_MR_PCSDEC_Msk;
	}
	else {
		module->periphery->SPI_MR &= ~SPI_MR_PCSDEC_Msk;
	}

	return ENone;
}

enum retCode SPI_write(struct SPI_module* const module, const uint8_t slaveID, const uint16_t* const buffer, const uint16_t len, spi_tx_cb callback)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized and SPI is disabled */
	Try(!module->ID || (module->periphery->SPI_SR & SPI_SR_SPIENS_Msk), EDenied);

	/* Check, if buffer isn't NULL */
	Try(!buffer, ENullPtr);

	/* Check, if len isn't zero */
	Try(!len, EInvalidParameter);

	/* Set variables */
	module->write.buffer = buffer;
	module->write.len = len;
	module->write.callback = callback;

	/* Set slave ID */
	module->periphery->SPI_MR = (module->periphery->SPI_MR & ~SPI_MR_PCS_Msk) | (SPI_MR_PCS(slaveID));

	/* Enable SPI periphery */
	module->periphery->SPI_CR = SPI_CR_SPIEN_Msk;

	module->periphery->SPI_TDR = buffer[0];
	module->write.index = 1;

	return ENone;
}

enum retCode SPI_call(struct SPI_module* const module, const uint8_t slaveID, uint16_t* const buffer, const uint16_t maxlen, spi_rx_cb callback)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized and SPI is disabled*/
	Try(!module->ID || (module->periphery->SPI_SR & SPI_SR_SPIENS_Msk), EDenied);

	/* Check, if buffer isn't NULL */
	Try(!buffer, ENullPtr);

	/* Set variables */
	module->read.buffer = buffer;
	module->read.len = maxlen;
	module->read.index = 0;
	module->read.callback = callback;

	/* Set slave ID */
	module->periphery->SPI_MR = (module->periphery->SPI_MR & ~SPI_MR_PCS_Msk) | (SPI_MR_PCS(slaveID));

	/* Enable SPI periphery */
	module->periphery->SPI_CR = SPI_CR_SPIEN_Msk;

	module->periphery->SPI_TDR = 0x00;

	return ENone;
}

enum retCode SPI_call_with_write(struct SPI_module* const module, const uint8_t slaveID, const uint16_t* const writeBuffer, const uint16_t writeLen, spi_tx_cb writeCb, uint16_t* const readBuffer, const uint16_t readMaxlen, spi_rx_cb readCb)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized and SPI is disabled */
	Try(!module->ID || (module->periphery->SPI_SR & SPI_SR_SPIENS_Msk), EDenied);

	/* Check, if buffers aren't NULL */
	Try(!writeBuffer || !readBuffer, ENullPtr);

	/* Set variables */
	module->write.buffer = writeBuffer;
	module->write.len = writeLen;
	module->write.callback = writeCb;
	module->read.buffer = readBuffer;
	module->read.len = readMaxlen;
	module->read.index = 0;
	module->read.callback = readCb;

	/* Set slave ID */
	module->periphery->SPI_MR = (module->periphery->SPI_MR & ~SPI_MR_PCS_Msk) | (SPI_MR_PCS(slaveID));

	/* Enable SPI periphery */
	module->periphery->SPI_CR = SPI_CR_SPIEN_Msk;

	module->periphery->SPI_TDR = writeBuffer[0];
	module->write.index = 1;

	return ENone;
}
