#include "bsp.h"

#if defined (GD32F30X_HD) || defined (GD32F30X_XD) || defined (GD32F30X_CL)
#define FMC_FLAG_PGERR  FMC_FLAG_BANK0_PGERR
#define FMC_FLAG_PGAERR  FMC_FLAG_BANK0_PGERR
#define FMC_FLAG_WPERR  FMC_FLAG_BANK0_WPERR
#define FMC_FLAG_END  FMC_FLAG_BANK0_END
#endif

#define one_page_size 2048

#define data_bk_page_index 4
#define data_page_index 3
#define sn_page_index 2
#define magic_page_index 1 //must is the last page in 256K eara
static void _fmc_write_data(uint32_t addr, uint8_t *data, int len);
static void _fmc_read_data(uint32_t addr, uint8_t *data, int len);
static uint32_t _sn_addr(void);
static uint32_t _data_addr(int index);
static uint32_t _maigc_addr(void);

void fmc_write_sn(uint8_t *sn, int len){
	_fmc_write_data(_sn_addr(), sn, len);
}

void fmc_read_sn(uint8_t *sn, int len){
	_fmc_read_data(_sn_addr(), sn, len);
}

void fmc_write_data(int index, uint8_t *data, int len){
	_fmc_write_data(_data_addr(index), data, len);
}

void fmc_read_data(int index, uint8_t *data, int len){
	_fmc_read_data(_data_addr(index), data, len);
}

static __inline__ void _fmc_flag_clear(void) {
	fmc_flag_clear(FMC_FLAG_PGERR | FMC_FLAG_WPERR | FMC_FLAG_END);
}

void fmc_write_magic(uint32_t magic){
	uint32_t address = _maigc_addr();
	uint32_t length, checksum, value;

	value = REG32(address + 8);
	if (magic == value) {
		return;
	}

	length = REG32(address);
	checksum = REG32(address + 4);

	fmc_unlock();

	if (value != 0xFFFFFFFF) {
		_fmc_flag_clear();
		fmc_page_erase(address);

		_fmc_flag_clear();
		fmc_word_program(address, length);

		_fmc_flag_clear();
		fmc_word_program(address + 4, checksum);
	}

	if (magic != 0xFFFFFFFF) {
		_fmc_flag_clear();
		fmc_word_program(address + 8, magic);
	}

	fmc_lock();
}

uint32_t fmc_read_magic(void){
	uint32_t magic = 0x5555aaaa;
	_fmc_read_data(_maigc_addr(), (uint8_t *)&magic, sizeof(magic));
	return magic;
}

//if flash is lager than 256k, we just use the 256k
static uint32_t __inline__ _flash_capatity(void){
	uint32_t capacity;
	capacity = (REG32(0x1FFFF7E0) & 0xFFFF) << 10;
	if (capacity > (256 * 1024)){
		capacity =  256 * 1024;
	}
	return capacity;
}

static uint32_t _sn_addr(void){
	return 0x08000000 + (_flash_capatity() - one_page_size * sn_page_index);
}

static uint32_t _data_addr(int index){
	return 0x08000000 + (_flash_capatity() - one_page_size * (data_page_index + index));
}

static uint32_t _maigc_addr(void){
	return 0x08000000 + (_flash_capatity() - one_page_size * magic_page_index);
}

static void _fmc_read_data(uint32_t addr, uint8_t *data, int len){
	int i = 0;
	for (i = 0; i < len; i++){
		data[i] = REG8(addr + i);
	}
}

uint32_t fmc_get_addr(int page) {
	return 0x08000000 + (_flash_capatity() - one_page_size * page);
}


static void _fmc_write_data(uint32_t addr, uint8_t *data, int len){
	int page_count = (len + (one_page_size-1))/one_page_size;
	fmc_unlock();
	fmc_flag_clear(FMC_FLAG_PGERR | FMC_FLAG_WPERR | FMC_FLAG_END);
	for (int i = 0; i < page_count; i++) {
		fmc_page_erase(addr + i * one_page_size);
	}
	int total_words = len>>2;
	uint32_t *p_u32_data = (uint32_t *)data;
	int i;
	for (i = 0; i < total_words; i++){
		fmc_flag_clear(FMC_FLAG_PGERR | FMC_FLAG_WPERR | FMC_FLAG_END);
		fmc_word_program(addr + i * 4, p_u32_data[i]);
	}
	data += i * 4;
	addr += i * 4;
	total_words = len - total_words * 4;
	if (total_words > 0){
		if (total_words == 1){
			uint16_t half = *data;
			fmc_flag_clear(FMC_FLAG_PGERR | FMC_FLAG_WPERR | FMC_FLAG_END);
			fmc_halfword_program(addr, half);
		}else if (total_words == 2){
			uint16_t half = *((uint16_t *)data);
			fmc_flag_clear(FMC_FLAG_PGERR | FMC_FLAG_WPERR | FMC_FLAG_END);
			fmc_halfword_program(addr, half);
		}else {
			uint32_t words = *((uint32_t *)data);
			fmc_flag_clear(FMC_FLAG_PGERR | FMC_FLAG_WPERR | FMC_FLAG_END);
			fmc_word_program(addr, words);
		}
	}
	fmc_lock();
}

