//----------------------------------------------------
/**
	\file		atsam4e16e/inc/spi.h
	\ingroup	platform
	\ingroup	cortex-m4

	\brief		Knihovna pro prenosy pres SPI
				<<< CORTEX M4 >>>

	\author		ConTeK s.r.o., Jan Prasil
	\version	01.01
	\date		26.02.2014
*/
//----------------------------------------------------
#ifndef _SPI_H_
#define _SPI_H_		//!< Definice pritomnosti knihovny

#include <libsets.h>
#include <cortex-m4/inc/ctktypes.h>
#include <sam4e.h>
#include <sync.h>

//----------------------------------------------------

#ifdef SPI_ENABLE_0
extern const uint32_t CS[];
#endif

//----------------------------------------------------
//!	\name	Konstanty definujici status transakci
//@{

#define SPS_SUCCESS			0	//!< Transakce byla úspěšně dokončena
#define SPS_PENDING			1	//!< Transakce probíhá
#define SPS_BUS_ERROR		2	//!< Chyba sběrnice
#define SPS_ERROR_MODF		3	//!< Chyba
#define SPS_ERROR_OVRES		4	//!< Preteceni vnitrnich bufferu SPI - nejspise zpusobene pomalym odebiranim dat
//@}

#define SPI_MAX_CHANNELS	4	//!< Pocet kanalu SPI
#define SPI_COUNT			1	//!< Pocet SPI jednotek na cipu


//----------------------------------------------------
//!	Typ délky prenasenych dat

typedef uint16_t SPISIZE;


//----------------------------------------------------
//!	Hodnota vracena getSPIChannelconfig pokud se SPI nema konfigurovat

#define CSR_FALSE	0x04


//----------------------------------------------------
//!	Typ pro urceni zapojeni SPI (3V = push-pull, 5V = otevreny kolektor)

typedef enum _SPI_VOLTAGE
{
	SPI_3V,
	SPI_5V,
	SPI_VOLT_NO_CHANGE
} SPI_VOLTAGE;


//----------------------------------------------------
/**
	\brief	Callback typ volany po dokonceni transakce

	\param	status		Vysledek transakce (viz SPS_*)
	\param	userData	Uzivatelska data
*/
//----------------------------------------------------

typedef void (* SPI_CALLBACK) (const uint8_t _status, void * const _userData);


//----------------------------------------------------
//!	Struktura kanalu SPI

typedef struct
{
	volatile uint8_t Status;			//!< Status kanalu SPI[]
	void *			TxA_Data;			//!< Vysilana data A
	SPISIZE			TxA_Size;			//!< Delka vysilanych dat A
	void *			TxB_Data;			//!< Vysilana data B
	SPISIZE			TxB_Size;			//!< Delka vysilanych dat B
	void *			TxC_Data;			//!< Vysilana data C
	void *			RxC_Data;			//!< Vysilana data C
	SPISIZE			TRxC_Size;			//!< Delka vysilanych a prijimanych dat c
	void *			RxD_Data;			//!< Prijimana data D
	SPISIZE			RxD_Size;			//!< Delka prijimanych dat D
	SPI_CALLBACK	CallBack;			//!< Callback volany pri dokonceni SPI transakce
	void *			CbData;				//!< Uzivatelska data, ktera budou callbacku predana
} SPICHANNEL;


//----------------------------------------------------
//!	Struktura kontextu automatu

typedef struct _SPIDESC
{
	volatile bool_t		flRunning;		//!< Je inicializovano, po ininializaci na TRUE, pred ukoncenim FALSE
	volatile bool_t		flLocking;		//!< Priznak zda transakce bude provadena se zakazanym prerusenim
	volatile bool_t		flTick;			//!< Priznak zda se ma vynutit Tick
	volatile bool_t		Busy;			//!< Priznak probihajici transakce
	volatile uint8_t	Channel;		//!< Cislo aktualne obsluhovaneho kanalu
	SPI_VOLTAGE			Voltage;
	bool_t				CSUsed[SPI_MAX_CHANNELS];		//!< Povoleni jednotlivych CS
	SPICHANNEL 			Channels[SPI_MAX_CHANNELS];
	Spi *				pSPI;
} SPIDESC;

//----------------------------------------------------
/**
	\brief	Funkce pro provedení transakce na SPI

	\param	_spi				struktura kontextu automatu
	\param	_channel			Číslo komunikačního kanálu (0 .. SPI_MAX_CHANNELS - 1)
	\param	_txA_data			Ukazatel na bufferA pro vysilani
	\param	_txA_size			Délka vysilanych datA
	\param	_txB_data			Ukazatel na bufferB pro vysilani
	\param	_txB_size			Délka vysilanych datB
	\param	_txC_data			Ukazatel na bufferC pro vysilani
	\param	_rxC_data			Ukazatel na bufferC pro prijem
	\param	_trxC_size			Délka vysilanych a prijimanych datC
	\param	_rxD_data			Ukazatel na bufferD pro příjem
	\param	_rxD_size			Délka ctenych datD
	\return	TRUE				Transakce probehla vporadku
			FALSE				Transakce neprobehla, nebo skoncila chybou
*/
//----------------------------------------------------
inline bool_t SpiShortTransact(SPIDESC * const _spi, const uint8_t _channel,
		 void * const _txA_data, SPISIZE _txA_size, void * const _txB_data, SPISIZE _txB_size, void * const _txC_data, void * const _rxC_data, SPISIZE _trxC_size,
		 void * const _rxD_data, SPISIZE _rxD_size)
{
	Spi *pSPI = _spi->pSPI;
	uint8_t * tx_buffer = (uint8_t *)_txC_data;
	uint8_t * rx_buffer = (uint8_t *)_rxC_data;
	uint32_t __attribute__((unused))tmp;

	// delka cele transakce - vyuziva se ke spravnemu ukonceni transakce bitem LASTXFER
	SPISIZE transactSize = _txA_size + _txB_size + _trxC_size + _rxD_size;

	// TX transakce
	while (!(pSPI->SPI_SR & SPI_SR_TXEMPTY))					// cekej na dokonceni Tx
		;
	// vycteni nepouzitych dat
	tmp = pSPI->SPI_RDR;

	if (_spi->Channel != _channel)
	{
		_spi->Channel = _channel;
	}

	tx_buffer = (uint8_t *)_txA_data;
	while(_txA_size--)
	{
		transactSize--;
		if(transactSize == 0)
		{
			// vysilej buffer A s priznakem ukonceni transakce
			pSPI->SPI_TDR = SPI_TDR_LASTXFER | SPI_TDR_PCS(0x07 >> (3 - _channel)) | *(tx_buffer++);
		}
		else
		{
			// vysilej buffer A
			pSPI->SPI_TDR = SPI_TDR_PCS(0x07 >> (3 - _channel)) | *(tx_buffer++);
		}
		//cekani na odeslani TDR do shift registru
		while (!(pSPI->SPI_SR & SPI_SR_TDRE))
			;
	}

	tx_buffer = (uint8_t *)_txB_data;
	while(_txB_size--)
	{
		transactSize--;
		if(transactSize == 0)
		{
			// vysilej buffer B s priznakem ukonceni transakce
			pSPI->SPI_TDR = SPI_TDR_LASTXFER | SPI_TDR_PCS(0x07 >> (3 - _channel)) | *(tx_buffer++);
		}
		else
		{
			// vysilej buffer B
			pSPI->SPI_TDR = SPI_TDR_PCS(0x07 >> (3 - _channel)) | *(tx_buffer++);
		}
		//cekani na odeslani TDR do shift registru
		while (!(pSPI->SPI_SR & SPI_SR_TDRE))
			;
	}

	if(_trxC_size)
	{
		tx_buffer = (uint8_t *)_txC_data;
		rx_buffer = (uint8_t *)_rxC_data;

		// TX transakce
		while (!(pSPI->SPI_SR & SPI_SR_TXEMPTY))				// cekej na dokonceni Tx
			;
		// vycteni nepouzitych dat
		tmp = pSPI->SPI_RDR;

		while(_trxC_size--)
		{
			transactSize--;
			if(transactSize == 0)
			{
				// vysilej buffer C s priznakem ukonceni transakce
				pSPI->SPI_TDR = SPI_TDR_LASTXFER | SPI_TDR_PCS(0x07 >> (3 - _channel)) | *(tx_buffer++);
			}
			else
			{
				// vysilej buffer C
				pSPI->SPI_TDR = SPI_TDR_PCS(0x07 >> (3 - _channel)) | *(tx_buffer++);
			}
			// cekani na data
			while (!(pSPI->SPI_SR & SPI_SR_RDRF))
				;
			//ulozeni byte
			*(rx_buffer++) = (uint8_t)(pSPI->SPI_RDR & 0xFF);	// prijmi do bufferu C
		}
	}

	if(_rxD_size)
	{
		rx_buffer = (uint8_t *)_rxD_data;						// prijem, v shift registru posledni vysilani - ignorovat tento prvni prijem

		// TX transakce
		while (!(pSPI->SPI_SR & SPI_SR_TXEMPTY))				// cekej na dokonceni Tx
			;
		// vycteni nepouzitych dat
		tmp = pSPI->SPI_RDR;

		while(_rxD_size--)
		{
			transactSize--;
			if(transactSize == 0)
			{
				//generovani hodin pro prijem s priznakem ukonceni transakce
				pSPI->SPI_TDR = SPI_TDR_LASTXFER | SPI_TDR_PCS(0x07 >> (3 - _channel)) | (uint8_t)0xFF;
			}
			else
			{
				//generovani hodin pro prijem
				pSPI->SPI_TDR = SPI_TDR_PCS(0x07 >> (3 - _channel)) | (uint8_t)0xFF;
			}
			// cekani na data
			while (!(pSPI->SPI_SR & SPI_SR_RDRF))
				;
			//ulozeni byte
			*(rx_buffer++) = (uint8_t)(pSPI->SPI_RDR & 0xFF);	// prijmi do bufferu D
		}
	}


	return TRUE;
}

//----------------------------------------------------
/**
	\brief	Funkce pro provedení synchronizovane transakce na SPI

	\param	_spi				struktura kontextu automatu
	\param	_channel			Číslo komunikačního kanálu (0 .. SPI_MAX_CHANNELS - 1)
	\param	_tx1_data			Ukazatel na buffer1 pro vysilani
	\param	_tx1_size			Délka vysilanych dat1
	\param	_tx2_data			Ukazatel na buffer2 pro vysilani
	\param	_tx2_size			Délka vysilanych dat2
	\param	_tx3_data			Ukazatel na buffer3 pro vysilani
	\param	_rx3_data			Ukazatel na buffer3 pro prijem
	\param	_trx3_size			Délka vysilanych a prijimanych dat
	\param	_rx_data			Ukazatel na buffer pro příjem
	\param	_rx_size			Délka ctenych dat
	\return	TRUE				Transakce probehla vporadku
			FALSE				Transakce neprobehla, nebo skoncila chybou
*/
//----------------------------------------------------

inline bool_t SpiSyncShortTransact(SPIDESC * const _spi, const uint8_t _channel,
		 void * const _txA_data, SPISIZE _txA_size, void * const _txB_data, SPISIZE _txB_size, void * const _txC_data, void * const _rxC_data, SPISIZE _txC_size,
		 void * const _rxD_data, SPISIZE _rxD_size)
{
	flock_t lock = FastLock();
	if (_spi->Busy)
	{
		// Spi je prave vyuzivana nekym jinym
		FastUnlock(lock);
		return FALSE;	// transakce nemohla byt vykonana, navracime FALSE
	}

	_spi->Busy = TRUE;

	if (!_spi->flLocking)
	{
		FastUnlock(lock);
	}

	bool_t retval = SpiShortTransact(_spi, _channel, _txA_data, _txA_size, _txB_data, _txB_size, _txC_data, _rxC_data, _txC_size, _rxD_data, _rxD_size);

	AtomWrite(_spi->Busy, FALSE);

	if (_spi->flLocking)
	{
		FastUnlock(lock);
	}

	return retval;
}

//----------------------------------------------------
/**
	\brief	Spi tick

	\param	_spi				Struktura kontextu automatu
	\return	TRUE				Zadna transakce neni uz pozadovana
			FALSE				Transakce neprobehla, nebo skoncila chybou
*/
//----------------------------------------------------

inline bool_t SpiTick(SPIDESC * const _spi)
{
	bool_t flEmpty = TRUE;
	// ma se tick provest
	if (_spi->flTick)
	{
		bool_t flRep;
		_spi->flTick = FALSE;
		do
		{
			flEmpty = TRUE;
			flRep = FALSE;

			uint8_t i;
			uint8_t chni = 0;
			SPICHANNEL * chn = NULL;

			// zjistime, zda ve fronte neceka pozadavek
			flock_t lock = FastLock();

			for (i = _spi->Channel + 1; i < SPI_MAX_CHANNELS; i++)
			{
				if (_spi->Channels[i].Status == SPS_PENDING)
				{
					chni = i;
					chn = &_spi->Channels[i];
					break;
				}
			}

			if (!chn)
			{
				for (i = 0; i <= _spi->Channel; i++)
				{
					if (_spi->Channels[i].Status == SPS_PENDING)
					{
						chni = i;
						chn = &_spi->Channels[i];
						break;
					}
				}
			}

			if (chn)
			{
				// zpracujeme pozadavek
				if (SpiSyncShortTransact(_spi, chni,
						chn->TxA_Data, chn->TxA_Size, chn->TxB_Data, chn->TxB_Size, chn->TxC_Data, chn->RxC_Data, chn->TRxC_Size,
						chn->RxD_Data, chn->RxD_Size))
				{
					// pozadavek byl zpracovan
					SPI_CALLBACK callBack = chn->CallBack;
					void * cbData = chn->CbData;
					// uvolnime buffer
					_spi->Channels[chni].Status = SPS_SUCCESS;

					FastUnlock(lock);

					if (callBack)
					{
						callBack(SPS_SUCCESS, cbData);
					}

					flRep = TRUE;
				}
				else
				{
					FastUnlock(lock);
					flEmpty = FALSE;
				}
			}
			else
			{
				FastUnlock(lock);
			}
		} while (flRep);
	}

	return flEmpty;
}

//----------------------------------------------------
/**
	\brief	Funkce pro provedení synchronizovane transakce na SPI

	\param	_spi				struktura kontextu automatu
	\param	_channel			Číslo komunikačního kanálu (0 .. SPI_MAX_CHANNELS - 1)
	\param	_txA_data			Ukazatel na bufferA pro vysilani
	\param	_txA_size			Délka vysilanych datA
	\param	_txB_data			Ukazatel na bufferB pro vysilani
	\param	_txB_size			Délka vysilanych datB
	\param	_txC_data			Ukazatel na bufferC pro vysilani
	\param	_rxC_data			Ukazatel na bufferC pro prijem
	\param	_trxC_size			Délka vysilanych a prijimanych datC
	\param	_rxD_data			Ukazatel na bufferD pro příjem
	\param	_rxD_size			Délka ctenych datD
	\param	_callBack			Callback volany po dokonceni transakce
	\param	_cbData				Data predana callbacku pri jeho volani
	\return	TRUE				Transakce probehla vporadku
			FALSE				Transakce neprobehla, nebo skoncila chybou
*/
//----------------------------------------------------

bool_t SpiTransact(SPIDESC * const _spi, const uint8_t _channel,
		 void * const _txA_data, SPISIZE _txA_size, void * const _txB_data, SPISIZE _txB_size, void * const _txC_data, void * const _rxC_data, SPISIZE _trxC_size,
		 void * const _rxD_data, SPISIZE _rxD_size,
		 SPI_CALLBACK _callBack, void * const _cbData);


//--------------------------------------------------------------------
/**
	\brief		zadane periferii SPI nastavi rezim dle vystupniho napeti

	\param	_spi		struktura kontextu automatu
	\param	_voltage	napeti

	\note	nastevni se tyka pinu hodin, dat a vsech pouzitych CS.
*/
//--------------------------------------------------------------------
void SpiSetVoltage(SPIDESC * _spi, SPI_VOLTAGE _voltage);

void SpiSetChannelConfig(SPIDESC * _spi, uint8_t _channel, uint32_t _config, SPI_VOLTAGE _voltage);

//----------------------------------------------------
/**
	\brief	Inicializace SPI

	\return	TRUE, pokud se inicializace zdarila
*/
//----------------------------------------------------

bool_t SpiInitialize(SPIDESC * const _spi, const SPI_VOLTAGE _voltage);

//----------------------------------------------------
/**
	\brief	Finalizace SPI

	\return	TRUE, pokud se finalizace zdarila
*/
//----------------------------------------------------

bool_t SpiFinalize(SPIDESC * const _spi);

#endif	//	_SPI_H_
