/******************************************************************************
 *
 * Copyright(c) 2007 - 2011 Realtek Corporation. All rights reserved.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
 *
 *
 ******************************************************************************/

/* ************************************************************
 * include files
 * ************************************************************ */

#include "mp_precomp.h"

#include "phydm_precomp.h"

#if (RTL8188E_SUPPORT == 1)

void
odm_dig_lower_bound_88e(
	struct PHY_DM_STRUCT		*p_dm_odm
)
{
	struct _dynamic_initial_gain_threshold_		*p_dm_dig_table = &p_dm_odm->dm_dig_table;

	if (p_dm_odm->ant_div_type == CG_TRX_HW_ANTDIV) {
		p_dm_dig_table->rx_gain_range_min = (u8) p_dm_dig_table->ant_div_rssi_max;
		ODM_RT_TRACE(p_dm_odm, ODM_COMP_ANT_DIV, ODM_DBG_LOUD, ("odm_dig_lower_bound_88e(): p_dm_dig_table->ant_div_rssi_max=%d\n", p_dm_dig_table->ant_div_rssi_max));
	}
	/* If only one Entry connected */
}

/*=============================================================
*  AntDiv Before Link
===============================================================*/
void
odm_sw_ant_div_reset_before_link(
	struct PHY_DM_STRUCT		*p_dm_odm
)
{

	struct _sw_antenna_switch_		*p_dm_swat_table = &p_dm_odm->dm_swat_table;

	p_dm_swat_table->swas_no_link_state = 0;

}


/* 3============================================================
 * 3 Dynamic Primary CCA
 * 3============================================================ */

void
odm_primary_cca_init(
	struct PHY_DM_STRUCT		*p_dm_odm)
{
	struct _dynamic_primary_cca		*primary_cca = &(p_dm_odm->dm_pri_cca);
	primary_cca->dup_rts_flag = 0;
	primary_cca->intf_flag = 0;
	primary_cca->intf_type = 0;
	primary_cca->monitor_flag = 0;
	primary_cca->pri_cca_flag = 0;
}

bool
odm_dynamic_primary_cca_dup_rts(
	struct PHY_DM_STRUCT		*p_dm_odm
)
{
	struct _dynamic_primary_cca		*primary_cca = &(p_dm_odm->dm_pri_cca);

	return	primary_cca->dup_rts_flag;
}

void
odm_dynamic_primary_cca(
	struct PHY_DM_STRUCT		*p_dm_odm
)
{

#if (DM_ODM_SUPPORT_TYPE != ODM_CE)

	struct _ADAPTER	*adapter =  p_dm_odm->adapter;	/* for NIC */

#if (DM_ODM_SUPPORT_TYPE & (ODM_WIN))
	struct sta_info	*p_entry;
#endif

	struct _FALSE_ALARM_STATISTICS		*false_alm_cnt = (struct _FALSE_ALARM_STATISTICS *)phydm_get_structure(p_dm_odm, PHYDM_FALSEALMCNT);
	struct _dynamic_primary_cca		*primary_cca = &(p_dm_odm->dm_pri_cca);

	bool		is_40mhz;
	bool		client_40mhz = false, client_tmp = false;      /* connected client BW */
	bool		is_connected = false;		/* connected or not */
	static u8	client_40mhz_pre = 0;
	static u64	last_tx_ok_cnt = 0;
	static u64	last_rx_ok_cnt = 0;
	static u32	counter = 0;
	static u8	delay = 1;
	u64		cur_tx_ok_cnt;
	u64		cur_rx_ok_cnt;
	u8		sec_ch_offset;
	u8		i;

	if (!(p_dm_odm->support_ability & ODM_BB_PRIMARY_CCA))
		return;

	if (p_dm_odm->support_ic_type != ODM_RTL8188E)
		return;

	is_40mhz = *(p_dm_odm->p_band_width);
	sec_ch_offset = *(p_dm_odm->p_sec_ch_offset);
	ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("Second CH Offset = %d\n", sec_ch_offset));

#if (DM_ODM_SUPPORT_TYPE == ODM_WIN)
	if (is_40mhz == 1)
		sec_ch_offset = sec_ch_offset % 2 + 1; /* NIC's definition is reverse to AP   1:secondary below,  2: secondary above */
	ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("Second CH Offset = %d\n", sec_ch_offset));
	/* 3 Check Current WLAN Traffic */
	cur_tx_ok_cnt = adapter->TxStats.NumTxBytesUnicast - last_tx_ok_cnt;
	cur_rx_ok_cnt = adapter->RxStats.NumRxBytesUnicast - last_rx_ok_cnt;
	last_tx_ok_cnt = adapter->TxStats.NumTxBytesUnicast;
	last_rx_ok_cnt = adapter->RxStats.NumRxBytesUnicast;
#elif (DM_ODM_SUPPORT_TYPE == ODM_AP)
	/* 3 Check Current WLAN Traffic */
	cur_tx_ok_cnt = *(p_dm_odm->p_num_tx_bytes_unicast) - last_tx_ok_cnt;
	cur_rx_ok_cnt = *(p_dm_odm->p_num_rx_bytes_unicast) - last_rx_ok_cnt;
	last_tx_ok_cnt = *(p_dm_odm->p_num_tx_bytes_unicast);
	last_rx_ok_cnt = *(p_dm_odm->p_num_rx_bytes_unicast);
#endif

	/* ==================Debug Message==================== */
	ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("TP = %llu\n", cur_tx_ok_cnt + cur_rx_ok_cnt));
	ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("is_40mhz = %d\n", is_40mhz));
	ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("BW_LSC = %d\n", false_alm_cnt->cnt_bw_lsc));
	ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("BW_USC = %d\n", false_alm_cnt->cnt_bw_usc));
	ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("CCA OFDM = %d\n", false_alm_cnt->cnt_ofdm_cca));
	ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("CCA CCK = %d\n", false_alm_cnt->cnt_cck_cca));
	ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("OFDM FA = %d\n", false_alm_cnt->cnt_ofdm_fail));
	ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("CCK FA = %d\n", false_alm_cnt->cnt_cck_fail));
	/* ================================================ */

#if (DM_ODM_SUPPORT_TYPE == ODM_WIN)
	if (ACTING_AS_AP(adapter))   /* primary cca process only do at AP mode */
#endif
	{

#if (DM_ODM_SUPPORT_TYPE == ODM_WIN)
		ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("ACTING as AP mode=%d\n", ACTING_AS_AP(adapter)));
		/* 3 To get entry's connection and BW infomation status. */
		for (i = 0; i < ASSOCIATE_ENTRY_NUM; i++) {
			if (IsAPModeExist(adapter) && GetFirstExtAdapter(adapter) != NULL)
				p_entry = AsocEntry_EnumStation(GetFirstExtAdapter(adapter), i);
			else
				p_entry = AsocEntry_EnumStation(GetDefaultAdapter(adapter), i);
			if (p_entry != NULL) {
				client_tmp = p_entry->BandWidth;   /* client BW */
				ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("Client_BW=%d\n", client_tmp));
				if (client_tmp > client_40mhz)
					client_40mhz = client_tmp;     /* 40M/20M coexist => 40M priority is High */

				if (p_entry->bAssociated) {
					is_connected = true;  /* client is connected or not */
					break;
				}
			} else
				break;
		}
#elif (DM_ODM_SUPPORT_TYPE == ODM_AP)
		/* 3 To get entry's connection and BW infomation status. */

		struct sta_info *pstat;

		for (i = 0; i < ODM_ASSOCIATE_ENTRY_NUM; i++) {
			pstat = p_dm_odm->p_odm_sta_info[i];
			if (IS_STA_VALID(pstat)) {
				client_tmp = pstat->tx_bw;
				if (client_tmp > client_40mhz)
					client_40mhz = client_tmp;     /* 40M/20M coexist => 40M priority is High */

				is_connected = true;
			}
		}
#endif
		ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("is_connected=%d\n", is_connected));
		ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("Is Client 40MHz=%d\n", client_40mhz));
		/* 1 Monitor whether the interference exists or not */
		if (primary_cca->monitor_flag == 1) {
			if (sec_ch_offset == 1) {    /* secondary channel is below the primary channel */
				if ((false_alm_cnt->cnt_ofdm_cca > 500) && (false_alm_cnt->cnt_bw_lsc > false_alm_cnt->cnt_bw_usc + 500)) {
					if (false_alm_cnt->cnt_ofdm_fail > false_alm_cnt->cnt_ofdm_cca >> 1) {
						primary_cca->intf_type = 1;
						primary_cca->pri_cca_flag = 1;
						odm_set_bb_reg(p_dm_odm, 0xc6c, BIT(8) | BIT7, 2); /* USC MF */
						if (primary_cca->dup_rts_flag == 1)
							primary_cca->dup_rts_flag = 0;
					} else {
						primary_cca->intf_type = 2;
						if (primary_cca->dup_rts_flag == 0)
							primary_cca->dup_rts_flag = 1;
					}

				} else { /* interferecne disappear */
					primary_cca->dup_rts_flag = 0;
					primary_cca->intf_flag = 0;
					primary_cca->intf_type = 0;
				}
			} else if (sec_ch_offset == 2) { /* secondary channel is above the primary channel */
				if ((false_alm_cnt->cnt_ofdm_cca > 500) && (false_alm_cnt->cnt_bw_usc > false_alm_cnt->cnt_bw_lsc + 500)) {
					if (false_alm_cnt->cnt_ofdm_fail > false_alm_cnt->cnt_ofdm_cca >> 1) {
						primary_cca->intf_type = 1;
						primary_cca->pri_cca_flag = 1;
						odm_set_bb_reg(p_dm_odm, 0xc6c, BIT(8) | BIT7, 1); /* LSC MF */
						if (primary_cca->dup_rts_flag == 1)
							primary_cca->dup_rts_flag = 0;
					} else {
						primary_cca->intf_type = 2;
						if (primary_cca->dup_rts_flag == 0)
							primary_cca->dup_rts_flag = 1;
					}

				} else { /* interferecne disappear */
					primary_cca->dup_rts_flag = 0;
					primary_cca->intf_flag = 0;
					primary_cca->intf_type = 0;
				}


			}
			primary_cca->monitor_flag = 0;
		}

		/* 1 Dynamic Primary CCA Main Function */
		if (primary_cca->monitor_flag == 0) {
			if (is_40mhz) {  		/* if RFBW==40M mode which require to process primary cca */
				/* 2 STA is NOT Connected */
				if (!is_connected) {
					ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("STA NOT Connected!!!!\n"));

					if (primary_cca->pri_cca_flag == 1) {	/* reset primary cca when STA is disconnected */
						primary_cca->pri_cca_flag = 0;
						odm_set_bb_reg(p_dm_odm, 0xc6c, BIT(8) | BIT(7), 0);
					}
					if (primary_cca->dup_rts_flag == 1)		/* reset Duplicate RTS when STA is disconnected */
						primary_cca->dup_rts_flag = 0;

					if (sec_ch_offset == 1) { /* secondary channel is below the primary channel */
						if ((false_alm_cnt->cnt_ofdm_cca > 800) && (false_alm_cnt->cnt_bw_lsc * 5 > false_alm_cnt->cnt_bw_usc * 9)) {
							primary_cca->intf_flag = 1;    /* secondary channel interference is detected!!! */
							if (false_alm_cnt->cnt_ofdm_fail > false_alm_cnt->cnt_ofdm_cca >> 1)
								primary_cca->intf_type = 1;   	/* interference is shift */
							else
								primary_cca->intf_type = 2;   	/* interference is in-band */
						} else {
							primary_cca->intf_flag = 0;
							primary_cca->intf_type = 0;
						}
					} else if (sec_ch_offset == 2) { /* secondary channel is above the primary channel */
						if ((false_alm_cnt->cnt_ofdm_cca > 800) && (false_alm_cnt->cnt_bw_usc * 5 > false_alm_cnt->cnt_bw_lsc * 9)) {
							primary_cca->intf_flag = 1;    /* secondary channel interference is detected!!! */
							if (false_alm_cnt->cnt_ofdm_fail > false_alm_cnt->cnt_ofdm_cca >> 1)
								primary_cca->intf_type = 1;   	/* interference is shift */
							else
								primary_cca->intf_type = 2;   	/* interference is in-band */
						} else {
							primary_cca->intf_flag = 0;
							primary_cca->intf_type = 0;
						}
					}
					ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("primary_cca=%d\n", primary_cca->pri_cca_flag));
					ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("Intf_Type=%d\n", primary_cca->intf_type));
				}
				/* 2 STA is Connected */
				else {
					if (client_40mhz == 0)		/* 3 */ { /* client BW = 20MHz */
						if (primary_cca->pri_cca_flag == 0) {
							primary_cca->pri_cca_flag = 1;
							if (sec_ch_offset == 1)
								odm_set_bb_reg(p_dm_odm, 0xc6c, BIT(8) | BIT(7), 2);
							else if (sec_ch_offset == 2)
								odm_set_bb_reg(p_dm_odm, 0xc6c, BIT(8) | BIT(7), 1);
						}
						ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("STA Connected 20M!!! primary_cca=%d\n", primary_cca->pri_cca_flag));
					} else		/* 3 */ { /* client BW = 40MHz */
						if (primary_cca->intf_flag == 1) { /* interference is detected!! */
							if (primary_cca->intf_type == 1) {
								if (primary_cca->pri_cca_flag != 1) {
									primary_cca->pri_cca_flag = 1;
									if (sec_ch_offset == 1)
										odm_set_bb_reg(p_dm_odm, 0xc6c, BIT(8) | BIT(7), 2);
									else if (sec_ch_offset == 2)
										odm_set_bb_reg(p_dm_odm, 0xc6c, BIT(8) | BIT(7), 1);
								}
							} else if (primary_cca->intf_type == 2) {
								if (primary_cca->dup_rts_flag != 1)
									primary_cca->dup_rts_flag = 1;
							}
						} else { /* if intf_flag==0 */
							if ((cur_tx_ok_cnt + cur_rx_ok_cnt) < 10000) { /* idle mode or TP traffic is very low */
								if (sec_ch_offset == 1) {
									if ((false_alm_cnt->cnt_ofdm_cca > 800) && (false_alm_cnt->cnt_bw_lsc * 5 > false_alm_cnt->cnt_bw_usc * 9)) {
										primary_cca->intf_flag = 1;
										if (false_alm_cnt->cnt_ofdm_fail > false_alm_cnt->cnt_ofdm_cca >> 1)
											primary_cca->intf_type = 1;   	/* interference is shift */
										else
											primary_cca->intf_type = 2;   	/* interference is in-band */
									}
								} else if (sec_ch_offset == 2) {
									if ((false_alm_cnt->cnt_ofdm_cca > 800) && (false_alm_cnt->cnt_bw_usc * 5 > false_alm_cnt->cnt_bw_lsc * 9)) {
										primary_cca->intf_flag = 1;
										if (false_alm_cnt->cnt_ofdm_fail > false_alm_cnt->cnt_ofdm_cca >> 1)
											primary_cca->intf_type = 1;   	/* interference is shift */
										else
											primary_cca->intf_type = 2;   	/* interference is in-band */
									}

								}
							} else { /* TP Traffic is High */
								if (sec_ch_offset == 1) {
									if (false_alm_cnt->cnt_bw_lsc > (false_alm_cnt->cnt_bw_usc + 500)) {
										if (delay == 0) { /* add delay to avoid interference occurring abruptly, jump one time */
											primary_cca->intf_flag = 1;
											if (false_alm_cnt->cnt_ofdm_fail > false_alm_cnt->cnt_ofdm_cca >> 1)
												primary_cca->intf_type = 1;   	/* interference is shift */
											else
												primary_cca->intf_type = 2;   	/* interference is in-band */
											delay = 1;
										} else
											delay = 0;
									}
								} else if (sec_ch_offset == 2) {
									if (false_alm_cnt->cnt_bw_usc > (false_alm_cnt->cnt_bw_lsc + 500)) {
										if (delay == 0) { /* add delay to avoid interference occurring abruptly */
											primary_cca->intf_flag = 1;
											if (false_alm_cnt->cnt_ofdm_fail > false_alm_cnt->cnt_ofdm_cca >> 1)
												primary_cca->intf_type = 1;   	/* interference is shift */
											else
												primary_cca->intf_type = 2;   	/* interference is in-band */
											delay = 1;
										} else
											delay = 0;
									}
								}
							}
						}
						ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("Primary CCA=%d\n", primary_cca->pri_cca_flag));
						ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("Duplicate RTS=%d\n", primary_cca->dup_rts_flag));
					}

				} /* end of connected */
			}
		}
		/* 1 Dynamic Primary CCA Monitor counter */
		if ((primary_cca->pri_cca_flag == 1) || (primary_cca->dup_rts_flag == 1)) {
			if (client_40mhz == 0) {  /* client=20M no need to monitor primary cca flag */
				client_40mhz_pre = client_40mhz;
				return;
			}
			counter++;
			ODM_RT_TRACE(p_dm_odm, ODM_COMP_DYNAMIC_PRICCA, ODM_DBG_LOUD, ("counter=%d\n", counter));
			if ((counter == 30) || ((client_40mhz - client_40mhz_pre) == 1)) { /* Every 60 sec to monitor one time */
				primary_cca->monitor_flag = 1;     /* monitor flag is triggered!!!!! */
				if (primary_cca->pri_cca_flag == 1) {
					primary_cca->pri_cca_flag = 0;
					odm_set_bb_reg(p_dm_odm, 0xc6c, BIT(8) | BIT(7), 0);
				}
				counter = 0;
			}
		}
	}

	client_40mhz_pre = client_40mhz;
#endif
}

#endif /* #if (RTL8188E_SUPPORT == 1) */
