/*
 * flash_eep.c
 *
 *  Created on: 19/01/2015
 *      Author: pvvx
 */
#include <string.h>
#include "types.h"
//#include "config.h"
#include "flash.h"
#include "flash_eep.h"

//-----------------------------------------------------------------------------
#define FEEP_ERR_PREFIX         "[FEEP Err]"
#define FEEP_WARN_PREFIX        "[FEEP Wrn]"
#define FEEP_INFO_PREFIX        "[FEEP Inf]"

#ifdef CONFIG_DEBUG_ERR_MSG
#define DBG_FEEP_ERR(...)     do {\
	if (likely(ConfigDebugErr & _DBG_FEEP_)) \
	dbg_printf(FEEP_ERR_PREFIX __VA_ARGS__); \
}while(0)
#else
#define DBG_FEEP_ERR(...)
#endif

#ifdef CONFIG_DEBUG_WARN_MSG
#define DBG_FEEP_WARN(...)     do {\
	if (likely(ConfigDebugWarn & _DBG_FEEP_)) \
	dbg_printf(FEEP_WARN_PREFIX __VA_ARGS__); \
}while(0)
#else
#define DBG_FEEP_WARN(...)
#endif

#ifdef CONFIG_DEBUG_INFO_MSG
#define DBG_FEEP_INFO(...)     do {\
	if (unlikely(ConfigDebugInfo & _DBG_FEEP_)) \
	dbg_printf(FEEP_INFO_PREFIX __VA_ARGS__); \
}while(0)
#else
#define DBG_FEEP_INFO(...)
#endif

#define _flash_mutex_lock()
#define _flash_mutex_unlock()
#define _flash_clear_cache()	// TLRS8xxx ?
#define _flash_erase_sector(addr) hal_flash_erase_sector(FLASH_BASE_ADDR + addr)
#define _flash_write_dword(addr, wd) flash_write_word(FLASH_BASE_ADDR + addr,wd)
#if MAX_FOBJ_SIZE > 256
// wraddr, len, pbuf
#define _flash_write(addr,len,pbuf) hal_flash_write(FLASH_BASE_ADDR + addr,(unsigned char *)pbuf, len)
#else
// wraddr, len, pbuf
#define _flash_write(addr,len,pbuf) hal_flash_write(FLASH_BASE_ADDR + addr,(unsigned char *)pbuf, len)
#endif

#ifndef LOCAL
#define LOCAL static
#endif
#ifndef true
#define true (1)
#endif
#ifndef false
#define false (0)
#endif

#ifndef mMIN
#define mMIN(a, b)  ((a < b)? a : b)
#endif
#define align(a) ((a + 3) & 0xFFFFFFFC)

#define FEEP_CODE_ATTR	__ATTR_SECTION_XIP__
#define FEEP_DATA_ATTR

typedef union __attribute__((packed)) // заголовок объекта сохранения feep
{
	struct {
	unsigned short size;
	unsigned short id;
	} __attribute__((packed)) n;
	unsigned int x;
} fobj_head;

#define fobj_head_size 4
#define fobj_x_free 0xffffffff
#define FMEM_ERROR_MAX 5


#define USE_BUFFER_RAM 0 // =1 RAM, =0 Stack
#if USE_BUFFER_RAM
//extern void *pvPortMalloc( size_t xWantedSize );
//extern void vPortFree( void *pv );
FEEP_DATA_ATTR
unsigned char buf_epp[MAX_FOBJ_SIZE+fobj_head_size];
#define eep_malloc(a)	buf_epp // pvPortMalloc(a)
#define eep_free(a)	// vPortFree(a)
#endif

#if 1 // =1 Use XIP
#define _flash_read_dword(a) (*(volatile uint32_t*)(FLASH_BASE_ADDR + (a)))
#define _flash_read(a,b,c) memcpy((void *)c, (void *)(FLASH_BASE_ADDR + (unsigned int)a), b) // _flash_read(rdaddr, len, pbuf);
#define _flash_memcmp(a,b,c) memcmp((void *)(FLASH_BASE_ADDR + (unsigned int)a), c, b) // _flash_memcmp(xfaddr + fobj_head_size, size, ptr) == 0)
#else
#define _flash_read(aadr,len,pbuf) hal_flash_read(FLASH_BASE_ADDR + addr, (u8 *) pbuf, len)


inline unsigned int _flash_read_dword(unsigned int addr) {
	unsigned int ret;
	_flash_read(FLASH_BASE_ADDR + addr, 4, &ret);
	return ret;
}

inline unsigned int _flash_memcmp(unsigned int addr, unsigned int len, unsigned char * buf) {
	_flash_read(FLASH_BASE_ADDR + addr, len, &buf_epp);
	return memcmp(buf_epp, buf, len);
}
#endif

/*
void flash_erase_sector_bt(unsigned int addr) {
	if(bls_ll_requestConnBrxEventDisable() > 256) {
		bls_ll_disableConnBrxEvent();
		flash_erase_sector(addr);
		bls_ll_restoreConnBrxEvent();
	} else
		flash_erase_sector(addr);
}
*/

//-----------------------------------------------------------------------------
// FunctionName : get_addr_bscfg
// поиск текушего сегмента
// Return     : адрес сегмента
// ret < FMEM_ERROR_MAX - ошибка
//-----------------------------------------------------------------------------
FEEP_CODE_ATTR
LOCAL unsigned int get_addr_bscfg(void)
{
	unsigned int x1 = 0xFFFFFFFF, x2;
	unsigned int faddr = FMEMORY_SCFG_BASE_ADDR;
	unsigned int reta = FMEMORY_SCFG_BASE_ADDR;
	do {
		x2 = _flash_read_dword(faddr); // if (flash_read(faddr, &x2, 4)) return -(FMEM_FLASH_ERR);
		if (x2 < x1) { // поиск текущего сегмента
			x1 = x2;
			reta = faddr; // новый адрес сегмента для записи
		};
		faddr += FMEMORY_SCFG_BANK_SIZE;
	} while (faddr < (FMEMORY_SCFG_BASE_ADDR + FMEMORY_SCFG_BANKS * FMEMORY_SCFG_BANK_SIZE));

	if ((x1 == 0xFFFFFFFF)&&(reta == FMEMORY_SCFG_BASE_ADDR)) { // первый старт?
		_flash_write_dword(reta, 0x7FFFFFFF); // if (flash_write(reta, &x1, 4)) return -(FMEM_FLASH_ERR);
	}
#if CONFIG_DEBUG_LOG > 3
	DBG_FEEP_INFO("base seg: %p [%p]\n", reta, _flash_read_dword(reta));
#endif
	return reta;
}
//-----------------------------------------------------------------------------
// FunctionName : get_addr_fobj
// Опции:
//  false - Поиск последней записи объекта по id и size
//  true - Поиск присуствия записи объекта по id и size
// Returns : адрес записи данных объекта
// 0 - не найден
// ret < FMEM_ERROR_MAX - ошибка
//-----------------------------------------------------------------------------
FEEP_CODE_ATTR
LOCAL unsigned int get_addr_fobj(unsigned int base, fobj_head *obj, bool flg)
{
//	if (base == 0) return 0;
	fobj_head fobj;
	unsigned int faddr = base + 4;
	unsigned int fend = base + FMEMORY_SCFG_BANK_SIZE - align(fobj_head_size);
	unsigned int reta = 0;
	do {
		fobj.x = _flash_read_dword(faddr); // if (flash_read(faddr, &fobj, fobj_head_size)) return -(FMEM_FLASH_ERR);
		if (fobj.x == fobj_x_free) break;
		if (fobj.n.size <= MAX_FOBJ_SIZE) {
			if (fobj.n.id == obj->n.id) {
				if (flg) {
					return faddr;
				}
				obj->n.size = fobj.n.size;
				reta = faddr;
			}
			faddr += align(fobj.n.size + fobj_head_size);
		}
		else faddr += align(MAX_FOBJ_SIZE + fobj_head_size);
	}
	while (faddr < fend);
	return reta;
}
//-----------------------------------------------------------------------------
// FunctionName : get_addr_fend
// Поиск последнего адреса в сегменте для записи объекта
// Returns : адрес для записи объекта
// ret < FMEM_ERROR_MAX - ошибка
// ret = 0 - не влезет, на pack
//-----------------------------------------------------------------------------
FEEP_CODE_ATTR
LOCAL unsigned int get_addr_fobj_save(unsigned int base, fobj_head obj)
{
	fobj_head fobj;
	unsigned int faddr = base + 4;
	unsigned int fend = base + FMEMORY_SCFG_BANK_SIZE - align(obj.n.size + fobj_head_size);
	do {
		fobj.x = _flash_read_dword(faddr); // if (flash_read(faddr, &fobj, fobj_head_size)) return -(FMEM_FLASH_ERR);
		if (fobj.x == fobj_x_free) {
			if (faddr < fend) {
				return faddr;
			}
			return 0; // не влезет, на pack
		}
		if (fobj.n.size <= MAX_FOBJ_SIZE) {
			faddr += align(fobj.n.size + fobj_head_size);
		}
		else faddr += align(MAX_FOBJ_SIZE + fobj_head_size);
	}
	while (faddr < fend);
	return 0; // не влезет, на pack
}
//=============================================================================
// FunctionName : pack_cfg_fmem
// Returns      : адрес для записи объекта
//-----------------------------------------------------------------------------
FEEP_CODE_ATTR
LOCAL unsigned int pack_cfg_fmem(fobj_head obj)
{
	fobj_head fobj;
	unsigned int foldseg = get_addr_bscfg(); // поиск текушего сегмента
//	if (foldseg < FMEM_ERROR_MAX) return fnewseg; // error
	unsigned int fnewseg = foldseg + FMEMORY_SCFG_BANK_SIZE;
	if (fnewseg >= (FMEMORY_SCFG_BASE_ADDR + FMEMORY_SCFG_BANKS * FMEMORY_SCFG_BANK_SIZE))
		fnewseg = FMEMORY_SCFG_BASE_ADDR;
	unsigned int faddr = foldseg;
	unsigned int rdaddr, wraddr;
	unsigned short len;
#if	USE_BUFFER_RAM
	unsigned int * pbuf = (unsigned int *) eep_malloc(align(MAX_FOBJ_SIZE + fobj_head_size) >> 2);
	if (pbuf == NULL) {
		DBG_FEEP_ERR("pack malloc error!\n");
		return -(FMEM_MEM_ERR);
	}
#else
	unsigned int eep_buf[(MAX_FOBJ_SIZE + fobj_head_size) >> 2];
	unsigned int * pbuf = eep_buf;
#endif
#if CONFIG_DEBUG_LOG > 3
	DBG_FEEP_INFO("repack base to new seg: %p\n", fnewseg);
#endif
	if (_flash_read_dword(fnewseg) != 0xFFFFFFFF)
		_flash_erase_sector(fnewseg); // if (flash_erase_sector(fnewseg)) return -(FMEM_FLASH_ERR);
	_flash_write_dword(fnewseg, 0x7FFFFFFF); // сегмент занят
	faddr += 4;
	wraddr = fnewseg + 4;
	do {
		fobj.x = _flash_read_dword(faddr); //if (flash_read(faddr, &fobj, fobj_head_size)) return -(FMEM_FLASH_ERR); // последовательное чтение id из старого сегмента
		if (fobj.x == fobj_x_free) break;
		if (fobj.n.size > MAX_FOBJ_SIZE) len = align(MAX_FOBJ_SIZE + fobj_head_size);
		else len = align(fobj.n.size + fobj_head_size);
		if (fobj.n.id != obj.n.id &&  fobj.n.size <= MAX_FOBJ_SIZE) { // объект валидный
			if (get_addr_fobj(fnewseg, &fobj, true) == 0) { // найдем, сохранили ли мы его уже? нет
				rdaddr = get_addr_fobj(foldseg, &fobj, false); // найдем последнее сохранение объекта в старом сенгменте, size изменен
				if (rdaddr < FMEM_ERROR_MAX) return rdaddr; // ???
				if (wraddr + len >= fnewseg + FMEMORY_SCFG_BANK_SIZE) {
					DBG_FEEP_ERR("pack segment overflow!\n");
					return -(FMEM_OVR_ERR);
				};
				_flash_read(rdaddr, len, pbuf);
				// перепишем данные obj в новый сектор
				_flash_write(wraddr, len, pbuf);
			};
		};
		faddr += len;
	} while (faddr  < (foldseg + FMEMORY_SCFG_BANK_SIZE - align(fobj_head_size+1)));
#if	USE_BUFFER_RAM
	eep_free(pbuf);
#endif
	// обратный счетчик стираний/записей секторов как id
	_flash_write_dword(fnewseg, (_flash_read_dword(foldseg) - 1)); // if (flash_write(fnewseg, &foldseg + SPI_FLASH_BASE, 4)) return -(FMEM_FLASH_ERR);
	_flash_erase_sector(foldseg);
#if CONFIG_DEBUG_LOG > 3
	DBG_FEEP_INFO("free: %d\n", FMEMORY_SCFG_BANK_SIZE - (faddr & (FMEMORY_SCFG_BANK_SIZE-1)));
#endif
	return get_addr_fobj_save(fnewseg, obj); // адрес для записи объекта;
}
//-----------------------------------------------------------------------------
FEEP_CODE_ATTR
LOCAL signed short _flash_write_cfg(void *ptr, unsigned short id, unsigned short size)
{
	fobj_head fobj;
	fobj.n.id = id;
	fobj.n.size = size;
//	bool retb = false;
	unsigned int faddr = get_addr_bscfg();

	if (faddr >= FMEM_ERROR_MAX)  {
		unsigned int xfaddr = get_addr_fobj(faddr, &fobj, false);
		if (xfaddr > FMEM_ERROR_MAX && size == fobj.n.size) {
			if (size == 0
					|| _flash_memcmp(xfaddr + fobj_head_size, size, ptr) == 0) {
#if CONFIG_DEBUG_LOG > 3
					DBG_FEEP_INFO("write obj is identical, id: %04x [%d]\n", id, size);
#endif
					return size; // уже записано то-же самое
			}
#if CONFIG_DEBUG_LOG > 100
			else {
				int i;
				uint8_t * p = (uint8_t *)(SPI_FLASH_BASE + xfaddr + fobj_head_size);
				uint8_t * r = (uint8_t *) ptr;
				for(i=0; i < size; i+=8) {
					dbg_printf("buf[%d]\t%02X %02X %02X %02X  %02X %02X %02X %02X\n",
								i, r[i], r[i+1], r[i+2], r[i+3], r[i+4], r[i+5], r[i+6], r[i+7]);
					dbg_printf("obj[%d]\t%02X %02X %02X %02X  %02X %02X %02X %02X\n",
								i, p[i], p[i+1], p[i+2], p[i+3], p[i+4], p[i+5], p[i+6], p[i+7]);
				}
			}
#endif
		}
	}
	DBG_FEEP_INFO("write obj id: %04x [%d]\n", id, size);
	fobj.n.size = size;
//	flash_write_protect(&flashobj, 0); // Flash Unprotect
	faddr = get_addr_fobj_save(faddr, fobj);
	if (faddr == 0) {
		faddr = pack_cfg_fmem(fobj);
		if (faddr == 0) {
			DBG_FEEP_ERR("banks overflow!\n");
			return FMEM_NOT_FOUND;
		}
	}
	else if (faddr < FMEM_ERROR_MAX) return - faddr - 1; // error

#if CONFIG_DEBUG_LOG > 3
	DBG_FEEP_INFO("write obj to faddr %p\n", faddr);
#endif
	_flash_write_dword(faddr, fobj.x); // if (flash_write(faddr, &fobj.x, 4)) return FMEM_FLASH_ERR;
	faddr+=4;
#if 1
	uint32_t len = (size + 3) & (~3);
	if (len) _flash_write(faddr, len, ptr);
#else
	union {
		unsigned char uc[4];
		unsigned int ud;
	}tmp;
	uint32_t len = (size + 3) >> 2;
	unsigned char * ps = ptr;
  	while (len--) {
		tmp.uc[0] = *ps++;
		tmp.uc[1] = *ps++;
		tmp.uc[2] = *ps++;
		tmp.uc[3] = *ps++;
		_flash_write_dword(faddr, tmp.ud); // if (flash_write(faddr, &tmp.ud, 4)) return FMEM_FLASH_ERR;
		faddr += 4;
	}
#endif
	_flash_clear_cache();
	return size;
}
//=============================================================================
//- Сохранить объект в flash --------------------------------------------------
//  Returns	: false/true
//-----------------------------------------------------------------------------
FEEP_CODE_ATTR
bool flash_write_cfg(void *ptr, unsigned short id, unsigned short size)
{
	bool retb = false;
	if (size > MAX_FOBJ_SIZE) return retb;
	_flash_mutex_lock();
	if (_flash_write_cfg(ptr, id, size) >= 0) {
#if CONFIG_DEBUG_LOG > 3
		DBG_FEEP_INFO("saved ok\n");
#endif
		retb = true;
	}
	_flash_mutex_unlock();
	return retb;
}
//=============================================================================
//- Прочитать объект из flash -------------------------------------------------
//  Параметры:
//   prt - указатель, куда сохранить
//   id - идентификатор искомого объекта
//   maxsize - сколько байт сохранить максимум из найденного объекта, по ptr
//  Returns:
//  -3 - error
//  -2 - flash rd/wr/clr error
//  -1 - не найден
//   0..MAX_FOBJ_SIZE - ok, сохраненный размер объекта
//-----------------------------------------------------------------------------
FEEP_CODE_ATTR
signed short flash_read_cfg(void *ptr, unsigned short id, unsigned short maxsize)
{
    signed short rets = FMEM_ERROR;
	if (maxsize <= MAX_FOBJ_SIZE) {
		_flash_mutex_lock();
		fobj_head fobj;
		fobj.n.id = id;
		fobj.n.size = 0;
		DBG_FEEP_INFO("read obj id: %04x[%d]\n", id, maxsize);
		unsigned int faddr = get_addr_bscfg();
		if (faddr >= FMEM_ERROR_MAX) {
			faddr = get_addr_fobj(faddr, &fobj, false);
			if (faddr >= FMEM_ERROR_MAX) {
				if (maxsize != 0 && ptr != NULL)
					_flash_read(faddr + fobj_head_size, mMIN(fobj.n.size, maxsize), ptr);

#if CONFIG_DEBUG_LOG > 3
				DBG_FEEP_INFO("read ok, faddr: %p, size: %d\n", faddr,  fobj.n.size);
#endif
				rets = fobj.n.size;
			}
			else {
#if CONFIG_DEBUG_LOG > 3
				DBG_FEEP_INFO("obj not found\n");
#endif
				rets = -faddr-1;
			}
		}
		else rets = -faddr-1;
		_flash_mutex_unlock();
	}
    return rets;
}
//=============================================================================
FEEP_CODE_ATTR
bool flash_supported_eep_ver(unsigned int min_ver, unsigned int new_ver) {
	unsigned int tmp;
	unsigned int faddr = FMEMORY_SCFG_BASE_ADDR;
	_flash_mutex_lock();
	// flash_unlock(); // Flash Unprotect, in user_init_normal()
	if (flash_read_cfg(&tmp, EEP_ID_VER, sizeof(tmp)) == sizeof(tmp) && tmp >= min_ver) {
		if(tmp != new_ver) {
			tmp = new_ver;
			flash_write_cfg(&tmp, EEP_ID_VER, sizeof(tmp));
		}
		_flash_mutex_unlock();
		return true;
	}
	do{
		tmp = _flash_read_dword(faddr);
		_flash_erase_sector(faddr);
		_flash_write_dword(faddr, --tmp);
		faddr += FLASH_SECTOR_SIZE;
	} while (faddr < FLASH_SIZE);
	_flash_clear_cache();
	tmp = new_ver;
	flash_write_cfg(&tmp, EEP_ID_VER, sizeof(tmp));
	_flash_mutex_unlock();
	return false;
}

