/******************************************************************************
 *
 * Copyright(c) 2019 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 _HAL_PS_C_
#include "hal_headers.h"
#ifdef CONFIG_POWER_SAVE
#define case_pwr_state(src) \
	case PS_PWR_STATE_##src: return #src
const char *hal_ps_pwr_state_to_str(u8 pwr_state)
{
	switch (pwr_state) {
	case_pwr_state(ACTIVE);
	case_pwr_state(BAND0_RFON);
	case_pwr_state(BAND1_RFON);
	case_pwr_state(BAND0_RFOFF);
	case_pwr_state(BAND1_RFOFF);
	case_pwr_state(CLK_GATED);
	case_pwr_state(PWR_GATED);
	case_pwr_state(MAX);
	default:
		return "Undefined";
	}
}

#ifndef PS_PROTOCAL_LEAVE_TOLERANCE
#define PS_PROTOCAL_LEAVE_TOLERANCE 50 /* ms */
#endif
#ifndef PS_PROTOCAL_LEAVE_MIN_POLLING_CNT
#define PS_PROTOCAL_LEAVE_MIN_POLLING_CNT 3
#endif
#ifndef PWR_LVL_CHANGE_TOLERANCE
#define PWR_LVL_CHANGE_TOLERANCE 50 /* ms */
#endif
#ifndef PWR_LVL_CHANGE_MIN_POLLING_CNT
#define PWR_LVL_CHANGE_MIN_POLLING_CNT 3
#endif
#ifndef MAX_CHK_PWR_STATE_CHANGE_CNT
#define MAX_CHK_PWR_STATE_CHANGE_CNT 5
#endif

static enum rtw_hal_status
_hal_ps_lps_chk_leave(struct hal_info_t *hal_info, u16 macid)
{
	enum rtw_hal_status status = RTW_HAL_STATUS_SUCCESS;
	u32 mac_sts = 0;
	u32 priv_mac_sts = 0;
	u32 start_time = _os_get_cur_time_ms();
	u32 pass_time = 0;
	u32 polling_cnt = 1;

	do {
		status = rtw_hal_mac_lps_chk_leave(hal_info, macid, &mac_sts);
		pass_time = phl_get_passing_time_ms(start_time);
		if (status == RTW_HAL_STATUS_SUCCESS) {
			PHL_TRACE(COMP_PHL_PS, _PHL_INFO_,
				"[HALPS], %s(): pass time = %d ms, cnt = %u.\n",
				__func__, pass_time, polling_cnt);
			break;
		}

		if (mac_sts != priv_mac_sts) {
			PHL_TRACE(COMP_PHL_PS, _PHL_INFO_,
				"[HALPS], %s(): mac status %u, pass time = %d ms, cnt = %u.\n",
				__func__, mac_sts, pass_time, polling_cnt);
			priv_mac_sts = mac_sts;
		}

		if (pass_time > PS_PROTOCAL_LEAVE_TOLERANCE &&
			polling_cnt >= PS_PROTOCAL_LEAVE_MIN_POLLING_CNT)
			break;

		_os_sleep_us(hal_to_drvpriv(hal_info), 50);
		polling_cnt++;
	} while (1);

#ifdef CONFIG_PHL_PS_FW_DBG
	rtw_hal_fw_dbg_dump(hal_info);
#endif
	if (status != RTW_HAL_STATUS_SUCCESS)
		PHL_TRACE(COMP_PHL_PS, _PHL_ERR_,
			"[HALPS], %s(): polling timeout! (%u ms, %u cnt)\n",
			__func__, pass_time, polling_cnt);

	return status;
}

static enum rtw_hal_status
_hal_ps_lps_cfg(struct hal_info_t *hal_info,
			struct rtw_hal_lps_info *lps_info)
{
	PHL_TRACE(COMP_PHL_PS, _PHL_INFO_,
		"[HALPS], %s(): mode(%d), listen bcn mode(%d), awake interval(%d), smart_ps_mode(%d), bcnnohit(%d) dyntxant_en(%d) 1tx(%d).\n",
		__func__, lps_info->en, lps_info->listen_bcn_mode,
		lps_info->awake_interval, lps_info->smart_ps_mode,
		lps_info->bcnnohit_en, lps_info->dyntxant_en, lps_info->lpstxant);

	return rtw_hal_mac_lps_cfg(hal_info, lps_info);
}

static enum rtw_hal_status
_hal_ps_pwr_state_chk(struct hal_info_t *hal_info, u8 req_pwr_state)
{
	enum rtw_hal_status status = RTW_HAL_STATUS_SUCCESS;
	u32 mac_sts = 0;
	u32 priv_mac_sts = 0;
	u32 start_time = _os_get_cur_time_ms();
	u32 pass_time = 0;
	u32 polling_cnt = 1;

	do {
		status = rtw_hal_mac_chk_pwr_state(hal_info, req_pwr_state, &mac_sts);
		pass_time = phl_get_passing_time_ms(start_time);
		if (status == RTW_HAL_STATUS_SUCCESS) {
			PHL_TRACE(COMP_PHL_PS, _PHL_INFO_,
				"[HALPS], %s(): pass time = %d ms, cnt = %u.\n",
				__func__, pass_time, polling_cnt);
			break;
		}

		if (mac_sts != priv_mac_sts) {
			PHL_TRACE(COMP_PHL_PS, _PHL_INFO_,
				"[HALPS], %s(): mac status %u, pass time = %d ms, cnt = %u.\n",
				__func__, mac_sts, pass_time, polling_cnt);
			priv_mac_sts = mac_sts;
		}

		if (pass_time > PWR_LVL_CHANGE_TOLERANCE
			&& polling_cnt >= PWR_LVL_CHANGE_MIN_POLLING_CNT)
			break;

		_os_sleep_us(hal_to_drvpriv(hal_info), 50);
		polling_cnt++;
	} while (1);

#ifdef CONFIG_PHL_PS_FW_DBG
	rtw_hal_fw_dbg_dump(hal_info);
#endif

	if (status != RTW_HAL_STATUS_SUCCESS)
		PHL_TRACE(COMP_PHL_PS, _PHL_ERR_,
			"[HALPS], %s(): polling timeout! (%u ms, %u cnt)\n",
			__func__, pass_time, polling_cnt);

	return status;
}

static enum rtw_hal_status
_hal_ps_cfg_pwr_state(struct hal_info_t *hal_info, u8 req_pwr_state)
{
#if (HAL_LPS_SKIP_HW_CFG == 0)
	enum rtw_hal_status status = RTW_HAL_STATUS_SUCCESS;
	u8 cnt = 0;

	for (cnt = 0; cnt < MAX_CHK_PWR_STATE_CHANGE_CNT; cnt++) {

		/* rpwm */
		status = rtw_hal_mac_req_pwr_state(hal_info, req_pwr_state);
		if (status != RTW_HAL_STATUS_SUCCESS) {
			PHL_TRACE(COMP_PHL_PS, _PHL_ERR_,
				"[HALPS], %s(): set pwr state %s fail!\n",
				__func__, hal_ps_pwr_state_to_str(req_pwr_state));
			return RTW_HAL_STATUS_FAILURE;
		}

		/* cpwm */
		status = _hal_ps_pwr_state_chk(hal_info, req_pwr_state);
		if (status == RTW_HAL_STATUS_SUCCESS)
			break;
	}

	if (cnt < MAX_CHK_PWR_STATE_CHANGE_CNT) {
		PHL_TRACE(COMP_PHL_PS, _PHL_INFO_,
				"[HALPS], %s(): chk pwr state %s success, cnt %d.\n",
				__func__, hal_ps_pwr_state_to_str(req_pwr_state), cnt);
		return RTW_HAL_STATUS_SUCCESS;
	} else {
		PHL_TRACE(COMP_PHL_PS, _PHL_ERR_,
				"[HALPS], %s(): chk pwr state %s fail!\n",
				__func__, hal_ps_pwr_state_to_str(req_pwr_state));
		return RTW_HAL_STATUS_FAILURE;
	}
#else
	return RTW_HAL_STATUS_SUCCESS;
#endif
}

static enum rtw_hal_status
_hal_ps_cfg_pwr_on(struct rtw_phl_com_t *phl_com,
	struct hal_info_t *hal_info)
{
	struct rtw_phl_evt_ops *ops = &phl_com->evt_ops;
	enum rtw_hal_status status = RTW_HAL_STATUS_SUCCESS;

	if (hal_info->hal_com->is_hal_init) {
		#if (HAL_LPS_SKIP_HW_CFG == 0)
		status = _hal_ps_cfg_pwr_state(hal_info, PS_PWR_STATE_ACTIVE);
		#else
		status = RTW_HAL_STATUS_SUCCESS;
		#endif
	} else {
		#if (HAL_IPS_SKIP_HW_CFG == 0)
		if (ops->set_rf_state(phlcom_to_drvpriv(phl_com), RTW_RF_ON) == true)
			status = RTW_HAL_STATUS_SUCCESS;
		else
			status = RTW_HAL_STATUS_FAILURE;
		#else
			status = RTW_HAL_STATUS_SUCCESS;
		#endif
	}

	return status;
}

static enum rtw_hal_status
_hal_ps_cfg_pwr_off(struct rtw_phl_com_t *phl_com,
	struct hal_info_t *hal_info)
{
	struct rtw_phl_evt_ops *ops = &phl_com->evt_ops;

	#if (HAL_IPS_SKIP_HW_CFG == 0)
	if (ops->set_rf_state(phlcom_to_drvpriv(phl_com), RTW_RF_OFF) == true)
		return RTW_HAL_STATUS_SUCCESS;
	else
		return RTW_HAL_STATUS_FAILURE;
	#else
		return RTW_HAL_STATUS_SUCCESS;
	#endif
}

static enum rtw_hal_status
_hal_ps_pwr_lvl_cfg(struct rtw_phl_com_t *phl_com, struct hal_info_t *hal_info,
			u8 req_pwr_lvl)
{
	enum rtw_hal_status status = RTW_HAL_STATUS_FAILURE;

	switch (req_pwr_lvl) {
	case PS_PWR_LVL_PWROFF:
		status = _hal_ps_cfg_pwr_off(phl_com, hal_info);
		break;
	case PS_PWR_LVL_PWR_GATED:
		status = _hal_ps_cfg_pwr_state(hal_info, PS_PWR_STATE_PWR_GATED);
		break;
	case PS_PWR_LVL_CLK_GATED:
		status = _hal_ps_cfg_pwr_state(hal_info, PS_PWR_STATE_CLK_GATED);
		break;
	case PS_PWR_LVL_RF_OFF:
		status = _hal_ps_cfg_pwr_state(hal_info, PS_PWR_STATE_BAND0_RFOFF);
		break;
	case PS_PWR_LVL_PWRON:
		status = _hal_ps_cfg_pwr_on(phl_com, hal_info);
		break;
	default:
		PHL_TRACE(COMP_PHL_PS, _PHL_WARNING_, "[HALPS], %s(): undefined pwr lvl!\n", __func__);
		break;
	}

	return status;
}

void _hal_ps_proc_hw_rf_state_done(void* priv, struct phl_msg* msg)
{
	struct rtw_phl_com_t *phl_com = (struct rtw_phl_com_t *)priv;

	if (msg->inbuf && msg->inlen) {
		_os_kmem_free(phlcom_to_drvpriv(phl_com), msg->inbuf, msg->inlen);
	}
}

/**
 * configured requested power level
 * return success if configure power level ok
 * @phl_com: see rtw_phl_com_t
 * @hal: see hal_info_t
 * @req_pwr_lvl: target power level to configured
 */
enum rtw_hal_status
rtw_hal_ps_pwr_lvl_cfg(struct rtw_phl_com_t *phl_com, void *hal,
			u8 req_pwr_lvl)
{
	enum rtw_hal_status status = RTW_HAL_STATUS_FAILURE;
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;

	status = _hal_ps_pwr_lvl_cfg(phl_com, hal_info, req_pwr_lvl);
	if (status != RTW_HAL_STATUS_SUCCESS) {
		PHL_TRACE(COMP_PHL_PS, _PHL_ERR_, "[HALPS], %s(): pwr lvl cfg fail!\n", __func__);
	}

	return status;
}

/**
 * configure the legacy power save (protocol)
 * return configure lps fail or not
 * @hal: see hal_info_t
 * @lps: see rtw_hal_lps_info
 */
enum rtw_hal_status
rtw_hal_ps_lps_cfg(void *hal, struct rtw_hal_lps_info *lps_info)
{
	enum rtw_hal_status status = RTW_HAL_STATUS_FAILURE;
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;

	status = _hal_ps_lps_cfg(hal_info, lps_info);

	if (status == RTW_HAL_STATUS_SUCCESS) {
		if (lps_info->en == false)
			status = _hal_ps_lps_chk_leave(hal_info, lps_info->macid);
	}

	return status;
}

enum rtw_hal_status rtw_hal_ps_pwr_req(struct rtw_phl_com_t *phl_com, u8 src, bool pwr_req)
{
	enum rtw_phl_status status = RTW_PHL_STATUS_FAILURE;

	status = phl_ps_hal_pwr_req(phl_com, src, pwr_req);
	if (status != RTW_PHL_STATUS_SUCCESS) {
		PHL_TRACE(COMP_PHL_PS, _PHL_ERR_, "[HALPS], %s(): fail (pwr_req %d).\n", __func__, pwr_req);
		return RTW_HAL_STATUS_FAILURE;
	}

	return RTW_HAL_STATUS_SUCCESS;
}

static enum rtw_hal_status
_hal_ps_ips_chk_leave(struct hal_info_t *hal_info, u16 macid)
{
	enum rtw_hal_status status = RTW_HAL_STATUS_SUCCESS;
	u32 start_time = _os_get_cur_time_ms();
	u32 pass_time = 0;

	do {
		status = rtw_hal_mac_ips_chk_leave(hal_info, macid);
		if (status == RTW_HAL_STATUS_SUCCESS) {
			PHL_TRACE(COMP_PHL_PS, _PHL_INFO_,
				"[HALPS], %s(): pass time = %d ms.\n",
				__func__, pass_time);
			break;
		}

		pass_time = phl_get_passing_time_ms(start_time);
		if (pass_time > PS_PROTOCAL_LEAVE_TOLERANCE)
			break;

		_os_sleep_us(hal_to_drvpriv(hal_info), 50);

	} while (1);

#ifdef CONFIG_PHL_PS_FW_DBG
	rtw_hal_fw_dbg_dump(hal_info);
#endif
	if (status != RTW_HAL_STATUS_SUCCESS)
		PHL_TRACE(COMP_PHL_PS, _PHL_ERR_, "[HALPS], %s(): polling timeout!\n", __func__);

	return status;
}

enum rtw_hal_status rtw_hal_ps_ips_cfg(void *hal,
	struct rtw_hal_ips_info *ips_info)
{
	enum rtw_hal_status hstatus = RTW_HAL_STATUS_SUCCESS;
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;

	hstatus = rtw_hal_mac_ips_cfg(hal, ips_info->macid, ips_info->en);

	if (hstatus == RTW_HAL_STATUS_SUCCESS && !ips_info->en)
		hstatus = _hal_ps_ips_chk_leave(hal_info, ips_info->macid);

	return hstatus;
}

#ifdef CONFIG_HW_RADIO_ONOFF_DETECT
static void
_hal_ps_ntfy_hw_rf_state(struct rtw_phl_com_t *phl_com,
			enum rtw_rf_state rf_state)
{
	struct phl_msg msg = {0};
	struct phl_msg_attribute attr = {0};
	void *d = phlcom_to_drvpriv(phl_com);
	enum rtw_rf_state *rf_ntfy = NULL;

	rf_ntfy = (enum rtw_rf_state *)_os_kmem_alloc(d, sizeof(*rf_ntfy));
	if (rf_ntfy == NULL) {
		PHL_ERR("[HALPS], %s(): alloc for ntfy fail.\n", __func__);
		return;
	}

	*rf_ntfy = rf_state;
	msg.inbuf = (u8 *)rf_ntfy;
	msg.inlen = sizeof(*rf_ntfy);

	SET_MSG_MDL_ID_FIELD(msg.msg_id, PHL_MDL_POWER_MGNT);
	SET_MSG_EVT_ID_FIELD(msg.msg_id, MSG_EVT_HW_RF_CHG);
	attr.completion.completion = _hal_ps_proc_hw_rf_state_done;
	attr.completion.priv = phl_com;
	if (rtw_phl_msg_hub_hal_send(phl_com, &attr, &msg) !=
		RTW_PHL_STATUS_SUCCESS) {
		PHL_ERR("[HALPS], %s(): send msg failed\n", __func__);
		_os_kmem_free(d, rf_ntfy, sizeof(*rf_ntfy));
	}
}

void
rtw_hal_ps_chk_hw_rf_state(struct rtw_phl_com_t *phl_com, void *hal)
{
#ifdef USE_TRUE_PHY
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hstatus = RTW_HAL_STATUS_SUCCESS;
	enum rtw_rf_state rf_state = RTW_RF_ON;
	u8 val = 0;

	hstatus = rtw_hal_mac_get_wl_dis_val(hal_info, &val);
	if (hstatus != RTW_HAL_STATUS_SUCCESS) {
		PHL_TRACE(COMP_PHL_PS, _PHL_ERR_, "[HALPS], %s(): get wl dis val fail, status: %d\n",
		          __func__, hstatus);
		return;
	}

	/* get new rf state */
	if (val == 1) {
		rf_state = RTW_RF_ON;
	} else if (val == 0) {
		rf_state = RTW_RF_OFF;
	} else {
		PHL_TRACE(COMP_PHL_PS, _PHL_INFO_, "[HALPS], %s(): wl_dis is invalid value: %d\n",
		          __func__, val);
		return;
	}

	PHL_TRACE(COMP_PHL_PS, _PHL_INFO_, "[HALPS], %s(): rf state = %d\n",
	          __func__, rf_state);
	_hal_ps_ntfy_hw_rf_state(phl_com, rf_state);
#endif /*USE_TRUE_PHY*/
}
#endif
void rtw_hal_ps_notify_wake(void *hal)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;

	rtw_hal_mac_ps_notify_wake(hal_info);
}

enum rtw_hal_status
rtw_hal_lps_bcn_tracking_cfg(void *hal,
	struct rtw_bcn_tracking_cfg *cfg, struct rtw_bcn_tracking_cfg *cfg_cur)
{
	return rtw_hal_mac_lps_bcn_tracking_cfg(hal, cfg, cfg_cur);
}

void rtw_hal_lps_pvb_wait_rx(void *hal, struct rtw_phl_lps_adv_cfg *cur,
			u16 macid, bool pvb_wait_rx)
{
	struct hal_info_t *hal_i = (struct hal_info_t *)hal;
	enum ps_adv_action act = PS_ADV_NONE;

	act = pvb_wait_rx ? PS_ADV_EN_WAIT_PVB_RX : PS_ADV_DIS_WAIT_PVB_RX;
	rtw_hal_mac_lps_adv_setting(hal_i, cur, (u8)macid, act);
}

void rtw_hal_lps_ext_trx_ntfy(void *hal, struct rtw_phl_lps_adv_cfg *cur,
				u16 macid)
{
	struct hal_info_t *hal_i = (struct hal_info_t *)hal;

	rtw_hal_mac_lps_adv_setting(hal_i, cur, (u8)macid, PS_ADV_EN_TRX_EXT);
}
#endif /* CONFIG_POWER_SAVE */
