blob: dcf7db6fc52589b17b43e10f39b9ae44e87db581 [file] [log] [blame]
diff --git a/package/kernel/mt76/patches/1006-mt76-mt7915-testmode-patches.patch b/package/kernel/mt76/patches/1006-mt76-mt7915-testmode-patches.patch
new file mode 100644
index 00000000..002d1fcb
--- /dev/null
+++ b/package/kernel/mt76/patches/1006-mt76-mt7915-testmode-patches.patch
@@ -0,0 +1,2014 @@
+From cdb7bc7976835e2b96ae5f19392a793d1895fd4f Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Wed, 19 Jan 2022 15:46:06 +0800
+Subject: [PATCH 1006/1006] mt76: mt7915: testmode patches
+
+---
+ drivers/net/wireless/mediatek/mt76/mac80211.c | 18 +-
+ drivers/net/wireless/mediatek/mt76/mt76.h | 123 ++++-
+ .../wireless/mediatek/mt76/mt76_connac_mcu.c | 5 +
+ .../wireless/mediatek/mt76/mt76_connac_mcu.h | 1 +
+ .../net/wireless/mediatek/mt76/mt7915/init.c | 2 +-
+ .../net/wireless/mediatek/mt76/mt7915/mac.c | 26 +-
+ .../net/wireless/mediatek/mt76/mt7915/mcu.c | 13 +-
+ .../net/wireless/mediatek/mt76/mt7915/mcu.h | 5 +
+ .../net/wireless/mediatek/mt76/mt7915/mmio.c | 2 +
+ .../wireless/mediatek/mt76/mt7915/mt7915.h | 2 +-
+ .../net/wireless/mediatek/mt76/mt7915/regs.h | 16 +-
+ .../wireless/mediatek/mt76/mt7915/testmode.c | 513 +++++++++++++++---
+ .../wireless/mediatek/mt76/mt7915/testmode.h | 38 ++
+ drivers/net/wireless/mediatek/mt76/testmode.c | 276 ++++++++--
+ drivers/net/wireless/mediatek/mt76/testmode.h | 71 +++
+ .../net/wireless/mediatek/mt76/tools/fields.c | 76 +++
+ drivers/net/wireless/mediatek/mt76/tx.c | 3 +-
+ 17 files changed, 1048 insertions(+), 142 deletions(-)
+
+diff --git a/mac80211.c b/mac80211.c
+index 9796419..e473227 100644
+--- a/mac80211.c
++++ b/mac80211.c
+@@ -45,6 +45,9 @@ static const struct ieee80211_channel mt76_channels_2ghz[] = {
+ };
+
+ static const struct ieee80211_channel mt76_channels_5ghz[] = {
++ CHAN5G(12, 5060),
++ CHAN5G(16, 5080),
++
+ CHAN5G(36, 5180),
+ CHAN5G(40, 5200),
+ CHAN5G(44, 5220),
+@@ -55,6 +58,13 @@ static const struct ieee80211_channel mt76_channels_5ghz[] = {
+ CHAN5G(60, 5300),
+ CHAN5G(64, 5320),
+
++ CHAN5G(68, 5340),
++ CHAN5G(80, 5400),
++ CHAN5G(84, 5420),
++ CHAN5G(88, 5440),
++ CHAN5G(92, 5460),
++ CHAN5G(96, 5480),
++
+ CHAN5G(100, 5500),
+ CHAN5G(104, 5520),
+ CHAN5G(108, 5540),
+@@ -75,6 +85,11 @@ static const struct ieee80211_channel mt76_channels_5ghz[] = {
+ CHAN5G(165, 5825),
+ CHAN5G(169, 5845),
+ CHAN5G(173, 5865),
++
++ CHAN5G(184, 4920),
++ CHAN5G(188, 4940),
++ CHAN5G(192, 4960),
++ CHAN5G(196, 4980),
+ };
+
+ static const struct ieee80211_channel mt76_channels_6ghz[] = {
+@@ -737,7 +752,8 @@ void mt76_rx(struct mt76_dev *dev, enum mt76_rxq_id q, struct sk_buff *skb)
+ }
+
+ #ifdef CONFIG_NL80211_TESTMODE
+- if (phy->test.state == MT76_TM_STATE_RX_FRAMES) {
++ if (!(phy->test.flag & MT_TM_FW_RX_COUNT) &&
++ phy->test.state == MT76_TM_STATE_RX_FRAMES) {
+ phy->test.rx_stats.packets[q]++;
+ if (status->flag & RX_FLAG_FAILED_FCS_CRC)
+ phy->test.rx_stats.fcs_error[q]++;
+diff --git a/mt76.h b/mt76.h
+index 5e10fe1..4b502c6 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -581,6 +581,25 @@ struct mt76_testmode_ops {
+ int (*set_params)(struct mt76_phy *phy, struct nlattr **tb,
+ enum mt76_testmode_state new_state);
+ int (*dump_stats)(struct mt76_phy *phy, struct sk_buff *msg);
++ int (*set_eeprom)(struct mt76_phy *phy, u32 offset, u8 *val, u8 action);
++};
++
++#define MT_TM_FW_RX_COUNT BIT(0)
++
++struct mt76_testmode_sta_data {
++ u16 tx_mpdu_len;
++ u8 tx_rate_idx;
++ u8 tx_rate_nss;
++ u8 tx_rate_ldpc;
++
++ u8 aid;
++ u8 ru_alloc;
++ u8 ru_idx;
++};
++
++struct mt76_testmode_sta {
++ struct sk_buff *tx_skb;
++ struct mt76_testmode_sta_data sd;
+ };
+
+ struct mt76_testmode_data {
+@@ -590,13 +609,9 @@ struct mt76_testmode_data {
+ struct sk_buff *tx_skb;
+
+ u32 tx_count;
+- u16 tx_mpdu_len;
+
+ u8 tx_rate_mode;
+- u8 tx_rate_idx;
+- u8 tx_rate_nss;
+ u8 tx_rate_sgi;
+- u8 tx_rate_ldpc;
+ u8 tx_rate_stbc;
+ u8 tx_ltf;
+
+@@ -614,6 +629,35 @@ struct mt76_testmode_data {
+
+ u8 addr[3][ETH_ALEN];
+
++ u8 flag;
++
++ struct {
++ u8 type;
++ u8 enable;
++ } cfg;
++
++ u8 off_ch_scan_ch;
++ u8 off_ch_scan_center_ch;
++ u8 off_ch_scan_bw;
++ u8 off_ch_scan_path;
++
++ struct mt76_wcid *tm_wcid[MT76_TM_MAX_STA_NUM + 1];
++ u8 cur_aid;
++ u16 tm_sta_mask;
++ union {
++ struct mt76_testmode_sta_data sd;
++ struct {
++ u16 tx_mpdu_len;
++ u8 tx_rate_idx;
++ u8 tx_rate_nss;
++ u8 tx_rate_ldpc;
++
++ u8 aid;
++ u8 ru_alloc;
++ u8 ru_idx;
++ };
++ };
++
+ u32 tx_pending;
+ u32 tx_queued;
+ u16 tx_queued_limit;
+@@ -621,6 +665,7 @@ struct mt76_testmode_data {
+ struct {
+ u64 packets[__MT_RXQ_MAX];
+ u64 fcs_error[__MT_RXQ_MAX];
++ u64 len_mismatch;
+ } rx_stats;
+ };
+
+@@ -1091,22 +1136,69 @@ static inline bool mt76_testmode_enabled(struct mt76_phy *phy)
+ #endif
+ }
+
++#ifdef CONFIG_NL80211_TESTMODE
++static inline bool
++mt76_testmode_has_sta(struct mt76_phy *phy)
++{
++ return phy->test.tm_sta_mask != 0;
++}
++
++static inline struct mt76_testmode_sta *
++mt76_testmode_aid_get_sta(struct mt76_phy *phy, u8 aid)
++{
++ struct mt76_wcid *wcid = phy->test.tm_wcid[aid];
++
++ if (!wcid || !aid)
++ return NULL;
++
++ return (struct mt76_testmode_sta *)((u8 *)wcid + phy->hw->sta_data_size);
++}
++
++#define mt76_testmode_for_each_sta(phy, aid, tm_sta) \
++ for (aid = 1, tm_sta = mt76_testmode_aid_get_sta(phy, 1); \
++ aid <= hweight16(phy->test.tm_sta_mask); \
++ aid = phy->test.tm_sta_mask >> aid ? \
++ ffs(phy->test.tm_sta_mask >> aid) + aid : \
++ aid + 1, \
++ tm_sta = mt76_testmode_aid_get_sta(phy, aid))
++
++static inline bool
++__mt76_testmode_check_skb(struct mt76_phy *phy, struct sk_buff *skb)
++{
++ struct mt76_testmode_sta *tm_sta;
++ int i;
++
++ if (!mt76_testmode_has_sta(phy))
++ return false;
++
++ mt76_testmode_for_each_sta(phy, i, tm_sta) {
++ if (tm_sta->tx_skb == skb)
++ return true;
++ }
++
++ return false;
++}
++
+ static inline bool mt76_is_testmode_skb(struct mt76_dev *dev,
+ struct sk_buff *skb,
+ struct ieee80211_hw **hw)
+ {
+-#ifdef CONFIG_NL80211_TESTMODE
+- if (skb == dev->phy.test.tx_skb)
+- *hw = dev->phy.hw;
+- else if (dev->phy2 && skb == dev->phy2->test.tx_skb)
+- *hw = dev->phy2->hw;
+- else
+- return false;
+- return true;
+-#else
++ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
++ struct mt76_phy *phy = &dev->phy;
++
++ if ((info->hw_queue & MT_TX_HW_QUEUE_EXT_PHY) && dev->phy2)
++ phy = dev->phy2;
++
++ if (mt76_testmode_enabled(phy) &&
++ (skb == phy->test.tx_skb ||
++ __mt76_testmode_check_skb(phy, skb))) {
++ *hw = phy->hw;
++ return true;
++ }
++
+ return false;
+-#endif
+ }
++#endif
+
+ void mt76_rx(struct mt76_dev *dev, enum mt76_rxq_id q, struct sk_buff *skb);
+ void mt76_tx(struct mt76_phy *dev, struct ieee80211_sta *sta,
+@@ -1198,7 +1290,7 @@ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ int mt76_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *skb,
+ struct netlink_callback *cb, void *data, int len);
+ int mt76_testmode_set_state(struct mt76_phy *phy, enum mt76_testmode_state state);
+-int mt76_testmode_alloc_skb(struct mt76_phy *phy, u32 len);
++int mt76_testmode_init_skb(struct mt76_phy *phy, u32 len, u8 aid, struct sk_buff **skb);
+
+ static inline void mt76_testmode_reset(struct mt76_phy *phy, bool disable)
+ {
+@@ -1212,7 +1304,6 @@ static inline void mt76_testmode_reset(struct mt76_phy *phy, bool disable)
+ #endif
+ }
+
+-
+ /* internal */
+ static inline struct ieee80211_hw *
+ mt76_tx_status_get_hw(struct mt76_dev *dev, struct sk_buff *skb)
+diff --git a/mt76_connac_mcu.c b/mt76_connac_mcu.c
+index 0a646ae..9158329 100644
+--- a/mt76_connac_mcu.c
++++ b/mt76_connac_mcu.c
+@@ -389,6 +389,7 @@ void mt76_connac_mcu_sta_basic_tlv(struct sk_buff *skb,
+ switch (vif->type) {
+ case NL80211_IFTYPE_MESH_POINT:
+ case NL80211_IFTYPE_AP:
++ case NL80211_IFTYPE_MONITOR:
+ if (vif->p2p)
+ conn_type = CONNECTION_P2P_GC;
+ else
+@@ -577,6 +578,10 @@ void mt76_connac_mcu_wtbl_generic_tlv(struct mt76_dev *dev,
+ wtbl_tlv, sta_wtbl);
+ spe = (struct wtbl_spe *)tlv;
+ spe->spe_idx = 24;
++
++ /* check */
++ if (vif->type == NL80211_IFTYPE_MONITOR)
++ rx->rca1 = 0;
+ }
+ EXPORT_SYMBOL_GPL(mt76_connac_mcu_wtbl_generic_tlv);
+
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 8903e08..cb7d096 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -987,6 +987,7 @@ enum {
+ MCU_EXT_CMD_OFFCH_SCAN_CTRL = 0x9a,
+ MCU_EXT_CMD_SET_RDD_TH = 0x9d,
+ MCU_EXT_CMD_MURU_CTRL = 0x9f,
++ MCU_EXT_CMD_RX_STAT = 0xa4,
+ MCU_EXT_CMD_SET_SPR = 0xa8,
+ MCU_EXT_CMD_GROUP_PRE_CAL_INFO = 0xab,
+ MCU_EXT_CMD_DPD_PRE_CAL_INFO = 0xac,
+diff --git a/mt7915/init.c b/mt7915/init.c
+index aed4731..7ec48e0 100644
+--- a/mt7915/init.c
++++ b/mt7915/init.c
+@@ -568,7 +568,7 @@ static void mt7915_init_work(struct work_struct *work)
+ struct mt7915_dev *dev = container_of(work, struct mt7915_dev,
+ init_work);
+
+- mt7915_mcu_set_eeprom(dev);
++ mt7915_mcu_set_eeprom(dev, dev->flash_mode);
+ mt7915_mac_init(dev);
+ mt7915_init_txpower(dev, &dev->mphy.sband_2g.sband);
+ mt7915_init_txpower(dev, &dev->mphy.sband_5g.sband);
+diff --git a/mt7915/mac.c b/mt7915/mac.c
+index efdc1b1..9a5bb20 100644
+--- a/mt7915/mac.c
++++ b/mt7915/mac.c
+@@ -904,16 +904,28 @@ mt7915_mac_write_txwi_tm(struct mt7915_phy *phy, __le32 *txwi,
+ {
+ #ifdef CONFIG_NL80211_TESTMODE
+ struct mt76_testmode_data *td = &phy->mt76->test;
++ struct mt76_testmode_sta_data *sd = &td->sd;
+ const struct ieee80211_rate *r;
+- u8 bw, mode, nss = td->tx_rate_nss;
+- u8 rate_idx = td->tx_rate_idx;
++ u8 bw, mode, nss, rate_idx;
+ u16 rateval = 0;
+ u32 val;
+ bool cck = false;
+ int band;
+
+- if (skb != phy->mt76->test.tx_skb)
+- return;
++ if (mt76_testmode_has_sta(phy->mt76)) {
++ struct mt76_testmode_sta *tm_sta;
++ int i;
++
++ mt76_testmode_for_each_sta(phy->mt76, i, tm_sta) {
++ if (tm_sta->tx_skb == skb) {
++ sd = &tm_sta->sd;
++ break;
++ }
++ }
++ }
++
++ nss = sd->tx_rate_nss;
++ rate_idx = sd->tx_rate_idx;
+
+ switch (td->tx_rate_mode) {
+ case MT76_TM_TX_MODE_HT:
+@@ -1003,9 +1015,10 @@ mt7915_mac_write_txwi_tm(struct mt7915_phy *phy, __le32 *txwi,
+ if (mode >= MT_PHY_TYPE_HE_SU)
+ val |= FIELD_PREP(MT_TXD6_HELTF, td->tx_ltf);
+
+- if (td->tx_rate_ldpc || (bw > 0 && mode >= MT_PHY_TYPE_HE_SU))
++ if (sd->tx_rate_ldpc || (bw > 0 && mode >= MT_PHY_TYPE_HE_SU))
+ val |= MT_TXD6_LDPC;
+
++ txwi[1] &= ~cpu_to_le32(MT_TXD1_VTA);
+ txwi[3] &= ~cpu_to_le32(MT_TXD3_SN_VALID);
+ txwi[6] |= cpu_to_le32(val);
+ txwi[7] |= cpu_to_le32(FIELD_PREP(MT_TXD7_SPE_IDX,
+@@ -1472,6 +1485,9 @@ mt7915_mac_tx_free(struct mt7915_dev *dev, void *data, int len)
+ continue;
+
+ msta = container_of(wcid, struct mt7915_sta, wcid);
++ if (mt76_testmode_enabled(msta->vif->phy->mt76))
++ continue;
++
+ spin_lock_bh(&dev->sta_poll_lock);
+ if (list_empty(&msta->poll_list))
+ list_add_tail(&msta->poll_list, &dev->sta_poll_list);
+diff --git a/mt7915/mcu.c b/mt7915/mcu.c
+index bb77edc..29ba3ed 100644
+--- a/mt7915/mcu.c
++++ b/mt7915/mcu.c
+@@ -289,7 +289,6 @@ mt7915_mcu_send_message(struct mt76_dev *mdev, struct sk_buff *skb,
+ if (mcu_txd->ext_cid) {
+ mcu_txd->ext_cid_ack = 1;
+
+- /* do not use Q_SET for efuse */
+ if (cmd & __MCU_CMD_FIELD_QUERY)
+ mcu_txd->set_query = MCU_Q_QUERY;
+ else
+@@ -2784,7 +2783,6 @@ int mt7915_mcu_set_chan_info(struct mt7915_phy *phy, int cmd)
+ struct mt7915_dev *dev = phy->dev;
+ struct cfg80211_chan_def *chandef = &phy->mt76->chandef;
+ int freq1 = chandef->center_freq1;
+- bool ext_phy = phy != &dev->phy;
+ struct {
+ u8 control_ch;
+ u8 center_ch;
+@@ -2814,14 +2812,9 @@ int mt7915_mcu_set_chan_info(struct mt7915_phy *phy, int cmd)
+
+ #ifdef CONFIG_NL80211_TESTMODE
+ if (phy->mt76->test.tx_antenna_mask &&
+- (phy->mt76->test.state == MT76_TM_STATE_TX_FRAMES ||
+- phy->mt76->test.state == MT76_TM_STATE_RX_FRAMES ||
+- phy->mt76->test.state == MT76_TM_STATE_TX_CONT)) {
++ mt76_testmode_enabled(phy->mt76)) {
+ req.tx_streams_num = fls(phy->mt76->test.tx_antenna_mask);
+ req.rx_streams = phy->mt76->test.tx_antenna_mask;
+-
+- if (ext_phy)
+- req.rx_streams >>= dev->chainshift;
+ }
+ #endif
+
+@@ -2887,14 +2880,14 @@ static int mt7915_mcu_set_eeprom_flash(struct mt7915_dev *dev)
+ return 0;
+ }
+
+-int mt7915_mcu_set_eeprom(struct mt7915_dev *dev)
++int mt7915_mcu_set_eeprom(struct mt7915_dev *dev, bool flash_mode)
+ {
+ struct mt7915_mcu_eeprom req = {
+ .buffer_mode = EE_MODE_EFUSE,
+ .format = EE_FORMAT_WHOLE,
+ };
+
+- if (dev->flash_mode)
++ if (flash_mode)
+ return mt7915_mcu_set_eeprom_flash(dev);
+
+ return mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(EFUSE_BUFFER_MODE),
+diff --git a/mt7915/mcu.h b/mt7915/mcu.h
+index 30211cb..4b78468 100644
+--- a/mt7915/mcu.h
++++ b/mt7915/mcu.h
+@@ -27,7 +27,12 @@ struct mt7915_mcu_txd {
+
+ enum {
+ MCU_ATE_SET_TRX = 0x1,
++ MCU_ATE_SET_TSSI = 0x5,
++ MCU_ATE_SET_DPD = 0x6,
++ MCU_ATE_SET_RATE_POWER_OFFSET = 0x7,
++ MCU_ATE_SET_THERMAL_COMP = 0x8,
+ MCU_ATE_SET_FREQ_OFFSET = 0xa,
++ MCU_ATE_SET_PHY_COUNT = 0x11,
+ MCU_ATE_SET_SLOT_TIME = 0x13,
+ MCU_ATE_CLEAN_TXQUEUE = 0x1c,
+ };
+diff --git a/mt7915/mmio.c b/mt7915/mmio.c
+index 1b14bba..207941d 100644
+--- a/mt7915/mmio.c
++++ b/mt7915/mmio.c
+@@ -53,6 +53,7 @@ static const u32 mt7986_reg[] = {
+ };
+
+ static const u32 mt7915_offs[] = {
++ [TMAC_TCR2] = 0x05c,
+ [TMAC_CDTR] = 0x090,
+ [TMAC_ODTR] = 0x094,
+ [TMAC_ATCR] = 0x098,
+@@ -125,6 +126,7 @@ static const u32 mt7915_offs[] = {
+ };
+
+ static const u32 mt7916_offs[] = {
++ [TMAC_TCR2] = 0x004,
+ [TMAC_CDTR] = 0x0c8,
+ [TMAC_ODTR] = 0x0cc,
+ [TMAC_ATCR] = 0x00c,
+diff --git a/mt7915/mt7915.h b/mt7915/mt7915.h
+index b3abe77..db4c6bc 100644
+--- a/mt7915/mt7915.h
++++ b/mt7915/mt7915.h
+@@ -539,7 +539,7 @@ int mt7915_mcu_set_fixed_rate_ctrl(struct mt7915_dev *dev,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta,
+ void *data, u32 field);
+-int mt7915_mcu_set_eeprom(struct mt7915_dev *dev);
++int mt7915_mcu_set_eeprom(struct mt7915_dev *dev, bool flash_mode);
+ int mt7915_mcu_get_eeprom(struct mt7915_dev *dev, u32 offset);
+ int mt7915_mcu_get_eeprom_free_block(struct mt7915_dev *dev, u8 *block_num);
+ int mt7915_mcu_set_mac(struct mt7915_dev *dev, int band, bool enable,
+diff --git a/mt7915/regs.h b/mt7915/regs.h
+index 71f325a..dcfb3f8 100644
+--- a/mt7915/regs.h
++++ b/mt7915/regs.h
+@@ -34,6 +34,7 @@ enum reg_rev {
+ };
+
+ enum offs_rev {
++ TMAC_TCR2,
+ TMAC_CDTR,
+ TMAC_ODTR,
+ TMAC_ATCR,
+@@ -171,6 +172,12 @@ enum offs_rev {
+ #define MT_MDP_TO_HIF 0
+ #define MT_MDP_TO_WM 1
+
++#define MT_MDP_TOP_DBG_WDT_CTRL MT_MDP(0x0d0)
++#define MT_MDP_TOP_DBG_WDT_CTRL_TDP_DIS_BLK BIT(7)
++
++#define MT_MDP_TOP_DBG_CTRL MT_MDP(0x0dc)
++#define MT_MDP_TOP_DBG_CTRL_ENQ_MODE BIT(30)
++
+ /* TMAC: band 0(0x820e4000), band 1(0x820f4000) */
+ #define MT_WF_TMAC_BASE(_band) ((_band) ? 0x820f4000 : 0x820e4000)
+ #define MT_WF_TMAC(_band, ofs) (MT_WF_TMAC_BASE(_band) + (ofs))
+@@ -179,6 +186,9 @@ enum offs_rev {
+ #define MT_TMAC_TCR0_TX_BLINK GENMASK(7, 6)
+ #define MT_TMAC_TCR0_TBTT_STOP_CTRL BIT(25)
+
++#define MT_TMAC_TCR2(_band) MT_WF_TMAC(_band, __OFFS(TMAC_TCR2))
++#define MT_TMAC_TCR2_SCH_DET_DIS BIT(19)
++
+ #define MT_TMAC_CDTR(_band) MT_WF_TMAC(_band, __OFFS(TMAC_CDTR))
+ #define MT_TMAC_ODTR(_band) MT_WF_TMAC(_band, __OFFS(TMAC_ODTR))
+ #define MT_TIMEOUT_VAL_PLCP GENMASK(15, 0)
+@@ -437,8 +447,10 @@ enum offs_rev {
+ #define MT_AGG_PCR0_VHT_PROT BIT(13)
+ #define MT_AGG_PCR0_PTA_WIN_DIS BIT(15)
+
+-#define MT_AGG_PCR1_RTS0_NUM_THRES GENMASK(31, 23)
+-#define MT_AGG_PCR1_RTS0_LEN_THRES GENMASK(19, 0)
++#define MT_AGG_PCR1_RTS0_NUM_THRES GENMASK(31, 23)
++#define MT_AGG_PCR1_RTS0_LEN_THRES GENMASK(19, 0)
++#define MT_AGG_PCR1_RTS0_NUM_THRES_MT7916 GENMASK(29, 24)
++#define MT_AGG_PCR1_RTS0_LEN_THRES_MT7916 GENMASK(22, 0)
+
+ #define MT_AGG_ACR0(_band) MT_WF_AGG(_band, __OFFS(AGG_ACR0))
+ #define MT_AGG_ACR_CFEND_RATE GENMASK(13, 0)
+diff --git a/mt7915/testmode.c b/mt7915/testmode.c
+index 6605e24..9f13919 100644
+--- a/mt7915/testmode.c
++++ b/mt7915/testmode.c
+@@ -9,6 +9,9 @@
+ enum {
+ TM_CHANGED_TXPOWER,
+ TM_CHANGED_FREQ_OFFSET,
++ TM_CHANGED_CFG,
++ TM_CHANGED_OFF_CH_SCAN_CH,
++ TM_CHANGED_AID,
+
+ /* must be last */
+ NUM_TM_CHANGED
+@@ -17,6 +20,9 @@ enum {
+ static const u8 tm_change_map[] = {
+ [TM_CHANGED_TXPOWER] = MT76_TM_ATTR_TX_POWER,
+ [TM_CHANGED_FREQ_OFFSET] = MT76_TM_ATTR_FREQ_OFFSET,
++ [TM_CHANGED_CFG] = MT76_TM_ATTR_CFG,
++ [TM_CHANGED_OFF_CH_SCAN_CH] = MT76_TM_ATTR_OFF_CH_SCAN_CH,
++ [TM_CHANGED_AID] = MT76_TM_ATTR_AID,
+ };
+
+ struct reg_band {
+@@ -30,10 +36,29 @@ struct reg_band {
+ { _list.band[0] = MT_##_reg(0, _idx); \
+ _list.band[1] = MT_##_reg(1, _idx); }
+
+-#define TM_REG_MAX_ID 17
++#define TM_REG_MAX_ID 20
+ static struct reg_band reg_backup_list[TM_REG_MAX_ID];
+
+
++static u8 mt7915_tm_chan_bw(enum nl80211_chan_width width)
++{
++ static const u8 width_to_bw[] = {
++ [NL80211_CHAN_WIDTH_40] = TM_CBW_40MHZ,
++ [NL80211_CHAN_WIDTH_80] = TM_CBW_80MHZ,
++ [NL80211_CHAN_WIDTH_80P80] = TM_CBW_8080MHZ,
++ [NL80211_CHAN_WIDTH_160] = TM_CBW_160MHZ,
++ [NL80211_CHAN_WIDTH_5] = TM_CBW_5MHZ,
++ [NL80211_CHAN_WIDTH_10] = TM_CBW_10MHZ,
++ [NL80211_CHAN_WIDTH_20] = TM_CBW_20MHZ,
++ [NL80211_CHAN_WIDTH_20_NOHT] = TM_CBW_20MHZ,
++ };
++
++ if (width >= ARRAY_SIZE(width_to_bw))
++ return 0;
++
++ return width_to_bw[width];
++}
++
+ static int
+ mt7915_tm_set_tx_power(struct mt7915_phy *phy)
+ {
+@@ -119,15 +144,45 @@ mt7915_tm_set_trx(struct mt7915_phy *phy, int type, bool en)
+ }
+
+ static int
+-mt7915_tm_clean_hwq(struct mt7915_phy *phy, u8 wcid)
++mt7915_tm_clean_hwq(struct mt7915_phy *phy)
+ {
+ struct mt7915_dev *dev = phy->dev;
+ struct mt7915_tm_cmd req = {
+ .testmode_en = 1,
+ .param_idx = MCU_ATE_CLEAN_TXQUEUE,
+- .param.clean.wcid = wcid,
+ .param.clean.band = phy != &dev->phy,
+ };
++ struct mt76_testmode_sta *tm_sta;
++ int ret, i;
++
++ if (!mt76_testmode_has_sta(phy->mt76)) {
++ req.param.clean.wcid = dev->mt76.global_wcid.idx;
++
++ return mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(ATE_CTRL),
++ &req, sizeof(req), false);
++ }
++
++ mt76_testmode_for_each_sta(phy->mt76, i, tm_sta) {
++ req.param.clean.wcid = phy->mt76->test.tm_wcid[i]->idx;
++ ret = mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(ATE_CTRL),
++ &req, sizeof(req), false);
++ if (ret)
++ return ret;
++ }
++
++ return 0;
++}
++
++static int
++mt7915_tm_set_phy_count(struct mt7915_phy *phy, u8 control)
++{
++ struct mt7915_dev *dev = phy->dev;
++ struct mt7915_tm_cmd req = {
++ .testmode_en = 1,
++ .param_idx = MCU_ATE_SET_PHY_COUNT,
++ .param.cfg.enable = control,
++ .param.cfg.band = phy != &dev->phy,
++ };
+
+ return mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(ATE_CTRL), &req,
+ sizeof(req), false);
+@@ -167,6 +222,77 @@ mt7915_tm_set_tam_arb(struct mt7915_phy *phy, bool enable, bool mu)
+ return mt7915_mcu_set_muru_ctrl(dev, MURU_SET_ARB_OP_MODE, op_mode);
+ }
+
++static int
++mt7915_tm_set_cfg(struct mt7915_phy *phy)
++{
++ static const u8 cfg_cmd[] = {
++ [MT76_TM_CFG_TSSI] = MCU_ATE_SET_TSSI,
++ [MT76_TM_CFG_DPD] = MCU_ATE_SET_DPD,
++ [MT76_TM_CFG_RATE_POWER_OFFSET] = MCU_ATE_SET_RATE_POWER_OFFSET,
++ [MT76_TM_CFG_THERMAL_COMP] = MCU_ATE_SET_THERMAL_COMP,
++ };
++ struct mt76_testmode_data *td = &phy->mt76->test;
++ struct mt7915_dev *dev = phy->dev;
++ struct mt7915_tm_cmd req = {
++ .testmode_en = !(phy->mt76->test.state == MT76_TM_STATE_OFF),
++ .param_idx = cfg_cmd[td->cfg.type],
++ .param.cfg.enable = td->cfg.enable,
++ .param.cfg.band = phy != &dev->phy,
++ };
++
++ return mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(ATE_CTRL), &req,
++ sizeof(req), false);
++}
++
++static int
++mt7915_tm_set_off_channel_scan(struct mt7915_phy *phy)
++{
++#define OFF_CH_SCAN_SIMPLE_RX 2
++ struct mt76_testmode_data *td = &phy->mt76->test;
++ struct mt7915_dev *dev = phy->dev;
++ struct cfg80211_chan_def *chandef = &phy->mt76->chandef;
++ int freq1 = chandef->center_freq1;
++ struct {
++ u8 cur_pri_ch;
++ u8 cur_center_ch;
++ u8 cur_bw;
++ u8 cur_tx_path;
++ u8 cur_rx_path;
++
++ u8 scan_pri_ch;
++ u8 scan_center_ch;
++ u8 scan_bw;
++ u8 scan_tx_path;
++ u8 scan_rx_path;
++
++ u8 enable;
++ u8 band_idx;
++ u8 type;
++ u8 is_5g;
++ u8 _rsv[2];
++ } __packed req = {
++ .cur_pri_ch = chandef->chan->hw_value,
++ .cur_center_ch = ieee80211_frequency_to_channel(freq1),
++ .cur_bw = mt7915_tm_chan_bw(chandef->width),
++ .cur_tx_path = td->tx_antenna_mask,
++ .cur_rx_path = td->tx_antenna_mask,
++
++ .scan_pri_ch = td->off_ch_scan_ch,
++ .scan_center_ch = td->off_ch_scan_center_ch,
++ .scan_bw = td->off_ch_scan_bw,
++ .scan_tx_path = td->off_ch_scan_path,
++ .scan_rx_path = td->off_ch_scan_path,
++
++ .enable = !!td->off_ch_scan_ch,
++ .band_idx = phy != &dev->phy,
++ .type = OFF_CH_SCAN_SIMPLE_RX,
++ .is_5g = td->off_ch_scan_ch > 14 ? 1 : 0,
++ };
++
++ return mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(OFFCH_SCAN_CTRL), &req,
++ sizeof(req), false);
++}
++
+ static int
+ mt7915_tm_set_wmm_qid(struct mt7915_dev *dev, u8 qid, u8 aifs, u8 cw_min,
+ u16 cw_max, u16 txop)
+@@ -320,7 +446,7 @@ mt7915_tm_set_tx_len(struct mt7915_phy *phy, u32 tx_time)
+ bitrate = cfg80211_calculate_bitrate(&rate);
+ tx_len = bitrate * tx_time / 10 / 8;
+
+- ret = mt76_testmode_alloc_skb(phy->mt76, tx_len);
++ ret = mt76_testmode_init_skb(phy->mt76, tx_len, 0, &td->tx_skb);
+ if (ret)
+ return ret;
+
+@@ -332,7 +458,7 @@ mt7915_tm_reg_backup_restore(struct mt7915_phy *phy)
+ {
+ int n_regs = ARRAY_SIZE(reg_backup_list);
+ struct mt7915_dev *dev = phy->dev;
+- u32 *b = phy->test.reg_backup;
++ u32 *b = phy->test.reg_backup, val;
+ int i;
+
+ REG_BAND_IDX(reg_backup_list[0], AGG_PCR0, 0);
+@@ -344,18 +470,28 @@ mt7915_tm_reg_backup_restore(struct mt7915_phy *phy)
+ REG_BAND(reg_backup_list[6], AGG_MRCR);
+ REG_BAND(reg_backup_list[7], TMAC_TFCR0);
+ REG_BAND(reg_backup_list[8], TMAC_TCR0);
+- REG_BAND(reg_backup_list[9], AGG_ATCR1);
+- REG_BAND(reg_backup_list[10], AGG_ATCR3);
+- REG_BAND(reg_backup_list[11], TMAC_TRCR0);
+- REG_BAND(reg_backup_list[12], TMAC_ICR0);
+- REG_BAND_IDX(reg_backup_list[13], ARB_DRNGR0, 0);
+- REG_BAND_IDX(reg_backup_list[14], ARB_DRNGR0, 1);
+- REG_BAND(reg_backup_list[15], WF_RFCR);
+- REG_BAND(reg_backup_list[16], WF_RFCR1);
++ REG_BAND(reg_backup_list[9], TMAC_TCR2);
++ REG_BAND(reg_backup_list[10], AGG_ATCR1);
++ REG_BAND(reg_backup_list[11], AGG_ATCR3);
++ REG_BAND(reg_backup_list[12], TMAC_TRCR0);
++ REG_BAND(reg_backup_list[13], TMAC_ICR0);
++ REG_BAND_IDX(reg_backup_list[14], ARB_DRNGR0, 0);
++ REG_BAND_IDX(reg_backup_list[15], ARB_DRNGR0, 1);
++ REG_BAND(reg_backup_list[16], WF_RFCR);
++ REG_BAND(reg_backup_list[17], WF_RFCR1);
++
++ if (is_mt7916(&dev->mt76)) {
++ reg_backup_list[18].band[phy->band_idx] = MT_MDP_TOP_DBG_WDT_CTRL;
++ reg_backup_list[19].band[phy->band_idx] = MT_MDP_TOP_DBG_CTRL;
++ }
+
+ if (phy->mt76->test.state == MT76_TM_STATE_OFF) {
+- for (i = 0; i < n_regs; i++)
+- mt76_wr(dev, reg_backup_list[i].band[phy->band_idx], b[i]);
++ for (i = 0; i < n_regs; i++) {
++ u8 reg = reg_backup_list[i].band[phy->band_idx];
++
++ if (reg)
++ mt76_wr(dev, reg, b[i]);
++ }
+ return;
+ }
+
+@@ -375,8 +511,13 @@ mt7915_tm_reg_backup_restore(struct mt7915_phy *phy)
+ MT_AGG_PCR0_BW40_PROT | MT_AGG_PCR0_BW80_PROT);
+ mt76_set(dev, MT_AGG_PCR0(phy->band_idx, 0), MT_AGG_PCR0_PTA_WIN_DIS);
+
+- mt76_wr(dev, MT_AGG_PCR0(phy->band_idx, 1), MT_AGG_PCR1_RTS0_NUM_THRES |
+- MT_AGG_PCR1_RTS0_LEN_THRES);
++ if (is_mt7915(&dev->mt76))
++ val = MT_AGG_PCR1_RTS0_NUM_THRES | MT_AGG_PCR1_RTS0_LEN_THRES;
++ else
++ val = MT_AGG_PCR1_RTS0_NUM_THRES_MT7916 |
++ MT_AGG_PCR1_RTS0_LEN_THRES_MT7916;
++
++ mt76_wr(dev, MT_AGG_PCR0(phy->band_idx, 1), val);
+
+ mt76_clear(dev, MT_AGG_MRCR(phy->band_idx), MT_AGG_MRCR_BAR_CNT_LIMIT |
+ MT_AGG_MRCR_LAST_RTS_CTS_RN | MT_AGG_MRCR_RTS_FAIL_LIMIT |
+@@ -389,31 +530,124 @@ mt7915_tm_reg_backup_restore(struct mt7915_phy *phy)
+
+ mt76_wr(dev, MT_TMAC_TFCR0(phy->band_idx), 0);
+ mt76_clear(dev, MT_TMAC_TCR0(phy->band_idx), MT_TMAC_TCR0_TBTT_STOP_CTRL);
++ mt76_set(dev, MT_TMAC_TCR2(phy->band_idx), MT_TMAC_TCR2_SCH_DET_DIS);
+
+ /* config rx filter for testmode rx */
+ mt76_wr(dev, MT_WF_RFCR(phy->band_idx), 0xcf70a);
+ mt76_wr(dev, MT_WF_RFCR1(phy->band_idx), 0);
++
++ if (is_mt7916(&dev->mt76)) {
++ /* enable MDP Tx block mode */
++ mt76_clear(dev, MT_MDP_TOP_DBG_WDT_CTRL,
++ MT_MDP_TOP_DBG_WDT_CTRL_TDP_DIS_BLK);
++ mt76_clear(dev, MT_MDP_TOP_DBG_CTRL,
++ MT_MDP_TOP_DBG_CTRL_ENQ_MODE);
++ }
++}
++
++static int
++mt7915_tm_sta_add(struct mt7915_phy *phy, u8 aid,
++ struct mt76_testmode_sta_data *sd)
++{
++ struct mt76_testmode_data *td = &phy->mt76->test;
++ struct mt76_testmode_sta *tm_sta;
++
++ if (!aid)
++ return 0;
++
++ if (!td->tm_wcid[aid]) {
++ struct ieee80211_vif *vif = phy->monitor_vif;
++ struct ieee80211_sband_iftype_data *data;
++ struct ieee80211_supported_band *sband;
++ struct ieee80211_sta *sta;
++ struct mt7915_sta *msta;
++ int ret;
++
++ sta = kzalloc(sizeof(*sta) + phy->mt76->hw->sta_data_size +
++ sizeof(*tm_sta), GFP_KERNEL);
++ if (!sta)
++ return -ENOMEM;
++
++ if (phy->mt76->chandef.chan->band == NL80211_BAND_5GHZ) {
++ sband = &phy->mt76->sband_5g.sband;
++ data = phy->iftype[NL80211_BAND_5GHZ];
++ } else {
++ sband = &phy->mt76->sband_2g.sband;
++ data = phy->iftype[NL80211_BAND_2GHZ];
++ }
++
++ ether_addr_copy(sta->addr, phy->mt76->macaddr);
++ sta->addr[0] += aid * 4;
++ memcpy(&sta->ht_cap, &sband->ht_cap, sizeof(sta->ht_cap));
++ memcpy(&sta->vht_cap, &sband->vht_cap, sizeof(sta->vht_cap));
++ memcpy(&sta->he_cap, &data[NL80211_IFTYPE_STATION].he_cap,
++ sizeof(sta->he_cap));
++ sta->aid = aid;
++ sta->wme = 1;
++
++ ret = mt7915_mac_sta_add(&phy->dev->mt76, vif, sta);
++ if (ret) {
++ kfree(sta);
++ return ret;
++ }
++
++ msta = (struct mt7915_sta *)sta->drv_priv;
++ td->tm_wcid[aid] = &msta->wcid;
++ td->tm_sta_mask |= BIT(aid - 1);
++ }
++
++ tm_sta = mt76_testmode_aid_get_sta(phy->mt76, aid);
++ memcpy(&tm_sta->sd, sd, sizeof(tm_sta->sd));
++
++ return 0;
+ }
+
+ static void
+-mt7915_tm_init(struct mt7915_phy *phy, bool en)
++mt7915_tm_sta_remove(struct mt7915_phy *phy, u8 aid)
+ {
++ struct mt76_testmode_data *td = &phy->mt76->test;
++ struct mt76_wcid *wcid = td->tm_wcid[aid];
+ struct mt7915_dev *dev = phy->dev;
++ struct ieee80211_sta *sta = wcid_to_sta(wcid);
+
+- if (!test_bit(MT76_STATE_RUNNING, &phy->mt76->state))
++ mt7915_mac_sta_remove(&dev->mt76, phy->monitor_vif, sta);
++ mt76_wcid_mask_clear(dev->mt76.wcid_mask, wcid->idx);
++
++ kfree(sta);
++ td->tm_wcid[aid] = NULL;
++ td->tm_sta_mask &= ~BIT(aid - 1);
++}
++
++static void
++mt7915_tm_sta_remove_all(struct mt7915_phy *phy)
++{
++ int i;
++
++ if (!mt76_testmode_has_sta(phy->mt76))
+ return;
+
+- mt7915_mcu_set_sku_en(phy, !en);
++ for (i = 1; i < ARRAY_SIZE(phy->mt76->test.tm_wcid); i++) {
++ if (phy->mt76->test.tm_wcid[i])
++ mt7915_tm_sta_remove(phy, i);
++ }
++}
+
+- mt7915_tm_mode_ctrl(dev, en);
+- mt7915_tm_reg_backup_restore(phy);
+- mt7915_tm_set_trx(phy, TM_MAC_TXRX, !en);
++static int
++mt7915_tm_set_sta(struct mt7915_phy *phy)
++{
++ struct mt76_testmode_data *td = &phy->mt76->test;
+
+- mt7915_mcu_add_bss_info(phy, phy->monitor_vif, en);
+- mt7915_mcu_add_sta(dev, phy->monitor_vif, NULL, en);
++ if (!td->aid) {
++ mt7915_tm_sta_remove_all(phy);
++ return 0;
++ }
+
+- if (!en)
+- mt7915_tm_set_tam_arb(phy, en, 0);
++ if (td->tx_count == 0) {
++ mt7915_tm_sta_remove(phy, td->aid);
++ return 0;
++ }
++
++ return mt7915_tm_sta_add(phy, td->aid, &td->sd);
+ }
+
+ static void
+@@ -426,59 +660,122 @@ mt7915_tm_update_channel(struct mt7915_phy *phy)
+ mt7915_mcu_set_chan_info(phy, MCU_EXT_CMD(SET_RX_PATH));
+ }
+
++static bool
++mt7915_tm_check_skb(struct mt7915_phy *phy)
++{
++ struct mt76_testmode_data *td = &phy->mt76->test;
++ struct ieee80211_tx_info *info;
++
++ if (!mt76_testmode_has_sta(phy->mt76)) {
++ if (!td->tx_skb)
++ return false;
++
++ info = IEEE80211_SKB_CB(td->tx_skb);
++ info->control.vif = phy->monitor_vif;
++ } else {
++ struct mt76_testmode_sta *tm_sta;
++ int i;
++
++ mt76_testmode_for_each_sta(phy->mt76, i, tm_sta) {
++ if (!tm_sta->tx_skb)
++ return false;
++
++ info = IEEE80211_SKB_CB(tm_sta->tx_skb);
++ info->control.vif = phy->monitor_vif;
++ }
++ }
++
++ return true;
++}
++
+ static void
+ mt7915_tm_set_tx_frames(struct mt7915_phy *phy, bool en)
+ {
+ static const u8 spe_idx_map[] = {0, 0, 1, 0, 3, 2, 4, 0,
+ 9, 8, 6, 10, 16, 12, 18, 0};
+ struct mt76_testmode_data *td = &phy->mt76->test;
+- struct mt7915_dev *dev = phy->dev;
+- struct ieee80211_tx_info *info;
+- u8 duty_cycle = td->tx_duty_cycle;
+- u32 tx_time = td->tx_time;
+- u32 ipg = td->tx_ipg;
+
+ mt7915_tm_set_trx(phy, TM_MAC_RX_RXV, false);
+- mt7915_tm_clean_hwq(phy, dev->mt76.global_wcid.idx);
++ mt7915_tm_set_trx(phy, TM_MAC_TX, false);
+
+ if (en) {
++ u32 tx_time = td->tx_time, ipg = td->tx_ipg;
++ u8 duty_cycle = td->tx_duty_cycle;
++
+ mt7915_tm_update_channel(phy);
+
+ if (td->tx_spe_idx) {
+ phy->test.spe_idx = td->tx_spe_idx;
+ } else {
+- u8 tx_ant = td->tx_antenna_mask;
++ phy->test.spe_idx = spe_idx_map[td->tx_antenna_mask];
++ }
+
+- if (phy != &dev->phy)
+- tx_ant >>= dev->chainshift;
+- phy->test.spe_idx = spe_idx_map[tx_ant];
++ /* if all three params are set, duty_cycle will be ignored */
++ if (duty_cycle && tx_time && !ipg) {
++ ipg = tx_time * 100 / duty_cycle - tx_time;
++ } else if (duty_cycle && !tx_time && ipg) {
++ if (duty_cycle < 100)
++ tx_time = duty_cycle * ipg / (100 - duty_cycle);
+ }
++
++ mt7915_tm_set_ipg_params(phy, ipg, td->tx_rate_mode);
++ mt7915_tm_set_tx_len(phy, tx_time);
++
++ if (ipg)
++ td->tx_queued_limit = MT76_TM_TIMEOUT * 1000000 / ipg / 2;
++
++ if (!mt7915_tm_check_skb(phy))
++ return;
++ } else {
++ mt7915_tm_clean_hwq(phy);
+ }
+
+ mt7915_tm_set_tam_arb(phy, en,
+ td->tx_rate_mode == MT76_TM_TX_MODE_HE_MU);
+
+- /* if all three params are set, duty_cycle will be ignored */
+- if (duty_cycle && tx_time && !ipg) {
+- ipg = tx_time * 100 / duty_cycle - tx_time;
+- } else if (duty_cycle && !tx_time && ipg) {
+- if (duty_cycle < 100)
+- tx_time = duty_cycle * ipg / (100 - duty_cycle);
+- }
++ mt7915_tm_set_trx(phy, TM_MAC_TX, en);
++}
+
+- mt7915_tm_set_ipg_params(phy, ipg, td->tx_rate_mode);
+- mt7915_tm_set_tx_len(phy, tx_time);
++static int
++mt7915_tm_get_rx_stats(struct mt7915_phy *phy, bool clear)
++{
++#define CMD_RX_STAT_BAND 0x3
++ struct mt76_testmode_data *td = &phy->mt76->test;
++ struct mt7915_tm_rx_stat_band *rs_band;
++ struct mt7915_dev *dev = phy->dev;
++ struct sk_buff *skb;
++ struct {
++ u8 format_id;
++ u8 band;
++ u8 _rsv[2];
++ } __packed req = {
++ .format_id = CMD_RX_STAT_BAND,
++ .band = phy != &dev->phy,
++ };
++ int ret;
+
+- if (ipg)
+- td->tx_queued_limit = MT76_TM_TIMEOUT * 1000000 / ipg / 2;
++ ret = mt76_mcu_send_and_get_msg(&dev->mt76, MCU_EXT_CMD(RX_STAT),
++ &req, sizeof(req), true, &skb);
++ if (ret)
++ return ret;
+
+- if (!en || !td->tx_skb)
+- return;
++ rs_band = (struct mt7915_tm_rx_stat_band *)skb->data;
++ /* pr_info("mdrdy_cnt = %d\n", le32_to_cpu(rs_band->mdrdy_cnt)); */
++ /* pr_info("fcs_err = %d\n", le16_to_cpu(rs_band->fcs_err)); */
++ /* pr_info("len_mismatch = %d\n", le16_to_cpu(rs_band->len_mismatch)); */
++ /* pr_info("fcs_ok = %d\n", le16_to_cpu(rs_band->fcs_succ)); */
+
+- info = IEEE80211_SKB_CB(td->tx_skb);
+- info->control.vif = phy->monitor_vif;
++ if (!clear) {
++ enum mt76_rxq_id q = req.band ? MT_RXQ_EXT : MT_RXQ_MAIN;
+
+- mt7915_tm_set_trx(phy, TM_MAC_TX, en);
++ td->rx_stats.packets[q] += le32_to_cpu(rs_band->mdrdy_cnt);
++ td->rx_stats.fcs_error[q] += le16_to_cpu(rs_band->fcs_err);
++ td->rx_stats.len_mismatch += le16_to_cpu(rs_band->len_mismatch);
++ }
++
++ dev_kfree_skb(skb);
++
++ return 0;
+ }
+
+ static void
+@@ -487,12 +784,15 @@ mt7915_tm_set_rx_frames(struct mt7915_phy *phy, bool en)
+ mt7915_tm_set_trx(phy, TM_MAC_RX_RXV, false);
+
+ if (en) {
+- struct mt7915_dev *dev = phy->dev;
+-
+ mt7915_tm_update_channel(phy);
+
+ /* read-clear */
+- mt76_rr(dev, MT_MIB_SDR3(phy != &dev->phy));
++ mt7915_tm_get_rx_stats(phy, true);
++
++ /* clear fw count */
++ mt7915_tm_set_phy_count(phy, 0);
++ mt7915_tm_set_phy_count(phy, 1);
++
+ mt7915_tm_set_trx(phy, TM_MAC_RX_RXV, en);
+ }
+ }
+@@ -631,6 +931,31 @@ out:
+ sizeof(req), true);
+ }
+
++static void
++mt7915_tm_init(struct mt7915_phy *phy, bool en)
++{
++ struct mt7915_dev *dev = phy->dev;
++
++ if (!test_bit(MT76_STATE_RUNNING, &phy->mt76->state))
++ return;
++
++ mt7915_mcu_set_sku_en(phy, !en);
++
++ mt7915_tm_mode_ctrl(dev, en);
++ mt7915_tm_reg_backup_restore(phy);
++ mt7915_tm_set_trx(phy, TM_MAC_TXRX, !en);
++
++ mt7915_mcu_add_bss_info(phy, phy->monitor_vif, en);
++ mt7915_mcu_add_sta(dev, phy->monitor_vif, NULL, en);
++
++ phy->mt76->test.flag |= MT_TM_FW_RX_COUNT;
++
++ if (!en) {
++ mt7915_tm_set_tam_arb(phy, en, 0);
++ mt7915_tm_sta_remove_all(phy);
++ }
++}
++
+ static void
+ mt7915_tm_update_params(struct mt7915_phy *phy, u32 changed)
+ {
+@@ -641,6 +966,12 @@ mt7915_tm_update_params(struct mt7915_phy *phy, u32 changed)
+ mt7915_tm_set_freq_offset(phy, en, en ? td->freq_offset : 0);
+ if (changed & BIT(TM_CHANGED_TXPOWER))
+ mt7915_tm_set_tx_power(phy);
++ if (changed & BIT(TM_CHANGED_CFG))
++ mt7915_tm_set_cfg(phy);
++ if (changed & BIT(TM_CHANGED_OFF_CH_SCAN_CH))
++ mt7915_tm_set_off_channel_scan(phy);
++ if (changed & BIT(TM_CHANGED_AID))
++ mt7915_tm_set_sta(phy);
+ }
+
+ static int
+@@ -700,9 +1031,6 @@ mt7915_tm_set_params(struct mt76_phy *mphy, struct nlattr **tb,
+ td->state == MT76_TM_STATE_OFF)
+ return 0;
+
+- if (td->tx_antenna_mask & ~mphy->chainmask)
+- return -EINVAL;
+-
+ for (i = 0; i < ARRAY_SIZE(tm_change_map); i++) {
+ if (tb[tm_change_map[i]])
+ changed |= BIT(i);
+@@ -717,12 +1045,8 @@ static int
+ mt7915_tm_dump_stats(struct mt76_phy *mphy, struct sk_buff *msg)
+ {
+ struct mt7915_phy *phy = mphy->priv;
+- struct mt7915_dev *dev = phy->dev;
+- enum mt76_rxq_id q;
+ void *rx, *rssi;
+- u16 fcs_err;
+ int i;
+- u32 cnt;
+
+ rx = nla_nest_start(msg, MT76_TM_STATS_ATTR_LAST_RX);
+ if (!rx)
+@@ -766,19 +1090,68 @@ mt7915_tm_dump_stats(struct mt76_phy *mphy, struct sk_buff *msg)
+
+ nla_nest_end(msg, rx);
+
+- cnt = mt76_rr(dev, MT_MIB_SDR3(phy->band_idx));
+- fcs_err = is_mt7915(&dev->mt76) ? FIELD_GET(MT_MIB_SDR3_FCS_ERR_MASK, cnt) :
+- FIELD_GET(MT_MIB_SDR3_FCS_ERR_MASK_MT7916, cnt);
++ return mt7915_tm_get_rx_stats(phy, false);
++}
++
++static int
++mt7915_tm_write_back_to_efuse(struct mt7915_dev *dev)
++{
++ struct mt7915_mcu_eeprom_info req = {};
++ u8 *eeprom = dev->mt76.eeprom.data;
++ int i, ret = -EINVAL;
+
+- q = phy->band_idx ? MT_RXQ_EXT : MT_RXQ_MAIN;
+- mphy->test.rx_stats.packets[q] += fcs_err;
+- mphy->test.rx_stats.fcs_error[q] += fcs_err;
++ if (is_mt7986(&dev->mt76))
++ goto out;
+
+- return 0;
++ /* prevent from damaging chip id in efuse */
++ if (mt76_chip(&dev->mt76) != get_unaligned_le16(eeprom))
++ goto out;
++
++ for (i = 0; i < MT7915_EEPROM_SIZE; i += MT76_TM_EEPROM_BLOCK_SIZE) {
++ req.addr = cpu_to_le32(i);
++ memcpy(&req.data, eeprom + i, MT76_TM_EEPROM_BLOCK_SIZE);
++
++ ret = mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(EFUSE_ACCESS),
++ &req, sizeof(req), true);
++ if (ret)
++ return ret;
++ }
++
++out:
++ return ret;
++}
++
++static int
++mt7915_tm_set_eeprom(struct mt76_phy *mphy, u32 offset, u8 *val, u8 action)
++{
++ struct mt7915_phy *phy = mphy->priv;
++ struct mt7915_dev *dev = phy->dev;
++ u8 *eeprom = dev->mt76.eeprom.data;
++ int ret = 0;
++
++ if (offset >= MT7915_EEPROM_SIZE)
++ return -EINVAL;
++
++ switch (action) {
++ case MT76_TM_EEPROM_ACTION_UPDATE_DATA:
++ memcpy(eeprom + offset, val, MT76_TM_EEPROM_BLOCK_SIZE);
++ break;
++ case MT76_TM_EEPROM_ACTION_UPDATE_BUFFER_MODE:
++ ret = mt7915_mcu_set_eeprom(dev, true);
++ break;
++ case MT76_TM_EEPROM_ACTION_WRITE_TO_EFUSE:
++ ret = mt7915_tm_write_back_to_efuse(dev);
++ break;
++ default:
++ break;
++ }
++
++ return ret;
+ }
+
+ const struct mt76_testmode_ops mt7915_testmode_ops = {
+ .set_state = mt7915_tm_set_state,
+ .set_params = mt7915_tm_set_params,
+ .dump_stats = mt7915_tm_dump_stats,
++ .set_eeprom = mt7915_tm_set_eeprom,
+ };
+diff --git a/mt7915/testmode.h b/mt7915/testmode.h
+index 5573ac3..d22aabe 100644
+--- a/mt7915/testmode.h
++++ b/mt7915/testmode.h
+@@ -33,6 +33,12 @@ struct mt7915_tm_clean_txq {
+ u8 rsv;
+ };
+
++struct mt7915_tm_cfg {
++ u8 enable;
++ u8 band;
++ u8 _rsv[2];
++};
++
+ struct mt7915_tm_cmd {
+ u8 testmode_en;
+ u8 param_idx;
+@@ -43,6 +49,7 @@ struct mt7915_tm_cmd {
+ struct mt7915_tm_freq_offset freq;
+ struct mt7915_tm_slot_time slot;
+ struct mt7915_tm_clean_txq clean;
++ struct mt7915_tm_cfg cfg;
+ u8 test[72];
+ } param;
+ } __packed;
+@@ -102,4 +109,35 @@ enum {
+ TAM_ARB_OP_MODE_FORCE_SU = 5,
+ };
+
++struct mt7915_tm_rx_stat_band {
++ u8 category;
++
++ /* mac */
++ __le16 fcs_err;
++ __le16 len_mismatch;
++ __le16 fcs_succ;
++ __le32 mdrdy_cnt;
++ /* phy */
++ __le16 fcs_err_cck;
++ __le16 fcs_err_ofdm;
++ __le16 pd_cck;
++ __le16 pd_ofdm;
++ __le16 sig_err_cck;
++ __le16 sfd_err_cck;
++ __le16 sig_err_ofdm;
++ __le16 tag_err_ofdm;
++ __le16 mdrdy_cnt_cck;
++ __le16 mdrdy_cnt_ofdm;
++};
++
++enum {
++ TM_CBW_20MHZ,
++ TM_CBW_40MHZ,
++ TM_CBW_80MHZ,
++ TM_CBW_10MHZ,
++ TM_CBW_5MHZ,
++ TM_CBW_160MHZ,
++ TM_CBW_8080MHZ,
++};
++
+ #endif
+diff --git a/testmode.c b/testmode.c
+index 382b456..9da490c 100644
+--- a/testmode.c
++++ b/testmode.c
+@@ -25,18 +25,18 @@ const struct nla_policy mt76_tm_policy[NUM_MT76_TM_ATTRS] = {
+ };
+ EXPORT_SYMBOL_GPL(mt76_tm_policy);
+
+-void mt76_testmode_tx_pending(struct mt76_phy *phy)
++static u16
++mt76_testmode_queue_tx(struct mt76_phy *phy, struct mt76_wcid *wcid,
++ struct sk_buff *skb, u32 limit)
+ {
+ struct mt76_testmode_data *td = &phy->test;
+ struct mt76_dev *dev = phy->dev;
+- struct mt76_wcid *wcid = &dev->global_wcid;
+- struct sk_buff *skb = td->tx_skb;
+ struct mt76_queue *q;
+- u16 tx_queued_limit;
++ u16 tx_queued_limit, count = 0;
+ int qid;
+
+- if (!skb || !td->tx_pending)
+- return;
++ if (!skb)
++ return 0;
+
+ qid = skb_get_queue_mapping(skb);
+ q = phy->q_tx[qid];
+@@ -45,7 +45,7 @@ void mt76_testmode_tx_pending(struct mt76_phy *phy)
+
+ spin_lock_bh(&q->lock);
+
+- while (td->tx_pending > 0 &&
++ while (count < limit &&
+ td->tx_queued - td->tx_done < tx_queued_limit &&
+ q->queued < q->ndesc / 2) {
+ int ret;
+@@ -55,13 +55,56 @@ void mt76_testmode_tx_pending(struct mt76_phy *phy)
+ if (ret < 0)
+ break;
+
+- td->tx_pending--;
+ td->tx_queued++;
++ count++;
+ }
+
+ dev->queue_ops->kick(dev, q);
+
+ spin_unlock_bh(&q->lock);
++
++ return count;
++}
++
++void mt76_testmode_tx_pending(struct mt76_phy *phy)
++{
++ struct mt76_testmode_data *td = &phy->test;
++ u16 count;
++
++ if (!td->tx_pending)
++ return;
++
++ if (!mt76_testmode_has_sta(phy)) {
++ count = mt76_testmode_queue_tx(phy, &phy->dev->global_wcid,
++ td->tx_skb, td->tx_pending);
++ td->tx_pending -= count;
++
++ return;
++ }
++
++ while (true) {
++ struct mt76_testmode_sta *tm_sta;
++ struct mt76_wcid *wcid;
++ u32 limit, per_sta_cnt = 1;
++
++ if (td->tx_rate_mode != MT76_TM_TX_MODE_HE_MU)
++ per_sta_cnt = td->tx_count / hweight16(phy->test.tm_sta_mask);
++
++ limit = td->tx_pending % per_sta_cnt;
++ if (limit == 0)
++ limit = per_sta_cnt;
++
++ tm_sta = mt76_testmode_aid_get_sta(phy, td->cur_aid);
++ wcid = td->tm_wcid[td->cur_aid];
++ count = mt76_testmode_queue_tx(phy, wcid, tm_sta->tx_skb, limit);
++
++ td->tx_pending -= count;
++
++ if (td->tx_pending && (td->tx_pending % per_sta_cnt == 0))
++ td->cur_aid = ffs(td->tm_sta_mask >> td->cur_aid) + td->cur_aid;
++ else
++ break;
++ }
+ }
+
+ static u32
+@@ -87,15 +130,34 @@ mt76_testmode_max_mpdu_len(struct mt76_phy *phy, u8 tx_rate_mode)
+ }
+
+ static void
+-mt76_testmode_free_skb(struct mt76_phy *phy)
++mt76_testmode_free_skb(struct sk_buff **tx_skb)
++{
++ dev_kfree_skb(*tx_skb);
++ *tx_skb = NULL;
++}
++
++static void
++mt76_testmode_free_skb_all(struct mt76_phy *phy)
+ {
+ struct mt76_testmode_data *td = &phy->test;
+
+- dev_kfree_skb(td->tx_skb);
+- td->tx_skb = NULL;
++ if (mt76_testmode_has_sta(phy)) {
++ struct mt76_testmode_sta *tm_sta;
++ int i;
++
++ mt76_testmode_for_each_sta(phy, i, tm_sta) {
++ mt76_testmode_free_skb(&tm_sta->tx_skb);
++ }
++
++ return;
++ }
++
++ mt76_testmode_free_skb(&td->tx_skb);
+ }
+
+-int mt76_testmode_alloc_skb(struct mt76_phy *phy, u32 len)
++static int
++mt76_testmode_alloc_skb(struct mt76_phy *phy, u32 len,
++ struct sk_buff **tx_skb, u8 *da)
+ {
+ #define MT_TXP_MAX_LEN 4095
+ u16 fc = IEEE80211_FTYPE_DATA | IEEE80211_STYPE_DATA |
+@@ -128,7 +190,9 @@ int mt76_testmode_alloc_skb(struct mt76_phy *phy, u32 len)
+ hdr->frame_control = cpu_to_le16(fc);
+ memcpy(hdr->addr1, td->addr[0], ETH_ALEN);
+ memcpy(hdr->addr2, td->addr[1], ETH_ALEN);
+- memcpy(hdr->addr3, td->addr[2], ETH_ALEN);
++ /* memcpy(hdr->addr3, td->addr[2], ETH_ALEN); */
++ memcpy(hdr->addr3, da, ETH_ALEN);
++
+ skb_set_queue_mapping(head, IEEE80211_AC_BE);
+
+ info = IEEE80211_SKB_CB(head);
+@@ -152,7 +216,7 @@ int mt76_testmode_alloc_skb(struct mt76_phy *phy, u32 len)
+
+ frag = alloc_skb(frag_len, GFP_KERNEL);
+ if (!frag) {
+- mt76_testmode_free_skb(phy);
++ mt76_testmode_free_skb(tx_skb);
+ dev_kfree_skb(head);
+ return -ENOMEM;
+ }
+@@ -165,23 +229,25 @@ int mt76_testmode_alloc_skb(struct mt76_phy *phy, u32 len)
+ frag_tail = &(*frag_tail)->next;
+ }
+
+- mt76_testmode_free_skb(phy);
+- td->tx_skb = head;
++ mt76_testmode_free_skb(tx_skb);
++ *tx_skb = head;
+
+ return 0;
+ }
+-EXPORT_SYMBOL(mt76_testmode_alloc_skb);
+
+-static int
+-mt76_testmode_tx_init(struct mt76_phy *phy)
++int mt76_testmode_init_skb(struct mt76_phy *phy, u32 len, u8 aid,
++ struct sk_buff **tx_skb)
+ {
+ struct mt76_testmode_data *td = &phy->test;
+ struct ieee80211_tx_info *info;
+ struct ieee80211_tx_rate *rate;
+ u8 max_nss = hweight8(phy->antenna_mask);
++ u8 da[ETH_ALEN];
+ int ret;
+
+- ret = mt76_testmode_alloc_skb(phy, td->tx_mpdu_len);
++ ether_addr_copy(da, phy->macaddr);
++ da[0] += aid * 4;
++ ret = mt76_testmode_alloc_skb(phy, len, tx_skb, da);
+ if (ret)
+ return ret;
+
+@@ -191,7 +257,7 @@ mt76_testmode_tx_init(struct mt76_phy *phy)
+ if (td->tx_antenna_mask)
+ max_nss = min_t(u8, max_nss, hweight8(td->tx_antenna_mask));
+
+- info = IEEE80211_SKB_CB(td->tx_skb);
++ info = IEEE80211_SKB_CB(*tx_skb);
+ rate = &info->control.rates[0];
+ rate->count = 1;
+ rate->idx = td->tx_rate_idx;
+@@ -263,6 +329,28 @@ mt76_testmode_tx_init(struct mt76_phy *phy)
+ out:
+ return 0;
+ }
++EXPORT_SYMBOL(mt76_testmode_init_skb);
++
++static int
++mt76_testmode_tx_init(struct mt76_phy *phy)
++{
++ struct mt76_testmode_data *td = &phy->test;
++ struct mt76_testmode_sta *tm_sta;
++ int ret, i;
++
++ if (!mt76_testmode_has_sta(phy))
++ return mt76_testmode_init_skb(phy, td->tx_mpdu_len,
++ 0, &td->tx_skb);
++
++ mt76_testmode_for_each_sta(phy, i, tm_sta) {
++ ret = mt76_testmode_init_skb(phy, tm_sta->sd.tx_mpdu_len,
++ tm_sta->sd.aid, &tm_sta->tx_skb);
++ if (ret)
++ return ret;
++ }
++
++ return 0;
++}
+
+ static void
+ mt76_testmode_tx_start(struct mt76_phy *phy)
+@@ -273,6 +361,17 @@ mt76_testmode_tx_start(struct mt76_phy *phy)
+ td->tx_queued = 0;
+ td->tx_done = 0;
+ td->tx_pending = td->tx_count;
++
++ if (mt76_testmode_has_sta(phy)) {
++ td->cur_aid = ffs(td->tm_sta_mask);
++
++ /* The actual tx count of MU packets will be pass to FW
++ * by a mcu command in testmode.
++ */
++ if (td->tx_rate_mode == MT76_TM_TX_MODE_HE_MU)
++ td->tx_pending = hweight16(phy->test.tm_sta_mask);
++ }
++
+ mt76_worker_schedule(&dev->tx_worker);
+ }
+
+@@ -291,7 +390,7 @@ mt76_testmode_tx_stop(struct mt76_phy *phy)
+ wait_event_timeout(dev->tx_wait, td->tx_done == td->tx_queued,
+ MT76_TM_TIMEOUT * HZ);
+
+- mt76_testmode_free_skb(phy);
++ mt76_testmode_free_skb_all(phy);
+ }
+
+ static inline void
+@@ -331,8 +430,11 @@ __mt76_testmode_set_state(struct mt76_phy *phy, enum mt76_testmode_state state)
+ struct mt76_dev *dev = phy->dev;
+ int err;
+
+- if (prev_state == MT76_TM_STATE_TX_FRAMES)
++ if (prev_state == MT76_TM_STATE_TX_FRAMES) {
++ if (phy->test.tx_rate_mode == MT76_TM_TX_MODE_HE_MU)
++ dev->test_ops->set_state(phy, MT76_TM_STATE_IDLE);
+ mt76_testmode_tx_stop(phy);
++ }
+
+ if (state == MT76_TM_STATE_TX_FRAMES) {
+ err = mt76_testmode_tx_init(phy);
+@@ -382,7 +484,6 @@ int mt76_testmode_set_state(struct mt76_phy *phy, enum mt76_testmode_state state
+ }
+
+ return __mt76_testmode_set_state(phy, state);
+-
+ }
+ EXPORT_SYMBOL(mt76_testmode_set_state);
+
+@@ -402,6 +503,44 @@ mt76_tm_get_u8(struct nlattr *attr, u8 *dest, u8 min, u8 max)
+ return 0;
+ }
+
++static int
++mt76_testmode_set_eeprom(struct mt76_phy *phy, struct nlattr **tb)
++{
++ struct mt76_dev *dev = phy->dev;
++ u8 action, val[MT76_TM_EEPROM_BLOCK_SIZE];
++ u32 offset = 0;
++ int err = -EINVAL;
++
++ if (!dev->test_ops->set_eeprom)
++ return -EOPNOTSUPP;
++
++ if (mt76_tm_get_u8(tb[MT76_TM_ATTR_EEPROM_ACTION], &action,
++ 0, MT76_TM_EEPROM_ACTION_MAX))
++ goto out;
++
++ if (tb[MT76_TM_ATTR_EEPROM_OFFSET]) {
++ struct nlattr *cur;
++ int rem, idx = 0;
++
++ offset = nla_get_u32(tb[MT76_TM_ATTR_EEPROM_OFFSET]);
++ if (!!(offset % MT76_TM_EEPROM_BLOCK_SIZE) ||
++ !tb[MT76_TM_ATTR_EEPROM_VAL])
++ goto out;
++
++ nla_for_each_nested(cur, tb[MT76_TM_ATTR_EEPROM_VAL], rem) {
++ if (nla_len(cur) != 1 || idx >= ARRAY_SIZE(val))
++ goto out;
++
++ val[idx++] = nla_get_u8(cur);
++ }
++ }
++
++ err = dev->test_ops->set_eeprom(phy, offset, val, action);
++
++out:
++ return err;
++}
++
+ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ void *data, int len)
+ {
+@@ -425,6 +564,11 @@ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+
+ mutex_lock(&dev->mutex);
+
++ if (tb[MT76_TM_ATTR_EEPROM_ACTION]) {
++ err = mt76_testmode_set_eeprom(phy, tb);
++ goto out;
++ }
++
+ if (tb[MT76_TM_ATTR_RESET]) {
+ mt76_testmode_set_state(phy, MT76_TM_STATE_OFF);
+ memset(td, 0, sizeof(*td));
+@@ -446,13 +590,16 @@ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_RATE_LDPC], &td->tx_rate_ldpc, 0, 1) ||
+ mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_RATE_STBC], &td->tx_rate_stbc, 0, 1) ||
+ mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_LTF], &td->tx_ltf, 0, 2) ||
+- mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_ANTENNA],
+- &td->tx_antenna_mask, 0, 0xff) ||
++ mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_ANTENNA], &td->tx_antenna_mask,
++ 1, phy->antenna_mask) ||
+ mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_SPE_IDX], &td->tx_spe_idx, 0, 27) ||
+ mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_DUTY_CYCLE],
+ &td->tx_duty_cycle, 0, 99) ||
+ mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_POWER_CONTROL],
+- &td->tx_power_control, 0, 1))
++ &td->tx_power_control, 0, 1) ||
++ mt76_tm_get_u8(tb[MT76_TM_ATTR_AID], &td->aid, 0, 16) ||
++ mt76_tm_get_u8(tb[MT76_TM_ATTR_RU_ALLOC], &td->ru_alloc, 0, 0xff) ||
++ mt76_tm_get_u8(tb[MT76_TM_ATTR_RU_IDX], &td->ru_idx, 0, 68))
+ goto out;
+
+ if (tb[MT76_TM_ATTR_TX_LENGTH]) {
+@@ -484,8 +631,7 @@ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+
+ if (tb[MT76_TM_ATTR_TX_POWER]) {
+ struct nlattr *cur;
+- int idx = 0;
+- int rem;
++ int rem, idx = 0;
+
+ nla_for_each_nested(cur, tb[MT76_TM_ATTR_TX_POWER], rem) {
+ if (nla_len(cur) != 1 ||
+@@ -505,11 +651,47 @@ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ if (nla_len(cur) != ETH_ALEN || idx >= 3)
+ goto out;
+
+- memcpy(td->addr[idx], nla_data(cur), ETH_ALEN);
++ memcpy(td->addr[idx++], nla_data(cur), ETH_ALEN);
++ }
++ }
++
++ if (tb[MT76_TM_ATTR_CFG]) {
++ struct nlattr *cur;
++ int rem, idx = 0;
++
++ nla_for_each_nested(cur, tb[MT76_TM_ATTR_CFG], rem) {
++ if (nla_len(cur) != 1 || idx >= 2)
++ goto out;
++
++ if (idx == 0)
++ td->cfg.type = nla_get_u8(cur);
++ else
++ td->cfg.enable = nla_get_u8(cur);
+ idx++;
+ }
+ }
+
++ if (tb[MT76_TM_ATTR_OFF_CH_SCAN_CH]) {
++ u8 ch = nla_get_u8(tb[MT76_TM_ATTR_OFF_CH_SCAN_CH]);
++ struct ieee80211_supported_band *sband;
++
++ sband = ch > 14 ? &phy->sband_5g.sband :
++ &phy->sband_2g.sband;
++ if (ch && (ch < sband->channels[0].hw_value ||
++ ch > sband->channels[sband->n_channels - 1].hw_value))
++ goto out;
++
++ td->off_ch_scan_ch = ch;
++
++ if (mt76_tm_get_u8(tb[MT76_TM_ATTR_OFF_CH_SCAN_CENTER_CH],
++ &td->off_ch_scan_center_ch, ch - 6, ch + 6) ||
++ mt76_tm_get_u8(tb[MT76_TM_ATTR_OFF_CH_SCAN_BW],
++ &td->off_ch_scan_bw, 0, 6) ||
++ mt76_tm_get_u8(tb[MT76_TM_ATTR_OFF_CH_SCAN_PATH],
++ &td->off_ch_scan_path, 1, 0xff))
++ goto out;
++ }
++
+ if (dev->test_ops->set_params) {
+ err = dev->test_ops->set_params(phy, tb, state);
+ if (err)
+@@ -559,6 +741,9 @@ mt76_testmode_dump_stats(struct mt76_phy *phy, struct sk_buff *msg)
+ nla_put_u64_64bit(msg, MT76_TM_STATS_ATTR_RX_PACKETS, rx_packets,
+ MT76_TM_STATS_ATTR_PAD) ||
+ nla_put_u64_64bit(msg, MT76_TM_STATS_ATTR_RX_FCS_ERROR, rx_fcs_error,
++ MT76_TM_STATS_ATTR_PAD) ||
++ nla_put_u64_64bit(msg, MT76_TM_STATS_ATTR_RX_LEN_MISMATCH,
++ td->rx_stats.len_mismatch,
+ MT76_TM_STATS_ATTR_PAD))
+ return -EMSGSIZE;
+
+@@ -571,6 +756,7 @@ int mt76_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *msg,
+ struct mt76_phy *phy = hw->priv;
+ struct mt76_dev *dev = phy->dev;
+ struct mt76_testmode_data *td = &phy->test;
++ struct mt76_testmode_sta_data *sd = &td->sd;
+ struct nlattr *tb[NUM_MT76_TM_ATTRS] = {};
+ int err = 0;
+ void *a;
+@@ -603,6 +789,23 @@ int mt76_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *msg,
+ goto out;
+ }
+
++ if (tb[MT76_TM_ATTR_AID]) {
++ struct mt76_testmode_sta *tm_sta;
++ u8 aid;
++
++ err = mt76_tm_get_u8(tb[MT76_TM_ATTR_AID], &aid, 1, 16);
++ if (err)
++ goto out;
++
++ tm_sta = mt76_testmode_aid_get_sta(phy, aid);
++ if (!tm_sta) {
++ err = -EINVAL;
++ goto out;
++ }
++
++ sd = &tm_sta->sd;
++ }
++
+ mt76_testmode_init_defaults(phy);
+
+ err = -EMSGSIZE;
+@@ -615,12 +818,8 @@ int mt76_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *msg,
+ goto out;
+
+ if (nla_put_u32(msg, MT76_TM_ATTR_TX_COUNT, td->tx_count) ||
+- nla_put_u32(msg, MT76_TM_ATTR_TX_LENGTH, td->tx_mpdu_len) ||
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_MODE, td->tx_rate_mode) ||
+- nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_NSS, td->tx_rate_nss) ||
+- nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_IDX, td->tx_rate_idx) ||
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_SGI, td->tx_rate_sgi) ||
+- nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_LDPC, td->tx_rate_ldpc) ||
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_STBC, td->tx_rate_stbc) ||
+ (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_LTF) &&
+ nla_put_u8(msg, MT76_TM_ATTR_TX_LTF, td->tx_ltf)) ||
+@@ -640,6 +839,15 @@ int mt76_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *msg,
+ nla_put_u8(msg, MT76_TM_ATTR_FREQ_OFFSET, td->freq_offset)))
+ goto out;
+
++ if (nla_put_u8(msg, MT76_TM_ATTR_AID, sd->aid) ||
++ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_NSS, sd->tx_rate_nss) ||
++ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_IDX, sd->tx_rate_idx) ||
++ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_LDPC, sd->tx_rate_ldpc) ||
++ nla_put_u8(msg, MT76_TM_ATTR_RU_ALLOC, sd->ru_alloc) ||
++ nla_put_u8(msg, MT76_TM_ATTR_RU_IDX, sd->ru_idx) ||
++ nla_put_u32(msg, MT76_TM_ATTR_TX_LENGTH, sd->tx_mpdu_len))
++ goto out;
++
+ if (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_POWER)) {
+ a = nla_nest_start(msg, MT76_TM_ATTR_TX_POWER);
+ if (!a)
+diff --git a/testmode.h b/testmode.h
+index 5e2792d..b360d7a 100644
+--- a/testmode.h
++++ b/testmode.h
+@@ -6,6 +6,8 @@
+ #define __MT76_TESTMODE_H
+
+ #define MT76_TM_TIMEOUT 10
++#define MT76_TM_EEPROM_BLOCK_SIZE 16
++#define MT76_TM_MAX_STA_NUM 16
+
+ /**
+ * enum mt76_testmode_attr - testmode attributes inside NL80211_ATTR_TESTDATA
+@@ -47,6 +49,20 @@
+ * @MT76_TM_ATTR_DRV_DATA: driver specific netlink attrs (nested)
+ *
+ * @MT76_TM_ATTR_MAC_ADDRS: array of nested MAC addresses (nested)
++ *
++ * @MT76_TM_ATTR_EEPROM_ACTION: eeprom setting actions
++ * (u8, see &enum mt76_testmode_eeprom_action)
++ * @MT76_TM_ATTR_EEPROM_OFFSET: offset of eeprom data block for writing (u32)
++ * @MT76_TM_ATTR_EEPROM_VAL: values for writing into a 16-byte data block
++ * (nested, u8 attrs)
++ *
++ * @MT76_TM_ATTR_CFG: config testmode rf feature (nested, see &mt76_testmode_cfg)
++ *
++ * @MT76_TM_ATTR_OFF_CH_SCAN_CH: monitored channel for off channel scan (u8)
++ * @MT76_TM_ATTR_OFF_CH_SCAN_CENTER_CH: monitored channel for off channel scan (u8)
++ * @MT76_TM_ATTR_OFF_CH_SCAN_BW: monitored bw for off channel scan (u8)
++ * @MT76_TM_ATTR_OFF_CH_SCAN_PATH: monitored rx path for off channel scan (u8)
++ *
+ */
+ enum mt76_testmode_attr {
+ MT76_TM_ATTR_UNSPEC,
+@@ -85,6 +101,21 @@ enum mt76_testmode_attr {
+
+ MT76_TM_ATTR_MAC_ADDRS,
+
++ MT76_TM_ATTR_EEPROM_ACTION,
++ MT76_TM_ATTR_EEPROM_OFFSET,
++ MT76_TM_ATTR_EEPROM_VAL,
++
++ MT76_TM_ATTR_CFG,
++
++ MT76_TM_ATTR_OFF_CH_SCAN_CH,
++ MT76_TM_ATTR_OFF_CH_SCAN_CENTER_CH,
++ MT76_TM_ATTR_OFF_CH_SCAN_BW,
++ MT76_TM_ATTR_OFF_CH_SCAN_PATH,
++
++ MT76_TM_ATTR_AID,
++ MT76_TM_ATTR_RU_ALLOC,
++ MT76_TM_ATTR_RU_IDX,
++
+ /* keep last */
+ NUM_MT76_TM_ATTRS,
+ MT76_TM_ATTR_MAX = NUM_MT76_TM_ATTRS - 1,
+@@ -101,6 +132,8 @@ enum mt76_testmode_attr {
+ * @MT76_TM_STATS_ATTR_RX_FCS_ERROR: number of rx packets with FCS error (u64)
+ * @MT76_TM_STATS_ATTR_LAST_RX: information about the last received packet
+ * see &enum mt76_testmode_rx_attr
++ * @MT76_TM_STATS_ATTR_RX_LEN_MISMATCH: number of rx packets with length
++ * mismatch error (u64)
+ */
+ enum mt76_testmode_stats_attr {
+ MT76_TM_STATS_ATTR_UNSPEC,
+@@ -113,6 +146,7 @@ enum mt76_testmode_stats_attr {
+ MT76_TM_STATS_ATTR_RX_PACKETS,
+ MT76_TM_STATS_ATTR_RX_FCS_ERROR,
+ MT76_TM_STATS_ATTR_LAST_RX,
++ MT76_TM_STATS_ATTR_RX_LEN_MISMATCH,
+
+ /* keep last */
+ NUM_MT76_TM_STATS_ATTRS,
+@@ -195,4 +229,41 @@ enum mt76_testmode_tx_mode {
+
+ extern const struct nla_policy mt76_tm_policy[NUM_MT76_TM_ATTRS];
+
++/**
++ * enum mt76_testmode_eeprom_action - eeprom setting actions
++ *
++ * @MT76_TM_EEPROM_ACTION_UPDATE_DATA: update rf values to specific
++ * eeprom data block
++ * @MT76_TM_EEPROM_ACTION_UPDATE_BUFFER_MODE: send updated eeprom data to fw
++ * @MT76_TM_EEPROM_ACTION_WRITE_TO_EFUSE: write eeprom data back to efuse
++ */
++enum mt76_testmode_eeprom_action {
++ MT76_TM_EEPROM_ACTION_UPDATE_DATA,
++ MT76_TM_EEPROM_ACTION_UPDATE_BUFFER_MODE,
++ MT76_TM_EEPROM_ACTION_WRITE_TO_EFUSE,
++
++ /* keep last */
++ NUM_MT76_TM_EEPROM_ACTION,
++ MT76_TM_EEPROM_ACTION_MAX = NUM_MT76_TM_EEPROM_ACTION - 1,
++};
++
++/**
++ * enum mt76_testmode_cfg - packet tx phy mode
++ *
++ * @MT76_TM_EEPROM_ACTION_UPDATE_DATA: update rf values to specific
++ * eeprom data block
++ * @MT76_TM_EEPROM_ACTION_UPDATE_BUFFER_MODE: send updated eeprom data to fw
++ * @MT76_TM_EEPROM_ACTION_WRITE_TO_EFUSE: write eeprom data back to efuse
++ */
++enum mt76_testmode_cfg {
++ MT76_TM_CFG_TSSI,
++ MT76_TM_CFG_DPD,
++ MT76_TM_CFG_RATE_POWER_OFFSET,
++ MT76_TM_CFG_THERMAL_COMP,
++
++ /* keep last */
++ NUM_MT76_TM_CFG,
++ MT76_TM_CFG_MAX = NUM_MT76_TM_CFG - 1,
++};
++
+ #endif
+diff --git a/tools/fields.c b/tools/fields.c
+index e3f6908..036406c 100644
+--- a/tools/fields.c
++++ b/tools/fields.c
+@@ -10,6 +10,7 @@ static const char * const testmode_state[] = {
+ [MT76_TM_STATE_IDLE] = "idle",
+ [MT76_TM_STATE_TX_FRAMES] = "tx_frames",
+ [MT76_TM_STATE_RX_FRAMES] = "rx_frames",
++ [MT76_TM_STATE_TX_CONT] = "tx_cont",
+ };
+
+ static const char * const testmode_tx_mode[] = {
+@@ -201,6 +202,63 @@ static void print_extra_stats(const struct tm_field *field, struct nlattr **tb)
+ printf("%srx_per=%.02f%%\n", prefix, 100 * failed / total);
+ }
+
++static bool parse_mac(const struct tm_field *field, int idx,
++ struct nl_msg *msg, const char *val)
++{
++#define ETH_ALEN 6
++ bool ret = true;
++ char *str, *cur, *ap;
++ void *a;
++
++ ap = str = strdup(val);
++
++ a = nla_nest_start(msg, idx);
++
++ idx = 0;
++ while ((cur = strsep(&ap, ",")) != NULL) {
++ unsigned char addr[ETH_ALEN];
++ char *val, *tmp = cur;
++ int i = 0;
++
++ while ((val = strsep(&tmp, ":")) != NULL) {
++ if (i >= ETH_ALEN)
++ break;
++
++ addr[i++] = strtoul(val, NULL, 16);
++ }
++
++ nla_put(msg, idx, ETH_ALEN, addr);
++
++ idx++;
++ }
++
++ nla_nest_end(msg, a);
++
++ free(str);
++
++ return ret;
++}
++
++static void print_mac(const struct tm_field *field, struct nlattr *attr)
++{
++#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]
++#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x"
++ unsigned char addr[3][6];
++ struct nlattr *cur;
++ int idx = 0;
++ int rem;
++
++ nla_for_each_nested(cur, attr, rem) {
++ if (nla_len(cur) != 6)
++ continue;
++ memcpy(addr[idx++], nla_data(cur), 6);
++ }
++
++ printf("" MACSTR "," MACSTR "," MACSTR "",
++ MAC2STR(addr[0]), MAC2STR(addr[1]), MAC2STR(addr[2]));
++
++ return;
++}
+
+ #define FIELD_GENERIC(_field, _name, ...) \
+ [FIELD_NAME(_field)] = { \
+@@ -250,6 +308,13 @@ static void print_extra_stats(const struct tm_field *field, struct nlattr **tb)
+ ##__VA_ARGS__ \
+ )
+
++#define FIELD_MAC(_field, _name) \
++ [FIELD_NAME(_field)] = { \
++ .name = _name, \
++ .parse = parse_mac, \
++ .print = print_mac \
++ }
++
+ #define FIELD_NAME(_field) MT76_TM_RX_ATTR_##_field
+ static const struct tm_field rx_fields[NUM_MT76_TM_RX_ATTRS] = {
+ FIELD_RO(s32, FREQ_OFFSET, "freq_offset"),
+@@ -300,10 +365,16 @@ static const struct tm_field testdata_fields[NUM_MT76_TM_ATTRS] = {
+ FIELD(u8, TX_RATE_LDPC, "tx_rate_ldpc"),
+ FIELD(u8, TX_RATE_STBC, "tx_rate_stbc"),
+ FIELD(u8, TX_LTF, "tx_ltf"),
++ FIELD(u8, TX_DUTY_CYCLE, "tx_duty_cycle"),
++ FIELD(u32, TX_IPG, "tx_ipg"),
++ FIELD(u32, TX_TIME, "tx_time"),
+ FIELD(u8, TX_POWER_CONTROL, "tx_power_control"),
+ FIELD_ARRAY(u8, TX_POWER, "tx_power"),
+ FIELD(u8, TX_ANTENNA, "tx_antenna"),
++ FIELD(u8, TX_SPE_IDX, "tx_spe_idx"),
+ FIELD(u32, FREQ_OFFSET, "freq_offset"),
++ FIELD(u8, AID, "aid"),
++ FIELD_MAC(MAC_ADDRS, "mac_addrs"),
+ FIELD_NESTED_RO(STATS, stats, "",
+ .print_extra = print_extra_stats),
+ };
+@@ -322,9 +393,14 @@ static struct nla_policy testdata_policy[NUM_MT76_TM_ATTRS] = {
+ [MT76_TM_ATTR_TX_RATE_LDPC] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_RATE_STBC] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_LTF] = { .type = NLA_U8 },
++ [MT76_TM_ATTR_TX_DUTY_CYCLE] = { .type = NLA_U8 },
++ [MT76_TM_ATTR_TX_IPG] = { .type = NLA_U32 },
++ [MT76_TM_ATTR_TX_TIME] = { .type = NLA_U32 },
+ [MT76_TM_ATTR_TX_POWER_CONTROL] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_ANTENNA] = { .type = NLA_U8 },
++ [MT76_TM_ATTR_TX_SPE_IDX] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_FREQ_OFFSET] = { .type = NLA_U32 },
++ [MT76_TM_ATTR_AID] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_STATS] = { .type = NLA_NESTED },
+ };
+
+diff --git a/tx.c b/tx.c
+index 6b8c9dc..ca5e6d9 100644
+--- a/tx.c
++++ b/tx.c
+@@ -245,8 +245,7 @@ void __mt76_tx_complete_skb(struct mt76_dev *dev, u16 wcid_idx, struct sk_buff *
+ if (mt76_is_testmode_skb(dev, skb, &hw)) {
+ struct mt76_phy *phy = hw->priv;
+
+- if (skb == phy->test.tx_skb)
+- phy->test.tx_done++;
++ phy->test.tx_done++;
+ if (phy->test.tx_queued == phy->test.tx_done)
+ wake_up(&dev->tx_wait);
+
+--
+2.25.1
+