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

#ifdef CONFIG_BTCOEX

void _chk_btc_err(struct btc_t *btc, u8 type, u32 cnt)
{
	struct btc_cx *cx = &btc->cx;
	struct btc_dm *dm = &btc->dm;
	struct btc_wl_info *wl = &cx->wl;
	struct btc_bt_info *bt = &cx->bt;

	switch (type) {
	case BTC_DCNT_RPT_FREEZE:
		if (wl->status.map.lps == BTC_LPS_RF_OFF ||
		    wl->status.map.rf_off)
			return;

		if (dm->cnt_dm[BTC_DCNT_RPT] == cnt && btc->fwinfo.rpt_en_map)
			dm->cnt_dm[BTC_DCNT_RPT_FREEZE]++;
		else
			dm->cnt_dm[BTC_DCNT_RPT_FREEZE] = 0;

		if (dm->cnt_dm[BTC_DCNT_RPT_FREEZE] >= BTC_CHK_HANG_MAX)
			dm->error.map.wl_fw_hang = true;
		else
			dm->error.map.wl_fw_hang = false;

		dm->cnt_dm[BTC_DCNT_RPT] = cnt;
		break;
	case BTC_DCNT_CYCLE_FREEZE:
		if (dm->cnt_dm[BTC_DCNT_CYCLE] == cnt &&
		    (dm->tdma_now.type != CXTDMA_OFF ||
		     dm->tdma_now.ext_ctrl == CXECTL_EXT))
			dm->cnt_dm[BTC_DCNT_CYCLE_FREEZE]++;
		else
			dm->cnt_dm[BTC_DCNT_CYCLE_FREEZE] = 0;

		if (dm->cnt_dm[BTC_DCNT_CYCLE_FREEZE] >= BTC_CHK_HANG_MAX)
			dm->error.map.cycle_hang = true;
		else
			dm->error.map.cycle_hang = false;

		dm->cnt_dm[BTC_DCNT_CYCLE] = cnt;
		break;
	case BTC_DCNT_W1_FREEZE:
		if (dm->cnt_dm[BTC_DCNT_W1] == cnt &&
		    dm->tdma_now.type != CXTDMA_OFF)
			dm->cnt_dm[BTC_DCNT_W1_FREEZE]++;
		else
			dm->cnt_dm[BTC_DCNT_W1_FREEZE] = 0;

		if (dm->cnt_dm[BTC_DCNT_W1_FREEZE] >= BTC_CHK_HANG_MAX)
			dm->error.map.w1_hang = true;
		else
			dm->error.map.w1_hang = false;

		dm->cnt_dm[BTC_DCNT_W1] = cnt;
		break;
	case BTC_DCNT_B1_FREEZE:
		if (dm->cnt_dm[BTC_DCNT_B1] == cnt &&
		    dm->tdma_now.type != CXTDMA_OFF)
			dm->cnt_dm[BTC_DCNT_B1_FREEZE]++;
		else
			dm->cnt_dm[BTC_DCNT_B1_FREEZE] = 0;

		if (dm->cnt_dm[BTC_DCNT_B1_FREEZE] >= BTC_CHK_HANG_MAX)
			dm->error.map.b1_hang = true;
		else
			dm->error.map.b1_hang = false;

		dm->cnt_dm[BTC_DCNT_B1] = cnt;
		break;
	case BTC_DCNT_TDMA_NONSYNC:
		if (cnt != 0) /* if tdma not sync between drv/fw  */
			dm->cnt_dm[BTC_DCNT_TDMA_NONSYNC]++;
		else
			dm->cnt_dm[BTC_DCNT_TDMA_NONSYNC] = 0;

		if (dm->cnt_dm[BTC_DCNT_TDMA_NONSYNC] >= BTC_CHK_HANG_MAX)
			dm->error.map.tdma_no_sync = true;
		else
			dm->error.map.tdma_no_sync = false;
		break;
	case BTC_DCNT_SLOT_NONSYNC:
		if (cnt != 0) /* if slot not sync between drv/fw  */
			dm->cnt_dm[BTC_DCNT_SLOT_NONSYNC]++;
		else
			dm->cnt_dm[BTC_DCNT_SLOT_NONSYNC] = 0;

		if (dm->cnt_dm[BTC_DCNT_SLOT_NONSYNC] >= BTC_CHK_HANG_MAX)
			dm->error.map.tdma_no_sync = true;
		else
			dm->error.map.tdma_no_sync = false;
		break;
	case BTC_DCNT_BTCNT_FREEZE:
		cnt = cx->cnt_bt[BTC_BCNT_HIPRI_RX] +
		      cx->cnt_bt[BTC_BCNT_HIPRI_TX] +
		      cx->cnt_bt[BTC_BCNT_LOPRI_RX] +
		      cx->cnt_bt[BTC_BCNT_LOPRI_TX];

		if (cnt == 0)
			dm->cnt_dm[BTC_DCNT_BTCNT_FREEZE]++;
		else
			dm->cnt_dm[BTC_DCNT_BTCNT_FREEZE] = 0;

		if ((dm->cnt_dm[BTC_DCNT_BTCNT_FREEZE] >= BTC_CHK_HANG_MAX &&
		     bt->enable.now) || (!dm->cnt_dm[BTC_DCNT_BTCNT_FREEZE] &&
		     !bt->enable.now))
			_update_bt_scbd(btc, false);
		break;
	case BTC_DCNT_WL_SLOT_DRIFT:
		if (cnt >= BTC_CHK_WLSLOT_DRIFT_MAX)
			dm->cnt_dm[BTC_DCNT_WL_SLOT_DRIFT]++;
		else
			dm->cnt_dm[BTC_DCNT_WL_SLOT_DRIFT] = 0;

		if (dm->cnt_dm[BTC_DCNT_WL_SLOT_DRIFT] >= BTC_CHK_HANG_MAX)
			dm->error.map.wl_slot_drift = true;
		else
			dm->error.map.wl_slot_drift = false;
		break;
	case BTC_DCNT_BT_SLOT_DRIFT:
		if (cnt >= BTC_CHK_BTSLOT_DRIFT_MAX)
			dm->cnt_dm[BTC_DCNT_BT_SLOT_DRIFT]++;
		else
			dm->cnt_dm[BTC_DCNT_BT_SLOT_DRIFT] = 0;

		if (dm->cnt_dm[BTC_DCNT_BT_SLOT_DRIFT] >= BTC_CHK_HANG_MAX)
			dm->error.map.bt_slot_drift = true;
		else
			dm->error.map.bt_slot_drift = false;
		break;
	}
}

static void _update_bt_report(struct btc_t *btc, u8 rpt_type, u8* pfinfo)
{
	struct rtw_hal_com_t *h = btc->hal;
	struct btc_bt_info *bt = &btc->cx.bt;
	struct btc_bt_link_info *bt_linfo = &bt->link_info;
	struct btc_bt_a2dp_desc *a2dp = &bt_linfo->a2dp_desc;

	struct fbtc_btver* pver = (struct fbtc_btver*) pfinfo;
	struct fbtc_btscan* pscan = (struct fbtc_btscan*) pfinfo;
	struct fbtc_btafh* pafh = (struct fbtc_btafh*) pfinfo;
	struct fbtc_btdevinfo* pdev = (struct fbtc_btdevinfo*) pfinfo;

	switch (rpt_type) {
	case BTC_RPT_TYPE_BT_VER:
		bt->ver_info.fw = pver->fw_ver;
		bt->ver_info.fw_coex = (pver->coex_ver & bMASKB0);
		bt->feature = pver->feature;
		break;
	case BTC_RPT_TYPE_BT_SCAN:
		hal_mem_cpy(h, bt->scan_info, pscan->scan, BTC_SCAN_MAX1);
		break;
	case BTC_RPT_TYPE_BT_AFH:
		hal_mem_cpy(h, &bt_linfo->afh_map[0], pafh->afh_l, 4);
		hal_mem_cpy(h, &bt_linfo->afh_map[4], pafh->afh_m, 4);
		hal_mem_cpy(h, &bt_linfo->afh_map[8], pafh->afh_h, 2);
		break;
	case BTC_RPT_TYPE_BT_DEVICE:
		a2dp->device_name = pdev->dev_name;
		a2dp->vendor_id = pdev->vendor_id;
		a2dp->flush_time = pdev->flush_time;
		break;
	default:
		break;
	}
}

static u32 _chk_btc_report(struct btc_t *btc, struct btf_fwinfo *pfwinfo,
			u8 *prptbuf, u32 index)
{
	struct btc_dm *dm = &btc->dm;
	struct rtw_hal_com_t *hal = btc->hal;
	struct btc_rpt_cmn_info *pcinfo = NULL;
	struct btc_wl_info *wl = &btc->cx.wl;
	struct btc_bt_info *bt = &btc->cx.bt;
	struct fbtc_rpt_ctrl *prpt = NULL;
	struct fbtc_cysta *pcysta = NULL;
	u8 rpt_type = 0, *rpt_content = NULL, *pfinfo = NULL;
	u16 wl_slot_set = 0, polt_cnt = 0;
	u32 rpt_len = 0, diff_t, bt_slot_real = 0;

	if (!prptbuf) {
		pfwinfo->err[BTFRE_INVALID_INPUT]++;
		return 0;
	}

	rpt_type = prptbuf[index];
	rpt_len = (prptbuf[index+2] << 8) + prptbuf[index+1];
	rpt_content = &prptbuf[index+3];

	switch (rpt_type) {
	case BTC_RPT_TYPE_CTRL:
		pcinfo = &pfwinfo->rpt_ctrl.cinfo;
		pfinfo = (u8 *)(&pfwinfo->rpt_ctrl.finfo);
		pcinfo->req_len = sizeof(struct fbtc_rpt_ctrl);
		pcinfo->req_fver = FCX_BTCRPT_VER;
		pcinfo->rsp_fver = *rpt_content;
		break;
	case BTC_RPT_TYPE_TDMA:
		pcinfo = &pfwinfo->rpt_fbtc_tdma.cinfo;
		pfinfo = (u8 *)(&pfwinfo->rpt_fbtc_tdma.finfo);
		pcinfo->req_len = sizeof(struct fbtc_1tdma);
		pcinfo->req_fver = FCX_TDMA_VER;
		break;
	case BTC_RPT_TYPE_SLOT:
		pcinfo = &pfwinfo->rpt_fbtc_slots.cinfo;
		pfinfo = (u8 *)(&pfwinfo->rpt_fbtc_slots.finfo);
		pcinfo->req_len = sizeof(struct fbtc_slots);
		pcinfo->req_fver = FCX_SLOT_VER;
		break;
	case BTC_RPT_TYPE_CYSTA:
		pcinfo = &pfwinfo->rpt_fbtc_cysta.cinfo;
		pfinfo = (u8 *)(&pfwinfo->rpt_fbtc_cysta.finfo);
		pcysta = &pfwinfo->rpt_fbtc_cysta.finfo;
		pcinfo->req_len = sizeof(struct fbtc_cysta);
		pcinfo->req_fver = FCX_CYSTA_VER;
		break;
	case BTC_RPT_TYPE_STEP:
		pcinfo = &pfwinfo->rpt_fbtc_step.cinfo;
		pfinfo = (u8 *)(&pfwinfo->rpt_fbtc_step.finfo);
		pcinfo->req_len = 8 + sizeof(struct fbtc_step) * FCXDEF_STEP;
		pcinfo->req_fver = FCX_STEP_VER;
		break;
	case BTC_RPT_TYPE_NULLSTA:
		pcinfo = &pfwinfo->rpt_fbtc_nullsta.cinfo;
		pfinfo = (u8 *)(&pfwinfo->rpt_fbtc_nullsta.finfo);
		pcinfo->req_len = sizeof(struct fbtc_cynullsta);
		pcinfo->req_fver = FCX_NULLSTA_VER;
		break;
	case BTC_RPT_TYPE_MREG:
		pcinfo = &pfwinfo->rpt_fbtc_mregval.cinfo;
		pfinfo = (u8 *)(&pfwinfo->rpt_fbtc_mregval.finfo);
		pcinfo->req_len = sizeof(struct fbtc_mreg_val);
		pcinfo->req_fver = FCX_MREG_VER;
		break;
	case BTC_RPT_TYPE_GPIO_DBG:
		pcinfo = &pfwinfo->rpt_fbtc_gpio_dbg.cinfo;
		pfinfo = (u8 *)(&pfwinfo->rpt_fbtc_gpio_dbg.finfo);
		pcinfo->req_len = sizeof(struct fbtc_gpio_dbg);
		pcinfo->req_fver = FCX_GPIODBG_VER;
		break;
	case BTC_RPT_TYPE_BT_VER:
		pcinfo = &pfwinfo->rpt_fbtc_btver.cinfo;
		pfinfo = (u8 *)(&pfwinfo->rpt_fbtc_btver.finfo);
		pcinfo->req_len = sizeof(struct fbtc_btver);
		pcinfo->req_fver = FCX_BTVER_VER;
		break;
	case BTC_RPT_TYPE_BT_SCAN:
		pcinfo = &pfwinfo->rpt_fbtc_btscan.cinfo;
		pfinfo = (u8 *)(&pfwinfo->rpt_fbtc_btscan.finfo);
		pcinfo->req_len = sizeof(struct fbtc_btscan);
		pcinfo->req_fver = FCX_BTSCAN_VER;
		break;
	case BTC_RPT_TYPE_BT_AFH:
		pcinfo = &pfwinfo->rpt_fbtc_btafh.cinfo;
		pfinfo = (u8 *)(&pfwinfo->rpt_fbtc_btafh.finfo);
		pcinfo->req_len = sizeof(struct fbtc_btafh);
		pcinfo->req_fver = FCX_BTAFH_VER;
		break;
	case BTC_RPT_TYPE_BT_DEVICE:
		pcinfo = &pfwinfo->rpt_fbtc_btdev.cinfo;
		pfinfo = (u8 *)(&pfwinfo->rpt_fbtc_btdev.finfo);
		pcinfo->req_len = sizeof(struct fbtc_btdevinfo);
		pcinfo->req_fver = FCX_BTDEVINFO_VER;
		break;
	default:
		pfwinfo->err[BTFRE_UNDEF_TYPE]++;
		return 0;
	}

	pcinfo->rsp_fver = *rpt_content;
	pcinfo->rx_len = rpt_len;
	pcinfo->rx_cnt++;

	if (rpt_len != pcinfo->req_len) {
		if (rpt_type < BTC_RPT_TYPE_MAX)
			pfwinfo->len_mismch |= (0x1 << rpt_type);
		else
			pfwinfo->len_mismch |= BIT31;

		pcinfo->valid = 0;
		return 0;
	} else if (pcinfo->req_fver != pcinfo->rsp_fver) {
		if (rpt_type < BTC_RPT_TYPE_MAX)
			pfwinfo->fver_mismch |= (0x1 << rpt_type);
		else
			pfwinfo->fver_mismch |= BIT31;
		pcinfo->valid = 0;
		return 0;
	} else if (!pfinfo || !rpt_content || !pcinfo->req_len) {
		pfwinfo->err[BTFRE_EXCEPTION]++;
		pcinfo->valid = 0;
		return 0;
	}


	hal_mem_cpy(hal, (void *)pfinfo, (void *)rpt_content, pcinfo->req_len);
	pcinfo->valid = 1;

	if (rpt_type == BTC_RPT_TYPE_TDMA) {
#if BTC_CX_FW_OFFLOAD /* update tdma_now if fw offload for debug */
		_tdma_cpy(&dm->tdma_now, &pfwinfo->rpt_fbtc_tdma.finfo.tdma);
#else
		_chk_btc_err(btc, BTC_DCNT_TDMA_NONSYNC,
			     _tdma_cmp(&dm->tdma_now,
				       &pfwinfo->rpt_fbtc_tdma.finfo.tdma));
#endif
	}

	if (rpt_type == BTC_RPT_TYPE_SLOT) {
#if BTC_CX_FW_OFFLOAD /* update slot_now if fw offload for debug */
		_slots_cpy(dm->slot_now, pfwinfo->rpt_fbtc_slots.finfo.slot);
#else
		_chk_btc_err(btc, BTC_DCNT_SLOT_NONSYNC,
			     _tdma_cmp(dm->slot_now,
				       pfwinfo->rpt_fbtc_slots.finfo.slot));
#endif
	}

	if (rpt_type == BTC_RPT_TYPE_CYSTA &&
	    pcysta->cycles >= BTC_CYSTA_CHK_PERIOD) {
		/* Check Leak-AP */
		if (pcysta->slot_cnt[CXST_LK] != 0 &&
		    pcysta->leak_slot.cnt_rximr != 0 && dm->tdma_now.rxflctrl) {
			if (pcysta->slot_cnt[CXST_LK] <
			    BTC_LEAK_AP_TH * pcysta->leak_slot.cnt_rximr)
				dm->leak_ap = 1;
		}

		/* Check diff time between real WL slot and W1 slot */
		if (dm->tdma_now.type != CXTDMA_OFF) {
			wl_slot_set = dm->slot_now[CXST_W1].dur;

			if (pcysta->cycle_time.tavg[CXT_WL] > wl_slot_set) {
				diff_t = pcysta->cycle_time.tavg[CXT_WL] -
					 wl_slot_set;
				_chk_btc_err(btc, BTC_DCNT_WL_SLOT_DRIFT, diff_t);
			}
		}

		/* Check diff time between real BT slot and EBT/E5G slot */
		if (dm->tdma_now.type == CXTDMA_OFF &&
		    dm->tdma_now.ext_ctrl == CXECTL_EXT &&
		    btc->bt_req_len != 0) {
			bt_slot_real = pcysta->cycle_time.tavg[CXT_BT];

			if (btc->bt_req_len > bt_slot_real) {
				diff_t = btc->bt_req_len - bt_slot_real;
				_chk_btc_err(btc, BTC_DCNT_BT_SLOT_DRIFT, diff_t);
			}

		}

		_chk_btc_err(btc, BTC_DCNT_W1_FREEZE, pcysta->slot_cnt[CXST_W1]);
		_chk_btc_err(btc, BTC_DCNT_B1_FREEZE, pcysta->slot_cnt[CXST_B1]);
		_chk_btc_err(btc, BTC_DCNT_CYCLE_FREEZE, (u32)pcysta->cycles);
	}

	if (rpt_type == BTC_RPT_TYPE_CTRL) {
		prpt = &pfwinfo->rpt_ctrl.finfo;
		btc->fwinfo.rpt_en_map = prpt->rpt_info.en;
		wl->ver_info.fw_coex = prpt->wl_fw_info.cx_ver;
		wl->ver_info.fw = prpt->wl_fw_info.fw_ver;
		dm->wl_fw_cx_offload = !!(prpt->wl_fw_info.cx_offload);

		if (dm->wl_fw_cx_offload != BTC_CX_FW_OFFLOAD)
			dm->error.map.offload_mismatch = true;
		else
			dm->error.map.offload_mismatch = false;

		if (prpt->bt_rfk_cnt[BTC_BCNT_RFK_TIMEOUT] > 0)
			bt->rfk_info.map.timeout = 1;
		else
			bt->rfk_info.map.timeout = 0;

		dm->error.map.bt_rfk_timeout = bt->rfk_info.map.timeout;

		_chk_btc_err(btc, BTC_DCNT_RPT_FREEZE,
			     pfwinfo->event[BTF_EVNT_RPT]);

		/* To avoid I/O if WL LPS or power-off  */
		if (wl->status.map.lps != BTC_LPS_RF_OFF &&
		    !wl->status.map.rf_off) {
			btc->chip->ops->update_bt_cnt(btc);
			_chk_btc_err(btc, BTC_DCNT_BTCNT_FREEZE, 0);
			rtw_hal_mac_get_bt_polt_cnt(btc->hal, HW_PHY_0,
						    &polt_cnt);
			btc->cx.cnt_bt[BTC_BCNT_POLUT] = polt_cnt;
		}
	}

	if (rpt_type >= BTC_RPT_TYPE_BT_VER &&
	    rpt_type <= BTC_RPT_TYPE_BT_DEVICE)
		_update_bt_report(btc, rpt_type, pfinfo);

	return (rpt_len + BTC_RPT_HDR_SIZE);
}

static void _parse_btc_report(struct btc_t *btc, struct btf_fwinfo *pfwinfo,
			      u8 *pbuf, u32 buf_len)
{
	u32 index = 0, rpt_len = 0;

	while (pbuf) {
		if (index+2 >= RTW_PHL_BTC_FWINFO_BUF)
			break;
		/* At least 3 bytes: type(1) & len(2) */
		rpt_len = (pbuf[index+2] << 8) + pbuf[index+1];
		if ((index + rpt_len + BTC_RPT_HDR_SIZE) > buf_len)
			break;

		rpt_len = _chk_btc_report(btc, pfwinfo, pbuf, index);
		if (!rpt_len)
			break;
		index += rpt_len;
	}
}

static void _append_tdma(struct btc_t *btc, bool force_exec)
{
	struct btc_dm *dm = &btc->dm;
	struct btf_tlv *tlv = NULL;
	struct fbtc_1tdma *v = NULL;
	u16 len = btc->policy_len;

	if (!force_exec && !_tdma_cmp(&dm->tdma, &dm->tdma_now)) {
		PHL_TRACE(COMP_PHL_BTC, _PHL_DEBUG_, "[BTC], %s(): tdma no change!\n",
			  __func__);
		return;
	}

	tlv = (struct btf_tlv *)&btc->policy[len];
	tlv->type = CXPOLICY_TDMA;
	tlv->len = sizeof(struct fbtc_1tdma);
	v = (struct fbtc_1tdma *)&tlv->val[0];
	v->fver = FCX_TDMA_VER;

	_tdma_cpy(&v->tdma, &dm->tdma);

	btc->policy_len = len + 2 + sizeof(struct fbtc_1tdma);

	PHL_INFO("[BTC], %s: type:%d, rxflctrl=%d, txflctrl=%d, wtgle_n=%d, leak_n=%d, ext_ctrl=%d, rxflctrl_role=0x%x\n",
		 __func__, dm->tdma.type, dm->tdma.rxflctrl, dm->tdma.txflctrl,
		 dm->tdma.wtgle_n, dm->tdma.leak_n, dm->tdma.ext_ctrl,
		 dm->tdma.rxflctrl_role);
}

static void _append_slot(struct btc_t *btc, bool force_exec)
{
	struct btc_dm *dm = &btc->dm;
	struct btf_tlv *tlv = NULL;
	struct fbtc_1slot *v = NULL;
	u16 len = 0;
	u8 i, cnt = 0;

	for (i = 0; i < CXST_MAX; i++) {
		if (!force_exec && !_slot_cmp(&dm->slot[i], &dm->slot_now[i]))
			continue;

		len = btc->policy_len;

		tlv = (struct btf_tlv *)&btc->policy[len];
		tlv->type = CXPOLICY_SLOT;
		tlv->len = sizeof(struct fbtc_1slot);
		v = (struct fbtc_1slot *)&tlv->val[0];

		v->fver = FCX_SLOT_VER;
		v->sid = i;
		_slot_cpy(&v->slot, &dm->slot[i]);

		PHL_INFO("[BTC], %s: slot-%d: dur=%d, table=0x%08x, type=%d\n",
			 __func__, i,dm->slot[i].dur, dm->slot[i].cxtbl,
			 dm->slot[i].cxtype);
		cnt++;
		btc->policy_len = len + 2 + sizeof(struct fbtc_1slot);
	}

	if (cnt > 0)
		PHL_INFO("[BTC], %s: slot update (cnt=%d)!!\n", __func__, cnt);
}

/*
 * extern functions
 */

void hal_btc_fw_en_rpt(struct btc_t *btc, u32 rpt_map, u32 rpt_state)
{
	struct btc_ops *ops = btc->ops;
	struct btf_set_report r = {0};
	struct btf_fwinfo* fwinfo = &btc->fwinfo;
	u32 val = 0;
	u8 en;

	if (!ops || !ops->fw_cmd)
		return;

	en = rpt_state & 0x1;
	if (en)
		val = fwinfo->rpt_en_map | rpt_map;
	else
		val = fwinfo->rpt_en_map & (~rpt_map);

	if (val == fwinfo->rpt_en_map)
		return;

	fwinfo->rpt_en_map = val;

	r.fver = FCX_BTCRPT_VER;
	r.enable = val;
	r.para = en;

	ops->fw_cmd(btc, BTFC_SET, SET_REPORT_EN, (u8 *)&r, sizeof(r));
}

void hal_btc_fw_set_slots(struct btc_t *btc, u8 num, struct fbtc_slot *s)
{
#if !BTC_CX_FW_OFFLOAD
	struct rtw_hal_com_t *h = btc->hal;
	struct btc_ops *ops = btc->ops;
	struct btf_set_slot_table *tbl = NULL;
	u8 *ptr = NULL;
	u16 n = 0;

	if (!ops || !ops->fw_cmd)
		return;

	n = (sizeof(struct fbtc_slot) * num) + sizeof(*tbl) - 1;
	tbl = hal_mem_alloc(h, n);
	if (!tbl)
		return;

	tbl->fver = FCX_SLOT_VER;
	tbl->tbl_num = num;
	ptr = &tbl->buf[0];
	hal_mem_cpy(h, (void*)ptr, s, num * sizeof(struct fbtc_slot));

	ops->fw_cmd(btc, BTFC_SET, SET_SLOT_TABLE, (u8*)tbl, n);
	hal_mem_free(h, (void*)tbl, n);
#endif
}

/* set RPT_EN_MREG = 0 to stop 2s monitor timer in WL FW,
 * before SET_MREG_TABLE, and set RPT_EN_MREG = 1 after
 * SET_MREG_TABLE
 */
void hal_btc_fw_set_monreg(struct btc_t *btc)
{
	struct rtw_hal_com_t *h = btc->hal;
	struct btc_ops *ops = btc->ops;
	struct btf_set_mon_reg *monreg = NULL;
	u8 n, *ptr = NULL, ulen;
	u16 sz = 0;

	if (!ops || !ops->fw_cmd)
		return;

	n = btc->chip->mon_reg_num;

	if (n > CXMREG_MAX) {
		PHL_TRACE(COMP_PHL_BTC, _PHL_ERR_, "[BTC], mon reg count %d > %d\n",
			  n, CXMREG_MAX);
		return;
	}

	ulen = sizeof(struct fbtc_mreg);
	sz = (ulen * n) + sizeof(*monreg) - 1;
	monreg = hal_mem_alloc(h, sz);
	if (!monreg)
		return;

	monreg->fver = FCX_MREG_VER;
	monreg->reg_num = n;
	ptr = &monreg->buf[0];
	hal_mem_cpy(h, (void *)ptr, btc->chip->mon_reg, n * ulen);

	ops->fw_cmd(btc, BTFC_SET, SET_MREG_TABLE, (u8 *)monreg, sz);
	hal_mem_free(h, (void *)monreg, sz);
	hal_btc_fw_en_rpt(btc, RPT_EN_MREG, 1);
}

bool hal_btc_fw_set_1tdma(struct btc_t *btc, u16 len, u8 *buf)
{ /* for wlcli manual control  */
	struct btc_dm *dm = &btc->dm;

	if (len != sizeof(struct fbtc_tdma)) {
		PHL_TRACE(COMP_PHL_BTC, _PHL_ERR_, "[BTC], %s(): return because len != %d\n",
			 __func__, (int)sizeof(struct fbtc_tdma));
		return false;
	} else if (buf[0] >= CXTDMA_MAX) {
		PHL_TRACE(COMP_PHL_BTC, _PHL_ERR_, "[BTC], %s(): return because tdma_type >= %d\n",
			 __func__, CXTDMA_MAX);
		return false;
	}

	_tdma_cpy(&dm->tdma, buf);
	return true;
}

bool hal_btc_fw_set_1slot(struct btc_t *btc, u16 len, u8 *buf)
{ /* for wlcli manual control  */
	struct btc_dm *dm = &btc->dm;

	if (len != sizeof(struct fbtc_slot) + 1) {
		PHL_TRACE(COMP_PHL_BTC, _PHL_ERR_, "[BTC], %s(): return because len != %d\n",
			 __func__, (int)sizeof(struct fbtc_slot) + 1);
		return false;
	} else if (buf[0] >= CXST_MAX) {
		PHL_TRACE(COMP_PHL_BTC, _PHL_ERR_, "[BTC], %s(): return because slot_id >= %d\n",
			 __func__, CXST_MAX);
		return false;
	}

	_slot_cpy(&dm->slot[buf[0]], &buf[1]);
	return true;
}

bool hal_btc_fw_set_policy(struct btc_t *btc, bool force_exec, u16 policy_type,
			   const char* action)
{
	struct btc_dm *dm = &btc->dm;
	struct btc_ops *ops = btc->ops;
	u32 len = _os_strlen((u8 *)action) + 1;

	if (!ops || !ops->fw_cmd)
		return false;

	len = (len < BTC_ACT_MAXLEN) ? len : BTC_ACT_MAXLEN;
	_act_cpy(dm->run_action, (char*)action, len);
	_update_dm_step(btc, action);
	_update_dm_step(btc, id_to_str(BTC_STR_POLICY, (u32)policy_type));

	btc->policy_len = 0; /* clear length before append */
	btc->policy_type = policy_type;

	_append_tdma(btc, force_exec);
	_append_slot(btc, force_exec);

	if (btc->policy_len == 0 || btc->policy_len > BTC_POLICY_MAXLEN)
		return false;

	PHL_INFO("[BTC], %s(): action=%s -> policy type/len: 0x%04x/%d\n",
		 __func__, action, policy_type, btc->policy_len);

	if (dm->tdma.rxflctrl == CXFLC_NULLP)
		btc->hal->btc_ctrl.lps = 1;
	else
		btc->hal->btc_ctrl.lps = 0;

	if (btc->hal->btc_ctrl.lps == 1)
		hal_btc_notify_ps_tdma(btc, btc->hal->btc_ctrl.lps);

	ops->fw_cmd(btc, BTFC_SET, SET_CX_POLICY, btc->policy, btc->policy_len);

	_tdma_cpy(&dm->tdma_now, &dm->tdma);
	_slots_cpy(dm->slot_now, dm->slot);

	if (btc->hal->btc_ctrl.lps == 0)
		hal_btc_notify_ps_tdma(btc, btc->hal->btc_ctrl.lps);

	return true;
}

void hal_btc_fw_set_gpio_dbg(struct btc_t *btc, u8 type, u32 val)
{
	struct rtw_hal_com_t *h = btc->hal;
	struct btc_ops *ops = btc->ops;
	u8 data[7] = {0}, len = 0;

	if (!ops || !ops->fw_cmd || type >= CXDGPIO_MAX)
		return;

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

	data[0] = FCX_GPIODBG_VER;
	data[1] = 0;
	data[2] = type;

	switch(type) {
	case CXDGPIO_EN_MAP:
		len = sizeof(u32) + 3;
		hal_mem_cpy(h, &data[3], &val, sizeof(u32));
		break;
	case CXDGPIO_MUX_MAP:
		len = sizeof(8) * 2 + 3;
		data[3] = (u8)(val & bMASKB0);
		data[4] = (u8)((val & bMASKB1) >> 8);
		break;
	default:
		return;
	}

	ops->fw_cmd(btc, BTFC_SET, SET_GPIO_DBG, data, len);
}

void hal_btc_fw_set_drv_info(struct btc_t *btc, u8 type)
{
	struct rtw_hal_com_t *h = btc->hal;
	struct btc_wl_info *wl = &btc->cx.wl;
	struct btc_dm *dm = &btc->dm;
	struct btc_ops *ops = btc->ops;
	u8 buf[256] = {0};
	u8 sz = 0, n = 0;

	if (!ops || !ops->fw_cmd || type >= CXDRVINFO_MAX)
		return;

	switch (type) {
	case CXDRVINFO_INIT:
		n = sizeof(dm->init_info);
		sz = n + 2;

		if (sz > sizeof(buf))
			return;

		buf[0] = CXDRVINFO_INIT;
		buf[1] = n;
		hal_mem_cpy(h, (void *)&buf[2], &dm->init_info, n);
		break;
	case CXDRVINFO_ROLE:
		n = sizeof(wl->role_info);
		sz = n + 2;

		if (sz > sizeof(buf))
			return;

		buf[0] = CXDRVINFO_ROLE;
		buf[1] = n;
		hal_mem_cpy(h, (void *)&buf[2], &wl->role_info, n);
		break;
	case CXDRVINFO_CTRL:
		n = sizeof(btc->ctrl);
		sz = n + 2;

		if (sz > sizeof(buf))
			return;

		buf[0] = CXDRVINFO_CTRL;
		buf[1] = n;
		hal_mem_cpy(h, (void *)&buf[2], &btc->ctrl, n);
		break;
	case CXDRVINFO_RFK:
		n = sizeof(wl->rfk_info);
		sz = n + 2;

		if (sz > sizeof(buf))
			return;

		buf[0] = CXDRVINFO_RFK;
		buf[1] = n;
		hal_mem_cpy(h, (void *)&buf[2], &wl->rfk_info, n);
		break;
#if BTC_CX_FW_OFFLOAD
	case CXDRVINFO_DBCC:
		n = sizeof(wl->dbcc_info);
		sz = n + 2;

		if (sz > sizeof(buf))
			return;

		buf[0] = CXDRVINFO_DBCC;
		buf[1] = n;
		hal_mem_cpy(h, (void *)&buf[2], &wl->dbcc_info, n);
		break;
	case CXDRVINFO_SMAP:
		n = sizeof(wl->status);
		sz = n + 2;

		if (sz > sizeof(buf))
			return;

		buf[0] = CXDRVINFO_SMAP;
		buf[1] = n;
		hal_mem_cpy(h, (void *)&buf[2], &wl->status, n);
		break;
	case CXDRVINFO_RUN:
		n = BTC_RSN_MAXLEN;
		sz = n + 2;

		if (sz > sizeof(buf))
			return;

		buf[0] = CXDRVINFO_RUN;
		buf[1] = n;
		hal_mem_cpy(h, (void *)&buf[2], dm->run_reason, n);
		break;
	case CXDRVINFO_SCAN:
		n = sizeof(wl->scan_info);
		sz = n + 2;

		if (sz > sizeof(buf))
			return;

		buf[0] = CXDRVINFO_SCAN;
		buf[1] = n;
		hal_mem_cpy(h, (void *)&buf[2], &wl->scan_info, n);
		break;
#endif
	default:
		return;
	}

	ops->fw_cmd(btc, BTFC_SET, SET_DRV_INFO, (u8*)buf, sz);
}

void hal_btc_fw_set_drv_event(struct btc_t *btc, u8 type)
{
	struct btc_ops *ops = btc->ops;

	if (!ops || !ops->fw_cmd)
		return;

	ops->fw_cmd(btc, BTFC_SET, SET_DRV_EVENT, &type, 1);
}

void hal_btc_fw_set_bt(struct btc_t *btc, u8 type, u16 len, u8* buf)
{
	struct btc_ops *ops = btc->ops;

	if (!ops || !ops->fw_cmd ||
	    (type < SET_BT_WREG_ADDR || type > SET_BT_GOLDEN_RX_RANGE))
		return;

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

	ops->fw_cmd(btc, BTFC_SET, type, buf, len);
}

void hal_btc_fw_event(struct btc_t *btc, u8 evt_id, void *data, u32 len)
{
	struct btf_fwinfo *pfwinfo = &btc->fwinfo;

	if (!len || !data)
		return;

	switch (evt_id) {
	case BTF_EVNT_RPT:
		_parse_btc_report(btc, pfwinfo, data, len);
		break;
	default:
		break;
	}
}

#endif
