//-----------------------------------------------------------------------------
///
/// \brief  SPI
///
///         Blah blah blub
///
/// \date   20150121
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//-----------------------------------------------------------------------------


//---Includes------------------------------------------------------------------


//---Own------------------------------

#include <biwak/biwak.h>         // exception
#include <biwak/spi.hpp>
#include <biwak/stm32/spi.hpp>
#include <biwak/gpio.hpp>
#include <biwak/rcc.h>           // rccDacClockEnable
#include <HALWrapper/stm32_spi.h>
#include <biwak/nvic.h>          // ePeripheral
#include <string.h>              // memset()
#include <biwak/handler.h>       // IRQ_Funcion_imp
#include <biwak/exception.h>
#include <arpa/inet.h>
#include <stdlib.h>              // malloc


#define SPI_TIMEOUT     10
// #define SPI_TIMEOUT     1000000
#define SPI_CHECK_STATUS
//#define SPI_CHECK_STATUS (void)m_status;
//#define SPI_CHECK_STATUS if(status!=HAL_OK)error();


//---Implementation------------------------------------------------------------


constexpr SPI_InitTypeDef spiDefaultInit
{
   .Mode = SPI_MODE_MASTER,
   #if defined( BIWAK_SPI_HALF_DUPLEX )
   .Direction = SPI_DIRECTION_1LINE,
   #else
   .Direction = SPI_DIRECTION_2LINES,
   #endif
   .DataSize = SPI_DATASIZE_8BIT,
   .CLKPolarity = SPI_POLARITY_LOW,
   .CLKPhase = SPI_PHASE_1EDGE /* SPI_CR1_CPHA */,
   //m_pHandler->Init.CLKPhase = SPI_CR1_CPHA;

   #if defined BIWAK_USE_SPI_NSS
   .NSS = SPI_NSS_HARD_OUTPUT,
   .NSSPMode = SPI_NSS_PULSE_DISABLE,
   #else
   .NSS = SPI_NSS_SOFT,
   #endif

   //m_pHandler->Init.CLKPolarity = SPI_POLARITY_HIGH;
   .FirstBit = SPI_FIRSTBIT_MSB,
   .TIMode = SPI_TIMODE_DISABLE,
   .CRCCalculation = SPI_CRCCALCULATION_DISABLE,
   .CRCPolynomial = 7,
   #if defined ( SPI_CRC_LENGTH_8BIT )
   .CRCLength = SPI_CRC_LENGTH_8BIT,
   #endif
   #if defined ( SPI_NSS_PULSE_DISABLE )
  .NSSPMode = SPI_NSS_PULSE_DISABLE,
   #endif
};


CSpi::CSpi( const SSpiConfig &_configData, ESpiMode eSpiMode)
   :configData(_configData)
{
   #if defined ( BIWAK_AUTO_GPIO_INIT )
   CGpio::init( configData.eGpioPeripheralSck, configData.eGpioPinSck, gpioModeSpi, configData.altFunc );
   CGpio::init( configData.eGpioPeripheralMosi, configData.eGpioPinMosi, gpioModeSpi, configData.altFunc );
   CGpio::init( configData.eGpioPeripheralMiso, configData.eGpioPinMiso, gpioModeSpi, configData.altFunc );
   #endif

   #if defined BIWAK_USE_SPI_NSS
   CGpio::init( configData.eGpioPeripheralNss, configData.eGpioPinNss, gpioModeSpi, configData.altFunc );
   #endif
   
   #if 1
      m_pHandler=
            new SPI_HandleTypeDef{
         .Instance=configData.instance,
         .Init{ spiDefaultInit },
      };
   #else
      m_pHandler=(SPI_HandleTypeDef*)malloc( sizeof( SPI_HandleTypeDef ) );
      m_pHandler->Instance=configData.instance;
      memcpy( &m_pHandler->Init, &spiDefaultInit, sizeof(spiDefaultInit) );
   #endif
   
   // Prescaler: the smaller the faster; 16 -> 3Mhz, 8 -> 6Mhz, 4 -> 12Mhz
   // 32 -> 1Mhz?; 64: 500Khz
   // All values verified on stm32f303
   setMode(eSpiMode);
}



void CSpi::setMode(ESpiMode eSpiMode)
{
   m_pHandler->Init.BaudRatePrescaler = eSpiMode;
   init();

   return;
}

ESpiMode CSpi::getMode()
{
   return ( (ESpiMode)m_pHandler->Init.BaudRatePrescaler );
}

bool CSpi::isSlowerOrEqual( ESpiMode mode )
{
   static_assert( eSpiMode187Khz > eSpiMode375Khz, "X" );
   static_assert( eSpiMode375Khz > eSpiMode750Khz, "X" );
   static_assert( eSpiMode750Khz > eSpiMode1500Khz, "X" );
   static_assert( eSpiMode1500Khz > eSpiMode3Mhz, "X" );
   static_assert( eSpiMode3Mhz > eSpiMode6Mhz, "X" );
   static_assert( eSpiMode6Mhz > eSpiMode12Mhz, "X" );
   static_assert( eSpiMode12Mhz > eSpiMode24Mhz, "X" );
   
   return( getMode() >= mode );
}

CSpi::~CSpi()
{
   HAL_SPI_DeInit(m_pHandler);
   setNVInterrupt(configData.irqn, false, NVI_DEFAULT_PRIORITY);
}

void CSpi::setDataSize(int size)
{
   uint32_t dataSize;

   // #if ( __XBYTE_ORDER == __XBIG_ENDIAN )
   #if ! defined(__BYTE_ORDER__)
      #error Unknown endianess
   #else
      #if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
         #error Only little endian machines are supported yet MCU_REV
      #endif
   #endif

   #if defined ( STM32F1 ) || defined ( STM32L0 ) || defined ( STM32L1 )
      switch(size)
      {
         case 8:
            dataSize = SPI_DATASIZE_8BIT;
            m_bytesPerData = 1;
            break;
         case 16:
            dataSize = SPI_DATASIZE_16BIT;
            m_bytesPerData = 2;
            break;
         default:
            // unknown spi data size
            exception();
            break;
      }

   #else
      // e.g. SPI_DATASIZE_8BIT is 0x00000700U
      static_assert( (16-1) << 8 == SPI_DATASIZE_16BIT );
      static_assert( (9-1) << 8 == SPI_DATASIZE_9BIT );
      dataSize = (size-1) << 8;
      m_bytesPerData = ( size > 8 ) ? 2 : 1;
   #endif

   if( m_pHandler->Init.DataSize == dataSize )
   {
      return;
   }
   
   m_pHandler->Init.DataSize = dataSize;

   // Only tested with STM32F3. We must not call init() or HAL_SPI_Init().
   // Otherwise CLK/Mosi/Miso will flicker
   uint32_t frxth=0;

   #if ( ! defined(STM32F4) ) && ( ! defined(STM32F1) ) \
            && ( ! defined(STM32L1) ) && ( ! defined(STM32L0) )
      if(m_pHandler->Init.DataSize > SPI_DATASIZE_8BIT)
      {
         frxth = SPI_RXFIFO_THRESHOLD_HF;
      }
      else
      {
         frxth = SPI_RXFIFO_THRESHOLD_QF;
      }
   #endif

   m_pHandler->Instance->CR2 = (((m_pHandler->Init.NSS >> 16) & SPI_CR2_SSOE) |
                          m_pHandler->Init.TIMode |
                          #if ( ! defined(STM32F4) ) && ( ! defined(STM32F1) ) \
                                && ( ! defined(STM32L1) ) && ( ! defined(STM32L0) )
                            m_pHandler->Init.NSSPMode |
                          #endif
                          m_pHandler->Init.DataSize ) | frxth;
}


void CSpi::init()
{
   #if defined ( BIWAK_CONFIG_AUTO_RCC )
      rccSpiClockEnable(m_pHandler->Instance);
   #endif

   // Wen need high priority because timer might be used in ISRs.
   setNVInterrupt(configData.irqn, true, NVI_DEFAULT_PRIORITY);

   HAL_SPI_Init(m_pHandler);
}


void CSpi::convertEndianess(uint32_t &value, int count)
{
   uint16_t* value16=(uint16_t*)&value;

   switch( m_bytesPerData * count)
   {
      case 1:
         break;
      case 2:
         value16[0]=htons( value16[0] );
         value16[1]=0;
         break;
      case 4:
         // e.g. MAX31855 really has 32bit values. 2x16bit does not work here.
         value=htonl( value );
         break;
      default:
         // "unplasible"
         exception( );
         break;
   }
}

uint32_t CSpi::writeReadData(uint32_t value, int count)
{
   uint32_t retValue=0;
   convertEndianess(value, count);
   m_status=HAL_SPI_TransmitReceive( m_pHandler, (uint8_t*)&value, (uint8_t*)&retValue, count, SPI_TIMEOUT);
   convertEndianess(retValue, count);
   SPI_CHECK_STATUS;

   return(retValue);
}


int CSpi::writeReadData(const uint8_t* out, uint8_t* in, int count)
{
   m_status=HAL_SPI_TransmitReceive( m_pHandler, (uint8_t*)out, in, count, SPI_TIMEOUT);
   SPI_CHECK_STATUS;

   return( m_status );
}


int CSpi::writeData(uint32_t value, int count)
{
   convertEndianess(value, count);
   m_status=HAL_SPI_Transmit( m_pHandler, (uint8_t*)&value, count, SPI_TIMEOUT);
   SPI_CHECK_STATUS;

   return( m_status );
}


int CSpi::writeData(const uint8_t* data, int count)
{
   m_status=HAL_SPI_Transmit( m_pHandler, (uint8_t *)data, count, SPI_TIMEOUT);
   SPI_CHECK_STATUS;

   return( m_status );
}


uint32_t CSpi::readData( int count )
{
   uint32_t retValue=0x0;

   m_status=HAL_SPI_Receive( m_pHandler, (uint8_t *)&retValue, count, SPI_TIMEOUT);
   SPI_CHECK_STATUS;

   convertEndianess(retValue, count);

   if( m_status != HAL_OK )
      retValue=-1;

   return(retValue);
}


uint32_t CSpi::readData(uint8_t *data, int count)
{
   m_status=HAL_SPI_Receive( m_pHandler, (uint8_t *)data, count, SPI_TIMEOUT);
   SPI_CHECK_STATUS;

   return(m_status);
}

void CSpi::error()
{
   // "SPI error"
   exception( );
}

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wattributes"

IRQ_Funcion_imp( SPI1_IRQHandler )
{
   // "Not implemented"
   exception( );
}

#pragma GCC diagnostic pop

//---fin-----------------------------------------------------------------------
