/** 
 * \file spi.h
 * \brief spi header file
 * \details File contains structures, enumerations and functions declarations for SPI module
 *
 * \author Juraj Golej
 * \version 1.0
 */


#ifndef SPI_H_
#define SPI_H_

#include "sam.h"
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <clocks/clocks/clocks.h>
#include <utils/retcode/retcode.h>
#include <utils/assert/arm_assert.h>

/** 
 * \brief SPI mode (shift, capture, inactive level of SPCK) enumeration
 */
enum SPI_MODE {
	SPI_MODE_0 = 0x02UL,	///< Shift: falling SPCK Edge, Capture: rising SPCK Edge, SPCK Inactive in low level
	SPI_MODE_1 = 0x00UL,	///< Shift: rising SPCK Edge, Capture: falling SPCK Edge, SPCK Inactive in low level
	SPI_MODE_2 = 0x03UL,	///< Shift: rising SPCK Edge, Capture: falling SPCK Edge, SPCK Inactive in high level
	SPI_MODE_3 = 0x01UL,	///< Shift: falling SPCK Edge, Capture: rising SPCK Edge, SPCK Inactive in high level
}; /* enum SPI_MODE */

/** 
 * \brief Data length enumeration
 */
enum SPI_BITS {
	SPI_BITS_8,		///< 8 bits (one byte)
	SPI_BITS_9,		///< 9 bits
	SPI_BITS_10,	///< 10 bits
	SPI_BITS_11,	///< 11 bits
	SPI_BITS_12,	///< 12 bits
	SPI_BITS_13,	///< 13 bits
	SPI_BITS_14,	///< 14 bits
	SPI_BITS_15,	///< 15 bits
	SPI_BITS_16,	///< 16 bits (two bytes)
}; /* enum SPI_BITS */

/** 
 * \brief SPI slave channels enumeration
 */
enum SPI_CHANNEL {
	SPI_CHANNEL_0,	///< Defines peripheral chip select signals 0 to 3 (when Extended Slaves are used)
	SPI_CHANNEL_1,	///< Defines peripheral chip select signals 4 to 7 (when Extended Slaves are used)
	SPI_CHANNEL_2,	///< Defines peripheral chip select signals 8 to 11 (when Extended Slaves are used)
	SPI_CHANNEL_3,	///< Defines peripheral chip select signals 12 to 14 (when Extended Slaves are used)
}; /* enum SPI_CHANNEL */

struct SPI_module;

/**
 * \brief SPI transfer finish user defined function (callback)
 * 
 * \param[in] module Pointer on SPI module
 * \param[in] buffer Pointer on buffer, where are original transfered data
 * \param[in] len Number of original transfered data
 */
typedef void (*spi_tx_cb)(struct SPI_module* const module, volatile const uint16_t* buffer, const uint16_t len);

/**
 * \brief SPI receive finish user defined function (callback)
 * 
 * \param[in] module Pointer on SPI module
 * \param[in] buffer Pointer on buffer, where are received data
 * \param[in] len Number of received data
 */
typedef void (*spi_rx_cb)(struct SPI_module* const module, volatile uint16_t* buffer, const uint16_t len);

/** 
 * \brief SPI module structure
 */
struct SPI_module {
	uint8_t ID;						///< Register status (if >0, module is initialized)
	Spi* periphery;					///< Pointer on SPI periphery
	
	struct write {
		volatile const uint16_t* buffer;	///< Pointer on data array for transfer
		uint16_t len;				///< Number of bytes to be transferred
		volatile uint16_t index;	///< Actual index in write buffer array
		spi_tx_cb callback;			///< Pointer on transfer finish callback function
	} write;
	struct read {
		volatile uint16_t* buffer;	///< Pointer on data array for receive
		uint16_t len;				///< Maximum number of receive data (buffer size)
		volatile uint16_t index;	///< Actual index in read buffer array
		spi_rx_cb callback;			///< Pointer on receive finish callback function
	} read;
}; /* struct SPI_module */

/**
 * \brief Get SPI peripheral hardware ID
 * 
 * \param[in] periphery Pointer on SPI periphery
 * 
 * \return \b int16_t Peripheral ID or -1, if can't find this \c periphery
 */
static inline int16_t SPI_getPeripheralHWID(Spi* periphery)
{
	switch((uint32_t)periphery) {
		case (uint32_t)SPI0:
			return ID_SPI0;
		case (uint32_t)SPI1:
			return ID_SPI1;
		default:
			return -1;
	}
}

/**
 * \brief Initialize SPI module
 * 
 * \param[in] module Pointer on SPI module
 * \param[in] periphery Pointer on SPI periphery
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Periphery is already in use
 * \retval EInvalidParameter Invalid \c periphery parameter
 *
 * \note Call this function before anything else
 */
enum retCode SPI_init(struct SPI_module* const module, Spi* periphery);

/**
 * \brief Deinitialize SPI module
 * 
 * \param[in] module Pointer on SPI module
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 */
enum retCode SPI_deinit(struct SPI_module* const module);

/**
 * \brief Configure one SPI slave channel
 * 
 * \param[in] module Pointer on SPI module
 * \param[in] channel Channel ID
 * \param[in] freq SPCK frequency
 * \param[in] mode SPI mode
 * \param[in] bits Number of bits in one data packet
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 * \retval EInvalidParameter Can't create this SPCK frequency (change \c freq parameter or peripheral clock)
 */
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);

/**
 * \brief Set the delay between two consecutive transfers with the same peripheral without removing the chip select
 * 
 * \param[in] module Pointer on SPI module
 * \param[in] channel Channel ID
 * \param[in] delay Delay
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 *
 * \note tDELAY = 32 * \c delay / fPER
 * \note When \c delay = 0, no delay between consecutive transfers is inserted and the clock keeps its duty cycle over the character transfers.
 */
enum retCode SPI_setBetweenDelay(struct SPI_module* const module, const uint8_t channel, const uint8_t delay);

/**
 * \brief Set the delay from NPCS falling edge (activation) to the first valid SPCK transition
 * 
 * \param[in] module Pointer on SPI module
 * \param[in] channel Channel ID
 * \param[in] delay Delay
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 *
 * \note tDELAY = \c delay / fPER
 * \note When \c delay = 0, the delay is half the SPCK clock period.
 */
enum retCode SPI_setBeforeClockDelay(struct SPI_module* const module, const uint8_t channel, const uint8_t delay);

/**
 * \brief Set the delay between the inactivation and the activation of NPCS
 * 
 * \param[in] module Pointer on SPI module
 * \param[in] delay Delay
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 *
 * \note tDELAY = \c delay / fPER
 * \note When \c delay = 0, the delay is half the SPCK clock period.
 */
enum retCode SPI_setChipSelectDelay(struct SPI_module* const module, const uint8_t delay);

/**
 * \brief Enable/disable local loopback (MISO is directly connected to MOSI)
 * 
 * \param[in] module Pointer on SPI module
 * \param[in] enable \c true for enable, \c false for disable
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 */
enum retCode SPI_setLocalLoopback(struct SPI_module* const module, const bool enable);

/**
 * \brief Enable/disable extended slaves mode (NPCS pins must be connected to external demultiplexor 4 to 16)
 * 
 * \param[in] module Pointer on SPI module
 * \param[in] enable \c true for enable, \c false for disable
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 */
enum retCode SPI_setExtendedSlaves(struct SPI_module* const module, const bool enable);

/**
 * \brief Send data throught SPI
 * 
 * \param[in] module Pointer on SPI module
 * \param[in] slaveID Slave number (0 to 3 (normal slave mode) or 0 to 14 (extended slave mode)
 * \param[in] buffer Pointer on data buffer for transfer
 * \param[in] len Number of packets in data buffer
 * \param[in] callback Pointer on user defined function (called when transfer finish)
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized or is busy
 * \retval ENullPtr \c buffer is NULL
 * \retval EInvalidParameter \c len is zero
 */
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);

/**
 * \brief Call to slave device, if it have some data for transfer
 * 
 * Master (this) start sending dummy data (zero). Received data are pushing to \c buffer. 
 * Receive is finished when \c buffer is full.
 *
 * \param[in] module Pointer on SPI module
 * \param[in] slaveID Slave number (0 to 3 (normal slave mode) or 0 to 14 (extended slave mode)
 * \param[out] buffer Pointer on data buffer for receive
 * \param[in] maxlen Maximum number of received packets
 * \param[in] callback Pointer on user defined function (called when receive finish)
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized or is busy
 * \retval ENullPtr \c buffer is NULL
 */
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);

/**
 * \brief Combine receive and call (data are transferred and received at the same time
 *
 * \param[in] module Pointer on SPI module
 * \param[in] slaveID Slave number (0 to 3 (normal slave mode) or 0 to 14 (extended slave mode)
 * \param[in] writeBuffer Pointer on data buffer for transfer
 * \param[in] writeLen Number of packets in write data buffer
 * \param[in] writeCb Pointer on user defined function (called when transfer finish)
 * \param[out] readBuffer Pointer on data buffer for receive
 * \param[in] readMaxlen Maximum number of received packets
 * \param[in] callback Pointer on user defined function (called when receive finish)
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized or is busy
 * \retval ENullPtr \c writeBuffer or \c readBuffer are NULL
 *
 * \see SPI_write
 * \see SPI_call
 */
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);

/**
 * \brief Get busy status
 * 
 * \param[in] module Pointer on SPI module
 * 
 * \return \b bool Busy status
 * \retval true SPI is busy
 * \retval false SPI isn't busy (you can transfer/receive new data)
 */
static inline bool SPI_isBusy(struct SPI_module* const module)
{
	Assert(module);
	return(module->periphery->SPI_SR & SPI_SR_SPIENS_Msk);
}

#endif /* SPI_H_ */