//-----------------------------------------------------------------------------
///
/// \brief  QSPI
///
///         https://www.gigadevice.com/datasheet/gd25q16c/
///
/// \date   20220707
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//-----------------------------------------------------------------------------


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


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

#include <biwak/biwak.h>         // exception
#include <biwak/qspi.hpp>
#include <biwak/stm32/qspi.hpp>
#include <biwak/handler.h>
#include <biwak/exception.h>
#include <biwak/nvic.h>
#include <biwak/rcc.h>
#include "qspi_commands.h"

#include <HALWrapper/stm32_rcc.h>
#include <arpa/inet.h>
#include <lepto/print.h>
#include <memory.h>

#define QSPI_FLASH_SIZE                   0x15
#define QSPI_OK                           0
#define QSPI_ERROR                        1
#define QSPI_ERROR_CMD                    2
#define QSPI_ERROR_POL                    3
#define QSPI_ERROR_REC                    4
#define QSPI_ERROR_TX                     5
#define DUMMY_CLOCK_CYCLES_READ_QUAD      10
#define N25Q128A_DUMMY_CYCLES_READ_QUAD   10
#define QSPI_PAGE_SIZE                    256
#define QSPI_END_ADDR                     ( 1 << QSPI_FLASH_SIZE )
#define QSPI_DMA_REQUEST                  DMA_REQUEST_5
#define QSPI_DMA_CHANNEL                  DMA1_Channel5
#define QSPI_DMA_IRQ                      DMA1_Channel5_IRQn
#define QSPI_DMA_IRQ_HANDLER              DMA1_Channel5_IRQHandler

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

CQSpi *interruptQSpi1=nullptr;
int CmdCplt;
int TxCplt;
int RxCplt;
int StatusMatch;
int intCount=0;

CQSpi::CQSpi( const SQSpiConfig &configData)
      : m_configData(configData)
      , m_cs(configData.eGpioPeripheralCs, configData.eGpioPinCs,
             gpioModeQSpi, configData.altFunc)
      , m_sck(configData.eGpioPeripheralClk, configData.eGpioPinClk,
            gpioModeQSpi, configData.altFunc)
      , m_io{
               {configData.eGpioPeripheralIO0, configData.eGpioPinIO0
           , gpioModeQSpi, configData.altFunc},
               {configData.eGpioPeripheralIO1, configData.eGpioPinIO1
           , gpioModeQSpi, configData.altFunc},
               {configData.eGpioPeripheralIO2, configData.eGpioPinIO2
           , gpioModeQSpi, configData.altFunc},
               {configData.eGpioPeripheralIO3, configData.eGpioPinIO3
           , gpioModeQSpi, configData.altFunc},
        }
{
   m_pHandle=new QSPI_HandleTypeDef{0};
   interruptQSpi1=this;
   rccQSpiClockEnable( (void *)QSPI_BASE );

   setNVInterrupt( m_configData.irqn, true, NVI_DEFAULT_PRIORITY);
   init();
}

int CQSpi::init()
{
   m_pHandle->Instance = QUADSPI;
   m_pHandle->Init.ClockPrescaler     = 64; // 64 worked, 32 not with GD25Q16CSIG
   m_pHandle->Init.FifoThreshold      = 4;
   m_pHandle->Init.SampleShifting     = QSPI_SAMPLE_SHIFTING_NONE;
   m_pHandle->Init.FlashSize          = QSPI_FLASH_SIZE;
   m_pHandle->Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_1_CYCLE;
   m_pHandle->Init.ClockMode          = QSPI_CLOCK_MODE_0;
   m_pHandle->Init.DualFlash          = QSPI_DUALFLASH_DISABLE;

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

   __HAL_RCC_DMA1_CLK_ENABLE();

#pragma GCC diagnostic pop

   hdma.Init.PeriphInc           = DMA_PINC_DISABLE;
   hdma.Init.MemInc              = DMA_MINC_ENABLE;
   hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
   hdma.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
   hdma.Init.Mode                = DMA_NORMAL;
   hdma.Init.Priority            = DMA_PRIORITY_LOW;
   hdma.Init.Request             = QSPI_DMA_REQUEST;
   hdma.Instance                 = QSPI_DMA_CHANNEL;

   __HAL_LINKDMA( m_pHandle, hdma, hdma);
   HAL_DMA_Init(&hdma);

   commandDefault();

   setNVInterrupt( QSPI_DMA_IRQ, true, NVI_DEFAULT_PRIORITY);
   HAL_QSPI_DeInit( m_pHandle );
   if (HAL_QSPI_Init( m_pHandle ) != HAL_OK)
   {
     exception();
   }

   return(0);
}

void CQSpi::commandDefault()
{
   m_sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
   m_sCommand.Instruction       = READ_DATA_CMD;
   m_sCommand.AddressMode       = QSPI_ADDRESS_1_LINE;
   m_sCommand.Address           = 0x0;
   m_sCommand.AddressSize       = QSPI_ADDRESS_24_BITS;
   m_sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
   m_sCommand.DataMode          = QSPI_DATA_1_LINE;
   m_sCommand.DummyCycles       = 0;
   m_sCommand.NbData            = BUFFERSIZE;
   m_sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;
   m_sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
   m_sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
}

int CQSpi::memMap()
{
   m_sCommand.Instruction       = QUAD_INPUT_FAST_READ_1_CMD;
   m_sCommand.DummyCycles       = 8;
   m_sCommand.DataMode          = QSPI_DATA_4_LINES;
   m_sCommand.AddressMode       = QSPI_ADDRESS_1_LINE;

   m_sMemMappedCfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;

   if (HAL_QSPI_MemoryMapped( m_pHandle, &m_sCommand, &m_sMemMappedCfg) != HAL_OK)
   {
    exception();
   }

   return(0);
}

int CQSpi::writeEnable( )
{
   int sta;

   if ( ( sta = command(WRITE_ENABLE_CMD) ) != HAL_OK)
   {
      exception();
      return QSPI_ERROR_CMD;
   }

   return( waitFlag(MX25R6435F_SR_WEL, MX25R6435F_SR_WEL) );
}

int CQSpi::waitFlag( int mask, int result )
{
   int sta;
   QSPI_AutoPollingTypeDef sConfig;

   /* Configure automatic polling mode to wait for write enabling */
   sConfig.Match           = result;
   sConfig.Mask            = mask;
   sConfig.MatchMode       = QSPI_MATCH_MODE_AND;
   sConfig.StatusBytesSize = 1;
   sConfig.Interval        = 0x10;
   sConfig.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;

   m_sCommand.Instruction    = READ_STATUS_REG_CMD;
   m_sCommand.AddressMode       = QSPI_ADDRESS_NONE;
   m_sCommand.DataMode       = QSPI_DATA_1_LINE;
   m_sCommand.NbData            = 1;

   if ( ( sta = HAL_QSPI_AutoPolling( m_pHandle, &m_sCommand, &sConfig,
                           HAL_QPSI_TIMEOUT_DEFAULT_VALUE) ) != HAL_OK)
   {
      // QSPI-Autopoll
      exception();
      return QSPI_ERROR_POL;
   }

   return QSPI_OK;
}

int CQSpi::autoPollingMemReady( uint32_t Timeout )
{
  if ( waitFlag(MX25R6435F_SR_WIP, 0) != HAL_OK)
  {
    return QSPI_ERROR;
  }

  return QSPI_OK;
}

int CQSpi::readJedecId()
{
  int id=0;

  /* Initialize the read volatile configuration register command */
  m_sCommand.Instruction       = READ_JEDECID;
  m_sCommand.AddressMode       = QSPI_ADDRESS_NONE;
  m_sCommand.DataMode          = QSPI_DATA_1_LINE;
  m_sCommand.NbData            = 3;

  if (HAL_QSPI_Command( m_pHandle, &m_sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    return QSPI_ERROR_CMD;
  }

  if (HAL_QSPI_Receive(m_pHandle, (uint8_t *)&id, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    return QSPI_ERROR_REC;
  }

  id=ntohl( id << 8 );

  return(id);
}


int CQSpi::readStatus()
{
  int id=0, id2=0;

  /* Initialize the read volatile configuration register command */
  m_sCommand.Instruction       = READ_STATUS_REG_CMD;
  m_sCommand.AddressMode       = QSPI_ADDRESS_NONE;
  m_sCommand.DataMode          = QSPI_DATA_1_LINE;
  m_sCommand.NbData            = 1;

  if (HAL_QSPI_Command( m_pHandle, &m_sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    return QSPI_ERROR_CMD;
  }

  if (HAL_QSPI_Receive(m_pHandle, (uint8_t *)&id, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    return QSPI_ERROR_REC;
  }

  m_sCommand.Instruction       = READ_STATUS2_REG_CMD;
  if (HAL_QSPI_Command( m_pHandle, &m_sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    return QSPI_ERROR_CMD;
  }

  if (HAL_QSPI_Receive(m_pHandle, (uint8_t *)&id2, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    return QSPI_ERROR_REC;
  }
  id = id | ( id2 << 8 );

  return(id);
}

int CQSpi::writeStatus(uint16_t status)
{
   m_sCommand.Instruction       = WRITE_STATUS_REG_CMD;
   m_sCommand.AddressMode       = QSPI_ADDRESS_NONE;
   m_sCommand.DataMode          = QSPI_DATA_1_LINE;
   m_sCommand.NbData            = 2;

   if (HAL_QSPI_Command( m_pHandle, &m_sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
   {
      return QSPI_ERROR_CMD;
   }

   if (HAL_QSPI_Transmit(m_pHandle, (uint8_t *)&status, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
   {
      return QSPI_ERROR_REC;
   }

   return(0);
}

int CQSpi::dump(int address)
{
  uint8_t data[0x100];
  memset(data, 0x87, sizeof(data));

   /* Fallback normal SPI command
  sCommand.Instruction       = READ_DATA_CMD;
  sCommand.AddressMode       = QSPI_ADDRESS_1_LINE;
  sCommand.Address           = 0x0;
  sCommand.DataMode          = QSPI_DATA_1_LINE;
  sCommand.NbData            = sizeof(data);
  */

  m_sCommand.Instruction       = QUAD_INPUT_FAST_READ_1_CMD;
  m_sCommand.DummyCycles       = 8;
  m_sCommand.DataMode          = QSPI_DATA_4_LINES;
  m_sCommand.AddressMode       = QSPI_ADDRESS_1_LINE;
  m_sCommand.NbData            = sizeof(data);

  if (HAL_QSPI_Command( m_pHandle, &m_sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    return QSPI_ERROR_CMD;
  }

  if (HAL_QSPI_Receive(m_pHandle, data, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    return QSPI_ERROR_REC;
  }

  m_sCommand.DummyCycles       = 0;

  hexDump(data, sizeof(data) );

  return(0);
}

int CQSpi::write(int address, const uint8_t *data, int len)
{
  int sta;

  /* Fallback normal SPI command
  sCommand.Instruction       = PAGE_PROG_CMD;
  sCommand.AddressMode       = QSPI_ADDRESS_1_LINE;
  sCommand.DataMode          = QSPI_DATA_1_LINE;
   */

  m_sCommand.Instruction       = QUAD_PAGE_PROG_CMD;
  m_sCommand.AddressMode       = QSPI_ADDRESS_1_LINE;
  m_sCommand.Address           =  0;
  m_sCommand.DataMode          = QSPI_DATA_4_LINES;
  m_sCommand.NbData            = 0x100;

  if( ( sta=writeEnable() ) )
  {
     return(sta);
  }

  if (HAL_QSPI_Command( m_pHandle, &m_sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    return QSPI_ERROR_CMD;
  }

  if ( ( sta = HAL_QSPI_Transmit(m_pHandle, (uint8_t *)data, 5000U ) ) != HAL_OK)
  {
    return QSPI_ERROR_TX;
  }

  return(0);
}


int CQSpi::enableHPM()
{
   return( command ( ENABLE_HPM ) );
}


int CQSpi::deepPowerDown()
{
   return ( command(DEEP_POWER_DOWN) );
}

int CQSpi::wakeUp()
{
   return ( command(WAKE_UP) );
}

int CQSpi::command(uint32_t inst)
{
   m_sCommand.Instruction       = inst;
   m_sCommand.AddressMode       = QSPI_ADDRESS_NONE;
   m_sCommand.DataMode          = QSPI_DATA_NONE;

   if (HAL_QSPI_Command( m_pHandle, &m_sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
   {
     return QSPI_ERROR_CMD;
   }

   return(0);
}

void CQSpi::interrupt()
{
   HAL_QSPI_IRQHandler(m_pHandle);
   return;
}

#pragma GCC diagnostic ignored "-Wattributes"

IRQ_Funcion_imp( QUADSPI_IRQHandler )
{
   interruptQSpi1->interrupt();
   intCount++;
   return;
}

extern "C"
{
   void QSPI_DMA_IRQ_HANDLER(void)
   {
      HAL_DMA_IRQHandler(interruptQSpi1->m_pHandle->hdma);
   }
};

#pragma GCC diagnostic push

void HAL_QSPI_AbortCpltCallback    (QSPI_HandleTypeDef *hqspi)
{
   exception();
}

void HAL_QSPI_FifoThresholdCallback(QSPI_HandleTypeDef *hqspi)
{
   //exception();
}

void HAL_QSPI_RxHalfCpltCallback   (QSPI_HandleTypeDef *hqspi)
{
   exception();
}

void HAL_QSPI_TxHalfCpltCallback   (QSPI_HandleTypeDef *hqspi)
{
   //exception();
}

void HAL_QSPI_CmdCpltCallback(QSPI_HandleTypeDef *hqspi)
{
   CmdCplt++;
}

void HAL_QSPI_RxCpltCallback(QSPI_HandleTypeDef *hqspi)
{
   RxCplt++;
}

void HAL_QSPI_TxCpltCallback(QSPI_HandleTypeDef *hqspi)
{
   TxCplt++;
}

void HAL_QSPI_StatusMatchCallback(QSPI_HandleTypeDef *hqspi)
{
   exception();
   StatusMatch++;
}

void HAL_QSPI_ErrorCallback(QSPI_HandleTypeDef *hqspi)
{
   exception( );
}

void HAL_QSPI_TimeOutCallback(QSPI_HandleTypeDef *hqspi)
{
   exception( );
}


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