/*
 * This program was created by AXELL CORPORATION.
 * Copyright (C) 2017 AXELL CORPORATION, all rights reserved.
 */
#include "sample_common.h"

#include "dmac/dmacmgr.h"
#include "gpio/gpiomgr.h"
#include "eqs/eqsctl.h"

#include "eqs_sample.h"

/*************************************************************************************
 * 定数定義
 ************************************************************************************/
#define	EQS_SYSCLK			(198)		/* システムクロック [MHz]				*/

#define	EQS_SCLK			(50)		/* FPGA内臓SPIRAMシリアルクロック [MHz]	*/
#define	EQS_DEVSIZ			(1024)		/* FPGA内臓SPIRAM容量 [Byte]			*/
#define	EQS_CMD_RD			(0x03)		/* Read Data							*/
#define	EQS_CMD_WR			(0x02)		/* Write Data							*/
#define	EQS_CMD_EQIO		(0x38)		/* Enter QUAD I/O Access				*/
#define	EQS_CMD_RSTIO		(0xFF)		/* Reset QUAD I/O Access				*/

#define	EQS_AREA			(1 & 0xF)	/* Area0:1, Area1:2, Area3:4, Area4:8 	*/
#define	EQS_DMAC_CH			(0 & 0x7)	/* DMACチャンネル(0〜7)					*/

#define EQS_BUFFER_SIZE		(4<<10)		/* バッファサイズ（データチェック用）	*/

/*************************************************************************************
 * 型定義
 ************************************************************************************/

typedef struct {
	uint8_t				exec;
	uint8_t				area;						/* EQSエリア番号			*/
	bool				eqsmode;					/* 通信モード				*/
	bool				wait;						/* DMAC転送完了待ちフラグ	*/
	bool				dir;						/* DMA転送方向				*/
	uint8_t				*addr[AG903_EQS_AREA_NUM];	/* デバイスアドレスリスト	*/
	uint8_t				*src;						/* SRCバッファ				*/
	uint8_t				*dst;						/* DSTバッファ				*/
	AG903_DMACMgrHandle	*dmacid;					/* DMACハンドル				*/
}EQSConfig;

static EQSConfig gEQSConf = {0};

/*************************************************************************************
 * プロトタイプ定義
 ************************************************************************************/
static void eqs_spiram(uint8_t mode);
static void eqs_spiram_init(EQSConfig *eqs);
static void eqs_spiram_setup(EQSConfig *eqs);
static int32_t eqs_spiram_verify(EQSConfig *eqs, uint8_t *src, uint8_t *dst, uint32_t siz);
static void eqs_spiram_pio(EQSConfig *eqs);
static void eqs_spiram_pio_write(uint8_t area, EQSConfig *eqs);
static void eqs_spiram_pio_read(uint8_t area, EQSConfig *eqs);
static void eqs_spiram_dma(EQSConfig *eqs);
static void eqs_spiram_dma_write(uint8_t area, EQSConfig *eqs);
static void eqs_spiram_dma_read(uint8_t area, EQSConfig *eqs);

static void eqs_write32(uint32_t addr, int32_t data);
static uint32_t eqs_read32(uint32_t addr);
static uint8_t eqs_read8(uint32_t addr);


static void eqs_callback_DMA(AG903_DMACMgrHandle* handle, AG903_DMACMgrDesc** desc, uint8_t* end);

static int32_t init_module(void);
static int32_t term_module(void);


void EQS_help(void)
{
	EQS_Printf("\t# 00 ... Help\r\n");

	EQS_Printf("\t# 01 ... SPIRAM SPI/PIO\r\n");
	EQS_Printf("\t# 02 ... SPIRAM SQI/PIO\r\n");
	EQS_Printf("\t# 03 ... SPIRAM SPI/DMA Write\r\n");
	EQS_Printf("\t# 04 ... SPIRAM SQI/DMA Read\r\n");

	EQS_Printf("\t# FF ... Exit Sample\r\n");
}


int EQS_main(uint8_t param)
{
	int32_t		rc = AG903_ENONE;
	char		input[2+2];

	((void)param);

	rc = init_module();

	EQS_help();

	while ((rc == AG903_ENONE) && (gEQSConf.exec != 0xFF)) {
		if (rc == AG903_ENONE) {
			EQS_Printf(" -- Choose sample number (00h<HELP> to FFh): ");
			COM_GetNum_Wait(input, sizeof(input));

			gEQSConf.exec = (ASCtoBIN(input[0]) << 4)
							| (ASCtoBIN(input[1]) << 0);
		}

		switch (gEQSConf.exec) {
			case 0x01: eqs_spiram(0); break;		/* 23A1024(SPIRAM) SPI/PIO			*/
			case 0x02: eqs_spiram(1); break;		/* 23A1024(SPIRAM) SQI/PIO			*/
			case 0x03: eqs_spiram(2); break;		/* 23A1024(SPIRAM) SPI/DMA Write	*/
			case 0x04: eqs_spiram(3); break;		/* 23A1024(SPIRAM) SQI/DMA Read		*/
			case 0xFF: break;						/* Exit								*/
			case 0x00:								/* HELP								*/
			default:
				EQS_help();
				break;
		}

	}

	rc = term_module();

	return rc;
}

int EQS_sub(uint32_t param)
{
	((void)param);
	return AG903_ENONE;
}


static void eqs_spiram(uint8_t mode)
{
	int32_t		rc = AG903_ENONE;

	switch (mode) {
		case 0:  { gEQSConf.eqsmode = AG903_EQS_MODE_NORM; break; }
		case 1:  { gEQSConf.eqsmode = AG903_EQS_MODE_QUAD; break; }
		case 2:  { gEQSConf.eqsmode = AG903_EQS_MODE_NORM; break; }
		case 3:  { gEQSConf.eqsmode = AG903_EQS_MODE_QUAD; break; }
		default:
			rc = -AG903_EINVAL;
			break;
	}

	if (rc == AG903_ENONE) {
		eqs_spiram_init(&gEQSConf);
		eqs_spiram_setup(&gEQSConf);

		if (mode < 2) {
			eqs_spiram_pio(&gEQSConf);
		}
		else {
			if (mode == 2) {
				gEQSConf.dir = 1;				/* WR:DMA RD:PIO				*/
			}
			else {
				gEQSConf.dir = 0;				/* WR:PIO RD:DMA				*/
			}

			eqs_spiram_dma(&gEQSConf);
		}
	}

	return;
}

static void eqs_spiram_init(EQSConfig *eqs)
{
	((void)eqs);

	Board_ConnectEqsAndRam();				/* FPGA内臓RAM(23A1024)接続設定	*/

	uint32_t div = (uint32_t)((EQS_SYSCLK+(EQS_SCLK-1))/EQS_SCLK);
	AG903_EQSCtlInit(div, 1);				/* EQSモジュール初期化			*/

	return;
}

static void eqs_spiram_setup(EQSConfig *eqs)
{
	AG903_EQSCtlFormat	format;
	uint8_t				qio;
	uint32_t			idx;

	if (eqs->eqsmode == AG903_EQS_MODE_QUAD) {
		qio = EQS_CMD_EQIO;
	}
	else {
		qio = EQS_CMD_RSTIO;
	}

	EQS_Printf("- EQS BANK -\r\n");

	for (idx=0; idx<AG903_EQS_AREA_NUM; idx++){
		if (eqs->area & (1 << idx)) {
			if (AG903_EQSCtlGetAddress(idx, (uint32_t *)&eqs->addr[idx]) != AG903_ENONE)
				EQS_Printf("デバイスアドレス取得");

			format.command  = qio;
			format.wait     = AG903_EQS_WIDTH_NONE;
			format.data_len = AG903_EQS_DATA_NONE;
			format.addr_len = AG903_EQS_WIDTH_NONE;
			format.flow     = AG903_EQS_FLOW_OFF;
			if (AG903_EQSCtlSetup(idx, AG903_EQS_DIR_WRITE, &format) != AG903_ENONE)
				EQS_Printf("EQSセットアップ");


			*(eqs->addr[idx]) = 0;			/* Enter/Reset QUAD mode		*/

			sys_dlytsk(1);

			format.command  = EQS_CMD_WR;
			format.wait     = AG903_EQS_WIDTH_NONE;
			format.data_len = AG903_EQS_DATA_BUS;
			format.addr_len = AG903_EQS_WIDTH_24BIT;
			format.flow     = AG903_EQS_FLOW_OFF;
			if (AG903_EQSCtlSetup(idx, AG903_EQS_DIR_WRITE, &format) != AG903_ENONE)
				EQS_Printf("EQSセットアップ");

			format.command  = EQS_CMD_RD;
			format.wait     = AG903_EQS_WIDTH_16BIT;
			format.data_len = AG903_EQS_DATA_NONE;
			format.addr_len = AG903_EQS_WIDTH_24BIT;
			format.flow     = AG903_EQS_FLOW_OFF;
			if (AG903_EQSCtlSetup(idx, AG903_EQS_DIR_READ, &format) != AG903_ENONE)
				EQS_Printf("EQSセットアップ");

			EQS_Printf("  [%d]:0x%08X - 0x%08X\r\n", idx, (unsigned int)eqs->addr[idx], (unsigned int)(eqs->addr[idx] + 0x1000000 - 1));
		}
	}

	if (AG903_EQSCtlSetMode(eqs->eqsmode) != AG903_ENONE)
		EQS_Printf("通信モード設定");

	return;
}

static int32_t eqs_spiram_verify(EQSConfig *eqs, uint8_t *src, uint8_t *dst, uint32_t siz)
{
	int32_t		rc = AG903_ENONE;
	uint32_t	idx;

	((void)eqs);
	EQS_Printf("\r\n- VERIFY -\r\n");

	for (idx=0; idx<siz; idx++){
		if (*src != *dst) {
			rc = -AG903_EFAULT;
			EQS_Printf("%04X: SRC:%02X DST:%02X\r\n", (uint32_t)dst, *src, *dst);
		}

		src++; dst++;
	}

	if (rc == AG903_ENONE) {
		EQS_Printf("  SUCCESS.\r\n");
	}

	return rc;
}

static void eqs_spiram_pio(EQSConfig *eqs)
{
	int32_t		rc = AG903_ENONE;
	uint32_t	idx;

	for (idx=0; idx<AG903_EQS_AREA_NUM; idx++){
		if (eqs->area & (1 << idx)) {
			eqs_spiram_pio_write(idx, eqs);
			eqs_spiram_pio_read(idx, eqs);

			rc = eqs_spiram_verify(eqs, eqs->src, eqs->dst, EQS_DEVSIZ);
			if (rc != AG903_ENONE) { break; }
		}
	}

	return;
}

static void eqs_spiram_pio_write(uint8_t area, EQSConfig *eqs)
{
	uint32_t	idx;
	uint32_t	buf;

	for (idx=0; idx<EQS_DEVSIZ; idx+=4){
		buf = sys_rand();
		eqs_write32((uint32_t)(eqs->src + idx), buf);
		eqs_write32((uint32_t)(eqs->addr[area] + idx), buf);
	}

	return;
}

static void eqs_spiram_pio_read(uint8_t area, EQSConfig *eqs)
{
	uint8_t		buf[16];
	uint32_t	idx;

	EQS_Printf("\r\n[%d] : 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F", area);
	EQS_Printf("\r\n    +------------------------------------------------\r\n");

	for (idx=0; idx<EQS_DEVSIZ;){
		if ((idx > 0) && (idx % 16) == 0) {
			EQS_Printf("%04X: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\r\n", (idx - 16),
					   buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15]);
		}

		if ((EQS_DEVSIZ - idx) >= 4) {
			*(uint32_t *)&eqs->dst[idx] = eqs_read32((uint32_t)(eqs->addr[area] + idx));

			buf[(idx+0) & 0x0F] = eqs->dst[idx+0];
			buf[(idx+1) & 0x0F] = eqs->dst[idx+1];
			buf[(idx+2) & 0x0F] = eqs->dst[idx+2];
			buf[(idx+3) & 0x0F] = eqs->dst[idx+3];
			idx += 4;
		}
		else {
			buf[idx & 0x0F] = eqs->dst[idx] = eqs_read8((uint32_t)(eqs->addr[area] + idx));
			idx++;
		}
	}

	return;
}

static void eqs_spiram_dma(EQSConfig *eqs)
{
	int32_t		rc = AG903_ENONE;
	uint32_t	idx;

	if (rc == AG903_ENONE) {
		rc = AG903_DMACMgrGetHandle(EQS_DMAC_CH, &eqs->dmacid);		/* DMACハンドル取得				*/
	}

	if (rc == AG903_ENONE) {
		if (AG903_DMACMgrSetIntCallback(eqs->dmacid, eqs_callback_DMA, NULL, NULL) != AG903_ENONE)
			EQS_Printf("DMACコールバック登録");

		for (idx=0; idx<AG903_EQS_AREA_NUM; idx++){
			if (eqs->area & (1 << idx)) {
				if (eqs->dir) {								/* WR:DMA RD:PIO				*/
					eqs_spiram_dma_write(idx, eqs);
				}
				else {										/* WR:PIO RD:DMA				*/
					eqs_spiram_dma_read(idx, eqs);
				}

				rc = eqs_spiram_verify(eqs, eqs->src, eqs->dst, EQS_DEVSIZ);
				if (rc != AG903_ENONE) { break; }
			}
		}
	}

	if (eqs->dmacid) {
		if (AG903_DMACMgrReleaseHandle(eqs->dmacid) != AG903_ENONE)
			EQS_Printf("DMACハンドル解放");
	}

	return;
}

static void eqs_spiram_dma_write(uint8_t area, EQSConfig *eqs)
{
	uint32_t	idx;

	for (idx=0; idx<EQS_DEVSIZ; idx += 4){ eqs_write32((uint32_t)(eqs->src + idx), sys_rand()); }

	if (AG903_DMACMgrSetSimpleTrnsDesc(eqs->dmacid, (void *)eqs->src, EQS_DEVSIZ, (void *)eqs->addr[area]) != AG903_ENONE)
		EQS_Printf("簡易転送用ディスクリプタ設定");
	eqs->wait = 1;											/* DMAC転送完了待ちフラグ:ON	*/
	if (AG903_DMACMgrEnable(eqs->dmacid) != AG903_ENONE)
		EQS_Printf("DMA実行");

	do { sys_dlytsk(1); } while (eqs->wait);					/* 転送完了待ち					*/

	eqs_spiram_pio_read(area, eqs);

	return;
}

static void eqs_spiram_dma_read(uint8_t area, EQSConfig *eqs)
{
	uint8_t		buf[16];
	uint32_t	idx;

	eqs_spiram_pio_write(area, eqs);

	{/* デスクリプタ設定 */
		AG903_DMACMgrConfig	conf;
		AG903_DMACMgrDesc	desc;

		conf.val              = 0;
		conf.st.DstHEn        = false;								/* ペリフェラルとのハンドシェイク要	*/

		desc.Ctrl.val         = 0;
		desc.Ctrl.st.SrcTcnt  = 0;									/* 転送サイズ					*/
		desc.Ctrl.st.SrcWidth = 2;									/* 転送元データ幅				*/
		desc.Ctrl.st.DstWidth = 2;									/* 転送先データ幅				*/
		desc.Ctrl.st.SrcCtrl  = 0;									/* 転送元アドレスインクリメント	*/
		desc.Ctrl.st.DstCtrl  = 0;									/* 転送先アドレスインクリメント	*/
		desc.SrcAddr          = (uint32_t)eqs->addr[area];			/* 転送元アドレス				*/
		desc.DstAddr          = (uint32_t)eqs->dst;					/* 転送先アドレス				*/
		desc.Trns.val         = EQS_DEVSIZ / 4;							/* [B] 転送回数					*/
		desc.Stride.val       = 0;									/* オフセット転送(2D転送)不要	*/
		desc.next             = NULL;								/* デイジーチェインなし			*/
		if (AG903_DMACMgrSetDescList(eqs->dmacid, &conf, &desc, 1) != AG903_ENONE)
			EQS_Printf("ディスクリプタ設定");

		eqs->wait = 1;														/* DMAC転送完了待ちフラグ:ON	*/
		if (AG903_DMACMgrEnable(eqs->dmacid) != AG903_ENONE)
			EQS_Printf("DMA実行");

		do { sys_dlytsk(1); } while (eqs->wait);								/* 転送完了待ち					*/
	}

	EQS_Printf("\r\n[%d] : 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F", area);
	EQS_Printf("\r\n    +------------------------------------------------\r\n");

	for (idx=0; idx<EQS_DEVSIZ;  ){
		if ((idx > 0) && (idx % 16) == 0) {
			EQS_Printf("%04X: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\r\n", (idx - 16),
					   buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15]);
		}

		buf[(idx+0) & 0x0F] = *(uint8_t *)&eqs->dst[idx+0];
		buf[(idx+1) & 0x0F] = *(uint8_t *)&eqs->dst[idx+1];
		buf[(idx+2) & 0x0F] = *(uint8_t *)&eqs->dst[idx+2];
		buf[(idx+3) & 0x0F] = *(uint8_t *)&eqs->dst[idx+3];
		idx += 4;
	}

	return;
}

static void eqs_write32(uint32_t addr, int32_t data)
{
	*((uint32_t *)addr) = data;

	return;
}
static uint32_t eqs_read32(uint32_t addr)
{
	return *((uint32_t *)addr);
}
static uint8_t eqs_read8(uint32_t addr)
{
	return *((uint8_t *)addr);
}


static void eqs_callback_DMA(AG903_DMACMgrHandle* handle, AG903_DMACMgrDesc** desc, uint8_t* end)
{
	((void)handle);
	((void)desc);
	((void)end);

	gEQSConf.wait = 0;		/* DMAC転送完了待ちフラグ:OFF */

	return;
}

static int32_t init_module(void)
{
	int32_t rc = AG903_ENONE;

	sys_memset(&gEQSConf, 0, sizeof(EQSConfig));

	gEQSConf.area    = EQS_AREA;
	gEQSConf.eqsmode = AG903_EQS_MODE_NORM;
	gEQSConf.wait    = 1;

	/* メモリ確保(キャッシュOFF) */
	rc = sys_meminit(AG903_SAMPLE_VRAM_ALLOC_SIZE,
					 (void *)AG903_SAMPLE_VRAM_ADDR,
					 SYSMEM_NORMAL_CACHE_OFF);

	if(rc == AG903_ENONE) {
		gEQSConf.src = (uint8_t *)sys_malloc(EQS_BUFFER_SIZE);
		if(gEQSConf.src == NULL) {
			EQS_ErrPrintf("ERROR: Buffer allocation failure\r\n");
			rc = -AG903_ENOMEM;
		} else {
			sys_memset(gEQSConf.src, 0, EQS_BUFFER_SIZE);
		}
	}

	if(rc == AG903_ENONE) {
		gEQSConf.dst = (uint8_t *)sys_malloc(EQS_BUFFER_SIZE);
		if(gEQSConf.dst == NULL) {
			EQS_ErrPrintf("ERROR: Buffer allocation failure\r\n");
			rc = -AG903_ENOMEM;
		} else {
			sys_memset(gEQSConf.dst, 0, EQS_BUFFER_SIZE);
		}
	}


	return rc;
}

static int32_t term_module(void)
{
	sys_free(gEQSConf.src);
	gEQSConf.src = NULL;

	sys_free(gEQSConf.dst);
	gEQSConf.dst = NULL;

	sys_memfinal();

	return AG903_ENONE;
}
