/**
  ******************************************************************************
  * @file    flash_interface.c
  * @author  MCD Application Team
  * @brief   Contains FLASH access functions
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2023 Puya Semiconductor Co.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by Puya under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "platform.h"
#include "openbl_mem.h"
#include "openbootloader.h"
#include "common_interface.h"
#include "flash_interface.h"
#include "iwdg_interface.h"
#include "wwdg_interface.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
static void OPENBL_FLASH_ProgramPage(uint32_t adr, uint32_t sz, uint8_t *buf);

/* Exported variables --------------------------------------------------------*/
OPENBL_MemoryTypeDef FLASH_Descriptor =
{
  .StartAddress = FLASH_START_ADDRESS,
  .EndAddress = MAX_FLASH_END_ADDRESS,
  .Size = MAX_FLASH_END_ADDRESS - FLASH_START_ADDRESS,
  .Type = AREA_FLASH,
  .Read = OPENBL_FLASH_Read,
  .Write = OPENBL_FLASH_Write,
  .EraseChip = OPENBL_FLASH_EraseChip,
  .EraseBank = OPENBL_FLASH_EraseBank,
  .EraseBlock = OPENBL_FLASH_EraseBlock,
  .EraseSector = OPENBL_FLASH_EraseSector,
  .ErasePage = OPENBL_FLASH_ErasePage,
  .JumpToAddress = OPENBL_FLASH_JumpToAddress,
};

/* Exported functions --------------------------------------------------------*/

/**
  * @brief  Unlock the FLASH control register access.
  * @retval None.
  */
void OPENBL_FLASH_Unlock(void)
{
  FLASH->KEYR = FLASH_KEY1;
  FLASH->KEYR = FLASH_KEY2;
}

/**
  * @brief  Lock the FLASH control register access.
  * @retval None.
  */
void OPENBL_FLASH_Lock(void)
{
  SET_BIT(FLASH->CR, FLASH_CR_LOCK);
}

void OPENBL_FLASH_Read(uint32_t Address, uint8_t *Data, uint32_t DataLength)
{
  for(uint32_t i = 0; i < DataLength; i++)
  {
    *Data++ = (*(uint8_t *)(Address++));
  }
}

/**
  * @brief  This function is used to write data in FLASH memory.
  * @param  Address The address where that data will be written.
  * @param  Data The data to be written.
  * @param  DataLength The length of the data to be written.
  * @retval None.
  */
void OPENBL_FLASH_Write(uint32_t Address, uint8_t *Data, uint32_t DataLength)
{
  for (uint32_t i = 0U; i < DataLength; i += FLASH_PAGE_SIZE)
  {
    OPENBL_FLASH_ProgramPage(Address + i, FLASH_PAGE_SIZE, Data + i);
  }
}

/**
  * @brief  This function is used to jump to a given address.
  * @param  Address The address where the function will jump.
  * @retval None.
  */
void OPENBL_FLASH_JumpToAddress(uint32_t Address)
{
  Function_Pointer jump_to_address;

  /* Deinitialize all HW resources used by the Bootloader to their reset values */
  OpenBootloader_DeInit();

  /* Enable IRQ */
  Common_EnableIrq();

  jump_to_address = (Function_Pointer)(*(__IO uint32_t *)(Address + 4U));

  /* Initialize user application's stack pointer */
  Common_SetMsp(*(__IO uint32_t *) Address);

  jump_to_address();
}

static ErrorStatus OPENBL_FLASH_Erase(uint32_t Address, uint32_t EraseReg)
{
  OPENBL_WWDG_Refresh();
  OPENBL_IWDG_Refresh();

  /* Clear FLASH_SR_EOP flags */
  SET_BIT(FLASH->SR, FLASH_SR_EOP);

  SET_BIT(FLASH->CR, EraseReg | FLASH_CR_EOPIE);

  M32(Address) = 0xFFFFFFFF;

  while (FLASH->SR & (FLASH_SR_BSY0 | FLASH_SR_BSY1))
  {
    OPENBL_WWDG_Refresh();
    OPENBL_IWDG_Refresh();
  }

  CLEAR_BIT(FLASH->CR, EraseReg | FLASH_CR_EOPIE);

  if (READ_BIT(FLASH->SR, FLASH_SR_EOP) == FLASH_SR_EOP)
  {
    return SUCCESS;
  }
  else
  {
    return ERROR;
  }
}

ErrorStatus OPENBL_FLASH_EraseChip(void)
{
  uint32_t banksize = (((M32(0x1FFF7D00) & 0x3) + 1) * 128 * 1024) / 2;
  
  if( OPENBL_FLASH_EraseBank(FLASH_START_ADDRESS) != SUCCESS)
  {
    return ERROR;
  }

  if( OPENBL_FLASH_EraseBank(FLASH_START_ADDRESS + banksize) != SUCCESS)
  {
    return ERROR;
  }

  return SUCCESS;
}

ErrorStatus OPENBL_FLASH_EraseBank(uint32_t Address)
{
  uint32_t mer;
  uint32_t banksize = (((M32(0x1FFF7D00) & 0x3) + 1) * 128 * 1024) / 2;
  uint32_t midaddr = FLASH_START_ADDRESS + banksize;

  if(Address < midaddr)
  {
    if(READ_BIT(FLASH->OPTR1, FLASH_OPTR1_BFB) != FLASH_OPTR1_BFB)
    {
      mer = FLASH_CR_MER0;
    }
    else
    {
      mer = FLASH_CR_MER1;
    }
  }
  else
  {
    if(READ_BIT(FLASH->OPTR1, FLASH_OPTR1_BFB) != FLASH_OPTR1_BFB)
    {
      mer = FLASH_CR_MER1;
    }
    else
    {
      mer = FLASH_CR_MER0;
    }
  }

  return OPENBL_FLASH_Erase(Address, mer);
}

ErrorStatus OPENBL_FLASH_EraseBlock(uint32_t Address)
{
  return OPENBL_FLASH_Erase(Address, FLASH_CR_BER);
}

ErrorStatus OPENBL_FLASH_EraseSector(uint32_t Address)
{
  return OPENBL_FLASH_Erase(Address, FLASH_CR_SER);
}

ErrorStatus OPENBL_FLASH_ErasePage(uint32_t Address)
{
  return OPENBL_FLASH_Erase(Address, FLASH_CR_PER);
}

/* Private functions ---------------------------------------------------------*/

/*
 *  Program Page in Flash Memory
 *    Parameter:      adr:  Page Start Address
 *                    sz:   Page Size
 *                    buf:  Page Data
 */
static void OPENBL_FLASH_ProgramPage(uint32_t adr, uint32_t sz, uint8_t *buf)
{
  SET_BIT(FLASH->SR, FLASH_SR_EOP);         // Clear EOP flag

  SET_BIT(FLASH->CR, FLASH_CR_PG | FLASH_CR_EOPIE);          // Programming Enabled

  for (uint32_t i = 0; i < sz; i += 4)
  {

    M32(adr + i) = M32(buf + i);   // Program the first word of the Double Word

    if (i == sz - 8)
    {
      FLASH->CR  |= FLASH_CR_PGSTRT;
    }
  }

  while (FLASH->SR & (FLASH_SR_BSY0 | FLASH_SR_BSY1))
  {
    OPENBL_WWDG_Refresh();
    OPENBL_IWDG_Refresh();
  }

  CLEAR_BIT(FLASH->CR, FLASH_CR_PG | FLASH_CR_EOPIE);   // Programming Disabled
}


