/****************************************************************************//**
  \file rf4ceFlash.c

  \brief Implementation function for RF4CE OTAU.

  \author
    Atmel Corporation: http://www.atmel.com \n
    Support email: avr@atmel.com

  Copyright (c) 2008-2015, Atmel Corporation. All rights reserved.
  Licensed under Atmel's Limited License Agreement (BitCloudTM).

  \internal
  History:
    12/01/11 A. Khromykh - Created
*******************************************************************************/
/******************************************************************************
                   Includes section
******************************************************************************/
#if defined (INCLUDE_RF4CE_FUNCTIONS)

#include <types.h>
#include <bootStructure.h>
#include <sp_driver.h>
#include <flashLoader.h>
#include <rf4ceFlash.h>
#include <moveIntVector.h>

/******************************************************************************
                   Define(s) section
******************************************************************************/
/** This macro saves the global interrupt status. */
#define ENTER_CRITICAL_REGION()         {uint8_t sreg = SREG; cli()
/** This macro restores the global interrupt status. */
#define LEAVE_CRITICAL_REGION()         SREG = sreg;}
#define EEPROM_BUSY_WAIT()              do{}while(EECR & (1 << EEPE))

/******************************************************************************
                   Types section
******************************************************************************/
typedef void (* AccessPoint_t)(void);

/******************************************************************************
                   Prototypes section
******************************************************************************/
#if defined(__ICCAVR__)
_Pragma("location=\"RF4CE_SECT\"") static void flash_fill_page_buffer(fill_page_buffer_req_t *param);
_Pragma("location=\"RF4CE_SECT\"") static void flash_program_page(program_page_req_t *param);
_Pragma("location=\"RF4CE_SECT\"") __noreturn static void flash_swap(swap_images_req_t *param);
_Pragma("location=\"RF4CE_SECT\"") static void flash_boot_request_handler(void);
_Pragma("location=\"RF4CE_SECT\"") static void flash_erase_page(erase_page_req_t *param);
_Pragma("location=\"RF4CE_SECT\"") static void flash_write_page(write_page_req_t *param);
#if defined(RFR2_SPM)
_Pragma("location=\"RF4CE_SECT\"") static void save_restore_interrupts(bool save, uint8_t *buffer_ptr);
#endif
#elif defined(__GNUC__)
static void flash_fill_page_buffer(fill_page_buffer_req_t *param) __attribute__((section(".rf4ce")));
static void flash_program_page(program_page_req_t *param) __attribute__((section(".rf4ce")));
static void flash_swap(swap_images_req_t *param) __attribute__((section(".rf4ce"))) __attribute__((__noreturn__));
static void flash_boot_request_handler(void) __attribute__((section(".rf4ce")));
static void flash_erase_page(erase_page_req_t *param) __attribute__((section(".rf4ce")));
static void flash_write_page(write_page_req_t *param) __attribute__((section(".rf4ce")));
#if defined(RFR2_SPM)
static inline void save_restore_interrupts(bool save, uint8_t *buffer_ptr) __attribute__((section(".rf4ce")));
#endif
#else
  #error 'Unsupported compiler.'
#endif

/******************************************************************************
                   Global variables section
******************************************************************************/
#if defined(__ICCAVR__)
__root const __farflash AccessPoint_t access_point @ (FLASHEND + 1ul - sizeof(AccessPoint_t)) = flash_boot_request_handler;
#elif defined(__GNUC__)
__attribute__((section(".access_sect"))) AccessPoint_t access_point = flash_boot_request_handler;
#endif
/******************************************************************************
                    Implementation section
******************************************************************************/
/**************************************************************************//**
\brief Enter point to bootloader function
******************************************************************************/
static void flash_boot_request_handler(void)
{
  rf4ce_boot_req_t *request_address;
  uint16_t temp_value;

  temp_value = GPIOR1;
  temp_value <<= 8;
  temp_value |= GPIOR0;
  request_address = (rf4ce_boot_req_t *)temp_value;

#if defined(RFR2_SPM)
  uint8_t interrupt_buffer[20];
  // Enable change of Interrupt Vectors
  ENTER_CRITICAL_REGION();
  // Store the interrupts of application section in local variable and disable all the interrupts
  save_restore_interrupts(true,interrupt_buffer);
  // Move Interrupt Vectors from application section to boot section
  moveIntVectToBootSection();
#endif
  
  switch (request_address->request_type)
  {
    case FILL_PAGE_BUFFER:
      flash_fill_page_buffer(&request_address->common_boot_req.fill_page_buffer_req);
      break;
    case PROGRAM_PAGE:
      flash_program_page(&request_address->common_boot_req.program_page_req);
      break;
    case SWAP_IMAGE:
      flash_swap(&request_address->common_boot_req.swap_images_req);
      break;
    case ERASE_PAGE:
      flash_erase_page(&request_address->common_boot_req.erase_page_req);
      break;
    case WRITE_PAGE:
      flash_write_page(&request_address->common_boot_req.write_page_req);
      break;
    default:
      break;
  }

#if defined(RFR2_SPM)
  // Move Interrupt Vectors from boot section to application section
  moveIntVectToAppSection();
  // Restore the applicaiton section interrupts
  save_restore_interrupts(false,interrupt_buffer);
  LEAVE_CRITICAL_REGION();
#endif
  
}

#if defined(RFR2_SPM)
static void save_restore_interrupts(bool save, uint8_t *buffer_ptr)
{
  if (save)
  {
    *(buffer_ptr++)   = EIMSK;  EIMSK  = 0;
    *(buffer_ptr++)   = PCICR;  PCICR  = 0;
    *(buffer_ptr++) = TIMSK0; TIMSK0 = 0;
    *(buffer_ptr++) = TIMSK1; TIMSK1 = 0;
    *(buffer_ptr++) = TIMSK2; TIMSK2 = 0;
    *(buffer_ptr++) = TIMSK3; TIMSK3 = 0;
    *(buffer_ptr++) = TIMSK4; TIMSK4 = 0;
    *(buffer_ptr++) = TIMSK5; TIMSK5 = 0;
    *(buffer_ptr++)  = IRQ_MASK;  IRQ_MASK  = 0;
    *(buffer_ptr++) = IRQ_MASK1; IRQ_MASK1 = 0;
    *(buffer_ptr++)    = SCIRQM;    SCIRQM    = 0;

    *(buffer_ptr++) = UCSR0B & ((1<<RXCIE0) | (1<<TXCIE0) | (1<<UDRIE0));
    UCSR0B &= ~((1<<RXCIE0) | (1<<TXCIE0) | (1<<UDRIE0));

    *(buffer_ptr++) = UCSR1B & ((1<<RXCIE1) | (1<<TXCIE1) | (1<<UDRIE1));
    UCSR1B &= ~((1<<RXCIE1) | (1<<TXCIE1) | (1<<UDRIE1));

    *(buffer_ptr++)   = SPCR & (1<<SPIE);
    SPCR   &= ~(1<<SPIE);

    *(buffer_ptr++)   = TWCR & (1<<TWIE);
    TWCR   &= ~((1<<TWIE) | (1<<TWINT));

    *(buffer_ptr++)   = EECR & (1<<EERIE);
    EECR   &= ~(1<<EERIE);

    *(buffer_ptr++)   = ACSR & (1<<ACIE);
    ACSR   &= ~((1<<ACIE) | (1<<ACI));
    
    *(buffer_ptr++) = ADCSRA & (1<<ADIE);
    ADCSRA &= ~((1<<ADIE) | (1<<ADIF));

    *(buffer_ptr++) = BATMON & (1<<BAT_LOW_EN);
    BATMON &= ~((1<<BAT_LOW_EN) | (1<<BAT_LOW));
    
    *(buffer_ptr) = AES_CTRL & (1<<AES_IM);
    AES_CTRL &= ~((1<<AES_IM) | (1<<AES_REQUEST));
  }
  else
  {
    EIMSK     = *(buffer_ptr++);
    PCICR     = *(buffer_ptr++);
    TIMSK0    = *(buffer_ptr++);
    TIMSK1    = *(buffer_ptr++);
    TIMSK2    = *(buffer_ptr++);
    TIMSK3    = *(buffer_ptr++);
    TIMSK4    = *(buffer_ptr++);
    TIMSK5    = *(buffer_ptr++);
    IRQ_MASK  = *(buffer_ptr++);
    IRQ_MASK1 = *(buffer_ptr++);
    SCIRQM    = *(buffer_ptr++);
    UCSR0B   |= *(buffer_ptr++);
    UCSR1B   |= *(buffer_ptr++);
    SPCR     |= *(buffer_ptr++);
    TWCR     |= *(buffer_ptr++);
    EECR     |= *(buffer_ptr++);
    ACSR     |= *(buffer_ptr++);
    ADCSRA   |= *(buffer_ptr++);
    BATMON   |= *(buffer_ptr++);
    AES_CTRL |= *(buffer_ptr);
  }
}
#endif

/**************************************************************************//**
\brief Fill flash buffer

\param[in] param - pointer to request parameters for page buffer filling.
******************************************************************************/
static void flash_fill_page_buffer(fill_page_buffer_req_t *param)
{
  uint16_t addr;

  for (addr = (uint16_t)param->start_offset; addr < param->length + (uint16_t)param->start_offset; addr += sizeof(uint16_t), param->data += sizeof(uint16_t))
  {
    // Perform word load.
    SP_LoadFlashWord(addr, *(uint16_t *)param->data);
    // Wait for SPM to finish.
    flashWaitForSPM();
  }
}

/**************************************************************************//**
\brief Store data from flash buffer to flash page

\param[in] param - pointer to request parameters for page programing.
******************************************************************************/
static void flash_program_page(program_page_req_t *param)
{
  bool sleepEnable = false;
#if !defined(RFR2_SPM)
  ENTER_CRITICAL_REGION();
#endif

#if defined(RFR2_SPM)
  // Check for NRWW section.
  // When erasing or writing a page located inside the NRWW section, the CPU is halted. This makes device to indefinite sleep. 
  // So, check is added to avoid indefinite sleep
  if (param->page_start_addr >= NRWW_FLASH_START_ADDRESS)
    sleepEnable = false;
  else
    sleepEnable = true;
#endif
  
  EEPROM_BUSY_WAIT();
  // Perform page erase.
  SP_EraseApplicationPage(param->page_start_addr);
  // Wait for SPM to finish.
  flashSPMBusyWaitOrInterrupt(sleepEnable);
  // Perform page write.
  SP_WriteApplicationPage(param->page_start_addr);
  // Wait for SPM to finish.
  flashSPMBusyWaitOrInterrupt(sleepEnable);
  // enable rww section
  SP_RWWSectionEnable();
  // Wait for SPM to finish.
  flashWaitForSPM();

#if !defined(RFR2_SPM)
  LEAVE_CRITICAL_REGION();
#endif
}

/**************************************************************************//**
\brief Swap flash data from additional area to general area.

\param[in] param - pointer to request parameters for image swapping.
******************************************************************************/
#ifndef DISABLE_FLASH_SWAP_RF4CE_FUNCTION
static void flash_swap(swap_images_req_t *param)
{
  uint16_t current_page_number = param->image_start_addr >> PAGE_NUMBER_SHIFT;
  uint16_t data_page_number = current_page_number;
  common_boot_req_t common_boot_req;
  uint32_t page_start_addr;
  uint16_t saving_page_number = 0;
  uint8_t  data_byte[sizeof(uint16_t)];

  cli();  // Disable global interrupts

  EEPROM_BUSY_WAIT();

  while (param->image_size)
  {
    data_page_number = param->image_start_addr >> PAGE_NUMBER_SHIFT;

    if (data_page_number != current_page_number)
    {
      /* Store data from flash buffer to flash page */
      common_boot_req.program_page_req.page_start_addr = (uint32_t)saving_page_number << PAGE_NUMBER_SHIFT;
      flash_program_page(&common_boot_req.program_page_req);
      current_page_number = data_page_number;
      saving_page_number++;
    }

    page_start_addr = param->image_start_addr & PAGE_POSITION_OFFSET_MASK;
    data_byte[0] = SP_ReadInternalFlash(param->image_start_addr);
    data_byte[1] = SP_ReadInternalFlash(param->image_start_addr + 1);
    /* Fill temporary page buffer */
    common_boot_req.fill_page_buffer_req.start_offset = page_start_addr;
    common_boot_req.fill_page_buffer_req.length = sizeof(data_byte);
    common_boot_req.fill_page_buffer_req.data = data_byte;
    flash_fill_page_buffer(&common_boot_req.fill_page_buffer_req);

    param->image_start_addr += sizeof(data_byte);
    param->image_size -= sizeof(data_byte);
  }

  /* flush last page into flash */
  common_boot_req.program_page_req.page_start_addr = (uint32_t)saving_page_number << PAGE_NUMBER_SHIFT;
  flash_program_page(&common_boot_req.program_page_req);

  /* Start wdt on 16 ms and reset mcu */
  wdt_reset();
  WDTCSR |= (1 << WDCE) | (1 << WDE);
  WDTCSR = (1 << WDE);
  while(1);
}
#else
static void flash_swap(swap_images_req_t *param)
{
  (void)param;
}
#endif // DISABLE_FLASH_SWAP_RF4CE_FUNCTION

static void flash_erase_page(erase_page_req_t *param)
{
    bool sleepEnable = false;
#if !defined(RFR2_SPM)
    ENTER_CRITICAL_REGION();
#endif

#if defined(RFR2_SPM)
  // Check for NRWW section.
  // When erasing or writing a page located inside the NRWW section, the CPU is halted. This makes device to indefinite sleep. 
  // So, check is added to avoid indefinite sleep
  if (param->page_start_addr >= NRWW_FLASH_START_ADDRESS)
    sleepEnable = false;
  else
    sleepEnable = true;
#endif 

    EEPROM_BUSY_WAIT();

    SP_EraseApplicationPage(param->page_start_addr);
    flashSPMBusyWaitOrInterrupt(sleepEnable); // Wait until the memory is erased.

    // Reenable RWW-section again. We need this if we want to jump back
    // to the application after bootloading.
    SP_RWWSectionEnable();
    flashWaitForSPM();

#if !defined(RFR2_SPM)
    LEAVE_CRITICAL_REGION();
#endif
}

static void flash_write_page(write_page_req_t *param)
{
    bool sleepEnable = false;
#if !defined(RFR2_SPM)
    ENTER_CRITICAL_REGION();
#endif
    
#if defined(RFR2_SPM)
  // Check for NRWW section.
  // When erasing or writing a page located inside the NRWW section, the CPU is halted. This makes device to indefinite sleep. 
  // So, check is added to avoid indefinite sleep
  if (param->page_start_addr >= NRWW_FLASH_START_ADDRESS)
    sleepEnable = false;
  else
    sleepEnable = true;
#endif

    EEPROM_BUSY_WAIT();

    SP_WriteApplicationPage(param->page_start_addr);  // Store buffer in flash page.
    
    flashSPMBusyWaitOrInterrupt(sleepEnable); // Wait until the memory is written.

    // Reenable RWW-section again. We need this if we want to jump back
    // to the application after bootloading.
    SP_RWWSectionEnable();
    flashWaitForSPM();

#if !defined(RFR2_SPM)
    LEAVE_CRITICAL_REGION();
#endif
}

#endif // defined (INCLUDE_RF4CE_FUNCTIONS)

// eof rf4ceFlash.c
