/******************************************************************************
 *
 * Copyright(c) 2007 - 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.
 *
 *****************************************************************************/

#include <drv_types.h>
#include <hal_data.h>
#ifdef CONFIG_RTW_80211K
#include "rtw_rm_fsm.h"
#include "rtw_rm_util.h"

/* 802.11-2012 Table E-1 Operationg classes in United States */
static RT_OPERATING_CLASS RTW_OP_CLASS_US[] = {
	/* 0, OP_CLASS_NULL */	{  0,  0, {}},
	/* 1, OP_CLASS_1 */	{115,  4, {36, 40, 44, 48}},
	/* 2, OP_CLASS_2 */	{118,  4, {52, 56, 60, 64}},
	/* 3, OP_CLASS_3 */	{124,  4, {149, 153, 157, 161}},
	/* 4, OP_CLASS_4 */	{121, 11, {100, 104, 108, 112, 116, 120, 124,
						128, 132, 136, 140}},
	/* 5, OP_CLASS_5 */	{125,  5, {149, 153, 157, 161, 165}},
	/* 6, OP_CLASS_12 */	{ 81, 11, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}}
};

u8 rm_get_ch_set(
	struct rtw_ieee80211_channel *pch_set, u8 op_class, u8 ch_num)
{
	int i,j,sz;
	u8 ch_amount = 0;


	sz = sizeof(RTW_OP_CLASS_US)/sizeof(struct _RT_OPERATING_CLASS);

	if (ch_num != 0) {
		pch_set[0].hw_value = ch_num;
		ch_amount = 1;
		RTW_INFO("RM: meas_ch->hw_value = %u\n", pch_set->hw_value);
		goto done;
	}

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

		if (RTW_OP_CLASS_US[i].global_op_class == op_class) {

			for (j = 0; j < RTW_OP_CLASS_US[i].Len; j++) {
				pch_set[j].hw_value =
					RTW_OP_CLASS_US[i].Channel[j];
				RTW_INFO("RM: meas_ch[%d].hw_value = %u\n",
					j, pch_set[j].hw_value);
			}
			ch_amount = RTW_OP_CLASS_US[i].Len;
			break;
		}
	}
done:
	return ch_amount;
}

u8 rm_get_ch_set_from_bcn_req_opt(
	struct rtw_ieee80211_channel *pch_set, struct bcn_req_opt *opt)
{
	int i,j,k,sz;
	struct _RT_OPERATING_CLASS *ap_ch_rpt;
	u8 ch_amount = 0;

	k = 0;
	for (i = 0; i < opt->ap_ch_rpt_num; i++) {
		if (opt->ap_ch_rpt[i] == NULL)
			break;
		ap_ch_rpt = opt->ap_ch_rpt[i];
		for (j = 0; j < ap_ch_rpt->Len; j++) {
			pch_set[k].hw_value =
				ap_ch_rpt->Channel[j];
			RTW_INFO("RM: meas_ch[%d].hw_value = %u\n",
				j, pch_set[k].hw_value);
			k++;
		}
	}
	return k;
}

u8 rm_get_oper_class_via_ch(u8 ch)
{
	int i,j,sz;


	sz = sizeof(RTW_OP_CLASS_US)/sizeof(struct _RT_OPERATING_CLASS);

	for (i = 0; i < sz; i++) {
		for (j = 0; j < RTW_OP_CLASS_US[i].Len; j++) {
			if ( ch == RTW_OP_CLASS_US[i].Channel[j]) {
				RTW_INFO("RM: ch %u in oper_calss %u\n",
					ch, RTW_OP_CLASS_US[i].global_op_class);
				return RTW_OP_CLASS_US[i].global_op_class;
				break;
			}
		}
	}
	return 0;
}

int is_wildcard_bssid(u8 *bssid)
{
	int i;
	u8 val8 = 0xff;


	for (i=0;i<6;i++)
		val8 &= bssid[i];

	if (val8 == 0xff)
		return _SUCCESS;
	return _FALSE;
}

u8 translate_dbm_to_rcpi(s8 SignalPower)
{
	/* RCPI = Int{(Power in dBm + 110)*2} for 0dBm > Power > -110dBm
	 *    0	: power <= -110.0 dBm
	 *    1	: power =  -109.5 dBm
	 *    2	: power =  -109.0 dBm
	 */
	return (SignalPower + 110)*2;
}

u8 translate_percentage_to_rcpi(u32 SignalStrengthIndex)
{
	/* Translate to dBm (x=y-100) */
	return translate_dbm_to_rcpi(SignalStrengthIndex - 100);
}

u8 rm_get_bcn_rcpi(struct rm_obj *prm, struct wlan_network *pnetwork)
{
	return translate_percentage_to_rcpi(
		pnetwork->network.PhyInfo.SignalStrength);
}

u8 rm_get_frame_rsni(struct rm_obj *prm, union recv_frame *pframe)
{
	int i;
	u8 val8, snr, rx_num;
	struct hal_spec_t *hal_spec = GET_HAL_SPEC(prm->psta->padapter);

	if (IS_CCK_RATE((hw_rate_to_m_rate(pframe->u.hdr.attrib.data_rate))))
		val8 = 255;
	else {
		snr = rx_num = 0;
		for (i = 0; i < hal_spec->rf_reg_path_num; i++) {
			if (GET_HAL_RX_PATH_BMP(prm->psta->padapter) & BIT(i)) {
				snr += pframe->u.hdr.attrib.phy_info.rx_snr[i];
				rx_num++;
			}
		}
		snr = snr / rx_num;
		val8 = (u8)(snr + 10)*2;
	}
	return val8;
}

u8 rm_get_bcn_rsni(struct rm_obj *prm, struct wlan_network *pnetwork)
{
	int i;
	u8 val8, snr, rx_num;
	struct hal_spec_t *hal_spec = GET_HAL_SPEC(prm->psta->padapter);

	if (pnetwork->network.PhyInfo.is_cck_rate) {
		/* current HW doesn't have CCK RSNI */
		/* 255 indicates RSNI is unavailable */
		val8 = 255;
	} else {
		snr = rx_num = 0;
		for (i = 0; i < hal_spec->rf_reg_path_num; i++) {
			if (GET_HAL_RX_PATH_BMP(prm->psta->padapter) & BIT(i)) {
				snr += pnetwork->network.PhyInfo.rx_snr[i];
				rx_num++;
			}
		}
		snr = snr / rx_num;
		val8 = (u8)(snr + 10)*2;
	}
	return val8;
}

/* output: pwr (unit dBm) */
int rm_get_tx_power(PADAPTER adapter, enum rf_path path, enum MGN_RATE rate, s8 *pwr)
{
	struct hal_spec_t *hal_spec = GET_HAL_SPEC(adapter);
	HAL_DATA_TYPE *hal_data = GET_HAL_DATA(adapter);
	int tx_num, band, bw, ch, n, rs;
	u8 base;
	s8 limt_offset = 127; /* max value of s8 */
	s8 rate_offset;
	s8 powr_offset;
	int rate_pos;


	band = hal_data->current_band_type;
	bw = hal_data->current_channel_bw;
	ch = hal_data->current_channel;

	if (!HAL_SPEC_CHK_RF_PATH(hal_spec, band, path))
		return -1;

	if (HAL_IsLegalChannel(adapter, ch) == _FALSE) {
		RTW_INFO("Illegal channel!!\n");
		return -2;
	}

	*pwr = phy_get_tx_power_final_absolute_value(adapter, path, rate, bw, ch);

	return 0;
}

int rm_get_rx_sensitivity(PADAPTER adapter, enum channel_width bw, enum MGN_RATE rate, s8 *pwr)
{
	s8 rx_sensitivity = -110;

	switch(rate) {
	case MGN_1M:
		rx_sensitivity= -101;
		break;
	case MGN_2M:
		rx_sensitivity= -98;
		break;
	case MGN_5_5M:
		rx_sensitivity= -92;
		break;
	case MGN_11M:
		rx_sensitivity= -89;
		break;
	case MGN_6M:
	case MGN_9M:
	case MGN_12M:
		rx_sensitivity = -92;
		break;
	case MGN_18M:
		rx_sensitivity = -90;
		break;
	case MGN_24M:
		rx_sensitivity = -88;
		break;
	case MGN_36M:
		rx_sensitivity = -84;
		break;
	case MGN_48M:
		rx_sensitivity = -79;
		break;
	case MGN_54M:
		rx_sensitivity = -78;
		break;

	case MGN_MCS0:
	case MGN_MCS8:
	case MGN_MCS16:
	case MGN_MCS24:
	case MGN_VHT1SS_MCS0:
	case MGN_VHT2SS_MCS0:
	case MGN_VHT3SS_MCS0:
	case MGN_VHT4SS_MCS0:
		/* BW20 BPSK 1/2 */
		rx_sensitivity = -82;
		break;

	case MGN_MCS1:
	case MGN_MCS9:
	case MGN_MCS17:
	case MGN_MCS25:
	case MGN_VHT1SS_MCS1:
	case MGN_VHT2SS_MCS1:
	case MGN_VHT3SS_MCS1:
	case MGN_VHT4SS_MCS1:
		/* BW20 QPSK 1/2 */
		rx_sensitivity = -79;
		break;

	case MGN_MCS2:
	case MGN_MCS10:
	case MGN_MCS18:
	case MGN_MCS26:
	case MGN_VHT1SS_MCS2:
	case MGN_VHT2SS_MCS2:
	case MGN_VHT3SS_MCS2:
	case MGN_VHT4SS_MCS2:
		/* BW20 QPSK 3/4 */
		rx_sensitivity = -77;
		break;

	case MGN_MCS3:
	case MGN_MCS11:
	case MGN_MCS19:
	case MGN_MCS27:
	case MGN_VHT1SS_MCS3:
	case MGN_VHT2SS_MCS3:
	case MGN_VHT3SS_MCS3:
	case MGN_VHT4SS_MCS3:
		/* BW20 16-QAM 1/2 */
		rx_sensitivity = -74;
		break;

	case MGN_MCS4:
	case MGN_MCS12:
	case MGN_MCS20:
	case MGN_MCS28:
	case MGN_VHT1SS_MCS4:
	case MGN_VHT2SS_MCS4:
	case MGN_VHT3SS_MCS4:
	case MGN_VHT4SS_MCS4:
		/* BW20 16-QAM 3/4 */
		rx_sensitivity = -70;
		break;

	case MGN_MCS5:
	case MGN_MCS13:
	case MGN_MCS21:
	case MGN_MCS29:
	case MGN_VHT1SS_MCS5:
	case MGN_VHT2SS_MCS5:
	case MGN_VHT3SS_MCS5:
	case MGN_VHT4SS_MCS5:
		/* BW20 64-QAM 2/3 */
		rx_sensitivity = -66;
		break;

	case MGN_MCS6:
	case MGN_MCS14:
	case MGN_MCS22:
	case MGN_MCS30:
	case MGN_VHT1SS_MCS6:
	case MGN_VHT2SS_MCS6:
	case MGN_VHT3SS_MCS6:
	case MGN_VHT4SS_MCS6:
		/* BW20 64-QAM 3/4 */
		rx_sensitivity = -65;
		break;

	case MGN_MCS7:
	case MGN_MCS15:
	case MGN_MCS23:
	case MGN_MCS31:
	case MGN_VHT1SS_MCS7:
	case MGN_VHT2SS_MCS7:
	case MGN_VHT3SS_MCS7:
	case MGN_VHT4SS_MCS7:
		/* BW20 64-QAM 5/6 */
		rx_sensitivity = -64;
		break;

	case MGN_VHT1SS_MCS8:
	case MGN_VHT2SS_MCS8:
	case MGN_VHT3SS_MCS8:
	case MGN_VHT4SS_MCS8:
		/* BW20 256-QAM 3/4 */
		rx_sensitivity = -59;
		break;

	case MGN_VHT1SS_MCS9:
	case MGN_VHT2SS_MCS9:
	case MGN_VHT3SS_MCS9:
	case MGN_VHT4SS_MCS9:
		/* BW20 256-QAM 5/6 */
		rx_sensitivity = -57;
		break;

	default:
		return -1;
		break;

	}

	switch(bw) {
	case CHANNEL_WIDTH_20:
		break;
	case CHANNEL_WIDTH_40:
		rx_sensitivity -= 3;
		break;
	case CHANNEL_WIDTH_80:
		rx_sensitivity -= 6;
		break;
	case CHANNEL_WIDTH_160:
		rx_sensitivity -= 9;
		break;
	case CHANNEL_WIDTH_5:
	case CHANNEL_WIDTH_10:
	case CHANNEL_WIDTH_80_80:
	default:
		return -1;
		break;
	}
	*pwr = rx_sensitivity;

	return 0;
}

/* output: path_a max tx power in dBm */
int rm_get_path_a_max_tx_power(_adapter *adapter, s8 *path_a)
{
	struct hal_spec_t *hal_spec = GET_HAL_SPEC(adapter);
	HAL_DATA_TYPE *hal_data = GET_HAL_DATA(adapter);
	int path, tx_num, band, bw, ch, n, rs;
	u8 rate_num;
	s8 max_pwr[RF_PATH_MAX], pwr;


	band = hal_data->current_band_type;
	bw = hal_data->current_channel_bw;
	ch = hal_data->current_channel;

	for (path = 0; path < RF_PATH_MAX; path++) {
		if (!HAL_SPEC_CHK_RF_PATH(hal_spec, band, path))
			break;

		max_pwr[path] = -127; /* min value of s8 */
#if (RM_MORE_DBG_MSG)
		RTW_INFO("RM: [%s][%c]\n", band_str(band), rf_path_char(path));
#endif
		for (rs = 0; rs < RATE_SECTION_NUM; rs++) {
			tx_num = rate_section_to_tx_num(rs);

			if (tx_num >= hal_spec->tx_nss_num)
				continue;

			if (band == BAND_ON_5G && IS_CCK_RATE_SECTION(rs))
				continue;

			if (IS_VHT_RATE_SECTION(rs) && !IS_HARDWARE_TYPE_JAGUAR_ALL(adapter))
				continue;

			rate_num = rate_section_rate_num(rs);

			/* get power by rate in db */
			for (n = rate_num - 1; n >= 0; n--) {
				pwr = phy_get_tx_power_final_absolute_value(adapter, path, rates_by_sections[rs].rates[n], bw, ch);
				max_pwr[path] = MAX(max_pwr[path], pwr);
#if (RM_MORE_DBG_MSG)
				RTW_INFO("RM: %9s = %2d\n",
					MGN_RATE_STR(rates_by_sections[rs].rates[n]), pwr);
#endif
			}
		}
	}
#if (RM_MORE_DBG_MSG)
	RTW_INFO("RM: path_a max_pwr=%ddBm\n", max_pwr[0]);
#endif
	*path_a = max_pwr[0];
	return 0;
}

u8 rm_gen_dialog_token(_adapter *padapter)
{
	struct rm_priv *prmpriv = &(padapter->rmpriv);
	struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
	struct mlme_ext_info *pmlmeinfo = &pmlmeext->mlmext_info;

	do {
		pmlmeinfo->dialogToken++;
	} while (pmlmeinfo->dialogToken == 0);

	return pmlmeinfo->dialogToken;
}

u8 rm_gen_meas_token(_adapter *padapter)
{
	struct rm_priv *prmpriv = &(padapter->rmpriv);

	do {
		prmpriv->meas_token++;
	} while (prmpriv->meas_token == 0);

	return prmpriv->meas_token;
}

u32 rm_gen_rmid(_adapter *padapter, struct rm_obj *prm, u8 role)
{
	u32 rmid;

	if (prm->psta == NULL)
		goto err;

	if (prm->q.diag_token == 0)
		goto err;

	rmid = prm->psta->cmn.aid << 16
		| prm->q.diag_token << 8
		| role;

	return rmid;
err:
	RTW_ERR("RM: unable to gen rmid\n");
	return 0;
}

#endif /* CONFIG_RTW_80211K */
