/*
 * This file is part of the EasyFlash Library.
 *
 * Copyright (c) 2015, Armink, <armink.ztl@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * 'Software'), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Function: Portable interface for SFUD flash driver.
 * Created on: 2015-01-16
 */

#include <easyflash.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#if !WINDOWS && !PLATFORM_TXW81X && !PLATFORM_RDA5981 && !LINUX && !defined(PLATFORM_W800) && !defined(PLATFORM_W600)
#include "FreeRTOS.h"
#include "semphr.h"
#include "queue.h"
#endif

#if PLATFORM_REALTEK

#include "flash_api.h"
#include "device_lock.h"

flash_t flash;

#elif defined(PLATFORM_W800) || defined(PLATFORM_W600)

#include "FreeRTOS.h"
#include "rtoslist.h"
#include "semphr.h"
#include "rtosqueue.h"
#include "wm_internal_flash.h"
#include "wm_flash.h"
#define QueueHandle_t xQueueHandle

#elif PLATFORM_XRADIO

#include "driver/chip/hal_flash.h"
#include <image/flash.h>
#define QueueHandle_t xQueueHandle

#elif PLATFORM_TXW81X

#include "sys_config.h"
#include "typesdef.h"
#include "csi_kernel.h"
#include "osal/csky/defs.h"
#include "dev.h"
#include "hal/spi_nor.h"
#include "lib/syscfg/syscfg.h"
#include "osal/csky/string.h"

typedef k_mutex_handle_t QueueHandle_t;
#define xSemaphoreCreateMutex csi_kernel_mutex_new
#define xSemaphoreTake(a, b) csi_kernel_mutex_lock(a, b, 0)
#define xSemaphoreGive(a) csi_kernel_mutex_unlock(a)
extern struct spi_nor_flash* obk_flash;

#elif PLATFORM_RDA5981

#include "stdbool.h"
#include "rda_sys_wrapper.h"

typedef void* QueueHandle_t;
#define xSemaphoreCreateMutex rda_mutex_create
#define xSemaphoreTake rda_mutex_wait
#define xSemaphoreGive rda_mutex_realease

#elif PLATFORM_BEKEN

#include "include.h"
#include "flash_pub.h"
#include "rtos_pub.h"
#include "uart_pub.h"
#include "drv_model_pub.h"
#include "flash.h"
extern int hal_flash_lock(void);
extern int hal_flash_unlock(void);

#elif PLATFORM_BL_NEW

#include "bflb_mtd.h"

__attribute__((aligned(32))) static bflb_mtd_handle_t handle;
uint32_t ENV_AREA_SIZE;
uint32_t SECTOR_NUM;

#elif WINDOWS

#include "framework.h"

#define QueueHandle_t HANDLE
extern QueueHandle_t ef_mutex;

BYTE* env_area = NULL;
uint32_t ENV_AREA_SIZE = 0;

DllExport BYTE* get_env_area(void)
{
	return env_area;
}

DllExport void set_env_size(uint32_t size)
{
	ENV_AREA_SIZE = size;
	if(env_area) free(env_area);
	env_area = malloc(size * sizeof(BYTE));
}

HANDLE xSemaphoreCreateMutex()
{
	return CreateMutex(NULL, FALSE, NULL);
}

void xSemaphoreTake(HANDLE handle, int time)
{
	WaitForSingleObject(ef_mutex, time);
}

void xSemaphoreGive(HANDLE handle)
{
	ReleaseMutex(ef_mutex);
}

#elif LINUX

#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <pthread.h>

#define QueueHandle_t pthread_mutex_t
extern QueueHandle_t ef_mutex;

uint8_t* env_area = NULL;
uint32_t ENV_AREA_SIZE = 0;

DllExport uint8_t* get_env_area(void)
{
	return env_area;
}

DllExport void set_env_size(uint32_t size)
{
	ENV_AREA_SIZE = size;
	if(env_area) free(env_area);
	env_area = malloc(size * sizeof(uint8_t));
}

QueueHandle_t xSemaphoreCreateMutex()
{
	pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
	pthread_mutex_init(&mutex, NULL);
	return mutex;
}

void xSemaphoreTake(QueueHandle_t handle, int time)
{
	pthread_mutex_lock(&handle);
}

void xSemaphoreGive(QueueHandle_t handle)
{
	pthread_mutex_unlock(&handle);
}

#endif

/* default ENV set for user */
static const ef_env default_env_set[] =
{
#if PLATFORM_BL_NEW
	{"boot_times", "3", 1}
#else
	{"nv_version","0.01"}
#endif
};

QueueHandle_t ef_mutex;

/**
 * Flash port for hardware initialize.
 *
 * @param default_env default ENV set for user
 * @param default_env_size default ENV size
 *
 * @return result
 */
EfErrCode ef_port_init(ef_env const** default_env, size_t* default_env_size)
{
	EfErrCode result = EF_NO_ERR;

	*default_env = default_env_set;
	*default_env_size = sizeof(default_env_set) / sizeof(default_env_set[0]);

#if configUSE_RECURSIVE_MUTEXES && PLATFORM_BL_NEW
	ef_mutex = xSemaphoreCreateRecursiveMutex();
#else
	ef_mutex = xSemaphoreCreateMutex();
#endif

#if PLATFORM_BL_NEW
	int ret;
	__attribute__((aligned(32))) bflb_mtd_info_t info;

	ret = bflb_mtd_open(BFLB_MTD_PARTITION_NAME_PSM, &handle, BFLB_MTD_OPEN_FLAG_BUSADDR);
	if(ret < 0)
	{
		EF_INFO("[EF] [PART] [XIP] error when get PSM partition %d\r\n", ret);
		puts("[EF] [PART] [XIP] Dead Loop. Reason: no Valid PSM partition found\r\n");
		while(1)
		{
		}
	}
	memset(&info, 0, sizeof(info));
	bflb_mtd_info(handle, &info);
	EF_INFO("[EF] Found Valid PSM partition, XIP Addr %08x, flash addr %08x, size %d\r\n",
		info.xip_addr,
		info.offset,
		info.size
	);
	if(info.size < 8 * 1024)
	{
		printf("[ERROR]psm partition is less than 8k,easyflash can not work!");
		while(1);
	}
	ENV_AREA_SIZE = (info.size / EF_ERASE_MIN_SIZE) * EF_ERASE_MIN_SIZE;
	SECTOR_NUM = ENV_AREA_SIZE / EF_ERASE_MIN_SIZE;
	printf("ENV AREA SIZE %ld, SECTOR NUM %ld\r\n", ENV_AREA_SIZE, SECTOR_NUM);

	printf("*default_env_size = 0x%08x\r\n", *default_env_size);
#endif

	return result;
}

/**
 * Read data from flash.
 * @note This operation's units is word.
 *
 * @param addr flash address
 * @param buf buffer to store read data
 * @param size read bytes size
 *
 * @return result
 */
EfErrCode ef_port_read(uint32_t addr, uint32_t* buf, size_t size)
{
#if PLATFORM_REALTEK
	device_mutex_lock(RT_DEV_LOCK_FLASH);
	int res = flash_stream_read(&flash, addr, size, buf);
	device_mutex_unlock(RT_DEV_LOCK_FLASH);
	if(res) return EF_NO_ERR;
	else return EF_READ_ERR;
#elif defined(PLATFORM_W800) || defined(PLATFORM_W600)
	int res = tls_fls_read(addr, (uint8_t*)buf, size);
	if(res != TLS_FLS_STATUS_OK) return EF_READ_ERR;
	else return EF_NO_ERR;
#elif PLATFORM_XRADIO
	int res = flash_rw(0, addr, (void*)buf, size, 0);
	if(res == 0) res = EF_READ_ERR;
	else res = EF_NO_ERR;
	return res;
#elif WINDOWS || LINUX
	memcpy(buf, env_area + addr, size);
	return EF_NO_ERR;
#elif PLATFORM_TXW81X || PLATFORM_RDA5981
	HAL_FlashRead(buf, size, addr);
	return EF_NO_ERR;
#elif PLATFORM_BEKEN
	hal_flash_lock();
	flash_read((char*)buf, (unsigned long)size, addr);
	hal_flash_unlock();
	return EF_NO_ERR;
#elif PLATFORM_BL_NEW
	if(bflb_mtd_read(handle, addr, size, (uint8_t*)buf) < 0)
	{
		return EF_READ_ERR;
	}
	return EF_NO_ERR;
#endif
}

/**
 * Erase data on flash.
 * @note This operation is irreversible.
 * @note This operation's units is different which on many chips.
 *
 * @param addr flash address
 * @param size erase bytes size
 *
 * @return result
 */
EfErrCode ef_port_erase(uint32_t addr, size_t size)
{
	EfErrCode result = EF_NO_ERR;

	/* make sure the start address is a multiple of FLASH_ERASE_MIN_SIZE */
	EF_ASSERT(addr % EF_ERASE_MIN_SIZE == 0);

#if PLATFORM_REALTEK
	device_mutex_lock(RT_DEV_LOCK_FLASH);
	flash_erase_sector(&flash, addr);
	device_mutex_unlock(RT_DEV_LOCK_FLASH);

#elif defined(PLATFORM_W800) || defined(PLATFORM_W600)
	int res = tls_fls_erase(addr / EF_ERASE_MIN_SIZE);
	if(res != TLS_FLS_STATUS_OK) return EF_ERASE_ERR;
	else return EF_NO_ERR;
#elif PLATFORM_XRADIO
	int res = flash_erase(0, addr, EF_ERASE_MIN_SIZE);
	if(res != 0) res = EF_ERASE_ERR;
	else res = EF_NO_ERR;
	return res;
#elif WINDOWS || LINUX
	memset(env_area + addr, 0xFF, size);
#elif PLATFORM_TXW81X || PLATFORM_RDA5981
	HAL_FlashEraseSector(addr);
	return EF_NO_ERR;
#elif PLATFORM_BEKEN
	int param;
	UINT32 status;
	int protect_type;
	DD_HANDLE flash_handle;
	unsigned int _size = size;

	hal_flash_lock();

	flash_handle = ddev_open(FLASH_DEV_NAME, &status, 0);
	ddev_control(flash_handle, CMD_FLASH_GET_PROTECT, (void*)&protect_type);

	if(FLASH_PROTECT_NONE != protect_type)
	{
		param = FLASH_PROTECT_NONE;
		ddev_control(flash_handle, CMD_FLASH_SET_PROTECT, (void*)&param);
	}

	/* Calculate the start address of the flash sector(4kbytes) */
	addr = addr & 0x00FFF000;

	do
	{
		flash_ctrl(CMD_FLASH_ERASE_SECTOR, &addr);
		addr += 4096;

		if(_size < 4096)
			_size = 0;
		else
			_size -= 4096;

	} while(_size);

	if(FLASH_PROTECT_NONE != protect_type)
	{
		param = protect_type;
		ddev_control(flash_handle, CMD_FLASH_SET_PROTECT, (void*)&param);
	}
	hal_flash_unlock();

	return EF_NO_ERR;

#elif PLATFORM_BL_NEW
	if(bflb_mtd_erase(handle, addr, size) < 0)
	{
		return EF_ERASE_ERR;
	}
	return EF_NO_ERR;
#endif
	return result;
}
/**
 * Write data to flash.
 * @note This operation's units is word.
 * @note This operation must after erase. @see flash_erase.
 *
 * @param addr flash address
 * @param buf the write data buffer
 * @param size write bytes size
 *
 * @return result
 */
EfErrCode ef_port_write(uint32_t addr, const uint32_t* buf, size_t size)
{
#if PLATFORM_REALTEK
	device_mutex_lock(RT_DEV_LOCK_FLASH);
	int res = flash_stream_write(&flash, addr, size, buf);
	device_mutex_unlock(RT_DEV_LOCK_FLASH);

	if(res) return EF_NO_ERR;
	else return EF_WRITE_ERR;

#elif defined(PLATFORM_W800) || defined(PLATFORM_W600)
	int res = tls_fls_write(addr, (uint8_t*)buf, size);
	if(res != TLS_FLS_STATUS_OK) return EF_WRITE_ERR;
	else return EF_NO_ERR;
#elif PLATFORM_XRADIO
	int res = flash_rw(0, addr, (void*)buf, size, 1);
	if(res == 0) res = EF_WRITE_ERR;
	else res = EF_NO_ERR;
	return res;
#elif WINDOWS || LINUX
	memcpy(env_area + addr, buf, size);
	return EF_NO_ERR;
#elif PLATFORM_TXW81X || PLATFORM_RDA5981
	HAL_FlashWrite(buf, size, addr);
	return EF_NO_ERR;
#elif PLATFORM_BEKEN
	int param;
	UINT32 status;
	int protect_type;
	DD_HANDLE flash_handle;

	hal_flash_lock();

	flash_handle = ddev_open(FLASH_DEV_NAME, &status, 0);
	ddev_control(flash_handle, CMD_FLASH_GET_PROTECT, (void*)&protect_type);

	if(FLASH_PROTECT_NONE != protect_type)
	{
		param = FLASH_PROTECT_NONE;
		ddev_control(flash_handle, CMD_FLASH_SET_PROTECT, (void*)&param);
	}

	flash_write((char*)buf, (unsigned long)size, addr);
	if(FLASH_PROTECT_NONE != protect_type)
	{
		param = protect_type;
		ddev_control(flash_handle, CMD_FLASH_SET_PROTECT, (void*)&param);
	}

	hal_flash_unlock();

	return EF_NO_ERR;
#elif PLATFORM_BL_NEW
	if(bflb_mtd_write(handle, addr, size, (const uint8_t*)buf) < 0)
	{
		return EF_WRITE_ERR;
	}
	return EF_NO_ERR;
#endif
}

/**
 * lock the ENV ram cache
 */
void ef_port_env_lock(void)
{
#if configUSE_RECURSIVE_MUTEXES && PLATFORM_BL_NEW
	xSemaphoreTakeRecursive(ef_mutex, 0xFFFFFFFF);
#else
	xSemaphoreTake(ef_mutex, 0xFFFFFFFF);
#endif
}

/**
 * unlock the ENV ram cache
 */
void ef_port_env_unlock(void)
{
#if configUSE_RECURSIVE_MUTEXES && PLATFORM_BL_NEW
	xSemaphoreGiveRecursive(ef_mutex);
#else
	xSemaphoreGive(ef_mutex);
#endif
}

/**
 * This function is print flash debug info.
 *
 * @param file the file which has call this function
 * @param line the line number which has call this function
 * @param format output format
 * @param ... args
 *
 */
void ef_log_debug(const char* file, const long line, const char* format, ...)
{

}

/**
 * This function is print flash routine info.
 *
 * @param format output format
 * @param ... args
 */
void ef_log_info(const char* format, ...)
{
	va_list args;

	/* args point to the first variable parameter */
	va_start(args, format);
	vprintf(format, args);
	va_end(args);
}
/**
 * This function is print flash non-package info.
 *
 * @param format output format
 * @param ... args
 */
void ef_print(const char* format, ...)
{
	va_list args;

	/* args point to the first variable parameter */
	va_start(args, format);
	vprintf(format, args);
	va_end(args);
}
