/**
  *********************************************************************************
  *
  * @file    ald_flash_ext.c
  * @brief   FLASH extra module driver.
  *
  * @version V1.0
  * @date    17 Jun 2019
  * @author  AE Team
  * @note
  *          Change Logs:
  *          Date            Author          Notes
  *          17 Jun 2019     AE Team         The first version
  *
  * Copyright (C) Shanghai Eastsoft Microelectronics Co. Ltd. All rights reserved.
  *
  * SPDX-License-Identifier: Apache-2.0
  *
  * Licensed under the Apache License, Version 2.0 (the License); you may
  * not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  * www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an AS IS BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  **********************************************************************************
  * @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 ald_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 ald_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 ald_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 "ald_conf.h"


/** @addtogroup ES32FXXX_ALD
  * @{
  */

/** @addtogroup FLASH
  * @{
  */

#ifdef ALD_FLASH

/** @addtogroup Flash_Private_Variables
  * @{
  */
/* opration buffer*/
static uint8_t write_buf[FLASH_PAGE_SIZE];
/**
  * @}
  */

/** @addtogroup Flash_Private_Functions
  * @{
  */

/**
  * @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_Public_Functions Flash Public Functions
  * @verbatim
 ===============================================================================
              ##### Flash operation functions #####
 ===============================================================================
  [..]
    This section provides functions allowing to operate flash, such as read and write.

    @endverbatim
  * @{
  */

/**
  * @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 ald_status_t.
  */
ald_status_t ald_flash_read(uint32_t *ram_addr, uint32_t addr, uint16_t len)
{
	uint32_t i;
	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) || (((addr) & 0x3) != 0))
		return ERROR;

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

	return OK;
}

/**
  * @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,and multiple of 2.
  * @retval Status, see @ref ald_status_t.
  */

ald_status_t ald_flash_write(uint32_t addr, uint8_t *buf, uint16_t len)
{
	uint32_t index = 0;
	uint32_t para = 0;
	uint32_t index2 = 0;
	uint32_t start_write_addr;
	uint32_t end_write_addr;
	uint32_t start_word_addr;
	uint32_t end_word_addr;
	uint16_t len_to_write;
	uint32_t len_index;
	type_bool_t need_erase_page;

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

	len_to_write = len;

	__disable_irq();
	while (len_to_write > 0) {
		need_erase_page = FALSE;

		for (index = 0; index < FLASH_PAGE_SIZE; index++)
			write_buf[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 == ald_flash_read((uint32_t *)write_buf, FLASH_PAGE_ADDR(start_write_addr),
                                 		  FLASH_PAGE_SIZE >> 2)) {
				__enable_irq();
				return ERROR;
			}

			if (ERROR == flash_page_erase(FLASH_PAGE_ADDR(start_write_addr))) {
				__enable_irq();
				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)
				write_buf[index++] = buf[index2++];

			index2     = 0;
			index      = FLASH_PAGE_ADDR(start_write_addr);
			len_index  = FLASH_PAGE_SIZE;
		}
		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)
				write_buf[index++] = buf[index2++];

			start_word_addr = FLASH_WORD_ADDR(start_write_addr);
			end_word_addr   = FLASH_WORDEND_ADDR(end_write_addr);
			index2          = (FLASH_WORD_ADDR(start_word_addr) - FLASH_PAGE_ADDR(start_word_addr));
			index           = start_word_addr;
			len_index       = end_word_addr - start_word_addr + 1;
		}

		if (ERROR == flash_word_program(index, (uint32_t *)(write_buf + index2), (len_index >> 3), FLASH_FIFO)) {
			__enable_irq();
			return ERROR;
		}

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

	__enable_irq();
	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 ald_status_t.
  */
ald_status_t ald_flash_erase(uint32_t addr, uint16_t len)
{
	uint32_t index;
	int32_t para;
	int32_t start_erase_addr;
	int32_t end_erase_addr;
	uint16_t len_not_erase;
	uint32_t len_index;
	type_bool_t page_need_save;

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

	len_not_erase = len;

	__disable_irq();
	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;
		}
		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 == ald_flash_read((uint32_t *)write_buf, FLASH_PAGE_ADDR(start_erase_addr),
			                                                     FLASH_PAGE_SIZE >> 2)) {
				__enable_irq();
				return ERROR;
			}
		}

		if (ERROR == flash_page_erase(FLASH_PAGE_ADDR(start_erase_addr))) {
			__enable_irq();
			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)
				write_buf[index++] = 0xFF;

			index     = FLASH_PAGE_ADDR(start_erase_addr);
			len_index = FLASH_PAGE_SIZE;
			if (ERROR == flash_word_program(index, (uint32_t *)write_buf, (len_index >> 3), FLASH_FIFO)) {
				__enable_irq();
				return ERROR;
			}
		}
		len_not_erase = len_not_erase - (end_erase_addr - start_erase_addr + 1);
	}

	__enable_irq();
	return OK;
}
/**
  * @}
  */


#endif

/**
  * @}
  */

/**
  * @}
  */
