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

#include "kernel.h"
#include "sample_common.h"
#include "ffsys.h"

#include "AG903_intno.h"
#include "sys/spcprm.h"
#include "sys/sscprm.h"
#include "int/intmgr.h"
#include "vram/vrammgr.h"
#include "dsp/dspmgr.h"

#include "gvd_sample.h"
#include "gvd_common.h"
#include "../dsp/dsp_common.h"


/*************************************************************************************
 * プロトタイプ定義
 ************************************************************************************/
int32_t GVD_cbGVDFatalerror(void *param_gvd, void *param_user, gvdError error);
void GVD_ExecuteTask(void *exinf);
static int32_t init_module(void);
static int32_t term_module(void);
static int32_t GVD_setup_display(void);
static int32_t GVD_close_display(void);

static void VblankHandler(void);

extern void GVD_trickplay_internal(void);
extern void GVD_trickplay_external(void);

extern AG903_SPCPrmPllnParam	DSP_SAMPLE_PLL[];
extern AG903_DSPMgrSyncParam	DSP_SAMPLE_SYNCPARAM[];


uint32_t STREAM_WIDTH[GVD_MAX_STREAM]	= {160, 1280};	/* ストリーム横幅 */
uint32_t STREAM_HEIGHT[GVD_MAX_STREAM]	= {90, 800};	/* ストリーム高さ */

uint32_t STREAM_REFS[GVD_MAX_STREAM]	= {3, 3};		/* ストリーム参照フレーム数 */

uint32_t WINDOW_WIDTH[GVD_MAX_STREAM]	= {160, 1280};	/* 動画ウィンドウ横幅 */
uint32_t WINDOW_HEIGHT[GVD_MAX_STREAM]	= {90, 800};	/* 動画ウィンドウ高さ */

/* 動画ファイル名 */
char STREAM_NAME[GVD_MAX_STREAM][GVD_MAX_FILENAME_LEN] = {
    "A:\\AXELL_logo_s.h264",
    "A:\\AXELL_logo_l.h264",
};

AG903_DSPMgrHandle		*dsp_handle = NULL;
AG903_TIMRMgrHandle		*tim_handle = NULL;

void			*gWindowAttributeAddr	= NULL;
ER_ID			gWindowTaskID			= -1;	/* ウィンドウ制御タスクID */
ER_ID			gAsyncTaskID			= -1;	/* 非同期処理タスクID */
int32_t			gVBlankHdrID			= -1;	/* 垂直ブランクハンドラID */
gvdFatalErrorInfo	gFatal				= {0, 0};
gvdContext		*gGVDctx[GVD_MAX_STREAM];
ER_ID			gSemID_BufSync; 				/* フレームバッファ用セマフォ */
GVDFlags		gFlags[GVD_MAX_STREAM] = {{false}};

GVDBuffers		gGVDBufs;

int GVD_main(uint8_t param)
{
	int32_t		rc = AG903_ENONE;

	((void)param);

	uint8_t		mode = 0;
	char		input[2+2]; /* 入力文字+CRLF */

	rc = init_module();

	while ((rc == AG903_ENONE) && (mode != 0xFF)) {

		switch (mode) {
			case GVD_SAMPLE_INTERNAL: { GVD_trickplay_internal(); break; }
			case GVD_SAMPLE_EXTERNAL: { GVD_trickplay_external(); break; }

			case GVD_SAMPLE_HELP:
			default:
				GVD_Printf("\t# 00 ... Help\r\n");
				GVD_Printf("\t# 01 ... H.264 movie play : Internal Streamer\r\n");
				GVD_Printf("\t# 02 ... H.264 movie play : External Streamer\r\n");
				GVD_Printf("\t# FF ... Exit Sample\r\n");
				break;
		}

		if (rc == AG903_ENONE) {
			GVD_Printf(" -- Choose sample number (00h<HELP> to FFh): ");
			COM_GetNum_Wait(input, sizeof(input));

			mode = (ASCtoBIN(input[0]) << 4)
				 | (ASCtoBIN(input[1]) << 0);
		}
	}

	rc = term_module();

	return rc;
}

int GVD_sub(uint32_t param)
{
	((void)param);
	return 0;
}

static int32_t init_module(void)
{
	int32_t rc = AG903_ENONE;
	rc = sys_meminit(AG903_SAMPLE_VRAM_ALLOC_SIZE,
					 (void *)AG903_SAMPLE_VRAM_ADDR,
					 SYSMEM_NORMAL_CACHE_OFF);

	if(rc == AG903_ENONE) rc = GVD_setup_display();

	AG903_INTMgrHdrPrm intparam;
	intparam.atr	= AG903_INT_HLNG;
	intparam.func	= (void *)VblankHandler;
	intparam.intno	= AG903_IRQ16_DSP0;
	if(rc == AG903_ENONE)
		gVBlankHdrID = AG903_INTMgrSetHandler(&intparam);
	if(gVBlankHdrID >= 0)
		AG903_INTMgrEnableInt(AG903_IRQ16_DSP0);
	else
		rc = gVBlankHdrID;

	/* File system */
	{
	    ER ercd;

	    ercd = mountp('A', 0, (ID)Fsif_GetDevid(FSIF_DEV_CF)); /* CF->'A' */
	    if (ercd != E_OK)
			return -AG903_ENOENT;
	}

	/* semaphore */
	{
		T_CSEM pk_csem = {
		.sematr = TA_TPRI,
		.isemcnt = 1,
		.maxsem = 1,
		.name = "sem_window",
	    };
	    gSemID_BufSync = acre_sem(&pk_csem);
	    if(gSemID_BufSync < 0)
	    {
			GVD_ErrPrintf("ERROR: Sem create failed.\r\n");
			return -AG903_EBUSY;
	    }
	}

	return rc;
}

static int32_t term_module(void)
{
	int32_t rc = AG903_ENONE;
	del_sem(gSemID_BufSync);

	rc = umountp(0, (ID)Fsif_GetDevid(FSIF_DEV_CF)); /* CFアンマウント */

	rc = AG903_INTMgrDisableInt(AG903_IRQ16_DSP0);
	rc = AG903_INTMgrDeleteHandler(gVBlankHdrID);

	rc = GVD_close_display();

	sys_memfinal();

	return rc;
}

uint32_t GVD_getCID(gvdContext *ctx)
{
	int32_t idx;
	for(idx=0; idx<GVD_MAX_STREAM; idx++) {
		if(ctx == gGVDctx[idx]) {
			return idx;
		}
	}
	return -1;
}

bool GVD_isAllQuit()
{
	int32_t idx;
	bool q = true;

	for(idx=0; idx<GVD_MAX_STREAM; idx++) {
		q = q & gFlags[idx].quit;
	}

	return q;
}

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

	gWindowAttributeAddr = sys_memalign(0x20 * 16, 8);
    if(gWindowAttributeAddr == NULL)
    {
		GVD_ErrPrintf("ERROR: Out of memory.\r\n");
		return -AG903_ENOMEM;
    }

	AG903_DSPMgrCtrlParam		dsp_ctrl	= {0};
    AG903_DSPMgrCMOSParam		dsp_cmos	= {0};
    AG903_DSPMgrWindowParam		dsp_win		= {0};
	AG903_DSPMgrIntParam		dsp_int		= {0};
	AG903_DSPMgrWinAttribute	*dsp_attr	= NULL;

    if(rc == AG903_ENONE) rc = AG903_DSPMgrInit();
	else return rc;

    GVD_Printf("Initialize display settings...\r\n");

    AG903_DSPMgrGetHandle(0, &dsp_handle);

	/* PORT */
	AG903_DSPMgrSetPortSel(dsp_handle, AG903_VOD0_PORTSEL_LVCMOS24);

    dsp_ctrl.ip_sel = AG903_DSP_VMODE_NONINTERLACE;
    dsp_ctrl.hrz_framesize = 1280;
    dsp_ctrl.vt_framesize = 1024;
    dsp_ctrl.syncparam = &DSP_SAMPLE_SYNCPARAM[DSP_SAMPLE_RES_SXGA];

	/* PLL */
	AG903_SPCPrmPllnParam pll = {0};
	AG903_SPCPrmSetPll1Ctrl(&pll);
	sys_delayus(50);
	AG903_SPCPrmSetPll1Ctrl(&DSP_SAMPLE_PLL[DSP_SAMPLE_RES_SXGA]);
	sys_delayus(50);

    dsp_cmos.dotclk_polarity = AG903_VOD_DOTCLK_LATCH_FALL;
    dsp_cmos.field_polarity  = AG903_DSP_POLARITY_POSI;
    dsp_cmos.hsync_polarity  = AG903_DSP_POLARITY_NEGA;
	dsp_cmos.vsync_polarity  = AG903_DSP_POLARITY_NEGA;
    dsp_cmos.rgbde_polarity  = AG903_DSP_POLARITY_POSI;
	dsp_cmos.hsync_en        = true;
	dsp_cmos.vsync_en        = true;
	dsp_cmos.rgbde_en        = true;
	dsp_cmos.pixeldata_en    = true;
    if(rc == AG903_ENONE) rc = AG903_DSPMgrSetCMOSParam(dsp_handle, &dsp_ctrl, &dsp_cmos);
	else return rc;

	dsp_int.bmureq_timing		= AG903_DSP_BMU_WINDATA;
	dsp_int.mask_dspoff			= true;
	dsp_int.mask_error			= true;
	dsp_int.mask_hrz_line		= true;
	dsp_int.mask_vt_blank		= false;
	dsp_int.trigger_out			= AG903_DSP_EVENT_VT;
	dsp_int.trigger_vt			= AG903_DSP_TRG_VT_START_OF_VBLANK;
    if(rc == AG903_ENONE) rc = AG903_DSPMgrSetIntParam(dsp_handle, &dsp_int);
	else return rc;

    dsp_win.update_timing = AG903_DSP_ATTR_END_OF_VSYNC;
    dsp_win.background = 0x00000000;
    dsp_win.num_config = GVD_MAX_STREAM;
    dsp_win.num_attr = GVD_MAX_STREAM;
    dsp_win.palette_update = 0;
    dsp_win.window_attr_base = (uint32_t)gWindowAttributeAddr;
    dsp_win.window_attr_update = 1;
    if(rc == AG903_ENONE) rc = AG903_DSPMgrSetWindowParam(dsp_handle, &dsp_win);
	else return rc;

	AG903_DSPMgrGetAttribute(dsp_handle, 0, &dsp_attr);
	sys_memset(dsp_attr, 0, 0x20 * GVD_MAX_STREAM);
	dsp_attr[0].position_x			= 0;
	dsp_attr[0].position_y			= 0;
	dsp_attr[0].destination_width	= WINDOW_WIDTH[0];
	dsp_attr[0].destination_height	= WINDOW_HEIGHT[0];
	dsp_attr[0].framebuffer_base	= 0;
	dsp_attr[0].source_width		= STREAM_WIDTH[0];
	dsp_attr[0].source_height		= STREAM_HEIGHT[0];
	dsp_attr[0].hrz_size			= dsp_attr[0].source_width * 2;
	dsp_attr[0].conf.default_alpha	= 0xFF;
	dsp_attr[0].conf.framebuffer_format = AG903_DSP_FFMT_YUV422_BT601_LIMIT;
	dsp_attr[0].conf.valid			= false;

	/* Window 1 */
	dsp_attr[1].position_x			= 0;
	dsp_attr[1].position_y			= 0;
	dsp_attr[1].destination_width	= WINDOW_WIDTH[1];
	dsp_attr[1].destination_height	= WINDOW_HEIGHT[1];
	dsp_attr[1].framebuffer_base	= 0;
	dsp_attr[1].source_width		= STREAM_WIDTH[1];
	dsp_attr[1].source_height		= STREAM_HEIGHT[1];
	dsp_attr[1].hrz_size			= dsp_attr[1].source_width * 2;
	dsp_attr[1].conf.default_alpha	= 0xFF;
	dsp_attr[1].conf.framebuffer_format = AG903_DSP_FFMT_YUV422_BT601_LIMIT;
	dsp_attr[1].conf.valid			= false;
	if(rc == AG903_ENONE) rc = AG903_DSPMgrSetAttribute(dsp_handle, 0);
	else return rc;

    /* バックライト制御 OFF */
    Board_BacklightSel(0, 0);
    Board_BacklightSel(1, 0);

    /* 表示ON */
    AG903_DSPMgrEnable(dsp_handle);

    /* CMOS出力有効 */
    Board_SelectDviTX();

    sys_dlytsk(1000); /* ディスプレイの安定待ち */

	return rc;
}

static int32_t GVD_close_display(void)
{
	int32_t rc = AG903_ENONE;
	rc = AG903_DSPMgrDisable(dsp_handle);
	while(AG903_DSPMgrCheckStopped(dsp_handle));
	AG903_DSPMgrIntStat intclr = {
		.clr_dspoff		= true,
		.clr_hrz_line	= true,
		.clr_vt_blank	= true,
	};
	AG903_DSPMgrClearIntStat(dsp_handle, &intclr);
	if(rc == AG903_ENONE) rc = AG903_DSPMgrReleaseHandle(dsp_handle);
	if(rc == AG903_ENONE) dsp_handle = NULL;

	if(gWindowAttributeAddr != NULL) {
		sys_free(gWindowAttributeAddr);
		gWindowAttributeAddr = NULL;
	}

	return rc;
}


static void VblankHandler(void)
{
	AG903_DSPMgrIntStat intstat = {0};
	AG903_DSPMgrGetIntStat(dsp_handle, &intstat);
	intstat.clr_vt_blank = intstat.int_vt_blank;
	AG903_DSPMgrClearIntStat(dsp_handle, &intstat);
	if(gWindowTaskID > 0)
		wup_tsk(gWindowTaskID);
	return;
}


/* GVDの致命的エラーのコールバック */
int32_t GVD_cbGVDFatalerror(void *param_gvd, void *param_user, gvdError error)
{
	((void)param_user);
	((void)error);
	gFatal = *(gvdFatalErrorInfo *)param_gvd;
	GVD_ErrPrintf("ERROR: Fatal error detected. cont=%hhu, cause=%hhu\n",
			   gFatal.context_id, gFatal.cause);
	return 0;
}

/*----------------------------
		共通タスク
----------------------------*/

void async_tsk(VP_INT exinf)
{
	((void)exinf);

	while(!GVD_isAllQuit())
	{
		if(gFatal.cause != 0) break;

		GVD_Execute();
	}

	GVD_Printf("Async task done.\r\n");
	ext_tsk();
}

