[][MAC80211][mt76][Add ZWDFS testmode support]

[Description]
Add ZWDFS testmode support including background chain channel switch
and show IPI histogram command

[Release-log]
N/A

Change-Id: I0acc2c52abd30f77db4a8c22c8516ad69c13fe11
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/6696081
diff --git a/autobuild_mac80211_release/package/kernel/mt76/patches/1114-mt76-testmode-add-ZWDFS-test-mode-support.patch b/autobuild_mac80211_release/package/kernel/mt76/patches/1114-mt76-testmode-add-ZWDFS-test-mode-support.patch
new file mode 100644
index 0000000..1deda11
--- /dev/null
+++ b/autobuild_mac80211_release/package/kernel/mt76/patches/1114-mt76-testmode-add-ZWDFS-test-mode-support.patch
@@ -0,0 +1,644 @@
+From 8f5eeab00cf1eeb394132306db23fb1cd2ef5afa Mon Sep 17 00:00:00 2001
+From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+Date: Thu, 27 Oct 2022 17:42:07 +0800
+Subject: [PATCH] mt76: testmode: add ZWDFS test mode support
+
+Change-Id: I14d104b7158a35acf6b0595357d07fb87f5a9d94
+Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
+---
+ mt76.h            |   8 ++
+ mt76_connac_mcu.h |   2 +
+ mt7915/mcu.c      |  64 ++++++++++++++
+ mt7915/mcu.h      |  46 ++++++++++
+ mt7915/mt7915.h   |   4 +
+ mt7915/testmode.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++
+ testmode.c        |  23 ++++-
+ testmode.h        |  43 ++++++++++
+ tools/fields.c    |  20 +++++
+ 9 files changed, 422 insertions(+), 1 deletion(-)
+
+diff --git a/mt76.h b/mt76.h
+index 3ca449f..adeba98 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -708,6 +708,14 @@ struct mt76_testmode_data {
+ 		u64 fcs_error[__MT_RXQ_MAX];
+ 		u64 len_mismatch;
+ 	} rx_stats;
++
++	u8 offchan_ch;
++	u8 offchan_center_ch;
++	u8 offchan_bw;
++
++	u8 ipi_threshold;
++	u32 ipi_period;
++	u8 ipi_antenna_idx;
+ };
+ 
+ struct mt76_vif {
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index b14f931..d7ea781 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1148,6 +1148,7 @@ enum {
+ 	MCU_EXT_CMD_OFFCH_SCAN_CTRL = 0x9a,
+ 	MCU_EXT_CMD_SET_RDD_TH = 0x9d,
+ 	MCU_EXT_CMD_MURU_CTRL = 0x9f,
++	MCU_EXT_CMD_IPI_HIST_CTRL = 0xa3,
+ 	MCU_EXT_CMD_RX_STAT = 0xa4,
+ 	MCU_EXT_CMD_SET_SPR = 0xa8,
+ 	MCU_EXT_CMD_GROUP_PRE_CAL_INFO = 0xab,
+@@ -1158,6 +1159,7 @@ enum {
+ 	MCU_EXT_CMD_RX_STAT_USER_CTRL = 0xb3,
+ 	MCU_EXT_CMD_CERT_CFG = 0xb7,
+ 	MCU_EXT_CMD_CSI_CTRL = 0xc2,
++	MCU_EXT_CMD_IPI_HIST_SCAN = 0xc5,
+ };
+ 
+ enum {
+diff --git a/mt7915/mcu.c b/mt7915/mcu.c
+index 34ce7d6..87578d7 100644
+--- a/mt7915/mcu.c
++++ b/mt7915/mcu.c
+@@ -2665,6 +2665,7 @@ mt7915_mcu_background_chain_ctrl(struct mt7915_phy *phy,
+ 		req.monitor_chan = chandef->chan->hw_value;
+ 		req.monitor_central_chan =
+ 			ieee80211_frequency_to_channel(chandef->center_freq1);
++		req.monitor_bw = mt76_connac_chan_bw(chandef);
+ 		req.band_idx = phy != &dev->phy;
+ 		req.scan_mode = 2;
+ 		break;
+@@ -4236,3 +4237,66 @@ int mt7915_mcu_set_amsdu_algo(struct mt7915_dev *dev, u16 wcid, u8 enable)
+ 	return mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(MEC_CTRL), &req, sizeof(req), true);
+ }
+ #endif
++
++int mt7915_mcu_ipi_hist_ctrl(struct mt7915_phy *phy, void *data, u8 cmd, bool wait_resp)
++{
++	struct mt7915_dev *dev = phy->dev;
++	struct sk_buff *skb;
++	int ret;
++	struct {
++		u8 ipi_hist_idx;
++		u8 band_idx;
++		u8 set_val;
++		u8 rsv;
++		int idle_power_th;
++		u32 idle_power_max_cnt;
++		u32 idle_power_duration;
++		u32 idle_power_cmd_type;
++	} __packed req = {
++		.ipi_hist_idx = cmd,
++		.band_idx = phy->band_idx,
++	};
++
++	ret = mt76_mcu_send_and_get_msg(&dev->mt76, MCU_EXT_CMD(IPI_HIST_CTRL),
++					&req, sizeof(req), wait_resp, &skb);
++	if (ret)
++		return ret;
++
++	if (!wait_resp)
++		return 0;
++
++	memcpy(data, skb->data, sizeof(struct mt7915_mcu_rdd_ipi_ctrl));
++	dev_kfree_skb(skb);
++
++	return 0;
++}
++
++int mt7915_mcu_ipi_hist_scan(struct mt7915_phy *phy, void *data, u8 mode, bool wait_resp)
++{
++	struct mt7915_dev *dev = phy->dev;
++	struct sk_buff *skb;
++	int ret;
++	struct rdd_ipi_hist_scan {
++		u8 mode;
++		u8 pd_setting;
++		u8 band_idx;
++		u8 rsv;
++	} __packed req = {
++		.mode = mode,
++		.pd_setting = 1,
++		.band_idx = phy->band_idx,
++	};
++
++	ret = mt76_mcu_send_and_get_msg(&dev->mt76, MCU_EXT_CMD(IPI_HIST_SCAN),
++					&req, sizeof(req), wait_resp, &skb);
++	if (ret)
++		return ret;
++
++	if (!wait_resp)
++		return 0;
++
++	memcpy(data, skb->data, sizeof(struct mt7915_mcu_rdd_ipi_scan));
++	dev_kfree_skb(skb);
++
++	return 0;
++}
+diff --git a/mt7915/mcu.h b/mt7915/mcu.h
+index ad85e56..e0fb5a1 100644
+--- a/mt7915/mcu.h
++++ b/mt7915/mcu.h
+@@ -576,6 +576,52 @@ struct csi_data {
+ };
+ #endif
+ 
++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 RDM_NF_MAX_WF_IDX		8
++#define POWER_INDICATE_HIST_MAX		RDD_IPI_FREE_RUN_CNT
++#define IPI_HIST_TYPE_NUM		(POWER_INDICATE_HIST_MAX + 1)
++
++struct mt7915_mcu_rdd_ipi_ctrl {
++	u8 ipi_hist_idx;
++	u8 band_idx;
++	u8 rsv[2];
++	u32 ipi_hist_val[IPI_HIST_TYPE_NUM];
++	u32 tx_assert_time;						/* unit: us */
++} __packed;
++
++struct mt7915_mcu_rdd_ipi_scan {
++	u32 ipi_hist_val[RDM_NF_MAX_WF_IDX][POWER_INDICATE_HIST_MAX];
++	u8 band_idx;
++	u8 rsv[2];
++	u8 tx_assert_time;						/* unit: us */
++} __packed;
++
+ /* MURU */
+ #define OFDMA_DL                       BIT(0)
+ #define OFDMA_UL                       BIT(1)
+diff --git a/mt7915/mt7915.h b/mt7915/mt7915.h
+index f3bbdba..d29c8ef 100644
+--- a/mt7915/mt7915.h
++++ b/mt7915/mt7915.h
+@@ -289,6 +289,7 @@ struct mt7915_phy {
+ 
+ 	struct mib_stats mib;
+ 	struct mt76_channel_state state_ts;
++	struct delayed_work ipi_work;
+ 
+ #ifdef CONFIG_NL80211_TESTMODE
+ 	struct {
+@@ -725,6 +726,9 @@ int mt7915_vendor_amnt_sta_remove(struct mt7915_phy *phy,
+ 				  struct ieee80211_sta *sta);
+ #endif
+ 
++int mt7915_mcu_ipi_hist_ctrl(struct mt7915_phy *phy, void *data, u8 cmd, bool wait_resp);
++int mt7915_mcu_ipi_hist_scan(struct mt7915_phy *phy, void *data, u8 mode, bool wait_resp);
++
+ #ifdef MTK_DEBUG
+ int mt7915_mtk_init_debugfs(struct mt7915_phy *phy, struct dentry *dir);
+ int mt7915_dbg_mcu_wa_cmd(struct mt7915_dev *dev, int cmd, u32 a1, u32 a2, u32 a3, bool wait_resp);
+diff --git a/mt7915/testmode.c b/mt7915/testmode.c
+index f735ed6..8c48d2d 100644
+--- a/mt7915/testmode.c
++++ b/mt7915/testmode.c
+@@ -13,6 +13,11 @@ enum {
+ 	TM_CHANGED_AID,
+ 	TM_CHANGED_CFG,
+ 	TM_CHANGED_TXBF_ACT,
++	TM_CHANGED_OFF_CHAN_CH,
++	TM_CHANGED_OFF_CHAN_CENTER_CH,
++	TM_CHANGED_OFF_CHAN_BW,
++	TM_CHANGED_IPI_THRESHOLD,
++	TM_CHANGED_IPI_PERIOD,
+ 
+ 	/* must be last */
+ 	NUM_TM_CHANGED
+@@ -24,6 +29,11 @@ static const u8 tm_change_map[] = {
+ 	[TM_CHANGED_AID] = MT76_TM_ATTR_AID,
+ 	[TM_CHANGED_CFG] = MT76_TM_ATTR_CFG,
+ 	[TM_CHANGED_TXBF_ACT] = MT76_TM_ATTR_TXBF_ACT,
++	[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,
+ };
+ 
+ struct reg_band {
+@@ -962,6 +972,201 @@ mt7915_tm_set_txbf(struct mt7915_phy *phy)
+ 	return 0;
+ }
+ 
++static int
++mt7915_tm_set_offchan(struct mt7915_phy *phy)
++{
++	struct mt76_phy *mphy = phy->mt76;
++	struct mt7915_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);
++	int width_mhz;
++	const int bw_to_mhz[] = {
++		[NL80211_CHAN_WIDTH_20_NOHT] = 20,
++		[NL80211_CHAN_WIDTH_20] = 20,
++		[NL80211_CHAN_WIDTH_40] = 40,
++		[NL80211_CHAN_WIDTH_80] = 80,
++		[NL80211_CHAN_WIDTH_80P80] = 80,
++		[NL80211_CHAN_WIDTH_160] = 160,
++	};
++
++	if (phy->band_idx != NL80211_BAND_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;
++	width_mhz = bw_to_mhz[chandef.width];
++	chandef.center_freq1 = freq;
++	chan = ieee80211_get_channel(hw->wiphy, freq);
++	chandef.chan = chan;
++
++	memset(&dev->rdd2_chandef, 0, sizeof(struct cfg80211_chan_def));
++
++	ret = mt7915_mcu_rdd_background_enable(phy, &chandef);
++
++	if (ret)
++		goto out;
++
++	dev->rdd2_phy = phy;
++	dev->rdd2_chandef = chandef;
++
++	return ret;
++
++out:
++	td->offchan_ch = 0;
++	td->offchan_bw = 0;
++
++	return ret;
++}
++
++static void
++mt7915_tm_dump_ipi(struct mt7915_phy *phy, void *data, u8 antenna_num,
++		   u8 start_antenna_idx, bool is_scan)
++{
++#define PRECISION	100
++	struct mt7915_dev *dev = phy->dev;
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	struct mt7915_mcu_rdd_ipi_scan *scan_data;
++	struct mt7915_mcu_rdd_ipi_ctrl *ctrl_data;
++	u32 ipi_idx, ipi_free_count, ipi_percentage, ipi_hist_count_th, ipi_hist_total_count;
++	u32 self_idle_ratio, ipi_idle_ratio, channel_load, tx_assert_time;
++	u8 i, antenna_idx = start_antenna_idx;
++	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",
++	};
++
++	if (is_scan) {
++		scan_data = (struct mt7915_mcu_rdd_ipi_scan *)data;
++		tx_assert_time = scan_data->tx_assert_time;
++	} else {
++		ctrl_data = (struct mt7915_mcu_rdd_ipi_ctrl *)data;
++		tx_assert_time = ctrl_data->tx_assert_time;
++	}
++
++	for (i = 0; i < antenna_num; i++) {
++		ipi_free_count = 0;
++		ipi_hist_count_th = 0;
++		ipi_hist_total_count = 0;
++		ipi_hist_data = is_scan ? scan_data->ipi_hist_val[antenna_idx] :
++					  ctrl_data->ipi_hist_val;
++
++		dev_info(dev->mt76.dev, "Antenna index: %d\n", antenna_idx);
++		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 = is_scan ? ipi_hist_total_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",
++			 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 - (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->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->band_idx, channel_load, self_idle_ratio, ipi_idle_ratio);
++		antenna_idx++;
++	}
++}
++
++static void
++mt7915_tm_ipi_work(struct work_struct *work)
++{
++	struct mt7915_phy *phy = container_of(work, struct mt7915_phy, ipi_work.work);
++	struct mt7915_dev *dev = phy->dev;
++	struct mt76_testmode_data *td = &phy->mt76->test;
++	u8 start_antenna_idx = 0, antenna_num = 1;
++
++	if (!is_mt7915(&dev->mt76)) {
++		struct mt7915_mcu_rdd_ipi_scan data;
++
++		if (phy->band_idx)
++			start_antenna_idx = 4;
++
++		/* Use all antenna */
++		if (td->ipi_antenna_idx == MT76_TM_IPI_ANTENNA_ALL)
++			antenna_num = 4;
++		else
++			start_antenna_idx += td->ipi_antenna_idx;
++
++		mt7915_mcu_ipi_hist_scan(phy, &data, 0, true);
++		mt7915_tm_dump_ipi(phy, &data, antenna_num, start_antenna_idx, true);
++	} else {
++		struct mt7915_mcu_rdd_ipi_ctrl data;
++
++		mt7915_mcu_ipi_hist_ctrl(phy, &data, RDD_IPI_HIST_ALL_CNT, true);
++		mt7915_tm_dump_ipi(phy, &data, antenna_num, start_antenna_idx, false);
++	}
++}
++
++static int
++mt7915_tm_set_ipi(struct mt7915_phy *phy)
++{
++	struct mt76_testmode_data *td = &phy->mt76->test;
++
++	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 int
+ mt7915_tm_set_wmm_qid(struct mt7915_phy *phy, u8 qid, u8 aifs, u8 cw_min,
+ 		      u16 cw_max, u16 txop, u8 tx_cmd)
+@@ -1247,6 +1452,8 @@ mt7915_tm_init(struct mt7915_phy *phy, bool en)
+ 		phy->mt76->test.tx_mpdu_len = 0;
+ 		phy->test.bf_en = 0;
+ 		mt7915_tm_set_entry(phy);
++	} else {
++		INIT_DELAYED_WORK(&phy->ipi_work, mt7915_tm_ipi_work);
+ 	}
+ }
+ 
+@@ -2004,6 +2211,12 @@ mt7915_tm_update_params(struct mt7915_phy *phy, u32 changed)
+ 		mt7915_tm_set_cfg(phy);
+ 	if (changed & BIT(TM_CHANGED_TXBF_ACT))
+ 		mt7915_tm_set_txbf(phy);
++	if ((changed & BIT(TM_CHANGED_OFF_CHAN_CH)) &&
++	    (changed & BIT(TM_CHANGED_OFF_CHAN_BW)))
++		mt7915_tm_set_offchan(phy);
++	if ((changed & BIT(TM_CHANGED_IPI_THRESHOLD)) &&
++	    (changed & BIT(TM_CHANGED_IPI_PERIOD)))
++		mt7915_tm_set_ipi(phy);
+ }
+ 
+ static int
+diff --git a/testmode.c b/testmode.c
+index aa874a8..de2892b 100644
+--- a/testmode.c
++++ b/testmode.c
+@@ -24,6 +24,12 @@ 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_PATH] = { .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 },
+ };
+ EXPORT_SYMBOL_GPL(mt76_tm_policy);
+ 
+@@ -402,6 +408,7 @@ mt76_testmode_init_defaults(struct mt76_phy *phy)
+ 	td->tx_count = 1;
+ 	td->tx_rate_mode = MT76_TM_TX_MODE_OFDM;
+ 	td->tx_rate_nss = 1;
++	td->ipi_antenna_idx = MT76_TM_IPI_ANTENNA_ALL;
+ 
+ 	memcpy(td->addr[0], phy->macaddr, ETH_ALEN);
+ 	memcpy(td->addr[1], phy->macaddr, ETH_ALEN);
+@@ -607,6 +614,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,
+@@ -623,7 +633,15 @@ int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 			   &td->tx_power_control, 0, 1) ||
+ 	    mt76_tm_get_u8(tb[MT76_TM_ATTR_AID], &td->aid, 0, 16) ||
+ 	    mt76_tm_get_u8(tb[MT76_TM_ATTR_RU_ALLOC], &td->ru_alloc, 0, 0xff) ||
+-	    mt76_tm_get_u8(tb[MT76_TM_ATTR_RU_IDX], &td->ru_idx, 0, 68))
++	    mt76_tm_get_u8(tb[MT76_TM_ATTR_RU_IDX], &td->ru_idx, 0, 68) ||
++	    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_ANTENNA_INDEX], &td->ipi_antenna_idx,
++			   MT76_TM_IPI_ANTENNA_0, MT76_TM_IPI_ANTENNA_ALL))
+ 		goto out;
+ 
+ 	if (tb[MT76_TM_ATTR_TX_LENGTH]) {
+@@ -857,6 +875,9 @@ int mt76_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *msg,
+ 	    nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_MODE, td->tx_rate_mode) ||
+ 	    nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_SGI, td->tx_rate_sgi) ||
+ 	    nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_STBC, td->tx_rate_stbc) ||
++	    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/testmode.h b/testmode.h
+index 5d1fe79..9f61847 100644
+--- a/testmode.h
++++ b/testmode.h
+@@ -63,6 +63,19 @@
+  * 	(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)
+  *
+  */
+ enum mt76_testmode_attr {
+@@ -116,6 +129,15 @@ enum mt76_testmode_attr {
+ 	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,
++
+ 	/* keep last */
+ 	NUM_MT76_TM_ATTRS,
+ 	MT76_TM_ATTR_MAX = NUM_MT76_TM_ATTRS - 1,
+@@ -292,4 +314,25 @@ enum mt76_testmode_txbf_act {
+ 	MT76_TM_TXBF_ACT_MAX = NUM_MT76_TM_TXBF_ACT - 1,
+ };
+ 
++/**
++ * enum mt76_testmode_ipi_antenna - specify antenna index for ipi count
++ *
++ * @MT76_TM_IPI_ANTENNA_0: use antenna 0
++ * @MT76_TM_IPI_ANTENNA_1: use antenna 1
++ * @MT76_TM_IPI_ANTENNA_2: use antenna 2
++ * @MT76_TM_IPI_ANTENNA_3: use antenna 3
++ * @MT76_TM_IPI_ANTENNA_ALL: use all antenna
++ */
++enum mt76_testmode_ipi_antenna {
++	MT76_TM_IPI_ANTENNA_0,
++	MT76_TM_IPI_ANTENNA_1,
++	MT76_TM_IPI_ANTENNA_2,
++	MT76_TM_IPI_ANTENNA_3,
++	MT76_TM_IPI_ANTENNA_ALL,
++
++	/* keep last */
++	NUM_MT76_TM_IPI_ANTENNA,
++	MT76_TM_IPI_ANTENNA_MAX = NUM_MT76_TM_IPI_ANTENNA - 1,
++};
++
+ #endif
+diff --git a/tools/fields.c b/tools/fields.c
+index 47fc69f..21f6b19 100644
+--- a/tools/fields.c
++++ b/tools/fields.c
+@@ -46,6 +46,14 @@ static const char * const testmode_txbf_act[] = {
+ 	[MT76_TM_TXBF_ACT_E2P_UPDATE] = "e2p_update",
+ };
+ 
++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_160] = "160",
++};
++
+ static void print_enum(const struct tm_field *field, struct nlattr *attr)
+ {
+ 	unsigned int i = nla_get_u8(attr);
+@@ -411,6 +419,12 @@ static const struct tm_field testdata_fields[NUM_MT76_TM_ATTRS] = {
+ 	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),
++	FIELD(u8, IPI_THRESHOLD, "ipi_threshold"),
++	FIELD(u32, IPI_PERIOD, "ipi_period"),
++	FIELD(u8, IPI_ANTENNA_INDEX, "ipi_antenna_idx"),
+ 	FIELD_MAC(MAC_ADDRS, "mac_addrs"),
+ 	FIELD_NESTED_RO(STATS, stats, "",
+ 			.print_extra = print_extra_stats),
+@@ -442,6 +456,12 @@ static struct nla_policy testdata_policy[NUM_MT76_TM_ATTRS] = {
+ 	[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 },
+ };
+ 
+ const struct tm_field msg_field = {
+-- 
+2.18.0
+