[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
+