/******************************************************************************
 *
 * 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_BTC_C_
#include "../hal_headers_le.h"
#include "hal_btc.h"
#include "halbtc_fw.h"
#include "halbtc_def.h"
#include "halbtc_action.h"

#ifdef CONFIG_BTCOEX

/* [31:24] -> main-version
 * [23:16] -> sub-version
 * [15:8]  -> Hot-Fix-version
 * [7:0]   -> branch ID, ex: 0x00-> Main-Branch
 * Modify bt_8852x.c chip_8852x member: btcx_desired, wlcx_desired if required
 * btcx_desired: BT FW coex version -> main-version + 1 if update.
 * wlcx_desired: WL FW coex version -> sub-version + 1 if update
 */
const u32 coex_ver = 0x0601000f;

static struct btc_ops _btc_ops = {
	_send_fw_cmd,
	_ntfy_power_on,
	_ntfy_power_off,
	_ntfy_init_coex,
	_ntfy_scan_start,
	_ntfy_scan_finish,
	_ntfy_switch_band,
	_ntfy_specific_packet,
	_ntfy_role_info,
	_ntfy_radio_state,
	_ntfy_customerize,
	_ntfy_wl_rfk,
	_ntfy_wl_sta,
	_ntfy_fwinfo,
	_ntfy_timer
};

#define _update_dbcc_band(phy_idx) \
	btc->cx.wl.dbcc_info.real_band[phy_idx] =\
	(btc->cx.wl.scan_info.phy_map & BIT(phy_idx) ?\
	btc->cx.wl.dbcc_info.scan_band[phy_idx] :\
	btc->cx.wl.dbcc_info.op_band[phy_idx])

/******************************************************************************
 *
 * coex internal functions
 *
 *****************************************************************************/
static void _set_btc_timer(struct btc_t *btc, u16 tmr_id, u32 ms)
{
	struct btc_tmr *btmr = NULL;

	if (tmr_id < BTC_TIMER_MAX) {
		btmr = &btc->timer[tmr_id];
		_os_set_timer(halcom_to_drvpriv(btc->hal), &btmr->tmr, ms);
	}
}

static void _btmr_stop(struct btc_t *btc)
{
	struct btc_tmr *btmr = NULL;
	u8 i = 0;

	PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_, "[BTC], %s(): stop btc timers!!\n",
		  __func__);

	btc->tmr_stop = true;

	for (i = 0; i < BTC_TIMER_MAX; i++) {
		btmr = &btc->timer[i];
		_os_cancel_timer(halcom_to_drvpriv(btc->hal), &btmr->tmr);
	}
	_os_cancel_timer(halcom_to_drvpriv(btc->hal), &btc->delay_tmr);
}

static void _btmr_release(struct btc_t *btc)
{
	struct btc_tmr *btmr = NULL;
	u8 i = 0;

	PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_, "[BTC], %s(): release btc timers!!\n",
		  __func__);

	for (i = 0; i < BTC_TIMER_MAX; i++) {
		btmr = &btc->timer[i];
		_os_release_timer(halcom_to_drvpriv(btc->hal), &btmr->tmr);
	}
	_os_release_timer(halcom_to_drvpriv(btc->hal), &btc->delay_tmr);
}

static void _btmr_start(struct btc_t *btc)
{
	PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_, "[BTC], %s(): start btc periodic timer!!\n",
		  __func__);

	btc->tmr_stop = false;

	/* Wait 2 sec for phl starting then periodic timer will be started */
	_os_set_timer(halcom_to_drvpriv(btc->hal),
			&btc->delay_tmr, BTC_DELAYED_PERIODIC_TIME);
}

static void _delay_tmr_cb(void *ctx)
{
	struct btc_t *btc = NULL;

	PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_, "[BTC], %s() !!\n", __func__);

	if (!ctx)
		return;

	btc = (struct btc_t *)ctx;

	_set_btc_timer(btc, BTC_TIMER_PERIODIC, BTC_PERIODIC_TIME);
}

static void _btmr_cb(void *ctx)
{
	struct btc_tmr *btmr = NULL;
	struct btc_t *btc = NULL;

	PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_, "[BTC], %s(), ctx = 0x%p !!\n",
		  __func__, ctx);

	if (!ctx)
		return;

	btmr = (struct btc_tmr *)ctx;
	btc = (struct btc_t *)btmr->btc;

	if (!btc->tmr_init || btc->tmr_stop)
		return;

	hal_btc_send_event(btc, (u8 *)btmr, sizeof(struct btc_tmr *),
			   BTC_HMSG_TMR_EN);
}

static void _btmr_init(struct btc_t *btc)
{
	struct btc_tmr *btmr = NULL;
	u8 i = 0;

	PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_, "[BTC], %s(): !!\n", __func__);

	for (i = 0; i < BTC_TIMER_MAX; i++) {
		btmr = &btc->timer[i];
		btmr->btc = btc;
		btmr->id = i;
		PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_,
			  "[BTC], init btc timer(%d) = 0x%p !!\n", i, btmr);
		_os_init_timer(halcom_to_drvpriv(btc->hal), &btmr->tmr,
			       _btmr_cb, btmr, "btc_tmr");
	}

	_os_init_timer(halcom_to_drvpriv(btc->hal), &btc->delay_tmr,
			       _delay_tmr_cb, btc, NULL);

	btc->tmr_init = true;
	btc->tmr_stop = true;
}

static void _btmr_deinit(struct btc_t *btc)
{
	if (btc->tmr_init) {
		_btmr_stop(btc);
		_btmr_release(btc);
		btc->tmr_init = false;
	}
}

static void
_send_fw_cmd(struct btc_t *btc, u8 h2c_class, u8 h2c_func, u8 *param, u16 len)
{
	struct rtw_hal_com_t *h = btc->hal;
	struct btc_wl_info *wl = &btc->cx.wl;
	struct rtw_g6_h2c_hdr hdr = {0};

	hdr.h2c_class = h2c_class;
	hdr.h2c_func = h2c_func;
	hdr.type = H2CB_TYPE_DATA;
	hdr.content_len = len;
	hdr.done_ack = 1;

	if (!wl->status.map.init_ok) {
		PHL_TRACE(COMP_PHL_BTC, _PHL_ERR_, "[BTC], %s(): return by btc not init!!\n",
			  __func__);
		btc->fwinfo.cnt_h2c_fail++;
		return;
	} else if ((wl->status.map.rf_off_pre == 1 &&
		   wl->status.map.rf_off == 1) ||
		   (wl->status.map.lps_pre == 1 &&
		   wl->status.map.lps == 1)) {
		PHL_TRACE(COMP_PHL_BTC, _PHL_ERR_, "[BTC], %s(): return by wl off!!\n",
			  __func__);
		btc->fwinfo.cnt_h2c_fail++;
		return;
	}

	btc->fwinfo.cnt_h2c++;

	if (rtw_hal_mac_send_h2c(h, &hdr, (u32 *)param) != 0)
		btc->fwinfo.cnt_h2c_fail++;
}

u32 _read_cx_reg(struct btc_t *btc, u32 offset)
{
	u32 val = 0;
	rtw_hal_mac_coex_reg_read(btc->hal, offset, &val);
	return val;
}

u8 _read_cx_ctrl(struct btc_t *btc)
{
	u32 val = 0;
	rtw_hal_mac_get_coex_ctrl(btc->hal, &val);

	return ((u8)val);
}

u32 _read_scbd(struct btc_t *btc)
{
	const struct btc_chip *chip = btc->chip;
	u32 scbd_val = 0;

	if (!chip->scbd)
		return 0;

	rtw_hal_mac_get_scoreboard(btc->hal, &scbd_val);

	PHL_INFO("[BTC], read scbd : 0x%08x \n", scbd_val);
	btc->cx.cnt_bt[BTC_BCNT_SCBDREAD]++;
	return (scbd_val);
}

void _write_scbd(struct btc_t *btc, u32 val, bool state)
{
	const struct btc_chip *chip = btc->chip;
	struct btc_wl_info *wl = &btc->cx.wl;
	u32 scbd_val = 0;

	if (!chip->scbd)
		return;

	/* only use bit23~0 */
	scbd_val = (state ? (wl->scbd | val) : (wl->scbd & (~val)));

	if (scbd_val != wl->scbd) {
		rtw_hal_mac_set_scoreboard(btc->hal, &scbd_val);
		PHL_INFO("[BTC], write scbd : 0x%08x \n", scbd_val);
		wl->scbd = scbd_val;

		btc->cx.cnt_wl[BTC_WCNT_SCBDUPDATE]++;
	}
}

static u8
_update_rssi_state(struct btc_t *btc, u8 pre_state, u8 rssi, u8 thresh)
{
	u8 next_state, tol = btc->chip->rssi_tol;

	if (pre_state == BTC_RSSI_ST_LOW ||
	    pre_state == BTC_RSSI_ST_STAY_LOW) {
		if (rssi >= (thresh + tol))
			next_state = BTC_RSSI_ST_HIGH;
		else
			next_state = BTC_RSSI_ST_STAY_LOW;
	} else {
		if (rssi < thresh)
			next_state = BTC_RSSI_ST_LOW;
		else
			next_state = BTC_RSSI_ST_STAY_HIGH;
	}

	return next_state;
}

void _write_bt_reg(struct btc_t *btc, u8 reg_type, u16 addr, u32 val)
{
	u8 buf[4] = {0};

	/* set write address */
	buf[0] = reg_type;
	buf[1] = addr & bMASKB0;
	buf[2] = (addr & bMASKB1) >> 8;
	hal_btc_fw_set_bt(btc, SET_BT_WREG_ADDR, 3, buf);

	/* set write value */
	buf[0] = val & bMASKB0;
	buf[1] = (val & bMASKB1) >> 8;
	buf[2] = (val & bMASKB2) >> 16;
	buf[3] = (val & bMASKB3) >> 23;
	hal_btc_fw_set_bt(btc, SET_BT_WREG_VAL, 4, buf);
}

void _read_bt_reg(struct btc_t *btc, u8 reg_type, u16 addr)
{
	/* this function is only for API call.
	 * If BTC should use hal_btc_fw_set_monreg to read bt reg.
	 */
	u8 buf[3] = {0};

	/* set write address */
	buf[0] = reg_type;
	buf[1] = addr & bMASKB0;
	buf[2] = (addr & bMASKB1) >> 8;
	hal_btc_fw_set_bt(btc, SET_BT_RREG_ADDR, sizeof(buf), buf);

	/* To do wait FW event -> BTF_EVNT_BT_REG*/
}

void _set_bt_psd_report(struct btc_t *btc, u8 start_idx, u8 rpt_type)
{
	u8 buf[2] = {0};

	PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_, "[BTC], %s(): set bt psd\n",
		  __func__);

	buf[0] = start_idx;
	buf[1] = rpt_type;
	hal_btc_fw_set_bt(btc, SET_BT_PSD_REPORT, 2, buf);
}

static void _set_bt_info_report(struct btc_t *btc, u8 trigger)
{
	u8 buf = 0;

	PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_, "[BTC], %s(): query bt info\n",
		  __func__);

	buf = trigger;
	hal_btc_fw_set_bt(btc, SET_BT_INFO_REPORT, 1, &buf);
}

static void _reset_btc_var(struct btc_t *btc, u8 type)
{
	struct rtw_hal_com_t *h = btc->hal;
	struct btc_cx *cx = &btc->cx;
	struct btc_wl_info *wl = &cx->wl;
	struct btc_bt_info *bt = &cx->bt;
	struct btc_bt_link_info *bt_linfo = &bt->link_info;
	struct btc_wl_link_info *wl_linfo = wl->link_info;
	u8 i;

	PHL_INFO("[BTC], %s()\n", __func__);

	/* Reset Coex variable */
	if (type & BTC_RESET_CX)
		hal_mem_set(h, cx, 0, sizeof(struct btc_cx));
	else if (type & BTC_RESET_BTINFO) /* only for BT enable */
		hal_mem_set(h, bt, 0, sizeof(struct btc_bt_info));

	if (type & BTC_RESET_CTRL)
		hal_mem_set(h, &btc->ctrl, 0, sizeof(struct btc_ctrl));

	/* Init Coex variables that are not zero */
	if (type & BTC_RESET_DM) {
		hal_mem_set(h, &btc->dm, 0, sizeof(struct btc_dm));
		hal_mem_set(h, bt_linfo->rssi_state, 0, BTC_BT_RSSI_THMAX);

		for (i = 0; i < MAX_WIFI_ROLE_NUMBER; i++)
			hal_mem_set(h, wl_linfo[i].rssi_state, 0,
				    BTC_WL_RSSI_THMAX);

		/* set the slot_now table to original */
		_tdma_cpy(&btc->dm.tdma_now, &t_def[CXTD_OFF]);
		_tdma_cpy(&btc->dm.tdma, &t_def[CXTD_OFF]);
		_slots_cpy(btc->dm.slot_now, s_def);
		_slots_cpy(btc->dm.slot, s_def);
		btc->policy_len = 0;
		btc->bt_req_len = 0;
		btc->hubmsg_cnt = 0;

		btc->dm.coex_info_map = BTC_COEX_INFO_ALL;
		btc->dm.wl_tx_limit.tx_time = BTC_MAX_TX_TIME_DEF;
		btc->dm.wl_tx_limit.tx_retry = BTC_MAX_TX_RETRY_DEF;
	}

	if (type & BTC_RESET_MDINFO)
		hal_mem_set(h, &btc->mdinfo, 0, sizeof(struct btc_module));
}

static bool _chk_wl_rfk_request(struct btc_t *btc)
{
	struct btc_cx *cx = &btc->cx;
	struct btc_bt_info *bt = &cx->bt;

	_update_bt_scbd(btc, true);

	cx->cnt_wl[BTC_WCNT_RFK_REQ]++;

	if ((bt->rfk_info.map.run || bt->rfk_info.map.req) &&
	    (!bt->rfk_info.map.timeout)) {
		cx->cnt_wl[BTC_WCNT_RFK_REJECT]++;
		return BTC_WRFK_REJECT;
	} else {
		cx->cnt_wl[BTC_WCNT_RFK_GO]++;
		return BTC_WRFK_ALLOW;
	}
}

void _set_init_info(struct btc_t *btc)
{
	struct btc_dm *dm = &btc->dm;
	struct btc_wl_info *wl = &btc->cx.wl;

	dm->init_info.wl_only = (u8)dm->wl_only;
	dm->init_info.bt_only = (u8)dm->bt_only;
	dm->init_info.wl_init_ok = (u8)wl->status.map.init_ok;
	dm->init_info.dbcc_en = btc->hal->dbcc_en;
	dm->init_info.cx_other = btc->cx.other.type;
	dm->init_info.wl_guard_ch = btc->chip->afh_guard_ch;
	dm->init_info.module = btc->mdinfo;
}

u8 _get_role_link_mode(struct btc_t *btc, u8 role)
{
	switch(role) {
	case PHL_RTYPE_STATION:
		return BTC_WLINK_2G_STA;
	case PHL_RTYPE_P2P_GO:
		return BTC_WLINK_2G_GO;
	case PHL_RTYPE_P2P_GC:
		return BTC_WLINK_2G_GC;
	case PHL_RTYPE_AP:
		return BTC_WLINK_2G_AP;
	default:
		return BTC_WLINK_OTHER;
	}
}

u8 _get_wl_role_idx(struct btc_t *btc, u8 role)
{
	struct btc_wl_role_info *wl_rinfo = &btc->cx.wl.role_info;
	u8 i, pid = 0;

	for (i = 0; i < MAX_WIFI_ROLE_NUMBER; i++) {
		if (wl_rinfo->active_role[i].role == role)
			break;
	}

	pid = i;
	return pid; /*cation: return MAX_WIFI_ROLE_NUMBER if role not found */
}

void _set_wl_req_mac(struct btc_t *btc, u8 mac_id)
{
	struct btc_wl_info *wl = &btc->cx.wl;
	struct btc_chip_ops *ops = btc->chip->ops;

	if (mac_id == wl->pta_req_mac)
		return;

	if (ops && ops->wl_req_mac)
		ops->wl_req_mac(btc, mac_id);

	wl->pta_req_mac = mac_id;
}

static bool _chk_role_ch_group(struct btc_t *btc, struct rtw_chan_def r1, struct rtw_chan_def r2)
{
	bool is_grouped = false;

	if (r1.chan != r2.chan) {
		/* primary ch is different */
		goto exit;
	} else if (r1.bw == CHANNEL_WIDTH_40 && r2.bw == CHANNEL_WIDTH_40) {
		if (r1.offset != r2.offset)
			goto exit;
	}

	is_grouped = true;

exit:
	return is_grouped;
}

static void _update_wl_info(struct btc_t *btc)
{
	struct btc_wl_info *wl = &btc->cx.wl;
	struct btc_wl_link_info *wl_linfo = wl->link_info;
	struct btc_wl_role_info *wl_rinfo = &wl->role_info;
	struct btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info;
	struct btc_wl_active_role *active_role = NULL;
	struct rtw_hal_com_t *h = btc->hal;
	struct rtw_chan_def cid_ch[MAX_WIFI_ROLE_NUMBER];
	u8 i, j, k, cnt_connect = 0, cnt_connecting = 0, cnt_active = 0;
	u8 cnt_2g = 0, cnt_5g = 0, max_role_cnt = BTC_TDMA_WLROLE_MAX;
	u8 cid_phy[MAX_WIFI_ROLE_NUMBER] = {0};
	u8 cid_role[MAX_WIFI_ROLE_NUMBER] = {0};
	u8 dbcc_2g_phy = 0, dbcc_2g_cid = 0, dbcc_2g_role = 0;
	u32 noa_duration = 0;
	bool b2g = false, b5g = false, client_joined = false, noa_exist = false;

	hal_mem_set(h, wl_rinfo, 0, sizeof(struct btc_wl_role_info));
	hal_mem_set(h, wl_dinfo, 0, sizeof(struct btc_wl_dbcc_info));
	hal_mem_set(h, cid_ch, 0, MAX_WIFI_ROLE_NUMBER * sizeof(struct rtw_chan_def));

	for (i = 0; i < MAX_WIFI_ROLE_NUMBER; i++) {
		/* check if role active? */
		if (!wl_linfo[i].active || wl_linfo[i].phy >= HW_PHY_MAX)
			continue;

		cnt_active++;
		active_role = &wl_rinfo->active_role[cnt_active-1];
		active_role->role = wl_linfo[i].role;
		active_role->pid = wl_linfo[i].pid;
		active_role->phy = wl_linfo[i].phy;
		active_role->band = wl_linfo[i].chdef.band;
		active_role->noa = (u8)wl_linfo[i].noa;
		active_role->noa_duration = wl_linfo[i].noa_duration/1000;

		/* check if role connect? */
		if (wl_linfo[i].connected == MLME_NO_LINK) {
			wl_rinfo->active_role[cnt_active-1].connected = 0;
			continue;
		} else if (wl_linfo[i].connected == MLME_LINKING) {
			cnt_connecting++;
		} else {
			cnt_connect++;
			/* only if client connect for p2p-Go/AP */
			if ((wl_linfo[i].role == PHL_RTYPE_P2P_GO ||
			     wl_linfo[i].role == PHL_RTYPE_AP) &&
			     wl_linfo[i].client_cnt > 1)
				client_joined = true;

			cid_phy[cnt_connect-1] = wl_linfo[i].phy;
			hal_mem_cpy(h, &cid_ch[cnt_connect-1],
				    &wl_linfo[i].chdef, sizeof(struct rtw_chan_def));

			cid_role[cnt_connect-1] = wl_linfo[i].role;
		}

		wl_rinfo->role_map.val |= BIT(wl_linfo[i].role);
		active_role->ch = wl_linfo[i].chdef.center_ch;
		active_role->bw = wl_linfo[i].chdef.bw;
		active_role->connected = 1;

		/* only one noa-role exist */
		if (active_role->noa && active_role->noa_duration > 0) {
			noa_exist = true;
			noa_duration = active_role->noa_duration;
		}

		/* Check dbcc role,the last role may overwrite the former role,
		 * This will be modified in the below code.
		 */
		if (h->dbcc_en) {
			wl_dinfo->role[wl_linfo[i].phy] = wl_linfo[i].role;
			wl_dinfo->op_band[wl_linfo[i].phy] = wl_linfo[i].chdef.band;
			max_role_cnt = BTC_TDMA_WLROLE_MAX + 1;
		}

		if (wl_linfo[i].chdef.band != BAND_ON_24G) {
			cnt_5g++;
			b5g = true;
		} else {
			cnt_2g++;
			b2g = true;
		}
	}

	wl_rinfo->connect_cnt = cnt_connect;

	/* Be careful to change the following sequence!! */
	if (cnt_connect == 0) {
		wl_rinfo->link_mode = BTC_WLINK_NOLINK;
		wl_rinfo->role_map.role.none = 1;
	} else if (!b2g && b5g) {
		wl_rinfo->link_mode = BTC_WLINK_5G;
	} else if (wl_rinfo->role_map.role.nan) {
		wl_rinfo->link_mode = BTC_WLINK_2G_NAN;
	} else if (cnt_connect > max_role_cnt) {
		wl_rinfo->link_mode = BTC_WLINK_OTHER;
	} else if (h->dbcc_en) {
		/* find out the 2G-PHY by connect-id ->ch  */
		for (j = 0; j < cnt_connect; j++) {
			if (cid_ch[j].center_ch <= 14 || j == cnt_connect-1)
				break;
		}

		dbcc_2g_cid = j;
		dbcc_2g_phy = cid_phy[j];
		dbcc_2g_role = cid_role[j];

		/* find out the other role in the 2.4G_band-PHY*/
		if (cnt_connect == 3) {
			for (k = 0; k < cnt_connect; k++) {
				if (k == j)
					continue;
				else if (cid_phy[k] == dbcc_2g_phy)
					break;
			}

			if (k < cnt_connect) { /* if find -> DBCC + MCC/SCC */
				if (cid_ch[k].center_ch > 14)
					wl_rinfo->link_mode = BTC_WLINK_25G_MCC;
				else if (_chk_role_ch_group(btc, cid_ch[j], cid_ch[k]))
					wl_rinfo->link_mode = BTC_WLINK_2G_SCC;
				else
					wl_rinfo->link_mode = BTC_WLINK_2G_MCC;

				/* correct 2G-located PHY role/band
				 * for gnt ctrl.
				 */
				wl_dinfo->role[dbcc_2g_phy] = dbcc_2g_role;
				wl_dinfo->op_band[dbcc_2g_phy] = BAND_ON_24G;
				goto exit;
			}
		}

		wl_rinfo->link_mode = _get_role_link_mode(btc, cid_role[j]);
	} else if (b2g && b5g && cnt_connect == 2) {
		wl_rinfo->link_mode = BTC_WLINK_25G_MCC;
	} else if (!b5g && cnt_connect == 2) { /* cnt_connect = 2 */
		if (_chk_role_ch_group(btc, cid_ch[0], cid_ch[cnt_connect - 1]))
			wl_rinfo->link_mode = BTC_WLINK_2G_SCC;
		else
			wl_rinfo->link_mode= BTC_WLINK_2G_MCC;
	} else if (!b5g && cnt_connect == 1) { /* cnt_connect = 1 */
		wl_rinfo->link_mode = _get_role_link_mode(btc, cid_role[0]);
	}

	/* if no client_joined, don't care P2P-GO/AP role */
	if ((wl_rinfo->role_map.role.p2p_go || wl_rinfo->role_map.role.ap) &&
	     !client_joined) {
#if 0 /* work-around for Go/AP(no-client) can not move channel to same as STA*/
		if (wl_rinfo->link_mode == BTC_WLINK_2G_SCC ||
		    wl_rinfo->link_mode == BTC_WLINK_2G_MCC)
#else
		if (wl_rinfo->link_mode == BTC_WLINK_2G_SCC)
#endif

		{
			wl_rinfo->link_mode = BTC_WLINK_2G_STA;
			wl_rinfo->connect_cnt--;
		} else if (wl_rinfo->link_mode == BTC_WLINK_2G_GO ||
			wl_rinfo->link_mode == BTC_WLINK_2G_AP) {
			wl_rinfo->link_mode = BTC_WLINK_NOLINK;
			wl_rinfo->connect_cnt--;
		}
	}

exit:
	/* Identify 2-Role type  */
	if (wl_rinfo->link_mode == BTC_WLINK_2G_SCC ||
	    wl_rinfo->link_mode == BTC_WLINK_2G_MCC ||
	    wl_rinfo->link_mode == BTC_WLINK_25G_MCC ||
	    (wl_rinfo->link_mode == BTC_WLINK_5G && cnt_connect > 1)) {
		if (wl_rinfo->role_map.role.p2p_go ||
		    wl_rinfo->role_map.role.ap) {
			if (noa_exist)
				wl_rinfo->mrole_type = BTC_WLMROLE_STA_GO_NOA;
			else
				wl_rinfo->mrole_type = BTC_WLMROLE_STA_GO;
		} else if (wl_rinfo->role_map.role.p2p_gc) {
			if (noa_exist)
				wl_rinfo->mrole_type = BTC_WLMROLE_STA_GC_NOA;
			else
				wl_rinfo->mrole_type = BTC_WLMROLE_STA_GC;
		} else {
			wl_rinfo->mrole_type = BTC_WLMROLE_STA_STA;
		}

		wl_rinfo->mrole_noa_duration = noa_duration;
	} else {
		wl_rinfo->mrole_type = BTC_WLMROLE_NONE;
		wl_rinfo->mrole_noa_duration = 0;
	}

	hal_btc_fw_set_drv_info(btc, CXDRVINFO_ROLE);

	if (h->dbcc_en) {
		_set_wl_req_mac(btc, dbcc_2g_phy);
		_update_dbcc_band(HW_PHY_0);
		_update_dbcc_band(HW_PHY_1);
		hal_btc_fw_set_drv_info(btc, CXDRVINFO_DBCC);
	} else {
		_set_wl_req_mac(btc, HW_PHY_0);
	}
}

void _run_coex(struct btc_t *btc, const char *reason)
{
	struct btc_dm *dm = &btc->dm;
	struct btc_cx *cx = &btc->cx;
	struct btc_wl_info *wl = &cx->wl;
	struct btc_bt_info *bt = &cx->bt;
	struct btc_wl_role_info *wl_rinfo = &wl->role_info;
	u8 mode = wl_rinfo->link_mode;

	PHL_INFO("[BTC], %s(): reason = %s, mode=%d\n", __func__, reason, mode);
	_rsn_cpy(dm->run_reason, (char*)reason);
	_update_dm_step(btc, reason);

#if BTC_CX_FW_OFFLOAD
	if (wl->rfk_info.state != BTC_WRFK_STOP && mode != BTC_WLINK_5G) {
		_action_wl_rfk(btc);
	} else  if (wl->rfk_info.state == BTC_WRFK_STOP) {
		PHL_INFO("[BTC], %s(): offload to WL_FW\n", __func__);
		hal_btc_fw_set_drv_info(btc, CXDRVINFO_SMAP);
		hal_btc_fw_set_drv_info(btc, CXDRVINFO_RUN);
	}

	goto exit;
#else

	_update_btc_state_map(btc);
	_get_wl_nhm_dbm(btc);

	/* Be careful to change the following function sequence!! */
	if (btc->ctrl.manual) {
		PHL_INFO("[BTC], %s(): return for Manual CTRL!!\n", __func__);
		return;
	}

	if (btc->ctrl.igno_bt &&
	    (run_rsn("_update_bt_info") || run_rsn("_update_bt_scbd"))) {
		PHL_INFO("[BTC], %s(): return for Stop Coex DM!!\n", __func__);
		return;
	}

	if (!wl->status.map.init_ok) {
		PHL_INFO("[BTC], %s(): return for WL init fail!!\n", __func__);
		return;
	}

	if (wl->status.map.rf_off_pre == wl->status.map.rf_off &&
	    wl->status.map.lps_pre == wl->status.map.lps) {
	    	if (run_rsn("_ntfy_power_off") ||
		    run_rsn("_ntfy_radio_state")) {
			PHL_INFO("[BTC], %s(): return for WL rf off state no change!!\n",
			 __func__);
			return;
	    	}

		if (wl->status.map.rf_off == 1 || wl->status.map.lps == 1)  {
			PHL_INFO("[BTC], %s(): return for WL rf off state!!\n",
				 __func__);
			return;
		}
	}

	dm->cnt_dm[BTC_DCNT_RUN]++;
	dm->freerun = false;
	btc->ctrl.igno_bt = false;
	bt->scan_rx_low_pri = false;

	if (btc->ctrl.always_freerun) {
		_action_freerun(btc);
		btc->ctrl.igno_bt = true;
		goto exit;
	}

	if (dm->wl_only) {
		_action_wl_only(btc);
		btc->ctrl.igno_bt = true;
		goto exit;
	}

	if (wl->status.map.rf_off || wl->status.map.lps || dm->bt_only) {
		_action_wl_off(btc);
		btc->ctrl.igno_bt = true;
		goto exit;
	}

	if (run_rsn("_ntfy_init_coex")) {
		_action_wl_init(btc);
		goto exit;
	}

	if (!cx->bt.enable.now && !cx->other.type) {
		_action_bt_off(btc);
		goto exit;
	}

	if (cx->bt.whql_test) {
		_action_bt_whql(btc);
		goto exit;
	}

	if (wl->rfk_info.state != BTC_WRFK_STOP) {
		_action_wl_rfk(btc);
		goto exit;
	}

	if (cx->state_map == BTC_WLINKING &&
	    (mode == BTC_WLINK_NOLINK || mode == BTC_WLINK_2G_STA ||
	     mode == BTC_WLINK_5G)) {
		_action_wl_scan(btc);
		goto exit;
	}

	if (wl->status.map.scan) {
		_action_wl_scan(btc);
		goto exit;
	}

	switch (mode) {
	case BTC_WLINK_NOLINK:
		_action_wl_nc(btc);
		break;
	case BTC_WLINK_2G_STA:
		_action_wl_2g_sta(btc);
		break;
	case BTC_WLINK_2G_AP:
		bt->scan_rx_low_pri = true;
		_action_wl_2g_ap(btc);
		break;
	case BTC_WLINK_2G_GO:
		bt->scan_rx_low_pri = true;
		_action_wl_2g_go(btc);
		break;
	case BTC_WLINK_2G_GC:
		bt->scan_rx_low_pri = true;
		_action_wl_2g_gc(btc);
		break;
	case BTC_WLINK_2G_SCC:
		bt->scan_rx_low_pri = true;
		_action_wl_2g_scc(btc);
		break;
	case BTC_WLINK_2G_MCC:
		bt->scan_rx_low_pri = true;
		_action_wl_2g_mcc(btc);
		break;
	case BTC_WLINK_25G_MCC:
		bt->scan_rx_low_pri = true;
		_action_wl_25g_mcc(btc);
		break;
	case BTC_WLINK_5G:
		_action_wl_5g(btc);
		break;
	case BTC_WLINK_2G_NAN:
		_action_wl_2g_nan(btc);
		break;
	default:
		_action_wl_other(btc);
		break;
	}
#endif
exit:
	_action_common(btc);
}

static void _update_offload_runinfo(struct btc_t *btc, u8 *buf, u32 len)
{
#if BTC_CX_FW_OFFLOAD
	u32 val;
	struct btc_cxr_result *r = NULL;

	if (!buf || buf[0] >= BTC_CXR_MAX)
		return;

	switch(buf[0]) {
	case BTC_CXR_WSCBD:
		val = (buf[4] << 24) + (buf[3] << 16) + (buf[2] << 8) + buf[1];
		if (val & BIT(31)) /* if val & BIT(31) --> write scbd bit false */
			_write_scbd(btc, val & 0x7fffffff, false);
		else
			_write_scbd(btc, val, true);
		break;
	case BTC_CXR_RESULT:
		r = (struct btc_cxr_result*) &buf[1];

		btc->dm.freerun = r->dm.freerun;
		btc->dm.wl_ps_ctrl = r->dm.wl_ps_ctrl;
		btc->dm.leak_ap = r->dm.leak_ap;
		btc->ctrl.igno_bt = (u16)r->dm.igno_bt;
		btc->dm.noisy_level = r->dm.noisy_level;
		btc->dm.set_ant_path = r->dm.set_ant_path;

		btc->dm.rf_trx_para = r->rf_trx_para;
		btc->cx.state_map = r->cx_state_map;
		btc->policy_type = (u16)r->policy_type;
		btc->dm.cnt_dm[BTC_DCNT_RUN] = r->run_cnt;

		hal_mem_cpy(btc->hal, btc->dm.run_reason, r->run_reason,
			    BTC_RSN_MAXLEN);
		hal_mem_cpy(btc->hal, btc->dm.run_action, r->run_action,
			    BTC_ACT_MAXLEN);
		break;
	}
#endif
}

void _update_bt_scbd(struct btc_t *btc, bool only_update)
{
	struct btc_cx *cx = &btc->cx;
	struct btc_bt_info *bt = &cx->bt;
	u32 val;
	bool status_change = false, bt_link_change = false;

	if (!btc->chip->scbd)
		return;

	PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_, "[BTC], %s()\n", __func__);

	val = _read_scbd(btc);

	if (val == 0xffffffff) {
		PHL_TRACE(COMP_PHL_BTC, _PHL_ERR_, "[BTC], %s return by invalid scbd value\n",
			  __func__);
		return;
	}

	if (!(val & BTC_BSCB_ON) ||
	    btc->dm.cnt_dm[BTC_DCNT_BTCNT_FREEZE] >= BTC_CHK_HANG_MAX)
		bt->enable.now = 0;
	else
		bt->enable.now = 1;

	if (bt->enable.now != bt->enable.last) {
		status_change = true;
		bt_link_change = true;
	}

	/* reset bt info if bt re-enable */
	if (bt->enable.now && !bt->enable.last) {
		_reset_btc_var(btc, BTC_RESET_BTINFO);
		cx->cnt_bt[BTC_BCNT_REENABLE]++;
		bt->enable.now = 1;
	}

	bt->enable.last = bt->enable.now;

	bt->scbd = val;
	bt->mbx_avl = !!(val & BTC_BSCB_ACT);

        if (bt->whql_test != (u32)(!!(val & BTC_BSCB_WHQL)))
		status_change = true;

	bt->whql_test = !!(val & BTC_BSCB_WHQL);

	bt->btg_type = (val & BTC_BSCB_BT_S1 ? BTC_BT_BTG: BTC_BT_ALONE);

	bt->link_info.a2dp_desc.active = !!(val & BTC_BSCB_A2DP_ACT);

	/* if rfk run 1->0 */
	if (bt->rfk_info.map.run && !(val & BTC_BSCB_RFK_RUN))
		status_change = true;

	bt->rfk_info.map.run  = !!(val & BTC_BSCB_RFK_RUN);

	bt->rfk_info.map.req = !!(val & BTC_BSCB_RFK_REQ);

	bt->hi_lna_rx = !!(val & BTC_BSCB_BT_HILNA);

	/* if connect change */
	if ((bt->link_info.status.map.connect && (!(val & BTC_BSCB_BT_CONNECT))) ||
	    (!bt->link_info.status.map.connect && (val & BTC_BSCB_BT_CONNECT))) {
		status_change = true;
		bt_link_change = true;
	}

	bt->link_info.status.map.connect = !!(val & BTC_BSCB_BT_CONNECT);

	bt->run_patch_code = !!(val & BTC_BSCB_PATCH_CODE);

#if !BTC_CX_FW_OFFLOAD
	if (bt_link_change) {
		PHL_INFO("[BTC], %s: bt status change!!\n", __func__);
		hal_btc_send_event(btc, NULL, 0, BTC_HMSG_BT_LINK_CHG);
	}

	if (!only_update && status_change)
		_run_coex(btc, __func__);
#endif
}

/******************************************************************************
 *
 * coexistence operations for external notifications
 *
 *****************************************************************************/
static void _ntfy_power_on(struct btc_t *btc)
{
	/* no action for power on, beacuse power-on move halmac API
	* the _ntfy_power_on = _ntfy_init_coex
	*/
}

static void _ntfy_power_off(struct btc_t *btc)
{
	PHL_INFO("[BTC], %s()\n", __func__);
	btc->dm.cnt_notify[BTC_NCNT_POWER_OFF]++;

	btc->cx.wl.status.map.rf_off = 1;
	btc->cx.wl.status.map.busy = 0;
	_os_delay_us(btc->hal, 1000);
	_write_scbd(btc, BTC_WSCB_ALL, false);
	_run_coex(btc, __func__);

	hal_btc_fw_en_rpt(btc, RPT_EN_ALL, 0);

	btc->cx.wl.status.map.rf_off_pre = btc->cx.wl.status.map.rf_off;
}

static void _ntfy_init_coex(struct btc_t *btc, u8 mode)
{
	struct btc_dm *dm = &btc->dm;
	struct btc_wl_info *wl = &btc->cx.wl;
	struct btc_chip_ops *ops = btc->chip->ops;

	PHL_INFO("[BTC], %s(): mode=%d\n", __func__, mode);
	dm->cnt_notify[BTC_NCNT_INIT_COEX]++;
	dm->wl_only = (mode == BTC_MODE_WL? 1 : 0);
	dm->bt_only = (mode == BTC_MODE_BT? 1 : 0);
	wl->status.map.rf_off = (mode == BTC_MODE_WLOFF? 1 : 0);

	if (!wl->status.map.init_ok) {
		PHL_TRACE(COMP_PHL_BTC, _PHL_ERR_, "[BTC], %s(): return for WL init fail!!\n",
			  __func__);
		dm->error.map.init = true;
		return;
	}

	/* Setup RF front end type from EFuse RFE type*/
	if (ops && ops->set_rfe)
		ops->set_rfe(btc);

	if (ops && ops->init_cfg)
		ops->init_cfg(btc);

	_write_scbd(btc, BTC_WSCB_ACTIVE | BTC_WSCB_ON | BTC_WSCB_BTLOG, true);
	_update_bt_scbd(btc, true);

	/* check PTA control owner to avoid BT coex issue */
	if (_read_cx_ctrl(btc) == BTC_CTRL_BY_WL) {
		PHL_TRACE(COMP_PHL_BTC, _PHL_ERR_, "[BTC], %s(): PTA owner warning!!\n",
			  __func__);
		dm->error.map.pta_owner = true;
	}

	_set_init_info(btc);
	_set_wl_tx_power(btc, BTC_WL_DEF_TX_PWR); /* original tx power, no Tx power adjust */
	hal_btc_fw_set_slots(btc, CXST_MAX, dm->slot);
	hal_btc_fw_set_monreg(btc);
	hal_btc_fw_set_drv_info(btc, CXDRVINFO_INIT);
	hal_btc_fw_set_drv_info(btc, CXDRVINFO_CTRL);

	_run_coex(btc, __func__);
	_btmr_start(btc);

#if 0
	hal_btc_fw_set_gpio_dbg(btc, CXDGPIO_EN_MAP,
				BIT(BTC_DBG_GNT_WL) | BIT(BTC_DBG_GNT_BT));
#endif
}

static void _ntfy_scan_start(struct btc_t *btc, u8 phy_idx, u8 band)
{
	struct btc_wl_info *wl = &btc->cx.wl;

	PHL_INFO("[BTC], %s(): phy_idx=%d, band=%d\n", __func__, phy_idx, band);
	btc->dm.cnt_notify[BTC_NCNT_SCAN_START]++;

	wl->status.map.scan = true;
	wl->scan_info.band[phy_idx] = band;
	wl->scan_info.phy_map |= BIT(phy_idx);
	hal_btc_fw_set_drv_info(btc, CXDRVINFO_SCAN);

	if (btc->hal->dbcc_en) {
		wl->dbcc_info.scan_band[phy_idx] = band;
		_update_dbcc_band(phy_idx);
		hal_btc_fw_set_drv_info(btc, CXDRVINFO_DBCC);
	}

	_run_coex(btc, __func__);
}

static void _ntfy_scan_finish(struct btc_t *btc, u8 phy_idx)
{
	struct btc_wl_info *wl = &btc->cx.wl;

	PHL_INFO("[BTC], %s(): phy_idx=%d\n", __func__, phy_idx);
	btc->dm.cnt_notify[BTC_NCNT_SCAN_FINISH]++;

	wl->status.map.scan = false;
	wl->scan_info.phy_map &= ~BIT(phy_idx);
	hal_btc_fw_set_drv_info(btc, CXDRVINFO_SCAN);

	if (btc->hal->dbcc_en) {
		_update_dbcc_band(phy_idx);
		hal_btc_fw_set_drv_info(btc, CXDRVINFO_DBCC);
	}

	_run_coex(btc, __func__);
}

static void _ntfy_switch_band(struct btc_t *btc, u8 phy_idx, u8 band)
{
	struct btc_wl_info *wl = &btc->cx.wl;

	PHL_INFO("[BTC], %s(): phy_idx=%d, band=%d\n", __func__, phy_idx, band);
	btc->dm.cnt_notify[BTC_NCNT_SWITCH_BAND]++;

	wl->scan_info.band[phy_idx] = band;
	wl->scan_info.phy_map |= BIT(phy_idx);
	hal_btc_fw_set_drv_info(btc, CXDRVINFO_SCAN);

	if (btc->hal->dbcc_en) {
		wl->dbcc_info.scan_band[phy_idx] = band;
		_update_dbcc_band(phy_idx);
		hal_btc_fw_set_drv_info(btc, CXDRVINFO_DBCC);
	}

	_run_coex(btc, __func__);
}

static void _ntfy_specific_packet(struct btc_t *btc, u8 pkt_type)
{
	struct btc_cx *cx = &btc->cx;
	struct btc_wl_info *wl = &cx->wl;
	struct btc_bt_link_info *b = &cx->bt.link_info;
	struct btc_bt_hfp_desc *hfp = &b->hfp_desc;
	struct btc_bt_hid_desc *hid = &b->hid_desc;
	u32 cnt, delay = BTC_SPECPKT_MAXT;

	switch (pkt_type) {
	case BTC_PKT_EVT_DHCP:
		cnt = ++cx->cnt_wl[BTC_WCNT_DHCP];
		PHL_INFO("[BTC], %s(): pkt_type=%d, DHCP cnt=%d \n", __func__,
			 pkt_type, cnt);
		wl->status.map.connecting = true;
		_set_btc_timer(btc, BTC_TIMER_WL_SPECPKT, delay);
		break;
	case BTC_PKT_EVT_EAPOL:
		cnt = ++cx->cnt_wl[BTC_WCNT_EAPOL];
		PHL_INFO("[BTC], %s(): pkt_type=%d, EAPOL cnt=%d \n", __func__,
			 pkt_type, cnt);
		wl->status.map._4way = true;

		if (hfp->exist || hid->exist)
			delay = delay / 2;

		_set_btc_timer(btc, BTC_TIMER_WL_SPECPKT, delay);
		break;
	case BTC_PKT_EVT_ADD_KEY:
		cnt = ++cx->cnt_wl[BTC_WCNT_EAPOL];
		PHL_INFO("[BTC], %s(): pkt_type=%d, EAPOL_End cnt=%d \n", __func__,
			 pkt_type, cnt);
		wl->status.map._4way = false;
		break;
	default:
	case BTC_PKT_EVT_ARP:
		cnt = ++cx->cnt_wl[BTC_WCNT_ARP];
		PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_, "[BTC], %s(): pkt_type=%d, ARP cnt=%d\n",
			  __func__, pkt_type, cnt);
		return;
	}

	btc->dm.cnt_notify[BTC_NCNT_SPECIAL_PACKET]++;

	_run_coex(btc, __func__);
}

static void _update_bt_psd(struct btc_t *btc, u8 *buf, u32 len)
{
	PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_, "[BTC], %s():\n", __func__);
}

void _update_dm_step(struct btc_t *btc, const char *strin)
{
	struct btc_dm *dm = &btc->dm;
	u32 store_index = 0;

	dm->dm_step.cnt++;
	if (dm->dm_step.cnt == 0)
		dm->dm_step.cnt = 1;

	store_index = ((dm->dm_step.cnt-1) % BTC_DM_MAXSTEP);
	_rsn_cpy(dm->dm_step.step[store_index], (char*)strin);
}

static void _update_bt_info(struct btc_t *btc, u8 *buf, u32 len)
{
	struct rtw_hal_com_t *h = btc->hal;
	struct btc_cx *cx = &btc->cx;
	struct btc_bt_info *bt = &cx->bt;
	struct btc_bt_link_info *b = &bt->link_info;
	struct btc_bt_hfp_desc *hfp = &b->hfp_desc;
	struct btc_bt_hid_desc *hid = &b->hid_desc;
	struct btc_bt_a2dp_desc *a2dp = &b->a2dp_desc;
	struct btc_bt_pan_desc *pan = &b->pan_desc;
	union btc_btinfo btinfo;
	bool bt_link_change = false;

	if (buf[BTC_BTINFO_L1] != BTC_BT_INFO_LEN)
		return;

	/* return if bt info match last bt-info  */
	if (!hal_mem_cmp(h, bt->raw_info, buf, BTC_BTINFO_MAX)) {
		PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_, "[BTC], %s return by bt-info duplicate!!\n",
			  __func__);
		cx->cnt_bt[BTC_BCNT_INFOSAME]++;
		return;
	}

	hal_mem_cpy(h, bt->raw_info, buf, BTC_BTINFO_MAX);

	PHL_INFO("[BTC], %s: bt_info[2]=0x%02x\n", __func__, bt->raw_info[2]);

	/* reset to mo-connect before update */
	b->profile_cnt.last = b->profile_cnt.now;
	b->relink.last = b->relink.now;
	a2dp->exist_last = a2dp->exist;
	b->multi_link.last = b->multi_link.now;
	bt->inq_pag.last = bt->inq_pag.now;
	b->profile_cnt.now = 0;
	hid->type = 0;

	/* ======= parse raw info low-Byte2 ======= */
	btinfo.val = bt->raw_info[BTC_BTINFO_L2];
	b->status.map.connect = btinfo.lb2.connect;
	b->status.map.sco_busy = btinfo.lb2.sco_busy;
	b->status.map.acl_busy = btinfo.lb2.acl_busy;
	b->status.map.inq_pag = btinfo.lb2.inq_pag;
	bt->inq_pag.now = btinfo.lb2.inq_pag;
	cx->cnt_bt[BTC_BCNT_INQPAG] += !!(bt->inq_pag.now && !bt->inq_pag.last);

	hfp->exist = btinfo.lb2.hfp;
	b->profile_cnt.now += (u8)hfp->exist;
	hid->exist = btinfo.lb2.hid;
	b->profile_cnt.now += (u8)hid->exist;
	a2dp->exist = btinfo.lb2.a2dp;
	b->profile_cnt.now += (u8)a2dp->exist;
	pan->exist = btinfo.lb2.pan;
	b->profile_cnt.now += (u8)pan->exist;

	/* ======= parse raw info low-Byte3 ======= */
	btinfo.val = bt->raw_info[BTC_BTINFO_L3];
	if (btinfo.lb3.retry != 0)
		cx->cnt_bt[BTC_BCNT_RETRY]++;
	b->cqddr = btinfo.lb3.cqddr;
	cx->cnt_bt[BTC_BCNT_INQ] += !!(btinfo.lb3.inq && !bt->inq);
	bt->inq = btinfo.lb3.inq;
	cx->cnt_bt[BTC_BCNT_PAGE] += !!(btinfo.lb3.pag && !bt->pag);
	bt->pag = btinfo.lb3.pag;

	b->status.map.mesh_busy = btinfo.lb3.mesh_busy;
	/* ======= parse raw info high-Byte0 ======= */
	btinfo.val = bt->raw_info[BTC_BTINFO_H0];
	/* raw val is dBm unit, translate from -100~ 0dBm to 0~100%*/
	b->rssi = btc->chip->ops->bt_rssi(btc, btinfo.hb0.rssi);

	/* ======= parse raw info high-Byte1 ======= */
	btinfo.val = bt->raw_info[BTC_BTINFO_H1];
	b->status.map.ble_connect = btinfo.hb1.ble_connect;
	if (btinfo.hb1.ble_connect)
		hid->type |= (hid->exist? BTC_HID_BLE : BTC_HID_RCU);

	cx->cnt_bt[BTC_BCNT_REINIT] += !!(btinfo.hb1.reinit && !bt->reinit);
	bt->reinit = btinfo.hb1.reinit;
	cx->cnt_bt[BTC_BCNT_RELINK] += !!(btinfo.hb1.relink && !b->relink.now);
	b->relink.now = btinfo.hb1.relink;
	cx->cnt_bt[BTC_BCNT_IGNOWL] += !!(btinfo.hb1.igno_wl && !bt->igno_wl);
	bt->igno_wl = btinfo.hb1.igno_wl;

	hid->type |= (btinfo.hb1.voice? BTC_HID_RCU_VOICE : 0);
	bt->ble_scan_en = btinfo.hb1.ble_scan;

	cx->cnt_bt[BTC_BCNT_ROLESW] += !!(btinfo.hb1.role_sw && !b->role_sw);
	b->role_sw = btinfo.hb1.role_sw;

	b->multi_link.now = btinfo.hb1.multi_link;

	if (b->multi_link.now != b->multi_link.last)
		bt_link_change = true;

	/* ======= parse raw info high-Byte2 ======= */
	btinfo.val = bt->raw_info[BTC_BTINFO_H2];
	pan->active = !!btinfo.hb2.pan_active;

	cx->cnt_bt[BTC_BCNT_AFH] += !!(btinfo.hb2.afh_update && !b->afh_update);
	b->afh_update = btinfo.hb2.afh_update;
	a2dp->active = btinfo.hb2.a2dp_active;
	b->slave_role = btinfo.hb2.slave;
	hid->slot_info = btinfo.hb2.hid_slot;

	if (hid->pair_cnt != btinfo.hb2.hid_cnt)
		bt_link_change = true;

	hid->pair_cnt = btinfo.hb2.hid_cnt;
	hid->type |= (hid->slot_info == BTC_HID_218? BTC_HID_218 : BTC_HID_418);

	/* ======= parse raw info high-Byte3 ======= */
	btinfo.val = bt->raw_info[BTC_BTINFO_H3];
	a2dp->bitpool = btinfo.hb3.a2dp_bitpool;

	if (b->tx_3M != (u32)btinfo.hb3.tx_3M)
		cx->cnt_bt[BTC_BCNT_RATECHG]++;
	b->tx_3M = (u32)btinfo.hb3.tx_3M;

	a2dp->sink = btinfo.hb3.a2dp_sink;

#if !BTC_CX_FW_OFFLOAD
	if (bt->igno_wl && !cx->wl.status.map.rf_off)
		_set_bt_ignore_wlan_act(btc, false);

	if (b->profile_cnt.now || b->status.map.ble_connect)
		hal_btc_fw_en_rpt(btc, RPT_EN_BT_AFH_MAP, 1);
	else
		hal_btc_fw_en_rpt(btc, RPT_EN_BT_AFH_MAP, 0);

	if (bt_link_change) {
		PHL_INFO("[BTC], %s: bt link change!!\n", __func__);
		hal_btc_send_event(btc, NULL, 0, BTC_HMSG_BT_LINK_CHG);
	}

	/* reset after A2DP stop->play */
	if (!a2dp->exist_last && a2dp->exist) {
		a2dp->vendor_id = 0;
		a2dp->flush_time = 0;
		a2dp->play_latency = 1;
		_set_btc_timer(btc, BTC_TIMER_BT_A2DPPLAY, BTC_A2DP_RESUME_MAXT);
	}

	if (a2dp->exist && (a2dp->flush_time == 0 || a2dp->vendor_id == 0 ||
	    a2dp->play_latency == 1))
		hal_btc_fw_en_rpt(btc, RPT_EN_BT_DEVICE_INFO, 1);
	else
		hal_btc_fw_en_rpt(btc, RPT_EN_BT_DEVICE_INFO, 0);

	_run_coex(btc, __func__);
#endif
}

static void _ntfy_role_info(struct btc_t *btc, u8 rid,
			    struct btc_wl_link_info *info,
			    enum role_state reason)
{
	struct rtw_hal_com_t *h = btc->hal;
	struct btc_wl_info *wl = &btc->cx.wl;
	struct btc_wl_link_info *wlinfo = NULL;

	PHL_INFO("[BTC], %s(), role_id=%d, reason=%d\n", __func__, rid, reason);

	if (rid >= MAX_WIFI_ROLE_NUMBER)
		return;

	btc->dm.cnt_notify[BTC_NCNT_ROLE_INFO]++;

	wlinfo = &wl->link_info[rid];

	hal_mem_cpy(h, wlinfo, info, sizeof(struct btc_wl_link_info));

	/* update wifi_link_info_ext variable */
	_update_wl_info(btc);

	if (wlinfo->role == PHL_RTYPE_STATION &&
	    wlinfo->connected == MLME_NO_LINK)
	    btc->dm.leak_ap = 0;

	if (reason == PHL_ROLE_MSTS_STA_CONN_START)
		wl->status.map.connecting = 1;
	else
		wl->status.map.connecting = 0;

	if (reason == PHL_ROLE_MSTS_STA_DIS_CONN)
		wl->status.map._4way = false;

	_run_coex(btc, __func__);
}

static void _ntfy_radio_state(struct btc_t *btc, u8 rf_state)
{
	struct btc_wl_info *wl = &btc->cx.wl;
	struct btc_chip_ops *ops = btc->chip->ops;

	PHL_INFO("[BTC], %s(): rf_state =%d\n", __func__, rf_state);
	btc->dm.cnt_notify[BTC_NCNT_RADIO_STATE]++;

	switch(rf_state) {
	case BTC_RFCTRL_WL_OFF:
		wl->status.map.rf_off = 1;
		wl->status.map.lps = 0;
		wl->status.map.busy = 0;
		break;
	case BTC_RFCTRL_FW_CTRL: /*  LPS-PG, LPS-CG */
		wl->status.map.rf_off = 0;
		wl->status.map.lps = 1;
		wl->status.map.busy = 0;
		break;
	case BTC_RFCTRL_LPS_WL_ON: /* LPS-Protocol (RFon) */
		wl->status.map.rf_off = 0;
		wl->status.map.lps = 2;
		wl->status.map.busy = 0;
		break;
	case BTC_RFCTRL_WL_ON:
	default:
		wl->status.map.rf_off = 0;
		wl->status.map.lps = 0;
		break;
	}

	if (rf_state == BTC_RFCTRL_WL_ON) {
		hal_btc_fw_en_rpt(btc, RPT_EN_MREG, 1);
		_write_scbd(btc, BTC_WSCB_ACTIVE | BTC_WSCB_ON | BTC_WSCB_BTLOG, true);
		_update_bt_scbd(btc, true);
		if (ops && ops->init_cfg)
			ops->init_cfg(btc);
	} else {
		hal_btc_fw_en_rpt(btc, RPT_EN_ALL, 0);
		/* for BT only isolated issue,
		 * clear all scbd if LPS-PG or "LPS-off to LPS-Protocol"
		 */
		if (rf_state == BTC_RFCTRL_LPS_WL_ON &&
		    wl->status.map.lps_pre != 0) {
			_write_scbd(btc, BTC_WSCB_ON, true);
		} else {
			_os_delay_us(btc->hal, 1000);
			_write_scbd(btc, BTC_WSCB_ALL, false);
		}
	}

	_run_coex(btc, __func__);

	wl->status.map.rf_off_pre = wl->status.map.rf_off;
	wl->status.map.lps_pre = wl->status.map.lps;
}

static void _ntfy_customerize(struct btc_t *btc, u8 type, u16 len, u8 *buf)
{
	struct btc_bt_info *bt = &btc->cx.bt;

	if (!buf)
		return;

	PHL_INFO("[BTC], %s !! \n", __func__);
	btc->dm.cnt_notify[BTC_NCNT_CUSTOMERIZE]++;

	switch (type) {
	case PHL_BTC_CNTFY_BTINFO:
		if (len != 1)
			return;
		buf[0] = bt->raw_info[BTC_BTINFO_L2];
		break;
	}
}

static u8 _ntfy_wl_rfk(struct btc_t *btc, u8 phy_path, u8 type, u8 state)
{
	struct btc_cx *cx = &btc->cx;
	struct btc_wl_info *wl = &cx->wl;
	bool result = BTC_WRFK_REJECT;

	wl->rfk_info.type = type;
	wl->rfk_info.path_map = phy_path & BTC_RFK_PATH_MAP;
	wl->rfk_info.phy_map = (phy_path & BTC_RFK_PHY_MAP) >> 4;
	wl->rfk_info.band = (phy_path & BTC_RFK_BAND_MAP) >> 6;
	state &= (BIT(0) | BIT(1));

	PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_, "[BTC], %s()_start: phy=0x%x, path=0x%x, type=%d, state=%d\n",
		 __func__, wl->rfk_info.phy_map, wl->rfk_info.path_map,
		 type, state);

	switch (state) {
	case BTC_WRFK_START:
		result = _chk_wl_rfk_request(btc);
		wl->rfk_info.state = (result? BTC_WRFK_START : BTC_WRFK_STOP);
#if 0
		hal_btc_fw_set_drv_info(btc, CXDRVINFO_RFK);
		_write_scbd(btc, BTC_WSCB_WLRFK, result);
#endif

		btc->dm.cnt_notify[BTC_NCNT_WL_RFK]++;
		break;
	case BTC_WRFK_ONESHOT_START:
	case BTC_WRFK_ONESHOT_STOP:
		if (wl->rfk_info.state == BTC_WRFK_STOP) {
			result = BTC_WRFK_REJECT;
		} else {
			result = BTC_WRFK_ALLOW;
			wl->rfk_info.state = state;
		}
		break;
	case BTC_WRFK_STOP:
		result = BTC_WRFK_ALLOW;
		wl->rfk_info.state = BTC_WRFK_STOP;
#if 0
		hal_btc_fw_set_drv_info(btc, CXDRVINFO_RFK);
		_write_scbd(btc, BTC_WSCB_WLRFK, false);
#endif
		break;
	}

	if (result == BTC_WRFK_ALLOW) {
		/* Only update coex for RFK START and STOP
		 * because Start -> OneSHOT_START time is short
		 */
		if (wl->rfk_info.state == BTC_WRFK_START ||
		    wl->rfk_info.state == BTC_WRFK_STOP)
			_run_coex(btc, __func__);

		if (wl->rfk_info.state == BTC_WRFK_START) /* wait 300ms */
			_set_btc_timer(btc, BTC_TIMER_WL_RFKTO, BTC_WRFK_MAXT);

	}

	PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_,"[BTC], %s()_finish: rfk_cnt=%d, result=%d\n",
		 __func__, btc->dm.cnt_notify[BTC_NCNT_WL_RFK], result);

	return result;
}

static void _ntfy_wl_sta(struct btc_t *btc, u8 ntfy_num,
			 struct btc_wl_stat_info stat_info[], u8 reason)
{
	struct rtw_hal_com_t *h = btc->hal;
	struct btc_wl_info *wl = &btc->cx.wl;
	struct btc_module *module = &btc->mdinfo;
	struct btc_wl_stat_info *w = NULL;
	struct btc_traffic *t = NULL, *link_info_t = NULL;
	struct btc_wl_link_info *link_info = NULL;
	u8 i, j, busy = 0, dir = 0, rssi_map = 0;
	u8 busy_all = 0, dir_all = 0, rssi_map_all = 0;
	bool is_sta_change = false, is_traffic_change = false;
	u16 last_tx_rate, last_rx_rate, last_tx_lvl, last_rx_lvl;
	u32 chk_intvl = 20;

	/* rssi_map = 4 bits for rssi locate in which {60, 50, 40, 30}
	 * if rssi >= 60% (-50dBm) --> map = 4b'0000 --> rssi_level = 0
	 * if 50% <= rssi < 60%    --> map = 4b'0001 --> rssi_level = 1
	 * if 40% <= rssi < 50%    --> map = 4b'0011 --> rssi_level = 2
	 * if 30% <= rssi < 40%    --> map = 4b'0111 --> rssi_level = 3
	 * if rssi < 20%           --> map = 4b'1111 --> rssi_level = 4
	 */

	btc->dm.cnt_notify[BTC_NCNT_WL_STA]++;

	for (i = 0; i < ntfy_num; i++) {
		w = &stat_info[i];
		t = &w->stat.traffic;

		link_info = &wl->link_info[w->pid];
		link_info_t = &link_info->stat.traffic;
		rssi_map = 0;

		if (link_info->connected == MLME_NO_LINK) {
			link_info->rx_rate_drop_cnt = 0;
			rssi_map_all |= rssi_map;
			continue;
		}

		last_tx_rate = link_info_t->tx_rate;
		last_rx_rate = link_info_t->rx_rate;
		last_tx_lvl = (u16)link_info_t->tx_lvl;
		last_rx_lvl = (u16)link_info_t->rx_lvl;

		/* refresh link_info traffic/rssi related info from t */
		hal_mem_cpy(h, (void *)link_info_t, (void *)t,
			    sizeof(struct btc_traffic));

		link_info->stat.rssi = w->stat.rssi;

		/* check if rssi across wl_rssi_thres boundary */
		for (j = 0; j < BTC_WL_RSSI_THMAX; j++) {
			link_info->rssi_state[j] =
				_update_rssi_state(btc,
						   link_info->rssi_state[j],
						   link_info->stat.rssi,
						   btc->chip->wl_rssi_thres[j]);
			/* fill rssi bit map 0~3 if rssi < threshold  */
			if (BTC_RSSI_LOW(link_info->rssi_state[j]))
				rssi_map |= BIT(j);

			if (module->ant.type == BTC_ANT_DEDICATED &&
			    BTC_RSSI_CHANGE(link_info->rssi_state[j]))
				is_sta_change = true;
		}

		/* OR all role rssi map */
		rssi_map_all |= rssi_map;

		/* set busy once idle->busy immediately */
		if (t->tx_lvl != RTW_TFC_IDLE || t->rx_lvl != RTW_TFC_IDLE) {
			busy = 1;
			link_info->busy_t = _os_get_cur_time_ms();

			if (t->tx_lvl > t->rx_lvl)
				dir = TRAFFIC_UL;
			else
				dir = TRAFFIC_DL;
		} else {/*set idle if busy -> idle after BTC_BUSY2IDLE_THRES*/
			if (phl_get_passing_time_ms(link_info->busy_t) >
				BTC_BUSY2IDLE_THRES || !wl->role_info.role_map.role.station ||
				wl->role_info.link_mode == BTC_WLINK_NOLINK) {
				busy = 0;
				dir = TRAFFIC_DL;
			} else {
				busy = link_info->busy;
				dir = link_info->dir;
			}
		}

		if (link_info->busy != busy || link_info->dir != dir) {
			is_sta_change = true;
			link_info->busy = busy;
			link_info->dir = dir;
		}

		busy_all |= link_info->busy;
		dir_all |= BIT(link_info->dir);

		if (link_info_t->rx_rate <= RTW_DATA_RATE_CCK2 &&
		    last_rx_rate > RTW_DATA_RATE_CCK2 &&
		    link_info_t->rx_lvl > 0)
			link_info->rx_rate_drop_cnt++;

		if (last_tx_rate != link_info_t->tx_rate ||
		    last_rx_rate != link_info_t->rx_rate ||
		    last_tx_lvl != link_info_t->tx_lvl ||
		    last_rx_lvl != link_info_t->rx_lvl )
			is_traffic_change = true;

		wl->role_info.active_role[w->pid].tx_lvl = (u16)t->tx_lvl;
		wl->role_info.active_role[w->pid].rx_lvl = (u16)t->rx_lvl;
		wl->role_info.active_role[w->pid].tx_rate = t->tx_rate;
		wl->role_info.active_role[w->pid].rx_rate = t->rx_rate;
	}

	wl->rssi_level = 0;
	for (j = BTC_WL_RSSI_THMAX; j > 0; j--) {
		/* set RSSI level 4 ~ 0 if rssi bit map match */
		if (rssi_map_all & BIT(j-1)) {
			wl->rssi_level = j;
			break;
		}
	}

	/* for TDD/FDD packet estimation in WL FW */
	if (is_traffic_change)
		hal_btc_fw_set_drv_info(btc, CXDRVINFO_ROLE);

	chk_intvl = 2000/BTC_PERIODIC_TIME;

	if (is_sta_change) {
		wl->status.map.busy = (u32)busy_all;
		wl->status.map.traffic_dir = (u32)dir_all;
		_write_scbd(btc, BTC_WSCB_WLBUSY, (bool)(!!wl->status.map.busy));
		_run_coex(btc, __func__);
	} else if (btc->dm.cnt_notify[BTC_NCNT_WL_STA] >=
		   btc->dm.cnt_dm[BTC_DCNT_WL_STA_LAST] + chk_intvl) {
		_get_wl_nhm_dbm(btc);
		btc->dm.cnt_dm[BTC_DCNT_WL_STA_LAST] =
			btc->dm.cnt_notify[BTC_NCNT_WL_STA];
	} else if (btc->dm.cnt_notify[BTC_NCNT_WL_STA] <
		   btc->dm.cnt_dm[BTC_DCNT_WL_STA_LAST]) {
		   btc->dm.cnt_dm[BTC_DCNT_WL_STA_LAST] =
			btc->dm.cnt_notify[BTC_NCNT_WL_STA];
	}
}

static void _ntfy_fwinfo(struct btc_t *btc, u8 *buf, u32 len, u8 cls, u8 func)
{
	struct btf_fwinfo *pfwinfo = &btc->fwinfo;

	if (!buf || !len)
		return;

	btc->dm.cnt_notify[BTC_NCNT_FWINFO]++;
	pfwinfo->cnt_c2h++;

	if (cls == BTFC_FW_EVENT) {
		switch (func) {
		case BTF_EVNT_RPT:
		case BTF_EVNT_BUF_OVERFLOW:
			pfwinfo->event[func]++;
			hal_btc_fw_event(btc, func, buf, len);
			break;
		case BTF_EVNT_BT_INFO:
			btc->cx.cnt_bt[BTC_BCNT_INFOUPDATE]++;
			_update_bt_info(btc, buf, len);
			break;
		case BTF_EVNT_BT_SCBD:
			btc->cx.cnt_bt[BTC_BCNT_SCBDUPDATE]++;
			_update_bt_scbd(btc, false);
			break;
		case BTF_EVNT_BT_PSD:
			_update_bt_psd(btc, buf, len);
			break;
		case BTF_EVNT_BT_REG:
			btc->dbg.rb_done = true;
			btc->dbg.rb_val = ((buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | (buf[0]));
			break;
		case BTF_EVNT_C2H_LOOPBACK:
			btc->dbg.rb_done = true;
			btc->dbg.rb_val = buf[0];
			break;
		case BTF_EVNT_CX_RUNINFO:
			btc->dm.cnt_dm[BTC_DCNT_CX_RUNINFO]++;
			_update_offload_runinfo(btc, buf, len);
			break;
		}
	}
}

static void _ntfy_timer(struct btc_t *btc, u16 tmr_id)
{
	struct btc_dm *dm = &btc->dm;
	struct btc_cx *cx = &btc->cx;
	struct btc_wl_info *wl = &cx->wl;
	struct btc_bt_a2dp_desc *a2dp = &cx->bt.link_info.a2dp_desc;
	bool is_sta_change = false;

	PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_,
		  "[BTC], %s(): tmr_id =%d\n", __func__, tmr_id);

	dm->cnt_notify[BTC_NCNT_TIMER]++;

	if (tmr_id == BTC_TIMER_PERIODIC) {
		/* start next periodic timer */
		_set_btc_timer(btc, BTC_TIMER_PERIODIC, BTC_PERIODIC_TIME);
	} else if (tmr_id == BTC_TIMER_WL_RFKTO) {
		if (wl->rfk_info.state != BTC_WRFK_STOP) {
			cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT]++;
			dm->error.map.wl_rfk_timeout = true;
			wl->rfk_info.state = BTC_WRFK_STOP;
			/* _write_scbd(btc, BTC_WSCB_WLRFK, false); */
			is_sta_change = true;
		}
	} else if (tmr_id == BTC_TIMER_WL_SPECPKT) {
		wl->status.map._4way = false;
		wl->status.map.connecting = 0;
		is_sta_change = true;
	} else if (tmr_id == BTC_TIMER_BT_A2DPPLAY) {
		a2dp->play_latency = 0;
		is_sta_change = true;
	}

	if (is_sta_change)
		_run_coex(btc, __func__);
}

/******************************************************************************
 *
 * coexistence extern functions
 *
 *****************************************************************************/
/*
 * btc related sw initialization
 */
bool hal_btc_init(struct btc_t *btc)
{
	switch (btc->hal->chip_id) {
#ifdef BTC_8852A_SUPPORT
	case CHIP_WIFI6_8852A:
		PHL_INFO("[BTC], %s(): Init 8852A!!\n", __func__);
		btc->chip = &chip_8852a;
		break;
#endif
#ifdef BTC_8852B_SUPPORT
	case CHIP_WIFI6_8852B:
		PHL_INFO("[BTC], %s(): Init 8852B!!\n", __func__);
		btc->chip = &chip_8852b;
		break;
#endif
#ifdef BTC_8852C_SUPPORT
	case CHIP_WIFI6_8852C:
		PHL_INFO("[BTC], %s(): Init 8852C!!\n", __func__);
		btc->chip = &chip_8852c;
		break;
#endif
	default:
		PHL_ERR("[BTC], %s(): no matched IC!!\n", __func__);
		btc->cx.wl.status.map.init_ok = false;
		return false;
	}

	_reset_btc_var(btc, BTC_RESET_ALL);
	_btmr_init(btc);

	_rsn_cpy(btc->dm.run_reason, "None");
	_act_cpy(btc->dm.run_action, "None");

	btc->hal->btc_vc.btc_ver = coex_ver;
	btc->ops = &_btc_ops;
	btc->mlen = BTC_MSG_MAXLEN;
	btc->ctrl.igno_bt = true;
	btc->cx.wl.status.map.init_ok = true;

	return true;
}

void hal_btc_deinit(struct btc_t *btc)
{
	_btmr_deinit(btc);
}

#endif

