/**
 * @brief		サンプル共通部
 * @file		sample_common.c
 * @author		AXELL
 * @history     2017_02_22	初版
 * @description
 * @note
*/

/*
 * This program was created by AXELL CORPORATION.
 * Copyright (C) 2017 AXELL CORPORATION, all rights reserved.
 */

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include "kernel.h"
#include "AG903_UC3.h"
#include "sample_common.h"
#include "com.h"
#include "vram/vrammgr.h"
#include "sys/sscprm.h"
#include "ffsysdrv_cfg.h"

/*-- System Memory --*/
static int32_t	gMplID = 0;
static void*	gMemList[AG903_SAMPLE_SYS_MALLOC_MAX] = {NULL};
static const int8_t MemTypeTbl[SYSMEM_TYPE_NUM] =
{AG903_VRAM_NORMAL_CACHE_OFF, AG903_VRAM_NORMAL_CACHE_ON, AG903_VRAM_STRONGLY_ORDERED};

static void* GetBufferPoolMemory(size_t size, uint32_t align);

/**
 * @brief		システムメモリ初期化
 * @param		mplsz [in] メモリサイズ
 * @param		mpl   [in] メモリアドレス
 * @param		type  [in] メモリタイプ
 * @return		エラーコード
 * @note		malloc回数の上限は"AG903_SAMPLE_SYS_MALLOC_MAX"で定義
*/
int32_t sys_meminit(uint32_t mplsz, void *mpl, SysMemType type)
{
	int32_t				rc = AG903_ENONE;
	AG903_VRAMMgrMplPrm	mplprm;

	if(0 != gMplID) {
		return -AG903_EFAULT;	/* 初期化済み */
	}

	mplprm.memtype = MemTypeTbl[type];
	mplprm.mplatr  = AG903_VRAM_ATRFIFO;
	mplprm.mplsz   = mplsz;
	mplprm.mpl     = mpl;

	gMplID = AG903_VRAMMgrCreateMpl(&mplprm);

	if (gMplID <= 0) {
		rc = -AG903_ENOMEM;
	}
	else {
		sys_memset((void *)&gMemList, 0, sizeof(gMemList));
	}

	return rc;
}

/**
 * @brief		システムメモリ解放
 * @param		なし
 * @return		なし
 * @note
*/
void sys_memfinal(void)
{
	int32_t	ii;

	for (ii = 0;ii < AG903_SAMPLE_SYS_MALLOC_MAX;ii++) {
		if (gMemList[ii]) {
			sys_free((void *)gMemList[ii]);
		}
	}

	if (gMplID > 0) {
		AG903_VRAMMgrDeleteMpl(gMplID);
		gMplID = 0;
	}

	return;
}

/**
 * @brief		メモリ確保（アライン4固定）
 * @param		size [in] 確保サイズ
 * @return		確保メモリアドレス
 * @note
*/
void* sys_malloc(size_t size)
{
	int32_t	ii;
	void	*ptr = NULL;

	for (ii = 0;ii < AG903_SAMPLE_SYS_MALLOC_MAX;ii++) {
		if (gMemList[ii] == NULL) {
			gMemList[ii] = ptr = GetBufferPoolMemory(size, 4);
			break;
		}
	}

	return ptr;
}

/**
 * @brief		メモリ確保（アライン指定）
 * @param		size [in] 確保サイズ
 * @param		align [in] アラインサイズ
 * @return		確保メモリアドレス
 * @note
*/
void* sys_memalign(size_t size, uint32_t align)
{
	int32_t	ii;
	void	*ptr = NULL;

	for (ii = 0;ii < AG903_SAMPLE_SYS_MALLOC_MAX;ii++) {
		if (gMemList[ii] == NULL) {
			gMemList[ii] = ptr =GetBufferPoolMemory(size, align);
			break;
		}
	}

	return ptr;
}

/**
 * @brief		メモリ解放
 * @param		blk [in] 解放メモリアドレス
 * @return		エラーコード
 * @note
*/
int32_t sys_free(void *blk)
{
	int32_t	rc = AG903_ENONE;
	int32_t	ii;

	for (ii = 0;ii < AG903_SAMPLE_SYS_MALLOC_MAX;ii++) {
		if (gMemList[ii] == blk) {
			rc = AG903_VRAMMgrFree(gMplID, blk);
			if (rc == AG903_ENONE) { gMemList[ii] = NULL; }
			break;
		}
	}

	return rc;
}

/**
 * @brief		メモリ状態取得
 * @param		size [out] 確保可能サイズ
 * @return		エラーコード
 * @note
*/
void sys_memspace(size_t *size)
{
	int32_t					rc;
	AG903_VRAMMgrMplStat	mplsta;

	rc = AG903_VRAMMgrMrefer(gMplID, &mplsta);

	if (rc == AG903_ENONE) {
		*size = mplsta.fblksz;
	}
	else {
		*size = 0;
	}

	return;
}

static void* GetBufferPoolMemory(size_t size, uint32_t align)
{
	void	*addr;

	if (align < 4) {
		align = 4/*最少境界=4バイト*/;
	}

	addr = AG903_VRAMMgrMallocAlign(gMplID, align, size);

	return addr;
}


/*-- FileSystem I/F --*/
#if (CFG_DRV_DSK_CNT > 2)
extern void msc_getContext(uint8_t devnum, int32_t* pContext);
#endif
/**
 * @brief		デバイスID取得
 * @param		device [in] 対象デバイス
 * @return		デバイスID
 * @note
*/
int32_t Fsif_GetDevid(uint8_t device)
{
	int32_t devid;

	switch(device) {
		case FSIF_DEV_SD:
			devid = 0;		/** devidはffsysdrv_cfg.hの内容に依存 **/
			break;
#if (CFG_DRV_DSK_CNT > 1)
		case FSIF_DEV_CF:
			devid = 1;		/** devidはffsysdrv_cfg.hの内容に依存 **/
			break;
#endif
#if (CFG_DRV_DSK_CNT > 2)
		case FSIF_DEV_USB0:
			msc_getContext(0, &devid);
			break;
		case FSIF_DEV_USB1:
			msc_getContext(1, &devid);
			break;
#endif
		default:
			devid=(-1);
			break;
	}

	return devid;
}


/*-- utility --*/
/**
 * @brief		Print
 * @param		cond [in] 0：Print、1：エラー出力、その他：何もしない
 * @param		pFormat [in] 書式文字列
 * @return		なし
 * @note		文字列上限は"AG903_SAMPLE_SYS_PRINT_MAX"
*/
void sys_print(int32_t cond, char *pFormat, ...)
{
	char		xxx[AG903_SAMPLE_SYS_PRINT_MAX];
	va_list		ap;

	if (cond == 0 || cond == 1) {
		va_start(ap, pFormat);
		(void)vsprintf(xxx, pFormat, ap);
		va_end(ap);
		COM_PutStr(xxx);
	}

	if (cond == 1) {
		sys_dlytsk(AG903_SAMPLE_DLY_ERROUT);
	}

	return;
}

/**
 * @brief		指定時間待機（us単位）
 * @param		us [in] 時間 (<= 20,000,000[us])
 * @return		なし
 * @note		指定時間ロックします.
 * @note		20秒よりも長い時間が指定された場合, 待機しません.
*/
void sys_delayus(uint32_t us)
{
	if(us > 20000000) return;

	uint32_t upper, lower;
	uint64_t start, now;
	uint64_t time = (AG903_SAMPLE_SYSCLK/1000000)*us;

	AG903_SSCPrmGetFreeRunCnt(&upper, &lower);
	start = (uint64_t)upper << 32 | lower;

	do {
		AG903_SSCPrmGetFreeRunCnt(&upper, &lower);
		now = (uint64_t)upper << 32 | lower;
	}while(now - start < time);
}

/**
 * @brief		Delay（ms単位）
 * @param		msec [in] delay時間
 * @return		なし
 * @note
*/
void sys_dlytsk(uint32_t msec)
{
	uint32_t tim;

	tim = (msec/AG903_SAMPLE_TICK);
	if(msec%AG903_SAMPLE_TICK) {
		tim ++;
	}
	dly_tsk(tim);
	return;
}

/**
 * @brief		メモリセット
 * @param		buf [in] メモリアドレス
 * @param		chr [in] セット文字
 * @param		len [in] セットサイズ
 * @return		なし
 * @note
*/
void sys_memset(void* buf, int32_t chr, uint32_t len)
{
	uint8_t *str = (uint8_t *)buf,
			*end = (str + len);

	while (str < end) { *str = chr; str++; }
	return;
}

/**
 * @brief		メモリコピー
 * @param		dst [in] コピー先アドレス
 * @param		src [in] コピー元アドレス
 * @param		len [in] コピーサイズ
 * @return		コピー先アドレス
 * @note
*/
void* sys_memcpy(void *dst, const void *src, uint32_t len)
{
	int8_t	*ptr1 = (int8_t *)dst,
			*ptr2 = (int8_t *)src;

	while (len-- > 0) {
		*ptr1 = *ptr2;
		ptr1++;
		ptr2++;
	}

	return dst;
}

/**
 * @brief		文字検索
 * @param		buf [in] 検索アドレス
 * @param		chr [in] 検索文字
 * @param		len [in] 検索サイズ
 * @return		コピー先アドレス
 * @note
*/
void* sys_memchr(const void *buf, uint8_t chr, uint32_t len)
{
	uint8_t		*sp = (uint8_t *)buf,
				*ep = ((uint8_t *)buf + len);
	void 		*pp = NULL;

	while (sp <= ep) {
		if (*sp == chr) {
			pp = (void *)sp;
			break;
		}

		sp++;
	}

	return pp;
}

/**
 * @brief		文字列検索
 * @param		str1 [in] 検索アドレス
 * @param		str2 [in] 検索文字列
 * @return		一致アドレス
 * @note
*/
char* sys_strstr(char* str1, char* str2)
{
	char	*str = NULL;
	char	*ptr1, *ptr2;

	if (*str2 == '\0') {
		str =  str1;
	}
	else {
		do {
			if (*str1 == *str2) {
				ptr1 = str1;
				ptr2 = str2;

				while (*ptr1 == *ptr2) {
					ptr1++;
					ptr2++;

					if (*ptr2 == '\0') {
						str = str1;
						break;
					}
				}
			}
		} while ((str == NULL) && *str1++);
	}

	return str;
}


/**
 * @brief		擬似乱数取得
 * @return		擬似乱数
*/
uint32_t sys_rand(void)
{
	uint32_t upper, lower;
	AG903_SSCPrmGetFreeRunCnt(&upper, &lower);

	srand(lower);

	return ((uint32_t)rand());
}

/* 文字列をuint32_tに変換 */
uint32_t sys_strtoul(const char * restrict nptr, char ** restrict endptr, int32_t base)
{
	return strtoul(nptr, endptr, base);
}

/**
 * @brief		ASCIIバイナリ変換
 * @param		chr [in] ASCII
 * @return		バイナリ値
 * @note
*/
uint8_t ASCtoBIN(uint8_t chr)
{
	uint8_t	val;

	if ((chr >= '0') && (chr <= '9')) {
		val = chr - '0';
	}
	else if ((chr >= 'A') && (chr <= 'F')) {
		val = chr - 'A' + 10;
	}
	else if ((chr >= 'a') && (chr <= 'f')) {
		val = chr - 'a' + 10;
	}
	else {
		val = 0;
	}

	return val;
}

void Port_SetFunction(uint8_t group, uint8_t function)
{
	uint32_t val;
	uint8_t  regno;
	uint8_t  offset;

	regno  = group/16;
	offset = (group%16)*2;

	AG903_SSCPrmGetPinFunction(regno, &val);
	val   &= ~(0x03 << offset);
	val   |= function << offset;
	AG903_SSCPrmSetPinFunction(regno, val);

	return;
}

void Port_DisableGpio(uint8_t gpio, uint8_t num)
{
	uint32_t	val;
	uint8_t		loop;
	uint8_t		reg;
	uint8_t 	bit;

	for(loop=0; loop<num; loop++) {
		reg = (gpio+loop)/32;
		bit = (gpio+loop)%32;
		AG903_SSCPrmGetPinGpioEnable(reg, &val);
		val &= ~(0x01 << bit);
		AG903_SSCPrmSetPinGpioEnable(reg, val);
	}

	return;
}

void Port_EnableGpio(uint8_t gpio, uint8_t num)
{
	uint32_t	val;
	uint8_t		loop;
	uint8_t		reg;
	uint8_t 	bit;

	for(loop=0; loop<num; loop++) {
		reg = (gpio+loop)/32;
		bit = (gpio+loop)%32;
		AG903_SSCPrmGetPinGpioEnable(reg, &val);
		val |= (0x01 << bit);
		AG903_SSCPrmSetPinGpioEnable(reg, val);
	}

	return;
}

void Sys_SetDmaInterface(uint8_t interface, uint8_t req)
{
	uint32_t	val;
	uint8_t		reg_no;
	uint8_t		offset;

	reg_no = interface/4;
	offset = interface%4;

	AG903_SSCPrmGetDmaInterface(reg_no, &val);
	val &= ~(0xFF << (offset*8));
	val |= (req << (offset*8));
	AG903_SSCPrmSetDmaInterface(reg_no, val);

	return;
}

