/********************************** (C) COPYRIGHT  *******************************
* File Name          : ch32f20x_flash.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2021/08/08
* Description        : This file provides all the FLASH firmware functions.
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* SPDX-License-Identifier: Apache-2.0
***************************************************************************************/
#include "ch32f20x_flash.h"

/* Flash Control Register bits */
#define CR_PG_Set                ((uint32_t)0x00000001)
#define CR_PG_Reset              ((uint32_t)0xFFFFFFFE)
#define CR_PER_Set               ((uint32_t)0x00000002)
#define CR_PER_Reset             ((uint32_t)0xFFFFFFFD)
#define CR_MER_Set               ((uint32_t)0x00000004)
#define CR_MER_Reset             ((uint32_t)0xFFFFFFFB)
#define CR_OPTPG_Set             ((uint32_t)0x00000010)
#define CR_OPTPG_Reset           ((uint32_t)0xFFFFFFEF)
#define CR_OPTER_Set             ((uint32_t)0x00000020)
#define CR_OPTER_Reset           ((uint32_t)0xFFFFFFDF)
#define CR_STRT_Set              ((uint32_t)0x00000040)
#define CR_LOCK_Set              ((uint32_t)0x00000080)
#define CR_FAST_LOCK_Set         ((uint32_t)0x00008000)
#define CR_PAGE_PG 	             ((uint32_t)0x00010000)
#define CR_PAGE_ER				       ((uint32_t)0x00020000)
#define CR_BER32 	               ((uint32_t)0x00040000)
#define CR_BER64 			           ((uint32_t)0x00080000)
#define CR_PG_STRT               ((uint32_t)0x00200000)

/* FLASH Status Register bits */
#define SR_BSY 					         ((uint32_t)0x00000001)
#define SR_WR_BSY 			         ((uint32_t)0x00000002)
#define SR_WRPRTERR 		         ((uint32_t)0x00000010)
#define SR_EOP 					         ((uint32_t)0x00000020)

/* FLASH Mask */
#define RDPRT_Mask               ((uint32_t)0x00000002)
#define WRP0_Mask                ((uint32_t)0x000000FF)
#define WRP1_Mask                ((uint32_t)0x0000FF00)
#define WRP2_Mask                ((uint32_t)0x00FF0000)
#define WRP3_Mask                ((uint32_t)0xFF000000)
#define OB_USER_BFB2             ((uint16_t)0x0008)

/* FLASH Keys */
#define RDP_Key                  ((uint16_t)0x00A5)
#define FLASH_KEY1               ((uint32_t)0x45670123)
#define FLASH_KEY2               ((uint32_t)0xCDEF89AB)

/* FLASH BANK address */
#define FLASH_BANK1_END_ADDRESS  ((uint32_t)0x807FFFF)

/* EEPROM address */
#define EEPROM_ADDRESS           ((uint32_t)0x8070000)

/* Delay definition */
#define EraseTimeout             ((uint32_t)0x000B0000)
#define ProgramTimeout           ((uint32_t)0x00005000)

/*********************************************************************
 * @fn      FLASH_Unlock
 *
 * @brief   Unlocks the FLASH Program Erase Controller.
 *
 * @return  none
 */
void FLASH_Unlock( void )
{
    /* Authorize the FPEC of Bank1 Access */
    FLASH->KEYR = FLASH_KEY1;
    FLASH->KEYR = FLASH_KEY2;
}


/*********************************************************************
 * @fn      FLASH_UnlockBank1
 *
 * @brief   Unlocks the FLASH Bank1 Program Erase Controller.
 *          equivalent to FLASH_Unlock function.
 *
 * @return  none
 */
void FLASH_UnlockBank1( void )
{
    FLASH->KEYR = FLASH_KEY1;
    FLASH->KEYR = FLASH_KEY2;
}

/*********************************************************************
 * @fn      FLASH_Lock
 *
 * @brief   Locks the FLASH Program Erase Controller.
 *
 * @return  none
 */
void FLASH_Lock( void )
{
    FLASH->CTLR |= CR_LOCK_Set;
}

/*********************************************************************
 * @fn      FLASH_LockBank1
 *
 * @brief   Locks the FLASH Bank1 Program Erase Controller.
 *
 * @return  none
 */
void FLASH_LockBank1( void )
{
    FLASH->CTLR |= CR_LOCK_Set;
}


/*********************************************************************
 * @fn      FLASH_ErasePage
 *
 * @brief   Erases a specified FLASH page(page size 4KB).
 *
 * @param   Page_Address - The page address to be erased.
 *
 * @return  FLASH Status - The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
 *        FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
 */
FLASH_Status FLASH_ErasePage( uint32_t Page_Address )
{
    FLASH_Status status = FLASH_COMPLETE;

    status = FLASH_WaitForLastOperation( EraseTimeout );

    if( status == FLASH_COMPLETE )
    {
        FLASH->CTLR |= CR_PER_Set;
        FLASH->ADDR = Page_Address;
        FLASH->CTLR |= CR_STRT_Set;

        status = FLASH_WaitForLastOperation( EraseTimeout );

        FLASH->CTLR &= CR_PER_Reset;
    }

    return status;
}


/*********************************************************************
 * @fn      FLASH_EraseAllPages
 *
 * @brief   Erases all FLASH pages.
 *
 * @return  FLASH Status - The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
 *        FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
 */
FLASH_Status FLASH_EraseAllPages( void )
{
    FLASH_Status status = FLASH_COMPLETE;

    status = FLASH_WaitForLastOperation( EraseTimeout );
    if( status == FLASH_COMPLETE )
    {
        FLASH->CTLR |= CR_MER_Set;
        FLASH->CTLR |= CR_STRT_Set;

        status = FLASH_WaitForLastOperation( EraseTimeout );

        FLASH->CTLR &= CR_MER_Reset;
    }

    return status;
}

/*********************************************************************
 * @fn      FLASH_EraseAllBank1Pages
 *
 * @brief   Erases all Bank1 FLASH pages.
 *
 * @return  FLASH Status - The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
 *        FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
 */
FLASH_Status FLASH_EraseAllBank1Pages( void )
{
    FLASH_Status status = FLASH_COMPLETE;
    status = FLASH_WaitForLastBank1Operation( EraseTimeout );

    if( status == FLASH_COMPLETE )
    {
        FLASH->CTLR |= CR_MER_Set;
        FLASH->CTLR |= CR_STRT_Set;

        status = FLASH_WaitForLastBank1Operation( EraseTimeout );

        FLASH->CTLR &= CR_MER_Reset;
    }
    return status;
}

/*********************************************************************
 * @fn      FLASH_EraseOptionBytes
 *
 * @brief   Erases the FLASH option bytes.
 *
 * @return  FLASH Status - The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
 *        FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
 */
FLASH_Status FLASH_EraseOptionBytes( void )
{
    uint16_t rdptmp = RDP_Key;
    uint32_t Address = 0x1FFFF800;
    __IO uint8_t i;

    FLASH_Status status = FLASH_COMPLETE;
    if( FLASH_GetReadOutProtectionStatus() != RESET )
    {
        rdptmp = 0x00;
    }
    status = FLASH_WaitForLastOperation( EraseTimeout );
    if( status == FLASH_COMPLETE )
    {
        FLASH->OBKEYR = FLASH_KEY1;
        FLASH->OBKEYR = FLASH_KEY2;

        FLASH->CTLR |= CR_OPTER_Set;
        FLASH->CTLR |= CR_STRT_Set;
        status = FLASH_WaitForLastOperation( EraseTimeout );

        if( status == FLASH_COMPLETE )
        {
            FLASH->CTLR &= CR_OPTER_Reset;
            FLASH->CTLR |= CR_OPTPG_Set;
            OB->RDPR = ( uint16_t )rdptmp;
            status = FLASH_WaitForLastOperation( ProgramTimeout );

            if( status != FLASH_TIMEOUT )
            {
                FLASH->CTLR &= CR_OPTPG_Reset;
            }
        }
        else
        {
            if( status != FLASH_TIMEOUT )
            {
                FLASH->CTLR &= CR_OPTPG_Reset;
            }
        }

        /* Write 0xFF */
        FLASH->CTLR |= CR_OPTPG_Set;

        for( i = 0; i < 8; i++ ){
            *( uint16_t * )( Address + 2 * i ) = 0x00FF;
            while( FLASH->STATR & SR_BSY );
        }

        FLASH->CTLR &= ~CR_OPTPG_Set;


    }
    return status;
}

/*********************************************************************
 * @fn      FLASH_ProgramWord
 *
 * @brief   Programs a word at a specified address.
 *
 * @param   Address - specifies the address to be programmed.
 *          Data - specifies the data to be programmed.
 *
 * @return  FLASH Status - The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
 *        FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
 */
FLASH_Status FLASH_ProgramWord( uint32_t Address, uint32_t Data )
{
    FLASH_Status status = FLASH_COMPLETE;
    __IO uint32_t tmp = 0;

    status = FLASH_WaitForLastOperation( ProgramTimeout );

    if( status == FLASH_COMPLETE )
    {
        FLASH->CTLR |= CR_PG_Set;

        *( __IO uint16_t * )Address = ( uint16_t )Data;
        status = FLASH_WaitForLastOperation( ProgramTimeout );

        if( status == FLASH_COMPLETE )
        {
            tmp = Address + 2;
            *( __IO uint16_t * ) tmp = Data >> 16;
            status = FLASH_WaitForLastOperation( ProgramTimeout );
            FLASH->CTLR &= CR_PG_Reset;
        }
        else
        {
            FLASH->CTLR &= CR_PG_Reset;
        }
    }

    return status;
}

/*********************************************************************
 * @fn      FLASH_ProgramHalfWord
 *
 * @brief   Programs a half word at a specified address.
 *
 * @param   Address - specifies the address to be programmed.
 *          Data - specifies the data to be programmed.
 *
 * @return  FLASH Status - The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
 *        FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
 */
FLASH_Status FLASH_ProgramHalfWord( uint32_t Address, uint16_t Data )
{
    FLASH_Status status = FLASH_COMPLETE;

    status = FLASH_WaitForLastOperation( ProgramTimeout );

    if( status == FLASH_COMPLETE )
    {
        FLASH->CTLR |= CR_PG_Set;
        *( __IO uint16_t * )Address = Data;
        status = FLASH_WaitForLastOperation( ProgramTimeout );
        FLASH->CTLR &= CR_PG_Reset;
    }

    return status;
}

/*********************************************************************
 * @fn      FLASH_ProgramOptionByteData
 *
 * @brief   Programs a half word at a specified Option Byte Data address.
 *
 * @param   Address - specifies the address to be programmed.
 *          Data - specifies the data to be programmed.
 *
 * @return  FLASH Status - The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
 *        FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
 */
FLASH_Status FLASH_ProgramOptionByteData( uint32_t Address, uint8_t Data )
{
    FLASH_Status status = FLASH_COMPLETE;
    uint32_t Addr = 0x1FFFF800;
    __IO uint8_t i;
    uint16_t pbuf[8];

    status = FLASH_WaitForLastOperation( ProgramTimeout );
    if( status == FLASH_COMPLETE )
    {
        FLASH->OBKEYR = FLASH_KEY1;
        FLASH->OBKEYR = FLASH_KEY2;

        /* Read optionbytes */
        for( i = 0; i < 8; i++ ){
            pbuf[i] = *( uint16_t * )( Addr + 2 * i );
        }

        /* Erase optionbytes */
        FLASH->CTLR |= CR_OPTER_Set;
        FLASH->CTLR |= CR_STRT_Set;
        while( FLASH->STATR & SR_BSY );
        FLASH->CTLR &= ~CR_OPTER_Set;

        /* Write optionbytes */
        pbuf[( ( Address - 0x1FFFF800 ) / 2 )] = ( ( ( ( uint16_t )~( Data ) ) << 8 ) | ( ( uint16_t )Data ) );

        FLASH->CTLR |= CR_OPTPG_Set;

        for( i = 0; i < 8; i++ ){
            *( uint16_t * )( Addr + 2 * i ) = pbuf[i];
            while( FLASH->STATR & SR_BSY );
        }

        FLASH->CTLR &= ~CR_OPTPG_Set;
    }

    return status;
}

/*********************************************************************
 * @fn      FLASH_EnableWriteProtection
 *
 * @brief   Write protects the desired sectors
 *
 * @param   FLASH_Sectors - specifies the address of the pages to be write protected.
 *
 * @return  FLASH Status - The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
 *        FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
 */
FLASH_Status FLASH_EnableWriteProtection( uint32_t FLASH_Sectors )
{
    uint16_t WRP0_Data = 0xFFFF, WRP1_Data = 0xFFFF, WRP2_Data = 0xFFFF, WRP3_Data = 0xFFFF;
    FLASH_Status status = FLASH_COMPLETE;
    uint32_t Addr = 0x1FFFF800;
    __IO uint8_t i;
    uint16_t pbuf[8];

    FLASH_Sectors = ( uint32_t )( ~FLASH_Sectors );
    WRP0_Data = ( uint16_t )( FLASH_Sectors & WRP0_Mask );
    WRP1_Data = ( uint16_t )( ( FLASH_Sectors & WRP1_Mask ) >> 8 );
    WRP2_Data = ( uint16_t )( ( FLASH_Sectors & WRP2_Mask ) >> 16 );
    WRP3_Data = ( uint16_t )( ( FLASH_Sectors & WRP3_Mask ) >> 24 );

    status = FLASH_WaitForLastOperation( ProgramTimeout );

    if( status == FLASH_COMPLETE )
    {
        FLASH->OBKEYR = FLASH_KEY1;
        FLASH->OBKEYR = FLASH_KEY2;

        /* Read optionbytes */
        for( i = 0; i < 8; i++ )
        {
            pbuf[i] = *( uint16_t * )( Addr + 2 * i );
        }

        /* Erase optionbytes */
        FLASH->CTLR |= CR_OPTER_Set;
        FLASH->CTLR |= CR_STRT_Set;
        while( FLASH->STATR & SR_BSY );
        FLASH->CTLR &= ~CR_OPTER_Set;

        /* Write optionbytes */
        pbuf[4] = WRP0_Data; pbuf[5] = WRP1_Data; pbuf[6] = WRP2_Data; pbuf[7] = WRP3_Data;

        FLASH->CTLR |= CR_OPTPG_Set;
        for( i = 0; i < 8; i++ ){
            *( uint16_t * )( Addr + 2 * i ) = pbuf[i];
            while( FLASH->STATR & SR_BSY );
        }
        FLASH->CTLR &= ~CR_OPTPG_Set;
    }
    return status;
}

/*********************************************************************
 * @fn      FLASH_ReadOutProtection
 *
 * @brief   Enables or disables the read out protection.
 *
 * @param   Newstate - new state of the ReadOut Protection(ENABLE or DISABLE).
 *
 * @return  FLASH Status - The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
 *        FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
 */
FLASH_Status FLASH_ReadOutProtection( FunctionalState NewState )
{
    FLASH_Status status = FLASH_COMPLETE;
    uint32_t Addr = 0x1FFFF800;
    __IO uint8_t i;
    uint16_t pbuf[8];

    status = FLASH_WaitForLastOperation( EraseTimeout );
    if( status == FLASH_COMPLETE )
    {
        FLASH->OBKEYR = FLASH_KEY1;
        FLASH->OBKEYR = FLASH_KEY2;

        /* Read optionbytes */
        for( i = 0; i < 8; i++ ){
            pbuf[i] = *( uint16_t * )( Addr + 2 * i );
        }

        /* Erase optionbytes */
        FLASH->CTLR |= CR_OPTER_Set;
        FLASH->CTLR |= CR_STRT_Set;
        while( FLASH->STATR & SR_BSY );
        FLASH->CTLR &= ~CR_OPTER_Set;

        /* Write optionbytes */
        if( NewState == DISABLE )
        {
            pbuf[0] = 0x5AA5;
        }
        else
        {
            pbuf[0] = 0x00FF;
        }

        FLASH->CTLR |= CR_OPTPG_Set;
        for( i = 0; i < 8; i++ ){
            *( uint16_t * )( Addr + 2 * i ) = pbuf[i];
            while( FLASH->STATR & SR_BSY );
        }
        FLASH->CTLR &= ~CR_OPTPG_Set;

    }
    return status;
}

/*********************************************************************
 * @fn      FLASH_UserOptionByteConfig
 *
 * @brief   Programs the FLASH User Option Byte - IWDG_SW / RST_STOP / RST_STDBY.
 *
 * @param   OB_IWDG - Selects the IWDG mode
 *            OB_IWDG_SW - Software IWDG selected
 *            OB_IWDG_HW - Hardware IWDG selected
 *          OB_STOP - Reset event when entering STOP mode.
 *            OB_STOP_NoRST - No reset generated when entering in STOP
 *            OB_STOP_RST - Reset generated when entering in STOP
 *          OB_STDBY - Reset event when entering Standby mode.
 *            OB_STDBY_NoRST - No reset generated when entering in STANDBY
 *            OB_STDBY_RST - Reset generated when entering in STANDBY
 *
 * @return  FLASH Status - The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
 *        FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
 */
FLASH_Status FLASH_UserOptionByteConfig( uint16_t OB_IWDG, uint16_t OB_STOP, uint16_t OB_STDBY )
{
    FLASH_Status status = FLASH_COMPLETE;
    uint32_t Addr = 0x1FFFF800;
    __IO uint8_t i;
    uint16_t pbuf[8];

    FLASH->OBKEYR = FLASH_KEY1;
    FLASH->OBKEYR = FLASH_KEY2;
    status = FLASH_WaitForLastOperation( ProgramTimeout );

    if( status == FLASH_COMPLETE )
    {
        /* Read optionbytes */
        for( i = 0; i < 8; i++ ){
            pbuf[i] = *( uint16_t * )( Addr + 2 * i );
        }

        /* Erase optionbytes */
        FLASH->CTLR |= CR_OPTER_Set;
        FLASH->CTLR |= CR_STRT_Set;
        while( FLASH->STATR & SR_BSY );
        FLASH->CTLR &= ~CR_OPTER_Set;

        /* Write optionbytes */
        pbuf[1] = OB_IWDG | ( uint16_t )( OB_STOP | ( uint16_t )( OB_STDBY | ( ( uint16_t )0xF8 ) ) );

        FLASH->CTLR |= CR_OPTPG_Set;
        for( i = 0; i < 8; i++ ){
            *( uint16_t * )( Addr + 2 * i ) = pbuf[i];
            while( FLASH->STATR & SR_BSY );
        }
        FLASH->CTLR &= ~CR_OPTPG_Set;
    }
    return status;
}

/*********************************************************************
 * @fn      FLASH_GetUserOptionByte
 *
 * @brief   Returns the FLASH User Option Bytes values.
 *
 * @return  The FLASH User Option Bytes values:IWDG_SW(Bit0), RST_STOP(Bit1)
 *        and RST_STDBY(Bit2).
 */
uint32_t FLASH_GetUserOptionByte( void )
{
    return ( uint32_t )( FLASH->OBR >> 2 );
}


/*********************************************************************
 * @fn      FLASH_GetWriteProtectionOptionByte
 *
 * @brief   Returns the FLASH Write Protection Option Bytes Register value.
 *
 * @return  The FLASH Write Protection Option Bytes Register value.
 */
uint32_t FLASH_GetWriteProtectionOptionByte( void )
{
    return ( uint32_t )( FLASH->WPR );
}


/*********************************************************************
 * @fn      FLASH_GetReadOutProtectionStatus
 *
 * @brief   Checks whether the FLASH Read Out Protection Status is set or not.
 *
 * @return  FLASH ReadOut Protection Status(SET or RESET)
 */
FlagStatus FLASH_GetReadOutProtectionStatus( void )
{
    FlagStatus readoutstatus = RESET;
    if( ( FLASH->OBR & RDPRT_Mask ) != ( uint32_t )RESET )
    {
        readoutstatus = SET;
    }
    else
    {
        readoutstatus = RESET;
    }
    return readoutstatus;
}

/*********************************************************************
 * @fn      FLASH_ITConfig
 *
 * @brief   Enables or disables the specified FLASH interrupts.
 *
 * @param   FLASH_IT - specifies the FLASH interrupt sources to be enabled or disabled.
 *            FLASH_IT_ERROR - FLASH Error Interrupt
 *            FLASH_IT_EOP - FLASH end of operation Interrupt
 *          NewState - new state of the specified Flash interrupts(ENABLE or DISABLE).
 *
 * @return  FLASH Prefetch Buffer Status (SET or RESET).
 */
void FLASH_ITConfig( uint32_t FLASH_IT, FunctionalState NewState )
{
    if( NewState != DISABLE )
    {
        FLASH->CTLR |= FLASH_IT;
    }
    else
    {
        FLASH->CTLR &= ~( uint32_t )FLASH_IT;
    }
}


/*********************************************************************
 * @fn      FLASH_GetFlagStatus
 *
 * @brief   Checks whether the specified FLASH flag is set or not.
 *
 * @param   FLASH_FLAG - specifies the FLASH flag to check.
 *            FLASH_FLAG_BSY - FLASH Busy flag
 *            FLASH_FLAG_PGERR - FLASH Program error flag
 *            FLASH_FLAG_WRPRTERR - FLASH Write protected error flag
 *            FLASH_FLAG_EOP - FLASH End of Operation flag
 *            FLASH_FLAG_OPTERR - FLASH Option Byte error flag
 *
 * @return  The new state of FLASH_FLAG (SET or RESET).
 */
FlagStatus FLASH_GetFlagStatus( uint32_t FLASH_FLAG )
{
    FlagStatus bitstatus = RESET;

    if( FLASH_FLAG == FLASH_FLAG_OPTERR )
    {
        if( ( FLASH->OBR & FLASH_FLAG_OPTERR ) != ( uint32_t )RESET )
        {
            bitstatus = SET;
        }
        else
        {
            bitstatus = RESET;
        }
    }
    else
    {
        if( ( FLASH->STATR & FLASH_FLAG ) != ( uint32_t )RESET )
        {
            bitstatus = SET;
        }
        else
        {
            bitstatus = RESET;
        }
    }
    return bitstatus;
}

/*********************************************************************
 * @fn      FLASH_ClearFlag
 *
 * @brief   Clears the FLASH's pending flags.
 *
 * @param   FLASH_FLAG - specifies the FLASH flags to clear.
 *            FLASH_FLAG_PGERR - FLASH Program error flag
 *            FLASH_FLAG_WRPRTERR - FLASH Write protected error flag
 *            FLASH_FLAG_EOP - FLASH End of Operation flag
 *
 * @return  none
 */
void FLASH_ClearFlag( uint32_t FLASH_FLAG )
{

    FLASH->STATR = FLASH_FLAG;

}

/*********************************************************************
 * @fn      FLASH_GetStatus
 *
 * @brief   Returns the FLASH Status.
 *
 * @return  FLASH Status - The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
 *        FLASH_ERROR_WRP or FLASH_COMPLETE.
 */
FLASH_Status FLASH_GetStatus( void )
{
    FLASH_Status flashstatus = FLASH_COMPLETE;

    if( ( FLASH->STATR & FLASH_FLAG_BSY ) == FLASH_FLAG_BSY )
    {
        flashstatus = FLASH_BUSY;
    }
    else
    {
        if( ( FLASH->STATR & FLASH_FLAG_PGERR ) != 0 )
        {
            flashstatus = FLASH_ERROR_PG;
        }
        else
        {
            if( ( FLASH->STATR & FLASH_FLAG_WRPRTERR ) != 0 )
            {
                flashstatus = FLASH_ERROR_WRP;
            }
            else
            {
                flashstatus = FLASH_COMPLETE;
            }
        }
    }
    return flashstatus;
}

/*********************************************************************
 * @fn      FLASH_GetBank1Status
 *
 * @brief   Returns the FLASH Bank1 Status.
 *
 * @return  FLASH Status - The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
 *        FLASH_ERROR_WRP or FLASH_COMPLETE.
 */
FLASH_Status FLASH_GetBank1Status( void )
{
    FLASH_Status flashstatus = FLASH_COMPLETE;

    if( ( FLASH->STATR & FLASH_FLAG_BANK1_BSY ) == FLASH_FLAG_BSY )
    {
        flashstatus = FLASH_BUSY;
    }
    else
    {
        if( ( FLASH->STATR & FLASH_FLAG_BANK1_PGERR ) != 0 )
        {
            flashstatus = FLASH_ERROR_PG;
        }
        else
        {
            if( ( FLASH->STATR & FLASH_FLAG_BANK1_WRPRTERR ) != 0 )
            {
                flashstatus = FLASH_ERROR_WRP;
            }
            else
            {
                flashstatus = FLASH_COMPLETE;
            }
        }
    }
    return flashstatus;
}


/*********************************************************************
 * @fn      FLASH_WaitForLastOperation
 *
 * @brief   Waits for a Flash operation to complete or a TIMEOUT to occur.
 *
 * @param   Timeout - FLASH programming Timeout
 *
 * @return  FLASH Status - The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
 *        FLASH_ERROR_WRP or FLASH_COMPLETE.
 */
FLASH_Status FLASH_WaitForLastOperation( uint32_t Timeout )
{
    FLASH_Status status = FLASH_COMPLETE;

    status = FLASH_GetBank1Status();
    while( ( status == FLASH_BUSY ) && ( Timeout != 0x00 ) )
    {
        status = FLASH_GetBank1Status();
        Timeout--;
    }
    if( Timeout == 0x00 )
    {
        status = FLASH_TIMEOUT;
    }
    return status;
}


/*********************************************************************
 * @fn      FLASH_WaitForLastBank1Operation
 *
 * @brief   Waits for a Flash operation on Bank1 to complete or a TIMEOUT to occur.
 *
 * @param   Timeout - FLASH programming Timeout
 *
 * @return  FLASH Status - The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
 *        FLASH_ERROR_WRP or FLASH_COMPLETE.
 */
FLASH_Status FLASH_WaitForLastBank1Operation( uint32_t Timeout )
{
    FLASH_Status status = FLASH_COMPLETE;

    status = FLASH_GetBank1Status();
    while( ( status == FLASH_FLAG_BANK1_BSY ) && ( Timeout != 0x00 ) )
    {
        status = FLASH_GetBank1Status();
        Timeout--;
    }
    if( Timeout == 0x00 )
    {
        status = FLASH_TIMEOUT;
    }
    return status;
}

/*********************************************************************
 * @fn      FLASH_Unlock_Fast
 *
 * @brief   Unlocks the Fast Program Erase Mode.
 *
 * @return  none
 */
void FLASH_Unlock_Fast( void )
{
    /* Authorize the FPEC of Bank1 Access */
    FLASH->KEYR = FLASH_KEY1;
    FLASH->KEYR = FLASH_KEY2;

    /* Fast program mode unlock */
    FLASH->MODEKEYR = FLASH_KEY1;
    FLASH->MODEKEYR = FLASH_KEY2;
}

/*********************************************************************
 * @fn      FLASH_Lock_Fast
 *
 * @brief   Locks the Fast Program Erase Mode.
 *
 * @return  none
 */
void FLASH_Lock_Fast( void )
{
    FLASH->CTLR |= CR_LOCK_Set;
}

/*********************************************************************
 * @fn      FLASH_ErasePage_Fast
 *
 * @brief   Erases a specified FLASH page (1page = 256Byte).
 *
 * @param   Page_Address - The page address to be erased.
 *
 * @return  none
 */
void FLASH_ErasePage_Fast( uint32_t Page_Address )
{
    Page_Address &= 0xFFFFFF00;

    FLASH->CTLR |= CR_PAGE_ER;
    FLASH->ADDR = Page_Address;
    FLASH->CTLR |= CR_STRT_Set;
    while( FLASH->STATR & SR_BSY );
    FLASH->CTLR &= ~CR_PAGE_ER;
}

/*********************************************************************
 * @fn      FLASH_EraseBlock_32K_Fast
 *
 * @brief   Erases a specified FLASH Block (1Block = 32KByte).
 *
 * @param   Block_Address - The block address to be erased.
 *
 * @return  none
 */
void FLASH_EraseBlock_32K_Fast( uint32_t Block_Address )
{
    Block_Address &= 0xFFFF8000;

    FLASH->CTLR |= CR_BER32;
    FLASH->ADDR = Block_Address;
    FLASH->CTLR |= CR_STRT_Set;
    while( FLASH->STATR & SR_BSY );
    FLASH->CTLR &= ~CR_BER32;
}

/*********************************************************************
 * @fn      FLASH_EraseBlock_64K_Fast
 *
 * @brief   Erases a specified FLASH Block (1Block = 64KByte).
 *
 * @param   Block_Address - The block address to be erased.
 *
 * @return  none
 */
void FLASH_EraseBlock_64K_Fast( uint32_t Block_Address )
{
    Block_Address &= 0xFFFF0000;

    FLASH->CTLR |= CR_BER64;
    FLASH->ADDR = Block_Address;
    FLASH->CTLR |= CR_STRT_Set;
    while( FLASH->STATR & SR_BSY );
    FLASH->CTLR &= ~CR_BER64;
}

/*********************************************************************
 * @fn      FLASH_ProgramPage_Fast
 *
 * @brief   Program a specified FLASH page (1page = 256Byte).
 *
 * @param   Page_Address - The page address to be programed.
 *
 * @return  none
 */
void FLASH_ProgramPage_Fast( uint32_t Page_Address, uint32_t *pbuf )
{
    uint8_t size = 64;

    Page_Address &= 0xFFFFFF00;

    FLASH->CTLR |= CR_PAGE_PG;
    while( FLASH->STATR & SR_BSY );
    while( FLASH->STATR & SR_WR_BSY );

    while( size )
    {
        *( uint32_t * )Page_Address = *( uint32_t * )pbuf;
        Page_Address += 4;
        pbuf += 1;
        size -= 1;
        while( FLASH->STATR & SR_WR_BSY );
    }

    FLASH->CTLR |= CR_PG_STRT;
    while( FLASH->STATR & SR_BSY );
    FLASH->CTLR &= ~CR_PAGE_PG;
}

/*********************************************************************
 * @fn      FLASH_Access_Clock_Cfg
 *
 * @brief   Config FLASH Access Clock(Need to unlock )
 *
 * @param   FLASH_Access_CLK -
 *            FLASH_Access_SYSTEM_HALF - System clock/2
 *            FLASH_Access_SYSTEM - System clock
 *
 * @return  none
 */
void FLASH_Access_Clock_Cfg(uint32_t FLASH_Access_CLK)
{
    FLASH->CTLR &= ~(1 << 25);
    FLASH->CTLR |= FLASH_Access_CLK;
}

/*********************************************************************
 * @fn      FLASH_Enhance_Mode
 *
 * @brief   Read FLASH Enhance Mode
 *
 * @param
 *          Newstate - new state of the ReadOut Protection(ENABLE or DISABLE).
 *
 * @return  none
 */
void FLASH_Enhance_Mode(FunctionalState NewState)
{
    if(NewState)
    {
        FLASH->CTLR |= (1 << 24);
    }
    else
    {
        FLASH->CTLR &= ~(1 << 24);
        FLASH->CTLR |= (1 << 22);
    }
}

/********************************************************************************
 * @fn        EEPROM_READ
 *
 * @brief     Read EEPROM
 *
 * @param     StartAddr: Read EEPROM start address
 *            *Buffer: READ EEPROM data
 *            Length: READ EEPROM length.
 *
 * @return None
 */
FLASH_Status EEPROM_READ( uint32_t StartAddr, void *Buffer, uint32_t Length )
{
    uint32_t *src;
    uint8_t *pbuf;
    uint8_t offset;
    uint32_t value;
    offset = Length % 4;
    if( offset )
    {
        Length -= offset;
    }
    src = Buffer;
    for( ; Length > 0; Length -= 4 ){
        *src = *( uint32_t * )( StartAddr + EEPROM_ADDRESS );
        StartAddr += 4;
        src++;
    }
    pbuf = ( uint8_t * )src;
    value = *( uint32_t * )( StartAddr + EEPROM_ADDRESS );
    for( ; offset > 0; offset -- ){
        *pbuf = value & 0xFF;
        pbuf++;
        value >>= 8;
    }
    return FLASH_COMPLETE;
}

/********************************************************************************
 * @fn          EEPROM_ERASE
 *
 * @brief       Erase EEPROM
 *
 * @param       StartAddr: Erase EEPROM start address
 *              Length: Erase EEPROM length.
 *
 * @return      None
 */
FLASH_Status EEPROM_ERASE( uint32_t StartAddr, uint32_t Length )
{
    FLASH_Status state;
    uint8_t i;
    for( i = 0; i < ( Length / 4096 + 1 ); i++ ){
        state = FLASH_ErasePage( StartAddr + 4096 * i + EEPROM_ADDRESS );
        if( state != FLASH_COMPLETE )
        {
            break;
        }
    }
    return state;
}

/********************************************************************************
 * @fn         EEPROM_WRITE
 *
 * @brief      Write EEPROM
 *
 * @param      StartAddr: Write EEPROM start address
 *             *Buffer: Write EEPROM data
 *             Length: Write EEPROM length.
 *
 * @return     None
 */
FLASH_Status EEPROM_WRITE( uint32_t StartAddr, void *Buffer, uint32_t Length )
{
    FLASH_Status state;
    uint8_t offset;
    uint32_t *src;

    offset = Length % 4;
    if( offset )
    {
        Length += 4 - offset;
    }
    src = Buffer;
    for( ; Length > 0; Length -= 4 ){
        state = FLASH_ProgramWord( StartAddr + EEPROM_ADDRESS, *src );
        if( state != FLASH_COMPLETE )
        {
            break;
        }
        StartAddr += 4;
        src++;
    }
    return state;
}

/********************************************************************************
 * @fn        FLASH_GetMACAddress
 *
 * @brief     Get MAC address
 *
 * @param     *Buffer: Mac address
 *
 * @return    None
 */
void FLASH_GetMACAddress( uint8_t *Buffer )
{
    uint32_t value;

    value = *( uint32_t * )( 0x1FFFF7E8 );
    Buffer[0] = value & 0xFF;
    Buffer[1] = ( value >> 8 ) & 0xFF;
    Buffer[2] = ( value >> 16 ) & 0xFF;
    Buffer[3] = ( value >> 24 ) & 0xFF;
    value = *( uint32_t * )( 0x1FFFF7EC );
    Buffer[4] = value & 0xFF;
    Buffer[5] = ( value >> 8 ) & 0xFF;
}
