/******************************************************************************
 *
 * Copyright(c) 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.
 *
 *****************************************************************************/
#define _PHL_ECSA_C_
#include "phl_headers.h"

#ifdef CONFIG_PHL_ECSA
void
_phl_ecsa_dump_param(
	struct rtw_phl_ecsa_param *param
)
{
	PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_, "%s: Channel %d\n", __FUNCTION__,
		  param->ch);
	PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_, "%s: Op class %d\n", __FUNCTION__,
		  param->op_class);
	PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_, "%s: Count %d\n", __FUNCTION__,
		  param->count);
	PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_, "%s: Mode %d\n", __FUNCTION__,
		  param->mode);
	PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_, "%s: Delay time %d\n", __FUNCTION__,
		  param->delay_start_ms);
	PHL_DUMP_CHAN_DEF(&(param->new_chan_def));
}

enum rtw_phl_status
_phl_ecsa_tx_pause(
	struct phl_ecsa_ctrl_t *ecsa_ctrl
)
{
	enum rtw_phl_status status = RTW_PHL_STATUS_SUCCESS;
	struct rtw_phl_com_t *phl_com = ecsa_ctrl->phl_com;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl_com->phl_priv;
	struct rtw_wifi_role_t *wifi_role = ecsa_ctrl->role;

	/* Pause SW Tx */
	rtw_phl_tx_stop(phl_info);
	rtw_phl_tx_req_notify(phl_info);

	/* Disable hw tx all  */
	if (rtw_hal_dfs_pause_tx(phl_info->hal, wifi_role->hw_band, true) ==
	    RTW_HAL_STATUS_SUCCESS) {
		status = RTW_PHL_STATUS_SUCCESS;
		PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_, "[ECSA] hw tx pause OK\n");
	} else {
		status = RTW_PHL_STATUS_FAILURE;
		PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_, "[ECSA] hw tx pause fail\n");
	}

	return status;
}

enum rtw_phl_status
_phl_ecsa_tx_resume(
	struct phl_ecsa_ctrl_t *ecsa_ctrl
)
{
	enum rtw_phl_status status = RTW_PHL_STATUS_SUCCESS;
	struct rtw_phl_com_t *phl_com = ecsa_ctrl->phl_com;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl_com->phl_priv;
	struct rtw_wifi_role_t *wifi_role = ecsa_ctrl->role;

	/* Enable hw tx all  */
	if (rtw_hal_dfs_pause_tx(phl_info->hal, wifi_role->hw_band, false) ==
	    RTW_HAL_STATUS_SUCCESS) {
		status = RTW_PHL_STATUS_SUCCESS;
		PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_, "[ECSA] hw tx unpause OK\n");
	} else {
		status = RTW_PHL_STATUS_FAILURE;
		PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_, "[ECSA] hw tx unpause fail\n");
	}

	rtw_phl_tx_resume(phl_info);

	return status;
}

u32
_phl_ecsa_calculate_next_timer_ap(
	struct phl_ecsa_ctrl_t *ecsa_ctrl
)
{
	struct rtw_phl_com_t *phl_com = ecsa_ctrl->phl_com;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl_com->phl_priv;
	struct rtw_bcn_info_cmn *bcn_cmn = NULL;
	u32 tsf_h = 0, tsf_l = 0;
	u64 tsf = 0;
	u32 beacon_period_us = 0, timeslot_us = 0, next_timeslot_us = 0;
	u32 current_time_ms = _os_get_cur_time_ms();

	if (RTW_HAL_STATUS_SUCCESS != rtw_hal_get_tsf(phl_info->hal,
						      ecsa_ctrl->role->hw_port,
						      &tsf_h,
						      &tsf_l)) {
		PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_, "_phl_ecsa_timer_callback(): Get tsf fail\n");
		return 0;
	}
	tsf = tsf_h;
	tsf = tsf << 32;
	tsf |= tsf_l;

	bcn_cmn = &ecsa_ctrl->role->bcn_cmn;
	beacon_period_us = bcn_cmn->bcn_interval * TU;

	timeslot_us = (u32)_os_modular64(tsf, beacon_period_us);

	PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_, "%s: CurTimeMs = %d State = %x timeslot = %d\n",
		  __FUNCTION__, current_time_ms, ecsa_ctrl->state, timeslot_us);

	if(ecsa_ctrl->state == ECSA_STATE_START){
		next_timeslot_us = beacon_period_us - timeslot_us + (2 * TU);
	}
	/* To make sure first ECSA IE show in Beacon */
	else if(ecsa_ctrl->state == ECSA_STATE_UPDATE_FIRST_BCN_DONE){
		next_timeslot_us = (beacon_period_us - timeslot_us -
				    ECSA_UPDATE_BCN_BEFORE_TBTT_US);
		ecsa_ctrl->expected_tbtt_ms = current_time_ms +
				  (beacon_period_us - timeslot_us)/1000;
	}
	else if(ecsa_ctrl->state == ECSA_STATE_COUNT_DOWN){
		if(ecsa_ctrl->ecsa_param.count == 1){
			next_timeslot_us = (beacon_period_us - timeslot_us) +
					ECSA_SWITCH_TIME_AFTER_LAST_COUNT_DOWN;
		}
		else{
			next_timeslot_us = (beacon_period_us - timeslot_us) +
					(beacon_period_us - ECSA_UPDATE_BCN_BEFORE_TBTT_US);

			ecsa_ctrl->expected_tbtt_ms = current_time_ms +
					(2 * beacon_period_us - timeslot_us)/1000;
		}
	}

	PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_, "%s: Expected tbtt %d!\n", __FUNCTION__, ecsa_ctrl->expected_tbtt_ms);
	return next_timeslot_us/1000;
}

u32
_phl_ecsa_calculate_next_timer_sta(
	struct phl_ecsa_ctrl_t *ecsa_ctrl
)
{
	struct rtw_wifi_role_t *wifi_role = ecsa_ctrl->role;
	struct rtw_phl_com_t *phl_com = ecsa_ctrl->phl_com;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl_com->phl_priv;
	struct rtw_phl_stainfo_t *sta = NULL;
	u32 beacon_period_us = 0, next_timeslot = 0;
	u32 current_time_ms = 0;

	current_time_ms = _os_get_cur_time_ms();
	PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_, "%s: CurTimeMs = %d State = %x\n",
		  __FUNCTION__, current_time_ms, ecsa_ctrl->state);

	sta = rtw_phl_get_stainfo_self(phl_info, wifi_role);
	if(sta == NULL){
		PHL_TRACE(COMP_PHL_ECSA, _PHL_ERR_, "%s: Get sta info fail!\n",
			  __FUNCTION__);
		return 0;
	}

	beacon_period_us = sta->asoc_cap.bcn_interval * TU;

	if(ecsa_ctrl->state == ECSA_STATE_START){
		next_timeslot = 0;
	}
	else if(ecsa_ctrl->state == ECSA_STATE_COUNT_DOWN){
		u8 count = ecsa_ctrl->ecsa_param.count;
		next_timeslot = (beacon_period_us * count) / 1000; /* ms */
	}
	else if(ecsa_ctrl->state == ECSA_STATE_SWITCH){
		next_timeslot = 1000; /* 1s */
	}

	return next_timeslot;
}

void
_phl_ecsa_calculate_next_timer(
	struct phl_ecsa_ctrl_t *ecsa_ctrl
)
{
	struct rtw_phl_com_t *phl_com = ecsa_ctrl->phl_com;
	void *d = phlcom_to_drvpriv(phl_com);
	u32 next_timeslot = 0; /* ms */

	if(IS_ECSA_TYPE_AP(ecsa_ctrl))
		next_timeslot = _phl_ecsa_calculate_next_timer_ap(ecsa_ctrl);

	if(IS_ECSA_TYPE_STA(ecsa_ctrl))
		next_timeslot = _phl_ecsa_calculate_next_timer_sta(ecsa_ctrl);;
	PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_, "%s: Next time slot %d!\n", __FUNCTION__, next_timeslot);
	_os_set_timer(d, &ecsa_ctrl->timer, next_timeslot);

}

void _phl_ecsa_state_change_ap(
	struct phl_ecsa_ctrl_t *ecsa_ctrl
)
{
	enum rtw_phl_status status = RTW_PHL_STATUS_SUCCESS;
	struct rtw_phl_com_t *phl_com = ecsa_ctrl->phl_com;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl_com->phl_priv;
	struct phl_msg msg = {0};
	struct phl_msg_attribute attr = {0};
	void *d = phlcom_to_drvpriv(phl_com);

	SET_MSG_MDL_ID_FIELD(msg.msg_id, PHL_FG_MDL_ECSA);

	PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_, "%s: CurTimeMs = %d State = %x\n",
		  __FUNCTION__, _os_get_cur_time_ms(), ecsa_ctrl->state);

	/* Protect ECSA state change to prevent timer callback racing */
	_os_spinlock(d, &(ecsa_ctrl->lock), _bh, NULL);

	if(ecsa_ctrl->state == ECSA_STATE_WAIT_DELAY){
		status = rtw_phl_ecsa_cmd_request(phl_info, ecsa_ctrl->role);
		if(status != RTW_PHL_STATUS_SUCCESS){
			PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_,
				  "%s: ECSA command fail!\n", __FUNCTION__);
			ecsa_ctrl->state = ECSA_STATE_NONE;
		}
		else{
			ecsa_ctrl->state = ECSA_STATE_START;
		}

	}
	else if(ecsa_ctrl->state == ECSA_STATE_START){
		ecsa_ctrl->state = ECSA_STATE_UPDATE_FIRST_BCN_DONE;
		SET_MSG_EVT_ID_FIELD(msg.msg_id, MSG_EVT_ECSA_UPDATE_FIRST_BCN_DONE);

		status = phl_disp_eng_send_msg(phl_info, &msg, &attr, NULL);
		if(status != RTW_PHL_STATUS_SUCCESS)
			PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_, "%s: Send msg fail!\n", __FUNCTION__);
	}
	/* To make sure first ECSA IE show in Beacon */
	else if(ecsa_ctrl->state == ECSA_STATE_UPDATE_FIRST_BCN_DONE){
		ecsa_ctrl->state = ECSA_STATE_COUNT_DOWN;
		SET_MSG_EVT_ID_FIELD(msg.msg_id, MSG_EVT_ECSA_COUNT_DOWN);

		status = phl_disp_eng_send_msg(phl_info, &msg, &attr, NULL);
		if(status != RTW_PHL_STATUS_SUCCESS)
			PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_, "%s: Send msg fail!\n", __FUNCTION__);
	}
	else if(ecsa_ctrl->state == ECSA_STATE_COUNT_DOWN){
		if(ecsa_ctrl->ecsa_param.count == 1){
			ecsa_ctrl->state = ECSA_STATE_SWITCH;
			SET_MSG_EVT_ID_FIELD(msg.msg_id, MSG_EVT_ECSA_SWITCH_START);
			msg.rsvd[0] =  (u8*)ecsa_ctrl->role;
			status = phl_disp_eng_send_msg(phl_info, &msg, &attr, NULL);
			if(status != RTW_PHL_STATUS_SUCCESS)
				PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_, "%s: Send msg fail!\n", __FUNCTION__);
		}
		else{
			SET_MSG_EVT_ID_FIELD(msg.msg_id, MSG_EVT_ECSA_COUNT_DOWN);

			status = phl_disp_eng_send_msg(phl_info, &msg, &attr, NULL);
			if(status != RTW_PHL_STATUS_SUCCESS)
				PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_, "%s: Send msg fail!\n", __FUNCTION__);
		}
	}
	_os_spinunlock(d, &(ecsa_ctrl->lock), _bh, NULL);
}

void _phl_ecsa_state_change_sta(
	struct phl_ecsa_ctrl_t *ecsa_ctrl
)
{
	enum rtw_phl_status status = RTW_PHL_STATUS_SUCCESS;
	struct rtw_phl_com_t *phl_com = ecsa_ctrl->phl_com;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl_com->phl_priv;
	struct phl_msg msg = {0};
	struct phl_msg_attribute attr = {0};
	void *d = phlcom_to_drvpriv(phl_com);

	SET_MSG_MDL_ID_FIELD(msg.msg_id, PHL_FG_MDL_ECSA);

	PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_, "%s: CurTimeMs = %d State = %x\n",
		  __FUNCTION__, _os_get_cur_time_ms(), ecsa_ctrl->state);

	/* Protect ECSA state change to prevent timer callback racing */
	_os_spinlock(d, &(ecsa_ctrl->lock), _bh, NULL);

	if(ecsa_ctrl->state == ECSA_STATE_WAIT_DELAY){
		status = rtw_phl_ecsa_cmd_request(phl_info, ecsa_ctrl->role);
		if(status != RTW_PHL_STATUS_SUCCESS){
			PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_,
				  "%s: ECSA command fail!\n", __FUNCTION__);
			ecsa_ctrl->state = ECSA_STATE_NONE;
		}
		else{
			ecsa_ctrl->state = ECSA_STATE_START;
		}
	}
	else if(ecsa_ctrl->state == ECSA_STATE_START){
		ecsa_ctrl->state = ECSA_STATE_COUNT_DOWN;
		SET_MSG_EVT_ID_FIELD(msg.msg_id, MSG_EVT_ECSA_COUNT_DOWN);

		status = phl_disp_eng_send_msg(phl_info, &msg, &attr, NULL);
		if(status != RTW_PHL_STATUS_SUCCESS)
			PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_, "%s: Send msg fail!\n", __FUNCTION__);
	}
	else if(ecsa_ctrl->state == ECSA_STATE_COUNT_DOWN){
		ecsa_ctrl->state = ECSA_STATE_SWITCH;
		SET_MSG_EVT_ID_FIELD(msg.msg_id, MSG_EVT_ECSA_SWITCH_START);
		msg.rsvd[0] =  (u8*)ecsa_ctrl->role;
		status = phl_disp_eng_send_msg(phl_info, &msg, &attr, NULL);
		if(status != RTW_PHL_STATUS_SUCCESS)
			PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_, "%s: Send msg fail!\n", __FUNCTION__);
	}
	else if(ecsa_ctrl->state == ECSA_STATE_SWITCH){
		SET_MSG_EVT_ID_FIELD(msg.msg_id, MSG_EVT_ECSA_CHECK_TX_RESUME);
		status = phl_disp_eng_send_msg(phl_info, &msg, &attr, NULL);
		if(status != RTW_PHL_STATUS_SUCCESS)
			PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_, "%s: Send msg fail!\n", __FUNCTION__);
	}
	_os_spinunlock(d, &(ecsa_ctrl->lock), _bh, NULL);
}

void
_phl_ecsa_timer_callback(
	void *context
	)
{
	struct phl_ecsa_ctrl_t *ecsa_ctrl = (struct phl_ecsa_ctrl_t *)context;

	if(IS_ECSA_TYPE_AP(ecsa_ctrl))
		_phl_ecsa_state_change_ap(ecsa_ctrl);

	if(IS_ECSA_TYPE_STA(ecsa_ctrl))
		_phl_ecsa_state_change_sta(ecsa_ctrl);
}

void
_phl_ecsa_cmd_abort_hdlr(
	void* dispr,
	void* priv,
	bool abort
)
{
	struct phl_ecsa_ctrl_t *ecsa_ctrl = (struct phl_ecsa_ctrl_t *)priv;
	struct rtw_wifi_role_t *wifi_role = ecsa_ctrl->role;
	struct rtw_phl_com_t *phl_com = wifi_role->phl_com;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl_com->phl_priv;
	struct rtw_phl_ecsa_ops *ops = &ecsa_ctrl->ops;
	struct phl_msg msg = {0};
	struct phl_msg_attribute attr = {0};
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_SUCCESS;
	void *d = phlcom_to_drvpriv(phl_com);

	_os_cancel_timer(d, &ecsa_ctrl->timer);

	/* ECSA AP abort handle */
	if(IS_ECSA_TYPE_AP(ecsa_ctrl) &&
	   ecsa_ctrl->ecsa_param.flag != 0){
		ecsa_ctrl->state = ECSA_STATE_NONE;
		CLEAR_STATUS_FLAG(ecsa_ctrl->ecsa_param.flag,
				  ECSA_PARAM_FLAG_APPEND_BCN);
		CLEAR_STATUS_FLAG(ecsa_ctrl->ecsa_param.flag,
				  ECSA_PARAM_FLAG_APPEND_PROBERSP);
		/* Update Bcn */
		if(ops->update_beacon)
			ops->update_beacon(ops->priv, wifi_role);
	}

	/* ECSA STA abort handle */
	if(IS_ECSA_TYPE_STA(ecsa_ctrl)){
		if(ecsa_ctrl->ecsa_param.mode == true)
			_phl_ecsa_tx_resume(ecsa_ctrl);

		if(ops->ecsa_complete)
			ops->ecsa_complete(ops->priv, wifi_role);
	}

	SET_MSG_MDL_ID_FIELD(msg.msg_id, PHL_FG_MDL_ECSA);
	SET_MSG_EVT_ID_FIELD(msg.msg_id, MSG_EVT_ECSA_DONE);

	if(abort)
		attr.opt = MSG_OPT_SEND_IN_ABORT;

	pstatus = phl_disp_eng_send_msg(phl_info, &msg, &attr, NULL);

	if(pstatus != RTW_PHL_STATUS_SUCCESS) {
		PHL_ERR("%s:[ECSA] dispr_send_msg failed (0x%X)\n",
			__FUNCTION__, pstatus);
	}
}

enum phl_mdl_ret_code
_phl_ecsa_cmd_acquired(
	void* dispr,
	void* priv)
{
	enum rtw_phl_status status = RTW_PHL_STATUS_SUCCESS;
	enum phl_mdl_ret_code ret = MDL_RET_FAIL;
	struct phl_ecsa_ctrl_t *ecsa_ctrl = (struct phl_ecsa_ctrl_t *)priv;
	struct rtw_wifi_role_t *wifi_role = ecsa_ctrl->role;
	struct rtw_phl_com_t *phl_com = wifi_role->phl_com;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl_com->phl_priv;
	struct phl_msg msg = {0};
	struct phl_msg_attribute attr = {0};

	SET_MSG_MDL_ID_FIELD(msg.msg_id, PHL_FG_MDL_ECSA);
	SET_MSG_EVT_ID_FIELD(msg.msg_id, MSG_EVT_ECSA_START);

	status = phl_disp_eng_send_msg(phl_info, &msg, &attr, NULL);
	if(status != RTW_PHL_STATUS_SUCCESS){
		PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_, "%s: Send msg fail!\n", __FUNCTION__);
		goto exit;
	}

	ret = MDL_RET_SUCCESS;
exit:
	return ret;
}

enum phl_mdl_ret_code
_phl_ecsa_cmd_abort(
	void* dispr,
	void* priv)
{
	_phl_ecsa_cmd_abort_hdlr(dispr, priv, true);
	return MDL_RET_SUCCESS;
}

enum phl_mdl_ret_code
_phl_ecsa_cmd_msg_hdlr(
	void* dispr,
	void* priv,
	struct phl_msg* msg)
{
	struct phl_ecsa_ctrl_t *ecsa_ctrl = (struct phl_ecsa_ctrl_t *)priv;
	struct rtw_wifi_role_t *wifi_role = ecsa_ctrl->role;
	struct rtw_phl_com_t *phl_com = wifi_role->phl_com;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl_com->phl_priv;
	void *d = phlcom_to_drvpriv(phl_com);
	enum phl_mdl_ret_code ret = MDL_RET_IGNORE;
	enum rtw_phl_status status = RTW_PHL_STATUS_SUCCESS;
	struct phl_msg nextmsg = {0};
	struct phl_msg_attribute attr = {0};
	struct rtw_phl_ecsa_ops *ops = &ecsa_ctrl->ops;
	u32 current_time_ms = _os_get_cur_time_ms();
	struct rtw_bcn_info_cmn *bcn_cmn = &ecsa_ctrl->role->bcn_cmn;
	u32 beacon_period_ms = bcn_cmn->bcn_interval * TU / 1000;
	u8 countdown_n = 1;
	struct rtw_chan_def chdef_to_switch = {0};

	if(MSG_MDL_ID_FIELD(msg->msg_id) != PHL_FG_MDL_ECSA) {
		return MDL_RET_IGNORE;
	}

	if(IS_MSG_FAIL(msg->msg_id)) {
		_phl_ecsa_cmd_abort_hdlr(dispr, priv, false);
		status = phl_disp_eng_free_token(phl_info,
						 wifi_role->hw_band,
						 &ecsa_ctrl->req_hdl);
		if(status != RTW_PHL_STATUS_SUCCESS)
			PHL_WARN("%s: Free token fail!\n", __FUNCTION__);
		return MDL_RET_SUCCESS;
	}

	SET_MSG_MDL_ID_FIELD(nextmsg.msg_id, PHL_FG_MDL_ECSA);

	switch(MSG_EVT_ID_FIELD(msg->msg_id)){
		case MSG_EVT_ECSA_START:
			PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_,
				  "%s: MSG_EVT_ECSA_START\n", __FUNCTION__);
			if(IS_ECSA_TYPE_AP(ecsa_ctrl)){
				SET_STATUS_FLAG(ecsa_ctrl->ecsa_param.flag,
						ECSA_PARAM_FLAG_APPEND_BCN);
				/* Update Bcn */
				if(ops->update_beacon)
					ops->update_beacon(ops->priv, wifi_role);
			}

			if(IS_ECSA_TYPE_STA(ecsa_ctrl) &&
			   ecsa_ctrl->ecsa_param.mode == true){
				_phl_ecsa_tx_pause(ecsa_ctrl);
			}
			_phl_ecsa_calculate_next_timer(ecsa_ctrl);
			break;
		case MSG_EVT_ECSA_UPDATE_FIRST_BCN_DONE:
			PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_,
				  "%s: MSG_EVT_ECSA_UPDATE_FIRST_BCN_DONE\n", __FUNCTION__);
			SET_STATUS_FLAG(ecsa_ctrl->ecsa_param.flag,
					ECSA_PARAM_FLAG_APPEND_PROBERSP);
			_phl_ecsa_calculate_next_timer(ecsa_ctrl);
			break;
		case MSG_EVT_ECSA_COUNT_DOWN:
			PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_,
				  "%s: MSG_EVT_ECSA_COUNT_DOWN\n", __FUNCTION__);

			/* Count down mode of STA ECSA only calculate the switch time */
			if(IS_ECSA_TYPE_STA(ecsa_ctrl)){
				_phl_ecsa_calculate_next_timer(ecsa_ctrl);
				break;
			}

			/* Count down mode of AP ECSA calculate the update beacon time */
			if(ecsa_ctrl->expected_tbtt_ms > current_time_ms){
				countdown_n = 1;
			}
			else{
				/*
				 * There may be delay time during msg delivery,
				 * calulate the actual countdown value
				 */
				countdown_n = (u8)((current_time_ms-(ecsa_ctrl->expected_tbtt_ms))%beacon_period_ms+1);

			}

			PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_,
				  "%s: count down %d\n", __FUNCTION__, countdown_n);

			if(ecsa_ctrl->ecsa_param.count > countdown_n){
				ecsa_ctrl->ecsa_param.count -= countdown_n;
				/* Update Bcn */
				if(ops->update_beacon)
					ops->update_beacon(ops->priv, wifi_role);
				_phl_ecsa_calculate_next_timer(ecsa_ctrl);
			}
			else{
				/*
				 * If the countdown value is less than 1,
				 * we have to switch channel immediately
				 */
				ecsa_ctrl->ecsa_param.count = 0;
				ecsa_ctrl->state = ECSA_STATE_SWITCH;
				SET_MSG_EVT_ID_FIELD(nextmsg.msg_id, MSG_EVT_ECSA_SWITCH_START);
				nextmsg.rsvd[0] =  (u8*)ecsa_ctrl->role;
				status = phl_disp_eng_send_msg(phl_info,
							       &nextmsg,
							       &attr,
							       NULL);
				if(status != RTW_PHL_STATUS_SUCCESS)
					PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_, "%s: Send msg fail!\n", __FUNCTION__);
			}
			break;
		case MSG_EVT_ECSA_SWITCH_START:
			PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_,
				  "%s: MSG_EVT_ECSA_SWITCH_START\n", __FUNCTION__);

			/* Update channel info */
			if(ops->update_chan_info){
				ops->update_chan_info(ops->priv,
						      wifi_role,
						      ecsa_ctrl->ecsa_param.new_chan_def);
				PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_,
				  "%s: update_chan_info done!\n", __FUNCTION__);
			}
			else{
				PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_,
				  "%s: update_chan_info is NULL!\n", __FUNCTION__);
			}

			/* AP mode ECSA should update beacon to remove ECSA IE and update the channel info */
			if(IS_ECSA_TYPE_AP(ecsa_ctrl)){
				CLEAR_STATUS_FLAG(ecsa_ctrl->ecsa_param.flag,
						  ECSA_PARAM_FLAG_APPEND_BCN);
				CLEAR_STATUS_FLAG(ecsa_ctrl->ecsa_param.flag,
						  ECSA_PARAM_FLAG_APPEND_PROBERSP);
				/* Update Bcn */
				if(ops->update_beacon)
					ops->update_beacon(ops->priv, wifi_role);
			}

			/*
			 * We should use chandef of the chanctx to switch,
			 * the bw may not be same as the ECSA operating class
			 * because of the SCC mode with different bandwidth.
			 */
			if(wifi_role->chanctx != NULL){
				_os_mem_cpy(d, &chdef_to_switch, &(wifi_role->chanctx->chan_def),
					    sizeof(struct rtw_chan_def));
				if(wifi_role->chanctx->chan_def.chan !=
				   ecsa_ctrl->ecsa_param.new_chan_def.chan)
					PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_,
				  		  "%s: channel is not same as ECSA parameter!\n",
						  __FUNCTION__);
			}
			else{
				_os_mem_cpy(d, &chdef_to_switch, &(ecsa_ctrl->ecsa_param.new_chan_def),
					    sizeof(struct rtw_chan_def));
				PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_,
				  	  "%s: chanctx of role is NULL use ECSA parameter!\n",
					  __FUNCTION__);
			}

			/* Switch channel */
			phl_set_ch_bw(wifi_role, &chdef_to_switch, true);

			SET_MSG_EVT_ID_FIELD(nextmsg.msg_id, MSG_EVT_ECSA_SWITCH_DONE);
			nextmsg.rsvd[0] =  (u8*)ecsa_ctrl->role;
			status = phl_disp_eng_send_msg(phl_info,
						       &nextmsg,
						       &attr,
						       NULL);
			if(status != RTW_PHL_STATUS_SUCCESS)
				PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_,
					  "%s: Send msg fail!\n", __FUNCTION__);
			break;
		case MSG_EVT_ECSA_SWITCH_DONE:
			PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_,
				  "%s: MSG_EVT_ECSA_SWITCH_DONE\n", __FUNCTION__);
			if(IS_ECSA_TYPE_STA(ecsa_ctrl) &&
			   ecsa_ctrl->ecsa_param.mode == true){
				SET_MSG_EVT_ID_FIELD(nextmsg.msg_id, MSG_EVT_ECSA_CHECK_TX_RESUME);
				status = phl_disp_eng_send_msg(phl_info, &nextmsg, &attr, NULL);
				if(status != RTW_PHL_STATUS_SUCCESS)
					PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_,
						"%s: Send msg fail!\n", __FUNCTION__);
				break;
			}

			SET_MSG_EVT_ID_FIELD(nextmsg.msg_id, MSG_EVT_ECSA_DONE);
			status = phl_disp_eng_send_msg(phl_info,
						       &nextmsg,
						       &attr,
						       NULL);
			if(status != RTW_PHL_STATUS_SUCCESS)
				PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_,
					  "%s: Send msg fail!\n", __FUNCTION__);
			break;
		case MSG_EVT_ECSA_CHECK_TX_RESUME:
			PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_,
				  "%s: MSG_EVT_ECSA_CHECK_TX_RESUME\n", __FUNCTION__);
			if(IS_ECSA_TYPE_STA(ecsa_ctrl) &&
			   ecsa_ctrl->ecsa_param.mode == true){
				/*
				 * TODO: If driver support DFS-slave with radar
				 * detection, ECSA should tx un-pause directly
				 * and the tx pause should be handled by DFS-slave.
				 */
				if(ops->check_tx_resume_allow){
					if(!ops->check_tx_resume_allow(ops->priv, wifi_role)){
						PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_,
				  			  "%s: Keep Tx pause...\n", __FUNCTION__);
						_phl_ecsa_calculate_next_timer(ecsa_ctrl);
						break;
					}
				}
				PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_,
				  	  "%s: Tx resume!\n", __FUNCTION__);
				_phl_ecsa_tx_resume(ecsa_ctrl);
			}

			SET_MSG_EVT_ID_FIELD(nextmsg.msg_id, MSG_EVT_ECSA_DONE);
			status = phl_disp_eng_send_msg(phl_info, &nextmsg, &attr, NULL);
			if(status != RTW_PHL_STATUS_SUCCESS)
				PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_,
					  "%s: Send msg fail!\n", __FUNCTION__);

			break;
		case MSG_EVT_ECSA_DONE:
			PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_,
				  "%s: MSG_EVT_ECSA_DONE\n", __FUNCTION__);
			ecsa_ctrl->state = ECSA_STATE_NONE;

			if(ops->ecsa_complete){
				ops->ecsa_complete(ops->priv, wifi_role);
			}
			else{
				PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_,
			  "%s: ecsa_complete is NULL!\n", __FUNCTION__);
			}

			status = phl_disp_eng_free_token(phl_info,
							 wifi_role->hw_band,
							 &ecsa_ctrl->req_hdl);
			if(status != RTW_PHL_STATUS_SUCCESS)
				PHL_WARN("%s: Free token fail!\n", __FUNCTION__);
			break;
		default:
			break;
	}
	return ret;
}

enum phl_mdl_ret_code
_phl_ecsa_cmd_set_info(
	void* dispr,
	void* priv,
	struct phl_module_op_info* info)
{
	enum phl_mdl_ret_code ret = MDL_RET_IGNORE;

	/* PHL_INFO(" %s :: info->op_code=%d \n", __func__, info->op_code); */
	return ret;
}

enum phl_mdl_ret_code
_phl_ecsa_cmd_query_info(
	void* dispr,
	void* priv,
	struct phl_module_op_info* info)
{
	struct phl_ecsa_ctrl_t *ecsa_ctrl = (struct phl_ecsa_ctrl_t *)priv;
	enum phl_mdl_ret_code ret = MDL_RET_IGNORE;
	/* PHL_INFO(" %s :: info->op_code=%d \n", __func__, info->op_code); */

	switch(info->op_code) {
		case FG_REQ_OP_GET_ROLE:
			info->outbuf = (u8*)ecsa_ctrl->role;
			ret = MDL_RET_SUCCESS;
			break;
		default:
			break;
	}

	return ret;
}

enum rtw_phl_status
rtw_phl_ecsa_cmd_request(
	void *phl,
	struct rtw_wifi_role_t *role
	)
{
	enum rtw_phl_status status = RTW_PHL_STATUS_FAILURE;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl;
	struct phl_ecsa_ctrl_t *ecsa_ctrl =
		(struct phl_ecsa_ctrl_t *)phl_info->ecsa_ctrl;
	struct phl_cmd_token_req req={0};

	if(ecsa_ctrl == NULL)
		goto exit;

	/* Fill foreground command request */
	req.module_id= PHL_FG_MDL_ECSA;
	req.priv = ecsa_ctrl;
	req.role = role;

	req.acquired = _phl_ecsa_cmd_acquired;
	req.abort = _phl_ecsa_cmd_abort;
	req.msg_hdlr = _phl_ecsa_cmd_msg_hdlr;
	req.set_info = _phl_ecsa_cmd_set_info;
	req.query_info = _phl_ecsa_cmd_query_info;

	status = phl_disp_eng_add_token_req(phl, role->hw_band, &req,
					    &ecsa_ctrl->req_hdl);
	if((status != RTW_PHL_STATUS_SUCCESS) &&
	   (status != RTW_PHL_STATUS_PENDING))
		goto exit;

	status = RTW_PHL_STATUS_SUCCESS;

exit:
	return status;
}

enum rtw_phl_status
rtw_phl_ecsa_start(
	void *phl,
	struct rtw_wifi_role_t *role,
	struct rtw_phl_ecsa_param *param
	)
{
	struct phl_info_t *phl_info = (struct phl_info_t *)phl;
	void *d = phlcom_to_drvpriv(phl_info->phl_com);
	struct phl_ecsa_ctrl_t *ecsa_ctrl =
		(struct phl_ecsa_ctrl_t *)phl_info->ecsa_ctrl;
	struct rtw_phl_ecsa_param *ecsa_param = &(ecsa_ctrl->ecsa_param);

	if(ecsa_ctrl == NULL)
		return RTW_PHL_STATUS_FAILURE;
	if(ecsa_ctrl->state != ECSA_STATE_NONE){
		PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_, "%s: ECSA already started!\n",
			  __FUNCTION__);
		return RTW_PHL_STATUS_FAILURE;
	}

	ecsa_ctrl->role = role;
	_os_mem_cpy(d, ecsa_param, param, sizeof(struct rtw_phl_ecsa_param));
	_phl_ecsa_dump_param(ecsa_param);
	ecsa_ctrl->state = ECSA_STATE_WAIT_DELAY;
	PHL_TRACE(COMP_PHL_ECSA, _PHL_INFO_, "%s: ECSA start after %dms !\n",
		  __FUNCTION__, ecsa_ctrl->ecsa_param.delay_start_ms);
	_os_set_timer(d, &ecsa_ctrl->timer, ecsa_ctrl->ecsa_param.delay_start_ms);

	return RTW_PHL_STATUS_SUCCESS;
}

enum rtw_phl_status
rtw_phl_ecsa_cancel(
	void *phl,
	struct rtw_wifi_role_t *role
	)
{
	enum rtw_phl_status status = RTW_PHL_STATUS_SUCCESS;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl;
	void *d = phlcom_to_drvpriv(phl_info->phl_com);
	struct phl_ecsa_ctrl_t *ecsa_ctrl =
		(struct phl_ecsa_ctrl_t *)phl_info->ecsa_ctrl;

	if(ecsa_ctrl == NULL){
		status = RTW_PHL_STATUS_FAILURE;
		goto exit;
	}

	if(ecsa_ctrl->state == ECSA_STATE_NONE)
		goto exit;

	_os_cancel_timer(d, &ecsa_ctrl->timer);

	_os_spinlock(d, &(ecsa_ctrl->lock), _bh, NULL);
	if(ecsa_ctrl->state > ECSA_STATE_WAIT_DELAY){
		status = phl_disp_eng_cancel_token_req(phl_info,
							role->hw_band,
							&ecsa_ctrl->req_hdl);

		if(status != RTW_PHL_STATUS_SUCCESS){
			PHL_TRACE(COMP_PHL_ECSA, _PHL_WARNING_,
				"%s: ECSA cancel req fail!\n", __FUNCTION__);
		}

	}
	else{
		ecsa_ctrl->state = ECSA_STATE_NONE;
	}
	_os_spinunlock(d, &(ecsa_ctrl->lock), _bh, NULL);

exit:
	return status;
}

enum rtw_phl_status
rtw_phl_ecsa_get_param(
	void *phl,
	struct rtw_phl_ecsa_param **param
	)
{
	enum rtw_phl_status status = RTW_PHL_STATUS_FAILURE;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl;
	struct phl_ecsa_ctrl_t *ecsa_ctrl =
		(struct phl_ecsa_ctrl_t *)phl_info->ecsa_ctrl;

	if(ecsa_ctrl == NULL)
		goto exit;

	*param = &ecsa_ctrl->ecsa_param;
exit:
	return status;
}

enum rtw_phl_status
phl_ecsa_ctrl_init(
	struct phl_info_t *phl_info
	)
{
	void *drv_priv = phl_to_drvpriv(phl_info);
	struct phl_ecsa_ctrl_t *ecsa_ctrl = NULL;

	ecsa_ctrl = _os_mem_alloc(drv_priv, sizeof(struct phl_ecsa_ctrl_t));
	if (ecsa_ctrl == NULL) {
		phl_info->ecsa_ctrl = NULL;
		return RTW_PHL_STATUS_FAILURE;
	}

	phl_info->ecsa_ctrl = ecsa_ctrl;

	/* set default value */
	ecsa_ctrl->state = ECSA_STATE_NONE;
	ecsa_ctrl->phl_com = phl_info->phl_com;
	ecsa_ctrl->role = NULL;
	ecsa_ctrl->expected_tbtt_ms = 0;

	_os_init_timer(drv_priv, &ecsa_ctrl->timer, _phl_ecsa_timer_callback,
		       ecsa_ctrl, "phl_ecsa_timer");

	_os_spinlock_init(drv_priv, &(ecsa_ctrl->lock));

	return RTW_PHL_STATUS_SUCCESS;
}

void
phl_ecsa_ctrl_deinit(
	struct phl_info_t *phl_info
	)
{
	void *drv_priv = phl_to_drvpriv(phl_info);
	struct phl_ecsa_ctrl_t *ecsa_ctrl =
	    (struct phl_ecsa_ctrl_t *)(phl_info->ecsa_ctrl);

	if (ecsa_ctrl == NULL)
		return;
	_os_spinlock_free(drv_priv, &(ecsa_ctrl->lock));
	_os_release_timer(drv_priv, &ecsa_ctrl->timer);
	_os_mem_free(drv_priv, ecsa_ctrl, sizeof(struct phl_ecsa_ctrl_t));

	phl_info->ecsa_ctrl = NULL;
}

enum rtw_phl_status
rtw_phl_ecsa_init_ops(
	void *phl,
	struct rtw_phl_ecsa_ops *ops)
{
	enum rtw_phl_status status = RTW_PHL_STATUS_FAILURE;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl;
	struct phl_ecsa_ctrl_t *ecsa_ctrl =
		(struct phl_ecsa_ctrl_t *)phl_info->ecsa_ctrl;

	if(ecsa_ctrl == NULL)
		goto exit;

	ecsa_ctrl->ops.priv = ops->priv;
	ecsa_ctrl->ops.update_beacon = ops->update_beacon;
	ecsa_ctrl->ops.update_chan_info = ops->update_chan_info;
	ecsa_ctrl->ops.check_ecsa_allow = ops->check_ecsa_allow;
	ecsa_ctrl->ops.ecsa_complete = ops->ecsa_complete;
	ecsa_ctrl->ops.check_tx_resume_allow = ops->check_tx_resume_allow;
	status = RTW_PHL_STATUS_SUCCESS;
exit:
	return status;
}

#ifdef CONFIG_PHL_ECSA_EXTEND_OPTION
void
rtw_phl_ecsa_extend_option_hdlr(
	u32 extend_option,
	struct rtw_phl_ecsa_param *param
)
{
	if ((extend_option & ECSA_EX_OPTION_FORCE_BW20) &&
		(param->new_chan_def.bw != CHANNEL_WIDTH_20)) {
		/* force 20M mode, set attributes accordingly */
		param->new_chan_def.bw = CHANNEL_WIDTH_20;
		param->new_chan_def.center_ch = param->new_chan_def.chan;
		param->new_chan_def.offset = CHAN_OFFSET_NO_EXT;
		param->op_class = rtw_phl_get_operating_class(param->new_chan_def);
	}
}
#endif

bool
rtw_phl_ecsa_check_allow(
	void *phl,
	struct rtw_wifi_role_t *role,
	struct rtw_chan_def chan_def,
	enum phl_ecsa_start_reason reason,
#ifdef CONFIG_PHL_ECSA_EXTEND_OPTION
	u32 *extend_option,
#endif
	u32 *delay_start_ms
)
{
	struct phl_info_t *phl_info = (struct phl_info_t *)phl;
	struct phl_ecsa_ctrl_t *ecsa_ctrl =
		(struct phl_ecsa_ctrl_t *)phl_info->ecsa_ctrl;
	struct rtw_phl_ecsa_ops *ops = &(ecsa_ctrl->ops);
	bool ecsa_allow = false;

	if(ops->check_ecsa_allow)
		ecsa_allow = ops->check_ecsa_allow(ops->priv,
						   role,
						   chan_def,
						   reason,
#ifdef CONFIG_PHL_ECSA_EXTEND_OPTION
						   extend_option,
#endif
						   delay_start_ms);
	return ecsa_allow;
}
#endif /* CONFIG_PHL_ECSA */