[][MAC80211][WiFi7][mt76][add mt76 patches for mt76 MLO build]

[Description]
Add mt76 patches for mt76 MLO build.

[Release-log]
N/A

Change-Id: I11ad22165d03c26b688b2314155d8cac4ca6c974
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/8832329
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0001-mtk-Revert-wifi-mt76-mt7996-fill-txd-by-host-driver.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0001-mtk-Revert-wifi-mt76-mt7996-fill-txd-by-host-driver.patch
new file mode 100644
index 0000000..e6a160c
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0001-mtk-Revert-wifi-mt76-mt7996-fill-txd-by-host-driver.patch
@@ -0,0 +1,45 @@
+From ebb26f451f8d62598e74063217da8b4b59074d3e Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Tue, 19 Sep 2023 11:21:23 +0800
+Subject: [PATCH 001/120] mtk: Revert "wifi: mt76: mt7996: fill txd by host
+ driver"
+
+This reverts commit 325a0c4931990d553487024c4f76c776492bdcc2.
+---
+ mt7996/mac.c | 13 +++++++++----
+ 1 file changed, 9 insertions(+), 4 deletions(-)
+
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index bc7111a71..3afdd7eb9 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -938,8 +938,11 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
+ 		return id;
+ 
+ 	pid = mt76_tx_status_skb_add(mdev, wcid, tx_info->skb);
+-	mt7996_mac_write_txwi(dev, txwi_ptr, tx_info->skb, wcid, key,
+-			      pid, qid, 0);
++	memset(txwi_ptr, 0, MT_TXD_SIZE);
++	/* Transmit non qos data by 802.11 header and need to fill txd by host*/
++	if (!is_8023 || pid >= MT_PACKET_ID_FIRST)
++		mt7996_mac_write_txwi(dev, txwi_ptr, tx_info->skb, wcid, key,
++				      pid, qid, 0);
+ 
+ 	txp = (struct mt76_connac_txp_common *)(txwi + MT_TXD_SIZE);
+ 	for (i = 0; i < nbuf; i++) {
+@@ -956,8 +959,10 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
+ 	}
+ 	txp->fw.nbuf = nbuf;
+ 
+-	txp->fw.flags =
+-		cpu_to_le16(MT_CT_INFO_FROM_HOST | MT_CT_INFO_APPLY_TXD);
++	txp->fw.flags = cpu_to_le16(MT_CT_INFO_FROM_HOST);
++
++	if (!is_8023 || pid >= MT_PACKET_ID_FIRST)
++		txp->fw.flags |= cpu_to_le16(MT_CT_INFO_APPLY_TXD);
+ 
+ 	if (!key)
+ 		txp->fw.flags |= cpu_to_le16(MT_CT_INFO_NONE_CIPHER_FRAME);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0002-bp-sync-upstream-changes.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0002-bp-sync-upstream-changes.patch
new file mode 100644
index 0000000..5339666
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0002-bp-sync-upstream-changes.patch
@@ -0,0 +1,79 @@
+From 5c80225b98ec17a975f7a6f5916f38795e059167 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Thu, 21 Mar 2024 12:14:20 +0800
+Subject: [PATCH 002/120] bp: sync upstream changes
+
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mac80211.c   | 6 +++---
+ mt7615/mcu.c | 2 +-
+ mt7915/mcu.c | 2 +-
+ mt7996/mcu.c | 2 +-
+ 4 files changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/mac80211.c b/mac80211.c
+index b603d40c2..6f8b85d85 100644
+--- a/mac80211.c
++++ b/mac80211.c
+@@ -1614,8 +1614,8 @@ EXPORT_SYMBOL_GPL(mt76_get_sar_power);
+ static void
+ __mt76_csa_finish(void *priv, u8 *mac, struct ieee80211_vif *vif)
+ {
+-	if (vif->bss_conf.csa_active && ieee80211_beacon_cntdwn_is_complete(vif))
+-		ieee80211_csa_finish(vif);
++	if (vif->bss_conf.csa_active && ieee80211_beacon_cntdwn_is_complete(vif, 0))
++		ieee80211_csa_finish(vif, 0);
+ }
+ 
+ void mt76_csa_finish(struct mt76_dev *dev)
+@@ -1639,7 +1639,7 @@ __mt76_csa_check(void *priv, u8 *mac, struct ieee80211_vif *vif)
+ 	if (!vif->bss_conf.csa_active)
+ 		return;
+ 
+-	dev->csa_complete |= ieee80211_beacon_cntdwn_is_complete(vif);
++	dev->csa_complete |= ieee80211_beacon_cntdwn_is_complete(vif, 0);
+ }
+ 
+ void mt76_csa_check(struct mt76_dev *dev)
+diff --git a/mt7615/mcu.c b/mt7615/mcu.c
+index ae34d019e..c807bd8d9 100644
+--- a/mt7615/mcu.c
++++ b/mt7615/mcu.c
+@@ -353,7 +353,7 @@ static void
+ mt7615_mcu_csa_finish(void *priv, u8 *mac, struct ieee80211_vif *vif)
+ {
+ 	if (vif->bss_conf.csa_active)
+-		ieee80211_csa_finish(vif);
++		ieee80211_csa_finish(vif, 0);
+ }
+ 
+ static void
+diff --git a/mt7915/mcu.c b/mt7915/mcu.c
+index fe54a2f40..24daa0835 100644
+--- a/mt7915/mcu.c
++++ b/mt7915/mcu.c
+@@ -228,7 +228,7 @@ mt7915_mcu_csa_finish(void *priv, u8 *mac, struct ieee80211_vif *vif)
+ 	if (!vif->bss_conf.csa_active || vif->type == NL80211_IFTYPE_STATION)
+ 		return;
+ 
+-	ieee80211_csa_finish(vif);
++	ieee80211_csa_finish(vif, 0);
+ }
+ 
+ static void
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 1356ac14b..b44abe2ac 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -341,7 +341,7 @@ mt7996_mcu_csa_finish(void *priv, u8 *mac, struct ieee80211_vif *vif)
+ 	if (!vif->bss_conf.csa_active || vif->type == NL80211_IFTYPE_STATION)
+ 		return;
+ 
+-	ieee80211_csa_finish(vif);
++	ieee80211_csa_finish(vif, 0);
+ }
+ 
+ static void
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0003-mtk-wifi-mt76-connac-use-peer-address-for-station-BM.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0003-mtk-wifi-mt76-connac-use-peer-address-for-station-BM.patch
new file mode 100644
index 0000000..29c53e2
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0003-mtk-wifi-mt76-connac-use-peer-address-for-station-BM.patch
@@ -0,0 +1,135 @@
+From c6f2e5ba5b8f4fd246fc9047465a4d02fc8a823a Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Thu, 24 Aug 2023 18:38:11 +0800
+Subject: [PATCH 003/120] mtk: wifi: mt76: connac: use peer address for station
+ BMC entry
+
+Set peer address and aid for the BMC wtbl of station interface. For some
+functions such as parsing MU_EDCA parameters from beacon, firmware will
+need the peer address to do correct parsing.
+Without this patch, MU uplink traffic would get suffered.
+
+Change-Id: I0e812312fe730f69f8e431215b8e591c5faec06a
+Reported-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt76_connac_mcu.c |  9 ++++++++-
+ mt7996/main.c     | 15 ++++++++++-----
+ mt7996/mcu.c      |  6 +++---
+ mt7996/mt7996.h   |  2 +-
+ 4 files changed, 22 insertions(+), 10 deletions(-)
+
+diff --git a/mt76_connac_mcu.c b/mt76_connac_mcu.c
+index 368c5f46a..5b286f4ba 100644
+--- a/mt76_connac_mcu.c
++++ b/mt76_connac_mcu.c
+@@ -392,7 +392,14 @@ void mt76_connac_mcu_sta_basic_tlv(struct mt76_dev *dev, struct sk_buff *skb,
+ 
+ 	if (!sta) {
+ 		basic->conn_type = cpu_to_le32(CONNECTION_INFRA_BC);
+-		eth_broadcast_addr(basic->peer_addr);
++
++		if (is_mt799x(dev) && vif->type == NL80211_IFTYPE_STATION &&
++		    !is_zero_ether_addr(vif->bss_conf.bssid)) {
++			memcpy(basic->peer_addr, vif->bss_conf.bssid, ETH_ALEN);
++			basic->aid = cpu_to_le16(vif->cfg.aid);
++		} else {
++			eth_broadcast_addr(basic->peer_addr);
++		}
+ 		return;
+ 	}
+ 
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 2bf8e8a88..a41d5d06b 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -238,7 +238,11 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 	mt7996_init_bitrate_mask(vif);
+ 
+ 	mt7996_mcu_add_bss_info(phy, vif, true);
+-	mt7996_mcu_add_sta(dev, vif, NULL, true);
++	/* defer the first STA_REC of BMC entry to BSS_CHANGED_BSSID for STA
++	 * interface, since firmware only records BSSID when the entry is new
++	 */
++	if (vif->type != NL80211_IFTYPE_STATION)
++		mt7996_mcu_add_sta(dev, vif, NULL, true, true);
+ 	rcu_assign_pointer(dev->mt76.wcid[idx], &mvif->sta.wcid);
+ 
+ out:
+@@ -256,7 +260,7 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	int idx = msta->wcid.idx;
+ 
+-	mt7996_mcu_add_sta(dev, vif, NULL, false);
++	mt7996_mcu_add_sta(dev, vif, NULL, false, false);
+ 	mt7996_mcu_add_bss_info(phy, vif, false);
+ 
+ 	if (vif == phy->monitor_vif)
+@@ -599,7 +603,8 @@ static void mt7996_bss_info_changed(struct ieee80211_hw *hw,
+ 	    (changed & BSS_CHANGED_ASSOC && vif->cfg.assoc) ||
+ 	    (changed & BSS_CHANGED_BEACON_ENABLED && info->enable_beacon)) {
+ 		mt7996_mcu_add_bss_info(phy, vif, true);
+-		mt7996_mcu_add_sta(dev, vif, NULL, true);
++		mt7996_mcu_add_sta(dev, vif, NULL, true,
++				   !!(changed & BSS_CHANGED_BSSID));
+ 	}
+ 
+ 	if (changed & BSS_CHANGED_ERP_CTS_PROT)
+@@ -688,7 +693,7 @@ int mt7996_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	mt7996_mac_wtbl_update(dev, idx,
+ 			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+ 
+-	ret = mt7996_mcu_add_sta(dev, vif, sta, true);
++	ret = mt7996_mcu_add_sta(dev, vif, sta, true, true);
+ 	if (ret)
+ 		return ret;
+ 
+@@ -702,7 +707,7 @@ void mt7996_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	int i;
+ 
+-	mt7996_mcu_add_sta(dev, vif, sta, false);
++	mt7996_mcu_add_sta(dev, vif, sta, false, false);
+ 
+ 	mt7996_mac_wtbl_update(dev, msta->wcid.idx,
+ 			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index b44abe2ac..df6be2091 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -2133,7 +2133,7 @@ mt7996_mcu_add_group(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ }
+ 
+ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+-		       struct ieee80211_sta *sta, bool enable)
++		       struct ieee80211_sta *sta, bool enable, bool newly)
+ {
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta;
+@@ -2149,8 +2149,8 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 		return PTR_ERR(skb);
+ 
+ 	/* starec basic */
+-	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, skb, vif, sta, enable,
+-				      !rcu_access_pointer(dev->mt76.wcid[msta->wcid.idx]));
++	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, skb, vif, sta, enable, newly);
++
+ 	if (!enable)
+ 		goto out;
+ 
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 36d1f247d..c30d133d9 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -450,7 +450,7 @@ int mt7996_mcu_add_dev_info(struct mt7996_phy *phy,
+ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy,
+ 			    struct ieee80211_vif *vif, int enable);
+ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+-		       struct ieee80211_sta *sta, bool enable);
++		       struct ieee80211_sta *sta, bool enable, bool newly);
+ int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev,
+ 			 struct ieee80211_ampdu_params *params,
+ 			 bool add);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0004-mtk-wifi-mt76-mt7996-disable-rx-header-translation-f.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0004-mtk-wifi-mt76-mt7996-disable-rx-header-translation-f.patch
new file mode 100644
index 0000000..9ca8ac5
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0004-mtk-wifi-mt76-mt7996-disable-rx-header-translation-f.patch
@@ -0,0 +1,56 @@
+From 90f135ced9ca6325fe8ffebe1dff907acf6f9c58 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Tue, 5 Sep 2023 17:31:49 +0800
+Subject: [PATCH 004/120] mtk: wifi: mt76: mt7996: disable rx header
+ translation for BMC entry
+
+When a BMC wtbl of station interface is correctly set with peer address,
+HW will do rx header translation for broadcast data packets, which makes
+mac80211 unable to find the corresponding ieee80211_sta and drop the
+packets. To fix this, disable HW rx header translation for BMC entry.
+
+Change-Id: Ia98bb775af528fe1002590fa25bb8855945cfc4b
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/mcu.c | 9 +++++----
+ 1 file changed, 5 insertions(+), 4 deletions(-)
+
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index df6be2091..fd0cc945b 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -1778,10 +1778,10 @@ mt7996_mcu_sta_hdr_trans_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 	else
+ 		hdr_trans->from_ds = true;
+ 
+-	wcid = (struct mt76_wcid *)sta->drv_priv;
+-	if (!wcid)
++	if (!sta)
+ 		return;
+ 
++	wcid = (struct mt76_wcid *)sta->drv_priv;
+ 	hdr_trans->dis_rx_hdr_tran = !test_bit(MT_WCID_FLAG_HDR_TRANS, &wcid->flags);
+ 	if (test_bit(MT_WCID_FLAG_4ADDR, &wcid->flags)) {
+ 		hdr_trans->to_ds = true;
+@@ -2154,6 +2154,9 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 	if (!enable)
+ 		goto out;
+ 
++	/* starec hdr trans */
++	mt7996_mcu_sta_hdr_trans_tlv(dev, skb, vif, sta);
++
+ 	/* tag order is in accordance with firmware dependency. */
+ 	if (sta) {
+ 		/* starec hdrt mode */
+@@ -2178,8 +2181,6 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 		mt7996_mcu_sta_muru_tlv(dev, skb, vif, sta);
+ 		/* starec bfee */
+ 		mt7996_mcu_sta_bfee_tlv(dev, skb, vif, sta);
+-		/* starec hdr trans */
+-		mt7996_mcu_sta_hdr_trans_tlv(dev, skb, vif, sta);
+ 	}
+ 
+ 	ret = mt7996_mcu_add_group(dev, vif, sta);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0005-mtk-wifi-mt76-mt7996-set-RCPI-value-in-rate-control-.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0005-mtk-wifi-mt76-mt7996-set-RCPI-value-in-rate-control-.patch
new file mode 100644
index 0000000..2cf9284
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0005-mtk-wifi-mt76-mt7996-set-RCPI-value-in-rate-control-.patch
@@ -0,0 +1,42 @@
+From 8ef2f37d8d1e932d911e49d89ddd25b2ed630f58 Mon Sep 17 00:00:00 2001
+From: Peter Chiu <chui-hao.chiu@mediatek.com>
+Date: Mon, 13 Nov 2023 20:15:39 +0800
+Subject: [PATCH 005/120] mtk: wifi: mt76: mt7996: set RCPI value in rate
+ control command
+
+Set RCPI values in mt7996_mcu_sta_rate_ctrl_tlv(), which can make the
+FW rate control algorithm be initialized with a better MCS selection
+table.
+
+CR-Id: WCNCR00240772
+Change-Id: Ie1a99984ad1bbbd36d31d6bb924c3edebdf5fa52
+Signed-off-by: Peter Chiu <chui-hao.chiu@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/mcu.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index fd0cc945b..ddd93ec1d 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -1968,6 +1968,7 @@ static void
+ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev,
+ 			     struct ieee80211_vif *vif, struct ieee80211_sta *sta)
+ {
++#define INIT_RCPI 180
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt76_phy *mphy = mvif->phy->mt76;
+ 	struct cfg80211_chan_def *chandef = &mphy->chandef;
+@@ -2065,6 +2066,8 @@ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev,
+ 					       IEEE80211_HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP);
+ 	}
+ 	ra->sta_cap = cpu_to_le32(cap);
++
++	memset(ra->rx_rcpi, INIT_RCPI, sizeof(ra->rx_rcpi));
+ }
+ 
+ int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0006-mtk-wifi-mt76-connac-enable-HW-CSO-module-for-mt7996.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0006-mtk-wifi-mt76-connac-enable-HW-CSO-module-for-mt7996.patch
new file mode 100644
index 0000000..bccde34
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0006-mtk-wifi-mt76-connac-enable-HW-CSO-module-for-mt7996.patch
@@ -0,0 +1,79 @@
+From 50f7d72067bee8a8db45f8546e3345d250e47470 Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Wed, 3 Jan 2024 15:21:44 +0800
+Subject: [PATCH 006/120] mtk: wifi: mt76: connac: enable HW CSO module for
+ mt7996
+
+For mt7996 chipsets, the HW CSO module can help to identify TCP traffic,
+which assists the firmware in adjusting algorithms to improve overall
+performance.
+
+CR-Id: WCNCR00362693
+Change-Id: I50300949e303c885a8091834d7e1b4107a42a813
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt76_connac_mcu.h |  7 +++++++
+ mt7996/mcu.c      | 14 ++++++++++++++
+ 2 files changed, 21 insertions(+)
+
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 2a4aa7969..f1cd2e505 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -610,6 +610,12 @@ struct sta_rec_ra_fixed {
+ 	u8 mmps_mode;
+ } __packed;
+ 
++struct sta_rec_tx_proc {
++	__le16 tag;
++	__le16 len;
++	__le32 flag;
++} __packed;
++
+ /* wtbl_rec */
+ 
+ struct wtbl_req_hdr {
+@@ -777,6 +783,7 @@ struct wtbl_raw {
+ 					 sizeof(struct sta_rec_ra_fixed) + \
+ 					 sizeof(struct sta_rec_he_6g_capa) + \
+ 					 sizeof(struct sta_rec_pn_info) + \
++					 sizeof(struct sta_rec_tx_proc) + \
+ 					 sizeof(struct tlv) +		\
+ 					 MT76_CONNAC_WTBL_UPDATE_MAX_SIZE)
+ 
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index ddd93ec1d..744156655 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -1748,6 +1748,18 @@ mt7996_mcu_sta_bfee_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 	bfee->fb_identity_matrix = (nrow == 1 && tx_ant == 2);
+ }
+ 
++static void
++mt7996_mcu_sta_tx_proc_tlv(struct sk_buff *skb)
++{
++	struct sta_rec_tx_proc *tx_proc;
++	struct tlv *tlv;
++
++	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_TX_PROC, sizeof(*tx_proc));
++
++	tx_proc = (struct sta_rec_tx_proc *)tlv;
++	tx_proc->flag = cpu_to_le32(0);
++}
++
+ static void
+ mt7996_mcu_sta_hdrt_tlv(struct mt7996_dev *dev, struct sk_buff *skb)
+ {
+@@ -2159,6 +2171,8 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 
+ 	/* starec hdr trans */
+ 	mt7996_mcu_sta_hdr_trans_tlv(dev, skb, vif, sta);
++	/* starec tx proc */
++	mt7996_mcu_sta_tx_proc_tlv(skb);
+ 
+ 	/* tag order is in accordance with firmware dependency. */
+ 	if (sta) {
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0007-mtk-wifi-mt76-mt7996-fix-non-main-BSS-no-beacon-issu.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0007-mtk-wifi-mt76-mt7996-fix-non-main-BSS-no-beacon-issu.patch
new file mode 100644
index 0000000..5189e7f
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0007-mtk-wifi-mt76-mt7996-fix-non-main-BSS-no-beacon-issu.patch
@@ -0,0 +1,40 @@
+From c24b6ac827df2a55ed603406396f050cb5b2031d Mon Sep 17 00:00:00 2001
+From: Henry Yen <henry.yen@mediatek.com>
+Date: Fri, 2 Feb 2024 16:42:24 +0800
+Subject: [PATCH 007/120] mtk: wifi: mt76: mt7996: fix non-main BSS no beacon
+ issue for MBSS scenario
+
+Do not add UNI_BSS_INFO_11V_MBSSID tag when bssid_indicator is not set
+to avoid abnormal beaconing behavior in non-11v MBSS scenario.
+
+CR-Id: WCNCR00185554
+Change-Id: Icbb8b4027e4ac253f0fd46a3c366033f2c66d291
+Signed-off-by: Henry Yen <henry.yen@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/mcu.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 744156655..c4eefc593 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -819,11 +819,14 @@ mt7996_mcu_bss_mbssid_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
+ 	struct bss_info_uni_mbssid *mbssid;
+ 	struct tlv *tlv;
+ 
++	if (!vif->bss_conf.bssid_indicator)
++		return;
++
+ 	tlv = mt7996_mcu_add_uni_tlv(skb, UNI_BSS_INFO_11V_MBSSID, sizeof(*mbssid));
+ 
+ 	mbssid = (struct bss_info_uni_mbssid *)tlv;
+ 
+-	if (enable && vif->bss_conf.bssid_indicator) {
++	if (enable) {
+ 		mbssid->max_indicator = vif->bss_conf.bssid_indicator;
+ 		mbssid->mbss_idx = vif->bss_conf.bssid_index;
+ 		mbssid->tx_bss_omac_idx = 0;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0008-mtk-wifi-mt76-mt7996-fix-potential-memory-leakage-wh.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0008-mtk-wifi-mt76-mt7996-fix-potential-memory-leakage-wh.patch
new file mode 100644
index 0000000..e2082ed
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0008-mtk-wifi-mt76-mt7996-fix-potential-memory-leakage-wh.patch
@@ -0,0 +1,44 @@
+From f03eca7e22ac0c3914ec64143992ad7d4266a8b8 Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Tue, 12 Mar 2024 10:02:17 +0800
+Subject: [PATCH 008/120] mtk: wifi: mt76: mt7996: fix potential memory leakage
+ when reading chip temperature
+
+Without this commit, reading chip temperature will cause memory leakage.
+
+CR-Id: WCNCR00240772
+Fixes: 6879b2e94172 ("wifi: mt76: mt7996: add thermal sensor device support")
+Reported-by: Ryder Lee <ryder.lee@mediatek.com>
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/mcu.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index c4eefc593..d5e35f6dd 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -3750,6 +3750,7 @@ int mt7996_mcu_get_temperature(struct mt7996_phy *phy)
+ 	} __packed * res;
+ 	struct sk_buff *skb;
+ 	int ret;
++	u32 temp;
+ 
+ 	ret = mt76_mcu_send_and_get_msg(&phy->dev->mt76, MCU_WM_UNI_CMD(THERMAL),
+ 					&req, sizeof(req), true, &skb);
+@@ -3757,8 +3758,10 @@ int mt7996_mcu_get_temperature(struct mt7996_phy *phy)
+ 		return ret;
+ 
+ 	res = (void *)skb->data;
++	temp = le32_to_cpu(res->temperature);
++	dev_kfree_skb(skb);
+ 
+-	return le32_to_cpu(res->temperature);
++	return temp;
+ }
+ 
+ int mt7996_mcu_set_thermal_throttling(struct mt7996_phy *phy, u8 state)
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0009-mtk-wifi-mt76-connac-enable-critical-packet-mode-sup.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0009-mtk-wifi-mt76-connac-enable-critical-packet-mode-sup.patch
new file mode 100644
index 0000000..14468f0
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0009-mtk-wifi-mt76-connac-enable-critical-packet-mode-sup.patch
@@ -0,0 +1,88 @@
+From a57cd62348f7843777dd315fad9796adb3a9e0d1 Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Mon, 18 Mar 2024 16:33:12 +0800
+Subject: [PATCH 009/120] mtk: wifi: mt76: connac: enable critical packet mode
+ support for mt7992
+
+For mt7992 chipsets, critical packet mode should be properly configured
+to let the HW SDO module correctly fill the AC queue in TX descriptors of
+some higher priority packets such as ARP and ICMP.
+Without this patch, HW queues may hang when running MU traffic.
+
+CR-Id: WCNCR00240772
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt76_connac_mcu.h |  1 +
+ mt7996/main.c     |  8 ++++++++
+ mt7996/mcu.c      | 13 +++++++++++++
+ mt7996/mt7996.h   |  1 +
+ 4 files changed, 23 insertions(+)
+
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index f1cd2e505..67be14d2a 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1220,6 +1220,7 @@ enum {
+ 	MCU_EXT_CMD_TXDPD_CAL = 0x60,
+ 	MCU_EXT_CMD_CAL_CACHE = 0x67,
+ 	MCU_EXT_CMD_RED_ENABLE = 0x68,
++	MCU_EXT_CMD_CP_SUPPORT = 0x75,
+ 	MCU_EXT_CMD_SET_RADAR_TH = 0x7c,
+ 	MCU_EXT_CMD_SET_RDD_PATTERN = 0x7d,
+ 	MCU_EXT_CMD_MWDS_SUPPORT = 0x80,
+diff --git a/mt7996/main.c b/mt7996/main.c
+index a41d5d06b..16115c279 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -35,6 +35,14 @@ int mt7996_run(struct ieee80211_hw *hw)
+ 		ret = mt7996_mcu_set_hdr_trans(dev, true);
+ 		if (ret)
+ 			goto out;
++
++		if (is_mt7992(&dev->mt76)) {
++			u8 queue = mt76_connac_lmac_mapping(IEEE80211_AC_VI);
++
++			ret = mt7996_mcu_cp_support(dev, queue);
++			if (ret)
++				goto out;
++		}
+ 	}
+ 
+ 	mt7996_mac_enable_nf(dev, phy->mt76->band_idx);
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index d5e35f6dd..ccae954a2 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -4530,3 +4530,16 @@ int mt7996_mcu_set_txpower_sku(struct mt7996_phy *phy)
+ 	return mt76_mcu_skb_send_msg(&dev->mt76, skb,
+ 				     MCU_WM_UNI_CMD(TXPOWER), true);
+ }
++
++int mt7996_mcu_cp_support(struct mt7996_dev *dev, u8 mode)
++{
++	__le32 cp_mode;
++
++	if (mode < mt76_connac_lmac_mapping(IEEE80211_AC_BE) ||
++	    mode > mt76_connac_lmac_mapping(IEEE80211_AC_VO))
++		return -EINVAL;
++
++	cp_mode = cpu_to_le32(mode);
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WA_EXT_CMD(CP_SUPPORT),
++				 &cp_mode, sizeof(cp_mode), true);
++}
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index c30d133d9..b9b0bb4c4 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -612,6 +612,7 @@ int mt7996_mcu_bcn_prot_enable(struct mt7996_dev *dev, struct ieee80211_vif *vif
+ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev,
+ 				     struct ieee80211_vif *vif,
+ 				     struct ieee80211_sta *sta);
++int mt7996_mcu_cp_support(struct mt7996_dev *dev, u8 mode);
+ #ifdef CONFIG_MAC80211_DEBUGFS
+ void mt7996_sta_add_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 			    struct ieee80211_sta *sta, struct dentry *dir);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0010-mtk-wifi-mt76-mt7996-add-sanity-checks-for-backgroun.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0010-mtk-wifi-mt76-mt7996-add-sanity-checks-for-backgroun.patch
new file mode 100644
index 0000000..aef0bea
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0010-mtk-wifi-mt76-mt7996-add-sanity-checks-for-backgroun.patch
@@ -0,0 +1,52 @@
+From a744f7654a271c833e13e799232a8b0cbb1cd38a Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Mon, 18 Mar 2024 17:39:01 +0800
+Subject: [PATCH 010/120] mtk: wifi: mt76: mt7996: add sanity checks for
+ background radar trigger
+
+Check if background radar is enabled or not before manually triggering it,
+and also add more checks in radar detected event.
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/debugfs.c | 5 +++++
+ mt7996/mcu.c     | 5 ++++-
+ 2 files changed, 9 insertions(+), 1 deletion(-)
+
+diff --git a/mt7996/debugfs.c b/mt7996/debugfs.c
+index 9bd953586..62c03d088 100644
+--- a/mt7996/debugfs.c
++++ b/mt7996/debugfs.c
+@@ -225,6 +225,11 @@ mt7996_radar_trigger(void *data, u64 val)
+ 	if (val > MT_RX_SEL2)
+ 		return -EINVAL;
+ 
++	if (val == MT_RX_SEL2 && !dev->rdd2_phy) {
++		dev_err(dev->mt76.dev, "Background radar is not enabled\n");
++		return -EINVAL;
++	}
++
+ 	return mt7996_mcu_rdd_cmd(dev, RDD_RADAR_EMULATE,
+ 				  val, 0, 0);
+ }
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index ccae954a2..c024f138f 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -355,7 +355,10 @@ mt7996_mcu_rx_radar_detected(struct mt7996_dev *dev, struct sk_buff *skb)
+ 	if (r->band_idx >= ARRAY_SIZE(dev->mt76.phys))
+ 		return;
+ 
+-	if (dev->rdd2_phy && r->band_idx == MT_RX_SEL2)
++	if (r->band_idx == MT_RX_SEL2 && !dev->rdd2_phy)
++		return;
++
++	if (r->band_idx == MT_RX_SEL2)
+ 		mphy = dev->rdd2_phy->mt76;
+ 	else
+ 		mphy = dev->mt76.phys[r->band_idx];
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0011-mtk-wifi-mt76-mt7996-let-upper-layer-handle-MGMT-fra.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0011-mtk-wifi-mt76-mt7996-let-upper-layer-handle-MGMT-fra.patch
new file mode 100644
index 0000000..8ac89a2
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0011-mtk-wifi-mt76-mt7996-let-upper-layer-handle-MGMT-fra.patch
@@ -0,0 +1,50 @@
+From 6d4222f48ea89565f2203ddd5eeb3357d02d201c Mon Sep 17 00:00:00 2001
+From: Michael-CY Lee <michael-cy.lee@mediatek.com>
+Date: Mon, 18 Mar 2024 17:06:58 +0800
+Subject: [PATCH 011/120] mtk: wifi: mt76: mt7996: let upper layer handle MGMT
+ frame protection
+
+The firmware support for management frame protection has limitations:
+- do not support cipher BIP-GMAC-128 and BIP-GMAC-256
+- support cipher BIP-CMAC-128 and BIP-CMAC-256, except action frame with
+  action type 'not robust'.
+
+Therefore, to simplify the logic, do not set the IGTK to firmware and
+let the encryption of management frames be handled by upper layer.
+
+CR-Id: WCNCR00289305
+Signed-off-by: Michael-CY Lee <michael-cy.lee@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/main.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 16115c279..0ff8af93b 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -352,10 +352,6 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+ 
+ 	/* fall back to sw encryption for unsupported ciphers */
+ 	switch (key->cipher) {
+-	case WLAN_CIPHER_SUITE_AES_CMAC:
+-		wcid_keyidx = &wcid->hw_key_idx2;
+-		key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIE;
+-		break;
+ 	case WLAN_CIPHER_SUITE_TKIP:
+ 	case WLAN_CIPHER_SUITE_CCMP:
+ 	case WLAN_CIPHER_SUITE_CCMP_256:
+@@ -363,6 +359,10 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+ 	case WLAN_CIPHER_SUITE_GCMP_256:
+ 	case WLAN_CIPHER_SUITE_SMS4:
+ 		break;
++	case WLAN_CIPHER_SUITE_AES_CMAC:
++		key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIE;
++		fallthrough;
++	case WLAN_CIPHER_SUITE_BIP_CMAC_256:
+ 	case WLAN_CIPHER_SUITE_BIP_GMAC_128:
+ 	case WLAN_CIPHER_SUITE_BIP_GMAC_256:
+ 		if (key->keyidx == 6 || key->keyidx == 7)
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0012-mtk-wifi-mt76-mt7996-add-sanity-check-for-NAPI-sched.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0012-mtk-wifi-mt76-mt7996-add-sanity-check-for-NAPI-sched.patch
new file mode 100644
index 0000000..bb26ecd
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0012-mtk-wifi-mt76-mt7996-add-sanity-check-for-NAPI-sched.patch
@@ -0,0 +1,38 @@
+From e1e69be464ae5ef9a2249c76d5eeeb3783417011 Mon Sep 17 00:00:00 2001
+From: Henry Yen <henry.yen@mediatek.com>
+Date: Tue, 16 Jan 2024 11:30:02 +0800
+Subject: [PATCH 012/120] mtk: wifi: mt76: mt7996: add sanity check for NAPI
+ schedule
+
+It's observed that host driver might occasionally receive
+interrupts from unexpected Rx ring, whose Rx NAPI hasn't been
+prepared yet. Under such situation, __napi_poll crash issue
+would occur, so we add a sanity check to prevent it.
+
+Without this patch, we might encounter kernel crash issue
+especially in WED-on & RRO-on software path.
+
+CR-Id: WCNCR00185554
+Change-Id: I165b003a41207d034cfb720b4db979b1e1d5f695
+Signed-off-by: Henry Yen <henry.yen@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/mmio.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mt7996/mmio.c b/mt7996/mmio.c
+index 341fa089e..fd4d41d90 100644
+--- a/mt7996/mmio.c
++++ b/mt7996/mmio.c
+@@ -560,7 +560,7 @@ static void mt7996_irq_tasklet(struct tasklet_struct *t)
+ 		napi_schedule(&dev->mt76.tx_napi);
+ 
+ 	for (i = 0; i < __MT_RXQ_MAX; i++) {
+-		if ((intr & MT_INT_RX(i)))
++		if ((intr & MT_INT_RX(i)) && dev->mt76.napi[i].poll)
+ 			napi_schedule(&dev->mt76.napi[i]);
+ 	}
+ 
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0013-mtk-wifi-mt76-mt7996-initialize-variable-to-avoid-un.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0013-mtk-wifi-mt76-mt7996-initialize-variable-to-avoid-un.patch
new file mode 100644
index 0000000..d0093d0
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0013-mtk-wifi-mt76-mt7996-initialize-variable-to-avoid-un.patch
@@ -0,0 +1,33 @@
+From 8636b419bcccf44fe85d79ad416a5a057b256b53 Mon Sep 17 00:00:00 2001
+From: Henry Yen <henry.yen@mediatek.com>
+Date: Fri, 19 Jan 2024 11:11:19 +0800
+Subject: [PATCH 013/120] mtk: wifi: mt76: mt7996: initialize variable to avoid
+ unexpected IRQ handling
+
+Initialize the variable to avoid processing unexpected interrupts given
+from wrong source.
+
+CR-Id: WCNCR00185554
+Change-Id: I4ba7465f84f253035261a8b07951add9ca5b2f6a
+Signed-off-by: Henry Yen <henry.yen@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/mmio.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mt7996/mmio.c b/mt7996/mmio.c
+index fd4d41d90..367a204d2 100644
+--- a/mt7996/mmio.c
++++ b/mt7996/mmio.c
+@@ -519,7 +519,7 @@ static void mt7996_irq_tasklet(struct tasklet_struct *t)
+ 	struct mt7996_dev *dev = from_tasklet(dev, t, mt76.irq_tasklet);
+ 	struct mtk_wed_device *wed = &dev->mt76.mmio.wed;
+ 	struct mtk_wed_device *wed_hif2 = &dev->mt76.mmio.wed_hif2;
+-	u32 i, intr, mask, intr1;
++	u32 i, intr, mask, intr1 = 0;
+ 
+ 	if (dev->hif2 && mtk_wed_device_active(wed_hif2)) {
+ 		mtk_wed_device_irq_set_mask(wed_hif2, 0);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0014-mtk-wifi-mt76-mt7996-enable-ser-query.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0014-mtk-wifi-mt76-mt7996-enable-ser-query.patch
new file mode 100644
index 0000000..02b1442
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0014-mtk-wifi-mt76-mt7996-enable-ser-query.patch
@@ -0,0 +1,30 @@
+From 9370f4ec3b37d2e544cf96cf47752670f67692a4 Mon Sep 17 00:00:00 2001
+From: Peter Chiu <chui-hao.chiu@mediatek.com>
+Date: Mon, 30 Oct 2023 20:19:41 +0800
+Subject: [PATCH 014/120] mtk: wifi: mt76: mt7996: enable ser query
+
+Do not return -EINVAL when action is UNI_CMD_SER_QUERY for user
+to dump SER information from FW.
+
+CR-Id: WCNCR00240772
+Signed-off-by: Peter Chiu <chui-hao.chiu@mediatek.com>
+---
+ mt7996/mcu.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index c024f138f..e948405bc 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -3865,6 +3865,8 @@ int mt7996_mcu_set_ser(struct mt7996_dev *dev, u8 action, u8 val, u8 band)
+ 	};
+ 
+ 	switch (action) {
++	case UNI_CMD_SER_QUERY:
++		break;
+ 	case UNI_CMD_SER_SET:
+ 		req.set.mask = cpu_to_le32(val);
+ 		break;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0015-mtk-wifi-mt76-mt7996-Fix-TGax-HE-4.51.1_24G-fail.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0015-mtk-wifi-mt76-mt7996-Fix-TGax-HE-4.51.1_24G-fail.patch
new file mode 100644
index 0000000..0a8bc08
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0015-mtk-wifi-mt76-mt7996-Fix-TGax-HE-4.51.1_24G-fail.patch
@@ -0,0 +1,36 @@
+From 2fbbeebaaf2aa1e997485f335069d597a339d38c Mon Sep 17 00:00:00 2001
+From: mtk27745 <rex.lu@mediatek.com>
+Date: Fri, 17 Nov 2023 11:01:04 +0800
+Subject: [PATCH 015/120] mtk: wifi: mt76: mt7996: Fix TGax HE-4.51.1_24G fail
+
+According to sta capability to decide to enable/disable wed pao when create ppe entry.
+without this patch, TGax HE-4.51.1_24G will test fail
+
+Signed-off-by: mtk27745 <rex.lu@mediatek.com>
+CR-Id: WCNCR00259516
+Change-Id: Ic51473fcc24757887f0f9f81a31b6f01dee2c845
+---
+ mt7996/main.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 0ff8af93b..0a576daad 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -1457,7 +1457,12 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw,
+ 	path->mtk_wdma.queue = 0;
+ 	path->mtk_wdma.wcid = msta->wcid.idx;
+ 
+-	path->mtk_wdma.amsdu = mtk_wed_is_amsdu_supported(wed);
++	if (ieee80211_hw_check(hw, SUPPORTS_AMSDU_IN_AMPDU) &&
++	    mtk_wed_is_amsdu_supported(wed))
++		path->mtk_wdma.amsdu = msta->wcid.amsdu;
++	else
++		path->mtk_wdma.amsdu = 0;
++
+ 	ctx->dev = NULL;
+ 
+ 	return 0;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0016-mtk-wifi-mt76-mt7996-add-eagle-default-bin-of-differ.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0016-mtk-wifi-mt76-mt7996-add-eagle-default-bin-of-differ.patch
new file mode 100644
index 0000000..de35146
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0016-mtk-wifi-mt76-mt7996-add-eagle-default-bin-of-differ.patch
@@ -0,0 +1,111 @@
+From f4dc72298a8f766d3318cab772ff2c145c078608 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Thu, 20 Jul 2023 17:27:22 +0800
+Subject: [PATCH 016/120] mtk: wifi: mt76: mt7996: add eagle default bin of
+ different sku variants
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt7996/eeprom.c |  2 ++
+ mt7996/init.c   |  4 ++++
+ mt7996/mt7996.h | 28 ++++++++++++++++++++++++++--
+ 3 files changed, 32 insertions(+), 2 deletions(-)
+
+diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
+index 4a8237118..7505a8b7d 100644
+--- a/mt7996/eeprom.c
++++ b/mt7996/eeprom.c
+@@ -26,6 +26,8 @@ static char *mt7996_eeprom_name(struct mt7996_dev *dev)
+ {
+ 	switch (mt76_chip(&dev->mt76)) {
+ 	case 0x7990:
++		if (dev->chip_sku == MT7996_SKU_404)
++			return MT7996_EEPROM_DEFAULT_404;
+ 		return MT7996_EEPROM_DEFAULT;
+ 	case 0x7992:
+ 		return MT7992_EEPROM_DEFAULT;
+diff --git a/mt7996/init.c b/mt7996/init.c
+index 9aa97e4a7..274863dc3 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -897,6 +897,10 @@ static int mt7996_init_hardware(struct mt7996_dev *dev)
+ 	INIT_LIST_HEAD(&dev->wed_rro.poll_list);
+ 	spin_lock_init(&dev->wed_rro.lock);
+ 
++	ret = mt7996_get_chip_sku(dev);
++	if (ret)
++		return ret;
++
+ 	ret = mt7996_dma_init(dev);
+ 	if (ret)
+ 		return ret;
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index b9b0bb4c4..2fe34e522 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -40,6 +40,7 @@
+ #define MT7992_ROM_PATCH		"mediatek/mt7996/mt7992_rom_patch.bin"
+ 
+ #define MT7996_EEPROM_DEFAULT		"mediatek/mt7996/mt7996_eeprom.bin"
++#define MT7996_EEPROM_DEFAULT_404	"mediatek/mt7996/mt7996_eeprom_dual_404.bin"
+ #define MT7992_EEPROM_DEFAULT		"mediatek/mt7996/mt7992_eeprom.bin"
+ #define MT7996_EEPROM_SIZE		7680
+ #define MT7996_EEPROM_BLOCK_SIZE	16
+@@ -88,6 +89,11 @@ struct mt7996_sta;
+ struct mt7996_dfs_pulse;
+ struct mt7996_dfs_pattern;
+ 
++enum mt7996_sku_type {
++	MT7996_SKU_404,
++	MT7996_SKU_444,
++};
++
+ enum mt7996_ram_type {
+ 	MT7996_RAM_TYPE_WM,
+ 	MT7996_RAM_TYPE_WA,
+@@ -257,6 +263,8 @@ struct mt7996_dev {
+ 	struct cfg80211_chan_def rdd2_chandef;
+ 	struct mt7996_phy *rdd2_phy;
+ 
++	u8 chip_sku;
++
+ 	u16 chainmask;
+ 	u8 chainshift[__MT_MAX_BAND];
+ 	u32 hif_idx;
+@@ -398,6 +406,23 @@ mt7996_phy3(struct mt7996_dev *dev)
+ 	return __mt7996_phy(dev, MT_BAND2);
+ }
+ 
++static inline int
++mt7996_get_chip_sku(struct mt7996_dev *dev)
++{
++	u32 val = mt76_rr(dev, MT_PAD_GPIO);
++
++	/* reserve for future variants */
++	switch (mt76_chip(&dev->mt76)) {
++	case 0x7990:
++		dev->chip_sku = FIELD_GET(MT_PAD_GPIO_ADIE_COMB, val) <= 1;
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	return 0;
++}
++
+ static inline bool
+ mt7996_band_valid(struct mt7996_dev *dev, u8 band)
+ {
+@@ -405,8 +430,7 @@ mt7996_band_valid(struct mt7996_dev *dev, u8 band)
+ 		return band <= MT_BAND1;
+ 
+ 	/* tri-band support */
+-	if (band <= MT_BAND2 &&
+-	    mt76_get_field(dev, MT_PAD_GPIO, MT_PAD_GPIO_ADIE_COMB) <= 1)
++	if (band <= MT_BAND2 && dev->chip_sku)
+ 		return true;
+ 
+ 	return band == MT_BAND0 || band == MT_BAND2;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0017-mtk-wifi-mt76-mt7996-add-kite-fw-default-bin-for-dif.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0017-mtk-wifi-mt76-mt7996-add-kite-fw-default-bin-for-dif.patch
new file mode 100644
index 0000000..cd0c188
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0017-mtk-wifi-mt76-mt7996-add-kite-fw-default-bin-for-dif.patch
@@ -0,0 +1,326 @@
+From 9432c29f3d8cd359c3b0d887a13b92e974e522b4 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Fri, 21 Jul 2023 10:41:28 +0800
+Subject: [PATCH 017/120] mtk: wifi: mt76: mt7996: add kite fw & default bin
+ for different sku variants
+
+Add fem type (2i5i, 2i5e, 2e5e, ...)
+Add Kite default bin for each fem type since loading wrong default bin
+will fail to setup interface
+Add eeprom fem type check
+
+Add adie 7976c efuse check
+Efuse offset 0x470 will be set to 0xc after final test if 7976c adie is used
+Chip manufactoring factories may transfer, which leads to different adie chip versions,
+so we add this efuse check to avoid 7976c recognition failure.
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Change-Id: I98caec6675670e3d1c0ee953bef2aeb71c3cf74e
+
+GPIO ADie Combination of BE5040 should be considered as don't care
+instead of 0
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+
+Only check eeprom chip id when fem type (= MT7996_FEM_UNSET) is not determined yet
+Without this fix, mt7996_check_eeprom will return EINVAL in mt7996_eeprom_check_fw_mode
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt7996/eeprom.c | 38 +++++++++++++++++++++++++++++--
+ mt7996/eeprom.h |  1 +
+ mt7996/init.c   | 59 +++++++++++++++++++++++++++++++++++++++++++++++++
+ mt7996/mcu.c    |  7 +++++-
+ mt7996/mt7996.h | 49 +++++++++++++++++++++++++---------------
+ mt7996/regs.h   |  7 ++++++
+ 6 files changed, 140 insertions(+), 21 deletions(-)
+
+diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
+index 7505a8b7d..3260d1fef 100644
+--- a/mt7996/eeprom.c
++++ b/mt7996/eeprom.c
+@@ -9,14 +9,33 @@
+ 
+ static int mt7996_check_eeprom(struct mt7996_dev *dev)
+ {
++#define FEM_INT				0
++#define FEM_EXT				3
+ 	u8 *eeprom = dev->mt76.eeprom.data;
++	u8 i, fem[__MT_MAX_BAND], fem_type;
+ 	u16 val = get_unaligned_le16(eeprom);
+ 
++	for (i = 0; i < __MT_MAX_BAND; i++)
++		fem[i] = eeprom[MT_EE_WIFI_CONF + 6 + i] & MT_EE_WIFI_PA_LNA_CONFIG;
++
+ 	switch (val) {
+ 	case 0x7990:
+ 		return is_mt7996(&dev->mt76) ? 0 : -EINVAL;
+ 	case 0x7992:
+-		return is_mt7992(&dev->mt76) ? 0 : -EINVAL;
++		if (dev->fem_type == MT7996_FEM_UNSET)
++			return is_mt7992(&dev->mt76) ? 0 : -EINVAL;
++
++		if (fem[0] == FEM_EXT && fem[1] == FEM_EXT)
++			fem_type = MT7996_FEM_EXT;
++		else if (fem[0] == FEM_INT && fem[1] == FEM_INT)
++			fem_type = MT7996_FEM_INT;
++		else if (fem[0] == FEM_INT && fem[1] == FEM_EXT)
++			fem_type = MT7996_FEM_MIX;
++		else
++			return -EINVAL;
++
++		return (is_mt7992(&dev->mt76) ? 0 : -EINVAL) |
++		       (dev->fem_type == fem_type ? 0 : -EINVAL);
+ 	default:
+ 		return -EINVAL;
+ 	}
+@@ -30,7 +49,18 @@ static char *mt7996_eeprom_name(struct mt7996_dev *dev)
+ 			return MT7996_EEPROM_DEFAULT_404;
+ 		return MT7996_EEPROM_DEFAULT;
+ 	case 0x7992:
+-		return MT7992_EEPROM_DEFAULT;
++		if (dev->chip_sku == MT7992_SKU_23) {
++			if (dev->fem_type == MT7996_FEM_INT)
++				return MT7992_EEPROM_DEFAULT_23;
++			return MT7992_EEPROM_DEFAULT_23_EXT;
++		} else if (dev->chip_sku == MT7992_SKU_44) {
++			if (dev->fem_type == MT7996_FEM_INT)
++				return MT7992_EEPROM_DEFAULT;
++			else if (dev->fem_type == MT7996_FEM_MIX)
++				return MT7992_EEPROM_DEFAULT_MIX;
++			return MT7992_EEPROM_DEFAULT_EXT;
++		}
++		return MT7992_EEPROM_DEFAULT_24;
+ 	default:
+ 		return MT7996_EEPROM_DEFAULT;
+ 	}
+@@ -221,6 +251,10 @@ int mt7996_eeprom_init(struct mt7996_dev *dev)
+ {
+ 	int ret;
+ 
++	ret = mt7996_get_chip_sku(dev);
++	if (ret)
++		return ret;
++
+ 	ret = mt7996_eeprom_load(dev);
+ 	if (ret < 0) {
+ 		if (ret != -EINVAL)
+diff --git a/mt7996/eeprom.h b/mt7996/eeprom.h
+index 412d6e2f8..72c38ad3b 100644
+--- a/mt7996/eeprom.h
++++ b/mt7996/eeprom.h
+@@ -29,6 +29,7 @@ enum mt7996_eeprom_field {
+ #define MT_EE_WIFI_CONF0_BAND_SEL		GENMASK(2, 0)
+ #define MT_EE_WIFI_CONF1_BAND_SEL		GENMASK(5, 3)
+ #define MT_EE_WIFI_CONF2_BAND_SEL		GENMASK(2, 0)
++#define MT_EE_WIFI_PA_LNA_CONFIG		GENMASK(1, 0)
+ 
+ #define MT_EE_WIFI_CONF1_TX_PATH_BAND0		GENMASK(5, 3)
+ #define MT_EE_WIFI_CONF2_TX_PATH_BAND1		GENMASK(2, 0)
+diff --git a/mt7996/init.c b/mt7996/init.c
+index 274863dc3..0e3cdc051 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -882,6 +882,65 @@ out:
+ #endif
+ }
+ 
++int mt7996_get_chip_sku(struct mt7996_dev *dev)
++{
++#define MT7976C_CHIP_VER	0x8a10
++#define MT7976C_HL_CHIP_VER	0x8b00
++#define MT7976C_PS_CHIP_VER	0x8c10
++#define MT7976C_EFUSE_OFFSET	0x470
++#define MT7976C_EFUSE_VALUE	0xc
++	u32 regval, val = mt76_rr(dev, MT_PAD_GPIO);
++	u16 adie_chip_id, adie_chip_ver;
++	u8 adie_comb, adie_num, adie_idx = 0;
++
++	switch (mt76_chip(&dev->mt76)) {
++	case 0x7990:
++		adie_comb = FIELD_GET(MT_PAD_GPIO_ADIE_COMB, val);
++		if (adie_comb <= 1)
++			dev->chip_sku = MT7996_SKU_444;
++		else
++			dev->chip_sku = MT7996_SKU_404;
++		break;
++	case 0x7992:
++		adie_comb = FIELD_GET(MT_PAD_GPIO_ADIE_COMB_7992, val);
++		adie_num = FIELD_GET(MT_PAD_GPIO_ADIE_NUM_7992, val);
++		adie_idx = !adie_num;
++		if (adie_num)
++			dev->chip_sku = MT7992_SKU_23;
++		else if (adie_comb)
++			dev->chip_sku = MT7992_SKU_44;
++		else
++			dev->chip_sku = MT7992_SKU_24;
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	if (test_bit(MT76_STATE_MCU_RUNNING, &dev->mphy.state)) {
++		u8 buf[MT7996_EEPROM_BLOCK_SIZE];
++		u8 idx = MT7976C_EFUSE_OFFSET % MT7996_EEPROM_BLOCK_SIZE;
++		bool is_7976c;
++
++		mt7996_mcu_rf_regval(dev, MT_ADIE_CHIP_ID(adie_idx), &regval, false);
++		adie_chip_id = FIELD_GET(MT_ADIE_CHIP_ID_MASK, regval);
++		adie_chip_ver = FIELD_GET(MT_ADIE_VERSION_MASK, regval);
++		mt7996_mcu_get_eeprom(dev, MT7976C_EFUSE_OFFSET, buf);
++		is_7976c = (adie_chip_ver == MT7976C_CHIP_VER) ||
++			   (adie_chip_ver == MT7976C_HL_CHIP_VER) ||
++			   (adie_chip_ver == MT7976C_PS_CHIP_VER) ||
++			   (buf[idx] == MT7976C_EFUSE_VALUE);
++		if (adie_chip_id == 0x7975 || (adie_chip_id == 0x7976 && is_7976c) ||
++		    adie_chip_id == 0x7979)
++			dev->fem_type = MT7996_FEM_INT;
++		else if (adie_chip_id == 0x7977 && adie_comb == 1)
++			dev->fem_type = MT7996_FEM_MIX;
++		else
++			dev->fem_type = MT7996_FEM_EXT;
++	}
++
++	return 0;
++}
++
+ static int mt7996_init_hardware(struct mt7996_dev *dev)
+ {
+ 	int ret, idx;
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index e948405bc..c316041d4 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -14,7 +14,12 @@
+ 	char *_fw;						\
+ 	switch (mt76_chip(&(_dev)->mt76)) {			\
+ 	case 0x7992:						\
+-		_fw = MT7992_##name;				\
++		if ((_dev)->chip_sku == MT7992_SKU_23)		\
++			_fw = MT7992_##name##_23;		\
++		else if ((_dev)->chip_sku == MT7992_SKU_24)	\
++			_fw = MT7992_##name##_24;		\
++		else						\
++			_fw = MT7992_##name;			\
+ 		break;						\
+ 	case 0x7990:						\
+ 	default:						\
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 2fe34e522..32e4d340e 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -39,9 +39,24 @@
+ #define MT7992_FIRMWARE_DSP		"mediatek/mt7996/mt7992_dsp.bin"
+ #define MT7992_ROM_PATCH		"mediatek/mt7996/mt7992_rom_patch.bin"
+ 
++#define MT7992_FIRMWARE_WA_24		"mediatek/mt7996/mt7992_wa_24.bin"
++#define MT7992_FIRMWARE_WM_24		"mediatek/mt7996/mt7992_wm_24.bin"
++#define MT7992_FIRMWARE_DSP_24		"mediatek/mt7996/mt7992_dsp_24.bin"
++#define MT7992_ROM_PATCH_24		"mediatek/mt7996/mt7992_rom_patch_24.bin"
++
++#define MT7992_FIRMWARE_WA_23		"mediatek/mt7996/mt7992_wa_23.bin"
++#define MT7992_FIRMWARE_WM_23		"mediatek/mt7996/mt7992_wm_23.bin"
++#define MT7992_FIRMWARE_DSP_23		"mediatek/mt7996/mt7992_dsp_23.bin"
++#define MT7992_ROM_PATCH_23		"mediatek/mt7996/mt7992_rom_patch_23.bin"
++
+ #define MT7996_EEPROM_DEFAULT		"mediatek/mt7996/mt7996_eeprom.bin"
+ #define MT7996_EEPROM_DEFAULT_404	"mediatek/mt7996/mt7996_eeprom_dual_404.bin"
+-#define MT7992_EEPROM_DEFAULT		"mediatek/mt7996/mt7992_eeprom.bin"
++#define MT7992_EEPROM_DEFAULT		"mediatek/mt7996/mt7992_eeprom_2i5i.bin"
++#define MT7992_EEPROM_DEFAULT_EXT	"mediatek/mt7996/mt7992_eeprom_2e5e.bin"
++#define MT7992_EEPROM_DEFAULT_MIX	"mediatek/mt7996/mt7992_eeprom_2i5e.bin"
++#define MT7992_EEPROM_DEFAULT_24	"mediatek/mt7996/mt7992_eeprom_24_2i5i.bin"
++#define MT7992_EEPROM_DEFAULT_23	"mediatek/mt7996/mt7992_eeprom_23_2i5i.bin"
++#define MT7992_EEPROM_DEFAULT_23_EXT	"mediatek/mt7996/mt7992_eeprom_23_2e5e.bin"
+ #define MT7996_EEPROM_SIZE		7680
+ #define MT7996_EEPROM_BLOCK_SIZE	16
+ #define MT7996_TOKEN_SIZE		16384
+@@ -89,11 +104,24 @@ struct mt7996_sta;
+ struct mt7996_dfs_pulse;
+ struct mt7996_dfs_pattern;
+ 
++enum mt7996_fem_type {
++	MT7996_FEM_UNSET,
++	MT7996_FEM_EXT,
++	MT7996_FEM_INT,
++	MT7996_FEM_MIX,
++};
++
+ enum mt7996_sku_type {
+ 	MT7996_SKU_404,
+ 	MT7996_SKU_444,
+ };
+ 
++enum mt7992_sku_type {
++	MT7992_SKU_23,
++	MT7992_SKU_24,
++	MT7992_SKU_44,
++};
++
+ enum mt7996_ram_type {
+ 	MT7996_RAM_TYPE_WM,
+ 	MT7996_RAM_TYPE_WA,
+@@ -264,6 +292,7 @@ struct mt7996_dev {
+ 	struct mt7996_phy *rdd2_phy;
+ 
+ 	u8 chip_sku;
++	u8 fem_type;
+ 
+ 	u16 chainmask;
+ 	u8 chainshift[__MT_MAX_BAND];
+@@ -406,23 +435,6 @@ mt7996_phy3(struct mt7996_dev *dev)
+ 	return __mt7996_phy(dev, MT_BAND2);
+ }
+ 
+-static inline int
+-mt7996_get_chip_sku(struct mt7996_dev *dev)
+-{
+-	u32 val = mt76_rr(dev, MT_PAD_GPIO);
+-
+-	/* reserve for future variants */
+-	switch (mt76_chip(&dev->mt76)) {
+-	case 0x7990:
+-		dev->chip_sku = FIELD_GET(MT_PAD_GPIO_ADIE_COMB, val) <= 1;
+-		break;
+-	default:
+-		return -EINVAL;
+-	}
+-
+-	return 0;
+-}
+-
+ static inline bool
+ mt7996_band_valid(struct mt7996_dev *dev, u8 band)
+ {
+@@ -461,6 +473,7 @@ int mt7996_init_tx_queues(struct mt7996_phy *phy, int idx,
+ 			  int n_desc, int ring_base, struct mtk_wed_device *wed);
+ void mt7996_init_txpower(struct mt7996_phy *phy);
+ int mt7996_txbf_init(struct mt7996_dev *dev);
++int mt7996_get_chip_sku(struct mt7996_dev *dev);
+ void mt7996_reset(struct mt7996_dev *dev);
+ int mt7996_run(struct ieee80211_hw *hw);
+ int mt7996_mcu_init(struct mt7996_dev *dev);
+diff --git a/mt7996/regs.h b/mt7996/regs.h
+index 47b429d8b..cf12c5e02 100644
+--- a/mt7996/regs.h
++++ b/mt7996/regs.h
+@@ -662,6 +662,13 @@ enum offs_rev {
+ 
+ #define MT_PAD_GPIO				0x700056f0
+ #define MT_PAD_GPIO_ADIE_COMB			GENMASK(16, 15)
++#define MT_PAD_GPIO_ADIE_COMB_7992		GENMASK(17, 16)
++#define MT_PAD_GPIO_ADIE_NUM_7992		BIT(15)
++
++/* ADIE */
++#define MT_ADIE_CHIP_ID(_idx)                  (0x0f00002c + ((_idx) << 28))
++#define MT_ADIE_VERSION_MASK                   GENMASK(15, 0)
++#define MT_ADIE_CHIP_ID_MASK                   GENMASK(31, 16)
+ 
+ #define MT_HW_REV				0x70010204
+ #define MT_HW_REV1				0x8a00
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0018-mtk-wifi-mt76-mt7996-ACS-channel-time-too-long-on-du.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0018-mtk-wifi-mt76-mt7996-ACS-channel-time-too-long-on-du.patch
new file mode 100644
index 0000000..b9c34b6
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0018-mtk-wifi-mt76-mt7996-ACS-channel-time-too-long-on-du.patch
@@ -0,0 +1,70 @@
+From ef9482fa33cd00ed11ea3ac2fc968f01ba7b6644 Mon Sep 17 00:00:00 2001
+From: "fancy.liu" <fancy.liu@mediatek.com>
+Date: Tue, 14 Nov 2023 10:13:24 +0800
+Subject: [PATCH 018/120] mtk: wifi: mt76: mt7996: ACS channel time too long on
+ duty channel
+
+Step and issue:
+1. Set channel to 36 in hostapd config;
+2. Bootup;
+3. Enable ACS through UCI command and reload;
+4. Check hostapd log, channel 36 channel_time is much longer than other channels.
+
+Root cause:
+The reset chan_stat condition missed duty channel.
+
+Solution:
+When scan start, need to reset chan_stat in each channel switch.
+
+CR-Id: WCNCR00351054
+Signed-off-by: fancy.liu <fancy.liu@mediatek.com>
+
+Issue:
+There's a chance that the channel time for duty channel is zero in ACS
+scan.
+
+Root cause:
+The chan_stat may be reset when restore to duty channel.
+Mac80211 will notify to hostapd when scan done and then restore to duty
+channel.
+And mt76 will clear scan flag after restore done.
+If hostapd get the chan_stat before channel_restore, will get the
+correct channel time;
+If hostapd get the chan_stat after channel_restore, will get zero
+channel time;
+
+Solution:
+When channel switch, will check the mac80211 scan state but not the mt76 scan flag.
+Mac80211 scan state will be set in scanning, and will be reset after
+scan done and before restore to duty channel.
+
+CR-Id: WCNCR00357653
+Signed-off-by: fancy.liu <fancy.liu@mediatek.com>
+---
+ mac80211.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/mac80211.c b/mac80211.c
+index 6f8b85d85..7a61d3c19 100644
+--- a/mac80211.c
++++ b/mac80211.c
+@@ -928,6 +928,7 @@ void mt76_set_channel(struct mt76_phy *phy)
+ 	struct cfg80211_chan_def *chandef = &hw->conf.chandef;
+ 	bool offchannel = hw->conf.flags & IEEE80211_CONF_OFFCHANNEL;
+ 	int timeout = HZ / 5;
++	unsigned long was_scanning = ieee80211_get_scanning(hw);
+ 
+ 	wait_event_timeout(dev->tx_wait, !mt76_has_tx_pending(phy), timeout);
+ 	mt76_update_survey(phy);
+@@ -942,7 +943,7 @@ void mt76_set_channel(struct mt76_phy *phy)
+ 	if (!offchannel)
+ 		phy->main_chan = chandef->chan;
+ 
+-	if (chandef->chan != phy->main_chan)
++	if (chandef->chan != phy->main_chan || was_scanning)
+ 		memset(phy->chan_state, 0, sizeof(*phy->chan_state));
+ }
+ EXPORT_SYMBOL_GPL(mt76_set_channel);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0019-mtk-wifi-mt76-mt7996-Fixed-null-pointer-dereference-.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0019-mtk-wifi-mt76-mt7996-Fixed-null-pointer-dereference-.patch
new file mode 100644
index 0000000..f1bed8d
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0019-mtk-wifi-mt76-mt7996-Fixed-null-pointer-dereference-.patch
@@ -0,0 +1,44 @@
+From f6d53616762cdea29d9d06353a42d9349791a80d Mon Sep 17 00:00:00 2001
+From: MeiChia Chiu <meichia.chiu@mediatek.com>
+Date: Thu, 26 Oct 2023 10:08:10 +0800
+Subject: [PATCH 019/120] mtk: wifi: mt76: mt7996: Fixed null pointer
+ dereference issue
+
+Without this patch, when the station is still in Authentication stage and
+sends a "Notify bandwidth change action frame" to AP at the same time,
+there will be a race condition that causes a crash to occur because the AP
+access "msta->vif" that has not been fully initialized.
+
+CR-ID: WCNCR00240597
+Change-Id: Ie17fbdd8ab11651a9ae0c30faac0b5ad82176e95
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Money Wang <money.wang@mediatek.com>
+Signed-off-by: MeiChia Chiu <meichia.chiu@mediatek.com>
+---
+ mt7996/main.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 0a576daad..2a6706c25 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -1073,9 +1073,16 @@ static void mt7996_sta_rc_update(struct ieee80211_hw *hw,
+ 				 struct ieee80211_sta *sta,
+ 				 u32 changed)
+ {
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	struct mt7996_dev *dev = phy->dev;
+ 
++	if (!msta->vif) {
++		dev_warn(dev->mt76.dev, "Un-initialized STA %pM wcid %d in rc_work\n",
++			 sta->addr, msta->wcid.idx);
++		return;
++	}
++
+ 	mt7996_sta_rc_work(&changed, sta);
+ 	ieee80211_queue_work(hw, &dev->rc_work);
+ }
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0020-mtk-wifi-mt76-add-sanity-check-to-prevent-kernel-cra.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0020-mtk-wifi-mt76-add-sanity-check-to-prevent-kernel-cra.patch
new file mode 100644
index 0000000..7030800
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0020-mtk-wifi-mt76-add-sanity-check-to-prevent-kernel-cra.patch
@@ -0,0 +1,38 @@
+From a51ea8a1b4d04afbd26882f35e04c60d7fee5fbd Mon Sep 17 00:00:00 2001
+From: Peter Chiu <chui-hao.chiu@mediatek.com>
+Date: Mon, 30 Oct 2023 11:06:19 +0800
+Subject: [PATCH 020/120] mtk: wifi: mt76: add sanity check to prevent kernel
+ crash
+
+wcid may not be initialized when mac80211 calls mt76.tx and it would lead to
+kernel crash.
+
+CR-Id: WCNCR00240772
+Signed-off-by: Peter Chiu <chui-hao.chiu@mediatek.com>
+Change-Id: I90004271c6e91620c6991195dd332780ce28380e
+---
+ tx.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/tx.c b/tx.c
+index 5cf6edee4..ab42f69b8 100644
+--- a/tx.c
++++ b/tx.c
+@@ -345,6 +345,14 @@ mt76_tx(struct mt76_phy *phy, struct ieee80211_sta *sta,
+ 
+ 	info->hw_queue |= FIELD_PREP(MT_TX_HW_QUEUE_PHY, phy->band_idx);
+ 
++	if (!wcid->tx_pending.prev || !wcid->tx_pending.next) {
++		dev_warn(phy->dev->dev, "Un-initialized STA %pM wcid %d in mt76_tx\n",
++			 sta->addr, wcid->idx);
++
++		ieee80211_free_txskb(phy->hw, skb);
++		return;
++	}
++
+ 	spin_lock_bh(&wcid->tx_pending.lock);
+ 	__skb_queue_tail(&wcid->tx_pending, skb);
+ 	spin_unlock_bh(&wcid->tx_pending.lock);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0021-mtk-wifi-mt76-mt7996-add-firmware-WA-s-coredump.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0021-mtk-wifi-mt76-mt7996-add-firmware-WA-s-coredump.patch
new file mode 100644
index 0000000..11ad2c2
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0021-mtk-wifi-mt76-mt7996-add-firmware-WA-s-coredump.patch
@@ -0,0 +1,598 @@
+From a6148f627d5489982357e7b53153462ece5e552f Mon Sep 17 00:00:00 2001
+From: Bo Jiao <Bo.Jiao@mediatek.com>
+Date: Fri, 19 May 2023 14:16:50 +0800
+Subject: [PATCH 021/120] mtk: wifi: mt76: mt7996: add firmware WA's coredump.
+
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Change-Id: I51f115b4ae15bc0f871f93652570d72511dbf880
+---
+ mt7996/coredump.c | 180 ++++++++++++++++++++++++++++++----------------
+ mt7996/coredump.h |  35 ++++++---
+ mt7996/mac.c      |  31 +++++---
+ mt7996/mcu.c      |   5 ++
+ mt7996/mt7996.h   |   7 +-
+ mt7996/regs.h     |   7 +-
+ 6 files changed, 182 insertions(+), 83 deletions(-)
+
+diff --git a/mt7996/coredump.c b/mt7996/coredump.c
+index ccab0d7b9..60b88085c 100644
+--- a/mt7996/coredump.c
++++ b/mt7996/coredump.c
+@@ -7,11 +7,11 @@
+ #include <linux/utsname.h>
+ #include "coredump.h"
+ 
+-static bool coredump_memdump;
++static bool coredump_memdump = true;
+ module_param(coredump_memdump, bool, 0644);
+ MODULE_PARM_DESC(coredump_memdump, "Optional ability to dump firmware memory");
+ 
+-static const struct mt7996_mem_region mt7996_mem_regions[] = {
++static const struct mt7996_mem_region mt7996_wm_mem_regions[] = {
+ 	{
+ 		.start = 0x00800000,
+ 		.len = 0x0004ffff,
+@@ -44,27 +44,55 @@ static const struct mt7996_mem_region mt7996_mem_regions[] = {
+ 	},
+ };
+ 
++static const struct mt7996_mem_region mt7996_wa_mem_regions[] = {
++	{
++		.start = 0xE0000000,
++		.len = 0x0000ffff,
++		.name = "CRAM",
++	},
++	{
++		.start = 0xE0010000,
++		.len = 0x000117ff,
++		.name = "CRAM2",
++	},
++	{
++		.start = 0x10000000,
++		.len = 0x0001bfff,
++		.name = "ILM",
++	},
++	{
++		.start = 0x10200000,
++		.len = 0x00063fff,
++		.name = "DLM",
++	},
++};
++
+ const struct mt7996_mem_region*
+-mt7996_coredump_get_mem_layout(struct mt7996_dev *dev, u32 *num)
++mt7996_coredump_get_mem_layout(struct mt7996_dev *dev, u8 type, u32 *num)
+ {
+ 	switch (mt76_chip(&dev->mt76)) {
+ 	case 0x7990:
+ 	case 0x7991:
+-		*num = ARRAY_SIZE(mt7996_mem_regions);
+-		return &mt7996_mem_regions[0];
++		if (type == MT7996_RAM_TYPE_WA) {
++			*num = ARRAY_SIZE(mt7996_wa_mem_regions);
++			return &mt7996_wa_mem_regions[0];
++		}
++
++		*num = ARRAY_SIZE(mt7996_wm_mem_regions);
++		return &mt7996_wm_mem_regions[0];
+ 	default:
+ 		return NULL;
+ 	}
+ }
+ 
+-static int mt7996_coredump_get_mem_size(struct mt7996_dev *dev)
++static int mt7996_coredump_get_mem_size(struct mt7996_dev *dev, u8 type)
+ {
+ 	const struct mt7996_mem_region *mem_region;
+ 	size_t size = 0;
+ 	u32 num;
+ 	int i;
+ 
+-	mem_region = mt7996_coredump_get_mem_layout(dev, &num);
++	mem_region = mt7996_coredump_get_mem_layout(dev, type, &num);
+ 	if (!mem_region)
+ 		return 0;
+ 
+@@ -81,14 +109,13 @@ static int mt7996_coredump_get_mem_size(struct mt7996_dev *dev)
+ 	return size;
+ }
+ 
+-struct mt7996_crash_data *mt7996_coredump_new(struct mt7996_dev *dev)
++struct mt7996_crash_data *mt7996_coredump_new(struct mt7996_dev *dev, u8 type)
+ {
+-	struct mt7996_crash_data *crash_data = dev->coredump.crash_data;
++	struct mt7996_crash_data *crash_data = dev->coredump.crash_data[type];
+ 
+ 	lockdep_assert_held(&dev->dump_mutex);
+ 
+-	if (coredump_memdump &&
+-	    !mt76_poll_msec(dev, MT_FW_DUMP_STATE, 0x3, 0x2, 500))
++	if (!coredump_memdump)
+ 		return NULL;
+ 
+ 	guid_gen(&crash_data->guid);
+@@ -98,12 +125,15 @@ struct mt7996_crash_data *mt7996_coredump_new(struct mt7996_dev *dev)
+ }
+ 
+ static void
+-mt7996_coredump_fw_state(struct mt7996_dev *dev, struct mt7996_coredump *dump,
++mt7996_coredump_fw_state(struct mt7996_dev *dev, u8 type, struct mt7996_coredump *dump,
+ 			 bool *exception)
+ {
+-	u32 count;
++	u32 count, reg = MT_FW_WM_DUMP_STATE;
++
++	if (type == MT7996_RAM_TYPE_WA)
++		reg = MT_FW_WA_DUMP_STATE;
+ 
+-	count = mt76_rr(dev, MT_FW_ASSERT_CNT);
++	count = mt76_rr(dev, reg);
+ 
+ 	/* normal mode: driver can manually trigger assert for detail info */
+ 	if (!count)
+@@ -115,53 +145,59 @@ mt7996_coredump_fw_state(struct mt7996_dev *dev, struct mt7996_coredump *dump,
+ }
+ 
+ static void
+-mt7996_coredump_fw_stack(struct mt7996_dev *dev, struct mt7996_coredump *dump,
++mt7996_coredump_fw_stack(struct mt7996_dev *dev, u8 type, struct mt7996_coredump *dump,
+ 			 bool exception)
+ {
+-	u32 oldest, i, idx;
++	u32 reg, i, offset = 0, val = MT7996_RAM_TYPE_WM;
+ 
+-	strscpy(dump->pc_current, "program counter", sizeof(dump->pc_current));
++	if (type == MT7996_RAM_TYPE_WA) {
++		offset = MT_MCU_WA_EXCP_BASE - MT_MCU_WM_EXCP_BASE;
++		val = MT7996_RAM_TYPE_WA;
++	}
+ 
+-	/* 0: WM PC log output */
+-	mt76_wr(dev, MT_CONN_DBG_CTL_OUT_SEL, 0);
++	/* 0: WM PC log output, 1: WA PC log output  */
++	mt76_wr(dev, MT_CONN_DBG_CTL_OUT_SEL, val);
+ 	/* choose 33th PC log buffer to read current PC index */
+ 	mt76_wr(dev, MT_CONN_DBG_CTL_PC_LOG_SEL, 0x3f);
+ 
+ 	/* read current PC */
+-	dump->pc_stack[0] = mt76_rr(dev, MT_CONN_DBG_CTL_PC_LOG);
++	for (i = 0; i < 10; i++)
++		dump->pc_cur[i] = mt76_rr(dev, MT_CONN_DBG_CTL_PC_LOG);
+ 
+ 	/* stop call stack record */
+ 	if (!exception) {
+-		mt76_clear(dev, MT_MCU_WM_EXCP_PC_CTRL, BIT(0));
+-		mt76_clear(dev, MT_MCU_WM_EXCP_LR_CTRL, BIT(0));
++		mt76_clear(dev, MT_MCU_WM_EXCP_PC_CTRL + offset, BIT(0));
++		mt76_clear(dev, MT_MCU_WM_EXCP_LR_CTRL + offset, BIT(0));
+ 	}
+ 
+-	oldest = (u32)mt76_get_field(dev, MT_MCU_WM_EXCP_PC_CTRL,
+-				     GENMASK(20, 16)) + 2;
+-	for (i = 0; i < 16; i++) {
+-		idx = ((oldest + 2 * i + 1) % 32);
+-		dump->pc_stack[i + 1] =
+-			mt76_rr(dev, MT_MCU_WM_EXCP_PC_LOG + idx * 4);
++	/* read PC log */
++	dump->pc_dbg_ctrl = mt76_rr(dev, MT_MCU_WM_EXCP_PC_CTRL + offset);
++	dump->pc_cur_idx = FIELD_GET(MT_MCU_WM_EXCP_PC_CTRL_IDX_STATUS,
++				     dump->pc_dbg_ctrl);
++	for (i = 0; i < 32; i++) {
++		reg = MT_MCU_WM_EXCP_PC_LOG + i * 4 + offset;
++		dump->pc_stack[i] = mt76_rr(dev, reg);
+ 	}
+ 
+-	oldest = (u32)mt76_get_field(dev, MT_MCU_WM_EXCP_LR_CTRL,
+-				     GENMASK(20, 16)) + 2;
+-	for (i = 0; i < 16; i++) {
+-		idx = ((oldest + 2 * i + 1) % 32);
+-		dump->lr_stack[i] =
+-			mt76_rr(dev, MT_MCU_WM_EXCP_LR_LOG + idx * 4);
++	/* read LR log */
++	dump->lr_dbg_ctrl = mt76_rr(dev, MT_MCU_WM_EXCP_LR_CTRL + offset);
++	dump->lr_cur_idx = FIELD_GET(MT_MCU_WM_EXCP_LR_CTRL_IDX_STATUS,
++				     dump->lr_dbg_ctrl);
++	for (i = 0; i < 32; i++) {
++		reg = MT_MCU_WM_EXCP_LR_LOG + i * 4 + offset;
++		dump->lr_stack[i] = mt76_rr(dev, reg);
+ 	}
+ 
+ 	/* start call stack record */
+ 	if (!exception) {
+-		mt76_set(dev, MT_MCU_WM_EXCP_PC_CTRL, BIT(0));
+-		mt76_set(dev, MT_MCU_WM_EXCP_LR_CTRL, BIT(0));
++		mt76_set(dev, MT_MCU_WM_EXCP_PC_CTRL + offset, BIT(0));
++		mt76_set(dev, MT_MCU_WM_EXCP_LR_CTRL + offset, BIT(0));
+ 	}
+ }
+ 
+-static struct mt7996_coredump *mt7996_coredump_build(struct mt7996_dev *dev)
++static struct mt7996_coredump *mt7996_coredump_build(struct mt7996_dev *dev, u8 type)
+ {
+-	struct mt7996_crash_data *crash_data = dev->coredump.crash_data;
++	struct mt7996_crash_data *crash_data = dev->coredump.crash_data[type];
+ 	struct mt7996_coredump *dump;
+ 	struct mt7996_coredump_mem *dump_mem;
+ 	size_t len, sofar = 0, hdr_len = sizeof(*dump);
+@@ -186,20 +222,31 @@ static struct mt7996_coredump *mt7996_coredump_build(struct mt7996_dev *dev)
+ 
+ 	dump = (struct mt7996_coredump *)(buf);
+ 	dump->len = len;
++	dump->hdr_len = hdr_len;
+ 
+ 	/* plain text */
+ 	strscpy(dump->magic, "mt76-crash-dump", sizeof(dump->magic));
+ 	strscpy(dump->kernel, init_utsname()->release, sizeof(dump->kernel));
++	strscpy(dump->fw_type, ((type == MT7996_RAM_TYPE_WA) ? "WA" : "WM"),
++		sizeof(dump->fw_type));
+ 	strscpy(dump->fw_ver, dev->mt76.hw->wiphy->fw_version,
+ 		sizeof(dump->fw_ver));
++	strscpy(dump->fw_patch_date, dev->patch_build_date,
++		sizeof(dump->fw_patch_date));
++	strscpy(dump->fw_ram_date[MT7996_RAM_TYPE_WM],
++		dev->ram_build_date[MT7996_RAM_TYPE_WM],
++		MT7996_BUILD_TIME_LEN);
++	strscpy(dump->fw_ram_date[MT7996_RAM_TYPE_WA],
++		dev->ram_build_date[MT7996_RAM_TYPE_WA],
++		MT7996_BUILD_TIME_LEN);
+ 
+ 	guid_copy(&dump->guid, &crash_data->guid);
+ 	dump->tv_sec = crash_data->timestamp.tv_sec;
+ 	dump->tv_nsec = crash_data->timestamp.tv_nsec;
+ 	dump->device_id = mt76_chip(&dev->mt76);
+ 
+-	mt7996_coredump_fw_state(dev, dump, &exception);
+-	mt7996_coredump_fw_stack(dev, dump, exception);
++	mt7996_coredump_fw_state(dev, type, dump, &exception);
++	mt7996_coredump_fw_stack(dev, type, dump, exception);
+ 
+ 	/* gather memory content */
+ 	dump_mem = (struct mt7996_coredump_mem *)(buf + sofar);
+@@ -213,17 +260,19 @@ static struct mt7996_coredump *mt7996_coredump_build(struct mt7996_dev *dev)
+ 	return dump;
+ }
+ 
+-int mt7996_coredump_submit(struct mt7996_dev *dev)
++int mt7996_coredump_submit(struct mt7996_dev *dev, u8 type)
+ {
+ 	struct mt7996_coredump *dump;
+ 
+-	dump = mt7996_coredump_build(dev);
++	dump = mt7996_coredump_build(dev, type);
+ 	if (!dump) {
+ 		dev_warn(dev->mt76.dev, "no crash dump data found\n");
+ 		return -ENODATA;
+ 	}
+ 
+ 	dev_coredumpv(dev->mt76.dev, dump, dump->len, GFP_KERNEL);
++	dev_info(dev->mt76.dev, "%s coredump completed\n",
++		 wiphy_name(dev->mt76.hw->wiphy));
+ 
+ 	return 0;
+ }
+@@ -231,23 +280,26 @@ int mt7996_coredump_submit(struct mt7996_dev *dev)
+ int mt7996_coredump_register(struct mt7996_dev *dev)
+ {
+ 	struct mt7996_crash_data *crash_data;
++	int i;
+ 
+-	crash_data = vzalloc(sizeof(*dev->coredump.crash_data));
+-	if (!crash_data)
+-		return -ENOMEM;
++	for (i = 0; i < MT7996_COREDUMP_MAX; i++) {
++		crash_data = vzalloc(sizeof(*dev->coredump.crash_data[i]));
++		if (!crash_data)
++			return -ENOMEM;
+ 
+-	dev->coredump.crash_data = crash_data;
++		dev->coredump.crash_data[i] = crash_data;
+ 
+-	if (coredump_memdump) {
+-		crash_data->memdump_buf_len = mt7996_coredump_get_mem_size(dev);
+-		if (!crash_data->memdump_buf_len)
+-			/* no memory content */
+-			return 0;
++		if (coredump_memdump) {
++			crash_data->memdump_buf_len = mt7996_coredump_get_mem_size(dev, i);
++			if (!crash_data->memdump_buf_len)
++				/* no memory content */
++				return 0;
+ 
+-		crash_data->memdump_buf = vzalloc(crash_data->memdump_buf_len);
+-		if (!crash_data->memdump_buf) {
+-			vfree(crash_data);
+-			return -ENOMEM;
++			crash_data->memdump_buf = vzalloc(crash_data->memdump_buf_len);
++			if (!crash_data->memdump_buf) {
++				vfree(crash_data);
++				return -ENOMEM;
++			}
+ 		}
+ 	}
+ 
+@@ -256,13 +308,17 @@ int mt7996_coredump_register(struct mt7996_dev *dev)
+ 
+ void mt7996_coredump_unregister(struct mt7996_dev *dev)
+ {
+-	if (dev->coredump.crash_data->memdump_buf) {
+-		vfree(dev->coredump.crash_data->memdump_buf);
+-		dev->coredump.crash_data->memdump_buf = NULL;
+-		dev->coredump.crash_data->memdump_buf_len = 0;
+-	}
++	int i;
+ 
+-	vfree(dev->coredump.crash_data);
+-	dev->coredump.crash_data = NULL;
++	for (i = 0; i < MT7996_COREDUMP_MAX; i++) {
++		if (dev->coredump.crash_data[i]->memdump_buf) {
++			vfree(dev->coredump.crash_data[i]->memdump_buf);
++			dev->coredump.crash_data[i]->memdump_buf = NULL;
++			dev->coredump.crash_data[i]->memdump_buf_len = 0;
++		}
++
++		vfree(dev->coredump.crash_data[i]);
++		dev->coredump.crash_data[i] = NULL;
++	}
+ }
+ 
+diff --git a/mt7996/coredump.h b/mt7996/coredump.h
+index af2ba219b..01ed3731c 100644
+--- a/mt7996/coredump.h
++++ b/mt7996/coredump.h
+@@ -6,10 +6,13 @@
+ 
+ #include "mt7996.h"
+ 
++#define MT7996_COREDUMP_MAX	(MT7996_RAM_TYPE_WA + 1)
++
+ struct mt7996_coredump {
+ 	char magic[16];
+ 
+ 	u32 len;
++	u32 hdr_len;
+ 
+ 	guid_t guid;
+ 
+@@ -21,17 +24,28 @@ struct mt7996_coredump {
+ 	char kernel[64];
+ 	/* firmware version */
+ 	char fw_ver[ETHTOOL_FWVERS_LEN];
++	char fw_patch_date[MT7996_BUILD_TIME_LEN];
++	char fw_ram_date[MT7996_COREDUMP_MAX][MT7996_BUILD_TIME_LEN];
+ 
+ 	u32 device_id;
+ 
++	/* fw type */
++	char fw_type[8];
++
+ 	/* exception state */
+ 	char fw_state[12];
+ 
+ 	/* program counters */
+-	char pc_current[16];
+-	u32 pc_stack[17];
+-	/* link registers */
+-	u32 lr_stack[16];
++	u32 pc_dbg_ctrl;
++	u32 pc_cur_idx;
++	u32 pc_cur[10];
++	/* PC registers */
++	u32 pc_stack[32];
++
++	u32 lr_dbg_ctrl;
++	u32 lr_cur_idx;
++	/* LR registers */
++	u32 lr_stack[32];
+ 
+ 	/* memory content */
+ 	u8 data[];
+@@ -43,6 +57,7 @@ struct mt7996_coredump_mem {
+ } __packed;
+ 
+ struct mt7996_mem_hdr {
++	char name[64];
+ 	u32 start;
+ 	u32 len;
+ 	u8 data[];
+@@ -58,27 +73,27 @@ struct mt7996_mem_region {
+ #ifdef CONFIG_DEV_COREDUMP
+ 
+ const struct mt7996_mem_region *
+-mt7996_coredump_get_mem_layout(struct mt7996_dev *dev, u32 *num);
+-struct mt7996_crash_data *mt7996_coredump_new(struct mt7996_dev *dev);
+-int mt7996_coredump_submit(struct mt7996_dev *dev);
++mt7996_coredump_get_mem_layout(struct mt7996_dev *dev, u8 type, u32 *num);
++struct mt7996_crash_data *mt7996_coredump_new(struct mt7996_dev *dev, u8 type);
++int mt7996_coredump_submit(struct mt7996_dev *dev, u8 type);
+ int mt7996_coredump_register(struct mt7996_dev *dev);
+ void mt7996_coredump_unregister(struct mt7996_dev *dev);
+ 
+ #else /* CONFIG_DEV_COREDUMP */
+ 
+ static inline const struct mt7996_mem_region *
+-mt7996_coredump_get_mem_layout(struct mt7996_dev *dev, u32 *num)
++mt7996_coredump_get_mem_layout(struct mt7996_dev *dev, u8 type, u32 *num)
+ {
+ 	return NULL;
+ }
+ 
+-static inline int mt7996_coredump_submit(struct mt7996_dev *dev)
++static inline int mt7996_coredump_submit(struct mt7996_dev *dev, u8 type)
+ {
+ 	return 0;
+ }
+ 
+ static inline struct
+-mt7996_crash_data *mt7996_coredump_new(struct mt7996_dev *dev)
++mt7996_crash_data *mt7996_coredump_new(struct mt7996_dev *dev, u8 type)
+ {
+ 	return NULL;
+ }
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 3afdd7eb9..d88bbfb24 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -1998,28 +1998,25 @@ void mt7996_mac_reset_work(struct work_struct *work)
+ }
+ 
+ /* firmware coredump */
+-void mt7996_mac_dump_work(struct work_struct *work)
++void mt7996_mac_fw_coredump(struct mt7996_dev *dev, u8 type)
+ {
+ 	const struct mt7996_mem_region *mem_region;
+ 	struct mt7996_crash_data *crash_data;
+-	struct mt7996_dev *dev;
+ 	struct mt7996_mem_hdr *hdr;
+ 	size_t buf_len;
+ 	int i;
+ 	u32 num;
+ 	u8 *buf;
+ 
+-	dev = container_of(work, struct mt7996_dev, dump_work);
+-
+ 	mutex_lock(&dev->dump_mutex);
+ 
+-	crash_data = mt7996_coredump_new(dev);
++	crash_data = mt7996_coredump_new(dev, type);
+ 	if (!crash_data) {
+ 		mutex_unlock(&dev->dump_mutex);
+-		goto skip_coredump;
++		return;
+ 	}
+ 
+-	mem_region = mt7996_coredump_get_mem_layout(dev, &num);
++	mem_region = mt7996_coredump_get_mem_layout(dev, type, &num);
+ 	if (!mem_region || !crash_data->memdump_buf_len) {
+ 		mutex_unlock(&dev->dump_mutex);
+ 		goto skip_memdump;
+@@ -2029,6 +2026,9 @@ void mt7996_mac_dump_work(struct work_struct *work)
+ 	buf_len = crash_data->memdump_buf_len;
+ 
+ 	/* dumping memory content... */
++	dev_info(dev->mt76.dev, "%s start coredump for %s\n",
++		 wiphy_name(dev->mt76.hw->wiphy),
++		 ((type == MT7996_RAM_TYPE_WA) ? "WA" : "WM"));
+ 	memset(buf, 0, buf_len);
+ 	for (i = 0; i < num; i++) {
+ 		if (mem_region->len > buf_len) {
+@@ -2045,6 +2045,7 @@ void mt7996_mac_dump_work(struct work_struct *work)
+ 		mt7996_memcpy_fromio(dev, buf, mem_region->start,
+ 				     mem_region->len);
+ 
++		strscpy(hdr->name, mem_region->name, sizeof(mem_region->name));
+ 		hdr->start = mem_region->start;
+ 		hdr->len = mem_region->len;
+ 
+@@ -2061,8 +2062,20 @@ void mt7996_mac_dump_work(struct work_struct *work)
+ 	mutex_unlock(&dev->dump_mutex);
+ 
+ skip_memdump:
+-	mt7996_coredump_submit(dev);
+-skip_coredump:
++	mt7996_coredump_submit(dev, type);
++}
++
++void mt7996_mac_dump_work(struct work_struct *work)
++{
++	struct mt7996_dev *dev;
++
++	dev = container_of(work, struct mt7996_dev, dump_work);
++	if (READ_ONCE(dev->recovery.state) & MT_MCU_CMD_WA_WDT)
++		mt7996_mac_fw_coredump(dev, MT7996_RAM_TYPE_WA);
++
++	if (READ_ONCE(dev->recovery.state) & MT_MCU_CMD_WM_WDT)
++		mt7996_mac_fw_coredump(dev, MT7996_RAM_TYPE_WM);
++
+ 	queue_work(dev->mt76.wq, &dev->reset_work);
+ }
+ 
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index c316041d4..b4260d3a7 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -2704,6 +2704,8 @@ static int mt7996_load_patch(struct mt7996_dev *dev)
+ 
+ 	dev_info(dev->mt76.dev, "HW/SW Version: 0x%x, Build Time: %.16s\n",
+ 		 be32_to_cpu(hdr->hw_sw_ver), hdr->build_date);
++	memcpy(dev->patch_build_date, hdr->build_date,
++	       sizeof(dev->patch_build_date));
+ 
+ 	for (i = 0; i < be32_to_cpu(hdr->desc.n_region); i++) {
+ 		struct mt7996_patch_sec *sec;
+@@ -2830,6 +2832,9 @@ static int __mt7996_load_ram(struct mt7996_dev *dev, const char *fw_type,
+ 	}
+ 
+ 	hdr = (const void *)(fw->data + fw->size - sizeof(*hdr));
++	memcpy(dev->ram_build_date[ram_type],
++	       hdr->build_date,
++	       sizeof(dev->ram_build_date[ram_type]));
+ 	dev_info(dev->mt76.dev, "%s Firmware Version: %.10s, Build Time: %.15s\n",
+ 		 fw_type, hdr->fw_ver, hdr->build_date);
+ 
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 32e4d340e..1b2afa543 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -83,6 +83,8 @@
+ #define MT7996_CRIT_TEMP		110
+ #define MT7996_MAX_TEMP			120
+ 
++#define MT7996_BUILD_TIME_LEN		24
++
+ #define MT7996_RRO_MAX_SESSION		1024
+ #define MT7996_RRO_WINDOW_MAX_LEN	1024
+ #define MT7996_RRO_ADDR_ELEM_LEN	128
+@@ -126,6 +128,7 @@ enum mt7996_ram_type {
+ 	MT7996_RAM_TYPE_WM,
+ 	MT7996_RAM_TYPE_WA,
+ 	MT7996_RAM_TYPE_DSP,
++	__MT7996_RAM_TYPE_MAX,
+ };
+ 
+ enum mt7996_txq_id {
+@@ -316,9 +319,11 @@ struct mt7996_dev {
+ 	struct mutex dump_mutex;
+ #ifdef CONFIG_DEV_COREDUMP
+ 	struct {
+-		struct mt7996_crash_data *crash_data;
++		struct mt7996_crash_data *crash_data[__MT7996_RAM_TYPE_MAX];
+ 	} coredump;
+ #endif
++	char patch_build_date[MT7996_BUILD_TIME_LEN];
++	char ram_build_date[__MT7996_RAM_TYPE_MAX][MT7996_BUILD_TIME_LEN];
+ 
+ 	struct list_head sta_rc_list;
+ 	struct list_head twt_list;
+diff --git a/mt7996/regs.h b/mt7996/regs.h
+index cf12c5e02..4c20a67d7 100644
+--- a/mt7996/regs.h
++++ b/mt7996/regs.h
+@@ -597,7 +597,8 @@ enum offs_rev {
+ 
+ /* FW MODE SYNC */
+ #define MT_FW_ASSERT_CNT			0x02208274
+-#define MT_FW_DUMP_STATE			0x02209e90
++#define MT_FW_WM_DUMP_STATE			0x02209e90
++#define MT_FW_WA_DUMP_STATE			0x7C05B080
+ 
+ #define MT_SWDEF_BASE				0x00401400
+ 
+@@ -714,11 +715,15 @@ enum offs_rev {
+ #define MT_WF_PHYRX_CSD_BAND_RXTD12_IRPI_SW_CLR		BIT(29)
+ 
+ /* CONN MCU EXCP CON */
++#define MT_MCU_WA_EXCP_BASE			0x890d0000
+ #define MT_MCU_WM_EXCP_BASE			0x89050000
++
+ #define MT_MCU_WM_EXCP(ofs)			(MT_MCU_WM_EXCP_BASE + (ofs))
+ #define MT_MCU_WM_EXCP_PC_CTRL			MT_MCU_WM_EXCP(0x100)
++#define MT_MCU_WM_EXCP_PC_CTRL_IDX_STATUS	GENMASK(20, 16)
+ #define MT_MCU_WM_EXCP_PC_LOG			MT_MCU_WM_EXCP(0x104)
+ #define MT_MCU_WM_EXCP_LR_CTRL			MT_MCU_WM_EXCP(0x200)
++#define MT_MCU_WM_EXCP_LR_CTRL_IDX_STATUS	GENMASK(20, 16)
+ #define MT_MCU_WM_EXCP_LR_LOG			MT_MCU_WM_EXCP(0x204)
+ 
+ /* CONN AFE CTL CON */
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0022-mtk-wifi-mt76-mt7996-add-preamble-puncture-support-f.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0022-mtk-wifi-mt76-mt7996-add-preamble-puncture-support-f.patch
new file mode 100644
index 0000000..34984cd
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0022-mtk-wifi-mt76-mt7996-add-preamble-puncture-support-f.patch
@@ -0,0 +1,97 @@
+From cece47da10f365e58ba43f1a94dc243a3d38d067 Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Fri, 22 Sep 2023 10:32:37 +0800
+Subject: [PATCH 022/120] mtk: wifi: mt76: mt7996: add preamble puncture
+ support for mt7996
+
+Add support configure preamble puncture feature through mcu commands.
+
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+---
+ mt76_connac_mcu.h |  1 +
+ mt7996/mcu.c      | 30 ++++++++++++++++++++++++++++++
+ mt7996/mcu.h      |  4 ++++
+ mt7996/mt7996.h   |  2 ++
+ 4 files changed, 37 insertions(+)
+
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 67be14d2a..70def0a3b 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1271,6 +1271,7 @@ enum {
+ 	MCU_UNI_CMD_CHANNEL_SWITCH = 0x34,
+ 	MCU_UNI_CMD_THERMAL = 0x35,
+ 	MCU_UNI_CMD_VOW = 0x37,
++	MCU_UNI_CMD_PP = 0x38,
+ 	MCU_UNI_CMD_FIXED_RATE_TABLE = 0x40,
+ 	MCU_UNI_CMD_RRO = 0x57,
+ 	MCU_UNI_CMD_OFFCH_SCAN_CTRL = 0x58,
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index b4260d3a7..d3cc7fbaf 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -4558,3 +4558,33 @@ int mt7996_mcu_cp_support(struct mt7996_dev *dev, u8 mode)
+ 	return mt76_mcu_send_msg(&dev->mt76, MCU_WA_EXT_CMD(CP_SUPPORT),
+ 				 &cp_mode, sizeof(cp_mode), true);
+ }
++
++int mt7996_mcu_set_pp_en(struct mt7996_phy *phy, bool auto_mode,
++			 u8 force_bitmap_ctrl, u16 bitmap)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct {
++		u8 _rsv[4];
++
++		__le16 tag;
++		__le16 len;
++		bool mgmt_mode;
++		u8 band_idx;
++		u8 force_bitmap_ctrl;
++		bool auto_mode;
++		__le16 bitmap;
++		u8 _rsv2[2];
++	} __packed req = {
++		.tag = cpu_to_le16(UNI_CMD_PP_EN_CTRL),
++		.len = cpu_to_le16(sizeof(req) - 4),
++
++		.mgmt_mode = !auto_mode,
++		.band_idx = phy->mt76->band_idx,
++		.force_bitmap_ctrl = force_bitmap_ctrl,
++		.auto_mode = auto_mode,
++		.bitmap = cpu_to_le16(bitmap),
++	};
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(PP),
++				 &req, sizeof(req), false);
++}
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index a9ba63d14..238c4c534 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -923,6 +923,10 @@ enum {
+ 	MT7996_SEC_MODE_MAX,
+ };
+ 
++enum {
++	UNI_CMD_PP_EN_CTRL,
++};
++
+ #define MT7996_PATCH_SEC		GENMASK(31, 24)
+ #define MT7996_PATCH_SCRAMBLE_KEY	GENMASK(15, 8)
+ #define MT7996_PATCH_AES_KEY		GENMASK(7, 0)
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 1b2afa543..34508c4fd 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -655,6 +655,8 @@ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev,
+ 				     struct ieee80211_vif *vif,
+ 				     struct ieee80211_sta *sta);
+ int mt7996_mcu_cp_support(struct mt7996_dev *dev, u8 mode);
++int mt7996_mcu_set_pp_en(struct mt7996_phy *phy, bool auto_mode, u8 force_bitmap,
++			 u16 bitmap);
+ #ifdef CONFIG_MAC80211_DEBUGFS
+ void mt7996_sta_add_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 			    struct ieee80211_sta *sta, struct dentry *dir);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0023-mtk-wifi-mt76-mt7996-for-build-pass.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0023-mtk-wifi-mt76-mt7996-for-build-pass.patch
new file mode 100644
index 0000000..8d96c92
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0023-mtk-wifi-mt76-mt7996-for-build-pass.patch
@@ -0,0 +1,137 @@
+From fc954fed4de0e8c72cff82604531c87f01379852 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Thu, 3 Nov 2022 00:27:17 +0800
+Subject: [PATCH 023/120] mtk: wifi: mt76: mt7996: for build pass
+
+Change-Id: Ieb44c33ee6e6a2e6058c1ef528404c1a1cbcfdaf
+---
+ debugfs.c         | 3 +++
+ dma.c             | 2 +-
+ mcu.c             | 1 +
+ mt7615/mcu.c      | 1 +
+ mt76_connac_mcu.c | 1 +
+ mt7915/mcu.c      | 1 +
+ mt7996/dma.c      | 4 ++--
+ mt7996/eeprom.c   | 1 +
+ mt7996/mcu.c      | 1 +
+ 9 files changed, 12 insertions(+), 3 deletions(-)
+
+diff --git a/debugfs.c b/debugfs.c
+index c4649ba04..ac5207e5e 100644
+--- a/debugfs.c
++++ b/debugfs.c
+@@ -33,8 +33,11 @@ mt76_napi_threaded_set(void *data, u64 val)
+ 	if (!mt76_is_mmio(dev))
+ 		return -EOPNOTSUPP;
+ 
++#if 0
++	/* need to backport patch from networking stack */
+ 	if (dev->napi_dev.threaded != val)
+ 		return dev_set_threaded(&dev->napi_dev, val);
++#endif
+ 
+ 	return 0;
+ }
+diff --git a/dma.c b/dma.c
+index f4f88c444..560446395 100644
+--- a/dma.c
++++ b/dma.c
+@@ -883,7 +883,7 @@ mt76_dma_rx_process(struct mt76_dev *dev, struct mt76_queue *q, int budget)
+ 		    !(dev->drv->rx_check(dev, data, len)))
+ 			goto free_frag;
+ 
+-		skb = napi_build_skb(data, q->buf_size);
++		skb = build_skb(data, q->buf_size);
+ 		if (!skb)
+ 			goto free_frag;
+ 
+diff --git a/mcu.c b/mcu.c
+index a8cafa39a..fa4b05441 100644
+--- a/mcu.c
++++ b/mcu.c
+@@ -4,6 +4,7 @@
+  */
+ 
+ #include "mt76.h"
++#include <linux/moduleparam.h>
+ 
+ struct sk_buff *
+ __mt76_mcu_msg_alloc(struct mt76_dev *dev, const void *data,
+diff --git a/mt7615/mcu.c b/mt7615/mcu.c
+index c807bd8d9..a9310660b 100644
+--- a/mt7615/mcu.c
++++ b/mt7615/mcu.c
+@@ -10,6 +10,7 @@
+ #include "mcu.h"
+ #include "mac.h"
+ #include "eeprom.h"
++#include <linux/moduleparam.h>
+ 
+ static bool prefer_offload_fw = true;
+ module_param(prefer_offload_fw, bool, 0644);
+diff --git a/mt76_connac_mcu.c b/mt76_connac_mcu.c
+index 5b286f4ba..768c4b9a9 100644
+--- a/mt76_connac_mcu.c
++++ b/mt76_connac_mcu.c
+@@ -4,6 +4,7 @@
+ #include <linux/firmware.h>
+ #include "mt76_connac2_mac.h"
+ #include "mt76_connac_mcu.h"
++#include <linux/module.h>
+ 
+ int mt76_connac_mcu_start_firmware(struct mt76_dev *dev, u32 addr, u32 option)
+ {
+diff --git a/mt7915/mcu.c b/mt7915/mcu.c
+index 24daa0835..2d017396c 100644
+--- a/mt7915/mcu.c
++++ b/mt7915/mcu.c
+@@ -6,6 +6,7 @@
+ #include "mcu.h"
+ #include "mac.h"
+ #include "eeprom.h"
++#include <linux/moduleparam.h>
+ 
+ #define fw_name(_dev, name, ...)	({			\
+ 	char *_fw;						\
+diff --git a/mt7996/dma.c b/mt7996/dma.c
+index 73e633d0d..759a58e8e 100644
+--- a/mt7996/dma.c
++++ b/mt7996/dma.c
+@@ -641,8 +641,8 @@ int mt7996_dma_init(struct mt7996_dev *dev)
+ 	if (ret < 0)
+ 		return ret;
+ 
+-	netif_napi_add_tx(&dev->mt76.tx_napi_dev, &dev->mt76.tx_napi,
+-			  mt7996_poll_tx);
++	netif_tx_napi_add(&dev->mt76.tx_napi_dev, &dev->mt76.tx_napi,
++			  mt7996_poll_tx, NAPI_POLL_WEIGHT);
+ 	napi_enable(&dev->mt76.tx_napi);
+ 
+ 	mt7996_dma_enable(dev, false);
+diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
+index 3260d1fef..121a3c958 100644
+--- a/mt7996/eeprom.c
++++ b/mt7996/eeprom.c
+@@ -138,6 +138,7 @@ static int mt7996_eeprom_parse_efuse_hw_cap(struct mt7996_dev *dev)
+ 	if (ret)
+ 		return ret;
+ 
++	cap = 0x4b249248;	/* internal hardcode */
+ 	if (cap) {
+ 		dev->has_eht = !(cap & MODE_HE_ONLY);
+ 		dev->wtbl_size_group = u32_get_bits(cap, WTBL_SIZE_GROUP);
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index d3cc7fbaf..8c80bb231 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -5,6 +5,7 @@
+ 
+ #include <linux/firmware.h>
+ #include <linux/fs.h>
++#include <linux/moduleparam.h>
+ #include "mt7996.h"
+ #include "mcu.h"
+ #include "mac.h"
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0024-mtk-wifi-mt76-mt7996-add-debug-tool.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0024-mtk-wifi-mt76-mt7996-add-debug-tool.patch
new file mode 100644
index 0000000..a03f561
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0024-mtk-wifi-mt76-mt7996-add-debug-tool.patch
@@ -0,0 +1,5245 @@
+From e5955a81f1b6be32ad46e3525140b4adb86181e7 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Fri, 24 Mar 2023 14:02:32 +0800
+Subject: [PATCH 024/120] mtk: wifi: mt76: mt7996: add debug tool
+
+Change-Id: Ie10390b01f17db893dbfbf3221bf63a4bd1fe38f
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+
+Add PSM bit in sta_info
+
+CR-Id: WCNCR00240772
+Signed-off-by: Peter Chiu <chui-hao.chiu@mediatek.com>
+Change-Id: I591b558a9eec2fbd46d166c9bb1580a94e22072c
+
+Remove the duplicate function in mtk_debugfs.c & mtk_debug_i.c
+Only enable mt7996_mcu_fw_log_2_host function in mcu.c
+
+CR-ID: WCNCR00240597
+Signed-off-by: MeiChia Chiu <meichia.chiu@mediatek.com>
+
+Support more ids category NDPA/NDP TXD/FBK and debug log recommended by
+CTD members.
+
+This commit equals to run the follwoing commands on Logan driver:
+command:
+1. iwpriv ra0 set fw_dbg=1:84
+2. iwpriv ra0 set fw_dbg=2:84
+3. iwpriv ra0 set fw_dbg=1:101
+
+CR-Id: WCNCR00261410
+Change-Id: Ifddd4db86982d39f2d39d198b8f5d3e7028983c2
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+
+mtk: wifi: mt76: mt7996: add wtbl_info support for mt7992
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+
+mtk: wifi: mt76: mt7996: add mt7992 & mt7996 CR debug offset revision
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+
+mtk: wifi: mt76: mt7992: refactor code for FW log
+
+Refactor code for FW log.
+
+CR-Id: WCNCR00298425
+Signed-off-by: Benjamin Lin <benjamin-jw.lin@mediatek.com>
+Change-Id: I00c760b31009142848e32b1249d305800585e7fd
+---
+ mt76.h               |    2 +
+ mt7996/Makefile      |    4 +
+ mt7996/coredump.c    |   10 +-
+ mt7996/coredump.h    |    7 +
+ mt7996/debugfs.c     |   64 +-
+ mt7996/mac.c         |    3 +
+ mt7996/mt7996.h      |   11 +
+ mt7996/mtk_debug.h   | 2286 ++++++++++++++++++++++++++++++++++++++
+ mt7996/mtk_debugfs.c | 2484 ++++++++++++++++++++++++++++++++++++++++++
+ mt7996/mtk_mcu.c     |   18 +
+ mt7996/mtk_mcu.h     |   16 +
+ tools/fwlog.c        |   25 +-
+ 12 files changed, 4905 insertions(+), 25 deletions(-)
+ create mode 100644 mt7996/mtk_debug.h
+ create mode 100644 mt7996/mtk_debugfs.c
+ create mode 100644 mt7996/mtk_mcu.c
+ create mode 100644 mt7996/mtk_mcu.h
+
+diff --git a/mt76.h b/mt76.h
+index 294e379a2..8cf21f980 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -394,6 +394,8 @@ struct mt76_txwi_cache {
+ 		struct sk_buff *skb;
+ 		void *ptr;
+ 	};
++
++	unsigned long jiffies;
+ };
+ 
+ struct mt76_rx_tid {
+diff --git a/mt7996/Makefile b/mt7996/Makefile
+index 07c8b555c..a056b40e0 100644
+--- a/mt7996/Makefile
++++ b/mt7996/Makefile
+@@ -1,4 +1,6 @@
+ # SPDX-License-Identifier: ISC
++EXTRA_CFLAGS += -DCONFIG_MT76_LEDS
++EXTRA_CFLAGS += -DCONFIG_MTK_DEBUG
+ 
+ obj-$(CONFIG_MT7996E) += mt7996e.o
+ 
+@@ -6,3 +8,5 @@ mt7996e-y := pci.o init.o dma.o eeprom.o main.o mcu.o mac.o \
+ 	     debugfs.o mmio.o
+ 
+ mt7996e-$(CONFIG_DEV_COREDUMP) += coredump.o
++
++mt7996e-y += mtk_debugfs.o mtk_mcu.o
+diff --git a/mt7996/coredump.c b/mt7996/coredump.c
+index 60b88085c..a7f91b56d 100644
+--- a/mt7996/coredump.c
++++ b/mt7996/coredump.c
+@@ -195,7 +195,7 @@ mt7996_coredump_fw_stack(struct mt7996_dev *dev, u8 type, struct mt7996_coredump
+ 	}
+ }
+ 
+-static struct mt7996_coredump *mt7996_coredump_build(struct mt7996_dev *dev, u8 type)
++struct mt7996_coredump *mt7996_coredump_build(struct mt7996_dev *dev, u8 type, bool full_dump)
+ {
+ 	struct mt7996_crash_data *crash_data = dev->coredump.crash_data[type];
+ 	struct mt7996_coredump *dump;
+@@ -206,7 +206,7 @@ static struct mt7996_coredump *mt7996_coredump_build(struct mt7996_dev *dev, u8
+ 
+ 	len = hdr_len;
+ 
+-	if (coredump_memdump && crash_data->memdump_buf_len)
++	if (full_dump && coredump_memdump && crash_data->memdump_buf_len)
+ 		len += sizeof(*dump_mem) + crash_data->memdump_buf_len;
+ 
+ 	sofar += hdr_len;
+@@ -248,6 +248,9 @@ static struct mt7996_coredump *mt7996_coredump_build(struct mt7996_dev *dev, u8
+ 	mt7996_coredump_fw_state(dev, type, dump, &exception);
+ 	mt7996_coredump_fw_stack(dev, type, dump, exception);
+ 
++	if (!full_dump)
++		goto skip_dump_mem;
++
+ 	/* gather memory content */
+ 	dump_mem = (struct mt7996_coredump_mem *)(buf + sofar);
+ 	dump_mem->len = crash_data->memdump_buf_len;
+@@ -255,6 +258,7 @@ static struct mt7996_coredump *mt7996_coredump_build(struct mt7996_dev *dev, u8
+ 		memcpy(dump_mem->data, crash_data->memdump_buf,
+ 		       crash_data->memdump_buf_len);
+ 
++skip_dump_mem:
+ 	mutex_unlock(&dev->dump_mutex);
+ 
+ 	return dump;
+@@ -264,7 +268,7 @@ int mt7996_coredump_submit(struct mt7996_dev *dev, u8 type)
+ {
+ 	struct mt7996_coredump *dump;
+ 
+-	dump = mt7996_coredump_build(dev, type);
++	dump = mt7996_coredump_build(dev, type, true);
+ 	if (!dump) {
+ 		dev_warn(dev->mt76.dev, "no crash dump data found\n");
+ 		return -ENODATA;
+diff --git a/mt7996/coredump.h b/mt7996/coredump.h
+index 01ed3731c..93cd84a03 100644
+--- a/mt7996/coredump.h
++++ b/mt7996/coredump.h
+@@ -75,6 +75,7 @@ struct mt7996_mem_region {
+ const struct mt7996_mem_region *
+ mt7996_coredump_get_mem_layout(struct mt7996_dev *dev, u8 type, u32 *num);
+ struct mt7996_crash_data *mt7996_coredump_new(struct mt7996_dev *dev, u8 type);
++struct mt7996_coredump *mt7996_coredump_build(struct mt7996_dev *dev, u8 type, bool full_dump);
+ int mt7996_coredump_submit(struct mt7996_dev *dev, u8 type);
+ int mt7996_coredump_register(struct mt7996_dev *dev);
+ void mt7996_coredump_unregister(struct mt7996_dev *dev);
+@@ -92,6 +93,12 @@ static inline int mt7996_coredump_submit(struct mt7996_dev *dev, u8 type)
+ 	return 0;
+ }
+ 
++static inline struct
++mt7996_coredump *mt7996_coredump_build(struct mt7996_dev *dev, u8 type, bool full_dump)
++{
++	return NULL;
++}
++
+ static inline struct
+ mt7996_crash_data *mt7996_coredump_new(struct mt7996_dev *dev, u8 type)
+ {
+diff --git a/mt7996/debugfs.c b/mt7996/debugfs.c
+index 62c03d088..1f5c5b19c 100644
+--- a/mt7996/debugfs.c
++++ b/mt7996/debugfs.c
+@@ -295,11 +295,39 @@ mt7996_fw_debug_wm_set(void *data, u64 val)
+ 		DEBUG_SPL,
+ 		DEBUG_RPT_RX,
+ 		DEBUG_RPT_RA = 68,
+-	} debug;
++		DEBUG_IDS_SND = 84,
++		DEBUG_IDS_PP = 93,
++		DEBUG_IDS_RA = 94,
++		DEBUG_IDS_BF = 95,
++		DEBUG_IDS_SR = 96,
++		DEBUG_IDS_RU = 97,
++		DEBUG_IDS_MUMIMO = 98,
++		DEBUG_IDS_ERR_LOG = 101,
++	};
++	u8 debug_category[] = {
++		DEBUG_TXCMD,
++		DEBUG_CMD_RPT_TX,
++		DEBUG_CMD_RPT_TRIG,
++		DEBUG_SPL,
++		DEBUG_RPT_RX,
++		DEBUG_RPT_RA,
++		DEBUG_IDS_SND,
++		DEBUG_IDS_PP,
++		DEBUG_IDS_RA,
++		DEBUG_IDS_BF,
++		DEBUG_IDS_SR,
++		DEBUG_IDS_RU,
++		DEBUG_IDS_MUMIMO,
++		DEBUG_IDS_ERR_LOG,
++	};
+ 	bool tx, rx, en;
+ 	int ret;
++	u8 i;
+ 
+ 	dev->fw_debug_wm = val ? MCU_FW_LOG_TO_HOST : 0;
++#ifdef CONFIG_MTK_DEBUG
++	dev->fw_debug_wm = val;
++#endif
+ 
+ 	if (dev->fw_debug_bin)
+ 		val = MCU_FW_LOG_RELAY;
+@@ -314,18 +342,21 @@ mt7996_fw_debug_wm_set(void *data, u64 val)
+ 	if (ret)
+ 		return ret;
+ 
+-	for (debug = DEBUG_TXCMD; debug <= DEBUG_RPT_RA; debug++) {
+-		if (debug == 67)
+-			continue;
+-
+-		if (debug == DEBUG_RPT_RX)
++	for (i = 0; i < ARRAY_SIZE(debug_category); i++) {
++		if (debug_category[i] == DEBUG_RPT_RX)
+ 			val = en && rx;
+ 		else
+ 			val = en && tx;
+ 
+-		ret = mt7996_mcu_fw_dbg_ctrl(dev, debug, val);
++		ret = mt7996_mcu_fw_dbg_ctrl(dev, debug_category[i], val);
+ 		if (ret)
+ 			return ret;
++
++		if (debug_category[i] == DEBUG_IDS_SND && en) {
++			ret = mt7996_mcu_fw_dbg_ctrl(dev, debug_category[i], 2);
++			if (ret)
++				return ret;
++		}
+ 	}
+ 
+ 	return 0;
+@@ -406,11 +437,12 @@ mt7996_fw_debug_bin_set(void *data, u64 val)
+ 	};
+ 	struct mt7996_dev *dev = data;
+ 
+-	if (!dev->relay_fwlog)
++	if (!dev->relay_fwlog) {
+ 		dev->relay_fwlog = relay_open("fwlog_data", dev->debugfs_dir,
+ 					      1500, 512, &relay_cb, NULL);
+-	if (!dev->relay_fwlog)
+-		return -ENOMEM;
++		if (!dev->relay_fwlog)
++			return -ENOMEM;
++	}
+ 
+ 	dev->fw_debug_bin = val;
+ 
+@@ -824,6 +856,11 @@ int mt7996_init_debugfs(struct mt7996_phy *phy)
+ 	if (phy == &dev->phy)
+ 		dev->debugfs_dir = dir;
+ 
++#ifdef CONFIG_MTK_DEBUG
++	debugfs_create_u16("wlan_idx", 0600, dir, &dev->wlan_idx);
++	mt7996_mtk_init_debugfs(phy, dir);
++#endif
++
+ 	return 0;
+ }
+ 
+@@ -835,7 +872,11 @@ mt7996_debugfs_write_fwlog(struct mt7996_dev *dev, const void *hdr, int hdrlen,
+ 	unsigned long flags;
+ 	void *dest;
+ 
++	if (!dev->relay_fwlog)
++		return;
++
+ 	spin_lock_irqsave(&lock, flags);
++
+ 	dest = relay_reserve(dev->relay_fwlog, hdrlen + len + 4);
+ 	if (dest) {
+ 		*(u32 *)dest = hdrlen + len;
+@@ -868,9 +909,6 @@ void mt7996_debugfs_rx_fw_monitor(struct mt7996_dev *dev, const void *data, int
+ 		.msg_type = cpu_to_le16(PKT_TYPE_RX_FW_MONITOR),
+ 	};
+ 
+-	if (!dev->relay_fwlog)
+-		return;
+-
+ 	hdr.serial_id = cpu_to_le16(dev->fw_debug_seq++);
+ 	hdr.timestamp = cpu_to_le32(mt76_rr(dev, MT_LPON_FRCR(0)));
+ 	hdr.len = *(__le16 *)data;
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index d88bbfb24..1f53d2303 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -936,6 +936,9 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
+ 	id = mt76_token_consume(mdev, &t);
+ 	if (id < 0)
+ 		return id;
++#ifdef CONFIG_MTK_DEBUG
++	t->jiffies = jiffies;
++#endif
+ 
+ 	pid = mt76_tx_status_skb_add(mdev, wcid, tx_info->skb);
+ 	memset(txwi_ptr, 0, MT_TXD_SIZE);
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 34508c4fd..0396c6306 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -370,6 +370,17 @@ struct mt7996_dev {
+ 	spinlock_t reg_lock;
+ 
+ 	u8 wtbl_size_group;
++
++#ifdef CONFIG_MTK_DEBUG
++	u16 wlan_idx;
++	struct {
++		u8 sku_disable;
++		u32 fw_dbg_module;
++		u8 fw_dbg_lv;
++		u32 bcn_total_cnt[__MT_MAX_BAND];
++	} dbg;
++	const struct mt7996_dbg_reg_desc *dbg_reg;
++#endif
+ };
+ 
+ enum {
+diff --git a/mt7996/mtk_debug.h b/mt7996/mtk_debug.h
+new file mode 100644
+index 000000000..27d8f1cb2
+--- /dev/null
++++ b/mt7996/mtk_debug.h
+@@ -0,0 +1,2286 @@
++#ifndef __MTK_DEBUG_H
++#define __MTK_DEBUG_H
++
++#ifdef CONFIG_MTK_DEBUG
++#define NO_SHIFT_DEFINE 0xFFFFFFFF
++#define BITS(m, n)              (~(BIT(m)-1) & ((BIT(n) - 1) | BIT(n)))
++
++#define GET_FIELD(_field, _reg)	\
++	({	\
++		(((_reg) & (_field##_MASK)) >> (_field##_SHIFT));	\
++	})
++
++#define __DBG_OFFS(id)		(dev->dbg_reg->offs_rev[(id)])
++
++enum dbg_offs_rev {
++	AGG_AALCR2,
++	AGG_AALCR3,
++	AGG_AALCR4,
++	AGG_AALCR5,
++	AGG_AALCR6,
++	AGG_AALCR7,
++	MIB_TDRCR0,
++	MIB_TDRCR1,
++	MIB_TDRCR2,
++	MIB_TDRCR3,
++	MIB_TDRCR4,
++	MIB_RSCR26,
++	MIB_TSCR18,
++	MIB_TRDR0,
++	MIB_TRDR2,
++	MIB_TRDR3,
++	MIB_TRDR4,
++	MIB_TRDR5,
++	MIB_TRDR6,
++	MIB_TRDR7,
++	MIB_TRDR8,
++	MIB_TRDR9,
++	MIB_TRDR10,
++	MIB_TRDR11,
++	MIB_TRDR12,
++	MIB_TRDR13,
++	MIB_TRDR14,
++	MIB_TRDR15,
++	MIB_MSR0,
++	MIB_MSR1,
++	MIB_MSR2,
++	MIB_MCTR5,
++	MIB_MCTR6,
++	__MT_DBG_OFFS_REV_MAX,
++};
++
++static const u32 mt7996_dbg_offs[] = {
++	[AGG_AALCR2]		= 0x128,
++	[AGG_AALCR3]		= 0x12c,
++	[AGG_AALCR4]		= 0x130,
++	[AGG_AALCR5]		= 0x134,
++	[AGG_AALCR6]		= 0x138,
++	[AGG_AALCR7]		= 0x13c,
++	[MIB_TDRCR0]		= 0x728,
++	[MIB_TDRCR1]		= 0x72c,
++	[MIB_TDRCR2]		= 0x730,
++	[MIB_TDRCR3]		= 0x734,
++	[MIB_TDRCR4]		= 0x738,
++	[MIB_RSCR26]		= 0x950,
++	[MIB_TSCR18]		= 0xa1c,
++	[MIB_TRDR0]		= 0xa24,
++	[MIB_TRDR2]		= 0xa2c,
++	[MIB_TRDR3]		= 0xa30,
++	[MIB_TRDR4]		= 0xa34,
++	[MIB_TRDR5]		= 0xa38,
++	[MIB_TRDR6]		= 0xa3c,
++	[MIB_TRDR7]		= 0xa40,
++	[MIB_TRDR8]		= 0xa44,
++	[MIB_TRDR9]		= 0xa48,
++	[MIB_TRDR10]		= 0xa4c,
++	[MIB_TRDR11]		= 0xa50,
++	[MIB_TRDR12]		= 0xa54,
++	[MIB_TRDR13]		= 0xa58,
++	[MIB_TRDR14]		= 0xa5c,
++	[MIB_TRDR15]		= 0xa60,
++	[MIB_MSR0]		= 0xa64,
++	[MIB_MSR1]		= 0xa68,
++	[MIB_MSR2]		= 0xa6c,
++	[MIB_MCTR5]		= 0xa70,
++	[MIB_MCTR6]		= 0xa74,
++};
++
++static const u32 mt7992_dbg_offs[] = {
++	[AGG_AALCR2]		= 0x12c,
++	[AGG_AALCR3]		= 0x130,
++	[AGG_AALCR4]		= 0x134,
++	[AGG_AALCR5]		= 0x138,
++	[AGG_AALCR6]		= 0x13c,
++	[AGG_AALCR7]		= 0x140,
++	[MIB_TDRCR0]		= 0x768,
++	[MIB_TDRCR1]		= 0x76c,
++	[MIB_TDRCR2]		= 0x770,
++	[MIB_TDRCR3]		= 0x774,
++	[MIB_TDRCR4]		= 0x778,
++	[MIB_RSCR26]		= 0x994,
++	[MIB_TSCR18]		= 0xb18,
++	[MIB_TRDR0]		= 0xb20,
++	[MIB_TRDR2]		= 0xb28,
++	[MIB_TRDR3]		= 0xb2c,
++	[MIB_TRDR4]		= 0xb30,
++	[MIB_TRDR5]		= 0xb34,
++	[MIB_TRDR6]		= 0xb38,
++	[MIB_TRDR7]		= 0xb3c,
++	[MIB_TRDR8]		= 0xb40,
++	[MIB_TRDR9]		= 0xb44,
++	[MIB_TRDR10]		= 0xb48,
++	[MIB_TRDR11]		= 0xb4c,
++	[MIB_TRDR12]		= 0xb50,
++	[MIB_TRDR13]		= 0xb54,
++	[MIB_TRDR14]		= 0xb58,
++	[MIB_TRDR15]		= 0xb5c,
++	[MIB_MSR0]		= 0xb60,
++	[MIB_MSR1]		= 0xb64,
++	[MIB_MSR2]		= 0xb68,
++	[MIB_MCTR5]		= 0xb6c,
++	[MIB_MCTR6]		= 0xb70,
++};
++
++/* used to differentiate between generations */
++struct mt7996_dbg_reg_desc {
++	const u32 id;
++	const u32 *offs_rev;
++};
++
++/* AGG */
++#define BN0_WF_AGG_TOP_BASE                                    0x820e2000
++#define BN1_WF_AGG_TOP_BASE                                    0x820f2000
++#define IP1_BN0_WF_AGG_TOP_BASE                                0x830e2000
++
++#define BN0_WF_AGG_TOP_SCR_ADDR                                (BN0_WF_AGG_TOP_BASE + 0x0) // 2000
++#define BN0_WF_AGG_TOP_SCR0_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x4) // 2004
++#define BN0_WF_AGG_TOP_SCR1_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x8) // 2008
++#define BN0_WF_AGG_TOP_BCR_ADDR                                (BN0_WF_AGG_TOP_BASE + 0xc) // 200C
++#define BN0_WF_AGG_TOP_BWCR_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x10) // 2010
++#define BN0_WF_AGG_TOP_ARCR0_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x14) // 2014
++#define BN0_WF_AGG_TOP_ARUCR_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x18) // 2018
++#define BN0_WF_AGG_TOP_ARDCR_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x1c) // 201C
++#define BN0_WF_AGG_TOP_AALCR0_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x20) // 2020
++#define BN0_WF_AGG_TOP_AALCR1_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x24) // 2024
++#define BN0_WF_AGG_TOP_PCR0_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x28) // 2028
++#define BN0_WF_AGG_TOP_PCR1_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x2c) // 202C
++#define BN0_WF_AGG_TOP_TTCR0_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x30) // 2030
++#define BN0_WF_AGG_TOP_TTCR1_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x34) // 2034
++#define BN0_WF_AGG_TOP_ACR1_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x38) // 2038
++#define BN0_WF_AGG_TOP_ACR4_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x3c) // 203C
++#define BN0_WF_AGG_TOP_ACR5_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x40) // 2040
++#define BN0_WF_AGG_TOP_ACR6_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x44) // 2044
++#define BN0_WF_AGG_TOP_ACR8_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x4c) // 204C
++#define BN0_WF_AGG_TOP_MRCR_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x50) // 2050
++#define BN0_WF_AGG_TOP_MMPDR_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x54) // 2054
++#define BN0_WF_AGG_TOP_GFPDR_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x58) // 2058
++#define BN0_WF_AGG_TOP_VHTPDR_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x5c) // 205C
++#define BN0_WF_AGG_TOP_HEPDR_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x60) // 2060
++#define BN0_WF_AGG_TOP_CTCR_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x64) // 2064
++#define BN0_WF_AGG_TOP_ATCR3_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x68) // 2068
++#define BN0_WF_AGG_TOP_SRCR_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x6c) // 206C
++#define BN0_WF_AGG_TOP_VBCR_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x70) // 2070
++#define BN0_WF_AGG_TOP_TCR_ADDR                                (BN0_WF_AGG_TOP_BASE + 0x74) // 2074
++#define BN0_WF_AGG_TOP_SRHS_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x78) // 2078
++#define BN0_WF_AGG_TOP_DBRCR0_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x7c) // 207C
++#define BN0_WF_AGG_TOP_DBRCR1_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x80) // 2080
++#define BN0_WF_AGG_TOP_CTETCR_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x84) // 2084
++#define BN0_WF_AGG_TOP_WPDR_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x88) // 2088
++#define BN0_WF_AGG_TOP_PLRPDR_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x8c) // 208C
++#define BN0_WF_AGG_TOP_CECR_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x90) // 2090
++#define BN0_WF_AGG_TOP_OMRCR0_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x94) // 2094
++#define BN0_WF_AGG_TOP_OMRCR1_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x98) // 2098
++#define BN0_WF_AGG_TOP_OMRCR2_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x9c) // 209C
++#define BN0_WF_AGG_TOP_OMRCR3_ADDR                             (BN0_WF_AGG_TOP_BASE + 0xa0) // 20A0
++#define BN0_WF_AGG_TOP_TMCR_ADDR                               (BN0_WF_AGG_TOP_BASE + 0xa4) // 20A4
++#define BN0_WF_AGG_TOP_TWTCR_ADDR                              (BN0_WF_AGG_TOP_BASE + 0xa8) // 20A8
++#define BN0_WF_AGG_TOP_TWTSTACR_ADDR                           (BN0_WF_AGG_TOP_BASE + 0xac) // 20AC
++#define BN0_WF_AGG_TOP_TWTE0TB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xb0) // 20B0
++#define BN0_WF_AGG_TOP_TWTE1TB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xb4) // 20B4
++#define BN0_WF_AGG_TOP_TWTE2TB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xb8) // 20B8
++#define BN0_WF_AGG_TOP_TWTE3TB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xbc) // 20BC
++#define BN0_WF_AGG_TOP_TWTE4TB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xc0) // 20C0
++#define BN0_WF_AGG_TOP_TWTE5TB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xc4) // 20C4
++#define BN0_WF_AGG_TOP_TWTE6TB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xc8) // 20C8
++#define BN0_WF_AGG_TOP_TWTE7TB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xcc) // 20CC
++#define BN0_WF_AGG_TOP_TWTE8TB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xd0) // 20D0
++#define BN0_WF_AGG_TOP_TWTE9TB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xd4) // 20D4
++#define BN0_WF_AGG_TOP_TWTEATB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xd8) // 20D8
++#define BN0_WF_AGG_TOP_TWTEBTB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xdc) // 20DC
++#define BN0_WF_AGG_TOP_TWTECTB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xe0) // 20E0
++#define BN0_WF_AGG_TOP_TWTEDTB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xe4) // 20E4
++#define BN0_WF_AGG_TOP_TWTEETB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xe8) // 20E8
++#define BN0_WF_AGG_TOP_TWTEFTB_ADDR                            (BN0_WF_AGG_TOP_BASE + 0xec) // 20EC
++#define BN0_WF_AGG_TOP_ATCR0_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x108) // 2108
++#define BN0_WF_AGG_TOP_ATCR1_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x10c) // 210C
++#define BN0_WF_AGG_TOP_TCCR_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x110) // 2110
++#define BN0_WF_AGG_TOP_TFCR_ADDR                               (BN0_WF_AGG_TOP_BASE + 0x114) // 2114
++#define BN0_WF_AGG_TOP_MUCR0_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x118) // 2118
++#define BN0_WF_AGG_TOP_MUCR1_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x11c) // 211C
++#define BN0_WF_AGG_TOP_AALCR2_ADDR                             (BN0_WF_AGG_TOP_BASE + __DBG_OFFS(AGG_AALCR2))
++#define BN0_WF_AGG_TOP_AALCR3_ADDR                             (BN0_WF_AGG_TOP_BASE + __DBG_OFFS(AGG_AALCR3))
++#define BN0_WF_AGG_TOP_AALCR4_ADDR                             (BN0_WF_AGG_TOP_BASE + __DBG_OFFS(AGG_AALCR4))
++#define BN0_WF_AGG_TOP_AALCR5_ADDR                             (BN0_WF_AGG_TOP_BASE + __DBG_OFFS(AGG_AALCR5))
++#define BN0_WF_AGG_TOP_AALCR6_ADDR                             (BN0_WF_AGG_TOP_BASE + __DBG_OFFS(AGG_AALCR6))
++#define BN0_WF_AGG_TOP_AALCR7_ADDR                             (BN0_WF_AGG_TOP_BASE + __DBG_OFFS(AGG_AALCR7))
++#define BN0_WF_AGG_TOP_CSDCR0_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x150) // 2150
++#define BN0_WF_AGG_TOP_CSDCR1_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x154) // 2154
++#define BN0_WF_AGG_TOP_CSDCR2_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x158) // 2158
++#define BN0_WF_AGG_TOP_CSDCR3_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x15c) // 215C
++#define BN0_WF_AGG_TOP_CSDCR4_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x160) // 2160
++#define BN0_WF_AGG_TOP_DYNSCR_ADDR                             (BN0_WF_AGG_TOP_BASE + 0x178) // 2178
++#define BN0_WF_AGG_TOP_DYNSSCR_ADDR                            (BN0_WF_AGG_TOP_BASE + 0x198) // 2198
++#define BN0_WF_AGG_TOP_TCDCNT0_ADDR                            (BN0_WF_AGG_TOP_BASE + 0x2c8) // 22C8
++#define BN0_WF_AGG_TOP_TCDCNT1_ADDR                            (BN0_WF_AGG_TOP_BASE + 0x2cc) // 22CC
++#define BN0_WF_AGG_TOP_TCSR0_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x2d0) // 22D0
++#define BN0_WF_AGG_TOP_TCSR1_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x2d4) // 22D4
++#define BN0_WF_AGG_TOP_TCSR2_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x2d8) // 22D8
++#define BN0_WF_AGG_TOP_DCR_ADDR                                (BN0_WF_AGG_TOP_BASE + 0x2e4) // 22E4
++#define BN0_WF_AGG_TOP_SMDCR_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x2e8) // 22E8
++#define BN0_WF_AGG_TOP_TXCMDSMCR_ADDR                          (BN0_WF_AGG_TOP_BASE + 0x2ec) // 22EC
++#define BN0_WF_AGG_TOP_SMCR0_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x2f0) // 22F0
++#define BN0_WF_AGG_TOP_SMCR1_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x2f4) // 22F4
++#define BN0_WF_AGG_TOP_SMCR2_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x2f8) // 22F8
++#define BN0_WF_AGG_TOP_SMCR3_ADDR                              (BN0_WF_AGG_TOP_BASE + 0x2fc) // 22FC
++
++#define BN0_WF_AGG_TOP_AALCR0_AC01_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR0_ADDR
++#define BN0_WF_AGG_TOP_AALCR0_AC01_AGG_LIMIT_MASK              0x03FF0000                // AC01_AGG_LIMIT[25..16]
++#define BN0_WF_AGG_TOP_AALCR0_AC01_AGG_LIMIT_SHFT              16
++#define BN0_WF_AGG_TOP_AALCR0_AC00_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR0_ADDR
++#define BN0_WF_AGG_TOP_AALCR0_AC00_AGG_LIMIT_MASK              0x000003FF                // AC00_AGG_LIMIT[9..0]
++#define BN0_WF_AGG_TOP_AALCR0_AC00_AGG_LIMIT_SHFT              0
++
++#define BN0_WF_AGG_TOP_AALCR1_AC03_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR1_ADDR
++#define BN0_WF_AGG_TOP_AALCR1_AC03_AGG_LIMIT_MASK              0x03FF0000                // AC03_AGG_LIMIT[25..16]
++#define BN0_WF_AGG_TOP_AALCR1_AC03_AGG_LIMIT_SHFT              16
++#define BN0_WF_AGG_TOP_AALCR1_AC02_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR1_ADDR
++#define BN0_WF_AGG_TOP_AALCR1_AC02_AGG_LIMIT_MASK              0x000003FF                // AC02_AGG_LIMIT[9..0]
++#define BN0_WF_AGG_TOP_AALCR1_AC02_AGG_LIMIT_SHFT              0
++
++#define BN0_WF_AGG_TOP_AALCR2_AC11_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR2_ADDR
++#define BN0_WF_AGG_TOP_AALCR2_AC11_AGG_LIMIT_MASK              0x03FF0000                // AC11_AGG_LIMIT[25..16]
++#define BN0_WF_AGG_TOP_AALCR2_AC11_AGG_LIMIT_SHFT              16
++#define BN0_WF_AGG_TOP_AALCR2_AC10_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR2_ADDR
++#define BN0_WF_AGG_TOP_AALCR2_AC10_AGG_LIMIT_MASK              0x000003FF                // AC10_AGG_LIMIT[9..0]
++#define BN0_WF_AGG_TOP_AALCR2_AC10_AGG_LIMIT_SHFT              0
++
++#define BN0_WF_AGG_TOP_AALCR3_AC13_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR3_ADDR
++#define BN0_WF_AGG_TOP_AALCR3_AC13_AGG_LIMIT_MASK              0x03FF0000                // AC13_AGG_LIMIT[25..16]
++#define BN0_WF_AGG_TOP_AALCR3_AC13_AGG_LIMIT_SHFT              16
++#define BN0_WF_AGG_TOP_AALCR3_AC12_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR3_ADDR
++#define BN0_WF_AGG_TOP_AALCR3_AC12_AGG_LIMIT_MASK              0x000003FF                // AC12_AGG_LIMIT[9..0]
++#define BN0_WF_AGG_TOP_AALCR3_AC12_AGG_LIMIT_SHFT              0
++
++#define BN0_WF_AGG_TOP_AALCR4_AC21_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR4_ADDR
++#define BN0_WF_AGG_TOP_AALCR4_AC21_AGG_LIMIT_MASK              0x03FF0000                // AC21_AGG_LIMIT[25..16]
++#define BN0_WF_AGG_TOP_AALCR4_AC21_AGG_LIMIT_SHFT              16
++#define BN0_WF_AGG_TOP_AALCR4_AC20_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR4_ADDR
++#define BN0_WF_AGG_TOP_AALCR4_AC20_AGG_LIMIT_MASK              0x000003FF                // AC20_AGG_LIMIT[9..0]
++#define BN0_WF_AGG_TOP_AALCR4_AC20_AGG_LIMIT_SHFT              0
++
++#define BN0_WF_AGG_TOP_AALCR5_AC23_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR5_ADDR
++#define BN0_WF_AGG_TOP_AALCR5_AC23_AGG_LIMIT_MASK              0x03FF0000                // AC23_AGG_LIMIT[25..16]
++#define BN0_WF_AGG_TOP_AALCR5_AC23_AGG_LIMIT_SHFT              16
++#define BN0_WF_AGG_TOP_AALCR5_AC22_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR5_ADDR
++#define BN0_WF_AGG_TOP_AALCR5_AC22_AGG_LIMIT_MASK              0x000003FF                // AC22_AGG_LIMIT[9..0]
++#define BN0_WF_AGG_TOP_AALCR5_AC22_AGG_LIMIT_SHFT              0
++
++#define BN0_WF_AGG_TOP_AALCR6_AC31_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR6_ADDR
++#define BN0_WF_AGG_TOP_AALCR6_AC31_AGG_LIMIT_MASK              0x03FF0000                // AC31_AGG_LIMIT[25..16]
++#define BN0_WF_AGG_TOP_AALCR6_AC31_AGG_LIMIT_SHFT              16
++#define BN0_WF_AGG_TOP_AALCR6_AC30_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR6_ADDR
++#define BN0_WF_AGG_TOP_AALCR6_AC30_AGG_LIMIT_MASK              0x000003FF                // AC30_AGG_LIMIT[9..0]
++#define BN0_WF_AGG_TOP_AALCR6_AC30_AGG_LIMIT_SHFT              0
++#define BN0_WF_AGG_TOP_AALCR7_AC33_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR7_ADDR
++#define BN0_WF_AGG_TOP_AALCR7_AC33_AGG_LIMIT_MASK              0x03FF0000                // AC33_AGG_LIMIT[25..16]
++#define BN0_WF_AGG_TOP_AALCR7_AC33_AGG_LIMIT_SHFT              16
++#define BN0_WF_AGG_TOP_AALCR7_AC32_AGG_LIMIT_ADDR              BN0_WF_AGG_TOP_AALCR7_ADDR
++#define BN0_WF_AGG_TOP_AALCR7_AC32_AGG_LIMIT_MASK              0x000003FF                // AC32_AGG_LIMIT[9..0]
++#define BN0_WF_AGG_TOP_AALCR7_AC32_AGG_LIMIT_SHFT              0
++
++/* DMA */
++struct queue_desc {
++	u32 hw_desc_base;
++	u16 ring_size;
++	char *const ring_info;
++};
++
++// HOST DMA
++#define WF_WFDMA_HOST_DMA0_BASE                                0xd4000
++
++#define WF_WFDMA_HOST_DMA0_HOST_INT_STA_ADDR                                   \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x200) /* 4200 */
++#define WF_WFDMA_HOST_DMA0_HOST_INT_ENA_ADDR                                   \
++	(WF_WFDMA_HOST_DMA0_BASE + 0X204) /* 4204 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_ADDR                                  \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x208) /* 4208 */
++
++#define WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_RX_DMA_BUSY_ADDR                      \
++	WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_ADDR
++#define WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_RX_DMA_BUSY_MASK                      \
++	0x00000008 /* RX_DMA_BUSY[3] */
++#define WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_RX_DMA_BUSY_SHFT 3
++#define WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_RX_DMA_EN_ADDR                        \
++	WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_ADDR
++#define WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_RX_DMA_EN_MASK                        \
++	0x00000004 /* RX_DMA_EN[2] */
++#define WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_RX_DMA_EN_SHFT 2
++#define WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_TX_DMA_BUSY_ADDR                      \
++	WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_ADDR
++#define WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_TX_DMA_BUSY_MASK                      \
++	0x00000002 /* TX_DMA_BUSY[1] */
++#define WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_TX_DMA_BUSY_SHFT 1
++#define WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_TX_DMA_EN_ADDR                        \
++	WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_ADDR
++#define WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_TX_DMA_EN_MASK                        \
++	0x00000001 /* TX_DMA_EN[0] */
++#define WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_TX_DMA_EN_SHFT 0
++
++
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING0_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x300) /* 4300 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING0_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x304) /* 4304 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING0_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x308) /* 4308 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING0_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x30c) /* 430C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING1_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x310) /* 4310 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING1_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x314) /* 4314 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING1_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x318) /* 4318 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING1_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x31c) /* 431C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING2_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x320) /* 4320 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING2_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x324) /* 4324 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING2_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x328) /* 4328 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING2_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x32c) /* 432C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING3_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x330) /* 4330 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING3_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x334) /* 4334 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING3_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x338) /* 4338 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING3_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x33c) /* 433C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING4_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x340) /* 4340 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING4_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x344) /* 4344 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING4_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x348) /* 4348 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING4_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x34c) /* 434C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING5_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x350) /* 4350 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING5_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x354) /* 4354 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING5_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x358) /* 4358 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING5_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x35c) /* 435C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING6_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x360) /* 4360 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING6_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x364) /* 4364 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING6_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x368) /* 4368 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING6_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x36c) /* 436C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING16_CTRL0_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x400) /* 4400 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING16_CTRL1_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x404) /* 4404 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING16_CTRL2_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x408) /* 4408 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING16_CTRL3_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x40c) /* 440C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING17_CTRL0_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x410) /* 4410 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING17_CTRL1_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x414) /* 4414 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING17_CTRL2_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x418) /* 4418 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING17_CTRL3_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x41c) /* 441C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING18_CTRL0_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x420) /* 4420 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING18_CTRL1_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x424) /* 4424 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING18_CTRL2_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x428) /* 4428 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING18_CTRL3_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x42c) /* 442C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING19_CTRL0_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x430) /* 4430 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING19_CTRL1_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x434) /* 4434 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING19_CTRL2_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x438) /* 4438 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING19_CTRL3_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x43c) /* 443C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING20_CTRL0_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x440) /* 4440 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING20_CTRL1_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x444) /* 4444 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING20_CTRL2_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x448) /* 4448 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING20_CTRL3_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x44c) /* 444C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING21_CTRL0_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x450) /* 4450 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING21_CTRL1_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x454) /* 4454 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING21_CTRL2_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x458) /* 4458 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING21_CTRL3_ADDR                          \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x45c) /* 445c */
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING22_CTRL0_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x460) // 4460
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING22_CTRL1_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x464) // 4464
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING22_CTRL2_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x468) // 4468
++#define WF_WFDMA_HOST_DMA0_WPDMA_TX_RING22_CTRL3_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x46c) // 446C
++
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING0_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x500) /* 4500 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING0_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x504) /* 4504 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING0_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x508) /* 4508 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING0_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x50c) /* 450C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING1_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x510) /* 4510 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING1_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x514) /* 4514 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING1_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x518) /* 4518 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING1_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x51c) /* 451C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING2_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x520) /* 4520 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING2_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x524) /* 4524 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING2_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x528) /* 4528 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING2_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x52C) /* 452C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING3_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x530) /* 4530 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING3_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x534) /* 4534 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING3_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x538) /* 4538 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING3_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x53C) /* 453C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING4_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x540) /* 4540 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING4_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x544) /* 4544 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING4_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x548) /* 4548 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING4_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x54c) /* 454C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING5_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x550) /* 4550 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING5_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x554) /* 4554 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING5_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x558) /* 4558 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING5_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x55c) /* 455C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING6_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x560) /* 4560 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING6_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x564) /* 4564 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING6_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x568) /* 4568 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING6_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x56c) /* 456C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING7_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x570) /* 4570 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING7_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x574) /* 4574 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING7_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x578) /* 4578 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING7_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x57c) /* 457C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING8_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x580) /* 4580 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING8_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x584) /* 4584 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING8_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x588) /* 4588 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING8_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x58c) /* 458C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING9_CTRL0_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x590) /* 4590 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING9_CTRL1_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x594) /* 4594 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING9_CTRL2_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x598) /* 4598 */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING9_CTRL3_ADDR                           \
++	(WF_WFDMA_HOST_DMA0_BASE + 0x59c) /* 459C */
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING10_CTRL0_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x5a0) // 45A0
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING10_CTRL1_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x5a4) // 45A4
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING10_CTRL2_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x5a8) // 45A8
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING10_CTRL3_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x5ac) // 45AC
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING11_CTRL0_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x5b0) // 45B0
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING11_CTRL1_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x5b4) // 45B4
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING11_CTRL2_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x5b8) // 45B8
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING11_CTRL3_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x5bc) // 45BC
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING12_CTRL0_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x5C0) // 45C0
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING12_CTRL1_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x5C4) // 45C4
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING12_CTRL2_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x5C8) // 45C8
++#define WF_WFDMA_HOST_DMA0_WPDMA_RX_RING12_CTRL3_ADDR          (WF_WFDMA_HOST_DMA0_BASE + 0x5CC) // 45CC
++
++// HOST PCIE1 DMA
++#define WF_WFDMA_HOST_DMA0_PCIE1_BASE				0xd8000
++
++#define WF_WFDMA_HOST_DMA0_PCIE1_HOST_INT_STA_ADDR		(WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x200) // 8200
++#define WF_WFDMA_HOST_DMA0_PCIE1_HOST_INT_ENA_ADDR		(WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0X204) // 8204
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_ADDR		(WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x208) // 8208
++
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_PDMA_BT_SIZE_SHFT	4
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_RX_DMA_BUSY_MASK		0x00000008
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_RX_DMA_BUSY_SHFT		3
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_RX_DMA_EN_MASK		0x00000004
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_RX_DMA_EN_SHFT		2
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_TX_DMA_BUSY_MASK		0x00000002
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_TX_DMA_BUSY_SHFT		1
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_TX_DMA_EN_MASK		0x00000001
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_TX_DMA_EN_SHFT		0
++
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_TX_RING21_CTRL0_ADDR    (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x450) // 8450
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_TX_RING21_CTRL1_ADDR    (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x454) // 8454
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_TX_RING21_CTRL2_ADDR    (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x458) // 8458
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_TX_RING21_CTRL3_ADDR    (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x45c) // 845C
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_TX_RING22_CTRL0_ADDR    (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x460) // 8460
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_TX_RING22_CTRL1_ADDR    (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x464) // 8464
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_TX_RING22_CTRL2_ADDR    (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x468) // 8468
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_TX_RING22_CTRL3_ADDR    (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x46c) // 846C
++
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING3_CTRL0_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x530) // 8530
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING3_CTRL1_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x534) // 8534
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING3_CTRL2_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x538) // 8538
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING3_CTRL3_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x53C) // 853C
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING5_CTRL0_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x550) // 8550
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING5_CTRL1_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x554) // 8554
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING5_CTRL2_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x558) // 8558
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING5_CTRL3_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x55c) // 855C
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING6_CTRL0_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x560) // 8560
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING6_CTRL1_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x564) // 8564
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING6_CTRL2_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x568) // 8568
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING6_CTRL3_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x56c) // 856C
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING7_CTRL0_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x570) // 8570
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING7_CTRL1_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x574) // 8574
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING7_CTRL2_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x578) // 8578
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING7_CTRL3_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x57c) // 857C
++//MCU DMA
++//#define WF_WFDMA_MCU_DMA0_BASE                                 0x02000
++#define WF_WFDMA_MCU_DMA0_BASE                                 0x54000000
++
++#define WF_WFDMA_MCU_DMA0_HOST_INT_STA_ADDR                    (WF_WFDMA_MCU_DMA0_BASE + 0x200) // 0200
++#define WF_WFDMA_MCU_DMA0_HOST_INT_ENA_ADDR                    (WF_WFDMA_MCU_DMA0_BASE + 0X204) // 0204
++#define WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_ADDR                   (WF_WFDMA_MCU_DMA0_BASE + 0x208) // 0208
++
++#define WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_RX_DMA_BUSY_ADDR       WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_ADDR
++#define WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_RX_DMA_BUSY_MASK       0x00000008                // RX_DMA_BUSY[3]
++#define WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_RX_DMA_BUSY_SHFT       3
++#define WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_RX_DMA_EN_ADDR         WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_ADDR
++#define WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_RX_DMA_EN_MASK         0x00000004                // RX_DMA_EN[2]
++#define WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_RX_DMA_EN_SHFT         2
++#define WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_TX_DMA_BUSY_ADDR       WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_ADDR
++#define WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_TX_DMA_BUSY_MASK       0x00000002                // TX_DMA_BUSY[1]
++#define WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_TX_DMA_BUSY_SHFT       1
++#define WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_TX_DMA_EN_ADDR         WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_ADDR
++#define WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_TX_DMA_EN_MASK         0x00000001                // TX_DMA_EN[0]
++#define WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_TX_DMA_EN_SHFT         0
++
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING0_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x300) // 0300
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING0_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x304) // 0304
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING0_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x308) // 0308
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING0_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x30c) // 030C
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING1_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x310) // 0310
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING1_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x314) // 0314
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING1_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x318) // 0318
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING1_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x31c) // 031C
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING2_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x320) // 0320
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING2_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x324) // 0324
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING2_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x328) // 0328
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING2_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x32c) // 032C
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING3_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x330) // 0330
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING3_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x334) // 0334
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING3_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x338) // 0338
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING3_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x33c) // 033C
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING4_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x340) // 0340
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING4_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x344) // 0344
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING4_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x348) // 0348
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING4_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x34c) // 034C
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING5_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x350) // 0350
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING5_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x354) // 0354
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING5_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x358) // 0358
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING5_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x35c) // 035C
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING6_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x360) // 0360
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING6_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x364) // 0364
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING6_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x368) // 0368
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING6_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x36c) // 036C
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING7_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x370) // 0370
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING7_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x374) // 0374
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING7_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x378) // 0378
++#define WF_WFDMA_MCU_DMA0_WPDMA_TX_RING7_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x37c) // 037C
++
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING0_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x500) // 0500
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING0_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x504) // 0504
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING0_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x508) // 0508
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING0_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x50c) // 050C
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING1_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x510) // 0510
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING1_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x514) // 0514
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING1_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x518) // 0518
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING1_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x51c) // 051C
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING2_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x520) // 0520
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING2_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x524) // 0524
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING2_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x528) // 0528
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING2_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x52C) // 052C
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING3_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x530) // 0530
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING3_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x534) // 0534
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING3_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x538) // 0538
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING3_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x53C) // 053C
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING4_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x540) // 0540
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING4_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x544) // 0544
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING4_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x548) // 0548
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING4_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x54C) // 054C
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING5_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x550) // 0550
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING5_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x554) // 0554
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING5_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x558) // 0558
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING5_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x55C) // 055C
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING6_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x560) // 0560
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING6_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x564) // 0564
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING6_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x568) // 0568
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING6_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x56c) // 056C
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING7_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x570) // 0570
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING7_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x574) // 0574
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING7_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x578) // 0578
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING7_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x57c) // 057C
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING8_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x580) // 0580
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING8_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x584) // 0584
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING8_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x588) // 0588
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING8_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x58c) // 058C
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING9_CTRL0_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x590) // 0590
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING9_CTRL1_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x594) // 0594
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING9_CTRL2_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x598) // 0598
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING9_CTRL3_ADDR            (WF_WFDMA_MCU_DMA0_BASE + 0x59c) // 059C
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING10_CTRL0_ADDR           (WF_WFDMA_MCU_DMA0_BASE + 0x5A0) // 05A0
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING10_CTRL1_ADDR           (WF_WFDMA_MCU_DMA0_BASE + 0x5A4) // 05A4
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING10_CTRL2_ADDR           (WF_WFDMA_MCU_DMA0_BASE + 0x5A8) // 05A8
++#define WF_WFDMA_MCU_DMA0_WPDMA_RX_RING10_CTRL3_ADDR           (WF_WFDMA_MCU_DMA0_BASE + 0x5Ac) // 05AC
++
++// MEM DMA
++#define WF_WFDMA_MEM_DMA_BASE                                  0x58000000
++
++#define WF_WFDMA_MEM_DMA_HOST_INT_STA_ADDR                     (WF_WFDMA_MEM_DMA_BASE + 0x200) // 0200
++#define WF_WFDMA_MEM_DMA_HOST_INT_ENA_ADDR                     (WF_WFDMA_MEM_DMA_BASE + 0X204) // 0204
++#define WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_ADDR                    (WF_WFDMA_MEM_DMA_BASE + 0x208) // 0208
++
++#define WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_RX_DMA_BUSY_ADDR        WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_ADDR
++#define WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_RX_DMA_BUSY_MASK        0x00000008                // RX_DMA_BUSY[3]
++#define WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_RX_DMA_BUSY_SHFT        3
++#define WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_RX_DMA_EN_ADDR          WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_ADDR
++#define WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_RX_DMA_EN_MASK          0x00000004                // RX_DMA_EN[2]
++#define WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_RX_DMA_EN_SHFT          2
++#define WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_TX_DMA_BUSY_ADDR        WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_ADDR
++#define WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_TX_DMA_BUSY_MASK        0x00000002                // TX_DMA_BUSY[1]
++#define WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_TX_DMA_BUSY_SHFT        1
++#define WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_TX_DMA_EN_ADDR          WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_ADDR
++#define WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_TX_DMA_EN_MASK          0x00000001                // TX_DMA_EN[0]
++#define WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_TX_DMA_EN_SHFT          0
++
++#define WF_WFDMA_MEM_DMA_WPDMA_TX_RING0_CTRL0_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x300) // 0300
++#define WF_WFDMA_MEM_DMA_WPDMA_TX_RING0_CTRL1_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x304) // 0304
++#define WF_WFDMA_MEM_DMA_WPDMA_TX_RING0_CTRL2_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x308) // 0308
++#define WF_WFDMA_MEM_DMA_WPDMA_TX_RING0_CTRL3_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x30c) // 030C
++#define WF_WFDMA_MEM_DMA_WPDMA_TX_RING1_CTRL0_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x310) // 0310
++#define WF_WFDMA_MEM_DMA_WPDMA_TX_RING1_CTRL1_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x314) // 0314
++#define WF_WFDMA_MEM_DMA_WPDMA_TX_RING1_CTRL2_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x318) // 0318
++#define WF_WFDMA_MEM_DMA_WPDMA_TX_RING1_CTRL3_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x31c) // 031C
++#define WF_WFDMA_MEM_DMA_WPDMA_TX_RING2_CTRL0_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x320) // 0320
++#define WF_WFDMA_MEM_DMA_WPDMA_TX_RING2_CTRL1_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x324) // 0324
++#define WF_WFDMA_MEM_DMA_WPDMA_TX_RING2_CTRL2_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x328) // 0328
++#define WF_WFDMA_MEM_DMA_WPDMA_TX_RING2_CTRL3_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x32c) // 032C
++
++#define WF_WFDMA_MEM_DMA_WPDMA_RX_RING0_CTRL0_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x500) // 0500
++#define WF_WFDMA_MEM_DMA_WPDMA_RX_RING0_CTRL1_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x504) // 0504
++#define WF_WFDMA_MEM_DMA_WPDMA_RX_RING0_CTRL2_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x508) // 0508
++#define WF_WFDMA_MEM_DMA_WPDMA_RX_RING0_CTRL3_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x50c) // 050C
++#define WF_WFDMA_MEM_DMA_WPDMA_RX_RING1_CTRL0_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x510) // 0510
++#define WF_WFDMA_MEM_DMA_WPDMA_RX_RING1_CTRL1_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x514) // 0514
++#define WF_WFDMA_MEM_DMA_WPDMA_RX_RING1_CTRL2_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x518) // 0518
++#define WF_WFDMA_MEM_DMA_WPDMA_RX_RING1_CTRL3_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x51c) // 051C
++#define WF_WFDMA_MEM_DMA_WPDMA_RX_RING2_CTRL0_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x520) // 0520
++#define WF_WFDMA_MEM_DMA_WPDMA_RX_RING2_CTRL1_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x524) // 0524
++#define WF_WFDMA_MEM_DMA_WPDMA_RX_RING2_CTRL2_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x528) // 0528
++#define WF_WFDMA_MEM_DMA_WPDMA_RX_RING2_CTRL3_ADDR             (WF_WFDMA_MEM_DMA_BASE + 0x52C) // 052C
++
++/* MIB */
++#define WF_UMIB_TOP_BASE                                       0x820cd000
++#define BN0_WF_MIB_TOP_BASE                                    0x820ed000
++#define BN1_WF_MIB_TOP_BASE                                    0x820fd000
++#define IP1_BN0_WF_MIB_TOP_BASE                                0x830ed000
++
++#define WF_UMIB_TOP_B0BROCR_ADDR                               (WF_UMIB_TOP_BASE + 0x484) // D484
++#define WF_UMIB_TOP_B0BRBCR_ADDR                               (WF_UMIB_TOP_BASE + 0x4D4) // D4D4
++#define WF_UMIB_TOP_B0BRDCR_ADDR                               (WF_UMIB_TOP_BASE + 0x524) // D524
++#define WF_UMIB_TOP_B1BROCR_ADDR                               (WF_UMIB_TOP_BASE + 0x5E8) // D5E8
++#define WF_UMIB_TOP_B2BROCR_ADDR                               (WF_UMIB_TOP_BASE + 0x74C) // D74C
++
++#define BN0_WF_MIB_TOP_M0SCR0_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x000) // D000
++#define BN0_WF_MIB_TOP_M0SDR6_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x020) // D020
++#define BN0_WF_MIB_TOP_M0SDR9_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x024) // D024
++#define BN0_WF_MIB_TOP_M0SDR18_ADDR                            (BN0_WF_MIB_TOP_BASE + 0x030) // D030
++#define BN0_WF_MIB_TOP_BTOCR_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x400) // D400
++#define BN0_WF_MIB_TOP_BTBCR_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x450) // D450
++#define BN0_WF_MIB_TOP_BTDCR_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x590) // D590
++#define BN0_WF_MIB_TOP_BTCR_ADDR                               (BN0_WF_MIB_TOP_BASE + 0x5A0) // D5A0
++#define BN0_WF_MIB_TOP_RVSR0_ADDR                              (BN0_WF_MIB_TOP_BASE + __OFFS(MIB_RVSR0))
++
++#define BN0_WF_MIB_TOP_TSCR0_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x6B0) // D6B0
++#define BN0_WF_MIB_TOP_TSCR3_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x6BC) // D6BC
++#define BN0_WF_MIB_TOP_TSCR4_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x6C0) // D6C0
++#define BN0_WF_MIB_TOP_TSCR5_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x6C4) // D6C4
++#define BN0_WF_MIB_TOP_TSCR6_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x6C8) // D6C8
++#define BN0_WF_MIB_TOP_TSCR7_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x6D0) // D6D0
++#define BN0_WF_MIB_TOP_TSCR8_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x6CC) // D6CC
++
++#define BN0_WF_MIB_TOP_TBCR0_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x6EC) // D6EC
++#define BN0_WF_MIB_TOP_TBCR1_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x6F0) // D6F0
++#define BN0_WF_MIB_TOP_TBCR2_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x6F4) // D6F4
++#define BN0_WF_MIB_TOP_TBCR3_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x6F8) // D6F8
++#define BN0_WF_MIB_TOP_TBCR4_ADDR                              (BN0_WF_MIB_TOP_BASE + 0x6FC) // D6FC
++
++#define BN0_WF_MIB_TOP_TDRCR0_ADDR                             (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TDRCR0))
++#define BN0_WF_MIB_TOP_TDRCR1_ADDR                             (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TDRCR1))
++#define BN0_WF_MIB_TOP_TDRCR2_ADDR                             (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TDRCR2))
++#define BN0_WF_MIB_TOP_TDRCR3_ADDR                             (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TDRCR3))
++#define BN0_WF_MIB_TOP_TDRCR4_ADDR                             (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TDRCR4))
++
++#define BN0_WF_MIB_TOP_BTSCR0_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x5E0) // D5E0
++#define BN0_WF_MIB_TOP_BTSCR1_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x5F0) // D5F0
++#define BN0_WF_MIB_TOP_BTSCR2_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x600) // D600
++#define BN0_WF_MIB_TOP_BTSCR3_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x610) // D610
++#define BN0_WF_MIB_TOP_BTSCR4_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x620) // D620
++#define BN0_WF_MIB_TOP_BTSCR5_ADDR                             (BN0_WF_MIB_TOP_BASE + __OFFS(MIB_BTSCR5))
++#define BN0_WF_MIB_TOP_BTSCR6_ADDR                             (BN0_WF_MIB_TOP_BASE + __OFFS(MIB_BTSCR6))
++
++#define BN0_WF_MIB_TOP_RSCR1_ADDR                              (BN0_WF_MIB_TOP_BASE + __OFFS(MIB_RSCR1))
++#define BN0_WF_MIB_TOP_BSCR2_ADDR                              (BN0_WF_MIB_TOP_BASE + __OFFS(MIB_BSCR2))
++#define BN0_WF_MIB_TOP_TSCR18_ADDR                             (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TSCR18))
++
++#define BN0_WF_MIB_TOP_MSR0_ADDR                               (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_MSR0))
++#define BN0_WF_MIB_TOP_MSR1_ADDR                               (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_MSR1))
++#define BN0_WF_MIB_TOP_MSR2_ADDR                               (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_MSR2))
++#define BN0_WF_MIB_TOP_MCTR5_ADDR                              (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_MCTR5))
++#define BN0_WF_MIB_TOP_MCTR6_ADDR                              (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_MCTR6))
++
++#define BN0_WF_MIB_TOP_RSCR26_ADDR                             (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_RSCR26))
++#define BN0_WF_MIB_TOP_RSCR27_ADDR                             (BN0_WF_MIB_TOP_BASE + __OFFS(MIB_RSCR27))
++#define BN0_WF_MIB_TOP_RSCR28_ADDR                             (BN0_WF_MIB_TOP_BASE + __OFFS(MIB_RSCR28))
++#define BN0_WF_MIB_TOP_RSCR31_ADDR                             (BN0_WF_MIB_TOP_BASE + __OFFS(MIB_RSCR31))
++#define BN0_WF_MIB_TOP_RSCR33_ADDR                             (BN0_WF_MIB_TOP_BASE + __OFFS(MIB_RSCR33))
++#define BN0_WF_MIB_TOP_RSCR35_ADDR                             (BN0_WF_MIB_TOP_BASE + __OFFS(MIB_RSCR35))
++#define BN0_WF_MIB_TOP_RSCR36_ADDR                             (BN0_WF_MIB_TOP_BASE + __OFFS(MIB_RSCR36))
++
++#define BN0_WF_MIB_TOP_TSCR3_AMPDU_MPDU_COUNT_MASK             0xFFFFFFFF                // AMPDU_MPDU_COUNT[31..0]
++#define BN0_WF_MIB_TOP_TSCR4_AMPDU_ACKED_COUNT_MASK            0xFFFFFFFF                // AMPDU_ACKED_COUNT[31..0]
++#define BN0_WF_MIB_TOP_M0SDR6_CHANNEL_IDLE_COUNT_MASK          0x0000FFFF                // CHANNEL_IDLE_COUNT[15..0]
++#define BN0_WF_MIB_TOP_M0SDR9_CCA_NAV_TX_TIME_MASK             0x00FFFFFF                // CCA_NAV_TX_TIME[23..0]
++#define BN0_WF_MIB_TOP_RSCR26_RX_MDRDY_COUNT_MASK              0xFFFFFFFF                // RX_MDRDY_COUNT[31..0]
++#define BN0_WF_MIB_TOP_MSR0_CCK_MDRDY_TIME_MASK                0xFFFFFFFF                // CCK_MDRDY_TIME[31..0]
++#define BN0_WF_MIB_TOP_MSR1_OFDM_LG_MIXED_VHT_MDRDY_TIME_MASK  0xFFFFFFFF                // OFDM_LG_MIXED_VHT_MDRDY_TIME[31..0]
++#define BN0_WF_MIB_TOP_MSR2_OFDM_GREEN_MDRDY_TIME_MASK         0xFFFFFFFF                // OFDM_GREEN_MDRDY_TIME[31..0]
++#define BN0_WF_MIB_TOP_MCTR5_P_CCA_TIME_MASK                   0xFFFFFFFF                // P_CCA_TIME[31..0]
++#define BN0_WF_MIB_TOP_MCTR6_S_CCA_TIME_MASK                   0xFFFFFFFF                // S_CCA_TIME[31..0]
++#define BN0_WF_MIB_TOP_M0SDR18_P_ED_TIME_MASK                  0x00FFFFFF                // P_ED_TIME[23..0]
++#define BN0_WF_MIB_TOP_TSCR18_BEACONTXCOUNT_MASK               0xFFFFFFFF                // BEACONTXCOUNT[31..0]
++#define BN0_WF_MIB_TOP_TBCR0_TX_20MHZ_CNT_MASK                 0xFFFFFFFF                // TX_20MHZ_CNT[31..0]
++#define BN0_WF_MIB_TOP_TBCR1_TX_40MHZ_CNT_MASK                 0xFFFFFFFF                // TX_40MHZ_CNT[31..0]
++#define BN0_WF_MIB_TOP_TBCR2_TX_80MHZ_CNT_MASK                 0xFFFFFFFF                // TX_80MHZ_CNT[31..0]
++#define BN0_WF_MIB_TOP_TBCR3_TX_160MHZ_CNT_MASK                0xFFFFFFFF                // TX_160MHZ_CNT[31..0]
++#define BN0_WF_MIB_TOP_TBCR4_TX_320MHZ_CNT_MASK                0xFFFFFFFF                // TX_320MHZ_CNT[31..0]
++#define BN0_WF_MIB_TOP_BSCR2_MUBF_TX_COUNT_MASK                0xFFFFFFFF                // MUBF_TX_COUNT[31..0]
++#define BN0_WF_MIB_TOP_RVSR0_VEC_MISS_COUNT_MASK               0xFFFFFFFF                // VEC_MISS_COUNT[31..0]
++#define BN0_WF_MIB_TOP_RSCR35_DELIMITER_FAIL_COUNT_MASK        0xFFFFFFFF                // DELIMITER_FAIL_COUNT[31..0]
++#define BN0_WF_MIB_TOP_RSCR1_RX_FCS_ERROR_COUNT_MASK           0xFFFFFFFF                // RX_FCS_ERROR_COUNT[31..0]
++#define BN0_WF_MIB_TOP_RSCR33_RX_FIFO_FULL_COUNT_MASK          0xFFFFFFFF                // RX_FIFO_FULL_COUNT[31..0]
++#define BN0_WF_MIB_TOP_RSCR36_RX_LEN_MISMATCH_MASK             0xFFFFFFFF                // RX_LEN_MISMATCH[31..0]
++#define BN0_WF_MIB_TOP_RSCR31_RX_MPDU_COUNT_MASK               0xFFFFFFFF                // RX_MPDU_COUNT[31..0]
++#define BN0_WF_MIB_TOP_BTSCR5_RTSTXCOUNTn_MASK                 0xFFFFFFFF                // RTSTXCOUNTn[31..0]
++#define BN0_WF_MIB_TOP_BTSCR6_RTSRETRYCOUNTn_MASK              0xFFFFFFFF                // RTSRETRYCOUNTn[31..0]
++#define BN0_WF_MIB_TOP_BTSCR0_BAMISSCOUNTn_MASK                0xFFFFFFFF                // BAMISSCOUNTn[31..0]
++#define BN0_WF_MIB_TOP_BTSCR1_ACKFAILCOUNTn_MASK               0xFFFFFFFF                // ACKFAILCOUNTn[31..0]
++#define BN0_WF_MIB_TOP_BTSCR2_FRAMERETRYCOUNTn_MASK            0xFFFFFFFF                // FRAMERETRYCOUNTn[31..0]
++#define BN0_WF_MIB_TOP_BTSCR3_FRAMERETRY2COUNTn_MASK           0xFFFFFFFF                // FRAMERETRY2COUNTn[31..0]
++#define BN0_WF_MIB_TOP_BTSCR4_FRAMERETRY3COUNTn_MASK           0xFFFFFFFF                // FRAMERETRY3COUNTn[31..0]
++#define BN0_WF_MIB_TOP_TRARC0_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x0B0) // D0B0
++#define BN0_WF_MIB_TOP_TRARC1_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x0B4) // D0B4
++#define BN0_WF_MIB_TOP_TRARC2_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x0B8) // D0B8
++#define BN0_WF_MIB_TOP_TRARC3_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x0BC) // D0BC
++#define BN0_WF_MIB_TOP_TRARC4_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x0C0) // D0C0
++#define BN0_WF_MIB_TOP_TRARC5_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x0C4) // D0C4
++#define BN0_WF_MIB_TOP_TRARC6_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x0C8) // D0C8
++#define BN0_WF_MIB_TOP_TRARC7_ADDR                             (BN0_WF_MIB_TOP_BASE + 0x0CC) // D0CC
++
++#define BN0_WF_MIB_TOP_TRDR0_ADDR                              (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR0))
++#define BN0_WF_MIB_TOP_TRDR1_ADDR                              (BN0_WF_MIB_TOP_BASE + __OFFS(MIB_TRDR1))
++#define BN0_WF_MIB_TOP_TRDR2_ADDR                              (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR2))
++#define BN0_WF_MIB_TOP_TRDR3_ADDR                              (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR3))
++#define BN0_WF_MIB_TOP_TRDR4_ADDR                              (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR4))
++#define BN0_WF_MIB_TOP_TRDR5_ADDR                              (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR5))
++#define BN0_WF_MIB_TOP_TRDR6_ADDR                              (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR6))
++#define BN0_WF_MIB_TOP_TRDR7_ADDR                              (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR7))
++#define BN0_WF_MIB_TOP_TRDR8_ADDR                              (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR8))
++#define BN0_WF_MIB_TOP_TRDR9_ADDR                              (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR9))
++#define BN0_WF_MIB_TOP_TRDR10_ADDR                             (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR10))
++#define BN0_WF_MIB_TOP_TRDR11_ADDR                             (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR11))
++#define BN0_WF_MIB_TOP_TRDR12_ADDR                             (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR12))
++#define BN0_WF_MIB_TOP_TRDR13_ADDR                             (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR13))
++#define BN0_WF_MIB_TOP_TRDR14_ADDR                             (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR14))
++#define BN0_WF_MIB_TOP_TRDR15_ADDR                             (BN0_WF_MIB_TOP_BASE + __DBG_OFFS(MIB_TRDR15))
++
++#define BN0_WF_MIB_TOP_TRARC0_AGG_RANG_SEL_1_ADDR              BN0_WF_MIB_TOP_TRARC0_ADDR
++#define BN0_WF_MIB_TOP_TRARC0_AGG_RANG_SEL_1_MASK              0x03FF0000                // AGG_RANG_SEL_1[25..16]
++#define BN0_WF_MIB_TOP_TRARC0_AGG_RANG_SEL_1_SHFT              16
++#define BN0_WF_MIB_TOP_TRARC0_AGG_RANG_SEL_0_ADDR              BN0_WF_MIB_TOP_TRARC0_ADDR
++#define BN0_WF_MIB_TOP_TRARC0_AGG_RANG_SEL_0_MASK              0x000003FF                // AGG_RANG_SEL_0[9..0]
++#define BN0_WF_MIB_TOP_TRARC0_AGG_RANG_SEL_0_SHFT              0
++
++#define BN0_WF_MIB_TOP_TRARC1_AGG_RANG_SEL_3_ADDR              BN0_WF_MIB_TOP_TRARC1_ADDR
++#define BN0_WF_MIB_TOP_TRARC1_AGG_RANG_SEL_3_MASK              0x03FF0000                // AGG_RANG_SEL_3[25..16]
++#define BN0_WF_MIB_TOP_TRARC1_AGG_RANG_SEL_3_SHFT              16
++#define BN0_WF_MIB_TOP_TRARC1_AGG_RANG_SEL_2_ADDR              BN0_WF_MIB_TOP_TRARC1_ADDR
++#define BN0_WF_MIB_TOP_TRARC1_AGG_RANG_SEL_2_MASK              0x000003FF                // AGG_RANG_SEL_2[9..0]
++#define BN0_WF_MIB_TOP_TRARC1_AGG_RANG_SEL_2_SHFT              0
++
++#define BN0_WF_MIB_TOP_TRARC2_AGG_RANG_SEL_5_ADDR              BN0_WF_MIB_TOP_TRARC2_ADDR
++#define BN0_WF_MIB_TOP_TRARC2_AGG_RANG_SEL_5_MASK              0x03FF0000                // AGG_RANG_SEL_5[25..16]
++#define BN0_WF_MIB_TOP_TRARC2_AGG_RANG_SEL_5_SHFT              16
++#define BN0_WF_MIB_TOP_TRARC2_AGG_RANG_SEL_4_ADDR              BN0_WF_MIB_TOP_TRARC2_ADDR
++#define BN0_WF_MIB_TOP_TRARC2_AGG_RANG_SEL_4_MASK              0x000003FF                // AGG_RANG_SEL_4[9..0]
++#define BN0_WF_MIB_TOP_TRARC2_AGG_RANG_SEL_4_SHFT              0
++
++#define BN0_WF_MIB_TOP_TRARC3_AGG_RANG_SEL_7_ADDR              BN0_WF_MIB_TOP_TRARC3_ADDR
++#define BN0_WF_MIB_TOP_TRARC3_AGG_RANG_SEL_7_MASK              0x03FF0000                // AGG_RANG_SEL_7[25..16]
++#define BN0_WF_MIB_TOP_TRARC3_AGG_RANG_SEL_7_SHFT              16
++#define BN0_WF_MIB_TOP_TRARC3_AGG_RANG_SEL_6_ADDR              BN0_WF_MIB_TOP_TRARC3_ADDR
++#define BN0_WF_MIB_TOP_TRARC3_AGG_RANG_SEL_6_MASK              0x000003FF                // AGG_RANG_SEL_6[9..0]
++#define BN0_WF_MIB_TOP_TRARC3_AGG_RANG_SEL_6_SHFT              0
++
++#define BN0_WF_MIB_TOP_TRARC4_AGG_RANG_SEL_9_ADDR              BN0_WF_MIB_TOP_TRARC4_ADDR
++#define BN0_WF_MIB_TOP_TRARC4_AGG_RANG_SEL_9_MASK              0x03FF0000                // AGG_RANG_SEL_9[25..16]
++#define BN0_WF_MIB_TOP_TRARC4_AGG_RANG_SEL_9_SHFT              16
++#define BN0_WF_MIB_TOP_TRARC4_AGG_RANG_SEL_8_ADDR              BN0_WF_MIB_TOP_TRARC4_ADDR
++#define BN0_WF_MIB_TOP_TRARC4_AGG_RANG_SEL_8_MASK              0x000003FF                // AGG_RANG_SEL_8[9..0]
++#define BN0_WF_MIB_TOP_TRARC4_AGG_RANG_SEL_8_SHFT              0
++
++#define BN0_WF_MIB_TOP_TRARC5_AGG_RANG_SEL_11_ADDR             BN0_WF_MIB_TOP_TRARC5_ADDR
++#define BN0_WF_MIB_TOP_TRARC5_AGG_RANG_SEL_11_MASK             0x03FF0000                // AGG_RANG_SEL_11[25..16]
++#define BN0_WF_MIB_TOP_TRARC5_AGG_RANG_SEL_11_SHFT             16
++#define BN0_WF_MIB_TOP_TRARC5_AGG_RANG_SEL_10_ADDR             BN0_WF_MIB_TOP_TRARC5_ADDR
++#define BN0_WF_MIB_TOP_TRARC5_AGG_RANG_SEL_10_MASK             0x000003FF                // AGG_RANG_SEL_10[9..0]
++#define BN0_WF_MIB_TOP_TRARC5_AGG_RANG_SEL_10_SHFT             0
++
++#define BN0_WF_MIB_TOP_TRARC6_AGG_RANG_SEL_13_ADDR             BN0_WF_MIB_TOP_TRARC6_ADDR
++#define BN0_WF_MIB_TOP_TRARC6_AGG_RANG_SEL_13_MASK             0x03FF0000                // AGG_RANG_SEL_13[25..16]
++#define BN0_WF_MIB_TOP_TRARC6_AGG_RANG_SEL_13_SHFT             16
++#define BN0_WF_MIB_TOP_TRARC6_AGG_RANG_SEL_12_ADDR             BN0_WF_MIB_TOP_TRARC6_ADDR
++#define BN0_WF_MIB_TOP_TRARC6_AGG_RANG_SEL_12_MASK             0x000003FF                // AGG_RANG_SEL_12[9..0]
++#define BN0_WF_MIB_TOP_TRARC6_AGG_RANG_SEL_12_SHFT             0
++
++#define BN0_WF_MIB_TOP_TRARC7_AGG_RANG_SEL_14_ADDR             BN0_WF_MIB_TOP_TRARC7_ADDR
++#define BN0_WF_MIB_TOP_TRARC7_AGG_RANG_SEL_14_MASK             0x000003FF                // AGG_RANG_SEL_14[9..0]
++#define BN0_WF_MIB_TOP_TRARC7_AGG_RANG_SEL_14_SHFT             0
++
++/* RRO TOP */
++#define WF_RRO_TOP_BASE                                        0xA000 /*0x820C2000 */
++#define WF_RRO_TOP_IND_CMD_0_CTRL0_ADDR                        (WF_RRO_TOP_BASE + 0x40) // 2040
++											//
++/* WTBL */
++enum mt7996_wtbl_type {
++	WTBL_TYPE_LMAC, 	/* WTBL in LMAC */
++	WTBL_TYPE_UMAC, 	/* WTBL in UMAC */
++	WTBL_TYPE_KEY,		/* Key Table */
++	MAX_NUM_WTBL_TYPE
++};
++
++struct berse_wtbl_parse {
++	u8 *name;
++	u32 mask;
++	u32 shift;
++	u8 new_line;
++};
++
++enum muar_idx {
++	MUAR_INDEX_OWN_MAC_ADDR_0 = 0,
++	MUAR_INDEX_OWN_MAC_ADDR_1,
++	MUAR_INDEX_OWN_MAC_ADDR_2,
++	MUAR_INDEX_OWN_MAC_ADDR_3,
++	MUAR_INDEX_OWN_MAC_ADDR_4,
++	MUAR_INDEX_OWN_MAC_ADDR_BC_MC = 0xE,
++	MUAR_INDEX_UNMATCHED = 0xF,
++	MUAR_INDEX_OWN_MAC_ADDR_11 = 0x11,
++	MUAR_INDEX_OWN_MAC_ADDR_12,
++	MUAR_INDEX_OWN_MAC_ADDR_13,
++	MUAR_INDEX_OWN_MAC_ADDR_14,
++	MUAR_INDEX_OWN_MAC_ADDR_15,
++	MUAR_INDEX_OWN_MAC_ADDR_16,
++	MUAR_INDEX_OWN_MAC_ADDR_17,
++	MUAR_INDEX_OWN_MAC_ADDR_18,
++	MUAR_INDEX_OWN_MAC_ADDR_19,
++	MUAR_INDEX_OWN_MAC_ADDR_1A,
++	MUAR_INDEX_OWN_MAC_ADDR_1B,
++	MUAR_INDEX_OWN_MAC_ADDR_1C,
++	MUAR_INDEX_OWN_MAC_ADDR_1D,
++	MUAR_INDEX_OWN_MAC_ADDR_1E,
++	MUAR_INDEX_OWN_MAC_ADDR_1F,
++	MUAR_INDEX_OWN_MAC_ADDR_20,
++	MUAR_INDEX_OWN_MAC_ADDR_21,
++	MUAR_INDEX_OWN_MAC_ADDR_22,
++	MUAR_INDEX_OWN_MAC_ADDR_23,
++	MUAR_INDEX_OWN_MAC_ADDR_24,
++	MUAR_INDEX_OWN_MAC_ADDR_25,
++	MUAR_INDEX_OWN_MAC_ADDR_26,
++	MUAR_INDEX_OWN_MAC_ADDR_27,
++	MUAR_INDEX_OWN_MAC_ADDR_28,
++	MUAR_INDEX_OWN_MAC_ADDR_29,
++	MUAR_INDEX_OWN_MAC_ADDR_2A,
++	MUAR_INDEX_OWN_MAC_ADDR_2B,
++	MUAR_INDEX_OWN_MAC_ADDR_2C,
++	MUAR_INDEX_OWN_MAC_ADDR_2D,
++	MUAR_INDEX_OWN_MAC_ADDR_2E,
++	MUAR_INDEX_OWN_MAC_ADDR_2F
++};
++
++enum cipher_suit {
++	IGTK_CIPHER_SUIT_NONE = 0,
++	IGTK_CIPHER_SUIT_BIP,
++	IGTK_CIPHER_SUIT_BIP_256
++};
++
++#define LWTBL_LEN_IN_DW			36
++#define UWTBL_LEN_IN_DW			16
++
++#define MT_DBG_WTBL_BASE		0x820D8000
++
++#define MT_DBG_WTBLON_TOP_BASE		0x820d4000
++#define MT_DBG_WTBLON_TOP_WDUCR_ADDR	(MT_DBG_WTBLON_TOP_BASE + 0x0370) // 4370
++#define MT_DBG_WTBLON_TOP_WDUCR_GROUP	GENMASK(4, 0)
++
++#define MT_DBG_UWTBL_TOP_BASE		0x820c4000
++#define MT_DBG_UWTBL_TOP_WDUCR_ADDR	(MT_DBG_UWTBL_TOP_BASE + 0x0104) // 4104
++#define MT_DBG_UWTBL_TOP_WDUCR_GROUP	GENMASK(5, 0)
++#define MT_DBG_UWTBL_TOP_WDUCR_TARGET	BIT(31)
++
++#define LWTBL_IDX2BASE_ID		GENMASK(14, 8)
++#define LWTBL_IDX2BASE_DW		GENMASK(7, 2)
++#define LWTBL_IDX2BASE(_id, _dw)	(MT_DBG_WTBL_BASE | \
++					FIELD_PREP(LWTBL_IDX2BASE_ID, _id) | \
++					FIELD_PREP(LWTBL_IDX2BASE_DW, _dw))
++
++#define UWTBL_IDX2BASE_ID		GENMASK(12, 6)
++#define UWTBL_IDX2BASE_DW		GENMASK(5, 2)
++#define UWTBL_IDX2BASE(_id, _dw)	(MT_DBG_UWTBL_TOP_BASE | 0x2000 | \
++					FIELD_PREP(UWTBL_IDX2BASE_ID, _id) | \
++					FIELD_PREP(UWTBL_IDX2BASE_DW, _dw))
++
++#define KEYTBL_IDX2BASE_KEY		GENMASK(12, 6)
++#define KEYTBL_IDX2BASE_DW		GENMASK(5, 2)
++#define KEYTBL_IDX2BASE(_key, _dw)	(MT_DBG_UWTBL_TOP_BASE | 0x2000 | \
++					FIELD_PREP(KEYTBL_IDX2BASE_KEY, _key) | \
++					FIELD_PREP(KEYTBL_IDX2BASE_DW, _dw))
++
++// UMAC WTBL
++// DW0
++#define WF_UWTBL_PEER_MLD_ADDRESS_47_32__DW                         0
++#define WF_UWTBL_PEER_MLD_ADDRESS_47_32__ADDR                       0
++#define WF_UWTBL_PEER_MLD_ADDRESS_47_32__MASK                       0x0000ffff // 15- 0
++#define WF_UWTBL_PEER_MLD_ADDRESS_47_32__SHIFT                      0
++#define WF_UWTBL_OWN_MLD_ID_DW                                      0
++#define WF_UWTBL_OWN_MLD_ID_ADDR                                    0
++#define WF_UWTBL_OWN_MLD_ID_MASK                                    0x003f0000 // 21-16
++#define WF_UWTBL_OWN_MLD_ID_SHIFT                                   16
++// DW1
++#define WF_UWTBL_PEER_MLD_ADDRESS_31_0__DW                          1
++#define WF_UWTBL_PEER_MLD_ADDRESS_31_0__ADDR                        4
++#define WF_UWTBL_PEER_MLD_ADDRESS_31_0__MASK                        0xffffffff // 31- 0
++#define WF_UWTBL_PEER_MLD_ADDRESS_31_0__SHIFT                       0
++// DW2
++#define WF_UWTBL_PN_31_0__DW                                        2
++#define WF_UWTBL_PN_31_0__ADDR                                      8
++#define WF_UWTBL_PN_31_0__MASK                                      0xffffffff // 31- 0
++#define WF_UWTBL_PN_31_0__SHIFT                                     0
++// DW3
++#define WF_UWTBL_PN_47_32__DW                                       3
++#define WF_UWTBL_PN_47_32__ADDR                                     12
++#define WF_UWTBL_PN_47_32__MASK                                     0x0000ffff // 15- 0
++#define WF_UWTBL_PN_47_32__SHIFT                                    0
++#define WF_UWTBL_COM_SN_DW                                          3
++#define WF_UWTBL_COM_SN_ADDR                                        12
++#define WF_UWTBL_COM_SN_MASK                                        0x0fff0000 // 27-16
++#define WF_UWTBL_COM_SN_SHIFT                                       16
++// DW4
++#define WF_UWTBL_TID0_SN_DW                                         4
++#define WF_UWTBL_TID0_SN_ADDR                                       16
++#define WF_UWTBL_TID0_SN_MASK                                       0x00000fff // 11- 0
++#define WF_UWTBL_TID0_SN_SHIFT                                      0
++#define WF_UWTBL_RX_BIPN_31_0__DW                                   4
++#define WF_UWTBL_RX_BIPN_31_0__ADDR                                 16
++#define WF_UWTBL_RX_BIPN_31_0__MASK                                 0xffffffff // 31- 0
++#define WF_UWTBL_RX_BIPN_31_0__SHIFT                                0
++#define WF_UWTBL_TID1_SN_DW                                         4
++#define WF_UWTBL_TID1_SN_ADDR                                       16
++#define WF_UWTBL_TID1_SN_MASK                                       0x00fff000 // 23-12
++#define WF_UWTBL_TID1_SN_SHIFT                                      12
++#define WF_UWTBL_TID2_SN_7_0__DW                                    4
++#define WF_UWTBL_TID2_SN_7_0__ADDR                                  16
++#define WF_UWTBL_TID2_SN_7_0__MASK                                  0xff000000 // 31-24
++#define WF_UWTBL_TID2_SN_7_0__SHIFT                                 24
++// DW5
++#define WF_UWTBL_TID2_SN_11_8__DW                                   5
++#define WF_UWTBL_TID2_SN_11_8__ADDR                                 20
++#define WF_UWTBL_TID2_SN_11_8__MASK                                 0x0000000f //  3- 0
++#define WF_UWTBL_TID2_SN_11_8__SHIFT                                0
++#define WF_UWTBL_RX_BIPN_47_32__DW                                  5
++#define WF_UWTBL_RX_BIPN_47_32__ADDR                                20
++#define WF_UWTBL_RX_BIPN_47_32__MASK                                0x0000ffff // 15- 0
++#define WF_UWTBL_RX_BIPN_47_32__SHIFT                               0
++#define WF_UWTBL_TID3_SN_DW                                         5
++#define WF_UWTBL_TID3_SN_ADDR                                       20
++#define WF_UWTBL_TID3_SN_MASK                                       0x0000fff0 // 15- 4
++#define WF_UWTBL_TID3_SN_SHIFT                                      4
++#define WF_UWTBL_TID4_SN_DW                                         5
++#define WF_UWTBL_TID4_SN_ADDR                                       20
++#define WF_UWTBL_TID4_SN_MASK                                       0x0fff0000 // 27-16
++#define WF_UWTBL_TID4_SN_SHIFT                                      16
++#define WF_UWTBL_TID5_SN_3_0__DW                                    5
++#define WF_UWTBL_TID5_SN_3_0__ADDR                                  20
++#define WF_UWTBL_TID5_SN_3_0__MASK                                  0xf0000000 // 31-28
++#define WF_UWTBL_TID5_SN_3_0__SHIFT                                 28
++// DW6
++#define WF_UWTBL_TID5_SN_11_4__DW                                   6
++#define WF_UWTBL_TID5_SN_11_4__ADDR                                 24
++#define WF_UWTBL_TID5_SN_11_4__MASK                                 0x000000ff //  7- 0
++#define WF_UWTBL_TID5_SN_11_4__SHIFT                                0
++#define WF_UWTBL_KEY_LOC2_DW                                        6
++#define WF_UWTBL_KEY_LOC2_ADDR                                      24
++#define WF_UWTBL_KEY_LOC2_MASK                                      0x00001fff // 12- 0
++#define WF_UWTBL_KEY_LOC2_SHIFT                                     0
++#define WF_UWTBL_TID6_SN_DW                                         6
++#define WF_UWTBL_TID6_SN_ADDR                                       24
++#define WF_UWTBL_TID6_SN_MASK                                       0x000fff00 // 19- 8
++#define WF_UWTBL_TID6_SN_SHIFT                                      8
++#define WF_UWTBL_TID7_SN_DW                                         6
++#define WF_UWTBL_TID7_SN_ADDR                                       24
++#define WF_UWTBL_TID7_SN_MASK                                       0xfff00000 // 31-20
++#define WF_UWTBL_TID7_SN_SHIFT                                      20
++// DW7
++#define WF_UWTBL_KEY_LOC0_DW                                        7
++#define WF_UWTBL_KEY_LOC0_ADDR                                      28
++#define WF_UWTBL_KEY_LOC0_MASK                                      0x00001fff // 12- 0
++#define WF_UWTBL_KEY_LOC0_SHIFT                                     0
++#define WF_UWTBL_KEY_LOC1_DW                                        7
++#define WF_UWTBL_KEY_LOC1_ADDR                                      28
++#define WF_UWTBL_KEY_LOC1_MASK                                      0x1fff0000 // 28-16
++#define WF_UWTBL_KEY_LOC1_SHIFT                                     16
++// DW8
++#define WF_UWTBL_AMSDU_CFG_DW                                       8
++#define WF_UWTBL_AMSDU_CFG_ADDR                                     32
++#define WF_UWTBL_AMSDU_CFG_MASK                                     0x00000fff // 11- 0
++#define WF_UWTBL_AMSDU_CFG_SHIFT                                    0
++#define WF_UWTBL_SEC_ADDR_MODE_DW                                   8
++#define WF_UWTBL_SEC_ADDR_MODE_ADDR                                 32
++#define WF_UWTBL_SEC_ADDR_MODE_MASK                                 0x00300000 // 21-20
++#define WF_UWTBL_SEC_ADDR_MODE_SHIFT                                20
++#define WF_UWTBL_WMM_Q_DW                                           8
++#define WF_UWTBL_WMM_Q_ADDR                                         32
++#define WF_UWTBL_WMM_Q_MASK                                         0x06000000 // 26-25
++#define WF_UWTBL_WMM_Q_SHIFT                                        25
++#define WF_UWTBL_QOS_DW                                             8
++#define WF_UWTBL_QOS_ADDR                                           32
++#define WF_UWTBL_QOS_MASK                                           0x08000000 // 27-27
++#define WF_UWTBL_QOS_SHIFT                                          27
++#define WF_UWTBL_HT_DW                                              8
++#define WF_UWTBL_HT_ADDR                                            32
++#define WF_UWTBL_HT_MASK                                            0x10000000 // 28-28
++#define WF_UWTBL_HT_SHIFT                                           28
++#define WF_UWTBL_HDRT_MODE_DW                                       8
++#define WF_UWTBL_HDRT_MODE_ADDR                                     32
++#define WF_UWTBL_HDRT_MODE_MASK                                     0x20000000 // 29-29
++#define WF_UWTBL_HDRT_MODE_SHIFT                                    29
++// DW9
++#define WF_UWTBL_RELATED_IDX0_DW                                    9
++#define WF_UWTBL_RELATED_IDX0_ADDR                                  36
++#define WF_UWTBL_RELATED_IDX0_MASK                                  0x00000fff // 11- 0
++#define WF_UWTBL_RELATED_IDX0_SHIFT                                 0
++#define WF_UWTBL_RELATED_BAND0_DW                                   9
++#define WF_UWTBL_RELATED_BAND0_ADDR                                 36
++#define WF_UWTBL_RELATED_BAND0_MASK                                 0x00003000 // 13-12
++#define WF_UWTBL_RELATED_BAND0_SHIFT                                12
++#define WF_UWTBL_PRIMARY_MLD_BAND_DW                                9
++#define WF_UWTBL_PRIMARY_MLD_BAND_ADDR                              36
++#define WF_UWTBL_PRIMARY_MLD_BAND_MASK                              0x0000c000 // 15-14
++#define WF_UWTBL_PRIMARY_MLD_BAND_SHIFT                             14
++#define WF_UWTBL_RELATED_IDX1_DW                                    9
++#define WF_UWTBL_RELATED_IDX1_ADDR                                  36
++#define WF_UWTBL_RELATED_IDX1_MASK                                  0x0fff0000 // 27-16
++#define WF_UWTBL_RELATED_IDX1_SHIFT                                 16
++#define WF_UWTBL_RELATED_BAND1_DW                                   9
++#define WF_UWTBL_RELATED_BAND1_ADDR                                 36
++#define WF_UWTBL_RELATED_BAND1_MASK                                 0x30000000 // 29-28
++#define WF_UWTBL_RELATED_BAND1_SHIFT                                28
++#define WF_UWTBL_SECONDARY_MLD_BAND_DW                              9
++#define WF_UWTBL_SECONDARY_MLD_BAND_ADDR                            36
++#define WF_UWTBL_SECONDARY_MLD_BAND_MASK                            0xc0000000 // 31-30
++#define WF_UWTBL_SECONDARY_MLD_BAND_SHIFT                           30
++
++/* LMAC WTBL */
++// DW0
++#define WF_LWTBL_PEER_LINK_ADDRESS_47_32__DW                        0
++#define WF_LWTBL_PEER_LINK_ADDRESS_47_32__ADDR                      0
++#define WF_LWTBL_PEER_LINK_ADDRESS_47_32__MASK \
++	0x0000ffff // 15- 0
++#define WF_LWTBL_PEER_LINK_ADDRESS_47_32__SHIFT                     0
++#define WF_LWTBL_MUAR_DW                                            0
++#define WF_LWTBL_MUAR_ADDR                                          0
++#define WF_LWTBL_MUAR_MASK \
++	0x003f0000 // 21-16
++#define WF_LWTBL_MUAR_SHIFT                                         16
++#define WF_LWTBL_RCA1_DW                                            0
++#define WF_LWTBL_RCA1_ADDR                                          0
++#define WF_LWTBL_RCA1_MASK \
++	0x00400000 // 22-22
++#define WF_LWTBL_RCA1_SHIFT                                         22
++#define WF_LWTBL_KID_DW                                             0
++#define WF_LWTBL_KID_ADDR                                           0
++#define WF_LWTBL_KID_MASK \
++	0x01800000 // 24-23
++#define WF_LWTBL_KID_SHIFT                                          23
++#define WF_LWTBL_RCID_DW                                            0
++#define WF_LWTBL_RCID_ADDR                                          0
++#define WF_LWTBL_RCID_MASK \
++	0x02000000 // 25-25
++#define WF_LWTBL_RCID_SHIFT                                         25
++#define WF_LWTBL_BAND_DW                                            0
++#define WF_LWTBL_BAND_ADDR                                          0
++#define WF_LWTBL_BAND_MASK \
++	0x0c000000 // 27-26
++#define WF_LWTBL_BAND_SHIFT                                         26
++#define WF_LWTBL_RV_DW                                              0
++#define WF_LWTBL_RV_ADDR                                            0
++#define WF_LWTBL_RV_MASK \
++	0x10000000 // 28-28
++#define WF_LWTBL_RV_SHIFT                                           28
++#define WF_LWTBL_RCA2_DW                                            0
++#define WF_LWTBL_RCA2_ADDR                                          0
++#define WF_LWTBL_RCA2_MASK \
++	0x20000000 // 29-29
++#define WF_LWTBL_RCA2_SHIFT                                         29
++#define WF_LWTBL_WPI_FLAG_DW                                        0
++#define WF_LWTBL_WPI_FLAG_ADDR                                      0
++#define WF_LWTBL_WPI_FLAG_MASK \
++	0x40000000 // 30-30
++#define WF_LWTBL_WPI_FLAG_SHIFT                                     30
++// DW1
++#define WF_LWTBL_PEER_LINK_ADDRESS_31_0__DW                         1
++#define WF_LWTBL_PEER_LINK_ADDRESS_31_0__ADDR                       4
++#define WF_LWTBL_PEER_LINK_ADDRESS_31_0__MASK \
++	0xffffffff // 31- 0
++#define WF_LWTBL_PEER_LINK_ADDRESS_31_0__SHIFT                      0
++// DW2
++#define WF_LWTBL_AID_DW                                             2
++#define WF_LWTBL_AID_ADDR                                           8
++#define WF_LWTBL_AID_MASK \
++	0x00000fff // 11- 0
++#define WF_LWTBL_AID_SHIFT                                          0
++#define WF_LWTBL_GID_SU_DW                                          2
++#define WF_LWTBL_GID_SU_ADDR                                        8
++#define WF_LWTBL_GID_SU_MASK \
++	0x00001000 // 12-12
++#define WF_LWTBL_GID_SU_SHIFT                                       12
++#define WF_LWTBL_SPP_EN_DW                                          2
++#define WF_LWTBL_SPP_EN_ADDR                                        8
++#define WF_LWTBL_SPP_EN_MASK \
++	0x00002000 // 13-13
++#define WF_LWTBL_SPP_EN_SHIFT                                       13
++#define WF_LWTBL_WPI_EVEN_DW                                        2
++#define WF_LWTBL_WPI_EVEN_ADDR                                      8
++#define WF_LWTBL_WPI_EVEN_MASK \
++	0x00004000 // 14-14
++#define WF_LWTBL_WPI_EVEN_SHIFT                                     14
++#define WF_LWTBL_AAD_OM_DW                                          2
++#define WF_LWTBL_AAD_OM_ADDR                                        8
++#define WF_LWTBL_AAD_OM_MASK \
++	0x00008000 // 15-15
++#define WF_LWTBL_AAD_OM_SHIFT                                       15
++/* kite DW2 field bit 13-14 */
++#define WF_LWTBL_DUAL_PTEC_EN_DW                                    2
++#define WF_LWTBL_DUAL_PTEC_EN_ADDR                                  8
++#define WF_LWTBL_DUAL_PTEC_EN_MASK \
++	0x00002000 // 13-13
++#define WF_LWTBL_DUAL_PTEC_EN_SHIFT                                 13
++#define WF_LWTBL_DUAL_CTS_CAP_DW                                    2
++#define WF_LWTBL_DUAL_CTS_CAP_ADDR                                  8
++#define WF_LWTBL_DUAL_CTS_CAP_MASK \
++	0x00004000 // 14-14
++#define WF_LWTBL_DUAL_CTS_CAP_SHIFT                                 14
++#define WF_LWTBL_CIPHER_SUIT_PGTK_DW                                2
++#define WF_LWTBL_CIPHER_SUIT_PGTK_ADDR                              8
++#define WF_LWTBL_CIPHER_SUIT_PGTK_MASK \
++	0x001f0000 // 20-16
++#define WF_LWTBL_CIPHER_SUIT_PGTK_SHIFT                             16
++#define WF_LWTBL_FD_DW                                              2
++#define WF_LWTBL_FD_ADDR                                            8
++#define WF_LWTBL_FD_MASK \
++	0x00200000 // 21-21
++#define WF_LWTBL_FD_SHIFT                                           21
++#define WF_LWTBL_TD_DW                                              2
++#define WF_LWTBL_TD_ADDR                                            8
++#define WF_LWTBL_TD_MASK \
++	0x00400000 // 22-22
++#define WF_LWTBL_TD_SHIFT                                           22
++#define WF_LWTBL_SW_DW                                              2
++#define WF_LWTBL_SW_ADDR                                            8
++#define WF_LWTBL_SW_MASK \
++	0x00800000 // 23-23
++#define WF_LWTBL_SW_SHIFT                                           23
++#define WF_LWTBL_UL_DW                                              2
++#define WF_LWTBL_UL_ADDR                                            8
++#define WF_LWTBL_UL_MASK \
++	0x01000000 // 24-24
++#define WF_LWTBL_UL_SHIFT                                           24
++#define WF_LWTBL_TX_PS_DW                                           2
++#define WF_LWTBL_TX_PS_ADDR                                         8
++#define WF_LWTBL_TX_PS_MASK \
++	0x02000000 // 25-25
++#define WF_LWTBL_TX_PS_SHIFT                                        25
++#define WF_LWTBL_QOS_DW                                             2
++#define WF_LWTBL_QOS_ADDR                                           8
++#define WF_LWTBL_QOS_MASK \
++	0x04000000 // 26-26
++#define WF_LWTBL_QOS_SHIFT                                          26
++#define WF_LWTBL_HT_DW                                              2
++#define WF_LWTBL_HT_ADDR                                            8
++#define WF_LWTBL_HT_MASK \
++	0x08000000 // 27-27
++#define WF_LWTBL_HT_SHIFT                                           27
++#define WF_LWTBL_VHT_DW                                             2
++#define WF_LWTBL_VHT_ADDR                                           8
++#define WF_LWTBL_VHT_MASK \
++	0x10000000 // 28-28
++#define WF_LWTBL_VHT_SHIFT                                          28
++#define WF_LWTBL_HE_DW                                              2
++#define WF_LWTBL_HE_ADDR                                            8
++#define WF_LWTBL_HE_MASK \
++	0x20000000 // 29-29
++#define WF_LWTBL_HE_SHIFT                                           29
++#define WF_LWTBL_EHT_DW                                             2
++#define WF_LWTBL_EHT_ADDR                                           8
++#define WF_LWTBL_EHT_MASK \
++	0x40000000 // 30-30
++#define WF_LWTBL_EHT_SHIFT                                          30
++#define WF_LWTBL_MESH_DW                                            2
++#define WF_LWTBL_MESH_ADDR                                          8
++#define WF_LWTBL_MESH_MASK \
++	0x80000000 // 31-31
++#define WF_LWTBL_MESH_SHIFT                                         31
++// DW3
++#define WF_LWTBL_WMM_Q_DW                                           3
++#define WF_LWTBL_WMM_Q_ADDR                                         12
++#define WF_LWTBL_WMM_Q_MASK \
++	0x00000003 // 1- 0
++#define WF_LWTBL_WMM_Q_SHIFT                                        0
++#define WF_LWTBL_EHT_SIG_MCS_DW                                     3
++#define WF_LWTBL_EHT_SIG_MCS_ADDR                                   12
++#define WF_LWTBL_EHT_SIG_MCS_MASK \
++	0x0000000c // 3- 2
++#define WF_LWTBL_EHT_SIG_MCS_SHIFT                                  2
++#define WF_LWTBL_HDRT_MODE_DW                                       3
++#define WF_LWTBL_HDRT_MODE_ADDR                                     12
++#define WF_LWTBL_HDRT_MODE_MASK \
++	0x00000010 // 4- 4
++#define WF_LWTBL_HDRT_MODE_SHIFT                                    4
++#define WF_LWTBL_BEAM_CHG_DW                                        3
++#define WF_LWTBL_BEAM_CHG_ADDR                                      12
++#define WF_LWTBL_BEAM_CHG_MASK \
++	0x00000020 // 5- 5
++#define WF_LWTBL_BEAM_CHG_SHIFT                                     5
++#define WF_LWTBL_EHT_LTF_SYM_NUM_OPT_DW                             3
++#define WF_LWTBL_EHT_LTF_SYM_NUM_OPT_ADDR                           12
++#define WF_LWTBL_EHT_LTF_SYM_NUM_OPT_MASK \
++	0x000000c0 // 7- 6
++#define WF_LWTBL_EHT_LTF_SYM_NUM_OPT_SHIFT                          6
++#define WF_LWTBL_PFMU_IDX_DW                                        3
++#define WF_LWTBL_PFMU_IDX_ADDR                                      12
++#define WF_LWTBL_PFMU_IDX_MASK \
++	0x0000ff00 // 15- 8
++#define WF_LWTBL_PFMU_IDX_SHIFT                                     8
++#define WF_LWTBL_ULPF_IDX_DW                                        3
++#define WF_LWTBL_ULPF_IDX_ADDR                                      12
++#define WF_LWTBL_ULPF_IDX_MASK \
++	0x00ff0000 // 23-16
++#define WF_LWTBL_ULPF_IDX_SHIFT                                     16
++#define WF_LWTBL_RIBF_DW                                            3
++#define WF_LWTBL_RIBF_ADDR                                          12
++#define WF_LWTBL_RIBF_MASK \
++	0x01000000 // 24-24
++#define WF_LWTBL_RIBF_SHIFT                                         24
++#define WF_LWTBL_ULPF_DW                                            3
++#define WF_LWTBL_ULPF_ADDR                                          12
++#define WF_LWTBL_ULPF_MASK \
++	0x02000000 // 25-25
++#define WF_LWTBL_ULPF_SHIFT                                         25
++#define WF_LWTBL_BYPASS_TXSMM_DW                                    3
++#define WF_LWTBL_BYPASS_TXSMM_ADDR                                  12
++#define WF_LWTBL_BYPASS_TXSMM_MASK \
++	0x04000000 // 26-26
++#define WF_LWTBL_BYPASS_TXSMM_SHIFT                                 26
++#define WF_LWTBL_TBF_HT_DW                                          3
++#define WF_LWTBL_TBF_HT_ADDR                                        12
++#define WF_LWTBL_TBF_HT_MASK \
++	0x08000000 // 27-27
++#define WF_LWTBL_TBF_HT_SHIFT                                       27
++#define WF_LWTBL_TBF_VHT_DW                                         3
++#define WF_LWTBL_TBF_VHT_ADDR                                       12
++#define WF_LWTBL_TBF_VHT_MASK \
++	0x10000000 // 28-28
++#define WF_LWTBL_TBF_VHT_SHIFT                                      28
++#define WF_LWTBL_TBF_HE_DW                                          3
++#define WF_LWTBL_TBF_HE_ADDR                                        12
++#define WF_LWTBL_TBF_HE_MASK \
++	0x20000000 // 29-29
++#define WF_LWTBL_TBF_HE_SHIFT                                       29
++#define WF_LWTBL_TBF_EHT_DW                                         3
++#define WF_LWTBL_TBF_EHT_ADDR                                       12
++#define WF_LWTBL_TBF_EHT_MASK \
++	0x40000000 // 30-30
++#define WF_LWTBL_TBF_EHT_SHIFT                                      30
++#define WF_LWTBL_IGN_FBK_DW                                         3
++#define WF_LWTBL_IGN_FBK_ADDR                                       12
++#define WF_LWTBL_IGN_FBK_MASK \
++	0x80000000 // 31-31
++#define WF_LWTBL_IGN_FBK_SHIFT                                      31
++// DW4
++#define WF_LWTBL_NEGOTIATED_WINSIZE0_DW                             4
++#define WF_LWTBL_NEGOTIATED_WINSIZE0_ADDR                           16
++#define WF_LWTBL_NEGOTIATED_WINSIZE0_MASK \
++	0x00000007 // 2- 0
++#define WF_LWTBL_NEGOTIATED_WINSIZE0_SHIFT                          0
++#define WF_LWTBL_NEGOTIATED_WINSIZE1_DW                             4
++#define WF_LWTBL_NEGOTIATED_WINSIZE1_ADDR                           16
++#define WF_LWTBL_NEGOTIATED_WINSIZE1_MASK \
++	0x00000038 // 5- 3
++#define WF_LWTBL_NEGOTIATED_WINSIZE1_SHIFT                          3
++#define WF_LWTBL_NEGOTIATED_WINSIZE2_DW                             4
++#define WF_LWTBL_NEGOTIATED_WINSIZE2_ADDR                           16
++#define WF_LWTBL_NEGOTIATED_WINSIZE2_MASK \
++	0x000001c0 // 8- 6
++#define WF_LWTBL_NEGOTIATED_WINSIZE2_SHIFT                          6
++#define WF_LWTBL_NEGOTIATED_WINSIZE3_DW                             4
++#define WF_LWTBL_NEGOTIATED_WINSIZE3_ADDR                           16
++#define WF_LWTBL_NEGOTIATED_WINSIZE3_MASK \
++	0x00000e00 // 11- 9
++#define WF_LWTBL_NEGOTIATED_WINSIZE3_SHIFT                          9
++#define WF_LWTBL_NEGOTIATED_WINSIZE4_DW                             4
++#define WF_LWTBL_NEGOTIATED_WINSIZE4_ADDR                           16
++#define WF_LWTBL_NEGOTIATED_WINSIZE4_MASK \
++	0x00007000 // 14-12
++#define WF_LWTBL_NEGOTIATED_WINSIZE4_SHIFT                          12
++#define WF_LWTBL_NEGOTIATED_WINSIZE5_DW                             4
++#define WF_LWTBL_NEGOTIATED_WINSIZE5_ADDR                           16
++#define WF_LWTBL_NEGOTIATED_WINSIZE5_MASK \
++	0x00038000 // 17-15
++#define WF_LWTBL_NEGOTIATED_WINSIZE5_SHIFT                          15
++#define WF_LWTBL_NEGOTIATED_WINSIZE6_DW                             4
++#define WF_LWTBL_NEGOTIATED_WINSIZE6_ADDR                           16
++#define WF_LWTBL_NEGOTIATED_WINSIZE6_MASK \
++	0x001c0000 // 20-18
++#define WF_LWTBL_NEGOTIATED_WINSIZE6_SHIFT                          18
++#define WF_LWTBL_NEGOTIATED_WINSIZE7_DW                             4
++#define WF_LWTBL_NEGOTIATED_WINSIZE7_ADDR                           16
++#define WF_LWTBL_NEGOTIATED_WINSIZE7_MASK \
++	0x00e00000 // 23-21
++#define WF_LWTBL_NEGOTIATED_WINSIZE7_SHIFT                          21
++#define WF_LWTBL_PE_DW                                              4
++#define WF_LWTBL_PE_ADDR                                            16
++#define WF_LWTBL_PE_MASK \
++	0x03000000 // 25-24
++#define WF_LWTBL_PE_SHIFT                                           24
++#define WF_LWTBL_DIS_RHTR_DW                                        4
++#define WF_LWTBL_DIS_RHTR_ADDR                                      16
++#define WF_LWTBL_DIS_RHTR_MASK \
++	0x04000000 // 26-26
++#define WF_LWTBL_DIS_RHTR_SHIFT                                     26
++#define WF_LWTBL_LDPC_HT_DW                                         4
++#define WF_LWTBL_LDPC_HT_ADDR                                       16
++#define WF_LWTBL_LDPC_HT_MASK \
++	0x08000000 // 27-27
++#define WF_LWTBL_LDPC_HT_SHIFT                                      27
++#define WF_LWTBL_LDPC_VHT_DW                                        4
++#define WF_LWTBL_LDPC_VHT_ADDR                                      16
++#define WF_LWTBL_LDPC_VHT_MASK \
++	0x10000000 // 28-28
++#define WF_LWTBL_LDPC_VHT_SHIFT                                     28
++#define WF_LWTBL_LDPC_HE_DW                                         4
++#define WF_LWTBL_LDPC_HE_ADDR                                       16
++#define WF_LWTBL_LDPC_HE_MASK \
++	0x20000000 // 29-29
++#define WF_LWTBL_LDPC_HE_SHIFT                                      29
++#define WF_LWTBL_LDPC_EHT_DW                                        4
++#define WF_LWTBL_LDPC_EHT_ADDR                                      16
++#define WF_LWTBL_LDPC_EHT_MASK \
++	0x40000000 // 30-30
++#define WF_LWTBL_LDPC_EHT_SHIFT                                     30
++#define WF_LWTBL_BA_MODE_DW                                         4
++#define WF_LWTBL_BA_MODE_ADDR                                       16
++#define WF_LWTBL_BA_MODE_MASK \
++	0x80000000 // 31-31
++#define WF_LWTBL_BA_MODE_SHIFT                                      31
++// DW5
++#define WF_LWTBL_AF_DW                                              5
++#define WF_LWTBL_AF_ADDR                                            20
++#define WF_LWTBL_AF_MASK \
++	0x00000007 // 2- 0
++#define WF_LWTBL_AF_MASK_7992 \
++	0x0000000f // 3- 0
++#define WF_LWTBL_AF_SHIFT                                           0
++#define WF_LWTBL_AF_HE_DW                                           5
++#define WF_LWTBL_AF_HE_ADDR                                         20
++#define WF_LWTBL_AF_HE_MASK \
++	0x00000018 // 4- 3
++#define WF_LWTBL_AF_HE_SHIFT                                        3
++#define WF_LWTBL_RTS_DW                                             5
++#define WF_LWTBL_RTS_ADDR                                           20
++#define WF_LWTBL_RTS_MASK \
++	0x00000020 // 5- 5
++#define WF_LWTBL_RTS_SHIFT                                          5
++#define WF_LWTBL_SMPS_DW                                            5
++#define WF_LWTBL_SMPS_ADDR                                          20
++#define WF_LWTBL_SMPS_MASK \
++	0x00000040 // 6- 6
++#define WF_LWTBL_SMPS_SHIFT                                         6
++#define WF_LWTBL_DYN_BW_DW                                          5
++#define WF_LWTBL_DYN_BW_ADDR                                        20
++#define WF_LWTBL_DYN_BW_MASK \
++	0x00000080 // 7- 7
++#define WF_LWTBL_DYN_BW_SHIFT                                       7
++#define WF_LWTBL_MMSS_DW                                            5
++#define WF_LWTBL_MMSS_ADDR                                          20
++#define WF_LWTBL_MMSS_MASK \
++	0x00000700 // 10- 8
++#define WF_LWTBL_MMSS_SHIFT                                         8
++#define WF_LWTBL_USR_DW                                             5
++#define WF_LWTBL_USR_ADDR                                           20
++#define WF_LWTBL_USR_MASK \
++	0x00000800 // 11-11
++#define WF_LWTBL_USR_SHIFT                                          11
++#define WF_LWTBL_SR_R_DW                                            5
++#define WF_LWTBL_SR_R_ADDR                                          20
++#define WF_LWTBL_SR_R_MASK \
++	0x00007000 // 14-12
++#define WF_LWTBL_SR_R_SHIFT                                         12
++#define WF_LWTBL_SR_ABORT_DW                                        5
++#define WF_LWTBL_SR_ABORT_ADDR                                      20
++#define WF_LWTBL_SR_ABORT_MASK \
++	0x00008000 // 15-15
++#define WF_LWTBL_SR_ABORT_SHIFT                                     15
++#define WF_LWTBL_TX_POWER_OFFSET_DW                                 5
++#define WF_LWTBL_TX_POWER_OFFSET_ADDR                               20
++#define WF_LWTBL_TX_POWER_OFFSET_MASK \
++	0x003f0000 // 21-16
++#define WF_LWTBL_TX_POWER_OFFSET_SHIFT                              16
++#define WF_LWTBL_LTF_EHT_DW                                         5
++#define WF_LWTBL_LTF_EHT_ADDR                                       20
++#define WF_LWTBL_LTF_EHT_MASK \
++	0x00c00000 // 23-22
++#define WF_LWTBL_LTF_EHT_SHIFT                                      22
++#define WF_LWTBL_GI_EHT_DW                                          5
++#define WF_LWTBL_GI_EHT_ADDR                                        20
++#define WF_LWTBL_GI_EHT_MASK \
++	0x03000000 // 25-24
++#define WF_LWTBL_GI_EHT_SHIFT                                       24
++#define WF_LWTBL_DOPPL_DW                                           5
++#define WF_LWTBL_DOPPL_ADDR                                         20
++#define WF_LWTBL_DOPPL_MASK \
++	0x04000000 // 26-26
++#define WF_LWTBL_DOPPL_SHIFT                                        26
++#define WF_LWTBL_TXOP_PS_CAP_DW                                     5
++#define WF_LWTBL_TXOP_PS_CAP_ADDR                                   20
++#define WF_LWTBL_TXOP_PS_CAP_MASK \
++	0x08000000 // 27-27
++#define WF_LWTBL_TXOP_PS_CAP_SHIFT                                  27
++#define WF_LWTBL_DU_I_PSM_DW                                        5
++#define WF_LWTBL_DU_I_PSM_ADDR                                      20
++#define WF_LWTBL_DU_I_PSM_MASK \
++	0x10000000 // 28-28
++#define WF_LWTBL_DU_I_PSM_SHIFT                                     28
++#define WF_LWTBL_I_PSM_DW                                           5
++#define WF_LWTBL_I_PSM_ADDR                                         20
++#define WF_LWTBL_I_PSM_MASK \
++	0x20000000 // 29-29
++#define WF_LWTBL_I_PSM_SHIFT                                        29
++#define WF_LWTBL_PSM_DW                                             5
++#define WF_LWTBL_PSM_ADDR                                           20
++#define WF_LWTBL_PSM_MASK \
++	0x40000000 // 30-30
++#define WF_LWTBL_PSM_SHIFT                                          30
++#define WF_LWTBL_SKIP_TX_DW                                         5
++#define WF_LWTBL_SKIP_TX_ADDR                                       20
++#define WF_LWTBL_SKIP_TX_MASK \
++	0x80000000 // 31-31
++#define WF_LWTBL_SKIP_TX_SHIFT                                      31
++// DW6
++#define WF_LWTBL_CBRN_DW                                            6
++#define WF_LWTBL_CBRN_ADDR                                          24
++#define WF_LWTBL_CBRN_MASK \
++	0x00000007 // 2- 0
++#define WF_LWTBL_CBRN_SHIFT                                         0
++#define WF_LWTBL_DBNSS_EN_DW                                        6
++#define WF_LWTBL_DBNSS_EN_ADDR                                      24
++#define WF_LWTBL_DBNSS_EN_MASK \
++	0x00000008 // 3- 3
++#define WF_LWTBL_DBNSS_EN_SHIFT                                     3
++#define WF_LWTBL_BAF_EN_DW                                          6
++#define WF_LWTBL_BAF_EN_ADDR                                        24
++#define WF_LWTBL_BAF_EN_MASK \
++	0x00000010 // 4- 4
++#define WF_LWTBL_BAF_EN_SHIFT                                       4
++#define WF_LWTBL_RDGBA_DW                                           6
++#define WF_LWTBL_RDGBA_ADDR                                         24
++#define WF_LWTBL_RDGBA_MASK \
++	0x00000020 // 5- 5
++#define WF_LWTBL_RDGBA_SHIFT                                        5
++#define WF_LWTBL_R_DW                                               6
++#define WF_LWTBL_R_ADDR                                             24
++#define WF_LWTBL_R_MASK \
++	0x00000040 // 6- 6
++#define WF_LWTBL_R_SHIFT                                            6
++#define WF_LWTBL_SPE_IDX_DW                                         6
++#define WF_LWTBL_SPE_IDX_ADDR                                       24
++#define WF_LWTBL_SPE_IDX_MASK \
++	0x00000f80 // 11- 7
++#define WF_LWTBL_SPE_IDX_SHIFT                                      7
++#define WF_LWTBL_G2_DW                                              6
++#define WF_LWTBL_G2_ADDR                                            24
++#define WF_LWTBL_G2_MASK \
++	0x00001000 // 12-12
++#define WF_LWTBL_G2_SHIFT                                           12
++#define WF_LWTBL_G4_DW                                              6
++#define WF_LWTBL_G4_ADDR                                            24
++#define WF_LWTBL_G4_MASK \
++	0x00002000 // 13-13
++#define WF_LWTBL_G4_SHIFT                                           13
++#define WF_LWTBL_G8_DW                                              6
++#define WF_LWTBL_G8_ADDR                                            24
++#define WF_LWTBL_G8_MASK \
++	0x00004000 // 14-14
++#define WF_LWTBL_G8_SHIFT                                           14
++#define WF_LWTBL_G16_DW                                             6
++#define WF_LWTBL_G16_ADDR                                           24
++#define WF_LWTBL_G16_MASK \
++	0x00008000 // 15-15
++#define WF_LWTBL_G16_SHIFT                                          15
++#define WF_LWTBL_G2_LTF_DW                                          6
++#define WF_LWTBL_G2_LTF_ADDR                                        24
++#define WF_LWTBL_G2_LTF_MASK \
++	0x00030000 // 17-16
++#define WF_LWTBL_G2_LTF_SHIFT                                       16
++#define WF_LWTBL_G4_LTF_DW                                          6
++#define WF_LWTBL_G4_LTF_ADDR                                        24
++#define WF_LWTBL_G4_LTF_MASK \
++	0x000c0000 // 19-18
++#define WF_LWTBL_G4_LTF_SHIFT                                       18
++#define WF_LWTBL_G8_LTF_DW                                          6
++#define WF_LWTBL_G8_LTF_ADDR                                        24
++#define WF_LWTBL_G8_LTF_MASK \
++	0x00300000 // 21-20
++#define WF_LWTBL_G8_LTF_SHIFT                                       20
++#define WF_LWTBL_G16_LTF_DW                                         6
++#define WF_LWTBL_G16_LTF_ADDR                                       24
++#define WF_LWTBL_G16_LTF_MASK \
++	0x00c00000 // 23-22
++#define WF_LWTBL_G16_LTF_SHIFT                                      22
++#define WF_LWTBL_G2_HE_DW                                           6
++#define WF_LWTBL_G2_HE_ADDR                                         24
++#define WF_LWTBL_G2_HE_MASK \
++	0x03000000 // 25-24
++#define WF_LWTBL_G2_HE_SHIFT                                        24
++#define WF_LWTBL_G4_HE_DW                                           6
++#define WF_LWTBL_G4_HE_ADDR                                         24
++#define WF_LWTBL_G4_HE_MASK \
++	0x0c000000 // 27-26
++#define WF_LWTBL_G4_HE_SHIFT                                        26
++#define WF_LWTBL_G8_HE_DW                                           6
++#define WF_LWTBL_G8_HE_ADDR                                         24
++#define WF_LWTBL_G8_HE_MASK \
++	0x30000000 // 29-28
++#define WF_LWTBL_G8_HE_SHIFT                                        28
++#define WF_LWTBL_G16_HE_DW                                          6
++#define WF_LWTBL_G16_HE_ADDR                                        24
++#define WF_LWTBL_G16_HE_MASK \
++	0xc0000000 // 31-30
++#define WF_LWTBL_G16_HE_SHIFT                                       30
++// DW7
++#define WF_LWTBL_BA_WIN_SIZE0_DW                                    7
++#define WF_LWTBL_BA_WIN_SIZE0_ADDR                                  28
++#define WF_LWTBL_BA_WIN_SIZE0_MASK \
++	0x0000000f // 3- 0
++#define WF_LWTBL_BA_WIN_SIZE0_SHIFT                                 0
++#define WF_LWTBL_BA_WIN_SIZE1_DW                                    7
++#define WF_LWTBL_BA_WIN_SIZE1_ADDR                                  28
++#define WF_LWTBL_BA_WIN_SIZE1_MASK \
++	0x000000f0 // 7- 4
++#define WF_LWTBL_BA_WIN_SIZE1_SHIFT                                 4
++#define WF_LWTBL_BA_WIN_SIZE2_DW                                    7
++#define WF_LWTBL_BA_WIN_SIZE2_ADDR                                  28
++#define WF_LWTBL_BA_WIN_SIZE2_MASK \
++	0x00000f00 // 11- 8
++#define WF_LWTBL_BA_WIN_SIZE2_SHIFT                                 8
++#define WF_LWTBL_BA_WIN_SIZE3_DW                                    7
++#define WF_LWTBL_BA_WIN_SIZE3_ADDR                                  28
++#define WF_LWTBL_BA_WIN_SIZE3_MASK \
++	0x0000f000 // 15-12
++#define WF_LWTBL_BA_WIN_SIZE3_SHIFT                                 12
++#define WF_LWTBL_BA_WIN_SIZE4_DW                                    7
++#define WF_LWTBL_BA_WIN_SIZE4_ADDR                                  28
++#define WF_LWTBL_BA_WIN_SIZE4_MASK \
++	0x000f0000 // 19-16
++#define WF_LWTBL_BA_WIN_SIZE4_SHIFT                                 16
++#define WF_LWTBL_BA_WIN_SIZE5_DW                                    7
++#define WF_LWTBL_BA_WIN_SIZE5_ADDR                                  28
++#define WF_LWTBL_BA_WIN_SIZE5_MASK \
++	0x00f00000 // 23-20
++#define WF_LWTBL_BA_WIN_SIZE5_SHIFT                                 20
++#define WF_LWTBL_BA_WIN_SIZE6_DW                                    7
++#define WF_LWTBL_BA_WIN_SIZE6_ADDR                                  28
++#define WF_LWTBL_BA_WIN_SIZE6_MASK \
++	0x0f000000 // 27-24
++#define WF_LWTBL_BA_WIN_SIZE6_SHIFT                                 24
++#define WF_LWTBL_BA_WIN_SIZE7_DW                                    7
++#define WF_LWTBL_BA_WIN_SIZE7_ADDR                                  28
++#define WF_LWTBL_BA_WIN_SIZE7_MASK \
++	0xf0000000 // 31-28
++#define WF_LWTBL_BA_WIN_SIZE7_SHIFT                                 28
++// DW8
++#define WF_LWTBL_AC0_RTS_FAIL_CNT_DW                                8
++#define WF_LWTBL_AC0_RTS_FAIL_CNT_ADDR                              32
++#define WF_LWTBL_AC0_RTS_FAIL_CNT_MASK \
++	0x0000001f // 4- 0
++#define WF_LWTBL_AC0_RTS_FAIL_CNT_SHIFT                             0
++#define WF_LWTBL_AC1_RTS_FAIL_CNT_DW                                8
++#define WF_LWTBL_AC1_RTS_FAIL_CNT_ADDR                              32
++#define WF_LWTBL_AC1_RTS_FAIL_CNT_MASK \
++	0x000003e0 // 9- 5
++#define WF_LWTBL_AC1_RTS_FAIL_CNT_SHIFT                             5
++#define WF_LWTBL_AC2_RTS_FAIL_CNT_DW                                8
++#define WF_LWTBL_AC2_RTS_FAIL_CNT_ADDR                              32
++#define WF_LWTBL_AC2_RTS_FAIL_CNT_MASK \
++	0x00007c00 // 14-10
++#define WF_LWTBL_AC2_RTS_FAIL_CNT_SHIFT                             10
++#define WF_LWTBL_AC3_RTS_FAIL_CNT_DW                                8
++#define WF_LWTBL_AC3_RTS_FAIL_CNT_ADDR                              32
++#define WF_LWTBL_AC3_RTS_FAIL_CNT_MASK \
++	0x000f8000 // 19-15
++#define WF_LWTBL_AC3_RTS_FAIL_CNT_SHIFT                             15
++#define WF_LWTBL_PARTIAL_AID_DW                                     8
++#define WF_LWTBL_PARTIAL_AID_ADDR                                   32
++#define WF_LWTBL_PARTIAL_AID_MASK \
++	0x1ff00000 // 28-20
++#define WF_LWTBL_PARTIAL_AID_SHIFT                                  20
++#define WF_LWTBL_CHK_PER_DW                                         8
++#define WF_LWTBL_CHK_PER_ADDR                                       32
++#define WF_LWTBL_CHK_PER_MASK \
++	0x80000000 // 31-31
++#define WF_LWTBL_CHK_PER_SHIFT                                      31
++// DW9
++#define WF_LWTBL_RX_AVG_MPDU_SIZE_DW                                9
++#define WF_LWTBL_RX_AVG_MPDU_SIZE_ADDR                              36
++#define WF_LWTBL_RX_AVG_MPDU_SIZE_MASK \
++	0x00003fff // 13- 0
++#define WF_LWTBL_RX_AVG_MPDU_SIZE_SHIFT                             0
++#define WF_LWTBL_PRITX_SW_MODE_DW                                   9
++#define WF_LWTBL_PRITX_SW_MODE_ADDR                                 36
++#define WF_LWTBL_PRITX_SW_MODE_MASK \
++	0x00008000 // 15-15
++#define WF_LWTBL_PRITX_SW_MODE_SHIFT                                15
++#define WF_LWTBL_PRITX_SW_MODE_MASK_7992 \
++	0x00004000 // 14-14
++#define WF_LWTBL_PRITX_SW_MODE_SHIFT_7992                           14
++#define WF_LWTBL_PRITX_ERSU_DW                                      9
++#define WF_LWTBL_PRITX_ERSU_ADDR                                    36
++#define WF_LWTBL_PRITX_ERSU_MASK \
++	0x00010000 // 16-16
++#define WF_LWTBL_PRITX_ERSU_SHIFT                                   16
++#define WF_LWTBL_PRITX_ERSU_MASK_7992 \
++	0x00008000 // 15-15
++#define WF_LWTBL_PRITX_ERSU_SHIFT_7992                              15
++#define WF_LWTBL_PRITX_PLR_DW                                       9
++#define WF_LWTBL_PRITX_PLR_ADDR                                     36
++#define WF_LWTBL_PRITX_PLR_MASK \
++	0x00020000 // 17-17
++#define WF_LWTBL_PRITX_PLR_SHIFT                                    17
++#define WF_LWTBL_PRITX_PLR_MASK_7992 \
++	0x00030000 // 17-16
++#define WF_LWTBL_PRITX_PLR_SHIFT_7992                               16
++#define WF_LWTBL_PRITX_DCM_DW                                       9
++#define WF_LWTBL_PRITX_DCM_ADDR                                     36
++#define WF_LWTBL_PRITX_DCM_MASK \
++	0x00040000 // 18-18
++#define WF_LWTBL_PRITX_DCM_SHIFT                                    18
++#define WF_LWTBL_PRITX_ER106T_DW                                    9
++#define WF_LWTBL_PRITX_ER106T_ADDR                                  36
++#define WF_LWTBL_PRITX_ER106T_MASK \
++	0x00080000 // 19-19
++#define WF_LWTBL_PRITX_ER106T_SHIFT                                 19
++#define WF_LWTBL_FCAP_DW                                            9
++#define WF_LWTBL_FCAP_ADDR                                          36
++#define WF_LWTBL_FCAP_MASK \
++	0x00700000 // 22-20
++#define WF_LWTBL_FCAP_SHIFT                                         20
++#define WF_LWTBL_MPDU_FAIL_CNT_DW                                   9
++#define WF_LWTBL_MPDU_FAIL_CNT_ADDR                                 36
++#define WF_LWTBL_MPDU_FAIL_CNT_MASK \
++	0x03800000 // 25-23
++#define WF_LWTBL_MPDU_FAIL_CNT_SHIFT                                23
++#define WF_LWTBL_MPDU_OK_CNT_DW                                     9
++#define WF_LWTBL_MPDU_OK_CNT_ADDR                                   36
++#define WF_LWTBL_MPDU_OK_CNT_MASK \
++	0x1c000000 // 28-26
++#define WF_LWTBL_MPDU_OK_CNT_SHIFT                                  26
++#define WF_LWTBL_RATE_IDX_DW                                        9
++#define WF_LWTBL_RATE_IDX_ADDR                                      36
++#define WF_LWTBL_RATE_IDX_MASK \
++	0xe0000000 // 31-29
++#define WF_LWTBL_RATE_IDX_SHIFT                                     29
++// DW10
++#define WF_LWTBL_RATE1_DW                                           10
++#define WF_LWTBL_RATE1_ADDR                                         40
++#define WF_LWTBL_RATE1_MASK \
++	0x00007fff // 14- 0
++#define WF_LWTBL_RATE1_SHIFT                                        0
++#define WF_LWTBL_RATE2_DW                                           10
++#define WF_LWTBL_RATE2_ADDR                                         40
++#define WF_LWTBL_RATE2_MASK \
++	0x7fff0000 // 30-16
++#define WF_LWTBL_RATE2_SHIFT                                        16
++// DW11
++#define WF_LWTBL_RATE3_DW                                           11
++#define WF_LWTBL_RATE3_ADDR                                         44
++#define WF_LWTBL_RATE3_MASK \
++	0x00007fff // 14- 0
++#define WF_LWTBL_RATE3_SHIFT                                        0
++#define WF_LWTBL_RATE4_DW                                           11
++#define WF_LWTBL_RATE4_ADDR                                         44
++#define WF_LWTBL_RATE4_MASK \
++	0x7fff0000 // 30-16
++#define WF_LWTBL_RATE4_SHIFT                                        16
++// DW12
++#define WF_LWTBL_RATE5_DW                                           12
++#define WF_LWTBL_RATE5_ADDR                                         48
++#define WF_LWTBL_RATE5_MASK \
++	0x00007fff // 14- 0
++#define WF_LWTBL_RATE5_SHIFT                                        0
++#define WF_LWTBL_RATE6_DW                                           12
++#define WF_LWTBL_RATE6_ADDR                                         48
++#define WF_LWTBL_RATE6_MASK \
++	0x7fff0000 // 30-16
++#define WF_LWTBL_RATE6_SHIFT                                        16
++// DW13
++#define WF_LWTBL_RATE7_DW                                           13
++#define WF_LWTBL_RATE7_ADDR                                         52
++#define WF_LWTBL_RATE7_MASK \
++	0x00007fff // 14- 0
++#define WF_LWTBL_RATE7_SHIFT                                        0
++#define WF_LWTBL_RATE8_DW                                           13
++#define WF_LWTBL_RATE8_ADDR                                         52
++#define WF_LWTBL_RATE8_MASK \
++	0x7fff0000 // 30-16
++#define WF_LWTBL_RATE8_SHIFT                                        16
++// DW14
++#define WF_LWTBL_RATE1_TX_CNT_DW                                    14
++#define WF_LWTBL_RATE1_TX_CNT_ADDR                                  56
++#define WF_LWTBL_RATE1_TX_CNT_MASK \
++	0x0000ffff // 15- 0
++#define WF_LWTBL_RATE1_TX_CNT_SHIFT                                 0
++#define WF_LWTBL_CIPHER_SUIT_IGTK_DW                                14
++#define WF_LWTBL_CIPHER_SUIT_IGTK_ADDR                              56
++#define WF_LWTBL_CIPHER_SUIT_IGTK_MASK \
++	0x00003000 // 13-12
++#define WF_LWTBL_CIPHER_SUIT_IGTK_SHIFT                             12
++#define WF_LWTBL_CIPHER_SUIT_BIGTK_DW                               14
++#define WF_LWTBL_CIPHER_SUIT_BIGTK_ADDR                             56
++#define WF_LWTBL_CIPHER_SUIT_BIGTK_MASK \
++	0x0000c000 // 15-14
++#define WF_LWTBL_CIPHER_SUIT_BIGTK_SHIFT                            14
++#define WF_LWTBL_RATE1_FAIL_CNT_DW                                  14
++#define WF_LWTBL_RATE1_FAIL_CNT_ADDR                                56
++#define WF_LWTBL_RATE1_FAIL_CNT_MASK \
++	0xffff0000 // 31-16
++#define WF_LWTBL_RATE1_FAIL_CNT_SHIFT                               16
++// DW15
++#define WF_LWTBL_RATE2_OK_CNT_DW                                    15
++#define WF_LWTBL_RATE2_OK_CNT_ADDR                                  60
++#define WF_LWTBL_RATE2_OK_CNT_MASK \
++	0x0000ffff // 15- 0
++#define WF_LWTBL_RATE2_OK_CNT_SHIFT                                 0
++#define WF_LWTBL_RATE3_OK_CNT_DW                                    15
++#define WF_LWTBL_RATE3_OK_CNT_ADDR                                  60
++#define WF_LWTBL_RATE3_OK_CNT_MASK \
++	0xffff0000 // 31-16
++#define WF_LWTBL_RATE3_OK_CNT_SHIFT                                 16
++// DW16
++#define WF_LWTBL_CURRENT_BW_TX_CNT_DW                               16
++#define WF_LWTBL_CURRENT_BW_TX_CNT_ADDR                             64
++#define WF_LWTBL_CURRENT_BW_TX_CNT_MASK \
++	0x0000ffff // 15- 0
++#define WF_LWTBL_CURRENT_BW_TX_CNT_SHIFT                            0
++#define WF_LWTBL_CURRENT_BW_FAIL_CNT_DW                             16
++#define WF_LWTBL_CURRENT_BW_FAIL_CNT_ADDR                           64
++#define WF_LWTBL_CURRENT_BW_FAIL_CNT_MASK \
++	0xffff0000 // 31-16
++#define WF_LWTBL_CURRENT_BW_FAIL_CNT_SHIFT                          16
++// DW17
++#define WF_LWTBL_OTHER_BW_TX_CNT_DW                                 17
++#define WF_LWTBL_OTHER_BW_TX_CNT_ADDR                               68
++#define WF_LWTBL_OTHER_BW_TX_CNT_MASK \
++	0x0000ffff // 15- 0
++#define WF_LWTBL_OTHER_BW_TX_CNT_SHIFT                              0
++#define WF_LWTBL_OTHER_BW_FAIL_CNT_DW                               17
++#define WF_LWTBL_OTHER_BW_FAIL_CNT_ADDR                             68
++#define WF_LWTBL_OTHER_BW_FAIL_CNT_MASK \
++	0xffff0000 // 31-16
++#define WF_LWTBL_OTHER_BW_FAIL_CNT_SHIFT                            16
++// DW18
++#define WF_LWTBL_RTS_OK_CNT_DW                                      18
++#define WF_LWTBL_RTS_OK_CNT_ADDR                                    72
++#define WF_LWTBL_RTS_OK_CNT_MASK \
++	0x0000ffff // 15- 0
++#define WF_LWTBL_RTS_OK_CNT_SHIFT                                   0
++#define WF_LWTBL_RTS_FAIL_CNT_DW                                    18
++#define WF_LWTBL_RTS_FAIL_CNT_ADDR                                  72
++#define WF_LWTBL_RTS_FAIL_CNT_MASK \
++	0xffff0000 // 31-16
++#define WF_LWTBL_RTS_FAIL_CNT_SHIFT                                 16
++// DW19
++#define WF_LWTBL_DATA_RETRY_CNT_DW                                  19
++#define WF_LWTBL_DATA_RETRY_CNT_ADDR                                76
++#define WF_LWTBL_DATA_RETRY_CNT_MASK \
++	0x0000ffff // 15- 0
++#define WF_LWTBL_DATA_RETRY_CNT_SHIFT                               0
++#define WF_LWTBL_MGNT_RETRY_CNT_DW                                  19
++#define WF_LWTBL_MGNT_RETRY_CNT_ADDR                                76
++#define WF_LWTBL_MGNT_RETRY_CNT_MASK \
++	0xffff0000 // 31-16
++#define WF_LWTBL_MGNT_RETRY_CNT_SHIFT                               16
++// DW20
++#define WF_LWTBL_AC0_CTT_CDT_CRB_DW                                 20
++#define WF_LWTBL_AC0_CTT_CDT_CRB_ADDR                               80
++#define WF_LWTBL_AC0_CTT_CDT_CRB_MASK \
++	0xffffffff // 31- 0
++#define WF_LWTBL_AC0_CTT_CDT_CRB_SHIFT                              0
++// DW21
++// DO NOT process repeat field(adm[0])
++// DW22
++#define WF_LWTBL_AC1_CTT_CDT_CRB_DW                                 22
++#define WF_LWTBL_AC1_CTT_CDT_CRB_ADDR                               88
++#define WF_LWTBL_AC1_CTT_CDT_CRB_MASK \
++	0xffffffff // 31- 0
++#define WF_LWTBL_AC1_CTT_CDT_CRB_SHIFT                              0
++// DW23
++// DO NOT process repeat field(adm[1])
++// DW24
++#define WF_LWTBL_AC2_CTT_CDT_CRB_DW                                 24
++#define WF_LWTBL_AC2_CTT_CDT_CRB_ADDR                               96
++#define WF_LWTBL_AC2_CTT_CDT_CRB_MASK \
++	0xffffffff // 31- 0
++#define WF_LWTBL_AC2_CTT_CDT_CRB_SHIFT                              0
++// DW25
++// DO NOT process repeat field(adm[2])
++// DW26
++#define WF_LWTBL_AC3_CTT_CDT_CRB_DW                                 26
++#define WF_LWTBL_AC3_CTT_CDT_CRB_ADDR                               104
++#define WF_LWTBL_AC3_CTT_CDT_CRB_MASK \
++	0xffffffff // 31- 0
++#define WF_LWTBL_AC3_CTT_CDT_CRB_SHIFT                              0
++// DW27
++// DO NOT process repeat field(adm[3])
++// DW28
++#define WF_LWTBL_RELATED_IDX0_DW                                    28
++#define WF_LWTBL_RELATED_IDX0_ADDR                                  112
++#define WF_LWTBL_RELATED_IDX0_MASK \
++	0x00000fff // 11- 0
++#define WF_LWTBL_RELATED_IDX0_SHIFT                                 0
++#define WF_LWTBL_RELATED_BAND0_DW                                   28
++#define WF_LWTBL_RELATED_BAND0_ADDR                                 112
++#define WF_LWTBL_RELATED_BAND0_MASK \
++	0x00003000 // 13-12
++#define WF_LWTBL_RELATED_BAND0_SHIFT                                12
++#define WF_LWTBL_PRIMARY_MLD_BAND_DW                                28
++#define WF_LWTBL_PRIMARY_MLD_BAND_ADDR                              112
++#define WF_LWTBL_PRIMARY_MLD_BAND_MASK \
++	0x0000c000 // 15-14
++#define WF_LWTBL_PRIMARY_MLD_BAND_SHIFT                             14
++#define WF_LWTBL_RELATED_IDX1_DW                                    28
++#define WF_LWTBL_RELATED_IDX1_ADDR                                  112
++#define WF_LWTBL_RELATED_IDX1_MASK \
++	0x0fff0000 // 27-16
++#define WF_LWTBL_RELATED_IDX1_SHIFT                                 16
++#define WF_LWTBL_RELATED_BAND1_DW                                   28
++#define WF_LWTBL_RELATED_BAND1_ADDR                                 112
++#define WF_LWTBL_RELATED_BAND1_MASK \
++	0x30000000 // 29-28
++#define WF_LWTBL_RELATED_BAND1_SHIFT                                28
++#define WF_LWTBL_SECONDARY_MLD_BAND_DW                              28
++#define WF_LWTBL_SECONDARY_MLD_BAND_ADDR                            112
++#define WF_LWTBL_SECONDARY_MLD_BAND_MASK \
++	0xc0000000 // 31-30
++#define WF_LWTBL_SECONDARY_MLD_BAND_SHIFT                           30
++// DW29
++#define WF_LWTBL_DISPATCH_POLICY0_DW                                29
++#define WF_LWTBL_DISPATCH_POLICY0_ADDR                              116
++#define WF_LWTBL_DISPATCH_POLICY0_MASK \
++	0x00000003 // 1- 0
++#define WF_LWTBL_DISPATCH_POLICY0_SHIFT                             0
++#define WF_LWTBL_DISPATCH_POLICY1_DW                                29
++#define WF_LWTBL_DISPATCH_POLICY1_ADDR                              116
++#define WF_LWTBL_DISPATCH_POLICY1_MASK \
++	0x0000000c // 3- 2
++#define WF_LWTBL_DISPATCH_POLICY1_SHIFT                             2
++#define WF_LWTBL_DISPATCH_POLICY2_DW                                29
++#define WF_LWTBL_DISPATCH_POLICY2_ADDR                              116
++#define WF_LWTBL_DISPATCH_POLICY2_MASK \
++	0x00000030 // 5- 4
++#define WF_LWTBL_DISPATCH_POLICY2_SHIFT                             4
++#define WF_LWTBL_DISPATCH_POLICY3_DW                                29
++#define WF_LWTBL_DISPATCH_POLICY3_ADDR                              116
++#define WF_LWTBL_DISPATCH_POLICY3_MASK \
++	0x000000c0 // 7- 6
++#define WF_LWTBL_DISPATCH_POLICY3_SHIFT                             6
++#define WF_LWTBL_DISPATCH_POLICY4_DW                                29
++#define WF_LWTBL_DISPATCH_POLICY4_ADDR                              116
++#define WF_LWTBL_DISPATCH_POLICY4_MASK \
++	0x00000300 // 9- 8
++#define WF_LWTBL_DISPATCH_POLICY4_SHIFT                             8
++#define WF_LWTBL_DISPATCH_POLICY5_DW                                29
++#define WF_LWTBL_DISPATCH_POLICY5_ADDR                              116
++#define WF_LWTBL_DISPATCH_POLICY5_MASK \
++	0x00000c00 // 11-10
++#define WF_LWTBL_DISPATCH_POLICY5_SHIFT                             10
++#define WF_LWTBL_DISPATCH_POLICY6_DW                                29
++#define WF_LWTBL_DISPATCH_POLICY6_ADDR                              116
++#define WF_LWTBL_DISPATCH_POLICY6_MASK \
++	0x00003000 // 13-12
++#define WF_LWTBL_DISPATCH_POLICY6_SHIFT                             12
++#define WF_LWTBL_DISPATCH_POLICY7_DW                                29
++#define WF_LWTBL_DISPATCH_POLICY7_ADDR                              116
++#define WF_LWTBL_DISPATCH_POLICY7_MASK \
++	0x0000c000 // 15-14
++#define WF_LWTBL_DISPATCH_POLICY7_SHIFT                             14
++#define WF_LWTBL_OWN_MLD_ID_DW                                      29
++#define WF_LWTBL_OWN_MLD_ID_ADDR                                    116
++#define WF_LWTBL_OWN_MLD_ID_MASK \
++	0x003f0000 // 21-16
++#define WF_LWTBL_OWN_MLD_ID_SHIFT                                   16
++#define WF_LWTBL_EMLSR0_DW                                          29
++#define WF_LWTBL_EMLSR0_ADDR                                        116
++#define WF_LWTBL_EMLSR0_MASK \
++	0x00400000 // 22-22
++#define WF_LWTBL_EMLSR0_SHIFT                                       22
++#define WF_LWTBL_EMLMR0_DW                                          29
++#define WF_LWTBL_EMLMR0_ADDR                                        116
++#define WF_LWTBL_EMLMR0_MASK \
++	0x00800000 // 23-23
++#define WF_LWTBL_EMLMR0_SHIFT                                       23
++#define WF_LWTBL_EMLSR1_DW                                          29
++#define WF_LWTBL_EMLSR1_ADDR                                        116
++#define WF_LWTBL_EMLSR1_MASK \
++	0x01000000 // 24-24
++#define WF_LWTBL_EMLSR1_SHIFT                                       24
++#define WF_LWTBL_EMLMR1_DW                                          29
++#define WF_LWTBL_EMLMR1_ADDR                                        116
++#define WF_LWTBL_EMLMR1_MASK \
++	0x02000000 // 25-25
++#define WF_LWTBL_EMLMR1_SHIFT                                       25
++#define WF_LWTBL_EMLSR2_DW                                          29
++#define WF_LWTBL_EMLSR2_ADDR                                        116
++#define WF_LWTBL_EMLSR2_MASK \
++	0x04000000 // 26-26
++#define WF_LWTBL_EMLSR2_SHIFT                                       26
++#define WF_LWTBL_EMLMR2_DW                                          29
++#define WF_LWTBL_EMLMR2_ADDR                                        116
++#define WF_LWTBL_EMLMR2_MASK \
++	0x08000000 // 27-27
++#define WF_LWTBL_EMLMR2_SHIFT                                       27
++#define WF_LWTBL_STR_BITMAP_DW                                      29
++#define WF_LWTBL_STR_BITMAP_ADDR                                    116
++#define WF_LWTBL_STR_BITMAP_MASK \
++	0xe0000000 // 31-29
++#define WF_LWTBL_STR_BITMAP_SHIFT                                   29
++// DW30
++#define WF_LWTBL_DISPATCH_ORDER_DW                                  30
++#define WF_LWTBL_DISPATCH_ORDER_ADDR                                120
++#define WF_LWTBL_DISPATCH_ORDER_MASK \
++	0x0000007f // 6- 0
++#define WF_LWTBL_DISPATCH_ORDER_SHIFT                               0
++#define WF_LWTBL_DISPATCH_RATIO_DW                                  30
++#define WF_LWTBL_DISPATCH_RATIO_ADDR                                120
++#define WF_LWTBL_DISPATCH_RATIO_MASK \
++	0x00003f80 // 13- 7
++#define WF_LWTBL_DISPATCH_RATIO_SHIFT                               7
++#define WF_LWTBL_LINK_MGF_DW                                        30
++#define WF_LWTBL_LINK_MGF_ADDR                                      120
++#define WF_LWTBL_LINK_MGF_MASK \
++	0xffff0000 // 31-16
++#define WF_LWTBL_LINK_MGF_SHIFT                                     16
++// DW31
++#define WF_LWTBL_BFTX_TB_DW                                         31
++#define WF_LWTBL_BFTX_TB_ADDR                                       124
++#define WF_LWTBL_BFTX_TB_MASK \
++	0x00800000 // 23-23
++#define WF_LWTBL_DROP_DW                                            31
++#define WF_LWTBL_DROP_ADDR                                          124
++#define WF_LWTBL_DROP_MASK \
++	0x01000000 // 24-24
++#define WF_LWTBL_DROP_SHIFT                                         24
++#define WF_LWTBL_CASCAD_DW                                          31
++#define WF_LWTBL_CASCAD_ADDR                                        124
++#define WF_LWTBL_CASCAD_MASK \
++	0x02000000 // 25-25
++#define WF_LWTBL_CASCAD_SHIFT                                       25
++#define WF_LWTBL_ALL_ACK_DW                                         31
++#define WF_LWTBL_ALL_ACK_ADDR                                       124
++#define WF_LWTBL_ALL_ACK_MASK \
++	0x04000000 // 26-26
++#define WF_LWTBL_ALL_ACK_SHIFT                                      26
++#define WF_LWTBL_MPDU_SIZE_DW                                       31
++#define WF_LWTBL_MPDU_SIZE_ADDR                                     124
++#define WF_LWTBL_MPDU_SIZE_MASK \
++	0x18000000 // 28-27
++#define WF_LWTBL_MPDU_SIZE_SHIFT                                    27
++#define WF_LWTBL_RXD_DUP_MODE_DW                                    31
++#define WF_LWTBL_RXD_DUP_MODE_ADDR                                  124
++#define WF_LWTBL_RXD_DUP_MODE_MASK \
++	0x60000000 // 30-29
++#define WF_LWTBL_RXD_DUP_MODE_SHIFT                                 29
++#define WF_LWTBL_ACK_EN_DW                                          31
++#define WF_LWTBL_ACK_EN_ADDR                                        128
++#define WF_LWTBL_ACK_EN_MASK \
++	0x80000000 // 31-31
++#define WF_LWTBL_ACK_EN_SHIFT                                       31
++// DW32
++#define WF_LWTBL_OM_INFO_DW                                         32
++#define WF_LWTBL_OM_INFO_ADDR                                       128
++#define WF_LWTBL_OM_INFO_MASK \
++	0x00000fff // 11- 0
++#define WF_LWTBL_OM_INFO_SHIFT                                      0
++#define WF_LWTBL_OM_INFO_EHT_DW                                     32
++#define WF_LWTBL_OM_INFO_EHT_ADDR                                   128
++#define WF_LWTBL_OM_INFO_EHT_MASK \
++	0x0000f000 // 15-12
++#define WF_LWTBL_OM_INFO_EHT_SHIFT                                  12
++#define WF_LWTBL_RXD_DUP_FOR_OM_CHG_DW                              32
++#define WF_LWTBL_RXD_DUP_FOR_OM_CHG_ADDR                            128
++#define WF_LWTBL_RXD_DUP_FOR_OM_CHG_MASK \
++	0x00010000 // 16-16
++#define WF_LWTBL_RXD_DUP_FOR_OM_CHG_SHIFT                           16
++#define WF_LWTBL_RXD_DUP_WHITE_LIST_DW                              32
++#define WF_LWTBL_RXD_DUP_WHITE_LIST_ADDR                            128
++#define WF_LWTBL_RXD_DUP_WHITE_LIST_MASK \
++	0x1ffe0000 // 28-17
++#define WF_LWTBL_RXD_DUP_WHITE_LIST_SHIFT                           17
++// DW33
++#define WF_LWTBL_USER_RSSI_DW                                       33
++#define WF_LWTBL_USER_RSSI_ADDR                                     132
++#define WF_LWTBL_USER_RSSI_MASK \
++	0x000001ff // 8- 0
++#define WF_LWTBL_USER_RSSI_SHIFT                                    0
++#define WF_LWTBL_USER_SNR_DW                                        33
++#define WF_LWTBL_USER_SNR_ADDR                                      132
++#define WF_LWTBL_USER_SNR_MASK \
++	0x00007e00 // 14- 9
++#define WF_LWTBL_USER_SNR_SHIFT                                     9
++#define WF_LWTBL_RAPID_REACTION_RATE_DW                             33
++#define WF_LWTBL_RAPID_REACTION_RATE_ADDR                           132
++#define WF_LWTBL_RAPID_REACTION_RATE_MASK \
++	0x0fff0000 // 27-16
++#define WF_LWTBL_RAPID_REACTION_RATE_SHIFT                          16
++#define WF_LWTBL_HT_AMSDU_DW                                        33
++#define WF_LWTBL_HT_AMSDU_ADDR                                      132
++#define WF_LWTBL_HT_AMSDU_MASK \
++	0x40000000 // 30-30
++#define WF_LWTBL_HT_AMSDU_SHIFT                                     30
++#define WF_LWTBL_AMSDU_CROSS_LG_DW                                  33
++#define WF_LWTBL_AMSDU_CROSS_LG_ADDR                                132
++#define WF_LWTBL_AMSDU_CROSS_LG_MASK \
++	0x80000000 // 31-31
++#define WF_LWTBL_AMSDU_CROSS_LG_SHIFT                               31
++// DW34
++#define WF_LWTBL_RESP_RCPI0_DW                                      34
++#define WF_LWTBL_RESP_RCPI0_ADDR                                    136
++#define WF_LWTBL_RESP_RCPI0_MASK \
++	0x000000ff // 7- 0
++#define WF_LWTBL_RESP_RCPI0_SHIFT                                   0
++#define WF_LWTBL_RESP_RCPI1_DW                                      34
++#define WF_LWTBL_RESP_RCPI1_ADDR                                    136
++#define WF_LWTBL_RESP_RCPI1_MASK \
++	0x0000ff00 // 15- 8
++#define WF_LWTBL_RESP_RCPI1_SHIFT                                   8
++#define WF_LWTBL_RESP_RCPI2_DW                                      34
++#define WF_LWTBL_RESP_RCPI2_ADDR                                    136
++#define WF_LWTBL_RESP_RCPI2_MASK \
++	0x00ff0000 // 23-16
++#define WF_LWTBL_RESP_RCPI2_SHIFT                                   16
++#define WF_LWTBL_RESP_RCPI3_DW                                      34
++#define WF_LWTBL_RESP_RCPI3_ADDR                                    136
++#define WF_LWTBL_RESP_RCPI3_MASK \
++	0xff000000 // 31-24
++#define WF_LWTBL_RESP_RCPI3_SHIFT                                   24
++// DW35
++#define WF_LWTBL_SNR_RX0_DW                                         35
++#define WF_LWTBL_SNR_RX0_ADDR                                       140
++#define WF_LWTBL_SNR_RX0_MASK \
++	0x0000003f // 5- 0
++#define WF_LWTBL_SNR_RX0_SHIFT                                      0
++#define WF_LWTBL_SNR_RX1_DW                                         35
++#define WF_LWTBL_SNR_RX1_ADDR                                       140
++#define WF_LWTBL_SNR_RX1_MASK \
++	0x00000fc0 // 11- 6
++#define WF_LWTBL_SNR_RX1_SHIFT                                      6
++#define WF_LWTBL_SNR_RX2_DW                                         35
++#define WF_LWTBL_SNR_RX2_ADDR                                       140
++#define WF_LWTBL_SNR_RX2_MASK \
++	0x0003f000 // 17-12
++#define WF_LWTBL_SNR_RX2_SHIFT                                      12
++#define WF_LWTBL_SNR_RX3_DW                                         35
++#define WF_LWTBL_SNR_RX3_ADDR                                       140
++#define WF_LWTBL_SNR_RX3_MASK \
++	0x00fc0000 // 23-18
++#define WF_LWTBL_SNR_RX3_SHIFT                                      18
++
++/* WTBL Group - Packet Number */
++/* DW 2 */
++#define WTBL_PN0_MASK                   BITS(0, 7)
++#define WTBL_PN0_OFFSET                 0
++#define WTBL_PN1_MASK                   BITS(8, 15)
++#define WTBL_PN1_OFFSET                 8
++#define WTBL_PN2_MASK                   BITS(16, 23)
++#define WTBL_PN2_OFFSET                 16
++#define WTBL_PN3_MASK                   BITS(24, 31)
++#define WTBL_PN3_OFFSET                 24
++
++/* DW 3 */
++#define WTBL_PN4_MASK                   BITS(0, 7)
++#define WTBL_PN4_OFFSET                 0
++#define WTBL_PN5_MASK                   BITS(8, 15)
++#define WTBL_PN5_OFFSET                 8
++
++/* DW 4 */
++#define WTBL_BIPN0_MASK                 BITS(0, 7)
++#define WTBL_BIPN0_OFFSET               0
++#define WTBL_BIPN1_MASK                 BITS(8, 15)
++#define WTBL_BIPN1_OFFSET               8
++#define WTBL_BIPN2_MASK                 BITS(16, 23)
++#define WTBL_BIPN2_OFFSET               16
++#define WTBL_BIPN3_MASK                 BITS(24, 31)
++#define WTBL_BIPN3_OFFSET               24
++
++/* DW 5 */
++#define WTBL_BIPN4_MASK                 BITS(0, 7)
++#define WTBL_BIPN4_OFFSET               0
++#define WTBL_BIPN5_MASK                 BITS(8, 15)
++#define WTBL_BIPN5_OFFSET               8
++
++/* UWTBL DW 6 */
++#define WTBL_AMSDU_LEN_MASK             BITS(0, 5)
++#define WTBL_AMSDU_LEN_OFFSET           0
++#define WTBL_AMSDU_NUM_MASK             BITS(6, 10)
++#define WTBL_AMSDU_NUM_OFFSET           6
++#define WTBL_AMSDU_EN_MASK              BIT(11)
++#define WTBL_AMSDU_EN_OFFSET            11
++
++/* UWTBL DW 8 */
++#define WTBL_SEC_ADDR_MODE_MASK		BITS(20, 21)
++#define WTBL_SEC_ADDR_MODE_OFFSET	20
++
++/* LWTBL Rate field */
++#define WTBL_RATE_TX_RATE_MASK          BITS(0, 5)
++#define WTBL_RATE_TX_RATE_OFFSET        0
++#define WTBL_RATE_TX_MODE_MASK          BITS(6, 9)
++#define WTBL_RATE_TX_MODE_OFFSET        6
++#define WTBL_RATE_NSTS_MASK             BITS(10, 13)
++#define WTBL_RATE_NSTS_OFFSET           10
++#define WTBL_RATE_STBC_MASK             BIT(14)
++#define WTBL_RATE_STBC_OFFSET           14
++
++/***** WTBL(LMAC) DW Offset *****/
++/* LMAC WTBL Group - Peer Unique Information */
++#define WTBL_GROUP_PEER_INFO_DW_0               0
++#define WTBL_GROUP_PEER_INFO_DW_1               1
++
++/* WTBL Group - TxRx Capability/Information */
++#define WTBL_GROUP_TRX_CAP_DW_2                 2
++#define WTBL_GROUP_TRX_CAP_DW_3                 3
++#define WTBL_GROUP_TRX_CAP_DW_4                 4
++#define WTBL_GROUP_TRX_CAP_DW_5                 5
++#define WTBL_GROUP_TRX_CAP_DW_6                 6
++#define WTBL_GROUP_TRX_CAP_DW_7                 7
++#define WTBL_GROUP_TRX_CAP_DW_8                 8
++#define WTBL_GROUP_TRX_CAP_DW_9                 9
++
++/* WTBL Group - Auto Rate Table*/
++#define WTBL_GROUP_AUTO_RATE_1_2                10
++#define WTBL_GROUP_AUTO_RATE_3_4                11
++#define WTBL_GROUP_AUTO_RATE_5_6                12
++#define WTBL_GROUP_AUTO_RATE_7_8                13
++
++/* WTBL Group - Tx Counter */
++#define WTBL_GROUP_TX_CNT_LINE_1                14
++#define WTBL_GROUP_TX_CNT_LINE_2                15
++#define WTBL_GROUP_TX_CNT_LINE_3                16
++#define WTBL_GROUP_TX_CNT_LINE_4                17
++#define WTBL_GROUP_TX_CNT_LINE_5                18
++#define WTBL_GROUP_TX_CNT_LINE_6                19
++
++/* WTBL Group - Admission Control Counter */
++#define WTBL_GROUP_ADM_CNT_LINE_1               20
++#define WTBL_GROUP_ADM_CNT_LINE_2               21
++#define WTBL_GROUP_ADM_CNT_LINE_3               22
++#define WTBL_GROUP_ADM_CNT_LINE_4               23
++#define WTBL_GROUP_ADM_CNT_LINE_5               24
++#define WTBL_GROUP_ADM_CNT_LINE_6               25
++#define WTBL_GROUP_ADM_CNT_LINE_7               26
++#define WTBL_GROUP_ADM_CNT_LINE_8               27
++
++/* WTBL Group -MLO Info */
++#define WTBL_GROUP_MLO_INFO_LINE_1              28
++#define WTBL_GROUP_MLO_INFO_LINE_2              29
++#define WTBL_GROUP_MLO_INFO_LINE_3              30
++
++/* WTBL Group -RESP Info */
++#define WTBL_GROUP_RESP_INFO_DW_31              31
++
++/* WTBL Group -RX DUP Info */
++#define WTBL_GROUP_RX_DUP_INFO_DW_32            32
++
++/* WTBL Group - Rx Statistics Counter */
++#define WTBL_GROUP_RX_STAT_CNT_LINE_1           33
++#define WTBL_GROUP_RX_STAT_CNT_LINE_2           34
++#define WTBL_GROUP_RX_STAT_CNT_LINE_3           35
++
++/* UWTBL Group - HW AMSDU */
++#define UWTBL_HW_AMSDU_DW                       WF_UWTBL_AMSDU_CFG_DW
++
++/* LWTBL DW 4 */
++#define WTBL_DIS_RHTR                           WF_LWTBL_DIS_RHTR_MASK
++
++/* UWTBL DW 5 */
++#define WTBL_KEY_LINK_DW_KEY_LOC0_MASK          BITS(0, 10)
++#define WTBL_PSM				WF_LWTBL_PSM_MASK
++
++/* Need to sync with FW define */
++#define INVALID_KEY_ENTRY                       WTBL_KEY_LINK_DW_KEY_LOC0_MASK
++
++// RATE
++#define WTBL_RATE_TX_RATE_MASK          	BITS(0, 5)
++#define WTBL_RATE_TX_RATE_OFFSET        	0
++#define WTBL_RATE_TX_MODE_MASK          	BITS(6, 9)
++#define WTBL_RATE_TX_MODE_OFFSET        	6
++#define WTBL_RATE_NSTS_MASK             	BITS(10, 13)
++#define WTBL_RATE_NSTS_OFFSET           	10
++#define WTBL_RATE_STBC_MASK            	 	BIT(14)
++#define WTBL_RATE_STBC_OFFSET          	 	14
++#endif
++
++#endif
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+new file mode 100644
+index 000000000..678b009e7
+--- /dev/null
++++ b/mt7996/mtk_debugfs.c
+@@ -0,0 +1,2484 @@
++// SPDX-License-Identifier: ISC
++/*
++ * Copyright (C) 2023 MediaTek Inc.
++ */
++#include "mt7996.h"
++#include "../mt76.h"
++#include "mcu.h"
++#include "mac.h"
++#include "eeprom.h"
++#include "mtk_debug.h"
++#include "mtk_mcu.h"
++#include "coredump.h"
++
++#ifdef CONFIG_MTK_DEBUG
++
++/* AGG INFO */
++static int
++mt7996_agginfo_read_per_band(struct seq_file *s, int band_idx)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	u64 total_burst, total_ampdu, ampdu_cnt[16];
++	u32 value, idx, row_idx, col_idx, start_range, agg_rang_sel[16], burst_cnt[16], band_offset = 0;
++	u8 partial_str[16] = {}, full_str[64] = {};
++
++	switch (band_idx) {
++	case 0:
++		band_offset = 0;
++		break;
++	case 1:
++		band_offset = BN1_WF_AGG_TOP_BASE - BN0_WF_AGG_TOP_BASE;
++		break;
++	case 2:
++		band_offset = IP1_BN0_WF_AGG_TOP_BASE - BN0_WF_AGG_TOP_BASE;
++		break;
++	default:
++		return 0;
++	}
++
++	seq_printf(s, "Band %d AGG Status\n", band_idx);
++	seq_printf(s, "===============================\n");
++	value = mt76_rr(dev, BN0_WF_AGG_TOP_AALCR0_ADDR + band_offset);
++	seq_printf(s, "AC00 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR0_AC00_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR0_AC00_AGG_LIMIT_SHFT);
++	seq_printf(s, "AC01 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR0_AC01_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR0_AC01_AGG_LIMIT_SHFT);
++	value = mt76_rr(dev, BN0_WF_AGG_TOP_AALCR1_ADDR + band_offset);
++	seq_printf(s, "AC02 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR1_AC02_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR1_AC02_AGG_LIMIT_SHFT);
++	seq_printf(s, "AC03 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR1_AC03_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR1_AC03_AGG_LIMIT_SHFT);
++	value = mt76_rr(dev, BN0_WF_AGG_TOP_AALCR2_ADDR + band_offset);
++	seq_printf(s, "AC10 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR2_AC10_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR2_AC10_AGG_LIMIT_SHFT);
++	seq_printf(s, "AC11 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR2_AC11_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR2_AC11_AGG_LIMIT_SHFT);
++	value = mt76_rr(dev, BN0_WF_AGG_TOP_AALCR3_ADDR + band_offset);
++	seq_printf(s, "AC12 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR3_AC12_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR3_AC12_AGG_LIMIT_SHFT);
++	seq_printf(s, "AC13 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR3_AC13_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR3_AC13_AGG_LIMIT_SHFT);
++	value = mt76_rr(dev, BN0_WF_AGG_TOP_AALCR4_ADDR + band_offset);
++	seq_printf(s, "AC20 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR4_AC20_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR4_AC20_AGG_LIMIT_SHFT);
++	seq_printf(s, "AC21 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR4_AC21_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR4_AC21_AGG_LIMIT_SHFT);
++	value = mt76_rr(dev, BN0_WF_AGG_TOP_AALCR5_ADDR + band_offset);
++	seq_printf(s, "AC22 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR5_AC22_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR5_AC22_AGG_LIMIT_SHFT);
++	seq_printf(s, "AC23 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR5_AC23_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR5_AC23_AGG_LIMIT_SHFT);
++	value = mt76_rr(dev, BN0_WF_AGG_TOP_AALCR6_ADDR + band_offset);
++	seq_printf(s, "AC30 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR6_AC30_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR6_AC30_AGG_LIMIT_SHFT);
++	seq_printf(s, "AC31 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR6_AC31_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR6_AC31_AGG_LIMIT_SHFT);
++	value = mt76_rr(dev, BN0_WF_AGG_TOP_AALCR7_ADDR + band_offset);
++	seq_printf(s, "AC32 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR7_AC32_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR7_AC32_AGG_LIMIT_SHFT);
++	seq_printf(s, "AC33 Agg limit = %d\t", (value & BN0_WF_AGG_TOP_AALCR7_AC33_AGG_LIMIT_MASK) >>  BN0_WF_AGG_TOP_AALCR7_AC33_AGG_LIMIT_SHFT);
++
++	switch (band_idx) {
++	case 0:
++		band_offset = 0;
++		break;
++	case 1:
++		band_offset = BN1_WF_MIB_TOP_BASE - BN0_WF_MIB_TOP_BASE;
++		break;
++	case 2:
++		band_offset = IP1_BN0_WF_MIB_TOP_BASE - BN0_WF_MIB_TOP_BASE;
++		break;
++	default:
++		return 0;
++	}
++
++	seq_printf(s, "===AMPDU Related Counters===\n");
++
++	value = mt76_rr(dev, BN0_WF_MIB_TOP_TRARC0_ADDR + band_offset);
++	agg_rang_sel[0] = (value & BN0_WF_MIB_TOP_TRARC0_AGG_RANG_SEL_0_MASK) >> BN0_WF_MIB_TOP_TRARC0_AGG_RANG_SEL_0_SHFT;
++	agg_rang_sel[1] = (value & BN0_WF_MIB_TOP_TRARC0_AGG_RANG_SEL_1_MASK) >> BN0_WF_MIB_TOP_TRARC0_AGG_RANG_SEL_1_SHFT;
++	value = mt76_rr(dev, BN0_WF_MIB_TOP_TRARC1_ADDR + band_offset);
++	agg_rang_sel[2] = (value & BN0_WF_MIB_TOP_TRARC1_AGG_RANG_SEL_2_MASK) >> BN0_WF_MIB_TOP_TRARC1_AGG_RANG_SEL_2_SHFT;
++	agg_rang_sel[3] = (value & BN0_WF_MIB_TOP_TRARC1_AGG_RANG_SEL_3_MASK) >> BN0_WF_MIB_TOP_TRARC1_AGG_RANG_SEL_3_SHFT;
++	value = mt76_rr(dev, BN0_WF_MIB_TOP_TRARC2_ADDR + band_offset);
++	agg_rang_sel[4] = (value & BN0_WF_MIB_TOP_TRARC2_AGG_RANG_SEL_4_MASK) >> BN0_WF_MIB_TOP_TRARC2_AGG_RANG_SEL_4_SHFT;
++	agg_rang_sel[5] = (value & BN0_WF_MIB_TOP_TRARC2_AGG_RANG_SEL_5_MASK) >> BN0_WF_MIB_TOP_TRARC2_AGG_RANG_SEL_5_SHFT;
++	value = mt76_rr(dev, BN0_WF_MIB_TOP_TRARC3_ADDR + band_offset);
++	agg_rang_sel[6] = (value & BN0_WF_MIB_TOP_TRARC3_AGG_RANG_SEL_6_MASK) >> BN0_WF_MIB_TOP_TRARC3_AGG_RANG_SEL_6_SHFT;
++	agg_rang_sel[7] = (value & BN0_WF_MIB_TOP_TRARC3_AGG_RANG_SEL_7_MASK) >> BN0_WF_MIB_TOP_TRARC3_AGG_RANG_SEL_7_SHFT;
++	value = mt76_rr(dev, BN0_WF_MIB_TOP_TRARC4_ADDR + band_offset);
++	agg_rang_sel[8] = (value & BN0_WF_MIB_TOP_TRARC4_AGG_RANG_SEL_8_MASK) >> BN0_WF_MIB_TOP_TRARC4_AGG_RANG_SEL_8_SHFT;
++	agg_rang_sel[9] = (value & BN0_WF_MIB_TOP_TRARC4_AGG_RANG_SEL_9_MASK) >> BN0_WF_MIB_TOP_TRARC4_AGG_RANG_SEL_9_SHFT;
++	value = mt76_rr(dev, BN0_WF_MIB_TOP_TRARC5_ADDR + band_offset);
++	agg_rang_sel[10] = (value & BN0_WF_MIB_TOP_TRARC5_AGG_RANG_SEL_10_MASK) >> BN0_WF_MIB_TOP_TRARC5_AGG_RANG_SEL_10_SHFT;
++	agg_rang_sel[11] = (value & BN0_WF_MIB_TOP_TRARC5_AGG_RANG_SEL_11_MASK) >> BN0_WF_MIB_TOP_TRARC5_AGG_RANG_SEL_11_SHFT;
++	value = mt76_rr(dev, BN0_WF_MIB_TOP_TRARC6_ADDR + band_offset);
++	agg_rang_sel[12] = (value & BN0_WF_MIB_TOP_TRARC6_AGG_RANG_SEL_12_MASK) >> BN0_WF_MIB_TOP_TRARC6_AGG_RANG_SEL_12_SHFT;
++	agg_rang_sel[13] = (value & BN0_WF_MIB_TOP_TRARC6_AGG_RANG_SEL_13_MASK) >> BN0_WF_MIB_TOP_TRARC6_AGG_RANG_SEL_13_SHFT;
++	value = mt76_rr(dev, BN0_WF_MIB_TOP_TRARC7_ADDR + band_offset);
++	agg_rang_sel[14] = (value & BN0_WF_MIB_TOP_TRARC7_AGG_RANG_SEL_14_MASK) >> BN0_WF_MIB_TOP_TRARC7_AGG_RANG_SEL_14_SHFT;
++
++	burst_cnt[0] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR0_ADDR + band_offset);
++	burst_cnt[1] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR1_ADDR + band_offset);
++	burst_cnt[2] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR2_ADDR + band_offset);
++	burst_cnt[3] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR3_ADDR + band_offset);
++	burst_cnt[4] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR4_ADDR + band_offset);
++	burst_cnt[5] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR5_ADDR + band_offset);
++	burst_cnt[6] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR6_ADDR + band_offset);
++	burst_cnt[7] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR7_ADDR + band_offset);
++	burst_cnt[8] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR8_ADDR + band_offset);
++	burst_cnt[9] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR9_ADDR + band_offset);
++	burst_cnt[10] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR10_ADDR + band_offset);
++	burst_cnt[11] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR11_ADDR + band_offset);
++	burst_cnt[12] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR12_ADDR + band_offset);
++	burst_cnt[13] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR13_ADDR + band_offset);
++	burst_cnt[14] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR14_ADDR + band_offset);
++	burst_cnt[15] = mt76_rr(dev, BN0_WF_MIB_TOP_TRDR15_ADDR + band_offset);
++
++	start_range = 1;
++	total_burst = 0;
++	total_ampdu = 0;
++	agg_rang_sel[15] = 1023;
++
++	/* Need to add 1 after read from AGG_RANG_SEL CR */
++	for (idx = 0; idx < 16; idx++) {
++		agg_rang_sel[idx]++;
++		total_burst += burst_cnt[idx];
++
++		if (start_range == agg_rang_sel[idx])
++			ampdu_cnt[idx] = (u64) start_range * burst_cnt[idx];
++		else
++			ampdu_cnt[idx] = (u64) ((start_range + agg_rang_sel[idx]) >> 1) * burst_cnt[idx];
++
++		start_range = agg_rang_sel[idx] + 1;
++		total_ampdu += ampdu_cnt[idx];
++	}
++
++	start_range = 1;
++	sprintf(full_str, "%13s ", "Tx Agg Range:");
++
++	for (row_idx = 0; row_idx < 4; row_idx++) {
++		for (col_idx = 0; col_idx < 4; col_idx++, idx++) {
++			idx = 4 * row_idx + col_idx;
++
++			if (start_range == agg_rang_sel[idx])
++				sprintf(partial_str, "%d", agg_rang_sel[idx]);
++			else
++				sprintf(partial_str, "%d~%d", start_range, agg_rang_sel[idx]);
++
++			start_range = agg_rang_sel[idx] + 1;
++			sprintf(full_str + strlen(full_str), "%-11s ", partial_str);
++		}
++
++		idx = 4 * row_idx;
++
++		seq_printf(s, "%s\n", full_str);
++		seq_printf(s, "%13s 0x%-9x 0x%-9x 0x%-9x 0x%-9x\n",
++			row_idx ? "" : "Burst count:",
++			burst_cnt[idx], burst_cnt[idx + 1],
++			burst_cnt[idx + 2], burst_cnt[idx + 3]);
++
++		if (total_burst != 0) {
++			if (row_idx == 0)
++				sprintf(full_str, "%13s ",
++					"Burst ratio:");
++			else
++				sprintf(full_str, "%13s ", "");
++
++			for (col_idx = 0; col_idx < 4; col_idx++) {
++				u64 count = (u64) burst_cnt[idx + col_idx] * 100;
++
++				sprintf(partial_str, "(%llu%%)",
++					div64_u64(count, total_burst));
++				sprintf(full_str + strlen(full_str),
++					"%-11s ", partial_str);
++			}
++
++			seq_printf(s, "%s\n", full_str);
++
++			if (row_idx == 0)
++				sprintf(full_str, "%13s ",
++					"MDPU ratio:");
++			else
++				sprintf(full_str, "%13s ", "");
++
++			for (col_idx = 0; col_idx < 4; col_idx++) {
++				u64 count = ampdu_cnt[idx + col_idx] * 100;
++
++				sprintf(partial_str, "(%llu%%)",
++					div64_u64(count, total_ampdu));
++				sprintf(full_str + strlen(full_str),
++					"%-11s ", partial_str);
++			}
++
++			seq_printf(s, "%s\n", full_str);
++		}
++
++		sprintf(full_str, "%13s ", "");
++	}
++
++	return 0;
++}
++
++static int mt7996_agginfo_read_band0(struct seq_file *s, void *data)
++{
++	mt7996_agginfo_read_per_band(s, MT_BAND0);
++	return 0;
++}
++
++static int mt7996_agginfo_read_band1(struct seq_file *s, void *data)
++{
++	mt7996_agginfo_read_per_band(s, MT_BAND1);
++	return 0;
++}
++
++static int mt7996_agginfo_read_band2(struct seq_file *s, void *data)
++{
++	mt7996_agginfo_read_per_band(s, MT_BAND2);
++	return 0;
++}
++
++/* AMSDU INFO */
++static int mt7996_amsdu_result_read(struct seq_file *s, void *data)
++{
++#define HW_MSDU_CNT_ADDR 0xf400
++#define HW_MSDU_NUM_MAX 33
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	u32 ple_stat[HW_MSDU_NUM_MAX] = {0}, total_amsdu = 0;
++	u8 i;
++
++	for (i = 0; i < HW_MSDU_NUM_MAX; i++)
++		ple_stat[i] = mt76_rr(dev, HW_MSDU_CNT_ADDR + i * 0x04);
++
++	seq_printf(s, "TXD counter status of MSDU:\n");
++
++	for (i = 0; i < HW_MSDU_NUM_MAX; i++)
++		total_amsdu += ple_stat[i];
++
++	for (i = 0; i < HW_MSDU_NUM_MAX; i++) {
++		seq_printf(s, "AMSDU pack count of %d MSDU in TXD: 0x%x ", i, ple_stat[i]);
++		if (total_amsdu != 0)
++			seq_printf(s, "(%d%%)\n", ple_stat[i] * 100 / total_amsdu);
++		else
++			seq_printf(s, "\n");
++	}
++
++	return 0;
++}
++
++/* DBG MODLE */
++static int
++mt7996_fw_debug_module_set(void *data, u64 module)
++{
++	struct mt7996_dev *dev = data;
++
++	dev->dbg.fw_dbg_module = module;
++	return 0;
++}
++
++static int
++mt7996_fw_debug_module_get(void *data, u64 *module)
++{
++	struct mt7996_dev *dev = data;
++
++	*module = dev->dbg.fw_dbg_module;
++	return 0;
++}
++
++DEFINE_DEBUGFS_ATTRIBUTE(fops_fw_debug_module, mt7996_fw_debug_module_get,
++			 mt7996_fw_debug_module_set, "%lld\n");
++
++static int
++mt7996_fw_debug_level_set(void *data, u64 level)
++{
++	struct mt7996_dev *dev = data;
++
++	dev->dbg.fw_dbg_lv = level;
++	mt7996_mcu_fw_dbg_ctrl(dev, dev->dbg.fw_dbg_module, dev->dbg.fw_dbg_lv);
++	return 0;
++}
++
++static int
++mt7996_fw_debug_level_get(void *data, u64 *level)
++{
++	struct mt7996_dev *dev = data;
++
++	*level = dev->dbg.fw_dbg_lv;
++	return 0;
++}
++
++DEFINE_DEBUGFS_ATTRIBUTE(fops_fw_debug_level, mt7996_fw_debug_level_get,
++			 mt7996_fw_debug_level_set, "%lld\n");
++
++/* usage: echo 0x[arg3][arg2][arg1] > fw_wa_set */
++static int
++mt7996_wa_set(void *data, u64 val)
++{
++	struct mt7996_dev *dev = data;
++	u32 arg1, arg2, arg3;
++
++	arg1 = FIELD_GET(GENMASK_ULL(7, 0), val);
++	arg2 = FIELD_GET(GENMASK_ULL(15, 8), val);
++	arg3 = FIELD_GET(GENMASK_ULL(23, 16), val);
++
++	return mt7996_mcu_wa_cmd(dev, MCU_WA_PARAM_CMD(SET),
++				arg1, arg2, arg3);
++}
++
++DEFINE_DEBUGFS_ATTRIBUTE(fops_wa_set, NULL, mt7996_wa_set,
++			 "0x%llx\n");
++
++/* usage: echo 0x[arg3][arg2][arg1] > fw_wa_query */
++static int
++mt7996_wa_query(void *data, u64 val)
++{
++	struct mt7996_dev *dev = data;
++	u32 arg1, arg2, arg3;
++
++	arg1 = FIELD_GET(GENMASK_ULL(7, 0), val);
++	arg2 = FIELD_GET(GENMASK_ULL(15, 8), val);
++	arg3 = FIELD_GET(GENMASK_ULL(23, 16), val);
++
++	return mt7996_mcu_wa_cmd(dev, MCU_WA_PARAM_CMD(QUERY),
++				arg1, arg2, arg3);
++	return 0;
++}
++
++DEFINE_DEBUGFS_ATTRIBUTE(fops_wa_query, NULL, mt7996_wa_query,
++			 "0x%llx\n");
++
++static int mt7996_dump_version(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	seq_printf(s, "Version: 3.3.16.0\n");
++
++	if (!test_bit(MT76_STATE_MCU_RUNNING, &dev->mphy.state))
++		return 0;
++
++	seq_printf(s, "Rom Patch Build Time: %.16s\n", dev->patch_build_date);
++	seq_printf(s, "WM Patch Build Time: %.15s, Mode: %s\n",
++		   dev->ram_build_date[MT7996_RAM_TYPE_WM],
++		   dev->testmode_enable ? "Testmode" : "Normal mode");
++	seq_printf(s, "WA Patch Build Time: %.15s\n",
++		   dev->ram_build_date[MT7996_RAM_TYPE_WA]);
++	seq_printf(s, "DSP Patch Build Time: %.15s\n",
++		   dev->ram_build_date[MT7996_RAM_TYPE_DSP]);
++	return 0;
++}
++
++/* fw wm call trace info dump */
++void mt7996_show_lp_history(struct seq_file *s, u32 type)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	struct mt7996_crash_data *crash_data;
++	struct mt7996_coredump *dump;
++	u64 now = 0;
++	int i = 0;
++	u8 fw_type = !!type;
++
++	mutex_lock(&dev->dump_mutex);
++
++	crash_data = mt7996_coredump_new(dev, fw_type);
++	if (!crash_data) {
++		mutex_unlock(&dev->dump_mutex);
++		seq_printf(s, "the coredump is disable!\n");
++		return;
++	}
++	mutex_unlock(&dev->dump_mutex);
++
++	dump = mt7996_coredump_build(dev, fw_type, false);
++	if (!dump) {
++		seq_printf(s, "no call stack data found!\n");
++		return;
++	}
++
++	seq_printf(s, "\x1b[32m%s log output\x1b[0m\n", dump->fw_type);
++	seq_printf(s, "\x1b[32mfw status: %s\n", dump->fw_state);
++	mt7996_dump_version(s, NULL);
++	/* PC log */
++	now = jiffies;
++	for (i = 0; i < 10; i++)
++		seq_printf(s, "\tCurrent PC=%x\n", dump->pc_cur[i]);
++
++	seq_printf(s, "PC log contorl=0x%x(T=%llu)(latest PC index = 0x%x)\n",
++		dump->pc_dbg_ctrl, now, dump->pc_cur_idx);
++	for (i = 0; i < 32; i++)
++		seq_printf(s, "\tPC log(%d)=0x%08x\n", i, dump->pc_stack[i]);
++
++	/* LR log */
++	now = jiffies;
++	seq_printf(s, "\nLR log contorl=0x%x(T=%llu)(latest LR index = 0x%x)\n",
++		dump->lr_dbg_ctrl, now, dump->lr_cur_idx);
++	for (i = 0; i < 32; i++)
++		seq_printf(s, "\tLR log(%d)=0x%08x\n", i, dump->lr_stack[i]);
++
++	vfree(dump);
++}
++
++static int mt7996_fw_wa_info_read(struct seq_file *s, void *data)
++{
++	seq_printf(s, "======[ShowPcLpHistory]======\n");
++	mt7996_show_lp_history(s, MT7996_RAM_TYPE_WA);
++	seq_printf(s, "======[End ShowPcLpHistory]==\n");
++
++	return 0;
++}
++
++static int mt7996_fw_wm_info_read(struct seq_file *s, void *data)
++{
++	seq_printf(s, "======[ShowPcLpHistory]======\n");
++	mt7996_show_lp_history(s, MT7996_RAM_TYPE_WM);
++	seq_printf(s, "======[End ShowPcLpHistory]==\n");
++
++	return 0;
++}
++
++/* dma info dump */
++static void
++dump_dma_tx_ring_info(struct seq_file *s, struct mt7996_dev *dev,  char *str1, char *str2, u32 ring_base)
++{
++	u32 base, cnt, cidx, didx, queue_cnt;
++
++	base= mt76_rr(dev, ring_base);
++	cnt = mt76_rr(dev, ring_base + 4);
++	cidx = mt76_rr(dev, ring_base + 8);
++	didx = mt76_rr(dev, ring_base + 12);
++	queue_cnt = (cidx >= didx) ? (cidx - didx) : (cidx - didx + cnt);
++
++	seq_printf(s, "%20s %6s %10x %15x %10x %10x %10x\n", str1, str2, base, cnt, cidx, didx, queue_cnt);
++}
++
++static void
++dump_dma_rx_ring_info(struct seq_file *s, struct mt7996_dev *dev,  char *str1, char *str2, u32 ring_base)
++{
++	u32 base, ctrl1, cnt, cidx, didx, queue_cnt;
++
++	base= mt76_rr(dev, ring_base);
++	ctrl1 = mt76_rr(dev, ring_base + 4);
++	cidx = mt76_rr(dev, ring_base + 8) & 0xfff;
++	didx = mt76_rr(dev, ring_base + 12) & 0xfff;
++	cnt = ctrl1 & 0xfff;
++	queue_cnt = (didx > cidx) ? (didx - cidx - 1) : (didx - cidx + cnt - 1);
++
++	seq_printf(s, "%20s %6s %10x %10x(%3x) %10x %10x %10x\n",
++		   str1, str2, base, ctrl1, cnt, cidx, didx, queue_cnt);
++}
++
++static void
++mt7996_show_dma_info(struct seq_file *s, struct mt7996_dev *dev)
++{
++	u32 sys_ctrl[10];
++
++	/* HOST DMA0 information */
++	sys_ctrl[0] = mt76_rr(dev, WF_WFDMA_HOST_DMA0_HOST_INT_STA_ADDR);
++	sys_ctrl[1] = mt76_rr(dev, WF_WFDMA_HOST_DMA0_HOST_INT_ENA_ADDR);
++	sys_ctrl[2] = mt76_rr(dev, WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_ADDR);
++
++	seq_printf(s, "HOST_DMA Configuration\n");
++	seq_printf(s, "%10s %10s %10s %10s %10s %10s\n",
++		"DMA", "IntCSR", "IntMask", "Glocfg", "Tx/RxEn", "Tx/RxBusy");
++	seq_printf(s, "%10s %10x %10x %10x %4x/%5x %4x/%5x\n",
++		"DMA0", sys_ctrl[0], sys_ctrl[1], sys_ctrl[2],
++		(sys_ctrl[2] & WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_TX_DMA_EN_MASK)
++			>> WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_TX_DMA_EN_SHFT,
++		(sys_ctrl[2] & WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_RX_DMA_EN_MASK)
++			>> WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_RX_DMA_EN_SHFT,
++		(sys_ctrl[2] & WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_TX_DMA_BUSY_MASK)
++			>> WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_TX_DMA_BUSY_SHFT,
++		(sys_ctrl[2] & WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_RX_DMA_BUSY_MASK)
++			>> WF_WFDMA_HOST_DMA0_WPDMA_GLO_CFG_RX_DMA_BUSY_SHFT);
++
++	if (dev->hif2) {
++		/* HOST DMA1 information */
++		sys_ctrl[0] = mt76_rr(dev, WF_WFDMA_HOST_DMA0_PCIE1_HOST_INT_STA_ADDR);
++		sys_ctrl[1] = mt76_rr(dev, WF_WFDMA_HOST_DMA0_PCIE1_HOST_INT_ENA_ADDR);
++		sys_ctrl[2] = mt76_rr(dev, WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_ADDR);
++
++		seq_printf(s, "%10s %10x %10x %10x %4x/%5x %4x/%5x\n",
++			"DMA0P1", sys_ctrl[0], sys_ctrl[1], sys_ctrl[2],
++			(sys_ctrl[2] & WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_TX_DMA_EN_MASK)
++				>> WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_TX_DMA_EN_SHFT,
++			(sys_ctrl[2] & WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_RX_DMA_EN_MASK)
++				>> WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_RX_DMA_EN_SHFT,
++			(sys_ctrl[2] & WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_TX_DMA_BUSY_MASK)
++				>> WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_TX_DMA_BUSY_SHFT,
++			(sys_ctrl[2] & WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_RX_DMA_BUSY_MASK)
++				>> WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_GLO_CFG_RX_DMA_BUSY_SHFT);
++	}
++
++	seq_printf(s, "HOST_DMA0 Ring Configuration\n");
++	seq_printf(s, "%20s %6s %10s %15s %10s %10s %10s\n",
++		"Name", "Used", "Base", "Ctrl1(Cnt)", "CIDX", "DIDX", "QCnt");
++	dump_dma_tx_ring_info(s, dev, "T0:TXD0(H2MAC)", "STA",
++		WF_WFDMA_HOST_DMA0_WPDMA_TX_RING0_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T1:TXD1(H2MAC)", "STA",
++		WF_WFDMA_HOST_DMA0_WPDMA_TX_RING1_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T2:TXD2(H2MAC)", "STA",
++		WF_WFDMA_HOST_DMA0_WPDMA_TX_RING2_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T3:", "STA",
++		WF_WFDMA_HOST_DMA0_WPDMA_TX_RING3_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T4:", "STA",
++		WF_WFDMA_HOST_DMA0_WPDMA_TX_RING4_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T5:", "STA",
++		WF_WFDMA_HOST_DMA0_WPDMA_TX_RING5_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T6:", "STA",
++		WF_WFDMA_HOST_DMA0_WPDMA_TX_RING6_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T16:FWDL", "Both",
++		WF_WFDMA_HOST_DMA0_WPDMA_TX_RING16_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T17:Cmd(H2WM)", "Both",
++		WF_WFDMA_HOST_DMA0_WPDMA_TX_RING17_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T18:TXD0(H2WA)", "AP",
++		WF_WFDMA_HOST_DMA0_WPDMA_TX_RING18_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T19:TXD1(H2WA)", "AP",
++		WF_WFDMA_HOST_DMA0_WPDMA_TX_RING19_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T20:Cmd(H2WA)", "AP",
++		WF_WFDMA_HOST_DMA0_WPDMA_TX_RING20_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T21:TXD2(H2WA)", "AP",
++		WF_WFDMA_HOST_DMA0_WPDMA_TX_RING21_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T22:TXD3(H2WA)", "AP",
++		WF_WFDMA_HOST_DMA0_WPDMA_TX_RING22_CTRL0_ADDR);
++
++
++	dump_dma_rx_ring_info(s, dev, "R0:Event(WM2H)", "Both",
++		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING0_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R1:Event(WA2H)", "AP",
++		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING1_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R2:TxDone0(WA2H)", "AP",
++		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING2_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R3:TxDone1(WA2H)", "AP",
++		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING3_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R4:Data0(MAC2H)", "Both",
++		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING4_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R5:Data1(MAC2H)", "Both",
++		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING5_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R6:BUF1(MAC2H)", "Both",
++		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING6_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R7:TxDone1(MAC2H)", "Both",
++		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING7_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R8:BUF0(MAC2H)", "Both",
++		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING8_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R9:TxDone0(MAC2H)", "Both",
++		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING9_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R10:MSDU_PG0(MAC2H)", "Both",
++		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING10_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R11:MSDU_PG1(MAC2H)", "Both",
++		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING11_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R12:MSDU_PG2(MAC2H)", "Both",
++		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING12_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "IND:IND_CMD(MAC2H)", "Both",
++		WF_RRO_TOP_IND_CMD_0_CTRL0_ADDR);
++
++	if (dev->hif2) {
++		seq_printf(s, "HOST_DMA0 PCIe1 Ring Configuration\n");
++		seq_printf(s, "%20s %6s %10s %15s %10s %10s %10s\n",
++			"Name", "Used", "Base", "Ctrl1(Cnt)", "CIDX", "DIDX", "QCnt");
++		dump_dma_tx_ring_info(s, dev, "T21:TXD2(H2WA)", "AP",
++			WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_TX_RING21_CTRL0_ADDR);
++		dump_dma_tx_ring_info(s, dev, "T22:TXD?(H2WA)", "AP",
++			WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_TX_RING22_CTRL0_ADDR);
++
++		dump_dma_rx_ring_info(s, dev, "R3:TxDone1(WA2H)", "AP",
++			WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING3_CTRL0_ADDR);
++		dump_dma_rx_ring_info(s, dev, "R5:Data1(MAC2H)", "Both",
++			WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING5_CTRL0_ADDR);
++		dump_dma_rx_ring_info(s, dev, "R6:BUF1(MAC2H)", "Both",
++			WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING6_CTRL0_ADDR);
++		dump_dma_rx_ring_info(s, dev, "R7:TxDone1(MAC2H)", "Both",
++			WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING7_CTRL0_ADDR);
++	}
++
++	/* MCU DMA information */
++	sys_ctrl[0] = mt76_rr(dev, WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_ADDR);
++	sys_ctrl[1] = mt76_rr(dev, WF_WFDMA_MCU_DMA0_HOST_INT_STA_ADDR);
++	sys_ctrl[2] = mt76_rr(dev, WF_WFDMA_MCU_DMA0_HOST_INT_ENA_ADDR);
++
++	seq_printf(s, "MCU_DMA Configuration\n");
++	seq_printf(s, "%10s %10s %10s %10s %10s %10s\n",
++		"DMA", "IntCSR", "IntMask", "Glocfg", "Tx/RxEn", "Tx/RxBusy");
++	seq_printf(s, "%10s %10x %10x %10x %4x/%5x %4x/%5x\n",
++		"DMA0", sys_ctrl[1], sys_ctrl[2], sys_ctrl[0],
++		(sys_ctrl[0] & WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_TX_DMA_EN_MASK)
++			>> WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_TX_DMA_EN_SHFT,
++		(sys_ctrl[0] & WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_RX_DMA_EN_MASK)
++			>> WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_RX_DMA_EN_SHFT,
++		(sys_ctrl[0] & WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_TX_DMA_BUSY_MASK)
++			>> WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_TX_DMA_BUSY_SHFT,
++		(sys_ctrl[0] & WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_RX_DMA_BUSY_MASK)
++			>> WF_WFDMA_MCU_DMA0_WPDMA_GLO_CFG_RX_DMA_BUSY_SHFT);
++
++	seq_printf(s, "MCU_DMA0 Ring Configuration\n");
++	seq_printf(s, "%20s %6s %10s %15s %10s %10s %10s\n",
++		"Name", "Used", "Base", "Cnt", "CIDX", "DIDX", "QCnt");
++	dump_dma_tx_ring_info(s, dev, "T0:Event(WM2H)", "Both",
++		WF_WFDMA_MCU_DMA0_WPDMA_TX_RING0_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T1:Event(WA2H)", "AP",
++		WF_WFDMA_MCU_DMA0_WPDMA_TX_RING1_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T2:TxDone0(WA2H)", "AP",
++		WF_WFDMA_MCU_DMA0_WPDMA_TX_RING2_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T3:TxDone1(WA2H)", "AP",
++		WF_WFDMA_MCU_DMA0_WPDMA_TX_RING3_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T4:TXD(WM2MAC)", "Both",
++		WF_WFDMA_MCU_DMA0_WPDMA_TX_RING4_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T5:TXCMD(WM2MAC)", "Both",
++		WF_WFDMA_MCU_DMA0_WPDMA_TX_RING5_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T6:TXD(WA2MAC)", "AP",
++		WF_WFDMA_MCU_DMA0_WPDMA_TX_RING6_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R0:FWDL", "Both",
++		WF_WFDMA_MCU_DMA0_WPDMA_RX_RING0_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R1:Cmd(H2WM)", "Both",
++		WF_WFDMA_MCU_DMA0_WPDMA_RX_RING1_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R2:TXD0(H2WA)", "AP",
++		WF_WFDMA_MCU_DMA0_WPDMA_RX_RING2_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R3:TXD1(H2WA)", "AP",
++		WF_WFDMA_MCU_DMA0_WPDMA_RX_RING3_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R4:Cmd(H2WA)", "AP",
++		WF_WFDMA_MCU_DMA0_WPDMA_RX_RING4_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R5:Data0(MAC2WM)", "Both",
++		WF_WFDMA_MCU_DMA0_WPDMA_RX_RING5_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R6:TxDone(MAC2WM)", "Both",
++		WF_WFDMA_MCU_DMA0_WPDMA_RX_RING6_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R7:SPL/RPT(MAC2WM)", "Both",
++		WF_WFDMA_MCU_DMA0_WPDMA_RX_RING7_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R8:TxDone(MAC2WA)", "AP",
++		WF_WFDMA_MCU_DMA0_WPDMA_RX_RING8_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R9:Data1(MAC2WM)", "Both",
++		WF_WFDMA_MCU_DMA0_WPDMA_RX_RING9_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R10:TXD2(H2WA)", "AP",
++		WF_WFDMA_MCU_DMA0_WPDMA_RX_RING10_CTRL0_ADDR);
++
++	/* MEM DMA information */
++	sys_ctrl[0] = mt76_rr(dev, WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_ADDR);
++	sys_ctrl[1] = mt76_rr(dev, WF_WFDMA_MEM_DMA_HOST_INT_STA_ADDR);
++	sys_ctrl[2] = mt76_rr(dev, WF_WFDMA_MEM_DMA_HOST_INT_ENA_ADDR);
++
++	seq_printf(s, "MEM_DMA Configuration\n");
++	seq_printf(s, "%10s %10s %10s %10s %10s %10s\n",
++		"DMA", "IntCSR", "IntMask", "Glocfg", "Tx/RxEn", "Tx/RxBusy");
++	seq_printf(s, "%10s %10x %10x %10x %4x/%5x %4x/%5x\n",
++		"MEM", sys_ctrl[1], sys_ctrl[2], sys_ctrl[0],
++		(sys_ctrl[0] & WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_TX_DMA_EN_MASK)
++			>> WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_TX_DMA_EN_SHFT,
++		(sys_ctrl[0] & WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_RX_DMA_EN_MASK)
++			>> WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_RX_DMA_EN_SHFT,
++		(sys_ctrl[0] & WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_TX_DMA_BUSY_MASK)
++			>> WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_TX_DMA_BUSY_SHFT,
++		(sys_ctrl[0] & WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_RX_DMA_BUSY_MASK)
++			>> WF_WFDMA_MEM_DMA_WPDMA_GLO_CFG_RX_DMA_BUSY_SHFT);
++
++	seq_printf(s, "MEM_DMA Ring Configuration\n");
++	seq_printf(s, "%20s %6s %10s %10s %10s %10s %10s\n",
++		"Name", "Used", "Base", "Cnt", "CIDX", "DIDX", "QCnt");
++	dump_dma_tx_ring_info(s, dev, "T0:CmdEvent(WM2WA)", "AP",
++		WF_WFDMA_MEM_DMA_WPDMA_TX_RING0_CTRL0_ADDR);
++	dump_dma_tx_ring_info(s, dev, "T1:CmdEvent(WA2WM)", "AP",
++		WF_WFDMA_MEM_DMA_WPDMA_TX_RING1_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R0:CmdEvent(WM2WA)", "AP",
++		WF_WFDMA_MEM_DMA_WPDMA_RX_RING0_CTRL0_ADDR);
++	dump_dma_rx_ring_info(s, dev, "R1:CmdEvent(WA2WM)", "AP",
++		WF_WFDMA_MEM_DMA_WPDMA_RX_RING1_CTRL0_ADDR);
++}
++
++static int mt7996_trinfo_read(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	mt7996_show_dma_info(s, dev);
++	return 0;
++}
++
++/* MIB INFO */
++static int mt7996_mibinfo_read_per_band(struct seq_file *s, int band_idx)
++{
++#define BSS_NUM	4
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	u8 bss_nums = BSS_NUM;
++	u32 idx;
++	u32 mac_val, band_offset = 0, band_offset_umib = 0;
++	u32 msdr6, msdr9, msdr18;
++	u32 rvsr0, rscr26, rscr35, mctr5, mctr6, msr0, msr1, msr2;
++	u32 tbcr0, tbcr1, tbcr2, tbcr3, tbcr4;
++	u32 btscr[7];
++	u32 tdrcr[5];
++	u32 mbtocr[16], mbtbcr[16], mbrocr[16], mbrbcr[16];
++	u32 btcr, btbcr, brocr, brbcr, btdcr, brdcr;
++	u32 mu_cnt[5];
++	u32 ampdu_cnt[3];
++	u64 per;
++
++	switch (band_idx) {
++	case 0:
++		band_offset = 0;
++		band_offset_umib = 0;
++		break;
++	case 1:
++		band_offset = BN1_WF_MIB_TOP_BASE - BN0_WF_MIB_TOP_BASE;
++		band_offset_umib = WF_UMIB_TOP_B1BROCR_ADDR - WF_UMIB_TOP_B0BROCR_ADDR;
++		break;
++	case 2:
++		band_offset = IP1_BN0_WF_MIB_TOP_BASE - BN0_WF_MIB_TOP_BASE;
++		band_offset_umib = WF_UMIB_TOP_B2BROCR_ADDR - WF_UMIB_TOP_B0BROCR_ADDR;
++		break;
++	default:
++		return true;
++	}
++
++	seq_printf(s, "Band %d MIB Status\n", band_idx);
++	seq_printf(s, "===============================\n");
++	mac_val = mt76_rr(dev, BN0_WF_MIB_TOP_M0SCR0_ADDR + band_offset);
++	seq_printf(s, "MIB Status Control=0x%x\n", mac_val);
++
++	msdr6 = mt76_rr(dev, BN0_WF_MIB_TOP_M0SDR6_ADDR + band_offset);
++	rvsr0 = mt76_rr(dev, BN0_WF_MIB_TOP_RVSR0_ADDR + band_offset);
++	rscr35 = mt76_rr(dev, BN0_WF_MIB_TOP_RSCR35_ADDR + band_offset);
++	msdr9 = mt76_rr(dev, BN0_WF_MIB_TOP_M0SDR9_ADDR + band_offset);
++	rscr26 = mt76_rr(dev, BN0_WF_MIB_TOP_RSCR26_ADDR + band_offset);
++	mctr5 = mt76_rr(dev, BN0_WF_MIB_TOP_MCTR5_ADDR + band_offset);
++	mctr6 = mt76_rr(dev, BN0_WF_MIB_TOP_MCTR6_ADDR + band_offset);
++	msdr18 = mt76_rr(dev, BN0_WF_MIB_TOP_M0SDR18_ADDR + band_offset);
++	msr0 = mt76_rr(dev, BN0_WF_MIB_TOP_MSR0_ADDR + band_offset);
++	msr1 = mt76_rr(dev, BN0_WF_MIB_TOP_MSR1_ADDR + band_offset);
++	msr2 = mt76_rr(dev, BN0_WF_MIB_TOP_MSR2_ADDR + band_offset);
++	ampdu_cnt[0] = mt76_rr(dev, BN0_WF_MIB_TOP_TSCR0_ADDR + band_offset);
++	ampdu_cnt[1] = mt76_rr(dev, BN0_WF_MIB_TOP_TSCR3_ADDR + band_offset);
++	ampdu_cnt[2] = mt76_rr(dev, BN0_WF_MIB_TOP_TSCR4_ADDR + band_offset);
++	ampdu_cnt[1] &= BN0_WF_MIB_TOP_TSCR3_AMPDU_MPDU_COUNT_MASK;
++	ampdu_cnt[2] &= BN0_WF_MIB_TOP_TSCR4_AMPDU_ACKED_COUNT_MASK;
++
++	seq_printf(s, "===Phy/Timing Related Counters===\n");
++	seq_printf(s, "\tChannelIdleCnt=0x%x\n",
++		msdr6 & BN0_WF_MIB_TOP_M0SDR6_CHANNEL_IDLE_COUNT_MASK);
++	seq_printf(s, "\tCCA_NAV_Tx_Time=0x%x\n",
++		msdr9 & BN0_WF_MIB_TOP_M0SDR9_CCA_NAV_TX_TIME_MASK);
++	seq_printf(s, "\tRx_MDRDY_CNT=0x%x\n",
++		rscr26 & BN0_WF_MIB_TOP_RSCR26_RX_MDRDY_COUNT_MASK);
++	seq_printf(s, "\tCCK_MDRDY_TIME=0x%x, OFDM_MDRDY_TIME=0x%x",
++		msr0 & BN0_WF_MIB_TOP_MSR0_CCK_MDRDY_TIME_MASK,
++		msr1 & BN0_WF_MIB_TOP_MSR1_OFDM_LG_MIXED_VHT_MDRDY_TIME_MASK);
++	seq_printf(s, ", OFDM_GREEN_MDRDY_TIME=0x%x\n",
++		msr2 & BN0_WF_MIB_TOP_MSR2_OFDM_GREEN_MDRDY_TIME_MASK);
++	seq_printf(s, "\tPrim CCA Time=0x%x\n",
++		mctr5 & BN0_WF_MIB_TOP_MCTR5_P_CCA_TIME_MASK);
++	seq_printf(s, "\tSec CCA Time=0x%x\n",
++		mctr6 & BN0_WF_MIB_TOP_MCTR6_S_CCA_TIME_MASK);
++	seq_printf(s, "\tPrim ED Time=0x%x\n",
++		msdr18 & BN0_WF_MIB_TOP_M0SDR18_P_ED_TIME_MASK);
++
++	seq_printf(s, "===Tx Related Counters(Generic)===\n");
++	mac_val = mt76_rr(dev, BN0_WF_MIB_TOP_TSCR18_ADDR + band_offset);
++	dev->dbg.bcn_total_cnt[band_idx] +=
++		(mac_val & BN0_WF_MIB_TOP_TSCR18_BEACONTXCOUNT_MASK);
++	seq_printf(s, "\tBeaconTxCnt=0x%x\n", dev->dbg.bcn_total_cnt[band_idx]);
++	dev->dbg.bcn_total_cnt[band_idx] = 0;
++
++	tbcr0 = mt76_rr(dev, BN0_WF_MIB_TOP_TBCR0_ADDR + band_offset);
++	seq_printf(s, "\tTx 20MHz Cnt=0x%x\n",
++		tbcr0 & BN0_WF_MIB_TOP_TBCR0_TX_20MHZ_CNT_MASK);
++	tbcr1 = mt76_rr(dev, BN0_WF_MIB_TOP_TBCR1_ADDR + band_offset);
++	seq_printf(s, "\tTx 40MHz Cnt=0x%x\n",
++		tbcr1 & BN0_WF_MIB_TOP_TBCR1_TX_40MHZ_CNT_MASK);
++	tbcr2 = mt76_rr(dev, BN0_WF_MIB_TOP_TBCR2_ADDR + band_offset);
++	seq_printf(s, "\tTx 80MHz Cnt=0x%x\n",
++		tbcr2 & BN0_WF_MIB_TOP_TBCR2_TX_80MHZ_CNT_MASK);
++	tbcr3 = mt76_rr(dev, BN0_WF_MIB_TOP_TBCR3_ADDR + band_offset);
++	seq_printf(s, "\tTx 160MHz Cnt=0x%x\n",
++		tbcr3 & BN0_WF_MIB_TOP_TBCR3_TX_160MHZ_CNT_MASK);
++	tbcr4 = mt76_rr(dev, BN0_WF_MIB_TOP_TBCR4_ADDR + band_offset);
++	seq_printf(s, "\tTx 320MHz Cnt=0x%x\n",
++		tbcr4 & BN0_WF_MIB_TOP_TBCR4_TX_320MHZ_CNT_MASK);
++	seq_printf(s, "\tAMPDU Cnt=0x%x\n", ampdu_cnt[0]);
++	seq_printf(s, "\tAMPDU MPDU Cnt=0x%x\n", ampdu_cnt[1]);
++	seq_printf(s, "\tAMPDU MPDU Ack Cnt=0x%x\n", ampdu_cnt[2]);
++	per = (ampdu_cnt[2] == 0 ?
++		0 : 1000 * (ampdu_cnt[1] - ampdu_cnt[2]) / ampdu_cnt[1]);
++	seq_printf(s, "\tAMPDU MPDU PER=%llu.%1llu%%\n", per / 10, per % 10);
++
++	seq_printf(s, "===MU Related Counters===\n");
++	mu_cnt[0] = mt76_rr(dev, BN0_WF_MIB_TOP_BSCR2_ADDR + band_offset);
++	mu_cnt[1] = mt76_rr(dev, BN0_WF_MIB_TOP_TSCR5_ADDR + band_offset);
++	mu_cnt[2] = mt76_rr(dev, BN0_WF_MIB_TOP_TSCR6_ADDR + band_offset);
++	mu_cnt[3] = mt76_rr(dev, BN0_WF_MIB_TOP_TSCR8_ADDR + band_offset);
++	mu_cnt[4] = mt76_rr(dev, BN0_WF_MIB_TOP_TSCR7_ADDR + band_offset);
++
++	seq_printf(s, "\tMUBF_TX_COUNT=0x%x\n",
++		mu_cnt[0] & BN0_WF_MIB_TOP_BSCR2_MUBF_TX_COUNT_MASK);
++	seq_printf(s, "\tMU_TX_MPDU_COUNT(Ok+Fail)=0x%x\n", mu_cnt[1]);
++	seq_printf(s, "\tMU_TX_OK_MPDU_COUNT=0x%x\n", mu_cnt[2]);
++	seq_printf(s, "\tMU_TO_MU_FAIL_PPDU_COUNT=0x%x\n", mu_cnt[3]);
++	seq_printf(s, "\tSU_TX_OK_MPDU_COUNT=0x%x\n", mu_cnt[4]);
++
++	seq_printf(s, "===Rx Related Counters(Generic)===\n");
++	seq_printf(s, "\tVector Mismacth Cnt=0x%x\n",
++		rvsr0 & BN0_WF_MIB_TOP_RVSR0_VEC_MISS_COUNT_MASK);
++	seq_printf(s, "\tDelimiter Fail Cnt=0x%x\n",
++		rscr35 & BN0_WF_MIB_TOP_RSCR35_DELIMITER_FAIL_COUNT_MASK);
++
++	mac_val = mt76_rr(dev, BN0_WF_MIB_TOP_RSCR1_ADDR + band_offset);
++	seq_printf(s, "\tRxFCSErrCnt=0x%x\n",
++		(mac_val & BN0_WF_MIB_TOP_RSCR1_RX_FCS_ERROR_COUNT_MASK));
++	mac_val = mt76_rr(dev, BN0_WF_MIB_TOP_RSCR33_ADDR + band_offset);
++	seq_printf(s, "\tRxFifoFullCnt=0x%x\n",
++		(mac_val & BN0_WF_MIB_TOP_RSCR33_RX_FIFO_FULL_COUNT_MASK));
++	mac_val = mt76_rr(dev, BN0_WF_MIB_TOP_RSCR36_ADDR + band_offset);
++	seq_printf(s, "\tRxLenMismatch=0x%x\n",
++		(mac_val & BN0_WF_MIB_TOP_RSCR36_RX_LEN_MISMATCH_MASK));
++	mac_val = mt76_rr(dev, BN0_WF_MIB_TOP_RSCR31_ADDR + band_offset);
++	seq_printf(s, "\tRxMPDUCnt=0x%x\n",
++		(mac_val & BN0_WF_MIB_TOP_RSCR31_RX_MPDU_COUNT_MASK));
++	mac_val = mt76_rr(dev, BN0_WF_MIB_TOP_RSCR27_ADDR + band_offset);
++	seq_printf(s, "\tRx AMPDU Cnt=0x%x\n", mac_val);
++	mac_val = mt76_rr(dev, BN0_WF_MIB_TOP_RSCR28_ADDR + band_offset);
++	seq_printf(s, "\tRx Total ByteCnt=0x%x\n", mac_val);
++
++
++	/* Per-BSS T/RX Counters */
++	seq_printf(s, "===Per-BSS Related Tx/Rx Counters===\n");
++	seq_printf(s, "BSS Idx TxCnt/DataCnt TxByteCnt RxOkCnt/DataCnt RxByteCnt\n");
++	for (idx = 0; idx < bss_nums; idx++) {
++		btcr = mt76_rr(dev, BN0_WF_MIB_TOP_BTCR_ADDR + band_offset + idx * 4);
++		btdcr = mt76_rr(dev, BN0_WF_MIB_TOP_BTDCR_ADDR + band_offset + idx * 4);
++		btbcr = mt76_rr(dev, BN0_WF_MIB_TOP_BTBCR_ADDR + band_offset + idx * 4);
++
++		brocr = mt76_rr(dev, WF_UMIB_TOP_B0BROCR_ADDR + band_offset_umib + idx * 4);
++		brdcr = mt76_rr(dev, WF_UMIB_TOP_B0BRDCR_ADDR + band_offset_umib + idx * 4);
++		brbcr = mt76_rr(dev, WF_UMIB_TOP_B0BRBCR_ADDR + band_offset_umib + idx * 4);
++
++		seq_printf(s, "%d\t 0x%x/0x%x\t 0x%x \t 0x%x/0x%x \t 0x%x\n",
++			idx, btcr, btdcr, btbcr, brocr, brdcr, brbcr);
++	}
++
++	seq_printf(s, "===Per-BSS Related MIB Counters===\n");
++	seq_printf(s, "BSS Idx RTSTx/RetryCnt BAMissCnt AckFailCnt FrmRetry1/2/3Cnt\n");
++
++	/* Per-BSS TX Status */
++	for (idx = 0; idx < bss_nums; idx++) {
++		btscr[0] = mt76_rr(dev, BN0_WF_MIB_TOP_BTSCR5_ADDR + band_offset + idx * 4);
++		btscr[1] = mt76_rr(dev, BN0_WF_MIB_TOP_BTSCR6_ADDR + band_offset + idx * 4);
++		btscr[2] = mt76_rr(dev, BN0_WF_MIB_TOP_BTSCR0_ADDR + band_offset + idx * 4);
++		btscr[3] = mt76_rr(dev, BN0_WF_MIB_TOP_BTSCR1_ADDR + band_offset + idx * 4);
++		btscr[4] = mt76_rr(dev, BN0_WF_MIB_TOP_BTSCR2_ADDR + band_offset + idx * 4);
++		btscr[5] = mt76_rr(dev, BN0_WF_MIB_TOP_BTSCR3_ADDR + band_offset + idx * 4);
++		btscr[6] = mt76_rr(dev, BN0_WF_MIB_TOP_BTSCR4_ADDR + band_offset + idx * 4);
++
++		seq_printf(s, "%d:\t0x%x/0x%x  0x%x \t 0x%x \t  0x%x/0x%x/0x%x\n",
++			idx, (btscr[0] & BN0_WF_MIB_TOP_BTSCR5_RTSTXCOUNTn_MASK),
++			(btscr[1] & BN0_WF_MIB_TOP_BTSCR6_RTSRETRYCOUNTn_MASK),
++			(btscr[2] & BN0_WF_MIB_TOP_BTSCR0_BAMISSCOUNTn_MASK),
++			(btscr[3] & BN0_WF_MIB_TOP_BTSCR1_ACKFAILCOUNTn_MASK),
++			(btscr[4] & BN0_WF_MIB_TOP_BTSCR2_FRAMERETRYCOUNTn_MASK),
++			(btscr[5] & BN0_WF_MIB_TOP_BTSCR3_FRAMERETRY2COUNTn_MASK),
++			(btscr[6] & BN0_WF_MIB_TOP_BTSCR4_FRAMERETRY3COUNTn_MASK));
++	}
++
++	/* Dummy delimiter insertion result */
++	seq_printf(s, "===Dummy delimiter insertion result===\n");
++	tdrcr[0] = mt76_rr(dev, BN0_WF_MIB_TOP_TDRCR0_ADDR + band_offset);
++	tdrcr[1] = mt76_rr(dev, BN0_WF_MIB_TOP_TDRCR1_ADDR + band_offset);
++	tdrcr[2] = mt76_rr(dev, BN0_WF_MIB_TOP_TDRCR2_ADDR + band_offset);
++	tdrcr[3] = mt76_rr(dev, BN0_WF_MIB_TOP_TDRCR3_ADDR + band_offset);
++	tdrcr[4] = mt76_rr(dev, BN0_WF_MIB_TOP_TDRCR4_ADDR + band_offset);
++
++	seq_printf(s, "Range0 = %d\t Range1 = %d\t Range2 = %d\t Range3 = %d\t Range4 = %d\n",
++		tdrcr[0],
++		tdrcr[1],
++		tdrcr[2],
++		tdrcr[3],
++		tdrcr[4]);
++
++	/* Per-MBSS T/RX Counters */
++	seq_printf(s, "===Per-MBSS Related Tx/Rx Counters===\n");
++	seq_printf(s, "MBSSIdx   TxOkCnt  TxByteCnt  RxOkCnt  RxByteCnt\n");
++
++	for (idx = 0; idx < 16; idx++) {
++		mbtocr[idx] = mt76_rr(dev, BN0_WF_MIB_TOP_BTOCR_ADDR + band_offset + (bss_nums + idx) * 4);
++		mbtbcr[idx] = mt76_rr(dev, BN0_WF_MIB_TOP_BTBCR_ADDR + band_offset + (bss_nums + idx) * 4);
++
++		mbrocr[idx] = mt76_rr(dev, WF_UMIB_TOP_B0BROCR_ADDR + band_offset_umib + (bss_nums + idx) * 4);
++		mbrbcr[idx] = mt76_rr(dev, WF_UMIB_TOP_B0BRBCR_ADDR + band_offset_umib + (bss_nums + idx) * 4);
++	}
++
++	for (idx = 0; idx < 16; idx++) {
++		seq_printf(s, "%d\t 0x%x\t 0x%x \t 0x%x \t 0x%x\n",
++			idx, mbtocr[idx], mbtbcr[idx], mbrocr[idx], mbrbcr[idx]);
++	}
++
++	return 0;
++}
++
++static int mt7996_mibinfo_band0(struct seq_file *s, void *data)
++{
++	mt7996_mibinfo_read_per_band(s, MT_BAND0);
++	return 0;
++}
++
++static int mt7996_mibinfo_band1(struct seq_file *s, void *data)
++{
++	mt7996_mibinfo_read_per_band(s, MT_BAND1);
++	return 0;
++}
++
++static int mt7996_mibinfo_band2(struct seq_file *s, void *data)
++{
++	mt7996_mibinfo_read_per_band(s, MT_BAND2);
++	return 0;
++}
++
++/* WTBL INFO */
++static int
++mt7996_wtbl_read_raw(struct mt7996_dev *dev, u16 idx,
++		     enum mt7996_wtbl_type type, u16 start_dw,
++		     u16 len, void *buf)
++{
++	u32 *dest_cpy = (u32 *)buf;
++	u32 size_dw = len;
++	u32 src = 0;
++
++	if (!buf)
++		return 0xFF;
++
++	if (type == WTBL_TYPE_LMAC) {
++		mt76_wr(dev, MT_DBG_WTBLON_TOP_WDUCR_ADDR,
++			FIELD_PREP(MT_DBG_WTBLON_TOP_WDUCR_GROUP, (idx >> 7)));
++		src = LWTBL_IDX2BASE(idx, start_dw);
++	} else if (type == WTBL_TYPE_UMAC) {
++		mt76_wr(dev,  MT_DBG_UWTBL_TOP_WDUCR_ADDR,
++			FIELD_PREP(MT_DBG_UWTBL_TOP_WDUCR_GROUP, (idx >> 7)));
++		src = UWTBL_IDX2BASE(idx, start_dw);
++	} else if (type == WTBL_TYPE_KEY) {
++		mt76_wr(dev,  MT_DBG_UWTBL_TOP_WDUCR_ADDR,
++			MT_DBG_UWTBL_TOP_WDUCR_TARGET |
++			FIELD_PREP(MT_DBG_UWTBL_TOP_WDUCR_GROUP, (idx >> 7)));
++		src = KEYTBL_IDX2BASE(idx, start_dw);
++	}
++
++	while (size_dw--) {
++		*dest_cpy++ = mt76_rr(dev, src);
++		src += 4;
++	};
++
++	return 0;
++}
++
++#if 0
++static int
++mt7996_wtbl_write_raw(struct mt7996_dev *dev, u16 idx,
++			  enum mt7996_wtbl_type type, u16 start_dw,
++			  u32 val)
++{
++	u32 addr = 0;
++
++	if (type == WTBL_TYPE_LMAC) {
++		mt76_wr(dev, MT_DBG_WTBLON_TOP_WDUCR_ADDR,
++			FIELD_PREP(MT_DBG_WTBLON_TOP_WDUCR_GROUP, (idx >> 7)));
++		addr = LWTBL_IDX2BASE(idx, start_dw);
++	} else if (type == WTBL_TYPE_UMAC) {
++		mt76_wr(dev, MT_DBG_UWTBL_TOP_WDUCR_ADDR,
++			FIELD_PREP(MT_DBG_UWTBL_TOP_WDUCR_GROUP, (idx >> 7)));
++		addr = UWTBL_IDX2BASE(idx, start_dw);
++	} else if (type == WTBL_TYPE_KEY) {
++		mt76_wr(dev, MT_DBG_UWTBL_TOP_WDUCR_ADDR,
++			MT_DBG_UWTBL_TOP_WDUCR_TARGET |
++			FIELD_PREP(MT_DBG_UWTBL_TOP_WDUCR_GROUP, (idx >> 7)));
++		addr = KEYTBL_IDX2BASE(idx, start_dw);
++	}
++
++	mt76_wr(dev, addr, val);
++
++	return 0;
++}
++#endif
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW0[] = {
++	{"MUAR_IDX",    WF_LWTBL_MUAR_MASK, WF_LWTBL_MUAR_SHIFT,false},
++	{"RCA1",        WF_LWTBL_RCA1_MASK, NO_SHIFT_DEFINE,	false},
++	{"KID",         WF_LWTBL_KID_MASK,  WF_LWTBL_KID_SHIFT,	false},
++	{"RCID",        WF_LWTBL_RCID_MASK, NO_SHIFT_DEFINE,	false},
++	{"BAND",        WF_LWTBL_BAND_MASK, WF_LWTBL_BAND_SHIFT,false},
++	{"RV",          WF_LWTBL_RV_MASK,   NO_SHIFT_DEFINE,	false},
++	{"RCA2",        WF_LWTBL_RCA2_MASK, NO_SHIFT_DEFINE,	false},
++	{"WPI_FLAG",    WF_LWTBL_WPI_FLAG_MASK, NO_SHIFT_DEFINE,true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw0_1(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LinkAddr: %02x:%02x:%02x:%02x:%02x:%02x(D0[B0~15], D1[B0~31])\n",
++		lwtbl[4], lwtbl[5], lwtbl[6], lwtbl[7], lwtbl[0], lwtbl[1]);
++
++	/* LMAC WTBL DW 0 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 0/1\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_PEER_INFO_DW_0*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW0[i].name) {
++
++		if (WTBL_LMAC_DW0[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW0[i].name,
++					 (dw_value & WTBL_LMAC_DW0[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW0[i].name,
++					  (dw_value & WTBL_LMAC_DW0[i].mask) >> WTBL_LMAC_DW0[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse *WTBL_LMAC_DW2;
++static const struct berse_wtbl_parse WTBL_LMAC_DW2_7996[] = {
++	{"AID",                 WF_LWTBL_AID_MASK,              WF_LWTBL_AID_SHIFT,	false},
++	{"GID_SU",              WF_LWTBL_GID_SU_MASK,           NO_SHIFT_DEFINE,	false},
++	{"SPP_EN",              WF_LWTBL_SPP_EN_MASK,           NO_SHIFT_DEFINE,	false},
++	{"WPI_EVEN",            WF_LWTBL_WPI_EVEN_MASK,         NO_SHIFT_DEFINE,	false},
++	{"AAD_OM",              WF_LWTBL_AAD_OM_MASK,           NO_SHIFT_DEFINE,	false},
++	{"CIPHER_PGTK",WF_LWTBL_CIPHER_SUIT_PGTK_MASK, WF_LWTBL_CIPHER_SUIT_PGTK_SHIFT,	true},
++	{"FROM_DS",             WF_LWTBL_FD_MASK,               NO_SHIFT_DEFINE,	false},
++	{"TO_DS",               WF_LWTBL_TD_MASK,               NO_SHIFT_DEFINE,	false},
++	{"SW",                  WF_LWTBL_SW_MASK,               NO_SHIFT_DEFINE,	false},
++	{"UL",                  WF_LWTBL_UL_MASK,               NO_SHIFT_DEFINE,	false},
++	{"TX_POWER_SAVE",       WF_LWTBL_TX_PS_MASK,            NO_SHIFT_DEFINE,	true},
++	{"QOS",                 WF_LWTBL_QOS_MASK,              NO_SHIFT_DEFINE,	false},
++	{"HT",                  WF_LWTBL_HT_MASK,               NO_SHIFT_DEFINE,	false},
++	{"VHT",                 WF_LWTBL_VHT_MASK,              NO_SHIFT_DEFINE,	false},
++	{"HE",                  WF_LWTBL_HE_MASK,               NO_SHIFT_DEFINE,	false},
++	{"EHT",                 WF_LWTBL_EHT_MASK,              NO_SHIFT_DEFINE,	false},
++	{"MESH",                WF_LWTBL_MESH_MASK,             NO_SHIFT_DEFINE,	true},
++	{NULL,}
++};
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW2_7992[] = {
++	{"AID",                 WF_LWTBL_AID_MASK,              WF_LWTBL_AID_SHIFT,	false},
++	{"GID_SU",              WF_LWTBL_GID_SU_MASK,           NO_SHIFT_DEFINE,	false},
++	{"DUAL_PTEC_EN",        WF_LWTBL_DUAL_PTEC_EN_MASK,     NO_SHIFT_DEFINE,	false},
++	{"DUAL_CTS_CAP",        WF_LWTBL_DUAL_CTS_CAP_MASK,     NO_SHIFT_DEFINE,	false},
++	{"CIPHER_PGTK",WF_LWTBL_CIPHER_SUIT_PGTK_MASK, WF_LWTBL_CIPHER_SUIT_PGTK_SHIFT,	true},
++	{"FROM_DS",             WF_LWTBL_FD_MASK,               NO_SHIFT_DEFINE,	false},
++	{"TO_DS",               WF_LWTBL_TD_MASK,               NO_SHIFT_DEFINE,	false},
++	{"SW",                  WF_LWTBL_SW_MASK,               NO_SHIFT_DEFINE,	false},
++	{"UL",                  WF_LWTBL_UL_MASK,               NO_SHIFT_DEFINE,	false},
++	{"TX_POWER_SAVE",       WF_LWTBL_TX_PS_MASK,            NO_SHIFT_DEFINE,	true},
++	{"QOS",                 WF_LWTBL_QOS_MASK,              NO_SHIFT_DEFINE,	false},
++	{"HT",                  WF_LWTBL_HT_MASK,               NO_SHIFT_DEFINE,	false},
++	{"VHT",                 WF_LWTBL_VHT_MASK,              NO_SHIFT_DEFINE,	false},
++	{"HE",                  WF_LWTBL_HE_MASK,               NO_SHIFT_DEFINE,	false},
++	{"EHT",                 WF_LWTBL_EHT_MASK,              NO_SHIFT_DEFINE,	false},
++	{"MESH",                WF_LWTBL_MESH_MASK,             NO_SHIFT_DEFINE,	true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw2(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 2 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 2\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_TRX_CAP_DW_2*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW2[i].name) {
++
++		if (WTBL_LMAC_DW2[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW2[i].name,
++					 (dw_value & WTBL_LMAC_DW2[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW2[i].name,
++					  (dw_value & WTBL_LMAC_DW2[i].mask) >> WTBL_LMAC_DW2[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW3[] = {
++	{"WMM_Q",           WF_LWTBL_WMM_Q_MASK,		WF_LWTBL_WMM_Q_SHIFT,		false},
++	{"EHT_SIG_MCS",     WF_LWTBL_EHT_SIG_MCS_MASK,		WF_LWTBL_EHT_SIG_MCS_SHIFT,	false},
++	{"HDRT_MODE",       WF_LWTBL_HDRT_MODE_MASK,		NO_SHIFT_DEFINE,		false},
++	{"BEAM_CHG",        WF_LWTBL_BEAM_CHG_MASK,		NO_SHIFT_DEFINE,		false},
++	{"EHT_LTF_SYM_NUM", WF_LWTBL_EHT_LTF_SYM_NUM_OPT_MASK,  WF_LWTBL_EHT_LTF_SYM_NUM_OPT_SHIFT,	true},
++	{"PFMU_IDX",	    WF_LWTBL_PFMU_IDX_MASK,		WF_LWTBL_PFMU_IDX_SHIFT,	false},
++	{"ULPF_IDX",	    WF_LWTBL_ULPF_IDX_MASK,		WF_LWTBL_ULPF_IDX_SHIFT,	false},
++	{"RIBF",	    WF_LWTBL_RIBF_MASK,			NO_SHIFT_DEFINE,		false},
++	{"ULPF",	    WF_LWTBL_ULPF_MASK,			NO_SHIFT_DEFINE,		false},
++	{"BYPASS_TXSMM",    WF_LWTBL_BYPASS_TXSMM_MASK,         NO_SHIFT_DEFINE,		true},
++	{"TBF_HT",          WF_LWTBL_TBF_HT_MASK,		NO_SHIFT_DEFINE,		false},
++	{"TBF_VHT",         WF_LWTBL_TBF_VHT_MASK,		NO_SHIFT_DEFINE,		false},
++	{"TBF_HE",          WF_LWTBL_TBF_HE_MASK,		NO_SHIFT_DEFINE,		false},
++	{"TBF_EHT",         WF_LWTBL_TBF_EHT_MASK,		NO_SHIFT_DEFINE,		false},
++	{"IGN_FBK",         WF_LWTBL_IGN_FBK_MASK,		NO_SHIFT_DEFINE,		true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw3(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 3 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 3\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_TRX_CAP_DW_3*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW3[i].name) {
++
++		if (WTBL_LMAC_DW3[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW3[i].name,
++					 (dw_value & WTBL_LMAC_DW3[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW3[i].name,
++					  (dw_value & WTBL_LMAC_DW3[i].mask) >> WTBL_LMAC_DW3[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW4[] = {
++	{"NEGOTIATED_WINSIZE0",	WF_LWTBL_NEGOTIATED_WINSIZE0_MASK,	WF_LWTBL_NEGOTIATED_WINSIZE0_SHIFT,    false},
++	{"WINSIZE1",	WF_LWTBL_NEGOTIATED_WINSIZE1_MASK,	WF_LWTBL_NEGOTIATED_WINSIZE1_SHIFT,    false},
++	{"WINSIZE2",	WF_LWTBL_NEGOTIATED_WINSIZE2_MASK,	WF_LWTBL_NEGOTIATED_WINSIZE2_SHIFT,    false},
++	{"WINSIZE3",	WF_LWTBL_NEGOTIATED_WINSIZE3_MASK,	WF_LWTBL_NEGOTIATED_WINSIZE3_SHIFT,    true},
++	{"WINSIZE4",	WF_LWTBL_NEGOTIATED_WINSIZE4_MASK,	WF_LWTBL_NEGOTIATED_WINSIZE4_SHIFT,    false},
++	{"WINSIZE5",	WF_LWTBL_NEGOTIATED_WINSIZE5_MASK,	WF_LWTBL_NEGOTIATED_WINSIZE5_SHIFT,    false},
++	{"WINSIZE6",	WF_LWTBL_NEGOTIATED_WINSIZE6_MASK,	WF_LWTBL_NEGOTIATED_WINSIZE6_SHIFT,    false},
++	{"WINSIZE7",	WF_LWTBL_NEGOTIATED_WINSIZE7_MASK,	WF_LWTBL_NEGOTIATED_WINSIZE7_SHIFT,    true},
++	{"PE",              WF_LWTBL_PE_MASK,           WF_LWTBL_PE_SHIFT,	false},
++	{"DIS_RHTR",        WF_LWTBL_DIS_RHTR_MASK,     NO_SHIFT_DEFINE,	false},
++	{"LDPC_HT",         WF_LWTBL_LDPC_HT_MASK,      NO_SHIFT_DEFINE,	false},
++	{"LDPC_VHT",        WF_LWTBL_LDPC_VHT_MASK,     NO_SHIFT_DEFINE,	false},
++	{"LDPC_HE",         WF_LWTBL_LDPC_HE_MASK,      NO_SHIFT_DEFINE,	false},
++	{"LDPC_EHT",	    WF_LWTBL_LDPC_EHT_MASK,	NO_SHIFT_DEFINE,	true},
++	{"BA_MODE",	    WF_LWTBL_BA_MODE_MASK,	NO_SHIFT_DEFINE,	true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw4(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 4 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 4\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_TRX_CAP_DW_4*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW4[i].name) {
++		if (WTBL_LMAC_DW4[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW4[i].name,
++					 (dw_value & WTBL_LMAC_DW4[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW4[i].name,
++					  (dw_value & WTBL_LMAC_DW4[i].mask) >> WTBL_LMAC_DW4[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse *WTBL_LMAC_DW5;
++static const struct berse_wtbl_parse WTBL_LMAC_DW5_7996[] = {
++	{"AF",                  WF_LWTBL_AF_MASK,           WF_LWTBL_AF_SHIFT,	false},
++	{"AF_HE",               WF_LWTBL_AF_HE_MASK,        WF_LWTBL_AF_HE_SHIFT,false},
++	{"RTS",                 WF_LWTBL_RTS_MASK,          NO_SHIFT_DEFINE,	false},
++	{"SMPS",                WF_LWTBL_SMPS_MASK,         NO_SHIFT_DEFINE,	false},
++	{"DYN_BW",              WF_LWTBL_DYN_BW_MASK,       NO_SHIFT_DEFINE,	true},
++	{"MMSS",                WF_LWTBL_MMSS_MASK,         WF_LWTBL_MMSS_SHIFT,false},
++	{"USR",                 WF_LWTBL_USR_MASK,          NO_SHIFT_DEFINE,	false},
++	{"SR_RATE",             WF_LWTBL_SR_R_MASK,         WF_LWTBL_SR_R_SHIFT,false},
++	{"SR_ABORT",            WF_LWTBL_SR_ABORT_MASK,     NO_SHIFT_DEFINE,	true},
++	{"TX_POWER_OFFSET",     WF_LWTBL_TX_POWER_OFFSET_MASK,  WF_LWTBL_TX_POWER_OFFSET_SHIFT,	false},
++	{"LTF_EHT",		WF_LWTBL_LTF_EHT_MASK,      WF_LWTBL_LTF_EHT_SHIFT, false},
++	{"GI_EHT",		WF_LWTBL_GI_EHT_MASK,       WF_LWTBL_GI_EHT_SHIFT, false},
++	{"DOPPL",               WF_LWTBL_DOPPL_MASK,        NO_SHIFT_DEFINE,	false},
++	{"TXOP_PS_CAP",         WF_LWTBL_TXOP_PS_CAP_MASK,  NO_SHIFT_DEFINE,	false},
++	{"DONOT_UPDATE_I_PSM",  WF_LWTBL_DU_I_PSM_MASK,     NO_SHIFT_DEFINE,	true},
++	{"I_PSM",               WF_LWTBL_I_PSM_MASK,        NO_SHIFT_DEFINE,	false},
++	{"PSM",                 WF_LWTBL_PSM_MASK,          NO_SHIFT_DEFINE,	false},
++	{"SKIP_TX",             WF_LWTBL_SKIP_TX_MASK,      NO_SHIFT_DEFINE,	true},
++	{NULL,}
++};
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW5_7992[] = {
++	{"AF",                  WF_LWTBL_AF_MASK_7992,      WF_LWTBL_AF_SHIFT,	false},
++	{"RTS",                 WF_LWTBL_RTS_MASK,          NO_SHIFT_DEFINE,	false},
++	{"SMPS",                WF_LWTBL_SMPS_MASK,         NO_SHIFT_DEFINE,	false},
++	{"DYN_BW",              WF_LWTBL_DYN_BW_MASK,       NO_SHIFT_DEFINE,	true},
++	{"MMSS",                WF_LWTBL_MMSS_MASK,         WF_LWTBL_MMSS_SHIFT,false},
++	{"USR",                 WF_LWTBL_USR_MASK,          NO_SHIFT_DEFINE,	false},
++	{"SR_RATE",             WF_LWTBL_SR_R_MASK,         WF_LWTBL_SR_R_SHIFT,false},
++	{"SR_ABORT",            WF_LWTBL_SR_ABORT_MASK,     NO_SHIFT_DEFINE,	true},
++	{"TX_POWER_OFFSET",     WF_LWTBL_TX_POWER_OFFSET_MASK,  WF_LWTBL_TX_POWER_OFFSET_SHIFT,	false},
++	{"LTF_EHT",		WF_LWTBL_LTF_EHT_MASK,      WF_LWTBL_LTF_EHT_SHIFT, false},
++	{"GI_EHT",		WF_LWTBL_GI_EHT_MASK,       WF_LWTBL_GI_EHT_SHIFT, false},
++	{"DOPPL",               WF_LWTBL_DOPPL_MASK,        NO_SHIFT_DEFINE,	false},
++	{"TXOP_PS_CAP",         WF_LWTBL_TXOP_PS_CAP_MASK,  NO_SHIFT_DEFINE,	false},
++	{"DONOT_UPDATE_I_PSM",  WF_LWTBL_DU_I_PSM_MASK,     NO_SHIFT_DEFINE,	true},
++	{"I_PSM",               WF_LWTBL_I_PSM_MASK,        NO_SHIFT_DEFINE,	false},
++	{"PSM",                 WF_LWTBL_PSM_MASK,          NO_SHIFT_DEFINE,	false},
++	{"SKIP_TX",             WF_LWTBL_SKIP_TX_MASK,      NO_SHIFT_DEFINE,	true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw5(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 5 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 5\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_TRX_CAP_DW_5*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW5[i].name) {
++		if (WTBL_LMAC_DW5[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW5[i].name,
++					 (dw_value & WTBL_LMAC_DW5[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW5[i].name,
++					  (dw_value & WTBL_LMAC_DW5[i].mask) >> WTBL_LMAC_DW5[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW6[] = {
++	{"CBRN",        WF_LWTBL_CBRN_MASK,	    WF_LWTBL_CBRN_SHIFT,	false},
++	{"DBNSS_EN",    WF_LWTBL_DBNSS_EN_MASK, NO_SHIFT_DEFINE,	false},
++	{"BAF_EN",      WF_LWTBL_BAF_EN_MASK,   NO_SHIFT_DEFINE,	false},
++	{"RDGBA",       WF_LWTBL_RDGBA_MASK,    NO_SHIFT_DEFINE,	false},
++	{"RDG",         WF_LWTBL_R_MASK,        NO_SHIFT_DEFINE,	false},
++	{"SPE_IDX",     WF_LWTBL_SPE_IDX_MASK,  WF_LWTBL_SPE_IDX_SHIFT,	true},
++	{"G2",          WF_LWTBL_G2_MASK,       NO_SHIFT_DEFINE,	false},
++	{"G4",          WF_LWTBL_G4_MASK,       NO_SHIFT_DEFINE,	false},
++	{"G8",          WF_LWTBL_G8_MASK,       NO_SHIFT_DEFINE,	false},
++	{"G16",         WF_LWTBL_G16_MASK,      NO_SHIFT_DEFINE,	true},
++	{"G2_LTF",      WF_LWTBL_G2_LTF_MASK,   WF_LWTBL_G2_LTF_SHIFT,	false},
++	{"G4_LTF",      WF_LWTBL_G4_LTF_MASK,   WF_LWTBL_G4_LTF_SHIFT,	false},
++	{"G8_LTF",      WF_LWTBL_G8_LTF_MASK,   WF_LWTBL_G8_LTF_SHIFT,	false},
++	{"G16_LTF",     WF_LWTBL_G16_LTF_MASK,  WF_LWTBL_G16_LTF_SHIFT,	true},
++	{"G2_HE",       WF_LWTBL_G2_HE_MASK,    WF_LWTBL_G2_HE_SHIFT,	false},
++	{"G4_HE",       WF_LWTBL_G4_HE_MASK,    WF_LWTBL_G4_HE_SHIFT,	false},
++	{"G8_HE",       WF_LWTBL_G8_HE_MASK,    WF_LWTBL_G8_HE_SHIFT,	false},
++	{"G16_HE",      WF_LWTBL_G16_HE_MASK,   WF_LWTBL_G16_HE_SHIFT,	true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw6(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 6 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 6\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_TRX_CAP_DW_6*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW6[i].name) {
++		if (WTBL_LMAC_DW6[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW6[i].name,
++					 (dw_value & WTBL_LMAC_DW6[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW6[i].name,
++					  (dw_value & WTBL_LMAC_DW6[i].mask) >> WTBL_LMAC_DW6[i].shift);
++		i++;
++	}
++}
++
++static void parse_fmac_lwtbl_dw7(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	int i = 0;
++
++	/* LMAC WTBL DW 7 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 7\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_TRX_CAP_DW_7*4]);
++	dw_value = *addr;
++
++	for (i = 0; i < 8; i++) {
++		seq_printf(s, "\tBA_WIN_SIZE%u:%lu\n", i, ((dw_value & BITS(i*4, i*4+3)) >> i*4));
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW8[] = {
++	{"RTS_FAIL_CNT_AC0",    WF_LWTBL_AC0_RTS_FAIL_CNT_MASK,	WF_LWTBL_AC0_RTS_FAIL_CNT_SHIFT,	false},
++	{"AC1",                 WF_LWTBL_AC1_RTS_FAIL_CNT_MASK,	WF_LWTBL_AC1_RTS_FAIL_CNT_SHIFT,	false},
++	{"AC2",                 WF_LWTBL_AC2_RTS_FAIL_CNT_MASK,	WF_LWTBL_AC2_RTS_FAIL_CNT_SHIFT,	false},
++	{"AC3",                 WF_LWTBL_AC3_RTS_FAIL_CNT_MASK,	WF_LWTBL_AC3_RTS_FAIL_CNT_SHIFT,	true},
++	{"PARTIAL_AID",         WF_LWTBL_PARTIAL_AID_MASK,		WF_LWTBL_PARTIAL_AID_SHIFT,	false},
++	{"CHK_PER",             WF_LWTBL_CHK_PER_MASK,		NO_SHIFT_DEFINE,	true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw8(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 8 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 8\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_TRX_CAP_DW_8*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW8[i].name) {
++		if (WTBL_LMAC_DW8[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW8[i].name,
++					 (dw_value & WTBL_LMAC_DW8[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW8[i].name,
++					  (dw_value & WTBL_LMAC_DW8[i].mask) >> WTBL_LMAC_DW8[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse *WTBL_LMAC_DW9;
++static const struct berse_wtbl_parse WTBL_LMAC_DW9_7996[] = {
++	{"RX_AVG_MPDU_SIZE",    WF_LWTBL_RX_AVG_MPDU_SIZE_MASK,    WF_LWTBL_RX_AVG_MPDU_SIZE_SHIFT,	false},
++	{"PRITX_SW_MODE",       WF_LWTBL_PRITX_SW_MODE_MASK,       NO_SHIFT_DEFINE,	false},
++	{"PRITX_ERSU",	    WF_LWTBL_PRITX_ERSU_MASK,	       NO_SHIFT_DEFINE,	false},
++	{"PRITX_PLR",           WF_LWTBL_PRITX_PLR_MASK,           NO_SHIFT_DEFINE,	true},
++	{"PRITX_DCM",           WF_LWTBL_PRITX_DCM_MASK,           NO_SHIFT_DEFINE,	false},
++	{"PRITX_ER106T",        WF_LWTBL_PRITX_ER106T_MASK,        NO_SHIFT_DEFINE,	true},
++	/* {"FCAP(0:20 1:~40)",    WTBL_FCAP_20_TO_160_MHZ,	WTBL_FCAP_20_TO_160_MHZ_OFFSET}, */
++	{"MPDU_FAIL_CNT",       WF_LWTBL_MPDU_FAIL_CNT_MASK,       WF_LWTBL_MPDU_FAIL_CNT_SHIFT,	false},
++	{"MPDU_OK_CNT",         WF_LWTBL_MPDU_OK_CNT_MASK,         WF_LWTBL_MPDU_OK_CNT_SHIFT,	false},
++	{"RATE_IDX",            WF_LWTBL_RATE_IDX_MASK,            WF_LWTBL_RATE_IDX_SHIFT,	true},
++	{NULL,}
++};
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW9_7992[] = {
++	{"RX_AVG_MPDU_SIZE",    WF_LWTBL_RX_AVG_MPDU_SIZE_MASK,    WF_LWTBL_RX_AVG_MPDU_SIZE_SHIFT,	false},
++	{"PRITX_SW_MODE",       WF_LWTBL_PRITX_SW_MODE_MASK_7992,       NO_SHIFT_DEFINE,	false},
++	{"PRITX_ERSU",	    WF_LWTBL_PRITX_ERSU_MASK_7992,	       NO_SHIFT_DEFINE,	false},
++	{"PRITX_PLR",           WF_LWTBL_PRITX_PLR_MASK_7992,           NO_SHIFT_DEFINE,	true},
++	{"PRITX_DCM",           WF_LWTBL_PRITX_DCM_MASK,           NO_SHIFT_DEFINE,	false},
++	{"PRITX_ER106T",        WF_LWTBL_PRITX_ER106T_MASK,        NO_SHIFT_DEFINE,	true},
++	/* {"FCAP(0:20 1:~40)",    WTBL_FCAP_20_TO_160_MHZ,	WTBL_FCAP_20_TO_160_MHZ_OFFSET}, */
++	{"MPDU_FAIL_CNT",       WF_LWTBL_MPDU_FAIL_CNT_MASK,       WF_LWTBL_MPDU_FAIL_CNT_SHIFT,	false},
++	{"MPDU_OK_CNT",         WF_LWTBL_MPDU_OK_CNT_MASK,         WF_LWTBL_MPDU_OK_CNT_SHIFT,	false},
++	{"RATE_IDX",            WF_LWTBL_RATE_IDX_MASK,            WF_LWTBL_RATE_IDX_SHIFT,	true},
++	{NULL,}
++};
++
++char *fcap_name[] = {"20MHz", "20/40MHz", "20/40/80MHz", "20/40/80/160/80+80MHz", "20/40/80/160/80+80/320MHz"};
++
++static void parse_fmac_lwtbl_dw9(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 9 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 9\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_TRX_CAP_DW_9*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW9[i].name) {
++		if (WTBL_LMAC_DW9[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW9[i].name,
++					 (dw_value & WTBL_LMAC_DW9[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW9[i].name,
++					  (dw_value & WTBL_LMAC_DW9[i].mask) >> WTBL_LMAC_DW9[i].shift);
++		i++;
++	}
++
++	/* FCAP parser */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "FCAP:%s\n", fcap_name[(dw_value & WF_LWTBL_FCAP_MASK) >> WF_LWTBL_FCAP_SHIFT]);
++}
++
++#define HW_TX_RATE_TO_MODE(_x)			(((_x) & WTBL_RATE_TX_MODE_MASK) >> WTBL_RATE_TX_MODE_OFFSET)
++#define HW_TX_RATE_TO_MCS(_x, _mode)		((_x) & WTBL_RATE_TX_RATE_MASK >> WTBL_RATE_TX_RATE_OFFSET)
++#define HW_TX_RATE_TO_NSS(_x)			(((_x) & WTBL_RATE_NSTS_MASK) >> WTBL_RATE_NSTS_OFFSET)
++#define HW_TX_RATE_TO_STBC(_x)			(((_x) & WTBL_RATE_STBC_MASK) >> WTBL_RATE_STBC_OFFSET)
++
++#define MAX_TX_MODE 16
++static char *HW_TX_MODE_STR[] = {"CCK", "OFDM", "HT-Mix", "HT-GF", "VHT",
++				 "N/A", "N/A", "N/A",
++				 "HE_SU", "HE_EXT_SU", "HE_TRIG", "HE_MU",
++				 "N/A",
++				 "EHT_EXT_SU", "EHT_TRIG", "EHT_MU",
++				 "N/A"};
++static char *HW_TX_RATE_CCK_STR[] = {"1M", "2Mlong", "5.5Mlong", "11Mlong", "N/A", "2Mshort", "5.5Mshort", "11Mshort", "N/A"};
++static char *HW_TX_RATE_OFDM_STR[] = {"6M", "9M", "12M", "18M", "24M", "36M", "48M", "54M", "N/A"};
++
++static char *hw_rate_ofdm_str(uint16_t ofdm_idx)
++{
++	switch (ofdm_idx) {
++	case 11: /* 6M */
++		return HW_TX_RATE_OFDM_STR[0];
++
++	case 15: /* 9M */
++		return HW_TX_RATE_OFDM_STR[1];
++
++	case 10: /* 12M */
++		return HW_TX_RATE_OFDM_STR[2];
++
++	case 14: /* 18M */
++		return HW_TX_RATE_OFDM_STR[3];
++
++	case 9: /* 24M */
++		return HW_TX_RATE_OFDM_STR[4];
++
++	case 13: /* 36M */
++		return HW_TX_RATE_OFDM_STR[5];
++
++	case 8: /* 48M */
++		return HW_TX_RATE_OFDM_STR[6];
++
++	case 12: /* 54M */
++		return HW_TX_RATE_OFDM_STR[7];
++
++	default:
++		return HW_TX_RATE_OFDM_STR[8];
++	}
++}
++
++static char *hw_rate_str(u8 mode, uint16_t rate_idx)
++{
++	if (mode == 0)
++		return rate_idx < 8 ? HW_TX_RATE_CCK_STR[rate_idx] : HW_TX_RATE_CCK_STR[8];
++	else if (mode == 1)
++		return hw_rate_ofdm_str(rate_idx);
++	else
++		return "MCS";
++}
++
++static void
++parse_rate(struct seq_file *s, uint16_t rate_idx, uint16_t txrate)
++{
++	uint16_t txmode, mcs, nss, stbc;
++
++	txmode = HW_TX_RATE_TO_MODE(txrate);
++	mcs = HW_TX_RATE_TO_MCS(txrate, txmode);
++	nss = HW_TX_RATE_TO_NSS(txrate);
++	stbc = HW_TX_RATE_TO_STBC(txrate);
++
++	seq_printf(s, "\tRate%d(0x%x):TxMode=%d(%s), TxRate=%d(%s), Nsts=%d, STBC=%d\n",
++			  rate_idx + 1, txrate,
++			  txmode, (txmode < MAX_TX_MODE ? HW_TX_MODE_STR[txmode] : HW_TX_MODE_STR[MAX_TX_MODE]),
++			  mcs, hw_rate_str(txmode, mcs), nss, stbc);
++}
++
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW10[] = {
++	{"RATE1",       WF_LWTBL_RATE1_MASK,        WF_LWTBL_RATE1_SHIFT},
++	{"RATE2",       WF_LWTBL_RATE2_MASK,        WF_LWTBL_RATE2_SHIFT},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw10(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 10 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 10\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_AUTO_RATE_1_2*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW10[i].name) {
++		parse_rate(s, i, (dw_value & WTBL_LMAC_DW10[i].mask) >> WTBL_LMAC_DW10[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW11[] = {
++	{"RATE3",       WF_LWTBL_RATE3_MASK,        WF_LWTBL_RATE3_SHIFT},
++	{"RATE4",       WF_LWTBL_RATE4_MASK,        WF_LWTBL_RATE4_SHIFT},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw11(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 11 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 11\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_AUTO_RATE_3_4*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW11[i].name) {
++		parse_rate(s, i+2, (dw_value & WTBL_LMAC_DW11[i].mask) >> WTBL_LMAC_DW11[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW12[] = {
++	{"RATE5",       WF_LWTBL_RATE5_MASK,        WF_LWTBL_RATE5_SHIFT},
++	{"RATE6",       WF_LWTBL_RATE6_MASK,        WF_LWTBL_RATE6_SHIFT},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw12(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 12 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 12\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_AUTO_RATE_5_6*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW12[i].name) {
++		parse_rate(s, i+4, (dw_value & WTBL_LMAC_DW12[i].mask) >> WTBL_LMAC_DW12[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW13[] = {
++	{"RATE7",       WF_LWTBL_RATE7_MASK,        WF_LWTBL_RATE7_SHIFT},
++	{"RATE8",       WF_LWTBL_RATE8_MASK,        WF_LWTBL_RATE8_SHIFT},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw13(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 13 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 13\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_AUTO_RATE_7_8*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW13[i].name) {
++		parse_rate(s, i+6, (dw_value & WTBL_LMAC_DW13[i].mask) >> WTBL_LMAC_DW13[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW14_BMC[] = {
++	{"CIPHER_IGTK",         WF_LWTBL_CIPHER_SUIT_IGTK_MASK,    WF_LWTBL_CIPHER_SUIT_IGTK_SHIFT,		false},
++	{"CIPHER_BIGTK",        WF_LWTBL_CIPHER_SUIT_BIGTK_MASK,   WF_LWTBL_CIPHER_SUIT_BIGTK_SHIFT,	true},
++	{NULL,}
++};
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW14[] = {
++	{"RATE1_TX_CNT",      WF_LWTBL_RATE1_TX_CNT_MASK,     WF_LWTBL_RATE1_TX_CNT_SHIFT,   false},
++	{"RATE1_FAIL_CNT",    WF_LWTBL_RATE1_FAIL_CNT_MASK,   WF_LWTBL_RATE1_FAIL_CNT_SHIFT, true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw14(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr, *muar_addr = 0;
++	u32 dw_value, muar_dw_value = 0;
++	u16 i = 0;
++
++	/* DUMP DW14 for BMC entry only */
++	muar_addr = (u32 *)&(lwtbl[WF_LWTBL_MUAR_DW*4]);
++	muar_dw_value = *muar_addr;
++	if (((muar_dw_value & WF_LWTBL_MUAR_MASK) >> WF_LWTBL_MUAR_SHIFT)
++		== MUAR_INDEX_OWN_MAC_ADDR_BC_MC) {
++		/* LMAC WTBL DW 14 */
++		seq_printf(s, "\t\n");
++		seq_printf(s, "LWTBL DW 14\n");
++		addr = (u32 *)&(lwtbl[WF_LWTBL_CIPHER_SUIT_IGTK_DW*4]);
++		dw_value = *addr;
++
++		while (WTBL_LMAC_DW14_BMC[i].name) {
++			if (WTBL_LMAC_DW14_BMC[i].shift == NO_SHIFT_DEFINE)
++				seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW14_BMC[i].name,
++					(dw_value & WTBL_LMAC_DW14_BMC[i].mask) ? 1 : 0);
++			else
++				seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW14_BMC[i].name,
++					(dw_value & WTBL_LMAC_DW14_BMC[i].mask) >> WTBL_LMAC_DW14_BMC[i].shift);
++			i++;
++		}
++	} else {
++		seq_printf(s, "\t\n");
++		seq_printf(s, "LWTBL DW 14\n");
++		addr = (u32 *)&(lwtbl[WF_LWTBL_CIPHER_SUIT_IGTK_DW*4]);
++		dw_value = *addr;
++
++		while (WTBL_LMAC_DW14[i].name) {
++			if (WTBL_LMAC_DW14[i].shift == NO_SHIFT_DEFINE)
++				seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW14[i].name,
++					(dw_value & WTBL_LMAC_DW14[i].mask) ? 1 : 0);
++			else
++				seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW14[i].name,
++					(dw_value & WTBL_LMAC_DW14[i].mask) >> WTBL_LMAC_DW14[i].shift);
++			i++;
++		}
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW28[] = {
++	{"RELATED_IDX0",	WF_LWTBL_RELATED_IDX0_MASK,		WF_LWTBL_RELATED_IDX0_SHIFT,	false},
++	{"RELATED_BAND0",	WF_LWTBL_RELATED_BAND0_MASK,		WF_LWTBL_RELATED_BAND0_SHIFT,	false},
++	{"PRI_MLD_BAND",    WF_LWTBL_PRIMARY_MLD_BAND_MASK,		WF_LWTBL_PRIMARY_MLD_BAND_SHIFT,	true},
++	{"RELATED_IDX1",	WF_LWTBL_RELATED_IDX1_MASK,		WF_LWTBL_RELATED_IDX1_SHIFT,	false},
++	{"RELATED_BAND1",   WF_LWTBL_RELATED_BAND1_MASK,		WF_LWTBL_RELATED_BAND1_SHIFT,	false},
++	{"SEC_MLD_BAND",	WF_LWTBL_SECONDARY_MLD_BAND_MASK,	WF_LWTBL_SECONDARY_MLD_BAND_SHIFT,	true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw28(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 28 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 28\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_MLO_INFO_LINE_1*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW28[i].name) {
++		if (WTBL_LMAC_DW28[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW28[i].name,
++				(dw_value & WTBL_LMAC_DW28[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW28[i].name,
++				(dw_value & WTBL_LMAC_DW28[i].mask) >>
++					WTBL_LMAC_DW28[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW29[] = {
++	{"DISPATCH_POLICY_MLD_TID0", WF_LWTBL_DISPATCH_POLICY0_MASK,	WF_LWTBL_DISPATCH_POLICY0_SHIFT,	false},
++	{"MLD_TID1",	WF_LWTBL_DISPATCH_POLICY1_MASK,		WF_LWTBL_DISPATCH_POLICY1_SHIFT,	false},
++	{"MLD_TID2",	WF_LWTBL_DISPATCH_POLICY2_MASK,		WF_LWTBL_DISPATCH_POLICY2_SHIFT,	false},
++	{"MLD_TID3",	WF_LWTBL_DISPATCH_POLICY3_MASK,	WF_LWTBL_DISPATCH_POLICY3_SHIFT,	true},
++	{"MLD_TID4",	WF_LWTBL_DISPATCH_POLICY4_MASK,		WF_LWTBL_DISPATCH_POLICY4_SHIFT,	false},
++	{"MLD_TID5",	WF_LWTBL_DISPATCH_POLICY5_MASK,		WF_LWTBL_DISPATCH_POLICY5_SHIFT,	false},
++	{"MLD_TID6",	WF_LWTBL_DISPATCH_POLICY6_MASK,		WF_LWTBL_DISPATCH_POLICY6_SHIFT,	false},
++	{"MLD_TID7",	WF_LWTBL_DISPATCH_POLICY7_MASK,		WF_LWTBL_DISPATCH_POLICY7_SHIFT,	true},
++	{"OMLD_ID",		WF_LWTBL_OWN_MLD_ID_MASK,	WF_LWTBL_OWN_MLD_ID_SHIFT,	false},
++	{"EMLSR0",		WF_LWTBL_EMLSR0_MASK,		NO_SHIFT_DEFINE,	false},
++	{"EMLMR0",		WF_LWTBL_EMLMR0_MASK,		NO_SHIFT_DEFINE,	false},
++	{"EMLSR1",		WF_LWTBL_EMLSR1_MASK,		NO_SHIFT_DEFINE,	false},
++	{"EMLMR1",		WF_LWTBL_EMLMR1_MASK,		NO_SHIFT_DEFINE,	true},
++	{"EMLSR2",		WF_LWTBL_EMLSR2_MASK,		NO_SHIFT_DEFINE,	false},
++	{"EMLMR2",		WF_LWTBL_EMLMR2_MASK,		NO_SHIFT_DEFINE,	false},
++	{"STR_BITMAP",	WF_LWTBL_STR_BITMAP_MASK,	WF_LWTBL_STR_BITMAP_SHIFT,	true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw29(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 29 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 29\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_MLO_INFO_LINE_2*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW29[i].name) {
++		if (WTBL_LMAC_DW29[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW29[i].name,
++				(dw_value & WTBL_LMAC_DW29[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW29[i].name,
++				(dw_value & WTBL_LMAC_DW29[i].mask) >>
++					WTBL_LMAC_DW29[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW30[] = {
++	{"DISPATCH_ORDER",	WF_LWTBL_DISPATCH_ORDER_MASK,	WF_LWTBL_DISPATCH_ORDER_SHIFT,	false},
++	{"DISPATCH_RATIO",	WF_LWTBL_DISPATCH_RATIO_MASK,	WF_LWTBL_DISPATCH_RATIO_SHIFT,	false},
++	{"LINK_MGF",		WF_LWTBL_LINK_MGF_MASK,		WF_LWTBL_LINK_MGF_SHIFT,	true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw30(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 30 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 30\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_MLO_INFO_LINE_3*4]);
++	dw_value = *addr;
++
++
++	while (WTBL_LMAC_DW30[i].name) {
++		if (WTBL_LMAC_DW30[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW30[i].name,
++				(dw_value & WTBL_LMAC_DW30[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW30[i].name,
++				(dw_value & WTBL_LMAC_DW30[i].mask) >> WTBL_LMAC_DW30[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW31[] = {
++	{"BFTX_TB",          WF_LWTBL_BFTX_TB_MASK,                 NO_SHIFT_DEFINE,    false},
++	{"DROP",          WF_LWTBL_DROP_MASK,                 NO_SHIFT_DEFINE,    false},
++	{"CASCAD",	        WF_LWTBL_CASCAD_MASK,			NO_SHIFT_DEFINE,    false},
++	{"ALL_ACK",	        WF_LWTBL_ALL_ACK_MASK,			NO_SHIFT_DEFINE,    false},
++	{"MPDU_SIZE",	WF_LWTBL_MPDU_SIZE_MASK,		WF_LWTBL_MPDU_SIZE_SHIFT,  false},
++	{"RXD_DUP_MODE",	WF_LWTBL_RXD_DUP_MODE_MASK,			WF_LWTBL_RXD_DUP_MODE_SHIFT,  true},
++	{"ACK_EN",		WF_LWTBL_ACK_EN_MASK,			NO_SHIFT_DEFINE,		true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw31(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 31 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 31\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_RESP_INFO_DW_31*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW31[i].name) {
++		if (WTBL_LMAC_DW31[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW31[i].name,
++				(dw_value & WTBL_LMAC_DW31[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW31[i].name,
++				(dw_value & WTBL_LMAC_DW31[i].mask) >>
++					WTBL_LMAC_DW31[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW32[] = {
++	{"OM_INFO",			WF_LWTBL_OM_INFO_MASK,			WF_LWTBL_OM_INFO_SHIFT,		false},
++	{"OM_INFO_EHT",         WF_LWTBL_OM_INFO_EHT_MASK,         WF_LWTBL_OM_INFO_EHT_SHIFT,  false},
++	{"RXD_DUP_FOR_OM_CHG",		WF_LWTBL_RXD_DUP_FOR_OM_CHG_MASK,	NO_SHIFT_DEFINE,		false},
++	{"RXD_DUP_WHITE_LIST",	WF_LWTBL_RXD_DUP_WHITE_LIST_MASK,	WF_LWTBL_RXD_DUP_WHITE_LIST_SHIFT,	false},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw32(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 32 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 32\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_RX_DUP_INFO_DW_32*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW32[i].name) {
++		if (WTBL_LMAC_DW32[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW32[i].name,
++				(dw_value & WTBL_LMAC_DW32[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW32[i].name,
++				(dw_value & WTBL_LMAC_DW32[i].mask) >>
++					WTBL_LMAC_DW32[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW33[] = {
++	{"USER_RSSI",                   WF_LWTBL_USER_RSSI_MASK,            WF_LWTBL_USER_RSSI_SHIFT,	false},
++	{"USER_SNR",                    WF_LWTBL_USER_SNR_MASK,             WF_LWTBL_USER_SNR_SHIFT,	false},
++	{"RAPID_REACTION_RATE",         WF_LWTBL_RAPID_REACTION_RATE_MASK,  WF_LWTBL_RAPID_REACTION_RATE_SHIFT,	true},
++	{"HT_AMSDU(Read Only)",         WF_LWTBL_HT_AMSDU_MASK,             NO_SHIFT_DEFINE,	false},
++	{"AMSDU_CROSS_LG(Read Only)",   WF_LWTBL_AMSDU_CROSS_LG_MASK,       NO_SHIFT_DEFINE,	true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw33(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 33 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 33\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_RX_STAT_CNT_LINE_1*4]);
++	dw_value = *addr;
++
++	while (WTBL_LMAC_DW33[i].name) {
++		if (WTBL_LMAC_DW33[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW33[i].name,
++				(dw_value & WTBL_LMAC_DW33[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW33[i].name,
++				(dw_value & WTBL_LMAC_DW33[i].mask) >>
++					WTBL_LMAC_DW33[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW34[] = {
++	{"RESP_RCPI0",	WF_LWTBL_RESP_RCPI0_MASK,	WF_LWTBL_RESP_RCPI0_SHIFT,	false},
++	{"RCPI1",	WF_LWTBL_RESP_RCPI1_MASK,	WF_LWTBL_RESP_RCPI1_SHIFT,	false},
++	{"RCPI2",	WF_LWTBL_RESP_RCPI2_MASK,	WF_LWTBL_RESP_RCPI2_SHIFT,	false},
++	{"RCPI3",	WF_LWTBL_RESP_RCPI3_MASK,	WF_LWTBL_RESP_RCPI3_SHIFT,	true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw34(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 34 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 34\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_RX_STAT_CNT_LINE_2*4]);
++	dw_value = *addr;
++
++
++	while (WTBL_LMAC_DW34[i].name) {
++		if (WTBL_LMAC_DW34[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW34[i].name,
++				(dw_value & WTBL_LMAC_DW34[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW34[i].name,
++				(dw_value & WTBL_LMAC_DW34[i].mask) >>
++					WTBL_LMAC_DW34[i].shift);
++		i++;
++	}
++}
++
++static const struct berse_wtbl_parse WTBL_LMAC_DW35[] = {
++	{"SNR 0",	WF_LWTBL_SNR_RX0_MASK,		WF_LWTBL_SNR_RX0_SHIFT,	false},
++	{"SNR 1",	WF_LWTBL_SNR_RX1_MASK,		WF_LWTBL_SNR_RX1_SHIFT,	false},
++	{"SNR 2",	WF_LWTBL_SNR_RX2_MASK,		WF_LWTBL_SNR_RX2_SHIFT,	false},
++	{"SNR 3",	WF_LWTBL_SNR_RX3_MASK,		WF_LWTBL_SNR_RX3_SHIFT,	true},
++	{NULL,}
++};
++
++static void parse_fmac_lwtbl_dw35(struct seq_file *s, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	/* LMAC WTBL DW 35 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "LWTBL DW 35\n");
++	addr = (u32 *)&(lwtbl[WTBL_GROUP_RX_STAT_CNT_LINE_3*4]);
++	dw_value = *addr;
++
++
++	while (WTBL_LMAC_DW35[i].name) {
++		if (WTBL_LMAC_DW35[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_LMAC_DW35[i].name,
++				(dw_value & WTBL_LMAC_DW35[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_LMAC_DW35[i].name,
++				(dw_value & WTBL_LMAC_DW35[i].mask) >>
++					WTBL_LMAC_DW35[i].shift);
++		i++;
++	}
++}
++
++static void parse_fmac_lwtbl_rx_stats(struct seq_file *s, u8 *lwtbl)
++{
++	parse_fmac_lwtbl_dw33(s, lwtbl);
++	parse_fmac_lwtbl_dw34(s, lwtbl);
++	parse_fmac_lwtbl_dw35(s, lwtbl);
++}
++
++static void parse_fmac_lwtbl_mlo_info(struct seq_file *s, u8 *lwtbl)
++{
++	parse_fmac_lwtbl_dw28(s, lwtbl);
++	parse_fmac_lwtbl_dw29(s, lwtbl);
++	parse_fmac_lwtbl_dw30(s, lwtbl);
++}
++
++static const struct berse_wtbl_parse WTBL_UMAC_DW9[] = {
++	{"RELATED_IDX0",	WF_UWTBL_RELATED_IDX0_MASK,		WF_UWTBL_RELATED_IDX0_SHIFT,	false},
++	{"RELATED_BAND0",	WF_UWTBL_RELATED_BAND0_MASK,		WF_UWTBL_RELATED_BAND0_SHIFT,	false},
++	{"PRI_MLD_BAND",    WF_UWTBL_PRIMARY_MLD_BAND_MASK,		WF_UWTBL_PRIMARY_MLD_BAND_SHIFT,	true},
++	{"RELATED_IDX1",	WF_UWTBL_RELATED_IDX1_MASK,		WF_UWTBL_RELATED_IDX1_SHIFT,	false},
++	{"RELATED_BAND1",   WF_UWTBL_RELATED_BAND1_MASK,		WF_UWTBL_RELATED_BAND1_SHIFT,	false},
++	{"SEC_MLD_BAND",	WF_UWTBL_SECONDARY_MLD_BAND_MASK,	WF_UWTBL_SECONDARY_MLD_BAND_SHIFT,	true},
++	{NULL,}
++};
++
++static void parse_fmac_uwtbl_mlo_info(struct seq_file *s, u8 *uwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	seq_printf(s, "\t\n");
++	seq_printf(s, "MldAddr: %02x:%02x:%02x:%02x:%02x:%02x(D0[B0~15], D1[B0~31])\n",
++		uwtbl[4], uwtbl[5], uwtbl[6], uwtbl[7], uwtbl[0], uwtbl[1]);
++
++	/* UMAC WTBL DW 0 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "UWTBL DW 0\n");
++	addr = (u32 *)&(uwtbl[WF_UWTBL_OWN_MLD_ID_DW*4]);
++	dw_value = *addr;
++
++	seq_printf(s, "\t%s:%u\n", "OMLD_ID",
++		(dw_value & WF_UWTBL_OWN_MLD_ID_MASK) >> WF_UWTBL_OWN_MLD_ID_SHIFT);
++
++	/* UMAC WTBL DW 9 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "UWTBL DW 9\n");
++	addr = (u32 *)&(uwtbl[WF_UWTBL_RELATED_IDX0_DW*4]);
++	dw_value = *addr;
++
++	while (WTBL_UMAC_DW9[i].name) {
++
++		if (WTBL_UMAC_DW9[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_UMAC_DW9[i].name,
++				(dw_value & WTBL_UMAC_DW9[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_UMAC_DW9[i].name,
++				 (dw_value & WTBL_UMAC_DW9[i].mask) >>
++					WTBL_UMAC_DW9[i].shift);
++		i++;
++	}
++}
++
++static bool
++is_wtbl_bigtk_exist(u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++
++	addr = (u32 *)&(lwtbl[WF_LWTBL_MUAR_DW*4]);
++	dw_value = *addr;
++	if (((dw_value & WF_LWTBL_MUAR_MASK) >> WF_LWTBL_MUAR_SHIFT) ==
++					MUAR_INDEX_OWN_MAC_ADDR_BC_MC) {
++		addr = (u32 *)&(lwtbl[WF_LWTBL_CIPHER_SUIT_BIGTK_DW*4]);
++		dw_value = *addr;
++		if (((dw_value & WF_LWTBL_CIPHER_SUIT_BIGTK_MASK) >>
++			WF_LWTBL_CIPHER_SUIT_BIGTK_SHIFT) != IGTK_CIPHER_SUIT_NONE)
++			return true;
++	}
++
++	return false;
++}
++
++static const struct berse_wtbl_parse WTBL_UMAC_DW2[] = {
++	{"PN0",		WTBL_PN0_MASK,		WTBL_PN0_OFFSET,	false},
++	{"PN1",		WTBL_PN1_MASK,		WTBL_PN1_OFFSET,	false},
++	{"PN2",		WTBL_PN2_MASK,		WTBL_PN2_OFFSET,	true},
++	{"PN3",		WTBL_PN3_MASK,		WTBL_PN3_OFFSET,	false},
++	{NULL,}
++};
++
++static const struct berse_wtbl_parse WTBL_UMAC_DW3[] = {
++	{"PN4",     WTBL_PN4_MASK,      WTBL_PN4_OFFSET,	false},
++	{"PN5",     WTBL_PN5_MASK,      WTBL_PN5_OFFSET,	true},
++	{"COM_SN",     WF_UWTBL_COM_SN_MASK,     WF_UWTBL_COM_SN_SHIFT,	true},
++	{NULL,}
++};
++
++static const struct berse_wtbl_parse WTBL_UMAC_DW4_BIPN[] = {
++	{"BIPN0",	WTBL_BIPN0_MASK,	WTBL_BIPN0_OFFSET,	false},
++	{"BIPN1",	WTBL_BIPN1_MASK,	WTBL_BIPN1_OFFSET,	false},
++	{"BIPN2",	WTBL_BIPN2_MASK,	WTBL_BIPN2_OFFSET,	true},
++	{"BIPN3",	WTBL_BIPN3_MASK,	WTBL_BIPN3_OFFSET,	false},
++	{NULL,}
++};
++
++static const struct berse_wtbl_parse WTBL_UMAC_DW5_BIPN[] = {
++	{"BIPN4",	WTBL_BIPN4_MASK,	WTBL_BIPN4_OFFSET,	false},
++	{"BIPN5",	WTBL_BIPN5_MASK,	WTBL_BIPN5_OFFSET,	true},
++	{NULL,}
++};
++
++static void parse_fmac_uwtbl_pn(struct seq_file *s, u8 *uwtbl, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u16 i = 0;
++
++	seq_printf(s, "\t\n");
++	seq_printf(s, "UWTBL PN\n");
++
++	/* UMAC WTBL DW 2/3 */
++	addr = (u32 *)&(uwtbl[WF_UWTBL_PN_31_0__DW*4]);
++	dw_value = *addr;
++
++	while (WTBL_UMAC_DW2[i].name) {
++		seq_printf(s, "\t%s:%u\n", WTBL_UMAC_DW2[i].name,
++			(dw_value & WTBL_UMAC_DW2[i].mask) >>
++				WTBL_UMAC_DW2[i].shift);
++		i++;
++	}
++
++	i = 0;
++	addr = (u32 *)&(uwtbl[WF_UWTBL_PN_47_32__DW*4]);
++	dw_value = *addr;
++
++	while (WTBL_UMAC_DW3[i].name) {
++		seq_printf(s, "\t%s:%u\n", WTBL_UMAC_DW3[i].name,
++			 (dw_value & WTBL_UMAC_DW3[i].mask) >>
++			WTBL_UMAC_DW3[i].shift);
++		i++;
++	}
++
++
++	/* UMAC WTBL DW 4/5 for BIGTK */
++	if (is_wtbl_bigtk_exist(lwtbl) == true) {
++		i = 0;
++		addr = (u32 *)&(uwtbl[WF_UWTBL_RX_BIPN_31_0__DW*4]);
++		dw_value = *addr;
++
++		while (WTBL_UMAC_DW4_BIPN[i].name) {
++			seq_printf(s, "\t%s:%u\n", WTBL_UMAC_DW4_BIPN[i].name,
++				(dw_value & WTBL_UMAC_DW4_BIPN[i].mask) >>
++					WTBL_UMAC_DW4_BIPN[i].shift);
++			i++;
++		}
++
++		i = 0;
++		addr = (u32 *)&(uwtbl[WF_UWTBL_RX_BIPN_47_32__DW*4]);
++		dw_value = *addr;
++
++		while (WTBL_UMAC_DW5_BIPN[i].name) {
++			seq_printf(s, "\t%s:%u\n", WTBL_UMAC_DW5_BIPN[i].name,
++				(dw_value & WTBL_UMAC_DW5_BIPN[i].mask) >>
++				WTBL_UMAC_DW5_BIPN[i].shift);
++			i++;
++		}
++	}
++}
++
++static void parse_fmac_uwtbl_sn(struct seq_file *s, u8 *uwtbl)
++{
++	u32 *addr = 0;
++	u32 u2SN = 0;
++
++	/* UMAC WTBL DW SN part */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "UWTBL SN\n");
++
++	addr = (u32 *)&(uwtbl[WF_UWTBL_TID0_SN_DW*4]);
++	u2SN = ((*addr) & WF_UWTBL_TID0_SN_MASK) >> WF_UWTBL_TID0_SN_SHIFT;
++	seq_printf(s, "\t%s:%u\n", "TID0_AC0_SN", u2SN);
++
++	addr = (u32 *)&(uwtbl[WF_UWTBL_TID1_SN_DW*4]);
++	u2SN = ((*addr) & WF_UWTBL_TID1_SN_MASK) >> WF_UWTBL_TID1_SN_SHIFT;
++	seq_printf(s, "\t%s:%u\n", "TID1_AC1_SN", u2SN);
++
++	addr = (u32 *)&(uwtbl[WF_UWTBL_TID2_SN_7_0__DW*4]);
++	u2SN = ((*addr) & WF_UWTBL_TID2_SN_7_0__MASK) >>
++				WF_UWTBL_TID2_SN_7_0__SHIFT;
++	addr = (u32 *)&(uwtbl[WF_UWTBL_TID2_SN_11_8__DW*4]);
++	u2SN |= (((*addr) & WF_UWTBL_TID2_SN_11_8__MASK) >>
++			WF_UWTBL_TID2_SN_11_8__SHIFT) << 8;
++	seq_printf(s, "\t%s:%u\n", "TID2_AC2_SN", u2SN);
++
++	addr = (u32 *)&(uwtbl[WF_UWTBL_TID3_SN_DW*4]);
++	u2SN = ((*addr) & WF_UWTBL_TID3_SN_MASK) >> WF_UWTBL_TID3_SN_SHIFT;
++	seq_printf(s, "\t%s:%u\n", "TID3_AC3_SN", u2SN);
++
++	addr = (u32 *)&(uwtbl[WF_UWTBL_TID4_SN_DW*4]);
++	u2SN = ((*addr) & WF_UWTBL_TID4_SN_MASK) >> WF_UWTBL_TID4_SN_SHIFT;
++	seq_printf(s, "\t%s:%u\n", "TID4_SN", u2SN);
++
++	addr = (u32 *)&(uwtbl[WF_UWTBL_TID5_SN_3_0__DW*4]);
++	u2SN = ((*addr) & WF_UWTBL_TID5_SN_3_0__MASK) >>
++				WF_UWTBL_TID5_SN_3_0__SHIFT;
++	addr = (u32 *)&(uwtbl[WF_UWTBL_TID5_SN_11_4__DW*4]);
++	u2SN |= (((*addr) & WF_UWTBL_TID5_SN_11_4__MASK) >>
++				WF_UWTBL_TID5_SN_11_4__SHIFT) << 4;
++	seq_printf(s, "\t%s:%u\n", "TID5_SN", u2SN);
++
++	addr = (u32 *)&(uwtbl[WF_UWTBL_TID6_SN_DW*4]);
++	u2SN = ((*addr) & WF_UWTBL_TID6_SN_MASK) >> WF_UWTBL_TID6_SN_SHIFT;
++	seq_printf(s, "\t%s:%u\n", "TID6_SN", u2SN);
++
++	addr = (u32 *)&(uwtbl[WF_UWTBL_TID7_SN_DW*4]);
++	u2SN = ((*addr) & WF_UWTBL_TID7_SN_MASK) >> WF_UWTBL_TID7_SN_SHIFT;
++	seq_printf(s, "\t%s:%u\n", "TID7_SN", u2SN);
++
++	addr = (u32 *)&(uwtbl[WF_UWTBL_COM_SN_DW*4]);
++	u2SN = ((*addr) & WF_UWTBL_COM_SN_MASK) >> WF_UWTBL_COM_SN_SHIFT;
++	seq_printf(s, "\t%s:%u\n", "COM_SN", u2SN);
++}
++
++static void dump_key_table(
++	struct seq_file *s,
++	uint16_t keyloc0,
++	uint16_t keyloc1,
++	uint16_t keyloc2
++)
++{
++#define ONE_KEY_ENTRY_LEN_IN_DW                8
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	u8 keytbl[ONE_KEY_ENTRY_LEN_IN_DW*4] = {0};
++	uint16_t x;
++
++	seq_printf(s, "\t\n");
++	seq_printf(s, "\t%s:%d\n", "keyloc0", keyloc0);
++	if (keyloc0 != INVALID_KEY_ENTRY) {
++
++		/* Don't swap below two lines, halWtblReadRaw will
++		* write new value WF_WTBLON_TOP_WDUCR_ADDR
++		*/
++		mt7996_wtbl_read_raw(dev, keyloc0,
++			WTBL_TYPE_KEY, 0, ONE_KEY_ENTRY_LEN_IN_DW, keytbl);
++		seq_printf(s, "\t\tKEY WTBL Addr: group:0x%x=0x%x addr: 0x%lx\n",
++			MT_DBG_UWTBL_TOP_WDUCR_ADDR,
++			mt76_rr(dev, MT_DBG_UWTBL_TOP_WDUCR_ADDR),
++			KEYTBL_IDX2BASE(keyloc0, 0));
++		for (x = 0; x < ONE_KEY_ENTRY_LEN_IN_DW; x++) {
++			seq_printf(s, "\t\tDW%02d: %02x %02x %02x %02x\n",
++				x,
++				keytbl[x * 4 + 3],
++				keytbl[x * 4 + 2],
++				keytbl[x * 4 + 1],
++				keytbl[x * 4]);
++		}
++	}
++
++	seq_printf(s, "\t%s:%d\n", "keyloc1", keyloc1);
++	if (keyloc1 != INVALID_KEY_ENTRY) {
++		/* Don't swap below two lines, halWtblReadRaw will
++		* write new value WF_WTBLON_TOP_WDUCR_ADDR
++		*/
++		mt7996_wtbl_read_raw(dev, keyloc1,
++			WTBL_TYPE_KEY, 0, ONE_KEY_ENTRY_LEN_IN_DW, keytbl);
++		seq_printf(s, "\t\tKEY WTBL Addr: group:0x%x=0x%x addr: 0x%lx\n",
++			MT_DBG_UWTBL_TOP_WDUCR_ADDR,
++			mt76_rr(dev, MT_DBG_UWTBL_TOP_WDUCR_ADDR),
++			KEYTBL_IDX2BASE(keyloc1, 0));
++		for (x = 0; x < ONE_KEY_ENTRY_LEN_IN_DW; x++) {
++			seq_printf(s, "\t\tDW%02d: %02x %02x %02x %02x\n",
++				x,
++				keytbl[x * 4 + 3],
++				keytbl[x * 4 + 2],
++				keytbl[x * 4 + 1],
++				keytbl[x * 4]);
++		}
++	}
++
++	seq_printf(s, "\t%s:%d\n", "keyloc2", keyloc2);
++	if (keyloc2 != INVALID_KEY_ENTRY) {
++		/* Don't swap below two lines, halWtblReadRaw will
++		* write new value WF_WTBLON_TOP_WDUCR_ADDR
++		*/
++		mt7996_wtbl_read_raw(dev, keyloc2,
++			WTBL_TYPE_KEY, 0, ONE_KEY_ENTRY_LEN_IN_DW, keytbl);
++		seq_printf(s, "\t\tKEY WTBL Addr: group:0x%x=0x%x addr: 0x%lx\n",
++			MT_DBG_UWTBL_TOP_WDUCR_ADDR,
++			mt76_rr(dev, MT_DBG_UWTBL_TOP_WDUCR_ADDR),
++			KEYTBL_IDX2BASE(keyloc2, 0));
++		for (x = 0; x < ONE_KEY_ENTRY_LEN_IN_DW; x++) {
++			seq_printf(s, "\t\tDW%02d: %02x %02x %02x %02x\n",
++				x,
++				keytbl[x * 4 + 3],
++				keytbl[x * 4 + 2],
++				keytbl[x * 4 + 1],
++				keytbl[x * 4]);
++		}
++	}
++}
++
++static void parse_fmac_uwtbl_key_info(struct seq_file *s, u8 *uwtbl, u8 *lwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	uint16_t keyloc0 = INVALID_KEY_ENTRY;
++	uint16_t keyloc1 = INVALID_KEY_ENTRY;
++	uint16_t keyloc2 = INVALID_KEY_ENTRY;
++
++	/* UMAC WTBL DW 7 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "UWTBL key info\n");
++
++	addr = (u32 *)&(uwtbl[WF_UWTBL_KEY_LOC0_DW*4]);
++	dw_value = *addr;
++	keyloc0 = (dw_value & WF_UWTBL_KEY_LOC0_MASK) >> WF_UWTBL_KEY_LOC0_SHIFT;
++	keyloc1 = (dw_value & WF_UWTBL_KEY_LOC1_MASK) >> WF_UWTBL_KEY_LOC1_SHIFT;
++
++	seq_printf(s, "\t%s:%u/%u\n", "Key Loc 0/1", keyloc0, keyloc1);
++
++	/* UMAC WTBL DW 6 for BIGTK */
++	if (is_wtbl_bigtk_exist(lwtbl) == true) {
++		addr = (u32 *)&(uwtbl[WF_UWTBL_KEY_LOC2_DW*4]);
++		dw_value = *addr;
++		keyloc2 = (dw_value & WF_UWTBL_KEY_LOC2_MASK) >>
++			WF_UWTBL_KEY_LOC2_SHIFT;
++		seq_printf(s, "\t%s:%u\n", "Key Loc 2", keyloc2);
++	}
++
++	/* Parse KEY link */
++	dump_key_table(s, keyloc0, keyloc1, keyloc2);
++}
++
++static const struct berse_wtbl_parse WTBL_UMAC_DW8[] = {
++	{"UWTBL_WMM_Q",		WF_UWTBL_WMM_Q_MASK,		WF_UWTBL_WMM_Q_SHIFT,	false},
++	{"UWTBL_QOS",		WF_UWTBL_QOS_MASK,		NO_SHIFT_DEFINE,	false},
++	{"UWTBL_HT_VHT_HE",	WF_UWTBL_HT_MASK,		NO_SHIFT_DEFINE,	false},
++	{"UWTBL_HDRT_MODE",	WF_UWTBL_HDRT_MODE_MASK,	NO_SHIFT_DEFINE,	true},
++	{NULL,}
++};
++
++static void parse_fmac_uwtbl_msdu_info(struct seq_file *s, u8 *uwtbl)
++{
++	u32 *addr = 0;
++	u32 dw_value = 0;
++	u32 amsdu_len = 0;
++	u16 i = 0;
++
++	/* UMAC WTBL DW 8 */
++	seq_printf(s, "\t\n");
++	seq_printf(s, "UWTBL DW8\n");
++
++	addr = (u32 *)&(uwtbl[WF_UWTBL_AMSDU_CFG_DW*4]);
++	dw_value = *addr;
++
++	while (WTBL_UMAC_DW8[i].name) {
++
++		if (WTBL_UMAC_DW8[i].shift == NO_SHIFT_DEFINE)
++			seq_printf(s, "\t%s:%d\n", WTBL_UMAC_DW8[i].name,
++				(dw_value & WTBL_UMAC_DW8[i].mask) ? 1 : 0);
++		else
++			seq_printf(s, "\t%s:%u\n", WTBL_UMAC_DW8[i].name,
++				(dw_value & WTBL_UMAC_DW8[i].mask) >>
++					WTBL_UMAC_DW8[i].shift);
++		i++;
++	}
++
++	/* UMAC WTBL DW 8 - SEC_ADDR_MODE */
++	addr = (u32 *)&(uwtbl[WF_UWTBL_SEC_ADDR_MODE_DW*4]);
++	dw_value = *addr;
++	seq_printf(s, "\t%s:%lu\n", "SEC_ADDR_MODE",
++		(dw_value & WTBL_SEC_ADDR_MODE_MASK) >> WTBL_SEC_ADDR_MODE_OFFSET);
++
++	/* UMAC WTBL DW 8 - AMSDU_CFG */
++	seq_printf(s, "\t%s:%d\n", "HW AMSDU Enable",
++				(dw_value & WTBL_AMSDU_EN_MASK) ? 1 : 0);
++
++	amsdu_len = (dw_value & WTBL_AMSDU_LEN_MASK) >> WTBL_AMSDU_LEN_OFFSET;
++	if (amsdu_len == 0)
++		seq_printf(s, "\t%s:invalid (WTBL value=0x%x)\n", "HW AMSDU Len",
++			amsdu_len);
++	else if (amsdu_len == 1)
++		seq_printf(s, "\t%s:%d~%d (WTBL value=0x%x)\n", "HW AMSDU Len",
++			1,
++			255,
++			amsdu_len);
++	else if (amsdu_len == 2)
++		seq_printf(s, "\t%s:%d~%d (WTBL value=0x%x)\n", "HW AMSDU Len",
++			256,
++			511,
++			amsdu_len);
++	else if (amsdu_len == 3)
++		seq_printf(s, "\t%s:%d~%d (WTBL value=0x%x)\n", "HW AMSDU Len",
++			512,
++			767,
++			amsdu_len);
++	else
++		seq_printf(s, "\t%s:%d~%d (WTBL value=0x%x)\n", "HW AMSDU Len",
++			256 * (amsdu_len - 1),
++			256 * (amsdu_len - 1) + 255,
++			amsdu_len);
++
++	seq_printf(s, "\t%s:%lu (WTBL value=0x%lx)\n", "HW AMSDU Num",
++		((dw_value & WTBL_AMSDU_NUM_MASK) >> WTBL_AMSDU_NUM_OFFSET) + 1,
++		(dw_value & WTBL_AMSDU_NUM_MASK) >> WTBL_AMSDU_NUM_OFFSET);
++}
++
++static int mt7996_wtbl_read(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	u8 lwtbl[LWTBL_LEN_IN_DW * 4] = {0};
++	u8 uwtbl[UWTBL_LEN_IN_DW * 4] = {0};
++	int x;
++
++	mt7996_wtbl_read_raw(dev, dev->wlan_idx, WTBL_TYPE_LMAC, 0,
++				 LWTBL_LEN_IN_DW, lwtbl);
++	seq_printf(s, "Dump WTBL info of WLAN_IDX:%d\n", dev->wlan_idx);
++	seq_printf(s, "LMAC WTBL Addr: group:0x%x=0x%x addr: 0x%lx\n",
++		   MT_DBG_WTBLON_TOP_WDUCR_ADDR,
++		   mt76_rr(dev, MT_DBG_WTBLON_TOP_WDUCR_ADDR),
++		   LWTBL_IDX2BASE(dev->wlan_idx, 0));
++	for (x = 0; x < LWTBL_LEN_IN_DW; x++) {
++		seq_printf(s, "DW%02d: %02x %02x %02x %02x\n",
++			   x,
++			   lwtbl[x * 4 + 3],
++			   lwtbl[x * 4 + 2],
++			   lwtbl[x * 4 + 1],
++			   lwtbl[x * 4]);
++	}
++
++	/* Parse LWTBL */
++	parse_fmac_lwtbl_dw0_1(s, lwtbl);
++	parse_fmac_lwtbl_dw2(s, lwtbl);
++	parse_fmac_lwtbl_dw3(s, lwtbl);
++	parse_fmac_lwtbl_dw4(s, lwtbl);
++	parse_fmac_lwtbl_dw5(s, lwtbl);
++	parse_fmac_lwtbl_dw6(s, lwtbl);
++	parse_fmac_lwtbl_dw7(s, lwtbl);
++	parse_fmac_lwtbl_dw8(s, lwtbl);
++	parse_fmac_lwtbl_dw9(s, lwtbl);
++	parse_fmac_lwtbl_dw10(s, lwtbl);
++	parse_fmac_lwtbl_dw11(s, lwtbl);
++	parse_fmac_lwtbl_dw12(s, lwtbl);
++	parse_fmac_lwtbl_dw13(s, lwtbl);
++	parse_fmac_lwtbl_dw14(s, lwtbl);
++	parse_fmac_lwtbl_mlo_info(s, lwtbl);
++	parse_fmac_lwtbl_dw31(s, lwtbl);
++	parse_fmac_lwtbl_dw32(s, lwtbl);
++	parse_fmac_lwtbl_rx_stats(s, lwtbl);
++
++	mt7996_wtbl_read_raw(dev, dev->wlan_idx, WTBL_TYPE_UMAC, 0,
++				 UWTBL_LEN_IN_DW, uwtbl);
++	seq_printf(s, "Dump WTBL info of WLAN_IDX:%d\n", dev->wlan_idx);
++	seq_printf(s, "UMAC WTBL Addr: group:0x%x=0x%x addr: 0x%lx\n",
++		   MT_DBG_UWTBL_TOP_WDUCR_ADDR,
++		   mt76_rr(dev, MT_DBG_UWTBL_TOP_WDUCR_ADDR),
++		   UWTBL_IDX2BASE(dev->wlan_idx, 0));
++	for (x = 0; x < UWTBL_LEN_IN_DW; x++) {
++		seq_printf(s, "DW%02d: %02x %02x %02x %02x\n",
++			   x,
++			   uwtbl[x * 4 + 3],
++			   uwtbl[x * 4 + 2],
++			   uwtbl[x * 4 + 1],
++			   uwtbl[x * 4]);
++	}
++
++	/* Parse UWTBL */
++	parse_fmac_uwtbl_mlo_info(s, uwtbl);
++	parse_fmac_uwtbl_pn(s, uwtbl, lwtbl);
++	parse_fmac_uwtbl_sn(s, uwtbl);
++	parse_fmac_uwtbl_key_info(s, uwtbl, lwtbl);
++	parse_fmac_uwtbl_msdu_info(s, uwtbl);
++
++	return 0;
++}
++
++static int mt7996_sta_info(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	u8 lwtbl[LWTBL_LEN_IN_DW*4] = {0};
++	u16 i = 0;
++
++	for (i=0; i < mt7996_wtbl_size(dev); i++) {
++		mt7996_wtbl_read_raw(dev, i, WTBL_TYPE_LMAC, 0,
++				     LWTBL_LEN_IN_DW, lwtbl);
++
++		if (lwtbl[4] || lwtbl[5] || lwtbl[6] || lwtbl[7] || lwtbl[0] || lwtbl[1]) {
++			u32 *addr, dw_value;
++
++			seq_printf(s, "wcid:%d\tAddr: %02x:%02x:%02x:%02x:%02x:%02x",
++					i, lwtbl[4], lwtbl[5], lwtbl[6], lwtbl[7], lwtbl[0], lwtbl[1]);
++
++			addr = (u32 *)&(lwtbl[WTBL_GROUP_TRX_CAP_DW_2*4]);
++			dw_value = *addr;
++			seq_printf(s, "\t%s:%u", WTBL_LMAC_DW2[0].name,
++					(dw_value & WTBL_LMAC_DW2[0].mask) >> WTBL_LMAC_DW2[0].shift);
++
++			addr = (u32 *)&(lwtbl[WTBL_GROUP_TRX_CAP_DW_5*4]);
++			dw_value = *addr;
++			seq_printf(s, "\tPSM:%u\n", !!(dw_value & WF_LWTBL_PSM_MASK));
++		}
++	}
++
++	return 0;
++}
++
++static int mt7996_token_read(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	int msdu_id;
++	struct mt76_txwi_cache *txwi;
++
++	seq_printf(s, "Token from host:\n");
++	spin_lock_bh(&dev->mt76.token_lock);
++	idr_for_each_entry(&dev->mt76.token, txwi, msdu_id) {
++		seq_printf(s, "%4d (pending time %u ms)\n", msdu_id,
++			   jiffies_to_msecs(jiffies - txwi->jiffies));
++	}
++	spin_unlock_bh(&dev->mt76.token_lock);
++	seq_printf(s, "\n");
++
++	return 0;
++}
++
++int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
++{
++	struct mt7996_dev *dev = phy->dev;
++	u32 device_id = (dev->mt76.rev) >> 16;
++	int i = 0;
++	static const struct mt7996_dbg_reg_desc dbg_reg_s[] = {
++		{ 0x7990, mt7996_dbg_offs },
++		{ 0x7992, mt7992_dbg_offs },
++	};
++
++	for (i = 0; i < ARRAY_SIZE(dbg_reg_s); i++) {
++		if (device_id == dbg_reg_s[i].id) {
++			dev->dbg_reg = &dbg_reg_s[i];
++			break;
++		}
++	}
++
++	if (is_mt7996(&dev->mt76)) {
++		WTBL_LMAC_DW2 = WTBL_LMAC_DW2_7996;
++		WTBL_LMAC_DW5 = WTBL_LMAC_DW5_7996;
++		WTBL_LMAC_DW9 = WTBL_LMAC_DW9_7996;
++	} else {
++		WTBL_LMAC_DW2 = WTBL_LMAC_DW2_7992;
++		WTBL_LMAC_DW5 = WTBL_LMAC_DW5_7992;
++		WTBL_LMAC_DW9 = WTBL_LMAC_DW9_7992;
++	}
++
++	/* agg */
++	debugfs_create_devm_seqfile(dev->mt76.dev, "agg_info0", dir,
++				    mt7996_agginfo_read_band0);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "agg_info1", dir,
++				    mt7996_agginfo_read_band1);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "agg_info2", dir,
++				    mt7996_agginfo_read_band2);
++	/* amsdu */
++	debugfs_create_devm_seqfile(dev->mt76.dev, "amsdu_info", dir,
++				    mt7996_amsdu_result_read);
++
++	debugfs_create_file("fw_debug_module", 0600, dir, dev,
++			    &fops_fw_debug_module);
++	debugfs_create_file("fw_debug_level", 0600, dir, dev,
++			    &fops_fw_debug_level);
++	debugfs_create_file("fw_wa_query", 0600, dir, dev, &fops_wa_query);
++	debugfs_create_file("fw_wa_set", 0600, dir, dev, &fops_wa_set);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "fw_version", dir,
++				    mt7996_dump_version);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "fw_wa_info", dir,
++				    mt7996_fw_wa_info_read);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "fw_wm_info", dir,
++				    mt7996_fw_wm_info_read);
++
++	debugfs_create_devm_seqfile(dev->mt76.dev, "mib_info0", dir,
++				    mt7996_mibinfo_band0);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "mib_info1", dir,
++				    mt7996_mibinfo_band1);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "mib_info2", dir,
++				    mt7996_mibinfo_band2);
++
++	debugfs_create_devm_seqfile(dev->mt76.dev, "sta_info", dir,
++				    mt7996_sta_info);
++
++	debugfs_create_devm_seqfile(dev->mt76.dev, "tr_info", dir,
++				    mt7996_trinfo_read);
++
++	debugfs_create_devm_seqfile(dev->mt76.dev, "wtbl_info", dir,
++				    mt7996_wtbl_read);
++
++	debugfs_create_devm_seqfile(dev->mt76.dev, "token", dir, mt7996_token_read);
++
++	debugfs_create_u8("sku_disable", 0600, dir, &dev->dbg.sku_disable);
++
++	return 0;
++}
++
++#endif
+diff --git a/mt7996/mtk_mcu.c b/mt7996/mtk_mcu.c
+new file mode 100644
+index 000000000..e88701667
+--- /dev/null
++++ b/mt7996/mtk_mcu.c
+@@ -0,0 +1,18 @@
++// SPDX-License-Identifier: ISC
++/*
++ * Copyright (C) 2023 MediaTek Inc.
++ */
++
++#include <linux/firmware.h>
++#include <linux/fs.h>
++#include "mt7996.h"
++#include "mcu.h"
++#include "mac.h"
++#include "mtk_mcu.h"
++
++#ifdef CONFIG_MTK_DEBUG
++
++
++
++
++#endif
+diff --git a/mt7996/mtk_mcu.h b/mt7996/mtk_mcu.h
+new file mode 100644
+index 000000000..e741aa278
+--- /dev/null
++++ b/mt7996/mtk_mcu.h
+@@ -0,0 +1,16 @@
++/* SPDX-License-Identifier: ISC */
++/*
++ * Copyright (C) 2023 MediaTek Inc.
++ */
++
++#ifndef __MT7996_MTK_MCU_H
++#define __MT7996_MTK_MCU_H
++
++#include "../mt76_connac_mcu.h"
++
++#ifdef CONFIG_MTK_DEBUG
++
++
++#endif
++
++#endif
+diff --git a/tools/fwlog.c b/tools/fwlog.c
+index e5d4a1051..3c6a61d71 100644
+--- a/tools/fwlog.c
++++ b/tools/fwlog.c
+@@ -26,7 +26,7 @@ static const char *debugfs_path(const char *phyname, const char *file)
+ 	return path;
+ }
+ 
+-static int mt76_set_fwlog_en(const char *phyname, bool en)
++static int mt76_set_fwlog_en(const char *phyname, bool en, char *val)
+ {
+ 	FILE *f = fopen(debugfs_path(phyname, "fw_debug_bin"), "w");
+ 
+@@ -35,7 +35,13 @@ static int mt76_set_fwlog_en(const char *phyname, bool en)
+ 		return 1;
+ 	}
+ 
+-	fprintf(f, "7");
++	if (en && val)
++		fprintf(f, "%s", val);
++	else if (en)
++		fprintf(f, "7");
++	else
++		fprintf(f, "0");
++
+ 	fclose(f);
+ 
+ 	return 0;
+@@ -76,6 +82,7 @@ static void handle_signal(int sig)
+ 
+ int mt76_fwlog(const char *phyname, int argc, char **argv)
+ {
++#define BUF_SIZE 1504
+ 	struct sockaddr_in local = {
+ 		.sin_family = AF_INET,
+ 		.sin_addr.s_addr = INADDR_ANY,
+@@ -84,9 +91,9 @@ int mt76_fwlog(const char *phyname, int argc, char **argv)
+ 		.sin_family = AF_INET,
+ 		.sin_port = htons(55688),
+ 	};
+-	char buf[1504];
++	char *buf = calloc(BUF_SIZE, sizeof(char));
+ 	int ret = 0;
+-	int yes = 1;
++	/* int yes = 1; */
+ 	int s, fd;
+ 
+ 	if (argc < 1) {
+@@ -105,13 +112,13 @@ int mt76_fwlog(const char *phyname, int argc, char **argv)
+ 		return 1;
+ 	}
+ 
+-	setsockopt(s, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes));
++	/* setsockopt(s, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes)); */
+ 	if (bind(s, (struct sockaddr *)&local, sizeof(local)) < 0) {
+ 		perror("bind");
+ 		return 1;
+ 	}
+ 
+-	if (mt76_set_fwlog_en(phyname, true))
++	if (mt76_set_fwlog_en(phyname, true, argv[1]))
+ 		return 1;
+ 
+ 	fd = open(debugfs_path(phyname, "fwlog_data"), O_RDONLY);
+@@ -145,8 +152,8 @@ int mt76_fwlog(const char *phyname, int argc, char **argv)
+ 		if (!r)
+ 			continue;
+ 
+-		if (len > sizeof(buf)) {
+-			fprintf(stderr, "Length error: %d > %d\n", len, (int)sizeof(buf));
++		if (len > BUF_SIZE) {
++			fprintf(stderr, "Length error: %d > %d\n", len, BUF_SIZE);
+ 			ret = 1;
+ 			break;
+ 		}
+@@ -171,7 +178,7 @@ int mt76_fwlog(const char *phyname, int argc, char **argv)
+ 	close(fd);
+ 
+ out:
+-	mt76_set_fwlog_en(phyname, false);
++	mt76_set_fwlog_en(phyname, false, NULL);
+ 
+ 	return ret;
+ }
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0025-mtk-wifi-mt76-mt7996-support-record-muru-algo-log-wh.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0025-mtk-wifi-mt76-mt7996-support-record-muru-algo-log-wh.patch
new file mode 100644
index 0000000..b0aedb7
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0025-mtk-wifi-mt76-mt7996-support-record-muru-algo-log-wh.patch
@@ -0,0 +1,146 @@
+From a4e0245481409a583d0bd3710915e24a33bb7b2d Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Tue, 28 Nov 2023 16:01:33 +0800
+Subject: [PATCH 025/120] mtk: wifi: mt76: mt7996: support record muru algo log
+ when record fw log
+
+Support record muru algorithm debug log in firmware when we use
+chihuahua tool to record fw log. This can help us to check some key
+point of muru algorithm result, like bsrp status, airtime busy status,
+ru candidate...
+Corresponding to Logan driver, it is the same as execute the iwpriv
+command: iwpriv rax0 set muruDbgInfo=[category]-1
+
+Disable muru debug log when we stop record fwlog. Without this commit,
+if we run $ echo 2 > fw_debug_wm after recording fwlog, it will print
+out too many fw debug log.
+
+CR-Id: WCNCR00261410
+Change-Id: If6944ce9698558a96d252017f2de8819d976e24f
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+---
+ mt7996/debugfs.c | 35 +++++++++++++++++++++++++++++++++++
+ mt7996/mt7996.h  |  1 +
+ mt7996/mtk_mcu.c | 21 +++++++++++++++++++++
+ mt7996/mtk_mcu.h |  3 +++
+ 4 files changed, 60 insertions(+)
+
+diff --git a/mt7996/debugfs.c b/mt7996/debugfs.c
+index 1f5c5b19c..2e9c7ea16 100644
+--- a/mt7996/debugfs.c
++++ b/mt7996/debugfs.c
+@@ -428,6 +428,36 @@ remove_buf_file_cb(struct dentry *f)
+ 	return 0;
+ }
+ 
++static int
++mt7996_fw_debug_muru_set(void *data)
++{
++	struct mt7996_dev *dev = data;
++	enum {
++		DEBUG_BSRP_STATUS = 256,
++		DEBUG_TX_DATA_BYTE_CONUT,
++		DEBUG_RX_DATA_BYTE_CONUT,
++		DEBUG_RX_TOTAL_BYTE_CONUT,
++		DEBUG_INVALID_TID_BSR,
++		DEBUG_UL_LONG_TERM_PPDU_TYPE,
++		DEBUG_DL_LONG_TERM_PPDU_TYPE,
++		DEBUG_PPDU_CLASS_TRIG_ONOFF,
++		DEBUG_AIRTIME_BUSY_STATUS,
++		DEBUG_UL_OFDMA_MIMO_STATUS,
++		DEBUG_RU_CANDIDATE,
++		DEBUG_MEC_UPDATE_AMSDU,
++	} debug;
++	int ret;
++
++	for (debug = DEBUG_BSRP_STATUS; debug <= DEBUG_MEC_UPDATE_AMSDU; debug++) {
++		ret = mt7996_mcu_muru_dbg_info(dev, debug,
++					       dev->fw_debug_bin & BIT(0));
++		if (ret)
++			return ret;
++	}
++
++	return 0;
++}
++
+ static int
+ mt7996_fw_debug_bin_set(void *data, u64 val)
+ {
+@@ -436,6 +466,7 @@ mt7996_fw_debug_bin_set(void *data, u64 val)
+ 		.remove_buf_file = remove_buf_file_cb,
+ 	};
+ 	struct mt7996_dev *dev = data;
++	int ret;
+ 
+ 	if (!dev->relay_fwlog) {
+ 		dev->relay_fwlog = relay_open("fwlog_data", dev->debugfs_dir,
+@@ -448,6 +479,10 @@ mt7996_fw_debug_bin_set(void *data, u64 val)
+ 
+ 	relay_reset(dev->relay_fwlog);
+ 
++	ret = mt7996_fw_debug_muru_set(dev);
++	if (ret)
++		return ret;
++
+ 	return mt7996_fw_debug_wm_set(dev, dev->fw_debug_wm);
+ }
+ 
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 0396c6306..4184d065f 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -678,6 +678,7 @@ u32 mt7996_wed_init_buf(void *ptr, dma_addr_t phys, int token_id);
+ 
+ #ifdef CONFIG_MTK_DEBUG
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir);
++int mt7996_mcu_muru_dbg_info(struct mt7996_dev *dev, u16 item, u8 val);
+ #endif
+ 
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
+diff --git a/mt7996/mtk_mcu.c b/mt7996/mtk_mcu.c
+index e88701667..c16b25ab5 100644
+--- a/mt7996/mtk_mcu.c
++++ b/mt7996/mtk_mcu.c
+@@ -15,4 +15,25 @@
+ 
+ 
+ 
++int mt7996_mcu_muru_dbg_info(struct mt7996_dev *dev, u16 item, u8 val)
++{
++	struct {
++		u8 __rsv1[4];
++
++		__le16 tag;
++		__le16 len;
++
++		__le16 item;
++		u8 __rsv2[2];
++		__le32 value;
++	} __packed req = {
++		.tag = cpu_to_le16(UNI_CMD_MURU_DBG_INFO),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.item = cpu_to_le16(item),
++		.value = cpu_to_le32(val),
++	};
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(MURU), &req,
++				 sizeof(req), true);
++}
+ #endif
+diff --git a/mt7996/mtk_mcu.h b/mt7996/mtk_mcu.h
+index e741aa278..7f4d4e029 100644
+--- a/mt7996/mtk_mcu.h
++++ b/mt7996/mtk_mcu.h
+@@ -10,6 +10,9 @@
+ 
+ #ifdef CONFIG_MTK_DEBUG
+ 
++enum {
++	UNI_CMD_MURU_DBG_INFO = 0x18,
++};
+ 
+ #endif
+ 
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0026-mtk-wifi-mt76-mt7996-add-check-for-hostapd-config-he.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0026-mtk-wifi-mt76-mt7996-add-check-for-hostapd-config-he.patch
new file mode 100644
index 0000000..0dbdd25
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0026-mtk-wifi-mt76-mt7996-add-check-for-hostapd-config-he.patch
@@ -0,0 +1,66 @@
+From fa1760c46b5c71ea14ece6e2166cf4bc7f32b8b7 Mon Sep 17 00:00:00 2001
+From: "Allen.Ye" <allen.ye@mediatek.com>
+Date: Thu, 8 Jun 2023 17:32:33 +0800
+Subject: [PATCH 026/120] mtk: wifi: mt76: mt7996: add check for hostapd config
+ he_ldpc
+
+Add check for hostapd config he_ldpc.
+This capabilities is checked in mcu_beacon_check_caps in 7915.
+
+Add check for STA LDPC cap, if STA only have BCC we should not overwrite the phy_cap with config he_ldpc.
+
+CR-Id: WCNCR00259302
+Change-Id: I6d6f59df8897e3c00f2e0a1e3c6e5701e31c5e4b
+Change-Id: Ibe7e40ec1dbb40bd3f3d96741e9933ec00b50df0
+Signed-off-by: Allen.Ye <allen.ye@mediatek.com>
+---
+ mt7996/mcu.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 8c80bb231..e1c8cd725 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -1188,7 +1188,8 @@ int mt7996_mcu_add_rx_ba(struct mt7996_dev *dev,
+ }
+ 
+ static void
+-mt7996_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_sta *sta)
++mt7996_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
++		      struct ieee80211_sta *sta)
+ {
+ 	struct ieee80211_he_cap_elem *elem = &sta->deflink.he_cap.he_cap_elem;
+ 	struct ieee80211_he_mcs_nss_supp mcs_map;
+@@ -1208,6 +1209,11 @@ mt7996_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_sta *sta)
+ 		he->he_phy_cap[i] = elem->phy_cap_info[i];
+ 	}
+ 
++	if (vif->type == NL80211_IFTYPE_AP &&
++	    (elem->phy_cap_info[1] & IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD))
++		u8p_replace_bits(&he->he_phy_cap[1], vif->bss_conf.he_ldpc,
++				 IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD);
++
+ 	mcs_map = sta->deflink.he_cap.he_mcs_nss_supp;
+ 	switch (sta->deflink.bandwidth) {
+ 	case IEEE80211_STA_RX_BW_160:
+@@ -2113,7 +2119,7 @@ int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 	 * update sta_rec_he here.
+ 	 */
+ 	if (changed)
+-		mt7996_mcu_sta_he_tlv(skb, sta);
++		mt7996_mcu_sta_he_tlv(skb, vif, sta);
+ 
+ 	/* sta_rec_ra accommodates BW, NSS and only MCS range format
+ 	 * i.e 0-{7,8,9} for VHT.
+@@ -2201,7 +2207,7 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 		/* starec amsdu */
+ 		mt7996_mcu_sta_amsdu_tlv(dev, skb, vif, sta);
+ 		/* starec he */
+-		mt7996_mcu_sta_he_tlv(skb, sta);
++		mt7996_mcu_sta_he_tlv(skb, vif, sta);
+ 		/* starec he 6g*/
+ 		mt7996_mcu_sta_he_6g_tlv(skb, sta);
+ 		/* starec eht */
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0027-mtk-wifi-mt76-testmode-add-atenl-support-in-mt7996.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0027-mtk-wifi-mt76-testmode-add-atenl-support-in-mt7996.patch
new file mode 100644
index 0000000..ccae45d
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0027-mtk-wifi-mt76-testmode-add-atenl-support-in-mt7996.patch
@@ -0,0 +1,49 @@
+From 449d393ae006d4c0bd9fbb8921aea85b75e9240b Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Wed, 28 Dec 2022 22:24:25 +0800
+Subject: [PATCH 027/120] mtk: wifi: mt76: testmode: add atenl support in
+ mt7996
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ testmode.c | 3 ++-
+ testmode.h | 2 ++
+ 2 files changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/testmode.c b/testmode.c
+index ca4feccf3..37783160a 100644
+--- a/testmode.c
++++ b/testmode.c
+@@ -613,7 +613,8 @@ int mt76_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *msg,
+ 
+ 	if (dev->test_mtd.name &&
+ 	    (nla_put_string(msg, MT76_TM_ATTR_MTD_PART, dev->test_mtd.name) ||
+-	     nla_put_u32(msg, MT76_TM_ATTR_MTD_OFFSET, dev->test_mtd.offset)))
++	     nla_put_u32(msg, MT76_TM_ATTR_MTD_OFFSET, dev->test_mtd.offset) ||
++	     nla_put_u8(msg, MT76_TM_ATTR_BAND_IDX, phy->band_idx)))
+ 		goto out;
+ 
+ 	if (nla_put_u32(msg, MT76_TM_ATTR_TX_COUNT, td->tx_count) ||
+diff --git a/testmode.h b/testmode.h
+index 5e2792d81..a40cd74b4 100644
+--- a/testmode.h
++++ b/testmode.h
+@@ -17,6 +17,7 @@
+  *
+  * @MT76_TM_ATTR_MTD_PART: mtd partition used for eeprom data (string)
+  * @MT76_TM_ATTR_MTD_OFFSET: offset of eeprom data within the partition (u32)
++ * @MT76_TM_ATTR_BAND_IDX: band idx of the chip (u8)
+  *
+  * @MT76_TM_ATTR_TX_COUNT: configured number of frames to send when setting
+  *	state to MT76_TM_STATE_TX_FRAMES (u32)
+@@ -56,6 +57,7 @@ enum mt76_testmode_attr {
+ 
+ 	MT76_TM_ATTR_MTD_PART,
+ 	MT76_TM_ATTR_MTD_OFFSET,
++	MT76_TM_ATTR_BAND_IDX,
+ 
+ 	MT76_TM_ATTR_TX_COUNT,
+ 	MT76_TM_ATTR_TX_LENGTH,
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0028-mtk-wifi-mt76-testmode-add-basic-testmode-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0028-mtk-wifi-mt76-testmode-add-basic-testmode-support.patch
new file mode 100644
index 0000000..2323f24
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0028-mtk-wifi-mt76-testmode-add-basic-testmode-support.patch
@@ -0,0 +1,2360 @@
+From b63be5833a893a27a36837e1c67a17f1f62d7a7f Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Wed, 1 Mar 2023 11:59:16 +0800
+Subject: [PATCH 028/120] mtk: wifi: mt76: testmode: add basic testmode support
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Change-Id: I0e09e7f5bc0fb9aa4e4ec906a0f5f169bcc261cb
+
+Add testmode eeprom buffer mode support
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+
+Fix power & freq offset issue for iTest power cal & tx/rx verifcation
+1. Wait for fw to tx. Otherwise, iTest testing tool cannot get the
+accurate tx power.
+2. In crystal mode, freq offset is set in 6G band and forwarded to 5G
+and 2G band. Therefore, we should avoid reseting freq offset to 0 when
+6G interface is off.
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Change-Id: I849b11b4ccdecd2b7b525b29801c02b5207bbf91
+
+edcca return err in testmode; therefore, bypass it when we are in testmode idle state or testmode bf is on
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ eeprom.c          |   6 +-
+ mac80211.c        |   3 +-
+ mt76.h            |  36 +++
+ mt76_connac_mcu.h |   2 +
+ mt7996/Makefile   |   1 +
+ mt7996/eeprom.c   |  37 ++-
+ mt7996/eeprom.h   |   1 +
+ mt7996/init.c     |   8 +
+ mt7996/mac.c      |   3 +-
+ mt7996/main.c     |  26 ++
+ mt7996/mcu.c      |  59 +++-
+ mt7996/mcu.h      |  33 +++
+ mt7996/mt7996.h   |  28 +-
+ mt7996/testmode.c | 740 ++++++++++++++++++++++++++++++++++++++++++++++
+ mt7996/testmode.h | 299 +++++++++++++++++++
+ testmode.c        | 123 ++++++--
+ testmode.h        |  85 +++++-
+ tools/fields.c    | 102 ++++++-
+ 18 files changed, 1543 insertions(+), 49 deletions(-)
+ create mode 100644 mt7996/testmode.c
+ create mode 100644 mt7996/testmode.h
+
+diff --git a/eeprom.c b/eeprom.c
+index 0bc66cc19..a0047d791 100644
+--- a/eeprom.c
++++ b/eeprom.c
+@@ -94,8 +94,10 @@ int mt76_get_of_data_from_mtd(struct mt76_dev *dev, void *eep, int offset, int l
+ 	}
+ 
+ #ifdef CONFIG_NL80211_TESTMODE
+-	dev->test_mtd.name = devm_kstrdup(dev->dev, part, GFP_KERNEL);
+-	dev->test_mtd.offset = offset;
++	if (len == dev->eeprom.size) {
++		dev->test_mtd.name = devm_kstrdup(dev->dev, part, GFP_KERNEL);
++		dev->test_mtd.offset = offset;
++	}
+ #endif
+ 
+ out_put_node:
+diff --git a/mac80211.c b/mac80211.c
+index 7a61d3c19..94824355a 100644
+--- a/mac80211.c
++++ b/mac80211.c
+@@ -846,7 +846,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 8cf21f980..f2b1e0c23 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -695,14 +695,21 @@ 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);
++	void (*reset_rx_stats)(struct mt76_phy *phy);
++	void (*tx_stop)(struct mt76_phy *phy);
++	int (*set_eeprom)(struct mt76_phy *phy, u32 offset, u8 *val, u8 action);
+ };
+ 
++#define MT_TM_FW_RX_COUNT	BIT(0)
++
+ struct mt76_testmode_data {
+ 	enum mt76_testmode_state state;
+ 
+ 	u32 param_set[DIV_ROUND_UP(NUM_MT76_TM_ATTRS, 32)];
+ 	struct sk_buff *tx_skb;
+ 
++	u8 sku_en;
++
+ 	u32 tx_count;
+ 	u16 tx_mpdu_len;
+ 
+@@ -712,6 +719,7 @@ struct mt76_testmode_data {
+ 	u8 tx_rate_sgi;
+ 	u8 tx_rate_ldpc;
+ 	u8 tx_rate_stbc;
++	u16 tx_preamble_puncture;
+ 	u8 tx_ltf;
+ 
+ 	u8 tx_antenna_mask;
+@@ -721,6 +729,9 @@ struct mt76_testmode_data {
+ 	u32 tx_time;
+ 	u32 tx_ipg;
+ 
++	bool ibf;
++	bool ebf;
++
+ 	u32 freq_offset;
+ 
+ 	u8 tx_power[4];
+@@ -735,7 +746,16 @@ struct mt76_testmode_data {
+ 	struct {
+ 		u64 packets[__MT_RXQ_MAX];
+ 		u64 fcs_error[__MT_RXQ_MAX];
++		u64 len_mismatch;
+ 	} rx_stats;
++	u8 flag;
++
++	struct {
++		u8 type;
++		u8 enable;
++	} cfg;
++
++	u8 aid;
+ };
+ 
+ struct mt76_vif {
+@@ -1439,6 +1459,22 @@ int mt76_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *skb,
+ 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);
+ 
++static inline void
++mt76_testmode_param_set(struct mt76_testmode_data *td, u16 idx)
++{
++#ifdef CONFIG_NL80211_TESTMODE
++	td->param_set[idx / 32] |= BIT(idx % 32);
++#endif
++}
++
++static inline bool
++mt76_testmode_param_present(struct mt76_testmode_data *td, u16 idx)
++{
++#ifdef CONFIG_NL80211_TESTMODE
++	return td->param_set[idx / 32] & BIT(idx % 32);
++#endif
++}
++
+ static inline void mt76_testmode_reset(struct mt76_phy *phy, bool disable)
+ {
+ #ifdef CONFIG_NL80211_TESTMODE
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 70def0a3b..718552baf 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1267,12 +1267,14 @@ enum {
+ 	MCU_UNI_CMD_EFUSE_CTRL = 0x2d,
+ 	MCU_UNI_CMD_RA = 0x2f,
+ 	MCU_UNI_CMD_MURU = 0x31,
++	MCU_UNI_CMD_TESTMODE_RX_STAT = 0x32,
+ 	MCU_UNI_CMD_BF = 0x33,
+ 	MCU_UNI_CMD_CHANNEL_SWITCH = 0x34,
+ 	MCU_UNI_CMD_THERMAL = 0x35,
+ 	MCU_UNI_CMD_VOW = 0x37,
+ 	MCU_UNI_CMD_PP = 0x38,
+ 	MCU_UNI_CMD_FIXED_RATE_TABLE = 0x40,
++	MCU_UNI_CMD_TESTMODE_CTRL = 0x46,
+ 	MCU_UNI_CMD_RRO = 0x57,
+ 	MCU_UNI_CMD_OFFCH_SCAN_CTRL = 0x58,
+ 	MCU_UNI_CMD_PER_STA_INFO = 0x6d,
+diff --git a/mt7996/Makefile b/mt7996/Makefile
+index a056b40e0..7bb17f440 100644
+--- a/mt7996/Makefile
++++ b/mt7996/Makefile
+@@ -8,5 +8,6 @@ mt7996e-y := pci.o init.o dma.o eeprom.o main.o mcu.o mac.o \
+ 	     debugfs.o mmio.o
+ 
+ mt7996e-$(CONFIG_DEV_COREDUMP) += coredump.o
++mt7996e-$(CONFIG_NL80211_TESTMODE) += testmode.o
+ 
+ mt7996e-y += mtk_debugfs.o mtk_mcu.o
+diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
+index 121a3c958..f9b9ca25d 100644
+--- a/mt7996/eeprom.c
++++ b/mt7996/eeprom.c
+@@ -6,6 +6,11 @@
+ #include <linux/firmware.h>
+ #include "mt7996.h"
+ #include "eeprom.h"
++#include <linux/moduleparam.h>
++
++static bool testmode_enable;
++module_param(testmode_enable, bool, 0644);
++MODULE_PARM_DESC(testmode_enable, "Enable testmode");
+ 
+ static int mt7996_check_eeprom(struct mt7996_dev *dev)
+ {
+@@ -43,6 +48,9 @@ static int mt7996_check_eeprom(struct mt7996_dev *dev)
+ 
+ static char *mt7996_eeprom_name(struct mt7996_dev *dev)
+ {
++	if (dev->testmode_enable)
++		return MT7996_EEPROM_DEFAULT_TM;
++
+ 	switch (mt76_chip(&dev->mt76)) {
+ 	case 0x7990:
+ 		if (dev->chip_sku == MT7996_SKU_404)
+@@ -92,21 +100,36 @@ out:
+ 	return ret;
+ }
+ 
+-static int mt7996_eeprom_load(struct mt7996_dev *dev)
++int mt7996_eeprom_check_fw_mode(struct mt7996_dev *dev)
+ {
++	u8 *eeprom;
+ 	int ret;
+ 
++	/* load eeprom in flash or bin file mode to determine fw mode */
+ 	ret = mt76_eeprom_init(&dev->mt76, MT7996_EEPROM_SIZE);
+ 	if (ret < 0)
+ 		return ret;
+ 
+ 	if (ret) {
+ 		dev->flash_mode = true;
+-	} else {
+-		u8 free_block_num;
+-		u32 block_num, i;
+-		u32 eeprom_blk_size = MT7996_EEPROM_BLOCK_SIZE;
++		eeprom = dev->mt76.eeprom.data;
++		/* testmode enable priority: eeprom field > module parameter */
++		dev->testmode_enable = !mt7996_check_eeprom(dev) ? eeprom[MT_EE_TESTMODE_EN] :
++								   testmode_enable;
++	}
++
++	return ret;
++}
++
++static int mt7996_eeprom_load(struct mt7996_dev *dev)
++{
++	int ret;
++	u8 free_block_num;
++	u32 block_num, i;
++	u32 eeprom_blk_size = MT7996_EEPROM_BLOCK_SIZE;
+ 
++	/* flash or bin file mode eeprom is loaded before mcu init */
++	if (!dev->flash_mode) {
+ 		ret = mt7996_mcu_get_eeprom_free_block(dev, &free_block_num);
+ 		if (ret < 0)
+ 			return ret;
+@@ -118,8 +141,8 @@ static int mt7996_eeprom_load(struct mt7996_dev *dev)
+ 		/* read eeprom data from efuse */
+ 		block_num = DIV_ROUND_UP(MT7996_EEPROM_SIZE, eeprom_blk_size);
+ 		for (i = 0; i < block_num; i++) {
+-			ret = mt7996_mcu_get_eeprom(dev, i * eeprom_blk_size);
+-			if (ret < 0)
++			ret = mt7996_mcu_get_eeprom(dev, i * eeprom_blk_size, NULL);
++			if (ret && ret != -EINVAL)
+ 				return ret;
+ 		}
+ 	}
+diff --git a/mt7996/eeprom.h b/mt7996/eeprom.h
+index 72c38ad3b..de3ff4e27 100644
+--- a/mt7996/eeprom.h
++++ b/mt7996/eeprom.h
+@@ -14,6 +14,7 @@ enum mt7996_eeprom_field {
+ 	MT_EE_MAC_ADDR =	0x004,
+ 	MT_EE_MAC_ADDR2 =	0x00a,
+ 	MT_EE_WIFI_CONF =	0x190,
++	MT_EE_TESTMODE_EN =	0x1af,
+ 	MT_EE_MAC_ADDR3 =	0x2c0,
+ 	MT_EE_RATE_DELTA_2G =	0x1400,
+ 	MT_EE_RATE_DELTA_5G =	0x147d,
+diff --git a/mt7996/init.c b/mt7996/init.c
+index 0e3cdc051..d4d1a60b4 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -966,6 +966,10 @@ static int mt7996_init_hardware(struct mt7996_dev *dev)
+ 
+ 	set_bit(MT76_STATE_INITIALIZED, &dev->mphy.state);
+ 
++	ret = mt7996_eeprom_check_fw_mode(dev);
++	if (ret < 0)
++		return ret;
++
+ 	ret = mt7996_mcu_init(dev);
+ 	if (ret)
+ 		return ret;
+@@ -1384,6 +1388,10 @@ int mt7996_register_device(struct mt7996_dev *dev)
+ 
+ 	mt7996_init_wiphy(hw, &dev->mt76.mmio.wed);
+ 
++#ifdef CONFIG_NL80211_TESTMODE
++	dev->mt76.test_ops = &mt7996_testmode_ops;
++#endif
++
+ 	ret = mt76_register_device(&dev->mt76, true, mt76_rates,
+ 				   ARRAY_SIZE(mt76_rates));
+ 	if (ret)
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 1f53d2303..603f6c0d7 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -685,7 +685,8 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
+ 				     *info);
+ 	}
+ 
+-	if (rxv && mode >= MT_PHY_TYPE_HE_SU && !(status->flag & RX_FLAG_8023))
++	if (rxv && mode >= MT_PHY_TYPE_HE_SU && mode < MT_PHY_TYPE_EHT_SU &&
++	    !(status->flag & RX_FLAG_8023))
+ 		mt76_connac3_mac_decode_he_radiotap(skb, rxv, mode);
+ 
+ 	if (!status->wcid || !ieee80211_is_data_qos(fc) || hw_aggr)
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 2a6706c25..ecf65d71f 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -23,6 +23,18 @@ static bool mt7996_dev_running(struct mt7996_dev *dev)
+ 	return phy && test_bit(MT76_STATE_RUNNING, &phy->mt76->state);
+ }
+ 
++static void mt7996_testmode_disable_all(struct mt7996_dev *dev)
++{
++	struct mt7996_phy *phy;
++	int i;
++
++	for (i = 0; i < __MT_MAX_BAND; i++) {
++		phy = __mt7996_phy(dev, i);
++		if (phy)
++			mt76_testmode_set_state(phy->mt76, MT76_TM_STATE_OFF);
++	}
++}
++
+ int mt7996_run(struct ieee80211_hw *hw)
+ {
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+@@ -45,6 +57,8 @@ int mt7996_run(struct ieee80211_hw *hw)
+ 		}
+ 	}
+ 
++	mt7996_testmode_disable_all(dev);
++
+ 	mt7996_mac_enable_nf(dev, phy->mt76->band_idx);
+ 
+ 	ret = mt7996_mcu_set_rts_thresh(phy, 0x92b);
+@@ -303,6 +317,11 @@ int mt7996_set_channel(struct mt7996_phy *phy)
+ 
+ 	mt76_set_channel(phy->mt76);
+ 
++	if (mt76_testmode_enabled(phy->mt76) || phy->mt76->test.bf_en) {
++		mt7996_tm_update_channel(phy);
++		goto out;
++	}
++
+ 	ret = mt7996_mcu_set_chan_info(phy, UNI_CHANNEL_SWITCH);
+ 	if (ret)
+ 		goto out;
+@@ -410,6 +429,11 @@ static int mt7996_config(struct ieee80211_hw *hw, u32 changed)
+ 	int ret;
+ 
+ 	if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
++		if (!mt76_testmode_enabled(phy->mt76) && !phy->mt76->test.bf_en) {
++			ret = mt7996_mcu_edcca_enable(phy, true);
++			if (ret)
++				return ret;
++		}
+ 		ieee80211_stop_queues(hw);
+ 		ret = mt7996_set_channel(phy);
+ 		if (ret)
+@@ -1517,6 +1541,8 @@ const struct ieee80211_ops mt7996_ops = {
+ 	.sta_set_decap_offload = mt7996_sta_set_decap_offload,
+ 	.add_twt_setup = mt7996_mac_add_twt_setup,
+ 	.twt_teardown_request = mt7996_twt_teardown_request,
++	CFG80211_TESTMODE_CMD(mt76_testmode_cmd)
++	CFG80211_TESTMODE_DUMP(mt76_testmode_dump)
+ #ifdef CONFIG_MAC80211_DEBUGFS
+ 	.sta_add_debugfs = mt7996_sta_add_debugfs,
+ #endif
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index e1c8cd725..c7bede6b9 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -2865,8 +2865,12 @@ static int mt7996_load_ram(struct mt7996_dev *dev)
+ {
+ 	int ret;
+ 
+-	ret = __mt7996_load_ram(dev, "WM", fw_name(dev, FIRMWARE_WM),
+-				MT7996_RAM_TYPE_WM);
++	if (dev->testmode_enable)
++		ret = __mt7996_load_ram(dev, "WM_TM", fw_name(dev, FIRMWARE_WM_TM),
++					MT7996_RAM_TYPE_WM_TM);
++	else
++		ret = __mt7996_load_ram(dev, "WM", fw_name(dev, FIRMWARE_WM),
++					MT7996_RAM_TYPE_WM);
+ 	if (ret)
+ 		return ret;
+ 
+@@ -3557,17 +3561,9 @@ int mt7996_mcu_set_eeprom(struct mt7996_dev *dev)
+ 				 &req, sizeof(req), true);
+ }
+ 
+-int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset)
++int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *read_buf)
+ {
+-	struct {
+-		u8 _rsv[4];
+-
+-		__le16 tag;
+-		__le16 len;
+-		__le32 addr;
+-		__le32 valid;
+-		u8 data[16];
+-	} __packed req = {
++	struct mt7996_mcu_eeprom_info req = {
+ 		.tag = cpu_to_le16(UNI_EFUSE_ACCESS),
+ 		.len = cpu_to_le16(sizeof(req) - 4),
+ 		.addr = cpu_to_le32(round_down(offset,
+@@ -3576,6 +3572,7 @@ int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset)
+ 	struct sk_buff *skb;
+ 	bool valid;
+ 	int ret;
++	u8 *buf = read_buf;
+ 
+ 	ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ 					MCU_WM_UNI_CMD_QUERY(EFUSE_CTRL),
+@@ -3586,7 +3583,9 @@ int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset)
+ 	valid = le32_to_cpu(*(__le32 *)(skb->data + 16));
+ 	if (valid) {
+ 		u32 addr = le32_to_cpu(*(__le32 *)(skb->data + 12));
+-		u8 *buf = (u8 *)dev->mt76.eeprom.data + addr;
++
++		if (!buf)
++			buf = (u8 *)dev->mt76.eeprom.data + addr;
+ 
+ 		skb_pull(skb, 48);
+ 		memcpy(buf, skb->data, MT7996_EEPROM_BLOCK_SIZE);
+@@ -4595,3 +4594,37 @@ int mt7996_mcu_set_pp_en(struct mt7996_phy *phy, bool auto_mode,
+ 	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(PP),
+ 				 &req, sizeof(req), false);
+ }
++
++int mt7996_mcu_set_tx_power_ctrl(struct mt7996_phy *phy, u8 power_ctrl_id, u8 data)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct tx_power_ctrl req = {
++		.tag = cpu_to_le16(power_ctrl_id),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.power_ctrl_id = power_ctrl_id,
++		.band_idx = phy->mt76->band_idx,
++	};
++
++	switch (power_ctrl_id) {
++	case UNI_TXPOWER_SKU_POWER_LIMIT_CTRL:
++		req.sku_enable = !!data;
++		break;
++	case UNI_TXPOWER_PERCENTAGE_CTRL:
++		req.percentage_ctrl_enable = !!data;
++		break;
++	case UNI_TXPOWER_PERCENTAGE_DROP_CTRL:
++		req.power_drop_level = data;
++		break;
++	case UNI_TXPOWER_BACKOFF_POWER_LIMIT_CTRL:
++		req.bf_backoff_enable = !!data;
++		break;
++	case UNI_TXPOWER_ATE_MODE_CTRL:
++		req.ate_mode_enable = !!data;
++		break;
++	default:
++		req.sku_enable = !!data;
++	}
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(TXPOWER),
++				 &req, sizeof(req), false);
++}
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 238c4c534..325c3c973 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -157,6 +157,16 @@ struct mt7996_mcu_eeprom {
+ 	__le16 buf_len;
+ } __packed;
+ 
++struct mt7996_mcu_eeprom_info {
++	u8 _rsv[4];
++
++	__le16 tag;
++	__le16 len;
++	__le32 addr;
++	__le32 valid;
++	u8 data[MT7996_EEPROM_BLOCK_SIZE];
++} __packed;
++
+ struct mt7996_mcu_phy_rx_info {
+ 	u8 category;
+ 	u8 rate;
+@@ -889,8 +899,31 @@ enum {
+ 	UNI_CMD_THERMAL_PROTECT_DUTY_CONFIG,
+ };
+ 
++struct tx_power_ctrl {
++	u8 _rsv[4];
++
++	__le16 tag;
++	__le16 len;
++
++	u8 power_ctrl_id;
++	union {
++		bool sku_enable;
++		bool ate_mode_enable;
++		bool percentage_ctrl_enable;
++		bool bf_backoff_enable;
++		u8 power_drop_level;
++	};
++	u8 band_idx;
++	u8 rsv[1];
++} __packed;
++
+ enum {
++	UNI_TXPOWER_SKU_POWER_LIMIT_CTRL = 0,
++	UNI_TXPOWER_PERCENTAGE_CTRL = 1,
++	UNI_TXPOWER_PERCENTAGE_DROP_CTRL = 2,
++	UNI_TXPOWER_BACKOFF_POWER_LIMIT_CTRL = 3,
+ 	UNI_TXPOWER_POWER_LIMIT_TABLE_CTRL = 4,
++	UNI_TXPOWER_ATE_MODE_CTRL = 6,
+ };
+ 
+ enum {
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 4184d065f..72a200fcf 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -32,25 +32,30 @@
+ #define MT7996_FIRMWARE_WA		"mediatek/mt7996/mt7996_wa.bin"
+ #define MT7996_FIRMWARE_WM		"mediatek/mt7996/mt7996_wm.bin"
+ #define MT7996_FIRMWARE_DSP		"mediatek/mt7996/mt7996_dsp.bin"
++#define MT7996_FIRMWARE_WM_TM		"mediatek/mt7996/mt7996_wm_tm.bin"
+ #define MT7996_ROM_PATCH		"mediatek/mt7996/mt7996_rom_patch.bin"
+ 
+ #define MT7992_FIRMWARE_WA		"mediatek/mt7996/mt7992_wa.bin"
+ #define MT7992_FIRMWARE_WM		"mediatek/mt7996/mt7992_wm.bin"
+ #define MT7992_FIRMWARE_DSP		"mediatek/mt7996/mt7992_dsp.bin"
++#define MT7992_FIRMWARE_WM_TM		"mediatek/mt7996/mt7992_wm_tm.bin"
+ #define MT7992_ROM_PATCH		"mediatek/mt7996/mt7992_rom_patch.bin"
+ 
+ #define MT7992_FIRMWARE_WA_24		"mediatek/mt7996/mt7992_wa_24.bin"
+ #define MT7992_FIRMWARE_WM_24		"mediatek/mt7996/mt7992_wm_24.bin"
+ #define MT7992_FIRMWARE_DSP_24		"mediatek/mt7996/mt7992_dsp_24.bin"
++#define MT7992_FIRMWARE_WM_TM_24	"mediatek/mt7996/mt7992_wm_tm_24.bin"
+ #define MT7992_ROM_PATCH_24		"mediatek/mt7996/mt7992_rom_patch_24.bin"
+ 
+ #define MT7992_FIRMWARE_WA_23		"mediatek/mt7996/mt7992_wa_23.bin"
+ #define MT7992_FIRMWARE_WM_23		"mediatek/mt7996/mt7992_wm_23.bin"
+ #define MT7992_FIRMWARE_DSP_23		"mediatek/mt7996/mt7992_dsp_23.bin"
++#define MT7992_FIRMWARE_WM_TM_23	"mediatek/mt7996/mt7992_wm_tm_23.bin"
+ #define MT7992_ROM_PATCH_23		"mediatek/mt7996/mt7992_rom_patch_23.bin"
+ 
+ #define MT7996_EEPROM_DEFAULT		"mediatek/mt7996/mt7996_eeprom.bin"
+ #define MT7996_EEPROM_DEFAULT_404	"mediatek/mt7996/mt7996_eeprom_dual_404.bin"
++#define MT7996_EEPROM_DEFAULT_TM	"mediatek/mt7996/mt7996_eeprom_tm.bin"
+ #define MT7992_EEPROM_DEFAULT		"mediatek/mt7996/mt7992_eeprom_2i5i.bin"
+ #define MT7992_EEPROM_DEFAULT_EXT	"mediatek/mt7996/mt7992_eeprom_2e5e.bin"
+ #define MT7992_EEPROM_DEFAULT_MIX	"mediatek/mt7996/mt7992_eeprom_2i5e.bin"
+@@ -126,6 +131,7 @@ enum mt7992_sku_type {
+ 
+ enum mt7996_ram_type {
+ 	MT7996_RAM_TYPE_WM,
++	MT7996_RAM_TYPE_WM_TM = MT7996_RAM_TYPE_WM,
+ 	MT7996_RAM_TYPE_WA,
+ 	MT7996_RAM_TYPE_DSP,
+ 	__MT7996_RAM_TYPE_MAX,
+@@ -273,6 +279,21 @@ struct mt7996_phy {
+ 	struct mt76_channel_state state_ts;
+ 
+ 	bool has_aux_rx;
++
++#ifdef CONFIG_NL80211_TESTMODE
++	struct {
++		u32 *reg_backup;
++
++		s32 last_freq_offset;
++		u8 last_rcpi[4];
++		s8 last_rssi[4];
++		s8 last_ib_rssi[4];
++		s8 last_wb_rssi[4];
++		u8 last_snr;
++
++		u8 spe_idx;
++	} test;
++#endif
+ };
+ 
+ struct mt7996_dev {
+@@ -353,6 +374,8 @@ struct mt7996_dev {
+ 		spinlock_t lock;
+ 	} wed_rro;
+ 
++	bool testmode_enable;
++
+ 	bool ibf;
+ 	u8 fw_debug_wm;
+ 	u8 fw_debug_wa;
+@@ -467,6 +490,7 @@ mt7996_band_valid(struct mt7996_dev *dev, u8 band)
+ extern const struct ieee80211_ops mt7996_ops;
+ extern struct pci_driver mt7996_pci_driver;
+ extern struct pci_driver mt7996_hif_driver;
++extern const struct mt76_testmode_ops mt7996_testmode_ops;
+ 
+ struct mt7996_dev *mt7996_mmio_probe(struct device *pdev,
+ 				     void __iomem *mem_base, u32 device_id);
+@@ -476,6 +500,7 @@ u64 __mt7996_get_tsf(struct ieee80211_hw *hw, struct mt7996_vif *mvif);
+ int mt7996_register_device(struct mt7996_dev *dev);
+ void mt7996_unregister_device(struct mt7996_dev *dev);
+ int mt7996_eeprom_init(struct mt7996_dev *dev);
++int mt7996_eeprom_check_fw_mode(struct mt7996_dev *dev);
+ int mt7996_eeprom_parse_hw_cap(struct mt7996_dev *dev, struct mt7996_phy *phy);
+ int mt7996_eeprom_get_target_power(struct mt7996_dev *dev,
+ 				   struct ieee80211_channel *chan);
+@@ -528,7 +553,7 @@ int mt7996_mcu_set_fixed_rate_ctrl(struct mt7996_dev *dev,
+ int mt7996_mcu_set_fixed_field(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 			       struct ieee80211_sta *sta, void *data, u32 field);
+ int mt7996_mcu_set_eeprom(struct mt7996_dev *dev);
+-int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset);
++int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *read_buf);
+ int mt7996_mcu_get_eeprom_free_block(struct mt7996_dev *dev, u8 *block_num);
+ int mt7996_mcu_get_chip_config(struct mt7996_dev *dev, u32 *cap);
+ int mt7996_mcu_set_ser(struct mt7996_dev *dev, u8 action, u8 set, u8 band);
+@@ -563,6 +588,7 @@ void mt7996_mcu_rx_event(struct mt7996_dev *dev, struct sk_buff *skb);
+ void mt7996_mcu_exit(struct mt7996_dev *dev);
+ int mt7996_mcu_get_all_sta_info(struct mt7996_phy *phy, u16 tag);
+ int mt7996_mcu_wed_rro_reset_sessions(struct mt7996_dev *dev, u16 id);
++int mt7996_mcu_set_tx_power_ctrl(struct mt7996_phy *phy, u8 power_ctrl_id, u8 data);
+ 
+ static inline u8 mt7996_max_interface_num(struct mt7996_dev *dev)
+ {
+diff --git a/mt7996/testmode.c b/mt7996/testmode.c
+new file mode 100644
+index 000000000..98eebceed
+--- /dev/null
++++ b/mt7996/testmode.c
+@@ -0,0 +1,740 @@
++// SPDX-License-Identifier: ISC
++/*
++ * Copyright (C) 2022 MediaTek Inc.
++ */
++
++#include "mt7996.h"
++#include "mac.h"
++#include "mcu.h"
++#include "testmode.h"
++
++enum {
++	TM_CHANGED_TXPOWER,
++	TM_CHANGED_FREQ_OFFSET,
++	TM_CHANGED_SKU_EN,
++	TM_CHANGED_TX_LENGTH,
++	TM_CHANGED_TX_TIME,
++	TM_CHANGED_CFG,
++
++	/* must be last */
++	NUM_TM_CHANGED
++};
++
++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_SKU_EN] = MT76_TM_ATTR_SKU_EN,
++	[TM_CHANGED_TX_LENGTH] = MT76_TM_ATTR_TX_LENGTH,
++	[TM_CHANGED_TX_TIME] = MT76_TM_ATTR_TX_TIME,
++	[TM_CHANGED_CFG] = MT76_TM_ATTR_CFG,
++};
++
++static u8 mt7996_tm_bw_mapping(enum nl80211_chan_width width, enum bw_mapping_method method)
++{
++	static const u8 width_to_bw[][NUM_BW_MAP] = {
++		[NL80211_CHAN_WIDTH_40] = {FW_CDBW_40MHZ, TM_CBW_40MHZ},
++		[NL80211_CHAN_WIDTH_80] = {FW_CDBW_80MHZ, TM_CBW_80MHZ},
++		[NL80211_CHAN_WIDTH_80P80] = {FW_CDBW_8080MHZ, TM_CBW_8080MHZ},
++		[NL80211_CHAN_WIDTH_160] = {FW_CDBW_160MHZ, TM_CBW_160MHZ},
++		[NL80211_CHAN_WIDTH_5] = {FW_CDBW_5MHZ, TM_CBW_5MHZ},
++		[NL80211_CHAN_WIDTH_10] = {FW_CDBW_10MHZ, TM_CBW_10MHZ},
++		[NL80211_CHAN_WIDTH_20] = {FW_CDBW_20MHZ, TM_CBW_20MHZ},
++		[NL80211_CHAN_WIDTH_20_NOHT] = {FW_CDBW_20MHZ, TM_CBW_20MHZ},
++		[NL80211_CHAN_WIDTH_320] = {FW_CDBW_320MHZ, TM_CBW_320MHZ},
++	};
++
++	if (width >= ARRAY_SIZE(width_to_bw))
++		return 0;
++
++	return width_to_bw[width][method];
++}
++
++static u8 mt7996_tm_rate_to_phy(u8 tx_rate_mode)
++{
++	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,
++	};
++
++	if (tx_rate_mode > MT76_TM_TX_MODE_MAX)
++		return -EINVAL;
++
++	return rate_to_phy[tx_rate_mode];
++}
++
++static int
++mt7996_tm_check_antenna(struct mt7996_phy *phy)
++{
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	struct mt7996_dev *dev = phy->dev;
++	u8 band_idx = phy->mt76->band_idx;
++	u32 chainmask = phy->mt76->chainmask;
++	u32 aux_rx_mask;
++
++	chainmask = chainmask >> dev->chainshift[band_idx];
++	aux_rx_mask = BIT(fls(chainmask)) * phy->has_aux_rx;
++	if (td->tx_antenna_mask & ~(chainmask | aux_rx_mask)) {
++		dev_err(dev->mt76.dev,
++			"tx antenna mask 0x%x exceeds hw limit (chainmask 0x%x, has aux rx: %s)\n",
++			td->tx_antenna_mask, chainmask, phy->has_aux_rx ? "yes" : "no");
++		return -EINVAL;
++	}
++
++	return 0;
++}
++
++static int
++mt7996_tm_set(struct mt7996_dev *dev, u32 func_idx, u32 data)
++{
++	struct mt7996_tm_req req = {
++		.rf_test = {
++			.tag = cpu_to_le16(UNI_RF_TEST_CTRL),
++			.len = cpu_to_le16(sizeof(req.rf_test)),
++			.action = RF_ACTION_SET,
++			.op.rf.func_idx = func_idx,
++			.op.rf.param.func_data = cpu_to_le32(data),
++		},
++	};
++	bool wait = (data == RF_CMD(START_TX)) ? true : false;
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(TESTMODE_CTRL), &req,
++				 sizeof(req), wait);
++}
++
++static int
++mt7996_tm_get(struct mt7996_dev *dev, u32 func_idx, u32 data, u32 *result)
++{
++	struct mt7996_tm_req req = {
++		.rf_test = {
++			.tag = cpu_to_le16(UNI_RF_TEST_CTRL),
++			.len = cpu_to_le16(sizeof(req.rf_test)),
++			.action = RF_ACTION_GET,
++			.op.rf.func_idx = func_idx,
++			.op.rf.param.func_data = cpu_to_le32(data),
++		},
++	};
++	struct mt7996_tm_event *event;
++	struct sk_buff *skb;
++	int ret;
++
++	ret = mt76_mcu_send_and_get_msg(&dev->mt76, MCU_WM_UNI_CMD_QUERY(TESTMODE_CTRL),
++					&req, sizeof(req), true, &skb);
++	if (ret)
++		return ret;
++
++	event = (struct mt7996_tm_event *)skb->data;
++	*result = event->result.payload_length;
++
++	dev_kfree_skb(skb);
++
++	return ret;
++}
++
++static void
++mt7996_tm_set_antenna(struct mt7996_phy *phy, u32 func_idx)
++{
++#define SPE_INDEX_MASK		BIT(31)
++#define TX_ANTENNA_MASK		GENMASK(3, 0)
++#define RX_ANTENNA_MASK		GENMASK(20, 16)		/* RX antenna mask at most 5 bit */
++	struct mt7996_dev *dev = phy->dev;
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	u32 antenna_mask;
++
++	if (!mt76_testmode_param_present(td, MT76_TM_ATTR_TX_ANTENNA))
++		return;
++
++	if (func_idx == SET_ID(TX_PATH))
++		antenna_mask = td->tx_spe_idx ? (SPE_INDEX_MASK | td->tx_spe_idx) :
++						td->tx_antenna_mask & TX_ANTENNA_MASK;
++	else if (func_idx == SET_ID(RX_PATH))
++		antenna_mask = u32_encode_bits(td->tx_antenna_mask, RX_ANTENNA_MASK);
++	else
++		return;
++
++	mt7996_tm_set(dev, func_idx, antenna_mask);
++}
++
++static void
++mt7996_tm_set_mac_addr(struct mt7996_dev *dev, u8 *addr, u32 func_idx)
++{
++#define REMAIN_PART_TAG		BIT(18)
++	u32 own_mac_first = 0, own_mac_remain = 0;
++	int len = sizeof(u32);
++
++	memcpy(&own_mac_first, addr, len);
++	mt7996_tm_set(dev, func_idx, own_mac_first);
++	/* Set the remain part of mac address */
++	memcpy(&own_mac_remain, addr + len, ETH_ALEN - len);
++	mt7996_tm_set(dev, func_idx | REMAIN_PART_TAG, own_mac_remain);
++}
++
++static int
++mt7996_tm_rf_switch_mode(struct mt7996_dev *dev, u32 op_mode)
++{
++	struct mt7996_tm_req req = {
++		.rf_test = {
++			.tag = cpu_to_le16(UNI_RF_TEST_CTRL),
++			.len = cpu_to_le16(sizeof(req.rf_test)),
++			.action = RF_ACTION_SWITCH_TO_RF_TEST,
++			.op.op_mode = cpu_to_le32(op_mode),
++		},
++	};
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(TESTMODE_CTRL), &req,
++				 sizeof(req), false);
++}
++
++static void
++mt7996_tm_init(struct mt7996_phy *phy, bool en)
++{
++	struct mt7996_dev *dev = phy->dev;
++	u8 rf_test_mode = en ? RF_OPER_RF_TEST : RF_OPER_NORMAL;
++
++	if (!test_bit(MT76_STATE_RUNNING, &phy->mt76->state))
++		return;
++
++	mt7996_mcu_set_tx_power_ctrl(phy, POWER_CTRL(ATE_MODE), en);
++	mt7996_mcu_set_tx_power_ctrl(phy, POWER_CTRL(SKU_POWER_LIMIT), !en);
++	mt7996_mcu_set_tx_power_ctrl(phy, POWER_CTRL(BACKOFF_POWER_LIMIT), !en);
++
++	mt7996_tm_rf_switch_mode(dev, rf_test_mode);
++
++	mt7996_mcu_add_bss_info(phy, phy->monitor_vif, en);
++	mt7996_mcu_add_sta(dev, phy->monitor_vif, NULL, en, false);
++
++	mt7996_tm_set(dev, SET_ID(BAND_IDX), phy->mt76->band_idx);
++
++	/* use firmware counter for RX stats */
++	phy->mt76->test.flag |= MT_TM_FW_RX_COUNT;
++}
++
++static void
++mt7996_tm_update_channel(struct mt7996_phy *phy)
++{
++#define CHAN_FREQ_BW_80P80_TAG		(SET_ID(CHAN_FREQ) | BIT(16))
++	struct mt7996_dev *dev = phy->dev;
++	struct cfg80211_chan_def *chandef = &phy->mt76->chandef;
++	struct ieee80211_channel *chan = chandef->chan;
++	u8 width = chandef->width;
++	static const u8 ch_band[] = {
++		[NL80211_BAND_2GHZ] = 0,
++		[NL80211_BAND_5GHZ] = 1,
++		[NL80211_BAND_6GHZ] = 2,
++	};
++
++	if (!chan || !chandef) {
++		dev_info(dev->mt76.dev, "chandef not found, channel update failed!\n");
++		return;
++	}
++
++	/* system bw */
++	mt7996_tm_set(dev, SET_ID(CBW), mt7996_tm_bw_mapping(width, BW_MAP_NL_TO_FW));
++
++	if (width == NL80211_CHAN_WIDTH_80P80) {
++		width = NL80211_CHAN_WIDTH_160;
++		mt7996_tm_set(dev, CHAN_FREQ_BW_80P80_TAG, chandef->center_freq2 * 1000);
++	}
++
++	/* TODO: define per-packet bw */
++	/* per-packet bw */
++	mt7996_tm_set(dev, SET_ID(DBW), mt7996_tm_bw_mapping(width, BW_MAP_NL_TO_FW));
++
++	/* control channel selection index */
++	mt7996_tm_set(dev, SET_ID(PRIMARY_CH), 0);
++	mt7996_tm_set(dev, SET_ID(BAND), ch_band[chan->band]);
++
++	/* trigger switch channel calibration */
++	mt7996_tm_set(dev, SET_ID(CHAN_FREQ), chandef->center_freq1 * 1000);
++
++	// TODO: update power limit table
++}
++
++static void
++mt7996_tm_tx_stop(struct mt76_phy *mphy)
++{
++	struct mt76_testmode_data *td = &mphy->test;
++	struct mt7996_phy *phy = mphy->priv;
++	struct mt7996_dev *dev = phy->dev;
++
++	mt7996_tm_set(dev, SET_ID(COMMAND), RF_CMD(STOP_TEST));
++	td->tx_pending = 0;
++}
++
++static void
++mt7996_tm_set_tx_frames(struct mt7996_phy *phy, bool en)
++{
++#define FRAME_CONTROL		0x88
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	struct mt7996_dev *dev = phy->dev;
++
++	//TODO: RU operation, replace mcs, nss, and ldpc
++	if (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_RATE), td->tx_rate_idx);
++
++		if (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_POWER))
++			mt7996_tm_set(dev, SET_ID(POWER), td->tx_power[0]);
++
++		if (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_TIME)) {
++			mt7996_tm_set(dev, SET_ID(TX_LEN), 0);
++			mt7996_tm_set(dev, SET_ID(TX_TIME), td->tx_time);
++		} else {
++			mt7996_tm_set(dev, SET_ID(TX_LEN), td->tx_mpdu_len);
++			mt7996_tm_set(dev, SET_ID(TX_TIME), 0);
++		}
++
++		mt7996_tm_set_antenna(phy, SET_ID(TX_PATH));
++		mt7996_tm_set_antenna(phy, SET_ID(RX_PATH));
++		mt7996_tm_set(dev, SET_ID(STBC), td->tx_rate_stbc);
++		mt7996_tm_set(dev, SET_ID(ENCODE_MODE), td->tx_rate_ldpc);
++		mt7996_tm_set(dev, SET_ID(IBF_ENABLE), td->ibf);
++		mt7996_tm_set(dev, SET_ID(EBF_ENABLE), td->ebf);
++		mt7996_tm_set(dev, SET_ID(IPG), td->tx_ipg);
++		mt7996_tm_set(dev, SET_ID(GI), td->tx_rate_sgi);
++		mt7996_tm_set(dev, SET_ID(NSS), td->tx_rate_nss);
++		mt7996_tm_set(dev, SET_ID(AID_OFFSET), 0);
++		mt7996_tm_set(dev, SET_ID(PUNCTURE), td->tx_preamble_puncture);
++
++		mt7996_tm_set(dev, SET_ID(MAX_PE), 2);
++		mt7996_tm_set(dev, SET_ID(HW_TX_MODE), 0);
++		mt7996_tm_update_channel(phy);
++
++		/* trigger firmware to start TX */
++		mt7996_tm_set(dev, SET_ID(COMMAND), RF_CMD(START_TX));
++	} else {
++		mt7996_tm_tx_stop(phy->mt76);
++	}
++}
++
++static int
++mt7996_tm_rx_stats_user_ctrl(struct mt7996_phy *phy, u16 user_idx)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_tm_rx_req req = {
++		.band = phy->mt76->band_idx,
++		.user_ctrl = {
++			.tag = cpu_to_le16(UNI_TM_RX_STAT_SET_USER_CTRL),
++			.len = cpu_to_le16(sizeof(req.user_ctrl)),
++			.band_idx = phy->mt76->band_idx,
++			.user_idx = cpu_to_le16(user_idx),
++		},
++	};
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(TESTMODE_RX_STAT), &req,
++				 sizeof(req), false);
++}
++
++static void
++mt7996_tm_set_rx_frames(struct mt7996_phy *phy, bool en)
++{
++#define RX_MU_DISABLE	0xf800
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	struct mt7996_dev *dev = phy->dev;
++	int ret;
++
++	if (en) {
++		ret = mt7996_tm_rx_stats_user_ctrl(phy, td->aid);
++		if (ret) {
++			dev_info(dev->mt76.dev, "Set RX stats user control failed!\n");
++			return;
++		}
++
++		mt7996_tm_update_channel(phy);
++
++		if (td->tx_rate_mode >= MT76_TM_TX_MODE_HE_MU) {
++			if (td->aid)
++				ret = mt7996_tm_set(dev, SET_ID(RX_MU_AID), td->aid);
++			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(GI), td->tx_rate_sgi);
++		mt7996_tm_set_antenna(phy, SET_ID(RX_PATH));
++		mt7996_tm_set(dev, SET_ID(MAX_PE), 2);
++
++		mt7996_tm_set_mac_addr(dev, td->addr[1], SET_ID(SA));
++
++		/* trigger firmware to start RX */
++		mt7996_tm_set(dev, SET_ID(COMMAND), RF_CMD(START_RX));
++	} else {
++		/* trigger firmware to stop RX */
++		mt7996_tm_set(dev, SET_ID(COMMAND), RF_CMD(STOP_TEST));
++	}
++}
++
++static void
++mt7996_tm_set_tx_cont(struct mt7996_phy *phy, bool en)
++{
++#define CONT_WAVE_MODE_OFDM	3
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	struct mt7996_dev *dev = phy->dev;
++
++	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_RATE), td->tx_rate_idx);
++		/* fix payload is OFDM */
++		mt7996_tm_set(dev, SET_ID(CONT_WAVE_MODE), CONT_WAVE_MODE_OFDM);
++		mt7996_tm_set(dev, SET_ID(ANT_MASK), td->tx_antenna_mask);
++
++		/* trigger firmware to start CONT TX */
++		mt7996_tm_set(dev, SET_ID(COMMAND), RF_CMD(CONT_WAVE));
++	} else {
++		/* trigger firmware to stop CONT TX  */
++		mt7996_tm_set(dev, SET_ID(COMMAND), RF_CMD(STOP_TEST));
++	}
++}
++
++static void
++mt7996_tm_update_params(struct mt7996_phy *phy, u32 changed)
++{
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	struct mt7996_dev *dev = phy->dev;
++
++	if (changed & BIT(TM_CHANGED_FREQ_OFFSET)) {
++		mt7996_tm_set(dev, SET_ID(FREQ_OFFSET), td->freq_offset);
++		mt7996_tm_set(dev, SET_ID(FREQ_OFFSET_C2), td->freq_offset);
++	}
++	if (changed & BIT(TM_CHANGED_TXPOWER))
++		mt7996_tm_set(dev, SET_ID(POWER), td->tx_power[0]);
++	if (changed & BIT(TM_CHANGED_SKU_EN)) {
++		mt7996_tm_update_channel(phy);
++		mt7996_mcu_set_tx_power_ctrl(phy, POWER_CTRL(SKU_POWER_LIMIT), td->sku_en);
++		mt7996_mcu_set_tx_power_ctrl(phy, POWER_CTRL(BACKOFF_POWER_LIMIT), td->sku_en);
++		mt7996_mcu_set_txpower_sku(phy);
++	}
++	if (changed & BIT(TM_CHANGED_TX_LENGTH)) {
++		mt7996_tm_set(dev, SET_ID(TX_LEN), td->tx_mpdu_len);
++		mt7996_tm_set(dev, SET_ID(TX_TIME), 0);
++	}
++	if (changed & BIT(TM_CHANGED_TX_TIME)) {
++		mt7996_tm_set(dev, SET_ID(TX_LEN), 0);
++		mt7996_tm_set(dev, SET_ID(TX_TIME), td->tx_time);
++	}
++	if (changed & BIT(TM_CHANGED_CFG)) {
++		u32 func_idx = td->cfg.enable ? SET_ID(CFG_ON) : SET_ID(CFG_OFF);
++
++		mt7996_tm_set(dev, func_idx, td->cfg.type);
++	}
++}
++
++static int
++mt7996_tm_set_state(struct mt76_phy *mphy, enum mt76_testmode_state state)
++{
++	struct mt76_testmode_data *td = &mphy->test;
++	struct mt7996_phy *phy = mphy->priv;
++	enum mt76_testmode_state prev_state = td->state;
++
++	mphy->test.state = state;
++
++	if (prev_state != MT76_TM_STATE_OFF)
++		mt7996_tm_set(phy->dev, SET_ID(BAND_IDX), mphy->band_idx);
++
++	if (prev_state == MT76_TM_STATE_TX_FRAMES ||
++	    state == MT76_TM_STATE_TX_FRAMES)
++		mt7996_tm_set_tx_frames(phy, state == MT76_TM_STATE_TX_FRAMES);
++	else if (prev_state == MT76_TM_STATE_RX_FRAMES ||
++		 state == MT76_TM_STATE_RX_FRAMES)
++		mt7996_tm_set_rx_frames(phy, state == MT76_TM_STATE_RX_FRAMES);
++	else if (prev_state == MT76_TM_STATE_TX_CONT ||
++		 state == MT76_TM_STATE_TX_CONT)
++		mt7996_tm_set_tx_cont(phy, state == MT76_TM_STATE_TX_CONT);
++	else if (prev_state == MT76_TM_STATE_OFF ||
++		 state == MT76_TM_STATE_OFF)
++		mt7996_tm_init(phy, !(state == MT76_TM_STATE_OFF));
++
++	if ((state == MT76_TM_STATE_IDLE &&
++	     prev_state == MT76_TM_STATE_OFF) ||
++	    (state == MT76_TM_STATE_OFF &&
++	     prev_state == MT76_TM_STATE_IDLE)) {
++		u32 changed = 0;
++		int i, ret;
++
++		for (i = 0; i < ARRAY_SIZE(tm_change_map); i++) {
++			u16 cur = tm_change_map[i];
++
++			if (mt76_testmode_param_present(td, cur))
++				changed |= BIT(i);
++		}
++
++		ret = mt7996_tm_check_antenna(phy);
++		if (ret)
++			return ret;
++
++		mt7996_tm_update_params(phy, changed);
++	}
++
++	return 0;
++}
++
++static int
++mt7996_tm_set_params(struct mt76_phy *mphy, struct nlattr **tb,
++		     enum mt76_testmode_state new_state)
++{
++	struct mt76_testmode_data *td = &mphy->test;
++	struct mt7996_phy *phy = mphy->priv;
++	struct mt7996_dev *dev = phy->dev;
++	u32 changed = 0;
++	int i, ret;
++
++	BUILD_BUG_ON(NUM_TM_CHANGED >= 32);
++
++	if (new_state == MT76_TM_STATE_OFF ||
++	    td->state == MT76_TM_STATE_OFF)
++		return 0;
++
++	ret = mt7996_tm_check_antenna(phy);
++	if (ret)
++		return ret;
++
++	for (i = 0; i < ARRAY_SIZE(tm_change_map); i++) {
++		if (tb[tm_change_map[i]])
++			changed |= BIT(i);
++	}
++
++	mt7996_tm_set(dev, SET_ID(BAND_IDX), mphy->band_idx);
++	mt7996_tm_update_params(phy, changed);
++
++	return 0;
++}
++
++static int
++mt7996_tm_get_rx_stats(struct mt7996_phy *phy)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_tm_rx_req req = {
++		.band = phy->mt76->band_idx,
++		.rx_stat_all = {
++			.tag = cpu_to_le16(UNI_TM_RX_STAT_GET_ALL_V2),
++			.len = cpu_to_le16(sizeof(req.rx_stat_all)),
++			.band_idx = phy->mt76->band_idx,
++		},
++	};
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	struct mt7996_tm_rx_event *rx_stats;
++	struct mt7996_tm_rx_event_stat_all *rx_stats_all;
++	struct sk_buff *skb;
++	enum mt76_rxq_id qid;
++	int i, ret = 0;
++	u32 mac_rx_mdrdy_cnt;
++	u16 mac_rx_len_mismatch, fcs_err_count;
++
++	if (td->state != MT76_TM_STATE_RX_FRAMES)
++		return 0;
++
++	ret = mt76_mcu_send_and_get_msg(&dev->mt76, MCU_WM_UNI_CMD_QUERY(TESTMODE_RX_STAT),
++					&req, sizeof(req), true, &skb);
++
++	if (ret)
++		return ret;
++
++	rx_stats = (struct mt7996_tm_rx_event *)skb->data;
++	rx_stats_all = &rx_stats->rx_stat_all;
++
++	phy->test.last_freq_offset = le32_to_cpu(rx_stats_all->user_info[0].freq_offset);
++	phy->test.last_snr = le32_to_cpu(rx_stats_all->user_info[0].snr);
++	for (i = 0; i < ARRAY_SIZE(phy->test.last_rcpi); i++) {
++		phy->test.last_rcpi[i] = le16_to_cpu(rx_stats_all->rxv_info[i].rcpi);
++		phy->test.last_rssi[i] = le16_to_cpu(rx_stats_all->rxv_info[i].rssi);
++		phy->test.last_ib_rssi[i] = rx_stats_all->fagc[i].ib_rssi;
++		phy->test.last_wb_rssi[i] = rx_stats_all->fagc[i].wb_rssi;
++	}
++
++	if (phy->mt76->band_idx == 2)
++		qid = MT_RXQ_BAND2;
++	else if (phy->mt76->band_idx == 1)
++		qid = MT_RXQ_BAND1;
++	else
++		qid = MT_RXQ_MAIN;
++
++	fcs_err_count = le16_to_cpu(rx_stats_all->band_info.mac_rx_fcs_err_cnt);
++	mac_rx_len_mismatch = le16_to_cpu(rx_stats_all->band_info.mac_rx_len_mismatch);
++	mac_rx_mdrdy_cnt = le32_to_cpu(rx_stats_all->band_info.mac_rx_mdrdy_cnt);
++	td->rx_stats.packets[qid] += mac_rx_mdrdy_cnt;
++	td->rx_stats.packets[qid] += fcs_err_count;
++	td->rx_stats.fcs_error[qid] += fcs_err_count;
++	td->rx_stats.len_mismatch += mac_rx_len_mismatch;
++
++	dev_kfree_skb(skb);
++
++	return ret;
++}
++
++static void
++mt7996_tm_reset_trx_stats(struct mt76_phy *mphy)
++{
++	struct mt7996_phy *phy = mphy->priv;
++	struct mt7996_dev *dev = phy->dev;
++
++	memset(&mphy->test.rx_stats, 0, sizeof(mphy->test.rx_stats));
++	mt7996_tm_set(dev, SET_ID(TRX_COUNTER_RESET), 0);
++}
++
++static int
++mt7996_tm_get_tx_stats(struct mt7996_phy *phy)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	int ret;
++
++	if (td->state != MT76_TM_STATE_TX_FRAMES)
++		return 0;
++
++	ret = mt7996_tm_get(dev, GET_ID(TXED_COUNT), 0, &td->tx_done);
++	if (ret)
++		return ret;
++
++	td->tx_pending = td->tx_count - td->tx_done;
++
++	return ret;
++}
++
++static int
++mt7996_tm_dump_stats(struct mt76_phy *mphy, struct sk_buff *msg)
++{
++	struct mt7996_phy *phy = mphy->priv;
++	void *rx, *rssi;
++	int i;
++
++	mt7996_tm_set(phy->dev, SET_ID(BAND_IDX), mphy->band_idx);
++	mt7996_tm_get_rx_stats(phy);
++	mt7996_tm_get_tx_stats(phy);
++
++	rx = nla_nest_start(msg, MT76_TM_STATS_ATTR_LAST_RX);
++	if (!rx)
++		return -ENOMEM;
++
++	if (nla_put_s32(msg, MT76_TM_RX_ATTR_FREQ_OFFSET, phy->test.last_freq_offset))
++		return -ENOMEM;
++
++	rssi = nla_nest_start(msg, MT76_TM_RX_ATTR_RCPI);
++	if (!rssi)
++		return -ENOMEM;
++
++	for (i = 0; i < ARRAY_SIZE(phy->test.last_rcpi); i++)
++		if (nla_put_u8(msg, i, phy->test.last_rcpi[i]))
++			return -ENOMEM;
++
++	nla_nest_end(msg, rssi);
++
++	rssi = nla_nest_start(msg, MT76_TM_RX_ATTR_RSSI);
++	if (!rssi)
++		return -ENOMEM;
++
++	for (i = 0; i < ARRAY_SIZE(phy->test.last_rssi); i++)
++		if (nla_put_s8(msg, i, phy->test.last_rssi[i]))
++			return -ENOMEM;
++
++	nla_nest_end(msg, rssi);
++
++	rssi = nla_nest_start(msg, MT76_TM_RX_ATTR_IB_RSSI);
++	if (!rssi)
++		return -ENOMEM;
++
++	for (i = 0; i < ARRAY_SIZE(phy->test.last_ib_rssi); i++)
++		if (nla_put_s8(msg, i, phy->test.last_ib_rssi[i]))
++			return -ENOMEM;
++
++	nla_nest_end(msg, rssi);
++
++	rssi = nla_nest_start(msg, MT76_TM_RX_ATTR_WB_RSSI);
++	if (!rssi)
++		return -ENOMEM;
++
++	for (i = 0; i < ARRAY_SIZE(phy->test.last_wb_rssi); i++)
++		if (nla_put_s8(msg, i, phy->test.last_wb_rssi[i]))
++			return -ENOMEM;
++
++	nla_nest_end(msg, rssi);
++
++	if (nla_put_u8(msg, MT76_TM_RX_ATTR_SNR, phy->test.last_snr))
++		return -ENOMEM;
++
++	nla_nest_end(msg, rx);
++
++	return 0;
++}
++
++static int
++mt7996_tm_write_back_to_efuse(struct mt7996_dev *dev)
++{
++	struct mt7996_mcu_eeprom_info req = {
++		.tag = cpu_to_le16(UNI_EFUSE_ACCESS),
++		.len = cpu_to_le16(sizeof(req) - 4),
++	};
++	u8 read_buf[MT76_TM_EEPROM_BLOCK_SIZE], *eeprom = dev->mt76.eeprom.data;
++	int i, ret = -EINVAL;
++
++	/* prevent from damaging chip id in efuse */
++	if (mt76_chip(&dev->mt76) != get_unaligned_le16(eeprom))
++		goto out;
++
++	for (i = 0; i < MT7996_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 = mt7996_mcu_get_eeprom(dev, i, read_buf);
++		if (ret < 0)
++			return ret;
++
++		if (!memcmp(req.data, read_buf, MT76_TM_EEPROM_BLOCK_SIZE))
++			continue;
++
++		ret = mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(EFUSE_CTRL),
++					&req, sizeof(req), true);
++		if (ret)
++			return ret;
++	}
++
++out:
++	return ret;
++}
++
++static int
++mt7996_tm_set_eeprom(struct mt76_phy *mphy, u32 offset, u8 *val, u8 action)
++{
++	struct mt7996_phy *phy = mphy->priv;
++	struct mt7996_dev *dev = phy->dev;
++	u8 *eeprom = dev->mt76.eeprom.data;
++	int ret = 0;
++
++	if (offset >= MT7996_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 = mt7996_mcu_set_eeprom(dev);
++		break;
++	case MT76_TM_EEPROM_ACTION_WRITE_TO_EFUSE:
++		ret = mt7996_tm_write_back_to_efuse(dev);
++		break;
++	default:
++		break;
++	}
++
++	return ret;
++}
++
++const struct mt76_testmode_ops mt7996_testmode_ops = {
++	.set_state = mt7996_tm_set_state,
++	.set_params = mt7996_tm_set_params,
++	.dump_stats = mt7996_tm_dump_stats,
++	.reset_rx_stats = mt7996_tm_reset_trx_stats,
++	.tx_stop = mt7996_tm_tx_stop,
++	.set_eeprom = mt7996_tm_set_eeprom,
++};
+diff --git a/mt7996/testmode.h b/mt7996/testmode.h
+new file mode 100644
+index 000000000..319ef257a
+--- /dev/null
++++ b/mt7996/testmode.h
+@@ -0,0 +1,299 @@
++/* SPDX-License-Identifier: ISC */
++/* Copyright (C) 2020 MediaTek Inc. */
++
++#ifndef __MT7996_TESTMODE_H
++#define __MT7996_TESTMODE_H
++
++enum {
++	TM_CBW_20MHZ,
++	TM_CBW_40MHZ,
++	TM_CBW_80MHZ,
++	TM_CBW_10MHZ,
++	TM_CBW_5MHZ,
++	TM_CBW_160MHZ,
++	TM_CBW_8080MHZ,
++	TM_CBW_320MHZ = 12,
++};
++
++/* BW defined in FW hal_cal_flow_rom.h */
++enum {
++	FW_CDBW_20MHZ,
++	FW_CDBW_40MHZ,
++	FW_CDBW_80MHZ,
++	FW_CDBW_160MHZ,
++	FW_CDBW_320MHZ,
++	FW_CDBW_5MHZ,
++	FW_CDBW_10MHZ,
++	FW_CDBW_8080MHZ,
++};
++
++enum bw_mapping_method {
++	BW_MAP_NL_TO_FW,
++	BW_MAP_NL_TO_TM,
++
++	NUM_BW_MAP,
++};
++
++struct mt7996_tm_rf_test {
++	__le16 tag;
++	__le16 len;
++
++	u8 action;
++	u8 icap_len;
++	u8 _rsv[2];
++	union {
++		__le32 op_mode;
++		__le32 freq;
++
++		struct {
++			__le32 func_idx;
++			union {
++				__le32 func_data;
++				__le32 cal_dump;
++
++				u8 _pad[80];
++			} param;
++		} rf;
++	} op;
++} __packed;
++
++struct mt7996_tm_req {
++	u8 _rsv[4];
++
++	struct mt7996_tm_rf_test rf_test;
++} __packed;
++
++struct mt7996_tm_rf_test_result {
++	__le32 func_idx;
++	__le32 payload_length;
++	u8 event[0];
++};
++
++struct mt7996_tm_event {
++	u8 _rsv[4];
++
++	__le16 tag;
++	__le16 len;
++	struct mt7996_tm_rf_test_result result;
++} __packed;
++
++enum {
++	RF_ACTION_SWITCH_TO_RF_TEST,
++	RF_ACTION_IN_RF_TEST,
++	RF_ACTION_SET = 3,
++	RF_ACTION_GET,
++};
++
++enum {
++	RF_OPER_NORMAL,
++	RF_OPER_RF_TEST,
++	RF_OPER_ICAP,
++	RF_OPER_ICAP_OVERLAP,
++	RF_OPER_WIFI_SPECTRUM,
++};
++
++enum {
++	UNI_RF_TEST_CTRL,
++};
++
++#define RF_CMD(cmd)		RF_TEST_CMD_##cmd
++
++enum {
++	RF_TEST_CMD_STOP_TEST = 0,
++	RF_TEST_CMD_START_TX = 1,
++	RF_TEST_CMD_START_RX = 2,
++	RF_TEST_CMD_CONT_WAVE = 10,
++	RF_TEST_CMD_TX_COMMIT = 18,
++	RF_TEST_CMD_RX_COMMIT = 19,
++};
++
++#define SET_ID(id)		RF_TEST_ID_SET_##id
++#define GET_ID(id)		RF_TEST_ID_GET_##id
++
++enum {
++	RF_TEST_ID_SET_COMMAND = 1,
++	RF_TEST_ID_SET_POWER = 2,
++	RF_TEST_ID_SET_TX_RATE = 3,
++	RF_TEST_ID_SET_TX_MODE = 4,
++	RF_TEST_ID_SET_TX_LEN = 6,
++	RF_TEST_ID_SET_TX_COUNT = 7,
++	RF_TEST_ID_SET_IPG = 8,
++	RF_TEST_ID_SET_GI = 16,
++	RF_TEST_ID_SET_STBC = 17,
++	RF_TEST_ID_SET_CHAN_FREQ = 18,
++	RF_TEST_ID_GET_TXED_COUNT = 32,
++	RF_TEST_ID_SET_CONT_WAVE_MODE = 65,
++	RF_TEST_ID_SET_DA = 68,
++	RF_TEST_ID_SET_SA = 69,
++	RF_TEST_ID_SET_CBW = 71,
++	RF_TEST_ID_SET_DBW = 72,
++	RF_TEST_ID_SET_PRIMARY_CH = 73,
++	RF_TEST_ID_SET_ENCODE_MODE = 74,
++	RF_TEST_ID_SET_BAND = 90,
++	RF_TEST_ID_SET_TRX_COUNTER_RESET = 91,
++	RF_TEST_ID_SET_MAC_HEADER = 101,
++	RF_TEST_ID_SET_SEQ_CTRL = 102,
++	RF_TEST_ID_SET_PAYLOAD = 103,
++	RF_TEST_ID_SET_BAND_IDX = 104,
++	RF_TEST_ID_SET_RX_PATH = 106,
++	RF_TEST_ID_SET_FREQ_OFFSET = 107,
++	RF_TEST_ID_GET_FREQ_OFFSET = 108,
++	RF_TEST_ID_SET_TX_PATH = 113,
++	RF_TEST_ID_SET_NSS = 114,
++	RF_TEST_ID_SET_ANT_MASK = 115,
++	RF_TEST_ID_SET_IBF_ENABLE = 126,
++	RF_TEST_ID_SET_EBF_ENABLE = 127,
++	RF_TEST_ID_GET_TX_POWER = 136,
++	RF_TEST_ID_SET_RX_MU_AID = 157,
++	RF_TEST_ID_SET_HW_TX_MODE = 167,
++	RF_TEST_ID_SET_PUNCTURE = 168,
++	RF_TEST_ID_SET_FREQ_OFFSET_C2 = 171,
++	RF_TEST_ID_GET_FREQ_OFFSET_C2 = 172,
++	RF_TEST_ID_SET_CFG_ON = 176,
++	RF_TEST_ID_SET_CFG_OFF = 177,
++	RF_TEST_ID_SET_BSSID = 189,
++	RF_TEST_ID_SET_TX_TIME = 190,
++	RF_TEST_ID_SET_MAX_PE = 191,
++	RF_TEST_ID_SET_AID_OFFSET = 204,
++};
++
++#define POWER_CTRL(type)	UNI_TXPOWER_##type##_CTRL
++
++struct mt7996_tm_rx_stat_user_ctrl {
++	__le16 tag;
++	__le16 len;
++
++	u8 band_idx;
++	u8 rsv;
++	__le16 user_idx;
++} __packed;
++
++struct mt7996_tm_rx_stat_all {
++	__le16 tag;
++	__le16 len;
++
++	u8 band_idx;
++	u8 rsv[3];
++} __packed;
++
++struct mt7996_tm_rx_req {
++	u8 band;
++	u8 _rsv[3];
++
++	union {
++		struct mt7996_tm_rx_stat_user_ctrl user_ctrl;
++		struct mt7996_tm_rx_stat_all rx_stat_all;
++	};
++} __packed;
++
++enum {
++	UNI_TM_RX_STAT_SET_USER_CTRL = 7,
++	UNI_TM_RX_STAT_GET_ALL_V2 = 9,
++};
++
++struct rx_band_info {
++	/* mac part */
++	__le16 mac_rx_fcs_err_cnt;
++	__le16 mac_rx_len_mismatch;
++	__le16 mac_rx_fcs_ok_cnt;
++	u8 rsv1[2];
++	__le32 mac_rx_mdrdy_cnt;
++
++	/* phy part */
++	__le16 phy_rx_fcs_err_cnt_cck;
++	__le16 phy_rx_fcs_err_cnt_ofdm;
++	__le16 phy_rx_pd_cck;
++	__le16 phy_rx_pd_ofdm;
++	__le16 phy_rx_sig_err_cck;
++	__le16 phy_rx_sfd_err_cck;
++	__le16 phy_rx_sig_err_ofdm;
++	__le16 phy_rx_tag_err_ofdm;
++	__le16 phy_rx_mdrdy_cnt_cck;
++	__le16 phy_rx_mdrdy_cnt_ofdm;
++} __packed;
++
++struct rx_band_info_ext {
++	/* mac part */
++	__le32 mac_rx_mpdu_cnt;
++
++	/* phy part */
++	u8 rsv[4];
++} __packed;
++
++struct rx_common_info {
++	__le16 rx_fifo_full;
++	u8 rsv[2];
++	__le32 aci_hit_low;
++	__le32 aci_hit_high;
++} __packed;
++
++struct rx_common_info_ext {
++	__le32 driver_rx_count;
++	__le32 sinr;
++	__le32 mu_pkt_count;
++
++	/* mac part */
++	u8 _rsv[4];
++
++	/* phy part */
++	u8 sig_mcs;
++	u8 rsv[3];
++} __packed;
++
++struct rx_rxv_info {
++	__le16 rcpi;
++	s16 rssi;
++	s16 snr;
++	s16 adc_rssi;
++} __packed;
++
++struct rx_rssi_info {
++	s8 ib_rssi;
++	s8 wb_rssi;
++	u8 rsv[2];
++} __packed;
++
++struct rx_user_info {
++	s32 freq_offset;
++	s32 snr;
++	__le32 fcs_err_count;
++} __packed;
++
++struct rx_user_info_ext {
++	s8 ne_var_db_all_user;
++	u8 rsv[3];
++} __packed;
++
++#define MAX_ANTENNA_NUM		8
++#define MAX_USER_NUM		16
++
++struct mt7996_tm_rx_event_stat_all {
++	__le16 tag;
++	__le16 len;
++
++	struct rx_band_info band_info;
++	struct rx_band_info_ext band_info_ext;
++	struct rx_common_info common_info;
++	struct rx_common_info_ext common_info_ext;
++
++	/* RXV info */
++	struct rx_rxv_info rxv_info[MAX_ANTENNA_NUM];
++
++	/* RSSI info */
++	struct rx_rssi_info fagc[MAX_ANTENNA_NUM];
++	struct rx_rssi_info inst[MAX_ANTENNA_NUM];
++
++	/* User info */
++	struct rx_user_info user_info[MAX_USER_NUM];
++	struct rx_user_info_ext user_info_ext[MAX_USER_NUM];
++} __packed;
++
++struct mt7996_tm_rx_event {
++	u8 _rsv[4];
++
++	union {
++		struct mt7996_tm_rx_event_stat_all rx_stat_all;
++	};
++} __packed;
++
++#endif
+diff --git a/testmode.c b/testmode.c
+index 37783160a..44f3a5bfc 100644
+--- a/testmode.c
++++ b/testmode.c
+@@ -2,11 +2,13 @@
+ /* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> */
+ 
+ #include <linux/random.h>
++#include "mt76_connac.h"
+ #include "mt76.h"
+ 
+ const struct nla_policy mt76_tm_policy[NUM_MT76_TM_ATTRS] = {
+ 	[MT76_TM_ATTR_RESET] = { .type = NLA_FLAG },
+ 	[MT76_TM_ATTR_STATE] = { .type = NLA_U8 },
++	[MT76_TM_ATTR_SKU_EN] = { .type = NLA_U8 },
+ 	[MT76_TM_ATTR_TX_COUNT] = { .type = NLA_U32 },
+ 	[MT76_TM_ATTR_TX_LENGTH] = { .type = NLA_U32 },
+ 	[MT76_TM_ATTR_TX_RATE_MODE] = { .type = NLA_U8 },
+@@ -82,6 +84,11 @@ mt76_testmode_max_mpdu_len(struct mt76_phy *phy, u8 tx_rate_mode)
+ 		    IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991)
+ 			return IEEE80211_MAX_MPDU_LEN_VHT_7991;
+ 		return IEEE80211_MAX_MPDU_LEN_VHT_11454;
++	case MT76_TM_TX_MODE_EHT_SU:
++	case MT76_TM_TX_MODE_EHT_TRIG:
++	case MT76_TM_TX_MODE_EHT_MU:
++		/* TODO: check the limit */
++		return UINT_MAX;
+ 	case MT76_TM_TX_MODE_CCK:
+ 	case MT76_TM_TX_MODE_OFDM:
+ 	default:
+@@ -183,6 +190,9 @@ mt76_testmode_tx_init(struct mt76_phy *phy)
+ 	u8 max_nss = hweight8(phy->antenna_mask);
+ 	int ret;
+ 
++	if (is_mt7996(phy->dev))
++		return 0;
++
+ 	ret = mt76_testmode_alloc_skb(phy, td->tx_mpdu_len);
+ 	if (ret)
+ 		return ret;
+@@ -275,7 +285,9 @@ mt76_testmode_tx_start(struct mt76_phy *phy)
+ 	td->tx_queued = 0;
+ 	td->tx_done = 0;
+ 	td->tx_pending = td->tx_count;
+-	mt76_worker_schedule(&dev->tx_worker);
++
++	if (!is_mt7996(dev))
++		mt76_worker_schedule(&dev->tx_worker);
+ }
+ 
+ static void
+@@ -284,6 +296,11 @@ mt76_testmode_tx_stop(struct mt76_phy *phy)
+ 	struct mt76_testmode_data *td = &phy->test;
+ 	struct mt76_dev *dev = phy->dev;
+ 
++	if (is_mt7996(dev) && dev->test_ops->tx_stop) {
++		dev->test_ops->tx_stop(phy);
++		return;
++	}
++
+ 	mt76_worker_disable(&dev->tx_worker);
+ 
+ 	td->tx_pending = 0;
+@@ -296,22 +313,11 @@ mt76_testmode_tx_stop(struct mt76_phy *phy)
+ 	mt76_testmode_free_skb(phy);
+ }
+ 
+-static inline void
+-mt76_testmode_param_set(struct mt76_testmode_data *td, u16 idx)
+-{
+-	td->param_set[idx / 32] |= BIT(idx % 32);
+-}
+-
+-static inline bool
+-mt76_testmode_param_present(struct mt76_testmode_data *td, u16 idx)
+-{
+-	return td->param_set[idx / 32] & BIT(idx % 32);
+-}
+-
+ static void
+ mt76_testmode_init_defaults(struct mt76_phy *phy)
+ {
+ 	struct mt76_testmode_data *td = &phy->test;
++	u8 addr[ETH_ALEN] = {phy->band_idx, 0x11, 0x22, 0xaa, 0xbb, 0xcc};
+ 
+ 	if (td->tx_mpdu_len > 0)
+ 		return;
+@@ -319,11 +325,18 @@ mt76_testmode_init_defaults(struct mt76_phy *phy)
+ 	td->tx_mpdu_len = 1024;
+ 	td->tx_count = 1;
+ 	td->tx_rate_mode = MT76_TM_TX_MODE_OFDM;
++	td->tx_rate_idx = 7;
+ 	td->tx_rate_nss = 1;
++	/* 0xffff for OFDMA no puncture */
++	td->tx_preamble_puncture = ~(td->tx_preamble_puncture & 0);
++	td->tx_ipg = 50;
+ 
+-	memcpy(td->addr[0], phy->macaddr, ETH_ALEN);
+-	memcpy(td->addr[1], phy->macaddr, ETH_ALEN);
+-	memcpy(td->addr[2], phy->macaddr, ETH_ALEN);
++	/* rx stat user config */
++	td->aid = 1;
++
++	memcpy(td->addr[0], addr, ETH_ALEN);
++	memcpy(td->addr[1], addr, ETH_ALEN);
++	memcpy(td->addr[2], addr, ETH_ALEN);
+ }
+ 
+ static int
+@@ -353,7 +366,7 @@ __mt76_testmode_set_state(struct mt76_phy *phy, enum mt76_testmode_state state)
+ 	if (state == MT76_TM_STATE_TX_FRAMES)
+ 		mt76_testmode_tx_start(phy);
+ 	else if (state == MT76_TM_STATE_RX_FRAMES) {
+-		memset(&phy->test.rx_stats, 0, sizeof(phy->test.rx_stats));
++		dev->test_ops->reset_rx_stats(phy);
+ 	}
+ 
+ 	phy->test.state = state;
+@@ -404,6 +417,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)
+ {
+@@ -427,6 +478,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));
+@@ -434,6 +490,9 @@ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 
+ 	mt76_testmode_init_defaults(phy);
+ 
++	if (tb[MT76_TM_ATTR_SKU_EN])
++		td->sku_en = nla_get_u8(tb[MT76_TM_ATTR_SKU_EN]);
++
+ 	if (tb[MT76_TM_ATTR_TX_COUNT])
+ 		td->tx_count = nla_get_u32(tb[MT76_TM_ATTR_TX_COUNT]);
+ 
+@@ -454,7 +513,8 @@ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	    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))
+ 		goto out;
+ 
+ 	if (tb[MT76_TM_ATTR_TX_LENGTH]) {
+@@ -494,7 +554,9 @@ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 			    idx >= ARRAY_SIZE(td->tx_power))
+ 				goto out;
+ 
+-			td->tx_power[idx++] = nla_get_u8(cur);
++			err = mt76_tm_get_u8(cur, &td->tx_power[idx++], 0, 63);
++			if (err)
++				return err;
+ 		}
+ 	}
+ 
+@@ -512,6 +574,22 @@ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 		}
+ 	}
+ 
++	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 (dev->test_ops->set_params) {
+ 		err = dev->test_ops->set_params(phy, tb, state);
+ 		if (err)
+@@ -561,6 +639,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;
+ 
+@@ -625,6 +706,8 @@ int mt76_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *msg,
+ 	    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) ||
++	    nla_put_u8(msg, MT76_TM_ATTR_SKU_EN, td->sku_en) ||
++	    nla_put_u8(msg, MT76_TM_ATTR_AID, td->aid) ||
+ 	    (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_LTF) &&
+ 	     nla_put_u8(msg, MT76_TM_ATTR_TX_LTF, td->tx_ltf)) ||
+ 	    (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_ANTENNA) &&
+@@ -640,7 +723,7 @@ int mt76_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *msg,
+ 	    (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_POWER_CONTROL) &&
+ 	     nla_put_u8(msg, MT76_TM_ATTR_TX_POWER_CONTROL, td->tx_power_control)) ||
+ 	    (mt76_testmode_param_present(td, MT76_TM_ATTR_FREQ_OFFSET) &&
+-	     nla_put_u8(msg, MT76_TM_ATTR_FREQ_OFFSET, td->freq_offset)))
++	     nla_put_u32(msg, MT76_TM_ATTR_FREQ_OFFSET, td->freq_offset)))
+ 		goto out;
+ 
+ 	if (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_POWER)) {
+diff --git a/testmode.h b/testmode.h
+index a40cd74b4..96872e8cd 100644
+--- a/testmode.h
++++ b/testmode.h
+@@ -5,7 +5,8 @@
+ #ifndef __MT76_TESTMODE_H
+ #define __MT76_TESTMODE_H
+ 
+-#define MT76_TM_TIMEOUT	10
++#define MT76_TM_TIMEOUT			10
++#define MT76_TM_EEPROM_BLOCK_SIZE	16
+ 
+ /**
+  * enum mt76_testmode_attr - testmode attributes inside NL80211_ATTR_TESTDATA
+@@ -19,6 +20,7 @@
+  * @MT76_TM_ATTR_MTD_OFFSET: offset of eeprom data within the partition (u32)
+  * @MT76_TM_ATTR_BAND_IDX: band idx of the chip (u8)
+  *
++ * @MT76_TM_ATTR_SKU_EN: config txpower sku is enabled or disabled in testmode (u8)
+  * @MT76_TM_ATTR_TX_COUNT: configured number of frames to send when setting
+  *	state to MT76_TM_STATE_TX_FRAMES (u32)
+  * @MT76_TM_ATTR_TX_PENDING: pending frames during MT76_TM_STATE_TX_FRAMES (u32)
+@@ -39,6 +41,11 @@
+  *
+  * @MT76_TM_ATTR_STATS: statistics (nested, see &enum mt76_testmode_stats_attr)
+  *
++ * @MT76_TM_ATTR_PRECAL: Pre-cal data (u8)
++ * @MT76_TM_ATTR_PRECAL_INFO: group size, dpd size, dpd_info, transmit size,
++ *                            eeprom cal indicator (u32),
++ *                            dpd_info = [dpd_per_chan_size, chan_num_2g,
++ *                                        chan_num_5g, chan_num_6g]
+  * @MT76_TM_ATTR_TX_SPE_IDX: tx spatial extension index (u8)
+  *
+  * @MT76_TM_ATTR_TX_DUTY_CYCLE: packet tx duty cycle (u8)
+@@ -48,6 +55,29 @@
+  * @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_TXBF_ACT: txbf setting actions (u8)
++ * @MT76_TM_ATTR_TXBF_PARAM: txbf parameters (nested)
++ *
++ * @MT76_TM_ATTR_OFF_CH_SCAN_CH: config the channel of background chain (ZWDFS) (u8)
++ * @MT76_TM_ATTR_OFF_CH_SCAN_CENTER_CH: config the center channel of background chain (ZWDFS) (u8)
++ * @MT76_TM_ATTR_OFF_CH_SCAN_BW: config the bandwidth of background chain (ZWDFS) (u8)
++ * @MT76_TM_ATTR_OFF_CH_SCAN_PATH: config the tx path of background chain (ZWDFS) (u8)
++ *
++ * @MT76_TM_ATTR_IPI_THRESHOLD: config the IPI index you want to read (u8)
++ * @MT76_TM_ATTR_IPI_PERIOD: config the time period for reading
++ *			     the histogram of specific IPI index (u8)
++ * @MT76_TM_ATTR_IPI_ANTENNA_INDEX: config the antenna index for reading
++ *				    the histogram of specific IPI index (u8)
++ * @MT76_TM_ATTR_IPI_RESET: Reset the IPI counter
++ *
+  */
+ enum mt76_testmode_attr {
+ 	MT76_TM_ATTR_UNSPEC,
+@@ -59,6 +89,7 @@ enum mt76_testmode_attr {
+ 	MT76_TM_ATTR_MTD_OFFSET,
+ 	MT76_TM_ATTR_BAND_IDX,
+ 
++	MT76_TM_ATTR_SKU_EN,
+ 	MT76_TM_ATTR_TX_COUNT,
+ 	MT76_TM_ATTR_TX_LENGTH,
+ 	MT76_TM_ATTR_TX_RATE_MODE,
+@@ -76,6 +107,8 @@ enum mt76_testmode_attr {
+ 	MT76_TM_ATTR_FREQ_OFFSET,
+ 
+ 	MT76_TM_ATTR_STATS,
++	MT76_TM_ATTR_PRECAL,
++	MT76_TM_ATTR_PRECAL_INFO,
+ 
+ 	MT76_TM_ATTR_TX_SPE_IDX,
+ 
+@@ -86,6 +119,27 @@ enum mt76_testmode_attr {
+ 	MT76_TM_ATTR_DRV_DATA,
+ 
+ 	MT76_TM_ATTR_MAC_ADDRS,
++	MT76_TM_ATTR_AID,
++	MT76_TM_ATTR_RU_ALLOC,
++	MT76_TM_ATTR_RU_IDX,
++
++	MT76_TM_ATTR_EEPROM_ACTION,
++	MT76_TM_ATTR_EEPROM_OFFSET,
++	MT76_TM_ATTR_EEPROM_VAL,
++
++	MT76_TM_ATTR_CFG,
++	MT76_TM_ATTR_TXBF_ACT,
++	MT76_TM_ATTR_TXBF_PARAM,
++
++	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_IPI_THRESHOLD,
++	MT76_TM_ATTR_IPI_PERIOD,
++	MT76_TM_ATTR_IPI_ANTENNA_INDEX,
++	MT76_TM_ATTR_IPI_RESET,
+ 
+ 	/* keep last */
+ 	NUM_MT76_TM_ATTRS,
+@@ -103,6 +157,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,
+@@ -115,6 +171,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,
+@@ -127,6 +184,7 @@ enum mt76_testmode_stats_attr {
+  *
+  * @MT76_TM_RX_ATTR_FREQ_OFFSET: frequency offset (s32)
+  * @MT76_TM_RX_ATTR_RCPI: received channel power indicator (array, u8)
++ * @MT76_TM_RX_ATTR_RSSI: received signal strength indicator (array, s8)
+  * @MT76_TM_RX_ATTR_IB_RSSI: internal inband RSSI (array, s8)
+  * @MT76_TM_RX_ATTR_WB_RSSI: internal wideband RSSI (array, s8)
+  * @MT76_TM_RX_ATTR_SNR: signal-to-noise ratio (u8)
+@@ -136,6 +194,7 @@ enum mt76_testmode_rx_attr {
+ 
+ 	MT76_TM_RX_ATTR_FREQ_OFFSET,
+ 	MT76_TM_RX_ATTR_RCPI,
++	MT76_TM_RX_ATTR_RSSI,
+ 	MT76_TM_RX_ATTR_IB_RSSI,
+ 	MT76_TM_RX_ATTR_WB_RSSI,
+ 	MT76_TM_RX_ATTR_SNR,
+@@ -179,6 +238,9 @@ enum mt76_testmode_state {
+  * @MT76_TM_TX_MODE_HE_EXT_SU: 802.11ax extended-range SU
+  * @MT76_TM_TX_MODE_HE_TB: 802.11ax trigger-based
+  * @MT76_TM_TX_MODE_HE_MU: 802.11ax multi-user MIMO
++ * @MT76_TM_TX_MODE_EHT_SU: 802.11be single-user MIMO
++ * @MT76_TM_TX_MODE_EHT_TRIG: 802.11be trigger-based
++ * @MT76_TM_TX_MODE_EHT_MU: 802.11be multi-user MIMO
+  */
+ enum mt76_testmode_tx_mode {
+ 	MT76_TM_TX_MODE_CCK,
+@@ -189,12 +251,33 @@ enum mt76_testmode_tx_mode {
+ 	MT76_TM_TX_MODE_HE_EXT_SU,
+ 	MT76_TM_TX_MODE_HE_TB,
+ 	MT76_TM_TX_MODE_HE_MU,
++	MT76_TM_TX_MODE_EHT_SU,
++	MT76_TM_TX_MODE_EHT_TRIG,
++	MT76_TM_TX_MODE_EHT_MU,
+ 
+ 	/* keep last */
+ 	NUM_MT76_TM_TX_MODES,
+ 	MT76_TM_TX_MODE_MAX = NUM_MT76_TM_TX_MODES - 1,
+ };
+ 
++/**
++ * 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,
++};
++
+ extern const struct nla_policy mt76_tm_policy[NUM_MT76_TM_ATTRS];
+ 
+ #endif
+diff --git a/tools/fields.c b/tools/fields.c
+index e3f690896..055f90f3c 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[] = {
+@@ -21,6 +22,9 @@ static const char * const testmode_tx_mode[] = {
+ 	[MT76_TM_TX_MODE_HE_EXT_SU] = "he_ext_su",
+ 	[MT76_TM_TX_MODE_HE_TB] = "he_tb",
+ 	[MT76_TM_TX_MODE_HE_MU] = "he_mu",
++	[MT76_TM_TX_MODE_EHT_SU] = "eht_su",
++	[MT76_TM_TX_MODE_EHT_TRIG] = "eht_tb",
++	[MT76_TM_TX_MODE_EHT_MU] = "eht_mu",
+ };
+ 
+ static void print_enum(const struct tm_field *field, struct nlattr *attr)
+@@ -65,7 +69,7 @@ static bool parse_u8(const struct tm_field *field, int idx,
+ 
+ static void print_u8(const struct tm_field *field, struct nlattr *attr)
+ {
+-	printf("%d", nla_get_u8(attr));
++	printf("%u", nla_get_u8(attr));
+ }
+ 
+ static void print_s8(const struct tm_field *field, struct nlattr *attr)
+@@ -86,12 +90,12 @@ static void print_s32(const struct tm_field *field, struct nlattr *attr)
+ 
+ static void print_u32(const struct tm_field *field, struct nlattr *attr)
+ {
+-	printf("%d", nla_get_u32(attr));
++	printf("%u", nla_get_u32(attr));
+ }
+ 
+ static void print_u64(const struct tm_field *field, struct nlattr *attr)
+ {
+-	printf("%lld", (unsigned long long)nla_get_u64(attr));
++	printf("%llu", (unsigned long long)nla_get_u64(attr));
+ }
+ 
+ static bool parse_flag(const struct tm_field *field, int idx,
+@@ -201,6 +205,62 @@ 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;
++
++	str = strdup(val);
++	ap = str;
++
++	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]));
++}
+ 
+ #define FIELD_GENERIC(_field, _name, ...)	\
+ 	[FIELD_NAME(_field)] = {			\
+@@ -250,10 +310,18 @@ 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"),
+ 	FIELD_ARRAY_RO(u8, RCPI, "rcpi"),
++	FIELD_ARRAY_RO(s8, RSSI, "rssi"),
+ 	FIELD_ARRAY_RO(s8, IB_RSSI, "ib_rssi"),
+ 	FIELD_ARRAY_RO(s8, WB_RSSI, "wb_rssi"),
+ 	FIELD_RO(s8, SNR, "snr"),
+@@ -261,6 +329,7 @@ static const struct tm_field rx_fields[NUM_MT76_TM_RX_ATTRS] = {
+ static struct nla_policy rx_policy[NUM_MT76_TM_RX_ATTRS] = {
+ 	[MT76_TM_RX_ATTR_FREQ_OFFSET] = { .type = NLA_U32 },
+ 	[MT76_TM_RX_ATTR_RCPI] = { .type = NLA_NESTED },
++	[MT76_TM_RX_ATTR_RSSI] = { .type = NLA_NESTED },
+ 	[MT76_TM_RX_ATTR_IB_RSSI] = { .type = NLA_NESTED },
+ 	[MT76_TM_RX_ATTR_WB_RSSI] = { .type = NLA_NESTED },
+ 	[MT76_TM_RX_ATTR_SNR] = { .type = NLA_U8 },
+@@ -274,6 +343,7 @@ static const struct tm_field stats_fields[NUM_MT76_TM_STATS_ATTRS] = {
+ 	FIELD_RO(u32, TX_DONE, "tx_done"),
+ 	FIELD_RO(u64, RX_PACKETS, "rx_packets"),
+ 	FIELD_RO(u64, RX_FCS_ERROR, "rx_fcs_error"),
++	FIELD_RO(u64, RX_LEN_MISMATCH, "rx_len_mismatch"),
+ 	FIELD_NESTED_RO(LAST_RX, rx, "last_"),
+ };
+ static struct nla_policy stats_policy[NUM_MT76_TM_STATS_ATTRS] = {
+@@ -282,6 +352,7 @@ static struct nla_policy stats_policy[NUM_MT76_TM_STATS_ATTRS] = {
+ 	[MT76_TM_STATS_ATTR_TX_DONE] = { .type = NLA_U32 },
+ 	[MT76_TM_STATS_ATTR_RX_PACKETS] = { .type = NLA_U64 },
+ 	[MT76_TM_STATS_ATTR_RX_FCS_ERROR] = { .type = NLA_U64 },
++	[MT76_TM_STATS_ATTR_RX_LEN_MISMATCH] = { .type = NLA_U64 },
+ };
+ #undef FIELD_NAME
+ 
+@@ -291,6 +362,7 @@ static const struct tm_field testdata_fields[NUM_MT76_TM_ATTRS] = {
+ 	FIELD_ENUM(STATE, "state", testmode_state),
+ 	FIELD_RO(string, MTD_PART, "mtd_part"),
+ 	FIELD_RO(u32, MTD_OFFSET, "mtd_offset"),
++	FIELD(u8, SKU_EN, "sku_en"),
+ 	FIELD(u32, TX_COUNT, "tx_count"),
+ 	FIELD(u32, TX_LENGTH, "tx_length"),
+ 	FIELD_ENUM(TX_RATE_MODE, "tx_rate_mode", testmode_tx_mode),
+@@ -300,12 +372,20 @@ 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(u32, FREQ_OFFSET, "freq_offset"),
++	FIELD(u8, AID, "aid"),
++	FIELD(u8, RU_ALLOC, "ru_alloc"),
++	FIELD(u8, RU_IDX, "ru_idx"),
++	FIELD_MAC(MAC_ADDRS, "mac_addrs"),
+ 	FIELD_NESTED_RO(STATS, stats, "",
+ 			.print_extra = print_extra_stats),
++
+ };
+ #undef FIELD_NAME
+ 
+@@ -313,6 +393,7 @@ static struct nla_policy testdata_policy[NUM_MT76_TM_ATTRS] = {
+ 	[MT76_TM_ATTR_STATE] = { .type = NLA_U8 },
+ 	[MT76_TM_ATTR_MTD_PART] = { .type = NLA_STRING },
+ 	[MT76_TM_ATTR_MTD_OFFSET] = { .type = NLA_U32 },
++	[MT76_TM_ATTR_SKU_EN] = { .type = NLA_U8 },
+ 	[MT76_TM_ATTR_TX_COUNT] = { .type = NLA_U32 },
+ 	[MT76_TM_ATTR_TX_LENGTH] = { .type = NLA_U32 },
+ 	[MT76_TM_ATTR_TX_RATE_MODE] = { .type = NLA_U8 },
+@@ -322,10 +403,25 @@ 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_RU_ALLOC] = { .type = NLA_U8 },
++	[MT76_TM_ATTR_RU_IDX] = { .type = NLA_U8 },
+ 	[MT76_TM_ATTR_STATS] = { .type = NLA_NESTED },
++	[MT76_TM_ATTR_TXBF_ACT] = { .type = NLA_U8 },
++	[MT76_TM_ATTR_OFF_CH_SCAN_CH] = { .type = NLA_U8 },
++	[MT76_TM_ATTR_OFF_CH_SCAN_CENTER_CH] = { .type = NLA_U8 },
++	[MT76_TM_ATTR_OFF_CH_SCAN_BW] = { .type = NLA_U8 },
++	[MT76_TM_ATTR_IPI_THRESHOLD] = { .type = NLA_U8 },
++	[MT76_TM_ATTR_IPI_PERIOD] = { .type = NLA_U32 },
++	[MT76_TM_ATTR_IPI_ANTENNA_INDEX] = { .type = NLA_U8 },
++	[MT76_TM_ATTR_IPI_RESET] = { .type = NLA_U8 },
+ };
+ 
+ const struct tm_field msg_field = {
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0029-mtk-wifi-mt76-testmode-add-testmode-pre-calibration-.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0029-mtk-wifi-mt76-testmode-add-testmode-pre-calibration-.patch
new file mode 100644
index 0000000..a01192e
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0029-mtk-wifi-mt76-testmode-add-testmode-pre-calibration-.patch
@@ -0,0 +1,900 @@
+From 1f004fd49f96e00013b33a5b316d488d8efd077e Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Fri, 31 Mar 2023 11:27:24 +0800
+Subject: [PATCH 029/120] mtk: wifi: mt76: testmode: add testmode
+ pre-calibration support
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Change-Id: If8a6cc02fa20e35f079c826e0571e8c04c3f9c7e
+---
+ mac80211.c        |  21 ---
+ mt76.h            |  22 +++
+ mt76_connac_mcu.h |   2 +
+ mt7996/eeprom.c   |  66 +++++++
+ mt7996/eeprom.h   |  47 +++++
+ mt7996/mcu.c      |   5 +
+ mt7996/mt7996.h   |   7 +
+ mt7996/testmode.c | 437 ++++++++++++++++++++++++++++++++++++++++++++++
+ mt7996/testmode.h |  20 ++-
+ testmode.c        |  12 ++
+ testmode.h        |   8 +
+ tools/fields.c    |   8 +
+ 12 files changed, 632 insertions(+), 23 deletions(-)
+
+diff --git a/mac80211.c b/mac80211.c
+index 94824355a..dbab04031 100644
+--- a/mac80211.c
++++ b/mac80211.c
+@@ -7,27 +7,6 @@
+ #include <net/page_pool.h>
+ #include "mt76.h"
+ 
+-#define CHAN2G(_idx, _freq) {			\
+-	.band = NL80211_BAND_2GHZ,		\
+-	.center_freq = (_freq),			\
+-	.hw_value = (_idx),			\
+-	.max_power = 30,			\
+-}
+-
+-#define CHAN5G(_idx, _freq) {			\
+-	.band = NL80211_BAND_5GHZ,		\
+-	.center_freq = (_freq),			\
+-	.hw_value = (_idx),			\
+-	.max_power = 30,			\
+-}
+-
+-#define CHAN6G(_idx, _freq) {			\
+-	.band = NL80211_BAND_6GHZ,		\
+-	.center_freq = (_freq),			\
+-	.hw_value = (_idx),			\
+-	.max_power = 30,			\
+-}
+-
+ static const struct ieee80211_channel mt76_channels_2ghz[] = {
+ 	CHAN2G(1, 2412),
+ 	CHAN2G(2, 2417),
+diff --git a/mt76.h b/mt76.h
+index f2b1e0c23..14c5fcb1e 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -18,6 +18,27 @@
+ #include "util.h"
+ #include "testmode.h"
+ 
++#define CHAN2G(_idx, _freq) {			\
++	.band = NL80211_BAND_2GHZ,		\
++	.center_freq = (_freq),			\
++	.hw_value = (_idx),			\
++	.max_power = 30,			\
++}
++
++#define CHAN5G(_idx, _freq) {			\
++	.band = NL80211_BAND_5GHZ,		\
++	.center_freq = (_freq),			\
++	.hw_value = (_idx),			\
++	.max_power = 30,			\
++}
++
++#define CHAN6G(_idx, _freq) {			\
++	.band = NL80211_BAND_6GHZ,		\
++	.center_freq = (_freq),			\
++	.hw_value = (_idx),			\
++	.max_power = 30,			\
++}
++
+ #define MT_MCU_RING_SIZE	32
+ #define MT_RX_BUF_SIZE		2048
+ #define MT_SKB_HEAD_LEN		256
+@@ -698,6 +719,7 @@ struct mt76_testmode_ops {
+ 	void (*reset_rx_stats)(struct mt76_phy *phy);
+ 	void (*tx_stop)(struct mt76_phy *phy);
+ 	int (*set_eeprom)(struct mt76_phy *phy, u32 offset, u8 *val, u8 action);
++	int (*dump_precal)(struct mt76_phy *mphy, struct sk_buff *msg, int flag, int type);
+ };
+ 
+ #define MT_TM_FW_RX_COUNT	BIT(0)
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 718552baf..f5ea719e4 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1042,8 +1042,10 @@ enum {
+ 	MCU_UNI_EVENT_RDD_REPORT = 0x11,
+ 	MCU_UNI_EVENT_ROC = 0x27,
+ 	MCU_UNI_EVENT_TX_DONE = 0x2d,
++	MCU_UNI_EVENT_BF = 0x33,
+ 	MCU_UNI_EVENT_THERMAL = 0x35,
+ 	MCU_UNI_EVENT_NIC_CAPAB = 0x43,
++	MCU_UNI_EVENT_TESTMODE_CTRL = 0x46,
+ 	MCU_UNI_EVENT_WED_RRO = 0x57,
+ 	MCU_UNI_EVENT_PER_STA_INFO = 0x6d,
+ 	MCU_UNI_EVENT_ALL_STA_INFO = 0x6e,
+diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
+index f9b9ca25d..62c1ad489 100644
+--- a/mt7996/eeprom.c
++++ b/mt7996/eeprom.c
+@@ -12,6 +12,42 @@ static bool testmode_enable;
+ module_param(testmode_enable, bool, 0644);
+ MODULE_PARM_DESC(testmode_enable, "Enable testmode");
+ 
++const struct ieee80211_channel dpd_2g_ch_list_bw20[] = {
++	CHAN2G(3, 2422),
++	CHAN2G(7, 2442),
++	CHAN2G(11, 2462)
++};
++
++const struct ieee80211_channel dpd_5g_ch_list_bw160[] = {
++	CHAN5G(50, 5250),
++	CHAN5G(114, 5570),
++	CHAN5G(163, 5815)
++};
++
++const struct ieee80211_channel dpd_6g_ch_list_bw160[] = {
++	CHAN6G(15, 6025),
++	CHAN6G(47, 6185),
++	CHAN6G(79, 6345),
++	CHAN6G(111, 6505),
++	CHAN6G(143, 6665),
++	CHAN6G(175, 6825),
++	CHAN6G(207, 6985)
++};
++
++const struct ieee80211_channel dpd_6g_ch_list_bw320[] = {
++	CHAN6G(31, 6105),
++	CHAN6G(63, 6265),
++	CHAN6G(95, 6425),
++	CHAN6G(127, 6585),
++	CHAN6G(159, 6745),
++	CHAN6G(191, 6905)
++};
++
++const u32 dpd_2g_bw20_ch_num = ARRAY_SIZE(dpd_2g_ch_list_bw20);
++const u32 dpd_5g_bw160_ch_num = ARRAY_SIZE(dpd_5g_ch_list_bw160);
++const u32 dpd_6g_bw160_ch_num = ARRAY_SIZE(dpd_6g_ch_list_bw160);
++const u32 dpd_6g_bw320_ch_num = ARRAY_SIZE(dpd_6g_ch_list_bw320);
++
+ static int mt7996_check_eeprom(struct mt7996_dev *dev)
+ {
+ #define FEM_INT				0
+@@ -74,6 +110,36 @@ static char *mt7996_eeprom_name(struct mt7996_dev *dev)
+ 	}
+ }
+ 
++int
++mt7996_get_dpd_per_band_size(struct mt7996_dev *dev, enum nl80211_band band)
++{
++	/* handle different sku */
++	static const u8 band_to_idx[] = {
++		[NL80211_BAND_2GHZ] = MT_BAND0,
++		[NL80211_BAND_5GHZ] = MT_BAND1,
++		[NL80211_BAND_6GHZ] = MT_BAND2,
++	};
++	struct mt7996_phy *phy = __mt7996_phy(dev, band_to_idx[band]);
++	struct mt76_phy *mphy;
++	int dpd_size;
++
++	if (!phy)
++		return 0;
++
++	mphy = phy->mt76;
++
++	if (band == NL80211_BAND_2GHZ)
++		dpd_size = dpd_2g_bw20_ch_num * DPD_PER_CH_BW20_SIZE;
++	else if (band == NL80211_BAND_5GHZ)
++		dpd_size = mphy->sband_5g.sband.n_channels * DPD_PER_CH_BW20_SIZE +
++			   dpd_5g_bw160_ch_num * DPD_PER_CH_GT_BW20_SIZE;
++	else
++		dpd_size = mphy->sband_6g.sband.n_channels * DPD_PER_CH_BW20_SIZE +
++			   (dpd_6g_bw160_ch_num + dpd_6g_bw320_ch_num) * DPD_PER_CH_GT_BW20_SIZE;
++
++	return dpd_size;
++}
++
+ static int
+ mt7996_eeprom_load_default(struct mt7996_dev *dev)
+ {
+diff --git a/mt7996/eeprom.h b/mt7996/eeprom.h
+index de3ff4e27..849b8bcab 100644
+--- a/mt7996/eeprom.h
++++ b/mt7996/eeprom.h
+@@ -14,6 +14,7 @@ enum mt7996_eeprom_field {
+ 	MT_EE_MAC_ADDR =	0x004,
+ 	MT_EE_MAC_ADDR2 =	0x00a,
+ 	MT_EE_WIFI_CONF =	0x190,
++	MT_EE_DO_PRE_CAL =	0x1a5,
+ 	MT_EE_TESTMODE_EN =	0x1af,
+ 	MT_EE_MAC_ADDR3 =	0x2c0,
+ 	MT_EE_RATE_DELTA_2G =	0x1400,
+@@ -32,6 +33,52 @@ enum mt7996_eeprom_field {
+ #define MT_EE_WIFI_CONF2_BAND_SEL		GENMASK(2, 0)
+ #define MT_EE_WIFI_PA_LNA_CONFIG		GENMASK(1, 0)
+ 
++#define MT_EE_WIFI_CAL_GROUP_2G			BIT(0)
++#define MT_EE_WIFI_CAL_GROUP_5G			BIT(1)
++#define MT_EE_WIFI_CAL_GROUP_6G			BIT(2)
++#define MT_EE_WIFI_CAL_GROUP			GENMASK(2, 0)
++#define MT_EE_WIFI_CAL_DPD_2G			BIT(3)
++#define MT_EE_WIFI_CAL_DPD_5G			BIT(4)
++#define MT_EE_WIFI_CAL_DPD_6G			BIT(5)
++#define MT_EE_WIFI_CAL_DPD			GENMASK(5, 3)
++
++#define MT_EE_CAL_UNIT				1024
++#define MT_EE_CAL_GROUP_SIZE_2G			(4 * MT_EE_CAL_UNIT)
++#define MT_EE_CAL_GROUP_SIZE_5G			(45 * MT_EE_CAL_UNIT)
++#define MT_EE_CAL_GROUP_SIZE_6G			(125 * MT_EE_CAL_UNIT)
++#define MT_EE_CAL_ADCDCOC_SIZE_2G		(4 * 4)
++#define MT_EE_CAL_ADCDCOC_SIZE_5G		(4 * 4)
++#define MT_EE_CAL_ADCDCOC_SIZE_6G		(4 * 5)
++#define MT_EE_CAL_GROUP_SIZE			(MT_EE_CAL_GROUP_SIZE_2G + \
++						 MT_EE_CAL_GROUP_SIZE_5G + \
++						 MT_EE_CAL_GROUP_SIZE_6G + \
++						 MT_EE_CAL_ADCDCOC_SIZE_2G + \
++						 MT_EE_CAL_ADCDCOC_SIZE_5G + \
++						 MT_EE_CAL_ADCDCOC_SIZE_6G)
++
++#define DPD_PER_CH_LEGACY_SIZE			(4 * MT_EE_CAL_UNIT)
++#define DPD_PER_CH_MEM_SIZE			(13 * MT_EE_CAL_UNIT)
++#define DPD_PER_CH_OTFG0_SIZE			(2 * MT_EE_CAL_UNIT)
++#define DPD_PER_CH_BW20_SIZE			(DPD_PER_CH_LEGACY_SIZE + DPD_PER_CH_OTFG0_SIZE)
++#define DPD_PER_CH_GT_BW20_SIZE			(DPD_PER_CH_MEM_SIZE + DPD_PER_CH_OTFG0_SIZE)
++#define MT_EE_CAL_DPD_SIZE			(780 * MT_EE_CAL_UNIT)
++
++extern const struct ieee80211_channel dpd_2g_ch_list_bw20[];
++extern const u32 dpd_2g_bw20_ch_num;
++extern const struct ieee80211_channel dpd_5g_ch_list_bw160[];
++extern const u32 dpd_5g_bw160_ch_num;
++extern const struct ieee80211_channel dpd_6g_ch_list_bw160[];
++extern const u32 dpd_6g_bw160_ch_num;
++extern const struct ieee80211_channel dpd_6g_ch_list_bw320[];
++extern const u32 dpd_6g_bw320_ch_num;
++
++#define RF_DPD_FLAT_CAL				BIT(28)
++#define RF_PRE_CAL				BIT(29)
++#define RF_DPD_FLAT_5G_CAL			GENMASK(29, 28)
++#define RF_DPD_FLAT_5G_MEM_CAL			(BIT(30) | BIT(28))
++#define RF_DPD_FLAT_6G_CAL			GENMASK(30, 28)
++#define RF_DPD_FLAT_6G_MEM_CAL			(BIT(31) | BIT(28))
++
+ #define MT_EE_WIFI_CONF1_TX_PATH_BAND0		GENMASK(5, 3)
+ #define MT_EE_WIFI_CONF2_TX_PATH_BAND1		GENMASK(2, 0)
+ #define MT_EE_WIFI_CONF2_TX_PATH_BAND2		GENMASK(5, 3)
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index c7bede6b9..ef9042a88 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -715,6 +715,11 @@ mt7996_mcu_uni_rx_unsolicited_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 	case MCU_UNI_EVENT_WED_RRO:
+ 		mt7996_mcu_wed_rro_event(dev, skb);
+ 		break;
++#ifdef CONFIG_NL80211_TESTMODE
++	case MCU_UNI_EVENT_TESTMODE_CTRL:
++		mt7996_tm_rf_test_event(dev, skb);
++		break;
++#endif
+ 	default:
+ 		break;
+ 	}
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 72a200fcf..776abddb1 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -385,6 +385,9 @@ struct mt7996_dev {
+ 	struct dentry *debugfs_dir;
+ 	struct rchan *relay_fwlog;
+ 
++	void *cal;
++	u32 cur_prek_offset;
++
+ 	struct {
+ 		u16 table_mask;
+ 		u8 n_agrt;
+@@ -505,6 +508,7 @@ int mt7996_eeprom_parse_hw_cap(struct mt7996_dev *dev, struct mt7996_phy *phy);
+ int mt7996_eeprom_get_target_power(struct mt7996_dev *dev,
+ 				   struct ieee80211_channel *chan);
+ s8 mt7996_eeprom_get_power_delta(struct mt7996_dev *dev, int band);
++int mt7996_get_dpd_per_band_size(struct mt7996_dev *dev, enum nl80211_band band);
+ int mt7996_dma_init(struct mt7996_dev *dev);
+ void mt7996_dma_reset(struct mt7996_dev *dev, bool force);
+ void mt7996_dma_prefetch(struct mt7996_dev *dev);
+@@ -589,6 +593,9 @@ void mt7996_mcu_exit(struct mt7996_dev *dev);
+ int mt7996_mcu_get_all_sta_info(struct mt7996_phy *phy, u16 tag);
+ int mt7996_mcu_wed_rro_reset_sessions(struct mt7996_dev *dev, u16 id);
+ int mt7996_mcu_set_tx_power_ctrl(struct mt7996_phy *phy, u8 power_ctrl_id, u8 data);
++#ifdef CONFIG_NL80211_TESTMODE
++void mt7996_tm_rf_test_event(struct mt7996_dev *dev, struct sk_buff *skb);
++#endif
+ 
+ static inline u8 mt7996_max_interface_num(struct mt7996_dev *dev)
+ {
+diff --git a/mt7996/testmode.c b/mt7996/testmode.c
+index 98eebceed..a756ee10d 100644
+--- a/mt7996/testmode.c
++++ b/mt7996/testmode.c
+@@ -7,6 +7,8 @@
+ #include "mac.h"
+ #include "mcu.h"
+ #include "testmode.h"
++#include "eeprom.h"
++#include "mtk_mcu.h"
+ 
+ enum {
+ 	TM_CHANGED_TXPOWER,
+@@ -397,6 +399,436 @@ mt7996_tm_set_tx_cont(struct mt7996_phy *phy, bool en)
+ 	}
+ }
+ 
++static int
++mt7996_tm_group_prek(struct mt7996_phy *phy, enum mt76_testmode_state state)
++{
++	u8 *eeprom;
++	u32 i, group_size, dpd_size, size, offs, *pre_cal;
++	int ret = 0;
++	struct mt7996_dev *dev = phy->dev;
++	struct mt76_dev *mdev = &dev->mt76;
++	struct mt7996_tm_req req = {
++		.rf_test = {
++			.tag = cpu_to_le16(UNI_RF_TEST_CTRL),
++			.len = cpu_to_le16(sizeof(req.rf_test)),
++			.action = RF_ACTION_IN_RF_TEST,
++			.icap_len = RF_TEST_ICAP_LEN,
++			.op.rf.func_idx = cpu_to_le32(RF_TEST_RE_CAL),
++			.op.rf.param.cal_param.func_data = cpu_to_le32(RF_PRE_CAL),
++			.op.rf.param.cal_param.band_idx = phy->mt76->band_idx,
++		},
++	};
++
++	if (!dev->flash_mode) {
++		dev_err(dev->mt76.dev, "Currently not in FLASH or BIN FILE mode, return!\n");
++		return -EOPNOTSUPP;
++	}
++
++	eeprom = mdev->eeprom.data;
++	dev->cur_prek_offset = 0;
++	group_size = MT_EE_CAL_GROUP_SIZE;
++	dpd_size = MT_EE_CAL_DPD_SIZE;
++	size = group_size + dpd_size;
++	offs = MT_EE_DO_PRE_CAL;
++
++	switch (state) {
++	case MT76_TM_STATE_GROUP_PREK:
++		if (!dev->cal) {
++			dev->cal = devm_kzalloc(mdev->dev, size, GFP_KERNEL);
++			if (!dev->cal)
++				return -ENOMEM;
++		}
++
++		ret = mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(TESTMODE_CTRL), &req,
++					sizeof(req), false);
++		wait_event_timeout(mdev->mcu.wait, dev->cur_prek_offset == group_size,
++				   30 * HZ);
++
++		if (ret) {
++			dev_err(dev->mt76.dev, "Group Pre-cal: mcu send msg failed!\n");
++			return ret;
++		}
++
++		if (!ret)
++			eeprom[offs] |= MT_EE_WIFI_CAL_GROUP;
++		break;
++	case MT76_TM_STATE_GROUP_PREK_DUMP:
++		pre_cal = (u32 *)dev->cal;
++		if (!pre_cal) {
++			dev_info(dev->mt76.dev, "Not group pre-cal yet!\n");
++			return ret;
++		}
++		dev_info(dev->mt76.dev, "Group Pre-Cal:\n");
++		for (i = 0; i < (group_size / sizeof(u32)); i += 4) {
++			dev_info(dev->mt76.dev, "[0x%08lx] 0x%8x 0x%8x 0x%8x 0x%8x\n",
++				 i * sizeof(u32), pre_cal[i], pre_cal[i + 1],
++				 pre_cal[i + 2], pre_cal[i + 3]);
++		}
++		break;
++	case MT76_TM_STATE_GROUP_PREK_CLEAN:
++		pre_cal = (u32 *)dev->cal;
++		if (!pre_cal)
++			return ret;
++		memset(pre_cal, 0, group_size);
++		eeprom[offs] &= ~MT_EE_WIFI_CAL_GROUP;
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	return ret;
++}
++
++static int
++mt7996_tm_dpd_prek_send_req(struct mt7996_phy *phy, struct mt7996_tm_req *req,
++			    const struct ieee80211_channel *chan_list, u32 channel_size,
++			    enum nl80211_chan_width width, u32 func_data)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct mt76_phy *mphy = phy->mt76;
++	struct cfg80211_chan_def chandef_backup, *chandef = &mphy->chandef;
++	struct ieee80211_channel chan_backup;
++	int i, ret;
++
++	if (!chan_list)
++		return -EOPNOTSUPP;
++
++	req->rf_test.op.rf.param.cal_param.func_data = cpu_to_le32(func_data);
++
++	memcpy(&chan_backup, chandef->chan, sizeof(struct ieee80211_channel));
++	memcpy(&chandef_backup, chandef, sizeof(struct cfg80211_chan_def));
++
++	for (i = 0; i < channel_size; i++) {
++		memcpy(chandef->chan, &chan_list[i], sizeof(struct ieee80211_channel));
++		chandef->width = width;
++
++		/* set channel switch reason */
++		mphy->hw->conf.flags |= IEEE80211_CONF_OFFCHANNEL;
++		mt7996_mcu_set_chan_info(phy, UNI_CHANNEL_SWITCH);
++
++		ret = mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(TESTMODE_CTRL), req,
++					sizeof(*req), false);
++		if (ret) {
++			dev_err(dev->mt76.dev, "DPD Pre-cal: mcu send msg failed!\n");
++			goto out;
++		}
++	}
++
++out:
++	mphy->hw->conf.flags &= ~IEEE80211_CONF_OFFCHANNEL;
++	memcpy(chandef, &chandef_backup, sizeof(struct cfg80211_chan_def));
++	memcpy(chandef->chan, &chan_backup, sizeof(struct ieee80211_channel));
++	mt7996_mcu_set_chan_info(phy, UNI_CHANNEL_SWITCH);
++
++	return ret;
++}
++
++static int
++mt7996_tm_dpd_prek(struct mt7996_phy *phy, enum mt76_testmode_state state)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct mt76_dev *mdev = &dev->mt76;
++	struct mt76_phy *mphy = phy->mt76;
++	struct mt7996_tm_req req = {
++		.rf_test = {
++			.tag = cpu_to_le16(UNI_RF_TEST_CTRL),
++			.len = cpu_to_le16(sizeof(req.rf_test)),
++			.action = RF_ACTION_IN_RF_TEST,
++			.icap_len = RF_TEST_ICAP_LEN,
++			.op.rf.func_idx = cpu_to_le32(RF_TEST_RE_CAL),
++			.op.rf.param.cal_param.band_idx = phy->mt76->band_idx,
++		},
++	};
++	u32 i, j, group_size, dpd_size, size, offs, *pre_cal;
++	u32 wait_on_prek_offset = 0;
++	u8 do_precal, *eeprom;
++	int ret = 0;
++
++	if (!dev->flash_mode) {
++		dev_err(dev->mt76.dev, "Currently not in FLASH or BIN FILE mode, return!\n");
++		return -EOPNOTSUPP;
++	}
++
++	eeprom = mdev->eeprom.data;
++	dev->cur_prek_offset = 0;
++	group_size = MT_EE_CAL_GROUP_SIZE;
++	dpd_size = MT_EE_CAL_DPD_SIZE;
++	size = group_size + dpd_size;
++	offs = MT_EE_DO_PRE_CAL;
++
++	if (!dev->cal && state < MT76_TM_STATE_DPD_DUMP) {
++		dev->cal = devm_kzalloc(mdev->dev, size, GFP_KERNEL);
++		if (!dev->cal)
++			return -ENOMEM;
++	}
++
++	switch (state) {
++	case MT76_TM_STATE_DPD_2G:
++		ret = mt7996_tm_dpd_prek_send_req(phy, &req, dpd_2g_ch_list_bw20,
++						  dpd_2g_bw20_ch_num,
++						  NL80211_CHAN_WIDTH_20, RF_DPD_FLAT_CAL);
++		wait_on_prek_offset += dpd_2g_bw20_ch_num * DPD_PER_CH_BW20_SIZE;
++		wait_event_timeout(mdev->mcu.wait,
++				   dev->cur_prek_offset == wait_on_prek_offset, 30 * HZ);
++
++		do_precal = MT_EE_WIFI_CAL_DPD_2G;
++		break;
++	case MT76_TM_STATE_DPD_5G:
++		/* 5g channel bw20 calibration */
++		ret = mt7996_tm_dpd_prek_send_req(phy, &req, mphy->sband_5g.sband.channels,
++						  mphy->sband_5g.sband.n_channels,
++						  NL80211_CHAN_WIDTH_20, RF_DPD_FLAT_5G_CAL);
++		if (ret)
++			return ret;
++		wait_on_prek_offset += mphy->sband_5g.sband.n_channels * DPD_PER_CH_BW20_SIZE;
++		wait_event_timeout(mdev->mcu.wait,
++				   dev->cur_prek_offset == wait_on_prek_offset, 30 * HZ);
++
++		/* 5g channel bw160 calibration */
++		ret = mt7996_tm_dpd_prek_send_req(phy, &req, dpd_5g_ch_list_bw160,
++						  dpd_5g_bw160_ch_num,
++						  NL80211_CHAN_WIDTH_160, RF_DPD_FLAT_5G_MEM_CAL);
++		wait_on_prek_offset += dpd_5g_bw160_ch_num * DPD_PER_CH_GT_BW20_SIZE;
++		wait_event_timeout(mdev->mcu.wait,
++				   dev->cur_prek_offset == wait_on_prek_offset, 30 * HZ);
++
++		do_precal = MT_EE_WIFI_CAL_DPD_5G;
++		break;
++	case MT76_TM_STATE_DPD_6G:
++		/* 6g channel bw20 calibration */
++		ret = mt7996_tm_dpd_prek_send_req(phy, &req, mphy->sband_6g.sband.channels,
++						  mphy->sband_6g.sband.n_channels,
++						  NL80211_CHAN_WIDTH_20, RF_DPD_FLAT_6G_CAL);
++		if (ret)
++			return ret;
++		wait_on_prek_offset += mphy->sband_6g.sband.n_channels * DPD_PER_CH_BW20_SIZE;
++		wait_event_timeout(mdev->mcu.wait,
++				   dev->cur_prek_offset == wait_on_prek_offset, 30 * HZ);
++
++		/* 6g channel bw160 calibration */
++		ret = mt7996_tm_dpd_prek_send_req(phy, &req, dpd_6g_ch_list_bw160,
++						  dpd_6g_bw160_ch_num,
++						  NL80211_CHAN_WIDTH_160, RF_DPD_FLAT_6G_MEM_CAL);
++		if (ret)
++			return ret;
++		wait_on_prek_offset += dpd_6g_bw160_ch_num * DPD_PER_CH_GT_BW20_SIZE;
++		wait_event_timeout(mdev->mcu.wait,
++				   dev->cur_prek_offset == wait_on_prek_offset, 30 * HZ);
++
++		/* 6g channel bw320 calibration */
++		ret = mt7996_tm_dpd_prek_send_req(phy, &req, dpd_6g_ch_list_bw320,
++						  dpd_6g_bw320_ch_num,
++						  NL80211_CHAN_WIDTH_320, RF_DPD_FLAT_6G_MEM_CAL);
++		wait_on_prek_offset += dpd_6g_bw320_ch_num * DPD_PER_CH_GT_BW20_SIZE;
++		wait_event_timeout(mdev->mcu.wait,
++				   dev->cur_prek_offset == wait_on_prek_offset, 30 * HZ);
++
++		do_precal = MT_EE_WIFI_CAL_DPD_6G;
++		break;
++	case MT76_TM_STATE_DPD_DUMP:
++		if (!dev->cal) {
++			dev_info(dev->mt76.dev, "Not DPD pre-cal yet!\n");
++			return ret;
++		}
++		pre_cal = (u32 *)dev->cal;
++		dev_info(dev->mt76.dev, "DPD Pre-Cal:\n");
++		for (i = 0; i < dpd_size / sizeof(u32); i += 4) {
++			j = i + (group_size / sizeof(u32));
++			dev_info(dev->mt76.dev, "[0x%08lx] 0x%8x 0x%8x 0x%8x 0x%8x\n",
++				 j * sizeof(u32), pre_cal[j], pre_cal[j + 1],
++				 pre_cal[j + 2], pre_cal[j + 3]);
++		}
++		return 0;
++	case MT76_TM_STATE_DPD_CLEAN:
++		pre_cal = (u32 *)dev->cal;
++		if (!pre_cal)
++			return ret;
++		memset(pre_cal + (group_size / sizeof(u32)), 0, dpd_size);
++		do_precal = MT_EE_WIFI_CAL_DPD;
++		eeprom[offs] &= ~do_precal;
++		return 0;
++	default:
++		return -EINVAL;
++	}
++
++	if (!ret)
++		eeprom[offs] |= do_precal;
++
++	return ret;
++}
++
++static int
++mt7996_tm_dump_precal(struct mt76_phy *mphy, struct sk_buff *msg, int flag, int type)
++{
++#define DPD_PER_CHAN_SIZE_MASK		GENMASK(31, 30)
++#define DPD_2G_RATIO_MASK		GENMASK(29, 20)
++#define DPD_5G_RATIO_MASK		GENMASK(19, 10)
++#define DPD_6G_RATIO_MASK		GENMASK(9, 0)
++	struct mt7996_phy *phy = mphy->priv;
++	struct mt7996_dev *dev = phy->dev;
++	u32 i, group_size, dpd_size, total_size, size, dpd_info = 0;
++	u32 dpd_size_2g, dpd_size_5g, dpd_size_6g;
++	u32 base, offs, transmit_size = 1000;
++	u8 *pre_cal, *eeprom;
++	void *precal;
++	enum prek_ops {
++		PREK_GET_INFO,
++		PREK_SYNC_ALL,
++		PREK_SYNC_GROUP,
++		PREK_SYNC_DPD_2G,
++		PREK_SYNC_DPD_5G,
++		PREK_SYNC_DPD_6G,
++		PREK_CLEAN_GROUP,
++		PREK_CLEAN_DPD,
++	};
++
++	if (!dev->cal) {
++		dev_info(dev->mt76.dev, "Not pre-cal yet!\n");
++		return 0;
++	}
++
++	group_size = MT_EE_CAL_GROUP_SIZE;
++	dpd_size = MT_EE_CAL_DPD_SIZE;
++	total_size = group_size + dpd_size;
++	pre_cal = dev->cal;
++	eeprom = dev->mt76.eeprom.data;
++	offs = MT_EE_DO_PRE_CAL;
++
++	dpd_size_2g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_2GHZ);
++	dpd_size_5g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_5GHZ);
++	dpd_size_6g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_6GHZ);
++
++	switch (type) {
++	case PREK_SYNC_ALL:
++		base = 0;
++		size = total_size;
++		break;
++	case PREK_SYNC_GROUP:
++		base = 0;
++		size = group_size;
++		break;
++	case PREK_SYNC_DPD_2G:
++		base = group_size;
++		size = dpd_size_2g;
++		break;
++	case PREK_SYNC_DPD_5G:
++		base = group_size + dpd_size_2g;
++		size = dpd_size_5g;
++		break;
++	case PREK_SYNC_DPD_6G:
++		base = group_size + dpd_size_2g + dpd_size_5g;
++		size = dpd_size_6g;
++		break;
++	case PREK_GET_INFO:
++		break;
++	default:
++		return 0;
++	}
++
++	if (!flag) {
++		if (eeprom[offs] & MT_EE_WIFI_CAL_DPD) {
++			dpd_info |= u32_encode_bits(1, DPD_PER_CHAN_SIZE_MASK) |
++				    u32_encode_bits(dpd_size_2g / MT_EE_CAL_UNIT,
++						    DPD_2G_RATIO_MASK) |
++				    u32_encode_bits(dpd_size_5g / MT_EE_CAL_UNIT,
++						    DPD_5G_RATIO_MASK) |
++				    u32_encode_bits(dpd_size_6g / MT_EE_CAL_UNIT,
++						    DPD_6G_RATIO_MASK);
++		}
++		dev->cur_prek_offset = 0;
++		precal = nla_nest_start(msg, MT76_TM_ATTR_PRECAL_INFO);
++		if (!precal)
++			return -ENOMEM;
++		nla_put_u32(msg, 0, group_size);
++		nla_put_u32(msg, 1, dpd_size);
++		nla_put_u32(msg, 2, dpd_info);
++		nla_put_u32(msg, 3, transmit_size);
++		nla_put_u32(msg, 4, eeprom[offs]);
++		nla_nest_end(msg, precal);
++	} else {
++		precal = nla_nest_start(msg, MT76_TM_ATTR_PRECAL);
++		if (!precal)
++			return -ENOMEM;
++
++		transmit_size = (dev->cur_prek_offset + transmit_size < size) ?
++				transmit_size : (size - dev->cur_prek_offset);
++		for (i = 0; i < transmit_size; i++) {
++			if (nla_put_u8(msg, i, pre_cal[base + dev->cur_prek_offset + i]))
++				return -ENOMEM;
++		}
++		dev->cur_prek_offset += transmit_size;
++
++		nla_nest_end(msg, precal);
++	}
++
++	return 0;
++}
++
++static void
++mt7996_tm_re_cal_event(struct mt7996_dev *dev, struct mt7996_tm_rf_test_result *result,
++		       struct mt7996_tm_rf_test_data *data)
++{
++	u32 base, dpd_size_2g, dpd_size_5g, dpd_size_6g, cal_idx, cal_type, len = 0;
++	u8 *pre_cal;
++
++	pre_cal = dev->cal;
++	dpd_size_2g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_2GHZ);
++	dpd_size_5g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_5GHZ);
++	dpd_size_6g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_6GHZ);
++
++	cal_idx = le32_to_cpu(data->cal_idx);
++	cal_type = le32_to_cpu(data->cal_type);
++	len = le32_to_cpu(result->payload_length);
++	len = len - sizeof(struct mt7996_tm_rf_test_data);
++
++	switch (cal_type) {
++	case RF_PRE_CAL:
++		base = 0;
++		break;
++	case RF_DPD_FLAT_CAL:
++		base = MT_EE_CAL_GROUP_SIZE;
++		break;
++	case RF_DPD_FLAT_5G_CAL:
++	case RF_DPD_FLAT_5G_MEM_CAL:
++		base = MT_EE_CAL_GROUP_SIZE + dpd_size_2g;
++		break;
++	case RF_DPD_FLAT_6G_CAL:
++	case RF_DPD_FLAT_6G_MEM_CAL:
++		base = MT_EE_CAL_GROUP_SIZE + dpd_size_2g + dpd_size_5g;
++		break;
++	default:
++		dev_info(dev->mt76.dev, "Unknown calibration type!\n");
++		return;
++	}
++	pre_cal += (base + dev->cur_prek_offset);
++
++	memcpy(pre_cal, data->cal_data, len);
++	dev->cur_prek_offset += len;
++}
++
++void mt7996_tm_rf_test_event(struct mt7996_dev *dev, struct sk_buff *skb)
++{
++	struct mt7996_tm_event *event;
++	struct mt7996_tm_rf_test_result *result;
++	struct mt7996_tm_rf_test_data *data;
++	static u32 event_type;
++
++	skb_pull(skb, sizeof(struct mt7996_mcu_rxd));
++	event = (struct mt7996_tm_event *)skb->data;
++	result = (struct mt7996_tm_rf_test_result *)&event->result;
++	data = (struct mt7996_tm_rf_test_data *)result->data;
++
++	event_type = le32_to_cpu(result->func_idx);
++
++	switch (event_type) {
++	case RF_TEST_RE_CAL:
++		mt7996_tm_re_cal_event(dev, result, data);
++		break;
++	default:
++		break;
++	}
++}
++
+ static void
+ mt7996_tm_update_params(struct mt7996_phy *phy, u32 changed)
+ {
+@@ -454,6 +886,10 @@ mt7996_tm_set_state(struct mt76_phy *mphy, enum mt76_testmode_state state)
+ 	else if (prev_state == MT76_TM_STATE_OFF ||
+ 		 state == MT76_TM_STATE_OFF)
+ 		mt7996_tm_init(phy, !(state == MT76_TM_STATE_OFF));
++	else if (state >= MT76_TM_STATE_GROUP_PREK && state <= MT76_TM_STATE_GROUP_PREK_CLEAN)
++		return mt7996_tm_group_prek(phy, state);
++	else if (state >= MT76_TM_STATE_DPD_2G && state <= MT76_TM_STATE_DPD_CLEAN)
++		return mt7996_tm_dpd_prek(phy, state);
+ 
+ 	if ((state == MT76_TM_STATE_IDLE &&
+ 	     prev_state == MT76_TM_STATE_OFF) ||
+@@ -737,4 +1173,5 @@ const struct mt76_testmode_ops mt7996_testmode_ops = {
+ 	.reset_rx_stats = mt7996_tm_reset_trx_stats,
+ 	.tx_stop = mt7996_tm_tx_stop,
+ 	.set_eeprom = mt7996_tm_set_eeprom,
++	.dump_precal = mt7996_tm_dump_precal,
+ };
+diff --git a/mt7996/testmode.h b/mt7996/testmode.h
+index 319ef257a..9bfb86f28 100644
+--- a/mt7996/testmode.h
++++ b/mt7996/testmode.h
+@@ -34,6 +34,12 @@ enum bw_mapping_method {
+ 	NUM_BW_MAP,
+ };
+ 
++struct tm_cal_param {
++	__le32 func_data;
++	u8 band_idx;
++	u8 rsv[3];
++};
++
+ struct mt7996_tm_rf_test {
+ 	__le16 tag;
+ 	__le16 len;
+@@ -50,7 +56,7 @@ struct mt7996_tm_rf_test {
+ 			union {
+ 				__le32 func_data;
+ 				__le32 cal_dump;
+-
++				struct tm_cal_param cal_param;
+ 				u8 _pad[80];
+ 			} param;
+ 		} rf;
+@@ -63,10 +69,16 @@ struct mt7996_tm_req {
+ 	struct mt7996_tm_rf_test rf_test;
+ } __packed;
+ 
++struct mt7996_tm_rf_test_data {
++	__le32 cal_idx;
++	__le32 cal_type;
++	u8 cal_data[0];
++} __packed;
++
+ struct mt7996_tm_rf_test_result {
+ 	__le32 func_idx;
+ 	__le32 payload_length;
+-	u8 event[0];
++	u8 data[0];
+ };
+ 
+ struct mt7996_tm_event {
+@@ -77,6 +89,8 @@ struct mt7996_tm_event {
+ 	struct mt7996_tm_rf_test_result result;
+ } __packed;
+ 
++#define RF_TEST_RE_CAL		0x01
++
+ enum {
+ 	RF_ACTION_SWITCH_TO_RF_TEST,
+ 	RF_ACTION_IN_RF_TEST,
+@@ -84,6 +98,8 @@ enum {
+ 	RF_ACTION_GET,
+ };
+ 
++#define RF_TEST_ICAP_LEN	120
++
+ enum {
+ 	RF_OPER_NORMAL,
+ 	RF_OPER_RF_TEST,
+diff --git a/testmode.c b/testmode.c
+index 44f3a5bfc..cd8cb6553 100644
+--- a/testmode.c
++++ b/testmode.c
+@@ -674,6 +674,18 @@ int mt76_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *msg,
+ 
+ 	mutex_lock(&dev->mutex);
+ 
++	if (tb[MT76_TM_ATTR_PRECAL] || tb[MT76_TM_ATTR_PRECAL_INFO]) {
++		int flag, type;
++
++		err = -EINVAL;
++		flag = tb[MT76_TM_ATTR_PRECAL] ? 1 : 0;
++		type = flag ? nla_get_u8(tb[MT76_TM_ATTR_PRECAL_INFO]) : 0;
++		if (dev->test_ops->dump_precal)
++			err = dev->test_ops->dump_precal(phy, msg, flag, type);
++
++		goto out;
++	}
++
+ 	if (tb[MT76_TM_ATTR_STATS]) {
+ 		err = -EINVAL;
+ 
+diff --git a/testmode.h b/testmode.h
+index 96872e8cd..d6601cdcf 100644
+--- a/testmode.h
++++ b/testmode.h
+@@ -220,6 +220,14 @@ enum mt76_testmode_state {
+ 	MT76_TM_STATE_TX_FRAMES,
+ 	MT76_TM_STATE_RX_FRAMES,
+ 	MT76_TM_STATE_TX_CONT,
++	MT76_TM_STATE_GROUP_PREK,
++	MT76_TM_STATE_GROUP_PREK_DUMP,
++	MT76_TM_STATE_GROUP_PREK_CLEAN,
++	MT76_TM_STATE_DPD_2G,
++	MT76_TM_STATE_DPD_5G,
++	MT76_TM_STATE_DPD_6G,
++	MT76_TM_STATE_DPD_DUMP,
++	MT76_TM_STATE_DPD_CLEAN,
+ 	MT76_TM_STATE_ON,
+ 
+ 	/* keep last */
+diff --git a/tools/fields.c b/tools/fields.c
+index 055f90f3c..b01227638 100644
+--- a/tools/fields.c
++++ b/tools/fields.c
+@@ -11,6 +11,14 @@ static const char * const testmode_state[] = {
+ 	[MT76_TM_STATE_TX_FRAMES] = "tx_frames",
+ 	[MT76_TM_STATE_RX_FRAMES] = "rx_frames",
+ 	[MT76_TM_STATE_TX_CONT] = "tx_cont",
++	[MT76_TM_STATE_GROUP_PREK] = "group_prek",
++	[MT76_TM_STATE_GROUP_PREK_DUMP] = "group_prek_dump",
++	[MT76_TM_STATE_GROUP_PREK_CLEAN] = "group_prek_clean",
++	[MT76_TM_STATE_DPD_2G] = "dpd_2g",
++	[MT76_TM_STATE_DPD_5G] = "dpd_5g",
++	[MT76_TM_STATE_DPD_6G] = "dpd_6g",
++	[MT76_TM_STATE_DPD_DUMP] = "dpd_dump",
++	[MT76_TM_STATE_DPD_CLEAN] = "dpd_clean",
+ };
+ 
+ static const char * const testmode_tx_mode[] = {
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0030-mtk-wifi-mt76-mt7996-enable-SCS-feature-for-mt7996-d.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0030-mtk-wifi-mt76-mt7996-enable-SCS-feature-for-mt7996-d.patch
new file mode 100644
index 0000000..8084432
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0030-mtk-wifi-mt76-mt7996-enable-SCS-feature-for-mt7996-d.patch
@@ -0,0 +1,304 @@
+From dcd9b6090ba1f336d70a0d0c367ce22e08c5b2db Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Mon, 8 May 2023 09:03:50 +0800
+Subject: [PATCH 030/120] mtk: wifi: mt76: mt7996: enable SCS feature for
+ mt7996 driver
+
+Enable Smart Carrier Sense algorithn by default to improve performance
+in a noisy environment.
+
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+---
+ mt76_connac_mcu.h    |   1 +
+ mt7996/init.c        |   1 +
+ mt7996/mac.c         |   2 +
+ mt7996/main.c        |   7 +++
+ mt7996/mcu.c         | 105 +++++++++++++++++++++++++++++++++++++++++++
+ mt7996/mcu.h         |   6 +++
+ mt7996/mt7996.h      |  15 +++++++
+ mt7996/mtk_debugfs.c |  11 +++++
+ 8 files changed, 148 insertions(+)
+
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index f5ea719e4..26a824846 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1262,6 +1262,7 @@ enum {
+ 	MCU_UNI_CMD_GET_STAT_INFO = 0x23,
+ 	MCU_UNI_CMD_SNIFFER = 0x24,
+ 	MCU_UNI_CMD_SR = 0x25,
++	MCU_UNI_CMD_SCS = 0x26,
+ 	MCU_UNI_CMD_ROC = 0x27,
+ 	MCU_UNI_CMD_SET_DBDC_PARMS = 0x28,
+ 	MCU_UNI_CMD_TXPOWER = 0x2b,
+diff --git a/mt7996/init.c b/mt7996/init.c
+index d4d1a60b4..23a9b88bf 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -1374,6 +1374,7 @@ int mt7996_register_device(struct mt7996_dev *dev)
+ 	dev->mt76.phy.priv = &dev->phy;
+ 	INIT_WORK(&dev->rc_work, mt7996_mac_sta_rc_work);
+ 	INIT_DELAYED_WORK(&dev->mphy.mac_work, mt7996_mac_work);
++	INIT_DELAYED_WORK(&dev->scs_work, mt7996_mcu_scs_sta_poll);
+ 	INIT_LIST_HEAD(&dev->sta_rc_list);
+ 	INIT_LIST_HEAD(&dev->twt_list);
+ 
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 603f6c0d7..c9f45abef 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -1795,6 +1795,7 @@ mt7996_mac_full_reset(struct mt7996_dev *dev)
+ 		cancel_delayed_work_sync(&phy2->mt76->mac_work);
+ 	if (phy3)
+ 		cancel_delayed_work_sync(&phy3->mt76->mac_work);
++	cancel_delayed_work_sync(&dev->scs_work);
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 	for (i = 0; i < 10; i++) {
+@@ -1830,6 +1831,7 @@ mt7996_mac_full_reset(struct mt7996_dev *dev)
+ 		ieee80211_queue_delayed_work(phy3->mt76->hw,
+ 					     &phy3->mt76->mac_work,
+ 					     MT7996_WATCHDOG_TIME);
++	ieee80211_queue_delayed_work(mt76_hw(dev), &dev->scs_work, HZ);
+ }
+ 
+ void mt7996_mac_reset_work(struct work_struct *work)
+diff --git a/mt7996/main.c b/mt7996/main.c
+index ecf65d71f..93a6eeded 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -81,11 +81,17 @@ int mt7996_run(struct ieee80211_hw *hw)
+ 	if (ret)
+ 		goto out;
+ 
++	ret = mt7996_mcu_set_scs(phy, SCS_ENABLE);
++	if (ret)
++		goto out;
++
+ 	set_bit(MT76_STATE_RUNNING, &phy->mt76->state);
+ 
+ 	ieee80211_queue_delayed_work(hw, &phy->mt76->mac_work,
+ 				     MT7996_WATCHDOG_TIME);
+ 
++	ieee80211_queue_delayed_work(mt76_hw(dev), &dev->scs_work, HZ);
++
+ 	if (!running)
+ 		mt7996_mac_reset_counters(phy);
+ 
+@@ -113,6 +119,7 @@ static void mt7996_stop(struct ieee80211_hw *hw)
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 
+ 	cancel_delayed_work_sync(&phy->mt76->mac_work);
++	cancel_delayed_work_sync(&dev->scs_work);
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index ef9042a88..a7fbaf025 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -4633,3 +4633,108 @@ int mt7996_mcu_set_tx_power_ctrl(struct mt7996_phy *phy, u8 power_ctrl_id, u8 da
+ 	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(TXPOWER),
+ 				 &req, sizeof(req), false);
+ }
++
++int mt7996_mcu_set_scs_stats(struct mt7996_phy *phy)
++{
++	struct mt7996_scs_ctrl ctrl = phy->scs_ctrl;
++	struct {
++		u8 band_idx;
++		u8 _rsv[3];
++
++		__le16 tag;
++		__le16 len;
++
++		u8 _rsv2[6];
++		s8 min_rssi;
++		u8 _rsv3;
++	} __packed req = {
++		.band_idx = phy->mt76->band_idx,
++		.tag = cpu_to_le16(UNI_CMD_SCS_SEND_DATA),
++		.len = cpu_to_le16(sizeof(req) - 4),
++
++		.min_rssi = ctrl.sta_min_rssi,
++	};
++
++	return mt76_mcu_send_msg(&phy->dev->mt76, MCU_WM_UNI_CMD(SCS),
++				 &req, sizeof(req), false);
++}
++
++void mt7996_sta_rssi_work(void *data, struct ieee80211_sta *sta)
++{
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_phy *poll_phy = (struct mt7996_phy *) data;
++
++	if (poll_phy->scs_ctrl.sta_min_rssi > msta->ack_signal)
++		poll_phy->scs_ctrl.sta_min_rssi = msta->ack_signal;
++}
++
++void mt7996_mcu_scs_sta_poll(struct work_struct *work)
++{
++	struct mt7996_dev *dev = container_of(work, struct mt7996_dev,
++				 scs_work.work);
++	bool scs_enable_flag = false;
++	u8 i;
++
++	for (i = 0; i < __MT_MAX_BAND; i++) {
++		struct mt7996_phy *phy;
++
++		switch (i) {
++		case MT_BAND0:
++			phy = dev->mphy.priv;
++			break;
++		case MT_BAND1:
++			phy = mt7996_phy2(dev);
++			break;
++		case MT_BAND2:
++			phy = mt7996_phy3(dev);
++			break;
++		default:
++			phy = NULL;
++			break;
++		}
++
++		if (!phy || !test_bit(MT76_STATE_RUNNING, &phy->mt76->state) ||
++		    !phy->scs_ctrl.scs_enable)
++			continue;
++
++		ieee80211_iterate_stations_atomic(phy->mt76->hw,
++						  mt7996_sta_rssi_work, phy);
++
++		scs_enable_flag = true;
++		if (mt7996_mcu_set_scs_stats(phy))
++			dev_err(dev->mt76.dev, "Failed to send scs mcu cmd\n");
++		phy->scs_ctrl.sta_min_rssi = 0;
++	}
++
++	if (scs_enable_flag)
++		ieee80211_queue_delayed_work(mt76_hw(dev), &dev->scs_work, HZ);
++}
++
++
++int mt7996_mcu_set_scs(struct mt7996_phy *phy, u8 enable)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct {
++		u8 band_idx;
++		u8 _rsv[3];
++
++		__le16 tag;
++		__le16 len;
++
++		u8 scs_enable;
++		u8 _rsv2[3];
++	} __packed req = {
++		.band_idx = phy->mt76->band_idx,
++		.tag = cpu_to_le16(UNI_CMD_SCS_ENABLE),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.scs_enable = enable,
++	};
++
++	phy->scs_ctrl.scs_enable = enable;
++
++	if (enable == SCS_ENABLE)
++		ieee80211_queue_delayed_work(mt76_hw(dev), &dev->scs_work, HZ);
++
++	return mt76_mcu_send_msg(&phy->dev->mt76, MCU_WM_UNI_CMD(SCS),
++				 &req, sizeof(req), false);
++}
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 325c3c973..4f4994d82 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -960,6 +960,12 @@ enum {
+ 	UNI_CMD_PP_EN_CTRL,
+ };
+ 
++enum {
++	UNI_CMD_SCS_SEND_DATA,
++	UNI_CMD_SCS_SET_PD_THR_RANGE = 2,
++	UNI_CMD_SCS_ENABLE,
++};
++
+ #define MT7996_PATCH_SEC		GENMASK(31, 24)
+ #define MT7996_PATCH_SCRAMBLE_KEY	GENMASK(15, 8)
+ #define MT7996_PATCH_AES_KEY		GENMASK(7, 0)
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 776abddb1..5dabdad84 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -233,6 +233,16 @@ struct mt7996_hif {
+ 	int irq;
+ };
+ 
++struct mt7996_scs_ctrl {
++	u8 scs_enable;
++	s8 sta_min_rssi;
++};
++
++enum {
++	SCS_DISABLE = 0,
++	SCS_ENABLE,
++};
++
+ struct mt7996_wed_rro_addr {
+ 	u32 head_low;
+ 	u32 head_high : 4;
+@@ -280,6 +290,8 @@ struct mt7996_phy {
+ 
+ 	bool has_aux_rx;
+ 
++	struct mt7996_scs_ctrl scs_ctrl;
++
+ #ifdef CONFIG_NL80211_TESTMODE
+ 	struct {
+ 		u32 *reg_backup;
+@@ -326,6 +338,7 @@ struct mt7996_dev {
+ 	struct work_struct rc_work;
+ 	struct work_struct dump_work;
+ 	struct work_struct reset_work;
++	struct delayed_work scs_work;
+ 	wait_queue_head_t reset_wait;
+ 	struct {
+ 		u32 state;
+@@ -596,6 +609,8 @@ int mt7996_mcu_set_tx_power_ctrl(struct mt7996_phy *phy, u8 power_ctrl_id, u8 da
+ #ifdef CONFIG_NL80211_TESTMODE
+ void mt7996_tm_rf_test_event(struct mt7996_dev *dev, struct sk_buff *skb);
+ #endif
++int mt7996_mcu_set_scs(struct mt7996_phy *phy, u8 enable);
++void mt7996_mcu_scs_sta_poll(struct work_struct *work);
+ 
+ static inline u8 mt7996_max_interface_num(struct mt7996_dev *dev)
+ {
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index 678b009e7..7e4ac77c6 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -2407,6 +2407,16 @@ static int mt7996_token_read(struct seq_file *s, void *data)
+ 	return 0;
+ }
+ 
++static int
++mt7996_scs_enable_set(void *data, u64 val)
++{
++	struct mt7996_phy *phy = data;
++
++	return mt7996_mcu_set_scs(phy, (u8) val);
++}
++DEFINE_DEBUGFS_ATTRIBUTE(fops_scs_enable, NULL,
++			 mt7996_scs_enable_set, "%lld\n");
++
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -2477,6 +2487,7 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ 	debugfs_create_devm_seqfile(dev->mt76.dev, "token", dir, mt7996_token_read);
+ 
+ 	debugfs_create_u8("sku_disable", 0600, dir, &dev->dbg.sku_disable);
++	debugfs_create_file("scs_enable", 0200, dir, phy, &fops_scs_enable);
+ 
+ 	return 0;
+ }
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0031-mtk-wifi-mt76-mt7996-add-txpower-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0031-mtk-wifi-mt76-mt7996-add-txpower-support.patch
new file mode 100644
index 0000000..d825c91
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0031-mtk-wifi-mt76-mt7996-add-txpower-support.patch
@@ -0,0 +1,676 @@
+From 66969734192c66ea8136754917014acf8c870f44 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Fri, 24 Mar 2023 23:35:30 +0800
+Subject: [PATCH 031/120] mtk: wifi: mt76: mt7996: add txpower support
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Change-Id: Ic3e7b17f3664fa7f774137572f885359fa2ec93b
+---
+ mt7996/eeprom.c      |  34 +++++
+ mt7996/eeprom.h      |  42 ++++++
+ mt7996/mcu.h         |   2 +
+ mt7996/mt7996.h      |   1 +
+ mt7996/mtk_debugfs.c | 326 +++++++++++++++++++++++++++++++++++++++++++
+ mt7996/mtk_mcu.c     |  23 +++
+ mt7996/mtk_mcu.h     |  92 ++++++++++++
+ mt7996/regs.h        |  29 ++--
+ 8 files changed, 538 insertions(+), 11 deletions(-)
+
+diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
+index 62c1ad489..726607126 100644
+--- a/mt7996/eeprom.c
++++ b/mt7996/eeprom.c
+@@ -404,3 +404,37 @@ s8 mt7996_eeprom_get_power_delta(struct mt7996_dev *dev, int band)
+ 
+ 	return val & MT_EE_RATE_DELTA_SIGN ? delta : -delta;
+ }
++
++const u8 mt7996_sku_group_len[] = {
++	[SKU_CCK] = 4,
++	[SKU_OFDM] = 8,
++	[SKU_HT20] = 8,
++	[SKU_HT40] = 9,
++	[SKU_VHT20] = 12,
++	[SKU_VHT40] = 12,
++	[SKU_VHT80] = 12,
++	[SKU_VHT160] = 12,
++	[SKU_HE26] = 12,
++	[SKU_HE52] = 12,
++	[SKU_HE106] = 12,
++	[SKU_HE242] = 12,
++	[SKU_HE484] = 12,
++	[SKU_HE996] = 12,
++	[SKU_HE2x996] = 12,
++	[SKU_EHT26] = 16,
++	[SKU_EHT52] = 16,
++	[SKU_EHT106] = 16,
++	[SKU_EHT242] = 16,
++	[SKU_EHT484] = 16,
++	[SKU_EHT996] = 16,
++	[SKU_EHT2x996] = 16,
++	[SKU_EHT4x996] = 16,
++	[SKU_EHT26_52] = 16,
++	[SKU_EHT26_106] = 16,
++	[SKU_EHT484_242] = 16,
++	[SKU_EHT996_484] = 16,
++	[SKU_EHT996_484_242] = 16,
++	[SKU_EHT2x996_484] = 16,
++	[SKU_EHT3x996] = 16,
++	[SKU_EHT3x996_484] = 16,
++};
+diff --git a/mt7996/eeprom.h b/mt7996/eeprom.h
+index 849b8bcab..23d4929d7 100644
+--- a/mt7996/eeprom.h
++++ b/mt7996/eeprom.h
+@@ -123,4 +123,46 @@ mt7996_get_channel_group_6g(int channel)
+ 	return DIV_ROUND_UP(channel - 29, 32);
+ }
+ 
++enum mt7996_sku_rate_group {
++	SKU_CCK,
++	SKU_OFDM,
++
++	SKU_HT20,
++	SKU_HT40,
++
++	SKU_VHT20,
++	SKU_VHT40,
++	SKU_VHT80,
++	SKU_VHT160,
++
++	SKU_HE26,
++	SKU_HE52,
++	SKU_HE106,
++	SKU_HE242,
++	SKU_HE484,
++	SKU_HE996,
++	SKU_HE2x996,
++
++	SKU_EHT26,
++	SKU_EHT52,
++	SKU_EHT106,
++	SKU_EHT242,
++	SKU_EHT484,
++	SKU_EHT996,
++	SKU_EHT2x996,
++	SKU_EHT4x996,
++	SKU_EHT26_52,
++	SKU_EHT26_106,
++	SKU_EHT484_242,
++	SKU_EHT996_484,
++	SKU_EHT996_484_242,
++	SKU_EHT2x996_484,
++	SKU_EHT3x996,
++	SKU_EHT3x996_484,
++
++	MAX_SKU_RATE_GROUP_NUM,
++};
++
++extern const u8 mt7996_sku_group_len[MAX_SKU_RATE_GROUP_NUM];
++
+ #endif
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 4f4994d82..887d9b49e 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -911,6 +911,7 @@ struct tx_power_ctrl {
+ 		bool ate_mode_enable;
+ 		bool percentage_ctrl_enable;
+ 		bool bf_backoff_enable;
++		u8 show_info_category;
+ 		u8 power_drop_level;
+ 	};
+ 	u8 band_idx;
+@@ -924,6 +925,7 @@ enum {
+ 	UNI_TXPOWER_BACKOFF_POWER_LIMIT_CTRL = 3,
+ 	UNI_TXPOWER_POWER_LIMIT_TABLE_CTRL = 4,
+ 	UNI_TXPOWER_ATE_MODE_CTRL = 6,
++	UNI_TXPOWER_SHOW_INFO = 7,
+ };
+ 
+ enum {
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 5dabdad84..fb4d9708a 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -609,6 +609,7 @@ int mt7996_mcu_set_tx_power_ctrl(struct mt7996_phy *phy, u8 power_ctrl_id, u8 da
+ #ifdef CONFIG_NL80211_TESTMODE
+ void mt7996_tm_rf_test_event(struct mt7996_dev *dev, struct sk_buff *skb);
+ #endif
++int mt7996_mcu_get_tx_power_info(struct mt7996_phy *phy, u8 category, void *event);
+ int mt7996_mcu_set_scs(struct mt7996_phy *phy, u8 enable);
+ void mt7996_mcu_scs_sta_poll(struct work_struct *work);
+ 
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index 7e4ac77c6..c47d65c96 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -2417,6 +2417,328 @@ mt7996_scs_enable_set(void *data, u64 val)
+ DEFINE_DEBUGFS_ATTRIBUTE(fops_scs_enable, NULL,
+ 			 mt7996_scs_enable_set, "%lld\n");
+ 
++static int
++mt7996_txpower_level_set(void *data, u64 val)
++{
++	struct mt7996_phy *phy = data;
++	int ret;
++
++	if (val > 100)
++		return -EINVAL;
++
++	ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_PERCENTAGE_CTRL, !!val);
++	if (ret)
++		return ret;
++
++	return mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_PERCENTAGE_DROP_CTRL, val);
++}
++
++DEFINE_DEBUGFS_ATTRIBUTE(fops_txpower_level, NULL,
++			 mt7996_txpower_level_set, "%lld\n");
++
++static ssize_t
++mt7996_get_txpower_info(struct file *file, char __user *user_buf,
++			size_t count, loff_t *ppos)
++{
++	struct mt7996_phy *phy = file->private_data;
++	struct mt7996_mcu_txpower_event *event;
++	struct txpower_basic_info *basic_info;
++	static const size_t size = 2048;
++	int len = 0;
++	ssize_t ret;
++	char *buf;
++
++	buf = kzalloc(size, GFP_KERNEL);
++	event = kzalloc(sizeof(*event), GFP_KERNEL);
++	if (!buf || !event) {
++		ret = -ENOMEM;
++		goto out;
++	}
++
++	ret = mt7996_mcu_get_tx_power_info(phy, BASIC_INFO, event);
++	if (ret ||
++	    le32_to_cpu(event->basic_info.category) != UNI_TXPOWER_BASIC_INFO)
++		goto out;
++
++	basic_info = &event->basic_info;
++
++	len += scnprintf(buf + len, size - len,
++			 "======================== BASIC INFO ========================\n");
++	len += scnprintf(buf + len, size - len, "    Band Index: %d, Channel Band: %d\n",
++			 basic_info->band_idx, basic_info->band);
++	len += scnprintf(buf + len, size - len, "    PA Type: %s\n",
++			 basic_info->is_epa ? "ePA" : "iPA");
++	len += scnprintf(buf + len, size - len, "    LNA Type: %s\n",
++			 basic_info->is_elna ? "eLNA" : "iLNA");
++
++	len += scnprintf(buf + len, size - len,
++			 "------------------------------------------------------------\n");
++	len += scnprintf(buf + len, size - len, "    SKU: %s\n",
++			 basic_info->sku_enable ? "enable" : "disable");
++	len += scnprintf(buf + len, size - len, "    Percentage Control: %s\n",
++			 basic_info->percentage_ctrl_enable ? "enable" : "disable");
++	len += scnprintf(buf + len, size - len, "    Power Drop: %d [dBm]\n",
++			 basic_info->power_drop_level >> 1);
++	len += scnprintf(buf + len, size - len, "    Backoff: %s\n",
++			 basic_info->bf_backoff_enable ? "enable" : "disable");
++	len += scnprintf(buf + len, size - len, "    TX Front-end Loss:  %d, %d, %d, %d\n",
++			 basic_info->front_end_loss_tx[0], basic_info->front_end_loss_tx[1],
++			 basic_info->front_end_loss_tx[2], basic_info->front_end_loss_tx[3]);
++	len += scnprintf(buf + len, size - len, "    RX Front-end Loss:  %d, %d, %d, %d\n",
++			 basic_info->front_end_loss_rx[0], basic_info->front_end_loss_rx[1],
++			 basic_info->front_end_loss_rx[2], basic_info->front_end_loss_rx[3]);
++	len += scnprintf(buf + len, size - len,
++			 "    MU TX Power Mode:  %s\n",
++			 basic_info->mu_tx_power_manual_enable ? "manual" : "auto");
++	len += scnprintf(buf + len, size - len,
++			 "    MU TX Power (Auto / Manual): %d / %d [0.5 dBm]\n",
++			 basic_info->mu_tx_power_auto, basic_info->mu_tx_power_manual);
++	len += scnprintf(buf + len, size - len,
++			 "    Thermal Compensation:  %s\n",
++			 basic_info->thermal_compensate_enable ? "enable" : "disable");
++	len += scnprintf(buf + len, size - len,
++			 "    Theraml Compensation Value: %d\n",
++			 basic_info->thermal_compensate_value);
++
++	ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
++
++out:
++	kfree(buf);
++	kfree(event);
++	return ret;
++}
++
++static const struct file_operations mt7996_txpower_info_fops = {
++	.read = mt7996_get_txpower_info,
++	.open = simple_open,
++	.owner = THIS_MODULE,
++	.llseek = default_llseek,
++};
++
++#define mt7996_txpower_puts(rate)							\
++({											\
++	len += scnprintf(buf + len, size - len, "%-21s:", #rate " (TMAC)");		\
++	for (i = 0; i < mt7996_sku_group_len[SKU_##rate]; i++, offs++)			\
++		len += scnprintf(buf + len, size - len, " %6d",				\
++				 event->phy_rate_info.frame_power[offs][band_idx]);	\
++	len += scnprintf(buf + len, size - len, "\n");					\
++})
++
++static ssize_t
++mt7996_get_txpower_sku(struct file *file, char __user *user_buf,
++		       size_t count, loff_t *ppos)
++{
++	struct mt7996_phy *phy = file->private_data;
++	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_mcu_txpower_event *event;
++	u8 band_idx = phy->mt76->band_idx;
++	static const size_t size = 5120;
++	int i, offs = 0, len = 0;
++	ssize_t ret;
++	char *buf;
++	u32 reg;
++
++	buf = kzalloc(size, GFP_KERNEL);
++	event = kzalloc(sizeof(*event), GFP_KERNEL);
++	if (!buf || !event) {
++		ret = -ENOMEM;
++		goto out;
++	}
++
++	ret = mt7996_mcu_get_tx_power_info(phy, PHY_RATE_INFO, event);
++	if (ret ||
++	    le32_to_cpu(event->phy_rate_info.category) != UNI_TXPOWER_PHY_RATE_INFO)
++		goto out;
++
++	len += scnprintf(buf + len, size - len,
++			 "\nPhy %d TX Power Table (Channel %d)\n",
++			 band_idx, phy->mt76->chandef.chan->hw_value);
++	len += scnprintf(buf + len, size - len, "%-21s  %6s %6s %6s %6s\n",
++			 " ", "1m", "2m", "5m", "11m");
++	mt7996_txpower_puts(CCK);
++
++	len += scnprintf(buf + len, size - len,
++			 "%-21s  %6s %6s %6s %6s %6s %6s %6s %6s\n",
++			 " ", "6m", "9m", "12m", "18m", "24m", "36m", "48m",
++			 "54m");
++	mt7996_txpower_puts(OFDM);
++
++	len += scnprintf(buf + len, size - len,
++			 "%-21s  %6s %6s %6s %6s %6s %6s %6s %6s\n",
++			 " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4",
++			 "mcs5", "mcs6", "mcs7");
++	mt7996_txpower_puts(HT20);
++
++	len += scnprintf(buf + len, size - len,
++			 "%-21s  %6s %6s %6s %6s %6s %6s %6s %6s %6s\n",
++			 " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5",
++			 "mcs6", "mcs7", "mcs32");
++	mt7996_txpower_puts(HT40);
++
++	len += scnprintf(buf + len, size - len,
++			 "%-21s  %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s\n",
++			 " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5",
++			 "mcs6", "mcs7", "mcs8", "mcs9", "mcs10", "mcs11");
++	mt7996_txpower_puts(VHT20);
++	mt7996_txpower_puts(VHT40);
++	mt7996_txpower_puts(VHT80);
++	mt7996_txpower_puts(VHT160);
++	mt7996_txpower_puts(HE26);
++	mt7996_txpower_puts(HE52);
++	mt7996_txpower_puts(HE106);
++	mt7996_txpower_puts(HE242);
++	mt7996_txpower_puts(HE484);
++	mt7996_txpower_puts(HE996);
++	mt7996_txpower_puts(HE2x996);
++
++	len += scnprintf(buf + len, size - len,
++			 "%-21s  %6s %6s %6s %6s %6s %6s %6s %6s ",
++			 " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5", "mcs6", "mcs7");
++	len += scnprintf(buf + len, size - len,
++			 "%6s %6s %6s %6s %6s %6s %6s %6s\n",
++			 "mcs8", "mcs9", "mcs10", "mcs11", "mcs12", "mcs13", "mcs14", "mcs15");
++	mt7996_txpower_puts(EHT26);
++	mt7996_txpower_puts(EHT52);
++	mt7996_txpower_puts(EHT106);
++	mt7996_txpower_puts(EHT242);
++	mt7996_txpower_puts(EHT484);
++	mt7996_txpower_puts(EHT996);
++	mt7996_txpower_puts(EHT2x996);
++	mt7996_txpower_puts(EHT4x996);
++	mt7996_txpower_puts(EHT26_52);
++	mt7996_txpower_puts(EHT26_106);
++	mt7996_txpower_puts(EHT484_242);
++	mt7996_txpower_puts(EHT996_484);
++	mt7996_txpower_puts(EHT996_484_242);
++	mt7996_txpower_puts(EHT2x996_484);
++	mt7996_txpower_puts(EHT3x996);
++	mt7996_txpower_puts(EHT3x996_484);
++
++	len += scnprintf(buf + len, size - len, "\nePA Gain: %d\n",
++			 event->phy_rate_info.epa_gain);
++	len += scnprintf(buf + len, size - len, "Max Power Bound: %d\n",
++			 event->phy_rate_info.max_power_bound);
++	len += scnprintf(buf + len, size - len, "Min Power Bound: %d\n",
++			 event->phy_rate_info.min_power_bound);
++
++	reg = MT_WF_PHYDFE_BAND_TPC_CTRL_STAT0(band_idx);
++	len += scnprintf(buf + len, size - len,
++			 "BBP TX Power (target power from TMAC)  : %6ld [0.5 dBm]\n",
++			 mt76_get_field(dev, reg, MT_WF_PHY_TPC_POWER_TMAC));
++	len += scnprintf(buf + len, size - len,
++			 "BBP TX Power (target power from RMAC)  : %6ld [0.5 dBm]\n",
++			 mt76_get_field(dev, reg, MT_WF_PHY_TPC_POWER_RMAC));
++	len += scnprintf(buf + len, size - len,
++			 "BBP TX Power (TSSI module power input)  : %6ld [0.5 dBm]\n",
++			 mt76_get_field(dev, reg, MT_WF_PHY_TPC_POWER_TSSI));
++
++	ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
++
++out:
++	kfree(buf);
++	kfree(event);
++	return ret;
++}
++
++static const struct file_operations mt7996_txpower_sku_fops = {
++	.read = mt7996_get_txpower_sku,
++	.open = simple_open,
++	.owner = THIS_MODULE,
++	.llseek = default_llseek,
++};
++
++#define mt7996_txpower_path_puts(rate, arr_length)					\
++({											\
++	len += scnprintf(buf + len, size - len, "%-23s:", #rate " (TMAC)");		\
++	for (i = 0; i < arr_length; i++, offs++)					\
++		len += scnprintf(buf + len, size - len, " %4d",				\
++				 event->backoff_table_info.frame_power[offs]);		\
++	len += scnprintf(buf + len, size - len, "\n");					\
++})
++
++static ssize_t
++mt7996_get_txpower_path(struct file *file, char __user *user_buf,
++		       size_t count, loff_t *ppos)
++{
++	struct mt7996_phy *phy = file->private_data;
++	struct mt7996_mcu_txpower_event *event;
++	static const size_t size = 5120;
++	int i, offs = 0, len = 0;
++	ssize_t ret;
++	char *buf;
++
++	buf = kzalloc(size, GFP_KERNEL);
++	event = kzalloc(sizeof(*event), GFP_KERNEL);
++	if (!buf || !event) {
++		ret = -ENOMEM;
++		goto out;
++	}
++
++	ret = mt7996_mcu_get_tx_power_info(phy, BACKOFF_TABLE_INFO, event);
++	if (ret ||
++	    le32_to_cpu(event->phy_rate_info.category) != UNI_TXPOWER_BACKOFF_TABLE_SHOW_INFO)
++		goto out;
++
++	len += scnprintf(buf + len, size - len, "\n%*c", 25, ' ');
++	len += scnprintf(buf + len, size - len, "1T1S/2T1S/3T1S/4T1S/5T1S/2T2S/3T2S/4T2S/5T2S/"
++			 "3T3S/4T3S/5T3S/4T4S/5T4S/5T5S\n");
++
++	mt7996_txpower_path_puts(CCK, 5);
++	mt7996_txpower_path_puts(OFDM, 5);
++	mt7996_txpower_path_puts(BF-OFDM, 4);
++
++	mt7996_txpower_path_puts(RU26, 15);
++	mt7996_txpower_path_puts(BF-RU26, 15);
++	mt7996_txpower_path_puts(RU52, 15);
++	mt7996_txpower_path_puts(BF-RU52, 15);
++	mt7996_txpower_path_puts(RU26_52, 15);
++	mt7996_txpower_path_puts(BF-RU26_52, 15);
++	mt7996_txpower_path_puts(RU106, 15);
++	mt7996_txpower_path_puts(BF-RU106, 15);
++	mt7996_txpower_path_puts(RU106_52, 15);
++	mt7996_txpower_path_puts(BF-RU106_52, 15);
++
++	mt7996_txpower_path_puts(BW20/RU242, 15);
++	mt7996_txpower_path_puts(BF-BW20/RU242, 15);
++	mt7996_txpower_path_puts(BW40/RU484, 15);
++	mt7996_txpower_path_puts(BF-BW40/RU484, 15);
++	mt7996_txpower_path_puts(RU242_484, 15);
++	mt7996_txpower_path_puts(BF-RU242_484, 15);
++	mt7996_txpower_path_puts(BW80/RU996, 15);
++	mt7996_txpower_path_puts(BF-BW80/RU996, 15);
++	mt7996_txpower_path_puts(RU484_996, 15);
++	mt7996_txpower_path_puts(BF-RU484_996, 15);
++	mt7996_txpower_path_puts(RU242_484_996, 15);
++	mt7996_txpower_path_puts(BF-RU242_484_996, 15);
++	mt7996_txpower_path_puts(BW160/RU996x2, 15);
++	mt7996_txpower_path_puts(BF-BW160/RU996x2, 15);
++	mt7996_txpower_path_puts(RU484_996x2, 15);
++	mt7996_txpower_path_puts(BF-RU484_996x2, 15);
++	mt7996_txpower_path_puts(RU996x3, 15);
++	mt7996_txpower_path_puts(BF-RU996x3, 15);
++	mt7996_txpower_path_puts(RU484_996x3, 15);
++	mt7996_txpower_path_puts(BF-RU484_996x3, 15);
++	mt7996_txpower_path_puts(BW320/RU996x4, 15);
++	mt7996_txpower_path_puts(BF-BW320/RU996x4, 15);
++
++	len += scnprintf(buf + len, size - len, "\nBackoff table: %s\n",
++			 event->backoff_table_info.backoff_en ? "enable" : "disable");
++
++	ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
++
++out:
++	kfree(buf);
++	kfree(event);
++	return ret;
++}
++
++static const struct file_operations mt7996_txpower_path_fops = {
++	.read = mt7996_get_txpower_path,
++	.open = simple_open,
++	.owner = THIS_MODULE,
++	.llseek = default_llseek,
++};
++
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -2480,6 +2802,10 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ 
+ 	debugfs_create_devm_seqfile(dev->mt76.dev, "tr_info", dir,
+ 				    mt7996_trinfo_read);
++	debugfs_create_file("txpower_level", 0600, dir, phy, &fops_txpower_level);
++	debugfs_create_file("txpower_info", 0600, dir, phy, &mt7996_txpower_info_fops);
++	debugfs_create_file("txpower_sku", 0600, dir, phy, &mt7996_txpower_sku_fops);
++	debugfs_create_file("txpower_path", 0600, dir, phy, &mt7996_txpower_path_fops);
+ 
+ 	debugfs_create_devm_seqfile(dev->mt76.dev, "wtbl_info", dir,
+ 				    mt7996_wtbl_read);
+diff --git a/mt7996/mtk_mcu.c b/mt7996/mtk_mcu.c
+index c16b25ab5..e56ddd8ff 100644
+--- a/mt7996/mtk_mcu.c
++++ b/mt7996/mtk_mcu.c
+@@ -12,8 +12,31 @@
+ 
+ #ifdef CONFIG_MTK_DEBUG
+ 
++int mt7996_mcu_get_tx_power_info(struct mt7996_phy *phy, u8 category, void *event)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct tx_power_ctrl req = {
++		.tag = cpu_to_le16(UNI_TXPOWER_SHOW_INFO),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.power_ctrl_id = UNI_TXPOWER_SHOW_INFO,
++		.show_info_category = category,
++		.band_idx = phy->mt76->band_idx,
++	};
++	struct sk_buff *skb;
++	int ret;
+ 
++	ret = mt76_mcu_send_and_get_msg(&dev->mt76,
++					MCU_WM_UNI_CMD_QUERY(TXPOWER),
++					&req, sizeof(req), true, &skb);
++	if (ret)
++		return ret;
+ 
++	memcpy(event, skb->data, sizeof(struct mt7996_mcu_txpower_event));
++
++	dev_kfree_skb(skb);
++
++	return 0;
++}
+ 
+ int mt7996_mcu_muru_dbg_info(struct mt7996_dev *dev, u16 item, u8 val)
+ {
+diff --git a/mt7996/mtk_mcu.h b/mt7996/mtk_mcu.h
+index 7f4d4e029..c30418cae 100644
+--- a/mt7996/mtk_mcu.h
++++ b/mt7996/mtk_mcu.h
+@@ -14,6 +14,98 @@ enum {
+ 	UNI_CMD_MURU_DBG_INFO = 0x18,
+ };
+ 
++struct txpower_basic_info {
++	u8 category;
++	u8 rsv1;
++
++	/* basic info */
++	u8 band_idx;
++	u8 band;
++
++	/* board type info */
++	bool is_epa;
++	bool is_elna;
++
++	/* power percentage info */
++	bool percentage_ctrl_enable;
++	s8 power_drop_level;
++
++	/* frond-end loss TX info */
++	s8 front_end_loss_tx[4];
++
++	/* frond-end loss RX info */
++	s8 front_end_loss_rx[4];
++
++	/* thermal info */
++	bool thermal_compensate_enable;
++	s8 thermal_compensate_value;
++	u8 rsv2;
++
++	/* TX power max/min limit info */
++	s8 max_power_bound;
++	s8 min_power_bound;
++
++	/* power limit info */
++	bool sku_enable;
++	bool bf_backoff_enable;
++
++	/* MU TX power info */
++	bool mu_tx_power_manual_enable;
++	s8 mu_tx_power_auto;
++	s8 mu_tx_power_manual;
++	u8 rsv3;
++};
++
++struct txpower_phy_rate_info {
++	u8 category;
++	u8 band_idx;
++	u8 band;
++	u8 epa_gain;
++
++	/* rate power info [dBm] */
++	s8 frame_power[MT7996_SKU_RATE_NUM][__MT_MAX_BAND];
++
++	/* TX power max/min limit info */
++	s8 max_power_bound;
++	s8 min_power_bound;
++	u8 rsv1;
++};
++
++struct txpower_backoff_table_info {
++	u8 category;
++	u8 band_idx;
++	u8 band;
++	u8 backoff_en;
++
++	s8 frame_power[MT7996_SKU_PATH_NUM];
++	u8 rsv[3];
++};
++
++struct mt7996_mcu_txpower_event {
++	u8 _rsv[4];
++
++	__le16 tag;
++	__le16 len;
++
++	union {
++		struct txpower_basic_info basic_info;
++		struct txpower_phy_rate_info phy_rate_info;
++		struct txpower_backoff_table_info backoff_table_info;
++	};
++};
++
++enum txpower_category {
++	BASIC_INFO,
++	BACKOFF_TABLE_INFO,
++	PHY_RATE_INFO,
++};
++
++enum txpower_event {
++	UNI_TXPOWER_BASIC_INFO = 0,
++	UNI_TXPOWER_BACKOFF_TABLE_SHOW_INFO = 3,
++	UNI_TXPOWER_PHY_RATE_INFO = 5,
++};
++
+ #endif
+ 
+ #endif
+diff --git a/mt7996/regs.h b/mt7996/regs.h
+index 4c20a67d7..e94f9a90b 100644
+--- a/mt7996/regs.h
++++ b/mt7996/regs.h
+@@ -693,24 +693,31 @@ enum offs_rev {
+ 						 ((_wf) << 16) + (ofs))
+ #define MT_WF_PHYRX_CSD_IRPI(_band, _wf)	MT_WF_PHYRX_CSD(_band, _wf, 0x1000)
+ 
+-/* PHYRX CTRL */
+-#define MT_WF_PHYRX_BAND_BASE			0x83080000
+-#define MT_WF_PHYRX_BAND(_band, ofs)		(MT_WF_PHYRX_BAND_BASE + \
++/* PHY CTRL */
++#define MT_WF_PHY_BAND_BASE			0x83080000
++#define MT_WF_PHY_BAND(_band, ofs)		(MT_WF_PHY_BAND_BASE + \
+ 						 ((_band) << 20) + (ofs))
+ 
+-#define MT_WF_PHYRX_BAND_GID_TAB_VLD0(_band)	MT_WF_PHYRX_BAND(_band, 0x1054)
+-#define MT_WF_PHYRX_BAND_GID_TAB_VLD1(_band)	MT_WF_PHYRX_BAND(_band, 0x1058)
+-#define MT_WF_PHYRX_BAND_GID_TAB_POS0(_band)	MT_WF_PHYRX_BAND(_band, 0x105c)
+-#define MT_WF_PHYRX_BAND_GID_TAB_POS1(_band)	MT_WF_PHYRX_BAND(_band, 0x1060)
+-#define MT_WF_PHYRX_BAND_GID_TAB_POS2(_band)	MT_WF_PHYRX_BAND(_band, 0x1064)
+-#define MT_WF_PHYRX_BAND_GID_TAB_POS3(_band)	MT_WF_PHYRX_BAND(_band, 0x1068)
++#define MT_WF_PHYRX_BAND_GID_TAB_VLD0(_band)	MT_WF_PHY_BAND(_band, 0x1054)
++#define MT_WF_PHYRX_BAND_GID_TAB_VLD1(_band)	MT_WF_PHY_BAND(_band, 0x1058)
++#define MT_WF_PHYRX_BAND_GID_TAB_POS0(_band)	MT_WF_PHY_BAND(_band, 0x105c)
++#define MT_WF_PHYRX_BAND_GID_TAB_POS1(_band)	MT_WF_PHY_BAND(_band, 0x1060)
++#define MT_WF_PHYRX_BAND_GID_TAB_POS2(_band)	MT_WF_PHY_BAND(_band, 0x1064)
++#define MT_WF_PHYRX_BAND_GID_TAB_POS3(_band)	MT_WF_PHY_BAND(_band, 0x1068)
+ 
+-#define MT_WF_PHYRX_BAND_RX_CTRL1(_band)	MT_WF_PHYRX_BAND(_band, 0x2004)
++/* PHYRX CTRL */
++#define MT_WF_PHYRX_BAND_RX_CTRL1(_band)	MT_WF_PHY_BAND(_band, 0x2004)
+ #define MT_WF_PHYRX_BAND_RX_CTRL1_IPI_EN	GENMASK(2, 0)
+ #define MT_WF_PHYRX_BAND_RX_CTRL1_STSCNT_EN	GENMASK(11, 9)
+ 
++/* PHYDFE CTRL */
++#define MT_WF_PHYDFE_BAND_TPC_CTRL_STAT0(_phy)	MT_WF_PHY_BAND(_phy, 0xe7a0)
++#define MT_WF_PHY_TPC_POWER_TMAC		GENMASK(15, 8)
++#define MT_WF_PHY_TPC_POWER_RMAC		GENMASK(23, 16)
++#define MT_WF_PHY_TPC_POWER_TSSI		GENMASK(31, 24)
++
+ /* PHYRX CSD BAND */
+-#define MT_WF_PHYRX_CSD_BAND_RXTD12(_band)		MT_WF_PHYRX_BAND(_band, 0x8230)
++#define MT_WF_PHYRX_CSD_BAND_RXTD12(_band)		MT_WF_PHY_BAND(_band, 0x8230)
+ #define MT_WF_PHYRX_CSD_BAND_RXTD12_IRPI_SW_CLR_ONLY	BIT(18)
+ #define MT_WF_PHYRX_CSD_BAND_RXTD12_IRPI_SW_CLR		BIT(29)
+ 
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0032-mtk-wifi-mt76-mt7996-add-single-sku.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0032-mtk-wifi-mt76-mt7996-add-single-sku.patch
new file mode 100644
index 0000000..8fa492e
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0032-mtk-wifi-mt76-mt7996-add-single-sku.patch
@@ -0,0 +1,278 @@
+From b4f11e6e90be3509b4e7dad733405fbb1701807c Mon Sep 17 00:00:00 2001
+From: "Allen.Ye" <allen.ye@mediatek.com>
+Date: Mon, 10 Jul 2023 19:56:16 +0800
+Subject: [PATCH 032/120] mtk: wifi: mt76: mt7996: add single sku
+
+Add single sku and default enable sku.
+
+Signed-off-by: Allen.Ye <allen.ye@mediatek.com>
+Change-Id: Ib5e1ea410d78fe4a60c6be103694475c8441b748
+---
+ eeprom.c          | 50 ++++++++++++++++++++++++++++++++++++++++++-----
+ mt76.h            |  9 +++++++++
+ mt76_connac_mcu.c |  2 +-
+ mt7996/init.c     |  2 ++
+ mt7996/main.c     |  9 +++++++++
+ mt7996/mcu.c      | 41 ++++++++++++++++++++++++++++++++++----
+ mt7996/mt7996.h   |  1 +
+ 7 files changed, 104 insertions(+), 10 deletions(-)
+
+diff --git a/eeprom.c b/eeprom.c
+index a0047d791..c3650bcc7 100644
+--- a/eeprom.c
++++ b/eeprom.c
+@@ -335,6 +335,7 @@ mt76_apply_multi_array_limit(s8 *pwr, size_t pwr_len, s8 pwr_num,
+ s8 mt76_get_rate_power_limits(struct mt76_phy *phy,
+ 			      struct ieee80211_channel *chan,
+ 			      struct mt76_power_limits *dest,
++			      struct mt76_power_path_limits *dest_path,
+ 			      s8 target_power)
+ {
+ 	struct mt76_dev *dev = phy->dev;
+@@ -342,16 +343,20 @@ s8 mt76_get_rate_power_limits(struct mt76_phy *phy,
+ 	const __be32 *val;
+ 	char name[16];
+ 	u32 mcs_rates = dev->drv->mcs_rates;
+-	u32 ru_rates = ARRAY_SIZE(dest->ru[0]);
+ 	char band;
+ 	size_t len;
+-	s8 max_power = 0;
++	s8 max_power = -127;
++	s8 max_power_backoff = -127;
+ 	s8 txs_delta;
++	int n_chains = hweight16(phy->chainmask);
++	s8 target_power_combine = target_power + mt76_tx_power_nss_delta(n_chains);
+ 
+ 	if (!mcs_rates)
+-		mcs_rates = 10;
++		mcs_rates = 12;
+ 
+ 	memset(dest, target_power, sizeof(*dest));
++	if (dest_path != NULL)
++		memset(dest_path, 0, sizeof(*dest_path));
+ 
+ 	if (!IS_ENABLED(CONFIG_OF))
+ 		return target_power;
+@@ -399,12 +404,47 @@ s8 mt76_get_rate_power_limits(struct mt76_phy *phy,
+ 				     ARRAY_SIZE(dest->mcs), val, len,
+ 				     target_power, txs_delta, &max_power);
+ 
+-	val = mt76_get_of_array(np, "rates-ru", &len, ru_rates + 1);
++	val = mt76_get_of_array(np, "rates-ru", &len, ARRAY_SIZE(dest->ru[0]) + 1);
+ 	mt76_apply_multi_array_limit(dest->ru[0], ARRAY_SIZE(dest->ru[0]),
+ 				     ARRAY_SIZE(dest->ru), val, len,
+ 				     target_power, txs_delta, &max_power);
+ 
+-	return max_power;
++	val = mt76_get_of_array(np, "rates-eht", &len, ARRAY_SIZE(dest->eht[0]) + 1);
++	mt76_apply_multi_array_limit(dest->eht[0], ARRAY_SIZE(dest->eht[0]),
++				     ARRAY_SIZE(dest->eht), val, len,
++				     target_power, txs_delta, &max_power);
++
++	if (dest_path == NULL)
++		return max_power;
++
++	max_power_backoff = max_power;
++
++	val = mt76_get_of_array(np, "paths-cck", &len, ARRAY_SIZE(dest_path->cck));
++	mt76_apply_array_limit(dest_path->cck, ARRAY_SIZE(dest_path->cck), val,
++			       target_power_combine, txs_delta, &max_power_backoff);
++
++	val = mt76_get_of_array(np, "paths-ofdm", &len, ARRAY_SIZE(dest_path->ofdm));
++	mt76_apply_array_limit(dest_path->ofdm, ARRAY_SIZE(dest_path->ofdm), val,
++			       target_power_combine, txs_delta, &max_power_backoff);
++
++	val = mt76_get_of_array(np, "paths-ofdm-bf", &len, ARRAY_SIZE(dest_path->ofdm_bf));
++	mt76_apply_array_limit(dest_path->ofdm_bf, ARRAY_SIZE(dest_path->ofdm_bf), val,
++			       target_power_combine, txs_delta, &max_power_backoff);
++
++	val = mt76_get_of_array(np, "paths-ru", &len, ARRAY_SIZE(dest_path->ru[0]) + 1);
++	mt76_apply_multi_array_limit(dest_path->ru[0], ARRAY_SIZE(dest_path->ru[0]),
++				     ARRAY_SIZE(dest_path->ru), val, len,
++				     target_power_combine, txs_delta, &max_power_backoff);
++
++	val = mt76_get_of_array(np, "paths-ru-bf", &len, ARRAY_SIZE(dest_path->ru_bf[0]) + 1);
++	mt76_apply_multi_array_limit(dest_path->ru_bf[0], ARRAY_SIZE(dest_path->ru_bf[0]),
++				     ARRAY_SIZE(dest_path->ru_bf), val, len,
++				     target_power_combine, txs_delta, &max_power_backoff);
++
++	if (max_power_backoff == target_power_combine)
++		return max_power;
++
++	return max_power_backoff;
+ }
+ EXPORT_SYMBOL_GPL(mt76_get_rate_power_limits);
+ 
+diff --git a/mt76.h b/mt76.h
+index 14c5fcb1e..630b39038 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -1054,6 +1054,14 @@ struct mt76_power_limits {
+ 	s8 eht[16][16];
+ };
+ 
++struct mt76_power_path_limits {
++	s8 cck[5];
++	s8 ofdm[5];
++	s8 ofdm_bf[4];
++	s8 ru[16][15];
++	s8 ru_bf[16][15];
++};
++
+ struct mt76_ethtool_worker_info {
+ 	u64 *data;
+ 	int idx;
+@@ -1664,6 +1672,7 @@ mt76_find_channel_node(struct device_node *np, struct ieee80211_channel *chan);
+ s8 mt76_get_rate_power_limits(struct mt76_phy *phy,
+ 			      struct ieee80211_channel *chan,
+ 			      struct mt76_power_limits *dest,
++			      struct mt76_power_path_limits *dest_path,
+ 			      s8 target_power);
+ 
+ static inline bool mt76_queue_is_wed_tx_free(struct mt76_queue *q)
+diff --git a/mt76_connac_mcu.c b/mt76_connac_mcu.c
+index 768c4b9a9..6dabb7fdc 100644
+--- a/mt76_connac_mcu.c
++++ b/mt76_connac_mcu.c
+@@ -2150,7 +2150,7 @@ mt76_connac_mcu_rate_txpower_band(struct mt76_phy *phy,
+ 			sar_power = mt76_get_sar_power(phy, &chan, reg_power);
+ 
+ 			mt76_get_rate_power_limits(phy, &chan, limits,
+-						   sar_power);
++						   NULL, sar_power);
+ 
+ 			tx_power_tlv.last_msg = ch_list[idx] == last_ch;
+ 			sku_tlbv.channel = ch_list[idx];
+diff --git a/mt7996/init.c b/mt7996/init.c
+index 23a9b88bf..5f937b266 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -295,6 +295,7 @@ static void __mt7996_init_txpower(struct mt7996_phy *phy,
+ 	int nss_delta = mt76_tx_power_nss_delta(nss);
+ 	int pwr_delta = mt7996_eeprom_get_power_delta(dev, sband->band);
+ 	struct mt76_power_limits limits;
++	struct mt76_power_path_limits limits_path;
+ 
+ 	for (i = 0; i < sband->n_channels; i++) {
+ 		struct ieee80211_channel *chan = &sband->channels[i];
+@@ -303,6 +304,7 @@ static void __mt7996_init_txpower(struct mt7996_phy *phy,
+ 		target_power += pwr_delta;
+ 		target_power = mt76_get_rate_power_limits(phy->mt76, chan,
+ 							  &limits,
++							  &limits_path,
+ 							  target_power);
+ 		target_power += nss_delta;
+ 		target_power = DIV_ROUND_UP(target_power, 2);
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 93a6eeded..f72e43ea4 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -85,6 +85,15 @@ int mt7996_run(struct ieee80211_hw *hw)
+ 	if (ret)
+ 		goto out;
+ 
++#ifdef CONFIG_MTK_DEBUG
++	ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_SKU_POWER_LIMIT_CTRL,
++					   !dev->dbg.sku_disable);
++#else
++	ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_SKU_POWER_LIMIT_CTRL, true);
++#endif
++	if (ret)
++		goto out;
++
+ 	set_bit(MT76_STATE_RUNNING, &phy->mt76->state);
+ 
+ 	ieee80211_queue_delayed_work(hw, &phy->mt76->mac_work,
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index a7fbaf025..43505a85c 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -4501,6 +4501,7 @@ int mt7996_mcu_wed_rro_reset_sessions(struct mt7996_dev *dev, u16 id)
+ int mt7996_mcu_set_txpower_sku(struct mt7996_phy *phy)
+ {
+ #define TX_POWER_LIMIT_TABLE_RATE	0
++#define TX_POWER_LIMIT_TABLE_PATH	1
+ 	struct mt7996_dev *dev = phy->dev;
+ 	struct mt76_phy *mphy = phy->mt76;
+ 	struct ieee80211_hw *hw = mphy->hw;
+@@ -4514,22 +4515,23 @@ int mt7996_mcu_set_txpower_sku(struct mt7996_phy *phy)
+ 		u8 band_idx;
+ 	} __packed req = {
+ 		.tag = cpu_to_le16(UNI_TXPOWER_POWER_LIMIT_TABLE_CTRL),
+-		.len = cpu_to_le16(sizeof(req) + MT7996_SKU_RATE_NUM - 4),
++		.len = cpu_to_le16(sizeof(req) + MT7996_SKU_PATH_NUM - 4),
+ 		.power_ctrl_id = UNI_TXPOWER_POWER_LIMIT_TABLE_CTRL,
+ 		.power_limit_type = TX_POWER_LIMIT_TABLE_RATE,
+ 		.band_idx = phy->mt76->band_idx,
+ 	};
+ 	struct mt76_power_limits la = {};
++	struct mt76_power_path_limits la_path = {};
+ 	struct sk_buff *skb;
+-	int i, tx_power;
++	int i, ret, tx_power;
+ 
+ 	tx_power = mt7996_get_power_bound(phy, hw->conf.power_level);
+ 	tx_power = mt76_get_rate_power_limits(mphy, mphy->chandef.chan,
+-					      &la, tx_power);
++					      &la, &la_path, tx_power);
+ 	mphy->txpower_cur = tx_power;
+ 
+ 	skb = mt76_mcu_msg_alloc(&dev->mt76, NULL,
+-				 sizeof(req) + MT7996_SKU_RATE_NUM);
++				 sizeof(req) + MT7996_SKU_PATH_NUM);
+ 	if (!skb)
+ 		return -ENOMEM;
+ 
+@@ -4553,6 +4555,37 @@ int mt7996_mcu_set_txpower_sku(struct mt7996_phy *phy)
+ 	/* eht */
+ 	skb_put_data(skb, &la.eht[0], sizeof(la.eht));
+ 
++	/* padding */
++	skb_put_zero(skb, MT7996_SKU_PATH_NUM - MT7996_SKU_RATE_NUM);
++
++	ret = mt76_mcu_skb_send_msg(&dev->mt76, skb,
++				    MCU_WM_UNI_CMD(TXPOWER), true);
++	if (ret)
++		return ret;
++
++	/* only set per-path power table when it's configured */
++	if (!la_path.ofdm[0])
++		return 0;
++
++	skb = mt76_mcu_msg_alloc(&dev->mt76, NULL,
++				 sizeof(req) + MT7996_SKU_PATH_NUM);
++	if (!skb)
++		return -ENOMEM;
++	req.power_limit_type = TX_POWER_LIMIT_TABLE_PATH;
++
++	skb_put_data(skb, &req, sizeof(req));
++	skb_put_data(skb, &la_path.cck, sizeof(la_path.cck));
++	skb_put_data(skb, &la_path.ofdm, sizeof(la_path.ofdm));
++	skb_put_data(skb, &la_path.ofdm_bf, sizeof(la_path.ofdm_bf));
++
++	for (i = 0; i < 32; i++) {
++		bool bf = i % 2;
++		u8 idx = i / 2;
++		s8 *buf = bf ? la_path.ru_bf[idx] : la_path.ru[idx];
++
++		skb_put_data(skb, buf, sizeof(la_path.ru[0]));
++	}
++
+ 	return mt76_mcu_skb_send_msg(&dev->mt76, skb,
+ 				     MCU_WM_UNI_CMD(TXPOWER), true);
+ }
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index fb4d9708a..bdb3f3797 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -71,6 +71,7 @@
+ #define MT7996_CFEND_RATE_11B		0x03	/* 11B LP, 11M */
+ 
+ #define MT7996_SKU_RATE_NUM		417
++#define MT7996_SKU_PATH_NUM		494
+ 
+ #define MT7996_MAX_TWT_AGRT		16
+ #define MT7996_MAX_STA_TWT_AGRT		8
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0033-mtk-wifi-mt76-mt7996-add-binfile-mode-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0033-mtk-wifi-mt76-mt7996-add-binfile-mode-support.patch
new file mode 100644
index 0000000..12cf337
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0033-mtk-wifi-mt76-mt7996-add-binfile-mode-support.patch
@@ -0,0 +1,369 @@
+From d5600018358f4d2608ecea3e19673643c5680944 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Fri, 31 Mar 2023 11:36:34 +0800
+Subject: [PATCH 033/120] mtk: wifi: mt76: mt7996: add binfile mode support
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Change-Id: I06369a46f75c0707d9ababd8ade70d79a89b1a1c
+
+Fix binfile cannot sync precal data to atenl
+Binfile is viewed as efuse mode in atenl, so atenl does not allocate
+precal memory for its eeprom file
+Use mtd offset == 0xFFFFFFFF to determine whether it is binfile or flash mode
+Add support for loading precal in binfile mode
+
+Align upstream
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Change-Id: I4c8fd2b0a9956d2ee961b70cd28973e2e9410aca
+---
+ eeprom.c             |  25 +++++++++++
+ mt76.h               |   3 ++
+ mt7996/eeprom.c      | 103 ++++++++++++++++++++++++++++++++++++++++---
+ mt7996/eeprom.h      |   7 +++
+ mt7996/mt7996.h      |   4 ++
+ mt7996/mtk_debugfs.c |  41 +++++++++++++++++
+ testmode.h           |   2 +-
+ 7 files changed, 179 insertions(+), 6 deletions(-)
+
+diff --git a/eeprom.c b/eeprom.c
+index c3650bcc7..47edb21e5 100644
+--- a/eeprom.c
++++ b/eeprom.c
+@@ -161,6 +161,31 @@ static int mt76_get_of_eeprom(struct mt76_dev *dev, void *eep, int len)
+ 	return mt76_get_of_data_from_nvmem(dev, eep, "eeprom", len);
+ }
+ 
++bool mt76_check_bin_file_mode(struct mt76_dev *dev)
++{
++	struct device_node *np = dev->dev->of_node;
++	const char *bin_file_name = NULL;
++
++	if (!np)
++		return false;
++
++	of_property_read_string(np, "bin_file_name", &bin_file_name);
++
++	dev->bin_file_name = bin_file_name;
++	if (dev->bin_file_name) {
++		dev_info(dev->dev, "Using bin file %s\n", dev->bin_file_name);
++#ifdef CONFIG_NL80211_TESTMODE
++		dev->test_mtd.name = devm_kstrdup(dev->dev, bin_file_name, GFP_KERNEL);
++		dev->test_mtd.offset = -1;
++#endif
++	}
++
++	of_node_put(np);
++
++	return dev->bin_file_name ? true : false;
++}
++EXPORT_SYMBOL_GPL(mt76_check_bin_file_mode);
++
+ void
+ mt76_eeprom_override(struct mt76_phy *phy)
+ {
+diff --git a/mt76.h b/mt76.h
+index 630b39038..b2cc1085c 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -949,6 +949,8 @@ struct mt76_dev {
+ 		struct mt76_usb usb;
+ 		struct mt76_sdio sdio;
+ 	};
++
++	const char *bin_file_name;
+ };
+ 
+ /* per-phy stats.  */
+@@ -1220,6 +1222,7 @@ void mt76_eeprom_override(struct mt76_phy *phy);
+ int mt76_get_of_data_from_mtd(struct mt76_dev *dev, void *eep, int offset, int len);
+ int mt76_get_of_data_from_nvmem(struct mt76_dev *dev, void *eep,
+ 				const char *cell_name, int len);
++bool mt76_check_bin_file_mode(struct mt76_dev *dev);
+ 
+ struct mt76_queue *
+ mt76_init_queue(struct mt76_dev *dev, int qid, int idx, int n_desc,
+diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
+index 726607126..f2e201bfe 100644
+--- a/mt7996/eeprom.c
++++ b/mt7996/eeprom.c
+@@ -82,10 +82,17 @@ static int mt7996_check_eeprom(struct mt7996_dev *dev)
+ 	}
+ }
+ 
+-static char *mt7996_eeprom_name(struct mt7996_dev *dev)
++const char *mt7996_eeprom_name(struct mt7996_dev *dev)
+ {
+-	if (dev->testmode_enable)
+-		return MT7996_EEPROM_DEFAULT_TM;
++	if (dev->bin_file_mode)
++		return dev->mt76.bin_file_name;
++
++	if (dev->testmode_enable) {
++		if (is_mt7992(&dev->mt76))
++			return MT7992_EEPROM_DEFAULT_TM;
++		else
++			return MT7996_EEPROM_DEFAULT_TM;
++	}
+ 
+ 	switch (mt76_chip(&dev->mt76)) {
+ 	case 0x7990:
+@@ -152,7 +159,10 @@ mt7996_eeprom_load_default(struct mt7996_dev *dev)
+ 		return ret;
+ 
+ 	if (!fw || !fw->data) {
+-		dev_err(dev->mt76.dev, "Invalid default bin\n");
++		if (dev->bin_file_mode)
++			dev_err(dev->mt76.dev, "Invalid bin (bin file mode)\n");
++		else
++			dev_err(dev->mt76.dev, "Invalid default bin\n");
+ 		ret = -EINVAL;
+ 		goto out;
+ 	}
+@@ -166,18 +176,45 @@ out:
+ 	return ret;
+ }
+ 
++static int mt7996_eeprom_load_flash(struct mt7996_dev *dev)
++{
++	int ret = 1;
++
++	/* return > 0 for load success, return 0 for load failed, return < 0 for non memory */
++	dev->bin_file_mode = mt76_check_bin_file_mode(&dev->mt76);
++	if (dev->bin_file_mode) {
++		dev->mt76.eeprom.size = MT7996_EEPROM_SIZE;
++		dev->mt76.eeprom.data = devm_kzalloc(dev->mt76.dev, dev->mt76.eeprom.size,
++						     GFP_KERNEL);
++		if (!dev->mt76.eeprom.data)
++			return -ENOMEM;
++
++		if (mt7996_eeprom_load_default(dev))
++			return 0;
++
++		if (mt7996_check_eeprom(dev))
++			return 0;
++	} else {
++		ret = mt76_eeprom_init(&dev->mt76, MT7996_EEPROM_SIZE);
++	}
++
++	return ret;
++}
++
+ int mt7996_eeprom_check_fw_mode(struct mt7996_dev *dev)
+ {
+ 	u8 *eeprom;
+ 	int ret;
+ 
+ 	/* load eeprom in flash or bin file mode to determine fw mode */
+-	ret = mt76_eeprom_init(&dev->mt76, MT7996_EEPROM_SIZE);
++	ret = mt7996_eeprom_load_flash(dev);
++
+ 	if (ret < 0)
+ 		return ret;
+ 
+ 	if (ret) {
+ 		dev->flash_mode = true;
++		dev->eeprom_mode = dev->bin_file_mode ? BIN_FILE_MODE : FLASH_MODE;
+ 		eeprom = dev->mt76.eeprom.data;
+ 		/* testmode enable priority: eeprom field > module parameter */
+ 		dev->testmode_enable = !mt7996_check_eeprom(dev) ? eeprom[MT_EE_TESTMODE_EN] :
+@@ -211,6 +248,7 @@ static int mt7996_eeprom_load(struct mt7996_dev *dev)
+ 			if (ret && ret != -EINVAL)
+ 				return ret;
+ 		}
++		dev->eeprom_mode = EFUSE_MODE;
+ 	}
+ 
+ 	return mt7996_check_eeprom(dev);
+@@ -337,6 +375,59 @@ int mt7996_eeprom_parse_hw_cap(struct mt7996_dev *dev, struct mt7996_phy *phy)
+ 	return mt7996_eeprom_parse_band_config(phy);
+ }
+ 
++static int
++mt7996_eeprom_load_precal_binfile(struct mt7996_dev *dev, u32 offs, u32 size)
++{
++	const struct firmware *fw = NULL;
++	int ret;
++
++	ret = request_firmware(&fw, dev->mt76.bin_file_name, dev->mt76.dev);
++	if (ret)
++		return ret;
++
++	if (!fw || !fw->data) {
++		dev_err(dev->mt76.dev, "Invalid bin (bin file mode), load precal fail\n");
++		ret = -EINVAL;
++		goto out;
++	}
++
++	memcpy(dev->cal, fw->data + offs, size);
++
++out:
++	release_firmware(fw);
++
++	return ret;
++}
++
++static int mt7996_eeprom_load_precal(struct mt7996_dev *dev)
++{
++	struct mt76_dev *mdev = &dev->mt76;
++	u8 *eeprom = mdev->eeprom.data;
++	u32 offs = MT_EE_DO_PRE_CAL;
++	u32 size, val = eeprom[offs];
++	int ret;
++
++	mt7996_eeprom_init_precal(dev);
++
++	if (!dev->flash_mode || !val)
++		return 0;
++
++	size = MT_EE_CAL_GROUP_SIZE + MT_EE_CAL_DPD_SIZE;
++
++	dev->cal = devm_kzalloc(mdev->dev, size, GFP_KERNEL);
++	if (!dev->cal)
++		return -ENOMEM;
++
++	if (dev->bin_file_mode)
++		return mt7996_eeprom_load_precal_binfile(dev, MT_EE_PRECAL, size);
++
++	ret = mt76_get_of_data_from_mtd(mdev, dev->cal, offs, size);
++	if (!ret)
++		return ret;
++
++	return mt76_get_of_data_from_nvmem(mdev, dev->cal, "precal", size);
++}
++
+ int mt7996_eeprom_init(struct mt7996_dev *dev)
+ {
+ 	int ret;
+@@ -351,6 +442,8 @@ int mt7996_eeprom_init(struct mt7996_dev *dev)
+ 			return ret;
+ 
+ 		dev_warn(dev->mt76.dev, "eeprom load fail, use default bin\n");
++		dev->bin_file_mode = false;
++		dev->eeprom_mode = DEFAULT_BIN_MODE;
+ 		ret = mt7996_eeprom_load_default(dev);
+ 		if (ret)
+ 			return ret;
+diff --git a/mt7996/eeprom.h b/mt7996/eeprom.h
+index 23d4929d7..8b555aebd 100644
+--- a/mt7996/eeprom.h
++++ b/mt7996/eeprom.h
+@@ -100,6 +100,13 @@ enum mt7996_eeprom_band {
+ 	MT_EE_BAND_SEL_6GHZ,
+ };
+ 
++enum mt7915_eeprom_mode {
++	DEFAULT_BIN_MODE,
++	EFUSE_MODE,
++	FLASH_MODE,
++	BIN_FILE_MODE,
++};
++
+ static inline int
+ mt7996_get_channel_group_5g(int channel)
+ {
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index bdb3f3797..bd9e790e5 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -62,6 +62,7 @@
+ #define MT7992_EEPROM_DEFAULT_24	"mediatek/mt7996/mt7992_eeprom_24_2i5i.bin"
+ #define MT7992_EEPROM_DEFAULT_23	"mediatek/mt7996/mt7992_eeprom_23_2i5i.bin"
+ #define MT7992_EEPROM_DEFAULT_23_EXT	"mediatek/mt7996/mt7992_eeprom_23_2e5e.bin"
++#define MT7992_EEPROM_DEFAULT_TM	"mediatek/mt7996/mt7992_eeprom_tm.bin"
+ #define MT7996_EEPROM_SIZE		7680
+ #define MT7996_EEPROM_BLOCK_SIZE	16
+ #define MT7996_TOKEN_SIZE		16384
+@@ -389,6 +390,8 @@ struct mt7996_dev {
+ 	} wed_rro;
+ 
+ 	bool testmode_enable;
++	bool bin_file_mode;
++	u8 eeprom_mode;
+ 
+ 	bool ibf;
+ 	u8 fw_debug_wm;
+@@ -516,6 +519,7 @@ irqreturn_t mt7996_irq_handler(int irq, void *dev_instance);
+ u64 __mt7996_get_tsf(struct ieee80211_hw *hw, struct mt7996_vif *mvif);
+ int mt7996_register_device(struct mt7996_dev *dev);
+ void mt7996_unregister_device(struct mt7996_dev *dev);
++const char *mt7996_eeprom_name(struct mt7996_dev *dev);
+ int mt7996_eeprom_init(struct mt7996_dev *dev);
+ int mt7996_eeprom_check_fw_mode(struct mt7996_dev *dev);
+ int mt7996_eeprom_parse_hw_cap(struct mt7996_dev *dev, struct mt7996_phy *phy);
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index c47d65c96..09652ef52 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -2739,6 +2739,44 @@ static const struct file_operations mt7996_txpower_path_fops = {
+ 	.llseek = default_llseek,
+ };
+ 
++static int mt7996_show_eeprom_mode(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	struct mt76_dev *mdev = &dev->mt76;
++#ifdef CONFIG_NL80211_TESTMODE
++	const char *mtd_name = mdev->test_mtd.name;
++	u32 mtd_offset = mdev->test_mtd.offset;
++#else
++	const char *mtd_name = NULL;
++	u32 mtd_offset;
++#endif
++
++	seq_printf(s, "Current eeprom mode:\n");
++
++	switch (dev->eeprom_mode) {
++	case DEFAULT_BIN_MODE:
++		seq_printf(s, "   default bin mode\n   filename = %s\n", mt7996_eeprom_name(dev));
++		break;
++	case EFUSE_MODE:
++		seq_printf(s, "   efuse mode\n");
++		break;
++	case FLASH_MODE:
++		if (mtd_name)
++			seq_printf(s, "   flash mode\n   mtd name = %s\n   flash offset = 0x%x\n",
++				   mtd_name, mtd_offset);
++		else
++			seq_printf(s, "   flash mode\n");
++		break;
++	case BIN_FILE_MODE:
++		seq_printf(s, "   bin file mode\n   filename = %s\n", mt7996_eeprom_name(dev));
++		break;
++	default:
++		break;
++	}
++
++	return 0;
++}
++
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -2807,6 +2845,9 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ 	debugfs_create_file("txpower_sku", 0600, dir, phy, &mt7996_txpower_sku_fops);
+ 	debugfs_create_file("txpower_path", 0600, dir, phy, &mt7996_txpower_path_fops);
+ 
++	debugfs_create_devm_seqfile(dev->mt76.dev, "eeprom_mode", dir,
++				    mt7996_show_eeprom_mode);
++
+ 	debugfs_create_devm_seqfile(dev->mt76.dev, "wtbl_info", dir,
+ 				    mt7996_wtbl_read);
+ 
+diff --git a/testmode.h b/testmode.h
+index d6601cdcf..5d677f8c1 100644
+--- a/testmode.h
++++ b/testmode.h
+@@ -16,7 +16,7 @@
+  * @MT76_TM_ATTR_RESET: reset parameters to default (flag)
+  * @MT76_TM_ATTR_STATE: test state (u32), see &enum mt76_testmode_state
+  *
+- * @MT76_TM_ATTR_MTD_PART: mtd partition used for eeprom data (string)
++ * @MT76_TM_ATTR_MTD_PART: mtd partition or binfile used for eeprom data (string)
+  * @MT76_TM_ATTR_MTD_OFFSET: offset of eeprom data within the partition (u32)
+  * @MT76_TM_ATTR_BAND_IDX: band idx of the chip (u8)
+  *
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0034-mtk-wifi-mt76-mt7996-add-normal-mode-pre-calibration.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0034-mtk-wifi-mt76-mt7996-add-normal-mode-pre-calibration.patch
new file mode 100644
index 0000000..a8995ac
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0034-mtk-wifi-mt76-mt7996-add-normal-mode-pre-calibration.patch
@@ -0,0 +1,285 @@
+From 46123ef3b77df16b043b89dd260e30cd82b3bd56 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Wed, 1 Mar 2023 12:12:51 +0800
+Subject: [PATCH 034/120] mtk: wifi: mt76: mt7996: add normal mode
+ pre-calibration support
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt76_connac_mcu.h |   1 +
+ mt7996/eeprom.c   |   4 ++
+ mt7996/eeprom.h   |   2 +
+ mt7996/init.c     |   6 ++
+ mt7996/main.c     |   6 ++
+ mt7996/mcu.c      | 166 ++++++++++++++++++++++++++++++++++++++++++++++
+ mt7996/mt7996.h   |   3 +
+ 7 files changed, 188 insertions(+)
+
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 26a824846..d8830dc25 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1278,6 +1278,7 @@ enum {
+ 	MCU_UNI_CMD_PP = 0x38,
+ 	MCU_UNI_CMD_FIXED_RATE_TABLE = 0x40,
+ 	MCU_UNI_CMD_TESTMODE_CTRL = 0x46,
++	MCU_UNI_CMD_PRECAL_RESULT = 0x47,
+ 	MCU_UNI_CMD_RRO = 0x57,
+ 	MCU_UNI_CMD_OFFCH_SCAN_CTRL = 0x58,
+ 	MCU_UNI_CMD_PER_STA_INFO = 0x6d,
+diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
+index f2e201bfe..fe8b25352 100644
+--- a/mt7996/eeprom.c
++++ b/mt7996/eeprom.c
+@@ -449,6 +449,10 @@ int mt7996_eeprom_init(struct mt7996_dev *dev)
+ 			return ret;
+ 	}
+ 
++	ret = mt7996_eeprom_load_precal(dev);
++	if (ret)
++		return ret;
++
+ 	ret = mt7996_eeprom_parse_hw_cap(dev, &dev->phy);
+ 	if (ret < 0)
+ 		return ret;
+diff --git a/mt7996/eeprom.h b/mt7996/eeprom.h
+index 8b555aebd..8f0f87b6b 100644
+--- a/mt7996/eeprom.h
++++ b/mt7996/eeprom.h
+@@ -25,6 +25,8 @@ enum mt7996_eeprom_field {
+ 	MT_EE_TX0_POWER_6G =	0x1310,
+ 
+ 	__MT_EE_MAX =	0x1dff,
++	/* 0x1e10 ~ 0x2d644 used to save group cal data */
++	MT_EE_PRECAL =		0x1e10,
+ };
+ 
+ #define MT_EE_WIFI_CONF0_TX_PATH		GENMASK(2, 0)
+diff --git a/mt7996/init.c b/mt7996/init.c
+index 5f937b266..c135da9c4 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -984,6 +984,12 @@ static int mt7996_init_hardware(struct mt7996_dev *dev)
+ 	if (ret < 0)
+ 		return ret;
+ 
++	if (dev->flash_mode) {
++		ret = mt7996_mcu_apply_group_cal(dev);
++		if (ret)
++			return ret;
++	}
++
+ 	/* Beacon and mgmt frames should occupy wcid 0 */
+ 	idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT7996_WTBL_STA);
+ 	if (idx)
+diff --git a/mt7996/main.c b/mt7996/main.c
+index f72e43ea4..7505037b4 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -333,6 +333,12 @@ int mt7996_set_channel(struct mt7996_phy *phy)
+ 
+ 	mt76_set_channel(phy->mt76);
+ 
++	if (dev->flash_mode) {
++		ret = mt7996_mcu_apply_tx_dpd(phy);
++		if (ret)
++			goto out;
++	}
++
+ 	if (mt76_testmode_enabled(phy->mt76) || phy->mt76->test.bf_en) {
+ 		mt7996_tm_update_channel(phy);
+ 		goto out;
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 43505a85c..a010a86cc 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -3631,6 +3631,172 @@ int mt7996_mcu_get_eeprom_free_block(struct mt7996_dev *dev, u8 *block_num)
+ 	return 0;
+ }
+ 
++static int mt7996_mcu_set_pre_cal(struct mt7996_dev *dev, u32 idx,
++				  u8 *cal, u32 len, u32 cal_id)
++{
++#define PRECAL_CMD_PRE_CAL_RESULT	0x0
++	struct {
++		/* fixed field */
++		u8 action;
++		u8 dest;
++		u8 attribute;
++		u8 tag_num;
++
++		__le16 tag;
++		__le16 len;
++
++		__le32 cal_id;
++		s8 precal;
++		u8 band;
++		u8 rsv[2];
++		__le32 idx;
++		__le32 cal_len;
++	} req = {
++		.tag = cpu_to_le16(PRECAL_CMD_PRE_CAL_RESULT),
++		.len = cpu_to_le16(sizeof(req) - 4 + len),
++		.cal_id = cpu_to_le32(cal_id),
++		.idx = cpu_to_le32(idx),
++		.cal_len = cpu_to_le32(len),
++	};
++	struct sk_buff *skb;
++
++	if (!len)
++		return 0;
++
++	skb = mt76_mcu_msg_alloc(&dev->mt76, NULL, sizeof(req) + len);
++	if (!skb)
++		return -ENOMEM;
++
++	skb_put_data(skb, &req, sizeof(req));
++	skb_put_data(skb, cal, len);
++
++	return mt76_mcu_skb_send_msg(&dev->mt76, skb, MCU_WM_UNI_CMD(PRECAL_RESULT), false);
++}
++
++int mt7996_mcu_apply_group_cal(struct mt7996_dev *dev)
++{
++	u8 *cal = dev->cal, *eeprom = dev->mt76.eeprom.data;
++	u32 idx = 0, total_idx = MT_EE_CAL_GROUP_SIZE / MT_EE_CAL_UNIT;
++	u32 offs = MT_EE_DO_PRE_CAL;
++	int ret = 0;
++
++	if (!(eeprom[offs] & MT_EE_WIFI_CAL_GROUP))
++		return 0;
++
++	for (idx = 0; idx < total_idx; idx++, cal += MT_EE_CAL_UNIT) {
++		ret = mt7996_mcu_set_pre_cal(dev, idx, cal, MT_EE_CAL_UNIT, RF_PRE_CAL);
++		if (ret)
++			goto out;
++	}
++
++	ret = mt7996_mcu_set_pre_cal(dev, total_idx, cal,
++				     MT_EE_CAL_GROUP_SIZE % MT_EE_CAL_UNIT, RF_PRE_CAL);
++
++out:
++	return ret;
++}
++
++int mt7996_mcu_apply_tx_dpd(struct mt7996_phy *phy)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct mt76_phy *mphy = phy->mt76;
++	struct cfg80211_chan_def *chandef = &phy->mt76->chandef;
++	enum nl80211_band band = chandef->chan->band;
++	enum nl80211_chan_width bw = chandef->width;
++	const struct ieee80211_channel *chan_list;
++	u32 cal_id, chan_list_size, base_offset = 0, offs = MT_EE_DO_PRE_CAL;
++	u32 dpd_size_2g, dpd_size_5g, per_chan_size = DPD_PER_CH_BW20_SIZE;
++	u16 channel = ieee80211_frequency_to_channel(chandef->center_freq1);
++	u8 dpd_mask, *cal = dev->cal, *eeprom = dev->mt76.eeprom.data;
++	int idx, i, ret;
++
++	dpd_size_2g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_2GHZ);
++	dpd_size_5g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_5GHZ);
++
++	switch (band) {
++	case NL80211_BAND_2GHZ:
++		dpd_mask = MT_EE_WIFI_CAL_DPD_2G;
++		/* channel 14 don't need DPD cal */
++		if (channel >= 1 && channel <= 4)
++			channel = 3;
++		else if (channel >= 5 && channel <= 9)
++			channel = 7;
++		else if (channel >= 10 && channel <= 13)
++			channel = 11;
++		else
++			return 0;
++		cal_id = RF_DPD_FLAT_CAL;
++		chan_list = dpd_2g_ch_list_bw20;
++		chan_list_size = dpd_2g_bw20_ch_num;
++		break;
++	case NL80211_BAND_5GHZ:
++		dpd_mask = MT_EE_WIFI_CAL_DPD_5G;
++		cal_id = RF_DPD_FLAT_5G_CAL;
++		chan_list = mphy->sband_5g.sband.channels;
++		chan_list_size = mphy->sband_5g.sband.n_channels;
++		base_offset += dpd_size_2g;
++		if (bw == NL80211_CHAN_WIDTH_160) {
++			base_offset += mphy->sband_5g.sband.n_channels * DPD_PER_CH_BW20_SIZE;
++			per_chan_size = DPD_PER_CH_GT_BW20_SIZE;
++			cal_id = RF_DPD_FLAT_5G_MEM_CAL;
++			chan_list = dpd_5g_ch_list_bw160;
++			chan_list_size = dpd_5g_bw160_ch_num;
++		} else if (bw > NL80211_CHAN_WIDTH_20) {
++			/* apply (center channel - 2)'s dpd cal data for bw 40/80 channels */
++			channel -= 2;
++		}
++		break;
++	case NL80211_BAND_6GHZ:
++		dpd_mask = MT_EE_WIFI_CAL_DPD_6G;
++		cal_id = RF_DPD_FLAT_6G_CAL;
++		chan_list = mphy->sband_6g.sband.channels;
++		chan_list_size = mphy->sband_6g.sband.n_channels;
++		base_offset += dpd_size_2g + dpd_size_5g;
++		if (bw == NL80211_CHAN_WIDTH_160) {
++			base_offset += mphy->sband_6g.sband.n_channels * DPD_PER_CH_BW20_SIZE;
++			per_chan_size = DPD_PER_CH_GT_BW20_SIZE;
++			cal_id = RF_DPD_FLAT_6G_MEM_CAL;
++			chan_list = dpd_6g_ch_list_bw160;
++			chan_list_size = dpd_6g_bw160_ch_num;
++		} else if (bw == NL80211_CHAN_WIDTH_320) {
++			base_offset += mphy->sband_6g.sband.n_channels * DPD_PER_CH_BW20_SIZE +
++				       dpd_6g_bw160_ch_num * DPD_PER_CH_GT_BW20_SIZE;
++			per_chan_size = DPD_PER_CH_GT_BW20_SIZE;
++			cal_id = RF_DPD_FLAT_6G_MEM_CAL;
++			chan_list = dpd_6g_ch_list_bw320;
++			chan_list_size = dpd_6g_bw320_ch_num;
++		} else if (bw > NL80211_CHAN_WIDTH_20) {
++			/* apply (center channel - 2)'s dpd cal data for bw 40/80 channels */
++			channel -= 2;
++		}
++		break;
++	default:
++		dpd_mask = 0;
++		break;
++	}
++
++	if (!(eeprom[offs] & dpd_mask))
++		return 0;
++
++	for (idx = 0; idx < chan_list_size; idx++)
++		if (channel == chan_list[idx].hw_value)
++			break;
++	if (idx == chan_list_size)
++		return -EINVAL;
++
++	cal += MT_EE_CAL_GROUP_SIZE + base_offset + idx * per_chan_size;
++
++	for (i = 0; i < per_chan_size / MT_EE_CAL_UNIT; i++) {
++		ret = mt7996_mcu_set_pre_cal(dev, i, cal, MT_EE_CAL_UNIT, cal_id);
++		if (ret)
++			return ret;
++
++		cal += MT_EE_CAL_UNIT;
++	}
++
++	return ret;
++}
++
+ int mt7996_mcu_get_chip_config(struct mt7996_dev *dev, u32 *cap)
+ {
+ #define NIC_CAP	3
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index bd9e790e5..e99ad422f 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -611,6 +611,9 @@ void mt7996_mcu_exit(struct mt7996_dev *dev);
+ int mt7996_mcu_get_all_sta_info(struct mt7996_phy *phy, u16 tag);
+ int mt7996_mcu_wed_rro_reset_sessions(struct mt7996_dev *dev, u16 id);
+ int mt7996_mcu_set_tx_power_ctrl(struct mt7996_phy *phy, u8 power_ctrl_id, u8 data);
++int mt7996_mcu_get_tx_power_info(struct mt7996_phy *phy, u8 category, void *event);
++int mt7996_mcu_apply_group_cal(struct mt7996_dev *dev);
++int mt7996_mcu_apply_tx_dpd(struct mt7996_phy *phy);
+ #ifdef CONFIG_NL80211_TESTMODE
+ void mt7996_tm_rf_test_event(struct mt7996_dev *dev, struct sk_buff *skb);
+ #endif
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0035-mtk-wifi-mt76-testmode-add-testmode-ZWDFS-verificati.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0035-mtk-wifi-mt76-testmode-add-testmode-ZWDFS-verificati.patch
new file mode 100644
index 0000000..e2851d3
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0035-mtk-wifi-mt76-testmode-add-testmode-ZWDFS-verificati.patch
@@ -0,0 +1,490 @@
+From 7449c120879f807f7425b42efefa486e6a66d7f9 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Wed, 22 Mar 2023 11:19:52 +0800
+Subject: [PATCH 035/120] mtk: wifi: mt76: testmode: add testmode ZWDFS
+ verification support
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt76.h            |   8 ++
+ mt7996/mt7996.h   |   1 +
+ mt7996/testmode.c | 248 ++++++++++++++++++++++++++++++++++++++++++++--
+ mt7996/testmode.h |  44 ++++++++
+ testmode.c        |  22 +++-
+ tools/fields.c    |  15 +++
+ 6 files changed, 326 insertions(+), 12 deletions(-)
+
+diff --git a/mt76.h b/mt76.h
+index b2cc1085c..a75277fee 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -778,6 +778,14 @@ struct mt76_testmode_data {
+ 	} cfg;
+ 
+ 	u8 aid;
++
++	u8 offchan_ch;
++	u8 offchan_center_ch;
++	u8 offchan_bw;
++
++	u8 ipi_threshold;
++	u32 ipi_period;
++	u8 ipi_reset;
+ };
+ 
+ struct mt76_vif {
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index e99ad422f..e5ca30776 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -289,6 +289,7 @@ struct mt7996_phy {
+ 
+ 	struct mt76_mib_stats mib;
+ 	struct mt76_channel_state state_ts;
++	struct delayed_work ipi_work;
+ 
+ 	bool has_aux_rx;
+ 
+diff --git a/mt7996/testmode.c b/mt7996/testmode.c
+index a756ee10d..836211f98 100644
+--- a/mt7996/testmode.c
++++ b/mt7996/testmode.c
+@@ -17,6 +17,12 @@ enum {
+ 	TM_CHANGED_TX_LENGTH,
+ 	TM_CHANGED_TX_TIME,
+ 	TM_CHANGED_CFG,
++	TM_CHANGED_OFF_CHAN_CH,
++	TM_CHANGED_OFF_CHAN_CENTER_CH,
++	TM_CHANGED_OFF_CHAN_BW,
++	TM_CHANGED_IPI_THRESHOLD,
++	TM_CHANGED_IPI_PERIOD,
++	TM_CHANGED_IPI_RESET,
+ 
+ 	/* must be last */
+ 	NUM_TM_CHANGED
+@@ -29,20 +35,31 @@ static const u8 tm_change_map[] = {
+ 	[TM_CHANGED_TX_LENGTH] = MT76_TM_ATTR_TX_LENGTH,
+ 	[TM_CHANGED_TX_TIME] = MT76_TM_ATTR_TX_TIME,
+ 	[TM_CHANGED_CFG] = MT76_TM_ATTR_CFG,
++	[TM_CHANGED_OFF_CHAN_CH] = MT76_TM_ATTR_OFF_CH_SCAN_CH,
++	[TM_CHANGED_OFF_CHAN_CENTER_CH] = MT76_TM_ATTR_OFF_CH_SCAN_CENTER_CH,
++	[TM_CHANGED_OFF_CHAN_BW] = MT76_TM_ATTR_OFF_CH_SCAN_BW,
++	[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,
+ };
+ 
+-static u8 mt7996_tm_bw_mapping(enum nl80211_chan_width width, enum bw_mapping_method method)
++static void mt7996_tm_ipi_work(struct work_struct *work);
++
++static u32 mt7996_tm_bw_mapping(enum nl80211_chan_width width, enum bw_mapping_method method)
+ {
+-	static const u8 width_to_bw[][NUM_BW_MAP] = {
+-		[NL80211_CHAN_WIDTH_40] = {FW_CDBW_40MHZ, TM_CBW_40MHZ},
+-		[NL80211_CHAN_WIDTH_80] = {FW_CDBW_80MHZ, TM_CBW_80MHZ},
+-		[NL80211_CHAN_WIDTH_80P80] = {FW_CDBW_8080MHZ, TM_CBW_8080MHZ},
+-		[NL80211_CHAN_WIDTH_160] = {FW_CDBW_160MHZ, TM_CBW_160MHZ},
+-		[NL80211_CHAN_WIDTH_5] = {FW_CDBW_5MHZ, TM_CBW_5MHZ},
+-		[NL80211_CHAN_WIDTH_10] = {FW_CDBW_10MHZ, TM_CBW_10MHZ},
+-		[NL80211_CHAN_WIDTH_20] = {FW_CDBW_20MHZ, TM_CBW_20MHZ},
+-		[NL80211_CHAN_WIDTH_20_NOHT] = {FW_CDBW_20MHZ, TM_CBW_20MHZ},
+-		[NL80211_CHAN_WIDTH_320] = {FW_CDBW_320MHZ, TM_CBW_320MHZ},
++	static const u32 width_to_bw[][NUM_BW_MAP] = {
++		[NL80211_CHAN_WIDTH_40] = {FW_CDBW_40MHZ, TM_CBW_40MHZ, 40,
++					   FIRST_CONTROL_CHAN_BITMAP_BW40},
++		[NL80211_CHAN_WIDTH_80] = {FW_CDBW_80MHZ, TM_CBW_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,
++					    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},
+ 	};
+ 
+ 	if (width >= ARRAY_SIZE(width_to_bw))
+@@ -217,6 +234,9 @@ mt7996_tm_init(struct mt7996_phy *phy, bool en)
+ 
+ 	/* use firmware counter for RX stats */
+ 	phy->mt76->test.flag |= MT_TM_FW_RX_COUNT;
++
++	if (en)
++		INIT_DELAYED_WORK(&phy->ipi_work, mt7996_tm_ipi_work);
+ }
+ 
+ static void
+@@ -829,6 +849,204 @@ void mt7996_tm_rf_test_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 	}
+ }
+ 
++static u8
++mt7996_tm_get_center_chan(struct mt7996_phy *phy, struct cfg80211_chan_def *chandef)
++{
++	struct mt76_phy *mphy = phy->mt76;
++	const struct ieee80211_channel *chan = mphy->sband_5g.sband.channels;
++	u32 bitmap, i, offset, width_mhz, size = mphy->sband_5g.sband.n_channels;
++	u16 first_control = 0, control_chan = chandef->chan->hw_value;
++
++	bitmap = mt7996_tm_bw_mapping(chandef->width, BW_MAP_NL_TO_CONTROL_BITMAP_5G);
++	if (!bitmap)
++		return control_chan;
++
++	width_mhz = mt7996_tm_bw_mapping(chandef->width, BW_MAP_NL_TO_MHZ);
++	offset = width_mhz / 10 - 2;
++
++	for (i = 0; i < size; i++) {
++		if (!((1 << i) & bitmap))
++			continue;
++
++		if (control_chan >= chan[i].hw_value)
++			first_control = chan[i].hw_value;
++		else
++			break;
++	}
++
++	if (i == size || first_control == 0)
++		return control_chan;
++
++	return first_control + offset;
++}
++
++static int
++mt7996_tm_set_offchan(struct mt7996_phy *phy, bool no_center)
++{
++	struct mt76_phy *mphy = phy->mt76;
++	struct mt7996_dev *dev = phy->dev;
++	struct ieee80211_hw *hw = mphy->hw;
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	struct cfg80211_chan_def chandef = {};
++	struct ieee80211_channel *chan;
++	int ret, freq = ieee80211_channel_to_frequency(td->offchan_ch, NL80211_BAND_5GHZ);
++
++	if (!mphy->cap.has_5ghz || !freq) {
++		ret = -EINVAL;
++		dev_info(dev->mt76.dev, "Failed to set offchan (invalid band or channel)!\n");
++		goto out;
++	}
++
++	chandef.width = td->offchan_bw;
++	chan = ieee80211_get_channel(hw->wiphy, freq);
++	chandef.chan = chan;
++	if (no_center)
++		td->offchan_center_ch = mt7996_tm_get_center_chan(phy, &chandef);
++	chandef.center_freq1 = ieee80211_channel_to_frequency(td->offchan_center_ch,
++							      NL80211_BAND_5GHZ);
++	if (!cfg80211_chandef_valid(&chandef)) {
++		ret = -EINVAL;
++		dev_info(dev->mt76.dev, "Failed to set offchan, chandef is invalid!\n");
++		goto out;
++	}
++
++	memset(&dev->rdd2_chandef, 0, sizeof(struct cfg80211_chan_def));
++
++	ret = mt7996_mcu_rdd_background_enable(phy, &chandef);
++
++	if (ret)
++		goto out;
++
++	dev->rdd2_phy = phy;
++	dev->rdd2_chandef = chandef;
++
++	return 0;
++
++out:
++	td->offchan_ch = 0;
++	td->offchan_center_ch = 0;
++	td->offchan_bw = 0;
++
++	return ret;
++}
++
++static void
++mt7996_tm_ipi_hist_ctrl(struct mt7996_phy *phy, struct mt7996_tm_rdd_ipi_ctrl *data, u8 cmd)
++{
++#define MT_IPI_RESET		0x830a5dfc
++#define MT_IPI_RESET_MASK	BIT(28)
++#define MT_IPI_COUNTER_BASE	0x83041000
++#define MT_IPI_COUNTER(idx)	(MT_IPI_COUNTER_BASE + ((idx) * 4))
++	struct mt7996_dev *dev = phy->dev;
++	bool val;
++	int i;
++
++	if (cmd == RDD_SET_IPI_HIST_RESET) {
++		val = mt76_rr(dev, MT_IPI_RESET) & MT_IPI_RESET_MASK;
++		mt76_rmw_field(dev, MT_IPI_RESET, MT_IPI_RESET_MASK, !val);
++		return;
++	}
++
++	for (i = 0; i < POWER_INDICATE_HIST_MAX; i++)
++		data->ipi_hist_val[i] = mt76_rr(dev, MT_IPI_COUNTER(i));
++}
++
++static void
++mt7996_tm_ipi_work(struct work_struct *work)
++{
++#define PRECISION	100
++	struct mt7996_phy *phy = container_of(work, struct mt7996_phy, ipi_work.work);
++	struct mt7996_dev *dev = phy->dev;
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	struct mt7996_tm_rdd_ipi_ctrl data;
++	u32 ipi_idx, ipi_free_count, ipi_percentage;
++	u32 ipi_hist_count_th = 0, ipi_hist_total_count = 0;
++	u32 self_idle_ratio, ipi_idle_ratio, channel_load;
++	u32 *ipi_hist_data;
++	const char *power_lower_bound, *power_upper_bound;
++	static const char * const ipi_idx_to_power_bound[] = {
++		[RDD_IPI_HIST_0] = "-92",
++		[RDD_IPI_HIST_1] = "-89",
++		[RDD_IPI_HIST_2] = "-86",
++		[RDD_IPI_HIST_3] = "-83",
++		[RDD_IPI_HIST_4] = "-80",
++		[RDD_IPI_HIST_5] = "-75",
++		[RDD_IPI_HIST_6] = "-70",
++		[RDD_IPI_HIST_7] = "-65",
++		[RDD_IPI_HIST_8] = "-60",
++		[RDD_IPI_HIST_9] = "-55",
++		[RDD_IPI_HIST_10] = "inf",
++	};
++
++	memset(&data, 0, sizeof(data));
++	mt7996_tm_ipi_hist_ctrl(phy, &data, RDD_IPI_HIST_ALL_CNT);
++
++	ipi_hist_data = data.ipi_hist_val;
++	for (ipi_idx = 0; ipi_idx < POWER_INDICATE_HIST_MAX; ipi_idx++) {
++		power_lower_bound = ipi_idx ? ipi_idx_to_power_bound[ipi_idx - 1] : "-inf";
++		power_upper_bound = ipi_idx_to_power_bound[ipi_idx];
++
++		dev_info(dev->mt76.dev, "IPI %d (power range: (%s, %s] dBm): ipi count = %d\n",
++			 ipi_idx, power_lower_bound, power_upper_bound, ipi_hist_data[ipi_idx]);
++
++		if (td->ipi_threshold <= ipi_idx && ipi_idx <= RDD_IPI_HIST_10)
++			ipi_hist_count_th += ipi_hist_data[ipi_idx];
++
++		ipi_hist_total_count += ipi_hist_data[ipi_idx];
++	}
++
++	ipi_free_count = ipi_hist_data[RDD_IPI_FREE_RUN_CNT];
++
++	dev_info(dev->mt76.dev, "IPI threshold %d: ipi_hist_count_th = %d, ipi_free_count = %d\n",
++		 td->ipi_threshold, ipi_hist_count_th, ipi_free_count);
++	dev_info(dev->mt76.dev, "TX assert time =  %d [ms]\n", data.tx_assert_time / 1000);
++
++	/* calculate channel load = (self idle ratio - idle ratio) / self idle ratio */
++	if (ipi_hist_count_th >= UINT_MAX / (100 * PRECISION))
++		ipi_percentage = 100 * PRECISION *
++				 (ipi_hist_count_th / (100 * PRECISION)) /
++				 (ipi_free_count / (100 * PRECISION));
++	else
++		ipi_percentage = PRECISION * 100 * ipi_hist_count_th / ipi_free_count;
++
++	ipi_idle_ratio = ((100 * PRECISION) - ipi_percentage) / PRECISION;
++
++	self_idle_ratio = PRECISION * 100 *
++			  (td->ipi_period - (data.tx_assert_time / 1000)) /
++			  td->ipi_period / PRECISION;
++
++	if (self_idle_ratio < ipi_idle_ratio)
++		channel_load = 0;
++	else
++		channel_load = self_idle_ratio - ipi_idle_ratio;
++
++	if (self_idle_ratio <= td->ipi_threshold) {
++		dev_info(dev->mt76.dev, "band[%d]: self idle ratio = %d%%, idle ratio = %d%%\n",
++			 phy->mt76->band_idx, self_idle_ratio, ipi_idle_ratio);
++		return;
++	}
++
++	channel_load = (100 * channel_load) / self_idle_ratio;
++	dev_info(dev->mt76.dev,
++		 "band[%d]: chan load = %d%%, self idle ratio = %d%%, idle ratio = %d%%\n",
++		 phy->mt76->band_idx, channel_load, self_idle_ratio, ipi_idle_ratio);
++}
++
++static int
++mt7996_tm_set_ipi(struct mt7996_phy *phy)
++{
++	struct mt76_testmode_data *td = &phy->mt76->test;
++
++	/* reset IPI CR */
++	mt7996_tm_ipi_hist_ctrl(phy, NULL, RDD_SET_IPI_HIST_RESET);
++
++	cancel_delayed_work(&phy->ipi_work);
++	ieee80211_queue_delayed_work(phy->mt76->hw, &phy->ipi_work,
++				     msecs_to_jiffies(td->ipi_period));
++
++	return 0;
++}
++
+ static void
+ mt7996_tm_update_params(struct mt7996_phy *phy, u32 changed)
+ {
+@@ -860,6 +1078,14 @@ mt7996_tm_update_params(struct mt7996_phy *phy, u32 changed)
+ 
+ 		mt7996_tm_set(dev, func_idx, td->cfg.type);
+ 	}
++	if ((changed & BIT(TM_CHANGED_OFF_CHAN_CH)) &&
++	    (changed & BIT(TM_CHANGED_OFF_CHAN_BW)))
++		mt7996_tm_set_offchan(phy, !(changed & BIT(TM_CHANGED_OFF_CHAN_CENTER_CH)));
++	if ((changed & BIT(TM_CHANGED_IPI_THRESHOLD)) &&
++	    (changed & BIT(TM_CHANGED_IPI_PERIOD)))
++		mt7996_tm_set_ipi(phy);
++	if (changed & BIT(TM_CHANGED_IPI_RESET))
++		mt7996_tm_ipi_hist_ctrl(phy, NULL, RDD_SET_IPI_HIST_RESET);
+ }
+ 
+ static int
+diff --git a/mt7996/testmode.h b/mt7996/testmode.h
+index 9bfb86f28..78662b2ed 100644
+--- a/mt7996/testmode.h
++++ b/mt7996/testmode.h
+@@ -27,9 +27,15 @@ enum {
+ 	FW_CDBW_8080MHZ,
+ };
+ 
++#define FIRST_CONTROL_CHAN_BITMAP_BW40		0x5555555
++#define FIRST_CONTROL_CHAN_BITMAP_BW80		0x111111
++#define FIRST_CONTROL_CHAN_BITMAP_BW160		0x100101
++
+ enum bw_mapping_method {
+ 	BW_MAP_NL_TO_FW,
+ 	BW_MAP_NL_TO_TM,
++	BW_MAP_NL_TO_MHZ,
++	BW_MAP_NL_TO_CONTROL_BITMAP_5G,
+ 
+ 	NUM_BW_MAP,
+ };
+@@ -312,4 +318,42 @@ struct mt7996_tm_rx_event {
+ 	};
+ } __packed;
+ 
++enum {
++	RDD_SET_IPI_CR_INIT,		/* CR initialization */
++	RDD_SET_IPI_HIST_RESET,		/* Reset IPI histogram counter */
++	RDD_SET_IDLE_POWER,		/* Idle power info */
++	RDD_SET_IPI_HIST_NUM
++};
++
++enum {
++	RDD_IPI_HIST_0,			/* IPI count for power <= -92 (dBm) */
++	RDD_IPI_HIST_1,			/* IPI count for -92 < power <= -89 (dBm) */
++	RDD_IPI_HIST_2,			/* IPI count for -89 < power <= -86 (dBm) */
++	RDD_IPI_HIST_3,			/* IPI count for -86 < power <= -83 (dBm) */
++	RDD_IPI_HIST_4,			/* IPI count for -83 < power <= -80 (dBm) */
++	RDD_IPI_HIST_5,			/* IPI count for -80 < power <= -75 (dBm) */
++	RDD_IPI_HIST_6,			/* IPI count for -75 < power <= -70 (dBm) */
++	RDD_IPI_HIST_7,			/* IPI count for -70 < power <= -65 (dBm) */
++	RDD_IPI_HIST_8,			/* IPI count for -65 < power <= -60 (dBm) */
++	RDD_IPI_HIST_9,			/* IPI count for -60 < power <= -55 (dBm) */
++	RDD_IPI_HIST_10,		/* IPI count for -55 < power        (dBm) */
++	RDD_IPI_FREE_RUN_CNT,		/* IPI count for counter++ per 8 us */
++	RDD_IPI_HIST_ALL_CNT,		/* Get all IPI */
++	RDD_IPI_HIST_0_TO_10_CNT,	/* Get IPI histogram 0 to 10 */
++	RDD_IPI_HIST_2_TO_10_CNT,	/* Get IPI histogram 2 to 10 */
++	RDD_TX_ASSERT_TIME,		/* Get band 1 TX assert time */
++	RDD_IPI_HIST_NUM
++};
++
++#define POWER_INDICATE_HIST_MAX		RDD_IPI_FREE_RUN_CNT
++#define IPI_HIST_TYPE_NUM		(POWER_INDICATE_HIST_MAX + 1)
++
++struct mt7996_tm_rdd_ipi_ctrl {
++	u8 ipi_hist_idx;
++	u8 band_idx;
++	u8 rsv[2];
++	__le32 ipi_hist_val[IPI_HIST_TYPE_NUM];
++	__le32 tx_assert_time;		/* unit: us */
++} __packed;
++
+ #endif
+diff --git a/testmode.c b/testmode.c
+index cd8cb6553..69147f866 100644
+--- a/testmode.c
++++ b/testmode.c
+@@ -27,6 +27,13 @@ const struct nla_policy mt76_tm_policy[NUM_MT76_TM_ATTRS] = {
+ 	[MT76_TM_ATTR_TX_TIME] = { .type = NLA_U32 },
+ 	[MT76_TM_ATTR_FREQ_OFFSET] = { .type = NLA_U32 },
+ 	[MT76_TM_ATTR_DRV_DATA] = { .type = NLA_NESTED },
++	[MT76_TM_ATTR_OFF_CH_SCAN_CH] = { .type = NLA_U8 },
++	[MT76_TM_ATTR_OFF_CH_SCAN_CENTER_CH] = { .type = NLA_U8 },
++	[MT76_TM_ATTR_OFF_CH_SCAN_BW] = { .type = NLA_U8 },
++	[MT76_TM_ATTR_OFF_CH_SCAN_PATH] = { .type = NLA_U8 },
++	[MT76_TM_ATTR_IPI_THRESHOLD] = { .type = NLA_U8 },
++	[MT76_TM_ATTR_IPI_PERIOD] = { .type = NLA_U32 },
++	[MT76_TM_ATTR_IPI_RESET] = { .type = NLA_U8 },
+ };
+ EXPORT_SYMBOL_GPL(mt76_tm_policy);
+ 
+@@ -499,6 +506,9 @@ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	if (tb[MT76_TM_ATTR_TX_RATE_IDX])
+ 		td->tx_rate_idx = nla_get_u8(tb[MT76_TM_ATTR_TX_RATE_IDX]);
+ 
++	if (tb[MT76_TM_ATTR_IPI_PERIOD])
++		td->ipi_period = nla_get_u32(tb[MT76_TM_ATTR_IPI_PERIOD]);
++
+ 	if (mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_RATE_MODE], &td->tx_rate_mode,
+ 			   0, MT76_TM_TX_MODE_MAX) ||
+ 	    mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_RATE_NSS], &td->tx_rate_nss,
+@@ -514,7 +524,14 @@ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 			   &td->tx_duty_cycle, 0, 99) ||
+ 	    mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_POWER_CONTROL],
+ 			   &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_AID], &td->aid, 0, 16) ||
++	    mt76_tm_get_u8(tb[MT76_TM_ATTR_OFF_CH_SCAN_CH], &td->offchan_ch, 36, 196) ||
++	    mt76_tm_get_u8(tb[MT76_TM_ATTR_OFF_CH_SCAN_CENTER_CH], &td->offchan_center_ch,
++			   36, 196) ||
++	    mt76_tm_get_u8(tb[MT76_TM_ATTR_OFF_CH_SCAN_BW],
++			   &td->offchan_bw, NL80211_CHAN_WIDTH_20_NOHT, NL80211_CHAN_WIDTH_160) ||
++	    mt76_tm_get_u8(tb[MT76_TM_ATTR_IPI_THRESHOLD], &td->ipi_threshold, 0, 10) ||
++	    mt76_tm_get_u8(tb[MT76_TM_ATTR_IPI_RESET], &td->ipi_reset, 0, 1))
+ 		goto out;
+ 
+ 	if (tb[MT76_TM_ATTR_TX_LENGTH]) {
+@@ -720,6 +737,9 @@ int mt76_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *msg,
+ 	    nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_STBC, td->tx_rate_stbc) ||
+ 	    nla_put_u8(msg, MT76_TM_ATTR_SKU_EN, td->sku_en) ||
+ 	    nla_put_u8(msg, MT76_TM_ATTR_AID, td->aid) ||
++	    nla_put_u8(msg, MT76_TM_ATTR_OFF_CH_SCAN_CH, td->offchan_ch) ||
++	    nla_put_u8(msg, MT76_TM_ATTR_OFF_CH_SCAN_CENTER_CH, td->offchan_center_ch) ||
++	    nla_put_u8(msg, MT76_TM_ATTR_OFF_CH_SCAN_BW, td->offchan_bw) ||
+ 	    (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_LTF) &&
+ 	     nla_put_u8(msg, MT76_TM_ATTR_TX_LTF, td->tx_ltf)) ||
+ 	    (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_ANTENNA) &&
+diff --git a/tools/fields.c b/tools/fields.c
+index b01227638..77696ce7b 100644
+--- a/tools/fields.c
++++ b/tools/fields.c
+@@ -35,6 +35,15 @@ static const char * const testmode_tx_mode[] = {
+ 	[MT76_TM_TX_MODE_EHT_MU] = "eht_mu",
+ };
+ 
++static const char * const testmode_offchan_bw[] = {
++	[NL80211_CHAN_WIDTH_20_NOHT] = "NOHT",
++	[NL80211_CHAN_WIDTH_20] = "20",
++	[NL80211_CHAN_WIDTH_40] = "40",
++	[NL80211_CHAN_WIDTH_80] = "80",
++	[NL80211_CHAN_WIDTH_80P80] = "80p80",
++	[NL80211_CHAN_WIDTH_160] = "160",
++};
++
+ static void print_enum(const struct tm_field *field, struct nlattr *attr)
+ {
+ 	unsigned int i = nla_get_u8(attr);
+@@ -390,6 +399,12 @@ 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(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),
++	FIELD(u8, IPI_THRESHOLD, "ipi_threshold"),
++	FIELD(u32, IPI_PERIOD, "ipi_period"),
++	FIELD(u8, IPI_RESET, "ipi_reset"),
+ 	FIELD_MAC(MAC_ADDRS, "mac_addrs"),
+ 	FIELD_NESTED_RO(STATS, stats, "",
+ 			.print_extra = print_extra_stats),
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0036-mtk-wifi-mt76-mt7996-refactor-eeprom-loading-flow-fo.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0036-mtk-wifi-mt76-mt7996-refactor-eeprom-loading-flow-fo.patch
new file mode 100644
index 0000000..c2d6b31
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0036-mtk-wifi-mt76-mt7996-refactor-eeprom-loading-flow-fo.patch
@@ -0,0 +1,389 @@
+From ad34fb7616d5c2a679f4344dce94b0edd6e66609 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Mon, 11 Mar 2024 10:43:03 +0800
+Subject: [PATCH 036/120] mtk: wifi: mt76: mt7996: refactor eeprom loading flow
+ for sku checking
+
+Add eeprom sku checking mechanism to avoid using the
+wrong eeprom in flash/binfile mode
+The fields listed below will be checked by comparing the loaded eeprom to the default bin
+1. FEM type
+2. MAC address (warning for using default MAC address)
+3. RF path & streams
+   (to distinguish cases such as BE7200 4i5i, BE6500 3i5i, and BE5040 2i3i)
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Change-Id: If1905086f2a876a593d07f23a5facad35067f94a
+---
+ mt7996/eeprom.c      | 185 ++++++++++++++++++++++++++-----------------
+ mt7996/eeprom.h      |  32 ++++++++
+ mt7996/mtk_debugfs.c |   2 +-
+ 3 files changed, 144 insertions(+), 75 deletions(-)
+
+diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
+index fe8b25352..138bdb472 100644
+--- a/mt7996/eeprom.c
++++ b/mt7996/eeprom.c
+@@ -49,23 +49,43 @@ const u32 dpd_6g_bw160_ch_num = ARRAY_SIZE(dpd_6g_ch_list_bw160);
+ const u32 dpd_6g_bw320_ch_num = ARRAY_SIZE(dpd_6g_ch_list_bw320);
+ 
+ static int mt7996_check_eeprom(struct mt7996_dev *dev)
++{
++	u8 *eeprom = dev->mt76.eeprom.data;
++	u16 val = get_unaligned_le16(eeprom);
++
++	switch (val) {
++	case 0x7990:
++		return is_mt7996(&dev->mt76) ? 0 : -EINVAL;
++	case 0x7992:
++		return is_mt7992(&dev->mt76) ? 0 : -EINVAL;
++	default:
++		return -EINVAL;
++	}
++}
++
++static int mt7996_check_eeprom_sku(struct mt7996_dev *dev, const u8 *dflt)
+ {
+ #define FEM_INT				0
+ #define FEM_EXT				3
+ 	u8 *eeprom = dev->mt76.eeprom.data;
+ 	u8 i, fem[__MT_MAX_BAND], fem_type;
+ 	u16 val = get_unaligned_le16(eeprom);
++	u16 mac_addr[__MT_MAX_BAND] = {MT_EE_MAC_ADDR, MT_EE_MAC_ADDR2, MT_EE_MAC_ADDR3};
++	int max_band = __MT_MAX_BAND;
++
++	if (dev->fem_type == MT7996_FEM_UNSET)
++		return -EINVAL;
+ 
+ 	for (i = 0; i < __MT_MAX_BAND; i++)
+ 		fem[i] = eeprom[MT_EE_WIFI_CONF + 6 + i] & MT_EE_WIFI_PA_LNA_CONFIG;
+ 
+ 	switch (val) {
+ 	case 0x7990:
+-		return is_mt7996(&dev->mt76) ? 0 : -EINVAL;
++		/* Don't care Eagle's FEM type */
++		fem_type = 0;
++		break;
+ 	case 0x7992:
+-		if (dev->fem_type == MT7996_FEM_UNSET)
+-			return is_mt7992(&dev->mt76) ? 0 : -EINVAL;
+-
++		max_band = 2;
+ 		if (fem[0] == FEM_EXT && fem[1] == FEM_EXT)
+ 			fem_type = MT7996_FEM_EXT;
+ 		else if (fem[0] == FEM_INT && fem[1] == FEM_INT)
+@@ -74,19 +94,45 @@ static int mt7996_check_eeprom(struct mt7996_dev *dev)
+ 			fem_type = MT7996_FEM_MIX;
+ 		else
+ 			return -EINVAL;
+-
+-		return (is_mt7992(&dev->mt76) ? 0 : -EINVAL) |
+-		       (dev->fem_type == fem_type ? 0 : -EINVAL);
++		break;
+ 	default:
+ 		return -EINVAL;
+ 	}
++
++	/* FEM type */
++	if (fem_type && dev->fem_type != fem_type)
++		return -EINVAL;
++
++	/* RF path & stream */
++	for (i = 0; i < max_band; i++) {
++		u8 path, rx_path, nss;
++		u8 dflt_path, dflt_rx_path, dflt_nss;
++
++		/* MAC address */
++		if (ether_addr_equal(eeprom + mac_addr[i], dflt + mac_addr[i]))
++			dev_warn(dev->mt76.dev,
++				 "Currently using default MAC address for band %d\n", i);
++
++		mt7996_parse_eeprom_stream(eeprom, i, &path, &rx_path, &nss);
++		mt7996_parse_eeprom_stream(dflt, i, &dflt_path, &dflt_rx_path, &dflt_nss);
++		if (path > dflt_path || rx_path > dflt_rx_path || nss > dflt_nss) {
++			dev_err(dev->mt76.dev,
++				"Invalid path/stream configuration for band %d\n", i);
++			return -EINVAL;
++		} else if (path < dflt_path || rx_path < dflt_rx_path || nss < dflt_nss) {
++			dev_warn(dev->mt76.dev,
++				 "Restricted path/stream configuration for band %d\n", i);
++			dev_warn(dev->mt76.dev,
++				 "path: %u/%u, rx_path: %u/%u, nss: %u/%u\n",
++				 path, dflt_path, rx_path, dflt_rx_path, nss, dflt_nss);
++		}
++	}
++
++	return 0;
+ }
+ 
+ const char *mt7996_eeprom_name(struct mt7996_dev *dev)
+ {
+-	if (dev->bin_file_mode)
+-		return dev->mt76.bin_file_name;
+-
+ 	if (dev->testmode_enable) {
+ 		if (is_mt7992(&dev->mt76))
+ 			return MT7992_EEPROM_DEFAULT_TM;
+@@ -148,21 +194,18 @@ mt7996_get_dpd_per_band_size(struct mt7996_dev *dev, enum nl80211_band band)
+ }
+ 
+ static int
+-mt7996_eeprom_load_default(struct mt7996_dev *dev)
++mt7996_eeprom_load_bin(struct mt7996_dev *dev)
+ {
+ 	u8 *eeprom = dev->mt76.eeprom.data;
+ 	const struct firmware *fw = NULL;
+ 	int ret;
+ 
+-	ret = request_firmware(&fw, mt7996_eeprom_name(dev), dev->mt76.dev);
++	ret = request_firmware(&fw, dev->mt76.bin_file_name, dev->mt76.dev);
+ 	if (ret)
+ 		return ret;
+ 
+ 	if (!fw || !fw->data) {
+-		if (dev->bin_file_mode)
+-			dev_err(dev->mt76.dev, "Invalid bin (bin file mode)\n");
+-		else
+-			dev_err(dev->mt76.dev, "Invalid default bin\n");
++		dev_err(dev->mt76.dev, "Invalid bin %s\n", dev->mt76.bin_file_name);
+ 		ret = -EINVAL;
+ 		goto out;
+ 	}
+@@ -180,7 +223,7 @@ static int mt7996_eeprom_load_flash(struct mt7996_dev *dev)
+ {
+ 	int ret = 1;
+ 
+-	/* return > 0 for load success, return 0 for load failed, return < 0 for non memory */
++	/* return > 0 for load success, return 0 for load failed, return < 0 for no memory */
+ 	dev->bin_file_mode = mt76_check_bin_file_mode(&dev->mt76);
+ 	if (dev->bin_file_mode) {
+ 		dev->mt76.eeprom.size = MT7996_EEPROM_SIZE;
+@@ -189,15 +232,15 @@ static int mt7996_eeprom_load_flash(struct mt7996_dev *dev)
+ 		if (!dev->mt76.eeprom.data)
+ 			return -ENOMEM;
+ 
+-		if (mt7996_eeprom_load_default(dev))
+-			return 0;
+-
+-		if (mt7996_check_eeprom(dev))
++		if (mt7996_eeprom_load_bin(dev))
+ 			return 0;
+ 	} else {
+ 		ret = mt76_eeprom_init(&dev->mt76, MT7996_EEPROM_SIZE);
+ 	}
+ 
++	if (mt7996_check_eeprom(dev))
++		return 0;
++
+ 	return ret;
+ }
+ 
+@@ -206,30 +249,30 @@ int mt7996_eeprom_check_fw_mode(struct mt7996_dev *dev)
+ 	u8 *eeprom;
+ 	int ret;
+ 
++	dev->testmode_enable = testmode_enable;
++
+ 	/* load eeprom in flash or bin file mode to determine fw mode */
+ 	ret = mt7996_eeprom_load_flash(dev);
++	if (ret <= 0)
++		goto out;
+ 
+-	if (ret < 0)
+-		return ret;
+-
+-	if (ret) {
+-		dev->flash_mode = true;
+-		dev->eeprom_mode = dev->bin_file_mode ? BIN_FILE_MODE : FLASH_MODE;
+-		eeprom = dev->mt76.eeprom.data;
+-		/* testmode enable priority: eeprom field > module parameter */
+-		dev->testmode_enable = !mt7996_check_eeprom(dev) ? eeprom[MT_EE_TESTMODE_EN] :
+-								   testmode_enable;
+-	}
++	dev->flash_mode = true;
++	dev->eeprom_mode = dev->bin_file_mode ? BIN_FILE_MODE : FLASH_MODE;
++	eeprom = dev->mt76.eeprom.data;
++	/* testmode enable priority: eeprom field > module parameter */
++	dev->testmode_enable = eeprom[MT_EE_TESTMODE_EN];
+ 
++out:
+ 	return ret;
+ }
+ 
+ static int mt7996_eeprom_load(struct mt7996_dev *dev)
+ {
++	const struct firmware *fw = NULL;
+ 	int ret;
+-	u8 free_block_num;
+ 	u32 block_num, i;
+ 	u32 eeprom_blk_size = MT7996_EEPROM_BLOCK_SIZE;
++	u8 free_block_num;
+ 
+ 	/* flash or bin file mode eeprom is loaded before mcu init */
+ 	if (!dev->flash_mode) {
+@@ -239,19 +282,47 @@ static int mt7996_eeprom_load(struct mt7996_dev *dev)
+ 
+ 		/* efuse info isn't enough */
+ 		if (free_block_num >= 59)
+-			return -EINVAL;
++			goto dflt;
++
++		/* check if efuse contains valid eeprom data */
++		if (mt7996_mcu_get_eeprom(dev, 0, NULL) ||
++		    mt7996_check_eeprom(dev))
++			goto dflt;
+ 
+ 		/* read eeprom data from efuse */
+ 		block_num = DIV_ROUND_UP(MT7996_EEPROM_SIZE, eeprom_blk_size);
+-		for (i = 0; i < block_num; i++) {
++		for (i = 1; i < block_num; i++) {
+ 			ret = mt7996_mcu_get_eeprom(dev, i * eeprom_blk_size, NULL);
+ 			if (ret && ret != -EINVAL)
+-				return ret;
++				goto dflt;
+ 		}
+ 		dev->eeprom_mode = EFUSE_MODE;
+ 	}
+ 
+-	return mt7996_check_eeprom(dev);
++dflt:
++	ret = request_firmware(&fw, mt7996_eeprom_name(dev), dev->mt76.dev);
++	if (ret)
++		return ret;
++
++	if (!fw || !fw->data) {
++		dev_err(dev->mt76.dev, "Invalid default bin\n");
++		ret = -EINVAL;
++		goto out;
++	}
++
++	if (dev->eeprom_mode && !mt7996_check_eeprom_sku(dev, fw->data)) {
++		ret = 0;
++		goto out;
++	}
++
++	memcpy(dev->mt76.eeprom.data, fw->data, MT7996_EEPROM_SIZE);
++	dev->bin_file_mode = false;
++	dev->flash_mode = true;
++	dev->eeprom_mode = DEFAULT_BIN_MODE;
++	dev_warn(dev->mt76.dev, "eeprom load fail, use default bin\n");
++out:
++	release_firmware(fw);
++	return ret;
+ }
+ 
+ static int mt7996_eeprom_parse_efuse_hw_cap(struct mt7996_dev *dev)
+@@ -323,32 +394,7 @@ int mt7996_eeprom_parse_hw_cap(struct mt7996_dev *dev, struct mt7996_phy *phy)
+ 	int max_path = 5, max_nss = 4;
+ 	int ret;
+ 
+-	switch (band_idx) {
+-	case MT_BAND1:
+-		path = FIELD_GET(MT_EE_WIFI_CONF2_TX_PATH_BAND1,
+-				 eeprom[MT_EE_WIFI_CONF + 2]);
+-		rx_path = FIELD_GET(MT_EE_WIFI_CONF3_RX_PATH_BAND1,
+-				    eeprom[MT_EE_WIFI_CONF + 3]);
+-		nss = FIELD_GET(MT_EE_WIFI_CONF5_STREAM_NUM_BAND1,
+-				eeprom[MT_EE_WIFI_CONF + 5]);
+-		break;
+-	case MT_BAND2:
+-		path = FIELD_GET(MT_EE_WIFI_CONF2_TX_PATH_BAND2,
+-				 eeprom[MT_EE_WIFI_CONF + 2]);
+-		rx_path = FIELD_GET(MT_EE_WIFI_CONF4_RX_PATH_BAND2,
+-				    eeprom[MT_EE_WIFI_CONF + 4]);
+-		nss = FIELD_GET(MT_EE_WIFI_CONF5_STREAM_NUM_BAND2,
+-				eeprom[MT_EE_WIFI_CONF + 5]);
+-		break;
+-	default:
+-		path = FIELD_GET(MT_EE_WIFI_CONF1_TX_PATH_BAND0,
+-				 eeprom[MT_EE_WIFI_CONF + 1]);
+-		rx_path = FIELD_GET(MT_EE_WIFI_CONF3_RX_PATH_BAND0,
+-				    eeprom[MT_EE_WIFI_CONF + 3]);
+-		nss = FIELD_GET(MT_EE_WIFI_CONF4_STREAM_NUM_BAND0,
+-				eeprom[MT_EE_WIFI_CONF + 4]);
+-		break;
+-	}
++	mt7996_parse_eeprom_stream(eeprom, band_idx, &path, &rx_path, &nss);
+ 
+ 	if (!path || path > max_path)
+ 		path = max_path;
+@@ -437,17 +483,8 @@ int mt7996_eeprom_init(struct mt7996_dev *dev)
+ 		return ret;
+ 
+ 	ret = mt7996_eeprom_load(dev);
+-	if (ret < 0) {
+-		if (ret != -EINVAL)
+-			return ret;
+-
+-		dev_warn(dev->mt76.dev, "eeprom load fail, use default bin\n");
+-		dev->bin_file_mode = false;
+-		dev->eeprom_mode = DEFAULT_BIN_MODE;
+-		ret = mt7996_eeprom_load_default(dev);
+-		if (ret)
+-			return ret;
+-	}
++	if (ret)
++		return ret;
+ 
+ 	ret = mt7996_eeprom_load_precal(dev);
+ 	if (ret)
+diff --git a/mt7996/eeprom.h b/mt7996/eeprom.h
+index 8f0f87b6b..03a4fd07d 100644
+--- a/mt7996/eeprom.h
++++ b/mt7996/eeprom.h
+@@ -132,6 +132,38 @@ mt7996_get_channel_group_6g(int channel)
+ 	return DIV_ROUND_UP(channel - 29, 32);
+ }
+ 
++static inline void
++mt7996_parse_eeprom_stream(const u8 *eep, int band_idx,
++			   u8 *path, u8 *rx_path, u8 *nss)
++{
++	switch (band_idx) {
++	case MT_BAND1:
++		*path = FIELD_GET(MT_EE_WIFI_CONF2_TX_PATH_BAND1,
++				  eep[MT_EE_WIFI_CONF + 2]);
++		*rx_path = FIELD_GET(MT_EE_WIFI_CONF3_RX_PATH_BAND1,
++				     eep[MT_EE_WIFI_CONF + 3]);
++		*nss = FIELD_GET(MT_EE_WIFI_CONF5_STREAM_NUM_BAND1,
++				 eep[MT_EE_WIFI_CONF + 5]);
++		break;
++	case MT_BAND2:
++		*path = FIELD_GET(MT_EE_WIFI_CONF2_TX_PATH_BAND2,
++				  eep[MT_EE_WIFI_CONF + 2]);
++		*rx_path = FIELD_GET(MT_EE_WIFI_CONF4_RX_PATH_BAND2,
++				     eep[MT_EE_WIFI_CONF + 4]);
++		*nss = FIELD_GET(MT_EE_WIFI_CONF5_STREAM_NUM_BAND2,
++				 eep[MT_EE_WIFI_CONF + 5]);
++		break;
++	default:
++		*path = FIELD_GET(MT_EE_WIFI_CONF1_TX_PATH_BAND0,
++				  eep[MT_EE_WIFI_CONF + 1]);
++		*rx_path = FIELD_GET(MT_EE_WIFI_CONF3_RX_PATH_BAND0,
++				     eep[MT_EE_WIFI_CONF + 3]);
++		*nss = FIELD_GET(MT_EE_WIFI_CONF4_STREAM_NUM_BAND0,
++				 eep[MT_EE_WIFI_CONF + 4]);
++		break;
++	}
++}
++
+ enum mt7996_sku_rate_group {
+ 	SKU_CCK,
+ 	SKU_OFDM,
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index 09652ef52..8db8b6805 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -2768,7 +2768,7 @@ static int mt7996_show_eeprom_mode(struct seq_file *s, void *data)
+ 			seq_printf(s, "   flash mode\n");
+ 		break;
+ 	case BIN_FILE_MODE:
+-		seq_printf(s, "   bin file mode\n   filename = %s\n", mt7996_eeprom_name(dev));
++		seq_printf(s, "   bin file mode\n   filename = %s\n", dev->mt76.bin_file_name);
+ 		break;
+ 	default:
+ 		break;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0037-mtk-wifi-mt76-mt7996-add-mu-vendor-command-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0037-mtk-wifi-mt76-mt7996-add-mu-vendor-command-support.patch
new file mode 100644
index 0000000..097672f
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0037-mtk-wifi-mt76-mt7996-add-mu-vendor-command-support.patch
@@ -0,0 +1,309 @@
+From f7dff9127964cec4aefc57f6ac88b3166a14fd66 Mon Sep 17 00:00:00 2001
+From: MeiChia Chiu <meichia.chiu@mediatek.com>
+Date: Tue, 13 Dec 2022 15:17:43 +0800
+Subject: [PATCH 037/120] mtk: wifi: mt76: mt7996: add mu vendor command
+ support
+
+mtk: wifi: mt76: fix muru_onoff as all enabled by default
+
+Fix muru_onoff default value as 0xF, which means all MU & RU are
+enabled. The purpose of this commit is to align muru_onoff value with
+hostapd and mt76 driver
+
+CR-Id: WCNCR00360312
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+---
+ mt7996/Makefile |  3 +-
+ mt7996/init.c   |  9 ++++++
+ mt7996/mcu.c    | 37 ++++++++++++++++++---
+ mt7996/mcu.h    | 12 +++++++
+ mt7996/mt7996.h |  7 ++++
+ mt7996/vendor.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++++
+ mt7996/vendor.h | 22 +++++++++++++
+ 7 files changed, 169 insertions(+), 6 deletions(-)
+ create mode 100644 mt7996/vendor.c
+ create mode 100644 mt7996/vendor.h
+
+diff --git a/mt7996/Makefile b/mt7996/Makefile
+index 7bb17f440..6643c7a38 100644
+--- a/mt7996/Makefile
++++ b/mt7996/Makefile
+@@ -1,11 +1,12 @@
+ # SPDX-License-Identifier: ISC
+ EXTRA_CFLAGS += -DCONFIG_MT76_LEDS
+ EXTRA_CFLAGS += -DCONFIG_MTK_DEBUG
++EXTRA_CFLAGS += -DCONFIG_MTK_VENDOR
+ 
+ obj-$(CONFIG_MT7996E) += mt7996e.o
+ 
+ mt7996e-y := pci.o init.o dma.o eeprom.o main.o mcu.o mac.o \
+-	     debugfs.o mmio.o
++	     debugfs.o mmio.o vendor.o
+ 
+ mt7996e-$(CONFIG_DEV_COREDUMP) += coredump.o
+ mt7996e-$(CONFIG_NL80211_TESTMODE) += testmode.o
+diff --git a/mt7996/init.c b/mt7996/init.c
+index c135da9c4..fba61c017 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -368,6 +368,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed)
+ 
+ 	phy->slottime = 9;
+ 	phy->beacon_rate = -1;
++	phy->muru_onoff = OFDMA_UL | OFDMA_DL | MUMIMO_DL | MUMIMO_UL;
+ 
+ 	hw->sta_data_size = sizeof(struct mt7996_sta);
+ 	hw->vif_data_size = sizeof(struct mt7996_vif);
+@@ -616,6 +617,10 @@ static int mt7996_register_phy(struct mt7996_dev *dev, struct mt7996_phy *phy,
+ 	if (ret)
+ 		goto error;
+ 
++#ifdef CONFIG_MTK_VENDOR
++	mt7996_vendor_register(phy);
++#endif
++
+ 	ret = mt76_register_phy(mphy, true, mt76_rates,
+ 				ARRAY_SIZE(mt76_rates));
+ 	if (ret)
+@@ -1401,6 +1406,10 @@ int mt7996_register_device(struct mt7996_dev *dev)
+ 	dev->mt76.test_ops = &mt7996_testmode_ops;
+ #endif
+ 
++#ifdef CONFIG_MTK_VENDOR
++	mt7996_vendor_register(&dev->phy);
++#endif
++
+ 	ret = mt76_register_device(&dev->mt76, true, mt76_rates,
+ 				   ARRAY_SIZE(mt76_rates));
+ 	if (ret)
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index a010a86cc..bfa9709d3 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -1376,6 +1376,8 @@ static void
+ mt7996_mcu_sta_muru_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 			struct ieee80211_vif *vif, struct ieee80211_sta *sta)
+ {
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_phy *phy = mvif->phy;
+ 	struct ieee80211_he_cap_elem *elem = &sta->deflink.he_cap.he_cap_elem;
+ 	struct sta_rec_muru *muru;
+ 	struct tlv *tlv;
+@@ -1387,11 +1389,14 @@ mt7996_mcu_sta_muru_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_MURU, sizeof(*muru));
+ 
+ 	muru = (struct sta_rec_muru *)tlv;
+-	muru->cfg.mimo_dl_en = vif->bss_conf.eht_mu_beamformer ||
+-			       vif->bss_conf.he_mu_beamformer ||
+-			       vif->bss_conf.vht_mu_beamformer ||
+-			       vif->bss_conf.vht_mu_beamformee;
+-	muru->cfg.ofdma_dl_en = true;
++	muru->cfg.mimo_dl_en = (vif->bss_conf.eht_mu_beamformer ||
++				vif->bss_conf.he_mu_beamformer ||
++				vif->bss_conf.vht_mu_beamformer ||
++				vif->bss_conf.vht_mu_beamformee) &&
++			       !!(phy->muru_onoff & MUMIMO_DL);
++	muru->cfg.mimo_ul_en = !!(phy->muru_onoff & MUMIMO_UL);
++	muru->cfg.ofdma_dl_en = !!(phy->muru_onoff & OFDMA_DL);
++	muru->cfg.ofdma_ul_en = !!(phy->muru_onoff & OFDMA_UL);
+ 
+ 	if (sta->deflink.vht_cap.vht_supported)
+ 		muru->mimo_dl.vht_mu_bfee =
+@@ -4937,3 +4942,25 @@ int mt7996_mcu_set_scs(struct mt7996_phy *phy, u8 enable)
+ 	return mt76_mcu_send_msg(&phy->dev->mt76, MCU_WM_UNI_CMD(SCS),
+ 				 &req, sizeof(req), false);
+ }
++
++#ifdef CONFIG_MTK_VENDOR
++void mt7996_set_wireless_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
++{
++	u8 mode, val;
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_phy *phy =  mvif->phy;
++
++	mode = FIELD_GET(RATE_CFG_MODE, *((u32 *)data));
++	val = FIELD_GET(RATE_CFG_VAL, *((u32 *)data));
++
++	switch (mode) {
++	case RATE_PARAM_AUTO_MU:
++		if (val < 0 || val > 15) {
++			printk("Wrong value! The value is between 0-15.\n");
++			break;
++		}
++		phy->muru_onoff = val;
++		break;
++	}
++}
++#endif
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 887d9b49e..68bf82fca 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -754,8 +754,20 @@ enum {
+ 	RATE_PARAM_FIXED_MCS,
+ 	RATE_PARAM_FIXED_GI = 11,
+ 	RATE_PARAM_AUTO = 20,
++#ifdef CONFIG_MTK_VENDOR
++	RATE_PARAM_AUTO_MU = 32,
++#endif
+ };
+ 
++#define RATE_CFG_MODE	GENMASK(15, 8)
++#define RATE_CFG_VAL	GENMASK(7, 0)
++
++/* MURU */
++#define OFDMA_DL                       BIT(0)
++#define OFDMA_UL                       BIT(1)
++#define MUMIMO_DL                      BIT(2)
++#define MUMIMO_UL                      BIT(3)
++
+ enum {
+ 	BF_SOUNDING_ON = 1,
+ 	BF_HW_EN_UPDATE = 17,
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index e5ca30776..a5aa7a30a 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -295,6 +295,8 @@ struct mt7996_phy {
+ 
+ 	struct mt7996_scs_ctrl scs_ctrl;
+ 
++	u8 muru_onoff;
++
+ #ifdef CONFIG_NL80211_TESTMODE
+ 	struct {
+ 		u32 *reg_backup;
+@@ -734,6 +736,11 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr,
+ 			 bool hif2, int *irq);
+ u32 mt7996_wed_init_buf(void *ptr, dma_addr_t phys, int token_id);
+ 
++#ifdef CONFIG_MTK_VENDOR
++void mt7996_set_wireless_vif(void *data, u8 *mac, struct ieee80211_vif *vif);
++void mt7996_vendor_register(struct mt7996_phy *phy);
++#endif
++
+ #ifdef CONFIG_MTK_DEBUG
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir);
+ int mt7996_mcu_muru_dbg_info(struct mt7996_dev *dev, u16 item, u8 val);
+diff --git a/mt7996/vendor.c b/mt7996/vendor.c
+new file mode 100644
+index 000000000..b5ecbdf1d
+--- /dev/null
++++ b/mt7996/vendor.c
+@@ -0,0 +1,85 @@
++// SPDX-License-Identifier: ISC
++/*
++ * Copyright (C) 2020, MediaTek Inc. All rights reserved.
++ */
++
++#include <net/netlink.h>
++
++#include "mt7996.h"
++#include "mcu.h"
++#include "vendor.h"
++#include "mtk_mcu.h"
++
++static const struct nla_policy
++mu_ctrl_policy[NUM_MTK_VENDOR_ATTRS_MU_CTRL] = {
++	[MTK_VENDOR_ATTR_MU_CTRL_ONOFF] = {.type = NLA_U8 },
++	[MTK_VENDOR_ATTR_MU_CTRL_DUMP] = {.type = NLA_U8 },
++};
++
++static int mt7996_vendor_mu_ctrl(struct wiphy *wiphy,
++				 struct wireless_dev *wdev,
++				 const void *data,
++				 int data_len)
++{
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_MU_CTRL];
++	int err;
++	u8 val8;
++	u32 val32 = 0;
++
++	err = nla_parse(tb, MTK_VENDOR_ATTR_MU_CTRL_MAX, data, data_len,
++			mu_ctrl_policy, NULL);
++	if (err)
++		return err;
++
++	if (tb[MTK_VENDOR_ATTR_MU_CTRL_ONOFF]) {
++		val8 = nla_get_u8(tb[MTK_VENDOR_ATTR_MU_CTRL_ONOFF]);
++		val32 |= FIELD_PREP(RATE_CFG_MODE, RATE_PARAM_AUTO_MU) |
++			 FIELD_PREP(RATE_CFG_VAL, val8);
++		ieee80211_iterate_active_interfaces_atomic(hw, IEEE80211_IFACE_ITER_RESUME_ALL,
++							   mt7996_set_wireless_vif, &val32);
++	}
++
++	return 0;
++}
++
++static int
++mt7996_vendor_mu_ctrl_dump(struct wiphy *wiphy, struct wireless_dev *wdev,
++			   struct sk_buff *skb, const void *data, int data_len,
++			   unsigned long *storage)
++{
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	int len = 0;
++
++	if (*storage == 1)
++		return -ENOENT;
++	*storage = 1;
++
++	if (nla_put_u8(skb, MTK_VENDOR_ATTR_MU_CTRL_DUMP, phy->muru_onoff))
++		return -ENOMEM;
++	len += 1;
++
++	return len;
++}
++
++static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
++	{
++		.info = {
++			.vendor_id = MTK_NL80211_VENDOR_ID,
++			.subcmd = MTK_NL80211_VENDOR_SUBCMD_MU_CTRL,
++		},
++		.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
++			 WIPHY_VENDOR_CMD_NEED_RUNNING,
++		.doit = mt7996_vendor_mu_ctrl,
++		.dumpit = mt7996_vendor_mu_ctrl_dump,
++		.policy = mu_ctrl_policy,
++		.maxattr = MTK_VENDOR_ATTR_MU_CTRL_MAX,
++	},
++};
++
++void mt7996_vendor_register(struct mt7996_phy *phy)
++{
++	phy->mt76->hw->wiphy->vendor_commands = mt7996_vendor_commands;
++	phy->mt76->hw->wiphy->n_vendor_commands = ARRAY_SIZE(mt7996_vendor_commands);
++}
+diff --git a/mt7996/vendor.h b/mt7996/vendor.h
+new file mode 100644
+index 000000000..8ac3ba8ed
+--- /dev/null
++++ b/mt7996/vendor.h
+@@ -0,0 +1,22 @@
++#ifndef __MT7996_VENDOR_H
++#define __MT7996_VENDOR_H
++
++#define MTK_NL80211_VENDOR_ID	0x0ce7
++
++enum mtk_nl80211_vendor_subcmds {
++	MTK_NL80211_VENDOR_SUBCMD_MU_CTRL = 0xc5,
++};
++
++enum mtk_vendor_attr_mu_ctrl {
++	MTK_VENDOR_ATTR_MU_CTRL_UNSPEC,
++
++	MTK_VENDOR_ATTR_MU_CTRL_ONOFF,
++	MTK_VENDOR_ATTR_MU_CTRL_DUMP,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_MU_CTRL,
++	MTK_VENDOR_ATTR_MU_CTRL_MAX =
++		NUM_MTK_VENDOR_ATTRS_MU_CTRL - 1
++};
++
++#endif
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0038-mtk-wifi-mt76-mt7996-Add-air-monitor-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0038-mtk-wifi-mt76-mt7996-Add-air-monitor-support.patch
new file mode 100644
index 0000000..82b29df
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0038-mtk-wifi-mt76-mt7996-Add-air-monitor-support.patch
@@ -0,0 +1,566 @@
+From 19c8ee7666a236f4b0e5a4df3ee41cc273e9313e Mon Sep 17 00:00:00 2001
+From: Evelyn Tsai <evelyn.tsai@mediatek.com>
+Date: Wed, 26 Apr 2023 04:40:05 +0800
+Subject: [PATCH 038/120] mtk: wifi: mt76: mt7996: Add air monitor support
+
+Change-Id: I6b9cf0fcd1f815d3d29361a14ed63bfa77f7a958
+---
+ mt76_connac_mcu.h |   1 +
+ mt7996/mac.c      |   4 +
+ mt7996/main.c     |   4 +
+ mt7996/mt7996.h   |  35 +++++
+ mt7996/vendor.c   | 362 ++++++++++++++++++++++++++++++++++++++++++++++
+ mt7996/vendor.h   |  39 +++++
+ 6 files changed, 445 insertions(+)
+
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index d8830dc25..5b2eb5751 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1252,6 +1252,7 @@ enum {
+ 	MCU_UNI_CMD_REG_ACCESS = 0x0d,
+ 	MCU_UNI_CMD_CHIP_CONFIG = 0x0e,
+ 	MCU_UNI_CMD_POWER_CTRL = 0x0f,
++	MCU_UNI_CMD_CFG_SMESH = 0x10,
+ 	MCU_UNI_CMD_RX_HDR_TRANS = 0x12,
+ 	MCU_UNI_CMD_SER = 0x13,
+ 	MCU_UNI_CMD_TWT = 0x14,
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index c9f45abef..d55e5a761 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -679,6 +679,10 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
+ 			if (ieee80211_has_a4(fc) && is_mesh && status->amsdu)
+ 				*qos &= ~IEEE80211_QOS_CTL_A_MSDU_PRESENT;
+ 		}
++#ifdef CONFIG_MTK_VENDOR
++		if (phy->amnt_ctrl.enable && !ieee80211_is_beacon(fc))
++			mt7996_vendor_amnt_fill_rx(phy, skb);
++#endif
+ 	} else {
+ 		status->flag |= RX_FLAG_8023;
+ 		mt7996_wed_check_ppe(dev, &dev->mt76.q_rx[q], msta, skb,
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 7505037b4..ee754d44e 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -747,6 +747,10 @@ int mt7996_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	mt7996_mac_wtbl_update(dev, idx,
+ 			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+ 
++#ifdef CONFIG_MTK_VENDOR
++	mt7996_vendor_amnt_sta_remove(mvif->phy, sta);
++#endif
++
+ 	ret = mt7996_mcu_add_sta(dev, vif, sta, true, true);
+ 	if (ret)
+ 		return ret;
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index a5aa7a30a..11a73efb1 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -259,6 +259,34 @@ struct mt7996_wed_rro_session_id {
+ 	u16 id;
+ };
+ 
++#ifdef CONFIG_MTK_VENDOR
++#define MT7996_AIR_MONITOR_MAX_ENTRY	16
++#define MT7996_AIR_MONITOR_MAX_GROUP	(MT7996_AIR_MONITOR_MAX_ENTRY >> 1)
++
++struct mt7996_air_monitor_group {
++	bool enable;
++	bool used[2];
++};
++
++struct mt7996_air_monitor_entry {
++	bool enable;
++
++	u8 group_idx;
++	u8 group_used_idx;
++	u8 muar_idx;
++	u8 addr[ETH_ALEN];
++	u32 last_seen;
++	s8 rssi[4];
++	struct ieee80211_sta *sta;
++};
++
++struct mt7996_air_monitor_ctrl {
++	u8 enable;
++	struct mt7996_air_monitor_group group[MT7996_AIR_MONITOR_MAX_GROUP];
++	struct mt7996_air_monitor_entry entry[MT7996_AIR_MONITOR_MAX_ENTRY];
++};
++#endif
++
+ struct mt7996_phy {
+ 	struct mt76_phy *mt76;
+ 	struct mt7996_dev *dev;
+@@ -311,6 +339,10 @@ struct mt7996_phy {
+ 		u8 spe_idx;
+ 	} test;
+ #endif
++#ifdef CONFIG_MTK_VENDOR
++	spinlock_t amnt_lock;
++	struct mt7996_air_monitor_ctrl amnt_ctrl;
++#endif
+ };
+ 
+ struct mt7996_dev {
+@@ -739,6 +771,9 @@ u32 mt7996_wed_init_buf(void *ptr, dma_addr_t phys, int token_id);
+ #ifdef CONFIG_MTK_VENDOR
+ void mt7996_set_wireless_vif(void *data, u8 *mac, struct ieee80211_vif *vif);
+ void mt7996_vendor_register(struct mt7996_phy *phy);
++void mt7996_vendor_amnt_fill_rx(struct mt7996_phy *phy, struct sk_buff *skb);
++int mt7996_vendor_amnt_sta_remove(struct mt7996_phy *phy,
++				  struct ieee80211_sta *sta);
+ #endif
+ 
+ #ifdef CONFIG_MTK_DEBUG
+diff --git a/mt7996/vendor.c b/mt7996/vendor.c
+index b5ecbdf1d..f3b089d72 100644
+--- a/mt7996/vendor.c
++++ b/mt7996/vendor.c
+@@ -16,6 +16,32 @@ mu_ctrl_policy[NUM_MTK_VENDOR_ATTRS_MU_CTRL] = {
+ 	[MTK_VENDOR_ATTR_MU_CTRL_DUMP] = {.type = NLA_U8 },
+ };
+ 
++static const struct nla_policy
++amnt_ctrl_policy[NUM_MTK_VENDOR_ATTRS_AMNT_CTRL] = {
++	[MTK_VENDOR_ATTR_AMNT_CTRL_SET] = {.type = NLA_NESTED },
++	[MTK_VENDOR_ATTR_AMNT_CTRL_DUMP] = { .type = NLA_NESTED },
++};
++
++static const struct nla_policy
++amnt_set_policy[NUM_MTK_VENDOR_ATTRS_AMNT_SET] = {
++	[MTK_VENDOR_ATTR_AMNT_SET_INDEX] = {.type = NLA_U8 },
++	[MTK_VENDOR_ATTR_AMNT_SET_MACADDR] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
++};
++
++static const struct nla_policy
++amnt_dump_policy[NUM_MTK_VENDOR_ATTRS_AMNT_DUMP] = {
++	[MTK_VENDOR_ATTR_AMNT_DUMP_INDEX] = {.type = NLA_U8 },
++	[MTK_VENDOR_ATTR_AMNT_DUMP_LEN] = { .type = NLA_U8 },
++	[MTK_VENDOR_ATTR_AMNT_DUMP_RESULT] = { .type = NLA_NESTED },
++};
++
++struct mt7996_amnt_data {
++	u8 idx;
++	u8 addr[ETH_ALEN];
++	s8 rssi[4];
++	u32 last_seen;
++};
++
+ static int mt7996_vendor_mu_ctrl(struct wiphy *wiphy,
+ 				 struct wireless_dev *wdev,
+ 				 const void *data,
+@@ -63,6 +89,328 @@ mt7996_vendor_mu_ctrl_dump(struct wiphy *wiphy, struct wireless_dev *wdev,
+ 	return len;
+ }
+ 
++void mt7996_vendor_amnt_fill_rx(struct mt7996_phy *phy, struct sk_buff *skb)
++{
++	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
++	struct mt7996_air_monitor_ctrl *ctrl = &phy->amnt_ctrl;
++	struct ieee80211_hdr *hdr = mt76_skb_get_hdr(skb);
++	__le16 fc = hdr->frame_control;
++	u8 addr[ETH_ALEN];
++	int i;
++
++	if (!ieee80211_has_fromds(fc))
++		ether_addr_copy(addr, hdr->addr2);
++	else if (ieee80211_has_tods(fc))
++		ether_addr_copy(addr, hdr->addr4);
++	else
++		ether_addr_copy(addr, hdr->addr3);
++
++	spin_lock_bh(&phy->amnt_lock);
++	for (i = 0; i < MT7996_AIR_MONITOR_MAX_ENTRY; i++) {
++		struct mt7996_air_monitor_entry *entry;
++
++		if (ether_addr_equal(addr, ctrl->entry[i].addr)) {
++			entry = &ctrl->entry[i];
++			entry->rssi[0] = status->chain_signal[0];
++			entry->rssi[1] = status->chain_signal[1];
++			entry->rssi[2] = status->chain_signal[2];
++			entry->rssi[3] = status->chain_signal[3];
++			entry->last_seen = jiffies;
++			break;
++		}
++	}
++	spin_unlock_bh(&phy->amnt_lock);
++}
++
++static int
++mt7996_vendor_smesh_ctrl(struct mt7996_phy *phy, u8 write,
++			 u8 enable, u8 *value)
++{
++#define UNI_CMD_SMESH_PARAM  0
++	struct mt7996_dev *dev = phy->dev;
++	struct smesh_param {
++		__le16 tag;
++		__le16 length;
++
++		u8 enable;
++		bool a2;
++		bool a1;
++		bool data;
++		bool mgnt;
++		bool ctrl;
++		u8 padding[2];
++	} req = {
++		.tag = cpu_to_le16(UNI_CMD_SMESH_PARAM),
++		.length = cpu_to_le16(sizeof(req) - 4),
++
++		.enable = enable,
++		.a2 = true,
++		.a1 = true,
++		.data = true,
++		.mgnt = false,
++		.ctrl = false,
++	};
++	struct smesh_param *res;
++	struct sk_buff *skb;
++	int ret = 0;
++
++	if (!value)
++		return -EINVAL;
++
++	ret = mt76_mcu_send_and_get_msg(&dev->mt76, MCU_WM_UNI_CMD(CFG_SMESH),
++					&req, sizeof(req), !write, &skb);
++
++	if (ret || write)
++		return ret;
++
++	res = (struct smesh_param *) skb->data;
++
++	*value = res->enable;
++
++	dev_kfree_skb(skb);
++
++	return 0;
++}
++
++static int
++mt7996_vendor_amnt_muar(struct mt7996_phy *phy, u8 muar_idx, u8 *addr)
++{
++#define UNI_CMD_MUAR_ENTRY  2
++	struct mt7996_dev *dev = phy->dev;
++	struct muar_entry {
++		__le16 tag;
++		__le16 length;
++
++		bool smesh;
++		u8 hw_bss_index;
++		u8 muar_idx;
++		u8 entry_add;
++		u8 mac_addr[6];
++		u8 padding[2];
++	} __packed req = {
++		.tag = cpu_to_le16(UNI_CMD_MUAR_ENTRY),
++		.length = cpu_to_le16(sizeof(req) - 4),
++
++		.smesh = true,
++		.hw_bss_index = phy != &dev->phy,
++		.muar_idx = muar_idx,
++		.entry_add = 1,
++	};
++
++	ether_addr_copy(req.mac_addr, addr);
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(REPT_MUAR), &req,
++				 sizeof(req), true);
++}
++
++static int
++mt7996_vendor_amnt_set_en(struct mt7996_phy *phy, u8 enable)
++{
++	u8 status;
++	int ret;
++
++	ret = mt7996_vendor_smesh_ctrl(phy, 0, enable, &status);
++	if (ret)
++		return ret;
++
++	if (status == enable)
++		return 0;
++
++	ret = mt7996_vendor_smesh_ctrl(phy, 1, enable, &status);
++	if (ret)
++		return ret;
++
++	return 0;
++}
++
++static int
++mt7996_vendor_amnt_set_addr(struct mt7996_phy *phy, u8 index, u8 *addr)
++{
++	struct mt7996_air_monitor_ctrl *amnt_ctrl = &phy->amnt_ctrl;
++	struct mt7996_air_monitor_group *group;
++	struct mt7996_air_monitor_entry *entry;
++	int ret, i, j;
++
++	if (index >= MT7996_AIR_MONITOR_MAX_ENTRY)
++		return -1;
++
++	spin_lock_bh(&phy->amnt_lock);
++	entry = &amnt_ctrl->entry[index];
++	if (!is_zero_ether_addr(addr)) {
++		if (entry->enable == false) {
++			for (i = 0; i < MT7996_AIR_MONITOR_MAX_GROUP; i++) {
++				group = &(amnt_ctrl->group[i]);
++				if (group->used[0] == false)
++					j = 0;
++				else if (group->used[1] == false)
++					j = 1;
++				else
++					continue;
++
++				group->enable = true;
++				group->used[j] = true;
++				entry->enable = true;
++				entry->group_idx = i;
++				entry->group_used_idx = j;
++				entry->muar_idx = 32 + 4 * i + 2 * j;
++				break;
++			}
++		}
++	} else {
++		group = &(amnt_ctrl->group[entry->group_idx]);
++
++		group->used[entry->group_used_idx] = false;
++		if (group->used[0] == false && group->used[1] == false)
++			group->enable = false;
++
++		entry->enable = false;
++	}
++	ether_addr_copy(entry->addr, addr);
++	amnt_ctrl->enable &= ~(1 << entry->group_idx);
++	amnt_ctrl->enable |= entry->enable << entry->group_idx;
++	spin_unlock_bh(&phy->amnt_lock);
++
++	ret = mt7996_vendor_amnt_muar(phy, entry->muar_idx, addr);
++	if (ret)
++		return ret;
++
++	return mt7996_vendor_amnt_set_en(phy, amnt_ctrl->enable);
++}
++
++static int
++mt7966_vendor_amnt_ctrl(struct wiphy *wiphy, struct wireless_dev *wdev,
++			const void *data, int data_len)
++{
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct nlattr *tb1[NUM_MTK_VENDOR_ATTRS_AMNT_CTRL];
++	struct nlattr *tb2[NUM_MTK_VENDOR_ATTRS_AMNT_SET];
++	u8 index = 0;
++	u8 mac_addr[ETH_ALEN];
++	int err;
++
++	err = nla_parse(tb1, MTK_VENDOR_ATTR_AMNT_CTRL_MAX, data, data_len,
++			amnt_ctrl_policy, NULL);
++	if (err)
++		return err;
++
++	if (!tb1[MTK_VENDOR_ATTR_AMNT_CTRL_SET])
++		return -EINVAL;
++
++	err = nla_parse_nested(tb2, MTK_VENDOR_ATTR_AMNT_SET_MAX,
++		tb1[MTK_VENDOR_ATTR_AMNT_CTRL_SET], amnt_set_policy, NULL);
++
++	if (!tb2[MTK_VENDOR_ATTR_AMNT_SET_INDEX] ||
++		!tb2[MTK_VENDOR_ATTR_AMNT_SET_MACADDR])
++		return -EINVAL;
++
++	index = nla_get_u8(tb2[MTK_VENDOR_ATTR_AMNT_SET_INDEX]);
++	memcpy(mac_addr, nla_data(tb2[MTK_VENDOR_ATTR_AMNT_SET_MACADDR]), ETH_ALEN);
++
++	return mt7996_vendor_amnt_set_addr(phy, index, mac_addr);
++}
++
++int mt7996_vendor_amnt_sta_remove(struct mt7996_phy *phy,
++				  struct ieee80211_sta *sta)
++{
++	u8 zero[ETH_ALEN] = {};
++	int i;
++
++	if (!phy->amnt_ctrl.enable)
++		return 0;
++
++	for (i = 0; i < MT7996_AIR_MONITOR_MAX_ENTRY; i++)
++		if (ether_addr_equal(sta->addr, phy->amnt_ctrl.entry[i].addr))
++			return mt7996_vendor_amnt_set_addr(phy, i, zero);
++	return 0;
++}
++
++static int
++mt7996_amnt_dump(struct mt7996_phy *phy, struct sk_buff *skb,
++		 u8 amnt_idx, int *attrtype)
++{
++	struct mt7996_air_monitor_entry *entry;
++	struct mt7996_amnt_data data;
++	u32 last_seen = 0;
++
++	spin_lock_bh(&phy->amnt_lock);
++	entry = &phy->amnt_ctrl.entry[amnt_idx];
++	if (entry->enable == 0) {
++		spin_unlock_bh(&phy->amnt_lock);
++		return 0;
++	}
++
++	last_seen = jiffies_to_msecs(jiffies - entry->last_seen);
++	ether_addr_copy(data.addr, entry->addr);
++	data.rssi[0] = entry->rssi[0];
++	data.rssi[1] = entry->rssi[1];
++	data.rssi[2] = entry->rssi[2];
++	data.rssi[3] = entry->rssi[3];
++	spin_unlock_bh(&phy->amnt_lock);
++
++	data.idx = amnt_idx;
++	data.last_seen = last_seen;
++
++	nla_put(skb, (*attrtype)++, sizeof(struct mt7996_amnt_data), &data);
++
++	return 1;
++}
++
++static int
++mt7966_vendor_amnt_ctrl_dump(struct wiphy *wiphy, struct wireless_dev *wdev,
++			     struct sk_buff *skb, const void *data, int data_len,
++			     unsigned long *storage)
++{
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct nlattr *tb1[NUM_MTK_VENDOR_ATTRS_AMNT_CTRL];
++	struct nlattr *tb2[NUM_MTK_VENDOR_ATTRS_AMNT_DUMP];
++	void *a, *b;
++	int err = 0, attrtype = 0, i, len = 0;
++	u8 amnt_idx;
++
++	if (*storage == 1)
++		return -ENOENT;
++	*storage = 1;
++
++	err = nla_parse(tb1, MTK_VENDOR_ATTR_AMNT_CTRL_MAX, data, data_len,
++			amnt_ctrl_policy, NULL);
++	if (err)
++		return err;
++
++	if (!tb1[MTK_VENDOR_ATTR_AMNT_CTRL_DUMP])
++		return -EINVAL;
++
++	err = nla_parse_nested(tb2, MTK_VENDOR_ATTR_AMNT_DUMP_MAX,
++			       tb1[MTK_VENDOR_ATTR_AMNT_CTRL_DUMP],
++			       amnt_dump_policy, NULL);
++	if (err)
++		return err;
++
++	if (!tb2[MTK_VENDOR_ATTR_AMNT_DUMP_INDEX])
++		return -EINVAL;
++
++	amnt_idx = nla_get_u8(tb2[MTK_VENDOR_ATTR_AMNT_DUMP_INDEX]);
++
++	a = nla_nest_start(skb, MTK_VENDOR_ATTR_AMNT_CTRL_DUMP);
++	b = nla_nest_start(skb, MTK_VENDOR_ATTR_AMNT_DUMP_RESULT);
++
++	if (amnt_idx != 0xff) {
++		len += mt7996_amnt_dump(phy, skb, amnt_idx, &attrtype);
++	} else {
++		for (i = 0; i < MT7996_AIR_MONITOR_MAX_ENTRY; i++)
++			len += mt7996_amnt_dump(phy, skb, i, &attrtype);
++	}
++
++	nla_nest_end(skb, b);
++
++	nla_put_u8(skb, MTK_VENDOR_ATTR_AMNT_DUMP_LEN, len);
++
++	nla_nest_end(skb, a);
++
++	return len + 1;
++}
++
++
+ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 	{
+ 		.info = {
+@@ -76,10 +424,24 @@ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 		.policy = mu_ctrl_policy,
+ 		.maxattr = MTK_VENDOR_ATTR_MU_CTRL_MAX,
+ 	},
++	{
++		.info = {
++			.vendor_id = MTK_NL80211_VENDOR_ID,
++			.subcmd = MTK_NL80211_VENDOR_SUBCMD_AMNT_CTRL,
++		},
++		.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
++			 WIPHY_VENDOR_CMD_NEED_RUNNING,
++		.doit = mt7966_vendor_amnt_ctrl,
++		.dumpit = mt7966_vendor_amnt_ctrl_dump,
++		.policy = amnt_ctrl_policy,
++		.maxattr = MTK_VENDOR_ATTR_AMNT_CTRL_MAX,
++	},
+ };
+ 
+ void mt7996_vendor_register(struct mt7996_phy *phy)
+ {
+ 	phy->mt76->hw->wiphy->vendor_commands = mt7996_vendor_commands;
+ 	phy->mt76->hw->wiphy->n_vendor_commands = ARRAY_SIZE(mt7996_vendor_commands);
++
++	spin_lock_init(&phy->amnt_lock);
+ }
+diff --git a/mt7996/vendor.h b/mt7996/vendor.h
+index 8ac3ba8ed..2078cafaf 100644
+--- a/mt7996/vendor.h
++++ b/mt7996/vendor.h
+@@ -4,6 +4,7 @@
+ #define MTK_NL80211_VENDOR_ID	0x0ce7
+ 
+ enum mtk_nl80211_vendor_subcmds {
++	MTK_NL80211_VENDOR_SUBCMD_AMNT_CTRL = 0xae,
+ 	MTK_NL80211_VENDOR_SUBCMD_MU_CTRL = 0xc5,
+ };
+ 
+@@ -19,4 +20,42 @@ enum mtk_vendor_attr_mu_ctrl {
+ 		NUM_MTK_VENDOR_ATTRS_MU_CTRL - 1
+ };
+ 
++enum mtk_vendor_attr_mnt_ctrl {
++	MTK_VENDOR_ATTR_AMNT_CTRL_UNSPEC,
++
++	MTK_VENDOR_ATTR_AMNT_CTRL_SET,
++	MTK_VENDOR_ATTR_AMNT_CTRL_DUMP,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_AMNT_CTRL,
++	MTK_VENDOR_ATTR_AMNT_CTRL_MAX =
++		NUM_MTK_VENDOR_ATTRS_AMNT_CTRL - 1
++};
++
++enum mtk_vendor_attr_mnt_set {
++	MTK_VENDOR_ATTR_AMNT_SET_UNSPEC,
++
++	MTK_VENDOR_ATTR_AMNT_SET_INDEX,
++	MTK_VENDOR_ATTR_AMNT_SET_MACADDR,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_AMNT_SET,
++	MTK_VENDOR_ATTR_AMNT_SET_MAX =
++		NUM_MTK_VENDOR_ATTRS_AMNT_SET - 1
++};
++
++enum mtk_vendor_attr_mnt_dump {
++	MTK_VENDOR_ATTR_AMNT_DUMP_UNSPEC,
++
++	MTK_VENDOR_ATTR_AMNT_DUMP_INDEX,
++	MTK_VENDOR_ATTR_AMNT_DUMP_LEN,
++	MTK_VENDOR_ATTR_AMNT_DUMP_RESULT,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_AMNT_DUMP,
++	MTK_VENDOR_ATTR_AMNT_DUMP_MAX =
++		NUM_MTK_VENDOR_ATTRS_AMNT_DUMP - 1
++};
++
++
+ #endif
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0039-mtk-wifi-mt76-mt7996-add-driver-support-for-wpa3-ocv.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0039-mtk-wifi-mt76-mt7996-add-driver-support-for-wpa3-ocv.patch
new file mode 100644
index 0000000..eabeada
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0039-mtk-wifi-mt76-mt7996-add-driver-support-for-wpa3-ocv.patch
@@ -0,0 +1,27 @@
+From d4e9249cf62db96fee4c93af5370c526e2029208 Mon Sep 17 00:00:00 2001
+From: mtk23510 <rudra.shahi@mediatek.com>
+Date: Fri, 24 Mar 2023 19:18:53 +0800
+Subject: [PATCH 039/120] mtk: wifi: mt76: mt7996: add driver support for wpa3
+ ocv and bp mt76
+
+Signed-off-by: mtk23510 <rudra.shahi@mediatek.com>
+---
+ mt7996/init.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mt7996/init.c b/mt7996/init.c
+index fba61c017..1498787f1 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -391,6 +391,8 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed)
+ 	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CAN_REPLACE_PTK0);
+ 	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_MU_MIMO_AIR_SNIFFER);
+ 
++	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_OPERATING_CHANNEL_VALIDATION);
++	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_BEACON_PROTECTION);
+ 	if (!mdev->dev->of_node ||
+ 	    !of_property_read_bool(mdev->dev->of_node,
+ 				   "mediatek,disable-radar-background"))
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0040-mtk-wifi-mt76-mt7996-add-vendor-cmd-to-get-available.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0040-mtk-wifi-mt76-mt7996-add-vendor-cmd-to-get-available.patch
new file mode 100644
index 0000000..ef1efdf
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0040-mtk-wifi-mt76-mt7996-add-vendor-cmd-to-get-available.patch
@@ -0,0 +1,107 @@
+From 380180a44c2b8040ebf1a717497b0947c6dd0fb4 Mon Sep 17 00:00:00 2001
+From: Yi-Chia Hsieh <yi-chia.hsieh@mediatek.com>
+Date: Wed, 3 May 2023 05:08:07 +0800
+Subject: [PATCH 040/120] mtk: wifi: mt76: mt7996: add vendor cmd to get
+ available color bitmap
+
+Add a vendor cmd to notify user space available color bitmap.
+The OBSS BSS color bitmap is maintained in mac80211, so mt76 will make use of that.
+
+Signed-off-by: Yi-Chia Hsieh <yi-chia.hsieh@mediatek.com>
+---
+ mt7996/vendor.c | 36 ++++++++++++++++++++++++++++++++++++
+ mt7996/vendor.h | 11 +++++++++++
+ 2 files changed, 47 insertions(+)
+
+diff --git a/mt7996/vendor.c b/mt7996/vendor.c
+index f3b089d72..391015777 100644
+--- a/mt7996/vendor.c
++++ b/mt7996/vendor.c
+@@ -35,6 +35,11 @@ amnt_dump_policy[NUM_MTK_VENDOR_ATTRS_AMNT_DUMP] = {
+ 	[MTK_VENDOR_ATTR_AMNT_DUMP_RESULT] = { .type = NLA_NESTED },
+ };
+ 
++static struct nla_policy
++bss_color_ctrl_policy[NUM_MTK_VENDOR_ATTRS_BSS_COLOR_CTRL] = {
++	[MTK_VENDOR_ATTR_AVAL_BSS_COLOR_BMP] = { .type = NLA_U64 },
++};
++
+ struct mt7996_amnt_data {
+ 	u8 idx;
+ 	u8 addr[ETH_ALEN];
+@@ -410,6 +415,26 @@ mt7966_vendor_amnt_ctrl_dump(struct wiphy *wiphy, struct wireless_dev *wdev,
+ 	return len + 1;
+ }
+ 
++static int
++mt7996_vendor_bss_color_ctrl_dump(struct wiphy *wiphy, struct wireless_dev *wdev,
++				  struct sk_buff *skb, const void *data, int data_len,
++				  unsigned long *storage)
++{
++	struct ieee80211_vif *vif = wdev_to_ieee80211_vif(wdev);
++	struct ieee80211_bss_conf *bss_conf = &vif->bss_conf;
++	int len = 0;
++
++	if (*storage == 1)
++		return -ENOENT;
++	*storage = 1;
++
++	if (nla_put_u64_64bit(skb, MTK_VENDOR_ATTR_AVAL_BSS_COLOR_BMP,
++			      ~bss_conf->used_color_bitmap, NL80211_ATTR_PAD))
++		return -ENOMEM;
++	len += 1;
++
++	return len;
++}
+ 
+ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 	{
+@@ -436,6 +461,17 @@ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 		.policy = amnt_ctrl_policy,
+ 		.maxattr = MTK_VENDOR_ATTR_AMNT_CTRL_MAX,
+ 	},
++	{
++		.info = {
++			.vendor_id = MTK_NL80211_VENDOR_ID,
++			.subcmd = MTK_NL80211_VENDOR_SUBCMD_BSS_COLOR_CTRL,
++		},
++		.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
++			WIPHY_VENDOR_CMD_NEED_RUNNING,
++		.dumpit = mt7996_vendor_bss_color_ctrl_dump,
++		.policy = bss_color_ctrl_policy,
++		.maxattr = MTK_VENDOR_ATTR_BSS_COLOR_CTRL_MAX,
++	},
+ };
+ 
+ void mt7996_vendor_register(struct mt7996_phy *phy)
+diff --git a/mt7996/vendor.h b/mt7996/vendor.h
+index 2078cafaf..eec9e74a2 100644
+--- a/mt7996/vendor.h
++++ b/mt7996/vendor.h
+@@ -6,6 +6,7 @@
+ enum mtk_nl80211_vendor_subcmds {
+ 	MTK_NL80211_VENDOR_SUBCMD_AMNT_CTRL = 0xae,
+ 	MTK_NL80211_VENDOR_SUBCMD_MU_CTRL = 0xc5,
++	MTK_NL80211_VENDOR_SUBCMD_BSS_COLOR_CTRL = 0xca,
+ };
+ 
+ enum mtk_vendor_attr_mu_ctrl {
+@@ -57,5 +58,15 @@ enum mtk_vendor_attr_mnt_dump {
+ 		NUM_MTK_VENDOR_ATTRS_AMNT_DUMP - 1
+ };
+ 
++enum mtk_vendor_attr_bss_color_ctrl {
++	MTK_VENDOR_ATTR_BSS_COLOR_CTRL_UNSPEC,
++
++	MTK_VENDOR_ATTR_AVAL_BSS_COLOR_BMP,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_BSS_COLOR_CTRL,
++	MTK_VENDOR_ATTR_BSS_COLOR_CTRL_MAX =
++		NUM_MTK_VENDOR_ATTRS_BSS_COLOR_CTRL - 1
++};
+ 
+ #endif
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0041-mtk-wifi-mt76-mt7996-add-debugfs-for-fw-coredump.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0041-mtk-wifi-mt76-mt7996-add-debugfs-for-fw-coredump.patch
new file mode 100644
index 0000000..99f928b
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0041-mtk-wifi-mt76-mt7996-add-debugfs-for-fw-coredump.patch
@@ -0,0 +1,171 @@
+From 573b20002b79928f4698f9321f532fb8f0874973 Mon Sep 17 00:00:00 2001
+From: Bo Jiao <Bo.Jiao@mediatek.com>
+Date: Fri, 19 May 2023 14:56:07 +0800
+Subject: [PATCH 041/120] mtk: wifi: mt76: mt7996: add debugfs for fw coredump.
+
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+---
+ mt7996/debugfs.c | 19 +++++++++++++++++--
+ mt7996/mac.c     | 28 +++++++++++++++++++++++++---
+ mt7996/mcu.h     |  4 ++++
+ mt7996/mt7996.h  | 10 ++++++++++
+ 4 files changed, 56 insertions(+), 5 deletions(-)
+
+diff --git a/mt7996/debugfs.c b/mt7996/debugfs.c
+index 2e9c7ea16..da47a5480 100644
+--- a/mt7996/debugfs.c
++++ b/mt7996/debugfs.c
+@@ -84,6 +84,8 @@ mt7996_sys_recovery_set(struct file *file, const char __user *user_buf,
+ 	 * 7: trigger & enable system error L4 mdp recovery.
+ 	 * 8: trigger & enable system error full recovery.
+ 	 * 9: trigger firmware crash.
++	 * 10: trigger grab wa firmware coredump.
++	 * 11: trigger grab wm firmware coredump.
+ 	 */
+ 	case UNI_CMD_SER_QUERY:
+ 		ret = mt7996_mcu_set_ser(dev, UNI_CMD_SER_QUERY, 0, band);
+@@ -105,15 +107,25 @@ mt7996_sys_recovery_set(struct file *file, const char __user *user_buf,
+ 	/* enable full chip reset */
+ 	case UNI_CMD_SER_SET_RECOVER_FULL:
+ 		mt76_set(dev, MT_WFDMA0_MCU_HOST_INT_ENA, MT_MCU_CMD_WDT_MASK);
+-		dev->recovery.state |= MT_MCU_CMD_WDT_MASK;
++		dev->recovery.state |= MT_MCU_CMD_WM_WDT;
+ 		mt7996_reset(dev);
+ 		break;
+ 
+ 	/* WARNING: trigger firmware crash */
+ 	case UNI_CMD_SER_SET_SYSTEM_ASSERT:
++		// trigger wm assert exception
+ 		ret = mt7996_mcu_trigger_assert(dev);
+ 		if (ret)
+ 			return ret;
++		// trigger wa assert exception
++		mt76_wr(dev, 0x89098108, 0x20);
++		mt76_wr(dev, 0x89098118, 0x20);
++		break;
++	case UNI_CMD_SER_FW_COREDUMP_WA:
++		mt7996_coredump(dev, MT7996_COREDUMP_MANUAL_WA);
++		break;
++	case UNI_CMD_SER_FW_COREDUMP_WM:
++		mt7996_coredump(dev, MT7996_COREDUMP_MANUAL_WM);
+ 		break;
+ 	default:
+ 		break;
+@@ -160,7 +172,10 @@ mt7996_sys_recovery_get(struct file *file, char __user *user_buf,
+ 			  "8: trigger system error full recovery\n");
+ 	desc += scnprintf(buff + desc, bufsz - desc,
+ 			  "9: trigger firmware crash\n");
+-
++	desc += scnprintf(buff + desc, bufsz - desc,
++			  "10: trigger grab wa firmware coredump\n");
++	desc += scnprintf(buff + desc, bufsz - desc,
++			  "11: trigger grab wm firmware coredump\n");
+ 	/* SER statistics */
+ 	desc += scnprintf(buff + desc, bufsz - desc,
+ 			  "\nlet's dump firmware SER statistics...\n");
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index d55e5a761..1c1b3eb51 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -2080,15 +2080,36 @@ void mt7996_mac_dump_work(struct work_struct *work)
+ 	struct mt7996_dev *dev;
+ 
+ 	dev = container_of(work, struct mt7996_dev, dump_work);
+-	if (READ_ONCE(dev->recovery.state) & MT_MCU_CMD_WA_WDT)
++	if (dev->dump_state == MT7996_COREDUMP_MANUAL_WA ||
++	    READ_ONCE(dev->recovery.state) & MT_MCU_CMD_WA_WDT)
+ 		mt7996_mac_fw_coredump(dev, MT7996_RAM_TYPE_WA);
+ 
+-	if (READ_ONCE(dev->recovery.state) & MT_MCU_CMD_WM_WDT)
++	if (dev->dump_state == MT7996_COREDUMP_MANUAL_WM ||
++	    READ_ONCE(dev->recovery.state) & MT_MCU_CMD_WM_WDT)
+ 		mt7996_mac_fw_coredump(dev, MT7996_RAM_TYPE_WM);
+ 
+-	queue_work(dev->mt76.wq, &dev->reset_work);
++	if (READ_ONCE(dev->recovery.state) & MT_MCU_CMD_WDT_MASK)
++		queue_work(dev->mt76.wq, &dev->reset_work);
++
++	dev->dump_state = MT7996_COREDUMP_IDLE;
+ }
+ 
++void mt7996_coredump(struct mt7996_dev *dev, u8 state)
++{
++	if (state == MT7996_COREDUMP_IDLE ||
++	    state >= __MT7996_COREDUMP_TYPE_MAX)
++		return;
++
++	if (dev->dump_state != MT7996_COREDUMP_IDLE)
++		return;
++
++	dev->dump_state = state;
++	dev_info(dev->mt76.dev, "%s attempting grab coredump\n",
++		 wiphy_name(dev->mt76.hw->wiphy));
++
++	queue_work(dev->mt76.wq, &dev->dump_work);
++ }
++
+ void mt7996_reset(struct mt7996_dev *dev)
+ {
+ 	if (!dev->recovery.hw_init_done)
+@@ -2106,6 +2127,7 @@ void mt7996_reset(struct mt7996_dev *dev)
+ 
+ 		mt7996_irq_disable(dev, MT_INT_MCU_CMD);
+ 		queue_work(dev->mt76.wq, &dev->dump_work);
++		mt7996_coredump(dev, MT7996_COREDUMP_AUTO);
+ 		return;
+ 	}
+ 
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 68bf82fca..35f757dc2 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -956,7 +956,11 @@ enum {
+ 	UNI_CMD_SER_SET_RECOVER_L3_BF,
+ 	UNI_CMD_SER_SET_RECOVER_L4_MDP,
+ 	UNI_CMD_SER_SET_RECOVER_FULL,
++	/* fw assert */
+ 	UNI_CMD_SER_SET_SYSTEM_ASSERT,
++	/* coredump */
++	UNI_CMD_SER_FW_COREDUMP_WA,
++	UNI_CMD_SER_FW_COREDUMP_WM,
+ 	/* action */
+ 	UNI_CMD_SER_ENABLE = 1,
+ 	UNI_CMD_SER_SET,
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 11a73efb1..c6cd01c7d 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -139,6 +139,14 @@ enum mt7996_ram_type {
+ 	__MT7996_RAM_TYPE_MAX,
+ };
+ 
++enum mt7996_coredump_state {
++	MT7996_COREDUMP_IDLE = 0,
++	MT7996_COREDUMP_MANUAL_WA,
++	MT7996_COREDUMP_MANUAL_WM,
++	MT7996_COREDUMP_AUTO,
++	__MT7996_COREDUMP_TYPE_MAX,
++};
++
+ enum mt7996_txq_id {
+ 	MT7996_TXQ_FWDL = 16,
+ 	MT7996_TXQ_MCU_WM,
+@@ -388,6 +396,7 @@ struct mt7996_dev {
+ 
+ 	/* protects coredump data */
+ 	struct mutex dump_mutex;
++	u8 dump_state;
+ #ifdef CONFIG_DEV_COREDUMP
+ 	struct {
+ 		struct mt7996_crash_data *crash_data[__MT7996_RAM_TYPE_MAX];
+@@ -573,6 +582,7 @@ void mt7996_init_txpower(struct mt7996_phy *phy);
+ int mt7996_txbf_init(struct mt7996_dev *dev);
+ int mt7996_get_chip_sku(struct mt7996_dev *dev);
+ void mt7996_reset(struct mt7996_dev *dev);
++void mt7996_coredump(struct mt7996_dev *dev, u8 state);
+ int mt7996_run(struct ieee80211_hw *hw);
+ int mt7996_mcu_init(struct mt7996_dev *dev);
+ int mt7996_mcu_init_firmware(struct mt7996_dev *dev);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0042-mtk-wifi-mt76-mt7996-Add-mt7992-coredump-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0042-mtk-wifi-mt76-mt7996-Add-mt7992-coredump-support.patch
new file mode 100644
index 0000000..50f16c0
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0042-mtk-wifi-mt76-mt7996-Add-mt7992-coredump-support.patch
@@ -0,0 +1,164 @@
+From a4aff9967429d615181eeda2f423faffc08fc65c Mon Sep 17 00:00:00 2001
+From: Rex Lu <rex.lu@mediatek.com>
+Date: Mon, 25 Dec 2023 15:17:49 +0800
+Subject: [PATCH 042/120] mtk: wifi: mt76: mt7996: Add mt7992 coredump support
+
+1. Add mt7992 coredump support
+2. fixed if new ic have not support coredump, it may cause crash when remove module
+
+CR-Id: WCNCR00259516
+Signed-off-by: Rex Lu <rex.lu@mediatek.com>
+Change-Id: I2ae5425aac6be8ff69a2c411e796be308b558b6b
+---
+ mt7996/coredump.c | 80 ++++++++++++++++++++++++++++++++++++++---------
+ mt7996/mt7996.h   |  1 +
+ 2 files changed, 67 insertions(+), 14 deletions(-)
+
+diff --git a/mt7996/coredump.c b/mt7996/coredump.c
+index a7f91b56d..d09bcd4bd 100644
+--- a/mt7996/coredump.c
++++ b/mt7996/coredump.c
+@@ -67,6 +67,44 @@ static const struct mt7996_mem_region mt7996_wa_mem_regions[] = {
+ 	},
+ };
+ 
++static const struct mt7996_mem_region mt7992_wm_mem_regions[] = {
++	{
++		.start = 0x00800000,
++		.len = 0x0004bfff,
++		.name = "ULM0",
++	},
++	{
++		.start = 0x00900000,
++		.len = 0x00035fff,
++		.name = "ULM1",
++	},
++	{
++		.start = 0x02200000,
++		.len = 0x0003ffff,
++		.name = "ULM2",
++	},
++	{
++		.start = 0x00400000,
++		.len = 0x00027fff,
++		.name = "SRAM",
++	},
++	{
++		.start = 0xe0000000,
++		.len = 0x0015ffff,
++		.name = "CRAM0",
++	},
++	{
++		.start = 0xe0160000,
++		.len = 0x00c7fff,
++		.name = "CRAM1",
++	},
++	{
++		.start = 0x7c050000,
++		.len = 0x00007fff,
++		.name = "CONN_INFRA",
++	},
++};
++
+ const struct mt7996_mem_region*
+ mt7996_coredump_get_mem_layout(struct mt7996_dev *dev, u8 type, u32 *num)
+ {
+@@ -80,6 +118,14 @@ mt7996_coredump_get_mem_layout(struct mt7996_dev *dev, u8 type, u32 *num)
+ 
+ 		*num = ARRAY_SIZE(mt7996_wm_mem_regions);
+ 		return &mt7996_wm_mem_regions[0];
++	case 0x7992:
++		if (type == MT7996_RAM_TYPE_WA) {
++			/* mt7992 wa memory regions is the same as mt7996 */
++			*num = ARRAY_SIZE(mt7996_wa_mem_regions);
++			return &mt7996_wa_mem_regions[0];
++		}
++		*num = ARRAY_SIZE(mt7992_wm_mem_regions);
++		return &mt7992_wm_mem_regions[0];
+ 	default:
+ 		return NULL;
+ 	}
+@@ -115,7 +161,7 @@ struct mt7996_crash_data *mt7996_coredump_new(struct mt7996_dev *dev, u8 type)
+ 
+ 	lockdep_assert_held(&dev->dump_mutex);
+ 
+-	if (!coredump_memdump)
++	if (!coredump_memdump || !crash_data->supported)
+ 		return NULL;
+ 
+ 	guid_gen(&crash_data->guid);
+@@ -289,40 +335,46 @@ int mt7996_coredump_register(struct mt7996_dev *dev)
+ 	for (i = 0; i < MT7996_COREDUMP_MAX; i++) {
+ 		crash_data = vzalloc(sizeof(*dev->coredump.crash_data[i]));
+ 		if (!crash_data)
+-			return -ENOMEM;
++			goto nomem;
+ 
+ 		dev->coredump.crash_data[i] = crash_data;
++		crash_data->supported = false;
+ 
+ 		if (coredump_memdump) {
+ 			crash_data->memdump_buf_len = mt7996_coredump_get_mem_size(dev, i);
+ 			if (!crash_data->memdump_buf_len)
+ 				/* no memory content */
+-				return 0;
++				continue;
+ 
+ 			crash_data->memdump_buf = vzalloc(crash_data->memdump_buf_len);
+-			if (!crash_data->memdump_buf) {
+-				vfree(crash_data);
+-				return -ENOMEM;
+-			}
++			if (!crash_data->memdump_buf)
++				goto nomem;
++
++			crash_data->supported = true;
+ 		}
+ 	}
+ 
+ 	return 0;
++nomem:
++	mt7996_coredump_unregister(dev);
++	return -ENOMEM;
+ }
+ 
+ void mt7996_coredump_unregister(struct mt7996_dev *dev)
+ {
+ 	int i;
++	struct mt7996_crash_data *crash_data;
+ 
+ 	for (i = 0; i < MT7996_COREDUMP_MAX; i++) {
+-		if (dev->coredump.crash_data[i]->memdump_buf) {
+-			vfree(dev->coredump.crash_data[i]->memdump_buf);
+-			dev->coredump.crash_data[i]->memdump_buf = NULL;
+-			dev->coredump.crash_data[i]->memdump_buf_len = 0;
+-		}
++		crash_data = dev->coredump.crash_data[i];
++
++		if (!crash_data)
++			continue;
++
++		if (crash_data->memdump_buf)
++			vfree(crash_data->memdump_buf);
+ 
+-		vfree(dev->coredump.crash_data[i]);
+-		dev->coredump.crash_data[i] = NULL;
++		vfree(crash_data);
+ 	}
+ }
+ 
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index c6cd01c7d..7c806f110 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -230,6 +230,7 @@ struct mt7996_vif {
+ struct mt7996_crash_data {
+ 	guid_t guid;
+ 	struct timespec64 timestamp;
++	bool supported;
+ 
+ 	u8 *memdump_buf;
+ 	size_t memdump_buf_len;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0043-mtk-wifi-mt76-mt7996-add-support-for-runtime-set-in-.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0043-mtk-wifi-mt76-mt7996-add-support-for-runtime-set-in-.patch
new file mode 100644
index 0000000..2e85dfa
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0043-mtk-wifi-mt76-mt7996-add-support-for-runtime-set-in-.patch
@@ -0,0 +1,44 @@
+From fa88da805dff9e8a2f4198955ed5b76a55e099ce Mon Sep 17 00:00:00 2001
+From: MeiChia Chiu <meichia.chiu@mediatek.com>
+Date: Tue, 6 Jun 2023 16:57:10 +0800
+Subject: [PATCH 043/120] mtk: wifi: mt76: mt7996: add support for runtime set
+ in-band discovery
+
+with this patch, AP can runtime set inband discovery via hostapd_cli
+
+Usage:
+Enable FILS: hostapd_cli -i [interface] inband_discovery 2 20
+Enable UBPR: hostapd_cli -i [interface] inband_discovery 1 20
+Disable inband discovery: hostapd_cli -i [interface] inband_discovery 0 0
+
+Signed-off-by: MeiChia Chiu <MeiChia.Chiu@mediatek.com>
+---
+ mt7996/mcu.c | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index bfa9709d3..ff613531c 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -2607,8 +2607,7 @@ int mt7996_mcu_beacon_inband_discov(struct mt7996_dev *dev,
+ 	if (IS_ERR(rskb))
+ 		return PTR_ERR(rskb);
+ 
+-	if (changed & BSS_CHANGED_FILS_DISCOVERY &&
+-	    vif->bss_conf.fils_discovery.max_interval) {
++	if (changed & BSS_CHANGED_FILS_DISCOVERY) {
+ 		interval = vif->bss_conf.fils_discovery.max_interval;
+ 		skb = ieee80211_get_fils_discovery_tmpl(hw, vif);
+ 	} else if (changed & BSS_CHANGED_UNSOL_BCAST_PROBE_RESP &&
+@@ -2643,7 +2642,7 @@ int mt7996_mcu_beacon_inband_discov(struct mt7996_dev *dev,
+ 	discov->tx_type = !!(changed & BSS_CHANGED_FILS_DISCOVERY);
+ 	discov->tx_interval = interval;
+ 	discov->prob_rsp_len = cpu_to_le16(MT_TXD_SIZE + skb->len);
+-	discov->enable = true;
++	discov->enable = !!(interval);
+ 	discov->wcid = cpu_to_le16(MT7996_WTBL_RESERVED);
+ 
+ 	buf = (u8 *)tlv + sizeof(*discov);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0044-mtk-wifi-mt76-mt7996-add-vendor-subcmd-EDCCA-ctrl-en.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0044-mtk-wifi-mt76-mt7996-add-vendor-subcmd-EDCCA-ctrl-en.patch
new file mode 100644
index 0000000..275e342
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0044-mtk-wifi-mt76-mt7996-add-vendor-subcmd-EDCCA-ctrl-en.patch
@@ -0,0 +1,379 @@
+From 0ef47d9a5fdaf832da7a4d4f4f3cf8411ee25ae4 Mon Sep 17 00:00:00 2001
+From: mtk27745 <rex.lu@mediatek.com>
+Date: Thu, 8 Jun 2023 20:21:04 +0800
+Subject: [PATCH 044/120] mtk: wifi: mt76: mt7996: add vendor subcmd EDCCA ctrl
+ enable
+
+---
+ mt7996/mcu.h     |   2 +
+ mt7996/mt7996.h  |  11 ++++
+ mt7996/mtk_mcu.c |  87 +++++++++++++++++++++++++++++++
+ mt7996/mtk_mcu.h |  15 ++++++
+ mt7996/vendor.c  | 132 +++++++++++++++++++++++++++++++++++++++++++++++
+ mt7996/vendor.h  |  33 ++++++++++++
+ 6 files changed, 280 insertions(+)
+
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 35f757dc2..34fdfb261 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -845,6 +845,8 @@ mt7996_get_power_bound(struct mt7996_phy *phy, s8 txpower)
+ 
+ enum {
+ 	UNI_BAND_CONFIG_RADIO_ENABLE,
++	UNI_BAND_CONFIG_EDCCA_ENABLE = 0x05,
++	UNI_BAND_CONFIG_EDCCA_THRESHOLD = 0x06,
+ 	UNI_BAND_CONFIG_RTS_THRESHOLD = 0x08,
+ };
+ 
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 7c806f110..7770d9556 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -787,6 +787,17 @@ int mt7996_vendor_amnt_sta_remove(struct mt7996_phy *phy,
+ 				  struct ieee80211_sta *sta);
+ #endif
+ 
++int mt7996_mcu_edcca_enable(struct mt7996_phy *phy, bool enable);
++int mt7996_mcu_edcca_threshold_ctrl(struct mt7996_phy *phy, u8 *value, bool set);
++
++enum edcca_bw_id {
++	EDCCA_BW_20 = 0,
++	EDCCA_BW_40,
++	EDCCA_BW_80,
++	EDCCA_BW_160,
++	EDCCA_MAX_BW_NUM,
++};
++
+ #ifdef CONFIG_MTK_DEBUG
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir);
+ int mt7996_mcu_muru_dbg_info(struct mt7996_dev *dev, u16 item, u8 val);
+diff --git a/mt7996/mtk_mcu.c b/mt7996/mtk_mcu.c
+index e56ddd8ff..5c54d02c4 100644
+--- a/mt7996/mtk_mcu.c
++++ b/mt7996/mtk_mcu.c
+@@ -59,4 +59,91 @@ int mt7996_mcu_muru_dbg_info(struct mt7996_dev *dev, u16 item, u8 val)
+ 	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(MURU), &req,
+ 				 sizeof(req), true);
+ }
++
++int mt7996_mcu_edcca_enable(struct mt7996_phy *phy, bool enable)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct cfg80211_chan_def *chandef = &phy->mt76->chandef;
++	enum nl80211_band band = chandef->chan->band;
++	struct {
++		u8 band_idx;
++		u8 _rsv[3];
++
++		__le16 tag;
++		__le16 len;
++		u8 enable;
++		u8 std;
++		u8 _rsv2[2];
++	} __packed req = {
++		.band_idx = phy->mt76->band_idx,
++		.tag = cpu_to_le16(UNI_BAND_CONFIG_EDCCA_ENABLE),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.enable = enable,
++		.std = EDCCA_DEFAULT,
++	};
++
++	switch (dev->mt76.region) {
++	case NL80211_DFS_JP:
++		req.std = EDCCA_JAPAN;
++		break;
++	case NL80211_DFS_FCC:
++		if (band == NL80211_BAND_6GHZ)
++			req.std = EDCCA_FCC;
++		break;
++	case NL80211_DFS_ETSI:
++		if (band == NL80211_BAND_6GHZ)
++			req.std = EDCCA_ETSI;
++		break;
++	default:
++		break;
++	}
++
++	return mt76_mcu_send_msg(&phy->dev->mt76, MCU_WM_UNI_CMD(BAND_CONFIG),
++				 &req, sizeof(req), true);
++}
++
++int mt7996_mcu_edcca_threshold_ctrl(struct mt7996_phy *phy, u8 *value, bool set)
++{
++	struct {
++		u8 band_idx;
++		u8 _rsv[3];
++
++		__le16 tag;
++		__le16 len;
++		u8 threshold[4];
++		bool init;
++	} __packed *res, req = {
++		.band_idx = phy->mt76->band_idx,
++		.tag = cpu_to_le16(UNI_BAND_CONFIG_EDCCA_THRESHOLD),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.init = false,
++	};
++	struct sk_buff *skb;
++	int ret;
++	int i;
++
++	for (i = 0; i < EDCCA_MAX_BW_NUM; i++)
++		req.threshold[i] = value[i];
++
++	if (set)
++		return mt76_mcu_send_msg(&phy->dev->mt76, MCU_WM_UNI_CMD(BAND_CONFIG),
++					 &req, sizeof(req), true);
++
++	ret = mt76_mcu_send_and_get_msg(&phy->dev->mt76,
++					MCU_WM_UNI_CMD_QUERY(BAND_CONFIG),
++					&req, sizeof(req), true, &skb);
++
++	if (ret)
++		return ret;
++
++	res = (void *)skb->data;
++
++	for (i = 0; i < EDCCA_MAX_BW_NUM; i++)
++		value[i] = res->threshold[i];
++
++	dev_kfree_skb(skb);
++
++	return 0;
++}
++
+ #endif
+diff --git a/mt7996/mtk_mcu.h b/mt7996/mtk_mcu.h
+index c30418cae..36a58ad63 100644
+--- a/mt7996/mtk_mcu.h
++++ b/mt7996/mtk_mcu.h
+@@ -106,6 +106,21 @@ enum txpower_event {
+ 	UNI_TXPOWER_PHY_RATE_INFO = 5,
+ };
+ 
++enum {
++	EDCCA_CTRL_SET_EN = 0,
++	EDCCA_CTRL_SET_THRES,
++	EDCCA_CTRL_GET_EN,
++	EDCCA_CTRL_GET_THRES,
++	EDCCA_CTRL_NUM,
++};
++
++enum {
++	EDCCA_DEFAULT = 0,
++	EDCCA_FCC = 1,
++	EDCCA_ETSI = 2,
++	EDCCA_JAPAN = 3
++};
++
+ #endif
+ 
+ #endif
+diff --git a/mt7996/vendor.c b/mt7996/vendor.c
+index 391015777..9f333d0ee 100644
+--- a/mt7996/vendor.c
++++ b/mt7996/vendor.c
+@@ -40,6 +40,26 @@ bss_color_ctrl_policy[NUM_MTK_VENDOR_ATTRS_BSS_COLOR_CTRL] = {
+ 	[MTK_VENDOR_ATTR_AVAL_BSS_COLOR_BMP] = { .type = NLA_U64 },
+ };
+ 
++static const struct nla_policy
++edcca_ctrl_policy[NUM_MTK_VENDOR_ATTRS_EDCCA_CTRL] = {
++	[MTK_VENDOR_ATTR_EDCCA_CTRL_MODE] = { .type = NLA_U8 },
++	[MTK_VENDOR_ATTR_EDCCA_CTRL_PRI20_VAL] = { .type = NLA_U8 },
++	[MTK_VENDOR_ATTR_EDCCA_CTRL_SEC20_VAL] = { .type = NLA_U8 },
++	[MTK_VENDOR_ATTR_EDCCA_CTRL_SEC40_VAL] = { .type = NLA_U8 },
++	[MTK_VENDOR_ATTR_EDCCA_CTRL_SEC80_VAL] = { .type = NLA_U8 },
++	[MTK_VENDOR_ATTR_EDCCA_CTRL_COMPENSATE] = { .type = NLA_S8 },
++	[MTK_VENDOR_ATTR_EDCCA_CTRL_SEC160_VAL] = { .type = NLA_U8 },
++};
++
++static const struct nla_policy
++edcca_dump_policy[NUM_MTK_VENDOR_ATTRS_EDCCA_DUMP] = {
++	[MTK_VENDOR_ATTR_EDCCA_DUMP_MODE] = { .type = NLA_U8 },
++	[MTK_VENDOR_ATTR_EDCCA_DUMP_PRI20_VAL] = { .type = NLA_U8 },
++	[MTK_VENDOR_ATTR_EDCCA_DUMP_SEC40_VAL] = { .type = NLA_U8 },
++	[MTK_VENDOR_ATTR_EDCCA_DUMP_SEC80_VAL] = { .type = NLA_U8 },
++	[MTK_VENDOR_ATTR_EDCCA_DUMP_SEC160_VAL] = { .type = NLA_U8 },
++};
++
+ struct mt7996_amnt_data {
+ 	u8 idx;
+ 	u8 addr[ETH_ALEN];
+@@ -436,6 +456,106 @@ mt7996_vendor_bss_color_ctrl_dump(struct wiphy *wiphy, struct wireless_dev *wdev
+ 	return len;
+ }
+ 
++static int mt7996_vendor_edcca_ctrl(struct wiphy *wiphy, struct wireless_dev *wdev,
++				    const void *data, int data_len)
++{
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_EDCCA_CTRL];
++	int err;
++	u8 edcca_mode;
++	u8 edcca_value[EDCCA_MAX_BW_NUM];
++
++	err = nla_parse(tb, MTK_VENDOR_ATTR_EDCCA_CTRL_MAX, data, data_len,
++			edcca_ctrl_policy, NULL);
++	if (err)
++		return err;
++
++	if (!tb[MTK_VENDOR_ATTR_EDCCA_CTRL_MODE])
++		return -EINVAL;
++
++	edcca_mode = nla_get_u8(tb[MTK_VENDOR_ATTR_EDCCA_CTRL_MODE]);
++	if (edcca_mode == EDCCA_CTRL_SET_EN) {
++		if (!tb[MTK_VENDOR_ATTR_EDCCA_CTRL_PRI20_VAL])
++			return -EINVAL;
++
++		edcca_value[0] =
++			nla_get_u8(tb[MTK_VENDOR_ATTR_EDCCA_CTRL_PRI20_VAL]);
++
++		err = mt7996_mcu_edcca_enable(phy, !!edcca_value[0]);
++		if (err)
++			return err;
++	} else if (edcca_mode == EDCCA_CTRL_SET_THRES) {
++		if (!tb[MTK_VENDOR_ATTR_EDCCA_CTRL_PRI20_VAL] ||
++		    !tb[MTK_VENDOR_ATTR_EDCCA_CTRL_SEC40_VAL] ||
++		    !tb[MTK_VENDOR_ATTR_EDCCA_CTRL_SEC80_VAL] ||
++		    !tb[MTK_VENDOR_ATTR_EDCCA_CTRL_SEC160_VAL]) {
++			return -EINVAL;
++		}
++		edcca_value[EDCCA_BW_20] =
++			nla_get_u8(tb[MTK_VENDOR_ATTR_EDCCA_CTRL_PRI20_VAL]);
++		edcca_value[EDCCA_BW_40] =
++			nla_get_u8(tb[MTK_VENDOR_ATTR_EDCCA_CTRL_SEC40_VAL]);
++		edcca_value[EDCCA_BW_80] =
++			nla_get_u8(tb[MTK_VENDOR_ATTR_EDCCA_CTRL_SEC80_VAL]);
++		edcca_value[EDCCA_BW_160] =
++			nla_get_u8(tb[MTK_VENDOR_ATTR_EDCCA_CTRL_SEC160_VAL]);
++
++		err = mt7996_mcu_edcca_threshold_ctrl(phy, edcca_value, true);
++
++		if (err)
++			return err;
++	} else {
++		return -EINVAL;
++	}
++
++	return 0;
++}
++
++
++static int
++mt7996_vendor_edcca_ctrl_dump(struct wiphy *wiphy, struct wireless_dev *wdev,
++			     struct sk_buff *skb, const void *data, int data_len,
++			     unsigned long *storage)
++{
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_EDCCA_CTRL];
++	int err;
++	u8 edcca_mode;
++	u8 value[EDCCA_MAX_BW_NUM];
++
++	if (*storage == 1)
++		return -ENOENT;
++	*storage = 1;
++
++	err = nla_parse(tb, MTK_VENDOR_ATTR_EDCCA_CTRL_MAX, data, data_len,
++			edcca_ctrl_policy, NULL);
++	if (err)
++		return err;
++
++	if (!tb[MTK_VENDOR_ATTR_EDCCA_CTRL_MODE])
++		return -EINVAL;
++
++	edcca_mode = nla_get_u8(tb[MTK_VENDOR_ATTR_EDCCA_CTRL_MODE]);
++
++	if (edcca_mode != EDCCA_CTRL_GET_THRES)
++		return -EINVAL;
++
++	err = mt7996_mcu_edcca_threshold_ctrl(phy, value, false);
++
++	if (err)
++		return err;
++
++	if (nla_put_u8(skb, MTK_VENDOR_ATTR_EDCCA_DUMP_PRI20_VAL, value[EDCCA_BW_20]) ||
++	    nla_put_u8(skb, MTK_VENDOR_ATTR_EDCCA_DUMP_SEC40_VAL, value[EDCCA_BW_40]) ||
++	    nla_put_u8(skb, MTK_VENDOR_ATTR_EDCCA_DUMP_SEC80_VAL, value[EDCCA_BW_80]) ||
++	    nla_put_u8(skb, MTK_VENDOR_ATTR_EDCCA_DUMP_SEC160_VAL, value[EDCCA_BW_160]))
++		return -ENOMEM;
++
++	return EDCCA_MAX_BW_NUM;
++}
++
+ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 	{
+ 		.info = {
+@@ -472,6 +592,18 @@ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 		.policy = bss_color_ctrl_policy,
+ 		.maxattr = MTK_VENDOR_ATTR_BSS_COLOR_CTRL_MAX,
+ 	},
++	{
++		.info = {
++			.vendor_id = MTK_NL80211_VENDOR_ID,
++			.subcmd = MTK_NL80211_VENDOR_SUBCMD_EDCCA_CTRL,
++		},
++		.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
++			 WIPHY_VENDOR_CMD_NEED_RUNNING,
++		.doit = mt7996_vendor_edcca_ctrl,
++		.dumpit = mt7996_vendor_edcca_ctrl_dump,
++		.policy = edcca_ctrl_policy,
++		.maxattr = MTK_VENDOR_ATTR_EDCCA_CTRL_MAX,
++	},
+ };
+ 
+ void mt7996_vendor_register(struct mt7996_phy *phy)
+diff --git a/mt7996/vendor.h b/mt7996/vendor.h
+index eec9e74a2..4465bc9df 100644
+--- a/mt7996/vendor.h
++++ b/mt7996/vendor.h
+@@ -6,9 +6,42 @@
+ enum mtk_nl80211_vendor_subcmds {
+ 	MTK_NL80211_VENDOR_SUBCMD_AMNT_CTRL = 0xae,
+ 	MTK_NL80211_VENDOR_SUBCMD_MU_CTRL = 0xc5,
++	MTK_NL80211_VENDOR_SUBCMD_EDCCA_CTRL = 0xc7,
+ 	MTK_NL80211_VENDOR_SUBCMD_BSS_COLOR_CTRL = 0xca,
+ };
+ 
++enum mtk_vendor_attr_edcca_ctrl {
++	MTK_VENDOR_ATTR_EDCCA_THRESHOLD_INVALID = 0,
++
++	MTK_VENDOR_ATTR_EDCCA_CTRL_MODE,
++	MTK_VENDOR_ATTR_EDCCA_CTRL_PRI20_VAL,
++	MTK_VENDOR_ATTR_EDCCA_CTRL_SEC20_VAL,
++	MTK_VENDOR_ATTR_EDCCA_CTRL_SEC40_VAL,
++	MTK_VENDOR_ATTR_EDCCA_CTRL_SEC80_VAL,
++	MTK_VENDOR_ATTR_EDCCA_CTRL_COMPENSATE,
++	MTK_VENDOR_ATTR_EDCCA_CTRL_SEC160_VAL,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_EDCCA_CTRL,
++	MTK_VENDOR_ATTR_EDCCA_CTRL_MAX =
++		NUM_MTK_VENDOR_ATTRS_EDCCA_CTRL - 1
++};
++
++enum mtk_vendor_attr_edcca_dump {
++	MTK_VENDOR_ATTR_EDCCA_DUMP_UNSPEC = 0,
++
++	MTK_VENDOR_ATTR_EDCCA_DUMP_MODE,
++	MTK_VENDOR_ATTR_EDCCA_DUMP_PRI20_VAL,
++	MTK_VENDOR_ATTR_EDCCA_DUMP_SEC40_VAL,
++	MTK_VENDOR_ATTR_EDCCA_DUMP_SEC80_VAL,
++	MTK_VENDOR_ATTR_EDCCA_DUMP_SEC160_VAL,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_EDCCA_DUMP,
++	MTK_VENDOR_ATTR_EDCCA_DUMP_MAX =
++		NUM_MTK_VENDOR_ATTRS_EDCCA_DUMP - 1
++};
++
+ enum mtk_vendor_attr_mu_ctrl {
+ 	MTK_VENDOR_ATTR_MU_CTRL_UNSPEC,
+ 
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0045-mtk-wifi-mt76-mt7996-add-support-spatial-reuse-debug.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0045-mtk-wifi-mt76-mt7996-add-support-spatial-reuse-debug.patch
new file mode 100644
index 0000000..95c7bb1
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0045-mtk-wifi-mt76-mt7996-add-support-spatial-reuse-debug.patch
@@ -0,0 +1,398 @@
+From c032d811b4544c28c8c02fd69f39a58acb16bc4d Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Mon, 10 Jul 2023 11:47:29 +0800
+Subject: [PATCH 045/120] mtk: wifi: mt76: mt7996: add support spatial reuse
+ debug commands
+
+This commit adds the following debug commands in debugfs:
+1. sr_enable: enable/disable spatial reuse feature. Default is on.
+2. sr_enhanced_enable: enable/disable enhanced spatial reuse feature.
+Default is on. This feature is mtk proprietary feature.
+3. sr_stats: Check the Spatial reuse tx statistics.
+4. sr_scene_cond: Check the result of mtk scene detection algorithm. Mtk
+scene detection algorithm in firmware may decide whether current
+environment can SR Tx or not.
+
+To learn more details of these commands, please check:
+https://wiki.mediatek.inc/display/APKB/mt76+Phy+feature+debug+Cheetsheet#mt76PhyfeaturedebugCheetsheet-SpatialReuse
+
+Change-Id: I5bf83013ea2788dfc93caaaef08486a4f97a3649
+---
+ mt76_connac_mcu.h    |   1 +
+ mt7996/main.c        |   6 +++
+ mt7996/mcu.c         |   8 ++++
+ mt7996/mt7996.h      |   6 +++
+ mt7996/mtk_debugfs.c |  82 ++++++++++++++++++++++++++++++++
+ mt7996/mtk_mcu.c     | 111 +++++++++++++++++++++++++++++++++++++++++++
+ mt7996/mtk_mcu.h     |  56 ++++++++++++++++++++++
+ 7 files changed, 270 insertions(+)
+
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 5b2eb5751..f4557bd6e 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1040,6 +1040,7 @@ enum {
+ 	MCU_UNI_EVENT_BSS_BEACON_LOSS = 0x0c,
+ 	MCU_UNI_EVENT_SCAN_DONE = 0x0e,
+ 	MCU_UNI_EVENT_RDD_REPORT = 0x11,
++	MCU_UNI_EVENT_SR = 0x25,
+ 	MCU_UNI_EVENT_ROC = 0x27,
+ 	MCU_UNI_EVENT_TX_DONE = 0x2d,
+ 	MCU_UNI_EVENT_BF = 0x33,
+diff --git a/mt7996/main.c b/mt7996/main.c
+index ee754d44e..1cf677614 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -6,6 +6,9 @@
+ #include "mt7996.h"
+ #include "mcu.h"
+ #include "mac.h"
++#ifdef CONFIG_MTK_DEBUG
++#include "mtk_mcu.h"
++#endif
+ 
+ static bool mt7996_dev_running(struct mt7996_dev *dev)
+ {
+@@ -86,6 +89,9 @@ int mt7996_run(struct ieee80211_hw *hw)
+ 		goto out;
+ 
+ #ifdef CONFIG_MTK_DEBUG
++	phy->sr_enable = true;
++	phy->enhanced_sr_enable = true;
++
+ 	ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_SKU_POWER_LIMIT_CTRL,
+ 					   !dev->dbg.sku_disable);
+ #else
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index ff613531c..807ea5b08 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -715,6 +715,14 @@ mt7996_mcu_uni_rx_unsolicited_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 	case MCU_UNI_EVENT_WED_RRO:
+ 		mt7996_mcu_wed_rro_event(dev, skb);
+ 		break;
++#ifdef CONFIG_MTK_DEBUG
++	case MCU_UNI_EVENT_SR:
++		mt7996_mcu_rx_sr_event(dev, skb);
++		break;
++#endif
++	case MCU_UNI_EVENT_THERMAL:
++		mt7996_mcu_rx_thermal_notify(dev, skb);
++		break;
+ #ifdef CONFIG_NL80211_TESTMODE
+ 	case MCU_UNI_EVENT_TESTMODE_CTRL:
+ 		mt7996_tm_rf_test_event(dev, skb);
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 7770d9556..84de8e531 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -352,6 +352,10 @@ struct mt7996_phy {
+ 	spinlock_t amnt_lock;
+ 	struct mt7996_air_monitor_ctrl amnt_ctrl;
+ #endif
++#ifdef CONFIG_MTK_DEBUG
++	bool sr_enable:1;
++	bool enhanced_sr_enable:1;
++#endif
+ };
+ 
+ struct mt7996_dev {
+@@ -801,6 +805,8 @@ enum edcca_bw_id {
+ #ifdef CONFIG_MTK_DEBUG
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir);
+ 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);
+ #endif
+ 
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index 8db8b6805..17edd764d 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -2777,6 +2777,83 @@ static int mt7996_show_eeprom_mode(struct seq_file *s, void *data)
+ 	return 0;
+ }
+ 
++static int
++mt7996_sr_enable_get(void *data, u64 *val)
++{
++	struct mt7996_phy *phy = data;
++
++	*val = phy->sr_enable;
++
++	return 0;
++}
++
++static int
++mt7996_sr_enable_set(void *data, u64 val)
++{
++	struct mt7996_phy *phy = data;
++	int ret;
++
++	if (!!val == phy->sr_enable)
++		return 0;
++
++	ret = mt7996_mcu_set_sr_enable(phy, UNI_CMD_SR_CFG_SR_ENABLE, val, true);
++	if (ret)
++		return ret;
++
++	return mt7996_mcu_set_sr_enable(phy, UNI_CMD_SR_CFG_SR_ENABLE, 0, false);
++}
++DEFINE_DEBUGFS_ATTRIBUTE(fops_sr_enable, mt7996_sr_enable_get,
++			 mt7996_sr_enable_set, "%lld\n");
++static int
++mt7996_sr_enhanced_enable_get(void *data, u64 *val)
++{
++	struct mt7996_phy *phy = data;
++
++	*val = phy->enhanced_sr_enable;
++
++	return 0;
++}
++
++static int
++mt7996_sr_enhanced_enable_set(void *data, u64 val)
++{
++	struct mt7996_phy *phy = data;
++	int ret;
++
++	if (!!val == phy->enhanced_sr_enable)
++		return 0;
++
++	ret = mt7996_mcu_set_sr_enable(phy, UNI_CMD_SR_HW_ENHANCE_SR_ENABLE, val, true);
++	if (ret)
++		return ret;
++
++	return mt7996_mcu_set_sr_enable(phy, UNI_CMD_SR_HW_ENHANCE_SR_ENABLE, 0, false);
++}
++DEFINE_DEBUGFS_ATTRIBUTE(fops_sr_enhanced_enable, mt7996_sr_enhanced_enable_get,
++			 mt7996_sr_enhanced_enable_set, "%lld\n");
++
++static int
++mt7996_sr_stats_show(struct seq_file *file, void *data)
++{
++	struct mt7996_phy *phy = file->private;
++
++	mt7996_mcu_set_sr_enable(phy, UNI_CMD_SR_HW_IND, 0, false);
++
++	return 0;
++}
++DEFINE_SHOW_ATTRIBUTE(mt7996_sr_stats);
++
++static int
++mt7996_sr_scene_cond_show(struct seq_file *file, void *data)
++{
++	struct mt7996_phy *phy = file->private;
++
++	mt7996_mcu_set_sr_enable(phy, UNI_CMD_SR_SW_SD, 0, false);
++
++	return 0;
++}
++DEFINE_SHOW_ATTRIBUTE(mt7996_sr_scene_cond);
++
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -2856,6 +2933,11 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ 	debugfs_create_u8("sku_disable", 0600, dir, &dev->dbg.sku_disable);
+ 	debugfs_create_file("scs_enable", 0200, dir, phy, &fops_scs_enable);
+ 
++	debugfs_create_file("sr_enable", 0600, dir, phy, &fops_sr_enable);
++	debugfs_create_file("sr_enhanced_enable", 0600, dir, phy, &fops_sr_enhanced_enable);
++	debugfs_create_file("sr_stats", 0400, dir, phy, &mt7996_sr_stats_fops);
++	debugfs_create_file("sr_scene_cond", 0400, dir, phy, &mt7996_sr_scene_cond_fops);
++
+ 	return 0;
+ }
+ 
+diff --git a/mt7996/mtk_mcu.c b/mt7996/mtk_mcu.c
+index 5c54d02c4..dbdf8d809 100644
+--- a/mt7996/mtk_mcu.c
++++ b/mt7996/mtk_mcu.c
+@@ -146,4 +146,115 @@ int mt7996_mcu_edcca_threshold_ctrl(struct mt7996_phy *phy, u8 *value, bool set)
+ 	return 0;
+ }
+ 
++int mt7996_mcu_set_sr_enable(struct mt7996_phy *phy, u8 action, u64 val, bool set)
++{
++	struct {
++		u8 band_idx;
++		u8 _rsv[3];
++
++		__le16 tag;
++		__le16 len;
++
++		__le32 val;
++
++	} __packed req = {
++		.band_idx = phy->mt76->band_idx,
++
++		.tag = cpu_to_le16(action),
++		.len = cpu_to_le16(sizeof(req) - 4),
++
++		.val = cpu_to_le32((u32) val),
++	};
++
++	if (set)
++		return mt76_mcu_send_msg(&phy->dev->mt76, MCU_WM_UNI_CMD(SR), &req,
++					 sizeof(req), false);
++	else
++		return mt76_mcu_send_msg(&phy->dev->mt76, MCU_WM_UNI_CMD_QUERY(SR), &req,
++					 sizeof(req), false);
++}
++
++void mt7996_mcu_rx_sr_swsd(struct mt7996_dev *dev, struct sk_buff *skb)
++{
++#define SR_SCENE_DETECTION_TIMER_PERIOD_MS 500
++	struct mt7996_mcu_sr_swsd_event *event;
++	static const char * const rules[] = {"1 - NO CONNECTED", "2 - NO CONGESTION",
++					     "3 - NO INTERFERENCE", "4 - SR ON"};
++	u8 idx;
++
++	event = (struct mt7996_mcu_sr_swsd_event *)skb->data;
++	idx = event->basic.band_idx;
++
++	dev_info(dev->mt76.dev, "Band index = %u\n", le16_to_cpu(event->basic.band_idx));
++	dev_info(dev->mt76.dev, "Hit Rule = %s\n", rules[event->tlv[idx].rule]);
++	dev_info(dev->mt76.dev, "Timer Period = %d(us)\n"
++		 "Congestion Ratio  = %d.%1d%%\n",
++		 SR_SCENE_DETECTION_TIMER_PERIOD_MS * 1000,
++		 le32_to_cpu(event->tlv[idx].total_airtime_ratio) / 10,
++		 le32_to_cpu(event->tlv[idx].total_airtime_ratio) % 10);
++	dev_info(dev->mt76.dev,
++		 "Total Airtime = %d(us)\n"
++		 "ChBusy = %d\n"
++		 "SrTx = %d\n"
++		 "OBSS = %d\n"
++		 "MyTx = %d\n"
++		 "MyRx = %d\n"
++		 "Interference Ratio = %d.%1d%%\n",
++		 le32_to_cpu(event->tlv[idx].total_airtime),
++		 le32_to_cpu(event->tlv[idx].channel_busy_time),
++		 le32_to_cpu(event->tlv[idx].sr_tx_airtime),
++		 le32_to_cpu(event->tlv[idx].obss_airtime),
++		 le32_to_cpu(event->tlv[idx].my_tx_airtime),
++		 le32_to_cpu(event->tlv[idx].my_rx_airtime),
++		 le32_to_cpu(event->tlv[idx].obss_airtime_ratio) / 10,
++		 le32_to_cpu(event->tlv[idx].obss_airtime_ratio) % 10);
++}
++
++void mt7996_mcu_rx_sr_hw_indicator(struct mt7996_dev *dev, struct sk_buff *skb)
++{
++	struct mt7996_mcu_sr_hw_ind_event *event;
++
++	event = (struct mt7996_mcu_sr_hw_ind_event *)skb->data;
++
++	dev_info(dev->mt76.dev, "Inter PPDU Count = %u\n",
++		 le16_to_cpu(event->inter_bss_ppdu_cnt));
++	dev_info(dev->mt76.dev, "SR Valid Count = %u\n",
++		 le16_to_cpu(event->non_srg_valid_cnt));
++	dev_info(dev->mt76.dev, "SR Tx Count = %u\n",
++		 le32_to_cpu(event->sr_ampdu_mpdu_cnt));
++	dev_info(dev->mt76.dev, "SR Tx Acked Count = %u\n",
++		 le32_to_cpu(event->sr_ampdu_mpdu_acked_cnt));
++}
++
++void mt7996_mcu_rx_sr_event(struct mt7996_dev *dev, struct sk_buff *skb)
++{
++	struct mt76_phy *mphy = &dev->mt76.phy;
++	struct mt7996_phy *phy;
++	struct mt7996_mcu_sr_common_event *event;
++
++	event = (struct mt7996_mcu_sr_common_event *)skb->data;
++	mphy = dev->mt76.phys[event->basic.band_idx];
++	if (!mphy)
++		return;
++
++	phy = (struct mt7996_phy *)mphy->priv;
++
++	switch (le16_to_cpu(event->basic.tag)) {
++	case UNI_EVENT_SR_CFG_SR_ENABLE:
++		phy->sr_enable = le32_to_cpu(event->value) ? true : false;
++		break;
++	case UNI_EVENT_SR_HW_ESR_ENABLE:
++		phy->enhanced_sr_enable = le32_to_cpu(event->value) ? true : false;
++		break;
++	case UNI_EVENT_SR_SW_SD:
++		mt7996_mcu_rx_sr_swsd(dev, skb);
++		break;
++	case UNI_EVENT_SR_HW_IND:
++		mt7996_mcu_rx_sr_hw_indicator(dev, skb);
++		break;
++	default:
++		dev_info(dev->mt76.dev, "Unknown SR event tag %d\n",
++			 le16_to_cpu(event->basic.tag));
++	}
++}
+ #endif
+diff --git a/mt7996/mtk_mcu.h b/mt7996/mtk_mcu.h
+index 36a58ad63..098e63aef 100644
+--- a/mt7996/mtk_mcu.h
++++ b/mt7996/mtk_mcu.h
+@@ -121,6 +121,62 @@ enum {
+ 	EDCCA_JAPAN = 3
+ };
+ 
++enum {
++	UNI_EVENT_SR_CFG_SR_ENABLE = 0x1,
++	UNI_EVENT_SR_SW_SD = 0x83,
++	UNI_EVENT_SR_HW_IND = 0xC9,
++	UNI_EVENT_SR_HW_ESR_ENABLE = 0xD8,
++};
++enum {
++	UNI_CMD_SR_CFG_SR_ENABLE = 0x1,
++	UNI_CMD_SR_SW_SD = 0x84,
++	UNI_CMD_SR_HW_IND = 0xCB,
++	UNI_CMD_SR_HW_ENHANCE_SR_ENABLE = 0xDA,
++};
++
++struct mt7996_mcu_sr_basic_event {
++	struct mt7996_mcu_rxd rxd;
++
++	u8 band_idx;
++	u8 _rsv[3];
++
++	__le16 tag;
++	__le16 len;
++};
++
++struct sr_sd_tlv {
++	u8 _rsv[16];
++	__le32 sr_tx_airtime;
++	__le32 obss_airtime;
++	__le32 my_tx_airtime;
++	__le32 my_rx_airtime;
++	__le32 channel_busy_time;
++	__le32 total_airtime;
++	__le32 total_airtime_ratio;
++	__le32 obss_airtime_ratio;
++	u8 rule;
++	u8 _rsv2[59];
++} __packed;
++
++struct mt7996_mcu_sr_swsd_event {
++	struct mt7996_mcu_sr_basic_event basic;
++	struct sr_sd_tlv tlv[3];
++} __packed;
++
++struct mt7996_mcu_sr_common_event {
++	struct mt7996_mcu_sr_basic_event basic;
++	__le32 value;
++};
++
++struct mt7996_mcu_sr_hw_ind_event {
++	struct mt7996_mcu_sr_basic_event basic;
++	__le16 non_srg_valid_cnt;
++	u8 _rsv[4];
++	__le16 inter_bss_ppdu_cnt;
++	u8 _rsv2[4];
++	__le32 sr_ampdu_mpdu_cnt;
++	__le32 sr_ampdu_mpdu_acked_cnt;
++};
+ #endif
+ 
+ #endif
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0046-mtk-wifi-mt76-mt7996-Establish-BA-in-VO-queue.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0046-mtk-wifi-mt76-mt7996-Establish-BA-in-VO-queue.patch
new file mode 100644
index 0000000..1bd736d
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0046-mtk-wifi-mt76-mt7996-Establish-BA-in-VO-queue.patch
@@ -0,0 +1,25 @@
+From ba30574f2e67339f5c454ad856fadd8a62aa8fe2 Mon Sep 17 00:00:00 2001
+From: MeiChia Chiu <meichia.chiu@mediatek.com>
+Date: Tue, 1 Aug 2023 16:02:28 +0800
+Subject: [PATCH 046/120] mtk: wifi: mt76: mt7996: Establish BA in VO queue
+
+---
+ mt7996/mac.c | 2 --
+ 1 file changed, 2 deletions(-)
+
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 1c1b3eb51..4e52aa1bf 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -1032,8 +1032,6 @@ mt7996_tx_check_aggr(struct ieee80211_sta *sta, struct sk_buff *skb)
+ 		return;
+ 
+ 	tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK;
+-	if (tid >= 6) /* skip VO queue */
+-		return;
+ 
+ 	if (is_8023) {
+ 		fc = IEEE80211_FTYPE_DATA |
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0047-mtk-wifi-mt76-mt7996-add-eagle-iFEM-HWITS-ZWDFS-SW-w.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0047-mtk-wifi-mt76-mt7996-add-eagle-iFEM-HWITS-ZWDFS-SW-w.patch
new file mode 100644
index 0000000..d0dfc64
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0047-mtk-wifi-mt76-mt7996-add-eagle-iFEM-HWITS-ZWDFS-SW-w.patch
@@ -0,0 +1,156 @@
+From 3325c8812c3b81d32f77a785c9d3456d9c2f5a9e Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Wed, 5 Jul 2023 10:00:17 +0800
+Subject: [PATCH 047/120] mtk: wifi: mt76: mt7996: add eagle iFEM HWITS ZWDFS
+ SW workaround
+
+Fix the case that control channel is not first chan during first
+interface setup.
+Refactor ifem adie logic (if/else to switch, use sku_type & fem_type)
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt7996/main.c   | 65 ++++++++++++++++++++++++++++++++++++++++++++++---
+ mt7996/mcu.c    |  6 +++--
+ mt7996/mt7996.h |  1 +
+ 3 files changed, 67 insertions(+), 5 deletions(-)
+
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 1cf677614..a75f06f74 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -1441,6 +1441,61 @@ mt7996_twt_teardown_request(struct ieee80211_hw *hw,
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
++static void
++mt7996_background_radar_handle_7975_ifem(struct ieee80211_hw *hw,
++					 struct cfg80211_chan_def *user_chandef,
++					 struct cfg80211_chan_def *fw_chandef)
++{
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++	struct cfg80211_chan_def *c = user_chandef;
++	struct ieee80211_channel *first_chan;
++	bool is_ifem_adie, expand = false;
++
++	switch (mt76_chip(&dev->mt76)) {
++	case 0x7990:
++		is_ifem_adie = dev->fem_type == MT7996_FEM_INT &&
++			       dev->chip_sku != MT7996_SKU_233;
++		break;
++	case 0x7992:
++		is_ifem_adie = dev->chip_sku == MT7992_SKU_44 &&
++			       dev->fem_type != MT7996_FEM_EXT;
++		break;
++	default:
++		return;
++	}
++
++	if (!user_chandef || !is_ifem_adie)
++		goto out;
++
++	if (user_chandef->width == NL80211_CHAN_WIDTH_160) {
++		first_chan = ieee80211_get_channel(hw->wiphy, user_chandef->center_freq1 - 70);
++		if (dev->bg_nxt_freq)
++			goto out;
++
++		if (first_chan->flags & IEEE80211_CHAN_RADAR)
++			dev->bg_nxt_freq = first_chan->center_freq;
++		else
++			c = fw_chandef;
++
++		c->chan = ieee80211_get_channel(hw->wiphy, first_chan->center_freq + 80);
++	} else {
++		if (!dev->bg_nxt_freq)
++			goto out;
++
++		c->chan = ieee80211_get_channel(hw->wiphy, dev->bg_nxt_freq);
++		dev->bg_nxt_freq = 0;
++		expand = true;
++	}
++	c->width = NL80211_CHAN_WIDTH_80;
++	c->center_freq1 = c->chan->center_freq + 30;
++
++	if (c == user_chandef)
++		cfg80211_background_radar_update_channel(hw->wiphy, c, expand);
++	return;
++out:
++	dev->bg_nxt_freq = 0;
++}
++
+ static int
+ mt7996_set_radar_background(struct ieee80211_hw *hw,
+ 			    struct cfg80211_chan_def *chandef)
+@@ -1449,6 +1504,7 @@ mt7996_set_radar_background(struct ieee80211_hw *hw,
+ 	struct mt7996_dev *dev = phy->dev;
+ 	int ret = -EINVAL;
+ 	bool running;
++	struct cfg80211_chan_def ifem_chandef = {};
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+@@ -1461,13 +1517,14 @@ mt7996_set_radar_background(struct ieee80211_hw *hw,
+ 		goto out;
+ 	}
+ 
++	mt7996_background_radar_handle_7975_ifem(hw, chandef, &ifem_chandef);
++
+ 	/* rdd2 already configured on a radar channel */
+ 	running = dev->rdd2_phy &&
+ 		  cfg80211_chandef_valid(&dev->rdd2_chandef) &&
+ 		  !!(dev->rdd2_chandef.chan->flags & IEEE80211_CHAN_RADAR);
+ 
+-	if (!chandef || running ||
+-	    !(chandef->chan->flags & IEEE80211_CHAN_RADAR)) {
++	if (!chandef || running) {
+ 		ret = mt7996_mcu_rdd_background_enable(phy, NULL);
+ 		if (ret)
+ 			goto out;
+@@ -1476,7 +1533,9 @@ mt7996_set_radar_background(struct ieee80211_hw *hw,
+ 			goto update_phy;
+ 	}
+ 
+-	ret = mt7996_mcu_rdd_background_enable(phy, chandef);
++	ret = mt7996_mcu_rdd_background_enable(phy,
++					       ifem_chandef.chan ?
++					       &ifem_chandef : chandef);
+ 	if (ret)
+ 		goto out;
+ 
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 807ea5b08..576fc048d 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -372,12 +372,14 @@ mt7996_mcu_rx_radar_detected(struct mt7996_dev *dev, struct sk_buff *skb)
+ 	if (!mphy)
+ 		return;
+ 
+-	if (r->band_idx == MT_RX_SEL2)
++	if (r->band_idx == MT_RX_SEL2) {
++		dev->bg_nxt_freq = 0;
+ 		cfg80211_background_radar_event(mphy->hw->wiphy,
+ 						&dev->rdd2_chandef,
+ 						GFP_ATOMIC);
+-	else
++	} else {
+ 		ieee80211_radar_detected(mphy->hw);
++	}
+ 	dev->hw_pattern++;
+ }
+ 
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 84de8e531..f8fbcd5c9 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -441,6 +441,7 @@ struct mt7996_dev {
+ 	bool testmode_enable;
+ 	bool bin_file_mode;
+ 	u8 eeprom_mode;
++	u32 bg_nxt_freq;
+ 
+ 	bool ibf;
+ 	u8 fw_debug_wm;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0048-mtk-wifi-mt76-mt7996-report-tx-and-rx-byte-to-tpt_le.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0048-mtk-wifi-mt76-mt7996-report-tx-and-rx-byte-to-tpt_le.patch
new file mode 100644
index 0000000..0b93756
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0048-mtk-wifi-mt76-mt7996-report-tx-and-rx-byte-to-tpt_le.patch
@@ -0,0 +1,47 @@
+From 433d5edafae636d56f3eded9d9bc3d181f00f2cc Mon Sep 17 00:00:00 2001
+From: Yi-Chia Hsieh <yi-chia.hsieh@mediatek.com>
+Date: Sat, 12 Aug 2023 04:17:22 +0800
+Subject: [PATCH 048/120] mtk: wifi: mt76: mt7996: report tx and rx byte to
+ tpt_led
+
+---
+ mt7996/mcu.c | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
+
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 576fc048d..175699c1e 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -525,6 +525,8 @@ mt7996_mcu_rx_all_sta_info_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 		u8 ac;
+ 		u16 wlan_idx;
+ 		struct mt76_wcid *wcid;
++		struct mt76_phy *mphy;
++		u32 tx_bytes, rx_bytes;
+ 
+ 		switch (le16_to_cpu(res->tag)) {
+ 		case UNI_ALL_STA_TXRX_RATE:
+@@ -544,11 +546,16 @@ mt7996_mcu_rx_all_sta_info_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 			if (!wcid)
+ 				break;
+ 
++			mphy = mt76_dev_phy(&dev->mt76, wcid->phy_idx);
+ 			for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+-				wcid->stats.tx_bytes +=
+-					le32_to_cpu(res->adm_stat[i].tx_bytes[ac]);
+-				wcid->stats.rx_bytes +=
+-					le32_to_cpu(res->adm_stat[i].rx_bytes[ac]);
++				tx_bytes = le32_to_cpu(res->adm_stat[i].tx_bytes[ac]);
++				rx_bytes = le32_to_cpu(res->adm_stat[i].rx_bytes[ac]);
++
++				wcid->stats.tx_bytes += tx_bytes;
++				wcid->stats.rx_bytes += rx_bytes;
++
++				ieee80211_tpt_led_trig_tx(mphy->hw, tx_bytes);
++				ieee80211_tpt_led_trig_rx(mphy->hw, rx_bytes);
+ 			}
+ 			break;
+ 		case UNI_ALL_STA_TXRX_MSDU_COUNT:
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0049-mtk-wifi-mt76-mt7996-support-dup-wtbl.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0049-mtk-wifi-mt76-mt7996-support-dup-wtbl.patch
new file mode 100644
index 0000000..3954e5c
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0049-mtk-wifi-mt76-mt7996-support-dup-wtbl.patch
@@ -0,0 +1,72 @@
+From 4ab50a25d0bb37c9c46b3ddfe9bdf3e13f301d24 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Thu, 21 Sep 2023 00:52:46 +0800
+Subject: [PATCH 049/120] mtk: wifi: mt76: mt7996: support dup wtbl
+
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+Change-Id: I14ba41ace8341c23c1cfb6e9c4fbb2d5e93a5714
+---
+ mt7996/init.c    |  1 +
+ mt7996/mt7996.h  |  1 +
+ mt7996/mtk_mcu.c | 23 +++++++++++++++++++++++
+ 3 files changed, 25 insertions(+)
+
+diff --git a/mt7996/init.c b/mt7996/init.c
+index 1498787f1..30879ec35 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -675,6 +675,7 @@ static void mt7996_init_work(struct work_struct *work)
+ 	mt7996_mcu_set_eeprom(dev);
+ 	mt7996_mac_init(dev);
+ 	mt7996_txbf_init(dev);
++	mt7996_mcu_set_dup_wtbl(dev);
+ }
+ 
+ void mt7996_wfsys_reset(struct mt7996_dev *dev)
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index f8fbcd5c9..b016ea16d 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -808,6 +808,7 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir);
+ 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);
+ #endif
+ 
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
+diff --git a/mt7996/mtk_mcu.c b/mt7996/mtk_mcu.c
+index dbdf8d809..ea4e5bf28 100644
+--- a/mt7996/mtk_mcu.c
++++ b/mt7996/mtk_mcu.c
+@@ -257,4 +257,27 @@ void mt7996_mcu_rx_sr_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 			 le16_to_cpu(event->basic.tag));
+ 	}
+ }
++
++int mt7996_mcu_set_dup_wtbl(struct mt7996_dev *dev)
++{
++#define CHIP_CONFIG_DUP_WTBL	4
++#define DUP_WTBL_NUM	80
++	struct {
++		u8 _rsv[4];
++
++		__le16 tag;
++		__le16 len;
++		__le16 base;
++		__le16 num;
++		u8 _rsv2[4];
++	} __packed req = {
++		.tag = cpu_to_le16(CHIP_CONFIG_DUP_WTBL),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.base = cpu_to_le16(MT7996_WTBL_STA - DUP_WTBL_NUM + 1),
++		.num = cpu_to_le16(DUP_WTBL_NUM),
++	};
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(CHIP_CONFIG), &req,
++				 sizeof(req), true);
++}
+ #endif
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0050-mtk-wifi-mt76-mt7996-add-ibf-control-vendor-cmd.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0050-mtk-wifi-mt76-mt7996-add-ibf-control-vendor-cmd.patch
new file mode 100644
index 0000000..bb42c72
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0050-mtk-wifi-mt76-mt7996-add-ibf-control-vendor-cmd.patch
@@ -0,0 +1,143 @@
+From 3d6c69578b8a5b7d03c360e4717caea32d2a23d7 Mon Sep 17 00:00:00 2001
+From: "Allen.Ye" <allen.ye@mediatek.com>
+Date: Fri, 22 Sep 2023 09:54:49 +0800
+Subject: [PATCH 050/120] mtk: wifi: mt76: mt7996: add ibf control vendor cmd
+
+Signed-off-by: Allen.Ye <allen.ye@mediatek.com>
+---
+ mt7996/vendor.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++
+ mt7996/vendor.h | 23 +++++++++++++++++
+ 2 files changed, 88 insertions(+)
+
+diff --git a/mt7996/vendor.c b/mt7996/vendor.c
+index 9f333d0ee..dae3260ae 100644
+--- a/mt7996/vendor.c
++++ b/mt7996/vendor.c
+@@ -60,6 +60,11 @@ edcca_dump_policy[NUM_MTK_VENDOR_ATTRS_EDCCA_DUMP] = {
+ 	[MTK_VENDOR_ATTR_EDCCA_DUMP_SEC160_VAL] = { .type = NLA_U8 },
+ };
+ 
++static const struct nla_policy
++ibf_ctrl_policy[NUM_MTK_VENDOR_ATTRS_IBF_CTRL] = {
++	[MTK_VENDOR_ATTR_IBF_CTRL_ENABLE] = { .type = NLA_U8 },
++};
++
+ struct mt7996_amnt_data {
+ 	u8 idx;
+ 	u8 addr[ETH_ALEN];
+@@ -556,6 +561,54 @@ mt7996_vendor_edcca_ctrl_dump(struct wiphy *wiphy, struct wireless_dev *wdev,
+ 	return EDCCA_MAX_BW_NUM;
+ }
+ 
++static int mt7996_vendor_ibf_ctrl(struct wiphy *wiphy,
++				  struct wireless_dev *wdev,
++				  const void *data,
++				  int data_len)
++{
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_dev *dev = phy->dev;
++	struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_IBF_CTRL];
++	int err;
++	u8 val;
++
++	err = nla_parse(tb, MTK_VENDOR_ATTR_IBF_CTRL_MAX, data, data_len,
++			ibf_ctrl_policy, NULL);
++	if (err)
++		return err;
++
++	if (tb[MTK_VENDOR_ATTR_IBF_CTRL_ENABLE]) {
++		val = nla_get_u8(tb[MTK_VENDOR_ATTR_IBF_CTRL_ENABLE]);
++
++		dev->ibf = !!val;
++
++		err = mt7996_mcu_set_txbf(dev, BF_HW_EN_UPDATE);
++		if (err)
++			return err;
++	}
++	return 0;
++}
++
++static int
++mt7996_vendor_ibf_ctrl_dump(struct wiphy *wiphy, struct wireless_dev *wdev,
++			    struct sk_buff *skb, const void *data, int data_len,
++			    unsigned long *storage)
++{
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_dev *dev = phy->dev;
++
++	if (*storage == 1)
++		return -ENOENT;
++	*storage = 1;
++
++	if (nla_put_u8(skb, MTK_VENDOR_ATTR_IBF_DUMP_ENABLE, dev->ibf))
++		return -ENOMEM;
++
++	return 1;
++}
++
+ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 	{
+ 		.info = {
+@@ -604,6 +657,18 @@ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 		.policy = edcca_ctrl_policy,
+ 		.maxattr = MTK_VENDOR_ATTR_EDCCA_CTRL_MAX,
+ 	},
++	{
++		.info = {
++			.vendor_id = MTK_NL80211_VENDOR_ID,
++			.subcmd = MTK_NL80211_VENDOR_SUBCMD_IBF_CTRL,
++		},
++		.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
++			WIPHY_VENDOR_CMD_NEED_RUNNING,
++		.doit = mt7996_vendor_ibf_ctrl,
++		.dumpit = mt7996_vendor_ibf_ctrl_dump,
++		.policy = ibf_ctrl_policy,
++		.maxattr = MTK_VENDOR_ATTR_IBF_CTRL_MAX,
++	},
+ };
+ 
+ void mt7996_vendor_register(struct mt7996_phy *phy)
+diff --git a/mt7996/vendor.h b/mt7996/vendor.h
+index 4465bc9df..49f46f255 100644
+--- a/mt7996/vendor.h
++++ b/mt7996/vendor.h
+@@ -7,6 +7,7 @@ enum mtk_nl80211_vendor_subcmds {
+ 	MTK_NL80211_VENDOR_SUBCMD_AMNT_CTRL = 0xae,
+ 	MTK_NL80211_VENDOR_SUBCMD_MU_CTRL = 0xc5,
+ 	MTK_NL80211_VENDOR_SUBCMD_EDCCA_CTRL = 0xc7,
++	MTK_NL80211_VENDOR_SUBCMD_IBF_CTRL = 0xc9,
+ 	MTK_NL80211_VENDOR_SUBCMD_BSS_COLOR_CTRL = 0xca,
+ };
+ 
+@@ -102,4 +103,26 @@ enum mtk_vendor_attr_bss_color_ctrl {
+ 		NUM_MTK_VENDOR_ATTRS_BSS_COLOR_CTRL - 1
+ };
+ 
++enum mtk_vendor_attr_ibf_ctrl {
++	MTK_VENDOR_ATTR_IBF_CTRL_UNSPEC,
++
++	MTK_VENDOR_ATTR_IBF_CTRL_ENABLE,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_IBF_CTRL,
++	MTK_VENDOR_ATTR_IBF_CTRL_MAX =
++		NUM_MTK_VENDOR_ATTRS_IBF_CTRL - 1
++};
++
++enum mtk_vendor_attr_ibf_dump {
++	MTK_VENDOR_ATTR_IBF_DUMP_UNSPEC,
++
++	MTK_VENDOR_ATTR_IBF_DUMP_ENABLE,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_IBF_DUMP,
++	MTK_VENDOR_ATTR_IBF_DUMP_MAX =
++		NUM_MTK_VENDOR_ATTRS_IBF_DUMP - 1
++};
++
+ #endif
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0051-mtk-wifi-mt76-try-more-times-when-send-message-timeo.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0051-mtk-wifi-mt76-try-more-times-when-send-message-timeo.patch
new file mode 100644
index 0000000..381dbe9
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0051-mtk-wifi-mt76-try-more-times-when-send-message-timeo.patch
@@ -0,0 +1,214 @@
+From 536e93dd8e91ae66b58239ef51e8dcfd3dae32ad Mon Sep 17 00:00:00 2001
+From: Bo Jiao <Bo.Jiao@mediatek.com>
+Date: Mon, 6 Nov 2023 11:10:10 +0800
+Subject: [PATCH 051/120] mtk: wifi: mt76: try more times when send message
+ timeout.
+
+CR-Id: WCNCR00334773
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Change-Id: Ib7c01e6c9f74f68d8404a3d8bada9e5a10c4e232
+---
+ dma.c        |  7 ++++--
+ mcu.c        | 65 ++++++++++++++++++++++++++++++++++++----------------
+ mt7996/mac.c | 37 ++++++++++--------------------
+ 3 files changed, 62 insertions(+), 47 deletions(-)
+
+diff --git a/dma.c b/dma.c
+index 560446395..66c000ef0 100644
+--- a/dma.c
++++ b/dma.c
+@@ -504,9 +504,12 @@ mt76_dma_tx_queue_skb_raw(struct mt76_dev *dev, struct mt76_queue *q,
+ {
+ 	struct mt76_queue_buf buf = {};
+ 	dma_addr_t addr;
++	int ret = -ENOMEM;
+ 
+-	if (test_bit(MT76_MCU_RESET, &dev->phy.state))
++	if (test_bit(MT76_MCU_RESET, &dev->phy.state)) {
++		ret = -EAGAIN;
+ 		goto error;
++	}
+ 
+ 	if (q->queued + 1 >= q->ndesc - 1)
+ 		goto error;
+@@ -528,7 +531,7 @@ mt76_dma_tx_queue_skb_raw(struct mt76_dev *dev, struct mt76_queue *q,
+ 
+ error:
+ 	dev_kfree_skb(skb);
+-	return -ENOMEM;
++	return ret;
+ }
+ 
+ static int
+diff --git a/mcu.c b/mcu.c
+index fa4b05441..2926f7150 100644
+--- a/mcu.c
++++ b/mcu.c
+@@ -4,6 +4,7 @@
+  */
+ 
+ #include "mt76.h"
++#include "mt76_connac.h"
+ #include <linux/moduleparam.h>
+ 
+ struct sk_buff *
+@@ -74,35 +75,59 @@ int mt76_mcu_skb_send_and_get_msg(struct mt76_dev *dev, struct sk_buff *skb,
+ 				  int cmd, bool wait_resp,
+ 				  struct sk_buff **ret_skb)
+ {
++#define MT76_MSG_MAX_RETRY_CNT 3
+ 	unsigned long expires;
+-	int ret, seq;
++	int ret, seq, retry_cnt;
++	struct sk_buff *skb_tmp;
++	bool retry = wait_resp && is_mt7996(dev);
+ 
+ 	if (ret_skb)
+ 		*ret_skb = NULL;
+ 
+ 	mutex_lock(&dev->mcu.mutex);
+-
+-	ret = dev->mcu_ops->mcu_skb_send_msg(dev, skb, cmd, &seq);
+-	if (ret < 0)
+-		goto out;
+-
+-	if (!wait_resp) {
+-		ret = 0;
+-		goto out;
++	retry_cnt = retry ? MT76_MSG_MAX_RETRY_CNT : 1;
++	while (retry_cnt) {
++		skb_tmp = mt76_mcu_msg_alloc(dev, skb->data, skb->len);
++		if (!skb_tmp)
++			goto out;
++
++		if (retry && retry_cnt < MT76_MSG_MAX_RETRY_CNT) {
++			if (test_bit(MT76_MCU_RESET, &dev->phy.state))
++				usleep_range(200000, 500000);
++			dev_err(dev->dev, "send message %08x timeout, try again.\n", cmd);
++		}
++
++		ret = dev->mcu_ops->mcu_skb_send_msg(dev, skb_tmp, cmd, &seq);
++		if (ret < 0 && ret != -EAGAIN)
++			goto out;
++
++		if (!wait_resp) {
++			ret = 0;
++			goto out;
++		}
++
++		expires = jiffies + dev->mcu.timeout;
++
++		do {
++			skb_tmp = mt76_mcu_get_response(dev, expires);
++			ret = dev->mcu_ops->mcu_parse_response(dev, cmd, skb_tmp, seq);
++			if (ret == -ETIMEDOUT)
++				break;
++
++			if (!ret && ret_skb)
++				*ret_skb = skb_tmp;
++			else
++				dev_kfree_skb(skb_tmp);
++
++			if (ret != -EAGAIN)
++				goto out;
++		} while (ret == -EAGAIN);
++
++		retry_cnt--;
+ 	}
+ 
+-	expires = jiffies + dev->mcu.timeout;
+-
+-	do {
+-		skb = mt76_mcu_get_response(dev, expires);
+-		ret = dev->mcu_ops->mcu_parse_response(dev, cmd, skb, seq);
+-		if (!ret && ret_skb)
+-			*ret_skb = skb;
+-		else
+-			dev_kfree_skb(skb);
+-	} while (ret == -EAGAIN);
+-
+ out:
++	dev_kfree_skb(skb);
+ 	mutex_unlock(&dev->mcu.mutex);
+ 
+ 	return ret;
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 4e52aa1bf..56827c9b3 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -1784,13 +1784,24 @@ mt7996_mac_full_reset(struct mt7996_dev *dev)
+ 	phy3 = mt7996_phy3(dev);
+ 	dev->recovery.hw_full_reset = true;
+ 
+-	wake_up(&dev->mt76.mcu.wait);
+ 	ieee80211_stop_queues(mt76_hw(dev));
+ 	if (phy2)
+ 		ieee80211_stop_queues(phy2->mt76->hw);
+ 	if (phy3)
+ 		ieee80211_stop_queues(phy3->mt76->hw);
+ 
++	set_bit(MT76_RESET, &dev->mphy.state);
++	set_bit(MT76_MCU_RESET, &dev->mphy.state);
++	wake_up(&dev->mt76.mcu.wait);
++	if (phy2) {
++		set_bit(MT76_RESET, &phy2->mt76->state);
++		set_bit(MT76_MCU_RESET, &phy2->mt76->state);
++	}
++	if (phy3) {
++		set_bit(MT76_RESET, &phy3->mt76->state);
++		set_bit(MT76_MCU_RESET, &phy3->mt76->state);
++	}
++
+ 	cancel_work_sync(&dev->wed_rro.work);
+ 	cancel_delayed_work_sync(&dev->mphy.mac_work);
+ 	if (phy2)
+@@ -1893,16 +1904,6 @@ void mt7996_mac_reset_work(struct work_struct *work)
+ 	set_bit(MT76_MCU_RESET, &dev->mphy.state);
+ 	wake_up(&dev->mt76.mcu.wait);
+ 
+-	cancel_work_sync(&dev->wed_rro.work);
+-	cancel_delayed_work_sync(&dev->mphy.mac_work);
+-	if (phy2) {
+-		set_bit(MT76_RESET, &phy2->mt76->state);
+-		cancel_delayed_work_sync(&phy2->mt76->mac_work);
+-	}
+-	if (phy3) {
+-		set_bit(MT76_RESET, &phy3->mt76->state);
+-		cancel_delayed_work_sync(&phy3->mt76->mac_work);
+-	}
+ 	mt76_worker_disable(&dev->mt76.tx_worker);
+ 	mt76_for_each_q_rx(&dev->mt76, i) {
+ 		if (mtk_wed_device_active(&dev->mt76.mmio.wed) &&
+@@ -1913,8 +1914,6 @@ void mt7996_mac_reset_work(struct work_struct *work)
+ 	}
+ 	napi_disable(&dev->mt76.tx_napi);
+ 
+-	mutex_lock(&dev->mt76.mutex);
+-
+ 	mt76_wr(dev, MT_MCU_INT_EVENT, MT_MCU_INT_EVENT_DMA_STOPPED);
+ 
+ 	if (mt7996_wait_reset_state(dev, MT_MCU_CMD_RESET_DONE)) {
+@@ -1987,20 +1986,8 @@ void mt7996_mac_reset_work(struct work_struct *work)
+ 	if (phy3)
+ 		ieee80211_wake_queues(phy3->mt76->hw);
+ 
+-	mutex_unlock(&dev->mt76.mutex);
+-
+ 	mt7996_update_beacons(dev);
+ 
+-	ieee80211_queue_delayed_work(mt76_hw(dev), &dev->mphy.mac_work,
+-				     MT7996_WATCHDOG_TIME);
+-	if (phy2)
+-		ieee80211_queue_delayed_work(phy2->mt76->hw,
+-					     &phy2->mt76->mac_work,
+-					     MT7996_WATCHDOG_TIME);
+-	if (phy3)
+-		ieee80211_queue_delayed_work(phy3->mt76->hw,
+-					     &phy3->mt76->mac_work,
+-					     MT7996_WATCHDOG_TIME);
+ 	dev_info(dev->mt76.dev,"\n%s L1 SER recovery completed.",
+ 		 wiphy_name(dev->mt76.hw->wiphy));
+ }
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0052-mtk-wifi-mt76-mt7996-add-SER-overlap-handle.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0052-mtk-wifi-mt76-mt7996-add-SER-overlap-handle.patch
new file mode 100644
index 0000000..cd1e675
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0052-mtk-wifi-mt76-mt7996-add-SER-overlap-handle.patch
@@ -0,0 +1,96 @@
+From a2f972519b3ea9af44aefb8e644c1e18ddeb0a7a Mon Sep 17 00:00:00 2001
+From: Bo Jiao <Bo.Jiao@mediatek.com>
+Date: Tue, 21 Nov 2023 09:55:46 +0800
+Subject: [PATCH 052/120] mtk: wifi: mt76: mt7996: add SER overlap handle
+
+CR-ID: WCNCR00355921
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+---
+ mcu.c           | 3 ++-
+ mt7996/mac.c    | 8 ++++++++
+ mt7996/mcu.c    | 8 ++++++++
+ mt7996/mt7996.h | 2 ++
+ 4 files changed, 20 insertions(+), 1 deletion(-)
+
+diff --git a/mcu.c b/mcu.c
+index 2926f7150..a7afa2d7c 100644
+--- a/mcu.c
++++ b/mcu.c
+@@ -94,7 +94,8 @@ int mt76_mcu_skb_send_and_get_msg(struct mt76_dev *dev, struct sk_buff *skb,
+ 		if (retry && retry_cnt < MT76_MSG_MAX_RETRY_CNT) {
+ 			if (test_bit(MT76_MCU_RESET, &dev->phy.state))
+ 				usleep_range(200000, 500000);
+-			dev_err(dev->dev, "send message %08x timeout, try again.\n", cmd);
++			dev_err(dev->dev, "send message %08x timeout, try again(%d).\n",
++				cmd, (MT76_MSG_MAX_RETRY_CNT - retry_cnt));
+ 		}
+ 
+ 		ret = dev->mcu_ops->mcu_skb_send_msg(dev, skb_tmp, cmd, &seq);
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 56827c9b3..902609cf8 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -1885,6 +1885,7 @@ void mt7996_mac_reset_work(struct work_struct *work)
+ 	if (!(READ_ONCE(dev->recovery.state) & MT_MCU_CMD_STOP_DMA))
+ 		return;
+ 
++	dev->recovery.l1_reset_last = dev->recovery.l1_reset;
+ 	dev_info(dev->mt76.dev,"\n%s L1 SER recovery start.",
+ 		 wiphy_name(dev->mt76.hw->wiphy));
+ 
+@@ -1902,6 +1903,10 @@ void mt7996_mac_reset_work(struct work_struct *work)
+ 
+ 	set_bit(MT76_RESET, &dev->mphy.state);
+ 	set_bit(MT76_MCU_RESET, &dev->mphy.state);
++	if (phy2)
++		set_bit(MT76_RESET, &phy2->mt76->state);
++	if (phy3)
++		set_bit(MT76_RESET, &phy3->mt76->state);
+ 	wake_up(&dev->mt76.mcu.wait);
+ 
+ 	mt76_worker_disable(&dev->mt76.tx_worker);
+@@ -2116,6 +2121,9 @@ void mt7996_reset(struct mt7996_dev *dev)
+ 		return;
+ 	}
+ 
++	if ((READ_ONCE(dev->recovery.state) & MT_MCU_CMD_STOP_DMA))
++		dev->recovery.l1_reset++;
++
+ 	queue_work(dev->mt76.wq, &dev->reset_work);
+ 	wake_up(&dev->reset_wait);
+ }
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 175699c1e..ed8ed2200 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -246,6 +246,14 @@ mt7996_mcu_send_message(struct mt76_dev *mdev, struct sk_buff *skb,
+ 	u32 val;
+ 	u8 seq;
+ 
++	if (dev->recovery.l1_reset_last != dev->recovery.l1_reset) {
++		dev_info(dev->mt76.dev,"\n%s L1 SER recovery overlap, drop message %08x.",
++			 wiphy_name(dev->mt76.hw->wiphy), cmd);
++
++		dev_kfree_skb(skb);
++		return -EPERM;
++	}
++
+ 	mdev->mcu.timeout = 20 * HZ;
+ 
+ 	seq = ++dev->mt76.mcu.msg_seq & 0xf;
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index b016ea16d..08e239978 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -392,6 +392,8 @@ struct mt7996_dev {
+ 	wait_queue_head_t reset_wait;
+ 	struct {
+ 		u32 state;
++		u32 l1_reset;
++		u32 l1_reset_last;
+ 		u32 wa_reset_count;
+ 		u32 wm_reset_count;
+ 		bool hw_full_reset:1;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0053-mtk-wifi-mt76-mt7996-kite-default-1-pcie-setting.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0053-mtk-wifi-mt76-mt7996-kite-default-1-pcie-setting.patch
new file mode 100644
index 0000000..8bf3028
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0053-mtk-wifi-mt76-mt7996-kite-default-1-pcie-setting.patch
@@ -0,0 +1,56 @@
+From 39256d6f0299aded4c3ea016ebc25d5f2edd3294 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Thu, 13 Jul 2023 16:36:36 +0800
+Subject: [PATCH 053/120] mtk: wifi: mt76: mt7996: kite default 1-pcie setting
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt7996/pci.c | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+diff --git a/mt7996/pci.c b/mt7996/pci.c
+index 040561813..05830c01c 100644
+--- a/mt7996/pci.c
++++ b/mt7996/pci.c
+@@ -11,6 +11,9 @@
+ #include "mac.h"
+ #include "../trace.h"
+ 
++static bool hif2_enable = false;
++module_param(hif2_enable, bool, 0644);
++
+ static LIST_HEAD(hif_list);
+ static DEFINE_SPINLOCK(hif_lock);
+ static u32 hif_idx;
+@@ -63,6 +66,9 @@ static struct mt7996_hif *mt7996_pci_init_hif2(struct pci_dev *pdev)
+ {
+ 	hif_idx++;
+ 
++	if (!hif2_enable)
++		return NULL;
++
+ 	if (!pci_get_device(PCI_VENDOR_ID_MEDIATEK, 0x7991, NULL) &&
+ 	    !pci_get_device(PCI_VENDOR_ID_MEDIATEK, 0x799a, NULL))
+ 		return NULL;
+@@ -77,6 +83,9 @@ static int mt7996_pci_hif2_probe(struct pci_dev *pdev)
+ {
+ 	struct mt7996_hif *hif;
+ 
++	if (!hif2_enable)
++		return 0;
++
+ 	hif = devm_kzalloc(&pdev->dev, sizeof(*hif), GFP_KERNEL);
+ 	if (!hif)
+ 		return -ENOMEM;
+@@ -101,6 +110,8 @@ static int mt7996_pci_probe(struct pci_dev *pdev,
+ 	int irq, hif2_irq, ret;
+ 	struct mt76_dev *mdev;
+ 
++	hif2_enable |= (id->device == 0x7990 || id->device == 0x7991);
++
+ 	ret = pcim_enable_device(pdev);
+ 	if (ret)
+ 		return ret;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0054-mtk-wifi-mt76-mt7996-add-debugfs-knob-for-rx_counter.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0054-mtk-wifi-mt76-mt7996-add-debugfs-knob-for-rx_counter.patch
new file mode 100644
index 0000000..aeea22c
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0054-mtk-wifi-mt76-mt7996-add-debugfs-knob-for-rx_counter.patch
@@ -0,0 +1,291 @@
+From 3c44dda14dc9c8693b1de7e9a72c1e9dc8296268 Mon Sep 17 00:00:00 2001
+From: Peter Chiu <chui-hao.chiu@mediatek.com>
+Date: Fri, 28 Apr 2023 10:39:58 +0800
+Subject: [PATCH 054/120] mtk: wifi: mt76: mt7996: add debugfs knob for
+ rx_counters
+
+Signed-off-by: Peter Chiu <chui-hao.chiu@mediatek.com>
+---
+ agg-rx.c             |  8 ++++++++
+ mac80211.c           | 16 ++++++++++++++--
+ mt76.h               | 15 +++++++++++++++
+ mt7996/mac.c         | 18 +++++++++++++++---
+ mt7996/mtk_debugfs.c | 42 ++++++++++++++++++++++++++++++++++++++++++
+ 5 files changed, 94 insertions(+), 5 deletions(-)
+
+diff --git a/agg-rx.c b/agg-rx.c
+index 07c386c7b..37588ac20 100644
+--- a/agg-rx.c
++++ b/agg-rx.c
+@@ -33,10 +33,13 @@ mt76_rx_aggr_release_frames(struct mt76_rx_tid *tid,
+ 			    struct sk_buff_head *frames,
+ 			    u16 head)
+ {
++	struct mt76_phy *phy = mt76_dev_phy(tid->dev, tid->band_idx);
+ 	int idx;
+ 
+ 	while (ieee80211_sn_less(tid->head, head)) {
+ 		idx = tid->head % tid->size;
++		if (!tid->reorder_buf[idx])
++			phy->rx_stats.rx_agg_miss++;
+ 		mt76_aggr_release(tid, frames, idx);
+ 	}
+ }
+@@ -151,6 +154,7 @@ void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames)
+ 	struct mt76_wcid *wcid = status->wcid;
+ 	struct ieee80211_sta *sta;
+ 	struct mt76_rx_tid *tid;
++	struct mt76_phy *phy;
+ 	bool sn_less;
+ 	u16 seqno, head, size, idx;
+ 	u8 tidno = status->qos_ctl & IEEE80211_QOS_CTL_TID_MASK;
+@@ -186,6 +190,7 @@ void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames)
+ 	head = tid->head;
+ 	seqno = status->seqno;
+ 	size = tid->size;
++	phy = mt76_dev_phy(tid->dev, tid->band_idx);
+ 	sn_less = ieee80211_sn_less(seqno, head);
+ 
+ 	if (!tid->started) {
+@@ -197,6 +202,7 @@ void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames)
+ 
+ 	if (sn_less) {
+ 		__skb_unlink(skb, frames);
++		phy->rx_stats.rx_dup_drop++;
+ 		dev_kfree_skb(skb);
+ 		goto out;
+ 	}
+@@ -223,6 +229,7 @@ void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames)
+ 
+ 	/* Discard if the current slot is already in use */
+ 	if (tid->reorder_buf[idx]) {
++		phy->rx_stats.rx_dup_drop++;
+ 		dev_kfree_skb(skb);
+ 		goto out;
+ 	}
+@@ -254,6 +261,7 @@ int mt76_rx_aggr_start(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 tidno,
+ 	tid->head = ssn;
+ 	tid->size = size;
+ 	tid->num = tidno;
++	tid->band_idx = wcid->phy_idx;
+ 	INIT_DELAYED_WORK(&tid->reorder_work, mt76_rx_aggr_reorder_work);
+ 	spin_lock_init(&tid->lock);
+ 
+diff --git a/mac80211.c b/mac80211.c
+index dbab04031..92f326523 100644
+--- a/mac80211.c
++++ b/mac80211.c
+@@ -784,6 +784,7 @@ static void mt76_rx_release_amsdu(struct mt76_phy *phy, enum mt76_rxq_id q)
+ 		}
+ 
+ 		if (ether_addr_equal(skb->data + offset, rfc1042_header)) {
++			phy->rx_stats.rx_drop++;
+ 			dev_kfree_skb(skb);
+ 			return;
+ 		}
+@@ -1100,10 +1101,16 @@ mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,
+ 
+ 	*sta = wcid_to_sta(mstat.wcid);
+ 	*hw = mt76_phy_hw(dev, mstat.phy_idx);
++
++	if ((mstat.flag & RX_FLAG_8023) || ieee80211_is_data_qos(hdr->frame_control)) {
++		struct mt76_phy *phy = mt76_dev_phy(dev, mstat.phy_idx);
++
++		phy->rx_stats.rx_mac80211++;
++	}
+ }
+ 
+ static void
+-mt76_check_ccmp_pn(struct sk_buff *skb)
++mt76_check_ccmp_pn(struct mt76_dev *dev, struct sk_buff *skb)
+ {
+ 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
+ 	struct mt76_wcid *wcid = status->wcid;
+@@ -1150,7 +1157,11 @@ skip_hdr_check:
+ 	ret = memcmp(status->iv, wcid->rx_key_pn[security_idx],
+ 		     sizeof(status->iv));
+ 	if (ret <= 0) {
++		struct mt76_phy *phy = mt76_dev_phy(dev, status->phy_idx);
++
++		phy->rx_stats.rx_pn_iv_error++;
+ 		status->flag |= RX_FLAG_ONLY_MONITOR;
++
+ 		return;
+ 	}
+ 
+@@ -1331,7 +1342,7 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
+ 	while ((skb = __skb_dequeue(frames)) != NULL) {
+ 		struct sk_buff *nskb = skb_shinfo(skb)->frag_list;
+ 
+-		mt76_check_ccmp_pn(skb);
++		mt76_check_ccmp_pn(dev, skb);
+ 		skb_shinfo(skb)->frag_list = NULL;
+ 		mt76_rx_convert(dev, skb, &hw, &sta);
+ 		ieee80211_rx_list(hw, sta, skb, &list);
+@@ -1354,6 +1365,7 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
+ 	}
+ 
+ 	list_for_each_entry_safe(skb, tmp, &list, list) {
++		dev->rx_kernel++;
+ 		skb_list_del_init(skb);
+ 		napi_gro_receive(napi, skb);
+ 	}
+diff --git a/mt76.h b/mt76.h
+index a75277fee..58fd55b14 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -423,6 +423,7 @@ struct mt76_rx_tid {
+ 	struct rcu_head rcu_head;
+ 
+ 	struct mt76_dev *dev;
++	u8 band_idx;
+ 
+ 	spinlock_t lock;
+ 	struct delayed_work reorder_work;
+@@ -854,6 +855,19 @@ struct mt76_phy {
+ 		bool al;
+ 		u8 pin;
+ 	} leds;
++
++	struct {
++		u32 rx_mac80211;
++
++		u32 rx_drop;
++		u32 rx_rxd_drop;
++		u32 rx_dup_drop;
++		u32 rx_agg_miss;
++		u32 rx_icv_error;
++		u32 rx_fcs_error;
++		u32 rx_tkip_mic_error;
++		u32 rx_pn_iv_error;
++	} rx_stats;
+ };
+ 
+ struct mt76_dev {
+@@ -959,6 +973,7 @@ struct mt76_dev {
+ 	};
+ 
+ 	const char *bin_file_name;
++	u32 rx_kernel;
+ };
+ 
+ /* per-phy stats.  */
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 902609cf8..161639d4d 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -469,8 +469,10 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
+ 		return -EINVAL;
+ 
+ 	/* ICV error or CCMP/BIP/WPI MIC error */
+-	if (rxd1 & MT_RXD1_NORMAL_ICV_ERR)
++	if (rxd1 & MT_RXD1_NORMAL_ICV_ERR) {
++		mphy->rx_stats.rx_icv_error++;
+ 		status->flag |= RX_FLAG_ONLY_MONITOR;
++	}
+ 
+ 	unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
+ 	idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
+@@ -501,11 +503,15 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
+ 	    !(csum_status & (BIT(0) | BIT(2) | BIT(3))))
+ 		skb->ip_summed = CHECKSUM_UNNECESSARY;
+ 
+-	if (rxd1 & MT_RXD3_NORMAL_FCS_ERR)
++	if (rxd1 & MT_RXD3_NORMAL_FCS_ERR) {
++		mphy->rx_stats.rx_fcs_error++;
+ 		status->flag |= RX_FLAG_FAILED_FCS_CRC;
++	}
+ 
+-	if (rxd1 & MT_RXD1_NORMAL_TKIP_MIC_ERR)
++	if (rxd1 & MT_RXD1_NORMAL_TKIP_MIC_ERR) {
++		mphy->rx_stats.rx_tkip_mic_error++;
+ 		status->flag |= RX_FLAG_MMIC_ERROR;
++	}
+ 
+ 	if (FIELD_GET(MT_RXD2_NORMAL_SEC_MODE, rxd2) != 0 &&
+ 	    !(rxd1 & (MT_RXD1_NORMAL_CLM | MT_RXD1_NORMAL_CM))) {
+@@ -1415,8 +1421,10 @@ void mt7996_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q,
+ 			 struct sk_buff *skb, u32 *info)
+ {
+ 	struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76);
++	struct mt76_phy *phy;
+ 	__le32 *rxd = (__le32 *)skb->data;
+ 	__le32 *end = (__le32 *)&skb->data[skb->len];
++	u8 band_idx;
+ 	enum rx_pkt_type type;
+ 
+ 	type = le32_get_bits(rxd[0], MT_RXD0_PKT_TYPE);
+@@ -1458,6 +1466,10 @@ void mt7996_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q,
+ 		}
+ 		fallthrough;
+ 	default:
++		band_idx = le32_get_bits(rxd[1], MT_RXD1_NORMAL_BAND_IDX);
++		phy = mt76_dev_phy(mdev, band_idx);
++		if (likely(phy))
++			phy->rx_stats.rx_rxd_drop++;
+ 		dev_kfree_skb(skb);
+ 		break;
+ 	}
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index 17edd764d..686666872 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -2854,6 +2854,46 @@ mt7996_sr_scene_cond_show(struct seq_file *file, void *data)
+ }
+ DEFINE_SHOW_ATTRIBUTE(mt7996_sr_scene_cond);
+ 
++static int mt7996_rx_counters(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	u32 rx_mac80211 = 0;
++	int i = 0;
++
++	for (i = 0; i < __MT_MAX_BAND; i++) {
++		struct mt76_phy *phy = mt76_dev_phy(&dev->mt76, i);
++
++		if (!phy)
++			continue;
++
++		seq_printf(s, "\n==========PHY%d==========\n", i);
++
++#define SEQ_PRINT(_str, _rx_param) do {					\
++		seq_printf(s, _str"\n", phy->rx_stats._rx_param);	\
++	} while (0)
++
++		SEQ_PRINT("Rx to mac80211: %u", rx_mac80211);
++		SEQ_PRINT("Rx drop: %u", rx_drop);
++		SEQ_PRINT("Rx drop due to RXD type error: %u", rx_rxd_drop);
++		SEQ_PRINT("Rx duplicated drop: %u", rx_dup_drop);
++		SEQ_PRINT("Rx agg miss: %u", rx_agg_miss);
++		SEQ_PRINT("Rx ICV error: %u", rx_icv_error);
++		SEQ_PRINT("Rx FCS error: %u", rx_fcs_error);
++		SEQ_PRINT("Rx TKIP MIC error: %u", rx_tkip_mic_error);
++		SEQ_PRINT("Rx PN/IV error: %u", rx_pn_iv_error);
++#undef SEQ_PRINT
++
++		rx_mac80211 += phy->rx_stats.rx_mac80211;
++	}
++
++	seq_printf(s, "\n==========SUM==========\n");
++	seq_printf(s, "Rx to kernel: %u\n", dev->mt76.rx_kernel);
++	seq_printf(s, "Rx to mac80211: %u\n", rx_mac80211);
++
++
++	return 0;
++}
++
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -2917,6 +2957,8 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ 
+ 	debugfs_create_devm_seqfile(dev->mt76.dev, "tr_info", dir,
+ 				    mt7996_trinfo_read);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "rx_counters", dir,
++				    mt7996_rx_counters);
+ 	debugfs_create_file("txpower_level", 0600, dir, phy, &fops_txpower_level);
+ 	debugfs_create_file("txpower_info", 0600, dir, phy, &mt7996_txpower_info_fops);
+ 	debugfs_create_file("txpower_sku", 0600, dir, phy, &mt7996_txpower_sku_fops);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0055-mtk-wifi-mt76-mt7996-add-three-wire-pta-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0055-mtk-wifi-mt76-mt7996-add-three-wire-pta-support.patch
new file mode 100644
index 0000000..f3d51df
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0055-mtk-wifi-mt76-mt7996-add-three-wire-pta-support.patch
@@ -0,0 +1,134 @@
+From 255b1561b2cf5332a6e6c436ba9a3b00540f8a21 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Tue, 24 Oct 2023 15:59:18 +0800
+Subject: [PATCH 055/120] mtk: wifi: mt76: mt7996: add three wire pta support
+
+three wire enable bit 0 & 1 for EXT0 & EXT1, respectively
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt76_connac_mcu.h |  1 +
+ mt7996/vendor.c   | 49 +++++++++++++++++++++++++++++++++++++++++++++++
+ mt7996/vendor.h   | 12 ++++++++++++
+ 3 files changed, 62 insertions(+)
+
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index f4557bd6e..864a802d7 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1286,6 +1286,7 @@ enum {
+ 	MCU_UNI_CMD_PER_STA_INFO = 0x6d,
+ 	MCU_UNI_CMD_ALL_STA_INFO = 0x6e,
+ 	MCU_UNI_CMD_ASSERT_DUMP = 0x6f,
++	MCU_UNI_CMD_PTA_3WIRE_CTRL = 0x78,
+ };
+ 
+ enum {
+diff --git a/mt7996/vendor.c b/mt7996/vendor.c
+index dae3260ae..9ba6f00ad 100644
+--- a/mt7996/vendor.c
++++ b/mt7996/vendor.c
+@@ -60,6 +60,11 @@ edcca_dump_policy[NUM_MTK_VENDOR_ATTRS_EDCCA_DUMP] = {
+ 	[MTK_VENDOR_ATTR_EDCCA_DUMP_SEC160_VAL] = { .type = NLA_U8 },
+ };
+ 
++static const struct nla_policy
++three_wire_ctrl_policy[NUM_MTK_VENDOR_ATTRS_3WIRE_CTRL] = {
++	[MTK_VENDOR_ATTR_3WIRE_CTRL_MODE] = {.type = NLA_U8 },
++};
++
+ static const struct nla_policy
+ ibf_ctrl_policy[NUM_MTK_VENDOR_ATTRS_IBF_CTRL] = {
+ 	[MTK_VENDOR_ATTR_IBF_CTRL_ENABLE] = { .type = NLA_U8 },
+@@ -561,6 +566,39 @@ mt7996_vendor_edcca_ctrl_dump(struct wiphy *wiphy, struct wireless_dev *wdev,
+ 	return EDCCA_MAX_BW_NUM;
+ }
+ 
++static int mt7996_vendor_3wire_ctrl(struct wiphy *wiphy, struct wireless_dev *wdev,
++				    const void *data, int data_len)
++{
++#define UNI_3WIRE_EXT_EN	0
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++	struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_3WIRE_CTRL];
++	struct {
++		u8 __rsv1[4];
++
++		__le16 tag;
++		__le16 len;
++		u8 three_wire_mode;
++	} __packed req = {
++		.tag = cpu_to_le16(UNI_3WIRE_EXT_EN),
++		.len = cpu_to_le16(sizeof(req) - 4),
++	};
++	int err;
++
++	err = nla_parse(tb, MTK_VENDOR_ATTR_3WIRE_CTRL_MAX, data, data_len,
++			three_wire_ctrl_policy, NULL);
++	if (err)
++		return err;
++
++	if (!tb[MTK_VENDOR_ATTR_3WIRE_CTRL_MODE])
++		return -EINVAL;
++
++	req.three_wire_mode = nla_get_u8(tb[MTK_VENDOR_ATTR_3WIRE_CTRL_MODE]);
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(PTA_3WIRE_CTRL), &req,
++				 sizeof(req), false);
++}
++
+ static int mt7996_vendor_ibf_ctrl(struct wiphy *wiphy,
+ 				  struct wireless_dev *wdev,
+ 				  const void *data,
+@@ -657,6 +695,17 @@ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 		.policy = edcca_ctrl_policy,
+ 		.maxattr = MTK_VENDOR_ATTR_EDCCA_CTRL_MAX,
+ 	},
++	{
++		.info = {
++			.vendor_id = MTK_NL80211_VENDOR_ID,
++			.subcmd = MTK_NL80211_VENDOR_SUBCMD_3WIRE_CTRL,
++		},
++		.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
++			 WIPHY_VENDOR_CMD_NEED_RUNNING,
++		.doit = mt7996_vendor_3wire_ctrl,
++		.policy = three_wire_ctrl_policy,
++		.maxattr = MTK_VENDOR_ATTR_3WIRE_CTRL_MAX,
++	},
+ 	{
+ 		.info = {
+ 			.vendor_id = MTK_NL80211_VENDOR_ID,
+diff --git a/mt7996/vendor.h b/mt7996/vendor.h
+index 49f46f255..29ccc050b 100644
+--- a/mt7996/vendor.h
++++ b/mt7996/vendor.h
+@@ -7,6 +7,7 @@ enum mtk_nl80211_vendor_subcmds {
+ 	MTK_NL80211_VENDOR_SUBCMD_AMNT_CTRL = 0xae,
+ 	MTK_NL80211_VENDOR_SUBCMD_MU_CTRL = 0xc5,
+ 	MTK_NL80211_VENDOR_SUBCMD_EDCCA_CTRL = 0xc7,
++	MTK_NL80211_VENDOR_SUBCMD_3WIRE_CTRL = 0xc8,
+ 	MTK_NL80211_VENDOR_SUBCMD_IBF_CTRL = 0xc9,
+ 	MTK_NL80211_VENDOR_SUBCMD_BSS_COLOR_CTRL = 0xca,
+ };
+@@ -43,6 +44,17 @@ enum mtk_vendor_attr_edcca_dump {
+ 		NUM_MTK_VENDOR_ATTRS_EDCCA_DUMP - 1
+ };
+ 
++enum mtk_vendor_attr_3wire_ctrl {
++	MTK_VENDOR_ATTR_3WIRE_CTRL_UNSPEC,
++
++	MTK_VENDOR_ATTR_3WIRE_CTRL_MODE,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_3WIRE_CTRL,
++	MTK_VENDOR_ATTR_3WIRE_CTRL_MAX =
++		NUM_MTK_VENDOR_ATTRS_3WIRE_CTRL - 1
++};
++
+ enum mtk_vendor_attr_mu_ctrl {
+ 	MTK_VENDOR_ATTR_MU_CTRL_UNSPEC,
+ 
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0056-mtk-wifi-mt76-mt7996-support-BF-MIMO-debug-commands.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0056-mtk-wifi-mt76-mt7996-support-BF-MIMO-debug-commands.patch
new file mode 100644
index 0000000..d8654f4
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0056-mtk-wifi-mt76-mt7996-support-BF-MIMO-debug-commands.patch
@@ -0,0 +1,1214 @@
+From 367e70f790595dc23ae328b24d980bdacd533b03 Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Tue, 3 Jan 2023 09:42:07 +0800
+Subject: [PATCH 056/120] mtk: wifi: mt76: mt7996: support BF/MIMO debug
+ commands
+
+This commit includes the following commands:
+1. starec_bf_read
+2. txbf_snd_info: start/stop sounding and set sounding period
+3. fbkRptInfo
+4. fix muru rate
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Change-Id: Id6cbaf16e4e6f63238a495ac9f45744e1dd38e9b
+
+fix the wrong wlan_idx for user3
+
+CR-Id: WCNCR00261410
+Change-Id: I7ece7399370f2bd22d2564029025baeda27057a5
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+
+Align the format of mcu event "mt7996_mcu_bf_starec_read" with
+firmware definition.
+
+Fw gerrit change:
+https://gerrit.mediatek.inc/c/neptune/firmware/bora/wifi/core/+/8218143
+
+CR-Id: WCNCR00240772
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+---
+ mt7996/mcu.c         |   5 +
+ mt7996/mcu.h         |   4 +
+ mt7996/mt7996.h      |   5 +
+ mt7996/mtk_debugfs.c | 120 +++++++++
+ mt7996/mtk_mcu.c     | 624 +++++++++++++++++++++++++++++++++++++++++++
+ mt7996/mtk_mcu.h     | 342 ++++++++++++++++++++++++
+ 6 files changed, 1100 insertions(+)
+
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index ed8ed2200..97142c5ef 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -744,6 +744,11 @@ mt7996_mcu_uni_rx_unsolicited_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 	case MCU_UNI_EVENT_TESTMODE_CTRL:
+ 		mt7996_tm_rf_test_event(dev, skb);
+ 		break;
++#endif
++#if defined CONFIG_NL80211_TESTMODE || defined CONFIG_MTK_DEBUG
++	case MCU_UNI_EVENT_BF:
++		mt7996_mcu_rx_bf_event(dev, skb);
++		break;
+ #endif
+ 	default:
+ 		break;
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 34fdfb261..347893c8c 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -770,8 +770,12 @@ enum {
+ 
+ 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,
+ };
+ 
+ enum {
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 08e239978..d012cc3ae 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -811,6 +811,11 @@ 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);
++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);
++int mt7996_mcu_set_txbf_snd_info(struct mt7996_phy *phy, void *para);
+ #endif
+ 
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index 686666872..2a17eafcd 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -2894,6 +2894,117 @@ static int mt7996_rx_counters(struct seq_file *s, void *data)
+ 	return 0;
+ }
+ 
++static int
++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);
++}
++DEFINE_DEBUGFS_ATTRIBUTE(fops_starec_bf_read, NULL,
++			 mt7996_starec_bf_read_set, "%lld\n");
++
++static ssize_t
++mt7996_bf_txsnd_info_set(struct file *file,
++			 const char __user *user_buf,
++			 size_t count, loff_t *ppos)
++{
++	struct mt7996_phy *phy = file->private_data;
++	char buf[40];
++	int ret;
++
++	if (count >= sizeof(buf))
++		return -EINVAL;
++
++	if (copy_from_user(buf, user_buf, count))
++		return -EFAULT;
++
++	if (count && buf[count - 1] == '\n')
++		buf[count - 1] = '\0';
++	else
++		buf[count] = '\0';
++
++	ret = mt7996_mcu_set_txbf_snd_info(phy, buf);
++
++	if (ret) return -EFAULT;
++
++	return count;
++}
++
++static const struct file_operations fops_bf_txsnd_info = {
++	.write = mt7996_bf_txsnd_info_set,
++	.read = NULL,
++	.open = simple_open,
++	.llseek = default_llseek,
++};
++
++static int
++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);
++}
++DEFINE_DEBUGFS_ATTRIBUTE(fops_bf_fbk_rpt, NULL,
++			 mt7996_bf_fbk_rpt_set, "%lld\n");
++
++static int
++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);
++}
++DEFINE_DEBUGFS_ATTRIBUTE(fops_bf_pfmu_tag_read, NULL,
++			 mt7996_bf_pfmu_tag_read_set, "%lld\n");
++
++static int
++mt7996_muru_fixed_rate_set(void *data, u64 val)
++{
++	struct mt7996_dev *dev = data;
++
++	return mt7996_mcu_set_muru_fixed_rate_enable(dev, UNI_CMD_MURU_FIXED_RATE_CTRL,
++						     val);
++}
++DEFINE_DEBUGFS_ATTRIBUTE(fops_muru_fixed_rate_enable, NULL,
++			 mt7996_muru_fixed_rate_set, "%lld\n");
++
++static ssize_t
++mt7996_muru_fixed_rate_parameter_set(struct file *file,
++				     const char __user *user_buf,
++				     size_t count, loff_t *ppos)
++{
++	struct mt7996_dev *dev = file->private_data;
++	char buf[40];
++	int ret;
++
++	if (count >= sizeof(buf))
++		return -EINVAL;
++
++	if (copy_from_user(buf, user_buf, count))
++		return -EFAULT;
++
++	if (count && buf[count - 1] == '\n')
++		buf[count - 1] = '\0';
++	else
++		buf[count] = '\0';
++
++
++	ret = mt7996_mcu_set_muru_fixed_rate_parameter(dev, UNI_CMD_MURU_FIXED_GROUP_RATE_CTRL,
++						       buf);
++
++	if (ret) return -EFAULT;
++
++	return count;
++}
++
++static const struct file_operations fops_muru_fixed_group_rate = {
++	.write = mt7996_muru_fixed_rate_parameter_set,
++	.read = NULL,
++	.open = simple_open,
++	.llseek = default_llseek,
++};
++
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -2980,6 +3091,15 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ 	debugfs_create_file("sr_stats", 0400, dir, phy, &mt7996_sr_stats_fops);
+ 	debugfs_create_file("sr_scene_cond", 0400, dir, phy, &mt7996_sr_scene_cond_fops);
+ 
++	debugfs_create_file("muru_fixed_rate_enable", 0600, dir, dev,
++			    &fops_muru_fixed_rate_enable);
++	debugfs_create_file("muru_fixed_group_rate", 0600, dir, dev,
++			    &fops_muru_fixed_group_rate);
++	debugfs_create_file("bf_txsnd_info", 0600, dir, phy, &fops_bf_txsnd_info);
++	debugfs_create_file("bf_starec_read", 0600, dir, phy, &fops_starec_bf_read);
++	debugfs_create_file("bf_fbk_rpt", 0600, dir, phy, &fops_bf_fbk_rpt);
++	debugfs_create_file("pfmu_tag_read", 0600, dir, phy, &fops_bf_pfmu_tag_read);
++
+ 	return 0;
+ }
+ 
+diff --git a/mt7996/mtk_mcu.c b/mt7996/mtk_mcu.c
+index ea4e5bf28..6b2cdad6f 100644
+--- a/mt7996/mtk_mcu.c
++++ b/mt7996/mtk_mcu.c
+@@ -280,4 +280,628 @@ int mt7996_mcu_set_dup_wtbl(struct mt7996_dev *dev)
+ 	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(CHIP_CONFIG), &req,
+ 				 sizeof(req), true);
+ }
++
++static struct tlv *
++__mt7996_mcu_add_uni_tlv(struct sk_buff *skb, u16 tag, u16 len)
++{
++	struct tlv *ptlv, tlv = {
++		.tag = cpu_to_le16(tag),
++		.len = cpu_to_le16(len),
++	};
++
++	ptlv = skb_put(skb, len);
++	memcpy(ptlv, &tlv, sizeof(tlv));
++
++	return ptlv;
++}
++
++int mt7996_mcu_set_txbf_internal(struct mt7996_phy *phy, u8 action, int idx)
++{
++	struct mt7996_dev *dev = phy->dev;
++#define MT7996_MTK_BF_MAX_SIZE	sizeof(struct bf_starec_read)
++	struct uni_header hdr;
++	struct sk_buff *skb;
++	struct tlv *tlv;
++	int len = sizeof(hdr) + MT7996_MTK_BF_MAX_SIZE;
++
++	memset(&hdr, 0, sizeof(hdr));
++
++	skb = mt76_mcu_msg_alloc(&dev->mt76, NULL, len);
++	if (!skb)
++		return -ENOMEM;
++
++	skb_put_data(skb, &hdr, sizeof(hdr));
++
++	switch (action) {
++	case BF_PFMU_TAG_READ: {
++		struct bf_pfmu_tag *req;
++
++		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->band_idx = phy->mt76->band_idx;
++		break;
++	}
++	case BF_STA_REC_READ: {
++		struct bf_starec_read *req;
++
++		tlv = __mt7996_mcu_add_uni_tlv(skb, action, sizeof(*req));
++		req = (struct bf_starec_read *)tlv;
++		req->wlan_idx = idx;
++		break;
++	}
++	case BF_FBRPT_DBG_INFO_READ: {
++		struct bf_fbk_rpt_info *req;
++
++		if (idx != 0) {
++			dev_info(dev->mt76.dev, "Invalid input");
++			return 0;
++		}
++
++		tlv = __mt7996_mcu_add_uni_tlv(skb, action, sizeof(*req));
++		req = (struct bf_fbk_rpt_info *)tlv;
++		req->action = idx;
++		req->band_idx = phy->mt76->band_idx;
++		break;
++	}
++	default:
++		return -EINVAL;
++	}
++
++	return mt76_mcu_skb_send_msg(&phy->dev->mt76, skb, MCU_WM_UNI_CMD(BF), false);
++}
++
++int mt7996_mcu_set_txbf_snd_info(struct mt7996_phy *phy, void *para)
++{
++	char *buf = (char *)para;
++	__le16 input[5] = {0};
++	u8 recv_arg = 0;
++	struct bf_txsnd_info *req;
++	struct uni_header hdr;
++	struct sk_buff *skb;
++	struct tlv *tlv;
++	int len = sizeof(hdr) + MT7996_MTK_BF_MAX_SIZE;
++
++	memset(&hdr, 0, sizeof(hdr));
++
++	skb = mt76_mcu_msg_alloc(&phy->dev->mt76, NULL, len);
++	if (!skb)
++		return -ENOMEM;
++
++	skb_put_data(skb, &hdr, sizeof(hdr));
++
++	recv_arg = sscanf(buf, "%hx:%hx:%hx:%hx:%hx", &input[0], &input[1], &input[2],
++						      &input[3], &input[4]);
++
++	if (!recv_arg)
++		return -EINVAL;
++
++	tlv = __mt7996_mcu_add_uni_tlv(skb, BF_TXSND_INFO, sizeof(*req));
++	req = (struct bf_txsnd_info *)tlv;
++	req->action = input[0];
++
++	switch (req->action) {
++	case BF_SND_READ_INFO: {
++		req->read_clr = input[1];
++		break;
++	}
++	case BF_SND_CFG_OPT: {
++		req->vht_opt = input[1];
++		req->he_opt = input[2];
++		req->glo_opt = input[3];
++		break;
++	}
++	case BF_SND_CFG_INTV: {
++		req->wlan_idx = input[1];
++		req->snd_intv = input[2];
++		break;
++	}
++	case BF_SND_STA_STOP: {
++		req->wlan_idx = input[1];
++		req->snd_stop = input[2];
++		break;
++	}
++	case BF_SND_CFG_MAX_STA: {
++		req->max_snd_stas = input[1];
++		break;
++	}
++	case BF_SND_CFG_BFRP: {
++		req->man = input[1];
++		req->tx_time = input[2];
++		req->mcs = input[3];
++		req->ldpc = input[4];
++		break;
++	}
++	case BF_SND_CFG_INF: {
++		req->inf = input[1];
++		break;
++	}
++	case BF_SND_CFG_TXOP_SND: {
++		req->man = input[1];
++		req->ac_queue = input[2];
++		req->sxn_protect = input[3];
++		req->direct_fbk = input[4];
++		break;
++	}
++	default:
++		return -EINVAL;
++	}
++
++	return mt76_mcu_skb_send_msg(&phy->dev->mt76, skb, MCU_WM_UNI_CMD(BF), 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;
++
++	dev_info(dev->mt76.dev, " bf_event tag = %d\n", event->tag);
++
++	switch (event->tag) {
++	case UNI_EVENT_BF_PFMU_TAG: {
++
++		struct mt7996_pfmu_tag_event *tag;
++		u32 *raw_t1, *raw_t2;
++
++		tag = (struct mt7996_pfmu_tag_event *) skb->data;
++
++		raw_t1 = (u32 *)&tag->t1;
++		raw_t2 = (u32 *)&tag->t2;
++
++		dev_info(dev->mt76.dev, "=================== TXBf Profile Tag1 Info ==================\n");
++		dev_info(dev->mt76.dev,
++			 "DW0 = 0x%08x, DW1 = 0x%08x, DW2 = 0x%08x\n",
++			 raw_t1[0], raw_t1[1], raw_t1[2]);
++		dev_info(dev->mt76.dev,
++			 "DW4 = 0x%08x, DW5 = 0x%08x, DW6 = 0x%08x\n\n",
++			 raw_t1[3], raw_t1[4], raw_t1[5]);
++		dev_info(dev->mt76.dev, "PFMU ID = %d              Invalid status = %d\n",
++			 tag->t1.pfmu_idx, tag->t1.invalid_prof);
++		dev_info(dev->mt76.dev, "iBf/eBf = %d\n\n", tag->t1.ebf);
++		dev_info(dev->mt76.dev, "DBW   = %d\n", tag->t1.data_bw);
++		dev_info(dev->mt76.dev, "SU/MU = %d\n", tag->t1.is_mu);
++		dev_info(dev->mt76.dev,
++			 "nrow = %d, ncol = %d, ng = %d, LM = %d, CodeBook = %d MobCalEn = %d\n",
++			 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) {
++			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 {
++			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);
++		dev_info(dev->mt76.dev, "Mem Col3 = %d, Mem Row3 = %d, Mem Col4 = %d, Mem Row4 = %d\n\n",
++			 tag->t1.col_id3, tag->t1.row_id3, tag->t1.col_id4, tag->t1.row_id4);
++		dev_info(dev->mt76.dev,
++			 "STS0_SNR = 0x%02x, STS1_SNR = 0x%02x, STS2_SNR = 0x%02x, STS3_SNR = 0x%02x\n",
++			 tag->t1.snr_sts0, tag->t1.snr_sts1, tag->t1.snr_sts2, tag->t1.snr_sts3);
++		dev_info(dev->mt76.dev,
++			 "STS4_SNR = 0x%02x, STS5_SNR = 0x%02x, STS6_SNR = 0x%02x, STS7_SNR = 0x%02x\n",
++			 tag->t1.snr_sts4, tag->t1.snr_sts5, tag->t1.snr_sts6, tag->t1.snr_sts7);
++		dev_info(dev->mt76.dev, "=============================================================\n");
++
++		dev_info(dev->mt76.dev, "=================== TXBf Profile Tag2 Info ==================\n");
++		dev_info(dev->mt76.dev,
++			 "DW0 = 0x%08x, DW1 = 0x%08x, DW2 = 0x%08x\n",
++			 raw_t2[0], raw_t2[1], raw_t2[2]);
++		dev_info(dev->mt76.dev,
++			 "DW3 = 0x%08x, DW4 = 0x%08x, DW5 = 0x%08x\n\n",
++			 raw_t2[3], raw_t2[4], raw_t2[5]);
++		dev_info(dev->mt76.dev, "Smart antenna ID = 0x%x,  SE index = %d\n",
++			 tag->t2.smart_ant, tag->t2.se_idx);
++		dev_info(dev->mt76.dev, "Timeout = 0x%x\n", tag->t2.ibf_timeout);
++		dev_info(dev->mt76.dev, "Desired BW = %d, Desired Ncol = %d, Desired Nrow = %d\n",
++			 tag->t2.ibf_data_bw, tag->t2.ibf_nc, tag->t2.ibf_nr);
++		dev_info(dev->mt76.dev, "Desired RU Allocation = %d\n", tag->t2.ibf_ru);
++		dev_info(dev->mt76.dev, "Mobility DeltaT = %d, Mobility LQ = %d\n",
++			 tag->t2.mob_delta_t, tag->t2.mob_lq_result);
++		dev_info(dev->mt76.dev, "=============================================================\n");
++		break;
++	}
++	case UNI_EVENT_BF_STAREC: {
++
++		struct mt7996_mcu_bf_starec_read *r;
++
++		r = (struct mt7996_mcu_bf_starec_read *)skb->data;
++		dev_info(dev->mt76.dev, "=================== BF StaRec ===================\n"
++					"rStaRecBf.u2PfmuId      = %d\n"
++					"rStaRecBf.fgSU_MU       = %d\n"
++					"rStaRecBf.u1TxBfCap     = %d\n"
++					"rStaRecBf.ucSoundingPhy = %d\n"
++					"rStaRecBf.ucNdpaRate    = %d\n"
++					"rStaRecBf.ucNdpRate     = %d\n"
++					"rStaRecBf.ucReptPollRate= %d\n"
++					"rStaRecBf.ucTxMode      = %d\n"
++					"rStaRecBf.ucNc          = %d\n"
++					"rStaRecBf.ucNr          = %d\n"
++					"rStaRecBf.ucCBW         = %d\n"
++					"rStaRecBf.ucMemRequire20M = %d\n"
++					"rStaRecBf.ucMemRow0     = %d\n"
++					"rStaRecBf.ucMemCol0     = %d\n"
++					"rStaRecBf.ucMemRow1     = %d\n"
++					"rStaRecBf.ucMemCol1     = %d\n"
++					"rStaRecBf.ucMemRow2     = %d\n"
++					"rStaRecBf.ucMemCol2     = %d\n"
++					"rStaRecBf.ucMemRow3     = %d\n"
++					"rStaRecBf.ucMemCol3     = %d\n",
++					r->pfmu_id,
++					r->is_su_mu,
++					r->txbf_cap,
++					r->sounding_phy,
++					r->ndpa_rate,
++					r->ndp_rate,
++					r->rpt_poll_rate,
++					r->tx_mode,
++					r->nc,
++					r->nr,
++					r->bw,
++					r->mem_require_20m,
++					r->mem_row0,
++					r->mem_col0,
++					r->mem_row1,
++					r->mem_col1,
++					r->mem_row2,
++					r->mem_col2,
++					r->mem_row3,
++					r->mem_col3);
++
++		dev_info(dev->mt76.dev, "rStaRecBf.u2SmartAnt    = 0x%x\n"
++					"rStaRecBf.ucSEIdx       = %d\n"
++					"rStaRecBf.uciBfTimeOut  = 0x%x\n"
++					"rStaRecBf.uciBfDBW      = %d\n"
++					"rStaRecBf.uciBfNcol     = %d\n"
++					"rStaRecBf.uciBfNrow     = %d\n"
++					"rStaRecBf.nr_bw160      = %d\n"
++					"rStaRecBf.nc_bw160 	  = %d\n"
++					"rStaRecBf.ru_start_idx  = %d\n"
++					"rStaRecBf.ru_end_idx 	  = %d\n"
++					"rStaRecBf.trigger_su 	  = %d\n"
++					"rStaRecBf.trigger_mu 	  = %d\n"
++					"rStaRecBf.ng16_su 	  = %d\n"
++					"rStaRecBf.ng16_mu 	  = %d\n"
++					"rStaRecBf.codebook42_su = %d\n"
++					"rStaRecBf.codebook75_mu = %d\n"
++					"rStaRecBf.he_ltf 	      = %d\n"
++					"======================================\n",
++					r->smart_ant,
++					r->se_idx,
++					r->bf_timeout,
++					r->bf_dbw,
++					r->bf_ncol,
++					r->bf_nrow,
++					r->nr_lt_bw80,
++					r->nc_lt_bw80,
++					r->ru_start_idx,
++					r->ru_end_idx,
++					r->trigger_su,
++					r->trigger_mu,
++					r->ng16_su,
++					r->ng16_mu,
++					r->codebook42_su,
++					r->codebook75_mu,
++					r->he_ltf);
++		break;
++	}
++	case UNI_EVENT_BF_FBK_INFO: {
++		struct mt7996_mcu_txbf_fbk_info *info;
++		__le32 total, i;
++
++		info = (struct mt7996_mcu_txbf_fbk_info *)skb->data;
++
++		total = info->u4PFMUWRDoneCnt + info->u4PFMUWRFailCnt;
++		total += info->u4PFMUWRTimeoutFreeCnt + info->u4FbRptPktDropCnt;
++
++		dev_info(dev->mt76.dev, "\n");
++		dev_info(dev->mt76.dev, "\x1b[32m =================================\x1b[m\n");
++		dev_info(dev->mt76.dev, "\x1b[32m PFMUWRDoneCnt              = %u\x1b[m\n",
++			info->u4PFMUWRDoneCnt);
++		dev_info(dev->mt76.dev, "\x1b[32m PFMUWRFailCnt              = %u\x1b[m\n",
++			info->u4PFMUWRFailCnt);
++		dev_info(dev->mt76.dev, "\x1b[32m PFMUWRTimeOutCnt           = %u\x1b[m\n",
++			info->u4PFMUWRTimeOutCnt);
++		dev_info(dev->mt76.dev, "\x1b[32m PFMUWRTimeoutFreeCnt       = %u\x1b[m\n",
++			info->u4PFMUWRTimeoutFreeCnt);
++		dev_info(dev->mt76.dev, "\x1b[32m FbRptPktDropCnt            = %u\x1b[m\n",
++			info->u4FbRptPktDropCnt);
++		dev_info(dev->mt76.dev, "\x1b[32m TotalFbRptPkt              = %u\x1b[m\n", total);
++		dev_info(dev->mt76.dev, "\x1b[32m PollPFMUIntrStatTimeOut    = %u(micro-sec)\x1b[m\n",
++			info->u4PollPFMUIntrStatTimeOut);
++		dev_info(dev->mt76.dev, "\x1b[32m FbRptDeQInterval           = %u(milli-sec)\x1b[m\n",
++			info->u4DeQInterval);
++		dev_info(dev->mt76.dev, "\x1b[32m PktCntInFbRptTimeOutQ      = %u\x1b[m\n",
++			info->u4RptPktTimeOutListNum);
++		dev_info(dev->mt76.dev, "\x1b[32m PktCntInFbRptQ             = %u\x1b[m\n",
++			info->u4RptPktListNum);
++
++		// [ToDo] Check if it is valid entry
++		for (i = 0; ((i < 5) && (i < CFG_BF_STA_REC_NUM)); i++) {
++
++			// [ToDo] AID needs to be refined
++			dev_info(dev->mt76.dev,"\x1b[32m AID%u  RxFbRptCnt           = %u\x1b[m\n"
++				, i, info->au4RxPerStaFbRptCnt[i]);
++		}
++
++		break;
++	}
++	case UNI_EVENT_BF_TXSND_INFO: {
++		struct mt7996_mcu_tx_snd_info *info;
++		struct uni_event_bf_txsnd_sta_info *snd_sta_info;
++		int Idx;
++		int max_wtbl_size = mt7996_wtbl_size(dev);
++
++		info = (struct mt7996_mcu_tx_snd_info *)skb->data;
++		dev_info(dev->mt76.dev, "=================== Global Setting ===================\n");
++
++		dev_info(dev->mt76.dev, "VhtOpt = 0x%02X, HeOpt = 0x%02X, GloOpt = 0x%02X\n",
++			info->vht_opt, info->he_opt, info->glo_opt);
++
++		for (Idx = 0; Idx < BF_SND_CTRL_STA_DWORD_CNT; Idx++) {
++			dev_info(dev->mt76.dev, "SuSta[%d] = 0x%08X,", Idx,
++				 info->snd_rec_su_sta[Idx]);
++			if ((Idx & 0x03) == 0x03)
++				dev_info(dev->mt76.dev, "\n");
++		}
++
++		if ((Idx & 0x03) != 0x03)
++			dev_info(dev->mt76.dev, "\n");
++
++
++		for (Idx = 0; Idx < BF_SND_CTRL_STA_DWORD_CNT; Idx++) {
++			dev_info(dev->mt76.dev, "VhtMuSta[%d] = 0x%08X,", Idx, info->snd_rec_vht_mu_sta[Idx]);
++			if ((Idx & 0x03) == 0x03)
++				dev_info(dev->mt76.dev, "\n");
++		}
++
++		if ((Idx & 0x03) != 0x03)
++			dev_info(dev->mt76.dev, "\n");
++
++		for (Idx = 0; Idx < BF_SND_CTRL_STA_DWORD_CNT; Idx++) {
++			dev_info(dev->mt76.dev, "HeTBSta[%d] = 0x%08X,", Idx, info->snd_rec_he_tb_sta[Idx]);
++			if ((Idx & 0x03) == 0x03)
++				dev_info(dev->mt76.dev, "\n");
++		}
++
++		if ((Idx & 0x03) != 0x03)
++			dev_info(dev->mt76.dev, "\n");
++
++		for (Idx = 0; Idx < BF_SND_CTRL_STA_DWORD_CNT; Idx++) {
++			dev_info(dev->mt76.dev, "EhtTBSta[%d] = 0x%08X,", Idx, info->snd_rec_eht_tb_sta[Idx]);
++			if ((Idx & 0x03) == 0x03)
++				dev_info(dev->mt76.dev, "\n");
++		}
++
++		if ((Idx & 0x03) != 0x03)
++			dev_info(dev->mt76.dev, "\n");
++
++		for (Idx = 0; Idx < CFG_WIFI_RAM_BAND_NUM; Idx++) {
++			dev_info(dev->mt76.dev, "Band%u:\n", Idx);
++			dev_info(dev->mt76.dev, "	 Wlan Idx For VHT MC Sounding = %u\n", info->wlan_idx_for_mc_snd[Idx]);
++			dev_info(dev->mt76.dev, "	 Wlan Idx For HE TB Sounding = %u\n", info->wlan_idx_for_he_tb_snd[Idx]);
++			dev_info(dev->mt76.dev, "	 Wlan Idx For EHT TB Sounding = %u\n", info->wlan_idx_for_eht_tb_snd[Idx]);
++		}
++
++		dev_info(dev->mt76.dev, "ULLen = %d, ULMcs = %d, ULLDCP = %d\n",
++			info->ul_length, info->mcs, info->ldpc);
++
++		dev_info(dev->mt76.dev, "=================== STA Info ===================\n");
++
++		for (Idx = 1; (Idx < 5 && (Idx < CFG_BF_STA_REC_NUM)); Idx++) {
++			snd_sta_info = &info->snd_sta_info[Idx];
++			dev_info(dev->mt76.dev, "Idx%2u Interval = %d, interval counter = %d, TxCnt = %d, StopReason = 0x%02X\n",
++				Idx,
++				snd_sta_info->snd_intv,
++				snd_sta_info->snd_intv_cnt,
++				snd_sta_info->snd_tx_cnt,
++				snd_sta_info->snd_stop_reason);
++		}
++
++		dev_info(dev->mt76.dev, "=================== STA Info Connected ===================\n");
++		// [ToDo] How to iterate and get AID info of station
++		// Check UniEventBFCtrlTxSndHandle() on Logan
++
++		//hardcode max_wtbl_size as 5
++		max_wtbl_size = 5;
++		for (Idx = 1; ((Idx < max_wtbl_size) && (Idx < CFG_BF_STA_REC_NUM)); Idx++) {
++
++			// [ToDo] We do not show AID info here
++			snd_sta_info = &info->snd_sta_info[Idx];
++			dev_info(dev->mt76.dev, " Interval = %d (%u ms), interval counter = %d (%u ms), TxCnt = %d, StopReason = 0x%02X\n",
++				snd_sta_info->snd_intv,
++				snd_sta_info->snd_intv * 10,
++				snd_sta_info->snd_intv_cnt,
++				snd_sta_info->snd_intv_cnt * 10,
++				snd_sta_info->snd_tx_cnt,
++				snd_sta_info->snd_stop_reason);
++		}
++
++		dev_info(dev->mt76.dev, "======================================\n");
++
++		break;
++	}
++	default:
++		dev_info(dev->mt76.dev, "%s: unknown bf event tag %d\n",
++			 __func__, event->tag);
++	}
++
++}
++
++
++int mt7996_mcu_set_muru_fixed_rate_enable(struct mt7996_dev *dev, u8 action, int val)
++{
++	struct {
++		u8 _rsv[4];
++
++		__le16 tag;
++		__le16 len;
++
++		__le16 value;
++		__le16 rsv;
++	} __packed data = {
++		.tag = cpu_to_le16(action),
++		.len = cpu_to_le16(sizeof(data) - 4),
++		.value = cpu_to_le16(!!val),
++	};
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(MURU), &data, sizeof(data),
++				 false);
++}
++
++int mt7996_mcu_set_muru_fixed_rate_parameter(struct mt7996_dev *dev, u8 action, void *para)
++{
++	char *buf = (char *)para;
++	u8 num_user = 0, recv_arg = 0, max_mcs = 0, usr_mcs[4] = {0};
++	__le16 bw;
++	int i;
++	struct {
++		u8 _rsv[4];
++
++		__le16 tag;
++		__le16 len;
++
++		u8 cmd_version;
++		u8 cmd_revision;
++		__le16 rsv;
++
++		struct uni_muru_mum_set_group_tbl_entry entry;
++	} __packed data = {
++		.tag = cpu_to_le16(action),
++		.len = cpu_to_le16(sizeof(data) - 4),
++	};
++
++#define __RUALLOC_TYPE_CHECK_HE(BW) ((BW == RUALLOC_BW20) || (BW == RUALLOC_BW40) || (BW == RUALLOC_BW80) || (BW == RUALLOC_BW160))
++#define __RUALLOC_TYPE_CHECK_EHT(BW) (__RUALLOC_TYPE_CHECK_HE(BW) || (BW == RUALLOC_BW320))
++	/* [Num of user] - 1~4
++	 * [RUAlloc] - BW320: 395, BW160: 137, BW80: 134, BW40: 130, BW20: 122
++	 * [LTF/GI] - For VHT, short GI: 0, Long GI: 1; 	 *
++	 * For HE/EHT, 4xLTF+3.2us: 0, 4xLTF+0.8us: 1, 2xLTF+0.8us:2
++	 * [Phy/FullBW] - VHT: 0 / HEFullBw: 1 / HEPartialBw: 2 / EHTFullBW: 3, EHTPartialBW: 4
++	 * [DL/UL] DL: 0, UL: 1, DL_UL: 2
++	 * [Wcid User0] - WCID 0
++	 * [MCS of WCID0] - For HE/VHT, 0-11: 1ss MCS0-MCS11, 12-23: 2SS MCS0-MCS11
++	 * For EHT, 0-13: 1ss MCS0-MCS13, 14-27: 2SS MCS0-MCS13
++	 * [WCID 1]
++	 * [MCS of WCID1]
++	 * [WCID 2]
++	 * [MCS of WCID2]
++	 * [WCID 3]
++	 * [MCS of WCID3]
++	 */
++
++	recv_arg = sscanf(buf, "%hhu %hu %hhu %hhu %hhu %hu %hhu %hu %hhu %hu %hhu %hu %hhu",
++			  &num_user, &bw, &data.entry.gi, &data.entry.capa, &data.entry.dl_ul,
++			  &data.entry.wlan_idx0, &usr_mcs[0],
++			  &data.entry.wlan_idx1, &usr_mcs[1],
++			  &data.entry.wlan_idx2, &usr_mcs[2],
++			  &data.entry.wlan_idx3, &usr_mcs[3]);
++
++	if (recv_arg != (5 + (2 * num_user))) {
++		dev_err(dev->mt76.dev, "The number of argument is invalid\n");
++		goto error;
++	}
++
++	if (num_user > 0 && num_user < 5)
++		data.entry.num_user = num_user - 1;
++	else {
++		dev_err(dev->mt76.dev, "The number of user count is invalid\n");
++		goto error;
++	}
++
++	/**
++	 * Older chip shall be set as HE. Refer to getHWSupportByChip() in Logan
++	 * driver to know the value for differnt chips
++	 */
++	data.cmd_version = UNI_CMD_MURU_VER_EHT;
++
++	if (data.cmd_version == UNI_CMD_MURU_VER_EHT)
++		max_mcs = UNI_MAX_MCS_SUPPORT_EHT;
++	else
++		max_mcs = UNI_MAX_MCS_SUPPORT_HE;
++
++
++	// Parameter Check
++	if (data.cmd_version != UNI_CMD_MURU_VER_EHT) {
++		if ((data.entry.capa > MAX_MODBF_HE) || (bw == RUALLOC_BW320))
++			goto error;
++	} else {
++		if ((data.entry.capa <= MAX_MODBF_HE) && (bw == RUALLOC_BW320))
++			goto error;
++	}
++
++	if (data.entry.capa <= MAX_MODBF_HE)
++		max_mcs = UNI_MAX_MCS_SUPPORT_HE;
++
++	if (__RUALLOC_TYPE_CHECK_EHT(bw)) {
++		data.entry.ru_alloc = (u8)(bw & 0xFF);
++		if (bw == RUALLOC_BW320)
++			data.entry.ru_alloc_ext = (u8)(bw >> 8);
++	} else {
++		dev_err(dev->mt76.dev, "RU_ALLOC argument is invalid\n");
++		goto error;
++	}
++
++	if ((data.entry.gi > 2) ||
++	    ((data.entry.gi > 1) && (data.entry.capa == MAX_MODBF_VHT))) {
++		dev_err(dev->mt76.dev, "GI argument is invalid\n");
++		goto error;
++	}
++
++	if (data.entry.dl_ul > 2) {
++		dev_err(dev->mt76.dev, "DL_UL argument is invalid\n");
++		goto error;
++	}
++
++#define __mcs_handler(_n)							\
++	do {									\
++		if (usr_mcs[_n] > max_mcs) {					\
++			usr_mcs[_n] -= (max_mcs + 1);				\
++			data.entry.nss##_n = 1;					\
++			if (usr_mcs[_n] > max_mcs)				\
++				usr_mcs[_n] = max_mcs;				\
++		}								\
++		if ((data.entry.dl_ul & 0x1) == 0)				\
++			data.entry.dl_mcs_user##_n = usr_mcs[_n];		\
++		if ((data.entry.dl_ul & 0x3) > 0)				\
++			data.entry.ul_mcs_user##_n = usr_mcs[_n];		\
++	}									\
++	while (0)
++
++	for (i=0; i<= data.entry.num_user; i++) {
++		switch (i) {
++			case 0:
++				__mcs_handler(0);
++				break;
++			case 1:
++				__mcs_handler(1);
++				break;
++			case 2:
++				__mcs_handler(2);
++				break;
++			case 3:
++				__mcs_handler(3);
++				break;
++			default:
++				break;
++		}
++	}
++#undef __mcs_handler
++
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(MURU), &data,
++				 sizeof(data), false);
++
++error:
++	dev_err(dev->mt76.dev, "Command failed!\n");
++	return -EINVAL;
++}
++
+ #endif
+diff --git a/mt7996/mtk_mcu.h b/mt7996/mtk_mcu.h
+index 098e63aef..27d6a05b8 100644
+--- a/mt7996/mtk_mcu.h
++++ b/mt7996/mtk_mcu.h
+@@ -119,6 +119,348 @@ enum {
+ 	EDCCA_FCC = 1,
+ 	EDCCA_ETSI = 2,
+ 	EDCCA_JAPAN = 3
++
++struct bf_pfmu_tag {
++	__le16 tag;
++	__le16 len;
++
++	u8 pfmu_id;
++	bool bfer;
++	u8 band_idx;
++	u8 __rsv[5];
++	u8 buf[56];
++} __packed;
++
++struct bf_starec_read {
++	__le16 tag;
++	__le16 len;
++
++	__le16 wlan_idx;
++	u8 __rsv[2];
++} __packed;
++
++struct bf_fbk_rpt_info {
++	__le16 tag;
++	__le16 len;
++
++	__le16 wlan_idx; // Only need for dynamic_pfmu_update 0x4
++	u8 action;
++	u8 band_idx;
++	u8 __rsv[4];
++
++} __packed;
++
++struct bf_txsnd_info {
++	__le16 tag;
++	__le16 len;
++
++	u8 action;
++	u8 read_clr;
++	u8 vht_opt;
++	u8 he_opt;
++	__le16 wlan_idx;
++	u8 glo_opt;
++	u8 snd_intv;
++	u8 snd_stop;
++	u8 max_snd_stas;
++	u8 tx_time;
++	u8 mcs;
++	u8 ldpc;
++	u8 inf;
++	u8 man;
++	u8 ac_queue;
++	u8 sxn_protect;
++	u8 direct_fbk;
++	u8 __rsv[2];
++} __packed;
++
++struct mt7996_mcu_bf_basic_event {
++	struct mt7996_mcu_rxd rxd;
++
++	u8 __rsv1[4];
++
++	__le16 tag;
++	__le16 len;
++};
++
++struct mt7996_mcu_bf_starec_read {
++
++	struct mt7996_mcu_bf_basic_event event;
++
++	__le16 pfmu_id;
++	bool is_su_mu;
++	u8 txbf_cap;
++	u8 sounding_phy;
++	u8 ndpa_rate;
++	u8 ndp_rate;
++	u8 rpt_poll_rate;
++	u8 tx_mode;
++	u8 nc;
++	u8 nr;
++	u8 bw;
++	u8 total_mem_require;
++	u8 mem_require_20m;
++	u8 mem_row0;
++	u8 mem_col0:6;
++	u8 mem_row0_msb:2;
++	u8 mem_row1;
++	u8 mem_col1:6;
++	u8 mem_row1_msb:2;
++	u8 mem_row2;
++	u8 mem_col2:6;
++	u8 mem_row2_msb:2;
++	u8 mem_row3;
++	u8 mem_col3:6;
++	u8 mem_row3_msb:2;
++
++	__le16 smart_ant;
++	u8 se_idx;
++	u8 auto_sounding_ctrl;
++
++	u8 bf_timeout;
++	u8 bf_dbw;
++	u8 bf_ncol;
++	u8 bf_nrow;
++
++	u8 nr_lt_bw80;
++	u8 nc_lt_bw80;
++	u8 ru_start_idx;
++	u8 ru_end_idx;
++
++	bool trigger_su;
++	bool trigger_mu;
++
++	bool ng16_su;
++	bool ng16_mu;
++
++	bool codebook42_su;
++	bool codebook75_mu;
++
++	u8 he_ltf;
++	u8 rsv[3];
++};
++
++#define TXBF_PFMU_ID_NUM_MAX 48
++
++#define TXBF_PFMU_ID_NUM_MAX_TBTC_BAND0 TXBF_PFMU_ID_NUM_MAX
++#define TXBF_PFMU_ID_NUM_MAX_TBTC_BAND1 TXBF_PFMU_ID_NUM_MAX
++#define TXBF_PFMU_ID_NUM_MAX_TBTC_BAND2 TXBF_PFMU_ID_NUM_MAX
++
++/* CFG_BF_STA_REC shall be varied based on BAND Num */
++#define CFG_BF_STA_REC_NUM (TXBF_PFMU_ID_NUM_MAX_TBTC_BAND0 + TXBF_PFMU_ID_NUM_MAX_TBTC_BAND1 + TXBF_PFMU_ID_NUM_MAX_TBTC_BAND2)
++
++#define BF_SND_CTRL_STA_DWORD_CNT   ((CFG_BF_STA_REC_NUM + 0x1F) >> 5)
++
++#ifndef ALIGN_4
++	#define ALIGN_4(_value)             (((_value) + 3) & ~3u)
++#endif /* ALIGN_4 */
++
++#define CFG_WIFI_RAM_BAND_NUM 3
++
++struct uni_event_bf_txsnd_sta_info {
++	u8 snd_intv;       /* Sounding interval upper bound, unit:15ms */
++	u8 snd_intv_cnt;   /* Sounding interval counter */
++	u8 snd_tx_cnt;     /* Tx sounding count for debug */
++	u8 snd_stop_reason;  /* Bitwise reason to put in Stop Queue */
++};
++
++struct mt7996_mcu_tx_snd_info {
++
++	struct mt7996_mcu_bf_basic_event event;
++
++	u8 vht_opt;
++	u8 he_opt;
++	u8 glo_opt;
++	u8 __rsv;
++	__le32 snd_rec_su_sta[BF_SND_CTRL_STA_DWORD_CNT];
++	__le32 snd_rec_vht_mu_sta[BF_SND_CTRL_STA_DWORD_CNT];
++	__le32 snd_rec_he_tb_sta[BF_SND_CTRL_STA_DWORD_CNT];
++	__le32 snd_rec_eht_tb_sta[BF_SND_CTRL_STA_DWORD_CNT];
++	__le16 wlan_idx_for_mc_snd[ALIGN_4(CFG_WIFI_RAM_BAND_NUM)];
++	__le16 wlan_idx_for_he_tb_snd[ALIGN_4(CFG_WIFI_RAM_BAND_NUM)];
++	__le16 wlan_idx_for_eht_tb_snd[ALIGN_4(CFG_WIFI_RAM_BAND_NUM)];
++	__le16 ul_length;
++	u8 mcs;
++	u8 ldpc;
++	struct uni_event_bf_txsnd_sta_info snd_sta_info[CFG_BF_STA_REC_NUM];
++};
++
++struct mt7996_mcu_txbf_fbk_info {
++
++	struct mt7996_mcu_bf_basic_event event;
++
++	__le32 u4DeQInterval;     /* By ms */
++	__le32 u4PollPFMUIntrStatTimeOut; /* micro-sec */
++	__le32 u4RptPktTimeOutListNum;
++	__le32 u4RptPktListNum;
++	__le32 u4PFMUWRTimeOutCnt;
++	__le32 u4PFMUWRFailCnt;
++	__le32 u4PFMUWRDoneCnt;
++	__le32 u4PFMUWRTimeoutFreeCnt;
++	__le32 u4FbRptPktDropCnt;
++	__le32 au4RxPerStaFbRptCnt[CFG_BF_STA_REC_NUM];
++};
++
++struct pfmu_ru_field {
++	__le32 ru_start_id:7;
++	__le32 _rsv1:1;
++	__le32 ru_end_id:7;
++	__le32 _rsv2:1;
++} __packed;
++
++struct pfmu_partial_bw_info {
++	__le32 partial_bw_info:9;
++	__le32 _rsv1:7;
++} __packed;
++
++struct mt7996_pfmu_tag1 {
++	__le32 pfmu_idx:10;
++	__le32 ebf:1;
++	__le32 data_bw:3;
++	__le32 lm:3;
++	__le32 is_mu:1;
++	__le32 nr:3;
++	__le32 nc:3;
++	__le32 codebook:2;
++	__le32 ngroup:2;
++	__le32 invalid_prof:1;
++	__le32 _rsv:3;
++
++	__le32 col_id1:7, row_id1:9;
++	__le32 col_id2:7, row_id2:9;
++	__le32 col_id3:7, row_id3:9;
++	__le32 col_id4:7, row_id4:9;
++
++	union {
++		struct pfmu_ru_field field;
++		struct pfmu_partial_bw_info bw_info;
++	};
++	__le32 mob_cal_en:1;
++	__le32 _rsv2:3;
++	__le32 mob_ru_alloc:9;	/* EHT profile uses full 9 bit */
++	__le32 _rsv3:3;
++
++	__le32 snr_sts0:8, snr_sts1:8, snr_sts2:8, snr_sts3:8;
++	__le32 snr_sts4:8, snr_sts5:8, snr_sts6:8, snr_sts7:8;
++
++	__le32 _rsv4;
++} __packed;
++
++struct mt7996_pfmu_tag2 {
++	__le32 smart_ant:24;
++	__le32 se_idx:5;
++	__le32 _rsv:3;
++
++	__le32 _rsv1:16;
++	__le32 ibf_timeout:8;
++	__le32 _rsv2:8;
++
++	__le32 ibf_data_bw:3;
++	__le32 ibf_nc:3;
++	__le32 ibf_nr:3;
++	__le32 ibf_ru:9;
++	__le32 _rsv3:14;
++
++	__le32 mob_delta_t:8;
++	__le32 mob_lq_result:7;
++	__le32 _rsv5:1;
++	__le32 _rsv6:16;
++
++	__le32 _rsv7;
++} __packed;
++
++struct mt7996_pfmu_tag_event {
++	struct mt7996_mcu_bf_basic_event event;
++
++	u8 bfer;
++	u8 __rsv[3];
++
++	struct mt7996_pfmu_tag1 t1;
++	struct mt7996_pfmu_tag2 t2;
++};
++
++enum {
++	UNI_EVENT_BF_PFMU_TAG = 0x5,
++	UNI_EVENT_BF_PFMU_DATA = 0x7,
++	UNI_EVENT_BF_STAREC = 0xB,
++	UNI_EVENT_BF_CAL_PHASE = 0xC,
++	UNI_EVENT_BF_FBK_INFO = 0x17,
++	UNI_EVENT_BF_TXSND_INFO = 0x18,
++	UNI_EVENT_BF_PLY_INFO = 0x19,
++	UNI_EVENT_BF_METRIC_INFO = 0x1A,
++	UNI_EVENT_BF_TXCMD_CFG_INFO = 0x1B,
++	UNI_EVENT_BF_SND_CNT_INFO = 0x1D,
++	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;
++	__le16 wlan_idx2;
++	__le16 wlan_idx3;
++
++	u8 dl_mcs_user0:4;
++	u8 dl_mcs_user1:4;
++	u8 dl_mcs_user2:4;
++	u8 dl_mcs_user3:4;
++	u8 ul_mcs_user0:4;
++	u8 ul_mcs_user1:4;
++	u8 ul_mcs_user2:4;
++	u8 ul_mcs_user3:4;
++
++	u8 num_user:2;
++	u8 rsv:6;
++	u8 nss0:2;
++	u8 nss1:2;
++	u8 nss2:2;
++	u8 nss3:2;
++	u8 ru_alloc;
++	u8 ru_alloc_ext;
++
++	u8 capa;
++	u8 gi;
++	u8 dl_ul;
++	u8 _rsv2;
++};
++
++enum UNI_CMD_MURU_VER_T {
++	UNI_CMD_MURU_VER_LEG = 0,
++	UNI_CMD_MURU_VER_HE,
++	UNI_CMD_MURU_VER_EHT,
++	UNI_CMD_MURU_VER_MAX
++};
++
++#define UNI_MAX_MCS_SUPPORT_HE 11
++#define UNI_MAX_MCS_SUPPORT_EHT 13
++
++enum {
++	RUALLOC_BW20 = 122,
++	RUALLOC_BW40 = 130,
++	RUALLOC_BW80 = 134,
++	RUALLOC_BW160 = 137,
++	RUALLOC_BW320 = 395,
++};
++
++enum {
++	MAX_MODBF_VHT = 0,
++	MAX_MODBF_HE = 2,
++	MAX_MODBF_EHT = 4,
++};
++
++enum {
++	BF_SND_READ_INFO = 0,
++	BF_SND_CFG_OPT,
++	BF_SND_CFG_INTV,
++	BF_SND_STA_STOP,
++	BF_SND_CFG_MAX_STA,
++	BF_SND_CFG_BFRP,
++	BF_SND_CFG_INF,
++	BF_SND_CFG_TXOP_SND
+ };
+ 
+ enum {
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0057-mtk-wifi-mt76-mt7996-add-build-the-following-MURU-mc.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0057-mtk-wifi-mt76-mt7996-add-build-the-following-MURU-mc.patch
new file mode 100644
index 0000000..fd0cb33
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0057-mtk-wifi-mt76-mt7996-add-build-the-following-MURU-mc.patch
@@ -0,0 +1,190 @@
+From 3a54f79020454e9d7632ad20b2635d1a67b302f9 Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Tue, 13 Jun 2023 14:49:02 +0800
+Subject: [PATCH 057/120] mtk: wifi: mt76: mt7996: add build the following MURU
+ mcu command tlvs
+
+It includes the following tlvs:
+1. MURU tlv id 0x10, 0x33, 0xC8, 0xC9, 0xCA, 0xCC, 0xCD
+2. BF tlv id 0x1c
+
+Change-Id: I0ae5cbed5b4370d39a6cfca50721873845659006
+---
+ mt7996/mcu.h         |  1 +
+ mt7996/mt7996.h      |  3 ++
+ mt7996/mtk_debugfs.c | 12 +++++++
+ mt7996/mtk_mcu.c     | 78 ++++++++++++++++++++++++++++++++++++++++++++
+ mt7996/mtk_mcu.h     | 14 ++++++++
+ 5 files changed, 108 insertions(+)
+
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 347893c8c..527c9c793 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -776,6 +776,7 @@ enum {
+ 	BF_MOD_EN_CTRL = 20,
+ 	BF_FBRPT_DBG_INFO_READ = 23,
+ 	BF_TXSND_INFO = 24,
++	BF_CFG_PHY = 28,
+ };
+ 
+ enum {
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index d012cc3ae..efa9a0f2f 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -816,6 +816,9 @@ 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);
+ int mt7996_mcu_set_txbf_snd_info(struct mt7996_phy *phy, void *para);
++int mt7996_mcu_set_muru_cmd(struct mt7996_dev *dev, u16 action, int val);
++int mt7996_mcu_muru_set_prot_frame_thr(struct mt7996_dev *dev, u32 val);
++int mt7996_mcu_set_bypass_smthint(struct mt7996_phy *phy, u8 val);
+ #endif
+ 
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index 2a17eafcd..9ea8fe33c 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -3005,6 +3005,16 @@ static const struct file_operations fops_muru_fixed_group_rate = {
+ 	.llseek = default_llseek,
+ };
+ 
++static int mt7996_muru_prot_thr_set(void *data, u64 val)
++{
++	struct mt7996_phy *phy = data;
++
++	return mt7996_mcu_muru_set_prot_frame_thr(phy->dev, (u32)val);
++}
++
++DEFINE_DEBUGFS_ATTRIBUTE(fops_muru_prot_thr, NULL,
++			 mt7996_muru_prot_thr_set, "%lld\n");
++
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -3100,6 +3110,8 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ 	debugfs_create_file("bf_fbk_rpt", 0600, dir, phy, &fops_bf_fbk_rpt);
+ 	debugfs_create_file("pfmu_tag_read", 0600, dir, phy, &fops_bf_pfmu_tag_read);
+ 
++	debugfs_create_file("muru_prot_thr", 0200, dir, phy, &fops_muru_prot_thr);
++
+ 	return 0;
+ }
+ 
+diff --git a/mt7996/mtk_mcu.c b/mt7996/mtk_mcu.c
+index 6b2cdad6f..686506234 100644
+--- a/mt7996/mtk_mcu.c
++++ b/mt7996/mtk_mcu.c
+@@ -904,4 +904,82 @@ error:
+ 	return -EINVAL;
+ }
+ 
++/**
++ * This function can be used to build the following commands
++ * MURU_SUTX_CTRL (0x10)
++ * SET_FORCE_MU (0x33)
++ * SET_MUDL_ACK_POLICY (0xC8)
++ * SET_TRIG_TYPE (0xC9)
++ * SET_20M_DYN_ALGO (0xCA)
++ * SET_CERT_MU_EDCA_OVERRIDE (0xCD)
++ */
++int mt7996_mcu_set_muru_cmd(struct mt7996_dev *dev, u16 action, int val)
++{
++	struct {
++		u8 _rsv[4];
++
++		__le16 tag;
++		__le16 len;
++
++		u8 config;
++		u8 rsv[3];
++	} __packed data = {
++		.tag = cpu_to_le16(action),
++		.len = cpu_to_le16(sizeof(data) - 4),
++		.config = (u8) val,
++	};
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(MURU), &data, sizeof(data),
++				 false);
++}
++
++int mt7996_mcu_muru_set_prot_frame_thr(struct mt7996_dev *dev, u32 val)
++{
++	struct {
++		u8 _rsv[4];
++
++		__le16 tag;
++		__le16 len;
++
++		__le32 prot_frame_thr;
++	} __packed data = {
++		.tag = cpu_to_le16(UNI_CMD_MURU_PROT_FRAME_THR),
++		.len = cpu_to_le16(sizeof(data) - 4),
++		.prot_frame_thr = cpu_to_le32(val),
++	};
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(MURU), &data, sizeof(data),
++				 false);
++}
++
++int mt7996_mcu_set_bypass_smthint(struct mt7996_phy *phy, u8 val)
++{
++#define BF_PHY_SMTH_INT_BYPASS 0
++#define BYPASS_VAL 1
++	struct mt7996_dev *dev = phy->dev;
++	struct {
++		u8 _rsv[4];
++
++		u16 tag;
++		u16 len;
++
++		u8 action;
++		u8 band_idx;
++		u8 smthintbypass;
++		u8 __rsv2[5];
++	} __packed data = {
++		.tag = cpu_to_le16(BF_CFG_PHY),
++		.len = cpu_to_le16(sizeof(data) - 4),
++		.action = BF_PHY_SMTH_INT_BYPASS,
++		.band_idx = phy->mt76->band_idx,
++		.smthintbypass = val,
++	};
++
++	if (val != BYPASS_VAL)
++		return -EINVAL;
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(BF), &data, sizeof(data),
++				 true);
++}
++
+ #endif
+diff --git a/mt7996/mtk_mcu.h b/mt7996/mtk_mcu.h
+index 27d6a05b8..d9686ebb5 100644
+--- a/mt7996/mtk_mcu.h
++++ b/mt7996/mtk_mcu.h
+@@ -119,6 +119,20 @@ enum {
+ 	EDCCA_FCC = 1,
+ 	EDCCA_ETSI = 2,
+ 	EDCCA_JAPAN = 3
++};
++
++enum {
++	UNI_CMD_MURU_SUTX_CTRL = 0x10,
++	UNI_CMD_MURU_FIXED_RATE_CTRL,
++	UNI_CMD_MURU_FIXED_GROUP_RATE_CTRL,
++	UNI_CMD_MURU_SET_FORCE_MU = 0x33,
++	UNI_CMD_MURU_MUNUAL_CONFIG = 0x64,
++	UNI_CMD_MURU_SET_MUDL_ACK_POLICY = 0xC9,
++	UNI_CMD_MURU_SET_TRIG_TYPE,
++	UNI_CMD_MURU_SET_20M_DYN_ALGO,
++	UNI_CMD_MURU_PROT_FRAME_THR = 0xCC,
++	UNI_CMD_MURU_SET_CERT_MU_EDCA_OVERRIDE,
++};
+ 
+ struct bf_pfmu_tag {
+ 	__le16 tag;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0058-mtk-wifi-mt76-mt7996-add-cert-patch.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0058-mtk-wifi-mt76-mt7996-add-cert-patch.patch
new file mode 100644
index 0000000..f3002f4
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0058-mtk-wifi-mt76-mt7996-add-cert-patch.patch
@@ -0,0 +1,1095 @@
+From fdbe349177075901b09a5954531a7b8faffb18eb Mon Sep 17 00:00:00 2001
+From: MeiChia Chiu <meichia.chiu@mediatek.com>
+Date: Mon, 14 Aug 2023 13:36:58 +0800
+Subject: [PATCH 058/120] mtk: wifi: mt76: mt7996: add cert patch
+
+This patch includes TGac and TGax
+
+Commit histroy:
+
+Add vendor cmd set ap wireless rts_sigta support
+
+CR-ID: WCNCR00348946
+Signed-off-by: ye he <ye.he@mediatek.com>
+---
+ mt7996/mac.c     |   9 ++
+ mt7996/main.c    |  31 ++++++-
+ mt7996/mcu.c     |  40 +++++++++
+ mt7996/mcu.h     |   6 ++
+ mt7996/mt7996.h  |  13 +++
+ mt7996/mtk_mcu.c | 205 ++++++++++++++++++++++++++++++++++++++++++
+ mt7996/mtk_mcu.h | 184 +++++++++++++++++++++++++++++++++++--
+ mt7996/vendor.c  | 230 ++++++++++++++++++++++++++++++++++++++++++++++-
+ mt7996/vendor.h  |  67 ++++++++++++++
+ 9 files changed, 778 insertions(+), 7 deletions(-)
+
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 161639d4d..9edccfed8 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -10,6 +10,7 @@
+ #include "../dma.h"
+ #include "mac.h"
+ #include "mcu.h"
++#include "vendor.h"
+ 
+ #define to_rssi(field, rcpi)	((FIELD_GET(field, rcpi) - 220) / 2)
+ 
+@@ -2281,6 +2282,14 @@ void mt7996_mac_update_stats(struct mt7996_phy *phy)
+ 	}
+ }
+ 
++void mt7996_set_wireless_amsdu(struct ieee80211_hw *hw, u8 en)
++{
++	if (en)
++		ieee80211_hw_set(hw, SUPPORTS_AMSDU_IN_AMPDU);
++	else
++		ieee80211_hw_clear(hw, SUPPORTS_AMSDU_IN_AMPDU);
++}
++
+ void mt7996_mac_sta_rc_work(struct work_struct *work)
+ {
+ 	struct mt7996_dev *dev = container_of(work, struct mt7996_dev, rc_work);
+diff --git a/mt7996/main.c b/mt7996/main.c
+index a75f06f74..8f0674b37 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -600,6 +600,7 @@ mt7996_get_rates_table(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 		       bool beacon, bool mcast)
+ {
+ 	struct mt76_vif *mvif = (struct mt76_vif *)vif->drv_priv;
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt76_phy *mphy = hw->priv;
+ 	u16 rate;
+ 	u8 i, idx;
+@@ -609,6 +610,9 @@ mt7996_get_rates_table(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	if (beacon) {
+ 		struct mt7996_phy *phy = mphy->priv;
+ 
++		if (dev->cert_mode && phy->mt76->band_idx == MT_BAND2)
++			rate = 0x0200;
++
+ 		/* odd index for driver, even index for firmware */
+ 		idx = MT7996_BEACON_RATES_TBL + 2 * phy->mt76->band_idx;
+ 		if (phy->beacon_rate != rate)
+@@ -736,6 +740,10 @@ int mt7996_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	u8 band_idx = mvif->phy->mt76->band_idx;
+ 	int ret, idx;
+ 
++#ifdef CONFIG_MTK_VENDOR
++	struct mt7996_phy *phy = &dev->phy;
++#endif
++
+ 	idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT7996_WTBL_STA);
+ 	if (idx < 0)
+ 		return -ENOSPC;
+@@ -761,7 +769,28 @@ int mt7996_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	if (ret)
+ 		return ret;
+ 
+-	return mt7996_mcu_add_rate_ctrl(dev, vif, sta, false);
++	ret = mt7996_mcu_add_rate_ctrl(dev, vif, sta, false);
++	if (ret)
++		return ret;
++
++#ifdef CONFIG_MTK_VENDOR
++	switch (band_idx) {
++	case MT_BAND1:
++		phy = mt7996_phy2(dev);
++		break;
++	case MT_BAND2:
++		phy = mt7996_phy3(dev);
++		break;
++	case MT_BAND0:
++	default:
++		break;
++	}
++
++	if (phy && phy->muru_onoff & MUMIMO_DL_CERT)
++		mt7996_mcu_set_mimo(phy);
++#endif
++
++	return 0;
+ }
+ 
+ void mt7996_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 97142c5ef..e26ba59ac 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -1352,6 +1352,10 @@ mt7996_mcu_sta_vht_tlv(struct sk_buff *skb, struct ieee80211_sta *sta)
+ {
+ 	struct sta_rec_vht *vht;
+ 	struct tlv *tlv;
++#ifdef CONFIG_MTK_VENDOR
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_phy *phy = (struct mt7996_phy *)msta->vif->phy;
++#endif
+ 
+ 	/* For 6G band, this tlv is necessary to let hw work normally */
+ 	if (!sta->deflink.he_6ghz_capa.capa && !sta->deflink.vht_cap.vht_supported)
+@@ -1363,6 +1367,9 @@ mt7996_mcu_sta_vht_tlv(struct sk_buff *skb, struct ieee80211_sta *sta)
+ 	vht->vht_cap = cpu_to_le32(sta->deflink.vht_cap.cap);
+ 	vht->vht_rx_mcs_map = sta->deflink.vht_cap.vht_mcs.rx_mcs_map;
+ 	vht->vht_tx_mcs_map = sta->deflink.vht_cap.vht_mcs.tx_mcs_map;
++#ifdef CONFIG_MTK_VENDOR
++	vht->rts_bw_sig = phy->rts_bw_sig;
++#endif
+ }
+ 
+ static void
+@@ -4455,6 +4462,27 @@ int mt7996_mcu_set_rts_thresh(struct mt7996_phy *phy, u32 val)
+ 				 &req, sizeof(req), true);
+ }
+ 
++int mt7996_mcu_set_band_confg(struct mt7996_phy *phy, u16 option, bool enable)
++{
++	struct {
++		u8 band_idx;
++		u8 _rsv[3];
++
++		__le16 tag;
++		__le16 len;
++		bool enable;
++		u8 _rsv2[3];
++	} __packed req = {
++		.band_idx = phy->mt76->band_idx,
++		.tag = cpu_to_le16(option),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.enable = enable,
++	};
++
++	return mt76_mcu_send_msg(&phy->dev->mt76, MCU_WM_UNI_CMD(BAND_CONFIG),
++				 &req, sizeof(req), true);
++}
++
+ int mt7996_mcu_set_radio_en(struct mt7996_phy *phy, bool enable)
+ {
+ 	struct {
+@@ -4983,6 +5011,18 @@ void mt7996_set_wireless_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
+ 	val = FIELD_GET(RATE_CFG_VAL, *((u32 *)data));
+ 
+ 	switch (mode) {
++	case RATE_PARAM_FIXED_OFDMA:
++		if (val == 3)
++			phy->muru_onoff = OFDMA_DL;
++		else
++			phy->muru_onoff = val;
++		break;
++	case RATE_PARAM_FIXED_MIMO:
++		if (val == 0)
++			phy->muru_onoff = MUMIMO_DL_CERT | MUMIMO_DL;
++		else
++			phy->muru_onoff = MUMIMO_UL;
++		break;
+ 	case RATE_PARAM_AUTO_MU:
+ 		if (val < 0 || val > 15) {
+ 			printk("Wrong value! The value is between 0-15.\n");
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 527c9c793..af078edda 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -755,6 +755,8 @@ enum {
+ 	RATE_PARAM_FIXED_GI = 11,
+ 	RATE_PARAM_AUTO = 20,
+ #ifdef CONFIG_MTK_VENDOR
++	RATE_PARAM_FIXED_MIMO = 30,
++	RATE_PARAM_FIXED_OFDMA = 31,
+ 	RATE_PARAM_AUTO_MU = 32,
+ #endif
+ };
+@@ -767,6 +769,7 @@ enum {
+ #define OFDMA_UL                       BIT(1)
+ #define MUMIMO_DL                      BIT(2)
+ #define MUMIMO_UL                      BIT(3)
++#define MUMIMO_DL_CERT                 BIT(4)
+ 
+ enum {
+ 	BF_SOUNDING_ON = 1,
+@@ -853,11 +856,14 @@ enum {
+ 	UNI_BAND_CONFIG_EDCCA_ENABLE = 0x05,
+ 	UNI_BAND_CONFIG_EDCCA_THRESHOLD = 0x06,
+ 	UNI_BAND_CONFIG_RTS_THRESHOLD = 0x08,
++	UNI_BAND_CONFIG_RTS_SIGTA_EN = 0x09,
++	UNI_BAND_CONFIG_DIS_SECCH_CCA_DET = 0x0a,
+ };
+ 
+ enum {
+ 	UNI_WSYS_CONFIG_FW_LOG_CTRL,
+ 	UNI_WSYS_CONFIG_FW_DBG_CTRL,
++	UNI_CMD_CERT_CFG = 6,
+ };
+ 
+ enum {
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index efa9a0f2f..f4db34e8f 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -349,6 +349,7 @@ struct mt7996_phy {
+ 	} test;
+ #endif
+ #ifdef CONFIG_MTK_VENDOR
++	u8 rts_bw_sig;
+ 	spinlock_t amnt_lock;
+ 	struct mt7996_air_monitor_ctrl amnt_ctrl;
+ #endif
+@@ -476,6 +477,9 @@ struct mt7996_dev {
+ 	} dbg;
+ 	const struct mt7996_dbg_reg_desc *dbg_reg;
+ #endif
++#ifdef CONFIG_MTK_VENDOR
++	bool cert_mode;
++#endif
+ };
+ 
+ enum {
+@@ -673,6 +677,7 @@ void mt7996_tm_rf_test_event(struct mt7996_dev *dev, struct sk_buff *skb);
+ int mt7996_mcu_get_tx_power_info(struct mt7996_phy *phy, u8 category, void *event);
+ int mt7996_mcu_set_scs(struct mt7996_phy *phy, u8 enable);
+ void mt7996_mcu_scs_sta_poll(struct work_struct *work);
++int mt7996_mcu_set_band_confg(struct mt7996_phy *phy, u16 option, bool enable);
+ 
+ static inline u8 mt7996_max_interface_num(struct mt7996_dev *dev)
+ {
+@@ -792,6 +797,10 @@ void mt7996_vendor_register(struct mt7996_phy *phy);
+ void mt7996_vendor_amnt_fill_rx(struct mt7996_phy *phy, struct sk_buff *skb);
+ int mt7996_vendor_amnt_sta_remove(struct mt7996_phy *phy,
+ 				  struct ieee80211_sta *sta);
++void mt7996_set_wireless_amsdu(struct ieee80211_hw *hw, u8 en);
++void mt7996_mcu_set_mimo(struct mt7996_phy *phy);
++int mt7996_set_muru_cfg(struct mt7996_phy *phy, u8 action, u8 val);
++int mt7996_mcu_set_muru_cfg(struct mt7996_phy *phy, void *data);
+ #endif
+ 
+ int mt7996_mcu_edcca_enable(struct mt7996_phy *phy, bool enable);
+@@ -819,6 +828,10 @@ int mt7996_mcu_set_txbf_snd_info(struct mt7996_phy *phy, void *para);
+ int mt7996_mcu_set_muru_cmd(struct mt7996_dev *dev, u16 action, int val);
+ int mt7996_mcu_muru_set_prot_frame_thr(struct mt7996_dev *dev, u32 val);
+ int mt7996_mcu_set_bypass_smthint(struct mt7996_phy *phy, u8 val);
++int mt7996_mcu_set_rfeature_trig_type(struct mt7996_phy *phy, u8 enable, u8 trig_type);
++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);
+ #endif
+ 
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
+diff --git a/mt7996/mtk_mcu.c b/mt7996/mtk_mcu.c
+index 686506234..b67d366d4 100644
+--- a/mt7996/mtk_mcu.c
++++ b/mt7996/mtk_mcu.c
+@@ -982,4 +982,209 @@ int mt7996_mcu_set_bypass_smthint(struct mt7996_phy *phy, u8 val)
+ 				 true);
+ }
+ 
++int mt7996_mcu_set_bsrp_ctrl(struct mt7996_phy *phy, u16 interval,
++			     u16 ru_alloc, u32 trig_type, u8 trig_flow, u8 ext_cmd)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct {
++		u8 _rsv[4];
++
++		__le16 tag;
++		__le16 len;
++
++		__le16 interval;
++		__le16 ru_alloc;
++		__le32 trigger_type;
++		u8 trigger_flow;
++		u8 ext_cmd_bsrp;
++		u8 band_bitmap;
++		u8 _rsv2;
++	} __packed req = {
++		.tag = cpu_to_le16(UNI_CMD_MURU_BSRP_CTRL),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.interval = cpu_to_le16(interval),
++		.ru_alloc = cpu_to_le16(ru_alloc),
++		.trigger_type = cpu_to_le32(trig_type),
++		.trigger_flow = trig_flow,
++		.ext_cmd_bsrp = ext_cmd,
++		.band_bitmap = BIT(phy->mt76->band_idx),
++	};
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(MURU), &req,
++				 sizeof(req), false);
++}
++
++int mt7996_mcu_set_rfeature_trig_type(struct mt7996_phy *phy, u8 enable, u8 trig_type)
++{
++	struct mt7996_dev *dev = phy->dev;
++	int ret = 0;
++	char buf[] = "01:00:00:1B";
++
++	if (enable) {
++		ret = mt7996_mcu_set_muru_cmd(dev, UNI_CMD_MURU_SET_TRIG_TYPE, trig_type);
++		if (ret)
++			return ret;
++	}
++
++	switch (trig_type) {
++	case CAPI_BASIC:
++		return mt7996_mcu_set_bsrp_ctrl(phy, 5, 67, 0, 0, enable);
++	case CAPI_BRP:
++		return mt7996_mcu_set_txbf_snd_info(phy, buf);
++	case CAPI_MU_BAR:
++		return mt7996_mcu_set_muru_cmd(dev, UNI_CMD_MURU_SET_MUDL_ACK_POLICY,
++					       MU_DL_ACK_POLICY_MU_BAR);
++	case CAPI_BSRP:
++		return mt7996_mcu_set_bsrp_ctrl(phy, 5, 67, 4, 0, enable);
++	default:
++		return 0;
++	}
++}
++
++int mt7996_mcu_set_muru_cfg(struct mt7996_phy *phy, void *data)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_muru *muru;
++	struct {
++		u8 _rsv[4];
++
++		__le16 tag;
++		__le16 len;
++
++		u8 version;
++		u8 revision;
++		u8 _rsv2[2];
++
++		struct mt7996_muru muru;
++	} __packed req = {
++		.tag = cpu_to_le16(UNI_CMD_MURU_MUNUAL_CONFIG),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.version = UNI_CMD_MURU_VER_EHT,
++	};
++
++	muru = (struct mt7996_muru *) data;
++	memcpy(&req.muru, muru, sizeof(struct mt7996_muru));
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(MURU), &req,
++				 sizeof(req), false);
++}
++
++int mt7996_set_muru_cfg(struct mt7996_phy *phy, u8 action, u8 val)
++{
++	struct mt7996_muru *muru;
++	struct mt7996_muru_dl *dl;
++	struct mt7996_muru_ul *ul;
++	struct mt7996_muru_comm *comm;
++	int ret = 0;
++
++	muru = kzalloc(sizeof(struct mt7996_muru), GFP_KERNEL);
++	dl = &muru->dl;
++	ul = &muru->ul;
++	comm = &muru->comm;
++
++	switch (action) {
++	case MU_CTRL_DL_USER_CNT:
++		dl->user_num = val;
++		comm->ppdu_format = MURU_PPDU_HE_MU;
++		comm->sch_type = MURU_OFDMA_SCH_TYPE_DL;
++		muru->cfg_comm = cpu_to_le32(MURU_COMM_SET);
++		muru->cfg_dl = cpu_to_le32(MURU_FIXED_DL_TOTAL_USER_CNT);
++		ret = mt7996_mcu_set_muru_cfg(phy, muru);
++		break;
++	case MU_CTRL_UL_USER_CNT:
++		ul->user_num = val;
++		comm->ppdu_format = MURU_PPDU_HE_TRIG;
++		comm->sch_type = MURU_OFDMA_SCH_TYPE_UL;
++		muru->cfg_comm = cpu_to_le32(MURU_COMM_SET);
++		muru->cfg_ul = cpu_to_le32(MURU_FIXED_UL_TOTAL_USER_CNT);
++		ret = mt7996_mcu_set_muru_cfg(phy, muru);
++		break;
++	default:
++		break;
++	}
++
++	kfree(muru);
++	return ret;
++}
++
++void mt7996_mcu_set_ppdu_tx_type(struct mt7996_phy *phy, u8 ppdu_type)
++{
++	struct mt7996_dev *dev = phy->dev;
++	int enable_su;
++
++	switch (ppdu_type) {
++	case CAPI_SU:
++		enable_su = 1;
++		mt7996_mcu_set_muru_cmd(dev, UNI_CMD_MURU_SUTX_CTRL, enable_su);
++		mt7996_set_muru_cfg(phy, MU_CTRL_DL_USER_CNT, 0);
++		break;
++	case CAPI_MU:
++		enable_su = 0;
++		mt7996_mcu_set_muru_cmd(dev, UNI_CMD_MURU_SUTX_CTRL, enable_su);
++		break;
++	default:
++		break;
++	}
++}
++
++void mt7996_mcu_set_nusers_ofdma(struct mt7996_phy *phy, u8 type, u8 user_cnt)
++{
++	struct mt7996_dev *dev = phy->dev;
++	int enable_su = 0;
++
++	mt7996_mcu_set_muru_cmd(dev, UNI_CMD_MURU_SUTX_CTRL, enable_su);
++	mt7996_mcu_set_muru_cmd(dev, UNI_CMD_MURU_SET_MUDL_ACK_POLICY, MU_DL_ACK_POLICY_SU_BAR);
++	mt7996_mcu_muru_set_prot_frame_thr(dev, 9999);
++
++	mt7996_set_muru_cfg(phy, type, user_cnt);
++}
++
++void mt7996_mcu_set_mimo(struct mt7996_phy *phy)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct cfg80211_chan_def *chandef = &phy->mt76->chandef;
++	int disable_ra = 1;
++	char buf[] = "2 134 0 1 0 1 2 2 2";
++	int force_mu = 1;
++
++	switch (chandef->width) {
++	case NL80211_CHAN_WIDTH_20_NOHT:
++	case NL80211_CHAN_WIDTH_20:
++		strscpy(buf, "2 122 0 1 0 1 2 2 2", sizeof(buf));
++		break;
++	case NL80211_CHAN_WIDTH_80:
++		break;
++	case NL80211_CHAN_WIDTH_160:
++		strscpy(buf, "2 137 0 1 0 1 2 2 2", sizeof(buf));
++		break;
++	default:
++		break;
++	}
++
++	mt7996_mcu_set_muru_cmd(dev, UNI_CMD_MURU_SET_MUDL_ACK_POLICY, MU_DL_ACK_POLICY_SU_BAR);
++	mt7996_mcu_set_muru_fixed_rate_enable(dev, UNI_CMD_MURU_FIXED_RATE_CTRL, disable_ra);
++	mt7996_mcu_set_muru_fixed_rate_parameter(dev, UNI_CMD_MURU_FIXED_GROUP_RATE_CTRL, buf);
++	mt7996_mcu_set_muru_cmd(dev, UNI_CMD_MURU_SET_FORCE_MU, force_mu);
++}
++
++void mt7996_mcu_set_cert(struct mt7996_phy *phy, u8 type)
++{
++	struct mt7996_dev *dev = phy->dev;
++	struct {
++		u8 _rsv[4];
++
++		__le16 tag;
++		__le16 len;
++		u8 action;
++		u8 _rsv2[3];
++	} __packed req = {
++		.tag = cpu_to_le16(UNI_CMD_CERT_CFG),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.action = type, /* 1: CAPI Enable */
++	};
++
++	mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(WSYS_CONFIG), &req,
++			  sizeof(req), false);
++}
++
+ #endif
+diff --git a/mt7996/mtk_mcu.h b/mt7996/mtk_mcu.h
+index d9686ebb5..7a4140b57 100644
+--- a/mt7996/mtk_mcu.h
++++ b/mt7996/mtk_mcu.h
+@@ -122,14 +122,15 @@ enum {
+ };
+ 
+ enum {
++	UNI_CMD_MURU_BSRP_CTRL = 0x01,
+ 	UNI_CMD_MURU_SUTX_CTRL = 0x10,
+-	UNI_CMD_MURU_FIXED_RATE_CTRL,
+-	UNI_CMD_MURU_FIXED_GROUP_RATE_CTRL,
++	UNI_CMD_MURU_FIXED_RATE_CTRL = 0x11,
++	UNI_CMD_MURU_FIXED_GROUP_RATE_CTRL = 0x12,
+ 	UNI_CMD_MURU_SET_FORCE_MU = 0x33,
+ 	UNI_CMD_MURU_MUNUAL_CONFIG = 0x64,
+-	UNI_CMD_MURU_SET_MUDL_ACK_POLICY = 0xC9,
+-	UNI_CMD_MURU_SET_TRIG_TYPE,
+-	UNI_CMD_MURU_SET_20M_DYN_ALGO,
++	UNI_CMD_MURU_SET_MUDL_ACK_POLICY = 0xC8,
++	UNI_CMD_MURU_SET_TRIG_TYPE = 0xC9,
++	UNI_CMD_MURU_SET_20M_DYN_ALGO = 0xCA,
+ 	UNI_CMD_MURU_PROT_FRAME_THR = 0xCC,
+ 	UNI_CMD_MURU_SET_CERT_MU_EDCA_OVERRIDE,
+ };
+@@ -533,6 +534,179 @@ struct mt7996_mcu_sr_hw_ind_event {
+ 	__le32 sr_ampdu_mpdu_cnt;
+ 	__le32 sr_ampdu_mpdu_acked_cnt;
+ };
++
++struct mt7996_muru_comm {
++	u8 pda_pol;
++	u8 band;
++	u8 spe_idx;
++	u8 proc_type;
++
++	__le16 mlo_ctrl;
++	u8 sch_type;
++	u8 ppdu_format;
++	u8 ac;
++	u8 _rsv[3];
++};
++
++struct mt7996_muru_dl {
++	u8 user_num;
++	u8 tx_mode;
++	u8 bw;
++	u8 gi;
++
++	u8 ltf;
++	u8 mcs;
++	u8 dcm;
++	u8 cmprs;
++
++	__le16 ru[16];
++
++	u8 c26[2];
++	u8 ack_policy;
++	u8 tx_power;
++
++	__le16 mu_ppdu_duration;
++	u8 agc_disp_order;
++	u8 _rsv1;
++
++	u8 agc_disp_pol;
++	u8 agc_disp_ratio;
++	__le16 agc_disp_linkMFG;
++
++	__le16 prmbl_punc_bmp;
++	u8 _rsv2[2];
++
++	struct {
++		__le16 wlan_idx;
++		u8 ru_alloc_seg;
++		u8 ru_idx;
++		u8 ldpc;
++		u8 nss;
++		u8 mcs;
++		u8 mu_group_idx;
++		u8 vht_groud_id;
++		u8 vht_up;
++		u8 he_start_stream;
++		u8 he_mu_spatial;
++		__le16 tx_power_alpha;
++		u8 ack_policy;
++		u8 ru_allo_ps160;
++	} usr[16];
++};
++
++struct mt7996_muru_ul {
++	u8 user_num;
++	u8 tx_mode;
++
++	u8 ba_type;
++	u8 _rsv;
++
++	u8 bw;
++	u8 gi_ltf;
++	__le16 ul_len;
++
++	__le16 trig_cnt;
++	u8 pad;
++	u8 trig_type;
++
++	__le16 trig_intv;
++	u8 trig_ta[ETH_ALEN];
++	__le16 ul_ru[16];
++
++	u8 c26[2];
++	__le16 agc_disp_linkMFG;
++
++	u8 agc_disp_mu_len;
++	u8 agc_disp_pol;
++	u8 agc_disp_ratio;
++	u8 agc_disp_pu_idx;
++
++	struct {
++		__le16 wlan_idx;
++		u8 ru_alloc_seg;
++		u8 ru_idx;
++		u8 ldpc;
++		u8 nss;
++		u8 mcs;
++		u8 target_rssi;
++		__le32 trig_pkt_size;
++		u8 ru_allo_ps160;
++		u8 _rsv2[3];
++	} usr[16];
++};
++
++struct mt7996_muru_dbg {
++	/* HE TB RX Debug */
++	__le32 rx_hetb_nonsf_en_bitmap;
++	__le32 rx_hetb_cfg[2];
++};
++
++struct mt7996_muru {
++	__le32 cfg_comm;
++	__le32 cfg_dl;
++	__le32 cfg_ul;
++	__le32 cfg_dbg;
++
++	struct mt7996_muru_comm comm;
++	struct mt7996_muru_dl dl;
++	struct mt7996_muru_ul ul;
++	struct mt7996_muru_dbg dbg;
++};
++
++
++#define MURU_PPDU_HE_TRIG	BIT(2)
++#define MURU_PPDU_HE_MU		BIT(3)
++
++#define MURU_OFDMA_SCH_TYPE_DL	BIT(0)
++#define MURU_OFDMA_SCH_TYPE_UL	BIT(1)
++
++/* Common Config */
++#define MURU_COMM_PPDU_FMT	BIT(0)
++#define MURU_COMM_SCH_TYPE	BIT(1)
++#define MURU_COMM_BAND		BIT(2)
++#define MURU_COMM_WMM		BIT(3)
++#define MURU_COMM_SPE_IDX	BIT(4)
++#define MURU_COMM_PROC_TYPE	BIT(5)
++#define MURU_COMM_SET		(MURU_COMM_PPDU_FMT | MURU_COMM_SCH_TYPE)
++#define MURU_COMM_SET_TM	(MURU_COMM_PPDU_FMT | MURU_COMM_BAND | \
++				 MURU_COMM_WMM | MURU_COMM_SPE_IDX)
++
++/* DL Common config */
++#define MURU_FIXED_DL_TOTAL_USER_CNT	BIT(4)
++
++/* UL Common Config */
++#define MURU_FIXED_UL_TOTAL_USER_CNT	BIT(4)
++
++enum {
++	CAPI_SU,
++	CAPI_MU,
++	CAPI_ER_SU,
++	CAPI_TB,
++	CAPI_LEGACY
++};
++
++enum {
++	CAPI_BASIC,
++	CAPI_BRP,
++	CAPI_MU_BAR,
++	CAPI_MU_RTS,
++	CAPI_BSRP,
++	CAPI_GCR_MU_BAR,
++	CAPI_BQRP,
++	CAPI_NDP_FRP,
++};
++
++enum {
++	MU_DL_ACK_POLICY_MU_BAR = 3,
++	MU_DL_ACK_POLICY_TF_FOR_ACK = 4,
++	MU_DL_ACK_POLICY_SU_BAR = 5,
++};
++
++enum muru_vendor_ctrl {
++	MU_CTRL_UPDATE,
++	MU_CTRL_DL_USER_CNT,
++	MU_CTRL_UL_USER_CNT,
++};
+ #endif
+ 
+ #endif
+diff --git a/mt7996/vendor.c b/mt7996/vendor.c
+index 9ba6f00ad..477c5c428 100644
+--- a/mt7996/vendor.c
++++ b/mt7996/vendor.c
+@@ -10,10 +10,31 @@
+ #include "vendor.h"
+ #include "mtk_mcu.h"
+ 
++#ifdef CONFIG_MTK_VENDOR
+ static const struct nla_policy
+ mu_ctrl_policy[NUM_MTK_VENDOR_ATTRS_MU_CTRL] = {
+ 	[MTK_VENDOR_ATTR_MU_CTRL_ONOFF] = {.type = NLA_U8 },
+ 	[MTK_VENDOR_ATTR_MU_CTRL_DUMP] = {.type = NLA_U8 },
++	[MTK_VENDOR_ATTR_MU_CTRL_STRUCT] = {.type = NLA_BINARY },
++};
++
++static const struct nla_policy
++wireless_ctrl_policy[NUM_MTK_VENDOR_ATTRS_WIRELESS_CTRL] = {
++	[MTK_VENDOR_ATTR_WIRELESS_CTRL_AMSDU] = {.type = NLA_U8 },
++	[MTK_VENDOR_ATTR_WIRELESS_CTRL_AMPDU] = {.type = NLA_U8 },
++	[MTK_VENDOR_ATTR_WIRELESS_CTRL_CERT] = {.type = NLA_U8 },
++	[MTK_VENDOR_ATTR_WIRELESS_CTRL_RTS_SIGTA] = {.type = NLA_U8 },
++	[MTK_VENDOR_ATTR_WIRELESS_CTRL_FIXED_MCS] = {.type = NLA_U8 },
++	[MTK_VENDOR_ATTR_WIRELESS_CTRL_OFDMA] = {.type = NLA_U8 },
++	[MTK_VENDOR_ATTR_WIRELESS_CTRL_PPDU_TX_TYPE] = {.type = NLA_U8 },
++	[MTK_VENDOR_ATTR_WIRELESS_CTRL_NUSERS_OFDMA] = {.type = NLA_U8 },
++	[MTK_VENDOR_ATTR_WIRELESS_CTRL_MIMO] = {.type = NLA_U8 },
++	[MTK_VENDOR_ATTR_WIRELESS_CTRL_BA_BUFFER_SIZE] = {.type = NLA_U16 },
++};
++
++static const struct nla_policy
++wireless_dump_policy[NUM_MTK_VENDOR_ATTRS_WIRELESS_DUMP] = {
++	[MTK_VENDOR_ATTR_WIRELESS_DUMP_AMSDU] = { .type = NLA_U8 },
+ };
+ 
+ static const struct nla_policy
+@@ -70,6 +91,17 @@ ibf_ctrl_policy[NUM_MTK_VENDOR_ATTRS_IBF_CTRL] = {
+ 	[MTK_VENDOR_ATTR_IBF_CTRL_ENABLE] = { .type = NLA_U8 },
+ };
+ 
++static const struct nla_policy
++rfeature_ctrl_policy[NUM_MTK_VENDOR_ATTRS_RFEATURE_CTRL] = {
++	[MTK_VENDOR_ATTR_RFEATURE_CTRL_HE_GI] = {.type = NLA_U8 },
++	[MTK_VENDOR_ATTR_RFEATURE_CTRL_HE_LTF] = { .type = NLA_U8 },
++	[MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE_CFG] = { .type = NLA_NESTED },
++	[MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE_EN] = { .type = NLA_U8 },
++	[MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE] = { .type = NLA_U8 },
++	[MTK_VENDOR_ATTR_RFEATURE_CTRL_ACK_PLCY] = { .type = NLA_U8 },
++	[MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TXBF] = { .type = NLA_U8 },
++};
++
+ struct mt7996_amnt_data {
+ 	u8 idx;
+ 	u8 addr[ETH_ALEN];
+@@ -84,6 +116,8 @@ static int mt7996_vendor_mu_ctrl(struct wiphy *wiphy,
+ {
+ 	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
+ 	struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_MU_CTRL];
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_muru *muru;
+ 	int err;
+ 	u8 val8;
+ 	u32 val32 = 0;
+@@ -99,9 +133,17 @@ static int mt7996_vendor_mu_ctrl(struct wiphy *wiphy,
+ 			 FIELD_PREP(RATE_CFG_VAL, val8);
+ 		ieee80211_iterate_active_interfaces_atomic(hw, IEEE80211_IFACE_ITER_RESUME_ALL,
+ 							   mt7996_set_wireless_vif, &val32);
++	} else if (tb[MTK_VENDOR_ATTR_MU_CTRL_STRUCT]) {
++		muru = kzalloc(sizeof(struct mt7996_muru), GFP_KERNEL);
++
++		nla_memcpy(muru, tb[MTK_VENDOR_ATTR_MU_CTRL_STRUCT],
++			   sizeof(struct mt7996_muru));
++
++		err = mt7996_mcu_set_muru_cfg(phy, muru);
++		kfree(muru);
+ 	}
+ 
+-	return 0;
++	return err;
+ }
+ 
+ static int
+@@ -124,6 +166,48 @@ mt7996_vendor_mu_ctrl_dump(struct wiphy *wiphy, struct wireless_dev *wdev,
+ 	return len;
+ }
+ 
++void mt7996_set_wireless_rts_sigta(struct ieee80211_hw *hw, u8 value) {
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++
++	switch (value) {
++	case BW_SIGNALING_STATIC:
++	case BW_SIGNALING_DYNAMIC:
++		mt7996_mcu_set_band_confg(phy, UNI_BAND_CONFIG_RTS_SIGTA_EN, true);
++		mt7996_mcu_set_band_confg(phy, UNI_BAND_CONFIG_DIS_SECCH_CCA_DET, false);
++		break;
++	default:
++		value = BW_SIGNALING_DISABLE;
++		mt7996_mcu_set_band_confg(phy, UNI_BAND_CONFIG_RTS_SIGTA_EN, false);
++		mt7996_mcu_set_band_confg(phy, UNI_BAND_CONFIG_DIS_SECCH_CCA_DET, true);
++		break;
++      }
++
++	phy->rts_bw_sig = value;
++
++	/* Set RTS Threshold to a lower Value */
++	mt7996_mcu_set_rts_thresh(phy, 500);
++}
++
++static int
++mt7996_vendor_wireless_ctrl_dump(struct wiphy *wiphy, struct wireless_dev *wdev,
++				 struct sk_buff *skb, const void *data, int data_len,
++				 unsigned long *storage)
++{
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	int len = 0;
++
++	if (*storage == 1)
++		return -ENOENT;
++	*storage = 1;
++
++	if (nla_put_u8(skb, MTK_VENDOR_ATTR_WIRELESS_DUMP_AMSDU,
++		       ieee80211_hw_check(hw, SUPPORTS_AMSDU_IN_AMPDU)))
++	return -ENOMEM;
++	len += 1;
++
++	return len;
++ }
++
+ void mt7996_vendor_amnt_fill_rx(struct mt7996_phy *phy, struct sk_buff *skb)
+ {
+ 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
+@@ -647,6 +731,126 @@ mt7996_vendor_ibf_ctrl_dump(struct wiphy *wiphy, struct wireless_dev *wdev,
+ 	return 1;
+ }
+ 
++static int mt7996_vendor_rfeature_ctrl(struct wiphy *wiphy,
++				       struct wireless_dev *wdev,
++				       const void *data,
++				       int data_len)
++{
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_dev *dev = phy->dev;
++	struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_RFEATURE_CTRL];
++	int err;
++	u32 val;
++
++	err = nla_parse(tb, MTK_VENDOR_ATTR_RFEATURE_CTRL_MAX, data, data_len,
++			rfeature_ctrl_policy, NULL);
++	if (err)
++		return err;
++
++	val = CAPI_RFEATURE_CHANGED;
++
++	if (tb[MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE_CFG]) {
++		u8 enable, trig_type;
++		int rem;
++		struct nlattr *cur;
++
++		nla_for_each_nested(cur, tb[MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE_CFG], rem) {
++			switch (nla_type(cur)) {
++			case MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE_EN:
++				enable = nla_get_u8(cur);
++				break;
++			case MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE:
++				trig_type = nla_get_u8(cur);
++				break;
++			default:
++				return -EINVAL;
++			};
++		}
++
++		err = mt7996_mcu_set_rfeature_trig_type(phy, enable, trig_type);
++		if (err)
++			return err;
++	} else if (tb[MTK_VENDOR_ATTR_RFEATURE_CTRL_ACK_PLCY]) {
++		u8 ack_policy;
++
++		ack_policy = nla_get_u8(tb[MTK_VENDOR_ATTR_RFEATURE_CTRL_ACK_PLCY]);
++		switch (ack_policy) {
++		case MU_DL_ACK_POLICY_TF_FOR_ACK:
++			return mt7996_mcu_set_muru_cmd(dev, UNI_CMD_MURU_SET_MUDL_ACK_POLICY,
++						       ack_policy);
++		default:
++			return 0;
++		}
++	}
++
++	return 0;
++}
++
++static int mt7996_vendor_wireless_ctrl(struct wiphy *wiphy,
++				       struct wireless_dev *wdev,
++				       const void *data,
++				       int data_len)
++{
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_dev *dev = phy->dev;
++	struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_WIRELESS_CTRL];
++	int err;
++	u8 val8;
++	u16 val16;
++	u32 val32;
++
++	err = nla_parse(tb, MTK_VENDOR_ATTR_WIRELESS_CTRL_MAX, data, data_len,
++			wireless_ctrl_policy, NULL);
++	if (err)
++		return err;
++
++	val32 = CAPI_WIRELESS_CHANGED;
++
++	if (tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_OFDMA]) {
++		val8 = nla_get_u8(tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_OFDMA]);
++		val32 |= FIELD_PREP(RATE_CFG_MODE, RATE_PARAM_FIXED_OFDMA) |
++			 FIELD_PREP(RATE_CFG_VAL, val8);
++		ieee80211_iterate_active_interfaces_atomic(hw, IEEE80211_IFACE_ITER_RESUME_ALL,
++			mt7996_set_wireless_vif, &val32);
++		if (val8 == 3) /* DL20and80 */
++			mt7996_mcu_set_muru_cmd(dev, UNI_CMD_MURU_SET_20M_DYN_ALGO, 1);
++	} else if (tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_BA_BUFFER_SIZE]) {
++		val16 = nla_get_u16(tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_BA_BUFFER_SIZE]);
++		hw->max_tx_aggregation_subframes = val16;
++		hw->max_rx_aggregation_subframes = val16;
++	} else if (tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_PPDU_TX_TYPE]) {
++		val8 = nla_get_u8(tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_PPDU_TX_TYPE]);
++		mt7996_mcu_set_ppdu_tx_type(phy, val8);
++	} else if (tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_NUSERS_OFDMA]) {
++		val8 = nla_get_u8(tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_NUSERS_OFDMA]);
++		if (phy->muru_onoff & OFDMA_UL)
++			mt7996_mcu_set_nusers_ofdma(phy, MU_CTRL_UL_USER_CNT, val8);
++		else
++			mt7996_mcu_set_nusers_ofdma(phy, MU_CTRL_DL_USER_CNT, val8);
++	} else if (tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_MIMO]) {
++		val8 = nla_get_u8(tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_MIMO]);
++		val32 |= FIELD_PREP(RATE_CFG_MODE, RATE_PARAM_FIXED_MIMO) |
++			 FIELD_PREP(RATE_CFG_VAL, val8);
++		ieee80211_iterate_active_interfaces_atomic(hw, IEEE80211_IFACE_ITER_RESUME_ALL,
++			mt7996_set_wireless_vif, &val32);
++	} else if (tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_CERT]) {
++		val8 = nla_get_u8(tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_CERT]);
++		dev->cert_mode = val8;
++		mt7996_mcu_set_cert(phy, val8);
++		mt7996_mcu_set_bypass_smthint(phy, val8);
++	} else if (tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_AMSDU]) {
++		val8 = nla_get_u8(tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_AMSDU]);
++		mt7996_set_wireless_amsdu(hw, val8);
++	} else if (tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_RTS_SIGTA]) {
++		val8 = nla_get_u8(tb[MTK_VENDOR_ATTR_WIRELESS_CTRL_RTS_SIGTA]);
++		mt7996_set_wireless_rts_sigta(hw, val8);
++	}
++
++	return 0;
++}
++
+ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 	{
+ 		.info = {
+@@ -660,6 +864,18 @@ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 		.policy = mu_ctrl_policy,
+ 		.maxattr = MTK_VENDOR_ATTR_MU_CTRL_MAX,
+ 	},
++	{
++		.info = {
++		        .vendor_id = MTK_NL80211_VENDOR_ID,
++		        .subcmd = MTK_NL80211_VENDOR_SUBCMD_WIRELESS_CTRL,
++		},
++		.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
++		        WIPHY_VENDOR_CMD_NEED_RUNNING,
++		.doit = mt7996_vendor_wireless_ctrl,
++		.dumpit = mt7996_vendor_wireless_ctrl_dump,
++		.policy = wireless_ctrl_policy,
++		.maxattr = MTK_VENDOR_ATTR_WIRELESS_CTRL_MAX,
++	},
+ 	{
+ 		.info = {
+ 			.vendor_id = MTK_NL80211_VENDOR_ID,
+@@ -718,6 +934,17 @@ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 		.policy = ibf_ctrl_policy,
+ 		.maxattr = MTK_VENDOR_ATTR_IBF_CTRL_MAX,
+ 	},
++	{
++		.info = {
++			.vendor_id = MTK_NL80211_VENDOR_ID,
++			.subcmd = MTK_NL80211_VENDOR_SUBCMD_RFEATURE_CTRL,
++		},
++		.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
++			WIPHY_VENDOR_CMD_NEED_RUNNING,
++		.doit = mt7996_vendor_rfeature_ctrl,
++		.policy = rfeature_ctrl_policy,
++		.maxattr = MTK_VENDOR_ATTR_RFEATURE_CTRL_MAX,
++	},
+ };
+ 
+ void mt7996_vendor_register(struct mt7996_phy *phy)
+@@ -727,3 +954,4 @@ void mt7996_vendor_register(struct mt7996_phy *phy)
+ 
+ 	spin_lock_init(&phy->amnt_lock);
+ }
++#endif
+diff --git a/mt7996/vendor.h b/mt7996/vendor.h
+index 29ccc050b..7011914b1 100644
+--- a/mt7996/vendor.h
++++ b/mt7996/vendor.h
+@@ -3,8 +3,12 @@
+ 
+ #define MTK_NL80211_VENDOR_ID	0x0ce7
+ 
++#ifdef CONFIG_MTK_VENDOR
++
+ enum mtk_nl80211_vendor_subcmds {
+ 	MTK_NL80211_VENDOR_SUBCMD_AMNT_CTRL = 0xae,
++	MTK_NL80211_VENDOR_SUBCMD_RFEATURE_CTRL = 0xc3,
++	MTK_NL80211_VENDOR_SUBCMD_WIRELESS_CTRL = 0xc4,
+ 	MTK_NL80211_VENDOR_SUBCMD_MU_CTRL = 0xc5,
+ 	MTK_NL80211_VENDOR_SUBCMD_EDCCA_CTRL = 0xc7,
+ 	MTK_NL80211_VENDOR_SUBCMD_3WIRE_CTRL = 0xc8,
+@@ -60,6 +64,7 @@ enum mtk_vendor_attr_mu_ctrl {
+ 
+ 	MTK_VENDOR_ATTR_MU_CTRL_ONOFF,
+ 	MTK_VENDOR_ATTR_MU_CTRL_DUMP,
++	MTK_VENDOR_ATTR_MU_CTRL_STRUCT,
+ 
+ 	/* keep last */
+ 	NUM_MTK_VENDOR_ATTRS_MU_CTRL,
+@@ -67,6 +72,66 @@ enum mtk_vendor_attr_mu_ctrl {
+ 		NUM_MTK_VENDOR_ATTRS_MU_CTRL - 1
+ };
+ 
++enum mtk_capi_control_changed {
++	CAPI_RFEATURE_CHANGED = BIT(16),
++	CAPI_WIRELESS_CHANGED = BIT(17),
++};
++
++enum mtk_vendor_attr_rfeature_ctrl {
++	MTK_VENDOR_ATTR_RFEATURE_CTRL_UNSPEC,
++
++	MTK_VENDOR_ATTR_RFEATURE_CTRL_HE_GI,
++	MTK_VENDOR_ATTR_RFEATURE_CTRL_HE_LTF,
++	MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE_CFG,
++	MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE_EN,
++	MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE,
++	MTK_VENDOR_ATTR_RFEATURE_CTRL_ACK_PLCY,
++	MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TXBF,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_RFEATURE_CTRL,
++	MTK_VENDOR_ATTR_RFEATURE_CTRL_MAX =
++	NUM_MTK_VENDOR_ATTRS_RFEATURE_CTRL - 1
++};
++
++enum mtk_vendor_attr_wireless_ctrl {
++	MTK_VENDOR_ATTR_WIRELESS_CTRL_UNSPEC,
++
++	MTK_VENDOR_ATTR_WIRELESS_CTRL_FIXED_MCS,
++	MTK_VENDOR_ATTR_WIRELESS_CTRL_OFDMA,
++	MTK_VENDOR_ATTR_WIRELESS_CTRL_PPDU_TX_TYPE,
++	MTK_VENDOR_ATTR_WIRELESS_CTRL_NUSERS_OFDMA,
++	MTK_VENDOR_ATTR_WIRELESS_CTRL_BA_BUFFER_SIZE,
++	MTK_VENDOR_ATTR_WIRELESS_CTRL_MIMO,
++	MTK_VENDOR_ATTR_WIRELESS_CTRL_AMSDU,
++	MTK_VENDOR_ATTR_WIRELESS_CTRL_AMPDU,
++	MTK_VENDOR_ATTR_WIRELESS_CTRL_CERT = 9,
++	MTK_VENDOR_ATTR_WIRELESS_CTRL_RTS_SIGTA,
++	MTK_VENDOR_ATTR_WIRELESS_CTRL_MU_EDCA, /* reserve */
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_WIRELESS_CTRL,
++	MTK_VENDOR_ATTR_WIRELESS_CTRL_MAX =
++		NUM_MTK_VENDOR_ATTRS_WIRELESS_CTRL - 1
++};
++
++enum mtk_vendor_attr_wireless_dump {
++	MTK_VENDOR_ATTR_WIRELESS_DUMP_UNSPEC,
++
++	MTK_VENDOR_ATTR_WIRELESS_DUMP_AMSDU,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_WIRELESS_DUMP,
++	MTK_VENDOR_ATTR_WIRELESS_DUMP_MAX =
++		NUM_MTK_VENDOR_ATTRS_WIRELESS_DUMP - 1
++};
++
++enum bw_sig {
++	BW_SIGNALING_DISABLE,
++	BW_SIGNALING_STATIC,
++	BW_SIGNALING_DYNAMIC
++};
++
+ enum mtk_vendor_attr_mnt_ctrl {
+ 	MTK_VENDOR_ATTR_AMNT_CTRL_UNSPEC,
+ 
+@@ -138,3 +203,5 @@ enum mtk_vendor_attr_ibf_dump {
+ };
+ 
+ #endif
++
++#endif
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0059-mtk-wifi-mt76-testmode-add-testmode-bf-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0059-mtk-wifi-mt76-testmode-add-testmode-bf-support.patch
new file mode 100644
index 0000000..5b985df
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0059-mtk-wifi-mt76-testmode-add-testmode-bf-support.patch
@@ -0,0 +1,1835 @@
+From 89b47e52c022f735791b3cc621afa08d8981e224 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 059/120] mtk: wifi: mt76: testmode: add testmode bf support
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+
+Add iTest additional bf command
+
+CR-Id: WCNCR00274293
+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
+
+CR-Id: WCNCR00274293
+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 58fd55b14..543d9de51 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -752,6 +752,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 864a802d7..885e5883e 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 {
+@@ -1279,6 +1280,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 e26ba59ac..1b9cca8ce 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -1073,7 +1073,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;
+ 	}
+ 
+@@ -4113,7 +4118,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 af078edda..054a616b2 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 f4db34e8f..e4dc4f165 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 {
+@@ -820,7 +828,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);
+@@ -832,10 +840,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 9ea8fe33c..0271ae52e 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 b67d366d4..9ef791db8 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);
+@@ -728,6 +752,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 7a4140b57..8a0ed9bc2 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 e94f9a90b..aa04d8d2c 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 836211f98..81c03f8e5 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 78662b2ed..f97ccb267 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 69147f866..a5c07f8dd 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 5d677f8c1..bda7624ad 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 77696ce7b..f793d1a51 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.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0060-mtk-wifi-mt76-mt7996-add-zwdfs-cert-mode.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0060-mtk-wifi-mt76-mt7996-add-zwdfs-cert-mode.patch
new file mode 100644
index 0000000..ee98f89
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0060-mtk-wifi-mt76-mt7996-add-zwdfs-cert-mode.patch
@@ -0,0 +1,221 @@
+From d3720b25750768faf440b59268a9a6523779f6b8 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Fri, 22 Sep 2023 12:33:06 +0800
+Subject: [PATCH 060/120] mtk: wifi: mt76: mt7996: add zwdfs cert mode
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt7996/mcu.c    | 44 ++++++++++++++++++++++++++++++++------------
+ mt7996/mcu.h    | 14 ++++++++++++++
+ mt7996/mt7996.h |  5 +++++
+ mt7996/vendor.c | 37 +++++++++++++++++++++++++++++++++++++
+ mt7996/vendor.h | 12 ++++++++++++
+ 5 files changed, 100 insertions(+), 12 deletions(-)
+
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 1b9cca8ce..ae0364216 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -4511,18 +4511,7 @@ int mt7996_mcu_set_radio_en(struct mt7996_phy *phy, bool enable)
+ int mt7996_mcu_rdd_cmd(struct mt7996_dev *dev, int cmd, u8 index,
+ 		       u8 rx_sel, u8 val)
+ {
+-	struct {
+-		u8 _rsv[4];
+-
+-		__le16 tag;
+-		__le16 len;
+-
+-		u8 ctrl;
+-		u8 rdd_idx;
+-		u8 rdd_rx_sel;
+-		u8 val;
+-		u8 rsv[4];
+-	} __packed req = {
++	struct mt7996_rdd_ctrl req = {
+ 		.tag = cpu_to_le16(UNI_RDD_CTRL_PARM),
+ 		.len = cpu_to_le16(sizeof(req) - 4),
+ 		.ctrl = cmd,
+@@ -4535,6 +4524,37 @@ int mt7996_mcu_rdd_cmd(struct mt7996_dev *dev, int cmd, u8 index,
+ 				 &req, sizeof(req), true);
+ }
+ 
++int mt7996_mcu_rdd_background_disable_timer(struct mt7996_dev *dev, bool disable_timer)
++{
++	struct mt7996_rdd_ctrl req = {
++		.tag = cpu_to_le16(UNI_RDD_CTRL_PARM),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.ctrl = RDD_DISABLE_ZW_TIMER,
++		.rdd_idx = MT_RX_SEL2,
++		.disable_timer = disable_timer,
++	};
++
++	if (!is_mt7996(&dev->mt76) ||
++	    (mt76_get_field(dev, MT_PAD_GPIO, MT_PAD_GPIO_ADIE_COMB) % 2))
++		return 0;
++
++	switch (dev->mt76.region) {
++	case NL80211_DFS_ETSI:
++		req.val = 0;
++		break;
++	case NL80211_DFS_JP:
++		req.val = 2;
++		break;
++	case NL80211_DFS_FCC:
++	default:
++		req.val = 1;
++		break;
++	}
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(RDD_CTRL),
++				 &req, sizeof(req), true);
++}
++
+ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev,
+ 				     struct ieee80211_vif *vif,
+ 				     struct ieee80211_sta *sta)
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 054a616b2..398bf3d27 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -119,6 +119,20 @@ struct mt7996_mcu_rdd_report {
+ 	} hw_pulse[32];
+ } __packed;
+ 
++struct mt7996_rdd_ctrl {
++	u8 _rsv[4];
++
++	__le16 tag;
++	__le16 len;
++
++	u8 ctrl;
++	u8 rdd_idx;
++	u8 rdd_rx_sel;
++	u8 val;
++	u8 disable_timer;
++	u8 rsv[3];
++} __packed;
++
+ struct mt7996_mcu_background_chain_ctrl {
+ 	u8 _rsv[4];
+ 
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index e4dc4f165..eff2e1be9 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -517,8 +517,11 @@ enum mt7996_rdd_cmd {
+ 	RDD_READ_PULSE,
+ 	RDD_RESUME_BF,
+ 	RDD_IRQ_OFF,
++	RDD_DISABLE_ZW_TIMER,
+ };
+ 
++#define RDD_ZW_TIMER_OFF	BIT(31)
++
+ static inline struct mt7996_phy *
+ mt7996_hw_phy(struct ieee80211_hw *hw)
+ {
+@@ -660,6 +663,8 @@ int mt7996_mcu_set_thermal_protect(struct mt7996_phy *phy, bool enable);
+ int mt7996_mcu_set_txpower_sku(struct mt7996_phy *phy);
+ int mt7996_mcu_rdd_cmd(struct mt7996_dev *dev, int cmd, u8 index,
+ 		       u8 rx_sel, u8 val);
++int mt7996_mcu_rdd_background_disable_timer(struct mt7996_dev *dev,
++					    bool disable_timer);
+ int mt7996_mcu_rdd_background_enable(struct mt7996_phy *phy,
+ 				     struct cfg80211_chan_def *chandef);
+ int mt7996_mcu_set_fixed_rate_table(struct mt7996_phy *phy, u8 table_idx,
+diff --git a/mt7996/vendor.c b/mt7996/vendor.c
+index 477c5c428..c7fd32785 100644
+--- a/mt7996/vendor.c
++++ b/mt7996/vendor.c
+@@ -102,6 +102,11 @@ rfeature_ctrl_policy[NUM_MTK_VENDOR_ATTRS_RFEATURE_CTRL] = {
+ 	[MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TXBF] = { .type = NLA_U8 },
+ };
+ 
++static const struct nla_policy
++background_radar_ctrl_policy[NUM_MTK_VENDOR_ATTRS_BACKGROUND_RADAR_CTRL] = {
++	[MTK_VENDOR_ATTR_BACKGROUND_RADAR_CTRL_MODE] = {.type = NLA_U8 },
++};
++
+ struct mt7996_amnt_data {
+ 	u8 idx;
+ 	u8 addr[ETH_ALEN];
+@@ -851,6 +856,27 @@ static int mt7996_vendor_wireless_ctrl(struct wiphy *wiphy,
+ 	return 0;
+ }
+ 
++static int mt7996_vendor_background_radar_mode_ctrl(struct wiphy *wiphy,
++						    struct wireless_dev *wdev,
++						    const void *data,
++						    int data_len)
++{
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++	struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_BACKGROUND_RADAR_CTRL];
++	int err;
++	u8 background_radar_mode;
++
++	err = nla_parse(tb, MTK_VENDOR_ATTR_BACKGROUND_RADAR_CTRL_MAX, data, data_len,
++			background_radar_ctrl_policy, NULL);
++	if (err)
++		return err;
++
++	background_radar_mode = nla_get_u8(tb[MTK_VENDOR_ATTR_BACKGROUND_RADAR_CTRL_MODE]);
++
++	return mt7996_mcu_rdd_background_disable_timer(dev, !!background_radar_mode);
++}
++
+ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 	{
+ 		.info = {
+@@ -945,6 +971,17 @@ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 		.policy = rfeature_ctrl_policy,
+ 		.maxattr = MTK_VENDOR_ATTR_RFEATURE_CTRL_MAX,
+ 	},
++	{
++		.info = {
++			.vendor_id = MTK_NL80211_VENDOR_ID,
++			.subcmd = MTK_NL80211_VENDOR_SUBCMD_BACKGROUND_RADAR_CTRL,
++		},
++		.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
++			WIPHY_VENDOR_CMD_NEED_RUNNING,
++		.doit = mt7996_vendor_background_radar_mode_ctrl,
++		.policy = background_radar_ctrl_policy,
++		.maxattr = MTK_VENDOR_ATTR_BACKGROUND_RADAR_CTRL_MAX,
++	},
+ };
+ 
+ void mt7996_vendor_register(struct mt7996_phy *phy)
+diff --git a/mt7996/vendor.h b/mt7996/vendor.h
+index 7011914b1..920b6e6ab 100644
+--- a/mt7996/vendor.h
++++ b/mt7996/vendor.h
+@@ -14,6 +14,7 @@ enum mtk_nl80211_vendor_subcmds {
+ 	MTK_NL80211_VENDOR_SUBCMD_3WIRE_CTRL = 0xc8,
+ 	MTK_NL80211_VENDOR_SUBCMD_IBF_CTRL = 0xc9,
+ 	MTK_NL80211_VENDOR_SUBCMD_BSS_COLOR_CTRL = 0xca,
++	MTK_NL80211_VENDOR_SUBCMD_BACKGROUND_RADAR_CTRL = 0xcb,
+ };
+ 
+ enum mtk_vendor_attr_edcca_ctrl {
+@@ -126,6 +127,17 @@ enum mtk_vendor_attr_wireless_dump {
+ 		NUM_MTK_VENDOR_ATTRS_WIRELESS_DUMP - 1
+ };
+ 
++enum mtk_vendor_attr_background_radar_ctrl {
++	MTK_VENDOR_ATTR_BACKGROUND_RADAR_CTRL_UNSPEC,
++
++	MTK_VENDOR_ATTR_BACKGROUND_RADAR_CTRL_MODE,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_BACKGROUND_RADAR_CTRL,
++	MTK_VENDOR_ATTR_BACKGROUND_RADAR_CTRL_MAX =
++		NUM_MTK_VENDOR_ATTRS_BACKGROUND_RADAR_CTRL - 1
++};
++
+ enum bw_sig {
+ 	BW_SIGNALING_DISABLE,
+ 	BW_SIGNALING_STATIC,
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0061-mtk-wifi-mt76-testmode-add-channel-68-96.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0061-mtk-wifi-mt76-testmode-add-channel-68-96.patch
new file mode 100644
index 0000000..3338b71
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0061-mtk-wifi-mt76-testmode-add-channel-68-96.patch
@@ -0,0 +1,250 @@
+From a5348bf8661cbf076e71c54e6ec89782574ea67f Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Mon, 11 Sep 2023 14:43:07 +0800
+Subject: [PATCH 061/120] mtk: wifi: mt76: testmode: add channel 68 & 96
+
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+
+Add all the channel between 68 & 96 since ibf 5g channel group 3 will use channel 84.
+Also, "mtk: wifi: mt76: testmode: add channel 68 & 96" can be
+merged into to "mtk: wifi: mt76: testmode: add basic testmode support"
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+
+Fix 5g channel list size
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mac80211.c        |  9 +++++++++
+ mt7996/eeprom.c   | 49 +++++++++++++++++++++++++++++++++++++++++++++--
+ mt7996/eeprom.h   |  2 ++
+ mt7996/mcu.c      | 10 +++++++++-
+ mt7996/testmode.c | 15 ++++++++++++---
+ mt7996/testmode.h |  6 +++---
+ 6 files changed, 82 insertions(+), 9 deletions(-)
+
+diff --git a/mac80211.c b/mac80211.c
+index 92f326523..517fb90fe 100644
+--- a/mac80211.c
++++ b/mac80211.c
+@@ -35,6 +35,15 @@ static const struct ieee80211_channel mt76_channels_5ghz[] = {
+ 	CHAN5G(60, 5300),
+ 	CHAN5G(64, 5320),
+ 
++	CHAN5G(68, 5340),
++	CHAN5G(72, 5360),
++	CHAN5G(76, 5380),
++	CHAN5G(80, 5400),
++	CHAN5G(84, 5420),
++	CHAN5G(88, 5440),
++	CHAN5G(92, 5460),
++	CHAN5G(96, 5480),
++
+ 	CHAN5G(100, 5500),
+ 	CHAN5G(104, 5520),
+ 	CHAN5G(108, 5540),
+diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
+index 138bdb472..32e910e0b 100644
+--- a/mt7996/eeprom.c
++++ b/mt7996/eeprom.c
+@@ -18,6 +18,17 @@ const struct ieee80211_channel dpd_2g_ch_list_bw20[] = {
+ 	CHAN2G(11, 2462)
+ };
+ 
++const struct ieee80211_channel dpd_5g_skip_ch_list[] = {
++	CHAN5G(68, 5340),
++	CHAN5G(72, 5360),
++	CHAN5G(76, 5380),
++	CHAN5G(80, 5400),
++	CHAN5G(84, 5420),
++	CHAN5G(88, 5440),
++	CHAN5G(92, 5460),
++	CHAN5G(96, 5480)
++};
++
+ const struct ieee80211_channel dpd_5g_ch_list_bw160[] = {
+ 	CHAN5G(50, 5250),
+ 	CHAN5G(114, 5570),
+@@ -44,6 +55,7 @@ const struct ieee80211_channel dpd_6g_ch_list_bw320[] = {
+ };
+ 
+ const u32 dpd_2g_bw20_ch_num = ARRAY_SIZE(dpd_2g_ch_list_bw20);
++const u32 dpd_5g_skip_ch_num = ARRAY_SIZE(dpd_5g_skip_ch_list);
+ const u32 dpd_5g_bw160_ch_num = ARRAY_SIZE(dpd_5g_ch_list_bw160);
+ const u32 dpd_6g_bw160_ch_num = ARRAY_SIZE(dpd_6g_ch_list_bw160);
+ const u32 dpd_6g_bw320_ch_num = ARRAY_SIZE(dpd_6g_ch_list_bw320);
+@@ -184,8 +196,8 @@ mt7996_get_dpd_per_band_size(struct mt7996_dev *dev, enum nl80211_band band)
+ 	if (band == NL80211_BAND_2GHZ)
+ 		dpd_size = dpd_2g_bw20_ch_num * DPD_PER_CH_BW20_SIZE;
+ 	else if (band == NL80211_BAND_5GHZ)
+-		dpd_size = mphy->sband_5g.sband.n_channels * DPD_PER_CH_BW20_SIZE +
+-			   dpd_5g_bw160_ch_num * DPD_PER_CH_GT_BW20_SIZE;
++		dpd_size = (mphy->sband_5g.sband.n_channels - dpd_5g_skip_ch_num) *
++			   DPD_PER_CH_BW20_SIZE + dpd_5g_bw160_ch_num * DPD_PER_CH_GT_BW20_SIZE;
+ 	else
+ 		dpd_size = mphy->sband_6g.sband.n_channels * DPD_PER_CH_BW20_SIZE +
+ 			   (dpd_6g_bw160_ch_num + dpd_6g_bw320_ch_num) * DPD_PER_CH_GT_BW20_SIZE;
+@@ -445,6 +457,39 @@ out:
+ 	return ret;
+ }
+ 
++static void mt7996_eeprom_init_precal(struct mt7996_dev *dev)
++{
++#define MT76_CHANNELS_5GHZ_SIZE		36	/* ARRAY_SIZE(mt76_channels_5ghz) */
++#define MT76_CHANNELS_6GHZ_SIZE		59	/* ARRAY_SIZE(mt76_channels_6ghz) */
++
++	dev->prek.dpd_ch_num[DPD_CH_NUM_BW20_2G] = ARRAY_SIZE(dpd_2g_ch_list_bw20);
++	dev->prek.dpd_ch_num[DPD_CH_NUM_BW20_5G_SKIP] = ARRAY_SIZE(dpd_5g_skip_ch_list);
++	dev->prek.dpd_ch_num[DPD_CH_NUM_BW20_5G] = MT76_CHANNELS_5GHZ_SIZE -
++						   DPD_CH_NUM(BW20_5G_SKIP);
++	dev->prek.dpd_ch_num[DPD_CH_NUM_BW160_5G] = ARRAY_SIZE(dpd_5g_ch_list_bw160);
++	dev->prek.dpd_ch_num[DPD_CH_NUM_BW20_6G] = MT76_CHANNELS_6GHZ_SIZE;
++	dev->prek.dpd_ch_num[DPD_CH_NUM_BW160_6G] = ARRAY_SIZE(dpd_6g_ch_list_bw160);
++
++	switch (mt76_chip(&dev->mt76)) {
++	case 0x7990:
++		dev->prek.rev = mt7996_prek_rev;
++		/* 5g & 6g bw 80 dpd channel list is not used */
++		dev->prek.dpd_ch_num[DPD_CH_NUM_BW320_6G] = ARRAY_SIZE(dpd_6g_ch_list_bw320);
++		break;
++	case 0x7992:
++		dev->prek.rev  = mt7992_prek_rev;
++		dev->prek.dpd_ch_num[DPD_CH_NUM_BW80_5G] = ARRAY_SIZE(dpd_5g_ch_list_bw80);
++		/* 6g is not used in current sku */
++		dev->prek.dpd_ch_num[DPD_CH_NUM_BW20_6G] = 0;
++		dev->prek.dpd_ch_num[DPD_CH_NUM_BW80_6G] = 0;
++		dev->prek.dpd_ch_num[DPD_CH_NUM_BW160_6G] = 0;
++		break;
++	default:
++		dev->prek.rev  = mt7996_prek_rev;
++		break;
++	}
++}
++
+ static int mt7996_eeprom_load_precal(struct mt7996_dev *dev)
+ {
+ 	struct mt76_dev *mdev = &dev->mt76;
+diff --git a/mt7996/eeprom.h b/mt7996/eeprom.h
+index 03a4fd07d..9a15b4462 100644
+--- a/mt7996/eeprom.h
++++ b/mt7996/eeprom.h
+@@ -67,6 +67,8 @@ enum mt7996_eeprom_field {
+ 
+ extern const struct ieee80211_channel dpd_2g_ch_list_bw20[];
+ extern const u32 dpd_2g_bw20_ch_num;
++extern const struct ieee80211_channel dpd_5g_skip_ch_list[];
++extern const u32 dpd_5g_skip_ch_num;
+ extern const struct ieee80211_channel dpd_5g_ch_list_bw160[];
+ extern const u32 dpd_5g_bw160_ch_num;
+ extern const struct ieee80211_channel dpd_6g_ch_list_bw160[];
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index ae0364216..5a28ef1c2 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -3782,7 +3782,8 @@ int mt7996_mcu_apply_tx_dpd(struct mt7996_phy *phy)
+ 		chan_list_size = mphy->sband_5g.sband.n_channels;
+ 		base_offset += dpd_size_2g;
+ 		if (bw == NL80211_CHAN_WIDTH_160) {
+-			base_offset += mphy->sband_5g.sband.n_channels * DPD_PER_CH_BW20_SIZE;
++			base_offset += (mphy->sband_5g.sband.n_channels - dpd_5g_skip_ch_num) *
++				       DPD_PER_CH_BW20_SIZE;
+ 			per_chan_size = DPD_PER_CH_GT_BW20_SIZE;
+ 			cal_id = RF_DPD_FLAT_5G_MEM_CAL;
+ 			chan_list = dpd_5g_ch_list_bw160;
+@@ -3791,6 +3792,9 @@ int mt7996_mcu_apply_tx_dpd(struct mt7996_phy *phy)
+ 			/* apply (center channel - 2)'s dpd cal data for bw 40/80 channels */
+ 			channel -= 2;
+ 		}
++		if (channel >= dpd_5g_skip_ch_list[0].hw_value &&
++		    channel <= dpd_5g_skip_ch_list[dpd_5g_skip_ch_num - 1].hw_value)
++			return 0;
+ 		break;
+ 	case NL80211_BAND_6GHZ:
+ 		dpd_mask = MT_EE_WIFI_CAL_DPD_6G;
+@@ -3830,6 +3834,10 @@ int mt7996_mcu_apply_tx_dpd(struct mt7996_phy *phy)
+ 	if (idx == chan_list_size)
+ 		return -EINVAL;
+ 
++	if (band == NL80211_BAND_5GHZ && bw != NL80211_CHAN_WIDTH_160 &&
++	    channel > dpd_5g_skip_ch_list[dpd_5g_skip_ch_num - 1].hw_value)
++		idx -= dpd_5g_skip_ch_num;
++
+ 	cal += MT_EE_CAL_GROUP_SIZE + base_offset + idx * per_chan_size;
+ 
+ 	for (i = 0; i < per_chan_size / MT_EE_CAL_UNIT; i++) {
+diff --git a/mt7996/testmode.c b/mt7996/testmode.c
+index 81c03f8e5..01f36fd9b 100644
+--- a/mt7996/testmode.c
++++ b/mt7996/testmode.c
+@@ -531,6 +531,11 @@ mt7996_tm_dpd_prek_send_req(struct mt7996_phy *phy, struct mt7996_tm_req *req,
+ 	memcpy(&chandef_backup, chandef, sizeof(struct cfg80211_chan_def));
+ 
+ 	for (i = 0; i < channel_size; i++) {
++		if (chan_list[i].band == NL80211_BAND_5GHZ &&
++		    chan_list[i].hw_value >= dpd_5g_skip_ch_list[0].hw_value &&
++		    chan_list[i].hw_value <= dpd_5g_skip_ch_list[dpd_5g_skip_ch_num - 1].hw_value)
++			continue;
++
+ 		memcpy(chandef->chan, &chan_list[i], sizeof(struct ieee80211_channel));
+ 		chandef->width = width;
+ 
+@@ -612,7 +617,8 @@ mt7996_tm_dpd_prek(struct mt7996_phy *phy, enum mt76_testmode_state state)
+ 						  NL80211_CHAN_WIDTH_20, RF_DPD_FLAT_5G_CAL);
+ 		if (ret)
+ 			return ret;
+-		wait_on_prek_offset += mphy->sband_5g.sband.n_channels * DPD_PER_CH_BW20_SIZE;
++		wait_on_prek_offset += (mphy->sband_5g.sband.n_channels - dpd_5g_skip_ch_num) *
++				       DPD_PER_CH_BW20_SIZE;
+ 		wait_event_timeout(mdev->mcu.wait,
+ 				   dev->cur_prek_offset == wait_on_prek_offset, 30 * HZ);
+ 
+@@ -868,6 +874,7 @@ mt7996_tm_get_center_chan(struct mt7996_phy *phy, struct cfg80211_chan_def *chan
+ 	const struct ieee80211_channel *chan = mphy->sband_5g.sband.channels;
+ 	u32 bitmap, i, offset, width_mhz, size = mphy->sband_5g.sband.n_channels;
+ 	u16 first_control = 0, control_chan = chandef->chan->hw_value;
++	bool not_first;
+ 
+ 	bitmap = mt7996_tm_bw_mapping(chandef->width, BW_MAP_NL_TO_CONTROL_BITMAP_5G);
+ 	if (!bitmap)
+@@ -877,7 +884,9 @@ mt7996_tm_get_center_chan(struct mt7996_phy *phy, struct cfg80211_chan_def *chan
+ 	offset = width_mhz / 10 - 2;
+ 
+ 	for (i = 0; i < size; i++) {
+-		if (!((1 << i) & bitmap))
++		not_first = (chandef->width != NL80211_CHAN_WIDTH_160) ?
++			    (i % bitmap) : (i >= 32) || !((1 << i) & bitmap);
++		if (not_first)
+ 			continue;
+ 
+ 		if (control_chan >= chan[i].hw_value)
+@@ -886,7 +895,7 @@ mt7996_tm_get_center_chan(struct mt7996_phy *phy, struct cfg80211_chan_def *chan
+ 			break;
+ 	}
+ 
+-	if (i == size || first_control == 0)
++	if (first_control == 0)
+ 		return control_chan;
+ 
+ 	return first_control + offset;
+diff --git a/mt7996/testmode.h b/mt7996/testmode.h
+index f97ccb267..ba1767aed 100644
+--- a/mt7996/testmode.h
++++ b/mt7996/testmode.h
+@@ -38,9 +38,9 @@ enum {
+ 	BF_CDBW_8080MHZ,
+ };
+ 
+-#define FIRST_CONTROL_CHAN_BITMAP_BW40		0x5555555
+-#define FIRST_CONTROL_CHAN_BITMAP_BW80		0x111111
+-#define FIRST_CONTROL_CHAN_BITMAP_BW160		0x100101
++#define FIRST_CONTROL_CHAN_BITMAP_BW40		2
++#define FIRST_CONTROL_CHAN_BITMAP_BW80		4
++#define FIRST_CONTROL_CHAN_BITMAP_BW160		0x10010101
+ 
+ enum bw_mapping_method {
+ 	BW_MAP_NL_TO_FW,
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0062-mtk-wifi-mt76-mt7996-support-enable-disable-pp-featu.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0062-mtk-wifi-mt76-mt7996-support-enable-disable-pp-featu.patch
new file mode 100644
index 0000000..ace5864
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0062-mtk-wifi-mt76-mt7996-support-enable-disable-pp-featu.patch
@@ -0,0 +1,112 @@
+From 124e9d200a346df3fd0194886929c05553b9b308 Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Mon, 25 Sep 2023 19:20:49 +0800
+Subject: [PATCH 062/120] mtk: wifi: mt76: mt7996: support enable/disable pp
+ feature by nl80211 vendor commands
+
+User can enable/disable preamble puncture feature through hostapd
+configuration and hostapd_cli. Driver can receive the nl80211 vendor
+message and convert it to mcu commands.
+
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+---
+ mt7996/vendor.c | 38 ++++++++++++++++++++++++++++++++++++++
+ mt7996/vendor.h | 12 ++++++++++++
+ 2 files changed, 50 insertions(+)
+
+diff --git a/mt7996/vendor.c b/mt7996/vendor.c
+index c7fd32785..9732ed287 100644
+--- a/mt7996/vendor.c
++++ b/mt7996/vendor.c
+@@ -107,6 +107,11 @@ background_radar_ctrl_policy[NUM_MTK_VENDOR_ATTRS_BACKGROUND_RADAR_CTRL] = {
+ 	[MTK_VENDOR_ATTR_BACKGROUND_RADAR_CTRL_MODE] = {.type = NLA_U8 },
+ };
+ 
++static struct nla_policy
++pp_ctrl_policy[NUM_MTK_VENDOR_ATTRS_PP_CTRL] = {
++	[MTK_VENDOR_ATTR_PP_MODE] = { .type = NLA_U8 },
++};
++
+ struct mt7996_amnt_data {
+ 	u8 idx;
+ 	u8 addr[ETH_ALEN];
+@@ -877,6 +882,28 @@ static int mt7996_vendor_background_radar_mode_ctrl(struct wiphy *wiphy,
+ 	return mt7996_mcu_rdd_background_disable_timer(dev, !!background_radar_mode);
+ }
+ 
++static int mt7996_vendor_pp_ctrl(struct wiphy *wiphy, struct wireless_dev *wdev,
++				 const void *data, int data_len)
++{
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_PP_CTRL];
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	int err;
++	u8 val8;
++
++	err = nla_parse(tb, MTK_VENDOR_ATTR_PP_CTRL_MAX, data, data_len,
++			pp_ctrl_policy, NULL);
++	if (err)
++		return err;
++
++	if (tb[MTK_VENDOR_ATTR_PP_MODE]) {
++		val8 = nla_get_u8(tb[MTK_VENDOR_ATTR_PP_MODE]);
++		err = mt7996_mcu_set_pp_en(phy, !!val8, 0, 0);
++	}
++
++	return err;
++}
++
+ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 	{
+ 		.info = {
+@@ -982,6 +1009,17 @@ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 		.policy = background_radar_ctrl_policy,
+ 		.maxattr = MTK_VENDOR_ATTR_BACKGROUND_RADAR_CTRL_MAX,
+ 	},
++	{
++		.info = {
++			.vendor_id = MTK_NL80211_VENDOR_ID,
++			.subcmd = MTK_NL80211_VENDOR_SUBCMD_PP_CTRL,
++		},
++		.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
++			WIPHY_VENDOR_CMD_NEED_RUNNING,
++		.doit = mt7996_vendor_pp_ctrl,
++		.policy = pp_ctrl_policy,
++		.maxattr = MTK_VENDOR_ATTR_PP_CTRL_MAX,
++	},
+ };
+ 
+ void mt7996_vendor_register(struct mt7996_phy *phy)
+diff --git a/mt7996/vendor.h b/mt7996/vendor.h
+index 920b6e6ab..981289658 100644
+--- a/mt7996/vendor.h
++++ b/mt7996/vendor.h
+@@ -15,6 +15,7 @@ enum mtk_nl80211_vendor_subcmds {
+ 	MTK_NL80211_VENDOR_SUBCMD_IBF_CTRL = 0xc9,
+ 	MTK_NL80211_VENDOR_SUBCMD_BSS_COLOR_CTRL = 0xca,
+ 	MTK_NL80211_VENDOR_SUBCMD_BACKGROUND_RADAR_CTRL = 0xcb,
++	MTK_NL80211_VENDOR_SUBCMD_PP_CTRL = 0xcc,
+ };
+ 
+ enum mtk_vendor_attr_edcca_ctrl {
+@@ -214,6 +215,17 @@ enum mtk_vendor_attr_ibf_dump {
+ 		NUM_MTK_VENDOR_ATTRS_IBF_DUMP - 1
+ };
+ 
++enum mtk_vendor_attr_pp_ctrl {
++	MTK_VENDOR_ATTR_PP_CTRL_UNSPEC,
++
++	MTK_VENDOR_ATTR_PP_MODE,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_PP_CTRL,
++	MTK_VENDOR_ATTR_PP_CTRL_MAX =
++		NUM_MTK_VENDOR_ATTRS_PP_CTRL - 1
++};
++
+ #endif
+ 
+ #endif
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0063-mtk-wifi-mt76-testmode-add-kite-testmode-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0063-mtk-wifi-mt76-testmode-add-kite-testmode-support.patch
new file mode 100644
index 0000000..65ae490
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0063-mtk-wifi-mt76-testmode-add-kite-testmode-support.patch
@@ -0,0 +1,598 @@
+From 442e736dbde0465b23d21a2c88a13db0370420b0 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Thu, 12 Oct 2023 16:17:33 +0800
+Subject: [PATCH 063/120] mtk: wifi: mt76: testmode: add kite testmode support
+
+Add Kite testmode support
+1. avoid entering connac 2 testmode flow in kite
+2. refactor prek implementation for handling chip difference
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt7996/eeprom.c   | 63 +++++++++++++-----------------
+ mt7996/eeprom.h   | 81 +++++++++++++++++++++++++++------------
+ mt7996/mcu.c      | 48 ++++++++++++++---------
+ mt7996/mt7996.h   | 18 ++++++++-
+ mt7996/testmode.c | 97 ++++++++++++++++++++++++++++-------------------
+ testmode.c        | 11 ++++--
+ 6 files changed, 198 insertions(+), 120 deletions(-)
+
+diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
+index 32e910e0b..c2088e2fa 100644
+--- a/mt7996/eeprom.c
++++ b/mt7996/eeprom.c
+@@ -29,12 +29,39 @@ const struct ieee80211_channel dpd_5g_skip_ch_list[] = {
+ 	CHAN5G(96, 5480)
+ };
+ 
++const struct ieee80211_channel dpd_5g_ch_list_bw80[] = {
++	CHAN5G(42, 5210),
++	CHAN5G(58, 5290),
++	CHAN5G(106, 5530),
++	CHAN5G(122, 5610),
++	CHAN5G(138, 5690),
++	CHAN5G(155, 5775),
++	CHAN5G(171, 5855)
++};
++
+ const struct ieee80211_channel dpd_5g_ch_list_bw160[] = {
+ 	CHAN5G(50, 5250),
+ 	CHAN5G(114, 5570),
+ 	CHAN5G(163, 5815)
+ };
+ 
++const struct ieee80211_channel dpd_6g_ch_list_bw80[] = {
++	CHAN6G(7, 5985),
++	CHAN6G(23, 6065),
++	CHAN6G(39, 6145),
++	CHAN6G(55, 6225),
++	CHAN6G(71, 6305),
++	CHAN6G(87, 6385),
++	CHAN6G(103, 6465),
++	CHAN6G(119, 6545),
++	CHAN6G(135, 6625),
++	CHAN6G(151, 6705),
++	CHAN6G(167, 6785),
++	CHAN6G(183, 6865),
++	CHAN6G(199, 6945),
++	CHAN6G(215, 7025)
++};
++
+ const struct ieee80211_channel dpd_6g_ch_list_bw160[] = {
+ 	CHAN6G(15, 6025),
+ 	CHAN6G(47, 6185),
+@@ -54,12 +81,6 @@ const struct ieee80211_channel dpd_6g_ch_list_bw320[] = {
+ 	CHAN6G(191, 6905)
+ };
+ 
+-const u32 dpd_2g_bw20_ch_num = ARRAY_SIZE(dpd_2g_ch_list_bw20);
+-const u32 dpd_5g_skip_ch_num = ARRAY_SIZE(dpd_5g_skip_ch_list);
+-const u32 dpd_5g_bw160_ch_num = ARRAY_SIZE(dpd_5g_ch_list_bw160);
+-const u32 dpd_6g_bw160_ch_num = ARRAY_SIZE(dpd_6g_ch_list_bw160);
+-const u32 dpd_6g_bw320_ch_num = ARRAY_SIZE(dpd_6g_ch_list_bw320);
+-
+ static int mt7996_check_eeprom(struct mt7996_dev *dev)
+ {
+ 	u8 *eeprom = dev->mt76.eeprom.data;
+@@ -175,36 +196,6 @@ const char *mt7996_eeprom_name(struct mt7996_dev *dev)
+ 	}
+ }
+ 
+-int
+-mt7996_get_dpd_per_band_size(struct mt7996_dev *dev, enum nl80211_band band)
+-{
+-	/* handle different sku */
+-	static const u8 band_to_idx[] = {
+-		[NL80211_BAND_2GHZ] = MT_BAND0,
+-		[NL80211_BAND_5GHZ] = MT_BAND1,
+-		[NL80211_BAND_6GHZ] = MT_BAND2,
+-	};
+-	struct mt7996_phy *phy = __mt7996_phy(dev, band_to_idx[band]);
+-	struct mt76_phy *mphy;
+-	int dpd_size;
+-
+-	if (!phy)
+-		return 0;
+-
+-	mphy = phy->mt76;
+-
+-	if (band == NL80211_BAND_2GHZ)
+-		dpd_size = dpd_2g_bw20_ch_num * DPD_PER_CH_BW20_SIZE;
+-	else if (band == NL80211_BAND_5GHZ)
+-		dpd_size = (mphy->sband_5g.sband.n_channels - dpd_5g_skip_ch_num) *
+-			   DPD_PER_CH_BW20_SIZE + dpd_5g_bw160_ch_num * DPD_PER_CH_GT_BW20_SIZE;
+-	else
+-		dpd_size = mphy->sband_6g.sband.n_channels * DPD_PER_CH_BW20_SIZE +
+-			   (dpd_6g_bw160_ch_num + dpd_6g_bw320_ch_num) * DPD_PER_CH_GT_BW20_SIZE;
+-
+-	return dpd_size;
+-}
+-
+ static int
+ mt7996_eeprom_load_bin(struct mt7996_dev *dev)
+ {
+diff --git a/mt7996/eeprom.h b/mt7996/eeprom.h
+index 9a15b4462..fa9c31e7a 100644
+--- a/mt7996/eeprom.h
++++ b/mt7996/eeprom.h
+@@ -45,36 +45,69 @@ enum mt7996_eeprom_field {
+ #define MT_EE_WIFI_CAL_DPD			GENMASK(5, 3)
+ 
+ #define MT_EE_CAL_UNIT				1024
+-#define MT_EE_CAL_GROUP_SIZE_2G			(4 * MT_EE_CAL_UNIT)
+-#define MT_EE_CAL_GROUP_SIZE_5G			(45 * MT_EE_CAL_UNIT)
+-#define MT_EE_CAL_GROUP_SIZE_6G			(125 * MT_EE_CAL_UNIT)
+-#define MT_EE_CAL_ADCDCOC_SIZE_2G		(4 * 4)
+-#define MT_EE_CAL_ADCDCOC_SIZE_5G		(4 * 4)
+-#define MT_EE_CAL_ADCDCOC_SIZE_6G		(4 * 5)
+-#define MT_EE_CAL_GROUP_SIZE			(MT_EE_CAL_GROUP_SIZE_2G + \
+-						 MT_EE_CAL_GROUP_SIZE_5G + \
+-						 MT_EE_CAL_GROUP_SIZE_6G + \
+-						 MT_EE_CAL_ADCDCOC_SIZE_2G + \
+-						 MT_EE_CAL_ADCDCOC_SIZE_5G + \
+-						 MT_EE_CAL_ADCDCOC_SIZE_6G)
+-
+-#define DPD_PER_CH_LEGACY_SIZE			(4 * MT_EE_CAL_UNIT)
+-#define DPD_PER_CH_MEM_SIZE			(13 * MT_EE_CAL_UNIT)
+-#define DPD_PER_CH_OTFG0_SIZE			(2 * MT_EE_CAL_UNIT)
+-#define DPD_PER_CH_BW20_SIZE			(DPD_PER_CH_LEGACY_SIZE + DPD_PER_CH_OTFG0_SIZE)
+-#define DPD_PER_CH_GT_BW20_SIZE			(DPD_PER_CH_MEM_SIZE + DPD_PER_CH_OTFG0_SIZE)
+-#define MT_EE_CAL_DPD_SIZE			(780 * MT_EE_CAL_UNIT)
++
++enum mt7996_prek_rev {
++	GROUP_SIZE_2G,
++	GROUP_SIZE_5G,
++	GROUP_SIZE_6G,
++	ADCDCOC_SIZE_2G,
++	ADCDCOC_SIZE_5G,
++	ADCDCOC_SIZE_6G,
++	DPD_LEGACY_SIZE,
++	DPD_MEM_SIZE,
++	DPD_OTFG0_SIZE,
++};
++
++static const u32 mt7996_prek_rev[] = {
++	[GROUP_SIZE_2G] =			4 * MT_EE_CAL_UNIT,
++	[GROUP_SIZE_5G] =			45 * MT_EE_CAL_UNIT,
++	[GROUP_SIZE_6G] =			125 * MT_EE_CAL_UNIT,
++	[ADCDCOC_SIZE_2G] =			4 * 4,
++	[ADCDCOC_SIZE_5G] =			4 * 4,
++	[ADCDCOC_SIZE_6G] =			4 * 5,
++	[DPD_LEGACY_SIZE] =			4 * MT_EE_CAL_UNIT,
++	[DPD_MEM_SIZE] =			13 * MT_EE_CAL_UNIT,
++	[DPD_OTFG0_SIZE] =			2 * MT_EE_CAL_UNIT,
++};
++
++/* kite 2/5g config */
++static const u32 mt7992_prek_rev[] = {
++	[GROUP_SIZE_2G] =			4 * MT_EE_CAL_UNIT,
++	[GROUP_SIZE_5G] =			110 * MT_EE_CAL_UNIT,
++	[GROUP_SIZE_6G] =			0,
++	[ADCDCOC_SIZE_2G] =			4 * 4,
++	[ADCDCOC_SIZE_5G] =			4 * 5,
++	[ADCDCOC_SIZE_6G] =			0,
++	[DPD_LEGACY_SIZE] =			5 * MT_EE_CAL_UNIT,
++	[DPD_MEM_SIZE] =			16 * MT_EE_CAL_UNIT,
++	[DPD_OTFG0_SIZE] =			2 * MT_EE_CAL_UNIT,
++};
+ 
+ extern const struct ieee80211_channel dpd_2g_ch_list_bw20[];
+-extern const u32 dpd_2g_bw20_ch_num;
+ extern const struct ieee80211_channel dpd_5g_skip_ch_list[];
+-extern const u32 dpd_5g_skip_ch_num;
++extern const struct ieee80211_channel dpd_5g_ch_list_bw80[];
+ extern const struct ieee80211_channel dpd_5g_ch_list_bw160[];
+-extern const u32 dpd_5g_bw160_ch_num;
++extern const struct ieee80211_channel dpd_6g_ch_list_bw80[];
+ extern const struct ieee80211_channel dpd_6g_ch_list_bw160[];
+-extern const u32 dpd_6g_bw160_ch_num;
+ extern const struct ieee80211_channel dpd_6g_ch_list_bw320[];
+-extern const u32 dpd_6g_bw320_ch_num;
++
++#define PREK(id)				(dev->prek.rev[(id)])
++#define DPD_CH_NUM(_type)			(dev->prek.dpd_ch_num[DPD_CH_NUM_##_type])
++#define MT_EE_CAL_GROUP_SIZE			(PREK(GROUP_SIZE_2G) + PREK(GROUP_SIZE_5G) + \
++						 PREK(GROUP_SIZE_6G) + PREK(ADCDCOC_SIZE_2G) + \
++						 PREK(ADCDCOC_SIZE_5G) + PREK(ADCDCOC_SIZE_6G))
++#define DPD_PER_CH_BW20_SIZE			(PREK(DPD_LEGACY_SIZE) + PREK(DPD_OTFG0_SIZE))
++#define DPD_PER_CH_GT_BW20_SIZE			(PREK(DPD_MEM_SIZE) + PREK(DPD_OTFG0_SIZE))
++#define MT_EE_CAL_DPD_SIZE_2G			(DPD_CH_NUM(BW20_2G) * DPD_PER_CH_BW20_SIZE)
++#define MT_EE_CAL_DPD_SIZE_5G			(DPD_CH_NUM(BW20_5G) * DPD_PER_CH_BW20_SIZE + \
++						 DPD_CH_NUM(BW80_5G) * DPD_PER_CH_GT_BW20_SIZE + \
++						 DPD_CH_NUM(BW160_5G) * DPD_PER_CH_GT_BW20_SIZE)
++#define MT_EE_CAL_DPD_SIZE_6G			(DPD_CH_NUM(BW20_6G) * DPD_PER_CH_BW20_SIZE + \
++						 DPD_CH_NUM(BW80_6G) * DPD_PER_CH_GT_BW20_SIZE + \
++						 DPD_CH_NUM(BW160_6G) * DPD_PER_CH_GT_BW20_SIZE + \
++						 DPD_CH_NUM(BW320_6G) * DPD_PER_CH_GT_BW20_SIZE)
++#define MT_EE_CAL_DPD_SIZE			(MT_EE_CAL_DPD_SIZE_2G + MT_EE_CAL_DPD_SIZE_5G + \
++						 MT_EE_CAL_DPD_SIZE_6G)
+ 
+ #define RF_DPD_FLAT_CAL				BIT(28)
+ #define RF_PRE_CAL				BIT(29)
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 5a28ef1c2..de223c6de 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -3751,13 +3751,11 @@ int mt7996_mcu_apply_tx_dpd(struct mt7996_phy *phy)
+ 	enum nl80211_chan_width bw = chandef->width;
+ 	const struct ieee80211_channel *chan_list;
+ 	u32 cal_id, chan_list_size, base_offset = 0, offs = MT_EE_DO_PRE_CAL;
+-	u32 dpd_size_2g, dpd_size_5g, per_chan_size = DPD_PER_CH_BW20_SIZE;
++	u32 per_chan_size = DPD_PER_CH_BW20_SIZE;
+ 	u16 channel = ieee80211_frequency_to_channel(chandef->center_freq1);
+ 	u8 dpd_mask, *cal = dev->cal, *eeprom = dev->mt76.eeprom.data;
+ 	int idx, i, ret;
+-
+-	dpd_size_2g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_2GHZ);
+-	dpd_size_5g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_5GHZ);
++	bool has_skip_ch = (band == NL80211_BAND_5GHZ);
+ 
+ 	switch (band) {
+ 	case NL80211_BAND_2GHZ:
+@@ -3773,27 +3771,35 @@ int mt7996_mcu_apply_tx_dpd(struct mt7996_phy *phy)
+ 			return 0;
+ 		cal_id = RF_DPD_FLAT_CAL;
+ 		chan_list = dpd_2g_ch_list_bw20;
+-		chan_list_size = dpd_2g_bw20_ch_num;
++		chan_list_size = DPD_CH_NUM(BW20_2G);
+ 		break;
+ 	case NL80211_BAND_5GHZ:
+ 		dpd_mask = MT_EE_WIFI_CAL_DPD_5G;
+ 		cal_id = RF_DPD_FLAT_5G_CAL;
+ 		chan_list = mphy->sband_5g.sband.channels;
+ 		chan_list_size = mphy->sband_5g.sband.n_channels;
+-		base_offset += dpd_size_2g;
++		base_offset += MT_EE_CAL_DPD_SIZE_2G;
+ 		if (bw == NL80211_CHAN_WIDTH_160) {
+-			base_offset += (mphy->sband_5g.sband.n_channels - dpd_5g_skip_ch_num) *
+-				       DPD_PER_CH_BW20_SIZE;
++			base_offset += DPD_CH_NUM(BW20_5G) * DPD_PER_CH_BW20_SIZE +
++				       DPD_CH_NUM(BW80_5G) * DPD_PER_CH_GT_BW20_SIZE;
+ 			per_chan_size = DPD_PER_CH_GT_BW20_SIZE;
+ 			cal_id = RF_DPD_FLAT_5G_MEM_CAL;
+ 			chan_list = dpd_5g_ch_list_bw160;
+-			chan_list_size = dpd_5g_bw160_ch_num;
++			chan_list_size = DPD_CH_NUM(BW160_5G);
++			has_skip_ch = false;
++		} else if (is_mt7992(&dev->mt76) && bw == NL80211_CHAN_WIDTH_80) {
++			base_offset += DPD_CH_NUM(BW20_5G) * DPD_PER_CH_BW20_SIZE;
++			per_chan_size = DPD_PER_CH_GT_BW20_SIZE;
++			cal_id = RF_DPD_FLAT_5G_MEM_CAL;
++			chan_list = dpd_5g_ch_list_bw80;
++			chan_list_size = DPD_CH_NUM(BW80_5G);
++			has_skip_ch = false;
+ 		} else if (bw > NL80211_CHAN_WIDTH_20) {
+ 			/* apply (center channel - 2)'s dpd cal data for bw 40/80 channels */
+ 			channel -= 2;
+ 		}
+ 		if (channel >= dpd_5g_skip_ch_list[0].hw_value &&
+-		    channel <= dpd_5g_skip_ch_list[dpd_5g_skip_ch_num - 1].hw_value)
++		    channel <= dpd_5g_skip_ch_list[DPD_CH_NUM(BW20_5G_SKIP) - 1].hw_value)
+ 			return 0;
+ 		break;
+ 	case NL80211_BAND_6GHZ:
+@@ -3801,20 +3807,27 @@ int mt7996_mcu_apply_tx_dpd(struct mt7996_phy *phy)
+ 		cal_id = RF_DPD_FLAT_6G_CAL;
+ 		chan_list = mphy->sband_6g.sband.channels;
+ 		chan_list_size = mphy->sband_6g.sband.n_channels;
+-		base_offset += dpd_size_2g + dpd_size_5g;
++		base_offset += MT_EE_CAL_DPD_SIZE_2G + MT_EE_CAL_DPD_SIZE_5G;
+ 		if (bw == NL80211_CHAN_WIDTH_160) {
+ 			base_offset += mphy->sband_6g.sband.n_channels * DPD_PER_CH_BW20_SIZE;
+ 			per_chan_size = DPD_PER_CH_GT_BW20_SIZE;
+ 			cal_id = RF_DPD_FLAT_6G_MEM_CAL;
+ 			chan_list = dpd_6g_ch_list_bw160;
+-			chan_list_size = dpd_6g_bw160_ch_num;
+-		} else if (bw == NL80211_CHAN_WIDTH_320) {
++			chan_list_size = DPD_CH_NUM(BW160_6G);
++		} else if (is_mt7996(&dev->mt76) && bw == NL80211_CHAN_WIDTH_320) {
+ 			base_offset += mphy->sband_6g.sband.n_channels * DPD_PER_CH_BW20_SIZE +
+-				       dpd_6g_bw160_ch_num * DPD_PER_CH_GT_BW20_SIZE;
++				       DPD_CH_NUM(BW80_6G) * DPD_PER_CH_GT_BW20_SIZE +
++				       DPD_CH_NUM(BW160_6G) * DPD_PER_CH_GT_BW20_SIZE;
+ 			per_chan_size = DPD_PER_CH_GT_BW20_SIZE;
+ 			cal_id = RF_DPD_FLAT_6G_MEM_CAL;
+ 			chan_list = dpd_6g_ch_list_bw320;
+-			chan_list_size = dpd_6g_bw320_ch_num;
++			chan_list_size = DPD_CH_NUM(BW320_6G);
++		} else if (is_mt7992(&dev->mt76) && bw == NL80211_CHAN_WIDTH_80) {
++			base_offset += mphy->sband_6g.sband.n_channels * DPD_PER_CH_BW20_SIZE;
++			per_chan_size = DPD_PER_CH_GT_BW20_SIZE;
++			cal_id = RF_DPD_FLAT_6G_MEM_CAL;
++			chan_list = dpd_6g_ch_list_bw80;
++			chan_list_size = DPD_CH_NUM(BW80_6G);
+ 		} else if (bw > NL80211_CHAN_WIDTH_20) {
+ 			/* apply (center channel - 2)'s dpd cal data for bw 40/80 channels */
+ 			channel -= 2;
+@@ -3834,9 +3847,8 @@ int mt7996_mcu_apply_tx_dpd(struct mt7996_phy *phy)
+ 	if (idx == chan_list_size)
+ 		return -EINVAL;
+ 
+-	if (band == NL80211_BAND_5GHZ && bw != NL80211_CHAN_WIDTH_160 &&
+-	    channel > dpd_5g_skip_ch_list[dpd_5g_skip_ch_num - 1].hw_value)
+-		idx -= dpd_5g_skip_ch_num;
++	if (has_skip_ch && channel > dpd_5g_skip_ch_list[DPD_CH_NUM(BW20_5G_SKIP) - 1].hw_value)
++		idx -= DPD_CH_NUM(BW20_5G_SKIP);
+ 
+ 	cal += MT_EE_CAL_GROUP_SIZE + base_offset + idx * per_chan_size;
+ 
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index eff2e1be9..324114363 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -195,6 +195,19 @@ struct mt7996_twt_flow {
+ 
+ DECLARE_EWMA(avg_signal, 10, 8)
+ 
++enum mt7996_dpd_ch_num {
++	DPD_CH_NUM_BW20_2G,
++	DPD_CH_NUM_BW20_5G,
++	DPD_CH_NUM_BW20_5G_SKIP,
++	DPD_CH_NUM_BW80_5G,
++	DPD_CH_NUM_BW160_5G,
++	DPD_CH_NUM_BW20_6G,
++	DPD_CH_NUM_BW80_6G,
++	DPD_CH_NUM_BW160_6G,
++	DPD_CH_NUM_BW320_6G,
++	DPD_CH_NUM_TYPE_MAX,
++};
++
+ struct mt7996_sta {
+ 	struct mt76_wcid wcid; /* must be first */
+ 
+@@ -457,6 +470,10 @@ struct mt7996_dev {
+ 
+ 	void *cal;
+ 	u32 cur_prek_offset;
++	struct {
++		const u32 *rev;
++		u8 dpd_ch_num[DPD_CH_NUM_TYPE_MAX];
++	} prek;
+ 
+ 	struct {
+ 		u16 table_mask;
+@@ -593,7 +610,6 @@ int mt7996_eeprom_parse_hw_cap(struct mt7996_dev *dev, struct mt7996_phy *phy);
+ int mt7996_eeprom_get_target_power(struct mt7996_dev *dev,
+ 				   struct ieee80211_channel *chan);
+ s8 mt7996_eeprom_get_power_delta(struct mt7996_dev *dev, int band);
+-int mt7996_get_dpd_per_band_size(struct mt7996_dev *dev, enum nl80211_band band);
+ int mt7996_dma_init(struct mt7996_dev *dev);
+ void mt7996_dma_reset(struct mt7996_dev *dev, bool force);
+ void mt7996_dma_prefetch(struct mt7996_dev *dev);
+diff --git a/mt7996/testmode.c b/mt7996/testmode.c
+index 01f36fd9b..c82ac562a 100644
+--- a/mt7996/testmode.c
++++ b/mt7996/testmode.c
+@@ -434,7 +434,7 @@ mt7996_tm_set_tx_cont(struct mt7996_phy *phy, bool en)
+ static int
+ mt7996_tm_group_prek(struct mt7996_phy *phy, enum mt76_testmode_state state)
+ {
+-	u8 *eeprom;
++	u8 *eeprom, do_precal;
+ 	u32 i, group_size, dpd_size, size, offs, *pre_cal;
+ 	int ret = 0;
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -462,6 +462,9 @@ mt7996_tm_group_prek(struct mt7996_phy *phy, enum mt76_testmode_state state)
+ 	dpd_size = MT_EE_CAL_DPD_SIZE;
+ 	size = group_size + dpd_size;
+ 	offs = MT_EE_DO_PRE_CAL;
++	do_precal = (MT_EE_WIFI_CAL_GROUP_2G * !!PREK(GROUP_SIZE_2G)) |
++		    (MT_EE_WIFI_CAL_GROUP_5G * !!PREK(GROUP_SIZE_5G)) |
++		    (MT_EE_WIFI_CAL_GROUP_6G * !!PREK(GROUP_SIZE_6G));
+ 
+ 	switch (state) {
+ 	case MT76_TM_STATE_GROUP_PREK:
+@@ -476,13 +479,10 @@ mt7996_tm_group_prek(struct mt7996_phy *phy, enum mt76_testmode_state state)
+ 		wait_event_timeout(mdev->mcu.wait, dev->cur_prek_offset == group_size,
+ 				   30 * HZ);
+ 
+-		if (ret) {
++		if (ret)
+ 			dev_err(dev->mt76.dev, "Group Pre-cal: mcu send msg failed!\n");
+-			return ret;
+-		}
+-
+-		if (!ret)
+-			eeprom[offs] |= MT_EE_WIFI_CAL_GROUP;
++		else
++			eeprom[offs] |= do_precal;
+ 		break;
+ 	case MT76_TM_STATE_GROUP_PREK_DUMP:
+ 		pre_cal = (u32 *)dev->cal;
+@@ -520,10 +520,12 @@ mt7996_tm_dpd_prek_send_req(struct mt7996_phy *phy, struct mt7996_tm_req *req,
+ 	struct mt76_phy *mphy = phy->mt76;
+ 	struct cfg80211_chan_def chandef_backup, *chandef = &mphy->chandef;
+ 	struct ieee80211_channel chan_backup;
+-	int i, ret;
++	int i, ret, skip_ch_num = DPD_CH_NUM(BW20_5G_SKIP);
+ 
+ 	if (!chan_list)
+ 		return -EOPNOTSUPP;
++	if (!channel_size)
++		return 0;
+ 
+ 	req->rf_test.op.rf.param.cal_param.func_data = cpu_to_le32(func_data);
+ 
+@@ -533,7 +535,7 @@ mt7996_tm_dpd_prek_send_req(struct mt7996_phy *phy, struct mt7996_tm_req *req,
+ 	for (i = 0; i < channel_size; i++) {
+ 		if (chan_list[i].band == NL80211_BAND_5GHZ &&
+ 		    chan_list[i].hw_value >= dpd_5g_skip_ch_list[0].hw_value &&
+-		    chan_list[i].hw_value <= dpd_5g_skip_ch_list[dpd_5g_skip_ch_num - 1].hw_value)
++		    chan_list[i].hw_value <= dpd_5g_skip_ch_list[skip_ch_num - 1].hw_value)
+ 			continue;
+ 
+ 		memcpy(chandef->chan, &chan_list[i], sizeof(struct ieee80211_channel));
+@@ -602,11 +604,11 @@ mt7996_tm_dpd_prek(struct mt7996_phy *phy, enum mt76_testmode_state state)
+ 	switch (state) {
+ 	case MT76_TM_STATE_DPD_2G:
+ 		ret = mt7996_tm_dpd_prek_send_req(phy, &req, dpd_2g_ch_list_bw20,
+-						  dpd_2g_bw20_ch_num,
++						  DPD_CH_NUM(BW20_2G),
+ 						  NL80211_CHAN_WIDTH_20, RF_DPD_FLAT_CAL);
+-		wait_on_prek_offset += dpd_2g_bw20_ch_num * DPD_PER_CH_BW20_SIZE;
+-		wait_event_timeout(mdev->mcu.wait,
+-				   dev->cur_prek_offset == wait_on_prek_offset, 30 * HZ);
++		wait_on_prek_offset += DPD_CH_NUM(BW20_2G) * DPD_PER_CH_BW20_SIZE;
++		wait_event_timeout(mdev->mcu.wait, dev->cur_prek_offset == wait_on_prek_offset,
++				   30 * HZ);
+ 
+ 		do_precal = MT_EE_WIFI_CAL_DPD_2G;
+ 		break;
+@@ -617,18 +619,27 @@ mt7996_tm_dpd_prek(struct mt7996_phy *phy, enum mt76_testmode_state state)
+ 						  NL80211_CHAN_WIDTH_20, RF_DPD_FLAT_5G_CAL);
+ 		if (ret)
+ 			return ret;
+-		wait_on_prek_offset += (mphy->sband_5g.sband.n_channels - dpd_5g_skip_ch_num) *
+-				       DPD_PER_CH_BW20_SIZE;
+-		wait_event_timeout(mdev->mcu.wait,
+-				   dev->cur_prek_offset == wait_on_prek_offset, 30 * HZ);
++		wait_on_prek_offset += DPD_CH_NUM(BW20_5G) * DPD_PER_CH_BW20_SIZE;
++		wait_event_timeout(mdev->mcu.wait, dev->cur_prek_offset == wait_on_prek_offset,
++				   30 * HZ);
++
++		/* 5g channel bw80 calibration */
++		ret = mt7996_tm_dpd_prek_send_req(phy, &req, dpd_5g_ch_list_bw80,
++						  DPD_CH_NUM(BW80_5G),
++						  NL80211_CHAN_WIDTH_80, RF_DPD_FLAT_5G_MEM_CAL);
++		if (ret)
++			return ret;
++		wait_on_prek_offset += DPD_CH_NUM(BW80_5G) * DPD_PER_CH_GT_BW20_SIZE;
++		wait_event_timeout(mdev->mcu.wait, dev->cur_prek_offset == wait_on_prek_offset,
++				   30 * HZ);
+ 
+ 		/* 5g channel bw160 calibration */
+ 		ret = mt7996_tm_dpd_prek_send_req(phy, &req, dpd_5g_ch_list_bw160,
+-						  dpd_5g_bw160_ch_num,
++						  DPD_CH_NUM(BW160_5G),
+ 						  NL80211_CHAN_WIDTH_160, RF_DPD_FLAT_5G_MEM_CAL);
+-		wait_on_prek_offset += dpd_5g_bw160_ch_num * DPD_PER_CH_GT_BW20_SIZE;
+-		wait_event_timeout(mdev->mcu.wait,
+-				   dev->cur_prek_offset == wait_on_prek_offset, 30 * HZ);
++		wait_on_prek_offset += DPD_CH_NUM(BW160_5G) * DPD_PER_CH_GT_BW20_SIZE;
++		wait_event_timeout(mdev->mcu.wait, dev->cur_prek_offset == wait_on_prek_offset,
++				   30 * HZ);
+ 
+ 		do_precal = MT_EE_WIFI_CAL_DPD_5G;
+ 		break;
+@@ -639,27 +650,37 @@ mt7996_tm_dpd_prek(struct mt7996_phy *phy, enum mt76_testmode_state state)
+ 						  NL80211_CHAN_WIDTH_20, RF_DPD_FLAT_6G_CAL);
+ 		if (ret)
+ 			return ret;
+-		wait_on_prek_offset += mphy->sband_6g.sband.n_channels * DPD_PER_CH_BW20_SIZE;
+-		wait_event_timeout(mdev->mcu.wait,
+-				   dev->cur_prek_offset == wait_on_prek_offset, 30 * HZ);
++		wait_on_prek_offset += DPD_CH_NUM(BW20_6G) * DPD_PER_CH_BW20_SIZE;
++		wait_event_timeout(mdev->mcu.wait, dev->cur_prek_offset == wait_on_prek_offset,
++				   30 * HZ);
++
++		/* 6g channel bw80 calibration */
++		ret = mt7996_tm_dpd_prek_send_req(phy, &req, dpd_6g_ch_list_bw80,
++						  DPD_CH_NUM(BW80_6G),
++						  NL80211_CHAN_WIDTH_80, RF_DPD_FLAT_6G_MEM_CAL);
++		if (ret)
++			return ret;
++		wait_on_prek_offset += DPD_CH_NUM(BW80_6G) * DPD_PER_CH_GT_BW20_SIZE;
++		wait_event_timeout(mdev->mcu.wait, dev->cur_prek_offset == wait_on_prek_offset,
++				   30 * HZ);
+ 
+ 		/* 6g channel bw160 calibration */
+ 		ret = mt7996_tm_dpd_prek_send_req(phy, &req, dpd_6g_ch_list_bw160,
+-						  dpd_6g_bw160_ch_num,
++						  DPD_CH_NUM(BW160_6G),
+ 						  NL80211_CHAN_WIDTH_160, RF_DPD_FLAT_6G_MEM_CAL);
+ 		if (ret)
+ 			return ret;
+-		wait_on_prek_offset += dpd_6g_bw160_ch_num * DPD_PER_CH_GT_BW20_SIZE;
+-		wait_event_timeout(mdev->mcu.wait,
+-				   dev->cur_prek_offset == wait_on_prek_offset, 30 * HZ);
++		wait_on_prek_offset += DPD_CH_NUM(BW160_6G) * DPD_PER_CH_GT_BW20_SIZE;
++		wait_event_timeout(mdev->mcu.wait, dev->cur_prek_offset == wait_on_prek_offset,
++				   30 * HZ);
+ 
+ 		/* 6g channel bw320 calibration */
+ 		ret = mt7996_tm_dpd_prek_send_req(phy, &req, dpd_6g_ch_list_bw320,
+-						  dpd_6g_bw320_ch_num,
++						  DPD_CH_NUM(BW320_6G),
+ 						  NL80211_CHAN_WIDTH_320, RF_DPD_FLAT_6G_MEM_CAL);
+-		wait_on_prek_offset += dpd_6g_bw320_ch_num * DPD_PER_CH_GT_BW20_SIZE;
+-		wait_event_timeout(mdev->mcu.wait,
+-				   dev->cur_prek_offset == wait_on_prek_offset, 30 * HZ);
++		wait_on_prek_offset += DPD_CH_NUM(BW320_6G) * DPD_PER_CH_GT_BW20_SIZE;
++		wait_event_timeout(mdev->mcu.wait, dev->cur_prek_offset == wait_on_prek_offset,
++				   30 * HZ);
+ 
+ 		do_precal = MT_EE_WIFI_CAL_DPD_6G;
+ 		break;
+@@ -732,9 +753,9 @@ mt7996_tm_dump_precal(struct mt76_phy *mphy, struct sk_buff *msg, int flag, int
+ 	eeprom = dev->mt76.eeprom.data;
+ 	offs = MT_EE_DO_PRE_CAL;
+ 
+-	dpd_size_2g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_2GHZ);
+-	dpd_size_5g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_5GHZ);
+-	dpd_size_6g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_6GHZ);
++	dpd_size_2g = MT_EE_CAL_DPD_SIZE_2G;
++	dpd_size_5g = MT_EE_CAL_DPD_SIZE_5G;
++	dpd_size_6g = MT_EE_CAL_DPD_SIZE_6G;
+ 
+ 	switch (type) {
+ 	case PREK_SYNC_ALL:
+@@ -810,9 +831,9 @@ mt7996_tm_re_cal_event(struct mt7996_dev *dev, struct mt7996_tm_rf_test_result *
+ 	u8 *pre_cal;
+ 
+ 	pre_cal = dev->cal;
+-	dpd_size_2g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_2GHZ);
+-	dpd_size_5g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_5GHZ);
+-	dpd_size_6g = mt7996_get_dpd_per_band_size(dev, NL80211_BAND_6GHZ);
++	dpd_size_2g = MT_EE_CAL_DPD_SIZE_2G;
++	dpd_size_5g = MT_EE_CAL_DPD_SIZE_5G;
++	dpd_size_6g = MT_EE_CAL_DPD_SIZE_6G;
+ 
+ 	cal_idx = le32_to_cpu(data->cal_idx);
+ 	cal_type = le32_to_cpu(data->cal_type);
+diff --git a/testmode.c b/testmode.c
+index a5c07f8dd..09ab68ce4 100644
+--- a/testmode.c
++++ b/testmode.c
+@@ -37,6 +37,11 @@ const struct nla_policy mt76_tm_policy[NUM_MT76_TM_ATTRS] = {
+ };
+ EXPORT_SYMBOL_GPL(mt76_tm_policy);
+ 
++static inline bool mt76_testmode_offload(struct mt76_dev *dev)
++{
++	return is_mt7996(dev) || is_mt7992(dev);
++}
++
+ void mt76_testmode_tx_pending(struct mt76_phy *phy)
+ {
+ 	struct mt76_testmode_data *td = &phy->test;
+@@ -197,7 +202,7 @@ mt76_testmode_tx_init(struct mt76_phy *phy)
+ 	u8 max_nss = hweight8(phy->antenna_mask);
+ 	int ret;
+ 
+-	if (is_mt7996(phy->dev))
++	if (mt76_testmode_offload(phy->dev))
+ 		return 0;
+ 
+ 	ret = mt76_testmode_alloc_skb(phy, td->tx_mpdu_len);
+@@ -293,7 +298,7 @@ mt76_testmode_tx_start(struct mt76_phy *phy)
+ 	td->tx_done = 0;
+ 	td->tx_pending = td->tx_count;
+ 
+-	if (!is_mt7996(dev))
++	if (!mt76_testmode_offload(dev))
+ 		mt76_worker_schedule(&dev->tx_worker);
+ }
+ 
+@@ -303,7 +308,7 @@ mt76_testmode_tx_stop(struct mt76_phy *phy)
+ 	struct mt76_testmode_data *td = &phy->test;
+ 	struct mt76_dev *dev = phy->dev;
+ 
+-	if (is_mt7996(dev) && dev->test_ops->tx_stop) {
++	if (mt76_testmode_offload(dev) && dev->test_ops->tx_stop) {
+ 		dev->test_ops->tx_stop(phy);
+ 		return;
+ 	}
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0064-mtk-wifi-mt76-mt7996-assign-DEAUTH-to-ALTX-queue-for.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0064-mtk-wifi-mt76-mt7996-assign-DEAUTH-to-ALTX-queue-for.patch
new file mode 100644
index 0000000..4215862
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0064-mtk-wifi-mt76-mt7996-assign-DEAUTH-to-ALTX-queue-for.patch
@@ -0,0 +1,43 @@
+From 84a309502aa66ff12ce8d0e46c7328ed274f8e13 Mon Sep 17 00:00:00 2001
+From: Michael-CY Lee <michael-cy.lee@mediatek.com>
+Date: Tue, 14 Nov 2023 11:27:06 +0800
+Subject: [PATCH 064/120] mtk: wifi: mt76: mt7996: assign DEAUTH to ALTX queue
+ for CERT
+
+Signed-off-by: Michael-CY Lee <michael-cy.lee@mediatek.com>
+CR-Id: WCNCR00289305
+---
+ mt7996/mac.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 9edccfed8..9c1c7c45d 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -753,6 +753,8 @@ static void
+ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi,
+ 			    struct sk_buff *skb, struct ieee80211_key_conf *key)
+ {
++	struct mt76_phy *mphy =
++		mt76_dev_phy(&dev->mt76, le32_get_bits(txwi[1], MT_TXD1_TGID));
+ 	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+ 	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+ 	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+@@ -762,6 +764,14 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi,
+ 	u8 fc_type, fc_stype;
+ 	u32 val;
+ 
++	if (ieee80211_is_cert_mode(mphy->hw) && ieee80211_is_deauth(fc)) {
++		/* In WPA3 cert TC-4.8.1, the deauth must be transmitted without
++		 * considering PSM bit
++		 */
++		txwi[0] &= ~cpu_to_le32(MT_TXD0_Q_IDX);
++		txwi[0] |= cpu_to_le32(FIELD_PREP(MT_TXD0_Q_IDX, MT_LMAC_ALTX0));
++	}
++
+ 	if (ieee80211_is_action(fc) &&
+ 	    mgmt->u.action.category == WLAN_CATEGORY_BACK &&
+ 	    mgmt->u.action.u.addba_req.action_code == WLAN_ACTION_ADDBA_REQ)
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0065-mtk-wifi-mt76-mt7996-add-no_beacon-vendor-command-fo.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0065-mtk-wifi-mt76-mt7996-add-no_beacon-vendor-command-fo.patch
new file mode 100644
index 0000000..c936f3c
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0065-mtk-wifi-mt76-mt7996-add-no_beacon-vendor-command-fo.patch
@@ -0,0 +1,155 @@
+From 7f446fe841ec74041662ef7942d09ab0ad77be8a Mon Sep 17 00:00:00 2001
+From: MeiChia Chiu <meichia.chiu@mediatek.com>
+Date: Wed, 22 Nov 2023 22:42:09 +0800
+Subject: [PATCH 065/120] mtk: wifi: mt76: mt7996: add no_beacon vendor command
+ for cert
+
+Add the vendor command to disable/enable beacon
+
+[Usage]
+hostapd_cli -i <interface> no_beacon <value>
+<value>
+0: enable beacon
+1: disable beacon
+
+CR-ID: WCNCR00240597
+Change-Id: Ia16707317135aeb02d6a5f6d50983e5174cc9e77
+Signed-off-by: MeiChia Chiu <meichia.chiu@mediatek.com>
+---
+ mt7996/mcu.c    | 11 +++++++++++
+ mt7996/mt7996.h |  1 +
+ mt7996/vendor.c | 41 +++++++++++++++++++++++++++++++++++++++++
+ mt7996/vendor.h | 12 ++++++++++++
+ 4 files changed, 65 insertions(+)
+
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index de223c6de..ec9705c9a 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -5076,4 +5076,15 @@ void mt7996_set_wireless_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
+ 		break;
+ 	}
+ }
++
++void mt7996_set_beacon_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
++{
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct ieee80211_hw *hw = mvif->phy->mt76->hw;
++	u8 val = *((u8 *)data);
++
++	vif->bss_conf.enable_beacon = val;
++
++	mt7996_mcu_add_beacon(hw, vif, val);
++}
+ #endif
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 324114363..0945894d1 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -830,6 +830,7 @@ void mt7996_set_wireless_amsdu(struct ieee80211_hw *hw, u8 en);
+ void mt7996_mcu_set_mimo(struct mt7996_phy *phy);
+ int mt7996_set_muru_cfg(struct mt7996_phy *phy, u8 action, u8 val);
+ int mt7996_mcu_set_muru_cfg(struct mt7996_phy *phy, void *data);
++void mt7996_set_beacon_vif(void *data, u8 *mac, struct ieee80211_vif *vif);
+ #endif
+ 
+ int mt7996_mcu_edcca_enable(struct mt7996_phy *phy, bool enable);
+diff --git a/mt7996/vendor.c b/mt7996/vendor.c
+index 9732ed287..c87cc5c1d 100644
+--- a/mt7996/vendor.c
++++ b/mt7996/vendor.c
+@@ -112,6 +112,11 @@ pp_ctrl_policy[NUM_MTK_VENDOR_ATTRS_PP_CTRL] = {
+ 	[MTK_VENDOR_ATTR_PP_MODE] = { .type = NLA_U8 },
+ };
+ 
++static const struct nla_policy
++beacon_ctrl_policy[NUM_MTK_VENDOR_ATTRS_BEACON_CTRL] = {
++	[MTK_VENDOR_ATTR_BEACON_CTRL_MODE] = { .type = NLA_U8 },
++};
++
+ struct mt7996_amnt_data {
+ 	u8 idx;
+ 	u8 addr[ETH_ALEN];
+@@ -904,6 +909,31 @@ static int mt7996_vendor_pp_ctrl(struct wiphy *wiphy, struct wireless_dev *wdev,
+ 	return err;
+ }
+ 
++static int mt7996_vendor_beacon_ctrl(struct wiphy *wiphy,
++				     struct wireless_dev *wdev,
++				     const void *data,
++				     int data_len)
++{
++	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
++	struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_BEACON_CTRL];
++	int err;
++	u8 val8;
++
++	err = nla_parse(tb, MTK_VENDOR_ATTR_BEACON_CTRL_MAX, data, data_len,
++			beacon_ctrl_policy, NULL);
++	if (err)
++		return err;
++
++	if (tb[MTK_VENDOR_ATTR_BEACON_CTRL_MODE]) {
++		val8 = nla_get_u8(tb[MTK_VENDOR_ATTR_BEACON_CTRL_MODE]);
++		ieee80211_iterate_active_interfaces_atomic(hw, IEEE80211_IFACE_ITER_RESUME_ALL,
++				mt7996_set_beacon_vif, &val8);
++	}
++
++	return 0;
++}
++
++
+ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 	{
+ 		.info = {
+@@ -1020,6 +1050,17 @@ static const struct wiphy_vendor_command mt7996_vendor_commands[] = {
+ 		.policy = pp_ctrl_policy,
+ 		.maxattr = MTK_VENDOR_ATTR_PP_CTRL_MAX,
+ 	},
++	{
++		.info = {
++			.vendor_id = MTK_NL80211_VENDOR_ID,
++			.subcmd = MTK_NL80211_VENDOR_SUBCMD_BEACON_CTRL,
++		},
++		.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
++			 WIPHY_VENDOR_CMD_NEED_RUNNING,
++		.doit = mt7996_vendor_beacon_ctrl,
++		.policy = beacon_ctrl_policy,
++		.maxattr = MTK_VENDOR_ATTR_BEACON_CTRL_MAX,
++	},
+ };
+ 
+ void mt7996_vendor_register(struct mt7996_phy *phy)
+diff --git a/mt7996/vendor.h b/mt7996/vendor.h
+index 981289658..e7d88828c 100644
+--- a/mt7996/vendor.h
++++ b/mt7996/vendor.h
+@@ -16,6 +16,7 @@ enum mtk_nl80211_vendor_subcmds {
+ 	MTK_NL80211_VENDOR_SUBCMD_BSS_COLOR_CTRL = 0xca,
+ 	MTK_NL80211_VENDOR_SUBCMD_BACKGROUND_RADAR_CTRL = 0xcb,
+ 	MTK_NL80211_VENDOR_SUBCMD_PP_CTRL = 0xcc,
++	MTK_NL80211_VENDOR_SUBCMD_BEACON_CTRL = 0xcd,
+ };
+ 
+ enum mtk_vendor_attr_edcca_ctrl {
+@@ -226,6 +227,17 @@ enum mtk_vendor_attr_pp_ctrl {
+ 		NUM_MTK_VENDOR_ATTRS_PP_CTRL - 1
+ };
+ 
++enum mtk_vendor_attr_beacon_ctrl {
++	MTK_VENDOR_ATTR_BEACON_CTRL_UNSPEC,
++
++	MTK_VENDOR_ATTR_BEACON_CTRL_MODE,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_BEACON_CTRL,
++	MTK_VENDOR_ATTR_BEACON_CTRL_MAX =
++		NUM_MTK_VENDOR_ATTRS_BEACON_CTRL - 1
++};
++
+ #endif
+ 
+ #endif
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0066-mtk-wifi-mt76-mt7996-add-adie-efuse-merge-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0066-mtk-wifi-mt76-mt7996-add-adie-efuse-merge-support.patch
new file mode 100644
index 0000000..5a3e9e1
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0066-mtk-wifi-mt76-mt7996-add-adie-efuse-merge-support.patch
@@ -0,0 +1,267 @@
+From 6206d1f200b980e4d2e863f2ceb99afc5a81bfcb Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Fri, 24 Nov 2023 09:49:08 +0800
+Subject: [PATCH 066/120] mtk: wifi: mt76: mt7996: add adie efuse merge support
+
+Merge adie-dependent parameters in efuse into eeprom after FT.
+Note that Eagle BE14000 is not considered yet.
+Add efuse dump command.
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Change-Id: Ib088b90147c75d7437f40dd3569e3584c6ff9ab0
+---
+ mt7996/debugfs.c |  41 ++++++++++++++
+ mt7996/eeprom.c  | 141 +++++++++++++++++++++++++++++++++++++++++++++++
+ mt7996/mcu.c     |   6 +-
+ 3 files changed, 186 insertions(+), 2 deletions(-)
+
+diff --git a/mt7996/debugfs.c b/mt7996/debugfs.c
+index da47a5480..2f76b15f6 100644
+--- a/mt7996/debugfs.c
++++ b/mt7996/debugfs.c
+@@ -867,6 +867,46 @@ mt7996_rf_regval_set(void *data, u64 val)
+ DEFINE_DEBUGFS_ATTRIBUTE(fops_rf_regval, mt7996_rf_regval_get,
+ 			 mt7996_rf_regval_set, "0x%08llx\n");
+ 
++static ssize_t
++mt7996_efuse_get(struct file *file, char __user *user_buf,
++		 size_t count, loff_t *ppos)
++{
++	struct mt7996_dev *dev = file->private_data;
++	struct mt76_dev *mdev = &dev->mt76;
++	u8 *buff = mdev->otp.data;
++	int i;
++	ssize_t ret;
++	u32 block_num;
++
++	mdev->otp.size = MT7996_EEPROM_SIZE;
++	if (is_mt7996(&dev->mt76) && dev->chip_sku == MT7996_SKU_444)
++		mdev->otp.size += 3 * MT_EE_CAL_UNIT;
++
++	if (!mdev->otp.data) {
++		mdev->otp.data = devm_kzalloc(mdev->dev, mdev->otp.size, GFP_KERNEL);
++		if (!mdev->otp.data)
++			return -ENOMEM;
++
++		block_num = DIV_ROUND_UP(mdev->otp.size, MT7996_EEPROM_BLOCK_SIZE);
++		for (i = 0; i < block_num; i++) {
++			buff = mdev->otp.data + i * MT7996_EEPROM_BLOCK_SIZE;
++			ret = mt7996_mcu_get_eeprom(dev, i * MT7996_EEPROM_BLOCK_SIZE, buff);
++			if (ret && ret != -EINVAL)
++				return ret;
++		}
++	}
++
++	ret = simple_read_from_buffer(user_buf, count, ppos, mdev->otp.data, mdev->otp.size);
++
++	return ret;
++}
++
++static const struct file_operations mt7996_efuse_ops = {
++	.read = mt7996_efuse_get,
++	.open = simple_open,
++	.llseek = default_llseek,
++};
++
+ int mt7996_init_debugfs(struct mt7996_phy *phy)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -893,6 +933,7 @@ int mt7996_init_debugfs(struct mt7996_phy *phy)
+ 	debugfs_create_devm_seqfile(dev->mt76.dev, "twt_stats", dir,
+ 				    mt7996_twt_stats);
+ 	debugfs_create_file("rf_regval", 0600, dir, dev, &fops_rf_regval);
++	debugfs_create_file("otp", 0400, dir, dev, &mt7996_efuse_ops);
+ 
+ 	if (phy->mt76->cap.has_5ghz) {
+ 		debugfs_create_u32("dfs_hw_pattern", 0400, dir,
+diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
+index c2088e2fa..41d535408 100644
+--- a/mt7996/eeprom.c
++++ b/mt7996/eeprom.c
+@@ -510,6 +510,143 @@ static int mt7996_eeprom_load_precal(struct mt7996_dev *dev)
+ 	return mt76_get_of_data_from_nvmem(mdev, dev->cal, "precal", size);
+ }
+ 
++static int mt7996_apply_cal_free_data(struct mt7996_dev *dev)
++{
++#define MT_EE_CAL_FREE_MAX_SIZE		30
++#define MT_EE_7977BN_OFFSET		(0x1200 - 0x500)
++#define MT_EE_END_OFFSET		0xffff
++	enum adie_type {
++		ADIE_7975,
++		ADIE_7976,
++		ADIE_7977,
++		ADIE_7978,
++		ADIE_7979,
++	};
++	static const u16 adie_offs_list[][MT_EE_CAL_FREE_MAX_SIZE] = {
++		[ADIE_7975] = {0x5cd, 0x5cf, 0x5d1, 0x5d3, 0x6c0, 0x6c1, 0x6c2, 0x6c3,
++			       0x7a1, 0x7a6, 0x7a8, 0x7aa, -1},
++		[ADIE_7976] = {0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x53, 0x55, 0x57, 0x59,
++			       0x70, 0x71, 0x790, 0x791, 0x794, 0x795, 0x7a6, 0x7a8, 0x7aa, -1},
++		[ADIE_7977] = {0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x53, 0x55, 0x57, 0x59,
++			       0x69, 0x6a, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, -1},
++		[ADIE_7978] = {0x91, 0x95, 0x100, 0x102, 0x104, 0x106, 0x107,
++			       0x108, 0x109, 0x10a, 0x10b, 0x10c, 0x10e, 0x110, -1},
++		[ADIE_7979] = {0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x53, 0x55, 0x57, 0x59,
++			       0x69, 0x6a, 0x7a, 0x7b, 0x7c, 0x7e, 0x80, -1},
++	};
++	static const u16 eep_offs_list[][MT_EE_CAL_FREE_MAX_SIZE] = {
++		[ADIE_7975] = {0x451, 0x453, 0x455, 0x457, 0x44c, 0x44d, 0x44e, 0x44f,
++			       0xba1, 0xba6, 0xba8, 0xbaa, -1},
++		[ADIE_7976] = {0x44c, 0x44d, 0x44e, 0x44f, 0x450,
++			       0x451, 0x453, 0x455, 0x457, 0x459,
++			       0x470, 0x471, 0xb90, 0xb91, 0xb94, 0xb95,
++			       0xba6, 0xba8, 0xbaa, -1},
++		[ADIE_7977] = {0x124c, 0x124d, 0x124e, 0x124f, 0x1250,
++			       0x1251, 0x1253, 0x1255, 0x1257, 0x1259,
++			       0x1269, 0x126a, 0x127a, 0x127b, 0x127c, 0x127d, 0x127e, -1},
++		[ADIE_7978] = {0xb91, 0xb95, 0x480, 0x482, 0x484, 0x486, 0x487, 0x488, 0x489,
++			       0x48a, 0x48b, 0x48c, 0x48e, 0x490, -1},
++		[ADIE_7979] = {0x124c, 0x124d, 0x124e, 0x124f, 0x1250, 0x1251,
++			       0x1253, 0x1255, 0x1257, 0x1259, 0x1269, 0x126a,
++			       0x127a, 0x127b, 0x127c, 0x127e, 0x1280, -1},
++	};
++	static const u16 adie_base_7996[] = {
++		0x400, 0x1e00, 0x1200
++	};
++	static const u16 adie_base_7992[] = {
++		0x400, 0x1200, 0x0
++	};
++	static const u16 *adie_offs[__MT_MAX_BAND];
++	static const u16 *eep_offs[__MT_MAX_BAND];
++	static const u16 *adie_base;
++	u8 *eeprom = dev->mt76.eeprom.data;
++	u8 buf[MT7996_EEPROM_BLOCK_SIZE];
++	int adie_id, band, i, ret;
++
++	switch (mt76_chip(&dev->mt76)) {
++	case 0x7990:
++		adie_base = adie_base_7996;
++		/* adie 0 */
++		if (dev->fem_type == MT7996_FEM_INT)
++			adie_id = ADIE_7975;
++		else
++			adie_id = ADIE_7976;
++		adie_offs[0] = adie_offs_list[adie_id];
++		eep_offs[0] = eep_offs_list[adie_id];
++
++		/* adie 1 */
++		if (dev->chip_sku != MT7996_SKU_404) {
++			adie_offs[1] = adie_offs_list[ADIE_7977];
++			eep_offs[1] = eep_offs_list[ADIE_7977];
++		}
++
++		/* adie 2 */
++		adie_offs[2] = adie_offs_list[ADIE_7977];
++		eep_offs[2] = eep_offs_list[ADIE_7977];
++		break;
++	case 0x7992:
++		adie_base = adie_base_7992;
++		/* adie 0 */
++		if (dev->chip_sku == MT7992_SKU_44 &&
++		    dev->fem_type != MT7996_FEM_EXT)
++			adie_id = ADIE_7975;
++		else if (dev->chip_sku == MT7992_SKU_24)
++			adie_id = ADIE_7978;
++		else
++			adie_id = ADIE_7976;
++		adie_offs[0] = adie_offs_list[adie_id];
++		eep_offs[0] = eep_offs_list[adie_id];
++
++		/* adie 1 */
++		if (dev->chip_sku == MT7992_SKU_44 &&
++		    dev->fem_type != MT7996_FEM_INT)
++			adie_id = ADIE_7977;
++		else if (dev->chip_sku != MT7992_SKU_23)
++			adie_id = ADIE_7979;
++		else
++			break;
++		adie_offs[1] = adie_offs_list[adie_id];
++		eep_offs[1] = eep_offs_list[adie_id];
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	for (band = 0; band < __MT_MAX_BAND; band++) {
++		u16 adie_offset, eep_offset;
++		u32 block_num, prev_block_num = -1;
++
++		if (!adie_offs[band])
++			continue;
++
++		for (i = 0; i < MT_EE_CAL_FREE_MAX_SIZE; i++) {
++			adie_offset = adie_offs[band][i] + adie_base[band];
++			eep_offset = eep_offs[band][i];
++			block_num = adie_offset / MT7996_EEPROM_BLOCK_SIZE;
++
++			if (adie_offs[band][i] == MT_EE_END_OFFSET)
++				break;
++
++			if (is_mt7996(&dev->mt76) && dev->chip_sku == MT7996_SKU_444 &&
++			    band == MT_BAND1)
++				eep_offset -= MT_EE_7977BN_OFFSET;
++
++			if (prev_block_num != block_num) {
++				ret = mt7996_mcu_get_eeprom(dev, adie_offset, buf);
++				if (ret) {
++					prev_block_num = -1;
++					continue;
++				}
++			}
++
++			eeprom[eep_offset] = buf[adie_offset % MT7996_EEPROM_BLOCK_SIZE];
++			prev_block_num = block_num;
++		}
++	}
++
++	return 0;
++}
++
+ int mt7996_eeprom_init(struct mt7996_dev *dev)
+ {
+ 	int ret;
+@@ -526,6 +663,10 @@ int mt7996_eeprom_init(struct mt7996_dev *dev)
+ 	if (ret)
+ 		return ret;
+ 
++	ret = mt7996_apply_cal_free_data(dev);
++	if (ret)
++		return ret;
++
+ 	ret = mt7996_eeprom_parse_hw_cap(dev, &dev->phy);
+ 	if (ret < 0)
+ 		return ret;
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index ec9705c9a..0a470f6e2 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -3622,7 +3622,7 @@ int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *read_buf)
+ 	};
+ 	struct sk_buff *skb;
+ 	bool valid;
+-	int ret;
++	int ret = 0;
+ 	u8 *buf = read_buf;
+ 
+ 	ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+@@ -3640,11 +3640,13 @@ int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *read_buf)
+ 
+ 		skb_pull(skb, 48);
+ 		memcpy(buf, skb->data, MT7996_EEPROM_BLOCK_SIZE);
++	} else {
++		ret = -EINVAL;
+ 	}
+ 
+ 	dev_kfree_skb(skb);
+ 
+-	return 0;
++	return ret;
+ }
+ 
+ int mt7996_mcu_get_eeprom_free_block(struct mt7996_dev *dev, u8 *block_num)
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0067-mtk-wifi-mt7996-add-Eagle-2adie-TBTC-BE14000-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0067-mtk-wifi-mt7996-add-Eagle-2adie-TBTC-BE14000-support.patch
new file mode 100644
index 0000000..1164489
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0067-mtk-wifi-mt7996-add-Eagle-2adie-TBTC-BE14000-support.patch
@@ -0,0 +1,169 @@
+From ee0c9542db9e9cc8bfafdf3ab477c5d8a8d00136 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Tue, 5 Dec 2023 16:48:33 +0800
+Subject: [PATCH 067/120] mtk: wifi: mt7996: add Eagle 2adie TBTC (BE14000)
+ support
+
+Add fwdl/default eeprom load support for Eagle 2 adie TBTC
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+
+Add Eagle 2adie TBTC efuse merge
+Add Eagle 2adie TBTC group prek size
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt7996/eeprom.c |  8 ++++++--
+ mt7996/eeprom.h | 12 ++++++++++++
+ mt7996/init.c   |  6 ++++++
+ mt7996/mcu.c    |  5 +++++
+ mt7996/mt7996.h |  8 ++++++++
+ mt7996/regs.h   |  1 +
+ 6 files changed, 38 insertions(+), 2 deletions(-)
+
+diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
+index 41d535408..aacfc623f 100644
+--- a/mt7996/eeprom.c
++++ b/mt7996/eeprom.c
+@@ -177,6 +177,8 @@ const char *mt7996_eeprom_name(struct mt7996_dev *dev)
+ 	case 0x7990:
+ 		if (dev->chip_sku == MT7996_SKU_404)
+ 			return MT7996_EEPROM_DEFAULT_404;
++		else if (dev->chip_sku == MT7996_SKU_233)
++			return MT7996_EEPROM_DEFAULT_233;
+ 		return MT7996_EEPROM_DEFAULT;
+ 	case 0x7992:
+ 		if (dev->chip_sku == MT7992_SKU_23) {
+@@ -464,6 +466,8 @@ static void mt7996_eeprom_init_precal(struct mt7996_dev *dev)
+ 	switch (mt76_chip(&dev->mt76)) {
+ 	case 0x7990:
+ 		dev->prek.rev = mt7996_prek_rev;
++		if (dev->chip_sku == MT7996_SKU_233)
++			dev->prek.rev = mt7996_prek_rev_233;
+ 		/* 5g & 6g bw 80 dpd channel list is not used */
+ 		dev->prek.dpd_ch_num[DPD_CH_NUM_BW320_6G] = ARRAY_SIZE(dpd_6g_ch_list_bw320);
+ 		break;
+@@ -567,7 +571,7 @@ static int mt7996_apply_cal_free_data(struct mt7996_dev *dev)
+ 	case 0x7990:
+ 		adie_base = adie_base_7996;
+ 		/* adie 0 */
+-		if (dev->fem_type == MT7996_FEM_INT)
++		if (dev->fem_type == MT7996_FEM_INT && dev->chip_sku != MT7996_SKU_233)
+ 			adie_id = ADIE_7975;
+ 		else
+ 			adie_id = ADIE_7976;
+@@ -575,7 +579,7 @@ static int mt7996_apply_cal_free_data(struct mt7996_dev *dev)
+ 		eep_offs[0] = eep_offs_list[adie_id];
+ 
+ 		/* adie 1 */
+-		if (dev->chip_sku != MT7996_SKU_404) {
++		if (dev->chip_sku == MT7996_SKU_444) {
+ 			adie_offs[1] = adie_offs_list[ADIE_7977];
+ 			eep_offs[1] = eep_offs_list[ADIE_7977];
+ 		}
+diff --git a/mt7996/eeprom.h b/mt7996/eeprom.h
+index fa9c31e7a..43c9783c0 100644
+--- a/mt7996/eeprom.h
++++ b/mt7996/eeprom.h
+@@ -70,6 +70,18 @@ static const u32 mt7996_prek_rev[] = {
+ 	[DPD_OTFG0_SIZE] =			2 * MT_EE_CAL_UNIT,
+ };
+ 
++static const u32 mt7996_prek_rev_233[] = {
++	[GROUP_SIZE_2G] =			4 * MT_EE_CAL_UNIT,
++	[GROUP_SIZE_5G] =			44 * MT_EE_CAL_UNIT,
++	[GROUP_SIZE_6G] =			100 * MT_EE_CAL_UNIT,
++	[ADCDCOC_SIZE_2G] =			4 * 4,
++	[ADCDCOC_SIZE_5G] =			4 * 4,
++	[ADCDCOC_SIZE_6G] =			4 * 5,
++	[DPD_LEGACY_SIZE] =			4 * MT_EE_CAL_UNIT,
++	[DPD_MEM_SIZE] =			13 * MT_EE_CAL_UNIT,
++	[DPD_OTFG0_SIZE] =			2 * MT_EE_CAL_UNIT,
++};
++
+ /* kite 2/5g config */
+ static const u32 mt7992_prek_rev[] = {
+ 	[GROUP_SIZE_2G] =			4 * MT_EE_CAL_UNIT,
+diff --git a/mt7996/init.c b/mt7996/init.c
+index 30879ec35..ec90cdc78 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -905,6 +905,12 @@ int mt7996_get_chip_sku(struct mt7996_dev *dev)
+ 
+ 	switch (mt76_chip(&dev->mt76)) {
+ 	case 0x7990:
++		if (FIELD_GET(MT_PAD_GPIO_2ADIE_TBTC, val)) {
++			dev->chip_sku = MT7996_SKU_233;
++			dev->fem_type = MT7996_FEM_INT;
++			return 0;
++		}
++
+ 		adie_comb = FIELD_GET(MT_PAD_GPIO_ADIE_COMB, val);
+ 		if (adie_comb <= 1)
+ 			dev->chip_sku = MT7996_SKU_444;
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 0a470f6e2..874598692 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -23,6 +23,11 @@
+ 			_fw = MT7992_##name;			\
+ 		break;						\
+ 	case 0x7990:						\
++		if ((_dev)->chip_sku == MT7996_SKU_233)		\
++			_fw = MT7996_##name##_233;		\
++		else						\
++			_fw = MT7996_##name;			\
++		break;						\
+ 	default:						\
+ 		_fw = MT7996_##name;				\
+ 		break;						\
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 0945894d1..d1cfb878c 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -35,6 +35,12 @@
+ #define MT7996_FIRMWARE_WM_TM		"mediatek/mt7996/mt7996_wm_tm.bin"
+ #define MT7996_ROM_PATCH		"mediatek/mt7996/mt7996_rom_patch.bin"
+ 
++#define MT7996_FIRMWARE_WA_233		"mediatek/mt7996/mt7996_wa_233.bin"
++#define MT7996_FIRMWARE_WM_233		"mediatek/mt7996/mt7996_wm_233.bin"
++#define MT7996_FIRMWARE_DSP_233		MT7996_FIRMWARE_DSP
++#define MT7996_FIRMWARE_WM_TM_233	"mediatek/mt7996/mt7996_wm_tm_233.bin"
++#define MT7996_ROM_PATCH_233		"mediatek/mt7996/mt7996_rom_patch_233.bin"
++
+ #define MT7992_FIRMWARE_WA		"mediatek/mt7996/mt7992_wa.bin"
+ #define MT7992_FIRMWARE_WM		"mediatek/mt7996/mt7992_wm.bin"
+ #define MT7992_FIRMWARE_DSP		"mediatek/mt7996/mt7992_dsp.bin"
+@@ -54,6 +60,7 @@
+ #define MT7992_ROM_PATCH_23		"mediatek/mt7996/mt7992_rom_patch_23.bin"
+ 
+ #define MT7996_EEPROM_DEFAULT		"mediatek/mt7996/mt7996_eeprom.bin"
++#define MT7996_EEPROM_DEFAULT_233	"mediatek/mt7996/mt7996_eeprom_233.bin"
+ #define MT7996_EEPROM_DEFAULT_404	"mediatek/mt7996/mt7996_eeprom_dual_404.bin"
+ #define MT7996_EEPROM_DEFAULT_TM	"mediatek/mt7996/mt7996_eeprom_tm.bin"
+ #define MT7992_EEPROM_DEFAULT		"mediatek/mt7996/mt7992_eeprom_2i5i.bin"
+@@ -123,6 +130,7 @@ enum mt7996_fem_type {
+ enum mt7996_sku_type {
+ 	MT7996_SKU_404,
+ 	MT7996_SKU_444,
++	MT7996_SKU_233,
+ };
+ 
+ enum mt7992_sku_type {
+diff --git a/mt7996/regs.h b/mt7996/regs.h
+index aa04d8d2c..8d1462a7d 100644
+--- a/mt7996/regs.h
++++ b/mt7996/regs.h
+@@ -666,6 +666,7 @@ enum offs_rev {
+ 
+ #define MT_PAD_GPIO				0x700056f0
+ #define MT_PAD_GPIO_ADIE_COMB			GENMASK(16, 15)
++#define MT_PAD_GPIO_2ADIE_TBTC			BIT(19)
+ #define MT_PAD_GPIO_ADIE_COMB_7992		GENMASK(17, 16)
+ #define MT_PAD_GPIO_ADIE_NUM_7992		BIT(15)
+ 
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0068-mtk-wifi-mt76-mt7996-add-background-radar-hw-cap-che.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0068-mtk-wifi-mt76-mt7996-add-background-radar-hw-cap-che.patch
new file mode 100644
index 0000000..6cb465a
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0068-mtk-wifi-mt76-mt7996-add-background-radar-hw-cap-che.patch
@@ -0,0 +1,82 @@
+From 02be5046a00b7acf8c07e76138fc2594432b4e5a Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Fri, 22 Dec 2023 17:27:10 +0800
+Subject: [PATCH 068/120] mtk: wifi: mt76: mt7996: add background radar hw cap
+ check
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt7996/debugfs.c |  5 +++++
+ mt7996/init.c    |  7 ++++---
+ mt7996/mt7996.h  | 20 ++++++++++++++++++++
+ 3 files changed, 29 insertions(+), 3 deletions(-)
+
+diff --git a/mt7996/debugfs.c b/mt7996/debugfs.c
+index 2f76b15f6..7eeb5329e 100644
+--- a/mt7996/debugfs.c
++++ b/mt7996/debugfs.c
+@@ -262,6 +262,11 @@ mt7996_rdd_monitor(struct seq_file *s, void *data)
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
++	if (!mt7996_get_background_radar_cap(dev)) {
++		seq_puts(s, "no background radar capability\n");
++		goto out;
++	}
++
+ 	if (!cfg80211_chandef_valid(chandef)) {
+ 		ret = -EINVAL;
+ 		goto out;
+diff --git a/mt7996/init.c b/mt7996/init.c
+index ec90cdc78..20415e3c0 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -393,9 +393,10 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed)
+ 
+ 	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_OPERATING_CHANNEL_VALIDATION);
+ 	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_BEACON_PROTECTION);
+-	if (!mdev->dev->of_node ||
+-	    !of_property_read_bool(mdev->dev->of_node,
+-				   "mediatek,disable-radar-background"))
++	if (mt7996_get_background_radar_cap(phy->dev) &&
++	    (!mdev->dev->of_node ||
++	     !of_property_read_bool(mdev->dev->of_node,
++				    "mediatek,disable-radar-background")))
+ 		wiphy_ext_feature_set(wiphy,
+ 				      NL80211_EXT_FEATURE_RADAR_BACKGROUND);
+ 
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index d1cfb878c..e64b5a1e4 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -599,6 +599,26 @@ mt7996_band_valid(struct mt7996_dev *dev, u8 band)
+ 	return band == MT_BAND0 || band == MT_BAND2;
+ }
+ 
++static inline bool
++mt7996_get_background_radar_cap(struct mt7996_dev *dev)
++{
++	switch (mt76_chip(&dev->mt76)) {
++	case 0x7990:
++		if (dev->chip_sku == MT7996_SKU_233)
++			return 0;
++		break;
++	case 0x7992:
++		if (dev->chip_sku == MT7992_SKU_23 ||
++		    dev->chip_sku == MT7992_SKU_24)
++			return 0;
++		break;
++	default:
++		break;
++	}
++
++	return 1;
++}
++
+ extern const struct ieee80211_ops mt7996_ops;
+ extern struct pci_driver mt7996_pci_driver;
+ extern struct pci_driver mt7996_hif_driver;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0069-mtk-wifi-mt76-mt7996-support-disable-muru-debug-info.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0069-mtk-wifi-mt76-mt7996-support-disable-muru-debug-info.patch
new file mode 100644
index 0000000..175c1f0
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0069-mtk-wifi-mt76-mt7996-support-disable-muru-debug-info.patch
@@ -0,0 +1,99 @@
+From 001ec9eae4fa8cf3deda23d6d2d853fd72934b2a Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Fri, 22 Dec 2023 10:53:00 +0800
+Subject: [PATCH 069/120] mtk: wifi: mt76: mt7996: support disable muru debug
+ info when recording fwlog
+
+When we record fwlog, we will also enable recording muru debug info log by
+default. However, in certain test scenarios, this can result in
+recording too many logs, causing inconvenience during issue analysis.
+Therefore, this commit adds an debug option, fw_debug_muru_disable, in
+debugfs. User can modify this option to enable/disable recording muru
+debug info log.
+
+[Usage]
+Set:
+$ echo val > debugfs/fw_debug_muru_disable
+Get:
+$ cat debugfs/fw_debug_muru_disable
+
+val can be the following values:
+0 = enable recording muru debug info (Default value)
+1 = disable recording muru debug info
+
+CR-Id: WCNCR00261410
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+---
+ mt7996/debugfs.c | 29 +++++++++++++++++++++++++++++
+ mt7996/mt7996.h  |  1 +
+ 2 files changed, 30 insertions(+)
+
+diff --git a/mt7996/debugfs.c b/mt7996/debugfs.c
+index 7eeb5329e..5b3e39672 100644
+--- a/mt7996/debugfs.c
++++ b/mt7996/debugfs.c
+@@ -468,6 +468,9 @@ mt7996_fw_debug_muru_set(void *data)
+ 	} debug;
+ 	int ret;
+ 
++	if (dev->fw_debug_muru_disable)
++		return 0;
++
+ 	for (debug = DEBUG_BSRP_STATUS; debug <= DEBUG_MEC_UPDATE_AMSDU; debug++) {
+ 		ret = mt7996_mcu_muru_dbg_info(dev, debug,
+ 					       dev->fw_debug_bin & BIT(0));
+@@ -912,6 +915,30 @@ static const struct file_operations mt7996_efuse_ops = {
+ 	.llseek = default_llseek,
+ };
+ 
++static int
++mt7996_fw_debug_muru_disable_set(void *data, u64 val)
++{
++	struct mt7996_dev *dev = data;
++
++	dev->fw_debug_muru_disable = !!val;
++
++	return 0;
++}
++
++static int
++mt7996_fw_debug_muru_disable_get(void *data, u64 *val)
++{
++	struct mt7996_dev *dev = data;
++
++	*val = dev->fw_debug_muru_disable;
++
++	return 0;
++}
++
++DEFINE_DEBUGFS_ATTRIBUTE(fops_fw_debug_muru_disable,
++			 mt7996_fw_debug_muru_disable_get,
++			 mt7996_fw_debug_muru_disable_set, "%lld\n");
++
+ int mt7996_init_debugfs(struct mt7996_phy *phy)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -948,6 +975,8 @@ int mt7996_init_debugfs(struct mt7996_phy *phy)
+ 		debugfs_create_devm_seqfile(dev->mt76.dev, "rdd_monitor", dir,
+ 					    mt7996_rdd_monitor);
+ 	}
++	debugfs_create_file("fw_debug_muru_disable", 0600, dir, dev,
++			    &fops_fw_debug_muru_disable);
+ 
+ 	if (phy == &dev->phy)
+ 		dev->debugfs_dir = dir;
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index e64b5a1e4..a91bcf4ec 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -472,6 +472,7 @@ struct mt7996_dev {
+ 	u8 fw_debug_wa;
+ 	u8 fw_debug_bin;
+ 	u16 fw_debug_seq;
++	bool fw_debug_muru_disable;
+ 
+ 	struct dentry *debugfs_dir;
+ 	struct rchan *relay_fwlog;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0070-mtk-wifi-mt76-testmode-add-testmode-ibf-ver2-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0070-mtk-wifi-mt76-testmode-add-testmode-ibf-ver2-support.patch
new file mode 100644
index 0000000..c89242b
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0070-mtk-wifi-mt76-testmode-add-testmode-ibf-ver2-support.patch
@@ -0,0 +1,530 @@
+From 59d96798d3492efdfe93cc1024d96e9972392893 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Mon, 8 Jan 2024 10:13:30 +0800
+Subject: [PATCH 070/120] mtk: wifi: mt76: testmode: add testmode ibf ver2
+ support
+
+Add ibf ver2 support for chips after Kite
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt7996/mtk_mcu.c  | 120 ++++++++++++++++++++++-------
+ mt7996/mtk_mcu.h  | 188 ++++++++++++++++++++++++++++++++++------------
+ mt7996/testmode.c |  39 +++++++---
+ 3 files changed, 261 insertions(+), 86 deletions(-)
+
+diff --git a/mt7996/mtk_mcu.c b/mt7996/mtk_mcu.c
+index 9ef791db8..a9a7db15a 100644
+--- a/mt7996/mtk_mcu.c
++++ b/mt7996/mtk_mcu.c
+@@ -438,24 +438,49 @@ mt7996_ibf_phase_assign(struct mt7996_dev *dev,
+ {
+ 	/* fw return ibf calibrated data with
+ 	 * the mt7996_txbf_phase_info_5g struct for both 2G and 5G.
++	 * (return struct mt7992_txbf_phase_info_5g for ibf 2.0)
+ 	 * 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);
++	if (get_ibf_version(dev) != IBF_VER_2) {
++		phase_assign(cal->group, v1, m_t0_h, true);
++		phase_assign(cal->group, v1, m_t1_h, true);
++		phase_assign(cal->group, v1, m_t2_h, true);
++		phase_assign(cal->group, v1, m_t2_h_sx2, false);
++		phase_assign_rx_v1(cal->group, v1, r0);
++		phase_assign_rx_v1(cal->group, v1, r1);
++		phase_assign_rx_v1(cal->group, v1, r2);
++		phase_assign_rx_v1(cal->group, v1, r3);
++		phase_assign_rx(cal->group, v1, r2_sx2, false);
++		phase_assign_rx(cal->group, v1, r3_sx2, false);
++		phase_assign(cal->group, v1, r0_reserved, false);
++		phase_assign(cal->group, v1, r1_reserved, false);
++		phase_assign(cal->group, v1, r2_reserved, false);
++		phase_assign(cal->group, v1, r3_reserved, false);
++		phase_assign(cal->group, v1, r2_sx2_reserved, false);
++		phase_assign(cal->group, v1, r3_sx2_reserved, false);
++	} else {
++		phase_assign(cal->group, v2, m_t0_h, true);
++		phase_assign(cal->group, v2, m_t1_h, true);
++		phase_assign(cal->group, v2, m_t2_h, true);
++		if (cal->group) {
++			phase->v2.phase_5g.m_t3_h = cal->v2.phase_5g.m_t3_h;
++			dev_info(dev->mt76.dev, "m_t3_h = %d\n", phase->v2.phase_5g.m_t3_h);
++		}
++		phase_assign_rx_ext(cal->group, v2, r0, true);
++		phase_assign_rx_ext(cal->group, v2, r1, true);
++		phase_assign_rx_ext(cal->group, v2, r2, true);
++		phase_assign_rx_ext(cal->group, v2, r3, true);
++		if (cal->group) {
++			memcpy(&phase->v2.phase_5g.r4, &cal->v2.phase_5g.r4,
++			       sizeof(struct txbf_rx_phase_ext));
++			dev_info(dev->mt76.dev, "r4.rx_uh = %d\n", phase->v2.phase_5g.r4.rx_uh);
++			dev_info(dev->mt76.dev, "r4.rx_h = %d\n", phase->v2.phase_5g.r4.rx_h);
++			dev_info(dev->mt76.dev, "r4.rx_mh = %d\n", phase->v2.phase_5g.r4.rx_mh);
++			dev_info(dev->mt76.dev, "r4.rx_m = %d\n", phase->v2.phase_5g.r4.rx_m);
++			dev_info(dev->mt76.dev, "r4.rx_l = %d\n", phase->v2.phase_5g.r4.rx_l);
++			dev_info(dev->mt76.dev, "r4.rx_ul = %d\n", phase->v2.phase_5g.r4.rx_ul);
++		}
++	}
+ }
+ 
+ void
+@@ -754,12 +779,18 @@ mt7996_mcu_rx_bf_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 	}
+ 	case UNI_EVENT_BF_CAL_PHASE: {
+ 		struct mt7996_ibf_cal_info *cal;
+-		struct mt7996_txbf_phase_out phase_out;
+ 		struct mt7996_txbf_phase *phase;
++		union {
++			struct mt7996_txbf_phase_out v1;
++			struct mt7992_txbf_phase_out v2;
++		} phase_out;
++		int phase_out_size = sizeof(struct mt7996_txbf_phase_out);
+ 
+ 		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));
++		if (get_ibf_version(dev) == IBF_VER_2)
++			phase_out_size = sizeof(struct mt7992_txbf_phase_out);
++		memcpy(&phase_out, &cal->buf, phase_out_size);
+ 		switch (cal->cal_type) {
+ 		case IBF_PHASE_CAL_NORMAL:
+ 		case IBF_PHASE_CAL_NORMAL_INSTRUMENT:
+@@ -780,16 +811,49 @@ mt7996_mcu_rx_bf_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 			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);
++		if (get_ibf_version(dev) == IBF_VER_2) {
++			dev_info(dev->mt76.dev,
++				 "c0_uh = %d, c1_uh = %d, c2_uh = %d, c3_uh = %d c4_uh = %d\n",
++				 phase_out.v2.c0_uh, phase_out.v2.c1_uh, phase_out.v2.c2_uh,
++				 phase_out.v2.c3_uh, phase_out.v2.c4_uh);
++			dev_info(dev->mt76.dev,
++				 "c0_h = %d, c1_h = %d, c2_h = %d, c3_h = %d c4_h = %d\n",
++				 phase_out.v2.c0_h, phase_out.v2.c1_h, phase_out.v2.c2_h,
++				 phase_out.v2.c3_h, phase_out.v2.c4_h);
++			dev_info(dev->mt76.dev,
++				 "c0_mh = %d, c1_mh = %d, c2_mh = %d, c3_mh = %d c4_mh = %d\n",
++				 phase_out.v2.c0_mh, phase_out.v2.c1_mh, phase_out.v2.c2_mh,
++				 phase_out.v2.c3_mh, phase_out.v2.c4_mh);
++			dev_info(dev->mt76.dev,
++				 "c0_m = %d, c1_m = %d, c2_m = %d, c3_m = %d c4_m = %d\n",
++				 phase_out.v2.c0_m, phase_out.v2.c1_m, phase_out.v2.c2_m,
++				 phase_out.v2.c3_m, phase_out.v2.c4_m);
++			dev_info(dev->mt76.dev,
++				 "c0_l = %d, c1_l = %d, c2_l = %d, c3_l = %d c4_l = %d\n",
++				 phase_out.v2.c0_l, phase_out.v2.c1_l, phase_out.v2.c2_l,
++				 phase_out.v2.c3_l, phase_out.v2.c4_l);
++		} else {
++			dev_info(dev->mt76.dev,
++				 "c0_uh = %d, c1_uh = %d, c2_uh = %d, c3_uh = %d\n",
++				 phase_out.v1.c0_uh, phase_out.v1.c1_uh,
++				 phase_out.v1.c2_uh, phase_out.v1.c3_uh);
++			dev_info(dev->mt76.dev,
++				 "c0_h = %d, c1_h = %d, c2_h = %d, c3_h = %d\n",
++				 phase_out.v1.c0_h, phase_out.v1.c1_h,
++				 phase_out.v1.c2_h, phase_out.v1.c3_h);
++			dev_info(dev->mt76.dev,
++				 "c0_mh = %d, c1_mh = %d, c2_mh = %d, c3_mh = %d\n",
++				 phase_out.v1.c0_mh, phase_out.v1.c1_mh,
++				 phase_out.v1.c2_mh, phase_out.v1.c3_mh);
++			dev_info(dev->mt76.dev,
++				 "c0_m = %d, c1_m = %d, c2_m = %d, c3_m = %d\n",
++				 phase_out.v1.c0_m, phase_out.v1.c1_m,
++				 phase_out.v1.c2_m, phase_out.v1.c3_m);
++			dev_info(dev->mt76.dev,
++				 "c0_l = %d, c1_l = %d, c2_l = %d, c3_l = %d\n",
++				 phase_out.v1.c0_l, phase_out.v1.c1_l,
++				 phase_out.v1.c2_l, phase_out.v1.c3_l);
++		}
+ 
+ 		break;
+ 	}
+diff --git a/mt7996/mtk_mcu.h b/mt7996/mtk_mcu.h
+index 8a0ed9bc2..7bfb5920d 100644
+--- a/mt7996/mtk_mcu.h
++++ b/mt7996/mtk_mcu.h
+@@ -189,7 +189,7 @@ struct bf_txsnd_info {
+ 	u8 __rsv[2];
+ } __packed;
+ 
+-#define MAX_PHASE_GROUP_NUM	9
++#define MAX_PHASE_GROUP_NUM	13
+ 
+ struct bf_phase_comp {
+ 	__le16 tag;
+@@ -227,7 +227,8 @@ struct bf_phase_cal {
+ 	u8 cal_type;
+ 	u8 lna_gain_level;
+ 	u8 band_idx;
+-	u8 rsv[2];
++	u8 version;
++	u8 rsv[1];
+ } __packed;
+ 
+ struct bf_txcmd {
+@@ -248,7 +249,7 @@ struct bf_pfmu_data_all {
+ 	u8 band_idx;
+ 	u8 rsv[2];
+ 
+-	u8 buf[512];
++	u8 buf[640];
+ } __packed;
+ 
+ #define TXBF_DUT_MAC_SUBADDR		0x22
+@@ -588,7 +589,35 @@ struct mt7996_txbf_phase_out {
+ 	u8 c3_uh;
+ };
+ 
+-struct mt7996_txbf_rx_phase_2g {
++struct mt7992_txbf_phase_out {
++	u8 c0_l;
++	u8 c1_l;
++	u8 c2_l;
++	u8 c3_l;
++	u8 c4_l;
++	u8 c0_m;
++	u8 c1_m;
++	u8 c2_m;
++	u8 c3_m;
++	u8 c4_m;
++	u8 c0_mh;
++	u8 c1_mh;
++	u8 c2_mh;
++	u8 c3_mh;
++	u8 c4_mh;
++	u8 c0_h;
++	u8 c1_h;
++	u8 c2_h;
++	u8 c3_h;
++	u8 c4_h;
++	u8 c0_uh;
++	u8 c1_uh;
++	u8 c2_uh;
++	u8 c3_uh;
++	u8 c4_uh;
++};
++
++struct txbf_rx_phase {
+ 	u8 rx_uh;
+ 	u8 rx_h;
+ 	u8 rx_m;
+@@ -596,7 +625,7 @@ struct mt7996_txbf_rx_phase_2g {
+ 	u8 rx_ul;
+ };
+ 
+-struct mt7996_txbf_rx_phase_5g {
++struct txbf_rx_phase_ext {
+ 	u8 rx_uh;
+ 	u8 rx_h;
+ 	u8 rx_mh;
+@@ -606,12 +635,12 @@ struct mt7996_txbf_rx_phase_5g {
+ };
+ 
+ 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;
++	struct txbf_rx_phase r0;
++	struct txbf_rx_phase r1;
++	struct txbf_rx_phase r2;
++	struct txbf_rx_phase r3;
++	struct txbf_rx_phase r2_sx2;
++	struct txbf_rx_phase r3_sx2;
+ 	u8 m_t0_h;
+ 	u8 m_t1_h;
+ 	u8 m_t2_h;
+@@ -625,12 +654,12 @@ struct mt7996_txbf_phase_info_2g {
+ };
+ 
+ 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 */
++	struct txbf_rx_phase_ext r0;
++	struct txbf_rx_phase_ext r1;
++	struct txbf_rx_phase_ext r2;
++	struct txbf_rx_phase_ext r3;
++	struct txbf_rx_phase r2_sx2;	/* no middle-high in r2_sx2 */
++	struct txbf_rx_phase r3_sx2;	/* no middle-high in r3_sx2 */
+ 	u8 m_t0_h;
+ 	u8 m_t1_h;
+ 	u8 m_t2_h;
+@@ -643,49 +672,83 @@ struct mt7996_txbf_phase_info_5g {
+ 	u8 r3_sx2_reserved;
+ };
+ 
++struct mt7992_txbf_phase_info_2g {
++	struct txbf_rx_phase_ext r0;
++	struct txbf_rx_phase_ext r1;
++	struct txbf_rx_phase_ext r2;
++	struct txbf_rx_phase_ext r3;
++	u8 m_t0_h;
++	u8 m_t1_h;
++	u8 m_t2_h;
++};
++
++struct mt7992_txbf_phase_info_5g {
++	struct txbf_rx_phase_ext r0;
++	struct txbf_rx_phase_ext r1;
++	struct txbf_rx_phase_ext r2;
++	struct txbf_rx_phase_ext r3;
++	struct txbf_rx_phase_ext r4;
++	u8 m_t0_h;
++	u8 m_t1_h;
++	u8 m_t2_h;
++	u8 m_t3_h;
++};
++
+ struct mt7996_txbf_phase {
+ 	u8 status;
+ 	union {
+-		struct mt7996_txbf_phase_info_2g phase_2g;
+-		struct mt7996_txbf_phase_info_5g phase_5g;
++		union {
++			struct mt7996_txbf_phase_info_2g phase_2g;
++			struct mt7996_txbf_phase_info_5g phase_5g;
++		} v1;
++		union {
++			struct mt7992_txbf_phase_info_2g phase_2g;
++			struct mt7992_txbf_phase_info_5g phase_5g;
++		} v2;
++		u8 buf[44];
+ 	};
+ };
+ 
+-#define phase_assign(group, field, dump, ...)	({						\
++#define phase_assign(group, v, field, dump, ...)	({					\
+ 	if (group) {										\
+-		phase->phase_5g.field = cal->phase_5g.field;					\
++		phase->v.phase_5g.field = cal->v.phase_5g.field;				\
+ 		if (dump)									\
+-			dev_info(dev->mt76.dev, "%s = %d\n", #field, phase->phase_5g.field);	\
++			dev_info(dev->mt76.dev, "%s = %d\n", #field, phase->v.phase_5g.field);	\
+ 	} else {										\
+-		phase->phase_2g.field = cal->phase_5g.field;					\
++		phase->v.phase_2g.field = cal->v.phase_5g.field;				\
+ 		if (dump)									\
+-			dev_info(dev->mt76.dev, "%s = %d\n", #field, phase->phase_2g.field);	\
++			dev_info(dev->mt76.dev, "%s = %d\n", #field, phase->v.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, v, rx, dump, ...)	({					\
++	phase_assign(group, v, rx.rx_uh, dump);							\
++	phase_assign(group, v, rx.rx_h, dump);							\
++	phase_assign(group, v, rx.rx_m, dump);							\
++	phase_assign(group, v, rx.rx_l, dump);							\
++	phase_assign(group, v, rx.rx_ul, dump);							\
+ })
+ 
+-#define phase_assign_rx(group, rx, ...)	({							\
++#define phase_assign_rx_ext(group, v, rx, dump, ...)	({					\
++	phase_assign(group, v, rx.rx_uh, dump);							\
++	phase_assign(group, v, rx.rx_h, dump);							\
++	phase_assign(group, v, rx.rx_mh, dump);							\
++	phase_assign(group, v, rx.rx_m, dump);							\
++	phase_assign(group, v, rx.rx_l, dump);							\
++	phase_assign(group, v, rx.rx_ul, dump);							\
++})
++
++#define phase_assign_rx_v1(group, v, 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);						\
++		phase_assign(group, v, rx.rx_uh, true);						\
++		phase_assign(group, v, rx.rx_h, true);						\
++		phase->v.phase_5g.rx.rx_mh = cal->v.phase_5g.rx.rx_mh;				\
++		dev_info(dev->mt76.dev, "%s.rx_mh = %d\n", #rx, phase->v.phase_5g.rx.rx_mh);	\
++		phase_assign(group, v, rx.rx_m, true);						\
++		phase_assign(group, v, rx.rx_l, true);						\
++		phase_assign(group, v, 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);						\
++		phase_assign_rx(group, v, rx, true, ...);					\
+ 	}											\
+ })
+ 
+@@ -709,11 +772,24 @@ struct mt7996_ibf_cal_info {
+ 	bool sx2;
+ 	u8 status;
+ 	u8 cal_type;
+-	u8 _rsv[2];
+-	struct mt7996_txbf_phase_out phase_out;
++	u8 nsts;
++	u8 version;
+ 	union {
+-		struct mt7996_txbf_phase_info_2g phase_2g;
+-		struct mt7996_txbf_phase_info_5g phase_5g;
++		struct {
++			struct mt7996_txbf_phase_out phase_out;
++			union {
++				struct mt7996_txbf_phase_info_2g phase_2g;
++				struct mt7996_txbf_phase_info_5g phase_5g;
++			};
++		} v1;
++		struct {
++			struct mt7992_txbf_phase_out phase_out;
++			union {
++				struct mt7992_txbf_phase_info_2g phase_2g;
++				struct mt7992_txbf_phase_info_5g phase_5g;
++			};
++		} v2;
++		u8 buf[64];
+ 	};
+ } __packed;
+ 
+@@ -725,7 +801,23 @@ enum {
+ 	IBF_PHASE_CAL_VERIFY_INSTRUMENT,
+ };
+ 
+-#define MT7996_TXBF_SUBCAR_NUM	64
++enum ibf_version {
++	IBF_VER_1,
++	IBF_VER_2 = 3,
++};
++
++static inline int get_ibf_version(struct mt7996_dev *dev)
++{
++	switch (mt76_chip(&dev->mt76)) {
++	case 0x7990:
++		return IBF_VER_1;
++	case 0x7992:
++	default:
++		return IBF_VER_2;
++	}
++}
++
++#define MT7996_TXBF_SUBCAR_NUM		64
+ 
+ enum {
+ 	UNI_EVENT_BF_PFMU_TAG = 0x5,
+diff --git a/mt7996/testmode.c b/mt7996/testmode.c
+index c82ac562a..4188cb35a 100644
+--- a/mt7996/testmode.c
++++ b/mt7996/testmode.c
+@@ -1257,6 +1257,23 @@ mt7996_tm_txbf_init(struct mt7996_phy *phy, u16 *val)
+ 	return 0;
+ }
+ 
++static inline void
++mt7996_tm_txbf_phase_copy(struct mt7996_dev *dev, void *des, void *src, int group)
++{
++	int phase_size;
++
++	if (group && get_ibf_version(dev) == IBF_VER_1)
++		phase_size = sizeof(struct mt7996_txbf_phase_info_5g);
++	else if (get_ibf_version(dev) == IBF_VER_1)
++		phase_size = sizeof(struct mt7996_txbf_phase_info_2g);
++	else if (group)
++		phase_size = sizeof(struct mt7992_txbf_phase_info_5g);
++	else
++		phase_size = sizeof(struct mt7992_txbf_phase_info_2g);
++
++	memcpy(des, src, phase_size);
++}
++
+ static int
+ mt7996_tm_txbf_phase_comp(struct mt7996_phy *phy, u16 *val)
+ {
+@@ -1274,12 +1291,10 @@ mt7996_tm_txbf_phase_comp(struct mt7996_phy *phy, u16 *val)
+ 		}
+ 	};
+ 	struct mt7996_txbf_phase *phase = (struct mt7996_txbf_phase *)dev->test.txbf_phase_cal;
++	int group = val[2];
+ 
+-	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));
++	wait_event_timeout(dev->mt76.tx_wait, phase[group].status != 0, HZ);
++	mt7996_tm_txbf_phase_copy(dev, req.phase_comp.buf, phase[group].buf, group);
+ 
+ 	pr_info("ibf cal process: phase comp info\n");
+ 	print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 16, 1,
+@@ -1470,6 +1485,7 @@ mt7996_tm_txbf_phase_cal(struct mt7996_phy *phy, u16 *val)
+ 			.cal_type = val[3],
+ 			.lna_gain_level = val[4],
+ 			.band_idx = phy->mt76->band_idx,
++			.version = val[5],
+ 		},
+ 	};
+ 	struct mt7996_txbf_phase *phase = (struct mt7996_txbf_phase *)dev->test.txbf_phase_cal;
+@@ -1545,7 +1561,9 @@ 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
++#define TXBF_PHASE_GROUP_EEPROM_OFFSET_VER_1	46
++#define TXBF_PHASE_G0_EEPROM_OFFSET_VER_2	sizeof(struct mt7992_txbf_phase_info_2g)
++#define TXBF_PHASE_GX_EEPROM_OFFSET_VER_2	sizeof(struct mt7992_txbf_phase_info_5g)
+ 	struct mt7996_txbf_phase *phase, *p;
+ 	struct mt7996_dev *dev = phy->dev;
+ 	u8 *eeprom = dev->mt76.eeprom.data;
+@@ -1561,11 +1579,12 @@ mt7996_tm_txbf_e2p_update(struct mt7996_phy *phy)
+ 			continue;
+ 
+ 		/* copy phase cal data to eeprom */
+-		if (i)
+-			memcpy(eeprom + offset, &p->phase_5g, sizeof(p->phase_5g));
++		mt7996_tm_txbf_phase_copy(dev, eeprom + offset, p->buf, i);
++		if (get_ibf_version(dev) == IBF_VER_1)
++			offset += TXBF_PHASE_GROUP_EEPROM_OFFSET_VER_1;
+ 		else
+-			memcpy(eeprom + offset, &p->phase_2g, sizeof(p->phase_2g));
+-		offset += TXBF_PHASE_GROUP_EEPROM_OFFSET;
++			offset += i ? TXBF_PHASE_GX_EEPROM_OFFSET_VER_2 :
++				      TXBF_PHASE_G0_EEPROM_OFFSET_VER_2;
+ 	}
+ 
+ 	return 0;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0071-mtk-wifi-mt76-testmode-add-testmode-ibf-5T5R-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0071-mtk-wifi-mt76-testmode-add-testmode-ibf-5T5R-support.patch
new file mode 100644
index 0000000..fd1c9f7
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0071-mtk-wifi-mt76-testmode-add-testmode-ibf-5T5R-support.patch
@@ -0,0 +1,269 @@
+From d04f6dbcf9838918fe2f08b384eb6d65be8c7173 Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Mon, 8 Jan 2024 10:09:40 +0800
+Subject: [PATCH 071/120] mtk: wifi: mt76: testmode: add testmode ibf 5T5R
+ support
+
+Add testmode ibf 5T5R support for Kite BE7200 2i5i
+
+CR-Id: WCNCR00274293
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt7996/mcu.h      |  1 +
+ mt7996/mtk_mcu.h  | 11 ++++++++
+ mt7996/testmode.c | 71 ++++++++++++++++++++++++++++++++++-------------
+ testmode.c        | 10 ++++---
+ 4 files changed, 70 insertions(+), 23 deletions(-)
+
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 398bf3d27..67777a639 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -756,6 +756,7 @@ enum {
+ 	BF_TXSND_INFO = 24,
+ 	BF_CMD_TXCMD = 27,
+ 	BF_CFG_PHY = 28,
++	BF_PROFILE_WRITE_20M_ALL_5X5 = 30,
+ };
+ 
+ struct ra_rate {
+diff --git a/mt7996/mtk_mcu.h b/mt7996/mtk_mcu.h
+index 7bfb5920d..58d61c517 100644
+--- a/mt7996/mtk_mcu.h
++++ b/mt7996/mtk_mcu.h
+@@ -763,6 +763,14 @@ struct mt7996_pfmu_data {
+ 	__le16 phi31;
+ };
+ 
++struct mt7996_pfmu_data_5x5 {
++	__le16 subc_idx;
++	__le16 phi11;
++	__le16 phi21;
++	__le16 phi31;
++	__le16 phi41;
++};
++
+ struct mt7996_ibf_cal_info {
+ 	struct mt7996_mcu_bf_basic_event event;
+ 
+@@ -818,6 +826,9 @@ static inline int get_ibf_version(struct mt7996_dev *dev)
+ }
+ 
+ #define MT7996_TXBF_SUBCAR_NUM		64
++#define MT7996_TXBF_PFMU_DATA_LEN	(MT7996_TXBF_SUBCAR_NUM * sizeof(struct mt7996_pfmu_data))
++#define MT7996_TXBF_PFMU_DATA_LEN_5X5	(MT7996_TXBF_SUBCAR_NUM * \
++					 sizeof(struct mt7996_pfmu_data_5x5))
+ 
+ enum {
+ 	UNI_EVENT_BF_PFMU_TAG = 0x5,
+diff --git a/mt7996/testmode.c b/mt7996/testmode.c
+index 4188cb35a..9fa4edcd6 100644
+--- a/mt7996/testmode.c
++++ b/mt7996/testmode.c
+@@ -1149,9 +1149,9 @@ mt7996_tm_txbf_init(struct mt7996_phy *phy, u16 *val)
+ 	}
+ 
+ 	if (!dev->test.txbf_pfmu_data) {
++		/* allocate max size for 5x5 pfmu data */
+ 		pfmu_data = devm_kzalloc(dev->mt76.dev,
+-					 sizeof(struct mt7996_pfmu_data) *
+-					 MT7996_TXBF_SUBCAR_NUM,
++					 MT7996_TXBF_PFMU_DATA_LEN_5X5,
+ 					 GFP_KERNEL);
+ 		if (!pfmu_data)
+ 			return -ENOMEM;
+@@ -1212,6 +1212,11 @@ mt7996_tm_txbf_init(struct mt7996_phy *phy, u16 *val)
+ 
+ 		td->tx_rate_mode = MT76_TM_TX_MODE_HT;
+ 		td->tx_rate_sgi = 0;
++		/* 5T5R ibf */
++		if (nss == 5) {
++			td->tx_rate_mode = MT76_TM_TX_MODE_VHT;
++			td->tx_rate_idx = 7;
++		}
+ 	} else {
+ 		if (td->is_txbf_dut) {
+ 			/* Enable ETxBF Capability */
+@@ -1356,12 +1361,12 @@ mt7996_tm_add_txbf_sta(struct mt7996_phy *phy, u8 pfmu_idx, u8 nr, u8 nc, bool e
+ 	};
+ 	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) {
++	if ((td->tx_rate_mode == MT76_TM_TX_MODE_HE_SU ||
++	     td->tx_rate_mode == MT76_TM_TX_MODE_EHT_SU) && !td->ibf) {
+ 		rept_poll_rate = 0x49;
+ 		ndpa_rate = 0x49;
+ 		ndp_rate = 0;
+-	} else if (td->tx_rate_mode == MT76_TM_TX_MODE_VHT) {
++	} else if (td->tx_rate_mode == MT76_TM_TX_MODE_VHT && !td->ibf) {
+ 		rept_poll_rate = 0x9;
+ 		ndpa_rate = 0x9;
+ 		ndp_rate = 0;
+@@ -1372,8 +1377,16 @@ mt7996_tm_add_txbf_sta(struct mt7996_phy *phy, u8 pfmu_idx, u8 nr, u8 nc, bool e
+ 			ndp_rate = 8;
+ 		else if (nr == 2)
+ 			ndp_rate = 16;
++		else if (nr == 4)
++			ndp_rate = 32;
+ 		else
+ 			ndp_rate = 24;
++
++		/* 5T5R ebf profile for ibf cal */
++		if (nr == 4 && td->ibf && ebf) {
++			ndp_rate = 0;
++			ndpa_rate = 11;
++		}
+ 	}
+ 
+ 	bf_bw = mt7996_tm_bw_mapping(phy->mt76->chandef.width, BW_MAP_NL_TO_BF);
+@@ -1406,7 +1419,7 @@ mt7996_tm_txbf_profile_update(struct mt7996_phy *phy, u16 *val, bool ebf)
+ 	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;
++	u8 rate, pfmu_idx = val[0], nc = val[2], nr;
+ 	int ret;
+ 	bool is_atenl = val[5];
+ 
+@@ -1414,6 +1427,8 @@ mt7996_tm_txbf_profile_update(struct mt7996_phy *phy, u16 *val, bool ebf)
+ 		nr = 1;
+ 	else if (td->tx_antenna_mask == 7)
+ 		nr = 2;
++	else if (td->tx_antenna_mask == 31)
++		nr = 4;
+ 	else
+ 		nr = 3;
+ 
+@@ -1437,7 +1452,8 @@ mt7996_tm_txbf_profile_update(struct mt7996_phy *phy, u16 *val, bool ebf)
+ 		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);
++		rate = nr == 4 ? td->tx_rate_mode : MT76_TM_TX_MODE_OFDM;
++		tag->t1.lm = mt7996_tm_rate_mapping(rate, RATE_MODE_TO_LM);
+ 
+ 		tag->t2.ibf_timeout = 0xff;
+ 		tag->t2.ibf_nr = nr;
+@@ -1500,7 +1516,6 @@ mt7996_tm_txbf_phase_cal(struct mt7996_phy *phy, u16 *val)
+ 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];
+@@ -1509,8 +1524,10 @@ mt7996_tm_txbf_profile_update_all(struct mt7996_phy *phy, u16 *val)
+ 	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;
++	u16 angle51 = val[6];
++	s16 phi11 = 0, phi21 = 0, phi31 = 0, phi41 = 0;
++	s16 *pfmu_data;
++	int offs = subc_id * sizeof(struct mt7996_pfmu_data) / sizeof(*pfmu_data);
+ 
+ 	if (subc_id > MT7996_TXBF_SUBCAR_NUM - 1)
+ 		return -EINVAL;
+@@ -1520,35 +1537,51 @@ mt7996_tm_txbf_profile_update_all(struct mt7996_phy *phy, u16 *val)
+ 	} else if (nss == 3) {
+ 		phi11 = (s16)(angle31 - angle11);
+ 		phi21 = (s16)(angle31 - angle21);
++	} else if (nss == 5) {
++		phi11 = (s16)(angle51 - angle11);
++		phi21 = (s16)(angle51 - angle21);
++		phi31 = (s16)(angle51 - angle31);
++		phi41 = (s16)(angle51 - angle41);
++		offs = subc_id * sizeof(struct mt7996_pfmu_data_5x5) / sizeof(*pfmu_data);
+ 	} 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];
++	pfmu_data = (s16 *)phy->dev->test.txbf_pfmu_data;
++	pfmu_data += offs;
+ 
+ 	if (subc_id < 32)
+-		pfmu_data->subc_idx = cpu_to_le16(subc_id + 224);
++		pfmu_data[0] = cpu_to_le16(subc_id + 224);
+ 	else
+-		pfmu_data->subc_idx = cpu_to_le16(subc_id - 32);
++		pfmu_data[0] = cpu_to_le16(subc_id - 32);
++
++	pfmu_data[1] = cpu_to_le16(phi11);
++	pfmu_data[2] = cpu_to_le16(phi21);
++	pfmu_data[3] = cpu_to_le16(phi31);
++	if (nss == 5)
++		pfmu_data[4] = cpu_to_le16(phi41);
+ 
+-	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),
++				.tag = cpu_to_le16(BF_PROFILE_WRITE_20M_ALL_5X5),
+ 				.len = cpu_to_le16(sizeof(req.pfmu_data_all)),
+ 				.pfmu_id = pfmu_idx,
+ 				.band_idx = phy->mt76->band_idx,
+ 			},
+ 		};
++		int size = MT7996_TXBF_PFMU_DATA_LEN_5X5;
+ 
+-		memcpy(req.pfmu_data_all.buf, dev->test.txbf_pfmu_data, MT7996_TXBF_PFMU_DATA_LEN);
++		if (nss != 5) {
++			size = MT7996_TXBF_PFMU_DATA_LEN;
++			req.pfmu_data_all.tag = cpu_to_le16(BF_PROFILE_WRITE_20M_ALL);
++			req.pfmu_data_all.len = cpu_to_le16(sizeof(req.pfmu_data_all) -
++							    MT7996_TXBF_PFMU_DATA_LEN_5X5 + size);
++		}
++		memcpy(req.pfmu_data_all.buf, dev->test.txbf_pfmu_data, size);
+ 
+ 		return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(BF),
+ 					 &req, sizeof(req), true);
+diff --git a/testmode.c b/testmode.c
+index 09ab68ce4..2dd184e48 100644
+--- a/testmode.c
++++ b/testmode.c
+@@ -471,26 +471,28 @@ static int
+ mt76_testmode_txbf_profile_update_all_cmd(struct mt76_phy *phy, struct nlattr **tb, u32 state)
+ {
+ #define PARAM_UNIT	5
++#define PARAM_UNIT_5X5	6
+ 	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;
++	u16 tmp_val[PARAM_UNIT_5X5], *val = td->txbf_param;
+ 	int idx, rem, ret, i = 0;
++	int param_len = td->tx_antenna_mask == 31 ? PARAM_UNIT_5X5 : PARAM_UNIT;
+ 
+ 	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;
++		idx = i % param_len;
+ 		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) {
++		if (idx == param_len - 1) {
+ 			val[0] = pfmu_idx;
+-			memcpy(val + 1, tmp_val, sizeof(tmp_val));
++			memcpy(val + 1, tmp_val, param_len * sizeof(u16));
+ 			if (dev->test_ops->set_params) {
+ 				ret = dev->test_ops->set_params(phy, tb, state);
+ 				if (ret)
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0072-mtk-wifi-mt76-revert-page_poll-for-kernel-5.4.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0072-mtk-wifi-mt76-revert-page_poll-for-kernel-5.4.patch
new file mode 100644
index 0000000..5870899
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0072-mtk-wifi-mt76-revert-page_poll-for-kernel-5.4.patch
@@ -0,0 +1,627 @@
+From 3ce23c7c2994ccca6569c817b4fd25f057a1f124 Mon Sep 17 00:00:00 2001
+From: Bo Jiao <Bo.Jiao@mediatek.com>
+Date: Mon, 6 Feb 2023 19:49:22 +0800
+Subject: [PATCH 072/120] mtk: wifi: mt76: revert page_poll for kernel 5.4
+
+This reverts commit e8c10835cf062c577ddf426913788c39d30b4bd7.
+
+Change-Id: I4e5764fc545087f691fb4c2f43e7a9cefd1e1657
+---
+ dma.c         | 75 ++++++++++++++++++++++++++-------------------------
+ mac80211.c    | 57 ---------------------------------------
+ mt76.h        | 22 +--------------
+ mt7915/main.c | 26 +++++++-----------
+ usb.c         | 43 ++++++++++++++---------------
+ wed.c         | 50 ++++++++++++++++++++++------------
+ 6 files changed, 104 insertions(+), 169 deletions(-)
+
+diff --git a/dma.c b/dma.c
+index 66c000ef0..33a84f5fa 100644
+--- a/dma.c
++++ b/dma.c
+@@ -178,7 +178,7 @@ mt76_free_pending_rxwi(struct mt76_dev *dev)
+ 	local_bh_disable();
+ 	while ((t = __mt76_get_rxwi(dev)) != NULL) {
+ 		if (t->ptr)
+-			mt76_put_page_pool_buf(t->ptr, false);
++			skb_free_frag(t->ptr);
+ 		kfree(t);
+ 	}
+ 	local_bh_enable();
+@@ -450,9 +450,9 @@ mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx,
+ 		if (!t)
+ 			return NULL;
+ 
+-		dma_sync_single_for_cpu(dev->dma_dev, t->dma_addr,
+-				SKB_WITH_OVERHEAD(q->buf_size),
+-				page_pool_get_dma_dir(q->page_pool));
++		dma_unmap_single(dev->dma_dev, t->dma_addr,
++				 SKB_WITH_OVERHEAD(q->buf_size),
++				 DMA_FROM_DEVICE);
+ 
+ 		buf = t->ptr;
+ 		t->dma_addr = 0;
+@@ -462,9 +462,9 @@ mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx,
+ 		if (drop)
+ 			*drop |= !!(buf1 & MT_DMA_CTL_WO_DROP);
+ 	} else {
+-		dma_sync_single_for_cpu(dev->dma_dev, e->dma_addr[0],
+-				SKB_WITH_OVERHEAD(q->buf_size),
+-				page_pool_get_dma_dir(q->page_pool));
++		dma_unmap_single(dev->dma_dev, e->dma_addr[0],
++				 SKB_WITH_OVERHEAD(q->buf_size),
++				 DMA_FROM_DEVICE);
+ 	}
+ 
+ done:
+@@ -638,7 +638,8 @@ int mt76_dma_rx_fill(struct mt76_dev *dev, struct mt76_queue *q,
+ 		     bool allow_direct)
+ {
+ 	int len = SKB_WITH_OVERHEAD(q->buf_size);
+-	int frames = 0;
++	int frames = 0, offset = q->buf_offset;
++	dma_addr_t addr;
+ 
+ 	if (!q->ndesc)
+ 		return 0;
+@@ -647,28 +648,29 @@ int mt76_dma_rx_fill(struct mt76_dev *dev, struct mt76_queue *q,
+ 
+ 	while (q->queued < q->ndesc - 1) {
+ 		struct mt76_queue_buf qbuf = {};
+-		enum dma_data_direction dir;
+-		dma_addr_t addr;
+-		int offset;
+ 		void *buf = NULL;
+ 
+ 		if (mt76_queue_is_wed_rro_ind(q))
+ 			goto done;
+ 
+-		buf = mt76_get_page_pool_buf(q, &offset, q->buf_size);
++		buf = page_frag_alloc(&q->rx_page, q->buf_size, GFP_ATOMIC);
+ 		if (!buf)
+ 			break;
+ 
+-		addr = page_pool_get_dma_addr(virt_to_head_page(buf)) + offset;
+-		dir = page_pool_get_dma_dir(q->page_pool);
+-		dma_sync_single_for_device(dev->dma_dev, addr, len, dir);
++		addr = dma_map_single(dev->dma_dev, buf, len, DMA_FROM_DEVICE);
++		if (unlikely(dma_mapping_error(dev->dma_dev, addr))) {
++			skb_free_frag(buf);
++			break;
++		}
+ 
+-		qbuf.addr = addr + q->buf_offset;
++		qbuf.addr = addr + offset;
+ done:
+-		qbuf.len = len - q->buf_offset;
++		qbuf.len = len - offset;
+ 		qbuf.skip_unmap = false;
+ 		if (mt76_dma_add_rx_buf(dev, q, &qbuf, buf) < 0) {
+-			mt76_put_page_pool_buf(buf, allow_direct);
++			dma_unmap_single(dev->dma_dev, addr, len,
++					 DMA_FROM_DEVICE);
++			skb_free_frag(buf);
+ 			break;
+ 		}
+ 		frames++;
+@@ -722,10 +724,6 @@ mt76_dma_alloc_queue(struct mt76_dev *dev, struct mt76_queue *q,
+ 	if (!q->entry)
+ 		return -ENOMEM;
+ 
+-	ret = mt76_create_page_pool(dev, q);
+-	if (ret)
+-		return ret;
+-
+ 	ret = mt76_wed_dma_setup(dev, q, false);
+ 	if (ret)
+ 		return ret;
+@@ -744,6 +742,7 @@ mt76_dma_alloc_queue(struct mt76_dev *dev, struct mt76_queue *q,
+ static void
+ mt76_dma_rx_cleanup(struct mt76_dev *dev, struct mt76_queue *q)
+ {
++	struct page *page;
+ 	void *buf;
+ 	bool more;
+ 
+@@ -759,7 +758,7 @@ mt76_dma_rx_cleanup(struct mt76_dev *dev, struct mt76_queue *q)
+ 			break;
+ 
+ 		if (!mt76_queue_is_wed_rro(q))
+-			mt76_put_page_pool_buf(buf, false);
++			skb_free_frag(buf);
+ 	} while (1);
+ 
+ 	spin_lock_bh(&q->lock);
+@@ -769,6 +768,16 @@ mt76_dma_rx_cleanup(struct mt76_dev *dev, struct mt76_queue *q)
+ 	}
+ 
+ 	spin_unlock_bh(&q->lock);
++
++	if (mt76_queue_is_wed_rx(q))
++		return;
++
++	if (!q->rx_page.va)
++		return;
++
++	page = virt_to_page(q->rx_page.va);
++	__page_frag_cache_drain(page, q->rx_page.pagecnt_bias);
++	memset(&q->rx_page, 0, sizeof(q->rx_page));
+ }
+ 
+ static void
+@@ -791,15 +800,10 @@ mt76_dma_rx_reset(struct mt76_dev *dev, enum mt76_rxq_id qid)
+ 	/* reset WED rx queues */
+ 	mt76_wed_dma_setup(dev, q, true);
+ 
+-	if (mt76_queue_is_wed_tx_free(q))
+-		return;
+-
+-	if (mtk_wed_device_active(&dev->mmio.wed) &&
+-	    mt76_queue_is_wed_rro(q))
+-		return;
+-
+-	mt76_dma_sync_idx(dev, q);
+-	mt76_dma_rx_fill(dev, q, false);
++	if (!mt76_queue_is_wed_tx_free(q)) {
++		mt76_dma_sync_idx(dev, q);
++		mt76_dma_rx_fill(dev, q, false);
++	}
+ }
+ 
+ static void
+@@ -816,7 +820,7 @@ mt76_add_fragment(struct mt76_dev *dev, struct mt76_queue *q, void *data,
+ 
+ 		skb_add_rx_frag(skb, nr_frags, page, offset, len, q->buf_size);
+ 	} else {
+-		mt76_put_page_pool_buf(data, allow_direct);
++		skb_free_frag(data);
+ 	}
+ 
+ 	if (more)
+@@ -891,7 +895,6 @@ mt76_dma_rx_process(struct mt76_dev *dev, struct mt76_queue *q, int budget)
+ 			goto free_frag;
+ 
+ 		skb_reserve(skb, q->buf_offset);
+-		skb_mark_for_recycle(skb);
+ 
+ 		*(u32 *)skb->cb = info;
+ 
+@@ -907,7 +910,7 @@ mt76_dma_rx_process(struct mt76_dev *dev, struct mt76_queue *q, int budget)
+ 		continue;
+ 
+ free_frag:
+-		mt76_put_page_pool_buf(data, allow_direct);
++		skb_free_frag(data);
+ 	}
+ 
+ 	mt76_dma_rx_fill(dev, q, true);
+@@ -1010,8 +1013,6 @@ void mt76_dma_cleanup(struct mt76_dev *dev)
+ 
+ 		netif_napi_del(&dev->napi[i]);
+ 		mt76_dma_rx_cleanup(dev, q);
+-
+-		page_pool_destroy(q->page_pool);
+ 	}
+ 
+ 	if (mtk_wed_device_active(&dev->mmio.wed))
+diff --git a/mac80211.c b/mac80211.c
+index 517fb90fe..f7b9ba6a0 100644
+--- a/mac80211.c
++++ b/mac80211.c
+@@ -4,7 +4,6 @@
+  */
+ #include <linux/sched.h>
+ #include <linux/of.h>
+-#include <net/page_pool.h>
+ #include "mt76.h"
+ 
+ static const struct ieee80211_channel mt76_channels_2ghz[] = {
+@@ -566,47 +565,6 @@ void mt76_unregister_phy(struct mt76_phy *phy)
+ }
+ EXPORT_SYMBOL_GPL(mt76_unregister_phy);
+ 
+-int mt76_create_page_pool(struct mt76_dev *dev, struct mt76_queue *q)
+-{
+-	struct page_pool_params pp_params = {
+-		.order = 0,
+-		.flags = PP_FLAG_PAGE_FRAG,
+-		.nid = NUMA_NO_NODE,
+-		.dev = dev->dma_dev,
+-	};
+-	int idx = q - dev->q_rx;
+-
+-	switch (idx) {
+-	case MT_RXQ_MAIN:
+-	case MT_RXQ_BAND1:
+-	case MT_RXQ_BAND2:
+-		pp_params.pool_size = 256;
+-		break;
+-	default:
+-		pp_params.pool_size = 16;
+-		break;
+-	}
+-
+-	if (mt76_is_mmio(dev)) {
+-		/* rely on page_pool for DMA mapping */
+-		pp_params.flags |= PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV;
+-		pp_params.dma_dir = DMA_FROM_DEVICE;
+-		pp_params.max_len = PAGE_SIZE;
+-		pp_params.offset = 0;
+-	}
+-
+-	q->page_pool = page_pool_create(&pp_params);
+-	if (IS_ERR(q->page_pool)) {
+-		int err = PTR_ERR(q->page_pool);
+-
+-		q->page_pool = NULL;
+-		return err;
+-	}
+-
+-	return 0;
+-}
+-EXPORT_SYMBOL_GPL(mt76_create_page_pool);
+-
+ struct mt76_dev *
+ mt76_alloc_device(struct device *pdev, unsigned int size,
+ 		  const struct ieee80211_ops *ops,
+@@ -1819,21 +1777,6 @@ void mt76_ethtool_worker(struct mt76_ethtool_worker_info *wi,
+ }
+ EXPORT_SYMBOL_GPL(mt76_ethtool_worker);
+ 
+-void mt76_ethtool_page_pool_stats(struct mt76_dev *dev, u64 *data, int *index)
+-{
+-#ifdef CONFIG_PAGE_POOL_STATS
+-	struct page_pool_stats stats = {};
+-	int i;
+-
+-	mt76_for_each_q_rx(dev, i)
+-		page_pool_get_stats(dev->q_rx[i].page_pool, &stats);
+-
+-	page_pool_ethtool_stats_get(data, &stats);
+-	*index += page_pool_ethtool_stats_get_count();
+-#endif
+-}
+-EXPORT_SYMBOL_GPL(mt76_ethtool_page_pool_stats);
+-
+ enum mt76_dfs_state mt76_phy_dfs_state(struct mt76_phy *phy)
+ {
+ 	struct ieee80211_hw *hw = phy->hw;
+diff --git a/mt76.h b/mt76.h
+index 543d9de51..540814f18 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -246,7 +246,7 @@ struct mt76_queue {
+ 
+ 	dma_addr_t desc_dma;
+ 	struct sk_buff *rx_head;
+-	struct page_pool *page_pool;
++	struct page_frag_cache rx_page;
+ };
+ 
+ struct mt76_mcu_ops {
+@@ -1601,7 +1601,6 @@ mt76u_bulk_msg(struct mt76_dev *dev, void *data, int len, int *actual_len,
+ 	return usb_bulk_msg(udev, pipe, data, len, actual_len, timeout);
+ }
+ 
+-void mt76_ethtool_page_pool_stats(struct mt76_dev *dev, u64 *data, int *index);
+ void mt76_ethtool_worker(struct mt76_ethtool_worker_info *wi,
+ 			 struct mt76_sta_stats *stats, bool eht);
+ int mt76_skb_adjust_pad(struct sk_buff *skb, int pad);
+@@ -1747,25 +1746,6 @@ void __mt76_set_tx_blocked(struct mt76_dev *dev, bool blocked);
+ struct mt76_txwi_cache *mt76_rx_token_release(struct mt76_dev *dev, int token);
+ int mt76_rx_token_consume(struct mt76_dev *dev, void *ptr,
+ 			  struct mt76_txwi_cache *r, dma_addr_t phys);
+-int mt76_create_page_pool(struct mt76_dev *dev, struct mt76_queue *q);
+-static inline void mt76_put_page_pool_buf(void *buf, bool allow_direct)
+-{
+-	struct page *page = virt_to_head_page(buf);
+-
+-	page_pool_put_full_page(page->pp, page, allow_direct);
+-}
+-
+-static inline void *
+-mt76_get_page_pool_buf(struct mt76_queue *q, u32 *offset, u32 size)
+-{
+-	struct page *page;
+-
+-	page = page_pool_dev_alloc_frag(q->page_pool, offset, size);
+-	if (!page)
+-		return NULL;
+-
+-	return page_address(page) + *offset;
+-}
+ 
+ static inline void mt76_set_tx_blocked(struct mt76_dev *dev, bool blocked)
+ {
+diff --git a/mt7915/main.c b/mt7915/main.c
+index b16a63366..ad91fc3c8 100644
+--- a/mt7915/main.c
++++ b/mt7915/main.c
+@@ -1402,22 +1402,19 @@ void mt7915_get_et_strings(struct ieee80211_hw *hw,
+ 			   struct ieee80211_vif *vif,
+ 			   u32 sset, u8 *data)
+ {
+-	if (sset != ETH_SS_STATS)
+-		return;
+-
+-	memcpy(data, mt7915_gstrings_stats, sizeof(mt7915_gstrings_stats));
+-	data += sizeof(mt7915_gstrings_stats);
+-	page_pool_ethtool_stats_get_strings(data);
++	if (sset == ETH_SS_STATS)
++		memcpy(data, mt7915_gstrings_stats,
++		       sizeof(mt7915_gstrings_stats));
+ }
+ 
+ static
+ int mt7915_get_et_sset_count(struct ieee80211_hw *hw,
+ 			     struct ieee80211_vif *vif, int sset)
+ {
+-	if (sset != ETH_SS_STATS)
+-		return 0;
++	if (sset == ETH_SS_STATS)
++		return MT7915_SSTATS_LEN;
+ 
+-	return MT7915_SSTATS_LEN + page_pool_ethtool_stats_get_count();
++	return 0;
+ }
+ 
+ static void mt7915_ethtool_worker(void *wi_data, struct ieee80211_sta *sta)
+@@ -1445,7 +1442,7 @@ void mt7915_get_et_stats(struct ieee80211_hw *hw,
+ 		.idx = mvif->mt76.idx,
+ 	};
+ 	/* See mt7915_ampdu_stat_read_phy, etc */
+-	int i, ei = 0, stats_size;
++	int i, ei = 0;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+@@ -1557,12 +1554,9 @@ void mt7915_get_et_stats(struct ieee80211_hw *hw,
+ 		return;
+ 
+ 	ei += wi.worker_stat_count;
+-
+-	mt76_ethtool_page_pool_stats(&dev->mt76, &data[ei], &ei);
+-
+-	stats_size = MT7915_SSTATS_LEN + page_pool_ethtool_stats_get_count();
+-	if (ei != stats_size)
+-		dev_err(dev->mt76.dev, "ei: %d size: %d", ei, stats_size);
++	if (ei != MT7915_SSTATS_LEN)
++		dev_err(dev->mt76.dev, "ei: %d  MT7915_SSTATS_LEN: %d",
++			ei, (int)MT7915_SSTATS_LEN);
+ }
+ 
+ static void
+diff --git a/usb.c b/usb.c
+index dc690d1cd..058f2d124 100644
+--- a/usb.c
++++ b/usb.c
+@@ -319,27 +319,29 @@ mt76u_set_endpoints(struct usb_interface *intf,
+ 
+ static int
+ mt76u_fill_rx_sg(struct mt76_dev *dev, struct mt76_queue *q, struct urb *urb,
+-		 int nsgs)
++		 int nsgs, gfp_t gfp)
+ {
+ 	int i;
+ 
+ 	for (i = 0; i < nsgs; i++) {
++		struct page *page;
+ 		void *data;
+ 		int offset;
+ 
+-		data = mt76_get_page_pool_buf(q, &offset, q->buf_size);
++		data = page_frag_alloc(&q->rx_page, q->buf_size, gfp);
+ 		if (!data)
+ 			break;
+ 
+-		sg_set_page(&urb->sg[i], virt_to_head_page(data), q->buf_size,
+-			    offset);
++		page = virt_to_head_page(data);
++		offset = data - page_address(page);
++		sg_set_page(&urb->sg[i], page, q->buf_size, offset);
+ 	}
+ 
+ 	if (i < nsgs) {
+ 		int j;
+ 
+ 		for (j = nsgs; j < urb->num_sgs; j++)
+-			mt76_put_page_pool_buf(sg_virt(&urb->sg[j]), false);
++			skb_free_frag(sg_virt(&urb->sg[j]));
+ 		urb->num_sgs = i;
+ 	}
+ 
+@@ -352,16 +354,15 @@ mt76u_fill_rx_sg(struct mt76_dev *dev, struct mt76_queue *q, struct urb *urb,
+ 
+ static int
+ mt76u_refill_rx(struct mt76_dev *dev, struct mt76_queue *q,
+-		struct urb *urb, int nsgs)
++		struct urb *urb, int nsgs, gfp_t gfp)
+ {
+ 	enum mt76_rxq_id qid = q - &dev->q_rx[MT_RXQ_MAIN];
+-	int offset;
+ 
+ 	if (qid == MT_RXQ_MAIN && dev->usb.sg_en)
+-		return mt76u_fill_rx_sg(dev, q, urb, nsgs);
++		return mt76u_fill_rx_sg(dev, q, urb, nsgs, gfp);
+ 
+ 	urb->transfer_buffer_length = q->buf_size;
+-	urb->transfer_buffer = mt76_get_page_pool_buf(q, &offset, q->buf_size);
++	urb->transfer_buffer = page_frag_alloc(&q->rx_page, q->buf_size, gfp);
+ 
+ 	return urb->transfer_buffer ? 0 : -ENOMEM;
+ }
+@@ -399,7 +400,7 @@ mt76u_rx_urb_alloc(struct mt76_dev *dev, struct mt76_queue *q,
+ 	if (err)
+ 		return err;
+ 
+-	return mt76u_refill_rx(dev, q, e->urb, sg_size);
++	return mt76u_refill_rx(dev, q, e->urb, sg_size, GFP_KERNEL);
+ }
+ 
+ static void mt76u_urb_free(struct urb *urb)
+@@ -407,10 +408,10 @@ static void mt76u_urb_free(struct urb *urb)
+ 	int i;
+ 
+ 	for (i = 0; i < urb->num_sgs; i++)
+-		mt76_put_page_pool_buf(sg_virt(&urb->sg[i]), false);
++		skb_free_frag(sg_virt(&urb->sg[i]));
+ 
+ 	if (urb->transfer_buffer)
+-		mt76_put_page_pool_buf(urb->transfer_buffer, false);
++		skb_free_frag(urb->transfer_buffer);
+ 
+ 	usb_free_urb(urb);
+ }
+@@ -546,8 +547,6 @@ mt76u_process_rx_entry(struct mt76_dev *dev, struct urb *urb,
+ 		len -= data_len;
+ 		nsgs++;
+ 	}
+-
+-	skb_mark_for_recycle(skb);
+ 	dev->drv->rx_skb(dev, MT_RXQ_MAIN, skb, NULL);
+ 
+ 	return nsgs;
+@@ -613,7 +612,7 @@ mt76u_process_rx_queue(struct mt76_dev *dev, struct mt76_queue *q)
+ 
+ 		count = mt76u_process_rx_entry(dev, urb, q->buf_size);
+ 		if (count > 0) {
+-			err = mt76u_refill_rx(dev, q, urb, count);
++			err = mt76u_refill_rx(dev, q, urb, count, GFP_ATOMIC);
+ 			if (err < 0)
+ 				break;
+ 		}
+@@ -664,10 +663,6 @@ mt76u_alloc_rx_queue(struct mt76_dev *dev, enum mt76_rxq_id qid)
+ 	struct mt76_queue *q = &dev->q_rx[qid];
+ 	int i, err;
+ 
+-	err = mt76_create_page_pool(dev, q);
+-	if (err)
+-		return err;
+-
+ 	spin_lock_init(&q->lock);
+ 	q->entry = devm_kcalloc(dev->dev,
+ 				MT_NUM_RX_ENTRIES, sizeof(*q->entry),
+@@ -696,6 +691,7 @@ EXPORT_SYMBOL_GPL(mt76u_alloc_mcu_queue);
+ static void
+ mt76u_free_rx_queue(struct mt76_dev *dev, struct mt76_queue *q)
+ {
++	struct page *page;
+ 	int i;
+ 
+ 	for (i = 0; i < q->ndesc; i++) {
+@@ -705,8 +701,13 @@ mt76u_free_rx_queue(struct mt76_dev *dev, struct mt76_queue *q)
+ 		mt76u_urb_free(q->entry[i].urb);
+ 		q->entry[i].urb = NULL;
+ 	}
+-	page_pool_destroy(q->page_pool);
+-	q->page_pool = NULL;
++
++	if (!q->rx_page.va)
++		return;
++
++	page = virt_to_page(q->rx_page.va);
++	__page_frag_cache_drain(page, q->rx_page.pagecnt_bias);
++	memset(&q->rx_page, 0, sizeof(q->rx_page));
+ }
+ 
+ static void mt76u_free_rx(struct mt76_dev *dev)
+diff --git a/wed.c b/wed.c
+index f89e45375..8eca4d818 100644
+--- a/wed.c
++++ b/wed.c
+@@ -9,8 +9,12 @@
+ void mt76_wed_release_rx_buf(struct mtk_wed_device *wed)
+ {
+ 	struct mt76_dev *dev = container_of(wed, struct mt76_dev, mmio.wed);
++	u32 length;
+ 	int i;
+ 
++	length = SKB_DATA_ALIGN(NET_SKB_PAD + wed->wlan.rx_size +
++				sizeof(struct skb_shared_info));
++
+ 	for (i = 0; i < dev->rx_token_size; i++) {
+ 		struct mt76_txwi_cache *t;
+ 
+@@ -18,7 +22,9 @@ void mt76_wed_release_rx_buf(struct mtk_wed_device *wed)
+ 		if (!t || !t->ptr)
+ 			continue;
+ 
+-		mt76_put_page_pool_buf(t->ptr, false);
++		dma_unmap_single(dev->dma_dev, t->dma_addr,
++				 wed->wlan.rx_size, DMA_FROM_DEVICE);
++		__free_pages(virt_to_page(t->ptr), get_order(length));
+ 		t->ptr = NULL;
+ 
+ 		mt76_put_rxwi(dev, t);
+@@ -33,33 +39,45 @@ u32 mt76_wed_init_rx_buf(struct mtk_wed_device *wed, int size)
+ {
+ 	struct mt76_dev *dev = container_of(wed, struct mt76_dev, mmio.wed);
+ 	struct mtk_wed_bm_desc *desc = wed->rx_buf_ring.desc;
+-	struct mt76_queue *q = &dev->q_rx[MT_RXQ_MAIN];
+-	int i, len = SKB_WITH_OVERHEAD(q->buf_size);
+-	struct mt76_txwi_cache *t = NULL;
++	u32 length;
++	int i;
++
++	length = SKB_DATA_ALIGN(NET_SKB_PAD + wed->wlan.rx_size +
++				sizeof(struct skb_shared_info));
+ 
+ 	for (i = 0; i < size; i++) {
+-		enum dma_data_direction dir;
++		struct mt76_txwi_cache *t = mt76_get_rxwi(dev);
+ 		dma_addr_t addr;
+-		u32 offset;
++		struct page *page;
+ 		int token;
+-		void *buf;
++		void *ptr;
+ 
+-		t = mt76_get_rxwi(dev);
+ 		if (!t)
+ 			goto unmap;
+ 
+-		buf = mt76_get_page_pool_buf(q, &offset, q->buf_size);
+-		if (!buf)
++		page = __dev_alloc_pages(GFP_KERNEL, get_order(length));
++		if (!page) {
++			mt76_put_rxwi(dev, t);
+ 			goto unmap;
++		}
+ 
+-		addr = page_pool_get_dma_addr(virt_to_head_page(buf)) + offset;
+-		dir = page_pool_get_dma_dir(q->page_pool);
+-		dma_sync_single_for_device(dev->dma_dev, addr, len, dir);
++		addr = dma_map_single(dev->dma_dev, ptr,
++					  wed->wlan.rx_size,
++					  DMA_TO_DEVICE);
++
++		if (unlikely(dma_mapping_error(dev->dev, addr))) {
++			skb_free_frag(ptr);
++			mt76_put_rxwi(dev, t);
++			goto unmap;
++		}
+ 
+ 		desc->buf0 = cpu_to_le32(addr);
+-		token = mt76_rx_token_consume(dev, buf, t, addr);
++		token = mt76_rx_token_consume(dev, ptr, t, addr);
+ 		if (token < 0) {
+-			mt76_put_page_pool_buf(buf, false);
++			dma_unmap_single(dev->dma_dev, addr,
++					 wed->wlan.rx_size, DMA_TO_DEVICE);
++			__free_pages(page, get_order(length));
++			mt76_put_rxwi(dev, t);
+ 			goto unmap;
+ 		}
+ 
+@@ -74,8 +92,6 @@ u32 mt76_wed_init_rx_buf(struct mtk_wed_device *wed, int size)
+ 	return 0;
+ 
+ unmap:
+-	if (t)
+-		mt76_put_rxwi(dev, t);
+ 	mt76_wed_release_rx_buf(wed);
+ 
+ 	return -ENOMEM;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0073-mtk-wifi-mt76-rework-wed-rx-flow.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0073-mtk-wifi-mt76-rework-wed-rx-flow.patch
new file mode 100644
index 0000000..9e61331
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0073-mtk-wifi-mt76-rework-wed-rx-flow.patch
@@ -0,0 +1,542 @@
+From 55f3c7cda12d32fb64c8725693e64a225792d3da Mon Sep 17 00:00:00 2001
+From: Bo Jiao <Bo.Jiao@mediatek.com>
+Date: Mon, 6 Feb 2023 13:37:23 +0800
+Subject: [PATCH 073/120] mtk: wifi: mt76: rework wed rx flow
+
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Change-Id: Icd787345c811cb5ad30d9c7c1c5f9e5298bd3be6
+---
+ dma.c           | 125 +++++++++++++++++++++++++++++++-----------------
+ mac80211.c      |   2 +-
+ mt76.h          |  25 ++++++----
+ mt7915/mmio.c   |   3 +-
+ mt7915/mt7915.h |   1 +
+ tx.c            |  16 +++----
+ wed.c           |  57 ++++++++++++++--------
+ 7 files changed, 144 insertions(+), 85 deletions(-)
+
+diff --git a/dma.c b/dma.c
+index 33a84f5fa..c54187bd6 100644
+--- a/dma.c
++++ b/dma.c
+@@ -64,17 +64,17 @@ mt76_alloc_txwi(struct mt76_dev *dev)
+ 	return t;
+ }
+ 
+-static struct mt76_txwi_cache *
++static struct mt76_rxwi_cache *
+ mt76_alloc_rxwi(struct mt76_dev *dev)
+ {
+-	struct mt76_txwi_cache *t;
++	struct mt76_rxwi_cache *r;
+ 
+-	t = kzalloc(L1_CACHE_ALIGN(sizeof(*t)), GFP_ATOMIC);
+-	if (!t)
++	r = kzalloc(L1_CACHE_ALIGN(sizeof(*r)), GFP_ATOMIC);
++	if (!r)
+ 		return NULL;
+ 
+-	t->ptr = NULL;
+-	return t;
++	r->ptr = NULL;
++	return r;
+ }
+ 
+ static struct mt76_txwi_cache *
+@@ -93,20 +93,20 @@ __mt76_get_txwi(struct mt76_dev *dev)
+ 	return t;
+ }
+ 
+-static struct mt76_txwi_cache *
++static struct mt76_rxwi_cache *
+ __mt76_get_rxwi(struct mt76_dev *dev)
+ {
+-	struct mt76_txwi_cache *t = NULL;
++	struct mt76_rxwi_cache *r = NULL;
+ 
+-	spin_lock_bh(&dev->wed_lock);
++	spin_lock_bh(&dev->lock);
+ 	if (!list_empty(&dev->rxwi_cache)) {
+-		t = list_first_entry(&dev->rxwi_cache, struct mt76_txwi_cache,
++		r = list_first_entry(&dev->rxwi_cache, struct mt76_rxwi_cache,
+ 				     list);
+-		list_del(&t->list);
++		list_del(&r->list);
+ 	}
+-	spin_unlock_bh(&dev->wed_lock);
++	spin_unlock_bh(&dev->lock);
+ 
+-	return t;
++	return r;
+ }
+ 
+ static struct mt76_txwi_cache *
+@@ -120,13 +120,13 @@ mt76_get_txwi(struct mt76_dev *dev)
+ 	return mt76_alloc_txwi(dev);
+ }
+ 
+-struct mt76_txwi_cache *
++struct mt76_rxwi_cache *
+ mt76_get_rxwi(struct mt76_dev *dev)
+ {
+-	struct mt76_txwi_cache *t = __mt76_get_rxwi(dev);
++	struct mt76_rxwi_cache *r = __mt76_get_rxwi(dev);
+ 
+-	if (t)
+-		return t;
++	if (r)
++		return r;
+ 
+ 	return mt76_alloc_rxwi(dev);
+ }
+@@ -145,14 +145,14 @@ mt76_put_txwi(struct mt76_dev *dev, struct mt76_txwi_cache *t)
+ EXPORT_SYMBOL_GPL(mt76_put_txwi);
+ 
+ void
+-mt76_put_rxwi(struct mt76_dev *dev, struct mt76_txwi_cache *t)
++mt76_put_rxwi(struct mt76_dev *dev, struct mt76_rxwi_cache *r)
+ {
+-	if (!t)
++	if (!r)
+ 		return;
+ 
+-	spin_lock_bh(&dev->wed_lock);
+-	list_add(&t->list, &dev->rxwi_cache);
+-	spin_unlock_bh(&dev->wed_lock);
++	spin_lock_bh(&dev->lock);
++	list_add(&r->list, &dev->rxwi_cache);
++	spin_unlock_bh(&dev->lock);
+ }
+ EXPORT_SYMBOL_GPL(mt76_put_rxwi);
+ 
+@@ -173,13 +173,13 @@ mt76_free_pending_txwi(struct mt76_dev *dev)
+ void
+ mt76_free_pending_rxwi(struct mt76_dev *dev)
+ {
+-	struct mt76_txwi_cache *t;
++	struct mt76_rxwi_cache *r;
+ 
+ 	local_bh_disable();
+-	while ((t = __mt76_get_rxwi(dev)) != NULL) {
+-		if (t->ptr)
+-			skb_free_frag(t->ptr);
+-		kfree(t);
++	while ((r = __mt76_get_rxwi(dev)) != NULL) {
++		if (r->ptr)
++			skb_free_frag(r->ptr);
++		kfree(r);
+ 	}
+ 	local_bh_enable();
+ }
+@@ -225,10 +225,10 @@ void mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q)
+ 
+ static int
+ mt76_dma_add_rx_buf(struct mt76_dev *dev, struct mt76_queue *q,
+-		    struct mt76_queue_buf *buf, void *data)
++		    struct mt76_queue_buf *buf, void *data,
++		    struct mt76_rxwi_cache *rxwi)
+ {
+ 	struct mt76_queue_entry *entry = &q->entry[q->head];
+-	struct mt76_txwi_cache *txwi = NULL;
+ 	struct mt76_desc *desc;
+ 	int idx = q->head;
+ 	u32 buf1 = 0, ctrl;
+@@ -249,13 +249,15 @@ mt76_dma_add_rx_buf(struct mt76_dev *dev, struct mt76_queue *q,
+ #endif
+ 
+ 	if (mt76_queue_is_wed_rx(q)) {
+-		txwi = mt76_get_rxwi(dev);
+-		if (!txwi)
+-			return -ENOMEM;
++		if (!rxwi) {
++			rxwi = mt76_get_rxwi(dev);
++			if (!rxwi)
++				return -ENOMEM;
++		}
+ 
+-		rx_token = mt76_rx_token_consume(dev, data, txwi, buf->addr);
++		rx_token = mt76_rx_token_consume(dev, data, rxwi, buf->addr);
+ 		if (rx_token < 0) {
+-			mt76_put_rxwi(dev, txwi);
++			mt76_put_rxwi(dev, rxwi);
+ 			return -ENOMEM;
+ 		}
+ 
+@@ -271,7 +273,7 @@ mt76_dma_add_rx_buf(struct mt76_dev *dev, struct mt76_queue *q,
+ done:
+ 	entry->dma_addr[0] = buf->addr;
+ 	entry->dma_len[0] = buf->len;
+-	entry->txwi = txwi;
++	entry->rxwi = rxwi;
+ 	entry->buf = data;
+ 	entry->wcid = 0xffff;
+ 	entry->skip_buf1 = true;
+@@ -420,7 +422,7 @@ mt76_dma_tx_cleanup(struct mt76_dev *dev, struct mt76_queue *q, bool flush)
+ 
+ static void *
+ mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx,
+-		 int *len, u32 *info, bool *more, bool *drop)
++		 int *len, u32 *info, bool *more, bool *drop, bool flush)
+ {
+ 	struct mt76_queue_entry *e = &q->entry[idx];
+ 	struct mt76_desc *desc = &q->desc[idx];
+@@ -445,20 +447,53 @@ mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx,
+ 
+ 	if (mt76_queue_is_wed_rx(q)) {
+ 		u32 token = FIELD_GET(MT_DMA_CTL_TOKEN, buf1);
+-		struct mt76_txwi_cache *t = mt76_rx_token_release(dev, token);
++		struct mt76_rxwi_cache *r = mt76_rx_token_release(dev, token);
+ 
+-		if (!t)
++		if (!r)
+ 			return NULL;
+ 
+-		dma_unmap_single(dev->dma_dev, t->dma_addr,
++		dma_unmap_single(dev->dma_dev, r->dma_addr,
+ 				 SKB_WITH_OVERHEAD(q->buf_size),
+ 				 DMA_FROM_DEVICE);
+ 
+-		buf = t->ptr;
+-		t->dma_addr = 0;
+-		t->ptr = NULL;
++		if (flush) {
++			buf = r->ptr;
++			r->dma_addr = 0;
++			r->ptr = NULL;
++
++			mt76_put_rxwi(dev, r);
++		} else {
++			struct mt76_queue_buf qbuf;
++
++			buf = page_frag_alloc(&q->rx_page, q->buf_size, GFP_ATOMIC);
++			if (!buf)
++				return NULL;
++
++			memcpy(buf, r->ptr, SKB_WITH_OVERHEAD(q->buf_size));
++
++			r->dma_addr = dma_map_single(dev->dma_dev, r->ptr,
++						     SKB_WITH_OVERHEAD(q->buf_size),
++						     DMA_FROM_DEVICE);
++			if (unlikely(dma_mapping_error(dev->dma_dev, r->dma_addr))) {
++				skb_free_frag(r->ptr);
++				mt76_put_rxwi(dev, r);
++				return NULL;
++			}
++
++			qbuf.addr = r->dma_addr;
++			qbuf.len = SKB_WITH_OVERHEAD(q->buf_size);
++			qbuf.skip_unmap = false;
++
++			if (mt76_dma_add_rx_buf(dev, q, &qbuf, r->ptr, r) < 0) {
++				dma_unmap_single(dev->dma_dev, r->dma_addr,
++						 SKB_WITH_OVERHEAD(q->buf_size),
++						 DMA_FROM_DEVICE);
++				skb_free_frag(r->ptr);
++				mt76_put_rxwi(dev, r);
++				return NULL;
++			}
++		}
+ 
+-		mt76_put_rxwi(dev, t);
+ 		if (drop)
+ 			*drop |= !!(buf1 & MT_DMA_CTL_WO_DROP);
+ 	} else {
+@@ -495,7 +530,7 @@ mt76_dma_dequeue(struct mt76_dev *dev, struct mt76_queue *q, bool flush,
+ 	q->tail = (q->tail + 1) % q->ndesc;
+ 	q->queued--;
+ 
+-	return mt76_dma_get_buf(dev, q, idx, len, info, more, drop);
++	return mt76_dma_get_buf(dev, q, idx, len, info, more, drop, flush);
+ }
+ 
+ static int
+@@ -667,7 +702,7 @@ int mt76_dma_rx_fill(struct mt76_dev *dev, struct mt76_queue *q,
+ done:
+ 		qbuf.len = len - offset;
+ 		qbuf.skip_unmap = false;
+-		if (mt76_dma_add_rx_buf(dev, q, &qbuf, buf) < 0) {
++		if (mt76_dma_add_rx_buf(dev, q, &qbuf, buf, NULL) < 0) {
+ 			dma_unmap_single(dev->dma_dev, addr, len,
+ 					 DMA_FROM_DEVICE);
+ 			skb_free_frag(buf);
+diff --git a/mac80211.c b/mac80211.c
+index f7b9ba6a0..c7b222837 100644
+--- a/mac80211.c
++++ b/mac80211.c
+@@ -595,7 +595,6 @@ mt76_alloc_device(struct device *pdev, unsigned int size,
+ 	spin_lock_init(&dev->lock);
+ 	spin_lock_init(&dev->cc_lock);
+ 	spin_lock_init(&dev->status_lock);
+-	spin_lock_init(&dev->wed_lock);
+ 	mutex_init(&dev->mutex);
+ 	init_waitqueue_head(&dev->tx_wait);
+ 
+@@ -628,6 +627,7 @@ mt76_alloc_device(struct device *pdev, unsigned int size,
+ 	INIT_LIST_HEAD(&dev->txwi_cache);
+ 	INIT_LIST_HEAD(&dev->rxwi_cache);
+ 	dev->token_size = dev->drv->token_size;
++	dev->rx_token_size = dev->drv->rx_token_size;
+ 
+ 	for (i = 0; i < ARRAY_SIZE(dev->q_rx); i++)
+ 		skb_queue_head_init(&dev->rx_skb[i]);
+diff --git a/mt76.h b/mt76.h
+index 540814f18..878553a20 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -200,6 +200,7 @@ struct mt76_queue_entry {
+ 	};
+ 	union {
+ 		struct mt76_txwi_cache *txwi;
++		struct mt76_rxwi_cache *rxwi;
+ 		struct urb *urb;
+ 		int buf_sz;
+ 	};
+@@ -411,12 +412,16 @@ struct mt76_txwi_cache {
+ 	struct list_head list;
+ 	dma_addr_t dma_addr;
+ 
+-	union {
+-		struct sk_buff *skb;
+-		void *ptr;
+-	};
+-
+ 	unsigned long jiffies;
++
++	struct sk_buff *skb;
++};
++
++struct mt76_rxwi_cache {
++	struct list_head list;
++	dma_addr_t dma_addr;
++
++	void *ptr;
+ };
+ 
+ struct mt76_rx_tid {
+@@ -504,6 +509,7 @@ struct mt76_driver_ops {
+ 	u16 txwi_size;
+ 	u16 token_size;
+ 	u8 mcs_rates;
++	u16 rx_token_size;
+ 
+ 	void (*update_survey)(struct mt76_phy *phy);
+ 
+@@ -881,7 +887,6 @@ struct mt76_dev {
+ 
+ 	struct ieee80211_hw *hw;
+ 
+-	spinlock_t wed_lock;
+ 	spinlock_t lock;
+ 	spinlock_t cc_lock;
+ 
+@@ -1563,8 +1568,8 @@ mt76_tx_status_get_hw(struct mt76_dev *dev, struct sk_buff *skb)
+ }
+ 
+ void mt76_put_txwi(struct mt76_dev *dev, struct mt76_txwi_cache *t);
+-void mt76_put_rxwi(struct mt76_dev *dev, struct mt76_txwi_cache *t);
+-struct mt76_txwi_cache *mt76_get_rxwi(struct mt76_dev *dev);
++void mt76_put_rxwi(struct mt76_dev *dev, struct mt76_rxwi_cache *r);
++struct mt76_rxwi_cache *mt76_get_rxwi(struct mt76_dev *dev);
+ void mt76_free_pending_rxwi(struct mt76_dev *dev);
+ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
+ 		      struct napi_struct *napi);
+@@ -1743,9 +1748,9 @@ struct mt76_txwi_cache *
+ mt76_token_release(struct mt76_dev *dev, int token, bool *wake);
+ int mt76_token_consume(struct mt76_dev *dev, struct mt76_txwi_cache **ptxwi);
+ void __mt76_set_tx_blocked(struct mt76_dev *dev, bool blocked);
+-struct mt76_txwi_cache *mt76_rx_token_release(struct mt76_dev *dev, int token);
++struct mt76_rxwi_cache *mt76_rx_token_release(struct mt76_dev *dev, int token);
+ int mt76_rx_token_consume(struct mt76_dev *dev, void *ptr,
+-			  struct mt76_txwi_cache *r, dma_addr_t phys);
++			  struct mt76_rxwi_cache *r, dma_addr_t phys);
+ 
+ static inline void mt76_set_tx_blocked(struct mt76_dev *dev, bool blocked)
+ {
+diff --git a/mt7915/mmio.c b/mt7915/mmio.c
+index 6004d64f5..5938bd9f2 100644
+--- a/mt7915/mmio.c
++++ b/mt7915/mmio.c
+@@ -714,7 +714,7 @@ int mt7915_mmio_wed_init(struct mt7915_dev *dev, void *pdev_ptr,
+ 	wed->wlan.reset = mt7915_mmio_wed_reset;
+ 	wed->wlan.reset_complete = mt76_wed_reset_complete;
+ 
+-	dev->mt76.rx_token_size = wed->wlan.rx_npkt;
++	dev->mt76.rx_token_size += wed->wlan.rx_npkt;
+ 
+ 	if (mtk_wed_device_attach(wed))
+ 		return 0;
+@@ -921,6 +921,7 @@ struct mt7915_dev *mt7915_mmio_probe(struct device *pdev,
+ 				SURVEY_INFO_TIME_RX |
+ 				SURVEY_INFO_TIME_BSS_RX,
+ 		.token_size = MT7915_TOKEN_SIZE,
++		.rx_token_size = MT7915_RX_TOKEN_SIZE;
+ 		.tx_prepare_skb = mt7915_tx_prepare_skb,
+ 		.tx_complete_skb = mt76_connac_tx_complete_skb,
+ 		.rx_skb = mt7915_queue_rx_skb,
+diff --git a/mt7915/mt7915.h b/mt7915/mt7915.h
+index a30d08eb0..f1e2c93a4 100644
+--- a/mt7915/mt7915.h
++++ b/mt7915/mt7915.h
+@@ -62,6 +62,7 @@
+ #define MT7915_EEPROM_BLOCK_SIZE	16
+ #define MT7915_HW_TOKEN_SIZE		4096
+ #define MT7915_TOKEN_SIZE		8192
++#define MT7915_RX_TOKEN_SIZE		4096
+ 
+ #define MT7915_CFEND_RATE_DEFAULT	0x49	/* OFDM 24M */
+ #define MT7915_CFEND_RATE_11B		0x03	/* 11B LP, 11M */
+diff --git a/tx.c b/tx.c
+index ab42f69b8..46dae6e0a 100644
+--- a/tx.c
++++ b/tx.c
+@@ -851,16 +851,16 @@ int mt76_token_consume(struct mt76_dev *dev, struct mt76_txwi_cache **ptxwi)
+ EXPORT_SYMBOL_GPL(mt76_token_consume);
+ 
+ int mt76_rx_token_consume(struct mt76_dev *dev, void *ptr,
+-			  struct mt76_txwi_cache *t, dma_addr_t phys)
++			  struct mt76_rxwi_cache *r, dma_addr_t phys)
+ {
+ 	int token;
+ 
+ 	spin_lock_bh(&dev->rx_token_lock);
+-	token = idr_alloc(&dev->rx_token, t, 0, dev->rx_token_size,
++	token = idr_alloc(&dev->rx_token, r, 0, dev->rx_token_size,
+ 			  GFP_ATOMIC);
+ 	if (token >= 0) {
+-		t->ptr = ptr;
+-		t->dma_addr = phys;
++		r->ptr = ptr;
++		r->dma_addr = phys;
+ 	}
+ 	spin_unlock_bh(&dev->rx_token_lock);
+ 
+@@ -897,15 +897,15 @@ mt76_token_release(struct mt76_dev *dev, int token, bool *wake)
+ }
+ EXPORT_SYMBOL_GPL(mt76_token_release);
+ 
+-struct mt76_txwi_cache *
++struct mt76_rxwi_cache *
+ mt76_rx_token_release(struct mt76_dev *dev, int token)
+ {
+-	struct mt76_txwi_cache *t;
++	struct mt76_rxwi_cache *r;
+ 
+ 	spin_lock_bh(&dev->rx_token_lock);
+-	t = idr_remove(&dev->rx_token, token);
++	r = idr_remove(&dev->rx_token, token);
+ 	spin_unlock_bh(&dev->rx_token_lock);
+ 
+-	return t;
++	return r;
+ }
+ EXPORT_SYMBOL_GPL(mt76_rx_token_release);
+diff --git a/wed.c b/wed.c
+index 8eca4d818..0a0b5c05c 100644
+--- a/wed.c
++++ b/wed.c
+@@ -9,28 +9,45 @@
+ void mt76_wed_release_rx_buf(struct mtk_wed_device *wed)
+ {
+ 	struct mt76_dev *dev = container_of(wed, struct mt76_dev, mmio.wed);
+-	u32 length;
++	struct page *page;
+ 	int i;
+ 
+-	length = SKB_DATA_ALIGN(NET_SKB_PAD + wed->wlan.rx_size +
+-				sizeof(struct skb_shared_info));
+-
+ 	for (i = 0; i < dev->rx_token_size; i++) {
+-		struct mt76_txwi_cache *t;
++		struct mt76_rxwi_cache *r;
+ 
+-		t = mt76_rx_token_release(dev, i);
+-		if (!t || !t->ptr)
++		r = mt76_rx_token_release(dev, i);
++		if (!r || !r->ptr)
+ 			continue;
+ 
+-		dma_unmap_single(dev->dma_dev, t->dma_addr,
++		dma_unmap_single(dev->dma_dev, r->dma_addr,
+ 				 wed->wlan.rx_size, DMA_FROM_DEVICE);
+-		__free_pages(virt_to_page(t->ptr), get_order(length));
+-		t->ptr = NULL;
++		skb_free_frag(r->ptr);
++		r->ptr = NULL;
+ 
+-		mt76_put_rxwi(dev, t);
++		mt76_put_rxwi(dev, r);
+ 	}
+ 
+ 	mt76_free_pending_rxwi(dev);
++
++	mt76_for_each_q_rx(dev, i) {
++		struct mt76_queue *q = &dev->q_rx[i];
++
++		if (mt76_queue_is_wed_rx(q)) {
++			if (!q->rx_page.va)
++				continue;
++
++			page = virt_to_page(q->rx_page.va);
++			__page_frag_cache_drain(page, q->rx_page.pagecnt_bias);
++			memset(&q->rx_page, 0, sizeof(q->rx_page));
++		}
++	}
++
++	if (!wed->rx_buf_ring.rx_page.va)
++		return;
++
++	page = virt_to_page(wed->rx_buf_ring.rx_page.va);
++	__page_frag_cache_drain(page, wed->rx_buf_ring.rx_page.pagecnt_bias);
++	memset(&wed->rx_buf_ring.rx_page, 0, sizeof(wed->rx_buf_ring.rx_page));
+ }
+ EXPORT_SYMBOL_GPL(mt76_wed_release_rx_buf);
+ 
+@@ -46,18 +63,18 @@ u32 mt76_wed_init_rx_buf(struct mtk_wed_device *wed, int size)
+ 				sizeof(struct skb_shared_info));
+ 
+ 	for (i = 0; i < size; i++) {
+-		struct mt76_txwi_cache *t = mt76_get_rxwi(dev);
++		struct mt76_rxwi_cache *r = mt76_get_rxwi(dev);
+ 		dma_addr_t addr;
+ 		struct page *page;
+ 		int token;
+ 		void *ptr;
+ 
+-		if (!t)
++		if (!r)
+ 			goto unmap;
+ 
+-		page = __dev_alloc_pages(GFP_KERNEL, get_order(length));
+-		if (!page) {
+-			mt76_put_rxwi(dev, t);
++		ptr = page_frag_alloc(&wed->rx_buf_ring.rx_page, length, GFP_ATOMIC);
++		if (!ptr) {
++			mt76_put_rxwi(dev, r);
+ 			goto unmap;
+ 		}
+ 
+@@ -67,17 +84,17 @@ u32 mt76_wed_init_rx_buf(struct mtk_wed_device *wed, int size)
+ 
+ 		if (unlikely(dma_mapping_error(dev->dev, addr))) {
+ 			skb_free_frag(ptr);
+-			mt76_put_rxwi(dev, t);
++			mt76_put_rxwi(dev, r);
+ 			goto unmap;
+ 		}
+ 
+ 		desc->buf0 = cpu_to_le32(addr);
+-		token = mt76_rx_token_consume(dev, ptr, t, addr);
++		token = mt76_rx_token_consume(dev, ptr, r, addr);
+ 		if (token < 0) {
+ 			dma_unmap_single(dev->dma_dev, addr,
+ 					 wed->wlan.rx_size, DMA_TO_DEVICE);
+-			__free_pages(page, get_order(length));
+-			mt76_put_rxwi(dev, t);
++			skb_free_frag(ptr);
++			mt76_put_rxwi(dev, r);
+ 			goto unmap;
+ 		}
+ 
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0074-mtk-wifi-mt76-wed-change-wed-token-init-size-to-adap.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0074-mtk-wifi-mt76-wed-change-wed-token-init-size-to-adap.patch
new file mode 100644
index 0000000..3393871
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0074-mtk-wifi-mt76-wed-change-wed-token-init-size-to-adap.patch
@@ -0,0 +1,38 @@
+From 39b7d46dcefc833d3cc565b353c3596462e3e5dc Mon Sep 17 00:00:00 2001
+From: "sujuan.chen" <sujuan.chen@mediatek.com>
+Date: Wed, 19 Apr 2023 17:13:41 +0800
+Subject: [PATCH 074/120] mtk: wifi: mt76: wed: change wed token init size to
+ adapt wed3.0
+
+Signed-off-by: sujuan.chen <sujuan.chen@mediatek.com>
+---
+ tx.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/tx.c b/tx.c
+index 46dae6e0a..e2795067c 100644
+--- a/tx.c
++++ b/tx.c
+@@ -827,12 +827,16 @@ EXPORT_SYMBOL_GPL(__mt76_set_tx_blocked);
+ 
+ int mt76_token_consume(struct mt76_dev *dev, struct mt76_txwi_cache **ptxwi)
+ {
+-	int token;
++	int token, start = 0;
++
++	if (mtk_wed_device_active(&dev->mmio.wed))
++		start = dev->mmio.wed.wlan.nbuf;
+ 
+ 	spin_lock_bh(&dev->token_lock);
+ 
+-	token = idr_alloc(&dev->token, *ptxwi, 0, dev->token_size, GFP_ATOMIC);
+-	if (token >= 0)
++	token = idr_alloc(&dev->token, *ptxwi, start, start + dev->token_size,
++			  GFP_ATOMIC);
++	if (token >= start)
+ 		dev->token_count++;
+ 
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0075-mtk-wifi-mt76-add-random-early-drop-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0075-mtk-wifi-mt76-add-random-early-drop-support.patch
new file mode 100644
index 0000000..2472894
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0075-mtk-wifi-mt76-add-random-early-drop-support.patch
@@ -0,0 +1,316 @@
+From d0eac83f3a7ca772fbd800fe6beff339474467d9 Mon Sep 17 00:00:00 2001
+From: Peter Chiu <chui-hao.chiu@mediatek.com>
+Date: Wed, 19 Apr 2023 18:32:41 +0800
+Subject: [PATCH 075/120] mtk: wifi: mt76: add random early drop support
+
+---
+ mt7996/debugfs.c     |  1 +
+ mt7996/mac.c         |  7 ++++
+ mt7996/mcu.c         | 81 ++++++++++++++++++++++++++++++++++++++++++--
+ mt7996/mcu.h         |  4 ++-
+ mt7996/mt7996.h      |  5 ++-
+ mt7996/mtk_debugfs.c | 23 +++++++++++++
+ mt7996/mtk_mcu.c     | 26 ++++++++++++++
+ mt7996/mtk_mcu.h     | 24 +++++++++++++
+ 8 files changed, 167 insertions(+), 4 deletions(-)
+
+diff --git a/mt7996/debugfs.c b/mt7996/debugfs.c
+index 5b3e39672..d38e85617 100644
+--- a/mt7996/debugfs.c
++++ b/mt7996/debugfs.c
+@@ -634,6 +634,7 @@ mt7996_tx_stats_show(struct seq_file *file, void *data)
+ 	seq_printf(file, "Tx attempts: %8u (MPDUs)\n", attempts);
+ 	seq_printf(file, "Tx success: %8u (MPDUs)\n", success);
+ 	seq_printf(file, "Tx PER: %u%%\n", per);
++	seq_printf(file, "Tx RED drop: %8u\n", phy->red_drop);
+ 
+ 	mt7996_txbf_stat_read_phy(phy, file);
+ 
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 9c1c7c45d..eae7e1b2d 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -1176,6 +1176,13 @@ mt7996_mac_tx_free(struct mt7996_dev *dev, void *data, int len)
+ 
+ 			wcid->stats.tx_retries += tx_retries;
+ 			wcid->stats.tx_failed += tx_failed;
++
++			if (FIELD_GET(MT_TXFREE_INFO_STAT, info) == 2) {
++				struct mt7996_phy *mphy =
++					__mt7996_phy(dev, wcid->phy_idx);
++
++				mphy->red_drop++;
++			}
+ 			continue;
+ 		}
+ 
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 874598692..3cc38b14e 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -3147,8 +3147,8 @@ int mt7996_mcu_init_firmware(struct mt7996_dev *dev)
+ 	if (ret)
+ 		return ret;
+ 
+-	return mt7996_mcu_wa_cmd(dev, MCU_WA_PARAM_CMD(SET),
+-				 MCU_WA_PARAM_RED, 0, 0);
++	return mt7996_mcu_red_config(dev,
++			mtk_wed_device_active(&dev->mt76.mmio.wed));
+ }
+ 
+ int mt7996_mcu_init(struct mt7996_dev *dev)
+@@ -3180,6 +3180,83 @@ out:
+ 	skb_queue_purge(&dev->mt76.mcu.res_q);
+ }
+ 
++static int mt7996_mcu_wa_red_config(struct mt7996_dev *dev)
++{
++#define RED_TOKEN_SRC_CNT	4
++#define RED_TOKEN_CONFIG	2
++	struct {
++		__le32 arg0;
++		__le32 arg1;
++		__le32 arg2;
++
++		u8 mode;
++		u8 version;
++		u8 _rsv[4];
++		__le16 len;
++
++		__le16 tcp_offset;
++		__le16 priority_offset;
++		__le16 token_per_src[RED_TOKEN_SRC_CNT];
++		__le16 token_thr_per_src[RED_TOKEN_SRC_CNT];
++
++		u8 _rsv2[604];
++	} __packed req = {
++		.arg0 = cpu_to_le32(MCU_WA_PARAM_RED_CONFIG),
++
++		.mode = RED_TOKEN_CONFIG,
++		.len = cpu_to_le16(sizeof(req) - sizeof(__le32) * 3),
++
++		.tcp_offset = cpu_to_le16(200),
++		.priority_offset = cpu_to_le16(255),
++	};
++	u8 i;
++
++	for (i = 0; i < RED_TOKEN_SRC_CNT; i++) {
++		req.token_per_src[i] = cpu_to_le16(MT7996_TOKEN_SIZE);
++		req.token_thr_per_src[i] = cpu_to_le16(MT7996_TOKEN_SIZE);
++	}
++
++	if (!mtk_wed_device_active(&dev->mt76.mmio.wed))
++		req.token_per_src[RED_TOKEN_SRC_CNT - 1] =
++			cpu_to_le16(MT7996_TOKEN_SIZE - MT7996_HW_TOKEN_SIZE);
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WA_PARAM_CMD(SET),
++				 &req, sizeof(req), false);
++}
++
++int mt7996_mcu_red_config(struct mt7996_dev *dev, bool enable)
++{
++#define RED_DISABLE		0
++#define RED_BY_WA_ENABLE	2
++	struct {
++		u8 __rsv1[4];
++
++		__le16 tag;
++		__le16 len;
++		u8 enable;
++		u8 __rsv2[3];
++	} __packed req = {
++		.tag = cpu_to_le16(UNI_VOW_RED_ENABLE),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.enable = enable ? RED_BY_WA_ENABLE : RED_DISABLE,
++	};
++	int ret;
++
++	ret = mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(VOW), &req,
++				 sizeof(req), true);
++
++	if (ret)
++		return ret;
++
++	ret = mt7996_mcu_wa_cmd(dev, MCU_WA_PARAM_CMD(SET),
++				MCU_WA_PARAM_RED_EN, enable, 0);
++
++	if (ret || !enable)
++		return ret;
++
++	return mt7996_mcu_wa_red_config(dev);
++}
++
+ int mt7996_mcu_set_hdr_trans(struct mt7996_dev *dev, bool hdr_trans)
+ {
+ 	struct {
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 67777a639..bdfaacccb 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -346,8 +346,9 @@ enum {
+ enum {
+ 	MCU_WA_PARAM_PDMA_RX = 0x04,
+ 	MCU_WA_PARAM_CPU_UTIL = 0x0b,
+-	MCU_WA_PARAM_RED = 0x0e,
++	MCU_WA_PARAM_RED_EN = 0x0e,
+ 	MCU_WA_PARAM_HW_PATH_HIF_VER = 0x2f,
++	MCU_WA_PARAM_RED_CONFIG = 0x40,
+ };
+ 
+ enum mcu_mmps_mode {
+@@ -920,6 +921,7 @@ enum {
+ 	UNI_VOW_DRR_CTRL,
+ 	UNI_VOW_RX_AT_AIRTIME_EN = 0x0b,
+ 	UNI_VOW_RX_AT_AIRTIME_CLR_EN = 0x0e,
++	UNI_VOW_RED_ENABLE = 0x18,
+ };
+ 
+ enum {
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index a91bcf4ec..2ff5ce855 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -352,6 +352,7 @@ struct mt7996_phy {
+ 	bool has_aux_rx;
+ 
+ 	struct mt7996_scs_ctrl scs_ctrl;
++	u32 red_drop;
+ 
+ 	u8 muru_onoff;
+ 
+@@ -718,6 +719,7 @@ int mt7996_mcu_rf_regval(struct mt7996_dev *dev, u32 regidx, u32 *val, bool set)
+ int mt7996_mcu_set_hdr_trans(struct mt7996_dev *dev, bool hdr_trans);
+ int mt7996_mcu_set_rro(struct mt7996_dev *dev, u16 tag, u16 val);
+ int mt7996_mcu_wa_cmd(struct mt7996_dev *dev, int cmd, u32 a1, u32 a2, u32 a3);
++int mt7996_mcu_red_config(struct mt7996_dev *dev, bool enable);
+ int mt7996_mcu_fw_log_2_host(struct mt7996_dev *dev, u8 type, u8 ctrl);
+ int mt7996_mcu_fw_dbg_ctrl(struct mt7996_dev *dev, u32 module, u8 level);
+ int mt7996_mcu_trigger_assert(struct mt7996_dev *dev);
+@@ -892,11 +894,12 @@ 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);
++
++int mt7996_mcu_set_vow_drr_dbg(struct mt7996_dev *dev, u32 val);
+ #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 0271ae52e..e49c33eac 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -3015,6 +3015,27 @@ static int mt7996_muru_prot_thr_set(void *data, u64 val)
+ DEFINE_DEBUGFS_ATTRIBUTE(fops_muru_prot_thr, NULL,
+ 			 mt7996_muru_prot_thr_set, "%lld\n");
+ 
++static int
++mt7996_red_config_set(void *data, u64 val)
++{
++	struct mt7996_dev *dev = data;
++
++	return mt7996_mcu_red_config(dev, !!val);
++}
++
++DEFINE_DEBUGFS_ATTRIBUTE(fops_red_config, NULL,
++			 mt7996_red_config_set, "%lld\n");
++
++static int
++mt7996_vow_drr_dbg(void *data, u64 val)
++{
++	struct mt7996_dev *dev = data;
++
++	return mt7996_mcu_set_vow_drr_dbg(dev, (u32)val);
++}
++DEFINE_DEBUGFS_ATTRIBUTE(fops_vow_drr_dbg, NULL,
++			 mt7996_vow_drr_dbg, "%lld\n");
++
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -3092,6 +3113,8 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ 				    mt7996_wtbl_read);
+ 
+ 	debugfs_create_devm_seqfile(dev->mt76.dev, "token", dir, mt7996_token_read);
++	debugfs_create_file("red", 0200, dir, dev, &fops_red_config);
++	debugfs_create_file("vow_drr_dbg", 0200, dir, dev, &fops_vow_drr_dbg);
+ 
+ 	debugfs_create_u8("sku_disable", 0600, dir, &dev->dbg.sku_disable);
+ 	debugfs_create_file("scs_enable", 0200, dir, phy, &fops_scs_enable);
+diff --git a/mt7996/mtk_mcu.c b/mt7996/mtk_mcu.c
+index a9a7db15a..aed32e982 100644
+--- a/mt7996/mtk_mcu.c
++++ b/mt7996/mtk_mcu.c
+@@ -1316,4 +1316,30 @@ void mt7996_mcu_set_cert(struct mt7996_phy *phy, u8 type)
+ 			  sizeof(req), false);
+ }
+ 
++int mt7996_mcu_set_vow_drr_dbg(struct mt7996_dev *dev, u32 val)
++{
++#define MT7996_VOW_DEBUG_MODE	0xe
++	struct {
++		u8 __rsv1[4];
++
++		__le16 tag;
++		__le16 len;
++		u8 __rsv2[4];
++		__le32 action;
++		__le32 val;
++		u8 __rsv3[8];
++	} __packed req = {
++		.tag = cpu_to_le16(UNI_VOW_DRR_CTRL),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.action = cpu_to_le32(MT7996_VOW_DEBUG_MODE),
++		.val = cpu_to_le32(val),
++	};
++
++	if (val & ~VOW_DRR_DBG_FLAGS)
++		return -EINVAL;
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(VOW), &req,
++				 sizeof(req), true);
++}
++
+ #endif
+diff --git a/mt7996/mtk_mcu.h b/mt7996/mtk_mcu.h
+index 58d61c517..2cffc8937 100644
+--- a/mt7996/mtk_mcu.h
++++ b/mt7996/mtk_mcu.h
+@@ -1138,6 +1138,30 @@ enum muru_vendor_ctrl {
+ 	MU_CTRL_DL_USER_CNT,
+ 	MU_CTRL_UL_USER_CNT,
+ };
++
++enum {
++	VOW_DRR_DBG_DUMP_BMP = BIT(0),
++	VOW_DRR_DBG_EST_AT_PRINT = BIT(1),
++	VOW_DRR_DBG_ADJ_GLOBAL_THLD = BIT(21),
++	VOW_DRR_DBG_PRN_LOUD = BIT(22),
++	VOW_DRR_DBG_PRN_ADJ_STA = BIT(23),
++	VOW_DRR_DBG_FIX_CR = GENMASK(27, 24),
++	VOW_DRR_DBG_CLR_FIX_CR = BIT(28),
++	VOW_DRR_DBG_DISABLE = BIT(29),
++	VOW_DRR_DBG_DUMP_CR = BIT(30),
++	VOW_DRR_DBG_PRN = BIT(31)
++};
++
++#define VOW_DRR_DBG_FLAGS (VOW_DRR_DBG_DUMP_BMP |	\
++			  VOW_DRR_DBG_EST_AT_PRINT |	\
++			  VOW_DRR_DBG_ADJ_GLOBAL_THLD |	\
++			  VOW_DRR_DBG_PRN_LOUD |	\
++			  VOW_DRR_DBG_PRN_ADJ_STA |	\
++			  VOW_DRR_DBG_FIX_CR |		\
++			  VOW_DRR_DBG_CLR_FIX_CR |	\
++			  VOW_DRR_DBG_DISABLE |		\
++			  VOW_DRR_DBG_DUMP_CR |		\
++			  VOW_DRR_DBG_PRN)
+ #endif
+ 
+ #endif
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0076-mtk-wifi-mt76-mt7996-reset-addr_elem-when-delete-ba.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0076-mtk-wifi-mt76-mt7996-reset-addr_elem-when-delete-ba.patch
new file mode 100644
index 0000000..9ee1d1a
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0076-mtk-wifi-mt76-mt7996-reset-addr_elem-when-delete-ba.patch
@@ -0,0 +1,96 @@
+From f36094608542f6623ca6271e84f5c505c4721cea Mon Sep 17 00:00:00 2001
+From: "sujuan.chen" <sujuan.chen@mediatek.com>
+Date: Thu, 18 May 2023 15:01:47 +0800
+Subject: [PATCH 076/120] mtk: wifi: mt76: mt7996: reset addr_elem when delete
+ ba
+
+The old addr element info may be used when the signature is not equel to
+0xff, and sta will find error SDP cause the SDP/SDL=0 issue.
+
+Signed-off-by: sujuan.chen <sujuan.chen@mediatek.com>
+Change-Id: I12fb27e28b2c0310f824e66af6103b4ceba3503e
+
+1. without this patch will delete wrong session id when delete ba.
+Due to fw change the cmd format.
+https://gerrit.mediatek.inc/c/neptune/firmware/bora/wifi/custom/+/7969193
+
+Signed-off-by: mtk27745 <rex.lu@mediatek.com>
+CR-Id: WCNCR00259516
+Change-Id: I456a5d3eb2320a2c40cf57247ba63083a6d50b2e
+---
+ mt76.h       |  1 +
+ mt7996/mcu.h | 46 ++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 47 insertions(+)
+
+diff --git a/mt76.h b/mt76.h
+index 878553a20..17418e86f 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -439,6 +439,7 @@ struct mt76_rx_tid {
+ 	u16 nframes;
+ 
+ 	u8 num;
++	u16 session_id;
+ 
+ 	u8 started:1, stopped:1, timer_pending:1;
+ 
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index bdfaacccb..1221cf1d9 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -298,6 +298,52 @@ struct mt7996_mcu_thermal_notify {
+ 	u8 __rsv2[4];
+ } __packed;
+ 
++struct mt7996_mcu_rro_event {
++	struct mt7996_mcu_rxd rxd;
++
++	u8 __rsv1[4];
++
++	__le16 tag;
++	__le16 len;
++} __packed;
++
++struct mt7996_mcu_rro_ba {
++	__le16 tag;
++	__le16 len;
++
++	__le16 wlan_id;
++	u8 tid;
++	u8 __rsv1;
++	__le32 status;
++	__le16 session_id;
++	u8 __rsv2[2];
++} __packed;
++
++struct mt7996_mcu_rro_ba_del_chk_done {
++	__le16 tag;
++	__le16 len;
++
++	__le16 session_id;
++	__le16 mld_id;
++	u8 tid;
++	u8 __rsv[3];
++} __packed;
++
++enum  {
++	UNI_RRO_BA_SESSION_STATUS = 0,
++	UNI_RRO_BA_SESSION_TBL	= 1,
++	UNI_RRO_BA_SESSION_DEL_CHK_DONE = 2,
++	UNI_RRO_BA_SESSION_MAX_NUM
++};
++
++struct mt7996_mcu_rro_del_ba {
++	struct mt7996_mcu_rro_event event;
++
++	u8  wlan_idx;
++	u8  tid;
++	u8 __rsv2[2];
++};
++
+ enum mt7996_chan_mib_offs {
+ 	UNI_MIB_OBSS_AIRTIME = 26,
+ 	UNI_MIB_NON_WIFI_TIME = 27,
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0077-mtk-wifi-mt76-wed-change-pcie0-R5-to-pcie1-to-get-6G.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0077-mtk-wifi-mt76-wed-change-pcie0-R5-to-pcie1-to-get-6G.patch
new file mode 100644
index 0000000..f6295af
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0077-mtk-wifi-mt76-wed-change-pcie0-R5-to-pcie1-to-get-6G.patch
@@ -0,0 +1,69 @@
+From 56adfcb542aba9261dc12bda46a9771d180a2d15 Mon Sep 17 00:00:00 2001
+From: "sujuan.chen" <sujuan.chen@mediatek.com>
+Date: Fri, 6 Oct 2023 14:01:41 +0800
+Subject: [PATCH 077/120] mtk: wifi: mt76: wed: change pcie0 R5 to pcie1 to get
+ 6G ICS
+
+Change-Id: I23a94e3e4b797b513a303b13e4c50e0a0d72bffb
+---
+ mt7996/dma.c  | 4 ++++
+ mt7996/init.c | 6 ++----
+ mt7996/mmio.c | 5 ++++-
+ 3 files changed, 10 insertions(+), 5 deletions(-)
+
+diff --git a/mt7996/dma.c b/mt7996/dma.c
+index 759a58e8e..5d85e9ea2 100644
+--- a/mt7996/dma.c
++++ b/mt7996/dma.c
+@@ -538,6 +538,10 @@ int mt7996_dma_init(struct mt7996_dev *dev)
+ 	if (mt7996_band_valid(dev, MT_BAND2)) {
+ 		/* rx data queue for mt7996 band2 */
+ 		rx_base = MT_RXQ_RING_BASE(MT_RXQ_BAND2) + hif1_ofs;
++		if (mtk_wed_device_active(wed_hif2) && mtk_wed_get_rx_capa(wed_hif2)) {
++			dev->mt76.q_rx[MT_RXQ_BAND2].flags = MT_WED_Q_RX(0);
++			dev->mt76.q_rx[MT_RXQ_BAND2].wed = wed_hif2;
++		}
+ 		ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_BAND2],
+ 				       MT_RXQ_ID(MT_RXQ_BAND2),
+ 				       MT7996_RX_RING_SIZE,
+diff --git a/mt7996/init.c b/mt7996/init.c
+index 20415e3c0..aedf4edc7 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -638,10 +638,8 @@ static int mt7996_register_phy(struct mt7996_dev *dev, struct mt7996_phy *phy,
+ 		goto error;
+ 
+ 	if (wed == &dev->mt76.mmio.wed_hif2 && mtk_wed_device_active(wed)) {
+-		u32 irq_mask = dev->mt76.mmio.irqmask | MT_INT_TX_DONE_BAND2;
+-
+-		mt76_wr(dev, MT_INT1_MASK_CSR, irq_mask);
+-		mtk_wed_device_start(&dev->mt76.mmio.wed_hif2, irq_mask);
++		mt76_wr(dev, MT_INT_PCIE1_MASK_CSR, MT_INT_TX_RX_DONE_EXT);
++		mtk_wed_device_start(&dev->mt76.mmio.wed_hif2, MT_INT_TX_RX_DONE_EXT);
+ 	}
+ 
+ 	return 0;
+diff --git a/mt7996/mmio.c b/mt7996/mmio.c
+index 367a204d2..44e64f861 100644
+--- a/mt7996/mmio.c
++++ b/mt7996/mmio.c
+@@ -527,12 +527,15 @@ static void mt7996_irq_tasklet(struct tasklet_struct *t)
+ 					       dev->mt76.mmio.irqmask);
+ 		if (intr1 & MT_INT_RX_TXFREE_EXT)
+ 			napi_schedule(&dev->mt76.napi[MT_RXQ_TXFREE_BAND2]);
++
++		if (intr1 & MT_INT_RX_DONE_BAND2_EXT)
++			napi_schedule(&dev->mt76.napi[MT_RXQ_BAND2]);
+ 	}
+ 
+ 	if (mtk_wed_device_active(wed)) {
+ 		mtk_wed_device_irq_set_mask(wed, 0);
+ 		intr = mtk_wed_device_irq_get(wed, dev->mt76.mmio.irqmask);
+-		intr |= (intr1 & ~MT_INT_RX_TXFREE_EXT);
++		intr |= (intr1 & ~MT_INT_TX_RX_DONE_EXT);
+ 	} else {
+ 		mt76_wr(dev, MT_INT_MASK_CSR, 0);
+ 		if (dev->hif2)
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0078-mtk-wifi-mt76-add-SER-support-for-wed3.0.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0078-mtk-wifi-mt76-add-SER-support-for-wed3.0.patch
new file mode 100644
index 0000000..ce62f61
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0078-mtk-wifi-mt76-add-SER-support-for-wed3.0.patch
@@ -0,0 +1,42 @@
+From ffec1fcb416aa040484d1bff1525674e1187c40f Mon Sep 17 00:00:00 2001
+From: mtk27745 <rex.lu@mediatek.com>
+Date: Tue, 23 May 2023 12:06:29 +0800
+Subject: [PATCH 078/120] mtk: wifi: mt76: add SER support for wed3.0
+
+Change-Id: I2711b9dc336fca9a1ae32a8fbf27810a7e27b1e3
+---
+ dma.c         | 5 +++--
+ mt7996/mmio.c | 1 +
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/dma.c b/dma.c
+index c54187bd6..e5be891cb 100644
+--- a/dma.c
++++ b/dma.c
+@@ -834,8 +834,9 @@ mt76_dma_rx_reset(struct mt76_dev *dev, enum mt76_rxq_id qid)
+ 
+ 	/* reset WED rx queues */
+ 	mt76_wed_dma_setup(dev, q, true);
+-
+-	if (!mt76_queue_is_wed_tx_free(q)) {
++	if (!mt76_queue_is_wed_tx_free(q) &&
++	    !(mt76_queue_is_wed_rro(q) &&
++	    mtk_wed_device_active(&dev->mmio.wed))) {
+ 		mt76_dma_sync_idx(dev, q);
+ 		mt76_dma_rx_fill(dev, q, false);
+ 	}
+diff --git a/mt7996/mmio.c b/mt7996/mmio.c
+index 44e64f861..92ae5138e 100644
+--- a/mt7996/mmio.c
++++ b/mt7996/mmio.c
+@@ -297,6 +297,7 @@ out:
+ 
+ 	return ret;
+ }
++
+ #endif
+ 
+ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr,
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0079-mtk-wifi-mt76-mt7915-wed-find-rx-token-by-physical-a.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0079-mtk-wifi-mt76-mt7915-wed-find-rx-token-by-physical-a.patch
new file mode 100644
index 0000000..037b15a
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0079-mtk-wifi-mt76-mt7915-wed-find-rx-token-by-physical-a.patch
@@ -0,0 +1,66 @@
+From 108202e5559c618db7baa627467b3e474697f59b Mon Sep 17 00:00:00 2001
+From: "sujuan.chen" <sujuan.chen@mediatek.com>
+Date: Wed, 19 Jul 2023 10:55:09 +0800
+Subject: [PATCH 079/120] mtk: wifi: mt76: mt7915: wed: find rx token by
+ physical address
+
+The token id in RxDMAD may be incorrect when it is not the last frame due to
+WED HW bug. Lookup correct token id by physical address in sdp0.
+Add len == 0 check to drop garbage frames
+
+Signed-off-by: Peter Chiu <chui-hao.chiu@mediatek.com>
+Change-Id: I4cd90294ad24990826075e92a710cc4e301dcbb7
+---
+ dma.c | 27 +++++++++++++++++++++++++--
+ 1 file changed, 25 insertions(+), 2 deletions(-)
+
+diff --git a/dma.c b/dma.c
+index e5be891cb..1021b3e5d 100644
+--- a/dma.c
++++ b/dma.c
+@@ -446,9 +446,32 @@ mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx,
+ 	mt76_dma_should_drop_buf(drop, ctrl, buf1, desc_info);
+ 
+ 	if (mt76_queue_is_wed_rx(q)) {
++		u32 id, find = 0;
+ 		u32 token = FIELD_GET(MT_DMA_CTL_TOKEN, buf1);
+-		struct mt76_rxwi_cache *r = mt76_rx_token_release(dev, token);
++		struct mt76_rxwi_cache *r;
++
++		if (*more) {
++			spin_lock_bh(&dev->rx_token_lock);
++
++			idr_for_each_entry(&dev->rx_token, r, id) {
++				if (r->dma_addr == le32_to_cpu(desc->buf0)) {
++					find = 1;
++					token = id;
++
++					/* Write correct id back to DMA*/
++					u32p_replace_bits(&buf1, id,
++							  MT_DMA_CTL_TOKEN);
++					WRITE_ONCE(desc->buf1, cpu_to_le32(buf1));
++					break;
++				}
++			}
+ 
++			spin_unlock_bh(&dev->rx_token_lock);
++			if (!find)
++				return NULL;
++		}
++
++		r = mt76_rx_token_release(dev, token);
+ 		if (!r)
+ 			return NULL;
+ 
+@@ -902,7 +925,7 @@ mt76_dma_rx_process(struct mt76_dev *dev, struct mt76_queue *q, int budget)
+ 		if (!data)
+ 			break;
+ 
+-		if (drop)
++		if (drop || (len == 0))
+ 			goto free_frag;
+ 
+ 		if (q->rx_head)
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0080-mtk-wifi-mt76-mt7996-add-dma-mask-limitation.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0080-mtk-wifi-mt76-mt7996-add-dma-mask-limitation.patch
new file mode 100644
index 0000000..b1d80c0
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0080-mtk-wifi-mt76-mt7996-add-dma-mask-limitation.patch
@@ -0,0 +1,57 @@
+From 5b0d72a06a693bffb8a4b8c6bdc7f7b8a611b20a Mon Sep 17 00:00:00 2001
+From: "sujuan.chen" <sujuan.chen@mediatek.com>
+Date: Thu, 20 Jul 2023 10:25:50 +0800
+Subject: [PATCH 080/120] mtk: wifi: mt76: mt7996: add dma mask limitation
+
+Signed-off-by: sujuan.chen <sujuan.chen@mediatek.com>
+---
+ dma.c | 4 ++--
+ wed.c | 4 ++--
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/dma.c b/dma.c
+index 1021b3e5d..da21f6410 100644
+--- a/dma.c
++++ b/dma.c
+@@ -488,7 +488,7 @@ mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx,
+ 		} else {
+ 			struct mt76_queue_buf qbuf;
+ 
+-			buf = page_frag_alloc(&q->rx_page, q->buf_size, GFP_ATOMIC);
++			buf = page_frag_alloc(&q->rx_page, q->buf_size, GFP_ATOMIC | GFP_DMA32);
+ 			if (!buf)
+ 				return NULL;
+ 
+@@ -711,7 +711,7 @@ int mt76_dma_rx_fill(struct mt76_dev *dev, struct mt76_queue *q,
+ 		if (mt76_queue_is_wed_rro_ind(q))
+ 			goto done;
+ 
+-		buf = page_frag_alloc(&q->rx_page, q->buf_size, GFP_ATOMIC);
++		buf = page_frag_alloc(&q->rx_page, q->buf_size, GFP_ATOMIC | GFP_DMA32);
+ 		if (!buf)
+ 			break;
+ 
+diff --git a/wed.c b/wed.c
+index 0a0b5c05c..1c6d53c84 100644
+--- a/wed.c
++++ b/wed.c
+@@ -65,14 +65,14 @@ u32 mt76_wed_init_rx_buf(struct mtk_wed_device *wed, int size)
+ 	for (i = 0; i < size; i++) {
+ 		struct mt76_rxwi_cache *r = mt76_get_rxwi(dev);
+ 		dma_addr_t addr;
+-		struct page *page;
+ 		int token;
+ 		void *ptr;
+ 
+ 		if (!r)
+ 			goto unmap;
+ 
+-		ptr = page_frag_alloc(&wed->rx_buf_ring.rx_page, length, GFP_ATOMIC);
++		ptr = page_frag_alloc(&wed->rx_buf_ring.rx_page, length,
++				      GFP_ATOMIC | GFP_DMA32);
+ 		if (!ptr) {
+ 			mt76_put_rxwi(dev, r);
+ 			goto unmap;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0081-mtk-wifi-mt76-mt7996-add-per-bss-statistic-info.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0081-mtk-wifi-mt76-mt7996-add-per-bss-statistic-info.patch
new file mode 100644
index 0000000..2cf9488
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0081-mtk-wifi-mt76-mt7996-add-per-bss-statistic-info.patch
@@ -0,0 +1,122 @@
+From b47548b378e805b5528aa938f9a9ffaccb1da820 Mon Sep 17 00:00:00 2001
+From: Yi-Chia Hsieh <yi-chia.hsieh@mediatek.com>
+Date: Fri, 18 Aug 2023 10:17:08 +0800
+Subject: [PATCH 081/120] mtk: wifi: mt76: mt7996: add per bss statistic info
+
+Whenever WED is enabled, unicast traffic might run through HW path.
+As a result, we need to count them using WM event.
+Broadcast and multicast traffic on the other hand, will be counted in mac80211
+as they always go through SW path and thus mac80211 can always see and count them.
+
+|         | Tx                             | Rx                        |
+|---------|--------------------------------|---------------------------|
+| Unicast | mt76                           | mt76                      |
+|         | __mt7996_stat_to_netdev()      | __mt7996_stat_to_netdev() |
+|---------|--------------------------------|---------------------------|
+| BMCast  | mac80211                       | mac80211                  |
+|         | __ieee80211_subif_start_xmit() | ieee80211_deliver_skb()   |
+---
+ mt7996/init.c |  1 +
+ mt7996/main.c |  1 +
+ mt7996/mcu.c  | 40 +++++++++++++++++++++++++++++++++++-----
+ 3 files changed, 37 insertions(+), 5 deletions(-)
+
+diff --git a/mt7996/init.c b/mt7996/init.c
+index aedf4edc7..518f70e43 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -390,6 +390,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed)
+ 	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_ACK_SIGNAL_SUPPORT);
+ 	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CAN_REPLACE_PTK0);
+ 	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_MU_MIMO_AIR_SNIFFER);
++	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_STAS_COUNT);
+ 
+ 	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_OPERATING_CHANNEL_VALIDATION);
+ 	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_BEACON_PROTECTION);
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 8f0674b37..6eadc0294 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -259,6 +259,7 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 	mvif->sta.wcid.phy_idx = band_idx;
+ 	mvif->sta.wcid.hw_key_idx = -1;
+ 	mvif->sta.wcid.tx_info |= MT_WCID_TX_INFO_SET;
++	mvif->sta.vif = mvif;
+ 	mt76_wcid_init(&mvif->sta.wcid);
+ 
+ 	mt7996_mac_wtbl_update(dev, idx,
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 3cc38b14e..57a5c7712 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -524,6 +524,27 @@ mt7996_mcu_update_tx_gi(struct rate_info *rate, struct all_sta_trx_rate *mcu_rat
+ 	return 0;
+ }
+ 
++static inline void __mt7996_stat_to_netdev(struct mt76_phy *mphy,
++					   struct mt76_wcid *wcid,
++					   u32 tx_bytes, u32 rx_bytes,
++					   u32 tx_packets, u32 rx_packets)
++{
++	struct mt7996_sta *msta;
++	struct ieee80211_vif *vif;
++	struct wireless_dev *wdev;
++
++	if (wiphy_ext_feature_isset(mphy->hw->wiphy,
++				    NL80211_EXT_FEATURE_STAS_COUNT)) {
++		msta = container_of(wcid, struct mt7996_sta, wcid);
++		vif = container_of((void *)msta->vif, struct ieee80211_vif,
++				   drv_priv);
++		wdev = ieee80211_vif_to_wdev(vif);
++
++		dev_sw_netstats_tx_add(wdev->netdev, tx_packets, tx_bytes);
++		dev_sw_netstats_rx_add(wdev->netdev, rx_packets, rx_bytes);
++	}
++}
++
+ static void
+ mt7996_mcu_rx_all_sta_info_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ {
+@@ -539,7 +560,7 @@ mt7996_mcu_rx_all_sta_info_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 		u16 wlan_idx;
+ 		struct mt76_wcid *wcid;
+ 		struct mt76_phy *mphy;
+-		u32 tx_bytes, rx_bytes;
++		u32 tx_bytes, rx_bytes, tx_packets, rx_packets;
+ 
+ 		switch (le16_to_cpu(res->tag)) {
+ 		case UNI_ALL_STA_TXRX_RATE:
+@@ -567,6 +588,9 @@ mt7996_mcu_rx_all_sta_info_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 				wcid->stats.tx_bytes += tx_bytes;
+ 				wcid->stats.rx_bytes += rx_bytes;
+ 
++				__mt7996_stat_to_netdev(mphy, wcid,
++							tx_bytes, rx_bytes, 0, 0);
++
+ 				ieee80211_tpt_led_trig_tx(mphy->hw, tx_bytes);
+ 				ieee80211_tpt_led_trig_rx(mphy->hw, rx_bytes);
+ 			}
+@@ -578,10 +602,16 @@ mt7996_mcu_rx_all_sta_info_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 			if (!wcid)
+ 				break;
+ 
+-			wcid->stats.tx_packets +=
+-				le32_to_cpu(res->msdu_cnt[i].tx_msdu_cnt);
+-			wcid->stats.rx_packets +=
+-				le32_to_cpu(res->msdu_cnt[i].rx_msdu_cnt);
++			mphy = mt76_dev_phy(&dev->mt76, wcid->phy_idx);
++
++			tx_packets = le32_to_cpu(res->msdu_cnt[i].tx_msdu_cnt);
++			rx_packets = le32_to_cpu(res->msdu_cnt[i].rx_msdu_cnt);
++
++			wcid->stats.tx_packets += tx_packets;
++			wcid->stats.rx_packets += rx_packets;
++
++			__mt7996_stat_to_netdev(mphy, wcid, 0, 0,
++						tx_packets, rx_packets);
+ 			break;
+ 		default:
+ 			break;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0082-mtk-wifi-mt76-mt7996-do-not-report-netdev-stats-on-m.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0082-mtk-wifi-mt76-mt7996-do-not-report-netdev-stats-on-m.patch
new file mode 100644
index 0000000..eb655c8
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0082-mtk-wifi-mt76-mt7996-do-not-report-netdev-stats-on-m.patch
@@ -0,0 +1,39 @@
+From 50f7fc1d76ac93fcbc8f6b9f1137953d89b197cb Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Thu, 26 Oct 2023 17:27:43 +0800
+Subject: [PATCH 082/120] mtk: wifi: mt76: mt7996: do not report netdev stats
+ on monitor vif
+
+This fixes the following NULL pointer crash when enabling monitor mode:
+[  205.593158] Call trace:
+[  205.595597]  mt7996_mcu_rx_event+0x4a0/0x6e8 [mt7996e]
+[  205.600725]  mt7996_queue_rx_skb+0x6e4/0xfa0 [mt7996e]
+[  205.605851]  mt76_dma_rx_poll+0x384/0x420 [mt76]
+[  205.610459]  __napi_poll+0x38/0x1c0
+[  205.613935]  napi_threaded_poll+0x80/0xe8
+[  205.617934]  kthread+0x124/0x128
+
+CR-Id: WCNCR00238098
+Change-Id: I66f2449401888255bf8d3edddc1d9f20bd8ba3e7
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/mcu.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 57a5c7712..c38218279 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -540,6 +540,9 @@ static inline void __mt7996_stat_to_netdev(struct mt76_phy *mphy,
+ 				   drv_priv);
+ 		wdev = ieee80211_vif_to_wdev(vif);
+ 
++		if (vif->type == NL80211_IFTYPE_MONITOR)
++			return;
++
+ 		dev_sw_netstats_tx_add(wdev->netdev, tx_packets, tx_bytes);
+ 		dev_sw_netstats_rx_add(wdev->netdev, rx_packets, rx_bytes);
+ 	}
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0083-mtk-wifi-mt76-mt7996-add-support-for-HW-ATF.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0083-mtk-wifi-mt76-mt7996-add-support-for-HW-ATF.patch
new file mode 100644
index 0000000..1140e3f
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0083-mtk-wifi-mt76-mt7996-add-support-for-HW-ATF.patch
@@ -0,0 +1,675 @@
+From 88fb8fe504f95e193dda4d389eee5e8d81a06234 Mon Sep 17 00:00:00 2001
+From: Benjamin Lin <benjamin-jw.lin@mediatek.com>
+Date: Mon, 11 Sep 2023 16:35:15 +0800
+Subject: [PATCH 083/120] mtk: wifi: mt76: mt7996: add support for HW-ATF
+
+---
+ mt7996/debugfs.c |  90 ++++++++++++++++
+ mt7996/init.c    |  43 ++++++++
+ mt7996/mac.c     |   6 ++
+ mt7996/mcu.c     | 265 ++++++++++++++++++++++++++++++++++++++++++-----
+ mt7996/mcu.h     |   1 +
+ mt7996/mt7996.h  |  96 ++++++++++++++++-
+ 6 files changed, 475 insertions(+), 26 deletions(-)
+
+diff --git a/mt7996/debugfs.c b/mt7996/debugfs.c
+index d38e85617..ff57b0f1f 100644
+--- a/mt7996/debugfs.c
++++ b/mt7996/debugfs.c
+@@ -940,6 +940,91 @@ DEFINE_DEBUGFS_ATTRIBUTE(fops_fw_debug_muru_disable,
+ 			 mt7996_fw_debug_muru_disable_get,
+ 			 mt7996_fw_debug_muru_disable_set, "%lld\n");
+ 
++static int
++mt7996_vow_info_read(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	struct mt7996_vow_ctrl *vow = &dev->vow;
++	int i;
++
++	seq_printf(s, "VoW ATF Configuration:\n");
++	seq_printf(s, "ATF: %s\n", vow->atf_enable ? "enabled" : "disabled");
++	seq_printf(s, "WATF: %s\n", vow->watf_enable ? "enabled" : "disabled");
++	seq_printf(s, "Airtime Quantums (unit: 256 us)\n");
++	for (i = 0; i < VOW_DRR_QUANTUM_NUM; ++i)
++		seq_printf(s, "\tL%d: %hhu\n", i, vow->drr_quantum[i]);
++	seq_printf(s, "Max Airtime Deficit: %hhu (unit: 256 us)\n", vow->max_deficit);
++
++	return 0;
++}
++
++static int
++mt7996_atf_enable_get(void *data, u64 *val)
++{
++	struct mt7996_phy *phy = data;
++
++	*val = phy->dev->vow.atf_enable;
++
++	return 0;
++}
++
++static int
++mt7996_atf_enable_set(void *data, u64 val)
++{
++	struct mt7996_phy *phy = data;
++	struct mt7996_vow_ctrl *vow = &phy->dev->vow;
++	int ret;
++
++	vow->max_deficit = val ? 64 : 1;
++	ret = mt7996_mcu_set_vow_drr_ctrl(phy, NULL, VOW_DRR_CTRL_AIRTIME_DEFICIT_BOUND);
++	if (ret)
++		return ret;
++
++	vow->atf_enable = !!val;
++	return mt7996_mcu_set_vow_feature_ctrl(phy);
++}
++
++DEFINE_DEBUGFS_ATTRIBUTE(fops_atf_enable, mt7996_atf_enable_get,
++	                 mt7996_atf_enable_set, "%llu\n");
++
++static int
++mt7996_airtime_read(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	struct mt76_dev *mdev = &dev->mt76;
++	struct mt7996_vow_sta_ctrl *vow;
++	struct ieee80211_sta *sta;
++	struct mt7996_sta *msta;
++	struct mt76_wcid *wcid;
++	struct mt76_vif *vif;
++	u64 airtime;
++	u16 i;
++
++	seq_printf(s, "VoW Airtime Information:\n");
++	rcu_read_lock();
++	for (i = 1; i < MT7996_WTBL_STA; ++i) {
++		wcid = rcu_dereference(mdev->wcid[i]);
++		if (!wcid || !wcid->sta)
++			continue;
++
++		msta = container_of(wcid, struct mt7996_sta, wcid);
++		sta = container_of((void *)msta, struct ieee80211_sta, drv_priv);
++		vow = &msta->vow;
++		vif = &msta->vif->mt76;
++
++		spin_lock_bh(&vow->lock);
++		airtime = vow->tx_airtime;
++		vow->tx_airtime = 0;
++		spin_unlock_bh(&vow->lock);
++
++		seq_printf(s, "%pM WCID: %hu BandIdx: %hhu OmacIdx: 0x%hhx\tTxAirtime: %llu\n",
++		           sta->addr, i, vif->band_idx, vif->omac_idx, airtime);
++	}
++	rcu_read_unlock();
++
++	return 0;
++}
++
+ int mt7996_init_debugfs(struct mt7996_phy *phy)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -967,6 +1052,11 @@ int mt7996_init_debugfs(struct mt7996_phy *phy)
+ 				    mt7996_twt_stats);
+ 	debugfs_create_file("rf_regval", 0600, dir, dev, &fops_rf_regval);
+ 	debugfs_create_file("otp", 0400, dir, dev, &mt7996_efuse_ops);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "vow_info", dir,
++	                            mt7996_vow_info_read);
++	debugfs_create_file("atf_enable", 0600, dir, phy, &fops_atf_enable);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "airtime", dir,
++	                            mt7996_airtime_read);
+ 
+ 	if (phy->mt76->cap.has_5ghz) {
+ 		debugfs_create_u32("dfs_hw_pattern", 0400, dir,
+diff --git a/mt7996/init.c b/mt7996/init.c
+index 518f70e43..b902bcc57 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -562,6 +562,37 @@ int mt7996_txbf_init(struct mt7996_dev *dev)
+ 	return mt7996_mcu_set_txbf(dev, BF_HW_EN_UPDATE);
+ }
+ 
++static int mt7996_vow_init(struct mt7996_phy *phy)
++{
++	struct mt7996_vow_ctrl *vow = &phy->dev->vow;
++	int ret;
++
++	vow->atf_enable = true;
++	vow->watf_enable = false;
++	vow->max_deficit = 64;
++	vow->sch_type = VOW_SCH_TYPE_FOLLOW_POLICY;
++	vow->sch_policy = VOW_SCH_POLICY_SRR;
++
++	vow->drr_quantum[0] = VOW_DRR_QUANTUM_L0;
++	vow->drr_quantum[1] = VOW_DRR_QUANTUM_L1;
++	vow->drr_quantum[2] = VOW_DRR_QUANTUM_L2;
++	vow->drr_quantum[3] = VOW_DRR_QUANTUM_L3;
++	vow->drr_quantum[4] = VOW_DRR_QUANTUM_L4;
++	vow->drr_quantum[5] = VOW_DRR_QUANTUM_L5;
++	vow->drr_quantum[6] = VOW_DRR_QUANTUM_L6;
++	vow->drr_quantum[7] = VOW_DRR_QUANTUM_L7;
++
++	ret = mt7996_mcu_set_vow_drr_ctrl(phy, NULL, VOW_DRR_CTRL_AIRTIME_DEFICIT_BOUND);
++	if (ret)
++		return ret;
++
++	ret = mt7996_mcu_set_vow_drr_ctrl(phy, NULL, VOW_DRR_CTRL_AIRTIME_QUANTUM_ALL);
++	if (ret)
++		return ret;
++
++	return mt7996_mcu_set_vow_feature_ctrl(phy);
++}
++
+ static int mt7996_register_phy(struct mt7996_dev *dev, struct mt7996_phy *phy,
+ 			       enum mt76_band_id band)
+ {
+@@ -634,6 +665,12 @@ static int mt7996_register_phy(struct mt7996_dev *dev, struct mt7996_phy *phy,
+ 	if (ret)
+ 		goto error;
+ 
++	if (mt7996_vow_should_enable(dev)) {
++		ret = mt7996_vow_init(phy);
++		if (ret)
++			goto error;
++	}
++
+ 	ret = mt7996_init_debugfs(phy);
+ 	if (ret)
+ 		goto error;
+@@ -1440,6 +1477,12 @@ int mt7996_register_device(struct mt7996_dev *dev)
+ 
+ 	dev->recovery.hw_init_done = true;
+ 
++	if (mt7996_vow_should_enable(dev)) {
++		ret = mt7996_vow_init(&dev->phy);
++		if (ret)
++			goto error;
++	}
++
+ 	ret = mt7996_init_debugfs(&dev->phy);
+ 	if (ret)
+ 		goto error;
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index eae7e1b2d..9b143433a 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -103,6 +103,7 @@ static void mt7996_mac_sta_poll(struct mt7996_dev *dev)
+ 	};
+ 	struct ieee80211_sta *sta;
+ 	struct mt7996_sta *msta;
++	struct mt7996_vow_sta_ctrl *vow;
+ 	u32 tx_time[IEEE80211_NUM_ACS], rx_time[IEEE80211_NUM_ACS];
+ 	LIST_HEAD(sta_poll_list);
+ 	int i;
+@@ -161,6 +162,7 @@ static void mt7996_mac_sta_poll(struct mt7996_dev *dev)
+ 
+ 		sta = container_of((void *)msta, struct ieee80211_sta,
+ 				   drv_priv);
++		vow = &msta->vow;
+ 		for (i = 0; i < IEEE80211_NUM_ACS; i++) {
+ 			u8 q = mt76_connac_lmac_mapping(i);
+ 			u32 tx_cur = tx_time[q];
+@@ -171,6 +173,10 @@ static void mt7996_mac_sta_poll(struct mt7996_dev *dev)
+ 				continue;
+ 
+ 			ieee80211_sta_register_airtime(sta, tid, tx_cur, rx_cur);
++
++			spin_lock_bh(&vow->lock);
++			vow->tx_airtime += tx_cur;
++			spin_unlock_bh(&vow->lock);
+ 		}
+ 
+ 		/* get signal strength of resp frames (CTS/BA/ACK) */
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index c38218279..adba9c770 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -2225,34 +2225,37 @@ int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ }
+ 
+ static int
+-mt7996_mcu_add_group(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+-		     struct ieee80211_sta *sta)
++mt7996_mcu_sta_init_vow(struct mt7996_phy *phy, struct mt7996_sta *msta)
+ {
+-#define MT_STA_BSS_GROUP		1
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_sta *msta;
+-	struct {
+-		u8 __rsv1[4];
++	struct mt7996_vow_sta_ctrl *vow = &msta->vow;
++	u8 omac_idx = msta->vif->mt76.omac_idx;
++	int ret;
+ 
+-		__le16 tag;
+-		__le16 len;
+-		__le16 wlan_idx;
+-		u8 __rsv2[2];
+-		__le32 action;
+-		__le32 val;
+-		u8 __rsv3[8];
+-	} __packed req = {
+-		.tag = cpu_to_le16(UNI_VOW_DRR_CTRL),
+-		.len = cpu_to_le16(sizeof(req) - 4),
+-		.action = cpu_to_le32(MT_STA_BSS_GROUP),
+-		.val = cpu_to_le32(mvif->mt76.idx % 16),
+-	};
++	/* Assignment of STA BSS group index aligns FW.
++	 * Each band has its own BSS group bitmap space.
++	 * 0: BSS 0
++	 * 4..18: BSS 0x11..0x1f
++	 */
++	vow->bss_grp_idx = (omac_idx <= HW_BSSID_MAX)
++	                   ? omac_idx
++	                   : HW_BSSID_MAX + omac_idx - EXT_BSSID_START;
++	vow->paused = false;
++	vow->drr_quantum[IEEE80211_AC_VO] = VOW_DRR_QUANTUM_IDX0;
++	vow->drr_quantum[IEEE80211_AC_VI] = VOW_DRR_QUANTUM_IDX1;
++	vow->drr_quantum[IEEE80211_AC_BE] = VOW_DRR_QUANTUM_IDX2;
++	vow->drr_quantum[IEEE80211_AC_BK] = VOW_DRR_QUANTUM_IDX2;
++	vow->tx_airtime = 0;
++	spin_lock_init(&vow->lock);
++
++	ret = mt7996_mcu_set_vow_drr_ctrl(phy, msta, VOW_DRR_CTRL_STA_BSS_GROUP);
++	if (ret)
++		return ret;
+ 
+-	msta = sta ? (struct mt7996_sta *)sta->drv_priv : &mvif->sta;
+-	req.wlan_idx = cpu_to_le16(msta->wcid.idx);
++	ret = mt7996_mcu_set_vow_drr_ctrl(phy, msta, VOW_DRR_CTRL_STA_PAUSE);
++	if (ret)
++		return ret;
+ 
+-	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(VOW), &req,
+-				 sizeof(req), true);
++	return mt7996_mcu_set_vow_drr_ctrl(phy, msta, VOW_DRR_CTRL_STA_ALL);
+ }
+ 
+ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+@@ -2308,7 +2311,7 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 		mt7996_mcu_sta_bfee_tlv(dev, skb, vif, sta);
+ 	}
+ 
+-	ret = mt7996_mcu_add_group(dev, vif, sta);
++	ret = mt7996_mcu_sta_init_vow(mvif->phy, msta);
+ 	if (ret) {
+ 		dev_kfree_skb(skb);
+ 		return ret;
+@@ -5161,6 +5164,218 @@ int mt7996_mcu_set_scs(struct mt7996_phy *phy, u8 enable)
+ 				 &req, sizeof(req), false);
+ }
+ 
++int mt7996_mcu_set_vow_drr_ctrl(struct mt7996_phy *phy, struct mt7996_sta *msta,
++	                        enum vow_drr_ctrl_id id)
++{
++	struct mt7996_vow_sta_ctrl *vow = msta ? &msta->vow : NULL;
++	u32 val = 0;
++	struct {
++		u8 __rsv1[4];
++
++		__le16 tag;
++		__le16 len;
++		__le16 wlan_idx;
++		u8 band_idx;
++		u8 wmm_idx;
++		__le32 ctrl_id;
++
++		union {
++			__le32 val;
++			u8 drr_quantum[VOW_DRR_QUANTUM_NUM];
++		};
++
++		u8 __rsv2[3];
++		u8 omac_idx;
++	} __packed req = {
++		.tag = cpu_to_le16(UNI_VOW_DRR_CTRL),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.wlan_idx = cpu_to_le16(msta ? msta->wcid.idx : 0),
++		.band_idx = phy->mt76->band_idx,
++		.wmm_idx = msta ? msta->vif->mt76.wmm_idx : 0,
++		.ctrl_id = cpu_to_le32(id),
++		.omac_idx = msta ? msta->vif->mt76.omac_idx : 0
++	};
++
++	switch (id) {
++	case VOW_DRR_CTRL_STA_ALL:
++		val |= FIELD_PREP(MT7996_DRR_STA_BSS_GRP_MASK, vow->bss_grp_idx);
++		val |= FIELD_PREP(MT7996_DRR_STA_AC0_QNTM_MASK, vow->drr_quantum[IEEE80211_AC_BK]);
++		val |= FIELD_PREP(MT7996_DRR_STA_AC1_QNTM_MASK, vow->drr_quantum[IEEE80211_AC_BE]);
++		val |= FIELD_PREP(MT7996_DRR_STA_AC2_QNTM_MASK, vow->drr_quantum[IEEE80211_AC_VI]);
++		val |= FIELD_PREP(MT7996_DRR_STA_AC3_QNTM_MASK, vow->drr_quantum[IEEE80211_AC_VO]);
++		req.val = cpu_to_le32(val);
++		break;
++	case VOW_DRR_CTRL_STA_BSS_GROUP:
++		req.val = cpu_to_le32(vow->bss_grp_idx);
++		break;
++	case VOW_DRR_CTRL_AIRTIME_DEFICIT_BOUND:
++		req.val = cpu_to_le32(phy->dev->vow.max_deficit);
++		break;
++	case VOW_DRR_CTRL_AIRTIME_QUANTUM_ALL:
++		memcpy(req.drr_quantum, phy->dev->vow.drr_quantum, VOW_DRR_QUANTUM_NUM);
++		break;
++	case VOW_DRR_CTRL_STA_PAUSE:
++		req.val = cpu_to_le32(vow->paused);
++		break;
++	default:
++		dev_err(phy->dev->mt76.dev, "Unknown VoW DRR Control ID: %u\n", id);
++		return -EINVAL;
++	}
++
++	return mt76_mcu_send_msg(&phy->dev->mt76, MCU_WM_UNI_CMD(VOW),
++	                         &req, sizeof(req), true);
++}
++
++int mt7996_mcu_set_vow_feature_ctrl(struct mt7996_phy *phy)
++{
++	struct mt7996_vow_ctrl *vow = &phy->dev->vow;
++	struct {
++		u8 __rsv1[4];
++
++		__le16 tag;
++		__le16 len;
++
++		/* DW0 */
++		__le16 apply_bwc_enable_per_grp;
++		__le16 apply_bwc_refill_period		: 1;
++		__le16 __rsv2				: 3;
++		__le16 apply_band1_search_rule		: 1;
++		__le16 apply_band0_search_rule		: 1;
++		__le16 __rsv3				: 3;
++		__le16 apply_watf_enable		: 1;
++		__le16 __rsv4				: 2;
++		__le16 apply_grp_no_change_in_txop	: 1;
++		__le16 apply_atf_enable			: 1;
++		__le16 apply_bwc_token_refill_enable	: 1;
++		__le16 apply_bwc_enable			: 1;
++
++		/* DW1 */
++		__le16 apply_bwc_check_time_token_per_grp;
++		__le16 __rsv5;
++
++		/* DW2 */
++		__le16 apply_bwc_check_len_token_per_grp;
++		__le16 __rsv6;
++
++		/* DW3 */
++		u8 band_idx;
++		u8 __rsv7[3];
++
++		/* DW4 */
++		__le32 __rsv8;
++
++		/* DW5 */
++		__le16 bwc_enable_per_grp;
++		__le16 bwc_refill_period	: 3;
++		__le16 __rsv9			: 1;
++		__le16 band1_search_rule	: 1;
++		__le16 band0_search_rule	: 1;
++		__le16 __rsv10			: 3;
++		__le16 watf_enable		: 1;
++		__le16 __rsv11			: 2;
++		__le16 grp_no_change_in_txop	: 1;
++		__le16 atf_enable		: 1;
++		__le16 bwc_token_refill_enable	: 1;
++		__le16 bwc_enable		: 1;
++
++		/* DW6 */
++		__le16 bwc_check_time_token_per_grp;
++		__le16 __rsv12;
++
++		/* DW7 */
++		__le16 bwc_check_len_token_per_grp;
++		__le16 __rsv13;
++
++		/* DW8 */
++		__le32 apply_atf_rts_sta_lock		: 1;
++		__le32 atf_rts_sta_lock			: 1;
++		__le32 apply_atf_keep_quantum		: 1;
++		__le32 atf_keep_quantum			: 1;
++		__le32 apply_tx_cnt_mode_ctrl		: 1;
++		__le32 tx_cnt_mode_ctrl			: 4;
++		__le32 apply_tx_measure_mode_enable	: 1;
++		__le32 tx_measure_mode_enable		: 1;
++		__le32 apply_backoff_ctrl		: 1;
++		__le32 backoff_bound_enable		: 1;
++		__le32 backoff_bound			: 5;
++		__le32 apply_atf_rts_fail_charge	: 1;
++		__le32 atf_rts_fail_charge		: 1;
++		__le32 apply_zero_eifs			: 1;
++		__le32 zero_eifs			: 1;
++		__le32 apply_rx_rifs_enable		: 1;
++		__le32 rx_rifs_enable			: 1;
++		__le32 apply_vow_ctrl			: 1;
++		__le32 vow_ctrl_val			: 1;
++		__le32 vow_ctrl_bit			: 5;
++		__le32 __rsv14				: 1;
++
++		/* DW9 */
++		__le32 apply_spl_sta_num	: 1;
++		__le32 spl_sta_num		: 3;
++		__le32 dbg_lvl			: 2;
++		__le32 apply_atf_sch_ctrl	: 1;
++		__le32 atf_sch_type		: 2;
++		__le32 atf_sch_policy		: 2;
++		__le32 __rsv15			: 21;
++	} __packed req = {
++		.tag = cpu_to_le16(UNI_VOW_FEATURE_CTRL),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		/* DW0 */
++		.apply_bwc_enable_per_grp = cpu_to_le16(0xffff),
++		.apply_bwc_refill_period = true,
++		.apply_band1_search_rule = true,
++		.apply_band0_search_rule = true,
++		.apply_watf_enable = true,
++		.apply_grp_no_change_in_txop = true,
++		.apply_atf_enable = true,
++		.apply_bwc_token_refill_enable = true,
++		.apply_bwc_enable = true,
++		/* DW1 */
++		.apply_bwc_check_time_token_per_grp = cpu_to_le16(0xffff),
++		/* DW2 */
++		.apply_bwc_check_len_token_per_grp = cpu_to_le16(0xffff),
++		/* DW3 */
++		.band_idx = phy->mt76->band_idx,
++		/* DW5 */
++		.bwc_enable_per_grp = cpu_to_le16(0xffff),
++		.bwc_refill_period = VOW_REFILL_PERIOD_32US,
++		.band1_search_rule = VOW_SEARCH_WMM_FIRST,
++		.band0_search_rule = VOW_SEARCH_WMM_FIRST,
++		.watf_enable = vow->watf_enable,
++		.grp_no_change_in_txop = true,
++		.atf_enable = vow->atf_enable,
++		.bwc_token_refill_enable = true,
++		.bwc_enable = false,
++		/* DW6 */
++		.bwc_check_time_token_per_grp = cpu_to_le16(0x0),
++		/* DW7 */
++		.bwc_check_len_token_per_grp = cpu_to_le16(0x0),
++		/* DW8 */
++		.apply_atf_rts_sta_lock = false,
++		.apply_atf_keep_quantum = true,
++		.atf_keep_quantum = true,
++		.apply_tx_cnt_mode_ctrl = false,
++		.apply_tx_measure_mode_enable = false,
++		.apply_backoff_ctrl = false,
++		.apply_atf_rts_fail_charge = false,
++		.apply_zero_eifs = false,
++		.apply_rx_rifs_enable = false,
++		.apply_vow_ctrl = true,
++		.vow_ctrl_val = true,
++		/* Reset DRR table when SER occurs. */
++		.vow_ctrl_bit = 26,
++		/* DW9 */
++		.apply_spl_sta_num = false,
++		.dbg_lvl = 0,
++		.apply_atf_sch_ctrl = true,
++		.atf_sch_type = vow->sch_type,
++		.atf_sch_policy = vow->sch_policy
++	};
++
++	return mt76_mcu_send_msg(&phy->dev->mt76, MCU_WM_UNI_CMD(VOW),
++	                         &req, sizeof(req), true);
++}
++
+ #ifdef CONFIG_MTK_VENDOR
+ void mt7996_set_wireless_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
+ {
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 1221cf1d9..e7debd4d6 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -965,6 +965,7 @@ enum {
+ 
+ enum {
+ 	UNI_VOW_DRR_CTRL,
++	UNI_VOW_FEATURE_CTRL,
+ 	UNI_VOW_RX_AT_AIRTIME_EN = 0x0b,
+ 	UNI_VOW_RX_AT_AIRTIME_CLR_EN = 0x0e,
+ 	UNI_VOW_RED_ENABLE = 0x18,
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 2ff5ce855..984837c77 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -115,6 +115,12 @@
+ #define MT7996_RX_MSDU_PAGE_SIZE	(128 + \
+ 					 SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
+ 
++#define MT7996_DRR_STA_BSS_GRP_MASK	GENMASK(5, 0)
++#define MT7996_DRR_STA_AC0_QNTM_MASK	GENMASK(10, 8)
++#define MT7996_DRR_STA_AC1_QNTM_MASK	GENMASK(14, 12)
++#define MT7996_DRR_STA_AC2_QNTM_MASK	GENMASK(18, 16)
++#define MT7996_DRR_STA_AC3_QNTM_MASK	GENMASK(22, 20)
++
+ struct mt7996_vif;
+ struct mt7996_sta;
+ struct mt7996_dfs_pulse;
+@@ -216,6 +222,81 @@ enum mt7996_dpd_ch_num {
+ 	DPD_CH_NUM_TYPE_MAX,
+ };
+ 
++enum {
++	VOW_SEARCH_AC_FIRST,
++	VOW_SEARCH_WMM_FIRST
++};
++
++enum {
++	VOW_REFILL_PERIOD_1US,
++	VOW_REFILL_PERIOD_2US,
++	VOW_REFILL_PERIOD_4US,
++	VOW_REFILL_PERIOD_8US,
++	VOW_REFILL_PERIOD_16US,
++	VOW_REFILL_PERIOD_32US,
++	VOW_REFILL_PERIOD_64US,
++	VOW_REFILL_PERIOD_128US
++};
++
++/* Default DRR airtime quantum of each level */
++enum {
++	VOW_DRR_QUANTUM_L0 = 6,
++	VOW_DRR_QUANTUM_L1 = 12,
++	VOW_DRR_QUANTUM_L2 = 16,
++	VOW_DRR_QUANTUM_L3 = 20,
++	VOW_DRR_QUANTUM_L4 = 24,
++	VOW_DRR_QUANTUM_L5 = 28,
++	VOW_DRR_QUANTUM_L6 = 32,
++	VOW_DRR_QUANTUM_L7 = 36
++};
++
++enum {
++	VOW_DRR_QUANTUM_IDX0,
++	VOW_DRR_QUANTUM_IDX1,
++	VOW_DRR_QUANTUM_IDX2,
++	VOW_DRR_QUANTUM_IDX3,
++	VOW_DRR_QUANTUM_IDX4,
++	VOW_DRR_QUANTUM_IDX5,
++	VOW_DRR_QUANTUM_IDX6,
++	VOW_DRR_QUANTUM_IDX7,
++	VOW_DRR_QUANTUM_NUM
++};
++
++enum {
++	VOW_SCH_TYPE_FOLLOW_POLICY,
++	VOW_SCH_TYPE_FOLLOW_HW
++};
++
++enum {
++	VOW_SCH_POLICY_SRR, /* Shared Round-Robin */
++	VOW_SCH_POLICY_WRR /* Weighted Round-Robin */
++};
++
++enum vow_drr_ctrl_id {
++	VOW_DRR_CTRL_STA_ALL,
++	VOW_DRR_CTRL_STA_BSS_GROUP,
++	VOW_DRR_CTRL_AIRTIME_DEFICIT_BOUND = 0x10,
++	VOW_DRR_CTRL_AIRTIME_QUANTUM_ALL = 0x28,
++	VOW_DRR_CTRL_STA_PAUSE = 0x30
++};
++
++struct mt7996_vow_ctrl {
++	bool atf_enable;
++	bool watf_enable;
++	u8 drr_quantum[VOW_DRR_QUANTUM_NUM];
++	u8 max_deficit;
++	u8 sch_type;
++	u8 sch_policy;
++};
++
++struct mt7996_vow_sta_ctrl {
++	bool paused;
++	u8 bss_grp_idx;
++	u8 drr_quantum[IEEE80211_NUM_ACS];
++	u64 tx_airtime;
++	spinlock_t lock;
++};
++
+ struct mt7996_sta {
+ 	struct mt76_wcid wcid; /* must be first */
+ 
+@@ -235,6 +316,8 @@ struct mt7996_sta {
+ 		u8 flowid_mask;
+ 		struct mt7996_twt_flow flow[MT7996_MAX_STA_TWT_AGRT];
+ 	} twt;
++
++	struct mt7996_vow_sta_ctrl vow;
+ };
+ 
+ struct mt7996_vif {
+@@ -494,6 +577,7 @@ struct mt7996_dev {
+ 
+ 	u8 wtbl_size_group;
+ 
++	struct mt7996_vow_ctrl vow;
+ #ifdef CONFIG_MTK_DEBUG
+ 	u16 wlan_idx;
+ 	struct {
+@@ -734,10 +818,12 @@ int mt7996_mcu_apply_tx_dpd(struct mt7996_phy *phy);
+ #ifdef CONFIG_NL80211_TESTMODE
+ void mt7996_tm_rf_test_event(struct mt7996_dev *dev, struct sk_buff *skb);
+ #endif
+-int mt7996_mcu_get_tx_power_info(struct mt7996_phy *phy, u8 category, void *event);
+ int mt7996_mcu_set_scs(struct mt7996_phy *phy, u8 enable);
+ void mt7996_mcu_scs_sta_poll(struct work_struct *work);
+ int mt7996_mcu_set_band_confg(struct mt7996_phy *phy, u16 option, bool enable);
++int mt7996_mcu_set_vow_drr_ctrl(struct mt7996_phy *phy, struct mt7996_sta *msta,
++	                        enum vow_drr_ctrl_id id);
++int mt7996_mcu_set_vow_feature_ctrl(struct mt7996_phy *phy);
+ 
+ static inline u8 mt7996_max_interface_num(struct mt7996_dev *dev)
+ {
+@@ -787,6 +873,14 @@ static inline u16 mt7996_rx_chainmask(struct mt7996_phy *phy)
+ 	return tx_chainmask | (BIT(fls(tx_chainmask)) * phy->has_aux_rx);
+ }
+ 
++static inline bool
++mt7996_vow_should_enable(struct mt7996_dev *dev)
++{
++	return !wiphy_ext_feature_isset(mt76_hw(dev)->wiphy,
++	                                NL80211_EXT_FEATURE_AIRTIME_FAIRNESS) ||
++	       mtk_wed_device_active(&dev->mt76.mmio.wed);
++}
++
+ void mt7996_mac_init(struct mt7996_dev *dev);
+ u32 mt7996_mac_wtbl_lmac_addr(struct mt7996_dev *dev, u16 wcid, u8 dw);
+ bool mt7996_mac_wtbl_update(struct mt7996_dev *dev, int idx, u32 mask);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0084-mtk-wifi-mt76-mt7996-wed-add-SER0.5-support-w-wed3.0.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0084-mtk-wifi-mt76-mt7996-wed-add-SER0.5-support-w-wed3.0.patch
new file mode 100644
index 0000000..5b98b8b
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0084-mtk-wifi-mt76-mt7996-wed-add-SER0.5-support-w-wed3.0.patch
@@ -0,0 +1,398 @@
+From c242128aec44c929fda0778e7cd05c73bb63c4a3 Mon Sep 17 00:00:00 2001
+From: "sujuan.chen" <sujuan.chen@mediatek.com>
+Date: Thu, 12 Oct 2023 10:04:54 +0800
+Subject: [PATCH 084/120] mtk: wifi: mt76: mt7996: wed: add SER0.5 support w/
+ wed3.0
+
+Signed-off-by: sujuan.chen <sujuan.chen@mediatek.com>
+Change-Id: I9b26cdbea6e8ee158a153fd153c2dea77b494f2f
+---
+ dma.c           |   9 ++--
+ dma.h           |   4 +-
+ mt76.h          |  14 ++++--
+ mt792x_dma.c    |   6 +--
+ mt7996/dma.c    |  20 ++++++--
+ mt7996/init.c   | 127 +++++++++++++++++++++++++++++++-----------------
+ mt7996/mac.c    |  25 ++++++++++
+ mt7996/mt7996.h |   1 +
+ wed.c           |   4 +-
+ 9 files changed, 146 insertions(+), 64 deletions(-)
+
+diff --git a/dma.c b/dma.c
+index da21f6410..e23b744b8 100644
+--- a/dma.c
++++ b/dma.c
+@@ -218,9 +218,9 @@ void __mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q,
+ 	mt76_dma_sync_idx(dev, q);
+ }
+ 
+-void mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q)
++void mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q, bool reset)
+ {
+-	__mt76_dma_queue_reset(dev, q, true);
++	__mt76_dma_queue_reset(dev, q, reset);
+ }
+ 
+ static int
+@@ -540,7 +540,8 @@ mt76_dma_dequeue(struct mt76_dev *dev, struct mt76_queue *q, bool flush,
+ 	if (!q->queued)
+ 		return NULL;
+ 
+-	if (mt76_queue_is_wed_rro_data(q))
++	if (mt76_queue_is_wed_rro_data(q) ||
++	    mt76_queue_is_wed_rro_msdu_pg(q))
+ 		return NULL;
+ 
+ 	if (!mt76_queue_is_wed_rro_ind(q)) {
+@@ -792,7 +793,7 @@ mt76_dma_alloc_queue(struct mt76_dev *dev, struct mt76_queue *q,
+ 			return 0;
+ 	}
+ 
+-	mt76_dma_queue_reset(dev, q);
++	mt76_dma_queue_reset(dev, q, true);
+ 
+ 	return 0;
+ }
+diff --git a/dma.h b/dma.h
+index 1de5a2b20..3a8c2e558 100644
+--- a/dma.h
++++ b/dma.h
+@@ -83,12 +83,12 @@ int mt76_dma_rx_fill(struct mt76_dev *dev, struct mt76_queue *q,
+ 		     bool allow_direct);
+ void __mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q,
+ 			    bool reset_idx);
+-void mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q);
++void mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q, bool reset);
+ 
+ static inline void
+ mt76_dma_reset_tx_queue(struct mt76_dev *dev, struct mt76_queue *q)
+ {
+-	dev->queue_ops->reset_q(dev, q);
++	dev->queue_ops->reset_q(dev, q, true);
+ 	if (mtk_wed_device_active(&dev->mmio.wed))
+ 		mt76_wed_dma_setup(dev, q, true);
+ }
+diff --git a/mt76.h b/mt76.h
+index 17418e86f..6ac8a279f 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -296,7 +296,7 @@ struct mt76_queue_ops {
+ 
+ 	void (*kick)(struct mt76_dev *dev, struct mt76_queue *q);
+ 
+-	void (*reset_q)(struct mt76_dev *dev, struct mt76_queue *q);
++	void (*reset_q)(struct mt76_dev *dev, struct mt76_queue *q, bool reset);
+ };
+ 
+ enum mt76_phy_type {
+@@ -1731,8 +1731,13 @@ static inline bool mt76_queue_is_wed_rro_ind(struct mt76_queue *q)
+ static inline bool mt76_queue_is_wed_rro_data(struct mt76_queue *q)
+ {
+ 	return mt76_queue_is_wed_rro(q) &&
+-	       (FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_RRO_Q_DATA ||
+-		FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_RRO_Q_MSDU_PG);
++	       (FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_RRO_Q_DATA);
++}
++
++static inline bool mt76_queue_is_wed_rro_msdu_pg(struct mt76_queue *q)
++{
++	return mt76_queue_is_wed_rro(q) &&
++	       (FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_RRO_Q_MSDU_PG);
+ }
+ 
+ static inline bool mt76_queue_is_wed_rx(struct mt76_queue *q)
+@@ -1741,7 +1746,8 @@ static inline bool mt76_queue_is_wed_rx(struct mt76_queue *q)
+ 		return false;
+ 
+ 	return FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_Q_RX ||
+-	       mt76_queue_is_wed_rro_ind(q) || mt76_queue_is_wed_rro_data(q);
++	       mt76_queue_is_wed_rro_ind(q) || mt76_queue_is_wed_rro_data(q) ||
++	       mt76_queue_is_wed_rro_msdu_pg(q);
+ 
+ }
+ 
+diff --git a/mt792x_dma.c b/mt792x_dma.c
+index 5cc2d59b7..c224bcc8a 100644
+--- a/mt792x_dma.c
++++ b/mt792x_dma.c
+@@ -181,13 +181,13 @@ mt792x_dma_reset(struct mt792x_dev *dev, bool force)
+ 
+ 	/* reset hw queues */
+ 	for (i = 0; i < __MT_TXQ_MAX; i++)
+-		mt76_queue_reset(dev, dev->mphy.q_tx[i]);
++		mt76_queue_reset(dev, dev->mphy.q_tx[i], true);
+ 
+ 	for (i = 0; i < __MT_MCUQ_MAX; i++)
+-		mt76_queue_reset(dev, dev->mt76.q_mcu[i]);
++		mt76_queue_reset(dev, dev->mt76.q_mcu[i], true);
+ 
+ 	mt76_for_each_q_rx(&dev->mt76, i)
+-		mt76_queue_reset(dev, &dev->mt76.q_rx[i]);
++		mt76_queue_reset(dev, &dev->mt76.q_rx[i], true);
+ 
+ 	mt76_tx_status_check(&dev->mt76, true);
+ 
+diff --git a/mt7996/dma.c b/mt7996/dma.c
+index 5d85e9ea2..d9e1b17ff 100644
+--- a/mt7996/dma.c
++++ b/mt7996/dma.c
+@@ -711,21 +711,31 @@ void mt7996_dma_reset(struct mt7996_dev *dev, bool force)
+ 	}
+ 
+ 	for (i = 0; i < __MT_MCUQ_MAX; i++)
+-		mt76_queue_reset(dev, dev->mt76.q_mcu[i]);
++		mt76_queue_reset(dev, dev->mt76.q_mcu[i], true);
+ 
+ 	mt76_for_each_q_rx(&dev->mt76, i) {
+-		if (mtk_wed_device_active(&dev->mt76.mmio.wed))
++		if (mtk_wed_device_active(&dev->mt76.mmio.wed)) {
+ 			if (mt76_queue_is_wed_rro(&dev->mt76.q_rx[i]) ||
+-			    mt76_queue_is_wed_tx_free(&dev->mt76.q_rx[i]))
++			    mt76_queue_is_wed_tx_free(&dev->mt76.q_rx[i])) {
++				if (force && mt76_queue_is_wed_rro_data(&dev->mt76.q_rx[i]))
++					mt76_queue_reset(dev, &dev->mt76.q_rx[i], false);
+ 				continue;
++			}
++		}
+ 
+-		mt76_queue_reset(dev, &dev->mt76.q_rx[i]);
++		mt76_queue_reset(dev, &dev->mt76.q_rx[i], true);
+ 	}
+ 
+ 	mt76_tx_status_check(&dev->mt76, true);
+ 
+-	mt76_for_each_q_rx(&dev->mt76, i)
++	mt76_for_each_q_rx(&dev->mt76, i) {
++		if (mtk_wed_device_active(&dev->mt76.mmio.wed) && force &&
++		    (mt76_queue_is_wed_rro_ind(&dev->mt76.q_rx[i]) ||
++		     mt76_queue_is_wed_rro_msdu_pg(&dev->mt76.q_rx[i])))
++			continue;
++
+ 		mt76_queue_rx_reset(dev, i);
++	}
+ 
+ 	mt7996_dma_enable(dev, !force);
+ }
+diff --git a/mt7996/init.c b/mt7996/init.c
+index b902bcc57..a97201203 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -724,11 +724,91 @@ void mt7996_wfsys_reset(struct mt7996_dev *dev)
+ 	msleep(20);
+ }
+ 
+-static int mt7996_wed_rro_init(struct mt7996_dev *dev)
++void mt7996_rro_hw_init(struct mt7996_dev *dev)
+ {
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
+ 	struct mtk_wed_device *wed = &dev->mt76.mmio.wed;
+ 	u32 reg = MT_RRO_ADDR_ELEM_SEG_ADDR0;
++	int i;
++
++	if (!dev->has_rro)
++		return;
++
++	if (is_mt7992(&dev->mt76)) {
++		/* set emul 3.0 function */
++		mt76_wr(dev, MT_RRO_3_0_EMU_CONF,
++			MT_RRO_3_0_EMU_CONF_EN_MASK);
++
++		mt76_wr(dev, MT_RRO_ADDR_ARRAY_BASE0,
++			dev->wed_rro.addr_elem[0].phy_addr);
++	} else {
++		/* TODO: remove line after WM has set */
++		mt76_clear(dev, WF_RRO_AXI_MST_CFG, WF_RRO_AXI_MST_CFG_DIDX_OK);
++
++		/* setup BA bitmap cache address */
++		mt76_wr(dev, MT_RRO_BA_BITMAP_BASE0,
++			dev->wed_rro.ba_bitmap[0].phy_addr);
++		mt76_wr(dev, MT_RRO_BA_BITMAP_BASE1, 0);
++		mt76_wr(dev, MT_RRO_BA_BITMAP_BASE_EXT0,
++			dev->wed_rro.ba_bitmap[1].phy_addr);
++		mt76_wr(dev, MT_RRO_BA_BITMAP_BASE_EXT1, 0);
++
++		/* setup Address element address */
++		for (i = 0; i < ARRAY_SIZE(dev->wed_rro.addr_elem); i++) {
++			mt76_wr(dev, reg, dev->wed_rro.addr_elem[i].phy_addr >> 4);
++			reg += 4;
++		}
++
++		/* setup Address element address - separate address segment mode */
++		mt76_wr(dev, MT_RRO_ADDR_ARRAY_BASE1,
++			MT_RRO_ADDR_ARRAY_ELEM_ADDR_SEG_MODE);
++	}
++	wed->wlan.ind_cmd.win_size = ffs(MT7996_RRO_WINDOW_MAX_LEN) - 6;
++	if (is_mt7996(&dev->mt76))
++		wed->wlan.ind_cmd.particular_sid = MT7996_RRO_MAX_SESSION;
++	else
++		wed->wlan.ind_cmd.particular_sid = 1;
++	wed->wlan.ind_cmd.particular_se_phys = dev->wed_rro.session.phy_addr;
++	wed->wlan.ind_cmd.se_group_nums = MT7996_RRO_ADDR_ELEM_LEN;
++	wed->wlan.ind_cmd.ack_sn_addr = MT_RRO_ACK_SN_CTRL;
++
++	mt76_wr(dev, MT_RRO_IND_CMD_SIGNATURE_BASE0, 0x15010e00);
++	mt76_set(dev, MT_RRO_IND_CMD_SIGNATURE_BASE1,
++		 MT_RRO_IND_CMD_SIGNATURE_BASE1_EN);
++
++	/* particular session configure */
++	/* use max session idx + 1 as particular session id */
++	mt76_wr(dev, MT_RRO_PARTICULAR_CFG0, dev->wed_rro.session.phy_addr);
++
++	if (is_mt7992(&dev->mt76)) {
++		reg = MT_RRO_MSDU_PG_SEG_ADDR0;
++
++		mt76_set(dev, MT_RRO_3_1_GLOBAL_CONFIG,
++			 MT_RRO_3_1_GLOBAL_CONFIG_INTERLEAVE_EN);
++
++		/* setup Msdu page address */
++		for (i = 0; i < MT7996_RRO_MSDU_PG_CR_CNT; i++) {
++			mt76_wr(dev, reg, dev->wed_rro.msdu_pg[i].phy_addr >> 4);
++			reg += 4;
++		}
++		mt76_wr(dev, MT_RRO_PARTICULAR_CFG1,
++			MT_RRO_PARTICULAR_CONFG_EN |
++			FIELD_PREP(MT_RRO_PARTICULAR_SID, 1));
++	} else {
++		mt76_wr(dev, MT_RRO_PARTICULAR_CFG1,
++			MT_RRO_PARTICULAR_CONFG_EN |
++			FIELD_PREP(MT_RRO_PARTICULAR_SID, MT7996_RRO_MAX_SESSION));
++	}
++	/* interrupt enable */
++	mt76_wr(dev, MT_RRO_HOST_INT_ENA,
++		MT_RRO_HOST_INT_ENA_HOST_RRO_DONE_ENA);
++#endif
++}
++
++static int mt7996_wed_rro_init(struct mt7996_dev *dev)
++{
++#ifdef CONFIG_NET_MEDIATEK_SOC_WED
++	struct mtk_wed_device *wed = &dev->mt76.mmio.wed;
+ 	struct mt7996_wed_rro_addr *addr;
+ 	void *ptr;
+ 	int i;
+@@ -788,50 +868,9 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev)
+ 		addr++;
+ 	}
+ 
+-	/* rro hw init */
+-	/* TODO: remove line after WM has set */
+-	mt76_clear(dev, WF_RRO_AXI_MST_CFG, WF_RRO_AXI_MST_CFG_DIDX_OK);
+-
+-	/* setup BA bitmap cache address */
+-	mt76_wr(dev, MT_RRO_BA_BITMAP_BASE0,
+-		dev->wed_rro.ba_bitmap[0].phy_addr);
+-	mt76_wr(dev, MT_RRO_BA_BITMAP_BASE1, 0);
+-	mt76_wr(dev, MT_RRO_BA_BITMAP_BASE_EXT0,
+-		dev->wed_rro.ba_bitmap[1].phy_addr);
+-	mt76_wr(dev, MT_RRO_BA_BITMAP_BASE_EXT1, 0);
+-
+-	/* setup Address element address */
+-	for (i = 0; i < ARRAY_SIZE(dev->wed_rro.addr_elem); i++) {
+-		mt76_wr(dev, reg, dev->wed_rro.addr_elem[i].phy_addr >> 4);
+-		reg += 4;
+-	}
+-
+-	/* setup Address element address - separate address segment mode */
+-	mt76_wr(dev, MT_RRO_ADDR_ARRAY_BASE1,
+-		MT_RRO_ADDR_ARRAY_ELEM_ADDR_SEG_MODE);
+-
+-	wed->wlan.ind_cmd.win_size = ffs(MT7996_RRO_WINDOW_MAX_LEN) - 6;
+-	wed->wlan.ind_cmd.particular_sid = MT7996_RRO_MAX_SESSION;
+-	wed->wlan.ind_cmd.particular_se_phys = dev->wed_rro.session.phy_addr;
+-	wed->wlan.ind_cmd.se_group_nums = MT7996_RRO_ADDR_ELEM_LEN;
+-	wed->wlan.ind_cmd.ack_sn_addr = MT_RRO_ACK_SN_CTRL;
+-
+-	mt76_wr(dev, MT_RRO_IND_CMD_SIGNATURE_BASE0, 0x15010e00);
+-	mt76_set(dev, MT_RRO_IND_CMD_SIGNATURE_BASE1,
+-		 MT_RRO_IND_CMD_SIGNATURE_BASE1_EN);
+-
+-	/* particular session configure */
+-	/* use max session idx + 1 as particular session id */
+-	mt76_wr(dev, MT_RRO_PARTICULAR_CFG0, dev->wed_rro.session.phy_addr);
+-	mt76_wr(dev, MT_RRO_PARTICULAR_CFG1,
+-		MT_RRO_PARTICULAR_CONFG_EN |
+-		FIELD_PREP(MT_RRO_PARTICULAR_SID, MT7996_RRO_MAX_SESSION));
+-
+-	/* interrupt enable */
+-	mt76_wr(dev, MT_RRO_HOST_INT_ENA,
+-		MT_RRO_HOST_INT_ENA_HOST_RRO_DONE_ENA);
+-
+ 	/* rro ind cmd queue init */
++	mt7996_rro_hw_init(dev);
++
+ 	return mt7996_dma_rro_init(dev);
+ #else
+ 	return 0;
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 9b143433a..539c58ee3 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -1764,6 +1764,31 @@ mt7996_mac_restart(struct mt7996_dev *dev)
+ 	if (ret)
+ 		goto out;
+ 
++	if (mtk_wed_device_active(&dev->mt76.mmio.wed) && dev->has_rro) {
++		u32 wed_irq_mask = dev->mt76.mmio.irqmask |
++				   MT_INT_RRO_RX_DONE |
++				   MT_INT_TX_DONE_BAND2;
++
++		mt7996_rro_hw_init(dev);
++		mt76_for_each_q_rx(&dev->mt76, i) {
++			if (mt76_queue_is_wed_rro_ind(&dev->mt76.q_rx[i]) ||
++			    mt76_queue_is_wed_rro_msdu_pg(&dev->mt76.q_rx[i]))
++				mt76_queue_rx_reset(dev, i);
++		}
++
++		mt76_wr(dev, MT_INT_MASK_CSR, wed_irq_mask);
++		mtk_wed_device_start_hwrro(&dev->mt76.mmio.wed, wed_irq_mask, false);
++		mt7996_irq_enable(dev, wed_irq_mask);
++		mt7996_irq_disable(dev, 0);
++	}
++
++	if (mtk_wed_device_active(&dev->mt76.mmio.wed_hif2)) {
++		mt76_wr(dev, MT_INT_PCIE1_MASK_CSR,
++			MT_INT_TX_RX_DONE_EXT);
++		mtk_wed_device_start(&dev->mt76.mmio.wed_hif2,
++				     MT_INT_TX_RX_DONE_EXT);
++	}
++
+ 	/* set the necessary init items */
+ 	ret = mt7996_mcu_set_eeprom(dev);
+ 	if (ret)
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 984837c77..52c1ce037 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -713,6 +713,7 @@ extern const struct mt76_testmode_ops mt7996_testmode_ops;
+ struct mt7996_dev *mt7996_mmio_probe(struct device *pdev,
+ 				     void __iomem *mem_base, u32 device_id);
+ void mt7996_wfsys_reset(struct mt7996_dev *dev);
++void mt7996_rro_hw_init(struct mt7996_dev *dev);
+ irqreturn_t mt7996_irq_handler(int irq, void *dev_instance);
+ u64 __mt7996_get_tsf(struct ieee80211_hw *hw, struct mt7996_vif *mvif);
+ int mt7996_register_device(struct mt7996_dev *dev);
+diff --git a/wed.c b/wed.c
+index 1c6d53c84..61a6badf2 100644
+--- a/wed.c
++++ b/wed.c
+@@ -155,7 +155,7 @@ int mt76_wed_dma_setup(struct mt76_dev *dev, struct mt76_queue *q, bool reset)
+ 	case MT76_WED_Q_TXFREE:
+ 		/* WED txfree queue needs ring to be initialized before setup */
+ 		q->flags = 0;
+-		mt76_dma_queue_reset(dev, q);
++		mt76_dma_queue_reset(dev, q, true);
+ 		mt76_dma_rx_fill(dev, q, false);
+ 
+ 		ret = mtk_wed_device_txfree_ring_setup(q->wed, q->regs);
+@@ -184,7 +184,7 @@ int mt76_wed_dma_setup(struct mt76_dev *dev, struct mt76_queue *q, bool reset)
+ 		break;
+ 	case MT76_WED_RRO_Q_IND:
+ 		q->flags &= ~MT_QFLAG_WED;
+-		mt76_dma_queue_reset(dev, q);
++		mt76_dma_queue_reset(dev, q, true);
+ 		mt76_dma_rx_fill(dev, q, false);
+ 		mtk_wed_device_ind_rx_ring_setup(q->wed, q->regs);
+ 		break;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0085-mtk-wifi-mt76-mt7996-support-backaward-compatiable.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0085-mtk-wifi-mt76-mt7996-support-backaward-compatiable.patch
new file mode 100644
index 0000000..ac6f314
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0085-mtk-wifi-mt76-mt7996-support-backaward-compatiable.patch
@@ -0,0 +1,212 @@
+From 40fb33b9400c46342e982d755401618b7670669b Mon Sep 17 00:00:00 2001
+From: mtk27745 <rex.lu@mediatek.com>
+Date: Fri, 6 Oct 2023 20:59:42 +0800
+Subject: [PATCH 085/120] mtk: wifi: mt76: mt7996: support backaward
+ compatiable
+
+revert upstream wed trigger mode to polling mode
+
+CR-Id: WCNCR00259341
+Signed-off-by: mtk27745 <rex.lu@mediatek.com>
+Change-Id: Ifd40df7094052b13e26f42f09908f6404917ad8e
+
+[Description]
+Change the SW token size from 1024 to 15360 according to HW capability.
+
+[Release-log]
+N/A
+
+CR-Id: WCNCR00240772
+Change-Id: I337f41aa80758d00b4c8e7a5cc4d6faeb6f0a4a2
+Signed-off-by: Peter Chiu <chui-hao.chiu@mediatek.com>
+Signed-off-by: Rex Lu <rex.lu@mediatek.com>
+---
+ mt76.h          |  2 ++
+ mt7996/mac.c    |  3 ++-
+ mt7996/mcu.c    |  2 +-
+ mt7996/mmio.c   | 12 +++++++-----
+ mt7996/mt7996.h |  1 +
+ mt7996/pci.c    | 17 +++++++++--------
+ wed.c           |  4 ++--
+ 7 files changed, 24 insertions(+), 17 deletions(-)
+
+diff --git a/mt76.h b/mt76.h
+index 6ac8a279f..49b66ff25 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -48,6 +48,8 @@
+ 
+ #define MT76_TOKEN_FREE_THR	64
+ 
++#define MT76_WED_SW_TOKEN_SIZE	15360
++
+ #define MT_QFLAG_WED_RING	GENMASK(1, 0)
+ #define MT_QFLAG_WED_TYPE	GENMASK(4, 2)
+ #define MT_QFLAG_WED		BIT(5)
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 539c58ee3..f6812a889 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -1777,7 +1777,7 @@ mt7996_mac_restart(struct mt7996_dev *dev)
+ 		}
+ 
+ 		mt76_wr(dev, MT_INT_MASK_CSR, wed_irq_mask);
+-		mtk_wed_device_start_hwrro(&dev->mt76.mmio.wed, wed_irq_mask, false);
++		mtk_wed_device_start_hw_rro(&dev->mt76.mmio.wed, wed_irq_mask, false);
+ 		mt7996_irq_enable(dev, wed_irq_mask);
+ 		mt7996_irq_disable(dev, 0);
+ 	}
+@@ -2009,6 +2009,7 @@ void mt7996_mac_reset_work(struct work_struct *work)
+ 
+ 		mtk_wed_device_start_hw_rro(&dev->mt76.mmio.wed, wed_irq_mask,
+ 					    true);
++
+ 		mt7996_irq_enable(dev, wed_irq_mask);
+ 		mt7996_irq_disable(dev, 0);
+ 	}
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index adba9c770..fa9090904 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -3254,7 +3254,7 @@ static int mt7996_mcu_wa_red_config(struct mt7996_dev *dev)
+ 
+ 	if (!mtk_wed_device_active(&dev->mt76.mmio.wed))
+ 		req.token_per_src[RED_TOKEN_SRC_CNT - 1] =
+-			cpu_to_le16(MT7996_TOKEN_SIZE - MT7996_HW_TOKEN_SIZE);
++			cpu_to_le16(MT7996_SW_TOKEN_SIZE);
+ 
+ 	return mt76_mcu_send_msg(&dev->mt76, MCU_WA_PARAM_CMD(SET),
+ 				 &req, sizeof(req), false);
+diff --git a/mt7996/mmio.c b/mt7996/mmio.c
+index 92ae5138e..eef70faf4 100644
+--- a/mt7996/mmio.c
++++ b/mt7996/mmio.c
+@@ -14,7 +14,7 @@
+ #include "../trace.h"
+ #include "../dma.h"
+ 
+-static bool wed_enable;
++static bool wed_enable = true;
+ module_param(wed_enable, bool, 0644);
+ 
+ static const struct __base mt7996_reg_base[] = {
+@@ -347,7 +347,7 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr,
+ 		}
+ 
+ 		wed->wlan.wpdma_rx_glo = wed->wlan.phy_base + hif1_ofs + MT_WFDMA0_GLO_CFG;
+-		wed->wlan.wpdma_rx = wed->wlan.phy_base + hif1_ofs +
++		wed->wlan.wpdma_rx[0] = wed->wlan.phy_base + hif1_ofs +
+ 				     MT_RXQ_RING_BASE(MT7996_RXQ_BAND0) +
+ 				     MT7996_RXQ_BAND0 * MT_RING_SIZE;
+ 
+@@ -362,7 +362,7 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr,
+ 
+ 		wed->wlan.wpdma_rx_glo = wed->wlan.phy_base + MT_WFDMA0_GLO_CFG;
+ 
+-		wed->wlan.wpdma_rx = wed->wlan.phy_base +
++		wed->wlan.wpdma_rx[0] = wed->wlan.phy_base +
+ 				     MT_RXQ_RING_BASE(MT7996_RXQ_BAND0) +
+ 				     MT7996_RXQ_BAND0 * MT_RING_SIZE;
+ 
+@@ -404,8 +404,8 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr,
+ 		dev->mt76.rx_token_size = MT7996_TOKEN_SIZE + wed->wlan.rx_npkt;
+ 	}
+ 
+-	wed->wlan.nbuf = MT7996_HW_TOKEN_SIZE;
+-	wed->wlan.token_start = MT7996_TOKEN_SIZE - wed->wlan.nbuf;
++	wed->wlan.nbuf = MT7996_TOKEN_SIZE;
++	wed->wlan.token_start = 0;
+ 
+ 	wed->wlan.amsdu_max_subframes = 8;
+ 	wed->wlan.amsdu_max_len = 1536;
+@@ -426,6 +426,8 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr,
+ 	*irq = wed->irq;
+ 	dev->mt76.dma_dev = wed->dev;
+ 
++	dev->mt76.token_size = MT7996_SW_TOKEN_SIZE;
++
+ 	return 1;
+ #else
+ 	return 0;
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 52c1ce037..d2b0a93d2 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -74,6 +74,7 @@
+ #define MT7996_EEPROM_BLOCK_SIZE	16
+ #define MT7996_TOKEN_SIZE		16384
+ #define MT7996_HW_TOKEN_SIZE		8192
++#define MT7996_SW_TOKEN_SIZE		15360
+ 
+ #define MT7996_CFEND_RATE_DEFAULT	0x49	/* OFDM 24M */
+ #define MT7996_CFEND_RATE_11B		0x03	/* 11B LP, 11M */
+diff --git a/mt7996/pci.c b/mt7996/pci.c
+index 05830c01c..4e957771f 100644
+--- a/mt7996/pci.c
++++ b/mt7996/pci.c
+@@ -171,7 +171,7 @@ static int mt7996_pci_probe(struct pci_dev *pdev,
+ 
+ 		ret = mt7996_mmio_wed_init(dev, hif2_dev, true, &hif2_irq);
+ 		if (ret < 0)
+-			goto free_hif2_wed_irq_vector;
++			goto free_wed_or_irq_vector;
+ 
+ 		if (!ret) {
+ 			ret = pci_alloc_irq_vectors(hif2_dev, 1, 1,
+@@ -180,14 +180,15 @@ static int mt7996_pci_probe(struct pci_dev *pdev,
+ 				goto free_hif2;
+ 
+ 			dev->hif2->irq = hif2_dev->irq;
+-			hif2_irq = dev->hif2->irq;
++		} else {
++			dev->hif2->irq = irq;
+ 		}
+ 
+-		ret = devm_request_irq(mdev->dev, hif2_irq, mt7996_irq_handler,
+-				       IRQF_SHARED, KBUILD_MODNAME "-hif",
+-				       dev);
++		ret = devm_request_irq(mdev->dev, dev->hif2->irq,
++				       mt7996_irq_handler, IRQF_SHARED,
++				       KBUILD_MODNAME "-hif", dev);
+ 		if (ret)
+-			goto free_hif2_wed_irq_vector;
++			goto free_hif2_irq_vector;
+ 
+ 		mt76_wr(dev, MT_INT1_MASK_CSR, 0);
+ 		/* master switch of PCIe tnterrupt enable */
+@@ -202,8 +203,8 @@ static int mt7996_pci_probe(struct pci_dev *pdev,
+ 
+ free_hif2_irq:
+ 	if (dev->hif2)
+-		devm_free_irq(mdev->dev, hif2_irq, dev);
+-free_hif2_wed_irq_vector:
++		devm_free_irq(mdev->dev, dev->hif2->irq, dev);
++free_hif2_irq_vector:
+ 	if (dev->hif2) {
+ 		if (mtk_wed_device_active(&dev->mt76.mmio.wed_hif2))
+ 			mtk_wed_device_detach(&dev->mt76.mmio.wed_hif2);
+diff --git a/wed.c b/wed.c
+index 61a6badf2..634c95cf9 100644
+--- a/wed.c
++++ b/wed.c
+@@ -120,7 +120,7 @@ int mt76_wed_offload_enable(struct mtk_wed_device *wed)
+ 	struct mt76_dev *dev = container_of(wed, struct mt76_dev, mmio.wed);
+ 
+ 	spin_lock_bh(&dev->token_lock);
+-	dev->token_size = wed->wlan.token_start;
++	dev->token_size = MT76_WED_SW_TOKEN_SIZE;
+ 	spin_unlock_bh(&dev->token_lock);
+ 
+ 	return !wait_event_timeout(dev->tx_wait, !dev->wed_token_count, HZ);
+@@ -204,7 +204,7 @@ void mt76_wed_offload_disable(struct mtk_wed_device *wed)
+ 	struct mt76_dev *dev = container_of(wed, struct mt76_dev, mmio.wed);
+ 
+ 	spin_lock_bh(&dev->token_lock);
+-	dev->token_size = dev->drv->token_size;
++	dev->token_size = MT76_WED_SW_TOKEN_SIZE;
+ 	spin_unlock_bh(&dev->token_lock);
+ }
+ EXPORT_SYMBOL_GPL(mt76_wed_offload_disable);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0086-mtk-wifi-mt76-mt7996-wed-add-wed-support-for-mt7992.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0086-mtk-wifi-mt76-mt7996-wed-add-wed-support-for-mt7992.patch
new file mode 100644
index 0000000..f873645
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0086-mtk-wifi-mt76-mt7996-wed-add-wed-support-for-mt7992.patch
@@ -0,0 +1,434 @@
+From 7ca45a685eb317e951eeeef4b64e066c31150123 Mon Sep 17 00:00:00 2001
+From: "sujuan.chen" <sujuan.chen@mediatek.com>
+Date: Fri, 8 Sep 2023 11:57:39 +0800
+Subject: [PATCH 086/120] mtk: wifi: mt76: mt7996: wed: add wed support for
+ mt7992
+
+Signed-off-by: sujuan.chen <sujuan.chen@mediatek.com>
+
+Fix incomplete WED initialization for Kite band-1 RX ring.
+
+CR-Id: WCNCR00298425
+Signed-off-by: Benjamin Lin <benjamin-jw.lin@mediatek.com>
+Change-Id: I2da06c9f1412f8392d1b55feea3ad8ff48ff90ad
+---
+ mt7996/dma.c    | 91 +++++++++++++++++++++++++++++++++----------------
+ mt7996/init.c   | 12 +++++++
+ mt7996/mac.c    |  4 +++
+ mt7996/mmio.c   | 49 ++++++++++++++++++--------
+ mt7996/mt7996.h | 10 +++++-
+ mt7996/pci.c    | 10 ++++--
+ mt7996/regs.h   | 14 +++++++-
+ 7 files changed, 142 insertions(+), 48 deletions(-)
+
+diff --git a/mt7996/dma.c b/mt7996/dma.c
+index d9e1b17ff..d62dc8ba9 100644
+--- a/mt7996/dma.c
++++ b/mt7996/dma.c
+@@ -77,18 +77,23 @@ static void mt7996_dma_config(struct mt7996_dev *dev)
+ 			   MT7996_RXQ_RRO_BAND0);
+ 		RXQ_CONFIG(MT_RXQ_MSDU_PAGE_BAND0, WFDMA0, MT_INT_RX_DONE_MSDU_PG_BAND0,
+ 			   MT7996_RXQ_MSDU_PG_BAND0);
+-		RXQ_CONFIG(MT_RXQ_TXFREE_BAND0, WFDMA0, MT_INT_RX_TXFREE_MAIN,
+-			   MT7996_RXQ_TXFREE0);
+-		/* band1 */
+-		RXQ_CONFIG(MT_RXQ_MSDU_PAGE_BAND1, WFDMA0, MT_INT_RX_DONE_MSDU_PG_BAND1,
+-			   MT7996_RXQ_MSDU_PG_BAND1);
+-		/* band2 */
+-		RXQ_CONFIG(MT_RXQ_RRO_BAND2, WFDMA0, MT_INT_RX_DONE_RRO_BAND2,
+-			   MT7996_RXQ_RRO_BAND2);
+-		RXQ_CONFIG(MT_RXQ_MSDU_PAGE_BAND2, WFDMA0, MT_INT_RX_DONE_MSDU_PG_BAND2,
+-			   MT7996_RXQ_MSDU_PG_BAND2);
+-		RXQ_CONFIG(MT_RXQ_TXFREE_BAND2, WFDMA0, MT_INT_RX_TXFREE_TRI,
+-			   MT7996_RXQ_TXFREE2);
++		if (is_mt7996(&dev->mt76)) {
++			RXQ_CONFIG(MT_RXQ_TXFREE_BAND0, WFDMA0, MT_INT_RX_TXFREE_MAIN,
++				   MT7996_RXQ_TXFREE0);
++			/* band1 */
++			RXQ_CONFIG(MT_RXQ_MSDU_PAGE_BAND1, WFDMA0, MT_INT_RX_DONE_MSDU_PG_BAND1,
++				   MT7996_RXQ_MSDU_PG_BAND1);
++			/* band2 */
++			RXQ_CONFIG(MT_RXQ_RRO_BAND2, WFDMA0, MT_INT_RX_DONE_RRO_BAND2,
++				   MT7996_RXQ_RRO_BAND2);
++			RXQ_CONFIG(MT_RXQ_MSDU_PAGE_BAND2, WFDMA0, MT_INT_RX_DONE_MSDU_PG_BAND2,
++				   MT7996_RXQ_MSDU_PG_BAND2);
++			RXQ_CONFIG(MT_RXQ_TXFREE_BAND2, WFDMA0, MT_INT_RX_TXFREE_TRI,
++				   MT7996_RXQ_TXFREE2);
++		} else {
++			RXQ_CONFIG(MT_RXQ_RRO_BAND1, WFDMA0, MT_INT_RX_DONE_RRO_BAND1,
++				   MT7996_RXQ_RRO_BAND1);
++		}
+ 
+ 		RXQ_CONFIG(MT_RXQ_RRO_IND, WFDMA0, MT_INT_RX_DONE_RRO_IND,
+ 			   MT7996_RXQ_RRO_IND);
+@@ -146,8 +151,13 @@ static void __mt7996_dma_prefetch(struct mt7996_dev *dev, u32 ofs)
+ 	if (dev->has_rro) {
+ 		mt76_wr(dev, MT_RXQ_BAND1_CTRL(MT_RXQ_RRO_BAND0) + ofs,
+ 			PREFETCH(0x10));
+-		mt76_wr(dev, MT_RXQ_BAND1_CTRL(MT_RXQ_RRO_BAND2) + ofs,
+-			PREFETCH(0x10));
++		if (is_mt7996(&dev->mt76))
++			mt76_wr(dev, MT_RXQ_BAND1_CTRL(MT_RXQ_RRO_BAND2) + ofs,
++				PREFETCH(0x10));
++		else
++			mt76_wr(dev, MT_RXQ_BAND1_CTRL(MT_RXQ_RRO_BAND1) + ofs,
++				PREFETCH(0x10));
++
+ 		mt76_wr(dev, MT_RXQ_BAND1_CTRL(MT_RXQ_MSDU_PAGE_BAND0) + ofs,
+ 			PREFETCH(0x4));
+ 		mt76_wr(dev, MT_RXQ_BAND1_CTRL(MT_RXQ_MSDU_PAGE_BAND1) + ofs,
+@@ -361,12 +371,16 @@ static void mt7996_dma_enable(struct mt7996_dev *dev, bool reset)
+ 		 * so, redirect pcie0 rx ring3 interrupt to pcie1
+ 		 */
+ 		if (mtk_wed_device_active(&dev->mt76.mmio.wed) &&
+-		    dev->has_rro)
++		    dev->has_rro) {
++			u32 intr = is_mt7996(&dev->mt76) ?
++				   MT_WFDMA0_RX_INT_SEL_RING6 :
++				   MT_WFDMA0_RX_INT_SEL_RING9;
+ 			mt76_set(dev, MT_WFDMA0_RX_INT_PCIE_SEL + hif1_ofs,
+-				 MT_WFDMA0_RX_INT_SEL_RING6);
+-		else
++				 intr);
++		} else {
+ 			mt76_set(dev, MT_WFDMA0_RX_INT_PCIE_SEL,
+ 				 MT_WFDMA0_RX_INT_SEL_RING3);
++		}
+ 	}
+ 
+ 	mt7996_dma_start(dev, reset, true);
+@@ -401,7 +415,7 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev)
+ 	if (ret)
+ 		return ret;
+ 
+-	if (mt7996_band_valid(dev, MT_BAND1)) {
++	if (mt7996_band_valid(dev, MT_BAND1) && is_mt7996(&dev->mt76)) {
+ 		/* rx msdu page queue for band1 */
+ 		mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND1].flags =
+ 			MT_WED_RRO_Q_MSDU_PG(1) | MT_QFLAG_WED_RRO_EN;
+@@ -522,7 +536,9 @@ int mt7996_dma_init(struct mt7996_dev *dev)
+ 		return ret;
+ 
+ 	/* tx free notify event from WA for band0 */
+-	if (mtk_wed_device_active(wed) && !dev->has_rro) {
++	if (mtk_wed_device_active(wed) &&
++	    ((is_mt7996(&dev->mt76) && !dev->has_rro) ||
++	     (is_mt7992(&dev->mt76)))) {
+ 		dev->mt76.q_rx[MT_RXQ_MAIN_WA].flags = MT_WED_Q_TXFREE;
+ 		dev->mt76.q_rx[MT_RXQ_MAIN_WA].wed = wed;
+ 	}
+@@ -568,6 +584,11 @@ int mt7996_dma_init(struct mt7996_dev *dev)
+ 	} else if (mt7996_band_valid(dev, MT_BAND1)) {
+ 		/* rx data queue for mt7992 band1 */
+ 		rx_base = MT_RXQ_RING_BASE(MT_RXQ_BAND1) + hif1_ofs;
++		if (mtk_wed_device_active(wed) && mtk_wed_get_rx_capa(wed)) {
++			dev->mt76.q_rx[MT_RXQ_BAND1].flags = MT_WED_Q_RX(1);
++			dev->mt76.q_rx[MT_RXQ_BAND1].wed = wed;
++		}
++
+ 		ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_BAND1],
+ 				       MT_RXQ_ID(MT_RXQ_BAND1),
+ 				       MT7996_RX_RING_SIZE,
+@@ -601,17 +622,29 @@ int mt7996_dma_init(struct mt7996_dev *dev)
+ 		if (ret)
+ 			return ret;
+ 
+-		/* tx free notify event from WA for band0 */
+-		dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].flags = MT_WED_Q_TXFREE;
+-		dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].wed = wed;
++		if (is_mt7992(&dev->mt76)) {
++			dev->mt76.q_rx[MT_RXQ_RRO_BAND1].flags =
++				MT_WED_RRO_Q_DATA(1) | MT_QFLAG_WED_RRO_EN;
++			dev->mt76.q_rx[MT_RXQ_RRO_BAND1].wed = wed;
++			ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_RRO_BAND1],
++					       MT_RXQ_ID(MT_RXQ_RRO_BAND1),
++					       MT7996_RX_RING_SIZE,
++					       MT7996_RX_BUF_SIZE,
++					       MT_RXQ_RING_BASE(MT_RXQ_RRO_BAND1));
++			if (ret)
++				return ret;
++		} else {
++			dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].flags = MT_WED_Q_TXFREE;
++			dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].wed = wed;
+ 
+-		ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0],
+-				       MT_RXQ_ID(MT_RXQ_TXFREE_BAND0),
+-				       MT7996_RX_MCU_RING_SIZE,
+-				       MT7996_RX_BUF_SIZE,
+-				       MT_RXQ_RING_BASE(MT_RXQ_TXFREE_BAND0));
+-		if (ret)
+-			return ret;
++			ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0],
++					       MT_RXQ_ID(MT_RXQ_TXFREE_BAND0),
++					       MT7996_RX_MCU_RING_SIZE,
++					       MT7996_RX_BUF_SIZE,
++					       MT_RXQ_RING_BASE(MT_RXQ_TXFREE_BAND0));
++			if (ret)
++				return ret;
++		}
+ 
+ 		if (mt7996_band_valid(dev, MT_BAND2)) {
+ 			/* rx rro data queue for band2 */
+diff --git a/mt7996/init.c b/mt7996/init.c
+index a97201203..7a9b97490 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -802,6 +802,7 @@ void mt7996_rro_hw_init(struct mt7996_dev *dev)
+ 	/* interrupt enable */
+ 	mt76_wr(dev, MT_RRO_HOST_INT_ENA,
+ 		MT_RRO_HOST_INT_ENA_HOST_RRO_DONE_ENA);
++
+ #endif
+ }
+ 
+@@ -854,6 +855,17 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev)
+ 			dev->wed_rro.addr_elem[i].phy_addr;
+ 	}
+ 
++	for (i = 0; i < MT7996_RRO_MSDU_PG_CR_CNT; i++) {
++		ptr = dmam_alloc_coherent(dev->mt76.dma_dev, MT7996_RRO_MSDU_PG_SIZE_PER_CR,
++					  &dev->wed_rro.msdu_pg[i].phy_addr,
++					  GFP_KERNEL);
++		if (!ptr)
++			return -ENOMEM;
++		dev->wed_rro.msdu_pg[i].ptr = ptr;
++
++		memset(dev->wed_rro.msdu_pg[i].ptr, 0, MT7996_RRO_MSDU_PG_SIZE_PER_CR);
++	}
++
+ 	ptr = dmam_alloc_coherent(dev->mt76.dma_dev,
+ 				  MT7996_RRO_WINDOW_MAX_LEN * sizeof(*addr),
+ 				  &dev->wed_rro.session.phy_addr,
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index f6812a889..9d1862e88 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -2007,6 +2007,10 @@ void mt7996_mac_reset_work(struct work_struct *work)
+ 
+ 		mt76_wr(dev, MT_INT_MASK_CSR, wed_irq_mask);
+ 
++		if (is_mt7992(&dev->mt76) && dev->has_rro)
++			mt76_wr(dev, MT_RRO_3_0_EMU_CONF,
++				MT_RRO_3_0_EMU_CONF_EN_MASK);
++
+ 		mtk_wed_device_start_hw_rro(&dev->mt76.mmio.wed, wed_irq_mask,
+ 					    true);
+ 
+diff --git a/mt7996/mmio.c b/mt7996/mmio.c
+index eef70faf4..e23c79fc7 100644
+--- a/mt7996/mmio.c
++++ b/mt7996/mmio.c
+@@ -313,7 +313,8 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr,
+ 
+ 	dev->has_rro = true;
+ 
+-	hif1_ofs = MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0);
++	if (dev->hif2)
++		hif1_ofs = MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0);
+ 
+ 	if (hif2)
+ 		wed = &dev->mt76.mmio.wed_hif2;
+@@ -348,8 +349,8 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr,
+ 
+ 		wed->wlan.wpdma_rx_glo = wed->wlan.phy_base + hif1_ofs + MT_WFDMA0_GLO_CFG;
+ 		wed->wlan.wpdma_rx[0] = wed->wlan.phy_base + hif1_ofs +
+-				     MT_RXQ_RING_BASE(MT7996_RXQ_BAND0) +
+-				     MT7996_RXQ_BAND0 * MT_RING_SIZE;
++				     MT_RXQ_RING_BASE(MT7996_RXQ_BAND2) +
++				     MT7996_RXQ_BAND2 * MT_RING_SIZE;
+ 
+ 		wed->wlan.id = 0x7991;
+ 		wed->wlan.tx_tbit[0] = ffs(MT_INT_TX_DONE_BAND2) - 1;
+@@ -369,9 +370,19 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr,
+ 		wed->wlan.wpdma_rx_rro[0] = wed->wlan.phy_base +
+ 					    MT_RXQ_RING_BASE(MT7996_RXQ_RRO_BAND0) +
+ 					    MT7996_RXQ_RRO_BAND0 * MT_RING_SIZE;
+-		wed->wlan.wpdma_rx_rro[1] = wed->wlan.phy_base + hif1_ofs +
+-					    MT_RXQ_RING_BASE(MT7996_RXQ_RRO_BAND2) +
+-					    MT7996_RXQ_RRO_BAND2 * MT_RING_SIZE;
++		if (is_mt7996(&dev->mt76)) {
++			wed->wlan.wpdma_rx_rro[1] = wed->wlan.phy_base + hif1_ofs +
++						    MT_RXQ_RING_BASE(MT7996_RXQ_RRO_BAND2) +
++						    MT7996_RXQ_RRO_BAND2 * MT_RING_SIZE;
++		} else {
++			wed->wlan.wpdma_rx_rro[1] = wed->wlan.phy_base +
++						    MT_RXQ_RING_BASE(MT7996_RXQ_RRO_BAND1) +
++						    MT7996_RXQ_RRO_BAND1 * MT_RING_SIZE;
++			wed->wlan.wpdma_rx[1] = wed->wlan.phy_base +
++						MT_RXQ_RING_BASE(MT7996_RXQ_BAND1) +
++						MT7996_RXQ_BAND1 * MT_RING_SIZE;
++		}
++
+ 		wed->wlan.wpdma_rx_pg = wed->wlan.phy_base +
+ 					MT_RXQ_RING_BASE(MT7996_RXQ_MSDU_PG_BAND0) +
+ 					MT7996_RXQ_MSDU_PG_BAND0 * MT_RING_SIZE;
+@@ -381,10 +392,14 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr,
+ 		wed->wlan.rx_size = SKB_WITH_OVERHEAD(MT_RX_BUF_SIZE);
+ 
+ 		wed->wlan.rx_tbit[0] = ffs(MT_INT_RX_DONE_BAND0) - 1;
+-		wed->wlan.rx_tbit[1] = ffs(MT_INT_RX_DONE_BAND2) - 1;
+-
+ 		wed->wlan.rro_rx_tbit[0] = ffs(MT_INT_RX_DONE_RRO_BAND0) - 1;
+-		wed->wlan.rro_rx_tbit[1] = ffs(MT_INT_RX_DONE_RRO_BAND2) - 1;
++		if (is_mt7996(&dev->mt76)) {
++			wed->wlan.rx_tbit[1] = ffs(MT_INT_RX_DONE_BAND2) - 1;
++			wed->wlan.rro_rx_tbit[1] = ffs(MT_INT_RX_DONE_RRO_BAND2) - 1;
++		} else {
++			wed->wlan.rx_tbit[1] = ffs(MT_INT_RX_DONE_BAND1) - 1;
++			wed->wlan.rro_rx_tbit[1] = ffs(MT_INT_RX_DONE_RRO_BAND1) - 1;
++		}
+ 
+ 		wed->wlan.rx_pg_tbit[0] = ffs(MT_INT_RX_DONE_MSDU_PG_BAND0) - 1;
+ 		wed->wlan.rx_pg_tbit[1] = ffs(MT_INT_RX_DONE_MSDU_PG_BAND1) - 1;
+@@ -392,14 +407,20 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr,
+ 
+ 		wed->wlan.tx_tbit[0] = ffs(MT_INT_TX_DONE_BAND0) - 1;
+ 		wed->wlan.tx_tbit[1] = ffs(MT_INT_TX_DONE_BAND1) - 1;
+-		if (dev->has_rro) {
+-			wed->wlan.wpdma_txfree = wed->wlan.phy_base + MT_RXQ_RING_BASE(0) +
+-						 MT7996_RXQ_TXFREE0 * MT_RING_SIZE;
+-			wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_MAIN) - 1;
++		if (is_mt7996(&dev->mt76)) {
++			if (dev->has_rro) {
++				wed->wlan.wpdma_txfree = wed->wlan.phy_base + MT_RXQ_RING_BASE(0) +
++							 MT7996_RXQ_TXFREE0 * MT_RING_SIZE;
++				wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_MAIN) - 1;
++			} else {
++				wed->wlan.txfree_tbit = ffs(MT_INT_RX_DONE_WA_MAIN) - 1;
++				wed->wlan.wpdma_txfree = wed->wlan.phy_base + MT_RXQ_RING_BASE(0) +
++							 MT7996_RXQ_MCU_WA_MAIN * MT_RING_SIZE;
++			}
+ 		} else {
+ 			wed->wlan.txfree_tbit = ffs(MT_INT_RX_DONE_WA_MAIN) - 1;
+ 			wed->wlan.wpdma_txfree = wed->wlan.phy_base + MT_RXQ_RING_BASE(0) +
+-						  MT7996_RXQ_MCU_WA_MAIN * MT_RING_SIZE;
++						 MT7996_RXQ_MCU_WA_MAIN * MT_RING_SIZE;
+ 		}
+ 		dev->mt76.rx_token_size = MT7996_TOKEN_SIZE + wed->wlan.rx_npkt;
+ 	}
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index d2b0a93d2..23e9de03b 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -122,6 +122,10 @@
+ #define MT7996_DRR_STA_AC2_QNTM_MASK	GENMASK(18, 16)
+ #define MT7996_DRR_STA_AC3_QNTM_MASK	GENMASK(22, 20)
+ 
++/* RRO 3.1 */
++#define MT7996_RRO_MSDU_PG_CR_CNT 8
++#define MT7996_RRO_MSDU_PG_SIZE_PER_CR 0x10000
++
+ struct mt7996_vif;
+ struct mt7996_sta;
+ struct mt7996_dfs_pulse;
+@@ -181,7 +185,7 @@ enum mt7996_rxq_id {
+ 	MT7996_RXQ_BAND1 = 5, /* for mt7992 */
+ 	MT7996_RXQ_BAND2 = 5,
+ 	MT7996_RXQ_RRO_BAND0 = 8,
+-	MT7996_RXQ_RRO_BAND1 = 8,/* unused */
++	MT7996_RXQ_RRO_BAND1 = 9,
+ 	MT7996_RXQ_RRO_BAND2 = 6,
+ 	MT7996_RXQ_MSDU_PG_BAND0 = 10,
+ 	MT7996_RXQ_MSDU_PG_BAND1 = 11,
+@@ -541,6 +545,10 @@ struct mt7996_dev {
+ 			void *ptr;
+ 			dma_addr_t phy_addr;
+ 		} session;
++		struct {
++			void *ptr;
++			dma_addr_t phy_addr;
++		} msdu_pg[MT7996_RRO_MSDU_PG_CR_CNT];
+ 
+ 		struct work_struct work;
+ 		struct list_head poll_list;
+diff --git a/mt7996/pci.c b/mt7996/pci.c
+index 4e957771f..f0d3f199c 100644
+--- a/mt7996/pci.c
++++ b/mt7996/pci.c
+@@ -107,7 +107,7 @@ static int mt7996_pci_probe(struct pci_dev *pdev,
+ 	struct pci_dev *hif2_dev;
+ 	struct mt7996_hif *hif2;
+ 	struct mt7996_dev *dev;
+-	int irq, hif2_irq, ret;
++	int irq, ret;
+ 	struct mt76_dev *mdev;
+ 
+ 	hif2_enable |= (id->device == 0x7990 || id->device == 0x7991);
+@@ -143,6 +143,8 @@ static int mt7996_pci_probe(struct pci_dev *pdev,
+ 	mdev = &dev->mt76;
+ 	mt7996_wfsys_reset(dev);
+ 	hif2 = mt7996_pci_init_hif2(pdev);
++	if (hif2)
++		dev->hif2 = hif2;
+ 
+ 	ret = mt7996_mmio_wed_init(dev, pdev, false, &irq);
+ 	if (ret < 0)
+@@ -167,9 +169,11 @@ static int mt7996_pci_probe(struct pci_dev *pdev,
+ 
+ 	if (hif2) {
+ 		hif2_dev = container_of(hif2->dev, struct pci_dev, dev);
+-		dev->hif2 = hif2;
++		ret = 0;
++
++		if (is_mt7996(&dev->mt76))
++			ret = mt7996_mmio_wed_init(dev, hif2_dev, true, &irq);
+ 
+-		ret = mt7996_mmio_wed_init(dev, hif2_dev, true, &hif2_irq);
+ 		if (ret < 0)
+ 			goto free_wed_or_irq_vector;
+ 
+diff --git a/mt7996/regs.h b/mt7996/regs.h
+index 8d1462a7d..352d1b292 100644
+--- a/mt7996/regs.h
++++ b/mt7996/regs.h
+@@ -77,6 +77,8 @@ enum offs_rev {
+ #define MT_RRO_BA_BITMAP_BASE1			MT_RRO_TOP(0xC)
+ #define WF_RRO_AXI_MST_CFG			MT_RRO_TOP(0xB8)
+ #define WF_RRO_AXI_MST_CFG_DIDX_OK		BIT(12)
++
++#define MT_RRO_ADDR_ARRAY_BASE0			MT_RRO_TOP(0x30)
+ #define MT_RRO_ADDR_ARRAY_BASE1			MT_RRO_TOP(0x34)
+ #define MT_RRO_ADDR_ARRAY_ELEM_ADDR_SEG_MODE	BIT(31)
+ 
+@@ -97,6 +99,14 @@ enum offs_rev {
+ 
+ #define MT_RRO_ADDR_ELEM_SEG_ADDR0		MT_RRO_TOP(0x400)
+ 
++#define MT_RRO_3_0_EMU_CONF			MT_RRO_TOP(0x600)
++#define MT_RRO_3_0_EMU_CONF_EN_MASK		BIT(11)
++
++#define MT_RRO_3_1_GLOBAL_CONFIG		MT_RRO_TOP(0x604)
++#define MT_RRO_3_1_GLOBAL_CONFIG_INTERLEAVE_EN	BIT(0)
++
++#define MT_RRO_MSDU_PG_SEG_ADDR0		MT_RRO_TOP(0x620)
++
+ #define MT_RRO_ACK_SN_CTRL			MT_RRO_TOP(0x50)
+ #define MT_RRO_ACK_SN_CTRL_SN_MASK		GENMASK(27, 16)
+ #define MT_RRO_ACK_SN_CTRL_SESSION_MASK		GENMASK(11, 0)
+@@ -402,6 +412,7 @@ enum offs_rev {
+ #define MT_WFDMA0_RX_INT_PCIE_SEL		MT_WFDMA0(0x154)
+ #define MT_WFDMA0_RX_INT_SEL_RING3		BIT(3)
+ #define MT_WFDMA0_RX_INT_SEL_RING6		BIT(6)
++#define MT_WFDMA0_RX_INT_SEL_RING9		BIT(9)
+ 
+ #define MT_WFDMA0_MCU_HOST_INT_ENA		MT_WFDMA0(0x1f4)
+ 
+@@ -503,13 +514,14 @@ enum offs_rev {
+ #define MT_INT_RX_DONE_WA_EXT			BIT(3) /* for mt7992 */
+ #define MT_INT_RX_DONE_WA_TRI			BIT(3)
+ #define MT_INT_RX_TXFREE_MAIN			BIT(17)
++#define MT_INT_RX_TXFREE_BAND1			BIT(15)
+ #define MT_INT_RX_TXFREE_TRI			BIT(15)
+ #define MT_INT_RX_DONE_BAND2_EXT		BIT(23)
+ #define MT_INT_RX_TXFREE_EXT			BIT(26)
+ #define MT_INT_MCU_CMD				BIT(29)
+ 
+ #define MT_INT_RX_DONE_RRO_BAND0		BIT(16)
+-#define MT_INT_RX_DONE_RRO_BAND1		BIT(16)
++#define MT_INT_RX_DONE_RRO_BAND1		BIT(17)
+ #define MT_INT_RX_DONE_RRO_BAND2		BIT(14)
+ #define MT_INT_RX_DONE_RRO_IND			BIT(11)
+ #define MT_INT_RX_DONE_MSDU_PG_BAND0		BIT(18)
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0087-mtk-wifi-mt76-mt7992-wed-add-2pcie-one-wed-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0087-mtk-wifi-mt76-mt7992-wed-add-2pcie-one-wed-support.patch
new file mode 100644
index 0000000..682ee83
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0087-mtk-wifi-mt76-mt7992-wed-add-2pcie-one-wed-support.patch
@@ -0,0 +1,177 @@
+From 60c33f349cd18d4785dead27ed0e0a0aa287fb7a Mon Sep 17 00:00:00 2001
+From: "sujuan.chen" <sujuan.chen@mediatek.com>
+Date: Wed, 13 Sep 2023 17:35:43 +0800
+Subject: [PATCH 087/120] mtk: wifi: mt76: mt7992: wed: add 2pcie one wed
+ support
+
+Signed-off-by: sujuan.chen <sujuan.chen@mediatek.com>
+---
+ mt7996/dma.c         | 13 +++++++++++--
+ mt7996/mmio.c        |  7 +++----
+ mt7996/mtk_debug.h   |  5 +++++
+ mt7996/mtk_debugfs.c | 25 ++++++++++++++++++-------
+ mt7996/regs.h        |  2 ++
+ 5 files changed, 39 insertions(+), 13 deletions(-)
+
+diff --git a/mt7996/dma.c b/mt7996/dma.c
+index d62dc8ba9..c23b0d651 100644
+--- a/mt7996/dma.c
++++ b/mt7996/dma.c
+@@ -355,6 +355,13 @@ static void mt7996_dma_enable(struct mt7996_dev *dev, bool reset)
+ 			 MT_WFDMA_HOST_CONFIG_PDMA_BAND |
+ 			 MT_WFDMA_HOST_CONFIG_BAND2_PCIE1);
+ 
++		if (mtk_wed_device_active(&dev->mt76.mmio.wed) &&
++		    is_mt7992(&dev->mt76)) {
++			mt76_set(dev, MT_WFDMA_HOST_CONFIG,
++				 MT_WFDMA_HOST_CONFIG_PDMA_BAND |
++				 MT_WFDMA_HOST_CONFIG_BAND1_PCIE1);
++		}
++
+ 		/* AXI read outstanding number */
+ 		mt76_rmw(dev, MT_WFDMA_AXI_R2A_CTRL,
+ 			 MT_WFDMA_AXI_R2A_CTRL_OUTSTAND_MASK, 0x14);
+@@ -374,7 +381,8 @@ static void mt7996_dma_enable(struct mt7996_dev *dev, bool reset)
+ 		    dev->has_rro) {
+ 			u32 intr = is_mt7996(&dev->mt76) ?
+ 				   MT_WFDMA0_RX_INT_SEL_RING6 :
+-				   MT_WFDMA0_RX_INT_SEL_RING9;
++				   MT_WFDMA0_RX_INT_SEL_RING9 |
++				   MT_WFDMA0_RX_INT_SEL_RING5;
+ 			mt76_set(dev, MT_WFDMA0_RX_INT_PCIE_SEL + hif1_ofs,
+ 				 intr);
+ 		} else {
+@@ -630,10 +638,11 @@ int mt7996_dma_init(struct mt7996_dev *dev)
+ 					       MT_RXQ_ID(MT_RXQ_RRO_BAND1),
+ 					       MT7996_RX_RING_SIZE,
+ 					       MT7996_RX_BUF_SIZE,
+-					       MT_RXQ_RING_BASE(MT_RXQ_RRO_BAND1));
++					       MT_RXQ_RING_BASE(MT_RXQ_RRO_BAND1) + hif1_ofs);
+ 			if (ret)
+ 				return ret;
+ 		} else {
++			/* tx free notify event from WA for band0 */
+ 			dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].flags = MT_WED_Q_TXFREE;
+ 			dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].wed = wed;
+ 
+diff --git a/mt7996/mmio.c b/mt7996/mmio.c
+index e23c79fc7..764c12445 100644
+--- a/mt7996/mmio.c
++++ b/mt7996/mmio.c
+@@ -375,10 +375,10 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr,
+ 						    MT_RXQ_RING_BASE(MT7996_RXQ_RRO_BAND2) +
+ 						    MT7996_RXQ_RRO_BAND2 * MT_RING_SIZE;
+ 		} else {
+-			wed->wlan.wpdma_rx_rro[1] = wed->wlan.phy_base +
++			wed->wlan.wpdma_rx_rro[1] = wed->wlan.phy_base + hif1_ofs +
+ 						    MT_RXQ_RING_BASE(MT7996_RXQ_RRO_BAND1) +
+ 						    MT7996_RXQ_RRO_BAND1 * MT_RING_SIZE;
+-			wed->wlan.wpdma_rx[1] = wed->wlan.phy_base +
++			wed->wlan.wpdma_rx[1] = wed->wlan.phy_base + hif1_ofs +
+ 						MT_RXQ_RING_BASE(MT7996_RXQ_BAND1) +
+ 						MT7996_RXQ_BAND1 * MT_RING_SIZE;
+ 		}
+@@ -516,10 +516,9 @@ void mt7996_dual_hif_set_irq_mask(struct mt7996_dev *dev, bool write_reg,
+ 		if (mtk_wed_device_active(&mdev->mmio.wed)) {
+ 			mtk_wed_device_irq_set_mask(&mdev->mmio.wed,
+ 						    mdev->mmio.irqmask);
+-			if (mtk_wed_device_active(&mdev->mmio.wed_hif2)) {
++			if (mtk_wed_device_active(&mdev->mmio.wed_hif2))
+ 				mtk_wed_device_irq_set_mask(&mdev->mmio.wed_hif2,
+ 							    mdev->mmio.irqmask);
+-			}
+ 		} else {
+ 			mt76_wr(dev, MT_INT_MASK_CSR, mdev->mmio.irqmask);
+ 			mt76_wr(dev, MT_INT1_MASK_CSR, mdev->mmio.irqmask);
+diff --git a/mt7996/mtk_debug.h b/mt7996/mtk_debug.h
+index 27d8f1cb2..da2a60723 100644
+--- a/mt7996/mtk_debug.h
++++ b/mt7996/mtk_debug.h
+@@ -561,6 +561,11 @@ struct queue_desc {
+ #define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING7_CTRL1_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x574) // 8574
+ #define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING7_CTRL2_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x578) // 8578
+ #define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING7_CTRL3_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x57c) // 857C
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING9_CTRL0_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x590) // 8590
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING9_CTRL1_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x594) // 8594
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING9_CTRL2_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x598) // 8598
++#define WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING9_CTRL3_ADDR     (WF_WFDMA_HOST_DMA0_PCIE1_BASE + 0x59c) // 859C
++
+ //MCU DMA
+ //#define WF_WFDMA_MCU_DMA0_BASE                                 0x02000
+ #define WF_WFDMA_MCU_DMA0_BASE                                 0x54000000
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index e49c33eac..737724bae 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -536,14 +536,22 @@ mt7996_show_dma_info(struct seq_file *s, struct mt7996_dev *dev)
+ 		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING4_CTRL0_ADDR);
+ 	dump_dma_rx_ring_info(s, dev, "R5:Data1(MAC2H)", "Both",
+ 		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING5_CTRL0_ADDR);
+-	dump_dma_rx_ring_info(s, dev, "R6:BUF1(MAC2H)", "Both",
+-		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING6_CTRL0_ADDR);
++	if (is_mt7996(&dev->mt76))
++		dump_dma_rx_ring_info(s, dev, "R6:BUF1(MAC2H)", "Both",
++			WF_WFDMA_HOST_DMA0_WPDMA_RX_RING6_CTRL0_ADDR);
++	else
++		dump_dma_rx_ring_info(s, dev, "R6:TxDone0(MAC2H)", "Both",
++			WF_WFDMA_HOST_DMA0_WPDMA_RX_RING6_CTRL0_ADDR);
+ 	dump_dma_rx_ring_info(s, dev, "R7:TxDone1(MAC2H)", "Both",
+ 		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING7_CTRL0_ADDR);
+ 	dump_dma_rx_ring_info(s, dev, "R8:BUF0(MAC2H)", "Both",
+ 		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING8_CTRL0_ADDR);
+-	dump_dma_rx_ring_info(s, dev, "R9:TxDone0(MAC2H)", "Both",
+-		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING9_CTRL0_ADDR);
++	if (is_mt7996(&dev->mt76))
++		dump_dma_rx_ring_info(s, dev, "R9:TxDone0(MAC2H)", "Both",
++			WF_WFDMA_HOST_DMA0_WPDMA_RX_RING9_CTRL0_ADDR);
++	else
++		dump_dma_rx_ring_info(s, dev, "R9:BUF0(MAC2H)", "Both",
++			WF_WFDMA_HOST_DMA0_WPDMA_RX_RING9_CTRL0_ADDR);
+ 	dump_dma_rx_ring_info(s, dev, "R10:MSDU_PG0(MAC2H)", "Both",
+ 		WF_WFDMA_HOST_DMA0_WPDMA_RX_RING10_CTRL0_ADDR);
+ 	dump_dma_rx_ring_info(s, dev, "R11:MSDU_PG1(MAC2H)", "Both",
+@@ -561,15 +569,18 @@ mt7996_show_dma_info(struct seq_file *s, struct mt7996_dev *dev)
+ 			WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_TX_RING21_CTRL0_ADDR);
+ 		dump_dma_tx_ring_info(s, dev, "T22:TXD?(H2WA)", "AP",
+ 			WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_TX_RING22_CTRL0_ADDR);
+-
+ 		dump_dma_rx_ring_info(s, dev, "R3:TxDone1(WA2H)", "AP",
+ 			WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING3_CTRL0_ADDR);
+ 		dump_dma_rx_ring_info(s, dev, "R5:Data1(MAC2H)", "Both",
+ 			WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING5_CTRL0_ADDR);
+-		dump_dma_rx_ring_info(s, dev, "R6:BUF1(MAC2H)", "Both",
+-			WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING6_CTRL0_ADDR);
++		if (is_mt7996(&dev->mt76))
++			dump_dma_rx_ring_info(s, dev, "R6:BUF1(MAC2H)", "Both",
++				WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING6_CTRL0_ADDR);
+ 		dump_dma_rx_ring_info(s, dev, "R7:TxDone1(MAC2H)", "Both",
+ 			WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING7_CTRL0_ADDR);
++		if (is_mt7992(&dev->mt76))
++			dump_dma_rx_ring_info(s, dev, "R9:BUF1(MAC2H)", "Both",
++				WF_WFDMA_HOST_DMA0_PCIE1_WPDMA_RX_RING9_CTRL0_ADDR);
+ 	}
+ 
+ 	/* MCU DMA information */
+diff --git a/mt7996/regs.h b/mt7996/regs.h
+index 352d1b292..a3b623390 100644
+--- a/mt7996/regs.h
++++ b/mt7996/regs.h
+@@ -411,6 +411,7 @@ enum offs_rev {
+ 
+ #define MT_WFDMA0_RX_INT_PCIE_SEL		MT_WFDMA0(0x154)
+ #define MT_WFDMA0_RX_INT_SEL_RING3		BIT(3)
++#define MT_WFDMA0_RX_INT_SEL_RING5		BIT(5)
+ #define MT_WFDMA0_RX_INT_SEL_RING6		BIT(6)
+ #define MT_WFDMA0_RX_INT_SEL_RING9		BIT(9)
+ 
+@@ -451,6 +452,7 @@ enum offs_rev {
+ 
+ #define MT_WFDMA_HOST_CONFIG			MT_WFDMA_EXT_CSR(0x30)
+ #define MT_WFDMA_HOST_CONFIG_PDMA_BAND		BIT(0)
++#define MT_WFDMA_HOST_CONFIG_BAND1_PCIE1	BIT(21)
+ #define MT_WFDMA_HOST_CONFIG_BAND2_PCIE1	BIT(22)
+ 
+ #define MT_WFDMA_EXT_CSR_HIF_MISC		MT_WFDMA_EXT_CSR(0x44)
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0088-mtk-wifi-mt76-mt7996-add-SER-state-log-for-debug.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0088-mtk-wifi-mt76-mt7996-add-SER-state-log-for-debug.patch
new file mode 100644
index 0000000..3b9d8ca
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0088-mtk-wifi-mt76-mt7996-add-SER-state-log-for-debug.patch
@@ -0,0 +1,28 @@
+From 4d703b5421cd64f2fc099b86b692297288643236 Mon Sep 17 00:00:00 2001
+From: Bo Jiao <Bo.Jiao@mediatek.com>
+Date: Mon, 6 Nov 2023 16:37:23 +0800
+Subject: [PATCH 088/120] mtk: wifi: mt76: mt7996: add SER state log for debug.
+
+CR-Id: WCNCR00334773
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+---
+ mt7996/mac.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 9d1862e88..b4703804b 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -2168,6 +2168,9 @@ void mt7996_coredump(struct mt7996_dev *dev, u8 state)
+ 
+ void mt7996_reset(struct mt7996_dev *dev)
+ {
++	dev_info(dev->mt76.dev, "%s SER recovery state: 0x%08x\n",
++		 wiphy_name(dev->mt76.hw->wiphy), READ_ONCE(dev->recovery.state));
++
+ 	if (!dev->recovery.hw_init_done)
+ 		return;
+ 
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0089-mtk-wifi-mt76-mt7996-Remove-wed-rro-ring-add-napi-at.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0089-mtk-wifi-mt76-mt7996-Remove-wed-rro-ring-add-napi-at.patch
new file mode 100644
index 0000000..10e5042
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0089-mtk-wifi-mt76-mt7996-Remove-wed-rro-ring-add-napi-at.patch
@@ -0,0 +1,32 @@
+From f6d83ef028c3a45bdcd02a4672b6bacb58dc2093 Mon Sep 17 00:00:00 2001
+From: mtk27745 <rex.lu@mediatek.com>
+Date: Mon, 6 Nov 2023 10:16:34 +0800
+Subject: [PATCH 089/120] mtk: wifi: mt76: mt7996: Remove wed rro ring add napi
+ at init state
+
+without this patch. rro ring will add napi at initial state. once rro ring add napi, it will have chance to be used by host driver. if host driver accessed the ring data, it will cause some issue.
+
+CR-Id: WCNCR00259341
+Signed-off-by: mtk27745 <rex.lu@mediatek.com>
+---
+ dma.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/dma.c b/dma.c
+index e23b744b8..38701c71e 100644
+--- a/dma.c
++++ b/dma.c
+@@ -1017,6 +1017,10 @@ mt76_dma_init(struct mt76_dev *dev,
+ 	init_completion(&dev->mmio.wed_reset_complete);
+ 
+ 	mt76_for_each_q_rx(dev, i) {
++		if (mtk_wed_device_active(&dev->mmio.wed) &&
++		    mt76_queue_is_wed_rro(&dev->q_rx[i]))
++			continue;
++
+ 		netif_napi_add(&dev->napi_dev, &dev->napi[i], poll);
+ 		mt76_dma_rx_fill(dev, &dev->q_rx[i], false);
+ 		napi_enable(&dev->napi[i]);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0090-mtk-wifi-mt76-mt7996-Remove-wed_stop-during-L1-SER.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0090-mtk-wifi-mt76-mt7996-Remove-wed_stop-during-L1-SER.patch
new file mode 100644
index 0000000..8362ea1
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0090-mtk-wifi-mt76-mt7996-Remove-wed_stop-during-L1-SER.patch
@@ -0,0 +1,34 @@
+From eba92493ca21e2f5014ce87e22810dcacde6d983 Mon Sep 17 00:00:00 2001
+From: Rex Lu <rex.lu@mediatek.com>
+Date: Wed, 29 Nov 2023 13:56:52 +0800
+Subject: [PATCH 090/120] mtk: wifi: mt76: mt7996: Remove wed_stop during L1
+ SER
+
+Align logan L1 SER flow. During L1 SER, didn't need to close wed interrupt.
+
+CR-Id: WCNCR00259341
+Signed-off-by: Rex Lu <rex.lu@mediatek.com>
+---
+ mt7996/mac.c | 6 ------
+ 1 file changed, 6 deletions(-)
+
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index b4703804b..0805251e8 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -1950,12 +1950,6 @@ void mt7996_mac_reset_work(struct work_struct *work)
+ 	dev_info(dev->mt76.dev,"\n%s L1 SER recovery start.",
+ 		 wiphy_name(dev->mt76.hw->wiphy));
+ 
+-	if (mtk_wed_device_active(&dev->mt76.mmio.wed_hif2))
+-		mtk_wed_device_stop(&dev->mt76.mmio.wed_hif2);
+-
+-	if (mtk_wed_device_active(&dev->mt76.mmio.wed))
+-		mtk_wed_device_stop(&dev->mt76.mmio.wed);
+-
+ 	ieee80211_stop_queues(mt76_hw(dev));
+ 	if (phy2)
+ 		ieee80211_stop_queues(phy2->mt76->hw);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0091-mtk-wifi-mt76-mt7996-Refactor-rro-del-ba-command-for.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0091-mtk-wifi-mt76-mt7996-Refactor-rro-del-ba-command-for.patch
new file mode 100644
index 0000000..4a09229
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0091-mtk-wifi-mt76-mt7996-Refactor-rro-del-ba-command-for.patch
@@ -0,0 +1,87 @@
+From 63ef4678c2470d844648b48c0da6c1c5aa6cd963 Mon Sep 17 00:00:00 2001
+From: Rex Lu <rex.lu@mediatek.com>
+Date: Wed, 29 Nov 2023 15:51:04 +0800
+Subject: [PATCH 091/120] mtk: wifi: mt76: mt7996: Refactor rro del ba command
+ format
+
+1. remove unused struct
+2. refactor upstream del ba command format
+
+CR-Id: WCNCR00259516
+Signed-off-by: Rex Lu <rex.lu@mediatek.com>
+Change-Id: Iab75446a54dd0bf3aa95dc80f32d7d80b6f7fc8b
+---
+ mt7996/mcu.h | 50 +++-----------------------------------------------
+ 1 file changed, 3 insertions(+), 47 deletions(-)
+
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index e7debd4d6..65e946c30 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -273,7 +273,9 @@ struct mt7996_mcu_wed_rro_ba_delete_event {
+ 	__le16 len;
+ 
+ 	__le16 session_id;
+-	u8 __rsv2[2];
++	__le16 mld_id;
++	u8 tid;
++	u8 __rsv[3];
+ } __packed;
+ 
+ enum  {
+@@ -298,52 +300,6 @@ struct mt7996_mcu_thermal_notify {
+ 	u8 __rsv2[4];
+ } __packed;
+ 
+-struct mt7996_mcu_rro_event {
+-	struct mt7996_mcu_rxd rxd;
+-
+-	u8 __rsv1[4];
+-
+-	__le16 tag;
+-	__le16 len;
+-} __packed;
+-
+-struct mt7996_mcu_rro_ba {
+-	__le16 tag;
+-	__le16 len;
+-
+-	__le16 wlan_id;
+-	u8 tid;
+-	u8 __rsv1;
+-	__le32 status;
+-	__le16 session_id;
+-	u8 __rsv2[2];
+-} __packed;
+-
+-struct mt7996_mcu_rro_ba_del_chk_done {
+-	__le16 tag;
+-	__le16 len;
+-
+-	__le16 session_id;
+-	__le16 mld_id;
+-	u8 tid;
+-	u8 __rsv[3];
+-} __packed;
+-
+-enum  {
+-	UNI_RRO_BA_SESSION_STATUS = 0,
+-	UNI_RRO_BA_SESSION_TBL	= 1,
+-	UNI_RRO_BA_SESSION_DEL_CHK_DONE = 2,
+-	UNI_RRO_BA_SESSION_MAX_NUM
+-};
+-
+-struct mt7996_mcu_rro_del_ba {
+-	struct mt7996_mcu_rro_event event;
+-
+-	u8  wlan_idx;
+-	u8  tid;
+-	u8 __rsv2[2];
+-};
+-
+ enum mt7996_chan_mib_offs {
+ 	UNI_MIB_OBSS_AIRTIME = 26,
+ 	UNI_MIB_NON_WIFI_TIME = 27,
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0092-mtk-wifi-mt76-mt7996-get-airtime-and-RSSI-via-MCU-co.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0092-mtk-wifi-mt76-mt7996-get-airtime-and-RSSI-via-MCU-co.patch
new file mode 100644
index 0000000..38b3a50
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0092-mtk-wifi-mt76-mt7996-get-airtime-and-RSSI-via-MCU-co.patch
@@ -0,0 +1,786 @@
+From 986674b074261c8fd6e37630b9ff0f6cf10ea7e2 Mon Sep 17 00:00:00 2001
+From: Benjamin Lin <benjamin-jw.lin@mediatek.com>
+Date: Fri, 17 Nov 2023 18:08:06 +0800
+Subject: [PATCH 092/120] mtk: wifi: mt76: mt7996: get airtime and RSSI via MCU
+ commands
+
+Direct access to WTBL for airtime and RSSI may cause synchronization issue with FW.
+Moreover, frequent access to WTBL, whenever TX-Free-Done event is received, leads to heavy CPU overheads.
+Therefore, indirect access to WTBL, through FW, with lower frequence is performed.
+
+Change-Id: I978e7432603742fae9c753f055ff3087cf6b632c
+CR-Id: WCNCR00298425
+Signed-off-by: Yi-Chia Hsieh <yi-chia.hsieh@mediatek.com>
+Signed-off-by: Benjamin Lin <benjamin-jw.lin@mediatek.com>
+---
+ mt76.h               |  20 +++++
+ mt76_connac_mcu.h    |  14 +++-
+ mt7996/debugfs.c     |  17 ++---
+ mt7996/mac.c         | 145 ++++++-----------------------------
+ mt7996/mcu.c         | 177 +++++++++++++++++++++++++++++++++++++++++--
+ mt7996/mcu.h         |  32 +++++++-
+ mt7996/mt7996.h      |  26 ++++++-
+ mt7996/mtk_debugfs.c |  71 +++++++++++++++++
+ mt7996/regs.h        |   2 +
+ 9 files changed, 361 insertions(+), 143 deletions(-)
+
+diff --git a/mt76.h b/mt76.h
+index 49b66ff25..c7816721d 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -327,11 +327,15 @@ struct mt76_sta_stats {
+ 	u32 tx_packets;		/* unit: MSDU */
+ 	u32 tx_retries;
+ 	u32 tx_failed;
++	u32 tx_total_mpdu_cnt;
++	u32 tx_failed_mpdu_cnt;
++	u64 tx_airtime;
+ 	/* WED RX */
+ 	u64 rx_bytes;
+ 	u32 rx_packets;
+ 	u32 rx_errors;
+ 	u32 rx_drops;
++	u64 rx_airtime;
+ };
+ 
+ enum mt76_wcid_flags {
+@@ -1330,6 +1334,22 @@ static inline int mt76_decr(int val, int size)
+ 
+ u8 mt76_ac_to_hwq(u8 ac);
+ 
++static inline u8
++mt76_ac_to_tid(u8 ac)
++{
++	static const u8 ac_to_tid[] = {
++		[IEEE80211_AC_BE] = 0,
++		[IEEE80211_AC_BK] = 1,
++		[IEEE80211_AC_VI] = 4,
++		[IEEE80211_AC_VO] = 6
++	};
++
++	if (WARN_ON(ac >= IEEE80211_NUM_ACS))
++		return 0;
++
++	return ac_to_tid[ac];
++}
++
+ static inline struct ieee80211_txq *
+ mtxq_to_txq(struct mt76_txq *mtxq)
+ {
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 885e5883e..45fef88b9 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1369,11 +1369,23 @@ enum {
+ 	UNI_OFFLOAD_OFFLOAD_BMC_RPY_DETECT,
+ };
+ 
++enum UNI_PER_STA_INFO_TAG {
++	UNI_PER_STA_RSSI,
++	UNI_PER_STA_CONTENTION_RX_RATE,
++	UNI_PER_STA_PER,
++	UNI_PER_STA_SNR,
++	UNI_PER_STA_TX_RATE,
++	UNI_PER_STA_TX_CNT,
++	UNI_PER_STA_TID_SN_GET,
++	UNI_PER_STA_TID_SN_SET,
++	UNI_PER_STA_MAX_NUM
++};
++
+ enum UNI_ALL_STA_INFO_TAG {
+ 	UNI_ALL_STA_TXRX_RATE,
+ 	UNI_ALL_STA_TX_STAT,
+ 	UNI_ALL_STA_TXRX_ADM_STAT,
+-	UNI_ALL_STA_TXRX_AIR_TIME,
++	UNI_ALL_STA_TXRX_AIRTIME,
+ 	UNI_ALL_STA_DATA_TX_RETRY_COUNT,
+ 	UNI_ALL_STA_GI_MODE,
+ 	UNI_ALL_STA_TXRX_MSDU_COUNT,
+diff --git a/mt7996/debugfs.c b/mt7996/debugfs.c
+index ff57b0f1f..36ee041c2 100644
+--- a/mt7996/debugfs.c
++++ b/mt7996/debugfs.c
+@@ -992,12 +992,11 @@ mt7996_airtime_read(struct seq_file *s, void *data)
+ {
+ 	struct mt7996_dev *dev = dev_get_drvdata(s->private);
+ 	struct mt76_dev *mdev = &dev->mt76;
+-	struct mt7996_vow_sta_ctrl *vow;
++	struct mt76_sta_stats *stats;
+ 	struct ieee80211_sta *sta;
+ 	struct mt7996_sta *msta;
+ 	struct mt76_wcid *wcid;
+ 	struct mt76_vif *vif;
+-	u64 airtime;
+ 	u16 i;
+ 
+ 	seq_printf(s, "VoW Airtime Information:\n");
+@@ -1009,16 +1008,16 @@ mt7996_airtime_read(struct seq_file *s, void *data)
+ 
+ 		msta = container_of(wcid, struct mt7996_sta, wcid);
+ 		sta = container_of((void *)msta, struct ieee80211_sta, drv_priv);
+-		vow = &msta->vow;
+ 		vif = &msta->vif->mt76;
++		stats = &wcid->stats;
+ 
+-		spin_lock_bh(&vow->lock);
+-		airtime = vow->tx_airtime;
+-		vow->tx_airtime = 0;
+-		spin_unlock_bh(&vow->lock);
++		seq_printf(s, "%pM WCID: %hu BandIdx: %hhu OmacIdx: 0x%hhx\t"
++		              "TxAirtime: %llu\tRxAirtime: %llu\n",
++		              sta->addr, i, vif->band_idx, vif->omac_idx,
++		              stats->tx_airtime, stats->rx_airtime);
+ 
+-		seq_printf(s, "%pM WCID: %hu BandIdx: %hhu OmacIdx: 0x%hhx\tTxAirtime: %llu\n",
+-		           sta->addr, i, vif->band_idx, vif->omac_idx, airtime);
++		stats->tx_airtime = 0;
++		stats->rx_airtime = 0;
+ 	}
+ 	rcu_read_unlock();
+ 
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 0805251e8..782594cd7 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -12,8 +12,6 @@
+ #include "mcu.h"
+ #include "vendor.h"
+ 
+-#define to_rssi(field, rcpi)	((FIELD_GET(field, rcpi) - 220) / 2)
+-
+ static const struct mt7996_dfs_radar_spec etsi_radar_specs = {
+ 	.pulse_th = { 110, -10, -80, 40, 5200, 128, 5200 },
+ 	.radar_pattern = {
+@@ -93,110 +91,6 @@ u32 mt7996_mac_wtbl_lmac_addr(struct mt7996_dev *dev, u16 wcid, u8 dw)
+ 	return MT_WTBL_LMAC_OFFS(wcid, dw);
+ }
+ 
+-static void mt7996_mac_sta_poll(struct mt7996_dev *dev)
+-{
+-	static const u8 ac_to_tid[] = {
+-		[IEEE80211_AC_BE] = 0,
+-		[IEEE80211_AC_BK] = 1,
+-		[IEEE80211_AC_VI] = 4,
+-		[IEEE80211_AC_VO] = 6
+-	};
+-	struct ieee80211_sta *sta;
+-	struct mt7996_sta *msta;
+-	struct mt7996_vow_sta_ctrl *vow;
+-	u32 tx_time[IEEE80211_NUM_ACS], rx_time[IEEE80211_NUM_ACS];
+-	LIST_HEAD(sta_poll_list);
+-	int i;
+-
+-	spin_lock_bh(&dev->mt76.sta_poll_lock);
+-	list_splice_init(&dev->mt76.sta_poll_list, &sta_poll_list);
+-	spin_unlock_bh(&dev->mt76.sta_poll_lock);
+-
+-	rcu_read_lock();
+-
+-	while (true) {
+-		bool clear = false;
+-		u32 addr, val;
+-		u16 idx;
+-		s8 rssi[4];
+-
+-		spin_lock_bh(&dev->mt76.sta_poll_lock);
+-		if (list_empty(&sta_poll_list)) {
+-			spin_unlock_bh(&dev->mt76.sta_poll_lock);
+-			break;
+-		}
+-		msta = list_first_entry(&sta_poll_list,
+-					struct mt7996_sta, wcid.poll_list);
+-		list_del_init(&msta->wcid.poll_list);
+-		spin_unlock_bh(&dev->mt76.sta_poll_lock);
+-
+-		idx = msta->wcid.idx;
+-
+-		/* refresh peer's airtime reporting */
+-		addr = mt7996_mac_wtbl_lmac_addr(dev, idx, 20);
+-
+-		for (i = 0; i < IEEE80211_NUM_ACS; i++) {
+-			u32 tx_last = msta->airtime_ac[i];
+-			u32 rx_last = msta->airtime_ac[i + 4];
+-
+-			msta->airtime_ac[i] = mt76_rr(dev, addr);
+-			msta->airtime_ac[i + 4] = mt76_rr(dev, addr + 4);
+-
+-			tx_time[i] = msta->airtime_ac[i] - tx_last;
+-			rx_time[i] = msta->airtime_ac[i + 4] - rx_last;
+-
+-			if ((tx_last | rx_last) & BIT(30))
+-				clear = true;
+-
+-			addr += 8;
+-		}
+-
+-		if (clear) {
+-			mt7996_mac_wtbl_update(dev, idx,
+-					       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+-			memset(msta->airtime_ac, 0, sizeof(msta->airtime_ac));
+-		}
+-
+-		if (!msta->wcid.sta)
+-			continue;
+-
+-		sta = container_of((void *)msta, struct ieee80211_sta,
+-				   drv_priv);
+-		vow = &msta->vow;
+-		for (i = 0; i < IEEE80211_NUM_ACS; i++) {
+-			u8 q = mt76_connac_lmac_mapping(i);
+-			u32 tx_cur = tx_time[q];
+-			u32 rx_cur = rx_time[q];
+-			u8 tid = ac_to_tid[i];
+-
+-			if (!tx_cur && !rx_cur)
+-				continue;
+-
+-			ieee80211_sta_register_airtime(sta, tid, tx_cur, rx_cur);
+-
+-			spin_lock_bh(&vow->lock);
+-			vow->tx_airtime += tx_cur;
+-			spin_unlock_bh(&vow->lock);
+-		}
+-
+-		/* get signal strength of resp frames (CTS/BA/ACK) */
+-		addr = mt7996_mac_wtbl_lmac_addr(dev, idx, 34);
+-		val = mt76_rr(dev, addr);
+-
+-		rssi[0] = to_rssi(GENMASK(7, 0), val);
+-		rssi[1] = to_rssi(GENMASK(15, 8), val);
+-		rssi[2] = to_rssi(GENMASK(23, 16), val);
+-		rssi[3] = to_rssi(GENMASK(31, 14), val);
+-
+-		msta->ack_signal =
+-			mt76_rx_signal(msta->vif->phy->mt76->antenna_mask, rssi);
+-
+-		ewma_avg_signal_add(&msta->avg_ack_signal, -msta->ack_signal);
+-	}
+-
+-	rcu_read_unlock();
+-}
+-
+ void mt7996_mac_enable_rtscts(struct mt7996_dev *dev,
+ 			      struct ieee80211_vif *vif, bool enable)
+ {
+@@ -1206,8 +1100,6 @@ mt7996_mac_tx_free(struct mt7996_dev *dev, void *data, int len)
+ 		}
+ 	}
+ 
+-	mt7996_mac_sta_poll(dev);
+-
+ 	if (wake)
+ 		mt76_set_tx_blocked(&dev->mt76, false);
+ 
+@@ -2379,31 +2271,42 @@ void mt7996_mac_sta_rc_work(struct work_struct *work)
+ 
+ void mt7996_mac_work(struct work_struct *work)
+ {
+-	struct mt7996_phy *phy;
+-	struct mt76_phy *mphy;
+-
+-	mphy = (struct mt76_phy *)container_of(work, struct mt76_phy,
+-					       mac_work.work);
+-	phy = mphy->priv;
++	struct mt76_phy *mphy = (struct mt76_phy *)container_of(work, struct mt76_phy,
++	                                                        mac_work.work);
++	struct mt7996_phy *phy = mphy->priv;
++	struct mt76_dev *mdev = mphy->dev;
+ 
+-	mutex_lock(&mphy->dev->mutex);
++	mutex_lock(&mdev->mutex);
+ 
+ 	mt76_update_survey(mphy);
+ 	if (++mphy->mac_work_count == 5) {
++		int i;
++
+ 		mphy->mac_work_count = 0;
+ 
+ 		mt7996_mac_update_stats(phy);
+ 
+-		mt7996_mcu_get_all_sta_info(phy, UNI_ALL_STA_TXRX_RATE);
+-		if (mtk_wed_device_active(&phy->dev->mt76.mmio.wed)) {
+-			mt7996_mcu_get_all_sta_info(phy, UNI_ALL_STA_TXRX_ADM_STAT);
+-			mt7996_mcu_get_all_sta_info(phy, UNI_ALL_STA_TXRX_MSDU_COUNT);
++		/* Update DEV-wise information only in
++		 * the MAC work of the first band running.
++		 */
++		for (i = MT_BAND0; i <= mphy->band_idx; ++i) {
++			if (i == mphy->band_idx) {
++				mt7996_mcu_get_all_sta_info(mdev, UNI_ALL_STA_TXRX_RATE);
++				mt7996_mcu_get_all_sta_info(mdev, UNI_ALL_STA_TXRX_AIRTIME);
++				mt7996_mcu_get_rssi(mdev);
++				if (mtk_wed_device_active(&mdev->mmio.wed)) {
++					mt7996_mcu_get_all_sta_info(mdev, UNI_ALL_STA_TXRX_ADM_STAT);
++					mt7996_mcu_get_all_sta_info(mdev, UNI_ALL_STA_TXRX_MSDU_COUNT);
++				}
++			} else if (mt7996_band_valid(phy->dev, i) &&
++			           test_bit(MT76_STATE_RUNNING, &mdev->phys[i]->state))
++				break;
+ 		}
+ 	}
+ 
+-	mutex_unlock(&mphy->dev->mutex);
++	mutex_unlock(&mdev->mutex);
+ 
+-	mt76_tx_status_check(mphy->dev, false);
++	mt76_tx_status_check(mdev, false);
+ 
+ 	ieee80211_queue_delayed_work(mphy->hw, &mphy->mac_work,
+ 				     MT7996_WATCHDOG_TIME);
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index fa9090904..81c80ddea 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -563,7 +563,8 @@ mt7996_mcu_rx_all_sta_info_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 		u16 wlan_idx;
+ 		struct mt76_wcid *wcid;
+ 		struct mt76_phy *mphy;
+-		u32 tx_bytes, rx_bytes, tx_packets, rx_packets;
++		struct ieee80211_sta *sta;
++		u32 tx_bytes, rx_bytes, tx_airtime, rx_airtime, tx_packets, rx_packets;
+ 
+ 		switch (le16_to_cpu(res->tag)) {
+ 		case UNI_ALL_STA_TXRX_RATE:
+@@ -584,7 +585,7 @@ mt7996_mcu_rx_all_sta_info_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 				break;
+ 
+ 			mphy = mt76_dev_phy(&dev->mt76, wcid->phy_idx);
+-			for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
++			for (ac = IEEE80211_AC_VO; ac < IEEE80211_NUM_ACS; ac++) {
+ 				tx_bytes = le32_to_cpu(res->adm_stat[i].tx_bytes[ac]);
+ 				rx_bytes = le32_to_cpu(res->adm_stat[i].rx_bytes[ac]);
+ 
+@@ -616,6 +617,24 @@ mt7996_mcu_rx_all_sta_info_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 			__mt7996_stat_to_netdev(mphy, wcid, 0, 0,
+ 						tx_packets, rx_packets);
+ 			break;
++		case UNI_ALL_STA_TXRX_AIRTIME:
++			wlan_idx = le16_to_cpu(res->airtime[i].wlan_idx);
++			wcid = rcu_dereference(dev->mt76.wcid[wlan_idx]);
++			sta = wcid_to_sta(wcid);
++			if (!sta)
++				continue;
++
++			for (ac = IEEE80211_AC_VO; ac < IEEE80211_NUM_ACS; ++ac) {
++				u8 lmac_ac = mt76_connac_lmac_mapping(ac);
++				tx_airtime = le32_to_cpu(res->airtime[i].tx[lmac_ac]);
++				rx_airtime = le32_to_cpu(res->airtime[i].rx[lmac_ac]);
++
++				wcid->stats.tx_airtime += tx_airtime;
++				wcid->stats.rx_airtime += rx_airtime;
++				ieee80211_sta_register_airtime(sta, mt76_ac_to_tid(ac),
++				                               tx_airtime, rx_airtime);
++			}
++			break;
+ 		default:
+ 			break;
+ 		}
+@@ -2244,8 +2263,6 @@ mt7996_mcu_sta_init_vow(struct mt7996_phy *phy, struct mt7996_sta *msta)
+ 	vow->drr_quantum[IEEE80211_AC_VI] = VOW_DRR_QUANTUM_IDX1;
+ 	vow->drr_quantum[IEEE80211_AC_BE] = VOW_DRR_QUANTUM_IDX2;
+ 	vow->drr_quantum[IEEE80211_AC_BK] = VOW_DRR_QUANTUM_IDX2;
+-	vow->tx_airtime = 0;
+-	spin_lock_init(&vow->lock);
+ 
+ 	ret = mt7996_mcu_set_vow_drr_ctrl(phy, msta, VOW_DRR_CTRL_STA_BSS_GROUP);
+ 	if (ret)
+@@ -4854,9 +4871,155 @@ int mt7996_mcu_set_rro(struct mt7996_dev *dev, u16 tag, u16 val)
+ 				 sizeof(req), true);
+ }
+ 
+-int mt7996_mcu_get_all_sta_info(struct mt7996_phy *phy, u16 tag)
++int mt7996_mcu_get_per_sta_info(struct mt76_dev *dev, u16 tag,
++	                        u16 sta_num, u16 *sta_list)
++{
++#define PER_STA_INFO_MAX_NUM	90
++	struct mt7996_mcu_per_sta_info_event *res;
++	struct mt76_wcid *wcid;
++	struct sk_buff *skb;
++	u16 wlan_idx;
++	int i, ret;
++	struct {
++		u8 __rsv1;
++		u8 unsolicit;
++		u8 __rsv2[2];
++
++		__le16 tag;
++		__le16 len;
++		__le16 sta_num;
++		u8 __rsv3[2];
++		__le16 sta_list[PER_STA_INFO_MAX_NUM];
++	} __packed req = {
++		.unsolicit = 0,
++		.tag = cpu_to_le16(tag),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.sta_num = cpu_to_le16(sta_num)
++	};
++
++	if (sta_num > PER_STA_INFO_MAX_NUM)
++		return -EINVAL;
++
++	for (i = 0; i < sta_num; ++i)
++		req.sta_list[i] = cpu_to_le16(sta_list[i]);
++
++	ret = mt76_mcu_send_and_get_msg(dev, MCU_WM_UNI_CMD(PER_STA_INFO),
++	                                &req, sizeof(req), true, &skb);
++	if (ret)
++		return ret;
++
++	res = (struct mt7996_mcu_per_sta_info_event *)skb->data;
++	if (le16_to_cpu(res->tag) != tag) {
++		ret = -EINVAL;
++		goto out;
++	}
++
++	rcu_read_lock();
++	switch (tag) {
++	case UNI_PER_STA_RSSI:
++		for (i = 0; i < sta_num; ++i) {
++			struct mt7996_sta *msta;
++			struct mt76_phy *phy;
++			s8 rssi[4];
++			u8 *rcpi;
++
++			wlan_idx = le16_to_cpu(res->rssi[i].wlan_idx);
++			wcid = rcu_dereference(dev->wcid[wlan_idx]);
++			if (wcid) {
++				rcpi = res->rssi[i].rcpi;
++				rssi[0] = to_rssi(MT_PRXV_RCPI0, rcpi[0]);
++				rssi[1] = to_rssi(MT_PRXV_RCPI0, rcpi[1]);
++				rssi[2] = to_rssi(MT_PRXV_RCPI0, rcpi[2]);
++				rssi[3] = to_rssi(MT_PRXV_RCPI0, rcpi[3]);
++
++				msta = container_of(wcid, struct mt7996_sta, wcid);
++				phy = msta->vif->phy->mt76;
++				msta->ack_signal = mt76_rx_signal(phy->antenna_mask, rssi);
++				ewma_avg_signal_add(&msta->avg_ack_signal, -msta->ack_signal);
++			} else {
++				ret = -EINVAL;
++				dev_err(dev->dev, "Failed to update RSSI for "
++				                  "invalid WCID: %hu\n", wlan_idx);
++			}
++		}
++		break;
++	case UNI_PER_STA_TX_CNT:
++		for (i = 0; i < sta_num; ++i) {
++			wlan_idx = le16_to_cpu(res->tx_cnt[i].wlan_idx);
++			wcid = rcu_dereference(dev->wcid[wlan_idx]);
++			if (wcid) {
++				wcid->stats.tx_total_mpdu_cnt +=
++				            le32_to_cpu(res->tx_cnt[i].total);
++				wcid->stats.tx_failed_mpdu_cnt +=
++				            le32_to_cpu(res->tx_cnt[i].failed);
++			} else {
++				ret = -EINVAL;
++				dev_err(dev->dev, "Failed to update TX MPDU counts "
++				                  "for invalid WCID: %hu\n", wlan_idx);
++			}
++		}
++		break;
++	default:
++		ret = -EINVAL;
++		dev_err(dev->dev, "Unknown UNI_PER_STA_INFO_TAG: %d\n", tag);
++	}
++	rcu_read_unlock();
++out:
++	dev_kfree_skb(skb);
++	return ret;
++}
++
++int mt7996_mcu_get_rssi(struct mt76_dev *dev)
++{
++	u16 sta_list[PER_STA_INFO_MAX_NUM];
++	LIST_HEAD(sta_poll_list);
++	struct mt7996_sta *msta;
++	int i, ret;
++	bool empty = false;
++
++	spin_lock_bh(&dev->sta_poll_lock);
++	list_splice_init(&dev->sta_poll_list, &sta_poll_list);
++	spin_unlock_bh(&dev->sta_poll_lock);
++
++	while (!empty) {
++		for (i = 0; i < PER_STA_INFO_MAX_NUM; ++i) {
++			spin_lock_bh(&dev->sta_poll_lock);
++			if (list_empty(&sta_poll_list)) {
++				spin_unlock_bh(&dev->sta_poll_lock);
++
++				if (i == 0)
++					return 0;
++
++				empty = true;
++				break;
++			}
++			msta = list_first_entry(&sta_poll_list,
++			                        struct mt7996_sta,
++			                        wcid.poll_list);
++			list_del_init(&msta->wcid.poll_list);
++			spin_unlock_bh(&dev->sta_poll_lock);
++
++			sta_list[i] = msta->wcid.idx;
++		}
++
++		ret = mt7996_mcu_get_per_sta_info(dev, UNI_PER_STA_RSSI,
++		                                  i, sta_list);
++		if (ret) {
++			/* Add STAs, whose RSSI has not been updated,
++			 * back to polling list.
++			 */
++			spin_lock_bh(&dev->sta_poll_lock);
++			list_splice(&sta_poll_list, &dev->sta_poll_list);
++			spin_unlock_bh(&dev->sta_poll_lock);
++			break;
++		}
++	}
++
++	return ret;
++}
++
++int mt7996_mcu_get_all_sta_info(struct mt76_dev *dev, u16 tag)
+ {
+-	struct mt7996_dev *dev = phy->dev;
+ 	struct {
+ 		u8 _rsv[4];
+ 
+@@ -4867,7 +5030,7 @@ int mt7996_mcu_get_all_sta_info(struct mt7996_phy *phy, u16 tag)
+ 		.len = cpu_to_le16(sizeof(req) - 4),
+ 	};
+ 
+-	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(ALL_STA_INFO),
++	return mt76_mcu_send_msg(dev, MCU_WM_UNI_CMD(ALL_STA_INFO),
+ 				 &req, sizeof(req), false);
+ }
+ 
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 65e946c30..21b75252b 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -199,6 +199,31 @@ struct mt7996_mcu_mib {
+ 	__le64 data;
+ } __packed;
+ 
++struct per_sta_rssi {
++	__le16 wlan_idx;
++	u8 __rsv[2];
++	u8 rcpi[4];
++} __packed;
++
++struct per_sta_tx_cnt {
++	__le16 wlan_idx;
++	u8 __rsv[2];
++	__le32 total;
++	__le32 failed;
++} __packed;
++
++struct mt7996_mcu_per_sta_info_event {
++	u8 __rsv[4];
++
++	__le16 tag;
++	__le16 len;
++
++	union {
++		struct per_sta_rssi rssi[0];
++		struct per_sta_tx_cnt tx_cnt[0];
++	};
++} __packed;
++
+ struct all_sta_trx_rate {
+ 	__le16 wlan_idx;
+ 	u8 __rsv1[2];
+@@ -237,13 +262,18 @@ struct mt7996_mcu_all_sta_info_event {
+ 			__le32 tx_bytes[IEEE80211_NUM_ACS];
+ 			__le32 rx_bytes[IEEE80211_NUM_ACS];
+ 		} adm_stat[0] __packed;
+-
+ 		struct {
+ 			__le16 wlan_idx;
+ 			u8 rsv[2];
+ 			__le32 tx_msdu_cnt;
+ 			__le32 rx_msdu_cnt;
+ 		} msdu_cnt[0] __packed;
++		struct {
++			__le16 wlan_idx;
++			u8 __rsv[2];
++			__le32 tx[IEEE80211_NUM_ACS];
++			__le32 rx[IEEE80211_NUM_ACS];
++		} airtime[0] __packed;
+ 	} __packed;
+ } __packed;
+ 
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 23e9de03b..06268e997 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -126,6 +126,8 @@
+ #define MT7996_RRO_MSDU_PG_CR_CNT 8
+ #define MT7996_RRO_MSDU_PG_SIZE_PER_CR 0x10000
+ 
++#define to_rssi(field, rcpi)	((FIELD_GET(field, rcpi) - 220) / 2)
++
+ struct mt7996_vif;
+ struct mt7996_sta;
+ struct mt7996_dfs_pulse;
+@@ -298,8 +300,6 @@ struct mt7996_vow_sta_ctrl {
+ 	bool paused;
+ 	u8 bss_grp_idx;
+ 	u8 drr_quantum[IEEE80211_NUM_ACS];
+-	u64 tx_airtime;
+-	spinlock_t lock;
+ };
+ 
+ struct mt7996_sta {
+@@ -308,7 +308,6 @@ struct mt7996_sta {
+ 	struct mt7996_vif *vif;
+ 
+ 	struct list_head rc_list;
+-	u32 airtime_ac[8];
+ 
+ 	int ack_signal;
+ 	struct ewma_avg_signal avg_ack_signal;
+@@ -405,6 +404,21 @@ struct mt7996_air_monitor_ctrl {
+ };
+ #endif
+ 
++struct mt7996_rro_ba_session {
++	u32 ack_sn         :12;
++	u32 win_sz         :3;
++	u32 bn             :1;
++	u32 last_in_sn     :12;
++	u32 bc             :1;
++	u32 bd             :1;
++	u32 sat            :1;
++	u32 cn             :1;
++	u32 within_cnt     :12;
++	u32 to_sel         :3;
++	u32 rsv            :1;
++	u32 last_in_rxtime :12;
++};
++
+ struct mt7996_phy {
+ 	struct mt76_phy *mt76;
+ 	struct mt7996_dev *dev;
+@@ -594,6 +608,7 @@ struct mt7996_dev {
+ 		u32 fw_dbg_module;
+ 		u8 fw_dbg_lv;
+ 		u32 bcn_total_cnt[__MT_MAX_BAND];
++		u32 sid;
+ 	} dbg;
+ 	const struct mt7996_dbg_reg_desc *dbg_reg;
+ #endif
+@@ -819,7 +834,10 @@ int mt7996_mcu_fw_dbg_ctrl(struct mt7996_dev *dev, u32 module, u8 level);
+ int mt7996_mcu_trigger_assert(struct mt7996_dev *dev);
+ void mt7996_mcu_rx_event(struct mt7996_dev *dev, struct sk_buff *skb);
+ void mt7996_mcu_exit(struct mt7996_dev *dev);
+-int mt7996_mcu_get_all_sta_info(struct mt7996_phy *phy, u16 tag);
++int mt7996_mcu_get_per_sta_info(struct mt76_dev *dev, u16 tag,
++	                        u16 sta_num, u16 *sta_list);
++int mt7996_mcu_get_rssi(struct mt76_dev *dev);
++int mt7996_mcu_get_all_sta_info(struct mt76_dev *dev, u16 tag);
+ int mt7996_mcu_wed_rro_reset_sessions(struct mt7996_dev *dev, u16 id);
+ int mt7996_mcu_set_tx_power_ctrl(struct mt7996_phy *phy, u8 power_ctrl_id, u8 data);
+ int mt7996_mcu_get_tx_power_info(struct mt7996_phy *phy, u8 category, void *event);
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index 737724bae..b5e81e904 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -3047,6 +3047,69 @@ mt7996_vow_drr_dbg(void *data, u64 val)
+ DEFINE_DEBUGFS_ATTRIBUTE(fops_vow_drr_dbg, NULL,
+ 			 mt7996_vow_drr_dbg, "%lld\n");
+ 
++static int
++mt7996_rro_session_read(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	struct mt7996_rro_ba_session *tbl;
++	u32 value[2];
++
++	mt76_wr(dev, MT_RRO_DBG_RD_CTRL, MT_RRO_DBG_RD_EXEC +
++		(dev->dbg.sid >> 1) + 0x200);
++
++	if (dev->dbg.sid & 0x1) {
++		value[0] = mt76_rr(dev, MT_RRO_DBG_RDAT_DW(2));
++		value[1] = mt76_rr(dev, MT_RRO_DBG_RDAT_DW(3));
++	} else {
++		value[0] = mt76_rr(dev, MT_RRO_DBG_RDAT_DW(0));
++		value[1] = mt76_rr(dev, MT_RRO_DBG_RDAT_DW(1));
++	}
++
++	tbl = (struct mt7996_rro_ba_session *)&value[0];
++
++	seq_printf(s, " seid %d:\nba session table DW0:%08x DW2:%08x\n",
++		   dev->dbg.sid, value[0], value[1]);
++
++	seq_printf(s, "ack_sn = 0x%x, last_in_sn = 0x%x, sat/bn/bc/bd/cn = %d/%d/%d/%d/%d\n",
++		   tbl->ack_sn, tbl->last_in_sn, tbl->sat, tbl->bn, tbl->bc, tbl->bd, tbl->cn);
++
++	seq_printf(s, "within_cnt = %d, to_sel = %d, last_in_rxtime = %d\n",
++		   tbl->within_cnt, tbl->to_sel, tbl->last_in_rxtime);
++
++	return 0;
++}
++
++static int
++mt7996_show_rro_mib(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	u32 reg[12];
++
++	seq_printf(s, "RRO mib Info:\n");
++
++	reg[0] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(0));
++	reg[1] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(1));
++	reg[2] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(2));
++	reg[3] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(3));
++	reg[4] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(4));
++	reg[5] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(5));
++	reg[6] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(6));
++	reg[7] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(7));
++	reg[8] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(8));
++	reg[9] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(9));
++	reg[10] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(10));
++	reg[11] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(11));
++
++	seq_printf(s, "STEP_ONE/WITHIN/SURPASS = %x/%x/%x\n", reg[0], reg[3], reg[4]);
++	seq_printf(s, "REPEAT/OLDPKT/BAR = %x/%x/%x\n", reg[1], reg[2], reg[5]);
++	seq_printf(s, "SURPASS with big gap = %x\n", reg[6]);
++	seq_printf(s, "DISCONNECT/INVALID = %x/%x\n", reg[7], reg[8]);
++	seq_printf(s, "TO(Step one)/TO(flush all) = %x/%x\n", reg[9], reg[10]);
++	seq_printf(s, "buf ran out = %x\n", reg[11]);
++
++	return 0;
++}
++
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -3146,6 +3209,14 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ 
+ 	debugfs_create_file("muru_prot_thr", 0200, dir, phy, &fops_muru_prot_thr);
+ 
++	if (dev->has_rro) {
++		debugfs_create_u32("rro_sid", 0600, dir, &dev->dbg.sid);
++		debugfs_create_devm_seqfile(dev->mt76.dev, "rro_sid_info", dir,
++					    mt7996_rro_session_read);
++		debugfs_create_devm_seqfile(dev->mt76.dev, "rro_mib", dir,
++					    mt7996_show_rro_mib);
++	}
++
+ 	return 0;
+ }
+ 
+diff --git a/mt7996/regs.h b/mt7996/regs.h
+index a3b623390..476b23c3d 100644
+--- a/mt7996/regs.h
++++ b/mt7996/regs.h
+@@ -122,6 +122,8 @@ enum offs_rev {
+ #define MT_MCU_INT_EVENT_DMA_INIT		BIT(1)
+ #define MT_MCU_INT_EVENT_RESET_DONE		BIT(3)
+ 
++#define WF_RRO_TOP_STATISTIC(_n)		MT_RRO_TOP(0x180 + _n * 0x4)
++
+ /* PLE */
+ #define MT_PLE_BASE				0x820c0000
+ #define MT_PLE(ofs)				(MT_PLE_BASE + (ofs))
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0093-mtk-wifi-mt76-mt7996-add-support-for-WMM-PBC-configu.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0093-mtk-wifi-mt76-mt7996-add-support-for-WMM-PBC-configu.patch
new file mode 100644
index 0000000..12bb164
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0093-mtk-wifi-mt76-mt7996-add-support-for-WMM-PBC-configu.patch
@@ -0,0 +1,220 @@
+From afcea21f59b9b63d4df970681e81be8228f4c606 Mon Sep 17 00:00:00 2001
+From: Benjamin Lin <benjamin-jw.lin@mediatek.com>
+Date: Thu, 4 Jan 2024 09:47:00 +0800
+Subject: [PATCH 093/120] mtk: wifi: mt76: mt7996: add support for WMM PBC
+ configuration
+
+Query per-AC-queue packet statistics from WA, and determine if multi-AC transmission is ongoing.
+If it is, enable WMM mode in WA. Otherwise, disable WMM mode.
+
+CR-Id: WCNCR00298425
+Signed-off-by: Benjamin Lin <benjamin-jw.lin@mediatek.com>
+Change-Id: I1852a45dc0e64780af5f091fd749ceab59697dbb
+---
+ mt76_connac_mcu.h |  2 ++
+ mt7996/init.c     |  2 ++
+ mt7996/mac.c      |  4 +++
+ mt7996/mcu.c      | 78 +++++++++++++++++++++++++++++++++++++++++++++++
+ mt7996/mcu.h      | 15 +++++++++
+ mt7996/mt7996.h   |  4 +++
+ 6 files changed, 105 insertions(+)
+
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 45fef88b9..12fdc6e12 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1026,6 +1026,7 @@ enum {
+ 	MCU_EXT_EVENT_ASSERT_DUMP = 0x23,
+ 	MCU_EXT_EVENT_RDD_REPORT = 0x3a,
+ 	MCU_EXT_EVENT_CSA_NOTIFY = 0x4f,
++	MCU_EXT_EVENT_BSS_ACQ_PKT_CNT = 0x52,
+ 	MCU_EXT_EVENT_WA_TX_STAT = 0x74,
+ 	MCU_EXT_EVENT_BCC_NOTIFY = 0x75,
+ 	MCU_EXT_EVENT_MURU_CTRL = 0x9f,
+@@ -1224,6 +1225,7 @@ enum {
+ 	MCU_EXT_CMD_TXDPD_CAL = 0x60,
+ 	MCU_EXT_CMD_CAL_CACHE = 0x67,
+ 	MCU_EXT_CMD_RED_ENABLE = 0x68,
++	MCU_EXT_CMD_PKT_BUDGET_CTRL = 0x6c,
+ 	MCU_EXT_CMD_CP_SUPPORT = 0x75,
+ 	MCU_EXT_CMD_SET_RADAR_TH = 0x7c,
+ 	MCU_EXT_CMD_SET_RDD_PATTERN = 0x7d,
+diff --git a/mt7996/init.c b/mt7996/init.c
+index 7a9b97490..90f3a4176 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -1493,6 +1493,8 @@ int mt7996_register_device(struct mt7996_dev *dev)
+ 	INIT_WORK(&dev->dump_work, mt7996_mac_dump_work);
+ 	mutex_init(&dev->dump_mutex);
+ 
++	INIT_WORK(&dev->wmm_pbc_work, mt7996_mcu_wmm_pbc_work);
++
+ 	ret = mt7996_init_hardware(dev);
+ 	if (ret)
+ 		return ret;
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 782594cd7..e3758ff13 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -2298,6 +2298,10 @@ void mt7996_mac_work(struct work_struct *work)
+ 					mt7996_mcu_get_all_sta_info(mdev, UNI_ALL_STA_TXRX_ADM_STAT);
+ 					mt7996_mcu_get_all_sta_info(mdev, UNI_ALL_STA_TXRX_MSDU_COUNT);
+ 				}
++
++				if (mt7996_mcu_wa_cmd(phy->dev, MCU_WA_PARAM_CMD(QUERY), MCU_WA_PARAM_BSS_ACQ_PKT_CNT,
++				                      BSS_ACQ_PKT_CNT_BSS_BITMAP_ALL | BSS_ACQ_PKT_CNT_READ_CLR, 0))
++					dev_err(mdev->dev, "Failed to query per-AC-queue packet counts.\n");
+ 			} else if (mt7996_band_valid(phy->dev, i) &&
+ 			           test_bit(MT76_STATE_RUNNING, &mdev->phys[i]->state))
+ 				break;
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 81c80ddea..1827e8092 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -669,6 +669,82 @@ mt7996_mcu_rx_thermal_notify(struct mt7996_dev *dev, struct sk_buff *skb)
+ 	phy->throttle_state = n->duty_percent;
+ }
+ 
++void mt7996_mcu_wmm_pbc_work(struct work_struct *work)
++{
++#define WMM_PBC_QUEUE_NUM	5
++#define WMM_PBC_BSS_ALL		0xff
++#define WMM_PBC_WLAN_IDX_ALL	0xffff
++#define WMM_PBC_BOUND_DEFAULT	0xffff
++#define WMM_PBC_LOW_BOUND_VO	1900
++#define WMM_PBC_LOW_BOUND_VI	1900
++#define WMM_PBC_LOW_BOUND_BE	1500
++#define WMM_PBC_LOW_BOUND_BK	900
++#define WMM_PBC_LOW_BOUND_MGMT	32
++	struct mt7996_dev *dev = container_of(work, struct mt7996_dev, wmm_pbc_work);
++	struct {
++		u8 bss_idx;
++		u8 queue_num;
++		__le16 wlan_idx;
++		u8 band_idx;
++		u8 __rsv[3];
++		struct {
++			__le16 low;
++			__le16 up;
++		} __packed bound[WMM_PBC_QUEUE_NUM];
++	} __packed req = {
++		.bss_idx = WMM_PBC_BSS_ALL,
++		.queue_num = WMM_PBC_QUEUE_NUM,
++		.wlan_idx = cpu_to_le16(WMM_PBC_WLAN_IDX_ALL),
++		.band_idx = dev->mphy.band_idx,
++	};
++	int i, ret;
++
++#define pbc_acq_low_bound_config(_ac, _bound)								\
++	req.bound[mt76_connac_lmac_mapping(_ac)].low = dev->wmm_pbc_enable ? cpu_to_le16(_bound) : 0
++	pbc_acq_low_bound_config(IEEE80211_AC_VO, WMM_PBC_LOW_BOUND_VO);
++	pbc_acq_low_bound_config(IEEE80211_AC_VI, WMM_PBC_LOW_BOUND_VI);
++	pbc_acq_low_bound_config(IEEE80211_AC_BE, WMM_PBC_LOW_BOUND_BE);
++	pbc_acq_low_bound_config(IEEE80211_AC_BK, WMM_PBC_LOW_BOUND_BK);
++	req.bound[4].low = dev->wmm_pbc_enable
++	                   ? cpu_to_le16(WMM_PBC_LOW_BOUND_MGMT) : 0;
++
++	for (i = 0; i < WMM_PBC_QUEUE_NUM; ++i)
++		req.bound[i].up = cpu_to_le16(WMM_PBC_BOUND_DEFAULT);
++
++	ret = mt76_mcu_send_msg(&dev->mt76, MCU_WA_EXT_CMD(PKT_BUDGET_CTRL),
++	                        &req, sizeof(req), true);
++	if (ret)
++		dev_err(dev->mt76.dev, "Failed to configure WMM PBC.\n");
++}
++
++static void
++mt7996_mcu_rx_bss_acq_pkt_cnt(struct mt7996_dev *dev, struct sk_buff *skb)
++{
++	struct mt7996_mcu_bss_acq_pkt_cnt_event *event = (struct mt7996_mcu_bss_acq_pkt_cnt_event *)skb->data;
++	u32 bitmap = le32_to_cpu(event->bss_bitmap);
++	u64 sum[IEEE80211_NUM_ACS] = {0};
++	u8 ac_cnt = 0;
++	int i, j;
++
++	for (i = 0; (i < BSS_ACQ_PKT_CNT_BSS_NUM) && (bitmap & (1 << i)); ++i) {
++		for (j = IEEE80211_AC_VO; j < IEEE80211_NUM_ACS; ++j)
++			sum[j] += le32_to_cpu(event->bss[i].cnt[mt76_connac_lmac_mapping(j)]);
++	}
++
++	for (i = IEEE80211_AC_VO; i < IEEE80211_NUM_ACS; ++i) {
++		if (sum[i] > WMM_PKT_THRESHOLD)
++			++ac_cnt;
++	}
++
++	if (ac_cnt > 1 && !dev->wmm_pbc_enable) {
++		dev->wmm_pbc_enable = true;
++		queue_work(dev->mt76.wq, &dev->wmm_pbc_work);
++	} else if (ac_cnt <= 1 && dev->wmm_pbc_enable) {
++		dev->wmm_pbc_enable = false;
++		queue_work(dev->mt76.wq, &dev->wmm_pbc_work);
++	}
++}
++
+ static void
+ mt7996_mcu_rx_ext_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ {
+@@ -678,6 +754,8 @@ mt7996_mcu_rx_ext_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 	case MCU_EXT_EVENT_FW_LOG_2_HOST:
+ 		mt7996_mcu_rx_log_message(dev, skb);
+ 		break;
++	case MCU_EXT_EVENT_BSS_ACQ_PKT_CNT:
++		mt7996_mcu_rx_bss_acq_pkt_cnt(dev, skb);
+ 	default:
+ 		break;
+ 	}
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 21b75252b..1dc5f68e5 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -375,10 +375,25 @@ enum {
+ 	MCU_WA_PARAM_CMD_DEBUG,
+ };
+ 
++#define BSS_ACQ_PKT_CNT_BSS_NUM		24
++#define BSS_ACQ_PKT_CNT_BSS_BITMAP_ALL	0x00ffffff
++#define BSS_ACQ_PKT_CNT_READ_CLR	BIT(31)
++#define WMM_PKT_THRESHOLD		100
++
++struct mt7996_mcu_bss_acq_pkt_cnt_event {
++	struct mt7996_mcu_rxd rxd;
++
++	__le32 bss_bitmap;
++	struct {
++		__le32 cnt[IEEE80211_NUM_ACS];
++	} __packed bss[BSS_ACQ_PKT_CNT_BSS_NUM];
++} __packed;
++
+ enum {
+ 	MCU_WA_PARAM_PDMA_RX = 0x04,
+ 	MCU_WA_PARAM_CPU_UTIL = 0x0b,
+ 	MCU_WA_PARAM_RED_EN = 0x0e,
++	MCU_WA_PARAM_BSS_ACQ_PKT_CNT = 0x12,
+ 	MCU_WA_PARAM_HW_PATH_HIF_VER = 0x2f,
+ 	MCU_WA_PARAM_RED_CONFIG = 0x40,
+ };
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 06268e997..9060d49b1 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -601,6 +601,9 @@ struct mt7996_dev {
+ 	u8 wtbl_size_group;
+ 
+ 	struct mt7996_vow_ctrl vow;
++
++	bool wmm_pbc_enable;
++	struct work_struct wmm_pbc_work;
+ #ifdef CONFIG_MTK_DEBUG
+ 	u16 wlan_idx;
+ 	struct {
+@@ -852,6 +855,7 @@ int mt7996_mcu_set_band_confg(struct mt7996_phy *phy, u16 option, bool enable);
+ int mt7996_mcu_set_vow_drr_ctrl(struct mt7996_phy *phy, struct mt7996_sta *msta,
+ 	                        enum vow_drr_ctrl_id id);
+ int mt7996_mcu_set_vow_feature_ctrl(struct mt7996_phy *phy);
++void mt7996_mcu_wmm_pbc_work(struct work_struct *work);
+ 
+ static inline u8 mt7996_max_interface_num(struct mt7996_dev *dev)
+ {
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0094-mtk-wifi-mt76-mt7996-eagle-support-extra-option_type.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0094-mtk-wifi-mt76-mt7996-eagle-support-extra-option_type.patch
new file mode 100644
index 0000000..8a8c1cd
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0094-mtk-wifi-mt76-mt7996-eagle-support-extra-option_type.patch
@@ -0,0 +1,330 @@
+From 0f6ceacce813c775eeec1eb720ce580ef89c7085 Mon Sep 17 00:00:00 2001
+From: Rex Lu <rex.lu@mediatek.com>
+Date: Thu, 1 Feb 2024 10:32:42 +0800
+Subject: [PATCH 094/120] mtk: wifi: mt76: mt7996: eagle support extra
+ option_type
+
+1. eagle + mt7988d option_type 2 support
+2. eagle single pcie support
+
+CR-Id: WCNCR00259516
+Change-Id: Ib8e741b3c9eba3c796704351259f926c1e4e9d69
+Signed-off-by: Rex Lu <rex.lu@mediatek.com>
+
+1. adjust pcie outstanding value by pcie speed. not no longer by option_type.
+
+CR-Id: WCNCR00259516
+Signed-off-by: Rex Lu <rex.lu@mediatek.com>
+Change-Id: I1c8fa6769aa0b192b32738aec0b14c86572a493b
+(cherry picked from commit 31d64e0f571eb06ed67f4916bc12fbcfe1263c47)
+---
+ mt7996/dma.c    | 51 +++++++++++++++++++++++++++++++++----
+ mt7996/init.c   | 67 ++++++++++++++++++++++++++++++++++++++-----------
+ mt7996/main.c   | 15 +++++++++--
+ mt7996/mt7996.h |  5 ++++
+ mt7996/pci.c    |  2 +-
+ mt7996/regs.h   |  5 ++++
+ 6 files changed, 123 insertions(+), 22 deletions(-)
+
+diff --git a/mt7996/dma.c b/mt7996/dma.c
+index c23b0d651..3dc0e8a1d 100644
+--- a/mt7996/dma.c
++++ b/mt7996/dma.c
+@@ -12,12 +12,20 @@ int mt7996_init_tx_queues(struct mt7996_phy *phy, int idx, int n_desc,
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+ 	u32 flags = 0;
++	int i;
++
++	if (phy->mt76->band_idx == MT_BAND1 && !dev->hif2 && is_mt7996(&dev->mt76)) {
++		phy->mt76->q_tx[0] = phy->mt76->dev->phys[MT_BAND0]->q_tx[0];
++		for (i = 1; i <= MT_TXQ_PSD; i++)
++			phy->mt76->q_tx[i] = phy->mt76->q_tx[0];
++		return 0;
++	}
+ 
+ 	if (mtk_wed_device_active(wed)) {
+ 		ring_base += MT_TXQ_ID(0) * MT_RING_SIZE;
+ 		idx -= MT_TXQ_ID(0);
+ 
+-		if (phy->mt76->band_idx == MT_BAND2)
++		if (wed == &dev->mt76.mmio.wed_hif2)
+ 			flags = MT_WED_Q_TX(0);
+ 		else
+ 			flags = MT_WED_Q_TX(idx);
+@@ -102,8 +110,20 @@ static void mt7996_dma_config(struct mt7996_dev *dev)
+ 	/* data tx queue */
+ 	TXQ_CONFIG(0, WFDMA0, MT_INT_TX_DONE_BAND0, MT7996_TXQ_BAND0);
+ 	if (is_mt7996(&dev->mt76)) {
+-		TXQ_CONFIG(1, WFDMA0, MT_INT_TX_DONE_BAND1, MT7996_TXQ_BAND1);
+-		TXQ_CONFIG(2, WFDMA0, MT_INT_TX_DONE_BAND2, MT7996_TXQ_BAND2);
++		if (dev->hif2) {
++			if (dev->option_type == 2) {
++				/*  bn1:ring21 bn2:ring19 */
++				TXQ_CONFIG(1, WFDMA0, MT_INT_TX_DONE_BAND2, MT7996_TXQ_BAND2);
++				TXQ_CONFIG(2, WFDMA0, MT_INT_TX_DONE_BAND1, MT7996_TXQ_BAND1);
++			} else {
++				/* default bn1:ring19 bn2:ring21 */
++				TXQ_CONFIG(1, WFDMA0, MT_INT_TX_DONE_BAND1, MT7996_TXQ_BAND1);
++				TXQ_CONFIG(2, WFDMA0, MT_INT_TX_DONE_BAND2, MT7996_TXQ_BAND2);
++			}
++		} else {
++			/* single pcie bn0/1:ring18 bn2:ring19 */
++			TXQ_CONFIG(2, WFDMA0, MT_INT_TX_DONE_BAND1, MT7996_TXQ_BAND1);
++		}
+ 	} else {
+ 		TXQ_CONFIG(1, WFDMA0, MT_INT_TX_DONE_BAND1, MT7996_TXQ_BAND1);
+ 	}
+@@ -352,8 +372,20 @@ static void mt7996_dma_enable(struct mt7996_dev *dev, bool reset)
+ 			 WF_WFDMA0_GLO_CFG_EXT1_TX_FCTRL_MODE);
+ 
+ 		mt76_set(dev, MT_WFDMA_HOST_CONFIG,
+-			 MT_WFDMA_HOST_CONFIG_PDMA_BAND |
+-			 MT_WFDMA_HOST_CONFIG_BAND2_PCIE1);
++			 MT_WFDMA_HOST_CONFIG_PDMA_BAND);
++
++		mt76_clear(dev, MT_WFDMA_HOST_CONFIG,
++			   MT_WFDMA_HOST_CONFIG_BAND0_PCIE1 |
++			   MT_WFDMA_HOST_CONFIG_BAND1_PCIE1 |
++			   MT_WFDMA_HOST_CONFIG_BAND2_PCIE1);
++
++		if (dev->option_type == 2)
++			mt76_set(dev, MT_WFDMA_HOST_CONFIG,
++				 MT_WFDMA_HOST_CONFIG_BAND0_PCIE1 |
++				 MT_WFDMA_HOST_CONFIG_BAND1_PCIE1);
++		else
++			mt76_set(dev, MT_WFDMA_HOST_CONFIG,
++				 MT_WFDMA_HOST_CONFIG_BAND2_PCIE1);
+ 
+ 		if (mtk_wed_device_active(&dev->mt76.mmio.wed) &&
+ 		    is_mt7992(&dev->mt76)) {
+@@ -366,6 +398,15 @@ static void mt7996_dma_enable(struct mt7996_dev *dev, bool reset)
+ 		mt76_rmw(dev, MT_WFDMA_AXI_R2A_CTRL,
+ 			 MT_WFDMA_AXI_R2A_CTRL_OUTSTAND_MASK, 0x14);
+ 
++		if (dev->hif2->speed < PCIE_SPEED_8_0GT ||
++		    (dev->hif2->speed == PCIE_SPEED_8_0GT && dev->hif2->width < 2)) {
++			mt76_rmw(dev, WF_WFDMA0_GLO_CFG_EXT0 + hif1_ofs,
++				 WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK,
++				 FIELD_PREP(WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK, 0x3));
++			mt76_rmw(dev, MT_WFDMA_AXI_R2A_CTRL2,
++				 MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK,
++				 FIELD_PREP(MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK, 0x3));
++		}
+ 		/* WFDMA rx threshold */
+ 		mt76_wr(dev, MT_WFDMA0_PAUSE_RX_Q_45_TH + hif1_ofs, 0xc000c);
+ 		mt76_wr(dev, MT_WFDMA0_PAUSE_RX_Q_67_TH + hif1_ofs, 0x10008);
+diff --git a/mt7996/init.c b/mt7996/init.c
+index 90f3a4176..85fedca6c 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -500,7 +500,7 @@ static void mt7996_mac_init_basic_rates(struct mt7996_dev *dev)
+ void mt7996_mac_init(struct mt7996_dev *dev)
+ {
+ #define HIF_TXD_V2_1	0x21
+-	int i;
++	int i, rx_path_type, rro_bypass, txfree_path;
+ 
+ 	mt76_clear(dev, MT_MDP_DCR2, MT_MDP_DCR2_RX_TRANS_SHORT);
+ 
+@@ -514,22 +514,45 @@ void mt7996_mac_init(struct mt7996_dev *dev)
+ 	}
+ 
+ 	/* rro module init */
+-	if (is_mt7996(&dev->mt76))
+-		mt7996_mcu_set_rro(dev, UNI_RRO_SET_PLATFORM_TYPE, 2);
+-	else
+-		mt7996_mcu_set_rro(dev, UNI_RRO_SET_PLATFORM_TYPE,
+-				   dev->hif2 ? 7 : 0);
++	switch (dev->option_type) {
++	case 2:
++		/* eagle + 7988d */
++		rx_path_type = 3;
++		rro_bypass = dev->has_rro ? 1 : 3;
++		txfree_path = dev->has_rro ? 0 : 1;
++		break;
++	case 3:
++		/* eagle + Airoha */
++		rx_path_type = 6;
++		rro_bypass = dev->has_rro ? 1 : 3;
++		txfree_path = dev->has_rro ? 0 : 1;
++		break;
++	case 4:
++		/* Bollinger */
++		rx_path_type = 2;
++		rro_bypass = dev->has_rro ? 1 : 3;
++		txfree_path = dev->has_rro ? 0 : 1;
++		break;
++	default:
++		if (is_mt7996(&dev->mt76))
++			rx_path_type = 2;
++		else
++			rx_path_type = 7;
++
++		rro_bypass = dev->has_rro ? 1 : 3;
++		txfree_path = dev->has_rro ? 0 : 1;
++		break;
++	}
++
++	mt7996_mcu_set_rro(dev, UNI_RRO_SET_PLATFORM_TYPE, dev->hif2 ? rx_path_type : 0);
++	mt7996_mcu_set_rro(dev, UNI_RRO_SET_BYPASS_MODE, rro_bypass);
++	mt7996_mcu_set_rro(dev, UNI_RRO_SET_TXFREE_PATH, txfree_path);
+ 
+ 	if (dev->has_rro) {
+ 		u16 timeout;
+ 
+ 		timeout = mt76_rr(dev, MT_HW_REV) == MT_HW_REV1 ? 512 : 128;
+ 		mt7996_mcu_set_rro(dev, UNI_RRO_SET_FLUSH_TIMEOUT, timeout);
+-		mt7996_mcu_set_rro(dev, UNI_RRO_SET_BYPASS_MODE, 1);
+-		mt7996_mcu_set_rro(dev, UNI_RRO_SET_TXFREE_PATH, 0);
+-	} else {
+-		mt7996_mcu_set_rro(dev, UNI_RRO_SET_BYPASS_MODE, 3);
+-		mt7996_mcu_set_rro(dev, UNI_RRO_SET_TXFREE_PATH, 1);
+ 	}
+ 
+ 	mt7996_mcu_wa_cmd(dev, MCU_WA_PARAM_CMD(SET),
+@@ -607,9 +630,22 @@ static int mt7996_register_phy(struct mt7996_dev *dev, struct mt7996_phy *phy,
+ 	if (phy)
+ 		return 0;
+ 
+-	if (is_mt7996(&dev->mt76) && band == MT_BAND2 && dev->hif2) {
+-		hif1_ofs = MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0);
+-		wed = &dev->mt76.mmio.wed_hif2;
++	if (is_mt7996(&dev->mt76) && dev->hif2) {
++		switch (dev->option_type) {
++		case 2:
++			/* eagle + 7988d */
++			if (band == MT_BAND1) {
++				hif1_ofs = MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0);
++				wed = &dev->mt76.mmio.wed_hif2;
++			}
++			break;
++		default:
++			if (band == MT_BAND2) {
++				hif1_ofs = MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0);
++				wed = &dev->mt76.mmio.wed_hif2;
++			}
++			break;
++		}
+ 	}
+ 
+ 	mphy = mt76_alloc_phy(&dev->mt76, sizeof(*phy), &mt7996_ops, band);
+@@ -1048,6 +1084,9 @@ int mt7996_get_chip_sku(struct mt7996_dev *dev)
+ static int mt7996_init_hardware(struct mt7996_dev *dev)
+ {
+ 	int ret, idx;
++	struct device_node *np = dev->mt76.dev->of_node;
++
++	of_property_read_u32(np, "option_type", &dev->option_type);
+ 
+ 	mt76_wr(dev, MT_INT_SOURCE_CSR, ~0);
+ 	if (is_mt7992(&dev->mt76)) {
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 6eadc0294..b19ba3363 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -1593,8 +1593,19 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw,
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	struct mtk_wed_device *wed = &dev->mt76.mmio.wed;
+ 
+-	if (phy != &dev->phy && phy->mt76->band_idx == MT_BAND2)
+-		wed = &dev->mt76.mmio.wed_hif2;
++	if (phy != &dev->phy && dev->hif2) {
++		switch (dev->option_type) {
++		case 2:
++			/* eagle + 7988d */
++			if (phy->mt76->band_idx == MT_BAND1)
++				wed = &dev->mt76.mmio.wed_hif2;
++			break;
++		default:
++			if (phy->mt76->band_idx == MT_BAND2)
++				wed = &dev->mt76.mmio.wed_hif2;
++			break;
++		}
++	}
+ 
+ 	if (!mtk_wed_device_active(wed))
+ 		return -ENODEV;
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 9060d49b1..cb782261a 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -8,6 +8,7 @@
+ 
+ #include <linux/interrupt.h>
+ #include <linux/ktime.h>
++#include <linux/pci.h>
+ #include "../mt76_connac.h"
+ #include "regs.h"
+ 
+@@ -350,6 +351,8 @@ struct mt7996_hif {
+ 	struct device *dev;
+ 	void __iomem *regs;
+ 	int irq;
++	enum pci_bus_speed speed;
++	enum pcie_link_width width;
+ };
+ 
+ struct mt7996_scs_ctrl {
+@@ -574,6 +577,8 @@ struct mt7996_dev {
+ 	u8 eeprom_mode;
+ 	u32 bg_nxt_freq;
+ 
++	u32 option_type;
++
+ 	bool ibf;
+ 	u8 fw_debug_wm;
+ 	u8 fw_debug_wa;
+diff --git a/mt7996/pci.c b/mt7996/pci.c
+index f0d3f199c..24d69d4dc 100644
+--- a/mt7996/pci.c
++++ b/mt7996/pci.c
+@@ -5,7 +5,6 @@
+ 
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+-#include <linux/pci.h>
+ 
+ #include "mt7996.h"
+ #include "mac.h"
+@@ -93,6 +92,7 @@ static int mt7996_pci_hif2_probe(struct pci_dev *pdev)
+ 	hif->dev = &pdev->dev;
+ 	hif->regs = pcim_iomap_table(pdev)[0];
+ 	hif->irq = pdev->irq;
++	pcie_bandwidth_available(pdev, NULL, &hif->speed, &hif->width);
+ 	spin_lock_bh(&hif_lock);
+ 	list_add(&hif->list, &hif_list);
+ 	spin_unlock_bh(&hif_lock);
+diff --git a/mt7996/regs.h b/mt7996/regs.h
+index 476b23c3d..050637c1b 100644
+--- a/mt7996/regs.h
++++ b/mt7996/regs.h
+@@ -435,6 +435,7 @@ enum offs_rev {
+ #define WF_WFDMA0_GLO_CFG_EXT0			MT_WFDMA0(0x2b0)
+ #define WF_WFDMA0_GLO_CFG_EXT0_RX_WB_RXD	BIT(18)
+ #define WF_WFDMA0_GLO_CFG_EXT0_WED_MERGE_MODE	BIT(14)
++#define WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK	GENMASK(27, 24)
+ 
+ #define WF_WFDMA0_GLO_CFG_EXT1			MT_WFDMA0(0x2b4)
+ #define WF_WFDMA0_GLO_CFG_EXT1_CALC_MODE	BIT(31)
+@@ -454,6 +455,7 @@ enum offs_rev {
+ 
+ #define MT_WFDMA_HOST_CONFIG			MT_WFDMA_EXT_CSR(0x30)
+ #define MT_WFDMA_HOST_CONFIG_PDMA_BAND		BIT(0)
++#define MT_WFDMA_HOST_CONFIG_BAND0_PCIE1	BIT(20)
+ #define MT_WFDMA_HOST_CONFIG_BAND1_PCIE1	BIT(21)
+ #define MT_WFDMA_HOST_CONFIG_BAND2_PCIE1	BIT(22)
+ 
+@@ -463,6 +465,9 @@ enum offs_rev {
+ #define MT_WFDMA_AXI_R2A_CTRL			MT_WFDMA_EXT_CSR(0x500)
+ #define MT_WFDMA_AXI_R2A_CTRL_OUTSTAND_MASK	GENMASK(4, 0)
+ 
++#define MT_WFDMA_AXI_R2A_CTRL2			MT_WFDMA_EXT_CSR(0x508)
++#define MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK	GENMASK(31, 28)
++
+ #define MT_PCIE_RECOG_ID			0xd7090
+ #define MT_PCIE_RECOG_ID_MASK			GENMASK(30, 0)
+ #define MT_PCIE_RECOG_ID_SEM			BIT(31)
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0095-mtk-wifi-mt76-mt7996-support-enable-disable-thermal-.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0095-mtk-wifi-mt76-mt7996-support-enable-disable-thermal-.patch
new file mode 100644
index 0000000..69f2cea
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0095-mtk-wifi-mt76-mt7996-support-enable-disable-thermal-.patch
@@ -0,0 +1,115 @@
+From b3e6373bc9ff270f49523668b69aabec308b5563 Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Thu, 27 Jul 2023 19:35:32 +0800
+Subject: [PATCH 095/120] mtk: wifi: mt76: mt7996: support enable/disable
+ thermal protection mechanism
+
+This commit adds a new debugfs thermal_enable to enable/disable thermal
+protection mechanism. The purpose of this commit is for autotest to
+verify thermal protection mechanism.
+
+[Usage]
+Enable thermal protection: echo 1 > thermal_enable
+Disable thermal protection: echo 0 > thermal_enable
+
+Please note that if you re-enable thermal protection mechanism, all the
+configuration values will be retained from the exising configuration,
+rather than using the default values.
+
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+---
+ mt7996/main.c        |  1 +
+ mt7996/mt7996.h      |  1 +
+ mt7996/mtk_debugfs.c | 45 ++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 47 insertions(+)
+
+diff --git a/mt7996/main.c b/mt7996/main.c
+index b19ba3363..4327ab373 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -91,6 +91,7 @@ int mt7996_run(struct ieee80211_hw *hw)
+ #ifdef CONFIG_MTK_DEBUG
+ 	phy->sr_enable = true;
+ 	phy->enhanced_sr_enable = true;
++	phy->thermal_protection_enable = true;
+ 
+ 	ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_SKU_POWER_LIMIT_CTRL,
+ 					   !dev->dbg.sku_disable);
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index cb782261a..c8348bbcf 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -483,6 +483,7 @@ struct mt7996_phy {
+ #ifdef CONFIG_MTK_DEBUG
+ 	bool sr_enable:1;
+ 	bool enhanced_sr_enable:1;
++	bool thermal_protection_enable:1;
+ #endif
+ };
+ 
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index b5e81e904..31319a46c 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -3110,6 +3110,49 @@ mt7996_show_rro_mib(struct seq_file *s, void *data)
+ 	return 0;
+ }
+ 
++static int
++mt7996_thermal_enable_get(void *data, u64 *enable)
++{
++	struct mt7996_phy *phy = data;
++
++	*enable = phy->thermal_protection_enable;
++
++	return 0;
++}
++
++static int
++mt7996_thermal_enable_set(void *data, u64 action)
++{
++	struct mt7996_phy *phy = data;
++	int ret;
++	u8 throttling;
++
++	if (action > 1)
++		return -EINVAL;
++
++	if (!!action == phy->thermal_protection_enable)
++		return 0;
++
++	ret = mt7996_mcu_set_thermal_protect(phy, !!action);
++	if (ret)
++		return ret;
++
++	if (!!!action)
++		goto out;
++
++	throttling = MT7996_THERMAL_THROTTLE_MAX - phy->cdev_state;
++	ret = mt7996_mcu_set_thermal_throttling(phy, throttling);
++	if (ret)
++		return ret;
++
++out:
++	phy->thermal_protection_enable = !!action;
++
++	return 0;
++}
++DEFINE_DEBUGFS_ATTRIBUTE(fops_thermal_enable, mt7996_thermal_enable_get,
++			 mt7996_thermal_enable_set, "%lld\n");
++
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -3217,6 +3260,8 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ 					    mt7996_show_rro_mib);
+ 	}
+ 
++	debugfs_create_file("thermal_enable", 0600, dir, phy, &fops_thermal_enable);
++
+ 	return 0;
+ }
+ 
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0096-mtk-wifi-mt76-mt7996-support-thermal-recal-debug-com.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0096-mtk-wifi-mt76-mt7996-support-thermal-recal-debug-com.patch
new file mode 100644
index 0000000..d233469
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0096-mtk-wifi-mt76-mt7996-support-thermal-recal-debug-com.patch
@@ -0,0 +1,118 @@
+From 370321466ca479086d6e41c817552450030bd27a Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Thu, 4 Jan 2024 19:53:37 +0800
+Subject: [PATCH 096/120] mtk: wifi: mt76: mt7996: support thermal recal debug
+ command
+
+Add support thermal recal debug command.
+
+Usage:
+$ echo val > debugfs/thermal_recal
+
+The val can be the following values:
+0 = disable
+1 = enable
+2 = manual trigger
+
+CR-Id: WCNCR00261410
+Change-Id: Ief064633dd7ab0faeb298ac3902ca1b399e70365
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+---
+ mt76_connac_mcu.h    |  1 +
+ mt7996/mt7996.h      |  1 +
+ mt7996/mtk_debugfs.c | 17 +++++++++++++++++
+ mt7996/mtk_mcu.c     | 21 +++++++++++++++++++++
+ 4 files changed, 40 insertions(+)
+
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 12fdc6e12..151183d08 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1285,6 +1285,7 @@ enum {
+ 	MCU_UNI_CMD_TESTMODE_TRX_PARAM = 0x42,
+ 	MCU_UNI_CMD_TESTMODE_CTRL = 0x46,
+ 	MCU_UNI_CMD_PRECAL_RESULT = 0x47,
++	MCU_UNI_CMD_THERMAL_CAL = 0x4c,
+ 	MCU_UNI_CMD_RRO = 0x57,
+ 	MCU_UNI_CMD_OFFCH_SCAN_CTRL = 0x58,
+ 	MCU_UNI_CMD_PER_STA_INFO = 0x6d,
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index c8348bbcf..34c3167e7 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -1028,6 +1028,7 @@ void mt7996_mcu_set_cert(struct mt7996_phy *phy, u8 type);
+ void mt7996_tm_update_channel(struct mt7996_phy *phy);
+ 
+ int mt7996_mcu_set_vow_drr_dbg(struct mt7996_dev *dev, u32 val);
++int mt7996_mcu_thermal_debug(struct mt7996_dev *dev, u8 mode, u8 action);
+ #endif
+ 
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index 31319a46c..5f6c177e4 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -3153,6 +3153,22 @@ out:
+ DEFINE_DEBUGFS_ATTRIBUTE(fops_thermal_enable, mt7996_thermal_enable_get,
+ 			 mt7996_thermal_enable_set, "%lld\n");
+ 
++static int
++mt7996_thermal_recal_set(void *data, u64 val)
++{
++#define THERMAL_DEBUG_OPERATION_MANUAL_TRIGGER 2
++#define THERMAL_DEBUG_MODE_RECAL 1
++	struct mt7996_dev *dev = data;
++
++	if (val > THERMAL_DEBUG_OPERATION_MANUAL_TRIGGER)
++		return -EINVAL;
++
++	return mt7996_mcu_thermal_debug(dev, THERMAL_DEBUG_MODE_RECAL, val);
++}
++
++DEFINE_DEBUGFS_ATTRIBUTE(fops_thermal_recal, NULL,
++			 mt7996_thermal_recal_set, "%llu\n");
++
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -3261,6 +3277,7 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ 	}
+ 
+ 	debugfs_create_file("thermal_enable", 0600, dir, phy, &fops_thermal_enable);
++	debugfs_create_file("thermal_recal", 0200, dir, dev, &fops_thermal_recal);
+ 
+ 	return 0;
+ }
+diff --git a/mt7996/mtk_mcu.c b/mt7996/mtk_mcu.c
+index aed32e982..aa44b4710 100644
+--- a/mt7996/mtk_mcu.c
++++ b/mt7996/mtk_mcu.c
+@@ -1342,4 +1342,25 @@ int mt7996_mcu_set_vow_drr_dbg(struct mt7996_dev *dev, u32 val)
+ 				 sizeof(req), true);
+ }
+ 
++int mt7996_mcu_thermal_debug(struct mt7996_dev *dev, u8 mode, u8 action)
++{
++	struct {
++		u8 __rsv1[4];
++
++		__le16 tag;
++		__le16 len;
++
++		u8 mode;
++		u8 action;
++		u8 __rsv2[2];
++	} __packed req = {
++		.tag = cpu_to_le16(mode),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.mode = mode,
++		.action = action,
++	};
++
++	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(THERMAL_CAL), &req,
++	                         sizeof(req), true);
++}
+ #endif
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0097-mtk-wifi-mt76-mt7996-Porting-wifi6-txpower-fix-to-ea.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0097-mtk-wifi-mt76-mt7996-Porting-wifi6-txpower-fix-to-ea.patch
new file mode 100644
index 0000000..eb89374
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0097-mtk-wifi-mt76-mt7996-Porting-wifi6-txpower-fix-to-ea.patch
@@ -0,0 +1,474 @@
+From 2fa971f5b8d2adb09d01257301a9fa36b6b66a94 Mon Sep 17 00:00:00 2001
+From: Allen Ye <allen.ye@mediatek.com>
+Date: Thu, 25 Jan 2024 10:57:08 +0800
+Subject: [PATCH 097/120] mtk: wifi: mt76: mt7996: Porting wifi6 txpower fix to
+ eagle
+
+Refactor txpower flow.
+1. Fix wrong bbp CR address
+2. Ignore RegDB power limit when we have single sku table. And dump more informaiton in debugfs.
+3. Refactor get_txpower ops flow, we only consider CCK and OFDM power value as maximum.
+4. Remove sku_disable due to SQC is over and default enable both sku tables.
+
+CR-Id: WCNCR00259302
+Change-Id: I49fd6143edbca691a2d7bd8377fa94aefb97df94
+
+Fix wrong power value when user set limit close to path table limit.
+
+CR-Id: WCNCR00259302
+Change-Id: Id662ae8a14139dc6444e1c21d71b1715095a8e5e
+---
+ eeprom.c             |  20 ++++----
+ mt7996/init.c        |  14 ++++-
+ mt7996/main.c        |  11 ++--
+ mt7996/mcu.c         |  41 ++++++++++++---
+ mt7996/mt7996.h      |   3 ++
+ mt7996/mtk_debugfs.c | 120 ++++++++++++++++++++++++++++---------------
+ mt7996/regs.h        |  10 ++--
+ 7 files changed, 149 insertions(+), 70 deletions(-)
+
+diff --git a/eeprom.c b/eeprom.c
+index 47edb21e5..3da94926e 100644
+--- a/eeprom.c
++++ b/eeprom.c
+@@ -330,9 +330,10 @@ mt76_apply_array_limit(s8 *pwr, size_t pwr_len, const __be32 *data,
+ static void
+ mt76_apply_multi_array_limit(s8 *pwr, size_t pwr_len, s8 pwr_num,
+ 			     const __be32 *data, size_t len, s8 target_power,
+-			     s8 nss_delta, s8 *max_power)
++			     s8 nss_delta)
+ {
+ 	int i, cur;
++	s8 max_power = -128;
+ 
+ 	if (!data)
+ 		return;
+@@ -344,7 +345,7 @@ mt76_apply_multi_array_limit(s8 *pwr, size_t pwr_len, s8 pwr_num,
+ 			break;
+ 
+ 		mt76_apply_array_limit(pwr + pwr_len * i, pwr_len, data + 1,
+-				       target_power, nss_delta, max_power);
++				       target_power, nss_delta, &max_power);
+ 		if (--cur > 0)
+ 			continue;
+ 
+@@ -427,17 +428,17 @@ s8 mt76_get_rate_power_limits(struct mt76_phy *phy,
+ 	val = mt76_get_of_array(np, "rates-mcs", &len, mcs_rates + 1);
+ 	mt76_apply_multi_array_limit(dest->mcs[0], ARRAY_SIZE(dest->mcs[0]),
+ 				     ARRAY_SIZE(dest->mcs), val, len,
+-				     target_power, txs_delta, &max_power);
++				     target_power, txs_delta);
+ 
+ 	val = mt76_get_of_array(np, "rates-ru", &len, ARRAY_SIZE(dest->ru[0]) + 1);
+ 	mt76_apply_multi_array_limit(dest->ru[0], ARRAY_SIZE(dest->ru[0]),
+ 				     ARRAY_SIZE(dest->ru), val, len,
+-				     target_power, txs_delta, &max_power);
++				     target_power, txs_delta);
+ 
+ 	val = mt76_get_of_array(np, "rates-eht", &len, ARRAY_SIZE(dest->eht[0]) + 1);
+ 	mt76_apply_multi_array_limit(dest->eht[0], ARRAY_SIZE(dest->eht[0]),
+ 				     ARRAY_SIZE(dest->eht), val, len,
+-				     target_power, txs_delta, &max_power);
++				     target_power, txs_delta);
+ 
+ 	if (dest_path == NULL)
+ 		return max_power;
+@@ -459,17 +460,14 @@ s8 mt76_get_rate_power_limits(struct mt76_phy *phy,
+ 	val = mt76_get_of_array(np, "paths-ru", &len, ARRAY_SIZE(dest_path->ru[0]) + 1);
+ 	mt76_apply_multi_array_limit(dest_path->ru[0], ARRAY_SIZE(dest_path->ru[0]),
+ 				     ARRAY_SIZE(dest_path->ru), val, len,
+-				     target_power_combine, txs_delta, &max_power_backoff);
++				     target_power_combine, txs_delta);
+ 
+ 	val = mt76_get_of_array(np, "paths-ru-bf", &len, ARRAY_SIZE(dest_path->ru_bf[0]) + 1);
+ 	mt76_apply_multi_array_limit(dest_path->ru_bf[0], ARRAY_SIZE(dest_path->ru_bf[0]),
+ 				     ARRAY_SIZE(dest_path->ru_bf), val, len,
+-				     target_power_combine, txs_delta, &max_power_backoff);
++				     target_power_combine, txs_delta);
+ 
+-	if (max_power_backoff == target_power_combine)
+-		return max_power;
+-
+-	return max_power_backoff;
++	return max_power;
+ }
+ EXPORT_SYMBOL_GPL(mt76_get_rate_power_limits);
+ 
+diff --git a/mt7996/init.c b/mt7996/init.c
+index 85fedca6c..bc8cfdbde 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -296,7 +296,11 @@ static void __mt7996_init_txpower(struct mt7996_phy *phy,
+ 	int pwr_delta = mt7996_eeprom_get_power_delta(dev, sband->band);
+ 	struct mt76_power_limits limits;
+ 	struct mt76_power_path_limits limits_path;
++	struct device_node *np;
+ 
++	phy->sku_limit_en = true;
++	phy->sku_path_en = true;
++	np = mt76_find_power_limits_node(&dev->mt76);
+ 	for (i = 0; i < sband->n_channels; i++) {
+ 		struct ieee80211_channel *chan = &sband->channels[i];
+ 		int target_power = mt7996_eeprom_get_target_power(dev, chan);
+@@ -306,10 +310,16 @@ static void __mt7996_init_txpower(struct mt7996_phy *phy,
+ 							  &limits,
+ 							  &limits_path,
+ 							  target_power);
++		if (!limits_path.ofdm[0])
++			phy->sku_path_en = false;
++
+ 		target_power += nss_delta;
+ 		target_power = DIV_ROUND_UP(target_power, 2);
+-		chan->max_power = min_t(int, chan->max_reg_power,
+-					target_power);
++		if (!np)
++			chan->max_power = min_t(int, chan->max_reg_power,
++						target_power);
++		else
++			chan->max_power = target_power;
+ 		chan->orig_mpwr = target_power;
+ 	}
+ }
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 4327ab373..2ae3d82c6 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -92,11 +92,16 @@ int mt7996_run(struct ieee80211_hw *hw)
+ 	phy->sr_enable = true;
+ 	phy->enhanced_sr_enable = true;
+ 	phy->thermal_protection_enable = true;
+-
+ 	ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_SKU_POWER_LIMIT_CTRL,
+-					   !dev->dbg.sku_disable);
++						dev->dbg.sku_disable ? 0 : phy->sku_limit_en);
++
++	ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_BACKOFF_POWER_LIMIT_CTRL,
++						dev->dbg.sku_disable ? 0 : phy->sku_path_en);
+ #else
+-	ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_SKU_POWER_LIMIT_CTRL, true);
++	ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_SKU_POWER_LIMIT_CTRL,
++						phy->sku_limit_en);
++	ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_BACKOFF_POWER_LIMIT_CTRL,
++						phy->sku_path_en);
+ #endif
+ 	if (ret)
+ 		goto out;
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 1827e8092..cd529a941 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -5131,6 +5131,27 @@ int mt7996_mcu_wed_rro_reset_sessions(struct mt7996_dev *dev, u16 id)
+ 				 sizeof(req), true);
+ }
+ 
++static void
++mt7996_update_max_txpower_cur(struct mt7996_phy *phy, int tx_power)
++{
++	struct mt76_phy *mphy = phy->mt76;
++	struct ieee80211_channel *chan = mphy->main_chan;
++	int e2p_power_limit = 0;
++
++	if (chan == NULL) {
++		mphy->txpower_cur = tx_power;
++		return;
++	}
++
++	e2p_power_limit = mt7996_eeprom_get_target_power(phy->dev, chan);
++	e2p_power_limit += mt7996_eeprom_get_power_delta(phy->dev, chan->band);
++
++	if (phy->sku_limit_en)
++		mphy->txpower_cur = min_t(int, e2p_power_limit, tx_power);
++	else
++		mphy->txpower_cur = e2p_power_limit;
++}
++
+ int mt7996_mcu_set_txpower_sku(struct mt7996_phy *phy)
+ {
+ #define TX_POWER_LIMIT_TABLE_RATE	0
+@@ -5156,12 +5177,20 @@ int mt7996_mcu_set_txpower_sku(struct mt7996_phy *phy)
+ 	struct mt76_power_limits la = {};
+ 	struct mt76_power_path_limits la_path = {};
+ 	struct sk_buff *skb;
+-	int i, ret, tx_power;
++	int i, ret, txpower_limit;
++
++	if (hw->conf.power_level == INT_MIN)
++		hw->conf.power_level = 127;
++	txpower_limit = mt7996_get_power_bound(phy, hw->conf.power_level);
+ 
+-	tx_power = mt7996_get_power_bound(phy, hw->conf.power_level);
+-	tx_power = mt76_get_rate_power_limits(mphy, mphy->chandef.chan,
+-					      &la, &la_path, tx_power);
+-	mphy->txpower_cur = tx_power;
++	if (phy->sku_limit_en) {
++		txpower_limit = mt76_get_rate_power_limits(mphy, mphy->chandef.chan,
++							   &la, &la_path, txpower_limit);
++		mt7996_update_max_txpower_cur(phy, txpower_limit);
++	} else {
++		mt7996_update_max_txpower_cur(phy, txpower_limit);
++		return 0;
++	}
+ 
+ 	skb = mt76_mcu_msg_alloc(&dev->mt76, NULL,
+ 				 sizeof(req) + MT7996_SKU_PATH_NUM);
+@@ -5197,7 +5226,7 @@ int mt7996_mcu_set_txpower_sku(struct mt7996_phy *phy)
+ 		return ret;
+ 
+ 	/* only set per-path power table when it's configured */
+-	if (!la_path.ofdm[0])
++	if (!phy->sku_path_en)
+ 		return 0;
+ 
+ 	skb = mt76_mcu_msg_alloc(&dev->mt76, NULL,
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 34c3167e7..9135c9182 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -461,6 +461,9 @@ struct mt7996_phy {
+ 
+ 	u8 muru_onoff;
+ 
++	bool sku_limit_en;
++	bool sku_path_en;
++
+ #ifdef CONFIG_NL80211_TESTMODE
+ 	struct {
+ 		u32 *reg_backup;
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index 5f6c177e4..8787a9493 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -2454,6 +2454,7 @@ mt7996_get_txpower_info(struct file *file, char __user *user_buf,
+ 	struct mt7996_phy *phy = file->private_data;
+ 	struct mt7996_mcu_txpower_event *event;
+ 	struct txpower_basic_info *basic_info;
++	struct device_node *np;
+ 	static const size_t size = 2048;
+ 	int len = 0;
+ 	ssize_t ret;
+@@ -2510,7 +2511,10 @@ mt7996_get_txpower_info(struct file *file, char __user *user_buf,
+ 	len += scnprintf(buf + len, size - len,
+ 			 "    Theraml Compensation Value: %d\n",
+ 			 basic_info->thermal_compensate_value);
+-
++	np = mt76_find_power_limits_node(phy->mt76->dev);
++	len += scnprintf(buf + len, size - len,
++			 "    RegDB:  %s\n",
++			 !np ? "enable" : "disable");
+ 	ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+ 
+ out:
+@@ -2526,9 +2530,9 @@ static const struct file_operations mt7996_txpower_info_fops = {
+ 	.llseek = default_llseek,
+ };
+ 
+-#define mt7996_txpower_puts(rate)							\
++#define mt7996_txpower_puts(rate, _len)							\
+ ({											\
+-	len += scnprintf(buf + len, size - len, "%-21s:", #rate " (TMAC)");		\
++	len += scnprintf(buf + len, size - len, "%-*s:", _len, #rate " (TMAC)");	\
+ 	for (i = 0; i < mt7996_sku_group_len[SKU_##rate]; i++, offs++)			\
+ 		len += scnprintf(buf + len, size - len, " %6d",				\
+ 				 event->phy_rate_info.frame_power[offs][band_idx]);	\
+@@ -2542,9 +2546,15 @@ mt7996_get_txpower_sku(struct file *file, char __user *user_buf,
+ 	struct mt7996_phy *phy = file->private_data;
+ 	struct mt7996_dev *dev = phy->dev;
+ 	struct mt7996_mcu_txpower_event *event;
++	struct ieee80211_channel *chan = phy->mt76->chandef.chan;
++	struct ieee80211_supported_band sband;
+ 	u8 band_idx = phy->mt76->band_idx;
+ 	static const size_t size = 5120;
+ 	int i, offs = 0, len = 0;
++	u32 target_power = 0;
++	int n_chains = hweight16(phy->mt76->chainmask);
++	int nss_delta = mt76_tx_power_nss_delta(n_chains);
++	int pwr_delta;
+ 	ssize_t ret;
+ 	char *buf;
+ 	u32 reg;
+@@ -2566,41 +2576,45 @@ mt7996_get_txpower_sku(struct file *file, char __user *user_buf,
+ 			 band_idx, phy->mt76->chandef.chan->hw_value);
+ 	len += scnprintf(buf + len, size - len, "%-21s  %6s %6s %6s %6s\n",
+ 			 " ", "1m", "2m", "5m", "11m");
+-	mt7996_txpower_puts(CCK);
++	mt7996_txpower_puts(CCK, 21);
+ 
+ 	len += scnprintf(buf + len, size - len,
+ 			 "%-21s  %6s %6s %6s %6s %6s %6s %6s %6s\n",
+ 			 " ", "6m", "9m", "12m", "18m", "24m", "36m", "48m",
+ 			 "54m");
+-	mt7996_txpower_puts(OFDM);
++	mt7996_txpower_puts(OFDM, 21);
+ 
+ 	len += scnprintf(buf + len, size - len,
+ 			 "%-21s  %6s %6s %6s %6s %6s %6s %6s %6s\n",
+ 			 " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4",
+ 			 "mcs5", "mcs6", "mcs7");
+-	mt7996_txpower_puts(HT20);
++	mt7996_txpower_puts(HT20, 21);
+ 
+ 	len += scnprintf(buf + len, size - len,
+ 			 "%-21s  %6s %6s %6s %6s %6s %6s %6s %6s %6s\n",
+ 			 " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5",
+ 			 "mcs6", "mcs7", "mcs32");
+-	mt7996_txpower_puts(HT40);
++	mt7996_txpower_puts(HT40, 21);
+ 
+ 	len += scnprintf(buf + len, size - len,
+ 			 "%-21s  %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s\n",
+ 			 " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5",
+ 			 "mcs6", "mcs7", "mcs8", "mcs9", "mcs10", "mcs11");
+-	mt7996_txpower_puts(VHT20);
+-	mt7996_txpower_puts(VHT40);
+-	mt7996_txpower_puts(VHT80);
+-	mt7996_txpower_puts(VHT160);
+-	mt7996_txpower_puts(HE26);
+-	mt7996_txpower_puts(HE52);
+-	mt7996_txpower_puts(HE106);
+-	mt7996_txpower_puts(HE242);
+-	mt7996_txpower_puts(HE484);
+-	mt7996_txpower_puts(HE996);
+-	mt7996_txpower_puts(HE2x996);
++	mt7996_txpower_puts(VHT20, 21);
++	mt7996_txpower_puts(VHT40, 21);
++	mt7996_txpower_puts(VHT80, 21);
++	mt7996_txpower_puts(VHT160, 21);
++	mt7996_txpower_puts(HE26, 21);
++	mt7996_txpower_puts(HE52, 21);
++	mt7996_txpower_puts(HE106, 21);
++	len += scnprintf(buf + len, size - len, "BW20/");
++	mt7996_txpower_puts(HE242, 16);
++	len += scnprintf(buf + len, size - len, "BW40/");
++	mt7996_txpower_puts(HE484, 16);
++	len += scnprintf(buf + len, size - len, "BW80/");
++	mt7996_txpower_puts(HE996, 16);
++	len += scnprintf(buf + len, size - len, "BW160/");
++	mt7996_txpower_puts(HE2x996, 15);
+ 
+ 	len += scnprintf(buf + len, size - len,
+ 			 "%-21s  %6s %6s %6s %6s %6s %6s %6s %6s ",
+@@ -2608,22 +2622,27 @@ mt7996_get_txpower_sku(struct file *file, char __user *user_buf,
+ 	len += scnprintf(buf + len, size - len,
+ 			 "%6s %6s %6s %6s %6s %6s %6s %6s\n",
+ 			 "mcs8", "mcs9", "mcs10", "mcs11", "mcs12", "mcs13", "mcs14", "mcs15");
+-	mt7996_txpower_puts(EHT26);
+-	mt7996_txpower_puts(EHT52);
+-	mt7996_txpower_puts(EHT106);
+-	mt7996_txpower_puts(EHT242);
+-	mt7996_txpower_puts(EHT484);
+-	mt7996_txpower_puts(EHT996);
+-	mt7996_txpower_puts(EHT2x996);
+-	mt7996_txpower_puts(EHT4x996);
+-	mt7996_txpower_puts(EHT26_52);
+-	mt7996_txpower_puts(EHT26_106);
+-	mt7996_txpower_puts(EHT484_242);
+-	mt7996_txpower_puts(EHT996_484);
+-	mt7996_txpower_puts(EHT996_484_242);
+-	mt7996_txpower_puts(EHT2x996_484);
+-	mt7996_txpower_puts(EHT3x996);
+-	mt7996_txpower_puts(EHT3x996_484);
++	mt7996_txpower_puts(EHT26, 21);
++	mt7996_txpower_puts(EHT52, 21);
++	mt7996_txpower_puts(EHT106, 21);
++	len += scnprintf(buf + len, size - len, "BW20/");
++	mt7996_txpower_puts(EHT242, 16);
++	len += scnprintf(buf + len, size - len, "BW40/");
++	mt7996_txpower_puts(EHT484, 16);
++	len += scnprintf(buf + len, size - len, "BW80/");
++	mt7996_txpower_puts(EHT996, 16);
++	len += scnprintf(buf + len, size - len, "BW160/");
++	mt7996_txpower_puts(EHT2x996, 15);
++	len += scnprintf(buf + len, size - len, "BW320/");
++	mt7996_txpower_puts(EHT4x996, 15);
++	mt7996_txpower_puts(EHT26_52, 21);
++	mt7996_txpower_puts(EHT26_106, 21);
++	mt7996_txpower_puts(EHT484_242, 21);
++	mt7996_txpower_puts(EHT996_484, 21);
++	mt7996_txpower_puts(EHT996_484_242, 21);
++	mt7996_txpower_puts(EHT2x996_484, 21);
++	mt7996_txpower_puts(EHT3x996, 21);
++	mt7996_txpower_puts(EHT3x996_484, 21);
+ 
+ 	len += scnprintf(buf + len, size - len, "\nePA Gain: %d\n",
+ 			 event->phy_rate_info.epa_gain);
+@@ -2632,16 +2651,33 @@ mt7996_get_txpower_sku(struct file *file, char __user *user_buf,
+ 	len += scnprintf(buf + len, size - len, "Min Power Bound: %d\n",
+ 			 event->phy_rate_info.min_power_bound);
+ 
+-	reg = MT_WF_PHYDFE_BAND_TPC_CTRL_STAT0(band_idx);
++	reg = MT_WF_PHYDFE_TSSI_TXCTRL01(band_idx);
+ 	len += scnprintf(buf + len, size - len,
+-			 "BBP TX Power (target power from TMAC)  : %6ld [0.5 dBm]\n",
+-			 mt76_get_field(dev, reg, MT_WF_PHY_TPC_POWER_TMAC));
++			 "\nBBP TX Power (target power from TMAC)  : %6ld [0.5 dBm]\n",
++			 mt76_get_field(dev, reg, MT_WF_PHYDFE_TSSI_TXCTRL_POWER_TMAC));
+ 	len += scnprintf(buf + len, size - len,
+-			 "BBP TX Power (target power from RMAC)  : %6ld [0.5 dBm]\n",
+-			 mt76_get_field(dev, reg, MT_WF_PHY_TPC_POWER_RMAC));
++			 "RegDB maximum power:\t%d [dBm]\n",
++			 chan->max_reg_power);
++
++	if (chan->band == NL80211_BAND_2GHZ)
++		sband = phy->mt76->sband_2g.sband;
++	else if (chan->band == NL80211_BAND_5GHZ)
++		sband = phy->mt76->sband_5g.sband;
++	else if (chan->band == NL80211_BAND_6GHZ)
++		sband = phy->mt76->sband_6g.sband;
++
++	pwr_delta = mt7996_eeprom_get_power_delta(dev, sband.band);
++
++	target_power = max_t(u32, target_power, mt7996_eeprom_get_target_power(dev, chan));
++	target_power += pwr_delta + nss_delta;
++	target_power = DIV_ROUND_UP(target_power, 2);
++	len += scnprintf(buf + len, size - len,
++			 "eeprom maximum power:\t%d [dBm]\n",
++			 target_power);
++
+ 	len += scnprintf(buf + len, size - len,
+-			 "BBP TX Power (TSSI module power input)  : %6ld [0.5 dBm]\n",
+-			 mt76_get_field(dev, reg, MT_WF_PHY_TPC_POWER_TSSI));
++			 "nss_delta:\t%d [0.5 dBm]\n",
++			 nss_delta);
+ 
+ 	ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+ 
+@@ -2660,7 +2696,7 @@ static const struct file_operations mt7996_txpower_sku_fops = {
+ 
+ #define mt7996_txpower_path_puts(rate, arr_length)					\
+ ({											\
+-	len += scnprintf(buf + len, size - len, "%-23s:", #rate " (TMAC)");		\
++	len += scnprintf(buf + len, size - len, "%23s:", #rate " (TMAC)");		\
+ 	for (i = 0; i < arr_length; i++, offs++)					\
+ 		len += scnprintf(buf + len, size - len, " %4d",				\
+ 				 event->backoff_table_info.frame_power[offs]);		\
+diff --git a/mt7996/regs.h b/mt7996/regs.h
+index 050637c1b..a0e4b3e11 100644
+--- a/mt7996/regs.h
++++ b/mt7996/regs.h
+@@ -718,6 +718,10 @@ enum offs_rev {
+ 						 ((_wf) << 16) + (ofs))
+ #define MT_WF_PHYRX_CSD_IRPI(_band, _wf)	MT_WF_PHYRX_CSD(_band, _wf, 0x1000)
+ 
++/* PHYDFE CTRL */
++#define MT_WF_PHYDFE_TSSI_TXCTRL01(_band)	MT_WF_PHYRX_CSD(_band, 0, 0xc718)
++#define MT_WF_PHYDFE_TSSI_TXCTRL_POWER_TMAC	GENMASK(31, 24)
++
+ /* PHY CTRL */
+ #define MT_WF_PHY_BAND_BASE			0x83080000
+ #define MT_WF_PHY_BAND(_band, ofs)		(MT_WF_PHY_BAND_BASE + \
+@@ -735,12 +739,6 @@ enum offs_rev {
+ #define MT_WF_PHYRX_BAND_RX_CTRL1_IPI_EN	GENMASK(2, 0)
+ #define MT_WF_PHYRX_BAND_RX_CTRL1_STSCNT_EN	GENMASK(11, 9)
+ 
+-/* PHYDFE CTRL */
+-#define MT_WF_PHYDFE_BAND_TPC_CTRL_STAT0(_phy)	MT_WF_PHY_BAND(_phy, 0xe7a0)
+-#define MT_WF_PHY_TPC_POWER_TMAC		GENMASK(15, 8)
+-#define MT_WF_PHY_TPC_POWER_RMAC		GENMASK(23, 16)
+-#define MT_WF_PHY_TPC_POWER_TSSI		GENMASK(31, 24)
+-
+ /* PHYRX CSD BAND */
+ #define MT_WF_PHYRX_CSD_BAND_RXTD12(_band)		MT_WF_PHY_BAND(_band, 0x8230)
+ #define MT_WF_PHYRX_CSD_BAND_RXTD12_IRPI_SW_CLR_ONLY	BIT(18)
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0098-wifi-mt76-mt7996-implement-and-switch-to-hw-scan-cal.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0098-wifi-mt76-mt7996-implement-and-switch-to-hw-scan-cal.patch
new file mode 100644
index 0000000..ea3dbad
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0098-wifi-mt76-mt7996-implement-and-switch-to-hw-scan-cal.patch
@@ -0,0 +1,438 @@
+From 8a5554e8d5f97faaf0a8cc3e7e942a9f20572588 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Fri, 3 Nov 2023 21:44:45 +0800
+Subject: [PATCH 098/120] wifi: mt76: mt7996: implement and switch to hw scan
+ callbacks
+
+To support MLO, hw_scan callbacks are mandatory. However, the
+firmware of AP-segment doesn't support hw_scan commands, so we need
+to implement it in the driver.
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Change-Id: Ic096b10e4a82211be4c5c435d3e34e0a76ea3b9e
+Co-developed-by: Peter Chiu <chui-hao.chiu@mediatek.com>
+Signed-off-by: Peter Chiu <chui-hao.chiu@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mac80211.c      |   3 +-
+ mt76.h          |   2 +
+ mt7996/init.c   |   5 ++
+ mt7996/mac.c    | 133 ++++++++++++++++++++++++++++++++++++++++++++++++
+ mt7996/main.c   |  95 ++++++++++++++++++++++++++++++----
+ mt7996/mcu.c    |   3 +-
+ mt7996/mt7996.h |  11 +++-
+ 7 files changed, 240 insertions(+), 12 deletions(-)
+
+diff --git a/mac80211.c b/mac80211.c
+index c7b222837..4fad03dd9 100644
+--- a/mac80211.c
++++ b/mac80211.c
+@@ -820,7 +820,7 @@ bool mt76_has_tx_pending(struct mt76_phy *phy)
+ }
+ EXPORT_SYMBOL_GPL(mt76_has_tx_pending);
+ 
+-static struct mt76_channel_state *
++struct mt76_channel_state *
+ mt76_channel_state(struct mt76_phy *phy, struct ieee80211_channel *c)
+ {
+ 	struct mt76_sband *msband;
+@@ -836,6 +836,7 @@ mt76_channel_state(struct mt76_phy *phy, struct ieee80211_channel *c)
+ 	idx = c - &msband->sband.channels[0];
+ 	return &msband->chan[idx];
+ }
++EXPORT_SYMBOL_GPL(mt76_channel_state);
+ 
+ void mt76_update_survey_active_time(struct mt76_phy *phy, ktime_t time)
+ {
+diff --git a/mt76.h b/mt76.h
+index c7816721d..818f94dab 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -1472,6 +1472,8 @@ void mt76_release_buffered_frames(struct ieee80211_hw *hw,
+ 				  enum ieee80211_frame_release_type reason,
+ 				  bool more_data);
+ bool mt76_has_tx_pending(struct mt76_phy *phy);
++struct mt76_channel_state *
++mt76_channel_state(struct mt76_phy *phy, struct ieee80211_channel *c);
+ void mt76_set_channel(struct mt76_phy *phy);
+ void mt76_update_survey(struct mt76_phy *phy);
+ void mt76_update_survey_active_time(struct mt76_phy *phy, ktime_t time);
+diff --git a/mt7996/init.c b/mt7996/init.c
+index bc8cfdbde..71cf29e3c 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -456,6 +456,9 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed)
+ 
+ 	wiphy->available_antennas_rx = phy->mt76->antenna_mask;
+ 	wiphy->available_antennas_tx = phy->mt76->antenna_mask;
++
++	wiphy->max_scan_ssids = 4;
++	wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN;
+ }
+ 
+ static void
+@@ -668,6 +671,7 @@ static int mt7996_register_phy(struct mt7996_dev *dev, struct mt7996_phy *phy,
+ 	mphy->dev->phys[band] = mphy;
+ 
+ 	INIT_DELAYED_WORK(&mphy->mac_work, mt7996_mac_work);
++	INIT_DELAYED_WORK(&phy->scan_work, mt7996_scan_work);
+ 
+ 	ret = mt7996_eeprom_parse_hw_cap(dev, phy);
+ 	if (ret)
+@@ -1533,6 +1537,7 @@ int mt7996_register_device(struct mt7996_dev *dev)
+ 	dev->mt76.phy.priv = &dev->phy;
+ 	INIT_WORK(&dev->rc_work, mt7996_mac_sta_rc_work);
+ 	INIT_DELAYED_WORK(&dev->mphy.mac_work, mt7996_mac_work);
++	INIT_DELAYED_WORK(&dev->phy.scan_work, mt7996_scan_work);
+ 	INIT_DELAYED_WORK(&dev->scs_work, mt7996_mcu_scs_sta_poll);
+ 	INIT_LIST_HEAD(&dev->sta_rc_list);
+ 	INIT_LIST_HEAD(&dev->twt_list);
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index e3758ff13..d52870718 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -2689,3 +2689,136 @@ void mt7996_mac_twt_teardown_flow(struct mt7996_dev *dev,
+ 	dev->twt.table_mask &= ~BIT(flow->table_id);
+ 	dev->twt.n_agrt--;
+ }
++
++static void
++mt7996_scan_send_probe(struct mt7996_phy *phy, struct cfg80211_ssid *ssid)
++{
++	struct ieee80211_hw *hw = phy->mt76->hw;
++	struct cfg80211_scan_request *req = phy->scan_req;
++	struct ieee80211_vif *vif = phy->scan_vif;
++	struct mt7996_vif *mvif;
++	struct mt76_wcid *wcid;
++	struct ieee80211_tx_info *info;
++	struct sk_buff *skb;
++
++	if (!req || !vif)
++		return;
++
++	mvif = (struct mt7996_vif *)vif->drv_priv;
++	wcid = &mvif->sta.wcid;
++
++	skb = ieee80211_probereq_get(hw, vif->addr,
++				     ssid->ssid, ssid->ssid_len, req->ie_len);
++	if (!skb)
++		return;
++
++	info = IEEE80211_SKB_CB(skb);
++	if (req->no_cck)
++		info->flags |= IEEE80211_TX_CTL_NO_CCK_RATE;
++
++	if (req->ie_len)
++		skb_put_data(skb, req->ie, req->ie_len);
++
++	skb_set_queue_mapping(skb, IEEE80211_AC_VO);
++
++	rcu_read_lock();
++	if (!ieee80211_tx_prepare_skb(hw, vif, skb,
++				      phy->scan_chan->band,
++				      NULL)) {
++		rcu_read_unlock();
++		ieee80211_free_txskb(hw, skb);
++		return;
++	}
++
++	local_bh_disable();
++	mt76_tx(phy->mt76, NULL, wcid, skb);
++	local_bh_enable();
++
++	rcu_read_unlock();
++}
++
++void mt7996_scan_complete(struct mt7996_phy *phy, bool aborted)
++{
++	struct cfg80211_scan_info info = {
++		.aborted = aborted,
++	};
++
++	ieee80211_scan_completed(phy->mt76->hw, &info);
++	phy->scan_chan = NULL;
++	phy->scan_req = NULL;
++	phy->scan_vif = NULL;
++	clear_bit(MT76_SCANNING, &phy->mt76->state);
++}
++
++static void mt7996_scan_check_sta(void *data, struct ieee80211_sta *sta)
++{
++	bool *has_sta = data;
++
++	if (*has_sta)
++		return;
++	*has_sta = true;
++}
++
++void mt7996_scan_work(struct work_struct *work)
++{
++	struct mt7996_phy *phy = container_of(work, struct mt7996_phy, scan_work.work);
++	struct ieee80211_hw *hw = phy->mt76->hw;
++	struct cfg80211_scan_request *req = phy->scan_req;
++	struct cfg80211_chan_def chandef = {};
++	int duration;
++	bool has_sta = false, active_scan = false;
++
++	mutex_lock(&phy->dev->mt76.mutex);
++	if (phy->scan_chan_idx >= req->n_channels) {
++		mt7996_scan_complete(phy, false);
++		mutex_unlock(&phy->dev->mt76.mutex);
++
++		mt7996_set_channel(phy, &hw->conf.chandef);
++
++		return;
++	}
++
++	ieee80211_iterate_stations_atomic(hw, mt7996_scan_check_sta, &has_sta);
++
++	/* go back to operating channel */
++	if (has_sta && phy->scan_chan) {
++		phy->scan_chan = NULL;
++		mutex_unlock(&phy->dev->mt76.mutex);
++
++		mt7996_set_channel(phy, &phy->mt76->chandef);
++
++		ieee80211_queue_delayed_work(hw, &phy->scan_work, HZ / 10);
++
++		return;
++	}
++
++	wiphy_info(hw->wiphy, "hw scan %d MHz\n",
++		   req->channels[phy->scan_chan_idx]->center_freq);
++
++	phy->scan_chan = req->channels[phy->scan_chan_idx++];
++
++	if (!req->n_ssids ||
++	    (phy->scan_chan->flags & (IEEE80211_CHAN_NO_IR |
++				      IEEE80211_CHAN_RADAR))) {
++		duration = HZ / 9; /* ~110 ms */
++	} else {
++		duration = HZ / 16; /* ~60 ms */
++		active_scan = true;
++	}
++
++	cfg80211_chandef_create(&chandef, phy->scan_chan, NL80211_CHAN_HT20);
++	mutex_unlock(&phy->dev->mt76.mutex);
++
++	mt7996_set_channel(phy, &chandef);
++
++	if (active_scan) {
++		int i;
++
++		mutex_lock(&phy->dev->mt76.mutex);
++		for (i = 0; i < req->n_ssids; i++)
++			mt7996_scan_send_probe(phy, &req->ssids[i]);
++		mutex_unlock(&phy->dev->mt76.mutex);
++	}
++
++	ieee80211_queue_delayed_work(hw, &phy->scan_work, duration);
++}
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 2ae3d82c6..21dd858ec 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -311,6 +311,8 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	int idx = msta->wcid.idx;
+ 
++	cancel_delayed_work_sync(&phy->scan_work);
++
+ 	mt7996_mcu_add_sta(dev, vif, NULL, false, false);
+ 	mt7996_mcu_add_bss_info(phy, vif, false);
+ 
+@@ -322,6 +324,10 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
+ 	rcu_assign_pointer(dev->mt76.wcid[idx], NULL);
+ 
+ 	mutex_lock(&dev->mt76.mutex);
++
++	if (test_bit(MT76_SCANNING, &phy->mt76->state))
++		mt7996_scan_complete(phy, true);
++
+ 	dev->mt76.vif_mask &= ~BIT_ULL(mvif->mt76.idx);
+ 	phy->omac_mask &= ~BIT_ULL(mvif->mt76.omac_idx);
+ 	mutex_unlock(&dev->mt76.mutex);
+@@ -334,7 +340,33 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
+ 	mt76_wcid_cleanup(&dev->mt76, &msta->wcid);
+ }
+ 
+-int mt7996_set_channel(struct mt7996_phy *phy)
++static void ___mt7996_set_channel(struct mt7996_phy *phy,
++				 struct cfg80211_chan_def *chandef)
++{
++	struct mt76_dev *mdev = phy->mt76->dev;
++	struct mt76_phy *mphy = phy->mt76;
++	bool offchannel = phy->scan_chan != NULL;
++	int timeout = HZ / 5;
++
++	wait_event_timeout(mdev->tx_wait, !mt76_has_tx_pending(mphy), timeout);
++	mt76_update_survey(mphy);
++
++	if (mphy->chandef.chan->center_freq != chandef->chan->center_freq ||
++	    mphy->chandef.width != chandef->width)
++		mphy->dfs_state = MT_DFS_STATE_UNKNOWN;
++
++	mphy->chandef = *chandef;
++	mphy->chan_state = mt76_channel_state(mphy, chandef->chan);
++
++	if (!offchannel)
++		mphy->main_chan = chandef->chan;
++
++	if (chandef->chan != mphy->main_chan)
++		memset(mphy->chan_state, 0, sizeof(*mphy->chan_state));
++}
++
++static int __mt7996_set_channel(struct mt7996_phy *phy,
++				struct cfg80211_chan_def *chandef)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+ 	int ret;
+@@ -344,7 +376,7 @@ int mt7996_set_channel(struct mt7996_phy *phy)
+ 	mutex_lock(&dev->mt76.mutex);
+ 	set_bit(MT76_RESET, &phy->mt76->state);
+ 
+-	mt76_set_channel(phy->mt76);
++	___mt7996_set_channel(phy, chandef);
+ 
+ 	if (dev->flash_mode) {
+ 		ret = mt7996_mcu_apply_tx_dpd(phy);
+@@ -380,6 +412,19 @@ out:
+ 	return ret;
+ }
+ 
++int mt7996_set_channel(struct mt7996_phy *phy, struct cfg80211_chan_def *chandef)
++{
++	int ret;
++
++	ieee80211_stop_queues(phy->mt76->hw);
++	ret = __mt7996_set_channel(phy, chandef);
++	if (ret)
++		return ret;
++	ieee80211_wake_queues(phy->mt76->hw);
++
++	return 0;
++}
++
+ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+ 			  struct ieee80211_vif *vif, struct ieee80211_sta *sta,
+ 			  struct ieee80211_key_conf *key)
+@@ -469,11 +514,7 @@ static int mt7996_config(struct ieee80211_hw *hw, u32 changed)
+ 			if (ret)
+ 				return ret;
+ 		}
+-		ieee80211_stop_queues(hw);
+-		ret = mt7996_set_channel(phy);
+-		if (ret)
+-			return ret;
+-		ieee80211_wake_queues(hw);
++		mt7996_set_channel(phy, &hw->conf.chandef);
+ 	}
+ 
+ 	if (changed & (IEEE80211_CONF_CHANGE_POWER |
+@@ -1639,6 +1680,42 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw,
+ 
+ #endif
+ 
++static int
++mt7996_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
++	       struct ieee80211_scan_request *hw_req)
++{
++	struct cfg80211_scan_request *req = &hw_req->req;
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++
++	mutex_lock(&phy->dev->mt76.mutex);
++	if (WARN_ON(phy->scan_req || phy->scan_chan)) {
++		mutex_unlock(&phy->dev->mt76.mutex);
++		return -EBUSY;
++	}
++
++	set_bit(MT76_SCANNING, &phy->mt76->state);
++	phy->scan_req = req;
++	phy->scan_vif = vif;
++	phy->scan_chan_idx = 0;
++	mutex_unlock(&phy->dev->mt76.mutex);
++
++	ieee80211_queue_delayed_work(hw, &phy->scan_work, 0);
++
++	return 0;
++}
++
++static void
++mt7996_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
++{
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++
++	cancel_delayed_work_sync(&phy->scan_work);
++
++	mutex_lock(&phy->dev->mt76.mutex);
++	mt7996_scan_complete(phy, true);
++	mutex_unlock(&phy->dev->mt76.mutex);
++}
++
+ const struct ieee80211_ops mt7996_ops = {
+ 	.tx = mt7996_tx,
+ 	.start = mt7996_start,
+@@ -1657,8 +1734,8 @@ const struct ieee80211_ops mt7996_ops = {
+ 	.ampdu_action = mt7996_ampdu_action,
+ 	.set_rts_threshold = mt7996_set_rts_threshold,
+ 	.wake_tx_queue = mt76_wake_tx_queue,
+-	.sw_scan_start = mt76_sw_scan,
+-	.sw_scan_complete = mt76_sw_scan_complete,
++	.hw_scan = mt7996_hw_scan,
++	.cancel_hw_scan = mt7996_cancel_hw_scan,
+ 	.release_buffered_frames = mt76_release_buffered_frames,
+ 	.get_txpower = mt76_get_txpower,
+ 	.channel_switch_beacon = mt7996_channel_switch_beacon,
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index cd529a941..7e7c5ff95 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -3743,7 +3743,8 @@ int mt7996_mcu_set_chan_info(struct mt7996_phy *phy, u16 tag)
+ 	if (phy->mt76->hw->conf.flags & IEEE80211_CONF_MONITOR)
+ 		req.switch_reason = CH_SWITCH_NORMAL;
+ 	else if (phy->mt76->hw->conf.flags & IEEE80211_CONF_OFFCHANNEL ||
+-		 phy->mt76->hw->conf.flags & IEEE80211_CONF_IDLE)
++		 phy->mt76->hw->conf.flags & IEEE80211_CONF_IDLE ||
++		 phy->scan_chan)
+ 		req.switch_reason = CH_SWITCH_SCAN_BYPASS_DPD;
+ 	else if (!cfg80211_reg_can_beacon(phy->mt76->hw->wiphy, chandef,
+ 					  NL80211_IFTYPE_AP))
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 9135c9182..a4172c4cf 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -456,6 +456,13 @@ struct mt7996_phy {
+ 
+ 	bool has_aux_rx;
+ 
++	/* for hw_scan */
++	struct delayed_work scan_work;
++	struct ieee80211_channel *scan_chan;
++	struct cfg80211_scan_request *scan_req;
++	struct ieee80211_vif *scan_vif;
++	int scan_chan_idx;
++
+ 	struct mt7996_scs_ctrl scs_ctrl;
+ 	u32 red_drop;
+ 
+@@ -802,7 +809,7 @@ int mt7996_mcu_add_obss_spr(struct mt7996_phy *phy, struct ieee80211_vif *vif,
+ 			    struct ieee80211_he_obss_pd *he_obss_pd);
+ int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 			     struct ieee80211_sta *sta, bool changed);
+-int mt7996_set_channel(struct mt7996_phy *phy);
++int mt7996_set_channel(struct mt7996_phy *phy, struct cfg80211_chan_def *chandef);
+ int mt7996_mcu_set_chan_info(struct mt7996_phy *phy, u16 tag);
+ int mt7996_mcu_set_tx(struct mt7996_dev *dev, struct ieee80211_vif *vif);
+ int mt7996_mcu_set_fixed_rate_ctrl(struct mt7996_dev *dev,
+@@ -959,6 +966,8 @@ void mt7996_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q,
+ 			 struct sk_buff *skb, u32 *info);
+ bool mt7996_rx_check(struct mt76_dev *mdev, void *data, int len);
+ void mt7996_stats_work(struct work_struct *work);
++void mt7996_scan_work(struct work_struct *work);
++void mt7996_scan_complete(struct mt7996_phy *phy, bool aborted);
+ int mt76_dfs_start_rdd(struct mt7996_dev *dev, bool force);
+ int mt7996_dfs_init_radar_detector(struct mt7996_phy *phy);
+ void mt7996_set_stream_he_eht_caps(struct mt7996_phy *phy);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0099-wifi-mt76-mt7996-implement-and-switch-to-chanctx-cal.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0099-wifi-mt76-mt7996-implement-and-switch-to-chanctx-cal.patch
new file mode 100644
index 0000000..ba6ee43
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0099-wifi-mt76-mt7996-implement-and-switch-to-chanctx-cal.patch
@@ -0,0 +1,354 @@
+From a470bfbe074b6c31723ee43aea7bbebe9122b3dd Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Mon, 6 Nov 2023 16:17:11 +0800
+Subject: [PATCH 099/120] wifi: mt76: mt7996: implement and switch to chanctx
+ callbacks
+
+To support MLO, chanctx callbacks are mandatory, since one VIF (MLD) could
+operate on multiple channels (links).
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Change-Id: Ie4530a3bc2ac9e51045184d5aecca14118177042
+Co-developed-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/init.c   |  22 +++++++
+ mt7996/mac.c    |  10 ++-
+ mt7996/main.c   | 162 +++++++++++++++++++++++++++++++++++++++++++++---
+ mt7996/mt7996.h |  17 +++++
+ 4 files changed, 200 insertions(+), 11 deletions(-)
+
+diff --git a/mt7996/init.c b/mt7996/init.c
+index 71cf29e3c..f91bdd0f0 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -382,6 +382,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed)
+ 
+ 	hw->sta_data_size = sizeof(struct mt7996_sta);
+ 	hw->vif_data_size = sizeof(struct mt7996_vif);
++	hw->chanctx_data_size = sizeof(struct mt7996_chanctx);
+ 
+ 	wiphy->iface_combinations = if_comb;
+ 	wiphy->n_iface_combinations = ARRAY_SIZE(if_comb);
+@@ -416,6 +417,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed)
+ 	ieee80211_hw_set(hw, SUPPORTS_RX_DECAP_OFFLOAD);
+ 	ieee80211_hw_set(hw, WANT_MONITOR_VIF);
+ 	ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID);
++	ieee80211_hw_set(hw, CHANCTX_STA_CSA);
+ 
+ 	hw->max_tx_fragments = 4;
+ 
+@@ -629,6 +631,22 @@ static int mt7996_vow_init(struct mt7996_phy *phy)
+ 	return mt7996_mcu_set_vow_feature_ctrl(phy);
+ }
+ 
++static void mt7996_init_chanctx(struct mt7996_phy *phy)
++{
++	struct ieee80211_supported_band *sband;
++	struct ieee80211_channel *chan;
++
++	if (phy->mt76->band_idx == MT_BAND2)
++		sband = &phy->mt76->sband_6g.sband;
++	else if (phy->mt76->band_idx == MT_BAND1)
++		sband = &phy->mt76->sband_5g.sband;
++	else
++		sband = &phy->mt76->sband_2g.sband;
++
++	chan = &sband->channels[0];
++	cfg80211_chandef_create(&phy->mt76->chandef, chan, NL80211_CHAN_HT20);
++}
++
+ static int mt7996_register_phy(struct mt7996_dev *dev, struct mt7996_phy *phy,
+ 			       enum mt76_band_id band)
+ {
+@@ -711,6 +729,8 @@ static int mt7996_register_phy(struct mt7996_dev *dev, struct mt7996_phy *phy,
+ 	if (ret)
+ 		goto error;
+ 
++	mt7996_init_chanctx(phy);
++
+ 	ret = mt7996_thermal_init(phy);
+ 	if (ret)
+ 		goto error;
+@@ -1568,6 +1588,8 @@ int mt7996_register_device(struct mt7996_dev *dev)
+ 	if (ret)
+ 		return ret;
+ 
++	mt7996_init_chanctx(&dev->phy);
++
+ 	ret = mt7996_thermal_init(&dev->phy);
+ 	if (ret)
+ 		return ret;
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index d52870718..dc16be88a 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -2773,7 +2773,10 @@ void mt7996_scan_work(struct work_struct *work)
+ 		mt7996_scan_complete(phy, false);
+ 		mutex_unlock(&phy->dev->mt76.mutex);
+ 
+-		mt7996_set_channel(phy, &hw->conf.chandef);
++		if (phy->chanctx)
++			mt7996_set_channel(phy, &phy->chanctx->chandef);
++		else
++			mt7996_set_channel(phy, &phy->mt76->chandef);
+ 
+ 		return;
+ 	}
+@@ -2785,7 +2788,10 @@ void mt7996_scan_work(struct work_struct *work)
+ 		phy->scan_chan = NULL;
+ 		mutex_unlock(&phy->dev->mt76.mutex);
+ 
+-		mt7996_set_channel(phy, &phy->mt76->chandef);
++		if (phy->chanctx)
++			mt7996_set_channel(phy, &phy->chanctx->chandef);
++		else
++			mt7996_set_channel(phy, &phy->mt76->chandef);
+ 
+ 		ieee80211_queue_delayed_work(hw, &phy->scan_work, HZ / 10);
+ 
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 21dd858ec..aa52f5d32 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -76,6 +76,11 @@ int mt7996_run(struct ieee80211_hw *hw)
+ 	if (ret)
+ 		goto out;
+ 
++	/* set a parking channel */
++	ret = mt7996_mcu_set_chan_info(phy, UNI_CHANNEL_SWITCH);
++	if (ret)
++		goto out;
++
+ 	ret = mt7996_mcu_set_thermal_throttling(phy, MT7996_THERMAL_THROTTLE_MAX);
+ 	if (ret)
+ 		goto out;
+@@ -508,15 +513,6 @@ static int mt7996_config(struct ieee80211_hw *hw, u32 changed)
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	int ret;
+ 
+-	if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
+-		if (!mt76_testmode_enabled(phy->mt76) && !phy->mt76->test.bf_en) {
+-			ret = mt7996_mcu_edcca_enable(phy, true);
+-			if (ret)
+-				return ret;
+-		}
+-		mt7996_set_channel(phy, &hw->conf.chandef);
+-	}
+-
+ 	if (changed & (IEEE80211_CONF_CHANGE_POWER |
+ 		       IEEE80211_CONF_CHANGE_CHANNEL)) {
+ 		ret = mt7996_mcu_set_txpower_sku(phy);
+@@ -1716,6 +1712,148 @@ mt7996_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+ 	mutex_unlock(&phy->dev->mt76.mutex);
+ }
+ 
++static int
++mt7996_add_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *conf)
++{
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_chanctx *ctx = mt7996_chanctx_get(conf);
++	int ret;
++
++	wiphy_info(hw->wiphy, "%s: add %u\n", __func__, conf->def.chan->hw_value);
++	mutex_lock(&phy->dev->mt76.mutex);
++
++	if (ctx->assigned) {
++		mutex_unlock(&phy->dev->mt76.mutex);
++		return -ENOSPC;
++	}
++
++	ctx->assigned = true;
++	ctx->chandef = conf->def;
++	ctx->phy = phy;
++	if (phy->chanctx) {
++		mutex_unlock(&phy->dev->mt76.mutex);
++		return 0;
++	}
++
++	phy->chanctx = ctx;
++	mutex_unlock(&phy->dev->mt76.mutex);
++
++	if (!mt76_testmode_enabled(phy->mt76) && !phy->mt76->test.bf_en) {
++		ret = mt7996_mcu_edcca_enable(phy, true);
++		if (ret)
++			return ret;
++	}
++
++	return mt7996_set_channel(phy, &ctx->chandef);
++}
++
++static void
++mt7996_remove_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *conf)
++{
++	struct mt7996_chanctx *ctx = mt7996_chanctx_get(conf);
++	struct mt7996_phy *phy = ctx->phy;
++
++	wiphy_info(hw->wiphy, "%s: remove %u\n", __func__, conf->def.chan->hw_value);
++	cancel_delayed_work_sync(&phy->scan_work);
++	cancel_delayed_work_sync(&phy->mt76->mac_work);
++
++	mutex_lock(&phy->dev->mt76.mutex);
++	ctx->assigned = false;
++	if (ctx == phy->chanctx)
++		phy->chanctx = NULL;
++	mutex_unlock(&phy->dev->mt76.mutex);
++}
++
++static void
++mt7996_change_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *conf,
++		      u32 changed)
++{
++	struct mt7996_chanctx *ctx = mt7996_chanctx_get(conf);
++	struct mt7996_phy *phy = ctx->phy;
++
++	wiphy_info(hw->wiphy, "%s: change %u, 0x%x\n", __func__, conf->def.chan->hw_value, changed);
++	if (changed & IEEE80211_CHANCTX_CHANGE_WIDTH) {
++		ctx->chandef = conf->def;
++
++		mt7996_set_channel(phy, &ctx->chandef);
++	}
++}
++
++static int
++mt7996_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
++			  struct ieee80211_bss_conf *link_conf,
++			  struct ieee80211_chanctx_conf *conf)
++{
++	struct mt7996_chanctx *ctx = mt7996_chanctx_get(conf);
++	struct mt7996_phy *phy = ctx->phy;
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++
++	wiphy_info(hw->wiphy, "Assign VIF (addr: %pM, type: %d, link_id: %d) to channel context: %d MHz\n",
++		    vif->addr, vif->type, link_conf->link_id,
++		    conf->def.chan->center_freq);
++
++	mutex_lock(&phy->dev->mt76.mutex);
++
++	mvif->chanctx = ctx;
++	ctx->nbss_assigned++;
++
++	mutex_unlock(&phy->dev->mt76.mutex);
++
++	return 0;
++}
++
++static void
++mt7996_unassign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
++			    struct ieee80211_bss_conf *link_conf,
++			    struct ieee80211_chanctx_conf *conf)
++{
++	struct mt7996_chanctx *ctx = mt7996_chanctx_get(conf);
++	struct mt7996_phy *phy = ctx->phy;
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++
++	wiphy_info(hw->wiphy, "Remove VIF (addr: %pM, type: %d, link_id: %d) from channel context: %d MHz\n",
++		   vif->addr, vif->type, link_conf->link_id,
++		   conf->def.chan->center_freq);
++	cancel_delayed_work_sync(&phy->scan_work);
++
++	mutex_lock(&phy->dev->mt76.mutex);
++
++	if (test_bit(MT76_SCANNING, &phy->mt76->state))
++		mt7996_scan_complete(phy, true);
++
++	mvif->chanctx = NULL;
++	ctx->nbss_assigned--;
++
++	mutex_unlock(&phy->dev->mt76.mutex);
++}
++
++static int
++mt7996_switch_vif_chanctx(struct ieee80211_hw *hw,
++			  struct ieee80211_vif_chanctx_switch *vifs,
++			  int n_vifs,
++			  enum ieee80211_chanctx_switch_mode mode)
++{
++	struct mt7996_chanctx *old_ctx = mt7996_chanctx_get(vifs->old_ctx);
++	struct mt7996_chanctx *new_ctx = mt7996_chanctx_get(vifs->new_ctx);
++	struct mt7996_phy *phy = old_ctx->phy;
++
++	wiphy_info(hw->wiphy, "%s: old=%d, new=%d\n", __func__, vifs->old_ctx->def.chan->hw_value, vifs->new_ctx->def.chan->hw_value);
++
++	if (WARN_ON(old_ctx != phy->chanctx))
++		return -EINVAL;
++
++	mutex_lock(&phy->dev->mt76.mutex);
++
++	phy->chanctx = new_ctx;
++	new_ctx->assigned = true;
++	new_ctx->chandef = vifs->new_ctx->def;
++	new_ctx->phy = phy;
++
++	mutex_unlock(&phy->dev->mt76.mutex);
++
++	return mt7996_set_channel(phy, &new_ctx->chandef);
++}
++
+ const struct ieee80211_ops mt7996_ops = {
+ 	.tx = mt7996_tx,
+ 	.start = mt7996_start,
+@@ -1766,4 +1904,10 @@ const struct ieee80211_ops mt7996_ops = {
+ 	.net_fill_forward_path = mt7996_net_fill_forward_path,
+ 	.net_setup_tc = mt76_wed_net_setup_tc,
+ #endif
++	.add_chanctx = mt7996_add_chanctx,
++	.remove_chanctx = mt7996_remove_chanctx,
++	.change_chanctx = mt7996_change_chanctx,
++	.assign_vif_chanctx = mt7996_assign_vif_chanctx,
++	.unassign_vif_chanctx = mt7996_unassign_vif_chanctx,
++	.switch_vif_chanctx = mt7996_switch_vif_chanctx,
+ };
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index a4172c4cf..08c27d37d 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -333,6 +333,8 @@ struct mt7996_vif {
+ 
+ 	struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS];
+ 	struct cfg80211_bitrate_mask bitrate_mask;
++
++	struct mt7996_chanctx *chanctx;
+ };
+ 
+ /* crash-dump */
+@@ -422,6 +424,14 @@ struct mt7996_rro_ba_session {
+ 	u32 last_in_rxtime :12;
+ };
+ 
++struct mt7996_chanctx {
++	struct cfg80211_chan_def chandef;
++	struct mt7996_phy *phy;
++
++	bool assigned;
++	u8 nbss_assigned;
++};
++
+ struct mt7996_phy {
+ 	struct mt76_phy *mt76;
+ 	struct mt7996_dev *dev;
+@@ -462,6 +472,7 @@ struct mt7996_phy {
+ 	struct cfg80211_scan_request *scan_req;
+ 	struct ieee80211_vif *scan_vif;
+ 	int scan_chan_idx;
++	struct mt7996_chanctx *chanctx;
+ 
+ 	struct mt7996_scs_ctrl scs_ctrl;
+ 	u32 red_drop;
+@@ -748,6 +759,12 @@ mt7996_get_background_radar_cap(struct mt7996_dev *dev)
+ 	return 1;
+ }
+ 
++static inline struct mt7996_chanctx *
++mt7996_chanctx_get(struct ieee80211_chanctx_conf *ctx)
++{
++	return (struct mt7996_chanctx *)&ctx->drv_priv;
++}
++
+ extern const struct ieee80211_ops mt7996_ops;
+ extern struct pci_driver mt7996_pci_driver;
+ extern struct pci_driver mt7996_hif_driver;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0100-wifi-mt76-mt7996-use-.sta_state-to-replace-.sta_add-.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0100-wifi-mt76-mt7996-use-.sta_state-to-replace-.sta_add-.patch
new file mode 100644
index 0000000..8af548b
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0100-wifi-mt76-mt7996-use-.sta_state-to-replace-.sta_add-.patch
@@ -0,0 +1,147 @@
+From 25ff83d103d13952ffeb4cddac9c366b91eb0416 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Wed, 8 Nov 2023 18:52:26 +0800
+Subject: [PATCH 100/120] wifi: mt76: mt7996: use .sta_state to replace
+ .sta_add and .sta_remove
+
+MAC80211 mostly uses MLD address through TX path, and leaves the header
+translation procedure to driver. To perform address translation, driver
+needs to get the setup link address at early stage (i.e., state 1),
+however, when using .sta_add/.sta_remove callbacks, driver can only get
+the link address at state 3, so it's necessary to switch to .sta_state
+callback to meet this requirement.
+
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Change-Id: I8a0faef919843f2c7d5ff3256702a3bf8384ea60
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/main.c   | 53 ++++++++++++++++++++-----------------------------
+ mt7996/mmio.c   |  1 +
+ mt7996/mt7996.h |  2 ++
+ 3 files changed, 24 insertions(+), 32 deletions(-)
+
+diff --git a/mt7996/main.c b/mt7996/main.c
+index aa52f5d32..66376e27c 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -782,7 +782,7 @@ int mt7996_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	u8 band_idx = mvif->phy->mt76->band_idx;
+-	int ret, idx;
++	int idx;
+ 
+ #ifdef CONFIG_MTK_VENDOR
+ 	struct mt7996_phy *phy = &dev->phy;
+@@ -800,23 +800,10 @@ int mt7996_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	msta->wcid.phy_idx = band_idx;
+ 	msta->wcid.tx_info |= MT_WCID_TX_INFO_SET;
+ 
+-	ewma_avg_signal_init(&msta->avg_ack_signal);
+-
+-	mt7996_mac_wtbl_update(dev, idx,
+-			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+-
+ #ifdef CONFIG_MTK_VENDOR
+ 	mt7996_vendor_amnt_sta_remove(mvif->phy, sta);
+ #endif
+ 
+-	ret = mt7996_mcu_add_sta(dev, vif, sta, true, true);
+-	if (ret)
+-		return ret;
+-
+-	ret = mt7996_mcu_add_rate_ctrl(dev, vif, sta, false);
+-	if (ret)
+-		return ret;
+-
+ #ifdef CONFIG_MTK_VENDOR
+ 	switch (band_idx) {
+ 	case MT_BAND1:
+@@ -837,6 +824,25 @@ int mt7996_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	return 0;
+ }
+ 
++void mt7996_mac_sta_assoc(struct mt76_dev *mdev, struct ieee80211_vif *vif,
++			  struct ieee80211_sta *sta)
++{
++	struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76);
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++
++	mutex_lock(&dev->mt76.mutex);
++
++	mt7996_mac_wtbl_update(dev, msta->wcid.idx,
++			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
++
++	mt7996_mcu_add_sta(dev, vif, sta, true, true);
++	mt7996_mcu_add_rate_ctrl(dev, vif, sta, false);
++
++	ewma_avg_signal_init(&msta->avg_ack_signal);
++
++	mutex_unlock(&dev->mt76.mutex);
++}
++
+ void mt7996_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 			   struct ieee80211_sta *sta)
+ {
+@@ -956,22 +962,6 @@ mt7996_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	return ret;
+ }
+ 
+-static int
+-mt7996_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+-	       struct ieee80211_sta *sta)
+-{
+-	return mt76_sta_state(hw, vif, sta, IEEE80211_STA_NOTEXIST,
+-			      IEEE80211_STA_NONE);
+-}
+-
+-static int
+-mt7996_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+-		  struct ieee80211_sta *sta)
+-{
+-	return mt76_sta_state(hw, vif, sta, IEEE80211_STA_NONE,
+-			      IEEE80211_STA_NOTEXIST);
+-}
+-
+ static int
+ mt7996_get_stats(struct ieee80211_hw *hw,
+ 		 struct ieee80211_low_level_stats *stats)
+@@ -1864,8 +1854,7 @@ const struct ieee80211_ops mt7996_ops = {
+ 	.conf_tx = mt7996_conf_tx,
+ 	.configure_filter = mt7996_configure_filter,
+ 	.bss_info_changed = mt7996_bss_info_changed,
+-	.sta_add = mt7996_sta_add,
+-	.sta_remove = mt7996_sta_remove,
++	.sta_state = mt76_sta_state,
+ 	.sta_pre_rcu_remove = mt76_sta_pre_rcu_remove,
+ 	.sta_rc_update = mt7996_sta_rc_update,
+ 	.set_key = mt7996_set_key,
+diff --git a/mt7996/mmio.c b/mt7996/mmio.c
+index 764c12445..61b1d7d62 100644
+--- a/mt7996/mmio.c
++++ b/mt7996/mmio.c
+@@ -644,6 +644,7 @@ struct mt7996_dev *mt7996_mmio_probe(struct device *pdev,
+ 		.rx_check = mt7996_rx_check,
+ 		.rx_poll_complete = mt7996_rx_poll_complete,
+ 		.sta_add = mt7996_mac_sta_add,
++		.sta_assoc = mt7996_mac_sta_assoc,
+ 		.sta_remove = mt7996_mac_sta_remove,
+ 		.update_survey = mt7996_update_channel,
+ 	};
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 08c27d37d..110979452 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -961,6 +961,8 @@ void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
+ void mt7996_mac_set_coverage_class(struct mt7996_phy *phy);
+ int mt7996_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 		       struct ieee80211_sta *sta);
++void mt7996_mac_sta_assoc(struct mt76_dev *mdev, struct ieee80211_vif *vif,
++			  struct ieee80211_sta *sta);
+ void mt7996_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 			   struct ieee80211_sta *sta);
+ void mt7996_mac_work(struct work_struct *work);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0101-wifi-mt76-mt7996-switch-to-per-link-data-structure-o.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0101-wifi-mt76-mt7996-switch-to-per-link-data-structure-o.patch
new file mode 100644
index 0000000..1bb3568
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0101-wifi-mt76-mt7996-switch-to-per-link-data-structure-o.patch
@@ -0,0 +1,3967 @@
+From 65da5e7d1c4ca9729c11bac4c289aef7b458ea4c Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Fri, 24 Nov 2023 11:31:55 +0800
+Subject: [PATCH 101/120] wifi: mt76: mt7996: switch to per-link data structure
+ of vif
+
+Introduce struct mt7996_bss_conf, data structure for per-link BSS.
+Note that mt7996_vif now represents a legacy or MLD device.
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/debugfs.c       |   10 +-
+ mt7996/init.c          |    4 +-
+ mt7996/mac.c           |   25 +-
+ mt7996/main.c          |  269 ++++---
+ mt7996/mcu.c           |  429 +++++------
+ mt7996/mt7996.h        |   72 +-
+ mt7996/mtk_debugfs_i.c | 1639 ++++++++++++++++++++++++++++++++++++++++
+ mt7996/testmode.c      |   17 +-
+ 8 files changed, 2102 insertions(+), 363 deletions(-)
+ create mode 100644 mt7996/mtk_debugfs_i.c
+
+diff --git a/mt7996/debugfs.c b/mt7996/debugfs.c
+index 36ee041c2..d4a74fef2 100644
+--- a/mt7996/debugfs.c
++++ b/mt7996/debugfs.c
+@@ -692,7 +692,7 @@ static void
+ mt7996_sta_hw_queue_read(void *data, struct ieee80211_sta *sta)
+ {
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+-	struct mt7996_dev *dev = msta->vif->phy->dev;
++	struct mt7996_dev *dev = msta->vif->deflink.phy->dev;
+ 	struct seq_file *s = data;
+ 	u8 ac;
+ 
+@@ -712,7 +712,7 @@ mt7996_sta_hw_queue_read(void *data, struct ieee80211_sta *sta)
+ 				      GENMASK(11, 0));
+ 		seq_printf(s, "\tSTA %pM wcid %d: AC%d%d queued:%d\n",
+ 			   sta->addr, msta->wcid.idx,
+-			   msta->vif->mt76.wmm_idx, ac, qlen);
++			   msta->vif->deflink.mt76.wmm_idx, ac, qlen);
+ 	}
+ }
+ 
+@@ -976,7 +976,7 @@ mt7996_atf_enable_set(void *data, u64 val)
+ 	int ret;
+ 
+ 	vow->max_deficit = val ? 64 : 1;
+-	ret = mt7996_mcu_set_vow_drr_ctrl(phy, NULL, VOW_DRR_CTRL_AIRTIME_DEFICIT_BOUND);
++	ret = mt7996_mcu_set_vow_drr_ctrl(phy, NULL, NULL, VOW_DRR_CTRL_AIRTIME_DEFICIT_BOUND);
+ 	if (ret)
+ 		return ret;
+ 
+@@ -1008,7 +1008,7 @@ mt7996_airtime_read(struct seq_file *s, void *data)
+ 
+ 		msta = container_of(wcid, struct mt7996_sta, wcid);
+ 		sta = container_of((void *)msta, struct ieee80211_sta, drv_priv);
+-		vif = &msta->vif->mt76;
++		vif = &msta->vif->deflink.mt76;
+ 		stats = &wcid->stats;
+ 
+ 		seq_printf(s, "%pM WCID: %hu BandIdx: %hhu OmacIdx: 0x%hhx\t"
+@@ -1152,7 +1152,7 @@ static ssize_t mt7996_sta_fixed_rate_set(struct file *file,
+ #define LONG_PREAMBLE 1
+ 	struct ieee80211_sta *sta = file->private_data;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+-	struct mt7996_dev *dev = msta->vif->phy->dev;
++	struct mt7996_dev *dev = msta->vif->deflink.phy->dev;
+ 	struct ra_rate phy = {};
+ 	char buf[100];
+ 	int ret;
+diff --git a/mt7996/init.c b/mt7996/init.c
+index f91bdd0f0..541361b86 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -620,11 +620,11 @@ static int mt7996_vow_init(struct mt7996_phy *phy)
+ 	vow->drr_quantum[6] = VOW_DRR_QUANTUM_L6;
+ 	vow->drr_quantum[7] = VOW_DRR_QUANTUM_L7;
+ 
+-	ret = mt7996_mcu_set_vow_drr_ctrl(phy, NULL, VOW_DRR_CTRL_AIRTIME_DEFICIT_BOUND);
++	ret = mt7996_mcu_set_vow_drr_ctrl(phy, NULL, NULL, VOW_DRR_CTRL_AIRTIME_DEFICIT_BOUND);
+ 	if (ret)
+ 		return ret;
+ 
+-	ret = mt7996_mcu_set_vow_drr_ctrl(phy, NULL, VOW_DRR_CTRL_AIRTIME_QUANTUM_ALL);
++	ret = mt7996_mcu_set_vow_drr_ctrl(phy, NULL, NULL, VOW_DRR_CTRL_AIRTIME_QUANTUM_ALL);
+ 	if (ret)
+ 		return ret;
+ 
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index dc16be88a..3f36938fe 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -897,8 +897,9 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
+ 
+ 	if (vif) {
+ 		struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++		struct mt7996_bss_conf *mconf = &mvif->deflink;
+ 
+-		txp->fw.bss_idx = mvif->mt76.idx;
++		txp->fw.bss_idx = mconf->mt76.idx;
+ 	}
+ 
+ 	txp->fw.token = cpu_to_le16(id);
+@@ -1518,12 +1519,15 @@ static void
+ mt7996_update_vif_beacon(void *priv, u8 *mac, struct ieee80211_vif *vif)
+ {
+ 	struct ieee80211_hw *hw = priv;
++	struct ieee80211_bss_conf *conf = &vif->bss_conf;
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_bss_conf *mconf = &mvif->deflink;
+ 
+ 	switch (vif->type) {
+ 	case NL80211_IFTYPE_MESH_POINT:
+ 	case NL80211_IFTYPE_ADHOC:
+ 	case NL80211_IFTYPE_AP:
+-		mt7996_mcu_add_beacon(hw, vif, vif->bss_conf.enable_beacon);
++		mt7996_mcu_add_beacon(hw, conf, mconf, conf->enable_beacon);
+ 		break;
+ 	default:
+ 		break;
+@@ -2237,6 +2241,8 @@ void mt7996_mac_sta_rc_work(struct work_struct *work)
+ 	struct mt7996_dev *dev = container_of(work, struct mt7996_dev, rc_work);
+ 	struct ieee80211_sta *sta;
+ 	struct ieee80211_vif *vif;
++	struct ieee80211_bss_conf *conf;
++	struct mt7996_bss_conf *mconf;
+ 	struct mt7996_sta *msta;
+ 	u32 changed;
+ 	LIST_HEAD(list);
+@@ -2253,14 +2259,16 @@ void mt7996_mac_sta_rc_work(struct work_struct *work)
+ 
+ 		sta = container_of((void *)msta, struct ieee80211_sta, drv_priv);
+ 		vif = container_of((void *)msta->vif, struct ieee80211_vif, drv_priv);
++		conf = &vif->bss_conf;
++		mconf = &msta->vif->deflink;
+ 
+ 		if (changed & (IEEE80211_RC_SUPP_RATES_CHANGED |
+ 			       IEEE80211_RC_NSS_CHANGED |
+ 			       IEEE80211_RC_BW_CHANGED))
+-			mt7996_mcu_add_rate_ctrl(dev, vif, sta, true);
++			mt7996_mcu_add_rate_ctrl(dev, conf, mconf, sta, true);
+ 
+ 		if (changed & IEEE80211_RC_SMPS_CHANGED)
+-			mt7996_mcu_set_fixed_field(dev, vif, sta, NULL,
++			mt7996_mcu_set_fixed_field(dev, mconf, sta, NULL,
+ 						   RATE_PARAM_MMPS_UPDATE);
+ 
+ 		spin_lock_bh(&dev->mt76.sta_poll_lock);
+@@ -2639,7 +2647,7 @@ void mt7996_mac_add_twt_setup(struct ieee80211_hw *hw,
+ 
+ 		flow->sched = true;
+ 		flow->start_tsf = mt7996_mac_twt_sched_list_add(dev, flow);
+-		curr_tsf = __mt7996_get_tsf(hw, msta->vif);
++		curr_tsf = __mt7996_get_tsf(hw, &msta->vif->deflink);
+ 		div_u64_rem(curr_tsf - flow->start_tsf, interval, &rem);
+ 		flow_tsf = curr_tsf + interval - rem;
+ 		twt_agrt->twt = cpu_to_le64(flow_tsf);
+@@ -2648,7 +2656,8 @@ void mt7996_mac_add_twt_setup(struct ieee80211_hw *hw,
+ 	}
+ 	flow->tsf = le64_to_cpu(twt_agrt->twt);
+ 
+-	if (mt7996_mcu_twt_agrt_update(dev, msta->vif, flow, MCU_TWT_AGRT_ADD))
++	if (mt7996_mcu_twt_agrt_update(dev, &msta->vif->deflink, flow,
++				       MCU_TWT_AGRT_ADD))
+ 		goto unlock;
+ 
+ 	setup_cmd = TWT_SETUP_CMD_ACCEPT;
+@@ -2670,6 +2679,7 @@ void mt7996_mac_twt_teardown_flow(struct mt7996_dev *dev,
+ 				  u8 flowid)
+ {
+ 	struct mt7996_twt_flow *flow;
++	struct mt7996_bss_conf *mconf = mconf_dereference_protected(msta->vif, 0);
+ 
+ 	lockdep_assert_held(&dev->mt76.mutex);
+ 
+@@ -2680,8 +2690,7 @@ void mt7996_mac_twt_teardown_flow(struct mt7996_dev *dev,
+ 		return;
+ 
+ 	flow = &msta->twt.flow[flowid];
+-	if (mt7996_mcu_twt_agrt_update(dev, msta->vif, flow,
+-				       MCU_TWT_AGRT_DELETE))
++	if (mt7996_mcu_twt_agrt_update(dev, mconf, flow, MCU_TWT_AGRT_DELETE))
+ 		return;
+ 
+ 	list_del_init(&flow->list);
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 66376e27c..7ea81e94c 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -204,29 +204,30 @@ static int get_omac_idx(enum nl80211_iftype type, u64 mask)
+ 	return -1;
+ }
+ 
+-static void mt7996_init_bitrate_mask(struct ieee80211_vif *vif)
++static void mt7996_init_bitrate_mask(struct mt7996_bss_conf *mconf)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	int i;
+ 
+-	for (i = 0; i < ARRAY_SIZE(mvif->bitrate_mask.control); i++) {
+-		mvif->bitrate_mask.control[i].gi = NL80211_TXRATE_DEFAULT_GI;
+-		mvif->bitrate_mask.control[i].he_gi = 0xff;
+-		mvif->bitrate_mask.control[i].he_ltf = 0xff;
+-		mvif->bitrate_mask.control[i].legacy = GENMASK(31, 0);
+-		memset(mvif->bitrate_mask.control[i].ht_mcs, 0xff,
+-		       sizeof(mvif->bitrate_mask.control[i].ht_mcs));
+-		memset(mvif->bitrate_mask.control[i].vht_mcs, 0xff,
+-		       sizeof(mvif->bitrate_mask.control[i].vht_mcs));
+-		memset(mvif->bitrate_mask.control[i].he_mcs, 0xff,
+-		       sizeof(mvif->bitrate_mask.control[i].he_mcs));
++	for (i = 0; i < ARRAY_SIZE(mconf->bitrate_mask.control); i++) {
++		mconf->bitrate_mask.control[i].gi = NL80211_TXRATE_DEFAULT_GI;
++		mconf->bitrate_mask.control[i].he_gi = 0xff;
++		mconf->bitrate_mask.control[i].he_ltf = 0xff;
++		mconf->bitrate_mask.control[i].legacy = GENMASK(31, 0);
++		memset(mconf->bitrate_mask.control[i].ht_mcs, 0xff,
++		       sizeof(mconf->bitrate_mask.control[i].ht_mcs));
++		memset(mconf->bitrate_mask.control[i].vht_mcs, 0xff,
++		       sizeof(mconf->bitrate_mask.control[i].vht_mcs));
++		memset(mconf->bitrate_mask.control[i].he_mcs, 0xff,
++		       sizeof(mconf->bitrate_mask.control[i].he_mcs));
+ 	}
+ }
+ 
+ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 				struct ieee80211_vif *vif)
+ {
++	struct ieee80211_bss_conf *conf = &vif->bss_conf;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_bss_conf *mconf = &mvif->deflink;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	struct mt76_txq *mtxq;
+@@ -239,8 +240,8 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 	    is_zero_ether_addr(vif->addr))
+ 		phy->monitor_vif = vif;
+ 
+-	mvif->mt76.idx = __ffs64(~dev->mt76.vif_mask);
+-	if (mvif->mt76.idx >= mt7996_max_interface_num(dev)) {
++	mconf->mt76.idx = __ffs64(~dev->mt76.vif_mask);
++	if (mconf->mt76.idx >= mt7996_max_interface_num(dev)) {
+ 		ret = -ENOSPC;
+ 		goto out;
+ 	}
+@@ -250,19 +251,21 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 		ret = -ENOSPC;
+ 		goto out;
+ 	}
+-	mvif->mt76.omac_idx = idx;
+-	mvif->phy = phy;
+-	mvif->mt76.band_idx = band_idx;
+-	mvif->mt76.wmm_idx = vif->type != NL80211_IFTYPE_AP;
+-
+-	ret = mt7996_mcu_add_dev_info(phy, vif, true);
++	mconf->mt76.omac_idx = idx;
++	mconf->vif = mvif;
++	mconf->phy = phy;
++	mconf->mt76.band_idx = band_idx;
++	mconf->mt76.wmm_idx = vif->type != NL80211_IFTYPE_AP;
++	mvif->dev = dev;
++
++	ret = mt7996_mcu_add_dev_info(phy, conf, mconf, true);
+ 	if (ret)
+ 		goto out;
+ 
+-	dev->mt76.vif_mask |= BIT_ULL(mvif->mt76.idx);
+-	phy->omac_mask |= BIT_ULL(mvif->mt76.omac_idx);
++	dev->mt76.vif_mask |= BIT_ULL(mconf->mt76.idx);
++	phy->omac_mask |= BIT_ULL(mconf->mt76.omac_idx);
+ 
+-	idx = MT7996_WTBL_RESERVED - mvif->mt76.idx;
++	idx = MT7996_WTBL_RESERVED - mconf->mt76.idx;
+ 
+ 	INIT_LIST_HEAD(&mvif->sta.rc_list);
+ 	INIT_LIST_HEAD(&mvif->sta.wcid.poll_list);
+@@ -282,24 +285,25 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 	}
+ 
+ 	if (vif->type != NL80211_IFTYPE_AP &&
+-	    (!mvif->mt76.omac_idx || mvif->mt76.omac_idx > 3))
++	    (!mconf->mt76.omac_idx || mconf->mt76.omac_idx > 3))
+ 		vif->offload_flags = 0;
+ 	vif->offload_flags |= IEEE80211_OFFLOAD_ENCAP_4ADDR;
+ 
+ 	if (phy->mt76->chandef.chan->band != NL80211_BAND_2GHZ)
+-		mvif->mt76.basic_rates_idx = MT7996_BASIC_RATES_TBL + 4;
++		mconf->mt76.basic_rates_idx = MT7996_BASIC_RATES_TBL + 4;
+ 	else
+-		mvif->mt76.basic_rates_idx = MT7996_BASIC_RATES_TBL;
++		mconf->mt76.basic_rates_idx = MT7996_BASIC_RATES_TBL;
+ 
+-	mt7996_init_bitrate_mask(vif);
++	mt7996_init_bitrate_mask(mconf);
+ 
+-	mt7996_mcu_add_bss_info(phy, vif, true);
++	mt7996_mcu_add_bss_info(phy, conf, mconf, true);
+ 	/* defer the first STA_REC of BMC entry to BSS_CHANGED_BSSID for STA
+ 	 * interface, since firmware only records BSSID when the entry is new
+ 	 */
+ 	if (vif->type != NL80211_IFTYPE_STATION)
+-		mt7996_mcu_add_sta(dev, vif, NULL, true, true);
++		mt7996_mcu_add_sta(dev, conf, mconf, NULL, true, true);
+ 	rcu_assign_pointer(dev->mt76.wcid[idx], &mvif->sta.wcid);
++	rcu_assign_pointer(mvif->link[0], mconf);
+ 
+ out:
+ 	mutex_unlock(&dev->mt76.mutex);
+@@ -310,7 +314,9 @@ out:
+ static void mt7996_remove_interface(struct ieee80211_hw *hw,
+ 				    struct ieee80211_vif *vif)
+ {
++	struct ieee80211_bss_conf *conf;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_bss_conf *mconf;
+ 	struct mt7996_sta *msta = &mvif->sta;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+@@ -318,24 +324,22 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
+ 
+ 	cancel_delayed_work_sync(&phy->scan_work);
+ 
+-	mt7996_mcu_add_sta(dev, vif, NULL, false, false);
+-	mt7996_mcu_add_bss_info(phy, vif, false);
++	mutex_lock(&dev->mt76.mutex);
++
++	conf = link_conf_dereference_protected(vif, 0);
++	mconf = mconf_dereference_protected(mvif, 0);
++	mt7996_mcu_add_sta(dev, conf, mconf, NULL, false, false);
++	mt7996_mcu_add_bss_info(phy, conf, mconf, false);
+ 
+ 	if (vif == phy->monitor_vif)
+ 		phy->monitor_vif = NULL;
+ 
+-	mt7996_mcu_add_dev_info(phy, vif, false);
++	mt7996_mcu_add_dev_info(phy, conf, mconf, false);
+ 
+ 	rcu_assign_pointer(dev->mt76.wcid[idx], NULL);
+ 
+-	mutex_lock(&dev->mt76.mutex);
+-
+-	if (test_bit(MT76_SCANNING, &phy->mt76->state))
+-		mt7996_scan_complete(phy, true);
+-
+-	dev->mt76.vif_mask &= ~BIT_ULL(mvif->mt76.idx);
+-	phy->omac_mask &= ~BIT_ULL(mvif->mt76.omac_idx);
+-	mutex_unlock(&dev->mt76.mutex);
++	dev->mt76.vif_mask &= ~BIT_ULL(mconf->mt76.idx);
++	phy->omac_mask &= ~BIT_ULL(mconf->mt76.omac_idx);
+ 
+ 	spin_lock_bh(&dev->mt76.sta_poll_lock);
+ 	if (!list_empty(&msta->wcid.poll_list))
+@@ -343,6 +347,9 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
+ 	spin_unlock_bh(&dev->mt76.sta_poll_lock);
+ 
+ 	mt76_wcid_cleanup(&dev->mt76, &msta->wcid);
++	rcu_assign_pointer(mvif->link[0], NULL);
++
++	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
+ static void ___mt7996_set_channel(struct mt7996_phy *phy,
+@@ -440,6 +447,8 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+ 	struct mt7996_sta *msta = sta ? (struct mt7996_sta *)sta->drv_priv :
+ 				  &mvif->sta;
+ 	struct mt76_wcid *wcid = &msta->wcid;
++	struct mt7996_bss_conf *mconf;
++	struct ieee80211_bss_conf *conf;
+ 	u8 *wcid_keyidx = &wcid->hw_key_idx;
+ 	int idx = key->keyidx;
+ 	int err = 0;
+@@ -480,9 +489,11 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+-	if (cmd == SET_KEY && !sta && !mvif->mt76.cipher) {
+-		mvif->mt76.cipher = mt76_connac_mcu_get_cipher(key->cipher);
+-		mt7996_mcu_add_bss_info(phy, vif, true);
++	conf = link_conf_dereference_protected(vif, 0);
++	mconf = mconf_dereference_protected(mvif, 0);
++	if (cmd == SET_KEY && !sta && !mconf->mt76.cipher) {
++		mconf->mt76.cipher = mt76_connac_mcu_get_cipher(key->cipher);
++		mt7996_mcu_add_bss_info(phy, conf, mconf, true);
+ 	}
+ 
+ 	if (cmd == SET_KEY) {
+@@ -496,9 +507,9 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+ 	mt76_wcid_key_setup(&dev->mt76, wcid, key);
+ 
+ 	if (key->keyidx == 6 || key->keyidx == 7)
+-		err = mt7996_mcu_bcn_prot_enable(dev, vif, key);
++		err = mt7996_mcu_bcn_prot_enable(dev, conf, mconf, key);
+ 	else
+-		err = mt7996_mcu_add_key(&dev->mt76, vif, key,
++		err = mt7996_mcu_add_key(&dev->mt76, mconf, key,
+ 					 MCU_WMWA_UNI_CMD(STA_REC_UPDATE),
+ 					 &msta->wcid, cmd);
+ out:
+@@ -545,7 +556,9 @@ mt7996_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	       unsigned int link_id, u16 queue,
+ 	       const struct ieee80211_tx_queue_params *params)
+ {
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_bss_conf *mconf;
+ 	const u8 mq_to_aci[] = {
+ 		[IEEE80211_AC_VO] = 3,
+ 		[IEEE80211_AC_VI] = 2,
+@@ -553,10 +566,15 @@ mt7996_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 		[IEEE80211_AC_BK] = 1,
+ 	};
+ 
++	mutex_lock(&dev->mt76.mutex);
++	mconf = mconf_dereference_protected(mvif, 0);
++
+ 	/* firmware uses access class index */
+-	mvif->queue_params[mq_to_aci[queue]] = *params;
++	mconf->queue_params[mq_to_aci[queue]] = *params;
+ 	/* no need to update right away, we'll get BSS_CHANGED_QOS */
+ 
++	mutex_unlock(&dev->mt76.mutex);
++
+ 	return 0;
+ }
+ 
+@@ -617,22 +635,20 @@ static void mt7996_configure_filter(struct ieee80211_hw *hw,
+ }
+ 
+ static void
+-mt7996_update_bss_color(struct ieee80211_hw *hw,
+-			struct ieee80211_vif *vif,
++mt7996_update_bss_color(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
++			struct mt7996_bss_conf *mconf,
+ 			struct cfg80211_he_bss_color *bss_color)
+ {
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 
+ 	switch (vif->type) {
+ 	case NL80211_IFTYPE_AP: {
+-		struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-
+-		if (mvif->mt76.omac_idx > HW_BSSID_MAX)
++		if (mconf->mt76.omac_idx > HW_BSSID_MAX)
+ 			return;
+ 		fallthrough;
+ 	}
+ 	case NL80211_IFTYPE_STATION:
+-		mt7996_mcu_update_bss_color(dev, vif, bss_color);
++		mt7996_mcu_update_bss_color(dev, mconf, bss_color);
+ 		break;
+ 	default:
+ 		break;
+@@ -640,16 +656,15 @@ mt7996_update_bss_color(struct ieee80211_hw *hw,
+ }
+ 
+ static u8
+-mt7996_get_rates_table(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+-		       bool beacon, bool mcast)
++mt7996_get_rates_table(struct ieee80211_hw *hw, struct ieee80211_bss_conf *conf,
++		       struct mt7996_bss_conf *mconf, bool beacon, bool mcast)
+ {
+-	struct mt76_vif *mvif = (struct mt76_vif *)vif->drv_priv;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+-	struct mt76_phy *mphy = hw->priv;
++	struct mt76_phy *mphy = mconf->phy->mt76;
+ 	u16 rate;
+ 	u8 i, idx;
+ 
+-	rate = mt76_connac2_mac_tx_rate_val(mphy, vif, beacon, mcast);
++	rate = mt76_connac2_mac_tx_rate_val(mphy, conf->vif, beacon, mcast);
+ 
+ 	if (beacon) {
+ 		struct mt7996_phy *phy = mphy->priv;
+@@ -670,23 +685,22 @@ mt7996_get_rates_table(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 		if ((mt76_rates[i].hw_value & GENMASK(7, 0)) == idx)
+ 			return MT7996_BASIC_RATES_TBL + 2 * i;
+ 
+-	return mvif->basic_rates_idx;
++	return mconf->mt76.basic_rates_idx;
+ }
+ 
+ static void
+-mt7996_update_mu_group(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+-		       struct ieee80211_bss_conf *info)
++mt7996_update_mu_group(struct ieee80211_hw *hw, struct ieee80211_bss_conf *conf,
++		       struct mt7996_bss_conf *mconf)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+-	u8 band = mvif->mt76.band_idx;
++	u8 band = mconf->mt76.band_idx;
+ 	u32 *mu;
+ 
+-	mu = (u32 *)info->mu_group.membership;
++	mu = (u32 *)conf->mu_group.membership;
+ 	mt76_wr(dev, MT_WF_PHYRX_BAND_GID_TAB_VLD0(band), mu[0]);
+ 	mt76_wr(dev, MT_WF_PHYRX_BAND_GID_TAB_VLD1(band), mu[1]);
+ 
+-	mu = (u32 *)info->mu_group.position;
++	mu = (u32 *)conf->mu_group.position;
+ 	mt76_wr(dev, MT_WF_PHYRX_BAND_GID_TAB_POS0(band), mu[0]);
+ 	mt76_wr(dev, MT_WF_PHYRX_BAND_GID_TAB_POS1(band), mu[1]);
+ 	mt76_wr(dev, MT_WF_PHYRX_BAND_GID_TAB_POS2(band), mu[2]);
+@@ -698,20 +712,22 @@ static void mt7996_bss_info_changed(struct ieee80211_hw *hw,
+ 				    struct ieee80211_bss_conf *info,
+ 				    u64 changed)
+ {
+-	struct mt76_vif *mvif = (struct mt76_vif *)vif->drv_priv;
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_bss_conf *mconf;
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
++	mconf = mconf_dereference_protected(mvif, 0);
+ 	/* station mode uses BSSID to map the wlan entry to a peer,
+ 	 * and then peer references bss_info_rfch to set bandwidth cap.
+ 	 */
+ 	if ((changed & BSS_CHANGED_BSSID && !is_zero_ether_addr(info->bssid)) ||
+ 	    (changed & BSS_CHANGED_ASSOC && vif->cfg.assoc) ||
+ 	    (changed & BSS_CHANGED_BEACON_ENABLED && info->enable_beacon)) {
+-		mt7996_mcu_add_bss_info(phy, vif, true);
+-		mt7996_mcu_add_sta(dev, vif, NULL, true,
++		mt7996_mcu_add_bss_info(phy, info, mconf, true);
++		mt7996_mcu_add_sta(dev, info, mconf, NULL, true,
+ 				   !!(changed & BSS_CHANGED_BSSID));
+ 	}
+ 
+@@ -723,42 +739,42 @@ static void mt7996_bss_info_changed(struct ieee80211_hw *hw,
+ 
+ 		if (slottime != phy->slottime) {
+ 			phy->slottime = slottime;
+-			mt7996_mcu_set_timing(phy, vif);
++			mt7996_mcu_set_timing(phy, mconf);
+ 		}
+ 	}
+ 
+ 	if (changed & BSS_CHANGED_MCAST_RATE)
+-		mvif->mcast_rates_idx =
+-			mt7996_get_rates_table(hw, vif, false, true);
++		mconf->mt76.mcast_rates_idx =
++			mt7996_get_rates_table(hw, info, mconf, false, true);
+ 
+ 	if (changed & BSS_CHANGED_BASIC_RATES)
+-		mvif->basic_rates_idx =
+-			mt7996_get_rates_table(hw, vif, false, false);
++		mconf->mt76.basic_rates_idx =
++			mt7996_get_rates_table(hw, info, mconf, false, false);
+ 
+ 	/* ensure that enable txcmd_mode after bss_info */
+ 	if (changed & (BSS_CHANGED_QOS | BSS_CHANGED_BEACON_ENABLED))
+-		mt7996_mcu_set_tx(dev, vif);
++		mt7996_mcu_set_tx(dev, mconf);
+ 
+ 	if (changed & BSS_CHANGED_HE_OBSS_PD)
+-		mt7996_mcu_add_obss_spr(phy, vif, &info->he_obss_pd);
++		mt7996_mcu_add_obss_spr(phy, mconf, &info->he_obss_pd);
+ 
+ 	if (changed & BSS_CHANGED_HE_BSS_COLOR)
+-		mt7996_update_bss_color(hw, vif, &info->he_bss_color);
++		mt7996_update_bss_color(hw, vif, mconf, &info->he_bss_color);
+ 
+ 	if (changed & (BSS_CHANGED_BEACON |
+ 		       BSS_CHANGED_BEACON_ENABLED)) {
+-		mvif->beacon_rates_idx =
+-			mt7996_get_rates_table(hw, vif, true, false);
++		mconf->mt76.beacon_rates_idx =
++			mt7996_get_rates_table(hw, info, mconf, true, false);
+ 
+-		mt7996_mcu_add_beacon(hw, vif, info->enable_beacon);
++		mt7996_mcu_add_beacon(hw, info, mconf, info->enable_beacon);
+ 	}
+ 
+ 	if (changed & (BSS_CHANGED_UNSOL_BCAST_PROBE_RESP |
+ 		       BSS_CHANGED_FILS_DISCOVERY))
+-		mt7996_mcu_beacon_inband_discov(dev, vif, changed);
++		mt7996_mcu_beacon_inband_discov(dev, info, mconf, changed);
+ 
+ 	if (changed & BSS_CHANGED_MU_GROUPS)
+-		mt7996_update_mu_group(hw, vif, info);
++		mt7996_update_mu_group(hw, info, mconf);
+ 
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+@@ -769,9 +785,14 @@ mt7996_channel_switch_beacon(struct ieee80211_hw *hw,
+ 			     struct cfg80211_chan_def *chandef)
+ {
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_bss_conf *mconf;
++	struct ieee80211_bss_conf *conf;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	mt7996_mcu_add_beacon(hw, vif, true);
++	mconf = mconf_dereference_protected(mvif, 0);
++	conf = link_conf_dereference_protected(vif, 0);
++	mt7996_mcu_add_beacon(hw, conf, mconf, true);
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
+@@ -781,7 +802,8 @@ int mt7996_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76);
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	u8 band_idx = mvif->phy->mt76->band_idx;
++	struct mt7996_bss_conf *mconf = mconf_dereference_protected(mvif, 0);
++	u8 band_idx = mconf->phy->mt76->band_idx;
+ 	int idx;
+ 
+ #ifdef CONFIG_MTK_VENDOR
+@@ -801,7 +823,7 @@ int mt7996_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	msta->wcid.tx_info |= MT_WCID_TX_INFO_SET;
+ 
+ #ifdef CONFIG_MTK_VENDOR
+-	mt7996_vendor_amnt_sta_remove(mvif->phy, sta);
++	mt7996_vendor_amnt_sta_remove(mconf->phy, sta);
+ #endif
+ 
+ #ifdef CONFIG_MTK_VENDOR
+@@ -828,15 +850,20 @@ void mt7996_mac_sta_assoc(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 			  struct ieee80211_sta *sta)
+ {
+ 	struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76);
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_bss_conf *mconf;
++	struct ieee80211_bss_conf *conf;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+ 	mt7996_mac_wtbl_update(dev, msta->wcid.idx,
+ 			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+ 
+-	mt7996_mcu_add_sta(dev, vif, sta, true, true);
+-	mt7996_mcu_add_rate_ctrl(dev, vif, sta, false);
++	conf = link_conf_dereference_protected(vif, 0);
++	mconf = mconf_dereference_protected(mvif, 0);
++	mt7996_mcu_add_sta(dev, conf, mconf, sta, true, true);
++	mt7996_mcu_add_rate_ctrl(dev, conf, mconf, sta, false);
+ 
+ 	ewma_avg_signal_init(&msta->avg_ack_signal);
+ 
+@@ -847,10 +874,15 @@ void mt7996_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 			   struct ieee80211_sta *sta)
+ {
+ 	struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76);
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_bss_conf *mconf;
++	struct ieee80211_bss_conf *conf;
+ 	int i;
+ 
+-	mt7996_mcu_add_sta(dev, vif, sta, false, false);
++	conf = link_conf_dereference_protected(vif, 0);
++	mconf = mconf_dereference_protected(mvif, 0);
++	mt7996_mcu_add_sta(dev, conf, mconf, sta, false, false);
+ 
+ 	mt7996_mac_wtbl_update(dev, msta->wcid.idx,
+ 			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+@@ -982,7 +1014,7 @@ mt7996_get_stats(struct ieee80211_hw *hw,
+ 	return 0;
+ }
+ 
+-u64 __mt7996_get_tsf(struct ieee80211_hw *hw, struct mt7996_vif *mvif)
++u64 __mt7996_get_tsf(struct ieee80211_hw *hw, struct mt7996_bss_conf *mconf)
+ {
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+@@ -994,8 +1026,8 @@ u64 __mt7996_get_tsf(struct ieee80211_hw *hw, struct mt7996_vif *mvif)
+ 
+ 	lockdep_assert_held(&dev->mt76.mutex);
+ 
+-	n = mvif->mt76.omac_idx > HW_BSSID_MAX ? HW_BSSID_0
+-					       : mvif->mt76.omac_idx;
++	n = mconf->mt76.omac_idx > HW_BSSID_MAX ? HW_BSSID_0
++					       : mconf->mt76.omac_idx;
+ 	/* TSF software read */
+ 	mt76_rmw(dev, MT_LPON_TCR(phy->mt76->band_idx, n), MT_LPON_TCR_SW_MODE,
+ 		 MT_LPON_TCR_SW_READ);
+@@ -1010,10 +1042,12 @@ mt7996_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+ {
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++	struct mt7996_bss_conf *mconf;
+ 	u64 ret;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	ret = __mt7996_get_tsf(hw, mvif);
++	mconf = mconf_dereference_protected(mvif, 0);
++	ret = __mt7996_get_tsf(hw, mconf);
+ 	mutex_unlock(&dev->mt76.mutex);
+ 
+ 	return ret;
+@@ -1026,6 +1060,7 @@ mt7996_set_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_bss_conf *mconf;
+ 	union {
+ 		u64 t64;
+ 		u32 t32[2];
+@@ -1034,8 +1069,9 @@ mt7996_set_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+-	n = mvif->mt76.omac_idx > HW_BSSID_MAX ? HW_BSSID_0
+-					       : mvif->mt76.omac_idx;
++	mconf = mconf_dereference_protected(mvif, 0);
++	n = mconf->mt76.omac_idx > HW_BSSID_MAX ? HW_BSSID_0
++					       : mconf->mt76.omac_idx;
+ 	mt76_wr(dev, MT_LPON_UTTR0(phy->mt76->band_idx), tsf.t32[0]);
+ 	mt76_wr(dev, MT_LPON_UTTR1(phy->mt76->band_idx), tsf.t32[1]);
+ 	/* TSF software overwrite */
+@@ -1052,6 +1088,7 @@ mt7996_offset_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_bss_conf *mconf;
+ 	union {
+ 		u64 t64;
+ 		u32 t32[2];
+@@ -1060,8 +1097,9 @@ mt7996_offset_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+-	n = mvif->mt76.omac_idx > HW_BSSID_MAX ? HW_BSSID_0
+-					       : mvif->mt76.omac_idx;
++	mconf = mconf_dereference_protected(mvif, 0);
++	n = mconf->mt76.omac_idx > HW_BSSID_MAX ? HW_BSSID_0
++					       : mconf->mt76.omac_idx;
+ 	mt76_wr(dev, MT_LPON_UTTR0(phy->mt76->band_idx), tsf.t32[0]);
+ 	mt76_wr(dev, MT_LPON_UTTR1(phy->mt76->band_idx), tsf.t32[1]);
+ 	/* TSF software adjust*/
+@@ -1177,7 +1215,7 @@ static void mt7996_sta_statistics(struct ieee80211_hw *hw,
+ static void mt7996_sta_rc_work(void *data, struct ieee80211_sta *sta)
+ {
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+-	struct mt7996_dev *dev = msta->vif->phy->dev;
++	struct mt7996_dev *dev = msta->vif->dev;
+ 	u32 *changed = data;
+ 
+ 	spin_lock_bh(&dev->mt76.sta_poll_lock);
+@@ -1213,9 +1251,13 @@ mt7996_set_bitrate_mask(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_bss_conf *mconf;
+ 	u32 changed = IEEE80211_RC_SUPP_RATES_CHANGED;
+ 
+-	mvif->bitrate_mask = *mask;
++	mutex_lock(&dev->mt76.mutex);
++	mconf = mconf_dereference_protected(mvif, 0);
++	mconf->bitrate_mask = *mask;
++	mutex_unlock(&dev->mt76.mutex);
+ 
+ 	/* if multiple rates across different preambles are given we can
+ 	 * reconfigure this info with all peers using sta_rec command with
+@@ -1237,14 +1279,20 @@ static void mt7996_sta_set_4addr(struct ieee80211_hw *hw,
+ 				 bool enabled)
+ {
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_bss_conf *mconf;
++
++	mutex_lock(&dev->mt76.mutex);
++	mconf = mconf_dereference_protected(mvif, 0);
+ 
+ 	if (enabled)
+ 		set_bit(MT_WCID_FLAG_4ADDR, &msta->wcid.flags);
+ 	else
+ 		clear_bit(MT_WCID_FLAG_4ADDR, &msta->wcid.flags);
+ 
+-	mt7996_mcu_wtbl_update_hdr_trans(dev, vif, sta);
++	mt7996_mcu_wtbl_update_hdr_trans(dev, vif, mconf, sta);
++	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
+ static void mt7996_sta_set_decap_offload(struct ieee80211_hw *hw,
+@@ -1253,14 +1301,20 @@ static void mt7996_sta_set_decap_offload(struct ieee80211_hw *hw,
+ 					 bool enabled)
+ {
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_bss_conf *mconf;
++
++	mutex_lock(&dev->mt76.mutex);
++	mconf = mconf_dereference_protected(mvif, 0);
+ 
+ 	if (enabled)
+ 		set_bit(MT_WCID_FLAG_HDR_TRANS, &msta->wcid.flags);
+ 	else
+ 		clear_bit(MT_WCID_FLAG_HDR_TRANS, &msta->wcid.flags);
+ 
+-	mt7996_mcu_wtbl_update_hdr_trans(dev, vif, sta);
++	mt7996_mcu_wtbl_update_hdr_trans(dev, vif, mconf, sta);
++	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
+ static const char mt7996_gstrings_stats[][ETH_GSTRING_LEN] = {
+@@ -1393,7 +1447,7 @@ static void mt7996_ethtool_worker(void *wi_data, struct ieee80211_sta *sta)
+ 	struct mt76_ethtool_worker_info *wi = wi_data;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 
+-	if (msta->vif->mt76.idx != wi->idx)
++	if (msta->vif->deflink.mt76.idx != wi->idx)
+ 		return;
+ 
+ 	mt76_ethtool_worker(wi, &msta->wcid.stats, true);
+@@ -1407,15 +1461,17 @@ void mt7996_get_et_stats(struct ieee80211_hw *hw,
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_bss_conf *mconf;
+ 	struct mt76_mib_stats *mib = &phy->mib;
+ 	struct mt76_ethtool_worker_info wi = {
+ 		.data = data,
+-		.idx = mvif->mt76.idx,
+ 	};
+ 	/* See mt7996_ampdu_stat_read_phy, etc */
+ 	int i, ei = 0;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
++	mconf = mconf_dereference_protected(mvif, 0);
++	wi.idx = mconf->mt76.idx,
+ 
+ 	mt7996_mac_update_stats(phy);
+ 
+@@ -1621,6 +1677,7 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw,
+ 			     struct net_device_path *path)
+ {
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_bss_conf *mconf = &mvif->deflink;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+@@ -1649,7 +1706,7 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw,
+ 	path->type = DEV_PATH_MTK_WDMA;
+ 	path->dev = ctx->dev;
+ 	path->mtk_wdma.wdma_idx = wed->wdma_idx;
+-	path->mtk_wdma.bss = mvif->mt76.idx;
++	path->mtk_wdma.bss = mconf->mt76.idx;
+ 	path->mtk_wdma.queue = 0;
+ 	path->mtk_wdma.wcid = msta->wcid.idx;
+ 
+@@ -1777,6 +1834,7 @@ mt7996_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	struct mt7996_chanctx *ctx = mt7996_chanctx_get(conf);
+ 	struct mt7996_phy *phy = ctx->phy;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_bss_conf *mconf;
+ 
+ 	wiphy_info(hw->wiphy, "Assign VIF (addr: %pM, type: %d, link_id: %d) to channel context: %d MHz\n",
+ 		    vif->addr, vif->type, link_conf->link_id,
+@@ -1784,7 +1842,8 @@ mt7996_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 
+ 	mutex_lock(&phy->dev->mt76.mutex);
+ 
+-	mvif->chanctx = ctx;
++	mconf = mconf_dereference_protected(mvif, 0);
++	mconf->chanctx = ctx;
+ 	ctx->nbss_assigned++;
+ 
+ 	mutex_unlock(&phy->dev->mt76.mutex);
+@@ -1800,6 +1859,7 @@ mt7996_unassign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	struct mt7996_chanctx *ctx = mt7996_chanctx_get(conf);
+ 	struct mt7996_phy *phy = ctx->phy;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_bss_conf *mconf;
+ 
+ 	wiphy_info(hw->wiphy, "Remove VIF (addr: %pM, type: %d, link_id: %d) from channel context: %d MHz\n",
+ 		   vif->addr, vif->type, link_conf->link_id,
+@@ -1811,7 +1871,8 @@ mt7996_unassign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	if (test_bit(MT76_SCANNING, &phy->mt76->state))
+ 		mt7996_scan_complete(phy, true);
+ 
+-	mvif->chanctx = NULL;
++	mconf = mconf_dereference_protected(mvif, 0);
++	mconf->chanctx = NULL;
+ 	ctx->nbss_assigned--;
+ 
+ 	mutex_unlock(&phy->dev->mt76.mutex);
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 7e7c5ff95..2806aa0dd 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -117,12 +117,12 @@ mt7996_mcu_get_sta_nss(u16 mcs_map)
+ }
+ 
+ static void
+-mt7996_mcu_set_sta_he_mcs(struct ieee80211_sta *sta, __le16 *he_mcs,
+-			  u16 mcs_map)
++mt7996_mcu_set_sta_he_mcs(struct ieee80211_sta *sta,
++			  struct mt7996_bss_conf *mconf,
++			  __le16 *he_mcs, u16 mcs_map)
+ {
+-	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+-	enum nl80211_band band = msta->vif->phy->mt76->chandef.chan->band;
+-	const u16 *mask = msta->vif->bitrate_mask.control[band].he_mcs;
++	enum nl80211_band band = mconf->phy->mt76->chandef.chan->band;
++	const u16 *mask = mconf->bitrate_mask.control[band].he_mcs;
+ 	int nss, max_nss = sta->deflink.rx_nss > 3 ? 4 : sta->deflink.rx_nss;
+ 
+ 	for (nss = 0; nss < max_nss; nss++) {
+@@ -920,8 +920,7 @@ mt7996_mcu_add_uni_tlv(struct sk_buff *skb, u16 tag, u16 len)
+ }
+ 
+ static void
+-mt7996_mcu_bss_rfch_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
+-			struct mt7996_phy *phy)
++mt7996_mcu_bss_rfch_tlv(struct sk_buff *skb, struct mt7996_phy *phy)
+ {
+ 	static const u8 rlm_ch_band[] = {
+ 		[NL80211_BAND_2GHZ] = 1,
+@@ -951,8 +950,7 @@ mt7996_mcu_bss_rfch_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
+ }
+ 
+ static void
+-mt7996_mcu_bss_ra_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
+-		      struct mt7996_phy *phy)
++mt7996_mcu_bss_ra_tlv(struct sk_buff *skb)
+ {
+ 	struct bss_ra_tlv *ra;
+ 	struct tlv *tlv;
+@@ -964,7 +962,7 @@ mt7996_mcu_bss_ra_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
+ }
+ 
+ static void
+-mt7996_mcu_bss_he_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
++mt7996_mcu_bss_he_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *conf,
+ 		      struct mt7996_phy *phy)
+ {
+ #define DEFAULT_HE_PE_DURATION		4
+@@ -973,16 +971,16 @@ mt7996_mcu_bss_he_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
+ 	struct bss_info_uni_he *he;
+ 	struct tlv *tlv;
+ 
+-	cap = mt76_connac_get_he_phy_cap(phy->mt76, vif);
++	cap = mt76_connac_get_he_phy_cap(phy->mt76, conf->vif);
+ 
+ 	tlv = mt7996_mcu_add_uni_tlv(skb, UNI_BSS_INFO_HE_BASIC, sizeof(*he));
+ 
+ 	he = (struct bss_info_uni_he *)tlv;
+-	he->he_pe_duration = vif->bss_conf.htc_trig_based_pkt_ext;
++	he->he_pe_duration = conf->htc_trig_based_pkt_ext;
+ 	if (!he->he_pe_duration)
+ 		he->he_pe_duration = DEFAULT_HE_PE_DURATION;
+ 
+-	he->he_rts_thres = cpu_to_le16(vif->bss_conf.frame_time_rts_th);
++	he->he_rts_thres = cpu_to_le16(conf->frame_time_rts_th);
+ 	if (!he->he_rts_thres)
+ 		he->he_rts_thres = cpu_to_le16(DEFAULT_HE_DURATION_RTS_THRES);
+ 
+@@ -992,13 +990,13 @@ mt7996_mcu_bss_he_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
+ }
+ 
+ static void
+-mt7996_mcu_bss_mbssid_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
++mt7996_mcu_bss_mbssid_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *conf,
+ 			  struct mt7996_phy *phy, int enable)
+ {
+ 	struct bss_info_uni_mbssid *mbssid;
+ 	struct tlv *tlv;
+ 
+-	if (!vif->bss_conf.bssid_indicator)
++	if (!conf->bssid_indicator)
+ 		return;
+ 
+ 	tlv = mt7996_mcu_add_uni_tlv(skb, UNI_BSS_INFO_11V_MBSSID, sizeof(*mbssid));
+@@ -1006,23 +1004,21 @@ mt7996_mcu_bss_mbssid_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
+ 	mbssid = (struct bss_info_uni_mbssid *)tlv;
+ 
+ 	if (enable) {
+-		mbssid->max_indicator = vif->bss_conf.bssid_indicator;
+-		mbssid->mbss_idx = vif->bss_conf.bssid_index;
++		mbssid->max_indicator = conf->bssid_indicator;
++		mbssid->mbss_idx = conf->bssid_index;
+ 		mbssid->tx_bss_omac_idx = 0;
+ 	}
+ }
+ 
+ static void
+-mt7996_mcu_bss_bmc_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
++mt7996_mcu_bss_bmc_tlv(struct sk_buff *skb, struct mt7996_bss_conf *mconf,
+ 		       struct mt7996_phy *phy)
+ {
+-	struct mt76_vif *mvif = (struct mt76_vif *)vif->drv_priv;
+ 	struct bss_rate_tlv *bmc;
+ 	struct cfg80211_chan_def *chandef = &phy->mt76->chandef;
+ 	enum nl80211_band band = chandef->chan->band;
+ 	struct tlv *tlv;
+-	u8 idx = mvif->mcast_rates_idx ?
+-		 mvif->mcast_rates_idx : mvif->basic_rates_idx;
++	u8 idx = mconf->mt76.mcast_rates_idx ?: mconf->mt76.basic_rates_idx;
+ 
+ 	tlv = mt7996_mcu_add_uni_tlv(skb, UNI_BSS_INFO_RATE, sizeof(*bmc));
+ 
+@@ -1046,9 +1042,9 @@ mt7996_mcu_bss_txcmd_tlv(struct sk_buff *skb, bool en)
+ }
+ 
+ static void
+-mt7996_mcu_bss_mld_tlv(struct sk_buff *skb, struct ieee80211_vif *vif)
++mt7996_mcu_bss_mld_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
++		       struct mt7996_bss_conf *mconf)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct bss_mld_tlv *mld;
+ 	struct tlv *tlv;
+ 
+@@ -1056,33 +1052,30 @@ mt7996_mcu_bss_mld_tlv(struct sk_buff *skb, struct ieee80211_vif *vif)
+ 
+ 	mld = (struct bss_mld_tlv *)tlv;
+ 	mld->group_mld_id = 0xff;
+-	mld->own_mld_id = mvif->mt76.idx;
++	mld->own_mld_id = mconf->mt76.idx;
+ 	mld->remap_idx = 0xff;
+ }
+ 
+ static void
+-mt7996_mcu_bss_sec_tlv(struct sk_buff *skb, struct ieee80211_vif *vif)
++mt7996_mcu_bss_sec_tlv(struct sk_buff *skb, struct mt7996_bss_conf *mconf)
+ {
+-	struct mt76_vif *mvif = (struct mt76_vif *)vif->drv_priv;
+ 	struct bss_sec_tlv *sec;
+ 	struct tlv *tlv;
+ 
+ 	tlv = mt7996_mcu_add_uni_tlv(skb, UNI_BSS_INFO_SEC, sizeof(*sec));
+ 
+ 	sec = (struct bss_sec_tlv *)tlv;
+-	sec->cipher = mvif->cipher;
++	sec->cipher = mconf->mt76.cipher;
+ }
+ 
+ static int
+-mt7996_mcu_muar_config(struct mt7996_phy *phy, struct ieee80211_vif *vif,
+-		       bool bssid, bool enable)
++mt7996_mcu_muar_config(struct mt7996_phy *phy, struct ieee80211_bss_conf *conf,
++		       struct mt7996_bss_conf *mconf, bool bssid, bool enable)
+ {
+ #define UNI_MUAR_ENTRY 2
+ 	struct mt7996_dev *dev = phy->dev;
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	u32 idx = mvif->mt76.omac_idx - REPEATER_BSSID_START;
+-	const u8 *addr = vif->addr;
+-
++	u32 idx = mconf->mt76.omac_idx - REPEATER_BSSID_START;
++	const u8 *addr = bssid ? conf->bssid : conf->vif->addr;
+ 	struct {
+ 		struct {
+ 			u8 band;
+@@ -1107,9 +1100,6 @@ mt7996_mcu_muar_config(struct mt7996_phy *phy, struct ieee80211_vif *vif,
+ 		.entry_add = true,
+ 	};
+ 
+-	if (bssid)
+-		addr = vif->bss_conf.bssid;
+-
+ 	if (enable)
+ 		memcpy(req.addr, addr, ETH_ALEN);
+ 
+@@ -1118,10 +1108,8 @@ mt7996_mcu_muar_config(struct mt7996_phy *phy, struct ieee80211_vif *vif,
+ }
+ 
+ static void
+-mt7996_mcu_bss_ifs_timing_tlv(struct sk_buff *skb, struct ieee80211_vif *vif)
++mt7996_mcu_bss_ifs_timing_tlv(struct sk_buff *skb, struct mt7996_phy *phy)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_phy *phy = mvif->phy;
+ 	struct bss_ifs_time_tlv *ifs_time;
+ 	struct tlv *tlv;
+ 	bool is_2ghz = phy->mt76->chandef.chan->band == NL80211_BAND_2GHZ;
+@@ -1147,12 +1135,13 @@ mt7996_mcu_bss_ifs_timing_tlv(struct sk_buff *skb, struct ieee80211_vif *vif)
+ 
+ static int
+ mt7996_mcu_bss_basic_tlv(struct sk_buff *skb,
+-			 struct ieee80211_vif *vif,
++			 struct ieee80211_bss_conf *conf,
++			 struct mt7996_bss_conf *mconf,
+ 			 struct ieee80211_sta *sta,
+ 			 struct mt76_phy *phy, u16 wlan_idx,
+ 			 bool enable)
+ {
+-	struct mt76_vif *mvif = (struct mt76_vif *)vif->drv_priv;
++	struct ieee80211_vif *vif = conf->vif;
+ 	struct cfg80211_chan_def *chandef = &phy->chandef;
+ 	struct mt76_connac_bss_basic_tlv *bss;
+ 	u32 type = CONNECTION_INFRA_AP;
+@@ -1169,8 +1158,7 @@ mt7996_mcu_bss_basic_tlv(struct sk_buff *skb,
+ 		if (enable) {
+ 			rcu_read_lock();
+ 			if (!sta)
+-				sta = ieee80211_find_sta(vif,
+-							 vif->bss_conf.bssid);
++				sta = ieee80211_find_sta(vif, conf->bssid);
+ 			/* TODO: enable BSS_INFO_UAPSD & BSS_INFO_PM */
+ 			if (sta) {
+ 				struct mt76_wcid *wcid;
+@@ -1193,18 +1181,17 @@ mt7996_mcu_bss_basic_tlv(struct sk_buff *skb,
+ 	tlv = mt7996_mcu_add_uni_tlv(skb, UNI_BSS_INFO_BASIC, sizeof(*bss));
+ 
+ 	bss = (struct mt76_connac_bss_basic_tlv *)tlv;
+-	bss->bcn_interval = cpu_to_le16(vif->bss_conf.beacon_int);
+-	bss->dtim_period = vif->bss_conf.dtim_period;
+ 	bss->bmc_tx_wlan_idx = cpu_to_le16(wlan_idx);
+ 	bss->sta_idx = cpu_to_le16(sta_wlan_idx);
+ 	bss->conn_type = cpu_to_le32(type);
+-	bss->omac_idx = mvif->omac_idx;
+-	bss->band_idx = mvif->band_idx;
+-	bss->wmm_idx = mvif->wmm_idx;
++	bss->omac_idx = mconf->mt76.omac_idx;
++	bss->band_idx = mconf->mt76.band_idx;
++	bss->wmm_idx = mconf->mt76.wmm_idx;
+ 	bss->conn_state = !enable;
+ 	bss->active = enable;
+ 
+-	idx = mvif->omac_idx > EXT_BSSID_START ? HW_BSSID_0 : mvif->omac_idx;
++	idx = mconf->mt76.omac_idx > EXT_BSSID_START ? HW_BSSID_0 :
++						       mconf->mt76.omac_idx;
+ 	bss->hw_bss_idx = idx;
+ 
+ 	if (vif->type == NL80211_IFTYPE_MONITOR) {
+@@ -1217,9 +1204,9 @@ mt7996_mcu_bss_basic_tlv(struct sk_buff *skb,
+ 		return 0;
+ 	}
+ 
+-	memcpy(bss->bssid, vif->bss_conf.bssid, ETH_ALEN);
+-	bss->bcn_interval = cpu_to_le16(vif->bss_conf.beacon_int);
+-	bss->dtim_period = vif->bss_conf.dtim_period;
++	memcpy(bss->bssid, conf->bssid, ETH_ALEN);
++	bss->bcn_interval = cpu_to_le16(conf->beacon_int);
++	bss->dtim_period = conf->dtim_period;
+ 	bss->phymode = mt76_connac_get_phy_mode(phy, vif,
+ 						chandef->chan->band, NULL);
+ 	bss->phymode_ext = mt76_connac_get_phy_mode_ext(phy, vif,
+@@ -1246,63 +1233,64 @@ __mt7996_mcu_alloc_bss_req(struct mt76_dev *dev, struct mt76_vif *mvif, int len)
+ }
+ 
+ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy,
+-			    struct ieee80211_vif *vif, int enable)
++			    struct ieee80211_bss_conf *conf,
++			    struct mt7996_bss_conf *mconf, int enable)
+ {
++	struct ieee80211_vif *vif = conf->vif;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_dev *dev = phy->dev;
+ 	struct sk_buff *skb;
+ 
+-	if (mvif->mt76.omac_idx >= REPEATER_BSSID_START) {
+-		mt7996_mcu_muar_config(phy, vif, false, enable);
+-		mt7996_mcu_muar_config(phy, vif, true, enable);
++	if (mconf->mt76.omac_idx >= REPEATER_BSSID_START) {
++		mt7996_mcu_muar_config(phy, conf, mconf, false, enable);
++		mt7996_mcu_muar_config(phy, conf, mconf, true, enable);
+ 	}
+ 
+-	skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &mvif->mt76,
++	skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &mconf->mt76,
+ 					 MT7996_BSS_UPDATE_MAX_SIZE);
+ 	if (IS_ERR(skb))
+ 		return PTR_ERR(skb);
+ 
+ 	/* bss_basic must be first */
+-	mt7996_mcu_bss_basic_tlv(skb, vif, NULL, phy->mt76,
++	mt7996_mcu_bss_basic_tlv(skb, conf, mconf, NULL, phy->mt76,
+ 				 mvif->sta.wcid.idx, enable);
+-	mt7996_mcu_bss_sec_tlv(skb, vif);
++	mt7996_mcu_bss_sec_tlv(skb, mconf);
+ 
+ 	if (vif->type == NL80211_IFTYPE_MONITOR)
+ 		goto out;
+ 
+ 	if (enable) {
+-		mt7996_mcu_bss_rfch_tlv(skb, vif, phy);
+-		mt7996_mcu_bss_bmc_tlv(skb, vif, phy);
+-		mt7996_mcu_bss_ra_tlv(skb, vif, phy);
++		mt7996_mcu_bss_rfch_tlv(skb, phy);
++		mt7996_mcu_bss_bmc_tlv(skb, mconf, phy);
++		mt7996_mcu_bss_ra_tlv(skb);
+ 		mt7996_mcu_bss_txcmd_tlv(skb, true);
+-		mt7996_mcu_bss_ifs_timing_tlv(skb, vif);
++		mt7996_mcu_bss_ifs_timing_tlv(skb, phy);
+ 
+-		if (vif->bss_conf.he_support)
+-			mt7996_mcu_bss_he_tlv(skb, vif, phy);
++		if (conf->he_support)
++			mt7996_mcu_bss_he_tlv(skb, conf, phy);
+ 
+ 		/* this tag is necessary no matter if the vif is MLD */
+-		mt7996_mcu_bss_mld_tlv(skb, vif);
++		mt7996_mcu_bss_mld_tlv(skb, vif, mconf);
+ 	}
+ 
+-	mt7996_mcu_bss_mbssid_tlv(skb, vif, phy, enable);
++	mt7996_mcu_bss_mbssid_tlv(skb, conf, phy, enable);
+ 
+ out:
+ 	return mt76_mcu_skb_send_msg(&dev->mt76, skb,
+ 				     MCU_WMWA_UNI_CMD(BSS_INFO_UPDATE), true);
+ }
+ 
+-int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct ieee80211_vif *vif)
++int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct mt7996_bss_conf *mconf)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_dev *dev = phy->dev;
+ 	struct sk_buff *skb;
+ 
+-	skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &mvif->mt76,
++	skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &mconf->mt76,
+ 					 MT7996_BSS_UPDATE_MAX_SIZE);
+ 	if (IS_ERR(skb))
+ 		return PTR_ERR(skb);
+ 
+-	mt7996_mcu_bss_ifs_timing_tlv(skb, vif);
++	mt7996_mcu_bss_ifs_timing_tlv(skb, phy);
+ 
+ 	return mt76_mcu_skb_send_msg(&dev->mt76, skb,
+ 				     MCU_WMWA_UNI_CMD(BSS_INFO_UPDATE), true);
+@@ -1344,12 +1332,12 @@ int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev,
+ 			 bool enable)
+ {
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)params->sta->drv_priv;
+-	struct mt7996_vif *mvif = msta->vif;
++	struct mt7996_bss_conf *mconf = mconf_dereference_protected(msta->vif, 0);
+ 
+ 	if (enable && !params->amsdu)
+ 		msta->wcid.amsdu = false;
+ 
+-	return mt7996_mcu_sta_ba(dev, &mvif->mt76, params, enable, true);
++	return mt7996_mcu_sta_ba(dev, &mconf->mt76, params, enable, true);
+ }
+ 
+ int mt7996_mcu_add_rx_ba(struct mt7996_dev *dev,
+@@ -1357,13 +1345,14 @@ int mt7996_mcu_add_rx_ba(struct mt7996_dev *dev,
+ 			 bool enable)
+ {
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)params->sta->drv_priv;
+-	struct mt7996_vif *mvif = msta->vif;
++	struct mt7996_bss_conf *mconf = mconf_dereference_protected(msta->vif, 0);
+ 
+-	return mt7996_mcu_sta_ba(dev, &mvif->mt76, params, enable, false);
++	return mt7996_mcu_sta_ba(dev, &mconf->mt76, params, enable, false);
+ }
+ 
+ static void
+-mt7996_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
++mt7996_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *conf,
++		      struct mt7996_bss_conf *mconf,
+ 		      struct ieee80211_sta *sta)
+ {
+ 	struct ieee80211_he_cap_elem *elem = &sta->deflink.he_cap.he_cap_elem;
+@@ -1384,9 +1373,9 @@ mt7996_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
+ 		he->he_phy_cap[i] = elem->phy_cap_info[i];
+ 	}
+ 
+-	if (vif->type == NL80211_IFTYPE_AP &&
++	if (conf->vif->type == NL80211_IFTYPE_AP &&
+ 	    (elem->phy_cap_info[1] & IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD))
+-		u8p_replace_bits(&he->he_phy_cap[1], vif->bss_conf.he_ldpc,
++		u8p_replace_bits(&he->he_phy_cap[1], conf->he_ldpc,
+ 				 IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD);
+ 
+ 	mcs_map = sta->deflink.he_cap.he_mcs_nss_supp;
+@@ -1394,16 +1383,16 @@ mt7996_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
+ 	case IEEE80211_STA_RX_BW_160:
+ 		if (elem->phy_cap_info[0] &
+ 		    IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G)
+-			mt7996_mcu_set_sta_he_mcs(sta,
++			mt7996_mcu_set_sta_he_mcs(sta, mconf,
+ 						  &he->max_nss_mcs[CMD_HE_MCS_BW8080],
+ 						  le16_to_cpu(mcs_map.rx_mcs_80p80));
+ 
+-		mt7996_mcu_set_sta_he_mcs(sta,
++		mt7996_mcu_set_sta_he_mcs(sta, mconf,
+ 					  &he->max_nss_mcs[CMD_HE_MCS_BW160],
+ 					  le16_to_cpu(mcs_map.rx_mcs_160));
+ 		fallthrough;
+ 	default:
+-		mt7996_mcu_set_sta_he_mcs(sta,
++		mt7996_mcu_set_sta_he_mcs(sta, mconf,
+ 					  &he->max_nss_mcs[CMD_HE_MCS_BW80],
+ 					  le16_to_cpu(mcs_map.rx_mcs_80));
+ 		break;
+@@ -1494,7 +1483,7 @@ mt7996_mcu_sta_vht_tlv(struct sk_buff *skb, struct ieee80211_sta *sta)
+ 	struct tlv *tlv;
+ #ifdef CONFIG_MTK_VENDOR
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+-	struct mt7996_phy *phy = (struct mt7996_phy *)msta->vif->phy;
++	struct mt7996_phy *phy = (struct mt7996_phy *)msta->vif->deflink.phy;
+ #endif
+ 
+ 	/* For 6G band, this tlv is necessary to let hw work normally */
+@@ -1551,25 +1540,26 @@ mt7996_mcu_sta_amsdu_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 
+ static void
+ mt7996_mcu_sta_muru_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+-			struct ieee80211_vif *vif, struct ieee80211_sta *sta)
++			struct ieee80211_bss_conf *conf,
++			struct mt7996_bss_conf *mconf,
++			struct ieee80211_sta *sta)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_phy *phy = mvif->phy;
++	struct mt7996_phy *phy = mconf->phy;
+ 	struct ieee80211_he_cap_elem *elem = &sta->deflink.he_cap.he_cap_elem;
+ 	struct sta_rec_muru *muru;
+ 	struct tlv *tlv;
+ 
+-	if (vif->type != NL80211_IFTYPE_STATION &&
+-	    vif->type != NL80211_IFTYPE_AP)
++	if (conf->vif->type != NL80211_IFTYPE_STATION &&
++	    conf->vif->type != NL80211_IFTYPE_AP)
+ 		return;
+ 
+ 	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_MURU, sizeof(*muru));
+ 
+ 	muru = (struct sta_rec_muru *)tlv;
+-	muru->cfg.mimo_dl_en = (vif->bss_conf.eht_mu_beamformer ||
+-				vif->bss_conf.he_mu_beamformer ||
+-				vif->bss_conf.vht_mu_beamformer ||
+-				vif->bss_conf.vht_mu_beamformee) &&
++	muru->cfg.mimo_dl_en = (conf->eht_mu_beamformer ||
++				conf->he_mu_beamformer ||
++				conf->vht_mu_beamformer ||
++				conf->vht_mu_beamformee) &&
+ 			       !!(phy->muru_onoff & MUMIMO_DL);
+ 	muru->cfg.mimo_ul_en = !!(phy->muru_onoff & MUMIMO_UL);
+ 	muru->cfg.ofdma_dl_en = !!(phy->muru_onoff & OFDMA_DL);
+@@ -1610,13 +1600,14 @@ mt7996_mcu_sta_muru_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ }
+ 
+ static inline bool
+-mt7996_is_ebf_supported(struct mt7996_phy *phy, struct ieee80211_vif *vif,
++mt7996_is_ebf_supported(struct mt7996_phy *phy, struct ieee80211_bss_conf *conf,
++			struct mt7996_bss_conf *mconf,
+ 			struct ieee80211_sta *sta, bool bfee)
+ {
+ 	int sts = hweight16(phy->mt76->chainmask);
+ 
+-	if (vif->type != NL80211_IFTYPE_STATION &&
+-	    vif->type != NL80211_IFTYPE_AP)
++	if (conf->vif->type != NL80211_IFTYPE_STATION &&
++	    conf->vif->type != NL80211_IFTYPE_AP)
+ 		return false;
+ 
+ 	if (!bfee && sts < 2)
+@@ -1627,10 +1618,10 @@ mt7996_is_ebf_supported(struct mt7996_phy *phy, struct ieee80211_vif *vif,
+ 		struct ieee80211_eht_cap_elem_fixed *pe = &pc->eht_cap_elem;
+ 
+ 		if (bfee)
+-			return vif->bss_conf.eht_su_beamformee &&
++			return conf->eht_su_beamformee &&
+ 			       EHT_PHY(CAP0_SU_BEAMFORMEE, pe->phy_cap_info[0]);
+ 		else
+-			return vif->bss_conf.eht_su_beamformer &&
++			return conf->eht_su_beamformer &&
+ 			       EHT_PHY(CAP0_SU_BEAMFORMER, pe->phy_cap_info[0]);
+ 	}
+ 
+@@ -1638,10 +1629,10 @@ mt7996_is_ebf_supported(struct mt7996_phy *phy, struct ieee80211_vif *vif,
+ 		struct ieee80211_he_cap_elem *pe = &sta->deflink.he_cap.he_cap_elem;
+ 
+ 		if (bfee)
+-			return vif->bss_conf.he_su_beamformee &&
++			return conf->he_su_beamformee &&
+ 			       HE_PHY(CAP3_SU_BEAMFORMER, pe->phy_cap_info[3]);
+ 		else
+-			return vif->bss_conf.he_su_beamformer &&
++			return conf->he_su_beamformer &&
+ 			       HE_PHY(CAP4_SU_BEAMFORMEE, pe->phy_cap_info[4]);
+ 	}
+ 
+@@ -1649,10 +1640,10 @@ mt7996_is_ebf_supported(struct mt7996_phy *phy, struct ieee80211_vif *vif,
+ 		u32 cap = sta->deflink.vht_cap.cap;
+ 
+ 		if (bfee)
+-			return vif->bss_conf.vht_su_beamformee &&
++			return conf->vht_su_beamformee &&
+ 			       (cap & IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE);
+ 		else
+-			return vif->bss_conf.vht_su_beamformer &&
++			return conf->vht_su_beamformer &&
+ 			       (cap & IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE);
+ 	}
+ 
+@@ -1848,10 +1839,10 @@ mt7996_mcu_sta_bfer_eht(struct ieee80211_sta *sta, struct ieee80211_vif *vif,
+ 
+ static void
+ mt7996_mcu_sta_bfer_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+-			struct ieee80211_vif *vif, struct ieee80211_sta *sta)
++			struct ieee80211_bss_conf *conf, struct mt7996_bss_conf *mconf,
++			struct ieee80211_sta *sta)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_phy *phy = mvif->phy;
++	struct mt7996_phy *phy = mconf->phy;
+ 	int tx_ant = hweight8(phy->mt76->chainmask) - 1;
+ 	struct sta_rec_bf *bf;
+ 	struct tlv *tlv;
+@@ -1866,7 +1857,7 @@ mt7996_mcu_sta_bfer_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 	if (!(sta->deflink.ht_cap.ht_supported || sta->deflink.he_cap.has_he))
+ 		return;
+ 
+-	ebf = mt7996_is_ebf_supported(phy, vif, sta, false);
++	ebf = mt7996_is_ebf_supported(phy, conf, mconf, sta, false);
+ 	if (!ebf && !dev->ibf)
+ 		return;
+ 
+@@ -1878,9 +1869,9 @@ mt7996_mcu_sta_bfer_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 	 * ht: iBF only, since mac80211 lacks of eBF support
+ 	 */
+ 	if (sta->deflink.eht_cap.has_eht && ebf)
+-		mt7996_mcu_sta_bfer_eht(sta, vif, phy, bf);
++		mt7996_mcu_sta_bfer_eht(sta, conf->vif, phy, bf);
+ 	else if (sta->deflink.he_cap.has_he && ebf)
+-		mt7996_mcu_sta_bfer_he(sta, vif, phy, bf);
++		mt7996_mcu_sta_bfer_he(sta, conf->vif, phy, bf);
+ 	else if (sta->deflink.vht_cap.vht_supported)
+ 		mt7996_mcu_sta_bfer_vht(sta, phy, bf, ebf);
+ 	else if (sta->deflink.ht_cap.ht_supported)
+@@ -1919,10 +1910,10 @@ mt7996_mcu_sta_bfer_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 
+ static void
+ mt7996_mcu_sta_bfee_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+-			struct ieee80211_vif *vif, struct ieee80211_sta *sta)
++			struct ieee80211_bss_conf *conf,
++			struct mt7996_bss_conf *mconf, struct ieee80211_sta *sta)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_phy *phy = mvif->phy;
++	struct mt7996_phy *phy = mconf->phy;
+ 	int tx_ant = hweight8(phy->mt76->antenna_mask) - 1;
+ 	struct sta_rec_bfee *bfee;
+ 	struct tlv *tlv;
+@@ -1931,7 +1922,7 @@ mt7996_mcu_sta_bfee_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 	if (!(sta->deflink.vht_cap.vht_supported || sta->deflink.he_cap.has_he))
+ 		return;
+ 
+-	if (!mt7996_is_ebf_supported(phy, vif, sta, true))
++	if (!mt7996_is_ebf_supported(phy, conf, mconf, sta, false))
+ 		return;
+ 
+ 	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_BFEE, sizeof(*bfee));
+@@ -2053,17 +2044,17 @@ int mt7996_mcu_set_fixed_rate_ctrl(struct mt7996_dev *dev,
+ 				     MCU_WM_UNI_CMD(RA), true);
+ }
+ 
+-int mt7996_mcu_set_fixed_field(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++int mt7996_mcu_set_fixed_field(struct mt7996_dev *dev,
++			       struct mt7996_bss_conf *mconf,
+ 			       struct ieee80211_sta *sta, void *data, u32 field)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct sta_phy_uni *phy = data;
+ 	struct sta_rec_ra_fixed_uni *ra;
+ 	struct sk_buff *skb;
+ 	struct tlv *tlv;
+ 
+-	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mvif->mt76,
++	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76,
+ 					      &msta->wcid,
+ 					      MT7996_STA_UPDATE_MAX_SIZE);
+ 	if (IS_ERR(skb))
+@@ -2095,12 +2086,13 @@ int mt7996_mcu_set_fixed_field(struct mt7996_dev *dev, struct ieee80211_vif *vif
+ }
+ 
+ static int
+-mt7996_mcu_add_rate_ctrl_fixed(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++mt7996_mcu_add_rate_ctrl_fixed(struct mt7996_dev *dev,
++			       struct ieee80211_bss_conf *conf,
++			       struct mt7996_bss_conf *mconf,
+ 			       struct ieee80211_sta *sta)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct cfg80211_chan_def *chandef = &mvif->phy->mt76->chandef;
+-	struct cfg80211_bitrate_mask *mask = &mvif->bitrate_mask;
++	struct cfg80211_chan_def *chandef = &mconf->phy->mt76->chandef;
++	struct cfg80211_bitrate_mask *mask = &mconf->bitrate_mask;
+ 	enum nl80211_band band = chandef->chan->band;
+ 	struct sta_phy_uni phy = {};
+ 	int ret, nrates = 0;
+@@ -2142,7 +2134,7 @@ mt7996_mcu_add_rate_ctrl_fixed(struct mt7996_dev *dev, struct ieee80211_vif *vif
+ 
+ 	/* fixed single rate */
+ 	if (nrates == 1) {
+-		ret = mt7996_mcu_set_fixed_field(dev, vif, sta, &phy,
++		ret = mt7996_mcu_set_fixed_field(dev, mconf, sta, &phy,
+ 						 RATE_PARAM_FIXED_MCS);
+ 		if (ret)
+ 			return ret;
+@@ -2164,7 +2156,7 @@ mt7996_mcu_add_rate_ctrl_fixed(struct mt7996_dev *dev, struct ieee80211_vif *vif
+ 		else
+ 			mt76_rmw_field(dev, addr, GENMASK(15, 12), phy.sgi);
+ 
+-		ret = mt7996_mcu_set_fixed_field(dev, vif, sta, &phy,
++		ret = mt7996_mcu_set_fixed_field(dev, mconf, sta, &phy,
+ 						 RATE_PARAM_FIXED_GI);
+ 		if (ret)
+ 			return ret;
+@@ -2172,7 +2164,7 @@ mt7996_mcu_add_rate_ctrl_fixed(struct mt7996_dev *dev, struct ieee80211_vif *vif
+ 
+ 	/* fixed HE_LTF */
+ 	if (mask->control[band].he_ltf != GENMASK(7, 0)) {
+-		ret = mt7996_mcu_set_fixed_field(dev, vif, sta, &phy,
++		ret = mt7996_mcu_set_fixed_field(dev, mconf, sta, &phy,
+ 						 RATE_PARAM_FIXED_HE_LTF);
+ 		if (ret)
+ 			return ret;
+@@ -2183,13 +2175,14 @@ mt7996_mcu_add_rate_ctrl_fixed(struct mt7996_dev *dev, struct ieee80211_vif *vif
+ 
+ static void
+ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev,
+-			     struct ieee80211_vif *vif, struct ieee80211_sta *sta)
++			     struct ieee80211_bss_conf *conf,
++			     struct mt7996_bss_conf *mconf,
++			     struct ieee80211_sta *sta)
+ {
+ #define INIT_RCPI 180
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt76_phy *mphy = mvif->phy->mt76;
++	struct mt76_phy *mphy = mconf->phy->mt76;
+ 	struct cfg80211_chan_def *chandef = &mphy->chandef;
+-	struct cfg80211_bitrate_mask *mask = &mvif->bitrate_mask;
++	struct cfg80211_bitrate_mask *mask = &mconf->bitrate_mask;
+ 	enum nl80211_band band = chandef->chan->band;
+ 	struct sta_rec_ra_uni *ra;
+ 	struct tlv *tlv;
+@@ -2201,7 +2194,7 @@ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev,
+ 
+ 	ra->valid = true;
+ 	ra->auto_rate = true;
+-	ra->phy_mode = mt76_connac_get_phy_mode(mphy, vif, band, sta);
++	ra->phy_mode = mt76_connac_get_phy_mode(mphy, conf->vif, band, sta);
+ 	ra->channel = chandef->chan->hw_value;
+ 	ra->bw = (sta->deflink.bandwidth == IEEE80211_STA_RX_BW_320) ?
+ 		 CMD_CBW_320MHZ : sta->deflink.bandwidth;
+@@ -2240,7 +2233,7 @@ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev,
+ 			cap |= STA_CAP_TX_STBC;
+ 		if (sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_RX_STBC)
+ 			cap |= STA_CAP_RX_STBC;
+-		if (vif->bss_conf.ht_ldpc &&
++		if (conf->ht_ldpc &&
+ 		    (sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_LDPC_CODING))
+ 			cap |= STA_CAP_LDPC;
+ 
+@@ -2266,7 +2259,7 @@ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev,
+ 			cap |= STA_CAP_VHT_TX_STBC;
+ 		if (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_1)
+ 			cap |= STA_CAP_VHT_RX_STBC;
+-		if (vif->bss_conf.vht_ldpc &&
++		if (conf->vht_ldpc &&
+ 		    (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_RXLDPC))
+ 			cap |= STA_CAP_VHT_LDPC;
+ 
+@@ -2287,15 +2280,16 @@ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev,
+ 	memset(ra->rx_rcpi, INIT_RCPI, sizeof(ra->rx_rcpi));
+ }
+ 
+-int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev,
++			     struct ieee80211_bss_conf *conf,
++			     struct mt7996_bss_conf *mconf,
+ 			     struct ieee80211_sta *sta, bool changed)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct sk_buff *skb;
+ 	int ret;
+ 
+-	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mvif->mt76,
++	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76,
+ 					      &msta->wcid,
+ 					      MT7996_STA_UPDATE_MAX_SIZE);
+ 	if (IS_ERR(skb))
+@@ -2306,26 +2300,27 @@ int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 	 * update sta_rec_he here.
+ 	 */
+ 	if (changed)
+-		mt7996_mcu_sta_he_tlv(skb, vif, sta);
++		mt7996_mcu_sta_he_tlv(skb, conf, mconf, sta);
+ 
+ 	/* sta_rec_ra accommodates BW, NSS and only MCS range format
+ 	 * i.e 0-{7,8,9} for VHT.
+ 	 */
+-	mt7996_mcu_sta_rate_ctrl_tlv(skb, dev, vif, sta);
++	mt7996_mcu_sta_rate_ctrl_tlv(skb, dev, conf, mconf, sta);
+ 
+ 	ret = mt76_mcu_skb_send_msg(&dev->mt76, skb,
+ 				    MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true);
+ 	if (ret)
+ 		return ret;
+ 
+-	return mt7996_mcu_add_rate_ctrl_fixed(dev, vif, sta);
++	return mt7996_mcu_add_rate_ctrl_fixed(dev, conf, mconf, sta);
+ }
+ 
+ static int
+-mt7996_mcu_sta_init_vow(struct mt7996_phy *phy, struct mt7996_sta *msta)
++mt7996_mcu_sta_init_vow(struct mt7996_bss_conf *mconf, struct mt7996_sta *msta)
+ {
++	struct mt7996_phy *phy = mconf->phy;
+ 	struct mt7996_vow_sta_ctrl *vow = &msta->vow;
+-	u8 omac_idx = msta->vif->mt76.omac_idx;
++	u8 omac_idx = mconf->mt76.omac_idx;
+ 	int ret;
+ 
+ 	/* Assignment of STA BSS group index aligns FW.
+@@ -2342,20 +2337,22 @@ mt7996_mcu_sta_init_vow(struct mt7996_phy *phy, struct mt7996_sta *msta)
+ 	vow->drr_quantum[IEEE80211_AC_BE] = VOW_DRR_QUANTUM_IDX2;
+ 	vow->drr_quantum[IEEE80211_AC_BK] = VOW_DRR_QUANTUM_IDX2;
+ 
+-	ret = mt7996_mcu_set_vow_drr_ctrl(phy, msta, VOW_DRR_CTRL_STA_BSS_GROUP);
++	ret = mt7996_mcu_set_vow_drr_ctrl(phy, mconf, msta, VOW_DRR_CTRL_STA_BSS_GROUP);
+ 	if (ret)
+ 		return ret;
+ 
+-	ret = mt7996_mcu_set_vow_drr_ctrl(phy, msta, VOW_DRR_CTRL_STA_PAUSE);
++	ret = mt7996_mcu_set_vow_drr_ctrl(phy, mconf, msta, VOW_DRR_CTRL_STA_PAUSE);
+ 	if (ret)
+ 		return ret;
+ 
+-	return mt7996_mcu_set_vow_drr_ctrl(phy, msta, VOW_DRR_CTRL_STA_ALL);
++	return mt7996_mcu_set_vow_drr_ctrl(phy, mconf, msta, VOW_DRR_CTRL_STA_ALL);
+ }
+ 
+-int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+-		       struct ieee80211_sta *sta, bool enable, bool newly)
++int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_bss_conf *conf,
++		       struct mt7996_bss_conf *mconf, struct ieee80211_sta *sta,
++		       bool enable, bool newly)
+ {
++	struct ieee80211_vif *vif = conf->vif;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta;
+ 	struct sk_buff *skb;
+@@ -2363,7 +2360,7 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 
+ 	msta = sta ? (struct mt7996_sta *)sta->drv_priv : &mvif->sta;
+ 
+-	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mvif->mt76,
++	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76,
+ 					      &msta->wcid,
+ 					      MT7996_STA_UPDATE_MAX_SIZE);
+ 	if (IS_ERR(skb))
+@@ -2385,7 +2382,7 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 		/* starec hdrt mode */
+ 		mt7996_mcu_sta_hdrt_tlv(dev, skb);
+ 		/* starec bfer */
+-		mt7996_mcu_sta_bfer_tlv(dev, skb, vif, sta);
++		mt7996_mcu_sta_bfer_tlv(dev, skb, conf, mconf, sta);
+ 		/* starec ht */
+ 		mt7996_mcu_sta_ht_tlv(skb, sta);
+ 		/* starec vht */
+@@ -2395,18 +2392,18 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 		/* starec amsdu */
+ 		mt7996_mcu_sta_amsdu_tlv(dev, skb, vif, sta);
+ 		/* starec he */
+-		mt7996_mcu_sta_he_tlv(skb, vif, sta);
++		mt7996_mcu_sta_he_tlv(skb, conf, mconf, sta);
+ 		/* starec he 6g*/
+ 		mt7996_mcu_sta_he_6g_tlv(skb, sta);
+ 		/* starec eht */
+ 		mt7996_mcu_sta_eht_tlv(skb, sta);
+ 		/* starec muru */
+-		mt7996_mcu_sta_muru_tlv(dev, skb, vif, sta);
++		mt7996_mcu_sta_muru_tlv(dev, skb, conf, mconf, sta);
+ 		/* starec bfee */
+-		mt7996_mcu_sta_bfee_tlv(dev, skb, vif, sta);
++		mt7996_mcu_sta_bfee_tlv(dev, skb, conf, mconf, sta);
+ 	}
+ 
+-	ret = mt7996_mcu_sta_init_vow(mvif->phy, msta);
++	ret = mt7996_mcu_sta_init_vow(mconf, msta);
+ 	if (ret) {
+ 		dev_kfree_skb(skb);
+ 		return ret;
+@@ -2461,16 +2458,15 @@ mt7996_mcu_sta_key_tlv(struct mt76_wcid *wcid,
+ 	return 0;
+ }
+ 
+-int mt7996_mcu_add_key(struct mt76_dev *dev, struct ieee80211_vif *vif,
++int mt7996_mcu_add_key(struct mt76_dev *dev, struct mt7996_bss_conf *mconf,
+ 		       struct ieee80211_key_conf *key, int mcu_cmd,
+ 		       struct mt76_wcid *wcid, enum set_key_cmd cmd)
+ {
+-	struct mt76_vif *mvif = (struct mt76_vif *)vif->drv_priv;
+ 	struct sk_buff *skb;
+ 	int ret;
+ 
+-	skb = __mt76_connac_mcu_alloc_sta_req(dev, mvif, wcid,
+-					      MT7996_STA_UPDATE_MAX_SIZE);
++	skb = __mt76_connac_mcu_alloc_sta_req(dev, (struct mt76_vif *)mconf,
++					      wcid, MT7996_STA_UPDATE_MAX_SIZE);
+ 	if (IS_ERR(skb))
+ 		return PTR_ERR(skb);
+ 
+@@ -2481,17 +2477,18 @@ int mt7996_mcu_add_key(struct mt76_dev *dev, struct ieee80211_vif *vif,
+ 	return mt76_mcu_skb_send_msg(dev, skb, mcu_cmd, true);
+ }
+ 
+-static int mt7996_mcu_get_pn(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+-			     u8 *pn)
++static int mt7996_mcu_get_pn(struct mt7996_dev *dev,
++			     struct ieee80211_bss_conf *conf,
++			     struct mt7996_bss_conf *mconf, u8 *pn)
+ {
+ #define TSC_TYPE_BIGTK_PN 2
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_vif *mvif = (struct mt7996_vif *)conf->vif->drv_priv;
+ 	struct sta_rec_pn_info *pn_info;
+ 	struct sk_buff *skb, *rskb;
+ 	struct tlv *tlv;
+ 	int ret;
+ 
+-	skb = mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mvif->mt76, &mvif->sta.wcid);
++	skb = mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76, &mvif->sta.wcid);
+ 	if (IS_ERR(skb))
+ 		return PTR_ERR(skb);
+ 
+@@ -2515,10 +2512,11 @@ static int mt7996_mcu_get_pn(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 	return 0;
+ }
+ 
+-int mt7996_mcu_bcn_prot_enable(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++int mt7996_mcu_bcn_prot_enable(struct mt7996_dev *dev,
++			       struct ieee80211_bss_conf *conf,
++			       struct mt7996_bss_conf *mconf,
+ 			       struct ieee80211_key_conf *key)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_mcu_bcn_prot_tlv *bcn_prot;
+ 	struct sk_buff *skb;
+ 	struct tlv *tlv;
+@@ -2527,7 +2525,7 @@ int mt7996_mcu_bcn_prot_enable(struct mt7996_dev *dev, struct ieee80211_vif *vif
+ 		  sizeof(struct mt7996_mcu_bcn_prot_tlv);
+ 	int ret;
+ 
+-	skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &mvif->mt76, len);
++	skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &mconf->mt76, len);
+ 	if (IS_ERR(skb))
+ 		return PTR_ERR(skb);
+ 
+@@ -2535,7 +2533,7 @@ int mt7996_mcu_bcn_prot_enable(struct mt7996_dev *dev, struct ieee80211_vif *vif
+ 
+ 	bcn_prot = (struct mt7996_mcu_bcn_prot_tlv *)tlv;
+ 
+-	ret = mt7996_mcu_get_pn(dev, vif, pn);
++	ret = mt7996_mcu_get_pn(dev, conf, mconf, pn);
+ 	if (ret) {
+ 		dev_kfree_skb(skb);
+ 		return ret;
+@@ -2568,10 +2566,10 @@ int mt7996_mcu_bcn_prot_enable(struct mt7996_dev *dev, struct ieee80211_vif *vif
+ 				     MCU_WMWA_UNI_CMD(BSS_INFO_UPDATE), true);
+ }
+ int mt7996_mcu_add_dev_info(struct mt7996_phy *phy,
+-			    struct ieee80211_vif *vif, bool enable)
++			    struct ieee80211_bss_conf *conf,
++			    struct mt7996_bss_conf *mconf, bool enable)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct {
+ 		struct req_hdr {
+ 			u8 omac_idx;
+@@ -2587,8 +2585,8 @@ int mt7996_mcu_add_dev_info(struct mt7996_phy *phy,
+ 		} __packed tlv;
+ 	} data = {
+ 		.hdr = {
+-			.omac_idx = mvif->mt76.omac_idx,
+-			.band_idx = mvif->mt76.band_idx,
++			.omac_idx = mconf->mt76.omac_idx,
++			.band_idx = mconf->mt76.band_idx,
+ 		},
+ 		.tlv = {
+ 			.tag = cpu_to_le16(DEV_INFO_ACTIVE),
+@@ -2597,16 +2595,16 @@ int mt7996_mcu_add_dev_info(struct mt7996_phy *phy,
+ 		},
+ 	};
+ 
+-	if (mvif->mt76.omac_idx >= REPEATER_BSSID_START)
+-		return mt7996_mcu_muar_config(phy, vif, false, enable);
++	if (mconf->mt76.omac_idx >= REPEATER_BSSID_START)
++		return mt7996_mcu_muar_config(phy, conf, mconf, false, enable);
+ 
+-	memcpy(data.tlv.omac_addr, vif->addr, ETH_ALEN);
++	memcpy(data.tlv.omac_addr, conf->addr, ETH_ALEN);
+ 	return mt76_mcu_send_msg(&dev->mt76, MCU_WMWA_UNI_CMD(DEV_INFO_UPDATE),
+ 				 &data, sizeof(data), true);
+ }
+ 
+ static void
+-mt7996_mcu_beacon_cntdwn(struct ieee80211_vif *vif, struct sk_buff *rskb,
++mt7996_mcu_beacon_cntdwn(struct ieee80211_bss_conf *conf, struct sk_buff *rskb,
+ 			 struct sk_buff *skb,
+ 			 struct ieee80211_mutable_offsets *offs)
+ {
+@@ -2617,7 +2615,7 @@ mt7996_mcu_beacon_cntdwn(struct ieee80211_vif *vif, struct sk_buff *rskb,
+ 	if (!offs->cntdwn_counter_offs[0])
+ 		return;
+ 
+-	tag = vif->bss_conf.csa_active ? UNI_BSS_INFO_BCN_CSA : UNI_BSS_INFO_BCN_BCC;
++	tag = conf->csa_active ? UNI_BSS_INFO_BCN_CSA : UNI_BSS_INFO_BCN_BCC;
+ 
+ 	tlv = mt7996_mcu_add_uni_tlv(rskb, tag, sizeof(*info));
+ 
+@@ -2627,14 +2625,15 @@ mt7996_mcu_beacon_cntdwn(struct ieee80211_vif *vif, struct sk_buff *rskb,
+ 
+ static void
+ mt7996_mcu_beacon_mbss(struct sk_buff *rskb, struct sk_buff *skb,
+-		       struct ieee80211_vif *vif, struct bss_bcn_content_tlv *bcn,
++		       struct ieee80211_bss_conf *conf,
++		       struct bss_bcn_content_tlv *bcn,
+ 		       struct ieee80211_mutable_offsets *offs)
+ {
+ 	struct bss_bcn_mbss_tlv *mbss;
+ 	const struct element *elem;
+ 	struct tlv *tlv;
+ 
+-	if (!vif->bss_conf.bssid_indicator)
++	if (!conf->bssid_indicator)
+ 		return;
+ 
+ 	tlv = mt7996_mcu_add_uni_tlv(rskb, UNI_BSS_INFO_BCN_MBSSID, sizeof(*mbss));
+@@ -2679,7 +2678,7 @@ mt7996_mcu_beacon_mbss(struct sk_buff *rskb, struct sk_buff *skb,
+ }
+ 
+ static void
+-mt7996_mcu_beacon_cont(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++mt7996_mcu_beacon_cont(struct mt7996_dev *dev, struct ieee80211_bss_conf *conf,
+ 		       struct sk_buff *rskb, struct sk_buff *skb,
+ 		       struct bss_bcn_content_tlv *bcn,
+ 		       struct ieee80211_mutable_offsets *offs)
+@@ -2693,9 +2692,9 @@ mt7996_mcu_beacon_cont(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 	if (offs->cntdwn_counter_offs[0]) {
+ 		u16 offset = offs->cntdwn_counter_offs[0];
+ 
+-		if (vif->bss_conf.csa_active)
++		if (conf->csa_active)
+ 			bcn->csa_ie_pos = cpu_to_le16(offset - 4);
+-		if (vif->bss_conf.color_change_active)
++		if (conf->color_change_active)
+ 			bcn->bcc_ie_pos = cpu_to_le16(offset - 3);
+ 	}
+ 
+@@ -2707,11 +2706,11 @@ mt7996_mcu_beacon_cont(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ }
+ 
+ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw,
+-			  struct ieee80211_vif *vif, int en)
++			  struct ieee80211_bss_conf *conf,
++			  struct mt7996_bss_conf *mconf, int en)
+ {
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct ieee80211_mutable_offsets offs;
+ 	struct ieee80211_tx_info *info;
+ 	struct sk_buff *skb, *rskb;
+@@ -2719,15 +2718,15 @@ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw,
+ 	struct bss_bcn_content_tlv *bcn;
+ 	int len;
+ 
+-	if (vif->bss_conf.nontransmitted)
++	if (conf->nontransmitted)
+ 		return 0;
+ 
+-	rskb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &mvif->mt76,
++	rskb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &mconf->mt76,
+ 					  MT7996_MAX_BSS_OFFLOAD_SIZE);
+ 	if (IS_ERR(rskb))
+ 		return PTR_ERR(rskb);
+ 
+-	skb = ieee80211_beacon_get_template(hw, vif, &offs, 0);
++	skb = ieee80211_beacon_get_template(hw, conf->vif, &offs, 0);
+ 	if (!skb) {
+ 		dev_kfree_skb(rskb);
+ 		return -EINVAL;
+@@ -2750,9 +2749,9 @@ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw,
+ 	if (!en)
+ 		goto out;
+ 
+-	mt7996_mcu_beacon_cont(dev, vif, rskb, skb, bcn, &offs);
+-	mt7996_mcu_beacon_mbss(rskb, skb, vif, bcn, &offs);
+-	mt7996_mcu_beacon_cntdwn(vif, rskb, skb, &offs);
++	mt7996_mcu_beacon_cont(dev, conf, rskb, skb, bcn, &offs);
++	mt7996_mcu_beacon_mbss(rskb, skb, conf, bcn, &offs);
++	mt7996_mcu_beacon_cntdwn(conf, rskb, skb, &offs);
+ out:
+ 	dev_kfree_skb(skb);
+ 	return mt76_mcu_skb_send_msg(&phy->dev->mt76, rskb,
+@@ -2760,14 +2759,15 @@ out:
+ }
+ 
+ int mt7996_mcu_beacon_inband_discov(struct mt7996_dev *dev,
+-				    struct ieee80211_vif *vif, u32 changed)
++				    struct ieee80211_bss_conf *conf,
++				    struct mt7996_bss_conf *mconf, u32 changed)
+ {
+ #define OFFLOAD_TX_MODE_SU	BIT(0)
+ #define OFFLOAD_TX_MODE_MU	BIT(1)
+ 	struct ieee80211_hw *hw = mt76_hw(dev);
++	struct ieee80211_vif *vif = conf->vif;
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct cfg80211_chan_def *chandef = &mvif->phy->mt76->chandef;
++	struct cfg80211_chan_def *chandef = &mconf->phy->mt76->chandef;
+ 	enum nl80211_band band = chandef->chan->band;
+ 	struct mt76_wcid *wcid = &dev->mt76.global_wcid;
+ 	struct bss_inband_discovery_tlv *discov;
+@@ -2777,20 +2777,20 @@ int mt7996_mcu_beacon_inband_discov(struct mt7996_dev *dev,
+ 	u8 *buf, interval;
+ 	int len;
+ 
+-	if (vif->bss_conf.nontransmitted)
++	if (conf->nontransmitted)
+ 		return 0;
+ 
+-	rskb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &mvif->mt76,
++	rskb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &mconf->mt76,
+ 					  MT7996_MAX_BSS_OFFLOAD_SIZE);
+ 	if (IS_ERR(rskb))
+ 		return PTR_ERR(rskb);
+ 
+ 	if (changed & BSS_CHANGED_FILS_DISCOVERY) {
+-		interval = vif->bss_conf.fils_discovery.max_interval;
++		interval = conf->fils_discovery.max_interval;
+ 		skb = ieee80211_get_fils_discovery_tmpl(hw, vif);
+ 	} else if (changed & BSS_CHANGED_UNSOL_BCAST_PROBE_RESP &&
+-		   vif->bss_conf.unsol_bcast_probe_resp_interval) {
+-		interval = vif->bss_conf.unsol_bcast_probe_resp_interval;
++		   conf->unsol_bcast_probe_resp_interval) {
++		interval = conf->unsol_bcast_probe_resp_interval;
+ 		skb = ieee80211_get_unsol_bcast_probe_resp_tmpl(hw, vif);
+ 	}
+ 
+@@ -3424,7 +3424,7 @@ int mt7996_mcu_set_hdr_trans(struct mt7996_dev *dev, bool hdr_trans)
+ 				     MCU_WM_UNI_CMD(RX_HDR_TRANS), true);
+ }
+ 
+-int mt7996_mcu_set_tx(struct mt7996_dev *dev, struct ieee80211_vif *vif)
++int mt7996_mcu_set_tx(struct mt7996_dev *dev, struct mt7996_bss_conf *mconf)
+ {
+ #define MCU_EDCA_AC_PARAM	0
+ #define WMM_AIFS_SET		BIT(0)
+@@ -3433,12 +3433,11 @@ int mt7996_mcu_set_tx(struct mt7996_dev *dev, struct ieee80211_vif *vif)
+ #define WMM_TXOP_SET		BIT(3)
+ #define WMM_PARAM_SET		(WMM_AIFS_SET | WMM_CW_MIN_SET | \
+ 				 WMM_CW_MAX_SET | WMM_TXOP_SET)
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct {
+ 		u8 bss_idx;
+ 		u8 __rsv[3];
+ 	} __packed hdr = {
+-		.bss_idx = mvif->mt76.idx,
++		.bss_idx = mconf->mt76.idx,
+ 	};
+ 	struct sk_buff *skb;
+ 	int len = sizeof(hdr) + IEEE80211_NUM_ACS * sizeof(struct edca);
+@@ -3451,7 +3450,7 @@ int mt7996_mcu_set_tx(struct mt7996_dev *dev, struct ieee80211_vif *vif)
+ 	skb_put_data(skb, &hdr, sizeof(hdr));
+ 
+ 	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+-		struct ieee80211_tx_queue_params *q = &mvif->queue_params[ac];
++		struct ieee80211_tx_queue_params *q = &mconf->queue_params[ac];
+ 		struct edca *e;
+ 		struct tlv *tlv;
+ 
+@@ -4479,12 +4478,12 @@ mt7996_mcu_set_obss_spr_pd(struct mt7996_phy *phy,
+ }
+ 
+ static int
+-mt7996_mcu_set_obss_spr_siga(struct mt7996_phy *phy, struct ieee80211_vif *vif,
++mt7996_mcu_set_obss_spr_siga(struct mt7996_phy *phy,
++			     struct mt7996_bss_conf *mconf,
+ 			     struct ieee80211_he_obss_pd *he_obss_pd)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_dev *dev = phy->dev;
+-	u8 omac = mvif->mt76.omac_idx;
++	u8 omac = mconf->mt76.omac_idx;
+ 	struct {
+ 		u8 band_idx;
+ 		u8 __rsv[3];
+@@ -4556,7 +4555,8 @@ mt7996_mcu_set_obss_spr_bitmap(struct mt7996_phy *phy,
+ 				 sizeof(req), true);
+ }
+ 
+-int mt7996_mcu_add_obss_spr(struct mt7996_phy *phy, struct ieee80211_vif *vif,
++int mt7996_mcu_add_obss_spr(struct mt7996_phy *phy,
++			    struct mt7996_bss_conf *mconf,
+ 			    struct ieee80211_he_obss_pd *he_obss_pd)
+ {
+ 	int ret;
+@@ -4590,7 +4590,7 @@ int mt7996_mcu_add_obss_spr(struct mt7996_phy *phy, struct ieee80211_vif *vif,
+ 		return ret;
+ 
+ 	/* Set SR prohibit */
+-	ret = mt7996_mcu_set_obss_spr_siga(phy, vif, he_obss_pd);
++	ret = mt7996_mcu_set_obss_spr_siga(phy, mconf, he_obss_pd);
+ 	if (ret)
+ 		return ret;
+ 
+@@ -4598,16 +4598,16 @@ int mt7996_mcu_add_obss_spr(struct mt7996_phy *phy, struct ieee80211_vif *vif,
+ 	return mt7996_mcu_set_obss_spr_bitmap(phy, he_obss_pd);
+ }
+ 
+-int mt7996_mcu_update_bss_color(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++int mt7996_mcu_update_bss_color(struct mt7996_dev *dev,
++				struct mt7996_bss_conf *mconf,
+ 				struct cfg80211_he_bss_color *he_bss_color)
+ {
+ 	int len = sizeof(struct bss_req_hdr) + sizeof(struct bss_color_tlv);
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct bss_color_tlv *bss_color;
+ 	struct sk_buff *skb;
+ 	struct tlv *tlv;
+ 
+-	skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &mvif->mt76, len);
++	skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &mconf->mt76, len);
+ 	if (IS_ERR(skb))
+ 		return PTR_ERR(skb);
+ 
+@@ -4626,7 +4626,7 @@ int mt7996_mcu_update_bss_color(struct mt7996_dev *dev, struct ieee80211_vif *vi
+ #define TWT_AGRT_PROTECT	BIT(2)
+ 
+ int mt7996_mcu_twt_agrt_update(struct mt7996_dev *dev,
+-			       struct mt7996_vif *mvif,
++			       struct mt7996_bss_conf *mconf,
+ 			       struct mt7996_twt_flow *flow,
+ 			       int cmd)
+ {
+@@ -4657,12 +4657,12 @@ int mt7996_mcu_twt_agrt_update(struct mt7996_dev *dev,
+ 		.len = cpu_to_le16(sizeof(req) - 4),
+ 		.tbl_idx = flow->table_id,
+ 		.cmd = cmd,
+-		.own_mac_idx = mvif->mt76.omac_idx,
++		.own_mac_idx = mconf->mt76.omac_idx,
+ 		.flowid = flow->id,
+ 		.peer_id = cpu_to_le16(flow->wcid),
+ 		.duration = flow->duration,
+-		.bss = mvif->mt76.idx,
+-		.bss_idx = mvif->mt76.idx,
++		.bss = mconf->mt76.idx,
++		.bss_idx = mconf->mt76.idx,
+ 		.start_tsf = cpu_to_le64(flow->tsf),
+ 		.mantissa = flow->mantissa,
+ 		.exponent = flow->exp,
+@@ -4793,15 +4793,15 @@ int mt7996_mcu_rdd_background_disable_timer(struct mt7996_dev *dev, bool disable
+ 
+ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev,
+ 				     struct ieee80211_vif *vif,
++				     struct mt7996_bss_conf *mconf,
+ 				     struct ieee80211_sta *sta)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta;
+ 	struct sk_buff *skb;
+ 
+-	msta = sta ? (struct mt7996_sta *)sta->drv_priv : &mvif->sta;
++	msta = sta ? (struct mt7996_sta *)sta->drv_priv : &mconf->vif->sta;
+ 
+-	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mvif->mt76,
++	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76,
+ 					      &msta->wcid,
+ 					      MT7996_STA_UPDATE_MAX_SIZE);
+ 	if (IS_ERR(skb))
+@@ -5435,8 +5435,9 @@ int mt7996_mcu_set_scs(struct mt7996_phy *phy, u8 enable)
+ 				 &req, sizeof(req), false);
+ }
+ 
+-int mt7996_mcu_set_vow_drr_ctrl(struct mt7996_phy *phy, struct mt7996_sta *msta,
+-	                        enum vow_drr_ctrl_id id)
++int mt7996_mcu_set_vow_drr_ctrl(struct mt7996_phy *phy,
++				struct mt7996_bss_conf *mconf,
++				struct mt7996_sta *msta, enum vow_drr_ctrl_id id)
+ {
+ 	struct mt7996_vow_sta_ctrl *vow = msta ? &msta->vow : NULL;
+ 	u32 val = 0;
+@@ -5462,9 +5463,9 @@ int mt7996_mcu_set_vow_drr_ctrl(struct mt7996_phy *phy, struct mt7996_sta *msta,
+ 		.len = cpu_to_le16(sizeof(req) - 4),
+ 		.wlan_idx = cpu_to_le16(msta ? msta->wcid.idx : 0),
+ 		.band_idx = phy->mt76->band_idx,
+-		.wmm_idx = msta ? msta->vif->mt76.wmm_idx : 0,
++		.wmm_idx = msta ? mconf->mt76.wmm_idx : 0,
+ 		.ctrl_id = cpu_to_le32(id),
+-		.omac_idx = msta ? msta->vif->mt76.omac_idx : 0
++		.omac_idx = msta ? mconf->mt76.omac_idx : 0
+ 	};
+ 
+ 	switch (id) {
+@@ -5652,7 +5653,7 @@ void mt7996_set_wireless_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
+ {
+ 	u8 mode, val;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_phy *phy =  mvif->phy;
++	struct mt7996_phy *phy =  mvif->deflink.phy;
+ 
+ 	mode = FIELD_GET(RATE_CFG_MODE, *((u32 *)data));
+ 	val = FIELD_GET(RATE_CFG_VAL, *((u32 *)data));
+@@ -5683,11 +5684,11 @@ void mt7996_set_wireless_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
+ void mt7996_set_beacon_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
+ {
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct ieee80211_hw *hw = mvif->phy->mt76->hw;
++	struct ieee80211_hw *hw = mvif->deflink.phy->mt76->hw;
+ 	u8 val = *((u8 *)data);
+ 
+ 	vif->bss_conf.enable_beacon = val;
+ 
+-	mt7996_mcu_add_beacon(hw, vif, val);
++	mt7996_mcu_add_beacon(hw, &vif->bss_conf, &mvif->deflink, val);
+ }
+ #endif
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 110979452..0fa2aaf7e 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -325,18 +325,25 @@ struct mt7996_sta {
+ 	struct mt7996_vow_sta_ctrl vow;
+ };
+ 
+-struct mt7996_vif {
++struct mt7996_bss_conf {
+ 	struct mt76_vif mt76; /* must be first */
+ 
+-	struct mt7996_sta sta;
++	struct mt7996_vif *vif;
+ 	struct mt7996_phy *phy;
+-
+ 	struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS];
+ 	struct cfg80211_bitrate_mask bitrate_mask;
+ 
+ 	struct mt7996_chanctx *chanctx;
+ };
+ 
++struct mt7996_vif {
++	struct mt7996_bss_conf deflink;
++	struct mt7996_bss_conf __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
++
++	struct mt7996_sta sta;
++	struct mt7996_dev *dev;
++};
++
+ /* crash-dump */
+ struct mt7996_crash_data {
+ 	guid_t guid;
+@@ -765,6 +772,13 @@ mt7996_chanctx_get(struct ieee80211_chanctx_conf *ctx)
+ 	return (struct mt7996_chanctx *)&ctx->drv_priv;
+ }
+ 
++static inline struct mt7996_bss_conf *
++mconf_dereference_protected(struct mt7996_vif *mvif, u8 link_id)
++{
++	return rcu_dereference_protected(mvif->link[link_id],
++					 lockdep_is_held(&mvif->dev->mt76.mutex));
++}
++
+ extern const struct ieee80211_ops mt7996_ops;
+ extern struct pci_driver mt7996_pci_driver;
+ extern struct pci_driver mt7996_hif_driver;
+@@ -775,7 +789,7 @@ struct mt7996_dev *mt7996_mmio_probe(struct device *pdev,
+ void mt7996_wfsys_reset(struct mt7996_dev *dev);
+ void mt7996_rro_hw_init(struct mt7996_dev *dev);
+ irqreturn_t mt7996_irq_handler(int irq, void *dev_instance);
+-u64 __mt7996_get_tsf(struct ieee80211_hw *hw, struct mt7996_vif *mvif);
++u64 __mt7996_get_tsf(struct ieee80211_hw *hw, struct mt7996_bss_conf *mconf);
+ int mt7996_register_device(struct mt7996_dev *dev);
+ void mt7996_unregister_device(struct mt7996_dev *dev);
+ const char *mt7996_eeprom_name(struct mt7996_dev *dev);
+@@ -801,37 +815,47 @@ int mt7996_run(struct ieee80211_hw *hw);
+ int mt7996_mcu_init(struct mt7996_dev *dev);
+ int mt7996_mcu_init_firmware(struct mt7996_dev *dev);
+ int mt7996_mcu_twt_agrt_update(struct mt7996_dev *dev,
+-			       struct mt7996_vif *mvif,
++			       struct mt7996_bss_conf *mconf,
+ 			       struct mt7996_twt_flow *flow,
+ 			       int cmd);
+ int mt7996_mcu_add_dev_info(struct mt7996_phy *phy,
+-			    struct ieee80211_vif *vif, bool enable);
++			    struct ieee80211_bss_conf *conf,
++			    struct mt7996_bss_conf *mconf, bool enable);
+ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy,
+-			    struct ieee80211_vif *vif, int enable);
+-int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+-		       struct ieee80211_sta *sta, bool enable, bool newly);
++			    struct ieee80211_bss_conf *conf,
++			    struct mt7996_bss_conf *mconf, int enable);
++int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_bss_conf *conf,
++		       struct mt7996_bss_conf *mconf, struct ieee80211_sta *sta,
++		       bool enable, bool newly);
+ int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev,
+ 			 struct ieee80211_ampdu_params *params,
+ 			 bool add);
+ int mt7996_mcu_add_rx_ba(struct mt7996_dev *dev,
+ 			 struct ieee80211_ampdu_params *params,
+ 			 bool add);
+-int mt7996_mcu_update_bss_color(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++int mt7996_mcu_update_bss_color(struct mt7996_dev *dev,
++				struct mt7996_bss_conf *mconf,
+ 				struct cfg80211_he_bss_color *he_bss_color);
+-int mt7996_mcu_add_beacon(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+-			  int enable);
++int mt7996_mcu_add_beacon(struct ieee80211_hw *hw,
++			  struct ieee80211_bss_conf *conf,
++			  struct mt7996_bss_conf *mconf, int en);
+ int mt7996_mcu_beacon_inband_discov(struct mt7996_dev *dev,
+-				    struct ieee80211_vif *vif, u32 changed);
+-int mt7996_mcu_add_obss_spr(struct mt7996_phy *phy, struct ieee80211_vif *vif,
++				    struct ieee80211_bss_conf *conf,
++				    struct mt7996_bss_conf *mconf, u32 changed);
++int mt7996_mcu_add_obss_spr(struct mt7996_phy *phy,
++			    struct mt7996_bss_conf *mconf,
+ 			    struct ieee80211_he_obss_pd *he_obss_pd);
+-int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev,
++			     struct ieee80211_bss_conf *conf,
++			     struct mt7996_bss_conf *mconf,
+ 			     struct ieee80211_sta *sta, bool changed);
+ int mt7996_set_channel(struct mt7996_phy *phy, struct cfg80211_chan_def *chandef);
+ int mt7996_mcu_set_chan_info(struct mt7996_phy *phy, u16 tag);
+-int mt7996_mcu_set_tx(struct mt7996_dev *dev, struct ieee80211_vif *vif);
++int mt7996_mcu_set_tx(struct mt7996_dev *dev, struct mt7996_bss_conf *mconf);
+ int mt7996_mcu_set_fixed_rate_ctrl(struct mt7996_dev *dev,
+ 				   void *data, u16 version);
+-int mt7996_mcu_set_fixed_field(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++int mt7996_mcu_set_fixed_field(struct mt7996_dev *dev,
++			       struct mt7996_bss_conf *mconf,
+ 			       struct ieee80211_sta *sta, void *data, u32 field);
+ int mt7996_mcu_set_eeprom(struct mt7996_dev *dev);
+ int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *read_buf);
+@@ -846,7 +870,7 @@ int mt7996_mcu_set_radar_th(struct mt7996_dev *dev, int index,
+ 			    const struct mt7996_dfs_pattern *pattern);
+ int mt7996_mcu_set_radio_en(struct mt7996_phy *phy, bool enable);
+ int mt7996_mcu_set_rts_thresh(struct mt7996_phy *phy, u32 val);
+-int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct ieee80211_vif *vif);
++int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct mt7996_bss_conf *mconf);
+ int mt7996_mcu_get_chan_mib_info(struct mt7996_phy *phy, bool chan_switch);
+ int mt7996_mcu_get_temperature(struct mt7996_phy *phy);
+ int mt7996_mcu_set_thermal_throttling(struct mt7996_phy *phy, u8 state);
+@@ -885,8 +909,9 @@ void mt7996_tm_rf_test_event(struct mt7996_dev *dev, struct sk_buff *skb);
+ int mt7996_mcu_set_scs(struct mt7996_phy *phy, u8 enable);
+ void mt7996_mcu_scs_sta_poll(struct work_struct *work);
+ int mt7996_mcu_set_band_confg(struct mt7996_phy *phy, u16 option, bool enable);
+-int mt7996_mcu_set_vow_drr_ctrl(struct mt7996_phy *phy, struct mt7996_sta *msta,
+-	                        enum vow_drr_ctrl_id id);
++int mt7996_mcu_set_vow_drr_ctrl(struct mt7996_phy *phy,
++				struct mt7996_bss_conf *mconf,
++				struct mt7996_sta *msta, enum vow_drr_ctrl_id id);
+ int mt7996_mcu_set_vow_feature_ctrl(struct mt7996_phy *phy);
+ void mt7996_mcu_wmm_pbc_work(struct work_struct *work);
+ 
+@@ -995,13 +1020,16 @@ void mt7996_update_channel(struct mt76_phy *mphy);
+ int mt7996_init_debugfs(struct mt7996_phy *phy);
+ void mt7996_debugfs_rx_fw_monitor(struct mt7996_dev *dev, const void *data, int len);
+ bool mt7996_debugfs_rx_log(struct mt7996_dev *dev, const void *data, int len);
+-int mt7996_mcu_add_key(struct mt76_dev *dev, struct ieee80211_vif *vif,
++int mt7996_mcu_add_key(struct mt76_dev *dev, struct mt7996_bss_conf *mconf,
+ 		       struct ieee80211_key_conf *key, int mcu_cmd,
+ 		       struct mt76_wcid *wcid, enum set_key_cmd cmd);
+-int mt7996_mcu_bcn_prot_enable(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++int mt7996_mcu_bcn_prot_enable(struct mt7996_dev *dev,
++			       struct ieee80211_bss_conf *conf,
++			       struct mt7996_bss_conf *mconf,
+ 			       struct ieee80211_key_conf *key);
+ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev,
+ 				     struct ieee80211_vif *vif,
++				     struct mt7996_bss_conf *mconf,
+ 				     struct ieee80211_sta *sta);
+ int mt7996_mcu_cp_support(struct mt7996_dev *dev, u8 mode);
+ int mt7996_mcu_set_pp_en(struct mt7996_phy *phy, bool auto_mode, u8 force_bitmap,
+diff --git a/mt7996/mtk_debugfs_i.c b/mt7996/mtk_debugfs_i.c
+new file mode 100644
+index 000000000..21748e798
+--- /dev/null
++++ b/mt7996/mtk_debugfs_i.c
+@@ -0,0 +1,1639 @@
++#include <linux/inet.h>
++#include "mt7996.h"
++#include "../mt76.h"
++#include "mcu.h"
++#include "mac.h"
++#include "eeprom.h"
++#include "mtk_debug.h"
++#include "mtk_debug_i.h"
++#include "mtk_mcu.h"
++#include "mtk_mcu_i.h"
++
++#ifdef CONFIG_MTK_DEBUG
++
++#define info_or_seq_printf(seq, fmt, ...)	do {	\
++	if (seq)					\
++		seq_printf(seq, fmt, ##__VA_ARGS__);	\
++	else						\
++		pr_info(fmt, ##__VA_ARGS__);		\
++} while (0)
++
++static void info_or_seq_hex_dump(struct seq_file *seq, int prefix_type,
++				 int rowsize, int groupsize, const void *buf,
++				 size_t len, bool ascii)
++{
++	if (seq)
++		seq_hex_dump(seq, "", prefix_type, rowsize, groupsize,
++			     buf, len, ascii);
++	else
++		print_hex_dump(KERN_INFO, "", prefix_type,
++			       rowsize, groupsize, buf, len, ascii);
++}
++
++void mt7996_packet_log_to_host(struct mt7996_dev *dev, const void *data, int len, int type, int des_len)
++{
++	struct bin_debug_hdr *hdr;
++	char *buf;
++
++	if (len > 1500 - sizeof(*hdr))
++	len = 1500 - sizeof(*hdr);
++
++	buf = kzalloc(sizeof(*hdr) + len, GFP_KERNEL);
++	if (!buf)
++		return;
++
++	hdr = (struct bin_debug_hdr *)buf;
++	hdr->magic_num = cpu_to_le32(PKT_BIN_DEBUG_MAGIC);
++	hdr->serial_id = cpu_to_le16(dev->fw_debug_seq++);
++	hdr->msg_type = cpu_to_le16(type);
++	hdr->len = cpu_to_le16(len);
++	hdr->des_len = cpu_to_le16(des_len);
++
++	memcpy(buf + sizeof(*hdr), data, len);
++
++	mt7996_debugfs_rx_log(dev, buf, sizeof(*hdr) + len);
++	kfree(buf);
++}
++
++//bmac dump mac txp
++static void mt7996_dump_bmac_mac_txp_info(struct seq_file *s, struct mt7996_dev *dev,
++					  __le32 *txp)
++{
++	struct mt7996_txp_token {
++		__le16 msdu[4];
++	} *msdu;
++	struct mt7996_txp_ptr {
++		__le32 addr1;
++		__le32 addr_info;
++		__le32 addr2;
++	} *ptr;
++	int i = 0;
++
++	for (i = 0; i < 12; i = i+2 ) {
++		if (i == 0 || i == 4) {
++			msdu = (struct mt7996_txp_token *) txp;
++			info_or_seq_printf(s, "msdu token(%d-%d)=%ld %ld %ld %ld (0x%08x-0x%08x)\n", i, i+3,
++				(msdu->msdu[0] & GENMASK(14, 0)),
++				(msdu->msdu[1] & GENMASK(14, 0)),
++				(msdu->msdu[2] & GENMASK(14, 0)),
++				(msdu->msdu[3] & GENMASK(14, 0)), *txp, *(txp+1));
++			txp = txp + 2;
++		}
++		ptr = (struct mt7996_txp_ptr *) txp;
++		info_or_seq_printf(s, "ptr%02d : addr(0x%08x) len(%ld) addr_h(%02lx) SRC(%d) ML(%d) \n",
++			i, ptr->addr1,
++			FIELD_GET(GENMASK(11, 0), ptr->addr_info),
++			FIELD_GET(GENMASK(13, 12), ptr->addr_info),
++			!!(ptr->addr_info & BIT(14)),
++			!!(ptr->addr_info & BIT(15)));
++		info_or_seq_printf(s, "ptr%02d : addr(0x%08x) len(%ld) addr_h(%02lx) SRC(%d) ML(%d) \n",
++			i+1, ptr->addr2,
++			FIELD_GET(GENMASK(27, 16), ptr->addr_info),
++			FIELD_GET(GENMASK(29, 28), ptr->addr_info),
++			!!(ptr->addr_info & BIT(30)),
++			!!(ptr->addr_info & BIT(31)));
++		txp = txp + 3;
++	}
++}
++
++//bmac dump hif txp
++void mt7996_dump_bmac_hif_txp_info(struct seq_file *s, struct mt7996_dev *dev,
++				   __le32 *txp, u32 hif_txp_ver)
++{
++	int i, j = 0;
++	u32 dw;
++
++	info_or_seq_printf(s, "txp raw data: size=%d\n", HIF_TXP_V2_SIZE);
++	info_or_seq_hex_dump(s, DUMP_PREFIX_OFFSET, 16, 1, (u8 *)txp, HIF_TXP_V2_SIZE, false);
++
++	info_or_seq_printf(s, "BMAC_TXP Fields:\n");
++
++	/* dw0 */
++	if (hif_txp_ver == 2) {
++		dw = le32_to_cpu(txp[0]);
++		info_or_seq_printf(s, "HIF_TXP_PRIORITY = %d\n",
++				GET_FIELD(HIF_TXP_PRIORITY, dw));
++		info_or_seq_printf(s, "HIF_TXP_FIXED_RATE = %d\n",
++				GET_FIELD(HIF_TXP_FIXED_RATE, dw));
++		info_or_seq_printf(s, "HIF_TXP_TCP = %d\n",
++				GET_FIELD(HIF_TXP_TCP, dw));
++		info_or_seq_printf(s, "HIF_TXP_NON_CIPHER = %d\n",
++				GET_FIELD(HIF_TXP_NON_CIPHER, dw));
++		info_or_seq_printf(s, "HIF_TXP_VLAN = %d\n",
++				GET_FIELD(HIF_TXP_VLAN, dw));
++		info_or_seq_printf(s, "HIF_TXP_BC_MC_FLAG = %d\n",
++				GET_FIELD(HIF_TXP_BC_MC_FLAG, dw));
++		info_or_seq_printf(s, "HIF_TXP_FR_HOST = %d\n",
++				GET_FIELD(HIF_TXP_FR_HOST, dw));
++		info_or_seq_printf(s, "HIF_TXP_ETYPE = %d\n",
++				GET_FIELD(HIF_TXP_ETYPE, dw));
++		info_or_seq_printf(s, "HIF_TXP_TXP_AMSDU = %d\n",
++				GET_FIELD(HIF_TXP_TXP_AMSDU, dw));
++		info_or_seq_printf(s, "HIF_TXP_TXP_MC_CLONE = %d\n",
++				GET_FIELD(HIF_TXP_TXP_MC_CLONE, dw));
++		info_or_seq_printf(s, "HIF_TXP_TOKEN_ID = %d\n",
++				GET_FIELD(HIF_TXP_TOKEN_ID, dw));
++
++		/* dw1 */
++		dw = le32_to_cpu(txp[1]);
++		info_or_seq_printf(s, "HIF_TXP_BSS_IDX = %d\n",
++				GET_FIELD(HIF_TXP_BSS_IDX, dw));
++		info_or_seq_printf(s, "HIF_TXP_USER_PRIORITY = %d\n",
++				GET_FIELD(HIF_TXP_USER_PRIORITY, dw));
++		info_or_seq_printf(s, "HIF_TXP_BUF_NUM = %d\n",
++				GET_FIELD(HIF_TXP_BUF_NUM, dw));
++		info_or_seq_printf(s, "HIF_TXP_MSDU_CNT = %d\n",
++				GET_FIELD(HIF_TXP_MSDU_CNT, dw));
++		info_or_seq_printf(s, "HIF_TXP_SRC = %d\n",
++				GET_FIELD(HIF_TXP_SRC, dw));
++
++		/* dw2 */
++		dw = le32_to_cpu(txp[2]);
++		info_or_seq_printf(s, "HIF_TXP_ETH_TYPE(network-endian) = 0x%x\n",
++				GET_FIELD(HIF_TXP_ETH_TYPE, dw));
++		info_or_seq_printf(s, "HIF_TXP_WLAN_IDX = %d\n",
++				GET_FIELD(HIF_TXP_WLAN_IDX, dw));
++
++		/* dw3 */
++		dw = le32_to_cpu(txp[3]);
++		info_or_seq_printf(s, "HIF_TXP_PPE_INFO = 0x%x\n",
++				GET_FIELD(HIF_TXP_PPE_INFO, dw));
++
++		for (i = 0; i < 13; i++) {
++			if (i % 2 == 0) {
++				info_or_seq_printf(s, "HIF_TXP_BUF_PTR%d_L = 0x%x\n",
++						i, GET_FIELD(HIF_TXP_BUF_PTR0_L,
++						le32_to_cpu(txp[4 + j])));
++				j++;
++				info_or_seq_printf(s, "HIF_TXP_BUF_LEN%d = %d\n",
++						i, GET_FIELD(HIF_TXP_BUF_LEN0, le32_to_cpu(txp[4 + j])));
++				info_or_seq_printf(s, "HIF_TXP_BUF_PTR%d_H = 0x%x\n",
++						i, GET_FIELD(HIF_TXP_BUF_PTR0_H, le32_to_cpu(txp[4 + j])));
++				if (i <= 10) {
++					info_or_seq_printf(s, "HIF_TXP_BUF_LEN%d = %d\n",
++							i + 1, GET_FIELD(HIF_TXP_BUF_LEN1, le32_to_cpu(txp[4 + j])));
++					info_or_seq_printf(s, "HIF_TXP_BUF_PTR%d_H = 0x%x\n",
++							i + 1, GET_FIELD(HIF_TXP_BUF_PTR1_H, le32_to_cpu(txp[4 + j])));
++				}
++				j++;
++			} else {
++				info_or_seq_printf(s, "HIF_TXP_BUF_PTR%d_L = 0x%x\n",
++					i, GET_FIELD(HIF_TXP_BUF_PTR1_L,
++					le32_to_cpu(txp[4 + j])));
++				j++;
++			}
++		}
++
++		info_or_seq_printf(s, "ml = 0x%x\n",
++			GET_FIELD(HIF_TXP_ML, le32_to_cpu(txp[23])));
++	} else {
++		struct mt76_connac_txp_common *txp_v1 = (struct mt76_connac_txp_common *)txp;
++
++		info_or_seq_printf(s, "FLAGS = (%04x)\n", txp_v1->fw.flags);
++
++		info_or_seq_printf(s, "MSDU = %d\n", txp_v1->fw.token);
++
++		info_or_seq_printf(s, "BSS_IDX = %d\n", txp_v1->fw.bss_idx);
++
++		info_or_seq_printf(s, "WCID = %d\n",txp_v1->fw.rept_wds_wcid);
++
++		info_or_seq_printf(s, "MSDU_CNT = %d\n", txp_v1->fw.nbuf);
++
++		for (i = 0; i < MT_TXP_MAX_BUF_NUM; i++)
++			info_or_seq_printf(s, "ptr%02d : addr(0x%08x) len(%d)\n", i, le32_to_cpu(txp_v1->fw.buf[i]),
++				le16_to_cpu(txp_v1->fw.len[i]));
++	}
++}
++
++/* bmac txd dump */
++void mt7996_dump_bmac_txd_info(struct seq_file *s, struct mt7996_dev *dev,
++			       __le32 *txd, bool is_hif_txd, bool dump_txp)
++{
++	u32 hif_txp_ver = 0;
++
++	/* dump stop */
++	if (!dev->dbg.txd_read_cnt)
++		return;
++
++	/* force dump */
++	if (dev->dbg.txd_read_cnt > 8)
++		dev->dbg.txd_read_cnt = 8;
++
++	/* dump txd_read_cnt times */
++	if (dev->dbg.txd_read_cnt != 8)
++		dev->dbg.txd_read_cnt--;
++
++	info_or_seq_printf(s, "txd raw data: size=%d\n", MT_TXD_SIZE);
++	info_or_seq_hex_dump(s, DUMP_PREFIX_OFFSET, 16, 1, (u8 *)txd, MT_TXD_SIZE, false);
++
++	info_or_seq_printf(s, "BMAC_TXD Fields:\n");
++	/* dw0 */
++	if (is_hif_txd) {
++		hif_txp_ver = FIELD_GET(GENMASK(22, 19), txd[0]);
++		info_or_seq_printf(s, "HIF TXD VER = %d\n", hif_txp_ver);
++	}
++	info_or_seq_printf(s, "TX_BYTE_COUNT = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_TX_BYTE_COUNT, txd[0]));
++	info_or_seq_printf(s, "ETHER_TYPE_OFFSET(word) = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_ETHER_TYPE_OFFSET, txd[0]));
++	info_or_seq_printf(s, "PKT_FT = %d%s%s%s%s\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_PKT_FT, txd[0]),
++			GET_FIELD(WF_TX_DESCRIPTOR_PKT_FT, txd[0]) == 0 ? "(ct)" : "",
++			GET_FIELD(WF_TX_DESCRIPTOR_PKT_FT, txd[0]) == 1 ? "(s&f)" : "",
++			GET_FIELD(WF_TX_DESCRIPTOR_PKT_FT, txd[0]) == 2 ? "(cmd)" : "",
++			GET_FIELD(WF_TX_DESCRIPTOR_PKT_FT, txd[0]) == 3 ? "(redirect)" : "");
++	info_or_seq_printf(s, "Q_IDX = %d%s%s%s\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_Q_IDX, txd[0]),
++			GET_FIELD(WF_TX_DESCRIPTOR_Q_IDX, txd[0]) == 0x10 ? "(ALTX)" : "",
++			GET_FIELD(WF_TX_DESCRIPTOR_Q_IDX, txd[0]) == 0x11 ? "(BMC)" : "",
++			GET_FIELD(WF_TX_DESCRIPTOR_Q_IDX, txd[0]) == 0x12 ? "(BCN)" : "");
++
++	/* dw1 */
++	info_or_seq_printf(s, "MLD_ID = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_MLD_ID, txd[1]));
++	info_or_seq_printf(s, "TGID = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_TGID, txd[1]));
++	info_or_seq_printf(s, "HF = %d%s%s%s%s\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_HF, txd[1]),
++			GET_FIELD(WF_TX_DESCRIPTOR_HF, txd[1]) == 0 ? "(eth/802.3)" : "",
++			GET_FIELD(WF_TX_DESCRIPTOR_HF, txd[1]) == 1 ? "(cmd)" : "",
++			GET_FIELD(WF_TX_DESCRIPTOR_HF, txd[1]) == 2 ? "(802.11)" : "",
++			GET_FIELD(WF_TX_DESCRIPTOR_HF, txd[1]) == 3 ? "(802.11 enhanced" : "");
++	info_or_seq_printf(s, "802.11 HEADER_LENGTH = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_HF, txd[1]) == 2 ?
++			GET_FIELD(WF_TX_DESCRIPTOR_HEADER_LENGTH, txd[1]) : 0);
++	info_or_seq_printf(s, "MRD = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_HF, txd[1]) == 0 ?
++			GET_FIELD(WF_TX_DESCRIPTOR_MRD, txd[1]) : 0);
++	info_or_seq_printf(s, "EOSP = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_HF, txd[1]) == 0 ?
++			GET_FIELD(WF_TX_DESCRIPTOR_EOSP, txd[1]) : 0);
++	info_or_seq_printf(s, "AMS = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_HF, txd[1]) == 3 ?
++			GET_FIELD(WF_TX_DESCRIPTOR_AMS, txd[1]) : 0);
++	info_or_seq_printf(s, "RMVL = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_HF, txd[1]) == 0 ?
++			GET_FIELD(WF_TX_DESCRIPTOR_RMVL, txd[1]): 0);
++	info_or_seq_printf(s, "VLAN = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_HF, txd[1]) == 0 ?
++			GET_FIELD(WF_TX_DESCRIPTOR_VLAN, txd[1]) : 0);
++	info_or_seq_printf(s, "ETYP = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_HF, txd[1]) == 0 ?
++			GET_FIELD(WF_TX_DESCRIPTOR_ETYP, txd[1]) : 0);
++	info_or_seq_printf(s, "TID_MGMT_TYPE = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_TID_MGMT_TYPE, txd[1]));
++	info_or_seq_printf(s, "OM = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_OM, txd[1]));
++	info_or_seq_printf(s, "FR = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_FR, txd[1]));
++
++	/* dw2 */
++	info_or_seq_printf(s, "SUBTYPE = %d%s%s%s%s\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_SUBTYPE, txd[2]),
++			(GET_FIELD(WF_TX_DESCRIPTOR_FTYPE, txd[2]) == 0) &&
++			(GET_FIELD(WF_TX_DESCRIPTOR_SUBTYPE, txd[2]) == 13) ?
++			"(action)" : "",
++			(GET_FIELD(WF_TX_DESCRIPTOR_FTYPE, txd[2]) == 1) &&
++			(GET_FIELD(WF_TX_DESCRIPTOR_SUBTYPE, txd[2]) == 8) ?
++			"(bar)" : "",
++			(GET_FIELD(WF_TX_DESCRIPTOR_FTYPE, txd[2]) == 2) &&
++			(GET_FIELD(WF_TX_DESCRIPTOR_SUBTYPE, txd[2]) == 4) ?
++			"(null)" : "",
++			(GET_FIELD(WF_TX_DESCRIPTOR_FTYPE, txd[2]) == 2) &&
++			(GET_FIELD(WF_TX_DESCRIPTOR_SUBTYPE, txd[2]) == 12) ?
++			"(qos null)" : "");
++
++	info_or_seq_printf(s, "FTYPE = %d%s%s%s\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_FTYPE, txd[2]),
++			GET_FIELD(WF_TX_DESCRIPTOR_FTYPE, txd[2]) == 0 ? "(mgmt)" : "",
++			GET_FIELD(WF_TX_DESCRIPTOR_FTYPE, txd[2]) == 1 ? "(ctl)" : "",
++			GET_FIELD(WF_TX_DESCRIPTOR_FTYPE, txd[2]) == 2 ? "(data)" : "");
++	info_or_seq_printf(s, "BF_TYPE = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_BF_TYPE, txd[2]));
++	info_or_seq_printf(s, "OM_MAP = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_OM_MAP, txd[2]));
++	info_or_seq_printf(s, "RTS = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_RTS, txd[2]));
++	info_or_seq_printf(s, "HEADER_PADDING = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_HEADER_PADDING, txd[2]));
++	info_or_seq_printf(s, "DU = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_DU, txd[2]));
++	info_or_seq_printf(s, "HE = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_HE, txd[2]));
++	info_or_seq_printf(s, "FRAG = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_FRAG, txd[2]));
++	info_or_seq_printf(s, "REMAINING_TX_TIME = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_REMAINING_TX_TIME, txd[2]));
++	info_or_seq_printf(s, "POWER_OFFSET = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_POWER_OFFSET, txd[2]));
++
++	/* dw3 */
++	info_or_seq_printf(s, "NA = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_NA, txd[3]));
++	info_or_seq_printf(s, "PF = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_PF, txd[3]));
++	info_or_seq_printf(s, "EMRD = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_EMRD, txd[3]));
++	info_or_seq_printf(s, "EEOSP = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_EEOSP, txd[3]));
++	info_or_seq_printf(s, "BM = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_BM, txd[3]));
++	info_or_seq_printf(s, "HW_AMSDU_CAP = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_HW_AMSDU_CAP, txd[3]));
++	info_or_seq_printf(s, "TX_COUNT = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_TX_COUNT, txd[3]));
++	info_or_seq_printf(s, "REMAINING_TX_COUNT = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_REMAINING_TX_COUNT, txd[3]));
++	info_or_seq_printf(s, "SN = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_SN, txd[3]));
++	info_or_seq_printf(s, "BA_DIS = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_BA_DIS, txd[3]));
++	info_or_seq_printf(s, "PM = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_PM, txd[3]));
++	info_or_seq_printf(s, "PN_VLD = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_PN_VLD, txd[3]));
++	info_or_seq_printf(s, "SN_VLD = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_SN_VLD, txd[3]));
++
++	/* dw4 */
++	info_or_seq_printf(s, "PN_31_0 = 0x%x\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_PN_31_0_, txd[4]));
++
++	/* dw5 */
++	info_or_seq_printf(s, "PID = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_PID, txd[5]));
++	info_or_seq_printf(s, "TXSFM = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_TXSFM, txd[5]));
++	info_or_seq_printf(s, "TXS2M = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_TXS2M, txd[5]));
++	info_or_seq_printf(s, "TXS2H = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_TXS2H, txd[5]));
++	info_or_seq_printf(s, "FBCZ = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_FBCZ, txd[5]));
++	info_or_seq_printf(s, "BYPASS_RBB = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_BYPASS_RBB, txd[5]));
++
++	info_or_seq_printf(s, "FL = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_FL, txd[5]));
++	info_or_seq_printf(s, "PN_47_32 = 0x%x\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_PN_47_32_, txd[5]));
++
++	/* dw6 */
++	info_or_seq_printf(s, "AMSDU_CAP_UTXB = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_AMSDU_CAP_UTXB, txd[6]));
++	info_or_seq_printf(s, "DAS = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_DAS, txd[6]));
++	info_or_seq_printf(s, "DIS_MAT = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_DIS_MAT, txd[6]));
++	info_or_seq_printf(s, "MSDU_COUNT = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_MSDU_COUNT, txd[6]));
++	info_or_seq_printf(s, "TIMESTAMP_OFFSET = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_TIMESTAMP_OFFSET_IDX, txd[6]));
++	info_or_seq_printf(s, "FIXED_RATE_IDX = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_FIXED_RATE_IDX, txd[6]));
++	info_or_seq_printf(s, "BW = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_BW, txd[6]));
++	info_or_seq_printf(s, "VTA = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_VTA, txd[6]));
++	info_or_seq_printf(s, "SRC = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_SRC, txd[6]));
++
++	/* dw7 */
++	info_or_seq_printf(s, "SW_TX_TIME(unit:65536ns) = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_SW_TX_TIME , txd[7]));
++	info_or_seq_printf(s, "UT = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_UT, txd[7]));
++	info_or_seq_printf(s, "CTXD_CNT = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_CTXD_CNT, txd[7]));
++	info_or_seq_printf(s, "HM = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_HM, txd[7]));
++	info_or_seq_printf(s, "DP = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_DP, txd[7]));
++	info_or_seq_printf(s, "IP = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_IP, txd[7]));
++	info_or_seq_printf(s, "TXD_LEN = %d\n",
++			GET_FIELD(WF_TX_DESCRIPTOR_TXD_LEN, txd[7]));
++
++	if (dump_txp) {
++		__le32 *txp = txd + 8;
++
++		if (is_hif_txd)
++			mt7996_dump_bmac_hif_txp_info(s, dev, txp, hif_txp_ver);
++		else
++			mt7996_dump_bmac_mac_txp_info(s, dev, txp);
++	}
++}
++
++static void
++mt7996_dump_mac_fid(struct seq_file *s, struct mt7996_dev *dev, u32 fid, bool is_ple)
++{
++#define PLE_MEM_SIZE	 128
++#define PSE_MEM_SIZE	 256
++	 u8 data[PSE_MEM_SIZE] = {0};
++	 u32 addr = 0;
++	 int i = 0, cr_cnt = PSE_MEM_SIZE;
++	 u32 *ptr = (u32 *) data;
++
++	 if (is_ple) {
++		cr_cnt = PLE_MEM_SIZE;
++		seq_printf(s, "dump ple: fid = 0x%08x\n", fid);
++	 } else {
++		seq_printf(s, "dump pse: fid = 0x%08x\n", fid);
++	 }
++
++	 for (i = 0; i < cr_cnt; i = i + 4) {
++		if (is_ple)
++			addr = (0xa << 28 | fid << 15) + i;
++		else
++			addr = (0xb << 28 | fid << 15) + i;
++		*ptr = mt76_rr(dev, addr);
++		ptr++;
++	 }
++
++	 seq_printf(s, "raw data: size=%d\n", cr_cnt);
++
++	 seq_hex_dump(s, "", DUMP_PREFIX_OFFSET, 16, 1, (u8 *)data, cr_cnt, false);
++	 /* dump one txd info */
++	 if (is_ple) {
++		 dev->dbg.txd_read_cnt = 1;
++		 mt7996_dump_bmac_txd_info(s, dev, (__le32 *)&data[0], false, true);
++	 }
++}
++
++static int
++mt7996_ple_fid_read(struct seq_file *s, void *data) {
++	 struct mt7996_dev *dev = dev_get_drvdata(s->private);
++
++	 mt7996_dump_mac_fid(s, dev, dev->dbg.fid_idx, true);
++	 return 0;
++}
++
++static int
++mt7996_pse_fid_read(struct seq_file *s, void *data) {
++	 struct mt7996_dev *dev = dev_get_drvdata(s->private);
++
++	 mt7996_dump_mac_fid(s, dev, dev->dbg.fid_idx, false);
++	 return 0;
++}
++
++void mt7996_dump_bmac_rxd_info(struct mt7996_dev *dev, __le32 *rxd)
++{
++	/* dump stop */
++	if (!dev->dbg.rxd_read_cnt)
++		return;
++
++	/* force dump */
++	if (dev->dbg.rxd_read_cnt > 8)
++		dev->dbg.rxd_read_cnt = 8;
++
++	/* dump txd_read_cnt times */
++	if (dev->dbg.rxd_read_cnt != 8)
++		dev->dbg.rxd_read_cnt--;
++
++	printk("rxd raw data: size=%d\n", MT_TXD_SIZE);
++	print_hex_dump(KERN_ERR , "", DUMP_PREFIX_OFFSET, 16, 1, (u8 *)rxd, 96, false);
++
++	printk("BMAC_RXD Fields:\n");
++
++	/* group0 */
++	/* dw0 */
++	printk("RX_BYTE_COUNT = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_RX_BYTE_COUNT, le32_to_cpu(rxd[0])));
++	printk("PACKET_TYPE = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_PACKET_TYPE, le32_to_cpu(rxd[0])));
++
++	/* dw1 */
++	printk("MLD_ID = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_MLD_ID, le32_to_cpu(rxd[1])));
++	printk("GROUP_VLD = 0x%x%s%s%s%s%s\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_GROUP_VLD, le32_to_cpu(rxd[1])),
++			GET_FIELD(WF_RX_DESCRIPTOR_GROUP_VLD, le32_to_cpu(rxd[1]))
++			& BMAC_GROUP_VLD_1 ? "[group1]" : "",
++			GET_FIELD(WF_RX_DESCRIPTOR_GROUP_VLD, le32_to_cpu(rxd[1]))
++			& BMAC_GROUP_VLD_2 ? "[group2]" : "",
++			GET_FIELD(WF_RX_DESCRIPTOR_GROUP_VLD, le32_to_cpu(rxd[1]))
++			& BMAC_GROUP_VLD_3 ? "[group3]" : "",
++			GET_FIELD(WF_RX_DESCRIPTOR_GROUP_VLD, le32_to_cpu(rxd[1]))
++			& BMAC_GROUP_VLD_4 ? "[group4]" : "",
++			GET_FIELD(WF_RX_DESCRIPTOR_GROUP_VLD, le32_to_cpu(rxd[1]))
++			& BMAC_GROUP_VLD_5 ? "[group5]" : "");
++	printk("KID = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_KID, le32_to_cpu(rxd[1])));
++	printk("CM = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_CM, le32_to_cpu(rxd[1])));
++	printk("CLM = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_CLM, le32_to_cpu(rxd[1])));
++	printk("I = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_I, le32_to_cpu(rxd[1])));
++	printk("T = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_T, le32_to_cpu(rxd[1])));
++	printk("BN = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_BN, le32_to_cpu(rxd[1])));
++	printk("BIPN_FAIL = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_BIPN_FAIL, le32_to_cpu(rxd[1])));
++
++	/* dw2 */
++	printk("BSSID = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_BSSID, le32_to_cpu(rxd[2])));
++	printk("H = %d%s\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_H, le32_to_cpu(rxd[2])),
++			GET_FIELD(WF_RX_DESCRIPTOR_H, le32_to_cpu(rxd[2])) == 0 ?
++			"802.11 frame" : "eth/802.3 frame");
++	printk("HEADER_LENGTH(word) = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_HEADER_LENGTH, le32_to_cpu(rxd[2])));
++	printk("HO(word) = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_HO, le32_to_cpu(rxd[2])));
++	printk("SEC_MODE = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_SEC_MODE, le32_to_cpu(rxd[2])));
++	printk("MUBAR = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_MUBAR, le32_to_cpu(rxd[2])));
++	printk("SWBIT = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_SWBIT, le32_to_cpu(rxd[2])));
++	printk("DAF = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_DAF, le32_to_cpu(rxd[2])));
++	printk("EL = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_EL, le32_to_cpu(rxd[2])));
++	printk("HTF = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_HTF, le32_to_cpu(rxd[2])));
++	printk("INTF = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_INTF, le32_to_cpu(rxd[2])));
++	printk("FRAG = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_FRAG, le32_to_cpu(rxd[2])));
++	printk("NUL = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_NUL, le32_to_cpu(rxd[2])));
++	printk("NDATA = %d%s\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_NDATA, le32_to_cpu(rxd[2])),
++			GET_FIELD(WF_RX_DESCRIPTOR_NDATA, le32_to_cpu(rxd[2])) == 0 ?
++			"[data frame]" : "[mgmt/ctl frame]");
++	printk("NAMP = %d%s\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_NAMP, le32_to_cpu(rxd[2])),
++			GET_FIELD(WF_RX_DESCRIPTOR_NAMP, le32_to_cpu(rxd[2])) == 0 ?
++			"[ampdu frame]" : "[mpdu frame]");
++	printk("BF_RPT = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_BF_RPT, le32_to_cpu(rxd[2])));
++
++	/* dw3 */
++	printk("RXV_SN = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_RXV_SN, le32_to_cpu(rxd[3])));
++	printk("CH_FREQUENCY = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_CH_FREQUENCY, le32_to_cpu(rxd[3])));
++	printk("A1_TYPE = %d%s%s%s%s\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_A1_TYPE, le32_to_cpu(rxd[3])),
++			GET_FIELD(WF_RX_DESCRIPTOR_A1_TYPE, le32_to_cpu(rxd[3])) == 0 ?
++			"[reserved]" : "",
++			GET_FIELD(WF_RX_DESCRIPTOR_A1_TYPE, le32_to_cpu(rxd[3])) == 1 ?
++			"[uc2me]" : "",
++			GET_FIELD(WF_RX_DESCRIPTOR_A1_TYPE, le32_to_cpu(rxd[3])) == 2 ?
++			"[mc]" : "",
++			GET_FIELD(WF_RX_DESCRIPTOR_A1_TYPE, le32_to_cpu(rxd[3])) == 3 ?
++			"[bc]" : "");
++	printk("HTC = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_HTC, le32_to_cpu(rxd[3])));
++	printk("TCL = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_TCL, le32_to_cpu(rxd[3])));
++	printk("BBM = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_BBM, le32_to_cpu(rxd[3])));
++	printk("BU = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_BU, le32_to_cpu(rxd[3])));
++	printk("CO_ANT = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_CO_ANT, le32_to_cpu(rxd[3])));
++	printk("BF_CQI = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_BF_CQI, le32_to_cpu(rxd[3])));
++	printk("FC = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_FC, le32_to_cpu(rxd[3])));
++	printk("VLAN = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_VLAN, le32_to_cpu(rxd[3])));
++
++	/* dw4 */
++	printk("PF = %d%s%s%s%s\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_PF, le32_to_cpu(rxd[4])),
++			GET_FIELD(WF_RX_DESCRIPTOR_PF, le32_to_cpu(rxd[4])) == 0 ?
++			"[msdu]" : "",
++			GET_FIELD(WF_RX_DESCRIPTOR_PF, le32_to_cpu(rxd[4])) == 1 ?
++			"[final amsdu]" : "",
++			GET_FIELD(WF_RX_DESCRIPTOR_PF, le32_to_cpu(rxd[4])) == 2 ?
++			"[middle amsdu]" : "",
++			GET_FIELD(WF_RX_DESCRIPTOR_PF, le32_to_cpu(rxd[4])) == 3 ?
++			"[first amsdu]" : "");
++	printk("MAC = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_MAC, le32_to_cpu(rxd[4])));
++	printk("TID = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_TID, le32_to_cpu(rxd[4])));
++	printk("ETHER_TYPE_OFFSET = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_ETHER_TYPE_OFFSET, le32_to_cpu(rxd[4])));
++	printk("IP = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_IP, le32_to_cpu(rxd[4])));
++	printk("UT = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_UT, le32_to_cpu(rxd[4])));
++	printk("PSE_FID = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_PSE_FID, le32_to_cpu(rxd[4])));
++
++	/* group4 */
++	/* dw0 */
++	printk("FRAME_CONTROL_FIELD = 0x%x\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_GROUP_VLD, le32_to_cpu(rxd[1]))
++			& BMAC_GROUP_VLD_4 ?
++			GET_FIELD(WF_RX_DESCRIPTOR_FRAME_CONTROL_FIELD, le32_to_cpu(rxd[8])) : 0);
++	printk("PEER_MLD_ADDRESS_15_0 = 0x%x\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_GROUP_VLD, le32_to_cpu(rxd[1]))
++			& BMAC_GROUP_VLD_4 ?
++			GET_FIELD(WF_RX_DESCRIPTOR_PEER_MLD_ADDRESS_15_0_,
++			le32_to_cpu(rxd[8])) : 0);
++
++	/* dw1 */
++	printk("PEER_MLD_ADDRESS_47_16 = 0x%x\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_GROUP_VLD, le32_to_cpu(rxd[1]))
++			& BMAC_GROUP_VLD_4 ?
++			GET_FIELD(WF_RX_DESCRIPTOR_PEER_MLD_ADDRESS_47_16_,
++			le32_to_cpu(rxd[9])) : 0);
++
++	/* dw2 */
++	printk("FRAGMENT_NUMBER = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_GROUP_VLD, le32_to_cpu(rxd[1]))
++			& BMAC_GROUP_VLD_4 ?
++			GET_FIELD(WF_RX_DESCRIPTOR_FRAGMENT_NUMBER,
++			le32_to_cpu(rxd[10])) : 0);
++	printk("SEQUENCE_NUMBER = %d\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_GROUP_VLD, le32_to_cpu(rxd[1]))
++			& BMAC_GROUP_VLD_4 ?
++			GET_FIELD(WF_RX_DESCRIPTOR_SEQUENCE_NUMBER,
++			le32_to_cpu(rxd[10])) : 0);
++	printk("QOS_CONTROL_FIELD = 0x%x\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_GROUP_VLD, le32_to_cpu(rxd[1]))
++			& BMAC_GROUP_VLD_4 ?
++			GET_FIELD(WF_RX_DESCRIPTOR_QOS_CONTROL_FIELD,
++			le32_to_cpu(rxd[10])) : 0);
++
++	/* dw3 */
++	printk("HT_CONTROL_FIELD = 0x%x\n",
++			GET_FIELD(WF_RX_DESCRIPTOR_GROUP_VLD, le32_to_cpu(rxd[1]))
++			& BMAC_GROUP_VLD_4 ?
++			GET_FIELD(WF_RX_DESCRIPTOR_HT_CONTROL_FIELD,
++			le32_to_cpu(rxd[11])) : 0);
++}
++
++static int mt7996_token_txd_read(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	struct mt76_txwi_cache *t;
++	u8* txwi;
++
++	seq_printf(s, "\n");
++	spin_lock_bh(&dev->mt76.token_lock);
++
++	t = idr_find(&dev->mt76.token, dev->dbg.token_idx);
++	if (t != NULL) {
++		struct mt76_dev *mdev = &dev->mt76;
++		txwi = ((u8*)(t)) - (mdev->drv->txwi_size);
++		/* dump one txd info */
++		dev->dbg.txd_read_cnt = 1;
++		mt7996_dump_bmac_txd_info(s, dev, (__le32 *)txwi, true, true);
++		seq_printf(s, "\n");
++		seq_printf(s, "[SKB]\n");
++		seq_hex_dump(s, "", DUMP_PREFIX_OFFSET, 16, 1, (u8 *)t->skb->data, t->skb->len, false);
++		seq_printf(s, "\n");
++	}
++	spin_unlock_bh(&dev->mt76.token_lock);
++	return 0;
++}
++
++static int mt7996_rx_token_read(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	int id, count = 0;
++	struct mt76_rxwi_cache *r;
++
++	seq_printf(s, "Rx cut through token:\n");
++	spin_lock_bh(&dev->mt76.rx_token_lock);
++	idr_for_each_entry(&dev->mt76.rx_token, r, id) {
++		count++;
++	}
++	seq_printf(s, "\ttotal:%8d used:%8d\n",
++		   dev->mt76.rx_token_size, count);
++	spin_unlock_bh(&dev->mt76.rx_token_lock);
++
++	return 0;
++}
++
++static int mt7996_rx_msdu_pg_read(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	struct list_head *p;
++	int i, count = 0, total = 0;
++
++	seq_printf(s, "Rx Msdu page:\n");
++	spin_lock(&dev->wed_rro.lock);
++	for (i = 0; i < MT7996_RRO_MSDU_PG_HASH_SIZE; i++) {
++		list_for_each(p, &dev->wed_rro.pg_hash_head[i]) {
++			count++;
++		}
++	}
++
++	total = count;
++	list_for_each(p, &dev->wed_rro.pg_addr_cache) {
++		total++;
++	}
++	seq_printf(s, "\ttotal:%8d used:%8d\n", total, count);
++	spin_unlock(&dev->wed_rro.lock);
++
++	return 0;
++}
++
++/* AMSDU SETTING */
++static ssize_t mt7996_amsdu_algo_write(struct file *file,
++				   const char __user *user_buf,
++				   size_t count,
++				   loff_t *ppos)
++{
++	struct mt7996_dev *dev = file->private_data;
++	char buf[100];
++	int ret;
++	struct {
++		u8 _rsv[4];
++
++		u16 tag;
++		u16 len;
++
++		u16 wlan_idx;
++		u8 algo_en;
++		u8 rsv[1];
++	} __packed data = {
++		.tag = cpu_to_le16(UNI_MEC_AMSDU_ALGO_EN_STA),
++		.len = cpu_to_le16(sizeof(data) - 4),
++	};
++
++	if (count >= sizeof(buf))
++		return -EINVAL;
++
++	if (copy_from_user(buf, user_buf, count))
++		return -EFAULT;
++
++	if (count && buf[count - 1] == '\n')
++		buf[count - 1] = '\0';
++	else
++		buf[count] = '\0';
++
++	if (sscanf(buf, "%hu %hhu", &data.wlan_idx, &data.algo_en) != 2)
++		return -EINVAL;
++
++	if (data.wlan_idx >= mt7996_wtbl_size(dev))
++		return -EINVAL;
++
++	ret = mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(MEC), &data,
++				sizeof(data), true);
++	if (ret)
++		return -EINVAL;
++
++	return count;
++}
++static const struct file_operations fops_amsdu_algo = {
++	.write = mt7996_amsdu_algo_write,
++	.read = NULL,
++	.open = simple_open,
++	.llseek = default_llseek,
++};
++
++static ssize_t mt7996_amsdu_para_write(struct file *file,
++				   const char __user *user_buf,
++				   size_t count,
++				   loff_t *ppos)
++{
++	struct mt7996_dev *dev = file->private_data;
++	char buf[100];
++	int ret;
++	struct {
++		u8 _rsv[4];
++
++		u16 tag;
++		u16 len;
++
++		u16 wlan_idx;
++		u8  amsdu_en;
++		u8  num;
++		u16 lenth;
++		u8  rsv[2];
++	} __packed data = {
++		.tag = cpu_to_le16(UNI_MEC_AMSDU_PARA_STA),
++		.len = cpu_to_le16(sizeof(data) - 4),
++	};
++
++	if (count >= sizeof(buf))
++		return -EINVAL;
++
++	if (copy_from_user(buf, user_buf, count))
++		return -EFAULT;
++
++	if (count && buf[count - 1] == '\n')
++		buf[count - 1] = '\0';
++	else
++		buf[count] = '\0';
++
++	if (sscanf(buf, "%hu %hhu %hhu %hu", &data.wlan_idx, &data.amsdu_en, &data.num, &data.lenth) != 4)
++		return -EINVAL;
++
++	if (data.wlan_idx >= mt7996_wtbl_size(dev))
++		return -EINVAL;
++
++	ret = mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(MEC), &data,
++			  sizeof(data), true);
++	if (ret)
++		return -EINVAL;
++
++	return count;
++}
++static const struct file_operations fops_amsdu_para = {
++	.write = mt7996_amsdu_para_write,
++	.read = NULL,
++	.open = simple_open,
++	.llseek = default_llseek,
++};
++
++static int mt7996_amsdu_info_read(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	u32 amsdu_cnt[WF_PLE_TOP_AMSDU_PACK_NUM] = {0}, total_cnt;
++	u8 i;
++
++	seq_printf(s, "HW A-MSDU Information:\n");
++
++	for (total_cnt = 0, i = 0; i < WF_PLE_TOP_AMSDU_PACK_NUM; ++i) {
++		amsdu_cnt[i] = mt76_rr(dev, WF_PLE_TOP_AMSDU_PACK_1_MSDU_CNT_ADDR + i * 4);
++		total_cnt += amsdu_cnt[i];
++	}
++
++	for (i = 0; i < WF_PLE_TOP_AMSDU_PACK_NUM; ++i) {
++		seq_printf(s, "# of HW A-MSDU containing %hhu MSDU: 0x%x",
++		           i + 1, amsdu_cnt[i]);
++		seq_printf(s, "\t(%u.%u%%)\n",
++		           total_cnt ? amsdu_cnt[i] * 1000 / total_cnt / 10 : 0,
++		           total_cnt ? amsdu_cnt[i] * 1000 / total_cnt % 10 : 0);
++	}
++
++	return 0;
++}
++
++/* PSE INFO */
++static struct bmac_queue_info_t pse_queue_empty_info[] = {
++	{"CPU Q0",  ENUM_UMAC_CPU_PORT_1,     ENUM_UMAC_CTX_Q_0},
++	{"CPU Q1",  ENUM_UMAC_CPU_PORT_1,     ENUM_UMAC_CTX_Q_1},
++	{"CPU Q2",  ENUM_UMAC_CPU_PORT_1,     ENUM_UMAC_CTX_Q_2},
++	{"CPU Q3",  ENUM_UMAC_CPU_PORT_1,     ENUM_UMAC_CTX_Q_3},
++	{NULL, 0, 0}, {NULL, 0, 0}, {NULL, 0, 0}, {NULL, 0, 0}, /* 4~7 not defined */
++	{NULL, 0, 0}, {NULL, 0, 0}, {NULL, 0, 0}, {NULL, 0, 0}, {NULL, 0, 0}, {NULL, 0, 0},
++	{NULL, 0, 0}, {NULL, 0, 0},  /* 14~15 not defined */
++	{"LMAC Q",  ENUM_UMAC_LMAC_PORT_2,    0},
++	{"MDP TX Q0", ENUM_UMAC_LMAC_PORT_2, 1},
++	{"MDP RX Q", ENUM_UMAC_LMAC_PORT_2, 2},
++	{"SEC TX Q0", ENUM_UMAC_LMAC_PORT_2, 3},
++	{"SEC RX Q", ENUM_UMAC_LMAC_PORT_2, 4},
++	{"SFD_PARK Q", ENUM_UMAC_LMAC_PORT_2, 5},
++	{"MDP_TXIOC Q0", ENUM_UMAC_LMAC_PORT_2, 6},
++	{"MDP_RXIOC Q0", ENUM_UMAC_LMAC_PORT_2, 7},
++	{"MDP TX Q1", ENUM_UMAC_LMAC_PORT_2, 0x11},
++	{"SEC TX Q1", ENUM_UMAC_LMAC_PORT_2, 0x13},
++	{"MDP_TXIOC Q1", ENUM_UMAC_LMAC_PORT_2, 0x16},
++	{"MDP_RXIOC Q1", ENUM_UMAC_LMAC_PORT_2, 0x17},
++	{"CPU Q3",  ENUM_UMAC_CPU_PORT_1,     4},
++	{NULL, 0, 0}, {NULL, 0, 0},
++	{"RLS Q",  ENUM_PLE_CTRL_PSE_PORT_3, ENUM_UMAC_PLE_CTRL_P3_Q_0X1F}
++};
++
++static struct bmac_queue_info_t pse_queue_empty2_info[] = {
++	{"MDP_TDPIOC Q0", ENUM_UMAC_LMAC_PORT_2, 0x8},
++	{"MDP_RDPIOC Q0", ENUM_UMAC_LMAC_PORT_2, 0x9},
++	{"MDP_TDPIOC Q1", ENUM_UMAC_LMAC_PORT_2, 0x18},
++	{"MDP_RDPIOC Q1", ENUM_UMAC_LMAC_PORT_2, 0x19},
++	{"MDP_TDPIOC Q2", ENUM_UMAC_LMAC_PORT_2, 0x28},
++	{"MDP_RDPIOC Q2", ENUM_UMAC_LMAC_PORT_2, 0x29},
++	{NULL, 0, 0},
++	{"MDP_RDPIOC Q3", ENUM_UMAC_LMAC_PORT_2, 0x39},
++	{"MDP TX Q2", ENUM_UMAC_LMAC_PORT_2, 0x21},
++	{"SEC TX Q2", ENUM_UMAC_LMAC_PORT_2, 0x23},
++	{"MDP_TXIOC Q2", ENUM_UMAC_LMAC_PORT_2, 0x26},
++	{"MDP_RXIOC Q2", ENUM_UMAC_LMAC_PORT_2, 0x27},
++	{NULL, 0, 0}, {NULL, 0, 0}, {NULL, 0, 0},
++	{"MDP_RXIOC Q3", ENUM_UMAC_LMAC_PORT_2, 0x37},
++	{"HIF Q0", ENUM_UMAC_HIF_PORT_0,    0},
++	{"HIF Q1", ENUM_UMAC_HIF_PORT_0,    1},
++	{"HIF Q2", ENUM_UMAC_HIF_PORT_0,    2},
++	{"HIF Q3", ENUM_UMAC_HIF_PORT_0,    3},
++	{"HIF Q4", ENUM_UMAC_HIF_PORT_0,    4},
++	{"HIF Q5", ENUM_UMAC_HIF_PORT_0,    5},
++	{"HIF Q6", ENUM_UMAC_HIF_PORT_0,    6},
++	{"HIF Q7", ENUM_UMAC_HIF_PORT_0,    7},
++	{"HIF Q8", ENUM_UMAC_HIF_PORT_0,    8},
++	{"HIF Q9", ENUM_UMAC_HIF_PORT_0,    9},
++	{"HIF Q10", ENUM_UMAC_HIF_PORT_0,    10},
++	{"HIF Q11", ENUM_UMAC_HIF_PORT_0,    11},
++	{"HIF Q12", ENUM_UMAC_HIF_PORT_0,    12},
++	{"HIF Q13", ENUM_UMAC_HIF_PORT_0,    13},
++	{NULL, 0, 0}, {NULL, 0, 0}
++};
++
++static int
++mt7996_pseinfo_read(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	u32 pse_buf_ctrl, pg_sz, pg_num;
++	u32 pse_stat[2], pg_flow_ctrl[28] = {0};
++	u32 fpg_cnt, ffa_cnt, fpg_head, fpg_tail;
++	u32 max_q, min_q, rsv_pg, used_pg;
++	int i;
++
++	pse_buf_ctrl = mt76_rr(dev, WF_PSE_TOP_PBUF_CTRL_ADDR);
++	pse_stat[0] = mt76_rr(dev, WF_PSE_TOP_QUEUE_EMPTY_ADDR);
++	pse_stat[1] = mt76_rr(dev, WF_PSE_TOP_QUEUE_EMPTY_1_ADDR);
++	pg_flow_ctrl[0] = mt76_rr(dev, WF_PSE_TOP_FREEPG_CNT_ADDR);
++	pg_flow_ctrl[1] = mt76_rr(dev, WF_PSE_TOP_FREEPG_HEAD_TAIL_ADDR);
++	pg_flow_ctrl[2] = mt76_rr(dev, WF_PSE_TOP_PG_HIF0_GROUP_ADDR);
++	pg_flow_ctrl[3] = mt76_rr(dev, WF_PSE_TOP_HIF0_PG_INFO_ADDR);
++	pg_flow_ctrl[4] = mt76_rr(dev, WF_PSE_TOP_PG_HIF1_GROUP_ADDR);
++	pg_flow_ctrl[5] = mt76_rr(dev, WF_PSE_TOP_HIF1_PG_INFO_ADDR);
++	pg_flow_ctrl[6] = mt76_rr(dev, WF_PSE_TOP_PG_CPU_GROUP_ADDR);
++	pg_flow_ctrl[7] = mt76_rr(dev, WF_PSE_TOP_CPU_PG_INFO_ADDR);
++	pg_flow_ctrl[8] = mt76_rr(dev, WF_PSE_TOP_PG_LMAC0_GROUP_ADDR);
++	pg_flow_ctrl[9] = mt76_rr(dev, WF_PSE_TOP_LMAC0_PG_INFO_ADDR);
++	pg_flow_ctrl[10] = mt76_rr(dev, WF_PSE_TOP_PG_LMAC1_GROUP_ADDR);
++	pg_flow_ctrl[11] = mt76_rr(dev, WF_PSE_TOP_LMAC1_PG_INFO_ADDR);
++	pg_flow_ctrl[12] = mt76_rr(dev, WF_PSE_TOP_PG_LMAC2_GROUP_ADDR);
++	pg_flow_ctrl[13] = mt76_rr(dev, WF_PSE_TOP_LMAC2_PG_INFO_ADDR);
++	pg_flow_ctrl[14] = mt76_rr(dev, WF_PSE_TOP_PG_PLE_GROUP_ADDR);
++	pg_flow_ctrl[15] = mt76_rr(dev, WF_PSE_TOP_PLE_PG_INFO_ADDR);
++	pg_flow_ctrl[16] = mt76_rr(dev, WF_PSE_TOP_PG_LMAC3_GROUP_ADDR);
++	pg_flow_ctrl[17] = mt76_rr(dev, WF_PSE_TOP_LMAC3_PG_INFO_ADDR);
++	pg_flow_ctrl[18] = mt76_rr(dev, WF_PSE_TOP_PG_MDP_GROUP_ADDR);
++	pg_flow_ctrl[19] = mt76_rr(dev, WF_PSE_TOP_MDP_PG_INFO_ADDR);
++	pg_flow_ctrl[20] = mt76_rr(dev, WF_PSE_TOP_PG_PLE1_GROUP_ADDR);
++	pg_flow_ctrl[21] = mt76_rr(dev, WF_PSE_TOP_PLE1_PG_INFO_ADDR);
++	pg_flow_ctrl[22] = mt76_rr(dev, WF_PSE_TOP_PG_MDP2_GROUP_ADDR);
++	pg_flow_ctrl[23] = mt76_rr(dev, WF_PSE_TOP_MDP2_PG_INFO_ADDR);
++	if (mt7996_band_valid(dev, MT_BAND2)) {
++		pg_flow_ctrl[24] = mt76_rr(dev, WF_PSE_TOP_PG_MDP3_GROUP_ADDR);
++		pg_flow_ctrl[25] = mt76_rr(dev, WF_PSE_TOP_MDP3_PG_INFO_ADDR);
++	}
++	pg_flow_ctrl[26] = mt76_rr(dev, WF_PSE_TOP_PG_HIF2_GROUP_ADDR);
++	pg_flow_ctrl[27] = mt76_rr(dev, WF_PSE_TOP_HIF2_PG_INFO_ADDR);
++	/* Configuration Info */
++	seq_printf(s, "PSE Configuration Info:\n");
++	seq_printf(s, "\tPacket Buffer Control: 0x%08x\n", pse_buf_ctrl);
++	pg_sz = (pse_buf_ctrl & WF_PSE_TOP_PBUF_CTRL_PAGE_SIZE_CFG_MASK) >> WF_PSE_TOP_PBUF_CTRL_PAGE_SIZE_CFG_SHFT;
++	seq_printf(s, "\t\tPage Size=%d(%d bytes per page)\n", pg_sz, (pg_sz == 1 ? 256 : 128));
++	seq_printf(s, "\t\tPage Offset=%d(in unit of 64KB)\n",
++			 (pse_buf_ctrl & WF_PSE_TOP_PBUF_CTRL_PBUF_OFFSET_MASK) >> WF_PSE_TOP_PBUF_CTRL_PBUF_OFFSET_SHFT);
++	pg_num = (pse_buf_ctrl & WF_PSE_TOP_PBUF_CTRL_TOTAL_PAGE_NUM_MASK) >> WF_PSE_TOP_PBUF_CTRL_TOTAL_PAGE_NUM_SHFT;
++	seq_printf(s, "\t\tTotal page numbers=%d pages\n", pg_num);
++	/* Page Flow Control */
++	seq_printf(s, "PSE Page Flow Control:\n");
++	seq_printf(s, "\tFree page counter: 0x%08x\n", pg_flow_ctrl[0]);
++	fpg_cnt = (pg_flow_ctrl[0] & WF_PSE_TOP_FREEPG_CNT_FREEPG_CNT_MASK) >> WF_PSE_TOP_FREEPG_CNT_FREEPG_CNT_SHFT;
++	seq_printf(s, "\t\tThe toal page number of free=0x%03x\n", fpg_cnt);
++	ffa_cnt = (pg_flow_ctrl[0] & WF_PSE_TOP_FREEPG_CNT_FFA_CNT_MASK) >> WF_PSE_TOP_FREEPG_CNT_FFA_CNT_SHFT;
++	seq_printf(s, "\t\tThe free page numbers of free for all=0x%03x\n", ffa_cnt);
++	seq_printf(s, "\tFree page head and tail: 0x%08x\n", pg_flow_ctrl[1]);
++	fpg_head = (pg_flow_ctrl[1] & WF_PSE_TOP_FREEPG_HEAD_TAIL_FREEPG_HEAD_MASK) >> WF_PSE_TOP_FREEPG_HEAD_TAIL_FREEPG_HEAD_SHFT;
++	fpg_tail = (pg_flow_ctrl[1] & WF_PSE_TOP_FREEPG_HEAD_TAIL_FREEPG_TAIL_MASK) >> WF_PSE_TOP_FREEPG_HEAD_TAIL_FREEPG_TAIL_SHFT;
++	seq_printf(s, "\t\tThe tail/head page of free page list=0x%03x/0x%03x\n", fpg_tail, fpg_head);
++	seq_printf(s, "\tReserved page counter of HIF0 group: 0x%08x\n", pg_flow_ctrl[2]);
++	seq_printf(s, "\tHIF0 group page status: 0x%08x\n", pg_flow_ctrl[3]);
++	min_q = (pg_flow_ctrl[2] & WF_PSE_TOP_PG_HIF0_GROUP_HIF0_MIN_QUOTA_MASK) >> WF_PSE_TOP_PG_HIF0_GROUP_HIF0_MIN_QUOTA_SHFT;
++	max_q = (pg_flow_ctrl[2] & WF_PSE_TOP_PG_HIF0_GROUP_HIF0_MAX_QUOTA_MASK) >> WF_PSE_TOP_PG_HIF0_GROUP_HIF0_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of HIF0 group=0x%03x/0x%03x\n", max_q, min_q);
++	rsv_pg = (pg_flow_ctrl[3] & WF_PSE_TOP_HIF0_PG_INFO_HIF0_RSV_CNT_MASK) >> WF_PSE_TOP_HIF0_PG_INFO_HIF0_RSV_CNT_SHFT;
++	used_pg = (pg_flow_ctrl[3] & WF_PSE_TOP_HIF0_PG_INFO_HIF0_SRC_CNT_MASK) >> WF_PSE_TOP_HIF0_PG_INFO_HIF0_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of HIF0 group=0x%03x/0x%03x\n", used_pg, rsv_pg);
++	seq_printf(s, "\tReserved page counter of HIF1 group: 0x%08x\n", pg_flow_ctrl[4]);
++	seq_printf(s, "\tHIF1 group page status: 0x%08x\n", pg_flow_ctrl[5]);
++	min_q = (pg_flow_ctrl[4] & WF_PSE_TOP_PG_HIF1_GROUP_HIF1_MIN_QUOTA_MASK) >> WF_PSE_TOP_PG_HIF1_GROUP_HIF1_MIN_QUOTA_SHFT;
++	max_q = (pg_flow_ctrl[4] & WF_PSE_TOP_PG_HIF1_GROUP_HIF1_MAX_QUOTA_MASK) >> WF_PSE_TOP_PG_HIF1_GROUP_HIF1_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of HIF1 group=0x%03x/0x%03x\n", max_q, min_q);
++	rsv_pg = (pg_flow_ctrl[5] & WF_PSE_TOP_HIF1_PG_INFO_HIF1_RSV_CNT_MASK) >> WF_PSE_TOP_HIF1_PG_INFO_HIF1_RSV_CNT_SHFT;
++	used_pg = (pg_flow_ctrl[5] & WF_PSE_TOP_HIF1_PG_INFO_HIF1_SRC_CNT_MASK) >> WF_PSE_TOP_HIF1_PG_INFO_HIF1_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of HIF1 group=0x%03x/0x%03x\n", used_pg, rsv_pg);
++	seq_printf(s, "\tReserved page counter of HIF2 group: 0x%08x\n", pg_flow_ctrl[26]);
++	seq_printf(s, "\tHIF2 group page status: 0x%08x\n", pg_flow_ctrl[27]);
++	min_q = (pg_flow_ctrl[26] & WF_PSE_TOP_PG_HIF2_GROUP_HIF2_MIN_QUOTA_MASK) >> WF_PSE_TOP_PG_HIF2_GROUP_HIF2_MIN_QUOTA_SHFT;
++	max_q = (pg_flow_ctrl[26] & WF_PSE_TOP_PG_HIF2_GROUP_HIF2_MAX_QUOTA_MASK) >> WF_PSE_TOP_PG_HIF2_GROUP_HIF2_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of HIF2 group=0x%03x/0x%03x\n", max_q, min_q);
++	rsv_pg = (pg_flow_ctrl[27] & WF_PSE_TOP_HIF2_PG_INFO_HIF2_RSV_CNT_MASK) >> WF_PSE_TOP_HIF2_PG_INFO_HIF2_RSV_CNT_SHFT;
++	used_pg = (pg_flow_ctrl[27] & WF_PSE_TOP_HIF2_PG_INFO_HIF2_SRC_CNT_MASK) >> WF_PSE_TOP_HIF2_PG_INFO_HIF2_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of HIF2 group=0x%03x/0x%03x\n", used_pg, rsv_pg);
++	seq_printf(s, "\tReserved page counter of CPU group: 0x%08x\n", pg_flow_ctrl[6]);
++	seq_printf(s, "\tCPU group page status: 0x%08x\n", pg_flow_ctrl[7]);
++	min_q = (pg_flow_ctrl[6] & WF_PSE_TOP_PG_CPU_GROUP_CPU_MIN_QUOTA_MASK) >> WF_PSE_TOP_PG_CPU_GROUP_CPU_MIN_QUOTA_SHFT;
++	max_q = (pg_flow_ctrl[6] & WF_PSE_TOP_PG_CPU_GROUP_CPU_MAX_QUOTA_MASK) >> WF_PSE_TOP_PG_CPU_GROUP_CPU_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of CPU group=0x%03x/0x%03x\n", max_q, min_q);
++	rsv_pg = (pg_flow_ctrl[7] & WF_PSE_TOP_CPU_PG_INFO_CPU_RSV_CNT_MASK) >> WF_PSE_TOP_CPU_PG_INFO_CPU_RSV_CNT_SHFT;
++	used_pg = (pg_flow_ctrl[7] & WF_PSE_TOP_CPU_PG_INFO_CPU_SRC_CNT_MASK) >> WF_PSE_TOP_CPU_PG_INFO_CPU_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of CPU group=0x%03x/0x%03x\n", used_pg, rsv_pg);
++	seq_printf(s, "\tReserved page counter of LMAC0 group: 0x%08x\n", pg_flow_ctrl[8]);
++	seq_printf(s, "\tLMAC0 group page status: 0x%08x\n", pg_flow_ctrl[9]);
++	min_q = (pg_flow_ctrl[8] & WF_PSE_TOP_PG_LMAC0_GROUP_LMAC0_MIN_QUOTA_MASK) >> WF_PSE_TOP_PG_LMAC0_GROUP_LMAC0_MIN_QUOTA_SHFT;
++	max_q = (pg_flow_ctrl[8] & WF_PSE_TOP_PG_LMAC0_GROUP_LMAC0_MAX_QUOTA_MASK) >> WF_PSE_TOP_PG_LMAC0_GROUP_LMAC0_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of LMAC0 group=0x%03x/0x%03x\n", max_q, min_q);
++	rsv_pg = (pg_flow_ctrl[9] & WF_PSE_TOP_LMAC0_PG_INFO_LMAC0_RSV_CNT_MASK) >> WF_PSE_TOP_LMAC0_PG_INFO_LMAC0_RSV_CNT_SHFT;
++	used_pg = (pg_flow_ctrl[9] & WF_PSE_TOP_LMAC0_PG_INFO_LMAC0_SRC_CNT_MASK) >> WF_PSE_TOP_LMAC0_PG_INFO_LMAC0_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of LMAC0 group=0x%03x/0x%03x\n", used_pg, rsv_pg);
++	seq_printf(s, "\tReserved page counter of LMAC1 group: 0x%08x\n", pg_flow_ctrl[10]);
++	seq_printf(s, "\tLMAC1 group page status: 0x%08x\n", pg_flow_ctrl[11]);
++	min_q = (pg_flow_ctrl[10] & WF_PSE_TOP_PG_LMAC1_GROUP_LMAC1_MIN_QUOTA_MASK) >> WF_PSE_TOP_PG_LMAC1_GROUP_LMAC1_MIN_QUOTA_SHFT;
++	max_q = (pg_flow_ctrl[10] & WF_PSE_TOP_PG_LMAC1_GROUP_LMAC1_MAX_QUOTA_MASK) >> WF_PSE_TOP_PG_LMAC1_GROUP_LMAC1_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of LMAC1 group=0x%03x/0x%03x\n", max_q, min_q);
++	rsv_pg = (pg_flow_ctrl[11] & WF_PSE_TOP_LMAC1_PG_INFO_LMAC1_RSV_CNT_MASK) >> WF_PSE_TOP_LMAC1_PG_INFO_LMAC1_RSV_CNT_SHFT;
++	used_pg = (pg_flow_ctrl[11] & WF_PSE_TOP_LMAC1_PG_INFO_LMAC1_SRC_CNT_MASK) >> WF_PSE_TOP_LMAC1_PG_INFO_LMAC1_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of LMAC1 group=0x%03x/0x%03x\n", used_pg, rsv_pg);
++	seq_printf(s, "\tReserved page counter of LMAC2 group: 0x%08x\n", pg_flow_ctrl[11]);
++	seq_printf(s, "\tLMAC2 group page status: 0x%08x\n", pg_flow_ctrl[12]);
++	min_q = (pg_flow_ctrl[12] & WF_PSE_TOP_PG_LMAC2_GROUP_LMAC2_MIN_QUOTA_MASK) >> WF_PSE_TOP_PG_LMAC2_GROUP_LMAC2_MIN_QUOTA_SHFT;
++	max_q = (pg_flow_ctrl[12] & WF_PSE_TOP_PG_LMAC2_GROUP_LMAC2_MAX_QUOTA_MASK) >> WF_PSE_TOP_PG_LMAC2_GROUP_LMAC2_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of LMAC2 group=0x%03x/0x%03x\n", max_q, min_q);
++	rsv_pg = (pg_flow_ctrl[13] & WF_PSE_TOP_LMAC2_PG_INFO_LMAC2_RSV_CNT_MASK) >> WF_PSE_TOP_LMAC2_PG_INFO_LMAC2_RSV_CNT_SHFT;
++	used_pg = (pg_flow_ctrl[13] & WF_PSE_TOP_LMAC2_PG_INFO_LMAC2_SRC_CNT_MASK) >> WF_PSE_TOP_LMAC2_PG_INFO_LMAC2_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of LMAC2 group=0x%03x/0x%03x\n", used_pg, rsv_pg);
++
++	seq_printf(s, "\tReserved page counter of LMAC3 group: 0x%08x\n", pg_flow_ctrl[16]);
++	seq_printf(s, "\tLMAC3 group page status: 0x%08x\n", pg_flow_ctrl[17]);
++	min_q = (pg_flow_ctrl[16] & WF_PSE_TOP_PG_LMAC3_GROUP_LMAC3_MIN_QUOTA_MASK) >> WF_PSE_TOP_PG_LMAC3_GROUP_LMAC3_MIN_QUOTA_SHFT;
++	max_q = (pg_flow_ctrl[16] & WF_PSE_TOP_PG_LMAC3_GROUP_LMAC3_MAX_QUOTA_MASK) >> WF_PSE_TOP_PG_LMAC3_GROUP_LMAC3_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of LMAC3 group=0x%03x/0x%03x\n", max_q, min_q);
++	rsv_pg = (pg_flow_ctrl[17] & WF_PSE_TOP_LMAC3_PG_INFO_LMAC3_RSV_CNT_MASK) >> WF_PSE_TOP_LMAC3_PG_INFO_LMAC3_RSV_CNT_SHFT;
++	used_pg = (pg_flow_ctrl[17] & WF_PSE_TOP_LMAC3_PG_INFO_LMAC3_SRC_CNT_MASK) >> WF_PSE_TOP_LMAC3_PG_INFO_LMAC3_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of LMAC3 group=0x%03x/0x%03x\n", used_pg, rsv_pg);
++
++	seq_printf(s, "\tReserved page counter of PLE group: 0x%08x\n", pg_flow_ctrl[14]);
++	seq_printf(s, "\tPLE group page status: 0x%08x\n", pg_flow_ctrl[15]);
++	min_q = (pg_flow_ctrl[14] & WF_PSE_TOP_PG_PLE_GROUP_PLE_MIN_QUOTA_MASK) >> WF_PSE_TOP_PG_PLE_GROUP_PLE_MIN_QUOTA_SHFT;
++	max_q = (pg_flow_ctrl[14] & WF_PSE_TOP_PG_PLE_GROUP_PLE_MAX_QUOTA_MASK) >> WF_PSE_TOP_PG_PLE_GROUP_PLE_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of PLE group=0x%03x/0x%03x\n", max_q, min_q);
++	rsv_pg = (pg_flow_ctrl[15] & WF_PSE_TOP_PLE_PG_INFO_PLE_RSV_CNT_MASK) >> WF_PSE_TOP_PLE_PG_INFO_PLE_RSV_CNT_SHFT;
++	used_pg = (pg_flow_ctrl[15] & WF_PSE_TOP_PLE_PG_INFO_PLE_SRC_CNT_MASK) >> WF_PSE_TOP_PLE_PG_INFO_PLE_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of PLE group=0x%03x/0x%03x\n", used_pg, rsv_pg);
++
++	seq_printf(s, "\tReserved page counter of PLE1 group: 0x%08x\n", pg_flow_ctrl[14]);
++	seq_printf(s, "\tPLE1 group page status: 0x%08x\n", pg_flow_ctrl[15]);
++	min_q = (pg_flow_ctrl[20] & WF_PSE_TOP_PG_PLE_GROUP_PLE_MIN_QUOTA_MASK) >> WF_PSE_TOP_PG_PLE_GROUP_PLE_MIN_QUOTA_SHFT;
++	max_q = (pg_flow_ctrl[20] & WF_PSE_TOP_PG_PLE_GROUP_PLE_MAX_QUOTA_MASK) >> WF_PSE_TOP_PG_PLE_GROUP_PLE_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of PLE1 group=0x%03x/0x%03x\n", max_q, min_q);
++	rsv_pg = (pg_flow_ctrl[21] & WF_PSE_TOP_PLE_PG_INFO_PLE_RSV_CNT_MASK) >> WF_PSE_TOP_PLE_PG_INFO_PLE_RSV_CNT_SHFT;
++	used_pg = (pg_flow_ctrl[21] & WF_PSE_TOP_PLE_PG_INFO_PLE_SRC_CNT_MASK) >> WF_PSE_TOP_PLE_PG_INFO_PLE_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of PLE1 group=0x%03x/0x%03x\n", used_pg, rsv_pg);
++
++	seq_printf(s, "\tReserved page counter of MDP group: 0x%08x\n", pg_flow_ctrl[18]);
++	seq_printf(s, "\tMDP group page status: 0x%08x\n", pg_flow_ctrl[19]);
++	min_q = (pg_flow_ctrl[18] & WF_PSE_TOP_PG_MDP_GROUP_MDP_MIN_QUOTA_MASK) >> WF_PSE_TOP_PG_MDP_GROUP_MDP_MIN_QUOTA_SHFT;
++	max_q = (pg_flow_ctrl[18] & WF_PSE_TOP_PG_MDP_GROUP_MDP_MAX_QUOTA_MASK) >> WF_PSE_TOP_PG_MDP_GROUP_MDP_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of MDP group=0x%03x/0x%03x\n", max_q, min_q);
++	rsv_pg = (pg_flow_ctrl[19] & WF_PSE_TOP_MDP_PG_INFO_MDP_RSV_CNT_MASK) >> WF_PSE_TOP_MDP_PG_INFO_MDP_RSV_CNT_SHFT;
++	used_pg = (pg_flow_ctrl[19] & WF_PSE_TOP_MDP_PG_INFO_MDP_SRC_CNT_MASK) >> WF_PSE_TOP_MDP_PG_INFO_MDP_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of MDP group=0x%03x/0x%03x\n", used_pg, rsv_pg);
++	seq_printf(s, "\tReserved page counter of MDP2 group: 0x%08x\n", pg_flow_ctrl[22]);
++	seq_printf(s, "\tMDP2 group page status: 0x%08x\n", pg_flow_ctrl[23]);
++	min_q = (pg_flow_ctrl[22] & WF_PSE_TOP_PG_MDP2_GROUP_MDP2_MIN_QUOTA_MASK) >> WF_PSE_TOP_PG_MDP2_GROUP_MDP2_MIN_QUOTA_SHFT;
++	max_q = (pg_flow_ctrl[22] & WF_PSE_TOP_PG_MDP2_GROUP_MDP2_MAX_QUOTA_MASK) >> WF_PSE_TOP_PG_MDP2_GROUP_MDP2_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of MDP2 group=0x%03x/0x%03x\n", max_q, min_q);
++	rsv_pg = (pg_flow_ctrl[23] & WF_PSE_TOP_MDP2_PG_INFO_MDP2_RSV_CNT_MASK) >> WF_PSE_TOP_MDP2_PG_INFO_MDP2_RSV_CNT_SHFT;
++	used_pg = (pg_flow_ctrl[23] & WF_PSE_TOP_MDP2_PG_INFO_MDP2_SRC_CNT_MASK) >> WF_PSE_TOP_MDP2_PG_INFO_MDP2_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of MDP2 group=0x%03x/0x%03x\n", used_pg, rsv_pg);
++	if (mt7996_band_valid(dev, MT_BAND2)) {
++		seq_printf(s, "\tReserved page counter of MDP3 group: 0x%08x\n", pg_flow_ctrl[24]);
++		seq_printf(s, "\tMDP3 group page status: 0x%08x\n", pg_flow_ctrl[25]);
++		min_q = (pg_flow_ctrl[24] & WF_PSE_TOP_PG_MDP3_GROUP_MDP3_MIN_QUOTA_MASK) >> WF_PSE_TOP_PG_MDP3_GROUP_MDP3_MIN_QUOTA_SHFT;
++		max_q = (pg_flow_ctrl[24] & WF_PSE_TOP_PG_MDP3_GROUP_MDP3_MAX_QUOTA_MASK) >> WF_PSE_TOP_PG_MDP3_GROUP_MDP3_MAX_QUOTA_SHFT;
++		seq_printf(s, "\t\tThe max/min quota pages of MDP3 group=0x%03x/0x%03x\n", max_q, min_q);
++		rsv_pg = (pg_flow_ctrl[25] & WF_PSE_TOP_MDP3_PG_INFO_MDP3_RSV_CNT_MASK) >> WF_PSE_TOP_MDP3_PG_INFO_MDP3_RSV_CNT_SHFT;
++		used_pg = (pg_flow_ctrl[25] & WF_PSE_TOP_MDP3_PG_INFO_MDP3_SRC_CNT_MASK) >> WF_PSE_TOP_MDP3_PG_INFO_MDP3_SRC_CNT_SHFT;
++		seq_printf(s, "\t\tThe used/reserved pages of MDP3 group=0x%03x/0x%03x\n", used_pg, rsv_pg);
++	}
++	/* Queue Empty Status */
++	seq_printf(s, "PSE Queue Empty Status:\n");
++	seq_printf(s, "\tQUEUE_EMPTY: 0x%08x, QUEUE_EMPTY2: 0x%08x\n", pse_stat[0], pse_stat[1]);
++	seq_printf(s, "\t\tCPU Q0/1/2/3/4 empty=%d/%d/%d/%d/%d\n",
++			  (pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_CPU_Q0_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_CPU_Q0_EMPTY_SHFT,
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_CPU_Q1_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_CPU_Q1_EMPTY_SHFT),
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_CPU_Q2_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_CPU_Q2_EMPTY_SHFT),
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_CPU_Q3_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_CPU_Q3_EMPTY_SHFT),
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_CPU_Q4_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_CPU_Q4_EMPTY_SHFT));
++	seq_printf(s, "\t\tHIF Q0/1/2/3/4/5/6/7/8/9/10/11/12/13 empty=%d/%d/%d/%d/%d/%d/%d/%d/%d/%d/%d/%d/%d/%d\n",
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_HIF_0_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_HIF_0_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_HIF_1_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_HIF_1_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_HIF_2_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_HIF_2_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_HIF_3_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_HIF_3_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_HIF_4_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_HIF_4_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_HIF_5_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_HIF_5_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_HIF_6_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_HIF_6_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_HIF_7_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_HIF_7_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_HIF_8_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_HIF_8_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_HIF_9_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_HIF_9_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_HIF_10_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_HIF_10_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_HIF_11_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_HIF_11_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_HIF_12_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_HIF_12_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_HIF_13_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_HIF_13_EMPTY_SHFT));
++	seq_printf(s, "\t\tLMAC TX Q empty=%d\n",
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_LMAC_TX_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_LMAC_TX_QUEUE_EMPTY_SHFT));
++	seq_printf(s, "\t\tMDP TX Q0/Q1/Q2/RX Q empty=%d/%d/%d/%d\n",
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_MDP_TX_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_MDP_TX_QUEUE_EMPTY_SHFT),
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_MDP_TX1_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_MDP_TX1_QUEUE_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_MDP_TX2_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_MDP_TX2_QUEUE_EMPTY_SHFT),
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_MDP_RX_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_MDP_RX_QUEUE_EMPTY_SHFT));
++	seq_printf(s, "\t\tSEC TX Q0/Q1/Q2/RX Q empty=%d/%d/%d/%d\n",
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_SEC_TX_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_SEC_TX_QUEUE_EMPTY_SHFT),
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_SEC_TX1_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_SEC_TX1_QUEUE_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_SEC_TX2_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_SEC_TX2_QUEUE_EMPTY_SHFT),
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_SEC_RX_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_SEC_RX_QUEUE_EMPTY_SHFT));
++	seq_printf(s, "\t\tSFD PARK Q empty=%d\n",
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_SFD_PARK_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_SFD_PARK_QUEUE_EMPTY_SHFT));
++	seq_printf(s, "\t\tMDP TXIOC Q0/Q1/Q2 empty=%d/%d/%d\n",
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_MDP_TXIOC_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_MDP_TXIOC_QUEUE_EMPTY_SHFT),
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_MDP_TXIOC1_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_MDP_TXIOC1_QUEUE_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_MDP_TXIOC2_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_MDP_TXIOC2_QUEUE_EMPTY_SHFT));
++	seq_printf(s, "\t\tMDP RXIOC Q0/Q1/Q2/Q3 empty=%d/%d/%d/%d\n",
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_MDP_RXIOC_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_MDP_RXIOC_QUEUE_EMPTY_SHFT),
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_MDP_RXIOC1_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_MDP_RXIOC1_QUEUE_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_MDP_RXIOC2_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_MDP_RXIOC2_QUEUE_EMPTY_SHFT),
++			  ((pse_stat[1] & WF_PSE_TOP_QUEUE_EMPTY_1_MDP_RXIOC3_QUEUE_EMPTY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_1_MDP_RXIOC3_QUEUE_EMPTY_SHFT));
++	seq_printf(s, "\t\tRLS Q empty=%d\n",
++			  ((pse_stat[0] & WF_PSE_TOP_QUEUE_EMPTY_RLS_Q_EMTPY_MASK) >> WF_PSE_TOP_QUEUE_EMPTY_RLS_Q_EMTPY_SHFT));
++	seq_printf(s, "Nonempty Q info:\n");
++
++	for (i = 0; i < 31; i++) {
++		if (((pse_stat[0] & (0x1 << i)) >> i) == 0) {
++			u32 hfid, tfid, pktcnt, fl_que_ctrl[3] = {0};
++
++			if (pse_queue_empty_info[i].QueueName != NULL) {
++				seq_printf(s, "\t%s: ", pse_queue_empty_info[i].QueueName);
++				fl_que_ctrl[0] |= WF_PSE_TOP_FL_QUE_CTRL_0_EXECUTE_MASK;
++				fl_que_ctrl[0] |= (pse_queue_empty_info[i].Portid << WF_PSE_TOP_FL_QUE_CTRL_0_Q_BUF_PID_SHFT);
++				fl_que_ctrl[0] |= (pse_queue_empty_info[i].Queueid << WF_PSE_TOP_FL_QUE_CTRL_0_Q_BUF_QID_SHFT);
++			} else
++				continue;
++
++			fl_que_ctrl[0] |= (0x1 << 31);
++			mt76_wr(dev, WF_PSE_TOP_FL_QUE_CTRL_0_ADDR, fl_que_ctrl[0]);
++			fl_que_ctrl[1] = mt76_rr(dev, WF_PSE_TOP_FL_QUE_CTRL_2_ADDR);
++			fl_que_ctrl[2] = mt76_rr(dev, WF_PSE_TOP_FL_QUE_CTRL_3_ADDR);
++			hfid = (fl_que_ctrl[1] & WF_PSE_TOP_FL_QUE_CTRL_2_QUEUE_HEAD_FID_MASK) >> WF_PSE_TOP_FL_QUE_CTRL_2_QUEUE_HEAD_FID_SHFT;
++			tfid = (fl_que_ctrl[1] & WF_PSE_TOP_FL_QUE_CTRL_2_QUEUE_TAIL_FID_MASK) >> WF_PSE_TOP_FL_QUE_CTRL_2_QUEUE_TAIL_FID_SHFT;
++			pktcnt = (fl_que_ctrl[2] & WF_PSE_TOP_FL_QUE_CTRL_3_QUEUE_PKT_NUM_MASK) >> WF_PSE_TOP_FL_QUE_CTRL_3_QUEUE_PKT_NUM_SHFT;
++			seq_printf(s, "tail/head fid = 0x%03x/0x%03x, pkt cnt = 0x%03x\n",
++					  tfid, hfid, pktcnt);
++		}
++	}
++
++	for (i = 0; i < 31; i++) {
++		if (((pse_stat[1] & (0x1 << i)) >> i) == 0) {
++			u32 hfid, tfid, pktcnt, fl_que_ctrl[3] = {0};
++
++			if (pse_queue_empty2_info[i].QueueName != NULL) {
++				seq_printf(s, "\t%s: ", pse_queue_empty2_info[i].QueueName);
++				fl_que_ctrl[0] |= WF_PSE_TOP_FL_QUE_CTRL_0_EXECUTE_MASK;
++				fl_que_ctrl[0] |= (pse_queue_empty2_info[i].Portid << WF_PSE_TOP_FL_QUE_CTRL_0_Q_BUF_PID_SHFT);
++				fl_que_ctrl[0] |= (pse_queue_empty2_info[i].Queueid << WF_PSE_TOP_FL_QUE_CTRL_0_Q_BUF_QID_SHFT);
++			} else
++				continue;
++
++			fl_que_ctrl[0] |= (0x1 << 31);
++			mt76_wr(dev, WF_PSE_TOP_FL_QUE_CTRL_0_ADDR, fl_que_ctrl[0]);
++			fl_que_ctrl[1] = mt76_rr(dev, WF_PSE_TOP_FL_QUE_CTRL_2_ADDR);
++			fl_que_ctrl[2] = mt76_rr(dev, WF_PSE_TOP_FL_QUE_CTRL_3_ADDR);
++			hfid = (fl_que_ctrl[1] & WF_PSE_TOP_FL_QUE_CTRL_2_QUEUE_HEAD_FID_MASK) >> WF_PSE_TOP_FL_QUE_CTRL_2_QUEUE_HEAD_FID_SHFT;
++			tfid = (fl_que_ctrl[1] & WF_PSE_TOP_FL_QUE_CTRL_2_QUEUE_TAIL_FID_MASK) >> WF_PSE_TOP_FL_QUE_CTRL_2_QUEUE_TAIL_FID_SHFT;
++			pktcnt = (fl_que_ctrl[2] & WF_PSE_TOP_FL_QUE_CTRL_3_QUEUE_PKT_NUM_MASK) >> WF_PSE_TOP_FL_QUE_CTRL_3_QUEUE_PKT_NUM_SHFT;
++			seq_printf(s, "tail/head fid = 0x%03x/0x%03x, pkt cnt = 0x%03x\n",
++					  tfid, hfid, pktcnt);
++		}
++	}
++
++	return 0;
++}
++
++/* PLE INFO */
++static char *sta_ctrl_reg[] = {"ENABLE", "DISABLE", "PAUSE", "TWT_PAUSE"};
++static struct bmac_queue_info ple_queue_empty_info[] = {
++	{"CPU Q0",  ENUM_UMAC_CPU_PORT_1,     ENUM_UMAC_CTX_Q_0, 0},
++	{"CPU Q1",  ENUM_UMAC_CPU_PORT_1,     ENUM_UMAC_CTX_Q_1, 0},
++	{"CPU Q2",  ENUM_UMAC_CPU_PORT_1,     ENUM_UMAC_CTX_Q_2, 0},
++	{"CPU Q3",  ENUM_UMAC_CPU_PORT_1,     ENUM_UMAC_CTX_Q_3, 0},
++	{"ALTX Q0", ENUM_UMAC_LMAC_PORT_2,    0x10, 0},
++	{"BMC Q0",  ENUM_UMAC_LMAC_PORT_2,    0x11, 0},
++	{"BCN Q0",  ENUM_UMAC_LMAC_PORT_2,    0x12, 0},
++	{"PSMP Q0", ENUM_UMAC_LMAC_PORT_2,    0x13, 0},
++	{"ALTX Q1", ENUM_UMAC_LMAC_PORT_2,    0x10, 1},
++	{"BMC Q1",  ENUM_UMAC_LMAC_PORT_2,    0x11, 1},
++	{"BCN Q1",  ENUM_UMAC_LMAC_PORT_2,    0x12, 1},
++	{"PSMP Q1", ENUM_UMAC_LMAC_PORT_2,    0x13, 1},
++	{"ALTX Q2", ENUM_UMAC_LMAC_PORT_2,    0x10, 2},
++	{"BMC Q2",  ENUM_UMAC_LMAC_PORT_2,    0x11, 2},
++	{"BCN Q2",  ENUM_UMAC_LMAC_PORT_2,    0x12, 2},
++	{"PSMP Q2", ENUM_UMAC_LMAC_PORT_2,    0x13, 2},
++	{"NAF Q",   ENUM_UMAC_LMAC_PORT_2,    0x18, 0},
++	{"NBCN Q",  ENUM_UMAC_LMAC_PORT_2,    0x19, 0},
++	{NULL, 0, 0, 0}, {NULL, 0, 0, 0}, /* 18, 19 not defined */
++	{"FIXFID Q", ENUM_UMAC_LMAC_PORT_2, 0x1a, 0},
++	{NULL, 0, 0, 0}, {NULL, 0, 0, 0}, {NULL, 0, 0, 0}, {NULL, 0, 0, 0}, {NULL, 0, 0, 0},
++	{NULL, 0, 0, 0}, {NULL, 0, 0, 0},
++	{"RLS4 Q",   ENUM_PLE_CTRL_PSE_PORT_3, 0x7c, 0},
++	{"RLS3 Q",   ENUM_PLE_CTRL_PSE_PORT_3, 0x7d, 0},
++	{"RLS2 Q",   ENUM_PLE_CTRL_PSE_PORT_3, 0x7e, 0},
++	{"RLS Q",  ENUM_PLE_CTRL_PSE_PORT_3, 0x7f, 0}
++};
++
++static struct bmac_queue_info_t ple_txcmd_queue_empty_info[] = {
++	{"AC00Q", ENUM_UMAC_LMAC_PORT_2, 0x40},
++	{"AC01Q", ENUM_UMAC_LMAC_PORT_2, 0x41},
++	{"AC02Q", ENUM_UMAC_LMAC_PORT_2, 0x42},
++	{"AC03Q", ENUM_UMAC_LMAC_PORT_2, 0x43},
++	{"AC10Q", ENUM_UMAC_LMAC_PORT_2, 0x44},
++	{"AC11Q", ENUM_UMAC_LMAC_PORT_2, 0x45},
++	{"AC12Q", ENUM_UMAC_LMAC_PORT_2, 0x46},
++	{"AC13Q", ENUM_UMAC_LMAC_PORT_2, 0x47},
++	{"AC20Q", ENUM_UMAC_LMAC_PORT_2, 0x48},
++	{"AC21Q", ENUM_UMAC_LMAC_PORT_2, 0x49},
++	{"AC22Q", ENUM_UMAC_LMAC_PORT_2, 0x4a},
++	{"AC23Q", ENUM_UMAC_LMAC_PORT_2, 0x4b},
++	{"AC30Q", ENUM_UMAC_LMAC_PORT_2, 0x4c},
++	{"AC31Q", ENUM_UMAC_LMAC_PORT_2, 0x4d},
++	{"AC32Q", ENUM_UMAC_LMAC_PORT_2, 0x4e},
++	{"AC33Q", ENUM_UMAC_LMAC_PORT_2, 0x4f},
++	{"ALTX Q0", ENUM_UMAC_LMAC_PORT_2, 0x50},
++	{"TF Q0", ENUM_UMAC_LMAC_PORT_2, 0x51},
++	{"TWT TSF-TF Q0", ENUM_UMAC_LMAC_PORT_2, 0x52},
++	{"TWT DL Q0", ENUM_UMAC_LMAC_PORT_2, 0x53},
++	{"TWT UL Q0", ENUM_UMAC_LMAC_PORT_2, 0x54},
++	{NULL, 0, 0}, {NULL, 0, 0}, {NULL, 0, 0}, {NULL, 0, 0}, {NULL, 0, 0},
++	{NULL, 0, 0}, {NULL, 0, 0}, {NULL, 0, 0}, {NULL, 0, 0}, {NULL, 0, 0}, {NULL, 0, 0},
++};
++
++static void
++mt7996_get_ple_acq_stat(struct mt7996_dev *dev, u32 *ple_stat)
++{
++	u32 i, addr;
++
++	ple_stat[0] = mt76_rr(dev, WF_PLE_TOP_QUEUE_EMPTY_ADDR);
++
++	/* Legacy */
++	addr = WF_PLE_TOP_AC0_QUEUE_EMPTY0_ADDR;
++	for (i = 1; i <= CR_NUM_OF_AC; i++, addr += 4)
++		ple_stat[i] = mt76_rr(dev, addr);
++
++	addr = WF_PLE_TOP_AC1_QUEUE_EMPTY0_ADDR;
++	for (; i <= CR_NUM_OF_AC * 2; i++, addr += 4)
++		ple_stat[i] = mt76_rr(dev, addr);
++
++	addr = WF_PLE_TOP_AC2_QUEUE_EMPTY0_ADDR;
++	for (; i <= CR_NUM_OF_AC * 3; i++, addr += 4)
++		ple_stat[i] = mt76_rr(dev, addr);
++
++	addr = WF_PLE_TOP_AC3_QUEUE_EMPTY0_ADDR;
++	for (; i <= CR_NUM_OF_AC * 4; i++, addr += 4)
++		ple_stat[i] = mt76_rr(dev, addr);
++}
++
++static void
++mt7996_get_ple_txcmd_stat(struct mt7996_dev *dev, u32 *ple_txcmd_stat)
++{
++	*ple_txcmd_stat = mt76_rr(dev, WF_PLE_TOP_NATIVE_TXCMD_QUEUE_EMPTY_ADDR);
++}
++
++static void
++mt7996_get_sta_pause(struct mt7996_dev *dev, u8 band, u32 *sta_pause, u32 *twt_pause)
++{
++	u32 i, addr;
++
++	/* switch to target band */
++	mt76_wr(dev, WF_DRR_TOP_SBRR_ADDR, u32_encode_bits(band, WF_DRR_TOP_SBRR_TARGET_BAND_MASK));
++
++	/* Legacy */
++	addr = WF_DRR_TOP_AC0_STATION_PAUSE00_ADDR;
++	for (i = 0; i < CR_NUM_OF_AC; i++, addr += 4)
++		sta_pause[i] = mt76_rr(dev, addr);
++
++	addr = WF_DRR_TOP_AC1_STATION_PAUSE00_ADDR;
++	for (; i < CR_NUM_OF_AC * 2; i++, addr += 4)
++		sta_pause[i] = mt76_rr(dev, addr);
++
++	addr = WF_DRR_TOP_AC2_STATION_PAUSE00_ADDR;
++	for (; i < CR_NUM_OF_AC * 3; i++, addr += 4)
++		sta_pause[i] = mt76_rr(dev, addr);
++
++	addr = WF_DRR_TOP_AC3_STATION_PAUSE00_ADDR;
++	for (; i < CR_NUM_OF_AC * 4; i++, addr += 4)
++		sta_pause[i] = mt76_rr(dev, addr);
++
++	/* TWT */
++	addr = WF_DRR_TOP_TWT_STA_MAP00_ADDR;
++	for (i = 0; i < CR_NUM_OF_AC; i++, addr += 4)
++		twt_pause[i] = mt76_rr(dev, addr);
++
++}
++
++static int
++mt7996_show_sta_acq_info(struct seq_file *s, u32 *ple_stat,
++			 u32 *sta_pause, u32 *twt_sta_pause,
++			 u32 dumptxd)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	int i, j;
++	u32 total_nonempty_cnt = 0;
++
++	for (j = 0; j < ALL_CR_NUM_OF_ALL_AC; j++) { /* show AC Q info */
++		for (i = 0; i < 32; i++) {
++			if (((ple_stat[j + 1] & (0x1 << i)) >> i) == 0) {
++				u32 hfid, tfid, pktcnt, ac_num = j / CR_NUM_OF_AC, ctrl = 0;
++				u32 sta_num = i + (j % CR_NUM_OF_AC) * 32, fl_que_ctrl[3] = {0};
++				u32 wmmidx = 0;
++				u8 band, idx;
++				struct mt7996_sta *msta;
++				struct mt76_wcid *wcid;
++
++				wcid = rcu_dereference(dev->mt76.wcid[sta_num]);
++				if (!wcid) {
++					seq_printf(s, "ERROR!! no found STA wcid=%d\n", sta_num);
++					return 0;
++				}
++				msta = container_of(wcid, struct mt7996_sta, wcid);
++				wmmidx = msta->vif->deflink.mt76.wmm_idx;
++
++				seq_printf(s, "\tSTA%d AC%d: ", sta_num, ac_num);
++
++				fl_que_ctrl[0] |= WF_PLE_TOP_FL_QUE_CTRL_0_EXECUTE_MASK;
++				fl_que_ctrl[0] |= (ENUM_UMAC_LMAC_PORT_2 << WF_PLE_TOP_FL_QUE_CTRL_0_Q_BUF_PID_SHFT);
++				fl_que_ctrl[0] |= (ac_num << WF_PLE_TOP_FL_QUE_CTRL_0_Q_BUF_QID_SHFT);
++				fl_que_ctrl[0] |= (sta_num << WF_PLE_TOP_FL_QUE_CTRL_0_Q_BUF_WLANID_SHFT);
++				mt76_wr(dev, WF_PLE_TOP_FL_QUE_CTRL_0_ADDR, fl_que_ctrl[0]);
++				fl_que_ctrl[1] = mt76_rr(dev, WF_PLE_TOP_FL_QUE_CTRL_2_ADDR);
++				fl_que_ctrl[2] = mt76_rr(dev, WF_PLE_TOP_FL_QUE_CTRL_3_ADDR);
++				hfid = (fl_que_ctrl[1] & WF_PLE_TOP_FL_QUE_CTRL_2_QUEUE_HEAD_FID_MASK) >>
++					WF_PLE_TOP_FL_QUE_CTRL_2_QUEUE_HEAD_FID_SHFT;
++				tfid = (fl_que_ctrl[1] & WF_PLE_TOP_FL_QUE_CTRL_2_QUEUE_TAIL_FID_MASK) >>
++					WF_PLE_TOP_FL_QUE_CTRL_2_QUEUE_TAIL_FID_SHFT;
++				pktcnt = (fl_que_ctrl[2] & WF_PLE_TOP_FL_QUE_CTRL_3_QUEUE_PKT_NUM_MASK) >>
++					WF_PLE_TOP_FL_QUE_CTRL_3_QUEUE_PKT_NUM_SHFT;
++				seq_printf(s, "tail/head fid = 0x%03x/0x%03x, pkt cnt = 0x%03x",
++						  tfid, hfid, pktcnt);
++
++				band = wcid->phy_idx;
++
++				idx = band * ALL_CR_NUM_OF_ALL_AC + j;
++				if (sta_pause[idx] & BIT(i))
++					ctrl = 2;
++
++				idx = band * CR_NUM_OF_AC + j % CR_NUM_OF_AC;
++				if (twt_sta_pause[idx] & BIT(i))
++					ctrl = 3;
++
++				seq_printf(s, " ctrl = %s", sta_ctrl_reg[ctrl]);
++				seq_printf(s, " (wmmidx=%d, band=%d)\n", wmmidx, band);
++
++				total_nonempty_cnt++;
++
++				if (pktcnt > 0 && dumptxd > 0)
++					mt7996_dump_mac_fid(s, dev, hfid, true);
++			}
++		}
++	}
++
++	return total_nonempty_cnt;
++}
++
++static void
++mt7996_show_txcmdq_info(struct seq_file *s, u32 ple_txcmd_stat)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	int i;
++
++	seq_printf(s, "Nonempty TXCMD Q info:\n");
++	for (i = 0; i < 32 ; i++) {
++		if (((ple_txcmd_stat & (0x1 << i)) >> i) == 0) {
++			u32 hfid, tfid, pktcnt, fl_que_ctrl[3] = {0};
++
++			if (ple_txcmd_queue_empty_info[i].QueueName != NULL) {
++				seq_printf(s, "\t%s: ", ple_txcmd_queue_empty_info[i].QueueName);
++				fl_que_ctrl[0] |= WF_PLE_TOP_FL_QUE_CTRL_0_EXECUTE_MASK;
++				fl_que_ctrl[0] |= (ple_txcmd_queue_empty_info[i].Portid <<
++							WF_PLE_TOP_FL_QUE_CTRL_0_Q_BUF_PID_SHFT);
++				fl_que_ctrl[0] |= (ple_txcmd_queue_empty_info[i].Queueid <<
++							WF_PLE_TOP_FL_QUE_CTRL_0_Q_BUF_QID_SHFT);
++				mt76_wr(dev, WF_PLE_TOP_FL_QUE_CTRL_0_ADDR, fl_que_ctrl[0]);
++			} else
++				continue;
++
++			fl_que_ctrl[1] = mt76_rr(dev, WF_PLE_TOP_FL_QUE_CTRL_2_ADDR);
++			fl_que_ctrl[2] = mt76_rr(dev, WF_PLE_TOP_FL_QUE_CTRL_3_ADDR);
++			hfid = (fl_que_ctrl[1] & WF_PLE_TOP_FL_QUE_CTRL_2_QUEUE_HEAD_FID_MASK) >>
++				WF_PLE_TOP_FL_QUE_CTRL_2_QUEUE_HEAD_FID_SHFT;
++			tfid = (fl_que_ctrl[1] & WF_PLE_TOP_FL_QUE_CTRL_2_QUEUE_TAIL_FID_MASK) >>
++				WF_PLE_TOP_FL_QUE_CTRL_2_QUEUE_TAIL_FID_SHFT;
++			pktcnt = (fl_que_ctrl[2] & WF_PLE_TOP_FL_QUE_CTRL_3_QUEUE_PKT_NUM_MASK) >>
++				WF_PLE_TOP_FL_QUE_CTRL_3_QUEUE_PKT_NUM_SHFT;
++			seq_printf(s, "tail/head fid = 0x%03x/0x%03x, pkt cnt = 0x%03x\n",
++					  tfid, hfid, pktcnt);
++		}
++	}
++}
++
++static int
++mt7996_pleinfo_read(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	u32 ple_buf_ctrl, pg_sz, pg_num;
++	u32 ple_stat[ALL_CR_NUM_OF_ALL_AC + 1] = {0}, pg_flow_ctrl[10] = {0};
++	u32 ple_native_txcmd_stat;
++	u32 ple_txcmd_stat;
++	u32 *sta_pause, *twt_sta_pause;
++	u32 fpg_cnt, ffa_cnt, fpg_head, fpg_tail, hif_max_q, hif_min_q;
++	u32 rpg_hif, upg_hif, cpu_max_q, cpu_min_q, rpg_cpu, upg_cpu;
++	int i, j;
++	u32 dumptxd = dev->dbg.dump_ple_txd;
++
++	sta_pause = kzalloc(CFG_WIFI_RAM_BAND_NUM * ALL_CR_NUM_OF_ALL_AC * sizeof(u32), GFP_KERNEL);
++	if (!sta_pause)
++		return -ENOMEM;
++
++	twt_sta_pause = kzalloc(CFG_WIFI_RAM_BAND_NUM * CR_NUM_OF_AC * sizeof(u32), GFP_KERNEL);
++	if (!twt_sta_pause) {
++		kfree(sta_pause);
++		return -ENOMEM;
++	}
++
++	ple_buf_ctrl = mt76_rr(dev, WF_PLE_TOP_PBUF_CTRL_ADDR);
++	mt7996_get_ple_acq_stat(dev, ple_stat);
++	ple_txcmd_stat = mt76_rr(dev, WF_PLE_TOP_TXCMD_QUEUE_EMPTY_ADDR);
++	mt7996_get_ple_txcmd_stat(dev, &ple_native_txcmd_stat);
++	pg_flow_ctrl[0] = mt76_rr(dev, WF_PLE_TOP_FREEPG_CNT_ADDR);
++	pg_flow_ctrl[1] = mt76_rr(dev, WF_PLE_TOP_FREEPG_HEAD_TAIL_ADDR);
++	pg_flow_ctrl[2] = mt76_rr(dev, WF_PLE_TOP_PG_HIF_GROUP_ADDR);
++	pg_flow_ctrl[3] = mt76_rr(dev, WF_PLE_TOP_HIF_PG_INFO_ADDR);
++	pg_flow_ctrl[4] = mt76_rr(dev, WF_PLE_TOP_PG_CPU_GROUP_ADDR);
++	pg_flow_ctrl[5] = mt76_rr(dev, WF_PLE_TOP_CPU_PG_INFO_ADDR);
++	pg_flow_ctrl[6] = mt76_rr(dev, WF_PLE_TOP_PG_HIF_TXCMD_GROUP_ADDR);
++	pg_flow_ctrl[7] = mt76_rr(dev, WF_PLE_TOP_HIF_TXCMD_PG_INFO_ADDR);
++	pg_flow_ctrl[8] = mt76_rr(dev, WF_PLE_TOP_PG_HIF_WMTXD_GROUP_ADDR);
++	pg_flow_ctrl[9] = mt76_rr(dev, WF_PLE_TOP_HIF_WMTXD_PG_INFO_ADDR);
++
++	for (i = 0; i < __MT_MAX_BAND; i++) {
++		if (!dev->mt76.phys[i])
++			continue;
++
++		mt7996_get_sta_pause(dev, i,
++				     sta_pause + i * ALL_CR_NUM_OF_ALL_AC,
++				     twt_sta_pause + i * CR_NUM_OF_AC);
++	}
++
++	/* Configuration Info */
++	seq_printf(s, "PLE Configuration Info:\n");
++	seq_printf(s, "\tPacket Buffer Control(0x82060014): 0x%08x\n", ple_buf_ctrl);
++	pg_sz = (ple_buf_ctrl & WF_PLE_TOP_PBUF_CTRL_PAGE_SIZE_CFG_MASK) >> WF_PLE_TOP_PBUF_CTRL_PAGE_SIZE_CFG_SHFT;
++	seq_printf(s, "\t\tPage Size=%d(%d bytes per page)\n", pg_sz, (pg_sz == 1 ? 128 : 64));
++	seq_printf(s, "\t\tPage Offset=%d(in unit of 2KB)\n",
++			 (ple_buf_ctrl & WF_PLE_TOP_PBUF_CTRL_PBUF_OFFSET_MASK) >> WF_PLE_TOP_PBUF_CTRL_PBUF_OFFSET_SHFT);
++	pg_num = (ple_buf_ctrl & WF_PLE_TOP_PBUF_CTRL_TOTAL_PAGE_NUM_MASK) >> WF_PLE_TOP_PBUF_CTRL_TOTAL_PAGE_NUM_SHFT;
++	seq_printf(s, "\t\tTotal Page=%d pages\n", pg_num);
++
++	/* Page Flow Control */
++	seq_printf(s, "PLE Page Flow Control:\n");
++	seq_printf(s, "\tFree page counter: 0x%08x\n", pg_flow_ctrl[0]);
++	fpg_cnt = (pg_flow_ctrl[0] & WF_PLE_TOP_FREEPG_CNT_FREEPG_CNT_MASK) >> WF_PLE_TOP_FREEPG_CNT_FREEPG_CNT_SHFT;
++	seq_printf(s, "\t\tThe toal page number of free=0x%03x\n", fpg_cnt);
++	ffa_cnt = (pg_flow_ctrl[0] & WF_PLE_TOP_FREEPG_CNT_FFA_CNT_MASK) >> WF_PLE_TOP_FREEPG_CNT_FFA_CNT_SHFT;
++	seq_printf(s, "\t\tThe free page numbers of free for all=0x%03x\n", ffa_cnt);
++	seq_printf(s, "\tFree page head and tail: 0x%08x\n", pg_flow_ctrl[1]);
++	fpg_head = (pg_flow_ctrl[1] & WF_PLE_TOP_FREEPG_HEAD_TAIL_FREEPG_HEAD_MASK) >> WF_PLE_TOP_FREEPG_HEAD_TAIL_FREEPG_HEAD_SHFT;
++	fpg_tail = (pg_flow_ctrl[1] & WF_PLE_TOP_FREEPG_HEAD_TAIL_FREEPG_TAIL_MASK) >> WF_PLE_TOP_FREEPG_HEAD_TAIL_FREEPG_TAIL_SHFT;
++	seq_printf(s, "\t\tThe tail/head page of free page list=0x%03x/0x%03x\n", fpg_tail, fpg_head);
++	seq_printf(s, "\tReserved page counter of HIF group: 0x%08x\n", pg_flow_ctrl[2]);
++	seq_printf(s, "\tHIF group page status: 0x%08x\n", pg_flow_ctrl[3]);
++	hif_min_q = (pg_flow_ctrl[2] & WF_PLE_TOP_PG_HIF_GROUP_HIF_MIN_QUOTA_MASK) >> WF_PLE_TOP_PG_HIF_GROUP_HIF_MIN_QUOTA_SHFT;
++	hif_max_q = (pg_flow_ctrl[2] & WF_PLE_TOP_PG_HIF_GROUP_HIF_MAX_QUOTA_MASK) >> WF_PLE_TOP_PG_HIF_GROUP_HIF_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of HIF group=0x%03x/0x%03x\n", hif_max_q, hif_min_q);
++	rpg_hif = (pg_flow_ctrl[3] & WF_PLE_TOP_HIF_PG_INFO_HIF_RSV_CNT_MASK) >> WF_PLE_TOP_HIF_PG_INFO_HIF_RSV_CNT_SHFT;
++	upg_hif = (pg_flow_ctrl[3] & WF_PLE_TOP_HIF_PG_INFO_HIF_SRC_CNT_MASK) >> WF_PLE_TOP_HIF_PG_INFO_HIF_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of HIF group=0x%03x/0x%03x\n", upg_hif, rpg_hif);
++
++	seq_printf(s, "\tReserved page counter of WMTXD group: 0x%08x\n", pg_flow_ctrl[8]);
++	seq_printf(s, "\tWMTXD group page status: 0x%08x\n", pg_flow_ctrl[9]);
++	cpu_min_q = (pg_flow_ctrl[8] & WF_PLE_TOP_PG_HIF_WMTXD_GROUP_HIF_WMTXD_MIN_QUOTA_MASK) >> WF_PLE_TOP_PG_HIF_WMTXD_GROUP_HIF_WMTXD_MIN_QUOTA_SHFT;
++	cpu_max_q = (pg_flow_ctrl[8] & WF_PLE_TOP_PG_HIF_WMTXD_GROUP_HIF_WMTXD_MAX_QUOTA_MASK) >> WF_PLE_TOP_PG_HIF_WMTXD_GROUP_HIF_WMTXD_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of WMTXD group=0x%03x/0x%03x\n", cpu_max_q, cpu_min_q);
++	rpg_cpu = (pg_flow_ctrl[9] & WF_PLE_TOP_HIF_WMTXD_PG_INFO_HIF_WMTXD_RSV_CNT_MASK) >> WF_PLE_TOP_HIF_WMTXD_PG_INFO_HIF_WMTXD_RSV_CNT_SHFT;
++	upg_cpu = (pg_flow_ctrl[9] & WF_PLE_TOP_HIF_WMTXD_PG_INFO_HIF_WMTXD_SRC_CNT_MASK) >> WF_PLE_TOP_HIF_WMTXD_PG_INFO_HIF_WMTXD_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of WMTXD group=0x%03x/0x%03x\n", upg_cpu, rpg_cpu);
++
++	seq_printf(s, "\tReserved page counter of HIF_TXCMD group: 0x%08x\n", pg_flow_ctrl[6]);
++	seq_printf(s, "\tHIF_TXCMD group page status: 0x%08x\n", pg_flow_ctrl[7]);
++	cpu_min_q = (pg_flow_ctrl[6] & WF_PLE_TOP_PG_HIF_TXCMD_GROUP_HIF_TXCMD_MIN_QUOTA_MASK) >> WF_PLE_TOP_PG_HIF_TXCMD_GROUP_HIF_TXCMD_MIN_QUOTA_SHFT;
++	cpu_max_q = (pg_flow_ctrl[6] & WF_PLE_TOP_PG_HIF_TXCMD_GROUP_HIF_TXCMD_MAX_QUOTA_MASK) >> WF_PLE_TOP_PG_HIF_TXCMD_GROUP_HIF_TXCMD_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of HIF_TXCMD group=0x%03x/0x%03x\n", cpu_max_q, cpu_min_q);
++	rpg_cpu = (pg_flow_ctrl[7] & WF_PLE_TOP_HIF_TXCMD_PG_INFO_HIF_TXCMD_RSV_CNT_MASK) >> WF_PLE_TOP_HIF_TXCMD_PG_INFO_HIF_TXCMD_RSV_CNT_SHFT;
++	upg_cpu = (pg_flow_ctrl[7] & WF_PLE_TOP_HIF_TXCMD_PG_INFO_HIF_TXCMD_SRC_CNT_MASK) >> WF_PLE_TOP_HIF_TXCMD_PG_INFO_HIF_TXCMD_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of HIF_TXCMD group=0x%03x/0x%03x\n", upg_cpu, rpg_cpu);
++
++	seq_printf(s, "\tReserved page counter of CPU group(0x820c0150): 0x%08x\n", pg_flow_ctrl[4]);
++	seq_printf(s, "\tCPU group page status(0x820c0154): 0x%08x\n", pg_flow_ctrl[5]);
++	cpu_min_q = (pg_flow_ctrl[4] & WF_PLE_TOP_PG_CPU_GROUP_CPU_MIN_QUOTA_MASK) >> WF_PLE_TOP_PG_CPU_GROUP_CPU_MIN_QUOTA_SHFT;
++	cpu_max_q = (pg_flow_ctrl[4] & WF_PLE_TOP_PG_CPU_GROUP_CPU_MAX_QUOTA_MASK) >> WF_PLE_TOP_PG_CPU_GROUP_CPU_MAX_QUOTA_SHFT;
++	seq_printf(s, "\t\tThe max/min quota pages of CPU group=0x%03x/0x%03x\n", cpu_max_q, cpu_min_q);
++	rpg_cpu = (pg_flow_ctrl[5] & WF_PLE_TOP_CPU_PG_INFO_CPU_RSV_CNT_MASK) >> WF_PLE_TOP_CPU_PG_INFO_CPU_RSV_CNT_SHFT;
++	upg_cpu = (pg_flow_ctrl[5] & WF_PLE_TOP_CPU_PG_INFO_CPU_SRC_CNT_MASK) >> WF_PLE_TOP_CPU_PG_INFO_CPU_SRC_CNT_SHFT;
++	seq_printf(s, "\t\tThe used/reserved pages of CPU group=0x%03x/0x%03x\n", upg_cpu, rpg_cpu);
++
++	if ((ple_stat[0] & WF_PLE_TOP_QUEUE_EMPTY_ALL_AC_EMPTY_MASK) == 0) {
++		for (j = 0; j < ALL_CR_NUM_OF_ALL_AC; j++) {
++			if (j % CR_NUM_OF_AC == 0) {
++				seq_printf(s, "\n\tNonempty AC%d Q of STA#: ", j / CR_NUM_OF_AC);
++			}
++
++			for (i = 0; i < 32; i++) {
++				if (((ple_stat[j + 1] & (0x1 << i)) >> i) == 0) {
++					seq_printf(s, "%d ", i + (j % CR_NUM_OF_AC) * 32);
++				}
++			}
++		}
++
++		seq_printf(s, "\n");
++	}
++
++	seq_printf(s, "non-native/native txcmd queue empty = %d/%d\n", ple_txcmd_stat, ple_native_txcmd_stat);
++
++	seq_printf(s, "Nonempty Q info:\n");
++
++	for (i = 0; i < 32; i++) {
++		if (((ple_stat[0] & (0x1 << i)) >> i) == 0) {
++			u32 hfid, tfid, pktcnt, fl_que_ctrl[3] = {0};
++
++			if (ple_queue_empty_info[i].QueueName != NULL) {
++				seq_printf(s, "\t%s: ", ple_queue_empty_info[i].QueueName);
++				fl_que_ctrl[0] |= WF_PLE_TOP_FL_QUE_CTRL_0_EXECUTE_MASK;
++				fl_que_ctrl[0] |= (ple_queue_empty_info[i].Portid << WF_PLE_TOP_FL_QUE_CTRL_0_Q_BUF_PID_SHFT);
++				fl_que_ctrl[0] |= (ple_queue_empty_info[i].tgid << WF_PLE_TOP_FL_QUE_CTRL_0_Q_BUF_TGID_SHFT);
++				fl_que_ctrl[0] |= (ple_queue_empty_info[i].Queueid << WF_PLE_TOP_FL_QUE_CTRL_0_Q_BUF_QID_SHFT);
++			} else
++				continue;
++
++			mt76_wr(dev, WF_PLE_TOP_FL_QUE_CTRL_0_ADDR, fl_que_ctrl[0]);
++			fl_que_ctrl[1] = mt76_rr(dev, WF_PLE_TOP_FL_QUE_CTRL_2_ADDR);
++			fl_que_ctrl[2] = mt76_rr(dev, WF_PLE_TOP_FL_QUE_CTRL_3_ADDR);
++
++			hfid = (fl_que_ctrl[1] & WF_PLE_TOP_FL_QUE_CTRL_2_QUEUE_HEAD_FID_MASK) >> WF_PLE_TOP_FL_QUE_CTRL_2_QUEUE_HEAD_FID_SHFT;
++			tfid = (fl_que_ctrl[1] & WF_PLE_TOP_FL_QUE_CTRL_2_QUEUE_TAIL_FID_MASK) >> WF_PLE_TOP_FL_QUE_CTRL_2_QUEUE_TAIL_FID_SHFT;
++			pktcnt = (fl_que_ctrl[2] & WF_PLE_TOP_FL_QUE_CTRL_3_QUEUE_PKT_NUM_MASK) >> WF_PLE_TOP_FL_QUE_CTRL_3_QUEUE_PKT_NUM_SHFT;
++			seq_printf(s, "tail/head fid = 0x%03x/0x%03x, pkt cnt = 0x%03x\n",
++					  tfid, hfid, pktcnt);
++
++			if (pktcnt > 0 && dumptxd > 0)
++				mt7996_dump_mac_fid(s, dev, hfid, true);
++		}
++	}
++
++	mt7996_show_sta_acq_info(s, ple_stat, sta_pause, twt_sta_pause, dumptxd);
++	mt7996_show_txcmdq_info(s, ple_native_txcmd_stat);
++
++	kfree(sta_pause);
++	kfree(twt_sta_pause);
++	return 0;
++}
++
++/* DRR */
++static int
++mt7996_drr_info(struct seq_file *s, void *data)
++{
++	/* TODO: Wait MIB counter API implement complete */
++	return 0;
++}
++
++int mt7996_mtk_init_debugfs_internal(struct mt7996_phy *phy, struct dentry *dir)
++{
++	struct mt7996_dev *dev = phy->dev;
++
++	debugfs_create_devm_seqfile(dev->mt76.dev, "drr_info", dir,
++				    mt7996_drr_info);
++
++	debugfs_create_u32("token_idx", 0600, dir, &dev->dbg.token_idx);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "token_txd", dir,
++				    mt7996_token_txd_read);
++	debugfs_create_u32("txd_dump", 0600, dir, &dev->dbg.txd_read_cnt);
++	debugfs_create_u32("rxd_dump", 0600, dir, &dev->dbg.rxd_read_cnt);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "rx_token", dir,
++				    mt7996_rx_token_read);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "rx_msdu_pg", dir,
++				    mt7996_rx_msdu_pg_read);
++
++	debugfs_create_devm_seqfile(dev->mt76.dev, "ple_info", dir,
++				    mt7996_pleinfo_read);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "pse_info", dir,
++				    mt7996_pseinfo_read);
++	/* ple/pse fid raw data dump */
++	debugfs_create_u32("fid_idx", 0600, dir, &dev->dbg.fid_idx);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "ple_fid", dir,
++				    mt7996_ple_fid_read);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "pse_fid", dir,
++				    mt7996_pse_fid_read);
++	/* amsdu */
++	debugfs_create_file("amsdu_algo", 0600, dir, dev, &fops_amsdu_algo);
++	debugfs_create_file("amsdu_para", 0600, dir, dev, &fops_amsdu_para);
++	debugfs_create_devm_seqfile(dev->mt76.dev, "amsdu_info", dir,
++	                            mt7996_amsdu_info_read);
++
++	debugfs_create_u8("dump_ple_txd", 0600, dir, &dev->dbg.dump_ple_txd);
++	return 0;
++}
++
++#endif
+diff --git a/mt7996/testmode.c b/mt7996/testmode.c
+index 9fa4edcd6..056c501df 100644
+--- a/mt7996/testmode.c
++++ b/mt7996/testmode.c
+@@ -223,6 +223,7 @@ static void
+ mt7996_tm_init(struct mt7996_phy *phy, bool en)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_vif *mvif = (struct mt7996_vif *)phy->monitor_vif->drv_priv;
+ 	u8 rf_test_mode = en ? RF_OPER_RF_TEST : RF_OPER_NORMAL;
+ 
+ 	if (!test_bit(MT76_STATE_RUNNING, &phy->mt76->state))
+@@ -234,8 +235,8 @@ mt7996_tm_init(struct mt7996_phy *phy, bool en)
+ 
+ 	mt7996_tm_rf_switch_mode(dev, rf_test_mode);
+ 
+-	mt7996_mcu_add_bss_info(phy, phy->monitor_vif, en);
+-	mt7996_mcu_add_sta(dev, phy->monitor_vif, NULL, en, false);
++	mt7996_mcu_add_bss_info(phy, &phy->monitor_vif->bss_conf, &mvif->deflink, en);
++	mt7996_mcu_add_sta(dev, &phy->monitor_vif->bss_conf, &mvif->deflink, NULL, en, false);
+ 
+ 	mt7996_tm_set(dev, SET_ID(BAND_IDX), phy->mt76->band_idx);
+ 
+@@ -1179,13 +1180,13 @@ mt7996_tm_txbf_init(struct mt7996_phy *phy, u16 *val)
+ 	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);
++	mvif->deflink.mt76.idx = band_idx;
++	dev->mt76.vif_mask |= BIT_ULL(mvif->deflink.mt76.idx);
++	mvif->deflink.mt76.omac_idx = band_idx;
++	phy->omac_mask |= BIT_ULL(mvif->deflink.mt76.omac_idx);
+ 
+-	mt7996_mcu_add_dev_info(phy, phy->monitor_vif, true);
+-	mt7996_mcu_add_bss_info(phy, phy->monitor_vif, true);
++	mt7996_mcu_add_dev_info(phy, &phy->monitor_vif->bss_conf, &mvif->deflink, true);
++	mt7996_mcu_add_bss_info(phy, &phy->monitor_vif->bss_conf, &mvif->deflink, true);
+ 
+ 	if (td->ibf) {
+ 		if (td->is_txbf_dut) {
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0102-wifi-mt76-mt7996-switch-to-per-link-data-structure-o.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0102-wifi-mt76-mt7996-switch-to-per-link-data-structure-o.patch
new file mode 100644
index 0000000..78b9cba
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0102-wifi-mt76-mt7996-switch-to-per-link-data-structure-o.patch
@@ -0,0 +1,2455 @@
+From d0f96d2beb1d76e08cf9479c77ede7c373ae40c2 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Mon, 27 Nov 2023 10:43:34 +0800
+Subject: [PATCH 102/120] wifi: mt76: mt7996: switch to per-link data structure
+ of sta
+
+Introduce struct mt7996_link_sta, data structure for per-link STA.
+Note that mt7996_sta now represents a peer legacy or MLD device.
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7615/mcu.c           |   4 +-
+ mt76_connac_mcu.c      |  18 +-
+ mt76_connac_mcu.h      |   7 +-
+ mt7915/mcu.c           |   4 +-
+ mt7925/mcu.c           |   4 +-
+ mt7996/debugfs.c       |  20 +-
+ mt7996/mac.c           | 109 ++++++-----
+ mt7996/main.c          | 223 +++++++++++++--------
+ mt7996/mcu.c           | 430 +++++++++++++++++++++--------------------
+ mt7996/mt7996.h        |  44 +++--
+ mt7996/mtk_debugfs_i.c |   6 +-
+ mt7996/testmode.c      |   6 +-
+ 12 files changed, 485 insertions(+), 390 deletions(-)
+
+diff --git a/mt7615/mcu.c b/mt7615/mcu.c
+index a9310660b..8f4f203ef 100644
+--- a/mt7615/mcu.c
++++ b/mt7615/mcu.c
+@@ -862,8 +862,8 @@ mt7615_mcu_wtbl_sta_add(struct mt7615_phy *phy, struct ieee80211_vif *vif,
+ 		else
+ 			mvif->sta_added = true;
+ 	}
+-	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, sskb, vif, sta, enable,
+-				      new_entry);
++	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, sskb, vif, &sta->deflink,
++				      enable, new_entry);
+ 	if (enable && sta)
+ 		mt76_connac_mcu_sta_tlv(phy->mt76, sskb, sta, vif, 0,
+ 					MT76_STA_INFO_STATE_ASSOC);
+diff --git a/mt76_connac_mcu.c b/mt76_connac_mcu.c
+index 6dabb7fdc..da2fe01ac 100644
+--- a/mt76_connac_mcu.c
++++ b/mt76_connac_mcu.c
+@@ -371,9 +371,10 @@ EXPORT_SYMBOL_GPL(mt76_connac_mcu_bss_omac_tlv);
+ 
+ void mt76_connac_mcu_sta_basic_tlv(struct mt76_dev *dev, struct sk_buff *skb,
+ 				   struct ieee80211_vif *vif,
+-				   struct ieee80211_sta *sta,
++				   struct ieee80211_link_sta *link_sta,
+ 				   bool enable, bool newly)
+ {
++	struct ieee80211_sta *sta = link_sta ? link_sta->sta : NULL;
+ 	struct sta_rec_basic *basic;
+ 	struct tlv *tlv;
+ 	int conn_type;
+@@ -431,7 +432,7 @@ void mt76_connac_mcu_sta_basic_tlv(struct mt76_dev *dev, struct sk_buff *skb,
+ 		break;
+ 	}
+ 
+-	memcpy(basic->peer_addr, sta->addr, ETH_ALEN);
++	memcpy(basic->peer_addr, link_sta->addr, ETH_ALEN);
+ 	basic->qos = sta->wme;
+ }
+ EXPORT_SYMBOL_GPL(mt76_connac_mcu_sta_basic_tlv);
+@@ -1055,7 +1056,7 @@ int mt76_connac_mcu_sta_cmd(struct mt76_phy *phy,
+ 		return PTR_ERR(skb);
+ 
+ 	if (info->sta || !info->offload_fw)
+-		mt76_connac_mcu_sta_basic_tlv(dev, skb, info->vif, info->sta,
++		mt76_connac_mcu_sta_basic_tlv(dev, skb, info->vif, &info->sta->deflink,
+ 					      info->enable, info->newly);
+ 	if (info->sta && info->enable)
+ 		mt76_connac_mcu_sta_tlv(phy, skb, info->sta,
+@@ -1306,7 +1307,8 @@ int mt76_connac_mcu_sta_ba(struct mt76_dev *dev, struct mt76_vif *mvif,
+ EXPORT_SYMBOL_GPL(mt76_connac_mcu_sta_ba);
+ 
+ u8 mt76_connac_get_phy_mode(struct mt76_phy *phy, struct ieee80211_vif *vif,
+-			    enum nl80211_band band, struct ieee80211_sta *sta)
++			    enum nl80211_band band,
++			    struct ieee80211_link_sta *link_sta)
+ {
+ 	struct mt76_dev *dev = phy->dev;
+ 	const struct ieee80211_sta_he_cap *he_cap;
+@@ -1317,10 +1319,10 @@ u8 mt76_connac_get_phy_mode(struct mt76_phy *phy, struct ieee80211_vif *vif,
+ 	if (is_connac_v1(dev))
+ 		return 0x38;
+ 
+-	if (sta) {
+-		ht_cap = &sta->deflink.ht_cap;
+-		vht_cap = &sta->deflink.vht_cap;
+-		he_cap = &sta->deflink.he_cap;
++	if (link_sta) {
++		ht_cap = &link_sta->ht_cap;
++		vht_cap = &link_sta->vht_cap;
++		he_cap = &link_sta->he_cap;
+ 	} else {
+ 		struct ieee80211_supported_band *sband;
+ 
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 151183d08..f7da63658 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1903,8 +1903,8 @@ int mt76_connac_mcu_set_channel_domain(struct mt76_phy *phy);
+ int mt76_connac_mcu_set_vif_ps(struct mt76_dev *dev, struct ieee80211_vif *vif);
+ void mt76_connac_mcu_sta_basic_tlv(struct mt76_dev *dev, struct sk_buff *skb,
+ 				   struct ieee80211_vif *vif,
+-				   struct ieee80211_sta *sta, bool enable,
+-				   bool newly);
++				   struct ieee80211_link_sta *link_sta,
++				   bool enable, bool newly);
+ void mt76_connac_mcu_wtbl_generic_tlv(struct mt76_dev *dev, struct sk_buff *skb,
+ 				      struct ieee80211_vif *vif,
+ 				      struct ieee80211_sta *sta, void *sta_wtbl,
+@@ -2012,7 +2012,8 @@ mt76_connac_get_he_phy_cap(struct mt76_phy *phy, struct ieee80211_vif *vif);
+ const struct ieee80211_sta_eht_cap *
+ mt76_connac_get_eht_phy_cap(struct mt76_phy *phy, struct ieee80211_vif *vif);
+ u8 mt76_connac_get_phy_mode(struct mt76_phy *phy, struct ieee80211_vif *vif,
+-			    enum nl80211_band band, struct ieee80211_sta *sta);
++			    enum nl80211_band band,
++			    struct ieee80211_link_sta *link_sta);
+ u8 mt76_connac_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_vif *vif,
+ 				enum nl80211_band band);
+ 
+diff --git a/mt7915/mcu.c b/mt7915/mcu.c
+index 2d017396c..1d1b3cead 100644
+--- a/mt7915/mcu.c
++++ b/mt7915/mcu.c
+@@ -1504,7 +1504,7 @@ mt7915_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7915_dev *dev,
+ 
+ 	ra->valid = true;
+ 	ra->auto_rate = true;
+-	ra->phy_mode = mt76_connac_get_phy_mode(mphy, vif, band, sta);
++	ra->phy_mode = mt76_connac_get_phy_mode(mphy, vif, band, &sta->deflink);
+ 	ra->channel = chandef->chan->hw_value;
+ 	ra->bw = sta->deflink.bandwidth;
+ 	ra->phy.bw = sta->deflink.bandwidth;
+@@ -1669,7 +1669,7 @@ int mt7915_mcu_add_sta(struct mt7915_dev *dev, struct ieee80211_vif *vif,
+ 		return PTR_ERR(skb);
+ 
+ 	/* starec basic */
+-	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, skb, vif, sta, enable,
++	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, skb, vif, &sta->deflink, enable,
+ 				      !rcu_access_pointer(dev->mt76.wcid[msta->wcid.idx]));
+ 	if (!enable)
+ 		goto out;
+diff --git a/mt7925/mcu.c b/mt7925/mcu.c
+index bd37cb8d7..1cb556302 100644
+--- a/mt7925/mcu.c
++++ b/mt7925/mcu.c
+@@ -1626,7 +1626,7 @@ mt7925_mcu_sta_cmd(struct mt76_phy *phy,
+ 		return PTR_ERR(skb);
+ 
+ 	if (info->sta || !info->offload_fw)
+-		mt76_connac_mcu_sta_basic_tlv(dev, skb, info->vif, info->sta,
++		mt76_connac_mcu_sta_basic_tlv(dev, skb, info->vif, &info->sta->deflink,
+ 					      info->enable, info->newly);
+ 	if (info->sta && info->enable) {
+ 		mt7925_mcu_sta_phy_tlv(skb, info->vif, info->sta);
+@@ -2092,7 +2092,7 @@ mt7925_mcu_bss_basic_tlv(struct sk_buff *skb,
+ 		basic_req->nonht_basic_phy = cpu_to_le16(PHY_TYPE_OFDM_INDEX);
+ 
+ 	memcpy(basic_req->bssid, vif->bss_conf.bssid, ETH_ALEN);
+-	basic_req->phymode = mt76_connac_get_phy_mode(phy, vif, band, sta);
++	basic_req->phymode = mt76_connac_get_phy_mode(phy, vif, band, &sta->deflink);
+ 	basic_req->bcn_interval = cpu_to_le16(vif->bss_conf.beacon_int);
+ 	basic_req->dtim_period = vif->bss_conf.dtim_period;
+ 	basic_req->bmc_tx_wlan_idx = cpu_to_le16(wlan_idx);
+diff --git a/mt7996/debugfs.c b/mt7996/debugfs.c
+index d4a74fef2..b609d0722 100644
+--- a/mt7996/debugfs.c
++++ b/mt7996/debugfs.c
+@@ -692,14 +692,15 @@ static void
+ mt7996_sta_hw_queue_read(void *data, struct ieee80211_sta *sta)
+ {
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_link_sta *mlink = &msta->deflink;
+ 	struct mt7996_dev *dev = msta->vif->deflink.phy->dev;
+ 	struct seq_file *s = data;
+ 	u8 ac;
+ 
+ 	for (ac = 0; ac < 4; ac++) {
+ 		u32 qlen, ctrl, val;
+-		u32 idx = msta->wcid.idx >> 5;
+-		u8 offs = msta->wcid.idx & GENMASK(4, 0);
++		u32 idx = mlink->wcid.idx >> 5;
++		u8 offs = mlink->wcid.idx & GENMASK(4, 0);
+ 
+ 		ctrl = BIT(31) | BIT(11) | (ac << 24);
+ 		val = mt76_rr(dev, MT_PLE_AC_QEMPTY(ac, idx));
+@@ -707,11 +708,11 @@ mt7996_sta_hw_queue_read(void *data, struct ieee80211_sta *sta)
+ 		if (val & BIT(offs))
+ 			continue;
+ 
+-		mt76_wr(dev, MT_FL_Q0_CTRL, ctrl | msta->wcid.idx);
++		mt76_wr(dev, MT_FL_Q0_CTRL, ctrl | mlink->wcid.idx);
+ 		qlen = mt76_get_field(dev, MT_FL_Q3_CTRL,
+ 				      GENMASK(11, 0));
+ 		seq_printf(s, "\tSTA %pM wcid %d: AC%d%d queued:%d\n",
+-			   sta->addr, msta->wcid.idx,
++			   sta->addr, mlink->wcid.idx,
+ 			   msta->vif->deflink.mt76.wmm_idx, ac, qlen);
+ 	}
+ }
+@@ -994,7 +995,7 @@ mt7996_airtime_read(struct seq_file *s, void *data)
+ 	struct mt76_dev *mdev = &dev->mt76;
+ 	struct mt76_sta_stats *stats;
+ 	struct ieee80211_sta *sta;
+-	struct mt7996_sta *msta;
++	struct mt7996_link_sta *mlink;
+ 	struct mt76_wcid *wcid;
+ 	struct mt76_vif *vif;
+ 	u16 i;
+@@ -1006,9 +1007,9 @@ mt7996_airtime_read(struct seq_file *s, void *data)
+ 		if (!wcid || !wcid->sta)
+ 			continue;
+ 
+-		msta = container_of(wcid, struct mt7996_sta, wcid);
+-		sta = container_of((void *)msta, struct ieee80211_sta, drv_priv);
+-		vif = &msta->vif->deflink.mt76;
++		mlink = container_of(wcid, struct mt7996_link_sta, wcid);
++		sta = container_of((void *)mlink->sta, struct ieee80211_sta, drv_priv);
++		vif = &mlink->sta->vif->deflink.mt76;
+ 		stats = &wcid->stats;
+ 
+ 		seq_printf(s, "%pM WCID: %hu BandIdx: %hhu OmacIdx: 0x%hhx\t"
+@@ -1152,6 +1153,7 @@ static ssize_t mt7996_sta_fixed_rate_set(struct file *file,
+ #define LONG_PREAMBLE 1
+ 	struct ieee80211_sta *sta = file->private_data;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_link_sta *mlink = &msta->deflink;
+ 	struct mt7996_dev *dev = msta->vif->deflink.phy->dev;
+ 	struct ra_rate phy = {};
+ 	char buf[100];
+@@ -1187,7 +1189,7 @@ static ssize_t mt7996_sta_fixed_rate_set(struct file *file,
+ 		goto out;
+ 	}
+ 
+-	phy.wlan_idx = cpu_to_le16(msta->wcid.idx);
++	phy.wlan_idx = cpu_to_le16(mlink->wcid.idx);
+ 	phy.gi = cpu_to_le16(gi);
+ 	phy.ltf = cpu_to_le16(ltf);
+ 	phy.ldpc = phy.ldpc ? 7 : 0;
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 3f36938fe..2f5a57f53 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -54,7 +54,7 @@ static const struct mt7996_dfs_radar_spec jp_radar_specs = {
+ static struct mt76_wcid *mt7996_rx_get_wcid(struct mt7996_dev *dev,
+ 					    u16 idx, bool unicast)
+ {
+-	struct mt7996_sta *sta;
++	struct mt7996_link_sta *mlink;
+ 	struct mt76_wcid *wcid;
+ 
+ 	if (idx >= ARRAY_SIZE(dev->mt76.wcid))
+@@ -67,11 +67,11 @@ static struct mt76_wcid *mt7996_rx_get_wcid(struct mt7996_dev *dev,
+ 	if (!wcid->sta)
+ 		return NULL;
+ 
+-	sta = container_of(wcid, struct mt7996_sta, wcid);
+-	if (!sta->vif)
++	mlink = container_of(wcid, struct mt7996_link_sta, wcid);
++	if (!mlink->sta->vif)
+ 		return NULL;
+ 
+-	return &sta->vif->sta.wcid;
++	return &mlink->wcid;
+ }
+ 
+ bool mt7996_mac_wtbl_update(struct mt7996_dev *dev, int idx, u32 mask)
+@@ -92,12 +92,11 @@ u32 mt7996_mac_wtbl_lmac_addr(struct mt7996_dev *dev, u16 wcid, u8 dw)
+ }
+ 
+ void mt7996_mac_enable_rtscts(struct mt7996_dev *dev,
+-			      struct ieee80211_vif *vif, bool enable)
++			      struct mt7996_link_sta *mlink, bool enable)
+ {
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	u32 addr;
+ 
+-	addr = mt7996_mac_wtbl_lmac_addr(dev, mvif->sta.wcid.idx, 5);
++	addr = mt7996_mac_wtbl_lmac_addr(dev, mlink->wcid.idx, 5);
+ 	if (enable)
+ 		mt76_set(dev, addr, BIT(5));
+ 	else
+@@ -349,7 +348,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
+ 	__le16 fc = 0;
+ 	int idx;
+ 	u8 hw_aggr = false;
+-	struct mt7996_sta *msta = NULL;
++	struct mt7996_link_sta *mlink = NULL;
+ 
+ 	hw_aggr = status->aggr;
+ 	memset(status, 0, sizeof(*status));
+@@ -380,10 +379,10 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
+ 	status->wcid = mt7996_rx_get_wcid(dev, idx, unicast);
+ 
+ 	if (status->wcid) {
+-		msta = container_of(status->wcid, struct mt7996_sta, wcid);
++		mlink = container_of(status->wcid, struct mt7996_link_sta, wcid);
+ 		spin_lock_bh(&dev->mt76.sta_poll_lock);
+-		if (list_empty(&msta->wcid.poll_list))
+-			list_add_tail(&msta->wcid.poll_list,
++		if (list_empty(&mlink->wcid.poll_list))
++			list_add_tail(&mlink->wcid.poll_list,
+ 				      &dev->mt76.sta_poll_list);
+ 		spin_unlock_bh(&dev->mt76.sta_poll_lock);
+ 	}
+@@ -592,7 +591,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
+ #endif
+ 	} else {
+ 		status->flag |= RX_FLAG_8023;
+-		mt7996_wed_check_ppe(dev, &dev->mt76.q_rx[q], msta, skb,
++		mt7996_wed_check_ppe(dev, &dev->mt76.q_rx[q], mlink ? mlink->sta : NULL, skb,
+ 				     *info);
+ 	}
+ 
+@@ -942,6 +941,7 @@ static void
+ mt7996_tx_check_aggr(struct ieee80211_sta *sta, struct sk_buff *skb)
+ {
+ 	struct mt7996_sta *msta;
++	struct mt7996_link_sta *mlink;
+ 	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ 	bool is_8023 = info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP;
+ 	u16 fc, tid;
+@@ -970,7 +970,8 @@ mt7996_tx_check_aggr(struct ieee80211_sta *sta, struct sk_buff *skb)
+ 		return;
+ 
+ 	msta = (struct mt7996_sta *)sta->drv_priv;
+-	if (!test_and_set_bit(tid, &msta->wcid.ampdu_state))
++	mlink = rcu_dereference(msta->link[0]);
++	if (!test_and_set_bit(tid, &mlink->wcid.ampdu_state))
+ 		ieee80211_start_tx_ba_session(sta, tid, 0);
+ }
+ 
+@@ -1048,7 +1049,7 @@ mt7996_mac_tx_free(struct mt7996_dev *dev, void *data, int len)
+ 		 */
+ 		info = le32_to_cpu(*cur_info);
+ 		if (info & MT_TXFREE_INFO_PAIR) {
+-			struct mt7996_sta *msta;
++			struct mt7996_link_sta *mlink;
+ 			u16 idx;
+ 
+ 			idx = FIELD_GET(MT_TXFREE_INFO_WLAN_ID, info);
+@@ -1057,10 +1058,10 @@ mt7996_mac_tx_free(struct mt7996_dev *dev, void *data, int len)
+ 			if (!sta)
+ 				continue;
+ 
+-			msta = container_of(wcid, struct mt7996_sta, wcid);
++			mlink = container_of(wcid, struct mt7996_link_sta, wcid);
+ 			spin_lock_bh(&mdev->sta_poll_lock);
+-			if (list_empty(&msta->wcid.poll_list))
+-				list_add_tail(&msta->wcid.poll_list,
++			if (list_empty(&mlink->wcid.poll_list))
++				list_add_tail(&mlink->wcid.poll_list,
+ 					      &mdev->sta_poll_list);
+ 			spin_unlock_bh(&mdev->sta_poll_lock);
+ 			continue;
+@@ -1265,7 +1266,7 @@ out:
+ 
+ static void mt7996_mac_add_txs(struct mt7996_dev *dev, void *data)
+ {
+-	struct mt7996_sta *msta = NULL;
++	struct mt7996_link_sta *mlink;
+ 	struct mt76_wcid *wcid;
+ 	__le32 *txs_data = data;
+ 	u16 wcidx;
+@@ -1286,16 +1287,15 @@ static void mt7996_mac_add_txs(struct mt7996_dev *dev, void *data)
+ 	if (!wcid)
+ 		goto out;
+ 
+-	msta = container_of(wcid, struct mt7996_sta, wcid);
+-
+ 	mt7996_mac_add_txs_skb(dev, wcid, pid, txs_data);
+ 
+ 	if (!wcid->sta)
+ 		goto out;
+ 
++	mlink = container_of(wcid, struct mt7996_link_sta, wcid);
+ 	spin_lock_bh(&dev->mt76.sta_poll_lock);
+-	if (list_empty(&msta->wcid.poll_list))
+-		list_add_tail(&msta->wcid.poll_list, &dev->mt76.sta_poll_list);
++	if (list_empty(&mlink->wcid.poll_list))
++		list_add_tail(&mlink->wcid.poll_list, &dev->mt76.sta_poll_list);
+ 	spin_unlock_bh(&dev->mt76.sta_poll_lock);
+ 
+ out:
+@@ -2242,8 +2242,9 @@ void mt7996_mac_sta_rc_work(struct work_struct *work)
+ 	struct ieee80211_sta *sta;
+ 	struct ieee80211_vif *vif;
+ 	struct ieee80211_bss_conf *conf;
++	struct ieee80211_link_sta *link_sta;
+ 	struct mt7996_bss_conf *mconf;
+-	struct mt7996_sta *msta;
++	struct mt7996_link_sta *mlink;
+ 	u32 changed;
+ 	LIST_HEAD(list);
+ 
+@@ -2251,24 +2252,25 @@ void mt7996_mac_sta_rc_work(struct work_struct *work)
+ 	list_splice_init(&dev->sta_rc_list, &list);
+ 
+ 	while (!list_empty(&list)) {
+-		msta = list_first_entry(&list, struct mt7996_sta, rc_list);
+-		list_del_init(&msta->rc_list);
+-		changed = msta->changed;
+-		msta->changed = 0;
++		mlink = list_first_entry(&list, struct mt7996_link_sta, rc_list);
++		list_del_init(&mlink->rc_list);
++		changed = mlink->changed;
++		mlink->changed = 0;
+ 		spin_unlock_bh(&dev->mt76.sta_poll_lock);
+ 
+-		sta = container_of((void *)msta, struct ieee80211_sta, drv_priv);
+-		vif = container_of((void *)msta->vif, struct ieee80211_vif, drv_priv);
++		sta = container_of((void *)mlink->sta, struct ieee80211_sta, drv_priv);
++		link_sta = &sta->deflink;
++		vif = container_of((void *)mlink->sta->vif, struct ieee80211_vif, drv_priv);
+ 		conf = &vif->bss_conf;
+-		mconf = &msta->vif->deflink;
++		mconf = &mlink->sta->vif->deflink;
+ 
+ 		if (changed & (IEEE80211_RC_SUPP_RATES_CHANGED |
+ 			       IEEE80211_RC_NSS_CHANGED |
+ 			       IEEE80211_RC_BW_CHANGED))
+-			mt7996_mcu_add_rate_ctrl(dev, conf, mconf, sta, true);
++			mt7996_mcu_add_rate_ctrl(dev, conf, mconf, link_sta, mlink, true);
+ 
+ 		if (changed & IEEE80211_RC_SMPS_CHANGED)
+-			mt7996_mcu_set_fixed_field(dev, mconf, sta, NULL,
++			mt7996_mcu_set_fixed_field(dev, mconf, link_sta, mlink, NULL,
+ 						   RATE_PARAM_MMPS_UPDATE);
+ 
+ 		spin_lock_bh(&dev->mt76.sta_poll_lock);
+@@ -2557,7 +2559,7 @@ static int mt7996_mac_check_twt_req(struct ieee80211_twt_setup *twt)
+ }
+ 
+ static bool
+-mt7996_mac_twt_param_equal(struct mt7996_sta *msta,
++mt7996_mac_twt_param_equal(struct mt7996_link_sta *mlink,
+ 			   struct ieee80211_twt_params *twt_agrt)
+ {
+ 	u16 type = le16_to_cpu(twt_agrt->req_type);
+@@ -2568,10 +2570,10 @@ mt7996_mac_twt_param_equal(struct mt7996_sta *msta,
+ 	for (i = 0; i < MT7996_MAX_STA_TWT_AGRT; i++) {
+ 		struct mt7996_twt_flow *f;
+ 
+-		if (!(msta->twt.flowid_mask & BIT(i)))
++		if (!(mlink->twt.flowid_mask & BIT(i)))
+ 			continue;
+ 
+-		f = &msta->twt.flow[i];
++		f = &mlink->twt.flow[i];
+ 		if (f->duration == twt_agrt->min_twt_dur &&
+ 		    f->mantissa == twt_agrt->mantissa &&
+ 		    f->exp == exp &&
+@@ -2590,6 +2592,7 @@ void mt7996_mac_add_twt_setup(struct ieee80211_hw *hw,
+ {
+ 	enum ieee80211_twt_setup_cmd setup_cmd = TWT_SETUP_CMD_REJECT;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_link_sta *mlink;
+ 	struct ieee80211_twt_params *twt_agrt = (void *)twt->params;
+ 	u16 req_type = le16_to_cpu(twt_agrt->req_type);
+ 	enum ieee80211_twt_setup_cmd sta_setup_cmd;
+@@ -2601,11 +2604,12 @@ void mt7996_mac_add_twt_setup(struct ieee80211_hw *hw,
+ 		goto out;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
++	mlink = mlink_dereference_protected(msta, 0);
+ 
+ 	if (dev->twt.n_agrt == MT7996_MAX_TWT_AGRT)
+ 		goto unlock;
+ 
+-	if (hweight8(msta->twt.flowid_mask) == ARRAY_SIZE(msta->twt.flow))
++	if (hweight8(mlink->twt.flowid_mask) == ARRAY_SIZE(mlink->twt.flow))
+ 		goto unlock;
+ 
+ 	if (twt_agrt->min_twt_dur < MT7996_MIN_TWT_DUR) {
+@@ -2614,10 +2618,10 @@ void mt7996_mac_add_twt_setup(struct ieee80211_hw *hw,
+ 		goto unlock;
+ 	}
+ 
+-	if (mt7996_mac_twt_param_equal(msta, twt_agrt))
++	if (mt7996_mac_twt_param_equal(mlink, twt_agrt))
+ 		goto unlock;
+ 
+-	flowid = ffs(~msta->twt.flowid_mask) - 1;
++	flowid = ffs(~mlink->twt.flowid_mask) - 1;
+ 	twt_agrt->req_type &= ~cpu_to_le16(IEEE80211_TWT_REQTYPE_FLOWID);
+ 	twt_agrt->req_type |= le16_encode_bits(flowid,
+ 					       IEEE80211_TWT_REQTYPE_FLOWID);
+@@ -2626,10 +2630,10 @@ void mt7996_mac_add_twt_setup(struct ieee80211_hw *hw,
+ 	exp = FIELD_GET(IEEE80211_TWT_REQTYPE_WAKE_INT_EXP, req_type);
+ 	sta_setup_cmd = FIELD_GET(IEEE80211_TWT_REQTYPE_SETUP_CMD, req_type);
+ 
+-	flow = &msta->twt.flow[flowid];
++	flow = &mlink->twt.flow[flowid];
+ 	memset(flow, 0, sizeof(*flow));
+ 	INIT_LIST_HEAD(&flow->list);
+-	flow->wcid = msta->wcid.idx;
++	flow->wcid = mlink->wcid.idx;
+ 	flow->table_id = table_id;
+ 	flow->id = flowid;
+ 	flow->duration = twt_agrt->min_twt_dur;
+@@ -2647,7 +2651,7 @@ void mt7996_mac_add_twt_setup(struct ieee80211_hw *hw,
+ 
+ 		flow->sched = true;
+ 		flow->start_tsf = mt7996_mac_twt_sched_list_add(dev, flow);
+-		curr_tsf = __mt7996_get_tsf(hw, &msta->vif->deflink);
++		curr_tsf = __mt7996_get_tsf(hw, &mlink->sta->vif->deflink);
+ 		div_u64_rem(curr_tsf - flow->start_tsf, interval, &rem);
+ 		flow_tsf = curr_tsf + interval - rem;
+ 		twt_agrt->twt = cpu_to_le64(flow_tsf);
+@@ -2656,13 +2660,13 @@ void mt7996_mac_add_twt_setup(struct ieee80211_hw *hw,
+ 	}
+ 	flow->tsf = le64_to_cpu(twt_agrt->twt);
+ 
+-	if (mt7996_mcu_twt_agrt_update(dev, &msta->vif->deflink, flow,
++	if (mt7996_mcu_twt_agrt_update(dev, &mlink->sta->vif->deflink, flow,
+ 				       MCU_TWT_AGRT_ADD))
+ 		goto unlock;
+ 
+ 	setup_cmd = TWT_SETUP_CMD_ACCEPT;
+ 	dev->twt.table_mask |= BIT(table_id);
+-	msta->twt.flowid_mask |= BIT(flowid);
++	mlink->twt.flowid_mask |= BIT(flowid);
+ 	dev->twt.n_agrt++;
+ 
+ unlock:
+@@ -2675,26 +2679,25 @@ out:
+ }
+ 
+ void mt7996_mac_twt_teardown_flow(struct mt7996_dev *dev,
+-				  struct mt7996_sta *msta,
+-				  u8 flowid)
++				  struct mt7996_link_sta *mlink, u8 flowid)
+ {
+ 	struct mt7996_twt_flow *flow;
+-	struct mt7996_bss_conf *mconf = mconf_dereference_protected(msta->vif, 0);
++	struct mt7996_bss_conf *mconf = mconf_dereference_protected(mlink->sta->vif, 0);
+ 
+ 	lockdep_assert_held(&dev->mt76.mutex);
+ 
+-	if (flowid >= ARRAY_SIZE(msta->twt.flow))
++	if (flowid >= ARRAY_SIZE(mlink->twt.flow))
+ 		return;
+ 
+-	if (!(msta->twt.flowid_mask & BIT(flowid)))
++	if (!(mlink->twt.flowid_mask & BIT(flowid)))
+ 		return;
+ 
+-	flow = &msta->twt.flow[flowid];
++	flow = &mlink->twt.flow[flowid];
+ 	if (mt7996_mcu_twt_agrt_update(dev, mconf, flow, MCU_TWT_AGRT_DELETE))
+ 		return;
+ 
+ 	list_del_init(&flow->list);
+-	msta->twt.flowid_mask &= ~BIT(flowid);
++	mlink->twt.flowid_mask &= ~BIT(flowid);
+ 	dev->twt.table_mask &= ~BIT(flow->table_id);
+ 	dev->twt.n_agrt--;
+ }
+@@ -2706,7 +2709,7 @@ mt7996_scan_send_probe(struct mt7996_phy *phy, struct cfg80211_ssid *ssid)
+ 	struct cfg80211_scan_request *req = phy->scan_req;
+ 	struct ieee80211_vif *vif = phy->scan_vif;
+ 	struct mt7996_vif *mvif;
+-	struct mt76_wcid *wcid;
++	struct mt7996_link_sta *mlink;
+ 	struct ieee80211_tx_info *info;
+ 	struct sk_buff *skb;
+ 
+@@ -2714,7 +2717,6 @@ mt7996_scan_send_probe(struct mt7996_phy *phy, struct cfg80211_ssid *ssid)
+ 		return;
+ 
+ 	mvif = (struct mt7996_vif *)vif->drv_priv;
+-	wcid = &mvif->sta.wcid;
+ 
+ 	skb = ieee80211_probereq_get(hw, vif->addr,
+ 				     ssid->ssid, ssid->ssid_len, req->ie_len);
+@@ -2740,7 +2742,8 @@ mt7996_scan_send_probe(struct mt7996_phy *phy, struct cfg80211_ssid *ssid)
+ 	}
+ 
+ 	local_bh_disable();
+-	mt76_tx(phy->mt76, NULL, wcid, skb);
++	mlink = rcu_dereference(mvif->sta.link[0]);
++	mt76_tx(phy->mt76, NULL, &mlink->wcid, skb);
+ 	local_bh_enable();
+ 
+ 	rcu_read_unlock();
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 7ea81e94c..798beebb5 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -228,6 +228,7 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 	struct ieee80211_bss_conf *conf = &vif->bss_conf;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_bss_conf *mconf = &mvif->deflink;
++	struct mt7996_link_sta *mlink = &mvif->sta.deflink;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	struct mt76_txq *mtxq;
+@@ -267,14 +268,15 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 
+ 	idx = MT7996_WTBL_RESERVED - mconf->mt76.idx;
+ 
+-	INIT_LIST_HEAD(&mvif->sta.rc_list);
+-	INIT_LIST_HEAD(&mvif->sta.wcid.poll_list);
+-	mvif->sta.wcid.idx = idx;
+-	mvif->sta.wcid.phy_idx = band_idx;
+-	mvif->sta.wcid.hw_key_idx = -1;
+-	mvif->sta.wcid.tx_info |= MT_WCID_TX_INFO_SET;
+-	mvif->sta.vif = mvif;
+-	mt76_wcid_init(&mvif->sta.wcid);
++	INIT_LIST_HEAD(&mlink->rc_list);
++	INIT_LIST_HEAD(&mlink->wcid.poll_list);
++	mlink->wcid.idx = idx;
++	mlink->wcid.phy_idx = band_idx;
++	mlink->wcid.hw_key_idx = -1;
++	mlink->wcid.tx_info |= MT_WCID_TX_INFO_SET;
++	mlink->sta = &mvif->sta;
++	mlink->sta->vif = mvif;
++	mt76_wcid_init(&mlink->wcid);
+ 
+ 	mt7996_mac_wtbl_update(dev, idx,
+ 			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+@@ -296,14 +298,15 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 
+ 	mt7996_init_bitrate_mask(mconf);
+ 
+-	mt7996_mcu_add_bss_info(phy, conf, mconf, true);
++	mt7996_mcu_add_bss_info(phy, conf, mconf, mlink, true);
+ 	/* defer the first STA_REC of BMC entry to BSS_CHANGED_BSSID for STA
+ 	 * interface, since firmware only records BSSID when the entry is new
+ 	 */
+ 	if (vif->type != NL80211_IFTYPE_STATION)
+-		mt7996_mcu_add_sta(dev, conf, mconf, NULL, true, true);
+-	rcu_assign_pointer(dev->mt76.wcid[idx], &mvif->sta.wcid);
++		mt7996_mcu_add_sta(dev, conf, mconf, NULL, mlink, true, true);
++	rcu_assign_pointer(dev->mt76.wcid[idx], &mlink->wcid);
+ 	rcu_assign_pointer(mvif->link[0], mconf);
++	rcu_assign_pointer(mvif->sta.link[0], mlink);
+ 
+ out:
+ 	mutex_unlock(&dev->mt76.mutex);
+@@ -317,10 +320,10 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
+ 	struct ieee80211_bss_conf *conf;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_bss_conf *mconf;
+-	struct mt7996_sta *msta = &mvif->sta;
++	struct mt7996_link_sta *mlink = &mvif->sta.deflink;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+-	int idx = msta->wcid.idx;
++	int idx = mlink->wcid.idx;
+ 
+ 	cancel_delayed_work_sync(&phy->scan_work);
+ 
+@@ -328,8 +331,8 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
+ 
+ 	conf = link_conf_dereference_protected(vif, 0);
+ 	mconf = mconf_dereference_protected(mvif, 0);
+-	mt7996_mcu_add_sta(dev, conf, mconf, NULL, false, false);
+-	mt7996_mcu_add_bss_info(phy, conf, mconf, false);
++	mt7996_mcu_add_sta(dev, conf, mconf, NULL, mlink, false, false);
++	mt7996_mcu_add_bss_info(phy, conf, mconf, mlink, false);
+ 
+ 	if (vif == phy->monitor_vif)
+ 		phy->monitor_vif = NULL;
+@@ -342,12 +345,13 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
+ 	phy->omac_mask &= ~BIT_ULL(mconf->mt76.omac_idx);
+ 
+ 	spin_lock_bh(&dev->mt76.sta_poll_lock);
+-	if (!list_empty(&msta->wcid.poll_list))
+-		list_del_init(&msta->wcid.poll_list);
++	if (!list_empty(&mlink->wcid.poll_list))
++		list_del_init(&mlink->wcid.poll_list);
+ 	spin_unlock_bh(&dev->mt76.sta_poll_lock);
+ 
+-	mt76_wcid_cleanup(&dev->mt76, &msta->wcid);
++	mt76_wcid_cleanup(&dev->mt76, &mlink->wcid);
+ 	rcu_assign_pointer(mvif->link[0], NULL);
++	rcu_assign_pointer(mvif->sta.link[0], NULL);
+ 
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+@@ -446,10 +450,10 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta = sta ? (struct mt7996_sta *)sta->drv_priv :
+ 				  &mvif->sta;
+-	struct mt76_wcid *wcid = &msta->wcid;
+ 	struct mt7996_bss_conf *mconf;
++	struct mt7996_link_sta *mlink;
+ 	struct ieee80211_bss_conf *conf;
+-	u8 *wcid_keyidx = &wcid->hw_key_idx;
++	u8 *wcid_keyidx;
+ 	int idx = key->keyidx;
+ 	int err = 0;
+ 
+@@ -463,6 +467,12 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+ 	    !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
+ 		return -EOPNOTSUPP;
+ 
++	mutex_lock(&dev->mt76.mutex);
++	conf = link_conf_dereference_protected(vif, 0);
++	mconf = mconf_dereference_protected(mvif, 0);
++	mlink = mlink_dereference_protected(msta, 0);
++	wcid_keyidx = &mlink->wcid.hw_key_idx;
++
+ 	/* fall back to sw encryption for unsupported ciphers */
+ 	switch (key->cipher) {
+ 	case WLAN_CIPHER_SUITE_TKIP:
+@@ -484,16 +494,13 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+ 	case WLAN_CIPHER_SUITE_WEP40:
+ 	case WLAN_CIPHER_SUITE_WEP104:
+ 	default:
++		mutex_unlock(&dev->mt76.mutex);
+ 		return -EOPNOTSUPP;
+ 	}
+ 
+-	mutex_lock(&dev->mt76.mutex);
+-
+-	conf = link_conf_dereference_protected(vif, 0);
+-	mconf = mconf_dereference_protected(mvif, 0);
+ 	if (cmd == SET_KEY && !sta && !mconf->mt76.cipher) {
+ 		mconf->mt76.cipher = mt76_connac_mcu_get_cipher(key->cipher);
+-		mt7996_mcu_add_bss_info(phy, conf, mconf, true);
++		mt7996_mcu_add_bss_info(phy, conf, mconf, mlink, true);
+ 	}
+ 
+ 	if (cmd == SET_KEY) {
+@@ -504,14 +511,14 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+ 		goto out;
+ 	}
+ 
+-	mt76_wcid_key_setup(&dev->mt76, wcid, key);
++	mt76_wcid_key_setup(&dev->mt76, &mlink->wcid, key);
+ 
+ 	if (key->keyidx == 6 || key->keyidx == 7)
+-		err = mt7996_mcu_bcn_prot_enable(dev, conf, mconf, key);
++		err = mt7996_mcu_bcn_prot_enable(dev, conf, mconf, mlink, key);
+ 	else
+ 		err = mt7996_mcu_add_key(&dev->mt76, mconf, key,
+ 					 MCU_WMWA_UNI_CMD(STA_REC_UPDATE),
+-					 &msta->wcid, cmd);
++					 &mlink->wcid, cmd);
+ out:
+ 	mutex_unlock(&dev->mt76.mutex);
+ 
+@@ -714,25 +721,27 @@ static void mt7996_bss_info_changed(struct ieee80211_hw *hw,
+ {
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_bss_conf *mconf;
++	struct mt7996_link_sta *mlink;
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+ 	mconf = mconf_dereference_protected(mvif, 0);
++	mlink = mlink_dereference_protected(&mvif->sta, 0);
+ 	/* station mode uses BSSID to map the wlan entry to a peer,
+ 	 * and then peer references bss_info_rfch to set bandwidth cap.
+ 	 */
+ 	if ((changed & BSS_CHANGED_BSSID && !is_zero_ether_addr(info->bssid)) ||
+ 	    (changed & BSS_CHANGED_ASSOC && vif->cfg.assoc) ||
+ 	    (changed & BSS_CHANGED_BEACON_ENABLED && info->enable_beacon)) {
+-		mt7996_mcu_add_bss_info(phy, info, mconf, true);
+-		mt7996_mcu_add_sta(dev, info, mconf, NULL, true,
++		mt7996_mcu_add_bss_info(phy, info, mconf, mlink, true);
++		mt7996_mcu_add_sta(dev, info, mconf, NULL, mlink, true,
+ 				   !!(changed & BSS_CHANGED_BSSID));
+ 	}
+ 
+ 	if (changed & BSS_CHANGED_ERP_CTS_PROT)
+-		mt7996_mac_enable_rtscts(dev, vif, info->use_cts_prot);
++		mt7996_mac_enable_rtscts(dev, mlink, info->use_cts_prot);
+ 
+ 	if (changed & BSS_CHANGED_ERP_SLOT) {
+ 		int slottime = info->use_short_slot ? 9 : 20;
+@@ -803,6 +812,7 @@ int mt7996_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_bss_conf *mconf = mconf_dereference_protected(mvif, 0);
++	struct mt7996_link_sta *mlink = &msta->deflink;
+ 	u8 band_idx = mconf->phy->mt76->band_idx;
+ 	int idx;
+ 
+@@ -814,13 +824,16 @@ int mt7996_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	if (idx < 0)
+ 		return -ENOSPC;
+ 
+-	INIT_LIST_HEAD(&msta->rc_list);
+-	INIT_LIST_HEAD(&msta->wcid.poll_list);
++	INIT_LIST_HEAD(&mlink->rc_list);
++	INIT_LIST_HEAD(&mlink->wcid.poll_list);
+ 	msta->vif = mvif;
+-	msta->wcid.sta = 1;
+-	msta->wcid.idx = idx;
+-	msta->wcid.phy_idx = band_idx;
+-	msta->wcid.tx_info |= MT_WCID_TX_INFO_SET;
++	mlink->wcid.sta = 1;
++	mlink->wcid.idx = idx;
++	mlink->wcid.phy_idx = band_idx;
++	mlink->wcid.tx_info |= MT_WCID_TX_INFO_SET;
++	mlink->sta = msta;
++
++	rcu_assign_pointer(msta->link[0], mlink);
+ 
+ #ifdef CONFIG_MTK_VENDOR
+ 	mt7996_vendor_amnt_sta_remove(mconf->phy, sta);
+@@ -853,19 +866,25 @@ void mt7996_mac_sta_assoc(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct mt7996_bss_conf *mconf;
++	struct mt7996_link_sta *mlink;
+ 	struct ieee80211_bss_conf *conf;
++	struct ieee80211_link_sta *link_sta;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+-	mt7996_mac_wtbl_update(dev, msta->wcid.idx,
+-			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+-
+ 	conf = link_conf_dereference_protected(vif, 0);
+ 	mconf = mconf_dereference_protected(mvif, 0);
+-	mt7996_mcu_add_sta(dev, conf, mconf, sta, true, true);
+-	mt7996_mcu_add_rate_ctrl(dev, conf, mconf, sta, false);
++	link_sta = link_sta_dereference_protected(sta, 0);
++	mlink = mlink_dereference_protected(msta, 0);
+ 
+-	ewma_avg_signal_init(&msta->avg_ack_signal);
++	mt7996_mac_wtbl_update(dev, mlink->wcid.idx,
++			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
++
++	mt7996_mcu_add_sta(dev, conf, mconf, link_sta, mlink, true, true);
++	mt7996_mcu_add_rate_ctrl(dev, conf, mconf, link_sta, mlink, false);
++	mlink->wcid.tx_info |= MT_WCID_TX_INFO_SET;
++
++	ewma_avg_signal_init(&mlink->avg_ack_signal);
+ 
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+@@ -877,25 +896,31 @@ void mt7996_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct mt7996_bss_conf *mconf;
++	struct mt7996_link_sta *mlink;
+ 	struct ieee80211_bss_conf *conf;
++	struct ieee80211_link_sta *link_sta;
+ 	int i;
+ 
+ 	conf = link_conf_dereference_protected(vif, 0);
+ 	mconf = mconf_dereference_protected(mvif, 0);
+-	mt7996_mcu_add_sta(dev, conf, mconf, sta, false, false);
++	link_sta = link_sta_dereference_protected(sta, 0);
++	mlink = mlink_dereference_protected(msta, 0);
++	mt7996_mcu_add_sta(dev, conf, mconf, link_sta, mlink, false, false);
+ 
+-	mt7996_mac_wtbl_update(dev, msta->wcid.idx,
++	mt7996_mac_wtbl_update(dev, mlink->wcid.idx,
+ 			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+ 
+-	for (i = 0; i < ARRAY_SIZE(msta->twt.flow); i++)
+-		mt7996_mac_twt_teardown_flow(dev, msta, i);
++	for (i = 0; i < ARRAY_SIZE(mlink->twt.flow); i++)
++		mt7996_mac_twt_teardown_flow(dev, mlink, i);
+ 
+ 	spin_lock_bh(&mdev->sta_poll_lock);
+-	if (!list_empty(&msta->wcid.poll_list))
+-		list_del_init(&msta->wcid.poll_list);
+-	if (!list_empty(&msta->rc_list))
+-		list_del_init(&msta->rc_list);
++	if (!list_empty(&mlink->wcid.poll_list))
++		list_del_init(&mlink->wcid.poll_list);
++	if (!list_empty(&mlink->rc_list))
++		list_del_init(&mlink->rc_list);
+ 	spin_unlock_bh(&mdev->sta_poll_lock);
++
++	rcu_assign_pointer(msta->link[0], NULL);
+ }
+ 
+ static void mt7996_tx(struct ieee80211_hw *hw,
+@@ -907,19 +932,22 @@ static void mt7996_tx(struct ieee80211_hw *hw,
+ 	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ 	struct ieee80211_vif *vif = info->control.vif;
+ 	struct mt76_wcid *wcid = &dev->mt76.global_wcid;
++	struct mt7996_link_sta *mlink;
+ 
+ 	if (control->sta) {
+-		struct mt7996_sta *sta;
++		struct mt7996_sta *msta;
+ 
+-		sta = (struct mt7996_sta *)control->sta->drv_priv;
+-		wcid = &sta->wcid;
++		msta = (struct mt7996_sta *)control->sta->drv_priv;
++		mlink = rcu_dereference(msta->link[0]);
++		wcid = &mlink->wcid;
+ 	}
+ 
+ 	if (vif && !control->sta) {
+ 		struct mt7996_vif *mvif;
+ 
+ 		mvif = (struct mt7996_vif *)vif->drv_priv;
+-		wcid = &mvif->sta.wcid;
++		mlink = rcu_dereference(mvif->sta.link[0]);
++		wcid = &mlink->wcid;
+ 	}
+ 
+ 	mt76_tx(mphy, control->sta, wcid, skb);
+@@ -946,6 +974,7 @@ mt7996_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	struct ieee80211_sta *sta = params->sta;
+ 	struct ieee80211_txq *txq = sta->txq[params->tid];
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_link_sta *mlink;
+ 	u16 tid = params->tid;
+ 	u16 ssn = params->ssn;
+ 	struct mt76_txq *mtxq;
+@@ -957,14 +986,15 @@ mt7996_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	mtxq = (struct mt76_txq *)txq->drv_priv;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
++	mlink = mlink_dereference_protected(msta, 0);
+ 	switch (action) {
+ 	case IEEE80211_AMPDU_RX_START:
+-		mt76_rx_aggr_start(&dev->mt76, &msta->wcid, tid, ssn,
++		mt76_rx_aggr_start(&dev->mt76, &mlink->wcid, tid, ssn,
+ 				   params->buf_size);
+ 		ret = mt7996_mcu_add_rx_ba(dev, params, true);
+ 		break;
+ 	case IEEE80211_AMPDU_RX_STOP:
+-		mt76_rx_aggr_stop(&dev->mt76, &msta->wcid, tid);
++		mt76_rx_aggr_stop(&dev->mt76, &mlink->wcid, tid);
+ 		ret = mt7996_mcu_add_rx_ba(dev, params, false);
+ 		break;
+ 	case IEEE80211_AMPDU_TX_OPERATIONAL:
+@@ -975,16 +1005,16 @@ mt7996_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	case IEEE80211_AMPDU_TX_STOP_FLUSH:
+ 	case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
+ 		mtxq->aggr = false;
+-		clear_bit(tid, &msta->wcid.ampdu_state);
++		clear_bit(tid, &mlink->wcid.ampdu_state);
+ 		ret = mt7996_mcu_add_tx_ba(dev, params, false);
+ 		break;
+ 	case IEEE80211_AMPDU_TX_START:
+-		set_bit(tid, &msta->wcid.ampdu_state);
++		set_bit(tid, &mlink->wcid.ampdu_state);
+ 		ret = IEEE80211_AMPDU_TX_START_IMMEDIATE;
+ 		break;
+ 	case IEEE80211_AMPDU_TX_STOP_CONT:
+ 		mtxq->aggr = false;
+-		clear_bit(tid, &msta->wcid.ampdu_state);
++		clear_bit(tid, &mlink->wcid.ampdu_state);
+ 		ret = mt7996_mcu_add_tx_ba(dev, params, false);
+ 		ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
+ 		break;
+@@ -1163,10 +1193,19 @@ static void mt7996_sta_statistics(struct ieee80211_hw *hw,
+ 				  struct ieee80211_sta *sta,
+ 				  struct station_info *sinfo)
+ {
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+-	struct rate_info *txrate = &msta->wcid.rate;
++	struct mt7996_link_sta *mlink;
++	struct rate_info *txrate;
+ 
++	/* TODO: support per-link rate report */
++	mutex_lock(&dev->mt76.mutex);
++	mlink = mlink_dereference_protected(msta, 0);
++	if (!mlink)
++		goto out;
++
++	txrate = &mlink->wcid.rate;
+ 	if (txrate->legacy || txrate->flags) {
+ 		if (txrate->legacy) {
+ 			sinfo->txrate.legacy = txrate->legacy;
+@@ -1185,44 +1224,52 @@ static void mt7996_sta_statistics(struct ieee80211_hw *hw,
+ 	sinfo->txrate.flags = txrate->flags;
+ 	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE);
+ 
+-	sinfo->tx_failed = msta->wcid.stats.tx_failed;
++	sinfo->tx_failed = mlink->wcid.stats.tx_failed;
+ 	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_FAILED);
+ 
+-	sinfo->tx_retries = msta->wcid.stats.tx_retries;
++	sinfo->tx_retries = mlink->wcid.stats.tx_retries;
+ 	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_RETRIES);
+ 
+-	sinfo->ack_signal = (s8)msta->ack_signal;
++	sinfo->ack_signal = (s8)mlink->ack_signal;
+ 	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_ACK_SIGNAL);
+ 
+-	sinfo->avg_ack_signal = -(s8)ewma_avg_signal_read(&msta->avg_ack_signal);
++	sinfo->avg_ack_signal = -(s8)ewma_avg_signal_read(&mlink->avg_ack_signal);
+ 	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_ACK_SIGNAL_AVG);
+ 
+ 	if (mtk_wed_device_active(&phy->dev->mt76.mmio.wed)) {
+-		sinfo->tx_bytes = msta->wcid.stats.tx_bytes;
++		sinfo->tx_bytes = mlink->wcid.stats.tx_bytes;
+ 		sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BYTES64);
+ 
+-		sinfo->rx_bytes = msta->wcid.stats.rx_bytes;
++		sinfo->rx_bytes = mlink->wcid.stats.rx_bytes;
+ 		sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BYTES64);
+ 
+-		sinfo->tx_packets = msta->wcid.stats.tx_packets;
++		sinfo->tx_packets = mlink->wcid.stats.tx_packets;
+ 		sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_PACKETS);
+ 
+-		sinfo->rx_packets = msta->wcid.stats.rx_packets;
++		sinfo->rx_packets = mlink->wcid.stats.rx_packets;
+ 		sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_PACKETS);
+ 	}
++out:
++	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
+ static void mt7996_sta_rc_work(void *data, struct ieee80211_sta *sta)
+ {
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_link_sta *mlink;
+ 	struct mt7996_dev *dev = msta->vif->dev;
+ 	u32 *changed = data;
+ 
++	rcu_read_lock();
++	mlink = rcu_dereference(msta->link[0]);
++
+ 	spin_lock_bh(&dev->mt76.sta_poll_lock);
+-	msta->changed |= *changed;
+-	if (list_empty(&msta->rc_list))
+-		list_add_tail(&msta->rc_list, &dev->sta_rc_list);
++	mlink->changed |= *changed;
++	if (list_empty(&mlink->rc_list))
++		list_add_tail(&mlink->rc_list, &dev->sta_rc_list);
+ 	spin_unlock_bh(&dev->mt76.sta_poll_lock);
++
++	rcu_read_unlock();
+ }
+ 
+ static void mt7996_sta_rc_update(struct ieee80211_hw *hw,
+@@ -1236,7 +1283,7 @@ static void mt7996_sta_rc_update(struct ieee80211_hw *hw,
+ 
+ 	if (!msta->vif) {
+ 		dev_warn(dev->mt76.dev, "Un-initialized STA %pM wcid %d in rc_work\n",
+-			 sta->addr, msta->wcid.idx);
++			 sta->addr, msta->deflink.wcid.idx);
+ 		return;
+ 	}
+ 
+@@ -1282,16 +1329,18 @@ static void mt7996_sta_set_4addr(struct ieee80211_hw *hw,
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct mt7996_bss_conf *mconf;
++	struct mt7996_link_sta *mlink;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 	mconf = mconf_dereference_protected(mvif, 0);
++	mlink = mlink_dereference_protected(msta, 0);
+ 
+ 	if (enabled)
+-		set_bit(MT_WCID_FLAG_4ADDR, &msta->wcid.flags);
++		set_bit(MT_WCID_FLAG_4ADDR, &mlink->wcid.flags);
+ 	else
+-		clear_bit(MT_WCID_FLAG_4ADDR, &msta->wcid.flags);
++		clear_bit(MT_WCID_FLAG_4ADDR, &mlink->wcid.flags);
+ 
+-	mt7996_mcu_wtbl_update_hdr_trans(dev, vif, mconf, sta);
++	mt7996_mcu_wtbl_update_hdr_trans(dev, vif, mconf, mlink);
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
+@@ -1304,16 +1353,18 @@ static void mt7996_sta_set_decap_offload(struct ieee80211_hw *hw,
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct mt7996_bss_conf *mconf;
++	struct mt7996_link_sta *mlink;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 	mconf = mconf_dereference_protected(mvif, 0);
++	mlink = mlink_dereference_protected(msta, 0);
+ 
+ 	if (enabled)
+-		set_bit(MT_WCID_FLAG_HDR_TRANS, &msta->wcid.flags);
++		set_bit(MT_WCID_FLAG_HDR_TRANS, &mlink->wcid.flags);
+ 	else
+-		clear_bit(MT_WCID_FLAG_HDR_TRANS, &msta->wcid.flags);
++		clear_bit(MT_WCID_FLAG_HDR_TRANS, &mlink->wcid.flags);
+ 
+-	mt7996_mcu_wtbl_update_hdr_trans(dev, vif, mconf, sta);
++	mt7996_mcu_wtbl_update_hdr_trans(dev, vif, mconf, mlink);
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
+@@ -1446,11 +1497,12 @@ static void mt7996_ethtool_worker(void *wi_data, struct ieee80211_sta *sta)
+ {
+ 	struct mt76_ethtool_worker_info *wi = wi_data;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_link_sta *mlink = &msta->deflink;
+ 
+ 	if (msta->vif->deflink.mt76.idx != wi->idx)
+ 		return;
+ 
+-	mt76_ethtool_worker(wi, &msta->wcid.stats, true);
++	mt76_ethtool_worker(wi, &mlink->wcid.stats, true);
+ }
+ 
+ static
+@@ -1553,10 +1605,12 @@ mt7996_twt_teardown_request(struct ieee80211_hw *hw,
+ 			    u8 flowid)
+ {
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_link_sta *mlink;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	mt7996_mac_twt_teardown_flow(dev, msta, flowid);
++	mlink = mlink_dereference_protected(msta, 0);
++	mt7996_mac_twt_teardown_flow(dev, mlink, flowid);
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
+@@ -1679,6 +1733,7 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw,
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_bss_conf *mconf = &mvif->deflink;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_link_sta *mlink = &msta->deflink;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	struct mtk_wed_device *wed = &dev->mt76.mmio.wed;
+@@ -1700,7 +1755,7 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw,
+ 	if (!mtk_wed_device_active(wed))
+ 		return -ENODEV;
+ 
+-	if (msta->wcid.idx > MT7996_WTBL_STA)
++	if (mlink->wcid.idx > MT7996_WTBL_STA)
+ 		return -EIO;
+ 
+ 	path->type = DEV_PATH_MTK_WDMA;
+@@ -1708,11 +1763,11 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw,
+ 	path->mtk_wdma.wdma_idx = wed->wdma_idx;
+ 	path->mtk_wdma.bss = mconf->mt76.idx;
+ 	path->mtk_wdma.queue = 0;
+-	path->mtk_wdma.wcid = msta->wcid.idx;
++	path->mtk_wdma.wcid = mlink->wcid.idx;
+ 
+ 	if (ieee80211_hw_check(hw, SUPPORTS_AMSDU_IN_AMPDU) &&
+ 	    mtk_wed_is_amsdu_supported(wed))
+-		path->mtk_wdma.amsdu = msta->wcid.amsdu;
++		path->mtk_wdma.amsdu = mlink->wcid.amsdu;
+ 	else
+ 		path->mtk_wdma.amsdu = 0;
+ 
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 2806aa0dd..8ef8506e1 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -117,13 +117,13 @@ mt7996_mcu_get_sta_nss(u16 mcs_map)
+ }
+ 
+ static void
+-mt7996_mcu_set_sta_he_mcs(struct ieee80211_sta *sta,
++mt7996_mcu_set_sta_he_mcs(struct ieee80211_link_sta *link_sta,
+ 			  struct mt7996_bss_conf *mconf,
+ 			  __le16 *he_mcs, u16 mcs_map)
+ {
+ 	enum nl80211_band band = mconf->phy->mt76->chandef.chan->band;
+ 	const u16 *mask = mconf->bitrate_mask.control[band].he_mcs;
+-	int nss, max_nss = sta->deflink.rx_nss > 3 ? 4 : sta->deflink.rx_nss;
++	int nss, max_nss = link_sta->rx_nss > 3 ? 4 : link_sta->rx_nss;
+ 
+ 	for (nss = 0; nss < max_nss; nss++) {
+ 		int mcs;
+@@ -166,11 +166,11 @@ mt7996_mcu_set_sta_he_mcs(struct ieee80211_sta *sta,
+ }
+ 
+ static void
+-mt7996_mcu_set_sta_vht_mcs(struct ieee80211_sta *sta, __le16 *vht_mcs,
+-			   const u16 *mask)
++mt7996_mcu_set_sta_vht_mcs(struct ieee80211_link_sta *link_sta,
++			   __le16 *vht_mcs, const u16 *mask)
+ {
+-	u16 mcs, mcs_map = le16_to_cpu(sta->deflink.vht_cap.vht_mcs.rx_mcs_map);
+-	int nss, max_nss = sta->deflink.rx_nss > 3 ? 4 : sta->deflink.rx_nss;
++	u16 mcs, mcs_map = le16_to_cpu(link_sta->vht_cap.vht_mcs.rx_mcs_map);
++	int nss, max_nss = link_sta->rx_nss > 3 ? 4 : link_sta->rx_nss;
+ 
+ 	for (nss = 0; nss < max_nss; nss++, mcs_map >>= 2) {
+ 		switch (mcs_map & 0x3) {
+@@ -192,13 +192,13 @@ mt7996_mcu_set_sta_vht_mcs(struct ieee80211_sta *sta, __le16 *vht_mcs,
+ }
+ 
+ static void
+-mt7996_mcu_set_sta_ht_mcs(struct ieee80211_sta *sta, u8 *ht_mcs,
++mt7996_mcu_set_sta_ht_mcs(struct ieee80211_link_sta *link_sta, u8 *ht_mcs,
+ 			  const u8 *mask)
+ {
+-	int nss, max_nss = sta->deflink.rx_nss > 3 ? 4 : sta->deflink.rx_nss;
++	int nss, max_nss = link_sta->rx_nss > 3 ? 4 : link_sta->rx_nss;
+ 
+ 	for (nss = 0; nss < max_nss; nss++)
+-		ht_mcs[nss] = sta->deflink.ht_cap.mcs.rx_mask[nss] & mask[nss];
++		ht_mcs[nss] = link_sta->ht_cap.mcs.rx_mask[nss] & mask[nss];
+ }
+ 
+ static int
+@@ -529,14 +529,14 @@ static inline void __mt7996_stat_to_netdev(struct mt76_phy *mphy,
+ 					   u32 tx_bytes, u32 rx_bytes,
+ 					   u32 tx_packets, u32 rx_packets)
+ {
+-	struct mt7996_sta *msta;
++	struct mt7996_link_sta *mlink;
+ 	struct ieee80211_vif *vif;
+ 	struct wireless_dev *wdev;
+ 
+ 	if (wiphy_ext_feature_isset(mphy->hw->wiphy,
+ 				    NL80211_EXT_FEATURE_STAS_COUNT)) {
+-		msta = container_of(wcid, struct mt7996_sta, wcid);
+-		vif = container_of((void *)msta->vif, struct ieee80211_vif,
++		mlink = container_of(wcid, struct mt7996_link_sta, wcid);
++		vif = container_of((void *)mlink->sta->vif, struct ieee80211_vif,
+ 				   drv_priv);
+ 		wdev = ieee80211_vif_to_wdev(vif);
+ 
+@@ -1234,10 +1234,10 @@ __mt7996_mcu_alloc_bss_req(struct mt76_dev *dev, struct mt76_vif *mvif, int len)
+ 
+ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy,
+ 			    struct ieee80211_bss_conf *conf,
+-			    struct mt7996_bss_conf *mconf, int enable)
++			    struct mt7996_bss_conf *mconf,
++			    struct mt7996_link_sta *mlink, int enable)
+ {
+ 	struct ieee80211_vif *vif = conf->vif;
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_dev *dev = phy->dev;
+ 	struct sk_buff *skb;
+ 
+@@ -1253,7 +1253,7 @@ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy,
+ 
+ 	/* bss_basic must be first */
+ 	mt7996_mcu_bss_basic_tlv(skb, conf, mconf, NULL, phy->mt76,
+-				 mvif->sta.wcid.idx, enable);
++				 mlink->wcid.idx, enable);
+ 	mt7996_mcu_bss_sec_tlv(skb, mconf);
+ 
+ 	if (vif->type == NL80211_IFTYPE_MONITOR)
+@@ -1333,9 +1333,10 @@ int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev,
+ {
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)params->sta->drv_priv;
+ 	struct mt7996_bss_conf *mconf = mconf_dereference_protected(msta->vif, 0);
++	struct mt7996_link_sta *mlink = mlink_dereference_protected(msta, 0);
+ 
+ 	if (enable && !params->amsdu)
+-		msta->wcid.amsdu = false;
++		mlink->wcid.amsdu = false;
+ 
+ 	return mt7996_mcu_sta_ba(dev, &mconf->mt76, params, enable, true);
+ }
+@@ -1353,15 +1354,15 @@ int mt7996_mcu_add_rx_ba(struct mt7996_dev *dev,
+ static void
+ mt7996_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *conf,
+ 		      struct mt7996_bss_conf *mconf,
+-		      struct ieee80211_sta *sta)
++		      struct ieee80211_link_sta *link_sta)
+ {
+-	struct ieee80211_he_cap_elem *elem = &sta->deflink.he_cap.he_cap_elem;
++	struct ieee80211_he_cap_elem *elem = &link_sta->he_cap.he_cap_elem;
+ 	struct ieee80211_he_mcs_nss_supp mcs_map;
+ 	struct sta_rec_he_v2 *he;
+ 	struct tlv *tlv;
+ 	int i = 0;
+ 
+-	if (!sta->deflink.he_cap.has_he)
++	if (!link_sta->he_cap.has_he)
+ 		return;
+ 
+ 	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_HE_V2, sizeof(*he));
+@@ -1378,21 +1379,21 @@ mt7996_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *conf,
+ 		u8p_replace_bits(&he->he_phy_cap[1], conf->he_ldpc,
+ 				 IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD);
+ 
+-	mcs_map = sta->deflink.he_cap.he_mcs_nss_supp;
+-	switch (sta->deflink.bandwidth) {
++	mcs_map = link_sta->he_cap.he_mcs_nss_supp;
++	switch (link_sta->bandwidth) {
+ 	case IEEE80211_STA_RX_BW_160:
+ 		if (elem->phy_cap_info[0] &
+ 		    IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G)
+-			mt7996_mcu_set_sta_he_mcs(sta, mconf,
++			mt7996_mcu_set_sta_he_mcs(link_sta, mconf,
+ 						  &he->max_nss_mcs[CMD_HE_MCS_BW8080],
+ 						  le16_to_cpu(mcs_map.rx_mcs_80p80));
+ 
+-		mt7996_mcu_set_sta_he_mcs(sta, mconf,
++		mt7996_mcu_set_sta_he_mcs(link_sta, mconf,
+ 					  &he->max_nss_mcs[CMD_HE_MCS_BW160],
+ 					  le16_to_cpu(mcs_map.rx_mcs_160));
+ 		fallthrough;
+ 	default:
+-		mt7996_mcu_set_sta_he_mcs(sta, mconf,
++		mt7996_mcu_set_sta_he_mcs(link_sta, mconf,
+ 					  &he->max_nss_mcs[CMD_HE_MCS_BW80],
+ 					  le16_to_cpu(mcs_map.rx_mcs_80));
+ 		break;
+@@ -1402,24 +1403,25 @@ mt7996_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *conf,
+ }
+ 
+ static void
+-mt7996_mcu_sta_he_6g_tlv(struct sk_buff *skb, struct ieee80211_sta *sta)
++mt7996_mcu_sta_he_6g_tlv(struct sk_buff *skb,
++			 struct ieee80211_link_sta *link_sta)
+ {
+ 	struct sta_rec_he_6g_capa *he_6g;
+ 	struct tlv *tlv;
+ 
+-	if (!sta->deflink.he_6ghz_capa.capa)
++	if (!link_sta->he_6ghz_capa.capa)
+ 		return;
+ 
+ 	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_HE_6G, sizeof(*he_6g));
+ 
+ 	he_6g = (struct sta_rec_he_6g_capa *)tlv;
+-	he_6g->capa = sta->deflink.he_6ghz_capa.capa;
++	he_6g->capa = link_sta->he_6ghz_capa.capa;
+ }
+ 
+ static void
+-mt7996_mcu_sta_eht_tlv(struct sk_buff *skb, struct ieee80211_sta *sta)
++mt7996_mcu_sta_eht_tlv(struct sk_buff *skb, struct ieee80211_link_sta *link_sta)
+ {
+-	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_sta *msta = (struct mt7996_sta *)link_sta->sta->drv_priv;
+ 	struct ieee80211_vif *vif = container_of((void *)msta->vif,
+ 						 struct ieee80211_vif, drv_priv);
+ 	struct ieee80211_eht_mcs_nss_supp *mcs_map;
+@@ -1427,11 +1429,11 @@ mt7996_mcu_sta_eht_tlv(struct sk_buff *skb, struct ieee80211_sta *sta)
+ 	struct sta_rec_eht *eht;
+ 	struct tlv *tlv;
+ 
+-	if (!sta->deflink.eht_cap.has_eht)
++	if (!link_sta->eht_cap.has_eht)
+ 		return;
+ 
+-	mcs_map = &sta->deflink.eht_cap.eht_mcs_nss_supp;
+-	elem = &sta->deflink.eht_cap.eht_cap_elem;
++	mcs_map = &link_sta->eht_cap.eht_mcs_nss_supp;
++	elem = &link_sta->eht_cap.eht_cap_elem;
+ 
+ 	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_EHT, sizeof(*eht));
+ 
+@@ -1442,7 +1444,7 @@ mt7996_mcu_sta_eht_tlv(struct sk_buff *skb, struct ieee80211_sta *sta)
+ 	eht->phy_cap_ext = cpu_to_le64(elem->phy_cap_info[8]);
+ 
+ 	if (vif->type != NL80211_IFTYPE_STATION &&
+-	    (sta->deflink.he_cap.he_cap_elem.phy_cap_info[0] &
++	    (link_sta->he_cap.he_cap_elem.phy_cap_info[0] &
+ 	     (IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G |
+ 	      IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G |
+ 	      IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G |
+@@ -1458,44 +1460,44 @@ mt7996_mcu_sta_eht_tlv(struct sk_buff *skb, struct ieee80211_sta *sta)
+ }
+ 
+ static void
+-mt7996_mcu_sta_ht_tlv(struct sk_buff *skb, struct ieee80211_sta *sta)
++mt7996_mcu_sta_ht_tlv(struct sk_buff *skb, struct ieee80211_link_sta *link_sta)
+ {
+ 	struct sta_rec_ht_uni *ht;
+ 	struct tlv *tlv;
+ 
+-	if (!sta->deflink.ht_cap.ht_supported)
++	if (!link_sta->ht_cap.ht_supported)
+ 		return;
+ 
+ 	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_HT, sizeof(*ht));
+ 
+ 	ht = (struct sta_rec_ht_uni *)tlv;
+-	ht->ht_cap = cpu_to_le16(sta->deflink.ht_cap.cap);
+-	ht->ampdu_param = u8_encode_bits(sta->deflink.ht_cap.ampdu_factor,
++	ht->ht_cap = cpu_to_le16(link_sta->ht_cap.cap);
++	ht->ampdu_param = u8_encode_bits(link_sta->ht_cap.ampdu_factor,
+ 					 IEEE80211_HT_AMPDU_PARM_FACTOR) |
+-			  u8_encode_bits(sta->deflink.ht_cap.ampdu_density,
++			  u8_encode_bits(link_sta->ht_cap.ampdu_density,
+ 					 IEEE80211_HT_AMPDU_PARM_DENSITY);
+ }
+ 
+ static void
+-mt7996_mcu_sta_vht_tlv(struct sk_buff *skb, struct ieee80211_sta *sta)
++mt7996_mcu_sta_vht_tlv(struct sk_buff *skb, struct ieee80211_link_sta *link_sta)
+ {
+ 	struct sta_rec_vht *vht;
+ 	struct tlv *tlv;
+ #ifdef CONFIG_MTK_VENDOR
+-	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_sta *msta = (struct mt7996_sta *)link_sta->sta->drv_priv;
+ 	struct mt7996_phy *phy = (struct mt7996_phy *)msta->vif->deflink.phy;
+ #endif
+ 
+ 	/* For 6G band, this tlv is necessary to let hw work normally */
+-	if (!sta->deflink.he_6ghz_capa.capa && !sta->deflink.vht_cap.vht_supported)
++	if (!link_sta->he_6ghz_capa.capa && !link_sta->vht_cap.vht_supported)
+ 		return;
+ 
+ 	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_VHT, sizeof(*vht));
+ 
+ 	vht = (struct sta_rec_vht *)tlv;
+-	vht->vht_cap = cpu_to_le32(sta->deflink.vht_cap.cap);
+-	vht->vht_rx_mcs_map = sta->deflink.vht_cap.vht_mcs.rx_mcs_map;
+-	vht->vht_tx_mcs_map = sta->deflink.vht_cap.vht_mcs.tx_mcs_map;
++	vht->vht_cap = cpu_to_le32(link_sta->vht_cap.cap);
++	vht->vht_rx_mcs_map = link_sta->vht_cap.vht_mcs.rx_mcs_map;
++	vht->vht_tx_mcs_map = link_sta->vht_cap.vht_mcs.tx_mcs_map;
+ #ifdef CONFIG_MTK_VENDOR
+ 	vht->rts_bw_sig = phy->rts_bw_sig;
+ #endif
+@@ -1503,9 +1505,10 @@ mt7996_mcu_sta_vht_tlv(struct sk_buff *skb, struct ieee80211_sta *sta)
+ 
+ static void
+ mt7996_mcu_sta_amsdu_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+-			 struct ieee80211_vif *vif, struct ieee80211_sta *sta)
++			 struct ieee80211_vif *vif,
++			 struct ieee80211_link_sta *link_sta,
++			 struct mt7996_link_sta *mlink)
+ {
+-	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct sta_rec_amsdu *amsdu;
+ 	struct tlv *tlv;
+ 
+@@ -1514,16 +1517,16 @@ mt7996_mcu_sta_amsdu_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 	    vif->type != NL80211_IFTYPE_AP)
+ 		return;
+ 
+-	if (!sta->deflink.agg.max_amsdu_len)
++	if (!link_sta->agg.max_amsdu_len)
+ 		return;
+ 
+ 	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_HW_AMSDU, sizeof(*amsdu));
+ 	amsdu = (struct sta_rec_amsdu *)tlv;
+ 	amsdu->max_amsdu_num = 8;
+ 	amsdu->amsdu_en = true;
+-	msta->wcid.amsdu = true;
++	mlink->wcid.amsdu = true;
+ 
+-	switch (sta->deflink.agg.max_amsdu_len) {
++	switch (link_sta->agg.max_amsdu_len) {
+ 	case IEEE80211_MAX_MPDU_LEN_VHT_11454:
+ 		amsdu->max_mpdu_size =
+ 			IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454;
+@@ -1542,10 +1545,10 @@ static void
+ mt7996_mcu_sta_muru_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 			struct ieee80211_bss_conf *conf,
+ 			struct mt7996_bss_conf *mconf,
+-			struct ieee80211_sta *sta)
++			struct ieee80211_link_sta *link_sta)
+ {
+ 	struct mt7996_phy *phy = mconf->phy;
+-	struct ieee80211_he_cap_elem *elem = &sta->deflink.he_cap.he_cap_elem;
++	struct ieee80211_he_cap_elem *elem = &link_sta->he_cap.he_cap_elem;
+ 	struct sta_rec_muru *muru;
+ 	struct tlv *tlv;
+ 
+@@ -1565,11 +1568,11 @@ mt7996_mcu_sta_muru_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 	muru->cfg.ofdma_dl_en = !!(phy->muru_onoff & OFDMA_DL);
+ 	muru->cfg.ofdma_ul_en = !!(phy->muru_onoff & OFDMA_UL);
+ 
+-	if (sta->deflink.vht_cap.vht_supported)
++	if (link_sta->vht_cap.vht_supported)
+ 		muru->mimo_dl.vht_mu_bfee =
+-			!!(sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE);
++			!!(link_sta->vht_cap.cap & IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE);
+ 
+-	if (!sta->deflink.he_cap.has_he)
++	if (!link_sta->he_cap.has_he)
+ 		return;
+ 
+ 	muru->mimo_dl.partial_bw_dl_mimo =
+@@ -1602,7 +1605,7 @@ mt7996_mcu_sta_muru_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ static inline bool
+ mt7996_is_ebf_supported(struct mt7996_phy *phy, struct ieee80211_bss_conf *conf,
+ 			struct mt7996_bss_conf *mconf,
+-			struct ieee80211_sta *sta, bool bfee)
++			struct ieee80211_link_sta *link_sta, bool bfee)
+ {
+ 	int sts = hweight16(phy->mt76->chainmask);
+ 
+@@ -1613,8 +1616,8 @@ mt7996_is_ebf_supported(struct mt7996_phy *phy, struct ieee80211_bss_conf *conf,
+ 	if (!bfee && sts < 2)
+ 		return false;
+ 
+-	if (sta->deflink.eht_cap.has_eht) {
+-		struct ieee80211_sta_eht_cap *pc = &sta->deflink.eht_cap;
++	if (link_sta->eht_cap.has_eht) {
++		struct ieee80211_sta_eht_cap *pc = &link_sta->eht_cap;
+ 		struct ieee80211_eht_cap_elem_fixed *pe = &pc->eht_cap_elem;
+ 
+ 		if (bfee)
+@@ -1625,8 +1628,8 @@ mt7996_is_ebf_supported(struct mt7996_phy *phy, struct ieee80211_bss_conf *conf,
+ 			       EHT_PHY(CAP0_SU_BEAMFORMER, pe->phy_cap_info[0]);
+ 	}
+ 
+-	if (sta->deflink.he_cap.has_he) {
+-		struct ieee80211_he_cap_elem *pe = &sta->deflink.he_cap.he_cap_elem;
++	if (link_sta->he_cap.has_he) {
++		struct ieee80211_he_cap_elem *pe = &link_sta->he_cap.he_cap_elem;
+ 
+ 		if (bfee)
+ 			return conf->he_su_beamformee &&
+@@ -1636,8 +1639,8 @@ mt7996_is_ebf_supported(struct mt7996_phy *phy, struct ieee80211_bss_conf *conf,
+ 			       HE_PHY(CAP4_SU_BEAMFORMEE, pe->phy_cap_info[4]);
+ 	}
+ 
+-	if (sta->deflink.vht_cap.vht_supported) {
+-		u32 cap = sta->deflink.vht_cap.cap;
++	if (link_sta->vht_cap.vht_supported) {
++		u32 cap = link_sta->vht_cap.cap;
+ 
+ 		if (bfee)
+ 			return conf->vht_su_beamformee &&
+@@ -1660,10 +1663,10 @@ mt7996_mcu_sta_sounding_rate(struct sta_rec_bf *bf)
+ }
+ 
+ static void
+-mt7996_mcu_sta_bfer_ht(struct ieee80211_sta *sta, struct mt7996_phy *phy,
+-		       struct sta_rec_bf *bf)
++mt7996_mcu_sta_bfer_ht(struct ieee80211_link_sta *link_sta,
++		       struct mt7996_phy *phy, struct sta_rec_bf *bf)
+ {
+-	struct ieee80211_mcs_info *mcs = &sta->deflink.ht_cap.mcs;
++	struct ieee80211_mcs_info *mcs = &link_sta->ht_cap.mcs;
+ 	u8 n = 0;
+ 
+ 	bf->tx_mode = MT_PHY_TYPE_HT;
+@@ -1685,10 +1688,11 @@ mt7996_mcu_sta_bfer_ht(struct ieee80211_sta *sta, struct mt7996_phy *phy,
+ }
+ 
+ static void
+-mt7996_mcu_sta_bfer_vht(struct ieee80211_sta *sta, struct mt7996_phy *phy,
+-			struct sta_rec_bf *bf, bool explicit)
++mt7996_mcu_sta_bfer_vht(struct ieee80211_link_sta *link_sta,
++			struct mt7996_phy *phy, struct sta_rec_bf *bf,
++			bool explicit)
+ {
+-	struct ieee80211_sta_vht_cap *pc = &sta->deflink.vht_cap;
++	struct ieee80211_sta_vht_cap *pc = &link_sta->vht_cap;
+ 	struct ieee80211_sta_vht_cap *vc = &phy->mt76->sband_5g.sband.vht_cap;
+ 	u16 mcs_map = le16_to_cpu(pc->vht_mcs.rx_mcs_map);
+ 	u8 nss_mcs = mt7996_mcu_get_sta_nss(mcs_map);
+@@ -1709,23 +1713,24 @@ mt7996_mcu_sta_bfer_vht(struct ieee80211_sta *sta, struct mt7996_phy *phy,
+ 		bf->ncol = min_t(u8, nss_mcs, bf->nrow);
+ 		bf->ibf_ncol = bf->ncol;
+ 
+-		if (sta->deflink.bandwidth == IEEE80211_STA_RX_BW_160)
++		if (link_sta->bandwidth == IEEE80211_STA_RX_BW_160)
+ 			bf->nrow = 1;
+ 	} else {
+ 		bf->nrow = tx_ant;
+ 		bf->ncol = min_t(u8, nss_mcs, bf->nrow);
+ 		bf->ibf_ncol = nss_mcs;
+ 
+-		if (sta->deflink.bandwidth == IEEE80211_STA_RX_BW_160)
++		if (link_sta->bandwidth == IEEE80211_STA_RX_BW_160)
+ 			bf->ibf_nrow = 1;
+ 	}
+ }
+ 
+ static void
+-mt7996_mcu_sta_bfer_he(struct ieee80211_sta *sta, struct ieee80211_vif *vif,
+-		       struct mt7996_phy *phy, struct sta_rec_bf *bf)
++mt7996_mcu_sta_bfer_he(struct ieee80211_link_sta *link_sta,
++		       struct ieee80211_vif *vif, struct mt7996_phy *phy,
++		       struct sta_rec_bf *bf)
+ {
+-	struct ieee80211_sta_he_cap *pc = &sta->deflink.he_cap;
++	struct ieee80211_sta_he_cap *pc = &link_sta->he_cap;
+ 	struct ieee80211_he_cap_elem *pe = &pc->he_cap_elem;
+ 	const struct ieee80211_sta_he_cap *vc =
+ 		mt76_connac_get_he_phy_cap(phy->mt76, vif);
+@@ -1750,7 +1755,7 @@ mt7996_mcu_sta_bfer_he(struct ieee80211_sta *sta, struct ieee80211_vif *vif,
+ 	bf->ncol = min_t(u8, nss_mcs, bf->nrow);
+ 	bf->ibf_ncol = bf->ncol;
+ 
+-	if (sta->deflink.bandwidth != IEEE80211_STA_RX_BW_160)
++	if (link_sta->bandwidth != IEEE80211_STA_RX_BW_160)
+ 		return;
+ 
+ 	/* go over for 160MHz and 80p80 */
+@@ -1782,10 +1787,11 @@ mt7996_mcu_sta_bfer_he(struct ieee80211_sta *sta, struct ieee80211_vif *vif,
+ }
+ 
+ static void
+-mt7996_mcu_sta_bfer_eht(struct ieee80211_sta *sta, struct ieee80211_vif *vif,
+-			struct mt7996_phy *phy, struct sta_rec_bf *bf)
++mt7996_mcu_sta_bfer_eht(struct ieee80211_link_sta *link_sta,
++			struct ieee80211_vif *vif, struct mt7996_phy *phy,
++			struct sta_rec_bf *bf)
+ {
+-	struct ieee80211_sta_eht_cap *pc = &sta->deflink.eht_cap;
++	struct ieee80211_sta_eht_cap *pc = &link_sta->eht_cap;
+ 	struct ieee80211_eht_cap_elem_fixed *pe = &pc->eht_cap_elem;
+ 	struct ieee80211_eht_mcs_nss_supp *eht_nss = &pc->eht_mcs_nss_supp;
+ 	const struct ieee80211_sta_eht_cap *vc =
+@@ -1808,10 +1814,10 @@ mt7996_mcu_sta_bfer_eht(struct ieee80211_sta *sta, struct ieee80211_vif *vif,
+ 	bf->ncol = min_t(u8, nss_mcs, bf->nrow);
+ 	bf->ibf_ncol = bf->ncol;
+ 
+-	if (sta->deflink.bandwidth < IEEE80211_STA_RX_BW_160)
++	if (link_sta->bandwidth < IEEE80211_STA_RX_BW_160)
+ 		return;
+ 
+-	switch (sta->deflink.bandwidth) {
++	switch (link_sta->bandwidth) {
+ 	case IEEE80211_STA_RX_BW_160:
+ 		snd_dim = EHT_PHY(CAP2_SOUNDING_DIM_160MHZ_MASK, ve->phy_cap_info[2]);
+ 		sts = EHT_PHY(CAP1_BEAMFORMEE_SS_160MHZ_MASK, pe->phy_cap_info[1]);
+@@ -1840,7 +1846,7 @@ mt7996_mcu_sta_bfer_eht(struct ieee80211_sta *sta, struct ieee80211_vif *vif,
+ static void
+ mt7996_mcu_sta_bfer_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 			struct ieee80211_bss_conf *conf, struct mt7996_bss_conf *mconf,
+-			struct ieee80211_sta *sta)
++			struct ieee80211_link_sta *link_sta)
+ {
+ 	struct mt7996_phy *phy = mconf->phy;
+ 	int tx_ant = hweight8(phy->mt76->chainmask) - 1;
+@@ -1854,10 +1860,10 @@ mt7996_mcu_sta_bfer_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 	};
+ 	bool ebf;
+ 
+-	if (!(sta->deflink.ht_cap.ht_supported || sta->deflink.he_cap.has_he))
++	if (!(link_sta->ht_cap.ht_supported || link_sta->he_cap.has_he))
+ 		return;
+ 
+-	ebf = mt7996_is_ebf_supported(phy, conf, mconf, sta, false);
++	ebf = mt7996_is_ebf_supported(phy, conf, mconf, link_sta, false);
+ 	if (!ebf && !dev->ibf)
+ 		return;
+ 
+@@ -1868,23 +1874,23 @@ mt7996_mcu_sta_bfer_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 	 * vht: support eBF and iBF
+ 	 * ht: iBF only, since mac80211 lacks of eBF support
+ 	 */
+-	if (sta->deflink.eht_cap.has_eht && ebf)
+-		mt7996_mcu_sta_bfer_eht(sta, conf->vif, phy, bf);
+-	else if (sta->deflink.he_cap.has_he && ebf)
+-		mt7996_mcu_sta_bfer_he(sta, conf->vif, phy, bf);
+-	else if (sta->deflink.vht_cap.vht_supported)
+-		mt7996_mcu_sta_bfer_vht(sta, phy, bf, ebf);
+-	else if (sta->deflink.ht_cap.ht_supported)
+-		mt7996_mcu_sta_bfer_ht(sta, phy, bf);
++	if (link_sta->eht_cap.has_eht && ebf)
++		mt7996_mcu_sta_bfer_eht(link_sta, conf->vif, phy, bf);
++	else if (link_sta->he_cap.has_he && ebf)
++		mt7996_mcu_sta_bfer_he(link_sta, conf->vif, phy, bf);
++	else if (link_sta->vht_cap.vht_supported)
++		mt7996_mcu_sta_bfer_vht(link_sta, phy, bf, ebf);
++	else if (link_sta->ht_cap.ht_supported)
++		mt7996_mcu_sta_bfer_ht(link_sta, phy, bf);
+ 	else
+ 		return;
+ 
+ 	bf->bf_cap = ebf ? ebf : dev->ibf << 1;
+-	bf->bw = sta->deflink.bandwidth;
+-	bf->ibf_dbw = sta->deflink.bandwidth;
++	bf->bw = link_sta->bandwidth;
++	bf->ibf_dbw = link_sta->bandwidth;
+ 	bf->ibf_nrow = tx_ant;
+ 
+-	if (!ebf && sta->deflink.bandwidth <= IEEE80211_STA_RX_BW_40 && !bf->ncol)
++	if (!ebf && link_sta->bandwidth <= IEEE80211_STA_RX_BW_40 && !bf->ncol)
+ 		bf->ibf_timeout = 0x48;
+ 	else
+ 		bf->ibf_timeout = 0x18;
+@@ -1894,7 +1900,7 @@ mt7996_mcu_sta_bfer_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 	else
+ 		bf->mem_20m = matrix[bf->nrow][bf->ncol];
+ 
+-	switch (sta->deflink.bandwidth) {
++	switch (link_sta->bandwidth) {
+ 	case IEEE80211_STA_RX_BW_160:
+ 	case IEEE80211_STA_RX_BW_80:
+ 		bf->mem_total = bf->mem_20m * 2;
+@@ -1911,7 +1917,8 @@ mt7996_mcu_sta_bfer_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ static void
+ mt7996_mcu_sta_bfee_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 			struct ieee80211_bss_conf *conf,
+-			struct mt7996_bss_conf *mconf, struct ieee80211_sta *sta)
++			struct mt7996_bss_conf *mconf,
++			struct ieee80211_link_sta *link_sta)
+ {
+ 	struct mt7996_phy *phy = mconf->phy;
+ 	int tx_ant = hweight8(phy->mt76->antenna_mask) - 1;
+@@ -1919,22 +1926,22 @@ mt7996_mcu_sta_bfee_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 	struct tlv *tlv;
+ 	u8 nrow = 0;
+ 
+-	if (!(sta->deflink.vht_cap.vht_supported || sta->deflink.he_cap.has_he))
++	if (!(link_sta->vht_cap.vht_supported || link_sta->he_cap.has_he))
+ 		return;
+ 
+-	if (!mt7996_is_ebf_supported(phy, conf, mconf, sta, false))
++	if (!mt7996_is_ebf_supported(phy, conf, mconf, link_sta, false))
+ 		return;
+ 
+ 	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_BFEE, sizeof(*bfee));
+ 	bfee = (struct sta_rec_bfee *)tlv;
+ 
+-	if (sta->deflink.he_cap.has_he) {
+-		struct ieee80211_he_cap_elem *pe = &sta->deflink.he_cap.he_cap_elem;
++	if (link_sta->he_cap.has_he) {
++		struct ieee80211_he_cap_elem *pe = &link_sta->he_cap.he_cap_elem;
+ 
+ 		nrow = HE_PHY(CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_MASK,
+ 			      pe->phy_cap_info[5]);
+-	} else if (sta->deflink.vht_cap.vht_supported) {
+-		struct ieee80211_sta_vht_cap *pc = &sta->deflink.vht_cap;
++	} else if (link_sta->vht_cap.vht_supported) {
++		struct ieee80211_sta_vht_cap *pc = &link_sta->vht_cap;
+ 
+ 		nrow = FIELD_GET(IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_MASK,
+ 				 pc->cap);
+@@ -1971,25 +1978,24 @@ mt7996_mcu_sta_hdrt_tlv(struct mt7996_dev *dev, struct sk_buff *skb)
+ static void
+ mt7996_mcu_sta_hdr_trans_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 			     struct ieee80211_vif *vif,
+-			     struct ieee80211_sta *sta)
++			     struct mt7996_link_sta *mlink)
+ {
+ 	struct sta_rec_hdr_trans *hdr_trans;
+-	struct mt76_wcid *wcid;
++	struct mt76_wcid *wcid = &mlink->wcid;
+ 	struct tlv *tlv;
+ 
+ 	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_HDR_TRANS, sizeof(*hdr_trans));
+ 	hdr_trans = (struct sta_rec_hdr_trans *)tlv;
+ 	hdr_trans->dis_rx_hdr_tran = true;
+ 
++	if (!wcid->sta)
++		return;
++
+ 	if (vif->type == NL80211_IFTYPE_STATION)
+ 		hdr_trans->to_ds = true;
+ 	else
+ 		hdr_trans->from_ds = true;
+ 
+-	if (!sta)
+-		return;
+-
+-	wcid = (struct mt76_wcid *)sta->drv_priv;
+ 	hdr_trans->dis_rx_hdr_tran = !test_bit(MT_WCID_FLAG_HDR_TRANS, &wcid->flags);
+ 	if (test_bit(MT_WCID_FLAG_4ADDR, &wcid->flags)) {
+ 		hdr_trans->to_ds = true;
+@@ -2046,16 +2052,17 @@ int mt7996_mcu_set_fixed_rate_ctrl(struct mt7996_dev *dev,
+ 
+ int mt7996_mcu_set_fixed_field(struct mt7996_dev *dev,
+ 			       struct mt7996_bss_conf *mconf,
+-			       struct ieee80211_sta *sta, void *data, u32 field)
++			       struct ieee80211_link_sta *link_sta,
++			       struct mt7996_link_sta *mlink, void *data,
++			       u32 field)
+ {
+-	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct sta_phy_uni *phy = data;
+ 	struct sta_rec_ra_fixed_uni *ra;
+ 	struct sk_buff *skb;
+ 	struct tlv *tlv;
+ 
+ 	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76,
+-					      &msta->wcid,
++					      &mlink->wcid,
+ 					      MT7996_STA_UPDATE_MAX_SIZE);
+ 	if (IS_ERR(skb))
+ 		return PTR_ERR(skb);
+@@ -2074,7 +2081,7 @@ int mt7996_mcu_set_fixed_field(struct mt7996_dev *dev,
+ 			ra->phy = *phy;
+ 		break;
+ 	case RATE_PARAM_MMPS_UPDATE:
+-		ra->mmps_mode = mt7996_mcu_get_mmps_mode(sta->deflink.smps_mode);
++		ra->mmps_mode = mt7996_mcu_get_mmps_mode(link_sta->smps_mode);
+ 		break;
+ 	default:
+ 		break;
+@@ -2089,7 +2096,8 @@ static int
+ mt7996_mcu_add_rate_ctrl_fixed(struct mt7996_dev *dev,
+ 			       struct ieee80211_bss_conf *conf,
+ 			       struct mt7996_bss_conf *mconf,
+-			       struct ieee80211_sta *sta)
++			       struct ieee80211_link_sta *link_sta,
++			       struct mt7996_link_sta *mlink)
+ {
+ 	struct cfg80211_chan_def *chandef = &mconf->phy->mt76->chandef;
+ 	struct cfg80211_bitrate_mask *mask = &mconf->bitrate_mask;
+@@ -2113,11 +2121,11 @@ mt7996_mcu_add_rate_ctrl_fixed(struct mt7996_dev *dev,
+ 		}								\
+ 	} while (0)
+ 
+-	if (sta->deflink.he_cap.has_he) {
++	if (link_sta->he_cap.has_he) {
+ 		__sta_phy_bitrate_mask_check(he_mcs, he_gi, 0, 1);
+-	} else if (sta->deflink.vht_cap.vht_supported) {
++	} else if (link_sta->vht_cap.vht_supported) {
+ 		__sta_phy_bitrate_mask_check(vht_mcs, gi, 0, 0);
+-	} else if (sta->deflink.ht_cap.ht_supported) {
++	} else if (link_sta->ht_cap.ht_supported) {
+ 		__sta_phy_bitrate_mask_check(ht_mcs, gi, 1, 0);
+ 	} else {
+ 		nrates = hweight32(mask->control[band].legacy);
+@@ -2134,8 +2142,8 @@ mt7996_mcu_add_rate_ctrl_fixed(struct mt7996_dev *dev,
+ 
+ 	/* fixed single rate */
+ 	if (nrates == 1) {
+-		ret = mt7996_mcu_set_fixed_field(dev, mconf, sta, &phy,
+-						 RATE_PARAM_FIXED_MCS);
++		ret = mt7996_mcu_set_fixed_field(dev, mconf, link_sta, mlink,
++						 &phy, RATE_PARAM_FIXED_MCS);
+ 		if (ret)
+ 			return ret;
+ 	}
+@@ -2143,29 +2151,28 @@ mt7996_mcu_add_rate_ctrl_fixed(struct mt7996_dev *dev,
+ 	/* fixed GI */
+ 	if (mask->control[band].gi != NL80211_TXRATE_DEFAULT_GI ||
+ 	    mask->control[band].he_gi != GENMASK(7, 0)) {
+-		struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 		u32 addr;
+ 
+ 		/* firmware updates only TXCMD but doesn't take WTBL into
+ 		 * account, so driver should update here to reflect the
+ 		 * actual txrate hardware sends out.
+ 		 */
+-		addr = mt7996_mac_wtbl_lmac_addr(dev, msta->wcid.idx, 7);
+-		if (sta->deflink.he_cap.has_he)
++		addr = mt7996_mac_wtbl_lmac_addr(dev, mlink->wcid.idx, 7);
++		if (link_sta->he_cap.has_he)
+ 			mt76_rmw_field(dev, addr, GENMASK(31, 24), phy.sgi);
+ 		else
+ 			mt76_rmw_field(dev, addr, GENMASK(15, 12), phy.sgi);
+ 
+-		ret = mt7996_mcu_set_fixed_field(dev, mconf, sta, &phy,
+-						 RATE_PARAM_FIXED_GI);
++		ret = mt7996_mcu_set_fixed_field(dev, mconf, link_sta, mlink,
++						 &phy, RATE_PARAM_FIXED_GI);
+ 		if (ret)
+ 			return ret;
+ 	}
+ 
+ 	/* fixed HE_LTF */
+ 	if (mask->control[band].he_ltf != GENMASK(7, 0)) {
+-		ret = mt7996_mcu_set_fixed_field(dev, mconf, sta, &phy,
+-						 RATE_PARAM_FIXED_HE_LTF);
++		ret = mt7996_mcu_set_fixed_field(dev, mconf, link_sta, mlink,
++						 &phy, RATE_PARAM_FIXED_HE_LTF);
+ 		if (ret)
+ 			return ret;
+ 	}
+@@ -2177,7 +2184,7 @@ static void
+ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev,
+ 			     struct ieee80211_bss_conf *conf,
+ 			     struct mt7996_bss_conf *mconf,
+-			     struct ieee80211_sta *sta)
++			     struct ieee80211_link_sta *link_sta)
+ {
+ #define INIT_RCPI 180
+ 	struct mt76_phy *mphy = mconf->phy->mt76;
+@@ -2186,20 +2193,20 @@ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev,
+ 	enum nl80211_band band = chandef->chan->band;
+ 	struct sta_rec_ra_uni *ra;
+ 	struct tlv *tlv;
+-	u32 supp_rate = sta->deflink.supp_rates[band];
+-	u32 cap = sta->wme ? STA_CAP_WMM : 0;
++	u32 supp_rate = link_sta->supp_rates[band];
++	u32 cap = link_sta->sta->wme ? STA_CAP_WMM : 0;
+ 
+ 	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_RA, sizeof(*ra));
+ 	ra = (struct sta_rec_ra_uni *)tlv;
+ 
+ 	ra->valid = true;
+ 	ra->auto_rate = true;
+-	ra->phy_mode = mt76_connac_get_phy_mode(mphy, conf->vif, band, sta);
++	ra->phy_mode = mt76_connac_get_phy_mode(mphy, conf->vif, band, link_sta);
+ 	ra->channel = chandef->chan->hw_value;
+-	ra->bw = (sta->deflink.bandwidth == IEEE80211_STA_RX_BW_320) ?
+-		 CMD_CBW_320MHZ : sta->deflink.bandwidth;
++	ra->bw = (link_sta->bandwidth == IEEE80211_STA_RX_BW_320) ?
++		 CMD_CBW_320MHZ : link_sta->bandwidth;
+ 	ra->phy.bw = ra->bw;
+-	ra->mmps_mode = mt7996_mcu_get_mmps_mode(sta->deflink.smps_mode);
++	ra->mmps_mode = mt7996_mcu_get_mmps_mode(link_sta->smps_mode);
+ 
+ 	if (supp_rate) {
+ 		supp_rate &= mask->control[band].legacy;
+@@ -2219,60 +2226,60 @@ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev,
+ 		}
+ 	}
+ 
+-	if (sta->deflink.ht_cap.ht_supported) {
++	if (link_sta->ht_cap.ht_supported) {
+ 		ra->supp_mode |= MODE_HT;
+-		ra->af = sta->deflink.ht_cap.ampdu_factor;
+-		ra->ht_gf = !!(sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_GRN_FLD);
++		ra->af = link_sta->ht_cap.ampdu_factor;
++		ra->ht_gf = !!(link_sta->ht_cap.cap & IEEE80211_HT_CAP_GRN_FLD);
+ 
+ 		cap |= STA_CAP_HT;
+-		if (sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_SGI_20)
++		if (link_sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20)
+ 			cap |= STA_CAP_SGI_20;
+-		if (sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_SGI_40)
++		if (link_sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40)
+ 			cap |= STA_CAP_SGI_40;
+-		if (sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_TX_STBC)
++		if (link_sta->ht_cap.cap & IEEE80211_HT_CAP_TX_STBC)
+ 			cap |= STA_CAP_TX_STBC;
+-		if (sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_RX_STBC)
++		if (link_sta->ht_cap.cap & IEEE80211_HT_CAP_RX_STBC)
+ 			cap |= STA_CAP_RX_STBC;
+ 		if (conf->ht_ldpc &&
+-		    (sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_LDPC_CODING))
++		    (link_sta->ht_cap.cap & IEEE80211_HT_CAP_LDPC_CODING))
+ 			cap |= STA_CAP_LDPC;
+ 
+-		mt7996_mcu_set_sta_ht_mcs(sta, ra->ht_mcs,
++		mt7996_mcu_set_sta_ht_mcs(link_sta, ra->ht_mcs,
+ 					  mask->control[band].ht_mcs);
+ 		ra->supp_ht_mcs = *(__le32 *)ra->ht_mcs;
+ 	}
+ 
+-	if (sta->deflink.vht_cap.vht_supported) {
++	if (link_sta->vht_cap.vht_supported) {
+ 		u8 af;
+ 
+ 		ra->supp_mode |= MODE_VHT;
+ 		af = FIELD_GET(IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK,
+-			       sta->deflink.vht_cap.cap);
++			       link_sta->vht_cap.cap);
+ 		ra->af = max_t(u8, ra->af, af);
+ 
+ 		cap |= STA_CAP_VHT;
+-		if (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_SHORT_GI_80)
++		if (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_SHORT_GI_80)
+ 			cap |= STA_CAP_VHT_SGI_80;
+-		if (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_SHORT_GI_160)
++		if (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_SHORT_GI_160)
+ 			cap |= STA_CAP_VHT_SGI_160;
+-		if (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_TXSTBC)
++		if (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_TXSTBC)
+ 			cap |= STA_CAP_VHT_TX_STBC;
+-		if (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_1)
++		if (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_1)
+ 			cap |= STA_CAP_VHT_RX_STBC;
+ 		if (conf->vht_ldpc &&
+-		    (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_RXLDPC))
++		    (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_RXLDPC))
+ 			cap |= STA_CAP_VHT_LDPC;
+ 
+-		mt7996_mcu_set_sta_vht_mcs(sta, ra->supp_vht_mcs,
++		mt7996_mcu_set_sta_vht_mcs(link_sta, ra->supp_vht_mcs,
+ 					   mask->control[band].vht_mcs);
+ 	}
+ 
+-	if (sta->deflink.he_cap.has_he) {
++	if (link_sta->he_cap.has_he) {
+ 		ra->supp_mode |= MODE_HE;
+ 		cap |= STA_CAP_HE;
+ 
+-		if (sta->deflink.he_6ghz_capa.capa)
+-			ra->af = le16_get_bits(sta->deflink.he_6ghz_capa.capa,
++		if (link_sta->he_6ghz_capa.capa)
++			ra->af = le16_get_bits(link_sta->he_6ghz_capa.capa,
+ 					       IEEE80211_HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP);
+ 	}
+ 	ra->sta_cap = cpu_to_le32(cap);
+@@ -2283,14 +2290,14 @@ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev,
+ int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev,
+ 			     struct ieee80211_bss_conf *conf,
+ 			     struct mt7996_bss_conf *mconf,
+-			     struct ieee80211_sta *sta, bool changed)
++			     struct ieee80211_link_sta *link_sta,
++			     struct mt7996_link_sta *mlink, bool changed)
+ {
+-	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct sk_buff *skb;
+ 	int ret;
+ 
+ 	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76,
+-					      &msta->wcid,
++					      &mlink->wcid,
+ 					      MT7996_STA_UPDATE_MAX_SIZE);
+ 	if (IS_ERR(skb))
+ 		return PTR_ERR(skb);
+@@ -2300,26 +2307,27 @@ int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev,
+ 	 * update sta_rec_he here.
+ 	 */
+ 	if (changed)
+-		mt7996_mcu_sta_he_tlv(skb, conf, mconf, sta);
++		mt7996_mcu_sta_he_tlv(skb, conf, mconf, link_sta);
+ 
+ 	/* sta_rec_ra accommodates BW, NSS and only MCS range format
+ 	 * i.e 0-{7,8,9} for VHT.
+ 	 */
+-	mt7996_mcu_sta_rate_ctrl_tlv(skb, dev, conf, mconf, sta);
++	mt7996_mcu_sta_rate_ctrl_tlv(skb, dev, conf, mconf, link_sta);
+ 
+ 	ret = mt76_mcu_skb_send_msg(&dev->mt76, skb,
+ 				    MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true);
+ 	if (ret)
+ 		return ret;
+ 
+-	return mt7996_mcu_add_rate_ctrl_fixed(dev, conf, mconf, sta);
++	return mt7996_mcu_add_rate_ctrl_fixed(dev, conf, mconf, link_sta, mlink);
+ }
+ 
+ static int
+-mt7996_mcu_sta_init_vow(struct mt7996_bss_conf *mconf, struct mt7996_sta *msta)
++mt7996_mcu_sta_init_vow(struct mt7996_bss_conf *mconf,
++			struct mt7996_link_sta *mlink)
+ {
+ 	struct mt7996_phy *phy = mconf->phy;
+-	struct mt7996_vow_sta_ctrl *vow = &msta->vow;
++	struct mt7996_vow_sta_ctrl *vow = &mlink->vow;
+ 	u8 omac_idx = mconf->mt76.omac_idx;
+ 	int ret;
+ 
+@@ -2337,73 +2345,70 @@ mt7996_mcu_sta_init_vow(struct mt7996_bss_conf *mconf, struct mt7996_sta *msta)
+ 	vow->drr_quantum[IEEE80211_AC_BE] = VOW_DRR_QUANTUM_IDX2;
+ 	vow->drr_quantum[IEEE80211_AC_BK] = VOW_DRR_QUANTUM_IDX2;
+ 
+-	ret = mt7996_mcu_set_vow_drr_ctrl(phy, mconf, msta, VOW_DRR_CTRL_STA_BSS_GROUP);
++	ret = mt7996_mcu_set_vow_drr_ctrl(phy, mconf, mlink, VOW_DRR_CTRL_STA_BSS_GROUP);
+ 	if (ret)
+ 		return ret;
+ 
+-	ret = mt7996_mcu_set_vow_drr_ctrl(phy, mconf, msta, VOW_DRR_CTRL_STA_PAUSE);
++	ret = mt7996_mcu_set_vow_drr_ctrl(phy, mconf, mlink, VOW_DRR_CTRL_STA_PAUSE);
+ 	if (ret)
+ 		return ret;
+ 
+-	return mt7996_mcu_set_vow_drr_ctrl(phy, mconf, msta, VOW_DRR_CTRL_STA_ALL);
++	return mt7996_mcu_set_vow_drr_ctrl(phy, mconf, mlink, VOW_DRR_CTRL_STA_ALL);
+ }
+ 
+ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_bss_conf *conf,
+-		       struct mt7996_bss_conf *mconf, struct ieee80211_sta *sta,
+-		       bool enable, bool newly)
++		       struct mt7996_bss_conf *mconf,
++		       struct ieee80211_link_sta *link_sta,
++		       struct mt7996_link_sta *mlink, bool enable, bool newly)
+ {
+ 	struct ieee80211_vif *vif = conf->vif;
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_sta *msta;
+ 	struct sk_buff *skb;
+ 	int ret;
+ 
+-	msta = sta ? (struct mt7996_sta *)sta->drv_priv : &mvif->sta;
+-
+ 	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76,
+-					      &msta->wcid,
++					      &mlink->wcid,
+ 					      MT7996_STA_UPDATE_MAX_SIZE);
+ 	if (IS_ERR(skb))
+ 		return PTR_ERR(skb);
+ 
+ 	/* starec basic */
+-	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, skb, vif, sta, enable, newly);
++	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, skb, vif, link_sta, enable, newly);
+ 
+ 	if (!enable)
+ 		goto out;
+ 
+ 	/* starec hdr trans */
+-	mt7996_mcu_sta_hdr_trans_tlv(dev, skb, vif, sta);
++	mt7996_mcu_sta_hdr_trans_tlv(dev, skb, vif, mlink);
+ 	/* starec tx proc */
+ 	mt7996_mcu_sta_tx_proc_tlv(skb);
+ 
+ 	/* tag order is in accordance with firmware dependency. */
+-	if (sta) {
++	if (link_sta) {
+ 		/* starec hdrt mode */
+ 		mt7996_mcu_sta_hdrt_tlv(dev, skb);
+ 		/* starec bfer */
+-		mt7996_mcu_sta_bfer_tlv(dev, skb, conf, mconf, sta);
++		mt7996_mcu_sta_bfer_tlv(dev, skb, conf, mconf, link_sta);
+ 		/* starec ht */
+-		mt7996_mcu_sta_ht_tlv(skb, sta);
++		mt7996_mcu_sta_ht_tlv(skb, link_sta);
+ 		/* starec vht */
+-		mt7996_mcu_sta_vht_tlv(skb, sta);
++		mt7996_mcu_sta_vht_tlv(skb, link_sta);
+ 		/* starec uapsd */
+-		mt76_connac_mcu_sta_uapsd(skb, vif, sta);
++		mt76_connac_mcu_sta_uapsd(skb, vif, link_sta->sta);
+ 		/* starec amsdu */
+-		mt7996_mcu_sta_amsdu_tlv(dev, skb, vif, sta);
++		mt7996_mcu_sta_amsdu_tlv(dev, skb, vif, link_sta, mlink);
+ 		/* starec he */
+-		mt7996_mcu_sta_he_tlv(skb, conf, mconf, sta);
++		mt7996_mcu_sta_he_tlv(skb, conf, mconf, link_sta);
+ 		/* starec he 6g*/
+-		mt7996_mcu_sta_he_6g_tlv(skb, sta);
++		mt7996_mcu_sta_he_6g_tlv(skb, link_sta);
+ 		/* starec eht */
+-		mt7996_mcu_sta_eht_tlv(skb, sta);
++		mt7996_mcu_sta_eht_tlv(skb, link_sta);
+ 		/* starec muru */
+-		mt7996_mcu_sta_muru_tlv(dev, skb, conf, mconf, sta);
++		mt7996_mcu_sta_muru_tlv(dev, skb, conf, mconf, link_sta);
+ 		/* starec bfee */
+-		mt7996_mcu_sta_bfee_tlv(dev, skb, conf, mconf, sta);
++		mt7996_mcu_sta_bfee_tlv(dev, skb, conf, mconf, link_sta);
+ 	}
+ 
+-	ret = mt7996_mcu_sta_init_vow(mconf, msta);
++	ret = mt7996_mcu_sta_init_vow(mconf, mlink);
+ 	if (ret) {
+ 		dev_kfree_skb(skb);
+ 		return ret;
+@@ -2479,16 +2484,16 @@ int mt7996_mcu_add_key(struct mt76_dev *dev, struct mt7996_bss_conf *mconf,
+ 
+ static int mt7996_mcu_get_pn(struct mt7996_dev *dev,
+ 			     struct ieee80211_bss_conf *conf,
+-			     struct mt7996_bss_conf *mconf, u8 *pn)
++			     struct mt7996_bss_conf *mconf,
++			     struct mt7996_link_sta *mlink, u8 *pn)
+ {
+ #define TSC_TYPE_BIGTK_PN 2
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)conf->vif->drv_priv;
+ 	struct sta_rec_pn_info *pn_info;
+ 	struct sk_buff *skb, *rskb;
+ 	struct tlv *tlv;
+ 	int ret;
+ 
+-	skb = mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76, &mvif->sta.wcid);
++	skb = mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76, &mlink->wcid);
+ 	if (IS_ERR(skb))
+ 		return PTR_ERR(skb);
+ 
+@@ -2515,6 +2520,7 @@ static int mt7996_mcu_get_pn(struct mt7996_dev *dev,
+ int mt7996_mcu_bcn_prot_enable(struct mt7996_dev *dev,
+ 			       struct ieee80211_bss_conf *conf,
+ 			       struct mt7996_bss_conf *mconf,
++			       struct mt7996_link_sta *mlink,
+ 			       struct ieee80211_key_conf *key)
+ {
+ 	struct mt7996_mcu_bcn_prot_tlv *bcn_prot;
+@@ -2533,7 +2539,7 @@ int mt7996_mcu_bcn_prot_enable(struct mt7996_dev *dev,
+ 
+ 	bcn_prot = (struct mt7996_mcu_bcn_prot_tlv *)tlv;
+ 
+-	ret = mt7996_mcu_get_pn(dev, conf, mconf, pn);
++	ret = mt7996_mcu_get_pn(dev, conf, mconf, mlink, pn);
+ 	if (ret) {
+ 		dev_kfree_skb(skb);
+ 		return ret;
+@@ -4794,21 +4800,18 @@ int mt7996_mcu_rdd_background_disable_timer(struct mt7996_dev *dev, bool disable
+ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev,
+ 				     struct ieee80211_vif *vif,
+ 				     struct mt7996_bss_conf *mconf,
+-				     struct ieee80211_sta *sta)
++				     struct mt7996_link_sta *mlink)
+ {
+-	struct mt7996_sta *msta;
+ 	struct sk_buff *skb;
+ 
+-	msta = sta ? (struct mt7996_sta *)sta->drv_priv : &mconf->vif->sta;
+-
+ 	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76,
+-					      &msta->wcid,
++					      &mlink->wcid,
+ 					      MT7996_STA_UPDATE_MAX_SIZE);
+ 	if (IS_ERR(skb))
+ 		return PTR_ERR(skb);
+ 
+ 	/* starec hdr trans */
+-	mt7996_mcu_sta_hdr_trans_tlv(dev, skb, vif, sta);
++	mt7996_mcu_sta_hdr_trans_tlv(dev, skb, vif, mlink);
+ 	return mt76_mcu_skb_send_msg(&dev->mt76, skb,
+ 				     MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true);
+ }
+@@ -4997,7 +5000,7 @@ int mt7996_mcu_get_per_sta_info(struct mt76_dev *dev, u16 tag,
+ 	switch (tag) {
+ 	case UNI_PER_STA_RSSI:
+ 		for (i = 0; i < sta_num; ++i) {
+-			struct mt7996_sta *msta;
++			struct mt7996_link_sta *mlink;
+ 			struct mt76_phy *phy;
+ 			s8 rssi[4];
+ 			u8 *rcpi;
+@@ -5011,10 +5014,10 @@ int mt7996_mcu_get_per_sta_info(struct mt76_dev *dev, u16 tag,
+ 				rssi[2] = to_rssi(MT_PRXV_RCPI0, rcpi[2]);
+ 				rssi[3] = to_rssi(MT_PRXV_RCPI0, rcpi[3]);
+ 
+-				msta = container_of(wcid, struct mt7996_sta, wcid);
+-				phy = msta->vif->phy->mt76;
+-				msta->ack_signal = mt76_rx_signal(phy->antenna_mask, rssi);
+-				ewma_avg_signal_add(&msta->avg_ack_signal, -msta->ack_signal);
++				mlink = container_of(wcid, struct mt7996_link_sta, wcid);
++				phy = mlink->sta->vif->deflink.phy->mt76;
++				mlink->ack_signal = mt76_rx_signal(phy->antenna_mask, rssi);
++				ewma_avg_signal_add(&mlink->avg_ack_signal, -mlink->ack_signal);
+ 			} else {
+ 				ret = -EINVAL;
+ 				dev_err(dev->dev, "Failed to update RSSI for "
+@@ -5052,7 +5055,7 @@ int mt7996_mcu_get_rssi(struct mt76_dev *dev)
+ {
+ 	u16 sta_list[PER_STA_INFO_MAX_NUM];
+ 	LIST_HEAD(sta_poll_list);
+-	struct mt7996_sta *msta;
++	struct mt7996_link_sta *mlink;
+ 	int i, ret;
+ 	bool empty = false;
+ 
+@@ -5072,13 +5075,13 @@ int mt7996_mcu_get_rssi(struct mt76_dev *dev)
+ 				empty = true;
+ 				break;
+ 			}
+-			msta = list_first_entry(&sta_poll_list,
+-			                        struct mt7996_sta,
++			mlink = list_first_entry(&sta_poll_list,
++			                        struct mt7996_link_sta,
+ 			                        wcid.poll_list);
+-			list_del_init(&msta->wcid.poll_list);
++			list_del_init(&mlink->wcid.poll_list);
+ 			spin_unlock_bh(&dev->sta_poll_lock);
+ 
+-			sta_list[i] = msta->wcid.idx;
++			sta_list[i] = mlink->wcid.idx;
+ 		}
+ 
+ 		ret = mt7996_mcu_get_per_sta_info(dev, UNI_PER_STA_RSSI,
+@@ -5358,10 +5361,18 @@ int mt7996_mcu_set_scs_stats(struct mt7996_phy *phy)
+ void mt7996_sta_rssi_work(void *data, struct ieee80211_sta *sta)
+ {
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_link_sta *mlink;
+ 	struct mt7996_phy *poll_phy = (struct mt7996_phy *) data;
+ 
+-	if (poll_phy->scs_ctrl.sta_min_rssi > msta->ack_signal)
+-		poll_phy->scs_ctrl.sta_min_rssi = msta->ack_signal;
++	mutex_lock(&poll_phy->dev->mt76.mutex);
++	mlink = mlink_dereference_protected(msta, 0);
++	if (!mlink)
++		goto out;
++
++	if (poll_phy->scs_ctrl.sta_min_rssi > mlink->ack_signal)
++		poll_phy->scs_ctrl.sta_min_rssi = mlink->ack_signal;
++out:
++	mutex_unlock(&poll_phy->dev->mt76.mutex);
+ }
+ 
+ void mt7996_mcu_scs_sta_poll(struct work_struct *work)
+@@ -5437,9 +5448,10 @@ int mt7996_mcu_set_scs(struct mt7996_phy *phy, u8 enable)
+ 
+ int mt7996_mcu_set_vow_drr_ctrl(struct mt7996_phy *phy,
+ 				struct mt7996_bss_conf *mconf,
+-				struct mt7996_sta *msta, enum vow_drr_ctrl_id id)
++				struct mt7996_link_sta *mlink,
++				enum vow_drr_ctrl_id id)
+ {
+-	struct mt7996_vow_sta_ctrl *vow = msta ? &msta->vow : NULL;
++	struct mt7996_vow_sta_ctrl *vow = mlink ? &mlink->vow : NULL;
+ 	u32 val = 0;
+ 	struct {
+ 		u8 __rsv1[4];
+@@ -5461,11 +5473,11 @@ int mt7996_mcu_set_vow_drr_ctrl(struct mt7996_phy *phy,
+ 	} __packed req = {
+ 		.tag = cpu_to_le16(UNI_VOW_DRR_CTRL),
+ 		.len = cpu_to_le16(sizeof(req) - 4),
+-		.wlan_idx = cpu_to_le16(msta ? msta->wcid.idx : 0),
++		.wlan_idx = cpu_to_le16(mlink ? mlink->wcid.idx : 0),
+ 		.band_idx = phy->mt76->band_idx,
+-		.wmm_idx = msta ? mconf->mt76.wmm_idx : 0,
++		.wmm_idx = mlink ? mconf->mt76.wmm_idx : 0,
+ 		.ctrl_id = cpu_to_le32(id),
+-		.omac_idx = msta ? mconf->mt76.omac_idx : 0
++		.omac_idx = mlink ? mconf->mt76.omac_idx : 0
+ 	};
+ 
+ 	switch (id) {
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 0fa2aaf7e..724829ec1 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -303,10 +303,10 @@ struct mt7996_vow_sta_ctrl {
+ 	u8 drr_quantum[IEEE80211_NUM_ACS];
+ };
+ 
+-struct mt7996_sta {
++struct mt7996_link_sta {
+ 	struct mt76_wcid wcid; /* must be first */
+ 
+-	struct mt7996_vif *vif;
++	struct mt7996_sta *sta;
+ 
+ 	struct list_head rc_list;
+ 
+@@ -325,6 +325,13 @@ struct mt7996_sta {
+ 	struct mt7996_vow_sta_ctrl vow;
+ };
+ 
++struct mt7996_sta {
++	struct mt7996_link_sta deflink;
++	struct mt7996_link_sta __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
++
++	struct mt7996_vif *vif;
++};
++
+ struct mt7996_bss_conf {
+ 	struct mt76_vif mt76; /* must be first */
+ 
+@@ -779,6 +786,13 @@ mconf_dereference_protected(struct mt7996_vif *mvif, u8 link_id)
+ 					 lockdep_is_held(&mvif->dev->mt76.mutex));
+ }
+ 
++static inline struct mt7996_link_sta *
++mlink_dereference_protected(struct mt7996_sta *msta, u8 link_id)
++{
++	return rcu_dereference_protected(msta->link[link_id],
++					 lockdep_is_held(&msta->vif->dev->mt76.mutex));
++}
++
+ extern const struct ieee80211_ops mt7996_ops;
+ extern struct pci_driver mt7996_pci_driver;
+ extern struct pci_driver mt7996_hif_driver;
+@@ -823,10 +837,12 @@ int mt7996_mcu_add_dev_info(struct mt7996_phy *phy,
+ 			    struct mt7996_bss_conf *mconf, bool enable);
+ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy,
+ 			    struct ieee80211_bss_conf *conf,
+-			    struct mt7996_bss_conf *mconf, int enable);
++			    struct mt7996_bss_conf *mconf,
++			    struct mt7996_link_sta *mlink, int enable);
+ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_bss_conf *conf,
+-		       struct mt7996_bss_conf *mconf, struct ieee80211_sta *sta,
+-		       bool enable, bool newly);
++		       struct mt7996_bss_conf *mconf,
++		       struct ieee80211_link_sta *link_sta,
++		       struct mt7996_link_sta *mlink, bool enable, bool newly);
+ int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev,
+ 			 struct ieee80211_ampdu_params *params,
+ 			 bool add);
+@@ -848,7 +864,8 @@ int mt7996_mcu_add_obss_spr(struct mt7996_phy *phy,
+ int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev,
+ 			     struct ieee80211_bss_conf *conf,
+ 			     struct mt7996_bss_conf *mconf,
+-			     struct ieee80211_sta *sta, bool changed);
++			     struct ieee80211_link_sta *link_sta,
++			     struct mt7996_link_sta *mlink, bool changed);
+ int mt7996_set_channel(struct mt7996_phy *phy, struct cfg80211_chan_def *chandef);
+ int mt7996_mcu_set_chan_info(struct mt7996_phy *phy, u16 tag);
+ int mt7996_mcu_set_tx(struct mt7996_dev *dev, struct mt7996_bss_conf *mconf);
+@@ -856,7 +873,9 @@ int mt7996_mcu_set_fixed_rate_ctrl(struct mt7996_dev *dev,
+ 				   void *data, u16 version);
+ int mt7996_mcu_set_fixed_field(struct mt7996_dev *dev,
+ 			       struct mt7996_bss_conf *mconf,
+-			       struct ieee80211_sta *sta, void *data, u32 field);
++			       struct ieee80211_link_sta *link_sta,
++			       struct mt7996_link_sta *mlink, void *data,
++			       u32 field);
+ int mt7996_mcu_set_eeprom(struct mt7996_dev *dev);
+ int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *read_buf);
+ int mt7996_mcu_get_eeprom_free_block(struct mt7996_dev *dev, u8 *block_num);
+@@ -911,7 +930,8 @@ void mt7996_mcu_scs_sta_poll(struct work_struct *work);
+ int mt7996_mcu_set_band_confg(struct mt7996_phy *phy, u16 option, bool enable);
+ int mt7996_mcu_set_vow_drr_ctrl(struct mt7996_phy *phy,
+ 				struct mt7996_bss_conf *mconf,
+-				struct mt7996_sta *msta, enum vow_drr_ctrl_id id);
++				struct mt7996_link_sta *mlink,
++				enum vow_drr_ctrl_id id);
+ int mt7996_mcu_set_vow_feature_ctrl(struct mt7996_phy *phy);
+ void mt7996_mcu_wmm_pbc_work(struct work_struct *work);
+ 
+@@ -978,7 +998,7 @@ void mt7996_mac_reset_counters(struct mt7996_phy *phy);
+ void mt7996_mac_cca_stats_reset(struct mt7996_phy *phy);
+ void mt7996_mac_enable_nf(struct mt7996_dev *dev, u8 band);
+ void mt7996_mac_enable_rtscts(struct mt7996_dev *dev,
+-			      struct ieee80211_vif *vif, bool enable);
++			      struct mt7996_link_sta *mlink, bool enable);
+ void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
+ 			   struct sk_buff *skb, struct mt76_wcid *wcid,
+ 			   struct ieee80211_key_conf *key, int pid,
+@@ -996,8 +1016,7 @@ void mt7996_mac_dump_work(struct work_struct *work);
+ void mt7996_mac_sta_rc_work(struct work_struct *work);
+ void mt7996_mac_update_stats(struct mt7996_phy *phy);
+ void mt7996_mac_twt_teardown_flow(struct mt7996_dev *dev,
+-				  struct mt7996_sta *msta,
+-				  u8 flowid);
++				  struct mt7996_link_sta *mlink, u8 flowid);
+ void mt7996_mac_add_twt_setup(struct ieee80211_hw *hw,
+ 			      struct ieee80211_sta *sta,
+ 			      struct ieee80211_twt_setup *twt);
+@@ -1026,11 +1045,12 @@ int mt7996_mcu_add_key(struct mt76_dev *dev, struct mt7996_bss_conf *mconf,
+ int mt7996_mcu_bcn_prot_enable(struct mt7996_dev *dev,
+ 			       struct ieee80211_bss_conf *conf,
+ 			       struct mt7996_bss_conf *mconf,
++			       struct mt7996_link_sta *mlink,
+ 			       struct ieee80211_key_conf *key);
+ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev,
+ 				     struct ieee80211_vif *vif,
+ 				     struct mt7996_bss_conf *mconf,
+-				     struct ieee80211_sta *sta);
++				     struct mt7996_link_sta *mlink);
+ int mt7996_mcu_cp_support(struct mt7996_dev *dev, u8 mode);
+ int mt7996_mcu_set_pp_en(struct mt7996_phy *phy, bool auto_mode, u8 force_bitmap,
+ 			 u16 bitmap);
+diff --git a/mt7996/mtk_debugfs_i.c b/mt7996/mtk_debugfs_i.c
+index 21748e798..e8e9ad969 100644
+--- a/mt7996/mtk_debugfs_i.c
++++ b/mt7996/mtk_debugfs_i.c
+@@ -1341,7 +1341,7 @@ mt7996_show_sta_acq_info(struct seq_file *s, u32 *ple_stat,
+ 				u32 sta_num = i + (j % CR_NUM_OF_AC) * 32, fl_que_ctrl[3] = {0};
+ 				u32 wmmidx = 0;
+ 				u8 band, idx;
+-				struct mt7996_sta *msta;
++				struct mt7996_link_sta *mlink;
+ 				struct mt76_wcid *wcid;
+ 
+ 				wcid = rcu_dereference(dev->mt76.wcid[sta_num]);
+@@ -1349,8 +1349,8 @@ mt7996_show_sta_acq_info(struct seq_file *s, u32 *ple_stat,
+ 					seq_printf(s, "ERROR!! no found STA wcid=%d\n", sta_num);
+ 					return 0;
+ 				}
+-				msta = container_of(wcid, struct mt7996_sta, wcid);
+-				wmmidx = msta->vif->deflink.mt76.wmm_idx;
++				mlink = container_of(wcid, struct mt7996_link_sta, wcid);
++				wmmidx = mlink->sta->vif->deflink.mt76.wmm_idx;
+ 
+ 				seq_printf(s, "\tSTA%d AC%d: ", sta_num, ac_num);
+ 
+diff --git a/mt7996/testmode.c b/mt7996/testmode.c
+index 056c501df..0a6d3de4b 100644
+--- a/mt7996/testmode.c
++++ b/mt7996/testmode.c
+@@ -235,8 +235,8 @@ mt7996_tm_init(struct mt7996_phy *phy, bool en)
+ 
+ 	mt7996_tm_rf_switch_mode(dev, rf_test_mode);
+ 
+-	mt7996_mcu_add_bss_info(phy, &phy->monitor_vif->bss_conf, &mvif->deflink, en);
+-	mt7996_mcu_add_sta(dev, &phy->monitor_vif->bss_conf, &mvif->deflink, NULL, en, false);
++	mt7996_mcu_add_bss_info(phy, &phy->monitor_vif->bss_conf, &mvif->deflink, &mvif->sta.deflink, en);
++	mt7996_mcu_add_sta(dev, &phy->monitor_vif->bss_conf, &mvif->deflink, NULL, &mvif->sta.deflink, en, false);
+ 
+ 	mt7996_tm_set(dev, SET_ID(BAND_IDX), phy->mt76->band_idx);
+ 
+@@ -1186,7 +1186,7 @@ mt7996_tm_txbf_init(struct mt7996_phy *phy, u16 *val)
+ 	phy->omac_mask |= BIT_ULL(mvif->deflink.mt76.omac_idx);
+ 
+ 	mt7996_mcu_add_dev_info(phy, &phy->monitor_vif->bss_conf, &mvif->deflink, true);
+-	mt7996_mcu_add_bss_info(phy, &phy->monitor_vif->bss_conf, &mvif->deflink, true);
++	mt7996_mcu_add_bss_info(phy, &phy->monitor_vif->bss_conf, &mvif->deflink, &mvif->sta.deflink, true);
+ 
+ 	if (td->ibf) {
+ 		if (td->is_txbf_dut) {
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0103-wifi-mt76-extend-wcid-and-sta-flow-for-MLO-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0103-wifi-mt76-extend-wcid-and-sta-flow-for-MLO-support.patch
new file mode 100644
index 0000000..cb43f12
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0103-wifi-mt76-extend-wcid-and-sta-flow-for-MLO-support.patch
@@ -0,0 +1,91 @@
+From d51dc43644b25b5220943e7b96d0e29e227ee7f4 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Wed, 29 Nov 2023 11:04:50 +0800
+Subject: [PATCH 103/120] wifi: mt76: extend wcid and sta flow for MLO support
+
+Add link related info to wcid, and split sta connection flow of common
+parts for MLO supported chipsets.
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mac80211.c | 12 +++++++++++-
+ mt76.h     |  7 ++++++-
+ 2 files changed, 17 insertions(+), 2 deletions(-)
+
+diff --git a/mac80211.c b/mac80211.c
+index 4fad03dd9..96fab2320 100644
+--- a/mac80211.c
++++ b/mac80211.c
+@@ -1066,6 +1066,10 @@ mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,
+ 		     sizeof(mstat.chain_signal));
+ 	memcpy(status->chain_signal, mstat.chain_signal,
+ 	       sizeof(mstat.chain_signal));
++	if (mstat.wcid) {
++		status->link_valid = mstat.wcid->link_valid;
++		status->link_id = mstat.wcid->link_id;
++	}
+ 
+ 	*sta = wcid_to_sta(mstat.wcid);
+ 	*hw = mt76_phy_hw(dev, mstat.phy_idx);
+@@ -1374,6 +1378,9 @@ mt76_sta_add(struct mt76_phy *phy, struct ieee80211_vif *vif,
+ 	if (ret)
+ 		goto out;
+ 
++	if (phy->hw->wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO)
++		goto out;
++
+ 	for (i = 0; i < ARRAY_SIZE(sta->txq); i++) {
+ 		struct mt76_txq *mtxq;
+ 
+@@ -1403,12 +1410,15 @@ void __mt76_sta_remove(struct mt76_dev *dev, struct ieee80211_vif *vif,
+ 	struct mt76_wcid *wcid = (struct mt76_wcid *)sta->drv_priv;
+ 	int i, idx = wcid->idx;
+ 
+-	for (i = 0; i < ARRAY_SIZE(wcid->aggr); i++)
++	for (i = 0; !sta->valid_links && i < ARRAY_SIZE(wcid->aggr); i++)
+ 		mt76_rx_aggr_stop(dev, wcid, i);
+ 
+ 	if (dev->drv->sta_remove)
+ 		dev->drv->sta_remove(dev, vif, sta);
+ 
++	if (sta->valid_links)
++		return;
++
+ 	mt76_wcid_cleanup(dev, wcid);
+ 
+ 	mt76_wcid_mask_clear(dev->wcid_mask, idx);
+diff --git a/mt76.h b/mt76.h
+index 818f94dab..3e9203512 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -376,6 +376,9 @@ struct mt76_wcid {
+ 	u8 sta:1;
+ 	u8 amsdu:1;
+ 	u8 phy_idx:2;
++	u8 link_id:4;
++	bool link_valid;
++	struct mt76_wcid *def_wcid;
+ 
+ 	u8 rx_check_pn;
+ 	u8 rx_key_pn[IEEE80211_NUM_TIDS + 1][6];
+@@ -1361,11 +1364,13 @@ mtxq_to_txq(struct mt76_txq *mtxq)
+ static inline struct ieee80211_sta *
+ wcid_to_sta(struct mt76_wcid *wcid)
+ {
+-	void *ptr = wcid;
++	void *ptr;
+ 
+ 	if (!wcid || !wcid->sta)
+ 		return NULL;
+ 
++	ptr = wcid->def_wcid ?: wcid;
++
+ 	return container_of(ptr, struct ieee80211_sta, drv_priv);
+ }
+ 
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0104-wifi-mt76-mt7996-enable-MLO-capability.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0104-wifi-mt76-mt7996-enable-MLO-capability.patch
new file mode 100644
index 0000000..d01d540
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0104-wifi-mt76-mt7996-enable-MLO-capability.patch
@@ -0,0 +1,108 @@
+From f8005d1d132e823d02b055b99e6a31c8d9c775af Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Thu, 30 Nov 2023 16:31:17 +0800
+Subject: [PATCH 104/120] wifi: mt76: mt7996: enable MLO capability
+
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/eeprom.c |  6 ++++++
+ mt7996/init.c   | 40 ++++++++++++++++++++++++++++++++++++++--
+ 2 files changed, 44 insertions(+), 2 deletions(-)
+
+diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
+index aacfc623f..9c97f5741 100644
+--- a/mt7996/eeprom.c
++++ b/mt7996/eeprom.c
+@@ -388,6 +388,12 @@ static int mt7996_eeprom_parse_band_config(struct mt7996_phy *phy)
+ 		break;
+ 	}
+ 
++	/* TODO: for MLO, we enable all band capabilities */
++	phy->mt76->cap.has_2ghz = true;
++	phy->mt76->cap.has_5ghz = true;
++	if (is_mt7996(&phy->dev->mt76))
++		phy->mt76->cap.has_6ghz = true;
++
+ 	return ret;
+ }
+ 
+diff --git a/mt7996/init.c b/mt7996/init.c
+index 541361b86..eaa7b0401 100644
+--- a/mt7996/init.c
++++ b/mt7996/init.c
+@@ -34,16 +34,45 @@ static const struct ieee80211_iface_combination if_comb[] = {
+ 		.limits = if_limits,
+ 		.n_limits = ARRAY_SIZE(if_limits),
+ 		.max_interfaces = MT7996_MAX_INTERFACES,
+-		.num_different_channels = 1,
++		.num_different_channels = 3,
+ 		.beacon_int_infra_match = true,
++		/*
+ 		.radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) |
+ 				       BIT(NL80211_CHAN_WIDTH_20) |
+ 				       BIT(NL80211_CHAN_WIDTH_40) |
+ 				       BIT(NL80211_CHAN_WIDTH_80) |
+ 				       BIT(NL80211_CHAN_WIDTH_160),
++		*/
+ 	}
+ };
+ 
++static const u8 mt7996_if_types_ext_capa[] = {
++	[0] = WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING,
++	[7] = WLAN_EXT_CAPA8_OPMODE_NOTIF,
++};
++
++static const struct wiphy_iftype_ext_capab mt7996_iftypes_ext_capa[] = {
++	{
++		.iftype = NL80211_IFTYPE_STATION,
++		.extended_capabilities = mt7996_if_types_ext_capa,
++		.extended_capabilities_mask = mt7996_if_types_ext_capa,
++		.extended_capabilities_len = sizeof(mt7996_if_types_ext_capa),
++		.mld_capa_and_ops = 2,
++	},
++	{
++		.iftype = NL80211_IFTYPE_AP,
++		.extended_capabilities = mt7996_if_types_ext_capa,
++		.extended_capabilities_mask = mt7996_if_types_ext_capa,
++		.extended_capabilities_len = sizeof(mt7996_if_types_ext_capa),
++		.mld_capa_and_ops = 2,
++		/* the max number of simultaneous links is defined as the
++		 * maximum number of affiliated APs minus 1.
++		 * mt7996 could have 3 links in an MLD AP, so currently
++		 * hardcode it to 2.
++		 */
++	},
++};
++
+ static ssize_t mt7996_thermal_temp_show(struct device *dev,
+ 					struct device_attribute *attr,
+ 					char *buf)
+@@ -416,8 +445,9 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed)
+ 	ieee80211_hw_set(hw, SUPPORTS_TX_ENCAP_OFFLOAD);
+ 	ieee80211_hw_set(hw, SUPPORTS_RX_DECAP_OFFLOAD);
+ 	ieee80211_hw_set(hw, WANT_MONITOR_VIF);
+-	ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID);
++	// ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID);
+ 	ieee80211_hw_set(hw, CHANCTX_STA_CSA);
++	ieee80211_hw_set(hw, CONNECTION_MONITOR);
+ 
+ 	hw->max_tx_fragments = 4;
+ 
+@@ -461,6 +491,12 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed)
+ 
+ 	wiphy->max_scan_ssids = 4;
+ 	wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN;
++
++	/* enable MLO support */
++	wiphy->flags |= WIPHY_FLAG_SUPPORTS_MLO;
++	wiphy->iftype_ext_capab = mt7996_iftypes_ext_capa;
++	wiphy->num_iftype_ext_capab = ARRAY_SIZE(mt7996_iftypes_ext_capa);
++	wiphy->features |= NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE;
+ }
+ 
+ static void
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0105-wifi-mt76-mt7996-support-multi-link-vif-links-and-ML.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0105-wifi-mt76-mt7996-support-multi-link-vif-links-and-ML.patch
new file mode 100644
index 0000000..9600fe8
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0105-wifi-mt76-mt7996-support-multi-link-vif-links-and-ML.patch
@@ -0,0 +1,590 @@
+From 5b96ef6f679b62a82fec6ca5ea1be7591c75a98b Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Thu, 23 Nov 2023 18:22:11 +0800
+Subject: [PATCH 105/120] wifi: mt76: mt7996: support multi-link vif links and
+ MLO bss callbacks
+
+Rework add/remove interface functions to add/remove bss_conf functions,
+and also switch to callbacks for MLO bss.
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/main.c   | 298 +++++++++++++++++++++++++++++++++++++++---------
+ mt7996/mcu.c    |  29 +++--
+ mt7996/mt7996.h |   9 ++
+ 3 files changed, 270 insertions(+), 66 deletions(-)
+
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 798beebb5..0ad3e3a6f 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -204,6 +204,38 @@ static int get_omac_idx(enum nl80211_iftype type, u64 mask)
+ 	return -1;
+ }
+ 
++static int get_own_mld_idx(u64 mask, bool group_mld)
++{
++	u8 start, end;
++	int i;
++
++	if (group_mld) {
++		start = 0;
++		end = 15;
++	} else {
++		start = 16;
++		end = 63;
++	}
++
++	i = get_free_idx(mask, start, end);
++	if (i)
++		return i - 1;
++
++	return -1;
++}
++
++static int get_mld_remap_idx(u64 mask)
++{
++	u8 start = 0, end = 15;
++	int i;
++
++	i = get_free_idx(mask, start, end);
++	if (i)
++		return i - 1;
++
++	return -1;
++}
++
+ static void mt7996_init_bitrate_mask(struct mt7996_bss_conf *mconf)
+ {
+ 	int i;
+@@ -222,48 +254,108 @@ static void mt7996_init_bitrate_mask(struct mt7996_bss_conf *mconf)
+ 	}
+ }
+ 
+-static int mt7996_add_interface(struct ieee80211_hw *hw,
+-				struct ieee80211_vif *vif)
++static void mt7996_remove_bss_conf(struct ieee80211_vif *vif,
++				   struct ieee80211_bss_conf *conf,
++				   struct mt7996_bss_conf *mconf)
+ {
+-	struct ieee80211_bss_conf *conf = &vif->bss_conf;
++	struct mt7996_phy *phy = mconf->phy;
++	struct mt7996_dev *dev = phy->dev;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_bss_conf *mconf = &mvif->deflink;
+-	struct mt7996_link_sta *mlink = &mvif->sta.deflink;
+-	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+-	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	u8 link_id = conf->link_id;
++	struct mt7996_link_sta *mlink =
++		mlink_dereference_protected(&mvif->sta, link_id);
++
++	if (!mlink)
++		return;
++
++	mt7996_mcu_add_sta(dev, conf, mconf, NULL, mlink, false, false);
++	mt7996_mcu_add_bss_info(phy, conf, mconf, mlink, false);
++	mt7996_mcu_add_dev_info(phy, conf, mconf, false);
++
++	rcu_assign_pointer(dev->mt76.wcid[mlink->wcid.idx], NULL);
++	rcu_assign_pointer(mvif->link[link_id], NULL);
++	rcu_assign_pointer(mvif->sta.link[link_id], NULL);
++
++	dev->mt76.vif_mask &= ~BIT_ULL(mconf->mt76.idx);
++	dev->mld_id_mask &= ~BIT_ULL(mconf->own_mld_id);
++	phy->omac_mask &= ~BIT_ULL(mconf->mt76.omac_idx);
++
++	spin_lock_bh(&dev->mt76.sta_poll_lock);
++	if (!list_empty(&mlink->wcid.poll_list))
++		list_del_init(&mlink->wcid.poll_list);
++	spin_unlock_bh(&dev->mt76.sta_poll_lock);
++
++	mt76_wcid_cleanup(&dev->mt76, &mlink->wcid);
++
++	if (mlink != &mvif->sta.deflink)
++		kfree(mlink);
++
++	if (mconf != &mvif->deflink)
++		kfree(mconf);
++}
++
++static int mt7996_add_bss_conf(struct mt7996_phy *phy,
++			       struct ieee80211_vif *vif,
++			       struct ieee80211_bss_conf *conf)
++{
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_bss_conf *mconf;
++	struct mt7996_link_sta *mlink;
+ 	struct mt76_txq *mtxq;
+ 	u8 band_idx = phy->mt76->band_idx;
+-	int idx, ret = 0;
+-
+-	mutex_lock(&dev->mt76.mutex);
++	u8 link_id = conf->link_id;
++	int idx, ret;
+ 
+-	if (vif->type == NL80211_IFTYPE_MONITOR &&
+-	    is_zero_ether_addr(vif->addr))
+-		phy->monitor_vif = vif;
++	if (conf != &vif->bss_conf) {
++		mconf = kzalloc(sizeof(*mconf), GFP_KERNEL);
++		if (!mconf)
++			return -ENOMEM;
++	} else {
++		mconf = &mvif->deflink;
++	}
+ 
+ 	mconf->mt76.idx = __ffs64(~dev->mt76.vif_mask);
+ 	if (mconf->mt76.idx >= mt7996_max_interface_num(dev)) {
+ 		ret = -ENOSPC;
+-		goto out;
++		goto error;
+ 	}
+ 
+ 	idx = get_omac_idx(vif->type, phy->omac_mask);
+ 	if (idx < 0) {
+ 		ret = -ENOSPC;
+-		goto out;
++		goto error;
++	}
++
++	mconf->own_mld_id = get_own_mld_idx(dev->mld_id_mask, false);
++	if (mconf->own_mld_id < 0) {
++		ret = -ENOSPC;
++		goto error;
+ 	}
++
+ 	mconf->mt76.omac_idx = idx;
+ 	mconf->vif = mvif;
+ 	mconf->phy = phy;
+ 	mconf->mt76.band_idx = band_idx;
+ 	mconf->mt76.wmm_idx = vif->type != NL80211_IFTYPE_AP;
+-	mvif->dev = dev;
++	mconf->link_id = link_id;
+ 
+ 	ret = mt7996_mcu_add_dev_info(phy, conf, mconf, true);
+ 	if (ret)
+-		goto out;
++		goto error;
++
++	if (ieee80211_vif_is_mld(vif)) {
++		mlink = kzalloc(sizeof(*mlink), GFP_KERNEL);
++		if (!mlink) {
++			ret = -ENOMEM;
++			goto error;
++		}
++	} else {
++		mlink = &mvif->sta.deflink;
++	}
+ 
+ 	dev->mt76.vif_mask |= BIT_ULL(mconf->mt76.idx);
++	dev->mld_id_mask |= BIT_ULL(mconf->own_mld_id);
+ 	phy->omac_mask |= BIT_ULL(mconf->mt76.omac_idx);
+ 
+ 	idx = MT7996_WTBL_RESERVED - mconf->mt76.idx;
+@@ -274,6 +366,9 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 	mlink->wcid.phy_idx = band_idx;
+ 	mlink->wcid.hw_key_idx = -1;
+ 	mlink->wcid.tx_info |= MT_WCID_TX_INFO_SET;
++	mlink->wcid.def_wcid = &mvif->sta.deflink.wcid;
++	mlink->wcid.link_id = link_id;
++	mlink->wcid.link_valid = ieee80211_vif_is_mld(vif);
+ 	mlink->sta = &mvif->sta;
+ 	mlink->sta->vif = mvif;
+ 	mt76_wcid_init(&mlink->wcid);
+@@ -295,7 +390,6 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 		mconf->mt76.basic_rates_idx = MT7996_BASIC_RATES_TBL + 4;
+ 	else
+ 		mconf->mt76.basic_rates_idx = MT7996_BASIC_RATES_TBL;
+-
+ 	mt7996_init_bitrate_mask(mconf);
+ 
+ 	mt7996_mcu_add_bss_info(phy, conf, mconf, mlink, true);
+@@ -305,10 +399,32 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 	if (vif->type != NL80211_IFTYPE_STATION)
+ 		mt7996_mcu_add_sta(dev, conf, mconf, NULL, mlink, true, true);
+ 	rcu_assign_pointer(dev->mt76.wcid[idx], &mlink->wcid);
+-	rcu_assign_pointer(mvif->link[0], mconf);
+-	rcu_assign_pointer(mvif->sta.link[0], mlink);
++	rcu_assign_pointer(mvif->link[link_id], mconf);
++	rcu_assign_pointer(mvif->sta.link[link_id], mlink);
+ 
+-out:
++	return 0;
++error:
++	mt7996_remove_bss_conf(vif, conf, mconf);
++	return ret;
++}
++
++static int mt7996_add_interface(struct ieee80211_hw *hw,
++			        struct ieee80211_vif *vif)
++{
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	int ret = 0;
++
++	mutex_lock(&dev->mt76.mutex);
++	if (vif->type == NL80211_IFTYPE_MONITOR &&
++	    is_zero_ether_addr(vif->addr))
++		phy->monitor_vif = vif;
++
++	mvif->dev = dev;
++	mvif->sta.vif = mvif;
++
++	ret = mt7996_add_bss_conf(phy, vif, &vif->bss_conf);
+ 	mutex_unlock(&dev->mt76.mutex);
+ 
+ 	return ret;
+@@ -320,38 +436,23 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
+ 	struct ieee80211_bss_conf *conf;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_bss_conf *mconf;
+-	struct mt7996_link_sta *mlink = &mvif->sta.deflink;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+-	int idx = mlink->wcid.idx;
+ 
+ 	cancel_delayed_work_sync(&phy->scan_work);
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+-	conf = link_conf_dereference_protected(vif, 0);
+-	mconf = mconf_dereference_protected(mvif, 0);
+-	mt7996_mcu_add_sta(dev, conf, mconf, NULL, mlink, false, false);
+-	mt7996_mcu_add_bss_info(phy, conf, mconf, mlink, false);
++	if (test_bit(MT76_SCANNING, &phy->mt76->state))
++		mt7996_scan_complete(phy, true);
+ 
+ 	if (vif == phy->monitor_vif)
+ 		phy->monitor_vif = NULL;
+ 
+-	mt7996_mcu_add_dev_info(phy, conf, mconf, false);
+-
+-	rcu_assign_pointer(dev->mt76.wcid[idx], NULL);
+-
+-	dev->mt76.vif_mask &= ~BIT_ULL(mconf->mt76.idx);
+-	phy->omac_mask &= ~BIT_ULL(mconf->mt76.omac_idx);
+-
+-	spin_lock_bh(&dev->mt76.sta_poll_lock);
+-	if (!list_empty(&mlink->wcid.poll_list))
+-		list_del_init(&mlink->wcid.poll_list);
+-	spin_unlock_bh(&dev->mt76.sta_poll_lock);
++	conf = link_conf_dereference_protected(vif, 0);
++	mconf = mconf_dereference_protected(mvif, 0);
+ 
+-	mt76_wcid_cleanup(&dev->mt76, &mlink->wcid);
+-	rcu_assign_pointer(mvif->link[0], NULL);
+-	rcu_assign_pointer(mvif->sta.link[0], NULL);
++	mt7996_remove_bss_conf(vif, conf, mconf);
+ 
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+@@ -714,10 +815,31 @@ mt7996_update_mu_group(struct ieee80211_hw *hw, struct ieee80211_bss_conf *conf,
+ 	mt76_wr(dev, MT_WF_PHYRX_BAND_GID_TAB_POS3(band), mu[3]);
+ }
+ 
+-static void mt7996_bss_info_changed(struct ieee80211_hw *hw,
+-				    struct ieee80211_vif *vif,
+-				    struct ieee80211_bss_conf *info,
+-				    u64 changed)
++static void mt7996_vif_cfg_changed(struct ieee80211_hw *hw,
++				   struct ieee80211_vif *vif, u64 changed)
++{
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++
++	mutex_lock(&dev->mt76.mutex);
++
++	if (changed & BSS_CHANGED_ASSOC && vif->cfg.assoc) {
++		struct ieee80211_bss_conf *conf = &vif->bss_conf;
++		struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++		struct mt7996_bss_conf *mconf = mconf_dereference_protected(mvif, 0);
++		struct mt7996_link_sta *mlink = mlink_dereference_protected(&mvif->sta, 0);
++
++		mt7996_mcu_add_bss_info(phy, conf, mconf, mlink, true);
++		mt7996_mcu_add_sta(dev, conf, mconf, NULL, mlink, true, false);
++	}
++
++	mutex_unlock(&dev->mt76.mutex);
++}
++
++static void mt7996_link_info_changed(struct ieee80211_hw *hw,
++				     struct ieee80211_vif *vif,
++				     struct ieee80211_bss_conf *info,
++				     u64 changed)
+ {
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_bss_conf *mconf;
+@@ -733,7 +855,6 @@ static void mt7996_bss_info_changed(struct ieee80211_hw *hw,
+ 	 * and then peer references bss_info_rfch to set bandwidth cap.
+ 	 */
+ 	if ((changed & BSS_CHANGED_BSSID && !is_zero_ether_addr(info->bssid)) ||
+-	    (changed & BSS_CHANGED_ASSOC && vif->cfg.assoc) ||
+ 	    (changed & BSS_CHANGED_BEACON_ENABLED && info->enable_beacon)) {
+ 		mt7996_mcu_add_bss_info(phy, info, mconf, mlink, true);
+ 		mt7996_mcu_add_sta(dev, info, mconf, NULL, mlink, true,
+@@ -1076,7 +1197,7 @@ mt7996_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+ 	u64 ret;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	mconf = mconf_dereference_protected(mvif, 0);
++	mconf = mconf_dereference_protected(mvif, mvif->master_link_id);
+ 	ret = __mt7996_get_tsf(hw, mconf);
+ 	mutex_unlock(&dev->mt76.mutex);
+ 
+@@ -1099,7 +1220,7 @@ mt7996_set_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+-	mconf = mconf_dereference_protected(mvif, 0);
++	mconf = mconf_dereference_protected(mvif, mvif->master_link_id);
+ 	n = mconf->mt76.omac_idx > HW_BSSID_MAX ? HW_BSSID_0
+ 					       : mconf->mt76.omac_idx;
+ 	mt76_wr(dev, MT_LPON_UTTR0(phy->mt76->band_idx), tsf.t32[0]);
+@@ -1127,7 +1248,7 @@ mt7996_offset_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+-	mconf = mconf_dereference_protected(mvif, 0);
++	mconf = mconf_dereference_protected(mvif, mvif->master_link_id);
+ 	n = mconf->mt76.omac_idx > HW_BSSID_MAX ? HW_BSSID_0
+ 					       : mconf->mt76.omac_idx;
+ 	mt76_wr(dev, MT_LPON_UTTR0(phy->mt76->band_idx), tsf.t32[0]);
+@@ -1302,7 +1423,7 @@ mt7996_set_bitrate_mask(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	u32 changed = IEEE80211_RC_SUPP_RATES_CHANGED;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	mconf = mconf_dereference_protected(mvif, 0);
++	mconf = mconf_dereference_protected(mvif, mvif->master_link_id);
+ 	mconf->bitrate_mask = *mask;
+ 	mutex_unlock(&dev->mt76.mutex);
+ 
+@@ -1522,7 +1643,7 @@ void mt7996_get_et_stats(struct ieee80211_hw *hw,
+ 	int i, ei = 0;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	mconf = mconf_dereference_protected(mvif, 0);
++	mconf = mconf_dereference_protected(mvif, mvif->master_link_id);
+ 	wi.idx = mconf->mt76.idx,
+ 
+ 	mt7996_mac_update_stats(phy);
+@@ -1890,6 +2011,8 @@ mt7996_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	struct mt7996_phy *phy = ctx->phy;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_bss_conf *mconf;
++	u8 link_id = link_conf->link_id;
++	int ret;
+ 
+ 	wiphy_info(hw->wiphy, "Assign VIF (addr: %pM, type: %d, link_id: %d) to channel context: %d MHz\n",
+ 		    vif->addr, vif->type, link_conf->link_id,
+@@ -1897,10 +2020,24 @@ mt7996_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 
+ 	mutex_lock(&phy->dev->mt76.mutex);
+ 
+-	mconf = mconf_dereference_protected(mvif, 0);
++	/* remove first */
++	if (rcu_access_pointer(mvif->link[link_id]))
++		mt7996_remove_bss_conf(vif, link_conf,
++				       mconf_dereference_protected(mvif, link_id));
++
++	ret = mt7996_add_bss_conf(phy, vif, link_conf);
++	if (ret) {
++		mutex_unlock(&phy->dev->mt76.mutex);
++		return ret;
++	}
++
++	mconf = mconf_dereference_protected(mvif, link_id);
+ 	mconf->chanctx = ctx;
+ 	ctx->nbss_assigned++;
+ 
++	if (mt7996_hw_phy(hw) == phy)
++		mvif->master_link_id = link_id;
++
+ 	mutex_unlock(&phy->dev->mt76.mutex);
+ 
+ 	return 0;
+@@ -1926,7 +2063,7 @@ mt7996_unassign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	if (test_bit(MT76_SCANNING, &phy->mt76->state))
+ 		mt7996_scan_complete(phy, true);
+ 
+-	mconf = mconf_dereference_protected(mvif, 0);
++	mconf = mconf_dereference_protected(mvif, link_conf->link_id);
+ 	mconf->chanctx = NULL;
+ 	ctx->nbss_assigned--;
+ 
+@@ -1960,6 +2097,57 @@ mt7996_switch_vif_chanctx(struct ieee80211_hw *hw,
+ 	return mt7996_set_channel(phy, &new_ctx->chandef);
+ }
+ 
++static int
++mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
++			u16 old_links, u16 new_links,
++			struct ieee80211_bss_conf *old[IEEE80211_MLD_MAX_NUM_LINKS])
++{
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	unsigned long rem = old_links & ~new_links;
++	unsigned int link_id;
++	int ret = 0;
++
++	if (old_links == new_links)
++		return 0;
++
++	mutex_lock(&dev->mt76.mutex);
++
++	/* check if there's legacy bss needed to be removed */
++	if (rcu_access_pointer(mvif->link[0]) == &mvif->deflink)
++		rem |= BIT(0);
++	/* remove first */
++	for_each_set_bit(link_id, &rem, IEEE80211_MLD_MAX_NUM_LINKS) {
++		struct mt7996_bss_conf *mconf =
++			mconf_dereference_protected(mvif, link_id);
++
++		if (!mconf)
++			continue;
++
++		mt7996_remove_bss_conf(vif, old[link_id], mconf);
++	}
++
++	if (!old_links) {
++		mvif->group_mld_id = get_own_mld_idx(dev->mld_id_mask, true);
++		dev->mld_id_mask |= BIT_ULL(mvif->group_mld_id);
++
++		mvif->mld_remap_id = get_mld_remap_idx(dev->mld_remap_id_mask);
++		dev->mld_remap_id_mask |= BIT_ULL(mvif->mld_remap_id);
++	}
++
++	/* fallback to non-MLO interface */
++	if (!new_links) {
++		ret = mt7996_add_bss_conf(phy, vif, &vif->bss_conf);
++		dev->mld_id_mask &= ~BIT_ULL(mvif->group_mld_id);
++		dev->mld_remap_id_mask &= ~BIT_ULL(mvif->mld_remap_id);
++	}
++
++	mutex_unlock(&dev->mt76.mutex);
++
++	return ret;
++}
++
+ const struct ieee80211_ops mt7996_ops = {
+ 	.tx = mt7996_tx,
+ 	.start = mt7996_start,
+@@ -1969,7 +2157,8 @@ const struct ieee80211_ops mt7996_ops = {
+ 	.config = mt7996_config,
+ 	.conf_tx = mt7996_conf_tx,
+ 	.configure_filter = mt7996_configure_filter,
+-	.bss_info_changed = mt7996_bss_info_changed,
++	.vif_cfg_changed = mt7996_vif_cfg_changed,
++	.link_info_changed = mt7996_link_info_changed,
+ 	.sta_state = mt76_sta_state,
+ 	.sta_pre_rcu_remove = mt76_sta_pre_rcu_remove,
+ 	.sta_rc_update = mt7996_sta_rc_update,
+@@ -2015,4 +2204,5 @@ const struct ieee80211_ops mt7996_ops = {
+ 	.assign_vif_chanctx = mt7996_assign_vif_chanctx,
+ 	.unassign_vif_chanctx = mt7996_unassign_vif_chanctx,
+ 	.switch_vif_chanctx = mt7996_switch_vif_chanctx,
++	.change_vif_links = mt7996_change_vif_links,
+ };
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 8ef8506e1..a27769426 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -1045,15 +1045,23 @@ static void
+ mt7996_mcu_bss_mld_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
+ 		       struct mt7996_bss_conf *mconf)
+ {
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct bss_mld_tlv *mld;
+ 	struct tlv *tlv;
+ 
+ 	tlv = mt7996_mcu_add_uni_tlv(skb, UNI_BSS_INFO_MLD, sizeof(*mld));
+-
+ 	mld = (struct bss_mld_tlv *)tlv;
+-	mld->group_mld_id = 0xff;
+-	mld->own_mld_id = mconf->mt76.idx;
+-	mld->remap_idx = 0xff;
++
++	if (ieee80211_vif_is_mld(vif)) {
++		mld->group_mld_id = mvif->group_mld_id;
++		mld->remap_idx = mvif->mld_remap_id;
++		memcpy(mld->mac_addr, vif->addr, ETH_ALEN);
++	} else {
++		mld->group_mld_id = 0xff;
++		mld->remap_idx = 0xff;
++	}
++
++	mld->own_mld_id = mconf->own_mld_id;
+ }
+ 
+ static void
+@@ -1134,13 +1142,11 @@ mt7996_mcu_bss_ifs_timing_tlv(struct sk_buff *skb, struct mt7996_phy *phy)
+ }
+ 
+ static int
+-mt7996_mcu_bss_basic_tlv(struct sk_buff *skb,
+-			 struct ieee80211_bss_conf *conf,
+-			 struct mt7996_bss_conf *mconf,
+-			 struct ieee80211_sta *sta,
+-			 struct mt76_phy *phy, u16 wlan_idx,
+-			 bool enable)
++mt7996_mcu_bss_basic_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *conf,
++			 struct mt7996_bss_conf *mconf, struct ieee80211_sta *sta,
++			 u16 wlan_idx, bool enable)
+ {
++	struct mt76_phy *phy = mconf->phy->mt76;
+ 	struct ieee80211_vif *vif = conf->vif;
+ 	struct cfg80211_chan_def *chandef = &phy->chandef;
+ 	struct mt76_connac_bss_basic_tlv *bss;
+@@ -1252,8 +1258,7 @@ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy,
+ 		return PTR_ERR(skb);
+ 
+ 	/* bss_basic must be first */
+-	mt7996_mcu_bss_basic_tlv(skb, conf, mconf, NULL, phy->mt76,
+-				 mlink->wcid.idx, enable);
++	mt7996_mcu_bss_basic_tlv(skb, conf, mconf, NULL, mlink->wcid.idx, enable);
+ 	mt7996_mcu_bss_sec_tlv(skb, mconf);
+ 
+ 	if (vif->type == NL80211_IFTYPE_MONITOR)
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 724829ec1..15912e8e8 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -341,6 +341,9 @@ struct mt7996_bss_conf {
+ 	struct cfg80211_bitrate_mask bitrate_mask;
+ 
+ 	struct mt7996_chanctx *chanctx;
++
++	u8 link_id;
++	u8 own_mld_id;
+ };
+ 
+ struct mt7996_vif {
+@@ -349,6 +352,10 @@ struct mt7996_vif {
+ 
+ 	struct mt7996_sta sta;
+ 	struct mt7996_dev *dev;
++
++	u8 master_link_id;
++	u8 group_mld_id;
++	u8 mld_remap_id;
+ };
+ 
+ /* crash-dump */
+@@ -547,6 +554,8 @@ struct mt7996_dev {
+ 	u16 chainmask;
+ 	u8 chainshift[__MT_MAX_BAND];
+ 	u32 hif_idx;
++	u64 mld_id_mask;
++	u64 mld_remap_id_mask;
+ 
+ 	struct work_struct init_work;
+ 	struct work_struct rc_work;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0106-wifi-mt76-mt7996-support-multi-link-sta-links-and-ML.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0106-wifi-mt76-mt7996-support-multi-link-sta-links-and-ML.patch
new file mode 100644
index 0000000..e9e03eb
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0106-wifi-mt76-mt7996-support-multi-link-sta-links-and-ML.patch
@@ -0,0 +1,604 @@
+From 822ea6010c8c7e7d7634b98e7bd83964d9dcf1d0 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Wed, 29 Nov 2023 10:12:39 +0800
+Subject: [PATCH 106/120] wifi: mt76: mt7996: support multi-link sta links and
+ MLO sta callbacks
+
+Rework add_sta functions to add_link_sta functions, and support
+.change_sta_links callback.
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt76_connac_mcu.h |   2 +
+ mt7996/main.c     | 309 ++++++++++++++++++++++++++++++++++++----------
+ mt7996/mcu.c      | 117 ++++++++++++++++++
+ mt7996/mcu.h      |  29 +++++
+ mt7996/mt7996.h   |   7 ++
+ 5 files changed, 398 insertions(+), 66 deletions(-)
+
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index f7da63658..d45765beb 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -814,7 +814,9 @@ enum {
+ 	STA_REC_HE_6G = 0x17,
+ 	STA_REC_HE_V2 = 0x19,
+ 	STA_REC_MLD = 0x20,
++	STA_REC_EHT_MLD = 0x21,
+ 	STA_REC_EHT = 0x22,
++	STA_REC_MLD_TEARDOWN = 0x23,
+ 	STA_REC_PN_INFO = 0x26,
+ 	STA_REC_KEY_V3 = 0x27,
+ 	STA_REC_HDRT = 0x28,
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 0ad3e3a6f..a0c34aa88 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -926,42 +926,234 @@ mt7996_channel_switch_beacon(struct ieee80211_hw *hw,
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
++static void mt7996_remove_link_sta(struct mt7996_dev *dev,
++				   struct ieee80211_bss_conf *conf,
++				   struct mt7996_bss_conf *mconf,
++				   struct ieee80211_link_sta *link_sta,
++				   struct mt7996_link_sta *mlink)
++{
++	struct ieee80211_sta *sta = link_sta->sta;
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	int i;
++
++	if (!mlink)
++		return;
++
++	for (i = 0; i < ARRAY_SIZE(mlink->wcid.aggr); i++)
++			mt76_rx_aggr_stop(&dev->mt76, &mlink->wcid, i);
++
++	if (sta->mlo)
++		mt7996_mcu_teardown_mld_sta(dev, mconf, mlink);
++	else
++		mt7996_mcu_add_sta(dev, conf, mconf, link_sta, mlink, false, false);
++
++	mt7996_mac_wtbl_update(dev, mlink->wcid.idx,
++			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
++
++	for (i = 0; i < ARRAY_SIZE(mlink->twt.flow); i++)
++		mt7996_mac_twt_teardown_flow(dev, mlink, i);
++
++	rcu_assign_pointer(mlink->sta->link[mlink->wcid.link_id], NULL);
++
++	spin_lock_bh(&dev->mt76.sta_poll_lock);
++	if (!list_empty(&mlink->wcid.poll_list))
++		list_del_init(&mlink->wcid.poll_list);
++	if (!list_empty(&mlink->rc_list))
++		list_del_init(&mlink->rc_list);
++	spin_unlock_bh(&dev->mt76.sta_poll_lock);
++
++	/* TODO: update primary link */
++	if (sta->valid_links) {
++		if (mlink->wcid.link_id == msta->pri_link)
++			msta->pri_link = msta->sec_link;
++
++		if (sta->valid_links & ~(BIT(msta->pri_link)))
++			msta->sec_link = __ffs(sta->valid_links & ~(BIT(msta->pri_link)));
++		else
++			msta->sec_link = msta->pri_link;
++	}
++
++	mt76_wcid_cleanup(&dev->mt76, &mlink->wcid);
++	mt76_wcid_mask_clear(dev->mt76.wcid_mask, mlink->wcid.idx);
++	mt76_wcid_mask_clear(dev->mt76.wcid_phy_mask, mlink->wcid.idx);
++
++	if (mlink != &msta->deflink)
++		kfree(mlink);
++}
++
++static int mt7996_add_link_sta(struct mt7996_dev *dev,
++			       struct ieee80211_bss_conf *conf,
++			       struct mt7996_bss_conf *mconf,
++			       struct ieee80211_link_sta *link_sta, bool assoc)
++{
++	struct ieee80211_sta *sta = link_sta->sta;
++	struct mt7996_vif *mvif = (struct mt7996_vif *)conf->vif->drv_priv;
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	u8 link_id = link_sta->link_id;
++	struct mt7996_link_sta *mlink = NULL;
++	int idx, ret;
++
++	if (!rcu_access_pointer(msta->link[link_id])) {
++		idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT7996_WTBL_STA);
++		if (idx < 0)
++			return -ENOSPC;
++
++		if (sta->mlo) {
++			mlink = kzalloc(sizeof(*mlink), GFP_KERNEL);
++			if (!mlink)
++				return -ENOMEM;
++		} else {
++			mlink = &msta->deflink;
++		}
++
++		INIT_LIST_HEAD(&mlink->rc_list);
++		INIT_LIST_HEAD(&mlink->wcid.poll_list);
++		msta->vif = mvif;
++		mlink->wcid.sta = 1;
++		mlink->wcid.idx = idx;
++		mlink->wcid.phy_idx = mconf->phy->mt76->band_idx;
++		mlink->wcid.tx_info |= MT_WCID_TX_INFO_SET;
++		mlink->wcid.def_wcid = &msta->deflink.wcid;
++		mlink->sta = msta;
++		if (sta->valid_links) {
++			mlink->wcid.link_valid = true;
++			mlink->wcid.link_id = link_id;
++			if (sta->valid_links & ~(BIT(msta->pri_link)))
++				msta->sec_link = __ffs(sta->valid_links &
++						       ~(BIT(msta->pri_link)));
++			else
++				msta->sec_link = msta->pri_link;
++		}
++
++		rcu_assign_pointer(msta->link[link_id], mlink);
++
++		ewma_signal_init(&mlink->wcid.rssi);
++		if (mconf->phy->mt76->band_idx == MT_BAND1)
++			mt76_wcid_mask_set(dev->mt76.wcid_phy_mask, idx);
++		rcu_assign_pointer(dev->mt76.wcid[idx], &mlink->wcid);
++		mt76_wcid_init(&mlink->wcid);
++	}
++
++	if (!assoc)
++		return 0;
++
++	if (!mlink)
++		mlink = mlink_dereference_protected(msta, link_id);
++	mt7996_mac_wtbl_update(dev, mlink->wcid.idx,
++			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
++
++	ret = mt7996_mcu_add_sta(dev, conf, mconf, link_sta, mlink, true, true);
++	if (ret)
++		goto error;
++
++	ret = mt7996_mcu_add_rate_ctrl(dev, conf, mconf, link_sta, mlink, false);
++	if (ret)
++		goto error;
++
++	ewma_avg_signal_init(&mlink->avg_ack_signal);
++
++	return 0;
++error:
++	mt7996_remove_link_sta(dev, conf, mconf, link_sta, mlink);
++	return ret;
++}
++
++static void
++mt7996_mac_sta_remove_links(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++			    struct ieee80211_sta *sta, unsigned long rem)
++{
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	unsigned int link_id;
++
++	for_each_set_bit(link_id, &rem, IEEE80211_MLD_MAX_NUM_LINKS) {
++		struct mt7996_bss_conf *mconf =
++			mconf_dereference_protected(mvif, link_id);
++		struct mt7996_link_sta *mlink =
++			mlink_dereference_protected(msta, link_id);
++		struct ieee80211_bss_conf *conf =
++			link_conf_dereference_protected(vif, link_id);
++		struct ieee80211_link_sta *link_sta =
++			link_sta_dereference_protected(sta, link_id);
++
++		mt7996_remove_link_sta(dev, conf, mconf, link_sta, mlink);
++	}
++}
++
++static int
++mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++			 struct ieee80211_sta *sta, unsigned long add,
++			 bool assoc)
++{
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_link_sta *mlink;
++	unsigned int link_id;
++	int i, ret;
++
++	for_each_set_bit(link_id, &add, IEEE80211_MLD_MAX_NUM_LINKS) {
++		struct mt7996_bss_conf *mconf =
++			mconf_dereference_protected(mvif, link_id);
++		struct ieee80211_bss_conf *conf =
++			link_conf_dereference_protected(vif, link_id);
++		struct ieee80211_link_sta *link_sta =
++			link_sta_dereference_protected(sta, link_id);
++
++		ret = mt7996_add_link_sta(dev, conf, mconf, link_sta, assoc);
++		if (ret)
++			goto error;
++	}
++
++	if (!assoc)
++		return 0;
++
++	mlink = mlink_dereference_protected(msta, msta->pri_link);
++	for (i = 0; i < ARRAY_SIZE(sta->txq); i++) {
++		struct mt76_txq *mtxq;
++
++		if (!sta->txq[i])
++			continue;
++		mtxq = (struct mt76_txq *)sta->txq[i]->drv_priv;
++		mtxq->wcid = mlink->wcid.idx;
++	}
++
++	ret = mt7996_mcu_add_mld_sta(dev, vif, sta, add);
++	if (ret)
++		goto error;
++
++	return 0;
++error:
++	mt7996_mac_sta_remove_links(dev, vif, sta, add);
++	return ret;
++}
++
+ int mt7996_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 		       struct ieee80211_sta *sta)
+ {
+ 	struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76);
+-	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_bss_conf *mconf = mconf_dereference_protected(mvif, 0);
+-	struct mt7996_link_sta *mlink = &msta->deflink;
+-	u8 band_idx = mconf->phy->mt76->band_idx;
+-	int idx;
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct mt7996_bss_conf *mconf;
++	u8 link_id = sta->valid_links ? __ffs(sta->valid_links) : 0;
++	unsigned long add = BIT(link_id);
++	int ret;
+ 
+ #ifdef CONFIG_MTK_VENDOR
+ 	struct mt7996_phy *phy = &dev->phy;
+ #endif
+ 
+-	idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT7996_WTBL_STA);
+-	if (idx < 0)
+-		return -ENOSPC;
+-
+-	INIT_LIST_HEAD(&mlink->rc_list);
+-	INIT_LIST_HEAD(&mlink->wcid.poll_list);
+-	msta->vif = mvif;
+-	mlink->wcid.sta = 1;
+-	mlink->wcid.idx = idx;
+-	mlink->wcid.phy_idx = band_idx;
+-	mlink->wcid.tx_info |= MT_WCID_TX_INFO_SET;
+-	mlink->sta = msta;
+-
+-	rcu_assign_pointer(msta->link[0], mlink);
++	msta->pri_link = link_id;
++	ret = mt7996_mac_sta_add_links(dev, vif, sta, add, false);
++	if (ret)
++		return ret;
+ 
+ #ifdef CONFIG_MTK_VENDOR
++	mconf = mconf_dereference_protected(mvif, link_id);
+ 	mt7996_vendor_amnt_sta_remove(mconf->phy, sta);
+ #endif
+ 
+ #ifdef CONFIG_MTK_VENDOR
+-	switch (band_idx) {
++	switch (mconf->phy->mt76->band_idx) {
+ 	case MT_BAND1:
+ 		phy = mt7996_phy2(dev);
+ 		break;
+@@ -984,28 +1176,11 @@ void mt7996_mac_sta_assoc(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 			  struct ieee80211_sta *sta)
+ {
+ 	struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76);
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+-	struct mt7996_bss_conf *mconf;
+-	struct mt7996_link_sta *mlink;
+-	struct ieee80211_bss_conf *conf;
+-	struct ieee80211_link_sta *link_sta;
++	unsigned long add = sta->valid_links ?: BIT(0);
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+-	conf = link_conf_dereference_protected(vif, 0);
+-	mconf = mconf_dereference_protected(mvif, 0);
+-	link_sta = link_sta_dereference_protected(sta, 0);
+-	mlink = mlink_dereference_protected(msta, 0);
+-
+-	mt7996_mac_wtbl_update(dev, mlink->wcid.idx,
+-			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+-
+-	mt7996_mcu_add_sta(dev, conf, mconf, link_sta, mlink, true, true);
+-	mt7996_mcu_add_rate_ctrl(dev, conf, mconf, link_sta, mlink, false);
+-	mlink->wcid.tx_info |= MT_WCID_TX_INFO_SET;
+-
+-	ewma_avg_signal_init(&mlink->avg_ack_signal);
++	mt7996_mac_sta_add_links(dev, vif, sta, add, true);
+ 
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+@@ -1014,34 +1189,9 @@ void mt7996_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 			   struct ieee80211_sta *sta)
+ {
+ 	struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76);
+-	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+-	struct mt7996_bss_conf *mconf;
+-	struct mt7996_link_sta *mlink;
+-	struct ieee80211_bss_conf *conf;
+-	struct ieee80211_link_sta *link_sta;
+-	int i;
+-
+-	conf = link_conf_dereference_protected(vif, 0);
+-	mconf = mconf_dereference_protected(mvif, 0);
+-	link_sta = link_sta_dereference_protected(sta, 0);
+-	mlink = mlink_dereference_protected(msta, 0);
+-	mt7996_mcu_add_sta(dev, conf, mconf, link_sta, mlink, false, false);
+-
+-	mt7996_mac_wtbl_update(dev, mlink->wcid.idx,
+-			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+-
+-	for (i = 0; i < ARRAY_SIZE(mlink->twt.flow); i++)
+-		mt7996_mac_twt_teardown_flow(dev, mlink, i);
++	unsigned long rem = sta->valid_links ?: BIT(0);
+ 
+-	spin_lock_bh(&mdev->sta_poll_lock);
+-	if (!list_empty(&mlink->wcid.poll_list))
+-		list_del_init(&mlink->wcid.poll_list);
+-	if (!list_empty(&mlink->rc_list))
+-		list_del_init(&mlink->rc_list);
+-	spin_unlock_bh(&mdev->sta_poll_lock);
+-
+-	rcu_assign_pointer(msta->link[0], NULL);
++	mt7996_mac_sta_remove_links(dev, vif, sta, rem);
+ }
+ 
+ static void mt7996_tx(struct ieee80211_hw *hw,
+@@ -2148,6 +2298,32 @@ mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	return ret;
+ }
+ 
++static int
++mt7996_change_sta_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
++			struct ieee80211_sta *sta, u16 old_links, u16 new_links)
++{
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++	unsigned long add = new_links & ~old_links;
++	unsigned long rem = old_links & ~new_links;
++	int ret = 0;
++
++	mutex_lock(&dev->mt76.mutex);
++
++	if (rem)
++		mt7996_mac_sta_remove_links(dev, vif, sta, rem);
++
++	ret = mt7996_mac_sta_add_links(dev, vif, sta, add, false);
++	if (ret)
++		goto remove;
++
++	goto out;
++remove:
++	mt7996_mac_sta_remove_links(dev, vif, sta, add);
++out:
++	mutex_unlock(&dev->mt76.mutex);
++	return ret;
++}
++
+ const struct ieee80211_ops mt7996_ops = {
+ 	.tx = mt7996_tx,
+ 	.start = mt7996_start,
+@@ -2205,4 +2381,5 @@ const struct ieee80211_ops mt7996_ops = {
+ 	.unassign_vif_chanctx = mt7996_unassign_vif_chanctx,
+ 	.switch_vif_chanctx = mt7996_switch_vif_chanctx,
+ 	.change_vif_links = mt7996_change_vif_links,
++	.change_sta_links = mt7996_change_sta_links,
+ };
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index a27769426..72daf69dd 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -2423,6 +2423,123 @@ out:
+ 				     MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true);
+ }
+ 
++static void
++mt7996_mcu_sta_mld_setup_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
++			     struct ieee80211_sta *sta)
++{
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	struct sta_rec_mld_setup *mld_setup;
++	struct mld_setup_link *mld_setup_link;
++	struct mt7996_link_sta *mlink;
++	struct mt7996_bss_conf *mconf;
++	struct tlv *tlv;
++	unsigned long valid_links = sta->valid_links;
++	unsigned int link_id;
++
++	mlink = mlink_dereference_protected(msta, msta->pri_link);
++	if (!mlink)
++		return;
++
++	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_MLD,
++				      sizeof(*mld_setup) +
++				      sizeof(struct mld_setup_link) *
++					     hweight16(sta->valid_links));
++
++	mld_setup = (struct sta_rec_mld_setup *)tlv;
++	memcpy(mld_setup->mld_addr, sta->addr, ETH_ALEN);
++	mld_setup->setup_wcid = cpu_to_le16(mlink->wcid.idx);
++	mld_setup->primary_id = cpu_to_le16(mlink->wcid.idx);
++	if (msta->sec_link != msta->pri_link) {
++		mlink = mlink_dereference_protected(msta, msta->sec_link);
++		if (!mlink)
++			return;
++	}
++	mld_setup->seconed_id = cpu_to_le16(mlink->wcid.idx);
++	mld_setup->link_num = hweight16(sta->valid_links);
++
++	mld_setup_link = (struct mld_setup_link *)mld_setup->link_info;
++	for_each_set_bit(link_id, &valid_links, IEEE80211_MLD_MAX_NUM_LINKS) {
++		mlink = mlink_dereference_protected(msta, link_id);
++		mconf = mconf_dereference_protected(msta->vif, link_id);
++
++		mld_setup_link->wcid = cpu_to_le16(mlink->wcid.idx);
++		mld_setup_link->bss_idx = mconf->mt76.idx;
++		mld_setup_link++;
++	}
++}
++
++static void
++mt7996_mcu_sta_eht_mld_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
++			   struct ieee80211_sta *sta)
++{
++	struct sta_rec_eht_mld *eht_mld;
++	struct tlv *tlv;
++	int i;
++
++	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_EHT_MLD, sizeof(*eht_mld));
++	eht_mld = (struct sta_rec_eht_mld *)tlv;
++
++	for (i = 0; i < ARRAY_SIZE(eht_mld->str_cap); i++)
++		eht_mld->str_cap[i] = 0x7;
++	/* TODO:
++	eht_mld->nsep = ;
++	eht_mld->eml_cap = cpu_to_le16()
++	*/
++}
++
++int mt7996_mcu_add_mld_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++			   struct ieee80211_sta *sta, unsigned long add)
++{
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	unsigned int link_id;
++
++	if (!sta->mlo)
++		return 0;
++
++	for_each_set_bit(link_id, &add, IEEE80211_MLD_MAX_NUM_LINKS) {
++		struct mt7996_bss_conf *mconf =
++			mconf_dereference_protected(mvif, link_id);
++		struct mt7996_link_sta *mlink =
++			mlink_dereference_protected(msta, link_id);
++		struct sk_buff *skb;
++		int ret;
++
++		skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76,
++						      &mlink->wcid,
++						      MT7996_STA_UPDATE_MAX_SIZE);
++		if (IS_ERR(skb))
++			return PTR_ERR(skb);
++		/* starec mld setup */
++		mt7996_mcu_sta_mld_setup_tlv(dev, skb, sta);
++		/* starec eht mld */
++		mt7996_mcu_sta_eht_mld_tlv(dev, skb, sta);
++		ret = mt76_mcu_skb_send_msg(&dev->mt76, skb,
++					    MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true);
++		if (ret)
++			return ret;
++	}
++	return 0;
++}
++int mt7996_mcu_teardown_mld_sta(struct mt7996_dev *dev,
++				struct mt7996_bss_conf *mconf,
++				struct mt7996_link_sta *mlink)
++{
++	struct sk_buff *skb;
++
++	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76,
++					      &mconf->mt76,
++					      &mlink->wcid,
++					      MT7996_STA_UPDATE_MAX_SIZE);
++	if (IS_ERR(skb))
++		return PTR_ERR(skb);
++
++	mt76_connac_mcu_add_tlv(skb, STA_REC_MLD_TEARDOWN, sizeof(struct tlv));
++
++	return mt76_mcu_skb_send_msg(&dev->mt76, skb,
++				     MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true);
++}
++
+ static int
+ mt7996_mcu_sta_key_tlv(struct mt76_wcid *wcid,
+ 		       struct sk_buff *skb,
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 1dc5f68e5..7d200a334 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -697,6 +697,35 @@ struct sta_rec_hdr_trans {
+ 	u8 mesh;
+ } __packed;
+ 
++struct sta_rec_mld_setup {
++	__le16 tag;
++	__le16 len;
++	u8 mld_addr[ETH_ALEN];
++	__le16 primary_id;
++	__le16 seconed_id;
++	__le16 setup_wcid;
++	u8 link_num;
++	u8 info;
++	u8 __rsv[2];
++	u8 link_info[];
++} __packed;
++
++struct mld_setup_link {
++	__le16 wcid;
++	u8 bss_idx;
++	u8 __rsv[1];
++} __packed;
++
++struct sta_rec_eht_mld {
++	__le16 tag;
++	__le16 len;
++	u8 nsep;
++	u8 __rsv1[2];
++	u8 str_cap[__MT_MAX_BAND];
++	__le16 eml_cap;
++	u8 __rsv2[4];
++} __packed;
++
+ struct hdr_trans_en {
+ 	__le16 tag;
+ 	__le16 len;
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 15912e8e8..69e7f64a5 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -330,6 +330,8 @@ struct mt7996_sta {
+ 	struct mt7996_link_sta __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
+ 
+ 	struct mt7996_vif *vif;
++	u8 pri_link;
++	u8 sec_link;
+ };
+ 
+ struct mt7996_bss_conf {
+@@ -852,6 +854,9 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_bss_conf *conf,
+ 		       struct mt7996_bss_conf *mconf,
+ 		       struct ieee80211_link_sta *link_sta,
+ 		       struct mt7996_link_sta *mlink, bool enable, bool newly);
++int mt7996_mcu_teardown_mld_sta(struct mt7996_dev *dev,
++				struct mt7996_bss_conf *mconf,
++				struct mt7996_link_sta *mlink);
+ int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev,
+ 			 struct ieee80211_ampdu_params *params,
+ 			 bool add);
+@@ -875,6 +880,8 @@ int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev,
+ 			     struct mt7996_bss_conf *mconf,
+ 			     struct ieee80211_link_sta *link_sta,
+ 			     struct mt7996_link_sta *mlink, bool changed);
++int mt7996_mcu_add_mld_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++			   struct ieee80211_sta *sta, unsigned long add);
+ int mt7996_set_channel(struct mt7996_phy *phy, struct cfg80211_chan_def *chandef);
+ int mt7996_mcu_set_chan_info(struct mt7996_phy *phy, u16 tag);
+ int mt7996_mcu_set_tx(struct mt7996_dev *dev, struct mt7996_bss_conf *mconf);
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0107-wifi-mt76-mt7996-introduce-mt7996_band_phy-for-ch-ba.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0107-wifi-mt76-mt7996-introduce-mt7996_band_phy-for-ch-ba.patch
new file mode 100644
index 0000000..99a5694
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0107-wifi-mt76-mt7996-introduce-mt7996_band_phy-for-ch-ba.patch
@@ -0,0 +1,271 @@
+From 4a201eaadf0009c0ee7e4fc2c35e398e1558892b Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Fri, 1 Dec 2023 16:01:53 +0800
+Subject: [PATCH 107/120] wifi: mt76: mt7996: introduce mt7996_band_phy() for
+ ch band and phy mapping
+
+For MLO devices, one ieee80211_hw can be mapped to several bands, and
+thus several mt76_phy. Add mt7996_band_phy() to temporarily do the
+mapping.
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/main.c   | 150 +++++++++++++++++++++++++++++-------------------
+ mt7996/mt7996.h |  22 +++++++
+ 2 files changed, 113 insertions(+), 59 deletions(-)
+
+diff --git a/mt7996/main.c b/mt7996/main.c
+index a0c34aa88..49406dba5 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -41,9 +41,8 @@ static void mt7996_testmode_disable_all(struct mt7996_dev *dev)
+ int mt7996_run(struct ieee80211_hw *hw)
+ {
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+-	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	bool running;
+-	int ret;
++	int band, ret;
+ 
+ 	running = mt7996_dev_running(dev);
+ 	if (!running) {
+@@ -62,65 +61,76 @@ int mt7996_run(struct ieee80211_hw *hw)
+ 
+ 	mt7996_testmode_disable_all(dev);
+ 
+-	mt7996_mac_enable_nf(dev, phy->mt76->band_idx);
++	for (band = 0; band < NUM_NL80211_BANDS; band++) {
++		struct mt7996_phy *phy;
+ 
+-	ret = mt7996_mcu_set_rts_thresh(phy, 0x92b);
+-	if (ret)
+-		goto out;
++		if (!hw->wiphy->bands[band])
++			continue;
+ 
+-	ret = mt7996_mcu_set_radio_en(phy, true);
+-	if (ret)
+-		goto out;
++		phy = mt7996_band_phy(hw, band);
+ 
+-	ret = mt7996_mcu_set_chan_info(phy, UNI_CHANNEL_RX_PATH);
+-	if (ret)
+-		goto out;
++		if (!phy || test_bit(MT76_STATE_RUNNING, &phy->mt76->state))
++			continue;
+ 
+-	/* set a parking channel */
+-	ret = mt7996_mcu_set_chan_info(phy, UNI_CHANNEL_SWITCH);
+-	if (ret)
+-		goto out;
++		mt7996_mac_enable_nf(dev, phy->mt76->band_idx);
+ 
+-	ret = mt7996_mcu_set_thermal_throttling(phy, MT7996_THERMAL_THROTTLE_MAX);
+-	if (ret)
+-		goto out;
++		ret = mt7996_mcu_set_rts_thresh(phy, 0x92b);
++		if (ret)
++			goto out;
+ 
+-	ret = mt7996_mcu_set_thermal_protect(phy, true);
+-	if (ret)
+-		goto out;
++		ret = mt7996_mcu_set_radio_en(phy, true);
++		if (ret)
++			goto out;
+ 
+-	ret = mt7996_mcu_set_scs(phy, SCS_ENABLE);
+-	if (ret)
+-		goto out;
++		ret = mt7996_mcu_set_chan_info(phy, UNI_CHANNEL_RX_PATH);
++		if (ret)
++			goto out;
++
++		/* set a parking channel */
++		ret = mt7996_mcu_set_chan_info(phy, UNI_CHANNEL_SWITCH);
++		if (ret)
++			goto out;
++
++		ret = mt7996_mcu_set_thermal_throttling(phy, MT7996_THERMAL_THROTTLE_MAX);
++		if (ret)
++			goto out;
++
++		ret = mt7996_mcu_set_thermal_protect(phy, true);
++		if (ret)
++			goto out;
++
++		ret = mt7996_mcu_set_scs(phy, SCS_ENABLE);
++		if (ret)
++			goto out;
+ 
+ #ifdef CONFIG_MTK_DEBUG
+-	phy->sr_enable = true;
+-	phy->enhanced_sr_enable = true;
+-	phy->thermal_protection_enable = true;
+-	ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_SKU_POWER_LIMIT_CTRL,
+-						dev->dbg.sku_disable ? 0 : phy->sku_limit_en);
+-
+-	ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_BACKOFF_POWER_LIMIT_CTRL,
+-						dev->dbg.sku_disable ? 0 : phy->sku_path_en);
++		phy->sr_enable = true;
++		phy->enhanced_sr_enable = true;
++		phy->thermal_protection_enable = true;
++		ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_SKU_POWER_LIMIT_CTRL,
++						   dev->dbg.sku_disable ? 0 : phy->sku_limit_en);
++
++		ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_BACKOFF_POWER_LIMIT_CTRL,
++						   dev->dbg.sku_disable ? 0 : phy->sku_path_en);
+ #else
+-	ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_SKU_POWER_LIMIT_CTRL,
+-						phy->sku_limit_en);
+-	ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_BACKOFF_POWER_LIMIT_CTRL,
+-						phy->sku_path_en);
++		ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_SKU_POWER_LIMIT_CTRL,
++						   phy->sku_limit_en);
++		ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_BACKOFF_POWER_LIMIT_CTRL,
++						   phy->sku_path_en);
+ #endif
+-	if (ret)
+-		goto out;
+-
+-	set_bit(MT76_STATE_RUNNING, &phy->mt76->state);
++		if (ret)
++			goto out;
+ 
+-	ieee80211_queue_delayed_work(hw, &phy->mt76->mac_work,
+-				     MT7996_WATCHDOG_TIME);
++		set_bit(MT76_STATE_RUNNING, &phy->mt76->state);
+ 
+-	ieee80211_queue_delayed_work(mt76_hw(dev), &dev->scs_work, HZ);
++		ieee80211_queue_delayed_work(hw, &phy->mt76->mac_work,
++					     MT7996_WATCHDOG_TIME);
+ 
+-	if (!running)
+-		mt7996_mac_reset_counters(phy);
++		if (!running)
++			mt7996_mac_reset_counters(phy);
++	}
+ 
++	ieee80211_queue_delayed_work(mt76_hw(dev), &dev->scs_work, HZ);
+ out:
+ 	return ret;
+ }
+@@ -142,18 +152,29 @@ static int mt7996_start(struct ieee80211_hw *hw)
+ static void mt7996_stop(struct ieee80211_hw *hw)
+ {
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+-	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	int band;
+ 
+-	cancel_delayed_work_sync(&phy->mt76->mac_work);
+ 	cancel_delayed_work_sync(&dev->scs_work);
+ 
+-	mutex_lock(&dev->mt76.mutex);
++	for (band = 0; band < NUM_NL80211_BANDS; band++) {
++		struct mt7996_phy *phy;
+ 
+-	mt7996_mcu_set_radio_en(phy, false);
++		if (!hw->wiphy->bands[band])
++			continue;
+ 
+-	clear_bit(MT76_STATE_RUNNING, &phy->mt76->state);
++		phy = mt7996_band_phy(hw, band);
+ 
+-	mutex_unlock(&dev->mt76.mutex);
++		if (!phy || !test_bit(MT76_STATE_RUNNING, &phy->mt76->state) ||
++		    (phy->chanctx && phy->chanctx->nbss_assigned))
++			continue;
++
++		cancel_delayed_work_sync(&phy->mt76->mac_work);
++
++		mutex_lock(&dev->mt76.mutex);
++		mt7996_mcu_set_radio_en(phy, false);
++		clear_bit(MT76_STATE_RUNNING, &phy->mt76->state);
++		mutex_unlock(&dev->mt76.mutex);
++	}
+ }
+ 
+ static inline int get_free_idx(u32 mask, u8 start, u8 end)
+@@ -2054,7 +2075,7 @@ mt7996_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	       struct ieee80211_scan_request *hw_req)
+ {
+ 	struct cfg80211_scan_request *req = &hw_req->req;
+-	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_phy *phy = mt7996_band_phy(hw, req->channels[0]->band);
+ 
+ 	mutex_lock(&phy->dev->mt76.mutex);
+ 	if (WARN_ON(phy->scan_req || phy->scan_chan)) {
+@@ -2076,19 +2097,30 @@ mt7996_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ static void
+ mt7996_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+ {
+-	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	int band;
+ 
+-	cancel_delayed_work_sync(&phy->scan_work);
++	for (band = 0; band < NUM_NL80211_BANDS; band++) {
++		struct mt7996_phy *phy;
+ 
+-	mutex_lock(&phy->dev->mt76.mutex);
+-	mt7996_scan_complete(phy, true);
+-	mutex_unlock(&phy->dev->mt76.mutex);
++		if (!hw->wiphy->bands[band])
++			continue;
++
++		phy = mt7996_band_phy(hw, band);
++		if (!(test_bit(MT76_SCANNING, &phy->mt76->state)))
++			continue;
++
++		cancel_delayed_work_sync(&phy->scan_work);
++
++		mutex_lock(&phy->dev->mt76.mutex);
++		mt7996_scan_complete(phy, true);
++		mutex_unlock(&phy->dev->mt76.mutex);
++	}
+ }
+ 
+ static int
+ mt7996_add_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *conf)
+ {
+-	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_phy *phy = mt7996_band_phy(hw, conf->def.chan->band);
+ 	struct mt7996_chanctx *ctx = mt7996_chanctx_get(conf);
+ 	int ret;
+ 
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 69e7f64a5..2932ab8d5 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -784,6 +784,28 @@ mt7996_get_background_radar_cap(struct mt7996_dev *dev)
+ 	return 1;
+ }
+ 
++static inline struct mt7996_phy *
++mt7996_band_phy(struct ieee80211_hw *hw, enum nl80211_band band)
++{
++	struct mt76_phy *phy = hw->priv;
++
++	if (!(hw->wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO))
++		return phy->priv;
++
++	/* TODO: mlo: temporarily hardcode */
++	if (band == NL80211_BAND_6GHZ)
++		phy = phy->dev->phys[MT_BAND2];
++	else if (band == NL80211_BAND_5GHZ)
++		phy = phy->dev->phys[MT_BAND1];
++	else
++		phy = phy->dev->phys[MT_BAND0];
++
++	if (!phy)
++		phy = hw->priv;
++
++	return phy->priv;
++}
++
+ static inline struct mt7996_chanctx *
+ mt7996_chanctx_get(struct ieee80211_chanctx_conf *ctx)
+ {
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0108-wifi-mt76-mt7996-rework-ieee80211_ops-callbacks-for-.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0108-wifi-mt76-mt7996-rework-ieee80211_ops-callbacks-for-.patch
new file mode 100644
index 0000000..a8c4a2d
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0108-wifi-mt76-mt7996-rework-ieee80211_ops-callbacks-for-.patch
@@ -0,0 +1,496 @@
+From e7396c6ce0cf5762179d3cb7c92aa0a958b7459d Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Fri, 1 Dec 2023 17:26:43 +0800
+Subject: [PATCH 108/120] wifi: mt76: mt7996: rework ieee80211_ops callbacks
+ for link consideration
+
+Extend ieee80211 callback functions to support multi-link operation.
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/main.c | 309 ++++++++++++++++++++++++++++++++------------------
+ 1 file changed, 200 insertions(+), 109 deletions(-)
+
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 49406dba5..f4488fdf7 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -568,7 +568,6 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+ 			  struct ieee80211_key_conf *key)
+ {
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+-	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta = sta ? (struct mt7996_sta *)sta->drv_priv :
+ 				  &mvif->sta;
+@@ -578,69 +577,76 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+ 	u8 *wcid_keyidx;
+ 	int idx = key->keyidx;
+ 	int err = 0;
++	unsigned long add;
++	unsigned int link_id;
+ 
+-	/* The hardware does not support per-STA RX GTK, fallback
+-	 * to software mode for these.
+-	 */
+-	if ((vif->type == NL80211_IFTYPE_ADHOC ||
+-	     vif->type == NL80211_IFTYPE_MESH_POINT) &&
+-	    (key->cipher == WLAN_CIPHER_SUITE_TKIP ||
+-	     key->cipher == WLAN_CIPHER_SUITE_CCMP) &&
+-	    !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
+-		return -EOPNOTSUPP;
++	if (key->link_id >= 0) {
++		add = BIT(key->link_id);
++	} else {
++		if (sta)
++			add = sta->valid_links ?: BIT(0);
++		else
++			add = vif->valid_links ?: BIT(0);
++	}
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	conf = link_conf_dereference_protected(vif, 0);
+-	mconf = mconf_dereference_protected(mvif, 0);
+-	mlink = mlink_dereference_protected(msta, 0);
+-	wcid_keyidx = &mlink->wcid.hw_key_idx;
+-
+-	/* fall back to sw encryption for unsupported ciphers */
+-	switch (key->cipher) {
+-	case WLAN_CIPHER_SUITE_TKIP:
+-	case WLAN_CIPHER_SUITE_CCMP:
+-	case WLAN_CIPHER_SUITE_CCMP_256:
+-	case WLAN_CIPHER_SUITE_GCMP:
+-	case WLAN_CIPHER_SUITE_GCMP_256:
+-	case WLAN_CIPHER_SUITE_SMS4:
+-		break;
+-	case WLAN_CIPHER_SUITE_AES_CMAC:
+-		key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIE;
+-		fallthrough;
+-	case WLAN_CIPHER_SUITE_BIP_CMAC_256:
+-	case WLAN_CIPHER_SUITE_BIP_GMAC_128:
+-	case WLAN_CIPHER_SUITE_BIP_GMAC_256:
+-		if (key->keyidx == 6 || key->keyidx == 7)
++
++	for_each_set_bit(link_id, &add, IEEE80211_MLD_MAX_NUM_LINKS) {
++		conf = link_conf_dereference_protected(vif, link_id);
++		mconf = mconf_dereference_protected(mvif, link_id);
++		mlink = mlink_dereference_protected(msta, link_id);
++		wcid_keyidx = &mlink->wcid.hw_key_idx;
++
++		if (!conf || !mconf || !mlink)
++			continue;
++
++		/* fall back to sw encryption for unsupported ciphers */
++		switch (key->cipher) {
++		case WLAN_CIPHER_SUITE_TKIP:
++		case WLAN_CIPHER_SUITE_CCMP:
++		case WLAN_CIPHER_SUITE_CCMP_256:
++		case WLAN_CIPHER_SUITE_GCMP:
++		case WLAN_CIPHER_SUITE_GCMP_256:
++		case WLAN_CIPHER_SUITE_SMS4:
+ 			break;
+-		fallthrough;
+-	case WLAN_CIPHER_SUITE_WEP40:
+-	case WLAN_CIPHER_SUITE_WEP104:
+-	default:
+-		mutex_unlock(&dev->mt76.mutex);
+-		return -EOPNOTSUPP;
+-	}
++		case WLAN_CIPHER_SUITE_AES_CMAC:
++			key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIE;
++			fallthrough;
++		case WLAN_CIPHER_SUITE_BIP_CMAC_256:
++		case WLAN_CIPHER_SUITE_BIP_GMAC_128:
++		case WLAN_CIPHER_SUITE_BIP_GMAC_256:
++			if (key->keyidx == 6 || key->keyidx == 7)
++				break;
++			fallthrough;
++		case WLAN_CIPHER_SUITE_WEP40:
++		case WLAN_CIPHER_SUITE_WEP104:
++		default:
++			mutex_unlock(&dev->mt76.mutex);
++			return -EOPNOTSUPP;
++		}
+ 
+-	if (cmd == SET_KEY && !sta && !mconf->mt76.cipher) {
+-		mconf->mt76.cipher = mt76_connac_mcu_get_cipher(key->cipher);
+-		mt7996_mcu_add_bss_info(phy, conf, mconf, mlink, true);
+-	}
++		if (cmd == SET_KEY && !sta && !mconf->mt76.cipher) {
++			mconf->mt76.cipher = mt76_connac_mcu_get_cipher(key->cipher);
++			mt7996_mcu_add_bss_info(mconf->phy, conf, mconf, mlink, true);
++		}
+ 
+-	if (cmd == SET_KEY) {
+-		*wcid_keyidx = idx;
+-	} else {
+-		if (idx == *wcid_keyidx)
+-			*wcid_keyidx = -1;
+-		goto out;
+-	}
++		if (cmd == SET_KEY) {
++			*wcid_keyidx = idx;
++		} else {
++			if (idx == *wcid_keyidx)
++				*wcid_keyidx = -1;
++			goto out;
++		}
+ 
+-	mt76_wcid_key_setup(&dev->mt76, &mlink->wcid, key);
++		mt76_wcid_key_setup(&dev->mt76, &mlink->wcid, key);
+ 
+-	if (key->keyidx == 6 || key->keyidx == 7)
+-		err = mt7996_mcu_bcn_prot_enable(dev, conf, mconf, mlink, key);
+-	else
+-		err = mt7996_mcu_add_key(&dev->mt76, mconf, key,
+-					 MCU_WMWA_UNI_CMD(STA_REC_UPDATE),
+-					 &mlink->wcid, cmd);
++		if (key->keyidx == 6 || key->keyidx == 7)
++			err = mt7996_mcu_bcn_prot_enable(dev, conf, mconf, mlink, key);
++		else
++			err = mt7996_mcu_add_key(&dev->mt76, mconf, key,
++						 MCU_WMWA_UNI_CMD(STA_REC_UPDATE),
++						 &mlink->wcid, cmd);
++	}
+ out:
+ 	mutex_unlock(&dev->mt76.mutex);
+ 
+@@ -696,7 +702,11 @@ mt7996_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	};
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	mconf = mconf_dereference_protected(mvif, 0);
++	mconf = mconf_dereference_protected(mvif, link_id);
++	if (!mconf) {
++		mutex_unlock(&dev->mt76.mutex);
++		return -EINVAL;
++	}
+ 
+ 	/* firmware uses access class index */
+ 	mconf->queue_params[mq_to_aci[queue]] = *params;
+@@ -839,19 +849,26 @@ mt7996_update_mu_group(struct ieee80211_hw *hw, struct ieee80211_bss_conf *conf,
+ static void mt7996_vif_cfg_changed(struct ieee80211_hw *hw,
+ 				   struct ieee80211_vif *vif, u64 changed)
+ {
+-	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+ 	if (changed & BSS_CHANGED_ASSOC && vif->cfg.assoc) {
+-		struct ieee80211_bss_conf *conf = &vif->bss_conf;
+ 		struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-		struct mt7996_bss_conf *mconf = mconf_dereference_protected(mvif, 0);
+-		struct mt7996_link_sta *mlink = mlink_dereference_protected(&mvif->sta, 0);
+-
+-		mt7996_mcu_add_bss_info(phy, conf, mconf, mlink, true);
+-		mt7996_mcu_add_sta(dev, conf, mconf, NULL, mlink, true, false);
++		unsigned long valid_links = vif->valid_links ?: BIT(0);
++		unsigned int link_id;
++
++		for_each_set_bit(link_id, &valid_links, IEEE80211_MLD_MAX_NUM_LINKS) {
++			struct ieee80211_bss_conf *conf =
++				link_conf_dereference_protected(vif, link_id);
++			struct mt7996_bss_conf *mconf =
++				mconf_dereference_protected(mvif, link_id);
++			struct mt7996_link_sta *mlink =
++				mlink_dereference_protected(&mvif->sta, link_id);
++
++			mt7996_mcu_add_bss_info(mconf->phy, conf, mconf, mlink, true);
++			mt7996_mcu_add_sta(dev, conf, mconf, NULL, mlink, true, false);
++		}
+ 	}
+ 
+ 	mutex_unlock(&dev->mt76.mutex);
+@@ -870,8 +887,13 @@ static void mt7996_link_info_changed(struct ieee80211_hw *hw,
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+-	mconf = mconf_dereference_protected(mvif, 0);
+-	mlink = mlink_dereference_protected(&mvif->sta, 0);
++	mconf = mconf_dereference_protected(mvif, info->link_id);
++	mlink = mlink_dereference_protected(&mvif->sta, info->link_id);
++	if (!mconf || !mlink)
++		goto out;
++
++	if (mconf->phy)
++		phy = mconf->phy;
+ 	/* station mode uses BSSID to map the wlan entry to a peer,
+ 	 * and then peer references bss_info_rfch to set bandwidth cap.
+ 	 */
+@@ -927,6 +949,7 @@ static void mt7996_link_info_changed(struct ieee80211_hw *hw,
+ 	if (changed & BSS_CHANGED_MU_GROUPS)
+ 		mt7996_update_mu_group(hw, info, mconf);
+ 
++out:
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
+@@ -937,13 +960,22 @@ mt7996_channel_switch_beacon(struct ieee80211_hw *hw,
+ {
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_bss_conf *mconf;
+-	struct ieee80211_bss_conf *conf;
++	struct mt7996_phy *phy = mt7996_band_phy(hw, chandef->chan->band);
++	unsigned long valid_links = vif->valid_links ?: BIT(0);
++	unsigned int link_id;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	mconf = mconf_dereference_protected(mvif, 0);
+-	conf = link_conf_dereference_protected(vif, 0);
+-	mt7996_mcu_add_beacon(hw, conf, mconf, true);
++	for_each_set_bit(link_id, &valid_links, IEEE80211_MLD_MAX_NUM_LINKS) {
++		struct mt7996_bss_conf *mconf =
++			mconf_dereference_protected(mvif, link_id);
++		struct ieee80211_bss_conf *conf =
++			link_conf_dereference_protected(vif, link_id);
++
++		if (!mconf || phy != mconf->phy)
++			continue;
++
++		mt7996_mcu_add_beacon(hw, conf, mconf, true);
++	}
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
+@@ -1215,34 +1247,70 @@ void mt7996_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif,
+ 	mt7996_mac_sta_remove_links(dev, vif, sta, rem);
+ }
+ 
++static void
++mt7996_sta_pre_rcu_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
++			  struct ieee80211_sta *sta)
++{
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	unsigned long rem = sta->valid_links ?: BIT(0);
++	unsigned int link_id;
++
++	mutex_lock(&dev->mt76.mutex);
++	spin_lock_bh(&dev->mt76.status_lock);
++	for_each_set_bit(link_id, &rem, IEEE80211_MLD_MAX_NUM_LINKS) {
++		struct mt7996_link_sta *mlink =
++			mlink_dereference_protected(msta, link_id);
++
++		rcu_assign_pointer(dev->mt76.wcid[mlink->wcid.idx], NULL);
++	}
++	spin_unlock_bh(&dev->mt76.status_lock);
++	mutex_unlock(&dev->mt76.mutex);
++}
++
+ static void mt7996_tx(struct ieee80211_hw *hw,
+ 		      struct ieee80211_tx_control *control,
+ 		      struct sk_buff *skb)
+ {
+-	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+-	struct mt76_phy *mphy = hw->priv;
++	struct mt76_phy *mphy;
+ 	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ 	struct ieee80211_vif *vif = info->control.vif;
+-	struct mt76_wcid *wcid = &dev->mt76.global_wcid;
+-	struct mt7996_link_sta *mlink;
++	struct mt76_wcid *wcid;
++	struct mt7996_vif *mvif;
++	struct mt7996_sta *msta;
+ 
+ 	if (control->sta) {
+-		struct mt7996_sta *msta;
+-
+ 		msta = (struct mt7996_sta *)control->sta->drv_priv;
+-		mlink = rcu_dereference(msta->link[0]);
+-		wcid = &mlink->wcid;
++		mvif = msta->vif;
++	} else if (vif) {
++		mvif = (struct mt7996_vif *)vif->drv_priv;
++		msta = &mvif->sta;
+ 	}
+ 
+-	if (vif && !control->sta) {
+-		struct mt7996_vif *mvif;
++	rcu_read_lock();
++	if (mvif && msta) {
++		struct mt7996_bss_conf *mconf;
++		struct mt7996_link_sta *mlink;
+ 
+-		mvif = (struct mt7996_vif *)vif->drv_priv;
+-		mlink = rcu_dereference(mvif->sta.link[0]);
++		u8 link_id = u32_get_bits(info->control.flags,
++					  IEEE80211_TX_CTRL_MLO_LINK);
++
++		if (link_id >= IEEE80211_LINK_UNSPECIFIED)
++			link_id = mvif->master_link_id;
++
++		mconf = rcu_dereference(mvif->link[link_id]);
++		mlink = rcu_dereference(msta->link[link_id]);
++		mphy = mconf->phy->mt76;
+ 		wcid = &mlink->wcid;
++	} else {
++		struct mt7996_dev *dev = mt7996_hw_dev(hw);
++
++		mphy = hw->priv;
++		wcid = &dev->mt76.global_wcid;
+ 	}
+ 
+ 	mt76_tx(mphy, control->sta, wcid, skb);
++	rcu_read_unlock();
+ }
+ 
+ static int mt7996_set_rts_threshold(struct ieee80211_hw *hw, u32 val)
+@@ -1278,7 +1346,7 @@ mt7996_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	mtxq = (struct mt76_txq *)txq->drv_priv;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	mlink = mlink_dereference_protected(msta, 0);
++	mlink = mlink_dereference_protected(msta, msta->pri_link);
+ 	switch (action) {
+ 	case IEEE80211_AMPDU_RX_START:
+ 		mt76_rx_aggr_start(&dev->mt76, &mlink->wcid, tid, ssn,
+@@ -1493,7 +1561,7 @@ static void mt7996_sta_statistics(struct ieee80211_hw *hw,
+ 
+ 	/* TODO: support per-link rate report */
+ 	mutex_lock(&dev->mt76.mutex);
+-	mlink = mlink_dereference_protected(msta, 0);
++	mlink = mlink_dereference_protected(msta, msta->pri_link);
+ 	if (!mlink)
+ 		goto out;
+ 
+@@ -1553,7 +1621,7 @@ static void mt7996_sta_rc_work(void *data, struct ieee80211_sta *sta)
+ 	u32 *changed = data;
+ 
+ 	rcu_read_lock();
+-	mlink = rcu_dereference(msta->link[0]);
++	mlink = rcu_dereference(msta->link[msta->pri_link]);
+ 
+ 	spin_lock_bh(&dev->mt76.sta_poll_lock);
+ 	mlink->changed |= *changed;
+@@ -1620,19 +1688,26 @@ static void mt7996_sta_set_4addr(struct ieee80211_hw *hw,
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+-	struct mt7996_bss_conf *mconf;
+-	struct mt7996_link_sta *mlink;
++	unsigned long valid_links = sta->valid_links ?: BIT(0);
++	unsigned int link_id;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	mconf = mconf_dereference_protected(mvif, 0);
+-	mlink = mlink_dereference_protected(msta, 0);
++	for_each_set_bit(link_id, &valid_links, IEEE80211_MLD_MAX_NUM_LINKS) {
++		struct mt7996_bss_conf *mconf =
++			mconf_dereference_protected(mvif, link_id);
++		struct mt7996_link_sta *mlink =
++			mlink_dereference_protected(msta, link_id);
+ 
+-	if (enabled)
+-		set_bit(MT_WCID_FLAG_4ADDR, &mlink->wcid.flags);
+-	else
+-		clear_bit(MT_WCID_FLAG_4ADDR, &mlink->wcid.flags);
++		if (!mconf || !mlink)
++			continue;
+ 
+-	mt7996_mcu_wtbl_update_hdr_trans(dev, vif, mconf, mlink);
++		if (enabled)
++			set_bit(MT_WCID_FLAG_4ADDR, &mlink->wcid.flags);
++		else
++			clear_bit(MT_WCID_FLAG_4ADDR, &mlink->wcid.flags);
++
++		mt7996_mcu_wtbl_update_hdr_trans(dev, vif, mconf, mlink);
++	}
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
+@@ -1644,19 +1719,26 @@ static void mt7996_sta_set_decap_offload(struct ieee80211_hw *hw,
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+-	struct mt7996_bss_conf *mconf;
+-	struct mt7996_link_sta *mlink;
++	unsigned long valid_links = sta->valid_links ?: BIT(0);
++	unsigned int link_id;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	mconf = mconf_dereference_protected(mvif, 0);
+-	mlink = mlink_dereference_protected(msta, 0);
++	for_each_set_bit(link_id, &valid_links, IEEE80211_MLD_MAX_NUM_LINKS) {
++		struct mt7996_bss_conf *mconf =
++			mconf_dereference_protected(mvif, link_id);
++		struct mt7996_link_sta *mlink =
++			mlink_dereference_protected(msta, link_id);
+ 
+-	if (enabled)
+-		set_bit(MT_WCID_FLAG_HDR_TRANS, &mlink->wcid.flags);
+-	else
+-		clear_bit(MT_WCID_FLAG_HDR_TRANS, &mlink->wcid.flags);
++		if (!mconf || !mlink)
++			continue;
++
++		if (enabled)
++			set_bit(MT_WCID_FLAG_HDR_TRANS, &mlink->wcid.flags);
++		else
++			clear_bit(MT_WCID_FLAG_HDR_TRANS, &mlink->wcid.flags);
+ 
+-	mt7996_mcu_wtbl_update_hdr_trans(dev, vif, mconf, mlink);
++		mt7996_mcu_wtbl_update_hdr_trans(dev, vif, mconf, mlink);
++	}
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
+@@ -1789,9 +1871,13 @@ static void mt7996_ethtool_worker(void *wi_data, struct ieee80211_sta *sta)
+ {
+ 	struct mt76_ethtool_worker_info *wi = wi_data;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+-	struct mt7996_link_sta *mlink = &msta->deflink;
++	struct mt7996_link_sta *mlink;
++	struct mt7996_bss_conf *mconf;
++
++	mlink = mlink_dereference_protected(msta, msta->pri_link);
++	mconf = mconf_dereference_protected(msta->vif, msta->pri_link);
+ 
+-	if (msta->vif->deflink.mt76.idx != wi->idx)
++	if (mconf->mt76.idx != wi->idx)
+ 		return;
+ 
+ 	mt76_ethtool_worker(wi, &mlink->wcid.stats, true);
+@@ -2023,12 +2109,13 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw,
+ 			     struct net_device_path *path)
+ {
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_bss_conf *mconf = &mvif->deflink;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+-	struct mt7996_link_sta *mlink = &msta->deflink;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	struct mtk_wed_device *wed = &dev->mt76.mmio.wed;
++	struct mt7996_bss_conf *mconf;
++	struct mt7996_link_sta *mlink;
++	u8 link_id;
+ 
+ 	if (phy != &dev->phy && dev->hif2) {
+ 		switch (dev->option_type) {
+@@ -2047,6 +2134,10 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw,
+ 	if (!mtk_wed_device_active(wed))
+ 		return -ENODEV;
+ 
++	link_id = msta->pri_link;
++	mconf = rcu_dereference(mvif->link[link_id]);
++	mlink = rcu_dereference(msta->link[link_id]);
++
+ 	if (mlink->wcid.idx > MT7996_WTBL_STA)
+ 		return -EIO;
+ 
+@@ -2368,7 +2459,7 @@ const struct ieee80211_ops mt7996_ops = {
+ 	.vif_cfg_changed = mt7996_vif_cfg_changed,
+ 	.link_info_changed = mt7996_link_info_changed,
+ 	.sta_state = mt76_sta_state,
+-	.sta_pre_rcu_remove = mt76_sta_pre_rcu_remove,
++	.sta_pre_rcu_remove = mt7996_sta_pre_rcu_remove,
+ 	.sta_rc_update = mt7996_sta_rc_update,
+ 	.set_key = mt7996_set_key,
+ 	.ampdu_action = mt7996_ampdu_action,
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0109-wifi-mt76-mt7996-rework-TXD-for-multi-link-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0109-wifi-mt76-mt7996-rework-TXD-for-multi-link-support.patch
new file mode 100644
index 0000000..cf1b40f
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0109-wifi-mt76-mt7996-rework-TXD-for-multi-link-support.patch
@@ -0,0 +1,203 @@
+From 78eed11814955fbb83bd1adb71a46aef5e9b98b7 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Mon, 4 Dec 2023 11:25:54 +0800
+Subject: [PATCH 109/120] wifi: mt76: mt7996: rework TXD for multi-link support
+
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/mac.c    | 88 +++++++++++++++++++++++++++++++++++--------------
+ mt7996/mt7996.h |  9 +++++
+ 2 files changed, 73 insertions(+), 24 deletions(-)
+
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 2f5a57f53..f6f028824 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -621,9 +621,8 @@ mt7996_mac_write_txwi_8023(struct mt7996_dev *dev, __le32 *txwi,
+ 	u32 val;
+ 
+ 	if (wcid->sta) {
+-		struct ieee80211_sta *sta;
++		struct ieee80211_sta *sta = wcid_to_sta(wcid);
+ 
+-		sta = container_of((void *)wcid, struct ieee80211_sta, drv_priv);
+ 		wmm = sta->wme;
+ 	}
+ 
+@@ -724,6 +723,10 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi,
+ 		txwi[3] |= cpu_to_le32(val);
+ 		txwi[3] &= ~cpu_to_le32(MT_TXD3_HW_AMSDU);
+ 	}
++
++	if (ieee80211_vif_is_mld(info->control.vif) &&
++	    (multicast || unlikely(skb->protocol == cpu_to_be16(ETH_P_PAE))))
++		txwi[5] |= cpu_to_le32(MT_TXD5_FL);
+ }
+ 
+ void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
+@@ -733,10 +736,12 @@ void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
+ {
+ 	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ 	struct ieee80211_vif *vif = info->control.vif;
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_bss_conf *mconf;
+ 	u8 band_idx = (info->hw_queue & MT_TX_HW_QUEUE_PHY) >> 2;
+ 	u8 p_fmt, q_idx, omac_idx = 0, wmm_idx = 0;
++	u8 link_id;
+ 	bool is_8023 = info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP;
+-	struct mt76_vif *mvif;
+ 	u16 tx_count = 15;
+ 	u32 val;
+ 	bool inband_disc = !!(changed & (BSS_CHANGED_UNSOL_BCAST_PROBE_RESP |
+@@ -744,11 +749,16 @@ void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
+ 	bool beacon = !!(changed & (BSS_CHANGED_BEACON |
+ 				    BSS_CHANGED_BEACON_ENABLED)) && (!inband_disc);
+ 
+-	mvif = vif ? (struct mt76_vif *)vif->drv_priv : NULL;
+-	if (mvif) {
+-		omac_idx = mvif->omac_idx;
+-		wmm_idx = mvif->wmm_idx;
+-		band_idx = mvif->band_idx;
++	if (likely(wcid != &dev->mt76.global_wcid))
++		link_id = wcid->link_id;
++	else
++		link_id = u32_get_bits(info->control.flags, IEEE80211_TX_CTRL_MLO_LINK);
++
++	mconf = rcu_dereference(mvif->link[link_id]);
++	if (mconf) {
++		omac_idx = mconf->mt76.omac_idx;
++		wmm_idx = mconf->mt76.wmm_idx;
++		band_idx = mconf->mt76.band_idx;
+ 	}
+ 
+ 	if (inband_disc) {
+@@ -795,7 +805,10 @@ void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
+ 		val |= MT_TXD5_TX_STATUS_HOST;
+ 	txwi[5] = cpu_to_le32(val);
+ 
+-	val = MT_TXD6_DIS_MAT | MT_TXD6_DAS;
++	val = MT_TXD6_DAS;
++	if ((q_idx >= MT_LMAC_ALTX0 && q_idx <= MT_LMAC_BCN0))
++		val |= MT_TXD6_DIS_MAT;
++
+ 	if (is_mt7996(&dev->mt76))
+ 		val |= FIELD_PREP(MT_TXD6_MSDU_CNT, 1);
+ 	else
+@@ -814,16 +827,18 @@ void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
+ 			     is_multicast_ether_addr(hdr->addr1);
+ 		u8 idx = MT7996_BASIC_RATES_TBL;
+ 
+-		if (mvif) {
+-			if (mcast && mvif->mcast_rates_idx)
+-				idx = mvif->mcast_rates_idx;
+-			else if (beacon && mvif->beacon_rates_idx)
+-				idx = mvif->beacon_rates_idx;
++		if (mconf) {
++			if (mcast && mconf->mt76.mcast_rates_idx)
++				idx = mconf->mt76.mcast_rates_idx;
++			else if (beacon && mconf->mt76.beacon_rates_idx)
++				idx = mconf->mt76.beacon_rates_idx;
+ 			else
+-				idx = mvif->basic_rates_idx;
++				idx = mconf->mt76.basic_rates_idx;
+ 		}
+ 
+ 		val = FIELD_PREP(MT_TXD6_TX_RATE, idx) | MT_TXD6_FIXED_BW;
++		if (mcast)
++			val |= MT_TXD6_DIS_MAT;
+ 		txwi[6] |= cpu_to_le32(val);
+ 		txwi[3] |= cpu_to_le32(MT_TXD3_BA_DISABLE);
+ 	}
+@@ -839,17 +854,48 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
+ 	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx_info->skb);
+ 	struct ieee80211_key_conf *key = info->control.hw_key;
+ 	struct ieee80211_vif *vif = info->control.vif;
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_sta *msta;
++	struct mt7996_bss_conf *mconf;
+ 	struct mt76_connac_txp_common *txp;
+ 	struct mt76_txwi_cache *t;
+ 	int id, i, pid, nbuf = tx_info->nbuf - 1;
+ 	bool is_8023 = info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP;
+ 	u8 *txwi = (u8 *)txwi_ptr;
++	u8 link_id;
+ 
+ 	if (unlikely(tx_info->skb->len <= ETH_HLEN))
+ 		return -EINVAL;
+ 
+-	if (!wcid)
+-		wcid = &dev->mt76.global_wcid;
++	if (WARN_ON(!wcid))
++		return -EINVAL;
++
++	msta = sta ? (struct mt7996_sta *)sta->drv_priv : &mvif->sta;
++	if (ieee80211_is_data_qos(hdr->frame_control) && sta->mlo) {
++		if (unlikely(tx_info->skb->protocol == cpu_to_be16(ETH_P_PAE))) {
++			link_id = msta->pri_link;
++		} else {
++			u8 tid = tx_info->skb->priority & IEEE80211_QOS_CTL_TID_MASK;
++
++			link_id = (tid % 2) ? msta->sec_link : msta->pri_link;
++		}
++	} else {
++		link_id = u32_get_bits(info->control.flags, IEEE80211_TX_CTRL_MLO_LINK);
++
++		if (link_id == IEEE80211_LINK_UNSPECIFIED || (sta && !sta->mlo))
++			link_id = wcid->link_id;
++	}
++
++	if (link_id != wcid->link_id) {
++		struct mt7996_link_sta *mlink = rcu_dereference(msta->link[link_id]);
++
++		if (mlink)
++			wcid = &mlink->wcid;
++	}
++
++	mconf = rcu_dereference(mvif->link[wcid->link_id]);
++	if (!mconf)
++		return -EINVAL;
+ 
+ 	t = (struct mt76_txwi_cache *)(txwi + mdev->drv->txwi_size);
+ 	t->skb = tx_info->skb;
+@@ -894,13 +940,7 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
+ 	if (!is_8023 && ieee80211_is_mgmt(hdr->frame_control))
+ 		txp->fw.flags |= cpu_to_le16(MT_CT_INFO_MGMT_FRAME);
+ 
+-	if (vif) {
+-		struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-		struct mt7996_bss_conf *mconf = &mvif->deflink;
+-
+-		txp->fw.bss_idx = mconf->mt76.idx;
+-	}
+-
++	txp->fw.bss_idx = mconf->mt76.idx;
+ 	txp->fw.token = cpu_to_le16(id);
+ 	txp->fw.rept_wds_wcid = cpu_to_le16(sta ? wcid->idx : 0xfff);
+ 
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 2932ab8d5..664d6d2ec 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -826,6 +826,15 @@ mlink_dereference_protected(struct mt7996_sta *msta, u8 link_id)
+ 					 lockdep_is_held(&msta->vif->dev->mt76.mutex));
+ }
+ 
++static inline struct mt7996_link_sta *
++wcid_to_mlink(struct mt76_wcid *wcid)
++{
++	if (!wcid)
++		return NULL;
++
++	return container_of(wcid, struct mt7996_link_sta, wcid);
++}
++
+ extern const struct ieee80211_ops mt7996_ops;
+ extern struct pci_driver mt7996_pci_driver;
+ extern struct pci_driver mt7996_hif_driver;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0110-wifi-mt76-mt7996-rework-TXS-for-multi-link-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0110-wifi-mt76-mt7996-rework-TXS-for-multi-link-support.patch
new file mode 100644
index 0000000..7ac5c74
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0110-wifi-mt76-mt7996-rework-TXS-for-multi-link-support.patch
@@ -0,0 +1,120 @@
+From f31a6d71ce640b43de1399d4a79dc0132b32b87b Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Mon, 4 Dec 2023 11:57:38 +0800
+Subject: [PATCH 110/120] wifi: mt76: mt7996: rework TXS for multi-link support
+
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/mac.c    |  9 +++++----
+ mt7996/main.c   |  1 +
+ mt7996/mt7996.h | 28 ++++++++++++++++++++++++++++
+ 3 files changed, 34 insertions(+), 4 deletions(-)
+
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index f6f028824..700029b04 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -1192,7 +1192,7 @@ mt7996_mac_add_txs_skb(struct mt7996_dev *dev, struct mt76_wcid *wcid,
+ 		struct ieee80211_sta *sta;
+ 		u8 tid;
+ 
+-		sta = container_of((void *)wcid, struct ieee80211_sta, drv_priv);
++		sta = wcid_to_sta(wcid);
+ 		tid = FIELD_GET(MT_TXS0_TID, txs);
+ 		ieee80211_refresh_tx_agg_session_timer(sta, tid);
+ 	}
+@@ -1310,9 +1310,10 @@ static void mt7996_mac_add_txs(struct mt7996_dev *dev, void *data)
+ 	struct mt76_wcid *wcid;
+ 	__le32 *txs_data = data;
+ 	u16 wcidx;
+-	u8 pid;
++	u8 band, pid;
+ 
+ 	wcidx = le32_get_bits(txs_data[2], MT_TXS2_WCID);
++	band = le32_get_bits(txs_data[2], MT_TXS2_BAND);
+ 	pid = le32_get_bits(txs_data[3], MT_TXS3_PID);
+ 
+ 	if (pid < MT_PACKET_ID_NO_SKB)
+@@ -1323,7 +1324,7 @@ static void mt7996_mac_add_txs(struct mt7996_dev *dev, void *data)
+ 
+ 	rcu_read_lock();
+ 
+-	wcid = rcu_dereference(dev->mt76.wcid[wcidx]);
++	wcid = mt7996_get_link_wcid(dev, wcidx, band);
+ 	if (!wcid)
+ 		goto out;
+ 
+@@ -1332,7 +1333,7 @@ static void mt7996_mac_add_txs(struct mt7996_dev *dev, void *data)
+ 	if (!wcid->sta)
+ 		goto out;
+ 
+-	mlink = container_of(wcid, struct mt7996_link_sta, wcid);
++	mlink = wcid_to_mlink(wcid);
+ 	spin_lock_bh(&dev->mt76.sta_poll_lock);
+ 	if (list_empty(&mlink->wcid.poll_list))
+ 		list_add_tail(&mlink->wcid.poll_list, &dev->mt76.sta_poll_list);
+diff --git a/mt7996/main.c b/mt7996/main.c
+index f4488fdf7..03f23453d 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -2307,6 +2307,7 @@ mt7996_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	mconf = mconf_dereference_protected(mvif, link_id);
+ 	mconf->chanctx = ctx;
+ 	ctx->nbss_assigned++;
++	mvif->band_to_link[phy->mt76->band_idx] = link_id;
+ 
+ 	if (mt7996_hw_phy(hw) == phy)
+ 		mvif->master_link_id = link_id;
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 664d6d2ec..62270101e 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -358,6 +358,8 @@ struct mt7996_vif {
+ 	u8 master_link_id;
+ 	u8 group_mld_id;
+ 	u8 mld_remap_id;
++
++	u8 band_to_link[__MT_MAX_BAND];
+ };
+ 
+ /* crash-dump */
+@@ -835,6 +837,32 @@ wcid_to_mlink(struct mt76_wcid *wcid)
+ 	return container_of(wcid, struct mt7996_link_sta, wcid);
+ }
+ 
++static inline struct mt76_wcid *
++mt7996_get_link_wcid(struct mt7996_dev *dev, u16 idx, u8 band_idx)
++{
++	struct mt7996_link_sta *mlink;
++	struct mt76_wcid *wcid;
++	u8 link_id;
++
++	if (idx >= ARRAY_SIZE(dev->mt76.wcid))
++		return NULL;
++
++	wcid = rcu_dereference(dev->mt76.wcid[idx]);
++	if (!wcid)
++		return NULL;
++
++	if (wcid->phy_idx == band_idx)
++		return wcid;
++
++	mlink = wcid_to_mlink(wcid);
++	link_id = mlink->sta->vif->band_to_link[band_idx];
++	mlink = rcu_dereference(mlink->sta->link[link_id]);
++	if (!mlink)
++		return wcid;
++
++	return &mlink->wcid;
++}
++
+ extern const struct ieee80211_ops mt7996_ops;
+ extern struct pci_driver mt7996_pci_driver;
+ extern struct pci_driver mt7996_hif_driver;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0111-wifi-mt76-mt7996-rework-RXD-for-multi-link-support.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0111-wifi-mt76-mt7996-rework-RXD-for-multi-link-support.patch
new file mode 100644
index 0000000..eddb979
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0111-wifi-mt76-mt7996-rework-RXD-for-multi-link-support.patch
@@ -0,0 +1,64 @@
+From bd6ca04b5a9b98529348fe6bf53213383e2f269e Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Mon, 4 Dec 2023 14:50:47 +0800
+Subject: [PATCH 111/120] wifi: mt76: mt7996: rework RXD for multi-link support
+
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/mac.c | 27 ++-------------------------
+ 1 file changed, 2 insertions(+), 25 deletions(-)
+
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 700029b04..47fd2e17a 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -51,29 +51,6 @@ static const struct mt7996_dfs_radar_spec jp_radar_specs = {
+ 	},
+ };
+ 
+-static struct mt76_wcid *mt7996_rx_get_wcid(struct mt7996_dev *dev,
+-					    u16 idx, bool unicast)
+-{
+-	struct mt7996_link_sta *mlink;
+-	struct mt76_wcid *wcid;
+-
+-	if (idx >= ARRAY_SIZE(dev->mt76.wcid))
+-		return NULL;
+-
+-	wcid = rcu_dereference(dev->mt76.wcid[idx]);
+-	if (unicast || !wcid)
+-		return wcid;
+-
+-	if (!wcid->sta)
+-		return NULL;
+-
+-	mlink = container_of(wcid, struct mt7996_link_sta, wcid);
+-	if (!mlink->sta->vif)
+-		return NULL;
+-
+-	return &mlink->wcid;
+-}
+-
+ bool mt7996_mac_wtbl_update(struct mt7996_dev *dev, int idx, u32 mask)
+ {
+ 	mt76_rmw(dev, MT_WTBL_UPDATE, MT_WTBL_UPDATE_WLAN_IDX,
+@@ -376,10 +353,10 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
+ 
+ 	unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
+ 	idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
+-	status->wcid = mt7996_rx_get_wcid(dev, idx, unicast);
++	status->wcid = mt7996_get_link_wcid(dev, idx, band_idx);
+ 
+ 	if (status->wcid) {
+-		mlink = container_of(status->wcid, struct mt7996_link_sta, wcid);
++		mlink = wcid_to_mlink(status->wcid);
+ 		spin_lock_bh(&dev->mt76.sta_poll_lock);
+ 		if (list_empty(&mlink->wcid.poll_list))
+ 			list_add_tail(&mlink->wcid.poll_list,
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0112-wifi-mt76-mt7996-rework-mac-functions-for-multi-link.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0112-wifi-mt76-mt7996-rework-mac-functions-for-multi-link.patch
new file mode 100644
index 0000000..9e9b9b6
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0112-wifi-mt76-mt7996-rework-mac-functions-for-multi-link.patch
@@ -0,0 +1,242 @@
+From 3195a95864143f9c0714e90ef81caecd7612e9bc Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Mon, 4 Dec 2023 18:31:02 +0800
+Subject: [PATCH 112/120] wifi: mt76: mt7996: rework mac functions for
+ multi-link support
+
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/mac.c | 84 ++++++++++++++++++++++++++++++++++++----------------
+ 1 file changed, 58 insertions(+), 26 deletions(-)
+
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 47fd2e17a..545bcb021 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -85,10 +85,11 @@ static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+ {
+ 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
+ 	struct ethhdr *eth_hdr = (struct ethhdr *)(skb->data + hdr_gap);
+-	struct mt7996_sta *msta = (struct mt7996_sta *)status->wcid;
++	struct mt7996_link_sta *mlink = (struct mt7996_link_sta *)status->wcid;
+ 	__le32 *rxd = (__le32 *)skb->data;
+ 	struct ieee80211_sta *sta;
+ 	struct ieee80211_vif *vif;
++	struct ieee80211_bss_conf *conf;
+ 	struct ieee80211_hdr hdr;
+ 	u16 frame_control;
+ 
+@@ -99,11 +100,12 @@ static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+ 	if (!(le32_to_cpu(rxd[1]) & MT_RXD1_NORMAL_GROUP_4))
+ 		return -EINVAL;
+ 
+-	if (!msta || !msta->vif)
++	if (!mlink->sta || !mlink->sta->vif)
+ 		return -EINVAL;
+ 
+-	sta = container_of((void *)msta, struct ieee80211_sta, drv_priv);
+-	vif = container_of((void *)msta->vif, struct ieee80211_vif, drv_priv);
++	sta = wcid_to_sta(status->wcid);
++	vif = container_of((void *)mlink->sta->vif, struct ieee80211_vif, drv_priv);
++	conf = rcu_dereference(vif->link_conf[mlink->wcid.link_id]);
+ 
+ 	/* store the info from RXD and ethhdr to avoid being overridden */
+ 	frame_control = le32_get_bits(rxd[8], MT_RXD8_FRAME_CONTROL);
+@@ -116,7 +118,7 @@ static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+ 	switch (frame_control & (IEEE80211_FCTL_TODS |
+ 				 IEEE80211_FCTL_FROMDS)) {
+ 	case 0:
+-		ether_addr_copy(hdr.addr3, vif->bss_conf.bssid);
++		ether_addr_copy(hdr.addr3, conf->bssid);
+ 		break;
+ 	case IEEE80211_FCTL_FROMDS:
+ 		ether_addr_copy(hdr.addr3, eth_hdr->h_source);
+@@ -955,15 +957,21 @@ u32 mt7996_wed_init_buf(void *ptr, dma_addr_t phys, int token_id)
+ }
+ 
+ static void
+-mt7996_tx_check_aggr(struct ieee80211_sta *sta, struct sk_buff *skb)
++mt7996_tx_check_aggr(struct ieee80211_sta *sta, struct sk_buff *skb,
++		     struct mt76_wcid *wcid)
+ {
+ 	struct mt7996_sta *msta;
+ 	struct mt7996_link_sta *mlink;
++	struct ieee80211_link_sta *link_sta;
+ 	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ 	bool is_8023 = info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP;
+ 	u16 fc, tid;
+ 
+-	if (!sta || !(sta->deflink.ht_cap.ht_supported || sta->deflink.he_cap.has_he))
++	link_sta = rcu_dereference(sta->link[wcid->link_id]);
++	if (!link_sta)
++		return;
++
++	if (!sta->mlo && !(link_sta->ht_cap.ht_supported || link_sta->he_cap.has_he))
+ 		return;
+ 
+ 	tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK;
+@@ -987,17 +995,17 @@ mt7996_tx_check_aggr(struct ieee80211_sta *sta, struct sk_buff *skb)
+ 		return;
+ 
+ 	msta = (struct mt7996_sta *)sta->drv_priv;
+-	mlink = rcu_dereference(msta->link[0]);
++	mlink = rcu_dereference(msta->link[msta->pri_link]);
+ 	if (!test_and_set_bit(tid, &mlink->wcid.ampdu_state))
+ 		ieee80211_start_tx_ba_session(sta, tid, 0);
+ }
+ 
+ static void
+ mt7996_txwi_free(struct mt7996_dev *dev, struct mt76_txwi_cache *t,
+-		 struct ieee80211_sta *sta, struct list_head *free_list)
++		 struct ieee80211_sta *sta, struct mt76_wcid *wcid,
++		 struct list_head *free_list)
+ {
+ 	struct mt76_dev *mdev = &dev->mt76;
+-	struct mt76_wcid *wcid;
+ 	__le32 *txwi;
+ 	u16 wcid_idx;
+ 
+@@ -1007,11 +1015,10 @@ mt7996_txwi_free(struct mt7996_dev *dev, struct mt76_txwi_cache *t,
+ 
+ 	txwi = (__le32 *)mt76_get_txwi_ptr(mdev, t);
+ 	if (sta) {
+-		wcid = (struct mt76_wcid *)sta->drv_priv;
+ 		wcid_idx = wcid->idx;
+ 
+ 		if (likely(t->skb->protocol != cpu_to_be16(ETH_P_PAE)))
+-			mt7996_tx_check_aggr(sta, t->skb);
++			mt7996_tx_check_aggr(sta, t->skb, wcid);
+ 	} else {
+ 		wcid_idx = le32_get_bits(txwi[9], MT_TXD9_WLAN_IDX);
+ 	}
+@@ -1066,7 +1073,9 @@ mt7996_mac_tx_free(struct mt7996_dev *dev, void *data, int len)
+ 		 */
+ 		info = le32_to_cpu(*cur_info);
+ 		if (info & MT_TXFREE_INFO_PAIR) {
+-			struct mt7996_link_sta *mlink;
++			struct mt7996_sta *msta;
++			unsigned long valid_links;
++			unsigned int link_id;
+ 			u16 idx;
+ 
+ 			idx = FIELD_GET(MT_TXFREE_INFO_WLAN_ID, info);
+@@ -1075,11 +1084,17 @@ mt7996_mac_tx_free(struct mt7996_dev *dev, void *data, int len)
+ 			if (!sta)
+ 				continue;
+ 
+-			mlink = container_of(wcid, struct mt7996_link_sta, wcid);
++			valid_links = sta->valid_links ?: BIT(0);
++			msta = (struct mt7996_sta *)sta->drv_priv;
++			/* for MLD STA, add all link's wcid to sta_poll_list */
+ 			spin_lock_bh(&mdev->sta_poll_lock);
+-			if (list_empty(&mlink->wcid.poll_list))
+-				list_add_tail(&mlink->wcid.poll_list,
+-					      &mdev->sta_poll_list);
++			for_each_set_bit(link_id, &valid_links, IEEE80211_MLD_MAX_NUM_LINKS) {
++				struct mt7996_link_sta *mlink =
++					rcu_dereference(msta->link[link_id]);
++
++				if (list_empty(&mlink->wcid.poll_list))
++					list_add_tail(&mlink->wcid.poll_list, &mdev->sta_poll_list);
++			}
+ 			spin_unlock_bh(&mdev->sta_poll_lock);
+ 			continue;
+ 		} else if (info & MT_TXFREE_INFO_HEADER) {
+@@ -1115,7 +1130,8 @@ mt7996_mac_tx_free(struct mt7996_dev *dev, void *data, int len)
+ 			if (!txwi)
+ 				continue;
+ 
+-			mt7996_txwi_free(dev, txwi, sta, &free_list);
++			mt7996_txwi_free(dev, txwi, sta, wcid, &free_list);
++			txwi->jiffies = 0;
+ 		}
+ 	}
+ 
+@@ -1537,19 +1553,29 @@ static void
+ mt7996_update_vif_beacon(void *priv, u8 *mac, struct ieee80211_vif *vif)
+ {
+ 	struct ieee80211_hw *hw = priv;
+-	struct ieee80211_bss_conf *conf = &vif->bss_conf;
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_bss_conf *mconf = &mvif->deflink;
++	unsigned long update = vif->valid_links ?: BIT(0);
++	unsigned int link_id;
+ 
++	mutex_lock(&dev->mt76.mutex);
+ 	switch (vif->type) {
+ 	case NL80211_IFTYPE_MESH_POINT:
+ 	case NL80211_IFTYPE_ADHOC:
+ 	case NL80211_IFTYPE_AP:
+-		mt7996_mcu_add_beacon(hw, conf, mconf, conf->enable_beacon);
++		for_each_set_bit(link_id, &update, IEEE80211_MLD_MAX_NUM_LINKS) {
++			struct mt7996_bss_conf *mconf =
++					mconf_dereference_protected(mvif, link_id);
++			struct ieee80211_bss_conf *conf =
++					link_conf_dereference_protected(vif, link_id);
++
++			mt7996_mcu_add_beacon(hw, conf, mconf, conf->enable_beacon);
++		}
+ 		break;
+ 	default:
+ 		break;
+ 	}
++	mutex_unlock(&dev->mt76.mutex);
+ }
+ 
+ static void
+@@ -1585,7 +1611,7 @@ void mt7996_tx_token_put(struct mt7996_dev *dev)
+ 
+ 	spin_lock_bh(&dev->mt76.token_lock);
+ 	idr_for_each_entry(&dev->mt76.token, txwi, id) {
+-		mt7996_txwi_free(dev, txwi, NULL, NULL);
++		mt7996_txwi_free(dev, txwi, NULL, NULL, NULL);
+ 		dev->mt76.token_count--;
+ 	}
+ 	spin_unlock_bh(&dev->mt76.token_lock);
+@@ -2266,21 +2292,26 @@ void mt7996_mac_sta_rc_work(struct work_struct *work)
+ 	u32 changed;
+ 	LIST_HEAD(list);
+ 
++	rcu_read_lock();
+ 	spin_lock_bh(&dev->mt76.sta_poll_lock);
+ 	list_splice_init(&dev->sta_rc_list, &list);
+ 
+ 	while (!list_empty(&list)) {
++		u8 link_id;
++
+ 		mlink = list_first_entry(&list, struct mt7996_link_sta, rc_list);
++		link_id = mlink->wcid.link_id;
++
+ 		list_del_init(&mlink->rc_list);
+ 		changed = mlink->changed;
+ 		mlink->changed = 0;
+ 		spin_unlock_bh(&dev->mt76.sta_poll_lock);
+ 
+-		sta = container_of((void *)mlink->sta, struct ieee80211_sta, drv_priv);
+-		link_sta = &sta->deflink;
+ 		vif = container_of((void *)mlink->sta->vif, struct ieee80211_vif, drv_priv);
+-		conf = &vif->bss_conf;
+-		mconf = &mlink->sta->vif->deflink;
++		conf = rcu_dereference(vif->link_conf[link_id]);
++		mconf = rcu_dereference(mlink->sta->vif->link[link_id]);
++		sta = wcid_to_sta(&mlink->wcid);
++		link_sta = rcu_dereference(sta->link[link_id]);
+ 
+ 		if (changed & (IEEE80211_RC_SUPP_RATES_CHANGED |
+ 			       IEEE80211_RC_NSS_CHANGED |
+@@ -2295,6 +2326,7 @@ void mt7996_mac_sta_rc_work(struct work_struct *work)
+ 	}
+ 
+ 	spin_unlock_bh(&dev->mt76.sta_poll_lock);
++	rcu_read_unlock();
+ }
+ 
+ void mt7996_mac_work(struct work_struct *work)
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0113-wifi-mt76-connac-rework-mcu-functions-for-multi-link.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0113-wifi-mt76-connac-rework-mcu-functions-for-multi-link.patch
new file mode 100644
index 0000000..6f1b34d
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0113-wifi-mt76-connac-rework-mcu-functions-for-multi-link.patch
@@ -0,0 +1,259 @@
+From 886e275bd0450cfa2fd5dd971a3b81aa34d5636e Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Thu, 7 Dec 2023 15:39:03 +0800
+Subject: [PATCH 113/120] wifi: mt76: connac: rework mcu functions for
+ multi-link support
+
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7615/mcu.c      |  4 +--
+ mt76_connac_mcu.c | 11 ++++----
+ mt76_connac_mcu.h |  2 +-
+ mt7915/mcu.c      |  3 +-
+ mt7925/mcu.c      |  3 +-
+ mt7996/mcu.c      | 70 +++++++++++++++++++++++++++++++++--------------
+ 6 files changed, 63 insertions(+), 30 deletions(-)
+
+diff --git a/mt7615/mcu.c b/mt7615/mcu.c
+index 8f4f203ef..e72dd654c 100644
+--- a/mt7615/mcu.c
++++ b/mt7615/mcu.c
+@@ -862,8 +862,8 @@ mt7615_mcu_wtbl_sta_add(struct mt7615_phy *phy, struct ieee80211_vif *vif,
+ 		else
+ 			mvif->sta_added = true;
+ 	}
+-	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, sskb, vif, &sta->deflink,
+-				      enable, new_entry);
++	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, sskb, &vif->bss_conf,
++				      &sta->deflink, enable, new_entry);
+ 	if (enable && sta)
+ 		mt76_connac_mcu_sta_tlv(phy->mt76, sskb, sta, vif, 0,
+ 					MT76_STA_INFO_STATE_ASSOC);
+diff --git a/mt76_connac_mcu.c b/mt76_connac_mcu.c
+index da2fe01ac..e7039a21f 100644
+--- a/mt76_connac_mcu.c
++++ b/mt76_connac_mcu.c
+@@ -370,10 +370,11 @@ void mt76_connac_mcu_bss_omac_tlv(struct sk_buff *skb,
+ EXPORT_SYMBOL_GPL(mt76_connac_mcu_bss_omac_tlv);
+ 
+ void mt76_connac_mcu_sta_basic_tlv(struct mt76_dev *dev, struct sk_buff *skb,
+-				   struct ieee80211_vif *vif,
++				   struct ieee80211_bss_conf *conf,
+ 				   struct ieee80211_link_sta *link_sta,
+ 				   bool enable, bool newly)
+ {
++	struct ieee80211_vif *vif = conf->vif;
+ 	struct ieee80211_sta *sta = link_sta ? link_sta->sta : NULL;
+ 	struct sta_rec_basic *basic;
+ 	struct tlv *tlv;
+@@ -394,10 +395,9 @@ void mt76_connac_mcu_sta_basic_tlv(struct mt76_dev *dev, struct sk_buff *skb,
+ 
+ 	if (!sta) {
+ 		basic->conn_type = cpu_to_le32(CONNECTION_INFRA_BC);
+-
+ 		if (is_mt799x(dev) && vif->type == NL80211_IFTYPE_STATION &&
+-		    !is_zero_ether_addr(vif->bss_conf.bssid)) {
+-			memcpy(basic->peer_addr, vif->bss_conf.bssid, ETH_ALEN);
++		    conf && !is_zero_ether_addr(conf->bssid)) {
++			memcpy(basic->peer_addr, conf->bssid, ETH_ALEN);
+ 			basic->aid = cpu_to_le16(vif->cfg.aid);
+ 		} else {
+ 			eth_broadcast_addr(basic->peer_addr);
+@@ -1056,7 +1056,8 @@ int mt76_connac_mcu_sta_cmd(struct mt76_phy *phy,
+ 		return PTR_ERR(skb);
+ 
+ 	if (info->sta || !info->offload_fw)
+-		mt76_connac_mcu_sta_basic_tlv(dev, skb, info->vif, &info->sta->deflink,
++		mt76_connac_mcu_sta_basic_tlv(dev, skb, &info->vif->bss_conf,
++					      &info->sta->deflink,
+ 					      info->enable, info->newly);
+ 	if (info->sta && info->enable)
+ 		mt76_connac_mcu_sta_tlv(phy, skb, info->sta,
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index d45765beb..7f1562b8e 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1904,7 +1904,7 @@ mt76_connac_mcu_add_tlv(struct sk_buff *skb, int tag, int len)
+ int mt76_connac_mcu_set_channel_domain(struct mt76_phy *phy);
+ int mt76_connac_mcu_set_vif_ps(struct mt76_dev *dev, struct ieee80211_vif *vif);
+ void mt76_connac_mcu_sta_basic_tlv(struct mt76_dev *dev, struct sk_buff *skb,
+-				   struct ieee80211_vif *vif,
++				   struct ieee80211_bss_conf *conf,
+ 				   struct ieee80211_link_sta *link_sta,
+ 				   bool enable, bool newly);
+ void mt76_connac_mcu_wtbl_generic_tlv(struct mt76_dev *dev, struct sk_buff *skb,
+diff --git a/mt7915/mcu.c b/mt7915/mcu.c
+index 1d1b3cead..366f2bba2 100644
+--- a/mt7915/mcu.c
++++ b/mt7915/mcu.c
+@@ -1669,7 +1669,8 @@ int mt7915_mcu_add_sta(struct mt7915_dev *dev, struct ieee80211_vif *vif,
+ 		return PTR_ERR(skb);
+ 
+ 	/* starec basic */
+-	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, skb, vif, &sta->deflink, enable,
++	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, skb, &vif->bss_conf,
++				      &sta->deflink, enable,
+ 				      !rcu_access_pointer(dev->mt76.wcid[msta->wcid.idx]));
+ 	if (!enable)
+ 		goto out;
+diff --git a/mt7925/mcu.c b/mt7925/mcu.c
+index 1cb556302..e9cf442b8 100644
+--- a/mt7925/mcu.c
++++ b/mt7925/mcu.c
+@@ -1626,7 +1626,8 @@ mt7925_mcu_sta_cmd(struct mt76_phy *phy,
+ 		return PTR_ERR(skb);
+ 
+ 	if (info->sta || !info->offload_fw)
+-		mt76_connac_mcu_sta_basic_tlv(dev, skb, info->vif, &info->sta->deflink,
++		mt76_connac_mcu_sta_basic_tlv(dev, skb, &info->vif->bss_conf,
++					      &info->sta->deflink,
+ 					      info->enable, info->newly);
+ 	if (info->sta && info->enable) {
+ 		mt7925_mcu_sta_phy_tlv(skb, info->vif, info->sta);
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 72daf69dd..31c4733c4 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -1167,10 +1167,12 @@ mt7996_mcu_bss_basic_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *conf,
+ 				sta = ieee80211_find_sta(vif, conf->bssid);
+ 			/* TODO: enable BSS_INFO_UAPSD & BSS_INFO_PM */
+ 			if (sta) {
+-				struct mt76_wcid *wcid;
++				struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++				struct mt7996_link_sta *mlink;
+ 
+-				wcid = (struct mt76_wcid *)sta->drv_priv;
+-				sta_wlan_idx = wcid->idx;
++				mlink = rcu_dereference(msta->link[conf->link_id]);
++				if (mlink)
++					sta_wlan_idx = mlink->wcid.idx;
+ 			}
+ 			rcu_read_unlock();
+ 		}
+@@ -1304,9 +1306,8 @@ int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct mt7996_bss_conf *mconf)
+ static int
+ mt7996_mcu_sta_ba(struct mt7996_dev *dev, struct mt76_vif *mvif,
+ 		  struct ieee80211_ampdu_params *params,
+-		  bool enable, bool tx)
++		  struct mt76_wcid *wcid, bool enable, bool tx)
+ {
+-	struct mt76_wcid *wcid = (struct mt76_wcid *)params->sta->drv_priv;
+ 	struct sta_rec_ba_uni *ba;
+ 	struct sk_buff *skb;
+ 	struct tlv *tlv;
+@@ -1336,24 +1337,53 @@ int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev,
+ 			 struct ieee80211_ampdu_params *params,
+ 			 bool enable)
+ {
+-	struct mt7996_sta *msta = (struct mt7996_sta *)params->sta->drv_priv;
+-	struct mt7996_bss_conf *mconf = mconf_dereference_protected(msta->vif, 0);
+-	struct mt7996_link_sta *mlink = mlink_dereference_protected(msta, 0);
++	struct ieee80211_sta *sta = params->sta;
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	unsigned long valid_links = sta->valid_links ?: BIT(0);
++	unsigned int link_id;
++
++	for_each_set_bit(link_id, &valid_links, IEEE80211_MLD_MAX_NUM_LINKS) {
++		struct mt7996_link_sta *mlink =
++			mlink_dereference_protected(msta, link_id);
++		struct mt7996_bss_conf *mconf =
++			mconf_dereference_protected(msta->vif, link_id);
++		int ret;
+ 
+-	if (enable && !params->amsdu)
+-		mlink->wcid.amsdu = false;
++		if (enable && !params->amsdu)
++			mlink->wcid.amsdu = false;
+ 
+-	return mt7996_mcu_sta_ba(dev, &mconf->mt76, params, enable, true);
++		ret = mt7996_mcu_sta_ba(dev, &mconf->mt76, params,
++					&mlink->wcid, enable, true);
++		if (ret)
++			return ret;
++	}
++
++	return 0;
+ }
+ 
+ int mt7996_mcu_add_rx_ba(struct mt7996_dev *dev,
+ 			 struct ieee80211_ampdu_params *params,
+ 			 bool enable)
+ {
+-	struct mt7996_sta *msta = (struct mt7996_sta *)params->sta->drv_priv;
+-	struct mt7996_bss_conf *mconf = mconf_dereference_protected(msta->vif, 0);
++	struct ieee80211_sta *sta = params->sta;
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	unsigned long valid_links = sta->valid_links ?: BIT(0);
++	unsigned int link_id;
+ 
+-	return mt7996_mcu_sta_ba(dev, &mconf->mt76, params, enable, false);
++	for_each_set_bit(link_id, &valid_links, IEEE80211_MLD_MAX_NUM_LINKS) {
++		struct mt7996_link_sta *mlink =
++			mlink_dereference_protected(msta, link_id);
++		struct mt7996_bss_conf *mconf =
++			mconf_dereference_protected(msta->vif, link_id);
++		int ret;
++
++		ret = mt7996_mcu_sta_ba(dev, &mconf->mt76, params, &mlink->wcid,
++					enable, false);
++		if (ret)
++			return ret;
++	}
++
++	return 0;
+ }
+ 
+ static void
+@@ -2377,7 +2407,7 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_bss_conf *conf,
+ 		return PTR_ERR(skb);
+ 
+ 	/* starec basic */
+-	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, skb, vif, link_sta, enable, newly);
++	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, skb, conf, link_sta, enable, newly);
+ 
+ 	if (!enable)
+ 		goto out;
+@@ -2838,7 +2868,7 @@ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw,
+ 			  struct mt7996_bss_conf *mconf, int en)
+ {
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+-	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_phy *phy = mconf->phy;
+ 	struct ieee80211_mutable_offsets offs;
+ 	struct ieee80211_tx_info *info;
+ 	struct sk_buff *skb, *rskb;
+@@ -2854,7 +2884,7 @@ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw,
+ 	if (IS_ERR(rskb))
+ 		return PTR_ERR(rskb);
+ 
+-	skb = ieee80211_beacon_get_template(hw, conf->vif, &offs, 0);
++	skb = ieee80211_beacon_get_template(hw, conf->vif, &offs, conf->link_id);
+ 	if (!skb) {
+ 		dev_kfree_skb(rskb);
+ 		return -EINVAL;
+@@ -2892,9 +2922,9 @@ int mt7996_mcu_beacon_inband_discov(struct mt7996_dev *dev,
+ {
+ #define OFFLOAD_TX_MODE_SU	BIT(0)
+ #define OFFLOAD_TX_MODE_MU	BIT(1)
+-	struct ieee80211_hw *hw = mt76_hw(dev);
+ 	struct ieee80211_vif *vif = conf->vif;
+-	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_phy *phy = mconf->phy;
+ 	struct cfg80211_chan_def *chandef = &mconf->phy->mt76->chandef;
+ 	enum nl80211_band band = chandef->chan->band;
+ 	struct mt76_wcid *wcid = &dev->mt76.global_wcid;
+@@ -5137,7 +5167,7 @@ int mt7996_mcu_get_per_sta_info(struct mt76_dev *dev, u16 tag,
+ 				rssi[3] = to_rssi(MT_PRXV_RCPI0, rcpi[3]);
+ 
+ 				mlink = container_of(wcid, struct mt7996_link_sta, wcid);
+-				phy = mlink->sta->vif->deflink.phy->mt76;
++				phy = dev->phys[wcid->phy_idx];
+ 				mlink->ack_signal = mt76_rx_signal(phy->antenna_mask, rssi);
+ 				ewma_avg_signal_add(&mlink->avg_ack_signal, -mlink->ack_signal);
+ 			} else {
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0114-wifi-mt76-connac-rework-connac-helpers.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0114-wifi-mt76-connac-rework-connac-helpers.patch
new file mode 100644
index 0000000..4330362
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0114-wifi-mt76-connac-rework-connac-helpers.patch
@@ -0,0 +1,174 @@
+From f0c66d83254a243ce43e669a8068492033673bb6 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Mon, 11 Dec 2023 18:45:00 +0800
+Subject: [PATCH 114/120] wifi: mt76: connac: rework connac helpers
+
+Rework connac helpers related to rate and phymode.
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt76_connac.h     |  2 +-
+ mt76_connac_mac.c | 17 ++++++++---------
+ mt76_connac_mcu.c |  6 +++---
+ mt76_connac_mcu.h |  2 +-
+ mt7925/main.c     |  2 +-
+ mt7996/main.c     |  2 +-
+ mt7996/mcu.c      |  2 +-
+ 7 files changed, 16 insertions(+), 17 deletions(-)
+
+diff --git a/mt76_connac.h b/mt76_connac.h
+index 98d64d3d2..ebee5a0c4 100644
+--- a/mt76_connac.h
++++ b/mt76_connac.h
+@@ -421,7 +421,7 @@ void mt76_connac2_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi,
+ 				 struct ieee80211_key_conf *key, int pid,
+ 				 enum mt76_txq_id qid, u32 changed);
+ u16 mt76_connac2_mac_tx_rate_val(struct mt76_phy *mphy,
+-				 struct ieee80211_vif *vif,
++				 struct ieee80211_bss_conf *conf,
+ 				 bool beacon, bool mcast);
+ bool mt76_connac2_mac_fill_txs(struct mt76_dev *dev, struct mt76_wcid *wcid,
+ 			       __le32 *txs_data);
+diff --git a/mt76_connac_mac.c b/mt76_connac_mac.c
+index b841bf628..c1e9ba0fc 100644
+--- a/mt76_connac_mac.c
++++ b/mt76_connac_mac.c
+@@ -291,12 +291,11 @@ EXPORT_SYMBOL_GPL(mt76_connac_init_tx_queues);
+ })
+ 
+ u16 mt76_connac2_mac_tx_rate_val(struct mt76_phy *mphy,
+-				 struct ieee80211_vif *vif,
++				 struct ieee80211_bss_conf *conf,
+ 				 bool beacon, bool mcast)
+ {
+-	struct mt76_vif *mvif = (struct mt76_vif *)vif->drv_priv;
+-	struct cfg80211_chan_def *chandef = mvif->ctx ?
+-					    &mvif->ctx->def : &mphy->chandef;
++	struct ieee80211_vif *vif = conf->vif;
++	struct cfg80211_chan_def *chandef = &mphy->chandef;
+ 	u8 nss = 0, mode = 0, band = chandef->chan->band;
+ 	int rateidx = 0, mcast_rate;
+ 
+@@ -304,14 +303,14 @@ u16 mt76_connac2_mac_tx_rate_val(struct mt76_phy *mphy,
+ 		goto legacy;
+ 
+ 	if (is_mt7921(mphy->dev)) {
+-		rateidx = ffs(vif->bss_conf.basic_rates) - 1;
++		rateidx = ffs(conf->basic_rates) - 1;
+ 		goto legacy;
+ 	}
+ 
+ 	if (beacon) {
+ 		struct cfg80211_bitrate_mask *mask;
+ 
+-		mask = &vif->bss_conf.beacon_tx_rate;
++		mask = &conf->beacon_tx_rate;
+ 
+ 		__bitrate_mask_check(he_mcs, HE_SU);
+ 		__bitrate_mask_check(vht_mcs, VHT);
+@@ -323,11 +322,11 @@ u16 mt76_connac2_mac_tx_rate_val(struct mt76_phy *mphy,
+ 		}
+ 	}
+ 
+-	mcast_rate = vif->bss_conf.mcast_rate[band];
++	mcast_rate = conf->mcast_rate[band];
+ 	if (mcast && mcast_rate > 0)
+ 		rateidx = mcast_rate - 1;
+ 	else
+-		rateidx = ffs(vif->bss_conf.basic_rates) - 1;
++		rateidx = ffs(conf->basic_rates) - 1;
+ 
+ legacy:
+ 	rateidx = mt76_calculate_default_rate(mphy, vif, rateidx);
+@@ -561,7 +560,7 @@ void mt76_connac2_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi,
+ 		struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+ 		bool multicast = ieee80211_is_data(hdr->frame_control) &&
+ 				 is_multicast_ether_addr(hdr->addr1);
+-		u16 rate = mt76_connac2_mac_tx_rate_val(mphy, vif, beacon,
++		u16 rate = mt76_connac2_mac_tx_rate_val(mphy, &vif->bss_conf, beacon,
+ 							multicast);
+ 		u32 val = MT_TXD6_FIXED_BW;
+ 
+diff --git a/mt76_connac_mcu.c b/mt76_connac_mcu.c
+index e7039a21f..5d76a088f 100644
+--- a/mt76_connac_mcu.c
++++ b/mt76_connac_mcu.c
+@@ -1361,7 +1361,7 @@ u8 mt76_connac_get_phy_mode(struct mt76_phy *phy, struct ieee80211_vif *vif,
+ }
+ EXPORT_SYMBOL_GPL(mt76_connac_get_phy_mode);
+ 
+-u8 mt76_connac_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_vif *vif,
++u8 mt76_connac_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_bss_conf *conf,
+ 				enum nl80211_band band)
+ {
+ 	const struct ieee80211_sta_eht_cap *eht_cap;
+@@ -1372,9 +1372,9 @@ u8 mt76_connac_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_vif *vif,
+ 		mode |= PHY_MODE_AX_6G;
+ 
+ 	sband = phy->hw->wiphy->bands[band];
+-	eht_cap = ieee80211_get_eht_iftype_cap(sband, vif->type);
++	eht_cap = ieee80211_get_eht_iftype_cap(sband, conf->vif->type);
+ 
+-	if (!eht_cap || !eht_cap->has_eht || !vif->bss_conf.eht_support)
++	if (!eht_cap || !eht_cap->has_eht || !conf->eht_support)
+ 		return mode;
+ 
+ 	switch (band) {
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 7f1562b8e..41e26b8a4 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -2016,7 +2016,7 @@ mt76_connac_get_eht_phy_cap(struct mt76_phy *phy, struct ieee80211_vif *vif);
+ u8 mt76_connac_get_phy_mode(struct mt76_phy *phy, struct ieee80211_vif *vif,
+ 			    enum nl80211_band band,
+ 			    struct ieee80211_link_sta *link_sta);
+-u8 mt76_connac_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_vif *vif,
++u8 mt76_connac_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_bss_conf *conf,
+ 				enum nl80211_band band);
+ 
+ int mt76_connac_mcu_add_key(struct mt76_dev *dev, struct ieee80211_vif *vif,
+diff --git a/mt7925/main.c b/mt7925/main.c
+index 1f07ec5a2..6d7ec7708 100644
+--- a/mt7925/main.c
++++ b/mt7925/main.c
+@@ -675,7 +675,7 @@ mt7925_get_rates_table(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	u16 rate;
+ 	u8 i, idx, ht;
+ 
+-	rate = mt76_connac2_mac_tx_rate_val(mphy, vif, beacon, mcast);
++	rate = mt76_connac2_mac_tx_rate_val(mphy, &vif->bss_conf, beacon, mcast);
+ 	ht = FIELD_GET(MT_TX_RATE_MODE, rate) > MT_PHY_TYPE_OFDM;
+ 
+ 	if (beacon && ht) {
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 03f23453d..ebbe4785d 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -803,7 +803,7 @@ mt7996_get_rates_table(struct ieee80211_hw *hw, struct ieee80211_bss_conf *conf,
+ 	u16 rate;
+ 	u8 i, idx;
+ 
+-	rate = mt76_connac2_mac_tx_rate_val(mphy, conf->vif, beacon, mcast);
++	rate = mt76_connac2_mac_tx_rate_val(mphy, conf, beacon, mcast);
+ 
+ 	if (beacon) {
+ 		struct mt7996_phy *phy = mphy->priv;
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 31c4733c4..fb73e2e23 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -1217,7 +1217,7 @@ mt7996_mcu_bss_basic_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *conf,
+ 	bss->dtim_period = conf->dtim_period;
+ 	bss->phymode = mt76_connac_get_phy_mode(phy, vif,
+ 						chandef->chan->band, NULL);
+-	bss->phymode_ext = mt76_connac_get_phy_mode_ext(phy, vif,
++	bss->phymode_ext = mt76_connac_get_phy_mode_ext(phy, conf,
+ 							chandef->chan->band);
+ 
+ 	return 0;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0115-wifi-mt76-mt7996-handle-mapping-for-hw-and-phy.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0115-wifi-mt76-mt7996-handle-mapping-for-hw-and-phy.patch
new file mode 100644
index 0000000..6fdcc78
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0115-wifi-mt76-mt7996-handle-mapping-for-hw-and-phy.patch
@@ -0,0 +1,218 @@
+From 8b4da78cf20c8b055bd505a34dcd6e4522b48a72 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Tue, 5 Dec 2023 13:56:51 +0800
+Subject: [PATCH 115/120] wifi: mt76: mt7996: handle mapping for hw and phy
+
+We've used mt7996_band_phy() to do mapping from ieee80211_hw to mt7996_phy,
+and this patch is a temporal workaround for opposite direction.
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mac80211.c    | 11 ++++++++++-
+ mt76.h        | 10 ++++++++++
+ mt7996/mac.c  |  7 +++++--
+ mt7996/main.c | 41 ++++++++++++++++++++++++++++++-----------
+ 4 files changed, 55 insertions(+), 14 deletions(-)
+
+diff --git a/mac80211.c b/mac80211.c
+index 96fab2320..175f19169 100644
+--- a/mac80211.c
++++ b/mac80211.c
+@@ -823,9 +823,13 @@ EXPORT_SYMBOL_GPL(mt76_has_tx_pending);
+ struct mt76_channel_state *
+ mt76_channel_state(struct mt76_phy *phy, struct ieee80211_channel *c)
+ {
++	struct mt76_phy *ori_phy = phy;
+ 	struct mt76_sband *msband;
+ 	int idx;
+ 
++	if (phy->main_phy)
++		phy = phy->main_phy;
++begin:
+ 	if (c->band == NL80211_BAND_2GHZ)
+ 		msband = &phy->sband_2g;
+ 	else if (c->band == NL80211_BAND_6GHZ)
+@@ -834,6 +838,11 @@ mt76_channel_state(struct mt76_phy *phy, struct ieee80211_channel *c)
+ 		msband = &phy->sband_5g;
+ 
+ 	idx = c - &msband->sband.channels[0];
++	/* TODO: mlo: this is a temp solution, need to come up with a more clever one */
++	if (idx < 0 || idx >= msband->sband.n_channels) {
++		phy = ori_phy;
++		goto begin;
++	}
+ 	return &msband->chan[idx];
+ }
+ EXPORT_SYMBOL_GPL(mt76_channel_state);
+@@ -1072,7 +1081,7 @@ mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,
+ 	}
+ 
+ 	*sta = wcid_to_sta(mstat.wcid);
+-	*hw = mt76_phy_hw(dev, mstat.phy_idx);
++	*hw = mt76_main_hw(dev->phys[mstat.phy_idx]);
+ 
+ 	if ((mstat.flag & RX_FLAG_8023) || ieee80211_is_data_qos(hdr->frame_control)) {
+ 		struct mt76_phy *phy = mt76_dev_phy(dev, mstat.phy_idx);
+diff --git a/mt76.h b/mt76.h
+index 3e9203512..e13ee1bdd 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -826,6 +826,7 @@ struct mt76_vif {
+ struct mt76_phy {
+ 	struct ieee80211_hw *hw;
+ 	struct mt76_dev *dev;
++	struct mt76_phy *main_phy;
+ 	void *priv;
+ 
+ 	unsigned long state;
+@@ -1317,6 +1318,15 @@ mt76_phy_hw(struct mt76_dev *dev, u8 phy_idx)
+ 	return mt76_dev_phy(dev, phy_idx)->hw;
+ }
+ 
++static inline struct ieee80211_hw *
++mt76_main_hw(struct mt76_phy *phy)
++{
++	if (phy->main_phy)
++		return mt76_dev_phy(phy->dev, phy->main_phy->band_idx)->hw;
++
++	return mt76_dev_phy(phy->dev, phy->band_idx)->hw;
++}
++
+ static inline u8 *
+ mt76_get_txwi_ptr(struct mt76_dev *dev, struct mt76_txwi_cache *t)
+ {
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 545bcb021..b4a581386 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -2372,7 +2372,10 @@ void mt7996_mac_work(struct work_struct *work)
+ 
+ 	mt76_tx_status_check(mdev, false);
+ 
+-	ieee80211_queue_delayed_work(mphy->hw, &mphy->mac_work,
++	if (mphy->main_phy && !test_bit(MT76_STATE_RUNNING, &mphy->main_phy->state))
++		return;
++
++	ieee80211_queue_delayed_work(mt76_main_hw(mphy), &mphy->mac_work,
+ 				     MT7996_WATCHDOG_TIME);
+ }
+ 
+@@ -2783,7 +2786,7 @@ mt7996_scan_send_probe(struct mt7996_phy *phy, struct cfg80211_ssid *ssid)
+ 	skb_set_queue_mapping(skb, IEEE80211_AC_VO);
+ 
+ 	rcu_read_lock();
+-	if (!ieee80211_tx_prepare_skb(hw, vif, skb,
++	if (!ieee80211_tx_prepare_skb(mt76_main_hw(phy->mt76), vif, skb,
+ 				      phy->scan_chan->band,
+ 				      NULL)) {
+ 		rcu_read_unlock();
+diff --git a/mt7996/main.c b/mt7996/main.c
+index ebbe4785d..c530ecdae 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -173,6 +173,7 @@ static void mt7996_stop(struct ieee80211_hw *hw)
+ 		mutex_lock(&dev->mt76.mutex);
+ 		mt7996_mcu_set_radio_en(phy, false);
+ 		clear_bit(MT76_STATE_RUNNING, &phy->mt76->state);
++		phy->mt76->main_phy = NULL;
+ 		mutex_unlock(&dev->mt76.mutex);
+ 	}
+ }
+@@ -541,9 +542,10 @@ out:
+ 	clear_bit(MT76_RESET, &phy->mt76->state);
+ 	mutex_unlock(&dev->mt76.mutex);
+ 
+-	mt76_txq_schedule_all(phy->mt76);
++	if (phy->mt76 == phy->mt76->main_phy)
++		mt76_txq_schedule_all(phy->mt76);
+ 
+-	ieee80211_queue_delayed_work(phy->mt76->hw,
++	ieee80211_queue_delayed_work(mt76_main_hw(phy->mt76),
+ 				     &phy->mt76->mac_work,
+ 				     MT7996_WATCHDOG_TIME);
+ 
+@@ -554,11 +556,11 @@ int mt7996_set_channel(struct mt7996_phy *phy, struct cfg80211_chan_def *chandef
+ {
+ 	int ret;
+ 
+-	ieee80211_stop_queues(phy->mt76->hw);
++	ieee80211_stop_queues(mt76_main_hw(phy->mt76));
+ 	ret = __mt7996_set_channel(phy, chandef);
+ 	if (ret)
+ 		return ret;
+-	ieee80211_wake_queues(phy->mt76->hw);
++	ieee80211_wake_queues(mt76_main_hw(phy->mt76));
+ 
+ 	return 0;
+ }
+@@ -730,6 +732,7 @@ static void mt7996_configure_filter(struct ieee80211_hw *hw,
+ 			MT_WF_RFCR1_DROP_CFEND |
+ 			MT_WF_RFCR1_DROP_CFACK;
+ 	u32 flags = 0;
++	u8 band;
+ 
+ #define MT76_FILTER(_flag, _hw) do {					\
+ 		flags |= *total_flags & FIF_##_flag;			\
+@@ -763,12 +766,26 @@ static void mt7996_configure_filter(struct ieee80211_hw *hw,
+ 			     MT_WF_RFCR_DROP_NDPA);
+ 
+ 	*total_flags = flags;
+-	mt76_wr(dev, MT_WF_RFCR(phy->mt76->band_idx), phy->rxfilter);
+ 
+-	if (*total_flags & FIF_CONTROL)
+-		mt76_clear(dev, MT_WF_RFCR1(phy->mt76->band_idx), ctl_flags);
+-	else
+-		mt76_set(dev, MT_WF_RFCR1(phy->mt76->band_idx), ctl_flags);
++	/* configure rx filter to all affliated phy */
++	for (band = 0; band < __MT_MAX_BAND; band++) {
++		struct mt7996_phy *tmp;
++
++		if (!hw->wiphy->bands[band])
++			continue;
++
++		tmp = dev->mt76.phys[band]->priv;
++		if (tmp->mt76->main_phy != phy->mt76)
++			continue;
++
++		tmp->rxfilter = phy->rxfilter;
++		mt76_wr(dev, MT_WF_RFCR(tmp->mt76->band_idx), phy->rxfilter);
++
++		if (*total_flags & FIF_CONTROL)
++			mt76_clear(dev, MT_WF_RFCR1(tmp->mt76->band_idx), ctl_flags);
++		else
++			mt76_set(dev, MT_WF_RFCR1(tmp->mt76->band_idx), ctl_flags);
++	}
+ 
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+@@ -2180,7 +2197,7 @@ mt7996_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	phy->scan_chan_idx = 0;
+ 	mutex_unlock(&phy->dev->mt76.mutex);
+ 
+-	ieee80211_queue_delayed_work(hw, &phy->scan_work, 0);
++	ieee80211_queue_delayed_work(mt76_main_hw(phy->mt76), &phy->scan_work, 0);
+ 
+ 	return 0;
+ }
+@@ -2197,7 +2214,8 @@ mt7996_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+ 			continue;
+ 
+ 		phy = mt7996_band_phy(hw, band);
+-		if (!(test_bit(MT76_SCANNING, &phy->mt76->state)))
++		if (!(test_bit(MT76_SCANNING, &phy->mt76->state) &&
++		      phy->mt76->main_phy == hw->priv))
+ 			continue;
+ 
+ 		cancel_delayed_work_sync(&phy->scan_work);
+@@ -2218,6 +2236,7 @@ mt7996_add_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *conf)
+ 	wiphy_info(hw->wiphy, "%s: add %u\n", __func__, conf->def.chan->hw_value);
+ 	mutex_lock(&phy->dev->mt76.mutex);
+ 
++	phy->mt76->main_phy = hw->priv;
+ 	if (ctx->assigned) {
+ 		mutex_unlock(&phy->dev->mt76.mutex);
+ 		return -ENOSPC;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0116-wifi-mt76-mt7996-handle-mapping-for-hw-and-vif.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0116-wifi-mt76-mt7996-handle-mapping-for-hw-and-vif.patch
new file mode 100644
index 0000000..68cac0c
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0116-wifi-mt76-mt7996-handle-mapping-for-hw-and-vif.patch
@@ -0,0 +1,222 @@
+From 604e77440b636e9ad439325ae7ca3466af9a1850 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Fri, 8 Dec 2023 18:08:13 +0800
+Subject: [PATCH 116/120] wifi: mt76: mt7996: handle mapping for hw and vif
+
+We have several temporal workarounds for ieee80211_hw and mt76_phy
+mappings. For legacy MBSS cases, we also need a method to do the
+hw and vif mappings.
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ dma.c           |  2 +-
+ mac80211.c      |  2 +-
+ mt76.h          | 14 ++++++++++++--
+ mt7996/mac.c    | 24 +++++++++++++++++++++++-
+ mt7996/main.c   |  1 +
+ mt7996/mcu.c    |  4 ++--
+ mt7996/mmio.c   |  1 +
+ mt7996/mt7996.h |  3 +++
+ tx.c            |  4 ++--
+ 9 files changed, 46 insertions(+), 9 deletions(-)
+
+diff --git a/dma.c b/dma.c
+index 38701c71e..3f1fb6c2a 100644
+--- a/dma.c
++++ b/dma.c
+@@ -685,7 +685,7 @@ free:
+ 
+ free_skb:
+ 	status.skb = tx_info.skb;
+-	hw = mt76_tx_status_get_hw(dev, tx_info.skb);
++	hw = mt76_tx_status_get_hw(dev, tx_info.skb, wcid);
+ 	spin_lock_bh(&dev->rx_lock);
+ 	ieee80211_tx_status_ext(hw, &status);
+ 	spin_unlock_bh(&dev->rx_lock);
+diff --git a/mac80211.c b/mac80211.c
+index 175f19169..dad659ecb 100644
+--- a/mac80211.c
++++ b/mac80211.c
+@@ -1519,7 +1519,7 @@ void mt76_wcid_cleanup(struct mt76_dev *dev, struct mt76_wcid *wcid)
+ 	spin_unlock_bh(&phy->tx_lock);
+ 
+ 	while ((skb = __skb_dequeue(&list)) != NULL) {
+-		hw = mt76_tx_status_get_hw(dev, skb);
++		hw = mt76_tx_status_get_hw(dev, skb, wcid);
+ 		ieee80211_free_txskb(hw, skb);
+ 	}
+ }
+diff --git a/mt76.h b/mt76.h
+index e13ee1bdd..984008024 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -551,6 +551,9 @@ struct mt76_driver_ops {
+ 
+ 	void (*sta_remove)(struct mt76_dev *dev, struct ieee80211_vif *vif,
+ 			   struct ieee80211_sta *sta);
++
++	void (*get_hw)(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 phy_idx,
++		       struct ieee80211_hw **hw);
+ };
+ 
+ struct mt76_channel_state {
+@@ -1596,14 +1599,21 @@ static inline void mt76_testmode_reset(struct mt76_phy *phy, bool disable)
+ 
+ /* internal */
+ static inline struct ieee80211_hw *
+-mt76_tx_status_get_hw(struct mt76_dev *dev, struct sk_buff *skb)
++mt76_tx_status_get_hw(struct mt76_dev *dev, struct sk_buff *skb,
++		      struct mt76_wcid *wcid)
+ {
+ 	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ 	u8 phy_idx = (info->hw_queue & MT_TX_HW_QUEUE_PHY) >> 2;
+-	struct ieee80211_hw *hw = mt76_phy_hw(dev, phy_idx);
++	struct ieee80211_hw *hw;
+ 
+ 	info->hw_queue &= ~MT_TX_HW_QUEUE_PHY;
+ 
++	if (dev->drv->get_hw) {
++		dev->drv->get_hw(dev, wcid, phy_idx, &hw);
++	} else {
++		hw = mt76_phy_hw(dev, phy_idx);
++	}
++
+ 	return hw;
+ }
+ 
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index b4a581386..2e24c5376 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -2827,13 +2827,23 @@ static void mt7996_scan_check_sta(void *data, struct ieee80211_sta *sta)
+ void mt7996_scan_work(struct work_struct *work)
+ {
+ 	struct mt7996_phy *phy = container_of(work, struct mt7996_phy, scan_work.work);
+-	struct ieee80211_hw *hw = phy->mt76->hw;
++	struct ieee80211_vif *vif = phy->scan_vif;
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct ieee80211_hw *hw = mvif->hw;
+ 	struct cfg80211_scan_request *req = phy->scan_req;
+ 	struct cfg80211_chan_def chandef = {};
+ 	int duration;
+ 	bool has_sta = false, active_scan = false;
+ 
+ 	mutex_lock(&phy->dev->mt76.mutex);
++	/* don't let non-MLD AP scan other bands */
++	if (vif->type != NL80211_IFTYPE_STATION && !ieee80211_vif_is_mld(vif) &&
++	    phy != mt7996_hw_phy(hw)) {
++		mt7996_scan_complete(phy, false);
++		mutex_unlock(&phy->dev->mt76.mutex);
++		return;
++	}
++
+ 	if (phy->scan_chan_idx >= req->n_channels) {
+ 		mt7996_scan_complete(phy, false);
+ 		mutex_unlock(&phy->dev->mt76.mutex);
+@@ -2893,3 +2903,15 @@ void mt7996_scan_work(struct work_struct *work)
+ 
+ 	ieee80211_queue_delayed_work(hw, &phy->scan_work, duration);
+ }
++
++void mt7996_get_hw(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 phy_idx,
++		   struct ieee80211_hw **hw)
++{
++	struct mt7996_link_sta *mlink = wcid_to_mlink(wcid);
++
++	if (mlink) {
++		*hw = mlink->sta->vif->hw;
++	} else {
++		*hw = mt76_phy_hw(dev, phy_idx);
++	}
++}
+diff --git a/mt7996/main.c b/mt7996/main.c
+index c530ecdae..527294e95 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -444,6 +444,7 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 		phy->monitor_vif = vif;
+ 
+ 	mvif->dev = dev;
++	mvif->hw = hw;
+ 	mvif->sta.vif = mvif;
+ 
+ 	ret = mt7996_add_bss_conf(phy, vif, &vif->bss_conf);
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index fb73e2e23..3e7d48ee8 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -2945,11 +2945,11 @@ int mt7996_mcu_beacon_inband_discov(struct mt7996_dev *dev,
+ 
+ 	if (changed & BSS_CHANGED_FILS_DISCOVERY) {
+ 		interval = conf->fils_discovery.max_interval;
+-		skb = ieee80211_get_fils_discovery_tmpl(hw, vif);
++		skb = ieee80211_get_fils_discovery_tmpl(mvif->hw, vif);
+ 	} else if (changed & BSS_CHANGED_UNSOL_BCAST_PROBE_RESP &&
+ 		   conf->unsol_bcast_probe_resp_interval) {
+ 		interval = conf->unsol_bcast_probe_resp_interval;
+-		skb = ieee80211_get_unsol_bcast_probe_resp_tmpl(hw, vif);
++		skb = ieee80211_get_unsol_bcast_probe_resp_tmpl(mvif->hw, vif);
+ 	}
+ 
+ 	if (!skb) {
+diff --git a/mt7996/mmio.c b/mt7996/mmio.c
+index 61b1d7d62..af39cf76f 100644
+--- a/mt7996/mmio.c
++++ b/mt7996/mmio.c
+@@ -647,6 +647,7 @@ struct mt7996_dev *mt7996_mmio_probe(struct device *pdev,
+ 		.sta_assoc = mt7996_mac_sta_assoc,
+ 		.sta_remove = mt7996_mac_sta_remove,
+ 		.update_survey = mt7996_update_channel,
++		.get_hw = mt7996_get_hw,
+ 	};
+ 	struct mt7996_dev *dev;
+ 	struct mt76_dev *mdev;
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 62270101e..691d2bc83 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -354,6 +354,7 @@ struct mt7996_vif {
+ 
+ 	struct mt7996_sta sta;
+ 	struct mt7996_dev *dev;
++	struct ieee80211_hw *hw;
+ 
+ 	u8 master_link_id;
+ 	u8 group_mld_id;
+@@ -893,6 +894,8 @@ int mt7996_init_tx_queues(struct mt7996_phy *phy, int idx,
+ void mt7996_init_txpower(struct mt7996_phy *phy);
+ int mt7996_txbf_init(struct mt7996_dev *dev);
+ int mt7996_get_chip_sku(struct mt7996_dev *dev);
++void mt7996_get_hw(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 phy_idx,
++		   struct ieee80211_hw **hw);
+ void mt7996_reset(struct mt7996_dev *dev);
+ void mt7996_coredump(struct mt7996_dev *dev, u8 state);
+ int mt7996_run(struct ieee80211_hw *hw);
+diff --git a/tx.c b/tx.c
+index e2795067c..6580833e9 100644
+--- a/tx.c
++++ b/tx.c
+@@ -76,7 +76,7 @@ mt76_tx_status_unlock(struct mt76_dev *dev, struct sk_buff_head *list)
+ 			}
+ 		}
+ 
+-		hw = mt76_tx_status_get_hw(dev, skb);
++		hw = mt76_tx_status_get_hw(dev, skb, wcid);
+ 		spin_lock_bh(&dev->rx_lock);
+ 		ieee80211_tx_status_ext(hw, &status);
+ 		spin_unlock_bh(&dev->rx_lock);
+@@ -272,7 +272,7 @@ void __mt76_tx_complete_skb(struct mt76_dev *dev, u16 wcid_idx, struct sk_buff *
+ 	if (cb->pktid < MT_PACKET_ID_FIRST) {
+ 		struct ieee80211_rate_status rs = {};
+ 
+-		hw = mt76_tx_status_get_hw(dev, skb);
++		hw = mt76_tx_status_get_hw(dev, skb, wcid);
+ 		status.sta = wcid_to_sta(wcid);
+ 		if (status.sta && (wcid->rate.flags || wcid->rate.legacy)) {
+ 			rs.rate_idx = wcid->rate;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0117-wifi-mt76-mt7996-rework-scanning-parts-for-MLD-STA-s.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0117-wifi-mt76-mt7996-rework-scanning-parts-for-MLD-STA-s.patch
new file mode 100644
index 0000000..032acc6
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0117-wifi-mt76-mt7996-rework-scanning-parts-for-MLD-STA-s.patch
@@ -0,0 +1,144 @@
+From f6435b1edaa098cab7faa67f996fb3843d4467ce Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Wed, 13 Dec 2023 16:58:31 +0800
+Subject: [PATCH 117/120] wifi: mt76: mt7996: rework scanning parts for MLD STA
+ support
+
+During the first scanning, the STA VIF is still a legacy interface,
+and at the moment it doesn't know if it's MLD or not. So do dome tricks
+here to let the legacy interface be abled to scan all bands.
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/mac.c  | 28 ++++++++++++++++++++--------
+ mt7996/main.c | 17 +++++++++++++++++
+ 2 files changed, 37 insertions(+), 8 deletions(-)
+
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 2e24c5376..86c45adfd 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -2758,20 +2758,34 @@ void mt7996_mac_twt_teardown_flow(struct mt7996_dev *dev,
+ static void
+ mt7996_scan_send_probe(struct mt7996_phy *phy, struct cfg80211_ssid *ssid)
+ {
+-	struct ieee80211_hw *hw = phy->mt76->hw;
+ 	struct cfg80211_scan_request *req = phy->scan_req;
+ 	struct ieee80211_vif *vif = phy->scan_vif;
++	struct ieee80211_bss_conf *conf;
+ 	struct mt7996_vif *mvif;
+ 	struct mt7996_link_sta *mlink;
+ 	struct ieee80211_tx_info *info;
++	struct ieee80211_hw *hw;
+ 	struct sk_buff *skb;
++	unsigned long valid_links;
++	unsigned int link_id;
+ 
+ 	if (!req || !vif)
+ 		return;
+ 
++	valid_links = vif->valid_links ?: BIT(0);
+ 	mvif = (struct mt7996_vif *)vif->drv_priv;
++	hw = mvif->hw;
+ 
+-	skb = ieee80211_probereq_get(hw, vif->addr,
++	rcu_read_lock();
++
++	for_each_set_bit(link_id, &valid_links, IEEE80211_MLD_MAX_NUM_LINKS) {
++		conf = rcu_dereference(vif->link_conf[link_id]);
++		mlink = rcu_dereference(mvif->sta.link[link_id]);
++		if (mlink->wcid.phy_idx != phy->mt76->band_idx)
++			continue;
++	}
++
++	skb = ieee80211_probereq_get(hw, conf->addr,
+ 				     ssid->ssid, ssid->ssid_len, req->ie_len);
+ 	if (!skb)
+ 		return;
+@@ -2785,17 +2799,14 @@ mt7996_scan_send_probe(struct mt7996_phy *phy, struct cfg80211_ssid *ssid)
+ 
+ 	skb_set_queue_mapping(skb, IEEE80211_AC_VO);
+ 
+-	rcu_read_lock();
+-	if (!ieee80211_tx_prepare_skb(mt76_main_hw(phy->mt76), vif, skb,
+-				      phy->scan_chan->band,
+-				      NULL)) {
++	if (!ieee80211_tx_prepare_skb(hw, vif, skb,
++				      phy->scan_chan->band, NULL)) {
+ 		rcu_read_unlock();
+ 		ieee80211_free_txskb(hw, skb);
+ 		return;
+ 	}
+ 
+ 	local_bh_disable();
+-	mlink = rcu_dereference(mvif->sta.link[0]);
+ 	mt76_tx(phy->mt76, NULL, &mlink->wcid, skb);
+ 	local_bh_enable();
+ 
+@@ -2804,11 +2815,12 @@ mt7996_scan_send_probe(struct mt7996_phy *phy, struct cfg80211_ssid *ssid)
+ 
+ void mt7996_scan_complete(struct mt7996_phy *phy, bool aborted)
+ {
++	struct mt7996_vif *mvif = (struct mt7996_vif *)phy->scan_vif->drv_priv;
+ 	struct cfg80211_scan_info info = {
+ 		.aborted = aborted,
+ 	};
+ 
+-	ieee80211_scan_completed(phy->mt76->hw, &info);
++	ieee80211_scan_completed(mvif->hw, &info);
+ 	phy->scan_chan = NULL;
+ 	phy->scan_req = NULL;
+ 	phy->scan_vif = NULL;
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 527294e95..b2c6354c4 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -2185,6 +2185,8 @@ mt7996_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ {
+ 	struct cfg80211_scan_request *req = &hw_req->req;
+ 	struct mt7996_phy *phy = mt7996_band_phy(hw, req->channels[0]->band);
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	int ret;
+ 
+ 	mutex_lock(&phy->dev->mt76.mutex);
+ 	if (WARN_ON(phy->scan_req || phy->scan_chan)) {
+@@ -2196,6 +2198,17 @@ mt7996_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	phy->scan_req = req;
+ 	phy->scan_vif = vif;
+ 	phy->scan_chan_idx = 0;
++	if (vif->type == NL80211_IFTYPE_STATION && !ieee80211_vif_is_mld(vif) &&
++	    (phy->mt76 != mvif->deflink.phy->mt76)) {
++		phy->mt76->main_phy = hw->priv;
++		mt7996_remove_bss_conf(vif, &vif->bss_conf, &mvif->deflink);
++
++		ret = mt7996_add_bss_conf(phy, vif, &vif->bss_conf);
++		if (ret) {
++			mutex_unlock(&phy->dev->mt76.mutex);
++			return ret;
++		}
++	}
+ 	mutex_unlock(&phy->dev->mt76.mutex);
+ 
+ 	ieee80211_queue_delayed_work(mt76_main_hw(phy->mt76), &phy->scan_work, 0);
+@@ -2206,6 +2219,7 @@ mt7996_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ static void
+ mt7996_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+ {
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	int band;
+ 
+ 	for (band = 0; band < NUM_NL80211_BANDS; band++) {
+@@ -2223,6 +2237,9 @@ mt7996_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+ 
+ 		mutex_lock(&phy->dev->mt76.mutex);
+ 		mt7996_scan_complete(phy, true);
++		if (vif->type == NL80211_IFTYPE_STATION && !ieee80211_vif_is_mld(vif) &&
++		    (phy->mt76 != mvif->deflink.phy->mt76))
++			phy->mt76->main_phy = NULL;
+ 		mutex_unlock(&phy->dev->mt76.mutex);
+ 	}
+ }
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0118-wifi-mt76-mt7996-implement-mld-address-translation.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0118-wifi-mt76-mt7996-implement-mld-address-translation.patch
new file mode 100644
index 0000000..d045d2b
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0118-wifi-mt76-mt7996-implement-mld-address-translation.patch
@@ -0,0 +1,116 @@
+From afd1d82534042740dfa6128464e9756483281942 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Thu, 7 Dec 2023 16:31:56 +0800
+Subject: [PATCH 118/120] wifi: mt76: mt7996: implement mld address translation
+
+Do the MLD to link address translation for EAPOL and management frames
+in driver.
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Co-developed-by: Michael-CY Lee <michael-cy.lee@mediatek.com>
+Signed-off-by: Michael-CY Lee <michael-cy.lee@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/mac.c  |  7 +++++++
+ mt7996/main.c | 46 +++++++++++++++++++++++++++++++++++++++++++---
+ 2 files changed, 50 insertions(+), 3 deletions(-)
+
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 86c45adfd..82387da8b 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -833,6 +833,7 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
+ 	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx_info->skb);
+ 	struct ieee80211_key_conf *key = info->control.hw_key;
+ 	struct ieee80211_vif *vif = info->control.vif;
++	struct ieee80211_bss_conf *conf;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_sta *msta;
+ 	struct mt7996_bss_conf *mconf;
+@@ -893,6 +894,12 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
+ 		mt7996_mac_write_txwi(dev, txwi_ptr, tx_info->skb, wcid, key,
+ 				      pid, qid, 0);
+ 
++	if (!is_8023 && sta && sta->mlo &&
++	    unlikely(tx_info->skb->protocol == cpu_to_be16(ETH_P_PAE))) {
++		conf = rcu_dereference(vif->link_conf[wcid->link_id]);
++		memcpy(hdr->addr3, conf->addr, ETH_ALEN);
++	}
++
+ 	txp = (struct mt76_connac_txp_common *)(txwi + MT_TXD_SIZE);
+ 	for (i = 0; i < nbuf; i++) {
+ 		u16 len;
+diff --git a/mt7996/main.c b/mt7996/main.c
+index b2c6354c4..bbe9b9524 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -1307,14 +1307,53 @@ static void mt7996_tx(struct ieee80211_hw *hw,
+ 
+ 	rcu_read_lock();
+ 	if (mvif && msta) {
++		struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+ 		struct mt7996_bss_conf *mconf;
+ 		struct mt7996_link_sta *mlink;
+-
+ 		u8 link_id = u32_get_bits(info->control.flags,
+ 					  IEEE80211_TX_CTRL_MLO_LINK);
++		struct ieee80211_sta *sta = ieee80211_find_sta(vif, hdr->addr1);
++
++		if (link_id >= IEEE80211_LINK_UNSPECIFIED) {
++			if (sta) {
++				struct mt7996_sta *peer;
+ 
+-		if (link_id >= IEEE80211_LINK_UNSPECIFIED)
+-			link_id = mvif->master_link_id;
++				peer = (struct mt7996_sta *)sta->drv_priv;
++				link_id = peer->pri_link;
++			} else {
++				link_id = mvif->master_link_id;
++			}
++		}
++
++		/* translate mld addr to link addr */
++		if (ieee80211_vif_is_mld(vif)) {
++			struct ieee80211_bss_conf *conf;
++			if (sta) {
++				struct ieee80211_link_sta *link_sta =
++					rcu_dereference(sta->link[link_id]);
++
++				if (!link_sta) {
++					mlo_dbg(mt7996_hw_phy(mvif->hw), "request TX on invalid link_id=%u, use primary link (id=%u) instead.\n",
++						      link_id, msta->pri_link);
++					link_id = msta->pri_link;
++					link_sta = rcu_dereference(sta->link[link_id]);
++
++					if (!link_sta) {
++						mlo_dbg(mt7996_hw_phy(mvif->hw), "primary link became invalid, give up the TX\n");
++						goto unlock;
++					}
++				}
++
++				memcpy(hdr->addr1, link_sta->addr, ETH_ALEN);
++				if (ether_addr_equal(sta->addr, hdr->addr3))
++					memcpy(hdr->addr3, link_sta->addr, ETH_ALEN);
++			}
++
++			conf = rcu_dereference(vif->link_conf[link_id]);
++			memcpy(hdr->addr2, conf->addr, ETH_ALEN);
++			if (ether_addr_equal(vif->addr, hdr->addr3))
++				memcpy(hdr->addr3, conf->addr, ETH_ALEN);
++		}
+ 
+ 		mconf = rcu_dereference(mvif->link[link_id]);
+ 		mlink = rcu_dereference(msta->link[link_id]);
+@@ -1328,6 +1367,7 @@ static void mt7996_tx(struct ieee80211_hw *hw,
+ 	}
+ 
+ 	mt76_tx(mphy, control->sta, wcid, skb);
++unlock:
+ 	rcu_read_unlock();
+ }
+ 
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0119-mlo-debug-print.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0119-mlo-debug-print.patch
new file mode 100644
index 0000000..bf0e6e3
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0119-mlo-debug-print.patch
@@ -0,0 +1,166 @@
+From 151293f0bd5e891e65c139fdde5ea71cf6237307 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Tue, 5 Dec 2023 17:20:02 +0800
+Subject: [PATCH 119/120] mlo debug print
+
+---
+ mt76_connac_mcu.c |  1 +
+ mt7996/main.c     | 14 ++++++++++++++
+ mt7996/mcu.c      | 14 ++++++++++++++
+ mt7996/mt7996.h   |  2 ++
+ 4 files changed, 31 insertions(+)
+
+diff --git a/mt76_connac_mcu.c b/mt76_connac_mcu.c
+index 5d76a088f..082e2379f 100644
+--- a/mt76_connac_mcu.c
++++ b/mt76_connac_mcu.c
+@@ -433,6 +433,7 @@ void mt76_connac_mcu_sta_basic_tlv(struct mt76_dev *dev, struct sk_buff *skb,
+ 	}
+ 
+ 	memcpy(basic->peer_addr, link_sta->addr, ETH_ALEN);
++	pr_info("%s: link %u addr [%pM]\n", __func__, link_sta->link_id, basic->peer_addr);
+ 	basic->qos = sta->wme;
+ }
+ EXPORT_SYMBOL_GPL(mt76_connac_mcu_sta_basic_tlv);
+diff --git a/mt7996/main.c b/mt7996/main.c
+index bbe9b9524..785f67503 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -424,6 +424,9 @@ static int mt7996_add_bss_conf(struct mt7996_phy *phy,
+ 	rcu_assign_pointer(mvif->link[link_id], mconf);
+ 	rcu_assign_pointer(mvif->sta.link[link_id], mlink);
+ 
++	mlo_dbg(phy, "bss_idx=%u, link_id=%u, wcid=%u\n",
++		mconf->mt76.idx, mconf->link_id, mlink->wcid.idx);
++
+ 	return 0;
+ error:
+ 	mt7996_remove_bss_conf(vif, conf, mconf);
+@@ -592,6 +595,11 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+ 			add = vif->valid_links ?: BIT(0);
+ 	}
+ 
++	mlo_dbg(mt7996_hw_phy(hw), "cipher = 0x%x, icv_len = %u, iv_len = %u, hw_key_idx = %u, keyidx = %d, flags = 0x%x, link_id = %d, keylen = %u\n",
++		     key->cipher, key->icv_len, key->iv_len, key->hw_key_idx, key->keyidx, key->flags, key->link_id, key->keylen);
++	// print_hex_dump(KERN_INFO , "", DUMP_PREFIX_OFFSET, 16, 1, key->key, key->keylen, false);
++	mlo_dbg(mt7996_hw_phy(hw), "add=%lx, valid_links=%x, active_links=%x\n", add, vif->valid_links, vif->active_links);
++
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+ 	for_each_set_bit(link_id, &add, IEEE80211_MLD_MAX_NUM_LINKS) {
+@@ -1103,6 +1111,8 @@ static int mt7996_add_link_sta(struct mt7996_dev *dev,
+ 			mt76_wcid_mask_set(dev->mt76.wcid_phy_mask, idx);
+ 		rcu_assign_pointer(dev->mt76.wcid[idx], &mlink->wcid);
+ 		mt76_wcid_init(&mlink->wcid);
++
++		mlo_dbg(mconf->phy, "wcid=%u, link_id=%u, link_addr=%pM, pri_link=%u, sec_link=%u\n", mlink->wcid.idx, link_id, link_sta->addr, msta->pri_link, msta->sec_link);
+ 	}
+ 
+ 	if (!assoc)
+@@ -1137,6 +1147,7 @@ mt7996_mac_sta_remove_links(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+ 	unsigned int link_id;
+ 
++	mlo_dbg(mt7996_hw_phy(mvif->hw), "rem=%lu\n", rem);
+ 	for_each_set_bit(link_id, &rem, IEEE80211_MLD_MAX_NUM_LINKS) {
+ 		struct mt7996_bss_conf *mconf =
+ 			mconf_dereference_protected(mvif, link_id);
+@@ -1162,6 +1173,7 @@ mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ 	unsigned int link_id;
+ 	int i, ret;
+ 
++	mlo_dbg(mt7996_hw_phy(mvif->hw), "add=%lu, assoc=%d\n", add, assoc);
+ 	for_each_set_bit(link_id, &add, IEEE80211_MLD_MAX_NUM_LINKS) {
+ 		struct mt7996_bss_conf *mconf =
+ 			mconf_dereference_protected(mvif, link_id);
+@@ -2460,6 +2472,7 @@ mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	unsigned int link_id;
+ 	int ret = 0;
+ 
++	mlo_dbg(phy, "old=%u, new=%u\n", old_links, new_links);
+ 	if (old_links == new_links)
+ 		return 0;
+ 
+@@ -2508,6 +2521,7 @@ mt7996_change_sta_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	unsigned long rem = old_links & ~new_links;
+ 	int ret = 0;
+ 
++	mlo_dbg(mt7996_hw_phy(hw), "old=%u, new=%u\n", old_links, new_links);
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+ 	if (rem)
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 3e7d48ee8..e4d840599 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -1062,6 +1062,8 @@ mt7996_mcu_bss_mld_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
+ 	}
+ 
+ 	mld->own_mld_id = mconf->own_mld_id;
++	pr_info("%s: group_mld_id=%d own_mld_id=%d remap_idx=%d mld->addr[%pM]\n",
++		__func__, mld->group_mld_id,  mld->own_mld_id, mld->remap_idx, mld->mac_addr);
+ }
+ 
+ static void
+@@ -1213,6 +1215,10 @@ mt7996_mcu_bss_basic_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *conf,
+ 	}
+ 
+ 	memcpy(bss->bssid, conf->bssid, ETH_ALEN);
++
++	mlo_dbg(mconf->phy, "omac_idx=%d band_idx=%d wmm_idx=%d bss->bssid=%pM enable=%d\n",
++		bss->omac_idx, bss->band_idx, bss->wmm_idx, bss->bssid, enable);
++
+ 	bss->bcn_interval = cpu_to_le16(conf->beacon_int);
+ 	bss->dtim_period = conf->dtim_period;
+ 	bss->phymode = mt76_connac_get_phy_mode(phy, vif,
+@@ -2408,6 +2414,8 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_bss_conf *conf,
+ 
+ 	/* starec basic */
+ 	mt76_connac_mcu_sta_basic_tlv(&dev->mt76, skb, conf, link_sta, enable, newly);
++	mlo_dbg(mconf->phy, "link=%u, newly=%d, en=%d\n",
++		mlink->wcid.link_id, newly, enable);
+ 
+ 	if (!enable)
+ 		goto out;
+@@ -2488,12 +2496,16 @@ mt7996_mcu_sta_mld_setup_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
+ 	mld_setup->link_num = hweight16(sta->valid_links);
+ 
+ 	mld_setup_link = (struct mld_setup_link *)mld_setup->link_info;
++	mlo_dbg(mt7996_hw_phy(mlink->sta->vif->hw), "pri_link(%u) primary_id(%d) seconed_id(%d) wcid(%d), link_num(%d), mld_addr[%pM]\n",
++		msta->pri_link, mld_setup->primary_id, mld_setup->seconed_id, mld_setup->setup_wcid, mld_setup->link_num, mld_setup->mld_addr);
+ 	for_each_set_bit(link_id, &valid_links, IEEE80211_MLD_MAX_NUM_LINKS) {
+ 		mlink = mlink_dereference_protected(msta, link_id);
+ 		mconf = mconf_dereference_protected(msta->vif, link_id);
+ 
+ 		mld_setup_link->wcid = cpu_to_le16(mlink->wcid.idx);
+ 		mld_setup_link->bss_idx = mconf->mt76.idx;
++		mlo_dbg(mt7996_hw_phy(mlink->sta->vif->hw), "link_id(%d) wcid(%d) bss_idx(%d)\n",
++		link_id, mld_setup_link->wcid, mld_setup_link->bss_idx);
+ 		mld_setup_link++;
+ 	}
+ }
+@@ -2757,6 +2769,8 @@ int mt7996_mcu_add_dev_info(struct mt7996_phy *phy,
+ 		return mt7996_mcu_muar_config(phy, conf, mconf, false, enable);
+ 
+ 	memcpy(data.tlv.omac_addr, conf->addr, ETH_ALEN);
++	mlo_dbg(phy, "omac=%u, band=%u, addr=%pM, en=%d\n",
++		data.hdr.omac_idx,data.hdr.band_idx, data.tlv.omac_addr, enable);
+ 	return mt76_mcu_send_msg(&dev->mt76, MCU_WMWA_UNI_CMD(DEV_INFO_UPDATE),
+ 				 &data, sizeof(data), true);
+ }
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 691d2bc83..20a2aeb89 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -864,6 +864,8 @@ mt7996_get_link_wcid(struct mt7996_dev *dev, u16 idx, u8 band_idx)
+ 	return &mlink->wcid;
+ }
+ 
++#define mlo_dbg(phy, fmt, ...)   wiphy_info(phy->mt76->hw->wiphy, "%s: " fmt, __func__, ##__VA_ARGS__)
++
+ extern const struct ieee80211_ops mt7996_ops;
+ extern struct pci_driver mt7996_pci_driver;
+ extern struct pci_driver mt7996_hif_driver;
+-- 
+2.39.2
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0120-mtk-wifi-mt76-mt7996-support-band_idx-option-for-set.patch b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0120-mtk-wifi-mt76-mt7996-support-band_idx-option-for-set.patch
new file mode 100644
index 0000000..6cde16b
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211_mlo/package/kernel/mt76/patches/0120-mtk-wifi-mt76-mt7996-support-band_idx-option-for-set.patch
@@ -0,0 +1,180 @@
+From 6b6bfdf1f132cdd665b88613ed714b93604a03a0 Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Mon, 4 Mar 2024 16:21:16 +0800
+Subject: [PATCH 120/120] mtk: wifi: mt76: mt7996: support band_idx option for
+ set_mu/get_mu vendor command
+
+The vendor command set_mu and get_mu should be executed with band_idx.
+With band_idx, driver can access the corrsponding phy by band_idx.
+
+CR-Id: WCNCR00240772
+Change-Id: Id33d5efd3752e767fc11e852836d9939e4d6a088
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+---
+ mt7996/mcu.c    | 26 +++++++++++++++++++-------
+ mt7996/mcu.h    |  1 +
+ mt7996/vendor.c | 40 +++++++++++++++++++++++++++++++++++-----
+ mt7996/vendor.h |  1 +
+ 4 files changed, 56 insertions(+), 12 deletions(-)
+
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index e4d840599..fee2d35c4 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -5829,12 +5829,23 @@ int mt7996_mcu_set_vow_feature_ctrl(struct mt7996_phy *phy)
+ #ifdef CONFIG_MTK_VENDOR
+ void mt7996_set_wireless_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
+ {
+-	u8 mode, val;
++	u8 mode, val, band_idx;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_phy *phy =  mvif->deflink.phy;
++	struct mt7996_phy *phy;
++	struct mt76_phy *mphy;
+ 
+ 	mode = FIELD_GET(RATE_CFG_MODE, *((u32 *)data));
+ 	val = FIELD_GET(RATE_CFG_VAL, *((u32 *)data));
++	band_idx = FIELD_GET(RATE_CFG_BAND_IDX, *((u32 *)data));
++
++	if (!mt7996_band_valid(mvif->dev, band_idx))
++		goto error;
++
++	mphy = mvif->dev->mt76.phys[band_idx];
++	if (!mphy)
++		goto error;
++
++	phy = (struct mt7996_phy *)mphy->priv;
+ 
+ 	switch (mode) {
+ 	case RATE_PARAM_FIXED_OFDMA:
+@@ -5850,13 +5861,14 @@ void mt7996_set_wireless_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
+ 			phy->muru_onoff = MUMIMO_UL;
+ 		break;
+ 	case RATE_PARAM_AUTO_MU:
+-		if (val < 0 || val > 15) {
+-			printk("Wrong value! The value is between 0-15.\n");
+-			break;
+-		}
+-		phy->muru_onoff = val;
++		phy->muru_onoff = val & GENMASK(3, 0);
+ 		break;
+ 	}
++
++	return;
++error:
++	dev_err(mvif->dev->mt76.dev, "Invalid band_idx to config\n");
++	return;
+ }
+ 
+ void mt7996_set_beacon_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 7d200a334..016e6764e 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -887,6 +887,7 @@ enum {
+ #endif
+ };
+ 
++#define RATE_CFG_BAND_IDX	GENMASK(17, 16)
+ #define RATE_CFG_MODE	GENMASK(15, 8)
+ #define RATE_CFG_VAL	GENMASK(7, 0)
+ 
+diff --git a/mt7996/vendor.c b/mt7996/vendor.c
+index c87cc5c1d..b46fd186e 100644
+--- a/mt7996/vendor.c
++++ b/mt7996/vendor.c
+@@ -16,6 +16,7 @@ mu_ctrl_policy[NUM_MTK_VENDOR_ATTRS_MU_CTRL] = {
+ 	[MTK_VENDOR_ATTR_MU_CTRL_ONOFF] = {.type = NLA_U8 },
+ 	[MTK_VENDOR_ATTR_MU_CTRL_DUMP] = {.type = NLA_U8 },
+ 	[MTK_VENDOR_ATTR_MU_CTRL_STRUCT] = {.type = NLA_BINARY },
++	[MTK_VENDOR_ATTR_MU_CTRL_BAND_IDX] = {.type = NLA_U8 },
+ };
+ 
+ static const struct nla_policy
+@@ -134,7 +135,7 @@ static int mt7996_vendor_mu_ctrl(struct wiphy *wiphy,
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+ 	struct mt7996_muru *muru;
+ 	int err;
+-	u8 val8;
++	u8 val8, band_idx;
+ 	u32 val32 = 0;
+ 
+ 	err = nla_parse(tb, MTK_VENDOR_ATTR_MU_CTRL_MAX, data, data_len,
+@@ -142,10 +143,13 @@ static int mt7996_vendor_mu_ctrl(struct wiphy *wiphy,
+ 	if (err)
+ 		return err;
+ 
+-	if (tb[MTK_VENDOR_ATTR_MU_CTRL_ONOFF]) {
++	if (tb[MTK_VENDOR_ATTR_MU_CTRL_ONOFF] &&
++	    tb[MTK_VENDOR_ATTR_MU_CTRL_BAND_IDX]) {
+ 		val8 = nla_get_u8(tb[MTK_VENDOR_ATTR_MU_CTRL_ONOFF]);
++		band_idx = nla_get_u8(tb[MTK_VENDOR_ATTR_MU_CTRL_BAND_IDX]);
+ 		val32 |= FIELD_PREP(RATE_CFG_MODE, RATE_PARAM_AUTO_MU) |
+-			 FIELD_PREP(RATE_CFG_VAL, val8);
++			 FIELD_PREP(RATE_CFG_VAL, val8) |
++			 FIELD_PREP(RATE_CFG_BAND_IDX, band_idx);
+ 		ieee80211_iterate_active_interfaces_atomic(hw, IEEE80211_IFACE_ITER_RESUME_ALL,
+ 							   mt7996_set_wireless_vif, &val32);
+ 	} else if (tb[MTK_VENDOR_ATTR_MU_CTRL_STRUCT]) {
+@@ -167,18 +171,44 @@ mt7996_vendor_mu_ctrl_dump(struct wiphy *wiphy, struct wireless_dev *wdev,
+ 			   unsigned long *storage)
+ {
+ 	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
+-	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+-	int len = 0;
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++	struct mt7996_phy *phy;
++	struct mt76_phy *mphy;
++	struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_MU_CTRL];
++	int len = 0, err;
++	u8 band_idx;
+ 
+ 	if (*storage == 1)
+ 		return -ENOENT;
+ 	*storage = 1;
+ 
++	err = nla_parse(tb, MTK_VENDOR_ATTR_MU_CTRL_MAX, data, data_len,
++			mu_ctrl_policy, NULL);
++	if (err)
++		return err;
++
++	if (!tb[MTK_VENDOR_ATTR_MU_CTRL_BAND_IDX])
++		return -EINVAL;
++
++	band_idx = nla_get_u8(tb[MTK_VENDOR_ATTR_MU_CTRL_BAND_IDX]);
++	if (!mt7996_band_valid(dev, band_idx))
++		goto error;
++
++	mphy = dev->mt76.phys[band_idx];
++	if (!mphy)
++		goto error;
++
++	phy = (struct mt7996_phy *)mphy->priv;
++
+ 	if (nla_put_u8(skb, MTK_VENDOR_ATTR_MU_CTRL_DUMP, phy->muru_onoff))
+ 		return -ENOMEM;
+ 	len += 1;
+ 
+ 	return len;
++
++error:
++	dev_err(dev->mt76.dev, "Invalid band idx to dump\n");
++	return -EINVAL;
+ }
+ 
+ void mt7996_set_wireless_rts_sigta(struct ieee80211_hw *hw, u8 value) {
+diff --git a/mt7996/vendor.h b/mt7996/vendor.h
+index e7d88828c..cb250ceff 100644
+--- a/mt7996/vendor.h
++++ b/mt7996/vendor.h
+@@ -68,6 +68,7 @@ enum mtk_vendor_attr_mu_ctrl {
+ 	MTK_VENDOR_ATTR_MU_CTRL_ONOFF,
+ 	MTK_VENDOR_ATTR_MU_CTRL_DUMP,
+ 	MTK_VENDOR_ATTR_MU_CTRL_STRUCT,
++	MTK_VENDOR_ATTR_MU_CTRL_BAND_IDX,
+ 
+ 	/* keep last */
+ 	NUM_MTK_VENDOR_ATTRS_MU_CTRL,
+-- 
+2.39.2
+