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

/*****************************************************************************/
/*     include file                                                          */
/*****************************************************************************/
/* いずれかは必ず"1"にすること */
#define AG903_SAMPLE_USB_MSC_ENABLE		(1)		/* MSCを有効にする場合は"1" */
#define AG903_SAMPLE_USB_CDC_ENABLE		(0)		/* CDCを有効にする場合は"1" */
#define AG903_SAMPLE_USB_HID_ENABLE		(1)		/* HIDを有効にする場合は"1" */

#if AG903_SAMPLE_USB_MSC_ENABLE
#include "storage/usb_msc_internal_api.h"
#endif
#if AG903_SAMPLE_USB_CDC_ENABLE
#include "cdc/usb_cdc_internal_api.h"
#endif
#if AG903_SAMPLE_USB_HID_ENABLE
#include "hid/usb_hid_internal_api.h"
#endif
#include "usbc_main.h"
#include "unknown/usb_unknown_device.h"
#include "sample_common.h"
#include "com.h"
#include "vram/vrammgr.h"
#include "usb_sample.h"
#if AG903_SAMPLE_USB_MSC_ENABLE
#include "sample_app_msc.h"
#endif
#if AG903_SAMPLE_USB_CDC_ENABLE
#include "sample_app_cdc.h"
#endif
#if AG903_SAMPLE_USB_HID_ENABLE
#include "sample_app_hid.h"
#endif
#include "sample_util.h"

/*****************************************************************************/
/*     function prototype                                                    */
/*****************************************************************************/
static int32_t Usbh_init_module(void);
static int32_t Usbh_term_module(void);
static void Usbh_SampleHelp(void);
#if AG903_SAMPLE_USB_CDC_ENABLE
static void Usbh_CdcSample(void);
#endif
#if AG903_SAMPLE_USB_HID_ENABLE
static void Usbh_HidSample(void);
#endif
static void Usbh_InitHw(void);
static void Usbh_SampleAplInit(uint8_t devcls);
static void Usbh_SampleAplEnd(uint8_t devcls);
static void Usbh_SampleAplProc(void);
static void Usbh_ReqSuspend(void);
static void Usbh_ReqResume(void);
static int sample_app_callback(int mod_id, int callback_id, void* pContext);
static void sample_app_unknowncallback(int callback_id, void* pContext);
static void sample_app_usbhcallback(int callback_id, void* pContext);
static int sample_app_split_err_callback(int _mod_id, int callback_id, void* pContext);

/*****************************************************************************/
/*     Param			                                                     */
/*****************************************************************************/
#define USBH_SMP_ATCTIME			(30000)					/* サンプルアクティブ時間[ms] */	/**暫定**/
#define USBH_SMP_MSC				(1<<0)					/* MSCデバイス指定 */
#define USBH_SMP_CDC				(1<<1)					/* CDCデバイス指定 */
#define USBH_SMP_HID				(1<<2)					/* HIDデバイス指定 */
#define USBH_SMP_

enum {	/* 動作状態 */
	USBH_SMP_STATE_NORMAL=0,
	USBH_SMP_STATE_SUSPEND,
};

/*****************************************************************************/
/*     global variable                                                       */
/*****************************************************************************/
int32_t			g_mplid_app;
static uint8_t	UsbhAplStat=USBH_SMP_STATE_NORMAL;

static void (*func[])() = {
	Usbh_SampleHelp,
#if AG903_SAMPLE_USB_HID_ENABLE
	Usbh_HidSample,
#endif
#if AG903_SAMPLE_USB_CDC_ENABLE
	Usbh_CdcSample,
#endif
};

/*****************************************************************************/
/*     Macro                                                                 */
/*****************************************************************************/
#define	EPRINT(FMT, ...)		sys_print(0, FMT,##__VA_ARGS__)
#define	PRINT(FMT, ...)			sys_print(0, FMT"\r\n",##__VA_ARGS__)
#define DMAC_ERROR(str)			sys_print(1, " ERROR:%s[%d] *%s*\r\n",__FUNCTION__, __LINE__, str)


/**
 * @brief		メイン処理
 * @param[in]	param	制御パラメータ
 * @return		なし
 * @note		なし
 */
void USBH_main(uint8_t param)
{
	uint8_t		mode = 0;
	char		input[2+2];

	((void)param);

	Usbh_SampleHelp();	/* Help表示 */

	Usbh_init_module();

	while (mode != 0xFF) {
		EPRINT(" -- Choose sample number (00h<HELP> to FFh): ");
		COM_GetNum_Wait(input, sizeof(input));
		mode = (ASCtoBIN(input[0]) << 4)
			 | (ASCtoBIN(input[1]) << 0);
		if(0xFF == mode) {
			break;
		}
		if(mode >= (sizeof(func)/sizeof(void*))) {
			func[0](); /* Help */
		}
		else {
			Usbh_SampleEnd();	/* USBH終了 */
			sys_dlytsk(10);
			Usbh_SampleInit();	/* USBH再起動 */
			func[mode]();		/* Sample func実行 */
		}
	}

	Usbh_term_module();

	return;
}

/**
 * @brief		サブ処理
 * @param[in]	param	制御パラメータ
 * @return		なし
 * @note		なし
 */
void USBH_sub(uint32_t param)
{
	((void)param);
	/* 処理なし */
	return;
}

/**
 * @brief		モジュール初期化
 * @param[in]	なし
 * @retval		AG903_ENONE		正常終了
 * @note		なし
 */
static int32_t Usbh_init_module(void)
{
	/* 処理なし */
	return AG903_ENONE;
}

/**
 * @brief		モジュール終了
 * @param[in]	なし
 * @retval		AG903_ENONE		正常終了
 * @note		なし
 */
static int32_t Usbh_term_module(void)
{
	/* 処理なし */
	return AG903_ENONE;
}

/**
 * @brief		ヘルプ表示
 * @param		なし
 * @return		なし
 * @note		なし
 */
static void Usbh_SampleHelp(void)
{
	PRINT("\t# 00 ... Help");
#if AG903_SAMPLE_USB_HID_ENABLE
	PRINT("\t# 01 ... hid sample");
#endif
#if AG903_SAMPLE_USB_CDC_ENABLE
	PRINT("\t# 02 ... cdc sample");
#endif
	PRINT("\t# FF ... Exit Sample");

	return;
}

#if AG903_SAMPLE_USB_CDC_ENABLE
/**
 * @brief		CDC サンプル動作
 * @param		なし
 * @return		なし
 * @note		なし
 */
static void Usbh_CdcSample(void)
{
	Usbh_SampleAplInit(USBH_SMP_CDC);
	sys_dlytsk(USBH_SMP_ATCTIME);
	Usbh_SampleAplEnd(USBH_SMP_CDC);

	return;
}
#endif

#if AG903_SAMPLE_USB_HID_ENABLE
/**
 * @brief		HID サンプル動作
 * @param		なし
 * @return		なし
 * @note		なし
 */
static void Usbh_HidSample(void)
{
	Usbh_SampleAplInit(USBH_SMP_HID);
	Usbh_SampleAplProc();
	Usbh_SampleAplEnd(USBH_SMP_HID);

	return;
}
#endif

/**
 * @brief		サンプル初期化
 * @param		なし
 * @return		なし
 * @note		なし
 */
void Usbh_SampleInit(void)
{
	AG903_VRAMMgrMplPrm		pool_prm = {0};					/* メモリプール生成パラメータ		*/
    usbh_regist_info		init;							/* USB初期化パラメータ				*/
    app_callback_info		app_cbinfo[4];					/* クラスドライバ情報				*/
    uhs_status_t			retval;							/* 戻り値							*/
    int						i = 0;

    Usbh_InitHw();

#if AG903_SAMPLE_USB_MSC_ENABLE
	/* MSCが挿抜された場合の処理関数情報 */
    app_cbinfo[i].clsdrv_type = USB_CLASS_MASSSTOR;	/* MSCクラスドライバ	*/
    app_cbinfo[i].attach_callback = sample_app_callback;
    app_cbinfo[i].detach_callback = sample_app_callback;
	app_cbinfo[i].class_ini_callback = (usb_class_init_callback_t)Usbh_Stor_Init;
    app_cbinfo[i++].is_lastnode = FALSE;
#endif

#if AG903_SAMPLE_USB_CDC_ENABLE
	/* Communications Deviceが挿抜された場合の処理関数情報 */
    app_cbinfo[i].clsdrv_type = USB_CLASS_CDC;		/* CDC クラスドライバ	*/
    app_cbinfo[i].attach_callback = sample_app_callback;
    app_cbinfo[i].detach_callback = sample_app_callback;
    app_cbinfo[i].class_ini_callback = (usb_class_init_callback_t)Usbh_Cdc_Init;
    app_cbinfo[i++].is_lastnode = FALSE;
#endif

#if AG903_SAMPLE_USB_HID_ENABLE
	/* Human Interface Deviceが挿抜された場合の処理関数情報 */
    app_cbinfo[i].clsdrv_type = USB_CLASS_HID;		/* HIDクラスドライバ	*/
    app_cbinfo[i].attach_callback = sample_app_callback;
    app_cbinfo[i].detach_callback = sample_app_callback;
	app_cbinfo[i].class_ini_callback = (usb_class_init_callback_t)Usbh_Hid_Init;
    app_cbinfo[i++].is_lastnode = FALSE;
#endif

	/* サポート外のUSBデバイスが挿抜された場合の処理関数情報 */
    app_cbinfo[i].clsdrv_type = USB_CLASS_UNKNOWN;			/* 非サポートデバイス	*/
    app_cbinfo[i].attach_callback = sample_app_callback;
    app_cbinfo[i].detach_callback = sample_app_callback;
    app_cbinfo[i].class_ini_callback = (usb_class_init_callback_t)Usb_UnknownClassDriver_Init;
    app_cbinfo[i].is_lastnode = TRUE;

    /* 初期化パラメータ設定 */
    init.app_callback = app_cbinfo;									/* クラスドライバ情報	*/
    init.overload_callback = sample_app_callback;					/* OverCurrent処理関数	*/
    init.status_notify_callback = sample_app_callback;				/* Status通知処理関数	*/
    init.split_err_notify_callback = sample_app_split_err_callback;	/* Split通信エラー処理関数	*/
    init.assert_callback = NULL;									/* Assert検出コールバック */
    init.logtype = USB_MSGMON_LOGTYPE_ANALYSIS;						/* 解析ログタイプ		*/

	/* USBホストドライバ初期化 */
	retval = Usbh_Init(init);
	if(retval != UDI_OK) {
		/* error */
		return;
	}

	/*------------------*/
	/* メモリプール生成 */
	/*------------------*/
	/* パラメータ設定 */
	pool_prm.mplatr	= AG903_OSW_ATRFIFO;					/* メモリプール待ち FIFO 順			*/
	pool_prm.mplsz	= MPL_APP_SIZE;							/* メモリプールのバイトサイズ		*/
	pool_prm.mpl	= (void *)MPL_APP_ADDR;					/* メモリプール領域の先頭番地		*/
	pool_prm.memtype = AG903_VRAM_NORMAL_CACHE_OFF;			/* キャッシュ OFF					*/

	/* メモリプール生成 */
	g_mplid_app = AG903_VRAMMgrCreateMpl(&pool_prm);

	return;
}

/**
 * @brief		サンプル終了
 * @param		なし
 * @return		なし
 * @note		なし
 */
void Usbh_SampleEnd(void)
{
    uint32_t			result;
    uhs_status_t		retval;

	retval = Usbh_Exit();
	if(retval != UDI_OK) {
		/* error */
	}

	result = AG903_VRAMMgrDeleteMpl(g_mplid_app);
	if(result != AG903_ENONE) {
		/* error */
	}

	return;
}

/**
 * @brief		HID サンプル動作
 * @param		なし
 * @return		なし
 * @note		なし
 */
static void Usbh_InitHw(void)
{
	Board_SelectUsbMode(AG903_USB_MODE_HOST);	/* USB HOSTモード */
	return;
}

#if defined(__ICCARM__)
	#pragma diag_suppress=Pe177
#endif
/**
 * @brief		サンプルアプリ初期化
 * @param[in]	devcls	デバイスクラス(bit指定)
 * @return		なし
 * @note		なし
 */
static void Usbh_SampleAplInit(uint8_t devcls)
{

#if AG903_SAMPLE_USB_MSC_ENABLE
	if(USBH_SMP_MSC&devcls) {
		msc_appinit();
	}
#endif
#if AG903_SAMPLE_USB_CDC_ENABLE
	if(USBH_SMP_CDC&devcls) {
		cdc_appinit();
	}
#endif
#if AG903_SAMPLE_USB_HID_ENABLE
	if(USBH_SMP_HID&devcls) {
		hid_appinit();
	}
#endif
	return;
}

/**
 * @brief		サンプルアプリ終了
 * @param[in]	devcls	デバイスクラス(bit指定)
 * @return		なし
 * @note		なし
 */
static void Usbh_SampleAplEnd(uint8_t devcls)
{

#if AG903_SAMPLE_USB_MSC_ENABLE
	if(USBH_SMP_MSC&devcls) {
		msc_append();
	}
#endif
#if AG903_SAMPLE_USB_CDC_ENABLE
	if(USBH_SMP_CDC&devcls) {
		cdc_append();
	}
#endif
#if AG903_SAMPLE_USB_HID_ENABLE
	if(USBH_SMP_HID&devcls) {
		hid_append();
	}
#endif
	return;
}

/**
 * @brief		サンプルアプリ実行中
 * @param		なし
 * @return		なし
 * @note		なし
 */
static void Usbh_SampleAplProc(void)
{
	char input[4];

	while(1) {
		COM_GetChara_Wait(input, 3); /* コンソール入力待ち */
		if( ('e'==input[0]) || ('E'==input[0]) ) {
			break; /* 終了 */
		}
		if( ('s'==input[0]) || ('S'==input[0]) ) {
			Usbh_ReqSuspend(); /* Suspend */
		}
		if( ('r'==input[0]) || ('R'==input[0]) ) {
			Usbh_ReqResume(); /* Resume */
		}
	}

	if(USBH_SMP_STATE_SUSPEND==UsbhAplStat) {
		Usbh_ReqResume(); /* Resume */
	}

	return;
}

#if defined(__ICCARM__)
	#pragma diag_default=Pe177
#endif

/**
 * @brief		サンプルアプリSUSPEND処理
 * @param		なし
 * @return		なし
 * @note		なし
 */
static void Usbh_ReqSuspend(void)
{
	int32_t			result=AG903_ENONE;
	uhs_status_t 	ret;

	do {
		/** 注：全てのデバイスに対してエニュメレーションが完了している事！ **/
		/**     （エニュメレーション中の動作は無保証）                     **/

		ret = Usbh_Api_ReqDisableAttDet(); /* アタッチ／デタッチ禁止 */
		if(UDI_OK != ret) {
			result=-AG903_EFAULT;
			break;
		}
#if AG903_SAMPLE_USB_CDC_ENABLE
		result = cdc_appstop(); /* CDC停止 */
		if(AG903_ENONE != result) {
			break;
		}
#endif
#if AG903_SAMPLE_USB_HID_ENABLE
		result = hid_appstop(); /* HID停止 */
		if(AG903_ENONE != result) {
			break;
		}
#endif
		/* MSCに対する処理はなし … unmount状態の前提 */

		ret = Usbh_Api_ReqSuspend(); /* suspend要求 */
		if(UDI_OK != ret) {
			result=-AG903_EFAULT;
			break;
		}
	}while(0);

	if(AG903_ENONE == result) {
		PRINT(" *** SUCCESS SUSPEND ***");
		UsbhAplStat = USBH_SMP_STATE_SUSPEND;
	}
	else {
		PRINT(" Error!! FAILURE SUSPEND");
	}
	return;
}

/**
 * @brief		サンプルアプリRESUME処理
 * @param		なし
 * @return		なし
 * @note		なし
 */
static void Usbh_ReqResume(void)
{
	int32_t			result=AG903_ENONE;
	uhs_status_t 	ret;

	do {
		ret = Usbh_Api_ReqResume(); /* resume要求 */
		if(ret != UDI_OK) {
			result=-AG903_EFAULT;
			break;
		}
#if AG903_SAMPLE_USB_CDC_ENABLE
		result = cdc_apprestart(); /* CDC再開 */
		if(AG903_ENONE != result) {
			break;
		}
#endif
#if AG903_SAMPLE_USB_HID_ENABLE
		result = hid_apprestart(); /* HID再開 */
		if(AG903_ENONE != result) {
			break;
		}
#endif
	}while(0);

	if(AG903_ENONE == result) {
		PRINT(" *** SUCCESS RESUME ***");
		UsbhAplStat = USBH_SMP_STATE_NORMAL;
	}
	else {
		PRINT(" Error!! FAILURE RESUME");
	}
	return;
}


/******************************************************************************
   Function：  sample_app_callback
   Argument：  mod_id      : コールバック元モジュール ID
               callback_id : コールバック種別
               pContext    : コールバック context
   Return  ：  TRUEのみ
   Summary ：  共通コールバック関数
   Caution ：  なし
******************************************************************************/
static int sample_app_callback(int mod_id, int callback_id, void* pContext)
{
	/*----------------------*/
	/* コールバック要因判定	*/
	/*----------------------*/
	switch(mod_id){
#if AG903_SAMPLE_USB_MSC_ENABLE
		/* MSC */
		case MOD_ID_CLASSDRV_STOR:
			msc_callback(callback_id, pContext);
			break;
#endif
#if AG903_SAMPLE_USB_CDC_ENABLE
		/* CDC */
		case MOD_ID_CLASSDRV_CDC:
			cdc_callback(callback_id, pContext);
			break;
#endif
#if AG903_SAMPLE_USB_HID_ENABLE
		/* HID */
		case MOD_ID_CLASSDRV_HID:
			hid_callback(callback_id, pContext);
			break;
#endif
		/* 非サポートデバイスクラス */
		case MOD_ID_CLASSDRV_UNKNOWNDEV:
			sample_app_unknowncallback(callback_id, pContext);
			break;
		/* USB コア */
		case MOD_ID_USBH:
			sample_app_usbhcallback(callback_id, pContext);
			break;
		/* その他 */
		default:
			/* Do Nothing */
			break;
	}
    return TRUE;
}

/******************************************************************************
   Function：  sample_app_unknowncallback
   Argument：  callback_id : コールバック種別
               pContext    : コールバックcontext
   Return  ：  なし
   Summary ：  非サポートデバイス用接続／切断コールバック関数
   Caution ：  なし
******************************************************************************/
static void sample_app_unknowncallback(int callback_id, void* pContext)
{
#if 0 /* 何もしない */
    struct usbd_usb_deviceinfo*	pUsbdev;	/* ペリフェラルデバイス情報			*/

	/* デバイス情報を取り出す */
	pUsbdev = ((usb_cbcontext_dev_attach *)pContext)->usbdev;

	switch(callback_id){
		/*--------*/
		/* 切断   */
		/*--------*/
		case CBID_CORE_DEVDETACH:
			SAMPLE_PRINT("Unknown device detached");
			/* LSデバイス */
			if(pUsbdev->usbdev_DevSpeed == USB_DEVSPEED_LS) {
				SAMPLE_PRINT(" [ LS Device ]");
			}
			/* FSデバイス */
			else if(pUsbdev->usbdev_DevSpeed == USB_DEVSPEED_FS) {
				SAMPLE_PRINT(" [ FS Device ]");
			}
			/* HSデバイス */
			else if(pUsbdev->usbdev_DevSpeed == USB_DEVSPEED_HS) {
				SAMPLE_PRINT(" [ HS Device ]");
			}
			SAMPLE_PRINT("\r\n");
			break;

		/*--------*/
		/* 接続   */
		/*--------*/
		case CBID_CORE_DEVATTACH:
			SAMPLE_PRINT("Unknown device attached");
			/* LSデバイス */
			if(pUsbdev->usbdev_DevSpeed == USB_DEVSPEED_LS) {
				SAMPLE_PRINT(" [ LS Device ]");
			}
			/* FSデバイス */
			else if(pUsbdev->usbdev_DevSpeed == USB_DEVSPEED_FS) {
				SAMPLE_PRINT(" [ FS Device ]");
			}
			/* HSデバイス */
			else if(pUsbdev->usbdev_DevSpeed == USB_DEVSPEED_HS) {
				SAMPLE_PRINT(" [ HS Device ]");
			}
			SAMPLE_PRINT("\r\n");
			break;

		/*--------*/
		/* その他 */
		/*--------*/
		default:
			SAMPLE_PRINT("Unknown device other event\r\n");
			break;
	}
#else
	((void)callback_id);
	((void)pContext);
#endif
}

/******************************************************************************
   Function：  sample_app_usbhcallback
   Argument：  callback_id : コールバック種別
               pContext    : コールバック context
   Return  ：  なし
   Summary ：  USB Host coreからのコールバック関数がコールされた際の処理
   Caution ：  なし
******************************************************************************/
static void sample_app_usbhcallback(int callback_id, void* pContext)
{
	usb_msgformat			msg;							/* メッセージフォーマット			*/


	switch(callback_id){
		/* 過電流 */
		case CBID_CORE_OVERCURRENT:
			/* エラー処理 */
			SAMPLE_PRINT("[info] USB Overcurrent!!\r\n");
			break;

		/* ステータス通知 */
		case CBID_CORE_STATUS:
			/* Local RAM にコピー */
			msg.msglayer	= ((usb_msgformat *)pContext)->msglayer;
			msg.msgsublayer = ((usb_msgformat *)pContext)->msgsublayer;
			msg.msgtype		= ((usb_msgformat *)pContext)->msgtype;
			msg.msglevel	= ((usb_msgformat *)pContext)->msglevel;
			msg.msgdata		= ((usb_msgformat *)pContext)->msgdata;
			msg.extdatasize = ((usb_msgformat *)pContext)->extdatasize;
			msg.extdata		= ((usb_msgformat *)pContext)->extdata;
			msg.rcvdtime	= ((usb_msgformat *)pContext)->rcvdtime;

			switch(msg.msglevel){
				/* 通常通知*/
				case USB_MSGMON_MSGLVL_MILESTONE:
					/* 通常通知処理 */
//					SAMPLE_PRINT("Milestone message\r\n");
					break;
				/* エラー通知 */
				case USB_MSGMON_MSGLVL_ERROR:
					SAMPLE_PRINT("[info] USB Error!!\r\n");
					/* エラー通知処理 */
					break;
				/* 警告通知 */
				case USB_MSGMON_MSGLVL_WARNING:
					SAMPLE_PRINT("[info] USB Warning!!\r\n");
					/* 警告通知処理 */
					break;
				default:
					break;
			}
			break;
		/* リクエストに対するコールバック */
		case CBID_CORE_REQ:
			/* Do Nothing */
//			SAMPLE_PRINT("Request callback\r\n");
			break;
		/* その他 */
		default:
			break;
	}
}

/******************************************************************************
   Function：  sample_app_split_err_callback
   Argument：  mod_id      : コールバック元モジュール ID
               callback_id : コールバック種別
               pContext    : コールバック context
   Return  ：  なし
   Summary ：  Splitエラーコールバック関数がコールされた際の処理
   Caution ：  なし
******************************************************************************/
static int sample_app_split_err_callback(int mod_id, int callback_id, void* pContext)
{
	((void)mod_id);
	((void)callback_id);
	((void)pContext);
	/* 処理無し */
    return TRUE;
}
