/**
  *********************************************************************************
  *
  * @file    hal_flash.c
  * @brief   FLASH module driver.
  *
  * @version V1.0
  * @date    20 Nov 2017
  * @author  AE Team
  * @note
  *
  * Copyright (C) Shanghai Eastsoft Microelectronics Co. Ltd. All rights reserved.
  *
  *********************************************************************************
  *
  *********************************************************************************
  * @verbatim
  ==============================================================================
                    ##### FLASH Peripheral features #####
  ==============================================================================
  [..]
  Base address is 0x00000000

  [..]
  FLASH have just one programme mode , word programme.
  word programme can programme 8 bytes once ;

  ==============================================================================
                        ##### How to use this driver #####
  ==============================================================================
  [..]
   (#) programme flash using flash_write(uint32_t addr, uint8_t *buf, uint16_t len)
       (++) call the function and supply all the three paraments is needs, addr means
            the first address to write in this operation, buf is a pointer to the data which
	    need writing to flash.

   (#) erase flash using flash_erase(uint32_t addr, uint16_t len)
       (++) call the function and supply two paraments, addr is the first address to erase,
            len is the length to erase

   (#) read flash using flash_read(uint32_t *ram_addr, uint32_t addr, uint16_t len)
       (++) read the flash and save to a buffer, ram_addr is the buffer's first address,
            addr is the start reading address in flash, len is the length need read

    @endverbatim
  */

#include "hal_flash.h"


/** @addtogroup ES32FXXX_HAL
  * @{
  */

/** @defgroup FLASH FLASH
  * @brief FLASH module driver
  * @{
  */

#ifdef HAL_FLASH

/** @addtogroup FLASH_Private_Types
  * @{
  */

/* opration buffer, global variable*/
static uint32_t write_buf[FLASH_PAGE_SIZE];
static op_cmd_type OP_CMD = OP_FLASH;

/**
  * @}
  */

/** @defgroup Flash_Private_Functions Flash Private Functions
  *  @brief   Flash Private functions
  * @{
  */

/**
  * @brief  Unlock the flash.
  * @retval Status, see @ref hal_status_t.
  */
static hal_status_t flash_unlock(void)
{
	uint16_t i;
	uint16_t op_cmd = OP_CMD;

	if (MSC->FLASHSR.BUSY)
		return ERROR;

	FLASH_REG_UNLOCK();
	FLASH_REQ();
	FLASH_IAP_ENABLE();

	for (i = 0; i < 0xFFFF; i++) {
		if (MSC->FLASHSR.FLASHACK)
			break;
	}

	return i == 0xFFFF ? ERROR : OK;
}

/**
  * @brief  Lock the flash.
  * @retval Status, see @ref hal_status_t.
  */
static hal_status_t flash_lock(void)
{
	uint16_t i;
	uint16_t op_cmd = OP_CMD;

	FLASH_REG_UNLOCK();
	MSC->FLASHCR.Word = 0x0;

	for (i = 0; i < 0xFFFF; i++) {
		if (!MSC->FLASHSR.FLASHACK)
			break;
	}

	return i == 0xFFFF ? ERROR : OK;
}

/**
  * @brief  Erase one page.
  * @param  addr: The erased page's address
  * @retval Status, see @ref hal_status_t.
  */
static hal_status_t flash_page_erase(uint32_t addr)
{
	uint16_t i;
	uint16_t op_cmd = OP_CMD;

	if (flash_unlock() != OK)
		goto end;

	MSC->FLASHADDR.IFREN = (op_cmd == OP_FLASH) ? DISABLE : ENABLE;
	MSC->FLASHADDR.ADDR  = (op_cmd == OP_FLASH) ? FLASH_PAGE_ADDR(addr) : INFO_PAGE_ADDR(addr);
	MSC->FLASHCMD.CMD    = FLASH_CMD_PE;

	for (i = 0; i < 0xFFFF; i++) {
		if (MSC->FLASHSR.BUSY)
			continue;
		if (MSC->FLASHSR.SERA)
			break;
	}

	if (i == 0xFFFF)
		goto end;

	if (flash_lock() == ERROR)
		goto end;

	return OK;
end:
	if (flash_lock() == ERROR)
		while(1);

	return ERROR;
}

/**
  * @brief  Programme a word.
  * @param  addr: The word's address, it is must word align.
  * @param  datal: The word low 4 bytes data.
  * @param  datah: The word high 4 bytes data.
  * @param  fifo: Choose if use fifo.
  * @retval Status, see @ref hal_status_t.
  */
static hal_status_t flash_word_program(uint32_t addr, uint32_t datal, uint32_t datah, uint32_t fifo)
{
	uint16_t i;
	uint16_t op_cmd = OP_CMD;

	if (flash_unlock() != OK)
		goto end;

	MSC->FLASHADDR.IFREN = (op_cmd == OP_FLASH) ? DISABLE : ENABLE;
	MSC->FLASHADDR.ADDR  = addr;

	if (fifo) {
		MSC->FLASHCR.FIFOEN = ENABLE;
		MSC->FLASHFIFO.FIFO = datal;
		MSC->FLASHFIFO.FIFO = datah;
	}
	else {
		MSC->FLASHDL.DATAL = datal;
		MSC->FLASHDH.DATAH = datah;
		MSC->FLASHCMD.CMD  = FLASH_CMD_WP;
	}

	for (i = 0; i < 0xFFFF; i++) {
		if (MSC->FLASHSR.BUSY)
			continue;
		if (MSC->FLASHSR.PROG)
			break;
	}

	if (i == 0xFFFF)
		goto end;

	if (flash_lock() == ERROR)
		goto end;

	return OK;
end:
	if (flash_lock() == ERROR)
		while(1);

	return ERROR;
}

/**
  * @brief  Read data from flash, and store in buffer.
  * @param  ram_addr: The stored buffer's address.
  * @param  addr: The start address in flash to read.
  * @param  len: The length of byte to read.
  * @retval Status, see @ref hal_status_t.
  */
static hal_status_t __flash_read(uint32_t ram_addr[], uint32_t addr, uint32_t len)
{
	uint32_t i;

	if (!len)
		return ERROR;

	for (i = 0; i < len; i++) {
		ram_addr[i] = ((uint32_t *)addr)[i];
	}

	return OK;
}

/**
  * @brief  Check whether the flash between the given address section
  *         have been writen, if it have been writen, return TRUE, else
  *         return FALSE.
  * @param  begin_addr: The begin address.
  * @param  end_addr: The end address.
  * @retval The check result
  *         - TRUE
  *         - FALSE
  */
static type_bool_t page_have_writen(uint32_t begin_addr, uint32_t end_addr)
{
	uint8_t* addr_to_read;
	uint8_t value;
	uint32_t index;

	/* Check the parameters */
	assert_param(IS_FLASH_ADDRESS(begin_addr));
	assert_param(IS_FLASH_ADDRESS(end_addr));

	addr_to_read = (uint8_t *)begin_addr;
	index        = begin_addr;
	value        = 0xFF;

	if (begin_addr > end_addr)
		return FALSE;

	while (index++ <= end_addr) {
		value = *addr_to_read++;

		if (value != 0xFF)
			break;
	}

	return value == 0xFF ? FALSE : TRUE;
}

/**
  * @}
  */

/** @defgroup FLASH_Exported_Functions FLASH Exported Functions
  * @verbatim
 ===============================================================================
              ##### Flash oprate functions #####
 ===============================================================================
  [..]
    This section provides functions allowing to operate flash, such as read and write.

    @endverbatim
  * @{
  */

/**
  * @brief  Write the give bytes to the given address section.
  * @param  addr: The start address to write.
  * @param  buf: The bytes' address.
  * @param  len: The length to write.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t flash_write(uint32_t addr, uint8_t *buf, uint16_t len)
{
	uint32_t index = 0;
	uint32_t para = 0;
	uint32_t index2 = 0;
	uint8_t* temp_addr;
	uint32_t start_write_addr;
	uint32_t end_write_addr;
	uint32_t start_word_addr;
	uint32_t end_word_addr;
	uint32_t index_flag;
	uint16_t len_to_write;
	type_bool_t need_erase_page;

	assert_param(IS_FLASH_ADDRESS(addr));
	assert_param(IS_FLASH_ADDRESS(addr + len - 1));

	len_to_write = len;
	temp_addr    = (uint8_t *)write_buf;

	while (len_to_write > 0) {
		need_erase_page = FALSE;

		for (index = 0; index < FLASH_PAGE_SIZE; index++)
			temp_addr[index] = 0xFF;

		start_write_addr = addr + (len - len_to_write);
		end_write_addr   = addr + len -1;
		end_write_addr   = FLASH_PAGE_ADDR(start_write_addr) == FLASH_PAGE_ADDR(end_write_addr)
		                   ? end_write_addr : FLASH_PAGEEND_ADDR(start_write_addr);
		need_erase_page  = page_have_writen(FLASH_WORD_ADDR(start_write_addr),
		                                    FLASH_WORDEND_ADDR(end_write_addr));

		if (need_erase_page) {
			if (ERROR == __flash_read(write_buf, FLASH_PAGE_ADDR(start_write_addr),
                                 		  FLASH_PAGE_SIZE >> 2))
				return ERROR;

			if (ERROR == flash_page_erase(FLASH_PAGE_ADDR(start_write_addr)))
				return ERROR;

			para   = end_write_addr & (FLASH_PAGE_SIZE - 1);
			index  = start_write_addr & (FLASH_PAGE_SIZE - 1);
			index2 = len - len_to_write;

			while (index <= para)
				temp_addr[index++] = buf[index2++];

			index2     = 0;
			index      = FLASH_PAGE_ADDR(start_write_addr);
			para       = FLASH_PAGE_ADDR(start_write_addr) + FLASH_PAGE_SIZE;
			index_flag = para;
		}
		else {
			para   = end_write_addr & (FLASH_PAGE_SIZE - 1);
			index  = start_write_addr & (FLASH_PAGE_SIZE - 1);
			index2 = len - len_to_write;

			while (index <= para)
				temp_addr[index++] = buf[index2++];

			start_word_addr = FLASH_WORD_ADDR(start_write_addr);
			end_word_addr   = FLASH_WORDEND_ADDR(end_write_addr);

			index2 = (FLASH_PAGE_ADDR(start_word_addr) - FLASH_WORD_ADDR(start_word_addr)) >> 2;
			index  = start_word_addr;
			index_flag = end_word_addr;
		}

		while (index < index_flag) {
			if (ERROR == flash_word_program(index, write_buf[index2],
							write_buf[index2 + 1], FLASH_FIFO))
				return ERROR;

			index2 += 2;
			index  += FLASH_WORD_SIZE;
		}

		len_to_write = len_to_write - (end_write_addr - start_write_addr + 1);
	}

	return OK;
}

/**
  * @brief  erase The flash between the given address section.
  * @param  addr: The start address to erase.
  * @param  len: The length to erase.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t flash_erase(uint32_t addr, uint16_t len)
{
	int32_t	index;
	int32_t index2;
	int32_t para;
	uint8_t* temp_addr;
	int32_t start_erase_addr;
	int32_t end_erase_addr;
	uint16_t len_not_erase;
	type_bool_t page_need_save;

	assert_param(IS_FLASH_ADDRESS(addr));
	assert_param(IS_FLASH_ADDRESS(addr + len - 1));

	len_not_erase = len;
	temp_addr     = (uint8_t *)write_buf;

	while (len_not_erase > 0) {
		page_need_save = FALSE;

		start_erase_addr = addr + len - len_not_erase;
		end_erase_addr   = addr + len - 1;
		end_erase_addr   = (FLASH_PAGE_ADDR(start_erase_addr) == FLASH_PAGE_ADDR(end_erase_addr))
		                    ? end_erase_addr : FLASH_PAGEEND_ADDR(start_erase_addr);

		if (start_erase_addr != FLASH_PAGE_ADDR(start_erase_addr)) {
			if (page_have_writen(FLASH_PAGE_ADDR(start_erase_addr), (start_erase_addr - 1)))
				page_need_save = TRUE;
		}
		else if (end_erase_addr != FLASH_PAGEEND_ADDR(end_erase_addr)) {
			if (page_have_writen((end_erase_addr + 1), FLASH_PAGEEND_ADDR(end_erase_addr)))
				page_need_save = TRUE;
		}

		if (page_need_save) {
			if (ERROR == __flash_read(write_buf, FLASH_PAGE_ADDR(start_erase_addr),
			                                                     FLASH_PAGE_SIZE >> 2))
				return ERROR;
		}

		if (ERROR == flash_page_erase(FLASH_PAGE_ADDR(start_erase_addr)))
			return ERROR;

		if (page_need_save) {
			para  = end_erase_addr & (FLASH_PAGE_SIZE - 1);
			index = start_erase_addr & (FLASH_PAGE_SIZE - 1);

			while (index <= para)
				temp_addr[index++] = 0xFF;

			index2 = 0;
			index  = FLASH_PAGE_ADDR(start_erase_addr);
			para   = FLASH_PAGE_ADDR(start_erase_addr) + FLASH_PAGE_SIZE;

			while (index < para) {
				if (ERROR == flash_word_program(index, write_buf[index2],
				                                write_buf[index2 + 1], FLASH_FIFO))
					return ERROR;

				index2 += 2;
				index  += FLASH_WORD_SIZE;
			}
		}
		len_not_erase = len_not_erase - (end_erase_addr - start_erase_addr + 1);
	}

	return OK;
}

/**
  * @brief  read the specified length bytes from flash, and store to the specified area.
  * @param  ram_addr: the specified area to store the reading bytes.
  * @param  addr: the start address.
  * @param  len: the length to read.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t flash_read(uint32_t *ram_addr, uint32_t addr, uint16_t len)
{
	uint32_t temp;

	assert_param(IS_4BYTES_ALIGN(ram_addr));
	assert_param(IS_FLASH_ADDRESS(addr));
	assert_param(IS_FLASH_ADDRESS(addr + len - 1));

	temp = (uint32_t)ram_addr;

	if ((temp & 0x3) != 0)
		return ERROR;

	return __flash_read(ram_addr, addr, len) == ERROR ? ERROR : OK;
}

/**
  * @}
  */
#endif

/**
  * @}
  */

/**
  * @}
  */
