/******************************************************************************
 *
 * 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";
	}
}

#define LPS_PROTOCAL_LEAVE_TOLERANCE 50 /* ms */
#define PWR_LVL_CHANGE_TOLERANCE 50 /* ms */
#define MAX_CHK_PWR_STATE_CHANGE_CNT 5

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;

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

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

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

		_os_sleep_us(hal_to_drvpriv(hal_info), 50);

	} while (1);

	rtw_hal_fw_dbg_dump(hal_info, false);

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

	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).\n",
		__func__, lps_info->lps_en, lps_info->listen_bcn_mode,
		lps_info->awake_interval, lps_info->smart_ps_mode);

	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;

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

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

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

		_os_sleep_us(hal_to_drvpriv(hal_info), 50);

	} while (1);

	rtw_hal_fw_dbg_dump(hal_info, true);

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

	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;
}

/**
 * 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->lps_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;
}
#endif /* CONFIG_POWER_SAVE */
