/***********************************************************************************************************************
* Copyright (C) All rights reserved.
***********************************************************************************************************************/

/***********************************************************************************************************************
* @file    flash.c
* @brief   This file implements flash sector erase and program.
* @version 1.0.0
* @date    2019/12/24
***********************************************************************************************************************/

/***********************************************************************************************************************
Includes
***********************************************************************************************************************/
#include "flash.h"
uint32_t flash_protect_flag = 0;

#if defined (__CC_ARM)
#pragma arm section code = "ram_fetch_code"   // Arm Compiler 5
#elif defined (__ARMCC_VERSION) && (__ARMCC_VERSION > 6010050)
#pragma clang section text = "ram_fetch_code" // Arm Compiler 6
#endif

/***********************************************************************************************************************
* Function Name: EraseChip
* @brief  Chip Erase Flash
* @param  adr - Any address of user code flash 
* @return status: 0 - OK, 1 - Failed
***********************************************************************************************************************/
int EraseChip (uint32_t adr)
{
#ifdef FLASH_PROTCET
	if(flash_protect_flag != PROTECT_VALUE)
	{
		return(1);		
	}
	flash_protect_flag = 0;
#endif
	__DI;
    FMC->FLERMD = 0x08;
    FMC->FLPROT = 0xF1;
    FMC->FLOPMD1 = 0x55;
    FMC->FLOPMD2 = 0xAA;  
    // Write data to start address of sector to trigger Erase Operation
    *(uint32_t *) adr = 0xFFFFFFFF;
    
    // polling OVER Flag
    while((FMC->FLSTS & FMC_FLSTS_OVF_Msk) == 0);
    FMC->FLSTS = FMC_FLSTS_OVF_Msk;
    FMC->FLERMD = 0x00;

    FMC->FLPROT = 0xF0;

    __EI;
    return(0);
}

/***********************************************************************************************************************
* Function Name: EraseSector
* @brief  Sector Erase Flash
* @param  adr - sector address of user code flash
* @return status: 0 - OK, 1 - Failed
***********************************************************************************************************************/
int EraseSector (uint32_t adr)
{
#ifdef FLASH_PROTCET
	if(flash_protect_flag != PROTECT_VALUE)
	{
		return(1);		
	}
	flash_protect_flag = 0;
#endif
	__DI;
    FMC->FLERMD = 0x10;
    FMC->FLPROT = 0xF1;
    FMC->FLOPMD1 = 0x55;
    FMC->FLOPMD2 = 0xAA;  
    // Write data to start address of sector to trigger Erase Operation
    *(uint32_t *) adr = 0xFFFFFFFF;
    
    // polling Erase Over Flag
    while((FMC->FLSTS & FMC_FLSTS_OVF_Msk) == 0);
    FMC->FLSTS = FMC_FLSTS_OVF_Msk;
    FMC->FLERMD = 0x00;
    FMC->FLPROT = 0xF0;

    if(FMC->FLSTS & FMC_FLSTS_EVF_Msk)
    {
        FMC->FLSTS = FMC_FLSTS_EVF_Msk;
        __EI;
        return(1);  /* verify ng */
    }
    else
    {
        __EI;
        return(0);  /* verify ok */
    }
}

/***********************************************************************************************************************
* Function Name: ProgramPage
* @brief  Write data to Flash
* @param  adr - Page Start Address 
* @param  sz - Page Size 
* @param  buf - Page Data 
* @return status: 0 - OK, 1 - Failed
***********************************************************************************************************************/
int ProgramPage (uint32_t adr, uint32_t sz, uint8_t *buf)
{
    uint32_t i;
    uint8_t *ptr;
    
    ptr = (uint8_t *) adr;
	
#ifdef FLASH_PROTCET	
	if(flash_protect_flag != PROTECT_VALUE)
	{
		return(1);		
	}
 	flash_protect_flag = 0;
#endif   
    FMC->FLPROT = 0xF1;
    
    for(i=0; i<sz; i++) 
    {
        __DI;
        FMC->FLOPMD1 = 0xAA;
        FMC->FLOPMD2 = 0x55;  
        *ptr++ = *buf++;    
        __EI;
        // polling OVER Flag
        while((FMC->FLSTS & FMC_FLSTS_OVF_Msk) == 0);
        FMC->FLSTS = FMC_FLSTS_OVF_Msk;
    }

    FMC->FLPROT = 0xF0;

    return (0);
}

/***********************************************************************************************************************
* Function Name: flash_write
* @brief  Write data to Flash area. Automatically determines if erasure is needed.
*         If the destination area is blank, omits Erase and Pragram directly.
*         Otherwise, save data of flash to ram, erase flash, and then write flash.
* @param  adr - Page Start Address 
* @param  sz - Page Size 
* @param  buf - Page Data 
* @return status - MD_OK or MD_ERROR
* @note   sz must be less then or equal to SECTOR_SIZE, if not, return MD_ERROR and don't change flash contents.
***********************************************************************************************************************/
FLASH_STATUS flash_write(uint32_t adr, uint32_t sz, uint8_t *buf)
{
    FLASH_STATUS status = FS_OK;
    uint8_t dat, tmp[SECTOR_SIZE *2];
    uint8_t  blank = 1;
    uint8_t  cross;
    uint8_t *ptr;
    uint8_t *ptr_base;      /* sector base address */
    uint16_t offset;
    uint16_t i;
    uint32_t sector_num;

#ifdef FLASH_PROTCET
	if(flash_protect_flag != PROTECT_VALUE)
	{
		return(FS_ERROR);		
	}
 	flash_protect_flag = 0;
#endif
	
    if(sz > SECTOR_SIZE) 
	{
       status = FS_ERROR; 
       return(status);
    }

    ptr = (uint8_t *)adr;

    /* Determine if the target area is blank */
    for(i=0; i<sz; i++) 
	{
        dat = *ptr++;
        if(dat != 0xFFU)
        {
            blank = 0;  /* Not blank */
        }
    }

    /* if it is blank, omits erase and program directlly. */
    if(blank)
	{
        /* write data to flash data */
		flash_protect_flag = PROTECT_VALUE;
        ProgramPage(adr, sz, buf);
    /* if it isn't blank, erase and then program */
    } 
	else 
	{
        sector_num = (adr & ~(SECTOR_SIZE-1)) >> 9;
        if((adr+sz) > ((sector_num+1) << 9)) 
		{
            cross = 1;  /* write area cross sectors */
        } 
		else
		{
            cross = 0;
        }
        ptr_base = (uint8_t *)(adr & ~(SECTOR_SIZE-1));    /* get sector base address: Each sector is 512 bytes (i.e. 128 words) */
        offset   = adr & (SECTOR_SIZE-1);                  /* get offset */
        
        /* Save the Flash data temporarily */
        if(cross) 
		{
            /* read two sectors */
            for(i = 0; i < SECTOR_SIZE * 2; i++)
            {
                tmp[i] = *ptr_base++;
            }
        }
		else
		{
            /* read one sector */
            for(i = 0; i < SECTOR_SIZE; i++)
            {
                tmp[i] = *ptr_base++;
            }
        }

        /* replace flash data with write data */
        for(i = 0; i < sz; i++)
		{
            tmp[offset+i] = *buf++;
        }

        /* write data to flash data */
        if(cross) 
		{
			flash_protect_flag = PROTECT_VALUE;
            EraseSector((sector_num + 0 ) << 9);
			flash_protect_flag = PROTECT_VALUE;			
            EraseSector((sector_num + 1 ) << 9);
			flash_protect_flag = PROTECT_VALUE;
            ProgramPage(sector_num << 9, SECTOR_SIZE * 2, tmp);
        } 
		else
		{
			flash_protect_flag = PROTECT_VALUE;
            EraseSector((sector_num + 0 ) << 9);
			flash_protect_flag = PROTECT_VALUE;
            ProgramPage(sector_num << 9, SECTOR_SIZE * 1, tmp);
        }
    }
    
    return (status);
}

void flash_read(uint32_t read_address, uint16_t size, uint8_t *readbuf)
{
    uint8_t *w_ptr;
    uint32_t i;
    w_ptr = (uint8_t *)read_address;
    for (i = 0; i < size; i++)
    {
        *readbuf = *w_ptr++;
        readbuf++;
    }
}

#if defined (__CC_ARM)
#pragma arm section code        // Arm Compiler 5
#elif defined (__ARMCC_VERSION) && (__ARMCC_VERSION > 6010050)
#pragma clang section text = "" // Arm Compiler 6
#endif
