* [RFC PATCH 1/3] dt-bindings: pinctrl: mt8516/mt8167: Move compatibles from mt66xx to mt6795
From: Luca Leonardo Scorcia @ 2026-06-25 10:46 UTC (permalink / raw)
To: linux-mediatek
Cc: Luca Leonardo Scorcia, Sean Wang, Linus Walleij, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, linux-gpio, devicetree, linux-kernel,
linux-arm-kernel
In-Reply-To: <20260625104742.113803-1-l.scorcia@gmail.com>
Pinctrl settings for MediaTek mt8516-mt8167 SoCs use two reg base
addresses, one for GPIO and the other for EINT, as it is common in the
"Paris" pinctrl platform that is described in the MediaTek mt6795 docs.
Move the binding compatible for these two SoCs from mt66xx to the mt6796
one as a prerequisite for migrating the pinctrl driver to the
pinctrl-paris platform.
Signed-off-by: Luca Leonardo Scorcia <l.scorcia@gmail.com>
---
.../devicetree/bindings/pinctrl/mediatek,mt65xx-pinctrl.yaml | 2 --
.../devicetree/bindings/pinctrl/mediatek,mt6795-pinctrl.yaml | 5 ++++-
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/pinctrl/mediatek,mt65xx-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/mediatek,mt65xx-pinctrl.yaml
index 1468c6f87cfa..0cff2a352b1f 100644
--- a/Documentation/devicetree/bindings/pinctrl/mediatek,mt65xx-pinctrl.yaml
+++ b/Documentation/devicetree/bindings/pinctrl/mediatek,mt65xx-pinctrl.yaml
@@ -22,9 +22,7 @@ properties:
- mediatek,mt7623-pinctrl
- mediatek,mt8127-pinctrl
- mediatek,mt8135-pinctrl
- - mediatek,mt8167-pinctrl
- mediatek,mt8173-pinctrl
- - mediatek,mt8516-pinctrl
reg:
maxItems: 1
diff --git a/Documentation/devicetree/bindings/pinctrl/mediatek,mt6795-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/mediatek,mt6795-pinctrl.yaml
index 9a937f414cc9..c703de72e1d5 100644
--- a/Documentation/devicetree/bindings/pinctrl/mediatek,mt6795-pinctrl.yaml
+++ b/Documentation/devicetree/bindings/pinctrl/mediatek,mt6795-pinctrl.yaml
@@ -15,7 +15,10 @@ description:
properties:
compatible:
- const: mediatek,mt6795-pinctrl
+ enum:
+ - mediatek,mt6795-pinctrl
+ - mediatek,mt8167-pinctrl
+ - mediatek,mt8516-pinctrl
gpio-controller: true
--
2.43.0
^ permalink raw reply related
* [RFC PATCH 0/3] pinctrl: mediatek: mt8516-mt8167: Convert to Paris driver
From: Luca Leonardo Scorcia @ 2026-06-25 10:46 UTC (permalink / raw)
To: linux-mediatek
Cc: Luca Leonardo Scorcia, Sean Wang, Linus Walleij, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, linux-gpio, devicetree, linux-kernel,
linux-arm-kernel
The pinctrl registers of the mt8516 and mt8167 SoCs follow the layout of
the Paris platform, but their pinctrl driver is currently modeled on
the mt65xx legacy driver. As suggested in [1], it is possible to migrate
them to the Paris driver.
In the process it is also possible to completely drop one of the two
drivers as their register layout is identical, they only differ in some
pin functions (mt8167 is basically mt8516 with added display blocks).
The Paris driver allows specifying two base registers, gpio and eint;
this way it's no longer necessary to have a syscfg node in the device
tree, referenced as a phandle in the pinctrl node. This also fixes the
following long standing dtbs_check errors:
mt8167-pumpkin.dtb: syscfg-pctl@10005000 (syscon): compatible: ['syscon']
is too short
mt8516-pumpkin.dtb: syscfg-pctl@10005000 (syscon): compatible: ['syscon']
is too short
The new driver has been checked against the SoC data sheet and adds the
capability to control pin driving strength and R1R0 pullup-pulldown
resistors.
This series is sent as a RFC since the changes could theoretically impact
existing devices. I am pretty sure that no device ever used upstream
drivers though, not even the Pumpkin board that's present in Linux
sources since this board lacks the associated mt6392 PMIC driver that
is required for regulator management. If for compatibility reasons it is
deemed better to keep both drivers in the kernel I would welcome any
suggestion on how to name the new driver, and how to adjust the two
bindings for coexistence.
These changes have been tested on the Xiaomi Mi Smart Clock X04G and on
the Lenovo Smart Clock 2 CD-24502F.
[1] https://lore.kernel.org/linux-mediatek/296b000c-5970-4668-bd42-b99ca78d598f@collabora.com/
Luca Leonardo Scorcia (3):
dt-bindings: pinctrl: mt8516/mt8167: Move compatibles from mt66xx to
mt6795
pinctrl: mediatek: mt8516/mt8167: Migrate driver to pinctrl-paris
platform
arm64: dts: mt8516/mt8167: Update pinctrl nodes for the new paris
driver
.../pinctrl/mediatek,mt65xx-pinctrl.yaml | 2 -
.../pinctrl/mediatek,mt6795-pinctrl.yaml | 5 +-
arch/arm64/boot/dts/mediatek/mt8167.dtsi | 15 +-
arch/arm64/boot/dts/mediatek/mt8516.dtsi | 12 +-
drivers/pinctrl/mediatek/Kconfig | 11 +-
drivers/pinctrl/mediatek/Makefile | 1 -
drivers/pinctrl/mediatek/pinctrl-mt8167.c | 345 --------
drivers/pinctrl/mediatek/pinctrl-mt8516.c | 770 +++++++++++-------
drivers/pinctrl/mediatek/pinctrl-mtk-mt8167.h | 562 +++++++------
drivers/pinctrl/mediatek/pinctrl-mtk-mt8516.h | 512 ++++++------
10 files changed, 1018 insertions(+), 1217 deletions(-)
delete mode 100644 drivers/pinctrl/mediatek/pinctrl-mt8167.c
base-commit: 4e5dfb7c84012007c3c7061126491bbc92d71bf1
--
2.43.0
^ permalink raw reply
* [PATCH net] net: airoha: dma map xmit frags with skb_frag_dma_map()
From: Lorenzo Bianconi @ 2026-06-25 9:42 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: linux-arm-kernel, linux-mediatek, netdev, Lorenzo Bianconi
Map xmit skb fragments using skb_frag_dma_map() instead of
dma_map_single(skb_frag_address()). skb_frag_address() relies on
page_address() to obtain a kernel virtual address, which is not
guaranteed to work for all page types (e.g. highmem pages or
user-pinned pages from MSG_ZEROCOPY).
skb_frag_dma_map() maps the fragment directly via its struct page and
offset through dma_map_page(), avoiding the need for a kernel virtual
address entirely.
Introduce an enum airoha_dma_map_type to track how each queue entry was
mapped (single vs page), so that the matching unmap function is called
on completion and in error paths.
Fixes: 23020f049327 ("net: airoha: Introduce ethernet support for EN7581 SoC")
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 61 ++++++++++++++++++++------------
drivers/net/ethernet/airoha/airoha_eth.h | 7 ++++
2 files changed, 45 insertions(+), 23 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 932b3a3df2e5..1caf6766f2c0 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -944,6 +944,25 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
q->txq_stopped = false;
}
+static void airoha_unmap_xmit_buf(struct airoha_eth *eth,
+ struct airoha_queue_entry *e)
+{
+ switch (e->dma_type) {
+ case AIROHA_DMA_MAP_PAGE:
+ dma_unmap_page(eth->dev, e->dma_addr, e->dma_len,
+ DMA_TO_DEVICE);
+ break;
+ case AIROHA_DMA_MAP_SINGLE:
+ dma_unmap_single(eth->dev, e->dma_addr, e->dma_len,
+ DMA_TO_DEVICE);
+ break;
+ case AIROHA_DMA_UNMAPPED:
+ default:
+ break;
+ }
+ e->dma_type = AIROHA_DMA_UNMAPPED;
+}
+
static int airoha_qdma_tx_napi_poll(struct napi_struct *napi, int budget)
{
struct airoha_tx_irq_queue *irq_q;
@@ -1006,9 +1025,7 @@ static int airoha_qdma_tx_napi_poll(struct napi_struct *napi, int budget)
skb = e->skb;
e->skb = NULL;
- dma_unmap_single(eth->dev, e->dma_addr, e->dma_len,
- DMA_TO_DEVICE);
- e->dma_addr = 0;
+ airoha_unmap_xmit_buf(eth, e);
list_add_tail(&e->list, &q->tx_list);
WRITE_ONCE(desc->msg0, 0);
@@ -1177,12 +1194,10 @@ static void airoha_qdma_tx_cleanup(struct airoha_qdma *qdma)
struct airoha_qdma_desc *desc = &q->desc[j];
struct sk_buff *skb = e->skb;
- if (!e->dma_addr)
+ if (e->dma_type == AIROHA_DMA_UNMAPPED)
continue;
- dma_unmap_single(qdma->eth->dev, e->dma_addr,
- e->dma_len, DMA_TO_DEVICE);
- e->dma_addr = 0;
+ airoha_unmap_xmit_buf(qdma->eth, e);
list_add_tail(&e->list, &q->tx_list);
WRITE_ONCE(desc->ctrl, 0);
@@ -2193,8 +2208,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
struct netdev_queue *txq;
struct airoha_queue *q;
LIST_HEAD(tx_list);
+ dma_addr_t addr;
int i = 0, qid;
- void *data;
u16 index;
u8 fport;
@@ -2250,24 +2265,22 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
return NETDEV_TX_BUSY;
}
- len = skb_headlen(skb);
- data = skb->data;
-
e = list_first_entry(&q->tx_list, struct airoha_queue_entry,
list);
+ len = skb_headlen(skb);
+ addr = dma_map_single(netdev->dev.parent, skb->data, len,
+ DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(netdev->dev.parent, addr)))
+ goto error_unlock;
+
+ e->dma_type = AIROHA_DMA_MAP_SINGLE;
index = e - q->entry;
while (true) {
struct airoha_qdma_desc *desc = &q->desc[index];
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
- dma_addr_t addr;
u32 val;
- addr = dma_map_single(netdev->dev.parent, data, len,
- DMA_TO_DEVICE);
- if (unlikely(dma_mapping_error(netdev->dev.parent, addr)))
- goto error_unmap;
-
list_move_tail(&e->list, &tx_list);
e->skb = i == nr_frags - 1 ? skb : NULL;
e->dma_addr = addr;
@@ -2291,8 +2304,13 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
if (++i == nr_frags)
break;
- data = skb_frag_address(frag);
len = skb_frag_size(frag);
+ addr = skb_frag_dma_map(netdev->dev.parent, frag, 0, len,
+ DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(netdev->dev.parent, addr)))
+ goto error_unmap;
+
+ e->dma_type = AIROHA_DMA_MAP_PAGE;
}
q->queued += i;
@@ -2313,11 +2331,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
return NETDEV_TX_OK;
error_unmap:
- list_for_each_entry(e, &tx_list, list) {
- dma_unmap_single(netdev->dev.parent, e->dma_addr, e->dma_len,
- DMA_TO_DEVICE);
- e->dma_addr = 0;
- }
+ list_for_each_entry(e, &tx_list, list)
+ airoha_unmap_xmit_buf(dev->eth, e);
list_splice(&tx_list, &q->tx_list);
error_unlock:
spin_unlock_bh(&q->lock);
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index d7ff8c5200e2..2765244d937c 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -170,12 +170,19 @@ enum trtcm_param {
#define TRTCM_TOKEN_RATE_MASK GENMASK(23, 6)
#define TRTCM_TOKEN_RATE_FRACTION_MASK GENMASK(5, 0)
+enum airoha_dma_map_type {
+ AIROHA_DMA_UNMAPPED,
+ AIROHA_DMA_MAP_SINGLE,
+ AIROHA_DMA_MAP_PAGE,
+};
+
struct airoha_queue_entry {
union {
void *buf;
struct {
struct list_head list;
struct sk_buff *skb;
+ enum airoha_dma_map_type dma_type;
};
};
dma_addr_t dma_addr;
---
base-commit: 232c4ca2343d1181cbfc061f9856d9591e397579
change-id: 20260625-airoha-eth-skb_frag_dma_map-bcccd5d6e4b1
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply related
* Re: [PATCH v2] dt-bindings: mediatek: cec: Correct the compatibles for mt7623-mt8167
From: Krzysztof Kozlowski @ 2026-06-25 7:32 UTC (permalink / raw)
To: Luca Leonardo Scorcia
Cc: linux-mediatek, Chun-Kuang Hu, Philipp Zabel, David Airlie,
Simona Vetter, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Matthias Brugger, AngeloGioacchino Del Regno, CK Hu, Jitao shi,
dri-devel, devicetree, linux-kernel, linux-arm-kernel
In-Reply-To: <20260624173627.19785-1-l.scorcia@gmail.com>
On Wed, Jun 24, 2026 at 07:36:15PM +0200, Luca Leonardo Scorcia wrote:
> The HDMI CEC driver for both mt7623 and mt8167 is actually the same as
> mt8173-cec and the mt7623n.dtsi board include file already uses mt8173-cec
> compatible as a fallback, but the documentation lists them as separate
> entries. Correct the binding by adding the correct fallback.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply
* [PATCH net] net: airoha: fix max receive size configuration
From: Lorenzo Bianconi @ 2026-06-25 6:49 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev, Madhur Agrawal
Set the GDM maximum receive size to AIROHA_MAX_RX_SIZE unconditionally
during hardware initialization instead of updating it according to the
configured MTU. This avoids dropping incoming frames that exceed the
current MTU but could still be processed by the networking stack, which
is able to fragment the reply on the TX side (e.g. ICMP echo requests).
Move the per-port MTU configuration to the PPE egress path where it
belongs, and set the tx frame size running airoha_ppe_set_xmit_frame_size()
to dynamically track the maximum MTU across running interfaces sharing
the same PPE instance.
Fix the PPE MTU register addressing to pack two port entries per
register word and add WAN_MTU0 configuration for non-LAN GDM devices.
Fixes: 54d989d58d2a ("net: airoha: Move min/max packet len configuration in airoha_dev_open()")
Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 68 ++++++++++---------------------
drivers/net/ethernet/airoha/airoha_eth.h | 2 +
drivers/net/ethernet/airoha/airoha_ppe.c | 39 +++++++++++++-----
drivers/net/ethernet/airoha/airoha_regs.h | 9 ++--
4 files changed, 58 insertions(+), 60 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 932b3a3df2e5..3f451c2d4c24 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -178,10 +178,15 @@ static void airoha_fe_maccr_init(struct airoha_eth *eth)
{
int p;
- for (p = 1; p <= ARRAY_SIZE(eth->ports); p++)
+ for (p = 1; p <= ARRAY_SIZE(eth->ports); p++) {
airoha_fe_set(eth, REG_GDM_FWD_CFG(p),
GDM_TCP_CKSUM_MASK | GDM_UDP_CKSUM_MASK |
GDM_IP4_CKSUM_MASK | GDM_DROP_CRC_ERR_MASK);
+ airoha_fe_rmw(eth, REG_GDM_LEN_CFG(p),
+ GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
+ FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
+ FIELD_PREP(GDM_LONG_LEN_MASK, AIROHA_MAX_RX_SIZE));
+ }
airoha_fe_rmw(eth, REG_CDM_VLAN_CTRL(1), CDM_VLAN_MASK,
FIELD_PREP(CDM_VLAN_MASK, 0x8100));
@@ -1831,13 +1836,24 @@ static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
spin_unlock(&port->stats_lock);
}
+static void airoha_dev_set_xmit_frame_size(struct net_device *netdev)
+{
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+
+ airoha_ppe_set_xmit_frame_size(dev);
+ if (!airoha_is_lan_gdm_dev(dev))
+ airoha_fe_rmw(dev->eth, REG_WAN_MTU0, WAN_MTU0_MASK,
+ FIELD_PREP(WAN_MTU0_MASK,
+ VLAN_ETH_HLEN + netdev->mtu));
+}
+
static int airoha_dev_open(struct net_device *netdev)
{
- int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
- u32 cur_len, pse_port = FE_PSE_PORT_PPE1;
struct airoha_qdma *qdma = dev->qdma;
+ u32 pse_port = FE_PSE_PORT_PPE1;
+ int err;
netif_tx_start_all_queues(netdev);
err = airoha_set_vip_for_gdm_port(dev, true);
@@ -1851,19 +1867,7 @@ static int airoha_dev_open(struct net_device *netdev)
airoha_fe_clear(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
GDM_STAG_EN_MASK);
- cur_len = airoha_fe_get(qdma->eth, REG_GDM_LEN_CFG(port->id),
- GDM_LONG_LEN_MASK);
- if (!port->users || len > cur_len) {
- /* Opening a sibling net_device with a larger MTU updates the
- * MTU of already running devices. This is required to allow
- * multiple net_devices with different MTUs to share the same
- * GDM port.
- */
- airoha_fe_rmw(qdma->eth, REG_GDM_LEN_CFG(port->id),
- GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
- FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
- FIELD_PREP(GDM_LONG_LEN_MASK, len));
- }
+ airoha_dev_set_xmit_frame_size(netdev);
port->users++;
if (!airoha_is_lan_gdm_dev(dev) &&
@@ -1875,30 +1879,6 @@ static int airoha_dev_open(struct net_device *netdev)
return 0;
}
-static void airoha_set_port_mtu(struct airoha_eth *eth,
- struct airoha_gdm_port *port)
-{
- u32 len = 0;
- int i;
-
- for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
- struct airoha_gdm_dev *dev = port->devs[i];
- struct net_device *netdev;
-
- if (!dev)
- continue;
-
- netdev = netdev_from_priv(dev);
- if (netif_running(netdev))
- len = max_t(u32, len, netdev->mtu);
- }
- len += ETH_HLEN + ETH_FCS_LEN;
-
- airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
- GDM_LONG_LEN_MASK,
- FIELD_PREP(GDM_LONG_LEN_MASK, len));
-}
-
static int airoha_dev_stop(struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
@@ -1909,7 +1889,7 @@ static int airoha_dev_stop(struct net_device *netdev)
airoha_set_vip_for_gdm_port(dev, false);
if (--port->users)
- airoha_set_port_mtu(dev->eth, port);
+ airoha_ppe_set_xmit_frame_size(dev);
else
airoha_set_gdm_port_fwd_cfg(qdma->eth,
REG_GDM_FWD_CFG(port->id),
@@ -1962,10 +1942,6 @@ static int airoha_enable_gdm2_loopback(struct airoha_gdm_dev *dev)
FIELD_PREP(LPBK_CHAN_MASK, chan) |
LBK_GAP_MODE_MASK | LBK_LEN_MODE_MASK |
LBK_CHAN_MODE_MASK | LPBK_EN_MASK);
- airoha_fe_rmw(eth, REG_GDM_LEN_CFG(AIROHA_GDM2_IDX),
- GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
- FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
- FIELD_PREP(GDM_LONG_LEN_MASK, AIROHA_MAX_MTU));
/* Forward the traffic to the proper GDM port */
pse_port = port->id == AIROHA_GDM3_IDX ? FE_PSE_PORT_GDM3
: FE_PSE_PORT_GDM4;
@@ -2098,7 +2074,7 @@ static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
WRITE_ONCE(netdev->mtu, mtu);
if (port->users)
- airoha_set_port_mtu(dev->eth, port);
+ airoha_dev_set_xmit_frame_size(netdev);
return 0;
}
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index d7ff8c5200e2..0c3fb6e5d7f1 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -23,6 +23,7 @@
#define AIROHA_MAX_DSA_PORTS 7
#define AIROHA_MAX_NUM_RSTS 3
#define AIROHA_MAX_MTU 9220
+#define AIROHA_MAX_RX_SIZE 16128
#define AIROHA_MAX_PACKET_SIZE 2048
#define AIROHA_NUM_QOS_CHANNELS 4
#define AIROHA_NUM_QOS_QUEUES 8
@@ -676,6 +677,7 @@ int airoha_get_fe_port(struct airoha_gdm_dev *dev);
bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
struct airoha_gdm_dev *dev);
+void airoha_ppe_set_xmit_frame_size(struct airoha_gdm_dev *dev);
void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport);
bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index);
void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb,
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index 42f4b0f21d17..e7c78293002a 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -97,6 +97,33 @@ void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport)
__field_prep(DFT_CPORT_MASK(fport), fe_cpu_port));
}
+void airoha_ppe_set_xmit_frame_size(struct airoha_gdm_dev *dev)
+{
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
+ int i, ppe_id, index;
+ u32 len = 0;
+
+ for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
+ struct airoha_gdm_dev *d = port->devs[i];
+ struct net_device *netdev;
+
+ if (!d)
+ continue;
+
+ netdev = netdev_from_priv(d);
+ if (netif_running(netdev))
+ len = max_t(u32, len, netdev->mtu);
+ }
+ len += VLAN_ETH_HLEN;
+
+ ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1);
+ index = port->id == AIROHA_GDM4_IDX ? 7 : port->id;
+ airoha_fe_rmw(eth, REG_PPE_MTU(ppe_id, index),
+ FP_EGRESS_MTU_MASK(index),
+ __field_prep(FP_EGRESS_MTU_MASK(index), len));
+}
+
static void airoha_ppe_hw_init(struct airoha_ppe *ppe)
{
u32 sram_ppe_num_data_entries = PPE_SRAM_NUM_ENTRIES, sram_num_entries;
@@ -115,8 +142,6 @@ static void airoha_ppe_hw_init(struct airoha_ppe *ppe)
PPE_RAM_NUM_ENTRIES_SHIFT(sram_ppe_num_data_entries);
for (i = 0; i < eth->soc->num_ppe; i++) {
- int p;
-
airoha_fe_wr(eth, REG_PPE_TB_BASE(i),
ppe->foe_dma + sram_tb_size);
@@ -166,15 +191,6 @@ static void airoha_ppe_hw_init(struct airoha_ppe *ppe)
airoha_fe_wr(eth, REG_PPE_HASH_SEED(i), PPE_HASH_SEED);
airoha_fe_clear(eth, REG_PPE_PPE_FLOW_CFG(i),
PPE_FLOW_CFG_IP6_6RD_MASK);
-
- for (p = 0; p < ARRAY_SIZE(eth->ports); p++)
- airoha_fe_rmw(eth, REG_PPE_MTU(i, p),
- FP0_EGRESS_MTU_MASK |
- FP1_EGRESS_MTU_MASK,
- FIELD_PREP(FP0_EGRESS_MTU_MASK,
- AIROHA_MAX_MTU) |
- FIELD_PREP(FP1_EGRESS_MTU_MASK,
- AIROHA_MAX_MTU));
}
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
@@ -196,6 +212,7 @@ static void airoha_ppe_hw_init(struct airoha_ppe *ppe)
airoha_ppe_is_enabled(eth, 1);
fport = airoha_get_fe_port(dev);
airoha_ppe_set_cpu_port(dev, ppe_id, fport);
+ airoha_ppe_set_xmit_frame_size(dev);
}
}
}
diff --git a/drivers/net/ethernet/airoha/airoha_regs.h b/drivers/net/ethernet/airoha/airoha_regs.h
index 436f3c8779c1..6fed63d013b4 100644
--- a/drivers/net/ethernet/airoha/airoha_regs.h
+++ b/drivers/net/ethernet/airoha/airoha_regs.h
@@ -327,9 +327,8 @@
#define PPE_SRAM_TABLE_EN_MASK BIT(0)
#define REG_PPE_MTU_BASE(_n) (((_n) ? PPE2_BASE : PPE1_BASE) + 0x304)
-#define REG_PPE_MTU(_m, _n) (REG_PPE_MTU_BASE(_m) + ((_n) << 2))
-#define FP1_EGRESS_MTU_MASK GENMASK(29, 16)
-#define FP0_EGRESS_MTU_MASK GENMASK(13, 0)
+#define REG_PPE_MTU(_m, _n) (REG_PPE_MTU_BASE(_m) + (((_n) / 2) << 2))
+#define FP_EGRESS_MTU_MASK(_n) GENMASK(13 + (((_n) % 2) << 4), ((_n) % 2) << 4)
#define REG_PPE_RAM_CTRL(_n) (((_n) ? PPE2_BASE : PPE1_BASE) + 0x31c)
#define PPE_SRAM_CTRL_ACK_MASK BIT(31)
@@ -377,6 +376,10 @@
#define REG_SRC_PORT_FC_MAP6 0x2298
#define FC_ID_OF_SRC_PORT_MASK(_n) GENMASK(4 + ((_n) << 3), ((_n) << 3))
+#define REG_WAN_MTU0 0x2300
+#define WAN_MTU1_MASK GENMASK(29, 16)
+#define WAN_MTU0_MASK GENMASK(13, 0)
+
#define REG_CDM5_RX_OQ1_DROP_CNT 0x29d4
/* QDMA */
---
base-commit: fd1269e454089abda0e4f9e5e25ecd02a90ab009
change-id: 20260618-airoha-fix-rx-max-len-57654b661646
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply related
* Re: [External Mail] Re: [PATCH v3 0/7] net: wwan: t9xx: Add MediaTek T9XX WWAN driver
From: Jakub Kicinski @ 2026-06-25 2:45 UTC (permalink / raw)
To: Wu. JackBB (GSM)
Cc: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Wen-Zhi Huang,
Shi-Wei Yeh, Minano Tseng, Matthias Brugger,
AngeloGioacchino Del Regno, Simon Horman, Jonathan Corbet,
Shuah Khan, linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-mediatek@lists.infradead.org, linux-doc@vger.kernel.org
In-Reply-To: <cec5736466864641967b99adcfaf324a@compal.com>
On Thu, 25 Jun 2026 01:55:49 +0000 Wu. JackBB (GSM) wrote:
> I have a question about the preferred workflow: the cover
> letter changelog would get quite long if I include detailed
> explanations for each sashiko comment we chose not to fix.
>
> Was the concern more about timing? Should we have replied
> to the sashiko review promptly when it came in, rather than
> waiting until the full v3 was ready?
Either way works. Either give reviewers 24h to dispute the comments or
add the comments to the repost. You don't have to keep a full detailed
log in the changelog.
> ================================================================================================================================================================
> This message may contain information which is private, privileged or confidential of Compal Electronics, Inc. If you are not the intended recipient of this message, please notify the sender and destroy/delete the message. Any review, retransmission, dissemination or other use of, or taking of any action in reliance upon this information, by persons or entities other than the intended recipient is prohibited.
> ================================================================================================================================================================
Again, please fix this.
^ permalink raw reply
* Re: [PATCH net v3] net: airoha: fix BQL underflow in shared QDMA TX ring
From: patchwork-bot+netdevbpf @ 2026-06-25 2:00 UTC (permalink / raw)
To: Lorenzo Bianconi
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, win847,
linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260620-airoha-bql-fixes-v3-1-76b95374e63e@kernel.org>
Hello:
This patch was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Sat, 20 Jun 2026 17:04:51 +0200 you wrote:
> When multiple netdevs share a QDMA TX ring and one device is stopped,
> netdev_tx_reset_subqueue() zeroes that device's BQL counters while its
> pending skbs remain in the shared HW TX ring. When NAPI later completes
> those skbs via netdev_tx_completed_queue(), the already-zeroed
> dql->num_queued counter underflows.
>
> Fix the issue:
> - Remove netdev_tx_reset_subqueue() from airoha_dev_stop() so pending
> skbs are completed naturally by NAPI with proper BQL accounting.
> - Rework airoha_qdma_tx_cleanup() to disable TX DMA, flush BQL
> counters, DMA-unmap and free all pending skbs while skb->dev
> references are still valid. Use a per-queue flushing flag checked
> under q->lock in airoha_dev_xmit() to prevent races between teardown
> and transmit. Call airoha_qdma_stop_napi() before
> airoha_qdma_tx_cleanup() at the call sites.
> - Move DMA engine start into probe. Split DMA teardown so TX DMA is
> disabled in airoha_qdma_tx_cleanup() and RX DMA in
> airoha_qdma_cleanup().
> - Remove qdma->users counter since DMA lifetime is now tied to
> probe/cleanup rather than per-netdev open/stop.
>
> [...]
Here is the summary with links:
- [net,v3] net: airoha: fix BQL underflow in shared QDMA TX ring
https://git.kernel.org/netdev/net/c/611709830945
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* RE: [External Mail] Re: [PATCH v3 0/7] net: wwan: t9xx: Add MediaTek T9XX WWAN driver
From: Wu. JackBB (GSM) @ 2026-06-25 1:55 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Wen-Zhi Huang,
Shi-Wei Yeh, Minano Tseng, Matthias Brugger,
AngeloGioacchino Del Regno, Simon Horman, Jonathan Corbet,
Shuah Khan, linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-mediatek@lists.infradead.org, linux-doc@vger.kernel.org
In-Reply-To: <20260624170917.09967c74@kernel.org>
Hi Jakub,
> On Wed, 24 Jun 2026 18:04:06 +0800 Jack Wu via B4 Relay wrote:
> > T9XX is the PCIe host device driver for MediaTek's
> > t900 modem. The driver uses the WWAN framework
> > infrastructure to create the following control ports
> > and network interfaces for data transactions.
>
> Replying after a long delay and then immediately posting a new version
> of patches is very bad. Don't bother replying and just put the comments
> you had in the changelog of the new posting. Otherwise the discussion
> may get split.
Sorry about the confusion.
I have a question about the preferred workflow: the cover
letter changelog would get quite long if I include detailed
explanations for each sashiko comment we chose not to fix.
Was the concern more about timing? Should we have replied
to the sashiko review promptly when it came in, rather than
waiting until the full v3 was ready?
Thanks for the guidance.
================================================================================================================================================================
This message may contain information which is private, privileged or confidential of Compal Electronics, Inc. If you are not the intended recipient of this message, please notify the sender and destroy/delete the message. Any review, retransmission, dissemination or other use of, or taking of any action in reliance upon this information, by persons or entities other than the intended recipient is prohibited.
================================================================================================================================================================
^ permalink raw reply
* Re: [PATCH net v2 0/2] airoha: fixes for sched HTB offload support
From: patchwork-bot+netdevbpf @ 2026-06-25 1:08 UTC (permalink / raw)
To: Lorenzo Bianconi
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms, win847,
linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260619-airoha-qos-fixes-v2-0-5c43485038f9@kernel.org>
Hello:
This series was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Fri, 19 Jun 2026 13:37:12 +0200 you wrote:
> ---
> Changes in v2:
> - cosmetics
> - Link to v1: https://lore.kernel.org/r/20260618-airoha-qos-fixes-v1-0-37192652157f@kernel.org
>
> ---
> Lorenzo Bianconi (2):
> net: airoha: Fix off-by-one in airoha_tc_remove_htb_queue()
> net: airoha: fix netif_set_real_num_tx_queues for sparse QoS channels
>
> [...]
Here is the summary with links:
- [net,v2,1/2] net: airoha: Fix off-by-one in airoha_tc_remove_htb_queue()
https://git.kernel.org/netdev/net/c/bfcce49c4aaa
- [net,v2,2/2] net: airoha: fix netif_set_real_num_tx_queues for sparse QoS channels
https://git.kernel.org/netdev/net/c/788663dd28e4
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* [RFC PATCH] wifi: mt76: fail channel switch when TX queues do not drain
From: Pengpeng Hou @ 2026-06-25 0:38 UTC (permalink / raw)
To: Felix Fietkau
Cc: pengpeng, Lorenzo Bianconi, Ryder Lee, Shayne Chen, Sean Wang,
Matthias Brugger, AngeloGioacchino Del Regno, linux-wireless,
linux-kernel, linux-arm-kernel, linux-mediatek
__mt76_set_channel() disables the TX worker, schedules pending TX queues,
sets MT76_RESET, and waits for pending TX to drain before changing the PHY
channel state and calling the driver set_channel() callback.
The wait result is ignored. If TX does not drain within the timeout, the
function still publishes the new channel state and asks the driver to
switch channels while old-channel TX may remain pending.
Return -ETIMEDOUT when the drain wait expires. Keep the survey update on
the old channel, then clear MT76_RESET and re-enable the TX worker through
the existing exit path.
This is marked RFC because the existing channel switch path treats the TX
drain wait as best-effort. Maintainer feedback is needed on whether a TX
drain timeout should fail the channel switch for all mt76 devices or
whether the current force-forward behavior is intentional.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/net/wireless/mediatek/mt76/mac80211.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 13c4e8abe..37474d64c 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -1033,6 +1033,7 @@ int __mt76_set_channel(struct mt76_phy *phy, struct cfg80211_chan_def *chandef,
bool offchannel)
{
struct mt76_dev *dev = phy->dev;
+ long time_left;
int timeout = HZ / 5;
int ret;
@@ -1040,8 +1041,13 @@ int __mt76_set_channel(struct mt76_phy *phy, struct cfg80211_chan_def *chandef,
mt76_txq_schedule_pending(phy);
set_bit(MT76_RESET, &phy->state);
- wait_event_timeout(dev->tx_wait, !mt76_has_tx_pending(phy), timeout);
+ time_left = wait_event_timeout(dev->tx_wait, !mt76_has_tx_pending(phy),
+ timeout);
mt76_update_survey(phy);
+ if (!time_left) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
if (phy->chandef.chan->center_freq != chandef->chan->center_freq ||
phy->chandef.width != chandef->width)
@@ -1059,6 +1065,7 @@ int __mt76_set_channel(struct mt76_phy *phy, struct cfg80211_chan_def *chandef,
ret = dev->drv->set_channel(phy);
+out:
clear_bit(MT76_RESET, &phy->state);
mt76_worker_enable(&dev->tx_worker);
mt76_worker_schedule(&dev->tx_worker);
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [RFC PATCH] mtd: rawnand: mtk: return errors when reset does not idle
From: Pengpeng Hou @ 2026-06-25 0:37 UTC (permalink / raw)
To: Miquel Raynal
Cc: pengpeng, Richard Weinberger, Vignesh Raghavendra,
Matthias Brugger, AngeloGioacchino Del Regno, linux-mtd,
linux-kernel, linux-arm-kernel, linux-mediatek
mtk_nfc_hw_reset() waits for the NFI master to become idle after forcing a
reset, but it only logs a warning when the wait times out. Probe-time
hardware init and later exec_op() calls then continue as if the reset had
completed.
Make the reset helper return the poll error and propagate it through both
initial hardware setup and NAND operation execution. Keep the final reset
writes so status registers affected by the NFI master are still forced
back to the reset state before the error is returned.
This is marked RFC because the existing code only warns about the idle
timeout and then performs the final reset writes. Maintainer feedback is
needed on whether this timeout should be treated as fatal or as a
recoverable reset condition on affected hardware.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/mtd/nand/raw/mtk_nand.c | 27 ++++++++++++++++++++-------
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/drivers/mtd/nand/raw/mtk_nand.c b/drivers/mtd/nand/raw/mtk_nand.c
index 21c7e1102..059b8070f 100644
--- a/drivers/mtd/nand/raw/mtk_nand.c
+++ b/drivers/mtd/nand/raw/mtk_nand.c
@@ -255,7 +255,7 @@ static inline u8 nfi_readb(struct mtk_nfc *nfc, u32 reg)
return readb_relaxed(nfc->regs + reg);
}
-static void mtk_nfc_hw_reset(struct mtk_nfc *nfc)
+static int mtk_nfc_hw_reset(struct mtk_nfc *nfc)
{
struct device *dev = nfc->dev;
u32 val;
@@ -269,12 +269,14 @@ static void mtk_nfc_hw_reset(struct mtk_nfc *nfc)
!(val & MASTER_STA_MASK), 50,
MTK_RESET_TIMEOUT);
if (ret)
- dev_warn(dev, "master active in reset [0x%x] = 0x%x\n",
- NFI_MASTER_STA, val);
+ dev_err(dev, "master active in reset [0x%x] = 0x%x\n",
+ NFI_MASTER_STA, val);
/* ensure any status register affected by the NFI master is reset */
nfi_writel(nfc, CON_FIFO_FLUSH | CON_NFI_RST, NFI_CON);
nfi_writew(nfc, STAR_DE, NFI_STRDATA);
+
+ return ret;
}
static int mtk_nfc_send_command(struct mtk_nfc *nfc, u8 command)
@@ -517,7 +519,10 @@ static int mtk_nfc_exec_op(struct nand_chip *chip,
if (check_only)
return 0;
- mtk_nfc_hw_reset(nfc);
+ ret = mtk_nfc_hw_reset(nfc);
+ if (ret)
+ return ret;
+
nfi_writew(nfc, CNFG_OP_CUST, NFI_CNFG);
mtk_nfc_select_target(chip, op->cs);
@@ -1084,8 +1089,10 @@ static int mtk_nfc_read_oob_std(struct nand_chip *chip, int page)
return mtk_nfc_read_page_raw(chip, NULL, 1, page);
}
-static inline void mtk_nfc_hw_init(struct mtk_nfc *nfc)
+static int mtk_nfc_hw_init(struct mtk_nfc *nfc)
{
+ int ret;
+
/*
* CNRNB: nand ready/busy register
* -------------------------------
@@ -1095,10 +1102,14 @@ static inline void mtk_nfc_hw_init(struct mtk_nfc *nfc)
nfi_writew(nfc, 0xf1, NFI_CNRNB);
nfi_writel(nfc, PAGEFMT_8K_16K, NFI_PAGEFMT);
- mtk_nfc_hw_reset(nfc);
+ ret = mtk_nfc_hw_reset(nfc);
+ if (ret)
+ return ret;
nfi_readl(nfc, NFI_INTR_STA);
nfi_writel(nfc, 0, NFI_INTR_EN);
+
+ return 0;
}
static irqreturn_t mtk_nfc_irq(int irq, void *id)
@@ -1411,7 +1422,9 @@ static int mtk_nfc_nand_chip_init(struct device *dev, struct mtk_nfc *nfc,
mtd->name = MTK_NAME;
mtd_set_ooblayout(mtd, &mtk_nfc_ooblayout_ops);
- mtk_nfc_hw_init(nfc);
+ ret = mtk_nfc_hw_init(nfc);
+ if (ret)
+ return ret;
ret = nand_scan(nand, nsels);
if (ret)
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [PATCH] mmc: mtk-sd: report DMA stop timeouts as data errors
From: Pengpeng Hou @ 2026-06-25 0:35 UTC (permalink / raw)
To: Chaotian Jing
Cc: pengpeng, Ulf Hansson, Matthias Brugger,
AngeloGioacchino Del Regno, linux-mmc, linux-kernel,
linux-arm-kernel, linux-mediatek
msdc_data_xfer_done() stops the DMA engine before completing a data
request, but it only logs failures from the DMA stop and DMA-inactive
polls.
When the interrupt status also carries MSDC_INT_XFER_COMPL, the function
can still report the full data length as transferred even though the DMA
engine did not confirm that it stopped or became inactive.
Treat DMA stop/inactive timeouts as data errors. This reuses the existing
reset/error path and prevents a timed out DMA stop from being reported as a
successful full-length transfer.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/mmc/host/mtk-sd.c | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/drivers/mmc/host/mtk-sd.c b/drivers/mmc/host/mtk-sd.c
index b2680cc05..e3caa9c32 100644
--- a/drivers/mmc/host/mtk-sd.c
+++ b/drivers/mmc/host/mtk-sd.c
@@ -1583,7 +1583,7 @@ static void msdc_data_xfer_done(struct msdc_host *host, u32 events,
| MSDC_INT_DMA_BDCSERR | MSDC_INT_DMA_GPDCSERR
| MSDC_INT_DMA_PROTECT);
u32 val;
- int ret;
+ int dma_err = 0, ret;
spin_lock_irqsave(&host->lock, flags);
done = !host->data;
@@ -1603,18 +1603,23 @@ static void msdc_data_xfer_done(struct msdc_host *host, u32 events,
ret = readl_poll_timeout_atomic(host->base + MSDC_DMA_CTRL, val,
!(val & MSDC_DMA_CTRL_STOP), 1, 20000);
- if (ret)
+ if (ret) {
dev_dbg(host->dev, "DMA stop timed out\n");
+ dma_err = ret;
+ }
ret = readl_poll_timeout_atomic(host->base + MSDC_DMA_CFG, val,
!(val & MSDC_DMA_CFG_STS), 1, 20000);
- if (ret)
+ if (ret) {
dev_dbg(host->dev, "DMA inactive timed out\n");
+ dma_err = ret;
+ }
sdr_clr_bits(host->base + MSDC_INTEN, data_ints_mask);
dev_dbg(host->dev, "DMA stop\n");
- if ((events & MSDC_INT_XFER_COMPL) && (!stop || !stop->error)) {
+ if (!dma_err && (events & MSDC_INT_XFER_COMPL) &&
+ (!stop || !stop->error)) {
data->bytes_xfered = data->blocks * data->blksz;
} else {
dev_dbg(host->dev, "interrupt events: %x\n", events);
@@ -1622,7 +1627,9 @@ static void msdc_data_xfer_done(struct msdc_host *host, u32 events,
host->error |= REQ_DAT_ERR;
data->bytes_xfered = 0;
- if (events & MSDC_INT_DATTMO)
+ if (dma_err)
+ data->error = -ETIMEDOUT;
+ else if (events & MSDC_INT_DATTMO)
data->error = -ETIMEDOUT;
else if (events & MSDC_INT_DATCRCERR)
data->error = -EILSEQ;
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [PATCH] media: mediatek: jpeg: retry HW selection after successful wait
From: Pengpeng Hou @ 2026-06-25 0:31 UTC (permalink / raw)
To: Bin Liu
Cc: pengpeng, Mauro Carvalho Chehab, Matthias Brugger,
AngeloGioacchino Del Regno, linux-media, linux-kernel,
linux-arm-kernel, linux-mediatek
wait_event_interruptible_timeout() returns a positive value when the
condition becomes true before the timeout expires.
mtk_jpegdec_worker() treats every non-zero return value as a failure and
finishes the mem2mem job with an error. A normal wakeup that indicates
that a decode engine is ready can therefore be handled as if all hardware
remained busy.
Only fail immediately on interrupted waits. Keep the existing retry limit
for real timeouts, and retry hardware selection after a successful wait.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c
index d147ec483..391db77a6 100644
--- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c
+++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c
@@ -1697,7 +1697,7 @@ static void mtk_jpegdec_worker(struct work_struct *work)
ret = wait_event_interruptible_timeout(jpeg->hw_wq,
atomic_read(&jpeg->hw_rdy) > 0,
MTK_JPEG_HW_TIMEOUT_MSEC);
- if (ret != 0 || (i++ > MTK_JPEG_MAX_RETRY_TIME)) {
+ if (ret < 0 || (!ret && i++ > MTK_JPEG_MAX_RETRY_TIME)) {
dev_err(jpeg->dev, "%s : %d, all HW are busy\n",
__func__, __LINE__);
v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx);
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [PATCH v2 9/9] wifi: mt76: mt792x: advertise NAN data support
From: Sean Wang @ 2026-06-25 0:18 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi
Cc: chengwei.yu, yu-ching.liu, jenhao.yang, posh.sun, linux-wireless,
linux-mediatek, Sean Wang
In-Reply-To: <20260625001834.475094-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Advertise NAN and NAN data support when firmware exposes NAN
capability.
Add NAN interface combinations on top of the dynamic combination
framework, advertise 2.4 GHz and 5 GHz NAN bands, and enable secure
NAN.
Keep the base interface combinations unchanged when NAN is unavailable
so existing STA/AP/P2P modes keep the same limits.
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>
---
.../net/wireless/mediatek/mt76/mt792x_core.c | 96 ++++++++++++++++++-
1 file changed, 92 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
index ffe0bcdf1df6..411c04640add 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
@@ -3,6 +3,7 @@
#include <linux/module.h>
#include <linux/firmware.h>
+#include <linux/slab.h>
#include "mt792x.h"
#include "dma.h"
@@ -60,6 +61,40 @@ static const struct ieee80211_iface_limit if_limits_chanctx_scc[] = {
}
};
+static const struct ieee80211_iface_limit if_limits_nan_mcc[] = {
+ {
+ .max = 2,
+ .types = BIT(NL80211_IFTYPE_STATION),
+ },
+ {
+ .max = 1,
+ .types = BIT(NL80211_IFTYPE_NAN),
+ },
+ {
+ .max = 2,
+ .types = BIT(NL80211_IFTYPE_NAN_DATA),
+ },
+};
+
+static const struct ieee80211_iface_limit if_limits_nan_scc[] = {
+ {
+ .max = 2,
+ .types = BIT(NL80211_IFTYPE_STATION),
+ },
+ {
+ .max = 1,
+ .types = BIT(NL80211_IFTYPE_NAN),
+ },
+ {
+ .max = 2,
+ .types = BIT(NL80211_IFTYPE_NAN_DATA),
+ },
+ {
+ .max = 1,
+ .types = BIT(NL80211_IFTYPE_AP),
+ },
+};
+
static const struct ieee80211_iface_combination if_comb_chanctx_base[] = {
{
.limits = if_limits_chanctx_mcc,
@@ -77,9 +112,31 @@ static const struct ieee80211_iface_combination if_comb_chanctx_base[] = {
}
};
-static int mt792x_setup_iface_combinations(struct mt792x_dev *dev)
+static const struct ieee80211_iface_combination if_comb_chanctx_nan[] = {
+ {
+ .limits = if_limits_nan_mcc,
+ .n_limits = ARRAY_SIZE(if_limits_nan_mcc),
+ .max_interfaces = MT792x_MAX_INTERFACES,
+ .num_different_channels = 2,
+ .beacon_int_infra_match = false,
+ },
+ {
+ .limits = if_limits_nan_scc,
+ .n_limits = ARRAY_SIZE(if_limits_nan_scc),
+ .max_interfaces = MT792x_MAX_INTERFACES,
+ .num_different_channels = 1,
+ .beacon_int_infra_match = false,
+ },
+};
+
+static int mt792x_setup_iface_combinations(struct mt792x_dev *dev,
+ struct wiphy *wiphy)
{
const bool cnm = !!(dev->fw_features & MT792x_FW_CAP_CNM);
+ const bool nan = !!(dev->fw_features & MT792x_FW_CAP_NAN);
+ const int n_base = ARRAY_SIZE(if_comb_chanctx_base);
+ const int n_nan = ARRAY_SIZE(if_comb_chanctx_nan);
+ struct ieee80211_iface_combination *comb;
if (!cnm) {
dev->iface_combinations = if_comb;
@@ -87,8 +144,24 @@ static int mt792x_setup_iface_combinations(struct mt792x_dev *dev)
return 0;
}
- dev->iface_combinations = if_comb_chanctx_base;
- dev->n_iface_combinations = ARRAY_SIZE(if_comb_chanctx_base);
+ /* CNM enabled, NAN optional */
+ if (!nan) {
+ dev->iface_combinations = if_comb_chanctx_base;
+ dev->n_iface_combinations = ARRAY_SIZE(if_comb_chanctx_base);
+ return 0;
+ }
+
+ /* CNM + NAN: dynamically build base + nan list */
+ comb = devm_kcalloc(&wiphy->dev, n_base + n_nan, sizeof(*comb),
+ GFP_KERNEL);
+ if (!comb)
+ return -ENOMEM;
+
+ memcpy(comb, if_comb_chanctx_base, sizeof(if_comb_chanctx_base));
+ memcpy(comb + n_base, if_comb_chanctx_nan, sizeof(if_comb_chanctx_nan));
+
+ dev->iface_combinations = comb;
+ dev->n_iface_combinations = n_base + n_nan;
return 0;
}
@@ -705,7 +778,7 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw)
else
wiphy->flags &= ~WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
- err = mt792x_setup_iface_combinations(dev);
+ err = mt792x_setup_iface_combinations(dev, wiphy);
if (err)
return err;
@@ -719,6 +792,21 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw)
BIT(NL80211_IFTYPE_P2P_GO) |
BIT(NL80211_IFTYPE_P2P_DEVICE);
+ if ((dev->fw_features & MT792x_FW_CAP_CNM) &&
+ (dev->fw_features & MT792x_FW_CAP_NAN)) {
+ wiphy->interface_modes |= BIT(NL80211_IFTYPE_NAN) |
+ BIT(NL80211_IFTYPE_NAN_DATA);
+ wiphy->nan_supported_bands = BIT(NL80211_BAND_2GHZ) |
+ BIT(NL80211_BAND_5GHZ);
+ wiphy->nan_capa.flags = WIPHY_NAN_FLAGS_CONFIGURABLE_SYNC |
+ WIPHY_NAN_FLAGS_USERSPACE_DE;
+ wiphy->nan_capa.op_mode = NAN_OP_MODE_PHY_MODE_MASK;
+ wiphy->nan_capa.n_antennas = 0x22;
+ wiphy->nan_capa.max_channel_switch_time = 12;
+ wiphy->nan_capa.dev_capabilities = NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED;
+ wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SECURE_NAN);
+ }
+
wiphy->max_scan_ie_len = MT76_CONNAC_SCAN_IE_LEN;
wiphy->max_scan_ssids = 4;
wiphy->max_sched_scan_plan_interval =
--
2.43.0
^ permalink raw reply related
* [PATCH v2 8/9] wifi: mt76: mt792x: build iface combinations dynamically
From: Sean Wang @ 2026-06-25 0:18 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi
Cc: chengwei.yu, yu-ching.liu, jenhao.yang, posh.sun, linux-wireless,
linux-mediatek, Sean Wang
In-Reply-To: <20260625001834.475094-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Move mt792x interface combination selection into a helper and store the
selected table in mt792x device state.
This keeps the existing non-CNM and CNM combinations unchanged while
making later firmware-gated extensions add combinations without touching
the common wiphy setup path.
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>
---
drivers/net/wireless/mediatek/mt76/mt792x.h | 2 ++
.../net/wireless/mediatek/mt76/mt792x_core.c | 36 ++++++++++++++-----
2 files changed, 30 insertions(+), 8 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 9d5a2adc81f6..73f2333c2970 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -324,6 +324,8 @@ struct mt792x_dev {
struct ieee80211_chanctx_conf *new_ctx;
struct ieee80211_vif *nan_vif;
+ const struct ieee80211_iface_combination *iface_combinations;
+ int n_iface_combinations;
};
static inline struct mt792x_bss_conf *
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
index a0db815c27bc..ffe0bcdf1df6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
@@ -60,7 +60,7 @@ static const struct ieee80211_iface_limit if_limits_chanctx_scc[] = {
}
};
-static const struct ieee80211_iface_combination if_comb_chanctx[] = {
+static const struct ieee80211_iface_combination if_comb_chanctx_base[] = {
{
.limits = if_limits_chanctx_mcc,
.n_limits = ARRAY_SIZE(if_limits_chanctx_mcc),
@@ -77,6 +77,22 @@ static const struct ieee80211_iface_combination if_comb_chanctx[] = {
}
};
+static int mt792x_setup_iface_combinations(struct mt792x_dev *dev)
+{
+ const bool cnm = !!(dev->fw_features & MT792x_FW_CAP_CNM);
+
+ if (!cnm) {
+ dev->iface_combinations = if_comb;
+ dev->n_iface_combinations = ARRAY_SIZE(if_comb);
+ return 0;
+ }
+
+ dev->iface_combinations = if_comb_chanctx_base;
+ dev->n_iface_combinations = ARRAY_SIZE(if_comb_chanctx_base);
+
+ return 0;
+}
+
void mt792x_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control,
struct sk_buff *skb)
{
@@ -663,6 +679,7 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw)
struct mt792x_phy *phy = mt792x_hw_phy(hw);
struct mt792x_dev *dev = phy->dev;
struct wiphy *wiphy = hw->wiphy;
+ int err;
hw->queues = 4;
if (dev->has_eht) {
@@ -683,15 +700,17 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw)
hw->vif_data_size = sizeof(struct mt792x_vif);
hw->chanctx_data_size = sizeof(struct mt792x_chanctx);
- if (dev->fw_features & MT792x_FW_CAP_CNM) {
+ if (dev->fw_features & MT792x_FW_CAP_CNM)
wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
- wiphy->iface_combinations = if_comb_chanctx;
- wiphy->n_iface_combinations = ARRAY_SIZE(if_comb_chanctx);
- } else {
+ else
wiphy->flags &= ~WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
- wiphy->iface_combinations = if_comb;
- wiphy->n_iface_combinations = ARRAY_SIZE(if_comb);
- }
+
+ err = mt792x_setup_iface_combinations(dev);
+ if (err)
+ return err;
+
+ wiphy->iface_combinations = dev->iface_combinations;
+ wiphy->n_iface_combinations = dev->n_iface_combinations;
wiphy->flags &= ~(WIPHY_FLAG_IBSS_RSN | WIPHY_FLAG_4ADDR_AP |
WIPHY_FLAG_4ADDR_STATION);
wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
@@ -699,6 +718,7 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw)
BIT(NL80211_IFTYPE_P2P_CLIENT) |
BIT(NL80211_IFTYPE_P2P_GO) |
BIT(NL80211_IFTYPE_P2P_DEVICE);
+
wiphy->max_scan_ie_len = MT76_CONNAC_SCAN_IE_LEN;
wiphy->max_scan_ssids = 4;
wiphy->max_sched_scan_plan_interval =
--
2.43.0
^ permalink raw reply related
* [PATCH v2 7/9] wifi: mt76: mt7925: wire up NAN operations
From: Sean Wang @ 2026-06-25 0:18 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi
Cc: chengwei.yu, yu-ching.liu, jenhao.yang, posh.sun, linux-wireless,
linux-mediatek, Sean Wang
In-Reply-To: <20260625001834.475094-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Wire mac80211 NAN start, stop and change_conf callbacks to the mt7925 NAN
MCU helpers. Track the active NAN vif and notify mac80211 on cluster join
events.
Initialize NAN PHY capabilities after the supported bands are ready.
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>
---
.../net/wireless/mediatek/mt76/mt7925/init.c | 29 +++
.../net/wireless/mediatek/mt76/mt7925/main.c | 201 ++++++++++++++-
.../net/wireless/mediatek/mt76/mt7925/nan.c | 239 +++++++++++++++---
.../net/wireless/mediatek/mt76/mt7925/nan.h | 2 +
drivers/net/wireless/mediatek/mt76/mt792x.h | 3 +
5 files changed, 430 insertions(+), 44 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
index e85b0d104fbe..1b44f5c8fb0d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
@@ -152,6 +152,33 @@ static int mt7925_init_hardware(struct mt792x_dev *dev)
return 0;
}
+static int mt7925_init_nan_cap(struct mt76_dev *mdev)
+{
+ struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76);
+ const struct ieee80211_sta_he_cap *he_cap;
+ struct ieee80211_supported_band *sband;
+ struct wiphy *wiphy = mdev->hw->wiphy;
+
+ if (!(dev->fw_features & MT792x_FW_CAP_NAN))
+ return 0;
+
+ sband = wiphy->bands[NL80211_BAND_2GHZ];
+ if (sband)
+ wiphy->nan_capa.phy.ht = sband->ht_cap;
+
+ sband = wiphy->bands[NL80211_BAND_5GHZ];
+ if (sband)
+ wiphy->nan_capa.phy.vht = sband->vht_cap;
+
+ sband = wiphy->bands[NL80211_BAND_2GHZ];
+ he_cap = sband ? ieee80211_get_he_iftype_cap(sband, NL80211_IFTYPE_NAN)
+ : NULL;
+ if (he_cap)
+ wiphy->nan_capa.phy.he = *he_cap;
+
+ return 0;
+}
+
static void mt7925_init_work(struct work_struct *work)
{
struct mt792x_dev *dev = container_of(work, struct mt792x_dev,
@@ -172,6 +199,8 @@ static void mt7925_init_work(struct work_struct *work)
return;
}
+ dev->mt76.init_wiphy = mt7925_init_nan_cap;
+
ret = mt76_register_device(&dev->mt76, true, mt76_rates,
ARRAY_SIZE(mt76_rates));
if (ret) {
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index a9059866b701..ddb637d6a3c3 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -11,6 +11,7 @@
#include "regd.h"
#include "mcu.h"
#include "mac.h"
+#include "nan.h"
static void
mt7925_init_he_caps(struct mt792x_phy *phy, enum nl80211_band band,
@@ -412,7 +413,8 @@ static int mt7925_mac_link_bss_add(struct mt792x_dev *dev,
0 : mconf->mt76.idx % MT76_CONNAC_MAX_WMM_SETS;
mconf->mt76.link_idx = hweight16(mvif->valid_links);
- if (mvif->phy->mt76->chandef.chan->band != NL80211_BAND_2GHZ)
+ if (mvif->phy->mt76->chandef.chan &&
+ mvif->phy->mt76->chandef.chan->band != NL80211_BAND_2GHZ)
mconf->mt76.basic_rates_idx = MT792x_BASIC_RATES_TBL + 4;
else
mconf->mt76.basic_rates_idx = MT792x_BASIC_RATES_TBL;
@@ -474,12 +476,32 @@ mt7925_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
INIT_WORK(&mvif->csa_work, mt7925_csa_work);
timer_setup(&mvif->csa_timer, mt792x_csa_timer, 0);
+ if (vif->type == NL80211_IFTYPE_NAN)
+ dev->nan_vif = vif;
out:
mt792x_mutex_release(dev);
return ret;
}
+static void
+mt7925_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+ struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+ struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ struct mt792x_bss_conf *mconf;
+
+ mt792x_mutex_acquire(dev);
+
+ if (dev->nan_vif == vif)
+ dev->nan_vif = NULL;
+
+ mconf = mt792x_link_conf_to_mconf(&vif->bss_conf);
+ mt792x_mac_link_bss_remove(dev, mconf, &mvif->sta.deflink);
+
+ mt792x_mutex_release(dev);
+}
+
static void mt7925_roc_iter(void *priv, u8 *mac,
struct ieee80211_vif *vif)
{
@@ -1217,19 +1239,36 @@ static void mt7925_mac_link_sta_assoc(struct mt76_dev *mdev,
int mt7925_mac_sta_event(struct mt76_dev *mdev, struct ieee80211_vif *vif,
struct ieee80211_sta *sta, enum mt76_sta_event ev)
{
+ struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76);
struct ieee80211_link_sta *link_sta = &sta->deflink;
- if (ev != MT76_STA_EVENT_ASSOC)
- return 0;
+ switch (ev) {
+ case MT76_STA_EVENT_ASSOC:
+ if (ieee80211_vif_is_mld(vif)) {
+ struct mt792x_sta *msta =
+ (struct mt792x_sta *)sta->drv_priv;
- if (ieee80211_vif_is_mld(vif)) {
- struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv;
+ link_sta = mt792x_sta_to_link_sta(vif, sta,
+ msta->deflink_id);
+ mt7925_mac_set_links(mdev, vif);
+ }
- link_sta = mt792x_sta_to_link_sta(vif, sta, msta->deflink_id);
- mt7925_mac_set_links(mdev, vif);
- }
+ mt7925_mac_link_sta_assoc(mdev, vif, link_sta);
+ break;
+ case MT76_STA_EVENT_AUTHORIZE:
+ if (vif->type == NL80211_IFTYPE_NAN_DATA) {
+ int ret;
- mt7925_mac_link_sta_assoc(mdev, vif, link_sta);
+ mt792x_mutex_acquire(dev);
+ ret = mt792x_nan_map_sta_rec(mdev, vif, sta);
+ mt792x_mutex_release(dev);
+
+ return ret;
+ }
+ break;
+ default:
+ break;
+ }
return 0;
}
@@ -1357,6 +1396,36 @@ void mt7925_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif,
struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv;
struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+ /* Release NAN peer record before tearing down the STA. */
+ if (vif->type == NL80211_IFTYPE_NAN ||
+ vif->type == NL80211_IFTYPE_NAN_DATA) {
+ int ret = mt792x_nan_set_peer_rec(mdev, sta);
+
+ if (ret)
+ dev_err(mdev->dev,
+ "NAN: failed to deactivate peer record: %d\n",
+ ret);
+ }
+
+ /* Release NDP context ID for NAN_DATA sta. */
+ if (vif->type == NL80211_IFTYPE_NAN_DATA) {
+ struct ieee80211_sta *nmi_sta;
+
+ rcu_read_lock();
+ nmi_sta = rcu_dereference(sta->nmi);
+ if (nmi_sta) {
+ struct mt792x_sta *nmi_msta =
+ (struct mt792x_sta *)nmi_sta->drv_priv;
+
+ if (msta->nan_sched.ndp_ctx_assigned) {
+ clear_bit(msta->nan_sched.ndp_ctx_id,
+ &nmi_msta->nan_sched.ndp_ctx_bitmap);
+ msta->nan_sched.ndp_ctx_assigned = false;
+ }
+ }
+ rcu_read_unlock();
+ }
+
if (ieee80211_vif_is_mld(vif)) {
mt7925_mac_sta_remove_links(dev, vif, sta, msta->valid_links);
mt7925_mcu_del_dev(mdev, vif);
@@ -2059,6 +2128,11 @@ static void mt7925_vif_cfg_changed(struct ieee80211_hw *hw,
}
mt792x_mutex_release(dev);
+
+ if (vif->type == NL80211_IFTYPE_NAN &&
+ changed & BSS_CHANGED_NAN_LOCAL_SCHED) {
+ mt7925_nan_local_sched_changed(dev, vif);
+ }
}
static void mt7925_link_info_changed(struct ieee80211_hw *hw,
@@ -2481,12 +2555,115 @@ static void mt7925_channel_switch_rx_beacon(struct ieee80211_hw *hw,
}
}
+static int mt7925_start_nan(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct cfg80211_nan_conf *conf)
+{
+ struct ieee80211_bss_conf *link_conf = &vif->bss_conf;
+ struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ struct ieee80211_channel *chan;
+ int err = 0;
+
+ mt792x_mutex_acquire(dev);
+
+ chan = conf->band_cfgs[NL80211_BAND_2GHZ].chan;
+ if (!chan) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ cfg80211_chandef_create(&link_conf->chanreq.oper, chan,
+ NL80211_CHAN_NO_HT);
+
+ err = mt7925_mcu_add_bss_info(&dev->phy, NULL, link_conf,
+ NULL, true);
+ if (err < 0)
+ goto out;
+
+ dev->nan_vif = vif;
+
+ err = mt7925_nan_set_nmi_addr(dev, vif->addr);
+ if (err)
+ goto rollback_bss;
+
+ err = mt7925_nan_enable(vif, dev, conf);
+ if (err)
+ goto rollback_bss;
+
+ goto out;
+
+rollback_bss:
+ dev->nan_vif = NULL;
+ mt7925_mcu_add_bss_info(&dev->phy, NULL, link_conf, NULL, false);
+
+out:
+ mt792x_mutex_release(dev);
+
+ return err;
+}
+
+static int mt7925_stop_nan(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct ieee80211_bss_conf *link_conf = &vif->bss_conf;
+ struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ int err, ret;
+
+ mt792x_mutex_acquire(dev);
+
+ err = mt7925_nan_disable(vif, dev);
+
+ ret = mt7925_mcu_add_bss_info(&dev->phy, NULL, link_conf,
+ NULL, false);
+ if (!err)
+ err = ret;
+
+ if (dev->nan_vif == vif)
+ dev->nan_vif = NULL;
+
+ mt792x_mutex_release(dev);
+
+ return err;
+}
+
+static int mt7925_nan_change_conf(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct cfg80211_nan_conf *conf,
+ u32 changes)
+{
+ struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ int err = 0;
+
+ mt792x_mutex_acquire(dev);
+
+ err = mt7925_nan_change_configure(vif, dev, conf);
+
+ mt792x_mutex_release(dev);
+
+ return err;
+}
+
+static int mt7925_nan_peer_sched_changed(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta)
+{
+ struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ int err = 0;
+
+ mt792x_mutex_acquire(dev);
+
+ err = mt792x_nan_set_peer_schedule(dev, sta);
+
+ mt792x_mutex_release(dev);
+
+ return err;
+}
+
const struct ieee80211_ops mt7925_ops = {
.tx = mt792x_tx,
.start = mt7925_start,
.stop = mt792x_stop,
.add_interface = mt7925_add_interface,
- .remove_interface = mt792x_remove_interface,
+ .remove_interface = mt7925_remove_interface,
.config = mt7925_config,
.conf_tx = mt7925_conf_tx,
.configure_filter = mt7925_configure_filter,
@@ -2550,6 +2727,10 @@ const struct ieee80211_ops mt7925_ops = {
.channel_switch = mt7925_channel_switch,
.abort_channel_switch = mt7925_abort_channel_switch,
.channel_switch_rx_beacon = mt7925_channel_switch_rx_beacon,
+ .start_nan = mt7925_start_nan,
+ .stop_nan = mt7925_stop_nan,
+ .nan_change_conf = mt7925_nan_change_conf,
+ .nan_peer_sched_changed = mt7925_nan_peer_sched_changed,
};
EXPORT_SYMBOL_GPL(mt7925_ops);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
index dc7aa2cd9449..849952c6ac21 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
@@ -31,6 +31,8 @@ static void mt7925_nan_set_5g_channel(struct mt792x_dev *dev,
if (!mt7925_regd_is_valid_channel(dev, NL80211_BAND_5GHZ, chan))
return;
+ req->config_support_5g = 1;
+ req->support_5g_val = 1;
req->config_5g_channel = 1;
if (chan->hw_value == NAN_5G_LOW_DISC_CHANNEL)
@@ -41,6 +43,16 @@ static void mt7925_nan_set_5g_channel(struct mt792x_dev *dev,
req->channel_5g_val = cpu_to_le32(ch5g);
}
+static void mt7925_nan_set_2g_support(struct mt7925_nan_enable_req_tlv *req,
+ struct cfg80211_nan_conf *conf)
+{
+ if (!conf->band_cfgs[NL80211_BAND_2GHZ].chan)
+ return;
+
+ req->config_2dot4g_support = 1;
+ req->support_2dot4g_val = 1;
+}
+
static void mt7925_nan_set_cluster_id(struct mt7925_nan_enable_req_tlv *req,
const u8 *cluster_id)
{
@@ -131,6 +143,38 @@ mt7925_nan_update_conf(struct mt792x_vif *mvif,
memcpy(mvif->nan.conf.cluster_id, conf->cluster_id, ETH_ALEN);
}
+int mt7925_nan_set_nmi_addr(struct mt792x_dev *dev, const u8 *addr)
+{
+ struct mt76_dev *mdev;
+ struct {
+ u8 rsv[4];
+ struct mt7925_nan_nmi_addr_tlv nmi_addr_tlv;
+ } nmi_cmd = {
+ .rsv = { 0 },
+ .nmi_addr_tlv = {
+ .tag = cpu_to_le16(NAN_UNI_CMD_CHANGE_NMI_ADDRESS),
+ .len = cpu_to_le16(sizeof(struct mt7925_nan_nmi_addr_tlv)),
+ },
+ };
+ int ret;
+
+ if (!dev || !addr)
+ return -EINVAL;
+
+ if (is_zero_ether_addr(addr) || is_multicast_ether_addr(addr)) {
+ dev_err(dev->mt76.dev, "NAN: invalid NMI address %pM\n", addr);
+ return -EINVAL;
+ }
+
+ mdev = &dev->mt76;
+ memcpy(nmi_cmd.nmi_addr_tlv.nmi_addr, addr, ETH_ALEN);
+
+ ret = mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nmi_cmd,
+ sizeof(nmi_cmd), true);
+
+ return ret;
+}
+
int mt7925_nan_enable(struct ieee80211_vif *vif,
struct mt792x_dev *dev,
struct cfg80211_nan_conf *conf)
@@ -152,12 +196,14 @@ int mt7925_nan_enable(struct ieee80211_vif *vif,
},
};
struct mt7925_nan_enable_req_tlv *p_nan_req_tlv = &nan_cmd.nan_req_tlv;
+ int ret;
if (!vif || !dev || !conf)
return -EINVAL;
p_nan_req_tlv->master_pref = conf->master_pref;
+ mt7925_nan_set_2g_support(p_nan_req_tlv, conf);
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);
@@ -167,7 +213,9 @@ int mt7925_nan_enable(struct ieee80211_vif *vif,
mt7925_nan_update_conf(mvif, conf);
- return mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nan_cmd, sizeof(nan_cmd), true);
+ ret = mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nan_cmd, sizeof(nan_cmd), true);
+
+ return ret;
}
int mt7925_nan_disable(struct ieee80211_vif *vif, struct mt792x_dev *dev)
@@ -427,7 +475,7 @@ mt7925_nan_mcu_handle_de_event(struct mt792x_dev *dev, struct tlv *tlv)
if (de_evt->event_type != NAN_EVENT_ID_JOINED_CLUSTER)
return;
- if (!ieee80211_vif_nan_started(dev->nan_vif)) {
+ if (!dev->nan_vif || !ieee80211_vif_nan_started(dev->nan_vif)) {
dev_warn(dev->mt76.dev, "nan: joined-cluster event but NAN not started\n");
return;
}
@@ -592,16 +640,21 @@ void mt7925_nan_local_sched_changed(struct mt792x_dev *dev,
{
struct mt7925_nan_common_hdr *hdr;
struct mt76_dev *mdev;
+ bool deferred;
struct sk_buff *skb;
+ int ret = -ENOMEM;
if (!dev || !vif)
return;
mdev = &dev->mt76;
+ deferred = vif->cfg.nan_sched.deferred;
+
+ mt792x_mutex_acquire(dev);
skb = mt76_mcu_msg_alloc(mdev, NULL, MT7925_NAN_AVAIL_MAX_SIZE);
if (!skb)
- return;
+ goto out;
hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr));
memset(hdr, 0, sizeof(*hdr));
@@ -609,11 +662,22 @@ void mt7925_nan_local_sched_changed(struct mt792x_dev *dev,
if (mt7925_nan_avail_ctrl_tlv(skb, vif) ||
mt7925_nan_avail_tlv(skb, vif)) {
dev_kfree_skb(skb);
- return;
+ goto out;
}
- mt76_mcu_skb_send_msg(mdev, skb,
- MCU_UNI_CMD(NAN), true);
+ ret = mt76_mcu_skb_send_msg(mdev, skb,
+ MCU_UNI_CMD(NAN), true);
+out:
+ mt792x_mutex_release(dev);
+
+ if (deferred) {
+ if (ret)
+ dev_err(mdev->dev,
+ "NAN: local schedule update failed: %d\n",
+ ret);
+
+ ieee80211_nan_sched_update_done(vif);
+ }
}
static int mt7925_nan_peer_rec_tlv(struct sk_buff *skb,
@@ -641,6 +705,23 @@ static int mt7925_nan_peer_rec_tlv(struct sk_buff *skb,
return 0;
}
+static u8 mt7925_nan_get_supported_bands(struct mt792x_vif *mvif)
+{
+ struct wiphy *wiphy;
+ u8 bands = 0;
+
+ if (!mvif || !mvif->phy)
+ return BIT(NAN_SUPPORTED_BAND_ID_2P4G);
+
+ wiphy = mvif->phy->mt76->hw->wiphy;
+ if (wiphy->nan_supported_bands & BIT(NL80211_BAND_2GHZ))
+ bands |= BIT(NAN_SUPPORTED_BAND_ID_2P4G);
+ if (wiphy->nan_supported_bands & BIT(NL80211_BAND_5GHZ))
+ bands |= BIT(NAN_SUPPORTED_BAND_ID_5G);
+
+ return bands ?: BIT(NAN_SUPPORTED_BAND_ID_2P4G);
+}
+
static int mt7925_nan_peer_cap_tlv(struct sk_buff *skb,
struct ieee80211_sta *sta,
struct mt792x_sta *msta)
@@ -667,7 +748,8 @@ static int mt7925_nan_peer_cap_tlv(struct sk_buff *skb,
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->supported_bands =
+ mt7925_nan_get_supported_bands(msta->vif);
peer_cap_tlv->max_chnl_switch_time = sched->max_chan_switch;
for (i = 0; i < sched->n_channels; i++) {
@@ -696,38 +778,52 @@ static int mt7925_nan_peer_cap_tlv(struct sk_buff *skb,
static void
mt7925_nan_fill_crb_committed(struct mt7925_nan_sched_update_crb_tlv *crb_tlv,
+ struct ieee80211_vif *vif,
struct ieee80211_nan_peer_sched *sched)
{
+ struct ieee80211_nan_sched_cfg *local_sched;
+ u8 local_map_id;
u32 m, slot;
- if (!sched)
+ if (!vif || !sched)
return;
+ local_sched = &vif->cfg.nan_sched;
+ local_map_id = mt7925_nan_avail_attr_ctrl(local_sched) &
+ NAN_AVAIL_CTRL_MAPID;
+
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];
+ u32 avail_map = 0;
if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
continue;
tl->map_id = map->map_id;
+ tl->local_map_id = local_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];
+ struct ieee80211_nan_channel *local_ch;
+ struct ieee80211_nan_channel *peer_ch;
+
+ local_ch = local_sched->schedule[slot];
+ peer_ch = map->slots[slot];
- if (!ch || !ch->chanctx_conf)
+ if (!local_ch || !local_ch->chanctx_conf ||
+ !peer_ch || !peer_ch->chanctx_conf)
continue;
- tl->avail_map[0] |= cpu_to_le32(BIT(slot));
+ if (local_ch->chanctx_conf != peer_ch->chanctx_conf)
+ continue;
+
+ avail_map |= BIT(slot);
}
+
+ tl->avail_map[0] = cpu_to_le32(avail_map);
}
}
@@ -753,7 +849,8 @@ static int mt7925_nan_update_crb_tlv(struct sk_buff *skb,
crb_tlv->is_use_ranging = false;
crb_tlv->comm_ndc_ctrl.is_valid = false;
- mt7925_nan_fill_crb_committed(crb_tlv, sta->nan_sched);
+ mt7925_nan_fill_crb_committed(crb_tlv, msta->vif->phy->dev->nan_vif,
+ sta->nan_sched);
return 0;
}
@@ -762,10 +859,12 @@ int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev,
struct ieee80211_sta *sta)
{
struct mt7925_nan_common_hdr *hdr;
+ bool idx_allocated = false;
struct mt792x_sta *msta;
struct mt792x_nan *nan;
struct mt76_dev *mdev;
struct sk_buff *skb;
+ int ret;
if (!dev || !sta)
return -EINVAL;
@@ -794,21 +893,36 @@ int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev,
set_bit(idx, &nan->conn_bitmap);
msta->nan_sched.sch_idx = idx;
msta->nan_sched.idx_assigned = true;
+ idx_allocated = 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;
+ ret = -ENOMEM;
+ goto free_skb;
}
}
if (mt7925_nan_update_crb_tlv(skb, sta, msta)) {
- dev_kfree_skb(skb);
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto free_skb;
}
- return mt76_mcu_skb_send_msg(mdev, skb,
- MCU_UNI_CMD(NAN), true);
+ ret = mt76_mcu_skb_send_msg(mdev, skb, MCU_UNI_CMD(NAN), true);
+ if (ret && idx_allocated)
+ goto clear_idx;
+
+ return ret;
+
+free_skb:
+ dev_kfree_skb(skb);
+ if (!idx_allocated)
+ return ret;
+
+clear_idx:
+ clear_bit(msta->nan_sched.sch_idx, &nan->conn_bitmap);
+ msta->nan_sched.idx_assigned = false;
+
+ return ret;
}
int mt792x_nan_set_peer_rec(struct mt76_dev *mdev,
@@ -818,6 +932,7 @@ int mt792x_nan_set_peer_rec(struct mt76_dev *mdev,
struct mt792x_sta *msta;
struct mt792x_nan *nan;
struct sk_buff *skb;
+ int ret;
if (!mdev || !sta)
return -EINVAL;
@@ -844,11 +959,14 @@ int mt792x_nan_set_peer_rec(struct mt76_dev *mdev,
return -ENOMEM;
}
+ ret = mt76_mcu_skb_send_msg(mdev, skb, MCU_UNI_CMD(NAN), true);
+ if (ret)
+ return ret;
+
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);
+ return 0;
}
int mt792x_nan_map_sta_rec(struct mt76_dev *mdev,
@@ -859,16 +977,21 @@ int mt792x_nan_map_sta_rec(struct mt76_dev *mdev,
struct mt7925_nan_common_hdr *hdr;
struct ieee80211_sta *nmi_sta;
struct mt792x_sta *nmi_msta;
+ struct mt792x_vif *mvif;
struct mt792x_sta *msta;
u8 nmi_addr[ETH_ALEN];
struct sk_buff *skb;
int ndp_ctx_id = 0;
+ int ret = -ENOMEM;
+ struct mt792x_dev *dev;
struct tlv *tlv;
if (!mdev || !vif || !sta)
return -EINVAL;
+ dev = container_of(mdev, struct mt792x_dev, mt76);
msta = (struct mt792x_sta *)sta->drv_priv;
+ mvif = (struct mt792x_vif *)vif->drv_priv;
rcu_read_lock();
nmi_sta = rcu_dereference(sta->nmi);
@@ -882,21 +1005,51 @@ int mt792x_nan_map_sta_rec(struct mt76_dev *mdev,
memcpy(nmi_addr, nmi_sta->addr, ETH_ALEN);
nmi_msta = (struct mt792x_sta *)nmi_sta->drv_priv;
+ if (!nmi_msta->nan_sched.idx_assigned) {
+ if (!nmi_sta->nan_sched) {
+ rcu_read_unlock();
+ dev_err(mdev->dev,
+ "NAN: peer schedule missing for NDI sta %pM\n",
+ sta->addr);
+ return -EAGAIN;
+ }
+
+ rcu_read_unlock();
+ ret = mt792x_nan_set_peer_schedule(dev, nmi_sta);
+ if (ret)
+ return ret;
+
+ 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;
+ }
+
+ 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;
+ if (ndp_ctx_id >= NAN_MAX_NDP_CXT) {
+ rcu_read_unlock();
+ return -ENOSPC;
+ }
+
+ set_bit(ndp_ctx_id, &nmi_msta->nan_sched.ndp_ctx_bitmap);
rcu_read_unlock();
msta->nan_sched.ndp_ctx_id = ndp_ctx_id;
+ msta->nan_sched.ndp_ctx_assigned = true;
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;
+ goto clear_ndp_ctx;
hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr));
memset(hdr, 0, sizeof(*hdr));
@@ -905,16 +1058,34 @@ int mt792x_nan_map_sta_rec(struct mt76_dev *mdev,
sizeof(struct mt7925_nan_sched_map_sta_rec_tlv));
if (!tlv) {
dev_kfree_skb(skb);
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto clear_ndp_ctx;
}
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;
+ map_tlv->role_idx = cpu_to_le32(mvif->bss_conf.mt76.idx);
memcpy(map_tlv->ndi_addr, vif->addr, ETH_ALEN);
- return mt76_mcu_skb_send_msg(mdev, skb,
- MCU_UNI_CMD(NAN), true);
+ ret = mt76_mcu_skb_send_msg(mdev, skb,
+ MCU_UNI_CMD(NAN), true);
+ if (ret)
+ goto clear_ndp_ctx;
+
+ return 0;
+
+clear_ndp_ctx:
+ rcu_read_lock();
+ nmi_sta = rcu_dereference(sta->nmi);
+ if (nmi_sta) {
+ nmi_msta = (struct mt792x_sta *)nmi_sta->drv_priv;
+ clear_bit(msta->nan_sched.ndp_ctx_id,
+ &nmi_msta->nan_sched.ndp_ctx_bitmap);
+ }
+ rcu_read_unlock();
+ msta->nan_sched.ndp_ctx_assigned = false;
+
+ return ret ?: -ENOMEM;
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
index 1895d0be8ee4..d308eadb3636 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
@@ -422,6 +422,8 @@ int mt7925_nan_change_configure(struct ieee80211_vif *vif,
void mt7925_nan_mcu_event(struct mt792x_dev *dev, struct sk_buff *skb);
+int mt7925_nan_set_nmi_addr(struct mt792x_dev *dev, const u8 *addr);
+
void mt7925_nan_local_sched_changed(struct mt792x_dev *dev,
struct ieee80211_vif *vif);
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 89c3f84a776a..9d5a2adc81f6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -23,6 +23,7 @@
#define MT792x_CFEND_RATE_11B 0x03 /* 11B LP, 11M */
#define MT792x_FW_TAG_FEATURE 4
+#define MT792x_FW_CAP_NAN BIT(5)
#define MT792x_FW_CAP_CNM BIT(7)
#define MT792x_CHIP_CAP_CLC_EVT_EN BIT(0)
@@ -116,10 +117,12 @@ struct mt792x_link_sta {
};
struct mt792x_sta_nan_sched {
+ /* protects NAN peer schedule state */
u16 committed_dw;
u32 sch_idx;
bool idx_assigned;
unsigned long ndp_ctx_bitmap;
+ bool ndp_ctx_assigned;
u8 ndp_ctx_id; /* assigned NDP context ID (for NDI sta) */
struct {
u8 map_id;
--
2.43.0
^ permalink raw reply related
* [PATCH v2 6/9] wifi: mt76: add init_wiphy callback
From: Sean Wang @ 2026-06-25 0:18 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi
Cc: chengwei.yu, yu-ching.liu, jenhao.yang, posh.sun, linux-wireless,
linux-mediatek, Sean Wang
In-Reply-To: <20260625001834.475094-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Add an optional callback for drivers to finalize wiphy state after mt76
has initialized the supported bands and before registration.
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>
---
drivers/net/wireless/mediatek/mt76/mac80211.c | 7 +++++++
drivers/net/wireless/mediatek/mt76/mt76.h | 3 +++
2 files changed, 10 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 13c4e8abe281..c4cbf7195b80 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -681,6 +681,7 @@ mt76_alloc_device(struct device *pdev, unsigned int size,
dev = hw->priv;
dev->hw = hw;
dev->dev = pdev;
+ dev->init_wiphy = NULL;
dev->drv = drv_ops;
dev->dma_dev = pdev;
@@ -779,6 +780,12 @@ int mt76_register_device(struct mt76_dev *dev, bool vht,
mt76_check_sband(&dev->phy, &phy->sband_5g, NL80211_BAND_5GHZ);
mt76_check_sband(&dev->phy, &phy->sband_6g, NL80211_BAND_6GHZ);
+ if (dev->init_wiphy) {
+ ret = dev->init_wiphy(dev);
+ if (ret)
+ return ret;
+ }
+
if (IS_ENABLED(CONFIG_MT76_LEDS)) {
ret = mt76_led_init(phy);
if (ret)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 07955555f84d..0d185675689a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -940,6 +940,9 @@ struct mt76_dev {
const struct mt76_bus_ops *bus;
const struct mt76_driver_ops *drv;
const struct mt76_mcu_ops *mcu_ops;
+
+ /* Optional callback to finalize wiphy state before registration. */
+ int (*init_wiphy)(struct mt76_dev *dev);
struct device *dev;
struct device *dma_dev;
--
2.43.0
^ permalink raw reply related
* [PATCH v2 5/9] wifi: mt76: mt7925: add NAN MCU handling
From: Sean Wang @ 2026-06-25 0:18 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi
Cc: chengwei.yu, yu-ching.liu, jenhao.yang, posh.sun, linux-wireless,
linux-mediatek, Sean Wang
In-Reply-To: <20260625001834.475094-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Route NAN MCU responses and unsolicited events through the mt7925 MCU
path, and handle NAN-specific BSS and station TLVs.
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>
---
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 99 ++++++++++++++++---
1 file changed, 84 insertions(+), 15 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index cff91b4eeac6..f58f24583453 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -7,10 +7,17 @@
#include "regd.h"
#include "mcu.h"
#include "mac.h"
+#include "nan.h"
#define MT_STA_BFER BIT(0)
#define MT_STA_BFEE BIT(1)
+static bool mt7925_vif_is_nan(struct ieee80211_vif *vif)
+{
+ return vif->type == NL80211_IFTYPE_NAN ||
+ vif->type == NL80211_IFTYPE_NAN_DATA;
+}
+
int mt7925_mcu_parse_response(struct mt76_dev *mdev, int cmd,
struct sk_buff *skb, int seq)
{
@@ -37,7 +44,8 @@ int mt7925_mcu_parse_response(struct mt76_dev *mdev, int cmd,
cmd == MCU_UNI_CMD(BSS_INFO_UPDATE) ||
cmd == MCU_UNI_CMD(STA_REC_UPDATE) ||
cmd == MCU_UNI_CMD(OFFLOAD) ||
- cmd == MCU_UNI_CMD(SUSPEND)) {
+ cmd == MCU_UNI_CMD(SUSPEND) ||
+ cmd == MCU_UNI_CMD(NAN)) {
struct mt7925_mcu_uni_event *event;
skb_pull(skb, sizeof(*rxd));
@@ -631,6 +639,9 @@ mt7925_mcu_uni_rx_unsolicited_event(struct mt792x_dev *dev,
dev->fw_assert = true;
mt76_connac_mcu_coredump_event(&dev->mt76, skb, &dev->coredump);
return;
+ case MCU_UNI_EVENT_NAN:
+ mt7925_nan_mcu_event(dev, skb);
+ break;
default:
break;
}
@@ -1835,9 +1846,20 @@ mt7925_mcu_sta_phy_tlv(struct sk_buff *skb,
tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_PHY, sizeof(*phy));
phy = (struct sta_rec_phy *)tlv;
- phy->phy_type = mt76_connac_get_phy_mode_v2(mvif->phy->mt76, vif,
- chandef->chan->band,
- link_sta);
+
+ if (mt7925_vif_is_nan(vif)) {
+ enum nl80211_band band = chandef->chan ? chandef->chan->band
+ : NL80211_BAND_2GHZ;
+ phy->phy_type = PHY_TYPE_BIT_OFDM | PHY_TYPE_BIT_ERP;
+ phy->phy_type |= mt76_connac_get_phy_mode_v2(mvif->phy->mt76, vif,
+ band,
+ link_sta);
+ } else {
+ phy->phy_type = mt76_connac_get_phy_mode_v2(mvif->phy->mt76, vif,
+ chandef->chan->band,
+ link_sta);
+ }
+
phy->basic_rate = cpu_to_le16((u16)link_conf->basic_rates);
if (link_sta->ht_cap.ht_supported) {
af = link_sta->ht_cap.ampdu_factor;
@@ -1910,11 +1932,15 @@ mt7925_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb,
mconf = mt792x_vif_to_link(mvif, link_sta->link_id);
chandef = mconf->mt76.ctx ? &mconf->mt76.ctx->def :
&link_conf->chanreq.oper;
- band = chandef->chan->band;
tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_RA, sizeof(*ra_info));
ra_info = (struct sta_rec_ra_info *)tlv;
+ if (mt7925_vif_is_nan(vif))
+ band = chandef->chan ? chandef->chan->band : NL80211_BAND_2GHZ;
+ else
+ band = chandef->chan->band;
+
supp_rates = link_sta->supp_rates[band];
if (band == NL80211_BAND_2GHZ)
supp_rates = FIELD_PREP(RA_LEGACY_OFDM, supp_rates >> 4) |
@@ -2561,6 +2587,29 @@ mt7925_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_vif *vif,
return mode;
}
+static void
+mt7925_mcu_bss_basic_tlv_nan(struct mt76_phy *phy,
+ struct ieee80211_vif *vif,
+ struct ieee80211_link_sta *link_sta,
+ struct mt76_connac_bss_basic_tlv *basic_req)
+{
+ u8 mode_2g, mode_5g;
+
+ mode_2g = mt7925_get_phy_mode_ext(phy, vif, NL80211_BAND_2GHZ,
+ link_sta);
+ mode_5g = mt7925_get_phy_mode_ext(phy, vif, NL80211_BAND_5GHZ,
+ link_sta);
+ basic_req->phymode_ext = mode_2g | mode_5g;
+
+ basic_req->nonht_basic_phy = cpu_to_le16(PHY_TYPE_ERP_INDEX);
+
+ mode_2g = mt76_connac_get_phy_mode(phy, vif, NL80211_BAND_2GHZ,
+ link_sta);
+ mode_5g = mt76_connac_get_phy_mode(phy, vif, NL80211_BAND_5GHZ,
+ link_sta);
+ basic_req->phymode = (mode_2g | mode_5g) & ~PHY_MODE_B;
+}
+
static void
mt7925_mcu_bss_basic_tlv(struct sk_buff *skb,
struct ieee80211_bss_conf *link_conf,
@@ -2575,7 +2624,7 @@ mt7925_mcu_bss_basic_tlv(struct sk_buff *skb,
struct mt792x_bss_conf *mconf = mt792x_link_conf_to_mconf(link_conf);
struct cfg80211_chan_def *chandef = ctx ? &ctx->def :
&link_conf->chanreq.oper;
- enum nl80211_band band = chandef->chan->band;
+ enum nl80211_band band = NL80211_BAND_2GHZ;
struct mt76_connac_bss_basic_tlv *basic_req;
struct tlv *tlv;
int conn_type;
@@ -2588,16 +2637,25 @@ mt7925_mcu_bss_basic_tlv(struct sk_buff *skb,
mconf->mt76.omac_idx;
basic_req->hw_bss_idx = idx;
- basic_req->phymode_ext = mt7925_get_phy_mode_ext(phy, vif, band,
- link_sta);
+ if (mt7925_vif_is_nan(vif)) {
+ mt7925_mcu_bss_basic_tlv_nan(phy, vif, link_sta, basic_req);
+ } else {
+ band = chandef->chan->band;
+ basic_req->phymode_ext = mt7925_get_phy_mode_ext(phy, vif, band,
+ link_sta);
- if (band == NL80211_BAND_2GHZ)
- basic_req->nonht_basic_phy = cpu_to_le16(PHY_TYPE_ERP_INDEX);
- else
- basic_req->nonht_basic_phy = cpu_to_le16(PHY_TYPE_OFDM_INDEX);
+ if (band == NL80211_BAND_2GHZ)
+ basic_req->nonht_basic_phy =
+ cpu_to_le16(PHY_TYPE_ERP_INDEX);
+ else
+ basic_req->nonht_basic_phy =
+ cpu_to_le16(PHY_TYPE_OFDM_INDEX);
+
+ memcpy(basic_req->bssid, link_conf->bssid, ETH_ALEN);
+ basic_req->phymode = mt76_connac_get_phy_mode(phy, vif, band,
+ link_sta);
+ }
- memcpy(basic_req->bssid, link_conf->bssid, ETH_ALEN);
- basic_req->phymode = mt76_connac_get_phy_mode(phy, vif, band, link_sta);
basic_req->bcn_interval = cpu_to_le16(link_conf->beacon_int);
basic_req->dtim_period = link_conf->dtim_period;
basic_req->bmc_tx_wlan_idx = cpu_to_le16(bmc_tx_wlan_idx);
@@ -2630,6 +2688,11 @@ mt7925_mcu_bss_basic_tlv(struct sk_buff *skb,
basic_req->conn_type = cpu_to_le32(CONNECTION_IBSS_ADHOC);
basic_req->active = true;
break;
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
+ basic_req->conn_type = cpu_to_le32(CONNECTION_NAN);
+ basic_req->active = enable;
+ break;
default:
WARN_ON(1);
break;
@@ -2688,10 +2751,11 @@ mt7925_mcu_bss_bmc_tlv(struct sk_buff *skb, struct mt792x_phy *phy,
struct ieee80211_chanctx_conf *ctx,
struct ieee80211_bss_conf *link_conf)
{
+ struct ieee80211_vif *vif = link_conf->vif;
struct cfg80211_chan_def *chandef = ctx ? &ctx->def :
&link_conf->chanreq.oper;
struct mt792x_bss_conf *mconf = mt792x_link_conf_to_mconf(link_conf);
- enum nl80211_band band = chandef->chan->band;
+ enum nl80211_band band = NL80211_BAND_2GHZ;
struct mt76_vif_link *mvif = &mconf->mt76;
struct bss_rate_tlv *bmc;
struct tlv *tlv;
@@ -2702,6 +2766,11 @@ mt7925_mcu_bss_bmc_tlv(struct sk_buff *skb, struct mt792x_phy *phy,
bmc = (struct bss_rate_tlv *)tlv;
+ if (mt7925_vif_is_nan(vif))
+ band = chandef->chan ? chandef->chan->band : NL80211_BAND_2GHZ;
+ else
+ band = chandef->chan->band;
+
if (band == NL80211_BAND_2GHZ)
bmc->basic_rate = cpu_to_le16(HR_DSSS_ERP_BASIC_RATE);
else
--
2.43.0
^ permalink raw reply related
* [PATCH v2 4/9] wifi: mt76: mt7925: add NAN MCU helpers
From: Sean Wang @ 2026-06-25 0:18 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi
Cc: chengwei.yu, yu-ching.liu, jenhao.yang, posh.sun, linux-wireless,
linux-mediatek, Sean Wang
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
^ permalink raw reply related
* [PATCH v2 3/9] wifi: mt76: connac: add NAN connection type
From: Sean Wang @ 2026-06-25 0:18 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi
Cc: chengwei.yu, yu-ching.liu, jenhao.yang, posh.sun, linux-wireless,
linux-mediatek, Sean Wang
In-Reply-To: <20260625001834.475094-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Introduce a dedicated NAN connection type for connac firmware and use it
for NAN interface device, BSS and station records.
Add the common NAN MCU command and event IDs used by mt7925.
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>
---
.../net/wireless/mediatek/mt76/mt76_connac_mcu.c | 14 ++++++++++++++
.../net/wireless/mediatek/mt76/mt76_connac_mcu.h | 4 ++++
2 files changed, 18 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
index 6596c9e198f4..67eba4dd9615 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
@@ -422,6 +422,10 @@ void mt76_connac_mcu_sta_basic_tlv(struct mt76_dev *dev, struct sk_buff *skb,
basic->conn_type = cpu_to_le32(CONNECTION_IBSS_ADHOC);
basic->aid = cpu_to_le16(link_sta->sta->aid);
break;
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
+ basic->conn_type = cpu_to_le32(CONNECTION_NAN);
+ break;
default:
WARN_ON(1);
break;
@@ -1217,6 +1221,11 @@ int mt76_connac_mcu_uni_add_dev(struct mt76_phy *phy,
case NL80211_IFTYPE_ADHOC:
basic_req.basic.conn_type = cpu_to_le32(CONNECTION_IBSS_ADHOC);
break;
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
+ basic_req.basic.conn_type = cpu_to_le32(CONNECTION_NAN);
+ basic_req.basic.conn_state = !enable;
+ break;
default:
WARN_ON(1);
break;
@@ -1625,6 +1634,11 @@ int mt76_connac_mcu_uni_add_bss(struct mt76_phy *phy,
case NL80211_IFTYPE_ADHOC:
basic_req.basic.conn_type = cpu_to_le32(CONNECTION_IBSS_ADHOC);
break;
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
+ basic_req.basic.conn_type = cpu_to_le32(CONNECTION_NAN);
+ basic_req.basic.active = enable;
+ break;
default:
WARN_ON(1);
break;
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index 78f633ad81a0..a9a4a87ae0a7 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -876,6 +876,7 @@ enum {
#define NETWORK_P2P BIT(17)
#define NETWORK_IBSS BIT(18)
#define NETWORK_WDS BIT(21)
+#define NETWORK_NAN BIT(22)
#define SCAN_FUNC_RANDOM_MAC BIT(0)
#define SCAN_FUNC_RNR_SCAN BIT(3)
@@ -888,6 +889,7 @@ enum {
#define CONNECTION_IBSS_ADHOC (STA_TYPE_ADHOC | NETWORK_IBSS)
#define CONNECTION_WDS (STA_TYPE_WDS | NETWORK_WDS)
#define CONNECTION_INFRA_BC (STA_TYPE_BC | NETWORK_INFRA)
+#define CONNECTION_NAN (NETWORK_NAN)
#define CONN_STATE_DISCONNECT 0
#define CONN_STATE_CONNECT 1
@@ -1074,6 +1076,7 @@ enum {
MCU_UNI_EVENT_THERMAL = 0x35,
MCU_UNI_EVENT_RSSI_MONITOR = 0x41,
MCU_UNI_EVENT_NIC_CAPAB = 0x43,
+ MCU_UNI_EVENT_NAN = 0x56,
MCU_UNI_EVENT_WED_RRO = 0x57,
MCU_UNI_EVENT_PER_STA_INFO = 0x6d,
MCU_UNI_EVENT_ALL_STA_INFO = 0x6e,
@@ -1313,6 +1316,7 @@ enum {
MCU_UNI_CMD_FIXED_RATE_TABLE = 0x40,
MCU_UNI_CMD_RSSI_MONITOR = 0x41,
MCU_UNI_CMD_TESTMODE_CTRL = 0x46,
+ MCU_UNI_CMD_NAN = 0x56,
MCU_UNI_CMD_RRO = 0x57,
MCU_UNI_CMD_OFFCH_SCAN_CTRL = 0x58,
MCU_UNI_CMD_PER_STA_INFO = 0x6d,
--
2.43.0
^ permalink raw reply related
* [PATCH v2 2/9] wifi: mt76: mt7925: guard BSS capability lookups
From: Sean Wang @ 2026-06-25 0:18 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi
Cc: chengwei.yu, yu-ching.liu, jenhao.yang, posh.sun, linux-wireless,
linux-mediatek, Sean Wang
In-Reply-To: <20260625001834.475094-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
mt7925 BSS setup may dereference missing channel data or query HE 6 GHz
capabilities for an iftype without HE support.
Guard both lookups before adding NAN paths that can use partially
configured BSS state.
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>
---
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 26 ++++++++++++++-----
1 file changed, 20 insertions(+), 6 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index e94fa544ff20..cff91b4eeac6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -2364,11 +2364,18 @@ void mt7925_mcu_bss_rlm_tlv(struct sk_buff *skb, struct mt76_phy *phy,
{
struct cfg80211_chan_def *chandef = ctx ? &ctx->def :
&link_conf->chanreq.oper;
- int freq1 = chandef->center_freq1, freq2 = chandef->center_freq2;
- enum nl80211_band band = chandef->chan->band;
struct bss_rlm_tlv *req;
+ enum nl80211_band band;
+ int freq1, freq2;
struct tlv *tlv;
+ if (WARN_ON_ONCE(!chandef || !chandef->chan))
+ return;
+
+ freq1 = chandef->center_freq1;
+ freq2 = chandef->center_freq2;
+ band = chandef->chan->band;
+
tlv = mt76_connac_mcu_add_tlv(skb, UNI_BSS_INFO_RLM, sizeof(*req));
req = (struct bss_rlm_tlv *)tlv;
req->control_channel = chandef->chan->hw_value;
@@ -2506,8 +2513,8 @@ mt7925_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_vif *vif,
enum nl80211_band band,
struct ieee80211_link_sta *link_sta)
{
- struct ieee80211_he_6ghz_capa *he_6ghz_capa;
- const struct ieee80211_sta_eht_cap *eht_cap;
+ struct ieee80211_he_6ghz_capa *he_6ghz_capa = NULL;
+ const struct ieee80211_sta_eht_cap *eht_cap = NULL;
__le16 capa = 0;
u8 mode = 0;
@@ -2515,11 +2522,18 @@ mt7925_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_vif *vif,
he_6ghz_capa = &link_sta->he_6ghz_capa;
eht_cap = &link_sta->eht_cap;
} else {
+ const struct ieee80211_sta_he_cap *he_cap;
struct ieee80211_supported_band *sband;
sband = phy->hw->wiphy->bands[band];
- capa = ieee80211_get_he_6ghz_capa(sband, vif->type);
- he_6ghz_capa = (struct ieee80211_he_6ghz_capa *)&capa;
+
+ he_cap = (band == NL80211_BAND_6GHZ) ?
+ ieee80211_get_he_iftype_cap(sband, vif->type) : NULL;
+
+ if (he_cap) {
+ capa = ieee80211_get_he_6ghz_capa(sband, vif->type);
+ he_6ghz_capa = (struct ieee80211_he_6ghz_capa *)&capa;
+ }
eht_cap = ieee80211_get_eht_iftype_cap(sband, vif->type);
}
--
2.43.0
^ permalink raw reply related
* [PATCH v2 1/9] wifi: mt76: mt792x: advertise mgmt frame registration
From: Sean Wang @ 2026-06-25 0:18 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi
Cc: chengwei.yu, yu-ching.liu, jenhao.yang, posh.sun, linux-wireless,
linux-mediatek, Sean Wang
In-Reply-To: <20260625001834.475094-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Advertise multicast management frame registration support so userspace
can subscribe to multicast management and action frames.
This capability is required for NAN discovery and related operations.
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>
---
drivers/net/wireless/mediatek/mt76/mt792x_core.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
index b50825eccdaf..a0db815c27bc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
@@ -719,6 +719,7 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw)
wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_BEACON_RATE_HE);
wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_ACK_SIGNAL_SUPPORT);
wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CAN_REPLACE_PTK0);
+ wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_MULTICAST_REGISTRATIONS);
ieee80211_hw_set(hw, SINGLE_SCAN_ON_ALL_BANDS);
ieee80211_hw_set(hw, HAS_RATE_CONTROL);
--
2.43.0
^ permalink raw reply related
* [PATCH v2 0/9] wifi: mt76: add mt7925 NAN support
From: Sean Wang @ 2026-06-25 0:18 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi
Cc: chengwei.yu, yu-ching.liu, jenhao.yang, posh.sun, linux-wireless,
linux-mediatek, Sean Wang
Add NAN support for mt7925. The series first advertises userspace
management-frame registration and hardens BSS capability lookups used by
partially configured BSS state.
The rest of the series adds the connac NAN connection type, mt7925 NAN
MCU helpers and event handling, a generic init_wiphy callback, mac80211
NAN operations, firmware-gated interface combinations and NAN data
advertisement.
Changes since v1
- Rebased and reworked the 7-patch v1 series into 9 focused patches.
- v1 patch 2 and 3 are folded into one BSS capability guard patch.
- v1 patch 7 is split into a framework-only interface combination
patch and a final NAN/NAN_DATA advertisement patch.
- v1 patch 5 is split into NAN MCU helpers and mt7925 MCU response,
event and NAN-specific BSS/STA TLV handling.
- v1 patch 6 is split so NAN PHY capability setup uses a generic
init_wiphy callback before mac80211 NAN ops are wired.
- Order init_wiphy before the mt7925 NAN ops patch so each patch
builds independently.
- Define MT792x_FW_CAP_NAN in the patch that first uses it.
- Move common NAN MCU command/event IDs to the connac patch and handle
NAN_DATA as a NAN connection type.
- Add NAN_DATA interface support, 2.4/5 GHz NAN bands and secure NAN
advertisement.
- Add NMI address programming, DW notifications, local availability
updates, peer schedule updates and NDI STA mapping.
- Add cleanup and rollback for NAN peer indexes, NDP contexts and MCU
failures.
- Drop temporary NAN channel debug logging and fix checkpatch issues.
Sean Wang (9):
wifi: mt76: mt792x: advertise mgmt frame registration
wifi: mt76: mt7925: guard BSS capability lookups
wifi: mt76: connac: add NAN connection type
wifi: mt76: mt7925: add NAN MCU helpers
wifi: mt76: mt7925: add NAN MCU handling
wifi: mt76: add init_wiphy callback
wifi: mt76: mt7925: wire up NAN operations
wifi: mt76: mt792x: build iface combinations dynamically
wifi: mt76: mt792x: advertise NAN data support
drivers/net/wireless/mediatek/mt76/mac80211.c | 7 +
drivers/net/wireless/mediatek/mt76/mt76.h | 3 +
.../wireless/mediatek/mt76/mt76_connac_mcu.c | 14 +
.../wireless/mediatek/mt76/mt76_connac_mcu.h | 4 +
.../wireless/mediatek/mt76/mt7925/Makefile | 2 +-
.../net/wireless/mediatek/mt76/mt7925/init.c | 29 +
.../net/wireless/mediatek/mt76/mt7925/main.c | 201 ++-
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 125 +-
.../net/wireless/mediatek/mt76/mt7925/nan.c | 1091 +++++++++++++++++
.../net/wireless/mediatek/mt76/mt7925/nan.h | 440 +++++++
.../net/wireless/mediatek/mt76/mt7925/regd.c | 30 +
.../net/wireless/mediatek/mt76/mt7925/regd.h | 3 +
drivers/net/wireless/mediatek/mt76/mt792x.h | 43 +
.../net/wireless/mediatek/mt76/mt792x_core.c | 125 +-
14 files changed, 2077 insertions(+), 40 deletions(-)
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/nan.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/nan.h
--
2.43.0
^ permalink raw reply
* Re: [PATCH v3 0/7] net: wwan: t9xx: Add MediaTek T9XX WWAN driver
From: Jakub Kicinski @ 2026-06-25 0:09 UTC (permalink / raw)
To: Jack Wu via B4 Relay
Cc: jackbb_wu, Loic Poulain, Sergey Ryazanov, Johannes Berg,
Andrew Lunn, David S. Miller, Eric Dumazet, Paolo Abeni,
Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng, Matthias Brugger,
AngeloGioacchino Del Regno, Simon Horman, Jonathan Corbet,
Shuah Khan, linux-kernel, netdev, linux-arm-kernel,
linux-mediatek, linux-doc
In-Reply-To: <20260624-t9xx_driver_v1-v3-0-73ff03f60c48@compal.com>
On Wed, 24 Jun 2026 18:04:06 +0800 Jack Wu via B4 Relay wrote:
> T9XX is the PCIe host device driver for MediaTek's
> t900 modem. The driver uses the WWAN framework
> infrastructure to create the following control ports
> and network interfaces for data transactions.
Replying after a long delay and then immediately posting a new version
of patches is very bad. Don't bother replying and just put the comments
you had in the changelog of the new posting. Otherwise the discussion
may get split.
^ permalink raw reply
* Re: [External Mail] [PATCH v2 1/7] net: wwan: t9xx: Add PCIe core
From: Jakub Kicinski @ 2026-06-24 23:35 UTC (permalink / raw)
To: Wu. JackBB (GSM)
Cc: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Wen-Zhi Huang,
Shi-Wei Yeh, Minano Tseng, Matthias Brugger,
AngeloGioacchino Del Regno, Simon Horman, Jonathan Corbet,
Shuah Khan, linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-mediatek@lists.infradead.org, linux-doc@vger.kernel.org
In-Reply-To: <b02c0e1e9f0449f2b819197e4329373b@compal.com>
On Wed, 24 Jun 2026 09:15:17 +0000 Wu. JackBB (GSM) wrote:
> ================================================================================================================================================================
> This message may contain information which is private, privileged or confidential of Compal Electronics, Inc. If you are not the intended recipient of this message, please notify the sender and destroy/delete the message. Any review, retransmission, dissemination or other use of, or taking of any action in reliance upon this information, by persons or entities other than the intended recipient is prohibited.
> ================================================================================================================================================================
If you want to do anything upstream you have to get rid of this first.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox