/***
 *** internal Pointer to the DATAFLASH MEMORY region start address
 ***/
#define NVM_MEMORY ((volatile uint32_t *)FLASH_ADDR)

void secure_dataflash_init(void)
{
 /*** Enable security features of Dataflash 
  *** Data Scrambling (key = 0x1234)
  *** Silent Access
  *** Tamper detection
  ***/
 NVMCTRL_SEC->DSCC.reg = NVMCTRL_DSCC_DSCKEY(0x1234);
 NVMCTRL_SEC->SECCTRL.reg = (NVMCTRL_SECCTRL_KEY_KEY|NVMCTRL_SECCTRL_DSCEN|NVMCTRL_SECCTRL_TEROW(0)|NVMCTRL_SECCTRL_SILACC| NVMCTRL_SECCTRL_TAMPEEN);
}

/***
 *** brief Execute a command on the NVM controller
 ***/
uint8_t _nvmc_exec_cmd(uint32_t cmd, uint32_t dst_addr)
{
 /*** Wait until NVMCTRL is ready ***/
 while (!(NVMCTRL_SEC->STATUS.reg & NVMCTRL_STATUS_READY));

 /*** Clear NVMCTRL flags ***/
 NVMCTRL_SEC->INTFLAG.reg = NVMCTRL_INTFLAG_MASK;

 /*** Set address if cmd requires it ***/
 if ((cmd == NVMCTRL_CTRLA_CMD_ER_Val) || (cmd == NVMCTRL_CTRLA_CMD_WP_Val)) 
 {
     NVMCTRL_SEC->ADDR.reg = (NVMCTRL_ADDR_ARRAY(0x01)| NVMCTRL_ADDR_AOFFSET(dst_addr)); 
 }

 /*** Set command ***/
 NVMCTRL_SEC->CTRLA.reg = (NVMCTRL_CTRLA_CMD(cmd)|NVMCTRL_CTRLA_CMDEX_KEY);

 /*** Wait until the command is performed and check errors ***/
 while (!(NVMCTRL_SEC->INTFLAG.reg & NVMCTRL_INTFLAG_DONE))
 {
     /*** Check if there is error in NVM erase operation ***/
     if (NVMCTRL_SEC->INTFLAG.reg & (NVMCTRL_INTFLAG_LOCKE | NVMCTRL_INTFLAG_NVME | NVMCTRL_INTFLAG_PROGE | NVMCTRL_INTFLAG_KEYE)) 
     {
         return 1;
     }
 }

 return 0;
}

/***
 *** brief Erase a row in NVM memory
 ***/
int32_t secure_dataflash_erase_row(const uint32_t dst_addr)
{
 return _nvmc_exec_cmd(NVMCTRL_CTRLA_CMD_ER_Val, dst_addr);
}

/***
 *** brief Write a page buffer in NVM memory
 ***/
uint32_t secure_dataflash_write_page(const uint32_t dst_addr, const uint32_t *buffer, const uint16_t length)
{
 uint16_t i;

 /*** Check if the write address is aligned to the start of a page ***/
 if (dst_addr & (NVMCTRL_PAGE_SIZE - 1)) {
     return 1;
 }

 /*** Check if the write length is longer than an NVM page ***/
 if (length > NVMCTRL_PAGE_SIZE/4) {
     return 1;
 }

 /*** Erase the page buffer before buffering new data ***/
 _nvmc_exec_cmd(NVMCTRL_CTRLA_CMD_PBC_Val, dst_addr);

 uint32_t nvm_addr = dst_addr / 2;

 /*** NVM must be accessed as a series of 32-bit words, 
  *** perform manual copy
  *** to ensure alignment 
    ***/
 for (i = 0; i < length; i++) 
 {
     NVM_MEMORY[nvm_addr++] = buffer[i];
 }

 /*** Execute NVM write page command ***/
 if ((NVMCTRL_SEC->CTRLC.reg & NVMCTRL_CTRLC_MANW)|| (length < NVMCTRL_PAGE_SIZE)) {
     return _nvmc_exec_cmd(NVMCTRL_CTRLA_CMD_WP_Val, dst_addr);
 }

 return 0;
}

/***
 *** brief Read a number of bytes from a page in the NVM memory
 ***/
int32_t secure_dataflash_read(uint32_t src_addr, uint32_t *buffer, uint32_t length)
{
 uint32_t i ;
 uint32_t* pAddr = src_addr;

 /*** Wait until NVMCTRL is ready ***/
 while (!(NVMCTRL_SEC->STATUS.reg & NVMCTRL_STATUS_READY));

 while (i < length) {
     buffer[i++]      = *(pAddr) ;
     pAddr ++ ;
 }

 return 0;
}