//-----------------------------------------------------------------------------
///
/// \brief  Generic spi flash
///
///         Using Microchip SST25VF016B
///
/// \date   20160717
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//-----------------------------------------------------------------------------


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


//---General--------------------------


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

// #define LEPTO_LOG_DEBUG

#include <biwak/spi_slave.h>
#include <biwak/flash_spi.hpp>
#include <biwak/biwak.h>
#include <lepto/log.h>
#include <string.h>           // memcpy
#include <arpa/inet.h>        // htons
#include <stdint.h>           // uint8_t
#include <exception>


//---Defines-------------------------------------------------------------------


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


/*    Manufacturer: 0xBF   SST
 *                               0x49     SST25VF010A 1MBit, Byte-AAI-program-only
 *                               0x41     SST25VF016B 16MBit, Word-AAI-program-only
 *                  0xC8   GIGADEVICE_ID
 *
 *   For a complete list see https://github.com/jhcloos/flashrom/blob/master/flashchips.h
 */

/// \brief  Le contructeur
///
CFlashSpi::CFlashSpi( CSpi &_spi, EGpioPeripheral eGpioPeripheralCS, EGpioPin eGpioPinCS )
   :CFlash( 0, 0 )
   ,CSpiSlave(_spi, eGpioPeripheralCS, eGpioPinCS )
{
   // First toggle necessary
   // chipSelect();
   // usleep(10);
   // chipDeselect();
   m_writeMode= EWriteMode::page;

   // parse erase size and chip size
   readJId();

   lDebug("Flash-ID: 0x%X", readId());
   lDebug("     JID: 0x%X", readJId());
   lDebug("    Size: 0x%X", m_size);

   if( m_jdecId == 0x41BF41BF )
   {
      m_writeMode = EWriteMode::aaiWord;
   }

   return;
}


CFlashSpi::~CFlashSpi()
{
   return;
}

void CFlashSpi::io( uint8_t *out_in, int len)
{
   chipSelect();
   getSpi().writeReadData( out_in, out_in , len);
   chipDeselect();
   return;
}

int CFlashSpi::io_wr( const uint8_t *out, int outlen, uint8_t *in, int inlen)
{
   int sta;
   chipSelect();
   sta=getSpi().writeData( out, outlen );
   if(!sta)
   {
      sta=getSpi().readData( in, inlen );
   }
   chipDeselect();
   return(sta);
}

void CFlashSpi::io_ww( const uint8_t *out1, int out1len, const  uint8_t *out2, int out2len)
{
   chipSelect();
   getSpi().writeData( out1, out1len );
   getSpi().writeData( out2, out2len );
   chipDeselect();
   return;
}


int CFlashSpi::readId()
{
   uint32_t command=CFI_INS_READID; // LSB
   uint32_t id=0;

   chipSelect();
   getSpi().writeReadData( (uint8_t *)&command, (uint8_t *)&id, 4);
   getSpi().readData( (uint8_t *)&id, 4);
   chipDeselect();

   return( id );
}


int CFlashSpi::readJId()
{
   uint32_t command=CFI_INS_READJID; // LSB
   m_jdecId = 0;
   int retries=3;
   
   // Right after reset, the spi controller seems to need some time. With this
   // retries the problem is fixed.
   while( ( m_jdecId == 0 ) && retries )
   {
      chipSelect();
      getSpi().writeReadData( (uint8_t *)&command, (uint8_t *)&m_jdecId, 4);
      chipDeselect();
      retries--;
   }

   lDebug( "JEDEC-Id: 0x%X; Tries: %d", m_jdecId, 5 - retries );

   if(m_jdecId!=0)
   {
      // MBits: (2^(capacity-0x11))
      // KBits: (2^(capacity-0x7))
      // Bytes: (2^capacity)
      m_size = ( 1 << ( ( ( m_jdecId >> 24 ) & 0xFF ) ) );
      m_erasePageSize = 1 << 0xC;
      m_vendor= ( m_jdecId >> 8 ) & 0xff;
   }
   else
   {
      m_size = 0;
   }

   return( m_jdecId );
}


int CFlashSpi::readErp()
{
   SCommandStatus com;
   com.instruction=CFI_INS_READ_ERP;
   io((uint8_t *)&com, 2);

   return(com.status);
}

int CFlashSpi::readSta()
{
   SCommandStatus com;
   com.instruction=CFI_INS_READ_STATUS;
   com.status=0;
   io((uint8_t *)&com, 2);

   return(com.status);
}


int CFlashSpi::waitForReady()
{
   int sta;
   while ( ( sta = readSta() ) & 1 )
   {
      usleep( BUSY_DELAY );
   }
   return(sta);
}


int CFlashSpi::readData(address_t addr, void *data, int size)
{
   waitForReady();
   SCommandAddress com;
   com.addr=__htonl( addr );
   com.instruction=CFI_INS_READ;
   io_wr((uint8_t *)&com, 4, (uint8_t *)data, size);

   return(0);
}


int CFlashSpi::writeByte(int addr, uint8_t data)
{
   SCommandAddress comStart;

   waitForReady();
   enableWrite();
   comStart.addr=__htonl( addr );
   comStart.instruction=CFI_BYTE_PROGRAMM;
   //io_ww( (uint8_t *)&comStart, 4, &data, 1);

   comStart.data[0]=data;
   io_ww( (uint8_t *)&comStart, 5, &data, 0);

   return(0);
}


int CFlashSpi::writeWord(int addr, const uint8_t *data, int size)
{
   /*
   for(int i1=0; i1<size; i1++)
   {
      write(addr, *data);
      addr++;
      data++;
   }
   return(0);
   */
   SCommandAddress comStart;
   SCommandWord com;
   int psize;
   uint8_t comEnd;
   //uint8_t cData[2];

   // 'programm word' has to be word aligned
   if((int)addr&1)
   {
      writeByte(addr, *data);
      addr++;
      size--;
      data++;
   }

   if(size>=2)
   {
      psize=1;

      waitForReady();
      enableWrite();
      comStart.addr=__htonl( addr );
      comStart.instruction=CFI_AAI_PROGRAMM;
      io_ww((uint8_t *)&comStart, 4, data, psize);
      size-=psize;
      data+=psize;
      addr+=psize;

      while(size>=2)
      {
         waitForReady();
         enableWrite();
         //com.data=__htons( (uint16_t *) );
         //com.data=__htons( *(uint16_t *)data );
         com.instruction=CFI_AAI_PROGRAMM;
         com.data=*(uint16_t *)data;
         io_ww((uint8_t *)&com, 1, data, psize);
         size-=psize;
         addr+=psize;
         data+=psize;
      }

      // Needed to terminate AAI
      comEnd=CFI_INS_WRDI;
      io(&comEnd, 1);
      //comEnd=CFI_INS_DBSY;
      //io(&comEnd, 1);
   }

   if(size)
   {
      writeByte(addr, *data);
   }

   return(0);
}

int CFlashSpi::writeData(address_t addr, const void *data, int size)
{
   int ret=0;
   switch(m_writeMode)
   {
      case EWriteMode::aaiWord:
      case EWriteMode::aaiByte:
         ret=_writeAai(addr, data, size);
         break;
      case EWriteMode::page:
         ret=writePage(addr, data, size);
         break;
      default:
         ret=-1;
         break;
   }
   return(ret);
}

int CFlashSpi::writePage(int addr, const void *data, int size)
{
   SCommandAddress comStart;
   int currSize;

   while(size)
   {
      waitForReady();
      enableWrite();
      currSize=MIN(size, PAGE_SIZE);
      comStart.addr=__htonl( addr );
      comStart.instruction=CFI_PAGE_PROGRAMM;
      io_ww( (uint8_t *)&comStart, 4, (const uint8_t*)data, currSize);
      size-=currSize;
      addr+=currSize;
   }

   return(0);
}

int CFlashSpi::_writeAai(int addr, const void *_data, int size)
{
   SCommandAddress comStart;
   SCommandByte com;
   int psize;
   uint8_t comEnd;
   int pageSize=0x100; // 320*240*3;//0x1000;
   int blockSize, initialBlockSize;
   const uint8_t *bData=(const uint8_t *)_data;
   int instruction=( m_writeMode==EWriteMode::aaiWord)? CFI_AAI_PROGRAMM_WORD : CFI_AAI_PROGRAMM;

   psize= ( m_writeMode==EWriteMode::aaiWord)? 2 : 1;

   if( ( m_writeMode==EWriteMode::aaiWord) && ( (int)addr&1 ) )
   {
      writeByte(addr, *(const uint8_t *)bData);
      addr++;
      size--;
      bData++;
   }

   while(size)
   {
      blockSize=size<pageSize?size:pageSize;
      initialBlockSize=blockSize;
      waitForReady();
      enableWrite();

      comStart.addr=__htonl( addr );
      comStart.instruction=instruction;
      io_ww((uint8_t *)&comStart, 4, bData, psize);
      blockSize-=psize;
      bData+=psize;
      addr+=psize;

      while(blockSize >= psize)
      {
         //waitForReady();
         //enableWrite();
         com.instruction=instruction;
         //com.data=*(uint8_t *)data;
         io_ww((uint8_t *)&com, 1, bData, psize);
         blockSize-=psize;
         addr+=psize;
         bData+=psize;
      }

      // Needed to terminate AAI
      comEnd=CFI_INS_WRDI;
      io(&comEnd, 1);

      size-=initialBlockSize;
   }

   if( ( m_writeMode==EWriteMode::aaiWord) && size)
   {
      writeByte(addr, *bData);
      addr++;
      size--;
      bData++;
   }

   return(0);
}

int CFlashSpi::erasePage(address_t addr, int count)
{
   waitForReady();
   enableWrite();

   SCommandAddress com;
   com.addr=__htonl( addr );
   com.instruction=INS_ERASE_SECTOR;
   io_wr((uint8_t *)&com, 4, nullptr, 0);

   return(0);
}

int CFlashSpi::eraseChip()
{
   waitForReady();
   enableWrite();

   SCommandAddress com;
   com.instruction=INS_ERASE_CHIP;
   io_wr((uint8_t *)&com, 1, nullptr, 0);

   return(0);
}


int CFlashSpi::enableWrite()
{
   char com;
   com=CFI_INS_ENABLE_WRITE;
   io((uint8_t *)&com, 1);
   return(0);
}

int CFlashSpi::unlock()
{
   char com;
   com=INS_ENABLE_WRITE_STATUS;
   io((uint8_t *)&com, 1);

   char coms[2];
   coms[0]=INS_WRITE_STATUS;
   coms[1]=0;
   io((uint8_t *)&coms, 2);
   return(0);
}


void CFlashSpi::deepPowerDown()
{
   char com;
   com=INS_DEEP_POWER_DOWN;
   io((uint8_t *)&com, 1);
   return;
}


void CFlashSpi::wakeUp()
{
   char com;
   com=INS_WAKE_UP;
   io((uint8_t *)&com, 1);
   return;
}


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