/******************************************************************************
 *
 * Copyright(c) 2007 - 2020  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.
 *
 * The full GNU General Public License is included in this distribution in the
 * file called LICENSE.
 *
 * Contact Information:
 * wlanfae <wlanfae@realtek.com>
 * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park,
 * Hsinchu 300, Taiwan.
 *
 * Larry Finger <Larry.Finger@lwfinger.net>
 *
 *****************************************************************************/
#include "halbb_precomp.h"

#ifdef HALBB_CFO_TRK_SUPPORT

#ifdef BB_DYN_CFO_TRK_LOP

void halbb_dyn_cfo_trk_loop_en(struct bb_info *bb, bool en)
{
	bb->bb_cfo_trk_i.bb_dyn_cfo_trk_lop_i.dyn_cfo_trk_loop_en = en;
}

void halbb_cfo_trk_loop_cr_cfg(struct bb_info *bb, enum bb_dctl_state_t state)
{
	struct bb_cfo_trk_info *cfo_trk = &bb->bb_cfo_trk_i;
	struct bb_dyn_cfo_trk_lop_info *dctl = &cfo_trk->bb_dyn_cfo_trk_lop_i;
	struct bb_cfo_trk_lop_cr_info  *cr;

	if (state >= DCTL_NUM)
		return;

	if (state == dctl->dyn_cfo_trk_loop_state) {
		dctl->dctl_hold_cnt++;
		BB_DBG(bb, DBG_IC_API, "hold_cnt = %d", dctl->dctl_hold_cnt);
		return;
	}

	dctl->dyn_cfo_trk_loop_state = state;

	dctl->dctl_hold_cnt = 0;
	cr = &dctl->bb_cfo_trk_lop_cr_i[state];

	halbb_set_reg(bb, 0x4404, 0x7C00, cr->dctl_data); /*8852a CR*/
	halbb_set_reg(bb, 0x440c, 0x7C00, cr->dctl_pilot);

	BB_DBG(bb, DBG_IC_API, "dctl_data = 0x%x, dctl_pilot = 0x%x", cr->dctl_data, cr->dctl_pilot);
}

void halbb_dyn_cfo_trk_loop(struct bb_info *bb)
{
	struct bb_cfo_trk_info *cfo_trk = &bb->bb_cfo_trk_i;
	struct bb_dyn_cfo_trk_lop_info *dctl = &cfo_trk->bb_dyn_cfo_trk_lop_i;
	struct bb_link_info *link = &bb->bb_link_i;
	struct rtw_phl_stainfo_t *sta;
	u16 snr_tmp = 0;

	if (!dctl->dyn_cfo_trk_loop_en) {
		BB_DBG(bb, DBG_IC_API, "dyn_cfo_trk_loop_en = %d",
		       dctl->dyn_cfo_trk_loop_en);
		return;
	}

	if (!link->is_linked)
		return;

	if (!link->is_one_entry_only)
		return;

	sta = bb->phl_sta_info[bb->bb_link_i.one_entry_macid];

	if (!sta)
		return;

	snr_tmp = sta->hal_sta->rssi_stat.snr_ma;
	BB_DBG(bb, DBG_IC_API, "macid=%d, SNR = %s",
	       bb->bb_link_i.one_entry_macid,
	       halbb_print_sign_frac_digit2(bb, snr_tmp, 16, 4));

	BB_DBG(bb, DBG_IC_API, "snr_th{L, H} = {%d, %d}",
	       dctl->dctl_snr_th_l >> RSSI_MA_H, dctl->dctl_snr_th_h >> RSSI_MA_H);

	if (snr_tmp >= dctl->dctl_snr_th_h)
		halbb_cfo_trk_loop_cr_cfg(bb, DCTL_LINK);
	else if (snr_tmp <= dctl->dctl_snr_th_l)
		halbb_cfo_trk_loop_cr_cfg(bb, DCTL_SNR);
}

void halbb_dyn_cfo_trk_loop_init(struct bb_info *bb)
{
	struct bb_cfo_trk_info *cfo_trk = &bb->bb_cfo_trk_i;
	struct bb_dyn_cfo_trk_lop_info *dctl = &cfo_trk->bb_dyn_cfo_trk_lop_i;

	dctl->dyn_cfo_trk_loop_en = false;
	dctl->dyn_cfo_trk_loop_state = DCTL_SNR;
	dctl->dctl_snr_th_l = (u16)5 << RSSI_MA_H;
	dctl->dctl_snr_th_h = (u16)7 << RSSI_MA_H;

	dctl->bb_cfo_trk_lop_cr_i[DCTL_SNR].dctl_data = 0x1f;
	dctl->bb_cfo_trk_lop_cr_i[DCTL_SNR].dctl_pilot = 0x7;

	dctl->bb_cfo_trk_lop_cr_i[DCTL_LINK].dctl_data = 0x1f;
	dctl->bb_cfo_trk_lop_cr_i[DCTL_LINK].dctl_pilot = 0x9;
}

#endif

void halbb_digital_cfo_comp(struct bb_info *bb, s32 curr_cfo)
{
	struct bb_cfo_trk_info *cfo_trk = &bb->bb_cfo_trk_i;
	struct bb_cfo_trk_cr_info *cr = &bb->bb_cfo_trk_i.bb_cfo_trk_cr_i;
	struct bb_link_info *bb_link = &bb->bb_link_i;
	struct rtw_hal_com_t *hal = bb->hal_com;
	//u32 r_ax_pwr_ul_ctrl2;
	s32 cfo_avg_312; /*in unit of sub-carrier spacing*/
	s32 digital_cfo_comp_offset;
	bool is_positive = IS_GREATER(curr_cfo, 0);

	if (!bb_link->is_linked) {
		BB_DBG(bb, DBG_CFO_TRK, "[%s] is_linked=%d\n", __func__,
		       bb_link->is_linked);
		return;
	}

	if (curr_cfo == 0) {
		BB_DBG(bb, DBG_CFO_TRK, "curr_cfo=0\n");
		return;
	}

	BB_DBG(bb, DBG_CFO_TRK, "[%s]\n", __func__);

	// For manually fine tune digital cfo
	digital_cfo_comp_offset = halbb_get_reg(bb, 0x4264, 0x00000003);
	
	/*CR{S(12,11} = (CFO_avg{S(12,2)} << 9) / 312.5*/
	/*CR = X(KHz) << 9 / 312.5(KHz) = X << 10 / 625 ~= X*(1000/625) = (X*8)/5 = (X << 3)/5 */
	if(is_positive) {
		cfo_avg_312 = HALBB_DIV(curr_cfo << 3, 5) + digital_cfo_comp_offset;
	} else {
		cfo_avg_312 = HALBB_DIV(curr_cfo << 3, 5) - digital_cfo_comp_offset;
	}

	halbb_print_sign_frac_digit(bb, curr_cfo, 32, 2, bb->dbg_buf, HALBB_SNPRINT_SIZE);
	BB_DBG(bb, DBG_CFO_TRK, "[CFO_DBG] [Digital Comp] cfo: %s KHz\n", bb->dbg_buf);
	
	halbb_print_sign_frac_digit(bb, cfo_avg_312, 32, 11, bb->dbg_buf, HALBB_SNPRINT_SIZE);
	BB_DBG(bb, DBG_CFO_TRK, "[CFO_DBG] cfo_avg_312: %s * 312.5KHz\n", bb->dbg_buf);

	#ifdef BB_8852A_2_SUPPORT
	if (bb->ic_type == BB_RTL8852A && hal->cv == CBV)
		cfo_avg_312 = cfo_avg_312 * (-1); /* 8852A bug*/
	#endif

	// 0x448C[11:0]	CFO compensation value in unit of sub-carrier spacing
	halbb_set_reg(bb, cr->r_cfo_comp_seg0_312p5khz, cr->r_cfo_comp_seg0_312p5khz_m, cfo_avg_312);
}

void halbb_digital_cfo_comp_init(struct bb_info *bb)
{
	struct bb_cfo_trk_info *cfo_trk = &bb->bb_cfo_trk_i;
	struct bb_cfo_trk_cr_info *cr = &bb->bb_cfo_trk_i.bb_cfo_trk_cr_i;

	// 0x4494[29] Whether the memory of r_cfo_comp_312p5khz is valid
	halbb_set_reg(bb, cr->r_cfo_comp_seg0_vld, cr->r_cfo_comp_seg0_vld_m, 1);

	// 0x4490[27:24] r_cfo_weighting
	halbb_set_reg(bb, cr->r_cfo_wgting, cr->r_cfo_wgting_m, 8);

	/* 0xD248 */
	/* All scenario set CFO comp.*/
	rtw_hal_mac_write_msk_pwr_reg(bb->hal_com, 0, 0xd248, 0x7, 0);
}

void halbb_cfo_trk_reset(struct bb_info *bb)
{
	struct bb_cfo_trk_info *bb_cfo_trk = &bb->bb_cfo_trk_i;

	BB_DBG(bb, DBG_CFO_TRK, "%s ======>\n", __func__);

	bb_cfo_trk->is_adjust = false;

	if (bb_cfo_trk->crystal_cap > bb_cfo_trk->def_x_cap) {
		halbb_set_crystal_cap(bb, bb_cfo_trk->crystal_cap - 1);
	} else if (bb_cfo_trk->crystal_cap < bb_cfo_trk->def_x_cap) {
		halbb_set_crystal_cap(bb, bb_cfo_trk->crystal_cap + 1);
	}
	BB_DBG(bb, DBG_CFO_TRK, "(0x%x) approach to dflt_val=(0x%x)\n",
	       bb_cfo_trk->crystal_cap, bb_cfo_trk->def_x_cap);
}

void halbb_cfo_acc_io_en(struct bb_info *bb)
{
	struct bb_cfo_trk_info *cfo_trk = &bb->bb_cfo_trk_i;

	BB_DBG(bb, DBG_CFO_TRK, "[%s]===>\n", __func__);

	if (!cfo_trk->cfo_trig_by_timer_en)
		return;

	halbb_cfo_dm(bb);
	halbb_cfg_timers(bb, BB_SET_TIMER, &bb->bb_cfo_trk_i.cfo_timer_i);
}

void halbb_cfo_acc_callback(void *context)
{
	struct bb_info *bb = (struct bb_info *)context;
	struct bb_cfo_trk_info *cfo_trk = &bb->bb_cfo_trk_i;
	struct halbb_timer_info *timer = &cfo_trk->cfo_timer_i;

	BB_DBG(bb, DBG_CFO_TRK, "[%s]===>\n", __func__);

	timer->timer_state = BB_TIMER_IDLE;

	if (bb->phl_com->hci_type == RTW_HCI_PCIE)
		halbb_cfo_acc_io_en(bb);
	else
		rtw_hal_cmd_notify(bb->phl_com, MSG_EVT_NOTIFY_BB, (void *)(&timer->event_idx), bb->bb_phy_idx);
}

void halbb_cfo_acc_timer_init(struct bb_info *bb)
{
	struct halbb_timer_info *timer = &bb->bb_cfo_trk_i.cfo_timer_i;

	BB_DBG(bb, DBG_CFO_TRK, "[%s]\n", __func__);

	timer->event_idx = BB_EVENT_TIMER_CFO;
	timer->timer_state = BB_TIMER_IDLE;

	halbb_init_timer(bb, &timer->timer_list, halbb_cfo_acc_callback, bb, "halbb_cfo_timer");
}

void halbb_cfo_deinit(struct bb_info *bb)
{
	BB_DBG(bb, DBG_CFO_TRK, "halbb_cfo_deinit");
}

void halbb_cfo_trk_init(struct bb_info *bb)
{
	struct bb_cfo_trk_info *bb_cfo_trk = &bb->bb_cfo_trk_i;
	struct rtw_phl_com_t *phl = bb->phl_com;
	struct dev_cap_t *dev = &phl->dev_cap;

	BB_DBG(bb, DBG_CFO_TRK, "[%s]=========>\n", __func__);

	/* Init crystal cap from efuse */
	bb_cfo_trk->def_x_cap = bb->phl_com->dev_cap.xcap & 0x7f;
	bb_cfo_trk->crystal_cap = bb_cfo_trk->def_x_cap;
	bb_cfo_trk->is_adjust = false;

	bb_cfo_trk->x_cap_ofst = 0;

	bb_cfo_trk->cfo_th[0] = CFO_TRK_TH_1 << 2;
	bb_cfo_trk->cfo_th[1] = CFO_TRK_TH_2 << 2;
	bb_cfo_trk->cfo_th[2] = CFO_TRK_TH_3 << 2;
	bb_cfo_trk->cfo_th[3] = CFO_TRK_TH_4 << 2;

	bb_cfo_trk->cfo_th_en = CFO_TRK_ENABLE_TH << 2;
	bb_cfo_trk->cfo_th_stop = CFO_TRK_STOP_TH << 2;

	bb_cfo_trk->sw_comp_fine_tune = CFO_SW_COMP_FINE_TUNE << 2;

	bb_cfo_trk->multi_sta_cfo_mode = TP_BASED_AVG_MODE;
	bb_cfo_trk->man_cfo_tol = false;
	BB_DBG(bb, DBG_CFO_TRK, "Default xcap=0x%x\n", bb_cfo_trk->def_x_cap);

	// For manually fine tune digital cfo
	halbb_set_reg(bb, 0x4264, 0x00000003, 1);
	bb_cfo_trk->tb_tx_comp_cfo_th = DIGI_CFO_COMP_LIMIT << 2;
	halbb_digital_cfo_comp_init(bb);

	bb_cfo_trk->cfo_period_cnt = CFO_PERIOD_CNT;
	bb_cfo_trk->cfo_timer_i.cb_time = CFO_COMP_PERIOD;
	bb_cfo_trk->cfo_trig_by_timer_en = false;

	bb_cfo_trk->cfo_tf_cnt_th = CFO_TF_CNT_TH;
	bb_cfo_trk->cfo_tf_cnt_pre = 0;
	bb_cfo_trk->bb_cfo_trk_acc_mode = CFO_ACC_MODE_1;

	bb_cfo_trk->bb_cfo_trk_state = CFO_STATE_0;
	bb_cfo_trk->bb_cfo_trk_cnt = 0;
	bb_cfo_trk->cfo_src = CFO_SRC_PREAMBLE;

	// For NIC only, to speed up sw CFO compensation
	bb_cfo_trk->cfo_dyn_acc_en = (dev->rfe_type < 50) ? true : false;
	bb_cfo_trk->cfo_trk_by_data_en = false;
}

void halbb_set_crystal_cap(struct bb_info *bb, u8 crystal_cap)
{
	struct bb_cfo_trk_info *bb_cfo_trk = &bb->bb_cfo_trk_i;
	struct rtw_hal_com_t *hal = bb->hal_com;
	u32 sc_xi_val = 0, sc_xo_val = 0;

	if (bb_cfo_trk->crystal_cap == crystal_cap)
		return;

	if (crystal_cap > 0x7F) {
		BB_DBG(bb, DBG_CFO_TRK, "crystal_cap(0x%x) > 0x7F\n", crystal_cap);
		crystal_cap = 0x7F;
	}

	rtw_hal_mac_set_xcap(hal, SC_XO, (u32)crystal_cap & 0x7F);
	rtw_hal_mac_set_xcap(hal, SC_XI, (u32)crystal_cap & 0x7F);

	rtw_hal_mac_get_xcap(hal, SC_XO, &sc_xo_val);
	rtw_hal_mac_get_xcap(hal, SC_XI, &sc_xi_val);
	
	BB_DBG(bb, DBG_CFO_TRK, "Set sc_xi/xo= {0x%x, 0x%x}\n", sc_xi_val, sc_xo_val);

	bb_cfo_trk->crystal_cap = (u8)sc_xi_val;

	bb_cfo_trk->x_cap_ofst = (s8)DIFF_2(bb_cfo_trk->crystal_cap, bb_cfo_trk->def_x_cap);
	if (bb_cfo_trk->crystal_cap < bb_cfo_trk->def_x_cap)
		bb_cfo_trk->x_cap_ofst = bb_cfo_trk->x_cap_ofst * (-1);
}

void halbb_crystal_cap_adjust(struct bb_info *bb, s32 curr_cfo)
{
	struct bb_cfo_trk_info *bb_cfo_trk = &bb->bb_cfo_trk_i;
	struct bb_path_info *bb_path = &bb->bb_path_i;	
	u8 x_cap = bb_cfo_trk->crystal_cap;
	u8 step = 0;
	s32 cfo_abs = ABS_32(curr_cfo);
	bool is_positive = IS_GREATER(curr_cfo, 0);

	BB_DBG(bb, DBG_CFO_TRK, "[CFO_DBG] %s ======>\n", __func__);

	if (!bb_cfo_trk->is_adjust) {
		/* If cfo_avg > th, enable tracking */
		if (cfo_abs > bb_cfo_trk->cfo_th_en)
			bb_cfo_trk->is_adjust = true;
	} else {
		if (cfo_abs <= bb_cfo_trk->cfo_th_stop)
			bb_cfo_trk->is_adjust = false;
	}

	if (!bb_cfo_trk->is_adjust) {
		BB_DBG(bb, DBG_CFO_TRK, "Stop Tracking\n");
		/*halbb_digital_cfo_comp(bb);*/
		return;
	}

	/*Adjust Crystal Cap. */
	if (cfo_abs > bb_cfo_trk->cfo_th[3])
		step = 7;
	else if (cfo_abs > bb_cfo_trk->cfo_th[2])
		step = 5;
	else if (cfo_abs > bb_cfo_trk->cfo_th[1])
		step = 3;
	else if (cfo_abs > bb_cfo_trk->cfo_th[0])
		step = 1;
	else
		return;

	if (is_positive)
		x_cap += step;
	else if (x_cap > step)
		x_cap -= step;

	//x_cap = (is_positive) ? (x_cap + step) : (x_cap - step);

	BB_DBG(bb, DBG_CFO_TRK, "TH[en, stop]={%d, %d}, TH[3:0]={%d, %d, %d, %d}\n",
	       bb_cfo_trk->cfo_th_en >> 2, bb_cfo_trk->cfo_th_stop >> 2,
	       bb_cfo_trk->cfo_th[3] >> 2, bb_cfo_trk->cfo_th[2] >> 2,
	       bb_cfo_trk->cfo_th[1] >> 2, bb_cfo_trk->cfo_th[0] >> 2);

	BB_DBG(bb, DBG_CFO_TRK, "step=%s%d\n",
	       (is_positive) ? "+" : "-", step);

	halbb_set_crystal_cap(bb, x_cap);
}

s32 halbb_avg_cfo_calc(struct bb_info *bb)
{
	struct bb_cfo_trk_info *bb_cfo_trk = &bb->bb_cfo_trk_i;
	struct bb_link_info *bb_link = &bb->bb_link_i;
	struct rtw_phl_stainfo_t *sta;
	struct rtw_cfo_info *cfo_t = NULL;
	s32 cfo_all_avg = 0;
	u16 macid;

	sta = bb->phl_sta_info[bb_link->one_entry_macid];

	if (!sta)
		return bb_cfo_trk->cfo_avg_pre;

	macid = sta->macid;
	cfo_t = &sta->hal_sta->cfo_stat;

	BB_DBG(bb, DBG_CFO_TRK, "one-entry-only macid=%d\n", macid);

	cfo_all_avg = HALBB_DIV(cfo_t->cfo_tail, (s32)cfo_t->cfo_cnt);

	BB_DBG(bb, DBG_CFO_TRK, "Total cfo=(%d), pkt_cnt=(%d), avg_cfo=(%d)\n",
	       cfo_t->cfo_tail >> 2, cfo_t->cfo_cnt, cfo_all_avg >> 2);
	return cfo_all_avg;
}

s32 halbb_multi_sta_avg_cfo_calc(struct bb_info *bb)
{
	struct bb_cfo_trk_info *bb_cfo_trk = &bb->bb_cfo_trk_i;
	struct rtw_hal_com_t *hal = bb->hal_com;
	struct rtw_phl_com_t *phl = bb->phl_com;
	struct dev_cap_t *dev = &phl->dev_cap;
	u8 band = bb->hal_com->band[0].cur_chandef.band;
	struct bb_link_info *bb_link = &bb->bb_link_i;
	struct rtw_phl_stainfo_t *sta;
	struct rtw_cfo_info *cfo_t = NULL;
	s32 target_cfo = 0;
	s32 cfo_khz_all = 0;
	s32 cfo_khz_all_tp_wgt = 0;
	s32 cfo_avg = 0;
	s32 max_cfo_lb= 0x80000000;
	s32 min_cfo_ub = 0x7fffffff;
	u16 cfo_cnt_all = 0;
	u8 active_entry_cnt = 0, sta_cnt = 0;
	u32 tp_all = 0;
	u16 active_entry = 0;
	u8 i;
	u8 cfo_tol = 0;
	u16 macid;

	BB_DBG(bb, DBG_CFO_TRK, "Multi entry cfo_trk\n");

	if (!bb_cfo_trk->man_cfo_tol) {
		if (band == BAND_ON_24G)
			bb_cfo_trk->sta_cfo_tolerance = STA_CFO_TOLERANCE_2G;
		else
			bb_cfo_trk->sta_cfo_tolerance = STA_CFO_TOLERANCE_5G;
	}

	BB_DBG(bb, DBG_CFO_TRK, "Default multi-sta cfo_trk tolerance=%d\n",
						 bb_cfo_trk->sta_cfo_tolerance);

	/* There are two extra multi-sta strategies remaining as references*/
#if 0
	if (bb_cfo_trk->multi_sta_cfo_mode == PKT_BASED_AVG_MODE) {
		/* Method-1: Centroid pkt based cfo tracking: Compute average cfo from all sta PPDUs */
		/* Just like one entry only method*/
		BB_DBG(bb, DBG_CFO_TRK, "Pkt based average mode\n");

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

			if (!bb->sta_exist[i])
				continue;

			if (bb_cfo_trk->cfo_cnt[i] == 0)
				continue;

			//BB_DBG(bb, DBG_CFO_TRK, "Macid=%d\n", i);
			cfo_khz_all += bb_cfo_trk->cfo_tail[i];
			cfo_cnt_all += bb_cfo_trk->cfo_cnt[i];
			cfo_avg = HALBB_DIV(cfo_khz_all, (s32)cfo_cnt_all);

			BB_DBG(bb, DBG_CFO_TRK, "s(32,2) Multi-sta total cfo=(%d), pkt_cnt=(%d), avg_cfo=(%d)\n",
					cfo_khz_all, cfo_cnt_all, cfo_avg);

			target_cfo = cfo_avg;
		}
	} else if (bb_cfo_trk->multi_sta_cfo_mode == ENTRY_BASED_AVG_MODE) {
		/* Method-2: Entry based cfo tracking: Compute average cfo of ertries */
		BB_DBG(bb, DBG_CFO_TRK, "Entry based average mode\n");

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

			if (!bb->sta_exist[i])
				continue;

			if (bb_cfo_trk->cfo_cnt[i] == 0)
				continue;

			active_entry |= BIT(i);
			bb_cfo_trk->cfo_avg[i] = HALBB_DIV(bb_cfo_trk->cfo_tail[i],
				                      (s32)bb_cfo_trk->cfo_cnt[i]);
			cfo_khz_all += bb_cfo_trk->cfo_avg[i];

			BB_DBG(bb, DBG_CFO_TRK, "Macid=%d, cfo_avg=%d\n",
				                     i, bb_cfo_trk->cfo_avg[i]);
		}

		/* Average of all entries */
		sta_cnt = (u8) halbb_ones_num_in_bitmap(active_entry, sizeof(active_entry) * 8);
		cfo_avg = HALBB_DIV(cfo_khz_all, (s32)sta_cnt);

		BB_DBG(bb, DBG_CFO_TRK, "s(32,2) Multi-sta cfo_acc=(%d), entry_cnt=(%d), avg_cfo=(%d)\n",
					cfo_khz_all, sta_cnt, cfo_avg);

		target_cfo = cfo_avg;

	}
#endif

	/* Method-3: Tp based cfo tracking: With Tp_wgt */
	BB_DBG(bb, DBG_CFO_TRK, "Throughput based average mode\n");
	cfo_tol = bb_cfo_trk->sta_cfo_tolerance;

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

		if (!bb->sta_exist[i])
			continue;

		sta = bb->phl_sta_info[i];
		macid = sta->macid;

		if (!is_sta_active(sta))
			continue;

		if ((dev->rfe_type >= 50) && (sta->macid == 0))
			continue;

		cfo_t = &sta->hal_sta->cfo_stat;

		sta_cnt++;

		if (cfo_t->cfo_cnt != 0) {
			cfo_t->cfo_avg =
				      HALBB_DIV(cfo_t->cfo_tail, (s32)cfo_t->cfo_cnt);
			active_entry_cnt++;
		} else { /* Linked, but no pkts received*/
			cfo_t->cfo_avg = cfo_t->pre_cfo_avg;
		}

		/* Calculate the cfo torlence window */
		if ((cfo_t->cfo_avg - cfo_tol) > max_cfo_lb)
			max_cfo_lb = cfo_t->cfo_avg - cfo_tol;
		if ((cfo_t->cfo_avg + cfo_tol) < min_cfo_ub)
			min_cfo_ub = cfo_t->cfo_avg + cfo_tol;

		cfo_khz_all += cfo_t->cfo_avg;

		/* Acc throuhgput of all entries */
		tp_all += cfo_t->tp;

		/* Multiple tp_wgt first*/
		cfo_khz_all_tp_wgt += cfo_t->cfo_avg * cfo_t->tp;

		BB_DBG(bb, DBG_CFO_TRK, "[%d] Macid=%d, cfo_avg=%d, tp=%d\n",
			  i, macid, cfo_t->cfo_avg, cfo_t->tp);

		cfo_t->pre_cfo_avg = cfo_t->cfo_avg;

		if (sta_cnt >= bb->hal_com->assoc_sta_cnt)
			break;
	}

	/* Average of all entries with tp_wgt */
	BB_DBG(bb, DBG_CFO_TRK, "Assoc. sta cnt(%d)\n", sta_cnt);
	BB_DBG(bb, DBG_CFO_TRK, "Active sta cnt(%d)\n", active_entry_cnt);

	/* Div. tp_all to normalize wgt*/
	cfo_avg =  HALBB_DIV(cfo_khz_all_tp_wgt, (s32)tp_all);

	BB_DBG(bb, DBG_CFO_TRK, "s(32,2) Multi-sta cfo with tp_wgt=(%d), avg_cfo=(%d)\n",
					   cfo_khz_all_tp_wgt, cfo_avg);

	/* The target cfo need inside the cfo torlence window*/
	BB_DBG(bb, DBG_CFO_TRK, "max_cfo_lb=%d, min_cfo_ub=%d\n",
					        max_cfo_lb, min_cfo_ub);

	if (max_cfo_lb <= min_cfo_ub) {
		BB_DBG(bb, DBG_CFO_TRK, "cfo torlence win. size = %d\n",
					       min_cfo_ub - max_cfo_lb);
		if (cfo_avg < max_cfo_lb) {
			BB_DBG(bb, DBG_CFO_TRK, "cfo_avg < win_lb\n");
			target_cfo = max_cfo_lb;
		} else if (cfo_avg > min_cfo_ub) {
			BB_DBG(bb, DBG_CFO_TRK, "cfo_avg > win_ub\n");
			target_cfo = min_cfo_ub;
		} else {
			target_cfo = cfo_avg;
		}
	} else { /* No intersection of multi-sta cfo torlence, avg. of all cfo of entries */
		BB_DBG(bb, DBG_CFO_TRK, "No intersection of cfo torlence windows\n");
		target_cfo = HALBB_DIV(cfo_khz_all, (s32)sta_cnt);
	}

	BB_DBG(bb, DBG_CFO_TRK, "s(32,2) Final target cfo=(%d)\n", target_cfo);

	return target_cfo;
}

void halbb_set_cfo_pause_val(struct bb_info *bb, u32 *val_buf, u8 val_len)
{
	if (val_len != 1) {
		BB_DBG(bb, DBG_CFO_TRK, "[Error][CFO]Need val_len=1\n");
		return;
	}
	BB_DBG(bb, DBG_CFO_TRK, "[%s] len=%d, val[0]=0x%x\n", __func__, val_len, val_buf[0]);

	halbb_set_crystal_cap(bb, (u8)(val_buf[0] & 0xff));
}

void
halbb_cfo_counter_rst(struct bb_info *bb)
{
	struct bb_cfo_trk_info *bb_cfo_trk = &bb->bb_cfo_trk_i;
	struct rtw_phl_stainfo_t *sta;
	struct rtw_cfo_info *cfo_t = NULL;
	u8 i, sta_cnt = 0;

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

		if (!bb->sta_exist[i])
			continue;

		sta = bb->phl_sta_info[i];

		if (!is_sta_active(sta))
			continue;
		
		cfo_t = &sta->hal_sta->cfo_stat;

		cfo_t->cfo_tail = 0;
		cfo_t->cfo_cnt = 0;
		cfo_t->cfo_avg = 0;
		cfo_t->tp = 0;

		sta_cnt++;
		if (sta_cnt >= bb->hal_com->assoc_sta_cnt)
			break;
	}

	bb_cfo_trk->cfo_pkt_cnt = 0;
}

bool
halbb_cfo_trk_abort(struct bb_info *bb)
{
	struct bb_cfo_trk_info *cfo_trk = &bb->bb_cfo_trk_i;
	s32 cfo_avg = 0;

	/* support_ability */
	if (!(bb->support_ability & BB_CFO_TRK)) {
		BB_DBG(bb, DBG_CFO_TRK, "[%s] DISABLED\n", __func__);
		halbb_cfo_trk_reset(bb);
		return true;
	}

	if (bb->pause_ability & BB_CFO_TRK) {
		cfo_avg = halbb_avg_cfo_calc(bb);
		halbb_print_sign_frac_digit(bb, cfo_avg, 32, 2, bb->dbg_buf, HALBB_SNPRINT_SIZE);
		
		BB_DBG(bb, DBG_CFO_TRK, "Return: Pause CFO_TRK in LV=%d\n",
		       bb->pause_lv_table.lv_cfo);
		
		BB_DBG(bb, DBG_CFO_TRK, "Xcap=0x%x, cfo_avg=%s\n",
		       cfo_trk->crystal_cap, bb->dbg_buf);

		halbb_cfo_counter_rst(bb);
		return true;
	}

	return false;
}

void halbb_cfo_trk(struct bb_info *bb, s32 curr_cfo)
{
	struct bb_cfo_trk_info *bb_cfo_trk = &bb->bb_cfo_trk_i;
	struct bb_link_info *bb_link = &bb->bb_link_i;
	struct rtw_phl_com_t *phl = bb->phl_com;
	struct dev_cap_t *dev = &phl->dev_cap;

	if (halbb_cfo_trk_abort(bb))
		return;

	BB_DBG(bb, DBG_CFO_TRK, "[%s]\n", __func__);

	if (!bb_link->is_linked) {
		BB_DBG(bb, DBG_CFO_TRK, "is_linked=%d\n",bb_link->is_linked);

		halbb_cfo_trk_reset(bb); /*xcap to default value */
		return;
	}

	/* To avoid NIC soft-AP going to multi-sta cfo until AP side is verified for a long time*/
	if ((!bb_link->is_one_entry_only) && (dev->rfe_type < 50)) {
		BB_DBG(bb, DBG_CFO_TRK, "rfe_type=%d, is_one_entry_only=%d\n",
				     dev->rfe_type, bb_link->is_one_entry_only);

		halbb_cfo_trk_reset(bb); /*xcap to default value */
		return;
	}

	if (bb_link->first_connect)
		return;

	if (curr_cfo == 0) {
		BB_DBG(bb, DBG_CFO_TRK, "curr_cfo=0\n");
		return;
	}

	halbb_crystal_cap_adjust(bb, curr_cfo); /*Decide xcap need to adjust or not */
}

void halbb_cfo_ul_ofdma_acc_enable(struct bb_info *bb)
{
	struct bb_cfo_trk_info *bb_cfo_trk = &bb->bb_cfo_trk_i;

	BB_DBG(bb, DBG_CFO_TRK, "[%s]\n", __func__);
	bb_cfo_trk->bb_cfo_trk_acc_mode = CFO_ACC_MODE_1;
}

/*need to call before cfo_trk_init for embedded system*/
void halbb_cfo_ul_ofdma_acc_disable(struct bb_info *bb)
{
	struct bb_cfo_trk_info *bb_cfo_trk = &bb->bb_cfo_trk_i;

	BB_DBG(bb, DBG_CFO_TRK, "[%s]\n", __func__);
	if (bb==NULL)
	{
		BB_DBG(bb, DBG_CFO_TRK, "cfo_ul_ofdma_acc_disable fail !\n");
		return;
	}
	bb_cfo_trk->bb_cfo_trk_acc_mode = CFO_ACC_MODE_0;
}

bool halbb_cfo_acc_mode_en(struct bb_info *bb)
{
	struct bb_cfo_trk_info *cfo_trk = &bb->bb_cfo_trk_i;
	struct bb_link_info *link = &bb->bb_link_i;
	struct rtw_phl_com_t *phl = bb->phl_com;
	struct dev_cap_t *dev = &phl->dev_cap;
	struct rtw_phl_stainfo_t *sta;
	u32 i = 0, cfo_tf_cnt, cfo_tf_cnt_cur = 0;
	bool is_ul_ofdma = false;

	if (!cfo_trk->cfo_dyn_acc_en)
		return false;

	for (i = 0; i < PHL_MAX_STA_NUM; i++) {
		if (!bb->sta_exist[i])
			continue;

		sta = bb->phl_sta_info[i];

		if (!is_sta_active(sta))
			continue;

		if (sta->macid == 0)
			continue;

		cfo_tf_cnt_cur += sta->stats.rx_tf_cnt;
		BB_DBG(bb, DBG_COMMON_FLOW, "[%d] macid=%d\n", i, sta->macid);
	}

	cfo_tf_cnt = cfo_tf_cnt_cur - cfo_trk->cfo_tf_cnt_pre;

	if (cfo_tf_cnt > cfo_trk->cfo_tf_cnt_th)
		is_ul_ofdma = true;

	// Check TP, switch compensation period
	switch (cfo_trk->bb_cfo_trk_state) {
	case CFO_STATE_0:
		if (link->total_tp >= CFO_TP_UPPER) {
			cfo_trk->bb_cfo_trk_state = CFO_STATE_1;
			cfo_trk->cfo_trig_by_timer_en = true;
			// cfo_trk speed up
			//cfo_trk->cfo_timer_i.cb_time = CFO_COMP_PERIOD;
			halbb_cfo_acc_io_en(bb);
		}
		break;

	case CFO_STATE_1:
		if (cfo_trk->bb_cfo_trk_cnt >= cfo_trk->cfo_period_cnt) {
			if (cfo_trk->bb_cfo_trk_acc_mode==CFO_ACC_MODE_0) {
				cfo_trk->cfo_trig_by_timer_en = false;
				cfo_trk->bb_cfo_trk_state = CFO_STATE_2;
			}
			else if (!is_ul_ofdma) {
				cfo_trk->cfo_trig_by_timer_en = false;
				cfo_trk->bb_cfo_trk_state = CFO_STATE_2;
			}
		}
		else
			cfo_trk->bb_cfo_trk_cnt++;

		if (link->total_tp <= CFO_TP_LOWER) {
			cfo_trk->bb_cfo_trk_state = CFO_STATE_0;
			cfo_trk->bb_cfo_trk_cnt = 0;
			cfo_trk->cfo_trig_by_timer_en = false;
		}
		break;

	case CFO_STATE_2:
		if ((cfo_trk->bb_cfo_trk_acc_mode==CFO_ACC_MODE_1) && (is_ul_ofdma)) {
			cfo_trk->bb_cfo_trk_state = CFO_STATE_1;
			cfo_trk->cfo_trig_by_timer_en = true;
			halbb_cfo_acc_io_en(bb);
		}

		if (link->total_tp <= CFO_TP_LOWER) {
			cfo_trk->bb_cfo_trk_state = CFO_STATE_0;
			cfo_trk->bb_cfo_trk_cnt = 0;
			cfo_trk->cfo_trig_by_timer_en = false;
		}
		break;

	default:
		cfo_trk->bb_cfo_trk_state = CFO_STATE_0;
		cfo_trk->bb_cfo_trk_cnt = 0;
		break;
	}
	BB_DBG(bb, DBG_CFO_TRK, "[CFO_COMP] WD, total_tp = %d, cfo_trk_state = %d, timer_en = %d, trk_cnt = %d, is_ul_ofdma = %d\n",
		link->total_tp, cfo_trk->bb_cfo_trk_state, cfo_trk->cfo_trig_by_timer_en, cfo_trk->bb_cfo_trk_cnt, is_ul_ofdma);
	BB_DBG(bb, DBG_CFO_TRK, "[CFO_COMP] WD, cfo_trk_acc_mode = %d, cfo_tf_cnt_cur = %d, cfo_tf_cnt_pre = %d\n",
		cfo_trk->bb_cfo_trk_acc_mode, cfo_tf_cnt_cur, cfo_trk->cfo_tf_cnt_pre);
	BB_DBG(bb, DBG_CFO_TRK, "[CFO_COMP] WD, cfo_tf_cnt = %d, cfo_tf_cnt_th = %d \n",
		cfo_tf_cnt, cfo_trk->cfo_tf_cnt_th);

	cfo_trk->cfo_tf_cnt_pre = cfo_tf_cnt_cur;
	return cfo_trk->cfo_trig_by_timer_en;
}

void halbb_cfo_dm(struct bb_info *bb)
{
	struct bb_cfo_trk_info *cfo_trk = &bb->bb_cfo_trk_i;
	struct bb_link_info *bb_link = &bb->bb_link_i;
	struct rtw_phl_com_t *phl = bb->phl_com;
	struct dev_cap_t *dev = &phl->dev_cap;
	bool x_cap_update = false;
	u8 pre_x_cap = cfo_trk->crystal_cap;
	s32 new_cfo = 0;
	//s8 xtal_th_ofst;
	//u8 thermal;

	BB_DBG(bb, DBG_CFO_TRK, "[%s]\n", __func__);

#if 0
	rtw_hal_rf_xtal_tracking_offset(bb->hal_com, &xtal_th_ofst);
	rtw_hal_rf_get_thermal(bb->hal_com, 0, &thermal);

	BB_DBG(bb, DBG_CFO_TRK, "xtal_th_ofst=%d, thermal=%d\n", xtal_th_ofst, thermal);
#endif

	/* No new packet */
	if (bb_link->is_linked && cfo_trk->cfo_pkt_cnt != 0) {
		/* NEED MODIFY*/
		if (bb_link->is_one_entry_only) {
			new_cfo = halbb_avg_cfo_calc(bb);
		} else {
			/* To avoid NIC soft-AP going to multi-sta cfo until AP side is verified for a long time*/
			if (dev->rfe_type >= 50) {
				/* Multi-sta cfo tracking -> calc. centroid cfo first*/
				new_cfo = halbb_multi_sta_avg_cfo_calc(bb);
			}
		}
	}

	halbb_cfo_trk(bb, new_cfo);

	cfo_trk->cfo_avg_pre = new_cfo;

	halbb_print_sign_frac_digit(bb, cfo_trk->cfo_avg_pre, 32, 2, bb->dbg_buf, HALBB_SNPRINT_SIZE);
	BB_DBG(bb, DBG_CFO_TRK, "cfo_avg=(%s) Khz\n", bb->dbg_buf);

	BB_DBG(bb, DBG_CFO_TRK, "X_cap {Default:0x%x} {Curr: 0x%x -> 0x%x}, x_cap_ofst=%d step\n",
	       cfo_trk->def_x_cap, pre_x_cap, cfo_trk->crystal_cap,
	       cfo_trk->x_cap_ofst);

	x_cap_update =  (cfo_trk->crystal_cap == pre_x_cap) ? false : true;

	BB_DBG(bb, DBG_CFO_TRK, "Xcap_up=%d\n", x_cap_update);

	if (x_cap_update) {
		if (IS_GREATER(new_cfo, 0))
			new_cfo -= cfo_trk->sw_comp_fine_tune;
		else
			new_cfo += cfo_trk->sw_comp_fine_tune;
	}
	halbb_digital_cfo_comp(bb, new_cfo);

	halbb_cfo_counter_rst(bb);
}

void halbb_cfo_watchdog(struct bb_info *bb)
{
	if (halbb_cfo_acc_mode_en(bb))
		return;

	BB_DBG(bb, DBG_CFO_TRK, "[%s]\n", __func__);
	halbb_cfo_dm(bb);
	BB_DBG(bb, DBG_CFO_TRK, "\n");
}

void halbb_parsing_cfo(struct bb_info *bb, u32 physts_bitmap,
		       struct physts_rxd *desc)
{
	struct bb_physts_info	*physts = &bb->bb_physts_i;
	struct bb_cfo_trk_info *bb_cfo_trk = &bb->bb_cfo_trk_i;
	struct bb_rate_info	*rate_info = &bb->bb_cmn_rpt_i.bb_rate_i;
	struct dev_cap_t *dev = &bb->phl_com->dev_cap;
	struct rtw_phl_stainfo_t *sta;
	struct rtw_cfo_info *cfo_t = NULL;
	s16 cfo;
	u8 fw_rate_idx = rate_info->fw_rate_idx;
	u8 bb_macid;

	if (!(physts_bitmap & BIT(IE01_CMN_OFDM) &&
	    physts->bb_physts_rslt_hdr_i.ie_map_type >= LEGACY_OFDM_PKT))
		return;

	if (bb_cfo_trk->cfo_trk_by_data_en) {
		if (!desc->user_i[0].is_data)
			return;
	}

	if (bb_cfo_trk->cfo_src == CFO_SRC_FD)
		cfo = physts->bb_physts_rslt_1_i.cfo_avg;
	else
		cfo = physts->bb_physts_rslt_1_i.cfo_pab_avg;

	if (desc->macid_su > PHL_MAX_STA_NUM)
		BB_WARNING("[%s] macid_su=%d\n", __func__, desc->macid_su);

	bb_macid = bb->phl2bb_macid_table[desc->macid_su];

	if (bb_macid > PHL_MAX_STA_NUM)
		BB_WARNING("[%s] bb_macid=%d\n", __func__, bb_macid);

	sta = bb->phl_sta_info[bb_macid];

	if (!is_sta_active(sta))
		return;

	if (sta->macid > PHL_MAX_STA_NUM)
		return;	

	if (!sta->hal_sta)
		return;

	cfo_t = &sta->hal_sta->cfo_stat;

	if ((dev->rfe_type >= 50) && (bb_macid == 0)) /* No need to cnt AP Rx boardcast pkt*/
		return;

	/*  CFO info. of all path from phy-status have been averaged */
	cfo_t->cfo_tail += cfo;
	cfo_t->cfo_cnt++;

	bb_cfo_trk->cfo_pkt_cnt++;

	/*BB_DBG(bb, DBG_CFO_TRK, "cfo_cnt[%d]=%d, all_cfo_cnt=%d\n", desc->macid_su, cfo_t->cfo_cnt, bb_cfo_trk->cfo_pkt_cnt);*/

	/* Calcute throughput from rx rate idx*/
	if (rate_info->mode == BB_HE_MODE) {
		/* HE[3.2] = VHT[LGI]x1.25*/
		cfo_t->tp +=
		((bb_phy_rate_table[fw_rate_idx - MAX_RATE_VHT - MAX_RATE_HT] << 2)
		+ bb_phy_rate_table[fw_rate_idx - MAX_RATE_VHT - MAX_RATE_HT]) >> 2;
	} else if (rate_info->mode == BB_VHT_MODE) {
		cfo_t->tp += bb_phy_rate_table[fw_rate_idx - MAX_RATE_HT];
	} else {
		cfo_t->tp +=  bb_phy_rate_table[fw_rate_idx];
	}
}


void halbb_cfo_trk_dbg(struct bb_info *bb, char input[][16], u32 *_used,
			      char *output, u32 *_out_len)
{
	struct bb_cfo_trk_info *bb_cfo_trk = &bb->bb_cfo_trk_i;
	u32 var[10] = {0};
	bool	timer_en_pre;

	if (_os_strcmp(input[1], "-h") == 0) {
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "src {0:fd, 1:preamble}\n");
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "data_only {en}\n");
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "dyn_acc {en}\n");
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "set Xcap: {1} {val}\n");
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "show Xcap: {100}\n");
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "th {en, stop, 0~3} {s(8,2)}\n");
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "tb_comp {s(8,2)}\n");
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "sw_comp {Xcap_enable_th (kHz)} {Xcap_stop_th (kHz)} {sw_comp_fine_tune (kHz)}\n");
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "acc {en} {ms} {period_cnt}\n");
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "acc_mode {0: disable_ul_ofmda_acc, 1: enable_ul_ofdma_acc} {tf_cnt_th}\n");
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "set_mode {multi-sta cfo_trk mode - 0:Pkts averaged mode, 1: Entry averaged mode, 2: TP based mode}\n");
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "cfo_tol {manually adjust hypothetical sta_cfo_tolerance in decimal kHz}\n");
		return;
	}

	HALBB_SCAN(input[1], DCMD_DECIMAL, &var[0]);
	if (var[0] == 1) {
		HALBB_SCAN(input[2], DCMD_HEX, &var[1]);
		halbb_set_crystal_cap(bb, (u8)var[1]);
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "Set X_cap=0x%x\n", bb_cfo_trk->crystal_cap);
	} else if (var[0] == 100) {
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "X_cap=0x%x\n", bb_cfo_trk->crystal_cap);
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "Xcap_enable_th = %d (kHz)\n", bb_cfo_trk->cfo_th_en >> 2);
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "Xcap_stop_th = %d (kHz)\n", bb_cfo_trk->cfo_th_stop >> 2);
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "sw_comp_fine_tune = %d (kHz)\n", bb_cfo_trk->sw_comp_fine_tune >> 2);

	} else if (_os_strcmp(input[1], "data_only") == 0) {
		HALBB_SCAN(input[2], DCMD_DECIMAL, &var[0]);
		
		bb_cfo_trk->cfo_trk_by_data_en = (bool)var[0];
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "cfo_trk_by_data_en: %d\n", bb_cfo_trk->cfo_trk_by_data_en);
	} else if (_os_strcmp(input[1], "dyn_acc") == 0) {
		HALBB_SCAN(input[2], DCMD_DECIMAL, &var[0]);
		
		bb_cfo_trk->cfo_dyn_acc_en = (bool)var[0];
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "cfo_dyn_acc_en: %d\n", bb_cfo_trk->cfo_dyn_acc_en);
	} else if (_os_strcmp(input[1], "src") == 0) {
		HALBB_SCAN(input[2], DCMD_DECIMAL, &var[0]);

		bb_cfo_trk->cfo_src = (enum bb_cfo_trk_src_t)var[0];
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "cfo_src: %s\n",
			    ((bb_cfo_trk->cfo_src == CFO_SRC_FD) ? "FD" : "PAB"));
	} else if (_os_strcmp(input[1], "acc") == 0) {
		HALBB_SCAN(input[2], DCMD_DECIMAL, &var[0]);
		HALBB_SCAN(input[3], DCMD_DECIMAL, &var[1]);
		HALBB_SCAN(input[4], DCMD_DECIMAL, &var[2]);

		timer_en_pre = bb_cfo_trk->cfo_trig_by_timer_en;
		bb_cfo_trk->cfo_trig_by_timer_en = (bool)var[0];

		if (var[1] > 2000)
			bb_cfo_trk->cfo_timer_i.cb_time = 2000;
		else if (var[1] < 5)
			bb_cfo_trk->cfo_timer_i.cb_time = 5;
		else
			bb_cfo_trk->cfo_timer_i.cb_time = (u16)var[1];

		bb_cfo_trk->cfo_period_cnt = (bool)var[2];
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "timer=%d ms, period_cnt=%d cnt, trig_by_timer_en=%d\n",
			    bb_cfo_trk->cfo_timer_i.cb_time, bb_cfo_trk->cfo_period_cnt, bb_cfo_trk->cfo_trig_by_timer_en);

		if (!timer_en_pre && bb_cfo_trk->cfo_trig_by_timer_en) {
			BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "[Start callback]\n");
			halbb_cfo_acc_io_en(bb);
		}
	}else if (_os_strcmp(input[1], "acc_mode") == 0) {
		HALBB_SCAN(input[2], DCMD_DECIMAL, &var[0]);
		HALBB_SCAN(input[3], DCMD_DECIMAL, &var[1]);

		bb_cfo_trk->bb_cfo_trk_acc_mode= (enum bb_cfo_trk_acc_mode_t)var[0] ;
		bb_cfo_trk->cfo_tf_cnt_th= var[1] ;

		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "cfo_trk_acc_mode = %d, cfo_tf_cnt_th = %d\n",
			    bb_cfo_trk->bb_cfo_trk_acc_mode,bb_cfo_trk->cfo_tf_cnt_th);
	} else if (_os_strcmp(input[1], "th") == 0) {

		HALBB_SCAN(input[3], DCMD_DECIMAL, &var[1]);

		if (_os_strcmp(input[2], "en") == 0) {
			bb_cfo_trk->cfo_th_en = (u8)var[1];
		} else if (_os_strcmp(input[2], "stop") == 0) {
			bb_cfo_trk->cfo_th_stop = (u8)var[1];
		} else {
			HALBB_SCAN(input[2], DCMD_DECIMAL, &var[0]);
			if (var[0] < CFO_TRK_TH_SIZE)
				bb_cfo_trk->cfo_th[var[0]] = (u8)var[1];
			else
				BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
					    "Set Err\n");
		}
		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "TH[en, stop]={%d, %d}, TH[3:0]={%d, %d, %d, %d}\n",
			    bb_cfo_trk->cfo_th_en, bb_cfo_trk->cfo_th_stop,
		            bb_cfo_trk->cfo_th[3], bb_cfo_trk->cfo_th[2],
		            bb_cfo_trk->cfo_th[1], bb_cfo_trk->cfo_th[0]);

	} else if (_os_strcmp(input[1], "tb_comp") == 0) {
		HALBB_SCAN(input[2], DCMD_DECIMAL, &var[0]);

		bb_cfo_trk->tb_tx_comp_cfo_th = (u8)var[0];

		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "tb_tx_comp_cfo_th = %d\n",
			    bb_cfo_trk->tb_tx_comp_cfo_th);
	} else if (_os_strcmp(input[1], "sw_comp") == 0) {
		HALBB_SCAN(input[2], DCMD_DECIMAL, &var[0]);
		HALBB_SCAN(input[3], DCMD_DECIMAL, &var[1]);
		HALBB_SCAN(input[4], DCMD_DECIMAL, &var[2]);

		bb_cfo_trk->cfo_th_en = (u8)var[0] << 2;
		bb_cfo_trk->cfo_th_stop = (u8)var[1] << 2;
		bb_cfo_trk->sw_comp_fine_tune = (u8)var[2] << 2;

		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "xcap_enable_th = %d,xcap_stop_th = %d,sw_comp_fine_tune = %d\n",
			    bb_cfo_trk->cfo_th_en >> 2,bb_cfo_trk->cfo_th_stop >> 2, bb_cfo_trk->sw_comp_fine_tune >> 2);
	} else if (_os_strcmp(input[1], "set_mode") == 0) {
		HALBB_SCAN(input[2], DCMD_DECIMAL, &var[0]);

		bb_cfo_trk->multi_sta_cfo_mode= (u8)var[0];

		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "multi_sta_cfo_mode = %d\n",
			    bb_cfo_trk->multi_sta_cfo_mode);
	} else if (_os_strcmp(input[1], "cfo_tol") == 0) {
		HALBB_SCAN(input[2], DCMD_DECIMAL, &var[0]);
		bb_cfo_trk->man_cfo_tol= true;
		bb_cfo_trk->sta_cfo_tolerance = (u8)var[0];

		BB_DBG_CNSL(*_out_len, *_used, output + *_used, *_out_len - *_used,
			    "manually adjust hypothetical sta_cfo_tolerance = %d\n",
			    bb_cfo_trk->sta_cfo_tolerance);
	} 
}

void halbb_cr_cfg_cfo_trk_init(struct bb_info *bb)
{
	struct bb_cfo_trk_cr_info *cr = &bb->bb_cfo_trk_i.bb_cfo_trk_cr_i;

	switch (bb->cr_type) {

	#ifdef HALBB_COMPILE_AP_SERIES
	case BB_AP:
		cr->r_cfo_comp_seg0_312p5khz = CFO_COMP_SEG0_312P5KHZ_0_A;
		cr->r_cfo_comp_seg0_312p5khz_m = CFO_COMP_SEG0_312P5KHZ_0_A_M;
		cr->r_cfo_comp_seg0_vld = CFO_COMP_SEG0_VLD_0_A;
		cr->r_cfo_comp_seg0_vld_m = CFO_COMP_SEG0_VLD_0_A_M;
		cr->r_cfo_wgting = CFO_WGTING_A;
		cr->r_cfo_wgting_m = CFO_WGTING_A_M;

		break;

	#endif
	#ifdef HALBB_COMPILE_CLIENT_SERIES
	case BB_CLIENT:
		cr->r_cfo_comp_seg0_312p5khz = CFO_COMP_SEG0_312P5KHZ_0_C;
		cr->r_cfo_comp_seg0_312p5khz_m = CFO_COMP_SEG0_312P5KHZ_0_C_M;
		cr->r_cfo_comp_seg0_vld = CFO_COMP_SEG0_VLD_0_C;
		cr->r_cfo_comp_seg0_vld_m = CFO_COMP_SEG0_VLD_0_C_M;
		cr->r_cfo_wgting = CFO_WGTING_C;
		cr->r_cfo_wgting_m = CFO_WGTING_C_M;

		break;
	#endif

	#ifdef HALBB_COMPILE_AP2_SERIES
	case BB_AP2:
		cr->r_cfo_comp_seg0_312p5khz = CFO_COMP_SEG0_312P5KHZ_0_A2;
		cr->r_cfo_comp_seg0_312p5khz_m = CFO_COMP_SEG0_312P5KHZ_0_A2_M;
		cr->r_cfo_comp_seg0_vld = CFO_COMP_SEG0_VLD_0_A2;
		cr->r_cfo_comp_seg0_vld_m = CFO_COMP_SEG0_VLD_0_A2_M;
		cr->r_cfo_wgting = CFO_WGTING_A2;
		cr->r_cfo_wgting_m = CFO_WGTING_A2_M;

		break;

	#endif
	
	default:
		break;
	}

}

#endif
