/******************************************************************************
 *
 * 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_WOW_C_
#include "hal_headers.h"

#ifdef CONFIG_WOWLAN

#define case_rsn(rsn) \
	case MAC_AX_WOW_##rsn: return RTW_WOW_RSN_##rsn

u8 _trans_wake_rsn(u8 mac_rsn)
{
	switch (mac_rsn) {
	case_rsn(RX_PAIRWISEKEY);
	case_rsn(RX_GTK);
	case_rsn(RX_FOURWAY_HANDSHAKE);
	case_rsn(RX_DISASSOC);
	case_rsn(RX_DEAUTH);
	case_rsn(RX_ARP_REQUEST);
	case_rsn(RX_NS);
	case_rsn(RX_EAPREQ_IDENTIFY);
	case_rsn(FW_DECISION_DISCONNECT);
	case_rsn(RX_MAGIC_PKT);
	case_rsn(RX_UNICAST_PKT);
	case_rsn(RX_PATTERN_PKT);
	case_rsn(RTD3_SSID_MATCH);
	case_rsn(RX_DATA_PKT);
	case_rsn(RX_SSDP_MATCH);
	case_rsn(RX_WSD_MATCH);
	case_rsn(RX_SLP_MATCH);
	case_rsn(RX_LLTD_MATCH);
	case_rsn(RX_MDNS_MATCH);
	case_rsn(RX_REALWOW_V2_WAKEUP_PKT);
	case_rsn(RX_REALWOW_V2_ACK_LOST);
	case_rsn(RX_REALWOW_V2_TX_KAPKT);
	case_rsn(ENABLE_FAIL_DMA_IDLE);
	case_rsn(ENABLE_FAIL_DMA_PAUSE);
	case_rsn(RTIME_FAIL_DMA_IDLE);
	case_rsn(RTIME_FAIL_DMA_PAUSE);
	case_rsn(RX_SNMP_MISMATCHED_PKT);
	case_rsn(RX_DESIGNATED_MAC_PKT);
	case_rsn(NLO_SSID_MACH);
	case_rsn(AP_OFFLOAD_WAKEUP);
	case_rsn(DMAC_ERROR_OCCURRED);
	case_rsn(EXCEPTION_OCCURRED);
	case_rsn(L0_TO_L1_ERROR_OCCURRED);
	case_rsn(ASSERT_OCCURRED);
	case_rsn(L2_ERROR_OCCURRED);
	case_rsn(WDT_TIMEOUT_WAKE);
	case_rsn(RX_ACTION);
	case_rsn(CLK_32K_UNLOCK);
	case_rsn(CLK_32K_LOCK);
	default:
		return RTW_WOW_RSN_MAX;
	}
}

#define MAX_POLLING_WOW_FW_STS_CNT 1000 /* 50ms */

static enum rtw_hal_status _hal_check_wow_fw_ready(void *hal, u8 func_en)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hstats = RTW_HAL_STATUS_FAILURE;
	void* drv_priv = hal_to_drvpriv(hal_info);
	u8 fw_status = 0;
	u32 poll_cnt = 0;

	PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] %s : start polling... (func_en %d)\n", __func__, func_en);

	/* polling fw status */
	while (1) {

		if (poll_cnt >= MAX_POLLING_WOW_FW_STS_CNT) {
			PHL_ERR("%s polling fw status timeout !!!\n", __func__);
			hstats = RTW_HAL_STATUS_FAILURE;
			break;
		}

		hstats = rtw_hal_mac_get_wow_fw_status(hal_info, &fw_status,
							func_en);

		if (RTW_HAL_STATUS_SUCCESS != hstats) {

			PHL_ERR("%s : status %u\n", __func__, fw_status);
			break;
		}

		if (fw_status) {
			hstats = RTW_HAL_STATUS_SUCCESS;

			PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_,
				"%s : polling count %u\n", __func__, poll_cnt);
			break;
		} else {

			_os_delay_us(drv_priv, 50);

		}
		poll_cnt++;

	}

	PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] %s : finish polling... (func_en %d)\n", __func__, func_en);

	return hstats;
}

enum rtw_hal_status rtw_hal_get_wake_rsn(void *hal, enum rtw_wow_wake_reason *wake_rsn, u8 *reset)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hstats = RTW_HAL_STATUS_FAILURE;
	u8 mac_rsn = 0;

	hstats = rtw_hal_mac_get_wake_rsn(hal_info, &mac_rsn, reset);
	PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "%s : wake rsn from mac in hex 0x%x (reset %d).\n",
				__func__, mac_rsn, *reset);

	if (RTW_HAL_STATUS_SUCCESS != hstats) {
		PHL_ERR("%s : rtw_hal_mac_get_wake_rsn failed.\n", __func__);
	} else {
		*wake_rsn = (mac_rsn == 0) ? RTW_WOW_RSN_UNKNOWN : _trans_wake_rsn(mac_rsn) ;
		PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "%s : wake reason %u.\n", __func__, *wake_rsn);
	}

	return hstats;
}

enum rtw_hal_status rtw_hal_cfg_wow_sleep(void *hal, u8 sleep)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hstats = RTW_HAL_STATUS_FAILURE;

	hstats = rtw_hal_mac_cfg_wow_sleep(hal_info, sleep);

	if (RTW_HAL_STATUS_SUCCESS != hstats)
		PHL_ERR("%s : sleep %u\n", __func__, sleep);

	return hstats;
}

enum rtw_hal_status rtw_hal_get_wow_aoac_rpt(void *hal, struct rtw_aoac_report *aoac_info, u8 rx_ready)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hstatus = RTW_HAL_STATUS_FAILURE;

	FUNCIN();

	/* get aoac report */
	hstatus = rtw_hal_mac_get_aoac_rpt(hal_info, aoac_info, rx_ready);

	if (RTW_HAL_STATUS_SUCCESS != hstatus)
		PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] %s(): failed with status(%u)\n", __func__, hstatus);

	FUNCOUT();

	return hstatus;
}


enum rtw_hal_status rtw_hal_reset_pkt_ofld_state(void *hal)
{

	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hstats = RTW_HAL_STATUS_FAILURE;

	hstats = rtw_hal_mac_reset_pkt_ofld_state(hal_info);

	if (RTW_HAL_STATUS_SUCCESS != hstats)
		PHL_ERR("%s : failed \n", __func__);

	return hstats;
}

#ifdef CONFIG_PCI_HCI
enum rtw_hal_status rtw_hal_wow_cfg_txdma(void *hal, u8 en)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	struct hal_trx_ops *trx_ops = hal_info->trx_ops;

	PHL_TRACE(COMP_PHL_DBG, _PHL_DEBUG_, "%s : enable %d.\n", __func__, en);

	trx_ops->cfg_wow_txdma(hal_info, en);

	return RTW_HAL_STATUS_SUCCESS;
}
#endif
enum rtw_hal_status rtw_hal_wow_init(struct rtw_phl_com_t *phl_com, void *hal,
									 struct rtw_phl_stainfo_t *sta)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hal_status = RTW_HAL_STATUS_FAILURE;
	struct hal_ops_t *hal_ops = hal_get_ops(hal_info);

	FUNCIN_WSTS(hal_status);

	/* download wowlan fw and do related tasks needed after redownload fw */
	hal_status = hal_ops->hal_wow_init(phl_com, hal_info, sta);
	if (hal_status != RTW_HAL_STATUS_SUCCESS)
		return hal_status;

	PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "%s successfully done.\n", __func__);

	FUNCOUT_WSTS(hal_status);

	return hal_status;
}

enum rtw_hal_status rtw_hal_wow_deinit(struct rtw_phl_com_t *phl_com, void *hal,
									   struct rtw_phl_stainfo_t *sta)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hal_status = RTW_HAL_STATUS_FAILURE;
	struct hal_ops_t *hal_ops = hal_get_ops(hal_info);

	FUNCIN_WSTS(hal_status);

	/* download wowlan fw and do related tasks needed after redownload fw */
	hal_status = hal_ops->hal_wow_deinit(phl_com, hal_info, sta);
	if (hal_status != RTW_HAL_STATUS_SUCCESS)
		return hal_status;

	PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "%s successfully done.\n", __func__);

	FUNCOUT_WSTS(hal_status);

	return hal_status;
}

enum rtw_hal_status rtw_hal_wow_func_en(struct rtw_phl_com_t *phl_com, void *hal, u16 macid, struct rtw_hal_wow_cfg *cfg)
{

	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hstatus = RTW_HAL_STATUS_FAILURE;

	FUNCIN();

	do {
		/* config wow cam : pattern match */
#ifndef RTW_WKARD_WOW_SKIP_WOW_CAM_CONFIG
		hstatus = rtw_hal_mac_cfg_wow_cam(hal_info, macid, true, cfg->pattern_match_info);
		if (RTW_HAL_STATUS_SUCCESS != hstatus)
			break;
#endif
		/* gtk offload */
		hstatus = rtw_hal_mac_cfg_gtk_ofld(hal_info, macid, true, cfg->gtk_ofld_cfg);
		if (RTW_HAL_STATUS_SUCCESS != hstatus)
			break;
		/* arp offload */
		hstatus = rtw_hal_mac_cfg_arp_ofld(hal_info, macid, true, cfg->arp_ofld_cfg);
		if (RTW_HAL_STATUS_SUCCESS != hstatus)
			break;
		/* ndp offload */
		hstatus = rtw_hal_mac_cfg_ndp_ofld(hal_info, macid, true, cfg->ndp_ofld_cfg);
		if (RTW_HAL_STATUS_SUCCESS != hstatus)
			break;
		/* config keep alive */
		hstatus = rtw_hal_mac_cfg_keep_alive(hal_info, macid, true, cfg->keep_alive_cfg);
		if (RTW_HAL_STATUS_SUCCESS != hstatus)
			break;
		/* config disconnect detection */
		hstatus = rtw_hal_mac_cfg_disc_dec(hal_info, macid, true, cfg->disc_det_cfg);
		if (RTW_HAL_STATUS_SUCCESS != hstatus)
			break;
		/* realwow offload */
		hstatus = rtw_hal_mac_cfg_realwow(hal_info, macid, true, cfg->realwow_cfg);
		if (RTW_HAL_STATUS_SUCCESS != hstatus)
			break;
		/* config gpio */
		hstatus = rtw_hal_mac_cfg_dev2hst_gpio(hal_info, true, cfg->wow_gpio);
		if (RTW_HAL_STATUS_SUCCESS != hstatus)
			break;
	} while(0);

	PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] %s status(%u).\n", __func__, hstatus);

	FUNCOUT();

	return hstatus;
}

enum rtw_hal_status rtw_hal_wow_func_dis(struct rtw_phl_com_t *phl_com, void *hal, u16 macid)
{

	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hstatus = RTW_HAL_STATUS_FAILURE;

	FUNCIN();

	/* cancel pattern match */
#ifndef RTW_WKARD_WOW_SKIP_WOW_CAM_CONFIG
	hstatus = rtw_hal_mac_cfg_wow_cam(hal_info, macid, false, NULL);
	if (RTW_HAL_STATUS_SUCCESS != hstatus)
		PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] rtw_hal_mac_cfg_wow_cam failed \n");
#endif
	/* cancel gtk offload */
	hstatus = rtw_hal_mac_cfg_gtk_ofld(hal_info, macid, false, NULL);
	if (RTW_HAL_STATUS_SUCCESS != hstatus)
		PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] rtw_hal_mac_cfg_gtk_ofld failed \n");
	/* cancel arp offload */
	hstatus = rtw_hal_mac_cfg_arp_ofld(hal_info, macid, false, NULL);
	if (RTW_HAL_STATUS_SUCCESS != hstatus)
		PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] rtw_hal_mac_cfg_arp_ofld failed \n");
	/* cancel ndp offload */
	hstatus = rtw_hal_mac_cfg_ndp_ofld(hal_info, macid, false, NULL);
	if (RTW_HAL_STATUS_SUCCESS != hstatus)
		PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] rtw_hal_mac_cfg_ndp_ofld failed \n");
	/* disable keep alive */
	hstatus = rtw_hal_mac_cfg_keep_alive(hal_info, macid, false, NULL);
	if (RTW_HAL_STATUS_SUCCESS != hstatus)
		PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] rtw_hal_mac_cfg_keep_alive failed \n");
	/* disable disconect detection */
	hstatus = rtw_hal_mac_cfg_disc_dec(hal_info, macid, false, NULL);
	if (RTW_HAL_STATUS_SUCCESS != hstatus)
		PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] rtw_hal_mac_cfg_disc_dec failed \n");
	/* realwow offload */
	hstatus = rtw_hal_mac_cfg_realwow(hal_info, macid, false, NULL);
	if (RTW_HAL_STATUS_SUCCESS != hstatus)
		PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] rtw_hal_mac_cfg_realwow_ofld failed \n");
	/* config gpio */
	hstatus = rtw_hal_mac_cfg_dev2hst_gpio(hal_info, false, NULL);
	if (RTW_HAL_STATUS_SUCCESS != hstatus)
		PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] rtw_hal_mac_cfg_dev2hst_gpio failed \n");

	PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] %s status(%u).\n", __func__, hstatus);

	FUNCOUT();

	return RTW_HAL_STATUS_SUCCESS;
}

enum rtw_hal_status rtw_hal_wow_func_start(struct rtw_phl_com_t *phl_com, void *hal, u16 macid, struct rtw_hal_wow_cfg *cfg)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hstatus = RTW_HAL_STATUS_FAILURE;

	do {
		hstatus = rtw_hal_mac_cfg_wow_wake(hal_info, macid, true, cfg->wow_wake_cfg);
		if (RTW_HAL_STATUS_SUCCESS != hstatus)
			break;
		/* poll fw status */
		hstatus = _hal_check_wow_fw_ready(hal_info, 1);
		if (RTW_HAL_STATUS_SUCCESS != hstatus)
			break;
	} while(0);

	PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] %s status(%u).\n", __func__, hstatus);

	return hstatus;
}

enum rtw_hal_status rtw_hal_wow_func_stop(struct rtw_phl_com_t *phl_com, void *hal, u16 macid)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hstatus = RTW_HAL_STATUS_FAILURE;

	/* config wow ctrl */
	hstatus = rtw_hal_mac_cfg_wow_wake(hal_info, macid, false, NULL);
	if (RTW_HAL_STATUS_SUCCESS != hstatus)
		PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] rtw_hal_mac_cfg_wow_wake failed \n");

	hstatus = _hal_check_wow_fw_ready(hal_info, 0);
	if (RTW_HAL_STATUS_SUCCESS != hstatus)
		PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] _hal_poll_wow_fw_status failed \n");

	PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] %s status(%u).\n", __func__, hstatus);

	return hstatus;
}

enum rtw_hal_status rtw_hal_set_wowlan(struct rtw_phl_com_t *phl_com, void *hal, u8 enter)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hal_status = RTW_HAL_STATUS_FAILURE;
	hal_status = rtw_hal_mac_set_wowlan(hal_info, enter);

	PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "%s : status(%u).\n", __func__, hal_status);

	return hal_status;
}

static enum rtw_hal_status _wow_chk_txq_empty(struct hal_info_t *hal_info, u8 *empty)
{
	enum rtw_hal_status hal_status = RTW_HAL_STATUS_FAILURE;

	hal_status = rtw_hal_mac_wow_chk_txq_empty(hal_info, empty);

	PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "%s : status(%u).\n", __func__, hal_status);

	return hal_status;
}

static enum rtw_hal_status _wow_wde_drop(struct hal_info_t *hal_info, u8 band)
{
	enum rtw_hal_status hal_status = RTW_HAL_STATUS_FAILURE;

	hal_status = rtw_hal_mac_wow_wde_drop(hal_info, band);

	PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "%s : status(%u).\n", __func__, hal_status);

	return hal_status;
}

#define MAX_WOW_DROP_HWTX_TRYCNT 3
enum rtw_hal_status rtw_hal_wow_drop_tx(void *hal, u8 band)
{
	enum rtw_hal_status hal_status = RTW_HAL_STATUS_FAILURE;
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	u8 empty = 0;
	u8 i = 0;

	for (i = 0; i < MAX_WOW_DROP_HWTX_TRYCNT; i++) {

		PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] %s : chk/drop for the %d time\n", __func__, i + 1);

		if (RTW_HAL_STATUS_SUCCESS != _wow_chk_txq_empty(hal_info, &empty))
			PHL_WARN("[wow] _wow_chk_txq_empty failed.\n");

		/* force drop wde if txq is not empty */
		if (!empty)
			_wow_wde_drop(hal_info, band);
		else
			break;
	}

	if (!empty) {
		PHL_WARN("[wow] %s : chk/drop fail!\n", __func__);
	} else {
		PHL_TRACE(COMP_PHL_WOW, _PHL_INFO_, "[wow] %s : chk/drop ok.\n", __func__);
		hal_status = RTW_HAL_STATUS_SUCCESS;
	}

	return hal_status;
}

#endif /* CONFIG_WOWLAN */
