[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250324211045.3508952-2-artur@conclusive.pl>
Date: Mon, 24 Mar 2025 22:10:44 +0100
From: Artur Rojek <artur@...clusive.pl>
To: Johannes Berg <johannes@...solutions.net>,
Rob Herring <robh@...nel.org>,
Krzysztof Kozlowski <krzk+dt@...nel.org>,
Conor Dooley <conor+dt@...nel.org>
Cc: linux-wireless@...r.kernel.org,
devicetree@...r.kernel.org,
linux-kernel@...r.kernel.org,
Jakub Klama <jakub@...clusive.pl>,
Wojciech Kloska <wojciech@...clusive.pl>,
Ulf Axelsson <ulf.axelsson@...dicsemi.no>,
Artur Rojek <artur@...clusive.pl>
Subject: [RFC PATCH 1/2] net: wireless: Add Nordic nRF70 series Wi-Fi driver
Introduce support for Nordic Semiconductor nRF70 series wireless
companion IC.
Signed-off-by: Artur Rojek <artur@...clusive.pl>
---
drivers/net/wireless/Kconfig | 1 +
drivers/net/wireless/Makefile | 1 +
drivers/net/wireless/nordic/Kconfig | 26 +
drivers/net/wireless/nordic/Makefile | 3 +
drivers/net/wireless/nordic/nrf70.c | 4671 +++++++++++++++++
drivers/net/wireless/nordic/nrf70_cmds.h | 1051 ++++
drivers/net/wireless/nordic/nrf70_rf_params.h | 98 +
7 files changed, 5851 insertions(+)
create mode 100644 drivers/net/wireless/nordic/Kconfig
create mode 100644 drivers/net/wireless/nordic/Makefile
create mode 100644 drivers/net/wireless/nordic/nrf70.c
create mode 100644 drivers/net/wireless/nordic/nrf70_cmds.h
create mode 100644 drivers/net/wireless/nordic/nrf70_rf_params.h
diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig
index c6599594dc99..ffd2c4dcd4ac 100644
--- a/drivers/net/wireless/Kconfig
+++ b/drivers/net/wireless/Kconfig
@@ -27,6 +27,7 @@ source "drivers/net/wireless/intersil/Kconfig"
source "drivers/net/wireless/marvell/Kconfig"
source "drivers/net/wireless/mediatek/Kconfig"
source "drivers/net/wireless/microchip/Kconfig"
+source "drivers/net/wireless/nordic/Kconfig"
source "drivers/net/wireless/purelifi/Kconfig"
source "drivers/net/wireless/ralink/Kconfig"
source "drivers/net/wireless/realtek/Kconfig"
diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile
index e1c4141c6004..a3f9579c9b27 100644
--- a/drivers/net/wireless/Makefile
+++ b/drivers/net/wireless/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_WLAN_VENDOR_INTERSIL) += intersil/
obj-$(CONFIG_WLAN_VENDOR_MARVELL) += marvell/
obj-$(CONFIG_WLAN_VENDOR_MEDIATEK) += mediatek/
obj-$(CONFIG_WLAN_VENDOR_MICROCHIP) += microchip/
+obj-$(CONFIG_WLAN_VENDOR_NORDIC) += nordic/
obj-$(CONFIG_WLAN_VENDOR_PURELIFI) += purelifi/
obj-$(CONFIG_WLAN_VENDOR_QUANTENNA) += quantenna/
obj-$(CONFIG_WLAN_VENDOR_RALINK) += ralink/
diff --git a/drivers/net/wireless/nordic/Kconfig b/drivers/net/wireless/nordic/Kconfig
new file mode 100644
index 000000000000..c29aa338188a
--- /dev/null
+++ b/drivers/net/wireless/nordic/Kconfig
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config WLAN_VENDOR_NORDIC
+ bool "Nordic Semiconductor devices"
+ default y
+ help
+ If you have a wireless card belonging to this class, say Y.
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all the
+ questions about these cards. If you say Y, you will be asked for
+ your specific card in the following questions.
+
+if WLAN_VENDOR_NORDIC
+
+config NRF70
+ tristate "Nordic Semiconductor nRF70 series wireless companion IC"
+ depends on CFG80211 && INET && SPI_MEM && CPU_LITTLE_ENDIAN
+ help
+ This enables support for the Nordic Semiconductor nRF70 family of
+ wireless companion ICs.
+
+ To compile this driver as a module, choose M here: the module will
+ be called nrf70.
+
+endif # WLAN_VENDOR_NORDIC
diff --git a/drivers/net/wireless/nordic/Makefile b/drivers/net/wireless/nordic/Makefile
new file mode 100644
index 000000000000..4559072a4b83
--- /dev/null
+++ b/drivers/net/wireless/nordic/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_NRF70) += nrf70.o
diff --git a/drivers/net/wireless/nordic/nrf70.c b/drivers/net/wireless/nordic/nrf70.c
new file mode 100644
index 000000000000..e4b1b4cc9b45
--- /dev/null
+++ b/drivers/net/wireless/nordic/nrf70.c
@@ -0,0 +1,4671 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Conclusive Engineering Sp. z o. o.
+ */
+
+#include <crypto/hash.h>
+#include <linux/crypto.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi-mem.h>
+#include <linux/units.h>
+#include <net/ieee80211_radiotap.h>
+#include <net/mac80211.h>
+#include <uapi/linux/if_arp.h>
+
+#include "nrf70_cmds.h"
+#include "nrf70_rf_params.h"
+
+#define NRF70_FW_SIGNATURE 0xdead1eaf
+#define NRF70_FW_HASH_LEN 32
+
+#define NRF70_OP_PP 0x02
+#define NRF70_OP_RDSR 0x05
+#define NRF70_OP_FASTRD 0x0b
+#define NRF70_OP_RDSR1 0x1f
+#define NRF70_OP_RDSR2 0x2f
+#define NRF70_OP_PP4 0x38
+#define NRF70_OP_WRSR2 0x3f
+#define NRF70_OP_RD4 0xeb
+
+#define NRF70_SR2_WAKEUP_REQ BIT(0)
+#define NRF70_SR1_AWAKE BIT(1)
+#define NRF70_SR1_READY BIT(2)
+
+#define NRF70_ADDR_INVAL (-1)
+
+#define NRF70_RPU_BASE_MCU 0x80000000
+#define NRF70_RPU_BASE_SBUS 0xa4000000
+#define NRF70_RPU_BASE_PBUS 0xa5000000
+#define NRF70_RPU_BASE_PKTRAM 0xb0000000
+#define NRF70_RPU_BASE_GRAM 0xb7000000
+#define NRF70_RPU_BASE_INDIRECT 0xc0000000
+
+#define NRF70_RPU_BASE_MASK 0xff000000
+
+#define NRF70_HOST_BASE_SBUS 0x000000
+#define NRF70_HOST_BASE_PBUS 0x040000
+#define NRF70_HOST_BASE_GRAM 0x080000
+#define NRF70_HOST_BASE_PKTRAM 0x0c0000
+#define NRF70_HOST_BASE_MCU 0x100000
+
+#define NRF70_HOST_BASE_MASK 0xff0000
+
+#define NRF70_HOST_OFFSET_LMAC 0x0
+#define NRF70_HOST_OFFSET_UMAC 0x100000
+
+#define NRF70_HOST_OFFSET_MASK 0x00ffffff
+/* Host addresses tagged with this bit are subject to multi-word SPI I/O. */
+#define NRF70_HOST_MW_IO BIT(23)
+
+/*
+ * All hw register addresses are from the RPU point of view. They are converted
+ * to SPI bus memory map in I/O helpers.
+ */
+#define NRF70_PBUS_CLK (NRF70_RPU_BASE_PBUS + 0x8c20)
+#define NRF70_PBUS_CLK_UNGATE BIT(8)
+
+#define NRF70_SBUS_MIPS_MCU_CONTROL (NRF70_RPU_BASE_SBUS + 0x0)
+#define NRF70_SBUS_MIPS_MCU_UCCP_INT_STATUS (NRF70_RPU_BASE_SBUS + 0x4)
+#define NRF70_SBUS_MIPS_MCU_UCCP_INT_CLEAR (NRF70_RPU_BASE_SBUS + 0xc)
+
+#define NRF70_SBUS_CP0_SLEEP_STATUS (NRF70_RPU_BASE_SBUS + 0x18)
+#define NRF70_SBUS_MIPS_MCU2_CONTROL (NRF70_RPU_BASE_SBUS + 0x100)
+#define NRF70_SBUS_CP1_SLEEP_STATUS (NRF70_RPU_BASE_SBUS + 0x118)
+
+#define NRF70_MCU_LMAC_PATCH_BIN \
+ (NRF70_RPU_BASE_MCU + NRF70_HOST_OFFSET_LMAC + 0x043a80)
+#define NRF70_MCU_LMAC_PATCH_BIMG \
+ (NRF70_RPU_BASE_MCU + NRF70_HOST_OFFSET_LMAC + 0x04bc00)
+#define NRF70_MCU_UMAC_PATCH_BIN \
+ (NRF70_RPU_BASE_MCU + NRF70_HOST_OFFSET_UMAC + 0x08c000)
+#define NRF70_MCU_UMAC_PATCH_BIMG \
+ (NRF70_RPU_BASE_MCU + NRF70_HOST_OFFSET_UMAC + 0x099400)
+
+#define NRF70_MCU_LMAC_BOOT_SIG (NRF70_RPU_BASE_GRAM + 0x000d50)
+#define NRF70_MCU_UMAC_BOOT_SIG (NRF70_RPU_BASE_PKTRAM + 0x000000)
+#define NRF70_MCU_UMAC_VERSION (NRF70_RPU_BASE_PKTRAM + 0x000004)
+#define NRF70_MCU_UMAC_HPQ (NRF70_RPU_BASE_PKTRAM + 0x24)
+#define NRF70_OTP_HWADDR (NRF70_RPU_BASE_PKTRAM + 0x84)
+#define NRF70_OTP_INFO_FLAGS (NRF70_RPU_BASE_PKTRAM + 0x4fdc)
+#define NRF70_OTP_INFO_FLAGS_HWADDR(n) BIT(1 + (n))
+
+#define NRF70_RX_CMD_BASE (NRF70_RPU_BASE_GRAM + 0xd58)
+#define NRF70_TX_CMD_BASE (NRF70_RPU_BASE_PKTRAM + 0xb8)
+
+#define NRF70_MCU_UMAC_VERSION_VER(n) ((n) >> 24 & 0xff)
+#define NRF70_MCU_UMAC_VERSION_MAJOR(n) ((n) >> 16 & 0xff)
+#define NRF70_MCU_UMAC_VERSION_MINOR(n) ((n) >> 8 & 0xff)
+#define NRF70_MCU_UMAC_VERSION_EXTRA(n) ((n) & 0xff)
+
+#define NRF70_LMAC_CORE_RET_START (NRF70_RPU_BASE_MCU + 0x40000)
+#define NRF70_UMAC_CORE_RET_START (NRF70_RPU_BASE_MCU + 0x80000)
+#define NRF70_LMAC_ROM_PATCH_OFFSET \
+ (NRF70_MCU_LMAC_PATCH_BIMG - NRF70_LMAC_CORE_RET_START)
+#define NRF70_UMAC_ROM_PATCH_OFFSET \
+ (NRF70_MCU_UMAC_PATCH_BIMG - NRF70_UMAC_CORE_RET_START)
+
+#define NRF70_UCC_SLEEP_CTRL_DATA_0 (NRF70_RPU_BASE_SBUS + 0x2c2c)
+#define NRF70_UCC_SLEEP_CTRL_DATA_1 (NRF70_RPU_BASE_SBUS + 0x2c30)
+
+#define NRF70_MCU_BOOT_SIG 0x5a5a5a5a
+
+#define NRF70_SBUS_CORE_MEM_CTRL (NRF70_RPU_BASE_SBUS + 0x30)
+#define NRF70_SBUS_CORE_MEM_WDATA (NRF70_RPU_BASE_SBUS + 0x34)
+
+#define NRF70_SBUS_MIPS_MCU_TIMER (NRF70_RPU_BASE_SBUS + 0x4c)
+#define NRF70_SBUS_MIPS_MCU_TIMER_RESET 0xffffff
+
+#define NRF70_SBUS_MIPS_MCU_BOOT_EXCP_INSTR_0 (NRF70_RPU_BASE_SBUS + 0x50)
+#define NRF70_SBUS_MIPS_MCU_BOOT_EXCP_INSTR_1 (NRF70_RPU_BASE_SBUS + 0x54)
+#define NRF70_SBUS_MIPS_MCU_BOOT_EXCP_INSTR_2 (NRF70_RPU_BASE_SBUS + 0x58)
+#define NRF70_SBUS_MIPS_MCU_BOOT_EXCP_INSTR_3 (NRF70_RPU_BASE_SBUS + 0x5c)
+#define NRF70_SBUS_MIPS_MCU2_BOOT_EXCP_INSTR_0 (NRF70_RPU_BASE_SBUS + 0x150)
+#define NRF70_SBUS_MIPS_MCU2_BOOT_EXCP_INSTR_1 (NRF70_RPU_BASE_SBUS + 0x154)
+#define NRF70_SBUS_MIPS_MCU2_BOOT_EXCP_INSTR_2 (NRF70_RPU_BASE_SBUS + 0x158)
+#define NRF70_SBUS_MIPS_MCU2_BOOT_EXCP_INSTR_3 (NRF70_RPU_BASE_SBUS + 0x15c)
+
+#define NRF70_MCU_LMAC_BOOT_EXCP_VECT_0 0x3c1a8000
+#define NRF70_MCU_LMAC_BOOT_EXCP_VECT_1 0x275a0000
+#define NRF70_MCU_LMAC_BOOT_EXCP_VECT_2 0x03400008
+#define NRF70_MCU_LMAC_BOOT_EXCP_VECT_3 0x00000000
+
+#define NRF70_MCU_UMAC_BOOT_EXCP_VECT_0 0x3c1a8000
+#define NRF70_MCU_UMAC_BOOT_EXCP_VECT_1 0x275a0000
+#define NRF70_MCU_UMAC_BOOT_EXCP_VECT_2 0x03400008
+#define NRF70_MCU_UMAC_BOOT_EXCP_VECT_3 0x00000000
+
+#define NRF70_SBUS_MIPS_MCU_WATCHDOG_INT BIT(1)
+
+#define NRF70_SBUS_UCCP_CORE_INT_ENAB (NRF70_RPU_BASE_SBUS + 0x400)
+#define NRF70_UCCP_MTX2_INT_IRQ_ENAB BIT(17)
+
+#define NRF70_SBUS_UCCP_CORE_HOST2_TO_MTX2_CMD (NRF70_RPU_BASE_SBUS + 0x480)
+#define NRF70_UCCP_HOST2_TO_MTX2_CMD_DATA_MASK 0x7fff0000
+
+#define NRF70_SBUS_UCCP_CORE_HOST2_TO_MTX2_ACK (NRF70_RPU_BASE_SBUS + 0x488)
+
+#define NRF70_SBUS_UCCP_CORE_MTX2_INT_ENABLE (NRF70_RPU_BASE_SBUS + 0x494)
+#define NRF70_UCCP_MTX2_INT_EN BIT(31)
+
+#define NRF70_RPU_PKTRAM_PKT_BASE (NRF70_RPU_BASE_PKTRAM + 0x5000)
+#define NRF70_RPU_PKTRAM_PKT_BASE_SZ 0x2c000
+
+#define NRF70_RPU_CMD_START_MAGIC 0xdead
+
+#define NRF70_PEERS_MAX 8
+#define NRF70_PEERS_MASK GENMASK(NRF70_PEERS_MAX - 1, 0)
+#define NRF70_VIFS_MAX 2
+#define NRF70_VIFS_MASK GENMASK(NRF70_VIFS_MAX - 1, 0)
+#define NRF70_SCAN_SSIDS_MAX 4
+
+#define NRF70_UMAC_CMD_MAX_SZ 400
+#define NRF70_DATA_CMD_RX_MAX_SZ 8
+#define NRF70_DATA_CMD_TX_MAX_SZ 148
+#define NRF70_EVENT_POOL_MAX_SZ 1000
+
+#define NRF70_NUM_RX_QUEUES 3
+#define NRF70_NUM_RX_BUFS 48
+#define NRF70_RX_DATA_MAX_SZ 1600
+#define NRF70_TX_DATA_MAX_SZ 1600
+#define NRF70_DESC_MASK GENMASK(15, 0)
+
+#define NRF70_TX_PENDING_MAX 100
+#define NRF70_TX_PENDING_WMARK (NRF70_TX_PENDING_MAX / 2)
+
+#define NRF70_NDEV_TO_IFACE(n) \
+ (((struct nrf70_ndev_priv *)netdev_priv(n))->vif->iface)
+
+#define NRF70_RADIOTAP_PRESENT_FIELDS \
+ cpu_to_le32((1 << IEEE80211_RADIOTAP_RATE) | \
+ (1 << IEEE80211_RADIOTAP_CHANNEL) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL))
+
+static const u8 nrf70_tuning_pattern[] = {
+ 0xa5, 0xa5, 0xde, 0xad, 0xc0, 0xde, 0x8b, 0xad,
+ 0xf0, 0x0d, 0xfe, 0xe1, 0xc0, 0x1d, 0x5a, 0x5a
+};
+
+struct nrf70_hpq {
+ u32 eq;
+ u32 dq;
+};
+
+enum nrf70_hpq_type {
+ NRF70_EVENT_BUSY_QUEUE,
+ NRF70_EVENT_AVL_QUEUE,
+ NRF70_CMD_BUSY_QUEUE,
+ NRF70_CMD_AVL_QUEUE,
+ NRF70_RX_BUSY_QUEUE,
+ NRF70_RX_BUSY_QUEUE2,
+ NRF70_RX_BUSY_QUEUE3,
+ NRF70_QUEUE_MAX
+};
+
+struct nrf70_cookie {
+ struct list_head list;
+ u64 host_cookie;
+ u64 rpu_cookie;
+};
+
+static struct nrf70_mem_op {
+ int op;
+ int width;
+ int dummy;
+ enum spi_mem_data_dir dir;
+} nrf70_read_ops[] = {
+ { NRF70_OP_RD4, 4, 3, SPI_MEM_DATA_OUT },
+ { NRF70_OP_FASTRD, 1, 1, SPI_MEM_DATA_OUT },
+}, nrf70_write_ops[] = {
+ { NRF70_OP_PP4, 4, 0, SPI_MEM_DATA_IN },
+ { NRF70_OP_PP, 1, 0, SPI_MEM_DATA_IN },
+};
+
+struct nrf70_sta {
+ u8 addr[ETH_ALEN];
+ struct sk_buff_head pending;
+ struct wiphy_work pending_work;
+ bool can_xmit;
+};
+
+struct nrf70_vif {
+ struct list_head list;
+ struct net_device *ndev;
+ struct wireless_dev wdev;
+ int iface;
+ /* Protects against concurrent access to sta and sta_bitmap members. */
+ spinlock_t sta_lock;
+ unsigned long sta_bitmap;
+ struct nrf70_sta sta[NRF70_PEERS_MAX];
+ struct sk_buff_head tx_queue;
+ struct wiphy_work xmit_work;
+ struct cfg80211_scan_request *scan_req;
+ struct cfg80211_bss *bss;
+ struct cfg80211_qos_map *qos_map;
+ unsigned long iface_stypes;
+ struct completion iface_updated;
+ struct completion chan_updated;
+ struct cfg80211_chan_def chandef;
+ struct {
+ struct u64_stats_sync syncp;
+ u64 rx_packets;
+ u64 tx_packets;
+ u64 rx_bytes;
+ u64 tx_bytes;
+ u64 rx_dropped;
+ u64 tx_dropped;
+ } stats;
+};
+
+struct nrf70_priv {
+ struct spi_mem *mem;
+ struct gpio_desc *buck_en;
+ struct gpio_desc *iovdd_en;
+ struct gpio_desc *irq;
+ struct nrf70_hpq queue[NRF70_QUEUE_MAX];
+ int num_cmds;
+ struct work_struct event_work;
+ struct wiphy *wiphy;
+ struct list_head vifs;
+ unsigned long vif_bitmap;
+ u8 hwaddr[NRF70_VIFS_MAX][ETH_ALEN];
+ u8 regdom[3];
+ struct completion regdom_updated;
+ u32 rx_cmd_base;
+ u32 tx_cmd_base;
+ u64 mgmt_frame_cookie;
+ struct list_head cookies;
+ bool scan_in_progress;
+ /* Provides synchronization for write operations. */
+ struct mutex write_lock;
+ /* Provides synchronization for read operations. */
+ struct mutex read_lock;
+ /* Provides synchronization while enqueuing messages. */
+ struct mutex enqueue_lock;
+ /* Protects against concurrent access to the tx_desc_bitmap member. */
+ struct mutex desc_lock;
+ unsigned long tx_desc_bitmap[NRF70_VIFS_MAX];
+ struct completion init_done;
+ struct nrf70_mem_op *read_op;
+ struct nrf70_mem_op *write_op;
+ int read_op_pad[3];
+ struct station_info *sinfo;
+ struct completion station_info_available;
+ bool has_raw_mode;
+ u32 tx_buf ____cacheline_aligned;
+ u32 rx_buf ____cacheline_aligned;
+};
+
+struct nrf70_wiphy_priv {
+ struct nrf70_priv *priv;
+};
+
+struct nrf70_ndev_priv {
+ struct nrf70_priv *priv;
+ struct nrf70_vif *vif;
+};
+
+static u32 rpu_to_host_addr(u32 address)
+{
+ u32 offset = (address & NRF70_HOST_OFFSET_MASK);
+
+ switch (address & NRF70_RPU_BASE_MASK) {
+ case NRF70_RPU_BASE_MCU:
+ return offset + NRF70_HOST_BASE_MCU;
+ case NRF70_RPU_BASE_SBUS:
+ return offset + NRF70_HOST_BASE_SBUS;
+ case NRF70_RPU_BASE_PBUS:
+ return offset + NRF70_HOST_BASE_PBUS;
+ case NRF70_RPU_BASE_PKTRAM:
+ return offset + NRF70_HOST_BASE_PKTRAM;
+ case NRF70_RPU_BASE_GRAM:
+ return offset + NRF70_HOST_BASE_GRAM;
+ case NRF70_RPU_BASE_INDIRECT:
+ pr_warn("Warning: Indirect address base\n");
+ fallthrough;
+ default:
+ return NRF70_ADDR_INVAL;
+ }
+}
+
+static int nrf70_read_padding(struct spi_mem *mem, u32 address)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+
+ switch (address & NRF70_HOST_BASE_MASK) {
+ case NRF70_HOST_BASE_PKTRAM:
+ return priv->read_op_pad[1];
+ default:
+ return address & NRF70_HOST_MW_IO ?
+ priv->read_op_pad[2] : priv->read_op_pad[0];
+ }
+}
+
+static void nrf70_writel(struct spi_mem *mem, u32 address, u32 value)
+{
+ u32 rpu_addr = rpu_to_host_addr(address);
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_mem_op *mop = priv->write_op;
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(mop->op, 1),
+ SPI_MEM_OP_ADDR(3, rpu_addr,
+ mop->width),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(4, &priv->tx_buf,
+ mop->width));
+
+ guard(mutex)(&priv->write_lock);
+ priv->tx_buf = value;
+
+ if (spi_mem_exec_op(mem, &op))
+ dev_err(&mem->spi->dev, "Failed to write to address %06x\n",
+ address);
+}
+
+static void nrf70_writev(struct spi_mem *mem, u32 address, const void *buf,
+ size_t len)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct device *dev = &mem->spi->dev;
+ u32 rpu_addr = rpu_to_host_addr(address) | NRF70_HOST_MW_IO;
+ struct nrf70_mem_op *mop = priv->write_op;
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(mop->op, 1),
+ SPI_MEM_OP_ADDR(3, rpu_addr,
+ mop->width),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(len, buf,
+ mop->width));
+ int ret;
+
+ if (len < 4) {
+ dev_err(dev, "Write buffer too small\n");
+ return;
+ }
+ if (address % 4 || len % 4) {
+ dev_err(dev, "Misaligned write: %x\n", address);
+ return;
+ }
+
+ guard(mutex)(&priv->write_lock);
+ while (len) {
+ op.data.nbytes = len;
+ ret = spi_mem_adjust_op_size(mem, &op);
+ if (ret) {
+ dev_err(dev, "Unsupported write op size: %zu, %d\n",
+ len, ret);
+ return;
+ }
+ ret = spi_mem_exec_op(mem, &op);
+ if (ret) {
+ dev_err(dev, "Failed to write to address: %06x, %d\n",
+ address, ret);
+ return;
+ }
+
+ len -= op.data.nbytes;
+ op.data.buf.out += op.data.nbytes;
+ op.addr.val += op.data.nbytes;
+ }
+}
+
+static u32 nrf70_readl(struct spi_mem *mem, u32 address)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ u32 rpu_addr = rpu_to_host_addr(address);
+ struct nrf70_mem_op *mop = priv->read_op;
+ int dummy = nrf70_read_padding(mem, rpu_addr) + mop->dummy;
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(mop->op, 1),
+ SPI_MEM_OP_ADDR(3, rpu_addr,
+ mop->width),
+ SPI_MEM_OP_DUMMY(dummy, mop->width),
+ SPI_MEM_OP_DATA_IN(4, &priv->rx_buf,
+ mop->width));
+
+ guard(mutex)(&priv->read_lock);
+ if (spi_mem_exec_op(mem, &op)) {
+ dev_err(&mem->spi->dev, "Failed to read from address %06x\n",
+ address);
+ return 0;
+ }
+
+ return priv->rx_buf;
+}
+
+static int nrf70_readv(struct spi_mem *mem, u32 address, void *buf, size_t len)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct device *dev = &mem->spi->dev;
+ u32 rpu_addr = rpu_to_host_addr(address) | NRF70_HOST_MW_IO;
+ struct nrf70_mem_op *mop = priv->read_op;
+ int dummy = nrf70_read_padding(mem, rpu_addr) + mop->dummy;
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(mop->op, 1),
+ SPI_MEM_OP_ADDR(3, rpu_addr,
+ mop->width),
+ SPI_MEM_OP_DUMMY(dummy, mop->width),
+ SPI_MEM_OP_DATA_IN(len, buf,
+ mop->width));
+ int ret;
+
+ if (len < 4) {
+ dev_err(dev, "Read buffer too small\n");
+ return -EINVAL;
+ }
+ if (address % 4) {
+ dev_err(dev, "Misaligned read\n");
+ return -EINVAL;
+ }
+
+ guard(mutex)(&priv->read_lock);
+ while (len) {
+ op.data.nbytes = len;
+ ret = spi_mem_adjust_op_size(mem, &op);
+ if (ret) {
+ dev_err(dev, "Unsupported read op size: %zu, %d\n",
+ len, ret);
+ return ret;
+ }
+ ret = spi_mem_exec_op(mem, &op);
+ if (ret) {
+ dev_err(dev, "Failed to read from address: %06x, %d\n",
+ address, ret);
+ return ret;
+ }
+
+ len -= op.data.nbytes;
+ op.data.buf.in += op.data.nbytes;
+ op.addr.val += op.data.nbytes;
+ }
+
+ return 0;
+}
+
+static u8 nrf70_rdsr1(struct spi_mem *mem)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(NRF70_OP_RDSR1, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(1, &priv->rx_buf,
+ 1));
+
+ guard(mutex)(&priv->read_lock);
+ if (spi_mem_exec_op(mem, &op))
+ dev_err(&mem->spi->dev, "Failed to perform RDSR1 operation\n");
+
+ return priv->rx_buf;
+}
+
+static u8 nrf70_rdsr2(struct spi_mem *mem)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(NRF70_OP_RDSR2, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(1, &priv->rx_buf,
+ 1));
+
+ guard(mutex)(&priv->read_lock);
+ if (spi_mem_exec_op(mem, &op))
+ dev_err(&mem->spi->dev, "Failed to perform RDSR2 operation\n");
+
+ return priv->rx_buf;
+}
+
+static void nrf70_wrsr2(struct spi_mem *mem, u8 value)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(NRF70_OP_WRSR2, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(1, &priv->tx_buf,
+ 1));
+
+ guard(mutex)(&priv->write_lock);
+ priv->tx_buf = value;
+
+ if (spi_mem_exec_op(mem, &op))
+ dev_err(&mem->spi->dev, "Failed to perform WRSR2 operation\n");
+}
+
+#define NRF70_FW_FEATURE_RAW_MODE BIT(3)
+struct __packed nrf70_fw_header {
+ u32 signature;
+ u32 num_images;
+ u32 version;
+ u32 feature_flags;
+ u32 length;
+ u8 hash[NRF70_FW_HASH_LEN];
+ u8 data[];
+};
+
+struct __packed nrf70_fw_img {
+ u32 type;
+ u32 length;
+ u8 data[];
+};
+
+static const u32 nrf_rpu_addr_lut[4] = { NRF70_MCU_UMAC_PATCH_BIMG,
+ NRF70_MCU_UMAC_PATCH_BIN,
+ NRF70_MCU_LMAC_PATCH_BIMG,
+ NRF70_MCU_LMAC_PATCH_BIN };
+
+static const u32 nrf_boot_vectors[][4][2] = {
+ {
+ {
+ NRF70_SBUS_MIPS_MCU_BOOT_EXCP_INSTR_0,
+ NRF70_MCU_LMAC_BOOT_EXCP_VECT_0
+ },
+ {
+ NRF70_SBUS_MIPS_MCU_BOOT_EXCP_INSTR_1,
+ NRF70_MCU_LMAC_BOOT_EXCP_VECT_1
+ },
+ {
+ NRF70_SBUS_MIPS_MCU_BOOT_EXCP_INSTR_2,
+ NRF70_MCU_LMAC_BOOT_EXCP_VECT_2
+ },
+ {
+ NRF70_SBUS_MIPS_MCU_BOOT_EXCP_INSTR_3,
+ NRF70_MCU_LMAC_BOOT_EXCP_VECT_3
+ },
+ },
+ {
+ {
+ NRF70_SBUS_MIPS_MCU2_BOOT_EXCP_INSTR_0,
+ NRF70_MCU_UMAC_BOOT_EXCP_VECT_0
+ },
+ {
+ NRF70_SBUS_MIPS_MCU2_BOOT_EXCP_INSTR_1,
+ NRF70_MCU_UMAC_BOOT_EXCP_VECT_1
+ },
+ {
+ NRF70_SBUS_MIPS_MCU2_BOOT_EXCP_INSTR_2,
+ NRF70_MCU_UMAC_BOOT_EXCP_VECT_2
+ },
+ {
+ NRF70_SBUS_MIPS_MCU2_BOOT_EXCP_INSTR_3,
+ NRF70_MCU_UMAC_BOOT_EXCP_VECT_3
+ },
+ }
+};
+
+static int nrf70_verify_firmware(struct device *dev,
+ const struct nrf70_fw_header *fw)
+{
+ struct crypto_shash *alg;
+ u8 hash[NRF70_FW_HASH_LEN];
+ int ret;
+
+ alg = crypto_alloc_shash("sha256", 0, 0);
+ if (IS_ERR(alg)) {
+ ret = PTR_ERR(alg);
+ dev_err(dev, "Unable to allocate shash memory: %d\n", ret);
+ goto out;
+ };
+
+ if (crypto_shash_digestsize(alg) != NRF70_FW_HASH_LEN) {
+ dev_err(dev, "Incorrect digest size\n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ ret = crypto_shash_tfm_digest(alg, fw->data, fw->length, hash);
+ if (ret) {
+ dev_err(dev, "Unable to compute hash\n");
+ goto out;
+ }
+
+ if (memcmp(fw->hash, hash, sizeof(hash))) {
+ dev_err(dev, "Invalid firmware checksum\n");
+ ret = -EFAULT;
+ }
+
+out:
+ crypto_free_shash(alg);
+
+ return ret;
+}
+
+static int nrf70_load_firmware(struct spi_mem *mem)
+{
+ struct device *dev = &mem->spi->dev;
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ const struct nrf70_fw_header *header;
+ const struct nrf70_fw_img *image;
+ const struct firmware *firmware;
+ int val, i, ret;
+ u32 type, len;
+
+ /* Perform RPU MCU reset. */
+ nrf70_writel(mem, NRF70_SBUS_MIPS_MCU_CONTROL, 0x1);
+
+ if (read_poll_timeout(nrf70_readl, val, !(val & 0x1),
+ 10 * USEC_PER_MSEC, 50 * USEC_PER_MSEC, false,
+ mem, NRF70_SBUS_MIPS_MCU_CONTROL)) {
+ dev_err(dev, "Unable to reset LMAC\n");
+ return -ETIMEDOUT;
+ }
+
+ if (read_poll_timeout(nrf70_readl, val, val & 0x1,
+ 10 * USEC_PER_MSEC, 50 * USEC_PER_MSEC, false,
+ mem, NRF70_SBUS_CP0_SLEEP_STATUS)) {
+ dev_err(dev, "Unable to reset LMAC2\n");
+ return -ETIMEDOUT;
+ }
+
+ nrf70_writel(mem, NRF70_SBUS_MIPS_MCU2_CONTROL, 0x1);
+
+ if (read_poll_timeout(nrf70_readl, val, !(val & 0x1),
+ 10 * USEC_PER_MSEC, 50 * USEC_PER_MSEC, false,
+ mem, NRF70_SBUS_MIPS_MCU2_CONTROL)) {
+ dev_err(dev, "Unable to reset UMAC\n");
+ return -ETIMEDOUT;
+ }
+
+ if (read_poll_timeout(nrf70_readl, val, val & 0x1,
+ 10 * USEC_PER_MSEC, 50 * USEC_PER_MSEC, false,
+ mem, NRF70_SBUS_CP1_SLEEP_STATUS)) {
+ dev_err(dev, "Unable to reset UMAC2\n");
+ return -ETIMEDOUT;
+ }
+
+ ret = request_firmware(&firmware, "nrf70.bin", dev);
+ if (ret < 0) {
+ dev_err(dev, "Failed to request firmware: %d\n", ret);
+ return ret;
+ }
+
+ header = (const struct nrf70_fw_header *)firmware->data;
+ if (header->signature != NRF70_FW_SIGNATURE) {
+ dev_err(dev, "Invalid firmware signature\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = nrf70_verify_firmware(dev, header);
+ if (ret)
+ goto out;
+
+ priv->has_raw_mode = header->feature_flags & NRF70_FW_FEATURE_RAW_MODE;
+
+ image = (const struct nrf70_fw_img *)header->data;
+
+ for (i = 0; i < header->num_images; i++) {
+ type = image->type;
+ if (type > 3) {
+ dev_err(dev, "Unknown firmware image type: %d\n", type);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ len = image->length;
+ if (len % 4) {
+ dev_err(dev, "Unaligned firmware image length: %d\n",
+ len);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ nrf70_writev(mem, nrf_rpu_addr_lut[type], image->data, len);
+
+ image = (void *)(image->data + image->length);
+ }
+
+ nrf70_writel(mem, NRF70_MCU_LMAC_BOOT_SIG, 0x0);
+ nrf70_writel(mem, NRF70_UCC_SLEEP_CTRL_DATA_0,
+ NRF70_LMAC_ROM_PATCH_OFFSET);
+ nrf70_writel(mem, NRF70_MCU_UMAC_BOOT_SIG, 0x0);
+ nrf70_writel(mem, NRF70_UCC_SLEEP_CTRL_DATA_1,
+ NRF70_UMAC_ROM_PATCH_OFFSET);
+
+ for (i = 0; i < 4; i++)
+ nrf70_writel(mem, nrf_boot_vectors[0][i][0],
+ nrf_boot_vectors[0][i][1]);
+
+ nrf70_writel(mem, NRF70_SBUS_MIPS_MCU_CONTROL, 0x1);
+
+ for (i = 0; i < 4; i++)
+ nrf70_writel(mem, nrf_boot_vectors[1][i][0],
+ nrf_boot_vectors[1][i][1]);
+
+ nrf70_writel(mem, NRF70_SBUS_MIPS_MCU2_CONTROL, 0x1);
+
+ if (read_poll_timeout(nrf70_readl, val, val == NRF70_MCU_BOOT_SIG,
+ 100, 10 * USEC_PER_MSEC, false,
+ mem, NRF70_MCU_LMAC_BOOT_SIG)) {
+ dev_err(dev, "Unable to read LMAC boot signature\n");
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ if (read_poll_timeout(nrf70_readl, val, val == NRF70_MCU_BOOT_SIG,
+ 100, 10 * USEC_PER_MSEC, false,
+ mem, NRF70_MCU_UMAC_BOOT_SIG)) {
+ dev_err(dev, "Unable to read UMAC boot signature\n");
+ ret = -ETIMEDOUT;
+ }
+
+out:
+ release_firmware(firmware);
+
+ return ret;
+}
+
+static int nrf70_dequeue(struct spi_mem *mem, enum nrf70_hpq_type queue,
+ u32 *addr)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ u32 val = nrf70_readl(mem, priv->queue[queue].dq);
+
+ if (!val || val == 0xaaaaaaaa || val == 0xffffffff)
+ return -EINVAL;
+
+ nrf70_writel(mem, priv->queue[queue].dq, val);
+ *addr = val;
+
+ return 0;
+}
+
+static int nrf70_enqueue_message(struct spi_mem *mem, struct nrf70_msg *msg)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct device *dev = &mem->spi->dev;
+ int val, total, len;
+ u32 addr;
+ u8 *buf = (u8 *)msg;
+
+ guard(mutex)(&priv->enqueue_lock);
+ for (total = msg->len; total > 0; total -= len, buf += len) {
+ len = min(ALIGN(total, 4), NRF70_UMAC_CMD_MAX_SZ);
+
+ if (read_poll_timeout(nrf70_dequeue, val, !val,
+ 2 * USEC_PER_MSEC, 10 * USEC_PER_MSEC,
+ false, mem, NRF70_CMD_AVL_QUEUE, &addr)) {
+ dev_err(dev, "Unable to send message\n");
+ return -ETIMEDOUT;
+ }
+
+ nrf70_writev(mem, addr, buf, len);
+ nrf70_writel(mem, priv->queue[NRF70_CMD_BUSY_QUEUE].eq, addr);
+
+ val = priv->num_cmds++ | NRF70_UCCP_HOST2_TO_MTX2_CMD_DATA_MASK;
+ nrf70_writel(mem, NRF70_SBUS_UCCP_CORE_HOST2_TO_MTX2_CMD, val);
+ }
+
+ return 0;
+}
+
+static int nrf70_init_rx(struct spi_mem *mem, int desc)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct device *dev = &mem->spi->dev;
+ u32 addr, bounce_addr;
+ size_t rx_addr_base = (NRF70_RPU_PKTRAM_PKT_BASE +
+ NRF70_RPU_PKTRAM_PKT_BASE_SZ) -
+ (NRF70_NUM_RX_BUFS * NRF70_RX_DATA_MAX_SZ);
+ int i = desc / (NRF70_NUM_RX_BUFS / NRF70_NUM_RX_QUEUES);
+
+ addr = priv->rx_cmd_base + desc * NRF70_DATA_CMD_RX_MAX_SZ;
+ if (addr % 4) {
+ dev_err(dev, "Misaligned indirect write\n");
+ return -EINVAL;
+ }
+
+ bounce_addr = rx_addr_base + desc * NRF70_RX_DATA_MAX_SZ;
+ nrf70_writel(mem, bounce_addr, desc);
+
+ addr = (addr & NRF70_HOST_OFFSET_MASK) >> 2;
+ nrf70_writel(mem, NRF70_SBUS_CORE_MEM_CTRL, addr);
+ nrf70_writel(mem, NRF70_SBUS_CORE_MEM_WDATA, bounce_addr);
+ nrf70_writel(mem, priv->queue[NRF70_RX_BUSY_QUEUE + i].eq,
+ priv->rx_cmd_base + desc * NRF70_DATA_CMD_RX_MAX_SZ);
+
+ return 0;
+}
+
+static int nrf70_init_rx_command(struct spi_mem *mem)
+{
+ int i, ret = 0;
+
+ for (i = 0; ret || i < NRF70_NUM_RX_BUFS; i++)
+ ret = nrf70_init_rx(mem, i);
+
+ return ret;
+}
+
+static struct nrf70_msg *nrf70_create_msg(u32 type, u32 id, size_t data_len,
+ int iface)
+{
+ struct nrf70_msg *msg;
+ union cmd_header {
+ struct __packed nrf70_header sys;
+ struct __packed nrf70_umac_header umac;
+ } *hdr;
+ size_t len = sizeof(*msg) + data_len;
+
+ msg = kzalloc(len, GFP_KERNEL);
+ if (!msg)
+ return ERR_PTR(-ENOMEM);
+
+ msg->type = type;
+ msg->len = len;
+
+ hdr = (union cmd_header *)msg->data;
+ switch (type) {
+ case NRF70_MSG_SYSTEM:
+ fallthrough;
+ case NRF70_MSG_DATA:
+ hdr->sys.id = id;
+ hdr->sys.len = data_len;
+ break;
+ case NRF70_MSG_UMAC:
+ hdr->umac.id = id;
+ if (iface >= 0) {
+ hdr->umac.idx.wdev_id = iface;
+ hdr->umac.idx.valid_fields = NRF70_UMAC_ID_WDEV;
+ }
+ break;
+ default:
+ kfree(msg);
+ return ERR_PTR(-EINVAL);
+ }
+
+ return msg;
+}
+
+static const u8 nrf7002_qfn_rf_params[NRF70_RF_PARAMS_SZ] = {
+ NRF70_RESERVED,
+ NRF70_RESERVED,
+ NRF70_RESERVED,
+ NRF70_RESERVED,
+ NRF70_RESERVED,
+ NRF70_RESERVED,
+ /* XO */
+ NRF70_QFN_XO_VAL,
+ /* PD adjust values for MCS7. Currently unused. */
+ NRF70_PD_ADJUST_VAL,
+ NRF70_PD_ADJUST_VAL,
+ NRF70_PD_ADJUST_VAL,
+ NRF70_PD_ADJUST_VAL,
+ /* TX power systematic offset. */
+ NRF70_SYSTEM_OFFSET_LB,
+ NRF70_SYSTEM_OFFSET_HB_CHAN_LOW,
+ NRF70_SYSTEM_OFFSET_HB_CHAN_MID,
+ NRF70_SYSTEM_OFFSET_HB_CHAN_HIGH,
+ /* Max TX power value for which both EVM and SEM pass. */
+ NRF70_QFN_MAX_TX_PWR_DSSS,
+ NRF70_QFN_MAX_TX_PWR_LB_MCS7,
+ NRF70_QFN_MAX_TX_PWR_LB_MCS0,
+ NRF70_QFN_MAX_TX_PWR_HB_LOW_CHAN_MCS7,
+ NRF70_QFN_MAX_TX_PWR_HB_MID_CHAN_MCS7,
+ NRF70_QFN_MAX_TX_PWR_HB_HIGH_CHAN_MCS7,
+ NRF70_QFN_MAX_TX_PWR_HB_LOW_CHAN_MCS0,
+ NRF70_QFN_MAX_TX_PWR_HB_MID_CHAN_MCS0,
+ NRF70_QFN_MAX_TX_PWR_HB_HIGH_CHAN_MCS0,
+ /* RX gain adjustment offsets. */
+ NRF70_RX_GAIN_OFFSET_LB_CHAN,
+ NRF70_RX_GAIN_OFFSET_HB_LOW_CHAN,
+ NRF70_RX_GAIN_OFFSET_HB_MID_CHAN,
+ NRF70_RX_GAIN_OFFSET_HB_HIGH_CHAN,
+ /* Voltage and temperature dependent backoffs. */
+ NRF70_QFN_MAX_CHIP_TEMP,
+ NRF70_QFN_MIN_CHIP_TEMP,
+ NRF70_QFN_LB_MAX_PWR_BKF_HI_TEMP,
+ NRF70_QFN_LB_MAX_PWR_BKF_LOW_TEMP,
+ NRF70_QFN_HB_MAX_PWR_BKF_HI_TEMP,
+ NRF70_QFN_HB_MAX_PWR_BKF_LOW_TEMP,
+ NRF70_QFN_LB_VBT_LT_VLOW,
+ NRF70_QFN_HB_VBT_LT_VLOW,
+ NRF70_QFN_LB_VBT_LT_LOW,
+ NRF70_QFN_HB_VBT_LT_LOW,
+ NRF70_RESERVED,
+ NRF70_RESERVED,
+ NRF70_RESERVED,
+ NRF70_RESERVED,
+ /* PHY parameters blob. */
+ NRF70_PHY_PARAMS,
+};
+
+static int nrf70_init_command(struct spi_mem *mem)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_sys_init *cmd;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_SYSTEM, NRF70_CMD_INIT, sizeof(*cmd),
+ -1);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_sys_init *)msg->data;
+ cmd->sys_param.phy_calib = NRF70_DEF_PHY_CALIB;
+ cmd->sys_param.hw_bringup_time = 7300;
+ cmd->sys_param.sw_bringup_time = 5000;
+ cmd->sys_param.bcn_time_out = 20000;
+ memcpy(cmd->sys_param.rf_params, nrf7002_qfn_rf_params,
+ sizeof(nrf7002_qfn_rf_params));
+ cmd->sys_param.rf_params_valid = true;
+ cmd->tcp_ip_checksum_offload = 1;
+ cmd->op_band = NRF70_OP_BAND_ALL;
+ cmd->discon_timeout = 20;
+
+ cmd->rx_buf_pools[0].size = NRF70_RX_DATA_MAX_SZ;
+ cmd->rx_buf_pools[1].size = NRF70_RX_DATA_MAX_SZ;
+ cmd->rx_buf_pools[2].size = NRF70_RX_DATA_MAX_SZ;
+ cmd->rx_buf_pools[0].count = NRF70_NUM_RX_BUFS / NRF70_NUM_RX_QUEUES;
+ cmd->rx_buf_pools[1].count = NRF70_NUM_RX_BUFS / NRF70_NUM_RX_QUEUES;
+ cmd->rx_buf_pools[2].count = NRF70_NUM_RX_BUFS / NRF70_NUM_RX_QUEUES;
+
+ cmd->data_config_params.rate_protection_type = 0;
+ cmd->data_config_params.aggregation = 1;
+ cmd->data_config_params.wmm = 0;
+ cmd->data_config_params.max_tx_agg_sessions = 4;
+ cmd->data_config_params.max_rx_agg_sessions = 8;
+ cmd->data_config_params.max_tx_aggregation = 12;
+ cmd->data_config_params.reorder_buf_size = 64;
+ cmd->data_config_params.max_rxampdu_size = 3; /* 64 KiB. */
+
+ cmd->vbat_config.temp_based_calib_en = 1;
+ cmd->vbat_config.temp_calib_bitmap = NRF70_DEF_PHY_TEMP_CALIB;
+ cmd->vbat_config.vbat_calibp_bitmap = NRF70_PHY_CALIB_FLAG_DPD;
+ cmd->vbat_config.temp_vbat_mon_period = 1024 * 1024;
+ cmd->vbat_config.vth_very_low = NRF70_VBAT_MV_TO_VTH(3060);
+ cmd->vbat_config.vth_low = NRF70_VBAT_MV_TO_VTH(3340);
+ cmd->vbat_config.vth_hi = NRF70_VBAT_MV_TO_VTH(3480);
+ cmd->vbat_config.temp_threshold = 40;
+
+ ret = nrf70_enqueue_message(mem, msg);
+ kfree(msg);
+
+ return ret ? ret : (wait_for_completion_timeout(&priv->init_done, HZ) ?
+ 0 : -ETIMEDOUT);
+}
+
+static int nrf70_hwaddr_change_command(struct spi_mem *mem, const u8 *hwaddr,
+ int iface)
+{
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_change_hwaddr *cmd;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_CHANGE_MACADDR,
+ sizeof(*cmd), iface);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_change_hwaddr *)msg->data;
+ ether_addr_copy(cmd->hwaddr, hwaddr);
+
+ ret = nrf70_enqueue_message(mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static struct nrf70_vif *nrf70_get_vif(struct nrf70_priv *priv, int iface)
+{
+ struct nrf70_vif *vif;
+
+ list_for_each_entry(vif, &priv->vifs, list)
+ if (vif->iface == iface)
+ return vif;
+
+ return ERR_PTR(-EINVAL);
+}
+
+static int nrf70_dequeue_sys_event(struct spi_mem *mem, void *data)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct device *dev = &mem->spi->dev;
+ struct nrf70_header *header = data;
+ struct nrf70_vif *vif;
+
+ switch (header->id) {
+ case NRF70_EVENT_INIT_DONE:
+ fallthrough;
+ case NRF70_EVENT_DEINIT_DONE:
+ complete_all(&priv->init_done);
+ break;
+ case NRF70_EVENT_MODE_SET_DONE:
+ {
+ struct nrf70_event_raw_config_mode *ev = data;
+
+ vif = nrf70_get_vif(priv, ev->if_idx);
+
+ if (IS_ERR(vif))
+ return PTR_ERR(vif);
+
+ if (!ev->status)
+ complete(&vif->iface_updated);
+ }
+ break;
+ case NRF70_EVENT_CHANNEL_SET_DONE:
+ {
+ struct nrf70_event_set_channel *ev = data;
+
+ vif = nrf70_get_vif(priv, ev->if_idx);
+
+ if (!ev->status)
+ complete(&vif->chan_updated);
+ }
+ break;
+ default:
+ dev_dbg(dev, "Unsupported system event type: %d\n",
+ header->id);
+ return 1;
+ }
+
+ return 0;
+}
+
+enum nrf70_rx_pkt_type {
+ NRF70_RX_PKT_DATA,
+ NRF70_RX_PKT_BCN_PRB_RSP,
+ NRF70_RAW_RX_PKT
+};
+
+enum nrf70_pkt_type {
+ NRF70_PKT_MPDU,
+ NRF70_PKT_MSDU_WITH_MAC,
+ NRF70_PKT_MSDU,
+};
+
+struct nrf70_radiotap_hdr {
+ struct ieee80211_radiotap_header hdr;
+ u8 rate;
+ __le16 chan;
+ __le16 chan_mask;
+ s8 signal;
+};
+
+static void nrf70_netif_rx(struct sk_buff *skb, struct nrf70_vif *vif)
+{
+ int len = skb->len;
+
+ if (netif_rx(skb)) {
+ u64_stats_update_begin(&vif->stats.syncp);
+ vif->stats.rx_dropped++;
+ u64_stats_update_end(&vif->stats.syncp);
+ return;
+ }
+
+ u64_stats_update_begin(&vif->stats.syncp);
+ vif->stats.rx_packets++;
+ vif->stats.rx_bytes += len;
+ u64_stats_update_end(&vif->stats.syncp);
+}
+
+static int nrf70_handle_cmd_rx_buf(struct spi_mem *mem,
+ struct nrf70_cmd_rx_buf *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_vif *vif = nrf70_get_vif(priv, ev->wdev_id);
+ struct device *dev = &mem->spi->dev;
+ struct ieee80211_mgmt *mgmt;
+ struct sk_buff *skb;
+ struct cfg80211_bss *bss;
+ struct nrf70_radiotap_hdr *hdr;
+ struct ieee80211_hdr *data_hdr;
+ struct ieee80211_channel *ch;
+ size_t len, rx_addr_base;
+ u32 bounce_addr, desc;
+ int data_offset, i;
+ bool amsdu;
+
+ if (IS_ERR(vif))
+ return PTR_ERR(vif);
+
+ for (i = 0; i < ev->rx_pkt_cnt; i++) {
+ desc = ev->buf_info[i].desc_id;
+ rx_addr_base = (NRF70_RPU_PKTRAM_PKT_BASE +
+ NRF70_RPU_PKTRAM_PKT_BASE_SZ) -
+ (NRF70_NUM_RX_BUFS * NRF70_RX_DATA_MAX_SZ);
+ bounce_addr = rx_addr_base + desc * NRF70_RX_DATA_MAX_SZ;
+ len = ev->buf_info[i].pkt_len;
+ skb = alloc_skb(len + sizeof(*hdr), GFP_ATOMIC);
+ if (!skb)
+ continue;
+
+ skb_reserve(skb, sizeof(*hdr));
+ skb_put(skb, len);
+ nrf70_readv(mem, bounce_addr, skb->data, len);
+ nrf70_init_rx(mem, ev->buf_info[i].desc_id);
+
+ ch = ieee80211_get_channel(priv->wiphy, ev->frequency);
+
+ switch (ev->rx_pkt_type) {
+ case NRF70_RX_PKT_DATA:
+ data_hdr = (struct ieee80211_hdr *)skb->data;
+ data_offset = ev->mac_header_len -
+ ieee80211_hdrlen(data_hdr->frame_control);
+
+ amsdu = ev->buf_info[i].pkt_type != NRF70_PKT_MPDU;
+ if (ieee80211_data_to_8023_exthdr(skb, NULL,
+ vif->ndev->dev_addr,
+ vif->wdev.iftype,
+ data_offset, amsdu)) {
+ u64_stats_update_begin(&vif->stats.syncp);
+ vif->stats.rx_dropped++;
+ u64_stats_update_end(&vif->stats.syncp);
+ break;
+ }
+
+ skb->dev = vif->ndev;
+ skb->protocol = eth_type_trans(skb, skb->dev);
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ nrf70_netif_rx(skb, vif);
+
+ /* Skip over kfree_skb - net stack will handle that. */
+ continue;
+ case NRF70_RX_PKT_BCN_PRB_RSP:
+ mgmt = (struct ieee80211_mgmt *)skb->data;
+ if (skb->len < 24 ||
+ (!ieee80211_is_probe_resp(mgmt->frame_control) &&
+ !ieee80211_is_beacon(mgmt->frame_control)))
+ break;
+
+ bss = cfg80211_inform_bss_frame(priv->wiphy, ch, mgmt,
+ skb->len, ev->signal,
+ GFP_ATOMIC);
+ cfg80211_put_bss(priv->wiphy, bss);
+ break;
+ case NRF70_RAW_RX_PKT:
+ hdr = skb_push(skb, sizeof(*hdr));
+ memset(hdr, 0, sizeof(*hdr));
+ hdr->hdr.it_version = PKTHDR_RADIOTAP_VERSION;
+ hdr->hdr.it_pad = 0;
+ hdr->hdr.it_len = cpu_to_le16(sizeof(*hdr));
+ hdr->hdr.it_present = NRF70_RADIOTAP_PRESENT_FIELDS;
+ hdr->rate = ev->rate;
+ hdr->chan = ev->frequency;
+
+ if (ch->band == NL80211_BAND_5GHZ)
+ hdr->chan_mask |= IEEE80211_CHAN_OFDM |
+ IEEE80211_CHAN_5GHZ;
+ else
+ hdr->chan_mask |= IEEE80211_CHAN_2GHZ;
+
+ hdr->signal = MBM_TO_DBM(ev->signal);
+
+ skb->dev = vif->ndev;
+ skb_reset_mac_header(skb);
+ skb->pkt_type = PACKET_OTHERHOST;
+ skb->protocol = htons(ETH_P_802_2);
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ nrf70_netif_rx(skb, vif);
+
+ /* Skip over kfree_skb - net stack will handle that. */
+ continue;
+ default:
+ dev_err(dev, "Unknown rx packet type: %d\n",
+ ev->rx_pkt_type);
+ break;
+ }
+ kfree_skb(skb);
+ }
+
+ return 0;
+}
+
+static int nrf70_get_sta_idx(struct nrf70_vif *vif, const u8 *mac)
+{
+ unsigned long bmp;
+ int bit;
+
+ bmp = READ_ONCE(vif->sta_bitmap) & NRF70_PEERS_MASK;
+
+ while (1) {
+ bit = ffz(bmp);
+ if (bit >= NRF70_PEERS_MAX)
+ return -ENOENT;
+
+ if (ether_addr_equal(vif->sta[bit].addr, mac))
+ return bit;
+
+ set_bit(bit, &bmp);
+ }
+}
+
+static int nrf70_handle_pm_mode(struct spi_mem *mem,
+ struct nrf70_cmd_sap_pm *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_vif *vif = nrf70_get_vif(priv, ev->wdev_id);
+ int i;
+
+ if (IS_ERR(vif))
+ return PTR_ERR(vif);
+
+ guard(spinlock_irqsave)(&vif->sta_lock);
+ i = nrf70_get_sta_idx(vif, ev->hwaddr);
+ if (i < 0)
+ return -EINVAL;
+
+ vif->sta[i].can_xmit = !ev->state;
+
+ lockdep_assert_wiphy(priv->wiphy);
+
+ if (ev->state == NRF70_SAP_PM_CLIENT_ACTIVE)
+ wiphy_work_queue(priv->wiphy, &vif->sta[i].pending_work);
+ else
+ wiphy_work_cancel(priv->wiphy, &vif->sta[i].pending_work);
+
+ return 0;
+}
+
+static void nrf70_drain_tx(struct nrf70_priv *priv, struct nrf70_vif *vif)
+{
+ unsigned long bmp;
+ int bit;
+
+ guard(spinlock_irqsave)(&vif->sta_lock);
+ bmp = READ_ONCE(vif->sta_bitmap) & NRF70_PEERS_MASK;
+
+ while (1) {
+ bit = ffz(bmp);
+ if (bit >= NRF70_PEERS_MAX)
+ break;
+
+ vif->sta[bit].can_xmit = false;
+ wiphy_work_cancel(priv->wiphy, &vif->sta[bit].pending_work);
+ skb_queue_purge(&vif->sta[bit].pending);
+
+ set_bit(bit, &bmp);
+ }
+
+ wiphy_work_cancel(priv->wiphy, &vif->xmit_work);
+ skb_queue_purge(&vif->tx_queue);
+}
+
+static void nrf70_carrier_change(struct nrf70_priv *priv, int iface, bool state)
+{
+ struct nrf70_vif *vif = nrf70_get_vif(priv, iface);
+
+ if (IS_ERR(vif))
+ return;
+
+ if (state) {
+ netif_carrier_on(vif->ndev);
+ return;
+ }
+
+ netif_carrier_off(vif->ndev);
+ nrf70_drain_tx(priv, vif);
+}
+
+static int nrf70_handle_cmd_tx_buff_done(struct spi_mem *mem,
+ struct nrf70_event_tx_buff_done *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_vif *vif;
+ int i, iface, desc = ev->tx_desc_num;
+
+ mutex_lock(&priv->desc_lock);
+ iface = !!(priv->tx_desc_bitmap[0] & BIT(desc));
+ set_bit(ev->tx_desc_num, &priv->tx_desc_bitmap[iface]);
+ mutex_unlock(&priv->desc_lock);
+
+ vif = nrf70_get_vif(priv, iface);
+ wiphy_work_queue(priv->wiphy, &vif->xmit_work);
+
+ for (i = 0; i < ev->num_tx_status_code; i++) {
+ if (ev->tx_status_code[i]) {
+ u64_stats_update_begin(&vif->stats.syncp);
+ vif->stats.tx_dropped++;
+ u64_stats_update_end(&vif->stats.syncp);
+ }
+ }
+
+ return 0;
+}
+
+static int nrf70_dequeue_data_event(struct spi_mem *mem, void *data)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct device *dev = &mem->spi->dev;
+ struct nrf70_header *header = data;
+
+ switch (header->id) {
+ case NRF70_CMD_TX_BUFF_DONE:
+ nrf70_handle_cmd_tx_buff_done(mem, data);
+ break;
+ case NRF70_CMD_CARRIER_ON:
+ fallthrough;
+ case NRF70_CMD_CARRIER_OFF:
+ {
+ struct nrf70_event_carrier_state *ev = data;
+ bool state = header->id == NRF70_CMD_CARRIER_ON;
+
+ nrf70_carrier_change(priv, ev->wdev_id, state);
+ }
+ break;
+ case NRF70_CMD_RX_BUFF:
+ nrf70_handle_cmd_rx_buf(mem, data);
+ break;
+ case NRF70_CMD_PM_MODE:
+ nrf70_handle_pm_mode(mem, data);
+ break;
+ default:
+ dev_dbg(dev, "Unsupported data event type: %d\n", header->id);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int nrf70_get_scan_results_command(struct spi_mem *mem, int iface)
+{
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_get_scan_results *cmd;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_GET_SCAN_RESULTS,
+ sizeof(*cmd), iface);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_get_scan_results *)msg->data;
+ cmd->reason = 0;
+
+ ret = nrf70_enqueue_message(mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static void nrf70_handle_cmd_status(struct spi_mem *mem,
+ struct nrf70_event_cmd_status *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_vif *vif = nrf70_get_vif(priv, ev->header.idx.wdev_id);
+ struct cfg80211_scan_info info = { .aborted = true, };
+
+ if (IS_ERR(vif))
+ return;
+
+ if (!ev->status)
+ return;
+
+ switch (ev->cmd_id) {
+ case NRF70_UMAC_CMD_TRIGGER_SCAN:
+ if (vif->scan_req) {
+ cfg80211_scan_done(vif->scan_req, &info);
+ vif->scan_req = NULL;
+ }
+
+ WRITE_ONCE(priv->scan_in_progress, false);
+ break;
+ case NRF70_UMAC_CMD_GET_STATION:
+ complete(&priv->station_info_available);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+nrf70_handle_scan_display_results(struct spi_mem *mem,
+ struct nrf70_event_scan_display_results *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_vif *vif = nrf70_get_vif(priv, ev->header.idx.wdev_id);
+ struct device *dev = &mem->spi->dev;
+ struct cfg80211_bss *bss;
+ struct cfg80211_scan_info info = { .aborted = false, };
+ struct nrf70_display_results *res;
+ struct ieee80211_channel *rx_chan;
+ u8 ie[2 + IEEE80211_MAX_SSID_LEN];
+ int i, freq;
+
+ if (IS_ERR(vif))
+ return;
+
+ if (ev->bss_count > NRF70_DISP_SCAN_RES_SZ) {
+ dev_err(dev, "BSS count %d too large\n", ev->bss_count);
+ return;
+ }
+
+ for (i = 0; i < ev->bss_count; i++) {
+ res = &ev->results[i];
+ freq = ieee80211_channel_to_freq_khz(res->chan, res->band);
+ rx_chan = ieee80211_get_channel_khz(priv->wiphy, freq);
+ bss = cfg80211_get_bss(priv->wiphy, rx_chan, res->hwaddr,
+ res->ssid.ssid, res->ssid.len,
+ IEEE80211_BSS_TYPE_ESS,
+ IEEE80211_PRIVACY_ANY);
+ if (bss) {
+ cfg80211_put_bss(priv->wiphy, bss);
+ continue;
+ }
+
+ /*
+ * Generate a partial entry until the first BSS info event
+ * becomes available.
+ */
+ memset(ie, 0, sizeof(ie));
+ ie[0] = WLAN_EID_SSID;
+ ie[1] = res->ssid.len;
+ memcpy(ie + 2, res->ssid.ssid, res->ssid.len);
+
+ bss = cfg80211_inform_bss(priv->wiphy,
+ rx_chan,
+ CFG80211_BSS_FTYPE_BEACON,
+ res->hwaddr,
+ 0,
+ res->capability,
+ res->beacon_interval,
+ ie, 2 + res->ssid.len,
+ res->signal.mbm_signal,
+ GFP_KERNEL);
+ cfg80211_put_bss(priv->wiphy, bss);
+ }
+
+ /* Final results for the scan request. */
+ if (!ev->header.seq) {
+ info.aborted = false;
+ cfg80211_scan_done(vif->scan_req, &info);
+ vif->scan_req = NULL;
+ }
+}
+
+static void nrf70_handle_auth(struct spi_mem *mem, struct nrf70_event_mlme *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_vif *vif = nrf70_get_vif(priv, ev->header.idx.wdev_id);
+
+ if (IS_ERR(vif))
+ return;
+
+ cfg80211_rx_mlme_mgmt(vif->ndev, ev->frame.data, ev->frame.len);
+}
+
+static void nrf70_handle_assoc(struct spi_mem *mem, struct nrf70_event_mlme *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_vif *vif = nrf70_get_vif(priv, ev->header.idx.wdev_id);
+ struct cfg80211_rx_assoc_resp_data data = {
+ .buf = ev->frame.data,
+ .len = ev->frame.len,
+ .uapsd_queues = -1,
+ .req_ies = ev->req_ie,
+ .req_ies_len = ev->req_ie_len,
+ .uapsd_queues = ev->wme_uapsd_queues,
+ };
+
+ if (IS_ERR(vif))
+ return;
+
+ data.links[0].bss = vif->bss;
+
+ cfg80211_rx_assoc_resp(vif->ndev, &data);
+}
+
+static void nrf70_handle_tx_mlme_mgmt(struct spi_mem *mem,
+ struct nrf70_event_mlme *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_vif *vif = nrf70_get_vif(priv, ev->header.idx.wdev_id);
+
+ if (IS_ERR(vif))
+ return;
+
+ cfg80211_tx_mlme_mgmt(vif->ndev, ev->frame.data, ev->frame.len, false);
+}
+
+static void nrf70_handle_rx_mgmt(struct spi_mem *mem,
+ struct nrf70_event_mlme *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_vif *vif = nrf70_get_vif(priv, ev->header.idx.wdev_id);
+
+ if (IS_ERR(vif))
+ return;
+
+ (void)cfg80211_rx_mgmt(&vif->wdev, ev->frequency, ev->rx_signal_dbm,
+ ev->frame.data, ev->frame.len, ev->wifi_flags);
+}
+
+static void nrf70_handle_cookie_resp(struct spi_mem *mem,
+ struct nrf70_event_cookie_resp *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_cookie *cookie;
+
+ cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
+ if (!cookie)
+ return;
+
+ INIT_LIST_HEAD(&cookie->list);
+ cookie->host_cookie = ev->host_cookie;
+ cookie->rpu_cookie = ev->cookie;
+
+ list_add_tail(&priv->cookies, &cookie->list);
+}
+
+static void nrf70_handle_frame_tx_status(struct spi_mem *mem,
+ struct nrf70_event_mlme *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_vif *vif = nrf70_get_vif(priv, ev->header.idx.wdev_id);
+ struct device *dev = &mem->spi->dev;
+ bool ack = ev->wifi_flags & NRF70_EVENT_MLME_ACK;
+ struct nrf70_cookie *cookie, *tmp;
+ u64 host_cookie = 0;
+
+ if (IS_ERR(vif))
+ return;
+
+ list_for_each_entry_safe(cookie, tmp, &priv->cookies, list) {
+ if (cookie->rpu_cookie != ev->cookie)
+ continue;
+
+ host_cookie = cookie->host_cookie;
+ list_del(&cookie->list);
+ kfree(cookie);
+ }
+
+ if (!host_cookie)
+ dev_err(dev, "Host cookie for %llx not found\n", ev->cookie);
+
+ cfg80211_mgmt_tx_status(&vif->wdev, host_cookie, ev->frame.data,
+ ev->frame.len, ack, GFP_ATOMIC);
+}
+
+#define NRF70_TX_DESC_BMP \
+ ((priv->tx_desc_bitmap[0] & priv->tx_desc_bitmap[1]) & NRF70_DESC_MASK)
+
+static int nrf70_dequeue_tx(struct nrf70_priv *priv, struct sk_buff **skb,
+ struct sk_buff_head *queue)
+{
+ int desc, iface;
+
+ guard(mutex)(&priv->desc_lock);
+
+ desc = ffs(NRF70_TX_DESC_BMP) - 1;
+
+ if (desc < 0)
+ return -1;
+
+ *skb = skb_dequeue(queue);
+ if (!*skb)
+ return -1;
+
+ iface = NRF70_NDEV_TO_IFACE((*skb)->dev);
+ clear_bit(desc, &priv->tx_desc_bitmap[iface]);
+
+ return desc;
+}
+
+static void nrf70_pending_worker(struct wiphy *wiphy, struct wiphy_work *work)
+{
+ struct nrf70_sta *sta = container_of(work, struct nrf70_sta,
+ pending_work);
+ struct sk_buff *skb;
+ struct nrf70_vif *vif;
+
+ /* Move pending skbs into the hw queue. */
+ while ((skb = skb_dequeue(&sta->pending))) {
+ vif = ((struct nrf70_ndev_priv *)netdev_priv(skb->dev))->vif;
+ skb_queue_tail(&vif->tx_queue, skb);
+ wiphy_work_queue(wiphy, &vif->xmit_work);
+ }
+}
+
+static void nrf70_xmit_worker(struct wiphy *wiphy, struct wiphy_work *work)
+{
+ struct nrf70_vif *vif = container_of(work, struct nrf70_vif, xmit_work);
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct spi_mem *mem = priv->mem;
+ struct device *dev = &mem->spi->dev;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_tx_buf *cmd;
+ struct sk_buff *skb;
+ size_t skb_len, msg_len;
+ u32 addr, bounce_addr, val;
+ int desc;
+
+ if (skb_queue_empty(&vif->tx_queue))
+ return;
+
+ msg_len = sizeof(*msg) + sizeof(*cmd) + sizeof(struct nrf70_buf_info);
+ msg = kzalloc(msg_len, GFP_KERNEL);
+ if (unlikely(!msg)) {
+ dev_err(dev, "Unable to allocate message buffer\n");
+ return;
+ }
+
+ while (!skb_queue_empty(&vif->tx_queue)) {
+ desc = nrf70_dequeue_tx(priv, &skb, &vif->tx_queue);
+ if (desc < 0)
+ break;
+
+ if (skb_queue_len(&vif->tx_queue) < NRF70_TX_PENDING_WMARK &&
+ netif_queue_stopped(skb->dev))
+ netif_wake_queue(skb->dev);
+
+ skb_len = ALIGN(skb->len, 4);
+ if (skb_len > NRF70_TX_DATA_MAX_SZ) {
+ u64_stats_update_begin(&vif->stats.syncp);
+ vif->stats.tx_dropped++;
+ u64_stats_update_end(&vif->stats.syncp);
+ goto consume;
+ }
+
+ bounce_addr = NRF70_RPU_PKTRAM_PKT_BASE +
+ desc * NRF70_TX_DATA_MAX_SZ;
+
+ nrf70_writev(priv->mem, bounce_addr, skb->data, skb_len);
+
+ msg->type = NRF70_MSG_DATA;
+ msg->len = msg_len;
+ cmd = (struct nrf70_cmd_tx_buf *)msg->data;
+ cmd->header.id = NRF70_CMD_TX_BUFF;
+ cmd->header.len = sizeof(*cmd) + sizeof(struct nrf70_buf_info);
+
+ ether_addr_copy(cmd->mac_hdr_info.dst, skb->data);
+ ether_addr_copy(cmd->mac_hdr_info.src, skb->data + ETH_ALEN);
+
+ cmd->mac_hdr_info.tx_flags = skb->priority & NRF70_TX_QOS_MASK;
+ if (skb->priority == 0xff)
+ cmd->mac_hdr_info.tx_flags |= NRF70_TX_FLAG_TWT_EMERG;
+
+ if (!skb_checksum_complete(skb))
+ cmd->mac_hdr_info.tx_flags |= NRF70_TX_FLAG_CSUM_AVAIL;
+
+ cmd->mac_hdr_info.etype = be16_to_cpu(skb->protocol);
+ cmd->mac_hdr_info.eosp = 0;
+ cmd->wdev_id = NRF70_NDEV_TO_IFACE(skb->dev);
+ cmd->tx_desc_num = desc;
+ cmd->num_tx_pkts = 1; /* Frame aggregation not yet supported. */
+
+ cmd->buf_info[0].pkt_len = skb->len;
+ cmd->buf_info[0].ddr_ptr = bounce_addr;
+
+ addr = priv->tx_cmd_base + desc * NRF70_DATA_CMD_TX_MAX_SZ;
+ nrf70_writev(mem, addr, msg, ALIGN(msg->len, 4));
+
+ mutex_lock(&priv->enqueue_lock);
+ nrf70_writel(mem, priv->queue[NRF70_CMD_BUSY_QUEUE].eq, addr);
+ val = priv->num_cmds++ | NRF70_UCCP_HOST2_TO_MTX2_CMD_DATA_MASK;
+ nrf70_writel(mem, NRF70_SBUS_UCCP_CORE_HOST2_TO_MTX2_CMD, val);
+ mutex_unlock(&priv->enqueue_lock);
+
+ u64_stats_update_begin(&vif->stats.syncp);
+ vif->stats.tx_packets++;
+ vif->stats.tx_bytes += skb->len;
+ u64_stats_update_end(&vif->stats.syncp);
+
+consume:
+ consume_skb(skb);
+ }
+
+ kfree(msg);
+}
+
+static void nrf70_handle_station(struct spi_mem *mem,
+ struct nrf70_event_new_station *ev,
+ bool new_station)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_vif *vif = nrf70_get_vif(priv, ev->header.idx.wdev_id);
+ struct device *dev = &mem->spi->dev;
+ int i;
+
+ if (IS_ERR(vif))
+ return;
+
+ guard(spinlock_irqsave)(&vif->sta_lock);
+ if (new_station) {
+ i = ffs(vif->sta_bitmap) - 1;
+ if (i < 0) {
+ dev_err(dev, "Unable to store new station data\n");
+ return;
+ }
+
+ clear_bit(i, &vif->sta_bitmap);
+ ether_addr_copy(vif->sta[i].addr, ev->hwaddr);
+
+ wiphy_work_init(&vif->sta[i].pending_work,
+ nrf70_pending_worker);
+ skb_queue_head_init(&vif->sta[i].pending);
+ vif->sta[i].can_xmit = true;
+
+ return;
+ }
+
+ i = nrf70_get_sta_idx(vif, ev->hwaddr);
+ if (i < 0)
+ return;
+
+ wiphy_work_cancel(priv->wiphy, &vif->sta[i].pending_work);
+ skb_queue_purge(&vif->sta[i].pending);
+ vif->sta[i].can_xmit = false;
+ set_bit(i, &vif->sta_bitmap);
+}
+
+static void nrf70_handle_get_station(struct spi_mem *mem,
+ struct nrf70_event_new_station *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct device *dev = &mem->spi->dev;
+ struct station_info *sinfo = priv->sinfo;
+ u32 valid = ev->sta_info.valid_fields;
+ struct nrf70_rate_info *rate_info;
+
+ if (!sinfo) {
+ dev_err(dev, "Invalid station info reference\n");
+ return;
+ }
+
+ if (valid & NRF70_STA_INFO_CONNECTED_TIME) {
+ sinfo->connected_time = ev->sta_info.connected_time;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_CONNECTED_TIME);
+ }
+ if (valid & NRF70_STA_INFO_INACTIVE_TIME) {
+ sinfo->inactive_time = ev->sta_info.inactive_time;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_INACTIVE_TIME);
+ }
+ if (valid & NRF70_STA_INFO_RX_BYTES) {
+ sinfo->rx_bytes = ev->sta_info.rx_bytes;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BYTES);
+ }
+ if (valid & NRF70_STA_INFO_TX_BYTES) {
+ sinfo->tx_bytes = ev->sta_info.tx_bytes;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BYTES);
+ }
+ sinfo->chains = ev->sta_info.chain.signal_mask;
+ if (valid & NRF70_STA_INFO_CHAIN_SIGNAL) {
+ memcpy(sinfo->chain_signal, ev->sta_info.chain.signal,
+ sizeof(sinfo->chain_signal));
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL);
+ }
+ if (valid & NRF70_STA_INFO_CHAIN_SIGNAL_AVG) {
+ memcpy(sinfo->chain_signal_avg, ev->sta_info.chain.signal_avg,
+ sizeof(sinfo->chain_signal_avg));
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG);
+ }
+ if (valid & NRF70_STA_INFO_TX_BITRATE) {
+ rate_info = &ev->sta_info.tx_bitrate;
+
+ if (rate_info->flags & NRF70_RATE_INFO_40_MHZ_WIDTH)
+ sinfo->txrate.bw = RATE_INFO_BW_40;
+ else if (rate_info->flags & NRF70_RATE_INFO_80_MHZ_WIDTH)
+ sinfo->txrate.bw = RATE_INFO_BW_80;
+ else if (rate_info->flags & NRF70_RATE_INFO_160_MHZ_WIDTH)
+ sinfo->txrate.bw = RATE_INFO_BW_160;
+ else
+ sinfo->txrate.bw = RATE_INFO_BW_20;
+
+ if (rate_info->flags & NRF70_RATE_INFO_SHORT_GI)
+ sinfo->txrate.flags |= RATE_INFO_FLAGS_SHORT_GI;
+
+ if (rate_info->valid_fields & NRF70_RATE_INFO_BITRATE)
+ sinfo->txrate.legacy = rate_info->bitrate;
+
+ if (rate_info->valid_fields & NRF70_RATE_INFO_MCS) {
+ sinfo->txrate.mcs = rate_info->mcs;
+ sinfo->txrate.flags |= RATE_INFO_FLAGS_MCS;
+ }
+
+ if (rate_info->valid_fields & NRF70_RATE_INFO_VHT_MCS) {
+ sinfo->txrate.mcs = rate_info->vht_mcs;
+ sinfo->txrate.flags |= RATE_INFO_FLAGS_VHT_MCS;
+ }
+
+ if (rate_info->valid_fields & NRF70_RATE_INFO_VHT_NSS)
+ sinfo->txrate.nss = rate_info->vht_nss;
+
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE);
+ }
+ if (valid & NRF70_STA_INFO_RX_BITRATE) {
+ rate_info = &ev->sta_info.rx_bitrate;
+
+ if (rate_info->flags & NRF70_RATE_INFO_40_MHZ_WIDTH)
+ sinfo->rxrate.bw = RATE_INFO_BW_40;
+ else if (rate_info->flags & NRF70_RATE_INFO_80_MHZ_WIDTH)
+ sinfo->rxrate.bw = RATE_INFO_BW_80;
+ else if (rate_info->flags & NRF70_RATE_INFO_160_MHZ_WIDTH)
+ sinfo->rxrate.bw = RATE_INFO_BW_160;
+ else
+ sinfo->rxrate.bw = RATE_INFO_BW_20;
+
+ if (rate_info->flags & NRF70_RATE_INFO_SHORT_GI)
+ sinfo->rxrate.flags |= RATE_INFO_FLAGS_SHORT_GI;
+
+ if (rate_info->valid_fields & NRF70_RATE_INFO_BITRATE)
+ sinfo->rxrate.legacy = rate_info->bitrate;
+
+ if (rate_info->valid_fields & NRF70_RATE_INFO_MCS) {
+ sinfo->rxrate.mcs = rate_info->mcs;
+ sinfo->rxrate.flags |= RATE_INFO_FLAGS_MCS;
+ }
+
+ if (rate_info->valid_fields & NRF70_RATE_INFO_VHT_MCS) {
+ sinfo->rxrate.mcs = rate_info->vht_mcs;
+ sinfo->rxrate.flags |= RATE_INFO_FLAGS_VHT_MCS;
+ }
+
+ if (rate_info->valid_fields & NRF70_RATE_INFO_VHT_NSS)
+ sinfo->rxrate.nss = rate_info->vht_nss;
+
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BITRATE);
+ }
+ if (valid & NRF70_STA_INFO_STA_FLAGS) {
+ sinfo->sta_flags.mask = ev->sta_info.sta_flags.mask;
+ sinfo->sta_flags.set = ev->sta_info.sta_flags.set;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_STA_FLAGS);
+ }
+ if (valid & NRF70_STA_INFO_SIGNAL) {
+ sinfo->signal = ev->sta_info.signal;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL);
+ }
+ if (valid & NRF70_STA_INFO_SIGNAL_AVG) {
+ sinfo->signal_avg = ev->sta_info.signal_avg;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL_AVG);
+ }
+ if (valid & NRF70_STA_INFO_RX_PACKETS) {
+ sinfo->rx_packets = ev->sta_info.rx_packets;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_PACKETS);
+ }
+ if (valid & NRF70_STA_INFO_TX_PACKETS) {
+ sinfo->tx_packets = ev->sta_info.tx.packets;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_PACKETS);
+ }
+ if (valid & NRF70_STA_INFO_TX_RETRIES) {
+ sinfo->tx_retries = ev->sta_info.tx.retries;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_RETRIES);
+ }
+ if (valid & NRF70_STA_INFO_TX_FAILED) {
+ sinfo->tx_failed = ev->sta_info.tx.failed;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_FAILED);
+ }
+ if (valid & NRF70_STA_INFO_EXPECTED_THROUGHPUT) {
+ sinfo->expected_throughput = ev->sta_info.expected_throughput;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_EXPECTED_THROUGHPUT);
+ }
+ if (valid & NRF70_STA_INFO_BEACON_LOSS_COUNT) {
+ sinfo->beacon_loss_count = ev->sta_info.beacon_loss_count;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BEACON_LOSS);
+ }
+ if (valid & NRF70_STA_INFO_T_OFFSET) {
+ sinfo->t_offset = ev->sta_info.t_offset;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_T_OFFSET);
+ }
+ if (valid & NRF70_STA_INFO_RX_DROPPED_MISC) {
+ sinfo->rx_dropped_misc = ev->sta_info.rx_dropped_misc;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_DROP_MISC);
+ }
+ if (valid & NRF70_STA_INFO_RX_BEACON) {
+ sinfo->rx_beacon = ev->sta_info.rx_beacon;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BEACON_RX);
+ }
+ if (valid & NRF70_STA_INFO_RX_BEACON_SIGNAL_AVG) {
+ sinfo->rx_beacon_signal_avg = ev->sta_info.rx_beacon_signal_avg;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BEACON_SIGNAL_AVG);
+ }
+ if (valid & NRF70_STA_INFO_BSS_PARAMS) {
+ sinfo->bss_param.flags = ev->sta_info.bss_param.flags;
+ sinfo->bss_param.dtim_period =
+ ev->sta_info.bss_param.dtim_period;
+ sinfo->bss_param.beacon_interval =
+ ev->sta_info.bss_param.beacon_interval;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BSS_PARAM);
+ }
+
+ complete(&priv->station_info_available);
+}
+
+static int nrf70_handle_get_channel(struct spi_mem *mem,
+ struct nrf70_event_get_chan *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_vif *vif = nrf70_get_vif(priv, ev->header.idx.wdev_id);
+
+ if (IS_ERR(vif))
+ return PTR_ERR(vif);
+
+ memset(&vif->chandef, 0, sizeof(vif->chandef));
+ vif->chandef.chan = ieee80211_get_channel(priv->wiphy,
+ ev->chan.center_freq);
+ vif->chandef.width = ev->width;
+ vif->chandef.center_freq1 = ev->center_freq1;
+ vif->chandef.center_freq2 = ev->center_freq2;
+
+ complete(&vif->chan_updated);
+
+ return 0;
+}
+
+static int nrf70_change_bss(struct wiphy *wiphy, struct net_device *ndev,
+ struct bss_parameters *params)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_set_bss *cmd;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_SET_BSS,
+ sizeof(*cmd), NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_set_bss *)msg->data;
+ cmd->info.ht_opmode = params->ht_opmode;
+ cmd->info.cts = params->use_cts_prot;
+ cmd->info.preamble = params->use_short_preamble;
+ cmd->info.slot = params->use_short_slot_time;
+ cmd->info.ap_isolate = params->ap_isolate;
+ cmd->info.num_basic_rates = params->basic_rates_len;
+ memcpy(cmd->info.basic_rates, params->basic_rates,
+ cmd->info.num_basic_rates);
+
+ if (in_range(params->p2p_ctwindow, 1, 126)) {
+ cmd->info.p2p_go_ctwindow = params->p2p_ctwindow;
+ cmd->info.p2p_opp_ps = params->p2p_opp_ps;
+ cmd->valid_fields = NRF70_SET_BSS_P2P_CTWINDOW |
+ NRF70_SET_BSS_P2P_OPPPS;
+ }
+
+ cmd->valid_fields |= NRF70_SET_BSS_CTS | NRF70_SET_BSS_PREAMBLE |
+ NRF70_SET_BSS_SLOT | NRF70_SET_BSS_HT_OPMODE |
+ NRF70_SET_BSS_AP_ISOLATE;
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static void nrf70_handle_event_get_reg(struct spi_mem *mem,
+ struct nrf70_event_get_reg *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+
+ memcpy(priv->regdom, ev->alpha2, sizeof(ev->alpha2));
+ complete(&priv->regdom_updated);
+}
+
+static void nrf70_handle_event_reg_change(struct spi_mem *mem,
+ struct nrf70_event_reg_change *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+
+ memcpy(priv->regdom, ev->alpha2, sizeof(ev->alpha2));
+ complete(&priv->regdom_updated);
+}
+
+static void nrf70_handle_rx_unprot_mlme_mgmt(struct spi_mem *mem,
+ struct nrf70_event_mlme *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_vif *vif = nrf70_get_vif(priv, ev->header.idx.wdev_id);
+
+ if (IS_ERR(vif))
+ return;
+
+ cfg80211_rx_unprot_mlme_mgmt(vif->ndev, ev->frame.data, ev->frame.len);
+}
+
+static void nrf70_handle_iface_update(struct spi_mem *mem,
+ struct nrf70_event_iface_update *ev)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_vif *vif = nrf70_get_vif(priv, ev->header.idx.wdev_id);
+
+ if (IS_ERR(vif))
+ return;
+
+ if (!ev->status)
+ complete(&vif->iface_updated);
+}
+
+static int nrf70_dequeue_umac_event(struct spi_mem *mem, void *data)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct device *dev = &mem->spi->dev;
+ struct nrf70_umac_header *header = data;
+ struct nrf70_vif *vif = nrf70_get_vif(priv, header->idx.wdev_id);
+ struct cfg80211_scan_info scan_info = { .aborted = true };
+
+ if (IS_ERR(vif))
+ return PTR_ERR(vif);
+
+ switch (header->id) {
+ case NRF70_UMAC_EVENT_TRIGGER_SCAN_START:
+ break;
+ case NRF70_UMAC_EVENT_SCAN_ABORTED:
+ if (vif->scan_req) {
+ cfg80211_scan_done(vif->scan_req, &scan_info);
+ vif->scan_req = NULL;
+ }
+
+ WRITE_ONCE(priv->scan_in_progress, false);
+ break;
+ case NRF70_UMAC_EVENT_SCAN_DONE:
+ if (!((struct nrf70_event_scan_done *)data)->status) {
+ nrf70_get_scan_results_command(mem, vif->iface);
+ } else if (vif->scan_req) {
+ cfg80211_scan_done(vif->scan_req, &scan_info);
+ vif->scan_req = NULL;
+ }
+
+ WRITE_ONCE(priv->scan_in_progress, false);
+ break;
+ case NRF70_UMAC_EVENT_AUTHENTICATE:
+ nrf70_handle_auth(mem, data);
+ break;
+ case NRF70_UMAC_EVENT_ASSOCIATE:
+ nrf70_handle_assoc(mem, data);
+ break;
+ case NRF70_UMAC_EVENT_CONNECT:
+ /* Nothing to be done. */
+ break;
+ case NRF70_UMAC_EVENT_DEAUTHENTICATE:
+ fallthrough;
+ case NRF70_UMAC_EVENT_DISASSOCIATE:
+ nrf70_handle_tx_mlme_mgmt(mem, data);
+ break;
+ case NRF70_UMAC_EVENT_DISCONNECT:
+ /* Nothing to be done. */
+ break;
+ case NRF70_UMAC_EVENT_FRAME:
+ nrf70_handle_rx_mgmt(mem, data);
+ break;
+ case NRF70_UMAC_EVENT_COOKIE_RESP:
+ nrf70_handle_cookie_resp(mem, data);
+ break;
+ case NRF70_UMAC_EVENT_FRAME_TX_STATUS:
+ nrf70_handle_frame_tx_status(mem, data);
+ break;
+ case NRF70_UMAC_EVENT_NEW_STATION:
+ nrf70_handle_station(mem, data, true);
+ break;
+ case NRF70_UMAC_EVENT_DEL_STATION:
+ nrf70_handle_station(mem, data, false);
+ break;
+ case NRF70_UMAC_EVENT_GET_STATION:
+ nrf70_handle_get_station(mem, data);
+ break;
+ case NRF70_UMAC_EVENT_GET_CHANNEL:
+ nrf70_handle_get_channel(mem, data);
+ break;
+ case NRF70_UMAC_EVENT_IFFLAGS_STATUS:
+ fallthrough;
+ case NRF70_UMAC_EVENT_SET_INTERFACE:
+ nrf70_handle_iface_update(mem, data);
+ break;
+ case NRF70_UMAC_EVENT_UNPROT_DEAUTHENTICATE:
+ fallthrough;
+ case NRF70_UMAC_EVENT_UNPROT_DISASSOCIATE:
+ nrf70_handle_rx_unprot_mlme_mgmt(mem, data);
+ break;
+ case NRF70_UMAC_EVENT_NEW_INTERFACE:
+ break;
+ case NRF70_UMAC_EVENT_GET_REG:
+ nrf70_handle_event_get_reg(mem, data);
+ break;
+ case NRF70_UMAC_EVENT_BEACON_HINT:
+ break;
+ case NRF70_UMAC_EVENT_REG_CHANGE:
+ nrf70_handle_event_reg_change(mem, data);
+ break;
+ case NRF70_UMAC_EVENT_SCAN_DISPLAY_RESULT:
+ nrf70_handle_scan_display_results(mem, data);
+ break;
+ case NRF70_UMAC_EVENT_CMD_STATUS:
+ nrf70_handle_cmd_status(mem, data);
+ break;
+ default:
+ dev_dbg(dev, "Unsupported umac event type: %d\n",
+ header->id);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void nrf70_event_worker(struct work_struct *work)
+{
+ struct nrf70_priv *priv = container_of(work, struct nrf70_priv,
+ event_work);
+ struct spi_mem *mem = priv->mem;
+ struct device *dev = &mem->spi->dev;
+ u32 addr, eq = priv->queue[NRF70_EVENT_AVL_QUEUE].eq;
+ struct nrf70_msg *msg;
+ int len, ret, i;
+
+ msg = kzalloc(NRF70_EVENT_POOL_MAX_SZ, GFP_KERNEL);
+ if (unlikely(!msg)) {
+ dev_err(dev, "Unable to allocate message buffer\n");
+ return;
+ }
+
+ while (!nrf70_dequeue(mem, NRF70_EVENT_BUSY_QUEUE, &addr)) {
+ len = nrf70_readl(mem, addr);
+
+ if (len < sizeof(*msg)) {
+ dev_dbg(dev, "Event length %d too small\n", len);
+ continue;
+ }
+ nrf70_readv(mem, addr, msg, min(len, NRF70_EVENT_POOL_MAX_SZ));
+
+ /* Put on empty queue. */
+ if (msg->resubmit)
+ nrf70_writel(mem, eq, addr);
+
+ if (len > NRF70_EVENT_POOL_MAX_SZ) {
+ dev_dbg(dev, "Fragmented event! Size %d > %d\n",
+ len, NRF70_EVENT_POOL_MAX_SZ);
+ continue;
+ }
+
+ switch (msg->type) {
+ case NRF70_MSG_SYSTEM:
+ ret = nrf70_dequeue_sys_event(mem, msg->data);
+ break;
+ case NRF70_MSG_DATA:
+ ret = nrf70_dequeue_data_event(mem, msg->data);
+ break;
+ case NRF70_MSG_UMAC:
+ ret = nrf70_dequeue_umac_event(mem, msg->data);
+ break;
+ default:
+ dev_dbg(dev, "Unknown message type\n");
+ ret = 1;
+ break;
+ }
+
+ if (ret && ret != -EINVAL) {
+ for (i = 0; i < len; i += 4) {
+ dev_dbg(dev, "[%d] = %08x\n",
+ i, *((u32 *)msg + i / 4));
+ }
+ }
+ }
+
+ kfree(msg);
+}
+
+static int nrf70_mac_init(struct spi_mem *mem)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct device *dev = &mem->spi->dev;
+ int val, i, idx, ret;
+ size_t sz;
+ u32 *tmpbuf;
+
+ val = nrf70_readl(mem, NRF70_MCU_UMAC_VERSION);
+ dev_info(dev, "UMAC version: %d.%d.%d.%d\n",
+ NRF70_MCU_UMAC_VERSION_VER(val),
+ NRF70_MCU_UMAC_VERSION_MAJOR(val),
+ NRF70_MCU_UMAC_VERSION_MINOR(val),
+ NRF70_MCU_UMAC_VERSION_EXTRA(val));
+ dev_info(dev, "Raw mode support: %s\n",
+ priv->has_raw_mode ? "yes" : "no");
+
+ sz = sizeof(priv->queue);
+ tmpbuf = kzalloc(sz, GFP_KERNEL);
+ if (!tmpbuf)
+ return -ENOMEM;
+
+ nrf70_readv(mem, NRF70_MCU_UMAC_HPQ, tmpbuf, sz);
+ for (i = 0; i < NRF70_QUEUE_MAX; i++) {
+ idx = i * 2;
+ priv->queue[i].eq = tmpbuf[idx];
+ priv->queue[i].dq = tmpbuf[idx + 1];
+ }
+ kfree(tmpbuf);
+
+ priv->num_cmds = NRF70_RPU_CMD_START_MAGIC;
+
+ priv->rx_cmd_base = nrf70_readl(mem, NRF70_RX_CMD_BASE);
+ priv->tx_cmd_base = NRF70_TX_CMD_BASE;
+
+ val = nrf70_readl(mem, NRF70_SBUS_UCCP_CORE_INT_ENAB);
+ val |= NRF70_UCCP_MTX2_INT_IRQ_ENAB;
+ nrf70_writel(mem, NRF70_SBUS_UCCP_CORE_INT_ENAB, val);
+ nrf70_writel(mem, NRF70_SBUS_UCCP_CORE_MTX2_INT_ENABLE,
+ NRF70_UCCP_MTX2_INT_EN);
+
+ tmpbuf = kzalloc(sizeof(priv->hwaddr), GFP_KERNEL);
+ if (!tmpbuf)
+ return -ENOMEM;
+
+ nrf70_readv(mem, NRF70_OTP_HWADDR, tmpbuf, sizeof(priv->hwaddr));
+ memcpy(priv->hwaddr, tmpbuf, sizeof(priv->hwaddr));
+ kfree(tmpbuf);
+ val = nrf70_readl(mem, NRF70_OTP_INFO_FLAGS);
+ for (i = 0; i < NRF70_VIFS_MAX; i++) {
+ if (!(val & NRF70_OTP_INFO_FLAGS_HWADDR(i)) &&
+ !is_zero_ether_addr(priv->hwaddr[i]))
+ continue;
+
+ dev_warn(dev, "OTP hwaddr %d invalid, using a random address\n",
+ i);
+ eth_random_addr(priv->hwaddr[i]);
+ }
+
+ ret = nrf70_init_rx_command(mem);
+ if (ret)
+ goto out;
+
+ ret = nrf70_init_command(mem);
+
+out:
+ return ret;
+}
+
+static irqreturn_t nrf70_irq(int irq, void *data)
+{
+ struct spi_mem *mem = data;
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ int val;
+
+ /* Rearm watchdog. */
+ val = nrf70_readl(mem, NRF70_SBUS_MIPS_MCU_UCCP_INT_STATUS);
+ if (val & NRF70_SBUS_MIPS_MCU_WATCHDOG_INT) {
+ nrf70_writel(mem, NRF70_SBUS_MIPS_MCU_TIMER,
+ NRF70_SBUS_MIPS_MCU_TIMER_RESET);
+ nrf70_writel(mem, NRF70_SBUS_MIPS_MCU_UCCP_INT_CLEAR,
+ NRF70_SBUS_MIPS_MCU_WATCHDOG_INT);
+ }
+
+ /* Check for pending events regardless of the IRQ source. */
+ schedule_work(&priv->event_work);
+
+ nrf70_writel(mem, NRF70_SBUS_UCCP_CORE_HOST2_TO_MTX2_ACK,
+ NRF70_UCCP_MTX2_INT_EN);
+
+ return IRQ_HANDLED;
+}
+
+static int nrf70_set_monitor_channel(struct wiphy *wiphy,
+ struct net_device *ndev,
+ struct cfg80211_chan_def *def)
+{
+ struct nrf70_ndev_priv *npriv = netdev_priv(ndev);
+ struct nrf70_priv *priv = npriv->priv;
+ struct nrf70_vif *vif = npriv->vif;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_set_channel *cmd;
+ u32 freq = def->chan->center_freq;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_SYSTEM, NRF70_CMD_CHANNEL,
+ sizeof(*cmd), -1);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_set_channel *)msg->data;
+ cmd->if_idx = NRF70_NDEV_TO_IFACE(ndev);
+ cmd->chan.primary_num = ieee80211_frequency_to_channel(freq);
+
+ reinit_completion(&vif->chan_updated);
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ if (ret)
+ return ret;
+
+ vif->chandef = *def;
+
+ return wait_for_completion_timeout(&vif->chan_updated,
+ msecs_to_jiffies(1000)) ?
+ 0 : -ETIMEDOUT;
+}
+
+static int nrf70_open(struct net_device *dev)
+{
+ struct nrf70_ndev_priv *npriv = netdev_priv(dev);
+ struct nrf70_priv *priv = npriv->priv;
+ struct nrf70_vif *vif = npriv->vif;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_chg_vif_state *cmd;
+ int ret, iface = vif->iface;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_SET_IFFLAGS,
+ sizeof(*cmd), iface);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_chg_vif_state *)msg->data;
+ cmd->info.state = 1;
+ cmd->info.if_idx = iface;
+
+ reinit_completion(&vif->iface_updated);
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ if (ret)
+ return ret;
+
+ ret = wait_for_completion_timeout(&vif->iface_updated,
+ msecs_to_jiffies(1000)) ?
+ 0 : -ETIMEDOUT;
+ if (ret || vif->wdev.iftype != NL80211_IFTYPE_MONITOR)
+ return ret;
+
+ return nrf70_set_monitor_channel(priv->wiphy, dev, &vif->chandef);
+}
+
+static int nrf70_close(struct net_device *dev)
+{
+ struct nrf70_ndev_priv *npriv = netdev_priv(dev);
+ struct nrf70_priv *priv = npriv->priv;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_chg_vif_state *cmd;
+ int ret, iface = npriv->vif->iface;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_SET_IFFLAGS,
+ sizeof(*cmd), iface);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_chg_vif_state *)msg->data;
+ cmd->info.state = 0;
+ cmd->info.if_idx = iface;
+
+ reinit_completion(&npriv->vif->iface_updated);
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ if (ret)
+ return ret;
+
+ if (!wait_for_completion_timeout(&npriv->vif->iface_updated,
+ msecs_to_jiffies(1000)))
+ return -ETIMEDOUT;
+
+ nrf70_carrier_change(priv, iface, false);
+
+ return 0;
+}
+
+static netdev_tx_t nrf70_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct nrf70_ndev_priv *npriv = netdev_priv(ndev);
+ struct nrf70_priv *priv = npriv->priv;
+ struct cfg80211_qos_map *qos_map = READ_ONCE(npriv->vif->qos_map);
+ struct nrf70_vif *vif = npriv->vif;
+ struct sk_buff_head *queue;
+ int i;
+
+ if (skb->priority == 0 || skb->priority > 7)
+ skb->priority = cfg80211_classify8021d(skb, qos_map);
+
+ guard(spinlock_irqsave)(&vif->sta_lock);
+ i = nrf70_get_sta_idx(vif, eth_hdr(skb)->h_dest);
+ queue = i < 0 || vif->sta[i].can_xmit ? &vif->tx_queue :
+ &vif->sta[i].pending;
+
+ skb_queue_tail(queue, skb);
+
+ if (skb_queue_len(queue) >= NRF70_TX_PENDING_MAX) {
+ if (queue == &vif->tx_queue) {
+ netif_stop_queue(ndev);
+ } else {
+ /* Toss the oldest pending skb. */
+ consume_skb(skb_dequeue(queue));
+ u64_stats_update_begin(&vif->stats.syncp);
+ vif->stats.tx_dropped++;
+ u64_stats_update_end(&vif->stats.syncp);
+ }
+ }
+
+ if (queue == &vif->tx_queue)
+ wiphy_work_queue(priv->wiphy, &vif->xmit_work);
+
+ return NETDEV_TX_OK;
+}
+
+static int nrf70_set_hwaddr(struct net_device *ndev, void *addr)
+{
+ struct nrf70_ndev_priv *npriv = netdev_priv(ndev);
+ struct nrf70_priv *priv = npriv->priv;
+ struct sockaddr *sa = addr;
+ int ret, iface = NRF70_NDEV_TO_IFACE(ndev);
+
+ ret = eth_prepare_mac_addr_change(ndev, addr);
+ if (ret)
+ return ret;
+
+ ret = nrf70_hwaddr_change_command(priv->mem, sa->sa_data, iface);
+ if (ret)
+ return ret;
+
+ eth_hw_addr_set(ndev, sa->sa_data);
+
+ return 0;
+}
+
+static void nrf70_get_stats64(struct net_device *ndev,
+ struct rtnl_link_stats64 *stats)
+{
+ struct nrf70_ndev_priv *npriv = netdev_priv(ndev);
+ struct nrf70_vif *vif = npriv->vif;
+ unsigned int start;
+
+ /*
+ * nRF70 hardware keeps track of MAC statistics, however they are not
+ * grouped based on individual VIFs, rendering them useless for
+ * get_stats64. Instead, return statistics collected by the driver.
+ */
+ do {
+ start = u64_stats_fetch_begin(&vif->stats.syncp);
+ stats->tx_packets = vif->stats.tx_packets;
+ stats->tx_bytes = vif->stats.tx_bytes;
+ stats->rx_packets = vif->stats.rx_packets;
+ stats->rx_bytes = vif->stats.rx_bytes;
+ stats->tx_dropped = vif->stats.tx_dropped;
+ } while (u64_stats_fetch_retry(&vif->stats.syncp, start));
+}
+
+static const struct net_device_ops nrf70_netdev_ops = {
+ .ndo_open = nrf70_open,
+ .ndo_stop = nrf70_close,
+ .ndo_start_xmit = nrf70_xmit,
+ .ndo_set_mac_address = nrf70_set_hwaddr,
+ .ndo_get_stats64 = nrf70_get_stats64,
+};
+
+static int nrf70_set_fmac_mode(struct spi_mem *mem, struct nrf70_vif *vif,
+ enum nl80211_iftype type)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_raw_config_mode *cmd;
+ struct ieee80211_channel *ch;
+ int mode, ret;
+
+ /* CMD_RAW_CONFIG_MODE is not required if raw mode is not present. */
+ if (!priv->has_raw_mode)
+ return 0;
+
+ switch (type) {
+ case NL80211_IFTYPE_STATION:
+ mode = NRF70_OP_MODE_STA;
+ break;
+ case NL80211_IFTYPE_AP:
+ mode = NRF70_OP_MODE_AP;
+ break;
+ case NL80211_IFTYPE_MONITOR:
+ mode = NRF70_OP_MODE_MONITOR;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ msg = nrf70_create_msg(NRF70_MSG_SYSTEM, NRF70_CMD_RAW_CONFIG_MODE,
+ sizeof(*cmd), -1);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_raw_config_mode *)msg->data;
+ cmd->if_idx = vif->iface;
+ cmd->mode = mode;
+
+ reinit_completion(&vif->iface_updated);
+ ret = nrf70_enqueue_message(mem, msg);
+ kfree(msg);
+
+ if (ret)
+ return ret;
+
+ ret = wait_for_completion_timeout(&vif->iface_updated,
+ msecs_to_jiffies(1000)) ?
+ 0 : -ETIMEDOUT;
+ if (ret || type != NL80211_IFTYPE_MONITOR)
+ return ret;
+
+ ch = priv->wiphy->bands[NL80211_BAND_2GHZ]->channels;
+ cfg80211_chandef_create(&vif->chandef, ch, NL80211_CHAN_NO_HT);
+
+ return nrf70_set_monitor_channel(priv->wiphy, vif->ndev,
+ &vif->chandef);
+}
+
+static int nrf70_add_vif_command(struct spi_mem *mem, struct nrf70_vif *vif)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_add_vif *cmd;
+ int ret;
+
+ if (!vif->iface)
+ return -EOPNOTSUPP;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_NEW_INTERFACE,
+ sizeof(*cmd), vif->iface);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_add_vif *)msg->data;
+ cmd->valid_fields = NRF70_ADD_VIF_HWADDR | NRF70_ADD_VIF_IFTYPE |
+ NRF70_ADD_VIF_IFNAME;
+ cmd->info.iftype = vif->wdev.iftype;
+ strscpy(cmd->info.ifacename, vif->ndev->name, IFNAMSIZ);
+ ether_addr_copy(cmd->info.hwaddr, priv->hwaddr[vif->iface]);
+
+ ret = nrf70_enqueue_message(mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static struct nrf70_vif *nrf70_add_if(struct nrf70_priv *priv, const char *name,
+ unsigned char name_assign_type,
+ enum nl80211_iftype iftype,
+ struct vif_params *params, bool locked)
+{
+ struct device *dev = &priv->mem->spi->dev;
+ struct net_device *ndev;
+ struct nrf70_ndev_priv *npriv;
+ struct nrf70_vif *vif;
+ bool is_monitor = false;
+ struct ieee80211_channel *ch;
+ u8 *hwaddr;
+ int ret;
+
+ switch (iftype) {
+ case NL80211_IFTYPE_STATION:
+ fallthrough;
+ case NL80211_IFTYPE_AP:
+ break;
+ case NL80211_IFTYPE_MONITOR:
+ if (!priv->has_raw_mode)
+ return ERR_PTR(-EOPNOTSUPP);
+ is_monitor = true;
+ break;
+ default:
+ return ERR_PTR(-EOPNOTSUPP);
+ }
+
+ vif = kzalloc(sizeof(*vif), GFP_KERNEL);
+ if (!vif)
+ return ERR_PTR(-ENOMEM);
+
+ vif->iface = ffs(priv->vif_bitmap) - 1;
+ if (vif->iface < 0 || vif->iface >= NRF70_VIFS_MAX)
+ return ERR_PTR(-EINVAL);
+ clear_bit(vif->iface, &priv->vif_bitmap);
+
+ vif->wdev.wiphy = priv->wiphy;
+ vif->wdev.iftype = iftype;
+
+ ndev = alloc_netdev(sizeof(*npriv), name, name_assign_type,
+ ether_setup);
+ if (!ndev) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ vif->ndev = ndev;
+ ndev->needs_free_netdev = true;
+ npriv = netdev_priv(ndev);
+ npriv->priv = priv;
+ npriv->vif = vif;
+
+ ndev->type = is_monitor ? ARPHRD_IEEE80211_RADIOTAP : ARPHRD_ETHER;
+ ndev->ieee80211_ptr = &vif->wdev;
+ SET_NETDEV_DEV(ndev, wiphy_dev(priv->wiphy));
+ vif->wdev.netdev = ndev;
+
+ hwaddr = (!params || is_zero_ether_addr(params->macaddr)) ?
+ priv->hwaddr[vif->iface] :
+ params->macaddr;
+ eth_hw_addr_set(ndev, hwaddr);
+
+ ndev->netdev_ops = &nrf70_netdev_ops;
+
+ ret = locked ? cfg80211_register_netdevice(ndev) :
+ register_netdev(ndev);
+ if (ret) {
+ dev_err(dev, "Unable to register netdev: %d\n", ret);
+ goto err_ndev;
+ }
+
+ netif_carrier_off(vif->ndev);
+
+ /*
+ * The primary interface is already created by UMAC FW, and as such
+ * there is no need to send a create command.
+ */
+ if (!vif->iface) {
+ ret = nrf70_hwaddr_change_command(priv->mem, ndev->dev_addr,
+ vif->iface);
+ if (ret) {
+ dev_err(dev, "Unable to set netdev MAC address: %d\n",
+ ret);
+
+ goto err_ndev;
+ }
+ } else {
+ ret = nrf70_add_vif_command(priv->mem, vif);
+ if (ret)
+ goto err_ndev;
+ }
+
+ list_add_tail(&vif->list, &priv->vifs);
+ init_completion(&vif->iface_updated);
+ init_completion(&vif->chan_updated);
+ u64_stats_init(&vif->stats.syncp);
+
+ ch = priv->wiphy->bands[NL80211_BAND_2GHZ]->channels;
+ cfg80211_chandef_create(&vif->chandef, ch, NL80211_CHAN_NO_HT);
+
+ skb_queue_head_init(&vif->tx_queue);
+ vif->sta_bitmap = NRF70_PEERS_MASK;
+ spin_lock_init(&vif->sta_lock);
+ wiphy_work_init(&vif->xmit_work, nrf70_xmit_worker);
+
+ nrf70_open(ndev);
+ ret = nrf70_set_fmac_mode(priv->mem, vif, iftype);
+ nrf70_close(ndev);
+ if (ret) {
+ list_del(&vif->list);
+ goto err_ndev;
+ }
+
+ return vif;
+
+err_ndev:
+ if (ndev->reg_state == NETREG_REGISTERED) {
+ if (locked)
+ cfg80211_unregister_netdevice(ndev);
+ else
+ unregister_netdev(ndev);
+ }
+ free_netdev(ndev);
+err:
+ set_bit(vif->iface, &priv->vif_bitmap);
+ kfree(vif);
+
+ return ERR_PTR(ret);
+}
+
+static struct wireless_dev *nrf70_add_vif(struct wiphy *wiphy,
+ const char *name,
+ unsigned char name_assign_type,
+ enum nl80211_iftype type,
+ struct vif_params *params)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_vif *vif;
+
+ vif = nrf70_add_if(priv, name, name_assign_type, type, params, true);
+
+ return IS_ERR(vif) ? ERR_CAST(vif) : &vif->wdev;
+}
+
+static int nrf70_del_vif_command(struct spi_mem *mem, struct nrf70_vif *vif)
+{
+ struct nrf70_msg *msg;
+ int ret;
+
+ if (!vif->iface)
+ return -EOPNOTSUPP;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_DEL_INTERFACE,
+ sizeof(struct nrf70_umac_header), vif->iface);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ ret = nrf70_enqueue_message(mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static int nrf70_del_if(struct nrf70_priv *priv, struct nrf70_vif *vif,
+ bool locked)
+{
+ int ret = 0;
+
+ netif_stop_queue(vif->ndev);
+ nrf70_drain_tx(priv, vif);
+
+ /*
+ * The primary interface is always present in UMAC FW, and as such we
+ * cannot send a delete command.
+ */
+ if (vif->iface)
+ ret = nrf70_del_vif_command(priv->mem, vif);
+
+ nrf70_carrier_change(priv, vif->iface, false);
+ set_bit(vif->iface, &priv->vif_bitmap);
+ complete(&vif->iface_updated);
+ complete(&vif->chan_updated);
+
+ if (vif->ndev->reg_state == NETREG_REGISTERED) {
+ if (locked)
+ cfg80211_unregister_netdevice(vif->ndev);
+ else
+ unregister_netdev(vif->ndev);
+ } else {
+ free_netdev(vif->ndev);
+ }
+
+ list_del(&vif->list);
+ kfree(vif);
+
+ return ret;
+}
+
+static int nrf70_del_vif(struct wiphy *wiphy, struct wireless_dev *wdev)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+
+ return nrf70_del_if(priv, container_of(wdev, struct nrf70_vif, wdev),
+ true);
+}
+
+static int nrf70_chg_vif(struct wiphy *wiphy, struct net_device *ndev,
+ enum nl80211_iftype type, struct vif_params *params)
+{
+ struct nrf70_ndev_priv *npriv = netdev_priv(ndev);
+ struct nrf70_priv *priv = npriv->priv;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_chg_vif_attr *cmd;
+ int ret;
+
+ nrf70_drain_tx(priv, npriv->vif);
+
+ ret = nrf70_set_fmac_mode(priv->mem, npriv->vif, type);
+ if (ret)
+ return ret;
+
+ /* CMD_SET_INTERFACE doesn't support monitor mode, so exit early. */
+ if (type == NL80211_IFTYPE_MONITOR) {
+ ndev->type = ARPHRD_IEEE80211_RADIOTAP;
+ ndev->ieee80211_ptr->iftype = type;
+
+ return 0;
+ }
+
+ nrf70_close(ndev);
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_SET_INTERFACE,
+ sizeof(*cmd), NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_chg_vif_attr *)msg->data;
+ cmd->valid_fields = NRF70_CHG_VIF_IFTYPE | NRF70_CHG_VIF_USE_4ADDR;
+ cmd->info.iftype = type;
+ cmd->info.use_4addr = params->use_4addr;
+
+ reinit_completion(&npriv->vif->iface_updated);
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ if (ret)
+ return ret;
+
+ if (!wait_for_completion_timeout(&npriv->vif->iface_updated,
+ msecs_to_jiffies(1000)))
+ return -ETIMEDOUT;
+
+ ndev->type = ARPHRD_ETHER;
+ ndev->ieee80211_ptr->iftype = type;
+
+ nrf70_open(ndev);
+
+ return ret;
+}
+
+static int nrf70_add_key(struct wiphy *wiphy, struct net_device *ndev,
+ int link_id, u8 key_index, bool pairwise,
+ const u8 *hwaddr, struct key_params *params)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_key *cmd;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_NEW_KEY,
+ sizeof(*cmd), NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_key *)msg->data;
+ cmd->info.key.len = params->key_len;
+ if (cmd->info.key.len) {
+ memcpy(cmd->info.key.data, params->key, cmd->info.key.len);
+ cmd->info.key_idx = key_index;
+ cmd->info.valid_fields |= NRF70_KEY_INFO_KEY |
+ NRF70_KEY_INFO_KEY_IDX;
+ }
+
+ cmd->info.seq.len = params->seq_len;
+ if (cmd->info.seq.len) {
+ memcpy(cmd->info.seq.data, params->seq, cmd->info.seq.len);
+ cmd->info.valid_fields |= NRF70_KEY_INFO_SEQ;
+ }
+
+ cmd->info.valid_fields |= NRF70_KEY_INFO_CIPHER_SUITE |
+ NRF70_KEY_INFO_KEY_TYPE;
+
+ cmd->info.cipher_suite = params->cipher;
+
+ cmd->info.key.type = pairwise ? NL80211_KEYTYPE_PAIRWISE :
+ NL80211_KEYTYPE_GROUP;
+
+ if (hwaddr) {
+ ether_addr_copy(cmd->hwaddr, hwaddr);
+ cmd->valid_fields |= NRF70_KEY_HWADDR;
+ }
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static int nrf70_del_key(struct wiphy *wiphy, struct net_device *ndev,
+ int link_id, u8 key_index, bool pairwise,
+ const u8 *hwaddr)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_key *cmd;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_DEL_KEY,
+ sizeof(*cmd), NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_key *)msg->data;
+ cmd->info.key_idx = key_index;
+ cmd->info.valid_fields |= NRF70_KEY_INFO_KEY_TYPE |
+ NRF70_KEY_INFO_KEY_IDX;
+ cmd->info.key.type = pairwise ? NL80211_KEYTYPE_PAIRWISE :
+ NL80211_KEYTYPE_GROUP;
+
+ if (hwaddr) {
+ ether_addr_copy(cmd->hwaddr, hwaddr);
+ cmd->valid_fields |= NRF70_KEY_HWADDR;
+ }
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static int nrf70_set_default_key(struct wiphy *wiphy, struct net_device *ndev,
+ int link_id, u8 key_index, bool unicast,
+ bool multicast)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_set_key *cmd;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_SET_KEY,
+ sizeof(*cmd), NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_set_key *)msg->data;
+ cmd->info.wifi_flags = NRF70_KEY_INFO_FLAG_DEFAULT;
+ if (unicast)
+ cmd->info.wifi_flags |= NRF70_KEY_INFO_FLAG_DEFAULT_UNICAST;
+ if (multicast)
+ cmd->info.wifi_flags |= NRF70_KEY_INFO_FLAG_DEFAULT_MULTICAST;
+
+ cmd->info.key_idx = key_index;
+ cmd->info.valid_fields = NRF70_KEY_INFO_KEY_IDX;
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static int nrf70_set_default_mgmt_key(struct wiphy *wiphy,
+ struct net_device *ndev, int link_id,
+ u8 key_index)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_set_key *cmd;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_SET_KEY,
+ sizeof(*cmd), NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_set_key *)msg->data;
+ cmd->info.wifi_flags = NRF70_KEY_INFO_FLAG_DEFAULT_MGMT;
+
+ cmd->info.key_idx = key_index;
+ cmd->info.valid_fields = NRF70_KEY_INFO_KEY_IDX;
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static int nrf70_set_wiphy_command(struct spi_mem *mem, int iface,
+ const struct nrf70_wiphy_info *info)
+{
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_set_wiphy *cmd;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_SET_WIPHY,
+ sizeof(*cmd), iface);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_set_wiphy *)msg->data;
+ cmd->info = *info;
+
+ if (cmd->info.freq_params.valid_fields)
+ cmd->valid_fields |= NRF70_SET_WIPHY_FREQ;
+ if (cmd->info.rts_threshold)
+ cmd->valid_fields |= NRF70_SET_WIPHY_RTS_THRESHOLD;
+ if (cmd->info.frag_threshold)
+ cmd->valid_fields |= NRF70_SET_WIPHY_FRAG_THRESHOLD;
+ if (cmd->info.retry_short)
+ cmd->valid_fields |= NRF70_SET_WIPHY_RETRY_SHORT;
+ if (cmd->info.retry_long)
+ cmd->valid_fields |= NRF70_SET_WIPHY_RETRY_LONG;
+ if (cmd->info.coverage_class)
+ cmd->valid_fields |= NRF70_SET_WIPHY_COVERAGE_CLASS;
+
+ ret = nrf70_enqueue_message(mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static int nrf70_get_auth_type(enum nl80211_auth_type type)
+{
+ if (type == NL80211_AUTHTYPE_AUTOMATIC)
+ return NRF70_AUTHTYPE_AUTOMATIC;
+
+ /* nRF70 doesn't support FILS auth algs. */
+ if (type > NL80211_AUTHTYPE_SAE)
+ return -EOPNOTSUPP;
+
+ /* Otherwise nl80211_auth_type matches 1:1 with nRF70 auth types. */
+ return type;
+}
+
+static void nrf70_set_crypto_info(struct nrf70_connect_info *info,
+ struct cfg80211_crypto_settings *crypto)
+{
+ size_t sz;
+
+ info->valid_fields |= NRF70_CONNECT_WPA_VERSIONS;
+ info->wpa_versions = crypto->wpa_versions;
+ info->cipher_suite_group = crypto->cipher_group;
+
+ info->control_port_no_encrypt = crypto->control_port_no_encrypt;
+ if (info->control_port_no_encrypt)
+ info->valid_fields |= NRF70_CONNECT_CONTROL_PORT_NO_ENCRYPT;
+
+ info->control_port_ethertype =
+ be16_to_cpu(crypto->control_port_ethertype);
+ if (info->control_port_ethertype)
+ info->valid_fields |= NRF70_CONNECT_CONTROL_PORT_ETHER_TYPE;
+
+ if (crypto->n_ciphers_pairwise) {
+ sz = sizeof(crypto->ciphers_pairwise[0]);
+ sz *= crypto->n_ciphers_pairwise;
+ memcpy(info->cipher_suites_pairwise, crypto->ciphers_pairwise,
+ sz);
+ info->num_cipher_suites_pairwise = sz;
+ info->valid_fields |= NRF70_CONNECT_CIPHER_PAIRWISE;
+ }
+
+ if (crypto->n_akm_suites) {
+ sz = sizeof(crypto->akm_suites[0]) * crypto->n_akm_suites;
+ memcpy(info->akm_suites, crypto->akm_suites, sz);
+ info->num_akm_suites = sz;
+ info->valid_fields |= NRF70_CONNECT_AKM_SUITES;
+ }
+
+ info->control_port = crypto->control_port;
+ info->control_port_no_encrypt = crypto->control_port_no_encrypt;
+}
+
+static int nrf70_start_ap(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_ap_settings *cfg)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_start_ap *cmd;
+ struct nrf70_freq_params *freq_params;
+ struct nrf70_connect_info *con_info;
+ struct nrf70_wiphy_info wiphy_info = {};
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_START_AP,
+ sizeof(*cmd), NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_start_ap *)msg->data;
+ cmd->valid_fields = NRF70_START_AP_BEACON_INTERVAL |
+ NRF70_START_AP_VERSIONS |
+ NRF70_START_AP_CIPHER_SUITE_GROUP;
+
+ cmd->info.beacon_interval = cfg->beacon_interval;
+ cmd->info.dtim_period = cfg->dtim_period;
+ cmd->info.auth_type = nrf70_get_auth_type(cfg->auth_type);
+ if (cmd->info.auth_type < 0) {
+ ret = cmd->info.auth_type;
+ goto out;
+ }
+
+ cmd->info.flags = cfg->privacy ? NRF70_START_AP_FLAG_PRIVACY :
+ NRF70_START_AP_FLAG_NO_ENCRYPT;
+
+ freq_params = &cmd->info.freq_params;
+ freq_params->frequency = cfg->chandef.chan->center_freq;
+ freq_params->channel_width = cfg->chandef.width;
+ freq_params->center_freq1 = cfg->chandef.center_freq1;
+ freq_params->center_freq2 = cfg->chandef.center_freq2;
+
+ freq_params->valid_fields = NRF70_FREQ_PARAMS_FREQ |
+ NRF70_FREQ_PARAMS_CHAN_WIDTH |
+ NRF70_FREQ_PARAMS_CENTER_FREQ1 |
+ NRF70_FREQ_PARAMS_CENTER_FREQ2 |
+ NRF70_FREQ_PARAMS_CHAN_TYPE;
+
+ freq_params->channel_type = cfg80211_get_chandef_type(&cfg->chandef);
+
+ if (cfg->ssid_len) {
+ memcpy(cmd->info.ssid.ssid, cfg->ssid, cfg->ssid_len);
+ cmd->info.ssid.len = cfg->ssid_len;
+ }
+ cmd->info.hidden_ssid = cfg->hidden_ssid;
+ cmd->info.inactivity_timeout = cfg->inactivity_timeout;
+
+ con_info = &cmd->info.connect_info;
+ nrf70_set_crypto_info(con_info, &cfg->crypto);
+
+ cmd->info.beacon_data.head_len = cfg->beacon.head_len;
+ if (cfg->beacon.head_len)
+ memcpy(cmd->info.beacon_data.head, cfg->beacon.head,
+ cfg->beacon.head_len);
+
+ cmd->info.beacon_data.tail_len = cfg->beacon.tail_len;
+ if (cfg->beacon.tail_len)
+ memcpy(cmd->info.beacon_data.tail, cfg->beacon.tail,
+ cfg->beacon.tail_len);
+
+ cmd->info.beacon_data.probe_resp_len = cfg->beacon.probe_resp_len;
+ if (cfg->beacon.probe_resp_len)
+ memcpy(cmd->info.beacon_data.probe_resp, cfg->beacon.probe_resp,
+ cfg->beacon.probe_resp_len);
+
+ if (cfg->p2p_ctwindow > 0 && cfg->p2p_ctwindow < 127) {
+ cmd->info.p2p_go_ctwindow = cfg->p2p_ctwindow;
+ cmd->info.p2p_opp_ps = cfg->p2p_opp_ps;
+ cmd->valid_fields |= NRF70_START_AP_FLAG_P2P_CTWINDOW |
+ NRF70_START_AP_FLAG_P2P_OPPPS;
+ }
+
+ wiphy_info.freq_params = *freq_params;
+ ret = nrf70_set_wiphy_command(priv->mem, NRF70_NDEV_TO_IFACE(ndev),
+ &wiphy_info);
+ if (ret)
+ goto out;
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+
+out:
+ kfree(msg);
+
+ return ret;
+}
+
+static int nrf70_change_beacon(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_ap_update *info)
+{
+ struct nrf70_ndev_priv *npriv = netdev_priv(ndev);
+ struct nrf70_priv *priv = npriv->priv;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_set_beacon *cmd;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_SET_BEACON,
+ sizeof(*cmd), NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_set_beacon *)msg->data;
+ cmd->beacon_data.head_len = info->beacon.head_len;
+ memcpy(cmd->beacon_data.head, info->beacon.head, info->beacon.head_len);
+
+ cmd->beacon_data.tail_len = info->beacon.tail_len;
+ memcpy(cmd->beacon_data.tail, info->beacon.tail, info->beacon.tail_len);
+
+ cmd->beacon_data.probe_resp_len = info->beacon.probe_resp_len;
+ memcpy(cmd->beacon_data.probe_resp, info->beacon.probe_resp,
+ info->beacon.probe_resp_len);
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static int nrf70_stop_ap(struct wiphy *wiphy, struct net_device *ndev,
+ unsigned int link_id)
+{
+ struct nrf70_ndev_priv *npriv = netdev_priv(ndev);
+ struct nrf70_priv *priv = npriv->priv;
+ struct nrf70_msg *msg;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_STOP_AP,
+ sizeof(struct nrf70_umac_header),
+ NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static int nrf70_scan(struct wiphy *wiphy, struct cfg80211_scan_request *req)
+{
+ struct wireless_dev *wdev = req->wdev;
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_vif *vif = container_of(wdev, struct nrf70_vif, wdev);
+ struct device *dev = &priv->mem->spi->dev;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_scan *cmd;
+ int duration_ms;
+ int ret, len, i;
+
+ if (wdev->iftype == NL80211_IFTYPE_AP)
+ return -EOPNOTSUPP;
+
+ if (req->n_channels > 64)
+ return -EINVAL;
+
+ if (READ_ONCE(priv->scan_in_progress))
+ return -EBUSY;
+
+ vif->scan_req = req;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_TRIGGER_SCAN,
+ sizeof(*cmd) + sizeof(int) * req->n_channels,
+ vif->iface);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_scan *)msg->data;
+ cmd->scan_info.reason = NRF70_SCAN_REASON_DISPLAY;
+ cmd->scan_info.num_scan_channels = req->n_channels;
+ cmd->scan_info.bands = NRF70_SCAN_BAND_ANY;
+
+ for (i = 0; i < req->n_channels; i++) {
+ cmd->scan_info.center_freq[i] = req->channels[i]->center_freq;
+
+ switch (req->channels[i]->band) {
+ case NL80211_BAND_2GHZ:
+ cmd->scan_info.bands |= NRF70_SCAN_BAND_2GHZ;
+ break;
+ case NL80211_BAND_5GHZ:
+ cmd->scan_info.bands |= NRF70_SCAN_BAND_5GHZ;
+ break;
+ default:
+ dev_warn(dev, "Unsupported chan %d band: %d\n",
+ i, req->channels[i]->band);
+ break;
+ }
+ }
+ cmd->scan_info.no_cck = req->no_cck;
+
+ if (req->ie && req->ie_len) {
+ memcpy(cmd->scan_info.ie.ie, req->ie, req->ie_len);
+ cmd->scan_info.ie.len = req->ie_len;
+ }
+
+ ether_addr_copy(cmd->scan_info.hwaddr, req->bssid);
+
+ /*
+ * If duration_ms is 0, UMAC will program dwell to 50ms for active scan,
+ * and to 150ms for passive scan.
+ */
+ duration_ms = ieee80211_tu_to_usec(req->duration) / USEC_PER_MSEC;
+ cmd->scan_info.passive_scan = !req->n_ssids;
+ if (cmd->scan_info.passive_scan)
+ cmd->scan_info.dwell_time_passive = duration_ms;
+ else
+ cmd->scan_info.dwell_time_active = duration_ms;
+
+ for (i = 0; i < req->n_ssids; i++) {
+ if (!req->ssids[i].ssid_len)
+ continue;
+
+ len = req->ssids[i].ssid_len;
+ if (len > 32) {
+ dev_err(dev, "SSID %d length %d too long\n", i, len);
+ ret = -ERANGE;
+ goto out;
+ }
+
+ if (cmd->scan_info.num_scan_ssids >= 2) {
+ dev_err(dev, "Maximum number of SSIDs reached\n");
+ ret = -ERANGE;
+ goto out;
+ }
+
+ memcpy(cmd->scan_info.scan_ssids[i].ssid, req->ssids[i].ssid,
+ len);
+ cmd->scan_info.scan_ssids[i].len = len;
+ cmd->scan_info.num_scan_ssids++;
+ }
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ WRITE_ONCE(priv->scan_in_progress, !ret);
+
+out:
+ kfree(msg);
+
+ return ret;
+}
+
+static void nrf70_abort_scan(struct wiphy *wiphy, struct wireless_dev *wdev)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_msg *msg;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_ABORT_SCAN,
+ sizeof(struct nrf70_umac_header),
+ NRF70_NDEV_TO_IFACE(wdev->netdev));
+ if (IS_ERR(msg))
+ return;
+
+ (void)nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+}
+
+static int nrf70_auth(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_auth_request *req)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_auth *cmd;
+ const u8 *ssid_ie;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_AUTHENTICATE,
+ sizeof(*cmd), NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_auth *)msg->data;
+ cmd->info.frequency = req->bss->channel->center_freq;
+ if (cmd->info.frequency)
+ cmd->valid_fields |= NRF70_AUTH_FREQ;
+
+ cmd->info.auth_type = nrf70_get_auth_type(req->auth_type);
+ if (cmd->info.auth_type < 0) {
+ ret = cmd->info.auth_type;
+ goto out;
+ }
+
+ memcpy(cmd->info.bssid, req->bss->bssid, ETH_ALEN);
+ memcpy(cmd->info.bss_ie.ie, req->bss->ies->data, req->bss->ies->len);
+ cmd->info.bss_ie.len = req->bss->ies->len;
+ cmd->info.scan_width = NL80211_BSS_CHAN_WIDTH_20;
+ cmd->info.signal = req->bss->signal;
+ cmd->info.capability = req->bss->capability;
+ cmd->info.beacon_interval = req->bss->beacon_interval;
+ cmd->info.tsf = req->bss->ies->tsf;
+ cmd->info.from_beacon = req->bss->ies->from_beacon;
+
+ ssid_ie = cfg80211_find_ie(WLAN_EID_SSID, req->bss->ies->data,
+ req->bss->ies->len);
+ cmd->info.ssid.len = ssid_ie[1];
+ if (cmd->info.ssid.len) {
+ if (cmd->info.ssid.len > IEEE80211_MAX_SSID_LEN)
+ goto out;
+ memcpy(cmd->info.ssid.ssid, ssid_ie + 2, cmd->info.ssid.len);
+ cmd->valid_fields |= NRF70_AUTH_SSID;
+ }
+
+ if (req->key_len) {
+ cmd->info.key_info.key_idx = req->key_idx;
+ memcpy(cmd->info.key_info.key.data, req->key, req->key_len);
+ cmd->info.key_info.key.len = req->key_len;
+ cmd->info.key_info.cipher_suite = req->key_len == 5 ?
+ WLAN_CIPHER_SUITE_WEP40 :
+ WLAN_CIPHER_SUITE_WEP104;
+ cmd->info.key_info.valid_fields = NRF70_KEY_INFO_KEY |
+ NRF70_KEY_INFO_KEY_IDX |
+ NRF70_KEY_INFO_CIPHER_SUITE;
+ cmd->valid_fields |= NRF70_AUTH_KEY_INFO;
+ }
+
+ if (req->auth_data_len) {
+ memcpy(cmd->info.sae.data, req->auth_data, req->auth_data_len);
+ cmd->info.sae.len = req->auth_data_len;
+ cmd->valid_fields |= NRF70_AUTH_SAE;
+ }
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+
+out:
+ kfree(msg);
+
+ return ret;
+}
+
+static int nrf70_assoc(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_assoc_request *req)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_vif *vif = nrf70_get_vif(priv, NRF70_NDEV_TO_IFACE(ndev));
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_assoc *cmd;
+ const u8 *ssid_ie;
+ int ret;
+
+ vif->bss = req->bss;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_ASSOCIATE,
+ sizeof(*cmd), vif->iface);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_assoc *)msg->data;
+ cmd->info.frequency = req->bss->channel->center_freq;
+ cmd->info.valid_fields |= NRF70_CONNECT_FREQ;
+
+ if (!is_zero_ether_addr(req->bss->bssid)) {
+ ether_addr_copy(cmd->info.hwaddr, req->bss->bssid);
+ cmd->info.valid_fields |= NRF70_CONNECT_HWADDR;
+ }
+
+ ssid_ie = cfg80211_find_ie(WLAN_EID_SSID, req->bss->ies->data,
+ req->bss->ies->len);
+ cmd->info.ssid.len = ssid_ie[1];
+ if (cmd->info.ssid.len) {
+ memcpy(cmd->info.ssid.ssid, ssid_ie + 2, cmd->info.ssid.len);
+ cmd->info.valid_fields |= NRF70_CONNECT_SSID;
+ }
+
+ cmd->info.wpa_ie.len = req->ie_len;
+ if (cmd->info.wpa_ie.len) {
+ memcpy(cmd->info.wpa_ie.ie, req->ie, cmd->info.wpa_ie.len);
+ cmd->info.valid_fields |= NRF70_CONNECT_WPA_IE;
+ }
+
+ cmd->info.use_mfp = req->use_mfp;
+ if (cmd->info.use_mfp)
+ cmd->info.valid_fields |= NRF70_CONNECT_MFP;
+
+ nrf70_set_crypto_info(&cmd->info, &req->crypto);
+
+ cmd->info.wifi_flags |= NRF70_CONNECT_FLAGS_USE_RRM;
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static int nrf70_deauth_command(struct spi_mem *mem, const u8 *hwaddr,
+ u16 reason, bool state_change,
+ struct net_device *ndev)
+{
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_disconn *cmd;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_DEAUTHENTICATE,
+ sizeof(*cmd), NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_disconn *)msg->data;
+ cmd->info.reason = reason;
+
+ if (!is_zero_ether_addr(hwaddr)) {
+ ether_addr_copy(cmd->info.hwaddr, hwaddr);
+ cmd->valid_fields |= NRF70_DISCONN_HWADDR;
+ }
+
+ if (state_change)
+ cmd->info.flags |= NRF70_DISCONN_FLAGS_LOCAL_STATE_CHANGE;
+
+ ret = nrf70_enqueue_message(mem, msg);
+ kfree(msg);
+
+ cfg80211_disconnected(ndev, reason, NULL, 0, true, GFP_KERNEL);
+
+ return ret;
+}
+
+static int nrf70_deauth(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_deauth_request *req)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+
+ return nrf70_deauth_command(priv->mem, req->bssid, req->reason_code,
+ req->local_state_change, ndev);
+}
+
+static int nrf70_disassoc(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_disassoc_request *req)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+
+ return nrf70_deauth_command(priv->mem, req->ap_addr, req->reason_code,
+ req->local_state_change, ndev);
+}
+
+static int nrf70_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
+ struct cfg80211_mgmt_tx_params *params, u64 *cookie)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_mgmt_tx *cmd;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_FRAME,
+ sizeof(*cmd), NRF70_NDEV_TO_IFACE(wdev->netdev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_mgmt_tx *)msg->data;
+ cmd->valid_fields = NRF70_MGMT_TX_FREQ | NRF70_MGMT_TX_DURATION |
+ NRF70_MGMT_TX_SET_FRAME_FREQ;
+ cmd->info.freq_params.valid_fields = NRF70_MGMT_TX_FREQ_MASK;
+
+ if (params->offchan)
+ cmd->info.wifi_flags |= NRF70_MGMT_TX_FLAGS_OFFCHAN_TX;
+ if (params->dont_wait_for_ack)
+ cmd->info.wifi_flags |= NRF70_MGMT_TX_FLAGS_NO_ACK;
+ if (params->no_cck)
+ cmd->info.wifi_flags |= NRF70_MGMT_TX_FLAGS_NO_CCK_RATE;
+ if (params->chan)
+ cmd->info.frequency = params->chan->center_freq;
+ if (params->len) {
+ memcpy(cmd->info.frame.data, params->buf, params->len);
+ cmd->info.frame.len = params->len;
+ }
+
+ cmd->info.dur = params->wait;
+ cmd->info.freq_params.frequency = cmd->info.frequency;
+ cmd->info.freq_params.channel_width = NL80211_CHAN_WIDTH_20;
+ cmd->info.freq_params.center_freq1 = cmd->info.frequency;
+ cmd->info.freq_params.center_freq2 = 0;
+ cmd->info.freq_params.channel_type = NL80211_CHAN_HT20;
+
+ while (!priv->mgmt_frame_cookie++)
+ ;
+ *cookie = cmd->info.cookie = priv->mgmt_frame_cookie;
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static void nrf70_update_mgmt_frame_reg(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ struct mgmt_frame_regs *upd)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_vif *vif = container_of(wdev, struct nrf70_vif, wdev);
+ struct device *dev = &priv->mem->spi->dev;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_mgmt_frame_reg *cmd;
+ unsigned long bmp = upd->interface_stypes;
+ int ret, bit;
+
+ if (!bmp) {
+ /* Clear state and exit. Usually called at AP start/teardown. */
+ WRITE_ONCE(vif->iface_stypes, 0);
+ return;
+ }
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_REGISTER_FRAME,
+ sizeof(*cmd), vif->iface);
+ if (IS_ERR(msg))
+ return;
+
+ cmd = (struct nrf70_cmd_mgmt_frame_reg *)msg->data;
+
+ while ((bit = ffs(bmp))) {
+ bit--;
+
+ clear_bit(bit, &bmp);
+ if (vif->iface_stypes & bit)
+ continue;
+
+ cmd->info.type = bit << 4;
+ cmd->info.match_len = 0;
+ set_bit(bit, &vif->iface_stypes);
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ if (ret) {
+ dev_err(dev, "Unable to register mgmt frame %d: %d\n",
+ cmd->info.type, ret);
+ continue;
+ }
+
+ /*
+ * There is no callback to know when the ongoing frame
+ * registration has completed. Instead, we need to wait for
+ * a bit before sending in another frame registration command.
+ * Below sleep value has been derived experimentally.
+ */
+ msleep(50);
+ }
+
+ kfree(msg);
+}
+
+static int nrf70_set_station(struct net_device *ndev, const u8 *mac,
+ struct station_parameters *params, int cmd_id)
+{
+ struct nrf70_ndev_priv *npriv = netdev_priv(ndev);
+ struct nrf70_priv *priv = npriv->priv;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_chg_sta *cmd;
+ int ret, flags_mask = NL80211_STA_FLAG_ASSOCIATED - 1;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, cmd_id, sizeof(*cmd),
+ NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_chg_sta *)msg->data;
+ cmd->valid_fields = NRF70_CHG_STA_LISTEN_INTERVAL;
+
+ cmd->aid = params->aid;
+ if (cmd->aid)
+ cmd->valid_fields |= NRF70_CHG_STA_AID;
+
+ cmd->sta_capability = params->capability;
+ if (cmd->sta_capability)
+ cmd->valid_fields |= NRF70_CHG_STA_STA_CAPAB;
+
+ cmd->listen_interval = params->listen_interval;
+
+ cmd->supp_rates.num_rates = params->link_sta_params.supported_rates_len;
+ if (cmd->supp_rates.num_rates) {
+ memcpy(cmd->supp_rates.rates,
+ params->link_sta_params.supported_rates,
+ cmd->supp_rates.num_rates);
+ cmd->valid_fields |= NRF70_CHG_STA_SUPP_RATES;
+ }
+
+ cmd->ext_cap_len = params->ext_capab_len;
+ if (cmd->ext_cap_len) {
+ memcpy(cmd->ext_cap, params->ext_capab, cmd->ext_cap_len);
+ cmd->valid_fields |= NRF70_CHG_STA_EXT_CAPAB;
+ }
+
+ cmd->sup_chans_len = params->supported_channels_len;
+ if (cmd->sup_chans_len) {
+ memcpy(cmd->sup_chans, params->supported_channels,
+ cmd->sup_chans_len);
+ cmd->valid_fields |= NRF70_CHG_STA_SUP_CHANS;
+ }
+
+ cmd->sup_oper_classes_len = params->supported_oper_classes_len;
+ if (cmd->sup_oper_classes_len) {
+ memcpy(cmd->sup_oper_classes, params->supported_oper_classes,
+ cmd->sup_oper_classes_len);
+ cmd->valid_fields |= NRF70_CHG_STA_OPER_CLASSES;
+ }
+
+ cmd->sta_flags2.mask = params->sta_flags_mask & flags_mask;
+ cmd->sta_flags2.set = params->sta_flags_set & flags_mask;
+ cmd->valid_fields |= NRF70_CHG_STA_FLAGS2;
+
+ if (params->link_sta_params.ht_capa) {
+ memcpy(cmd->ht_cap, params->link_sta_params.ht_capa,
+ sizeof(*params->link_sta_params.ht_capa));
+ cmd->valid_fields |= NRF70_CHG_STA_HT_CAP;
+ }
+
+ if (params->link_sta_params.vht_capa) {
+ memcpy(cmd->vht_cap, params->link_sta_params.vht_capa,
+ sizeof(*params->link_sta_params.vht_capa));
+ cmd->valid_fields |= NRF70_CHG_STA_VHT_CAP;
+ }
+
+ ether_addr_copy(cmd->hwaddr, mac);
+
+ if (params->link_sta_params.opmode_notif_used) {
+ cmd->opmode_notif = params->link_sta_params.opmode_notif;
+ cmd->valid_fields |= NRF70_CHG_STA_OPMODE_NOTIF;
+ }
+
+ cmd->wme_uapsd_queues = params->uapsd_queues;
+ if (cmd->wme_uapsd_queues)
+ cmd->valid_fields |= NRF70_CHG_STA_WME_UAPSD_QUEUES;
+
+ cmd->wme_max_sp = params->max_sp;
+ if (cmd->wme_max_sp)
+ cmd->valid_fields |= NRF70_CHG_STA_WME_MAX_SP;
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static int nrf70_add_station(struct wiphy *wiphy, struct net_device *ndev,
+ const u8 *mac, struct station_parameters *params)
+{
+ return nrf70_set_station(ndev, mac, params, NRF70_UMAC_CMD_NEW_STATION);
+}
+
+static int nrf70_change_station(struct wiphy *wiphy, struct net_device *ndev,
+ const u8 *mac,
+ struct station_parameters *params)
+{
+ return nrf70_set_station(ndev, mac, params, NRF70_UMAC_CMD_SET_STATION);
+}
+
+static int nrf70_del_station(struct wiphy *wiphy, struct net_device *ndev,
+ struct station_del_parameters *params)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_del_sta *cmd;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_DEL_STATION,
+ sizeof(*cmd), NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_del_sta *)msg->data;
+
+ if (params->mac && !is_zero_ether_addr(params->mac)) {
+ ether_addr_copy(cmd->hwaddr, params->mac);
+ cmd->valid_fields |= NRF70_DEL_STA_HWADDR;
+ }
+
+ cmd->mgmt_subtype = params->subtype;
+ if (cmd->mgmt_subtype)
+ cmd->valid_fields |= NRF70_DEL_STA_MGMT_SUBTYPE;
+
+ cmd->reason = params->reason_code;
+ if (cmd->reason)
+ cmd->valid_fields |= NRF70_DEL_STA_REASON;
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ return ret;
+}
+
+static int nrf70_get_station(struct wiphy *wiphy, struct net_device *ndev,
+ const u8 *mac, struct station_info *sinfo)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_get_sta *cmd;
+ int ret;
+
+ if (READ_ONCE(priv->sinfo))
+ return -EBUSY;
+
+ reinit_completion(&priv->station_info_available);
+ WRITE_ONCE(priv->sinfo, sinfo);
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_GET_STATION,
+ sizeof(*cmd), NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_get_sta *)msg->data;
+ ether_addr_copy(cmd->hwaddr, mac);
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+ if (ret)
+ goto out;
+
+ ret = wait_for_completion_timeout(&priv->station_info_available,
+ msecs_to_jiffies(100)) ?
+ 0 : -ETIMEDOUT;
+
+out:
+ WRITE_ONCE(priv->sinfo, NULL);
+
+ return ret;
+}
+
+static int nrf70_dump_station(struct wiphy *wiphy, struct net_device *ndev,
+ int idx, u8 *mac, struct station_info *sinfo)
+{
+ struct nrf70_ndev_priv *npriv = netdev_priv(ndev);
+ struct nrf70_vif *vif = npriv->vif;
+ unsigned long bmp, flags;
+ int bit;
+
+ spin_lock_irqsave(&vif->sta_lock, flags);
+ bmp = vif->sta_bitmap;
+ if (idx >= NRF70_PEERS_MAX || vif->sta_bitmap == NRF70_PEERS_MASK)
+ goto err;
+
+ do {
+ bit = ffz(bmp);
+ set_bit(bit, &bmp);
+ } while (idx--);
+
+ if (bit >= NRF70_PEERS_MAX)
+ goto err;
+
+ ether_addr_copy(mac, vif->sta[bit].addr);
+ spin_unlock_irqrestore(&vif->sta_lock, flags);
+
+ return nrf70_get_station(wiphy, ndev, mac, sinfo);
+
+err:
+ spin_unlock_irqrestore(&npriv->vif->sta_lock, flags);
+ return -ENOENT;
+}
+
+static int nrf70_set_qos_map(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_qos_map *qos_map)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_ndev_priv *npriv = netdev_priv(ndev);
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_set_qos_map *cmd;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_SET_QOS_MAP,
+ sizeof(*cmd), NRF70_NDEV_TO_IFACE(ndev));
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_set_qos_map *)msg->data;
+
+ /* NULL might be passed to disable QoS mapping. */
+ if (qos_map) {
+ cmd->map_info.len = sizeof(*qos_map);
+ memcpy(cmd->map_info.data, qos_map, cmd->map_info.len);
+ }
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ if (!ret)
+ WRITE_ONCE(npriv->vif->qos_map, qos_map);
+
+ kfree(msg);
+
+ return ret;
+}
+
+static int nrf70_set_wiphy_params(struct wiphy *wiphy, u32 changed)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_wiphy_info info = {};
+ struct nrf70_vif *vif = list_first_entry(&priv->vifs, typeof(*vif),
+ list);
+
+ if (list_entry_is_head(vif, &priv->vifs, list))
+ return -EINVAL;
+
+ if (changed & WIPHY_PARAM_RETRY_SHORT)
+ info.retry_short = wiphy->retry_short;
+ if (changed & WIPHY_PARAM_RETRY_LONG)
+ info.retry_long = wiphy->retry_long;
+ if (changed & WIPHY_PARAM_FRAG_THRESHOLD)
+ info.frag_threshold = wiphy->frag_threshold;
+ if (changed & WIPHY_PARAM_RTS_THRESHOLD)
+ info.rts_threshold = wiphy->rts_threshold;
+ if (changed & WIPHY_PARAM_COVERAGE_CLASS)
+ info.coverage_class = wiphy->coverage_class;
+
+ return nrf70_set_wiphy_command(priv->mem, vif->iface, &info);
+}
+
+static int nrf70_get_channel(struct wiphy *wiphy, struct wireless_dev *wdev,
+ unsigned int link_id,
+ struct cfg80211_chan_def *chandef)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ struct nrf70_vif *vif = container_of(wdev, struct nrf70_vif, wdev);
+ struct nrf70_msg *msg;
+ struct nrf70_umac_header *cmd;
+ int ret = 0;
+
+ if (!(vif->ndev->flags & IFF_UP))
+ return -ENETDOWN;
+
+ /*
+ * CMD_GET_CHANNEL works only for associated APs. In case of monitor
+ * mode, simply return the channel currently being tuned to.
+ */
+ if (vif->wdev.iftype == NL80211_IFTYPE_MONITOR) {
+ *chandef = vif->chandef;
+ goto out;
+ }
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_GET_CHANNEL,
+ sizeof(*cmd), vif->iface);
+ if (IS_ERR(msg)) {
+ ret = PTR_ERR(msg);
+ goto out;
+ }
+
+ reinit_completion(&vif->chan_updated);
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ if (ret)
+ goto out;
+
+ if (!wait_for_completion_timeout(&vif->chan_updated,
+ msecs_to_jiffies(1000)))
+ return -ETIMEDOUT;
+
+ *chandef = vif->chandef;
+
+ return chandef->center_freq1 ? 0 : -EINVAL;
+
+out:
+ return ret;
+}
+
+static int nrf70_probe_client(struct wiphy *wiphy, struct net_device *ndev,
+ const u8 *peer, u64 *cookie)
+{
+ /* Provide fake probe_client to work around hostapd limitations. */
+ return -EOPNOTSUPP;
+}
+
+static const struct cfg80211_ops nrf70_cfg80211_ops = {
+ .add_virtual_intf = nrf70_add_vif,
+ .del_virtual_intf = nrf70_del_vif,
+ .change_virtual_intf = nrf70_chg_vif,
+ .add_key = nrf70_add_key,
+ .del_key = nrf70_del_key,
+ .set_default_key = nrf70_set_default_key,
+ .set_default_mgmt_key = nrf70_set_default_mgmt_key,
+ .start_ap = nrf70_start_ap,
+ .change_beacon = nrf70_change_beacon,
+ .stop_ap = nrf70_stop_ap,
+ .set_monitor_channel = nrf70_set_monitor_channel,
+ .scan = nrf70_scan,
+ .abort_scan = nrf70_abort_scan,
+ .auth = nrf70_auth,
+ .assoc = nrf70_assoc,
+ .deauth = nrf70_deauth,
+ .disassoc = nrf70_disassoc,
+ .mgmt_tx = nrf70_mgmt_tx,
+ .update_mgmt_frame_registrations = nrf70_update_mgmt_frame_reg,
+ .add_station = nrf70_add_station,
+ .change_station = nrf70_change_station,
+ .del_station = nrf70_del_station,
+ .get_station = nrf70_get_station,
+ .dump_station = nrf70_dump_station,
+ .change_bss = nrf70_change_bss,
+ .set_qos_map = nrf70_set_qos_map,
+ .set_wiphy_params = nrf70_set_wiphy_params,
+ .get_channel = nrf70_get_channel,
+ .probe_client = nrf70_probe_client,
+};
+
+static const struct ieee80211_txrx_stypes
+nrf70_default_mgmt_stypes[NUM_NL80211_IFTYPES] = {
+ [NL80211_IFTYPE_STATION] = {
+ .tx = 0xffff,
+ .rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
+ BIT(IEEE80211_STYPE_PROBE_REQ >> 4),
+ },
+ [NL80211_IFTYPE_AP] = {
+ .tx = 0xffff,
+ .rx = BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) |
+ BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) |
+ BIT(IEEE80211_STYPE_PROBE_REQ >> 4) |
+ BIT(IEEE80211_STYPE_DISASSOC >> 4) |
+ BIT(IEEE80211_STYPE_AUTH >> 4) |
+ BIT(IEEE80211_STYPE_DEAUTH >> 4) |
+ BIT(IEEE80211_STYPE_ACTION >> 4),
+ },
+};
+
+static const struct ieee80211_iface_limit nrf70_if_limits[] = {
+ {
+ .max = NRF70_VIFS_MAX,
+ .types = BIT(NL80211_IFTYPE_STATION) |
+ BIT(NL80211_IFTYPE_AP),
+ },
+};
+
+static const struct ieee80211_iface_combination nrf70_if_comb[] = {
+ {
+ .limits = nrf70_if_limits,
+ .n_limits = ARRAY_SIZE(nrf70_if_limits),
+ .max_interfaces = NRF70_VIFS_MAX,
+ .num_different_channels = 1,
+ .beacon_int_infra_match = true,
+ }
+};
+
+#define NRF70_CHAN2G(freq, idx) \
+ { \
+ .band = NL80211_BAND_2GHZ, \
+ .center_freq = (freq), \
+ .hw_value = (idx), \
+ .max_power = 20, \
+ }
+
+static struct ieee80211_channel nrf70_dsss_chans[] = {
+ NRF70_CHAN2G(2412, 0),
+ NRF70_CHAN2G(2417, 1),
+ NRF70_CHAN2G(2422, 2),
+ NRF70_CHAN2G(2427, 3),
+ NRF70_CHAN2G(2432, 4),
+ NRF70_CHAN2G(2437, 5),
+ NRF70_CHAN2G(2442, 6),
+ NRF70_CHAN2G(2447, 7),
+ NRF70_CHAN2G(2452, 8),
+ NRF70_CHAN2G(2457, 9),
+ NRF70_CHAN2G(2462, 10),
+ NRF70_CHAN2G(2467, 11),
+ NRF70_CHAN2G(2472, 12),
+ NRF70_CHAN2G(2484, 13),
+};
+
+static struct ieee80211_rate nrf70_dsss_rates[] = {
+ { .bitrate = 10, .hw_value = 2 },
+ { .bitrate = 20, .hw_value = 4,
+ .flags = IEEE80211_RATE_SHORT_PREAMBLE },
+ { .bitrate = 55,
+ .hw_value = 11,
+ .flags = IEEE80211_RATE_SHORT_PREAMBLE },
+ { .bitrate = 110,
+ .hw_value = 22,
+ .flags = IEEE80211_RATE_SHORT_PREAMBLE },
+ { .bitrate = 60, .hw_value = 12 },
+ { .bitrate = 90, .hw_value = 18 },
+ { .bitrate = 120, .hw_value = 24 },
+ { .bitrate = 180, .hw_value = 36 },
+ { .bitrate = 240, .hw_value = 48 },
+ { .bitrate = 360, .hw_value = 72 },
+ { .bitrate = 480, .hw_value = 96 },
+ { .bitrate = 540, .hw_value = 108 },
+};
+
+static struct ieee80211_supported_band nrf70_band_2ghz = {
+ .channels = nrf70_dsss_chans,
+ .n_channels = ARRAY_SIZE(nrf70_dsss_chans),
+ .band = NL80211_BAND_2GHZ,
+ .bitrates = nrf70_dsss_rates,
+ .n_bitrates = ARRAY_SIZE(nrf70_dsss_rates),
+ .ht_cap = {
+ .ht_supported = 1,
+ .cap = IEEE80211_HT_CAP_MAX_AMSDU |
+ IEEE80211_HT_CAP_SGI_20 |
+ IEEE80211_HT_CAP_SGI_40 |
+ IEEE80211_HT_CAP_SUP_WIDTH_20_40 |
+ BIT(IEEE80211_HT_CAP_RX_STBC_SHIFT) |
+ IEEE80211_HT_CAP_LSIG_TXOP_PROT,
+ .ampdu_factor = IEEE80211_HT_MAX_AMPDU_32K,
+ .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16,
+ .mcs = {
+ .tx_params = IEEE80211_HT_MCS_TX_DEFINED,
+ .rx_mask[0] = 0xff,
+ .rx_mask[4] = 0x1,
+ },
+ },
+ .iftype_data = &(const struct ieee80211_sband_iftype_data){
+ .types_mask = BIT(NL80211_IFTYPE_STATION) |
+ BIT(NL80211_IFTYPE_AP),
+ .he_cap = {
+ .has_he = true,
+ .he_cap_elem = {
+ .mac_cap_info[0] = IEEE80211_HE_MAC_CAP0_HTC_HE,
+ },
+ .he_mcs_nss_supp = {
+ .rx_mcs_80 = 0xfffc,
+ .tx_mcs_80 = 0xfffc,
+ .rx_mcs_160 = 0xffff,
+ .tx_mcs_160 = 0xffff,
+ .rx_mcs_80p80 = 0xffff,
+ .tx_mcs_80p80 = 0xffff,
+ },
+ },
+ },
+ .n_iftype_data = 1,
+};
+
+#define NRF70_CHAN5G(freq, idx, flgs) \
+{ \
+ .band = NL80211_BAND_5GHZ, \
+ .center_freq = (freq), \
+ .hw_value = (idx), \
+ .max_power = 20, \
+ .flags = (flgs) \
+}
+
+static struct ieee80211_channel nrf70_ofdm_chans[] = {
+ NRF70_CHAN5G(5180, 14, 0),
+ NRF70_CHAN5G(5200, 15, 0),
+ NRF70_CHAN5G(5220, 16, 0),
+ NRF70_CHAN5G(5240, 17, 0),
+ NRF70_CHAN5G(5260, 18, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5280, 19, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5300, 20, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5320, 21, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5500, 22, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5520, 23, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5540, 24, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5560, 25, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5580, 26, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5600, 27, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5620, 28, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5640, 29, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5660, 30, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5680, 31, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5700, 32, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5720, 33, IEEE80211_CHAN_RADAR),
+ NRF70_CHAN5G(5745, 34, 0),
+ NRF70_CHAN5G(5765, 35, 0),
+ NRF70_CHAN5G(5785, 36, 0),
+ NRF70_CHAN5G(5805, 37, 0),
+ NRF70_CHAN5G(5825, 38, 0),
+ NRF70_CHAN5G(5845, 39, 0),
+ NRF70_CHAN5G(5865, 40, 0),
+ NRF70_CHAN5G(5885, 41, 0),
+};
+
+static struct ieee80211_rate nrf70_ofdm_rates[] = {
+ { .bitrate = 60, .hw_value = 12 },
+ { .bitrate = 90, .hw_value = 18 },
+ { .bitrate = 120, .hw_value = 24 },
+ { .bitrate = 180, .hw_value = 36 },
+ { .bitrate = 240, .hw_value = 48 },
+ { .bitrate = 360, .hw_value = 72 },
+ { .bitrate = 480, .hw_value = 96 },
+ { .bitrate = 540, .hw_value = 108 },
+};
+
+#define NRF70_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT \
+ (3 << IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT)
+
+#define NRF70_VHT_MCS_MAP \
+ ((IEEE80211_VHT_MCS_SUPPORT_0_7 << 2 * 0) | \
+ (IEEE80211_VHT_MCS_NOT_SUPPORTED << 2 * 1) | \
+ (IEEE80211_VHT_MCS_NOT_SUPPORTED << 2 * 2) | \
+ (IEEE80211_VHT_MCS_NOT_SUPPORTED << 2 * 3) | \
+ (IEEE80211_VHT_MCS_NOT_SUPPORTED << 2 * 4) | \
+ (IEEE80211_VHT_MCS_NOT_SUPPORTED << 2 * 5) | \
+ (IEEE80211_VHT_MCS_NOT_SUPPORTED << 2 * 6) | \
+ (IEEE80211_VHT_MCS_NOT_SUPPORTED << 2 * 7))
+
+static struct ieee80211_supported_band nrf70_band_5ghz = {
+ .channels = nrf70_ofdm_chans,
+ .n_channels = ARRAY_SIZE(nrf70_ofdm_chans),
+ .band = NL80211_BAND_5GHZ,
+ .bitrates = nrf70_ofdm_rates,
+ .n_bitrates = ARRAY_SIZE(nrf70_ofdm_rates),
+ .ht_cap = {
+ .ht_supported = 1,
+ .cap = IEEE80211_HT_CAP_MAX_AMSDU |
+ IEEE80211_HT_CAP_SGI_20 |
+ IEEE80211_HT_CAP_SGI_40 |
+ IEEE80211_HT_CAP_SUP_WIDTH_20_40 |
+ BIT(IEEE80211_HT_CAP_RX_STBC_SHIFT) |
+ IEEE80211_HT_CAP_LSIG_TXOP_PROT,
+ .ampdu_factor = IEEE80211_HT_MAX_AMPDU_32K,
+ .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16,
+ .mcs = {
+ .tx_params = IEEE80211_HT_MCS_TX_DEFINED,
+ .rx_mask[0] = 0xff,
+ .rx_mask[4] = 0x1,
+ },
+ },
+ .vht_cap = {
+ .vht_supported = true,
+ .cap = IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454 |
+ IEEE80211_VHT_CAP_SHORT_GI_80 |
+ IEEE80211_VHT_CAP_RXLDPC |
+ IEEE80211_VHT_CAP_TXSTBC |
+ IEEE80211_VHT_CAP_RXSTBC_1 |
+ IEEE80211_VHT_CAP_HTC_VHT |
+ NRF70_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT,
+ .vht_mcs = {
+ .rx_mcs_map = NRF70_VHT_MCS_MAP,
+ .tx_mcs_map = NRF70_VHT_MCS_MAP,
+ },
+ },
+ .iftype_data = &(const struct ieee80211_sband_iftype_data){
+ .types_mask = BIT(NL80211_IFTYPE_STATION) |
+ BIT(NL80211_IFTYPE_AP),
+ .he_cap = {
+ .has_he = true,
+ .he_cap_elem = {
+ .mac_cap_info[0] = IEEE80211_HE_MAC_CAP0_HTC_HE,
+ },
+ .he_mcs_nss_supp = {
+ .rx_mcs_80 = 0xfffc,
+ .tx_mcs_80 = 0xfffc,
+ .rx_mcs_160 = 0xffff,
+ .tx_mcs_160 = 0xffff,
+ .rx_mcs_80p80 = 0xffff,
+ .tx_mcs_80p80 = 0xffff,
+ },
+ },
+ },
+ .n_iftype_data = 1,
+};
+
+static const u32 nrf70_cipher_suites[] = {
+ WLAN_CIPHER_SUITE_WEP40, WLAN_CIPHER_SUITE_WEP104,
+ WLAN_CIPHER_SUITE_TKIP, WLAN_CIPHER_SUITE_CCMP,
+ WLAN_CIPHER_SUITE_CCMP_256, WLAN_CIPHER_SUITE_AES_CMAC,
+ WLAN_CIPHER_SUITE_GCMP, WLAN_CIPHER_SUITE_GCMP_256,
+ WLAN_CIPHER_SUITE_BIP_GMAC_128, WLAN_CIPHER_SUITE_BIP_GMAC_256,
+ WLAN_CIPHER_SUITE_BIP_CMAC_256,
+};
+
+#define NRF70_TUNING_LEN 16
+#define NRF70_PADDING_MAX 16
+static int nrf70_tune_read_op(struct spi_mem *mem)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ u32 *buf, addr = NRF70_RX_CMD_BASE;
+ int i, j, ret = 0;
+
+ buf = kzalloc(NRF70_TUNING_LEN, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* Test for readl timing. */
+ for (i = 0; i <= NRF70_PADDING_MAX; i++) {
+ priv->read_op_pad[0] = i;
+
+ memcpy(buf, nrf70_tuning_pattern, NRF70_TUNING_LEN);
+ nrf70_writev(mem, addr, buf, NRF70_TUNING_LEN);
+
+ memset(buf, 0, NRF70_TUNING_LEN);
+ for (j = 0; j < NRF70_TUNING_LEN / 4; j++)
+ buf[j] = nrf70_readl(mem, addr + 4 * j);
+
+ if (!memcmp(buf, nrf70_tuning_pattern, NRF70_TUNING_LEN))
+ break;
+ }
+ if (i > NRF70_PADDING_MAX) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ addr = NRF70_TX_CMD_BASE;
+ /* Test for PKTRAM readl timing. */
+ for (i = 0; i <= NRF70_PADDING_MAX; i++) {
+ priv->read_op_pad[1] = i;
+
+ memcpy(buf, nrf70_tuning_pattern, NRF70_TUNING_LEN);
+ nrf70_writev(mem, addr, buf, NRF70_TUNING_LEN);
+
+ memset(buf, 0, NRF70_TUNING_LEN);
+ for (j = 0; j < NRF70_TUNING_LEN / 4; j++)
+ buf[j] = nrf70_readl(mem, addr + 4 * j);
+
+ if (!memcmp(buf, nrf70_tuning_pattern, NRF70_TUNING_LEN))
+ break;
+ }
+ if (i > NRF70_PADDING_MAX) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Test for readv timing. */
+ for (i = 0; i <= NRF70_PADDING_MAX; i++) {
+ priv->read_op_pad[2] = i;
+
+ memcpy(buf, nrf70_tuning_pattern, NRF70_TUNING_LEN);
+ nrf70_writev(mem, addr, buf, NRF70_TUNING_LEN);
+
+ memset(buf, 0, NRF70_TUNING_LEN);
+ nrf70_readv(mem, addr, buf, NRF70_TUNING_LEN);
+
+ if (!memcmp(buf, nrf70_tuning_pattern, NRF70_TUNING_LEN))
+ break;
+ }
+ if (i > NRF70_PADDING_MAX)
+ ret = -EINVAL;
+
+out:
+ kfree(buf);
+
+ return ret;
+}
+
+static struct nrf70_mem_op *nrf70_select_op_variant(struct spi_mem *mem,
+ struct nrf70_mem_op *ops,
+ size_t num_ops)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0, 1),
+ SPI_MEM_OP_ADDR(3, 0, 0),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_NO_DATA);
+ int i;
+
+ if (num_ops <= 0)
+ return NULL;
+
+ for (i = 0; i < num_ops; i++) {
+ op.cmd.opcode = ops[i].op;
+ op.addr.nbytes = ops[i].width;
+ op.addr.buswidth = ops[i].width;
+ op.data.dir = ops[i].dir;
+ op.data.nbytes = 4;
+ op.data.buswidth = ops[i].width;
+
+ if (ops[i].dir == SPI_MEM_DATA_IN) {
+ op.dummy.nbytes = 5;
+ op.dummy.buswidth = ops[i].width;
+ op.data.buf.in = &priv->rx_buf;
+ } else {
+ op.data.buf.out = &priv->tx_buf;
+ }
+
+ if (spi_mem_supports_op(mem, &op))
+ break;
+ }
+
+ return i >= num_ops ? NULL : &ops[i];
+}
+
+static int nrf70_get_reg(struct nrf70_priv *priv)
+{
+ struct nrf70_msg *msg;
+ int ret;
+
+ if (!wait_for_completion_timeout(&priv->init_done, HZ))
+ return -EAGAIN;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_GET_REG,
+ sizeof(struct nrf70_umac_header), -1);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ reinit_completion(&priv->regdom_updated);
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ if (ret)
+ return ret;
+
+ return wait_for_completion_timeout(&priv->regdom_updated,
+ msecs_to_jiffies(1000)) ?
+ 0 : -ETIMEDOUT;
+}
+
+static int nrf70_set_reg(struct nrf70_priv *priv, char *alpha2)
+{
+ struct nrf70_msg *msg;
+ struct nrf70_cmd_set_reg *cmd;
+ int ret;
+
+ if (!wait_for_completion_timeout(&priv->init_done, HZ))
+ return -EAGAIN;
+
+ msg = nrf70_create_msg(NRF70_MSG_UMAC, NRF70_UMAC_CMD_REQ_SET_REG,
+ sizeof(*cmd), -1);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ cmd = (struct nrf70_cmd_set_reg *)msg->data;
+ memcpy(cmd->alpha2, alpha2, sizeof(cmd->alpha2));
+ cmd->valid_fields = NRF70_SET_REG_ALPHA2 | NRF70_SET_REG_USER_REG_FORCE;
+
+ reinit_completion(&priv->regdom_updated);
+
+ ret = nrf70_enqueue_message(priv->mem, msg);
+ kfree(msg);
+
+ if (ret)
+ return ret;
+
+ return wait_for_completion_timeout(&priv->regdom_updated,
+ msecs_to_jiffies(1000)) ?
+ 0 : -ETIMEDOUT;
+}
+
+static void nrf70_reg_notifier(struct wiphy *wiphy,
+ struct regulatory_request *request)
+{
+ struct nrf70_wiphy_priv *wpriv = wiphy_priv(wiphy);
+ struct nrf70_priv *priv = wpriv->priv;
+ int ret;
+
+ ret = nrf70_get_reg(priv);
+ if (ret || !memcmp(request->alpha2, priv->regdom, sizeof(priv->regdom)))
+ return;
+
+ (void)nrf70_set_reg(priv, request->alpha2);
+}
+
+static int nrf70_deinit_command(struct spi_mem *mem)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_msg *msg;
+ int ret;
+
+ msg = nrf70_create_msg(NRF70_MSG_SYSTEM, NRF70_CMD_DEINIT,
+ sizeof(struct nrf70_header), -1);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ reinit_completion(&priv->init_done);
+ ret = nrf70_enqueue_message(mem, msg);
+ kfree(msg);
+
+ return ret ? ret : (wait_for_completion_timeout(&priv->init_done, HZ) ?
+ 0 : -ETIMEDOUT);
+}
+
+static int nrf70_probe(struct spi_mem *mem)
+{
+ struct nrf70_priv *priv;
+ struct nrf70_wiphy_priv *wpriv;
+ struct device *dev = &mem->spi->dev;
+ struct nrf70_vif *vif;
+ int irq_num, ret, val;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ spi_mem_set_drvdata(mem, priv);
+ priv->mem = mem;
+
+ mutex_init(&priv->write_lock);
+ mutex_init(&priv->read_lock);
+ mutex_init(&priv->enqueue_lock);
+ mutex_init(&priv->desc_lock);
+
+ priv->buck_en = devm_gpiod_get(dev, "bucken", 0);
+ if (!priv->buck_en) {
+ dev_err(dev, "Unable to find bucken-gpios property\n");
+ return -EINVAL;
+ }
+
+ ret = gpiod_direction_output(priv->buck_en, 0);
+ if (ret) {
+ dev_err(dev, "Unable to set buck_en direction\n");
+ return -EIO;
+ }
+
+ priv->iovdd_en = devm_gpiod_get_optional(dev, "iovdd", 0);
+ if (IS_ERR(priv->iovdd_en)) {
+ dev_err(dev, "Invalid iovdd-gpios property\n");
+ return PTR_ERR(priv->iovdd_en);
+ }
+
+ if (priv->iovdd_en) {
+ ret = gpiod_direction_output(priv->iovdd_en, 0);
+ if (ret) {
+ dev_err(dev, "Unable to set iovdd_en direction\n");
+ return -EIO;
+ }
+ }
+
+ priv->irq = devm_gpiod_get(dev, "irq", 0);
+ if (!priv->irq) {
+ dev_err(dev, "Unable to find irq-gpios property\n");
+ return -EINVAL;
+ }
+
+ ret = gpiod_direction_input(priv->irq);
+ if (ret) {
+ dev_err(dev, "Unable to set irq direction\n");
+ return -EIO;
+ }
+
+ irq_num = gpiod_to_irq(priv->irq);
+ if (irq_num < 0) {
+ dev_err(dev, "Unable to get gpio irq number: %d\n", ret);
+ return irq_num;
+ }
+
+ /* Test support of opcodes. */
+ priv->read_op = nrf70_select_op_variant(mem, nrf70_read_ops,
+ ARRAY_SIZE(nrf70_read_ops));
+ if (!priv->read_op)
+ return -EOPNOTSUPP;
+ priv->write_op = nrf70_select_op_variant(mem, nrf70_write_ops,
+ ARRAY_SIZE(nrf70_write_ops));
+ if (!priv->write_op)
+ return -EOPNOTSUPP;
+
+ /* Wake up RPU. */
+ gpiod_set_value(priv->buck_en, 0);
+ if (priv->iovdd_en)
+ gpiod_set_value(priv->iovdd_en, 0);
+ usleep_range(1000, 2000);
+ gpiod_set_value(priv->buck_en, 1);
+ usleep_range(1000, 2000);
+ if (priv->iovdd_en) {
+ gpiod_set_value(priv->iovdd_en, 1);
+ usleep_range(1000, 2000);
+ }
+
+ nrf70_wrsr2(mem, NRF70_SR2_WAKEUP_REQ);
+
+ if (read_poll_timeout(nrf70_rdsr2, val, val & NRF70_SR2_WAKEUP_REQ,
+ 5 * USEC_PER_MSEC, 2 * USEC_PER_SEC, false,
+ mem)) {
+ dev_err(dev, "Unable to wake up RPU: request failed\n");
+ ret = -ETIMEDOUT;
+ goto err_disable_rpu;
+ }
+
+ if (read_poll_timeout(nrf70_rdsr1, val, val & NRF70_SR1_AWAKE,
+ 5 * USEC_PER_MSEC, 2 * USEC_PER_SEC, false,
+ mem)) {
+ dev_err(dev, "Unable to wake up RPU: bus not active\n");
+ ret = -ETIMEDOUT;
+ goto err_disable_rpu;
+ }
+
+ /* Ungate RPU clocks. */
+ nrf70_writel(mem, NRF70_PBUS_CLK, NRF70_PBUS_CLK_UNGATE);
+
+ ret = nrf70_tune_read_op(mem);
+ if (ret) {
+ dev_err(dev, "Unable to tune-in read op timing\n");
+ goto err_disable_rpu;
+ }
+
+ ret = nrf70_load_firmware(mem);
+ if (ret)
+ goto err_disable_rpu;
+
+ init_completion(&priv->init_done);
+ init_completion(&priv->station_info_available);
+ init_completion(&priv->regdom_updated);
+ INIT_WORK(&priv->event_work, nrf70_event_worker);
+
+ ret = devm_request_threaded_irq(dev, irq_num, NULL, nrf70_irq,
+ IRQF_ONESHOT | IRQF_TRIGGER_RISING,
+ dev_name(dev), mem);
+ if (ret < 0) {
+ dev_err(dev, "Unable to request threaded irq: %d\n", ret);
+ goto err_disable_rpu;
+ }
+
+ priv->tx_desc_bitmap[0] = NRF70_DESC_MASK;
+ priv->tx_desc_bitmap[1] = NRF70_DESC_MASK;
+ INIT_LIST_HEAD(&priv->cookies);
+ INIT_LIST_HEAD(&priv->vifs);
+
+ ret = nrf70_mac_init(mem);
+ if (ret < 0) {
+ dev_err(dev, "Unable to initialize UMAC: %d\n", ret);
+ goto err_disable_rpu;
+ }
+
+ priv->wiphy = wiphy_new(&nrf70_cfg80211_ops, sizeof(*wpriv));
+ if (!priv->wiphy) {
+ dev_err(dev, "Unable to allocate wiphy\n");
+ ret = -ENOMEM;
+ goto err_deinit_rpu;
+ }
+
+ set_wiphy_dev(priv->wiphy, dev);
+ wpriv = wiphy_priv(priv->wiphy);
+ wpriv->priv = priv;
+
+ priv->wiphy->mgmt_stypes = nrf70_default_mgmt_stypes;
+ priv->wiphy->iface_combinations = nrf70_if_comb;
+ priv->wiphy->flags |= WIPHY_FLAG_NETNS_OK | WIPHY_FLAG_4ADDR_AP |
+ WIPHY_FLAG_4ADDR_STATION |
+ WIPHY_FLAG_REPORTS_OBSS | WIPHY_FLAG_OFFCHAN_TX |
+ WIPHY_FLAG_CONTROL_PORT_PROTOCOL |
+ WIPHY_FLAG_AP_UAPSD;
+
+ priv->wiphy->features |= NL80211_FEATURE_SK_TX_STATUS |
+ NL80211_FEATURE_SAE |
+ NL80211_FEATURE_HT_IBSS |
+ NL80211_FEATURE_MAC_ON_CREATE;
+
+ wiphy_ext_feature_set(priv->wiphy, NL80211_EXT_FEATURE_SET_SCAN_DWELL);
+
+ priv->wiphy->bands[NL80211_BAND_2GHZ] = &nrf70_band_2ghz;
+ priv->wiphy->bands[NL80211_BAND_5GHZ] = &nrf70_band_5ghz;
+ priv->wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
+ priv->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
+ BIT(NL80211_IFTYPE_AP);
+ if (priv->has_raw_mode)
+ priv->wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR);
+ priv->wiphy->max_scan_ssids = NRF70_SCAN_SSIDS_MAX;
+ priv->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN;
+ priv->wiphy->cipher_suites = nrf70_cipher_suites;
+ priv->wiphy->n_cipher_suites = ARRAY_SIZE(nrf70_cipher_suites);
+
+ priv->wiphy->reg_notifier = nrf70_reg_notifier;
+
+ ret = wiphy_register(priv->wiphy);
+ if (ret < 0) {
+ dev_err(dev, "Unable to register wiphy: %d\n", ret);
+ goto err_wiphy;
+ }
+
+ priv->vif_bitmap = NRF70_VIFS_MASK;
+
+ /* Add primary net interface. */
+ vif = nrf70_add_if(priv, "nrf%d", NET_NAME_UNKNOWN,
+ NL80211_IFTYPE_STATION, NULL, false);
+ if (!IS_ERR(vif))
+ return 0;
+
+ ret = PTR_ERR(vif);
+ wiphy_unregister(priv->wiphy);
+err_wiphy:
+ wiphy_free(priv->wiphy);
+err_deinit_rpu:
+ nrf70_deinit_command(mem);
+err_disable_rpu:
+ nrf70_writel(mem, NRF70_PBUS_CLK, 0x0);
+ if (priv->iovdd_en)
+ gpiod_set_value(priv->iovdd_en, 0);
+ gpiod_set_value(priv->buck_en, 0);
+
+ return ret;
+}
+
+static int nrf70_remove(struct spi_mem *mem)
+{
+ struct nrf70_priv *priv = spi_mem_get_drvdata(mem);
+ struct nrf70_cookie *cookie, *tmpc;
+ struct nrf70_vif *vif, *tmpv;
+
+ list_for_each_entry_safe(cookie, tmpc, &priv->cookies, list) {
+ list_del(&cookie->list);
+ kfree(cookie);
+ }
+
+ list_for_each_entry_safe(vif, tmpv, &priv->vifs, list) {
+ nrf70_drain_tx(priv, vif);
+ unregister_netdev(vif->ndev);
+ list_del(&vif->list);
+ kfree(vif);
+ }
+
+ wiphy_unregister(priv->wiphy);
+ wiphy_free(priv->wiphy);
+
+ nrf70_deinit_command(mem);
+ cancel_work_sync(&priv->event_work);
+
+ /* Power off RPU. */
+ nrf70_writel(mem, NRF70_PBUS_CLK, 0x0);
+ if (priv->iovdd_en)
+ gpiod_set_value(priv->iovdd_en, 0);
+ gpiod_set_value(priv->buck_en, 0);
+
+ return 0;
+}
+
+static const struct of_device_id nrf70_of_match_table[] = {
+ { .compatible = "nordic,nrf70" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, nrf70_of_match_table);
+
+static struct spi_mem_driver nrf70_driver = {
+ .spidrv = {
+ .driver = {
+ .name = "nrf70",
+ .of_match_table = nrf70_of_match_table,
+ },
+ },
+ .probe = nrf70_probe,
+ .remove = nrf70_remove,
+};
+
+module_spi_mem_driver(nrf70_driver);
+MODULE_DESCRIPTION("Nordic Semiconductor nRF70 series wireless companion IC");
+MODULE_AUTHOR("Artur Rojek <artur@...clusive.tech>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wireless/nordic/nrf70_cmds.h b/drivers/net/wireless/nordic/nrf70_cmds.h
new file mode 100644
index 000000000000..d996c0a14676
--- /dev/null
+++ b/drivers/net/wireless/nordic/nrf70_cmds.h
@@ -0,0 +1,1051 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Conclusive Engineering Sp. z o. o.
+ */
+
+#ifndef _NRF70_CMDS_H
+#define _NRF70_CMDS_H
+
+#include <linux/if_ether.h>
+#include <net/cfg80211.h>
+
+enum nrf70_sys_cmds {
+ NRF70_CMD_INIT,
+ NRF70_CMD_TX,
+ NRF70_CMD_IF_TYPE,
+ NRF70_CMD_MODE,
+ NRF70_CMD_GET_STATS,
+ NRF70_CMD_CLEAR_STATS,
+ NRF70_CMD_RX,
+ NRF70_CMD_PWR,
+ NRF70_CMD_DEINIT,
+ NRF70_CMD_BTCOEX,
+ NRF70_CMD_RF_TEST,
+ NRF70_CMD_HE_GI_LTF_CONFIG,
+ NRF70_CMD_UMAC_INT_STATS,
+ NRF70_CMD_RADIO_TEST_INIT,
+ NRF70_CMD_RT_REQ_SET_REG,
+ NRF70_CMD_TX_FIX_DATA_RATE,
+ NRF70_CMD_CHANNEL,
+ NRF70_CMD_RAW_CONFIG_MODE,
+ NRF70_CMD_RAW_CONFIG_FILTER,
+ NRF70_CMD_RAW_TX_PKT,
+ NRF70_CMD_RESET_STATISTICS,
+ NRF70_CMD_MAX
+};
+
+/* Data commands and events share the same enums. */
+enum nrf70_data_cmds {
+ NRF70_CMD_MGMT_BUFF_CONFIG,
+ NRF70_CMD_TX_BUFF,
+ NRF70_CMD_TX_BUFF_DONE,
+ NRF70_CMD_RX_BUFF,
+ NRF70_CMD_CARRIER_ON,
+ NRF70_CMD_CARRIER_OFF,
+ NRF70_CMD_PM_MODE,
+ NRF70_CMD_PS_GET_FRAMES,
+};
+
+enum nrf70_umac_cmds {
+ NRF70_UMAC_CMD_TRIGGER_SCAN,
+ NRF70_UMAC_CMD_GET_SCAN_RESULTS,
+ NRF70_UMAC_CMD_AUTHENTICATE,
+ NRF70_UMAC_CMD_ASSOCIATE,
+ NRF70_UMAC_CMD_DEAUTHENTICATE,
+ NRF70_UMAC_CMD_SET_WIPHY,
+ NRF70_UMAC_CMD_NEW_KEY,
+ NRF70_UMAC_CMD_DEL_KEY,
+ NRF70_UMAC_CMD_SET_KEY,
+ NRF70_UMAC_CMD_GET_KEY,
+ NRF70_UMAC_CMD_NEW_BEACON,
+ NRF70_UMAC_CMD_SET_BEACON,
+ NRF70_UMAC_CMD_SET_BSS,
+ NRF70_UMAC_CMD_START_AP,
+ NRF70_UMAC_CMD_STOP_AP,
+ NRF70_UMAC_CMD_NEW_INTERFACE,
+ NRF70_UMAC_CMD_SET_INTERFACE,
+ NRF70_UMAC_CMD_DEL_INTERFACE,
+ NRF70_UMAC_CMD_SET_IFFLAGS,
+ NRF70_UMAC_CMD_NEW_STATION,
+ NRF70_UMAC_CMD_DEL_STATION,
+ NRF70_UMAC_CMD_SET_STATION,
+ NRF70_UMAC_CMD_GET_STATION,
+ NRF70_UMAC_CMD_START_P2P_DEVICE,
+ NRF70_UMAC_CMD_STOP_P2P_DEVICE,
+ NRF70_UMAC_CMD_REMAIN_ON_CHANNEL,
+ NRF70_UMAC_CMD_CANCEL_REMAIN_ON_CHANNEL,
+ NRF70_UMAC_CMD_SET_CHANNEL,
+ NRF70_UMAC_CMD_RADAR_DETECT,
+ NRF70_UMAC_CMD_REGISTER_FRAME,
+ NRF70_UMAC_CMD_FRAME,
+ NRF70_UMAC_CMD_JOIN_IBSS,
+ NRF70_UMAC_CMD_WIN_STA_CONNECT,
+ NRF70_UMAC_CMD_SET_POWER_SAVE,
+ NRF70_UMAC_CMD_SET_WOWLAN,
+ NRF70_UMAC_CMD_SUSPEND,
+ NRF70_UMAC_CMD_RESUME,
+ NRF70_UMAC_CMD_SET_QOS_MAP,
+ NRF70_UMAC_CMD_GET_CHANNEL,
+ NRF70_UMAC_CMD_GET_TX_POWER,
+ NRF70_UMAC_CMD_GET_INTERFACE,
+ NRF70_UMAC_CMD_GET_WIPHY,
+ NRF70_UMAC_CMD_GET_IFHWADDR,
+ NRF70_UMAC_CMD_SET_IFHWADDR,
+ NRF70_UMAC_CMD_GET_REG,
+ NRF70_UMAC_CMD_SET_REG,
+ NRF70_UMAC_CMD_REQ_SET_REG,
+ NRF70_UMAC_CMD_CONFIG_UAPSD,
+ NRF70_UMAC_CMD_CONFIG_TWT,
+ NRF70_UMAC_CMD_TEARDOWN_TWT,
+ NRF70_UMAC_CMD_ABORT_SCAN,
+ NRF70_UMAC_CMD_MCAST_FILTER,
+ NRF70_UMAC_CMD_CHANGE_MACADDR,
+ NRF70_UMAC_CMD_SET_POWER_SAVE_TIMEOUT,
+ NRF70_UMAC_CMD_GET_CONNECTION_INFO,
+ NRF70_UMAC_CMD_GET_POWER_SAVE_INFO,
+ NRF70_UMAC_CMD_SET_LISTEN_INTERVAL,
+ NRF70_UMAC_CMD_CONFIG_EXTENDED_PS,
+ NRF70_UMAC_CMD_CONFIG_QUIET_PERIOD,
+};
+
+enum nrf70_sys_events {
+ NRF70_EVENT_PWR_DATA,
+ NRF70_EVENT_INIT_DONE,
+ NRF70_EVENT_STATS,
+ NRF70_EVENT_DEINIT_DONE,
+ NRF70_EVENT_RF_TEST,
+ NRF70_EVENT_COEX_CONFIG,
+ NRF70_EVENT_INT_UMAC_STATS,
+ NRF70_EVENT_RADIOCMD_STATUS,
+ NRF70_EVENT_CHANNEL_SET_DONE,
+ NRF70_EVENT_MODE_SET_DONE,
+ NRF70_EVENT_FILTER_SET_DONE,
+ NRF70_EVENT_RAW_TX_DONE,
+};
+
+enum nrf70_umac_events {
+ NRF70_UMAC_EVENT_UNSPECIFIED = 256,
+ NRF70_UMAC_EVENT_TRIGGER_SCAN_START,
+ NRF70_UMAC_EVENT_SCAN_ABORTED,
+ NRF70_UMAC_EVENT_SCAN_DONE,
+ NRF70_UMAC_EVENT_SCAN_RESULT,
+ NRF70_UMAC_EVENT_AUTHENTICATE,
+ NRF70_UMAC_EVENT_ASSOCIATE,
+ NRF70_UMAC_EVENT_CONNECT,
+ NRF70_UMAC_EVENT_DEAUTHENTICATE,
+ NRF70_UMAC_EVENT_DISASSOCIATE,
+ NRF70_UMAC_EVENT_NEW_STATION,
+ NRF70_UMAC_EVENT_DEL_STATION,
+ NRF70_UMAC_EVENT_GET_STATION,
+ NRF70_UMAC_EVENT_REMAIN_ON_CHANNEL,
+ NRF70_UMAC_EVENT_CANCEL_REMAIN_ON_CHANNEL,
+ NRF70_UMAC_EVENT_DISCONNECT,
+ NRF70_UMAC_EVENT_FRAME,
+ NRF70_UMAC_EVENT_COOKIE_RESP,
+ NRF70_UMAC_EVENT_FRAME_TX_STATUS,
+ NRF70_UMAC_EVENT_IFFLAGS_STATUS,
+ NRF70_UMAC_EVENT_GET_TX_POWER,
+ NRF70_UMAC_EVENT_GET_CHANNEL,
+ NRF70_UMAC_EVENT_SET_INTERFACE,
+ NRF70_UMAC_EVENT_UNPROT_DEAUTHENTICATE,
+ NRF70_UMAC_EVENT_UNPROT_DISASSOCIATE,
+ NRF70_UMAC_EVENT_NEW_INTERFACE,
+ NRF70_UMAC_EVENT_NEW_WIPHY,
+ NRF70_UMAC_EVENT_GET_IFHWADDR,
+ NRF70_UMAC_EVENT_GET_REG,
+ NRF70_UMAC_EVENT_SET_REG,
+ NRF70_UMAC_EVENT_REQ_SET_REG,
+ NRF70_UMAC_EVENT_GET_KEY,
+ NRF70_UMAC_EVENT_BEACON_HINT,
+ NRF70_UMAC_EVENT_REG_CHANGE,
+ NRF70_UMAC_EVENT_WIPHY_REG_CHANGE,
+ NRF70_UMAC_EVENT_SCAN_DISPLAY_RESULT,
+ NRF70_UMAC_EVENT_CMD_STATUS,
+ NRF70_UMAC_EVENT_BSS_INFO,
+ NRF70_UMAC_EVENT_CONFIG_TWT,
+ NRF70_UMAC_EVENT_TEARDOWN_TWT,
+ NRF70_UMAC_EVENT_TWT_SLEEP,
+ NRF70_UMAC_EVENT_COALESCING,
+ NRF70_UMAC_EVENT_MCAST_FILTER,
+ NRF70_UMAC_EVENT_GET_CONNECTION_INFO,
+ NRF70_UMAC_EVENT_GET_POWER_SAVE_INFO
+};
+
+#define NRF70_MSG_SYSTEM 0
+#define NRF70_MSG_DATA 2
+#define NRF70_MSG_UMAC 3
+
+struct __packed nrf70_msg {
+ u32 len;
+ u32 resubmit;
+ u32 type;
+ u8 data[];
+};
+
+struct __packed nrf70_header {
+ u32 id;
+ u32 len;
+};
+
+#define NRF70_UMAC_ID_WDEV BIT(0)
+struct __packed nrf70_umac_header {
+ u32 portid; /* unused */
+ u32 seq; /* used only for EVENT_SCAN_DISPLAY_RESULT */
+ u32 id;
+ s32 ret_val; /* unused */
+ struct __packed {
+ u32 valid_fields;
+ s32 iface_id; /* unused */
+ s32 wiphy_id; /* unused */
+ u64 wdev_id;
+ } idx;
+};
+
+/* System commands. */
+#define NRF70_RF_PARAMS_SZ 200
+#define NRF70_RX_QUEUE_CNT 3
+#define NRF70_COUNTRY_CODE_LEN 2
+#define NRF70_OP_BAND_ALL 0
+#define NRF70_OP_BAND_2G 1
+struct __packed nrf70_cmd_sys_init {
+ struct nrf70_header header;
+ u32 dev_id;
+ struct __packed {
+ u32 sleep_enable;
+ u32 hw_bringup_time;
+ u32 sw_bringup_time;
+ u32 bcn_time_out;
+ u32 calib_sleep_clk;
+ u32 phy_calib;
+ u8 hwaddr[ETH_ALEN];
+ u8 rf_params[NRF70_RF_PARAMS_SZ];
+ u8 rf_params_valid;
+ } sys_param;
+ struct __packed {
+ u16 size;
+ u16 count;
+ } rx_buf_pools[NRF70_RX_QUEUE_CNT];
+ struct __packed {
+ u8 rate_protection_type;
+ u8 aggregation;
+ u8 wmm;
+ u8 max_tx_agg_sessions;
+ u8 max_rx_agg_sessions;
+ u8 max_tx_aggregation;
+ u8 reorder_buf_size;
+ u32 max_rxampdu_size;
+ } data_config_params;
+ struct __packed {
+ u32 temp_based_calib_en;
+ u32 temp_calib_bitmap;
+ u32 vbat_calibp_bitmap;
+ u32 temp_vbat_mon_period;
+ s32 vth_very_low;
+ s32 vth_low;
+ s32 vth_hi;
+ s32 temp_threshold;
+ s32 vbat_threshold;
+ } vbat_config;
+ u8 tcp_ip_checksum_offload;
+ u8 country_code[NRF70_COUNTRY_CODE_LEN];
+ u32 op_band;
+ u8 mgmt_buff_offload;
+ u32 feature_flags;
+ u32 disable_beamforming;
+ u32 discon_timeout;
+ u8 ps_data_retrieval_mech;
+};
+
+#define NRF70_OP_MODE_STA BIT(0)
+#define NRF70_OP_MODE_MONITOR BIT(1)
+#define NRF70_OP_MODE_AP BIT(4)
+struct __packed nrf70_cmd_raw_config_mode {
+ struct nrf70_header header;
+ u8 if_idx;
+ u8 mode;
+};
+
+struct __packed nrf70_event_raw_config_mode {
+ struct nrf70_header header;
+ u8 if_idx;
+ u8 mode;
+ s32 status;
+};
+
+struct __packed nrf70_cmd_set_channel {
+ struct nrf70_header header;
+ u8 if_idx;
+ struct __packed {
+ u32 primary_num;
+ u8 bandwidth;
+ s32 sec_20_offset;
+ s32 sec_40_offset;
+ } chan;
+};
+
+struct __packed nrf70_event_set_channel {
+ struct nrf70_header header;
+ u8 if_idx;
+ u32 chan;
+ s32 status;
+};
+
+#define NRF70_OP_MODE_PRODUCTION 0
+struct __packed nrf70_cmd_get_stats {
+ struct nrf70_header header;
+ u32 stats_type;
+ u32 op_mode;
+};
+
+/* Data commands/events. */
+#define NRF70_BUF_TIMESTAMP_SZ 6
+struct __packed nrf70_cmd_rx_buf {
+ struct nrf70_header header;
+ s16 rx_pkt_type;
+ u8 rate_flags;
+ u8 rate;
+ u8 wdev_id;
+ u8 rx_pkt_cnt;
+ u8 reserved;
+ u8 mac_header_len;
+ u16 frequency;
+ s16 signal;
+ struct __packed {
+ u16 desc_id;
+ u16 pkt_len;
+ u8 pkt_type;
+ u8 timestamp_rec[NRF70_BUF_TIMESTAMP_SZ];
+ u8 timestamp_ack[NRF70_BUF_TIMESTAMP_SZ];
+ } buf_info[];
+};
+
+#define NRF70_SAP_PM_CLIENT_ACTIVE 0
+#define NRF70_SAP_PM_CLIENT_PS_MODE 1
+struct __packed nrf70_cmd_sap_pm {
+ struct nrf70_header header;
+ u32 wdev_id;
+ u8 state;
+ u8 hwaddr[ETH_ALEN];
+};
+
+struct __packed nrf70_buf_info {
+ u16 pkt_len;
+ u32 ddr_ptr;
+};
+
+#define NRF70_TX_QOS_MASK 0xffff
+#define NRF70_TX_FLAG_CSUM_AVAIL BIT(30)
+#define NRF70_TX_FLAG_TWT_EMERG BIT(31)
+struct __packed nrf70_cmd_tx_buf {
+ struct nrf70_header header;
+ u8 wdev_id;
+ u8 tx_desc_num;
+ struct __packed {
+ s32 umac_fill_flags;
+ u16 fc;
+ u8 dst[ETH_ALEN];
+ u8 src[ETH_ALEN];
+ u16 etype;
+ u32 tx_flags;
+ u8 more_data;
+ u8 eosp;
+ } mac_hdr_info;
+ u32 pending_buf_size;
+ u8 num_tx_pkts;
+ struct nrf70_buf_info buf_info[];
+};
+
+struct __packed nrf70_event_tx_buff_done {
+ struct nrf70_header header;
+ u8 tx_desc_num;
+ u8 num_tx_status_code;
+ u8 timestamp_sent[NRF70_BUF_TIMESTAMP_SZ];
+ u8 timestamp_rec[NRF70_BUF_TIMESTAMP_SZ];
+ u8 tx_status_code[];
+};
+
+struct __packed nrf70_event_carrier_state {
+ struct nrf70_header header;
+ u32 wdev_id;
+};
+
+/* UMAC commands/events. */
+#define NRF70_SSID_SZ 32
+struct __packed nrf70_ssid {
+ u8 len;
+ u8 ssid[NRF70_SSID_SZ];
+};
+
+#define NRF70_IE_SZ 400
+struct __packed nrf70_ie {
+ u16 len;
+ s8 ie[NRF70_IE_SZ];
+};
+
+#define NRF70_FRAME_SZ 400
+struct __packed nrf70_frame {
+ s32 len;
+ s8 data[NRF70_FRAME_SZ];
+};
+
+#define NRF70_SCAN_REASON_DISPLAY 0
+#define NRF70_SCAN_REASON_CONNECT 1
+#define NRF70_SCAN_BAND_ANY 0
+#define NRF70_SCAN_BAND_2GHZ BIT(0)
+#define NRF70_SCAN_BAND_5GHZ BIT(1)
+struct __packed nrf70_cmd_scan {
+ struct nrf70_umac_header header;
+ struct __packed {
+ s32 reason;
+ u16 passive_scan;
+ u8 num_scan_ssids;
+ struct nrf70_ssid scan_ssids[2];
+ u8 no_cck;
+ u8 bands;
+ struct nrf70_ie ie;
+ u8 hwaddr[ETH_ALEN];
+ u16 dwell_time_active;
+ u16 dwell_time_passive;
+ u16 num_scan_channels;
+ u8 skip_local_admin_macs;
+ u32 center_freq[];
+ } scan_info;
+};
+
+struct __packed nrf70_event_cmd_status {
+ struct nrf70_umac_header header;
+ u32 cmd_id;
+ s32 status;
+};
+
+struct __packed nrf70_cmd_change_hwaddr {
+ struct nrf70_umac_header header;
+ u8 hwaddr[ETH_ALEN];
+};
+
+struct __packed nrf70_cmd_get_scan_results {
+ struct nrf70_umac_header header;
+ s32 reason;
+};
+
+struct __packed nrf70_cmd_chg_vif_state {
+ struct nrf70_umac_header header;
+ struct __packed {
+ u32 state;
+ s8 if_idx;
+ } info;
+};
+
+#define NRF70_CHG_VIF_IFTYPE BIT(0)
+#define NRF70_CHG_VIF_USE_4ADDR BIT(1)
+struct __packed nrf70_cmd_chg_vif_attr {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ struct __packed {
+ s32 iftype;
+ s32 use_4addr;
+ } info;
+};
+
+#define NRF70_ADD_VIF_USE_4ADDR BIT(0)
+#define NRF70_ADD_VIF_HWADDR BIT(1)
+#define NRF70_ADD_VIF_IFTYPE BIT(2)
+#define NRF70_ADD_VIF_IFNAME BIT(3)
+struct __packed nrf70_cmd_add_vif {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ struct __packed {
+ s32 iftype;
+ s32 use_4addr;
+ u32 mon_flags;
+ u8 hwaddr[ETH_ALEN];
+ s8 ifacename[IFNAMSIZ];
+ } info;
+};
+
+#define NRF70_FRAME_MATCH_MAX_LEN 8
+struct __packed nrf70_cmd_mgmt_frame_reg {
+ struct nrf70_umac_header header;
+ struct __packed {
+ u16 type;
+ u32 match_len;
+ u8 match[NRF70_FRAME_MATCH_MAX_LEN];
+ } info;
+};
+
+#define NRF70_KEY_INFO_KEY_SZ 256
+#define NRF70_KEY_INFO_SEQ_SZ 256
+#define NRF70_KEY_INFO_FLAG_DEFAULT BIT(0)
+#define NRF70_KEY_INFO_FLAG_DEFAULT_MGMT BIT(2)
+#define NRF70_KEY_INFO_FLAG_DEFAULT_UNICAST BIT(3)
+#define NRF70_KEY_INFO_FLAG_DEFAULT_MULTICAST BIT(4)
+#define NRF70_KEY_INFO_KEY BIT(0)
+#define NRF70_KEY_INFO_KEY_TYPE BIT(1)
+#define NRF70_KEY_INFO_KEY_IDX BIT(2)
+#define NRF70_KEY_INFO_SEQ BIT(3)
+#define NRF70_KEY_INFO_CIPHER_SUITE BIT(4)
+#define NRF70_KEY_INFO_KEY_INFO BIT(5)
+struct __packed nrf70_key_info {
+ u32 valid_fields;
+ u32 cipher_suite;
+ u16 wifi_flags;
+ struct __packed {
+ s32 type;
+ u32 len;
+ u8 data[NRF70_KEY_INFO_KEY_SZ];
+ } key;
+ struct __packed {
+ s32 len;
+ u8 data[NRF70_KEY_INFO_SEQ_SZ];
+ } seq;
+ u8 key_idx;
+};
+
+#define NRF70_KEY_HWADDR BIT(0)
+struct __packed nrf70_cmd_key {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ struct nrf70_key_info info;
+ u8 hwaddr[ETH_ALEN];
+};
+
+struct __packed nrf70_cmd_set_key {
+ struct nrf70_umac_header header;
+ struct nrf70_key_info info;
+};
+
+#define NRF70_AUTHTYPE_OPEN_SYSTEM 0
+#define NRF70_AUTHTYPE_SHARED_KEY 1
+#define NRF70_AUTHTYPE_FT 2
+#define NRF70_AUTHTYPE_NETWORK_EAP 3
+#define NRF70_AUTHTYPE_SAE 4
+#define NRF70_AUTHTYPE_AUTOMATIC 7
+
+#define NRF70_AUTH_INFO_SAE_SZ 256
+#define NRF70_AUTH_KEY_INFO BIT(0)
+#define NRF70_AUTH_BSSID BIT(1)
+#define NRF70_AUTH_FREQ BIT(2)
+#define NRF70_AUTH_SSID BIT(3)
+#define NRF70_AUTH_IE BIT(4)
+#define NRF70_AUTH_SAE BIT(5)
+struct __packed nrf70_cmd_auth {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ struct __packed {
+ u32 frequency;
+ u16 wifi_flags;
+ s32 auth_type;
+ struct nrf70_key_info key_info;
+ struct nrf70_ssid ssid;
+ struct nrf70_ie ie;
+ struct __packed {
+ s32 len;
+ u8 data[NRF70_AUTH_INFO_SAE_SZ];
+ } sae;
+ u8 bssid[ETH_ALEN];
+ s32 scan_width;
+ s32 signal;
+ s32 from_beacon;
+ struct nrf70_ie bss_ie;
+ u16 capability;
+ u16 beacon_interval;
+ u64 tsf;
+ } info;
+};
+
+#define NRF70_CONNECT_HWADDR BIT(0)
+#define NRF70_CONNECT_FREQ BIT(2)
+#define NRF70_CONNECT_SSID BIT(5)
+#define NRF70_CONNECT_WPA_IE BIT(6)
+#define NRF70_CONNECT_WPA_VERSIONS BIT(7)
+#define NRF70_CONNECT_CIPHER_PAIRWISE BIT(8)
+#define NRF70_CONNECT_CIPHER_GROUP BIT(9)
+#define NRF70_CONNECT_AKM_SUITES BIT(10)
+#define NRF70_CONNECT_MFP BIT(11)
+#define NRF70_CONNECT_CONTROL_PORT_ETHER_TYPE BIT(12)
+#define NRF70_CONNECT_CONTROL_PORT_NO_ENCRYPT BIT(13)
+#define NRF70_CONNECT_FLAGS_USE_RRM BIT(14)
+#define NRF70_HT_VHT_CAP_MAX_SZ 256
+struct __packed nrf70_connect_info {
+ u32 valid_fields;
+ u32 frequency;
+ u32 freq_hint;
+ u32 wpa_versions;
+ s32 num_cipher_suites_pairwise;
+ u32 cipher_suites_pairwise[7];
+ u32 cipher_suite_group;
+ u32 num_akm_suites;
+ u32 akm_suites[2];
+ s32 use_mfp;
+ u32 wifi_flags;
+ u16 bg_scan_period;
+ u8 hwaddr[ETH_ALEN];
+ u8 hwaddr_hint[ETH_ALEN];
+ struct nrf70_ssid ssid;
+ struct nrf70_ie wpa_ie;
+ struct __packed {
+ u32 valid_fields;
+ u16 flags;
+ u8 ht_capa[NRF70_HT_VHT_CAP_MAX_SZ];
+ u8 ht_capa_mask[NRF70_HT_VHT_CAP_MAX_SZ];
+ u8 vht_capa[NRF70_HT_VHT_CAP_MAX_SZ];
+ u8 vht_capa_mask[NRF70_HT_VHT_CAP_MAX_SZ];
+ } ht_vht_cap;
+ u16 control_port_ethertype;
+ u8 control_port_no_encrypt;
+ s8 control_port;
+ u8 prev_bssid[ETH_ALEN];
+ u16 maxidle_insec;
+};
+
+struct __packed nrf70_cmd_assoc {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ struct nrf70_connect_info info;
+ u8 hwaddr[ETH_ALEN];
+};
+
+#define NRF70_DISCONN_FLAGS_LOCAL_STATE_CHANGE BIT(0)
+#define NRF70_DISCONN_HWADDR BIT(0)
+struct __packed nrf70_cmd_disconn {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ struct __packed {
+ u16 flags;
+ u16 reason;
+ u8 hwaddr[ETH_ALEN];
+ } info;
+};
+
+#define NRF70_FREQ_PARAMS_FREQ BIT(0)
+#define NRF70_FREQ_PARAMS_CHAN_WIDTH BIT(1)
+#define NRF70_FREQ_PARAMS_CENTER_FREQ1 BIT(2)
+#define NRF70_FREQ_PARAMS_CENTER_FREQ2 BIT(3)
+#define NRF70_FREQ_PARAMS_CHAN_TYPE BIT(4)
+#define NRF70_CHAN_NO_HT 0
+#define NRF70_CHAN_HT20 1
+#define NRF70_CHAN_HT40MINUS 2
+#define NRF70_CHAN_HT40PLUS 3
+struct __packed nrf70_freq_params {
+ u32 valid_fields;
+ s32 frequency;
+ s32 channel_width;
+ s32 center_freq1;
+ s32 center_freq2;
+ s32 channel_type;
+};
+
+#define NRF70_MGMT_TX_FREQ BIT(0)
+#define NRF70_MGMT_TX_DURATION BIT(1)
+#define NRF70_MGMT_TX_SET_FRAME_FREQ BIT(2)
+#define NRF70_MGMT_TX_FLAGS_OFFCHAN_TX BIT(0)
+#define NRF70_MGMT_TX_FLAGS_NO_CCK_RATE BIT(1)
+#define NRF70_MGMT_TX_FLAGS_NO_ACK BIT(2)
+#define NRF70_MGMT_TX_FREQ_MASK GENMASK(4, 0)
+struct __packed nrf70_cmd_mgmt_tx {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ struct __packed {
+ u32 wifi_flags;
+ u32 frequency;
+ u32 dur;
+ struct nrf70_frame frame;
+ struct nrf70_freq_params freq_params;
+ u64 cookie;
+ } info;
+};
+
+struct __packed nrf70_sta_flag_update {
+ u32 mask;
+ u32 set;
+};
+
+#define NRF70_CHG_STA_SUPP_RATES BIT(0)
+#define NRF70_CHG_STA_AID BIT(1)
+#define NRF70_CHG_STA_STA_CAPAB BIT(3)
+#define NRF70_CHG_STA_EXT_CAPAB BIT(4)
+#define NRF70_CHG_STA_HT_CAP BIT(6)
+#define NRF70_CHG_STA_VHT_CAP BIT(7)
+#define NRF70_CHG_STA_OPMODE_NOTIF BIT(9)
+#define NRF70_CHG_STA_SUP_CHANS BIT(10)
+#define NRF70_CHG_STA_OPER_CLASSES BIT(11)
+#define NRF70_CHG_STA_FLAGS2 BIT(12)
+#define NRF70_CHG_STA_WME_UAPSD_QUEUES BIT(13)
+#define NRF70_CHG_STA_WME_MAX_SP BIT(14)
+#define NRF70_CHG_STA_LISTEN_INTERVAL BIT(15)
+struct __packed nrf70_cmd_chg_sta {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ s32 listen_interval;
+ u32 sta_vlan;
+ u16 aid;
+ u16 peer_aid;
+ u16 sta_capability;
+ u16 spare;
+ struct __packed {
+ u32 valid_fields;
+ s32 band;
+ s32 num_rates;
+ u8 rates[60];
+ } supp_rates;
+ u32 ext_cap_len;
+ u8 ext_cap[32];
+ u32 sup_chans_len;
+ u8 sup_chans[64];
+ u32 sup_oper_classes_len;
+ u8 sup_oper_classes[64];
+ struct nrf70_sta_flag_update sta_flags2;
+ u8 ht_cap[NRF70_HT_VHT_CAP_MAX_SZ];
+ u8 vht_cap[NRF70_HT_VHT_CAP_MAX_SZ];
+ u8 hwaddr[ETH_ALEN];
+ u8 opmode_notif;
+ u8 wme_uapsd_queues;
+ u8 wme_max_sp;
+};
+
+#define NRF70_DEL_STA_HWADDR BIT(0)
+#define NRF70_DEL_STA_MGMT_SUBTYPE BIT(1)
+#define NRF70_DEL_STA_REASON BIT(2)
+struct __packed nrf70_cmd_del_sta {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ u8 hwaddr[ETH_ALEN];
+ u8 mgmt_subtype;
+ u16 reason;
+};
+
+struct __packed nrf70_wiphy_info {
+ u32 rts_threshold;
+ u32 frag_threshold;
+ u32 antenna_tx;
+ u32 antenna_rx;
+ struct nrf70_freq_params freq_params;
+ struct __packed {
+ u16 toxp;
+ u16 cwmin;
+ u16 cwmax;
+ u8 aifs;
+ u8 ac;
+ } txq_params;
+ struct __packed {
+ u32 valid_fields;
+ s32 type;
+ s32 power_level;
+ } tx_power_settings;
+ u8 retry_short;
+ u8 retry_long;
+ u8 coverage_class;
+ s8 wiphy_name[32];
+};
+
+#define NRF70_SET_WIPHY_FREQ BIT(0)
+#define NRF70_SET_WIPHY_RTS_THRESHOLD BIT(2)
+#define NRF70_SET_WIPHY_FRAG_THRESHOLD BIT(3)
+#define NRF70_SET_WIPHY_RETRY_SHORT BIT(7)
+#define NRF70_SET_WIPHY_RETRY_LONG BIT(8)
+#define NRF70_SET_WIPHY_COVERAGE_CLASS BIT(9)
+struct __packed nrf70_cmd_set_wiphy {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ struct nrf70_wiphy_info info;
+};
+
+#define NRF70_BEACON_DATA_MAX_HEAD_LEN 256
+#define NRF70_BEACON_DATA_MAX_TAIL_LEN 512
+#define NRF70_BEACON_DATA_MAX_PROBE_RESP_LEN 400
+struct __packed nrf70_beacon_data {
+ u32 head_len;
+ u32 tail_len;
+ u32 probe_resp_len;
+ u8 head[NRF70_BEACON_DATA_MAX_HEAD_LEN];
+ u8 tail[NRF70_BEACON_DATA_MAX_TAIL_LEN];
+ u8 probe_resp[NRF70_BEACON_DATA_MAX_PROBE_RESP_LEN];
+};
+
+#define NRF70_SET_BSS_CTS BIT(0)
+#define NRF70_SET_BSS_PREAMBLE BIT(1)
+#define NRF70_SET_BSS_SLOT BIT(2)
+#define NRF70_SET_BSS_HT_OPMODE BIT(3)
+#define NRF70_SET_BSS_AP_ISOLATE BIT(4)
+#define NRF70_SET_BSS_P2P_CTWINDOW BIT(5)
+#define NRF70_SET_BSS_P2P_OPPPS BIT(6)
+#define NRF70_BSS_INFO_MAX_BASIC_RATES 32
+struct __packed nrf70_cmd_set_bss {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ struct __packed {
+ u32 p2p_go_ctwindow;
+ u32 p2p_opp_ps;
+ u32 num_basic_rates;
+ u16 ht_opmode;
+ u8 cts;
+ u8 preamble;
+ u8 slot;
+ u8 ap_isolate;
+ u8 basic_rates[NRF70_BSS_INFO_MAX_BASIC_RATES];
+ } info;
+};
+
+#define NRF70_START_AP_BEACON_INTERVAL BIT(0)
+#define NRF70_START_AP_AUTH_TYPE BIT(1)
+#define NRF70_START_AP_VERSIONS BIT(2)
+#define NRF70_START_AP_CIPHER_SUITE_GROUP BIT(3)
+#define NRF70_START_AP_INACTIVITY_TIMEOUT BIT(4)
+#define NRF70_START_AP_FREQ_PARAMS BIT(5)
+#define NRF70_START_AP_FLAG_PRIVACY BIT(0)
+#define NRF70_START_AP_FLAG_NO_ENCRYPT BIT(1)
+#define NRF70_START_AP_FLAG_P2P_CTWINDOW BIT(6)
+#define NRF70_START_AP_FLAG_P2P_OPPPS BIT(7)
+struct __packed nrf70_cmd_start_ap {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ struct __packed {
+ u16 beacon_interval;
+ u8 dtim_period;
+ s32 hidden_ssid;
+ s32 auth_type;
+ s32 smps_mode;
+ u32 flags;
+ struct nrf70_beacon_data beacon_data;
+ struct nrf70_ssid ssid;
+ struct nrf70_connect_info connect_info;
+ struct nrf70_freq_params freq_params;
+ u16 inactivity_timeout;
+ u8 p2p_go_ctwindow;
+ u8 p2p_opp_ps;
+ } info;
+};
+
+struct __packed nrf70_cmd_set_beacon {
+ struct nrf70_umac_header header;
+ struct nrf70_beacon_data beacon_data;
+};
+
+struct __packed nrf70_cmd_get_sta {
+ struct nrf70_umac_header header;
+ u8 hwaddr[ETH_ALEN];
+};
+
+struct __packed nrf70_cmd_set_qos_map {
+ struct nrf70_umac_header header;
+ struct __packed {
+ u32 len;
+ u8 data[256];
+ } map_info;
+};
+
+struct __packed nrf70_display_results {
+ struct nrf70_ssid ssid;
+ u8 hwaddr[ETH_ALEN];
+ s32 band;
+ u32 chan;
+ u8 protocol_flags;
+ s32 security_type;
+ u16 beacon_interval;
+ u16 capability;
+ struct __packed {
+ u32 type;
+ union __packed {
+ u32 mbm_signal;
+ u8 unspec_signal;
+ };
+ } signal;
+ u8 reserved[4];
+};
+
+#define NRF70_DISP_SCAN_RES_SZ 8
+struct __packed nrf70_event_scan_display_results {
+ struct nrf70_umac_header header;
+ u8 bss_count;
+ struct nrf70_display_results results[NRF70_DISP_SCAN_RES_SZ];
+};
+
+struct __packed nrf70_event_scan_done {
+ struct nrf70_umac_header header;
+ u32 status;
+ u32 scan_type;
+};
+
+struct __packed nrf70_event_get_reg {
+ struct nrf70_umac_header header;
+ u8 alpha2[NRF70_COUNTRY_CODE_LEN];
+ u32 num_chans;
+ struct __packed {
+ u32 center_freq;
+ u32 max_power;
+ u8 supported;
+ u8 passive_channel;
+ u8 dfs;
+ } chan_info[];
+};
+
+#define NRF70_EVENT_MLME_TIMED_OUT BIT(0)
+#define NRF70_EVENT_MLME_ACK BIT(1)
+struct __packed nrf70_event_mlme {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ u32 frequency;
+ u32 rx_signal_dbm;
+ u32 wifi_flags;
+ u64 cookie;
+ struct nrf70_frame frame;
+ u8 bssid[ETH_ALEN];
+ u8 wme_uapsd_queues;
+ u32 req_ie_len;
+ u8 req_ie[];
+};
+
+struct __packed nrf70_event_iface_update {
+ struct nrf70_umac_header header;
+ s32 status;
+};
+
+struct __packed nrf70_event_cookie_resp {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ u64 host_cookie;
+ u64 cookie;
+ u8 hwaddr[ETH_ALEN];
+};
+
+#define NRF70_RATE_INFO_BITRATE BIT(0)
+#define NRF70_RATE_INFO_BITRATE_COMPAT BIT(1)
+#define NRF70_RATE_INFO_MCS BIT(2)
+#define NRF70_RATE_INFO_VHT_MCS BIT(3)
+#define NRF70_RATE_INFO_VHT_NSS BIT(4)
+
+#define NRF70_RATE_INFO_0_MHZ_WIDTH BIT(0)
+#define NRF70_RATE_INFO_5_MHZ_WIDTH BIT(1)
+#define NRF70_RATE_INFO_10_MHZ_WIDTH BIT(2)
+#define NRF70_RATE_INFO_40_MHZ_WIDTH BIT(3)
+#define NRF70_RATE_INFO_80_MHZ_WIDTH BIT(4)
+#define NRF70_RATE_INFO_160_MHZ_WIDTH BIT(5)
+#define NRF70_RATE_INFO_SHORT_GI BIT(6)
+#define NRF70_RATE_INFO_80P80_MHZ_WIDTH BIT(7)
+struct __packed nrf70_rate_info {
+ u32 valid_fields;
+ u32 bitrate;
+ u16 bitrate_compat;
+ u8 mcs;
+ u8 vht_mcs;
+ u8 vht_nss;
+ u32 flags;
+};
+
+#define NRF70_STA_INFO_CONNECTED_TIME BIT(0)
+#define NRF70_STA_INFO_INACTIVE_TIME BIT(1)
+#define NRF70_STA_INFO_RX_BYTES BIT(2)
+#define NRF70_STA_INFO_TX_BYTES BIT(3)
+#define NRF70_STA_INFO_CHAIN_SIGNAL BIT(4)
+#define NRF70_STA_INFO_CHAIN_SIGNAL_AVG BIT(5)
+#define NRF70_STA_INFO_TX_BITRATE BIT(6)
+#define NRF70_STA_INFO_RX_BITRATE BIT(7)
+#define NRF70_STA_INFO_STA_FLAGS BIT(8)
+#define NRF70_STA_INFO_LLID BIT(9)
+#define NRF70_STA_INFO_PLID BIT(10)
+#define NRF70_STA_INFO_PLINK_STATE BIT(11)
+#define NRF70_STA_INFO_SIGNAL BIT(12)
+#define NRF70_STA_INFO_SIGNAL_AVG BIT(13)
+#define NRF70_STA_INFO_RX_PACKETS BIT(14)
+#define NRF70_STA_INFO_TX_PACKETS BIT(15)
+#define NRF70_STA_INFO_TX_RETRIES BIT(16)
+#define NRF70_STA_INFO_TX_FAILED BIT(17)
+#define NRF70_STA_INFO_EXPECTED_THROUGHPUT BIT(18)
+#define NRF70_STA_INFO_BEACON_LOSS_COUNT BIT(19)
+#define NRF70_STA_INFO_LOCAL_PM BIT(20)
+#define NRF70_STA_INFO_PEER_PM BIT(21)
+#define NRF70_STA_INFO_NONPEER_PM BIT(22)
+#define NRF70_STA_INFO_T_OFFSET BIT(23)
+#define NRF70_STA_INFO_RX_DROPPED_MISC BIT(24)
+#define NRF70_STA_INFO_RX_BEACON BIT(25)
+#define NRF70_STA_INFO_RX_BEACON_SIGNAL_AVG BIT(26)
+#define NRF70_STA_INFO_BSS_PARAMS BIT(27)
+struct __packed nrf70_event_new_station {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ u8 wme;
+ u8 sta_legacy;
+ u8 hwaddr[ETH_ALEN];
+ u32 generation;
+ struct __packed {
+ u32 valid_fields;
+ u32 connected_time;
+ u32 inactive_time;
+ u32 rx_bytes;
+ u32 tx_bytes;
+ struct __packed {
+ u32 signal_mask;
+ u8 signal[IEEE80211_MAX_CHAINS];
+ u32 signal_avg_mask;
+ u8 signal_avg[IEEE80211_MAX_CHAINS];
+ } chain;
+ struct nrf70_rate_info tx_bitrate;
+ struct nrf70_rate_info rx_bitrate;
+ u16 llid; /* unused */
+ u16 plid; /* unused */
+ u8 plink_state; /* unused */
+ s32 signal;
+ s32 signal_avg;
+ u32 rx_packets;
+ struct __packed {
+ u32 packets;
+ u32 retries;
+ u32 failed;
+ } tx;
+ u32 expected_throughput;
+ u32 beacon_loss_count;
+ u32 local_pm; /* unused */
+ u32 peer_pm; /* unused */
+ u32 nonpeer_pm; /* unused */
+ struct nrf70_sta_flag_update sta_flags;
+ u64 t_offset;
+ u64 rx_dropped_misc;
+ u64 rx_beacon;
+ s64 rx_beacon_signal_avg;
+ struct __packed {
+ u8 flags;
+ u8 dtim_period;
+ u16 beacon_interval;
+ } bss_param;
+ } sta_info;
+ struct nrf70_ie assoc_req_ies;
+};
+
+struct __packed nrf70_event_get_chan {
+ struct nrf70_umac_header header;
+ struct __packed {
+ s32 band;
+ u32 center_freq;
+ u32 flags;
+ s32 max_antenna_gain;
+ s32 max_power;
+ s32 max_reg_power;
+ u32 orig_flags;
+ s32 orig_mag;
+ s32 orig_mpwr;
+ u16 hw_value;
+ s8 beacon_found;
+ } chan;
+ s32 width;
+ u32 center_freq1;
+ u32 center_freq2;
+};
+
+#define NRF70_SET_REG_ALPHA2 BIT(0)
+#define NRF70_SET_REG_USER_REG_FORCE BIT(2)
+struct __packed nrf70_cmd_set_reg {
+ struct nrf70_umac_header header;
+ u32 valid_fields;
+ u32 user_reg_hint_type;
+ u8 alpha2[NRF70_COUNTRY_CODE_LEN];
+};
+
+struct __packed nrf70_event_reg_change {
+ struct nrf70_umac_header header;
+ u16 flags;
+ s32 intr;
+ s8 reg_type;
+ u8 alpha2[NRF70_COUNTRY_CODE_LEN];
+};
+
+#endif /* _NRF70_CMDS_H */
diff --git a/drivers/net/wireless/nordic/nrf70_rf_params.h b/drivers/net/wireless/nordic/nrf70_rf_params.h
new file mode 100644
index 000000000000..d6a746783ded
--- /dev/null
+++ b/drivers/net/wireless/nordic/nrf70_rf_params.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Conclusive Engineering Sp. z o. o.
+ */
+
+#ifndef _NRF70_RF_PARAMS_H
+#define _NRF70_RF_PARAMS_H
+
+#define NRF70_RESERVED 0x0
+
+#define NRF70_QFN_XO_VAL 0x2a
+#define NRF70_CSP_XO_VAL 0x2a
+
+#define NRF70_PD_ADJUST_VAL 0x0
+
+#define NRF70_SYSTEM_OFFSET_LB 0x3
+#define NRF70_SYSTEM_OFFSET_HB_CHAN_LOW 0x3
+#define NRF70_SYSTEM_OFFSET_HB_CHAN_MID 0x3
+#define NRF70_SYSTEM_OFFSET_HB_CHAN_HIGH 0x3
+
+#define NRF70_QFN_MAX_TX_PWR_DSSS 0x48
+#define NRF70_QFN_MAX_TX_PWR_LB_MCS7 0x40
+#define NRF70_QFN_MAX_TX_PWR_LB_MCS0 0x40
+#define NRF70_QFN_MAX_TX_PWR_HB_LOW_CHAN_MCS7 0x34
+#define NRF70_QFN_MAX_TX_PWR_HB_MID_CHAN_MCS7 0x34
+#define NRF70_QFN_MAX_TX_PWR_HB_HIGH_CHAN_MCS7 0x30
+#define NRF70_QFN_MAX_TX_PWR_HB_LOW_CHAN_MCS0 0x38
+#define NRF70_QFN_MAX_TX_PWR_HB_MID_CHAN_MCS0 0x34
+#define NRF70_QFN_MAX_TX_PWR_HB_HIGH_CHAN_MCS0 0x30
+
+#define NRF70_RX_GAIN_OFFSET_LB_CHAN 0x0
+#define NRF70_RX_GAIN_OFFSET_HB_LOW_CHAN 0x0
+#define NRF70_RX_GAIN_OFFSET_HB_MID_CHAN 0x0
+#define NRF70_RX_GAIN_OFFSET_HB_HIGH_CHAN 0x0
+
+#define NRF70_QFN_MAX_CHIP_TEMP 0x43
+#define NRF70_QFN_MIN_CHIP_TEMP 0x7
+#define NRF70_QFN_LB_MAX_PWR_BKF_HI_TEMP 0xfc
+#define NRF70_QFN_LB_MAX_PWR_BKF_LOW_TEMP 0x0
+#define NRF70_QFN_HB_MAX_PWR_BKF_HI_TEMP 0xf8
+#define NRF70_QFN_HB_MAX_PWR_BKF_LOW_TEMP 0xfc
+#define NRF70_QFN_LB_VBT_LT_VLOW 0xfc
+#define NRF70_QFN_HB_VBT_LT_VLOW 0xf8
+#define NRF70_QFN_LB_VBT_LT_LOW 0x0
+#define NRF70_QFN_HB_VBT_LT_LOW 0xfc
+
+#define NRF70_PHY_CALIB_FLAG_RXDC BIT(0)
+#define NRF70_PHY_CALIB_FLAG_TXDC BIT(1)
+#define NRF70_PHY_CALIB_FLAG_TXPOW 0
+#define NRF70_PHY_CALIB_FLAG_TXIQ BIT(3)
+#define NRF70_PHY_CALIB_FLAG_RXIQ BIT(4)
+#define NRF70_PHY_CALIB_FLAG_DPD BIT(5)
+
+#define NRF70_PHY_SCAN_CALIB_FLAG_RXDC (1 << 16)
+#define NRF70_PHY_SCAN_CALIB_FLAG_TXDC (2 << 16)
+#define NRF70_PHY_SCAN_CALIB_FLAG_TXPOW (0 << 16)
+#define NRF70_PHY_SCAN_CALIB_FLAG_TXIQ (0 << 16)
+#define NRF70_PHY_SCAN_CALIB_FLAG_RXIQ (0 << 16)
+#define NRF70_PHY_SCAN_CALIB_FLAG_DPD (0 << 16)
+
+#define NRF70_DEF_PHY_CALIB (NRF70_PHY_CALIB_FLAG_RXDC | \
+ NRF70_PHY_CALIB_FLAG_TXDC | \
+ NRF70_PHY_CALIB_FLAG_RXIQ | \
+ NRF70_PHY_CALIB_FLAG_TXIQ | \
+ NRF70_PHY_CALIB_FLAG_TXPOW | \
+ NRF70_PHY_CALIB_FLAG_DPD | \
+ NRF70_PHY_SCAN_CALIB_FLAG_RXDC | \
+ NRF70_PHY_SCAN_CALIB_FLAG_TXDC | \
+ NRF70_PHY_SCAN_CALIB_FLAG_RXIQ | \
+ NRF70_PHY_SCAN_CALIB_FLAG_TXIQ | \
+ NRF70_PHY_SCAN_CALIB_FLAG_TXPOW | \
+ NRF70_PHY_SCAN_CALIB_FLAG_DPD)
+
+/* Temperature based calibration params. */
+#define NRF70_DEF_PHY_TEMP_CALIB (NRF70_PHY_CALIB_FLAG_RXDC | \
+ NRF70_PHY_CALIB_FLAG_TXDC | \
+ NRF70_PHY_CALIB_FLAG_RXIQ | \
+ NRF70_PHY_CALIB_FLAG_TXIQ | \
+ NRF70_PHY_CALIB_FLAG_TXPOW | \
+ NRF70_PHY_CALIB_FLAG_DPD)
+
+/* Convert from millivolts to vbat threshold. Value must be above 2.5 V. */
+#define NRF70_VBAT_MV_TO_VTH(n) (((n) - 2500) / 70)
+
+#define NRF70_PHY_PARAMS \
+ 0x00, 0x70, 0x77, 0x00, 0x3f, 0x03, 0x24, 0x24, 0x00, 0x10, 0x00, \
+ 0x00, 0x28, 0x00, 0x32, 0x35, 0x00, 0x00, 0x0c, 0xf0, 0x08, 0x08, \
+ 0x7d, 0x81, 0x05, 0x01, 0x00, 0x71, 0x63, 0x03, 0x00, 0xee, 0xd5, \
+ 0x01, 0x00, 0x1f, 0x6f, 0x00, 0x00, 0x3b, 0x35, 0x01, 0x00, 0xf5, \
+ 0x2e, 0x00, 0x00, 0xe3, 0x5e, 0x00, 0x00, 0xb7, 0xb6, 0x00, 0x00, \
+ 0x66, 0xef, 0xfe, 0xff, 0xb5, 0xf6, 0x00, 0x00, 0x89, 0x62, 0x00, \
+ 0x00, 0x7a, 0x84, 0x02, 0x00, 0xe2, 0x8f, 0xfc, 0xff, 0x08, 0x08, \
+ 0x08, 0x08, 0x04, 0x08, 0x12, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa1, \
+ 0xa1, 0x01, 0x78, 0x00, 0x00, 0x00, 0x08, 0x00, 0x50, 0x00, 0x3b, \
+ 0x02, 0x07, 0x26, 0x18, 0x18, 0x18, 0x18, 0x1a, 0x12, 0x0a, 0x14, \
+ 0x0e, 0x06, 0x00
+
+#endif /* _NRF70_RF_PARAMS_H */
--
2.49.0
Powered by blists - more mailing lists