/******************************************************************************
 *
 * Copyright(c) 2019 - 2020 Realtek Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 *****************************************************************************/
#include "phl_headers.h"

/*
 * SND sub function
 */
#ifdef CONFIG_FSM
static void snd_set_timer(struct phl_sound_obj *snd, int duration, u16 event)
{
	FSM_INFO(snd->fsm, "%s, duration=%d\n", __func__, duration);
	phl_fsm_set_alarm(snd->fsm_obj, duration, event);
}


enum rtw_phl_status
phl_snd_upd_snd_para(struct phl_info_t *phl_info, void *param)
{
	struct phl_sound_obj *snd = (struct phl_sound_obj *)phl_info->snd_obj;
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_SUCCESS;
	struct phl_snd_start_req *req = (struct phl_snd_start_req *)param;
	struct phl_sound_param *snd_param = &snd->snd_param;
	struct rtw_wifi_role_t *wrole = NULL;
	FUNCIN_WSTS(pstatus);

	snd_param->snd_func_grp_num = 0;
	snd_param->grp_used_map = 0;
	snd_param->snd_fail_counter = 0;

	if (req != NULL) {
		snd_param->m_wrole = req->wrole;
		wrole = (struct rtw_wifi_role_t *)req->wrole;
		snd_param->snd_proc_timeout_ms = req->proc_timeout_ms;/* ms */
		snd_param->snd_dialog_token = req->dialog_token;
		snd_param->snd_proc_period = req->proc_period;
		snd_param->test_flag = req->test_flag;
		snd_param->bypass_snd_sts_chk = req->bypass_sts_chk;
	}

	PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
		  "wrole->type = 0x%x \n",
		  wrole->type);
	PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
		  "timeout = %d ms\n", snd_param->snd_proc_timeout_ms);
	PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
		  "dialog tkn = %d\n", snd_param->snd_dialog_token);
	PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
		  "snd period = %d ms\n", snd_param->snd_proc_period);
	PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
		  "test flag = %d \n", snd_param->test_flag);
	PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
		  "by pass sts check = %d \n", snd_param->bypass_snd_sts_chk);

	FUNCIN_WSTS(pstatus);
	return pstatus;
}



/*
 * SND state handler
 */

static int snd_fsm_func_init_st_hdl(void *obj, u16 event, void *param)
{
	struct phl_sound_obj *snd = (struct phl_sound_obj *)obj;
	void *d = phl_to_drvpriv(snd->phl_info);
	int ret = 0;

	switch (event) {
	case FSM_EV_STATE_IN:
		/* Pending and waiting sounding start event */
		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "snd_fsm_func_init_st_hdl : SND FUNC pending and wait SND START\n");
	break;
	case SND_FUNC_EV_SND_START:
		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "snd_fsm_func_init_st_hdl : Recv SND START Event at timestamp %d\n",
			  _os_get_cur_time_us());
		/* SND_FUNC_EV_SND_START Shall have input param */
		_os_spinlock(d, &snd->snd_lock, _bh, NULL);
		snd->snd_in_progress = 1; /*TODO: Check set flag timing*/
		_os_spinunlock(d, &snd->snd_lock, _bh, NULL);

		phl_snd_upd_snd_para(snd->phl_info, param);
		phl_fsm_state_goto(snd->fsm_obj, SND_ST_FUNC_READY);
	break;
	case FSM_EV_STATE_OUT:
		phl_fsm_cancel_alarm(snd->fsm_obj);
	break;
	case FSM_EV_CANCEL:
	case FSM_EV_TIMER_EXPIRE:
	break;
	default:
	break;
	}
	return ret;

}

static int snd_fsm_func_deinit_st_hdl(void *obj, u16 event, void *param)
{
	struct phl_sound_obj *snd = (struct phl_sound_obj *)obj;
	int ret = 0;


	switch (event) {
	case FSM_EV_STATE_IN:
	break;
	case FSM_EV_STATE_OUT:
		phl_fsm_cancel_alarm(snd->fsm_obj);
	break;
	case FSM_EV_CANCEL:
	case FSM_EV_TIMER_EXPIRE:
	break;
	default:
	break;
	}

	return ret;

}

static int snd_fsm_func_ready_st_hdl(void *obj, u16 event, void *param)
{
	struct phl_sound_obj *snd = (struct phl_sound_obj *)obj;
	int ret = 0;
	struct rtw_wifi_role_t *m_wrole =
			(struct rtw_wifi_role_t *)snd->snd_param.m_wrole;
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_SUCCESS;

	switch (event) {
	case FSM_EV_STATE_IN:
		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "snd_fsm_func_ready_st_hdl : FSM_EV_STATE_IN\n");
		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "snd_fsm_func_ready_st_hdl : (Re)Start Sounding Timestamp %d\n",
				_os_get_cur_time_us());
		if(NULL == m_wrole) {
			snd_set_timer(snd, 0, SND_EV_TERMINATE);
			PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
				  "snd_fsm_func_ready_st_hdl : NULL == m_wrole\n");
			break;
		}

		if ((PHL_RTYPE_STATION == m_wrole->type) &&
		    (MLME_LINKED != m_wrole->mstate)) {
				PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
					  "snd_fsm_func_ready_st_hdl : role STA in not in linked : 0x%x\n",
					   m_wrole->mstate);
				snd_set_timer(snd, 0, SND_EV_TERMINATE);
				break;
		}
		snd_set_timer(snd, 10, SND_FUNC_EV_PROC_GRP);
	break;
	case FSM_EV_STATE_OUT:
		phl_fsm_cancel_alarm(snd->fsm_obj);
	break;
	case FSM_EV_CANCEL:
	case FSM_EV_TIMER_EXPIRE:
		phl_fsm_cancel_alarm(snd->fsm_obj);
	break;
	case SND_FUNC_EV_PROC_GRP:
		phl_snd_func_remove_grp_all(snd->phl_info);
		/*TODO: loop for all of the wrole */
		if(NULL == m_wrole) {
			PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
				  "==> ROLE == NULL !!!\n STOP SND Function!!!\n");
			snd_set_timer(snd, 10, SND_EV_TERMINATE);
			break;
		}
		pstatus = phl_snd_func_grouping(snd->phl_info, m_wrole->id);
		if (pstatus != RTW_PHL_STATUS_SUCCESS) {
			PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
				  "==> SND_FUNC_EV_PROC_GRP FAIL !!!\n STOP SND Function!!!\n");
			snd_set_timer(snd, 10, SND_EV_TERMINATE);
			break;
		}
		if (0 == snd->snd_param.snd_func_grp_num) {
			PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
				  "==> Available SND GRP == 0 !!!\n STOP SND Function!!!\n");
			snd_set_timer(snd, 10, SND_EV_TERMINATE);
			break;
		}
		fallthrough;
		/* fall through */
	/*go through*/
	case SND_FUNC_EV_START_SND_PROC:
		pstatus = phl_snd_func_pre_config(snd->phl_info);
		if (pstatus != RTW_PHL_STATUS_SUCCESS) {
			PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
				  "==> SND_FUNC_EV_START_SND_PROC FAIL !!!\n STOP SND Function!!!\n");
			snd_set_timer(snd, 10, SND_EV_TERMINATE);
			break;
		}
		phl_fsm_state_goto(snd->fsm_obj, SND_ST_PROC_IDLE);
	break;
	case SND_EV_TERMINATE:
		snd->is_terminated = true;
		phl_fsm_cancel_alarm(snd->fsm_obj);
		phl_fsm_state_goto(snd->fsm_obj, SND_ST_PROC_TERMINATE);
	break;
	default:
	break;
	}

	return ret;
}

static int snd_fsm_func_leave_st_hdl(void *obj, u16 event, void *param)
{
	struct phl_sound_obj *snd = (struct phl_sound_obj *)obj;
	void *d = phl_to_drvpriv(snd->phl_info);
	int ret = 0;


	switch (event) {
	case FSM_EV_STATE_IN:
		_os_spinlock(d, &snd->snd_lock, _bh, NULL);
		snd->snd_in_progress = 0;
		_os_spinunlock(d, &snd->snd_lock, _bh, NULL);
		phl_fsm_state_goto(snd->fsm_obj, SND_ST_FUNC_INIT);
	break;
	case FSM_EV_STATE_OUT:
		phl_fsm_cancel_alarm(snd->fsm_obj);
	break;
	case FSM_EV_CANCEL:
	case FSM_EV_TIMER_EXPIRE:
		phl_fsm_cancel_alarm(snd->fsm_obj);
	break;
	case SND_EV_TERMINATE:
		_os_spinlock(d, &snd->snd_lock, _bh, NULL);
		snd->is_terminated = 1;
		snd->snd_in_progress = 0;
		_os_spinunlock(d, &snd->snd_lock, _bh, NULL);
		phl_fsm_state_goto(snd->fsm_obj, SND_ST_FUNC_INIT);
	break;
	default:
	break;
	}
	return ret;
}



static int snd_fsm_proc_idle_st_hdl(void *obj, u16 event, void *param)
{
	struct phl_sound_obj *snd = (struct phl_sound_obj *)obj;
	struct phl_sound_param *snd_param = &snd->snd_param;
	int ret = 0;
	struct phl_snd_grp *grp = NULL;

	switch (event) {
	case FSM_EV_STATE_IN:

	/* go through */
	case SND_PROC_EV_IDLE_FREE_PRE_RES :
	PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
		  "pre_proc_grp_idx : %d ; cur_proc_grp_idx : %d \n",
		  snd_param->pre_proc_grp_idx, snd_param->cur_proc_grp_idx);

		if (snd_param->cur_proc_grp_idx !=
			snd_param->pre_proc_grp_idx) {
			grp = phl_snd_get_grp_byidx(snd->phl_info,
						snd_param->pre_proc_grp_idx);
			if (grp != NULL) {
				phl_snd_proc_chk_prev_grp(snd->phl_info, grp);
			}
		}
		fallthrough;
		/* fall through */
	/* go through */
	case SND_PROC_EV_IDLE_GET_SND_GRP :
		if(MAX_SND_GRP_NUM == snd_param->cur_proc_grp_idx) {
			snd_set_timer(snd, 10, SND_EV_TERMINATE);
			break;
		}

		grp = phl_snd_get_grp_byidx(snd->phl_info,
					    snd_param->cur_proc_grp_idx);
		/* TODO: Check Grp STA is still available */
		/* phl_snd_check_condition() */
		if (NULL == grp) {
			snd_set_timer(snd, 0, SND_EV_TERMINATE);
			break;
		}
		phl_fsm_state_goto(snd->fsm_obj, SND_ST_PROC_BUSY); /* For SND FSM LOOP Test */
	break;
	case FSM_EV_CANCEL:
	case FSM_EV_TIMER_EXPIRE:
		phl_fsm_cancel_alarm(snd->fsm_obj);
	break;
	case FSM_EV_STATE_OUT:
		phl_fsm_cancel_alarm(snd->fsm_obj);
	break;
	case SND_EV_TERMINATE:
		snd->is_terminated = true;
		phl_fsm_state_goto(snd->fsm_obj, SND_ST_PROC_TERMINATE);
	break;
	default:
	break;
	}
	return ret;
}
static int snd_fsm_proc_busy_st_hdl(void *obj, u16 event, void *param)
{
	struct phl_sound_obj *snd = (struct phl_sound_obj *)obj;
	struct phl_sound_param *snd_param = &snd->snd_param;
	int ret = 0;
	struct phl_snd_grp *grp = NULL;
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_SUCCESS;
	u8 sta_num = 0;
	struct rtw_wifi_role_t *wrole =
		(struct rtw_wifi_role_t *)snd->snd_param.m_wrole;

	switch (event) {
	case FSM_EV_STATE_IN:
		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "SND PROC BUSY : Process Grp idx = %d\n",
			  snd_param->cur_proc_grp_idx);
		grp = phl_snd_get_grp_byidx(snd->phl_info,
					    snd_param->cur_proc_grp_idx);
		if (NULL == grp) {
			snd_set_timer(snd, 0, SND_PROC_EV_BUSY_SND_DOWN);
			break;
		}
		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "SND_PROC_BUSY : grp sta number = %d \n",
			  grp->num_sta);
		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "SND_PROC_BUSY : grp snd_fb_t = 0x%x \n",
			  grp->sta[0].snd_fb_t);
		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "SND_PROC_BUSY : grp primary sta macid = 0x%x \n",
			  grp->sta[0].macid);
		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "SND_PROC_BUSY : grp sta-1 macid = 0x%x \n",
			  grp->sta[1].macid);
		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "SND_PROC_BUSY : grp sta-2 macid = 0x%x \n",
			  grp->sta[2].macid);
		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "SND_PROC_BUSY : grp sta-3 macid = 0x%x \n",
			  grp->sta[3].macid);
		fallthrough;
		/* fall through */
	case SND_PROC_EV_BUSY_GET_BF_RES:
		pstatus = phl_snd_proc_get_res(snd->phl_info, grp, &sta_num);
		if ((sta_num == 0) || (RTW_PHL_STATUS_SUCCESS != pstatus)) {
			PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_, "SND_PROC_BUSY : sta_num = 0 !!! \n");
			grp->snd_sts = PHL_SND_STS_FAILURE;
			snd_set_timer(snd, 0, SND_PROC_EV_BUSY_SND_DOWN);
			break;
		}
		fallthrough;
		/* fall through */
	/* go through */
	case SND_PROC_EV_BUSY_PRE_CFG:
		pstatus = phl_snd_proc_precfg(snd->phl_info, grp);
		fallthrough;
		/* fall through */
	/* go through */
	case SND_PROC_EV_BUSY_TRIG_SND:
		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "[SND] Dialog token = 0x%x\n",
			  snd->snd_param.snd_dialog_token);
		pstatus = phl_snd_proc_start_sounding_fw(snd->phl_info, grp);
		if (RTW_PHL_STATUS_SUCCESS != pstatus) {
			PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
				  "phl_snd_proc_start_sounding_fw FAIL\n");
			grp->snd_sts = PHL_SND_STS_FAILURE;
			if((grp->snd_type == PHL_SND_TYPE_HE_HW) ||
			   (grp->snd_type == PHL_SND_TYPE_VHT_HW)) {
				phl_snd_mac_ctrl(snd->phl_info, wrole, 1);
			}
			/* TODO : FW / halmac might crashed , stop sounding fsm module */
			snd_set_timer(snd, 0, SND_EV_TERMINATE);
		}
		snd->snd_param.snd_dialog_token++;
		snd_set_timer(snd, 10, SND_PROC_EV_BUSY_REL_SND_CTRL);
	break;
	case SND_PROC_EV_BUSY_REL_SND_CTRL :
		grp = phl_snd_get_grp_byidx(snd->phl_info,
					    snd_param->cur_proc_grp_idx);
		if (NULL == grp) {
			snd_set_timer(snd, 0, SND_PROC_EV_BUSY_SND_DOWN);
			break;
		}
		if((grp->snd_type == PHL_SND_TYPE_HE_HW) ||
		   (grp->snd_type == PHL_SND_TYPE_VHT_HW)) {
			phl_snd_mac_ctrl(snd->phl_info, wrole, 1);
		}
		snd_set_timer(snd, snd->snd_param.snd_proc_timeout_ms,
			      SND_PROC_EV_BUSY_SND_DOWN);
	break;
	case SND_PROC_EV_BUSY_SND_DOWN:
		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "SND_PROC_BUSY: Recv C2H or Timeout , Switch to PROC DOWN\n");
		grp = phl_snd_get_grp_byidx(snd->phl_info,
					    snd_param->cur_proc_grp_idx);
		if (NULL != grp)
			phl_snd_polling_pri_sta_sts(snd->phl_info, grp);
		phl_fsm_state_goto(snd->fsm_obj, SND_ST_PROC_DOWN); /* For SND FSM Periodic LOOP Test */
	break;
	case FSM_EV_STATE_OUT:
		phl_fsm_cancel_alarm(snd->fsm_obj);
	break;
	case FSM_EV_CANCEL:
	case FSM_EV_TIMER_EXPIRE:
	break;
	case SND_EV_TERMINATE:
		snd->is_terminated = true;
		phl_fsm_state_goto(snd->fsm_obj, SND_ST_PROC_TERMINATE);
	break;
	default:
	break;
	}
	return ret;
}

static int snd_fsm_proc_down_st_hdl(void *obj, u16 event, void *param)
{
	struct phl_sound_obj *snd = (struct phl_sound_obj *)obj;
	int ret = 0;
	struct phl_sound_param *snd_param = &snd->snd_param;
	struct phl_snd_grp *grp = NULL;
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_SUCCESS;

	switch (event) {
	case FSM_EV_STATE_IN:
		grp = phl_snd_get_grp_byidx(snd->phl_info,
					    snd_param->cur_proc_grp_idx);
		if (NULL == grp) {
			snd_set_timer(snd, snd_param->snd_proc_period,
						SND_PROC_EV_DOWN_SND_END);
			break;
		}

		if (PHL_SND_STS_FAILURE == grp->snd_sts) {
			snd_set_timer(snd, snd_param->snd_proc_period,
				      SND_PROC_EV_DOWN_SND_END);
		}
		fallthrough;
		/* fall through */
	/* go through */
	case SND_PROC_EV_DOWN_POST_CFG:
		pstatus = phl_snd_proc_postcfg(snd->phl_info, grp);
		if (pstatus == RTW_PHL_STATUS_SUCCESS) {
			PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
				  "POST CONFIG SUCCESS!!!!\n");
		}
		fallthrough;
		/* fall through */
	/* go through */
	case SND_PROC_EV_DOWN_SND_END:
		/* Check Test Mode */
		if (snd_param->test_flag&PHL_SND_TEST_F_ONE_TIME) {
			PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
				  "TEST MODE : Set EV to TERMINATE by test_flag\n");
			snd_set_timer(snd, snd_param->snd_proc_period,
						SND_EV_TERMINATE);
			break;
		}
		/* TODO: check next sounding needed
			if any sta disassociate or new sta associate, need re-grouping? */
		grp = phl_snd_get_grp_byidx(snd->phl_info,
					    snd_param->cur_proc_grp_idx);
		if (grp == NULL) {
			snd_set_timer(snd, snd_param->snd_proc_period,
				      SND_EV_TERMINATE);
		}
		if (RTW_PHL_STATUS_SUCCESS ==
			phl_snd_proc_chk_condition(snd->phl_info, grp)) {
			PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
				  "Status : Linked ; Set Timer for Next Sounding \n");
			snd_set_timer(snd, snd_param->snd_proc_period,
						SND_PROC_EV_DOWN_NEXT_SND);
		} else {
			PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
				  "Status : ERROR ; Set Timer to Terminate \n");
			snd_set_timer(snd, snd_param->snd_proc_period,
						SND_EV_TERMINATE);
		}
	break;
	case FSM_EV_CANCEL:
	case FSM_EV_TIMER_EXPIRE:
	break;
	case FSM_EV_STATE_OUT:
		phl_fsm_cancel_alarm(snd->fsm_obj);
	break;
	case SND_PROC_EV_DOWN_NEXT_SND:
		snd_param->pre_proc_grp_idx = snd_param->cur_proc_grp_idx;
		snd_param->cur_proc_grp_idx++;

		if (snd_param->cur_proc_grp_idx >=
			snd_param->snd_func_grp_num) {
			/* start sounding process from group list head or terminated */
			if (snd->is_terminated)
				snd_param->cur_proc_grp_idx = MAX_SND_GRP_NUM;
			else
				snd_param->cur_proc_grp_idx = 0;
		}

		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "Next Sounding Group Idx = %d",
			  snd_param->cur_proc_grp_idx);

		phl_fsm_state_goto(snd->fsm_obj, SND_ST_PROC_IDLE);
	break;
	case SND_EV_TERMINATE:
		snd->is_terminated = true;
		phl_fsm_state_goto(snd->fsm_obj, SND_ST_PROC_TERMINATE);
	break;
	default:
	break;
	}
	return ret;
}

static int snd_fsm_proc_terminiate_st_hdl(void *obj, u16 event, void *param)
{
	struct phl_sound_obj *snd = (struct phl_sound_obj *)obj;
	int ret = 0;

	switch (event) {
	case FSM_EV_STATE_IN:
		/* Free all sounding resource and goto FUNC Leave */
		phl_snd_func_remove_grp_all(snd->phl_info);

		phl_fsm_state_goto(snd->fsm_obj, SND_ST_FUNC_LEAVE);
	break;
	case FSM_EV_CANCEL:
	case FSM_EV_TIMER_EXPIRE:
		phl_fsm_cancel_alarm(snd->fsm_obj);
	break;
	case FSM_EV_STATE_OUT:
		phl_fsm_cancel_alarm(snd->fsm_obj);
	break;
	default:
	break;
	}
	return ret;
}

static void snd_dump_obj(void *obj, char *s, int *sz)
{
	/* nothing to do for now */
}

static void snd_dump_fsm(void *fsm, char *s, int *sz)
{
	/* nothing to do for now */
}

/* STATE table */
static struct fsm_state_ent snd_state_tbl[] = {
	ST_ENT(SND_ST_FUNC_INIT, snd_fsm_func_init_st_hdl),
	ST_ENT(SND_ST_FUNC_DEINIT, snd_fsm_func_deinit_st_hdl),
	ST_ENT(SND_ST_FUNC_READY, snd_fsm_func_ready_st_hdl),
	ST_ENT(SND_ST_FUNC_LEAVE, snd_fsm_func_leave_st_hdl),
	ST_ENT(SND_ST_PROC_IDLE, snd_fsm_proc_idle_st_hdl),
	ST_ENT(SND_ST_PROC_BUSY, snd_fsm_proc_busy_st_hdl),
	ST_ENT(SND_ST_PROC_DOWN, snd_fsm_proc_down_st_hdl),
	ST_ENT(SND_ST_PROC_TERMINATE,snd_fsm_proc_terminiate_st_hdl)
};

/* EVENT table */
static struct fsm_event_ent snd_event_tbl[] = {
	EV_ENT(SND_FUNC_EV_INIT),
	EV_ENT(SND_FUNC_EV_DEINIT),
	EV_ENT(SND_FUNC_EV_SND_START),
	EV_ENT(SND_FUNC_EV_NOTIFY_PENDING_REQ),
	EV_ENT(SND_FUNC_EV_CHK_SND_STS),
	EV_ENT(SND_FUNC_EV_PROC_GRP),
	EV_ENT(SND_FUNC_EV_START_SND_PROC),
	EV_ENT(SND_FUNC_EV_END_SND),
	/*PROC IDLE*/
	EV_ENT(SND_PROC_EV_IDLE_GET_SND_GRP),
	/*PROC BUSY*/
	EV_ENT(SND_PROC_EV_BUSY_GET_BF_RES),
	EV_ENT(SND_PROC_EV_IDLE_FREE_PRE_RES),
	EV_ENT(SND_PROC_EV_BUSY_PRE_CFG),
	EV_ENT(SND_PROC_EV_BUSY_TRIG_SND),
	EV_ENT(SND_PROC_EV_BUSY_REL_SND_CTRL),
	EV_ENT(SND_PROC_EV_BUSY_POLLING_STS),
	EV_ENT(SND_PROC_EV_BUSY_SND_DOWN),
	/*PROC DOWN*/
	EV_ENT(SND_PROC_EV_DOWN_POST_CFG),
	EV_ENT(SND_PROC_EV_DOWN_SND_END),
	EV_ENT(SND_PROC_EV_DOWN_NEXT_SND),
	/*COMMON*/
	EV_ENT(SND_EV_TERMINATE),
	EV_ENT(SND_EV_DONOTHING),
	EV_ENT(SND_EV_MAX),
};

/* For EXTERNAL application to create a SND FSM */
/* @root: FSM root structure
 * @phl_info: private data structure to invoke hal/phl function
 *
 * return
 * fsm_main: FSM main structure (Do NOT expose)
 */
struct fsm_main *phl_sound_new_fsm(struct fsm_root *root,
	struct phl_info_t *phl_info)
{
	void *d = phl_to_drvpriv(phl_info);
	struct fsm_main *fsm = NULL;
	struct rtw_phl_fsm_tb tb;

	_os_mem_set(d, &tb, 0, sizeof(tb));


	tb.state_tbl = snd_state_tbl;
	tb.max_state = sizeof(snd_state_tbl)/sizeof(snd_state_tbl[0]);
	tb.max_event = sizeof(snd_event_tbl)/sizeof(snd_event_tbl[0]);
	tb.evt_tbl = snd_event_tbl;
	tb.dump_obj = snd_dump_obj;
	tb.dump_obj = snd_dump_fsm;
	tb.dbg_level = FSM_DBG_INFO;
	tb.evt_level = FSM_DBG_INFO;

	fsm = phl_fsm_init_fsm(root, "sound", phl_info, &tb);

	return fsm;
}

/* For EXTERNAL application to destory SND FSM */
/* @fsm: see fsm_main
 */
void phl_snd_destory_fsm(struct fsm_main *fsm)
{
	if (fsm == NULL)
		return;

	/* deinit fsm local variable if has */

	/* call FSM Framewro to deinit fsm */
	phl_fsm_deinit_fsm(fsm);
}

/* For EXTERNAL application to destory sound object */
/* @pcmd: local created command object
 *
 */
void phl_snd_destory_obj(struct phl_sound_obj *snd)
{
	void *d = phl_to_drvpriv(snd->phl_info);
	if (snd == NULL)
		return;

	/* deinit obj local variable if has */
	_os_spinlock_free(d, &snd->snd_lock);
	_os_spinlock_free(d, &snd->cmd_lock);

	/* inform FSM framewory to recycle fsm_obj */
	phl_fsm_destory_obj(snd->fsm_obj);
}

/* For EXTERNAL interrupt handler to send event into snd fsm (expose) */

enum rtw_phl_status phl_snd_fsm_ev_c2h_snd_down(void *phl)
{
	enum RTW_PHL_SND_NOTIFY_EVENT notify = RTW_PHL_SND_C2H_SND_DOWN;

	PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			"phl_snd_event_c2h_snd_down notify 0x%x\n", notify);
	return phl_snd_fsm_send_msg(phl, notify, NULL, 0);
}


enum rtw_phl_status
phl_snd_fsm_ev_start_func(void *phl, void *req)
{
	enum rtw_phl_status status = RTW_PHL_STATUS_FAILURE;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl;
	void *d = phl_to_drvpriv(phl_info);
	struct phl_snd_start_req *snd_req = NULL;//(struct phl_snd_start_req *)req;

	enum RTW_PHL_SND_NOTIFY_EVENT notify = RTW_PHL_SND_START;

	do {
		snd_req = (struct phl_snd_start_req *)_os_kmem_alloc(d,
				sizeof(*snd_req));

		if (snd_req == NULL)
			break;

		_os_mem_cpy(d, snd_req, req, sizeof(*snd_req));

		PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			  "phl_snd_event_start_func notify 0x%x\n", notify);

		status = phl_snd_fsm_send_msg(phl, notify, snd_req,
					      sizeof(*snd_req));

		_os_kmem_free(d, snd_req, sizeof(*snd_req));

	} while (0);

	return status;
}


enum rtw_phl_status phl_snd_fsm_ev_abort(void *phl)
{
	enum RTW_PHL_SND_NOTIFY_EVENT notify = RTW_PHL_SND_ABORT;

	PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
			"phl_snd_event_abort notify 0x%x\n", notify);
	return phl_snd_fsm_send_msg(phl, notify, NULL, 0);
}

/* @phl: phl_info_t
 * @notify: event to snd fsm
 */
enum rtw_phl_status
phl_snd_fsm_send_msg(void *phl, enum RTW_PHL_SND_NOTIFY_EVENT notify,
				void *buf, u32 buf_sz)
{
	struct phl_info_t *phl_info = (struct phl_info_t *)phl;
	struct phl_sound_obj *snd = phl_info->snd_obj;
	u16 event;

	switch (notify) {
	case RTW_PHL_SND_START: /* Start Sound Func */
		event = SND_FUNC_EV_SND_START;
		break;
	case RTW_PHL_SND_ABORT: /* Go Terminate*/
		event = SND_EV_TERMINATE;
		break;
	case RTW_PHL_SND_C2H_SND_DOWN: /* Sound Dwon Configuration */
		event = SND_PROC_EV_BUSY_SND_DOWN;
		break;
	default:
		return RTW_PHL_STATUS_FAILURE;
	}
	PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_,
		  "phl_snd_send_msg event %d\n",
		  event);
	return phl_fsm_gen_msg(phl, snd->fsm_obj, buf, buf_sz, event);
}


/* For EXTERNAL application notify from upper layer*/
void phl_snd_notify_from_upper_watchdog_status(void *phl, bool inprogress)
{
	struct phl_info_t *phl_info = (struct phl_info_t *)phl;
	struct phl_sound_obj *snd = NULL;

	snd = phl_info->snd_obj;
	PHL_TRACE(COMP_PHL_SOUND, _PHL_INFO_, "is snd in progress : %d \n",
		  snd->snd_in_progress);
	/*TODO:*/
}
#endif /*CONFIG_FSM*/

