Linux-mediatek Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Eason Lai <eason.lai@mediatek.com>
To: <nbd@nbd.name>, <lorenzo@kernel.org>
Cc: <linux-wireless@vger.kernel.org>,
	<linux-mediatek@lists.infradead.org>, <kun.wu@mediatek.com>,
	<deren.wu@mediatek.com>, <sean.wang@mediatek.com>,
	<quan.zhou@mediatek.com>, <ryder.lee@mediatek.com>,
	<leon.yen@mediatek.com>, <litien.chang@mediatek.com>,
	<jb.tsai@mediatek.com>, <michael.shih@mediatek.com>,
	<eason.lai@mediatek.com>, kernel test robot <lkp@intel.com>
Subject: [PATCH v2] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
Date: Thu, 27 Aug 2026 13:54:56 +0800	[thread overview]
Message-ID: <20260827055456.1550357-1-eason.lai@mediatek.com> (raw)
In-Reply-To: <20260824030746.60706-1-eason.lai@mediatek.com>

From: Michael Shih <michael.shih@mediatek.com>

Usage:
(1) Connect STA to AP  (You may use dhclient CMD to ask for DHCP IP, to use ping to AP)
    DHCP IP CMD:  dhclient ${WLAN_IF} -v

   Example: dhclient wlan0 -v

   ## (To simulate traffic) Ping 10 packets for every second to AP
   ping 192.168.0.XXX  -i0.1

(2) (Get stats) Use command, phyX is your WiFi interface:
     cat /sys/kernel/debug/ieee80211/phyX/mt76/stats

(3) You will see below stats info:
(STA) connected AP MAC Address = a8:42:a1:46:c4:6b
                        BssIdx = [0]
                        StaRecIdx = [1],
                        RSSI = -25
                        Link_Speed = 2402000 (kbit/s)
                        temperature = 40
                        Tx success = 716
                        Tx fail to Rcv ACK after retry = 0
                        Rx Mpdu = 23
                        Rx Fcs Error = 1 (MPDU)
                        Rx FIFO full = 0 (MPDU)
                        TRX_AGG_RANGE[0] = 0 (PPDU)
                        TRX_AGG_RANGE[1] = 0 (PPDU)
                        TRX_AGG_RANGE[2] = 0 (PPDU)
                        TRX_AGG_RANGE[3] = 0 (PPDU)
                        TRX_AGG_RANGE[4] = 0 (PPDU)
                        TRX_AGG_RANGE[5] = 0 (PPDU)
                        TRX_AGG_RANGE[6] = 0 (PPDU)
                        TRX_AGG_RANGE[7] = 0 (PPDU)
                        MPDUs in AMPDUs transmitted = 0 (MPDU)
                        MPDUs in AMPDUs transmitted with ACK reply = 0 (MPDU)
                        rate1_tx_cnt = 714
                        rate1_fail_cnt = 223
                        train_up = 2310
                        train_down = 3080
                        is_force_tx_stream = 0
                        is_force_se_off = 0
                        max_ampdu_factor = 9
                        tx_rate_up_penalty = 0
                        low_traffic_mode = 2
                        low_traffic_count = 0
                        low_traffic_dashboard = 3
                        dynamic_sgi_state = 0
                        dynamic_sgi_score = 2
                        dynamic_bw_state = 0
                        dynamic_gband_256qam_state = 3
                        vht_non_sp_rate_state = 0
                        Last TX Rate = 24M, BW20, OFDM, BCC, SPE24
                        Chip Out TX Power = +16.5 dBm
                        Cur Rate Index = 0
Rate index[0]    --> NSS2_MCS11, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[1]        NSS2_MCS9, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[2]        NSS2_MCS7, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[3]        NSS2_MCS4, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[4]        NSS2_MCS3, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[5]        NSS1_MCS2, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[6]        NSS1_MCS1, BW80, SGI, HE_SU, LDPC
Rate index[7]        NSS1_MCS0, BW80, SGI, HE_SU, LDPC

mib state:
=== Rx Related Counters ===
                        Rx with CRC = 24020 (MPDU)
                        Rx drop due to out of resource = 0 (MPDU)
                        Rx Mpdu = 93223 (MPDU)
                        Rx AMpdu = 668 (PPDU)
                        Rx PF Drop = 0 (MPDU)
                        Rx Len Mismatch = 5853 (PPDU)

=== Phy/Timing Related Counters ===
                        ChannelIdleCnt = 1040434
                        CCA_NAV_Tx_Time = 18510036
                        Rx_MDRDY_CNT = 166580 (PPDU)
                        CCK_MDRDY = 7782
                        OFDM_MDRDY = 11912731
                        OFDM_GREEN_MDRDY = 0
                        Prim CCA Time = 17917650
                        Sec CCA Time = 14922034
                        Prim ED Time = 5677528

=== Tx Related Counters(Generic) ===
                        BeaconTxCnt = 0
                        Tx 40MHz Cnt = 279  (MPDU)
                        Tx 80MHz Cnt = 44  (MPDU)
                        Tx 160MHz Cnt = 712  (MPDU)

=== BSSID[0] Related Counters ===
                        BA Miss Cnt = 210  (PPDU)
                        RTS Tx Cnt = 1003  (MPDU)
                        Frame Retry Cnt = 239 (MPDU)
                        Frame Retry 2 Cnt = 71 (MPDU)
                        RTS Retry Cnt = 152 (MPDU)
                        Ack Failed Cnt = 319 (PPDU)

=== AMPDU Related Counters ===
                        Tx AMPDU_Pkt_Cnt = 705  (PPDU)
                        Tx AMPDU_MPDU_Pkt_Cnt = 713  (MPDU)
                        AMPDU Tx success = 503 (MPDU)
                        AMPDU Tx fail count   = 210  (MPDU), PER=29.4%



Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202608251101.iQGUrSd0-lkp@intel.com/
Closes: https://lore.kernel.org/oe-kbuild-all/202608251526.VgKMT8Lv-lkp@intel.com/
Signed-off-by: Michael Shih <michael.shih@mediatek.com>
---
v2: (1) Remove redundant code to fix build errors. 
    (2) Move UNI_CMD_MIB_DATA enum definition from 'mt76/mt76_connac_mcu.h' to
        'mt7925/stats.h' to avoid redeclartion with 'mt76/mt7996/mcu.h'
---
 .../wireless/mediatek/mt76/mt76_connac_mcu.h  | 173 ++++++
 .../wireless/mediatek/mt76/mt7925/Makefile    |   2 +-
 .../wireless/mediatek/mt76/mt7925/debugfs.c   | 343 ++++++++++
 .../net/wireless/mediatek/mt76/mt7925/stats.c | 585 ++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7925/stats.h | 309 +++++++++
 5 files changed, 1411 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/stats.c
 create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/stats.h

diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index 0ededa569e9a..bf0f105c786e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -1483,6 +1483,179 @@ enum {
 	MT_NIC_CAP_EML_CAP = 0x22,
 };
 
+enum {
+	UNI_CMD_ID_STATISTICS_BASIC = 0,
+	UNI_CMD_ID_STATISTICS_LINK_QUALITY = 1,
+	UNI_CMD_ID_STATISTICS_STA = 2,
+	UNI_CMD_ID_STATISTICS_BUG_REPORT = 3,
+	UNI_CMD_ID_STATISTICS_TX_STATS = 4,
+	UNI_CMD_ID_STATISTICS_ALL_STATS = 5,
+	UNI_CMD_ID_STATISTICS_EML_STATS = 6,
+	UNI_CMD_ID_STATISTICS_REGULAR_STATS = 7,
+	UNI_CMD_ID_STATISTICS_BSS_LINK_QUALITY = 8,
+	UNI_CMD_ID_STATISTICS_ML_CHNL_COND = 9,
+	UNI_CMD_ID_STATISTICS_WTBL = 10,
+	UNI_CMD_ID_STATISTICS_LINK_LAYER_STATS = 128,
+	UNI_CMD_ID_STATISTICS_PPDU_LATENCY = 129,
+	UNI_CMD_ID_STATISTICS_CURRENT_TX_RATE = 130,
+	UNI_CMD_ID_STATISTICS_SET_TX_BITRATE_MONITOR = 131,
+	UNI_CMD_ID_STATISTICS_GET_PREDICT_TX_BITRATE = 132,
+	UNI_CMD_ID_STATISTICS_SET_ATPS_TYPE_RATE_MODE = 133,
+	UNI_CMD_ID_STATISTICS_SET_BEACON_REPORT = 134,
+	UNI_CMD_ID_STATISTICS_BSS_CURRENT_TX_RATE = 135,
+	UNI_CMD_ID_STATISTICS_GET_BSS_PREDICT_TX_BITRATE = 136,
+	UNI_CMD_ID_STATISTICS_MAX_NUM
+};
+
+enum mt7925_mib_counter {
+	UNI_CMD_MIB_CNT_RX_FCS_ERR = 0,
+	UNI_CMD_MIB_CNT_RX_FIFO_OVERFLOW = 1,
+	UNI_CMD_MIB_CNT_RX_MPDU = 2,
+	UNI_CMD_MIB_CNT_AMPDU_RX_COUNT = 3,
+	UNI_CMD_MIB_CNT_RX_TOTAL_BYTE = 4,
+	UNI_CMD_MIB_CNT_RX_VALID_AMPDU_SF = 5,
+	UNI_CMD_MIB_CNT_RX_VALID_BYTE = 6,
+	UNI_CMD_MIB_CNT_CHANNEL_IDLE = 7,
+	UNI_CMD_MIB_CNT_VEC_DROP = 8,
+	UNI_CMD_MIB_CNT_DELIMITER_FAIL = 9,
+	UNI_CMD_MIB_CNT_VEC_MISMATCH = 10,
+	UNI_CMD_MIB_CNT_MDRDY = 11,
+	UNI_CMD_MIB_CNT_RX_CCK_MDRDY_TIME = 12,
+	UNI_CMD_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME = 13,
+	UNI_CMD_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME = 14,
+	UNI_CMD_MIB_CNT_PF_DROP = 15,
+	UNI_CMD_MIB_CNT_LEN_MISMATCH = 16,
+	UNI_CMD_MIB_CNT_P_CCA_TIME = 17,
+	UNI_CMD_MIB_CNT_S_CCA_TIME = 18,
+	UNI_CMD_MIB_CNT_CCA_NAV_TX_TIME = 19,
+	UNI_CMD_MIB_CNT_P_ED_TIME = 20,
+	UNI_CMD_MIB_CNT_BCN_TX = 21,
+	UNI_CMD_MIB_CNT_TX_BW_20MHZ = 22,
+	UNI_CMD_MIB_CNT_TX_BW_40MHZ = 23,
+	UNI_CMD_MIB_CNT_TX_BW_80MHZ = 24,
+	UNI_CMD_MIB_CNT_TX_BW_160MHZ = 25,
+	UNI_CMD_RMAC_CNT_OBSS_AIRTIME = 26,
+	UNI_CMD_RMAC_CNT_NONWIFI_AIRTIME = 27,
+	UNI_CMD_MIB_CNT_TX_DUR_CNT = 28,
+	UNI_CMD_MIB_CNT_RX_DUR_CNT = 29,
+	UNI_CMD_MIB_CNT_BA_CNT = 30,
+	UNI_CMD_MIB_CNT_MAC2PHY_TX_TIME = 31,
+	UNI_CMD_MIB_CNT_RX_OUT_OF_RANGE_COUNT = 32,
+	UNI_CMD_MIB_CNT_IBF_TX = 33,
+	UNI_CMD_MIB_CNT_EBF_TX = 34,
+	UNI_CMD_MIB_CNT_MUBF_TX = 35,
+	UNI_CMD_MIB_CNT_RX_BF_VHTFBK = 36,
+	UNI_CMD_MIB_CNT_RX_BF_HTFBK = 37,
+	UNI_CMD_MIB_CNT_RX_BF_HEFBK = 38,
+	UNI_CMD_MIB_CNT_BFEE_TXFBK_BFPOLL_TRI = 39,
+	UNI_CMD_MIB_CNT_BFEE_TXFBK_TRI = 40,
+	UNI_CMD_MIB_CNT_BFEE_RX_FBKCQI = 41,
+	UNI_CMD_MIB_CNT_BFEE_RX_NDP = 42,
+	UNI_CMD_MIB_CNT_RX_BF_IBF_UPT = 43,
+	UNI_CMD_MIB_CNT_RX_BF_EBF_UPT = 44,
+	UNI_CMD_MIB_CNT_BFEE_SP_ABORT = 45,
+	UNI_CMD_MIB_CNT_BFEE_TB_LEN_ERR = 46,
+	UNI_CMD_MIB_CNT_BFEE_TXFBK_MUTE = 47,
+	UNI_CMD_MIB_CNT_BFEE_TMAC_ABORT = 48,
+	UNI_CMD_MIB_CNT_BFEE_TXFBK_CPL = 49,
+	UNI_CMD_MIB_CNT_BFEE_COANT_BLKTX = 50,
+	UNI_CMD_MIB_CNT_BFEE_FBK_SEG = 51,
+	UNI_CMD_MIB_CNT_NAV_TIME = 52,
+	UNI_CMD_MIB_CNT_TX_ABORT_CNT1 = 53,
+	UNI_CMD_MIB_CNT_TX_ABORT_CNT0 = 54,
+	UNI_CMD_MIB_CNT_TX_ABORT_CNT3 = 55,
+	UNI_CMD_MIB_CNT_TX_ABORT_CNT2 = 56,
+	UNI_CMD_MIB_CNT_BSS0_RTS_TX_CNT = 57,
+	UNI_CMD_MIB_CNT_BSS1_RTS_TX_CNT = 58,
+	UNI_CMD_MIB_CNT_BSS2_RTS_TX_CNT = 59,
+	UNI_CMD_MIB_CNT_BSS3_RTS_TX_CNT = 60,
+	UNI_CMD_MIB_CNT_BSS0_RTS_RETRY = 61,
+	UNI_CMD_MIB_CNT_BSS1_RTS_RETRY = 62,
+	UNI_CMD_MIB_CNT_BSS2_RTS_RETRY = 63,
+	UNI_CMD_MIB_CNT_BSS3_RTS_RETRY = 64,
+	UNI_CMD_MIB_CNT_BSS0_BA_MISS = 65,
+	UNI_CMD_MIB_CNT_BSS1_BA_MISS = 66,
+	UNI_CMD_MIB_CNT_BSS2_BA_MISS = 67,
+	UNI_CMD_MIB_CNT_BSS3_BA_MISS = 68,
+	UNI_CMD_MIB_CNT_BSS0_ACK_FAIL = 69,
+	UNI_CMD_MIB_CNT_BSS1_ACK_FAIL = 70,
+	UNI_CMD_MIB_CNT_BSS2_ACK_FAIL = 71,
+	UNI_CMD_MIB_CNT_BSS3_ACK_FAIL = 72,
+	UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY = 73,
+	UNI_CMD_MIB_CNT_BSS1_FRAME_RETRY = 74,
+	UNI_CMD_MIB_CNT_BSS2_FRAME_RETRY = 75,
+	UNI_CMD_MIB_CNT_BSS3_FRAME_RETRY = 76,
+	UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_2 = 77,
+	UNI_CMD_MIB_CNT_BSS1_FRAME_RETRY_2 = 78,
+	UNI_CMD_MIB_CNT_BSS2_FRAME_RETRY_2 = 79,
+	UNI_CMD_MIB_CNT_BSS3_FRAME_RETRY_2 = 80,
+	UNI_CMD_MIB_CNT_RX_A1_SEARCH = 81,
+	UNI_CMD_MIB_CNT_RX_DROP_MPDU = 82,
+	UNI_CMD_MIB_CNT_RX_UNWANTED = 83,
+	UNI_CMD_MIB_CNT_RX_FCS_OK = 84,
+	UNI_CMD_MIB_CNT_SU_TX_OK = 85,
+	UNI_CMD_MIB_CNT_TX_FULL_BW = 86,
+	UNI_CMD_MIB_CNT_TX_AUTO_BW = 87,
+	UNI_CMD_MIB_CNT_CCA_SAMPLE_IDLE = 88,
+	UNI_CMD_MIB_CNT_CCA_SAMPLE_ACTIVE = 89,
+	UNI_CMD_MIB_CNT_CCA_TIME = 90,
+	UNI_CMD_MIB_CNT_S_20BW_CCA_TIME = 91,
+	UNI_CMD_MIB_CNT_S_40BW_CCA_TIME = 92,
+	UNI_CMD_MIB_CNT_S_80BW_CCA_TIME = 93,
+	UNI_CMD_MIB_CNT_S_160BW_CCA_TIME = 94,
+	UNI_CMD_MIB_CNT_S_P20BW_0_ED_TIME = 95,
+	UNI_CMD_MIB_CNT_S_P20BW_1_ED_TIME = 96,
+	UNI_CMD_MIB_CNT_S_P20BW_2_ED_TIME = 97,
+	UNI_CMD_MIB_CNT_S_P20BW_3_ED_TIME = 98,
+	UNI_CMD_MIB_CNT_S_P20BW_4_ED_TIME = 99,
+	UNI_CMD_MIB_CNT_S_P20BW_5_ED_TIME = 100,
+	UNI_CMD_MIB_CNT_S_P20BW_6_ED_TIME = 101,
+	UNI_CMD_MIB_CNT_S_P20BW_7_ED_TIME = 102,
+	UNI_CMD_MIB_CNT_S_P20BW_8_ED_TIME = 103,
+	UNI_CMD_MIB_CNT_S_P20BW_9_ED_TIME = 104,
+	UNI_CMD_MIB_CNT_S_P20BW_10_ED_TIME = 105,
+	UNI_CMD_MIB_CNT_S_P20BW_11_ED_TIME = 106,
+	UNI_CMD_MIB_CNT_S_P20BW_12_ED_TIME = 107,
+	UNI_CMD_MIB_CNT_S_P20BW_13_ED_TIME = 108,
+	UNI_CMD_MIB_CNT_S_P20BW_14_ED_TIME = 109,
+	UNI_CMD_MIB_CNT_S_P20BW_15_ED_TIME = 110,
+	UNI_CMD_MIB_CNT_TX_BW_320MHZ = 111,
+	UNI_CMD_MIB_CNT_TX_DDLMT_RNG0 = 112, /* Rng0~Rng4 112~116 */
+	UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_3 = 117, /* 117~120 */
+	UNI_CMD_MIB_CNT_BSS0_TX = 121, /* 121~124 */
+	UNI_CMD_MIB_CNT_BSS0_TX_DATA = 125, /* 125~128 */
+	UNI_CMD_MIB_CNT_BSS0_TX_BYTE = 129, /* 129~132 */
+	UNI_CMD_MIB_CNT_RX_OK_BSS0 = 133, /* 133~136 */
+	UNI_CMD_MIB_CNT_RX_BYTE_BSS0 = 137, /* 137~140 */
+	UNI_CMD_MIB_CNT_RX_DATA_BSS0 = 141, /* 141~144 */
+	UNI_CMD_MIB_CNT_MBSS0_TX_OK = 145, /* 145~160 */
+	UNI_CMD_MIB_CNT_MBSS0_TX_BYTE = 161, /* 161~176 */
+	UNI_CMD_MIB_CNT_RX_OK_MBSS0 = 177, /* 177~192 */
+	UNI_CMD_MIB_CNT_RX_BYTE_MBSS0 = 193, /* 193~208 */
+	UNI_CMD_MIB_CNT_AMPDU = 209,
+	UNI_CMD_MIB_CNT_AMPDU_MPDU = 210,
+	UNI_CMD_MIB_CNT_AMPDU_ACKED = 211,
+	UNI_CMD_MIB_CNT_MAX_NUM
+};
+
+enum ENUM_UNI_CMD_GET_STATISTICS_TAG {
+	UNI_CMD_GET_STATISTICS_TAG_BASIC = 0,
+	UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1,
+	UNI_CMD_GET_STATISTICS_TAG_STA = 2,
+	UNI_CMD_GET_STATISTICS_TAG_BUG_REPORT = 3,
+	UNI_CMD_GET_STATISTICS_TAG_EML_STATS = 6,
+	UNI_CMD_GET_STATISTICS_TAG_REGULAR_STATS = 7,
+	UNI_CMD_GET_STATISTICS_TAG_BSS_LINK_QUALITY = 8,
+	UNI_CMD_GET_STATISTICS_TAG_GET_ML_CHNL_COND = 9,
+	UNI_CMD_GET_STATISTICS_TAG_STAT_WTBL = 10,
+	UNI_CMD_GET_STATISTICS_TAG_LINK_LAYER_STATS = 0x80,
+	UNI_CMD_GET_STATISTICS_TAG_PPDU_LATENCY,
+	UNI_CMD_GET_STATISTICS_TAG_CURRENT_TX_RATE,
+	UNI_CMD_GET_STATISTICS_TAG_BEACON_REPORT = 0x86,
+	UNI_CMD_GET_STATISTICS_TAG_BSS_CURRENT_TX_RATE = 0x87,
+	UNI_CMD_GET_STATISTICS_TAG_BSS_PRED_TX_BITRATE = 0x88,
+};
+
 #define UNI_WOW_DETECT_TYPE_MAGIC		BIT(0)
 #define UNI_WOW_DETECT_TYPE_ANY			BIT(1)
 #define UNI_WOW_DETECT_TYPE_DISCONNECT		BIT(2)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
index f9dcc0bba393..3863a89b8974 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
@@ -4,7 +4,7 @@ obj-$(CONFIG_MT7925_COMMON) += mt7925-common.o
 obj-$(CONFIG_MT7925E) += mt7925e.o
 obj-$(CONFIG_MT7925U) += mt7925u.o
 
-mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o
+mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o stats.o
 mt7925-common-$(CONFIG_NL80211_TESTMODE) += testmode.o
 mt7925e-y := pci.o pci_mac.o pci_mcu.o
 mt7925u-y := usb.o
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
index d01ff49de47a..d96a1979a196 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
@@ -3,6 +3,7 @@
 
 #include "mt7925.h"
 #include "mcu.h"
+#include "stats.h"
 
 static int
 mt7925_reg_set(void *data, u64 val)
@@ -286,6 +287,345 @@ static int mt7925_chip_reset(void *data, u64 val)
 
 DEFINE_DEBUGFS_ATTRIBUTE(fops_reset, NULL, mt7925_chip_reset, "%lld\n");
 
+static const u32 stats_query_mib_counters[] = {
+	UNI_CMD_MIB_CNT_RX_FCS_ERR,
+	UNI_CMD_MIB_CNT_RX_FIFO_OVERFLOW,
+	UNI_CMD_MIB_CNT_RX_MPDU,
+	UNI_CMD_MIB_CNT_AMPDU_RX_COUNT,
+	UNI_CMD_MIB_CNT_PF_DROP,
+	UNI_CMD_MIB_CNT_LEN_MISMATCH,
+	UNI_CMD_MIB_CNT_CHANNEL_IDLE,
+	UNI_CMD_MIB_CNT_CCA_NAV_TX_TIME,
+	UNI_CMD_MIB_CNT_MDRDY,
+	UNI_CMD_MIB_CNT_RX_CCK_MDRDY_TIME,
+	UNI_CMD_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME,
+	UNI_CMD_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME,
+	UNI_CMD_MIB_CNT_P_CCA_TIME,
+	UNI_CMD_MIB_CNT_S_CCA_TIME,
+	UNI_CMD_MIB_CNT_P_ED_TIME,
+	UNI_CMD_MIB_CNT_BCN_TX,
+	UNI_CMD_MIB_CNT_TX_BW_40MHZ,
+	UNI_CMD_MIB_CNT_TX_BW_80MHZ,
+	UNI_CMD_MIB_CNT_TX_BW_160MHZ,
+	UNI_CMD_MIB_CNT_BSS0_BA_MISS,
+	UNI_CMD_MIB_CNT_BSS0_RTS_TX_CNT,
+	UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY,
+	UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_2,
+	UNI_CMD_MIB_CNT_BSS0_RTS_RETRY,
+	UNI_CMD_MIB_CNT_BSS0_ACK_FAIL,
+	UNI_CMD_MIB_CNT_AMPDU,
+	UNI_CMD_MIB_CNT_AMPDU_MPDU,
+	UNI_CMD_MIB_CNT_AMPDU_ACKED,
+};
+
+/* Dump the whole firmware auto-rate table and mark the entry currently in use
+ * with "-->"
+ */
+static void
+mt7925_dump_auto_rate_table(struct seq_file *s,
+			    const struct mt7925_wtbl_rate *wr)
+{
+	u8 fcap = wr->fcap;
+	u8 rate_idx = wr->rate_idx;
+	u8 cbrn = wr->cbrn;
+	int i;
+
+	seq_printf(s, "%-22s  Cur Rate Index = %u\n", " ", rate_idx);
+
+	for (i = 0; i < MT7925_AUTO_RATE_NUM; i++) {
+		u16 rate_code = le16_to_cpu(wr->rate_code[i]);
+		u8 txmode = MT7925_HW_RATE_TO_MODE(rate_code);
+		u8 rate = MT7925_HW_RATE_TO_MCS(rate_code);
+		u8 nsts = MT7925_HW_RATE_TO_NSS(rate_code) + 1;
+		u8 stbc = MT7925_HW_RATE_TO_STBC(rate_code);
+		u8 dcm = MT7925_HW_RATE_TO_DCM(rate_code);
+		u8 ersu106t = MT7925_HW_RATE_TO_106T(rate_code);
+		u8 sgi = mt7925_wtbl_get_sgi(wr, txmode, fcap);
+		u8 ldpc = mt7925_wtbl_get_ldpc(wr, txmode);
+		const char *mode_str = "N/A";
+
+		if (dcm)
+			rate = MT7925_HW_RATE_UNMASK_DCM(rate);
+		if (ersu106t)
+			rate = MT7925_HW_RATE_UNMASK_106T(rate);
+
+		if (txmode < ARRAY_SIZE(mt7925_tx_mode_str))
+			mode_str = mt7925_tx_mode_str[txmode];
+
+		seq_printf(s, "Rate index[%d]    %s", i,
+			   rate_idx == i ? "--> " : "    ");
+
+		/* rate/MCS field */
+		if (txmode == MT7925_TX_RATE_MODE_CCK)
+			seq_printf(s, "%s, ",
+				   mt7925_tx_rate_cck_str[rate & 0x3]);
+		else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+			seq_printf(s, "%s, ", mt7925_hw_rate_ofdm_str(rate));
+		else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+			 txmode == MT7925_TX_RATE_MODE_HTGF)
+			seq_printf(s, "MCS%d, ", rate);
+		else
+			seq_printf(s, "%s%d_MCS%d, ",
+				   stbc ? "NSTS" : "NSS", nsts, rate);
+
+		/* bandwidth: mirror the CCK/OFDM -> BW20 and cbrn handling */
+		if (txmode == MT7925_TX_RATE_MODE_CCK ||
+		    txmode == MT7925_TX_RATE_MODE_OFDM)
+			seq_printf(s, "%s, ", mt7925_tx_rate_bw_str[0]);
+		else if (i > cbrn)
+			seq_printf(s, "%s, ",
+				   fcap < 5 ?
+				   (fcap > MT7925_BW_20 ?
+				    mt7925_tx_rate_bw_str[fcap - 1] :
+				    mt7925_tx_rate_bw_str[fcap]) :
+				   mt7925_tx_rate_bw_str[5]);
+		else
+			seq_printf(s, "%s, ",
+				   fcap < 5 ? mt7925_tx_rate_bw_str[fcap] :
+				   mt7925_tx_rate_bw_str[5]);
+
+		/* GI / preamble */
+		if (txmode == MT7925_TX_RATE_MODE_CCK)
+			seq_printf(s, "%s, ", rate < 4 ? "LP" : "SP");
+		else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+			; /* OFDM has no GI/preamble to print */
+		else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+			 txmode == MT7925_TX_RATE_MODE_HTGF ||
+			 txmode == MT7925_TX_RATE_MODE_VHT ||
+			 txmode == MT7925_TX_RATE_MODE_PLR)
+			seq_printf(s, "%s, ", sgi == 0 ? "LGI" : "SGI");
+		else
+			seq_printf(s, "%s, ",
+				   sgi == 0 ? "SGI" :
+				   (sgi == 1 ? "MGI" : "LGI"));
+
+		/* mode, DCM, 106t, STBC, coding */
+		seq_printf(s, "%s%s%s%s%s\n",
+			   mode_str,
+			   dcm ? ", DCM" : "", ersu106t ? ", 106t" : "",
+			   stbc ? ", STBC, " : ", ",
+			   (ldpc == 0 ||
+			    txmode == MT7925_TX_RATE_MODE_CCK ||
+			    txmode == MT7925_TX_RATE_MODE_OFDM) ?
+			   "BCC" : "LDPC");
+	}
+}
+
+static int mt792x_stats(struct seq_file *s, void *data)
+{
+	s8 rssi = 0;
+	u8 band_idx = 0;
+	bool wtbl_rate_valid = false;
+	int ret = 0;
+	int i = 0;
+	u32 stats_value = 0;
+	u32 tx_link_speed_kbps = 0;
+	u64 tx_total = 0;
+	u64 tx_fail = 0;
+	u64 tx_per = 0;
+	/* UNI_CMD_MIB_DATA */
+	u64 mib_values[ARRAY_SIZE(stats_query_mib_counters)];
+	struct mt792x_dev *dev = dev_get_drvdata(s->private);
+	struct mt7925_wtbl_rate wtbl_rate;
+	struct mt7925_sta_stats *stats = NULL;
+
+	band_idx = dev->mphy.band_idx;
+
+	 stats = kzalloc(sizeof(*stats), GFP_KERNEL);
+	if (!stats)
+		return -ENOMEM;
+
+	/* Get STA_STATS */
+	mt792x_mutex_acquire(dev);
+	ret = mt792x_mcu_get_stat(dev, stats);
+	mt792x_mutex_release(dev);
+	if (ret)
+		goto out;
+
+	/* Get MIB_STATS */
+	mt792x_mutex_acquire(dev);
+	ret = mt7925_mcu_get_mib_info(dev, band_idx, stats_query_mib_counters,
+				      ARRAY_SIZE(stats_query_mib_counters), mib_values);
+	mt792x_mutex_release(dev);
+	if (ret)
+		goto out;
+
+	/* Get Link_Speed and RSSI */
+	mt792x_mutex_acquire(dev);
+	ret = mt7925_mcu_get_link_quality(dev, stats->bss_idx, &rssi, &tx_link_speed_kbps);
+	mt792x_mutex_release(dev);
+	if (ret)
+		goto out;
+
+	/* Get the firmware auto-rate table (best effort; do not abort the
+	 * whole stats dump if the WTBL query is not available).
+	 */
+	mt792x_mutex_acquire(dev);
+	if (!mt792x_mcu_get_wtbl_rate(dev, stats->wtbl_idx, &wtbl_rate))
+		wtbl_rate_valid = true;
+	mt792x_mutex_release(dev);
+
+	/* Print STA_STATS info */
+	seq_printf(s, "(STA) connected AP MAC Address = %pM\n", stats->mac_addr);
+	seq_printf(s, "%-22s  BssIdx = [%u]\n", " ", stats->bss_idx);
+	seq_printf(s, "%-22s  StaRecIdx = [%u]\n",
+		   " ", stats->sta_idx);
+	seq_printf(s, "%-22s  RSSI = %d\n", " ", rssi);
+	seq_printf(s, "%-22s  Link_Speed = %u (kbit/s)\n", " ", tx_link_speed_kbps);
+
+	seq_printf(s, "%-22s  temperature = %u\n",
+		   " ", stats->temperature);
+
+	stats_value = le32_to_cpu(stats->transmit_count) - le32_to_cpu(stats->transmit_fail_count);
+	seq_printf(s, "%-22s  Tx success = %u\n",
+		   " ", stats_value);
+
+	stats_value = le32_to_cpu(stats->tx_fail_count) - le32_to_cpu(stats->tx_life_timeout_count);
+	seq_printf(s, "%-22s  Tx fail to Rcv ACK after retry = %u\n",
+		   " ", stats_value);
+
+	seq_printf(s, "%-22s  Rx Mpdu = %u\n",
+		   " ", le32_to_cpu(stats->mib[band_idx].rx_mpdu_cnt));
+
+	seq_printf(s, "%-22s  Rx Fcs Error = %u (MPDU)\n",
+		   " ", le32_to_cpu(stats->mib[band_idx].fcs_error));
+	seq_printf(s, "%-22s  Rx FIFO full = %u (MPDU)\n",
+		   " ", le32_to_cpu(stats->mib[band_idx].rx_fifo_full));
+
+	for (i = 0; i < STAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM; i++)
+		seq_printf(s, "%-22s  TRX_AGG_RANGE[%d] = %u (PPDU)\n",
+			   " ", i, le32_to_cpu(stats->mib[band_idx].tx_range_ampdu_cnt[i]));
+
+	seq_printf(s, "%-22s  MPDUs in AMPDUs transmitted = %u (MPDU)\n",
+		   " ", le32_to_cpu(stats->mib[band_idx].ampdu_tx_sf_cnt));
+	seq_printf(s, "%-22s  MPDUs in AMPDUs transmitted with ACK reply = %u (MPDU)\n",
+		   " ", le32_to_cpu(stats->mib[band_idx].ampdu_tx_ack_sf_cnt));
+	seq_printf(s, "%-22s  rate1_tx_cnt = %u\n",
+		   " ", le32_to_cpu(stats->rate1_tx_cnt));
+	seq_printf(s, "%-22s  rate1_fail_cnt = %u\n",
+		   " ", le32_to_cpu(stats->rate1_fail_cnt));
+	seq_printf(s, "%-22s  train_up = %u\n",
+		   " ", le16_to_cpu(stats->train_up));
+	seq_printf(s, "%-22s  train_down = %u\n",
+		   " ", le16_to_cpu(stats->train_down));
+	seq_printf(s, "%-22s  is_force_tx_stream = %u\n",
+		   " ", stats->is_force_tx_stream);
+	seq_printf(s, "%-22s  is_force_se_off = %u\n",
+		   " ", stats->is_force_se_off);
+	seq_printf(s, "%-22s  max_ampdu_factor = %u\n",
+		   " ", stats->max_ampdu_factor);
+	seq_printf(s, "%-22s  tx_rate_up_penalty = %u\n",
+		   " ", stats->tx_rate_up_penalty);
+	seq_printf(s, "%-22s  low_traffic_mode = %u\n",
+		   " ", stats->low_traffic_mode);
+	seq_printf(s, "%-22s  low_traffic_count = %u\n",
+		   " ", stats->low_traffic_count);
+	seq_printf(s, "%-22s  low_traffic_dashboard = %u\n",
+		   " ", stats->low_traffic_dashboard);
+	seq_printf(s, "%-22s  dynamic_sgi_state = %u\n",
+		   " ", stats->dynamic_sgi_state);
+	seq_printf(s, "%-22s  dynamic_sgi_score = %u\n",
+		   " ", stats->dynamic_sgi_score);
+	seq_printf(s, "%-22s  dynamic_bw_state = %u\n",
+		   " ", stats->dynamic_bw_state);
+	seq_printf(s, "%-22s  dynamic_gband_256qam_state = %u\n",
+		   " ", stats->dynamic_gband_256qam_state);
+	seq_printf(s, "%-22s  vht_non_sp_rate_state = %u\n",
+		   " ", stats->vht_non_sp_rate_state);
+
+	/* Decode the TX Vector BBP latch to show the current TX MCS rate */
+	if (band_idx < MT7925_STA_STATS_BAND_NUM)
+		mt7925_print_last_tx_rate(s, stats->tx_vector[band_idx].txv);
+
+	/* Dump the whole firmware auto-rate table and mark the entry currently */
+	if (wtbl_rate_valid)
+		mt7925_dump_auto_rate_table(s, &wtbl_rate);
+
+	seq_puts(s, "\nmib state:\n");
+	/* ===Rx Related Counters=== */
+	seq_puts(s, "=== Rx Related Counters ===\n");
+	seq_printf(s, "%-22s  Rx with CRC = %llu (MPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_RX_FCS_ERR]);
+	seq_printf(s, "%-22s  Rx drop due to out of resource = %llu (MPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_RX_FIFO_OVERFLOW]);
+	seq_printf(s, "%-22s  Rx Mpdu = %llu (MPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_RX_MPDU]);
+	seq_printf(s, "%-22s  Rx AMpdu = %llu (PPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_AMPDU_RX_COUNT]);
+	seq_printf(s, "%-22s  Rx PF Drop = %llu (MPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_PF_DROP]);
+	seq_printf(s, "%-22s  Rx Len Mismatch = %llu (PPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_LEN_MISMATCH]);
+
+	/* ===Phy/Timing Related Counters=== */
+	seq_puts(s, "\n=== Phy/Timing Related Counters ===\n");
+	seq_printf(s, "%-22s  ChannelIdleCnt = %llu\n",
+		   " ", mib_values[QUERY_MIB_CNT_CHANNEL_IDLE]);
+	seq_printf(s, "%-22s  CCA_NAV_Tx_Time = %llu\n",
+		   " ", mib_values[QUERY_MIB_CNT_CCA_NAV_TX_TIME]);
+	seq_printf(s, "%-22s  Rx_MDRDY_CNT = %llu (PPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_MDRDY]);
+	seq_printf(s, "%-22s  CCK_MDRDY = %llu\n",
+		   " ", mib_values[QUERY_MIB_CNT_RX_CCK_MDRDY_TIME]);
+	seq_printf(s, "%-22s  OFDM_MDRDY = %llu\n",
+		   " ", mib_values[QUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME]);
+	seq_printf(s, "%-22s  OFDM_GREEN_MDRDY = %llu\n",
+		   " ", mib_values[QUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME]);
+	seq_printf(s, "%-22s  Prim CCA Time = %llu\n",
+		   " ", mib_values[QUERY_MIB_CNT_P_CCA_TIME]);
+	seq_printf(s, "%-22s  Sec CCA Time = %llu\n",
+		   " ", mib_values[QUERY_MIB_CNT_S_CCA_TIME]);
+	seq_printf(s, "%-22s  Prim ED Time = %llu\n",
+		   " ", mib_values[QUERY_MIB_CNT_P_ED_TIME]);
+
+	/* ===Tx Related Counters(Generic)=== */
+	seq_puts(s, "\n=== Tx Related Counters(Generic) ===\n");
+	seq_printf(s, "%-22s  BeaconTxCnt = %llu\n",
+		   " ", mib_values[QUERY_MIB_CNT_BCN_TX]);
+	seq_printf(s, "%-22s  Tx 40MHz Cnt = %llu  (MPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_TX_BW_40MHZ]);
+	seq_printf(s, "%-22s  Tx 80MHz Cnt = %llu  (MPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_TX_BW_80MHZ]);
+	seq_printf(s, "%-22s  Tx 160MHz Cnt = %llu  (MPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_TX_BW_160MHZ]);
+
+	/* ===BSSID[0] Related Counters=== */
+	seq_puts(s, "\n=== BSSID[0] Related Counters ===\n");
+	seq_printf(s, "%-22s  BA Miss Cnt = %llu  (PPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_BSS0_BA_MISS]);
+	seq_printf(s, "%-22s  RTS Tx Cnt = %llu  (MPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_BSS0_RTS_TX_CNT]);
+	seq_printf(s, "%-22s  Frame Retry Cnt = %llu (MPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY]);
+	seq_printf(s, "%-22s  Frame Retry 2 Cnt = %llu (MPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY_2]);
+	seq_printf(s, "%-22s  RTS Retry Cnt = %llu (MPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_BSS0_RTS_RETRY]);
+	seq_printf(s, "%-22s  Ack Failed Cnt = %llu (PPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_BSS0_ACK_FAIL]);
+
+	/* ===AMPDU Related Counters=== */
+	seq_puts(s, "\n=== AMPDU Related Counters ===\n");
+	seq_printf(s, "%-22s  Tx AMPDU_Pkt_Cnt = %llu  (PPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_AMPDU]);
+	seq_printf(s, "%-22s  Tx AMPDU_MPDU_Pkt_Cnt = %llu  (MPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_AMPDU_MPDU]);
+	seq_printf(s, "%-22s  AMPDU Tx success = %llu (MPDU)\n",
+		   " ", mib_values[QUERY_MIB_CNT_AMPDU_ACKED]);
+
+	tx_total = mib_values[QUERY_MIB_CNT_AMPDU_MPDU];
+	tx_fail =  mib_values[QUERY_MIB_CNT_AMPDU_MPDU] - mib_values[QUERY_MIB_CNT_AMPDU_ACKED];
+	tx_per = tx_total == 0 ? 0 : (1000 * (tx_fail)) / tx_total;
+	seq_printf(s, "%-22s  AMPDU Tx fail count   = %llu  (MPDU), PER=%llu.%1llu%%\n",
+		   " ",
+		   tx_fail,
+		   tx_per / 10, tx_per % 10);
+
+out:
+	kfree(stats);
+	return ret;
+}
+
 int mt7925_init_debugfs(struct mt792x_dev *dev)
 {
 	struct dentry *dir;
@@ -313,5 +653,8 @@ int mt7925_init_debugfs(struct mt792x_dev *dev)
 				    mt792x_pm_stats);
 	debugfs_create_file("deep-sleep", 0600, dir, dev, &fops_ds);
 
+	debugfs_create_devm_seqfile(dev->mt76.dev, "stats", dir,
+				    mt792x_stats);
+
 	return 0;
 }
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.c b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
new file mode 100644
index 000000000000..9a47953062bc
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
@@ -0,0 +1,585 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/* Copyright (C) 2026 MediaTek Inc. */
+
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include "mt7925.h"
+#include "mcu.h"
+#include "mac.h"
+#include "stats.h"
+
+#define MT7925_STA_STATS_VERSION 1
+#define MT7925_STA_STATS_VALID 1U
+
+#define MT7925_MIB_MAX_CNT			32
+#define MT7925_MIB_CNT_MAX_NUM		512  /* firmware UNI_CMD_MIB_CNT_MAX_NUM upper bound */
+
+#define MT7925_LQ_BAND_NUM	4			/* Link quality band */
+
+const char * const mt7925_tx_mode_str[] = {
+	"CCK", "OFDM", "MM", "GF", "VHT", "PLR",
+	"PLRP", "ALR", "HE_SU", "HE_ER", "HE_TRIG", "HE_MU",
+	"EHT_ER", "TOF", "EHT_TRIG", "EHT_MU"
+};
+
+const char * const mt7925_tx_rate_cck_str[] = {
+	"1M", "2M", "5.5M", "11M", "N/A"
+};
+
+const char * const mt7925_tx_rate_bw_str[] = {
+	"BW20", "BW40", "BW80", "BW160/BW8080", "BW320", "N/A"
+};
+
+struct mt7925_sta_stats_req {
+	u8 rsv[4];
+	__le16 tag;
+	__le16 len;
+	u8 sta_rec_idx;
+	u8 rsv1;
+	u8 lls_read_clear;
+	u8 reset_counter;
+} __packed;
+
+struct mt7925_sta_stats_event {
+	u8 rsv[4];
+	__le16 tag;
+	__le16 len;
+	struct mt7925_sta_stats stats;
+} __packed;
+
+/* WTBL auto-rate request TLV, mirror Gen4m struct UNI_CMD_STAT_WTBL.
+ * The RA tag reuses UNI_CMD_ID_STATISTICS_WTBL (10).
+ */
+struct mt7925_wtbl_rate_req {
+	u8 rsv[4];
+	__le16 tag;
+	__le16 len;
+	__le16 wlan_idx;
+	__le16 rsv1;
+} __packed;
+
+/* WTBL auto-rate event TLV. Firmware answers cmd tag 10 with event tag 12. */
+struct mt7925_wtbl_rate_event {
+	u8 rsv[4];
+	struct mt7925_wtbl_rate rate;
+} __packed;
+
+/* fixed field, mirror UNI_(CMD|EVENT)_MIB_INFO */
+struct mt7925_mib_hdr {
+	u8 band_idx;
+	u8 rsv[3];
+} __packed;
+
+/* One requested/returned MIB counter, mirror UNI_(CMD|EVENT)_MIB_DATA_T. */
+struct mt7925_mib_data {
+	__le16 tag;	/* UNI_(CMD|EVENT)_MIB_DATA = 0 */
+	__le16 len;
+	__le32 counter;	/* MIB counter ID (enum mt7925_mib_counter) */
+	__le64 data;	/* only valid on event; ignored on cmd */
+} __packed;
+
+/* per-band link quality entry, mirror UNI_LINK_QUALITY */
+struct mt7925_link_quality {
+	s8 rssi;		/* moving-average beacon RSSI of connected AP */
+	s8 link_quality;	/* always 0 */
+	__le16 link_speed;	/* TX rate1, unit 500 Kb/s */
+	u8 medium_busy_pct;	/* always 0 */
+	u8 is_ready;		/* 0: fields invalid, 1: valid */
+	u8 rsv[2];
+} __packed;
+
+/* request TLV, mirror UNI_CMD_LINK_QUALITY (header + tag/len only) */
+struct mt7925_lq_req {
+	u8 rsv[4];		/* UNI_CMD_GET_STATISTICS fixed field */
+	__le16 tag;		/* UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1 */
+	__le16 len;
+} __packed;
+
+/* event TLV, mirror UNI_EVENT_LINK_QUALITY */
+struct mt7925_lq_event {
+	u8 rsv[4];		/* UNI_EVENT_STATISTICS fixed field */
+	__le16 tag;		/* UNI_EVENT_ID_STATISTICS_LINK_QUALITY = 1 */
+	__le16 len;
+	struct mt7925_link_quality lq[MT7925_LQ_BAND_NUM];
+} __packed;
+
+static void
+mt7925_get_ap_sta_rec_idx_iter(void *data, u8 *mac,
+			       struct ieee80211_vif *vif)
+{
+	struct mt7925_sta_rec_lookup *lookup = data;
+	struct mt792x_vif *mvif;
+	struct mt792x_sta *msta;
+
+	if (lookup->sta_rec_idx >= 0)
+		return;
+
+	if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc)
+		return;
+
+	mvif = (struct mt792x_vif *)vif->drv_priv;
+	msta = mvif->wep_sta;
+	if (!msta)
+		return;
+
+	if (!msta->deflink.wcid.sta)
+		return;
+
+	lookup->sta_rec_idx = msta->deflink.wcid.idx;
+}
+
+int
+mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx)
+{
+	struct mt7925_sta_rec_lookup lookup = {
+		.sta_rec_idx = -1,
+	};
+
+	lockdep_assert_held(&dev->mt76.mutex);
+
+	ieee80211_iterate_active_interfaces(dev->mphy.hw,
+					    IEEE80211_IFACE_ITER_RESUME_ALL,
+					    mt7925_get_ap_sta_rec_idx_iter,
+					    &lookup);
+
+	if (lookup.sta_rec_idx < 0)
+		return -ENOTCONN;
+
+	if (lookup.sta_rec_idx > U8_MAX)
+		return -ERANGE;
+
+	*sta_rec_idx = lookup.sta_rec_idx;
+
+	return 0;
+}
+
+/* Decode the TX Vector BBP latch and print the last TX rate */
+void
+mt7925_print_last_tx_rate(struct seq_file *s, const __le32 le_txv[3])
+{
+	const char *mode_str = "N/A";
+	u8 rate, txmode, frmode, sgi, ldpc, nsts, stbc, spe, dcm, ersu106t;
+	s8 txpwr, pos_txpwr;
+	u32 txv[3];
+
+	txv[0] = le32_to_cpu(le_txv[0]);
+	txv[1] = le32_to_cpu(le_txv[1]);
+	txv[2] = le32_to_cpu(le_txv[2]);
+
+	if (txv[0] == 0xffffffff) {
+		seq_printf(s, "%-22s  %s = %s\n", " ", "Last TX Rate", "N/A");
+		seq_printf(s, "%-22s  %s = %s\n",
+			   " ", "Chip Out TX Power", "N/A");
+		return;
+	}
+
+	rate = MT7925_TXV_GET_TX_RATE(txv);
+	txmode = MT7925_TXV_GET_TX_MODE(txv);
+	frmode = MT7925_TXV_GET_TX_FRMODE(txv);
+	nsts = MT7925_TXV_GET_TX_NSTS(txv) + 1;
+	sgi = MT7925_TXV_GET_TX_SGI(txv);
+	ldpc = MT7925_TXV_GET_TX_LDPC(txv);
+	stbc = MT7925_TXV_GET_TX_STBC(txv);
+	txpwr = MT7925_TXV_GET_TX_PWR(txv);
+	spe = MT7925_TXV_GET_TX_SPE_IDX(txv);
+	dcm = MT7925_TXV_GET_TX_DCM(txv);
+	ersu106t = MT7925_TXV_GET_TX_106T(txv);
+
+	if (dcm)
+		rate = MT7925_TXV_RATE_UNMASK_DCM(rate);
+	if (ersu106t)
+		rate = MT7925_TXV_RATE_UNMASK_106T(rate);
+
+	if (txmode < ARRAY_SIZE(mt7925_tx_mode_str))
+		mode_str = mt7925_tx_mode_str[txmode];
+
+	seq_printf(s, "%-22s  %s = ", " ", "Last TX Rate");
+
+	/* rate/MCS field */
+	if (txmode == MT7925_TX_RATE_MODE_CCK)
+		seq_printf(s, "%s, ",
+			   rate < 4 ? mt7925_tx_rate_cck_str[rate] :
+			   (((rate >= 5) && (rate <= 7)) ?
+			    mt7925_tx_rate_cck_str[rate - 4] :
+			    mt7925_tx_rate_cck_str[4]));
+	else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+		seq_printf(s, "%s, ", mt7925_hw_rate_ofdm_str(rate));
+	else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+		 txmode == MT7925_TX_RATE_MODE_HTGF)
+		seq_printf(s, "MCS%d, ", rate);
+	else
+		seq_printf(s, "%s%d_MCS%d, ",
+			   stbc ? "NSTS" : "NSS", nsts, rate);
+
+	/* bandwidth */
+	seq_printf(s, "%s, ",
+		   frmode < 5 ? mt7925_tx_rate_bw_str[frmode] :
+		   mt7925_tx_rate_bw_str[5]);
+
+	/* GI / preamble */
+	if (txmode == MT7925_TX_RATE_MODE_CCK)
+		seq_printf(s, "%s, ", rate < 4 ? "LP" : "SP");
+	else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+		; /* OFDM has no GI/preamble to print */
+	else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+		 txmode == MT7925_TX_RATE_MODE_HTGF ||
+		 txmode == MT7925_TX_RATE_MODE_VHT ||
+		 txmode == MT7925_TX_RATE_MODE_PLR)
+		seq_printf(s, "%s, ", sgi == 0 ? "LGI" : "SGI");
+	else
+		seq_printf(s, "%s, ",
+			   sgi == 0 ? "SGI" : (sgi == 1 ? "MGI" : "LGI"));
+
+	/* mode, DCM, 106t, STBC, coding, SPE */
+	seq_printf(s, "%s%s%s%s%s%s%d\n",
+		   mode_str,
+		   dcm ? ", DCM" : "", ersu106t ? ", 106t" : "",
+		   stbc ? ", STBC, " : ", ", ldpc == 0 ? "BCC" : "LDPC",
+		   ", SPE", spe);
+
+	/* chip out TX power (unit: 0.5 dBm) */
+	pos_txpwr = (txpwr < 0) ? (~txpwr + 1) : txpwr;
+	seq_printf(s, "%-22s  %s = %c%d.%1d dBm\n",
+		   " ", "Chip Out TX Power",
+		   (txpwr < 0) ? '-' : '+',
+		   (pos_txpwr / 2), 5 * (pos_txpwr % 2));
+}
+
+/* Guard interval used for the auto-rate entries, mirror Gen4m
+ * connac3x_get_sgi_info_from_fw(): pick the per-mode/per-BW flag.
+ */
+u8
+mt7925_wtbl_get_sgi(const struct mt7925_wtbl_rate *wr, u8 txmode, u8 fcap)
+{
+	bool he = (txmode == MT7925_TX_RATE_MODE_HE_SU ||
+		   txmode == MT7925_TX_RATE_MODE_HE_ER ||
+		   txmode == MT7925_TX_RATE_MODE_HE_TRIG ||
+		   txmode == MT7925_TX_RATE_MODE_HE_MU);
+	bool eht = (txmode == MT7925_TX_RATE_MODE_EHT_ER ||
+		    txmode == MT7925_TX_RATE_MODE_EHT_TRIG ||
+		    txmode == MT7925_TX_RATE_MODE_EHT_MU);
+
+	if (eht)
+		return wr->gi_eht;
+
+	switch (fcap) {
+	case MT7925_BW_20:
+		return he ? wr->g2_he : wr->g2;
+	case MT7925_BW_40:
+		return he ? wr->g4_he : wr->g4;
+	case MT7925_BW_80:
+		return he ? wr->g8_he : wr->g8;
+	case MT7925_BW_160:
+		return he ? wr->g16_he : wr->g16;
+	default:
+		return 0;
+	}
+}
+
+/* Coding used for the auto-rate entries, mirror Gen4m
+ * connac3x_wtbl_get_ldpc_info_from_fw(): 0 = BCC, 1 = LDPC.
+ */
+u8
+mt7925_wtbl_get_ldpc(const struct mt7925_wtbl_rate *wr, u8 txmode)
+{
+	switch (txmode) {
+	case MT7925_TX_RATE_MODE_HTMIX:
+	case MT7925_TX_RATE_MODE_HTGF:
+		return wr->ht_ldpc;
+	case MT7925_TX_RATE_MODE_VHT:
+		return wr->vht_ldpc;
+	case MT7925_TX_RATE_MODE_HE_SU:
+	case MT7925_TX_RATE_MODE_HE_ER:
+	case MT7925_TX_RATE_MODE_HE_TRIG:
+	case MT7925_TX_RATE_MODE_HE_MU:
+		return wr->he_ldpc;
+	case MT7925_TX_RATE_MODE_EHT_ER:
+	case MT7925_TX_RATE_MODE_EHT_TRIG:
+	case MT7925_TX_RATE_MODE_EHT_MU:
+		return wr->eht_ldpc;
+	default:
+		return 0;
+	}
+}
+
+int mt792x_mcu_get_stat(struct mt792x_dev *dev,
+			struct mt7925_sta_stats *stats)
+{
+	u16 len = 0;
+	int ret = 0;
+	struct mt7925_sta_stats_event *event = NULL;
+	struct sk_buff *skb = NULL;
+	struct mt7925_sta_stats_req req = {
+		.tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_STA),
+		.len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+		.lls_read_clear = false,
+		.reset_counter = false,
+	};
+
+	ret = mt7925_get_ap_sta_rec_idx(dev, &req.sta_rec_idx);
+	if (ret)
+		return ret;
+
+	if (!stats)
+		return -EINVAL;
+
+	ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+					MCU_UNI_QUERY(GET_STAT_INFO),
+					&req, sizeof(req), true, &skb);
+
+	if (ret)
+		return ret;
+
+	event = (struct mt7925_sta_stats_event *)skb->data;
+
+	if (le16_to_cpu(event->tag) != UNI_CMD_ID_STATISTICS_STA) {
+		ret = -EPROTO;
+		goto out;
+	}
+
+	len = le16_to_cpu(event->len);
+	if (len < sizeof(*event) - sizeof(event->rsv) ||
+	    len > skb->len - sizeof(event->rsv) || !IS_ALIGNED(len, 4)) {
+		ret = -EMSGSIZE;
+		goto out;
+	}
+
+	if (event->stats.version != MT7925_STA_STATS_VERSION) {
+		ret = -EPROTO;
+		goto out;
+	}
+
+	if (!(le32_to_cpu(event->stats.flags) & MT7925_STA_STATS_VALID)) {
+		ret = -ENOENT;
+		goto out;
+	}
+
+	memcpy(stats, &event->stats, sizeof(*stats));
+
+out:
+	dev_kfree_skb(skb);
+	return ret;
+}
+
+int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,
+			    const u32 *counters, u8 counter_num,
+			    u64 *values)
+{
+	u8 *req;
+	u8 *tlv, *tlv_end;
+	u16 req_len;
+	int i, ret;
+	struct mt7925_mib_hdr *req_hdr;
+	struct mt7925_mib_data *req_tlv;
+	struct sk_buff *skb;
+
+	if (!counters || !values || !counter_num ||
+	    counter_num > MT7925_MIB_MAX_CNT)
+		return -EINVAL;
+
+	/* clear output; counters not returned by firmware stay 0 */
+	memset(values, 0, counter_num * sizeof(*values));
+
+	req_len = sizeof(struct mt7925_mib_hdr) +
+		  counter_num * sizeof(struct mt7925_mib_data);
+
+	req = kzalloc(req_len, GFP_KERNEL);
+	if (!req)
+		return -ENOMEM;
+
+	req_hdr = (struct mt7925_mib_hdr *)req;
+	req_hdr->band_idx = band_idx;
+
+	req_tlv = (struct mt7925_mib_data *)(req + sizeof(*req_hdr));
+	for (i = 0; i < counter_num; i++) {
+		req_tlv[i].tag = cpu_to_le16(UNI_CMD_MIB_DATA);
+		req_tlv[i].len = cpu_to_le16(sizeof(struct mt7925_mib_data));
+		req_tlv[i].counter = cpu_to_le32(counters[i]);
+	}
+
+	ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+					MCU_UNI_QUERY(GET_MIB_INFO), req,
+					req_len, true, &skb);
+	kfree(req);
+	if (ret)
+		return ret;
+
+	if (skb->len < sizeof(struct mt7925_mib_hdr)) {
+		ret = -EMSGSIZE;
+		goto out;
+	}
+
+	/* walk the returned TLVs, match each counter ID to the request */
+	tlv = skb->data + sizeof(struct mt7925_mib_hdr);
+	tlv_end = skb->data + skb->len;
+
+	while (tlv + sizeof(struct mt7925_mib_data) <= tlv_end) {
+		struct mt7925_mib_data *evt_tlv =
+			(struct mt7925_mib_data *)tlv;
+		u16 tag = le16_to_cpu(evt_tlv->tag);
+		u16 len = le16_to_cpu(evt_tlv->len);
+		u32 counter;
+
+		if (len < sizeof(struct mt7925_mib_data) ||
+		    tlv + len > tlv_end)
+			break;
+
+		if (tag == UNI_CMD_MIB_DATA) {
+			counter = le32_to_cpu(evt_tlv->counter);
+
+			if (counter < MT7925_MIB_CNT_MAX_NUM) {
+				for (i = 0; i < counter_num; i++) {
+					if (counters[i] == counter) {
+						values[i] =
+						  le64_to_cpu(evt_tlv->data);
+						break;
+					}
+				}
+			}
+		}
+
+		tlv += len;
+	}
+
+	ret = 0;
+
+out:
+	dev_kfree_skb(skb);
+
+	return ret;
+}
+
+char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx)
+{
+	char * const ofdm_str[] = {
+		"6M", "9M", "12M", "18M", "24M", "36M", "48M", "54M", "N/A"
+	};
+
+	switch (ofdm_idx) {
+	case 11: return ofdm_str[0];	/* 6M */
+	case 15: return ofdm_str[1];	/* 9M */
+	case 10: return ofdm_str[2];	/* 12M */
+	case 14: return ofdm_str[3];	/* 18M */
+	case 9:  return ofdm_str[4];	/* 24M */
+	case 13: return ofdm_str[5];	/* 36M */
+	case 8:  return ofdm_str[6];	/* 48M */
+	case 12: return ofdm_str[7];	/* 54M */
+	default: return ofdm_str[8];
+	}
+}
+
+/* mt792x_mcu_get_wtbl_rate - fetch the firmware auto-rate table for a WCID
+ * @dev:	device
+ * @wlan_idx:	HW WTBL / WCID index (see mt7925_sta_stats.wtbl_idx)
+ * @rate:	out, filled with the auto-rate table snapshot
+ *
+ * Mirror of Gen4m wlanoidGetStatWtbl(): send UNI_CMD_GET_STAT_INFO with the
+ * WTBL rate TLV (cmd tag 10) and parse the reply (event tag 12) into @rate.
+ *
+ * Return: 0 on success, negative errno otherwise.
+ */
+int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,
+			     struct mt7925_wtbl_rate *rate)
+{
+	int ret;
+	struct mt7925_wtbl_rate_event *event = NULL;
+	struct sk_buff *skb = NULL;
+	struct mt7925_wtbl_rate_req req = {
+		.tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_WTBL),
+		.len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+		.wlan_idx = cpu_to_le16(wlan_idx),
+	};
+
+	if (!rate)
+		return -EINVAL;
+
+	ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+					MCU_UNI_QUERY(GET_STAT_INFO),
+					&req, sizeof(req), true, &skb);
+	if (ret)
+		return ret;
+
+	if (skb->len < sizeof(*event)) {
+		ret = -EMSGSIZE;
+		goto out;
+	}
+
+	event = (struct mt7925_wtbl_rate_event *)skb->data;
+
+	if (le16_to_cpu(event->rate.tag) != MT7925_UNI_EVENT_STATISTICS_WTBL) {
+		ret = -EPROTO;
+		goto out;
+	}
+
+	memcpy(rate, &event->rate, sizeof(*rate));
+	ret = 0;
+
+out:
+	dev_kfree_skb(skb);
+	return ret;
+}
+
+/**
+ * mt7925_mcu_get_link_quality - query RSSI and TX link speed for a band
+ * @dev:	device
+ * @band_idx:	band/BSS index (0..3)
+ * @rssi:	out, moving-average beacon RSSI in dBm (may be NULL)
+ * @tx_link_speed_kbps: out, TX rate in kbit/s (may be NULL)
+ *
+ * Return: 0 on success, -ENOENT if the band's link quality is not ready.
+ *
+ * Only RSSI and TX rate come from firmware here; for RX rate read wcid->rate.
+ */
+int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,
+				s8 *rssi, u32 *tx_link_speed_kbps)
+{
+	int ret;
+	struct mt7925_lq_event *event;
+	struct mt7925_link_quality *lq;
+	struct sk_buff *skb;
+	struct mt7925_lq_req req = {
+		.tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_LINK_QUALITY),
+		.len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+	};
+
+	if (band_idx >= MT7925_LQ_BAND_NUM)
+		return -EINVAL;
+
+	ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+					MCU_UNI_QUERY(GET_STAT_INFO), &req,
+					sizeof(req), true, &skb);
+	if (ret)
+		return ret;
+
+	if (skb->len < sizeof(*event)) {
+		ret = -EMSGSIZE;
+		goto out;
+	}
+
+	event = (struct mt7925_lq_event *)skb->data;
+
+	if (le16_to_cpu(event->tag) != UNI_CMD_ID_STATISTICS_LINK_QUALITY) {
+		ret = -EPROTO;
+		goto out;
+	}
+
+	lq = &event->lq[band_idx];
+	if (!lq->is_ready) {
+		ret = -ENOENT;
+		goto out;
+	}
+
+	if (rssi)
+		*rssi = lq->rssi;
+
+	/* firmware reports TX rate in 500 Kb/s units -> kbit/s */
+	if (tx_link_speed_kbps)
+		*tx_link_speed_kbps = le16_to_cpu(lq->link_speed) * 500;
+
+	ret = 0;
+
+out:
+	dev_kfree_skb(skb);
+
+	return ret;
+}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.h b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
new file mode 100644
index 000000000000..a70b9eb3aa80
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
@@ -0,0 +1,309 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/* Copyright (C) 2026 MediaTek Inc. */
+
+#ifndef __MT7925_STATS_H
+#define __MT7925_STATS_H
+
+#include <linux/if_ether.h>
+#include <linux/types.h>
+#include "../mt76_connac_mcu.h"
+
+#define MT7925_STA_STATS_AC_NUM 4
+#define MT7925_STA_STATS_BAND_NUM 2
+#define MT7925_STA_STATS_AGG_RANGE_NUM 16
+#define MT7925_STA_STATS_TX_QUALITY_NUM 4
+#define MT7925_TX_MODE_NUM      16
+#define MT7925_TX_RATE_CCK_NUM  5
+#define MT7925_TX_RATE_BW_NUM   6
+
+/* HW rate code (16-bit) field extract, mirror Gen4m CONNAC3X_HW_TX_RATE_TO_*.
+ * The auto-rate table stores one rate code per entry.
+ */
+#define MT7925_HW_RATE_TO_MCS(_x)	(((_x) & GENMASK(5, 0)) >> 0)
+#define MT7925_HW_RATE_TO_MODE(_x)	(((_x) & GENMASK(9, 6)) >> 6)
+#define MT7925_HW_RATE_TO_NSS(_x)	(((_x) & GENMASK(13, 10)) >> 10)
+#define MT7925_HW_RATE_TO_STBC(_x)	(((_x) & BIT(14)) >> 14)
+#define MT7925_HW_RATE_TO_DCM(_x)	(((_x) & BIT(4)) >> 4)
+#define MT7925_HW_RATE_TO_106T(_x)	(((_x) & BIT(5)) >> 5)
+#define MT7925_HW_RATE_UNMASK_DCM(_r)	((u8)(_r) & 0xef)
+#define MT7925_HW_RATE_UNMASK_106T(_r)	((u8)(_r) & 0xdf)
+
+/* Number of entries in the firmware auto-rate table (mirror Gen4m
+ * AUTO_RATE_NUM). RA cycles through these and reports which one is in use
+ * via wtbl->rate_idx.
+ */
+#define MT7925_AUTO_RATE_NUM 8
+
+/* Firmware EVENT_STATISTICS sub-event tags. The WTBL rate request uses cmd tag
+ * UNI_CMD_ID_STATISTICS_WTBL (10) but the firmware replies with event tag 12.
+ */
+#define MT7925_UNI_EVENT_STATISTICS_WTBL 12
+
+/* CONNAC3X TX Vector BBP latch bit fields (mirror cmm_asic_connac3x.h).
+ * These operate on the 3-DWORD tx_vector[band].txv[3] array.
+ */
+#define MT7925_TXV_GET_TX_RATE(_txv)	((_txv)[2] & 0x7f)
+#define MT7925_TXV_GET_TX_LDPC(_txv)	(((_txv)[2] & (0x1 << 7)) >> 7)
+#define MT7925_TXV_GET_TX_STBC(_txv)	(((_txv)[0] & (0x3 << 6)) >> 6)
+#define MT7925_TXV_GET_TX_FRMODE(_txv)	(((_txv)[0] & (0x7 << 8)) >> 8)
+#define MT7925_TXV_GET_TX_MODE(_txv)	(((_txv)[0] & (0xf << 12)) >> 12)
+#define MT7925_TXV_GET_TX_NSTS(_txv)	(((_txv)[2] & (0xfU << 28)) >> 28)
+#define MT7925_TXV_GET_TX_PWR(_txv)	(((_txv)[0] & (0xff << 16)) >> 16)
+#define MT7925_TXV_GET_TX_SGI(_txv)	(((_txv)[1] & (0x3 << 26)) >> 26)
+#define MT7925_TXV_GET_TX_SPE_IDX(_txv)	(((_txv)[0] & (0x1f << 0)) >> 0)
+#define MT7925_TXV_GET_TX_DCM(_txv)	(((_txv)[2] & (0x1 << 4)) >> 4)
+#define MT7925_TXV_GET_TX_106T(_txv)	(((_txv)[2] & (0x1 << 5)) >> 5)
+#define MT7925_TXV_RATE_UNMASK_DCM(_r)	((u8)(_r) & 0xef)
+#define MT7925_TXV_RATE_UNMASK_106T(_r)	((u8)(_r) & 0xdf)
+
+extern const char * const mt7925_tx_mode_str[MT7925_TX_MODE_NUM];
+extern const char * const mt7925_tx_rate_cck_str[MT7925_TX_RATE_CCK_NUM];
+extern const char * const mt7925_tx_rate_bw_str[MT7925_TX_RATE_BW_NUM];
+
+/* TX rate mode, mirror Gen4m TX_RATE_MODE_* (see nic_rate.h). Used to decode
+ * both the TX Vector BBP latch and the firmware auto-rate table rate codes.
+ */
+enum mt7925_tx_rate_mode {
+	MT7925_TX_RATE_MODE_CCK = 0,
+	MT7925_TX_RATE_MODE_OFDM = 1,
+	MT7925_TX_RATE_MODE_HTMIX = 2,
+	MT7925_TX_RATE_MODE_HTGF = 3,
+	MT7925_TX_RATE_MODE_VHT = 4,
+	MT7925_TX_RATE_MODE_PLR = 5,
+	MT7925_TX_RATE_MODE_HE_SU = 8,
+	MT7925_TX_RATE_MODE_HE_ER = 9,
+	MT7925_TX_RATE_MODE_HE_TRIG = 10,
+	MT7925_TX_RATE_MODE_HE_MU = 11,
+	MT7925_TX_RATE_MODE_EHT_ER = 13,
+	MT7925_TX_RATE_MODE_EHT_TRIG = 14,
+	MT7925_TX_RATE_MODE_EHT_MU = 15,
+};
+
+enum {
+	UNI_CMD_MIB_DATA,
+};
+
+enum stats_query_mib_array_index {
+	QUERY_MIB_CNT_RX_FCS_ERR = 0,
+	QUERY_MIB_CNT_RX_FIFO_OVERFLOW,
+	QUERY_MIB_CNT_RX_MPDU,
+	QUERY_MIB_CNT_AMPDU_RX_COUNT,
+	QUERY_MIB_CNT_PF_DROP,
+	QUERY_MIB_CNT_LEN_MISMATCH,
+	QUERY_MIB_CNT_CHANNEL_IDLE,
+	QUERY_MIB_CNT_CCA_NAV_TX_TIME,
+	QUERY_MIB_CNT_MDRDY,
+	QUERY_MIB_CNT_RX_CCK_MDRDY_TIME,
+	QUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME,
+	QUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME,
+	QUERY_MIB_CNT_P_CCA_TIME,
+	QUERY_MIB_CNT_S_CCA_TIME,
+	QUERY_MIB_CNT_P_ED_TIME,
+	QUERY_MIB_CNT_BCN_TX,
+	QUERY_MIB_CNT_TX_BW_40MHZ,
+	QUERY_MIB_CNT_TX_BW_80MHZ,
+	QUERY_MIB_CNT_TX_BW_160MHZ,
+	QUERY_MIB_CNT_BSS0_BA_MISS,
+	QUERY_MIB_CNT_BSS0_RTS_TX_CNT,
+	QUERY_MIB_CNT_BSS0_FRAME_RETRY,
+	QUERY_MIB_CNT_BSS0_FRAME_RETRY_2,
+	QUERY_MIB_CNT_BSS0_RTS_RETRY,
+	QUERY_MIB_CNT_BSS0_ACK_FAIL,
+	QUERY_MIB_CNT_AMPDU,
+	QUERY_MIB_CNT_AMPDU_MPDU,
+	QUERY_MIB_CNT_AMPDU_ACKED,
+	QUERY_MIB_CNT_MAX_NUM,
+};
+
+enum stats_trx_agg_range {
+	STAT_MIB_CNT_TRX_AGG_RANGE0_IDX,
+	STAT_MIB_CNT_TRX_AGG_RANGE1_IDX,
+	STAT_MIB_CNT_TRX_AGG_RANGE2_IDX,
+	STAT_MIB_CNT_TRX_AGG_RANGE3_IDX,
+	STAT_MIB_CNT_TRX_AGG_RANGE4_IDX,
+	STAT_MIB_CNT_TRX_AGG_RANGE5_IDX,
+	STAT_MIB_CNT_TRX_AGG_RANGE6_IDX,
+	STAT_MIB_CNT_TRX_AGG_RANGE7_IDX,
+	STAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM
+};
+
+enum {
+	MT7925_BW_20 = 0,
+	MT7925_BW_40,
+	MT7925_BW_80,
+	MT7925_BW_160,
+};
+
+struct mt7925_sta_stats_ac {
+	__le32 tx_fail;
+	__le32 tx_retry;
+} __packed;
+
+struct mt7925_sta_stats_tx_vector {
+	__le32 txv[3];
+} __packed;
+
+struct mt7925_sta_stats_mib {
+	__le32 rx_mpdu_cnt;
+	__le32 fcs_error;
+	__le32 rx_fifo_full;
+	__le32 ampdu_tx_sf_cnt;
+	__le32 ampdu_tx_ack_sf_cnt;
+	__le32 tx_range_ampdu_cnt[MT7925_STA_STATS_AGG_RANGE_NUM];
+} __packed;
+
+/* Firmware EVENT_STA_STATISTICS body. */
+struct mt7925_sta_stats {
+	u8 version;
+	u8 rsv[3];
+	__le32 flags;
+
+	u8 sta_idx;
+	u8 bss_idx;
+	u8 wtbl_idx;
+	u8 rsv1;
+
+	u8 mac_addr[ETH_ALEN];
+	u8 per;
+	u8 rcpi;
+
+	__le32 phy_mode;
+	__le16 link_speed; /* 0.5 Mbit/s units */
+	u8 link_quality;
+	u8 link_reserved;
+
+	__le32 tx_count;
+	__le32 tx_fail_count;
+	__le32 tx_life_timeout_count;
+	__le32 tx_done_air_time;
+	__le32 transmit_count;
+	__le32 transmit_fail_count;
+
+	struct mt7925_sta_stats_ac ac[MT7925_STA_STATS_AC_NUM];
+
+	u8 temperature;
+	u8 skip_ar;
+	u8 ar_table_idx;
+	u8 rate_entry_idx;
+	u8 rate_entry_idx_prev;
+	u8 tx_sgi_detect_pass_cnt;
+	u8 ave_per;
+	u8 rsv2;
+
+	__le32 agg_range_ctrl[2];
+	u8 range_mode;
+	u8 rsv3[3];
+	__le32 agg_range_ctrl_ext[6];
+
+	u8 ar_state_curr;
+	u8 ar_state_prev;
+	u8 ar_action_type;
+	u8 highest_rate_cnt;
+	u8 lowest_rate_cnt;
+	u8 rsv4;
+	__le16 train_up;
+	__le16 train_down;
+	u8 rsv5[2];
+	__le32 rate1_tx_cnt;
+	__le32 rate1_fail_cnt;
+
+	struct mt7925_sta_stats_tx_vector tx_vector[MT7925_STA_STATS_BAND_NUM];
+	struct mt7925_sta_stats_mib mib[MT7925_STA_STATS_BAND_NUM];
+
+	u8 is_force_tx_stream;
+	u8 is_force_se_off;
+	__le16 ra_running_cnt;
+	u8 ra_status;
+	u8 max_ampdu_factor;
+	u8 tx_quality[MT7925_STA_STATS_TX_QUALITY_NUM];
+	u8 tx_rate_up_penalty;
+	u8 low_traffic_mode;
+	u8 low_traffic_count;
+	u8 low_traffic_dashboard;
+	u8 dynamic_sgi_state;
+	u8 dynamic_sgi_score;
+	u8 dynamic_bw_state;
+	u8 dynamic_gband_256qam_state;
+	u8 vht_non_sp_rate_state;
+	u8 rsv6[3];
+	u8 rsv7[2];
+} __packed;
+
+struct mt7925_sta_rec_lookup {
+	int sta_rec_idx;
+};
+
+/* Firmware auto-rate table snapshot, mirror Gen4m struct UNI_EVENT_STAT_WTBL.
+ *
+ * The firmware RA maintains up to MT7925_AUTO_RATE_NUM candidate rates in
+ * rate_code[]. rate_idx tells which entry is currently in use, so a dump can
+ * point at it with "-->". fcap is the bandwidth capability and the ldpc/sgi
+ * flags describe the per-mode coding/guard-interval used for those rates.
+ */
+struct mt7925_wtbl_rate {
+	__le16 tag;
+	__le16 len;
+	u8 rx_rcpi0;
+	u8 rx_rcpi1;
+	u8 cbrn;
+	u8 fcap;
+	u8 spe_idx;
+	u8 g2;			/* SGI enabled, non-HE, BW20 */
+	u8 g4;			/* SGI enabled, non-HE, BW40 */
+	u8 g8;			/* SGI enabled, non-HE, BW80 */
+	u8 g16;			/* SGI enabled, non-HE, BW160 */
+	u8 g2_he;		/* GI, HE, BW20 */
+	u8 g4_he;		/* GI, HE, BW40 */
+	u8 g8_he;		/* GI, HE, BW80 */
+	u8 g16_he;		/* GI, HE, BW160 */
+	u8 gi_eht;		/* GI, EHT */
+	u8 ht_ldpc;
+	u8 vht_ldpc;
+	u8 he_ldpc;
+	u8 eht_ldpc;
+	u8 af;
+	u8 rate_idx;		/* index into rate_code[] currently in use */
+	__le16 rate_code[MT7925_AUTO_RATE_NUM];
+	__le32 rsv[4];
+} __packed;
+
+int
+mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx);
+
+void
+mt7925_print_last_tx_rate(struct seq_file *s, const __le32 le_txv[3]);
+
+u8
+mt7925_wtbl_get_sgi(const struct mt7925_wtbl_rate *wr, u8 txmode, u8 fcap);
+
+u8
+mt7925_wtbl_get_ldpc(const struct mt7925_wtbl_rate *wr, u8 txmode);
+
+char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx);
+
+int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,
+			     struct mt7925_wtbl_rate *rate);
+
+int mt792x_mcu_get_stat(struct mt792x_dev *dev,
+			struct mt7925_sta_stats *stats);
+
+int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,
+			    const u32 *counters, u8 counter_num,
+			    u64 *values);
+
+/* GET_STAT_INFO (UNI_CMD_ID_GET_STATISTICS = 0x23),
+ * sub-command UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1.
+ *
+ * @band_idx:            band/BSS index (0..3)
+ * @rssi:                out, moving-average beacon RSSI (dBm), may be NULL
+ * @tx_link_speed_kbps:  out, TX link speed in kbit/s, may be NULL
+ *
+ * Returns 0 on success, -ENOENT if the band's link quality is not ready.
+ *
+ * NOTE: firmware provides only RSSI and TX rate here. RX rate/bandwidth are
+ * not in this event; read wcid->rate for the RX rate.
+ */
+int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,
+				s8 *rssi, u32 *tx_link_speed_kbps);
+
+#endif
-- 
2.45.2



  parent reply	other threads:[~2026-08-27  5:55 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-24  3:07 [PATCH v1] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose Eason Lai
2026-08-25  4:00 ` kernel test robot
2026-08-25  7:55 ` kernel test robot
2026-08-27  5:54 ` Eason Lai [this message]
2026-08-27  8:44   ` [PATCH v3] " Eason Lai
2026-08-28  7:39     ` kernel test robot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260827055456.1550357-1-eason.lai@mediatek.com \
    --to=eason.lai@mediatek.com \
    --cc=deren.wu@mediatek.com \
    --cc=jb.tsai@mediatek.com \
    --cc=kun.wu@mediatek.com \
    --cc=leon.yen@mediatek.com \
    --cc=linux-mediatek@lists.infradead.org \
    --cc=linux-wireless@vger.kernel.org \
    --cc=litien.chang@mediatek.com \
    --cc=lkp@intel.com \
    --cc=lorenzo@kernel.org \
    --cc=michael.shih@mediatek.com \
    --cc=nbd@nbd.name \
    --cc=quan.zhou@mediatek.com \
    --cc=ryder.lee@mediatek.com \
    --cc=sean.wang@mediatek.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox