//-----------------------------------------------------------------------------
///
/// \brief  Flash
///
///         Access to internal flash.
///
/// \date   20150416
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//-----------------------------------------------------------------------------


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


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

// #define LEPTO_LOG_DEBUG

#include <lepto/log.h>
#include <biwak/biwak.h>           // exception
#include <biwak/flash_intern.h>
#include <biwak/handler.h>       // need C-Declaration of IRQ handler
#include <biwak/nvic.h>
#include <biwak/exception.h>

#include <HALWrapper/stm32_flash.h>       // dac wants that
#include <HALWrapper/stm32_rcc.h>         // RCC
#include <string.h>                // memcpy;


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


extern char _end_data_flash;
extern char _start_user_flash;


CFlashIntern::CFlashIntern( )
         //:CFlash((address_t)flashStart() + (address_t)size(), size(), EAcquireDirection::highLow)
         :CFlash((address_t)userStart(), userSize() )
{
   init();
   // Must be aligned
   bDebugAssert( ((address_t)getStartAddress() % (address_t)erasePageSize()) == 0, 0 );
   bDebugAssert( userStart() < flashEnd(), 0 );
   
   #if defined (STM32F4)
          // Really? 16K on STM32F4? 0x4000
      // Na pages at all? fix it to 0x800
      m_erasePageSize=0x800;
   #else
      m_erasePageSize=FLASH_PAGE_SIZE;
   #endif

   return;
}


CFlashIntern::~CFlashIntern( )
{
   return;
}


void CFlashIntern::init()
{
   // Internal flash does not need to be initialized, you are probably already
   // running on it
   #if defined(__HAL_RCC_FLASH_CLK_ENABLE)
      #pragma GCC diagnostic push
      #pragma GCC diagnostic ignored "-Wvolatile"
      __HAL_RCC_FLASH_CLK_ENABLE();
      #pragma GCC diagnostic pop
   #endif
   setNVInterrupt( FLASH_IRQn , true, NVI_DEFAULT_PRIORITY);
   //__HAL_FLASH_CLEAR_FLAG( FLASH_FLAG_OPTVERR | FLASH_FLAG_PGSERR );

   return;
}

#if 0
uint32_t CFlashIntern::writeDW(uint32_t address, int data)
{
   HAL_StatusTypeDef sta;

   HAL_FLASH_Unlock();
#if defined(STM32L4)
   exception("not implemented", -1);
#elif defined(STM32G4)
   sta=HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, data);
#else
   sta=HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data);
#endif
   return(sta);
}

#endif

#define BITS 3                      // How many bits are masked yout
#define MASK  ( ( 1 << BITS ) - 1 ) // '7'; mask with address to get the shift
#define BYTES ( 1 << BITS )         // 8 Bytes

void CFlashIntern::program(uint32_t address, uint64_t value) const
{
   HAL_StatusTypeDef sta = HAL_OK;
   sta=HAL_FLASH_Program(
         #if defined(FLASH_TYPEPROGRAM_DOUBLEWORD)
            FLASH_TYPEPROGRAM_DOUBLEWORD,
         #else
               // Not implemented
               FLASH_TYPEPROGRAM_WORD,
         #endif
                         address,
                         (uint64_t)value );
   if(sta!=HAL_OK)
   {
      lWarning( "Error writing internal flash: %d", sta);
      lWarning( "   Error: %d", HAL_FLASH_GetError());
      lWarning( "   addr: 0x%X", address);
   }
   else {
      //lInfo << "   Internal flash OK (A)";
   }

   return;
}

// Not all HALs have FLASH_FLAG_ALL_ERRORS
#if ! defined(FLASH_FLAG_ALL_ERRORS)
   #if defined(STM32L1)
      #define FLASH_FLAG_PGERR      0
   #endif
   #if defined (FLASH_FLAG_PGERR)
      #define FLASH_FLAG_ALL_ERRORS  ( FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGERR)
   #else
      // many errors missing
      #define FLASH_FLAG_ALL_ERRORS  ( FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR)
   #endif
#endif


#if 0
int CFlashIntern::writeData64(address_t address, const void *data, int size)
{
   HAL_StatusTypeDef sta=HAL_OK;
   //const uint8_t *bData=(const uint8_t *)data;

   //lDebug("Writing %d bytes @0x%X", address);

   sta=HAL_FLASH_Unlock();

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

   #if defined(STM32L1)
      // Not implemented
      exception();
   #elif defined (STM32L0)
      // No idea if this is correct
      CLEAR_BIT(FLASH->PECR, FLASH_PECR_PROG);
   #else
      CLEAR_BIT(FLASH->CR, FLASH_CR_PG);
   #endif
   __HAL_FLASH_CLEAR_FLAG( FLASH_FLAG_ALL_ERRORS );

   #pragma GCC diagnostic push
#if 0
   address_t alignedAddress;
   int shift=address&MASK;
   alignedAddress=address&~MASK;
   uint64_t value;
   uint64_t *data64=(uint64_t *)data;
   int rightGap;

   /* 'program()' only supports 64 write access (assume). So 64-Bit value has
    * to be read first.
   */
   if(shift)
   {
      value=*(uint64_t *)alignedAddress;
      uint64_t mask=(uint64_t)-1;
      rightGap=BYTES - (shift + size);
      if( rightGap > 0 )
      {
         mask<<=8*rightGap;
         mask>>=8*rightGap;
      }
      value|=(*data64 & mask);
      //memcpy((void *)(((char *)&value)+shift), bData, BYTES-shift);

      program( alignedAddress, (uint64_t)value );
      alignedAddress+=BYTES;
      bData+=BYTES-shift;
      size-=BYTES-shift;
   }

   for(int i1=0; size >= BYTES ; i1++)
   {
      #if defined(STM32L4) || defined(STM32L1)
         lFatal( "not implemented" );
            exception("not implemented");
      #elif defined(STM32G4)
         //a2 = (address+(i1 << BITS));
         value=*(uint64_t *)bData;
         program( alignedAddress, (uint64_t)value );
         size-=BYTES;
         alignedAddress+=BYTES;
         bData+=8;
      #elif defined( STM32L0 )
         sta=HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address+(i1 << BITS), ((uint32_t *)data)[i1] );
      #else
            sta=HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address+(i1 << BITS), ((uint32_t *)data)[i1] );
      #endif
   }

   if(size>0)
   {
      value=*(uint64_t *)alignedAddress;
      memcpy((void *)(((char *)&value)), bData, size);
      program( alignedAddress, (uint64_t)value );
      alignedAddress+=BYTES;
      bData+=BYTES-shift;
      size-=BYTES-shift;
   }
#endif
   lAssert( ( address & 7 ) == 0 );
   uint64_t* source=(uint64_t*)data;
   
   // Ok, maybe some additional bytes may get written
   while( ( size > 0 ) && ( !sta ) )
   {
      #if defined FLASH_TYPEPROGRAM_DOUBLEWORD
      sta=HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, source[0] );
      #else
      // Not implemented yet
      exception();
      #endif
      source++;
      address+=sizeof(source[0]);
      size-=sizeof(source[0]);
   }
   // lAssert( size == 0 );

   HAL_FLASH_Lock();

   return(sta);
}
#endif // 0

int CFlashIntern::writeData(address_t address, const void *data, int size)
{
   #if defined FLASH_TYPEPROGRAM_WORD
      return( writeData32( address, data, size) );
   #elif defined FLASH_TYPEPROGRAM_DOUBLEWORD
      return( writeData64( address, data, size) );
   #else
   exception();
   #endif
}


#if defined FLASH_TYPEPROGRAM_WORD
int CFlashIntern::writeData32(address_t address, const void *data, int size)
#elif defined FLASH_TYPEPROGRAM_DOUBLEWORD
int CFlashIntern::writeData64(address_t address, const void *data, int size)
#endif
{
   HAL_StatusTypeDef sta=HAL_OK;
   
   #if defined FLASH_TYPEPROGRAM_WORD
   if( ( address & ( 4 - 1 ) ) || ( size &  ( 4 - 1 ) ) )
   #elif defined FLASH_TYPEPROGRAM_DOUBLEWORD
   if( ( address & ( 8 - 1 ) ) || ( size &  ( 8 - 1 ) ) )
   #endif
   {
      exception();
   }
   
   sta=HAL_FLASH_Unlock();
   
   #pragma GCC diagnostic push
   #pragma GCC diagnostic ignored "-Wvolatile"
      
   #if defined(STM32L1)
          // Not implemented
      exception();
   #elif defined (STM32L0)
          // No idea if this is correct
      CLEAR_BIT(FLASH->PECR, FLASH_PECR_PROG);
   #else
      CLEAR_BIT(FLASH->CR, FLASH_CR_PG);
   #endif
      
   __HAL_FLASH_CLEAR_FLAG( FLASH_FLAG_ALL_ERRORS );
   
   #if defined FLASH_TYPEPROGRAM_WORD
      uint32_t* source=(uint32_t*)data;
   #elif defined FLASH_TYPEPROGRAM_DOUBLEWORD
      uint64_t* source=(uint64_t*)data;
   #endif
   
   // Ok, maybe some additional bytes may get written
   while( ( size > 0 ) && ( !sta ) )
   {
      #if defined FLASH_TYPEPROGRAM_WORD
         sta=HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, source[0] );
      #elif defined FLASH_TYPEPROGRAM_DOUBLEWORD
         sta=HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, source[0] );
         #if 0
         // Does nt work yet
         // FLASH_TYPEPROGRAM_DOUBLEWORD is really slow
         if( size > (int)sizeof(source[0]) )
         {
            sta=HAL_FLASH_Program(FLASH_TYPEPROGRAM_FAST, address, source[0] );
         }
         else
         {
            sta=HAL_FLASH_Program(FLASH_TYPEPROGRAM_FAST_AND_LAST, address, source[0] );
         }
         #endif
      #else
         #error Fooo
      #endif
      source++;
      address+=sizeof(source[0]);
      size-=sizeof(source[0]);
   }
   
   HAL_FLASH_Lock();
   
   return(sta);
}


int CFlashIntern::readData(address_t address, void *data, int size)
{
   memcpy(data, (void *)address, size);
   return(0);
}

int CFlashIntern::erasePage(address_t pageAddr, int count)
{
   HAL_StatusTypeDef sta;
   
   sta=HAL_FLASH_Unlock();
   lAssert( sta == HAL_OK );

   __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);

   #if defined(STM32F0) || defined(STM32F3) || defined(STM32F4) || defined(STM32L4) \
            || defined(STM32F1) || defined(STM32L1)
      FLASH_EraseInitTypeDef eraseInit;
      uint32_t pageError=0;

      #if defined( STM32F3 ) || defined (STM32F0)
         eraseInit.TypeErase=FLASH_TYPEERASE_PAGES;
         eraseInit.PageAddress=(address_t)pageAddr;
         eraseInit.NbPages=count;
      #elif defined (STM32F1)
         eraseInit.TypeErase=FLASH_TYPEERASE_PAGES;
         eraseInit.Banks=0;
         eraseInit.PageAddress=(address_t)pageAddr;
         eraseInit.NbPages=count;
      #elif defined (STM32F4)
         eraseInit.TypeErase=FLASH_TYPEERASE_SECTORS;
         eraseInit.Banks=0;
         eraseInit.NbSectors=count;
         eraseInit.Sector=(address_t)pageAddr;
         eraseInit.VoltageRange=FLASH_VOLTAGE_RANGE_3;
      #elif defined (STM32L1)
         eraseInit.TypeErase=FLASH_TYPEERASE_PAGES;
         eraseInit.PageAddress=(address_t)pageAddr;
         eraseInit.NbPages=count;
      #elif defined (STM32L4)
         eraseInit.TypeErase=FLASH_TYPEERASE_PAGES;
         eraseInit.Page=(address_t)pageAddr / FLASH_PAGE_SIZE;
         eraseInit.NbPages=count;
         eraseInit.Banks=0;
      #else
         #error "Uninitialized erase command"
      #endif
      sta=HAL_FLASHEx_Erase( &eraseInit, &pageError);
      // pageError: 0xFFFFFFFF means that all the pages have been correctly erased
      #if 0
      if( pageError != 0xFFFFFFFF )
      {
         if(!sta)
         {
            sta=HAL_ERROR;
         }
      }
      #endif
   #elif defined(STM32G4) || defined(STM32G0)
      for(int i1=0; i1<count; i1++)
      {
         FLASH_PageErase( addressToPage(pageAddr + ( i1 * FLASH_PAGE_SIZE ) )
                         , FLASH_BANK_1);
      }
   #elif defined (STM32L0)
      sta=HAL_OK;
      // On STM32L0 this function has a void return type
      FLASH_PageErase((address_t)pageAddr);
   #else
      sta=FLASH_PageErase((address_t)pageAddr);
      //HAL_StatusTypeDef sta=HAL_FLASHEx_PageErase((address_t)pageAddr);
   #endif

   clearPER();
   HAL_FLASH_Lock();

   if( sta != HAL_OK )
   {
      lWarning("ERFL");
      //lInfo("   Erase Code: %d", HAL_FLASH_GetError());
   }
   return(sta);
}

address_t CFlashIntern::addressToPage( address_t address ) const
{
   return( ( (address_t)address-flashStart() ) / erasePageSize() );
};

void CFlashIntern::clearPER() const
{
   #pragma GCC diagnostic push
   #pragma GCC diagnostic ignored "-Wvolatile"

   //#if ! defined (STM32G4)
      #if defined ( FLASH_CR_PER )
            FLASH->CR &= ~FLASH_CR_PER;
      #else
            // No idea
      #endif
   //#endif

   #pragma GCC diagnostic pop

   return;
}


#if ! defined(FLASH_SIZE)
   // FLASH_SIZE is supported now by tcbuilder
   extern char __flash_size;
   #define FLASH_SIZE   &__flash_size
#endif
 

int CFlashIntern::totalSize() const
{
   #if defined (STM32L1)
      return( FLASH_SIZE );
   #elif defined (STM32F4) || defined (STM32L1) || defined (STM32F0)
      //return( FLASH_SIZE );
      uint16_t *pSize=(uint16_t *)FLASHSIZE_BASE;
      return( *pSize );
   #else
    return(
      ((((*((uint16_t *)FLASH_SIZE_DATA_REGISTER)) == 0xFFFF))
               ? (0x400 << 10)
               : (((*((uint16_t *)FLASH_SIZE_DATA_REGISTER)) & (0x0FFF)) << 10))
    );
   #endif
}

address_t CFlashIntern::flashStart() const
{
   return( (address_t)FLASH_BASE );
}

address_t CFlashIntern::flashEnd() const
{
   return( (address_t)FLASH_BASE + (address_t)FLASH_SIZE );
}

address_t CFlashIntern::userStart() const
{
   // when _start_user_flash would not be available:
   #if 0
   address_t addr = (address_t )&_end_data_flash;
   address_t pmask = ~( erasePageSize() - 1 );
   address_t nmask = ~( erasePageSize() - 1 );
   if ( addr & pmask )
   {
      addr+=erasePageSize();
   }
   addr &= nmask;
   return( addr );
   #else
   return( (address_t )&_start_user_flash );
   #endif
}

int CFlashIntern::userSize() const
{
   return(
      //(address_t)size()
      //      - ((address_t)userStart()-(address_t)flashStart() )
         #if defined( FLASH_SIZE )
            ( (address_t)FLASH_BASE + (address_t)FLASH_SIZE )
            - (address_t)userStart() );
         #else
            ( (address_t)FLASH_BASE + (address_t)(void *)&FLASH_SIZE )
                  - (address_t)userStart() );
         #endif
}

void CFlashIntern::eraseUserFlash()
{
   // Check everything is alligned to pages
   lAssert( ( (  ( (address_t)flashStart() + size() ) - userStart() ) % erasePageSize()) == 0 );

   lInfo("Erasing user flash");

   erasePage( userStart(),
             ( ( (address_t)flashStart() + size() ) - userStart() ) / erasePageSize()
             );
}

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

IRQ_Funcion_imp ( FLASH_IRQHandler )
{
   HAL_FLASH_IRQHandler();

   return;
}

#pragma GCC diagnostic pop


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