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

static void _hal_send_fwdl_hub_msg(struct rtw_phl_com_t *phl_com, u8 dl_ok)
{
	struct phl_msg msg = {0};
	u16 evt_id = (dl_ok) ? MSG_EVT_FWDL_OK : MSG_EVT_FWDL_FAIL;

	msg.inbuf = NULL;
	msg.inlen = 0;
	SET_MSG_MDL_ID_FIELD(msg.msg_id, PHL_MDL_PHY_MGNT);
	SET_MSG_EVT_ID_FIELD(msg.msg_id, evt_id);
	msg.band_idx = HW_BAND_0;
	rtw_phl_msg_hub_hal_send(phl_com, NULL, &msg);
}

static void _hal_fw_log_set(struct rtw_hal_fw_log_cfg *fl_cfg, u8 type, u32 value)
{
	switch(type) {
	case FL_CFG_TYPE_LEVEL:
		fl_cfg->level = value;
		break;

	case FL_CFG_TYPE_OUTPUT:
		fl_cfg->output |= value;
		break;

	case FL_CFG_TYPE_COMP:
		fl_cfg->comp |= value;
		break;

	case FL_CFG_TYPE_COMP_EXT:
		fl_cfg->comp_ext |= value;
		break;

	default:
		break;
	}
}

static void _hal_fw_log_clr(struct rtw_hal_fw_log_cfg *fl_cfg, u8 type, u32 value)
{
	switch(type) {
	case FL_CFG_TYPE_LEVEL:
		break;

	case FL_CFG_TYPE_OUTPUT:
		fl_cfg->output &= (~value);
		break;

	case FL_CFG_TYPE_COMP:
		fl_cfg->comp &= (~value);
		break;

	case FL_CFG_TYPE_COMP_EXT:
		fl_cfg->comp_ext &= (~value);
		break;

	default:
		break;
	}
}

static void _hal_fw_log_info(struct rtw_hal_fw_log_cfg *fl_cfg)
{
	PHL_PRINT("%s: level %d, output 0x%08x, comp 0x%08x, comp ext 0x%08x.\n",
			__func__,
			fl_cfg->level,
			fl_cfg->output,
			fl_cfg->comp,
			fl_cfg->comp_ext);
}

/*
 * Only export for hal_submodule
*/
enum rtw_hal_status rtw_hal_fw_log_cfg(void *halcom, u8 op, u8 type, u32 value)
{
	struct rtw_hal_com_t *hal_com = (struct rtw_hal_com_t *)halcom;
	static struct rtw_hal_fw_log_cfg fl_cfg = {0};
	struct hal_info_t *hal_info = NULL;

	switch(op) {
	case FL_CFG_OP_SET:
		if (type == FL_CFG_TYPE_LEVEL) {
			hal_info = (struct hal_info_t *)hal_com->hal_priv;
			if (hal_info && !IS_FW_LOG_DUMP_ALLOWED(hal_info->phl_com)) {
				value = FL_LV_OFF;
				PHL_INFO("%s(): force log level to (%d) due to IS_FW_LOG_DUMP_ALLOWED(%d)\n",
						__func__,
						value,
						(IS_FW_LOG_DUMP_ALLOWED(hal_info->phl_com) > 0));
			}
		}
		_hal_fw_log_set(&fl_cfg, type, value);
		break;
	case FL_CFG_OP_CLR:
		_hal_fw_log_clr(&fl_cfg, type, value);
		break;
	case FL_CFG_OP_INFO:
		_hal_fw_log_info(&fl_cfg);
		break;
	default:
		break;
	}

	return rtw_hal_mac_fw_log_cfg(hal_com, &fl_cfg);
}

void rtw_hal_fw_recover_log_cfg(void *hal)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	struct rtw_hal_com_t *hal_com = hal_info->hal_com;

	rtw_hal_fw_log_cfg(hal_com, FL_CFG_OP_INFO, 0, 0);
}

void hal_fw_en_basic_log(struct rtw_hal_com_t *hal_com,
                         struct mac_ax_fw_log* fw_log_info)
{
	rtw_hal_fw_log_cfg(hal_com, FL_CFG_OP_SET, FL_CFG_TYPE_LEVEL,
				fw_log_info->level);
	rtw_hal_fw_log_cfg(hal_com, FL_CFG_OP_SET, FL_CFG_TYPE_OUTPUT,
				fw_log_info->output);
	rtw_hal_fw_log_cfg(hal_com, FL_CFG_OP_SET, FL_CFG_TYPE_COMP,
				fw_log_info->comp);
	rtw_hal_fw_log_cfg(hal_com, FL_CFG_OP_SET, FL_CFG_TYPE_COMP_EXT,
				fw_log_info->comp_ext);
}

enum rtw_hal_status rtw_hal_en_fw_log_comp(void *hal, u32 comp, bool en)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status status = RTW_HAL_STATUS_FAILURE;

	if (en)
		status = rtw_hal_fw_log_cfg(hal_info->hal_com, FL_CFG_OP_SET,
						FL_CFG_TYPE_COMP, comp);
	else
		status = rtw_hal_fw_log_cfg(hal_info->hal_com, FL_CFG_OP_CLR,
						FL_CFG_TYPE_COMP, comp);
	PHL_INFO("rtw_hal_en_fw_log_comp(): status(%d), en(%d)\n", status, en);
	return status;
}

enum rtw_hal_status rtw_hal_set_fw_log_lvl(void *hal, u32 lvl)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status status = RTW_HAL_STATUS_FAILURE;

	status = rtw_hal_fw_log_cfg(hal_info->hal_com, FL_CFG_OP_SET,
				    FL_CFG_TYPE_LEVEL, lvl);

	PHL_INFO("%s(): status(%d), level(%d)\n",
			 __func__, status, lvl);
	return status;
}

enum rtw_hal_status
rtw_hal_download_fw(struct rtw_phl_com_t *phl_com, void *hal)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hal_status = RTW_HAL_STATUS_FAILURE;
	struct rtw_fw_info_t *fw_info = &phl_com->fw_info;

	FUNCIN_WSTS(hal_status);

	if (!fw_info->fw_en)
		return hal_status;

	if (fw_info->dlrom_en) {
		hal_status = rtw_hal_mac_romdl(hal_info, fw_info->rom_buff,
									   fw_info->rom_size);

		if (hal_status != RTW_HAL_STATUS_SUCCESS)
			return hal_status;
	}

	hal_status = rtw_hal_mac_disable_cpu(hal_info);
	if (hal_status != RTW_HAL_STATUS_SUCCESS) {
		PHL_ERR("disable cpu fail!\n");
		return hal_status;
	}

	hal_status = rtw_hal_mac_enable_cpu(hal_info, 0, true);
	if (hal_status != RTW_HAL_STATUS_SUCCESS) {
		PHL_ERR("enable cpu fail!\n");
		return hal_status;
	}

	if (fw_info->dlram_en) {
		hal_status = rtw_hal_mac_fwdl(hal_info, fw_info->ram_buff,
									  fw_info->ram_size);
	}

	_hal_send_fwdl_hub_msg(phl_com, (!hal_status) ? true : false);

	FUNCOUT_WSTS(hal_status);

	return hal_status;
}

enum rtw_hal_status
rtw_hal_redownload_fw(struct rtw_phl_com_t *phl_com, void *hal)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hal_status = RTW_HAL_STATUS_FAILURE;
	struct rtw_fw_info_t *fw_info = &phl_com->fw_info;
	u32 start_t = 0;

	FUNCIN_WSTS(hal_status);

	/* Disable/Enable CPU is necessary first when FWDL from files */
	if(fw_info->dlram_en && fw_info->fw_src == RTW_FW_SRC_EXTNAL) {

		hal_status = rtw_hal_mac_disable_cpu(hal_info);
		if (hal_status != RTW_HAL_STATUS_SUCCESS) {
			PHL_ERR("disable cpu fail!\n");
			return hal_status;
		}

		hal_status = rtw_hal_mac_enable_cpu(hal_info, 0, true);
		if (hal_status != RTW_HAL_STATUS_SUCCESS) {
			PHL_ERR("enable cpu fail!\n");
			return hal_status;
		}

		hal_status = rtw_hal_mac_fwdl(hal_info, fw_info->ram_buff,
					      fw_info->ram_size);
		if (hal_status != RTW_HAL_STATUS_SUCCESS) {
			PHL_ERR("rtw_hal_mac_fwdl fail!\n");
			return hal_status;
		}
	}

	if (fw_info->dlram_en && fw_info->fw_src != RTW_FW_SRC_EXTNAL) {
		start_t = _os_get_cur_time_us();
		hal_status = rtw_hal_mac_enable_fw(hal_info, fw_info->fw_type);
		PHL_INFO("%s : fw_type: %d, FWDL Total Cost Time: %u us\n",
			 __func__, fw_info->fw_type,
			 phl_get_passing_time_us(start_t));
		if (hal_status != RTW_HAL_STATUS_SUCCESS) {
			PHL_ERR("rtw_hal_mac_enable_fw fail!\n");
			return hal_status;
		}
	}

	if (rtw_hal_mac_get_wcpu_cap(phl_com, hal_info) != RTW_HAL_STATUS_SUCCESS) {
		PHL_ERR("%s : can't get fw capability.\n", __func__);
		return RTW_HAL_STATUS_FAILURE;
	}

#ifdef CONFIG_POWER_SAVE
	if (fw_info->fw_type == RTW_FW_WOWLAN)
		rtw_hal_ps_fw_cap_decision(phl_com, true);
	else
		rtw_hal_ps_fw_cap_decision(phl_com, false);
#endif /* CONFIG_POWER_SAVE */

	rtw_hal_rf_config_radio_to_fw(hal_info);

	/**
	 * Should reset packet offload related information
	 * 1. phl packet offload module
	 * 2. halmac packet offload module
	 */
	rtw_phl_pkt_ofld_del_all_entry_req(phl_com);
	if (RTW_HAL_STATUS_SUCCESS != rtw_hal_mac_reset_pkt_ofld_state(hal_info))
		PHL_ERR("%s: reset pkt ofld state fail!\n", __func__);

	_hal_send_fwdl_hub_msg(phl_com, (!hal_status) ? true : false);
	#ifdef CONFIG_BTCOEX
	rtw_hal_btc_redownload_fw_ntfy(hal_info);
	#endif /* CONFIG_BTCOEX */
	FUNCOUT_WSTS(hal_status);

	return hal_status;
}

enum rtw_hal_status
rtw_hal_pg_redownload_fw(struct rtw_phl_com_t *phl_com, void *hal)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;
	enum rtw_hal_status hal_status = RTW_HAL_STATUS_SUCCESS;
	struct rtw_fw_info_t *fw_info = &phl_com->fw_info;
	u8 *fw_buff = NULL;
	u32 fw_size = 0;

	FUNCIN_WSTS(hal_status);

	if (!rtw_hal_mac_fwredl_needed(hal_info))
		goto out;

	if (fw_info->fw_src == RTW_FW_SRC_EXTNAL) {
		fw_buff = fw_info->ram_buff;
		fw_size = fw_info->ram_size;
	} else {
		hal_status = rtw_hal_mac_query_fw_buff(hal_info,
						fw_info->fw_type,
						&fw_buff, &fw_size);
		if (hal_status != RTW_HAL_STATUS_SUCCESS)
			return RTW_HAL_STATUS_FAILURE;
	}

	hal_status = rtw_hal_mac_fwredl(hal_info, fw_buff, fw_size);

out:
	FUNCOUT_WSTS(hal_status);

	return hal_status;
}

void rtw_hal_fw_dbg_dump(void *hal)
{
	struct hal_info_t *hal_info = (struct hal_info_t *)hal;

	rtw_hal_mac_fw_dbg_dump(hal_info);
}

enum rtw_fw_status rtw_hal_get_fw_status(void *h)
{
	struct hal_info_t *hal = (struct hal_info_t *)h;

	return rtw_hal_mac_get_fw_status(hal);
}
