/**
  **************************************************************************
  * @file     flash.c
  * @brief    flash program
  **************************************************************************
  *                       Copyright notice & Disclaimer
  *
  * The software Board Support Package (BSP) that is made available to
  * download from Artery official website is the copyrighted work of Artery.
  * Artery authorizes customers to use, copy, and distribute the BSP
  * software and its related documentation for the purpose of design and
  * development in conjunction with Artery microcontrollers. Use of the
  * software is governed by this copyright notice and the following disclaimer.
  *
  * THIS SOFTWARE IS PROVIDED ON "AS IS" BASIS WITHOUT WARRANTIES,
  * GUARANTEES OR REPRESENTATIONS OF ANY KIND. ARTERY EXPRESSLY DISCLAIMS,
  * TO THE FULLEST EXTENT PERMITTED BY LAW, ALL EXPRESS, IMPLIED OR
  * STATUTORY OR OTHER WARRANTIES, GUARANTEES OR REPRESENTATIONS,
  * INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
  *
  **************************************************************************
  */

#include "bsp_flash.h"
#include "string.h"

/** @addtogroup AT32A423_periph_examples
  * @{
  */

#if defined (AT32A423xC)
#define SECTOR_SIZE                      2048   /* this parameter depends on the specific model of the chip */
#else
#define SECTOR_SIZE                      1024   /* this parameter depends on the specific model of the chip */
#endif

#define FLASH_HEAD 0XA55AA55A
#define FLASH_TAIL 0X5AA55AA5

uint16_t flash_buf[SECTOR_SIZE / 2];

/**
  * @brief  read data using halfword mode
  * @param  read_addr: the address of reading
  * @param  p_buffer: the buffer of reading data
  * @param  num_read: the number of reading data
  * @retval 0 = success, 1 = fail
  */
uint8_t flash_read(uint32_t read_addr, void *p_buffer, uint32_t num_read)
{
    uint16_t *buf = (uint16_t *)p_buffer;
    uint32_t i;

    for(i = 0; i < num_read; i++)
    {
        buf[i] = *(uint16_t*)(read_addr);
        read_addr += 2;
    }
    return 0;
}

/**
  * @brief  write data using halfword mode without checking
  * @param  write_addr: the address of writing
  * @param  p_buffer: the buffer of writing data
  * @param  num_write: the number of writing data
  * @retval 0 = success, 1 = fail
  */
uint8_t flash_write_nocheck(uint32_t write_addr, const void *p_buffer, uint32_t num_write)
{
    uint16_t *buf = (uint16_t *)p_buffer;
    uint32_t i;
    flash_status_type status = FLASH_OPERATE_DONE;

    for(i = 0; i < num_write; i++)
    {
        status = flash_halfword_program(write_addr, buf[i]);
        if(status != FLASH_OPERATE_DONE)
            return 1;
        write_addr += 2;
    }
    return 0;
}

/**
  * @brief  write data using halfword mode with checking
  * @param  write_addr: the address of writing
  * @param  p_buffer: the buffer of writing data
  * @param  num_write: the number of writing data
  * @retval 0 = success, 1 = fail
  */
uint8_t flash_write(uint32_t write_addr, const void *p_buffer, uint32_t num_write)
{
    uint16_t *buf = (uint16_t *)p_buffer;
    uint32_t offset_addr;
    uint32_t sector_position;
    uint32_t sector_offset;
    uint32_t sector_remain;
    uint32_t i;
    flash_status_type status = FLASH_OPERATE_DONE;

    flash_unlock();
    offset_addr = write_addr - FLASH_BASE;
    sector_position = offset_addr / SECTOR_SIZE;
    sector_offset = (offset_addr % SECTOR_SIZE) / 2;
    sector_remain = (SECTOR_SIZE / 2) - sector_offset;

    if(num_write <= sector_remain)
        sector_remain = num_write;

    while(1)
    {
        flash_read(sector_position * SECTOR_SIZE + FLASH_BASE, flash_buf, SECTOR_SIZE / 2);
        for(i = 0; i < sector_remain; i++)
        {
            if(flash_buf[sector_offset + i] != 0xFFFF)
                break;
        }
        if(i < sector_remain)
        {
            status = flash_operation_wait_for(ERASE_TIMEOUT);

            if((status == FLASH_PROGRAM_ERROR) || (status == FLASH_EPP_ERROR))
                flash_flag_clear(FLASH_PRGMERR_FLAG | FLASH_EPPERR_FLAG);
            else if(status == FLASH_OPERATE_TIMEOUT)
            {
                flash_lock();
                return 1;
            }
            status = flash_sector_erase(sector_position * SECTOR_SIZE + FLASH_BASE);
            if(status != FLASH_OPERATE_DONE)
            {
                flash_lock();
                return 1;
            }
            for(i = 0; i < sector_remain; i++)
            {
                flash_buf[i + sector_offset] = buf[i];
            }
            if(flash_write_nocheck(sector_position * SECTOR_SIZE + FLASH_BASE, flash_buf, SECTOR_SIZE / 2) != 0)
            {
                flash_lock();
                return 1;
            }
        }
        else
        {
            if(flash_write_nocheck(write_addr, buf, sector_remain) != 0)
            {
                flash_lock();
                return 1;
            }
        }
        if(num_write == sector_remain)
            break;
        else
        {
            sector_position++;
            sector_offset = 0;
            buf += sector_remain;
            write_addr += (sector_remain * 2);
            num_write -= sector_remain;
            if(num_write > (SECTOR_SIZE / 2))
                sector_remain = SECTOR_SIZE / 2;
            else
                sector_remain = num_write;
        }
    }
    flash_lock();
    return 0;
}

int load_config_params(flash_para_struct *params)
{
    uint32_t *head = (uint32_t*)FMC_WRITE_START_ADDR;
    uint32_t *tail = (uint32_t*)(FMC_WRITE_START_ADDR + sizeof(flash_para_struct) - 4);

    if(*head == FLASH_HEAD && *tail == FLASH_TAIL)
    {
        memcpy((uint32_t *)params, head, sizeof(flash_para_struct));
        return 1;
    }
    else
    {
        return -1;
    }
}

int save_config_params(flash_para_struct *params)
{
    params->flash_head = FLASH_HEAD;
    params->flash_tail = FLASH_TAIL;
    flash_write(FMC_WRITE_START_ADDR, params, sizeof(flash_para_struct)/sizeof(uint16_t));
    return 0;
}

/**
 * @brief 擦除指定扇区
 * @param sector_addr 扇区起始地址
 * @param len 擦除长度（未使用，保持接口兼容）
 * @return 0 = success, 1 = fail
 */
uint8_t flash_erase(uint32_t sector_addr, uint32_t len)
{
    flash_status_type status;

    flash_unlock();
    status = flash_operation_wait_for(ERASE_TIMEOUT);
    if(status == FLASH_OPERATE_TIMEOUT)
    {
        flash_lock();
        return 1;
    }

    if((status == FLASH_PROGRAM_ERROR) || (status == FLASH_EPP_ERROR))
    {
        flash_flag_clear(FLASH_PRGMERR_FLAG | FLASH_EPPERR_FLAG);
    }

    status = flash_sector_erase(sector_addr);
    flash_lock();

    return (status == FLASH_OPERATE_DONE) ? 0 : 1;
}

/**
  * @}
  */

/**
  * @}
  */

uint8_t bsp_flash_is_erased(uint32_t addr, uint32_t size)
{
    const uint8_t *flash_ptr = (const uint8_t *)addr;
    uint32_t i;

    for (i = 0U; i < size; i++)
    {
        if (flash_ptr[i] != 0xFF)
        {
            return 0;
        }
    }
    return 1;
}

