Linux wireless drivers development
 help / color / mirror / Atom feed
From: Sean Wang <sean.wang@kernel.org>
To: Felix Fietkau <nbd@nbd.name>, Lorenzo Bianconi <lorenzo@kernel.org>
Cc: chengwei.yu@mediatek.com, yu-ching.liu@mediatek.com,
	jenhao.yang@mediatek.com, posh.sun@mediatek.com,
	linux-wireless@vger.kernel.org,
	linux-mediatek@lists.infradead.org,
	Sean Wang <sean.wang@mediatek.com>
Subject: [PATCH v2 4/9] wifi: mt76: mt7925: add NAN MCU helpers
Date: Wed, 24 Jun 2026 19:18:29 -0500	[thread overview]
Message-ID: <20260625001834.475094-5-sean.wang@kernel.org> (raw)
In-Reply-To: <20260625001834.475094-1-sean.wang@kernel.org>

From: Sean Wang <sean.wang@mediatek.com>

Add the mt7925 NAN MCU ABI and helpers for enable, disable, configuration
updates, availability updates and peer schedule commands.

Upper-layer integration is added by later patches.

Co-developed-by: Stella Liu <yu-ching.liu@mediatek.com>
Signed-off-by: Stella Liu <yu-ching.liu@mediatek.com>
Co-developed-by: Jeremy Yu <chengwei.yu@mediatek.com>
Signed-off-by: Jeremy Yu <chengwei.yu@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
 .../wireless/mediatek/mt76/mt7925/Makefile    |   2 +-
 .../net/wireless/mediatek/mt76/mt7925/nan.c   | 920 ++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7925/nan.h   | 438 +++++++++
 .../net/wireless/mediatek/mt76/mt7925/regd.c  |  30 +
 .../net/wireless/mediatek/mt76/mt7925/regd.h  |   3 +
 drivers/net/wireless/mediatek/mt76/mt792x.h   |  38 +
 6 files changed, 1430 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/nan.c
 create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/nan.h

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
index 8f1078ce3231..f9dcc0bba393 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
+mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.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/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
new file mode 100644
index 000000000000..dc7aa2cd9449
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
@@ -0,0 +1,920 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/* Copyright (C) 2025-2026 MediaTek Inc. */
+
+#include <asm/byteorder.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/stddef.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/ieee80211.h>
+#include <net/cfg80211.h>
+#include <net/mac80211.h>
+
+#include "mt7925.h"
+#include "mcu.h"
+#include "nan.h"
+#include "regd.h"
+
+static void mt7925_nan_set_5g_channel(struct mt792x_dev *dev,
+				      struct mt7925_nan_enable_req_tlv *req,
+				      struct cfg80211_nan_conf *conf)
+{
+	struct ieee80211_channel *chan;
+	u32 ch5g = 0;
+
+	chan = conf->band_cfgs[NL80211_BAND_5GHZ].chan;
+
+	if (!chan)
+		return;
+
+	if (!mt7925_regd_is_valid_channel(dev, NL80211_BAND_5GHZ, chan))
+		return;
+
+	req->config_5g_channel = 1;
+
+	if (chan->hw_value == NAN_5G_LOW_DISC_CHANNEL)
+		ch5g |= BIT(0);
+	else if (chan->hw_value == NAN_5G_HIGH_DISC_CHANNEL)
+		ch5g |= BIT(1);
+
+	req->channel_5g_val = cpu_to_le32(ch5g);
+}
+
+static void mt7925_nan_set_cluster_id(struct mt7925_nan_enable_req_tlv *req,
+				      const u8 *cluster_id)
+{
+	if (!cluster_id)
+		return;
+
+	req->cluster_high = cpu_to_le16(cluster_id[4] | cluster_id[5] << 8);
+	req->cluster_low = cpu_to_le16((u16)cluster_id[3]);
+}
+
+static void mt7925_nan_set_dw_interval(struct mt7925_nan_enable_req_tlv *req,
+				       struct cfg80211_nan_conf *conf)
+{
+	if (conf->band_cfgs[NL80211_BAND_2GHZ].awake_dw_interval > 0) {
+		req->config_dw.config_2dot4g_dw_band = 1;
+		req->config_dw.dw_2dot4g_interval_val =
+			cpu_to_le32(conf->band_cfgs[NL80211_BAND_2GHZ].awake_dw_interval);
+	}
+
+	if (conf->band_cfgs[NL80211_BAND_5GHZ].awake_dw_interval > 0) {
+		req->config_dw.config_5g_dw_band = 1;
+		req->config_dw.dw_5g_interval_val =
+			cpu_to_le32(conf->band_cfgs[NL80211_BAND_5GHZ].awake_dw_interval);
+	}
+}
+
+static void mt7925_nan_set_disc_beacon(struct mt7925_nan_enable_req_tlv *req,
+				       struct cfg80211_nan_conf *conf)
+{
+	if (conf->discovery_beacon_interval > 0) {
+		req->config_2dot4g_beacons = true;
+		req->beacon_2dot4g_val = conf->discovery_beacon_interval;
+	}
+}
+
+static void mt7925_nan_set_rssi_thresholds(struct mt7925_nan_enable_req_tlv *req,
+					   struct cfg80211_nan_conf *conf)
+{
+	if (conf->band_cfgs[NL80211_BAND_2GHZ].chan) {
+		req->config_2dot4g_rssi_close = 1;
+		req->rssi_close_2dot4g_val =
+			abs(conf->band_cfgs[NL80211_BAND_2GHZ].rssi_close);
+		req->config_2dot4g_rssi_middle = 1;
+		req->rssi_middle_2dot4g_val =
+			abs(conf->band_cfgs[NL80211_BAND_2GHZ].rssi_middle);
+	}
+
+	if (conf->band_cfgs[NL80211_BAND_5GHZ].chan) {
+		req->config_5g_rssi_close = 1;
+		req->rssi_close_5g_val =
+			abs(conf->band_cfgs[NL80211_BAND_5GHZ].rssi_close);
+		req->config_5g_rssi_middle = 1;
+		req->rssi_middle_5g_val =
+			abs(conf->band_cfgs[NL80211_BAND_5GHZ].rssi_middle);
+	}
+}
+
+static void mt7925_nan_set_scan_params(struct mt7925_nan_enable_req_tlv *req,
+				       struct cfg80211_nan_conf *conf)
+{
+	req->scan_params_val.scan_period[0] =
+		cpu_to_le16(conf->scan_period < 255 ? conf->scan_period : 255);
+	req->scan_params_val.dwell_time[0] =
+		conf->scan_dwell_time < 255 ? conf->scan_dwell_time : 255;
+}
+
+static u16
+mt7925_nan_avail_attr_ctrl(const struct ieee80211_nan_sched_cfg *sched)
+{
+	if (sched->avail_blob_len < NAN_AVAIL_ATTR_CTRL_OFFSET + 2)
+		return 0;
+
+	return sched->avail_blob[NAN_AVAIL_ATTR_CTRL_OFFSET] |
+	       sched->avail_blob[NAN_AVAIL_ATTR_CTRL_OFFSET + 1] << 8;
+}
+
+static void
+mt7925_nan_update_conf(struct mt792x_vif *mvif,
+		       const struct cfg80211_nan_conf *conf)
+{
+	mvif->nan.conf.master_pref = conf->master_pref;
+	mvif->nan.conf.bands = conf->bands;
+	mvif->nan.conf.discovery_beacon_interval =
+		conf->discovery_beacon_interval;
+	mvif->nan.conf.enable_dw_notification =
+		conf->enable_dw_notification;
+
+	memcpy(mvif->nan.conf.cluster_id, conf->cluster_id, ETH_ALEN);
+}
+
+int mt7925_nan_enable(struct ieee80211_vif *vif,
+		      struct mt792x_dev *dev,
+		      struct cfg80211_nan_conf *conf)
+{
+	struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+	struct mt76_dev *mdev = &dev->mt76;
+	struct {
+		u8 rsv[4];
+		struct mt7925_nan_enable_req_tlv nan_req_tlv;
+	} nan_cmd = {
+		.rsv = { 0 },
+		.nan_req_tlv = {
+			.tag = cpu_to_le16(NAN_UNI_CMD_ENABLE_REQUEST),
+			.len = cpu_to_le16(sizeof(struct mt7925_nan_enable_req_tlv)),
+			.config_random_factor_force = 0,
+			.random_factor_force_val = 0,
+			.config_hop_count_force = 0,
+			.hop_count_force_val = 0,
+		},
+	};
+	struct mt7925_nan_enable_req_tlv *p_nan_req_tlv = &nan_cmd.nan_req_tlv;
+
+	if (!vif || !dev || !conf)
+		return -EINVAL;
+
+	p_nan_req_tlv->master_pref = conf->master_pref;
+
+	mt7925_nan_set_5g_channel(dev, p_nan_req_tlv, conf);
+	mt7925_nan_set_cluster_id(p_nan_req_tlv, conf->cluster_id);
+	mt7925_nan_set_dw_interval(p_nan_req_tlv, conf);
+	mt7925_nan_set_disc_beacon(p_nan_req_tlv, conf);
+	mt7925_nan_set_rssi_thresholds(p_nan_req_tlv, conf);
+	mt7925_nan_set_scan_params(p_nan_req_tlv, conf);
+
+	mt7925_nan_update_conf(mvif, conf);
+
+	return mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nan_cmd, sizeof(nan_cmd), true);
+}
+
+int mt7925_nan_disable(struct ieee80211_vif *vif, struct mt792x_dev *dev)
+{
+	struct mt76_dev *mdev = &dev->mt76;
+	struct {
+		u8 rsv[4];
+		struct tlv nan_dis_tlv;
+	} nan_cmd = {
+		.rsv = { 0 },
+		.nan_dis_tlv = {
+			.tag = cpu_to_le16(NAN_UNI_CMD_DISABLE_REQUEST),
+			.len = cpu_to_le16(sizeof(struct tlv)),
+		},
+	};
+
+	if (!dev)
+		return -EINVAL;
+
+	return mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nan_cmd, sizeof(nan_cmd), true);
+}
+
+static int
+mt7925_nan_mp_tlv(struct sk_buff *skb, u8 master_pref)
+{
+	struct mt7925_nan_master_preference_tlv *mp_tlv = NULL;
+	struct tlv *tlv = NULL;
+
+	if (!skb)
+		return -EINVAL;
+
+	tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_SET_MASTER_PREFERENCE,
+				      sizeof(struct mt7925_nan_master_preference_tlv));
+	if (!tlv)
+		return -ENOMEM;
+
+	mp_tlv = (struct mt7925_nan_master_preference_tlv *)tlv;
+
+	if (master_pref > NAN_MAX_MASTER_PREFERENCE)
+		return 0;
+
+	mp_tlv->master_preference = master_pref;
+
+	return 0;
+}
+
+static int
+mt7925_nan_dw_tlv(struct sk_buff *skb, struct cfg80211_nan_conf *conf)
+{
+	struct mt7925_nan_dw_interval_tlv *dw_tlv = NULL;
+	struct tlv *tlv = NULL;
+	u16 interval;
+
+	if (!skb || !conf)
+		return -EINVAL;
+
+	tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_SET_DW_INTERVAL,
+				      sizeof(struct mt7925_nan_dw_interval_tlv));
+
+	if (!tlv)
+		return -ENOMEM;
+
+	dw_tlv = (struct mt7925_nan_dw_interval_tlv *)tlv;
+
+	/* Set DW interval for 2.4GHz and 5GHz bands if available */
+	if (conf->band_cfgs[NL80211_BAND_2GHZ].awake_dw_interval > 0) {
+		dw_tlv->dw_interval = conf->band_cfgs[NL80211_BAND_2GHZ].awake_dw_interval;
+	} else if (conf->band_cfgs[NL80211_BAND_5GHZ].awake_dw_interval > 0) {
+		dw_tlv->dw_interval = conf->band_cfgs[NL80211_BAND_5GHZ].awake_dw_interval;
+	} else {
+		/* Fallback to a default value or log a warning */
+		dw_tlv->dw_interval = NAN_DEFAULT_DW_INTERVAL;
+	}
+
+	/* Validate and set NAN Discovery Beacon Interval */
+	interval = conf->discovery_beacon_interval > 0 ?
+		   conf->discovery_beacon_interval :
+		   NAN_DEFAULT_DISC_BCN_INTERVAL;
+
+	dw_tlv->disc_bcn_interval = cpu_to_le16(interval);
+
+	return 0;
+}
+
+static int
+mt7925_nan_cluster_id_tlv(struct sk_buff *skb, const u8 *cluster_id)
+{
+	struct mt7925_nan_cluster_id_tlv *cluster_tlv = NULL;
+	struct tlv *tlv = NULL;
+
+	if (!skb || !cluster_id)
+		return -EINVAL;
+
+	tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_SET_CLUSTER_ID,
+				      sizeof(struct mt7925_nan_cluster_id_tlv));
+
+	if (!tlv)
+		return -ENOMEM;
+
+	cluster_tlv = (struct mt7925_nan_cluster_id_tlv *)tlv;
+
+	memcpy(cluster_tlv->cluster_id, cluster_id, ETH_ALEN);
+
+	return 0;
+}
+
+static int
+mt7925_nan_sync_rssi_tlv(struct sk_buff *skb, struct cfg80211_nan_conf *conf)
+{
+	struct mt7925_nan_sync_rssi_tlv *rssi_tlv = NULL;
+	struct tlv *tlv = NULL;
+
+	if (!skb || !conf)
+		return -EINVAL;
+
+	tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_SET_SYNC_RSSI,
+				      sizeof(struct mt7925_nan_sync_rssi_tlv));
+
+	if (!tlv)
+		return -ENOMEM;
+
+	rssi_tlv = (struct mt7925_nan_sync_rssi_tlv *)tlv;
+
+	if (conf->band_cfgs[NL80211_BAND_2GHZ].chan) {
+		rssi_tlv->rssi_close_2g =
+			conf->band_cfgs[NL80211_BAND_2GHZ].rssi_close;
+		rssi_tlv->rssi_middle_2g =
+			conf->band_cfgs[NL80211_BAND_2GHZ].rssi_middle;
+	}
+
+	if (conf->band_cfgs[NL80211_BAND_5GHZ].chan) {
+		rssi_tlv->rssi_close_5g =
+			conf->band_cfgs[NL80211_BAND_5GHZ].rssi_close;
+		rssi_tlv->rssi_middle_5g =
+			conf->band_cfgs[NL80211_BAND_5GHZ].rssi_middle;
+	}
+
+	return 0;
+}
+
+int mt7925_nan_change_configure(struct ieee80211_vif *vif,
+				struct mt792x_dev *dev,
+				struct cfg80211_nan_conf *conf)
+{
+	struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+	struct mt7925_nan_common_hdr *hdr = NULL;
+	struct mt76_dev *mdev = &dev->mt76;
+	struct sk_buff *skb = NULL;
+
+	if (!vif || !dev || !conf)
+		return -EINVAL;
+
+	skb = mt76_mcu_msg_alloc(mdev, NULL, MT7925_NAN_CONF_MAX_SIZE);
+	if (!skb)
+		return -ENOMEM;
+
+	hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr));
+	memset(hdr, 0, sizeof(*hdr));
+
+	if (mt7925_nan_mp_tlv(skb, conf->master_pref) ||
+	    mt7925_nan_dw_tlv(skb, conf) ||
+	    mt7925_nan_cluster_id_tlv(skb, conf->cluster_id) ||
+	    mt7925_nan_sync_rssi_tlv(skb, conf)) {
+		dev_kfree_skb(skb);
+		return -ENOMEM;
+	}
+
+	mt7925_nan_update_conf(mvif, conf);
+
+	return mt76_mcu_skb_send_msg(mdev, skb,
+				     MCU_UNI_CMD(NAN), true);
+}
+
+static void
+mt7925_nan_handle_dw_ind(struct mt792x_dev *dev, struct tlv *tlv)
+{
+	struct ieee80211_channel *chan;
+	struct nan_rpt_dw_evt *evt;
+	struct wireless_dev *wdev;
+	u16 len, channel, dw_num;
+	struct mt792x_vif *mvif;
+	enum nl80211_band band;
+	int freq;
+
+	if (!dev || !tlv)
+		return;
+
+	len = le16_to_cpu(tlv->len);
+	if (len < sizeof(*tlv) + sizeof(*evt)) {
+		dev_warn(dev->mt76.dev,
+			 "nan: short dw event tlv len=%u\n", len);
+		return;
+	}
+
+	if (!dev->nan_vif || !ieee80211_vif_nan_started(dev->nan_vif))
+		return;
+
+	wdev = ieee80211_vif_to_wdev(dev->nan_vif);
+	if (!wdev)
+		return;
+
+	mvif = (struct mt792x_vif *)dev->nan_vif->drv_priv;
+	if (!mvif->nan.conf.enable_dw_notification)
+		return;
+
+	evt = (struct nan_rpt_dw_evt *)tlv->data;
+	channel = le16_to_cpu(evt->channel);
+	dw_num = le16_to_cpu(evt->dw_num);
+
+	band = channel > 13 ? NL80211_BAND_5GHZ : NL80211_BAND_2GHZ;
+	freq = ieee80211_channel_to_frequency(channel, band);
+	chan = ieee80211_get_channel(dev->mt76.hw->wiphy, freq);
+	if (!chan) {
+		dev_dbg(dev->mt76.dev,
+			"nan: no channel for dw end event ch=%u dw=%u\n",
+			channel, dw_num);
+		return;
+	}
+
+	cfg80211_next_nan_dw_notif(wdev, chan, GFP_KERNEL);
+}
+
+static void
+mt7925_nan_mcu_handle_de_event(struct mt792x_dev *dev, struct tlv *tlv)
+{
+	u8 cluster_id[ETH_ALEN] __aligned(2) = {0x50, 0x6f, 0x9a, 0x01, 0x00, 0x00};
+	struct mt7925_nan_de_event *de_evt = NULL;
+	u16 len;
+
+	if (!dev || !tlv) {
+		if (dev)
+			dev_warn(dev->mt76.dev, "nan: failed to parse TLV\n");
+		return;
+	}
+
+	len = le16_to_cpu(tlv->len);
+	if (len < sizeof(*tlv) + sizeof(*de_evt)) {
+		dev_warn(dev->mt76.dev,
+			 "nan: short de_event tlv len=%u\n", len);
+		return;
+	}
+
+	de_evt = (struct mt7925_nan_de_event *)tlv->data;
+	if (!de_evt) {
+		dev_warn(dev->mt76.dev, "nan: missing DE event payload\n");
+		return;
+	}
+
+	if (de_evt->event_type == NAN_EVENT_ID_DISC_MAC_ADDR)
+		return;
+
+	memcpy(cluster_id, de_evt->cluster_id, ETH_ALEN);
+
+	dev_dbg(dev->mt76.dev, "nan: evt=%u cluster=%pM\n",
+		de_evt->event_type, de_evt->cluster_id);
+
+	if (de_evt->event_type != NAN_EVENT_ID_JOINED_CLUSTER)
+		return;
+
+	if (!ieee80211_vif_nan_started(dev->nan_vif)) {
+		dev_warn(dev->mt76.dev, "nan: joined-cluster event but NAN not started\n");
+		return;
+	}
+
+	dev_dbg(dev->mt76.dev, "nan: anchor_master_rank=%*phN\n",
+		NAN_ANCHOR_MASTER_RANK_NUM, de_evt->anchor_master_rank);
+
+	dev_dbg(dev->mt76.dev, "nan: own_nmi=%pM master_nmi=%pM\n",
+		de_evt->own_nmi, de_evt->master_nmi);
+
+	ieee80211_nan_cluster_joined(dev->nan_vif, cluster_id, true, GFP_KERNEL);
+}
+
+void mt7925_nan_mcu_event(struct mt792x_dev *dev, struct sk_buff *skb)
+{
+	struct tlv *tlv;
+	u32 tlv_len;
+
+	if (!dev || !skb)
+		return;
+
+	if (skb->len < sizeof(struct mt7925_mcu_rxd) + 4)
+		return;
+
+	skb_pull(skb, sizeof(struct mt7925_mcu_rxd) + 4);
+	tlv = (struct tlv *)skb->data;
+	tlv_len = skb->len;
+
+	while (tlv_len >= sizeof(*tlv)) {
+		u16 len = le16_to_cpu(tlv->len);
+
+		if (len < sizeof(*tlv) || len > tlv_len)
+			break;
+
+		switch (le16_to_cpu(tlv->tag)) {
+		case NAN_UNI_EVENT_ID_DE_EVENT_IND:
+			mt7925_nan_mcu_handle_de_event(dev, tlv);
+			break;
+		case NAN_UNI_EVENT_REPORT_DW_END:
+			mt7925_nan_handle_dw_ind(dev, tlv);
+			break;
+		default:
+			break;
+		}
+
+		tlv_len -= len;
+		tlv = (struct tlv *)((u8 *)tlv + len);
+	}
+}
+
+static int mt7925_nan_avail_ctrl_tlv(struct sk_buff *skb,
+				     struct ieee80211_vif *vif)
+{
+	struct mt7925_nan_avail_ctrl_tlv *avail_ctrl_tlv;
+	struct ieee80211_nan_sched_cfg *sched;
+	struct tlv *tlv;
+	u8 seq_id = 0;
+	u16 ctrl = 0;
+
+	if (!skb || !vif)
+		return -EINVAL;
+
+	tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_UPDATE_AVAILABILITY_CTRL,
+				      sizeof(struct mt7925_nan_avail_ctrl_tlv));
+
+	if (!tlv)
+		return -ENOMEM;
+
+	sched = &vif->cfg.nan_sched;
+
+	ctrl = mt7925_nan_avail_attr_ctrl(sched);
+	if (sched->avail_blob_len >= NAN_AVAIL_ATTR_CTRL_OFFSET + 2)
+		seq_id = sched->avail_blob[NAN_AVAIL_SEQ_ID_OFFSET];
+
+	avail_ctrl_tlv = (struct mt7925_nan_avail_ctrl_tlv *)tlv;
+	avail_ctrl_tlv->avail_ctrl = ctrl & NAN_AVAIL_CTRL_CHECK_FOR_CHANGED;
+	avail_ctrl_tlv->seq_id = seq_id;
+
+	return 0;
+}
+
+static u32 mt7925_nan_slot_to_bitmap(struct ieee80211_vif *vif,
+				     struct mt7925_nan_ch_timeline *ch_list)
+{
+	struct ieee80211_nan_channel **slots = vif->cfg.nan_sched.schedule;
+	struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+	u32 num_channels = 0;
+	u32 i, j;
+
+	for (i = 0; i < ARRAY_SIZE(mvif->nan.local_sched); i++) {
+		struct cfg80211_chan_def *slot_chan = &mvif->nan.local_sched[i];
+		struct ieee80211_nan_channel *slot = slots[i];
+		bool is_found = false;
+
+		if (slot && !IS_ERR(slot) && slot->chanctx_conf) {
+			*slot_chan = slot->chanctx_conf->def;
+		} else {
+			memset(slot_chan, 0, sizeof(*slot_chan));
+			continue;
+		}
+
+		for (j = 0; j < num_channels; j++) {
+			if (ch_list[j].ch_info.primary_ch ==
+			    slot_chan->chan->hw_value) {
+				ch_list[j].avail_map[0] |= BIT(i);
+				ch_list[j].num++;
+				is_found = true;
+				break;
+			}
+		}
+
+		if (!is_found && num_channels < NAN_TIMELINE_MGMT_CHNL_LIST_NUM) {
+			ch_list[num_channels].ch_info.primary_ch =
+				slot_chan->chan->hw_value;
+			ch_list[num_channels].ch_info.op_class =
+				slot->channel_entry[0];
+			ch_list[num_channels].avail_map[0] = BIT(i);
+			ch_list[num_channels].num++;
+			ch_list[num_channels].is_valid++;
+			num_channels++;
+		}
+	}
+
+	return num_channels;
+}
+
+static int mt7925_nan_avail_tlv(struct sk_buff *skb,
+				struct ieee80211_vif *vif)
+{
+	struct mt7925_nan_avail_entry_tlv *avail_tlv;
+	struct ieee80211_nan_sched_cfg *sched;
+	struct tlv *tlv;
+	u16 ctrl = 0;
+
+	if (!skb || !vif)
+		return -EINVAL;
+
+	tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_UPDATE_AVAILABILITY,
+				      sizeof(struct mt7925_nan_avail_entry_tlv));
+
+	if (!tlv)
+		return -ENOMEM;
+
+	sched = &vif->cfg.nan_sched;
+
+	ctrl = mt7925_nan_avail_attr_ctrl(sched);
+
+	avail_tlv = (struct mt7925_nan_avail_entry_tlv *)tlv;
+	avail_tlv->map_id = ctrl & NAN_AVAIL_CTRL_MAPID;
+	avail_tlv->is_cond_avail = false;
+	avail_tlv->timeline_idx = 0;
+
+	mt7925_nan_slot_to_bitmap(vif, avail_tlv->ch_list);
+
+	avail_tlv->is_multi_map = false;
+
+	return 0;
+}
+
+void mt7925_nan_local_sched_changed(struct mt792x_dev *dev,
+				    struct ieee80211_vif *vif)
+{
+	struct mt7925_nan_common_hdr *hdr;
+	struct mt76_dev *mdev;
+	struct sk_buff *skb;
+
+	if (!dev || !vif)
+		return;
+
+	mdev = &dev->mt76;
+
+	skb = mt76_mcu_msg_alloc(mdev, NULL, MT7925_NAN_AVAIL_MAX_SIZE);
+	if (!skb)
+		return;
+
+	hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr));
+	memset(hdr, 0, sizeof(*hdr));
+
+	if (mt7925_nan_avail_ctrl_tlv(skb, vif) ||
+	    mt7925_nan_avail_tlv(skb, vif)) {
+		dev_kfree_skb(skb);
+		return;
+	}
+
+	mt76_mcu_skb_send_msg(mdev, skb,
+			      MCU_UNI_CMD(NAN), true);
+}
+
+static int mt7925_nan_peer_rec_tlv(struct sk_buff *skb,
+				   struct ieee80211_sta *sta,
+				   struct mt792x_sta *msta,
+				   u8 is_activate)
+{
+	struct mt7925_nan_sched_manage_peer_rec_tlv *peer_rec_tlv;
+	struct tlv *tlv;
+
+	if (!skb || !sta || !msta)
+		return -EINVAL;
+
+	tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_MANAGE_PEER_SCH_RECORD,
+				      sizeof(struct mt7925_nan_sched_manage_peer_rec_tlv));
+
+	if (!tlv)
+		return -ENOMEM;
+
+	peer_rec_tlv = (struct mt7925_nan_sched_manage_peer_rec_tlv *)tlv;
+	peer_rec_tlv->sch_idx = msta->nan_sched.sch_idx;
+	peer_rec_tlv->is_activate = is_activate;
+	memcpy(peer_rec_tlv->nmi_addr, sta->addr, ETH_ALEN);
+
+	return 0;
+}
+
+static int mt7925_nan_peer_cap_tlv(struct sk_buff *skb,
+				   struct ieee80211_sta *sta,
+				   struct mt792x_sta *msta)
+{
+	struct mt7925_nan_sched_update_peer_cap_tlv *peer_cap_tlv;
+	struct ieee80211_nan_peer_sched *sched;
+	enum nl80211_band band;
+	struct tlv *tlv;
+	u16 primary_ch;
+	u32 i;
+
+	if (!skb || !sta || !msta)
+		return -EINVAL;
+
+	sched = sta->nan_sched;
+	if (!sched)
+		return -EINVAL;
+
+	tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_UPDATE_PEER_CAPABILITY,
+				      sizeof(struct mt7925_nan_sched_update_peer_cap_tlv));
+
+	if (!tlv)
+		return -ENOMEM;
+
+	peer_cap_tlv = (struct mt7925_nan_sched_update_peer_cap_tlv *)tlv;
+	peer_cap_tlv->sch_idx = msta->nan_sched.sch_idx;
+	peer_cap_tlv->supported_bands = BIT(NAN_SUPPORTED_BAND_ID_2P4G);
+	peer_cap_tlv->max_chnl_switch_time = sched->max_chan_switch;
+
+	for (i = 0; i < sched->n_channels; i++) {
+		if (!sched->channels[i].chanctx_conf)
+			continue;
+
+		band = sched->channels[i].chanctx_conf->def.chan->band;
+		primary_ch =
+			sched->channels[i].chanctx_conf->def.chan->hw_value;
+
+		if (band == NL80211_BAND_2GHZ)
+			peer_cap_tlv->peer_supported_bands |=
+				BIT(NAN_SUPPORTED_BN_2G);
+		else if (primary_ch >= UNII1_LOWER_BOUND &&
+			 primary_ch <= UNII1_UPPER_BOUND)
+			peer_cap_tlv->peer_supported_bands |=
+				BIT(NAN_SUPPORTED_BN_5G_LOW);
+		else if (primary_ch >= UNII3_LOWER_BOUND &&
+			 primary_ch <= UNII3_UPPER_BOUND)
+			peer_cap_tlv->peer_supported_bands |=
+				BIT(NAN_SUPPORTED_BN_5G_HIGH);
+	}
+
+	return 0;
+}
+
+static void
+mt7925_nan_fill_crb_committed(struct mt7925_nan_sched_update_crb_tlv *crb_tlv,
+			      struct ieee80211_nan_peer_sched *sched)
+{
+	u32 m, slot;
+
+	if (!sched)
+		return;
+
+	for (m = 0; m < CFG80211_NAN_MAX_PEER_MAPS &&
+	     m < NAN_TIMELINE_MGMT_SIZE; m++) {
+		struct mt7925_nan_sched_timeline *tl =
+			&crb_tlv->comm_faw_timeline[m];
+		struct ieee80211_nan_peer_map *map = &sched->maps[m];
+
+		if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
+			continue;
+
+		tl->map_id = map->map_id;
+
+		/*
+		 * Convert peer schedule slots to FW avail_map bitmap.
+		 * Each bit in avail_map[0] represents one time slot where
+		 * the peer has committed availability.
+		 */
+		for (slot = 0; slot < CFG80211_NAN_SCHED_NUM_TIME_SLOTS;
+		     slot++) {
+			struct ieee80211_nan_channel *ch = map->slots[slot];
+
+			if (!ch || !ch->chanctx_conf)
+				continue;
+
+			tl->avail_map[0] |= cpu_to_le32(BIT(slot));
+		}
+	}
+}
+
+static int mt7925_nan_update_crb_tlv(struct sk_buff *skb,
+				     struct ieee80211_sta *sta,
+				     struct mt792x_sta *msta)
+{
+	struct mt7925_nan_sched_update_crb_tlv *crb_tlv;
+	struct tlv *tlv;
+
+	if (!skb || !sta || !msta)
+		return -EINVAL;
+
+	tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_UPDATE_CRB,
+				      sizeof(struct mt7925_nan_sched_update_crb_tlv));
+
+	if (!tlv)
+		return -ENOMEM;
+
+	crb_tlv = (struct mt7925_nan_sched_update_crb_tlv *)tlv;
+	crb_tlv->sch_idx = msta->nan_sched.sch_idx;
+	crb_tlv->is_use_data_path = true;
+	crb_tlv->is_use_ranging = false;
+	crb_tlv->comm_ndc_ctrl.is_valid = false;
+
+	mt7925_nan_fill_crb_committed(crb_tlv, sta->nan_sched);
+
+	return 0;
+}
+
+int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev,
+				 struct ieee80211_sta *sta)
+{
+	struct mt7925_nan_common_hdr *hdr;
+	struct mt792x_sta *msta;
+	struct mt792x_nan *nan;
+	struct mt76_dev *mdev;
+	struct sk_buff *skb;
+
+	if (!dev || !sta)
+		return -EINVAL;
+
+	mdev = &dev->mt76;
+
+	skb = mt76_mcu_msg_alloc(mdev, NULL, MT7925_NAN_PEER_MAX_SIZE);
+	if (!skb)
+		return -ENOMEM;
+
+	hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr));
+	memset(hdr, 0, sizeof(*hdr));
+
+	msta = (struct mt792x_sta *)sta->drv_priv;
+	nan = &msta->vif->nan;
+
+	/* Allocate connection index on first call for this peer */
+	if (!msta->nan_sched.idx_assigned) {
+		int idx = find_first_zero_bit(&nan->conn_bitmap,
+					      NAN_MAX_CONN_CFG);
+		if (idx >= NAN_MAX_CONN_CFG) {
+			dev_kfree_skb(skb);
+			return -ENOSPC;
+		}
+
+		set_bit(idx, &nan->conn_bitmap);
+		msta->nan_sched.sch_idx = idx;
+		msta->nan_sched.idx_assigned = true;
+
+		if (mt7925_nan_peer_rec_tlv(skb, sta, msta, true) ||
+		    mt7925_nan_peer_cap_tlv(skb, sta, msta)) {
+			dev_kfree_skb(skb);
+			return -ENOMEM;
+		}
+	}
+
+	if (mt7925_nan_update_crb_tlv(skb, sta, msta)) {
+		dev_kfree_skb(skb);
+		return -ENOMEM;
+	}
+
+	return mt76_mcu_skb_send_msg(mdev, skb,
+				     MCU_UNI_CMD(NAN), true);
+}
+
+int mt792x_nan_set_peer_rec(struct mt76_dev *mdev,
+			    struct ieee80211_sta *sta)
+{
+	struct mt7925_nan_common_hdr *hdr;
+	struct mt792x_sta *msta;
+	struct mt792x_nan *nan;
+	struct sk_buff *skb;
+
+	if (!mdev || !sta)
+		return -EINVAL;
+
+	skb = mt76_mcu_msg_alloc(mdev, NULL,
+				 sizeof(struct mt7925_nan_common_hdr) +
+				 sizeof(struct mt7925_nan_sched_manage_peer_rec_tlv));
+	if (!skb)
+		return -ENOMEM;
+
+	hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr));
+	memset(hdr, 0, sizeof(*hdr));
+
+	msta = (struct mt792x_sta *)sta->drv_priv;
+	nan = &msta->vif->nan;
+
+	if (!msta->nan_sched.idx_assigned) {
+		dev_kfree_skb(skb);
+		return 0;
+	}
+
+	if (mt7925_nan_peer_rec_tlv(skb, sta, msta, false)) {
+		dev_kfree_skb(skb);
+		return -ENOMEM;
+	}
+
+	clear_bit(msta->nan_sched.sch_idx, &nan->conn_bitmap);
+	msta->nan_sched.idx_assigned = false;
+
+	return mt76_mcu_skb_send_msg(mdev, skb,
+				     MCU_UNI_CMD(NAN), true);
+}
+
+int mt792x_nan_map_sta_rec(struct mt76_dev *mdev,
+			   struct ieee80211_vif *vif,
+			   struct ieee80211_sta *sta)
+{
+	struct mt7925_nan_sched_map_sta_rec_tlv *map_tlv;
+	struct mt7925_nan_common_hdr *hdr;
+	struct ieee80211_sta *nmi_sta;
+	struct mt792x_sta *nmi_msta;
+	struct mt792x_sta *msta;
+	u8 nmi_addr[ETH_ALEN];
+	struct sk_buff *skb;
+	int ndp_ctx_id = 0;
+	struct tlv *tlv;
+
+	if (!mdev || !vif || !sta)
+		return -EINVAL;
+
+	msta = (struct mt792x_sta *)sta->drv_priv;
+
+	rcu_read_lock();
+	nmi_sta = rcu_dereference(sta->nmi);
+	if (!nmi_sta) {
+		rcu_read_unlock();
+		dev_err(mdev->dev, "NAN: NMI sta not found for NDI sta %pM\n",
+			sta->addr);
+		return -EINVAL;
+	}
+
+	memcpy(nmi_addr, nmi_sta->addr, ETH_ALEN);
+	nmi_msta = (struct mt792x_sta *)nmi_sta->drv_priv;
+
+	ndp_ctx_id = find_first_zero_bit(&nmi_msta->nan_sched.ndp_ctx_bitmap,
+					 NAN_MAX_NDP_CXT);
+	if (ndp_ctx_id < NAN_MAX_NDP_CXT)
+		set_bit(ndp_ctx_id, &nmi_msta->nan_sched.ndp_ctx_bitmap);
+	else
+		ndp_ctx_id = 0;
+	rcu_read_unlock();
+
+	msta->nan_sched.ndp_ctx_id = ndp_ctx_id;
+
+	skb = mt76_mcu_msg_alloc(mdev, NULL,
+				 sizeof(struct mt7925_nan_common_hdr) +
+				 sizeof(struct mt7925_nan_sched_map_sta_rec_tlv));
+	if (!skb)
+		return -ENOMEM;
+
+	hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr));
+	memset(hdr, 0, sizeof(*hdr));
+
+	tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_MAP_STA_RECORD,
+				      sizeof(struct mt7925_nan_sched_map_sta_rec_tlv));
+	if (!tlv) {
+		dev_kfree_skb(skb);
+		return -ENOMEM;
+	}
+
+	map_tlv = (struct mt7925_nan_sched_map_sta_rec_tlv *)tlv;
+	memcpy(map_tlv->nmi_addr, nmi_addr, ETH_ALEN);
+	map_tlv->sta_rec_idx = msta->deflink.wcid.idx;
+	map_tlv->ndp_ctx_id = ndp_ctx_id;
+	map_tlv->role_idx = 0;
+	memcpy(map_tlv->ndi_addr, vif->addr, ETH_ALEN);
+
+	return mt76_mcu_skb_send_msg(mdev, skb,
+				     MCU_UNI_CMD(NAN), true);
+}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
new file mode 100644
index 000000000000..1895d0be8ee4
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
@@ -0,0 +1,438 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/* Copyright (C) 2025-2026 MediaTek Inc. */
+
+#ifndef __MT7925_NAN_H
+#define __MT7925_NAN_H
+
+#include <linux/if_ether.h>
+#include <linux/types.h>
+
+#include "../mt76_connac_mcu.h"
+
+#define NAN_MAX_SOCIAL_CHANNELS		3
+#define NAN_ANCHOR_MASTER_RANK_NUM	8
+#define NAN_5G_LOW_DISC_CHANNEL		44
+#define NAN_5G_HIGH_DISC_CHANNEL	149
+#define NAN_MAX_MASTER_PREFERENCE	255
+#define NAN_DEFAULT_DW_INTERVAL		1
+#define NAN_DEFAULT_DISC_BCN_INTERVAL	100
+#define NAN_TOTAL_DW			16
+#define NAN_SUPPORTED_2G_FAW_CH_NUM	4
+#define NAN_SUPPORTED_5G_FAW_CH_NUM	4
+#define NAN_TIMELINE_MGMT_SIZE		2
+#define NAN_TIMELINE_MGMT_CHNL_LIST_NUM				\
+	((NAN_SUPPORTED_2G_FAW_CH_NUM +				\
+	  NAN_SUPPORTED_5G_FAW_CH_NUM) / NAN_TIMELINE_MGMT_SIZE)
+#define NAN_NUM_AVAIL_DB		2
+#define NAN_NDC_ATTRIBUTE_ID_LENGTH	6
+#define NAN_MAX_CONN_CFG		8
+#define NAN_MAX_NDP_CXT			4
+
+#define MT7925_NAN_CONF_MAX_SIZE					\
+	(sizeof(struct mt7925_nan_common_hdr) +				\
+	 sizeof(struct mt7925_nan_master_preference_tlv) +		\
+	 sizeof(struct mt7925_nan_dw_interval_tlv) +			\
+	 sizeof(struct mt7925_nan_cluster_id_tlv) +			\
+	 sizeof(struct mt7925_nan_sync_rssi_tlv))
+
+#define MT7925_NAN_AVAIL_MAX_SIZE					\
+	(sizeof(struct mt7925_nan_common_hdr) +				\
+	 sizeof(struct mt7925_nan_avail_ctrl_tlv) +			\
+	 sizeof(struct mt7925_nan_avail_entry_tlv))
+
+#define MT7925_NAN_PEER_MAX_SIZE					\
+	(sizeof(struct mt7925_nan_common_hdr) +				\
+	 sizeof(struct mt7925_nan_sched_manage_peer_rec_tlv) +		\
+	 sizeof(struct mt7925_nan_sched_update_peer_cap_tlv) +		\
+	 sizeof(struct mt7925_nan_sched_update_crb_tlv))
+
+/* NAN Availability Attribute */
+#define NAN_AVAIL_ATTR_ID_OFFSET	0
+#define NAN_AVAIL_ATTR_LEN_OFFSET	1
+#define NAN_AVAIL_SEQ_ID_OFFSET		3
+#define NAN_AVAIL_ATTR_CTRL_OFFSET	4
+
+/* NAN Availability Attribute - Attribute Control Field */
+#define NAN_AVAIL_CTRL_MAPID			GENMASK(3, 0)
+#define NAN_AVAIL_CTRL_COMMIT_CHANGED		BIT(4)
+#define NAN_AVAIL_CTRL_POTN_CHANGED		BIT(5)
+#define NAN_AVAIL_CTRL_PUBLIC_AVAIL_CHANGED	BIT(6)
+#define NAN_AVAIL_CTRL_NDC_CHANGED		BIT(7)
+#define NAN_AVAIL_CTRL_CHECK_FOR_CHANGED	GENMASK(7, 4)
+
+#define UNII1_LOWER_BOUND	36
+#define UNII1_UPPER_BOUND	50
+#define UNII3_LOWER_BOUND	149
+#define UNII3_UPPER_BOUND	165
+
+enum nan_uni_cmd_tag {
+	NAN_UNI_CMD_SET_MASTER_PREFERENCE	= 0,
+	NAN_UNI_CMD_ENABLE_REQUEST		= 7,
+	NAN_UNI_CMD_DISABLE_REQUEST		= 8,
+	NAN_UNI_CMD_UPDATE_AVAILABILITY		= 9,
+	NAN_UNI_CMD_UPDATE_CRB			= 10,
+	NAN_UNI_CMD_MANAGE_PEER_SCH_RECORD	= 12,
+	NAN_UNI_CMD_MAP_STA_RECORD		= 13,
+	NAN_UNI_CMD_UPDATE_AVAILABILITY_CTRL	= 20,
+	NAN_UNI_CMD_UPDATE_PEER_CAPABILITY	= 21,
+	NAN_UNI_CMD_CHANGE_NMI_ADDRESS		= 24,
+	NAN_UNI_CMD_SET_DW_INTERVAL		= 26,
+	NAN_UNI_CMD_SET_SYNC_RSSI		= 39,
+	NAN_UNI_CMD_SET_CLUSTER_ID		= 40,
+	NAN_UNI_CMD_KEY_MANAGEMENT		= 53,
+};
+
+enum nan_uni_event_tag {
+	NAN_UNI_EVENT_ID_DE_EVENT_IND		= 19,
+	NAN_UNI_EVENT_REPORT_DW_END		= 60,
+};
+
+enum nan_disc_event_type {
+	NAN_EVENT_ID_DISC_MAC_ADDR		= 0,
+	NAN_EVENT_ID_JOINED_CLUSTER		= 2,
+};
+
+/* NAN 4.0 Table 79. Device Capability attribute format, Supported Bands */
+enum nan_supported_bands {
+	NAN_SUPPORTED_BAND_ID_2P4G = 2,
+	NAN_SUPPORTED_BAND_ID_5G = 4,
+	NAN_PROPRIETARY_BAND_ID_6G = 6,
+	NAN_SUPPORTED_BAND_ID_6G = 7,
+};
+
+enum nan_peer_supported_bands {
+	NAN_SUPPORTED_BN_2G = 0,
+	NAN_SUPPORTED_BN_5G_LOW,
+	NAN_SUPPORTED_BN_5G_HIGH,
+	NAN_SUPPORTED_BN_6G,
+	NAN_SUPPORTED_BN_NUM
+};
+
+union nan_band_ch_ctrl {
+	struct {
+		__le32 type : 1;
+		__le32 reserved : 31;
+	};
+
+	struct {
+		__le32 band_type : 1;
+		__le32 band_rsvd : 23;
+		__le32 band_id_mask : 8;
+	};
+
+	struct {
+		__le32 ch_type : 1;
+		__le32 ch_rsvd : 7;
+		__le32 op_class : 8;
+		__le32 primary_ch : 8;
+		__le32 aux_center_ch : 8;
+	};
+
+	__le32 raw_data;
+};
+
+struct mt7925_nan_social_ch_scan_params {
+	u8 dwell_time[NAN_MAX_SOCIAL_CHANNELS];
+	__le16 scan_period[NAN_MAX_SOCIAL_CHANNELS];
+} __packed;
+
+/* Firmware-reported NAN device information */
+struct nan_dev_info_evt {
+	u8 is_enabled;
+	u8 my_addr[ETH_ALEN];
+	u8 en_fw_election;
+	__le32 nan_dev_role;
+	__le32 nan_dev_state;
+	u8 mst_preference;
+	u8 random_factor;
+	u8 cnt_hop;
+	u8 cluster_id[ETH_ALEN];
+	u8 anchor_mst_addr[ETH_ALEN];
+	u8 am_preference;
+	u8 am_random_factor;
+	u8 parent_mac[ETH_ALEN];
+	u8 parent_am_preference;
+	u8 parent_am_factor;
+	__le32 ambtt;
+	__le32 tsf[2];
+	u8 pn_igtk[6];
+	u8 pn_bigtk[6];
+};
+
+/* Firmware NAN discovery window event */
+struct nan_rpt_dw_evt {
+	struct nan_dev_info_evt device_info;
+	__le32 expected_tsf_h;
+	__le32 expected_tsf_l;
+	__le32 actual_tsf_h;
+	__le32 actual_tsf_l;
+	__le16 channel;
+	__le16 dw_num;
+};
+
+struct mt7925_nan_conf_dw {
+	u8 config_2dot4g_dw_band;
+	__le32 dw_2dot4g_interval_val;
+
+	u8 config_5g_dw_band;
+	__le32 dw_5g_interval_val;
+} __packed;
+
+struct mt7925_nan_enable_req_tlv {
+	__le16 tag;
+	__le16 len;
+
+	u8 master_pref;
+	__le16 cluster_low;
+	__le16 cluster_high;
+
+	u8 config_support_5g;
+	u8 support_5g_val;
+
+	u8 config_sid_beacon;
+	u8 sid_beacon_val;
+
+	u8 config_2dot4g_rssi_close;
+	u8 rssi_close_2dot4g_val;
+	u8 config_2dot4g_rssi_middle;
+	u8 rssi_middle_2dot4g_val;
+
+	u8 config_2dot4g_rssi_proximity;
+	u8 rssi_proximity_2dot4g_val;
+	u8 config_hop_count_limit;
+	u8 hop_count_limit_val;
+
+	u8 config_2dot4g_support;
+	u8 support_2dot4g_val;
+
+	u8 config_2dot4g_beacons;
+	u8 beacon_2dot4g_val;
+
+	u8 config_2dot4g_sdf;
+	u8 sdf_2dot4g_val;
+
+	u8 config_5g_beacons;
+	u8 beacon_5g_val;
+
+	u8 config_5g_sdf;
+	u8 sdf_5g_val;
+
+	u8 config_5g_rssi_close;
+	u8 rssi_close_5g_val;
+
+	u8 config_5g_rssi_middle;
+	u8 rssi_middle_5g_val;
+
+	u8 config_5g_rssi_close_proximity;
+	u8 rssi_close_proximity_5g_val;
+
+	u8 config_rssi_window_size;
+	u8 rssi_window_size_val;
+
+	u8 config_oui;
+	__le32 oui_val;
+
+	u8 config_intf_addr;
+	u8 intf_addr_val[ETH_ALEN];
+
+	u8 config_cluster_attribute_val;
+
+	u8 config_scan_params;
+	struct mt7925_nan_social_ch_scan_params scan_params_val;
+
+	u8 config_random_factor_force;
+	u8 random_factor_force_val;
+
+	u8 config_hop_count_force;
+	u8 hop_count_force_val;
+
+	u8 config_24g_channel;
+	__le32 channel_24g_val;
+
+	u8 config_5g_channel;
+	__le32 channel_5g_val;
+
+	struct mt7925_nan_conf_dw config_dw;
+
+	u8 config_disc_mac_addr_randomization;
+	__le32 disc_mac_addr_rand_interval_sec;
+
+	u8 discovery_indication_cfg;
+
+	u8 config_subscribe_sid_beacon;
+	__le32 subscribe_sid_beacon_val;
+
+	u8 enable_log_slot_statistics;
+} __packed __aligned(4);
+
+struct mt7925_nan_common_hdr {
+	u8 reserved[4];
+};
+
+struct mt7925_nan_master_preference_tlv {
+	__le16 tag;
+	__le16 len;
+	u8 master_preference;
+	u8 reserved[3];
+} __packed __aligned(4);
+
+struct mt7925_nan_dw_interval_tlv {
+	__le16 tag;
+	__le16 len;
+	u8 dw_interval;
+	u8 vendor_ioctl;
+	__le16 disc_bcn_interval;
+} __packed __aligned(4);
+
+struct mt7925_nan_cluster_id_tlv {
+	__le16 tag;
+	__le16 len;
+	u8 cluster_id[ETH_ALEN];
+	u8 reserved[2];
+} __packed __aligned(4);
+
+struct mt7925_nan_sync_rssi_tlv {
+	__le16 tag;
+	__le16 len;
+	s8 rssi_close_2g;
+	s8 rssi_middle_2g;
+	s8 rssi_close_5g;
+	s8 rssi_middle_5g;
+} __packed __aligned(4);
+
+struct mt7925_nan_de_event {
+	u8 event_type;
+	u8 cluster_id[ETH_ALEN];
+	u8 anchor_master_rank[NAN_ANCHOR_MASTER_RANK_NUM];
+	u8 own_nmi[ETH_ALEN];
+	u8 master_nmi[ETH_ALEN];
+};
+
+struct mt7925_nan_nmi_addr_tlv {
+	__le16 tag;
+	__le16 len;
+	u8 nmi_addr[ETH_ALEN];
+} __packed __aligned(4);
+
+struct mt7925_nan_avail_ctrl_tlv {
+	__le16 tag;
+	__le16 len;
+	__le16 avail_ctrl;
+	u8 seq_id;
+	u8 reserved[1];
+} __packed __aligned(4);
+
+struct mt7925_nan_ch_timeline {
+	u8 is_valid;
+	u8 reserved[3];
+
+	union nan_band_ch_ctrl ch_info;
+
+	__le32 num;
+	__le32 avail_map[NAN_TOTAL_DW];
+};
+
+struct mt7925_nan_avail_entry_tlv {
+	__le16 tag;
+	__le16 len;
+	u8 map_id;
+	u8 is_cond_avail;
+	u8 timeline_idx;
+	u8 is_multi_map;
+
+	struct mt7925_nan_ch_timeline ch_list[NAN_TIMELINE_MGMT_CHNL_LIST_NUM];
+} __packed __aligned(4);
+
+struct mt7925_nan_sched_manage_peer_rec_tlv {
+	__le16 tag;
+	__le16 len;
+	__le32 sch_idx;
+	u8 is_activate;
+	u8 nmi_addr[ETH_ALEN];
+	u8 reserved[1];
+} __packed __aligned(4);
+
+struct mt7925_nan_sched_update_peer_cap_tlv {
+	__le16 tag;
+	__le16 len;
+	__le32 sch_idx;
+	u8 supported_bands;
+	__le16 max_chnl_switch_time;
+	u8 peer_supported_bands;
+} __packed __aligned(4);
+
+struct mt7925_nan_sched_timeline {
+	u8 map_id;
+	u8 local_map_id;
+	u8 reserved[2];
+	union {
+		__le32 avail_map[NAN_TOTAL_DW];
+		u8 avail_block[NAN_TOTAL_DW * 4];
+	};
+};
+
+struct mt7925_nan_sched_faw_ndc_timeline {
+	__le32 avail_map[NAN_TOTAL_DW];
+};
+
+struct mt7925_nan_sched_ndc_ctrl {
+	u8 is_valid;
+	u8 ndc_id[NAN_NDC_ATTRIBUTE_ID_LENGTH];
+	u8 ndc_idx;
+	struct mt7925_nan_sched_timeline timeline[NAN_NUM_AVAIL_DB];
+};
+
+struct mt7925_nan_sched_update_crb_tlv {
+	__le16 tag;
+	__le16 len;
+	__le32 sch_idx;
+	u8 is_use_data_path : 1;
+	u8 avail_6g_format : 2;
+	u8 rsvd : 5;
+	u8 is_use_ranging;
+	u8 reserved[2];
+	struct mt7925_nan_sched_timeline comm_ranging_timeline[NAN_TIMELINE_MGMT_SIZE];
+	struct mt7925_nan_sched_timeline comm_faw_timeline[NAN_TIMELINE_MGMT_SIZE];
+	struct mt7925_nan_sched_ndc_ctrl comm_ndc_ctrl;
+	struct mt7925_nan_sched_faw_ndc_timeline faw_ndc_timeline[NAN_TIMELINE_MGMT_SIZE];
+} __packed __aligned(4);
+
+struct mt7925_nan_sched_map_sta_rec_tlv {
+	__le16 tag;
+	__le16 len;
+	u8 nmi_addr[ETH_ALEN];
+	u8 sta_rec_idx;
+	u8 ndp_ctx_id;
+
+	__le32 role_idx;
+	u8 ndi_addr[ETH_ALEN];
+	u8 reserved[2];
+} __packed __aligned(4);
+
+int mt7925_nan_enable(struct ieee80211_vif *vif,
+		      struct mt792x_dev *dev,
+		      struct cfg80211_nan_conf *conf);
+
+int mt7925_nan_disable(struct ieee80211_vif *vif,
+		       struct mt792x_dev *dev);
+
+int mt7925_nan_change_configure(struct ieee80211_vif *vif,
+				struct mt792x_dev *dev,
+				struct cfg80211_nan_conf *conf);
+
+void mt7925_nan_mcu_event(struct mt792x_dev *dev, struct sk_buff *skb);
+
+void mt7925_nan_local_sched_changed(struct mt792x_dev *dev,
+				    struct ieee80211_vif *vif);
+
+int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev,
+				 struct ieee80211_sta *sta);
+
+int mt792x_nan_set_peer_rec(struct mt76_dev *mdev,
+			    struct ieee80211_sta *sta);
+
+int mt792x_nan_map_sta_rec(struct mt76_dev *mdev,
+			   struct ieee80211_vif *vif,
+			   struct ieee80211_sta *sta);
+
+#endif
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c
index 16f56ee879d4..0235437d11d5 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c
@@ -217,6 +217,36 @@ mt7925_regd_is_valid_alpha2(const char *alpha2)
 	return false;
 }
 
+bool
+mt7925_regd_is_valid_channel(struct mt792x_dev *dev,
+			     enum nl80211_band band,
+			     struct ieee80211_channel *chan)
+{
+	struct ieee80211_hw *hw = mt76_hw(dev);
+	struct wiphy *wiphy = hw->wiphy;
+	struct ieee80211_supported_band *sband;
+	struct ieee80211_channel *ch;
+	int i;
+
+	if (!chan)
+		return false;
+
+	sband = wiphy->bands[band];
+	if (!sband)
+		return false;
+
+	for (i = 0; i < sband->n_channels; i++) {
+		ch = &sband->channels[i];
+
+		if (ch->hw_value == chan->hw_value &&
+		    ((ch->flags & IEEE80211_CHAN_DISABLED) == 0))
+			return true;
+	}
+
+	return false;
+}
+EXPORT_SYMBOL_GPL(mt7925_regd_is_valid_channel);
+
 int mt7925_regd_change(struct mt792x_phy *phy, char *alpha2)
 {
 	struct wiphy *wiphy = phy->mt76->hw->wiphy;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.h b/drivers/net/wireless/mediatek/mt76/mt7925/regd.h
index 0767f078862e..0b0754cf8ae7 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.h
@@ -13,6 +13,9 @@ void mt7925_regd_be_ctrl(struct mt792x_dev *dev, u8 *alpha2);
 void mt7925_regd_notifier(struct wiphy *wiphy, struct regulatory_request *req);
 bool mt7925_regd_clc_supported(struct mt792x_dev *dev);
 int mt7925_regd_change(struct mt792x_phy *phy, char *alpha2);
+bool mt7925_regd_is_valid_channel(struct mt792x_dev *dev,
+				  enum nl80211_band band,
+				  struct ieee80211_channel *chan);
 int mt7925_regd_init(struct mt792x_phy *phy);
 
 #endif
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 70073b43af54..89c3f84a776a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -115,6 +115,18 @@ struct mt792x_link_sta {
 	struct ieee80211_link_sta *pri_link;
 };
 
+struct mt792x_sta_nan_sched {
+	u16 committed_dw;
+	u32 sch_idx;
+	bool idx_assigned;
+	unsigned long ndp_ctx_bitmap;
+	u8 ndp_ctx_id;		/* assigned NDP context ID (for NDI sta) */
+	struct {
+		u8 map_id;
+		struct cfg80211_chan_def chans[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
+	} maps[CFG80211_NAN_MAX_PEER_MAPS];
+};
+
 struct mt792x_sta {
 	struct mt792x_link_sta deflink; /* must be first */
 	struct mt792x_link_sta __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
@@ -123,6 +135,9 @@ struct mt792x_sta {
 
 	u16 valid_links;
 	u8 deflink_id;
+
+	/* NAN peer schedule */
+	struct mt792x_sta_nan_sched nan_sched;
 };
 
 DECLARE_EWMA(rssi, 10, 8);
@@ -139,6 +154,25 @@ struct mt792x_bss_conf {
 	unsigned int link_id;
 };
 
+struct mt792x_nan_conf {
+	u8 master_pref;
+	u8 bands;
+	u8 cluster_id[ETH_ALEN];
+	u32 discovery_beacon_interval;
+	bool enable_dw_notification;
+};
+
+struct mt792x_nan {
+	struct mt792x_nan_conf conf;
+
+	/* Scheduler */
+	struct cfg80211_chan_def local_sched[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
+	u32 seq_id;
+
+	/* Connection index bitmap, up to NAN_MAX_CONN_CFG peers */
+	unsigned long conn_bitmap;
+};
+
 struct mt792x_vif {
 	struct mt792x_bss_conf bss_conf; /* must be first */
 	struct mt792x_bss_conf __rcu *link_conf[IEEE80211_MLD_MAX_NUM_LINKS];
@@ -153,6 +187,8 @@ struct mt792x_vif {
 
 	struct work_struct csa_work;
 	struct timer_list csa_timer;
+
+	struct mt792x_nan nan;
 };
 
 struct mt792x_phy {
@@ -283,6 +319,8 @@ struct mt792x_dev {
 	u32 backup_l2;
 
 	struct ieee80211_chanctx_conf *new_ctx;
+
+	struct ieee80211_vif *nan_vif;
 };
 
 static inline struct mt792x_bss_conf *
-- 
2.43.0


  parent reply	other threads:[~2026-06-25  0:19 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-25  0:18 [PATCH v2 0/9] wifi: mt76: add mt7925 NAN support Sean Wang
2026-06-25  0:18 ` [PATCH v2 1/9] wifi: mt76: mt792x: advertise mgmt frame registration Sean Wang
2026-06-25  0:18 ` [PATCH v2 2/9] wifi: mt76: mt7925: guard BSS capability lookups Sean Wang
2026-06-25  0:18 ` [PATCH v2 3/9] wifi: mt76: connac: add NAN connection type Sean Wang
2026-06-25  0:18 ` Sean Wang [this message]
2026-06-25  0:18 ` [PATCH v2 5/9] wifi: mt76: mt7925: add NAN MCU handling Sean Wang
2026-06-25  0:18 ` [PATCH v2 6/9] wifi: mt76: add init_wiphy callback Sean Wang
2026-06-25  0:18 ` [PATCH v2 7/9] wifi: mt76: mt7925: wire up NAN operations Sean Wang
2026-06-25  0:18 ` [PATCH v2 8/9] wifi: mt76: mt792x: build iface combinations dynamically Sean Wang
2026-06-25  0:18 ` [PATCH v2 9/9] wifi: mt76: mt792x: advertise NAN data support Sean Wang

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=20260625001834.475094-5-sean.wang@kernel.org \
    --to=sean.wang@kernel.org \
    --cc=chengwei.yu@mediatek.com \
    --cc=jenhao.yang@mediatek.com \
    --cc=linux-mediatek@lists.infradead.org \
    --cc=linux-wireless@vger.kernel.org \
    --cc=lorenzo@kernel.org \
    --cc=nbd@nbd.name \
    --cc=posh.sun@mediatek.com \
    --cc=sean.wang@mediatek.com \
    --cc=yu-ching.liu@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