// ****************************************************************************
// INCLUDE FILES: Header files of modules referenced by this module
// ****************************************************************************
#include "spi.h"
#include "conf_board.h"


// ****************************************************************************
// PRIVATE TYPES: Private types used by this module only
// ****************************************************************************


// ****************************************************************************
// PRIVATE MACROS: Private #define directives used by this module only
// ****************************************************************************


// ****************************************************************************
// PRIVATE VARIABLES: Variables used by this module only (static)
// ****************************************************************************
static uint32_t spiPeripheralClockHz;
static uint32_t spi_delay;
static uint32_t spiActiveChipSelectPin;
static Pio* spiActiveChipSelectPort; 

static const uint32_t spiChipSelectPinAxisBoardMapping[4][7] = 
{   //                 Board                
    //  1      2      3      4      5      6    main
    { CS_A0, CS_A1, CS_A2, CS_A3, CS_A4, CS_A5, 0   }, // A Axis
    { CS_B0, CS_B1, CS_B2, CS_B3, CS_B4, CS_B5, 0   }, // B Axis
    { CS_C0, CS_C1, CS_C2, CS_C3, CS_C4, CS_C5, 0   }, // C Axis
    { 0,     0,     0,     0,     0,     0,     0   }, // main
};

// For main axis/board left PIOC in order to avoid null pointer
// reference, however port selection for main axis/board 
// isn't effective, because it is done by another mechanism.
static Pio * const spiChipSelectPortAxisBoardMapping[4][7] =
{   //                 Board
    //  1    2     3     4     5     6    main
    { PIOC, PIOC, PIOC, PIOC, PIOC, PIOC, PIOC   }, // A Axis
    { PIOC, PIOC, PIOC, PIOC, PIOC, PIOC, PIOC   }, // B Axis
    { PIOC, PIOC, PIOC, PIOC, PIOC, PIOC, PIOC   }, // C Axis
    { PIOC, PIOC, PIOC, PIOC, PIOC, PIOC, PIOC   }, // main
};


// ****************************************************************************
// PRIVATE FUNCTIONS PROTOTYPES: Functions used by this module only (static)
// ****************************************************************************


// ****************************************************************************
// PUBLIC VARIABLES: Public variables shared with other modules
// ****************************************************************************


// ****************************************************************************
// PRIVATE FUNCTIONS: Private functions used by this module only
// ****************************************************************************


// ****************************************************************************
// PUBLIC FUNCTIONS: Public functions shared with other modules
// ****************************************************************************
void SPI_master_init(uint32_t freqHz, spi_pol_e pol, spi_pha_e pha, spi_bits_e bits, uint16_t delay)
{
    uint32_t clockDivider = spiPeripheralClockHz/freqHz;
    
    // Limit the clock divider to allowed values
    if( 0 == clockDivider )
    {
        // Zero is not allowed, divider must be at least 1.
        // Baudrate is limited to peripheral clock at high-end.
        clockDivider = 1;
    }
    else if( clockDivider > 255 )
    {
        // Baudrate is limited to peripheral clock / 255 at low-end.
        clockDivider = 255;
    }

    PMC->PMC_PCER0 = (1<<ID_SPI);
    
    SPI->SPI_MR = SPI_MR_MSTR|SPI_MR_MODFDIS|(0xE<<SPI_MR_PCS_Pos);
    SPI->SPI_CSR[0] = (clockDivider << SPI_CSR_SCBR_Pos) | (bits << SPI_CSR_BITS_Pos) | (pha << 1) | (pol << 0);
    
    spi_delay = delay;
    SPI->SPI_CR = SPI_CR_SPIEN;
}

void SPI_master_deinit(void)
{
    SPI->SPI_CR = SPI_CR_SPIDIS;
    PMC->PMC_PCDR0 = (1<<ID_SPI);
}


//******************************************************************************
//! @fn uint16_t SPI_master_read(void)
//!
//! @description
//!      This function reads from SPI.
//!
//! @param    none
//!
//! @return  Received 8-bit or 16-bits word
//!
//! @calls    none
//!
//! @req
//!
//! @example
//!  Receive 8-bit word
//! @code
//!  uint8_t data = 0;
//!  data = (uint8_t)SPI_master_read(); 
//! @endcode
//******************************************************************************
uint16_t SPI_master_read(void)
{
    while(!(SPI->SPI_SR & SPI_SR_RDRF));
    return SPI->SPI_RDR & SPI_RDR_RD_Msk;
}

//******************************************************************************
//! @fn void SPI_master_write(uint16_t data)
//!
//! @description
//!     This function write data into SPI
//!
//! @param    data      16bits unsigned number with data
//!
//! @return   none
//!
//! @calls    none
//!
//! @req
//!
//! @example
//!  Send 8-bit word 0xAB using SPI master module
//! @code
//!  SPI_master_write(0xAB);
//! @endcode
//******************************************************************************
void SPI_master_write(uint16_t data)
{
    while(!(SPI->SPI_SR & SPI_SR_TDRE));
    SPI->SPI_TDR = data;
    while(!(SPI->SPI_SR & SPI_SR_TXEMPTY));
    if(spi_delay > 0){
        delay_us(spi_delay);
    }
}

//******************************************************************************
//! @fn void SPI_master_reset(void)
//!
//! @description
//!         This function resets SPI
//!
//! @param    none
//!
//! @return   none
//!
//! @calls    none
//!
//! @req
//!
//! @example
//!  Reset master SPI
//! @code
//!  SPI_master_reset()
//! @endcode
//******************************************************************************
void SPI_master_reset(void)
{
    SPI->SPI_CR = SPI_CR_SWRST;
}

//******************************************************************************
//! @fn void SPI_setDec (sensor_select dec_val)
//!
//! @description
//!     This function sets SPI for communication with specific chip select
//!
//! @param    dec_val       Index of selected chip select 
//!                         (chip_EEPROM,
//!                         chip_1  - chip_6)
//!
//! @return   none
//!
//! @calls    none
//!
//! @req
//!
//! @example
//!  Set the chip select of EEPROM
//! @code
//!  SPI_setDec (chip_EEPROM)
//! @endcode
//******************************************************************************
void SPI_setDec (sensor_select_e dec_val)
{
    if(dec_val <= chip_7)
    {
        dec_val &= 0x0007;

        PIOC->PIO_CODR = ~(dec_val << 20) & (7u << 20);
        PIOC->PIO_SODR = (dec_val << 20) & (7u << 20);
    }
    #ifndef MAINBOARD40
    else if(dec_val == chip_imu)
    {
        spiActiveChipSelectPort = PIOA;
        spiActiveChipSelectPin = CS_IMU;
    }
    else if(dec_val == chip_adc)
    {
        spiActiveChipSelectPort = PIOC;
        spiActiveChipSelectPin = CS_ADC;
    }
		#ifdef MAINBOARD54
		else if(dec_val == chip_hum)
		{
			spiActiveChipSelectPort = PIOA;
			spiActiveChipSelectPin = CS_HUM;
		}
		#endif
    #endif
}

void SPI_setCs( void )
{
    // Assert (activate) chip select pre-configured by
    // SPI_ConfigureCs call 
    spiActiveChipSelectPort->PIO_CODR = spiActiveChipSelectPin;
}

void SPI_resetAllCs (void)
{
    #ifdef MAINBOARD40
    PIOC->PIO_SODR = CS_B0|CS_B1|CS_B2|CS_B3|CS_B4|CS_B5|
    CS_A0|CS_A1|CS_A2|CS_A3|CS_A4|CS_A5|
    CS_C0|CS_C1|CS_C2|CS_C3|CS_C4|CS_C5;
    #else
    PIOC->PIO_SODR = CS_B0|CS_B1|CS_B2|CS_B3|CS_B4|CS_B5|
    CS_A0|CS_A1|CS_A2|CS_A3|CS_A4|CS_A5|
    CS_C0|CS_C1|CS_C2|CS_C3|CS_C4|CS_C5|
    CS_ADC;
		#ifdef MAINBOARD54
		PIOA->PIO_SODR = CS_IMU | CS_HUM;
		#else
		PIOA->PIO_SODR = CS_IMU;
		#endif
    #endif
}

void SPI_ConfigureCs(sensor_axis_e axis, sensor_board_e board)
{
	#ifdef MAINBOARD54
	if(axis == axis_A){
		EN_AXIS_A_PORT->PIO_CODR = EN_AXIS_A_PIN;
		EN_AXIS_B_PORT->PIO_SODR = EN_AXIS_B_PIN;
		EN_AXIS_C_PORT->PIO_SODR = EN_AXIS_C_PIN;
	}
	else if(axis == axis_B){
		EN_AXIS_B_PORT->PIO_CODR = EN_AXIS_B_PIN;
		EN_AXIS_A_PORT->PIO_SODR = EN_AXIS_A_PIN;
		EN_AXIS_C_PORT->PIO_SODR = EN_AXIS_C_PIN;
	}
	else if(axis == axis_C){
		EN_AXIS_C_PORT->PIO_CODR = EN_AXIS_C_PIN;
		EN_AXIS_B_PORT->PIO_SODR = EN_AXIS_B_PIN;
		EN_AXIS_A_PORT->PIO_SODR = EN_AXIS_A_PIN;
	}
	else{
		EN_AXIS_A_PORT->PIO_SODR = EN_AXIS_A_PIN;
		EN_AXIS_B_PORT->PIO_SODR = EN_AXIS_B_PIN;
		EN_AXIS_C_PORT->PIO_SODR = EN_AXIS_C_PIN;
	}
	#endif

    // Select chip select to be operated based on the hard-coded
    // axis / board mapping
    spiActiveChipSelectPin = spiChipSelectPinAxisBoardMapping[axis][board];
    spiActiveChipSelectPort = spiChipSelectPortAxisBoardMapping[axis][board];
}

void SPI_UpdatePeriphClk(void)
{
    // Peripheral clock is kept in module's local variable
    // in order to save the time during spi init routine
    // which is called quite often.
    // It is expected that peripheral clock is constant
    // during entire runtime.
    spiPeripheralClockHz = sysclk_get_peripheral_hz();
}
