[rdkb][common][bsp][Refactor and sync wifi from openwrt]

[Description]
e6d9f8b2 [MAC80211][misc][Add Filogic 880 Non-MLO SDK Release. Add missing firmwares]
9def0c8f [MAC80211][WiFi7][ucode][Not to remove or create interface for non-primary MLD link]
c77412af [MAC80211][WiFi7][Misc][Add MLD configuration options]
d0bc5c22 [MAC80211][misc][Add Filogic 880 Non-MLO SDK Release]
01817e1f [mac80211][WiFi7][misc][Fix patch fail]
d6a80bd6 [MAC80211][WiFi6][Misc][Add lpi, duplicate mode and sku index]
0977e5a6 [MAC80211][WiFi6][hostapd][Add txpower vendor command]
2e2eb49f [MAC80211][WiFi6][mt76][Add support for lpi and duplicate mode]
c4856a8a [MAC80211][WiFi6][core][Add sta info flush and send deauth during DFS channel switch]
c586f5f5 [MAC80211][WiFi6][mt76][rebase patches]
fabcdbbd [MAC80211][WiFi6][mt76][Support thermal recal debug command]
1cba3dbf [MAC80211][WiFi6][mt76][Fix the fw version of wm and wa are on contrary]
99500b19 [MAC80211][WiFi6][core][Revert sending deauth frame for DFS channel switch]

[Release-log]

Change-Id: I357f4a5fec68ce0210927e1b4f172fcd3196139d
diff --git a/recipes-wifi/linux-mt76/files/patches-3.x/1034-mtk-wifi-mt76-testmode-add-testmode-bf-support.patch b/recipes-wifi/linux-mt76/files/patches-3.x/1034-mtk-wifi-mt76-testmode-add-testmode-bf-support.patch
new file mode 100644
index 0000000..6cd784f
--- /dev/null
+++ b/recipes-wifi/linux-mt76/files/patches-3.x/1034-mtk-wifi-mt76-testmode-add-testmode-bf-support.patch
@@ -0,0 +1,1833 @@
+From cc8ecb9fe62c4689899d79d8c710c2756a2bf131 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Thu, 6 Apr 2023 16:40:28 +0800
+Subject: [PATCH 1034/1044] mtk: wifi: mt76: testmode: add testmode bf support
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+
+Add iTest additional bf command
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+
+fw return Gx (5g band) ibf cal struct for both 2G and 5G ibf.
+Therefore, memcpy cannot be used for 2G ibf cal.
+https://gerrit.mediatek.inc/c/neptune/wlan_driver/logan/+/8206056
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt76.h               |   5 +
+ mt76_connac_mcu.h    |   4 +-
+ mt7996/mcu.c         |   8 +-
+ mt7996/mcu.h         |  45 ++-
+ mt7996/mt7996.h      |  12 +-
+ mt7996/mtk_debugfs.c |   6 +-
+ mt7996/mtk_mcu.c     |  79 ++++-
+ mt7996/mtk_mcu.h     | 338 +++++++++++++++++++-
+ mt7996/regs.h        |   3 +
+ mt7996/testmode.c    | 744 +++++++++++++++++++++++++++++++++++++++++--
+ mt7996/testmode.h    |  19 ++
+ testmode.c           |  60 ++++
+ testmode.h           |  53 +++
+ tools/fields.c       |  37 +++
+ 14 files changed, 1354 insertions(+), 59 deletions(-)
+
+diff --git a/mt76.h b/mt76.h
+index 9e8848f7..2a7b0ed9 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -751,6 +751,11 @@ struct mt76_testmode_data {
+ 	u32 tx_time;
+ 	u32 tx_ipg;
+ 
++	u8 txbf_act;
++	u16 txbf_param[8];
++	bool is_txbf_dut;
++	bool bf_en;
++	bool bf_ever_en;
+ 	bool ibf;
+ 	bool ebf;
+ 
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 06c8b960..2cdd5b9c 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -488,7 +488,8 @@ struct sta_rec_bf {
+ 	bool codebook75_mu;
+ 
+ 	u8 he_ltf;
+-	u8 rsv[3];
++	u8 pp_fd_val;
++	u8 rsv[2];
+ } __packed;
+ 
+ struct sta_rec_bfee {
+@@ -1274,6 +1275,7 @@ enum {
+ 	MCU_UNI_CMD_VOW = 0x37,
+ 	MCU_UNI_CMD_PP = 0x38,
+ 	MCU_UNI_CMD_FIXED_RATE_TABLE = 0x40,
++	MCU_UNI_CMD_TESTMODE_TRX_PARAM = 0x42,
+ 	MCU_UNI_CMD_TESTMODE_CTRL = 0x46,
+ 	MCU_UNI_CMD_PRECAL_RESULT = 0x47,
+ 	MCU_UNI_CMD_RRO = 0x57,
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 7bd1221b..5b5ddafb 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -1067,7 +1067,12 @@ mt7996_mcu_bss_basic_tlv(struct sk_buff *skb,
+ 	bss->hw_bss_idx = idx;
+ 
+ 	if (vif->type == NL80211_IFTYPE_MONITOR) {
+-		memcpy(bss->bssid, phy->macaddr, ETH_ALEN);
++		struct mt76_testmode_data *td = &phy->test;
++
++		if (!td->bf_en)
++			memcpy(bss->bssid, phy->macaddr, ETH_ALEN);
++		else
++			memcpy(bss->bssid, td->addr[2], ETH_ALEN);
+ 		return 0;
+ 	}
+ 
+@@ -4118,7 +4123,6 @@ int mt7996_mcu_set_ser(struct mt7996_dev *dev, u8 action, u8 val, u8 band)
+ int mt7996_mcu_set_txbf(struct mt7996_dev *dev, u8 action)
+ {
+ #define MT7996_BF_MAX_SIZE	sizeof(union bf_tag_tlv)
+-#define BF_PROCESSING	4
+ 	struct uni_header hdr;
+ 	struct sk_buff *skb;
+ 	struct tlv *tlv;
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index af078edd..054a616b 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -685,6 +685,22 @@ struct bf_sounding_on {
+ 	__le32 snd_period;
+ } __packed;
+ 
++enum sounding_mode {
++	SU_SOUNDING,
++	MU_SOUNDING,
++	SU_PERIODIC_SOUNDING,
++	MU_PERIODIC_SOUNDING,
++	BF_PROCESSING,
++	TXCMD_NONTB_SU_SOUNDING,
++	TXCMD_VHT_MU_SOUNDING,
++	TXCMD_TB_PER_BRP_SOUNDING,
++	TXCMD_TB_SOUNDING,
++
++	/* keep last */
++	NUM_SOUNDING_MODE,
++	SOUNDING_MODE_MAX = NUM_SOUNDING_MODE - 1,
++};
++
+ struct bf_hw_en_status_update {
+ 	__le16 tag;
+ 	__le16 len;
+@@ -710,6 +726,24 @@ union bf_tag_tlv {
+ 	struct bf_mod_en_ctrl bf_mod_en;
+ };
+ 
++enum {
++	BF_SOUNDING_OFF = 0,
++	BF_SOUNDING_ON = 1,
++	BF_DATA_PACKET_APPLY = 2,
++	BF_PFMU_TAG_READ = 5,
++	BF_PFMU_TAG_WRITE = 6,
++	BF_STA_REC_READ = 11,
++	BF_PHASE_CALIBRATION = 12,
++	BF_IBF_PHASE_COMP = 13,
++	BF_PROFILE_WRITE_20M_ALL = 15,
++	BF_HW_EN_UPDATE = 17,
++	BF_MOD_EN_CTRL = 20,
++	BF_FBRPT_DBG_INFO_READ = 23,
++	BF_TXSND_INFO = 24,
++	BF_CMD_TXCMD = 27,
++	BF_CFG_PHY = 28,
++};
++
+ struct ra_rate {
+ 	__le16 wlan_idx;
+ 	u8 mode;
+@@ -771,17 +805,6 @@ enum {
+ #define MUMIMO_UL                      BIT(3)
+ #define MUMIMO_DL_CERT                 BIT(4)
+ 
+-enum {
+-	BF_SOUNDING_ON = 1,
+-	BF_PFMU_TAG_READ = 5,
+-	BF_STA_REC_READ = 11,
+-	BF_HW_EN_UPDATE = 17,
+-	BF_MOD_EN_CTRL = 20,
+-	BF_FBRPT_DBG_INFO_READ = 23,
+-	BF_TXSND_INFO = 24,
+-	BF_CFG_PHY = 28,
+-};
+-
+ enum {
+ 	CMD_BAND_NONE,
+ 	CMD_BAND_24G,
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index b9c37612..2f76a0af 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -480,6 +480,14 @@ struct mt7996_dev {
+ #ifdef CONFIG_MTK_VENDOR
+ 	bool cert_mode;
+ #endif
++
++#if defined CONFIG_NL80211_TESTMODE || defined CONFIG_MTK_DEBUG
++	struct {
++		void *txbf_phase_cal;
++		void *txbf_pfmu_data;
++		void *txbf_pfmu_tag;
++	} test;
++#endif
+ };
+ 
+ enum {
+@@ -819,7 +827,7 @@ int mt7996_mcu_muru_dbg_info(struct mt7996_dev *dev, u16 item, u8 val);
+ int mt7996_mcu_set_sr_enable(struct mt7996_phy *phy, u8 action, u64 val, bool set);
+ void mt7996_mcu_rx_sr_event(struct mt7996_dev *dev, struct sk_buff *skb);
+ int mt7996_mcu_set_dup_wtbl(struct mt7996_dev *dev);
+-int mt7996_mcu_set_txbf_internal(struct mt7996_phy *phy, u8 action, int idx);
++int mt7996_mcu_set_txbf_internal(struct mt7996_phy *phy, u8 action, int idx, bool bfer);
+ void mt7996_mcu_rx_bf_event(struct mt7996_dev *dev, struct sk_buff *skb);
+ int mt7996_mcu_set_muru_fixed_rate_enable(struct mt7996_dev *dev, u8 action, int val);
+ int mt7996_mcu_set_muru_fixed_rate_parameter(struct mt7996_dev *dev, u8 action, void *para);
+@@ -831,10 +839,12 @@ int mt7996_mcu_set_rfeature_trig_type(struct mt7996_phy *phy, u8 enable, u8 trig
+ void mt7996_mcu_set_ppdu_tx_type(struct mt7996_phy *phy, u8 ppdu_type);
+ void mt7996_mcu_set_nusers_ofdma(struct mt7996_phy *phy, u8 type, u8 ofdma_user_cnt);
+ void mt7996_mcu_set_cert(struct mt7996_phy *phy, u8 type);
++void mt7996_tm_update_channel(struct mt7996_phy *phy);
+ #endif
+ 
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
+ int mt7996_dma_rro_init(struct mt7996_dev *dev);
+ #endif /* CONFIG_NET_MEDIATEK_SOC_WED */
+ 
++
+ #endif
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index 87c80828..9aeb6437 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -2899,7 +2899,7 @@ mt7996_starec_bf_read_set(void *data, u64 wlan_idx)
+ {
+ 	struct mt7996_phy *phy = data;
+ 
+-	return mt7996_mcu_set_txbf_internal(phy, BF_STA_REC_READ, wlan_idx);
++	return mt7996_mcu_set_txbf_internal(phy, BF_STA_REC_READ, wlan_idx, 0);
+ }
+ DEFINE_DEBUGFS_ATTRIBUTE(fops_starec_bf_read, NULL,
+ 			 mt7996_starec_bf_read_set, "%lld\n");
+@@ -2943,7 +2943,7 @@ mt7996_bf_fbk_rpt_set(void *data, u64 wlan_idx)
+ {
+ 	struct mt7996_phy *phy = data;
+ 
+-	return mt7996_mcu_set_txbf_internal(phy, BF_FBRPT_DBG_INFO_READ, wlan_idx);
++	return mt7996_mcu_set_txbf_internal(phy, BF_FBRPT_DBG_INFO_READ, wlan_idx, 0);
+ }
+ DEFINE_DEBUGFS_ATTRIBUTE(fops_bf_fbk_rpt, NULL,
+ 			 mt7996_bf_fbk_rpt_set, "%lld\n");
+@@ -2953,7 +2953,7 @@ mt7996_bf_pfmu_tag_read_set(void *data, u64 wlan_idx)
+ {
+ 	struct mt7996_phy *phy = data;
+ 
+-	return mt7996_mcu_set_txbf_internal(phy, BF_PFMU_TAG_READ, wlan_idx);
++	return mt7996_mcu_set_txbf_internal(phy, BF_PFMU_TAG_READ, wlan_idx, 1);
+ }
+ DEFINE_DEBUGFS_ATTRIBUTE(fops_bf_pfmu_tag_read, NULL,
+ 			 mt7996_bf_pfmu_tag_read_set, "%lld\n");
+diff --git a/mt7996/mtk_mcu.c b/mt7996/mtk_mcu.c
+index cacca449..f70bd0be 100644
+--- a/mt7996/mtk_mcu.c
++++ b/mt7996/mtk_mcu.c
+@@ -295,7 +295,7 @@ __mt7996_mcu_add_uni_tlv(struct sk_buff *skb, u16 tag, u16 len)
+ 	return ptlv;
+ }
+ 
+-int mt7996_mcu_set_txbf_internal(struct mt7996_phy *phy, u8 action, int idx)
++int mt7996_mcu_set_txbf_internal(struct mt7996_phy *phy, u8 action, int idx, bool bfer)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+ #define MT7996_MTK_BF_MAX_SIZE	sizeof(struct bf_starec_read)
+@@ -318,9 +318,8 @@ int mt7996_mcu_set_txbf_internal(struct mt7996_phy *phy, u8 action, int idx)
+ 
+ 		tlv = __mt7996_mcu_add_uni_tlv(skb, action, sizeof(*req));
+ 		req = (struct bf_pfmu_tag *)tlv;
+-#define BFER 1
+ 		req->pfmu_id = idx;
+-		req->bfer = BFER;
++		req->bfer = bfer;
+ 		req->band_idx = phy->mt76->band_idx;
+ 		break;
+ 	}
+@@ -432,10 +431,36 @@ int mt7996_mcu_set_txbf_snd_info(struct mt7996_phy *phy, void *para)
+ 	return mt76_mcu_skb_send_msg(&phy->dev->mt76, skb, MCU_WM_UNI_CMD(BF), false);
+ }
+ 
++static inline void
++mt7996_ibf_phase_assign(struct mt7996_dev *dev,
++			struct mt7996_ibf_cal_info *cal,
++			struct mt7996_txbf_phase *phase)
++{
++	/* fw return ibf calibrated data with
++	 * the mt7996_txbf_phase_info_5g struct for both 2G and 5G.
++	 * Therefore, memcpy cannot be used here.
++	 */
++	phase_assign(cal->group, m_t0_h, true);
++	phase_assign(cal->group, m_t1_h, true);
++	phase_assign(cal->group, m_t2_h, true);
++	phase_assign(cal->group, m_t2_h_sx2, false);
++	phase_assign_rx(cal->group, r0);
++	phase_assign_rx(cal->group, r1);
++	phase_assign_rx(cal->group, r2);
++	phase_assign_rx(cal->group, r3);
++	phase_assign_rx_g0(cal->group, r2_sx2);
++	phase_assign_rx_g0(cal->group, r3_sx2);
++	phase_assign(cal->group, r0_reserved, false);
++	phase_assign(cal->group, r1_reserved, false);
++	phase_assign(cal->group, r2_reserved, false);
++	phase_assign(cal->group, r3_reserved, false);
++	phase_assign(cal->group, r2_sx2_reserved, false);
++	phase_assign(cal->group, r3_sx2_reserved, false);
++}
++
+ void
+ mt7996_mcu_rx_bf_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ {
+-#define HE_MODE 3
+ 	struct mt7996_mcu_bf_basic_event *event;
+ 
+ 	event = (struct mt7996_mcu_bf_basic_event *)skb->data;
+@@ -470,13 +495,12 @@ mt7996_mcu_rx_bf_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 			 tag->t1.nr, tag->t1.nc, tag->t1.ngroup, tag->t1.lm, tag->t1.codebook,
+ 			 tag->t1.mob_cal_en);
+ 
+-		if (tag->t1.lm <= HE_MODE) {
++		if (tag->t1.lm <= BF_LM_HE)
+ 			dev_info(dev->mt76.dev, "RU start = %d, RU end = %d\n",
+ 				 tag->t1.field.ru_start_id, tag->t1.field.ru_end_id);
+-		} else {
++		else
+ 			dev_info(dev->mt76.dev, "PartialBW = %d\n",
+ 				 tag->t1.bw_info.partial_bw_info);
+-		}
+ 
+ 		dev_info(dev->mt76.dev, "Mem Col1 = %d, Mem Row1 = %d, Mem Col2 = %d, Mem Row2 = %d\n",
+ 			 tag->t1.col_id1, tag->t1.row_id1, tag->t1.col_id2, tag->t1.row_id2);
+@@ -730,6 +754,47 @@ mt7996_mcu_rx_bf_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 
+ 		break;
+ 	}
++	case UNI_EVENT_BF_CAL_PHASE: {
++		struct mt7996_ibf_cal_info *cal;
++		struct mt7996_txbf_phase_out phase_out;
++		struct mt7996_txbf_phase *phase;
++
++		cal = (struct mt7996_ibf_cal_info *)skb->data;
++		phase = (struct mt7996_txbf_phase *)dev->test.txbf_phase_cal;
++		memcpy(&phase_out, &cal->phase_out, sizeof(phase_out));
++		switch (cal->cal_type) {
++		case IBF_PHASE_CAL_NORMAL:
++		case IBF_PHASE_CAL_NORMAL_INSTRUMENT:
++			/* Only calibrate group M */
++			if (cal->group_l_m_n != GROUP_M)
++				break;
++			phase = &phase[cal->group];
++			phase->status = cal->status;
++			dev_info(dev->mt76.dev, "Calibrated result = %d\n", phase->status);
++			dev_info(dev->mt76.dev, "Group %d and Group M\n", cal->group);
++			mt7996_ibf_phase_assign(dev, cal, phase);
++			break;
++		case IBF_PHASE_CAL_VERIFY:
++		case IBF_PHASE_CAL_VERIFY_INSTRUMENT:
++			dev_info(dev->mt76.dev, "Verification result = %d\n", cal->status);
++			break;
++		default:
++			break;
++		}
++
++		dev_info(dev->mt76.dev, "c0_uh = %d, c1_uh = %d, c2_uh = %d, c3_uh = %d\n",
++			 phase_out.c0_uh, phase_out.c1_uh, phase_out.c2_uh, phase_out.c3_uh);
++		dev_info(dev->mt76.dev, "c0_h = %d, c1_h = %d, c2_h = %d, c3_h = %d\n",
++			 phase_out.c0_h, phase_out.c1_h, phase_out.c2_h, phase_out.c3_h);
++		dev_info(dev->mt76.dev, "c0_mh = %d, c1_mh = %d, c2_mh = %d, c3_mh = %d\n",
++			 phase_out.c0_mh, phase_out.c1_mh, phase_out.c2_mh, phase_out.c3_mh);
++		dev_info(dev->mt76.dev, "c0_m = %d, c1_m = %d, c2_m = %d, c3_m = %d\n",
++			 phase_out.c0_m, phase_out.c1_m, phase_out.c2_m, phase_out.c3_m);
++		dev_info(dev->mt76.dev, "c0_l = %d, c1_l = %d, c2_l = %d, c3_l = %d\n",
++			 phase_out.c0_l, phase_out.c1_l, phase_out.c2_l, phase_out.c3_l);
++
++		break;
++	}
+ 	default:
+ 		dev_info(dev->mt76.dev, "%s: unknown bf event tag %d\n",
+ 			 __func__, event->tag);
+diff --git a/mt7996/mtk_mcu.h b/mt7996/mtk_mcu.h
+index 98c9660a..252ae98e 100644
+--- a/mt7996/mtk_mcu.h
++++ b/mt7996/mtk_mcu.h
+@@ -189,6 +189,164 @@ struct bf_txsnd_info {
+ 	u8 __rsv[2];
+ } __packed;
+ 
++#define MAX_PHASE_GROUP_NUM	9
++
++struct bf_phase_comp {
++	__le16 tag;
++	__le16 len;
++
++	u8 bw;
++	u8 jp_band;
++	u8 band_idx;
++	bool read_from_e2p;
++	bool disable;
++	u8 group;
++	u8 rsv[2];
++	u8 buf[44];
++} __packed;
++
++struct bf_tx_apply {
++	__le16 tag;
++	__le16 len;
++
++	__le16 wlan_idx;
++	bool ebf;
++	bool ibf;
++	bool mu_txbf;
++	bool phase_cal;
++	u8 rsv[2];
++} __packed;
++
++struct bf_phase_cal {
++	__le16 tag;
++	__le16 len;
++
++	u8 group_l_m_n;
++	u8 group;
++	u8 sx2;
++	u8 cal_type;
++	u8 lna_gain_level;
++	u8 band_idx;
++	u8 rsv[2];
++} __packed;
++
++struct bf_txcmd {
++	__le16 tag;
++	__le16 len;
++
++	u8 action;
++	u8 bf_manual;
++	u8 bf_bit;
++	u8 rsv[5];
++} __packed;
++
++struct bf_pfmu_data_all {
++	__le16 tag;
++	__le16 len;
++
++	u8 pfmu_id;
++	u8 band_idx;
++	u8 rsv[2];
++
++	u8 buf[512];
++} __packed;
++
++#define TXBF_DUT_MAC_SUBADDR		0x22
++#define TXBF_GOLDEN_MAC_SUBADDR		0x11
++
++struct mt7996_tm_bf_req {
++	u8 _rsv[4];
++
++	union {
++		struct bf_sounding_on sounding;
++		struct bf_tx_apply tx_apply;
++		struct bf_pfmu_tag pfmu_tag;
++		struct bf_pfmu_data_all pfmu_data_all;
++		struct bf_phase_cal phase_cal;
++		struct bf_phase_comp phase_comp;
++		struct bf_txcmd txcmd;
++	};
++} __packed;
++
++enum tm_trx_mac_type {
++	TM_TRX_MAC_TX = 1,
++	TM_TRX_MAC_RX,
++	TM_TRX_MAC_TXRX,
++	TM_TRX_MAC_TXRX_RXV,
++	TM_TRX_MAC_RXV,
++	TM_TRX_MAC_RX_RXV,
++};
++
++enum tm_trx_param_idx {
++	TM_TRX_PARAM_RSV,
++	/* MAC */
++	TM_TRX_PARAM_SET_TRX,
++	TM_TRX_PARAM_RX_FILTER,
++	TM_TRX_PARAM_RX_FILTER_PKT_LEN,
++	TM_TRX_PARAM_SLOT_TIME,
++	TM_TRX_PARAM_CLEAN_PERSTA_TXQUEUE,
++	TM_TRX_PARAM_AMPDU_WTBL,
++	TM_TRX_PARAM_MU_RX_AID,
++	TM_TRX_PARAM_PHY_MANUAL_TX,
++
++	/* PHY */
++	TM_TRX_PARAM_RX_PATH,
++	TM_TRX_PARAM_TX_STREAM,
++	TM_TRX_PARAM_TSSI_STATUS,
++	TM_TRX_PARAM_DPD_STATUS,
++	TM_TRX_PARAM_RATE_POWER_OFFSET_ON_OFF,
++	TM_TRX_PARAM_THERMO_COMP_STATUS,
++	TM_TRX_PARAM_FREQ_OFFSET,
++	TM_TRX_PARAM_FAGC_RSSI_PATH,
++	TM_TRX_PARAM_PHY_STATUS_COUNT,
++	TM_TRX_PARAM_RXV_INDEX,
++
++	TM_TRX_PARAM_ANTENNA_PORT,
++	TM_TRX_PARAM_THERMAL_ONOFF,
++	TM_TRX_PARAM_TX_POWER_CONTROL_ALL_RF,
++	TM_TRX_PARAM_RATE_POWER_OFFSET,
++	TM_TRX_PARAM_SLT_CMD_TEST,
++	TM_TRX_PARAM_SKU,
++	TM_TRX_PARAM_POWER_PERCENTAGE_ON_OFF,
++	TM_TRX_PARAM_BF_BACKOFF_ON_OFF,
++	TM_TRX_PARAM_POWER_PERCENTAGE_LEVEL,
++	TM_TRX_PARAM_FRTBL_CFG,
++	TM_TRX_PARAM_PREAMBLE_PUNC_ON_OFF,
++
++	TM_TRX_PARAM_MAX_NUM,
++};
++
++enum trx_action {
++	TM_TRX_ACTION_SET,
++	TM_TRX_ACTION_GET,
++};
++
++struct tm_trx_set {
++	u8 type;
++	u8 enable;
++	u8 band_idx;
++	u8 rsv;
++} __packed;
++
++struct mt7996_tm_trx_req {
++	u8 param_num;
++	u8 _rsv[3];
++
++	__le16 tag;
++	__le16 len;
++
++	__le16 param_idx;
++	u8 band_idx;
++	u8 testmode_en;
++	u8 action;
++	u8 rsv[3];
++
++	u32 data;
++	struct tm_trx_set set_trx;
++
++	u8 buf[220];
++} __packed;
++
+ struct mt7996_mcu_bf_basic_event {
+ 	struct mt7996_mcu_rxd rxd;
+ 
+@@ -394,6 +552,181 @@ struct mt7996_pfmu_tag_event {
+ 	struct mt7996_pfmu_tag2 t2;
+ };
+ 
++struct mt7996_pfmu_tag {
++	struct mt7996_pfmu_tag1 t1;
++	struct mt7996_pfmu_tag2 t2;
++};
++
++enum bf_lm_type {
++	BF_LM_LEGACY,
++	BF_LM_HT,
++	BF_LM_VHT,
++	BF_LM_HE,
++	BF_LM_EHT,
++};
++
++struct mt7996_txbf_phase_out {
++	u8 c0_l;
++	u8 c1_l;
++	u8 c2_l;
++	u8 c3_l;
++	u8 c0_m;
++	u8 c1_m;
++	u8 c2_m;
++	u8 c3_m;
++	u8 c0_mh;
++	u8 c1_mh;
++	u8 c2_mh;
++	u8 c3_mh;
++	u8 c0_h;
++	u8 c1_h;
++	u8 c2_h;
++	u8 c3_h;
++	u8 c0_uh;
++	u8 c1_uh;
++	u8 c2_uh;
++	u8 c3_uh;
++};
++
++struct mt7996_txbf_rx_phase_2g {
++	u8 rx_uh;
++	u8 rx_h;
++	u8 rx_m;
++	u8 rx_l;
++	u8 rx_ul;
++};
++
++struct mt7996_txbf_rx_phase_5g {
++	u8 rx_uh;
++	u8 rx_h;
++	u8 rx_mh;
++	u8 rx_m;
++	u8 rx_l;
++	u8 rx_ul;
++};
++
++struct mt7996_txbf_phase_info_2g {
++	struct mt7996_txbf_rx_phase_2g r0;
++	struct mt7996_txbf_rx_phase_2g r1;
++	struct mt7996_txbf_rx_phase_2g r2;
++	struct mt7996_txbf_rx_phase_2g r3;
++	struct mt7996_txbf_rx_phase_2g r2_sx2;
++	struct mt7996_txbf_rx_phase_2g r3_sx2;
++	u8 m_t0_h;
++	u8 m_t1_h;
++	u8 m_t2_h;
++	u8 m_t2_h_sx2;
++	u8 r0_reserved;
++	u8 r1_reserved;
++	u8 r2_reserved;
++	u8 r3_reserved;
++	u8 r2_sx2_reserved;
++	u8 r3_sx2_reserved;
++};
++
++struct mt7996_txbf_phase_info_5g {
++	struct mt7996_txbf_rx_phase_5g r0;
++	struct mt7996_txbf_rx_phase_5g r1;
++	struct mt7996_txbf_rx_phase_5g r2;
++	struct mt7996_txbf_rx_phase_5g r3;
++	struct mt7996_txbf_rx_phase_2g r2_sx2;	/* no middle-high in r2_sx2 */
++	struct mt7996_txbf_rx_phase_2g r3_sx2;	/* no middle-high in r3_sx2 */
++	u8 m_t0_h;
++	u8 m_t1_h;
++	u8 m_t2_h;
++	u8 m_t2_h_sx2;
++	u8 r0_reserved;
++	u8 r1_reserved;
++	u8 r2_reserved;
++	u8 r3_reserved;
++	u8 r2_sx2_reserved;
++	u8 r3_sx2_reserved;
++};
++
++struct mt7996_txbf_phase {
++	u8 status;
++	union {
++		struct mt7996_txbf_phase_info_2g phase_2g;
++		struct mt7996_txbf_phase_info_5g phase_5g;
++	};
++};
++
++#define phase_assign(group, field, dump, ...)	({						\
++	if (group) {										\
++		phase->phase_5g.field = cal->phase_5g.field;					\
++		if (dump)									\
++			dev_info(dev->mt76.dev, "%s = %d\n", #field, phase->phase_5g.field);	\
++	} else {										\
++		phase->phase_2g.field = cal->phase_5g.field;					\
++		if (dump)									\
++			dev_info(dev->mt76.dev, "%s = %d\n", #field, phase->phase_2g.field);	\
++	}											\
++})
++
++#define phase_assign_rx_g0(group, rx, ...)	({						\
++	phase_assign(group, rx.rx_uh, false);							\
++	phase_assign(group, rx.rx_h, false);							\
++	phase_assign(group, rx.rx_m, false);							\
++	phase_assign(group, rx.rx_l, false);							\
++	phase_assign(group, rx.rx_ul, false);							\
++})
++
++#define phase_assign_rx(group, rx, ...)	({							\
++	if (group) {										\
++		phase_assign(group, rx.rx_uh, true);						\
++		phase_assign(group, rx.rx_h, true);						\
++		phase->phase_5g.rx.rx_mh = cal->phase_5g.rx.rx_mh;				\
++		dev_info(dev->mt76.dev, "%s.rx_mh = %d\n", #rx, phase->phase_5g.rx.rx_mh);	\
++		phase_assign(group, rx.rx_m, true);						\
++		phase_assign(group, rx.rx_l, true);						\
++		phase_assign(group, rx.rx_ul, true);						\
++	} else {										\
++		phase_assign(group, rx.rx_uh, true);						\
++		phase_assign(group, rx.rx_h, true);						\
++		phase_assign(group, rx.rx_m, true);						\
++		phase_assign(group, rx.rx_l, true);						\
++		phase_assign(group, rx.rx_ul, true);						\
++	}											\
++})
++
++#define GROUP_L		0
++#define GROUP_M		1
++#define GROUP_H		2
++
++struct mt7996_pfmu_data {
++	__le16 subc_idx;
++	__le16 phi11;
++	__le16 phi21;
++	__le16 phi31;
++};
++
++struct mt7996_ibf_cal_info {
++	struct mt7996_mcu_bf_basic_event event;
++
++	u8 category_id;
++	u8 group_l_m_n;
++	u8 group;
++	bool sx2;
++	u8 status;
++	u8 cal_type;
++	u8 _rsv[2];
++	struct mt7996_txbf_phase_out phase_out;
++	union {
++		struct mt7996_txbf_phase_info_2g phase_2g;
++		struct mt7996_txbf_phase_info_5g phase_5g;
++	};
++} __packed;
++
++enum {
++	IBF_PHASE_CAL_UNSPEC,
++	IBF_PHASE_CAL_NORMAL,
++	IBF_PHASE_CAL_VERIFY,
++	IBF_PHASE_CAL_NORMAL_INSTRUMENT,
++	IBF_PHASE_CAL_VERIFY_INSTRUMENT,
++};
++
++#define MT7996_TXBF_SUBCAR_NUM	64
++
+ enum {
+ 	UNI_EVENT_BF_PFMU_TAG = 0x5,
+ 	UNI_EVENT_BF_PFMU_DATA = 0x7,
+@@ -408,11 +741,6 @@ enum {
+ 	UNI_EVENT_BF_MAX_NUM
+ };
+ 
+-enum {
+-	UNI_CMD_MURU_FIXED_RATE_CTRL = 0x11,
+-	UNI_CMD_MURU_FIXED_GROUP_RATE_CTRL,
+-};
+-
+ struct uni_muru_mum_set_group_tbl_entry {
+ 	__le16 wlan_idx0;
+ 	__le16 wlan_idx1;
+diff --git a/mt7996/regs.h b/mt7996/regs.h
+index e94f9a90..aa04d8d2 100644
+--- a/mt7996/regs.h
++++ b/mt7996/regs.h
+@@ -326,6 +326,9 @@ enum offs_rev {
+ #define MT_ARB_SCR_TX_DISABLE			BIT(8)
+ #define MT_ARB_SCR_RX_DISABLE			BIT(9)
+ 
++#define MT_ARB_TQSAXM0(_band)			MT_WF_ARB(_band, 0x180)
++#define MT_ARB_TQSAXM_ALTX_START_MASK		GENMASK(12, 8)
++
+ /* RMAC: band 0(0x820e5000), band 1(0x820f5000), band 2(0x830e5000), */
+ #define MT_WF_RMAC_BASE(_band)			__BASE(WF_RMAC_BASE, (_band))
+ #define MT_WF_RMAC(_band, ofs)			(MT_WF_RMAC_BASE(_band) + (ofs))
+diff --git a/mt7996/testmode.c b/mt7996/testmode.c
+index 26ae5827..2fb36a97 100644
+--- a/mt7996/testmode.c
++++ b/mt7996/testmode.c
+@@ -23,6 +23,7 @@ enum {
+ 	TM_CHANGED_IPI_THRESHOLD,
+ 	TM_CHANGED_IPI_PERIOD,
+ 	TM_CHANGED_IPI_RESET,
++	TM_CHANGED_TXBF_ACT,
+ 
+ 	/* must be last */
+ 	NUM_TM_CHANGED
+@@ -41,25 +42,31 @@ static const u8 tm_change_map[] = {
+ 	[TM_CHANGED_IPI_THRESHOLD] = MT76_TM_ATTR_IPI_THRESHOLD,
+ 	[TM_CHANGED_IPI_PERIOD] = MT76_TM_ATTR_IPI_PERIOD,
+ 	[TM_CHANGED_IPI_RESET] = MT76_TM_ATTR_IPI_RESET,
++	[TM_CHANGED_TXBF_ACT] = MT76_TM_ATTR_TXBF_ACT,
+ };
+ 
+ static void mt7996_tm_ipi_work(struct work_struct *work);
++static int mt7996_tm_txbf_apply_tx(struct mt7996_phy *phy, u16 wlan_idx,
++				   bool ebf, bool ibf, bool phase_cal);
+ 
+ static u32 mt7996_tm_bw_mapping(enum nl80211_chan_width width, enum bw_mapping_method method)
+ {
+ 	static const u32 width_to_bw[][NUM_BW_MAP] = {
+-		[NL80211_CHAN_WIDTH_40] = {FW_CDBW_40MHZ, TM_CBW_40MHZ, 40,
++		[NL80211_CHAN_WIDTH_40] = {FW_CDBW_40MHZ, TM_CBW_40MHZ, BF_CDBW_40MHZ, 40,
+ 					   FIRST_CONTROL_CHAN_BITMAP_BW40},
+-		[NL80211_CHAN_WIDTH_80] = {FW_CDBW_80MHZ, TM_CBW_80MHZ, 80,
++		[NL80211_CHAN_WIDTH_80] = {FW_CDBW_80MHZ, TM_CBW_80MHZ, BF_CDBW_80MHZ, 80,
+ 					   FIRST_CONTROL_CHAN_BITMAP_BW80},
+-		[NL80211_CHAN_WIDTH_80P80] = {FW_CDBW_8080MHZ, TM_CBW_8080MHZ, 80, 0x0},
+-		[NL80211_CHAN_WIDTH_160] = {FW_CDBW_160MHZ, TM_CBW_160MHZ, 160,
++		[NL80211_CHAN_WIDTH_80P80] = {FW_CDBW_8080MHZ, TM_CBW_8080MHZ, BF_CDBW_8080MHZ,
++					      80, 0x0},
++		[NL80211_CHAN_WIDTH_160] = {FW_CDBW_160MHZ, TM_CBW_160MHZ, BF_CDBW_160MHZ, 160,
+ 					    FIRST_CONTROL_CHAN_BITMAP_BW160},
+-		[NL80211_CHAN_WIDTH_5] = {FW_CDBW_5MHZ, TM_CBW_5MHZ, 5, 0x0},
+-		[NL80211_CHAN_WIDTH_10] = {FW_CDBW_10MHZ, TM_CBW_10MHZ, 10, 0x0},
+-		[NL80211_CHAN_WIDTH_20] = {FW_CDBW_20MHZ, TM_CBW_20MHZ, 20, 0x0},
+-		[NL80211_CHAN_WIDTH_20_NOHT] = {FW_CDBW_20MHZ, TM_CBW_20MHZ, 20, 0x0},
+-		[NL80211_CHAN_WIDTH_320] = {FW_CDBW_320MHZ, TM_CBW_320MHZ, 320, 0x0},
++		[NL80211_CHAN_WIDTH_5] = {FW_CDBW_5MHZ, TM_CBW_5MHZ, BF_CDBW_5MHZ, 5, 0x0},
++		[NL80211_CHAN_WIDTH_10] = {FW_CDBW_10MHZ, TM_CBW_10MHZ, BF_CDBW_10MHZ, 10, 0x0},
++		[NL80211_CHAN_WIDTH_20] = {FW_CDBW_20MHZ, TM_CBW_20MHZ, BF_CDBW_20MHZ, 20, 0x0},
++		[NL80211_CHAN_WIDTH_20_NOHT] = {FW_CDBW_20MHZ, TM_CBW_20MHZ, BF_CDBW_20MHZ,
++						20, 0x0},
++		[NL80211_CHAN_WIDTH_320] = {FW_CDBW_320MHZ, TM_CBW_320MHZ, BF_CDBW_320MHZ,
++					    320, 0x0},
+ 	};
+ 
+ 	if (width >= ARRAY_SIZE(width_to_bw))
+@@ -68,26 +75,26 @@ static u32 mt7996_tm_bw_mapping(enum nl80211_chan_width width, enum bw_mapping_m
+ 	return width_to_bw[width][method];
+ }
+ 
+-static u8 mt7996_tm_rate_to_phy(u8 tx_rate_mode)
++static u8 mt7996_tm_rate_mapping(u8 tx_rate_mode, enum rate_mapping_type type)
+ {
+-	static const u8 rate_to_phy[] = {
+-		[MT76_TM_TX_MODE_CCK] = MT_PHY_TYPE_CCK,
+-		[MT76_TM_TX_MODE_OFDM] = MT_PHY_TYPE_OFDM,
+-		[MT76_TM_TX_MODE_HT] = MT_PHY_TYPE_HT,
+-		[MT76_TM_TX_MODE_VHT] = MT_PHY_TYPE_VHT,
+-		[MT76_TM_TX_MODE_HE_SU] = MT_PHY_TYPE_HE_SU,
+-		[MT76_TM_TX_MODE_HE_EXT_SU] = MT_PHY_TYPE_HE_EXT_SU,
+-		[MT76_TM_TX_MODE_HE_TB] = MT_PHY_TYPE_HE_TB,
+-		[MT76_TM_TX_MODE_HE_MU] = MT_PHY_TYPE_HE_MU,
+-		[MT76_TM_TX_MODE_EHT_SU] = MT_PHY_TYPE_EHT_SU,
+-		[MT76_TM_TX_MODE_EHT_TRIG] = MT_PHY_TYPE_EHT_TRIG,
+-		[MT76_TM_TX_MODE_EHT_MU] = MT_PHY_TYPE_EHT_MU,
++	static const u8 rate_to_phy[][NUM_RATE_MAP] = {
++		[MT76_TM_TX_MODE_CCK] = {MT_PHY_TYPE_CCK, BF_LM_LEGACY},
++		[MT76_TM_TX_MODE_OFDM] = {MT_PHY_TYPE_OFDM, BF_LM_LEGACY},
++		[MT76_TM_TX_MODE_HT] = {MT_PHY_TYPE_HT, BF_LM_HT},
++		[MT76_TM_TX_MODE_VHT] = {MT_PHY_TYPE_VHT, BF_LM_VHT},
++		[MT76_TM_TX_MODE_HE_SU] = {MT_PHY_TYPE_HE_SU, BF_LM_HE},
++		[MT76_TM_TX_MODE_HE_EXT_SU] = {MT_PHY_TYPE_HE_EXT_SU, BF_LM_HE},
++		[MT76_TM_TX_MODE_HE_TB] = {MT_PHY_TYPE_HE_TB, BF_LM_HE},
++		[MT76_TM_TX_MODE_HE_MU] = {MT_PHY_TYPE_HE_MU, BF_LM_HE},
++		[MT76_TM_TX_MODE_EHT_SU] = {MT_PHY_TYPE_EHT_SU, BF_LM_EHT},
++		[MT76_TM_TX_MODE_EHT_TRIG] = {MT_PHY_TYPE_EHT_TRIG, BF_LM_EHT},
++		[MT76_TM_TX_MODE_EHT_MU] = {MT_PHY_TYPE_EHT_MU, BF_LM_EHT},
+ 	};
+ 
+ 	if (tx_rate_mode > MT76_TM_TX_MODE_MAX)
+ 		return -EINVAL;
+ 
+-	return rate_to_phy[tx_rate_mode];
++	return rate_to_phy[tx_rate_mode][type];
+ }
+ 
+ static int
+@@ -239,7 +246,7 @@ mt7996_tm_init(struct mt7996_phy *phy, bool en)
+ 		INIT_DELAYED_WORK(&phy->ipi_work, mt7996_tm_ipi_work);
+ }
+ 
+-static void
++void
+ mt7996_tm_update_channel(struct mt7996_phy *phy)
+ {
+ #define CHAN_FREQ_BW_80P80_TAG		(SET_ID(CHAN_FREQ) | BIT(16))
+@@ -303,7 +310,8 @@ mt7996_tm_set_tx_frames(struct mt7996_phy *phy, bool en)
+ 		mt7996_tm_set(dev, SET_ID(MAC_HEADER), FRAME_CONTROL);
+ 		mt7996_tm_set(dev, SET_ID(SEQ_CTRL), 0);
+ 		mt7996_tm_set(dev, SET_ID(TX_COUNT), td->tx_count);
+-		mt7996_tm_set(dev, SET_ID(TX_MODE), mt7996_tm_rate_to_phy(td->tx_rate_mode));
++		mt7996_tm_set(dev, SET_ID(TX_MODE),
++			      mt7996_tm_rate_mapping(td->tx_rate_mode, RATE_MODE_TO_PHY));
+ 		mt7996_tm_set(dev, SET_ID(TX_RATE), td->tx_rate_idx);
+ 
+ 		if (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_POWER))
+@@ -331,7 +339,8 @@ mt7996_tm_set_tx_frames(struct mt7996_phy *phy, bool en)
+ 
+ 		mt7996_tm_set(dev, SET_ID(MAX_PE), 2);
+ 		mt7996_tm_set(dev, SET_ID(HW_TX_MODE), 0);
+-		mt7996_tm_update_channel(phy);
++		if (!td->bf_en)
++			mt7996_tm_update_channel(phy);
+ 
+ 		/* trigger firmware to start TX */
+ 		mt7996_tm_set(dev, SET_ID(COMMAND), RF_CMD(START_TX));
+@@ -373,7 +382,8 @@ mt7996_tm_set_rx_frames(struct mt7996_phy *phy, bool en)
+ 			return;
+ 		}
+ 
+-		mt7996_tm_update_channel(phy);
++		if (!td->bf_en)
++			mt7996_tm_update_channel(phy);
+ 
+ 		if (td->tx_rate_mode >= MT76_TM_TX_MODE_HE_MU) {
+ 			if (td->aid)
+@@ -381,7 +391,8 @@ mt7996_tm_set_rx_frames(struct mt7996_phy *phy, bool en)
+ 			else
+ 				ret = mt7996_tm_set(dev, SET_ID(RX_MU_AID), RX_MU_DISABLE);
+ 		}
+-		mt7996_tm_set(dev, SET_ID(TX_MODE), mt7996_tm_rate_to_phy(td->tx_rate_mode));
++		mt7996_tm_set(dev, SET_ID(TX_MODE),
++			      mt7996_tm_rate_mapping(td->tx_rate_mode, RATE_MODE_TO_PHY));
+ 		mt7996_tm_set(dev, SET_ID(GI), td->tx_rate_sgi);
+ 		mt7996_tm_set_antenna(phy, SET_ID(RX_PATH));
+ 		mt7996_tm_set(dev, SET_ID(MAX_PE), 2);
+@@ -405,7 +416,8 @@ mt7996_tm_set_tx_cont(struct mt7996_phy *phy, bool en)
+ 
+ 	if (en) {
+ 		mt7996_tm_update_channel(phy);
+-		mt7996_tm_set(dev, SET_ID(TX_MODE), mt7996_tm_rate_to_phy(td->tx_rate_mode));
++		mt7996_tm_set(dev, SET_ID(TX_MODE),
++			      mt7996_tm_rate_mapping(td->tx_rate_mode, RATE_MODE_TO_PHY));
+ 		mt7996_tm_set(dev, SET_ID(TX_RATE), td->tx_rate_idx);
+ 		/* fix payload is OFDM */
+ 		mt7996_tm_set(dev, SET_ID(CONT_WAVE_MODE), CONT_WAVE_MODE_OFDM);
+@@ -1047,6 +1059,678 @@ mt7996_tm_set_ipi(struct mt7996_phy *phy)
+ 	return 0;
+ }
+ 
++static int
++mt7996_tm_set_trx_mac(struct mt7996_phy *phy, u8 type, bool en)
++{
++#define UNI_TM_TRX_CTRL 0
++	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_tm_trx_req req = {
++		.param_num = 1,
++		.tag = cpu_to_le16(UNI_TM_TRX_CTRL),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.param_idx = cpu_to_le16(TM_TRX_PARAM_SET_TRX),
++		.band_idx = phy->mt76->band_idx,
++		.testmode_en = 1,
++		.action = TM_TRX_ACTION_SET,
++		.set_trx = {
++			.type = type,
++			.enable = en,
++			.band_idx = phy->mt76->band_idx,
++		}
++	};
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(TESTMODE_TRX_PARAM),
++				 &req, sizeof(req), false);
++}
++
++static int
++mt7996_tm_txbf_init(struct mt7996_phy *phy, u16 *val)
++{
++#define EBF_BBP_RX_OFFSET	0x10280
++#define EBF_BBP_RX_ENABLE	(BIT(0) | BIT(15))
++	struct mt7996_dev *dev = phy->dev;
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	bool enable = val[0];
++	void *phase_cal, *pfmu_data, *pfmu_tag;
++	u8 nss, band_idx = phy->mt76->band_idx;
++	enum nl80211_chan_width width = NL80211_CHAN_WIDTH_20;
++	u8 sub_addr = td->is_txbf_dut ? TXBF_DUT_MAC_SUBADDR : TXBF_GOLDEN_MAC_SUBADDR;
++	u8 peer_addr = td->is_txbf_dut ? TXBF_GOLDEN_MAC_SUBADDR : TXBF_DUT_MAC_SUBADDR;
++	u8 bss_addr = TXBF_DUT_MAC_SUBADDR;
++	u8 addr[ETH_ALEN] = {0x00, sub_addr, sub_addr, sub_addr, sub_addr, sub_addr};
++	u8 bssid[ETH_ALEN] = {0x00, bss_addr, bss_addr, bss_addr, bss_addr, bss_addr};
++	u8 peer_addrs[ETH_ALEN] = {0x00, peer_addr, peer_addr, peer_addr, peer_addr, peer_addr};
++	struct mt7996_vif *mvif = (struct mt7996_vif *)phy->monitor_vif->drv_priv;
++
++	if (!enable) {
++		td->bf_en = false;
++		return 0;
++	}
++
++	if (!dev->test.txbf_phase_cal) {
++		phase_cal = devm_kzalloc(dev->mt76.dev,
++					 sizeof(struct mt7996_txbf_phase) *
++					 MAX_PHASE_GROUP_NUM,
++					 GFP_KERNEL);
++		if (!phase_cal)
++			return -ENOMEM;
++
++		dev->test.txbf_phase_cal = phase_cal;
++	}
++
++	if (!dev->test.txbf_pfmu_data) {
++		pfmu_data = devm_kzalloc(dev->mt76.dev,
++					 sizeof(struct mt7996_pfmu_data) *
++					 MT7996_TXBF_SUBCAR_NUM,
++					 GFP_KERNEL);
++		if (!pfmu_data)
++			return -ENOMEM;
++
++		dev->test.txbf_pfmu_data = pfmu_data;
++	}
++
++	if (!dev->test.txbf_pfmu_tag) {
++		pfmu_tag = devm_kzalloc(dev->mt76.dev,
++					sizeof(struct mt7996_pfmu_tag), GFP_KERNEL);
++		if (!pfmu_tag)
++			return -ENOMEM;
++
++		dev->test.txbf_pfmu_tag = pfmu_tag;
++	}
++
++	td->bf_en = true;
++	dev->ibf = td->ibf;
++	memcpy(td->addr[0], peer_addrs, ETH_ALEN);
++	memcpy(td->addr[1], addr, ETH_ALEN);
++	memcpy(td->addr[2], bssid, ETH_ALEN);
++	memcpy(phy->monitor_vif->addr, addr, ETH_ALEN);
++	mt7996_tm_set_mac_addr(dev, td->addr[0], SET_ID(DA));
++	mt7996_tm_set_mac_addr(dev, td->addr[1], SET_ID(SA));
++	mt7996_tm_set_mac_addr(dev, td->addr[2], SET_ID(BSSID));
++
++	/* bss idx & omac idx should be set to band idx for ibf cal */
++	mvif->mt76.idx = band_idx;
++	dev->mt76.vif_mask |= BIT_ULL(mvif->mt76.idx);
++	mvif->mt76.omac_idx = band_idx;
++	phy->omac_mask |= BIT_ULL(mvif->mt76.omac_idx);
++
++	mt7996_mcu_add_dev_info(phy, phy->monitor_vif, true);
++	mt7996_mcu_add_bss_info(phy, phy->monitor_vif, true);
++
++	if (td->ibf) {
++		if (td->is_txbf_dut) {
++			/* Enable ITxBF Capability */
++			mt7996_mcu_set_txbf(dev, BF_HW_EN_UPDATE);
++			mt7996_tm_set_trx_mac(phy, TM_TRX_MAC_TX, true);
++
++			td->tx_ipg = 999;
++			td->tx_mpdu_len = 1024;
++			td->tx_antenna_mask = phy->mt76->chainmask >> dev->chainshift[band_idx];
++			nss = hweight8(td->tx_antenna_mask);
++			if (nss > 1 && nss <= 4)
++				td->tx_rate_idx = 15 + 8 * (nss - 2);
++			else
++				td->tx_rate_idx = 31;
++		} else {
++			td->tx_antenna_mask = 1;
++			td->tx_mpdu_len = 1024;
++			td->tx_rate_idx = 0;
++			mt76_set(dev, EBF_BBP_RX_OFFSET, EBF_BBP_RX_ENABLE);
++			dev_info(dev->mt76.dev, "Set BBP RX CR = %x\n",
++				 mt76_rr(dev, EBF_BBP_RX_OFFSET));
++		}
++
++		td->tx_rate_mode = MT76_TM_TX_MODE_HT;
++		td->tx_rate_sgi = 0;
++	} else {
++		if (td->is_txbf_dut) {
++			/* Enable ETxBF Capability */
++			mt7996_mcu_set_txbf(dev, BF_HW_EN_UPDATE);
++			td->tx_antenna_mask = phy->mt76->chainmask >> dev->chainshift[band_idx];
++			td->tx_spe_idx = 24 + phy->mt76->band_idx;
++			if (td->tx_rate_mode == MT76_TM_TX_MODE_VHT ||
++			    td->tx_rate_mode == MT76_TM_TX_MODE_HE_SU)
++				mt7996_tm_set(dev, SET_ID(NSS), td->tx_rate_nss);
++
++			mt7996_tm_set(dev, SET_ID(ENCODE_MODE), td->tx_rate_ldpc);
++			mt7996_tm_set(dev, SET_ID(TX_COUNT), td->tx_count);
++		} else {
++			/* Turn On BBP CR for RX */
++			mt76_set(dev, EBF_BBP_RX_OFFSET, EBF_BBP_RX_ENABLE);
++			dev_info(dev->mt76.dev, "Set BBP RX CR = %x\n",
++				 mt76_rr(dev, EBF_BBP_RX_OFFSET));
++
++			td->tx_antenna_mask = 1;
++		}
++		width = phy->mt76->chandef.width;
++
++		if (td->tx_rate_mode == MT76_TM_TX_MODE_EHT_MU)
++			td->tx_rate_mode = MT76_TM_TX_MODE_EHT_SU;
++	}
++	mt76_testmode_param_set(td, MT76_TM_ATTR_TX_ANTENNA);
++
++	mt7996_tm_set(dev, SET_ID(TX_MODE),
++		      mt7996_tm_rate_mapping(td->tx_rate_mode, RATE_MODE_TO_PHY));
++	mt7996_tm_set(dev, SET_ID(TX_RATE), td->tx_rate_idx);
++	mt7996_tm_set(dev, SET_ID(GI), td->tx_rate_sgi);
++	mt7996_tm_set(dev, SET_ID(CBW),
++		      mt7996_tm_bw_mapping(width, BW_MAP_NL_TO_FW));
++	mt7996_tm_set(dev, SET_ID(DBW),
++		      mt7996_tm_bw_mapping(width, BW_MAP_NL_TO_FW));
++	mt7996_tm_set_antenna(phy, SET_ID(TX_PATH));
++	mt7996_tm_set_antenna(phy, SET_ID(RX_PATH));
++	mt7996_tm_set(dev, SET_ID(IPG), td->tx_ipg);
++	mt7996_tm_set(dev, SET_ID(TX_LEN), td->tx_mpdu_len);
++	mt7996_tm_set(dev, SET_ID(TX_TIME), 0);
++	mt7996_tm_set(dev, SET_ID(COMMAND), RF_CMD(TX_COMMIT));
++
++	return 0;
++}
++
++static int
++mt7996_tm_txbf_phase_comp(struct mt7996_phy *phy, u16 *val)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_tm_bf_req req = {
++		.phase_comp = {
++			.tag = cpu_to_le16(BF_IBF_PHASE_COMP),
++			.len = cpu_to_le16(sizeof(req.phase_comp)),
++			.bw = val[0],
++			.jp_band = (val[2] == 1) ? 1 : 0,
++			.band_idx = phy->mt76->band_idx,
++			.read_from_e2p = val[3],
++			.disable = val[4],
++			.group = val[2],
++		}
++	};
++	struct mt7996_txbf_phase *phase = (struct mt7996_txbf_phase *)dev->test.txbf_phase_cal;
++
++	wait_event_timeout(dev->mt76.tx_wait, phase[val[2]].status != 0, HZ);
++	if (val[2])
++		memcpy(req.phase_comp.buf, &phase[val[2]].phase_5g, sizeof(req.phase_comp.buf));
++	else
++		memcpy(req.phase_comp.buf, &phase[val[2]].phase_2g, sizeof(req.phase_comp.buf));
++
++	pr_info("ibf cal process: phase comp info\n");
++	print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 16, 1,
++		       &req, sizeof(req), 0);
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(BF), &req,
++				 sizeof(req), false);
++}
++
++static int
++mt7996_tm_txbf_profile_tag_write(struct mt7996_phy *phy, u8 pfmu_idx, struct mt7996_pfmu_tag *tag)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_tm_bf_req req = {
++		.pfmu_tag = {
++			.tag = cpu_to_le16(BF_PFMU_TAG_WRITE),
++			.len = cpu_to_le16(sizeof(req.pfmu_tag)),
++			.pfmu_id = pfmu_idx,
++			.bfer = true,
++			.band_idx = phy->mt76->band_idx,
++		}
++	};
++
++	memcpy(req.pfmu_tag.buf, tag, sizeof(*tag));
++	wait_event_timeout(dev->mt76.tx_wait, tag->t1.pfmu_idx != 0, HZ);
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(BF), &req,
++				 sizeof(req), false);
++}
++
++static int
++mt7996_tm_add_txbf_sta(struct mt7996_phy *phy, u8 pfmu_idx, u8 nr, u8 nc, bool ebf)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	struct {
++		struct sta_req_hdr hdr;
++		struct sta_rec_bf bf;
++	} __packed req = {
++		.hdr = {
++			.bss_idx = phy->mt76->band_idx,
++			.wlan_idx_lo = to_wcid_lo(phy->mt76->band_idx + 1),
++			.tlv_num = 1,
++			.is_tlv_append = 1,
++			.muar_idx = 0,
++			.wlan_idx_hi = to_wcid_hi(phy->mt76->band_idx + 1),
++		},
++		.bf = {
++			.tag = cpu_to_le16(STA_REC_BF),
++			.len = cpu_to_le16(sizeof(req.bf)),
++			.pfmu = cpu_to_le16(pfmu_idx),
++			.sounding_phy = 1,
++			.bf_cap = ebf,
++			.ncol = nc,
++			.nrow = nr,
++			.ibf_timeout = 0xff,
++			.tx_mode = mt7996_tm_rate_mapping(td->tx_rate_mode, RATE_MODE_TO_PHY),
++		},
++	};
++	u8 ndp_rate, ndpa_rate, rept_poll_rate, bf_bw;
++
++	if (td->tx_rate_mode == MT76_TM_TX_MODE_HE_SU ||
++	    td->tx_rate_mode == MT76_TM_TX_MODE_EHT_SU) {
++		rept_poll_rate = 0x49;
++		ndpa_rate = 0x49;
++		ndp_rate = 0;
++	} else if (td->tx_rate_mode == MT76_TM_TX_MODE_VHT) {
++		rept_poll_rate = 0x9;
++		ndpa_rate = 0x9;
++		ndp_rate = 0;
++	} else {
++		rept_poll_rate = 0;
++		ndpa_rate = 0;
++		if (nr == 1)
++			ndp_rate = 8;
++		else if (nr == 2)
++			ndp_rate = 16;
++		else
++			ndp_rate = 24;
++	}
++
++	bf_bw = mt7996_tm_bw_mapping(phy->mt76->chandef.width, BW_MAP_NL_TO_BF);
++	req.bf.ndp_rate = ndp_rate;
++	req.bf.ndpa_rate = ndpa_rate;
++	req.bf.rept_poll_rate = rept_poll_rate;
++	req.bf.bw = bf_bw;
++	req.bf.tx_mode = (td->tx_rate_mode == MT76_TM_TX_MODE_EHT_SU) ? 0xf : req.bf.tx_mode;
++
++	if (ebf) {
++		req.bf.mem[0].row = 0;
++		req.bf.mem[1].row = 1;
++		req.bf.mem[2].row = 2;
++		req.bf.mem[3].row = 3;
++	} else {
++		req.bf.mem[0].row = 4;
++		req.bf.mem[1].row = 5;
++		req.bf.mem[2].row = 6;
++		req.bf.mem[3].row = 7;
++	}
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WMWA_UNI_CMD(STA_REC_UPDATE), &req,
++				 sizeof(req), true);
++}
++
++static int
++mt7996_tm_txbf_profile_update(struct mt7996_phy *phy, u16 *val, bool ebf)
++{
++#define MT_ARB_IBF_ENABLE			(BIT(0) | GENMASK(9, 8))
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_pfmu_tag *tag = dev->test.txbf_pfmu_tag;
++	u8 pfmu_idx = val[0], nc = val[2], nr;
++	int ret;
++	bool is_atenl = val[5];
++
++	if (td->tx_antenna_mask == 3)
++		nr = 1;
++	else if (td->tx_antenna_mask == 7)
++		nr = 2;
++	else
++		nr = 3;
++
++	memset(tag, 0, sizeof(*tag));
++	tag->t1.pfmu_idx = pfmu_idx;
++	tag->t1.ebf = ebf;
++	tag->t1.nr = nr;
++	tag->t1.nc = nc;
++	tag->t1.invalid_prof = true;
++	tag->t1.data_bw = mt7996_tm_bw_mapping(phy->mt76->chandef.width, BW_MAP_NL_TO_BF);
++	tag->t2.se_idx = td->tx_spe_idx;
++
++	if (ebf) {
++		tag->t1.row_id1 = 0;
++		tag->t1.row_id2 = 1;
++		tag->t1.row_id3 = 2;
++		tag->t1.row_id4 = 3;
++		tag->t1.lm = mt7996_tm_rate_mapping(td->tx_rate_mode, RATE_MODE_TO_LM);
++	} else {
++		tag->t1.row_id1 = 4;
++		tag->t1.row_id2 = 5;
++		tag->t1.row_id3 = 6;
++		tag->t1.row_id4 = 7;
++		tag->t1.lm = mt7996_tm_rate_mapping(MT76_TM_TX_MODE_OFDM, RATE_MODE_TO_LM);
++
++		tag->t2.ibf_timeout = 0xff;
++		tag->t2.ibf_nr = nr;
++		tag->t2.ibf_nc = nc;
++	}
++
++	ret = mt7996_tm_txbf_profile_tag_write(phy, pfmu_idx, tag);
++	if (ret)
++		return ret;
++
++	ret = mt7996_tm_add_txbf_sta(phy, pfmu_idx, nr, nc, ebf);
++	if (ret)
++		return ret;
++
++	if (!is_atenl && !td->ibf) {
++		mt76_set(dev, MT_ARB_TQSAXM0(phy->mt76->band_idx), MT_ARB_TQSAXM_ALTX_START_MASK);
++		dev_info(dev->mt76.dev, "Set TX queue start CR for AX management (0x%x) = 0x%x\n",
++			 MT_ARB_TQSAXM0(phy->mt76->band_idx),
++			 mt76_rr(dev, MT_ARB_TQSAXM0(phy->mt76->band_idx)));
++	} else if (!is_atenl && td->ibf && ebf) {
++		/* iBF's ebf profile update */
++		mt76_set(dev, MT_ARB_TQSAXM0(phy->mt76->band_idx), MT_ARB_IBF_ENABLE);
++		dev_info(dev->mt76.dev, "Set TX queue start CR for AX management (0x%x) = 0x%x\n",
++			 MT_ARB_TQSAXM0(phy->mt76->band_idx),
++			 mt76_rr(dev, MT_ARB_TQSAXM0(phy->mt76->band_idx)));
++	}
++
++	if (!ebf && is_atenl)
++		return mt7996_tm_txbf_apply_tx(phy, 1, false, true, true);
++
++	return 0;
++}
++
++static int
++mt7996_tm_txbf_phase_cal(struct mt7996_phy *phy, u16 *val)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_tm_bf_req req = {
++		.phase_cal = {
++			.tag = cpu_to_le16(BF_PHASE_CALIBRATION),
++			.len = cpu_to_le16(sizeof(req.phase_cal)),
++			.group = val[0],
++			.group_l_m_n = val[1],
++			.sx2 = val[2],
++			.cal_type = val[3],
++			.lna_gain_level = val[4],
++			.band_idx = phy->mt76->band_idx,
++		},
++	};
++	struct mt7996_txbf_phase *phase = (struct mt7996_txbf_phase *)dev->test.txbf_phase_cal;
++
++	/* reset phase status before update phase cal data */
++	phase[req.phase_cal.group].status = 0;
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(BF), &req,
++				 sizeof(req), false);
++}
++
++static int
++mt7996_tm_txbf_profile_update_all(struct mt7996_phy *phy, u16 *val)
++{
++#define MT7996_TXBF_PFMU_DATA_LEN	(MT7996_TXBF_SUBCAR_NUM * sizeof(struct mt7996_pfmu_data))
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	u8 nss = hweight8(td->tx_antenna_mask);
++	u16 pfmu_idx = val[0];
++	u16 subc_id = val[1];
++	u16 angle11 = val[2];
++	u16 angle21 = val[3];
++	u16 angle31 = val[4];
++	u16 angle41 = val[5];
++	s16 phi11 = 0, phi21 = 0, phi31 = 0;
++	struct mt7996_pfmu_data *pfmu_data;
++
++	if (subc_id > MT7996_TXBF_SUBCAR_NUM - 1)
++		return -EINVAL;
++
++	if (nss == 2) {
++		phi11 = (s16)(angle21 - angle11);
++	} else if (nss == 3) {
++		phi11 = (s16)(angle31 - angle11);
++		phi21 = (s16)(angle31 - angle21);
++	} else {
++		phi11 = (s16)(angle41 - angle11);
++		phi21 = (s16)(angle41 - angle21);
++		phi31 = (s16)(angle41 - angle31);
++	}
++
++	pfmu_data = (struct mt7996_pfmu_data *)phy->dev->test.txbf_pfmu_data;
++	pfmu_data = &pfmu_data[subc_id];
++
++	if (subc_id < 32)
++		pfmu_data->subc_idx = cpu_to_le16(subc_id + 224);
++	else
++		pfmu_data->subc_idx = cpu_to_le16(subc_id - 32);
++
++	pfmu_data->phi11 = cpu_to_le16(phi11);
++	pfmu_data->phi21 = cpu_to_le16(phi21);
++	pfmu_data->phi31 = cpu_to_le16(phi31);
++	if (subc_id == MT7996_TXBF_SUBCAR_NUM - 1) {
++		struct mt7996_dev *dev = phy->dev;
++		struct mt7996_tm_bf_req req = {
++			.pfmu_data_all = {
++				.tag = cpu_to_le16(BF_PROFILE_WRITE_20M_ALL),
++				.len = cpu_to_le16(sizeof(req.pfmu_data_all)),
++				.pfmu_id = pfmu_idx,
++				.band_idx = phy->mt76->band_idx,
++			},
++		};
++
++		memcpy(req.pfmu_data_all.buf, dev->test.txbf_pfmu_data, MT7996_TXBF_PFMU_DATA_LEN);
++
++		return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(BF),
++					 &req, sizeof(req), true);
++	}
++
++	return 0;
++}
++
++static int
++mt7996_tm_txbf_e2p_update(struct mt7996_phy *phy)
++{
++#define TXBF_PHASE_EEPROM_START_OFFSET		0xc00
++#define TXBF_PHASE_GROUP_EEPROM_OFFSET		0x2e
++	struct mt7996_txbf_phase *phase, *p;
++	struct mt7996_dev *dev = phy->dev;
++	u8 *eeprom = dev->mt76.eeprom.data;
++	u16 offset;
++	int i;
++
++	offset = TXBF_PHASE_EEPROM_START_OFFSET;
++	phase = (struct mt7996_txbf_phase *)dev->test.txbf_phase_cal;
++	for (i = 0; i < MAX_PHASE_GROUP_NUM; i++) {
++		p = &phase[i];
++
++		if (!p->status)
++			continue;
++
++		/* copy phase cal data to eeprom */
++		if (i)
++			memcpy(eeprom + offset, &p->phase_5g, sizeof(p->phase_5g));
++		else
++			memcpy(eeprom + offset, &p->phase_2g, sizeof(p->phase_2g));
++		offset += TXBF_PHASE_GROUP_EEPROM_OFFSET;
++	}
++
++	return 0;
++}
++
++static int
++mt7996_tm_txbf_apply_tx(struct mt7996_phy *phy, u16 wlan_idx, bool ebf,
++			bool ibf, bool phase_cal)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_tm_bf_req req = {
++		.tx_apply = {
++			.tag = cpu_to_le16(BF_DATA_PACKET_APPLY),
++			.len = cpu_to_le16(sizeof(req.tx_apply)),
++			.wlan_idx = cpu_to_le16(wlan_idx),
++			.ebf = ebf,
++			.ibf = ibf,
++			.phase_cal = phase_cal,
++		},
++	};
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(BF), &req, sizeof(req), false);
++}
++
++static int
++mt7996_tm_txbf_set_tx(struct mt7996_phy *phy, u16 *val)
++{
++	bool bf_on = val[0], update = val[3];
++	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_pfmu_tag *tag = dev->test.txbf_pfmu_tag;
++	struct mt76_testmode_data *td = &phy->mt76->test;
++
++	if (bf_on) {
++		mt7996_tm_set_rx_frames(phy, false);
++		mt7996_tm_set_tx_frames(phy, false);
++		mt7996_mcu_set_txbf_internal(phy, BF_PFMU_TAG_READ, 2, true);
++		tag->t1.invalid_prof = false;
++		mt7996_tm_txbf_profile_tag_write(phy, 2, tag);
++		td->bf_ever_en = true;
++
++		if (update)
++			mt7996_tm_txbf_apply_tx(phy, 1, 0, 1, 1);
++	} else {
++		if (!td->bf_ever_en) {
++			mt7996_tm_set_rx_frames(phy, false);
++			mt7996_tm_set_tx_frames(phy, false);
++			td->ibf = false;
++			td->ebf = false;
++
++			if (update)
++				mt7996_tm_txbf_apply_tx(phy, 1, 0, 0, 0);
++		} else {
++			td->bf_ever_en = false;
++
++			mt7996_mcu_set_txbf_internal(phy, BF_PFMU_TAG_READ, 2, true);
++			tag->t1.invalid_prof = true;
++			mt7996_tm_txbf_profile_tag_write(phy, 2, tag);
++		}
++	}
++
++	return 0;
++}
++
++static int
++mt7996_tm_trigger_sounding(struct mt7996_phy *phy, u16 *val, bool en)
++{
++	struct mt7996_dev *dev = phy->dev;
++	u8 sounding_mode = val[0];
++	u8 sta_num = val[1];
++	u32 sounding_interval = (u32)val[2] << 2;	/* input unit: 4ms */
++	u16 tag = en ? BF_SOUNDING_ON : BF_SOUNDING_OFF;
++	struct mt7996_tm_bf_req req = {
++		.sounding = {
++			.tag = cpu_to_le16(tag),
++			.len = cpu_to_le16(sizeof(req.sounding)),
++			.snd_mode = sounding_mode,
++			.sta_num = sta_num,
++			.wlan_id = {
++				cpu_to_le16(val[3]),
++				cpu_to_le16(val[4]),
++				cpu_to_le16(val[5]),
++				cpu_to_le16(val[6])
++			},
++			.snd_period = cpu_to_le32(sounding_interval),
++		},
++	};
++
++	if (sounding_mode > SOUNDING_MODE_MAX)
++		return -EINVAL;
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(BF),
++				 &req, sizeof(req), false);
++}
++
++static int
++mt7996_tm_txbf_txcmd(struct mt7996_phy *phy, u16 *val)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_tm_bf_req req = {
++		.txcmd = {
++			.tag = cpu_to_le16(BF_CMD_TXCMD),
++			.len = cpu_to_le16(sizeof(req.txcmd)),
++			.action = val[0],
++			.bf_manual = val[1],
++			.bf_bit = val[2],
++		},
++	};
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(BF), &req, sizeof(req), false);
++}
++
++static int
++mt7996_tm_set_txbf(struct mt7996_phy *phy)
++{
++#define TXBF_IS_DUT_MASK	BIT(0)
++#define TXBF_IBF_MASK		BIT(1)
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	u16 *val = td->txbf_param;
++
++	dev_info(phy->dev->mt76.dev,
++		 "ibf cal process: act = %u, val = %u, %u, %u, %u, %u, %u, %u, %u\n",
++		 td->txbf_act, val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7]);
++
++	switch (td->txbf_act) {
++	case MT76_TM_TXBF_ACT_GOLDEN_INIT:
++	case MT76_TM_TXBF_ACT_INIT:
++	case MT76_TM_TX_EBF_ACT_GOLDEN_INIT:
++	case MT76_TM_TX_EBF_ACT_INIT:
++		td->ibf = !u32_get_bits(td->txbf_act, TXBF_IBF_MASK);
++		td->ebf = true;
++		td->is_txbf_dut = !!u32_get_bits(td->txbf_act, TXBF_IS_DUT_MASK);
++		return mt7996_tm_txbf_init(phy, val);
++	case MT76_TM_TXBF_ACT_UPDATE_CH:
++		mt7996_tm_update_channel(phy);
++		break;
++	case MT76_TM_TXBF_ACT_PHASE_COMP:
++		return mt7996_tm_txbf_phase_comp(phy, val);
++	case MT76_TM_TXBF_ACT_TX_PREP:
++		return mt7996_tm_txbf_set_tx(phy, val);
++	case MT76_TM_TXBF_ACT_IBF_PROF_UPDATE:
++		return mt7996_tm_txbf_profile_update(phy, val, false);
++	case MT76_TM_TXBF_ACT_EBF_PROF_UPDATE:
++		return mt7996_tm_txbf_profile_update(phy, val, true);
++	case MT76_TM_TXBF_ACT_PHASE_CAL:
++		return mt7996_tm_txbf_phase_cal(phy, val);
++	case MT76_TM_TXBF_ACT_PROF_UPDATE_ALL_CMD:
++	case MT76_TM_TXBF_ACT_PROF_UPDATE_ALL:
++		return mt7996_tm_txbf_profile_update_all(phy, val);
++	case MT76_TM_TXBF_ACT_E2P_UPDATE:
++		return mt7996_tm_txbf_e2p_update(phy);
++	case MT76_TM_TXBF_ACT_APPLY_TX: {
++		u16 wlan_idx = val[0];
++		bool ebf = !!val[1], ibf = !!val[2], phase_cal = !!val[4];
++
++		return mt7996_tm_txbf_apply_tx(phy, wlan_idx, ebf, ibf, phase_cal);
++	}
++	case MT76_TM_TXBF_ACT_TRIGGER_SOUNDING:
++		return mt7996_tm_trigger_sounding(phy, val, true);
++	case MT76_TM_TXBF_ACT_STOP_SOUNDING:
++		memset(val, 0, sizeof(td->txbf_param));
++		return mt7996_tm_trigger_sounding(phy, val, false);
++	case MT76_TM_TXBF_ACT_PROFILE_TAG_READ:
++	case MT76_TM_TXBF_ACT_PROFILE_TAG_WRITE:
++	case MT76_TM_TXBF_ACT_PROFILE_TAG_INVALID: {
++		u8 pfmu_idx = val[0];
++		bool bfer = !!val[1];
++		struct mt7996_dev *dev = phy->dev;
++		struct mt7996_pfmu_tag *tag = dev->test.txbf_pfmu_tag;
++
++		if (!tag) {
++			dev_err(dev->mt76.dev,
++				"pfmu tag is not initialized!\n");
++			return 0;
++		}
++
++		if (td->txbf_act == MT76_TM_TXBF_ACT_PROFILE_TAG_WRITE)
++			return mt7996_tm_txbf_profile_tag_write(phy, pfmu_idx, tag);
++		else if (td->txbf_act == MT76_TM_TXBF_ACT_PROFILE_TAG_READ)
++			return mt7996_mcu_set_txbf_internal(phy, BF_PFMU_TAG_READ, pfmu_idx, bfer);
++
++		tag->t1.invalid_prof = !!val[0];
++
++		return 0;
++	}
++	case MT76_TM_TXBF_ACT_STA_REC_READ:
++		return mt7996_mcu_set_txbf_internal(phy, BF_STA_REC_READ, val[0], 0);
++	case MT76_TM_TXBF_ACT_TXCMD:
++		return mt7996_tm_txbf_txcmd(phy, val);
++	default:
++		break;
++	};
++
++	return 0;
++}
++
+ static void
+ mt7996_tm_update_params(struct mt7996_phy *phy, u32 changed)
+ {
+@@ -1086,6 +1770,8 @@ mt7996_tm_update_params(struct mt7996_phy *phy, u32 changed)
+ 		mt7996_tm_set_ipi(phy);
+ 	if (changed & BIT(TM_CHANGED_IPI_RESET))
+ 		mt7996_tm_ipi_hist_ctrl(phy, NULL, RDD_SET_IPI_HIST_RESET);
++	if (changed & BIT(TM_CHANGED_TXBF_ACT))
++		mt7996_tm_set_txbf(phy);
+ }
+ 
+ static int
+diff --git a/mt7996/testmode.h b/mt7996/testmode.h
+index 78662b2e..f97ccb26 100644
+--- a/mt7996/testmode.h
++++ b/mt7996/testmode.h
+@@ -27,6 +27,17 @@ enum {
+ 	FW_CDBW_8080MHZ,
+ };
+ 
++enum {
++	BF_CDBW_20MHZ,
++	BF_CDBW_40MHZ,
++	BF_CDBW_80MHZ,
++	BF_CDBW_160MHZ,
++	BF_CDBW_320MHZ,
++	BF_CDBW_10MHZ = BF_CDBW_320MHZ,
++	BF_CDBW_5MHZ,
++	BF_CDBW_8080MHZ,
++};
++
+ #define FIRST_CONTROL_CHAN_BITMAP_BW40		0x5555555
+ #define FIRST_CONTROL_CHAN_BITMAP_BW80		0x111111
+ #define FIRST_CONTROL_CHAN_BITMAP_BW160		0x100101
+@@ -34,12 +45,20 @@ enum {
+ enum bw_mapping_method {
+ 	BW_MAP_NL_TO_FW,
+ 	BW_MAP_NL_TO_TM,
++	BW_MAP_NL_TO_BF,
+ 	BW_MAP_NL_TO_MHZ,
+ 	BW_MAP_NL_TO_CONTROL_BITMAP_5G,
+ 
+ 	NUM_BW_MAP,
+ };
+ 
++enum rate_mapping_type {
++	RATE_MODE_TO_PHY,
++	RATE_MODE_TO_LM,
++
++	NUM_RATE_MAP,
++};
++
+ struct tm_cal_param {
+ 	__le32 func_data;
+ 	u8 band_idx;
+diff --git a/testmode.c b/testmode.c
+index 805ad83c..7b8f9e66 100644
+--- a/testmode.c
++++ b/testmode.c
+@@ -462,6 +462,42 @@ out:
+ 	return err;
+ }
+ 
++static int
++mt76_testmode_txbf_profile_update_all_cmd(struct mt76_phy *phy, struct nlattr **tb, u32 state)
++{
++#define PARAM_UNIT	5
++	static u8 pfmu_idx;
++	struct mt76_testmode_data *td = &phy->test;
++	struct mt76_dev *dev = phy->dev;
++	struct nlattr *cur;
++	u16 tmp_val[PARAM_UNIT], *val = td->txbf_param;
++	int idx, rem, ret, i = 0;
++
++	memset(td->txbf_param, 0, sizeof(td->txbf_param));
++	nla_for_each_nested(cur, tb[MT76_TM_ATTR_TXBF_PARAM], rem) {
++		if (nla_len(cur) != 2)
++			return -EINVAL;
++		idx = i % PARAM_UNIT;
++		tmp_val[idx] = nla_get_u16(cur);
++		if (idx == 1 && (tmp_val[idx] == 0xf0 || tmp_val[idx] == 0xff)) {
++			pfmu_idx = tmp_val[0];
++			return 0;
++		}
++		if (idx == PARAM_UNIT - 1) {
++			val[0] = pfmu_idx;
++			memcpy(val + 1, tmp_val, sizeof(tmp_val));
++			if (dev->test_ops->set_params) {
++				ret = dev->test_ops->set_params(phy, tb, state);
++				if (ret)
++					return ret;
++			}
++		}
++		i++;
++	}
++
++	return 0;
++}
++
+ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 		      void *data, int len)
+ {
+@@ -607,6 +643,30 @@ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 		}
+ 	}
+ 
++	if (tb[MT76_TM_ATTR_TXBF_ACT]) {
++		struct nlattr *cur;
++		int rem, idx = 0;
++
++		if (!tb[MT76_TM_ATTR_TXBF_PARAM] ||
++		    mt76_tm_get_u8(tb[MT76_TM_ATTR_TXBF_ACT], &td->txbf_act,
++				   0, MT76_TM_TXBF_ACT_MAX))
++			goto out;
++
++		if (td->txbf_act == MT76_TM_TXBF_ACT_PROF_UPDATE_ALL_CMD) {
++			err = mt76_testmode_txbf_profile_update_all_cmd(phy, tb, state);
++			goto out;
++		}
++
++		memset(td->txbf_param, 0, sizeof(td->txbf_param));
++		nla_for_each_nested(cur, tb[MT76_TM_ATTR_TXBF_PARAM], rem) {
++			if (nla_len(cur) != 2 ||
++			    idx >= ARRAY_SIZE(td->txbf_param))
++				goto out;
++
++			td->txbf_param[idx++] = nla_get_u16(cur);
++		}
++	}
++
+ 	if (dev->test_ops->set_params) {
+ 		err = dev->test_ops->set_params(phy, tb, state);
+ 		if (err)
+diff --git a/testmode.h b/testmode.h
+index 5d677f8c..bda7624a 100644
+--- a/testmode.h
++++ b/testmode.h
+@@ -286,6 +286,59 @@ enum mt76_testmode_eeprom_action {
+ 	MT76_TM_EEPROM_ACTION_MAX = NUM_MT76_TM_EEPROM_ACTION - 1,
+ };
+ 
++/**
++ * enum mt76_testmode_txbf_act - txbf action
++ *
++ * @MT76_TM_TXBF_ACT_GOLDEN_INIT: init ibf setting for golden device
++ * @MT76_TM_TXBF_ACT_INIT: init ibf setting for DUT
++ * @MT76_TM_TX_EBF_ACT_GOLDEN_INIT: init ebf setting for golden device
++ * @MT76_TM_TX_EBF_ACT_INIT: init ebf setting for DUT
++ * @MT76_TM_TXBF_ACT_UPDATE_CH: update channel info
++ * @MT76_TM_TXBF_ACT_PHASE_COMP: txbf phase compensation
++ * @MT76_TM_TXBF_ACT_TX_PREP: TX preparation for txbf
++ * @MT76_TM_TXBF_ACT_IBF_PROF_UPDATE: update ibf profile (pfmu tag, bf sta record)
++ * @MT76_TM_TXBF_ACT_EBF_PROF_UPDATE: update ebf profile
++ * @MT76_TM_TXBF_ACT_APPLY_TX: apply TX setting for txbf
++ * @MT76_TM_TXBF_ACT_PHASE_CAL: perform txbf phase calibration
++ * @MT76_TM_TXBF_ACT_PROF_UPDATE_ALL: update bf profile via instrument
++ * @MT76_TM_TXBF_ACT_PROF_UPDATE_ALL_CMD: update bf profile via instrument
++ * @MT76_TM_TXBF_ACT_E2P_UPDATE: write back txbf calibration result to eeprom
++ * @MT76_TM_TXBF_ACT_TRIGGER_SOUNDING: trigger beamformer to send sounding packet
++ * @MT76_TM_TXBF_ACT_STOP_SOUNDING: stop sending sounding packet
++ * @MT76_TM_TXBF_ACT_PROFILE_TAG_READ: read pfmu tag
++ * @MT76_TM_TXBF_ACT_PROFILE_TAG_WRITE: update pfmu tag
++ * @MT76_TM_TXBF_ACT_PROFILE_TAG_INVALID: invalidate pfmu tag
++ * @MT76_TM_TXBF_ACT_STA_REC_READ: read bf sta record
++ * @MT76_TM_TXBF_ACT_TXCMD: configure txcmd bf bit manually
++ */
++enum mt76_testmode_txbf_act {
++	MT76_TM_TXBF_ACT_GOLDEN_INIT,
++	MT76_TM_TXBF_ACT_INIT,
++	MT76_TM_TX_EBF_ACT_GOLDEN_INIT,
++	MT76_TM_TX_EBF_ACT_INIT,
++	MT76_TM_TXBF_ACT_UPDATE_CH,
++	MT76_TM_TXBF_ACT_PHASE_COMP,
++	MT76_TM_TXBF_ACT_TX_PREP,
++	MT76_TM_TXBF_ACT_IBF_PROF_UPDATE,
++	MT76_TM_TXBF_ACT_EBF_PROF_UPDATE,
++	MT76_TM_TXBF_ACT_APPLY_TX,
++	MT76_TM_TXBF_ACT_PHASE_CAL,
++	MT76_TM_TXBF_ACT_PROF_UPDATE_ALL,
++	MT76_TM_TXBF_ACT_PROF_UPDATE_ALL_CMD,
++	MT76_TM_TXBF_ACT_E2P_UPDATE,
++	MT76_TM_TXBF_ACT_TRIGGER_SOUNDING,
++	MT76_TM_TXBF_ACT_STOP_SOUNDING,
++	MT76_TM_TXBF_ACT_PROFILE_TAG_READ,
++	MT76_TM_TXBF_ACT_PROFILE_TAG_WRITE,
++	MT76_TM_TXBF_ACT_PROFILE_TAG_INVALID,
++	MT76_TM_TXBF_ACT_STA_REC_READ,
++	MT76_TM_TXBF_ACT_TXCMD,
++
++	/* keep last */
++	NUM_MT76_TM_TXBF_ACT,
++	MT76_TM_TXBF_ACT_MAX = NUM_MT76_TM_TXBF_ACT - 1,
++};
++
+ extern const struct nla_policy mt76_tm_policy[NUM_MT76_TM_ATTRS];
+ 
+ #endif
+diff --git a/tools/fields.c b/tools/fields.c
+index 77696ce7..f793d1a5 100644
+--- a/tools/fields.c
++++ b/tools/fields.c
+@@ -44,6 +44,30 @@ static const char * const testmode_offchan_bw[] = {
+ 	[NL80211_CHAN_WIDTH_160] = "160",
+ };
+ 
++static const char * const testmode_txbf_act[] = {
++	[MT76_TM_TXBF_ACT_GOLDEN_INIT] = "golden_init",
++	[MT76_TM_TXBF_ACT_INIT] = "init",
++	[MT76_TM_TX_EBF_ACT_GOLDEN_INIT] = "ebf_golden_init",
++	[MT76_TM_TX_EBF_ACT_INIT] = "ebf_init",
++	[MT76_TM_TXBF_ACT_UPDATE_CH] = "update_ch",
++	[MT76_TM_TXBF_ACT_PHASE_COMP] = "phase_comp",
++	[MT76_TM_TXBF_ACT_TX_PREP] = "tx_prep",
++	[MT76_TM_TXBF_ACT_IBF_PROF_UPDATE] = "ibf_prof_update",
++	[MT76_TM_TXBF_ACT_EBF_PROF_UPDATE] = "ebf_prof_update",
++	[MT76_TM_TXBF_ACT_APPLY_TX] = "apply_tx",
++	[MT76_TM_TXBF_ACT_PHASE_CAL] = "phase_cal",
++	[MT76_TM_TXBF_ACT_PROF_UPDATE_ALL] = "prof_update",
++	[MT76_TM_TXBF_ACT_PROF_UPDATE_ALL_CMD] = "prof_update_all",
++	[MT76_TM_TXBF_ACT_E2P_UPDATE] = "e2p_update",
++	[MT76_TM_TXBF_ACT_TRIGGER_SOUNDING] = "trigger_sounding",
++	[MT76_TM_TXBF_ACT_STOP_SOUNDING] = "stop_sounding",
++	[MT76_TM_TXBF_ACT_PROFILE_TAG_READ] = "pfmu_tag_read",
++	[MT76_TM_TXBF_ACT_PROFILE_TAG_WRITE] = "pfmu_tag_write",
++	[MT76_TM_TXBF_ACT_PROFILE_TAG_INVALID] = "set_invalid_prof",
++	[MT76_TM_TXBF_ACT_STA_REC_READ] = "sta_rec_read",
++	[MT76_TM_TXBF_ACT_TXCMD] = "txcmd",
++};
++
+ static void print_enum(const struct tm_field *field, struct nlattr *attr)
+ {
+ 	unsigned int i = nla_get_u8(attr);
+@@ -94,6 +118,17 @@ static void print_s8(const struct tm_field *field, struct nlattr *attr)
+ 	printf("%d", (int8_t)nla_get_u8(attr));
+ }
+ 
++static bool parse_u16_hex(const struct tm_field *field, int idx,
++			  struct nl_msg *msg, const char *val)
++{
++	return !nla_put_u16(msg, idx, strtoul(val, NULL, 16));
++}
++
++static void print_u16_hex(const struct tm_field *field, struct nlattr *attr)
++{
++	printf("%d", nla_get_u16(attr));
++}
++
+ static bool parse_u32(const struct tm_field *field, int idx,
+ 		      struct nl_msg *msg, const char *val)
+ {
+@@ -399,6 +434,8 @@ static const struct tm_field testdata_fields[NUM_MT76_TM_ATTRS] = {
+ 	FIELD(u8, AID, "aid"),
+ 	FIELD(u8, RU_ALLOC, "ru_alloc"),
+ 	FIELD(u8, RU_IDX, "ru_idx"),
++	FIELD_ENUM(TXBF_ACT, "txbf_act", testmode_txbf_act),
++	FIELD_ARRAY(u16_hex, TXBF_PARAM, "txbf_param"),
+ 	FIELD(u8, OFF_CH_SCAN_CH, "offchan_ch"),
+ 	FIELD(u8, OFF_CH_SCAN_CENTER_CH, "offchan_center_ch"),
+ 	FIELD_ENUM(OFF_CH_SCAN_BW, "offchan_bw", testmode_offchan_bw),
+-- 
+2.18.0
+