/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    flash_if.c
  * @author  MCD Application Team
  * @brief   This file provides set of firmware functions to manage Flash
  *          Interface functionalities.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "flash_if.h"
#include "stm32_mem.h"

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* External variables ---------------------------------------------------------*/
/* USER CODE BEGIN EV */

/* USER CODE END EV */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/**
  * @brief Flash empty status enumeration
  */
enum
{
  FLASH_EMPTY      = 0,
  FLASH_NOT_EMPTY  = 1
};

/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
/**
  * @brief  This function writes a data buffer in flash (data are 64-bit aligned).
  * @note   After writing data buffer, the flash content is checked.
  * @param  pDestination: Start address for target location
  * @param  pSource: pointer on buffer with data to write
  * @param  uLength: Length of data buffer in byte. It has to be 64-bit aligned.
  * @retval HAL Status.
  */
static int32_t FLASH_IF_Write_Buffer(uint32_t pDestination, uint8_t *pSource, uint32_t uLength);

/**
  * @brief  This function checks if part of Flash is empty
            It handles 32b unaligned address
  * @param  addr: Start of user flash area
  * @param  size: number of bytes.
  * @retval FLASH_EMPTY or FLASH_NOT_EMPTY.
  */
static int32_t FLASH_IF_IsEmpty(uint8_t *addr, uint32_t size);

/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Exported functions --------------------------------------------------------*/
int32_t FLASH_IF_Write(uint32_t address, uint8_t *data, uint32_t size, uint8_t *dataTempPage)
{
  /* USER CODE BEGIN FLASH_IF_Write_1 */

  /* USER CODE END FLASH_IF_Write_1 */
  int32_t status = FLASH_OK;
  uint32_t page_start_index = PAGE(address);
  uint32_t page_end_index = PAGE(address + size - 1);
  uint32_t curr_size = size;
  uint32_t curr_dest_addr = address;
  uint32_t curr_src_addr = (uint32_t)data;

  if ((data == NULL) || ((size % sizeof(uint64_t)) != 0) || ((address % sizeof(uint64_t)) != 0))
  {
    return FLASH_PARAM_ERROR;
  }

  if (READ_BIT(FLASH->CR, FLASH_CR_LOCK) != 0U)
  {
    return FLASH_LOCK_ERROR;
  }

  if (page_start_index != page_end_index)
  {
    curr_size = FLASH_PAGE_SIZE - (address % FLASH_PAGE_SIZE);
  }

  for (uint32_t idx = page_start_index; idx <= page_end_index; idx++)
  {
    if (FLASH_IF_IsEmpty((uint8_t *)curr_dest_addr, curr_size) != FLASH_EMPTY)
    {
      if (dataTempPage == NULL)
      {
        return FLASH_PARAM_ERROR;
      }
      /* backup initial Flash page data in RAM area */
      UTIL_MEM_cpy_8(dataTempPage, (uint8_t *)(idx * FLASH_PAGE_SIZE + FLASH_BASE), FLASH_PAGE_SIZE);
      /* copy fragment into RAM area */
      UTIL_MEM_cpy_8(&dataTempPage[((uint32_t)curr_dest_addr) % FLASH_PAGE_SIZE], (uint8_t *)curr_src_addr, curr_size);

      /*  erase the Flash sector, to avoid writing twice in RAM */
      if (FLASH_IF_EraseByPages(idx, 1, 0) != FLASH_OK)
      {
        status = FLASH_ERASE_ERROR;
        break; /* exit for loop */
      }
      else
      {
        /* copy the whole flash sector including fragment from RAM to Flash*/
        if (FLASH_IF_Write_Buffer(idx * FLASH_PAGE_SIZE + FLASH_BASE, dataTempPage, FLASH_PAGE_SIZE) != FLASH_OK)
        {
          status = FLASH_WRITE_ERROR;
          break; /* exit for loop */
        }
      }
    }
    else
    {
      if (FLASH_IF_Write_Buffer(curr_dest_addr, (uint8_t *)curr_src_addr, curr_size) != FLASH_OK)
      {
        status = FLASH_WRITE_ERROR;
        break; /* exit for loop */
      }
    }

    /* 2nd part of memory overlapped on 2nd flash sector */
    curr_dest_addr += curr_size;
    curr_src_addr += curr_size;
    curr_size = size - curr_size;
  }

  return status;
  /* USER CODE BEGIN FLASH_IF_Write_2 */

  /* USER CODE END FLASH_IF_Write_2 */
}

int32_t FLASH_IF_Write64(uint32_t address, uint64_t data)
{
  /* USER CODE BEGIN FLASH_IF_Write64_1 */

  /* USER CODE END FLASH_IF_Write64_1 */
  while (*(uint64_t *)address != data)
  {
    while (LL_FLASH_IsActiveFlag_OperationSuspended());
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, data);
  }

  return FLASH_OK;
  /* USER CODE BEGIN FLASH_IF_Write64_2 */

  /* USER CODE END FLASH_IF_Write64_2 */
}

int32_t FLASH_IF_EraseByPages(uint32_t page, uint16_t n, int32_t interrupt)
{
  /* USER CODE BEGIN FLASH_IF_EraseByPages_1 */

  /* USER CODE END FLASH_IF_EraseByPages_1 */
  HAL_StatusTypeDef hal_status;
  FLASH_EraseInitTypeDef erase_str;
  uint32_t page_error;

  erase_str.TypeErase = FLASH_TYPEERASE_PAGES;
  erase_str.Page = page;
  erase_str.NbPages = n;

  /* Erase the Page */
  if (interrupt)
  {
    hal_status = HAL_FLASHEx_Erase_IT(&erase_str);
  }
  else
  {
    hal_status = HAL_FLASHEx_Erase(&erase_str, &page_error);
  }

  return ((hal_status == HAL_OK) ? FLASH_OK : ((hal_status == HAL_BUSY) ? FLASH_BUSY : FLASH_ERASE_ERROR));
  /* USER CODE BEGIN FLASH_IF_EraseByPages_2 */

  /* USER CODE END FLASH_IF_EraseByPages_2 */
}

void HAL_FLASH_EndOfOperationCallback(uint32_t ReturnValue)
{
  /* USER CODE BEGIN HAL_FLASH_EndOfOperationCallback_1 */

  /* USER CODE END HAL_FLASH_EndOfOperationCallback_1 */
  /* Call CleanUp callback when all requested pages have been erased */
  if (ReturnValue == 0xFFFFFFFFUL)
  {
    HWCB_FLASH_EndOfCleanup();
  }
  /* USER CODE BEGIN HAL_FLASH_EndOfOperationCallback_2 */

  /* USER CODE END HAL_FLASH_EndOfOperationCallback_2 */
}

void HAL_FLASH_OperationErrorCallback(uint32_t ReturnValue)
{
  /* USER CODE BEGIN HAL_FLASH_OperationErrorCallback_1 */

  /* USER CODE END HAL_FLASH_OperationErrorCallback_1 */
}

void HWCB_FLASH_EndOfCleanup(void)
{
  /* USER CODE BEGIN HWCB_FLASH_EndOfCleanup_1 */

  /* USER CODE END HWCB_FLASH_EndOfCleanup_1 */
}

/* USER CODE BEGIN EF */

/* USER CODE END EF */

/* Private Functions Definition -----------------------------------------------*/
static int32_t FLASH_IF_Write_Buffer(uint32_t pDestination, uint8_t *pSource, uint32_t uLength)
{
  /* USER CODE BEGIN FLASH_IF_Write_Buffer_1 */

  /* USER CODE END FLASH_IF_Write_Buffer_1 */
  uint8_t *pSrc = pSource;
  uint64_t src_value;
  int32_t status = FLASH_OK;

  for (uint32_t i = 0; i < (uLength / sizeof(uint64_t)); i++)
  {
    UTIL_MEM_cpy_8(&src_value, pSrc, sizeof(uint64_t));

    /* Avoid writing 0xFFFFFFFFFFFFFFFFLL on erased Flash */
    if (src_value != UINT64_MAX)
    {
      status = FLASH_IF_Write64(pDestination, src_value);
    }

    pDestination += sizeof(uint64_t);
    pSrc += sizeof(uint64_t);

    if (status != FLASH_OK)
    {
      /* exit the for loop*/
      break;
    }
  }

  return status;
  /* USER CODE BEGIN FLASH_IF_Write_Buffer_2 */

  /* USER CODE END FLASH_IF_Write_Buffer_2 */
}

static int32_t FLASH_IF_IsEmpty(uint8_t *addr, uint32_t size)
{
  /* USER CODE BEGIN FLASH_IF_IsEmpty_1 */

  /* USER CODE END FLASH_IF_IsEmpty_1 */
  uint64_t *addr64;
  uint32_t i;

  /* start memory NOT 64bits aligned */
  while ((((uint32_t)addr) % sizeof(uint64_t)) != 0)
  {
    if (*addr++ != UINT8_MAX)
    {
      return FLASH_NOT_EMPTY;
    }
    size--;
  }

  /* addr64 is 64 bits aligned */
  addr64 = (uint64_t *)addr;
  for (i = 0; i < (size / sizeof(uint64_t)); i++)
  {
    if (*addr64++ != UINT64_MAX)
    {
      return FLASH_NOT_EMPTY;
    }
  }
  size -= sizeof(uint64_t) * i;

  /* end memory NOT 64 bits aligned */
  addr = (uint8_t *)addr64;
  while (size != 0)
  {
    if (*addr++ != UINT8_MAX)
    {
      return FLASH_NOT_EMPTY;
    }
    size--;
  }
  return FLASH_EMPTY;
  /* USER CODE BEGIN FLASH_IF_IsEmpty_2 */

  /* USER CODE END FLASH_IF_IsEmpty_2 */
}

/* USER CODE BEGIN PrFD */

/* USER CODE END PrFD */