/** @file hal/micro/cortexm3/flash.c
 * @brief Implements the generic flash manipulation routines.
 * 
 * The file 'flash-sw-spec.txt' should provide *all* the information needed
 * to understand and work with the FLITF and flash.
 * 
 * 
 * <!--(C) COPYRIGHT 2010 STMicroelectronics. All rights reserved.        -->
 */

#include PLATFORM_HEADER
#include "error.h"
#include "hal/micro/cortexm3/bootloader/fib-bootloader.h"
#include "hal/micro/cortexm3/mpu.h"
#include "memmap.h"
#include "flash.h"

#ifdef FLASH_PROGRAMMING_WITH_EMPTY_FIB
#define ST_EMU_TEST
#endif
// A translation table used to convert FibStatus codes to corresponding
//  StStatus values
static const StStatus fibToStStatus[] = {
  ST_SUCCESS,                    // FIB_SUCCESS             0
  ST_BAD_ARGUMENT,               // FIB_ERR_UNALIGNED       1
  ST_BAD_ARGUMENT,               // FIB_ERR_INVALID_ADDRESS 2
  ST_BAD_ARGUMENT,               // FIB_ERR_INVALID_TYPE    3
  ST_ERR_FLASH_PROG_FAIL,        // FIB_ERR_WRITE_PROTECTED 4
  ST_ERR_FLASH_PROG_FAIL,        // FIB_ERR_WRITE_FAILED    5
  ST_ERR_FLASH_WRITE_INHIBITED,  // FIB_ERR_ERASE_REQUIRED  6
  ST_ERR_FLASH_VERIFY_FAILED     // FIB_ERR_VERIFY_FAILED   7
};
  
//The purpose of flashEraseIsActive and halFlashEraseIsActive() is so that
//interrupts can query the flash library to find out of Flash Erase is
//active when their ISR gets invoked.  This is useful because Flash Erase
//causes the chip to go ATOMIC for 21ms and this delay will disrupt interrupt
//latency.  By having a sinple API that an ISR can query for this state,
//the ISR can appriopriately adjust for a 21ms latency time.
boolean flashEraseIsActive = FALSE;
boolean halFlashEraseIsActive(void)
{
  return flashEraseIsActive;
}


// Emulators do not have FIB bootloaders, so need to include a copy of
// these core flash routines.  

#if defined(ST_EMU_TEST)

static void enableFlitf(void)
{
  //First, unlock the FLITF by writing the two key values to the Flash
  //Protection Unlock register
  FPEC_KEY = FPEC_KEY1;
  FPEC_KEY = FPEC_KEY2;
  
  //Second, unlock the CIB by writing the two key values to the CIB
  //Protection Unlock register
  OPT_KEY = FPEC_KEY1;
  OPT_KEY = FPEC_KEY2;
  
  //Turn on the FPEC clock for flash manipulation operations
  FPEC_CLKREQ = FPEC_CLKREQ_FIELD;
  
  //make sure the FPEC clock is running before we proceed
  while( (FPEC_CLKSTAT&FPEC_CLKACK) != FPEC_CLKACK) {}
  
  //just in case, wait until the flash is no longer busy
  while( (FLASH_STATUS&FLASH_STATUS_FLA_BSY) == FLASH_STATUS_FLA_BSY ) {}
}

static void disableFlitf(void)
{
  //make sure the FPEC is completely idle before turning off the clock
  while( (FPEC_CLKSTAT&FPEC_CLKBSY) == FPEC_CLKBSY) {}
  
  //Turn off the FPEC clock now that we're done
  FPEC_CLKREQ = FPEC_CLKREQ_RESET;
  
  //Set LOCK and clear OPTWREN to lock both the FLITF and the CIB.
  //NOTE: The PROG bit must also be cleared otherwise Flash can still
  //      be programmed even with the LOCK bit set.  BugzID: 6267
  FLASH_CTRL = FLASH_CTRL_LOCK; //lock the flash from further accesses
}

static FibStatus fibFlashWrite(uint32_t address, uint8_t *data, uint32_t length, uint32_t dummy)
{
  uint32_t i;
  uint16_t *ptr;
  FibStatus status = FIB_SUCCESS;
  // Address and length must be half-word aligned.
  if ((address & 1) || (length & 1)) {
    return FIB_ERR_UNALIGNED;
  }
  // Start and end address must be in MFB or CIB.
  if (!((address >= MFB_BOTTOM && address + length <= MFB_TOP + 1)
        || (address >= CIB_BOTTOM && address + length <= CIB_TOP + 1))) {
    return FIB_ERR_INVALID_ADDRESS;
  }
  enableFlitf();
  ptr = (uint16_t *)address;
  for (i = 0; i < length; i += 2) {
    uint16_t currentData = *ptr;
    uint16_t newData = HIGH_LOW_TO_INT(data[i + 1], data[i]);
    // Only program the data if it makes sense to do so.
    if (currentData == newData) {
      // If the new data matches the flash, don't bother doing anything.
    } else if (currentData == 0xFFFF || newData == 0x0000) {
      // If the flash is 0xFFFF we're allowed to write anything.
      // If the new data is 0x0000 it doesn't matter what the flash is.
      // OPTWREN must stay set to keep CIB unlocked.
      if ((CIB_OB_BOTTOM <= (uint32_t)ptr) && ((uint32_t)ptr <= CIB_OB_TOP)) {
        FLASH_CTRL = (FLASH_CTRL_OPTWREN | FLASH_CTRL_OPTPROG);
      } else {
        FLASH_CTRL = (FLASH_CTRL_OPTWREN | FLASH_CTRL_PROG);
      }
      // Assigning data to the address performs the actual write.
      (*ptr) = newData;
      // Wait for the busy bit to clear, indicating operation is done.
      while ((FLASH_STATUS & FLASH_STATUS_FLA_BSY) != 0) {}
      // Reset the operation complete flag.
      FLASH_STATUS = FLASH_STATUS_EOP;
      // Check if any error bits have been tripped, and if so, exit.
      // The bit PAGE_PROG_ERR is not relevant in this programming mode.
      if (FLASH_STATUS & (FLASH_STATUS_WRP_ERR | FLASH_STATUS_PROG_ERR)) {
        if (FLASH_STATUS & FLASH_STATUS_WRP_ERR) {
          status = FIB_ERR_WRITE_PROTECTED;
        } else {
          status = FIB_ERR_WRITE_FAILED;
        }
        FLASH_STATUS = FLASH_STATUS_WRP_ERR;
        FLASH_STATUS = FLASH_STATUS_PROG_ERR;
        break;
      }
    } else {
      status = FIB_ERR_ERASE_REQUIRED;
      break;
    }
    ptr++;
  }
  disableFlitf();
  return status;
}

static FibStatus fibFlashWriteVerify(uint32_t address, uint8_t *data, uint32_t length)
{
  uint32_t i;
  uint8_t *ptr = (uint8_t *)address;
  for (i = 0; i < length; i++) {
    if (*ptr != data[i]) {
      return FIB_ERR_VERIFY_FAILED;
    }
    ptr++;
  }
  return FIB_SUCCESS;
}

static FibStatus fibFlashErase(FibEraseType eraseType, uint32_t address)
{
  uint32_t eraseOp;
  uint32_t *ptr;
  uint32_t length;
  FibStatus status = FIB_SUCCESS;
  if (BYTE_0(eraseType) == MFB_MASS_ERASE) {
    eraseOp = FLASH_CTRL_MASSERASE;
    ptr = (uint32_t *)MFB_BOTTOM;
    length = MFB_SIZE_W;
  } else if (BYTE_0(eraseType) == MFB_PAGE_ERASE) {
    if (address < MFB_BOTTOM || address > MFB_TOP) {
      return FIB_ERR_INVALID_ADDRESS;
    }
    eraseOp = FLASH_CTRL_PAGEERASE;
    ptr = (uint32_t *)(address & MFB_PAGE_MASK_B);
    length = MFB_PAGE_SIZE_W;
  } else if (BYTE_0(eraseType) == CIB_ERASE) {
    eraseOp = FLASH_CTRL_OPTWREN | FLASH_CTRL_OPTERASE;
    ptr = (uint32_t *)CIB_BOTTOM;
    length = CIB_SIZE_W;
  } else {
    return FIB_ERR_INVALID_TYPE;
  }
  if ((eraseType & DO_ERASE) != 0) {
    enableFlitf();
    FLASH_CTRL = eraseOp;
    if (BYTE_0(eraseType) == MFB_PAGE_ERASE) {
      FLASH_ADDR = (address & MFB_PAGE_MASK_B);
    }
    eraseOp |= FLASH_CTRL_FLA_START;
    // Perform the actual erase.
    FLASH_CTRL = eraseOp;
    // Wait for the busy bit to clear, indicating operation is done.
    while ((FLASH_STATUS & FLASH_STATUS_FLA_BSY) != 0) {}
    // Reset the operation complete flag.
    FLASH_STATUS = FLASH_STATUS_EOP;
    // Check for errors; the only relevant one for erasing is write protection.
    if (FLASH_STATUS & FLASH_STATUS_WRP_ERR) {
      FLASH_STATUS = FLASH_STATUS_WRP_ERR;
      status = FIB_ERR_WRITE_PROTECTED;
    }
    disableFlitf();
  }
  if (status == FIB_SUCCESS
      && (eraseType & DO_VERIFY) != 0) {
    uint32_t i;
    for (i = 0; i < length; i++) {
      if (*ptr != 0xFFFFFFFF) {
        return FIB_ERR_VERIFY_FAILED;
      }
      ptr++;
    }
  }
  return status;
}
#endif // ST_EMU_TEST

static boolean verifyFib(void)
{
  // Ensure that a programmed FIB of a proper version is present
  return ( (halFixedAddressTable.baseTable.type == FIXED_ADDRESS_TABLE_TYPE) &&
           ( ( (halFixedAddressTable.baseTable.version & FAT_MAJOR_VERSION_MASK) 
               == 0x0000 ) &&
             (halFixedAddressTable.baseTable.version >= 0x0002) 
           )
         );
}

//The parameter 'eraseType' chooses which erasure will be performed while
//the 'address' parameter chooses the page to be erased during MFB page erase.
StStatus halInternalFlashErase(uint8_t eraseType, uint32_t address)
{
  FibStatus status;
  
  ATOMIC(
    BYPASS_MPU(
      flashEraseIsActive = TRUE;
      #if defined(ST_EMU_TEST)
        // Always try to use the FIB bootloader if its present
        if(verifyFib()) {
          status = halFixedAddressTable.fibFlashErase(
                                             (((uint32_t)eraseType) | DO_ERASE), 
                                             address);
        } else {
          status = fibFlashErase((((uint32_t)eraseType) | DO_ERASE), address);
        }
      #else



        assert(verifyFib());
        status = halFixedAddressTable.fibFlashErase(
                                           (((uint32_t)eraseType) | DO_ERASE), 
                                           address);
      #endif
    )
  )
  //If there are any interrupts pending that could have been delayed for 21ms,
  //they will be serviced here since we exit the ATOMIC block.  These ISRs
  //can query the flash library and find out that erasing is active.  After
  //this point, we're no longer ATOMIC/disrupting latency so our erase
  //active flag should be cleared.
  flashEraseIsActive = FALSE;
  
  if(status!=FIB_SUCCESS) {
    return fibToStStatus[status];
  }

  #if defined(ST_EMU_TEST)
    // Always try to use the FIB bootloader if its present
    if(verifyFib()) {
      status = halFixedAddressTable.fibFlashErase(
                                          (((uint32_t)eraseType) | DO_VERIFY), 
                                          address);
    } else {
      status = fibFlashErase((((uint32_t)eraseType) | DO_VERIFY), address);
    }
  #else
    status = halFixedAddressTable.fibFlashErase(
                                        (((uint32_t)eraseType) | DO_VERIFY), 
                                        address);
  #endif
  return fibToStStatus[status];
}


//The parameter 'address' defines the starting address of where the
//programming will occur - this parameter MUST be half-word aligned since all
//programming operations are HW.  The parameter 'data' is a pointer to a buffer
//containin the 16bit half-words to be written.  Length is the number of 16bit
//half-words contained in 'data' to be written to flash.
//NOTE: This function can NOT write the option bytes and will throw an error
//if that is attempted.
StStatus halInternalFlashWrite(uint32_t address, uint16_t * data, uint32_t length)
{
  FibStatus status;
    
  length = length * 2;  // fib routines specify length in bytes
  
  ATOMIC(
    BYPASS_MPU( 
      #if defined(ST_EMU_TEST)
        // Always try to use the FIB bootloader if its present
        if(verifyFib()) {
          status = halFixedAddressTable.fibFlashWrite(address, 
                                                      (uint8_t *)data, 
                                                      length,
                                                      0);
        } else {
          status = fibFlashWrite(address, (uint8_t *)data, length, 0);
        }
      #else



        // Ensure that a programmed FIB of a proper version is present
        assert(verifyFib());
        status = halFixedAddressTable.fibFlashWrite(address, 
                                                    (uint8_t *)data, 
                                                    length,
                                                    0);
      #endif
    )
  )
  
  if(status!=FIB_SUCCESS) {
    return fibToStStatus[status];
  }
  
  #if defined(ST_EMU_TEST)
    // Always try to use the FIB bootloader if its present
    if(verifyFib()) {
      status = halFixedAddressTable.fibFlashWrite(address,
                                                  (uint8_t *)data,
                                                  0,
                                                  length);
    } else {
      status = fibFlashWriteVerify(address, (uint8_t *)data, length);
    }
  #else
    status = halFixedAddressTable.fibFlashWrite(address,
                                                (uint8_t *)data,
                                                0,
                                                length);
  #endif
  
  return fibToStStatus[status];
}


//The parameter 'byte' is the option byte number to be programmed.  This
//parameter can have a value of 0 through 7.  'data' is the 8bit value to be
//programmed into the option byte since the hardware will calculate the
//compliment and program the full 16bit option byte.
StStatus halInternalCibOptionByteWrite(uint8_t byte, uint8_t data)
{
  uint16_t dataAndInverse = HIGH_LOW_TO_INT(~data, data);
  // There are only 8 option bytes, don't try to program more than that.
  if (byte > 7) {
    return ST_ERR_FLASH_PROG_FAIL;
  }
  return halInternalFlashWrite(CIB_OB_BOTTOM + (byte << 1), &dataAndInverse, 1);
}