/**
  ******************************************************************************
  * @file    optionbytes_interface.c
  * @author  MCD Application Team
  * @brief   Contains Option Bytes 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 "optionbytes_interface.h"
#include "iwdg_interface.h"
#include "wwdg_interface.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define OPTR1_BASE              (OB_BASE + 0x00000000UL)
#define OPTR2_BASE              (OB_BASE + 0x00000004UL)
#define WRPR_BASE               (OB_BASE + 0x0000000CUL)
#define PCROP0SR_BASE           (OB_BASE + 0x00000010UL)
#define PCROP0ER_BASE           (OB_BASE + 0x00000014UL)
#define PCROP1SR_BASE           (OB_BASE + 0x00000018UL)
#define PCROP1ER_BASE           (OB_BASE + 0x0000001CUL)
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
const uint32_t DefaultOption[] =
{
  0x0955F6AA, 0x80FF7F00, 0xFFFFFFFF, 0xFF0000FF,
  0xFE0001FF, 0xFFFF0000, 0xFE0001FF, 0xFFFF0000,
};
/* Private function prototypes -----------------------------------------------*/
/* Exported variables --------------------------------------------------------*/
OPENBL_MemoryTypeDef OB_Descriptor =
{
  .StartAddress = OB_START_ADDRESS,
  .EndAddress = OB_END_ADDRESS,
  .Size = OB_END_ADDRESS - OB_START_ADDRESS,
  .Type = AREA_OB,
  .Read = OPENBL_OB_Read,
  .Write = OPENBL_OB_Write,
  .EraseChip = NULL,
  .EraseBank = NULL,
  .EraseBlock = NULL,
  .EraseSector = NULL,
  .ErasePage = NULL,
  .JumpToAddress = NULL,
};


/* Exported functions --------------------------------------------------------*/
/**
  * @brief  Unlock the FLASH Option Bytes Registers access.
  * @retval None.
  */
void OPENBL_OB_Unlock(void)
{
  FLASH->OPTKEYR = FLASH_OPTKEY1;
  FLASH->OPTKEYR = FLASH_OPTKEY2;
}

/**
  * @brief  Lock the FLASH Option Bytes Register access.
  * @retval None.
  */
void OPENBL_OB_Lock(void)
{
  SET_BIT(FLASH->CR, FLASH_CR_OPTLOCK);
}

/**
  * @brief  Launch the option byte loading.
  * @retval None.
  */
void OPENBL_OB_Launch(void)
{
  SET_BIT(FLASH->CR, FLASH_CR_OBL_LAUNCH);
}

void OPENBL_OB_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 Option bytes.
  * @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_OB_Write(uint32_t Address, uint8_t *Data, uint32_t DataLength)
{
  uint32_t dwOPTR1    = FLASH->OPTR1;
  uint32_t dwOPTR2    = FLASH->OPTR2;
  uint32_t dwWRPR     = FLASH->WRPR;
  uint32_t dwPCROP0SR = FLASH->PCROP0SR;
  uint32_t dwPCROP0ER = FLASH->PCROP0ER;
  uint32_t dwPCROP1SR = FLASH->PCROP1SR;
  uint32_t dwPCROP1ER = FLASH->PCROP1ER;

  /* Unlock the FLASH & Option Bytes Registers access */
  OPENBL_OB_Unlock();

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

  for (uint32_t i = 0; i < DataLength + 1; i++)
  {
    //OPTR1
    if ((OB_BASE + 0) == (Address + i))
    {
      dwOPTR1 &= ~(0x000000FF << 0);
      dwOPTR1 |= (Data[i] << 0);
    }
    else if ((OPTR1_BASE + 1) == (Address + i))
    {
      dwOPTR1 &= ~(0x000000FF << 8);
      dwOPTR1 |= (Data[i] << 8);
    }

    //OPTR2
    if ((OPTR2_BASE + 0) == (Address + i))
    {
      dwOPTR2 &= ~(0x000000FF << 0);
      dwOPTR2 |= (Data[i] << 0);
    }
    else if ((OPTR2_BASE + 1) == (Address + i))
    {
      dwOPTR2 &= ~(0x000000FF << 8);
      dwOPTR2 |= (Data[i] << 8);
    }

    //WRPR
    if ((WRPR_BASE + 0) == (Address + i)) //WRP[7:0]
    {
      dwWRPR &= ~(0x000000FF << 0);
      dwWRPR |= (Data[i] << 0);
    }
    else if ((WRPR_BASE + 1) == (Address + i)) //WRP[15:8]
    {
      dwWRPR &= ~(0x000000FF << 8);
      dwWRPR |= (Data[i] << 8);
    }

    //PCROP0SR
    if ((PCROP0SR_BASE + 0) == (Address + i))
    {
      dwPCROP0SR &= ~(0x000000FF << 0);
      dwPCROP0SR |= (Data[i] << 0);
    }
    else if ((PCROP0SR_BASE + 1) == (Address + i))
    {
      dwPCROP0SR &= ~(0x000000FF << 8);
      dwPCROP0SR |= (Data[i] << 8);
    }

    //PCROP0ER
    if ((PCROP0ER_BASE + 0) == (Address + i))
    {
      dwPCROP0ER &= ~(0x000000FF << 0);
      dwPCROP0ER |= (Data[i] << 0);
    }
    else if ((PCROP0ER_BASE + 1) == (Address + i))
    {
      dwPCROP0ER &= ~(0x000000FF << 8);
      dwPCROP0ER |= (Data[i] << 8);
    }

    //PCROP1SR
    if ((PCROP1SR_BASE + 0) == (Address + i))
    {
      dwPCROP1SR &= ~(0x000000FF << 0);
      dwPCROP1SR |= (Data[i] << 0);
    }
    else if ((PCROP1SR_BASE + 1) == (Address + i))
    {
      dwPCROP1SR &= ~(0x000000FF << 8);
      dwPCROP1SR |= (Data[i] << 8);
    }

    //PCROP1ER
    if ((PCROP1ER_BASE + 0) == (Address + i))
    {
      dwPCROP1ER &= ~(0x000000FF << 0);
      dwPCROP1ER |= (Data[i] << 0);
    }
    else if ((PCROP1ER_BASE + 1) == (Address + i))
    {
      dwPCROP1ER &= ~(0x000000FF << 8);
      dwPCROP1ER |= (Data[i] << 8);
    }
  }

  FLASH->OPTR1 = dwOPTR1;
  FLASH->OPTR2 = dwOPTR2;
  FLASH->WRPR = dwWRPR;
  FLASH->PCROP0SR = dwPCROP0SR;
  FLASH->PCROP0ER = dwPCROP0ER;
  FLASH->PCROP1SR = dwPCROP1SR;
  FLASH->PCROP1ER = dwPCROP1ER;

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

  M32(0x1FFF7400) = 0xFFFFFFFF;

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

  SET_BIT(FLASH->SR, FLASH_SR_EOP);
  CLEAR_BIT(FLASH->CR, (FLASH_CR_EOPIE | FLASH_CR_OPTSTRT));

  OPENBL_OB_Lock();
}

/**
  * @brief  Return the FLASH Read Protection level.
  * @retval The return value can be one of the following values:
  *         @arg OB_RDP_LEVEL_0: No protection
  *         @arg OB_RDP_LEVEL_1: Read protection of the memory
  *         @arg OB_RDP_LEVEL_2: Full chip protection
  */
uint32_t OPENBL_OB_GetReadOutProtectionLevel(void)
{
  uint32_t rdplvl = READ_BIT(FLASH->OPTR1, FLASH_OPTR1_RDP);

  if (rdplvl != RDP_LEVEL_0)
    return RDP_LEVEL_1;

  return RDP_LEVEL_0;
}


/**
  * @brief  Return the FLASH Read Protection level.
  * @param  Level Can be one of these values:
  *         @arg RDP_LEVEL_0: No protection
  *         @arg RDP_LEVEL_1: Read protection of the memory
  *         @arg RDP_LEVEL_2: Full chip protection
  * @retval None.
  */
void OPENBL_OB_SetReadOutProtectionLevel(uint32_t Level)
{
  uint8_t opt[0x20];

  for (uint32_t i = 0; i < sizeof(opt); i++)
  {
    opt[i] = M8(OB_BASE + i);
  }

  opt[0] = (uint8_t) Level;

  OPENBL_OB_Write(OB_BASE, opt, sizeof(opt));
}

/**
  * @brief  This function is used to enable or disable write protection of the specified FLASH areas.
  * @param  State Can be one of these values:
  *         @arg DISABLE: Disable FLASH write protection
  *         @arg ENABLE: Enable FLASH write protection
  * @param  ListOfPages Contains the list of pages to be protected.
  * @param  Length The length of the list of pages to be protected.
  * @retval An ErrorStatus enumeration value:
  *          - SUCCESS: Enable or disable of the write protection is done
  *          - ERROR:   Enable or disable of the write protection is not done
  */
ErrorStatus OPENBL_OB_SetWriteProtection(FunctionalState State, uint8_t *ListOfPages, uint32_t Length)
{
  ErrorStatus status = SUCCESS;
  uint8_t wrp = 0;
  uint8_t opt[0x20];

  for (uint32_t i = 0; i < sizeof(opt); i++)
  {
    opt[i] = M8(OB_BASE + i);
  }

  for (uint32_t i = 0; i < Length; i++)
  {
    SET_BIT(wrp, 1 << ListOfPages[i]);
  }

  if (State == ENABLE)
  {
    CLEAR_BIT(opt[0xC], wrp);
  }
  else
  {
    opt[0xC] = 0xFF;
  }

  OPENBL_OB_Write(OB_BASE, opt, sizeof(opt));

  return status;
}

