/****************************************************************************//**
  \file flashLoader.c

  \brief Implementation of internal flash loader.

  \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:
    20/12/13 Agasthian - Created
*******************************************************************************/
/******************************************************************************
                   Includes section
******************************************************************************/
#include <types.h>
#include <bootStructure.h>
#include <atsamr21.h>
#include <core_cm0plus.h>
#include <flashLoader.h>

/******************************************************************************
                   Define(s) section
******************************************************************************/
#define FLASH_PAGE_SIZE               256u
#define PAGE_NUMBER_SHIFT               8
#define PAGE_POSITION_OFFSET_MASK   0x000000FF

#define FLASH_READY() (NVMCTRL_INTFLAG_s.ready)

typedef enum flash_cmd_tag {
  ERASE_ROW = 0x02,
  WRITE_PAGE = 0x04,
  ERASE_AUXILARYROW = 0x05,
  WRITE_AUXILARYPAGE = 0x06,
  LOCK_REGION = 0x40,
  UNLOCK_REGION = 0x41,
  SET_POWERREDUCTIONMODE = 0x42,
  CLEAR_POWERREDUCTIONMODE = 0x43,
  PAGE_BUFFERCLEAR = 0x44,
  SET_SECURITYBIT = 0x45,
  INVALID_ALLCACHE = 0x46
 } flash_cmd_t;

#define FIRST_PAGE             0
#define LAST_PAGE            1023

/******************************************************************************
                   Types section
******************************************************************************/
typedef union
{
  uint8_t  byteData[2];
  uint16_t wordData;
} FlashWord_t;

/******************************************************************************
                   Prototypes section
******************************************************************************/
static void hwFillFlashPageBuffer(uint32_t startOffset, uint16_t length, uint8_t *data);
static void hwEraseFlashPage(uint32_t pageStartAddress);
static void hwWriteFlashPage(uint32_t pageStartAddress);
static void hwExecuteCommand(flash_cmd_t cmd);
static void flashUpdateAppStartAddr(void);

/******************************************************************************
                   External global variables section
******************************************************************************/
extern BootBuffer_t dataBuffer;

/******************************************************************************
                   Global variables section
******************************************************************************/
static uint16_t currentPage = 0;
static uint8_t temporaryPageBuffer[FLASH_PAGE_SIZE];
static uint32_t appStartAddr;
static uint16_t pageBufferToFill[ROW_SIZE_IN_HALF_WORDS];

/******************************************************************************
                    Implementation section
******************************************************************************/
/**************************************************************************//**
\brief Load received date to the flash.
******************************************************************************/
void flashPacketToPage(void)
{
  uint16_t packetPage;
  uint16_t tableAddress;
  uint8_t *dataPointer;

  dataPointer = dataBuffer.data;
 
  while (dataBuffer.dataSize)
  {
    packetPage = dataBuffer.address / FLASH_PAGE_SIZE;

    if ( 0 == currentPage)
    {
      // first data of the first page of application image
      currentPage = dataBuffer.address / FLASH_PAGE_SIZE;
      appStartAddr = dataBuffer.address;
    }
    if (currentPage != packetPage)
    {
      hwEraseFlashPage(currentPage *FLASH_PAGE_SIZE);
      hwFillFlashPageBuffer(currentPage *FLASH_PAGE_SIZE, FLASH_PAGE_SIZE, &temporaryPageBuffer[0]);
      hwWriteFlashPage(currentPage *FLASH_PAGE_SIZE);
      memset(&temporaryPageBuffer[0], 0xFF, FLASH_PAGE_SIZE);
      currentPage = packetPage;
    }

    tableAddress = dataBuffer.address & PAGE_POSITION_OFFSET_MASK;
    temporaryPageBuffer[tableAddress] = *dataPointer++;
    dataBuffer.address++;
    dataBuffer.dataSize--;
  }
}

/**************************************************************************//**
\brief Flush unloaded data before exit from bootloader.
******************************************************************************/
void flashFlushPage(void)
{
  // Do a Erase-Write of the page.
  hwEraseFlashPage(currentPage *FLASH_PAGE_SIZE);
  hwFillFlashPageBuffer(currentPage *FLASH_PAGE_SIZE, FLASH_PAGE_SIZE, &temporaryPageBuffer[0]);
  hwWriteFlashPage(currentPage *FLASH_PAGE_SIZE);
  flashUpdateAppStartAddr();
}
/**************************************************************************//**
  \brief Writes data to the page buffer.
  \param[in] startOffset - start position for writing within the page
  \param[in] length - data to write length in bytes
  \param[in] data - pointer to data to write
******************************************************************************/
static void hwFillFlashPageBuffer(uint32_t startOffset, uint16_t length, uint8_t *data)
{
  // Page size is 64 Bytes so that Row size becomes 4*64 = 256 Bytes
  // max. value of length may be 256 Bytes
  uint16_t unalignedWord;
  uint16_t alignedWord;
  uint8_t wordPos;
  
  if (startOffset % ROW_SIZE + length > ROW_SIZE)
  {
    // leaking into next row & abort the file operation
    return;
  }
  
  // Initialize the page buffer
  for (uint16_t index = 0; index < ROW_SIZE_IN_HALF_WORDS; index++)
    pageBufferToFill[index] = 0xFFFF;
  // Check, if first byte of current data is unaligned
  if (startOffset % 2)
  {
    /* Fill it as a MSB of a word */
    unalignedWord = 0x00FF | ((uint16_t)*data++ << 8);
    wordPos = (startOffset % ROW_SIZE - 1) / 2;
    pageBufferToFill[wordPos] = unalignedWord;
    startOffset++;
    length--;
  }

  // Handle if the last byte is not aligned
  if (length % 2)
  {
    unalignedWord = 0xFF00 | data[length - 1];
    wordPos = (startOffset % ROW_SIZE + length - 1) / 2 ;
    pageBufferToFill[wordPos] = unalignedWord;
    length--;
  }
  
  // Copy the remaining aligneed bytes to page buffer
  for (uint8_t index =0; index < length/2; index++)
  {
    wordPos = startOffset % ROW_SIZE / 2;
    alignedWord = *data++;
    alignedWord = alignedWord | (*data++ << 8);
    pageBufferToFill[wordPos] = alignedWord;
    startOffset += 2;
  }
}

/**************************************************************************//**
  \brief Erases specified flash page.
  \param[in] pageStartAddress - address within the target page
******************************************************************************/
static void hwEraseFlashPage(uint32_t pageStartAddress)
{
  if (pageStartAddress % 2)
    pageStartAddress--;
  /* Loading ADDR register */
  *(uint16_t *)(FLASH_MAIN_ADDRESS_SPACE + pageStartAddress) = 0xFFFF;
  hwExecuteCommand(ERASE_ROW);
}

/**************************************************************************//**
  \brief Writes flash page buffer without erasing.
  \param[in] pageStartAddress - address within the target page
******************************************************************************/
static void hwWriteFlashPage(uint32_t pageStartAddress)
{
  /* PDS page is mapped to row. so finding out the starting of row */
  if (pageStartAddress % ROW_SIZE)
    pageStartAddress = pageStartAddress - (pageStartAddress % ROW_SIZE);

  for (uint8_t pageCnt = 0; pageCnt < NO_OF_PAGES_PER_ROW; pageCnt++)
  {
    for (uint8_t wordCnt = 0; wordCnt < NO_OF_HALF_WORDS_PER_PAGE; wordCnt++)
      *(uint16_t *)(FLASH_MAIN_ADDRESS_SPACE + pageStartAddress + wordCnt * 2) = pageBufferToFill[pageCnt * NO_OF_HALF_WORDS_PER_PAGE + wordCnt];

    hwExecuteCommand(WRITE_PAGE);
    pageStartAddress += MCU_PAGE_SIZE;
  }
}

/**************************************************************************//**
  \brief Executes the specified command.
  \param[in] cmd - flash command
******************************************************************************/
static void hwExecuteCommand(flash_cmd_t cmd)
{
  while (!FLASH_READY()); 
  NVMCTRL_CTRLA = NVMCTRL_CTRLA_CMDEX(CMD_KEY)| NVMCTRL_CTRLA_CMD(cmd);
}

/**************************************************************************//**
\brief Updates application start address in end of flash
******************************************************************************/
static void flashUpdateAppStartAddr(void)
{
  uint32_t tempAddr;
  uint8_t tempPageData[FLASH_PAGE_SIZE];
  uint16_t PageNo;

  tempAddr = FLASH_END_ADDRESS - FLASH_PAGE_SIZE + 1;
  memcpy_P(&tempPageData[0], (void *)tempAddr, FLASH_PAGE_SIZE);
  memcpy(&tempPageData[FLASH_PAGE_SIZE-4], &appStartAddr, sizeof(uint32_t));

  PageNo = tempAddr / FLASH_PAGE_SIZE;
  hwEraseFlashPage(PageNo*FLASH_PAGE_SIZE);
  hwFillFlashPageBuffer(PageNo*FLASH_PAGE_SIZE, FLASH_PAGE_SIZE, &tempPageData[0]);
  hwWriteFlashPage(PageNo*FLASH_PAGE_SIZE);
  
}
/**************************************************************************//**
\brief Writes a page to the EEPROM.

\param[in]
  address - address of data byte.
\param[in]
  data - data for saving.
******************************************************************************/
 void hwFlashWriteEepromPage(HW_EepromParams_t *eeprom)
{
  uint16_t bytesToWrite;
  uint16_t pageNo;
  uint8_t pageBufferEeprom[ROW_SIZE];

  pageNo = eeprom->address / ROW_SIZE;

  bytesToWrite = ROW_SIZE - (eeprom->address % ROW_SIZE);
  memcpy_P(&pageBufferEeprom[0], (void *)(pageNo * ROW_SIZE), ROW_SIZE);
  
  if (eeprom->length < bytesToWrite)
    bytesToWrite = eeprom->length;

  memcpy(&pageBufferEeprom[eeprom->address % ROW_SIZE], eeprom->data, bytesToWrite);

  hwEraseFlashPage(pageNo*ROW_SIZE);
  hwFillFlashPageBuffer(pageNo*ROW_SIZE, ROW_SIZE, &pageBufferEeprom[0]);
  hwWriteFlashPage(pageNo*ROW_SIZE);
  
  eeprom->address += bytesToWrite;
  eeprom->length -= bytesToWrite;
  eeprom->data += bytesToWrite;
}
// eof flashLoader.c
