* [RFC PATCH 0/2] Nordic nRF70 series
@ 2025-03-24 21:10 Artur Rojek
2025-03-24 21:10 ` [RFC PATCH 1/2] net: wireless: Add Nordic nRF70 series Wi-Fi driver Artur Rojek
2025-03-24 21:10 ` [RFC PATCH 2/2] dt-bindings: wireless: Document Nordic nRF70 bindings Artur Rojek
0 siblings, 2 replies; 8+ messages in thread
From: Artur Rojek @ 2025-03-24 21:10 UTC (permalink / raw)
To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-wireless, devicetree, linux-kernel, Jakub Klama,
Wojciech Kloska, Ulf Axelsson, Artur Rojek
Hi all,
this patchset introduces support for Nordic Semiconductor nRF70 series
of wireless companion IC.
Nordic nRF70 series are FullMAC devices, which communicate over SPI or
QSPI interface. This driver provides support for STA, AP and monitor
modes, in combination with up to 2 VIFs. Throughput of up to ~16 Mbit/s
has been observed while using QSPI.
Patch [1/2] adds the nRF70 driver.
Patch [2/2] provides related Devicetree bindings documentation.
As this is our first Linux WLAN driver, I am tagging this as RFC and
looking for any sort of feedback, before I turn it into a regular
submission.
In the meantime, here are some pending questions that I have:
1) Nordic gave us permission to upstream the firmware blob [1] required
to use this driver. As that needs to go through separate
linux-firmware repository and is subject to different licensing,
should I try to upstream it in parallel with this series, or does it
need to wait until the kernel driver gets in?
2) In AP mode, for each connected peer I maintain a pending queue for TX
skbs that can't be transmitted while the peer is in power save mode.
I then use a wiphy_work (nrf70_pending_worker) to move the collected
skbs into a single hw queue once the peer is able to receive again.
This means there can be multiple workers putting skbs onto the hw
queue at any given time. As this scheme relies on the wiphy_work
workqueue, can I assume that multiple workers will be able to run in
parallel, even on a system with a single CPU? If not, what would be
a better solution to the above problem?
3) nRF70 hardware communicates using byte packed, little-endian
payloads (documented in nrf70_cmds.h). As these can get very large
and complicated, I decided against writing some sort of endianess
conversion scheme, and simply dropped big endian support by this
driver. Is that acceptable?
4) Please put particular attention to the wiphy configuration. I am not
100% confident I got all the flags/features/band caps right.
PS. checkpatch.pl spits out what I believe to be a false positive:
> ERROR: Macros with complex values should be enclosed in parentheses
> #5914: FILE: drivers/net/wireless/nordic/nrf70_rf_params.h:85:
> +#define NRF70_PHY_PARAMS \
The above define is used for generating the nrf7002_qfn_rf_params array,
and as such cannot be enclosed in parentheses.
Cheers,
Artur
[1] https://github.com/nrfconnect/sdk-nrfxlib/raw/refs/heads/main/nrf_wifi/bin/ncs/default/nrf70.bin
Artur Rojek (2):
net: wireless: Add Nordic nRF70 series Wi-Fi driver
dt-bindings: wireless: Document Nordic nRF70 bindings
.../bindings/net/wireless/nordic,nrf70.yaml | 56 +
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 +
8 files changed, 5907 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/wireless/nordic,nrf70.yaml
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
--
2.49.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [RFC PATCH 1/2] net: wireless: Add Nordic nRF70 series Wi-Fi driver
2025-03-24 21:10 [RFC PATCH 0/2] Nordic nRF70 series Artur Rojek
@ 2025-03-24 21:10 ` Artur Rojek
2025-04-01 14:11 ` Sascha Hauer
2025-04-29 5:04 ` Eric Biggers
2025-03-24 21:10 ` [RFC PATCH 2/2] dt-bindings: wireless: Document Nordic nRF70 bindings Artur Rojek
1 sibling, 2 replies; 8+ messages in thread
From: Artur Rojek @ 2025-03-24 21:10 UTC (permalink / raw)
To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-wireless, devicetree, linux-kernel, Jakub Klama,
Wojciech Kloska, Ulf Axelsson, Artur Rojek
Introduce support for Nordic Semiconductor nRF70 series wireless
companion IC.
Signed-off-by: Artur Rojek <artur@conclusive.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@conclusive.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
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [RFC PATCH 2/2] dt-bindings: wireless: Document Nordic nRF70 bindings
2025-03-24 21:10 [RFC PATCH 0/2] Nordic nRF70 series Artur Rojek
2025-03-24 21:10 ` [RFC PATCH 1/2] net: wireless: Add Nordic nRF70 series Wi-Fi driver Artur Rojek
@ 2025-03-24 21:10 ` Artur Rojek
2025-03-24 23:05 ` Rob Herring (Arm)
1 sibling, 1 reply; 8+ messages in thread
From: Artur Rojek @ 2025-03-24 21:10 UTC (permalink / raw)
To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-wireless, devicetree, linux-kernel, Jakub Klama,
Wojciech Kloska, Ulf Axelsson, Artur Rojek
Add a documentation file to describe the Device Tree bindings for the
Nordic Semiconductor nRF70 series wireless companion IC.
Signed-off-by: Artur Rojek <artur@conclusive.pl>
---
.../bindings/net/wireless/nordic,nrf70.yaml | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/wireless/nordic,nrf70.yaml
diff --git a/Documentation/devicetree/bindings/net/wireless/nordic,nrf70.yaml b/Documentation/devicetree/bindings/net/wireless/nordic,nrf70.yaml
new file mode 100644
index 000000000000..1c61f7bdbf8a
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/wireless/nordic,nrf70.yaml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/wireless/nordic,nrf70.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Nordic Semiconductor nRF70 series wireless companion IC
+
+maintainers:
+ - Artur Rojek <artur@conclusive.tech>
+
+properties:
+ compatible:
+ const: nordic,nrf70
+
+ req:
+ maxItems: 1
+
+ irq-gpios:
+ maxItems: 1
+ description: HOST_IRQ line, used for host processor interrupt requests.
+
+ bucken-gpios:
+ maxItems: 1
+ description: BUCKEN line, used for I/O voltage control.
+
+ iovdd-gpios:
+ maxItems: 1
+ description: External, GPIO-driven switch, found in some nRF70 based board
+ designs, and used together with BUCKEN for I/O voltage control. Optional.
+
+required:
+ - compatible
+ - reg
+ - irq-gpios
+ - bucken-gpios
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ nrf7002@0 {
+ compatible = "nordic,nrf70";
+ reg = <0>;
+ spi-max-frequency = <32000000>;
+ voltage-ranges = <1800 1800>;
+ bucken-gpios = <&gpio2 24 GPIO_ACTIVE_HIGH>;
+ irq-gpios = <&gpio2 13 GPIO_ACTIVE_HIGH>;
+ spi-rx-bus-width = <4>;
+ spi-tx-bus-width = <4>;
+ };
+ };
--
2.49.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [RFC PATCH 2/2] dt-bindings: wireless: Document Nordic nRF70 bindings
2025-03-24 21:10 ` [RFC PATCH 2/2] dt-bindings: wireless: Document Nordic nRF70 bindings Artur Rojek
@ 2025-03-24 23:05 ` Rob Herring (Arm)
0 siblings, 0 replies; 8+ messages in thread
From: Rob Herring (Arm) @ 2025-03-24 23:05 UTC (permalink / raw)
To: Artur Rojek
Cc: Krzysztof Kozlowski, devicetree, Jakub Klama, Johannes Berg,
Wojciech Kloska, Conor Dooley, linux-kernel, Ulf Axelsson,
linux-wireless
On Mon, 24 Mar 2025 22:10:45 +0100, Artur Rojek wrote:
> Add a documentation file to describe the Device Tree bindings for the
> Nordic Semiconductor nRF70 series wireless companion IC.
>
> Signed-off-by: Artur Rojek <artur@conclusive.pl>
> ---
> .../bindings/net/wireless/nordic,nrf70.yaml | 56 +++++++++++++++++++
> 1 file changed, 56 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/net/wireless/nordic,nrf70.yaml
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/net/wireless/nordic,nrf70.yaml: properties:compatible: 'anyOf' conditional failed, one must be fixed:
'req' is not one of ['$ref', 'additionalItems', 'additionalProperties', 'allOf', 'anyOf', 'const', 'contains', 'default', 'dependencies', 'dependentRequired', 'dependentSchemas', 'deprecated', 'description', 'else', 'enum', 'exclusiveMaximum', 'exclusiveMinimum', 'items', 'if', 'minItems', 'minimum', 'maxItems', 'maximum', 'multipleOf', 'not', 'oneOf', 'pattern', 'patternProperties', 'properties', 'required', 'then', 'typeSize', 'unevaluatedProperties', 'uniqueItems']
'type' was expected
from schema $id: http://devicetree.org/meta-schemas/keywords.yaml#
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/net/wireless/nordic,nrf70.yaml: 'oneOf' conditional failed, one must be fixed:
'unevaluatedProperties' is a required property
'additionalProperties' is a required property
hint: Either unevaluatedProperties or additionalProperties must be present
from schema $id: http://devicetree.org/meta-schemas/core.yaml#
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/net/wireless/nordic,nrf70.yaml: properties:compatible: Additional properties are not allowed ('req' was unexpected)
from schema $id: http://devicetree.org/meta-schemas/string-array.yaml#
doc reference errors (make refcheckdocs):
See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20250324211045.3508952-3-artur@conclusive.pl
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC PATCH 1/2] net: wireless: Add Nordic nRF70 series Wi-Fi driver
2025-03-24 21:10 ` [RFC PATCH 1/2] net: wireless: Add Nordic nRF70 series Wi-Fi driver Artur Rojek
@ 2025-04-01 14:11 ` Sascha Hauer
2025-04-18 12:46 ` Artur Rojek
2025-04-29 5:04 ` Eric Biggers
1 sibling, 1 reply; 8+ messages in thread
From: Sascha Hauer @ 2025-04-01 14:11 UTC (permalink / raw)
To: Artur Rojek
Cc: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-wireless, devicetree, linux-kernel, Jakub Klama,
Wojciech Kloska, Ulf Axelsson
Hi Artur,
Some review feedback inside.
As a general remark there are several functions returning 1 or -1 in
case of errors. Although they seem to be used only internally in the
driver it's far too easy to forward these errors to other kernel code
with future changes, so better return error codes instead.
On Mon, Mar 24, 2025 at 10:10:44PM +0100, Artur Rojek wrote:
> +
> +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 },
> +};
Should be const.
> +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) {
type >= ARRAY_SIZE(nrf_rpu_addr_lut)
> +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);
I think the loop break condition should be
!ret && i < NRF70_NUM_RX_BUFS
This is rather hard to read though, so I suggest a plain
for () {
ret = nrf70_init_rx();
if (ret)
return ret;
}
> +
> + 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;
This break is misaligned.
> + 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;
ditto
> + 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,
You could create defines for the array offsets instead of the values and use
C99 initializers here. For example
#define NRF70_QFN_XO_VAL_OFFSET 6
and then:
[NRF70_QFN_XO_VAL_OFFSET] = 0x2a,
That way the offsets are immediately clear without counting the array
members.
With this having defines for the values is IMO rather unnecessary then
as these are magic values anyway and usage of C99 initializers already
makes their meaning clear.
> + /* 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 struct ieee80211_rate nrf70_dsss_rates[] = {
Should be const.
> + { .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 = {
Should be const.
> + .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[] = {
const
> + 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[] = {
const
> + { .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 = {
const
> + .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 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) {
devm_gpiod_get() returns an error pointer in case of failure. You have
to check the return value with IS_ERR().
> + dev_err(dev, "Unable to find bucken-gpios property\n");
> + return -EINVAL;
> + }
You could use dev_err_probe() for returning an error from the probe
function.
> +
> + ret = gpiod_direction_output(priv->buck_en, 0);
> + if (ret) {
> + dev_err(dev, "Unable to set buck_en direction\n");
> + return -EIO;
> + }
Should this "bucken" GPIO rather be a regulator?
Is this really mandatory? It sounds like it could be hardwired to some
fixed voltage.
> +
> + priv->iovdd_en = devm_gpiod_get_optional(dev, "iovdd", 0);
Same here. Should this rather be a regulator?
> + 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;
> + }
Any particular reason you describe the interrupt as GPIO?
I would just use the interrupts / interrupt-parent binding.
> +
> + 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;
> +}
> +
Sascha
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC PATCH 1/2] net: wireless: Add Nordic nRF70 series Wi-Fi driver
2025-04-01 14:11 ` Sascha Hauer
@ 2025-04-18 12:46 ` Artur Rojek
2025-04-24 13:16 ` Sascha Hauer
0 siblings, 1 reply; 8+ messages in thread
From: Artur Rojek @ 2025-04-18 12:46 UTC (permalink / raw)
To: Sascha Hauer
Cc: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-wireless, devicetree, linux-kernel, Jakub Klama,
Wojciech Kloska, Ulf Axelsson
Hi Sascha,
thanks for the review, reply inline.
On Tue, Apr 1, 2025 at 4:11 PM Sascha Hauer <s.hauer@pengutronix.de> wrote:
>
> Hi Artur,
>
> Some review feedback inside.
>
> As a general remark there are several functions returning 1 or -1 in
> case of errors. Although they seem to be used only internally in the
> driver it's far too easy to forward these errors to other kernel code
> with future changes, so better return error codes instead.
>
>
> On Mon, Mar 24, 2025 at 10:10:44PM +0100, Artur Rojek wrote:
> > +
> > +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 },
> > +};
>
> Should be const.
>
> > +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) {
>
> type >= ARRAY_SIZE(nrf_rpu_addr_lut)
>
> > +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);
>
> I think the loop break condition should be
>
> !ret && i < NRF70_NUM_RX_BUFS
>
> This is rather hard to read though, so I suggest a plain
>
> for () {
> ret = nrf70_init_rx();
> if (ret)
> return ret;
> }
>
> > +
> > + 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;
>
> This break is misaligned.
>
> > + 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;
>
> ditto
>
> > + 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,
>
> You could create defines for the array offsets instead of the values and use
> C99 initializers here. For example
>
> #define NRF70_QFN_XO_VAL_OFFSET 6
>
> and then:
>
> [NRF70_QFN_XO_VAL_OFFSET] = 0x2a,
>
> That way the offsets are immediately clear without counting the array
> members.
>
> With this having defines for the values is IMO rather unnecessary then
> as these are magic values anyway and usage of C99 initializers already
> makes their meaning clear.
>
> > + /* 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 struct ieee80211_rate nrf70_dsss_rates[] = {
>
> Should be const.
>
> > + { .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 = {
>
> Should be const.
>
> > + .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[] = {
>
> const
>
> > + 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[] = {
>
> const
>
> > + { .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 = {
>
> const
>
> > + .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 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) {
>
> devm_gpiod_get() returns an error pointer in case of failure. You have
> to check the return value with IS_ERR().
>
> > + dev_err(dev, "Unable to find bucken-gpios property\n");
> > + return -EINVAL;
> > + }
>
> You could use dev_err_probe() for returning an error from the probe
> function.
>
> > +
> > + ret = gpiod_direction_output(priv->buck_en, 0);
> > + if (ret) {
> > + dev_err(dev, "Unable to set buck_en direction\n");
> > + return -EIO;
> > + }
>
> Should this "bucken" GPIO rather be a regulator?
>
> Is this really mandatory? It sounds like it could be hardwired to some
> fixed voltage.
Take this with a grain of salt, as I am not a hardware designer.
Nordic's Product Specification document [1] stipulates that BUCKEN line
controls the PWR IP core. In order to start the IC, a power up sequence
is required: first the various power supply lines, then BUCKEN, then
IOVDD signal. Similar with a power down sequence.
To me, this reads that BUCKEN cannot be simply wired to some fixed
voltage and needs some sort of state control. Additionally, it is
the (only?) way for the software to reset the IC and put it into a known
state during probe, or after a hang.
At least for the second case, the driver needs some sort of power
control, whether it's directly the BUCKEN line, or some other circuit
that in turn flicks the BUCKEN. I could rename it to 'vpwr-supply' if
that makes things more transparent. I would risk saying that it makes
it mandatory.
PS. The annoying part about the regulator API is that it reference
counts its usage, and as such regulator_disable() cannot be called
without a prior call to regulator_enable(). So to power-cycle the IC via
the BUCKEN line, I will need to do the following sequence:
regulator_enable() -> regulator_disable() -> regulator_enable()
But I can live with that ;)
[1] https://docs-be.nordicsemi.com/bundle/ps_nrf7002/attach/nRF7002_PS_v1.2.pdf?_LANG=enus
(p. 51)
Cheers,
Artur
>
> > +
> > + priv->iovdd_en = devm_gpiod_get_optional(dev, "iovdd", 0);
>
> Same here. Should this rather be a regulator?
>
> > + 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;
> > + }
>
> Any particular reason you describe the interrupt as GPIO?
>
> I would just use the interrupts / interrupt-parent binding.
>
> > +
> > + 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;
> > +}
> > +
>
> Sascha
>
> --
> Pengutronix e.K. | |
> Steuerwalder Str. 21 | http://www.pengutronix.de/ |
> 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC PATCH 1/2] net: wireless: Add Nordic nRF70 series Wi-Fi driver
2025-04-18 12:46 ` Artur Rojek
@ 2025-04-24 13:16 ` Sascha Hauer
0 siblings, 0 replies; 8+ messages in thread
From: Sascha Hauer @ 2025-04-24 13:16 UTC (permalink / raw)
To: Artur Rojek
Cc: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-wireless, devicetree, linux-kernel, Jakub Klama,
Wojciech Kloska, Ulf Axelsson
On Fri, Apr 18, 2025 at 02:46:34PM +0200, Artur Rojek wrote:
> Hi Sascha,
>
> thanks for the review, reply inline.
>
>
> > > +
> > > + ret = gpiod_direction_output(priv->buck_en, 0);
> > > + if (ret) {
> > > + dev_err(dev, "Unable to set buck_en direction\n");
> > > + return -EIO;
> > > + }
> >
> > Should this "bucken" GPIO rather be a regulator?
> >
> > Is this really mandatory? It sounds like it could be hardwired to some
> > fixed voltage.
>
> Take this with a grain of salt, as I am not a hardware designer.
> Nordic's Product Specification document [1] stipulates that BUCKEN line
> controls the PWR IP core. In order to start the IC, a power up sequence
> is required: first the various power supply lines, then BUCKEN, then
> IOVDD signal. Similar with a power down sequence.
> To me, this reads that BUCKEN cannot be simply wired to some fixed
> voltage and needs some sort of state control. Additionally, it is
> the (only?) way for the software to reset the IC and put it into a known
> state during probe, or after a hang.
> At least for the second case, the driver needs some sort of power
> control, whether it's directly the BUCKEN line, or some other circuit
> that in turn flicks the BUCKEN. I could rename it to 'vpwr-supply' if
> that makes things more transparent. I would risk saying that it makes
> it mandatory.
>
> PS. The annoying part about the regulator API is that it reference
> counts its usage, and as such regulator_disable() cannot be called
> without a prior call to regulator_enable(). So to power-cycle the IC via
> the BUCKEN line, I will need to do the following sequence:
> regulator_enable() -> regulator_disable() -> regulator_enable()
> But I can live with that ;)
Ok, With this explanation I think it's best to use it as GPIO like you
did. It just sounded like the regulator API would fit here, hence the
question.
Sascha
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC PATCH 1/2] net: wireless: Add Nordic nRF70 series Wi-Fi driver
2025-03-24 21:10 ` [RFC PATCH 1/2] net: wireless: Add Nordic nRF70 series Wi-Fi driver Artur Rojek
2025-04-01 14:11 ` Sascha Hauer
@ 2025-04-29 5:04 ` Eric Biggers
1 sibling, 0 replies; 8+ messages in thread
From: Eric Biggers @ 2025-04-29 5:04 UTC (permalink / raw)
To: Artur Rojek
Cc: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-wireless, devicetree, linux-kernel, Jakub Klama,
Wojciech Kloska, Ulf Axelsson
On Mon, Mar 24, 2025 at 10:10:44PM +0100, Artur Rojek wrote:
> +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;
> +}
You can just use sha256() here (and select CRYPTO_LIB_SHA256 from your kconfig
option). It's much simpler than crypto_shash.
- Eric
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2025-04-29 5:04 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-03-24 21:10 [RFC PATCH 0/2] Nordic nRF70 series Artur Rojek
2025-03-24 21:10 ` [RFC PATCH 1/2] net: wireless: Add Nordic nRF70 series Wi-Fi driver Artur Rojek
2025-04-01 14:11 ` Sascha Hauer
2025-04-18 12:46 ` Artur Rojek
2025-04-24 13:16 ` Sascha Hauer
2025-04-29 5:04 ` Eric Biggers
2025-03-24 21:10 ` [RFC PATCH 2/2] dt-bindings: wireless: Document Nordic nRF70 bindings Artur Rojek
2025-03-24 23:05 ` Rob Herring (Arm)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).