AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/06/26 06:48 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": [
    "WLAN_VENDOR_MORSEMICRO",
    "MM81X",
    "MM81X_USB",
    "MM81X_SDIO"
  ],
  "FocusSymbols": [
    "mm81x_usb_probe",
    "mm81x_usb_int_handler",
    "mm81x_usb_cmd_callback",
    "mm81x_usb_mem_rw_callback",
    "mm81x_sdio_probe",
    "mm81x_mac_event_recv",
    "mm81x_mac_rx_skb",
    "mm81x_yaps_work",
    "mm81x_yaps_hw_read_pkts",
    "mm81x_cmd_resp_process"
  ],
  "Reasoning": "The patch introduces a new mac80211 wireless driver for Morse Micro MM81x devices. It includes both USB and SDIO interfaces. The USB interface can be easily fuzzed using virtual USB host controllers (vhci) in virtualized environments. Therefore, this driver is reachable and should be fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/06/26 06:48 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 4ab6997f840e35fbcb89090f4ea0c42c8fae430e\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Jun 26 06:48:54 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex eb8cdcc76324fe..d5639668f3a928 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -18012,6 +18012,14 @@ F:\tdrivers/regulator/mpq7920.c\n F:\tdrivers/regulator/mpq7920.h\n F:\tinclude/linux/mfd/mp2629.h\n \n+MORSE MICRO MM81X WIRELESS DRIVER\n+M:\tLachlan Hodges \u003clachlan.hodges@morsemicro.com\u003e\n+M:\tDan Callaghan \u003cdan.callaghan@morsemicro.com\u003e\n+R:\tArien Judge \u003carien.judge@morsemicro.com\u003e\n+L:\tlinux-wireless@vger.kernel.org\n+S:\tSupported\n+F:\tdrivers/net/wireless/morsemicro/mm81x/\n+\n MOST(R) TECHNOLOGY DRIVER\n M:\tParthiban Veerasooran \u003cparthiban.veerasooran@microchip.com\u003e\n M:\tChristian Gromm \u003cchristian.gromm@microchip.com\u003e\ndiff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig\nindex c6599594dc995c..baddadf9ec3c24 100644\n--- a/drivers/net/wireless/Kconfig\n+++ b/drivers/net/wireless/Kconfig\n@@ -27,6 +27,7 @@ source \"drivers/net/wireless/intersil/Kconfig\"\n source \"drivers/net/wireless/marvell/Kconfig\"\n source \"drivers/net/wireless/mediatek/Kconfig\"\n source \"drivers/net/wireless/microchip/Kconfig\"\n+source \"drivers/net/wireless/morsemicro/Kconfig\"\n source \"drivers/net/wireless/purelifi/Kconfig\"\n source \"drivers/net/wireless/ralink/Kconfig\"\n source \"drivers/net/wireless/realtek/Kconfig\"\ndiff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile\nindex e1c4141c600443..d74f817b37de1c 100644\n--- a/drivers/net/wireless/Makefile\n+++ b/drivers/net/wireless/Makefile\n@@ -12,6 +12,7 @@ obj-$(CONFIG_WLAN_VENDOR_INTERSIL) += intersil/\n obj-$(CONFIG_WLAN_VENDOR_MARVELL) += marvell/\n obj-$(CONFIG_WLAN_VENDOR_MEDIATEK) += mediatek/\n obj-$(CONFIG_WLAN_VENDOR_MICROCHIP) += microchip/\n+obj-$(CONFIG_WLAN_VENDOR_MORSEMICRO) += morsemicro/\n obj-$(CONFIG_WLAN_VENDOR_PURELIFI) += purelifi/\n obj-$(CONFIG_WLAN_VENDOR_QUANTENNA) += quantenna/\n obj-$(CONFIG_WLAN_VENDOR_RALINK) += ralink/\ndiff --git a/drivers/net/wireless/morsemicro/Kconfig b/drivers/net/wireless/morsemicro/Kconfig\nnew file mode 100644\nindex 00000000000000..cb0653c77d87cc\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/Kconfig\n@@ -0,0 +1,15 @@\n+# SPDX-License-Identifier: GPL-2.0-only\n+config WLAN_VENDOR_MORSEMICRO\n+\tbool \"Morse Micro devices\"\n+\tdefault y\n+\thelp\n+\t  If you have a wireless card belonging to this class, say Y.\n+\n+\t  Note that the answer to this question doesn't directly affect the\n+\t  kernel: saying N will just cause the configurator to skip all the\n+\t  questions about these cards. If you say Y, you will be asked for\n+\t  your specific card in the following questions.\n+\n+if WLAN_VENDOR_MORSEMICRO\n+source \"drivers/net/wireless/morsemicro/mm81x/Kconfig\"\n+endif # WLAN_VENDOR_MORSEMICRO\ndiff --git a/drivers/net/wireless/morsemicro/Makefile b/drivers/net/wireless/morsemicro/Makefile\nnew file mode 100644\nindex 00000000000000..5b2670f7d1711f\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/Makefile\n@@ -0,0 +1,2 @@\n+# SPDX-License-Identifier: GPL-2.0\n+obj-$(CONFIG_MM81X) += mm81x/\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/Kconfig b/drivers/net/wireless/morsemicro/mm81x/Kconfig\nnew file mode 100644\nindex 00000000000000..33cdcc0df4de2e\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/Kconfig\n@@ -0,0 +1,24 @@\n+# SPDX-License-Identifier: GPL-2.0\n+\n+config MM81X\n+\ttristate \"Morse Micro MM81x wireless devices\"\n+\tdepends on MAC80211\n+\tselect FW_LOADER\n+\tselect CRC7\n+\thelp\n+\t  This module adds support for wireless devices based\n+\t  on Morse Micro MM81xx chipsets.\n+\n+config MM81X_USB\n+\ttristate \"Morse Micro MM81x USB support\"\n+\tdepends on MM81X \u0026\u0026 USB\n+\thelp\n+\t  This module adds support for the USB interface of\n+\t  devices using the Morse Micro MM81x chipset.\n+\n+config MM81X_SDIO\n+\ttristate \"Morse Micro MM81x SDIO support\"\n+\tdepends on MM81X \u0026\u0026 MMC\n+\thelp\n+\t  This module adds support for the SDIO interface of\n+\t  devices using the Morse Micro MM81x chipset.\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/Makefile b/drivers/net/wireless/morsemicro/mm81x/Makefile\nnew file mode 100644\nindex 00000000000000..0d494fda141257\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/Makefile\n@@ -0,0 +1,21 @@\n+# SPDX-License-Identifier: GPL-2.0\n+\n+obj-$(CONFIG_MM81X) += mm81x_core.o\n+\n+mm81x_core-y += core.o\n+mm81x_core-y += mac.o\n+mm81x_core-y += hw.o\n+mm81x_core-y += fw.o\n+mm81x_core-y += command.o\n+mm81x_core-y += ps.o\n+mm81x_core-y += skbq.o\n+mm81x_core-y += yaps_hw.o\n+mm81x_core-y += yaps.o\n+mm81x_core-y += rc.o\n+mm81x_core-y += mmrc.o\n+\n+obj-$(CONFIG_MM81X_USB) += mm81x_usb.o\n+mm81x_usb-y += usb.o\n+\n+obj-$(CONFIG_MM81X_SDIO) += mm81x_sdio.o\n+mm81x_sdio-y += sdio.o\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/bus.h b/drivers/net/wireless/morsemicro/mm81x/bus.h\nnew file mode 100644\nindex 00000000000000..d2ccabc037fb9b\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/bus.h\n@@ -0,0 +1,99 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#ifndef _MM81X_BUS_H_\n+#define _MM81X_BUS_H_\n+\n+#include \u003clinux/skbuff.h\u003e\n+#include \"core.h\"\n+\n+enum mm81x_bus_type {\n+\tMM81X_BUS_TYPE_USB,\n+\tMM81X_BUS_TYPE_SDIO,\n+};\n+\n+struct mm81x_bus_ops {\n+\tint (*dm_read)(struct mm81x *mors, u32 addr, u8 *data, int len);\n+\tint (*dm_write)(struct mm81x *mors, u32 addr, const u8 *data, int len);\n+\tint (*reg32_read)(struct mm81x *mors, u32 addr, u32 *data);\n+\tint (*reg32_write)(struct mm81x *mors, u32 addr, u32 data);\n+\tint (*digital_reset)(struct mm81x *mors);\n+\tvoid (*set_bus_enable)(struct mm81x *mors, bool enable);\n+\tvoid (*config_burst_mode)(struct mm81x *mors, bool enable_burst);\n+\tvoid (*claim)(struct mm81x *mors);\n+\tvoid (*set_irq)(struct mm81x *mors, bool enable);\n+\tvoid (*release)(struct mm81x *mors);\n+\tunsigned int bulk_alignment;\n+};\n+\n+/*\n+ * Default TX alignment for buses which don't care. mac80211 will give us\n+ * SKBs aligned to the 2 byte boundary, so 2 is effectively a noop.\n+ */\n+#define MM81X_BUS_DEFAULT_BULK_ALIGNMENT (2)\n+\n+/* mm81x_dm_read - len must be rounded up to the nearest 4-byte boundary */\n+static inline int mm81x_dm_read(struct mm81x *mors, u32 addr, u8 *data, int len)\n+{\n+\treturn mors-\u003ebus_ops-\u003edm_read(mors, addr, data, len);\n+}\n+\n+static inline int mm81x_dm_write(struct mm81x *mors, u32 addr, const u8 *data,\n+\t\t\t\t int len)\n+{\n+\treturn mors-\u003ebus_ops-\u003edm_write(mors, addr, data, len);\n+}\n+\n+static inline int mm81x_reg32_read(struct mm81x *mors, u32 addr, u32 *data)\n+{\n+\treturn mors-\u003ebus_ops-\u003ereg32_read(mors, addr, data);\n+}\n+\n+static inline int mm81x_reg32_write(struct mm81x *mors, u32 addr, u32 data)\n+{\n+\treturn mors-\u003ebus_ops-\u003ereg32_write(mors, addr, data);\n+}\n+\n+static inline int mm81x_bus_digital_reset(struct mm81x *mors)\n+{\n+\tif (mors-\u003ebus_ops-\u003edigital_reset)\n+\t\treturn mors-\u003ebus_ops-\u003edigital_reset(mors);\n+\n+\treturn 0;\n+}\n+\n+static inline void mm81x_set_bus_enable(struct mm81x *mors, bool enable)\n+{\n+\tmors-\u003ebus_ops-\u003eset_bus_enable(mors, enable);\n+}\n+\n+static inline void mm81x_bus_config_burst_mode(struct mm81x *mors,\n+\t\t\t\t\t       bool enable_burst)\n+{\n+\tif (mors-\u003ebus_ops-\u003econfig_burst_mode)\n+\t\tmors-\u003ebus_ops-\u003econfig_burst_mode(mors, enable_burst);\n+}\n+\n+static inline void mm81x_claim_bus(struct mm81x *mors)\n+{\n+\tmors-\u003ebus_ops-\u003eclaim(mors);\n+}\n+\n+static inline void mm81x_bus_set_irq(struct mm81x *mors, bool enable)\n+{\n+\tmors-\u003ebus_ops-\u003eset_irq(mors, enable);\n+}\n+\n+static inline void mm81x_release_bus(struct mm81x *mors)\n+{\n+\tmors-\u003ebus_ops-\u003erelease(mors);\n+}\n+\n+static inline unsigned int mm81x_bus_get_alignment(struct mm81x *mors)\n+{\n+\treturn mors-\u003ebus_ops-\u003ebulk_alignment;\n+}\n+\n+#endif /* !_MM81X_BUS_H_ */\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/command.c b/drivers/net/wireless/morsemicro/mm81x/command.c\nnew file mode 100644\nindex 00000000000000..afb7ee9bb236f1\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/command.c\n@@ -0,0 +1,563 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/atomic.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+\n+#include \"command.h\"\n+#include \"mac.h\"\n+#include \"ps.h\"\n+#include \"hif.h\"\n+\n+#define MM_MAX_COMMAND_RETRY 2\n+#define HOST_CMD_DEFAULT_TIMEOUT_MS 600\n+#define HOST_CMD_POWERSAVE_TIMEOUT_MS 2000\n+\n+#define INIT_CMD_HDR(_req, _cmd, _vif_id)\t\t\t\t\\\n+\t((struct host_cmd_header){\t\t\t\t\t\\\n+\t\t.message_id = cpu_to_le16(_cmd),\t\t\t\\\n+\t\t.len = cpu_to_le16(sizeof(_req) - sizeof((_req).hdr)),\t\\\n+\t\t.vif_id = cpu_to_le16(_vif_id),\t\t\t\t\\\n+\t})\n+\n+struct host_cmd_resp_cb {\n+\tint ret;\n+\tu32 length;\n+\tstruct host_cmd_resp *dest_resp;\n+};\n+\n+static int mm81x_cmd_tx(struct mm81x *mors, struct host_cmd_resp *resp,\n+\t\t\tstruct host_cmd_req *req, u32 length, u32 timeout)\n+{\n+\tint cmd_len;\n+\tint ret = 0;\n+\tu16 host_id;\n+\tint retry = 0;\n+\tunsigned long wait_ret = 0;\n+\tstruct sk_buff *skb;\n+\tstruct mm81x_skbq *cmd_q = mm81x_hif_get_tx_cmd_queue(mors);\n+\tstruct host_cmd_resp_cb *resp_cb;\n+\tDECLARE_COMPLETION_ONSTACK(cmd_comp);\n+\n+\tBUILD_BUG_ON(sizeof(struct host_cmd_resp_cb) \u003e\n+\t\t     IEEE80211_TX_INFO_DRIVER_DATA_SIZE);\n+\n+\tcmd_len = sizeof(*req) + le16_to_cpu(req-\u003ehdr.len);\n+\treq-\u003ehdr.flags = cpu_to_le16(HOST_CMD_TYPE_REQ);\n+\n+\tmutex_lock(\u0026mors-\u003ecmd_wait);\n+\tmors-\u003ecmd_seq++;\n+\tif (mors-\u003ecmd_seq \u003e HOST_CMD_HOST_ID_SEQ_MAX)\n+\t\tmors-\u003ecmd_seq = 1;\n+\thost_id = mors-\u003ecmd_seq \u003c\u003c HOST_CMD_HOST_ID_SEQ_SHIFT;\n+\n+\tmm81x_ps_disable(mors);\n+\n+\tdo {\n+\t\treq-\u003ehdr.host_id = cpu_to_le16(host_id | retry);\n+\n+\t\tskb = mm81x_skbq_alloc_skb(cmd_q, cmd_len);\n+\t\tif (!skb) {\n+\t\t\tret = -ENOMEM;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tmemcpy(skb-\u003edata, req, cmd_len);\n+\t\tresp_cb = (struct host_cmd_resp_cb *)IEEE80211_SKB_CB(skb)\n+\t\t\t\t  -\u003edriver_data;\n+\t\tresp_cb-\u003elength = length;\n+\t\tresp_cb-\u003edest_resp = resp;\n+\n+\t\tdev_dbg(mors-\u003edev, \"CMD 0x%04x:%04x\",\n+\t\t\tle16_to_cpu(req-\u003ehdr.message_id),\n+\t\t\tle16_to_cpu(req-\u003ehdr.host_id));\n+\n+\t\tmutex_lock(\u0026mors-\u003ecmd_lock);\n+\t\tmors-\u003ecmd_comp = \u0026cmd_comp;\n+\t\tif (retry \u003e 0)\n+\t\t\treinit_completion(\u0026cmd_comp);\n+\t\ttimeout = timeout ? timeout : HOST_CMD_DEFAULT_TIMEOUT_MS;\n+\t\tret = mm81x_skbq_skb_tx(cmd_q, \u0026skb, NULL,\n+\t\t\t\t\tMM81X_SKB_CHAN_COMMAND);\n+\t\tmutex_unlock(\u0026mors-\u003ecmd_lock);\n+\n+\t\tif (ret) {\n+\t\t\tdev_err(mors-\u003edev, \"mm81x_skbq_tx fail: %d\", ret);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\twait_ret = wait_for_completion_timeout(\n+\t\t\t\u0026cmd_comp, msecs_to_jiffies(timeout));\n+\t\tmutex_lock(\u0026mors-\u003ecmd_lock);\n+\t\tmors-\u003ecmd_comp = NULL;\n+\n+\t\tif (!wait_ret) {\n+\t\t\tdev_err(mors-\u003edev,\n+\t\t\t\t\"Try:%d Command %04x:%04x timeout after %u ms\",\n+\t\t\t\tretry, le16_to_cpu(req-\u003ehdr.message_id),\n+\t\t\t\tle16_to_cpu(req-\u003ehdr.host_id), timeout);\n+\t\t\tret = -ETIMEDOUT;\n+\t\t} else {\n+\t\t\tret = (length \u0026\u0026 resp) ? le32_to_cpu(resp-\u003estatus) :\n+\t\t\t\t\t\t resp_cb-\u003eret;\n+\t\t\tif (ret \u003e 0 || ret \u003c -MAX_ERRNO)\n+\t\t\t\tret = -EIO;\n+\n+\t\t\tdev_dbg(mors-\u003edev, \"Command 0x%04x:%04x status 0x%08x\",\n+\t\t\t\tle16_to_cpu(req-\u003ehdr.message_id),\n+\t\t\t\tle16_to_cpu(req-\u003ehdr.host_id), ret);\n+\t\t\tif (ret) {\n+\t\t\t\tdev_err(mors-\u003edev,\n+\t\t\t\t\t\"Command 0x%04x:%04x error %d\",\n+\t\t\t\t\tle16_to_cpu(req-\u003ehdr.message_id),\n+\t\t\t\t\tle16_to_cpu(req-\u003ehdr.host_id), ret);\n+\t\t\t}\n+\t\t}\n+\t\t/* Free the command request */\n+\t\tspin_lock_bh(\u0026cmd_q-\u003elock);\n+\t\tmm81x_skbq_skb_finish(cmd_q, skb, NULL);\n+\t\tspin_unlock_bh(\u0026cmd_q-\u003elock);\n+\t\tmutex_unlock(\u0026mors-\u003ecmd_lock);\n+\n+\t\tretry++;\n+\t} while ((ret == -ETIMEDOUT) \u0026\u0026 retry \u003c MM_MAX_COMMAND_RETRY);\n+\n+\tmm81x_ps_enable(mors);\n+\tmutex_unlock(\u0026mors-\u003ecmd_wait);\n+\n+\tif (ret == -ETIMEDOUT) {\n+\t\tdev_err(mors-\u003edev, \"Command %02x:%02x timed out\",\n+\t\t\tle16_to_cpu(req-\u003ehdr.message_id),\n+\t\t\tle16_to_cpu(req-\u003ehdr.host_id));\n+\t} else if (ret != 0) {\n+\t\tdev_err(mors-\u003edev,\n+\t\t\t\"Command %02x:%02x failed with rc %d (0x%x)\\n\",\n+\t\t\tle16_to_cpu(req-\u003ehdr.message_id),\n+\t\t\tle16_to_cpu(req-\u003ehdr.host_id), ret, ret);\n+\t}\n+\n+\treturn ret;\n+}\n+\n+int mm81x_cmd_resp_process(struct mm81x *mors, struct sk_buff *skb)\n+{\n+\tint length, ret = -ESRCH; /* No such process */\n+\tstruct mm81x_skbq *cmd_q = mm81x_hif_get_tx_cmd_queue(mors);\n+\tstruct host_cmd_resp *src_resp = (struct host_cmd_resp *)(skb-\u003edata);\n+\tstruct sk_buff *cmd_skb = NULL;\n+\tstruct host_cmd_resp_cb *resp_cb;\n+\tstruct host_cmd_resp *dest_resp;\n+\tstruct host_cmd_req *req;\n+\tu16 message_id = 0;\n+\tu16 host_id = 0;\n+\tu16 resp_message_id = le16_to_cpu(src_resp-\u003ehdr.message_id);\n+\tu16 resp_host_id = le16_to_cpu(src_resp-\u003ehdr.host_id);\n+\tbool is_late_response = false;\n+\n+\tdev_dbg(mors-\u003edev, \"EVT 0x%04x:0x%04x\", resp_message_id, resp_host_id);\n+\n+\tif (!HOST_CMD_IS_RESP(src_resp)) {\n+\t\tret = mm81x_mac_event_recv(mors, skb);\n+\t\tgoto exit_free;\n+\t}\n+\n+\tmutex_lock(\u0026mors-\u003ecmd_lock);\n+\n+\tcmd_skb = mm81x_skbq_tx_pending(cmd_q);\n+\tif (cmd_skb) {\n+\t\tmm81x_skbq_pull_hdr_post_tx(cmd_skb);\n+\t\treq = (struct host_cmd_req *)cmd_skb-\u003edata;\n+\t\tmessage_id = le16_to_cpu(req-\u003ehdr.message_id);\n+\t\thost_id = le16_to_cpu(req-\u003ehdr.host_id);\n+\t}\n+\n+\t/*\n+\t * If there is no pending command or the sequence ID does not match,\n+\t * this is a late response for a timed out command which has been\n+\t * cleaned up, so just free up the response. If a command was retried,\n+\t * the response may be from the retry or from the original command\n+\t * (late response) but not from both because the firmware will silently\n+\t * drop a retry if it received the initial request. So a mismatched\n+\t * retry counter is treated as a matched command and response.\n+\t */\n+\tif (!cmd_skb || message_id != resp_message_id ||\n+\t    (host_id \u0026 HOST_CMD_HOST_ID_SEQ_MASK) !=\n+\t\t    (resp_host_id \u0026 HOST_CMD_HOST_ID_SEQ_MASK)) {\n+\t\tdev_err(mors-\u003edev,\n+\t\t\t\"Late response for timed out req 0x%04x:%04x have 0x%04x:%04x 0x%04x\",\n+\t\t\tresp_message_id, resp_host_id, message_id, host_id,\n+\t\t\tmors-\u003ecmd_seq);\n+\t\tis_late_response = true;\n+\t\tgoto exit;\n+\t}\n+\tif ((host_id \u0026 HOST_CMD_HOST_ID_RETRY_MASK) !=\n+\t    (resp_host_id \u0026 HOST_CMD_HOST_ID_RETRY_MASK))\n+\t\tdev_dbg(mors-\u003edev,\n+\t\t\t\"Command retry mismatch 0x%04x:%04x 0x%04x:%04x\",\n+\t\t\tmessage_id, host_id, resp_message_id, resp_host_id);\n+\n+\tresp_cb = (struct host_cmd_resp_cb *)IEEE80211_SKB_CB(cmd_skb)\n+\t\t\t  -\u003edriver_data;\n+\tlength = resp_cb-\u003elength;\n+\tdest_resp = resp_cb-\u003edest_resp;\n+\tif (length \u003e= sizeof(struct host_cmd_resp) \u0026\u0026 dest_resp) {\n+\t\tret = 0;\n+\t\tlength = min_t(int, length,\n+\t\t\t       le16_to_cpu(src_resp-\u003ehdr.len) +\n+\t\t\t\t       sizeof(struct host_cmd_header));\n+\t\tmemcpy(dest_resp, src_resp, length);\n+\t} else {\n+\t\tret = le32_to_cpu(src_resp-\u003estatus);\n+\t}\n+\n+\tresp_cb-\u003eret = ret;\n+\n+exit:\n+\tif (cmd_skb \u0026\u0026 !is_late_response) {\n+\t\t/* Complete if not already timed out */\n+\t\tif (mors-\u003ecmd_comp)\n+\t\t\tcomplete(mors-\u003ecmd_comp);\n+\t}\n+\n+\tmutex_unlock(\u0026mors-\u003ecmd_lock);\n+exit_free:\n+\tdev_kfree_skb(skb);\n+\treturn 0;\n+}\n+\n+int mm81x_cmd_sta_state(struct mm81x *mors, struct mm81x_vif *mors_vif, u16 aid,\n+\t\t\tstruct ieee80211_sta *sta,\n+\t\t\tenum ieee80211_sta_state state)\n+{\n+\tstruct host_cmd_req_set_sta_state req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_STA_STATE,\n+\t\t\t\t    mors_vif-\u003eid),\n+\t\t.aid = cpu_to_le16(aid),\n+\t\t.state = cpu_to_le16(state),\n+\t\t.uapsd_queues = sta-\u003euapsd_queues,\n+\t};\n+\n+\tmemcpy(req.sta_addr, sta-\u003eaddr, sizeof(req.sta_addr));\n+\n+\treturn mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)\u0026req, 0, 0);\n+}\n+\n+int mm81x_cmd_add_if(struct mm81x *mors, u16 *vif_id, const u8 *addr,\n+\t\t     enum nl80211_iftype type)\n+{\n+\tint ret;\n+\tstruct host_cmd_req_add_interface req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_ADD_INTERFACE, 0),\n+\t};\n+\tstruct host_cmd_resp_add_interface resp;\n+\n+\tswitch (type) {\n+\tcase NL80211_IFTYPE_STATION:\n+\t\treq.interface_type = cpu_to_le32(HOST_CMD_INTERFACE_TYPE_STA);\n+\t\tbreak;\n+\tcase NL80211_IFTYPE_AP:\n+\t\treq.interface_type = cpu_to_le32(HOST_CMD_INTERFACE_TYPE_AP);\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tmemcpy(req.addr.octet, addr, sizeof(req.addr.octet));\n+\n+\tret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)\u0026resp,\n+\t\t\t   (struct host_cmd_req *)\u0026req, sizeof(resp), 0);\n+\tif (!ret)\n+\t\t*vif_id = le16_to_cpu(resp.hdr.vif_id);\n+\n+\treturn ret;\n+}\n+\n+int mm81x_cmd_get_capabilities(struct mm81x *mors, u16 vif_id,\n+\t\t\t       struct mm81x_fw_caps *capabilities)\n+{\n+\tint ret;\n+\tint i;\n+\tstruct host_cmd_req_get_capabilities req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_CAPABILITIES, vif_id),\n+\t};\n+\tstruct host_cmd_resp_get_capabilities rsp;\n+\n+\tret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)\u0026rsp,\n+\t\t\t   (struct host_cmd_req *)\u0026req, sizeof(rsp), 0);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tcapabilities-\u003eampdu_mss = rsp.capabilities.ampdu_mss;\n+\tcapabilities-\u003emm81x_mmss_offset = rsp.morse_mmss_offset;\n+\tcapabilities-\u003ebeamformee_sts_capability =\n+\t\trsp.capabilities.beamformee_sts_capability;\n+\tcapabilities-\u003emaximum_ampdu_length_exponent =\n+\t\trsp.capabilities.maximum_ampdu_length_exponent;\n+\tcapabilities-\u003enumber_sounding_dimensions =\n+\t\trsp.capabilities.number_sounding_dimensions;\n+\tfor (i = 0; i \u003c FW_CAPABILITIES_FLAGS_WIDTH; i++)\n+\t\tcapabilities-\u003eflags[i] = le32_to_cpu(rsp.capabilities.flags[i]);\n+\n+\treturn ret;\n+}\n+\n+int mm81x_cmd_get_max_txpower(struct mm81x *mors, s32 *out_power_mbm)\n+{\n+\tint ret;\n+\tstruct host_cmd_req_get_max_txpower req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_MAX_TXPOWER, 0),\n+\t};\n+\tstruct host_cmd_resp_get_max_txpower resp;\n+\n+\tret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)\u0026resp,\n+\t\t\t   (struct host_cmd_req *)\u0026req, sizeof(resp), 0);\n+\tif (!ret)\n+\t\t*out_power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm));\n+\n+\treturn ret;\n+}\n+\n+int mm81x_cmd_hw_scan(struct mm81x *mors, struct mm81x_hw_scan_params *params,\n+\t\t      bool store)\n+{\n+\tint ret;\n+\tstruct host_cmd_req_hw_scan *req;\n+\tsize_t cmd_size;\n+\tu8 *buf;\n+\tu32 flags = 0;\n+\n+\tcmd_size = mm81x_hw_scan_h_get_cmd_size(params);\n+\tcmd_size = ROUND_BYTES_TO_WORD(cmd_size);\n+\n+\treq = kzalloc(cmd_size, GFP_KERNEL);\n+\tif (!req)\n+\t\treturn -ENOMEM;\n+\n+\tbuf = req-\u003evariable;\n+\n+\tif (store)\n+\t\tflags = HOST_CMD_HW_SCAN_FLAGS_STORE;\n+\telse if (params-\u003eoperation == MM81X_HW_SCAN_OP_START)\n+\t\tflags |= HOST_CMD_HW_SCAN_FLAGS_START;\n+\telse if (params-\u003eoperation == MM81X_HW_SCAN_OP_STOP)\n+\t\tflags |= HOST_CMD_HW_SCAN_FLAGS_ABORT;\n+\n+\tflags |= HOST_CMD_HW_SCAN_FLAGS_1MHZ_PROBES;\n+\n+\tif (params-\u003eoperation == MM81X_HW_SCAN_OP_START) {\n+\t\treq-\u003edwell_time_ms = cpu_to_le32(params-\u003edwell_time_ms);\n+\t\tbuf = mm81x_hw_scan_h_insert_tlvs(params, buf);\n+\t}\n+\n+\treq-\u003eflags = cpu_to_le32(flags);\n+\treq-\u003ehdr = INIT_CMD_HDR((*req), HOST_CMD_ID_HW_SCAN, 0);\n+\treq-\u003ehdr.len = cpu_to_le16((u16)((buf - (u8 *)req) - sizeof(req-\u003ehdr)));\n+\tret = mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)req, 0, 0);\n+\tkfree(req);\n+\n+\treturn ret;\n+}\n+\n+int mm81x_cmd_set_txpower(struct mm81x *mors, s32 *out_power_mbm,\n+\t\t\t  int txpower_mbm)\n+{\n+\tint ret;\n+\tstruct host_cmd_req_set_txpower req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_TXPOWER, 0),\n+\t\t.power_qdbm = cpu_to_le32(MBM_TO_QDBM(txpower_mbm)),\n+\t};\n+\tstruct host_cmd_resp_set_txpower resp;\n+\n+\tret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)\u0026resp,\n+\t\t\t   (struct host_cmd_req *)\u0026req, sizeof(resp), 0);\n+\tif (!ret)\n+\t\t*out_power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm));\n+\n+\treturn ret;\n+}\n+\n+int mm81x_cmd_set_channel(struct mm81x *mors, u32 op_chan_freq_hz,\n+\t\t\t  u8 pri_1mhz_chan_idx, u8 op_bw_mhz, u8 pri_bw_mhz,\n+\t\t\t  s32 *power_mbm)\n+{\n+\tint ret;\n+\tstruct host_cmd_req_set_channel req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_CHANNEL, 0),\n+\t\t.op_chan_freq_hz = cpu_to_le32(op_chan_freq_hz),\n+\t\t.op_bw_mhz = op_bw_mhz,\n+\t\t.pri_bw_mhz = pri_bw_mhz,\n+\t\t.pri_1mhz_chan_idx = pri_1mhz_chan_idx,\n+\t\t.dot11_mode = HOST_CMD_DOT11_PROTO_MODE_AH,\n+\t};\n+\tstruct host_cmd_resp_set_channel resp;\n+\n+\tret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)\u0026resp,\n+\t\t\t   (struct host_cmd_req *)\u0026req, sizeof(resp), 0);\n+\tif (!ret)\n+\t\t*power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm));\n+\n+\treturn ret;\n+}\n+\n+int mm81x_cmd_disable_key(struct mm81x *mors, struct mm81x_vif *mors_vif,\n+\t\t\t  u16 aid, struct ieee80211_key_conf *key)\n+{\n+\tstruct host_cmd_req_disable_key req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_DISABLE_KEY, mors_vif-\u003eid),\n+\t\t.aid = cpu_to_le32(aid),\n+\t\t.key_idx = key-\u003ehw_key_idx,\n+\t\t.key_type =\n+\t\t\tcpu_to_le32((key-\u003eflags \u0026 IEEE80211_KEY_FLAG_PAIRWISE) ?\n+\t\t\t\t\t    HOST_CMD_TEMPORAL_KEY_TYPE_PTK :\n+\t\t\t\t\t    HOST_CMD_TEMPORAL_KEY_TYPE_GTK),\n+\t};\n+\n+\treturn mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)\u0026req, 0, 0);\n+}\n+\n+int mm81x_cmd_install_key(struct mm81x *mors, struct mm81x_vif *mors_vif,\n+\t\t\t  u16 aid, struct ieee80211_key_conf *key,\n+\t\t\t  enum host_cmd_key_cipher cipher,\n+\t\t\t  enum host_cmd_aes_key_len length)\n+{\n+\tint ret;\n+\tstruct host_cmd_req_install_key req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_INSTALL_KEY, mors_vif-\u003eid),\n+\t\t.pn = cpu_to_le64(atomic64_read(\u0026key-\u003etx_pn)),\n+\t\t.aid = cpu_to_le32(aid),\n+\t\t.cipher = cipher,\n+\t\t.key_length = length,\n+\t\t.key_idx = key-\u003ekeyidx,\n+\t\t.key_type = (key-\u003eflags \u0026 IEEE80211_KEY_FLAG_PAIRWISE) ?\n+\t\t\t\t    HOST_CMD_TEMPORAL_KEY_TYPE_PTK :\n+\t\t\t\t    HOST_CMD_TEMPORAL_KEY_TYPE_GTK,\n+\t};\n+\tstruct host_cmd_resp_install_key resp;\n+\n+\tif (key-\u003ekeylen \u003e sizeof(req.key))\n+\t\treturn -EINVAL;\n+\n+\tmemcpy(req.key, key-\u003ekey, key-\u003ekeylen);\n+\n+\tret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)\u0026resp,\n+\t\t\t   (struct host_cmd_req *)\u0026req, sizeof(resp), 0);\n+\tif (!ret) {\n+\t\tkey-\u003ehw_key_idx = resp.key_idx;\n+\t\tdev_dbg(mors-\u003edev, \"Installed key @ hw index: %d\",\n+\t\t\tresp.key_idx);\n+\t}\n+\n+\treturn ret;\n+}\n+\n+int mm81x_cmd_cfg_multicast_filter(struct mm81x *mors,\n+\t\t\t\t   struct mm81x_vif *mors_vif)\n+{\n+\tstruct host_cmd_req_mcast_filter *req;\n+\tstruct mcast_filter *filter = mors-\u003emcast_filter;\n+\tu16 filter_list_len = sizeof(filter-\u003eaddr_list[0]) * filter-\u003ecount;\n+\tu16 alloc_len = filter_list_len + sizeof(*req);\n+\tint ret = 0;\n+\n+\treq = kzalloc(alloc_len, GFP_KERNEL);\n+\tif (!req)\n+\t\treturn -ENOMEM;\n+\n+\treq-\u003ehdr = INIT_CMD_HDR((*req), HOST_CMD_ID_MCAST_FILTER, mors_vif-\u003eid);\n+\treq-\u003ehdr.len = cpu_to_le16(alloc_len - sizeof(req-\u003ehdr));\n+\treq-\u003ecount = filter-\u003ecount;\n+\tmemcpy(req-\u003ehw_addr, filter-\u003eaddr_list, filter_list_len);\n+\n+\tret = mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)req, 0, 0);\n+\tkfree(req);\n+\treturn ret;\n+}\n+\n+int mm81x_cmd_cfg_bss(struct mm81x *mors, u16 vif_id, u16 beacon_int,\n+\t\t      u16 dtim_period, u32 cssid)\n+{\n+\tstruct host_cmd_req_bss_config req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_BSS_CONFIG, vif_id),\n+\t\t.beacon_interval_tu = cpu_to_le16(beacon_int),\n+\t\t.cssid = cpu_to_le32(cssid),\n+\t\t.dtim_period = cpu_to_le16(dtim_period),\n+\t};\n+\n+\treturn mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)\u0026req, 0, 0);\n+}\n+\n+int mm81x_cmd_config_beacon_timer(struct mm81x *mors, void *mm81x_vif,\n+\t\t\t\t  bool enabled)\n+{\n+\tstruct mm81x_vif *vif = mm81x_vif;\n+\tstruct host_cmd_req_bss_beacon_config req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_BSS_BEACON_CONFIG,\n+\t\t\t\t    vif-\u003eid),\n+\t\t.enable = enabled,\n+\t};\n+\n+\treturn mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)\u0026req, 0, 0);\n+}\n+\n+int mm81x_cmd_set_ps(struct mm81x *mors, bool enabled)\n+{\n+\tstruct host_cmd_req_config_ps req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_CONFIG_PS, 0),\n+\t\t.enabled = (u8)enabled,\n+\t};\n+\n+\treturn mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)\u0026req, 0,\n+\t\t\t    HOST_CMD_POWERSAVE_TIMEOUT_MS);\n+}\n+\n+int mm81x_cmd_cfg_qos(struct mm81x *mors, struct mm81x_queue_params *params)\n+{\n+\tstruct host_cmd_req_set_qos_params req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_QOS_PARAMS, 0),\n+\t\t.uapsd = params-\u003euapsd,\n+\t\t.queue_idx = params-\u003eaci,\n+\t\t.aifs_slot_count = params-\u003eaifs,\n+\t\t.contention_window_min = cpu_to_le16(params-\u003ecw_min),\n+\t\t.contention_window_max = cpu_to_le16(params-\u003ecw_max),\n+\t\t.max_txop_usec = cpu_to_le32(params-\u003etxop),\n+\t};\n+\n+\treturn mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)\u0026req, 0, 0);\n+}\n+\n+int mm81x_cmd_rm_if(struct mm81x *mors, u16 vif_id)\n+{\n+\tstruct host_cmd_req_remove_interface req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_REMOVE_INTERFACE, vif_id),\n+\t};\n+\n+\treturn mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)\u0026req, 0, 0);\n+}\n+\n+int mm81x_cmd_set_frag_threshold(struct mm81x *mors, u32 frag_threshold)\n+{\n+\tstruct host_cmd_req_get_set_generic_param req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_SET_GENERIC_PARAM, 0),\n+\t\t.param_id = cpu_to_le32(HOST_CMD_PARAM_ID_FRAGMENT_THRESHOLD),\n+\t\t.action = cpu_to_le32(HOST_CMD_PARAM_ACTION_SET),\n+\t\t.value = cpu_to_le32(frag_threshold),\n+\t};\n+\n+\treturn mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)\u0026req, 0, 0);\n+}\n+\n+int mm81x_cmd_get_disabled_channels(\n+\tstruct mm81x *mors, struct host_cmd_resp_get_disabled_channels *resp,\n+\tuint resp_len)\n+{\n+\tstruct host_cmd_req req = {\n+\t\t.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_DISABLED_CHANNELS, 0),\n+\t};\n+\n+\treturn mm81x_cmd_tx(mors, (struct host_cmd_resp *)resp, \u0026req, resp_len,\n+\t\t\t    0);\n+}\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/command.h b/drivers/net/wireless/morsemicro/mm81x/command.h\nnew file mode 100644\nindex 00000000000000..0ea796f1d878ae\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/command.h\n@@ -0,0 +1,85 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#ifndef _MM81X_COMMAND_H_\n+#define _MM81X_COMMAND_H_\n+\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+#include \"core.h\"\n+#include \"command_defs.h\"\n+\n+#define HOST_CMD_IS_REQ(cmd) (le16_to_cpu((cmd)-\u003ehdr.flags) \u0026 HOST_CMD_TYPE_REQ)\n+#define HOST_CMD_IS_RESP(cmd) \\\n+\t(le16_to_cpu((cmd)-\u003ehdr.flags) \u0026 HOST_CMD_TYPE_RESP)\n+#define HOST_CMD_IS_EVT(cmd) (le16_to_cpu((cmd)-\u003ehdr.flags) \u0026 HOST_CMD_TYPE_EVT)\n+\n+struct mm81x_queue_params;\n+\n+enum mm81x_cmd_return_code {\n+\tMM81X_RET_SUCCESS = 0,\n+\tMM81X_RET_EPERM = -1,\n+\tMM81X_RET_ENOMEM = -12,\n+\tMM81X_RET_CMD_NOT_HANDLED = -32757,\n+};\n+\n+#define HOST_CMD_HOST_ID_SEQ_MAX 0xFFF\n+#define HOST_CMD_HOST_ID_RETRY_MASK 0x000F\n+#define HOST_CMD_HOST_ID_SEQ_SHIFT 4\n+#define HOST_CMD_HOST_ID_SEQ_MASK 0xFFF0\n+\n+struct host_cmd_req {\n+\tstruct host_cmd_header hdr;\n+\tu8 data[];\n+} __packed;\n+\n+struct host_cmd_resp {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\tu8 data[];\n+} __packed;\n+\n+struct host_cmd_event {\n+\tstruct host_cmd_header hdr;\n+\tu8 data[];\n+} __packed;\n+\n+int mm81x_cmd_resp_process(struct mm81x *mors, struct sk_buff *skb);\n+int mm81x_cmd_add_if(struct mm81x *mors, u16 *vif_id, const u8 *addr,\n+\t\t     enum nl80211_iftype type);\n+int mm81x_cmd_get_capabilities(struct mm81x *mors, u16 vif_id,\n+\t\t\t       struct mm81x_fw_caps *capabilities);\n+int mm81x_cmd_cfg_qos(struct mm81x *mors, struct mm81x_queue_params *params);\n+int mm81x_cmd_config_beacon_timer(struct mm81x *mors, void *mm81x_vif,\n+\t\t\t\t  bool enabled);\n+int mm81x_cmd_cfg_bss(struct mm81x *mors, u16 vif_id, u16 beacon_int,\n+\t\t      u16 dtim_period, u32 cssid);\n+int mm81x_cmd_set_channel(struct mm81x *mors, u32 op_chan_freq_hz,\n+\t\t\t  u8 pri_1mhz_chan_idx, u8 op_bw_mhz, u8 pri_bw_mhz,\n+\t\t\t  s32 *power_mbm);\n+int mm81x_cmd_get_max_txpower(struct mm81x *mors, s32 *out_power_mbm);\n+int mm81x_cmd_set_txpower(struct mm81x *mors, s32 *out_power_mbm,\n+\t\t\t  int txpower_mbm);\n+int mm81x_cmd_hw_scan(struct mm81x *mors, struct mm81x_hw_scan_params *params,\n+\t\t      bool store);\n+int mm81x_cmd_set_ps(struct mm81x *mors, bool enabled);\n+int mm81x_cmd_cfg_multicast_filter(struct mm81x *mors,\n+\t\t\t\t   struct mm81x_vif *mors_vif);\n+int mm81x_cmd_sta_state(struct mm81x *mors, struct mm81x_vif *mors_vif, u16 aid,\n+\t\t\tstruct ieee80211_sta *sta,\n+\t\t\tenum ieee80211_sta_state state);\n+int mm81x_cmd_install_key(struct mm81x *mors, struct mm81x_vif *mors_vif,\n+\t\t\t  u16 aid, struct ieee80211_key_conf *key,\n+\t\t\t  enum host_cmd_key_cipher cipher,\n+\t\t\t  enum host_cmd_aes_key_len length);\n+int mm81x_cmd_disable_key(struct mm81x *mors, struct mm81x_vif *mors_vif,\n+\t\t\t  u16 aid, struct ieee80211_key_conf *key);\n+int mm81x_cmd_rm_if(struct mm81x *mors, u16 vif_id);\n+int mm81x_cmd_set_frag_threshold(struct mm81x *mors, u32 frag_threshold);\n+int mm81x_cmd_get_disabled_channels(\n+\tstruct mm81x *mors, struct host_cmd_resp_get_disabled_channels *resp,\n+\tuint resp_len);\n+\n+#endif /* !_MM81X_COMMAND_H_ */\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/command_defs.h b/drivers/net/wireless/morsemicro/mm81x/command_defs.h\nnew file mode 100644\nindex 00000000000000..91a4ac09ad8080\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/command_defs.h\n@@ -0,0 +1,1658 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+#ifndef _MM81X_COMMAND_DEFS_H_\n+#define _MM81X_COMMAND_DEFS_H_\n+\n+#include \u003clinux/types.h\u003e\n+\n+#define __sle16 __le16\n+#define __sle32 __le32\n+#define __sle64 __le64\n+\n+#define HOST_CMD_SEMVER_MAJOR 56\n+#define HOST_CMD_SEMVER_MINOR 17\n+#define HOST_CMD_SEMVER_PATCH 0\n+\n+#define HOST_CMD_TYPE_REQ BIT(0)\n+#define HOST_CMD_TYPE_RESP BIT(1)\n+#define HOST_CMD_TYPE_EVT BIT(2)\n+\n+#define HOST_CMD_SSID_MAX_LEN 32\n+#define HOST_CMD_MAC_ADDR_LEN 6\n+\n+enum host_cmd_id {\n+\tHOST_CMD_ID_SET_CHANNEL = 0x0001,\n+\tHOST_CMD_ID_GET_CHANNEL = 0x001D,\n+\tHOST_CMD_ID_GET_CHANNEL_FULL = 0x0013,\n+\tHOST_CMD_ID_GET_CHANNEL_DTIM = 0x001C,\n+\tHOST_CMD_ID_GET_VERSION = 0x0002,\n+\tHOST_CMD_ID_SET_TXPOWER = 0x0003,\n+\tHOST_CMD_ID_GET_MAX_TXPOWER = 0x0024,\n+\tHOST_CMD_ID_ADD_INTERFACE = 0x0004,\n+\tHOST_CMD_ID_REMOVE_INTERFACE = 0x0005,\n+\tHOST_CMD_ID_BSS_CONFIG = 0x0006,\n+\tHOST_CMD_ID_SCAN_CONFIG = 0x0010,\n+\tHOST_CMD_ID_SET_QOS_PARAMS = 0x0011,\n+\tHOST_CMD_ID_GET_QOS_PARAMS = 0x0012,\n+\tHOST_CMD_ID_SET_STA_STATE = 0x0014,\n+\tHOST_CMD_ID_SET_BSS_COLOR = 0x0015,\n+\tHOST_CMD_ID_CONFIG_PS = 0x0016,\n+\tHOST_CMD_ID_HEALTH_CHECK = 0x0019,\n+\tHOST_CMD_ID_CTS_SELF_PS = 0x001A,\n+\tHOST_CMD_ID_DTIM_CHANNEL_ENABLE = 0x001B,\n+\tHOST_CMD_ID_ARP_OFFLOAD = 0x0020,\n+\tHOST_CMD_ID_SET_LONG_SLEEP_CONFIG = 0x0021,\n+\tHOST_CMD_ID_SET_DUTY_CYCLE = 0x0022,\n+\tHOST_CMD_ID_GET_DUTY_CYCLE = 0x0023,\n+\tHOST_CMD_ID_GET_CAPABILITIES = 0x0025,\n+\tHOST_CMD_ID_TWT_AGREEMENT_INSTALL = 0x0026,\n+\tHOST_CMD_ID_TWT_AGREEMENT_VALIDATE = 0x0036,\n+\tHOST_CMD_ID_TWT_AGREEMENT_REMOVE = 0x0027,\n+\tHOST_CMD_ID_GET_TSF = 0x0028,\n+\tHOST_CMD_ID_MAC_ADDR = 0x0029,\n+\tHOST_CMD_ID_MPSW_CONFIG = 0x0030,\n+\tHOST_CMD_ID_INSTALL_KEY = 0x000A,\n+\tHOST_CMD_ID_DISABLE_KEY = 0x000B,\n+\tHOST_CMD_ID_DHCP_OFFLOAD = 0x0032,\n+\tHOST_CMD_ID_SET_KEEP_ALIVE_OFFLOAD = 0x0033,\n+\tHOST_CMD_ID_UPDATE_OUI_FILTER = 0x0034,\n+\tHOST_CMD_ID_IBSS_CONFIG = 0x0035,\n+\tHOST_CMD_ID_OCS = 0x0038,\n+\tHOST_CMD_ID_MESH_CONFIG = 0x0039,\n+\tHOST_CMD_ID_SET_OFFSET_TSF = 0x003A,\n+\tHOST_CMD_ID_GET_CHANNEL_USAGE = 0x003B,\n+\tHOST_CMD_ID_MCAST_FILTER = 0x003C,\n+\tHOST_CMD_ID_BSS_BEACON_CONFIG = 0x003D,\n+\tHOST_CMD_ID_UAPSD_CONFIG = 0x0040,\n+\tHOST_CMD_ID_PAGE_SLICING_CONFIG = 0x0043,\n+\tHOST_CMD_ID_HW_SCAN = 0x0044,\n+\tHOST_CMD_ID_SET_WHITELIST = 0x0045,\n+\tHOST_CMD_ID_ARP_PERIODIC_REFRESH = 0x0046,\n+\tHOST_CMD_ID_SET_TCP_KEEPALIVE = 0x0047,\n+\tHOST_CMD_ID_FORCE_POWER_MODE = 0x0048,\n+\tHOST_CMD_ID_LI_SLEEP = 0x0049,\n+\tHOST_CMD_ID_GET_DISABLED_CHANNELS = 0x004A,\n+\tHOST_CMD_ID_SET_CQM_RSSI = 0x004F,\n+\tHOST_CMD_ID_GET_APF_CAPABILITIES = 0x0050,\n+\tHOST_CMD_ID_READ_WRITE_APF = 0x0051,\n+\tHOST_CMD_ID_BSSID_SET = 0x0052,\n+\tHOST_CMD_ID_BEACON_OFFLOAD = 0x0053,\n+\tHOST_CMD_ID_PROBE_RESPONSE_OFFLOAD = 0x0054,\n+\tHOST_CMD_ID_HOST_STATS_LOG = 0x2007,\n+\tHOST_CMD_ID_HOST_STATS_RESET = 0x2008,\n+\tHOST_CMD_ID_MAC_STATS_LOG = 0x200C,\n+\tHOST_CMD_ID_MAC_STATS_RESET = 0x200D,\n+\tHOST_CMD_ID_UPHY_STATS_LOG = 0x200E,\n+\tHOST_CMD_ID_UPHY_STATS_RESET = 0x200F,\n+\tHOST_CMD_ID_SET_STA_TYPE = 0xA000,\n+\tHOST_CMD_ID_SET_ENC_MODE = 0xA001,\n+\tHOST_CMD_ID_TEST_BA = 0xA002,\n+\tHOST_CMD_ID_SET_LISTEN_INTERVAL = 0xA003,\n+\tHOST_CMD_ID_SET_AMPDU = 0xA004,\n+\tHOST_CMD_ID_COREDUMP = 0xA006,\n+\tHOST_CMD_ID_SET_S1G_OP_CLASS = 0xA007,\n+\tHOST_CMD_ID_SEND_WAKE_ACTION_FRAME = 0xA008,\n+\tHOST_CMD_ID_VENDOR_IE_CONFIG = 0xA009,\n+\tHOST_CMD_ID_SET_TWT_CONF = 0xA010,\n+\tHOST_CMD_ID_GET_AVAILABLE_CHANNELS = 0xA011,\n+\tHOST_CMD_ID_SET_ECSA_S1G_INFO = 0xA012,\n+\tHOST_CMD_ID_GET_HW_VERSION = 0xA013,\n+\tHOST_CMD_ID_CAC = 0xA014,\n+\tHOST_CMD_ID_DRIVER_SET_DUTY_CYCLE = 0xA015,\n+\tHOST_CMD_ID_OCS_DRIVER = 0xA017,\n+\tHOST_CMD_ID_MBSSID = 0xA016,\n+\tHOST_CMD_ID_SET_MESH_CONFIG = 0xA018,\n+\tHOST_CMD_ID_SET_MCBA_CONF = 0xA019,\n+\tHOST_CMD_ID_DYNAMIC_PEERING_CONFIG = 0xA020,\n+\tHOST_CMD_ID_CONFIG_RAW = 0xA021,\n+\tHOST_CMD_ID_CONFIG_BSS_STATS = 0xA022,\n+\tHOST_CMD_ID_GET_RSSI = 0x1002,\n+\tHOST_CMD_ID_SET_IFS = 0x1003,\n+\tHOST_CMD_ID_SET_FEM_SETTINGS = 0x1005,\n+\tHOST_CMD_ID_SET_TXOP = 0x1008,\n+\tHOST_CMD_ID_SET_CONTROL_RESPONSE = 0x1009,\n+\tHOST_CMD_ID_SET_PERIODIC_CAL = 0x100A,\n+\tHOST_CMD_ID_SET_BCN_RSSI_THRESHOLD = 0x100B,\n+\tHOST_CMD_ID_SET_TX_PKT_LIFETIME_USECS = 0x100C,\n+\tHOST_CMD_ID_SET_PHYSM_WATCHDOG = 0x100D,\n+\tHOST_CMD_ID_TX_POLAR = 0x100E,\n+\tHOST_CMD_ID_EVT_STA_STATE = 0x4001,\n+\tHOST_CMD_ID_EVT_BEACON_LOSS = 0x4002,\n+\tHOST_CMD_ID_EVT_SIG_FIELD_ERROR = 0x4003,\n+\tHOST_CMD_ID_EVT_UMAC_TRAFFIC_CONTROL = 0x4004,\n+\tHOST_CMD_ID_EVT_DHCP_LEASE_UPDATE = 0x4005,\n+\tHOST_CMD_ID_EVT_OCS_DONE = 0x4006,\n+\tHOST_CMD_ID_EVT_HW_SCAN_DONE = 0x4011,\n+\tHOST_CMD_ID_EVT_CHANNEL_USAGE = 0x4012,\n+\tHOST_CMD_ID_EVT_CONNECTION_LOSS = 0x4013,\n+\tHOST_CMD_ID_EVT_SCHED_SCAN_RESULTS = 0x4014,\n+\tHOST_CMD_ID_EVT_CQM_RSSI_NOTIFY = 0x4015,\n+\tHOST_CMD_ID_EVT_SCAN_DONE = 0x4007,\n+\tHOST_CMD_ID_EVT_SCAN_RESULT = 0x4008,\n+\tHOST_CMD_ID_EVT_CONNECTED = 0x4009,\n+\tHOST_CMD_ID_EVT_DISCONNECTED = 0x4010,\n+\tHOST_CMD_ID_EVT_BEACON_FILTER_MATCH = 0x4016,\n+\tHOST_CMD_ID_SET_CAPABILITIES = 0x8118,\n+\tHOST_CMD_ID_SET_TRANSMISSION_RATE = 0x8009,\n+\tHOST_CMD_ID_FORCE_ASSERT = 0x800E,\n+\tHOST_CMD_ID_GET_SET_GENERIC_PARAM = 0x003E,\n+};\n+\n+struct host_cmd_mac_addr {\n+\tu8 octet[HOST_CMD_MAC_ADDR_LEN];\n+};\n+\n+enum host_cmd_ocs_subcmd {\n+\tHOST_CMD_OCS_SUBCMD_CONFIG = 1,\n+\tHOST_CMD_OCS_SUBCMD_STATUS = 2,\n+};\n+\n+enum host_cmd_headless_cfg_option {\n+\tHOST_CMD_HEADLESS_CFG_OPTION_KEEP_IFACES = BIT(0),\n+\tHOST_CMD_HEADLESS_CFG_OPTION_BUFFER_RX = BIT(1),\n+\tHOST_CMD_HEADLESS_CFG_OPTION_NOTIFY_ON_ANY_RX = BIT(2),\n+};\n+\n+struct host_cmd_header {\n+\t__le16 flags;\n+\t__le16 message_id;\n+\t__le16 len;\n+\t__le16 host_id;\n+\t__le16 vif_id;\n+\t__le16 pad;\n+};\n+\n+#define HOST_CMD_CHANNEL_BW_NOT_SET 0xFF\n+#define HOST_CMD_CHANNEL_IDX_NOT_SET 0xFF\n+#define HOST_CMD_CHANNEL_FREQ_NOT_SET 0xFFFFFFFF\n+\n+enum host_cmd_dot11_proto_mode {\n+\tHOST_CMD_DOT11_PROTO_MODE_AH = 0,\n+};\n+\n+struct host_cmd_req_set_channel {\n+\tstruct host_cmd_header hdr;\n+\t__le32 op_chan_freq_hz;\n+\tu8 op_bw_mhz;\n+\tu8 pri_bw_mhz;\n+\tu8 pri_1mhz_chan_idx;\n+\tu8 dot11_mode;\n+\tu8 __deprecated_reg_tx_power_set;\n+\tu8 is_off_channel;\n+} __packed;\n+\n+struct host_cmd_resp_set_channel {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__sle32 power_qdbm;\n+} __packed;\n+\n+struct host_cmd_req_get_channel {\n+\tstruct host_cmd_header hdr;\n+} __packed;\n+\n+struct host_cmd_resp_get_channel {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__le32 op_chan_freq_hz;\n+\tu8 op_chan_bw_mhz;\n+\tu8 pri_chan_bw_mhz;\n+\tu8 pri_1mhz_chan_idx;\n+} __packed;\n+\n+#define HOST_CMD_MAX_VERSION_LEN 128\n+\n+struct host_cmd_req_get_version {\n+\tstruct host_cmd_header hdr;\n+} __packed;\n+\n+struct host_cmd_resp_get_version {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__sle32 length;\n+\tu8 version[];\n+} __packed;\n+\n+struct host_cmd_req_set_txpower {\n+\tstruct host_cmd_header hdr;\n+\t__sle32 power_qdbm;\n+} __packed;\n+\n+struct host_cmd_resp_set_txpower {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__sle32 power_qdbm;\n+} __packed;\n+\n+struct host_cmd_req_get_max_txpower {\n+\tstruct host_cmd_header hdr;\n+} __packed;\n+\n+struct host_cmd_resp_get_max_txpower {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__sle32 power_qdbm;\n+} __packed;\n+\n+enum host_cmd_interface_type {\n+\tHOST_CMD_INTERFACE_TYPE_INVALID = 0,\n+\tHOST_CMD_INTERFACE_TYPE_STA = 1,\n+\tHOST_CMD_INTERFACE_TYPE_AP = 2,\n+\tHOST_CMD_INTERFACE_TYPE_MON = 3,\n+\tHOST_CMD_INTERFACE_TYPE_ADHOC = 4,\n+\tHOST_CMD_INTERFACE_TYPE_MESH = 5,\n+\tHOST_CMD_INTERFACE_TYPE_LAST = HOST_CMD_INTERFACE_TYPE_MESH,\n+};\n+\n+struct host_cmd_req_add_interface {\n+\tstruct host_cmd_header hdr;\n+\tstruct host_cmd_mac_addr addr;\n+\t__le32 interface_type;\n+} __packed;\n+\n+struct host_cmd_resp_add_interface {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_remove_interface {\n+\tstruct host_cmd_header hdr;\n+} __packed;\n+\n+struct host_cmd_resp_remove_interface {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_bss_config {\n+\tstruct host_cmd_header hdr;\n+\t__le16 beacon_interval_tu;\n+\t__le16 dtim_period;\n+\tu8 __padding[2];\n+\t__le32 cssid;\n+} __packed;\n+\n+struct host_cmd_resp_bss_config {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_scan_config {\n+\tstruct host_cmd_header hdr;\n+\tu8 enabled;\n+\tu8 is_survey;\n+} __packed;\n+\n+struct host_cmd_resp_scan_config {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_set_qos_params {\n+\tstruct host_cmd_header hdr;\n+\tu8 uapsd;\n+\tu8 queue_idx;\n+\tu8 aifs_slot_count;\n+\t__le16 contention_window_min;\n+\t__le16 contention_window_max;\n+\t__le32 max_txop_usec;\n+} __packed;\n+\n+struct host_cmd_resp_set_qos_params {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_get_qos_params {\n+\tstruct host_cmd_header hdr;\n+\tu8 queue_idx;\n+} __packed;\n+\n+struct host_cmd_resp_get_qos_params {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\tu8 aifs_slot_count;\n+\t__le16 contention_window_min;\n+\t__le16 contention_window_max;\n+\t__le32 max_txop_usec;\n+} __packed;\n+\n+struct host_cmd_req_set_sta_state {\n+\tstruct host_cmd_header hdr;\n+\tu8 sta_addr[HOST_CMD_MAC_ADDR_LEN];\n+\t__le16 aid;\n+\t__le16 state;\n+\tu8 uapsd_queues;\n+\t__le32 flags;\n+} __packed;\n+\n+struct host_cmd_resp_set_sta_state {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_set_bss_color {\n+\tstruct host_cmd_header hdr;\n+\tu8 bss_color;\n+} __packed;\n+\n+struct host_cmd_resp_set_bss_color {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_config_ps {\n+\tstruct host_cmd_header hdr;\n+\tu8 enabled;\n+\tu8 dynamic_ps_offload;\n+} __packed;\n+\n+struct host_cmd_resp_config_ps {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_health_check {\n+\tstruct host_cmd_header hdr;\n+} __packed;\n+\n+struct host_cmd_resp_health_check {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_cts_self_ps {\n+\tstruct host_cmd_header hdr;\n+\tu8 enable;\n+} __packed;\n+\n+struct host_cmd_resp_cts_self_ps {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_dtim_channel_enable {\n+\tstruct host_cmd_header hdr;\n+\tu8 enable;\n+} __packed;\n+\n+struct host_cmd_resp_dtim_channel_enable {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+#define HOST_CMD_ARP_OFFLOAD_MAX_IP_ADDRESSES 4\n+\n+struct host_cmd_req_arp_offload {\n+\tstruct host_cmd_header hdr;\n+\t__be32 ip_table[HOST_CMD_ARP_OFFLOAD_MAX_IP_ADDRESSES];\n+} __packed;\n+\n+struct host_cmd_resp_arp_offload {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_set_long_sleep_config {\n+\tstruct host_cmd_header hdr;\n+\tu8 enabled;\n+} __packed;\n+\n+struct host_cmd_resp_set_long_sleep_config {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+#define HOST_CMD_DUTY_CYCLE_SET_CFG_DUTY_CYCLE BIT(0)\n+#define HOST_CMD_DUTY_CYCLE_SET_CFG_OMIT_CONTROL_RESP BIT(1)\n+#define HOST_CMD_DUTY_CYCLE_SET_CFG_EXT BIT(2)\n+#define HOST_CMD_DUTY_CYCLE_SET_CFG_BURST_RECORD_UNIT BIT(3)\n+\n+enum host_cmd_duty_cycle_mode {\n+\tHOST_CMD_DUTY_CYCLE_MODE_SPREAD = 0,\n+\tHOST_CMD_DUTY_CYCLE_MODE_BURST = 1,\n+\tHOST_CMD_DUTY_CYCLE_MODE_LAST = HOST_CMD_DUTY_CYCLE_MODE_BURST,\n+};\n+\n+struct host_cmd_duty_cycle_configuration {\n+\tu8 omit_control_responses;\n+\t__le32 duty_cycle;\n+} __packed;\n+\n+struct host_cmd_duty_cycle_set_configuration_ext {\n+\t__le32 burst_record_unit_us;\n+\tu8 mode;\n+} __packed;\n+\n+struct host_cmd_duty_cycle_configuration_ext {\n+\t__le32 airtime_remaining_us;\n+\t__le32 burst_window_duration_us;\n+\tstruct host_cmd_duty_cycle_set_configuration_ext set;\n+} __packed;\n+\n+struct host_cmd_req_set_duty_cycle {\n+\tstruct host_cmd_header hdr;\n+\tstruct host_cmd_duty_cycle_configuration config;\n+\tu8 set_cfgs;\n+\tstruct host_cmd_duty_cycle_set_configuration_ext config_ext;\n+} __packed;\n+\n+struct host_cmd_resp_get_duty_cycle {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\tstruct host_cmd_duty_cycle_configuration config;\n+\tstruct host_cmd_duty_cycle_configuration_ext config_ext;\n+} __packed;\n+\n+#define HOST_CMD_SET_S1G_CAP_FLAGS BIT(0)\n+#define HOST_CMD_SET_S1G_CAP_AMPDU_MSS BIT(1)\n+#define HOST_CMD_SET_S1G_CAP_BEAM_STS BIT(2)\n+#define HOST_CMD_SET_S1G_CAP_NUM_SOUND_DIMS BIT(3)\n+#define HOST_CMD_SET_S1G_CAP_MAX_AMPDU_LEXP BIT(4)\n+#define HOST_CMD_SET_MORSE_CAP_MMSS_OFFSET BIT(5)\n+#define HOST_CMD_S1G_CAPABILITY_FLAGS_WIDTH 4\n+\n+struct host_cmd_mm_capabilities {\n+\t__le32 flags[HOST_CMD_S1G_CAPABILITY_FLAGS_WIDTH];\n+\tu8 ampdu_mss;\n+\tu8 beamformee_sts_capability;\n+\tu8 number_sounding_dimensions;\n+\tu8 maximum_ampdu_length_exponent;\n+} __packed;\n+\n+struct host_cmd_req_get_capabilities {\n+\tstruct host_cmd_header hdr;\n+} __packed;\n+\n+struct host_cmd_resp_get_capabilities {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\tstruct host_cmd_mm_capabilities capabilities;\n+\tu8 morse_mmss_offset;\n+} __packed;\n+\n+#define HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN 20\n+\n+struct host_cmd_req_twt_agreement_install {\n+\tstruct host_cmd_header hdr;\n+\tu8 flow_id;\n+\tu8 agreement_len;\n+\tu8 agreement[HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN];\n+} __packed;\n+\n+struct host_cmd_resp_twt_agreement_install {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_twt_agreement_validate {\n+\tstruct host_cmd_header hdr;\n+\tu8 flow_id;\n+\tu8 agreement_len;\n+\tu8 agreement[HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN];\n+} __packed;\n+\n+struct host_cmd_resp_twt_agreement_validate {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_twt_agreement_remove {\n+\tstruct host_cmd_header hdr;\n+\tu8 flow_id;\n+} __packed;\n+\n+struct host_cmd_req_get_tsf {\n+\tstruct host_cmd_header hdr;\n+} __packed;\n+\n+struct host_cmd_resp_get_tsf {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__le64 now_tsf;\n+\t__le64 now_chip_ts;\n+} __packed;\n+\n+struct host_cmd_req_mac_addr {\n+\tstruct host_cmd_header hdr;\n+\tu8 write;\n+\tu8 octet[HOST_CMD_MAC_ADDR_LEN];\n+} __packed;\n+\n+struct host_cmd_resp_mac_addr {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\tu8 octet[HOST_CMD_MAC_ADDR_LEN];\n+} __packed;\n+\n+#define HOST_CMD_SET_MPSW_CFG_AIRTIME_BOUNDS BIT(0)\n+#define HOST_CMD_SET_MPSW_CFG_PKT_SPC_WIN_LEN BIT(1)\n+#define HOST_CMD_SET_MPSW_CFG_ENABLED BIT(2)\n+\n+struct host_cmd_mpsw_configuration {\n+\t__le32 airtime_max_us;\n+\t__le32 airtime_min_us;\n+\t__le32 packet_space_window_length_us;\n+\tu8 enable;\n+} __packed;\n+\n+struct host_cmd_req_mpsw_config {\n+\tstruct host_cmd_header hdr;\n+\tstruct host_cmd_mpsw_configuration config;\n+\tu8 set_cfgs;\n+} __packed;\n+\n+struct host_cmd_resp_mpsw_config {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\tstruct host_cmd_mpsw_configuration config;\n+} __packed;\n+\n+#define HOST_CMD_MAX_KEY_LEN 32\n+\n+enum host_cmd_key_cipher {\n+\tHOST_CMD_KEY_CIPHER_INVALID = 0,\n+\tHOST_CMD_KEY_CIPHER_AES_CCM = 1,\n+\tHOST_CMD_KEY_CIPHER_AES_GCM = 2,\n+\tHOST_CMD_KEY_CIPHER_AES_CMAC = 3,\n+\tHOST_CMD_KEY_CIPHER_AES_GMAC = 4,\n+\tHOST_CMD_KEY_CIPHER_LAST = HOST_CMD_KEY_CIPHER_AES_GMAC,\n+};\n+\n+enum host_cmd_aes_key_len {\n+\tHOST_CMD_AES_KEY_LEN_INVALID = 0,\n+\tHOST_CMD_AES_KEY_LEN_LENGTH_128 = 1,\n+\tHOST_CMD_AES_KEY_LEN_LENGTH_256 = 2,\n+\tHOST_CMD_AES_KEY_LEN_LENGTH_LAST = HOST_CMD_AES_KEY_LEN_LENGTH_256,\n+};\n+\n+enum host_cmd_temporal_key_type {\n+\tHOST_CMD_TEMPORAL_KEY_TYPE_INVALID = 0,\n+\tHOST_CMD_TEMPORAL_KEY_TYPE_GTK = 1,\n+\tHOST_CMD_TEMPORAL_KEY_TYPE_PTK = 2,\n+\tHOST_CMD_TEMPORAL_KEY_TYPE_IGTK = 3,\n+\tHOST_CMD_TEMPORAL_KEY_TYPE_LAST = HOST_CMD_TEMPORAL_KEY_TYPE_IGTK,\n+};\n+\n+struct host_cmd_req_install_key {\n+\tstruct host_cmd_header hdr;\n+\t__le64 pn;\n+\t__le32 aid;\n+\tu8 key_idx;\n+\tu8 cipher;\n+\tu8 key_length;\n+\tu8 key_type;\n+\tu8 __padding[2];\n+\tu8 key[HOST_CMD_MAX_KEY_LEN];\n+} __packed;\n+\n+struct host_cmd_resp_install_key {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\tu8 key_idx;\n+} __packed;\n+\n+struct host_cmd_req_disable_key {\n+\tstruct host_cmd_header hdr;\n+\t__le32 key_type;\n+\t__le32 aid;\n+\tu8 key_idx;\n+} __packed;\n+\n+struct host_cmd_resp_disable_key {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+enum host_cmd_dhcp_opcode {\n+\tHOST_CMD_DHCP_OPCODE_ENABLE = 0,\n+\tHOST_CMD_DHCP_OPCODE_DO_DISCOVERY = 1,\n+\tHOST_CMD_DHCP_OPCODE_GET_LEASE = 2,\n+\tHOST_CMD_DHCP_OPCODE_CLEAR_LEASE = 3,\n+\tHOST_CMD_DHCP_OPCODE_RENEW_LEASE = 4,\n+\tHOST_CMD_DHCP_OPCODE_REBIND_LEASE = 5,\n+\tHOST_CMD_DHCP_OPCODE_SEND_LEASE_UPDATE = 6,\n+};\n+\n+enum host_cmd_dhcp_retcode {\n+\tHOST_CMD_DHCP_RETCODE_SUCCESS = 0,\n+\tHOST_CMD_DHCP_RETCODE_NOT_ENABLED = 1,\n+\tHOST_CMD_DHCP_RETCODE_ALREADY_ENABLED = 2,\n+\tHOST_CMD_DHCP_RETCODE_NO_LEASE = 3,\n+\tHOST_CMD_DHCP_RETCODE_HAVE_LEASE = 4,\n+\tHOST_CMD_DHCP_RETCODE_BUSY = 5,\n+\tHOST_CMD_DHCP_RETCODE_BAD_VIF = 6,\n+};\n+\n+struct host_cmd_req_dhcp_offload {\n+\tstruct host_cmd_header hdr;\n+\t__le32 opcode;\n+} __packed;\n+\n+struct host_cmd_resp_dhcp_offload {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__le32 retcode;\n+\t__le32 my_ip;\n+\t__le32 netmask;\n+\t__le32 router;\n+\t__le32 dns;\n+} __packed;\n+\n+struct host_cmd_req_set_keep_alive_offload {\n+\tstruct host_cmd_header hdr;\n+\t__le16 bss_max_idle_period;\n+\tu8 interpret_as_11ah;\n+} __packed;\n+\n+#define HOST_CMD_MAX_OUI_FILTERS 5\n+#define HOST_CMD_OUI_SIZE 3\n+#define HOST_CMD_MAX_OUI_FILTER_ARRAY_SIZE 15\n+\n+struct host_cmd_req_update_oui_filter {\n+\tstruct host_cmd_header hdr;\n+\tu8 n_ouis;\n+\tu8 ouis[HOST_CMD_MAX_OUI_FILTERS][HOST_CMD_OUI_SIZE];\n+} __packed;\n+\n+enum host_cmd_ibss_config_opcode {\n+\tHOST_CMD_IBSS_CONFIG_OPCODE_CREATE = 0,\n+\tHOST_CMD_IBSS_CONFIG_OPCODE_JOIN = 1,\n+\tHOST_CMD_IBSS_CONFIG_OPCODE_STOP = 2,\n+};\n+\n+struct host_cmd_req_ibss_config {\n+\tstruct host_cmd_header hdr;\n+\tu8 ibss_bssid[HOST_CMD_MAC_ADDR_LEN];\n+\tu8 ibss_cfg_opcode;\n+\tu8 ibss_probe_filtering;\n+} __packed;\n+\n+enum host_cmd_ocs_type {\n+\tHOST_CMD_OCS_TYPE_QNULL = 0,\n+\tHOST_CMD_OCS_TYPE_RAW = 1,\n+};\n+\n+struct host_cmd_ocs_config_req {\n+\t__le32 op_channel_freq_hz;\n+\tu8 op_channel_bw_mhz;\n+\tu8 pri_channel_bw_mhz;\n+\tu8 pri_1mhz_channel_index;\n+\t__le16 aid;\n+\tu8 type;\n+} __packed;\n+\n+struct host_cmd_ocs_status_resp {\n+\tu8 running;\n+} __packed;\n+\n+struct host_cmd_req_ocs {\n+\tstruct host_cmd_header hdr;\n+\t__le32 subcmd;\n+\tunion {\n+\t\tu8 opaque[0];\n+\t\tstruct host_cmd_ocs_config_req config;\n+\t};\n+} __packed;\n+\n+struct host_cmd_resp_ocs {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__le32 subcmd;\n+\tunion {\n+\t\tu8 opaque[0];\n+\t\tstruct host_cmd_ocs_status_resp ocs_status;\n+\t};\n+} __packed;\n+\n+enum host_cmd_mesh_config_opcode {\n+\tHOST_CMD_MESH_CONFIG_OPCODE_START = 0,\n+\tHOST_CMD_MESH_CONFIG_OPCODE_STOP = 1,\n+};\n+\n+struct host_cmd_req_mesh_config {\n+\tstruct host_cmd_header hdr;\n+\tu8 mesh_cfg_opcode;\n+\tu8 enable_beaconing;\n+\tu8 mbca_config;\n+\tu8 min_beacon_gap_ms;\n+\t__le16 mbss_start_scan_duration_ms;\n+\t__le16 tbtt_adj_timer_interval_ms;\n+} __packed;\n+\n+struct host_cmd_req_set_offset_tsf {\n+\tstruct host_cmd_header hdr;\n+\t__sle64 offset_tsf;\n+} __packed;\n+\n+struct host_cmd_req_get_channel_usage {\n+\tstruct host_cmd_header hdr;\n+} __packed;\n+\n+struct host_cmd_resp_get_channel_usage {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__le64 time_listen;\n+\t__le64 busy_time;\n+\t__le32 freq_hz;\n+\ts8 noise;\n+\tu8 bw_mhz;\n+} __packed;\n+\n+#define HOST_CMD_MAX_MCAST_FILTERS 12\n+\n+struct host_cmd_req_mcast_filter {\n+\tstruct host_cmd_header hdr;\n+\tu8 count;\n+\t__le32 hw_addr[];\n+} __packed;\n+\n+struct host_cmd_req_bss_beacon_config {\n+\tstruct host_cmd_header hdr;\n+\tu8 enable;\n+} __packed;\n+\n+struct host_cmd_resp_bss_beacon_config {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__le16 interface_id;\n+} __packed;\n+\n+struct host_cmd_req_uapsd_config {\n+\tstruct host_cmd_header hdr;\n+\tu8 auto_trigger_enabled;\n+\t__le32 auto_trigger_timeout;\n+} __packed;\n+\n+struct host_cmd_resp_uapsd_config {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\tu8 auto_trigger_enabled;\n+} __packed;\n+\n+struct host_cmd_req_page_slicing_config {\n+\tstruct host_cmd_header hdr;\n+\tu8 enable;\n+} __packed;\n+\n+#define HOST_CMD_HW_SCAN_FLAGS_START BIT(0)\n+#define HOST_CMD_HW_SCAN_FLAGS_ABORT BIT(1)\n+#define HOST_CMD_HW_SCAN_FLAGS_SURVEY BIT(2)\n+#define HOST_CMD_HW_SCAN_FLAGS_STORE BIT(3)\n+#define HOST_CMD_HW_SCAN_FLAGS_1MHZ_PROBES BIT(4)\n+#define HOST_CMD_HW_SCAN_FLAGS_SCHED_START BIT(5)\n+#define HOST_CMD_HW_SCAN_FLAGS_SCHED_STOP BIT(6)\n+#define HOST_CMD_HW_SCAN_FLAGS_PROBE_ON_DOZE_BEACON BIT(7)\n+\n+enum host_cmd_hw_scan_tlv_tag {\n+\tHOST_CMD_HW_SCAN_TLV_TAG_PAD = 0,\n+\tHOST_CMD_HW_SCAN_TLV_TAG_PROBE_REQ = 1,\n+\tHOST_CMD_HW_SCAN_TLV_TAG_CHAN_LIST = 2,\n+\tHOST_CMD_HW_SCAN_TLV_TAG_POWER_LIST = 3,\n+\tHOST_CMD_HW_SCAN_TLV_TAG_DWELL_ON_HOME = 4,\n+\tHOST_CMD_HW_SCAN_TLV_TAG_SCHED = 5,\n+\tHOST_CMD_HW_SCAN_TLV_TAG_FILTER = 6,\n+\tHOST_CMD_HW_SCAN_TLV_TAG_SCHED_PARAMS = 7,\n+};\n+\n+struct host_cmd_hw_scan_tlv {\n+\t__le16 tag;\n+\t__le16 len;\n+\tu8 value[];\n+} __packed;\n+\n+struct host_cmd_req_hw_scan {\n+\tstruct host_cmd_header hdr;\n+\t__le32 flags;\n+\t__le32 dwell_time_ms;\n+\tu8 variable[];\n+} __packed;\n+\n+#define HOST_CMD_WHITELIST_FLAGS_CLEAR BIT(0)\n+\n+struct host_cmd_req_set_whitelist {\n+\tstruct host_cmd_header hdr;\n+\tu8 flags;\n+\tu8 ip_protocol;\n+\t__be16 llc_protocol;\n+\t__be32 src_ip;\n+\t__be32 dest_ip;\n+\t__be32 netmask;\n+\t__be16 src_port;\n+\t__be16 dest_port;\n+} __packed;\n+\n+struct host_cmd_arp_periodic_params {\n+\t__le32 refresh_period_s;\n+\t__le32 destination_ip;\n+\tu8 send_as_garp;\n+} __packed;\n+\n+struct host_cmd_req_arp_periodic_refresh {\n+\tstruct host_cmd_header hdr;\n+\tstruct host_cmd_arp_periodic_params config;\n+} __packed;\n+\n+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_PERIOD BIT(0)\n+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_RETRY_COUNT BIT(1)\n+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_RETRY_INTERVAL BIT(2)\n+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_SRC_IP_ADDR BIT(3)\n+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_DEST_IP_ADDR BIT(4)\n+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_SRC_PORT BIT(5)\n+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_DEST_PORT BIT(6)\n+\n+struct host_cmd_req_set_tcp_keepalive {\n+\tstruct host_cmd_header hdr;\n+\tu8 enabled;\n+\tu8 retry_count;\n+\tu8 retry_interval_s;\n+\tu8 set_cfgs;\n+\t__be32 src_ip;\n+\t__be32 dest_ip;\n+\t__be16 src_port;\n+\t__be16 dest_port;\n+\t__le16 period_s;\n+} __packed;\n+\n+enum host_cmd_power_mode {\n+\tHOST_CMD_POWER_MODE_SNOOZE = 0,\n+\tHOST_CMD_POWER_MODE_DEEP_SLEEP = 1,\n+\tHOST_CMD_POWER_MODE_HIBERNATE = 2,\n+};\n+\n+struct host_cmd_req_force_power_mode {\n+\tstruct host_cmd_header hdr;\n+\t__le32 mode;\n+} __packed;\n+\n+struct host_cmd_req_li_sleep {\n+\tstruct host_cmd_header hdr;\n+\t__le32 listen_interval;\n+} __packed;\n+\n+struct host_cmd_disabled_channel_entry {\n+\t__le16 freq_100khz;\n+\tu8 bw_mhz;\n+} __packed;\n+\n+struct host_cmd_resp_get_disabled_channels {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__le32 n_channels;\n+\tstruct host_cmd_disabled_channel_entry channels[];\n+} __packed;\n+\n+struct host_cmd_req_set_cqm_rssi {\n+\tstruct host_cmd_header hdr;\n+\t__sle32 threshold;\n+\t__le32 hysteresis;\n+} __packed;\n+\n+struct host_cmd_req_get_apf_capabilities {\n+\tstruct host_cmd_header hdr;\n+} __packed;\n+\n+struct host_cmd_resp_get_apf_capabilities {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__le32 max_length;\n+\tu8 version;\n+} __packed;\n+\n+struct host_cmd_req_read_write_apf {\n+\tstruct host_cmd_header hdr;\n+\t__le32 offset;\n+\t__le16 program_length;\n+\tu8 write;\n+\tu8 program[];\n+} __packed;\n+\n+struct host_cmd_resp_read_write_apf {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__le16 program_length;\n+\tu8 program[];\n+} __packed;\n+\n+struct host_cmd_req_bssid_set {\n+\tstruct host_cmd_header hdr;\n+\tstruct host_cmd_mac_addr bssid;\n+} __packed;\n+\n+#define HOST_CMD_BEACON_OFFLOAD_FLAGS_START BIT(0)\n+#define HOST_CMD_BEACON_OFFLOAD_FLAGS_STOP BIT(1)\n+#define HOST_CMD_BEACON_OFFLOAD_CSSID_LEN 4\n+\n+enum host_cmd_beacon_offload_tlv_tag {\n+\tHOST_CMD_BEACON_OFFLOAD_TLV_TAG_DTIM_CNT = 0,\n+\tHOST_CMD_BEACON_OFFLOAD_TLV_TAG_FRAME_CTRL = 1,\n+\tHOST_CMD_BEACON_OFFLOAD_TLV_TAG_CHANGE_SEQ = 2,\n+\tHOST_CMD_BEACON_OFFLOAD_TLV_TAG_CSSID = 3,\n+\tHOST_CMD_BEACON_OFFLOAD_TLV_TAG_IES = 4,\n+\tHOST_CMD_BEACON_OFFLOAD_TLV_TAG_TX_INFO = 5,\n+};\n+\n+struct host_cmd_beacon_offload_tlv_hdr {\n+\t__le16 tag;\n+\t__le16 len;\n+} __packed;\n+\n+struct host_cmd_beacon_offload_tlv_generic {\n+\tstruct host_cmd_beacon_offload_tlv_hdr hdr;\n+\tu8 value[];\n+} __packed;\n+\n+struct host_cmd_beacon_offload_tlv_dtim_cnt {\n+\tstruct host_cmd_beacon_offload_tlv_hdr hdr;\n+\t__le16 dtim_cnt;\n+} __packed;\n+\n+struct host_cmd_beacon_offload_tlv_frame_ctrl {\n+\tstruct host_cmd_beacon_offload_tlv_hdr hdr;\n+\tu8 frame_ctrl[2];\n+} __packed;\n+\n+struct host_cmd_beacon_offload_tlv_change_seq {\n+\tstruct host_cmd_beacon_offload_tlv_hdr hdr;\n+\t__le16 change_seq;\n+} __packed;\n+\n+struct host_cmd_beacon_offload_tlv_tx_info {\n+\tstruct host_cmd_beacon_offload_tlv_hdr hdr;\n+\tu8 bw_mhz;\n+} __packed;\n+\n+struct host_cmd_beacon_offload_tlv_cssid {\n+\tstruct host_cmd_beacon_offload_tlv_hdr hdr;\n+\tu8 cssid[HOST_CMD_BEACON_OFFLOAD_CSSID_LEN];\n+} __packed;\n+\n+struct host_cmd_beacon_offload_tlv_ies {\n+\tstruct host_cmd_beacon_offload_tlv_hdr hdr;\n+\tu8 buf[];\n+} __packed;\n+\n+struct host_cmd_req_beacon_offload {\n+\tstruct host_cmd_header hdr;\n+\t__le32 flags;\n+\tu8 variable[];\n+} __packed;\n+\n+struct host_cmd_resp_beacon_offload {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__le16 dtim_count;\n+} __packed;\n+\n+struct host_cmd_req_probe_response_offload {\n+\tstruct host_cmd_header hdr;\n+\tu8 enable;\n+\t__le16 probe_resp_len;\n+\tu8 probe_resp_buf[];\n+} __packed;\n+\n+struct host_cmd_resp_probe_response_offload {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_set_sta_type {\n+\tstruct host_cmd_header hdr;\n+\tu8 sta_type;\n+} __packed;\n+\n+struct host_cmd_req_set_enc_mode {\n+\tstruct host_cmd_header hdr;\n+\tu8 enc_mode;\n+} __packed;\n+\n+struct host_cmd_req_test_ba {\n+\tstruct host_cmd_header hdr;\n+\tu8 addr[HOST_CMD_MAC_ADDR_LEN];\n+\tu8 start;\n+\tu8 tx;\n+\t__le32 tid;\n+} __packed;\n+\n+struct host_cmd_req_set_listen_interval {\n+\tstruct host_cmd_header hdr;\n+\t__le16 listen_interval;\n+} __packed;\n+\n+struct host_cmd_req_set_ampdu {\n+\tstruct host_cmd_header hdr;\n+\tu8 ampdu_enabled;\n+} __packed;\n+\n+struct host_cmd_req_set_s1g_op_class {\n+\tstruct host_cmd_header hdr;\n+\tu8 opclass;\n+\tu8 prim_opclass;\n+} __packed;\n+\n+struct host_cmd_req_send_wake_action_frame {\n+\tstruct host_cmd_header hdr;\n+\tu8 dest_addr[HOST_CMD_MAC_ADDR_LEN];\n+\t__le32 payload_size;\n+\tu8 payload[];\n+} __packed;\n+\n+#define HOST_CMD_MAX_VENDOR_IE_LENGTH 255\n+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_BEACON BIT(0)\n+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_PROBE_REQ BIT(1)\n+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_PROBE_RESP BIT(2)\n+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_ASSOC_REQ BIT(3)\n+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_ASSOC_RESP BIT(4)\n+\n+enum host_cmd_vendor_ie_op {\n+\tHOST_CMD_VENDOR_IE_OP_ADD_ELEMENT = 0,\n+\tHOST_CMD_VENDOR_IE_OP_CLEAR_ELEMENTS = 1,\n+\tHOST_CMD_VENDOR_IE_OP_ADD_FILTER = 2,\n+\tHOST_CMD_VENDOR_IE_OP_CLEAR_FILTERS = 3,\n+\tHOST_CMD_VENDOR_IE_OP_INVALID = U16_MAX,\n+};\n+\n+struct host_cmd_req_vendor_ie_config {\n+\tstruct host_cmd_header hdr;\n+\t__le16 opcode;\n+\t__le16 mgmt_type_mask;\n+\tu8 data[HOST_CMD_MAX_VENDOR_IE_LENGTH];\n+} __packed;\n+\n+struct host_cmd_resp_vendor_ie_config {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+enum host_cmd_twt_conf_op {\n+\tHOST_CMD_TWT_CONF_OP_CONFIGURE = 0,\n+\tHOST_CMD_TWT_CONF_OP_FORCE_INSTALL_AGREEMENT = 1,\n+\tHOST_CMD_TWT_CONF_OP_REMOVE_AGREEMENT = 2,\n+\tHOST_CMD_TWT_CONF_OP_CONFIGURE_EXPLICIT = 3,\n+};\n+\n+struct host_cmd_explicit_twt_wake_interval {\n+\t__le16 wake_interval_mantissa;\n+\tu8 wake_interval_exponent;\n+\tu8 __padding[5];\n+} __packed;\n+\n+union host_cmd_wake_interval {\n+\t__le64 wake_interval_us;\n+\tstruct host_cmd_explicit_twt_wake_interval explicit_twt;\n+} __packed;\n+\n+struct host_cmd_req_set_twt_conf {\n+\tstruct host_cmd_header hdr;\n+\tu8 opcode;\n+\tu8 flow_id;\n+\t__le64 target_wake_time;\n+\tunion host_cmd_wake_interval wake_interval;\n+\t__le32 wake_duration_us;\n+\tu8 twt_setup_command;\n+\tu8 __padding[3];\n+} __packed;\n+\n+#define HOST_CMD_MAX_AVAILABLE_CHANNELS 255\n+\n+struct host_cmd_channel_info {\n+\t__le32 frequency_khz;\n+\tu8 channel_5g;\n+\tu8 channel_s1g;\n+\tu8 bandwidth_mhz;\n+} __packed;\n+\n+struct host_cmd_resp_get_available_channels {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__le32 num_channels;\n+\tstruct host_cmd_channel_info channels[HOST_CMD_MAX_AVAILABLE_CHANNELS];\n+} __packed;\n+\n+#define HOST_CMD_S1G_CAP0_S1G_LONG BIT(0)\n+#define HOST_CMD_S1G_CAP0_SGI_1MHZ BIT(1)\n+#define HOST_CMD_S1G_CAP0_SGI_2MHZ BIT(2)\n+#define HOST_CMD_S1G_CAP0_SGI_4MHZ BIT(3)\n+#define HOST_CMD_S1G_CAP0_SGI_8MHZ BIT(4)\n+#define HOST_CMD_S1G_CAP0_SGI_16MHZ BIT(5)\n+\n+struct host_cmd_req_set_ecsa_s1g_info {\n+\tstruct host_cmd_header hdr;\n+\t__le32 operating_channel_freq_hz;\n+\tu8 opclass;\n+\tu8 primary_channel_bw_mhz;\n+\tu8 prim_1mhz_ch_idx;\n+\tu8 operating_channel_bw_mhz;\n+\tu8 prim_opclass;\n+\tu8 s1g_cap0;\n+\tu8 s1g_cap1;\n+\tu8 s1g_cap2;\n+\tu8 s1g_cap3;\n+} __packed;\n+\n+struct host_cmd_resp_get_hw_version {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\tu8 hw_version[64];\n+} __packed;\n+\n+#define HOST_CMD_CAC_CFG_CHANGE_RULE_MAX 8\n+#define HOST_CMD_CAC_CFG_ARFS_MAX 99\n+#define HOST_CMD_CAC_CFG_CHANGE_MAX 99\n+#define HOST_CMD_CAC_CFG_CHANGE_STEP 5\n+\n+enum host_cmd_cac_op {\n+\tHOST_CMD_CAC_OP_DISABLE = 0,\n+\tHOST_CMD_CAC_OP_ENABLE = 1,\n+\tHOST_CMD_CAC_OP_CFG_GET = 2,\n+\tHOST_CMD_CAC_OP_CFG_SET = 3,\n+};\n+\n+struct host_cmd_cac_change_rule {\n+\t__le16 arfs;\n+\t__sle16 threshold_change;\n+} __packed;\n+\n+struct host_cmd_req_cac {\n+\tstruct host_cmd_header hdr;\n+\tu8 opcode;\n+\tu8 rule_tot;\n+\tstruct host_cmd_cac_change_rule rule[HOST_CMD_CAC_CFG_CHANGE_RULE_MAX];\n+} __packed;\n+\n+struct host_cmd_resp_cac {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\tu8 rule_tot;\n+\tstruct host_cmd_cac_change_rule rule[HOST_CMD_CAC_CFG_CHANGE_RULE_MAX];\n+} __packed;\n+\n+struct host_cmd_ocs_driver_req {\n+\t__le32 op_channel_freq_hz;\n+\tu8 op_channel_bw_mhz;\n+\tu8 pri_channel_bw_mhz;\n+\tu8 pri_1mhz_channel_index;\n+} __packed;\n+\n+struct host_cmd_ocs_driver_resp {\n+\tu8 running;\n+} __packed;\n+\n+struct host_cmd_req_ocs_driver {\n+\tstruct host_cmd_header hdr;\n+\t__le32 subcmd;\n+\tunion {\n+\t\tu8 opaque[0];\n+\t\tstruct host_cmd_ocs_driver_req config;\n+\t};\n+} __packed;\n+\n+struct host_cmd_resp_ocs_driver {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__le32 subcmd;\n+\tunion {\n+\t\tu8 opaque[0];\n+\t\tstruct host_cmd_ocs_driver_resp ocs_status;\n+\t};\n+} __packed;\n+\n+#define HOST_CMD_IFNAMSIZ 16\n+\n+struct host_cmd_req_mbssid {\n+\tstruct host_cmd_header hdr;\n+\tu8 max_bssid_indicator;\n+\ts8 transmitter_iface[HOST_CMD_IFNAMSIZ];\n+} __packed;\n+\n+#define HOST_CMD_MESH_ID_LEN_MAX 32\n+#define HOST_CMD_MESH_BEACONLESS_MODE_DISABLE 0\n+#define HOST_CMD_MESH_BEACONLESS_MODE_ENABLE 1\n+#define HOST_CMD_MESH_PEER_LINKS_MIN 0\n+#define HOST_CMD_MESH_PEER_LINKS_MAX 10\n+\n+struct host_cmd_req_set_mesh_config {\n+\tstruct host_cmd_header hdr;\n+\tu8 mesh_id_len;\n+\tu8 mesh_id[HOST_CMD_MESH_ID_LEN_MAX];\n+\tu8 mesh_beaconless_mode;\n+\tu8 max_plinks;\n+} __packed;\n+\n+struct host_cmd_req_set_mcba_conf {\n+\tstruct host_cmd_header hdr;\n+\tu8 mbca_config;\n+\tu8 beacon_timing_report_interval;\n+\tu8 min_beacon_gap_ms;\n+\t__le16 mbss_start_scan_duration_ms;\n+\t__le16 tbtt_adj_interval_ms;\n+} __packed;\n+\n+struct host_cmd_req_dynamic_peering_config {\n+\tstruct host_cmd_header hdr;\n+\tu8 enabled;\n+\tu8 rssi_margin;\n+\t__le32 blacklist_timeout;\n+} __packed;\n+\n+#define HOST_CMD_CFG_RAW_FLAG_ENABLE BIT(0)\n+#define HOST_CMD_CFG_RAW_FLAG_DELETE BIT(1)\n+#define HOST_CMD_CFG_RAW_FLAG_UPDATE BIT(2)\n+#define HOST_CMD_CFG_RAW_FLAG_DYNAMIC BIT(3)\n+#define HOST_CMD_RAW_RESERVED_AID_DCS 2008\n+#define HOST_CMD_RAW_RESERVED_AID_DOWNLINK 2009\n+\n+enum host_cmd_raw_tlv_tag {\n+\tHOST_CMD_RAW_TLV_TAG_SLOT_DEF = 0,\n+\tHOST_CMD_RAW_TLV_TAG_GROUP = 1,\n+\tHOST_CMD_RAW_TLV_TAG_START_TIME = 2,\n+\tHOST_CMD_RAW_TLV_TAG_PRAW = 3,\n+\tHOST_CMD_RAW_TLV_TAG_BCN_SPREAD = 4,\n+\tHOST_CMD_RAW_TLV_TAG_DYN_GLOBAL = 5,\n+\tHOST_CMD_RAW_TLV_TAG_DYN_CONFIG = 6,\n+\tHOST_CMD_RAW_TLV_TAG_LAST = 7,\n+};\n+\n+struct host_cmd_raw_tlv_slot_def {\n+\tu8 tag;\n+\t__le32 raw_duration_us;\n+\tu8 num_slots;\n+\tu8 cross_slot_bleed;\n+} __packed;\n+\n+struct host_cmd_raw_tlv_group {\n+\tu8 tag;\n+\t__le16 aid_start;\n+\t__le16 aid_end;\n+} __packed;\n+\n+struct host_cmd_raw_tlv_start_time {\n+\tu8 tag;\n+\t__le32 start_time_us;\n+} __packed;\n+\n+struct host_cmd_raw_tlv_praw {\n+\tu8 tag;\n+\tu8 periodicity;\n+\tu8 validity;\n+\tu8 start_offset;\n+\tu8 refresh_on_expiry;\n+} __packed;\n+\n+struct host_cmd_raw_tlv_bcn_spread {\n+\tu8 tag;\n+\t__le16 max_spread;\n+\t__le16 nominal_sta_per_bcn;\n+} __packed;\n+\n+struct host_cmd_raw_tlv_dyn_global {\n+\tu8 tag;\n+\t__le16 num_configs;\n+\t__le16 num_bcn_indexes;\n+} __packed;\n+\n+struct host_cmd_raw_tlv_dyn_config {\n+\tu8 tag;\n+\t__le16 id;\n+\t__le16 index;\n+\t__le16 len;\n+\tu8 variable[];\n+} __packed;\n+\n+union host_cmd_raw_tlvs {\n+\tu8 tag;\n+\tstruct host_cmd_raw_tlv_slot_def slot_def;\n+\tstruct host_cmd_raw_tlv_group group;\n+\tstruct host_cmd_raw_tlv_start_time start_time;\n+\tstruct host_cmd_raw_tlv_praw praw;\n+\tstruct host_cmd_raw_tlv_bcn_spread bcn_spread;\n+\tstruct host_cmd_raw_tlv_dyn_global dyn_global;\n+\tstruct host_cmd_raw_tlv_dyn_config dyn_config;\n+} __packed;\n+\n+struct host_cmd_req_config_raw {\n+\tstruct host_cmd_header hdr;\n+\t__le32 flags;\n+\t__le16 id;\n+\tu8 variable[];\n+} __packed;\n+\n+struct host_cmd_req_config_bss_stats {\n+\tstruct host_cmd_header hdr;\n+\tu8 enable;\n+\t__le32 monitor_window_ms;\n+} __packed;\n+\n+struct host_cmd_req_get_rssi {\n+\tstruct host_cmd_header hdr;\n+} __packed;\n+\n+struct host_cmd_resp_get_rssi {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__sle32 rssi0;\n+\t__sle32 rssi1;\n+\t__sle32 rssi2;\n+\t__sle32 rssi3;\n+\t__sle32 rssi4;\n+\t__sle32 rssi5;\n+\t__sle32 rssi6;\n+\t__sle32 rssi7;\n+} __packed;\n+\n+#define HOST_CMD_SET_IFS_MIN_USECS 160\n+\n+struct host_cmd_req_set_ifs {\n+\tstruct host_cmd_header hdr;\n+\t__le32 period_usecs;\n+} __packed;\n+\n+struct host_cmd_resp_set_ifs {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_set_fem_settings {\n+\tstruct host_cmd_header hdr;\n+\t__le32 tx_antenna;\n+\t__le32 rx_antenna;\n+\t__le32 lna_enabled;\n+\t__le32 pa_enabled;\n+} __packed;\n+\n+struct host_cmd_resp_set_fem_settings {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_set_txop {\n+\tstruct host_cmd_header hdr;\n+\tu8 min_packet_count;\n+} __packed;\n+\n+struct host_cmd_resp_set_txop {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_set_control_response {\n+\tstruct host_cmd_header hdr;\n+\tu8 direction;\n+\tu8 control_response_1mhz_en;\n+} __packed;\n+\n+struct host_cmd_resp_set_control_response {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_set_periodic_cal {\n+\tstruct host_cmd_header hdr;\n+\t__le32 periodic_cal_en_mask;\n+} __packed;\n+\n+struct host_cmd_resp_set_periodic_cal {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_set_bcn_rssi_threshold {\n+\tstruct host_cmd_header hdr;\n+\tu8 threshold_db;\n+} __packed;\n+\n+struct host_cmd_resp_set_bcn_rssi_threshold {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_set_tx_pkt_lifetime_usecs {\n+\tstruct host_cmd_header hdr;\n+\t__le32 lifetime_usecs;\n+} __packed;\n+\n+struct host_cmd_resp_set_tx_pkt_lifetime_usecs {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_set_physm_watchdog {\n+\tstruct host_cmd_header hdr;\n+\tu8 physm_watchdog_en;\n+} __packed;\n+\n+struct host_cmd_req_tx_polar {\n+\tstruct host_cmd_header hdr;\n+\tu8 enable;\n+} __packed;\n+\n+struct host_cmd_evt_sta_state {\n+\tstruct host_cmd_header hdr;\n+\tu8 sta_addr[HOST_CMD_MAC_ADDR_LEN];\n+\t__le16 aid;\n+\t__le16 state;\n+} __packed;\n+\n+struct host_cmd_evt_beacon_loss {\n+\tstruct host_cmd_header hdr;\n+\t__le32 num_bcns;\n+} __packed;\n+\n+struct host_cmd_evt_sig_field_error {\n+\tstruct host_cmd_header hdr;\n+\t__le64 start_timestamp;\n+\t__le64 end_timestamp;\n+} __packed;\n+\n+#define HOST_CMD_UMAC_TRAFFIC_CONTROL_SOURCE_TWT BIT(0)\n+#define HOST_CMD_UMAC_TRAFFIC_CONTROL_SOURCE_DUTY_CYCLE BIT(1)\n+\n+struct host_cmd_evt_umac_traffic_control {\n+\tstruct host_cmd_header hdr;\n+\tu8 pause_data_traffic;\n+\t__le32 sources;\n+} __packed;\n+\n+struct host_cmd_evt_dhcp_lease_update {\n+\tstruct host_cmd_header hdr;\n+\t__le32 my_ip;\n+\t__le32 netmask;\n+\t__le32 router;\n+\t__le32 dns;\n+} __packed;\n+\n+struct host_cmd_evt_ocs_done {\n+\tstruct host_cmd_header hdr;\n+\t__le64 time_listen;\n+\t__le64 time_rx;\n+\ts8 noise;\n+\tu8 metric;\n+} __packed;\n+\n+struct host_cmd_evt_hw_scan_done {\n+\tstruct host_cmd_header hdr;\n+\tu8 aborted;\n+} __packed;\n+\n+struct host_cmd_evt_channel_usage {\n+\tstruct host_cmd_header hdr;\n+\t__le64 time_listen;\n+\t__le64 busy_time;\n+\t__le32 freq_hz;\n+\tu8 noise;\n+\tu8 bw_mhz;\n+} __packed;\n+\n+enum host_cmd_connection_loss_reason {\n+\tHOST_CMD_CONNECTION_LOSS_REASON_TSF_RESET = 0,\n+};\n+\n+struct host_cmd_evt_connection_loss {\n+\tstruct host_cmd_header hdr;\n+\t__le32 reason;\n+} __packed;\n+\n+struct host_cmd_evt_sched_scan_results {\n+\tstruct host_cmd_header hdr;\n+} __packed;\n+\n+enum host_cmd_cqm_rssi_threshold_event {\n+\tHOST_CMD_CQM_RSSI_THRESHOLD_EVENT_LOW = 0,\n+\tHOST_CMD_CQM_RSSI_THRESHOLD_EVENT_HIGH = 1,\n+};\n+\n+struct host_cmd_evt_cqm_rssi_notify {\n+\tstruct host_cmd_header hdr;\n+\t__sle16 rssi;\n+\t__le16 event;\n+} __packed;\n+\n+struct host_cmd_evt_scan_done {\n+\tstruct host_cmd_header hdr;\n+\tu8 aborted;\n+} __packed;\n+\n+enum host_cmd_scan_result_frame {\n+\tHOST_CMD_SCAN_RESULT_FRAME_UNKNOWN = 0,\n+\tHOST_CMD_SCAN_RESULT_FRAME_BEACON = 1,\n+\tHOST_CMD_SCAN_RESULT_FRAME_PROBE_RESPONSE = 2,\n+};\n+\n+struct host_cmd_evt_scan_result {\n+\tstruct host_cmd_header hdr;\n+\t__le32 channel_freq_hz;\n+\tu8 bw_mhz;\n+\tu8 frame_type;\n+\t__sle16 rssi;\n+\tu8 bssid[HOST_CMD_MAC_ADDR_LEN];\n+\t__le16 beacon_interval;\n+\t__le16 capability_info;\n+\t__le64 tsf;\n+\t__le16 ies_len;\n+\tu8 ies[];\n+} __packed;\n+\n+struct host_cmd_evt_connected {\n+\tstruct host_cmd_header hdr;\n+\tu8 bssid[HOST_CMD_MAC_ADDR_LEN];\n+\t__sle16 rssi;\n+\tu8 padding_0[8];\n+\t__le16 assoc_resp_ies_len;\n+\tu8 assoc_resp_ies[];\n+} __packed;\n+\n+struct host_cmd_evt_beacon_filter_match {\n+\tstruct host_cmd_header hdr;\n+\tu8 padding_0[4];\n+\t__le32 ies_len;\n+\tu8 ies[];\n+} __packed;\n+\n+struct host_cmd_req_set_capabilities {\n+\tstruct host_cmd_header hdr;\n+\tstruct host_cmd_mm_capabilities capabilities;\n+\tu8 set_caps;\n+\tu8 morse_mmss_offset;\n+} __packed;\n+\n+struct host_cmd_resp_set_capabilities {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+struct host_cmd_req_set_transmission_rate {\n+\tstruct host_cmd_header hdr;\n+\t__sle32 mcs_index;\n+\t__sle32 bandwidth_mhz;\n+\t__sle32 tx_80211ah_format;\n+\ts8 use_traveling_pilots;\n+\ts8 use_sgi;\n+\tu8 enabled;\n+\ts8 nss_idx;\n+\ts8 use_ldpc;\n+\ts8 use_stbc;\n+} __packed;\n+\n+struct host_cmd_resp_set_transmission_rate {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+} __packed;\n+\n+enum host_cmd_hart_id {\n+\tHOST_CMD_HART_ID_HOST = 0,\n+\tHOST_CMD_HART_ID_MAC = 1,\n+\tHOST_CMD_HART_ID_UPHY = 2,\n+\tHOST_CMD_HART_ID_LPHY = 3,\n+};\n+\n+struct host_cmd_req_force_assert {\n+\tstruct host_cmd_header hdr;\n+\t__le32 hart_id;\n+} __packed;\n+\n+#define HOST_CMD_HOST_BLOCK_TX_FRAMES BIT(0)\n+#define HOST_CMD_HOST_BLOCK_TX_CMD BIT(1)\n+\n+enum host_cmd_param_action {\n+\tHOST_CMD_PARAM_ACTION_SET = 0,\n+\tHOST_CMD_PARAM_ACTION_GET = 1,\n+\tHOST_CMD_PARAM_ACTION_LAST = 2,\n+};\n+\n+enum host_cmd_slow_clock_mode {\n+\tHOST_CMD_SLOW_CLOCK_MODE_AUTO = 0,\n+\tHOST_CMD_SLOW_CLOCK_MODE_INTERNAL = 1,\n+};\n+\n+enum host_cmd_param_id {\n+\tHOST_CMD_PARAM_ID_MAX_TRAFFIC_DELIVERY_WAIT_US = 0,\n+\tHOST_CMD_PARAM_ID_EXTRA_ACK_TIMEOUT_ADJUST_US = 1,\n+\tHOST_CMD_PARAM_ID_TX_STATUS_FLUSH_WATERMARK = 2,\n+\tHOST_CMD_PARAM_ID_TX_STATUS_FLUSH_MIN_AMPDU_SIZE = 3,\n+\tHOST_CMD_PARAM_ID_POWERSAVE_TYPE = 4,\n+\tHOST_CMD_PARAM_ID_SNOOZE_DURATION_ADJUST_US = 5,\n+\tHOST_CMD_PARAM_ID_TX_BLOCK = 6,\n+\tHOST_CMD_PARAM_ID_FORCED_SNOOZE_PERIOD_US = 7,\n+\tHOST_CMD_PARAM_ID_WAKE_ACTION_GPIO = 8,\n+\tHOST_CMD_PARAM_ID_WAKE_ACTION_GPIO_PULSE_MS = 9,\n+\tHOST_CMD_PARAM_ID_CONNECTION_MONITOR_GPIO = 10,\n+\tHOST_CMD_PARAM_ID_INPUT_TRIGGER_GPIO = 11,\n+\tHOST_CMD_PARAM_ID_INPUT_TRIGGER_MODE = 12,\n+\tHOST_CMD_PARAM_ID_COUNTRY = 13,\n+\tHOST_CMD_PARAM_ID_RTS_THRESHOLD = 14,\n+\tHOST_CMD_PARAM_ID_HOST_TX_BLOCK = 15,\n+\tHOST_CMD_PARAM_ID_MEM_RETENTION_CODE = 16,\n+\tHOST_CMD_PARAM_ID_NON_TIM_MODE = 17,\n+\tHOST_CMD_PARAM_ID_DYNAMIC_PS_TIMEOUT_MS = 18,\n+\tHOST_CMD_PARAM_ID_HOME_CHANNEL_DWELL_MS = 19,\n+\tHOST_CMD_PARAM_ID_SLOW_CLOCK_MODE = 20,\n+\tHOST_CMD_PARAM_ID_FRAGMENT_THRESHOLD = 21,\n+\tHOST_CMD_PARAM_ID_BEACON_LOSS_COUNT = 22,\n+\tHOST_CMD_PARAM_ID_AP_POWER_SAVE = 23,\n+\tHOST_CMD_PARAM_ID_BEACON_OFFLOAD = 24,\n+\tHOST_CMD_PARAM_ID_PROBE_RESP_OFFLOAD = 25,\n+\tHOST_CMD_PARAM_ID_BSS_MAX_AWAY_DURATION = 26,\n+\tHOST_CMD_PARAM_ID_DEFAULT_ACTIVE_SCAN_DWELL_MS = 27,\n+\tHOST_CMD_PARAM_ID_CTS_TO_SELF = 28,\n+\tHOST_CMD_PARAM_ID_CHANNELIZATION = 29,\n+\tHOST_CMD_PARAM_ID_LAST = 30,\n+};\n+\n+struct host_cmd_req_get_set_generic_param {\n+\tstruct host_cmd_header hdr;\n+\t__le32 param_id;\n+\t__le32 action;\n+\t__le32 flags;\n+\t__le32 value;\n+} __packed;\n+\n+struct host_cmd_resp_get_set_generic_param {\n+\tstruct host_cmd_header hdr;\n+\t__le32 status;\n+\t__le32 flags;\n+\t__le32 value;\n+} __packed;\n+\n+#endif\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/core.c b/drivers/net/wireless/morsemicro/mm81x/core.c\nnew file mode 100644\nindex 00000000000000..33548a6aea6c31\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/core.c\n@@ -0,0 +1,142 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+#include \u003clinux/module.h\u003e\n+#include \"core.h\"\n+#include \"bus.h\"\n+#include \"hif.h\"\n+#include \"mac.h\"\n+\n+static int mm81x_core_attach_regs(struct mm81x *mors)\n+{\n+\tint ret = 0;\n+\n+\tmm81x_claim_bus(mors);\n+\tret = mm81x_reg32_read(mors, MM8108_REG_CHIP_ID, \u0026mors-\u003echip_id);\n+\tmm81x_release_bus(mors);\n+\n+\tif (ret \u003c 0) {\n+\t\tdev_err(mors-\u003edev, \"failed to read chip id %d\", ret);\n+\t\treturn ret;\n+\t}\n+\n+\tswitch (mors-\u003echip_id) {\n+\tcase (MM8108B2_ID):\n+\t\tmors-\u003eregs = \u0026mm8108_regs;\n+\t\tmors-\u003ehif.ops = \u0026mm81x_yaps_ops;\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -ENODEV;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static char *mm81x_core_get_revision_string(u32 chip_id)\n+{\n+\tu8 chip_rev = MM81X_DEVICE_GET_CHIP_REV(chip_id);\n+\n+\tswitch (chip_rev) {\n+\tcase MM8108B2_REV:\n+\t\treturn MM8108B2_REV_STRING;\n+\tdefault:\n+\t\treturn \"??\";\n+\t}\n+}\n+\n+static void mm81x_core_init_mac_addr(struct mm81x *mors)\n+{\n+\tint ret = mm81x_hw_otp_get_mac_addr(mors);\n+\n+\tif (ret || !is_valid_ether_addr(mors-\u003emacaddr))\n+\t\teth_random_addr(mors-\u003emacaddr);\n+}\n+\n+char *mm81x_core_get_fw_path(u32 chip_id)\n+{\n+\treturn kasprintf(GFP_KERNEL,\n+\t\t\t MM81X_FW_DIR \"/\" MM8108_FW_BASE\n+\t\t\t\t      \"%s\" FW_ROM_LINKED_STRING MM81X_FW_EXT,\n+\t\t\t mm81x_core_get_revision_string(chip_id));\n+}\n+EXPORT_SYMBOL_GPL(mm81x_core_get_fw_path);\n+\n+struct mm81x *mm81x_core_alloc(size_t priv_size, struct device *dev)\n+{\n+\treturn mm81x_mac_alloc(priv_size, dev);\n+}\n+EXPORT_SYMBOL_GPL(mm81x_core_alloc);\n+\n+int mm81x_core_init(struct mm81x *mors)\n+{\n+\tint ret;\n+\n+\tset_bit(MM81X_STATE_CHIP_UNRESPONSIVE, \u0026mors-\u003estate_flags);\n+\tset_bit(MM81X_STATE_RELOAD_FW_AFTER_START, \u0026mors-\u003estate_flags);\n+\n+\tmm81x_core_init_mac_addr(mors);\n+\n+\tret = mm81x_core_attach_regs(mors);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tmors-\u003echip_wq = create_singlethread_workqueue(\"chip_wq\");\n+\tif (!mors-\u003echip_wq)\n+\t\treturn -ENOMEM;\n+\n+\tmors-\u003enet_wq = create_singlethread_workqueue(\"net_wq\");\n+\tif (!mors-\u003enet_wq) {\n+\t\tret = -ENOMEM;\n+\t\tgoto err_chip_wq;\n+\t}\n+\n+\tret = mm81x_hif_init(mors);\n+\tif (ret)\n+\t\tgoto err_wqs;\n+\n+\treturn 0;\n+\n+err_wqs:\n+\tflush_workqueue(mors-\u003enet_wq);\n+\tdestroy_workqueue(mors-\u003enet_wq);\n+\n+err_chip_wq:\n+\tflush_workqueue(mors-\u003echip_wq);\n+\tdestroy_workqueue(mors-\u003echip_wq);\n+\n+\treturn ret;\n+}\n+EXPORT_SYMBOL_GPL(mm81x_core_init);\n+\n+int mm81x_core_register(struct mm81x *mors)\n+{\n+\treturn mm81x_mac_register(mors);\n+}\n+EXPORT_SYMBOL_GPL(mm81x_core_register);\n+\n+void mm81x_core_unregister(struct mm81x *mors)\n+{\n+\tmm81x_mac_unregister(mors);\n+}\n+EXPORT_SYMBOL_GPL(mm81x_core_unregister);\n+\n+void mm81x_core_deinit(struct mm81x *mors)\n+{\n+\tmm81x_hif_finish(mors);\n+\tflush_workqueue(mors-\u003enet_wq);\n+\tdestroy_workqueue(mors-\u003enet_wq);\n+\tflush_workqueue(mors-\u003echip_wq);\n+\tdestroy_workqueue(mors-\u003echip_wq);\n+}\n+EXPORT_SYMBOL_GPL(mm81x_core_deinit);\n+\n+void mm81x_core_free(struct mm81x *mors)\n+{\n+\tmm81x_mac_free(mors);\n+}\n+EXPORT_SYMBOL_GPL(mm81x_core_free);\n+\n+MODULE_AUTHOR(\"Morse Micro\");\n+MODULE_DESCRIPTION(\"Driver support for Morse Micro MM81X core\");\n+MODULE_LICENSE(\"Dual BSD/GPL\");\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/core.h b/drivers/net/wireless/morsemicro/mm81x/core.h\nnew file mode 100644\nindex 00000000000000..d5b6dae59bcf1c\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/core.h\n@@ -0,0 +1,464 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#ifndef _MM81X_CORE_H_\n+#define _MM81X_CORE_H_\n+\n+#include \u003cnet/mac80211.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+#include \u003clinux/interrupt.h\u003e\n+#include \u003clinux/kfifo.h\u003e\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/version.h\u003e\n+#include \u003clinux/crc32.h\u003e\n+#include \u003clinux/notifier.h\u003e\n+#include \u003clinux/nospec.h\u003e\n+#include \u003clinux/wait.h\u003e\n+#include \"yaps.h\"\n+#include \"yaps_hw.h\"\n+#include \"hw.h\"\n+#include \"fw.h\"\n+#include \"rc.h\"\n+\n+#define MM81X_DRIVER_SEMVER_MAJOR 56\n+#define MM81X_DRIVER_SEMVER_MINOR 3\n+#define MM81X_DRIVER_SEMVER_PATCH 0\n+\n+#define MM81X_SEMVER_GET_MAJOR(x) (((x) \u003e\u003e 22) \u0026 0x3FF)\n+#define MM81X_SEMVER_GET_MINOR(x) (((x) \u003e\u003e 10) \u0026 0xFFF)\n+#define MM81X_SEMVER_GET_PATCH(x) ((x) \u0026 0x3FF)\n+\n+#define DRV_VERSION __stringify(MM81X_VERSION)\n+\n+#define MM8108_FW_BASE \"mm8108\"\n+\n+#define BCF_SIZE_MAX 48\n+\n+/* Generate a device ID from chip ID, revision, and chip type */\n+#define MM81X_DEVICE_ID(chip_id, chip_rev, chip_type) \\\n+\t((chip_id) | ((chip_rev) \u003c\u003c 8) | ((chip_type) \u003c\u003c 12))\n+\n+/* Get constituents of the device ID */\n+#define MM81X_DEVICE_GET_CHIP_ID(device_id) ((device_id) \u0026 0xff)\n+#define MM81X_DEVICE_GET_CHIP_REV(device_id) ((((device_id) \u003e\u003e 8) \u0026 0xf))\n+#define MM81X_DEVICE_GET_CHIP_TYPE(device_id) ((((device_id) \u003e\u003e 12) \u0026 0xf))\n+\n+#define KHZ100_TO_MHZ(x) ((x) / 10)\n+#define KHZ100_TO_KHZ(freq) ((freq) * 100)\n+#define KHZ100_TO_HZ(freq) ((freq) * 100000)\n+\n+#define QDBM_TO_MBM(gain) (((gain) * 100) \u003e\u003e 2)\n+#define MBM_TO_QDBM(gain) (((gain) \u003c\u003c 2) / 100)\n+#define QDBM_TO_DBM(gain) ((gain) / 4)\n+\n+#define BPS_TO_KBPS(x) ((x) / 1000)\n+\n+#define NSS_IDX_TO_NSS(x) ((x) + 1)\n+#define NSS_TO_NSS_IDX(x) ((x) - 1)\n+\n+#define ROUND_BYTES_TO_WORD(_nbytes) \\\n+\t(((_nbytes) + 3) \u0026 ~((typeof(_nbytes))0x03))\n+\n+struct mm81x_bus_ops;\n+struct mm81x_hif_ops;\n+\n+#define MM81X_CAPS_MAX_FW_VAL (128)\n+\n+/* Max number of interfaces */\n+#define MM81X_MAX_IF (2)\n+\n+enum mm81x_caps_flags {\n+\tMM81X_CAPS_FW_START = 0,\n+\tMM81X_CAPS_2MHZ = MM81X_CAPS_FW_START,\n+\tMM81X_CAPS_4MHZ,\n+\tMM81X_CAPS_8MHZ,\n+\tMM81X_CAPS_16MHZ,\n+\tMM81X_CAPS_SGI,\n+\tMM81X_CAPS_S1G_LONG,\n+\tMM81X_CAPS_TRAVELING_PILOT_ONE_STREAM,\n+\tMM81X_CAPS_TRAVELING_PILOT_TWO_STREAM,\n+\tMM81X_CAPS_MU_BEAMFORMEE,\n+\tMM81X_CAPS_MU_BEAMFORMER,\n+\tMM81X_CAPS_RD_RESPONDER,\n+\tMM81X_CAPS_STA_TYPE_SENSOR,\n+\tMM81X_CAPS_STA_TYPE_NON_SENSOR,\n+\tMM81X_CAPS_GROUP_AID,\n+\tMM81X_CAPS_NON_TIM,\n+\tMM81X_CAPS_TIM_ADE,\n+\tMM81X_CAPS_BAT,\n+\tMM81X_CAPS_DYNAMIC_AID,\n+\tMM81X_CAPS_UPLINK_SYNC,\n+\tMM81X_CAPS_FLOW_CONTROL,\n+\tMM81X_CAPS_AMPDU,\n+\tMM81X_CAPS_AMSDU,\n+\tMM81X_CAPS_1MHZ_CONTROL_RESPONSE_PREAMBLE,\n+\tMM81X_CAPS_PAGE_SLICING,\n+\tMM81X_CAPS_RAW,\n+\tMM81X_CAPS_MCS8,\n+\tMM81X_CAPS_MCS9,\n+\tMM81X_CAPS_ASYMMETRIC_BA_SUPPORT,\n+\tMM81X_CAPS_DAC,\n+\tMM81X_CAPS_CAC,\n+\tMM81X_CAPS_TXOP_SHARING_IMPLICIT_ACK,\n+\tMM81X_CAPS_NDP_PSPOLL,\n+\tMM81X_CAPS_FRAGMENT_BA,\n+\tMM81X_CAPS_OBSS_MITIGATION,\n+\tMM81X_CAPS_TMP_PS_MODE_SWITCH,\n+\tMM81X_CAPS_SECTOR_TRAINING,\n+\tMM81X_CAPS_UNSOLICIT_DYNAMIC_AID,\n+\tMM81X_CAPS_NDP_BEAMFORMING_REPORT,\n+\tMM81X_CAPS_MCS_NEGOTIATION,\n+\tMM81X_CAPS_DUPLICATE_1MHZ,\n+\tMM81X_CAPS_TACK_AS_PSPOLL,\n+\tMM81X_CAPS_PV1,\n+\tMM81X_CAPS_TWT_RESPONDER,\n+\tMM81X_CAPS_TWT_REQUESTER,\n+\tMM81X_CAPS_BDT,\n+\tMM81X_CAPS_TWT_GROUPING,\n+\tMM81X_CAPS_LINK_ADAPTATION_WO_NDP_CMAC,\n+\tMM81X_CAPS_LONG_MPDU,\n+\tMM81X_CAPS_TXOP_SECTORIZATION,\n+\tMM81X_CAPS_GROUP_SECTORIZATION,\n+\tMM81X_CAPS_HTC_VHT,\n+\tMM81X_CAPS_HTC_VHT_MFB,\n+\tMM81X_CAPS_HTC_VHT_MRQ,\n+\tMM81X_CAPS_2SS,\n+\tMM81X_CAPS_3SS,\n+\tMM81X_CAPS_4SS,\n+\tMM81X_CAPS_SU_BEAMFORMEE,\n+\tMM81X_CAPS_SU_BEAMFORMER,\n+\tMM81X_CAPS_RX_STBC,\n+\tMM81X_CAPS_TX_STBC,\n+\tMM81X_CAPS_RX_LDPC,\n+\tMM81X_CAPS_HW_FRAGMENT,\n+\n+\tMM81X_CAPS_FW_END = MM81X_CAPS_MAX_FW_VAL,\n+\tMM81X_CAPS_LAST = MM81X_CAPS_FW_END,\n+};\n+\n+struct mm81x_fw_caps {\n+\tu32 flags[FW_CAPABILITIES_FLAGS_WIDTH];\n+\tu8 ampdu_mss;\n+\tu8 beamformee_sts_capability;\n+\tu8 number_sounding_dimensions;\n+\tu8 maximum_ampdu_length_exponent;\n+\tu8 mm81x_mmss_offset;\n+};\n+\n+#define MM81X_FW_SUPP(MM81X_CAPS, CAPABILITY) \\\n+\tmm81x_caps_supported(MM81X_CAPS, MM81X_CAPS_##CAPABILITY)\n+\n+static inline bool mm81x_caps_supported(struct mm81x_fw_caps *caps,\n+\t\t\t\t\tenum mm81x_caps_flags flag)\n+{\n+\tconst unsigned long *flags_ptr = (unsigned long *)caps-\u003eflags;\n+\n+\treturn test_bit(flag, flags_ptr);\n+}\n+\n+struct mm81x_ps {\n+\tu32 wakers;\n+\tbool enable;\n+\tbool suspended;\n+\t/* PS state lock */\n+\tstruct mutex lock;\n+\tstruct delayed_work delayed_eval_work;\n+};\n+\n+enum mm81x_page_aci {\n+\tMM81X_ACI_BE = 0,\n+\tMM81X_ACI_BK = 1,\n+\tMM81X_ACI_VI = 2,\n+\tMM81X_ACI_VO = 3,\n+};\n+\n+enum mm81x_qos_tid_up_index {\n+\tMM81X_QOS_TID_UP_BK = 1,\n+\tMM81X_QOS_TID_UP_XX = 2,\n+\tMM81X_QOS_TID_UP_BE = 0,\n+\tMM81X_QOS_TID_UP_EE = 3,\n+\tMM81X_QOS_TID_UP_CL = 4,\n+\tMM81X_QOS_TID_UP_VI = 5,\n+\tMM81X_QOS_TID_UP_VO = 6,\n+\tMM81X_QOS_TID_UP_NC = 7,\n+\n+\tMM81X_QOS_TID_UP_LOWEST = MM81X_QOS_TID_UP_BK,\n+\tMM81X_QOS_TID_UP_HIGHEST = MM81X_QOS_TID_UP_NC\n+};\n+\n+struct mm81x_sw_version {\n+\tu8 major;\n+\tu8 minor;\n+\tu8 patch;\n+};\n+\n+struct mm81x_sta {\n+\tconst struct ieee80211_vif *vif;\n+\tu8 addr[ETH_ALEN];\n+\tenum ieee80211_sta_state state;\n+\tbool tid_tx[IEEE80211_NUM_TIDS];\n+\tbool tid_start_tx[IEEE80211_NUM_TIDS];\n+\tu8 tid_params[IEEE80211_NUM_TIDS];\n+\tint max_bw_mhz;\n+\tstruct mm81x_rc_sta rc;\n+\tstruct mmrc_rate last_sta_tx_rate;\n+\ts16 avg_rssi;\n+\tbool tx_ps_filter_en;\n+};\n+\n+struct mm81x_vif {\n+\tstruct mm81x *mors;\n+\tu16 id;\n+\n+\tunion {\n+\t\tstruct {\n+\t\t\tbool is_assoc;\n+\t\t} sta;\n+\t\tstruct {\n+\t\t\tu32 num_stas;\n+\t\t\tstruct work_struct beacon_work;\n+\t\t} ap;\n+\t} u;\n+};\n+\n+struct mm81x_stale_tx_status {\n+\t/* Stale Tx lock */\n+\tspinlock_t lock;\n+\tstruct timer_list timer;\n+};\n+\n+struct mcast_filter {\n+\tu8 count;\n+\t/*\n+\t * Integer representation of the last four bytes of a multicast MAC\n+\t * address. The first two bytes are always 0x0100 (IPv4) or 0x3333\n+\t * (IPv6).\n+\t */\n+\t__le32 addr_list[];\n+};\n+\n+enum mm81x_hw_scan_op {\n+\tMM81X_HW_SCAN_OP_START,\n+\tMM81X_HW_SCAN_OP_STOP,\n+};\n+\n+struct mm81x_hw_scan_params {\n+\tstruct ieee80211_hw *hw;\n+\n+\t/* vif which initiated the scan */\n+\tstruct ieee80211_vif *vif;\n+\tbool has_directed_ssid;\n+\tu32 dwell_time_ms;\n+\tu32 dwell_on_home_ms;\n+\tenum mm81x_hw_scan_op operation;\n+\tbool store;\n+\tstruct sk_buff *probe_req;\n+\tu16 num_chans;\n+\tu16 allocated_chans;\n+\n+\tstruct {\n+\t\tstruct ieee80211_channel *channel;\n+\t\t/* Index into @ref powers_qdbm for the power of this channel */\n+\t\tu8 power_idx;\n+\t} *channels;\n+\n+\ts32 *powers_qdbm;\n+\tu8 n_powers;\n+};\n+\n+enum mm81x_hw_scan_state {\n+\tHW_SCAN_STATE_IDLE,\n+\tHW_SCAN_STATE_RUNNING,\n+\tHW_SCAN_STATE_ABORTING,\n+};\n+\n+struct mm81x_hw_scan {\n+\tenum mm81x_hw_scan_state state;\n+\tstruct completion scan_done;\n+\tstruct mm81x_hw_scan_params *params;\n+\tstruct delayed_work timeout;\n+\tu32 home_dwell_ms;\n+};\n+\n+enum mm81x_hif_event_flags {\n+\tMM81X_HIF_EVT_RX_PEND,\n+\tMM81X_HIF_EVT_PAGE_RETURN_PEND,\n+\tMM81X_HIF_EVT_TX_COMMAND_PEND,\n+\tMM81X_HIF_EVT_TX_BEACON_PEND,\n+\tMM81X_HIF_EVT_TX_MGMT_PEND,\n+\tMM81X_HIF_EVT_TX_DATA_PEND,\n+\tMM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND,\n+\tMM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND,\n+\tMM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND,\n+\tMM81X_HIF_EVT_UPDATE_HW_CLOCK_REFERENCE,\n+};\n+\n+enum mm81x_state_flags {\n+\tMM81X_STATE_CHIP_UNRESPONSIVE,\n+\tMM81X_STATE_DATA_QS_STOPPED,\n+\tMM81X_STATE_DATA_TX_STOPPED,\n+\tMM81X_STATE_REGDOM_SET_BY_USER,\n+\tMM81X_STATE_REGDOM_SET_BY_OTP,\n+\tMM81X_STATE_RELOAD_FW_AFTER_START,\n+\tMM81X_STATE_HOST_TO_CHIP_TX_BLOCKED,\n+\tMM81X_STATE_HOST_TO_CHIP_CMD_BLOCKED,\n+};\n+\n+#define MM81X_COUNTRY_LEN (3)\n+#define INVALID_VIF_INDEX 0xFF\n+\n+struct mm81x {\n+\tu32 chip_id;\n+\tu32 host_table_ptr;\n+\n+\t/* Refer to @enum mm81x_bus_type */\n+\tu32 bus_type;\n+\tu32 bcf_address;\n+\n+\t/*\n+\t * Parsed from the release tag, which should be in the format\n+\t * 'rel_\u003cmajor\u003e_\u003cminor\u003e_\u003cpatch\u003e'. If the tag is not in this format\n+\t * then corresponding version field will be 0.\n+\t */\n+\tstruct mm81x_sw_version sw_ver;\n+\tu8 macaddr[ETH_ALEN];\n+\tu8 country[MM81X_COUNTRY_LEN];\n+\n+\t/* Mask of type @enum host_table_firmware_flags */\n+\tu32 firmware_flags;\n+\tstruct mm81x_fw_caps fw_caps;\n+\tbool started;\n+\tbool chip_was_reset;\n+\tstruct wiphy *wiphy;\n+\tstruct mm81x_hw_scan hw_scan;\n+\tstruct ieee80211_hw *hw;\n+\tstruct device *dev;\n+\n+\tstruct ieee80211_vif __rcu *vifs[MM81X_MAX_IF];\n+\n+\t/* @mm81x_state_flags */\n+\tunsigned long state_flags;\n+\n+\tu16 cmd_seq;\n+\tstruct completion *cmd_comp;\n+\t/* Serialises commands */\n+\tstruct mutex cmd_lock;\n+\n+\t/* Serialises command completion */\n+\tstruct mutex cmd_wait;\n+\n+\tconst struct mm81x_regs *regs;\n+\n+\tstruct {\n+\t\tunion {\n+\t\t\tstruct mm81x_yaps yaps;\n+\t\t} u;\n+\t\tconst struct mm81x_hif_ops *ops;\n+\t\t/* See @enum mm81x_hif_event_flags for values */\n+\t\tunsigned long event_flags;\n+\t\tbool validate_skb_checksum;\n+\t} hif;\n+\n+\tstruct workqueue_struct *chip_wq;\n+\tstruct work_struct hif_work;\n+\tstruct work_struct usb_irq_work;\n+\tstruct mm81x_stale_tx_status stale_status;\n+\tbool config_ps;\n+\tstruct mm81x_ps ps;\n+\n+\t/* Tx power in mBm received from the FW before association */\n+\ts32 tx_power_mbm;\n+\ts32 tx_max_power_mbm;\n+\n+\tconst struct mm81x_bus_ops *bus_ops;\n+\tstruct mm81x_rc mrc;\n+\tint rts_threshold;\n+\tstruct workqueue_struct *net_wq;\n+\tstruct work_struct tx_stale_work;\n+\twait_queue_head_t tx_empty_waitq;\n+\n+\tstruct cfg80211_chan_def chandef;\n+\tstruct mcast_filter *mcast_filter;\n+\tatomic_t num_bcn_vifs;\n+\tunsigned long beacon_irqs_enabled;\n+\tu8 drv_priv[] __aligned(sizeof(void *));\n+};\n+\n+/* Map from mac80211 queue to Morse ACI value for page metadata */\n+static inline u8 map_mac80211q_2_mm81x_aci(u16 mac80211queue)\n+{\n+\tswitch (mac80211queue) {\n+\tcase IEEE80211_AC_VO:\n+\t\treturn MM81X_ACI_VO;\n+\tcase IEEE80211_AC_VI:\n+\t\treturn MM81X_ACI_VI;\n+\tcase IEEE80211_AC_BK:\n+\t\treturn MM81X_ACI_BK;\n+\tdefault:\n+\t\treturn MM81X_ACI_BE;\n+\t}\n+}\n+\n+static inline enum mm81x_page_aci\n+dot11_tid_to_ac(enum mm81x_qos_tid_up_index tid)\n+{\n+\tswitch (tid) {\n+\tcase MM81X_QOS_TID_UP_BK:\n+\tcase MM81X_QOS_TID_UP_XX:\n+\t\treturn MM81X_ACI_BK;\n+\tcase MM81X_QOS_TID_UP_CL:\n+\tcase MM81X_QOS_TID_UP_VI:\n+\t\treturn MM81X_ACI_VI;\n+\tcase MM81X_QOS_TID_UP_VO:\n+\tcase MM81X_QOS_TID_UP_NC:\n+\t\treturn MM81X_ACI_VO;\n+\tcase MM81X_QOS_TID_UP_BE:\n+\tcase MM81X_QOS_TID_UP_EE:\n+\tdefault:\n+\t\treturn MM81X_ACI_BE;\n+\t}\n+}\n+\n+static inline bool mm81x_is_data_tx_allowed(struct mm81x *mors)\n+{\n+\treturn !test_bit(MM81X_STATE_DATA_TX_STOPPED, \u0026mors-\u003estate_flags) \u0026\u0026\n+\t       !test_bit(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND,\n+\t\t\t \u0026mors-\u003ehif.event_flags);\n+}\n+\n+static inline struct ieee80211_vif *\n+mm81x_vif_to_ieee80211_vif(struct mm81x_vif *mors_vif)\n+{\n+\treturn container_of((void *)mors_vif, struct ieee80211_vif, drv_priv);\n+}\n+\n+static inline struct mm81x_vif *\n+ieee80211_vif_to_mors_vif(struct ieee80211_vif *vif)\n+{\n+\treturn (struct mm81x_vif *)vif-\u003edrv_priv;\n+}\n+\n+static inline struct mm81x *mm81x_vif_to_mors(struct mm81x_vif *mors_vif)\n+{\n+\treturn mors_vif-\u003emors;\n+}\n+\n+static inline u32 mm81x_generate_cssid(const u8 *ssid, u8 len)\n+{\n+\treturn ~crc32(~0, ssid, len);\n+}\n+\n+int mm81x_beacon_init(struct mm81x_vif *mors_vif);\n+void mm81x_beacon_finish(struct mm81x_vif *mors_vif);\n+void mm81x_beacon_irq_handle(struct mm81x *mors, u32 status);\n+char *mm81x_core_get_fw_path(u32 chip_id);\n+struct mm81x *mm81x_core_alloc(size_t priv_size, struct device *dev);\n+int mm81x_core_init(struct mm81x *mors);\n+int mm81x_core_register(struct mm81x *mors);\n+void mm81x_core_unregister(struct mm81x *mors);\n+void mm81x_core_deinit(struct mm81x *mors);\n+void mm81x_core_free(struct mm81x *mors);\n+\n+#endif /* !_MM81X_MM81X_H_ */\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/fw.c b/drivers/net/wireless/morsemicro/mm81x/fw.c\nnew file mode 100644\nindex 00000000000000..337b809571d2bb\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/fw.c\n@@ -0,0 +1,732 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/firmware.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/iopoll.h\u003e\n+#include \u003clinux/string_choices.h\u003e\n+#include \u003cnet/mac80211.h\u003e\n+#include \u003clinux/elf.h\u003e\n+#include \u003clinux/crc32.h\u003e\n+#include \"fw.h\"\n+#include \"mac.h\"\n+#include \"bus.h\"\n+\n+/*\n+ * Maximum wait time (microseconds) for firmware to boot (for host table\n+ * pointer to be available)\n+ */\n+#define HOST_TABLE_PTR_POLL_TIMEOUT_US 1200000\n+#define HOST_TABLE_PTR_POLL_PERIOD_US 10000\n+\n+/* Number of times to attempt flashing FW */\n+#define FW_FLASH_ATTEMPT_COUNT 3\n+\n+static int mm81x_fw_get_header(const u8 *data, Elf32_Ehdr *ehdr)\n+{\n+\tconst struct mm81x_elf32_ehdr *p =\n+\t\t(const struct mm81x_elf32_ehdr *)data;\n+\n+\t/* Magic check */\n+\tif (p-\u003ee_ident[EI_MAG0] != ELFMAG0 || p-\u003ee_ident[EI_MAG1] != ELFMAG1 ||\n+\t    p-\u003ee_ident[EI_MAG2] != ELFMAG2 || p-\u003ee_ident[EI_MAG3] != ELFMAG3)\n+\t\treturn -EINVAL;\n+\n+\t/* elf32 and little endian */\n+\tif (p-\u003ee_ident[EI_DATA] != ELFDATA2LSB ||\n+\t    p-\u003ee_ident[EI_CLASS] != ELFCLASS32)\n+\t\treturn -EINVAL;\n+\n+\tehdr-\u003ee_phoff = le32_to_cpu(p-\u003ee_phoff);\n+\tehdr-\u003ee_phentsize = le16_to_cpu(p-\u003ee_phentsize);\n+\tehdr-\u003ee_phnum = le16_to_cpu(p-\u003ee_phnum);\n+\tehdr-\u003ee_shoff = le32_to_cpu(p-\u003ee_shoff);\n+\tehdr-\u003ee_shentsize = le16_to_cpu(p-\u003ee_shentsize);\n+\tehdr-\u003ee_shnum = le16_to_cpu(p-\u003ee_shnum);\n+\tehdr-\u003ee_shstrndx = le16_to_cpu(p-\u003ee_shstrndx);\n+\tehdr-\u003ee_entry = le32_to_cpu(p-\u003ee_entry);\n+\n+\treturn 0;\n+}\n+\n+static void mm81x_fw_parse_info(struct mm81x *mors, const u8 *data, int length)\n+{\n+\tconst struct mm81x_fw_info_tlv *tlv =\n+\t\t(const struct mm81x_fw_info_tlv *)data;\n+\n+\twhile ((u8 *)tlv \u003c (data + length)) {\n+\t\tswitch (le16_to_cpu(tlv-\u003etype)) {\n+\t\tcase MM81X_FW_INFO_TLV_BCF_ADDR:\n+\t\t\tmors-\u003ebcf_address = get_unaligned_le32(tlv-\u003eval);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\t\ttlv = (const struct mm81x_fw_info_tlv *)((u8 *)tlv +\n+\t\t\t\t\t\t\t le16_to_cpu(\n+\t\t\t\t\t\t\t\t tlv-\u003elength) +\n+\t\t\t\t\t\t\t sizeof(*tlv));\n+\t}\n+}\n+\n+static int mm81x_fw_get_section_header(const u8 *data, Elf32_Ehdr *ehdr,\n+\t\t\t\t       Elf32_Shdr *shdr, int i)\n+{\n+\tconst struct mm81x_elf32_shdr *p =\n+\t\t(void *)(data + ehdr-\u003ee_shoff + (i * ehdr-\u003ee_shentsize));\n+\n+\tshdr-\u003esh_name = le32_to_cpu(p-\u003esh_name);\n+\tshdr-\u003esh_type = le32_to_cpu(p-\u003esh_type);\n+\tshdr-\u003esh_offset = le32_to_cpu(p-\u003esh_offset);\n+\tshdr-\u003esh_addr = le32_to_cpu(p-\u003esh_addr);\n+\tshdr-\u003esh_size = le32_to_cpu(p-\u003esh_size);\n+\tshdr-\u003esh_flags = le32_to_cpu(p-\u003esh_flags);\n+\n+\treturn 0;\n+}\n+\n+static int mm81x_fw_set_boot_addr(struct mm81x *mors, uint32_t addr)\n+{\n+\tint status;\n+\n+\tdev_dbg(mors-\u003edev, \"Overwriting boot address to 0x%x\", addr);\n+\tmm81x_claim_bus(mors);\n+\tstatus = mm81x_reg32_write(mors, MM81X_REG_BOOT_ADDR(mors), addr);\n+\tmm81x_release_bus(mors);\n+\treturn status;\n+}\n+\n+static int mm81x_fw_load_fw(struct mm81x *mors, const struct firmware *fw)\n+{\n+\tint i;\n+\tint ret = 0;\n+\tElf32_Ehdr ehdr;\n+\tElf32_Phdr phdr;\n+\tElf32_Shdr shdr;\n+\tElf32_Shdr sh_strtab;\n+\tconst char *sh_strs;\n+\n+\tu8 *fw_buf = devm_kmalloc(mors-\u003edev, ROUND_BYTES_TO_WORD(fw-\u003esize),\n+\t\t\t\t  GFP_KERNEL);\n+\n+\tif (!fw_buf)\n+\t\treturn -ENOMEM;\n+\n+\tif (mm81x_fw_get_header(fw-\u003edata, \u0026ehdr)) {\n+\t\tdev_err(mors-\u003edev, \"Wrong file format\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (mm81x_fw_get_section_header(fw-\u003edata, \u0026ehdr, \u0026sh_strtab,\n+\t\t\t\t\tehdr.e_shstrndx)) {\n+\t\tdev_err(mors-\u003edev, \"Invalid firmware. Missing string table\");\n+\t\treturn -ENOENT;\n+\t}\n+\n+\tsh_strs = (const char *)fw-\u003edata + sh_strtab.sh_offset;\n+\n+\tfor (i = 0; i \u003c ehdr.e_phnum; i++) {\n+\t\tint status;\n+\t\tint address;\n+\t\tconst struct mm81x_elf32_phdr *p =\n+\t\t\t(void *)(fw-\u003edata + ehdr.e_phoff +\n+\t\t\t\t i * ehdr.e_phentsize);\n+\n+\t\tphdr.p_type = le32_to_cpu(p-\u003ep_type);\n+\t\tphdr.p_offset = le32_to_cpu(p-\u003ep_offset);\n+\t\tphdr.p_paddr = le32_to_cpu(p-\u003ep_paddr);\n+\t\tphdr.p_filesz = le32_to_cpu(p-\u003ep_filesz);\n+\t\tphdr.p_memsz = le32_to_cpu(p-\u003ep_memsz);\n+\n+\t\taddress = phdr.p_paddr;\n+\n+\t\tif (phdr.p_type != PT_LOAD || !phdr.p_memsz)\n+\t\t\tcontinue;\n+\n+\t\tif (phdr.p_filesz \u0026\u0026 phdr.p_offset \u0026\u0026\n+\t\t    (phdr.p_offset + phdr.p_filesz) \u003c fw-\u003esize) {\n+\t\t\tu32 padded_size = ROUND_BYTES_TO_WORD(phdr.p_filesz);\n+\n+\t\t\tmemcpy(fw_buf, fw-\u003edata + phdr.p_offset, padded_size);\n+\t\t\t/* Set padding to 0xff */\n+\t\t\tmemset(fw_buf + phdr.p_filesz, 0xff,\n+\t\t\t       padded_size - phdr.p_filesz);\n+\t\t\tmm81x_claim_bus(mors);\n+\t\t\tstatus = mm81x_dm_write(mors, address, fw_buf,\n+\t\t\t\t\t\tpadded_size);\n+\t\t\tmm81x_release_bus(mors);\n+\t\t\tif (status) {\n+\t\t\t\tret = -EIO;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i \u003c ehdr.e_shnum; i++) {\n+\t\tif (mm81x_fw_get_section_header(fw-\u003edata, \u0026ehdr, \u0026shdr, i))\n+\t\t\tcontinue;\n+\n+\t\t/* This is the firmware info. Parse it */\n+\t\tif (!strncmp(sh_strs + shdr.sh_name, \".fw_info\",\n+\t\t\t     sizeof(\".fw_info\")))\n+\t\t\tmm81x_fw_parse_info(mors, fw-\u003edata + shdr.sh_offset,\n+\t\t\t\t\t    shdr.sh_size);\n+\t}\n+\n+\tif (ehdr.e_entry)\n+\t\tret = mm81x_fw_set_boot_addr(mors, ehdr.e_entry);\n+\n+\tdevm_kfree(mors-\u003edev, fw_buf);\n+\treturn ret;\n+}\n+\n+static int __mm81x_fw_load_bcf(struct mm81x *mors, unsigned int addr,\n+\t\t\t       const void *src, size_t src_len, u8 *scratch,\n+\t\t\t       size_t scratch_cap)\n+{\n+\tsize_t rounded = ROUND_BYTES_TO_WORD(src_len);\n+\tint st;\n+\n+\tif (rounded \u003e scratch_cap)\n+\t\treturn -EINVAL;\n+\tif (rounded \u003e BCF_DATABASE_SIZE)\n+\t\treturn -EFBIG;\n+\n+\tmemcpy(scratch, src, src_len);\n+\tif (rounded \u003e src_len)\n+\t\tmemset(scratch + src_len, 0xff, rounded - src_len);\n+\n+\tmm81x_claim_bus(mors);\n+\tst = mm81x_dm_write(mors, addr, scratch, rounded);\n+\tmm81x_release_bus(mors);\n+\n+\treturn st ? -EIO : 0;\n+}\n+\n+static int mm81x_fw_load_bcf(struct mm81x *mors, const struct firmware *bcf,\n+\t\t\t     unsigned int bcf_address)\n+{\n+\tint i, ret = 0;\n+\tsize_t reg_prefix_len, cfg_len_rounded = 0, reg_len_rounded;\n+\tElf32_Ehdr ehdr;\n+\tElf32_Shdr shdr, sh_strtab;\n+\tconst char *sh_strs, *reg_prefix = \".regdom_\", *reg_src;\n+\tsize_t reg_len;\n+\tu8 *bcf_buf;\n+\n+\tbcf_buf = devm_kmalloc(mors-\u003edev, ROUND_BYTES_TO_WORD(bcf-\u003esize),\n+\t\t\t       GFP_KERNEL);\n+\tif (!bcf_buf)\n+\t\treturn -ENOMEM;\n+\n+\tif (mm81x_fw_get_header(bcf-\u003edata, \u0026ehdr)) {\n+\t\tdev_err(mors-\u003edev, \"Wrong file format\");\n+\t\tret = -EINVAL;\n+\t\tgoto out_free;\n+\t}\n+\n+\tif (mm81x_fw_get_section_header(bcf-\u003edata, \u0026ehdr, \u0026sh_strtab,\n+\t\t\t\t\tehdr.e_shstrndx)) {\n+\t\tdev_err(mors-\u003edev, \"Invalid BCF - missing string table\");\n+\t\tret = -ENOENT;\n+\t\tgoto out_free;\n+\t}\n+\n+\tsh_strs = (const char *)bcf-\u003edata + sh_strtab.sh_offset;\n+\treg_prefix_len = strlen(reg_prefix);\n+\n+\tfor (i = 0; i \u003c ehdr.e_shnum; i++) {\n+\t\tif (mm81x_fw_get_section_header(bcf-\u003edata, \u0026ehdr, \u0026shdr, i))\n+\t\t\tcontinue;\n+\t\tif (strcmp(sh_strs + shdr.sh_name, \".board_config\"))\n+\t\t\tcontinue;\n+\n+\t\tcfg_len_rounded = ROUND_BYTES_TO_WORD(shdr.sh_size);\n+\t\tdev_dbg(mors-\u003edev,\n+\t\t\t\"Write BCF board_config - addr 0x%x size %zu\",\n+\t\t\tbcf_address, cfg_len_rounded);\n+\n+\t\tret = __mm81x_fw_load_bcf(mors, bcf_address,\n+\t\t\t\t\t  bcf-\u003edata + shdr.sh_offset,\n+\t\t\t\t\t  shdr.sh_size, bcf_buf,\n+\t\t\t\t\t  ROUND_BYTES_TO_WORD(bcf-\u003esize));\n+\t\tif (ret)\n+\t\t\tgoto out_free;\n+\n+\t\tbcf_address += cfg_len_rounded;\n+\t\tbreak;\n+\t}\n+\n+\tret = -EINVAL;\n+\tfor (; i \u003c ehdr.e_shnum; i++) {\n+\t\tif (mm81x_fw_get_section_header(bcf-\u003edata, \u0026ehdr, \u0026shdr, i))\n+\t\t\tcontinue;\n+\t\tif (strncmp(sh_strs + shdr.sh_name, reg_prefix, reg_prefix_len))\n+\t\t\tcontinue;\n+\t\tif (strncmp(sh_strs + shdr.sh_name + reg_prefix_len,\n+\t\t\t    mors-\u003ecountry, 2))\n+\t\t\tcontinue;\n+\n+\t\treg_src = bcf-\u003edata + shdr.sh_offset;\n+\t\treg_len = shdr.sh_size;\n+\t\tdev_dbg(mors-\u003edev, \"Write BCF %s - addr 0x%x size %zu\",\n+\t\t\tsh_strs + shdr.sh_name, bcf_address,\n+\t\t\tROUND_BYTES_TO_WORD(reg_len));\n+\t\tret = 0;\n+\t\tbreak;\n+\t}\n+\n+\tif (ret)\n+\t\tgoto out_free;\n+\n+\treg_len_rounded = ROUND_BYTES_TO_WORD(reg_len);\n+\tif ((cfg_len_rounded + reg_len_rounded) \u003e BCF_DATABASE_SIZE) {\n+\t\tret = -EFBIG;\n+\t\tgoto out_free;\n+\t}\n+\n+\tret = __mm81x_fw_load_bcf(mors, bcf_address, reg_src, reg_len, bcf_buf,\n+\t\t\t\t  ROUND_BYTES_TO_WORD(bcf-\u003esize));\n+\n+out_free:\n+\tdevm_kfree(mors-\u003edev, bcf_buf);\n+\treturn ret;\n+}\n+\n+static void mm81x_fw_clear_aon(struct mm81x *mors)\n+{\n+\tint idx;\n+\tu8 count = MM81X_REG_AON_COUNT(mors);\n+\tu32 address = MM81X_REG_AON_ADDR(mors);\n+\n+\tfor (idx = 0; idx \u003c count; idx++, address += 4) {\n+\t\tif (mors-\u003ebus_type == MM81X_BUS_TYPE_USB \u0026\u0026 idx == 0)\n+\t\t\t/* Keep the USB power domain enabled in AON. */\n+\t\t\tmm81x_reg32_write(mors, address,\n+\t\t\t\t\t  MM81X_REG_AON_USB_RESET(mors));\n+\t\telse\n+\t\t\t/* clear AON */\n+\t\t\tmm81x_reg32_write(mors, address, 0x0);\n+\t}\n+\n+\tmm81x_hw_toggle_aon_latch(mors);\n+}\n+\n+static void mm81x_fw_trigger(struct mm81x *mors)\n+{\n+\tconst unsigned int wait_after_msi_trigger_ms = 1;\n+\n+\tmm81x_claim_bus(mors);\n+\t/*\n+\t * If not coming from a full reset, some AON flags may be latched.\n+\t * Make sure to clear any hanging AON bits (can affect booting).\n+\t */\n+\tmm81x_fw_clear_aon(mors);\n+\n+\tif (MM81X_REG_CLK_CTRL(mors))\n+\t\tmm81x_reg32_write(mors, MM81X_REG_CLK_CTRL(mors),\n+\t\t\t\t  MM81X_REG_CLK_CTRL_VALUE(mors));\n+\n+\tmm81x_reg32_write(mors, MM81X_REG_MSI(mors),\n+\t\t\t  MM81X_REG_MSI_HOST_INT(mors));\n+\tmm81x_release_bus(mors);\n+\n+\t/* Give the chip a chance to boot */\n+\tmdelay(wait_after_msi_trigger_ms);\n+}\n+\n+static int mm81x_fw_verify_magic(struct mm81x *mors)\n+{\n+\tint ret = 0;\n+\tint magic = ~MM81X_REG_HOST_MAGIC_VALUE(mors);\n+\n+\tmm81x_claim_bus(mors);\n+\tmm81x_reg32_read(mors,\n+\t\t\t mors-\u003ehost_table_ptr +\n+\t\t\t\t offsetof(struct host_table, magic_number),\n+\t\t\t \u0026magic);\n+\n+\tif (magic != MM81X_REG_HOST_MAGIC_VALUE(mors)) {\n+\t\tdev_err(mors-\u003edev, \"FW magic mismatch 0x%08x:0x%08x\",\n+\t\t\tMM81X_REG_HOST_MAGIC_VALUE(mors), magic);\n+\t\tret = -EIO;\n+\t}\n+\n+\tmm81x_release_bus(mors);\n+\treturn ret;\n+}\n+\n+static int mm81x_fw_get_flags(struct mm81x *mors)\n+{\n+\tint ret = 0;\n+\tint fw_flags = 0;\n+\n+\tmm81x_claim_bus(mors);\n+\tret = mm81x_reg32_read(mors,\n+\t\t\t       mors-\u003ehost_table_ptr +\n+\t\t\t\t       offsetof(struct host_table,\n+\t\t\t\t\t\tfirmware_flags),\n+\t\t\t       \u0026fw_flags);\n+\tmors-\u003efirmware_flags = fw_flags;\n+\tmm81x_release_bus(mors);\n+\n+\treturn ret;\n+}\n+\n+static int mm81x_fw_check_compatibility(struct mm81x *mors)\n+{\n+\tint ret = 0;\n+\tu32 fw_version;\n+\tu32 major;\n+\tu32 minor;\n+\tu32 patch;\n+\n+\tmm81x_claim_bus(mors);\n+\tret = mm81x_reg32_read(mors,\n+\t\t\t       mors-\u003ehost_table_ptr +\n+\t\t\t\t       offsetof(struct host_table,\n+\t\t\t\t\t\tfw_version_number),\n+\t\t\t       \u0026fw_version);\n+\tmm81x_release_bus(mors);\n+\n+\tmajor = MM81X_SEMVER_GET_MAJOR(fw_version);\n+\tminor = MM81X_SEMVER_GET_MINOR(fw_version);\n+\tpatch = MM81X_SEMVER_GET_PATCH(fw_version);\n+\n+\t/* Firmware on device must be recent enough for driver */\n+\tif (ret == 0 \u0026\u0026 major != HOST_CMD_SEMVER_MAJOR) {\n+\t\tdev_err(mors-\u003edev,\n+\t\t\t\"Incompatible FW version: (Driver) %d.%d.%d, (Chip) %d.%d.%d\\n\",\n+\t\t\tHOST_CMD_SEMVER_MAJOR, HOST_CMD_SEMVER_MINOR,\n+\t\t\tHOST_CMD_SEMVER_PATCH, major, minor, patch);\n+\t\tret = -EPERM;\n+\t} else if (ret == 0 \u0026\u0026 minor != HOST_CMD_SEMVER_MINOR) {\n+\t\tdev_warn(\n+\t\t\tmors-\u003edev,\n+\t\t\t\"FW version mismatch, some features might not be supported: (Driver) %d.%d.%d, (Chip) %d.%d.%d\",\n+\t\t\tHOST_CMD_SEMVER_MAJOR, HOST_CMD_SEMVER_MINOR,\n+\t\t\tHOST_CMD_SEMVER_PATCH, major, minor, patch);\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static int mm81x_fw_invalidate_host_ptr(struct mm81x *mors)\n+{\n+\tint ret;\n+\n+\tmors-\u003ehost_table_ptr = 0;\n+\tmm81x_claim_bus(mors);\n+\tret = mm81x_reg32_write(mors, MM81X_REG_HOST_MANIFEST_PTR(mors), 0);\n+\tmm81x_release_bus(mors);\n+\treturn ret;\n+}\n+\n+static int mm81x_fw_get_host_table_ptr(struct mm81x *mors)\n+{\n+\tint ret, err;\n+\n+\tmm81x_claim_bus(mors);\n+\tret = read_poll_timeout(mm81x_reg32_read, err,\n+\t\t\t\terr || mors-\u003ehost_table_ptr,\n+\t\t\t\tHOST_TABLE_PTR_POLL_PERIOD_US,\n+\t\t\t\tHOST_TABLE_PTR_POLL_TIMEOUT_US, false, mors,\n+\t\t\t\tMM81X_REG_HOST_MANIFEST_PTR(mors),\n+\t\t\t\t\u0026mors-\u003ehost_table_ptr);\n+\tmm81x_release_bus(mors);\n+\n+\treturn ret ? ret : err;\n+}\n+\n+static int mm81x_fw_read_ext_host_table(struct mm81x *mors,\n+\t\t\t\t\tstruct ext_host_tbl **ext_host_table)\n+{\n+\tint ret = 0;\n+\tu32 host_tbl_ptr = mors-\u003ehost_table_ptr;\n+\tu32 ext_host_tbl_ptr;\n+\tu32 ext_host_tbl_ptr_addr =\n+\t\thost_tbl_ptr + offsetof(struct host_table, ext_host_tbl_addr);\n+\tu32 ext_host_tbl_len;\n+\tu32 ext_host_tbl_len_ptr_addr;\n+\tstruct ext_host_tbl *host_tbl = NULL;\n+\n+\tmm81x_claim_bus(mors);\n+\tret = mm81x_reg32_read(mors, ext_host_tbl_ptr_addr, \u0026ext_host_tbl_ptr);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\tif (!ext_host_tbl_ptr) {\n+\t\tret = -ENXIO;\n+\t\tgoto exit;\n+\t}\n+\n+\text_host_tbl_len_ptr_addr =\n+\t\text_host_tbl_ptr +\n+\t\toffsetof(struct ext_host_tbl, ext_host_tbl_length);\n+\n+\tret = mm81x_reg32_read(mors, ext_host_tbl_len_ptr_addr,\n+\t\t\t       \u0026ext_host_tbl_len);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\text_host_tbl_len = ROUND_BYTES_TO_WORD(ext_host_tbl_len);\n+\tif (WARN_ON(ext_host_tbl_len == 0 || ext_host_tbl_len \u003e INT_MAX)) {\n+\t\tret = -EINVAL;\n+\t\tgoto exit;\n+\t}\n+\n+\thost_tbl = kmalloc(ext_host_tbl_len, GFP_KERNEL);\n+\tif (!host_tbl) {\n+\t\tret = -ENOMEM;\n+\t\tgoto exit;\n+\t}\n+\n+\tret = mm81x_dm_read(mors, ext_host_tbl_ptr, (u8 *)host_tbl,\n+\t\t\t    (int)ext_host_tbl_len);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\tmm81x_release_bus(mors);\n+\t*ext_host_table = host_tbl;\n+\treturn ret;\n+\n+exit:\n+\tmm81x_release_bus(mors);\n+\tkfree(host_tbl);\n+\treturn ret;\n+}\n+\n+static void mm81x_fw_update_capabilities(struct mm81x *mors,\n+\t\t\t\t\t struct ext_host_tbl_s1g_caps *caps)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i \u003c FW_CAPABILITIES_FLAGS_WIDTH; i++) {\n+\t\tmors-\u003efw_caps.flags[i] = le32_to_cpu(caps-\u003eflags[i]);\n+\t\tdev_dbg(mors-\u003edev, \"Firmware Manifest Flags%d: 0x%x\", i,\n+\t\t\tle32_to_cpu(caps-\u003eflags[i]));\n+\t}\n+\tmors-\u003efw_caps.ampdu_mss = caps-\u003eampdu_mss;\n+\tmors-\u003efw_caps.mm81x_mmss_offset = caps-\u003emm81x_mmss_offset;\n+\tmors-\u003efw_caps.beamformee_sts_capability =\n+\t\tcaps-\u003ebeamformee_sts_capability;\n+\tmors-\u003efw_caps.maximum_ampdu_length_exponent =\n+\t\tcaps-\u003emaximum_ampdu_length;\n+\tmors-\u003efw_caps.number_sounding_dimensions =\n+\t\tcaps-\u003enumber_sounding_dimensions;\n+\n+\tdev_dbg(mors-\u003edev, \"\\tAMPDU Minimum start spacing: %u\",\n+\t\tcaps-\u003eampdu_mss);\n+\tdev_dbg(mors-\u003edev, \"\\tMorse Minimum Start Spacing offset: %u\",\n+\t\tcaps-\u003emm81x_mmss_offset);\n+\tdev_dbg(mors-\u003edev, \"\\tBeamformee STS Capability: %u\",\n+\t\tcaps-\u003ebeamformee_sts_capability);\n+\tdev_dbg(mors-\u003edev, \"\\tNumber of Sounding Dimensions: %u\",\n+\t\tcaps-\u003enumber_sounding_dimensions);\n+\tdev_dbg(mors-\u003edev, \"\\tMaximum AMPDU Length Exponent: %u\",\n+\t\tcaps-\u003emaximum_ampdu_length);\n+}\n+\n+static void mm81x_fw_update_validate_skb_checksum(\n+\tstruct mm81x *mors,\n+\tstruct ext_host_tbl_insert_skb_checksum *validate_checksum)\n+{\n+\tmors-\u003ehif.validate_skb_checksum =\n+\t\tvalidate_checksum-\u003einsert_and_validate_checksum;\n+\tdev_dbg(mors-\u003edev, \"Validate checksum inserted by fw %s\",\n+\t\tstr_enabled_disabled(mors-\u003ehif.validate_skb_checksum));\n+}\n+\n+int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors)\n+{\n+\tint ret;\n+\tu8 *head;\n+\tu8 *end;\n+\tstruct ext_host_tbl *ext_host_table = NULL;\n+\n+\tret = mm81x_fw_read_ext_host_table(mors, \u0026ext_host_table);\n+\tif (ret || !ext_host_table)\n+\t\tgoto exit;\n+\n+\t/* Parse the TLVs */\n+\thead = ext_host_table-\u003eext_host_table_data_tlvs;\n+\tend = ((u8 *)ext_host_table) +\n+\t      le32_to_cpu(ext_host_table-\u003eext_host_tbl_length);\n+\n+\twhile (head \u003c end) {\n+\t\tstruct ext_host_tbl_tlv_hdr *hdr =\n+\t\t\t(struct ext_host_tbl_tlv_hdr *)head;\n+\n+\t\tswitch (le16_to_cpu(hdr-\u003etag)) {\n+\t\tcase MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES:\n+\t\t\tmm81x_fw_update_capabilities(\n+\t\t\t\tmors, (struct ext_host_tbl_s1g_caps *)hdr);\n+\t\t\tbreak;\n+\n+\t\tcase MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM:\n+\t\t\tmm81x_fw_update_validate_skb_checksum(\n+\t\t\t\tmors,\n+\t\t\t\t(struct ext_host_tbl_insert_skb_checksum *)hdr);\n+\t\t\tbreak;\n+\n+\t\tcase MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE:\n+\t\t\tmm81x_yaps_hw_read_table(\n+\t\t\t\tmors, \u0026((struct ext_host_tbl_yaps_table *)hdr)\n+\t\t\t\t\t       -\u003eyaps_table);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\thead += le16_to_cpu(hdr-\u003elength);\n+\t\tif (!hdr-\u003elength)\n+\t\t\tbreak;\n+\t}\n+\n+\tkfree(ext_host_table);\n+\treturn ret;\n+exit:\n+\tdev_err(mors-\u003edev, \"failed to parse ext host table %d\", ret);\n+\treturn ret;\n+}\n+\n+static int __mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw,\n+\t\t\t    const struct firmware *bcf, bool reset)\n+{\n+\tint ret;\n+\n+\tif (reset || !mors-\u003echip_was_reset) {\n+\t\tret = mm81x_hw_digital_reset(mors);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\tmm81x_hw_pre_firmware_ndr_hook(mors);\n+\n+\tret = mm81x_fw_invalidate_host_ptr(mors);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = mm81x_fw_load_fw(mors, fw);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = mm81x_fw_load_bcf(mors, bcf, mors-\u003ebcf_address);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tmm81x_fw_trigger(mors);\n+\tmm81x_hw_post_firmware_ndr_hook(mors);\n+\n+\tret = mm81x_fw_get_host_table_ptr(mors);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = mm81x_fw_verify_magic(mors);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn mm81x_fw_check_compatibility(mors);\n+}\n+\n+static int mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw,\n+\t\t\t  const struct firmware *bcf, bool reset)\n+{\n+\tint ret;\n+\tint retries = FW_FLASH_ATTEMPT_COUNT;\n+\n+\twhile (retries--) {\n+\t\tret = __mm81x_fw_flash(mors, fw, bcf, reset);\n+\t\tif (!ret)\n+\t\t\treturn 0;\n+\n+\t\tmors-\u003echip_was_reset = false;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static uint32_t binary_crc(const struct firmware *fw)\n+{\n+\treturn ~crc32_le(~0, (unsigned char const *)fw-\u003edata, fw-\u003esize) \u0026\n+\t       0xffffffff;\n+}\n+\n+int mm81x_fw_init(struct mm81x *mors, bool reset)\n+{\n+\tint ret;\n+\tint n;\n+\tint board_id;\n+\tchar *fw_path;\n+\tchar bcf_path[MAX_BCF_NAME_LEN];\n+\tconst struct firmware *fw = NULL;\n+\tconst struct firmware *bcf = NULL;\n+\n+\tfw_path = mm81x_core_get_fw_path(mors-\u003echip_id);\n+\tif (!fw_path)\n+\t\treturn -ENOMEM;\n+\n+\tboard_id = mm81x_hw_otp_get_board_type(mors);\n+\n+\tif (mm81x_hw_otp_valid_board_type(board_id)) {\n+\t\tdev_dbg(mors-\u003edev, \"Using board type 0x%04x from OTP\",\n+\t\t\tboard_id);\n+\t\tn = snprintf(bcf_path, sizeof(bcf_path),\n+\t\t\t     \"%s/bcf_boardtype_%04x.bin\", MM81X_FW_DIR,\n+\t\t\t     board_id);\n+\t} else {\n+\t\tdev_err(mors-\u003edev,\n+\t\t\t\"OTP not set, unable to determine BCF to use\");\n+\t\tret = -EINVAL;\n+\t\tgoto out;\n+\t}\n+\n+\tif (n \u003c 0 || n \u003e= sizeof(bcf_path)) {\n+\t\tdev_err(mors-\u003edev, \"Failed to create BCF path\");\n+\t\tret = -EINVAL;\n+\t\tgoto out;\n+\t}\n+\n+\tret = request_firmware(\u0026fw, fw_path, mors-\u003edev);\n+\tif (ret) {\n+\t\tif (ret == -ENOENT)\n+\t\t\tdev_err(mors-\u003edev, \"Firmware %s not found\\n\", fw_path);\n+\t\tgoto out;\n+\t}\n+\n+\tdev_info(mors-\u003edev, \"Loaded firmware from %s, size %zu, crc32 0x%08x\\n\",\n+\t\t fw_path, fw-\u003esize, binary_crc(fw));\n+\n+\tret = request_firmware(\u0026bcf, bcf_path, mors-\u003edev);\n+\tif (ret) {\n+\t\tif (ret == -ENOENT)\n+\t\t\tdev_err(mors-\u003edev, \"BCF %s not found\\n\", bcf_path);\n+\t\tgoto out;\n+\t}\n+\n+\tdev_info(mors-\u003edev, \"Loaded BCF from %s, size %zu, crc32 0x%08x\\n\",\n+\t\t bcf_path, bcf-\u003esize, binary_crc(bcf));\n+\n+\tret = mm81x_fw_flash(mors, fw, bcf, reset);\n+\tif (ret) {\n+\t\tdev_err(mors-\u003edev, \"failed to flash firmware: %d\", ret);\n+\t\tgoto out;\n+\t}\n+\n+\tret = mm81x_fw_get_flags(mors);\n+\n+out:\n+\trelease_firmware(fw);\n+\trelease_firmware(bcf);\n+\tkfree(fw_path);\n+\n+\tif (ret)\n+\t\tdev_err(mors-\u003edev, \"failed to init firmware: %d\", ret);\n+\telse\n+\t\tdev_dbg(mors-\u003edev, \"firmware initialised\");\n+\n+\treturn ret;\n+}\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/fw.h b/drivers/net/wireless/morsemicro/mm81x/fw.h\nnew file mode 100644\nindex 00000000000000..d590ad3121c136\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/fw.h\n@@ -0,0 +1,141 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#ifndef _MM81X_FW_H_\n+#define _MM81X_FW_H_\n+\n+#include \u003clinux/firmware.h\u003e\n+#include \u003clinux/completion.h\u003e\n+#include \u003clinux/elf.h\u003e\n+#include \"yaps_hw.h\"\n+\n+#define BCF_DATABASE_SIZE (1024)\n+#define MM81X_FW_DIR \"morsemicro\"\n+#define MM81X_FW_EXT \".bin\"\n+\n+#define MAX_BCF_NAME_LEN 64\n+\n+/* FW_CAPABILITIES_FLAGS_WIDTH = ceil(MM81X_CAPS_MAX_HW_LEN / 32) */\n+#define FW_CAPABILITIES_FLAGS_WIDTH (4)\n+\n+struct mm81x_elf32_ehdr {\n+\tunsigned char e_ident[EI_NIDENT];\n+\t__le16 e_type;\n+\t__le16 e_machine;\n+\t__le32 e_version;\n+\t__le32 e_entry;\n+\t__le32 e_phoff;\n+\t__le32 e_shoff;\n+\t__le32 e_flags;\n+\t__le16 e_ehsize;\n+\t__le16 e_phentsize;\n+\t__le16 e_phnum;\n+\t__le16 e_shentsize;\n+\t__le16 e_shnum;\n+\t__le16 e_shstrndx;\n+} __packed;\n+\n+struct mm81x_elf32_shdr {\n+\t__le32 sh_name;\n+\t__le32 sh_type;\n+\t__le32 sh_flags;\n+\t__le32 sh_addr;\n+\t__le32 sh_offset;\n+\t__le32 sh_size;\n+\t__le32 sh_link;\n+\t__le32 sh_info;\n+\t__le32 sh_addralign;\n+\t__le32 sh_entsize;\n+} __packed;\n+\n+struct mm81x_elf32_phdr {\n+\t__le32 p_type;\n+\t__le32 p_offset;\n+\t__le32 p_vaddr;\n+\t__le32 p_paddr;\n+\t__le32 p_filesz;\n+\t__le32 p_memsz;\n+\t__le32 p_flags;\n+\t__le32 p_align;\n+} __packed;\n+\n+enum mm81x_fw_info_tlv_type {\n+\tMM81X_FW_INFO_TLV_BCF_ADDR = 1,\n+};\n+\n+struct mm81x_fw_info_tlv {\n+\t__le16 type;\n+\t__le16 length;\n+\tu8 val[];\n+} __packed;\n+\n+enum mm81x_fw_ext_host_tbl_tag {\n+\t/* The S1G capability tag */\n+\tMM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES = 0,\n+\tMM81X_FW_HOST_TABLE_TAG_PAGER_BYPASS_TX_STATUS = 1,\n+\tMM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM = 2,\n+\tMM81X_FW_HOST_TABLE_TAG_YAPS_TABLE = 3,\n+\tMM81X_FW_HOST_TABLE_TAG_PAGER_PKT_MEMORY = 4,\n+\tMM81X_FW_HOST_TABLE_TAG_PAGER_BYPASS_CMD_RESP = 5,\n+};\n+\n+struct ext_host_tbl_tlv_hdr {\n+\t/* The tag used to identify which capability this represents */\n+\t__le16 tag;\n+\t/* The length of the capability structure including this header */\n+\t__le16 length;\n+} __packed;\n+\n+struct ext_host_tbl_s1g_caps {\n+\tstruct ext_host_tbl_tlv_hdr header;\n+\t__le32 flags[FW_CAPABILITIES_FLAGS_WIDTH];\n+\t/*\n+\t * The minimum A-MPDU start spacing required by firmware.\n+\t * Value | Description\n+\t * ------|------------\n+\t * 0     | No restriction\n+\t * 1     | 1/4 us\n+\t * 2     | 1/2 us\n+\t * 3     | 1 us\n+\t * 4     | 2 us\n+\t * 5     | 4 us\n+\t * 6     | 8 us\n+\t * 7     | 16 us\n+\t */\n+\tu8 ampdu_mss;\n+\tu8 beamformee_sts_capability;\n+\tu8 number_sounding_dimensions;\n+\t/*\n+\t * The maximum A-MPDU length. This is the exponent value such that\n+\t * (2^(13 + exponent) - 1) is the length\n+\t */\n+\tu8 maximum_ampdu_length;\n+\t/*\n+\t * Offset to apply to the specification's MMSS table to signal further\n+\t * minimum MPDU start spacing.\n+\t */\n+\tu8 mm81x_mmss_offset;\n+} __packed;\n+\n+struct ext_host_tbl_insert_skb_checksum {\n+\tstruct ext_host_tbl_tlv_hdr header;\n+\tu8 insert_and_validate_checksum;\n+};\n+\n+struct ext_host_tbl_yaps_table {\n+\tstruct ext_host_tbl_tlv_hdr header;\n+\tstruct mm81x_yaps_hw_table yaps_table;\n+} __packed;\n+\n+struct ext_host_tbl {\n+\t__le32 ext_host_tbl_length;\n+\tu8 dev_mac_addr[6];\n+\tu8 ext_host_table_data_tlvs[];\n+} __packed;\n+\n+int mm81x_fw_init(struct mm81x *mors, bool reset);\n+int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors);\n+\n+#endif /* !_MM81X_FW_H_ */\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/hif.h b/drivers/net/wireless/morsemicro/mm81x/hif.h\nnew file mode 100644\nindex 00000000000000..e3d23423049ae5\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/hif.h\n@@ -0,0 +1,117 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#ifndef _MM81X_HIF_H_\n+#define _MM81X_HIF_H_\n+\n+#include \"core.h\"\n+\n+struct mm81x_skbq;\n+\n+#define MM81X_HIF_BYPASS_TX_STATUS_IRQ_NUM (15)\n+#define MM81X_HIF_BYPASS_CMD_RESP_IRQ_NUM (29)\n+#define MM81X_HIF_IRQ_BYPASS_TX_STATUS_AVAILABLE \\\n+\tBIT(MM81X_HIF_BYPASS_TX_STATUS_IRQ_NUM)\n+#define MM81X_HIF_IRQ_BYPASS_CMD_RESP_AVAILABLE \\\n+\tBIT(MM81X_HIF_BYPASS_CMD_RESP_IRQ_NUM)\n+\n+/* Hardware IF interrupt mask. We may use any interrupts in this range */\n+#define MM81X_HIF_IRQ_MASK_ALL                                       \\\n+\t(GENMASK(13, 0) | MM81X_HIF_IRQ_BYPASS_TX_STATUS_AVAILABLE | \\\n+\t MM81X_HIF_IRQ_BYPASS_CMD_RESP_AVAILABLE)\n+\n+enum mm81x_hif_flags {\n+\tMM81X_HIF_FLAGS_DIR_TO_HOST = BIT(0),\n+\tMM81X_HIF_FLAGS_DIR_TO_CHIP = BIT(1),\n+\tMM81X_HIF_FLAGS_COMMAND = BIT(2),\n+\tMM81X_HIF_FLAGS_BEACON = BIT(3),\n+\tMM81X_HIF_FLAGS_DATA = BIT(4)\n+};\n+\n+struct mm81x_hif_ops {\n+\tint (*init)(struct mm81x *mors);\n+\tvoid (*flush_tx_data)(struct mm81x *mors);\n+\tvoid (*flush_cmds)(struct mm81x *mors);\n+\tvoid (*finish)(struct mm81x *mors);\n+\tvoid (*skbq_get_tx_qs)(struct mm81x *mors, struct mm81x_skbq **qs,\n+\t\t\t       int *num_qs);\n+\tstruct mm81x_skbq *(*get_tx_cmd_queue)(struct mm81x *mors);\n+\tstruct mm81x_skbq *(*get_tx_beacon_queue)(struct mm81x *mors);\n+\tstruct mm81x_skbq *(*get_tx_mgmt_queue)(struct mm81x *mors);\n+\tstruct mm81x_skbq *(*get_tx_data_queue)(struct mm81x *mors, int aci);\n+\tint (*handle_irq)(struct mm81x *mors, u32 status);\n+\tint (*get_tx_buffered_count)(struct mm81x *mors);\n+\tint (*get_tx_status_pending_count)(struct mm81x *mors);\n+};\n+\n+static inline void mm81x_hif_clear_events(struct mm81x *mors)\n+{\n+\tmors-\u003ehif.event_flags = 0;\n+}\n+\n+static inline int mm81x_hif_init(struct mm81x *mors)\n+{\n+\treturn mors-\u003ehif.ops-\u003einit(mors);\n+}\n+\n+static inline void mm81x_hif_flush_tx_data(struct mm81x *mors)\n+{\n+\tmors-\u003ehif.ops-\u003eflush_tx_data(mors);\n+}\n+\n+static inline void mm81x_hif_flush_cmds(struct mm81x *mors)\n+{\n+\tmors-\u003ehif.ops-\u003eflush_cmds(mors);\n+}\n+\n+static inline void mm81x_hif_finish(struct mm81x *mors)\n+{\n+\tmors-\u003ehif.ops-\u003efinish(mors);\n+}\n+\n+static inline void mm81x_hif_skbq_get_tx_qs(struct mm81x *mors,\n+\t\t\t\t\t    struct mm81x_skbq **qs, int *num_qs)\n+{\n+\tmors-\u003ehif.ops-\u003eskbq_get_tx_qs(mors, qs, num_qs);\n+}\n+\n+static inline struct mm81x_skbq *mm81x_hif_get_tx_cmd_queue(struct mm81x *mors)\n+{\n+\treturn mors-\u003ehif.ops-\u003eget_tx_cmd_queue(mors);\n+}\n+\n+static inline struct mm81x_skbq *\n+mm81x_hif_get_tx_beacon_queue(struct mm81x *mors)\n+{\n+\treturn mors-\u003ehif.ops-\u003eget_tx_beacon_queue(mors);\n+}\n+\n+static inline struct mm81x_skbq *mm81x_hif_get_tx_mgmt_queue(struct mm81x *mors)\n+{\n+\treturn mors-\u003ehif.ops-\u003eget_tx_mgmt_queue(mors);\n+}\n+\n+static inline struct mm81x_skbq *mm81x_hif_get_tx_data_queue(struct mm81x *mors,\n+\t\t\t\t\t\t\t     int aci)\n+{\n+\treturn mors-\u003ehif.ops-\u003eget_tx_data_queue(mors, aci);\n+}\n+\n+static inline int mm81x_hif_handle_irq(struct mm81x *mors, u32 status)\n+{\n+\treturn mors-\u003ehif.ops-\u003ehandle_irq(mors, status);\n+}\n+\n+static inline int mm81x_hif_get_tx_buffered_count(struct mm81x *mors)\n+{\n+\treturn mors-\u003ehif.ops-\u003eget_tx_buffered_count(mors);\n+}\n+\n+static inline int mm81x_hif_get_tx_status_pending_count(struct mm81x *mors)\n+{\n+\treturn mors-\u003ehif.ops-\u003eget_tx_status_pending_count(mors);\n+}\n+\n+#endif /* _MM81X_HIF_H_ */\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/hw.c b/drivers/net/wireless/morsemicro/mm81x/hw.c\nnew file mode 100644\nindex 00000000000000..081360455e7e8d\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/hw.c\n@@ -0,0 +1,367 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+#include \u003clinux/firmware.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/unaligned.h\u003e\n+#include \u003clinux/gpio.h\u003e\n+#include \"hif.h\"\n+#include \"mac.h\"\n+#include \"bus.h\"\n+#include \"core.h\"\n+#include \"fw.h\"\n+#include \"yaps.h\"\n+\n+#define MM8108_REG_HOST_MAGIC_VALUE 0xDEADBEEF\n+#define MM8108_REG_RESET_VALUE 0xDEAD\n+\n+#define MM8108_REG_SDIO_DEVICE_ADDR 0x0000207C\n+\n+#define MM8108_REG_SDIO_DEVICE_BURST_OFFSET 9\n+#define MM8108_REG_TRGR_BASE 0x00003c00\n+#define MM8108_REG_INT_BASE 0x00003c50\n+#define MM8108_REG_MSI_ADDRESS 0x00004100\n+#define MM8108_REG_MSI_VALUE 0x1\n+#define MM8108_REG_MANIFEST_PTR_ADDRESS 0x00002d40\n+#define MM8108_REG_APPS_BOOT_ADDR 0x00002084\n+#define MM8108_REG_RESET 0x000020AC\n+#define MM8108_REG_AON_COUNT 2\n+\n+#define MM8108_REG_AON_ADDR 0x00002114\n+#define MM8108_REG_AON_LATCH_ADDR 0x00405020\n+#define MM8108_REG_AON_LATCH_MASK 0x1\n+#define MM8108_REG_AON_RESET_USB_VALUE 0x8\n+#define MM8108_APPS_MAC_DMEM_ADDR_START 0x00100000\n+\n+#define MM8108_REG_RC_CLK_POWER_OFF_ADDR 0x00405020\n+#define MM8108_REG_RC_CLK_POWER_OFF_MASK 0x00000040\n+#define MM8108_SLOW_RC_POWER_ON_DELAY_MS 2\n+\n+#define MM8108_RESET_DELAY_TIME_MS 400\n+\n+#define MM8108_REG_OTPCTRL_PLDO 0x00004014\n+#define MM8108_REG_OTPCTRL_PENVDD2 0x00004010\n+#define MM8108_REG_OTPCTRL_PDSTB 0x00004018\n+#define MM8108_REG_OTPCTRL_PTM 0x0000401c\n+#define MM8108_REG_OTPCTRL_PCE 0x00004020\n+#define MM8108_REG_OTPCTRL_PA 0x00004034\n+#define MM8108_REG_OTPCTRL_PECCRDB 0x00004048\n+#define MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START 0x0000400c\n+#define MM8108_REG_OTPCTRL_PDOUT 0x00004040\n+\n+#define MM81X_OTP_MAC_ADDR_2_BANK_NUM 27\n+#define MM81X_OTP_MAC_ADDR_1_BANK_NUM 26\n+#define MM81X_OTP_MAC_ADDR_1_MASK GENMASK(31, 16)\n+#define MM81X_OTP_BOARD_TYPE_BANK_NUM 26\n+#define MM81X_OTP_BOARD_TYPE_MASK GENMASK(15, 0)\n+\n+#define MM810X_BOARD_TYPE_MAX_VALUE (MM81X_OTP_BOARD_TYPE_MASK - 1)\n+\n+static void mm81x_hw_otp_power_up(struct mm81x *mors)\n+{\n+\tmm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PENVDD2, 1);\n+\tudelay(2);\n+\n+\tmm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PLDO, 1);\n+\tusleep_range(10, 20);\n+\n+\tmm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PDSTB, 1);\n+\tudelay(3);\n+}\n+\n+static void mm81x_hw_otp_power_down(struct mm81x *mors)\n+{\n+\tmm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PDSTB, 0);\n+\tmm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PLDO, 0);\n+\tmm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PENVDD2, 0);\n+}\n+\n+static void mm81x_hw_otp_read_enable(struct mm81x *mors)\n+{\n+\tmm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PTM, 0);\n+\tmm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PCE, 1);\n+\tusleep_range(10, 20);\n+}\n+\n+static void mm81x_hw_otp_read_disable(struct mm81x *mors)\n+{\n+\tmm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PCE, 0);\n+\tudelay(1);\n+}\n+\n+static int mm81x_hw_otp_read(struct mm81x *mors, u8 bank_num, u32 *buf,\n+\t\t\t     u8 ignore_ecc)\n+{\n+\tu32 auto_rd_start_tmp;\n+\tu32 auto_rd_start = 1;\n+\tint i;\n+\n+\tmm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PA, bank_num);\n+\tmm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PECCRDB, ignore_ecc);\n+\n+\tmm81x_reg32_read(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START,\n+\t\t\t \u0026auto_rd_start_tmp);\n+\tauto_rd_start_tmp \u0026= 0xfffffffe;\n+\n+\tmm81x_reg32_write(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START,\n+\t\t\t  auto_rd_start | auto_rd_start_tmp);\n+\n+\t/* Attempt reading up to 5 times. */\n+\tfor (i = 0; i \u003c 5 \u0026\u0026 auto_rd_start; i++) {\n+\t\tusleep_range(15, 20);\n+\t\tmm81x_reg32_read(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START,\n+\t\t\t\t \u0026auto_rd_start_tmp);\n+\t\tauto_rd_start = auto_rd_start_tmp \u0026 0x1;\n+\t}\n+\n+\tif (i == 5)\n+\t\treturn -EIO;\n+\n+\tmm81x_reg32_read(mors, MM8108_REG_OTPCTRL_PDOUT, buf);\n+\n+\treturn 0;\n+}\n+\n+int mm81x_hw_otp_get_board_type(struct mm81x *mors)\n+{\n+\tint board_type = 0;\n+\tu32 otp_word = 0;\n+\tint ret;\n+\n+\tmm81x_claim_bus(mors);\n+\tmm81x_hw_otp_power_up(mors);\n+\tmm81x_hw_otp_read_enable(mors);\n+\n+\tret = mm81x_hw_otp_read(mors, MM81X_OTP_BOARD_TYPE_BANK_NUM, \u0026otp_word,\n+\t\t\t\t1);\n+\n+\tmm81x_hw_otp_read_disable(mors);\n+\tmm81x_hw_otp_power_down(mors);\n+\tmm81x_release_bus(mors);\n+\n+\tif (ret)\n+\t\treturn -EINVAL;\n+\n+\tboard_type = otp_word \u0026 MM81X_OTP_BOARD_TYPE_MASK;\n+\n+\treturn board_type;\n+}\n+\n+bool mm81x_hw_otp_valid_board_type(u32 board_type)\n+{\n+\treturn board_type \u003e 0 \u0026\u0026 board_type \u003c MM810X_BOARD_TYPE_MAX_VALUE;\n+}\n+\n+int mm81x_hw_otp_get_mac_addr(struct mm81x *mors)\n+{\n+\tu32 mac1 = 0;\n+\tu32 mac2 = 0;\n+\tint ret = 0;\n+\n+\tmm81x_claim_bus(mors);\n+\tmm81x_hw_otp_power_up(mors);\n+\tmm81x_hw_otp_read_enable(mors);\n+\n+\tret = mm81x_hw_otp_read(mors, MM81X_OTP_MAC_ADDR_1_BANK_NUM, \u0026mac1, 1);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\tret = mm81x_hw_otp_read(mors, MM81X_OTP_MAC_ADDR_2_BANK_NUM, \u0026mac2, 1);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\tput_unaligned_le16((mac1 \u0026 MM81X_OTP_MAC_ADDR_1_MASK) \u003e\u003e 16,\n+\t\t\t   \u0026mors-\u003emacaddr[0]);\n+\tput_unaligned_le32(mac2, \u0026mors-\u003emacaddr[2]);\n+\n+exit:\n+\tmm81x_hw_otp_read_disable(mors);\n+\tmm81x_hw_otp_power_down(mors);\n+\tmm81x_release_bus(mors);\n+\n+\treturn ret;\n+}\n+\n+void mm81x_hw_irq_enable(struct mm81x *mors, u32 irq, bool enable)\n+{\n+\tu32 irq_en, irq_en_addr = irq \u003c 32 ? MM81X_REG_INT1_EN(mors) :\n+\t\t\t\t\t     MM81X_REG_INT2_EN(mors);\n+\tu32 irq_clr_addr = irq \u003c 32 ? MM81X_REG_INT1_CLR(mors) :\n+\t\t\t\t      MM81X_REG_INT2_CLR(mors);\n+\tu32 mask = irq \u003c 32 ? (1 \u003c\u003c irq) : (1 \u003c\u003c (irq - 32));\n+\n+\tmm81x_claim_bus(mors);\n+\tmm81x_reg32_read(mors, irq_en_addr, \u0026irq_en);\n+\tif (enable)\n+\t\tirq_en |= (mask);\n+\telse\n+\t\tirq_en \u0026= ~(mask);\n+\tmm81x_reg32_write(mors, irq_clr_addr, mask);\n+\tmm81x_reg32_write(mors, irq_en_addr, irq_en);\n+\tmm81x_release_bus(mors);\n+}\n+\n+int mm81x_hw_irq_handle(struct mm81x *mors)\n+{\n+\tu32 status1 = 0;\n+\n+\tmm81x_reg32_read(mors, MM81X_REG_INT1_STS(mors), \u0026status1);\n+\n+\tif (status1 \u0026 MM81X_HIF_IRQ_MASK_ALL)\n+\t\tmm81x_hif_handle_irq(mors, status1);\n+\n+\tif (status1 \u0026 MM81X_INT_BEACON_VIF_MASK_ALL)\n+\t\tmm81x_mac_beacon_irq_handle(mors, status1);\n+\n+\tmm81x_reg32_write(mors, MM81X_REG_INT1_CLR(mors), status1);\n+\n+\treturn status1 ? 1 : 0;\n+}\n+EXPORT_SYMBOL_GPL(mm81x_hw_irq_handle);\n+\n+void mm81x_hw_irq_clear(struct mm81x *mors)\n+{\n+\tmm81x_claim_bus(mors);\n+\tmm81x_reg32_write(mors, MM81X_REG_INT1_CLR(mors), 0xFFFFFFFF);\n+\tmm81x_reg32_write(mors, MM81X_REG_INT2_CLR(mors), 0xFFFFFFFF);\n+\tmm81x_release_bus(mors);\n+}\n+\n+void mm81x_hw_toggle_aon_latch(struct mm81x *mors)\n+{\n+\tu32 address = MM81X_REG_AON_LATCH_ADDR(mors);\n+\tu32 mask = MM81X_REG_AON_LATCH_MASK(mors);\n+\tu32 latch;\n+\n+\tmm81x_reg32_read(mors, address, \u0026latch);\n+\tmm81x_reg32_write(mors, address, latch \u0026 ~(mask));\n+\tmdelay(5);\n+\tmm81x_reg32_write(mors, address, latch | mask);\n+\tmdelay(5);\n+\tmm81x_reg32_write(mors, address, latch \u0026 ~(mask));\n+\tmdelay(5);\n+}\n+\n+void mm81x_hw_enable_stop_notifications(struct mm81x *mors, bool enable)\n+{\n+\tmm81x_hw_irq_enable(mors, MM81X_INT_HW_STOP_NOTIFICATION_NUM, enable);\n+}\n+\n+void mm81x_hw_enable_burst_mode(struct mm81x *mors, const u8 burst_mode)\n+{\n+\tu32 reg32_value;\n+\n+\tmm81x_claim_bus(mors);\n+\tif (mm81x_reg32_read(mors, MM8108_REG_SDIO_DEVICE_ADDR, \u0026reg32_value))\n+\t\tgoto end;\n+\n+\treg32_value \u0026= ~(u32)(SDIO_WORD_BURST_MASK\n+\t\t\t      \u003c\u003c MM8108_REG_SDIO_DEVICE_BURST_OFFSET);\n+\treg32_value |= (u32)(burst_mode \u003c\u003c MM8108_REG_SDIO_DEVICE_BURST_OFFSET);\n+\n+\tdev_dbg(mors-\u003edev,\n+\t\t\"Setting Burst mode to %d Writing 0x%08X to the register\",\n+\t\tburst_mode, reg32_value);\n+\n+\tif (mm81x_reg32_write(mors, MM8108_REG_SDIO_DEVICE_ADDR, reg32_value))\n+\t\tgoto end;\n+\n+end:\n+\tmm81x_release_bus(mors);\n+}\n+EXPORT_SYMBOL_GPL(mm81x_hw_enable_burst_mode);\n+\n+static int mm81x_hw_enable_internal_slow_clock(struct mm81x *mors)\n+{\n+\tu32 rc_clock_reg_value;\n+\tint ret = 0;\n+\n+\tdev_dbg(mors-\u003edev, \"Enabling internal slow clock\");\n+\n+\tret = mm81x_reg32_read(mors, MM8108_REG_RC_CLK_POWER_OFF_ADDR,\n+\t\t\t       \u0026rc_clock_reg_value);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\trc_clock_reg_value \u0026= ~MM8108_REG_RC_CLK_POWER_OFF_MASK;\n+\tret = mm81x_reg32_write(mors, MM8108_REG_RC_CLK_POWER_OFF_ADDR,\n+\t\t\t\trc_clock_reg_value);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\tmm81x_hw_toggle_aon_latch(mors);\n+\n+\t/* Wait for the clock to turn on and settle */\n+\tmdelay(MM8108_SLOW_RC_POWER_ON_DELAY_MS);\n+exit:\n+\treturn ret;\n+}\n+\n+int mm81x_hw_digital_reset(struct mm81x *mors)\n+{\n+\tint ret = 0;\n+\n+\tmm81x_claim_bus(mors);\n+\n+\t/* This should be the first step in digital reset, do not reorder */\n+\tret = mm81x_hw_enable_internal_slow_clock(mors);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\tif (mors-\u003ebus_type == MM81X_BUS_TYPE_USB) {\n+\t\tret = mm81x_bus_digital_reset(mors);\n+\t\tgoto usb_done;\n+\t}\n+\n+\tif (MM81X_REG_RESET(mors) != 0)\n+\t\tret = mm81x_reg32_write(mors, MM81X_REG_RESET(mors),\n+\t\t\t\t\tMM81X_REG_RESET_VALUE(mors));\n+\n+usb_done:\n+\tmsleep(MM8108_RESET_DELAY_TIME_MS);\n+exit:\n+\tmm81x_release_bus(mors);\n+\n+\tif (!ret)\n+\t\tmors-\u003echip_was_reset = true;\n+\n+\treturn ret;\n+}\n+\n+void mm81x_hw_pre_firmware_ndr_hook(struct mm81x *mors)\n+{\n+\t/* We need disable bursting for firmware download/init procedure */\n+\tmm81x_bus_config_burst_mode(mors, false);\n+}\n+\n+void mm81x_hw_post_firmware_ndr_hook(struct mm81x *mors)\n+{\n+\t/* We are safe here to re-enable bursting again, if supported */\n+\tmm81x_bus_config_burst_mode(mors, true);\n+}\n+\n+const struct mm81x_regs mm8108_regs = {\n+\t.chip_id_address = MM8108_REG_CHIP_ID,\n+\t.irq_base_address = MM8108_REG_INT_BASE,\n+\t.trgr_base_address = MM8108_REG_TRGR_BASE,\n+\t.cpu_reset_address = MM8108_REG_RESET,\n+\t.cpu_reset_value = MM8108_REG_RESET_VALUE,\n+\t.manifest_ptr_address = MM8108_REG_MANIFEST_PTR_ADDRESS,\n+\t.msi_address = MM8108_REG_MSI_ADDRESS,\n+\t.msi_value = MM8108_REG_MSI_VALUE,\n+\t.magic_num_value = MM8108_REG_HOST_MAGIC_VALUE,\n+\t.early_clk_ctrl_value = 0,\n+\t.pager_base_address = MM8108_APPS_MAC_DMEM_ADDR_START,\n+\t.aon_latch = MM8108_REG_AON_LATCH_ADDR,\n+\t.aon_latch_mask = MM8108_REG_AON_LATCH_MASK,\n+\t.aon_reset_usb_value = MM8108_REG_AON_RESET_USB_VALUE,\n+\t.aon = MM8108_REG_AON_ADDR,\n+\t.aon_count = MM8108_REG_AON_COUNT,\n+\t.boot_address = MM8108_REG_APPS_BOOT_ADDR,\n+};\n+\n+/* B2 ROM_LINKED */\n+MODULE_FIRMWARE(MM81X_FW_DIR \"/\" MM8108_FW_BASE MM8108B2_REV_STRING\n+\t\t\tFW_ROM_LINKED_STRING MM81X_FW_EXT);\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/hw.h b/drivers/net/wireless/morsemicro/mm81x/hw.h\nnew file mode 100644\nindex 00000000000000..e22948f037bf90\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/hw.h\n@@ -0,0 +1,176 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#ifndef _MM81X_HW_H_\n+#define _MM81X_HW_H_\n+\n+#include \u003clinux/gpio/consumer.h\u003e\n+#include \"core.h\"\n+#include \"command_defs.h\"\n+\n+/* This should be at a fixed location for a family of chipset */\n+#define MM8108_REG_CHIP_ID 0x00002d20\n+\n+#define MM81X_SDIO_RW_ADDR_BOUNDARY_MASK ((u32)0xFFFF0000)\n+\n+#define MM81X_CONFIG_ACCESS_1BYTE 0\n+#define MM81X_CONFIG_ACCESS_2BYTE 1\n+#define MM81X_CONFIG_ACCESS_4BYTE 2\n+\n+#define MM81X_REG_TRGR_BASE(mors) ((mors)-\u003eregs-\u003etrgr_base_address)\n+#define MM81X_REG_TRGR1_STS(mors) (MM81X_REG_TRGR_BASE(mors) + 0x00)\n+#define MM81X_REG_TRGR1_SET(mors) (MM81X_REG_TRGR_BASE(mors) + 0x04)\n+#define MM81X_REG_TRGR1_CLR(mors) (MM81X_REG_TRGR_BASE(mors) + 0x08)\n+#define MM81X_REG_TRGR1_EN(mors) (MM81X_REG_TRGR_BASE(mors) + 0x0C)\n+#define MM81X_REG_TRGR2_STS(mors) (MM81X_REG_TRGR_BASE(mors) + 0x10)\n+#define MM81X_REG_TRGR2_SET(mors) (MM81X_REG_TRGR_BASE(mors) + 0x14)\n+#define MM81X_REG_TRGR2_CLR(mors) (MM81X_REG_TRGR_BASE(mors) + 0x18)\n+#define MM81X_REG_TRGR2_EN(mors) (MM81X_REG_TRGR_BASE(mors) + 0x1C)\n+\n+#define MM81X_REG_INT_BASE(mors) ((mors)-\u003eregs-\u003eirq_base_address)\n+#define MM81X_REG_INT1_STS(mors) (MM81X_REG_INT_BASE(mors) + 0x00)\n+#define MM81X_REG_INT1_SET(mors) (MM81X_REG_INT_BASE(mors) + 0x04)\n+#define MM81X_REG_INT1_CLR(mors) (MM81X_REG_INT_BASE(mors) + 0x08)\n+#define MM81X_REG_INT1_EN(mors) (MM81X_REG_INT_BASE(mors) + 0x0C)\n+#define MM81X_REG_INT2_STS(mors) (MM81X_REG_INT_BASE(mors) + 0x10)\n+#define MM81X_REG_INT2_SET(mors) (MM81X_REG_INT_BASE(mors) + 0x14)\n+#define MM81X_REG_INT2_CLR(mors) (MM81X_REG_INT_BASE(mors) + 0x18)\n+#define MM81X_REG_INT2_EN(mors) (MM81X_REG_INT_BASE(mors) + 0x1C)\n+\n+#define MM81X_REG_CHIP_ID(mors) ((mors)-\u003eregs-\u003echip_id_address)\n+\n+#define MM81X_REG_MSI(mors) ((mors)-\u003eregs-\u003emsi_address)\n+#define MM81X_REG_MSI_HOST_INT(mors) ((mors)-\u003eregs-\u003emsi_value)\n+\n+#define MM81X_REG_HOST_MAGIC_VALUE(mors) ((mors)-\u003eregs-\u003emagic_num_value)\n+\n+#define MM81X_REG_RESET(mors) ((mors)-\u003eregs-\u003ecpu_reset_address)\n+#define MM81X_REG_RESET_VALUE(mors) ((mors)-\u003eregs-\u003ecpu_reset_value)\n+\n+#define MM81X_REG_HOST_MANIFEST_PTR(mors) ((mors)-\u003eregs-\u003emanifest_ptr_address)\n+\n+#define MM81X_REG_EARLY_CLK_CTRL_VALUE(mors) \\\n+\t((mors)-\u003eregs-\u003eearly_clk_ctrl_value)\n+\n+#define MM81X_REG_CLK_CTRL(mors) ((mors)-\u003eregs-\u003eclk_ctrl_address)\n+#define MM81X_REG_CLK_CTRL_VALUE(mors) ((mors)-\u003eregs-\u003eclk_ctrl_value)\n+\n+#define MM81X_REG_BOOT_ADDR(mors) ((mors)-\u003eregs-\u003eboot_address)\n+#define MM81X_REG_BOOT_ADDR_VALUE(mors) ((mors)-\u003eregs-\u003eboot_value)\n+\n+#define MM81X_REG_AON_ADDR(mors) ((mors)-\u003eregs-\u003eaon)\n+#define MM81X_REG_AON_COUNT(mors) ((mors)-\u003eregs-\u003eaon_count)\n+#define MM81X_REG_AON_LATCH_ADDR(mors) ((mors)-\u003eregs-\u003eaon_latch)\n+#define MM81X_REG_AON_LATCH_MASK(mors) ((mors)-\u003eregs-\u003eaon_latch_mask)\n+#define MM81X_REG_AON_USB_RESET(mors) ((mors)-\u003eregs-\u003eaon_reset_usb_value)\n+\n+/* Bit 17 to 24 reserved for the beacon VIF 0 to 7 interrupts */\n+#define MM81X_INT_BEACON_VIF_MASK_ALL (GENMASK(24, 17))\n+#define MM81X_INT_BEACON_BASE_NUM (17)\n+\n+/* PV0 NDP probe interrupts (VIF 0 and 1). */\n+#define MM81X_INT_NDP_PROBE_REQ_PV0_VIF_MASK_ALL (GENMASK(26, 25))\n+#define MM81X_INT_NDP_PROBE_REQ_PV0_BASE_NUM (25)\n+\n+/* Bit 27 Chip to Host stop notify */\n+#define MM81X_INT_HW_STOP_NOTIFICATION_NUM (27)\n+#define MM81X_INT_HW_STOP_NOTIFICATION BIT(MM81X_INT_HW_STOP_NOTIFICATION_NUM)\n+\n+#define CHIP_TYPE_SILICON 0x0\n+\n+/* Chip ID */\n+#define MM8108XX_ID 0x9\n+\n+/* Chip Rev */\n+#define MM8108B2_REV 0x8\n+\n+/* Chip Rev String */\n+#define MM8108B_STRING \"b\"\n+#define MM8108B2_REV_STRING MM8108B_STRING \"2\"\n+\n+/* Chip ID for MM8108 */\n+#define MM8108B2_ID \\\n+\tMM81X_DEVICE_ID(MM8108XX_ID, MM8108B2_REV, CHIP_TYPE_SILICON)\n+\n+#define FW_RAM_ONLY_STRING \"\"\n+#define FW_ROM_LINKED_STRING \"-rl\"\n+#define FW_ROM_ALL_STRING \"-ro\"\n+\n+/*\n+ * Minimum time we must wait between attempting to reload the HW after a\n+ * stop notification\n+ */\n+#define HW_RELOAD_AFTER_STOP_WINDOW 5\n+\n+enum host_table_firmware_flags {\n+\tMM81X_FW_FLAGS_SUPPORT_S1G = BIT(0),\n+\tMM81X_FW_FLAGS_BUSY_ACTIVE_LOW = BIT(1),\n+\tMM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION = BIT(2),\n+\tMM81X_FW_FLAGS_SUPPORT_HW_SCAN = BIT(3),\n+\tMM81X_FW_FLAGS_SUPPORT_CHIP_HALT_IRQ = BIT(4),\n+};\n+\n+struct host_table {\n+\t__le32 magic_number;\n+\t__le32 fw_version_number;\n+\t__le32 host_flags;\n+\t__le32 firmware_flags;\n+\t__le32 memcmd_cmd_addr;\n+\t__le32 memcmd_resp_addr;\n+\t__le32 ext_host_tbl_addr;\n+} __packed;\n+\n+struct mm81x_regs {\n+\tu32 chip_id_address;\n+\tu32 irq_base_address;\n+\tu32 trgr_base_address;\n+\tu32 cpu_reset_address;\n+\tu32 cpu_reset_value;\n+\tu32 msi_address;\n+\tu32 msi_value;\n+\tu32 manifest_ptr_address;\n+\tu32 magic_num_value;\n+\tu32 clk_ctrl_address;\n+\tu32 clk_ctrl_value;\n+\tu32 early_clk_ctrl_value;\n+\tu32 boot_address;\n+\tu32 boot_value;\n+\tu32 pager_base_address;\n+\tu32 aon_latch;\n+\tu32 aon_latch_mask;\n+\tu32 aon_reset_usb_value;\n+\tu32 aon;\n+\tu8 aon_count;\n+};\n+\n+int mm81x_hw_otp_get_board_type(struct mm81x *mors);\n+bool mm81x_hw_otp_valid_board_type(u32 board_type);\n+int mm81x_hw_otp_get_mac_addr(struct mm81x *mors);\n+\n+void mm81x_hw_irq_enable(struct mm81x *mors, u32 irq, bool enable);\n+int mm81x_hw_irq_handle(struct mm81x *mors);\n+void mm81x_hw_irq_clear(struct mm81x *mors);\n+void mm81x_hw_toggle_aon_latch(struct mm81x *mors);\n+void mm81x_hw_enable_burst_mode(struct mm81x *mors, const u8 burst_mode);\n+int mm81x_hw_digital_reset(struct mm81x *mors);\n+void mm81x_hw_pre_firmware_ndr_hook(struct mm81x *mors);\n+void mm81x_hw_post_firmware_ndr_hook(struct mm81x *mors);\n+\n+enum sdio_burst_mode {\n+\tSDIO_WORD_BURST_DISABLE =\n+\t\t0, /* Intentionally duplicate to make it clear it's disabled */\n+\tSDIO_WORD_BURST_SIZE_0 = 0, /* 000: no bursting (single 32bit word) */\n+\tSDIO_WORD_BURST_SIZE_2 = 1, /* 001: bursts of 2 words */\n+\tSDIO_WORD_BURST_SIZE_4 = 2, /* 010: bursts of 4 words */\n+\tSDIO_WORD_BURST_SIZE_8 = 3, /* 011: bursts of 8 words */\n+\tSDIO_WORD_BURST_SIZE_16 = 4, /* 100: bursts of 16 words */\n+\tSDIO_WORD_BURST_MASK = 7,\n+};\n+\n+extern const struct mm81x_regs mm8108_regs;\n+\n+void mm81x_hw_enable_stop_notifications(struct mm81x *mors, bool enable);\n+\n+#endif /* !_MM81X_HW_H_ */\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/mac.c b/drivers/net/wireless/morsemicro/mm81x/mac.c\nnew file mode 100644\nindex 00000000000000..71d559be71ff3a\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/mac.c\n@@ -0,0 +1,2443 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+#include \"core.h\"\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/jiffies.h\u003e\n+#include \u003clinux/crc32.h\u003e\n+#include \u003cnet/mac80211.h\u003e\n+#include \u003casm/div64.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \"hif.h\"\n+#include \"mac.h\"\n+#include \"bus.h\"\n+#include \"ps.h\"\n+#include \"rc.h\"\n+\n+/*\n+ * Arbitrary size limit for the filter command address list, to ensure that\n+ * the command does not exceed page/MTU size. This will be far greater than\n+ * the number of filters supported by the firmware.\n+ */\n+#define MCAST_FILTER_COUNT_MAX (1024 / sizeof(filter-\u003eaddr_list[0]))\n+\n+/* Calculate average RSSI for Rx status */\n+#define CALC_AVG_RSSI(_avg, _sample) ((((_avg) * 9 + (_sample)) / 10))\n+\n+/*\n+ * When automatically trying MCS0 before MCS10, this is how many\n+ * MCS0 attempts to make\n+ */\n+#define MCS0_BEFORE_MCS10_COUNT (1)\n+\n+/* Maximum TX power (default) */\n+#define MAX_TX_POWER_MBM (2200)\n+\n+/*\n+ * Since S1G runs at 1/10th the clockrate of VHT, the worst-case\n+ * transmission time is significantly longer then that of non-S1G\n+ * PHYs.\n+ */\n+#define MM81X_FLUSH_TIMEOUT (16 * HZ)\n+\n+/* Default queue count */\n+#define MM81X_HW_QUEUE_COUNT (4)\n+\n+/* Max rates per skb */\n+#define MM81X_HW_MAX_RATES (4)\n+\n+/* Max reported rates */\n+#define MM81X_HW_MAX_REPORT_RATES (4)\n+\n+/* Max rate attempts */\n+#define MM81X_HW_MAX_RATE_TRIES (1)\n+\n+/* Max sk pacing shift */\n+#define MM81X_HW_TX_SK_PACING_SHIFT (3)\n+\n+/* NSS/MCS map values */\n+#define MM81X_NSS_MCS_BYTE_0 0xfe /* 1SS */\n+#define MM81X_NSS_MCS_BYTE_1 0x00\n+#define MM81X_NSS_MCS_BYTE_2 0xfc /* 1SS */\n+#define MM81X_NSS_MCS_BYTE_3 0x01\n+#define MM81X_NSS_MCS_BYTE_4 0x00\n+\n+/* HW restart delay time before terminating hardware IF work items */\n+#define MM81X_HW_RESTART_DELAY_MS 20\n+\n+/* clang-format off */\n+\n+/* mm81x chips do not support 16MHz */\n+#define CHANS1G(channel, frequency, offset, chan_flags)\t\t\\\n+{\t\t\t\t\t\t\t\t\\\n+\t.band = NL80211_BAND_S1GHZ,\t\t\t\t\\\n+\t.center_freq = (frequency),\t\t\t\t\\\n+\t.freq_offset = (offset),\t\t\t\t\\\n+\t.hw_value = (channel),\t\t\t\t\t\\\n+\t.flags = ((chan_flags) | IEEE80211_CHAN_NO_16MHZ),\t\\\n+\t.max_antenna_gain = 0,\t\t\t\t\t\\\n+\t.max_power = 30,\t\t\t\t\t\\\n+}\n+\n+static struct ieee80211_channel mors_s1ghz_channels[] = {\n+\tCHANS1G(1, 902, 500, IEEE80211_CHAN_S1G_NO_PRIMARY),\n+\tCHANS1G(3, 903, 500, 0),\n+\tCHANS1G(5, 904, 500, 0),\n+\tCHANS1G(7, 905, 500, 0),\n+\tCHANS1G(9, 906, 500, 0),\n+\tCHANS1G(11, 907, 500, 0),\n+\tCHANS1G(13, 908, 500, 0),\n+\tCHANS1G(15, 909, 500, 0),\n+\tCHANS1G(17, 910, 500, 0),\n+\tCHANS1G(19, 911, 500, 0),\n+\tCHANS1G(21, 912, 500, 0),\n+\tCHANS1G(23, 913, 500, 0),\n+\tCHANS1G(25, 914, 500, 0),\n+\tCHANS1G(27, 915, 500, 0),\n+\tCHANS1G(29, 916, 500, 0),\n+\tCHANS1G(31, 917, 500, 0),\n+\tCHANS1G(33, 918, 500, 0),\n+\tCHANS1G(35, 919, 500, 0),\n+\tCHANS1G(37, 920, 500, 0),\n+\tCHANS1G(39, 921, 500, 0),\n+\tCHANS1G(41, 922, 500, 0),\n+\tCHANS1G(43, 923, 500, 0),\n+\tCHANS1G(45, 924, 500, 0),\n+\tCHANS1G(47, 925, 500, 0),\n+\tCHANS1G(49, 926, 500, 0),\n+\tCHANS1G(51, 927, 500, IEEE80211_CHAN_S1G_NO_PRIMARY),\n+};\n+\n+/* clang-format on */\n+\n+static struct ieee80211_supported_band mors_band_s1ghz = {\n+\t.band = NL80211_BAND_S1GHZ,\n+\t.s1g_cap.s1g = true,\n+\t.channels = mors_s1ghz_channels,\n+\t.n_channels = ARRAY_SIZE(mors_s1ghz_channels),\n+\t.bitrates = NULL,\n+\t.n_bitrates = 0,\n+\t.s1g_cap.cap[4] = 0x80 /* STA type sensor only for AP \u0026 STA */\n+};\n+\n+static struct ieee80211_iface_limit mors_if_limits[] = {\n+\t{\n+\t\t.max = MM81X_MAX_IF,\n+\t\t.types = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP),\n+\t},\n+};\n+\n+static struct ieee80211_iface_combination mors_if_combs[] = {\n+\t{\n+\t\t.limits = mors_if_limits,\n+\t\t.n_limits = ARRAY_SIZE(mors_if_limits),\n+\t\t.max_interfaces = MM81X_MAX_IF,\n+\t\t.num_different_channels = 1,\n+\t},\n+};\n+\n+/* Convert from a time in time units (1024us) to us */\n+#define MM81X_TU_TO_US(x) ((x) * 1024UL)\n+\n+/* Convert from a time in time units (1024us) to ms */\n+#define MM81X_TU_TO_MS(x) (MM81X_TU_TO_US(x) / 1000UL)\n+\n+/* Default time to dwell on a scan channel */\n+#define MM81X_HWSCAN_DEFAULT_DWELL_TIME_MS (30)\n+\n+/* Default time to dwell on a scan channel for passive scan */\n+#define MM81X_HWSCAN_DEFAULT_PASSIVE_DWELL_TIME_MS (110)\n+\n+/* Default time to dwell on home channel, in between scan channels */\n+#define MM81X_HWSCAN_DEFAULT_DWELL_ON_HOME_MS (200)\n+\n+/* Typical time it takes to send the probe */\n+#define MM81X_HWSCAN_PROBE_DELAY_MS (30)\n+\n+/* A margin to account for event/command processing */\n+#define MM81X_HWSCAN_TIMEOUT_OVERHEAD_MS (2000)\n+\n+/* Scan channel frequency mask */\n+#define HW_SCAN_CH_LIST_FREQ_KHZ GENMASK(19, 0)\n+\n+/*\n+ * Scan channel bandwidth mask.\n+ * Encoded as: 0 = 1MHz, 1 = 2MHz, 2 = 4MHz, 3 = 8MHz\n+ */\n+#define HW_SCAN_CH_LIST_OP_BW GENMASK(21, 20)\n+\n+/*\n+ * Scan channel primary channel width.\n+ * Encoded as: 0 = 1MHz, 1 = 2MHz\n+ */\n+#define HW_SCAN_CH_LIST_PRIM_CH_WIDTH BIT(22)\n+\n+/* Index into power_list for tx power of channel */\n+#define HW_SCAN_CH_LIST_PWR_LIST_IDX GENMASK(31, 26)\n+\n+struct hw_scan_tlv_hdr {\n+\t__le16 tag;\n+\t__le16 len;\n+} __packed;\n+\n+struct hw_scan_tlv_channel_list {\n+\tstruct hw_scan_tlv_hdr hdr;\n+\t__le32 channels[];\n+} __packed;\n+\n+struct hw_scan_tlv_power_list {\n+\tstruct hw_scan_tlv_hdr hdr;\n+\ts32 tx_power_qdbm[];\n+} __packed;\n+\n+struct hw_scan_tlv_probe_req {\n+\tstruct hw_scan_tlv_hdr hdr;\n+\t/* Probe request frame template (including SSIDs) */\n+\tu8 buf[];\n+} __packed;\n+\n+struct hw_scan_tlv_dwell_on_home {\n+\tstruct hw_scan_tlv_hdr hdr;\n+\t/* Time to dwell on home between scan channels */\n+\t__le32 home_dwell_time_ms;\n+} __packed;\n+\n+#define DOT11AH_BA_MAX_MPDU_PER_AMPDU (32)\n+\n+/* wiphy scan params */\n+#define MM81X_MAX_SCAN_IE_LEN 512\n+#define MM81X_MAX_SCAN_SSIDS 1\n+#define MM81X_MAX_REMAIN_ON_CHAN_DURATION 10000\n+\n+static bool mm81x_reg_h_cc_equal(const char *cc1, const char *cc2)\n+{\n+\treturn (cc1[0] == cc2[0]) \u0026\u0026 (cc1[1] == cc2[1]);\n+}\n+\n+static bool mm81x_tx_h_pkt_over_rts_threshold(struct mm81x *mors,\n+\t\t\t\t\t      struct ieee80211_tx_info *info,\n+\t\t\t\t\t      struct sk_buff *skb)\n+{\n+\tu8 ccmp_len;\n+\n+\tif (!info-\u003econtrol.hw_key)\n+\t\treturn ((skb-\u003elen + FCS_LEN) \u003e mors-\u003erts_threshold);\n+\n+\tif (info-\u003econtrol.hw_key-\u003ekeylen == 32)\n+\t\tccmp_len =\n+\t\t\tIEEE80211_CCMP_256_HDR_LEN + IEEE80211_CCMP_256_MIC_LEN;\n+\telse if (info-\u003econtrol.hw_key-\u003ekeylen == 16)\n+\t\tccmp_len = IEEE80211_CCMP_HDR_LEN + IEEE80211_CCMP_MIC_LEN;\n+\telse\n+\t\tccmp_len = 0;\n+\n+\treturn ((skb-\u003elen + FCS_LEN + ccmp_len) \u003e mors-\u003erts_threshold);\n+}\n+\n+static bool mm81x_tx_h_ps_filtered_for_sta(struct mm81x *mors,\n+\t\t\t\t\t   struct sk_buff *skb,\n+\t\t\t\t\t   struct ieee80211_sta *sta)\n+{\n+\tstruct mm81x_sta *mors_sta;\n+\tstruct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);\n+\n+\tif (!sta)\n+\t\treturn false;\n+\n+\tmors_sta = (struct mm81x_sta *)sta-\u003edrv_priv;\n+\n+\tif (!mors_sta-\u003etx_ps_filter_en)\n+\t\treturn false;\n+\n+\tdev_dbg(mors-\u003edev, \"Frame for sta[%pM] PS filtered\", mors_sta-\u003eaddr);\n+\n+\tinfo-\u003eflags |= IEEE80211_TX_STAT_TX_FILTERED;\n+\tinfo-\u003eflags \u0026= ~IEEE80211_TX_CTL_AMPDU;\n+\n+\tieee80211_tx_status_skb(mors-\u003ehw, skb);\n+\treturn true;\n+}\n+\n+static void mm81x_mac_check_fw_disabled_chans(struct ieee80211_hw *hw)\n+{\n+\tint ret = 0;\n+\tu32 i;\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct host_cmd_resp_get_disabled_channels *resp;\n+\tu32 resp_len = sizeof(struct host_cmd_disabled_channel_entry) *\n+\t\t\t       ARRAY_SIZE(mors_s1ghz_channels) +\n+\t\t       sizeof(*resp);\n+\n+\tresp = kzalloc(resp_len, GFP_KERNEL);\n+\tif (!resp) {\n+\t\tret = -ENOMEM;\n+\t\tgoto out;\n+\t}\n+\n+\tret = mm81x_cmd_get_disabled_channels(mors, resp, resp_len);\n+\tif (ret)\n+\t\tgoto out;\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(mors_s1ghz_channels); i++) {\n+\t\tstruct ieee80211_channel *ch = \u0026mors_s1ghz_channels[i];\n+\n+\t\tif (ch-\u003eflags \u0026 IEEE80211_CHAN_DISABLED)\n+\t\t\tcontinue;\n+\n+\t\tch-\u003eflags \u0026= ~IEEE80211_CHAN_S1G_NO_PRIMARY;\n+\t}\n+\n+\tfor (i = 0; i \u003c le32_to_cpu(resp-\u003en_channels); i++) {\n+\t\tstruct ieee80211_channel *ch;\n+\t\tstruct host_cmd_disabled_channel_entry *entry =\n+\t\t\t\u0026resp-\u003echannels[i];\n+\n+\t\tif (entry-\u003ebw_mhz != 1)\n+\t\t\tcontinue;\n+\n+\t\tch = ieee80211_get_channel_khz(\n+\t\t\thw-\u003ewiphy,\n+\t\t\tKHZ100_TO_KHZ(le16_to_cpu(entry-\u003efreq_100khz)));\n+\t\tif (!ch)\n+\t\t\tcontinue;\n+\n+\t\tch-\u003eflags |= IEEE80211_CHAN_S1G_NO_PRIMARY;\n+\t\tdev_dbg(mors-\u003edev, \"set NO_PRIMARY on %u KHz\",\n+\t\t\tieee80211_channel_to_khz(ch));\n+\t}\n+\n+out:\n+\tif (ret)\n+\t\tdev_err(mors-\u003edev, \"failed to set disabled primary channels\");\n+\n+\tkfree(resp);\n+}\n+\n+static int mm81x_mac_ops_start(struct ieee80211_hw *hw)\n+{\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\n+\tmors-\u003estarted = true;\n+\treturn 0;\n+}\n+\n+static int mm81x_tx_h_get_max_bw(struct mm81x *mors)\n+{\n+\treturn MM81X_FW_SUPP(\u0026mors-\u003efw_caps, 8MHZ) ? 8 :\n+\t       MM81X_FW_SUPP(\u0026mors-\u003efw_caps, 4MHZ) ? 4 :\n+\t       MM81X_FW_SUPP(\u0026mors-\u003efw_caps, 2MHZ) ? 2 :\n+\t\t\t\t\t\t     1;\n+}\n+\n+static void mm81x_mac_caps_init(struct mm81x *mors)\n+{\n+\tstruct mm81x_fw_caps *fw_caps = \u0026mors-\u003efw_caps;\n+\tstruct ieee80211_sta_s1g_cap *s1g = \u0026mors_band_s1ghz.s1g_cap;\n+\n+#define __FW_CAP_N(_n, _cap, _bit)                \\\n+\tdo {                                      \\\n+\t\tif (MM81X_FW_SUPP(fw_caps, _cap)) \\\n+\t\t\ts1g-\u003ecap[_n] |= (_bit);   \\\n+\t} while (0)\n+\n+#define FW_CAP0(_cap, _bit) __FW_CAP_N(0, _cap, _bit)\n+#define FW_CAP3(_cap, _bit) __FW_CAP_N(3, _cap, _bit)\n+#define FW_CAP5(_cap, _bit) __FW_CAP_N(5, _cap, _bit)\n+#define FW_CAP6(_cap, _bit) __FW_CAP_N(6, _cap, _bit)\n+#define FW_CAP7(_cap, _bit) __FW_CAP_N(7, _cap, _bit)\n+#define FW_CAP8(_cap, _bit) __FW_CAP_N(8, _cap, _bit)\n+#define FW_CAP9(_cap, _bit) __FW_CAP_N(9, _cap, _bit)\n+\n+\tFW_CAP0(S1G_LONG, S1G_CAP0_S1G_LONG);\n+\n+\ts1g-\u003ecap[0] |= S1G_CAP0_SGI_1MHZ;\n+\tif (MM81X_FW_SUPP(fw_caps, SGI)) {\n+\t\tFW_CAP0(2MHZ, S1G_CAP0_SGI_2MHZ);\n+\t\tFW_CAP0(4MHZ, S1G_CAP0_SGI_4MHZ);\n+\t\tFW_CAP0(8MHZ, S1G_CAP0_SGI_8MHZ);\n+\t}\n+\n+\tif (MM81X_FW_SUPP(fw_caps, 8MHZ))\n+\t\ts1g-\u003ecap[0] |= S1G_SUPP_CH_WIDTH_8;\n+\telse if (MM81X_FW_SUPP(fw_caps, 4MHZ))\n+\t\ts1g-\u003ecap[0] |= S1G_SUPP_CH_WIDTH_4;\n+\telse if (MM81X_FW_SUPP(fw_caps, 2MHZ))\n+\t\ts1g-\u003ecap[0] |= S1G_SUPP_CH_WIDTH_2;\n+\n+\tFW_CAP3(RD_RESPONDER, S1G_CAP3_RD_RESPONDER);\n+\tFW_CAP3(LONG_MPDU, S1G_CAP3_MAX_MPDU_LEN);\n+\n+\tFW_CAP5(AMSDU, S1G_CAP5_AMSDU);\n+\tFW_CAP5(AMPDU, S1G_CAP5_AMPDU);\n+\tFW_CAP5(ASYMMETRIC_BA_SUPPORT, S1G_CAP5_ASYMMETRIC_BA);\n+\tFW_CAP5(FLOW_CONTROL, S1G_CAP5_FLOW_CONTROL);\n+\n+\tFW_CAP6(OBSS_MITIGATION, S1G_CAP6_OBSS_MITIGATION);\n+\tFW_CAP6(FRAGMENT_BA, S1G_CAP6_FRAGMENT_BA);\n+\tFW_CAP6(NDP_PSPOLL, S1G_CAP6_NDP_PS_POLL);\n+\tFW_CAP6(TXOP_SHARING_IMPLICIT_ACK, S1G_CAP6_TXOP_SHARING_IMP_ACK);\n+\tFW_CAP6(HTC_VHT_MFB, S1G_CAP6_VHT_LINK_ADAPT);\n+\n+\tFW_CAP7(TACK_AS_PSPOLL, S1G_CAP7_TACK_AS_PS_POLL);\n+\tFW_CAP7(DUPLICATE_1MHZ, S1G_CAP7_DUP_1MHZ);\n+\tFW_CAP7(MCS_NEGOTIATION, S1G_CAP7_MCS_NEGOTIATION);\n+\tFW_CAP7(1MHZ_CONTROL_RESPONSE_PREAMBLE,\n+\t\tS1G_CAP7_1MHZ_CTL_RESPONSE_PREAMBLE);\n+\tFW_CAP7(SECTOR_TRAINING, S1G_CAP7_SECTOR_TRAINING_OPERATION);\n+\tFW_CAP7(TMP_PS_MODE_SWITCH, S1G_CAP7_TEMP_PS_MODE_SWITCH);\n+\n+\tFW_CAP8(BDT, S1G_CAP8_BDT);\n+\n+\tFW_CAP9(LINK_ADAPTATION_WO_NDP_CMAC,\n+\t\tS1G_CAP9_LINK_ADAPT_PER_CONTROL_RESPONSE);\n+\n+\t/* 1SS MCS 9 for Rx / Tx map */\n+\ts1g-\u003enss_mcs[0] = MM81X_NSS_MCS_BYTE_0;\n+\ts1g-\u003enss_mcs[1] = MM81X_NSS_MCS_BYTE_1;\n+\ts1g-\u003enss_mcs[2] = MM81X_NSS_MCS_BYTE_2;\n+\ts1g-\u003enss_mcs[3] = MM81X_NSS_MCS_BYTE_3;\n+\ts1g-\u003enss_mcs[4] = MM81X_NSS_MCS_BYTE_4;\n+\n+#undef FW_CAP0\n+#undef FW_CAP3\n+#undef FW_CAP5\n+#undef FW_CAP6\n+#undef FW_CAP7\n+#undef FW_CAP8\n+#undef FW_CAP9\n+#undef __FW_CAP_N\n+}\n+\n+static void mm81x_mac_beacon_irq_enable(struct mm81x_vif *mors_vif, bool enable)\n+{\n+\tstruct mm81x *mors = mm81x_vif_to_mors(mors_vif);\n+\tu8 beacon_irq_num = MM81X_INT_BEACON_BASE_NUM + mors_vif-\u003eid;\n+\n+\tenable ? set_bit(beacon_irq_num, \u0026mors-\u003ebeacon_irqs_enabled) :\n+\t\t clear_bit(beacon_irq_num, \u0026mors-\u003ebeacon_irqs_enabled);\n+\n+\tmm81x_hw_irq_enable(mors, beacon_irq_num, enable);\n+}\n+\n+static void mm81x_beacon_h_fill_tx_info(struct mm81x *mors,\n+\t\t\t\t\tstruct mm81x_skb_tx_info *tx_info,\n+\t\t\t\t\tstruct mm81x_vif *mors_vif,\n+\t\t\t\t\tint tx_bw_mhz)\n+{\n+\tenum dot11_bandwidth bw_idx =\n+\t\tmm81x_ratecode_bw_mhz_to_bw_index(tx_bw_mhz);\n+\tenum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT;\n+\n+\ttx_info-\u003eflags |=\n+\t\tcpu_to_le32(MM81X_TX_CONF_FLAGS_VIF_ID_SET(mors_vif-\u003eid));\n+\n+\tif (bw_idx == DOT11_BANDWIDTH_1MHZ)\n+\t\tpream = MM81X_RATE_PREAMBLE_S1G_1M;\n+\n+\ttx_info-\u003erates[0].count = 1;\n+\ttx_info-\u003erates[1].count = 0;\n+\ttx_info-\u003erates[0].mm81x_ratecode =\n+\t\tmm81x_ratecode_init(bw_idx, 0, 0, pream);\n+\n+\tif (mors-\u003efirmware_flags \u0026 MM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION)\n+\t\ttx_info-\u003eflags |=\n+\t\t\tcpu_to_le32(MM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT);\n+}\n+\n+static void mm81x_mac_beacon_work(struct work_struct *work)\n+{\n+\tstruct mm81x_vif *mors_vif =\n+\t\tfrom_work(mors_vif, work, u.ap.beacon_work);\n+\tstruct mm81x *mors = mm81x_vif_to_mors(mors_vif);\n+\tstruct mm81x_skbq *mq;\n+\tstruct sk_buff *beacon;\n+\tstruct ieee80211_vif *vif = mm81x_vif_to_ieee80211_vif(mors_vif);\n+\tstruct mm81x_skb_tx_info tx_info = { 0 };\n+\tint num_bcn_vifs = atomic_read(\u0026mors-\u003enum_bcn_vifs);\n+\n+\tmq = mm81x_hif_get_tx_beacon_queue(mors);\n+\tif (!mq) {\n+\t\tdev_err(mors-\u003edev, \"no matching beacon Q found\");\n+\t\treturn;\n+\t}\n+\n+\tif (mm81x_skbq_count(mq) \u003e= num_bcn_vifs) {\n+\t\tdev_err(mors-\u003edev,\n+\t\t\t\"previous beacon not consumed, dropping req [id:%d]\",\n+\t\t\tmors_vif-\u003eid);\n+\t\treturn;\n+\t}\n+\n+\tbeacon = ieee80211_beacon_get(mors-\u003ehw, vif, false);\n+\tif (!beacon)\n+\t\treturn;\n+\n+\tmm81x_beacon_h_fill_tx_info(mors, \u0026tx_info, mors_vif,\n+\t\t\t\t    cfg80211_chandef_s1g_pri_width(\u0026mors-\u003echandef));\n+\tmm81x_skbq_skb_tx(mq, \u0026beacon, \u0026tx_info, MM81X_SKB_CHAN_BEACON);\n+}\n+\n+void mm81x_mac_beacon_irq_handle(struct mm81x *mors, u32 status)\n+{\n+\tint vif_id;\n+\tunsigned long masked_status = (status \u0026 mors-\u003ebeacon_irqs_enabled) \u003e\u003e\n+\t\t\t\t      MM81X_INT_BEACON_BASE_NUM;\n+\n+\tguard(rcu)();\n+\tfor_each_set_bit(vif_id, \u0026masked_status, MM81X_MAX_IF) {\n+\t\tstruct mm81x_vif *mors_vif;\n+\t\tstruct ieee80211_vif *vif;\n+\n+\t\tvif = mm81x_rcu_dereference_vif_id(mors, vif_id, true);\n+\t\tif (vif) {\n+\t\t\tmors_vif = ieee80211_vif_to_mors_vif(vif);\n+\t\t\tqueue_work(system_bh_wq, \u0026mors_vif-\u003eu.ap.beacon_work);\n+\t\t}\n+\t}\n+}\n+\n+static void mm81x_mac_beacon_init(struct mm81x_vif *mors_vif)\n+{\n+\tstruct mm81x *mors = mm81x_vif_to_mors(mors_vif);\n+\n+\tINIT_WORK(\u0026mors_vif-\u003eu.ap.beacon_work, mm81x_mac_beacon_work);\n+\tmm81x_mac_beacon_irq_enable(mors_vif, true);\n+\tatomic_inc(\u0026mors-\u003enum_bcn_vifs);\n+}\n+\n+static struct hw_scan_tlv_hdr mm81x_hw_scan_h_pack_tlv_hdr(u16 tag, u16 len)\n+{\n+\tstruct hw_scan_tlv_hdr hdr = { .tag = cpu_to_le16(tag),\n+\t\t\t\t       .len = cpu_to_le16(len) };\n+\treturn hdr;\n+}\n+\n+static __le32 mm81x_hw_scan_h_pack_channel(struct ieee80211_channel *chan,\n+\t\t\t\t\t   u8 pwr_idx)\n+{\n+\t__le32 packed = 0;\n+\tu32 freq_khz = ieee80211_channel_to_khz(chan);\n+\n+\tpacked |= le32_encode_bits(freq_khz, HW_SCAN_CH_LIST_FREQ_KHZ);\n+\tpacked |= le32_encode_bits(mm81x_ratecode_bw_mhz_to_bw_index(1),\n+\t\t\t\t   HW_SCAN_CH_LIST_OP_BW);\n+\tpacked |= le32_encode_bits(mm81x_ratecode_bw_mhz_to_bw_index(1),\n+\t\t\t\t   HW_SCAN_CH_LIST_PRIM_CH_WIDTH);\n+\tpacked |= le32_encode_bits(pwr_idx, HW_SCAN_CH_LIST_PWR_LIST_IDX);\n+\n+\treturn packed;\n+}\n+\n+static u8 *\n+mm81x_hw_scan_h_add_channel_list_tlv(u8 *buf,\n+\t\t\t\t     struct mm81x_hw_scan_params *params)\n+{\n+\tint i;\n+\tstruct hw_scan_tlv_channel_list *ch_list =\n+\t\t(struct hw_scan_tlv_channel_list *)buf;\n+\n+\tch_list-\u003ehdr = mm81x_hw_scan_h_pack_tlv_hdr(\n+\t\tHOST_CMD_HW_SCAN_TLV_TAG_CHAN_LIST,\n+\t\tparams-\u003enum_chans * sizeof(ch_list-\u003echannels[0]));\n+\n+\tfor (i = 0; i \u003c params-\u003enum_chans; i++) {\n+\t\tstruct ieee80211_channel *chan = params-\u003echannels[i].channel;\n+\n+\t\tch_list-\u003echannels[i] = mm81x_hw_scan_h_pack_channel(\n+\t\t\tchan, params-\u003echannels[i].power_idx);\n+\t}\n+\n+\treturn (u8 *)\u0026ch_list-\u003echannels[i];\n+}\n+\n+static u8 *\n+mm81x_hw_scan_h_add_power_list_tlv(u8 *buf, struct mm81x_hw_scan_params *params)\n+{\n+\tint i;\n+\tstruct hw_scan_tlv_power_list *pwr_list =\n+\t\t(struct hw_scan_tlv_power_list *)buf;\n+\tsize_t size = sizeof(pwr_list-\u003etx_power_qdbm[0]) * params-\u003en_powers;\n+\n+\tpwr_list-\u003ehdr = mm81x_hw_scan_h_pack_tlv_hdr(\n+\t\tHOST_CMD_HW_SCAN_TLV_TAG_POWER_LIST, size);\n+\n+\tfor (i = 0; i \u003c params-\u003en_powers; i++)\n+\t\tpwr_list-\u003etx_power_qdbm[i] = params-\u003epowers_qdbm[i];\n+\n+\treturn (u8 *)\u0026pwr_list-\u003etx_power_qdbm[i];\n+}\n+\n+static u8 *\n+mm81x_hw_scan_h_add_probe_req_tlv(u8 *buf, struct mm81x_hw_scan_params *params)\n+{\n+\tstruct sk_buff *skb = params-\u003eprobe_req;\n+\tstruct hw_scan_tlv_probe_req *probe_req =\n+\t\t(struct hw_scan_tlv_probe_req *)buf;\n+\n+\tprobe_req-\u003ehdr = mm81x_hw_scan_h_pack_tlv_hdr(\n+\t\tHOST_CMD_HW_SCAN_TLV_TAG_PROBE_REQ, skb-\u003elen);\n+\tmemcpy(probe_req-\u003ebuf, skb-\u003edata, skb-\u003elen);\n+\n+\treturn buf + sizeof(*probe_req) + skb-\u003elen;\n+}\n+\n+static u8 *\n+mm81x_hw_scan_h_insert_dwell_time_tlv(u8 *buf,\n+\t\t\t\t      struct mm81x_hw_scan_params *params)\n+{\n+\tstruct hw_scan_tlv_dwell_on_home *dwell =\n+\t\t(struct hw_scan_tlv_dwell_on_home *)buf;\n+\n+\tdwell-\u003ehdr = mm81x_hw_scan_h_pack_tlv_hdr(\n+\t\tHOST_CMD_HW_SCAN_TLV_TAG_DWELL_ON_HOME,\n+\t\tsizeof(*dwell) - sizeof(dwell-\u003ehdr));\n+\tdwell-\u003ehome_dwell_time_ms = cpu_to_le32(params-\u003edwell_on_home_ms);\n+\n+\treturn buf + sizeof(*dwell);\n+}\n+\n+static int __mm81x_hw_scan_h_init_probe_req(struct mm81x_hw_scan_params *params,\n+\t\t\t\t\t    u8 *ssid, u8 ssid_len,\n+\t\t\t\t\t    struct ieee80211_scan_ies *ies)\n+{\n+\tu8 *pos;\n+\tstruct sk_buff *probe_req;\n+\tstruct ieee80211_tx_info *info;\n+\tu16 ies_len = ies-\u003elen[NL80211_BAND_S1GHZ] + ies-\u003ecommon_ie_len;\n+\n+\tprobe_req = ieee80211_probereq_get(params-\u003ehw, params-\u003evif-\u003eaddr, ssid,\n+\t\t\t\t\t   ssid_len, ies_len);\n+\tif (!probe_req)\n+\t\treturn -ENOMEM;\n+\n+\tpos = skb_put(probe_req, ies_len);\n+\tmemcpy(pos, ies-\u003ecommon_ies, ies-\u003ecommon_ie_len);\n+\tpos += ies-\u003ecommon_ie_len;\n+\tmemcpy(pos, ies-\u003eies[NL80211_BAND_S1GHZ], ies-\u003elen[NL80211_BAND_S1GHZ]);\n+\n+\tinfo = IEEE80211_SKB_CB(probe_req);\n+\tinfo-\u003econtrol.vif = params-\u003evif;\n+\tparams-\u003eprobe_req = probe_req;\n+\n+\treturn 0;\n+}\n+\n+static void mm81x_hw_scan_h_init_ssid(struct mm81x *mors,\n+\t\t\t\t      struct cfg80211_ssid *ssids, int n_ssids,\n+\t\t\t\t      u8 **out_ssid, u8 *out_ssid_len)\n+{\n+\t*out_ssid = NULL;\n+\t*out_ssid_len = 0;\n+\n+\tif (n_ssids \u003e 0) {\n+\t\tif (n_ssids \u003e 1) {\n+\t\t\tdev_warn(\n+\t\t\t\tmors-\u003edev,\n+\t\t\t\t\"Multiple SSIDs found when only one supported. Using the first only.\");\n+\t\t}\n+\t\t*out_ssid_len = ssids[0].ssid_len;\n+\t\t*out_ssid = ssids[0].ssid;\n+\t}\n+}\n+\n+static int\n+mm81x_hw_scan_h_init_probe_req(struct mm81x_hw_scan_params *params,\n+\t\t\t       struct ieee80211_scan_request *scan_req)\n+{\n+\tstruct mm81x *mors = params-\u003ehw-\u003epriv;\n+\tstruct cfg80211_scan_request *req = \u0026scan_req-\u003ereq;\n+\tstruct ieee80211_scan_ies *ies = \u0026scan_req-\u003eies;\n+\tu8 ssid_len = 0;\n+\tu8 *ssid = NULL;\n+\n+\tmm81x_hw_scan_h_init_ssid(mors, req-\u003essids, req-\u003en_ssids, \u0026ssid,\n+\t\t\t\t  \u0026ssid_len);\n+\n+\treturn __mm81x_hw_scan_h_init_probe_req(params, ssid, ssid_len, ies);\n+}\n+\n+static bool\n+mm81x_hw_scan_h_is_chan_present(const struct mm81x_hw_scan_params *params,\n+\t\t\t\tconst struct ieee80211_channel *chan)\n+{\n+\tint channel;\n+\n+\tfor (channel = 0; channel \u003c params-\u003enum_chans; channel++) {\n+\t\tif (params-\u003echannels[channel].channel == chan)\n+\t\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\n+\n+static int mm81x_hw_scan_h_insert_chan(struct mm81x_hw_scan_params *params,\n+\t\t\t\t       struct ieee80211_channel *chan)\n+{\n+\tif (!params-\u003echannels)\n+\t\treturn -EFAULT;\n+\n+\tif (!chan)\n+\t\treturn -EFAULT;\n+\n+\tif (params-\u003enum_chans \u003e= params-\u003eallocated_chans)\n+\t\treturn -ENOMEM;\n+\n+\tif (mm81x_hw_scan_h_is_chan_present(params, chan))\n+\t\treturn 0;\n+\n+\tparams-\u003echannels[params-\u003enum_chans].channel = chan;\n+\tparams-\u003enum_chans++;\n+\treturn 0;\n+}\n+\n+static int mm81x_hw_scan_h_init_chan_list(struct mm81x_hw_scan_params *params,\n+\t\t\t\t\t  struct ieee80211_channel **chans,\n+\t\t\t\t\t  u32 n_channels)\n+{\n+\tint i, j;\n+\tint num_pwrs_coarse = 0;\n+\tint last_pwr = INT_MIN;\n+\tint chans_to_allocate = 0;\n+\n+\tfor (i = 0; i \u003c n_channels; i++)\n+\t\tif (chans[i])\n+\t\t\tchans_to_allocate++;\n+\n+\tparams-\u003enum_chans = 0;\n+\tparams-\u003eallocated_chans = 0;\n+\tparams-\u003echannels = kcalloc(chans_to_allocate, sizeof(*params-\u003echannels),\n+\t\t\t\t   GFP_KERNEL);\n+\tif (!params-\u003echannels)\n+\t\treturn -ENOMEM;\n+\n+\tparams-\u003eallocated_chans = chans_to_allocate;\n+\n+\tfor (i = 0; i \u003c n_channels; i++)\n+\t\tif (chans[i])\n+\t\t\tmm81x_hw_scan_h_insert_chan(params, chans[i]);\n+\n+\t/*\n+\t * Calculate a rough estimate of number of different channel\n+\t * powers required\n+\t */\n+\tfor (i = 0; i \u003c params-\u003enum_chans; i++) {\n+\t\tif (chans[i]-\u003emax_reg_power != last_pwr) {\n+\t\t\tlast_pwr = chans[i]-\u003emax_reg_power;\n+\t\t\tnum_pwrs_coarse++;\n+\t\t}\n+\t}\n+\n+\tparams-\u003epowers_qdbm = kmalloc_array(\n+\t\tnum_pwrs_coarse, sizeof(*params-\u003epowers_qdbm), GFP_KERNEL);\n+\tif (!params-\u003epowers_qdbm)\n+\t\treturn -ENOMEM;\n+\n+\tparams-\u003en_powers = 0;\n+\n+\tfor (i = 0; i \u003c params-\u003enum_chans; i++) {\n+\t\ts32 power_qdbm =\n+\t\t\tMBM_TO_QDBM(DBM_TO_MBM(chans[i]-\u003emax_reg_power));\n+\n+\t\t/* Try and find the power in the list */\n+\t\tfor (j = 0; j \u003c params-\u003en_powers; j++)\n+\t\t\tif (params-\u003epowers_qdbm[j] == power_qdbm)\n+\t\t\t\tbreak;\n+\n+\t\t/* Reached the end of the list - add the new power option */\n+\t\tif (j == params-\u003en_powers) {\n+\t\t\tparams-\u003epowers_qdbm[j] = power_qdbm;\n+\t\t\tparams-\u003en_powers++;\n+\t\t\tif (params-\u003en_powers \u003e num_pwrs_coarse) {\n+\t\t\t\tWARN_ON(1);\n+\t\t\t\treturn -EFAULT;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* Give the index of the power level to the channel */\n+\t\tparams-\u003echannels[i].power_idx = j;\n+\t}\n+\treturn 0;\n+}\n+\n+static void mm81x_hw_scan_h_clean_params(struct mm81x_hw_scan_params *params)\n+{\n+\tif (params-\u003eprobe_req)\n+\t\tdev_kfree_skb_any(params-\u003eprobe_req);\n+\tkfree(params-\u003echannels);\n+\tkfree(params-\u003epowers_qdbm);\n+\n+\tparams-\u003enum_chans = 0;\n+\tparams-\u003eallocated_chans = 0;\n+}\n+\n+size_t mm81x_hw_scan_h_get_cmd_size(struct mm81x_hw_scan_params *params)\n+{\n+\tstruct hw_scan_tlv_channel_list *ch_list;\n+\tstruct hw_scan_tlv_power_list *pwr_list;\n+\tstruct hw_scan_tlv_probe_req *probe_req;\n+\tstruct hw_scan_tlv_dwell_on_home *dwell;\n+\tstruct host_cmd_req_hw_scan *req;\n+\tsize_t cmd_size = sizeof(*req);\n+\n+\t/* No TLVs if simple abort command */\n+\tif (params-\u003eoperation != MM81X_HW_SCAN_OP_START)\n+\t\treturn cmd_size;\n+\n+\tcmd_size += struct_size(ch_list, channels, params-\u003enum_chans);\n+\tcmd_size += struct_size(pwr_list, tx_power_qdbm, params-\u003en_powers);\n+\n+\tif (params-\u003eprobe_req)\n+\t\tcmd_size += struct_size(probe_req, buf, params-\u003eprobe_req-\u003elen);\n+\tif (params-\u003edwell_on_home_ms)\n+\t\tcmd_size += sizeof(*dwell);\n+\n+\treturn cmd_size;\n+}\n+\n+u8 *mm81x_hw_scan_h_insert_tlvs(struct mm81x_hw_scan_params *params, u8 *buf)\n+{\n+\tbuf = mm81x_hw_scan_h_add_channel_list_tlv(buf, params);\n+\tbuf = mm81x_hw_scan_h_add_power_list_tlv(buf, params);\n+\n+\tif (params-\u003edwell_on_home_ms)\n+\t\tbuf = mm81x_hw_scan_h_insert_dwell_time_tlv(buf, params);\n+\tif (params-\u003eprobe_req)\n+\t\tbuf = mm81x_hw_scan_h_add_probe_req_tlv(buf, params);\n+\n+\treturn buf;\n+}\n+\n+static u32 mm81x_hw_scan_h_get_dwell_on_home(struct mm81x *mors,\n+\t\t\t\t\t     struct ieee80211_vif *vif)\n+{\n+\tif (vif-\u003etype == NL80211_IFTYPE_STATION \u0026\u0026 vif-\u003ecfg.assoc)\n+\t\treturn mors-\u003ehw_scan.home_dwell_ms;\n+\treturn 0;\n+}\n+\n+static struct mm81x_hw_scan_params *\n+__mm81x_hw_scan_h_init_params(struct mm81x *mors)\n+{\n+\tstruct mm81x_hw_scan_params *params = mors-\u003ehw_scan.params;\n+\n+\tif (!params) {\n+\t\tparams = kzalloc_obj(*params, GFP_KERNEL);\n+\t\tif (params)\n+\t\t\tmors-\u003ehw_scan.params = params;\n+\t} else {\n+\t\tmm81x_hw_scan_h_clean_params(params);\n+\t\tmemset(params, 0, sizeof(*params));\n+\t}\n+\n+\treturn params;\n+}\n+\n+static int mm81x_hw_scan_h_init_params(struct mm81x *mors,\n+\t\t\t\t       struct ieee80211_hw *hw,\n+\t\t\t\t       struct ieee80211_vif *vif,\n+\t\t\t\t       struct cfg80211_scan_request *req)\n+{\n+\tstruct mm81x_hw_scan_params *params = mors-\u003ehw_scan.params;\n+\n+\tparams = __mm81x_hw_scan_h_init_params(mors);\n+\tif (!params) {\n+\t\tmors-\u003ehw_scan.state = HW_SCAN_STATE_IDLE;\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tparams-\u003ehw = hw;\n+\tparams-\u003evif = vif;\n+\tparams-\u003ehas_directed_ssid = (req-\u003essids \u0026\u0026 req-\u003essids[0].ssid_len \u003e 0);\n+\tparams-\u003eoperation = MM81X_HW_SCAN_OP_START;\n+\tparams-\u003edwell_on_home_ms = mm81x_hw_scan_h_get_dwell_on_home(mors, vif);\n+\n+\tif (req-\u003eduration)\n+\t\tparams-\u003edwell_time_ms = MM81X_TU_TO_MS(req-\u003eduration);\n+\telse if (req-\u003en_ssids == 0)\n+\t\tparams-\u003edwell_time_ms =\n+\t\t\tMM81X_HWSCAN_DEFAULT_PASSIVE_DWELL_TIME_MS;\n+\telse\n+\t\tparams-\u003edwell_time_ms = MM81X_HWSCAN_DEFAULT_DWELL_TIME_MS;\n+\n+\treturn 0;\n+}\n+\n+static u32 mm81x_hw_scan_h_calc_timeout(struct mm81x_hw_scan_params *params)\n+{\n+\tu32 ret = 0;\n+\n+\tret = params-\u003edwell_time_ms + params-\u003edwell_on_home_ms;\n+\tif (params-\u003eprobe_req)\n+\t\tret += MM81X_HWSCAN_PROBE_DELAY_MS;\n+\n+\tret *= params-\u003enum_chans;\n+\tret += MM81X_HWSCAN_TIMEOUT_OVERHEAD_MS;\n+\n+\treturn ret;\n+}\n+\n+static int mm81x_mac_ops_hw_scan(struct ieee80211_hw *hw,\n+\t\t\t\t struct ieee80211_vif *vif,\n+\t\t\t\t struct ieee80211_scan_request *hw_req)\n+{\n+\tint ret = 0;\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct cfg80211_scan_request *req = \u0026hw_req-\u003ereq;\n+\tstruct mm81x_hw_scan_params *params;\n+\tstruct ieee80211_channel **chans = hw_req-\u003ereq.channels;\n+\n+\tdev_dbg(mors-\u003edev, \"state %d\", mors-\u003ehw_scan.state);\n+\n+\tif (!mors-\u003estarted) {\n+\t\tdev_warn(mors-\u003edev, \"device not ready\");\n+\t\tret = -ENODEV;\n+\t\tgoto exit;\n+\t}\n+\n+\tswitch (mors-\u003ehw_scan.state) {\n+\tcase HW_SCAN_STATE_IDLE:\n+\t\tmors-\u003ehw_scan.state = HW_SCAN_STATE_RUNNING;\n+\t\treinit_completion(\u0026mors-\u003ehw_scan.scan_done);\n+\t\tbreak;\n+\tcase HW_SCAN_STATE_RUNNING:\n+\tcase HW_SCAN_STATE_ABORTING:\n+\t\tret = -EBUSY;\n+\t\tgoto exit;\n+\t}\n+\n+\tret = mm81x_hw_scan_h_init_params(mors, hw, vif, req);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\tparams = mors-\u003ehw_scan.params;\n+\n+\tret = mm81x_hw_scan_h_init_chan_list(params, chans,\n+\t\t\t\t\t     hw_req-\u003ereq.n_channels);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\t/* Only init the probe request template if this is an active scan */\n+\tif (req-\u003en_ssids \u003e 0) {\n+\t\tret = mm81x_hw_scan_h_init_probe_req(params, hw_req);\n+\t\tif (ret) {\n+\t\t\tdev_err(mors-\u003edev, \"Failed to init probe req %d\", ret);\n+\t\t\tgoto exit;\n+\t\t}\n+\t}\n+\n+\tret = mm81x_cmd_hw_scan(mors, params, false);\n+\tif (ret) {\n+\t\tmors-\u003ehw_scan.state = HW_SCAN_STATE_IDLE;\n+\t\tgoto exit;\n+\t}\n+\n+\tieee80211_queue_delayed_work(\n+\t\tmors-\u003ehw, \u0026mors-\u003ehw_scan.timeout,\n+\t\tmsecs_to_jiffies(mm81x_hw_scan_h_calc_timeout(params)));\n+exit:\n+\treturn ret;\n+}\n+\n+static void mm81x_hw_scan_abort(struct mm81x *mors)\n+{\n+\tint ret;\n+\tstruct mm81x_hw_scan_params params = { 0 };\n+\n+\tswitch (mors-\u003ehw_scan.state) {\n+\tcase HW_SCAN_STATE_IDLE:\n+\tcase HW_SCAN_STATE_ABORTING:\n+\t\t/* scan not running */\n+\t\treturn;\n+\tcase HW_SCAN_STATE_RUNNING:\n+\t\tmors-\u003ehw_scan.state = HW_SCAN_STATE_ABORTING;\n+\t\tbreak;\n+\t}\n+\n+\tparams.operation = MM81X_HW_SCAN_OP_STOP;\n+\n+\tret = mm81x_cmd_hw_scan(mors, \u0026params, false);\n+\n+\tif (ret || !mors-\u003estarted ||\n+\t    !wait_for_completion_timeout(\u0026mors-\u003ehw_scan.scan_done, 1 * HZ)) {\n+\t\t/*\n+\t\t * We may have lost the event on the bus, the chip could be\n+\t\t * wedged, or the cmd failed for another reason. Nevertheless,\n+\t\t * we should call the done event so mac80211 knows to unblock\n+\t\t * itself.\n+\t\t */\n+\t\tstruct cfg80211_scan_info info = { .aborted = true };\n+\n+\t\tieee80211_scan_completed(mors-\u003ehw, \u0026info);\n+\t\tmors-\u003ehw_scan.state = HW_SCAN_STATE_IDLE;\n+\t}\n+}\n+\n+static void mm81x_mac_ops_cancel_hw_scan(struct ieee80211_hw *hw,\n+\t\t\t\t\t struct ieee80211_vif *vif)\n+{\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\n+\tcancel_delayed_work_sync(\u0026mors-\u003ehw_scan.timeout);\n+\tmm81x_hw_scan_abort(mors);\n+}\n+\n+static void mm81x_mac_hw_scan_done_event(struct ieee80211_hw *hw)\n+{\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct cfg80211_scan_info info = { 0 };\n+\n+\tdev_dbg(mors-\u003edev, \"completing hw scan\");\n+\n+\tswitch (mors-\u003ehw_scan.state) {\n+\tcase HW_SCAN_STATE_IDLE:\n+\t\t/* Scan has already been stopped. Just continue */\n+\t\tgoto exit;\n+\tcase HW_SCAN_STATE_RUNNING:\n+\tcase HW_SCAN_STATE_ABORTING:\n+\t\tinfo.aborted = (mors-\u003ehw_scan.state == HW_SCAN_STATE_ABORTING);\n+\t\tmors-\u003ehw_scan.state = HW_SCAN_STATE_IDLE;\n+\t}\n+\n+\tieee80211_scan_completed(mors-\u003ehw, \u0026info);\n+exit:\n+\tcomplete(\u0026mors-\u003ehw_scan.scan_done);\n+\tcancel_delayed_work_sync(\u0026mors-\u003ehw_scan.timeout);\n+}\n+\n+static void mm81x_mac_hw_scan_timeout_work(struct work_struct *work)\n+{\n+\tstruct mm81x *mors =\n+\t\tcontainer_of(work, struct mm81x, hw_scan.timeout.work);\n+\n+\tdev_err(mors-\u003edev, \"hw scan timed out, aborting\");\n+\tmm81x_hw_scan_abort(mors);\n+}\n+\n+static void mm81x_mac_hw_scan_init(struct mm81x *mors)\n+{\n+\tmors-\u003ehw_scan.state = HW_SCAN_STATE_IDLE;\n+\tmors-\u003ehw_scan.params = NULL;\n+\tmors-\u003ehw_scan.home_dwell_ms = MM81X_HWSCAN_DEFAULT_DWELL_ON_HOME_MS;\n+\n+\tinit_completion(\u0026mors-\u003ehw_scan.scan_done);\n+\tINIT_DELAYED_WORK(\u0026mors-\u003ehw_scan.timeout,\n+\t\t\t  mm81x_mac_hw_scan_timeout_work);\n+}\n+\n+static void mm81x_mac_hw_scan_destroy(struct mm81x *mors)\n+{\n+\tcancel_delayed_work_sync(\u0026mors-\u003ehw_scan.timeout);\n+\tif (mors-\u003ehw_scan.params)\n+\t\tmm81x_hw_scan_h_clean_params(mors-\u003ehw_scan.params);\n+\tkfree(mors-\u003ehw_scan.params);\n+\tmors-\u003ehw_scan.params = NULL;\n+}\n+\n+static void mm81x_mac_hw_scan_finish(struct mm81x *mors)\n+{\n+\tstruct cfg80211_scan_info info = {\n+\t\t.aborted = true,\n+\t};\n+\n+\tif (mors-\u003ehw_scan.state == HW_SCAN_STATE_IDLE)\n+\t\treturn;\n+\n+\tieee80211_scan_completed(mors-\u003ehw, \u0026info);\n+\tcomplete(\u0026mors-\u003ehw_scan.scan_done);\n+\tmors-\u003ehw_scan.state = HW_SCAN_STATE_IDLE;\n+\tcancel_delayed_work_sync(\u0026mors-\u003ehw_scan.timeout);\n+}\n+\n+int mm81x_mac_event_recv(struct mm81x *mors, struct sk_buff *skb)\n+{\n+\tstruct host_cmd_event *event = (struct host_cmd_event *)(skb-\u003edata);\n+\tu16 event_id = le16_to_cpu(event-\u003ehdr.message_id);\n+\tu16 event_iid = le16_to_cpu(event-\u003ehdr.host_id);\n+\tu16 vif_id = le16_to_cpu(event-\u003ehdr.vif_id);\n+\tstruct ieee80211_vif *vif;\n+\n+\tif (!HOST_CMD_IS_EVT(event) || event_iid != 0)\n+\t\treturn -EINVAL;\n+\n+\tswitch (event_id) {\n+\tcase HOST_CMD_ID_EVT_HW_SCAN_DONE:\n+\t\tdev_dbg(mors-\u003edev,\n+\t\t\t\"Event: HOST_CMD_ID_EVT_HW_SCAN_DONE Received.\");\n+\t\tmm81x_mac_hw_scan_done_event(mors-\u003ehw);\n+\t\tbreak;\n+\tcase HOST_CMD_ID_EVT_BEACON_LOSS:\n+\t\tdev_dbg(mors-\u003edev,\n+\t\t\t\"Event: HOST_CMD_ID_EVT_BEACON_LOSS Received\");\n+\t\tscoped_guard(rcu) {\n+\t\t\tvif = mm81x_rcu_dereference_vif_id(mors, vif_id, true);\n+\t\t\tif (vif)\n+\t\t\t\tieee80211_beacon_loss(vif);\n+\t\t}\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void mm81x_tx_h_apply_mcs10(struct mm81x *mors,\n+\t\t\t\t   struct mm81x_skb_tx_info *tx_info)\n+{\n+\tu8 i;\n+\tu8 j;\n+\tint mcs0_first_idx = -1;\n+\tint mcs0_last_idx = -1;\n+\n+\t/* Find out where our first and last MCS0 entries are. */\n+\tfor (i = 0; i \u003c IEEE80211_TX_MAX_RATES; i++) {\n+\t\tenum dot11_bandwidth bw_idx = mm81x_ratecode_bw_index_get(\n+\t\t\ttx_info-\u003erates[i].mm81x_ratecode);\n+\n+\t\tif (bw_idx == DOT11_BANDWIDTH_1MHZ) {\n+\t\t\tmcs0_last_idx = i;\n+\t\t\tif (mcs0_first_idx == -1)\n+\t\t\t\tmcs0_first_idx = i;\n+\t\t}\n+\n+\t\t/*\n+\t\t * If the count is 0 then we are at the end of the table.\n+\t\t * Break to allow us to reuse i indicating the end of the\n+\t\t * table.\n+\t\t */\n+\t\tif (tx_info-\u003erates[i].count == 0)\n+\t\t\tbreak;\n+\t}\n+\n+\t/* If there aren't any MCS0 (at 1MHz) entries we are done. */\n+\tif (mcs0_first_idx \u003c 0)\n+\t\treturn;\n+\n+\t/*\n+\t * If we are in MCS10_MODE_AUTO add MCS10 counts to the table if they\n+\t * will fit. There should be three cases:\n+\t *\n+\t * - There is one MSC0 entry and the table is full -\u003e do nothing\n+\t * - There is one MSC0 entry and the table has space -\u003e adjust MSC0\n+\t *   down and add MCS 10\n+\t * - There are multiple MCS0 entries -\u003e replace entries after the first\n+\t *   with MCS 10\n+\t */\n+\t/* Case 3 - replace additional entries. */\n+\tif (mcs0_last_idx \u003e mcs0_first_idx) {\n+\t\tfor (j = mcs0_first_idx + 1; j \u003c i; j++) {\n+\t\t\tenum dot11_bandwidth bw_idx =\n+\t\t\t\tmm81x_ratecode_bw_index_get(\n+\t\t\t\t\ttx_info-\u003erates[j].mm81x_ratecode);\n+\t\t\tu8 mcs_index = mm81x_ratecode_mcs_index_get(\n+\t\t\t\ttx_info-\u003erates[j].mm81x_ratecode);\n+\t\t\tif (mcs_index == 0 \u0026\u0026 bw_idx == DOT11_BANDWIDTH_1MHZ) {\n+\t\t\t\tmm81x_ratecode_mcs_index_set(\n+\t\t\t\t\t\u0026tx_info-\u003erates[j].mm81x_ratecode, 10);\n+\t\t\t}\n+\t\t}\n+\t\t/* Case 2 - add additional MCS10 entry. */\n+\t} else if (mcs0_last_idx == mcs0_first_idx \u0026\u0026\n+\t\t   i \u003c (IEEE80211_TX_MAX_RATES)) {\n+\t\tint pre_mcs10_mcs0_count =\n+\t\t\tmin_t(u8, tx_info-\u003erates[mcs0_last_idx].count,\n+\t\t\t      MCS0_BEFORE_MCS10_COUNT);\n+\t\tint mcs10_count = tx_info-\u003erates[mcs0_last_idx].count -\n+\t\t\t\t  pre_mcs10_mcs0_count;\n+\n+\t\t/*\n+\t\t * If there were less retries than our desired minimum MCS0 we\n+\t\t * don't add MCS10 retries.\n+\t\t */\n+\t\tif (mcs10_count \u003e 0) {\n+\t\t\t/* Use the same flags for MCS10 as MCS0. */\n+\t\t\ttx_info-\u003erates[i].mm81x_ratecode =\n+\t\t\t\ttx_info-\u003erates[mcs0_last_idx].mm81x_ratecode;\n+\t\t\tmm81x_ratecode_mcs_index_set(\n+\t\t\t\t\u0026tx_info-\u003erates[i].mm81x_ratecode, 10);\n+\t\t\ttx_info-\u003erates[mcs0_last_idx].count =\n+\t\t\t\tpre_mcs10_mcs0_count;\n+\t\t\ttx_info-\u003erates[i].count = mcs10_count;\n+\t\t}\n+\t}\n+}\n+\n+void mm81x_tx_h_check_aggr(struct ieee80211_sta *pubsta, struct sk_buff *skb)\n+{\n+\tstruct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb-\u003edata;\n+\tstruct mm81x_sta *mors_sta = (struct mm81x_sta *)pubsta-\u003edrv_priv;\n+\tu8 tid = ieee80211_get_tid(hdr);\n+\n+\t/* we are already aggregating */\n+\tif (mors_sta-\u003etid_tx[tid] || mors_sta-\u003etid_start_tx[tid])\n+\t\treturn;\n+\n+\tif (mors_sta-\u003estate \u003c IEEE80211_STA_AUTHORIZED)\n+\t\treturn;\n+\n+\tif (skb_get_queue_mapping(skb) == IEEE80211_AC_VO)\n+\t\treturn;\n+\n+\tif (unlikely(!ieee80211_is_data_qos(hdr-\u003eframe_control)))\n+\t\treturn;\n+\n+\tif (unlikely(skb-\u003eprotocol == cpu_to_be16(ETH_P_PAE)))\n+\t\treturn;\n+\n+\tmors_sta-\u003etid_start_tx[tid] = true;\n+\tieee80211_start_tx_ba_session(pubsta, tid, 0);\n+}\n+\n+int mm81x_tx_h_get_attempts(struct mm81x *mors,\n+\t\t\t    struct mm81x_skb_tx_status *tx_sts)\n+{\n+\tint attempts = 0;\n+\tint i;\n+\tint count = min_t(int, MM81X_SKB_MAX_RATES, IEEE80211_TX_MAX_RATES);\n+\n+\tfor (i = 0; i \u003c count; i++) {\n+\t\tif (tx_sts-\u003erates[i].count \u003e 0)\n+\t\t\tattempts += tx_sts-\u003erates[i].count;\n+\t\telse\n+\t\t\tbreak;\n+\t}\n+\n+\treturn attempts;\n+}\n+\n+static void mm81x_tx_h_fill_info(struct mm81x *mors,\n+\t\t\t\t struct mm81x_skb_tx_info *tx_info,\n+\t\t\t\t struct sk_buff *skb, struct ieee80211_vif *vif,\n+\t\t\t\t int tx_bw_mhz, struct ieee80211_sta *sta)\n+{\n+\tint i;\n+\tstruct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);\n+\tstruct mm81x_vif *mors_vif = ieee80211_vif_to_mors_vif(vif);\n+\tstruct mm81x_sta *mors_sta = NULL;\n+\tstruct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb-\u003edata;\n+\tint op_bw_mhz = cfg80211_chandef_get_width(\u0026mors-\u003echandef);\n+\tu8 tid = skb-\u003epriority \u0026 IEEE80211_QOS_CTL_TAG1D_MASK;\n+\tbool rts_allowed = op_bw_mhz \u003c 8;\n+\n+\tif (sta)\n+\t\tmors_sta = (struct mm81x_sta *)sta-\u003edrv_priv;\n+\n+\trts_allowed \u0026= mm81x_tx_h_pkt_over_rts_threshold(mors, info, skb);\n+\n+\tmm81x_rc_sta_fill_tx_rates(mors, tx_info, skb, sta, tx_bw_mhz,\n+\t\t\t\t   rts_allowed);\n+\n+\tfor (i = 0; i \u003c IEEE80211_TX_MAX_RATES; i++) {\n+\t\tif (rts_allowed)\n+\t\t\tmm81x_ratecode_enable_rts(\n+\t\t\t\t\u0026tx_info-\u003erates[i].mm81x_ratecode);\n+\n+\t\tif (info-\u003econtrol.rates[i].flags \u0026 IEEE80211_TX_RC_SHORT_GI)\n+\t\t\tmm81x_ratecode_enable_sgi(\n+\t\t\t\t\u0026tx_info-\u003erates[i].mm81x_ratecode);\n+\t}\n+\n+\t/* Apply change of MCS0 to MCS10 if required. */\n+\tmm81x_tx_h_apply_mcs10(mors, tx_info);\n+\n+\ttx_info-\u003eflags |=\n+\t\tcpu_to_le32(MM81X_TX_CONF_FLAGS_VIF_ID_SET(mors_vif-\u003eid));\n+\n+\tif (info-\u003eflags \u0026 IEEE80211_TX_CTL_AMPDU)\n+\t\ttx_info-\u003eflags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_CTL_AMPDU);\n+\n+\tif (info-\u003eflags \u0026 IEEE80211_TX_CTL_SEND_AFTER_DTIM)\n+\t\ttx_info-\u003eflags |=\n+\t\t\tcpu_to_le32(MM81X_TX_CONF_FLAGS_SEND_AFTER_DTIM);\n+\n+\tif (info-\u003eflags \u0026 IEEE80211_TX_CTL_NO_PS_BUFFER) {\n+\t\ttx_info-\u003eflags |= cpu_to_le32(MM81X_TX_CONF_NO_PS_BUFFER);\n+\n+\t\tif (info-\u003eflags \u0026 IEEE80211_TX_STATUS_EOSP)\n+\t\t\ttx_info-\u003eflags |= cpu_to_le32(\n+\t\t\t\tMM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT);\n+\t} else if (ieee80211_is_mgmt(hdr-\u003eframe_control) \u0026\u0026\n+\t\t   !ieee80211_is_bufferable_mmpdu(skb)) {\n+\t\ttx_info-\u003eflags |= cpu_to_le32(MM81X_TX_CONF_NO_PS_BUFFER);\n+\t}\n+\n+\tif (info-\u003econtrol.hw_key) {\n+\t\ttx_info-\u003eflags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_HW_ENCRYPT);\n+\t\ttx_info-\u003eflags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_KEY_IDX_SET(\n+\t\t\tinfo-\u003econtrol.hw_key-\u003ehw_key_idx));\n+\t}\n+\n+\ttx_info-\u003etid = tid;\n+\tif (mors_sta) {\n+\t\ttx_info-\u003etid_params = mors_sta-\u003etid_params[tid];\n+\n+\t\tif (info-\u003eflags \u0026 IEEE80211_TX_CTL_CLEAR_PS_FILT) {\n+\t\t\tif (mors_sta-\u003etx_ps_filter_en)\n+\t\t\t\tdev_dbg(mors-\u003edev,\n+\t\t\t\t\t\"TX ps filter cleared sta[%pM]\",\n+\t\t\t\t\tmors_sta-\u003eaddr);\n+\t\t\tmors_sta-\u003etx_ps_filter_en = false;\n+\t\t}\n+\t}\n+}\n+\n+static void mm81x_mac_ops_tx(struct ieee80211_hw *hw,\n+\t\t\t     struct ieee80211_tx_control *control,\n+\t\t\t     struct sk_buff *skb)\n+{\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct mm81x_skbq *mq = NULL;\n+\tstruct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);\n+\tstruct ieee80211_vif *vif = info-\u003econtrol.vif;\n+\tstruct mm81x_skb_tx_info tx_info = { 0 };\n+\tstruct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb-\u003edata;\n+\tbool is_mgmt = ieee80211_is_mgmt(hdr-\u003eframe_control);\n+\tint tx_bw_mhz = cfg80211_chandef_get_width(\u0026mors-\u003echandef);\n+\tstruct ieee80211_sta *sta = control-\u003esta;\n+\tint max_tx_bw = 0, sta_max_bw_mhz = 0;\n+\n+\tif (sta) {\n+\t\tstruct mm81x_sta *mors_sta = (struct mm81x_sta *)sta-\u003edrv_priv;\n+\n+\t\tsta_max_bw_mhz = mors_sta-\u003emax_bw_mhz;\n+\t}\n+\n+\tmax_tx_bw = mm81x_tx_h_get_max_bw(mors);\n+\ttx_bw_mhz = min(max_tx_bw, tx_bw_mhz);\n+\n+\tif (is_mgmt)\n+\t\ttx_bw_mhz = cfg80211_chandef_s1g_pri_width(\u0026mors-\u003echandef);\n+\tif (sta_max_bw_mhz)\n+\t\ttx_bw_mhz = min(tx_bw_mhz, sta_max_bw_mhz);\n+\tif (ieee80211_is_probe_resp(hdr-\u003eframe_control))\n+\t\ttx_bw_mhz = 1;\n+\n+\tmm81x_tx_h_fill_info(mors, \u0026tx_info, skb, vif, tx_bw_mhz, sta);\n+\n+\tif (mm81x_tx_h_ps_filtered_for_sta(mors, skb, sta))\n+\t\treturn;\n+\n+\tif (is_mgmt)\n+\t\tmq = mm81x_hif_get_tx_mgmt_queue(mors);\n+\telse\n+\t\tmq = mm81x_hif_get_tx_data_queue(mors,\n+\t\t\t\t\t\t dot11_tid_to_ac(tx_info.tid));\n+\n+\tmm81x_skbq_skb_tx(mq, \u0026skb, \u0026tx_info,\n+\t\t\t  (is_mgmt) ? MM81X_SKB_CHAN_MGMT :\n+\t\t\t\t      MM81X_SKB_CHAN_DATA);\n+}\n+\n+static void mm81x_mac_ops_stop(struct ieee80211_hw *hw, bool suspend)\n+{\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\n+\tmors-\u003estarted = false;\n+}\n+\n+static void mm81x_mac_beacon_finish(struct mm81x_vif *mors_vif)\n+{\n+\tstruct mm81x *mors = mm81x_vif_to_mors(mors_vif);\n+\n+\tmm81x_mac_beacon_irq_enable(mors_vif, false);\n+\tcancel_work_sync(\u0026mors_vif-\u003eu.ap.beacon_work);\n+\t/*\n+\t * Side effect of the restarting required when\n+\t * reacting to regdom changes...\n+\t */\n+\tatomic_add_unless(\u0026mors-\u003enum_bcn_vifs, -1, 0);\n+}\n+\n+static void mm81x_mac_ops_remove_interface(struct ieee80211_hw *hw,\n+\t\t\t\t\t   struct ieee80211_vif *vif)\n+{\n+\tint ret;\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct mm81x_vif *mors_vif = (struct mm81x_vif *)vif-\u003edrv_priv;\n+\n+\tret = mm81x_cmd_rm_if(mors, mors_vif-\u003eid);\n+\tif (ret)\n+\t\tdev_err(mors-\u003edev, \"mm81x_cmd_rm_if failed %d\", ret);\n+\n+\tRCU_INIT_POINTER(mors-\u003evifs[mors_vif-\u003eid], NULL);\n+}\n+\n+static s32 mm81x_mac_get_max_txpower(struct mm81x *mors)\n+{\n+\tint ret;\n+\ts32 power_mbm;\n+\n+\t/* Retrieve maximum TX power the chip can transmit */\n+\tret = mm81x_cmd_get_max_txpower(mors, \u0026power_mbm);\n+\tif (ret) {\n+\t\tdev_err(mors-\u003edev, \"using default tx max power %d mBm\",\n+\t\t\tMAX_TX_POWER_MBM);\n+\t\treturn MAX_TX_POWER_MBM;\n+\t}\n+\n+\tdev_dbg(mors-\u003edev, \"Max tx power detected %d mBm\", power_mbm);\n+\treturn power_mbm;\n+}\n+\n+static s32 mm81x_mac_set_txpower(struct mm81x *mors, s32 power_mbm)\n+{\n+\tint ret;\n+\ts32 out_power_mbm;\n+\n+\tif (mors-\u003etx_max_power_mbm == INT_MAX)\n+\t\tmors-\u003etx_max_power_mbm = mm81x_mac_get_max_txpower(mors);\n+\n+\tpower_mbm = min(power_mbm, mors-\u003etx_max_power_mbm);\n+\tif (power_mbm == mors-\u003etx_power_mbm)\n+\t\treturn mors-\u003etx_power_mbm;\n+\n+\tret = mm81x_cmd_set_txpower(mors, \u0026out_power_mbm, power_mbm);\n+\tif (ret) {\n+\t\tdev_err(mors-\u003edev, \"failed, power %d mBm ret %d\", power_mbm,\n+\t\t\tret);\n+\t\treturn mors-\u003etx_power_mbm;\n+\t}\n+\n+\tif (out_power_mbm != mors-\u003etx_power_mbm) {\n+\t\tdev_dbg(mors-\u003edev, \"%d -\u003e %d mBm\", mors-\u003etx_power_mbm,\n+\t\t\tout_power_mbm);\n+\t\tmors-\u003etx_power_mbm = out_power_mbm;\n+\t}\n+\n+\treturn mors-\u003etx_power_mbm;\n+}\n+\n+static int mm81x_mac_set_channel(struct mm81x *mors, u32 op_chan_freq_hz,\n+\t\t\t\t u8 pri_1mhz_chan_idx, u8 op_bw_mhz,\n+\t\t\t\t u8 pri_bw_mhz)\n+{\n+\tint ret;\n+\n+\tret = mm81x_cmd_set_channel(mors, op_chan_freq_hz, pri_1mhz_chan_idx,\n+\t\t\t\t    op_bw_mhz, pri_bw_mhz, \u0026mors-\u003etx_power_mbm);\n+\tif (ret) {\n+\t\tdev_err(mors-\u003edev, \"mm81x_cmd_set_channel() failed, ret %d\",\n+\t\t\tret);\n+\t\treturn ret;\n+\t}\n+\n+\tmm81x_mac_set_txpower(mors, mors-\u003etx_power_mbm);\n+\treturn 0;\n+}\n+\n+static u8 mm81x_mac_pri_chan_to_index(const struct cfg80211_chan_def *chandef)\n+{\n+\tu32 bw_mhz = cfg80211_chandef_get_width(chandef);\n+\tu32 op_center_khz = ieee80211_chandef_to_khz(chandef);\n+\tu32 first_1mhz_center_khz = op_center_khz - (bw_mhz * 500) + 500;\n+\tu32 pri_1mhz_khz = ieee80211_channel_to_khz(chandef-\u003echan);\n+\n+\treturn (pri_1mhz_khz - first_1mhz_center_khz) / 1000;\n+}\n+\n+static int mm81x_mac_ops_change_channel(struct ieee80211_hw *hw,\n+\t\t\t\t\tstruct cfg80211_chan_def *chandef)\n+{\n+\tint ret;\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tu64 freq_hz = KHZ_TO_HZ(ieee80211_chandef_to_khz(chandef));\n+\tu8 op_bw_mhz = cfg80211_chandef_get_width(chandef);\n+\tu8 pri_1mhz_idx = mm81x_mac_pri_chan_to_index(chandef);\n+\tint pri_chan_width_mhz = cfg80211_chandef_s1g_pri_width(chandef);\n+\n+\tdev_dbg(mors-\u003edev, \"ch: freq=%llu Hz bw=%u pri_idx=%d pri_bw=%d\",\n+\t\tfreq_hz, op_bw_mhz, pri_1mhz_idx, pri_chan_width_mhz);\n+\n+\tret = mm81x_mac_set_channel(mors, freq_hz, (u8)pri_1mhz_idx, op_bw_mhz,\n+\t\t\t\t    pri_chan_width_mhz);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tmemcpy(\u0026mors-\u003echandef, chandef, sizeof(mors-\u003echandef));\n+\treturn 0;\n+}\n+\n+static int mm81x_mac_ops_config(struct ieee80211_hw *hw, int radio_idx,\n+\t\t\t\tu32 changed)\n+{\n+\tint ret;\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct ieee80211_conf *conf = \u0026hw-\u003econf;\n+\tstruct ieee80211_channel *channel = conf-\u003echandef.chan;\n+\n+\tif (!mors-\u003estarted)\n+\t\treturn 0;\n+\n+\tif (changed \u0026 IEEE80211_CONF_CHANGE_CHANNEL) {\n+\t\tret = mm81x_mac_ops_change_channel(hw, \u0026conf-\u003echandef);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t}\n+\n+\tif ((changed \u0026 IEEE80211_CONF_CHANGE_POWER) \u0026\u0026\n+\t    !(changed \u0026 IEEE80211_CONF_CHANGE_CHANNEL) \u0026\u0026\n+\t    !(conf-\u003eflags \u0026 IEEE80211_CONF_MONITOR)) {\n+\t\ts32 power_mbm = DBM_TO_MBM(conf-\u003epower_level);\n+\n+\t\tpower_mbm = min(DBM_TO_MBM(channel-\u003emax_reg_power), power_mbm);\n+\t\tpower_mbm = mm81x_mac_set_txpower(mors, power_mbm);\n+\t\tconf-\u003epower_level = MBM_TO_DBM(power_mbm);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int mm81x_mac_ops_get_txpower(struct ieee80211_hw *hw,\n+\t\t\t\t     struct ieee80211_vif *vif,\n+\t\t\t\t     unsigned int link_id, int *dbm)\n+{\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct ieee80211_chanctx_conf *chanctx_conf;\n+\tstruct cfg80211_chan_def *chandef = \u0026vif-\u003ebss_conf.chanreq.oper;\n+\n+\tscoped_guard(rcu) {\n+\t\tchanctx_conf = rcu_access_pointer(vif-\u003ebss_conf.chanctx_conf);\n+\t\tif (!chanctx_conf ||\n+\t\t    !cfg80211_chandef_identical(chandef, \u0026chanctx_conf-\u003edef))\n+\t\t\treturn -ENODATA;\n+\t}\n+\n+\t*dbm = MBM_TO_DBM(mors-\u003etx_power_mbm);\n+\treturn 0;\n+}\n+\n+static void mm81x_mac_config_ps(struct mm81x *mors, struct ieee80211_vif *vif)\n+{\n+\tbool en_ps = vif-\u003ecfg.ps;\n+\n+\tif (vif-\u003etype == NL80211_IFTYPE_AP || !mors-\u003eps.enable)\n+\t\treturn;\n+\n+\tif (mors-\u003econfig_ps == en_ps)\n+\t\treturn;\n+\n+\tdev_dbg(mors-\u003edev, \"change powersave mode: %d (current %d)\", en_ps,\n+\t\tmors-\u003econfig_ps);\n+\n+\tmors-\u003econfig_ps = en_ps;\n+\n+\tif (en_ps) {\n+\t\tmm81x_cmd_set_ps(mors, true);\n+\t\tmm81x_ps_enable(mors);\n+\t} else {\n+\t\tmm81x_ps_disable(mors);\n+\t\tmm81x_cmd_set_ps(mors, false);\n+\t}\n+}\n+\n+static void mm81x_mac_ops_bss_info_changed(struct ieee80211_hw *hw,\n+\t\t\t\t\t   struct ieee80211_vif *vif,\n+\t\t\t\t\t   struct ieee80211_bss_conf *info,\n+\t\t\t\t\t   u64 changed)\n+{\n+\tint ret;\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct mm81x_vif *mors_vif = (struct mm81x_vif *)vif-\u003edrv_priv;\n+\n+\tif (changed \u0026 BSS_CHANGED_PS)\n+\t\tmm81x_mac_config_ps(mors, vif);\n+\n+\tif (changed \u0026 BSS_CHANGED_BEACON_ENABLED) {\n+\t\tmm81x_cmd_config_beacon_timer(mors, mors_vif,\n+\t\t\t\t\t      info-\u003eenable_beacon);\n+\n+\t\tif (!info-\u003eenable_beacon)\n+\t\t\tmm81x_mac_beacon_finish(mors_vif);\n+\t}\n+\n+\tif (changed \u0026 BSS_CHANGED_BEACON_INT || changed \u0026 BSS_CHANGED_SSID) {\n+\t\tret = mm81x_cmd_cfg_bss(mors, mors_vif-\u003eid, info-\u003ebeacon_int,\n+\t\t\t\t\tinfo-\u003edtim_period,\n+\t\t\t\t\tmm81x_vif_generate_cssid(vif));\n+\t\tif (ret)\n+\t\t\tdev_err(mors-\u003edev, \"mm81x_cmd_cfg_bss failed %d\", ret);\n+\t}\n+}\n+\n+static u64 mm81x_mac_ops_prepare_multicast(struct ieee80211_hw *hw,\n+\t\t\t\t\t   struct netdev_hw_addr_list *mc_list)\n+{\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct mcast_filter *filter;\n+\tstruct netdev_hw_addr *addr;\n+\tu16 addr_count = netdev_hw_addr_list_count(mc_list);\n+\tu16 len = sizeof(*filter) + addr_count * sizeof(filter-\u003eaddr_list[0]);\n+\n+\tfilter = kzalloc(len, GFP_ATOMIC);\n+\tif (!filter)\n+\t\treturn 0;\n+\n+\tif (addr_count \u003e MCAST_FILTER_COUNT_MAX) {\n+\t\tdev_warn(\n+\t\t\tmors-\u003edev,\n+\t\t\t\"Multicast filtering disabled - too many groups (%d) \u003e %u\",\n+\t\t\taddr_count, (u16)MCAST_FILTER_COUNT_MAX);\n+\t\tfilter-\u003ecount = 0;\n+\t} else {\n+\t\tnetdev_hw_addr_list_for_each(addr, mc_list) {\n+\t\t\tdev_dbg(mors-\u003edev, \"mcast whitelist (%d): %pM\",\n+\t\t\t\tfilter-\u003ecount, addr-\u003eaddr);\n+\t\t\tfilter-\u003eaddr_list[filter-\u003ecount++] =\n+\t\t\t\tmac2le32(addr-\u003eaddr);\n+\t\t}\n+\t}\n+\n+\treturn (u64)(unsigned long)filter;\n+}\n+\n+static void mm81x_mac_ops_configure_filter(struct ieee80211_hw *hw,\n+\t\t\t\t\t   unsigned int changed_flags,\n+\t\t\t\t\t   unsigned int *total_flags,\n+\t\t\t\t\t   u64 multicast)\n+{\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct mcast_filter *cmd = (void *)(unsigned long)multicast;\n+\tstruct mm81x_vif *mors_vif = NULL;\n+\tstruct ieee80211_vif *vif = NULL;\n+\tint vif_id = 0;\n+\tint ret = 0;\n+\n+\tif (!cmd)\n+\t\tgoto out;\n+\n+\tkfree(mors-\u003emcast_filter);\n+\tmors-\u003emcast_filter = cmd;\n+\n+\tfor (vif_id = 0; vif_id \u003c ARRAY_SIZE(mors-\u003evifs); vif_id++) {\n+\t\tvif = mm81x_rcu_dereference_vif_id(mors, vif_id, false);\n+\t\tif (!vif)\n+\t\t\tcontinue;\n+\n+\t\tmors_vif = ieee80211_vif_to_mors_vif(vif);\n+\n+\t\tret = mm81x_cmd_cfg_multicast_filter(mors, mors_vif);\n+\t\tif (!ret)\n+\t\t\tcontinue;\n+\n+\t\tdev_err(mors-\u003edev, \"Multicast filtering failed - rc=%d\", ret);\n+\t\tmors-\u003emcast_filter = NULL;\n+\t\tkfree(cmd);\n+\t\tbreak;\n+\t}\n+\n+out:\n+\t*total_flags \u0026= 0;\n+}\n+\n+static int mm81x_mac_ops_conf_tx(struct ieee80211_hw *hw,\n+\t\t\t\t struct ieee80211_vif *vif,\n+\t\t\t\t unsigned int link_id, u16 ac,\n+\t\t\t\t const struct ieee80211_tx_queue_params *params)\n+{\n+\tint ret;\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct mm81x_queue_params mqp;\n+\n+\tmqp.aci = map_mac80211q_2_mm81x_aci(ac);\n+\tmqp.aifs = params-\u003eaifs;\n+\tmqp.cw_max = params-\u003ecw_max;\n+\tmqp.cw_min = params-\u003ecw_min;\n+\tmqp.uapsd = params-\u003euapsd;\n+\tmqp.txop = params-\u003etxop \u003c\u003c 5;\n+\n+\tdev_dbg(mors-\u003edev, \"queue:%d txop:%d cw_min:%d cw_max:%d aifs:%d\",\n+\t\tmqp.aci, mqp.txop, mqp.cw_min, mqp.cw_max, mqp.aifs);\n+\n+\tret = mm81x_cmd_cfg_qos(mors, \u0026mqp);\n+\tif (ret)\n+\t\tdev_dbg(mors-\u003edev, \"mm81x_cmd_cfg_qos failed %d\", ret);\n+\treturn ret;\n+}\n+\n+static int mm81x_mac_ops_sta_state(struct ieee80211_hw *hw,\n+\t\t\t\t   struct ieee80211_vif *vif,\n+\t\t\t\t   struct ieee80211_sta *sta,\n+\t\t\t\t   enum ieee80211_sta_state old_state,\n+\t\t\t\t   enum ieee80211_sta_state new_state)\n+{\n+\tu16 aid;\n+\tint ret;\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct mm81x_vif *mors_vif = (struct mm81x_vif *)vif-\u003edrv_priv;\n+\tstruct mm81x_sta *mors_sta = (struct mm81x_sta *)sta-\u003edrv_priv;\n+\n+\t/* Ignore both NOTEXIST to NONE and NONE to NOTEXIST */\n+\tif ((old_state == IEEE80211_STA_NOTEXIST \u0026\u0026\n+\t     new_state == IEEE80211_STA_NONE) ||\n+\t    (old_state == IEEE80211_STA_NONE \u0026\u0026\n+\t     new_state == IEEE80211_STA_NOTEXIST))\n+\t\treturn 0;\n+\n+\tif (vif-\u003etype == NL80211_IFTYPE_STATION)\n+\t\taid = vif-\u003ecfg.aid;\n+\telse\n+\t\taid = sta-\u003eaid;\n+\n+\tret = mm81x_cmd_sta_state(mors, mors_vif, aid, sta, new_state);\n+\tif (ret \u003c 0)\n+\t\tgoto exit;\n+\n+\tether_addr_copy(mors_sta-\u003eaddr, sta-\u003eaddr);\n+\tmors_sta-\u003estate = new_state;\n+\n+\tif (new_state \u003e old_state \u0026\u0026 new_state == IEEE80211_STA_ASSOC) {\n+\t\tif (vif-\u003etype == NL80211_IFTYPE_AP)\n+\t\t\tmors_vif-\u003eu.ap.num_stas++;\n+\t\telse if (vif-\u003etype == NL80211_IFTYPE_STATION)\n+\t\t\tmors_vif-\u003eu.sta.is_assoc = true;\n+\t}\n+\n+\tif (new_state \u003c old_state \u0026\u0026 new_state == IEEE80211_STA_NONE) {\n+\t\tif (vif-\u003etype == NL80211_IFTYPE_AP)\n+\t\t\tmors_vif-\u003eu.ap.num_stas--;\n+\t\telse if (vif-\u003etype == NL80211_IFTYPE_STATION)\n+\t\t\tmors_vif-\u003eu.sta.is_assoc = false;\n+\t}\n+\n+exit:\n+\t/*\n+\t * Always update our mmrc sta state even on failure to ensure\n+\t * we don't hold a dangling sta on error\n+\t */\n+\tmm81x_rc_sta_state_check(mors, vif, sta, old_state, new_state);\n+\treturn new_state \u003c old_state ? 0 : ret;\n+}\n+\n+static int mm81x_mac_ops_ampdu_action(struct ieee80211_hw *hw,\n+\t\t\t\t      struct ieee80211_vif *vif,\n+\t\t\t\t      struct ieee80211_ampdu_params *params)\n+{\n+\tu16 tid = params-\u003etid;\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct ieee80211_sta *sta = params-\u003esta;\n+\tstruct mm81x_sta *mors_sta = (struct mm81x_sta *)sta-\u003edrv_priv;\n+\tu16 buf_size =\n+\t\tmin_t(u16, params-\u003ebuf_size, DOT11AH_BA_MAX_MPDU_PER_AMPDU);\n+\n+\tswitch (params-\u003eaction) {\n+\tcase IEEE80211_AMPDU_TX_START:\n+\t\tdev_dbg(mors-\u003edev, \"%pM.%d A-MPDU TX start\", mors_sta-\u003eaddr,\n+\t\t\ttid);\n+\t\tieee80211_start_tx_ba_cb_irqsafe(vif, sta-\u003eaddr, tid);\n+\t\tbreak;\n+\tcase IEEE80211_AMPDU_TX_STOP_CONT:\n+\tcase IEEE80211_AMPDU_TX_STOP_FLUSH:\n+\tcase IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:\n+\t\tdev_dbg(mors-\u003edev, \"%pM.%d A-MPDU TX flush\", mors_sta-\u003eaddr,\n+\t\t\ttid);\n+\t\tmors_sta-\u003etid_start_tx[tid] = false;\n+\t\tmors_sta-\u003etid_tx[tid] = false;\n+\t\tmors_sta-\u003etid_params[tid] = 0;\n+\t\tieee80211_stop_tx_ba_cb_irqsafe(vif, sta-\u003eaddr, tid);\n+\t\tbreak;\n+\tcase IEEE80211_AMPDU_TX_OPERATIONAL:\n+\t\tdev_dbg(mors-\u003edev, \"%pM.%d A-MPDU TX oper\", mors_sta-\u003eaddr,\n+\t\t\ttid);\n+\t\tmors_sta-\u003etid_tx[tid] = true;\n+\t\tif (!buf_size) {\n+\t\t\tdev_err(mors-\u003edev, \"%pM.%d A-MPDU Invalid buf size\",\n+\t\t\t\tmors_sta-\u003eaddr, tid);\n+\t\t\tbreak;\n+\t\t}\n+\t\tmors_sta-\u003etid_params[tid] =\n+\t\t\tu8_encode_bits(buf_size - 1,\n+\t\t\t\t       TX_INFO_TID_PARAMS_MAX_REORDER_BUF) |\n+\t\t\tu8_encode_bits(1, TX_INFO_TID_PARAMS_AMPDU_ENABLED) |\n+\t\t\tu8_encode_bits(params-\u003eamsdu,\n+\t\t\t\t       TX_INFO_TID_PARAMS_AMSDU_SUPPORTED);\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int mm81x_mac_ops_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,\n+\t\t\t\t struct ieee80211_vif *vif,\n+\t\t\t\t struct ieee80211_sta *sta,\n+\t\t\t\t struct ieee80211_key_conf *key)\n+{\n+\tu16 aid;\n+\tint ret = -EOPNOTSUPP;\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct mm81x_vif *mors_vif = (struct mm81x_vif *)vif-\u003edrv_priv;\n+\tenum host_cmd_key_cipher cipher;\n+\tenum host_cmd_aes_key_len length;\n+\n+\tif (vif-\u003etype == NL80211_IFTYPE_STATION) {\n+\t\taid = vif-\u003ecfg.aid;\n+\t} else if (sta) {\n+\t\taid = sta-\u003eaid;\n+\t} else {\n+\t\t/* Is a group key - AID is unused */\n+\t\tWARN_ON(key-\u003eflags \u0026 IEEE80211_KEY_FLAG_PAIRWISE);\n+\t\taid = 0;\n+\t}\n+\n+\tswitch (cmd) {\n+\tcase SET_KEY: {\n+\t\tswitch (key-\u003ecipher) {\n+\t\tcase WLAN_CIPHER_SUITE_CCMP:\n+\t\tcase WLAN_CIPHER_SUITE_CCMP_256:\n+\t\t\tcipher = HOST_CMD_KEY_CIPHER_AES_CCM;\n+\t\t\tbreak;\n+\t\tcase WLAN_CIPHER_SUITE_GCMP:\n+\t\tcase WLAN_CIPHER_SUITE_GCMP_256:\n+\t\t\tcipher = HOST_CMD_KEY_CIPHER_AES_GCM;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\t/* Cipher suite currently not supported */\n+\t\t\tret = -EOPNOTSUPP;\n+\t\t\tgoto exit;\n+\t\t}\n+\n+\t\tswitch (key-\u003ekeylen) {\n+\t\tcase 16:\n+\t\t\tlength = HOST_CMD_AES_KEY_LEN_LENGTH_128;\n+\t\t\tbreak;\n+\t\tcase 32:\n+\t\t\tlength = HOST_CMD_AES_KEY_LEN_LENGTH_256;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\t/* Key length not supported */\n+\t\t\tret = -EOPNOTSUPP;\n+\t\t\tgoto exit;\n+\t\t}\n+\n+\t\tret = mm81x_cmd_install_key(mors, mors_vif, aid, key, cipher,\n+\t\t\t\t\t    length);\n+\t\tbreak;\n+\t}\n+\tcase DISABLE_KEY:\n+\t\tret = mm81x_cmd_disable_key(mors, mors_vif, aid, key);\n+\t\tif (ret) {\n+\t\t\t/* Must return 0 */\n+\t\t\tdev_warn(mors-\u003edev, \"Failed to remove key\");\n+\t\t\tret = 0;\n+\t\t}\n+\t\tbreak;\n+\tdefault:\n+\t\tWARN_ON(1);\n+\t}\n+\n+\tif (ret) {\n+\t\tdev_dbg(mors-\u003edev, \"Falling back to software crypto\");\n+\t\tret = 1;\n+\t}\n+\n+exit:\n+\treturn ret;\n+}\n+\n+static int mm81x_mac_set_frag_threshold(struct ieee80211_hw *hw, int radio_idx,\n+\t\t\t\t\tu32 value)\n+{\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\n+\treturn mm81x_cmd_set_frag_threshold(mors, value);\n+}\n+\n+static u8 mm81x_rx_h_rc_bw_to_rx_bw(__le32 ratecode)\n+{\n+\tenum dot11_bandwidth bw = mm81x_ratecode_bw_index_get(ratecode);\n+\n+\tswitch (bw) {\n+\tcase DOT11_BANDWIDTH_1MHZ:\n+\t\treturn RATE_INFO_BW_1;\n+\tcase DOT11_BANDWIDTH_2MHZ:\n+\t\treturn RATE_INFO_BW_2;\n+\tcase DOT11_BANDWIDTH_4MHZ:\n+\t\treturn RATE_INFO_BW_4;\n+\tcase DOT11_BANDWIDTH_8MHZ:\n+\t\treturn RATE_INFO_BW_8;\n+\tdefault:\n+\t\treturn RATE_INFO_BW_1;\n+\t}\n+}\n+\n+static void mm81x_rx_h_fill_status(struct mm81x *mors,\n+\t\t\t\t   struct mm81x_skb_rx_status *hdr_rx_status,\n+\t\t\t\t   struct ieee80211_rx_status *rx_status,\n+\t\t\t\t   struct sk_buff *skb)\n+{\n+\tu32 flags = le32_to_cpu(hdr_rx_status-\u003eflags);\n+\tu16 freq_100khz = le16_to_cpu(hdr_rx_status-\u003efreq_100khz);\n+\t__le32 ratecode = hdr_rx_status-\u003emm81x_ratecode;\n+\n+\trx_status-\u003esignal = le16_to_cpu(hdr_rx_status-\u003erssi);\n+\trx_status-\u003eencoding = RX_ENC_S1G;\n+\trx_status-\u003eband = NL80211_BAND_S1GHZ;\n+\trx_status-\u003efreq = KHZ100_TO_MHZ(freq_100khz);\n+\trx_status-\u003efreq_offset = (freq_100khz % 10) ? 1 : 0;\n+\trx_status-\u003enss = NSS_IDX_TO_NSS(mm81x_ratecode_nss_index_get(ratecode));\n+\n+\tif (flags \u0026 MM81X_RX_STATUS_FLAGS_DECRYPTED)\n+\t\trx_status-\u003eflag |= RX_FLAG_DECRYPTED;\n+\n+\trx_status-\u003erate_idx = mm81x_ratecode_mcs_index_get(ratecode);\n+\trx_status-\u003ebw = mm81x_rx_h_rc_bw_to_rx_bw(ratecode);\n+\n+\tif (mm81x_ratecode_sgi_get(ratecode))\n+\t\trx_status-\u003eenc_flags |= RX_ENC_FLAG_SHORT_GI;\n+}\n+\n+static void mm81x_rx_h_update_sta(struct ieee80211_vif *vif,\n+\t\t\t\t  struct ieee80211_hdr *hdr,\n+\t\t\t\t  struct ieee80211_rx_status *rx_status)\n+{\n+\tstruct ieee80211_sta *sta;\n+\tstruct mm81x_sta *msta;\n+\tu8 *lookup = ieee80211_is_s1g_beacon(hdr-\u003eframe_control) ? hdr-\u003eaddr1 :\n+\t\t\t\t\t\t\t\t   hdr-\u003eaddr2;\n+\n+\tlockdep_assert_in_rcu_read_lock();\n+\n+\tsta = ieee80211_find_sta(vif, lookup);\n+\tif (!sta)\n+\t\treturn;\n+\n+\tmsta = (void *)sta-\u003edrv_priv;\n+\tif (msta-\u003eavg_rssi) {\n+\t\tmsta-\u003eavg_rssi =\n+\t\t\tCALC_AVG_RSSI(msta-\u003eavg_rssi, rx_status-\u003esignal);\n+\t} else {\n+\t\tmsta-\u003eavg_rssi = rx_status-\u003esignal;\n+\t}\n+}\n+\n+static struct ieee80211_vif *\n+mm81x_rx_h_skb_get_vif(struct mm81x *mors, struct sk_buff *skb,\n+\t\t       struct mm81x_skb_rx_status *hdr_rx_status)\n+{\n+\tu8 vif_id = u32_get_bits(le32_to_cpu(hdr_rx_status-\u003eflags),\n+\t\t\t\t MM81X_RX_STATUS_FLAGS_VIF_ID);\n+\n+\tlockdep_assert_in_rcu_read_lock();\n+\n+\tif (vif_id == INVALID_VIF_INDEX)\n+\t\treturn NULL;\n+\n+\treturn mm81x_rcu_dereference_vif_id(mors, vif_id, true);\n+}\n+\n+void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb,\n+\t\t      struct mm81x_skb_rx_status *hdr_rx_status)\n+{\n+\tstruct ieee80211_vif *vif;\n+\tstruct ieee80211_hw *hw = mors-\u003ehw;\n+\tstruct ieee80211_rx_status rx_status;\n+\tstruct ieee80211_hdr *hdr = (void *)skb-\u003edata;\n+\n+\tmemset(\u0026rx_status, 0, sizeof(rx_status));\n+\n+\tif (!mors-\u003estarted || !skb-\u003edata || !skb-\u003elen) {\n+\t\tdev_kfree_skb_any(skb);\n+\t\treturn;\n+\t}\n+\n+\tmm81x_rx_h_fill_status(mors, hdr_rx_status, \u0026rx_status, skb);\n+\n+\tscoped_guard(rcu) {\n+\t\tvif = mm81x_rx_h_skb_get_vif(mors, skb, hdr_rx_status);\n+\t\tif (!vif)\n+\t\t\tgoto rx;\n+\n+\t\tmm81x_rx_h_update_sta(vif, hdr, \u0026rx_status);\n+\t}\n+\n+rx:\n+\tmemcpy(IEEE80211_SKB_RXCB(skb), \u0026rx_status, sizeof(rx_status));\n+\tieee80211_rx_ni(hw, skb);\n+}\n+\n+static void mm81x_mac_flush_queues(struct mm81x *mors)\n+{\n+\t/*\n+\t * No need to call mm81x_skbq_stop_tx_queues as mac80211\n+\t * has already cancelled each queue prior to calling .flush()\n+\t */\n+\tmm81x_skbq_data_traffic_pause(mors);\n+\n+\tflush_work(\u0026mors-\u003ehif_work);\n+\tflush_work(\u0026mors-\u003etx_stale_work);\n+\n+\tmm81x_hif_clear_events(mors);\n+\tmm81x_hif_flush_tx_data(mors);\n+\tmm81x_hif_flush_cmds(mors);\n+\n+\t/* Re-enable data, not that there will be any */\n+\tmm81x_skbq_data_traffic_resume(mors);\n+}\n+\n+static bool mm81x_mac_has_tx_pending(struct mm81x *mors)\n+{\n+\tstruct mm81x_skbq *mgmt_q = mm81x_hif_get_tx_mgmt_queue(mors);\n+\tstruct mm81x_skbq *tx_qs;\n+\tint num_qs, i;\n+\n+\tmm81x_hif_skbq_get_tx_qs(mors, \u0026tx_qs, \u0026num_qs);\n+\tfor (i = 0; i \u003c num_qs; i++)\n+\t\tif (mm81x_skbq_count(\u0026tx_qs[i]) ||\n+\t\t    mm81x_skbq_pending_count(\u0026tx_qs[i]))\n+\t\t\treturn true;\n+\n+\tif (mm81x_skbq_count(mgmt_q) || mm81x_skbq_pending_count(mgmt_q))\n+\t\treturn true;\n+\n+\treturn false;\n+}\n+\n+static void mm81x_mac_wait_queues(struct mm81x *mors)\n+{\n+\tif (!wait_event_timeout(mors-\u003etx_empty_waitq,\n+\t\t\t\t!mm81x_mac_has_tx_pending(mors),\n+\t\t\t\tMM81X_FLUSH_TIMEOUT))\n+\t\tdev_warn(mors-\u003edev, \"Unable to empty queues before timeout\");\n+}\n+\n+static void mm81x_mac_ops_flush(struct ieee80211_hw *hw,\n+\t\t\t\tstruct ieee80211_vif *vif, u32 queues,\n+\t\t\t\tbool drop)\n+{\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\n+\t/* We don't support IEEE80211_HW_QUEUE_CONTROL so flush all queues */\n+\tif (drop)\n+\t\tmm81x_mac_flush_queues(mors);\n+\telse\n+\t\tmm81x_mac_wait_queues(mors);\n+}\n+\n+static int mm81x_mac_ops_set_rts_threshold(struct ieee80211_hw *hw,\n+\t\t\t\t\t   int radio_idx, u32 value)\n+{\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\n+\tmors-\u003erts_threshold = value;\n+\treturn 0;\n+}\n+\n+static void mm81x_mac_ops_sta_statistics(struct ieee80211_hw *hw,\n+\t\t\t\t\t struct ieee80211_vif *vif,\n+\t\t\t\t\t struct ieee80211_sta *sta,\n+\t\t\t\t\t struct station_info *sinfo)\n+{\n+\tstruct mm81x_sta *msta = (struct mm81x_sta *)sta-\u003edrv_priv;\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tconst struct mmrc_table *tb = msta-\u003erc.tb;\n+\tstruct mmrc_rate rate;\n+\n+\tif (!tb || tb-\u003ebest_tp.rate == MMRC_MCS_UNUSED) {\n+\t\tsinfo-\u003efilled \u0026= ~BIT_ULL(NL80211_STA_INFO_TX_BITRATE);\n+\t\treturn;\n+\t}\n+\n+\trate = tb-\u003ebest_tp;\n+\tsinfo-\u003etxrate.mcs = rate.rate;\n+\tsinfo-\u003etxrate.nss = NSS_IDX_TO_NSS(rate.ss);\n+\tsinfo-\u003etxrate.flags = RATE_INFO_FLAGS_S1G_MCS;\n+\tswitch (rate.bw) {\n+\tcase MMRC_BW_1MHZ:\n+\t\tsinfo-\u003etxrate.bw = RATE_INFO_BW_1;\n+\t\tbreak;\n+\tcase MMRC_BW_2MHZ:\n+\t\tsinfo-\u003etxrate.bw = RATE_INFO_BW_2;\n+\t\tbreak;\n+\tcase MMRC_BW_4MHZ:\n+\t\tsinfo-\u003etxrate.bw = RATE_INFO_BW_4;\n+\t\tbreak;\n+\tcase MMRC_BW_8MHZ:\n+\t\tsinfo-\u003etxrate.bw = RATE_INFO_BW_8;\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\tif (rate.guard == MMRC_GUARD_SHORT)\n+\t\tsinfo-\u003etxrate.flags |= (RATE_INFO_FLAGS_SHORT_GI);\n+\n+\tdev_dbg(mors-\u003edev, \"mcs: %d, bw: %d, flag: 0x%x\", rate.rate, rate.bw,\n+\t\tsinfo-\u003etxrate.flags);\n+\tsinfo-\u003efilled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE);\n+}\n+\n+static u32 mm81x_get_expected_throughput(struct ieee80211_hw *hw,\n+\t\t\t\t\t struct ieee80211_sta *sta)\n+{\n+\tstruct mm81x_sta *msta = (struct mm81x_sta *)sta-\u003edrv_priv;\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tconst struct mmrc_table *tb = msta-\u003erc.tb;\n+\tstruct mmrc_rate rate;\n+\tu32 tput;\n+\n+\tif (!tb || tb-\u003ebest_tp.rate == MMRC_MCS_UNUSED)\n+\t\treturn 0;\n+\n+\trate = tb-\u003ebest_tp;\n+\ttput = BPS_TO_KBPS(mmrc_calculate_theoretical_throughput(rate));\n+\tdev_dbg(mors-\u003edev, \"Throughput: MCS: %d, BW: %d, GI: %d -\u003e %u\",\n+\t\trate.rate, 1 \u003c\u003c rate.bw, rate.guard, tput);\n+\n+\treturn tput;\n+}\n+\n+static void mm81x_mac_restart_cleanup_iter(void *data, u8 *mac,\n+\t\t\t\t\t   struct ieee80211_vif *vif)\n+{\n+\tif (vif-\u003etype == NL80211_IFTYPE_AP)\n+\t\tmm81x_mac_beacon_finish((struct mm81x_vif *)vif-\u003edrv_priv);\n+}\n+\n+static void mm81x_mac_restart_cleanup(struct mm81x *mors)\n+{\n+\tieee80211_iterate_active_interfaces(mors-\u003ehw,\n+\t\t\t\t\t    IEEE80211_IFACE_ITER_NORMAL,\n+\t\t\t\t\t    mm81x_mac_restart_cleanup_iter,\n+\t\t\t\t\t    NULL);\n+\tmm81x_mac_hw_scan_finish(mors);\n+}\n+\n+static int mm81x_mac_restart(struct mm81x *mors)\n+{\n+\tint ret;\n+\tu32 chip_id;\n+\n+\tmors-\u003estarted = false;\n+\tmm81x_ps_disable(mors);\n+\tmm81x_bus_set_irq(mors, false);\n+\tmm81x_hw_irq_clear(mors);\n+\tieee80211_stop_queues(mors-\u003ehw);\n+\n+\tset_bit(MM81X_STATE_DATA_TX_STOPPED, \u0026mors-\u003estate_flags);\n+\tset_bit(MM81X_STATE_DATA_QS_STOPPED, \u0026mors-\u003estate_flags);\n+\n+\t/* Allow time for in-transit tx/rx packets to settle */\n+\tmdelay(MM81X_HW_RESTART_DELAY_MS);\n+\tflush_work(\u0026mors-\u003ehif_work);\n+\tflush_work(\u0026mors-\u003etx_stale_work);\n+\tmm81x_hif_clear_events(mors);\n+\tmm81x_hif_flush_tx_data(mors);\n+\tmm81x_hif_flush_cmds(mors);\n+\n+\tmm81x_claim_bus(mors);\n+\tret = mm81x_reg32_read(mors, MM81X_REG_CHIP_ID(mors), \u0026chip_id);\n+\tmm81x_release_bus(mors);\n+\n+\tif (ret \u003c 0) {\n+\t\tdev_err(mors-\u003edev, \"Failed to access HW: %d\", ret);\n+\t\tgoto exit;\n+\t}\n+\n+\tmm81x_mac_restart_cleanup(mors);\n+\n+\tret = mm81x_fw_init(mors, true);\n+\tif (ret \u003c 0) {\n+\t\tdev_err(mors-\u003edev, \"Failed to init firmware: %d\", ret);\n+\t\tgoto exit;\n+\t}\n+\n+\tmm81x_hw_irq_enable(mors, MM81X_INT_HW_STOP_NOTIFICATION_NUM, true);\n+\n+\tret = mm81x_fw_parse_ext_host_tbl(mors);\n+\tif (ret) {\n+\t\tdev_err(mors-\u003edev, \"failed to parse extended host table: %d\",\n+\t\t\tret);\n+\t\tgoto exit;\n+\t}\n+\n+\tmm81x_mac_caps_init(mors);\n+\n+\tmm81x_bus_set_irq(mors, true);\n+\tclear_bit(MM81X_STATE_DATA_TX_STOPPED, \u0026mors-\u003estate_flags);\n+\tclear_bit(MM81X_STATE_DATA_QS_STOPPED, \u0026mors-\u003estate_flags);\n+\tclear_bit(MM81X_STATE_CHIP_UNRESPONSIVE, \u0026mors-\u003estate_flags);\n+\tclear_bit(MM81X_STATE_RELOAD_FW_AFTER_START, \u0026mors-\u003estate_flags);\n+\tmm81x_mac_check_fw_disabled_chans(mors-\u003ehw);\n+\tieee80211_restart_hw(mors-\u003ehw);\n+\n+exit:\n+\tmm81x_ps_enable(mors);\n+\treturn ret;\n+}\n+\n+static int mm81x_mac_ops_add_interface(struct ieee80211_hw *hw,\n+\t\t\t\t       struct ieee80211_vif *vif)\n+{\n+\tint ret = 0;\n+\tstruct mm81x *mors = hw-\u003epriv;\n+\tstruct mm81x_vif *mors_vif = (struct mm81x_vif *)vif-\u003edrv_priv;\n+\n+\tif (test_bit(MM81X_STATE_RELOAD_FW_AFTER_START, \u0026mors-\u003estate_flags)) {\n+\t\tdev_info(mors-\u003edev, \"Restarting chip with regdom: %s\",\n+\t\t\t mors-\u003ecountry);\n+\n+\t\tret = mm81x_mac_restart(mors);\n+\t\tif (ret) {\n+\t\t\tdev_err(mors-\u003edev, \"Failed to restart chip\");\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\t/*\n+\t\t * mac_restart will trigger ieee80211_hw_restart and\n+\t\t * add_interface will re-enter. just exit here instead.\n+\t\t */\n+\t\treturn 0;\n+\t}\n+\n+\tvif-\u003edriver_flags |= IEEE80211_VIF_BEACON_FILTER;\n+\tmors_vif-\u003emors = mors;\n+\n+\tret = mm81x_cmd_add_if(mors, \u0026mors_vif-\u003eid, vif-\u003eaddr, vif-\u003etype);\n+\tif (ret) {\n+\t\tdev_err(mors-\u003edev, \"mm81x_cmd_add_if failed %d\", ret);\n+\t\treturn ret;\n+\t}\n+\n+\tif (mors_vif-\u003eid \u003e= ARRAY_SIZE(mors-\u003evifs)) {\n+\t\tdev_err(mors-\u003edev, \"vif_id is too large %u\", mors_vif-\u003eid);\n+\t\tret = -EOPNOTSUPP;\n+\t\treturn ret;\n+\t}\n+\n+\tif (mors_vif-\u003eid != (mors_vif-\u003eid \u0026 MM81X_TX_CONF_FLAGS_VIF_ID_MASK)) {\n+\t\tdev_err(mors-\u003edev, \"invalid vif_id %u\", mors_vif-\u003eid);\n+\t\tret = -EOPNOTSUPP;\n+\t\treturn ret;\n+\t}\n+\n+\trcu_assign_pointer(mors-\u003evifs[mors_vif-\u003eid], vif);\n+\n+\tif (vif-\u003etype == NL80211_IFTYPE_AP)\n+\t\tmm81x_mac_beacon_init(mors_vif);\n+\n+\tret = mm81x_cmd_get_capabilities(mors, mors_vif-\u003eid, \u0026mors-\u003efw_caps);\n+\tif (ret) {\n+\t\tdev_err(mors-\u003edev,\n+\t\t\t\"mm81x_cmd_get_capabilities failed for vif %d\",\n+\t\t\tmors_vif-\u003eid);\n+\t\treturn ret;\n+\t}\n+\n+\tieee80211_wake_queues(mors-\u003ehw);\n+\treturn ret;\n+}\n+\n+static const struct ieee80211_ops mm81x_ops = {\n+\t.start = mm81x_mac_ops_start,\n+\t.stop = mm81x_mac_ops_stop,\n+\t.config = mm81x_mac_ops_config,\n+\t.wake_tx_queue = ieee80211_handle_wake_tx_queue,\n+\t.tx = mm81x_mac_ops_tx,\n+\t.add_interface = mm81x_mac_ops_add_interface,\n+\t.remove_interface = mm81x_mac_ops_remove_interface,\n+\t.configure_filter = mm81x_mac_ops_configure_filter,\n+\t.sta_state = mm81x_mac_ops_sta_state,\n+\t.flush = mm81x_mac_ops_flush,\n+\t.set_frag_threshold = mm81x_mac_set_frag_threshold,\n+\t.set_rts_threshold = mm81x_mac_ops_set_rts_threshold,\n+\t.sta_statistics = mm81x_mac_ops_sta_statistics,\n+\t.get_expected_throughput = mm81x_get_expected_throughput,\n+\t.hw_scan = mm81x_mac_ops_hw_scan,\n+\t.cancel_hw_scan = mm81x_mac_ops_cancel_hw_scan,\n+\t.get_txpower = mm81x_mac_ops_get_txpower,\n+\t.bss_info_changed = mm81x_mac_ops_bss_info_changed,\n+\t.prepare_multicast = mm81x_mac_ops_prepare_multicast,\n+\t.conf_tx = mm81x_mac_ops_conf_tx,\n+\t.ampdu_action = mm81x_mac_ops_ampdu_action,\n+\t.set_key = mm81x_mac_ops_set_key,\n+\t.add_chanctx = ieee80211_emulate_add_chanctx,\n+\t.remove_chanctx = ieee80211_emulate_remove_chanctx,\n+\t.change_chanctx = ieee80211_emulate_change_chanctx,\n+\t.switch_vif_chanctx = ieee80211_emulate_switch_vif_chanctx,\n+};\n+\n+static void mm81x_reg_notifier(struct wiphy *wiphy,\n+\t\t\t       struct regulatory_request *request)\n+{\n+\tint ret;\n+\tstruct mm81x *mors = wiphy_to_ieee80211_hw(wiphy)-\u003epriv;\n+\n+\tif (mm81x_reg_h_cc_equal(request-\u003ealpha2, \"00\") ||\n+\t    mm81x_reg_h_cc_equal(request-\u003ealpha2, mors-\u003ecountry))\n+\t\treturn;\n+\n+\tmemcpy(mors-\u003ecountry, request-\u003ealpha2, sizeof(mors-\u003ecountry));\n+\n+\tret = mm81x_mac_restart(mors);\n+\tif (ret)\n+\t\tdev_err(mors-\u003edev, \"Failed to restart chip: %d\", ret);\n+}\n+\n+static void mm81x_mac_config_hw(struct mm81x *mors)\n+{\n+\tint i;\n+\tstruct ieee80211_hw *hw = mors-\u003ehw;\n+\tstruct wiphy *wiphy;\n+\n+\tfor (i = 0; i \u003c NUM_NL80211_BANDS; i++)\n+\t\thw-\u003ewiphy-\u003ebands[i] = NULL;\n+\n+\thw-\u003ewiphy-\u003ebands[NL80211_BAND_S1GHZ] = \u0026mors_band_s1ghz;\n+\thw-\u003ewiphy-\u003einterface_modes = BIT(NL80211_IFTYPE_AP) |\n+\t\t\t\t     BIT(NL80211_IFTYPE_STATION);\n+\thw-\u003ewiphy-\u003ereg_notifier = mm81x_reg_notifier;\n+\thw-\u003equeues = MM81X_HW_QUEUE_COUNT;\n+\thw-\u003emax_rates = MM81X_HW_MAX_RATES;\n+\thw-\u003emax_report_rates = MM81X_HW_MAX_REPORT_RATES;\n+\thw-\u003emax_rate_tries = MM81X_HW_MAX_RATE_TRIES;\n+\thw-\u003etx_sk_pacing_shift = MM81X_HW_TX_SK_PACING_SHIFT;\n+\thw-\u003evif_data_size = sizeof(struct mm81x_vif);\n+\thw-\u003esta_data_size = sizeof(struct mm81x_sta);\n+\thw-\u003eextra_tx_headroom =\n+\t\tsizeof(struct mm81x_skb_hdr) + mm81x_bus_get_alignment(mors);\n+\n+\tmors-\u003ewiphy = hw-\u003ewiphy;\n+\n+\tieee80211_hw_set(hw, SIGNAL_DBM);\n+\tieee80211_hw_set(hw, MFP_CAPABLE);\n+\tieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS);\n+\tieee80211_hw_set(hw, AMPDU_AGGREGATION);\n+\tieee80211_hw_set(hw, HOST_BROADCAST_PS_BUFFERING);\n+\tieee80211_hw_set(hw, HAS_RATE_CONTROL);\n+\tieee80211_hw_set(hw, SUPPORTS_PS);\n+\tieee80211_hw_set(hw, NEED_DTIM_BEFORE_ASSOC);\n+\tieee80211_hw_set(hw, PS_NULLFUNC_STACK);\n+\tieee80211_hw_set(hw, SUPPORTS_TX_FRAG);\n+\tieee80211_hw_set(hw, SUPPORTS_NDP_BLOCKACK);\n+\n+\tSET_IEEE80211_PERM_ADDR(hw, mors-\u003emacaddr);\n+\n+\twiphy = mors-\u003ewiphy;\n+\n+\twiphy-\u003eflags |= WIPHY_FLAG_AP_UAPSD;\n+\twiphy-\u003eflags |= WIPHY_FLAG_PS_ON_BY_DEFAULT;\n+\n+\tif (!mors-\u003eps.enable)\n+\t\twiphy-\u003eflags \u0026= ~WIPHY_FLAG_PS_ON_BY_DEFAULT;\n+\n+\twiphy-\u003efeatures |= NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE |\n+\t\t\t   NL80211_FEATURE_TX_POWER_INSERTION;\n+\n+\twiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_AIRTIME_FAIRNESS);\n+\twiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SET_SCAN_DWELL);\n+\n+\twiphy-\u003eiface_combinations = mors_if_combs;\n+\twiphy-\u003en_iface_combinations = ARRAY_SIZE(mors_if_combs);\n+\twiphy-\u003emax_scan_ie_len = MM81X_MAX_SCAN_IE_LEN;\n+\twiphy-\u003emax_scan_ssids = MM81X_MAX_SCAN_SSIDS;\n+\twiphy-\u003esignal_type = CFG80211_SIGNAL_TYPE_MBM;\n+\twiphy-\u003emax_remain_on_channel_duration =\n+\t\tMM81X_MAX_REMAIN_ON_CHAN_DURATION;\n+}\n+\n+static void mm81x_stale_tx_status_timer(struct timer_list *t)\n+{\n+\tstruct mm81x *mors = timer_container_of(mors, t, stale_status.timer);\n+\n+\tspin_lock_bh(\u0026mors-\u003estale_status.lock);\n+\tif (mm81x_hif_get_tx_status_pending_count(mors))\n+\t\tqueue_work(mors-\u003enet_wq, \u0026mors-\u003etx_stale_work);\n+\tspin_unlock_bh(\u0026mors-\u003estale_status.lock);\n+}\n+\n+static void mm81x_stale_tx_status_timer_finish(struct mm81x *mors)\n+{\n+\ttimer_delete_sync_try(\u0026mors-\u003estale_status.timer);\n+}\n+\n+static void mm81x_mac_stale_tx_status_timer_init(struct mm81x *mors)\n+{\n+\tspin_lock_init(\u0026mors-\u003estale_status.lock);\n+\ttimer_setup(\u0026mors-\u003estale_status.timer, mm81x_stale_tx_status_timer, 0);\n+}\n+\n+int mm81x_mac_register(struct mm81x *mors)\n+{\n+\tint ret;\n+\tstruct ieee80211_hw *hw = mors-\u003ehw;\n+\n+\tmors-\u003etx_power_mbm = INT_MAX;\n+\tmors-\u003etx_max_power_mbm = INT_MAX;\n+\tmors-\u003erts_threshold = IEEE80211_MAX_RTS_THRESHOLD;\n+\n+\tret = mm81x_ps_init(mors);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tmm81x_mac_config_hw(mors);\n+\tmm81x_mac_hw_scan_init(mors);\n+\tmm81x_mac_stale_tx_status_timer_init(mors);\n+\n+\tret = ieee80211_register_hw(hw);\n+\tif (ret) {\n+\t\tdev_err(mors-\u003edev, \"ieee80211_register_hw failed %d\", ret);\n+\t\tmm81x_mac_unregister(mors);\n+\t\treturn ret;\n+\t}\n+\n+\tmm81x_rc_init(mors);\n+\n+\t/*\n+\t * At this stage, we know bus and pager system interrupts are enabled.\n+\t * Trigger the receive workqueue to drain any incoming chip-to-host\n+\t * pending packets been pushed in the period between the firmware\n+\t * initialization and interrupts being enabled.\n+\t */\n+\tset_bit(MM81X_HIF_EVT_RX_PEND, \u0026mors-\u003ehif.event_flags);\n+\tqueue_work(mors-\u003echip_wq, \u0026mors-\u003ehif_work);\n+\n+\treturn ret;\n+}\n+\n+void mm81x_mac_unregister(struct mm81x *mors)\n+{\n+\tmm81x_ps_disable(mors);\n+\tmm81x_rc_deinit(mors);\n+\tmm81x_mac_hw_scan_destroy(mors);\n+\n+\tieee80211_stop_queues(mors-\u003ehw);\n+\tieee80211_unregister_hw(mors-\u003ehw);\n+\n+\tmm81x_hif_flush_tx_data(mors);\n+\tmm81x_hif_flush_cmds(mors);\n+\tmm81x_stale_tx_status_timer_finish(mors);\n+\tmm81x_ps_finish(mors);\n+\n+\tkfree(mors-\u003emcast_filter);\n+}\n+\n+struct mm81x *mm81x_mac_alloc(size_t priv_size, struct device *dev)\n+{\n+\tstruct ieee80211_hw *hw;\n+\tstruct mm81x *mors;\n+\n+\thw = ieee80211_alloc_hw(sizeof(*mors) + priv_size, \u0026mm81x_ops);\n+\tif (!hw) {\n+\t\tdev_err(dev, \"ieee80211_alloc_hw failed\\r\\n\");\n+\t\treturn NULL;\n+\t}\n+\n+\tSET_IEEE80211_DEV(hw, dev);\n+\tmemset(hw-\u003epriv, 0, sizeof(*mors));\n+\n+\tmors = hw-\u003epriv;\n+\tmors-\u003ehw = hw;\n+\tmors-\u003edev = dev;\n+\tmutex_init(\u0026mors-\u003ecmd_lock);\n+\tmutex_init(\u0026mors-\u003ecmd_wait);\n+\tinit_waitqueue_head(\u0026mors-\u003etx_empty_waitq);\n+\n+\treturn mors;\n+}\n+\n+void mm81x_mac_free(struct mm81x *mors)\n+{\n+\tieee80211_free_hw(mors-\u003ehw);\n+}\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/mac.h b/drivers/net/wireless/morsemicro/mm81x/mac.h\nnew file mode 100644\nindex 00000000000000..c540471f274eaf\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/mac.h\n@@ -0,0 +1,63 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#ifndef _MM81X_MAC_H_\n+#define _MM81X_MAC_H_\n+\n+#include \"core.h\"\n+#include \"command.h\"\n+\n+struct mm81x_queue_params {\n+\tu8 uapsd;\n+\tu8 aci;\n+\tu8 aifs;\n+\tu16 cw_min;\n+\tu16 cw_max;\n+\tu32 txop;\n+};\n+\n+static inline u32 mm81x_vif_generate_cssid(struct ieee80211_vif *vif)\n+{\n+\treturn mm81x_generate_cssid(vif-\u003ecfg.ssid, vif-\u003ecfg.ssid_len);\n+}\n+\n+/*\n+ * Build a little-endian word from the last four octets of a MAC address;\n+ * the first two octets are dropped.\n+ */\n+static inline __le32 mac2le32(const unsigned char *addr)\n+{\n+\treturn cpu_to_le32(((u32)(addr[2]) \u003c\u003c 24) | ((u32)(addr[3]) \u003c\u003c 16) |\n+\t\t\t   ((u32)(addr[4]) \u003c\u003c 8) | ((u32)(addr[5])));\n+}\n+\n+static inline struct ieee80211_vif *\n+mm81x_rcu_dereference_vif_id(struct mm81x *mors, u8 vif_id, bool rcu)\n+{\n+\tif (WARN_ON(vif_id \u003e= ARRAY_SIZE(mors-\u003evifs)))\n+\t\treturn NULL;\n+\n+\tif (rcu)\n+\t\treturn rcu_dereference(mors-\u003evifs[vif_id]);\n+\n+\treturn rcu_dereference_protected(mors-\u003evifs[vif_id],\n+\t\t\t\t\t lockdep_is_held(\u0026mors-\u003ehw-\u003ewiphy-\u003emtx));\n+}\n+\n+int mm81x_tx_h_get_attempts(struct mm81x *mors,\n+\t\t\t    struct mm81x_skb_tx_status *tx_sts);\n+struct mm81x *mm81x_mac_alloc(size_t priv_size, struct device *dev);\n+int mm81x_mac_register(struct mm81x *mors);\n+void mm81x_mac_free(struct mm81x *mors);\n+void mm81x_mac_unregister(struct mm81x *mors);\n+int mm81x_mac_event_recv(struct mm81x *mors, struct sk_buff *skb);\n+void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb,\n+\t\t      struct mm81x_skb_rx_status *hdr_rx_status);\n+void mm81x_mac_beacon_irq_handle(struct mm81x *mors, u32 status);\n+\n+u8 *mm81x_hw_scan_h_insert_tlvs(struct mm81x_hw_scan_params *params, u8 *buf);\n+size_t mm81x_hw_scan_h_get_cmd_size(struct mm81x_hw_scan_params *params);\n+void mm81x_tx_h_check_aggr(struct ieee80211_sta *pubsta, struct sk_buff *skb);\n+#endif /* !_MM81X_MAC_H_ */\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/mmrc.c b/drivers/net/wireless/morsemicro/mm81x/mmrc.c\nnew file mode 100644\nindex 00000000000000..fe7e4f501d6c23\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/mmrc.c\n@@ -0,0 +1,1354 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+#include \"mmrc.h\"\n+\n+/*\n+ * The default packet size in bits used for calculated throughput of a given\n+ * rate\n+ */\n+#define DEFAULT_PACKET_SIZE_BITS 9600\n+\n+/*\n+ * The default packet size in bytes used for calculating retries for a given\n+ * rate\n+ */\n+#define DEFAULT_PACKET_SIZE_BYTES 1200\n+\n+/* The sample frequencies at different stages */\n+#define LOOKAROUND_RATE_INIT 5\n+#define LOOKAROUND_RATE_NORMAL 50\n+#define LOOKAROUND_RATE_STABLE 100\n+\n+/* The thresholds for stability stages */\n+#define STABILITY_CNT_THRESHOLD_INIT 20\n+#define STABILITY_CNT_THRESHOLD_NORMAL 50\n+#define STABILITY_CNT_THRESHOLD_STABLE 100\n+\n+/* The backoff step size for the counter */\n+#define STABILITY_BACKOFF_STEP 2\n+\n+/*\n+ * The packet success threshold for attempting slower lookaround rates\n+ */\n+/*\n+ * Force a look around if there haven't been any for this number of cycles\n+ */\n+#define LOOKAROUND_MAX_RC_CYCLES 5\n+\n+/*\n+ * Number of attempts for each lookaround rate within at most two RC cycles\n+ * if there are enough packets\n+ */\n+#define LOOKAROUND_RATE_ATTEMPTS 4\n+\n+/*\n+ * Limit the number of times we try to pick a theoretically better rate to\n+ * sample. Necessary so we don't stall the CPU, due to constantly picking worse\n+ * rates.\n+ */\n+#define LOOKAROUND_FAIL_MAX 200\n+\n+/*\n+ * Initial and reset probability per rate in the table\n+ * Changing this value will have a severe implication on the current heuristic\n+ * It could mean that some rates will have better probability throughput even\n+ * with no edivence and so will cause unexpected changes in the rate table\n+ */\n+#define RATE_INIT_PROBABILITY 0\n+\n+/*\n+ * The lowest number of MPDUs within acknowledged AMPDUs that can be used for\n+ * rate stats\n+ */\n+#define AMPDU_STATS_MIN 2\n+\n+/*\n+ * The lowest number of stats to be used for processing in NORMAL lookaround\n+ * mode\n+ */\n+#define STATS_MIN_NORMAL 2\n+\n+/*\n+ * The lowest number of stats to be used for processing in INIT lookaround\n+ * mode\n+ */\n+#define STATS_MIN_INIT 1\n+\n+/* The lowest probability value considered for recognising a dip */\n+#define PROBABILITY_DIP_MIN 20\n+\n+/* The lowest probability value for recovering from a dip */\n+#define PROBABILITY_DIP_RECOVERY_MIN 40\n+\n+/*\n+ * The time cap on rate allocation for multiple attempts. If a single attempt\n+ * exceeds this window, no additional attempts will be generated\n+ */\n+#define MAX_WINDOW_ATTEMPT_TIME 4000\n+\n+/* The time window for all rates in rate table */\n+#define RATE_WINDOW_MICROSECONDS 24000\n+\n+/*\n+ * EWMA is the alpha coefficient in the exponential weighting moving average\n+ * filter used for probability updates.\n+ *\n+ * Y[n] = X[n] * (100 - EWMA) + (Y[n-1] * EWMA)\n+ *\t  -------------------------------------\n+ *\t\t\t   100\n+ *\n+ */\n+#define EWMA 75\n+\n+/*\n+ * Evidence scaling to allow for one decimal place. Needed for low\n+ * throughput, otherwise the history decays in a single cycle.\n+ */\n+#define EVIDENCE_SCALE 5\n+\n+/*\n+ * Evidence maximum to ensure history doesn't decay too slowly when\n+ * there is a lot of historical data.\n+ */\n+#define EVIDENCE_MAX 100\n+\n+/*\n+ * This fixed point conversion multiplies a value by one and shifts it\n+ * accordingly to account for the fixed point shifting at the return of a\n+ * function\n+ */\n+#define FP_8_MULT_1 256\n+\n+/* Fixed point conversion for 2.1 * 2^8 used for 4MHz symbol multiplication */\n+#define FP_8_4MHZ 537\n+\n+/* Fixed point conversion for 4.5 * 2^8 used for 8MHz symbol multiplication */\n+#define FP_8_8MHZ 1152\n+\n+/* Fixed point conversion for 9.0 * 2^8 used for 16MHz symbol multiplication */\n+#define FP_8_16MHZ 2301\n+\n+/*\n+ * Fixed point conversion for 3.6 * 2^8 used for long guard symbol tx time\n+ * multiplication\n+ */\n+#define FP_8_LONG_GUARD_SYMBOL_TIME 1024\n+\n+/*\n+ * Fixed point conversion for 4.0 * 2^8 used for short guard symbol tx time\n+ * multiplication\n+ */\n+#define FP_8_SHORT_GUARD_SYMBOL_TIME 921\n+\n+/*\n+ * Shift value to shift back our FP conversions\n+ */\n+#define FP_8_SHIFT 8\n+\n+/*\n+ * Limit to count of consecutive variations in one direction\n+ */\n+#define MAX_VARIATION_DIRECTION 5\n+\n+/*\n+ * Threshold for considering consecutive variation direction as variation\n+ * or not\n+ */\n+#define VARIATION_DIRECTION_THRESHOLD 3\n+\n+/* EWMA percentage value for averaging the best rate probability variation */\n+#define VARIATION_EWMA 95\n+\n+/* Percentage variation regarded as minor */\n+#define MINOR_VARIATION_THRESHOLD 1\n+\n+/* Percentage variation regarded as moderate */\n+#define MODERATE_VARIATION_THRESHOLD 3\n+\n+/* Percentage variation regarded as significant */\n+#define SIGNIFICANT_VARIATION_THRESHOLD 5\n+\n+/* If the best rate changes twice in this number of cycles, it is unstable */\n+#define BEST_RATE_UNSTABLE_THRESHOLD 4\n+\n+/*\n+ * Once the best rate is unchanged for this number of cycles it has\n+ * converged\n+ */\n+#define BEST_RATE_CONVERGED_THRESHOLD 10\n+\n+/* RSSI threshold for short range */\n+#define MMRC_SHORT_RANGE_RSSI_LIMIT -70\n+\n+/* RSSI threshold for mid range */\n+#define MMRC_MID_RANGE_RSSI_LIMIT -85\n+\n+#define MMRC_MAX_BW(bw_caps)                                \\\n+\t(((bw_caps) \u0026 BIT(MMRC_BW_16MHZ)) ? MMRC_BW_16MHZ : \\\n+\t ((bw_caps) \u0026 BIT(MMRC_BW_8MHZ))  ? MMRC_BW_8MHZ :  \\\n+\t ((bw_caps) \u0026 BIT(MMRC_BW_4MHZ))  ? MMRC_BW_4MHZ :  \\\n+\t ((bw_caps) \u0026 BIT(MMRC_BW_2MHZ))  ? MMRC_BW_2MHZ :  \\\n+\t\t\t\t\t    MMRC_BW_1MHZ)\n+\n+/*\n+ * This table stores the number of bits per symbols used for MCS0-MCS9 based\n+ * on 20MHz and 1SS\n+ */\n+static const u32 sym_table[10] = { 24, 36, 48, 72, 96, 144, 192, 216, 256, 288 };\n+\n+/*\n+ * Calculate which bit is the nth bit set in an integer based flag.\n+ */\n+static u8 nth_bit(u16 in, u16 index)\n+{\n+\tu32 i;\n+\tu8 count = 0;\n+\n+\tfor (i = 0; count != index + 1; i++) {\n+\t\tif (((1u \u003c\u003c i) \u0026 in) != 0)\n+\t\t\tcount++;\n+\t}\n+\n+\treturn i - 1;\n+}\n+\n+/*\n+ * Calculate the input bit's index among all the set bits in an integer\n+ * based flag.\n+ */\n+static u16 bit_index(u16 in, u32 bit_pos)\n+{\n+\tu16 i;\n+\tu16 index = 0;\n+\n+\tfor (i = 0; i != bit_pos + 1; i++) {\n+\t\tif (((1u \u003c\u003c i) \u0026 in) != 0)\n+\t\t\tindex++;\n+\t}\n+\n+\tif (index == 0) {\n+\t\t/* Could not match bit pos to caps */\n+\t\treturn 0;\n+\t}\n+\n+\treturn index - 1;\n+}\n+\n+static u16 rows_from_sta_caps(struct mmrc_sta_capabilities *caps)\n+{\n+\tu16 rows = 0;\n+\tu8 n_rates = hweight_long(caps-\u003erates);\n+\n+\t/* Taking MCS10 into account as it is relevant for 1 MHz entries */\n+\tif (caps-\u003erates \u0026 BIT(MMRC_MCS10)) {\n+\t\tn_rates -= 1;\n+\t\trows = 2;\n+\t}\n+\n+\trows += (hweight_long(caps-\u003ebandwidth) * n_rates *\n+\t\t hweight_long(caps-\u003eguard) *\n+\t\t hweight_long(caps-\u003espatial_streams));\n+\n+\treturn rows;\n+}\n+\n+static void rate_update_index(struct mmrc_table *tb, struct mmrc_rate *rate)\n+{\n+\tu16 index = 0;\n+\t/* Information about our rates */\n+\tu16 bw = hweight_long(tb-\u003ecaps.bandwidth);\n+\tu16 streams = hweight_long(tb-\u003ecaps.spatial_streams);\n+\tu16 guard = hweight_long(tb-\u003ecaps.guard);\n+\tu16 rows = rows_from_sta_caps(\u0026tb-\u003ecaps);\n+\n+\tindex = bit_index(tb-\u003ecaps.guard, rate-\u003eguard) +\n+\t\tbit_index(tb-\u003ecaps.bandwidth, rate-\u003ebw) * guard +\n+\t\tbit_index(tb-\u003ecaps.spatial_streams, rate-\u003ess) * guard * bw +\n+\t\tbit_index(tb-\u003ecaps.rates, rate-\u003erate) * bw * streams * guard;\n+\n+\tif (index \u003e= rows)\n+\t\tindex = 0;\n+\n+\trate-\u003eindex = index;\n+}\n+\n+static struct mmrc_rate get_rate_row(struct mmrc_table *tb, u16 index)\n+{\n+\tstruct mmrc_rate rate;\n+\tu16 ss_index;\n+\n+\t/* Information about our rates */\n+\tu16 mcs = hweight_long(tb-\u003ecaps.rates);\n+\tu16 bw = hweight_long(tb-\u003ecaps.bandwidth);\n+\tu16 streams = hweight_long(tb-\u003ecaps.spatial_streams);\n+\tu16 guard = hweight_long(tb-\u003ecaps.guard);\n+\tu16 total_caps = mcs * bw * streams * guard;\n+\n+\t/* Find our MCS */\n+\tu16 rows = total_caps / mcs;\n+\tu16 mcs_index = index / rows;\n+\tu16 mcs_modulo = index % rows;\n+\n+\tmcs = nth_bit(tb-\u003ecaps.rates, mcs_index);\n+\n+\t/* Find our spatial stream */\n+\trows = rows / streams;\n+\tstreams = nth_bit(tb-\u003ecaps.spatial_streams, mcs_modulo / rows);\n+\n+\t/* Find our bandwidth */\n+\tss_index = index % rows;\n+\trows = rows / bw;\n+\tbw = nth_bit(tb-\u003ecaps.bandwidth, ss_index / rows);\n+\n+\t/* Find our guard */\n+\tguard = nth_bit(tb-\u003ecaps.guard, index % guard);\n+\n+\t/* Add range checks to keep scan-build happy */\n+\tif (bw \u003e= MMRC_BW_MAX)\n+\t\tbw = MMRC_BW_1MHZ;\n+\n+\tif (guard \u003e= MMRC_GUARD_MAX)\n+\t\tguard = MMRC_GUARD_LONG;\n+\n+\t/* Validate guard against capability */\n+\tif (guard == MMRC_GUARD_SHORT \u0026\u0026\n+\t    !(tb-\u003ecaps.sgi_per_bw \u0026 SGI_PER_BW(bw)))\n+\t\tguard = MMRC_GUARD_LONG;\n+\n+\t/* Create our rate row and send it */\n+\trate.bw = MMRC_BW_TO_BITFIELD(bw);\n+\trate.ss = MMRC_SS_TO_BITFIELD(streams);\n+\trate.rate = MMRC_RATE_TO_BITFIELD(mcs);\n+\trate.guard = MMRC_GUARD_TO_BITFIELD(guard);\n+\trate.attempts = 0;\n+\trate.flags = 0;\n+\n+\t/* Update index as bw or guard may have changed */\n+\trate_update_index(tb, \u0026rate);\n+\n+\treturn rate;\n+}\n+\n+size_t mmrc_memory_required_for_caps(struct mmrc_sta_capabilities *caps)\n+{\n+\treturn sizeof(struct mmrc_table) +\n+\t       rows_from_sta_caps(caps) * sizeof(struct mmrc_stats_table);\n+}\n+\n+static u32 calculate_bits_per_symbol(struct mmrc_rate *rate)\n+{\n+\tu32 bps;\n+\n+\t/* If MCS10 is selected we return 2*MCS0 Symbols */\n+\tif (rate-\u003erate == MMRC_MCS10)\n+\t\treturn 6;\n+\n+\t/* Confirm that the rate is valid for the sym_table lookup */\n+\tif (rate-\u003erate \u003e= MMRC_MCS_UNUSED) {\n+\t\tpr_err(\"%s: Invalid MCS rate %d for sym_table lookup\\n\",\n+\t\t       __func__, rate-\u003erate);\n+\t\treturn 1;\n+\t}\n+\n+\t/*\n+\t * Coversion from 20MHz as in sym_table to:\n+\t * 40MHz   ==\tx 2.1\n+\t * 80MHz   ==\tx 4.5\n+\t * 160MHz  ==\tx 9.0\n+\t */\n+\tbps = sym_table[rate-\u003erate];\n+\tswitch (rate-\u003ebw) {\n+\tcase (MMRC_BW_4MHZ):\n+\t\tbps *= FP_8_4MHZ;\n+\t\tbreak;\n+\tcase (MMRC_BW_8MHZ):\n+\t\tbps *= FP_8_8MHZ;\n+\t\tbreak;\n+\tcase (MMRC_BW_16MHZ):\n+\t\tbps *= FP_8_16MHZ;\n+\t\tbreak;\n+\tcase (MMRC_BW_1MHZ):\n+\t\tbps = sym_table[rate-\u003erate] * 24 / 52;\n+\t\tbps *= FP_8_MULT_1;\n+\t\tbreak;\n+\tcase (MMRC_BW_2MHZ):\n+\tcase (MMRC_BW_MAX):\n+\tdefault:\n+\t\tbps *= FP_8_MULT_1;\n+\t\tbreak;\n+\t}\n+\t/* SS + 1 because mmrc_spatial_stream starts at 0 */\n+\treturn ((rate-\u003ess + 1) * bps) \u003e\u003e FP_8_SHIFT;\n+}\n+\n+static u32 get_tx_time(struct mmrc_rate *rate)\n+{\n+\tu32 tx = 0;\n+\tu32 n_sym;\n+\tu32 avg_bits;\n+\n+\t/* Calculate tx time based on a default packet size */\n+\tavg_bits = DEFAULT_PACKET_SIZE_BITS;\n+\n+\t/* Number of bits per symbol for this rate */\n+\tn_sym = calculate_bits_per_symbol(rate);\n+\n+\t/* In case of bad calcuation/parameter use lowest value */\n+\tn_sym = n_sym == 0 ? sym_table[0] : n_sym;\n+\n+\t/* number of symbols in default packet size */\n+\tn_sym = avg_bits / n_sym;\n+\n+\t/* tx is time to transmit average packet in us */\n+\tswitch (rate-\u003eguard) {\n+\tcase (MMRC_GUARD_LONG):\n+\t\ttx = n_sym * FP_8_LONG_GUARD_SYMBOL_TIME;\n+\t\tbreak;\n+\tcase (MMRC_GUARD_SHORT):\n+\t\ttx = n_sym * FP_8_SHORT_GUARD_SYMBOL_TIME;\n+\t\tbreak;\n+\tdefault:\n+\t\treturn 0;\n+\t}\n+\n+\treturn (tx * 10) \u003e\u003e FP_8_SHIFT;\n+}\n+\n+u32 mmrc_calculate_theoretical_throughput(struct mmrc_rate rate)\n+{\n+\tstatic const u32 s1g_tpt_lgi[4][11] = {\n+\t\t{ 300, 600, 900, 1200, 1800, 2400, 2700, 3000, 3600, 4000,\n+\t\t  150 },\n+\t\t{ 650, 1300, 1950, 2600, 3900, 5200, 5850, 6500, 7800, 0, 0 },\n+\t\t{ 1350, 2700, 4050, 5400, 8100, 10800, 12150, 13500, 16200,\n+\t\t  18000, 0 },\n+\t\t{ 2925, 5850, 8775, 11700, 17550, 23400, 26325, 29250, 35100,\n+\t\t  39000, 0 },\n+\t};\n+\n+\tstatic const u32 s1g_tpt_sgi[4][11] = {\n+\t\t{ 333, 666, 1000, 1333, 2000, 2666, 3000, 3333, 4000, 4444,\n+\t\t  166 },\n+\t\t{ 722, 1444, 2166, 2888, 4333, 5777, 6500, 7222, 8666, 0, 0 },\n+\t\t{ 1500, 3000, 4500, 6000, 9000, 12000, 13500, 15000, 18000,\n+\t\t  20000, 0 },\n+\t\t{ 3250, 6500, 9750, 13000, 19500, 26000, 29250, 32500, 39000,\n+\t\t  43333, 0 },\n+\t};\n+\n+\tif (rate.guard)\n+\t\treturn s1g_tpt_sgi[rate.bw][rate.rate] * 1000 * (rate.ss + 1);\n+\n+\treturn s1g_tpt_lgi[rate.bw][rate.rate] * 1000 * (rate.ss + 1);\n+}\n+\n+static u32 calculate_throughput(struct mmrc_table *tb, u8 index)\n+{\n+\tstruct mmrc_rate rate = get_rate_row(tb, index);\n+\n+\t/*\n+\t * Avoid the overflow (observed for 8MHz MCS9 rate: 43333) by dividing\n+\t * first before multiplying. Should not experience any loss of\n+\t * precision as the throughput is already multiplied by 1000 in\n+\t * mmrc_calculate_theoretical_throughput (returned as bits/sec)\n+\t */\n+\tif (tb-\u003etable[rate.index].prob \u003c 10)\n+\t\treturn 0;\n+\telse if (rate.index == tb-\u003ebest_tp.index \u0026\u0026 tb-\u003einterference_likely)\n+\t\t/*\n+\t\t * Assist the best rate by increasing the probability by the\n+\t\t * averaged variation\n+\t\t */\n+\t\treturn (mmrc_calculate_theoretical_throughput(rate) / 100) *\n+\t\t       (tb-\u003etable[rate.index].prob + tb-\u003eprobability_variation);\n+\telse\n+\t\treturn (mmrc_calculate_theoretical_throughput(rate) / 100) *\n+\t\t       tb-\u003etable[rate.index].prob;\n+}\n+\n+static bool validate_rate(struct mmrc_table *tb, struct mmrc_rate *rate)\n+{\n+\tif (rate-\u003erate == MMRC_MCS10 \u0026\u0026\n+\t    (rate-\u003ebw != MMRC_BW_1MHZ || rate-\u003ess != MMRC_SPATIAL_STREAM_1)) {\n+\t\t/*\n+\t\t * 802.11ah does not support MCS10 with BW that is not 1MHz or\n+\t\t * not 1 spatial stream.\n+\t\t */\n+\t\treturn false;\n+\t}\n+\n+\tif (rate-\u003erate == MMRC_MCS9 \u0026\u0026 rate-\u003ebw == MMRC_BW_2MHZ \u0026\u0026\n+\t    rate-\u003ess != MMRC_SPATIAL_STREAM_3) {\n+\t\t/*\n+\t\t * 802.11ah does not support MCS9 at 2MHz for 1, 2 or 4 spatial\n+\t\t * streams\n+\t\t */\n+\t\treturn false;\n+\t}\n+\n+\tif (rate-\u003eguard == MMRC_GUARD_SHORT \u0026\u0026\n+\t    !(tb-\u003ecaps.sgi_per_bw \u0026 SGI_PER_BW(rate-\u003ebw)))\n+\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+static u16 find_baseline_index(struct mmrc_table *tb)\n+{\n+\tu32 i, theoretical_tp, min_theoretical_tp;\n+\tu16 row_count = rows_from_sta_caps(\u0026tb-\u003ecaps);\n+\tu16 min_theoretical_tp_index = 0;\n+\tstruct mmrc_rate rate;\n+\n+\tif (tb-\u003ecaps.rates \u0026 BIT(MMRC_MCS10))\n+\t\treturn 0;\n+\n+\tmin_theoretical_tp =\n+\t\tmmrc_calculate_theoretical_throughput(get_rate_row(tb, 0));\n+\tfor (i = 0; i \u003c row_count; i++) {\n+\t\trate = get_rate_row(tb, i);\n+\t\tif (!validate_rate(tb, \u0026rate))\n+\t\t\tcontinue;\n+\n+\t\ttheoretical_tp = mmrc_calculate_theoretical_throughput(rate);\n+\t\tif (min_theoretical_tp \u003e theoretical_tp) {\n+\t\t\tmin_theoretical_tp = theoretical_tp;\n+\t\t\tmin_theoretical_tp_index = rate.index;\n+\t\t}\n+\t}\n+\n+\treturn min_theoretical_tp_index;\n+}\n+\n+/*\n+ * Fill out the remaining rates to be used once the best rate is selected.\n+ * Normally the retry rates are one MCS lower than the previous, however in\n+ * unconverged mode we limit the 3 respective retry rates to MCS 4, 2 and 0\n+ * respectively. The last retry rate is always MCS 0\n+ */\n+static void mmrc_fill_retry_rates(struct mmrc_table *tb)\n+{\n+\ttb-\u003esecond_tp = tb-\u003ebest_tp;\n+\tif (tb-\u003esecond_tp.rate != MMRC_MCS0) {\n+\t\ttb-\u003esecond_tp.rate--;\n+\t\tif (tb-\u003eunconverged \u0026\u0026 tb-\u003esecond_tp.rate \u003e MMRC_MCS4)\n+\t\t\ttb-\u003esecond_tp.rate = MMRC_MCS4;\n+\t\trate_update_index(tb, \u0026tb-\u003esecond_tp);\n+\t} else if (tb-\u003esecond_tp.bw \u003e MMRC_BW_1MHZ) {\n+\t\ttb-\u003esecond_tp.bw--;\n+\t\trate_update_index(tb, \u0026tb-\u003esecond_tp);\n+\t}\n+\n+\ttb-\u003ebest_prob = tb-\u003esecond_tp;\n+\tif (tb-\u003ebest_prob.rate != MMRC_MCS0) {\n+\t\ttb-\u003ebest_prob.rate--;\n+\t\tif (tb-\u003eunconverged \u0026\u0026 tb-\u003ebest_prob.rate \u003e MMRC_MCS2)\n+\t\t\ttb-\u003ebest_prob.rate = MMRC_MCS2;\n+\t\trate_update_index(tb, \u0026tb-\u003ebest_prob);\n+\t} else if (tb-\u003ebest_prob.bw \u003e MMRC_BW_1MHZ) {\n+\t\ttb-\u003ebest_prob.bw--;\n+\t\trate_update_index(tb, \u0026tb-\u003ebest_prob);\n+\t}\n+\n+\ttb-\u003ebaseline = tb-\u003ebest_prob;\n+\tif (tb-\u003ebaseline.rate != MMRC_MCS0) {\n+\t\ttb-\u003ebaseline.rate = MMRC_MCS0;\n+\t\trate_update_index(tb, \u0026tb-\u003ebaseline);\n+\t} else if (tb-\u003ebaseline.bw \u003e MMRC_BW_1MHZ) {\n+\t\ttb-\u003ebaseline.bw--;\n+\t\trate_update_index(tb, \u0026tb-\u003ebaseline);\n+\t}\n+}\n+\n+/*\n+ * Updates the mmrc_table with the appropriate rate priority based on the\n+ * latest update statistics\n+ */\n+static void generate_table_priority(struct mmrc_table *tb, u32 new_stats)\n+{\n+\tu16 i;\n+\tu16 best_row = tb-\u003ebest_tp.index;\n+\tu16 prev_best_row = best_row;\n+\tu8 prev_best_rate = tb-\u003ebest_tp.rate;\n+\tu16 second_best_row = tb-\u003esecond_tp.index;\n+\tu32 best_tp = calculate_throughput(tb, best_row);\n+\tu32 second_best_tp = calculate_throughput(tb, second_best_row);\n+\tu32 last_nonzero_prob = 0;\n+\tstruct mmrc_rate tmp;\n+\tu32 tmp_tp;\n+\n+\t/* Use fixed rate if set */\n+\tif (tb-\u003efixed_rate.rate != MMRC_MCS_UNUSED) {\n+\t\ttb-\u003ebest_tp = tb-\u003efixed_rate;\n+\t\ttb-\u003esecond_tp = tb-\u003efixed_rate;\n+\t\ttb-\u003ebest_prob = tb-\u003efixed_rate;\n+\t\treturn;\n+\t}\n+\n+\tfor (i = 0; i \u003c rows_from_sta_caps(\u0026tb-\u003ecaps); i++) {\n+\t\ttmp = get_rate_row(tb, i);\n+\t\tif (!validate_rate(tb, \u0026tmp))\n+\t\t\tcontinue;\n+\n+\t\tif (tb-\u003etable[tmp.index].evidence == 0)\n+\t\t\tcontinue;\n+\n+\t\t/*\n+\t\t * Besides better throughput, also consider this rate better if\n+\t\t * lower rates had worse probability. That indicates the rate\n+\t\t * itself is not the problem. Only do the probability check for\n+\t\t * rates up to the previous best rate.\n+\t\t */\n+\t\ttmp_tp = calculate_throughput(tb, tmp.index);\n+\n+\t\tif (tmp_tp \u003e best_tp ||\n+\t\t    (tb-\u003etable[tmp.index].max_throughput \u003c=\n+\t\t\t     tb-\u003etable[prev_best_row].max_throughput \u0026\u0026\n+\t\t     tb-\u003etable[tmp.index].prob \u003e=\n+\t\t\t     PROBABILITY_DIP_RECOVERY_MIN \u0026\u0026\n+\t\t     tb-\u003etable[tmp.index].prob \u003e\n+\t\t\t     tb-\u003etable[last_nonzero_prob].prob)) {\n+\t\t\tsecond_best_row = best_row;\n+\t\t\tsecond_best_tp = best_tp;\n+\n+\t\t\tbest_tp = tmp_tp;\n+\t\t\tbest_row = tmp.index;\n+\t\t} else if (tmp_tp \u003e second_best_tp \u0026\u0026 best_row != tmp.index) {\n+\t\t\tsecond_best_tp = tmp_tp;\n+\t\t\tsecond_best_row = tmp.index;\n+\t\t}\n+\n+\t\tif (tb-\u003etable[tmp.index].prob \u003e= PROBABILITY_DIP_MIN \u0026\u0026\n+\t\t    tb-\u003etable[tmp.index].max_throughput \u003e=\n+\t\t\t    tb-\u003etable[last_nonzero_prob].max_throughput)\n+\t\t\tlast_nonzero_prob = tmp.index;\n+\t}\n+\n+\t/* Only update rates and stability when there are new statistics */\n+\tif (!new_stats)\n+\t\treturn;\n+\n+\ttb-\u003ebest_tp = get_rate_row(tb, best_row);\n+\tif (best_tp == 0 \u0026\u0026 tb-\u003ebest_tp.rate \u003e MMRC_MCS0) {\n+\t\t/* Drop one rate, as the best throughput is zero */\n+\t\ttb-\u003ebest_tp.rate--;\n+\t\trate_update_index(tb, \u0026tb-\u003ebest_tp);\n+\t}\n+\ttb-\u003esecond_tp = get_rate_row(tb, second_best_row);\n+\tmmrc_fill_retry_rates(tb);\n+\n+\tif (tb-\u003ebest_tp.rate \u003e MMRC_MCS1 \u0026\u0026 prev_best_row == best_row) {\n+\t\t/* Increase the counter when the best rate is not changed */\n+\t\ttb-\u003estability_cnt++;\n+\t} else if (tb-\u003estability_cnt \u003e STABILITY_BACKOFF_STEP) {\n+\t\t/* Back off the counter when there is a new best rate */\n+\t\ttb-\u003estability_cnt -= STABILITY_BACKOFF_STEP;\n+\t} else {\n+\t\ttb-\u003estability_cnt = 0;\n+\t}\n+\n+\tif (prev_best_row != best_row) {\n+\t\ts8 latest_best_rate_diff = prev_best_rate - tb-\u003ebest_tp.rate;\n+\t\tu8 total_abs_best_rate_diff =\n+\t\t\tabs(tb-\u003ebest_rate_diff[0] + tb-\u003ebest_rate_diff[1] +\n+\t\t\t    latest_best_rate_diff);\n+\n+\t\tif (!tb-\u003einterference_likely) {\n+\t\t\ttb-\u003eprobability_variation = 0;\n+\t\t\tif (!tb-\u003eunconverged \u0026\u0026\n+\t\t\t    tb-\u003ebest_rate_cycle_count \u003c=\n+\t\t\t\t    BEST_RATE_UNSTABLE_THRESHOLD \u0026\u0026\n+\t\t\t    total_abs_best_rate_diff \u003e= 2) {\n+\t\t\t\t/*\n+\t\t\t\t * Best rate has changed twice in a few cycles\n+\t\t\t\t * and moved at least 2 MCSs from where it was\n+\t\t\t\t * 3 best rate changes ago\n+\t\t\t\t */\n+\t\t\t\ttb-\u003eunconverged = true;\n+\t\t\t\ttb-\u003enewly_unconverged = true;\n+\t\t\t}\n+\t\t}\n+\t\tif (tb-\u003eunconverged \u0026\u0026 !tb-\u003enewly_unconverged \u0026\u0026\n+\t\t    total_abs_best_rate_diff \u003c 2) {\n+\t\t\t/*\n+\t\t\t * Best rate has been relatively stable (not moved more\n+\t\t\t * than 1 MCS after the last 3 rate changes), go back\n+\t\t\t * to converged\n+\t\t\t */\n+\t\t\ttb-\u003eunconverged = false;\n+\t\t}\n+\t\ttb-\u003eprobability_variation_direction = 0;\n+\t\ttb-\u003ebest_rate_cycle_count = 0;\n+\t\ttb-\u003ebest_rate_diff[0] = tb-\u003ebest_rate_diff[1];\n+\t\ttb-\u003ebest_rate_diff[1] = latest_best_rate_diff;\n+\t} else {\n+\t\ttb-\u003ebest_rate_cycle_count++;\n+\t\tif (tb-\u003eunconverged \u0026\u0026 !tb-\u003enewly_unconverged \u0026\u0026\n+\t\t    tb-\u003ebest_rate_cycle_count \u003e=\n+\t\t\t    BEST_RATE_CONVERGED_THRESHOLD) {\n+\t\t\t/*\n+\t\t\t * Best rate has been stable for a while, go back to\n+\t\t\t * converged\n+\t\t\t */\n+\t\t\ttb-\u003eunconverged = false;\n+\t\t}\n+\t}\n+\n+\tif (tb-\u003enewly_unconverged)\n+\t\ttb-\u003enewly_unconverged = false;\n+}\n+\n+static u32 calculate_attempt_time(struct mmrc_rate *rate, size_t size)\n+{\n+\tu32 time;\n+\n+\ttime = get_tx_time(rate);\n+\n+\tif (size \u003e DEFAULT_PACKET_SIZE_BYTES)\n+\t\ttime = (time * ((size * 1000) / DEFAULT_PACKET_SIZE_BYTES)) /\n+\t\t       1000;\n+\telse\n+\t\ttime = (time * 1000) /\n+\t\t       ((DEFAULT_PACKET_SIZE_BYTES * 1000) / size);\n+\n+\treturn time;\n+}\n+\n+u32 mmrc_calculate_rate_tx_time(struct mmrc_rate *rate, size_t size)\n+{\n+\tu8 i;\n+\tu32 total_time = 0;\n+\n+\tfor (i = 0; i \u003c rate-\u003eattempts; i++)\n+\t\ttotal_time += calculate_attempt_time(rate, size);\n+\n+\treturn total_time;\n+}\n+\n+/*\n+ * Calculates the appropriate amount of additional attempts to make based on\n+ * packet size and theoretical throughput.\n+ */\n+static void calculate_remaining_attempts(struct mmrc_table *tb,\n+\t\t\t\t\t struct mmrc_rate_table *rate,\n+\t\t\t\t\t s32 *rem_time, size_t size)\n+{\n+\tsize_t i;\n+\n+\tif (*rem_time \u003c= 0)\n+\t\treturn;\n+\n+\tfor (i = 0; i \u003c MMRC_MAX_CHAIN_LENGTH; i++) {\n+\t\tu32 attempt_time;\n+\t\tu32 attempt;\n+\n+\t\tif (rate-\u003erates[i].rate == MMRC_MCS_UNUSED)\n+\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * The attempts for these rates were calculated in the initial\n+\t\t * attempt allocation\n+\t\t */\n+\t\tif (tb-\u003etable[rate-\u003erates[i].index].prob \u003c 20)\n+\t\t\tcontinue;\n+\n+\t\tif (i == 0 \u0026\u0026 (calculate_throughput(tb, rate-\u003erates[i].index) \u003c\n+\t\t\t       calculate_throughput(tb, tb-\u003ebest_prob.index)))\n+\t\t\tcontinue;\n+\n+\t\tattempt_time = calculate_attempt_time(\u0026rate-\u003erates[i], size);\n+\t\tif (!attempt_time)\n+\t\t\tcontinue;\n+\n+\t\tattempt = (*rem_time / tb-\u003ecaps.max_rates) / attempt_time;\n+\t\tattempt += rate-\u003erates[i].attempts;\n+\n+\t\trate-\u003erates[i].attempts = MMRC_ATTEMPTS_TO_BITFIELD(\n+\t\t\tattempt \u003e MMRC_MAX_CHAIN_ATTEMPTS ?\n+\t\t\t\tMMRC_MAX_CHAIN_ATTEMPTS :\n+\t\t\t\tattempt);\n+\t}\n+}\n+\n+/* Allocate initial attempts to all rates in a rate table */\n+static void allocate_initial_attempts(struct mmrc_rate_table *rate,\n+\t\t\t\t      s32 *rem_time, size_t size)\n+{\n+\tu32 i;\n+\n+\tfor (i = 0; i \u003c MMRC_MAX_CHAIN_LENGTH; i++) {\n+\t\tu32 attempt_time;\n+\n+\t\tif (rate-\u003erates[i].rate == MMRC_MCS_UNUSED)\n+\t\t\tbreak;\n+\n+\t\tattempt_time = calculate_attempt_time(\u0026rate-\u003erates[i], size);\n+\n+\t\t/*\n+\t\t * if the time for a single attempt is very long, lets just\n+\t\t * try once\n+\t\t */\n+\t\tif (attempt_time \u003e MAX_WINDOW_ATTEMPT_TIME) {\n+\t\t\t*rem_time -= attempt_time;\n+\t\t\trate-\u003erates[i].attempts = MMRC_ATTEMPTS_TO_BITFIELD(1);\n+\t\t} else {\n+\t\t\t*rem_time -= attempt_time * 2;\n+\t\t\trate-\u003erates[i].attempts = MMRC_ATTEMPTS_TO_BITFIELD(2);\n+\t\t}\n+\t}\n+}\n+\n+void mmrc_get_rates(struct mmrc_table *tb, struct mmrc_rate_table *out,\n+\t\t    size_t size)\n+{\n+\tu8 i;\n+\tu16 random_index;\n+\tstruct mmrc_rate random;\n+\tstruct mmrc_rate lookaround0 = tb-\u003ebest_tp;\n+\tstruct mmrc_rate lookaround1 = tb-\u003esecond_tp;\n+\tbool is_lookaround;\n+\tint lookaround_index = -1;\n+\tint best_index = 0;\n+\tint random_tp = 0;\n+\tint best_tp;\n+\tint lookaround_fail_count;\n+\tbool try_current_lookaround = false;\n+\n+\ts32 rem_time = RATE_WINDOW_MICROSECONDS;\n+\n+\tmemset(out, 0, sizeof(*out));\n+\n+\ttb-\u003elookaround_cnt = (tb-\u003elookaround_cnt + 1) % tb-\u003elookaround_wrap;\n+\t/*\n+\t * Look around if the counter wraps or there has been no look around\n+\t * for a number of rate control cycles.\n+\t */\n+\tis_lookaround = (tb-\u003efixed_rate.rate == MMRC_MCS_UNUSED) \u0026\u0026\n+\t\t\t((tb-\u003elookaround_cnt == 0) ||\n+\t\t\t ((tb-\u003elast_lookaround_cycle +\n+\t\t\t   LOOKAROUND_MAX_RC_CYCLES) \u003c= tb-\u003ecycle_cnt));\n+\n+\t/* Also skip sampling if we don't yet have data for our best rate */\n+\tif (tb-\u003etable[tb-\u003ebest_tp.index].evidence == 0)\n+\t\tis_lookaround = false;\n+\n+\tif (tb-\u003elookaround_wrap != LOOKAROUND_RATE_STABLE) {\n+\t\tif (tb-\u003estability_cnt \u003e= tb-\u003estability_cnt_threshold) {\n+\t\t\ttb-\u003elookaround_wrap = LOOKAROUND_RATE_STABLE;\n+\t\t\ttb-\u003estability_cnt_threshold =\n+\t\t\t\tSTABILITY_CNT_THRESHOLD_STABLE;\n+\t\t\ttb-\u003estability_cnt = STABILITY_CNT_THRESHOLD_STABLE * 2;\n+\t\t\tis_lookaround = false;\n+\t\t}\n+\t} else if (tb-\u003estability_cnt \u003c tb-\u003estability_cnt_threshold) {\n+\t\ttb-\u003estability_cnt_threshold = STABILITY_CNT_THRESHOLD_NORMAL;\n+\t\ttb-\u003elookaround_wrap = LOOKAROUND_RATE_NORMAL;\n+\t\ttb-\u003estability_cnt = 0;\n+\t}\n+\n+\t/* Look around only when the fixed rate is not set */\n+\tif (is_lookaround) {\n+\t\ttb-\u003etotal_lookaround++;\n+\t\ttb-\u003eforced_lookaround =\n+\t\t\t(tb-\u003eforced_lookaround + 1) % LOOKAROUND_RATE_NORMAL;\n+\t\ttb-\u003elast_lookaround_cycle = tb-\u003ecycle_cnt;\n+\n+\t\tif (tb-\u003ecurrent_lookaround_rate_attempts \u003c\n+\t\t    LOOKAROUND_RATE_ATTEMPTS)\n+\t\t\ttry_current_lookaround = true;\n+\n+\t\tbest_tp = calculate_throughput(tb, tb-\u003ebest_tp.index);\n+\n+\t\tfor (lookaround_fail_count = 0;\n+\t\t     lookaround_fail_count \u003c LOOKAROUND_FAIL_MAX;\n+\t\t     lookaround_fail_count++) {\n+\t\t\tif (try_current_lookaround) {\n+\t\t\t\trandom_index =\n+\t\t\t\t\ttb-\u003ecurrent_lookaround_rate_index;\n+\t\t\t\ttry_current_lookaround = false;\n+\t\t\t} else {\n+\t\t\t\trandom_index = get_random_u32_below(\n+\t\t\t\t\trows_from_sta_caps(\u0026tb-\u003ecaps));\n+\t\t\t}\n+\t\t\trandom = get_rate_row(tb, random_index);\n+\n+\t\t\tif (!validate_rate(tb, \u0026random))\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (random.rate == MMRC_MCS10)\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (tb-\u003etable[random_index].evidence \u003e 0)\n+\t\t\t\trandom_tp =\n+\t\t\t\t\tcalculate_throughput(tb, random_index);\n+\t\t\telse\n+\t\t\t\trandom_tp =\n+\t\t\t\t\tmmrc_calculate_theoretical_throughput(\n+\t\t\t\t\t\trandom);\n+\n+\t\t\t/*\n+\t\t\t * Skip rates that can only be worse than the current\n+\t\t\t * best\n+\t\t\t */\n+\t\t\tif (random_tp \u003c= best_tp)\n+\t\t\t\tcontinue;\n+\n+\t\t\t/*\n+\t\t\t * Force looking up the rate no more that one MCS.\n+\t\t\t * It will avoid looking for rates with very low\n+\t\t\t * success rate. In case of better environment\n+\t\t\t * conditions MMRC will collect enough statistics to\n+\t\t\t * climb up the rates one by one.\n+\t\t\t */\n+\t\t\tif (random.rate \u003e tb-\u003ebest_tp.rate + 1 ||\n+\t\t\t    random.bw \u003e tb-\u003ebest_tp.bw + 1 ||\n+\t\t\t    (random.rate \u003e tb-\u003ebest_tp.rate \u0026\u0026\n+\t\t\t     random.bw \u003e tb-\u003ebest_tp.bw))\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (tb-\u003ecurrent_lookaround_rate_index == random_index) {\n+\t\t\t\ttb-\u003ecurrent_lookaround_rate_attempts++;\n+\t\t\t} else {\n+\t\t\t\ttb-\u003ecurrent_lookaround_rate_attempts = 0;\n+\t\t\t\ttb-\u003ecurrent_lookaround_rate_index =\n+\t\t\t\t\trandom_index;\n+\t\t\t}\n+\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (lookaround_fail_count \u003e= LOOKAROUND_FAIL_MAX) {\n+\t\t\tis_lookaround = false;\n+\t\t\ttb-\u003ecurrent_lookaround_rate_index = tb-\u003ebest_tp.index;\n+\t\t} else {\n+\t\t\tlookaround0 = random;\n+\t\t\tlookaround1 = tb-\u003ebest_tp;\n+\t\t\tlookaround_index = 0;\n+\t\t\tbest_index = 1;\n+\t\t}\n+\t}\n+\n+\tif (tb-\u003ecaps.max_rates == 1) {\n+\t\tout-\u003erates[0] = (is_lookaround) ? lookaround0 : tb-\u003ebest_tp;\n+\t\tout-\u003erates[1].rate = MMRC_MCS_UNUSED;\n+\t\tout-\u003erates[2].rate = MMRC_MCS_UNUSED;\n+\t\tout-\u003erates[3].rate = MMRC_MCS_UNUSED;\n+\t} else if (tb-\u003ecaps.max_rates == 2) {\n+\t\tout-\u003erates[0] = (is_lookaround) ? lookaround0 : tb-\u003ebest_tp;\n+\t\tout-\u003erates[1] = (is_lookaround) ? lookaround1 : tb-\u003ebest_prob;\n+\t\tout-\u003erates[2].rate = MMRC_MCS_UNUSED;\n+\t\tout-\u003erates[3].rate = MMRC_MCS_UNUSED;\n+\t} else if (tb-\u003ecaps.max_rates == 3) {\n+\t\tout-\u003erates[0] = (is_lookaround) ? lookaround0 : tb-\u003ebest_tp;\n+\t\tout-\u003erates[1] = (is_lookaround) ? lookaround1 : tb-\u003esecond_tp;\n+\t\tout-\u003erates[2] = tb-\u003ebest_prob;\n+\t\tout-\u003erates[3].rate = MMRC_MCS_UNUSED;\n+\t} else {\n+\t\tout-\u003erates[0] = (is_lookaround) ? lookaround0 : tb-\u003ebest_tp;\n+\t\tout-\u003erates[1] = (is_lookaround) ? lookaround1 : tb-\u003esecond_tp;\n+\t\tout-\u003erates[2] = tb-\u003ebest_prob;\n+\t\tout-\u003erates[3] = tb-\u003ebaseline;\n+\t}\n+\n+\t/* For fallback rates, set RTS/CTS */\n+\tfor (i = 1; i \u003c MMRC_MAX_CHAIN_LENGTH; i++)\n+\t\tout-\u003erates[i].flags |= BIT(MMRC_FLAGS_CTS_RTS);\n+\n+\t/* Allocate initial attempts for rate */\n+\tallocate_initial_attempts(out, \u0026rem_time, size);\n+\n+\t/* Calculate and allocate remaining attempts */\n+\tcalculate_remaining_attempts(tb, out, \u0026rem_time, size);\n+\n+\t/* Enforce limits on each attempts */\n+\tfor (i = 0; i \u003c MMRC_MAX_CHAIN_LENGTH; i++) {\n+\t\tif (out-\u003erates[i].rate != MMRC_MCS_UNUSED) {\n+\t\t\tout-\u003erates[i].attempts =\n+\t\t\t\tout-\u003erates[i].attempts == 0 ?\n+\t\t\t\t\tMMRC_ATTEMPTS_TO_BITFIELD(\n+\t\t\t\t\t\tMMRC_MIN_CHAIN_ATTEMPTS) :\n+\t\t\t\t\tout-\u003erates[i].attempts;\n+\t\t\tout-\u003erates[i].attempts =\n+\t\t\t\tout-\u003erates[i].attempts \u003e\n+\t\t\t\t\t\tMMRC_MAX_CHAIN_ATTEMPTS ?\n+\t\t\t\t\tMMRC_ATTEMPTS_TO_BITFIELD(\n+\t\t\t\t\t\tMMRC_MAX_CHAIN_ATTEMPTS) :\n+\t\t\t\t\tout-\u003erates[i].attempts;\n+\t\t\tif (i == lookaround_index \u0026\u0026\n+\t\t\t    tb-\u003elookaround_wrap != LOOKAROUND_RATE_INIT)\n+\t\t\t\tout-\u003erates[i].attempts =\n+\t\t\t\t\tMMRC_ATTEMPTS_TO_BITFIELD(1);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Give the best rate at least 2 attempts to keep peak throughput\n+\t * unless it is too low\n+\t */\n+\tif (out-\u003erates[best_index].attempts == 1 \u0026\u0026\n+\t    out-\u003erates[best_index].rate \u003e MMRC_MCS1)\n+\t\tout-\u003erates[best_index].attempts = MMRC_ATTEMPTS_TO_BITFIELD(2);\n+\telse if (out-\u003erates[best_index].rate \u003c= MMRC_MCS1)\n+\t\tout-\u003erates[best_index].attempts = 1;\n+}\n+\n+static u32 calc_ewma_average(u32 avg, u32 latest, u32 weight)\n+{\n+\tWARN_ON_ONCE(!(weight \u003c= 100));\n+\n+\tif (avg == 0)\n+\t\treturn latest;\n+\n+\treturn ((latest * (100 - weight)) + (avg * weight)) / 100;\n+}\n+\n+static void mmrc_process_variation(struct mmrc_table *tb, u16 current_success,\n+\t\t\t\t   u32 index)\n+{\n+\tu32 current_variation;\n+\n+\t/*\n+\t * Only process probability variation for the best rate. It is likely\n+\t * the only rate to have enough data to see the variation and its\n+\t * statistics are more affected because they are usually collected over\n+\t * the full period.\n+\t */\n+\tif (index != tb-\u003ebest_tp.index)\n+\t\treturn;\n+\n+\tif (current_success == 0) {\n+\t\tif (!tb-\u003eunconverged) {\n+\t\t\t/*\n+\t\t\t * Best rate is failing completely, go to unconverged\n+\t\t\t * mode\n+\t\t\t */\n+\t\t\ttb-\u003eunconverged = true;\n+\t\t\ttb-\u003enewly_unconverged = true;\n+\t\t}\n+\t\treturn;\n+\t}\n+\n+\tif (tb-\u003etable[index].prob == 0)\n+\t\treturn;\n+\n+\t/* Don't process variation while converging after association */\n+\tif (tb-\u003elookaround_wrap == LOOKAROUND_RATE_INIT)\n+\t\treturn;\n+\n+\tcurrent_variation = abs(current_success - tb-\u003etable[index].prob);\n+\n+\t/* Calculate the EWMA of the probability variation */\n+\ttb-\u003eprobability_variation = calc_ewma_average(\n+\t\ttb-\u003eprobability_variation, current_variation, VARIATION_EWMA);\n+\n+\t/*\n+\t * Process the variation direction to distinguish converged and\n+\t * unconverged scenarios\n+\t */\n+\tif (tb-\u003eprobability_variation \u003e= MODERATE_VARIATION_THRESHOLD ||\n+\t    tb-\u003einterference_likely) {\n+\t\tif ((current_success - tb-\u003etable[index].prob) *\n+\t\t\t    tb-\u003eprobability_variation_direction \u003c\n+\t\t    0)\n+\t\t\ttb-\u003eprobability_variation_direction = 0;\n+\t\telse if (current_success \u003e tb-\u003etable[index].prob)\n+\t\t\ttb-\u003eprobability_variation_direction =\n+\t\t\t\tmin(tb-\u003eprobability_variation_direction + 1,\n+\t\t\t\t    MAX_VARIATION_DIRECTION);\n+\t\telse if (current_success \u003c tb-\u003etable[index].prob)\n+\t\t\ttb-\u003eprobability_variation_direction =\n+\t\t\t\tmax(tb-\u003eprobability_variation_direction - 1,\n+\t\t\t\t    -MAX_VARIATION_DIRECTION);\n+\t}\n+\n+\tif (tb-\u003ebest_rate_cycle_count \u003e VARIATION_DIRECTION_THRESHOLD \u0026\u0026\n+\t    tb-\u003eprobability_variation \u003e= SIGNIFICANT_VARIATION_THRESHOLD) {\n+\t\t/*\n+\t\t * Only enter interference mode if the best rate is stable for\n+\t\t * enough cycles to determine the direction is random and not\n+\t\t * in one direction only\n+\t\t */\n+\t\tif (abs(tb-\u003eprobability_variation_direction) \u003c=\n+\t\t\t    VARIATION_DIRECTION_THRESHOLD \u0026\u0026\n+\t\t    !tb-\u003einterference_likely) {\n+\t\t\ttb-\u003einterference_likely = true;\n+\t\t}\n+\t} else if (tb-\u003einterference_likely \u0026\u0026\n+\t\t   (tb-\u003eprobability_variation \u003c= MINOR_VARIATION_THRESHOLD ||\n+\t\t    abs(tb-\u003eprobability_variation_direction) ==\n+\t\t\t    MAX_VARIATION_DIRECTION)) {\n+\t\t/*\n+\t\t * Exit interference mode if the variability drops or the\n+\t\t * direction stops being random\n+\t\t */\n+\t\ttb-\u003einterference_likely = false;\n+\t}\n+}\n+\n+void mmrc_update(struct mmrc_table *tb)\n+{\n+\tu32 i;\n+\tu16 this_success;\n+\tu32 scale;\n+\tu32 scaled_ewma;\n+\tu32 new_stats = 0;\n+\tu32 attempts_for_stats;\n+\tu32 success_for_stats;\n+\tu32 min_stats;\n+\tu32 throughput;\n+\tu32 evidence_sent;\n+\n+\ttb-\u003ecycle_cnt++;\n+\n+\t/* Allow less minimum stats when converging */\n+\tif (tb-\u003elookaround_wrap != LOOKAROUND_RATE_INIT)\n+\t\tmin_stats = STATS_MIN_NORMAL;\n+\telse\n+\t\tmin_stats = STATS_MIN_INIT;\n+\n+\tfor (i = 0; i \u003c rows_from_sta_caps(\u0026tb-\u003ecaps); i++) {\n+\t\t/* This algorithm is keeping track of the amount of evidence,\n+\t\t * being packets that have been recently sent at this rate.\n+\t\t * This value is smoothed with an EWMA function over time and\n+\t\t * used to update the probability of a rate succeeding\n+\t\t * dynamically. This method allows MMRC to react timely if a\n+\t\t * new rate is used that hasn't been used recently\n+\t\t */\n+\n+\t\t/* Necessary to prevent a divide by 0 */\n+\t\tif (tb-\u003etable[i].evidence == 0)\n+\t\t\tscale = 0;\n+\t\telse\n+\t\t\tscale = ((tb-\u003etable[i].evidence * 2) * 100) /\n+\t\t\t\t((tb-\u003etable[i].sent * EVIDENCE_SCALE) +\n+\t\t\t\t tb-\u003etable[i].evidence);\n+\n+\t\t/* Restrict scale to appropriate values */\n+\t\tif (scale \u003e 100)\n+\t\t\tscale = 100;\n+\n+\t\tscaled_ewma = scale * EWMA / 100;\n+\n+\t\t/*\n+\t\t * Only count new packets for evidence if we will process\n+\t\t * them\n+\t\t */\n+\t\tevidence_sent =\n+\t\t\ttb-\u003etable[i].sent \u003e= min_stats ? tb-\u003etable[i].sent : 0;\n+\t\ttb-\u003etable[i].evidence = calc_ewma_average(\n+\t\t\ttb-\u003etable[i].evidence, evidence_sent * EVIDENCE_SCALE,\n+\t\t\tscaled_ewma);\n+\n+\t\tif (tb-\u003etable[i].evidence \u003e EVIDENCE_MAX)\n+\t\t\ttb-\u003etable[i].evidence = EVIDENCE_MAX;\n+\n+\t\t/* Try to use statistics from acknowledged AMPDUs first */\n+\t\tattempts_for_stats = tb-\u003etable[i].back_mpdu_success +\n+\t\t\t\t     tb-\u003etable[i].back_mpdu_failure;\n+\t\tsuccess_for_stats = tb-\u003etable[i].back_mpdu_success;\n+\n+\t\t/*\n+\t\t * Use the full statistics if rates are not converged or there\n+\t\t * were no AMPDUs for this rate or the remaining attempts are\n+\t\t * less than half of what we have from AMPDUs.\n+\t\t */\n+\t\tif (!tb-\u003etable[i].have_sent_ampdus || tb-\u003eunconverged ||\n+\t\t    attempts_for_stats \u003c AMPDU_STATS_MIN ||\n+\t\t    (tb-\u003etable[i].sent - attempts_for_stats \u003c\n+\t\t     attempts_for_stats / 2)) {\n+\t\t\tattempts_for_stats = tb-\u003etable[i].sent;\n+\t\t\tsuccess_for_stats = tb-\u003etable[i].sent_success;\n+\t\t}\n+\n+\t\tif (attempts_for_stats \u003e= min_stats ||\n+\t\t    (attempts_for_stats \u003e 0 \u0026\u0026 tb-\u003etable[i].prob \u003e 0)) {\n+\t\t\tnew_stats = 1;\n+\t\t\tthis_success =\n+\t\t\t\t(100 * success_for_stats) / attempts_for_stats;\n+\n+\t\t\tif (scaled_ewma)\n+\t\t\t\tmmrc_process_variation(tb, this_success, i);\n+\n+\t\t\ttb-\u003etable[i].prob = calc_ewma_average(\n+\t\t\t\ttb-\u003etable[i].prob, this_success, scaled_ewma);\n+\n+\t\t\t/* Clear our sent statistics and update totals */\n+\t\t\ttb-\u003etable[i].total_sent += tb-\u003etable[i].sent;\n+\t\t\ttb-\u003etable[i].sent = 0;\n+\n+\t\t\ttb-\u003etable[i].total_success += tb-\u003etable[i].sent_success;\n+\t\t\ttb-\u003etable[i].sent_success = 0;\n+\n+\t\t\ttb-\u003etable[i].back_mpdu_failure = 0;\n+\t\t\ttb-\u003etable[i].back_mpdu_success = 0;\n+\t\t\ttb-\u003etable[i].have_sent_ampdus = false;\n+\t\t}\n+\n+\t\tthroughput = calculate_throughput(tb, i);\n+\t\tif (tb-\u003etable[i].max_throughput \u003c throughput)\n+\t\t\ttb-\u003etable[i].max_throughput = throughput;\n+\n+\t\t/*\n+\t\t * Reset the running average windows if reached collector\n+\t\t * limits\n+\t\t */\n+\t\tif (tb-\u003etable[i].sum_throughput \u003e (0xFFFFFFFF - throughput)) {\n+\t\t\ttb-\u003etable[i].sum_throughput /=\n+\t\t\t\ttb-\u003etable[i].avg_throughput_counter;\n+\t\t\ttb-\u003etable[i].avg_throughput_counter = 1;\n+\t\t}\n+\t\t/* Update the sum and counter so it will be possible later to\n+\t\t * calculate the running average throughput\n+\t\t */\n+\t\ttb-\u003etable[i].sum_throughput += throughput;\n+\t\ttb-\u003etable[i].avg_throughput_counter++;\n+\t}\n+\n+\tgenerate_table_priority(tb, new_stats);\n+\n+\t/*\n+\t * Switch to faster lookaround mode if rates drop low at very low\n+\t * bandwidth or we are in unconverged mode. Switching at low bandwidth\n+\t * and rate is to help recover quickly from rates where we would need\n+\t * to fragment standard MTU size packets.\n+\t */\n+\tif (tb-\u003elookaround_wrap != LOOKAROUND_RATE_INIT \u0026\u0026\n+\t    (tb-\u003eunconverged || (tb-\u003ebest_tp.bw == MMRC_BW_1MHZ \u0026\u0026\n+\t\t\t\t tb-\u003ebest_tp.rate \u003c= MMRC_MCS2))) {\n+\t\ttb-\u003elookaround_cnt = 0;\n+\t\ttb-\u003elookaround_wrap = LOOKAROUND_RATE_INIT;\n+\t\ttb-\u003estability_cnt_threshold = STABILITY_CNT_THRESHOLD_INIT;\n+\t}\n+\n+\t/*\n+\t * If it is unlikely we can do the lookaround attempts in two RC cycles\n+\t * choose a new rate\n+\t */\n+\tif (tb-\u003ecurrent_lookaround_rate_attempts \u003c=\n+\t    (LOOKAROUND_RATE_ATTEMPTS / 2))\n+\t\ttb-\u003ecurrent_lookaround_rate_attempts = LOOKAROUND_RATE_ATTEMPTS;\n+}\n+\n+void mmrc_feedback(struct mmrc_table *tb, struct mmrc_rate_table *rates,\n+\t\t   s32 retry_count, bool was_aggregated)\n+{\n+\ts32 ind = retry_count;\n+\tu32 i;\n+\n+\tfor (i = 0; i \u003c MMRC_MAX_CHAIN_LENGTH; i++) {\n+\t\trate_update_index(tb, \u0026rates-\u003erates[i]);\n+\t\ttb-\u003etable[rates-\u003erates[i].index].have_sent_ampdus |=\n+\t\t\twas_aggregated;\n+\n+\t\tif ((s32)rates-\u003erates[i].attempts \u003c ind) {\n+\t\t\tind = ind - rates-\u003erates[i].attempts;\n+\t\t\ttb-\u003etable[rates-\u003erates[i].index].sent +=\n+\t\t\t\trates-\u003erates[i].attempts;\n+\t\t\tif (was_aggregated) {\n+\t\t\t\ttb-\u003etable[rates-\u003erates[i].index]\n+\t\t\t\t\t.back_mpdu_failure +=\n+\t\t\t\t\trates-\u003erates[i].attempts;\n+\t\t\t}\n+\t\t} else {\n+\t\t\ttb-\u003etable[rates-\u003erates[i].index].sent += ind;\n+\t\t\ttb-\u003etable[rates-\u003erates[i].index].sent_success += 1;\n+\t\t\tif (was_aggregated) {\n+\t\t\t\ttb-\u003etable[rates-\u003erates[i].index]\n+\t\t\t\t\t.back_mpdu_success += 1;\n+\t\t\t\ttb-\u003etable[rates-\u003erates[i].index]\n+\t\t\t\t\t.back_mpdu_failure +=\n+\t\t\t\t\tind \u003e 1 ? ind - 1 : 0;\n+\t\t\t}\n+\t\t\treturn;\n+\t\t}\n+\t}\n+}\n+\n+/*\n+ * Chooses a reasonable starting rate based on range (gathered from\n+ * RSSI measurements) or bandwidth. Then fills out the 3 retry rates\n+ * so a full set of rates is available.\n+ */\n+static void mmrc_init_rates(struct mmrc_table *tb, s8 rssi)\n+{\n+\ttb-\u003ebest_tp.bw = MMRC_MAX_BW(tb-\u003ecaps.bandwidth);\n+\tif (tb-\u003ecaps.sgi_per_bw \u0026 SGI_PER_BW(tb-\u003ebest_tp.bw))\n+\t\ttb-\u003ebest_tp.guard = MMRC_GUARD_TO_BITFIELD(MMRC_GUARD_SHORT);\n+\telse\n+\t\ttb-\u003ebest_tp.guard = MMRC_GUARD_TO_BITFIELD(MMRC_GUARD_LONG);\n+\ttb-\u003ebest_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS0);\n+\n+\tif (rssi \u003e= MMRC_SHORT_RANGE_RSSI_LIMIT)\n+\t\ttb-\u003ebest_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS7);\n+\telse if (rssi \u003c MMRC_SHORT_RANGE_RSSI_LIMIT \u0026\u0026\n+\t\t rssi \u003e= MMRC_MID_RANGE_RSSI_LIMIT)\n+\t\ttb-\u003ebest_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS3);\n+\telse if (tb-\u003ebest_tp.bw == MMRC_BW_1MHZ ||\n+\t\t tb-\u003ebest_tp.bw == MMRC_BW_2MHZ)\n+\t\t/*\n+\t\t * To compensate for slow feedback when running with 1 and 2\n+\t\t * MHz bandwidth, we start from MCS3 which will correspond to\n+\t\t * reasonable feedback and will avoid resetting the rate table\n+\t\t * evidence.\n+\t\t */\n+\t\ttb-\u003ebest_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS3);\n+\n+\ttb-\u003ebest_tp.ss = MMRC_SS_TO_BITFIELD(MMRC_SPATIAL_STREAM_1);\n+\trate_update_index(tb, \u0026tb-\u003ebest_tp);\n+\t/* Init every rate in case they are needed to set the retry rates */\n+\ttb-\u003esecond_tp = tb-\u003ebest_tp;\n+\ttb-\u003ebest_prob = tb-\u003ebest_tp;\n+\ttb-\u003ebaseline = tb-\u003ebest_tp;\n+\tmmrc_fill_retry_rates(tb);\n+}\n+\n+void mmrc_sta_init(struct mmrc_table *tb, struct mmrc_sta_capabilities *caps,\n+\t\t   s8 rssi)\n+{\n+\tu32 i;\n+\tu16 row_count = rows_from_sta_caps(caps);\n+\n+\tmemset(tb, 0, mmrc_memory_required_for_caps(caps));\n+\tmemcpy(\u0026tb-\u003ecaps, caps, sizeof(tb-\u003ecaps));\n+\n+\tfor (i = 0; i \u003c row_count; i++) {\n+\t\ttb-\u003etable[i].prob = RATE_INIT_PROBABILITY;\n+\t\ttb-\u003etable[i].evidence = 0;\n+\t\ttb-\u003etable[i].sum_throughput = 0;\n+\t\ttb-\u003etable[i].avg_throughput_counter = 0;\n+\t\ttb-\u003etable[i].max_throughput = 0;\n+\t}\n+\n+\ttb-\u003efixed_rate.rate = MMRC_MCS_UNUSED;\n+\ttb-\u003ecycle_cnt = 0;\n+\ttb-\u003elast_lookaround_cycle = 0;\n+\ttb-\u003elookaround_cnt = 0;\n+\ttb-\u003elookaround_wrap = LOOKAROUND_RATE_INIT;\n+\ttb-\u003eunconverged = true;\n+\ttb-\u003enewly_unconverged = true;\n+\ttb-\u003estability_cnt_threshold = STABILITY_CNT_THRESHOLD_INIT;\n+\ttb-\u003ebaseline = get_rate_row(tb, find_baseline_index(tb));\n+\tmmrc_init_rates(tb, rssi);\n+}\n+\n+bool mmrc_set_fixed_rate(struct mmrc_table *tb, struct mmrc_rate fixed_rate)\n+{\n+\tbool caps_support_rate = true;\n+\n+\t/* Do not accept rate which does not support the STA capabilities */\n+\tif ((BIT(fixed_rate.rate) \u0026 tb-\u003ecaps.rates) == 0 ||\n+\t    (BIT(fixed_rate.bw) \u0026 tb-\u003ecaps.bandwidth) == 0 ||\n+\t    (BIT(fixed_rate.ss) \u0026 tb-\u003ecaps.spatial_streams) == 0 ||\n+\t    (BIT(fixed_rate.guard) \u0026 tb-\u003ecaps.guard) == 0)\n+\t\tcaps_support_rate = false;\n+\n+\tif (validate_rate(tb, \u0026fixed_rate) \u0026\u0026 caps_support_rate) {\n+\t\ttb-\u003efixed_rate = fixed_rate;\n+\t\trate_update_index(tb, \u0026tb-\u003efixed_rate);\n+\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/mmrc.h b/drivers/net/wireless/morsemicro/mm81x/mmrc.h\nnew file mode 100644\nindex 00000000000000..a4c7d941ad55c1\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/mmrc.h\n@@ -0,0 +1,193 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#ifndef _MM81X_MMRC_H_\n+#define _MM81X_MMRC_H_\n+\n+#include \u003clinux/version.h\u003e\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/bitops.h\u003e\n+#include \u003clinux/random.h\u003e\n+#include \u003clinux/time.h\u003e\n+\n+/* The max length of a retry chain for a single packet transmission */\n+#define MMRC_MAX_CHAIN_LENGTH 4\n+\n+/* Rate minimum allowed attempts */\n+#define MMRC_MIN_CHAIN_ATTEMPTS 1\n+\n+/* Rate upper limit for attempts */\n+#define MMRC_MAX_CHAIN_ATTEMPTS 2\n+\n+/* The frequency of MMRC stat table updates */\n+#define MMRC_UPDATE_FREQUENCY_MS 100\n+\n+enum mmrc_flags {\n+\tMMRC_FLAGS_CTS_RTS,\n+};\n+\n+enum mmrc_mcs_rate {\n+\tMMRC_MCS0,\n+\tMMRC_MCS1,\n+\tMMRC_MCS2,\n+\tMMRC_MCS3,\n+\tMMRC_MCS4,\n+\tMMRC_MCS5,\n+\tMMRC_MCS6,\n+\tMMRC_MCS7,\n+\tMMRC_MCS8,\n+\tMMRC_MCS9,\n+\tMMRC_MCS10,\n+\tMMRC_MCS_UNUSED,\n+};\n+\n+enum mmrc_bw {\n+\tMMRC_BW_1MHZ = 0,\n+\tMMRC_BW_2MHZ = 1,\n+\tMMRC_BW_4MHZ = 2,\n+\tMMRC_BW_8MHZ = 3,\n+\tMMRC_BW_16MHZ = 4,\n+\tMMRC_BW_MAX = 5,\n+};\n+\n+enum mmrc_spatial_stream {\n+\tMMRC_SPATIAL_STREAM_1 = 0,\n+\tMMRC_SPATIAL_STREAM_2 = 1,\n+\tMMRC_SPATIAL_STREAM_3 = 2,\n+\tMMRC_SPATIAL_STREAM_4 = 3,\n+\tMMRC_SPATIAL_STREAM_MAX,\n+};\n+\n+enum mmrc_guard {\n+\tMMRC_GUARD_LONG = 0,\n+\tMMRC_GUARD_SHORT = 1,\n+\tMMRC_GUARD_MAX,\n+};\n+\n+#define MMRC_RATE_TO_BITFIELD(x) ((x) \u0026 0xF)\n+#define MMRC_ATTEMPTS_TO_BITFIELD(x) ((x) \u0026 0x7)\n+#define MMRC_GUARD_TO_BITFIELD(x) ((x) \u0026 0x1)\n+#define MMRC_SS_TO_BITFIELD(x) ((x) \u0026 0x3)\n+#define MMRC_BW_TO_BITFIELD(x) ((x) \u0026 0x7)\n+#define MMRC_FLAGS_TO_BITFIELD(x) ((x) \u0026 0x7)\n+\n+struct mmrc_rate {\n+\tu8 rate : 4;\n+\tu8 attempts : 3;\n+\tu8 guard : 1;\n+\tu8 ss : 2;\n+\tu8 bw : 3;\n+\tu8 flags : 3;\n+\tu16 index;\n+};\n+\n+struct mmrc_rate_table {\n+\tstruct mmrc_rate rates[MMRC_MAX_CHAIN_LENGTH];\n+};\n+\n+#define SGI_PER_BW(bw) (1 \u003c\u003c (bw))\n+\n+struct mmrc_sta_capabilities {\n+\tu8 max_rates : 3;\n+\tu8 max_retries : 3;\n+\tu8 bandwidth : 5;\n+\tu8 spatial_streams : 4;\n+\tu16 rates : 11;\n+\tu8 guard : 2;\n+\tu8 sta_flags : 4;\n+\tu8 sgi_per_bw : 5;\n+};\n+\n+struct mmrc_stats_table {\n+\tu32 avg_throughput_counter;\n+\tu32 sum_throughput;\n+\tu32 max_throughput;\n+\tu16 sent;\n+\tu16 sent_success;\n+\tu16 back_mpdu_success;\n+\tu16 back_mpdu_failure;\n+\tu32 total_sent;\n+\tu32 total_success;\n+\tu16 evidence;\n+\tu8 prob;\n+\tbool have_sent_ampdus;\n+};\n+\n+struct mmrc_table {\n+\tstruct mmrc_sta_capabilities caps;\n+\tstruct mmrc_rate best_tp;\n+\tstruct mmrc_rate second_tp;\n+\tstruct mmrc_rate baseline;\n+\tstruct mmrc_rate best_prob;\n+\tstruct mmrc_rate fixed_rate;\n+\tu32 cycle_cnt;\n+\tu32 last_lookaround_cycle;\n+\tu8 lookaround_cnt;\n+\n+\t/* The ratio of using normal rate and sampling */\n+\tu8 lookaround_wrap;\n+\n+\t/*\n+\t * A counter that is used to determine when we should force a\n+\t * lookaround. Should be a portion of the above lookaround with\n+\t * less constraints\n+\t */\n+\tu8 forced_lookaround;\n+\n+\tu8 current_lookaround_rate_attempts;\n+\tu16 current_lookaround_rate_index;\n+\tu32 total_lookaround;\n+\n+\t/*\n+\t * A counter to detect if the current best rate is optimal\n+\t * and may slow down sample frequency.\n+\t */\n+\tu32 stability_cnt;\n+\n+\tu32 stability_cnt_threshold;\n+\tu8 probability_variation;\n+\n+\t/* The difference in MCS from each of the last 2 rate changes */\n+\ts8 best_rate_diff[2];\n+\n+\t/* Indication of random versus consistently one-sided variation */\n+\ts8 probability_variation_direction;\n+\n+\t/* Has rate control detected possible interference */\n+\tbool interference_likely;\n+\n+\t/* Has rate control detected the best rate is no longer converged */\n+\tbool unconverged;\n+\n+\t/* Is rate control just entering unconverged state */\n+\tbool newly_unconverged;\n+\n+\t/*\n+\t * Number of rate control cycles the best rate has remained\n+\t * unchanged\n+\t */\n+\ts32 best_rate_cycle_count;\n+\n+\t/*\n+\t * The probability table for the STA. This MUST always be the last\n+\t * element in the struct.\n+\t */\n+\tstruct mmrc_stats_table table[];\n+};\n+\n+void mmrc_sta_init(struct mmrc_table *tb, struct mmrc_sta_capabilities *caps,\n+\t\t   s8 rssi);\n+size_t mmrc_memory_required_for_caps(struct mmrc_sta_capabilities *caps);\n+void mmrc_get_rates(struct mmrc_table *tb, struct mmrc_rate_table *out,\n+\t\t    size_t size);\n+void mmrc_feedback(struct mmrc_table *tb, struct mmrc_rate_table *rates,\n+\t\t   s32 retry_count, bool was_aggregated);\n+void mmrc_update(struct mmrc_table *tb);\n+bool mmrc_set_fixed_rate(struct mmrc_table *tb, struct mmrc_rate fixed_rate);\n+u32 mmrc_calculate_theoretical_throughput(struct mmrc_rate rate);\n+u32 mmrc_calculate_rate_tx_time(struct mmrc_rate *rate, size_t size);\n+\n+#endif /* _MMRC_H_ */\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/ps.c b/drivers/net/wireless/morsemicro/mm81x/ps.c\nnew file mode 100644\nindex 00000000000000..ab67823452eeb8\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/ps.c\n@@ -0,0 +1,120 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+#include \"hif.h\"\n+#include \"skbq.h\"\n+#include \"mac.h\"\n+#include \"bus.h\"\n+#include \"ps.h\"\n+\n+static void mm81x_ps_wakeup(struct mm81x_ps *mps)\n+{\n+\tstruct mm81x *mors = container_of(mps, struct mm81x, ps);\n+\n+\tif (!mps-\u003eenable || !mps-\u003esuspended)\n+\t\treturn;\n+\n+\tmm81x_set_bus_enable(mors, true);\n+\tmps-\u003esuspended = false;\n+}\n+\n+static void mm81x_ps_sleep(struct mm81x_ps *mps)\n+{\n+\tstruct mm81x *mors = container_of(mps, struct mm81x, ps);\n+\n+\tif (!mps-\u003eenable || mps-\u003esuspended)\n+\t\treturn;\n+\n+\tmps-\u003esuspended = true;\n+\tmm81x_set_bus_enable(mors, false);\n+}\n+\n+static void mm81x_ps_evaluate(struct mm81x_ps *mps)\n+{\n+\tstruct mm81x *mors = container_of(mps, struct mm81x, ps);\n+\tbool needs_wake = false;\n+\tunsigned long flags_on_entry =\n+\t\t(mors-\u003ehif.event_flags \u0026\n+\t\t ~BIT(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND));\n+\n+\tif (!mps-\u003eenable)\n+\t\treturn;\n+\n+\tneeds_wake = (mps-\u003ewakers \u003e 0);\n+\tneeds_wake |= (flags_on_entry \u003e 0);\n+\tneeds_wake |= (mm81x_hif_get_tx_buffered_count(mors) \u003e 0);\n+\n+\tif (needs_wake) {\n+\t\tmm81x_ps_wakeup(mps);\n+\t\treturn;\n+\t}\n+\n+\tmm81x_ps_sleep(mps);\n+}\n+\n+static void mm81x_ps_evaluate_work(struct work_struct *work)\n+{\n+\tstruct mm81x_ps *mps =\n+\t\tcontainer_of(work, struct mm81x_ps, delayed_eval_work.work);\n+\n+\tif (mps-\u003eenable) {\n+\t\tmutex_lock(\u0026mps-\u003elock);\n+\t\tmm81x_ps_evaluate(mps);\n+\t\tmutex_unlock(\u0026mps-\u003elock);\n+\t}\n+}\n+\n+void mm81x_ps_enable(struct mm81x *mors)\n+{\n+\tstruct mm81x_ps *mps = \u0026mors-\u003eps;\n+\n+\tif (mps-\u003eenable) {\n+\t\tmutex_lock(\u0026mps-\u003elock);\n+\t\tif (mps-\u003ewakers == 0) {\n+\t\t\tWARN_ON_ONCE(1);\n+\t\t} else {\n+\t\t\tmps-\u003ewakers--;\n+\t\t\tmm81x_ps_evaluate(mps);\n+\t\t}\n+\t\tmutex_unlock(\u0026mps-\u003elock);\n+\t}\n+}\n+\n+void mm81x_ps_disable(struct mm81x *mors)\n+{\n+\tstruct mm81x_ps *mps = \u0026mors-\u003eps;\n+\n+\tif (mps-\u003eenable) {\n+\t\tmutex_lock(\u0026mps-\u003elock);\n+\t\tmps-\u003ewakers++;\n+\t\tmm81x_ps_evaluate(mps);\n+\t\tmutex_unlock(\u0026mps-\u003elock);\n+\t}\n+}\n+\n+int mm81x_ps_init(struct mm81x *mors)\n+{\n+\tstruct mm81x_ps *mps = \u0026mors-\u003eps;\n+\n+\tmps-\u003eenable = (mors-\u003ebus_type == MM81X_BUS_TYPE_USB);\n+\tmps-\u003esuspended = true;\n+\tmps-\u003ewakers = 1; /* we default to being on */\n+\tmutex_init(\u0026mps-\u003elock);\n+\tINIT_DELAYED_WORK(\u0026mps-\u003edelayed_eval_work, mm81x_ps_evaluate_work);\n+\n+\treturn 0;\n+}\n+\n+void mm81x_ps_finish(struct mm81x *mors)\n+{\n+\tstruct mm81x_ps *mps = \u0026mors-\u003eps;\n+\n+\tif (mps-\u003eenable) {\n+\t\tmps-\u003eenable = false;\n+\t\tcancel_delayed_work_sync(\u0026mps-\u003edelayed_eval_work);\n+\t}\n+}\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/ps.h b/drivers/net/wireless/morsemicro/mm81x/ps.h\nnew file mode 100644\nindex 00000000000000..0b59bb4145ab9c\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/ps.h\n@@ -0,0 +1,22 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#ifndef _MM81X_PS_H_\n+#define _MM81X_PS_H_\n+\n+#include \"core.h\"\n+\n+/* This should be nominally \u003c= the dynamic ps timeout */\n+#define NETWORK_BUS_TIMEOUT_MS (90)\n+\n+/* The default period of time to wait to re-evaluate powersave */\n+#define DEFAULT_BUS_TIMEOUT_MS (50)\n+\n+void mm81x_ps_disable(struct mm81x *mors);\n+void mm81x_ps_enable(struct mm81x *mors);\n+int mm81x_ps_init(struct mm81x *mors);\n+void mm81x_ps_finish(struct mm81x *mors);\n+\n+#endif /* !_MM81X_PS_H_ */\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/rate_code.h b/drivers/net/wireless/morsemicro/mm81x/rate_code.h\nnew file mode 100644\nindex 00000000000000..c60fcb9447c4ab\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/rate_code.h\n@@ -0,0 +1,177 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#ifndef _MM81X_RATE_CODE_H_\n+#define _MM81X_RATE_CODE_H_\n+\n+#include \u003clinux/types.h\u003e\n+\n+enum dot11_bandwidth {\n+\tDOT11_BANDWIDTH_1MHZ = 0,\n+\tDOT11_BANDWIDTH_2MHZ = 1,\n+\tDOT11_BANDWIDTH_4MHZ = 2,\n+\tDOT11_BANDWIDTH_8MHZ = 3,\n+\tDOT11_BANDWIDTH_16MHZ = 4,\n+\n+\tDOT11_MAX_BANDWIDTH = DOT11_BANDWIDTH_16MHZ,\n+\tDOT11_INVALID_BANDWIDTH = 5\n+};\n+\n+enum mm81x_rate_preamble {\n+\t/* S1G LONG format (with SIG-A and SIG-B) */\n+\tMM81X_RATE_PREAMBLE_S1G_LONG = 0,\n+\t/* This is the most common format used */\n+\tMM81X_RATE_PREAMBLE_S1G_SHORT = 1,\n+\t/* S1G 1M format */\n+\tMM81X_RATE_PREAMBLE_S1G_1M = 2,\n+\n+\tMM81X_RATE_MAX_PREAMBLE = MM81X_RATE_PREAMBLE_S1G_1M,\n+\tMM81X_RATE_INVALID_PREAMBLE = 7\n+};\n+\n+typedef __le32 mm81x_rate_code_t;\n+\n+#define MM81X_RATECODE_PREAMBLE (0x0000000F)\n+#define MM81X_RATECODE_MCS_INDEX (0x000000F0)\n+#define MM81X_RATECODE_NSS_INDEX (0x00000700)\n+#define MM81X_RATECODE_BW_INDEX (0x00003800)\n+#define MM81X_RATECODE_RTS_FLAG (0x00010000)\n+#define MM81X_RATECODE_SHORT_GI_FLAG (0x00040000)\n+#define MM81X_RATECODE_DUP_BW_INDEX (0x01C00000)\n+\n+static inline enum mm81x_rate_preamble\n+mm81x_ratecode_preamble_get(mm81x_rate_code_t rc)\n+{\n+\treturn (enum mm81x_rate_preamble)(\n+\t\tle32_get_bits(rc, MM81X_RATECODE_PREAMBLE));\n+}\n+\n+static inline u8 mm81x_ratecode_mcs_index_get(mm81x_rate_code_t rc)\n+{\n+\treturn le32_get_bits(rc, MM81X_RATECODE_MCS_INDEX);\n+}\n+\n+static inline u8 mm81x_ratecode_nss_index_get(mm81x_rate_code_t rc)\n+{\n+\treturn le32_get_bits(rc, MM81X_RATECODE_NSS_INDEX);\n+}\n+\n+static inline enum dot11_bandwidth\n+mm81x_ratecode_bw_index_get(mm81x_rate_code_t rc)\n+{\n+\treturn (enum dot11_bandwidth)(\n+\t\tle32_get_bits(rc, MM81X_RATECODE_BW_INDEX));\n+}\n+\n+static inline bool mm81x_ratecode_rts_get(mm81x_rate_code_t rc)\n+{\n+\treturn le32_get_bits(rc, MM81X_RATECODE_RTS_FLAG);\n+}\n+\n+static inline bool mm81x_ratecode_sgi_get(mm81x_rate_code_t rc)\n+{\n+\treturn le32_get_bits(rc, MM81X_RATECODE_SHORT_GI_FLAG);\n+}\n+\n+static inline enum dot11_bandwidth\n+mm81x_ratecode_dup_bw_index_get(mm81x_rate_code_t rc)\n+{\n+\treturn (enum dot11_bandwidth)(\n+\t\tle32_get_bits(rc, MM81X_RATECODE_DUP_BW_INDEX));\n+}\n+\n+#define MM81X_RATECODE_INIT(bw_idx, nss_idx, mcs_idx, preamble)  \\\n+\t(le32_encode_bits((bw_idx), MM81X_RATECODE_BW_INDEX) |   \\\n+\t le32_encode_bits((nss_idx), MM81X_RATECODE_NSS_INDEX) | \\\n+\t le32_encode_bits((mcs_idx), MM81X_RATECODE_MCS_INDEX) | \\\n+\t le32_encode_bits((preamble), MM81X_RATECODE_PREAMBLE))\n+\n+static inline mm81x_rate_code_t\n+mm81x_ratecode_init(enum dot11_bandwidth bw_index, u32 nss_index, u32 mcs_index,\n+\t\t    enum mm81x_rate_preamble preamble)\n+{\n+\treturn MM81X_RATECODE_INIT(bw_index, nss_index, mcs_index, preamble);\n+}\n+\n+static inline void\n+mm81x_ratecode_preamble_set(mm81x_rate_code_t *rc,\n+\t\t\t    enum mm81x_rate_preamble preamble)\n+{\n+\t*rc = (*rc \u0026 cpu_to_le32(~MM81X_RATECODE_PREAMBLE)) |\n+\t      le32_encode_bits(preamble, MM81X_RATECODE_PREAMBLE);\n+}\n+\n+static inline void mm81x_ratecode_mcs_index_set(mm81x_rate_code_t *rc,\n+\t\t\t\t\t\tu32 mcs_index)\n+{\n+\t*rc = (*rc \u0026 cpu_to_le32(~MM81X_RATECODE_MCS_INDEX)) |\n+\t      le32_encode_bits(mcs_index, MM81X_RATECODE_MCS_INDEX);\n+}\n+\n+static inline void mm81x_ratecode_nss_index_set(mm81x_rate_code_t *rc,\n+\t\t\t\t\t\tu32 nss_index)\n+{\n+\t*rc = (*rc \u0026 cpu_to_le32(~MM81X_RATECODE_NSS_INDEX)) |\n+\t      le32_encode_bits(nss_index, MM81X_RATECODE_NSS_INDEX);\n+}\n+\n+static inline void mm81x_ratecode_bw_index_set(mm81x_rate_code_t *rc,\n+\t\t\t\t\t       enum dot11_bandwidth bw_index)\n+{\n+\t*rc = (*rc \u0026 cpu_to_le32(~MM81X_RATECODE_BW_INDEX)) |\n+\t      le32_encode_bits(bw_index, MM81X_RATECODE_BW_INDEX);\n+}\n+\n+static inline void\n+mm81x_ratecode_update_s1g_bw_preamble(mm81x_rate_code_t *rc,\n+\t\t\t\t      enum dot11_bandwidth bw_index)\n+{\n+\tenum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT;\n+\n+\tif (bw_index == DOT11_BANDWIDTH_1MHZ)\n+\t\tpream = MM81X_RATE_PREAMBLE_S1G_1M;\n+\n+\tmm81x_ratecode_preamble_set(rc, pream);\n+\tmm81x_ratecode_bw_index_set(rc, bw_index);\n+}\n+\n+static inline void\n+mm81x_ratecode_dup_bw_index_set(mm81x_rate_code_t *rc,\n+\t\t\t\tenum dot11_bandwidth dup_bw_index)\n+{\n+\t*rc = (*rc \u0026 cpu_to_le32(~MM81X_RATECODE_DUP_BW_INDEX)) |\n+\t      le32_encode_bits(dup_bw_index, MM81X_RATECODE_DUP_BW_INDEX);\n+}\n+\n+static inline void mm81x_ratecode_enable_rts(mm81x_rate_code_t *rc)\n+{\n+\t*rc |= cpu_to_le32(MM81X_RATECODE_RTS_FLAG);\n+}\n+\n+static inline void mm81x_ratecode_enable_sgi(mm81x_rate_code_t *rc)\n+{\n+\t*rc |= cpu_to_le32(MM81X_RATECODE_SHORT_GI_FLAG);\n+}\n+\n+static inline enum dot11_bandwidth mm81x_ratecode_bw_mhz_to_bw_index(u8 bw_mhz)\n+{\n+\treturn ((bw_mhz == 1) ? DOT11_BANDWIDTH_1MHZ :\n+\t\t(bw_mhz == 2) ? DOT11_BANDWIDTH_2MHZ :\n+\t\t(bw_mhz == 4) ? DOT11_BANDWIDTH_4MHZ :\n+\t\t(bw_mhz == 8) ? DOT11_BANDWIDTH_8MHZ :\n+\t\t\t\tDOT11_BANDWIDTH_2MHZ);\n+}\n+\n+static inline u8\n+mm81x_ratecode_bw_index_to_s1g_bw_mhz(enum dot11_bandwidth bw_idx)\n+{\n+\treturn ((bw_idx == DOT11_BANDWIDTH_1MHZ) ? 1 :\n+\t\t(bw_idx == DOT11_BANDWIDTH_2MHZ) ? 2 :\n+\t\t(bw_idx == DOT11_BANDWIDTH_4MHZ) ? 4 :\n+\t\t(bw_idx == DOT11_BANDWIDTH_8MHZ) ? 8 :\n+\t\t\t\t\t\t   2);\n+}\n+\n+#endif\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/rc.c b/drivers/net/wireless/morsemicro/mm81x/rc.c\nnew file mode 100644\nindex 00000000000000..04aff66de4bd96\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/rc.c\n@@ -0,0 +1,494 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/timer.h\u003e\n+#include \"core.h\"\n+#include \"mac.h\"\n+#include \"bus.h\"\n+#include \"rc.h\"\n+\n+#define MM81X_RC_BW_TO_MMRC_BW(X)    \\\n+\t(((X) == 1) ? MMRC_BW_1MHZ : \\\n+\t ((X) == 2) ? MMRC_BW_2MHZ : \\\n+\t ((X) == 4) ? MMRC_BW_4MHZ : \\\n+\t ((X) == 8) ? MMRC_BW_8MHZ : \\\n+\t\t      MMRC_BW_2MHZ)\n+\n+static void mm81x_rc_work(struct work_struct *work)\n+{\n+\tstruct mm81x_rc *mrc = container_of(work, struct mm81x_rc, work);\n+\tstruct list_head *pos;\n+\n+\tspin_lock_bh(\u0026mrc-\u003elock);\n+\n+\tlist_for_each(pos, \u0026mrc-\u003estas) {\n+\t\tstruct mm81x_rc_sta *mrc_sta =\n+\t\t\tcontainer_of(pos, struct mm81x_rc_sta, list);\n+\t\tunsigned long now = jiffies;\n+\n+\t\tmrc_sta-\u003elast_update = now;\n+\n+\t\tmmrc_update(mrc_sta-\u003etb);\n+\t}\n+\n+\tspin_unlock_bh(\u0026mrc-\u003elock);\n+\n+\tmod_timer(\u0026mrc-\u003etimer, jiffies + msecs_to_jiffies(100));\n+}\n+\n+static void mm81x_rc_timer(struct timer_list *t)\n+{\n+\tstruct mm81x_rc *mrc = timer_container_of(mrc, t, timer);\n+\tstruct mm81x *mors = mrc-\u003emors;\n+\n+\tqueue_work(mors-\u003enet_wq, \u0026mors-\u003emrc.work);\n+}\n+\n+void mm81x_rc_init(struct mm81x *mors)\n+{\n+\tINIT_LIST_HEAD(\u0026mors-\u003emrc.stas);\n+\tspin_lock_init(\u0026mors-\u003emrc.lock);\n+\n+\tINIT_WORK(\u0026mors-\u003emrc.work, mm81x_rc_work);\n+\ttimer_setup(\u0026mors-\u003emrc.timer, mm81x_rc_timer, 0);\n+\n+\tmors-\u003emrc.mors = mors;\n+\tmod_timer(\u0026mors-\u003emrc.timer, jiffies + msecs_to_jiffies(100));\n+}\n+\n+void mm81x_rc_deinit(struct mm81x *mors)\n+{\n+\tcancel_work_sync(\u0026mors-\u003emrc.work);\n+\ttimer_delete_sync_try(\u0026mors-\u003emrc.timer);\n+}\n+\n+static void mm81x_rc_sta_config_guard_per_bw(struct ieee80211_sta *sta,\n+\t\t\t\t\t     struct mmrc_sta_capabilities *caps)\n+{\n+\tcaps-\u003eguard = BIT(MMRC_GUARD_LONG);\n+\n+\tif (caps-\u003ebandwidth \u0026 BIT(MMRC_BW_1MHZ)) {\n+\t\tcaps-\u003esgi_per_bw |= SGI_PER_BW(MMRC_BW_1MHZ);\n+\t\tcaps-\u003eguard |= BIT(MMRC_GUARD_SHORT);\n+\t}\n+\n+\tif (caps-\u003ebandwidth \u0026 BIT(MMRC_BW_2MHZ)) {\n+\t\tcaps-\u003esgi_per_bw |= SGI_PER_BW(MMRC_BW_2MHZ);\n+\t\tcaps-\u003eguard |= BIT(MMRC_GUARD_SHORT);\n+\t}\n+\n+\tif (caps-\u003ebandwidth \u0026 BIT(MMRC_BW_4MHZ)) {\n+\t\tcaps-\u003esgi_per_bw |= SGI_PER_BW(MMRC_BW_4MHZ);\n+\t\tcaps-\u003eguard |= BIT(MMRC_GUARD_SHORT);\n+\t}\n+\n+\tif (caps-\u003ebandwidth \u0026 BIT(MMRC_BW_8MHZ)) {\n+\t\tcaps-\u003esgi_per_bw |= SGI_PER_BW(MMRC_BW_8MHZ);\n+\t\tcaps-\u003eguard |= BIT(MMRC_GUARD_SHORT);\n+\t}\n+}\n+\n+static void mm81x_rc_sta_add_s1g_sta_caps(struct mm81x *mors,\n+\t\t\t\t\t  struct mmrc_sta_capabilities *caps,\n+\t\t\t\t\t  struct ieee80211_sta_s1g_cap *s1g_cap)\n+{\n+\tint nss_idx = 0;\n+\tu8 rx_mcs = s1g_cap-\u003enss_mcs[0] \u0026 0x3; /* 1SS */\n+\tu8 tx_mcs = (s1g_cap-\u003enss_mcs[2] \u003e\u003e 1) \u0026 0x3; /* 1SS */\n+\tu8 mcs = min(rx_mcs, tx_mcs);\n+\n+\tswitch (mcs) {\n+\tcase IEEE80211_VHT_MCS_SUPPORT_0_9: /* VHT 9 -\u003e  S1G 9 */\n+\t\tcaps-\u003erates |= BIT(MMRC_MCS9) | BIT(MMRC_MCS8);\n+\t\tfallthrough;\n+\tcase IEEE80211_VHT_MCS_SUPPORT_0_8: /* VHT 8 -\u003e S1G 7 */\n+\t\tcaps-\u003erates |= BIT(MMRC_MCS7) | BIT(MMRC_MCS6) |\n+\t\t\t       BIT(MMRC_MCS5) | BIT(MMRC_MCS4) | BIT(MMRC_MCS3);\n+\t\tfallthrough;\n+\tcase IEEE80211_VHT_MCS_SUPPORT_0_7: /* VHT 7 -\u003e S1G 2 */\n+\t\tcaps-\u003erates |= BIT(MMRC_MCS2) | BIT(MMRC_MCS1) |\n+\t\t\t       BIT(MMRC_MCS0) | BIT(MMRC_MCS10);\n+\t\tcaps-\u003espatial_streams |= (BIT(nss_idx) \u0026 0x0F);\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tdev_warn(mors-\u003edev, \"Invalid MCS encoding 0x%02x for stream %d\",\n+\t\t\t mcs, nss_idx);\n+\t}\n+}\n+\n+int mm81x_rc_sta_add(struct mm81x *mors, struct ieee80211_vif *vif,\n+\t\t     struct ieee80211_sta *sta)\n+{\n+\tstruct ieee80211_sta_s1g_cap *s1g_cap = \u0026sta-\u003edeflink.s1g_cap;\n+\tstruct mm81x_sta *msta = (struct mm81x_sta *)sta-\u003edrv_priv;\n+\tstruct mmrc_sta_capabilities caps;\n+\tint oper_bw_mhz = cfg80211_chandef_get_width(\u0026mors-\u003echandef);\n+\tsize_t table_mem_size;\n+\tstruct mmrc_table *tb;\n+\n+\tmemset(\u0026caps, 0, sizeof(caps));\n+\n+\tmm81x_rc_sta_add_s1g_sta_caps(mors, \u0026caps, s1g_cap);\n+\n+\t/* Configure STA for support up to 8MHZ */\n+\twhile (oper_bw_mhz \u003e 0) {\n+\t\tcaps.bandwidth |= BIT(MM81X_RC_BW_TO_MMRC_BW(oper_bw_mhz));\n+\t\toper_bw_mhz \u003e\u003e= 1;\n+\t}\n+\n+\t/* Configure STA for short and long guard */\n+\tmm81x_rc_sta_config_guard_per_bw(sta, \u0026caps);\n+\n+\t/* Set max rates */\n+\tif (mors-\u003ehw-\u003emax_rates \u003e 0 \u0026\u0026\n+\t    mors-\u003ehw-\u003emax_rates \u003c IEEE80211_TX_MAX_RATES)\n+\t\tcaps.max_rates = mors-\u003ehw-\u003emax_rates;\n+\telse\n+\t\tcaps.max_rates = IEEE80211_TX_MAX_RATES;\n+\n+\t/* Set max reties */\n+\tif (mors-\u003ehw-\u003emax_rate_tries \u003e= MMRC_MIN_CHAIN_ATTEMPTS \u0026\u0026\n+\t    mors-\u003ehw-\u003emax_rate_tries \u003c MMRC_MAX_CHAIN_ATTEMPTS)\n+\t\tcaps.max_retries = mors-\u003ehw-\u003emax_rate_tries;\n+\telse\n+\t\tcaps.max_retries = MMRC_MAX_CHAIN_ATTEMPTS;\n+\n+\tWARN_ON(msta-\u003erc.tb);\n+\ttable_mem_size = mmrc_memory_required_for_caps(\u0026caps);\n+\ttb = kzalloc(table_mem_size, GFP_KERNEL);\n+\tif (!tb)\n+\t\treturn -ENOMEM;\n+\n+\t/* Initialise the STA rate control table */\n+\tmmrc_sta_init(tb, \u0026caps, msta-\u003eavg_rssi);\n+\n+\tspin_lock_bh(\u0026mors-\u003emrc.lock);\n+\tkfree(msta-\u003erc.tb);\n+\tmsta-\u003erc.tb = tb;\n+\tlist_add(\u0026msta-\u003erc.list, \u0026mors-\u003emrc.stas);\n+\tmsta-\u003erc.last_update = jiffies;\n+\tspin_unlock_bh(\u0026mors-\u003emrc.lock);\n+\n+\treturn 0;\n+}\n+\n+void mm81x_rc_sta_remove(struct mm81x *mors, struct ieee80211_sta *sta)\n+{\n+\tstruct mm81x_sta *msta = (struct mm81x_sta *)sta-\u003edrv_priv;\n+\n+\tspin_lock_bh(\u0026mors-\u003emrc.lock);\n+\tif (msta-\u003erc.tb) {\n+\t\tlist_del_init(\u0026msta-\u003erc.list);\n+\t\tkfree(msta-\u003erc.tb);\n+\t\tmsta-\u003erc.tb = NULL;\n+\t}\n+\tspin_unlock_bh(\u0026mors-\u003emrc.lock);\n+}\n+\n+static void mm81x_rc_sta_fill_basic_rates(struct mm81x_skb_tx_info *tx_info,\n+\t\t\t\t\t  struct ieee80211_tx_info *info,\n+\t\t\t\t\t  int tx_bw)\n+{\n+\tint i;\n+\tenum dot11_bandwidth bw_idx = mm81x_ratecode_bw_mhz_to_bw_index(tx_bw);\n+\tenum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT;\n+\n+\tmm81x_ratecode_mcs_index_set(\u0026tx_info-\u003erates[0].mm81x_ratecode, 0);\n+\tmm81x_ratecode_nss_index_set(\u0026tx_info-\u003erates[0].mm81x_ratecode,\n+\t\t\t\t     NSS_TO_NSS_IDX(1));\n+\tmm81x_ratecode_bw_index_set(\u0026tx_info-\u003erates[0].mm81x_ratecode, bw_idx);\n+\tif (bw_idx == DOT11_BANDWIDTH_1MHZ)\n+\t\tpream = MM81X_RATE_PREAMBLE_S1G_1M;\n+\tmm81x_ratecode_preamble_set(\u0026tx_info-\u003erates[0].mm81x_ratecode, pream);\n+\ttx_info-\u003erates[0].count = 4;\n+\n+\tfor (i = 1; i \u003c IEEE80211_TX_MAX_RATES; i++)\n+\t\ttx_info-\u003erates[i].count = 0;\n+\n+\tinfo-\u003econtrol.rates[0].idx = 0;\n+\tinfo-\u003econtrol.rates[0].count = tx_info-\u003erates[0].count;\n+\tinfo-\u003econtrol.rates[0].flags = 0;\n+\tinfo-\u003econtrol.rates[1].idx = -1;\n+}\n+\n+static int mm81x_rc_sta_get_rates(struct mm81x *mors, struct mm81x_sta *msta,\n+\t\t\t\t  struct mmrc_rate_table *rates, size_t size)\n+{\n+\tint ret = -ENOENT;\n+\tstruct list_head *pos;\n+\n+\tspin_lock_bh(\u0026mors-\u003emrc.lock);\n+\tlist_for_each(pos, \u0026mors-\u003emrc.stas) {\n+\t\tstruct mm81x_rc_sta *mrc_sta =\n+\t\t\tlist_entry(pos, struct mm81x_rc_sta, list);\n+\n+\t\tif (\u0026msta-\u003erc == mrc_sta) {\n+\t\t\tret = 0;\n+\t\t\tmmrc_get_rates(msta-\u003erc.tb, rates, size);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tspin_unlock_bh(\u0026mors-\u003emrc.lock);\n+\n+\treturn ret;\n+}\n+\n+static bool mm81x_rc_use_basic_rates(struct ieee80211_sta *sta,\n+\t\t\t\t     struct sk_buff *skb,\n+\t\t\t\t     struct ieee80211_hdr *hdr)\n+{\n+\tstruct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);\n+\n+\tif (!sta)\n+\t\treturn true;\n+\n+\tif (ieee80211_is_qos_nullfunc(hdr-\u003eframe_control) ||\n+\t    ieee80211_is_nullfunc(hdr-\u003eframe_control))\n+\t\treturn true;\n+\n+\tif (!ieee80211_is_data_qos(hdr-\u003eframe_control))\n+\t\treturn true;\n+\n+\t/* Use basic rates for EAPOL exchanges or when instructed */\n+\tif (unlikely((skb-\u003eprotocol == cpu_to_be16(ETH_P_PAE) ||\n+\t\t      info-\u003eflags \u0026 IEEE80211_TX_CTL_USE_MINRATE)))\n+\t\treturn true;\n+\n+\treturn false;\n+}\n+\n+void mm81x_rc_sta_fill_tx_rates(struct mm81x *mors,\n+\t\t\t\tstruct mm81x_skb_tx_info *tx_info,\n+\t\t\t\tstruct sk_buff *skb, struct ieee80211_sta *sta,\n+\t\t\t\tint tx_bw, bool rts_allowed)\n+{\n+\tint ret, i;\n+\tstruct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb-\u003edata;\n+\tstruct mm81x_sta *msta;\n+\tstruct mmrc_rate_table rates;\n+\tstruct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);\n+\n+\tBUILD_BUG_ON((MMRC_BW_1MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_1MHZ ||\n+\t\t      MMRC_BW_2MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_2MHZ ||\n+\t\t      MMRC_BW_4MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_4MHZ ||\n+\t\t      MMRC_BW_16MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_16MHZ));\n+\n+\tmemset(\u0026info-\u003econtrol.rates, 0, sizeof(info-\u003econtrol.rates));\n+\tmemset(\u0026info-\u003estatus.rates, 0, sizeof(info-\u003estatus.rates));\n+\tmm81x_rc_sta_fill_basic_rates(tx_info, info, tx_bw);\n+\n+\t/* Use basic rates for non data packets */\n+\tif (mm81x_rc_use_basic_rates(sta, skb, hdr))\n+\t\treturn;\n+\n+\tmsta = (struct mm81x_sta *)sta-\u003edrv_priv;\n+\tif (!msta)\n+\t\treturn;\n+\n+\tret = mm81x_rc_sta_get_rates(mors, msta, \u0026rates, skb-\u003elen);\n+\tif (ret != 0)\n+\t\treturn;\n+\n+\tfor (i = 0; i \u003c IEEE80211_TX_MAX_RATES; i++) {\n+\t\tinfo-\u003econtrol.rates[i].flags = 0;\n+\t\tif (rates.rates[i].rate != MMRC_MCS_UNUSED) {\n+\t\t\tu8 mcs = rates.rates[i].rate;\n+\t\t\tu8 nss_index = rates.rates[i].ss;\n+\t\t\tenum dot11_bandwidth bw_idx =\n+\t\t\t\t(enum dot11_bandwidth)rates.rates[i].bw;\n+\t\t\tenum mm81x_rate_preamble pream =\n+\t\t\t\tMM81X_RATE_PREAMBLE_S1G_SHORT;\n+\n+\t\t\tmm81x_ratecode_bw_index_set(\n+\t\t\t\t\u0026tx_info-\u003erates[i].mm81x_ratecode, bw_idx);\n+\t\t\tmm81x_ratecode_mcs_index_set(\n+\t\t\t\t\u0026tx_info-\u003erates[i].mm81x_ratecode, mcs);\n+\t\t\tmm81x_ratecode_nss_index_set(\n+\t\t\t\t\u0026tx_info-\u003erates[i].mm81x_ratecode, nss_index);\n+\t\t\tif (bw_idx == DOT11_BANDWIDTH_1MHZ)\n+\t\t\t\tpream = MM81X_RATE_PREAMBLE_S1G_1M;\n+\t\t\tmm81x_ratecode_preamble_set(\n+\t\t\t\t\u0026tx_info-\u003erates[i].mm81x_ratecode, pream);\n+\t\t\ttx_info-\u003erates[i].count = rates.rates[i].attempts;\n+\n+\t\t\tif (rts_allowed \u0026\u0026\n+\t\t\t    (rates.rates[i].flags \u0026 BIT(MMRC_FLAGS_CTS_RTS))) {\n+\t\t\t\tmm81x_ratecode_enable_rts(\n+\t\t\t\t\t\u0026tx_info-\u003erates[i].mm81x_ratecode);\n+\t\t\t\tinfo-\u003econtrol.rates[i].flags |=\n+\t\t\t\t\tIEEE80211_TX_RC_USE_RTS_CTS;\n+\t\t\t}\n+\n+\t\t\tif (rates.rates[i].guard == MMRC_GUARD_SHORT) {\n+\t\t\t\tmm81x_ratecode_enable_sgi(\n+\t\t\t\t\t\u0026tx_info-\u003erates[i].mm81x_ratecode);\n+\t\t\t\tinfo-\u003econtrol.rates[i].flags |=\n+\t\t\t\t\tIEEE80211_TX_RC_SHORT_GI;\n+\t\t\t}\n+\n+\t\t\t/* Update skb tx_info */\n+\t\t\tinfo-\u003econtrol.rates[i].idx = rates.rates[i].rate;\n+\t\t\tinfo-\u003econtrol.rates[i].count = rates.rates[i].attempts;\n+\t\t} else {\n+\t\t\tinfo-\u003econtrol.rates[i].idx = -1;\n+\t\t\tinfo-\u003econtrol.rates[i].count = 0;\n+\t\t\ttx_info-\u003erates[i].count = 0;\n+\t\t}\n+\t}\n+}\n+\n+static void mm81x_rc_sta_set_rates(struct mm81x *mors, struct mm81x_sta *msta,\n+\t\t\t\t   struct mmrc_rate_table *rates, int attempts,\n+\t\t\t\t   bool was_aggregated)\n+{\n+\tstruct list_head *pos;\n+\n+\tspin_lock_bh(\u0026mors-\u003emrc.lock);\n+\tlist_for_each(pos, \u0026mors-\u003emrc.stas) {\n+\t\tstruct mm81x_rc_sta *mrc_sta =\n+\t\t\tlist_entry(pos, struct mm81x_rc_sta, list);\n+\n+\t\tif (\u0026msta-\u003erc == mrc_sta) {\n+\t\t\tmmrc_feedback(msta-\u003erc.tb, rates, attempts,\n+\t\t\t\t      was_aggregated);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tspin_unlock_bh(\u0026mors-\u003emrc.lock);\n+}\n+\n+void mm81x_rc_sta_feedback_rates(struct mm81x *mors, struct sk_buff *skb,\n+\t\t\t\t struct ieee80211_sta *sta,\n+\t\t\t\t struct mm81x_skb_tx_status *tx_sts,\n+\t\t\t\t int attempts)\n+{\n+\tint i;\n+\tu32 tx_airtime = 0;\n+\tstruct mmrc_rate_table rates;\n+\tstruct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb-\u003edata;\n+\tstruct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);\n+\tstruct ieee80211_tx_rate *r = \u0026txi-\u003estatus.rates[0];\n+\tint count = min_t(int, MM81X_SKB_MAX_RATES, IEEE80211_TX_MAX_RATES);\n+\tstruct mm81x_sta *msta = msta = (struct mm81x_sta *)sta-\u003edrv_priv;\n+\n+\t/* Don't update rate info if basic rates were used */\n+\tif (mm81x_rc_use_basic_rates(sta, skb, hdr))\n+\t\tgoto exit;\n+\n+\tif (attempts \u003c= 0)\n+\t\t/* Did we really send the packet? */\n+\t\tgoto exit;\n+\n+\tfor (i = 0; i \u003c count; i++) {\n+\t\trates.rates[i].rate = mm81x_ratecode_mcs_index_get(\n+\t\t\ttx_sts-\u003erates[i].mm81x_ratecode);\n+\t\trates.rates[i].ss = mm81x_ratecode_nss_index_get(\n+\t\t\ttx_sts-\u003erates[i].mm81x_ratecode);\n+\t\trates.rates[i].guard =\n+\t\t\tmm81x_ratecode_sgi_get(tx_sts-\u003erates[i].mm81x_ratecode);\n+\t\trates.rates[i].bw = mm81x_ratecode_bw_index_get(\n+\t\t\ttx_sts-\u003erates[i].mm81x_ratecode);\n+\t\trates.rates[i].flags =\n+\t\t\tmm81x_ratecode_rts_get(tx_sts-\u003erates[i].mm81x_ratecode);\n+\t\trates.rates[i].attempts = tx_sts-\u003erates[i].count;\n+\n+\t\ttx_airtime +=\n+\t\t\tmmrc_calculate_rate_tx_time(\u0026rates.rates[i], skb-\u003elen);\n+\t}\n+\n+\tif (msta) {\n+\t\t/*\n+\t\t * Save the rate information. This will be used to update\n+\t\t * station's tx rate stats\n+\t\t */\n+\t\tmsta-\u003elast_sta_tx_rate.bw = rates.rates[0].bw;\n+\t\tmsta-\u003elast_sta_tx_rate.rate = rates.rates[0].rate;\n+\t\tmsta-\u003elast_sta_tx_rate.ss = rates.rates[0].ss;\n+\t\tmsta-\u003elast_sta_tx_rate.guard = rates.rates[0].guard;\n+\t}\n+\n+\tmm81x_rc_sta_set_rates(mors, msta, \u0026rates, attempts,\n+\t\t\t       !!(le32_to_cpu(tx_sts-\u003eflags) \u0026\n+\t\t\t\t  MM81X_TX_STATUS_WAS_AGGREGATED));\n+\n+\tieee80211_sta_register_airtime(sta, tx_sts-\u003etid, tx_airtime, 0);\n+\n+exit:\n+\tieee80211_tx_info_clear_status(txi);\n+\n+\tif (!(le32_to_cpu(tx_sts-\u003eflags) \u0026 MM81X_TX_STATUS_FLAGS_NO_ACK) \u0026\u0026\n+\t    !(txi-\u003eflags \u0026 IEEE80211_TX_CTL_NO_ACK))\n+\t\ttxi-\u003eflags |= IEEE80211_TX_STAT_ACK;\n+\n+\tif (le32_to_cpu(tx_sts-\u003eflags) \u0026 MM81X_TX_STATUS_FLAGS_PS_FILTERED) {\n+\t\ttxi-\u003eflags |= IEEE80211_TX_STAT_TX_FILTERED;\n+\n+\t\t/*\n+\t\t * Clear TX CTL AMPDU flag so that this frame gets rescheduled\n+\t\t * in ieee80211_handle_filtered_frame(). This flag will get set\n+\t\t * again by mac80211's tx path on rescheduling.\n+\t\t */\n+\t\ttxi-\u003eflags \u0026= ~IEEE80211_TX_CTL_AMPDU;\n+\t\tif (msta) {\n+\t\t\tif (!msta-\u003etx_ps_filter_en)\n+\t\t\t\tdev_dbg(mors-\u003edev, \"TX ps filter set sta[%pM]\",\n+\t\t\t\t\tmsta-\u003eaddr);\n+\t\t\tmsta-\u003etx_ps_filter_en = true;\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i \u003c count; i++) {\n+\t\tif (tx_sts-\u003erates[i].count \u003e 0) {\n+\t\t\tr[i].count = tx_sts-\u003erates[i].count;\n+\t\t\tr[i].flags |= IEEE80211_TX_RC_MCS;\n+\t\t} else {\n+\t\t\tr[i].idx = -1;\n+\t\t}\n+\t}\n+\n+\t/* single packet per A-MPDU (for now) */\n+\tif (txi-\u003eflags \u0026 IEEE80211_TX_CTL_AMPDU) {\n+\t\ttxi-\u003eflags |= IEEE80211_TX_STAT_AMPDU;\n+\t\ttxi-\u003estatus.ampdu_len = 1;\n+\t\ttxi-\u003estatus.ampdu_ack_len =\n+\t\t\ttxi-\u003eflags \u0026 IEEE80211_TX_STAT_ACK ? 1 : 0;\n+\t}\n+\n+\t/*\n+\t * Inform mac80211 that the SP (elicited by a PS-Poll or u-APSD) is\n+\t * over\n+\t */\n+\tif (sta \u0026\u0026 (txi-\u003eflags \u0026 IEEE80211_TX_STATUS_EOSP)) {\n+\t\ttxi-\u003eflags \u0026= ~IEEE80211_TX_STATUS_EOSP;\n+\t\tieee80211_sta_eosp(sta);\n+\t}\n+}\n+\n+void mm81x_rc_sta_state_check(struct mm81x *mors, struct ieee80211_vif *vif,\n+\t\t\t      struct ieee80211_sta *sta,\n+\t\t\t      enum ieee80211_sta_state old_state,\n+\t\t\t      enum ieee80211_sta_state new_state)\n+{\n+\tstruct mm81x_sta *msta = (struct mm81x_sta *)sta-\u003edrv_priv;\n+\n+\t/* Add to Morse RC STA list */\n+\tif (old_state \u003c new_state \u0026\u0026 new_state == IEEE80211_STA_ASSOC) {\n+\t\t/* Newly associated, add to RC */\n+\t\tmm81x_rc_sta_add(mors, vif, sta);\n+\t} else if (old_state \u003e new_state \u0026\u0026 (old_state == IEEE80211_STA_ASSOC ||\n+\t\t\t\t\t     old_state == IEEE80211_STA_AUTH)) {\n+\t\t/* Lost or failed association; remove from list */\n+\t\tmm81x_rc_sta_remove(mors, sta);\n+\t} else if (old_state \u003c new_state \u0026\u0026 old_state == IEEE80211_STA_NONE \u0026\u0026\n+\t\t   msta-\u003erc.list.prev) {\n+\t\t/*\n+\t\t * Special case for driver warning issue causing a sta to be\n+\t\t * left on the list\n+\t\t */\n+\t\tdev_dbg(mors-\u003edev, \"Remove stale sta from rc list\");\n+\t\tmm81x_rc_sta_remove(mors, sta);\n+\t}\n+}\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/rc.h b/drivers/net/wireless/morsemicro/mm81x/rc.h\nnew file mode 100644\nindex 00000000000000..53f12902440891\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/rc.h\n@@ -0,0 +1,51 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#ifndef _MM81X_RC_H_\n+#define _MM81X_RC_H_\n+\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+#include \"core.h\"\n+#include \"mmrc.h\"\n+\n+struct mm81x_vif;\n+\n+#define INIT_MAX_RATES_NUM 4\n+\n+struct mm81x_rc {\n+\t/* Serialise rate control queue manipulation and timer functions */\n+\tspinlock_t lock;\n+\tstruct list_head stas;\n+\tstruct timer_list timer;\n+\tstruct work_struct work;\n+\tstruct mm81x *mors;\n+};\n+\n+struct mm81x_rc_sta {\n+\tstruct mmrc_table *tb;\n+\tstruct list_head list;\n+\tunsigned long last_update;\n+};\n+\n+void mm81x_rc_init(struct mm81x *mors);\n+void mm81x_rc_deinit(struct mm81x *mors);\n+int mm81x_rc_sta_add(struct mm81x *mors, struct ieee80211_vif *vif,\n+\t\t     struct ieee80211_sta *sta);\n+void mm81x_rc_sta_remove(struct mm81x *mors, struct ieee80211_sta *sta);\n+void mm81x_rc_sta_fill_tx_rates(struct mm81x *mors,\n+\t\t\t\tstruct mm81x_skb_tx_info *tx_info,\n+\t\t\t\tstruct sk_buff *skb, struct ieee80211_sta *sta,\n+\t\t\t\tint tx_bw, bool rts_allowed);\n+void mm81x_rc_sta_feedback_rates(struct mm81x *mors, struct sk_buff *skb,\n+\t\t\t\t struct ieee80211_sta *sta,\n+\t\t\t\t struct mm81x_skb_tx_status *tx_sts,\n+\t\t\t\t int tx_attempts);\n+void mm81x_rc_sta_state_check(struct mm81x *mors, struct ieee80211_vif *vif,\n+\t\t\t      struct ieee80211_sta *sta,\n+\t\t\t      enum ieee80211_sta_state old_state,\n+\t\t\t      enum ieee80211_sta_state new_state);\n+\n+#endif /* !_MM81X_RC_H_ */\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/sdio.c b/drivers/net/wireless/morsemicro/mm81x/sdio.c\nnew file mode 100644\nindex 00000000000000..96fce187dd3522\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/sdio.c\n@@ -0,0 +1,613 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+#include \u003clinux/mmc/card.h\u003e\n+#include \u003clinux/mmc/mmc.h\u003e\n+#include \u003clinux/mmc/host.h\u003e\n+#include \u003clinux/mmc/sdio_func.h\u003e\n+#include \u003clinux/mmc/sdio_ids.h\u003e\n+#include \u003clinux/mmc/sdio.h\u003e\n+#include \u003clinux/mmc/sd.h\u003e\n+#include \"hw.h\"\n+#include \"core.h\"\n+#include \"bus.h\"\n+#include \"mac.h\"\n+#include \"fw.h\"\n+#include \"hif.h\"\n+\n+/*\n+ * Value to indicate that the base address for bulk/register\n+ * read/writes has yet to be set\n+ */\n+#define MM81X_SDIO_BASE_ADDR_UNSET 0xFFFFFFFF\n+\n+#define MM81X_SDIO_ALIGNMENT (8)\n+\n+#define MM81X_SDIO_REG_ADDRESS_BASE 0x10000\n+#define MM81X_SDIO_REG_ADDRESS_WINDOW_0 MM81X_SDIO_REG_ADDRESS_BASE\n+#define MM81X_SDIO_REG_ADDRESS_WINDOW_1 (MM81X_SDIO_REG_ADDRESS_BASE + 1)\n+#define MM81X_SDIO_REG_ADDRESS_CONFIG (MM81X_SDIO_REG_ADDRESS_BASE + 2)\n+\n+struct mm81x_sdio {\n+\tbool enabled;\n+\tu32 bulk_addr_base;\n+\tu32 register_addr_base;\n+\tstruct sdio_func *func;\n+\tconst struct sdio_device_id *id;\n+};\n+\n+static void irq_handler(struct sdio_func *func1)\n+{\n+\tstruct sdio_func *func = func1-\u003ecard-\u003esdio_func[1];\n+\tstruct mm81x *mors = sdio_get_drvdata(func);\n+\n+\tmm81x_hw_irq_handle(mors);\n+}\n+\n+static int mm81x_sdio_enable_irq(struct mm81x_sdio *sdio)\n+{\n+\tint ret;\n+\tstruct sdio_func *func = sdio-\u003efunc;\n+\tstruct sdio_func *func1 = func-\u003ecard-\u003esdio_func[0];\n+\tstruct mm81x *mors = sdio_get_drvdata(func);\n+\n+\tsdio_claim_host(func);\n+\tret = sdio_claim_irq(func1, irq_handler);\n+\tif (ret)\n+\t\tdev_err(mors-\u003edev, \"Failed to enable sdio irq: %d\\n\", ret);\n+\n+\tsdio_release_host(func);\n+\treturn ret;\n+}\n+\n+static void mm81x_sdio_disable_irq(struct mm81x_sdio *sdio)\n+{\n+\tstruct sdio_func *func = sdio-\u003efunc;\n+\tstruct sdio_func *func1 = func-\u003ecard-\u003esdio_func[0];\n+\n+\tsdio_claim_host(func);\n+\tsdio_release_irq(func1);\n+\tsdio_release_host(func);\n+}\n+\n+static void mm81x_sdio_set_irq(struct mm81x *mors, bool enable)\n+{\n+\tstruct mm81x_sdio *sdio = (struct mm81x_sdio *)mors-\u003edrv_priv;\n+\n+\tif (enable)\n+\t\tmm81x_sdio_enable_irq(sdio);\n+\telse\n+\t\tmm81x_sdio_disable_irq(sdio);\n+}\n+\n+static u32 mm81x_sdio_calculate_base_address(u32 address, u8 access)\n+{\n+\treturn (address \u0026 MM81X_SDIO_RW_ADDR_BOUNDARY_MASK) | (access \u0026 0x3);\n+}\n+\n+static void mm81x_sdio_reset_base_address(struct mm81x_sdio *sdio)\n+{\n+\tsdio-\u003ebulk_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;\n+\tsdio-\u003eregister_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;\n+}\n+\n+static int mm81x_sdio_set_func_address_base(struct mm81x_sdio *sdio,\n+\t\t\t\t\t    struct sdio_func *func, u32 address,\n+\t\t\t\t\t    u8 access)\n+{\n+\tint ret = 0;\n+\tint retries = 0;\n+\tstatic const int max_retries = 3;\n+\tstruct sdio_func *func2 = sdio-\u003efunc;\n+\tstruct mm81x *mors = sdio_get_drvdata(sdio-\u003efunc);\n+\ts32 calculated_addr_base =\n+\t\tmm81x_sdio_calculate_base_address(address, access);\n+\tu32 *current_addr_base = func == func2 ? \u0026sdio-\u003ebulk_addr_base :\n+\t\t\t\t\t\t \u0026sdio-\u003eregister_addr_base;\n+\n+\tif ((*current_addr_base) == calculated_addr_base \u0026\u0026\n+\t    *current_addr_base != MM81X_SDIO_BASE_ADDR_UNSET)\n+\t\treturn ret;\n+\n+retry:\n+\tsdio_writeb(func, (u8)u32_get_bits(address, GENMASK(23, 16)),\n+\t\t    MM81X_SDIO_REG_ADDRESS_WINDOW_0, \u0026ret);\n+\tif (ret)\n+\t\tgoto err;\n+\n+\tsdio_writeb(func, (u8)u32_get_bits(address, GENMASK(31, 24)),\n+\t\t    MM81X_SDIO_REG_ADDRESS_WINDOW_1, \u0026ret);\n+\tif (ret)\n+\t\tgoto err;\n+\n+\tsdio_writeb(func, access \u0026 0x3, MM81X_SDIO_REG_ADDRESS_CONFIG, \u0026ret);\n+\tif (ret)\n+\t\tgoto err;\n+\n+\t*current_addr_base = calculated_addr_base;\n+\tif (retries)\n+\t\tdev_dbg(mors-\u003edev, \"%s succeeded after %d retries\\n\", __func__,\n+\t\t\tretries);\n+\n+\treturn ret;\n+err:\n+\tretries++;\n+\tif (ret == -ETIMEDOUT \u0026\u0026 retries \u003c= max_retries) {\n+\t\tdev_dbg(mors-\u003edev, \"%s failed (%d), retrying (%d/%d)\\n\",\n+\t\t\t__func__, ret, retries, max_retries);\n+\t\tgoto retry;\n+\t}\n+\n+\t*current_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;\n+\treturn ret;\n+}\n+\n+static int mm81x_sdio_mem_write_block(struct mm81x_sdio *sdio, u32 address,\n+\t\t\t\t      u8 *data, ssize_t size)\n+{\n+\tint ret;\n+\tstruct sdio_func *func2 = sdio-\u003efunc;\n+\tstruct mm81x *mors = sdio_get_drvdata(sdio-\u003efunc);\n+\n+\tmm81x_sdio_set_func_address_base(sdio, func2, address,\n+\t\t\t\t\t MM81X_CONFIG_ACCESS_4BYTE);\n+\tif (unlikely(!IS_ALIGNED((uintptr_t)data,\n+\t\t\t\t mors-\u003ebus_ops-\u003ebulk_alignment))) {\n+\t\tret = -EBADE;\n+\t\tgoto exit;\n+\t}\n+\n+\taddress \u0026= 0x0000FFFF; /* remove base and keep offset */\n+\tret = sdio_memcpy_toio(func2, address, data, size);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\tret = size;\n+exit:\n+\treturn ret;\n+}\n+\n+static int mm81x_sdio_mem_write_byte(struct mm81x_sdio *sdio, u32 address,\n+\t\t\t\t     u8 *data, ssize_t size)\n+{\n+\tint i, ret;\n+\tstruct sdio_func *func1 = sdio-\u003efunc-\u003ecard-\u003esdio_func[0];\n+\n+\tmm81x_sdio_set_func_address_base(sdio, func1, address,\n+\t\t\t\t\t MM81X_CONFIG_ACCESS_1BYTE);\n+\n+\taddress \u0026= 0x0000FFFF; /* remove base and keep offset */\n+\tfor (i = 0; i \u003c size; i++) {\n+\t\tsdio_writeb(func1, data[i], address + i, (int *)\u0026ret);\n+\t\tif (ret)\n+\t\t\tgoto exit;\n+\t}\n+\n+\tret = size;\n+exit:\n+\treturn ret;\n+}\n+\n+static void mm81x_sdio_claim_host(struct mm81x *mors)\n+{\n+\tstruct mm81x_sdio *sdio = (struct mm81x_sdio *)mors-\u003edrv_priv;\n+\tstruct sdio_func *func = sdio-\u003efunc;\n+\n+\tsdio_claim_host(func);\n+}\n+\n+static void mm81x_sdio_release_host(struct mm81x *mors)\n+{\n+\tstruct mm81x_sdio *sdio = (struct mm81x_sdio *)mors-\u003edrv_priv;\n+\tstruct sdio_func *func = sdio-\u003efunc;\n+\n+\tsdio_release_host(func);\n+}\n+\n+static int mm81x_sdio_mem_read_block(struct mm81x_sdio *sdio, u32 address,\n+\t\t\t\t     u8 *data, ssize_t size)\n+{\n+\tint ret;\n+\tstruct sdio_func *func2 = sdio-\u003efunc;\n+\tstruct mm81x *mors = sdio_get_drvdata(sdio-\u003efunc);\n+\n+\tmm81x_sdio_set_func_address_base(sdio, func2, address,\n+\t\t\t\t\t MM81X_CONFIG_ACCESS_4BYTE);\n+\tif (unlikely(!IS_ALIGNED((uintptr_t)data,\n+\t\t\t\t mors-\u003ebus_ops-\u003ebulk_alignment))) {\n+\t\tret = -EBADE;\n+\t\tgoto exit;\n+\t}\n+\n+\taddress \u0026= 0x0000FFFF; /* remove base and keep offset */\n+\tret = sdio_memcpy_fromio(func2, data, address, size);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\t/*\n+\t * Observed sometimes that SDIO read repeats the first 4-bytes\n+\t * word twice, overwriting second word (hence, tail will be\n+\t * overwritten with 'sync' byte). When this happens, reading\n+\t * will fetch the correct word. NB: if repeated again, pass it\n+\t * anyway and upper layers will handle it\n+\t */\n+\n+\tif (size \u003e= 8 \u0026\u0026 memcmp(data, data + 4, 4) == 0)\n+\t\tsdio_memcpy_fromio(func2, data, address, 8);\n+\n+\tret = size;\n+exit:\n+\treturn ret;\n+}\n+\n+static int mm81x_sdio_mem_read_byte(struct mm81x_sdio *sdio, u32 address,\n+\t\t\t\t    u8 *data, ssize_t size)\n+{\n+\tint i, ret;\n+\tstruct sdio_func *func1 = sdio-\u003efunc-\u003ecard-\u003esdio_func[0];\n+\n+\tmm81x_sdio_set_func_address_base(sdio, func1, address,\n+\t\t\t\t\t MM81X_CONFIG_ACCESS_1BYTE);\n+\n+\taddress \u0026= 0x0000FFFF; /* remove base and keep offset */\n+\tfor (i = 0; i \u003c size; i++) {\n+\t\tdata[i] = sdio_readb(func1, address + i, (int *)\u0026ret);\n+\t\tif (ret)\n+\t\t\tgoto exit;\n+\t}\n+\n+\tret = size;\n+exit:\n+\treturn ret;\n+}\n+\n+static int mm81x_sdio_dm_write(struct mm81x *mors, u32 address, const u8 *data,\n+\t\t\t       int len)\n+{\n+\tint ret = 0;\n+\tint block_len, byte_len;\n+\tstruct mm81x_sdio *sdio = (struct mm81x_sdio *)mors-\u003edrv_priv;\n+\tint remaining = len;\n+\tint offset = 0;\n+\n+\tif (remaining \u003e 0 \u0026\u0026 address \u0026 0x3) {\n+\t\tlen = 4 - (address \u0026 0x3);\n+\t\tret = mm81x_sdio_mem_write_byte(sdio, address, (u8 *)data, len);\n+\t\tif (ret != len)\n+\t\t\treturn -EIO;\n+\n+\t\toffset += len;\n+\t\tremaining -= len;\n+\t}\n+\n+\twhile ((remaining) \u003e 0) {\n+\t\t/*\n+\t\t * We can only write up to the end of a single window in\n+\t\t * each write operation.\n+\t\t */\n+\t\tu32 window_end = (address + offset) |\n+\t\t\t\t ~MM81X_SDIO_RW_ADDR_BOUNDARY_MASK;\n+\n+\t\tlen = min(remaining, (int)(window_end + 1 - address - offset));\n+\t\tblock_len = len \u0026 ~0x3;\n+\t\tbyte_len = len \u0026 0x3;\n+\n+\t\tif (block_len) {\n+\t\t\tret = mm81x_sdio_mem_write_block(sdio, address + offset,\n+\t\t\t\t\t\t\t (u8 *)(data + offset),\n+\t\t\t\t\t\t\t block_len);\n+\t\t\tif (ret != block_len)\n+\t\t\t\treturn -EIO;\n+\n+\t\t\toffset += block_len;\n+\t\t}\n+\n+\t\tif (byte_len) {\n+\t\t\tret = mm81x_sdio_mem_write_byte(sdio, address + offset,\n+\t\t\t\t\t\t\t(u8 *)(data + offset),\n+\t\t\t\t\t\t\tbyte_len);\n+\t\t\tif (ret != byte_len)\n+\t\t\t\treturn -EIO;\n+\n+\t\t\toffset += byte_len;\n+\t\t}\n+\n+\t\tremaining -= len;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int mm81x_sdio_dm_read(struct mm81x *mors, u32 address, u8 *data,\n+\t\t\t      int len)\n+{\n+\tint ret = 0;\n+\tint block_len, byte_len;\n+\tstruct mm81x_sdio *sdio = (struct mm81x_sdio *)mors-\u003edrv_priv;\n+\tint remaining = len;\n+\tint offset = 0;\n+\n+\tif (remaining \u003e 0 \u0026\u0026 address \u0026 0x3) {\n+\t\tlen = 4 - (address \u0026 0x3);\n+\t\tret = mm81x_sdio_mem_read_byte(sdio, address, data, len);\n+\t\tif (ret != len)\n+\t\t\treturn -EIO;\n+\n+\t\toffset += len;\n+\t\tremaining -= len;\n+\t}\n+\n+\twhile (remaining \u003e 0) {\n+\t\t/*\n+\t\t * We can only read up to the end of a single window in\n+\t\t * each read operation.\n+\t\t */\n+\t\tu32 window_end = (address + offset) |\n+\t\t\t\t ~MM81X_SDIO_RW_ADDR_BOUNDARY_MASK;\n+\n+\t\tlen = min(remaining, (int)(window_end + 1 - address - offset));\n+\t\tblock_len = len \u0026 ~0x3;\n+\t\tbyte_len = len \u0026 0x3;\n+\n+\t\tif (block_len) {\n+\t\t\tret = mm81x_sdio_mem_read_block(sdio, address + offset,\n+\t\t\t\t\t\t\tdata + offset,\n+\t\t\t\t\t\t\tblock_len);\n+\t\t\tif (ret != block_len)\n+\t\t\t\treturn -EIO;\n+\n+\t\t\toffset += block_len;\n+\t\t}\n+\n+\t\tif (byte_len) {\n+\t\t\tret = mm81x_sdio_mem_read_byte(sdio, address + offset,\n+\t\t\t\t\t\t       data + offset, byte_len);\n+\t\t\tif (ret != byte_len)\n+\t\t\t\treturn -EIO;\n+\n+\t\t\toffset += byte_len;\n+\t\t}\n+\n+\t\tremaining -= len;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int mm81x_sdio_reg32_write(struct mm81x *mors, u32 address, u32 val)\n+{\n+\tssize_t ret = 0;\n+\tu32 original_address = address;\n+\tstruct mm81x_sdio *sdio = (struct mm81x_sdio *)mors-\u003edrv_priv;\n+\tstruct sdio_func *func1 = sdio-\u003efunc-\u003ecard-\u003esdio_func[0];\n+\n+\tmm81x_sdio_set_func_address_base(sdio, func1, address,\n+\t\t\t\t\t MM81X_CONFIG_ACCESS_4BYTE);\n+\n+\taddress \u0026= 0x0000FFFF;\n+\tsdio_writel(func1, (__force u32)cpu_to_le32(val),\n+\t\t    (__force u32)cpu_to_le32(address), (int *)\u0026ret);\n+\tif (ret)\n+\t\tgoto error;\n+\n+\treturn 0;\n+\n+error:\n+\tif (original_address == MM81X_REG_RESET(mors) \u0026\u0026\n+\t    val == MM81X_REG_RESET_VALUE(mors)) {\n+\t\tdev_dbg(mors-\u003edev,\n+\t\t\t\"SDIO reset detected, invalidating base addr\\n\");\n+\t\tmm81x_sdio_reset_base_address(sdio);\n+\t}\n+\n+\treturn -EIO;\n+}\n+\n+static int mm81x_sdio_reg32_read(struct mm81x *mors, u32 address, u32 *val)\n+{\n+\tu32 value;\n+\tssize_t ret = 0;\n+\tstruct mm81x_sdio *sdio = (struct mm81x_sdio *)mors-\u003edrv_priv;\n+\tstruct sdio_func *func1 = sdio-\u003efunc-\u003ecard-\u003esdio_func[0];\n+\n+\tmm81x_sdio_set_func_address_base(sdio, func1, address,\n+\t\t\t\t\t MM81X_CONFIG_ACCESS_4BYTE);\n+\n+\taddress \u0026= 0x0000FFFF;\n+\tvalue = sdio_readl(func1, (__force u32)cpu_to_le32(address),\n+\t\t\t   (int *)\u0026ret);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t*val = le32_to_cpup((__le32 *)\u0026value);\n+\treturn 0;\n+}\n+\n+static void mm81x_sdio_bus_enable(struct mm81x *mors, bool enable)\n+{\n+\tstruct mm81x_sdio *sdio = (struct mm81x_sdio *)mors-\u003edrv_priv;\n+\tstruct sdio_func *func = sdio-\u003efunc;\n+\tstruct mmc_host *host = func-\u003ecard-\u003ehost;\n+\n+\tsdio_claim_host(func);\n+\n+\tif (enable) {\n+\t\t/*\n+\t\t * No need to do anything special to re-enable the sdio bus.\n+\t\t * This will happen automatically when a read/write is\n+\t\t * attempted and sdio-\u003ebulk_addr_base == 0.\n+\t\t */\n+\t\tsdio-\u003eenabled = true;\n+\t\thost-\u003eops-\u003eenable_sdio_irq(host, 1);\n+\t\tdev_dbg(mors-\u003edev, \"%s: enabling bus\\n\", __func__);\n+\t} else {\n+\t\thost-\u003eops-\u003eenable_sdio_irq(host, 0);\n+\t\tmm81x_sdio_reset_base_address(sdio);\n+\t\tsdio-\u003eenabled = false;\n+\t\tdev_dbg(mors-\u003edev, \"%s: disabling bus\\n\", __func__);\n+\t}\n+\n+\tsdio_release_host(func);\n+}\n+\n+static void mm81x_sdio_reset(struct sdio_func *func)\n+{\n+\tsdio_claim_host(func);\n+\tsdio_disable_func(func);\n+\tsdio_release_host(func);\n+\n+\tmdelay(20);\n+\n+\tsdio_claim_host(func);\n+\tsdio_disable_func(func);\n+\tmmc_hw_reset(func-\u003ecard);\n+\tsdio_enable_func(func);\n+\tsdio_release_host(func);\n+}\n+\n+static void mm81x_sdio_config_burst_mode(struct mm81x *mors, bool enable_burst)\n+{\n+\tu8 burst_mode = (enable_burst) ? SDIO_WORD_BURST_SIZE_16 :\n+\t\t\t\t\t SDIO_WORD_BURST_DISABLE;\n+\n+\tmm81x_hw_enable_burst_mode(mors, burst_mode);\n+}\n+\n+static const struct mm81x_bus_ops mm81x_sdio_ops = {\n+\t.dm_read = mm81x_sdio_dm_read,\n+\t.dm_write = mm81x_sdio_dm_write,\n+\t.reg32_read = mm81x_sdio_reg32_read,\n+\t.reg32_write = mm81x_sdio_reg32_write,\n+\t.set_bus_enable = mm81x_sdio_bus_enable,\n+\t.claim = mm81x_sdio_claim_host,\n+\t.release = mm81x_sdio_release_host,\n+\t.config_burst_mode = mm81x_sdio_config_burst_mode,\n+\t.set_irq = mm81x_sdio_set_irq,\n+\t.bulk_alignment = MM81X_SDIO_ALIGNMENT\n+};\n+\n+static int mm81x_sdio_enable(struct mm81x_sdio *sdio)\n+{\n+\tint ret;\n+\tstruct sdio_func *func = sdio-\u003efunc;\n+\tstruct mm81x *mors = sdio_get_drvdata(func);\n+\n+\tsdio_claim_host(func);\n+\tret = sdio_enable_func(func);\n+\tif (ret)\n+\t\tdev_err(mors-\u003edev, \"sdio_enable_func failed: %d\\n\", ret);\n+\tsdio_release_host(func);\n+\treturn ret;\n+}\n+\n+static void mm81x_sdio_release(struct mm81x_sdio *sdio)\n+{\n+\tstruct sdio_func *func = sdio-\u003efunc;\n+\n+\tsdio_claim_host(func);\n+\tsdio_disable_func(func);\n+\tsdio_release_host(func);\n+}\n+\n+static int mm81x_sdio_probe(struct sdio_func *func,\n+\t\t\t    const struct sdio_device_id *id)\n+{\n+\tint ret = 0;\n+\tstruct mm81x *mors = NULL;\n+\tstruct mm81x_sdio *sdio;\n+\tstruct device *dev = \u0026func-\u003edev;\n+\n+\tif (func-\u003enum == 1)\n+\t\treturn 0;\n+\n+\tif (func-\u003enum != 2)\n+\t\treturn -ENODEV;\n+\n+\tmors = mm81x_core_alloc(sizeof(*sdio), dev);\n+\tif (!mors)\n+\t\treturn -ENOMEM;\n+\n+\tmors-\u003ebus_ops = \u0026mm81x_sdio_ops;\n+\tmors-\u003ebus_type = MM81X_BUS_TYPE_SDIO;\n+\n+\tsdio = (struct mm81x_sdio *)mors-\u003edrv_priv;\n+\tsdio-\u003efunc = func;\n+\tsdio-\u003eid = id;\n+\tsdio-\u003eenabled = true;\n+\tmm81x_sdio_reset_base_address(sdio);\n+\n+\tsdio_set_drvdata(func, mors);\n+\n+\tret = mm81x_sdio_enable(sdio);\n+\tif (ret)\n+\t\tgoto err_core_free;\n+\n+\tmm81x_sdio_config_burst_mode(mors, true);\n+\n+\tret = mm81x_core_init(mors);\n+\tif (ret)\n+\t\tgoto err_sdio_release;\n+\n+\tret = mm81x_sdio_enable_irq(sdio);\n+\tif (ret)\n+\t\tgoto err_core_deinit;\n+\n+\tret = mm81x_core_register(mors);\n+\tif (ret)\n+\t\tgoto err_disable_irq;\n+\n+\treturn 0;\n+\n+err_disable_irq:\n+\tmm81x_sdio_disable_irq(sdio);\n+err_core_deinit:\n+\tmm81x_core_deinit(mors);\n+err_sdio_release:\n+\tmm81x_sdio_release(sdio);\n+err_core_free:\n+\tmm81x_core_free(mors);\n+\treturn ret;\n+}\n+\n+static void mm81x_sdio_remove(struct sdio_func *func)\n+{\n+\tstruct mm81x *mors = sdio_get_drvdata(func);\n+\tstruct mm81x_sdio *sdio = (struct mm81x_sdio *)mors-\u003edrv_priv;\n+\n+\tif (!mors)\n+\t\treturn;\n+\n+\tmm81x_core_unregister(mors);\n+\tmm81x_sdio_disable_irq(sdio);\n+\tmm81x_core_deinit(mors);\n+\tmm81x_sdio_release(sdio);\n+\tmm81x_sdio_reset(func);\n+\tmm81x_core_free(mors);\n+\tsdio_set_drvdata(func, NULL);\n+}\n+\n+static const struct sdio_device_id mm81x_sdio_devices[] = {\n+\t{ SDIO_DEVICE(SDIO_VENDOR_ID_MORSEMICRO,\n+\t\t      SDIO_DEVICE_ID_MORSEMICRO_MM8108) },\n+\t{},\n+};\n+\n+MODULE_DEVICE_TABLE(sdio, mm81x_sdio_devices);\n+\n+static struct sdio_driver mm81x_sdio_driver = {\n+\t.name = \"mm81x_sdio\",\n+\t.id_table = mm81x_sdio_devices,\n+\t.probe = mm81x_sdio_probe,\n+\t.remove = mm81x_sdio_remove,\n+};\n+\n+module_sdio_driver(mm81x_sdio_driver);\n+\n+MODULE_AUTHOR(\"Morse Micro\");\n+MODULE_DESCRIPTION(\"Driver support for Morse Micro MM81X SDIO devices\");\n+MODULE_LICENSE(\"Dual BSD/GPL\");\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/skbq.c b/drivers/net/wireless/morsemicro/mm81x/skbq.c\nnew file mode 100644\nindex 00000000000000..9bc7899c05a861\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/skbq.c\n@@ -0,0 +1,1065 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+#include \u003clinux/ktime.h\u003e\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/jiffies.h\u003e\n+#include \"hif.h\"\n+#include \"skbq.h\"\n+#include \"mac.h\"\n+#include \"command.h\"\n+#include \"bus.h\"\n+\n+/* Returns number of bytes needed to word align */\n+#define BYTES_NEEDED_TO_WORD_ALIGN(bytes) \\\n+\t((bytes) \u0026 0x3 ? (4 - ((bytes) \u0026 0x3)) : 0)\n+\n+/* Rounds down to the nearest word boundary */\n+#define ROUND_DOWN_TO_WORD(bytes)                                  \\\n+\t(BYTES_NEEDED_TO_WORD_ALIGN(bytes) ?                       \\\n+\t\t bytes - (4 - BYTES_NEEDED_TO_WORD_ALIGN(bytes)) : \\\n+\t\t bytes)\n+\n+#define MM81X_SKBQ_MAX_TXQ_LEN 32\n+#define MM81X_SKBQ_TX_QUEUED_LIFETIME_MS 1000\n+#define MM81X_SKBQ_TX_STATUS_LIFETIME_MS (15 * 1000)\n+\n+/* Returns padding needed to align x up to a 4-byte boundary */\n+#define MM81X_PAD4(x) (((x) \u0026 0x3) ? (4 - ((x) \u0026 0x3)) : 0)\n+\n+struct mm81x_tx_status_priv {\n+\t/*\n+\t * Time (jiffies) at which this packet has spent too long the pending\n+\t * queue, waiting for status notification from the firmware, and\n+\t * should be considered lost.\n+\t */\n+\tunsigned long tx_status_expiry;\n+};\n+\n+static struct mm81x_tx_status_priv *\n+__mm81x_skbq_tx_status_priv(struct sk_buff *skb)\n+{\n+\tstruct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);\n+\n+\tBUILD_BUG_ON(sizeof(struct mm81x_tx_status_priv) \u003e\n+\t\t     sizeof(tx_info-\u003estatus.status_driver_data));\n+\treturn (struct mm81x_tx_status_priv *)\u0026tx_info-\u003estatus\n+\t\t.status_driver_data[0];\n+}\n+\n+static bool __mm81x_skbq_has_pending_tx_skb_timed_out(struct sk_buff *skb)\n+{\n+\tstruct mm81x_tx_status_priv *info = __mm81x_skbq_tx_status_priv(skb);\n+\n+\t/* If our timestamp value is in the past then we have timed out. */\n+\treturn time_is_before_jiffies(info-\u003etx_status_expiry);\n+}\n+\n+static u32 __mm81x_skbq_size(const struct mm81x_skbq *mq)\n+{\n+\treturn mq-\u003eskbq_size;\n+}\n+\n+static u32 __mm81x_skbq_space(const struct mm81x_skbq *mq)\n+{\n+\treturn MM81X_SKBQ_SIZE - __mm81x_skbq_size(mq);\n+}\n+\n+static bool __mm81x_skbq_over_threshold(struct mm81x_skbq *mq)\n+{\n+\treturn skb_queue_len(\u0026mq-\u003eskbq) \u003e= MM81X_SKBQ_MAX_TXQ_LEN;\n+}\n+\n+static bool __mm81x_skbq_under_threshold(struct mm81x_skbq *mq)\n+{\n+\treturn skb_queue_len(\u0026mq-\u003eskbq) \u003c (MM81X_SKBQ_MAX_TXQ_LEN - 2);\n+}\n+\n+static void __mm81x_skbq_unlink(struct mm81x_skbq *mq,\n+\t\t\t\tstruct sk_buff_head *queue, struct sk_buff *skb)\n+{\n+\tif (queue == \u0026mq-\u003eskbq) {\n+\t\tWARN_ON(skb-\u003elen \u003e mq-\u003eskbq_size);\n+\t\tmq-\u003eskbq_size -= min(skb-\u003elen, mq-\u003eskbq_size);\n+\t}\n+\n+\t__skb_unlink(skb, queue);\n+}\n+\n+static int __mm81x_skbq_put(struct mm81x_skbq *mq, struct sk_buff_head *queue,\n+\t\t\t    struct sk_buff *skb, bool queue_at_head,\n+\t\t\t    struct sk_buff *queue_before)\n+{\n+\t/* Limit the size of the Tx queue, but not the pending queue */\n+\tif (queue == \u0026mq-\u003eskbq) {\n+\t\tif (skb-\u003elen \u003e __mm81x_skbq_space(mq))\n+\t\t\treturn -ENOMEM;\n+\n+\t\tmq-\u003eskbq_size += skb-\u003elen;\n+\t}\n+\n+\tif (queue_before)\n+\t\t__skb_queue_before(queue, queue_before, skb);\n+\telse if (queue_at_head)\n+\t\t__skb_queue_head(queue, skb);\n+\telse\n+\t\t__skb_queue_tail(queue, skb);\n+\n+\treturn 0;\n+}\n+\n+static void __mm81x_skbq_pkt_id(struct mm81x_skbq *mq, struct sk_buff *skb)\n+{\n+\tstruct mm81x_skb_hdr *hdr = (struct mm81x_skb_hdr *)skb-\u003edata;\n+\n+\thdr-\u003etx_info.pkt_id = cpu_to_le32(mq-\u003epkt_seq++);\n+}\n+\n+static struct mm81x_skbq *\n+__mm81x_skbq_tx_status_to_skbq(struct mm81x *mors,\n+\t\t\t       const struct mm81x_skb_tx_status *tx_sts)\n+{\n+\tint aci;\n+\tstruct mm81x_skbq *mq = NULL;\n+\n+\tswitch (tx_sts-\u003echannel) {\n+\tcase MM81X_SKB_CHAN_DATA:\n+\tcase MM81X_SKB_CHAN_DATA_NOACK:\n+\t\taci = dot11_tid_to_ac(tx_sts-\u003etid);\n+\t\tmq = mm81x_hif_get_tx_data_queue(mors, aci);\n+\t\tbreak;\n+\tcase MM81X_SKB_CHAN_MGMT:\n+\t\tmq = mm81x_hif_get_tx_mgmt_queue(mors);\n+\t\tbreak;\n+\tcase MM81X_SKB_CHAN_BEACON:\n+\t\tmq = mm81x_hif_get_tx_beacon_queue(mors);\n+\t\tbreak;\n+\tdefault:\n+\t\tdev_err(mors-\u003edev,\n+\t\t\t\"unexpected channel on reported tx status [%d]\",\n+\t\t\ttx_sts-\u003echannel);\n+\t}\n+\n+\treturn mq;\n+}\n+\n+void mm81x_skbq_pull_hdr_post_tx(struct sk_buff *skb)\n+{\n+\tskb_pull(skb, sizeof(struct mm81x_skb_hdr) +\n+\t\t\t      ((struct mm81x_skb_hdr *)skb-\u003edata)-\u003eoffset);\n+}\n+\n+static void mm81x_skbq_insert_pending(struct mm81x_skbq *mq,\n+\t\t\t\t      struct sk_buff *skb, __le32 insertion_id)\n+{\n+\tstruct sk_buff *pfirst, *pnext;\n+\tstruct mm81x_skb_hdr *mhdr;\n+\tstruct sk_buff *tail = skb_peek_tail(\u0026mq-\u003eskbq);\n+\n+\t__mm81x_skbq_unlink(mq, \u0026mq-\u003epending, skb);\n+\n+\tif (!tail) {\n+\t\t__mm81x_skbq_put(mq, \u0026mq-\u003eskbq, skb, false, NULL);\n+\t\treturn;\n+\t}\n+\n+\t/* Check if it should just be inserted on to the end */\n+\tmhdr = (struct mm81x_skb_hdr *)tail-\u003edata;\n+\tWARN_ON(insertion_id == mhdr-\u003etx_info.pkt_id);\n+\tif (le32_to_cpu(insertion_id) \u003e= le32_to_cpu(mhdr-\u003etx_info.pkt_id)) {\n+\t\t__mm81x_skbq_put(mq, \u0026mq-\u003eskbq, skb, false, NULL);\n+\t\treturn;\n+\t}\n+\n+\t/* Otherwise, re-insert to correct spot in skbq */\n+\tskb_queue_walk_safe(\u0026mq-\u003eskbq, pfirst, pnext) {\n+\t\tmhdr = (struct mm81x_skb_hdr *)pfirst-\u003edata;\n+\n+\t\tWARN_ON(insertion_id == mhdr-\u003etx_info.pkt_id);\n+\t\tif (le32_to_cpu(insertion_id) \u003c=\n+\t\t    le32_to_cpu(mhdr-\u003etx_info.pkt_id)) {\n+\t\t\t__mm81x_skbq_put(mq, \u0026mq-\u003eskbq, skb, false, pfirst);\n+\t\t\treturn;\n+\t\t}\n+\t}\n+\n+\tWARN_ON_ONCE(1);\n+}\n+\n+static void mm81x_skbq_sta_eosp(struct mm81x *mors, struct sk_buff *skb)\n+{\n+\tstruct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);\n+\tstruct ieee80211_vif *vif = txi-\u003econtrol.vif;\n+\n+\tmm81x_skbq_pull_hdr_post_tx(skb);\n+\n+\t/*\n+\t * If this frame is the last frame in a PS-Poll or u-APSD SP,\n+\t * then mac80211 must be informed that the SP is now over.\n+\t */\n+\tif (txi-\u003eflags \u0026 IEEE80211_TX_STATUS_EOSP) {\n+\t\tstruct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb-\u003edata;\n+\t\tstruct ieee80211_sta *sta;\n+\n+\t\tscoped_guard(rcu) {\n+\t\t\tsta = ieee80211_find_sta(vif, hdr-\u003eaddr1);\n+\t\t\tif (sta)\n+\t\t\t\tieee80211_sta_eosp(sta);\n+\t\t}\n+\t}\n+}\n+\n+static void __mm81x_skbq_drop_pending_skb(struct mm81x_skbq *mq,\n+\t\t\t\t\t  struct sk_buff *skb)\n+{\n+\t__mm81x_skbq_unlink(mq, \u0026mq-\u003epending, skb);\n+\tmm81x_skbq_sta_eosp(mq-\u003emors, skb);\n+\tieee80211_free_txskb(mq-\u003emors-\u003ehw, skb);\n+}\n+\n+static bool mm81x_tx_h_is_ps_filtered(struct mm81x_skbq *mq,\n+\t\t\t\t      struct sk_buff *skb,\n+\t\t\t\t      struct mm81x_skb_tx_status *tx_sts)\n+{\n+\tstruct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);\n+\tstruct ieee80211_vif *vif = txi-\u003econtrol.vif;\n+\n+\tWARN_ON_ONCE(!(le32_to_cpu(tx_sts-\u003eflags) \u0026\n+\t\t       MM81X_TX_STATUS_FLAGS_PS_FILTERED));\n+\n+\tif (vif-\u003etype == NL80211_IFTYPE_AP) {\n+\t\t__mm81x_skbq_drop_pending_skb(mq, skb);\n+\t\treturn true;\n+\t}\n+\n+\tif (vif-\u003etype == NL80211_IFTYPE_STATION) {\n+\t\tmm81x_skbq_insert_pending(mq, skb, tx_sts-\u003epkt_id);\n+\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\n+\n+/*\n+ * Get a pending frame by its ID. This will also drop frames with\n+ * older packet ids that are in the list\n+ */\n+static struct sk_buff *__mm81x_skbq_get_pending_by_id(struct mm81x *mors,\n+\t\t\t\t\t\t      struct mm81x_skbq *mq,\n+\t\t\t\t\t\t      u32 pkt_id)\n+{\n+\tstruct sk_buff *pfirst, *pnext;\n+\tstruct sk_buff *ret = NULL;\n+\n+\t/* Move sent packets to pending list waiting for feedback */\n+\tskb_queue_walk_safe(\u0026mq-\u003epending, pfirst, pnext) {\n+\t\tstruct mm81x_skb_hdr *hdr =\n+\t\t\t(struct mm81x_skb_hdr *)pfirst-\u003edata;\n+\n+\t\tif (le32_to_cpu(hdr-\u003etx_info.pkt_id) == pkt_id) {\n+\t\t\tret = pfirst;\n+\t\t\tbreak;\n+\n+\t\t} else if (le32_to_cpu(hdr-\u003etx_info.pkt_id) \u003c pkt_id \u0026\u0026\n+\t\t\t   __mm81x_skbq_has_pending_tx_skb_timed_out(pfirst)) {\n+\t\t\t__mm81x_skbq_drop_pending_skb(mq, pfirst);\n+\t\t}\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static void mm81x_skbq_check_tx_empty(struct mm81x *mors, struct mm81x_skbq *mq)\n+{\n+\tlockdep_assert_held(\u0026mq-\u003elock);\n+\n+\tif (mq-\u003eflags \u0026 MM81X_HIF_FLAGS_BEACON)\n+\t\treturn;\n+\n+\tif (skb_queue_len(\u0026mq-\u003eskbq) + skb_queue_len(\u0026mq-\u003epending) == 0)\n+\t\twake_up(\u0026mq-\u003emors-\u003etx_empty_waitq);\n+}\n+\n+static void __mm81x_skbq_tx_status_process(struct mm81x *mors,\n+\t\t\t\t\t   struct mm81x_skbq *mq,\n+\t\t\t\t\t   struct mm81x_skb_tx_status *tx_sts)\n+{\n+\tstruct sk_buff *skb;\n+\n+\tlockdep_assert_held(\u0026mq-\u003elock);\n+\n+\tskb = __mm81x_skbq_get_pending_by_id(mors, mq,\n+\t\t\t\t\t     le32_to_cpu(tx_sts-\u003epkt_id));\n+\tif (!skb) {\n+\t\tdev_dbg(mors-\u003edev,\n+\t\t\t\"No pending pkt match found [pktid:%d chan:%d]\",\n+\t\t\ttx_sts-\u003epkt_id, tx_sts-\u003echannel);\n+\t\tgoto out;\n+\t}\n+\n+\tif (le32_to_cpu(tx_sts-\u003eflags) \u0026 MM81X_TX_STATUS_PAGE_INVALID) {\n+\t\t__mm81x_skbq_drop_pending_skb(mq, skb);\n+\t\tgoto out;\n+\t}\n+\n+\tif (le32_to_cpu(tx_sts-\u003eflags) \u0026 MM81X_TX_STATUS_FLAGS_PS_FILTERED \u0026\u0026\n+\t    mm81x_tx_h_is_ps_filtered(mq, skb, tx_sts))\n+\t\t/* Has been consumed by mm81x_tx_h_is_ps_filtered */\n+\t\tgoto out;\n+\n+\tmm81x_skbq_pull_hdr_post_tx(skb);\n+\tmm81x_skbq_skb_finish(mq, skb, tx_sts);\n+\n+out:\n+\tmm81x_skbq_check_tx_empty(mors, mq);\n+}\n+\n+static void mm81x_skbq_tx_status_process(struct mm81x *mors,\n+\t\t\t\t\t struct sk_buff *skb)\n+{\n+\tint i;\n+\tstruct mm81x_skb_tx_status *tx_sts =\n+\t\t(struct mm81x_skb_tx_status *)skb-\u003edata;\n+\tint count = skb-\u003elen / sizeof(*tx_sts);\n+\n+\tfor (i = 0; i \u003c count; tx_sts++, i++) {\n+\t\tstruct mm81x_skbq *mq =\n+\t\t\t__mm81x_skbq_tx_status_to_skbq(mors, tx_sts);\n+\n+\t\tif (mq) {\n+\t\t\tspin_lock_bh(\u0026mq-\u003elock);\n+\t\t\t__mm81x_skbq_tx_status_process(mors, mq, tx_sts);\n+\t\t\tspin_unlock_bh(\u0026mq-\u003elock);\n+\t\t}\n+\t}\n+\n+\tif (mors-\u003eps.enable \u0026\u0026 !mors-\u003eps.suspended \u0026\u0026\n+\t    (mm81x_hif_get_tx_buffered_count(mors) == 0)) {\n+\t\t/* Evaluate ps, check if it was gated on a pending tx status */\n+\t\tqueue_delayed_work(mors-\u003echip_wq, \u0026mors-\u003eps.delayed_eval_work,\n+\t\t\t\t   0);\n+\t}\n+}\n+\n+static void mm81x_skbq_dispatch_work(struct work_struct *dispatch_work)\n+{\n+\tstruct mm81x_skbq *mq =\n+\t\tcontainer_of(dispatch_work, struct mm81x_skbq, dispatch_work);\n+\tstruct mm81x *mors = mq-\u003emors;\n+\tstruct mm81x_skb_hdr *hdr;\n+\tstruct sk_buff_head skbq;\n+\tstruct sk_buff *pfirst, *pnext;\n+\tu8 channel;\n+\n+\t__skb_queue_head_init(\u0026skbq);\n+\n+\tmm81x_skbq_deq_num_skb(mq, \u0026skbq, mm81x_skbq_count(mq));\n+\n+\tskb_queue_walk_safe(\u0026skbq, pfirst, pnext) {\n+\t\t__skb_unlink(pfirst, \u0026skbq);\n+\t\t/* Header endianness has already be adjusted */\n+\t\thdr = (struct mm81x_skb_hdr *)pfirst-\u003edata;\n+\t\tchannel = hdr-\u003echannel;\n+\t\t/* Remove mm81x header and padding */\n+\t\t__skb_pull(pfirst, sizeof(*hdr) + hdr-\u003eoffset);\n+\n+\t\tswitch (channel) {\n+\t\tcase MM81X_SKB_CHAN_COMMAND:\n+\t\t\tmm81x_cmd_resp_process(mors, pfirst);\n+\t\t\tbreak;\n+\t\tcase MM81X_SKB_CHAN_TX_STATUS:\n+\t\t\tmm81x_skbq_tx_status_process(mors, pfirst);\n+\t\t\tdev_kfree_skb_any(pfirst);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tmm81x_mac_rx_skb(mors, pfirst, \u0026hdr-\u003erx_status);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (mm81x_skbq_count(mq))\n+\t\tqueue_work(mors-\u003enet_wq, \u0026mq-\u003edispatch_work);\n+}\n+\n+int mm81x_skbq_put(struct mm81x_skbq *mq, struct sk_buff *skb)\n+{\n+\tint ret;\n+\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\tret = __mm81x_skbq_put(mq, \u0026mq-\u003eskbq, skb, false, NULL);\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+\treturn ret;\n+}\n+\n+static void mm81x_skbq_set_queued_tx_skb_expiry(struct sk_buff *skb)\n+{\n+\tstruct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb-\u003edata;\n+\tstruct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);\n+\n+\tif (ieee80211_is_probe_req(hdr-\u003eframe_control) ||\n+\t    ieee80211_is_probe_resp(hdr-\u003eframe_control) ||\n+\t    ieee80211_is_auth(hdr-\u003eframe_control)) {\n+\t\ttxi-\u003econtrol.enqueue_time = (u32)jiffies;\n+\t} else {\n+\t\ttxi-\u003econtrol.enqueue_time = 0;\n+\t}\n+}\n+\n+static bool mm81x_skbq_has_queued_tx_skb_expired(struct sk_buff *skb)\n+{\n+\tstruct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);\n+\n+\tif (txi-\u003econtrol.enqueue_time \u003e 0) {\n+\t\tu32 expiry_time =\n+\t\t\ttxi-\u003econtrol.enqueue_time +\n+\t\t\tmsecs_to_jiffies(MM81X_SKBQ_TX_QUEUED_LIFETIME_MS);\n+\n+\t\treturn (s32)((u32)jiffies - expiry_time) \u003e 0;\n+\t}\n+\n+\treturn false;\n+}\n+\n+/*\n+ * Drop selected frames (those with an expiry time set) that could not\n+ * be sent within a reasonable timeframe due to congestion. These would\n+ * only be rejected or ignored by the peer, so are only contributing to\n+ * the problem.\n+ */\n+void mm81x_skbq_purge_aged(struct mm81x *mors, struct mm81x_skbq *mq)\n+{\n+\tstruct sk_buff *pfirst;\n+\tstruct sk_buff *pnext;\n+\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\tskb_queue_walk_safe(\u0026mq-\u003eskbq, pfirst, pnext) {\n+\t\tif (!mm81x_skbq_has_queued_tx_skb_expired(pfirst))\n+\t\t\tbreak;\n+\t\t__mm81x_skbq_unlink(mq, \u0026mq-\u003eskbq, pfirst);\n+\t\tieee80211_free_txskb(mors-\u003ehw, pfirst);\n+\t}\n+\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+}\n+\n+void mm81x_skbq_purge(struct mm81x_skbq *mq, struct sk_buff_head *skbq)\n+{\n+\tstruct sk_buff *skb;\n+\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\twhile ((skb = __skb_dequeue(skbq)))\n+\t\tdev_kfree_skb_any(skb);\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+}\n+\n+void mm81x_skbq_enq(struct mm81x_skbq *mq, struct sk_buff_head *skbq)\n+{\n+\tint size;\n+\tstruct sk_buff *pfirst, *pnext;\n+\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\tsize = __mm81x_skbq_space(mq);\n+\tskb_queue_walk_safe(skbq, pfirst, pnext) {\n+\t\tif (pfirst-\u003elen \u003e size)\n+\t\t\tbreak;\n+\t\t__skb_unlink(pfirst, skbq);\n+\t\t__mm81x_skbq_put(mq, \u0026mq-\u003eskbq, pfirst, false, NULL);\n+\t\tsize -= pfirst-\u003elen;\n+\t}\n+\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+}\n+\n+int mm81x_skbq_deq_num_skb(struct mm81x_skbq *mq, struct sk_buff_head *skbq,\n+\t\t\t   int num_skb)\n+{\n+\tint count = 0;\n+\tstruct sk_buff *pfirst, *pnext;\n+\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\tskb_queue_walk_safe(\u0026mq-\u003eskbq, pfirst, pnext) {\n+\t\tif (count \u003e= num_skb)\n+\t\t\tbreak;\n+\t\t__mm81x_skbq_unlink(mq, \u0026mq-\u003eskbq, pfirst);\n+\t\t__skb_queue_tail(skbq, pfirst);\n+\t\t++count;\n+\t}\n+\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+\treturn count;\n+}\n+\n+void mm81x_skbq_enq_prepend(struct mm81x_skbq *mq, struct sk_buff_head *skbq)\n+{\n+\tint size;\n+\tstruct sk_buff *pfirst, *pnext;\n+\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\tsize = __mm81x_skbq_space(mq);\n+\n+\t/*\n+\t * We are doing a reverse walk here to ensure the order remains the\n+\t * same. This means the last member of the queue goes in, on top of\n+\t * the queue first and gets pushed down as more members get added to\n+\t * the top of the queue.\n+\t */\n+\tskb_queue_reverse_walk_safe(skbq, pfirst, pnext) {\n+\t\tif (pfirst-\u003elen \u003e size)\n+\t\t\tbreak;\n+\t\t__skb_unlink(pfirst, skbq);\n+\t\t__mm81x_skbq_put(mq, \u0026mq-\u003eskbq, pfirst, true, NULL);\n+\t\tsize -= pfirst-\u003elen;\n+\t}\n+\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+}\n+\n+static void mm81x_skbq_stop_tx_queues(struct mm81x *mors)\n+{\n+\tint queue;\n+\n+\tif (!mors-\u003estarted)\n+\t\treturn;\n+\tfor (queue = IEEE80211_AC_VO; queue \u003c= IEEE80211_AC_BK; queue++)\n+\t\tieee80211_stop_queue(mors-\u003ehw, queue);\n+\n+\tset_bit(MM81X_STATE_DATA_QS_STOPPED, \u0026mors-\u003estate_flags);\n+}\n+\n+/* Wake all Tx queues if all queues are below threshold */\n+void mm81x_skbq_may_wake_tx_queues(struct mm81x *mors)\n+{\n+\tint queue;\n+\tstruct mm81x_skbq *qs;\n+\tint num_qs;\n+\tbool could_wake;\n+\n+\tif (!mors-\u003estarted)\n+\t\treturn;\n+\n+\tcould_wake = true;\n+\tmm81x_hif_skbq_get_tx_qs(mors, \u0026qs, \u0026num_qs);\n+\tfor (queue = 0; queue \u003c num_qs; queue++) {\n+\t\tstruct mm81x_skbq *mq = \u0026qs[queue];\n+\n+\t\tif (!could_wake)\n+\t\t\tbreak;\n+\n+\t\tspin_lock_bh(\u0026mq-\u003elock);\n+\t\tcould_wake \u0026= (__mm81x_skbq_under_threshold(mq));\n+\t\tspin_unlock_bh(\u0026mq-\u003elock);\n+\t}\n+\n+\tif (!could_wake)\n+\t\treturn;\n+\n+\tfor (queue = IEEE80211_AC_VO; queue \u003c= IEEE80211_AC_BK; queue++)\n+\t\tieee80211_wake_queue(mors-\u003ehw, queue);\n+\n+\tclear_bit(MM81X_STATE_DATA_QS_STOPPED, \u0026mors-\u003estate_flags);\n+}\n+\n+static int mm81x_skbq_tx(struct mm81x_skbq *mq, struct sk_buff *skb, u8 channel)\n+{\n+\tint rc;\n+\tbool mq_over_threshold;\n+\tstruct mm81x *mors = mq-\u003emors;\n+\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\trc = __mm81x_skbq_put(mq, \u0026mq-\u003eskbq, skb, false, NULL);\n+\tif (rc) {\n+\t\tdev_err(mors-\u003edev, \"skb put chan %d failed (%d)\", channel, rc);\n+\t\tif (channel == MM81X_SKB_CHAN_DATA) {\n+\t\t\tu16 queue = skb_get_queue_mapping(skb);\n+\n+\t\t\tdev_err(mors-\u003edev, \"skb put queue %d status %d\", queue,\n+\t\t\t\tieee80211_queue_stopped(mors-\u003ehw, queue));\n+\t\t}\n+\t}\n+\n+\t/* Fill packet ID in TX info */\n+\t__mm81x_skbq_pkt_id(mq, skb);\n+\n+\tmq_over_threshold = __mm81x_skbq_over_threshold(mq);\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+\n+\t/* For data packets stop queues */\n+\tif (channel == MM81X_SKB_CHAN_DATA \u0026\u0026 mq_over_threshold)\n+\t\tmm81x_skbq_stop_tx_queues(mors);\n+\n+\tswitch (channel) {\n+\tcase MM81X_SKB_CHAN_DATA:\n+\tcase MM81X_SKB_CHAN_DATA_NOACK:\n+\t\tif (mm81x_is_data_tx_allowed(mors)) {\n+\t\t\tset_bit(MM81X_HIF_EVT_TX_DATA_PEND,\n+\t\t\t\t\u0026mors-\u003ehif.event_flags);\n+\t\t\tqueue_work(mors-\u003echip_wq, \u0026mors-\u003ehif_work);\n+\t\t}\n+\t\tbreak;\n+\tcase MM81X_SKB_CHAN_MGMT:\n+\t\tset_bit(MM81X_HIF_EVT_TX_MGMT_PEND, \u0026mors-\u003ehif.event_flags);\n+\t\tqueue_work(mors-\u003echip_wq, \u0026mors-\u003ehif_work);\n+\t\tbreak;\n+\tcase MM81X_SKB_CHAN_BEACON:\n+\t\tset_bit(MM81X_HIF_EVT_TX_BEACON_PEND, \u0026mors-\u003ehif.event_flags);\n+\t\tqueue_work(mors-\u003echip_wq, \u0026mors-\u003ehif_work);\n+\t\tbreak;\n+\tcase MM81X_SKB_CHAN_COMMAND:\n+\t\tset_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, \u0026mors-\u003ehif.event_flags);\n+\t\tqueue_work(mors-\u003echip_wq, \u0026mors-\u003ehif_work);\n+\t\tbreak;\n+\tdefault:\n+\t\tdev_err(mors-\u003edev, \"Invalid skb channel: %d\", channel);\n+\t\tbreak;\n+\t}\n+\n+\treturn rc;\n+}\n+\n+static void __mm81x_skbq_tx_move_to_pending(struct mm81x_skbq *mq,\n+\t\t\t\t\t    struct sk_buff *skb)\n+{\n+\tstruct mm81x_tx_status_priv *pend_info =\n+\t\t__mm81x_skbq_tx_status_priv(skb);\n+\n+\tpend_info-\u003etx_status_expiry =\n+\t\tjiffies + msecs_to_jiffies(MM81X_SKBQ_TX_STATUS_LIFETIME_MS);\n+\t__mm81x_skbq_put(mq, \u0026mq-\u003epending, skb, false, NULL);\n+}\n+\n+void mm81x_skbq_tx_complete(struct mm81x_skbq *mq, struct sk_buff_head *skbq)\n+{\n+\tbool skb_awaits_tx_status = false;\n+\tstruct mm81x *mors = mq-\u003emors;\n+\tstruct sk_buff *pfirst, *pnext;\n+\tstruct sk_buff *peek = skb_peek(skbq);\n+\tstruct mm81x_skb_hdr *hdr;\n+\tconst bool fw_reports_bcn_tx_status =\n+\t\tmors-\u003efirmware_flags \u0026\n+\t\tMM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION;\n+\n+\tif (!peek)\n+\t\treturn;\n+\n+\t/* Move sent packets to pending list waiting for feedback */\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\tskb_queue_walk_safe(skbq, pfirst, pnext) {\n+\t\t__skb_unlink(pfirst, skbq);\n+\t\thdr = (struct mm81x_skb_hdr *)pfirst-\u003edata;\n+\t\t/*\n+\t\t * If firmware doesn't give status on beacons just free\n+\t\t * them, otherwise queue and wait for response.\n+\t\t */\n+\t\tswitch (hdr-\u003echannel) {\n+\t\tcase MM81X_SKB_CHAN_BEACON:\n+\t\t\tif (fw_reports_bcn_tx_status) {\n+\t\t\t\t__mm81x_skbq_tx_move_to_pending(mq, pfirst);\n+\t\t\t\tskb_awaits_tx_status = true;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\t/*\n+\t\t\t * If the FW doesn't give statuses on beacon's,\n+\t\t\t * then mark them as done.\n+\t\t\t */\n+\t\t\tmm81x_skbq_pull_hdr_post_tx(pfirst);\n+\t\t\tdev_kfree_skb_any(pfirst);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tif (le32_to_cpu(hdr-\u003etx_info.flags) \u0026\n+\t\t\t    MM81X_TX_STATUS_FLAGS_NO_REPORT) {\n+\t\t\t\tdev_kfree_skb_any(pfirst);\n+\t\t\t} else {\n+\t\t\t\t/*\n+\t\t\t\t * skb has been given to the chip. Store the\n+\t\t\t\t * time and queue the skb onto the pending\n+\t\t\t\t * queue while we wait for the tx_status.\n+\t\t\t\t */\n+\t\t\t\t__mm81x_skbq_tx_move_to_pending(mq, pfirst);\n+\t\t\t\tskb_awaits_tx_status = true;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+\n+\tif (skb_awaits_tx_status) {\n+\t\tspin_lock_bh(\u0026mors-\u003estale_status.lock);\n+\t\tmod_timer(\u0026mors-\u003estale_status.timer,\n+\t\t\t  jiffies + msecs_to_jiffies(\n+\t\t\t\t\t    MM81X_SKBQ_TX_STATUS_LIFETIME_MS));\n+\t\tspin_unlock_bh(\u0026mors-\u003estale_status.lock);\n+\t}\n+}\n+\n+/* Returns the first skb in the pending list. */\n+struct sk_buff *mm81x_skbq_tx_pending(struct mm81x_skbq *mq)\n+{\n+\tstruct sk_buff *pfirst;\n+\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\tpfirst = skb_peek(\u0026mq-\u003epending);\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+\treturn pfirst;\n+}\n+\n+int mm81x_skbq_check_for_stale_tx(struct mm81x *mors, struct mm81x_skbq *mq)\n+{\n+\tint flushed = 0;\n+\tstruct sk_buff *pfirst;\n+\tstruct sk_buff *pnext;\n+\n+\tif (!skb_queue_len(\u0026mq-\u003epending))\n+\t\treturn 0;\n+\n+\t/* Move sent packets to pending list waiting for feedback */\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\tskb_queue_walk_safe(\u0026mq-\u003epending, pfirst, pnext) {\n+\t\tstruct mm81x_skb_hdr *hdr =\n+\t\t\t(struct mm81x_skb_hdr *)pfirst-\u003edata;\n+\n+\t\tif (__mm81x_skbq_has_pending_tx_skb_timed_out(pfirst)) {\n+\t\t\tdev_dbg(mors-\u003edev, \"TX skb timed out [id:%d,chan:%d]\",\n+\t\t\t\thdr-\u003etx_info.pkt_id, hdr-\u003echannel);\n+\n+\t\t\t__mm81x_skbq_drop_pending_skb(mq, pfirst);\n+\t\t\tflushed++;\n+\t\t}\n+\t}\n+\n+\tif (flushed)\n+\t\tmm81x_skbq_check_tx_empty(mors, mq);\n+\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+\treturn flushed;\n+}\n+\n+/* Remove commands from pending (or skbq if not sent) */\n+static void __skbq_cmd_finish(struct mm81x_skbq *mq, struct sk_buff *skb)\n+{\n+\tstruct mm81x *mors = mq-\u003emors;\n+\n+\tif (skb_queue_len(\u0026mq-\u003epending)) {\n+\t\t__mm81x_skbq_unlink(mq, \u0026mq-\u003epending, skb);\n+\t\tdev_kfree_skb(skb);\n+\t} else if (skb_queue_len(\u0026mq-\u003eskbq)) {\n+\t\t/* Command was probably timed out before being sent */\n+\t\tdev_dbg(mors-\u003edev,\n+\t\t\t\"Command pending queue empty. Removing from SKBQ.\");\n+\t\t__mm81x_skbq_unlink(mq, \u0026mq-\u003eskbq, skb);\n+\t\tdev_kfree_skb(skb);\n+\t} else {\n+\t\tdev_dbg(mors-\u003edev, \"Command Q not found\");\n+\t}\n+}\n+\n+struct mm81x_update_sta_iter_data {\n+\tstruct mm81x *mors;\n+\tstruct sk_buff *skb;\n+\tstruct mm81x_skb_tx_status *tx_sts;\n+\tint tx_attempts;\n+\tbool updated;\n+};\n+\n+static void mm81x_tx_h_update_sta_iter(void *data, u8 *mac,\n+\t\t\t\t       struct ieee80211_vif *vif)\n+{\n+\tstruct mm81x_update_sta_iter_data *iter = data;\n+\tstruct ieee80211_hdr *hdr;\n+\tstruct ieee80211_sta *sta;\n+\n+\tif (iter-\u003eupdated || !iter-\u003eskb || !iter-\u003eskb-\u003edata)\n+\t\treturn;\n+\n+\thdr = (struct ieee80211_hdr *)iter-\u003eskb-\u003edata;\n+\n+\t/*\n+\t * Note that each iteration via\n+\t * ieee80211_iterate_active_interfaces_atomic is under an RCU critical\n+\t * section so there is no need for a local critical section within here\n+\t * when looking up the station.\n+\t */\n+\tsta = ieee80211_find_sta(vif, hdr-\u003eaddr1);\n+\tif (!sta)\n+\t\treturn;\n+\n+\tmm81x_rc_sta_feedback_rates(iter-\u003emors, iter-\u003eskb, sta, iter-\u003etx_sts,\n+\t\t\t\t    iter-\u003etx_attempts);\n+\tmm81x_tx_h_check_aggr(sta, iter-\u003eskb);\n+\n+\t/*\n+\t * In situations with multiple virtual interfaces, finish iteration\n+\t * once we have found our STA to prevent further iteration.\n+\t */\n+\titer-\u003eupdated = true;\n+}\n+\n+/* TX status/Response received remove packet from pending TX finish */\n+static void __skbq_data_tx_finish(struct mm81x_skbq *mq, struct sk_buff *skb,\n+\t\t\t\t  struct mm81x_skb_tx_status *tx_sts)\n+{\n+\tstruct mm81x *mors = mq-\u003emors;\n+\tstruct mm81x_update_sta_iter_data iter = {};\n+\n+\t__mm81x_skbq_unlink(mq, \u0026mq-\u003epending, skb);\n+\titer.mors = mors;\n+\titer.skb = skb;\n+\titer.tx_sts = tx_sts;\n+\titer.tx_attempts = mm81x_tx_h_get_attempts(mors, tx_sts);\n+\n+\tieee80211_iterate_active_interfaces_atomic(mors-\u003ehw,\n+\t\t\t\t\t\t   IEEE80211_IFACE_ITER_NORMAL,\n+\t\t\t\t\t\t   mm81x_tx_h_update_sta_iter,\n+\t\t\t\t\t\t   \u0026iter);\n+\n+\tieee80211_tx_status_skb(mors-\u003ehw, skb);\n+}\n+\n+void mm81x_skbq_skb_finish(struct mm81x_skbq *mq, struct sk_buff *skb,\n+\t\t\t   struct mm81x_skb_tx_status *tx_sts)\n+{\n+\tif (mq-\u003eflags \u0026 MM81X_HIF_FLAGS_COMMAND)\n+\t\t__skbq_cmd_finish(mq, skb);\n+\telse\n+\t\t__skbq_data_tx_finish(mq, skb, tx_sts);\n+}\n+\n+void mm81x_skbq_tx_flush(struct mm81x_skbq *mq)\n+{\n+\tstruct sk_buff *pfirst, *pnext;\n+\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\tskb_queue_walk_safe(\u0026mq-\u003epending, pfirst, pnext) {\n+\t\t__mm81x_skbq_unlink(mq, \u0026mq-\u003epending, pfirst);\n+\t\tieee80211_free_txskb(mq-\u003emors-\u003ehw, pfirst);\n+\t}\n+\n+\tskb_queue_walk_safe(\u0026mq-\u003eskbq, pfirst, pnext) {\n+\t\t__mm81x_skbq_unlink(mq, \u0026mq-\u003eskbq, pfirst);\n+\t\tieee80211_free_txskb(mq-\u003emors-\u003ehw, pfirst);\n+\t}\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+}\n+\n+void mm81x_skbq_init(struct mm81x *mors, struct mm81x_skbq *mq, u16 flags)\n+{\n+\tspin_lock_init(\u0026mq-\u003elock);\n+\t__skb_queue_head_init(\u0026mq-\u003eskbq);\n+\t__skb_queue_head_init(\u0026mq-\u003epending);\n+\tmq-\u003emors = mors;\n+\tmq-\u003eskbq_size = 0;\n+\tmq-\u003eflags = flags;\n+\tmq-\u003epkt_seq = 0;\n+\tif (flags \u0026 MM81X_HIF_FLAGS_DIR_TO_HOST)\n+\t\tINIT_WORK(\u0026mq-\u003edispatch_work, mm81x_skbq_dispatch_work);\n+}\n+\n+void mm81x_skbq_finish(struct mm81x_skbq *mq)\n+{\n+\tif (mq-\u003eskbq_size \u003e 0)\n+\t\tdev_dbg(mq-\u003emors-\u003edev,\n+\t\t\t\"Purging a non empty MorseQ. Dropping data!\");\n+\n+\t/* Clean up link to hif */\n+\tif (mq-\u003eflags \u0026 MM81X_HIF_FLAGS_DIR_TO_HOST)\n+\t\tcancel_work_sync(\u0026mq-\u003edispatch_work);\n+\tmm81x_skbq_purge(mq, \u0026mq-\u003eskbq);\n+\tmm81x_skbq_purge(mq, \u0026mq-\u003epending);\n+\tmq-\u003eskbq_size = 0;\n+}\n+\n+u32 mm81x_skbq_size(struct mm81x_skbq *mq)\n+{\n+\tu32 count;\n+\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\tcount = __mm81x_skbq_size(mq);\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+\treturn count;\n+}\n+\n+u32 mm81x_skbq_count(struct mm81x_skbq *mq)\n+{\n+\tu32 count = 0;\n+\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\tcount += skb_queue_len(\u0026mq-\u003eskbq);\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+\treturn count;\n+}\n+\n+u32 mm81x_skbq_pending_count(struct mm81x_skbq *mq)\n+{\n+\tu32 count;\n+\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\tcount = skb_queue_len(\u0026mq-\u003epending);\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+\treturn count;\n+}\n+\n+u32 mm81x_skbq_count_tx_ready(struct mm81x_skbq *mq)\n+{\n+\tstruct mm81x *mors = mq-\u003emors;\n+\n+\tif (!mm81x_is_data_tx_allowed(mors))\n+\t\treturn 0;\n+\n+\treturn mm81x_skbq_count(mq);\n+}\n+\n+u32 mm81x_skbq_space(struct mm81x_skbq *mq)\n+{\n+\tu32 space;\n+\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\tspace = __mm81x_skbq_space(mq);\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+\n+\treturn space;\n+}\n+\n+struct sk_buff *mm81x_skbq_alloc_skb(struct mm81x_skbq *mq, unsigned int length)\n+{\n+\tstruct sk_buff *skb;\n+\tint tx_headroom = sizeof(struct mm81x_skb_hdr) +\n+\t\t\t  mm81x_bus_get_alignment(mq-\u003emors);\n+\tint skb_len = tx_headroom + length + MM81X_PAD4(length);\n+\n+\tskb = dev_alloc_skb(skb_len);\n+\tif (!skb)\n+\t\treturn NULL;\n+\n+\tskb_reserve(skb, tx_headroom);\n+\tskb_put(skb, length);\n+\treturn skb;\n+}\n+\n+static int mm81x_skb_tx_h_validate_channel(const struct mm81x *mors, u8 channel)\n+{\n+\tif (channel == MM81X_SKB_CHAN_COMMAND) {\n+\t\tif (test_bit(MM81X_STATE_HOST_TO_CHIP_CMD_BLOCKED,\n+\t\t\t     \u0026mors-\u003estate_flags))\n+\t\t\treturn -EPERM;\n+\t} else {\n+\t\tif (test_bit(MM81X_STATE_HOST_TO_CHIP_TX_BLOCKED,\n+\t\t\t     \u0026mors-\u003estate_flags))\n+\t\t\treturn -EPERM;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int mm81x_skbq_skb_tx(struct mm81x_skbq *mq, struct sk_buff **skb_orig,\n+\t\t      struct mm81x_skb_tx_info *tx_info, u8 channel)\n+{\n+\tint ret;\n+\tstruct mm81x_skb_hdr hdr;\n+\tstruct mm81x *mors = mq-\u003emors;\n+\tsize_t end_of_skb_pad;\n+\tstruct sk_buff *skb = *skb_orig;\n+\tu8 *aligned_head, *data;\n+\n+\tif (test_bit(MM81X_STATE_CHIP_UNRESPONSIVE, \u0026mors-\u003estate_flags)) {\n+\t\tdev_kfree_skb_any(skb);\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tret = mm81x_skb_tx_h_validate_channel(mors, channel);\n+\tif (ret) {\n+\t\tdev_kfree_skb_any(skb);\n+\t\treturn ret;\n+\t}\n+\n+\tmm81x_skbq_set_queued_tx_skb_expiry(skb);\n+\n+\tdata = skb-\u003edata;\n+\taligned_head = PTR_ALIGN_DOWN((data - sizeof(hdr)),\n+\t\t\t\t      mm81x_bus_get_alignment(mors));\n+\thdr.sync = MM81X_SKB_HEADER_SYNC;\n+\thdr.channel = channel;\n+\thdr.len = cpu_to_le16(skb-\u003elen);\n+\thdr.offset = data - (aligned_head + sizeof(hdr));\n+\thdr.checksum_upper = 0;\n+\thdr.checksum_lower = 0;\n+\tif (tx_info)\n+\t\tmemcpy(\u0026hdr.tx_info, tx_info, sizeof(*tx_info));\n+\telse\n+\t\tmemset(\u0026hdr.tx_info, 0, sizeof(hdr.tx_info));\n+\n+\tskb_push(skb, data - aligned_head);\n+\tmemcpy(skb-\u003edata, \u0026hdr, sizeof(hdr));\n+\n+\tend_of_skb_pad = MM81X_PAD4(skb-\u003elen);\n+\tif (end_of_skb_pad \u0026\u0026 skb_pad(skb, end_of_skb_pad))\n+\t\treturn -EINVAL;\n+\n+\tret = mm81x_skbq_tx(mq, skb, channel);\n+\tif (ret) {\n+\t\tdev_err(mors-\u003edev, \"mm81x_skbq_tx fail: %d\", ret);\n+\t\tdev_kfree_skb_any(skb);\n+\t}\n+\n+\treturn ret;\n+}\n+\n+void mm81x_skbq_data_traffic_pause(struct mm81x *mors)\n+{\n+\tset_bit(MM81X_STATE_DATA_TX_STOPPED, \u0026mors-\u003estate_flags);\n+\t/* power-save requirements will be re-evaluated by the caller */\n+}\n+\n+void mm81x_skbq_data_traffic_resume(struct mm81x *mors)\n+{\n+\tclear_bit(MM81X_STATE_DATA_TX_STOPPED, \u0026mors-\u003estate_flags);\n+\n+\t/* Set the TX_DATA_PEND bit. This will kick the transmission path to\n+\t * send any frames pending in the TX buffers, and wake the mac80211\n+\t * data Qs if they were previously stopped.\n+\t */\n+\tset_bit(MM81X_HIF_EVT_TX_DATA_PEND, \u0026mors-\u003ehif.event_flags);\n+}\n+\n+bool mm81x_skbq_validate_checksum(u8 *data)\n+{\n+\tint i;\n+\tu32 xor = 0;\n+\tstruct mm81x_skb_hdr *skb_hdr = (struct mm81x_skb_hdr *)data;\n+\tstruct ieee80211_hdr *hdr =\n+\t\t(struct ieee80211_hdr *)(data + sizeof(*skb_hdr));\n+\tu16 len = le16_to_cpu(skb_hdr-\u003elen) + sizeof(*skb_hdr);\n+\tu32 *data_to_xor = (u32 *)data;\n+\tu32 header_xor = (le16_to_cpu(skb_hdr-\u003echecksum_upper) \u003c\u003c 8) |\n+\t\t\t (skb_hdr-\u003echecksum_lower);\n+\n+\t/*\n+\t * For data frames the calculate the xor for skb header, mac header\n+\t * and ccmp header. For all other channel the xor is calculated for\n+\t * the full skb.\n+\t */\n+\tif (skb_hdr-\u003echannel == MM81X_SKB_CHAN_DATA \u0026\u0026\n+\t    (ieee80211_is_data(hdr-\u003eframe_control) ||\n+\t     ieee80211_is_data_qos(hdr-\u003eframe_control))) {\n+\t\tu16 data_len = sizeof(*skb_hdr) +\n+\t\t\t       sizeof(struct ieee80211_qos_hdr) +\n+\t\t\t       IEEE80211_CCMP_HDR_LEN;\n+\n+\t\tlen = min(len, data_len);\n+\t\tlen = ROUND_DOWN_TO_WORD(len);\n+\t}\n+\n+\tskb_hdr-\u003echecksum_upper = 0;\n+\tskb_hdr-\u003echecksum_lower = 0;\n+\n+\tfor (i = 0; i \u003c len; i += 4) {\n+\t\txor ^= *data_to_xor;\n+\t\tdata_to_xor++;\n+\t}\n+\n+\txor \u0026= 0x00FFFFFF;\n+\n+\treturn xor == header_xor;\n+}\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/skbq.h b/drivers/net/wireless/morsemicro/mm81x/skbq.h\nnew file mode 100644\nindex 00000000000000..9930493141cfb7\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/skbq.h\n@@ -0,0 +1,218 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#ifndef _MM81X_SKBQ_H_\n+#define _MM81X_SKBQ_H_\n+\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+#include \"rate_code.h\"\n+\n+/* Sync value of skb header to indicate a valid skb */\n+#define MM81X_SKB_HEADER_SYNC (0xAA)\n+/* Sync value indicating that the chip owns this skb */\n+#define MM81X_SKB_HEADER_CHIP_OWNED_SYNC (0xBB)\n+\n+enum mm81x_tx_status_and_conf_flags {\n+\tMM81X_TX_STATUS_FLAGS_NO_ACK = BIT(0),\n+\tMM81X_TX_STATUS_FLAGS_NO_REPORT = BIT(1),\n+\tMM81X_TX_CONF_FLAGS_CTL_AMPDU = BIT(2),\n+\tMM81X_TX_CONF_FLAGS_HW_ENCRYPT = BIT(3),\n+\tMM81X_TX_CONF_FLAGS_VIF_ID = (BIT(4) | BIT(5) | BIT(6) | BIT(7) |\n+\t\t\t\t      BIT(8) | BIT(9) | BIT(10) | BIT(11)),\n+\tMM81X_TX_CONF_FLAGS_KEY_IDX = (BIT(12) | BIT(13) | BIT(14)),\n+\tMM81X_TX_STATUS_FLAGS_PS_FILTERED = (BIT(15)),\n+\tMM81X_TX_CONF_IGNORE_TWT = (BIT(16)),\n+\tMM81X_TX_STATUS_PAGE_INVALID = (BIT(17)),\n+\tMM81X_TX_CONF_NO_PS_BUFFER = (BIT(18)),\n+\tMM81X_TX_STATUS_DUTY_CYCLE_CANT_SEND = (BIT(19)),\n+\tMM81X_TX_CONF_HAS_PV1_BPN_IN_BODY = (BIT(21)),\n+\tMM81X_TX_CONF_FLAGS_SEND_AFTER_DTIM = (BIT(22)),\n+\tMM81X_TX_STATUS_WAS_AGGREGATED = (BIT(23)),\n+\tMM81X_TX_CONF_FLAGS_FULLMAC_REPORT = BIT(24),\n+\tMM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT = (BIT(31))\n+};\n+\n+/* Getter and setter macros for vif id */\n+#define MM81X_TX_CONF_FLAGS_VIF_ID_MASK (0xFF)\n+#define MM81X_TX_CONF_FLAGS_VIF_ID_SET(x) \\\n+\t(((x) \u0026 MM81X_TX_CONF_FLAGS_VIF_ID_MASK) \u003c\u003c 4)\n+#define MM81X_TX_CONF_FLAGS_VIF_ID_GET(x) \\\n+\t(((x) \u0026 MM81X_TX_CONF_FLAGS_VIF_ID) \u003e\u003e 4)\n+\n+/* Getter and setter macros for key index */\n+#define MM81X_TX_CONF_FLAGS_KEY_IDX_SET(x) (((x) \u0026 0x07) \u003c\u003c 12)\n+#define MM81X_TX_CONF_FLAGS_KEY_IDX_GET(x) \\\n+\t(((x) \u0026 MM81X_TX_CONF_FLAGS_KEY_IDX) \u003e\u003e 12)\n+\n+enum mm81x_rx_status_flags {\n+\tMM81X_RX_STATUS_FLAGS_ERROR = BIT(0),\n+\tMM81X_RX_STATUS_FLAGS_DECRYPTED = BIT(1),\n+\tMM81X_RX_STATUS_FLAGS_FCS_INCLUDED = BIT(2),\n+\tMM81X_RX_STATUS_FLAGS_EOF = BIT(3),\n+\tMM81X_RX_STATUS_FLAGS_AMPDU = BIT(4),\n+\tMM81X_RX_STATUS_FLAGS_NDP = BIT(7),\n+\tMM81X_RX_STATUS_FLAGS_UPLINK = BIT(8),\n+\tMM81X_RX_STATUS_FLAGS_RI = (BIT(9) | BIT(10)),\n+\tMM81X_RX_STATUS_FLAGS_NDP_TYPE = (BIT(11) | BIT(12) | BIT(13)),\n+\tMM81X_RX_STATUS_FLAGS_CRC_ERROR = BIT(14),\n+\tMM81X_RX_STATUS_FLAGS_VIF_ID = GENMASK(24, 17),\n+};\n+\n+/* Getter and Setter macros for vif id */\n+#define MM81X_RX_STATUS_FLAGS_VIF_ID_MASK (0xFF)\n+#define MM81X_RX_STATUS_FLAGS_VIF_ID_SET(x) \\\n+\t(((x) \u0026 MM81X_RX_STATUS_FLAGS_VIF_ID_MASK) \u003c\u003c 17)\n+#define MM81X_RX_STATUS_FLAGS_VIF_ID_GET(x) \\\n+\t(((x) \u0026 MM81X_RX_STATUS_FLAGS_VIF_ID) \u003e\u003e 17)\n+#define MM81X_RX_STATUS_FLAGS_VIF_ID_CLEAR(x) \\\n+\t((x) \u0026 ~(MM81X_RX_STATUS_FLAGS_VIF_ID_MASK \u003c\u003c 17))\n+\n+/* Getter macro for guard interval */\n+#define MM81X_RX_STATUS_FLAGS_UPL_IND_GET(x) \\\n+\t(((x) \u0026 MM81X_RX_STATUS_FLAGS_UPLINK) \u003e\u003e 8)\n+\n+/* Getter macro for response indication */\n+#define MM81X_RX_STATUS_FLAGS_RI_GET(x) (((x) \u0026 MM81X_RX_STATUS_FLAGS_RI) \u003e\u003e 9)\n+\n+/* Getter macro for NDP type */\n+#define MM81X_RX_STATUS_FLAGS_NDP_TYPE_GET(x) \\\n+\t(((x) \u0026 MM81X_RX_STATUS_FLAGS_NDP_TYPE) \u003e\u003e 11)\n+\n+enum mm81x_skb_channel {\n+\tMM81X_SKB_CHAN_DATA = 0x0,\n+\tMM81X_SKB_CHAN_NDP_FRAMES = 0x1,\n+\tMM81X_SKB_CHAN_DATA_NOACK = 0x2,\n+\tMM81X_SKB_CHAN_BEACON = 0x3,\n+\tMM81X_SKB_CHAN_MGMT = 0x4,\n+\tMM81X_SKB_CHAN_INTERNAL_CRIT_BEACON = 0x80,\n+\tMM81X_SKB_CHAN_COMMAND = 0xFE,\n+\tMM81X_SKB_CHAN_TX_STATUS = 0xFF\n+};\n+\n+#define MM81X_SKB_MAX_RATES (4)\n+\n+struct mm81x_skb_rate_info {\n+\tmm81x_rate_code_t mm81x_ratecode;\n+\tu8 count;\n+} __packed;\n+\n+struct mm81x_skb_tx_status {\n+\t__le32 flags;\n+\t__le32 pkt_id;\n+\tu8 tid;\n+\tu8 channel;\n+\t__le16 ampdu_info;\n+\tstruct mm81x_skb_rate_info rates[MM81X_SKB_MAX_RATES];\n+} __packed;\n+\n+#define MM81X_TXSTS_AMPDU_INFO_GET_TAG(x) (((x) \u003e\u003e 10) \u0026 0x3F)\n+#define MM81X_TXSTS_AMPDU_INFO_GET_LEN(x) (((x) \u003e\u003e 5) \u0026 0x1F)\n+#define MM81X_TXSTS_AMPDU_INFO_GET_SUC(x) ((x) \u0026 0x1F)\n+\n+struct mm81x_skb_tx_info {\n+\t__le32 flags;\n+\t__le32 pkt_id;\n+\tu8 tid;\n+\tu8 tid_params;\n+\tu8 mmss_params;\n+\tu8 padding[1];\n+\tstruct mm81x_skb_rate_info rates[MM81X_SKB_MAX_RATES];\n+} __packed;\n+\n+#define TX_INFO_TID_PARAMS_MAX_REORDER_BUF 0x1f\n+#define TX_INFO_TID_PARAMS_AMPDU_ENABLED 0x20\n+#define TX_INFO_TID_PARAMS_AMSDU_SUPPORTED 0x40\n+#define TX_INFO_TID_PARAMS_USE_LEGACY_BA 0x80\n+\n+/* Bitmap for MMSS (Minimum MPDU start spacing) parameters\n+ * +-----------+-----------+\n+ * | Morse     | MMSS set  |\n+ * | MMSS      | by S1G cap|\n+ * | offset    | IE        |\n+ * |-----------|-----------|\n+ * |b7|b6|b5|b4|b3|b2|b1|b0|\n+ */\n+#define TX_INFO_MMSS_PARAMS_MMSS_MASK GENMASK(3, 0)\n+#define TX_INFO_MMSS_PARAMS_MMSS_OFFSET_START 4\n+#define TX_INFO_MMSS_PARAMS_MMSS_OFFSET_MASK GENMASK(7, 4)\n+#define TX_INFO_MMSS_PARAMS_SET_MMSS(x) ((x) \u0026 TX_INFO_MMSS_PARAMS_MMSS_MASK)\n+#define TX_INFO_MMSS_PARAMS_SET_MMSS_OFFSET(x)            \\\n+\t(((x) \u003c\u003c TX_INFO_MMSS_PARAMS_MMSS_OFFSET_START) \u0026 \\\n+\t TX_INFO_MMSS_PARAMS_MMSS_OFFSET_MASK)\n+\n+struct mm81x_skb_rx_status {\n+\t__le32 flags;\n+\tmm81x_rate_code_t mm81x_ratecode;\n+\t__le16 rssi;\n+\t__le16 freq_100khz;\n+\tu8 bss_color;\n+\ts8 noise_dbm;\n+\t/** Padding for word alignment */\n+\tu8 padding[2];\n+\t__le64 rx_timestamp_us;\n+} __packed;\n+\n+struct mm81x_skb_hdr {\n+\tu8 sync;\n+\tu8 channel;\n+\t__le16 len;\n+\tu8 offset;\n+\tu8 checksum_lower;\n+\t__le16 checksum_upper;\n+\tunion {\n+\t\tstruct mm81x_skb_tx_info tx_info;\n+\t\tstruct mm81x_skb_tx_status tx_status;\n+\t\tstruct mm81x_skb_rx_status rx_status;\n+\t};\n+} __packed;\n+\n+#define MM81X_SKBQ_SIZE (4 * 128 * 1024)\n+\n+struct mm81x;\n+\n+struct mm81x_skbq {\n+\tstruct mm81x *mors;\n+\tu32 pkt_seq; /* SKB sequence used in tx_status */\n+\tu16 flags;\n+\tu32 skbq_size; /* current off loaded size */\n+\tspinlock_t lock;\n+\tstruct sk_buff_head skbq;\n+\tstruct sk_buff_head pending; /* packets sent pending feedback */\n+\tstruct work_struct dispatch_work;\n+};\n+\n+void mm81x_skbq_purge(struct mm81x_skbq *mq, struct sk_buff_head *skbq);\n+void mm81x_skbq_purge_aged(struct mm81x *mors, struct mm81x_skbq *mq);\n+u32 mm81x_skbq_space(struct mm81x_skbq *mq);\n+u32 mm81x_skbq_size(struct mm81x_skbq *mq);\n+int mm81x_skbq_deq_num_skb(struct mm81x_skbq *mq, struct sk_buff_head *skbq,\n+\t\t\t   int num_skb);\n+struct sk_buff *mm81x_skbq_alloc_skb(struct mm81x_skbq *mq,\n+\t\t\t\t     unsigned int length);\n+int mm81x_skbq_skb_tx(struct mm81x_skbq *mq, struct sk_buff **skb,\n+\t\t      struct mm81x_skb_tx_info *tx_info, u8 channel);\n+int mm81x_skbq_put(struct mm81x_skbq *mq, struct sk_buff *skb);\n+void mm81x_skbq_enq(struct mm81x_skbq *mq, struct sk_buff_head *skbq);\n+void mm81x_skbq_enq_prepend(struct mm81x_skbq *mq, struct sk_buff_head *skbq);\n+void mm81x_skbq_tx_complete(struct mm81x_skbq *mq, struct sk_buff_head *skbq);\n+struct sk_buff *mm81x_skbq_tx_pending(struct mm81x_skbq *mq);\n+void mm81x_skbq_init(struct mm81x *mors, struct mm81x_skbq *mq, u16 flags);\n+void mm81x_skbq_finish(struct mm81x_skbq *mq);\n+void mm81x_skbq_pull_hdr_post_tx(struct sk_buff *skb);\n+void mm81x_skbq_mon_dump(struct mm81x *mors, struct seq_file *file);\n+void mm81x_skbq_skb_finish(struct mm81x_skbq *mq, struct sk_buff *skb,\n+\t\t\t   struct mm81x_skb_tx_status *tx_sts);\n+void mm81x_skbq_tx_flush(struct mm81x_skbq *mq);\n+int mm81x_skbq_check_for_stale_tx(struct mm81x *mors, struct mm81x_skbq *mq);\n+void mm81x_skbq_may_wake_tx_queues(struct mm81x *mors);\n+u32 mm81x_skbq_count_tx_ready(struct mm81x_skbq *mq);\n+u32 mm81x_skbq_count(struct mm81x_skbq *mq);\n+u32 mm81x_skbq_pending_count(struct mm81x_skbq *mq);\n+void mm81x_skbq_data_traffic_pause(struct mm81x *mors);\n+void mm81x_skbq_data_traffic_resume(struct mm81x *mors);\n+bool mm81x_skbq_validate_checksum(u8 *data);\n+\n+#endif /* !_MM81X_SKBQ_H_ */\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/usb.c b/drivers/net/wireless/morsemicro/mm81x/usb.c\nnew file mode 100644\nindex 00000000000000..ec94936b157bbd\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/usb.c\n@@ -0,0 +1,943 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+#include \u003clinux/jiffies.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/usb.h\u003e\n+#include \"hif.h\"\n+#include \"bus.h\"\n+#include \"mac.h\"\n+#include \"core.h\"\n+\n+/*\n+ * URB timeout in milliseconds. If an URB does not complete within this\n+ * time, it will be killed. This timeout needs to account for USB suspendand\n+ * resume occurring before the URB can be transferred, and it also needs to\n+ * account for transferring USB_MAX_TRANSFER_SIZE bytes over a potentially\n+ * slow, congested USB Full Speed link.\n+ */\n+#define URB_TIMEOUT_MS 250\n+\n+/* High speed USB 2^(4-1) * 125usec = 1msec */\n+#define MM81X_USB_INTERRUPT_INTERVAL 4\n+\n+/* Max bytes per USB read/write */\n+#define USB_MAX_TRANSFER_SIZE (16 * 1024)\n+\n+/* INT EP buffer size */\n+#define MM81X_EP_INT_BUFFER_SIZE 8\n+\n+/* Morse vendor IDs*/\n+#define MM81X_VENDOR_ID 0x325b\n+#define MM81X_MM810X_PRODUCT_ID 0x8100\n+\n+/* Power management runtime auto-suspend delay value in milliseconds */\n+#define PM_RUNTIME_AUTOSUSPEND_DELAY_MS 100\n+\n+enum mm81x_usb_endpoints {\n+\tMM81X_EP_CMD = 0,\n+\tMM81X_EP_INT,\n+\tMM81X_EP_MEM_RD,\n+\tMM81X_EP_MEM_WR,\n+\tMM81X_EP_REG_RD,\n+\tMM81X_EP_REG_WR,\n+\tMM81X_EP_EP_MAX,\n+};\n+\n+struct mm81x_usb_endpoint {\n+\tunsigned char *buffer;\n+\tstruct urb *urb;\n+\t__u8 addr;\n+\tint size;\n+};\n+\n+enum mm81x_usb_flags { MM81X_USB_FLAG_ATTACHED, MM81X_USB_FLAG_SUSPENDED };\n+\n+struct mm81x_usb {\n+\tstruct usb_device *udev;\n+\tstruct usb_interface *interface;\n+\tstruct mm81x_usb_endpoint endpoints[MM81X_EP_EP_MAX];\n+\tint errors;\n+\n+\t/* serialise USB device struct */\n+\tstruct mutex lock;\n+\n+\t/* serialise USB bus access */\n+\tstruct mutex bus_lock;\n+\n+\tbool ongoing_cmd;\n+\tbool ongoing_rw;\n+\twait_queue_head_t rw_in_wait;\n+\tunsigned long flags;\n+};\n+\n+enum mm81x_usb_command_direction {\n+\tMM81X_USB_WRITE = 0x00,\n+\tMM81X_USB_READ = 0x80,\n+\tMM81X_USB_RESET = 0x02,\n+};\n+\n+struct mm81x_usb_command {\n+\t__le32 dir; /* Next BULK direction */\n+\t__le32 address; /* Next BULK address */\n+\t__le32 length; /* Next BULK size */\n+};\n+\n+static const struct usb_device_id mm81x_usb_table[] = {\n+\t{ USB_DEVICE(MM81X_VENDOR_ID, MM81X_MM810X_PRODUCT_ID) },\n+\t{} /* Terminating entry */\n+};\n+\n+MODULE_DEVICE_TABLE(usb, mm81x_usb_table);\n+\n+static void mm81x_usb_irq_work(struct work_struct *work)\n+{\n+\tstruct mm81x *mors = container_of(work, struct mm81x, usb_irq_work);\n+\n+\tmm81x_claim_bus(mors);\n+\tmm81x_hw_irq_handle(mors);\n+\tmm81x_release_bus(mors);\n+}\n+\n+static bool mm81x_usb_urb_status_is_disconnect(const struct urb *urb)\n+{\n+\treturn ((urb-\u003estatus == -EPROTO) || (urb-\u003estatus == -EILSEQ) ||\n+\t\t(urb-\u003estatus == -ETIME) || (urb-\u003estatus == -EPIPE));\n+}\n+\n+static void mm81x_usb_int_handler(struct urb *urb)\n+{\n+\tint ret;\n+\tstruct mm81x *mors = urb-\u003econtext;\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\n+\tif (!test_bit(MM81X_USB_FLAG_ATTACHED, \u0026musb-\u003eflags))\n+\t\treturn;\n+\n+\tif (urb-\u003estatus) {\n+\t\tif (mm81x_usb_urb_status_is_disconnect(urb)) {\n+\t\t\tclear_bit(MM81X_USB_FLAG_ATTACHED, \u0026musb-\u003eflags);\n+\t\t\tset_bit(MM81X_STATE_CHIP_UNRESPONSIVE,\n+\t\t\t\t\u0026mors-\u003estate_flags);\n+\t\t\tdev_dbg(mors-\u003edev,\n+\t\t\t\t\"USB sudden disconnect detected in %s\",\n+\t\t\t\t__func__);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tif (!(urb-\u003estatus == -ENOENT || urb-\u003estatus == -ECONNRESET ||\n+\t\t      urb-\u003estatus == -ESHUTDOWN))\n+\t\t\tdev_err(mors-\u003edev, \"- nonzero read status received: %d\",\n+\t\t\t\turb-\u003estatus);\n+\t}\n+\n+\tret = usb_submit_urb(urb, GFP_ATOMIC);\n+\n+\t/* usb_kill_urb has been called */\n+\tif (ret == -EPERM)\n+\t\treturn;\n+\telse if (ret)\n+\t\tdev_err(mors-\u003edev, \"error: resubmit urb %p err code %d\", urb,\n+\t\t\tret);\n+\n+\tqueue_work(mors-\u003echip_wq, \u0026mors-\u003eusb_irq_work);\n+}\n+\n+static int mm81x_usb_int_enable(struct mm81x *mors)\n+{\n+\tint ret = 0;\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\tstruct urb *urb;\n+\n+\tif (!test_bit(MM81X_USB_FLAG_ATTACHED, \u0026musb-\u003eflags))\n+\t\treturn -ENODEV;\n+\n+\turb = usb_alloc_urb(0, GFP_KERNEL);\n+\tif (!urb) {\n+\t\tret = -ENOMEM;\n+\t\tgoto out;\n+\t}\n+\n+\tmusb-\u003eendpoints[MM81X_EP_INT].urb = urb;\n+\n+\tmusb-\u003eendpoints[MM81X_EP_INT].buffer =\n+\t\tusb_alloc_coherent(musb-\u003eudev, MM81X_EP_INT_BUFFER_SIZE,\n+\t\t\t\t   GFP_KERNEL, \u0026urb-\u003etransfer_dma);\n+\tif (!musb-\u003eendpoints[MM81X_EP_INT].buffer) {\n+\t\tdev_err(mors-\u003edev, \"couldn't allocate transfer_buffer\");\n+\t\tret = -ENOMEM;\n+\t\tgoto error_set_urb_null;\n+\t}\n+\n+\tusb_fill_int_urb(\n+\t\tmusb-\u003eendpoints[MM81X_EP_INT].urb, musb-\u003eudev,\n+\t\tusb_rcvintpipe(musb-\u003eudev, musb-\u003eendpoints[MM81X_EP_INT].addr),\n+\t\tmusb-\u003eendpoints[MM81X_EP_INT].buffer, MM81X_EP_INT_BUFFER_SIZE,\n+\t\tmm81x_usb_int_handler, mors, MM81X_USB_INTERRUPT_INTERVAL);\n+\turb-\u003etransfer_flags |= URB_NO_TRANSFER_DMA_MAP;\n+\n+\tret = usb_submit_urb(urb, GFP_KERNEL);\n+\tif (ret) {\n+\t\tdev_err(mors-\u003edev, \"Couldn't submit urb. Error number %d\", ret);\n+\t\tgoto error;\n+\t}\n+\n+\treturn 0;\n+\n+error:\n+\tusb_free_coherent(musb-\u003eudev, MM81X_EP_INT_BUFFER_SIZE,\n+\t\t\t  musb-\u003eendpoints[MM81X_EP_INT].buffer,\n+\t\t\t  urb-\u003etransfer_dma);\n+error_set_urb_null:\n+\tmusb-\u003eendpoints[MM81X_EP_INT].urb = NULL;\n+\tusb_free_urb(urb);\n+out:\n+\treturn ret;\n+}\n+\n+static void mm81x_usb_int_stop(struct mm81x *mors)\n+{\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\n+\tusb_kill_urb(musb-\u003eendpoints[MM81X_EP_INT].urb);\n+\tcancel_work_sync(\u0026mors-\u003eusb_irq_work);\n+}\n+\n+static void mm81x_usb_cmd_callback(struct urb *urb)\n+{\n+\tstruct mm81x *mors = urb-\u003econtext;\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\n+\t/* sync/async unlink faults aren't errors */\n+\tif (urb-\u003estatus) {\n+\t\tif (!(urb-\u003estatus == -ENOENT || urb-\u003estatus == -ECONNRESET ||\n+\t\t      urb-\u003estatus == -ESHUTDOWN))\n+\t\t\tdev_err(mors-\u003edev,\n+\t\t\t\t\"nonzero write bulk status received: %d\",\n+\t\t\t\turb-\u003estatus);\n+\n+\t\tmusb-\u003eerrors = urb-\u003estatus;\n+\t}\n+\n+\tmusb-\u003eongoing_cmd = false;\n+\twake_up(\u0026musb-\u003erw_in_wait);\n+}\n+\n+static int mm81x_usb_cmd(struct mm81x_usb *musb,\n+\t\t\t const struct mm81x_usb_command *cmd)\n+{\n+\tint retval = 0;\n+\tstruct mm81x *mors = usb_get_intfdata(musb-\u003einterface);\n+\tstruct mm81x_usb_endpoint *ep = \u0026musb-\u003eendpoints[MM81X_EP_CMD];\n+\tsize_t writesize = sizeof(*cmd);\n+\n+\tif (!test_bit(MM81X_USB_FLAG_ATTACHED, \u0026musb-\u003eflags))\n+\t\treturn -ENODEV;\n+\n+\tmemcpy(ep-\u003ebuffer, cmd, writesize);\n+\n+\tusb_fill_bulk_urb(ep-\u003eurb, musb-\u003eudev,\n+\t\t\t  usb_sndbulkpipe(musb-\u003eudev, ep-\u003eaddr), ep-\u003ebuffer,\n+\t\t\t  writesize, mm81x_usb_cmd_callback, mors);\n+\tep-\u003eurb-\u003etransfer_flags |= URB_NO_TRANSFER_DMA_MAP;\n+\n+\tmusb-\u003eongoing_cmd = true;\n+\n+\tretval = usb_submit_urb(ep-\u003eurb, GFP_KERNEL);\n+\tif (retval) {\n+\t\tdev_err(mors-\u003edev, \"- failed submitting write urb, error %d\",\n+\t\t\tretval);\n+\n+\t\tgoto error;\n+\t}\n+\n+\tretval = wait_event_interruptible_timeout(\n+\t\tmusb-\u003erw_in_wait, (!musb-\u003eongoing_cmd),\n+\t\tmsecs_to_jiffies(URB_TIMEOUT_MS));\n+\tif (retval \u003c 0) {\n+\t\tdev_err(mors-\u003edev, \"error waiting for urb %d\", retval);\n+\t\tgoto error;\n+\t} else if (retval == 0) {\n+\t\tdev_err(mors-\u003edev, \"timed out waiting for urb\");\n+\t\tusb_kill_urb(ep-\u003eurb);\n+\t\tretval = -ETIMEDOUT;\n+\t\tgoto error;\n+\t}\n+\n+\tmusb-\u003eongoing_cmd = false;\n+\treturn writesize;\n+\n+error:\n+\tmusb-\u003eongoing_cmd = false;\n+\treturn retval;\n+}\n+\n+static int mm81x_usb_ndr_reset(struct mm81x *mors)\n+{\n+\tint ret;\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\tstruct mm81x_usb_command cmd;\n+\n+\tmutex_lock(\u0026musb-\u003elock);\n+\n+\tmusb-\u003eongoing_rw = true;\n+\tmusb-\u003eerrors = 0;\n+\n+\tcmd.dir = cpu_to_le32(MM81X_USB_RESET);\n+\tcmd.address = cpu_to_le32(0);\n+\tcmd.length = cpu_to_le32(0);\n+\n+\tret = mm81x_usb_cmd(musb, \u0026cmd);\n+\tif (ret \u003c 0)\n+\t\tdev_err(mors-\u003edev, \"mm81x_usb_cmd (MM81X_USB_RESET) error %d\\n\",\n+\t\t\tret);\n+\telse\n+\t\tret = 0;\n+\n+\tmusb-\u003eongoing_rw = false;\n+\tmutex_unlock(\u0026musb-\u003elock);\n+\treturn ret;\n+}\n+\n+static void mm81x_usb_mem_rw_callback(struct urb *urb)\n+{\n+\tstruct mm81x *mors = urb-\u003econtext;\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\n+\t/* sync/async unlink faults aren't errors */\n+\tif (urb-\u003estatus) {\n+\t\tif (!(urb-\u003estatus == -ENOENT || urb-\u003estatus == -ECONNRESET ||\n+\t\t      urb-\u003estatus == -ESHUTDOWN))\n+\t\t\tdev_err(mors-\u003edev,\n+\t\t\t\t\"nonzero write bulk status received: %d\",\n+\t\t\t\turb-\u003estatus);\n+\n+\t\tmusb-\u003eerrors = urb-\u003estatus;\n+\t}\n+\n+\tmusb-\u003eongoing_rw = false;\n+\twake_up(\u0026musb-\u003erw_in_wait);\n+}\n+\n+static int mm81x_usb_mem_read(struct mm81x_usb *musb, u32 address, u8 *data,\n+\t\t\t      ssize_t size)\n+{\n+\tint ret;\n+\tstruct mm81x_usb_command cmd;\n+\tstruct mm81x *mors = usb_get_intfdata(musb-\u003einterface);\n+\n+\tif (!test_bit(MM81X_USB_FLAG_ATTACHED, \u0026musb-\u003eflags))\n+\t\treturn -ENODEV;\n+\n+\tmutex_lock(\u0026musb-\u003elock);\n+\n+\tmusb-\u003eongoing_rw = true;\n+\tmusb-\u003eerrors = 0;\n+\n+\t/* Send command ahead to prepare for Tokens */\n+\tcmd.dir = cpu_to_le32(MM81X_USB_READ);\n+\tcmd.address = cpu_to_le32(address);\n+\tcmd.length = cpu_to_le32(size);\n+\n+\tret = mm81x_usb_cmd(musb, \u0026cmd);\n+\tif (ret \u003c 0) {\n+\t\tdev_err(mors-\u003edev, \"mm81x_usb_cmd error %d\", ret);\n+\t\tgoto error;\n+\t}\n+\n+\t/* Let's be fast push the next URB, don't wait until command is done */\n+\tusb_fill_bulk_urb(\n+\t\tmusb-\u003eendpoints[MM81X_EP_MEM_RD].urb, musb-\u003eudev,\n+\t\tusb_rcvbulkpipe(musb-\u003eudev,\n+\t\t\t\tmusb-\u003eendpoints[MM81X_EP_MEM_RD].addr),\n+\t\tmusb-\u003eendpoints[MM81X_EP_MEM_RD].buffer, size,\n+\t\tmm81x_usb_mem_rw_callback, mors);\n+\n+\tret = usb_submit_urb(musb-\u003eendpoints[MM81X_EP_MEM_RD].urb, GFP_ATOMIC);\n+\tif (ret \u003c 0) {\n+\t\tdev_err(mors-\u003edev, \"failed submitting read urb, error %d\", ret);\n+\t\tret = (ret == -ENOMEM) ? ret : -EIO;\n+\t\tgoto error;\n+\t}\n+\n+\tret = wait_event_interruptible_timeout(\n+\t\tmusb-\u003erw_in_wait, (!musb-\u003eongoing_rw),\n+\t\tmsecs_to_jiffies(URB_TIMEOUT_MS));\n+\tif (ret \u003c 0) {\n+\t\tdev_err(mors-\u003edev, \"wait_event_interruptible: error %d\", ret);\n+\t\tgoto error;\n+\t} else if (ret == 0) {\n+\t\t/* Timed out. */\n+\t\tusb_kill_urb(musb-\u003eendpoints[MM81X_EP_MEM_RD].urb);\n+\t}\n+\n+\tif (musb-\u003eerrors) {\n+\t\tret = musb-\u003eerrors;\n+\t\tdev_err(mors-\u003edev, \"mem read error %d\", ret);\n+\t\tgoto error;\n+\t}\n+\n+\tmemcpy(data, musb-\u003eendpoints[MM81X_EP_MEM_RD].buffer, size);\n+\tret = size;\n+\n+error:\n+\tmusb-\u003eongoing_rw = false;\n+\tmutex_unlock(\u0026musb-\u003elock);\n+\n+\treturn ret;\n+}\n+\n+static int mm81x_usb_mem_write(struct mm81x_usb *musb, u32 address, u8 *data,\n+\t\t\t       ssize_t size)\n+{\n+\tint ret;\n+\tstruct mm81x_usb_command cmd;\n+\tstruct mm81x *mors = usb_get_intfdata(musb-\u003einterface);\n+\n+\tif (!test_bit(MM81X_USB_FLAG_ATTACHED, \u0026musb-\u003eflags))\n+\t\treturn -ENODEV;\n+\n+\tmutex_lock(\u0026musb-\u003elock);\n+\n+\tmusb-\u003eongoing_rw = true;\n+\tmusb-\u003eerrors = 0;\n+\n+\t/* Send command ahead to prepare for Tokens */\n+\tcmd.dir = cpu_to_le32(MM81X_USB_WRITE);\n+\tcmd.address = cpu_to_le32(address);\n+\tcmd.length = cpu_to_le32(size);\n+\tret = mm81x_usb_cmd(musb, \u0026cmd);\n+\tif (ret \u003c 0) {\n+\t\tdev_err(mors-\u003edev, \"mm81x_usb_mem_read error %d\", ret);\n+\t\tgoto error;\n+\t}\n+\n+\tmemcpy(musb-\u003eendpoints[MM81X_EP_MEM_WR].buffer, data, size);\n+\n+\t/* prepare a read */\n+\tusb_fill_bulk_urb(\n+\t\tmusb-\u003eendpoints[MM81X_EP_MEM_WR].urb, musb-\u003eudev,\n+\t\tusb_sndbulkpipe(musb-\u003eudev,\n+\t\t\t\tmusb-\u003eendpoints[MM81X_EP_MEM_WR].addr),\n+\t\tmusb-\u003eendpoints[MM81X_EP_MEM_WR].buffer, size,\n+\t\tmm81x_usb_mem_rw_callback, mors);\n+\n+\tret = usb_submit_urb(musb-\u003eendpoints[MM81X_EP_MEM_WR].urb, GFP_ATOMIC);\n+\tif (ret \u003c 0) {\n+\t\tdev_err(mors-\u003edev, \"- failed submitting write urb, error %d\",\n+\t\t\tret);\n+\t\tret = (ret == -ENOMEM) ? ret : -EIO;\n+\t\tgoto error;\n+\t}\n+\n+\tret = wait_event_interruptible_timeout(\n+\t\tmusb-\u003erw_in_wait, (!musb-\u003eongoing_rw),\n+\t\tmsecs_to_jiffies(URB_TIMEOUT_MS));\n+\tif (ret \u003c 0) {\n+\t\tdev_err(mors-\u003edev, \"error %d\", ret);\n+\t\tgoto error;\n+\t} else if (ret == 0) {\n+\t\t/* Timed out. */\n+\t\tusb_kill_urb(musb-\u003eendpoints[MM81X_EP_MEM_WR].urb);\n+\t}\n+\n+\tif (musb-\u003eerrors) {\n+\t\tret = musb-\u003eerrors;\n+\t\tdev_err(mors-\u003edev, \"error %d\", ret);\n+\t\tgoto error;\n+\t}\n+\n+\tret = size;\n+\n+error:\n+\tmusb-\u003eongoing_rw = false;\n+\tmutex_unlock(\u0026musb-\u003elock);\n+\treturn ret;\n+}\n+\n+static int mm81x_usb_dm_read(struct mm81x *mors, u32 address, u8 *data, int len)\n+{\n+\tssize_t offset = 0;\n+\tint ret;\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\n+\twhile (offset \u003c len) {\n+\t\tret = mm81x_usb_mem_read(musb, address + offset,\n+\t\t\t\t\t (u8 *)(data + offset),\n+\t\t\t\t\t min((ssize_t)(len - offset),\n+\t\t\t\t\t     (ssize_t)USB_MAX_TRANSFER_SIZE));\n+\t\tif (ret \u003c 0) {\n+\t\t\tdev_err(mors-\u003edev, \"%s failed (errno=%d)\", __func__,\n+\t\t\t\tret);\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\toffset += ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int mm81x_usb_dm_write(struct mm81x *mors, u32 address, const u8 *data,\n+\t\t\t      int len)\n+{\n+\tssize_t offset = 0;\n+\tint ret;\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\n+\twhile (offset \u003c len) {\n+\t\tret = mm81x_usb_mem_write(musb, address + offset,\n+\t\t\t\t\t  (u8 *)(data + offset),\n+\t\t\t\t\t  min((ssize_t)(len - offset),\n+\t\t\t\t\t      (ssize_t)USB_MAX_TRANSFER_SIZE));\n+\t\tif (ret \u003c 0) {\n+\t\t\tdev_err(mors-\u003edev, \"%s failed (errno=%d)\", __func__,\n+\t\t\t\tret);\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\toffset += ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int mm81x_usb_reg32_read(struct mm81x *mors, u32 address, u32 *val)\n+{\n+\tint ret = 0;\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\n+\tret = mm81x_usb_mem_read(musb, address, (u8 *)val, sizeof(*val));\n+\tif (ret == sizeof(*val)) {\n+\t\t*val = le32_to_cpup((__le32 *)val);\n+\t\treturn 0;\n+\t}\n+\n+\tdev_err(mors-\u003edev, \"usb reg32 read failed %d\", ret);\n+\treturn ret;\n+}\n+\n+static int mm81x_usb_reg32_write(struct mm81x *mors, u32 address, u32 val)\n+{\n+\tint ret = 0;\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\t__le32 val_le = cpu_to_le32(val);\n+\n+\tret = mm81x_usb_mem_write(musb, address, (u8 *)\u0026val_le, sizeof(val_le));\n+\tif (ret == sizeof(val_le))\n+\t\treturn 0;\n+\n+\tdev_err(mors-\u003edev, \"usb reg32 write failed %d\", ret);\n+\treturn ret;\n+}\n+\n+static void mm81x_usb_bus_enable(struct mm81x *mors, bool enable)\n+{\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\n+\tif (enable)\n+\t\tusb_autopm_get_interface(musb-\u003einterface);\n+\telse\n+\t\tusb_autopm_put_interface(musb-\u003einterface);\n+}\n+\n+static void mm81x_usb_claim_bus(struct mm81x *mors)\n+{\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\n+\tmutex_lock(\u0026musb-\u003ebus_lock);\n+}\n+\n+static void mm81x_usb_release_bus(struct mm81x *mors)\n+{\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\n+\tmutex_unlock(\u0026musb-\u003ebus_lock);\n+}\n+\n+static void mm81x_usb_set_irq(struct mm81x *mors, bool enable)\n+{\n+}\n+\n+static const struct mm81x_bus_ops mm81x_usb_ops = {\n+\t.dm_read = mm81x_usb_dm_read,\n+\t.dm_write = mm81x_usb_dm_write,\n+\t.reg32_read = mm81x_usb_reg32_read,\n+\t.reg32_write = mm81x_usb_reg32_write,\n+\t.digital_reset = mm81x_usb_ndr_reset,\n+\t.set_bus_enable = mm81x_usb_bus_enable,\n+\t.claim = mm81x_usb_claim_bus,\n+\t.release = mm81x_usb_release_bus,\n+\t.set_irq = mm81x_usb_set_irq,\n+\t.bulk_alignment = MM81X_BUS_DEFAULT_BULK_ALIGNMENT,\n+};\n+\n+static int mm81x_usb_detect_endpoints(struct mm81x *mors,\n+\t\t\t\t      const struct usb_interface *intf)\n+{\n+\tint ret;\n+\tunsigned int i;\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\tstruct usb_endpoint_descriptor *ep_desc;\n+\tstruct usb_host_interface *intf_desc = intf-\u003ecur_altsetting;\n+\n+\tfor (i = 0; i \u003c intf_desc-\u003edesc.bNumEndpoints; i++) {\n+\t\tep_desc = \u0026intf_desc-\u003eendpoint[i].desc;\n+\n+\t\tif (usb_endpoint_is_bulk_in(ep_desc)) {\n+\t\t\tif (!musb-\u003eendpoints[MM81X_EP_MEM_RD].addr) {\n+\t\t\t\tmusb-\u003eendpoints[MM81X_EP_MEM_RD].addr =\n+\t\t\t\t\tusb_endpoint_num(ep_desc);\n+\t\t\t\tmusb-\u003eendpoints[MM81X_EP_MEM_RD].size =\n+\t\t\t\t\tusb_endpoint_maxp(ep_desc);\n+\t\t\t} else if (!musb-\u003eendpoints[MM81X_EP_REG_RD].addr) {\n+\t\t\t\tmusb-\u003eendpoints[MM81X_EP_REG_RD].addr =\n+\t\t\t\t\tusb_endpoint_num(ep_desc);\n+\t\t\t\tmusb-\u003eendpoints[MM81X_EP_REG_RD].size =\n+\t\t\t\t\tusb_endpoint_maxp(ep_desc);\n+\t\t\t}\n+\t\t} else if (usb_endpoint_is_bulk_out(ep_desc)) {\n+\t\t\tif (!musb-\u003eendpoints[MM81X_EP_MEM_WR].addr) {\n+\t\t\t\tmusb-\u003eendpoints[MM81X_EP_MEM_WR].addr =\n+\t\t\t\t\tusb_endpoint_num(ep_desc);\n+\t\t\t\tmusb-\u003eendpoints[MM81X_EP_MEM_WR].size =\n+\t\t\t\t\tusb_endpoint_maxp(ep_desc);\n+\t\t\t} else if (!musb-\u003eendpoints[MM81X_EP_REG_WR].addr) {\n+\t\t\t\tmusb-\u003eendpoints[MM81X_EP_REG_WR].addr =\n+\t\t\t\t\tusb_endpoint_num(ep_desc);\n+\t\t\t\tmusb-\u003eendpoints[MM81X_EP_REG_WR].size =\n+\t\t\t\t\tusb_endpoint_maxp(ep_desc);\n+\t\t\t}\n+\t\t} else if (usb_endpoint_is_int_in(ep_desc)) {\n+\t\t\tmusb-\u003eendpoints[MM81X_EP_INT].addr =\n+\t\t\t\tusb_endpoint_num(ep_desc);\n+\t\t\tmusb-\u003eendpoints[MM81X_EP_INT].size =\n+\t\t\t\tusb_endpoint_maxp(ep_desc);\n+\t\t}\n+\t}\n+\n+\tdev_dbg(mors-\u003edev, \"\\tMemory Endpoint IN %s detected: %u size %u\",\n+\t\tmusb-\u003eendpoints[MM81X_EP_MEM_RD].addr ? \"\" : \"not\",\n+\t\tmusb-\u003eendpoints[MM81X_EP_MEM_RD].addr,\n+\t\tmusb-\u003eendpoints[MM81X_EP_MEM_RD].size);\n+\tdev_dbg(mors-\u003edev, \"\\tMemory Endpoint OUT %s detected: %u size %u\",\n+\t\tmusb-\u003eendpoints[MM81X_EP_MEM_WR].addr ? \"\" : \"not\",\n+\t\tmusb-\u003eendpoints[MM81X_EP_MEM_WR].addr,\n+\t\tmusb-\u003eendpoints[MM81X_EP_MEM_WR].size);\n+\tdev_dbg(mors-\u003edev, \"\\tRegister Endpoint IN %s detected: %u\",\n+\t\tmusb-\u003eendpoints[MM81X_EP_REG_RD].addr ? \"\" : \"not\",\n+\t\tmusb-\u003eendpoints[MM81X_EP_REG_RD].addr);\n+\tdev_dbg(mors-\u003edev, \"\\tRegister Endpoint OUT %s detected: %u\",\n+\t\tmusb-\u003eendpoints[MM81X_EP_REG_WR].addr ? \"\" : \"not\",\n+\t\tmusb-\u003eendpoints[MM81X_EP_REG_WR].addr);\n+\tdev_dbg(mors-\u003edev, \"\\tStats IN endpoint %s detected: %u\",\n+\t\tmusb-\u003eendpoints[MM81X_EP_INT].addr ? \"\" : \"not\",\n+\t\tmusb-\u003eendpoints[MM81X_EP_INT].addr);\n+\n+\t/* Verify we have an IN and OUT */\n+\tif (!(musb-\u003eendpoints[MM81X_EP_MEM_RD].addr \u0026\u0026\n+\t      musb-\u003eendpoints[MM81X_EP_MEM_WR].addr))\n+\t\treturn -ENODEV;\n+\n+\t/* Verify the stats MM81X_EP_INT is detected */\n+\tif (!musb-\u003eendpoints[MM81X_EP_INT].addr)\n+\t\treturn -ENODEV;\n+\n+\t/* Verify minimum interrupt status read */\n+\tif (musb-\u003eendpoints[MM81X_EP_INT].size \u003c 8)\n+\t\treturn -ENODEV;\n+\n+\tmusb-\u003eendpoints[MM81X_EP_CMD].urb = usb_alloc_urb(0, GFP_KERNEL);\n+\tif (!musb-\u003eendpoints[MM81X_EP_CMD].urb) {\n+\t\tret = -ENOMEM;\n+\t\tgoto err_ep;\n+\t}\n+\n+\tmusb-\u003eendpoints[MM81X_EP_MEM_RD].urb = usb_alloc_urb(0, GFP_KERNEL);\n+\tif (!musb-\u003eendpoints[MM81X_EP_MEM_RD].urb) {\n+\t\tret = -ENOMEM;\n+\t\tgoto err_ep;\n+\t}\n+\n+\tmusb-\u003eendpoints[MM81X_EP_MEM_WR].urb = usb_alloc_urb(0, GFP_KERNEL);\n+\tif (!musb-\u003eendpoints[MM81X_EP_MEM_WR].urb) {\n+\t\tret = -ENOMEM;\n+\t\tgoto err_ep;\n+\t}\n+\n+\tmusb-\u003eendpoints[MM81X_EP_MEM_RD].buffer =\n+\t\tkmalloc(USB_MAX_TRANSFER_SIZE, GFP_KERNEL);\n+\tif (!musb-\u003eendpoints[MM81X_EP_MEM_RD].buffer) {\n+\t\tret = -ENOMEM;\n+\t\tgoto err_ep;\n+\t}\n+\n+\tmusb-\u003eendpoints[MM81X_EP_MEM_WR].buffer =\n+\t\tkmalloc(USB_MAX_TRANSFER_SIZE, GFP_KERNEL);\n+\tif (!musb-\u003eendpoints[MM81X_EP_MEM_WR].buffer) {\n+\t\tret = -ENOMEM;\n+\t\tgoto err_ep;\n+\t}\n+\n+\tmusb-\u003eendpoints[MM81X_EP_CMD].buffer = usb_alloc_coherent(\n+\t\tmusb-\u003eudev, sizeof(struct mm81x_usb_command), GFP_KERNEL,\n+\t\t\u0026musb-\u003eendpoints[MM81X_EP_CMD].urb-\u003etransfer_dma);\n+\n+\tif (!musb-\u003eendpoints[MM81X_EP_CMD].buffer) {\n+\t\tret = -ENOMEM;\n+\t\tgoto err_ep;\n+\t}\n+\n+\t/* Assign command to memory out end point */\n+\tmusb-\u003eendpoints[MM81X_EP_CMD].addr =\n+\t\tmusb-\u003eendpoints[MM81X_EP_MEM_WR].addr;\n+\tmusb-\u003eendpoints[MM81X_EP_CMD].size =\n+\t\tmusb-\u003eendpoints[MM81X_EP_MEM_WR].size;\n+\n+\treturn 0;\n+\n+err_ep:\n+\tif (musb-\u003eendpoints[MM81X_EP_CMD].urb \u0026\u0026\n+\t    musb-\u003eendpoints[MM81X_EP_CMD].buffer)\n+\t\tusb_free_coherent(\n+\t\t\tmusb-\u003eudev, sizeof(struct mm81x_usb_command),\n+\t\t\tmusb-\u003eendpoints[MM81X_EP_CMD].buffer,\n+\t\t\tmusb-\u003eendpoints[MM81X_EP_CMD].urb-\u003etransfer_dma);\n+\tusb_free_urb(musb-\u003eendpoints[MM81X_EP_MEM_RD].urb);\n+\tusb_free_urb(musb-\u003eendpoints[MM81X_EP_CMD].urb);\n+\tusb_free_urb(musb-\u003eendpoints[MM81X_EP_MEM_WR].urb);\n+\tkfree(musb-\u003eendpoints[MM81X_EP_MEM_RD].buffer);\n+\tkfree(musb-\u003eendpoints[MM81X_EP_MEM_WR].buffer);\n+\n+\treturn ret;\n+}\n+\n+static void mm81x_urb_cleanup(struct mm81x *mors)\n+{\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\tstruct mm81x_usb_endpoint *int_ep = \u0026musb-\u003eendpoints[MM81X_EP_INT];\n+\tstruct mm81x_usb_endpoint *rd_ep = \u0026musb-\u003eendpoints[MM81X_EP_MEM_RD];\n+\tstruct mm81x_usb_endpoint *wr_ep = \u0026musb-\u003eendpoints[MM81X_EP_MEM_WR];\n+\tstruct mm81x_usb_endpoint *cmd_ep = \u0026musb-\u003eendpoints[MM81X_EP_CMD];\n+\n+\tusb_kill_urb(rd_ep-\u003eurb);\n+\tusb_kill_urb(wr_ep-\u003eurb);\n+\tusb_kill_urb(cmd_ep-\u003eurb);\n+\n+\tif (int_ep-\u003eurb)\n+\t\tusb_free_coherent(musb-\u003eudev, MM81X_EP_INT_BUFFER_SIZE,\n+\t\t\t\t  int_ep-\u003ebuffer, int_ep-\u003eurb-\u003etransfer_dma);\n+\n+\tif (cmd_ep-\u003eurb)\n+\t\tusb_free_coherent(musb-\u003eudev, sizeof(struct mm81x_usb_command),\n+\t\t\t\t  cmd_ep-\u003ebuffer, cmd_ep-\u003eurb-\u003etransfer_dma);\n+\n+\tkfree(wr_ep-\u003ebuffer);\n+\tkfree(rd_ep-\u003ebuffer);\n+\n+\tusb_free_urb(int_ep-\u003eurb);\n+\tusb_free_urb(wr_ep-\u003eurb);\n+\tusb_free_urb(rd_ep-\u003eurb);\n+\tusb_free_urb(cmd_ep-\u003eurb);\n+}\n+\n+static int mm81x_usb_probe(struct usb_interface *interface,\n+\t\t\t   const struct usb_device_id *id)\n+{\n+\tint ret;\n+\tstruct mm81x *mors;\n+\tstruct mm81x_usb *musb;\n+\n+\tmors = mm81x_core_alloc(sizeof(*musb), \u0026interface-\u003edev);\n+\tif (!mors)\n+\t\treturn -ENOMEM;\n+\n+\tmors-\u003ebus_ops = \u0026mm81x_usb_ops;\n+\tmors-\u003ebus_type = MM81X_BUS_TYPE_USB;\n+\n+\tmusb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\tmusb-\u003eudev = usb_get_dev(interface_to_usbdev(interface));\n+\tmusb-\u003einterface = usb_get_intf(interface);\n+\n+\tmutex_init(\u0026musb-\u003elock);\n+\tmutex_init(\u0026musb-\u003ebus_lock);\n+\tinit_waitqueue_head(\u0026musb-\u003erw_in_wait);\n+\tusb_set_intfdata(interface, mors);\n+\n+\tret = mm81x_usb_detect_endpoints(mors, interface);\n+\tif (ret \u003c 0)\n+\t\tgoto err_core_free;\n+\n+\tset_bit(MM81X_USB_FLAG_ATTACHED, \u0026musb-\u003eflags);\n+\n+\tret = mm81x_core_init(mors);\n+\tif (ret)\n+\t\tgoto err_urb_cleanup;\n+\n+\tINIT_WORK(\u0026mors-\u003eusb_irq_work, mm81x_usb_irq_work);\n+\n+\tret = mm81x_usb_int_enable(mors);\n+\tif (ret)\n+\t\tgoto err_core_deinit;\n+\n+\tret = mm81x_core_register(mors);\n+\tif (ret)\n+\t\tgoto err_usb_int_stop;\n+\n+\t/* USB requires remote wakeup functionality for suspend */\n+\tclear_bit(MM81X_USB_FLAG_SUSPENDED, \u0026musb-\u003eflags);\n+\tmusb-\u003einterface-\u003eneeds_remote_wakeup = 1;\n+\tusb_enable_autosuspend(musb-\u003eudev);\n+\tpm_runtime_set_autosuspend_delay(\u0026musb-\u003eudev-\u003edev,\n+\t\t\t\t\t PM_RUNTIME_AUTOSUSPEND_DELAY_MS);\n+\n+\tusb_autopm_get_interface(interface);\n+\treturn 0;\n+\n+err_usb_int_stop:\n+\tmm81x_usb_int_stop(mors);\n+err_core_deinit:\n+\tmm81x_core_deinit(mors);\n+err_urb_cleanup:\n+\tmm81x_urb_cleanup(mors);\n+err_core_free:\n+\tmm81x_core_free(mors);\n+\tusb_put_intf(interface);\n+\tusb_put_dev(interface_to_usbdev(interface));\n+\treturn ret;\n+}\n+\n+static void mm81x_usb_disconnect(struct usb_interface *interface)\n+{\n+\tstruct mm81x *mors = usb_get_intfdata(interface);\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\tint minor = interface-\u003eminor;\n+\tstruct usb_device *udev = interface_to_usbdev(interface);\n+\n+\tif (udev-\u003estate == USB_STATE_NOTATTACHED) {\n+\t\tclear_bit(MM81X_USB_FLAG_ATTACHED, \u0026musb-\u003eflags);\n+\t\tset_bit(MM81X_STATE_CHIP_UNRESPONSIVE, \u0026mors-\u003estate_flags);\n+\t\tdev_dbg(mors-\u003edev, \"USB suddenly unplugged\");\n+\t}\n+\n+\tusb_disable_autosuspend(udev);\n+\n+\tif (test_bit(MM81X_USB_FLAG_SUSPENDED, \u0026musb-\u003eflags)) {\n+\t\tdev_dbg(mors-\u003edev, \"USB was suspended: release locks\");\n+\t\tmm81x_usb_release_bus(mors);\n+\t\tmutex_unlock(\u0026musb-\u003elock);\n+\t}\n+\n+\tclear_bit(MM81X_USB_FLAG_SUSPENDED, \u0026musb-\u003eflags);\n+\n+\tmm81x_core_unregister(mors);\n+\tmm81x_usb_int_stop(mors);\n+\tmm81x_core_deinit(mors);\n+\tmm81x_urb_cleanup(mors);\n+\tmm81x_core_free(mors);\n+\n+\tusb_autopm_put_interface(interface);\n+\tusb_set_intfdata(interface, NULL);\n+\tdev_info(\u0026interface-\u003edev, \"USB Morse #%d now disconnected\", minor);\n+\tusb_put_intf(interface);\n+\tusb_put_dev(udev);\n+}\n+\n+static int mm81x_usb_suspend(struct usb_interface *intf, pm_message_t message)\n+{\n+\tstruct mm81x *mors = usb_get_intfdata(intf);\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\tstruct mm81x_usb_endpoint *int_ep = \u0026musb-\u003eendpoints[MM81X_EP_INT];\n+\tstruct mm81x_usb_endpoint *rd_ep = \u0026musb-\u003eendpoints[MM81X_EP_MEM_RD];\n+\tstruct mm81x_usb_endpoint *wr_ep = \u0026musb-\u003eendpoints[MM81X_EP_MEM_WR];\n+\tstruct mm81x_usb_endpoint *cmd_ep = \u0026musb-\u003eendpoints[MM81X_EP_CMD];\n+\n+\tif (!test_bit(MM81X_USB_FLAG_ATTACHED, \u0026musb-\u003eflags))\n+\t\treturn -ENODEV;\n+\n+\tusb_kill_urb(int_ep-\u003eurb);\n+\tusb_kill_urb(rd_ep-\u003eurb);\n+\tusb_kill_urb(wr_ep-\u003eurb);\n+\tusb_kill_urb(cmd_ep-\u003eurb);\n+\n+\t/* Locking the bus. No USB communication after this point */\n+\tmm81x_usb_claim_bus(mors);\n+\tmutex_lock(\u0026musb-\u003elock);\n+\n+\tset_bit(MM81X_USB_FLAG_SUSPENDED, \u0026musb-\u003eflags);\n+\treturn 0;\n+}\n+\n+static int mm81x_usb_resume(struct usb_interface *intf)\n+{\n+\tstruct mm81x *mors = usb_get_intfdata(intf);\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\tint ret;\n+\tstruct mm81x_usb_endpoint *int_ep = \u0026musb-\u003eendpoints[MM81X_EP_INT];\n+\n+\tif (!test_bit(MM81X_USB_FLAG_ATTACHED, \u0026musb-\u003eflags))\n+\t\treturn -ENODEV;\n+\n+\tret = usb_submit_urb(int_ep-\u003eurb, GFP_KERNEL);\n+\tif (ret)\n+\t\tdev_err(mors-\u003edev, \"Couldn't submit urb. Error number %d\", ret);\n+\n+\tmm81x_usb_release_bus(mors);\n+\tmutex_unlock(\u0026musb-\u003elock);\n+\n+\tclear_bit(MM81X_USB_FLAG_SUSPENDED, \u0026musb-\u003eflags);\n+\treturn 0;\n+}\n+\n+static int mm81x_usb_reset_resume(struct usb_interface *intf)\n+{\n+\tstruct mm81x *mors = usb_get_intfdata(intf);\n+\tstruct mm81x_usb *musb = (struct mm81x_usb *)mors-\u003edrv_priv;\n+\tint ret;\n+\tstruct mm81x_usb_endpoint *int_ep = \u0026musb-\u003eendpoints[MM81X_EP_INT];\n+\n+\tif (!test_bit(MM81X_USB_FLAG_ATTACHED, \u0026musb-\u003eflags))\n+\t\treturn -ENODEV;\n+\n+\tret = usb_submit_urb(int_ep-\u003eurb, GFP_KERNEL);\n+\tif (ret)\n+\t\tdev_err(mors-\u003edev, \"Couldn't submit urb. Error number %d\", ret);\n+\n+\tmm81x_usb_release_bus(mors);\n+\tmutex_unlock(\u0026musb-\u003elock);\n+\n+\tclear_bit(MM81X_USB_FLAG_SUSPENDED, \u0026musb-\u003eflags);\n+\n+\treturn 0;\n+}\n+\n+static int mm81x_usb_pre_reset(struct usb_interface *intf)\n+{\n+\treturn 0;\n+}\n+\n+static int mm81x_usb_post_reset(struct usb_interface *intf)\n+{\n+\treturn 0;\n+}\n+\n+static struct usb_driver mm81x_usb_driver = {\n+\t.name = \"mm81x_usb\",\n+\t.probe = mm81x_usb_probe,\n+\t.disconnect = mm81x_usb_disconnect,\n+\t.suspend = mm81x_usb_suspend,\n+\t.resume = mm81x_usb_resume,\n+\t.reset_resume = mm81x_usb_reset_resume,\n+\t.pre_reset = mm81x_usb_pre_reset,\n+\t.post_reset = mm81x_usb_post_reset,\n+\t.id_table = mm81x_usb_table,\n+\t.supports_autosuspend = 1,\n+\t.soft_unbind = 1,\n+};\n+\n+module_usb_driver(mm81x_usb_driver);\n+\n+MODULE_AUTHOR(\"Morse Micro\");\n+MODULE_DESCRIPTION(\"Driver support for Morse Micro MM81X USB devices\");\n+MODULE_LICENSE(\"Dual BSD/GPL\");\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/yaps.c b/drivers/net/wireless/morsemicro/mm81x/yaps.c\nnew file mode 100644\nindex 00000000000000..bdadb822bf9a1b\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/yaps.c\n@@ -0,0 +1,704 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+#include \u003clinux/gpio.h\u003e\n+#include \u003clinux/random.h\u003e\n+#include \u003clinux/timer.h\u003e\n+#include \u003clinux/bitops.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \"hif.h\"\n+#include \"ps.h\"\n+#include \"bus.h\"\n+#include \"command.h\"\n+#include \"skbq.h\"\n+\n+/* This is a fail safe timeout */\n+#define CHIP_FULL_RECOVERY_TIMEOUT_MS 30\n+\n+/* Defined as the max number of MPDUs per AMPDU */\n+#define MAX_PKTS_PER_TX_TXN 16\n+#define MAX_PKTS_PER_RX_TXN 32\n+\n+static int mm81x_yaps_alloc_pkt_buffers(struct mm81x_yaps *yaps)\n+{\n+\tyaps-\u003ehw.to_chip_pkts = kcalloc(MAX_PKTS_PER_TX_TXN,\n+\t\t\t\t\tsizeof(*yaps-\u003ehw.to_chip_pkts),\n+\t\t\t\t\tGFP_KERNEL);\n+\tif (!yaps-\u003ehw.to_chip_pkts)\n+\t\treturn -ENOMEM;\n+\n+\tyaps-\u003ehw.from_chip_pkts = kcalloc(MAX_PKTS_PER_RX_TXN,\n+\t\t\t\t\t  sizeof(*yaps-\u003ehw.from_chip_pkts),\n+\t\t\t\t\t  GFP_KERNEL);\n+\tif (!yaps-\u003ehw.from_chip_pkts) {\n+\t\tkfree(yaps-\u003ehw.to_chip_pkts);\n+\t\tyaps-\u003ehw.to_chip_pkts = NULL;\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void mm81x_yaps_free_pkt_buffers(struct mm81x_yaps *yaps)\n+{\n+\tkfree(yaps-\u003ehw.from_chip_pkts);\n+\tyaps-\u003ehw.from_chip_pkts = NULL;\n+\tkfree(yaps-\u003ehw.to_chip_pkts);\n+\tyaps-\u003ehw.to_chip_pkts = NULL;\n+}\n+\n+static int mm81x_yaps_write_pkts(struct mm81x_yaps *yaps,\n+\t\t\t\t struct mm81x_yaps_pkt *pkts, int num_pkts,\n+\t\t\t\t int *num_pkts_sent)\n+{\n+\treturn yaps-\u003eops-\u003ewrite_pkts(yaps, pkts, num_pkts, num_pkts_sent);\n+}\n+\n+static int mm81x_yaps_read_pkts(struct mm81x_yaps *yaps,\n+\t\t\t\tstruct mm81x_yaps_pkt *pkts, int num_pkts_max,\n+\t\t\t\tint *num_pkts_received)\n+{\n+\treturn yaps-\u003eops-\u003eread_pkts(yaps, pkts, num_pkts_max,\n+\t\t\t\t    num_pkts_received);\n+}\n+\n+static int mm81x_yaps_update_status(struct mm81x_yaps *yaps)\n+{\n+\treturn yaps-\u003eops-\u003eupdate_status(yaps);\n+}\n+\n+/* Mappings between sk_buff, skbq and yaps */\n+static struct mm81x_skbq *mm81x_yaps_tc_q_from_aci(struct mm81x *mors, int aci)\n+{\n+\tstruct mm81x_yaps *yaps = \u0026mors-\u003ehif.u.yaps;\n+\n+\tif (aci \u003e= ARRAY_SIZE(yaps-\u003edata_tx_qs))\n+\t\treturn NULL;\n+\treturn \u0026yaps-\u003edata_tx_qs[aci];\n+}\n+\n+static void mm81x_yaps_get_tx_qs(struct mm81x *mors, struct mm81x_skbq **qs,\n+\t\t\t\t int *num_qs)\n+{\n+\t*qs = mors-\u003ehif.u.yaps.data_tx_qs;\n+\t*num_qs = YAPS_TX_SKBQ_MAX;\n+}\n+\n+static struct mm81x_skbq *mm81x_yaps_get_bcn_tc_q(struct mm81x *mors)\n+{\n+\treturn \u0026mors-\u003ehif.u.yaps.beacon_q;\n+}\n+\n+static struct mm81x_skbq *mm81x_yaps_get_mgmt_tc_q(struct mm81x *mors)\n+{\n+\treturn \u0026mors-\u003ehif.u.yaps.mgmt_q;\n+}\n+\n+static struct mm81x_skbq *mm81x_yaps_get_tx_cmd_queue(struct mm81x *mors)\n+{\n+\treturn \u0026mors-\u003ehif.u.yaps.cmd_q;\n+}\n+\n+static int mm81x_yaps_irq_handler(struct mm81x *mors, u32 status)\n+{\n+\tif (status \u0026 BIT(MM81X_INT_YAPS_FC_PKT_WAITING_IRQN))\n+\t\tset_bit(MM81X_HIF_EVT_RX_PEND, \u0026mors-\u003ehif.event_flags);\n+\n+\tif (status \u0026 BIT(MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN)) {\n+\t\ttimer_delete_sync_try(\u0026mors-\u003ehif.u.yaps.chip_queue_full.timer);\n+\t\tset_bit(MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND,\n+\t\t\t\u0026mors-\u003ehif.event_flags);\n+\t}\n+\n+\tqueue_work(mors-\u003echip_wq, \u0026mors-\u003ehif_work);\n+\treturn 0;\n+}\n+\n+const struct mm81x_hif_ops mm81x_yaps_ops = {\n+\t.init = mm81x_yaps_init,\n+\t.flush_tx_data = mm81x_yaps_flush_tx_data,\n+\t.flush_cmds = mm81x_yaps_flush_cmds,\n+\t.get_tx_status_pending_count = mm81x_yaps_get_tx_status_pending_count,\n+\t.get_tx_buffered_count = mm81x_yaps_get_tx_buffered_count,\n+\t.finish = mm81x_yaps_finish,\n+\t.skbq_get_tx_qs = mm81x_yaps_get_tx_qs,\n+\t.get_tx_beacon_queue = mm81x_yaps_get_bcn_tc_q,\n+\t.get_tx_mgmt_queue = mm81x_yaps_get_mgmt_tc_q,\n+\t.get_tx_cmd_queue = mm81x_yaps_get_tx_cmd_queue,\n+\t.get_tx_data_queue = mm81x_yaps_tc_q_from_aci,\n+\t.handle_irq = mm81x_yaps_irq_handler\n+};\n+\n+static int mm81x_yaps_read_pkt(struct mm81x_yaps *yaps, struct sk_buff *skb)\n+{\n+\tstruct mm81x *mors = yaps-\u003emors;\n+\tstruct sk_buff_head skbq;\n+\tstruct mm81x_skbq *mq = NULL;\n+\tstruct mm81x_skb_hdr *hdr;\n+\tint skb_bytes_remaining;\n+\tint skb_len;\n+\tint ret = 0;\n+\n+\tif (!skb) {\n+\t\tret = -EINVAL;\n+\t\tgoto exit_return_page;\n+\t}\n+\n+\t__skb_queue_head_init(\u0026skbq);\n+\n+\thdr = (struct mm81x_skb_hdr *)skb-\u003edata;\n+\tif (hdr-\u003esync != MM81X_SKB_HEADER_SYNC) {\n+\t\tdev_err(mors-\u003edev, \"sync value error [0xAA:%d], hdr.len %d\",\n+\t\t\thdr-\u003esync, hdr-\u003elen);\n+\t\tret = -EIO;\n+\t\tgoto exit_return_page;\n+\t}\n+\n+\tif (yaps-\u003emors-\u003ehif.validate_skb_checksum \u0026\u0026\n+\t    !mm81x_skbq_validate_checksum(skb-\u003edata)) {\n+\t\tdev_dbg(yaps-\u003emors-\u003edev,\n+\t\t\t\"SKB checksum is invalid hdr:[c:%02X s:%02X len:%d]\",\n+\t\t\thdr-\u003echannel, hdr-\u003esync, hdr-\u003elen);\n+\n+\t\tif (hdr-\u003echannel != MM81X_SKB_CHAN_TX_STATUS) {\n+\t\t\tret = -EIO;\n+\t\t\tgoto exit;\n+\t\t}\n+\t}\n+\n+\tswitch (hdr-\u003echannel) {\n+\tcase MM81X_SKB_CHAN_DATA:\n+\tcase MM81X_SKB_CHAN_NDP_FRAMES:\n+\tcase MM81X_SKB_CHAN_TX_STATUS:\n+\tcase MM81X_SKB_CHAN_DATA_NOACK:\n+\tcase MM81X_SKB_CHAN_BEACON:\n+\tcase MM81X_SKB_CHAN_MGMT:\n+\t\tmq = \u0026yaps-\u003edata_rx_q;\n+\t\tbreak;\n+\tcase MM81X_SKB_CHAN_COMMAND:\n+\t\tmq = \u0026yaps-\u003ecmd_resp_q;\n+\t\tbreak;\n+\tdefault:\n+\t\tdev_err(mors-\u003edev, \"channel value error [%d]\", hdr-\u003echannel);\n+\t\tret = -EIO;\n+\t\tgoto exit_return_page;\n+\t}\n+\n+\tskb_len = sizeof(*hdr) + hdr-\u003eoffset + le16_to_cpu(hdr-\u003elen);\n+\tskb_bytes_remaining = mm81x_skbq_space(mq);\n+\n+\tif (skb_len \u003e skb_bytes_remaining) {\n+\t\tdev_err(mors-\u003edev,\n+\t\t\t\"Page will not fit in SKBQ, dropping - len %d remain %d\",\n+\t\t\tskb_len, skb_bytes_remaining);\n+\t\tret = -ENOMEM;\n+\t\t/* Queue work to clear backlog */\n+\t\tqueue_work(mors-\u003enet_wq, \u0026mq-\u003edispatch_work);\n+\t\tgoto exit_return_page;\n+\t}\n+\n+\tskb_trim(skb, skb_len);\n+\t__skb_queue_tail(\u0026skbq, skb);\n+\n+\tif (skb_queue_len(\u0026skbq))\n+\t\tmm81x_skbq_enq(mq, \u0026skbq);\n+\n+\t/* push packets up in a different context */\n+\tqueue_work(mors-\u003enet_wq, \u0026mq-\u003edispatch_work);\n+\n+\tgoto exit;\n+\n+exit_return_page:\n+\tif (ret \u0026\u0026 mq) {\n+\t\tdev_err(mors-\u003edev, \"failed %d\", ret);\n+\t\tmm81x_skbq_purge(mq, \u0026skbq);\n+\t\tgoto exit;\n+\t}\n+\n+exit:\n+\tif (ret \u0026\u0026 skb)\n+\t\tdev_kfree_skb(skb);\n+\n+\treturn ret;\n+}\n+\n+static int mm81x_yaps_tx(struct mm81x_yaps *yaps, struct mm81x_skbq *mq)\n+{\n+\tint i;\n+\tint ret = 0;\n+\tint num_skbs = 0;\n+\tint tc_pkt_idx = 0;\n+\tint num_pkts_sent = 0;\n+\tstruct sk_buff *skb;\n+\tstruct sk_buff_head skbq_to_send;\n+\tstruct sk_buff_head skbq_sent;\n+\tstruct sk_buff_head skbq_failed;\n+\tstruct sk_buff *pfirst, *pnext;\n+\tstruct mm81x *mors = yaps-\u003emors;\n+\tstruct mm81x_skb_hdr *hdr;\n+\n+\t/* Check there is something on the queue */\n+\tspin_lock_bh(\u0026mq-\u003elock);\n+\tskb = skb_peek(\u0026mq-\u003eskbq);\n+\tspin_unlock_bh(\u0026mq-\u003elock);\n+\tif (!skb)\n+\t\treturn 0;\n+\n+\t__skb_queue_head_init(\u0026skbq_to_send);\n+\t__skb_queue_head_init(\u0026skbq_sent);\n+\t__skb_queue_head_init(\u0026skbq_failed);\n+\n+\tif (mq == \u0026yaps-\u003ecmd_q)\n+\t\t/* Purge timed-out commands (this should not happen) */\n+\t\tmm81x_skbq_purge(mq, \u0026mq-\u003epending);\n+\telse if (mq == \u0026yaps-\u003emgmt_q \u0026\u0026 skb_queue_len(\u0026mq-\u003eskbq) \u003e 0)\n+\t\t/*\n+\t\t * Purge old mgmt frames that have not been sent due to\n+\t\t * congestion\n+\t\t */\n+\t\tmm81x_skbq_purge_aged(mors, mq);\n+\n+\tnum_skbs =\n+\t\tmm81x_skbq_deq_num_skb(mq, \u0026skbq_to_send, MAX_PKTS_PER_TX_TXN);\n+\n+\tskb_queue_walk_safe(\u0026skbq_to_send, pfirst, pnext) {\n+\t\tenum mm81x_yaps_to_chip_q tc_queue;\n+\n+\t\thdr = (struct mm81x_skb_hdr *)pfirst-\u003edata;\n+\t\tswitch (hdr-\u003echannel) {\n+\t\tcase MM81X_SKB_CHAN_COMMAND:\n+\t\t\ttc_queue = MM81X_YAPS_CMD_Q;\n+\t\t\tbreak;\n+\t\tcase MM81X_SKB_CHAN_BEACON:\n+\t\t\ttc_queue = MM81X_YAPS_BEACON_Q;\n+\t\t\tbreak;\n+\t\tcase MM81X_SKB_CHAN_MGMT:\n+\t\t\ttc_queue = MM81X_YAPS_MGMT_Q;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\ttc_queue = MM81X_YAPS_TX_Q;\n+\t\t\tbreak;\n+\t\t}\n+\t\tyaps-\u003ehw.to_chip_pkts[tc_pkt_idx].tc_queue = tc_queue;\n+\t\tyaps-\u003ehw.to_chip_pkts[tc_pkt_idx].skb = pfirst;\n+\t\ttc_pkt_idx++;\n+\t}\n+\n+\t/* Send queued packets to chip */\n+\tret = mm81x_yaps_update_status(yaps);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = mm81x_yaps_write_pkts(yaps, yaps-\u003ehw.to_chip_pkts, tc_pkt_idx,\n+\t\t\t\t    \u0026num_pkts_sent);\n+\n+\t/* Move sent packets to done queue */\n+\tfor (i = 0; i \u003c num_pkts_sent; ++i) {\n+\t\tpfirst = __skb_dequeue(\u0026skbq_to_send);\n+\t\t__skb_queue_tail(\u0026skbq_sent, pfirst);\n+\t}\n+\n+\tfor (i = num_pkts_sent; i \u003c num_skbs; ++i) {\n+\t\tpfirst = __skb_dequeue(\u0026skbq_to_send);\n+\t\t__skb_queue_tail(\u0026skbq_failed, pfirst);\n+\t}\n+\n+\tif (skb_queue_len(\u0026skbq_failed) \u003e 0) {\n+\t\tmm81x_skbq_enq_prepend(mq, \u0026skbq_failed);\n+\n+\t\t/* queue full, can't requeue */\n+\t\tif (skb_queue_len(\u0026skbq_failed) \u003e 0) {\n+\t\t\tdev_warn(mors-\u003edev,\n+\t\t\t\t \"can't requeue failed pkts, purging\");\n+\t\t\t__skb_queue_purge(\u0026skbq_failed);\n+\t\t}\n+\t}\n+\n+\tif (skb_queue_len(\u0026skbq_sent) \u003e 0)\n+\t\tmm81x_skbq_tx_complete(mq, \u0026skbq_sent);\n+\n+\treturn ret;\n+}\n+\n+/* Returns true if there are TX data pages waiting to be sent */\n+static bool mm81x_yaps_tx_data_handler(struct mm81x_yaps *yaps)\n+{\n+\ts16 aci;\n+\tu32 count = 0;\n+\tstruct mm81x *mors = yaps-\u003emors;\n+\n+\tfor (aci = MM81X_ACI_VO; aci \u003e= 0; aci--) {\n+\t\tstruct mm81x_skbq *data_q = mm81x_yaps_tc_q_from_aci(mors, aci);\n+\n+\t\tif (!mm81x_is_data_tx_allowed(mors))\n+\t\t\tbreak;\n+\n+\t\tyaps-\u003echip_queue_full.is_full = mm81x_yaps_tx(yaps, data_q);\n+\t\tcount += mm81x_skbq_count(data_q);\n+\n+\t\tif (yaps-\u003echip_queue_full.is_full)\n+\t\t\tbreak;\n+\n+\t\tif (aci == MM81X_ACI_BE)\n+\t\t\tbreak;\n+\t}\n+\n+\t/*\n+\t * Data has potentially been transmitted from the data SKBQs.\n+\t * If the mac80211 TX data Qs were previously stopped, now would\n+\t * be a good time to check if they can be started again.\n+\t */\n+\tmm81x_skbq_may_wake_tx_queues(mors);\n+\n+\treturn (count \u003e 0) \u0026\u0026 mm81x_is_data_tx_allowed(mors);\n+}\n+\n+/* Returns true if there are commands waiting to be sent */\n+static bool mm81x_yaps_tx_cmd_handler(struct mm81x_yaps *yaps)\n+{\n+\tstruct mm81x_skbq *cmd_q = \u0026yaps-\u003ecmd_q;\n+\n+\tmm81x_yaps_tx(yaps, cmd_q);\n+\n+\treturn mm81x_skbq_count(cmd_q) \u003e 0;\n+}\n+\n+static bool mm81x_yaps_tx_beacon_handler(struct mm81x_yaps *yaps)\n+{\n+\tstruct mm81x_skbq *beacon_q = \u0026yaps-\u003ebeacon_q;\n+\n+\tmm81x_yaps_tx(yaps, beacon_q);\n+\n+\treturn mm81x_skbq_count(beacon_q) \u003e 0;\n+}\n+\n+static bool mm81x_yaps_tx_mgmt_handler(struct mm81x_yaps *yaps)\n+{\n+\tstruct mm81x_skbq *mgmt_q = \u0026yaps-\u003emgmt_q;\n+\n+\tmm81x_yaps_tx(yaps, mgmt_q);\n+\n+\treturn mm81x_skbq_count(mgmt_q) \u003e 0;\n+}\n+\n+/* Returns true if there are populated RX pages left in the device */\n+static bool mm81x_yaps_rx_handler(struct mm81x_yaps *yaps)\n+{\n+\tint ret = 0;\n+\tint i;\n+\tint num_pks_received;\n+\n+\tret = mm81x_yaps_update_status(yaps);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\tret = mm81x_yaps_read_pkts(yaps, yaps-\u003ehw.from_chip_pkts,\n+\t\t\t\t   MAX_PKTS_PER_RX_TXN, \u0026num_pks_received);\n+\tif (ret \u0026\u0026 ret != -EAGAIN) {\n+\t\tdev_err(yaps-\u003emors-\u003edev, \"YAPS read_pkts fail: %d\", ret);\n+\t\tgoto exit;\n+\t}\n+\n+\tfor (i = 0; i \u003c num_pks_received; ++i) {\n+\t\tmm81x_yaps_read_pkt(yaps, yaps-\u003ehw.from_chip_pkts[i].skb);\n+\t\tyaps-\u003ehw.from_chip_pkts[i].skb = NULL;\n+\t}\n+\n+exit:\n+\tif (ret == -ENOMEM || ret == -EAGAIN)\n+\t\treturn true;\n+\telse\n+\t\treturn false;\n+}\n+\n+void mm81x_yaps_stale_tx_work(struct work_struct *work)\n+{\n+\tint i;\n+\tint flushed = 0;\n+\tstruct mm81x *mors = container_of(work, struct mm81x, tx_stale_work);\n+\tstruct mm81x_yaps *yaps;\n+\n+\tyaps = \u0026mors-\u003ehif.u.yaps;\n+\tflushed += mm81x_skbq_check_for_stale_tx(mors, \u0026yaps-\u003ebeacon_q);\n+\tflushed += mm81x_skbq_check_for_stale_tx(mors, \u0026yaps-\u003emgmt_q);\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(yaps-\u003edata_tx_qs); i++)\n+\t\tflushed += mm81x_skbq_check_for_stale_tx(mors,\n+\t\t\t\t\t\t\t \u0026yaps-\u003edata_tx_qs[i]);\n+\n+\tif (!flushed)\n+\t\treturn;\n+\n+\tdev_dbg(mors-\u003edev, \"Flushed %d stale TX SKBs\", flushed);\n+\n+\tif (mors-\u003eps.enable \u0026\u0026 !mors-\u003eps.suspended \u0026\u0026\n+\t    (mm81x_yaps_get_tx_buffered_count(mors) == 0)) {\n+\t\t/* Evaluate ps to check if it was gated on a stale tx status */\n+\t\tqueue_delayed_work(mors-\u003echip_wq, \u0026mors-\u003eps.delayed_eval_work,\n+\t\t\t\t   0);\n+\t}\n+}\n+\n+void mm81x_yaps_work(struct work_struct *work)\n+{\n+\tstruct mm81x *mors = container_of(work, struct mm81x, hif_work);\n+\tunsigned long *flags = \u0026mors-\u003ehif.event_flags;\n+\tstruct mm81x_yaps *yaps = \u0026mors-\u003ehif.u.yaps;\n+\n+\tif (test_bit(MM81X_STATE_CHIP_UNRESPONSIVE, \u0026mors-\u003estate_flags))\n+\t\treturn;\n+\n+\tif (!*flags)\n+\t\treturn;\n+\n+\t/* Disable power save in case it is running */\n+\tmm81x_ps_disable(mors);\n+\tmm81x_claim_bus(mors);\n+\n+\t/*\n+\t * Handle any populated RX pages from chip first to\n+\t * avoid dropping pkts due to full on-chip buffers.\n+\t * Check if all pages were removed, set event flags if not.\n+\t */\n+\tif (test_and_clear_bit(MM81X_HIF_EVT_RX_PEND, flags)) {\n+\t\tif (mm81x_yaps_rx_handler(yaps))\n+\t\t\tset_bit(MM81X_HIF_EVT_RX_PEND, flags);\n+\t}\n+\n+\t/* TX any commands before considering data */\n+\tif (test_and_clear_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, flags)) {\n+\t\tif (mm81x_yaps_tx_cmd_handler(yaps))\n+\t\t\tset_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, flags);\n+\t}\n+\n+\t/* TX beacons before considering mgmt/data */\n+\tif (test_and_clear_bit(MM81X_HIF_EVT_TX_BEACON_PEND, flags)) {\n+\t\tif (mm81x_yaps_tx_beacon_handler(yaps))\n+\t\t\tset_bit(MM81X_HIF_EVT_TX_BEACON_PEND, flags);\n+\t}\n+\n+\t/* TX mgmt before considering data */\n+\tif (test_and_clear_bit(MM81X_HIF_EVT_TX_MGMT_PEND, flags)) {\n+\t\tif (mm81x_yaps_tx_mgmt_handler(yaps))\n+\t\t\tset_bit(MM81X_HIF_EVT_TX_MGMT_PEND, flags);\n+\t}\n+\n+\t/* Pause TX data Qs */\n+\tif (test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND, flags)) {\n+\t\ttest_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND,\n+\t\t\t\t   flags);\n+\t\tmm81x_skbq_data_traffic_pause(mors);\n+\t}\n+\n+\t/* Resume TX data Qs  */\n+\tif (test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND, flags))\n+\t\tmm81x_skbq_data_traffic_resume(mors);\n+\n+\t/* Handle chip queue status */\n+\tif (test_and_clear_bit(MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND, flags))\n+\t\tyaps-\u003echip_queue_full.is_full = false;\n+\n+\t/* Check to see if the queue is full or\n+\t * long enough has past since the queue was full\n+\t */\n+\tif (yaps-\u003echip_queue_full.is_full \u0026\u0026\n+\t    time_before(jiffies, yaps-\u003echip_queue_full.retry_expiry))\n+\t\tgoto exit;\n+\n+\t/* Finally TX any data */\n+\tif (test_and_clear_bit(MM81X_HIF_EVT_TX_DATA_PEND, flags)) {\n+\t\tif (mm81x_yaps_tx_data_handler(yaps))\n+\t\t\tset_bit(MM81X_HIF_EVT_TX_DATA_PEND, flags);\n+\n+\t\tif (yaps-\u003echip_queue_full.is_full) {\n+\t\t\tyaps-\u003echip_queue_full.retry_expiry =\n+\t\t\t\tjiffies +\n+\t\t\t\tmsecs_to_jiffies(CHIP_FULL_RECOVERY_TIMEOUT_MS);\n+\t\t\tmod_timer(\u0026yaps-\u003echip_queue_full.timer,\n+\t\t\t\t  yaps-\u003echip_queue_full.retry_expiry);\n+\t\t}\n+\t}\n+\n+exit:\n+\n+\t/* Disable power save in case it is running */\n+\tmm81x_release_bus(mors);\n+\tmm81x_ps_enable(mors);\n+\n+\t/* Don't requeue work if we are shutting down. */\n+\tif (yaps-\u003efinish)\n+\t\treturn;\n+\t/*\n+\t * Evaluate all events except MM81X_HIF_EVT_TX_DATA_PEND in case data\n+\t * tx queue is full\n+\t */\n+\tif ((*flags) \u0026 ~(1 \u003c\u003c MM81X_HIF_EVT_TX_DATA_PEND))\n+\t\tqueue_work(mors-\u003echip_wq, \u0026mors-\u003ehif_work);\n+\t/*\n+\t * if data tx queue is not full and the work hasn't been queued let's\n+\t * queue it\n+\t */\n+\telse if (!yaps-\u003echip_queue_full.is_full \u0026\u0026 *flags)\n+\t\tqueue_work(mors-\u003echip_wq, \u0026mors-\u003ehif_work);\n+}\n+\n+int mm81x_yaps_get_tx_status_pending_count(struct mm81x *mors)\n+{\n+\tint i = 0;\n+\tint count = 0;\n+\tstruct mm81x_yaps *yaps;\n+\n+\tyaps = \u0026mors-\u003ehif.u.yaps;\n+\tcount += skb_queue_len(\u0026yaps-\u003ebeacon_q.pending);\n+\tcount += skb_queue_len(\u0026yaps-\u003emgmt_q.pending);\n+\tcount += skb_queue_len(\u0026yaps-\u003ecmd_q.pending);\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(yaps-\u003edata_tx_qs); i++)\n+\t\tcount += skb_queue_len(\u0026yaps-\u003edata_tx_qs[i].pending);\n+\n+\treturn count;\n+}\n+\n+int mm81x_yaps_get_tx_buffered_count(struct mm81x *mors)\n+{\n+\tint i = 0;\n+\tint count = 0;\n+\tstruct mm81x_yaps *yaps;\n+\n+\tyaps = \u0026mors-\u003ehif.u.yaps;\n+\tcount += skb_queue_len(\u0026yaps-\u003ebeacon_q.skbq) +\n+\t\t skb_queue_len(\u0026yaps-\u003ebeacon_q.pending);\n+\tcount += skb_queue_len(\u0026yaps-\u003emgmt_q.skbq) +\n+\t\t skb_queue_len(\u0026yaps-\u003emgmt_q.pending);\n+\tcount += skb_queue_len(\u0026yaps-\u003ecmd_q.skbq) +\n+\t\t skb_queue_len(\u0026yaps-\u003ecmd_q.pending);\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(yaps-\u003edata_tx_qs); i++)\n+\t\tcount += mm81x_skbq_count_tx_ready(\u0026yaps-\u003edata_tx_qs[i]) +\n+\t\t\t skb_queue_len(\u0026yaps-\u003edata_tx_qs[i].pending);\n+\n+\treturn count;\n+}\n+\n+static void mm81x_yaps_tx_q_full_timer(struct timer_list *t)\n+{\n+\tstruct mm81x_yaps *yaps =\n+\t\ttimer_container_of(yaps, t, chip_queue_full.timer);\n+\n+\tqueue_work(yaps-\u003emors-\u003echip_wq, \u0026yaps-\u003emors-\u003ehif_work);\n+}\n+\n+static void mm81x_yaps_q_chip_full_timer_init(struct mm81x_yaps *yaps)\n+{\n+\ttimer_setup(\u0026yaps-\u003echip_queue_full.timer, mm81x_yaps_tx_q_full_timer,\n+\t\t    0);\n+}\n+\n+static void mm81x_yaps_q_chip_full_timer_finish(struct mm81x_yaps *yaps)\n+{\n+\ttimer_delete_sync_try(\u0026yaps-\u003echip_queue_full.timer);\n+}\n+\n+int mm81x_yaps_init(struct mm81x *mors)\n+{\n+\tint i, ret;\n+\tstruct mm81x_yaps *yaps;\n+\n+\tret = mm81x_yaps_hw_init(mors);\n+\tif (ret) {\n+\t\tdev_err(mors-\u003edev, \"mm81x_yaps_hw_init failed %d\", ret);\n+\t\treturn ret;\n+\t}\n+\n+\tyaps = \u0026mors-\u003ehif.u.yaps;\n+\tyaps-\u003emors = mors;\n+\n+\tmm81x_claim_bus(mors);\n+\n+\tret = mm81x_yaps_alloc_pkt_buffers(yaps);\n+\tif (ret) {\n+\t\tdev_err(mors-\u003edev, \"Failed to allocate YAPS packet buffers: %d\",\n+\t\t\tret);\n+\t\tmm81x_yaps_hw_finish(mors);\n+\t\tmm81x_release_bus(mors);\n+\t\treturn ret;\n+\t}\n+\n+\t/* YAPS is bi-directional */\n+\tmm81x_skbq_init(mors, \u0026yaps-\u003edata_rx_q,\n+\t\t\tMM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);\n+\tmm81x_skbq_init(mors, \u0026yaps-\u003ebeacon_q,\n+\t\t\tMM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);\n+\tmm81x_skbq_init(mors, \u0026yaps-\u003emgmt_q,\n+\t\t\tMM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(yaps-\u003edata_tx_qs); i++) {\n+\t\tmm81x_skbq_init(mors, \u0026yaps-\u003edata_tx_qs[i],\n+\t\t\t\tMM81X_HIF_FLAGS_DATA |\n+\t\t\t\t\tMM81X_HIF_FLAGS_DIR_TO_CHIP);\n+\t}\n+\n+\tmm81x_skbq_init(mors, \u0026yaps-\u003ecmd_q,\n+\t\t\tMM81X_HIF_FLAGS_COMMAND | MM81X_HIF_FLAGS_DIR_TO_CHIP);\n+\tmm81x_skbq_init(mors, \u0026yaps-\u003ecmd_resp_q,\n+\t\t\tMM81X_HIF_FLAGS_COMMAND | MM81X_HIF_FLAGS_DIR_TO_HOST);\n+\n+\tmm81x_yaps_q_chip_full_timer_init(yaps);\n+\tINIT_WORK(\u0026mors-\u003ehif_work, mm81x_yaps_work);\n+\tINIT_WORK(\u0026mors-\u003etx_stale_work, mm81x_yaps_stale_tx_work);\n+\tmm81x_release_bus(mors);\n+\tmm81x_hw_enable_stop_notifications(mors, true);\n+\treturn 0;\n+}\n+\n+void mm81x_yaps_finish(struct mm81x *mors)\n+{\n+\tint i;\n+\tstruct mm81x_yaps *yaps;\n+\n+\tmm81x_yaps_hw_enable_irqs(mors, false);\n+\n+\tyaps = \u0026mors-\u003ehif.u.yaps;\n+\tyaps-\u003efinish = true;\n+\n+\tmm81x_skbq_finish(\u0026yaps-\u003edata_rx_q);\n+\tmm81x_skbq_finish(\u0026yaps-\u003ebeacon_q);\n+\tmm81x_skbq_finish(\u0026yaps-\u003emgmt_q);\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(yaps-\u003edata_tx_qs); i++)\n+\t\tmm81x_skbq_finish(\u0026yaps-\u003edata_tx_qs[i]);\n+\n+\tmm81x_skbq_finish(\u0026yaps-\u003ecmd_q);\n+\tmm81x_skbq_finish(\u0026yaps-\u003ecmd_resp_q);\n+\n+\tmm81x_yaps_q_chip_full_timer_finish(yaps);\n+\n+\tcancel_work_sync(\u0026mors-\u003ehif_work);\n+\tcancel_work_sync(\u0026mors-\u003etx_stale_work);\n+\n+\tmm81x_yaps_free_pkt_buffers(yaps);\n+\tmm81x_yaps_hw_finish(mors);\n+}\n+\n+void mm81x_yaps_flush_tx_data(struct mm81x *mors)\n+{\n+\tint i;\n+\tstruct mm81x_yaps *yaps = \u0026mors-\u003ehif.u.yaps;\n+\n+\tmm81x_skbq_tx_flush(\u0026yaps-\u003ebeacon_q);\n+\tmm81x_skbq_tx_flush(\u0026yaps-\u003emgmt_q);\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(yaps-\u003edata_tx_qs); i++)\n+\t\tmm81x_skbq_tx_flush(\u0026yaps-\u003edata_tx_qs[i]);\n+}\n+\n+void mm81x_yaps_flush_cmds(struct mm81x *mors)\n+{\n+\tstruct mm81x_yaps *yaps = \u0026mors-\u003ehif.u.yaps;\n+\n+\tif (yaps-\u003eflags \u0026 MM81X_HIF_FLAGS_COMMAND) {\n+\t\tmm81x_skbq_finish(\u0026yaps-\u003ecmd_q);\n+\t\tmm81x_skbq_finish(\u0026yaps-\u003ecmd_resp_q);\n+\t}\n+}\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/yaps.h b/drivers/net/wireless/morsemicro/mm81x/yaps.h\nnew file mode 100644\nindex 00000000000000..2b2bb5f6e3990a\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/yaps.h\n@@ -0,0 +1,77 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#ifndef _MM81X_YAPS_H_\n+#define _MM81X_YAPS_H_\n+\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+#include \"skbq.h\"\n+\n+#define YAPS_TX_SKBQ_MAX 4\n+\n+struct mm81x_hif_ops;\n+extern const struct mm81x_hif_ops mm81x_yaps_ops;\n+\n+enum mm81x_yaps_to_chip_q {\n+\tMM81X_YAPS_TX_Q = 0,\n+\tMM81X_YAPS_CMD_Q,\n+\tMM81X_YAPS_BEACON_Q,\n+\tMM81X_YAPS_MGMT_Q,\n+\t/* Keep this last */\n+\tMM81X_YAPS_NUM_TC_Q\n+};\n+\n+struct mm81x_yaps_pkt {\n+\tstruct sk_buff *skb;\n+\tenum mm81x_yaps_to_chip_q tc_queue;\n+};\n+\n+struct mm81x_yaps {\n+\tstruct mm81x *mors;\n+\tstruct mm81x_yaps_hw_aux_data *aux_data;\n+\tconst struct mm81x_yaps_ops *ops;\n+\tu8 flags;\n+\tstruct {\n+\t\tstruct mm81x_yaps_pkt *to_chip_pkts;\n+\t\tstruct mm81x_yaps_pkt *from_chip_pkts;\n+\t} hw;\n+\n+\t/* Chip interface is stopping, new work should not be enqueued. */\n+\tbool finish;\n+\n+\tstruct mm81x_skbq data_tx_qs[YAPS_TX_SKBQ_MAX];\n+\tstruct mm81x_skbq beacon_q;\n+\tstruct mm81x_skbq mgmt_q;\n+\tstruct mm81x_skbq data_rx_q;\n+\tstruct mm81x_skbq cmd_q;\n+\tstruct mm81x_skbq cmd_resp_q;\n+\n+\tstruct {\n+\t\tstruct timer_list timer;\n+\t\tunsigned long retry_expiry;\n+\t\tbool is_full;\n+\t} chip_queue_full;\n+};\n+\n+struct mm81x_yaps_ops {\n+\tint (*write_pkts)(struct mm81x_yaps *yaps, struct mm81x_yaps_pkt *pkts,\n+\t\t\t  int num_pkts, int *num_pkts_sent);\n+\tint (*read_pkts)(struct mm81x_yaps *yaps, struct mm81x_yaps_pkt *pkts,\n+\t\t\t int num_pkts_max, int *num_pkts_received);\n+\tint (*update_status)(struct mm81x_yaps *yaps);\n+};\n+\n+int mm81x_yaps_init(struct mm81x *mors);\n+void mm81x_yaps_show(struct mm81x_yaps *yaps, struct seq_file *file);\n+void mm81x_yaps_finish(struct mm81x *mors);\n+void mm81x_yaps_flush_tx_data(struct mm81x *mors);\n+void mm81x_yaps_flush_cmds(struct mm81x *mors);\n+void mm81x_yaps_work(struct work_struct *work);\n+void mm81x_yaps_stale_tx_work(struct work_struct *work);\n+int mm81x_yaps_get_tx_status_pending_count(struct mm81x *mors);\n+int mm81x_yaps_get_tx_buffered_count(struct mm81x *mors);\n+\n+#endif /* !_MM81X_YAPS_H_ */\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c\nnew file mode 100644\nindex 00000000000000..3d641d23c35ba7\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c\n@@ -0,0 +1,702 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+#include \"yaps_hw.h\"\n+#include \"bus.h\"\n+#include \"hif.h\"\n+#include \"yaps.h\"\n+\n+#define YAPS_HW_WINDOW_SIZE_BYTES 32768\n+#define YAPS_MAX_PKT_SIZE_BYTES 16128\n+#define YAPS_METADATA_PAGE_COUNT 1\n+\n+#define YAPS_PHANDLE_CORRUPTION_WAR_EXTRA_PAGE 1\n+\n+#define YAPS_PAGE_SIZE 256\n+\n+/* Calculate padding required for yaps transaction */\n+#define YAPS_CALC_PADDING(_bytes) ((_bytes) \u0026 0x3 ? (4 - ((_bytes) \u0026 0x3)) : 0)\n+\n+#define YAPS_RESERVED_PAGE_SIZE 256\n+\n+/*\n+ * Yaps data stream delimiter is a 32 bit word with the following fields:\n+ *\n+ * pkt_size (14 bits) - Packet size not including delimiter or padding\n+ * pool_id  (3  bits) - Pool that pages should be allocated from.\n+ * padding  (2  bits) - Padding required to bring packet to word (4 byte)\n+ * irq      (1  bit ) - Raise a PKT_IRQ on the YDS this is sent to\n+ * reserved (5  bits) - Reserved, must write as 0\n+ * crc      (7  bits) - YAPS CRC\n+ */\n+\n+/* Packet size not including delimiter or padding */\n+#define YAPS_DELIM_GET_PKT_SIZE(_delim) \\\n+\t(((_delim) \u0026 0x3FFF) - YAPS_RESERVED_PAGE_SIZE)\n+#define YAPS_DELIM_SET_PKT_SIZE(_pkt_size) \\\n+\t(((_pkt_size) \u0026 0x3FFF) + YAPS_RESERVED_PAGE_SIZE)\n+#define YAPS_DELIM_GET_PHANDLE_SIZE(_delim) (((_delim) \u0026 0x3FFF))\n+\n+/* Pool that pages should be allocated from. */\n+#define YAPS_DELIM_SET_POOL_ID(_pool_id) (((_pool_id) \u0026 0x7) \u003c\u003c 14)\n+\n+/* Padding required to bring packet to word (4 byte) boundary */\n+#define YAPS_DELIM_GET_PADDING(_delim) (((_delim) \u003e\u003e 17) \u0026 0x3)\n+#define YAPS_DELIM_SET_PADDING(_padding) (((_padding) \u0026 0x3) \u003c\u003c 17)\n+\n+/* Raise a PKT_IRQ on the YDS this is sent to */\n+#define YAPS_DELIM_SET_IRQ(_irq) (((_irq) \u0026 0x1) \u003c\u003c 19)\n+\n+/* YAPS CRC */\n+#define YAPS_DELIM_GET_CRC(_delim) (((_delim) \u003e\u003e 25) \u0026 0x7F)\n+#define YAPS_DELIM_SET_CRC(_crc) (((_crc) \u0026 0x7F) \u003c\u003c 25)\n+\n+struct mm81x_yaps_status_regs {\n+\t/* Allocation pools */\n+\tu32 tc_tx_pool_num_pages;\n+\tu32 tc_cmd_pool_num_pages;\n+\tu32 tc_beacon_pool_num_pages;\n+\tu32 tc_mgmt_pool_num_pages;\n+\tu32 fc_rx_pool_num_pages;\n+\tu32 fc_resp_pool_num_pages;\n+\tu32 fc_tx_sts_pool_num_pages;\n+\tu32 fc_aux_pool_num_pages;\n+\tu32 tc_tx_num_pkts;\n+\tu32 tc_cmd_num_pkts;\n+\tu32 tc_beacon_num_pkts;\n+\tu32 tc_mgmt_num_pkts;\n+\tu32 fc_num_pkts;\n+\tu32 fc_done_num_pkts;\n+\tu32 fc_rx_bytes_in_queue;\n+\tu32 tc_delim_crc_fail_detected;\n+\tu32 fc_host_ysl_status;\n+\tu32 lock;\n+} __packed __aligned(8);\n+\n+struct mm81x_yaps_hw_status_regs {\n+\t__le32 tc_tx_pool_num_pages;\n+\t__le32 tc_cmd_pool_num_pages;\n+\t__le32 tc_beacon_pool_num_pages;\n+\t__le32 tc_mgmt_pool_num_pages;\n+\t__le32 fc_rx_pool_num_pages;\n+\t__le32 fc_resp_pool_num_pages;\n+\t__le32 fc_tx_sts_pool_num_pages;\n+\t__le32 fc_aux_pool_num_pages;\n+\t__le32 tc_tx_num_pkts;\n+\t__le32 tc_cmd_num_pkts;\n+\t__le32 tc_beacon_num_pkts;\n+\t__le32 tc_mgmt_num_pkts;\n+\t__le32 fc_num_pkts;\n+\t__le32 fc_done_num_pkts;\n+\t__le32 fc_rx_bytes_in_queue;\n+\t__le32 tc_delim_crc_fail_detected;\n+\t__le32 fc_host_ysl_status;\n+\t__le32 lock;\n+} __packed __aligned(8);\n+\n+struct mm81x_yaps_hw_aux_data {\n+\tunsigned long access_lock;\n+\n+\tu32 yds_addr;\n+\tu32 ysl_addr;\n+\tu32 status_regs_addr;\n+\n+\t/* Alloc pool sizes */\n+\tu16 tc_tx_pool_size;\n+\tu16 tc_cmd_pool_size;\n+\tu8 tc_beacon_pool_size;\n+\tu8 tc_mgmt_pool_size;\n+\tu8 fc_rx_pool_size;\n+\tu8 fc_resp_pool_size;\n+\tu8 fc_tx_sts_pool_size;\n+\tu8 fc_aux_pool_size;\n+\n+\t/* To chip/from chip queue sizes */\n+\tu8 tc_tx_q_size;\n+\tu8 tc_cmd_q_size;\n+\tu8 tc_beacon_q_size;\n+\tu8 tc_mgmt_q_size;\n+\tu8 fc_q_size;\n+\tu8 fc_done_q_size;\n+\n+\tu16 reserved_yaps_page_size;\n+\n+\t/* Buffers to/from chip to support large contiguous reads/writes */\n+\tchar *to_chip_buffer;\n+\tchar *from_chip_buffer;\n+\n+\t/* status registers in host endian */\n+\tstruct mm81x_yaps_status_regs status_regs;\n+\n+\t/* DMA target buffer in firmware endian */\n+\tstruct mm81x_yaps_hw_status_regs hw_status_regs;\n+};\n+\n+static int mm81x_yaps_hw_lock(struct mm81x_yaps *yaps)\n+{\n+\tif (test_and_set_bit_lock(0, \u0026yaps-\u003eaux_data-\u003eaccess_lock))\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+static void mm81x_yaps_hw_unlock(struct mm81x_yaps *yaps)\n+{\n+\tclear_bit_unlock(0, \u0026yaps-\u003eaux_data-\u003eaccess_lock);\n+}\n+\n+static void\n+mm81x_yaps_hw_fill_aux_data_from_hw_tbl(struct mm81x_yaps_hw_aux_data *a,\n+\t\t\t\t\tstruct mm81x_yaps_hw_table *t)\n+{\n+\ta-\u003eysl_addr = __le32_to_cpu(t-\u003eysl_addr);\n+\ta-\u003eyds_addr = __le32_to_cpu(t-\u003eyds_addr);\n+\ta-\u003estatus_regs_addr = __le32_to_cpu(t-\u003estatus_regs_addr);\n+\ta-\u003etc_tx_pool_size = __le16_to_cpu(t-\u003etc_tx_pool_size);\n+\ta-\u003efc_rx_pool_size = __le16_to_cpu(t-\u003efc_rx_pool_size);\n+\ta-\u003etc_cmd_pool_size = t-\u003etc_cmd_pool_size;\n+\ta-\u003etc_beacon_pool_size = t-\u003etc_beacon_pool_size;\n+\ta-\u003etc_mgmt_pool_size = t-\u003etc_mgmt_pool_size;\n+\ta-\u003efc_resp_pool_size = t-\u003efc_resp_pool_size;\n+\ta-\u003efc_tx_sts_pool_size = t-\u003efc_tx_sts_pool_size;\n+\ta-\u003efc_aux_pool_size = t-\u003efc_aux_pool_size;\n+\ta-\u003etc_tx_q_size = t-\u003etc_tx_q_size;\n+\ta-\u003etc_cmd_q_size = t-\u003etc_cmd_q_size;\n+\ta-\u003etc_beacon_q_size = t-\u003etc_beacon_q_size;\n+\ta-\u003etc_mgmt_q_size = t-\u003etc_mgmt_q_size;\n+\ta-\u003efc_q_size = t-\u003efc_q_size;\n+\ta-\u003efc_done_q_size = t-\u003efc_done_q_size;\n+\ta-\u003ereserved_yaps_page_size = le16_to_cpu(t-\u003eyaps_reserved_page_size);\n+}\n+\n+static u8 mm81x_yaps_hw_crc(u32 word)\n+{\n+\tu8 crc = 0;\n+\tu8 byte;\n+\tint i;\n+\n+\t/* Mask to look at only non-CRC bits */\n+\tword \u0026= 0x1ffffff;\n+\n+\tfor (i = 0; i \u003c 4; i++) {\n+\t\tbyte = (word \u003e\u003e 24) \u0026 0xff;\n+\t\tcrc = crc7_be(crc, \u0026byte, 1);\n+\t\tword \u003c\u003c= 8;\n+\t}\n+\n+\treturn crc \u003e\u003e 1;\n+}\n+\n+static u32 mm81x_write_pkts_h_build_delim(struct mm81x_yaps *yaps,\n+\t\t\t\t\t  unsigned int size, u8 pool_id,\n+\t\t\t\t\t  bool irq)\n+{\n+\tu32 delim = 0;\n+\n+\tdelim |= YAPS_DELIM_SET_PKT_SIZE(size);\n+\tdelim |= YAPS_DELIM_SET_PADDING(YAPS_CALC_PADDING(size));\n+\tdelim |= YAPS_DELIM_SET_POOL_ID(pool_id);\n+\tdelim |= YAPS_DELIM_SET_IRQ(irq);\n+\tdelim |= YAPS_DELIM_SET_CRC(mm81x_yaps_hw_crc(delim));\n+\treturn delim;\n+}\n+\n+void mm81x_yaps_hw_enable_irqs(struct mm81x *mors, bool enable)\n+{\n+\tmm81x_hw_irq_enable(mors, MM81X_INT_YAPS_FC_PKT_WAITING_IRQN, enable);\n+\tmm81x_hw_irq_enable(mors, MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN,\n+\t\t\t    enable);\n+}\n+\n+void mm81x_yaps_hw_read_table(struct mm81x *mors,\n+\t\t\t      struct mm81x_yaps_hw_table *tbl_ptr)\n+{\n+\tmm81x_yaps_hw_fill_aux_data_from_hw_tbl(mors-\u003ehif.u.yaps.aux_data,\n+\t\t\t\t\t\ttbl_ptr);\n+\tmm81x_yaps_hw_enable_irqs(mors, true);\n+}\n+\n+static unsigned int mm81x_write_pkts_h_pages_required(struct mm81x_yaps *yaps,\n+\t\t\t\t\t\t      unsigned int size_bytes)\n+{\n+\t/* Always account for the first metadata page */\n+\treturn DIV_ROUND_UP(size_bytes +\n+\t\t\t\t    yaps-\u003eaux_data-\u003ereserved_yaps_page_size,\n+\t\t\t    YAPS_PAGE_SIZE) +\n+\t       YAPS_METADATA_PAGE_COUNT +\n+\t       YAPS_PHANDLE_CORRUPTION_WAR_EXTRA_PAGE;\n+}\n+\n+/*\n+ * Checks if a single pkt will fit in the chip using the pool/alloc holding\n+ * information from the last status register read.\n+ */\n+static bool mm81x_write_pkts_h_will_fit(struct mm81x_yaps *yaps,\n+\t\t\t\t\tstruct mm81x_yaps_pkt *pkt, bool update)\n+{\n+\tbool will_fit = true;\n+\tconst int pages_required =\n+\t\tmm81x_write_pkts_h_pages_required(yaps, pkt-\u003eskb-\u003elen);\n+\tint *pool_pages_avail = NULL;\n+\tint *pkts_in_queue = NULL;\n+\tint queue_pkts_avail = 0;\n+\n+\tswitch (pkt-\u003etc_queue) {\n+\tcase MM81X_YAPS_TX_Q:\n+\t\tpool_pages_avail =\n+\t\t\t\u0026yaps-\u003eaux_data-\u003estatus_regs.tc_tx_pool_num_pages;\n+\t\tpkts_in_queue = \u0026yaps-\u003eaux_data-\u003estatus_regs.tc_tx_num_pkts;\n+\t\tqueue_pkts_avail =\n+\t\t\tyaps-\u003eaux_data-\u003etc_tx_q_size - *pkts_in_queue;\n+\t\tbreak;\n+\tcase MM81X_YAPS_CMD_Q:\n+\t\tpool_pages_avail =\n+\t\t\t\u0026yaps-\u003eaux_data-\u003estatus_regs.tc_cmd_pool_num_pages;\n+\t\tpkts_in_queue = \u0026yaps-\u003eaux_data-\u003estatus_regs.tc_cmd_num_pkts;\n+\t\tqueue_pkts_avail =\n+\t\t\tyaps-\u003eaux_data-\u003etc_cmd_q_size - *pkts_in_queue;\n+\t\tbreak;\n+\tcase MM81X_YAPS_BEACON_Q:\n+\t\tpool_pages_avail =\n+\t\t\t\u0026yaps-\u003eaux_data-\u003estatus_regs.tc_beacon_pool_num_pages;\n+\t\tpkts_in_queue = \u0026yaps-\u003eaux_data-\u003estatus_regs.tc_beacon_num_pkts;\n+\t\tqueue_pkts_avail =\n+\t\t\tyaps-\u003eaux_data-\u003etc_beacon_q_size - *pkts_in_queue;\n+\t\tbreak;\n+\tcase MM81X_YAPS_MGMT_Q:\n+\t\tpool_pages_avail =\n+\t\t\t\u0026yaps-\u003eaux_data-\u003estatus_regs.tc_mgmt_pool_num_pages;\n+\t\tpkts_in_queue = \u0026yaps-\u003eaux_data-\u003estatus_regs.tc_mgmt_num_pkts;\n+\t\tqueue_pkts_avail =\n+\t\t\tyaps-\u003eaux_data-\u003etc_mgmt_q_size - *pkts_in_queue;\n+\t\tbreak;\n+\tdefault:\n+\t\tdev_err(yaps-\u003emors-\u003edev, \"yaps invalid tc queue\");\n+\t\treturn false;\n+\t}\n+\n+\tWARN_ON(queue_pkts_avail \u003c 0);\n+\n+\tif (pages_required \u003e *pool_pages_avail)\n+\t\twill_fit = false;\n+\n+\tif (queue_pkts_avail == 0)\n+\t\twill_fit = false;\n+\n+\tif (will_fit \u0026\u0026 update) {\n+\t\t*pool_pages_avail -= pages_required;\n+\t\t*pkts_in_queue += 1;\n+\t}\n+\n+\treturn will_fit;\n+}\n+\n+static int mm81x_write_pkts_h_err_check(struct mm81x_yaps *yaps,\n+\t\t\t\t\tstruct mm81x_yaps_pkt *pkt)\n+{\n+\tif (pkt-\u003eskb-\u003elen + yaps-\u003eaux_data-\u003ereserved_yaps_page_size \u003e\n+\t    YAPS_MAX_PKT_SIZE_BYTES)\n+\t\treturn -EMSGSIZE;\n+\tif (pkt-\u003etc_queue \u003e= MM81X_YAPS_NUM_TC_Q)\n+\t\treturn -EINVAL;\n+\tif (!mm81x_write_pkts_h_will_fit(yaps, pkt, true))\n+\t\treturn -EAGAIN;\n+\n+\treturn 0;\n+}\n+\n+static int mm81x_yaps_hw_write_pkts(struct mm81x_yaps *yaps,\n+\t\t\t\t    struct mm81x_yaps_pkt *pkts, int num_pkts,\n+\t\t\t\t    int *num_pkts_sent)\n+{\n+\tint ret = 0;\n+\tint i;\n+\tu32 delim = 0;\n+\tint tx_len;\n+\tint batch_txn_len = 0;\n+\tint pkts_pending = 0;\n+\tbool delim_irq = false;\n+\tchar *to_chip_buffer_aligned =\n+\t\tPTR_ALIGN(yaps-\u003eaux_data-\u003eto_chip_buffer,\n+\t\t\t  mm81x_bus_get_alignment(yaps-\u003emors));\n+\tchar *write_buf = to_chip_buffer_aligned;\n+\n+\tret = mm81x_yaps_hw_lock(yaps);\n+\tif (ret) {\n+\t\tdev_dbg(yaps-\u003emors-\u003edev, \"yaps lock failed %d\", ret);\n+\t\treturn ret;\n+\t}\n+\n+\t*num_pkts_sent = 0;\n+\n+\t/* Check packet conditions */\n+\tret = mm81x_write_pkts_h_err_check(yaps, \u0026pkts[0]);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\t/* Batch packets into larger transactions */\n+\tfor (i = 0; i \u003c num_pkts; ++i) {\n+\t\tu32 pkt_size =\n+\t\t\tpkts[i].skb-\u003elen + YAPS_CALC_PADDING(pkts[i].skb-\u003elen);\n+\t\ttx_len = pkt_size + sizeof(delim);\n+\n+\t\t/*\n+\t\t * Send when we have reached window size, don't split pkt over\n+\t\t * boundary\n+\t\t */\n+\t\tif ((batch_txn_len + tx_len) \u003e YAPS_HW_WINDOW_SIZE_BYTES) {\n+\t\t\tret = mm81x_dm_write(yaps-\u003emors,\n+\t\t\t\t\t     yaps-\u003eaux_data-\u003eyds_addr,\n+\t\t\t\t\t     to_chip_buffer_aligned,\n+\t\t\t\t\t     batch_txn_len);\n+\n+\t\t\tbatch_txn_len = 0;\n+\t\t\tif (ret)\n+\t\t\t\tgoto exit;\n+\t\t\twrite_buf = to_chip_buffer_aligned;\n+\t\t\t*num_pkts_sent += pkts_pending;\n+\t\t\tpkts_pending = 0;\n+\t\t}\n+\n+\t\tif ((i + 1) == num_pkts) {\n+\t\t\t/* The last packet in the queue has IRQ set */\n+\t\t\tdelim_irq = true;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * Since this is not the last packet, we can check for\n+\t\t\t * the next one. In case of errors in the next packet\n+\t\t\t * set the IRQ\n+\t\t\t */\n+\t\t\tret = mm81x_write_pkts_h_err_check(yaps, \u0026pkts[i + 1]);\n+\t\t\tif (ret)\n+\t\t\t\tdelim_irq = true;\n+\t\t}\n+\n+\t\t/* Build stream header*/\n+\t\tdelim = mm81x_write_pkts_h_build_delim(\n+\t\t\tyaps, pkt_size, pkts[i].tc_queue, delim_irq);\n+\t\t*((__le32 *)write_buf) = cpu_to_le32(delim);\n+\t\tmemcpy(write_buf + sizeof(delim), pkts[i].skb-\u003edata,\n+\t\t       pkts[i].skb-\u003elen);\n+\n+\t\twrite_buf += tx_len;\n+\t\tbatch_txn_len += tx_len;\n+\t\tpkts_pending++;\n+\n+\t\tif (ret)\n+\t\t\tgoto exit;\n+\t}\n+\n+exit:\n+\tif (batch_txn_len \u003e 0) {\n+\t\tret = mm81x_dm_write(yaps-\u003emors, yaps-\u003eaux_data-\u003eyds_addr,\n+\t\t\t\t     to_chip_buffer_aligned, batch_txn_len);\n+\t\t*num_pkts_sent += pkts_pending;\n+\t}\n+\n+\tmm81x_yaps_hw_unlock(yaps);\n+\treturn ret;\n+}\n+\n+static bool mm81x_read_pkts_h_is_valid_delim(u32 delim)\n+{\n+\tu8 calc_crc = mm81x_yaps_hw_crc(delim);\n+\tint pkt_size = YAPS_DELIM_GET_PHANDLE_SIZE(delim);\n+\tint padding = YAPS_DELIM_GET_PADDING(delim);\n+\n+\tif (calc_crc != YAPS_DELIM_GET_CRC(delim))\n+\t\treturn false;\n+\n+\tif (pkt_size == 0)\n+\t\treturn false;\n+\n+\tif ((pkt_size + padding) \u003e YAPS_MAX_PKT_SIZE_BYTES)\n+\t\treturn false;\n+\n+\t/* Pkt length + padding should not require more padding */\n+\tif (YAPS_CALC_PADDING(pkt_size) != padding)\n+\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+static int mm81x_read_pkts_h_bytes_remaining(struct mm81x_yaps *yaps)\n+{\n+\tu32 bytes_in_queue = yaps-\u003eaux_data-\u003estatus_regs.fc_rx_bytes_in_queue;\n+\tu32 delim_overhead =\n+\t\tyaps-\u003eaux_data-\u003estatus_regs.fc_num_pkts * sizeof(u32);\n+\tu32 reserved_bytes = yaps-\u003eaux_data-\u003estatus_regs.fc_num_pkts *\n+\t\t\t     yaps-\u003eaux_data-\u003ereserved_yaps_page_size;\n+\n+\tif (WARN_ON(bytes_in_queue \u003e INT_MAX) ||\n+\t    WARN_ON(delim_overhead \u003e INT_MAX) ||\n+\t    WARN_ON(reserved_bytes \u003e INT_MAX))\n+\t\treturn -EIO;\n+\n+\treturn (int)bytes_in_queue;\n+}\n+\n+static int mm81x_yaps_hw_read_pkts(struct mm81x_yaps *yaps,\n+\t\t\t\t   struct mm81x_yaps_pkt *pkts,\n+\t\t\t\t   int num_pkts_max, int *num_pkts_received)\n+{\n+\tint ret;\n+\tint i = 0;\n+\tchar *from_chip_buffer_aligned =\n+\t\tPTR_ALIGN(yaps-\u003eaux_data-\u003efrom_chip_buffer,\n+\t\t\t  mm81x_bus_get_alignment(yaps-\u003emors));\n+\tchar *read_ptr = from_chip_buffer_aligned;\n+\tint bytes_remaining = mm81x_read_pkts_h_bytes_remaining(yaps);\n+\tbool again = false;\n+\n+\t*num_pkts_received = 0;\n+\n+\tif (num_pkts_max == 0 || bytes_remaining == 0)\n+\t\treturn 0;\n+\tif (bytes_remaining \u003c 0)\n+\t\treturn bytes_remaining;\n+\n+\tif (bytes_remaining \u003e YAPS_HW_WINDOW_SIZE_BYTES) {\n+\t\tbytes_remaining = YAPS_HW_WINDOW_SIZE_BYTES;\n+\t\tagain = true;\n+\t}\n+\n+\t/*\n+\t * This is more coarse-grained than it needs to be - once the data\n+\t * is read into a local buffer the lock can be released, however\n+\t * access to from_chip_buffer will need to be protected with its\n+\t * own lock\n+\t */\n+\tret = mm81x_yaps_hw_lock(yaps);\n+\tif (ret) {\n+\t\tdev_dbg(yaps-\u003emors-\u003edev, \"yaps lock failed %d\", ret);\n+\t\treturn ret;\n+\t}\n+\n+\t/* Read all available packets to the buffer */\n+\tret = mm81x_dm_read(yaps-\u003emors, yaps-\u003eaux_data-\u003eysl_addr,\n+\t\t\t    from_chip_buffer_aligned, bytes_remaining);\n+\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\t/* Split serialised packets from buffer */\n+\twhile (i \u003c num_pkts_max \u0026\u0026 bytes_remaining \u003e 0) {\n+\t\tu32 delim;\n+\t\tint total_len;\n+\t\tint pkt_size;\n+\n+\t\tdelim = le32_to_cpu(*((__le32 *)read_ptr));\n+\t\tread_ptr += sizeof(delim);\n+\t\tbytes_remaining -= sizeof(delim);\n+\n+\t\t/* End of stream */\n+\t\tif (!delim)\n+\t\t\tbreak;\n+\n+\t\tif (!mm81x_read_pkts_h_is_valid_delim(delim)) {\n+\t\t\t/*\n+\t\t\t * This will start a hunt for a valid delimiter. Given\n+\t\t\t * the CRC is only 7 bit it's possible to find an\n+\t\t\t * invalid block with a valid delimiter, leading to\n+\t\t\t * desynchronisation.\n+\t\t\t */\n+\t\t\tdev_warn(yaps-\u003emors-\u003edev, \"yaps invalid delim\");\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/* Total length in chip */\n+\t\tpkt_size = YAPS_DELIM_GET_PKT_SIZE(delim);\n+\t\ttotal_len = pkt_size + YAPS_DELIM_GET_PADDING(delim);\n+\n+\t\tif (pkts[i].skb)\n+\t\t\tdev_err(yaps-\u003emors-\u003edev, \"yaps packet leak\");\n+\n+\t\t/* SKB doesn't want padding */\n+\t\tpkts[i].skb = dev_alloc_skb(pkt_size);\n+\t\tif (!pkts[i].skb) {\n+\t\t\tret = -ENOMEM;\n+\t\t\tdev_err(yaps-\u003emors-\u003edev, \"yaps no mem for skb\");\n+\t\t\tgoto exit;\n+\t\t}\n+\t\tskb_put(pkts[i].skb, pkt_size);\n+\n+\t\tif (total_len \u003c= bytes_remaining) {\n+\t\t\tmemcpy(pkts[i].skb-\u003edata, read_ptr, pkt_size);\n+\t\t\tread_ptr += total_len;\n+\t\t\tbytes_remaining -= total_len;\n+\t\t} else {\n+\t\t\tconst int read_overhang_len =\n+\t\t\t\ttotal_len - bytes_remaining;\n+\t\t\tconst int pkt_overhang_len = pkt_size - bytes_remaining;\n+\n+\t\t\tmemcpy(pkts[i].skb-\u003edata, read_ptr, bytes_remaining);\n+\t\t\tread_ptr = from_chip_buffer_aligned;\n+\n+\t\t\tret = mm81x_dm_read(\n+\t\t\t\tyaps-\u003emors,\n+\t\t\t\t/* Offset by 4 to avoid retry logic */\n+\t\t\t\tyaps-\u003eaux_data-\u003eysl_addr + 4, read_ptr,\n+\t\t\t\tread_overhang_len);\n+\n+\t\t\tif (ret)\n+\t\t\t\tgoto exit;\n+\n+\t\t\tmemcpy(pkts[i].skb-\u003edata + bytes_remaining, read_ptr,\n+\t\t\t       pkt_overhang_len);\n+\t\t\tread_ptr += read_overhang_len;\n+\t\t\tbytes_remaining = 0;\n+\t\t}\n+\n+\t\t*num_pkts_received += 1;\n+\t\ti++;\n+\t}\n+\n+\tif (again)\n+\t\tret = -EAGAIN;\n+\n+exit:\n+\tmm81x_yaps_hw_unlock(yaps);\n+\treturn ret;\n+}\n+\n+static int mm81x_yaps_hw_update_status(struct mm81x_yaps *yaps)\n+{\n+\tint ret;\n+\tint tc_total_pkt_count;\n+\tunsigned long reg_read_timeout;\n+\tstruct mm81x_yaps_status_regs *r = \u0026yaps-\u003eaux_data-\u003estatus_regs;\n+\tstruct mm81x_yaps_hw_status_regs *hw_r = \u0026yaps-\u003eaux_data-\u003ehw_status_regs;\n+\n+\tret = mm81x_yaps_hw_lock(yaps);\n+\tif (ret) {\n+\t\tdev_dbg(yaps-\u003emors-\u003edev, \"yaps lock failed %d\", ret);\n+\t\treturn ret;\n+\t}\n+\n+\treg_read_timeout = jiffies + msecs_to_jiffies(100);\n+\tdo {\n+\t\tif (time_after(jiffies, reg_read_timeout)) {\n+\t\t\tdev_err(yaps-\u003emors-\u003edev,\n+\t\t\t\t\"timed out reading status registers: %d\", ret);\n+\t\t\tret = -ETIMEDOUT;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tret = mm81x_dm_read(yaps-\u003emors,\n+\t\t\t\t    yaps-\u003eaux_data-\u003estatus_regs_addr,\n+\t\t\t\t    (u8 *)hw_r, sizeof(*hw_r));\n+\t} while (!ret \u0026\u0026 le32_to_cpu(hw_r-\u003elock));\n+\n+\tif (ret) {\n+\t\tif (ret != -ENODEV) {\n+\t\t\tdev_err(yaps-\u003emors-\u003edev,\n+\t\t\t\t\"error reading yaps status registers: %d\", ret);\n+\t\t}\n+\t\tgoto exit_unlock;\n+\t}\n+\n+\tr-\u003etc_tx_pool_num_pages = le32_to_cpu(hw_r-\u003etc_tx_pool_num_pages);\n+\tr-\u003etc_cmd_pool_num_pages = le32_to_cpu(hw_r-\u003etc_cmd_pool_num_pages);\n+\tr-\u003etc_beacon_pool_num_pages = le32_to_cpu(hw_r-\u003etc_beacon_pool_num_pages);\n+\tr-\u003etc_mgmt_pool_num_pages = le32_to_cpu(hw_r-\u003etc_mgmt_pool_num_pages);\n+\tr-\u003efc_rx_pool_num_pages = le32_to_cpu(hw_r-\u003efc_rx_pool_num_pages);\n+\tr-\u003efc_resp_pool_num_pages = le32_to_cpu(hw_r-\u003efc_resp_pool_num_pages);\n+\tr-\u003efc_tx_sts_pool_num_pages = le32_to_cpu(hw_r-\u003efc_tx_sts_pool_num_pages);\n+\tr-\u003efc_aux_pool_num_pages = le32_to_cpu(hw_r-\u003efc_aux_pool_num_pages);\n+\tr-\u003etc_tx_num_pkts = le32_to_cpu(hw_r-\u003etc_tx_num_pkts);\n+\tr-\u003etc_cmd_num_pkts = le32_to_cpu(hw_r-\u003etc_cmd_num_pkts);\n+\tr-\u003etc_beacon_num_pkts = le32_to_cpu(hw_r-\u003etc_beacon_num_pkts);\n+\tr-\u003etc_mgmt_num_pkts = le32_to_cpu(hw_r-\u003etc_mgmt_num_pkts);\n+\tr-\u003efc_num_pkts = le32_to_cpu(hw_r-\u003efc_num_pkts);\n+\tr-\u003efc_done_num_pkts = le32_to_cpu(hw_r-\u003efc_done_num_pkts);\n+\tr-\u003efc_rx_bytes_in_queue = le32_to_cpu(hw_r-\u003efc_rx_bytes_in_queue);\n+\tr-\u003etc_delim_crc_fail_detected = le32_to_cpu(hw_r-\u003etc_delim_crc_fail_detected);\n+\tr-\u003elock = le32_to_cpu(hw_r-\u003elock);\n+\tr-\u003efc_host_ysl_status = le32_to_cpu(hw_r-\u003efc_host_ysl_status);\n+\n+\ttc_total_pkt_count = r-\u003etc_tx_num_pkts + r-\u003etc_cmd_num_pkts +\n+\t\t\t     r-\u003etc_beacon_num_pkts + r-\u003etc_mgmt_num_pkts;\n+\n+\tif (r-\u003etc_delim_crc_fail_detected) {\n+\t\t/*\n+\t\t * Host and chip have become desynchronised. This can happen if\n+\t\t * the chip crashes during a YAPS transaction. We cannot\n+\t\t * recover from this.\n+\t\t */\n+\t\tdev_err(yaps-\u003emors-\u003edev,\n+\t\t\t\"to-chip yaps delimiter CRC fail, pkt_count=%d\",\n+\t\t\ttc_total_pkt_count);\n+\t\tret = -EIO;\n+\t}\n+\n+\tif (mm81x_read_pkts_h_bytes_remaining(yaps))\n+\t\tset_bit(MM81X_HIF_EVT_RX_PEND, \u0026yaps-\u003emors-\u003ehif.event_flags);\n+\n+exit_unlock:\n+\tmm81x_yaps_hw_unlock(yaps);\n+\treturn ret;\n+}\n+\n+static const struct mm81x_yaps_ops mm81x_yaps_hw_ops = {\n+\t.write_pkts = mm81x_yaps_hw_write_pkts,\n+\t.read_pkts = mm81x_yaps_hw_read_pkts,\n+\t.update_status = mm81x_yaps_hw_update_status,\n+};\n+\n+int mm81x_yaps_hw_init(struct mm81x *mors)\n+{\n+\tint ret = 0;\n+\tstruct mm81x_yaps *yaps = NULL;\n+\tint aux_data_len = sizeof(struct mm81x_yaps_hw_aux_data);\n+\tint alignment = mm81x_bus_get_alignment(mors);\n+\n+\tyaps = \u0026mors-\u003ehif.u.yaps;\n+\tyaps-\u003eaux_data = kzalloc(aux_data_len, GFP_KERNEL);\n+\tif (!yaps-\u003eaux_data) {\n+\t\tret = -ENOMEM;\n+\t\tgoto err_exit;\n+\t}\n+\n+\tyaps-\u003eaux_data-\u003eto_chip_buffer =\n+\t\tkzalloc(YAPS_HW_WINDOW_SIZE_BYTES + alignment - 1, GFP_KERNEL);\n+\tif (!yaps-\u003eaux_data-\u003eto_chip_buffer) {\n+\t\tret = -ENOMEM;\n+\t\tgoto err_exit;\n+\t}\n+\n+\tyaps-\u003eaux_data-\u003efrom_chip_buffer =\n+\t\tkzalloc(YAPS_HW_WINDOW_SIZE_BYTES + alignment - 1, GFP_KERNEL);\n+\tif (!yaps-\u003eaux_data-\u003efrom_chip_buffer) {\n+\t\tret = -ENOMEM;\n+\t\tgoto err_exit;\n+\t}\n+\n+\tif (!IS_ALIGNED((uintptr_t)\u0026yaps-\u003eaux_data-\u003estatus_regs, alignment)) {\n+\t\tdev_warn(mors-\u003edev,\n+\t\t\t \"Status registers are not aligned to %d bytes\",\n+\t\t\t alignment);\n+\t}\n+\n+\tyaps-\u003eops = \u0026mm81x_yaps_hw_ops;\n+\treturn ret;\n+\n+err_exit:\n+\tmm81x_yaps_hw_finish(mors);\n+\treturn ret;\n+}\n+\n+void mm81x_yaps_hw_finish(struct mm81x *mors)\n+{\n+\tstruct mm81x_yaps *yaps;\n+\n+\tyaps = \u0026mors-\u003ehif.u.yaps;\n+\tif (yaps-\u003eaux_data) {\n+\t\tkfree(yaps-\u003eaux_data-\u003efrom_chip_buffer);\n+\t\tyaps-\u003eaux_data-\u003efrom_chip_buffer = NULL;\n+\t\tkfree(yaps-\u003eaux_data-\u003eto_chip_buffer);\n+\t\tyaps-\u003eaux_data-\u003eto_chip_buffer = NULL;\n+\t\tkfree(yaps-\u003eaux_data);\n+\t\tyaps-\u003eaux_data = NULL;\n+\t}\n+}\ndiff --git a/drivers/net/wireless/morsemicro/mm81x/yaps_hw.h b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.h\nnew file mode 100644\nindex 00000000000000..89e15375aabc1b\n--- /dev/null\n+++ b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.h\n@@ -0,0 +1,52 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (c) 2017-2026 Morse Micro\n+ */\n+\n+#ifndef _MM81X_YAPS_HW_H_\n+#define _MM81X_YAPS_HW_H_\n+\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/crc7.h\u003e\n+\n+#define MM81X_INT_YAPS_FC_PKT_WAITING_IRQN 0\n+#define MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN 1\n+\n+struct mm81x_yaps_hw_table {\n+\t/* NOTE: We need these padding bytes for yaps to work */\n+\tu8 padding[4];\n+\t__le32 ysl_addr;\n+\t__le32 yds_addr;\n+\t__le32 status_regs_addr;\n+\n+\t/* Alloc pool sizes */\n+\t__le16 tc_tx_pool_size;\n+\t__le16 fc_rx_pool_size;\n+\tu8 tc_cmd_pool_size;\n+\tu8 tc_beacon_pool_size;\n+\tu8 tc_mgmt_pool_size;\n+\tu8 fc_resp_pool_size;\n+\tu8 fc_tx_sts_pool_size;\n+\tu8 fc_aux_pool_size;\n+\n+\t/* To chip/from chip queue sizes */\n+\tu8 tc_tx_q_size;\n+\tu8 tc_cmd_q_size;\n+\tu8 tc_beacon_q_size;\n+\tu8 tc_mgmt_q_size;\n+\tu8 fc_q_size;\n+\tu8 fc_done_q_size;\n+\n+\t__le16 yaps_reserved_page_size;\n+\t__le16 reserved_unused;\n+} __packed;\n+\n+struct mm81x;\n+\n+void mm81x_yaps_hw_enable_irqs(struct mm81x *mors, bool enable);\n+int mm81x_yaps_hw_init(struct mm81x *mors);\n+void mm81x_yaps_hw_finish(struct mm81x *mors);\n+void mm81x_yaps_hw_read_table(struct mm81x *mors,\n+\t\t\t      struct mm81x_yaps_hw_table *tbl_ptr);\n+\n+#endif /* !_MM81X_YAPS_HW_H_ */\ndiff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h\nindex d40484451e9a28..084ad45aa2d8dd 100644\n--- a/include/linux/ieee80211.h\n+++ b/include/linux/ieee80211.h\n@@ -2616,6 +2616,7 @@ static inline int ieee80211_get_tdls_action(struct sk_buff *skb)\n /* convert frequencies */\n #define MHZ_TO_KHZ(freq) ((freq) * 1000)\n #define KHZ_TO_MHZ(freq) ((freq) / 1000)\n+#define KHZ_TO_HZ(x) ((x) * 1000)\n #define PR_KHZ(f) KHZ_TO_MHZ(f), f % 1000\n #define KHZ_F \"%d.%03d\"\n \ndiff --git a/include/linux/mmc/sdio_ids.h b/include/linux/mmc/sdio_ids.h\nindex 0685dd717e855c..bbffad9ae88e86 100644\n--- a/include/linux/mmc/sdio_ids.h\n+++ b/include/linux/mmc/sdio_ids.h\n@@ -117,6 +117,9 @@\n #define SDIO_VENDOR_ID_MICROCHIP_WILC\t\t0x0296\n #define SDIO_DEVICE_ID_MICROCHIP_WILC1000\t0x5347\n \n+#define SDIO_VENDOR_ID_MORSEMICRO\t\t0x325b\n+#define SDIO_DEVICE_ID_MORSEMICRO_MM8108\t0x0809\n+\n #define SDIO_VENDOR_ID_NXP\t\t\t0x0471\n #define SDIO_DEVICE_ID_NXP_IW61X\t\t0x0205\n \ndiff --git a/include/net/cfg80211.h b/include/net/cfg80211.h\nindex 8188ad200de539..a5d54d70d72b12 100644\n--- a/include/net/cfg80211.h\n+++ b/include/net/cfg80211.h\n@@ -1236,6 +1236,26 @@ ieee80211_chandef_max_power(struct cfg80211_chan_def *chandef)\n \treturn chandef-\u003echan-\u003emax_power;\n }\n \n+/**\n+ * cfg80211_chandef_s1g_pri_width - return S1G primary width in MHz\n+ *\n+ * An S1G interface may have a primary channel width of either 1\n+ * or 2MHz depending on whether chandef::s1g_primary_2mhz is set.\n+ *\n+ * Note: There is _always_ a 1MHz primary subchannel, regardless\n+ * of the primary width. So chandef::chan always points to this\n+ * 1MHz primary channel.\n+ *\n+ * @chandef: the chandef to use\n+ *\n+ * Returns: width in MHz of the S1G primary channel in use\n+ */\n+static inline int\n+cfg80211_chandef_s1g_pri_width(struct cfg80211_chan_def *chandef)\n+{\n+\treturn chandef-\u003es1g_primary_2mhz ? 2 : 1;\n+}\n+\n /**\n  * cfg80211_any_usable_channels - check for usable channels\n  * @wiphy: the wiphy to check for\n"
}

2/1 2026/06/26 06:48 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "WLAN_VENDOR_MORSEMICRO",
    "MM81X",
    "MM81X_USB",
    "MM81X_SDIO"
  ],
  "FocusSymbols": [
    "mm81x_usb_probe",
    "mm81x_usb_int_handler",
    "mm81x_usb_cmd_callback",
    "mm81x_usb_mem_rw_callback",
    "mm81x_sdio_probe",
    "mm81x_mac_event_recv",
    "mm81x_mac_rx_skb",
    "mm81x_yaps_work",
    "mm81x_yaps_hw_read_pkts",
    "mm81x_cmd_resp_process"
  ],
  "Reasoning": "The patch introduces a new mac80211 wireless driver for Morse Micro MM81x devices. It includes both USB and SDIO interfaces. The USB interface can be easily fuzzed using virtual USB host controllers (vhci) in virtualized environments. Therefore, this driver is reachable and should be fuzzed.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 4ab6997f840e35fbcb89090f4ea0c42c8fae430e
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Jun 26 06:48:54 2026 +0000

    syz-cluster: applied patch under review

diff --git a/MAINTAINERS b/MAINTAINERS
index eb8cdcc76324fe..d5639668f3a928 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18012,6 +18012,14 @@ F:	drivers/regulator/mpq7920.c
 F:	drivers/regulator/mpq7920.h
 F:	include/linux/mfd/mp2629.h
 
+MORSE MICRO MM81X WIRELESS DRIVER
+M:	Lachlan Hodges <lachlan.hodges@morsemicro.com>
+M:	Dan Callaghan <dan.callaghan@morsemicro.com>
+R:	Arien Judge <arien.judge@morsemicro.com>
+L:	linux-wireless@vger.kernel.org
+S:	Supported
+F:	drivers/net/wireless/morsemicro/mm81x/
+
 MOST(R) TECHNOLOGY DRIVER
 M:	Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
 M:	Christian Gromm <christian.gromm@microchip.com>
diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig
index c6599594dc995c..baddadf9ec3c24 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/morsemicro/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 e1c4141c600443..d74f817b37de1c 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_MORSEMICRO) += morsemicro/
 obj-$(CONFIG_WLAN_VENDOR_PURELIFI) += purelifi/
 obj-$(CONFIG_WLAN_VENDOR_QUANTENNA) += quantenna/
 obj-$(CONFIG_WLAN_VENDOR_RALINK) += ralink/
diff --git a/drivers/net/wireless/morsemicro/Kconfig b/drivers/net/wireless/morsemicro/Kconfig
new file mode 100644
index 00000000000000..cb0653c77d87cc
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config WLAN_VENDOR_MORSEMICRO
+	bool "Morse Micro 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_MORSEMICRO
+source "drivers/net/wireless/morsemicro/mm81x/Kconfig"
+endif # WLAN_VENDOR_MORSEMICRO
diff --git a/drivers/net/wireless/morsemicro/Makefile b/drivers/net/wireless/morsemicro/Makefile
new file mode 100644
index 00000000000000..5b2670f7d1711f
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_MM81X) += mm81x/
diff --git a/drivers/net/wireless/morsemicro/mm81x/Kconfig b/drivers/net/wireless/morsemicro/mm81x/Kconfig
new file mode 100644
index 00000000000000..33cdcc0df4de2e
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/Kconfig
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config MM81X
+	tristate "Morse Micro MM81x wireless devices"
+	depends on MAC80211
+	select FW_LOADER
+	select CRC7
+	help
+	  This module adds support for wireless devices based
+	  on Morse Micro MM81xx chipsets.
+
+config MM81X_USB
+	tristate "Morse Micro MM81x USB support"
+	depends on MM81X && USB
+	help
+	  This module adds support for the USB interface of
+	  devices using the Morse Micro MM81x chipset.
+
+config MM81X_SDIO
+	tristate "Morse Micro MM81x SDIO support"
+	depends on MM81X && MMC
+	help
+	  This module adds support for the SDIO interface of
+	  devices using the Morse Micro MM81x chipset.
diff --git a/drivers/net/wireless/morsemicro/mm81x/Makefile b/drivers/net/wireless/morsemicro/mm81x/Makefile
new file mode 100644
index 00000000000000..0d494fda141257
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/Makefile
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_MM81X) += mm81x_core.o
+
+mm81x_core-y += core.o
+mm81x_core-y += mac.o
+mm81x_core-y += hw.o
+mm81x_core-y += fw.o
+mm81x_core-y += command.o
+mm81x_core-y += ps.o
+mm81x_core-y += skbq.o
+mm81x_core-y += yaps_hw.o
+mm81x_core-y += yaps.o
+mm81x_core-y += rc.o
+mm81x_core-y += mmrc.o
+
+obj-$(CONFIG_MM81X_USB) += mm81x_usb.o
+mm81x_usb-y += usb.o
+
+obj-$(CONFIG_MM81X_SDIO) += mm81x_sdio.o
+mm81x_sdio-y += sdio.o
diff --git a/drivers/net/wireless/morsemicro/mm81x/bus.h b/drivers/net/wireless/morsemicro/mm81x/bus.h
new file mode 100644
index 00000000000000..d2ccabc037fb9b
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/bus.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_BUS_H_
+#define _MM81X_BUS_H_
+
+#include <linux/skbuff.h>
+#include "core.h"
+
+enum mm81x_bus_type {
+	MM81X_BUS_TYPE_USB,
+	MM81X_BUS_TYPE_SDIO,
+};
+
+struct mm81x_bus_ops {
+	int (*dm_read)(struct mm81x *mors, u32 addr, u8 *data, int len);
+	int (*dm_write)(struct mm81x *mors, u32 addr, const u8 *data, int len);
+	int (*reg32_read)(struct mm81x *mors, u32 addr, u32 *data);
+	int (*reg32_write)(struct mm81x *mors, u32 addr, u32 data);
+	int (*digital_reset)(struct mm81x *mors);
+	void (*set_bus_enable)(struct mm81x *mors, bool enable);
+	void (*config_burst_mode)(struct mm81x *mors, bool enable_burst);
+	void (*claim)(struct mm81x *mors);
+	void (*set_irq)(struct mm81x *mors, bool enable);
+	void (*release)(struct mm81x *mors);
+	unsigned int bulk_alignment;
+};
+
+/*
+ * Default TX alignment for buses which don't care. mac80211 will give us
+ * SKBs aligned to the 2 byte boundary, so 2 is effectively a noop.
+ */
+#define MM81X_BUS_DEFAULT_BULK_ALIGNMENT (2)
+
+/* mm81x_dm_read - len must be rounded up to the nearest 4-byte boundary */
+static inline int mm81x_dm_read(struct mm81x *mors, u32 addr, u8 *data, int len)
+{
+	return mors->bus_ops->dm_read(mors, addr, data, len);
+}
+
+static inline int mm81x_dm_write(struct mm81x *mors, u32 addr, const u8 *data,
+				 int len)
+{
+	return mors->bus_ops->dm_write(mors, addr, data, len);
+}
+
+static inline int mm81x_reg32_read(struct mm81x *mors, u32 addr, u32 *data)
+{
+	return mors->bus_ops->reg32_read(mors, addr, data);
+}
+
+static inline int mm81x_reg32_write(struct mm81x *mors, u32 addr, u32 data)
+{
+	return mors->bus_ops->reg32_write(mors, addr, data);
+}
+
+static inline int mm81x_bus_digital_reset(struct mm81x *mors)
+{
+	if (mors->bus_ops->digital_reset)
+		return mors->bus_ops->digital_reset(mors);
+
+	return 0;
+}
+
+static inline void mm81x_set_bus_enable(struct mm81x *mors, bool enable)
+{
+	mors->bus_ops->set_bus_enable(mors, enable);
+}
+
+static inline void mm81x_bus_config_burst_mode(struct mm81x *mors,
+					       bool enable_burst)
+{
+	if (mors->bus_ops->config_burst_mode)
+		mors->bus_ops->config_burst_mode(mors, enable_burst);
+}
+
+static inline void mm81x_claim_bus(struct mm81x *mors)
+{
+	mors->bus_ops->claim(mors);
+}
+
+static inline void mm81x_bus_set_irq(struct mm81x *mors, bool enable)
+{
+	mors->bus_ops->set_irq(mors, enable);
+}
+
+static inline void mm81x_release_bus(struct mm81x *mors)
+{
+	mors->bus_ops->release(mors);
+}
+
+static inline unsigned int mm81x_bus_get_alignment(struct mm81x *mors)
+{
+	return mors->bus_ops->bulk_alignment;
+}
+
+#endif /* !_MM81X_BUS_H_ */
diff --git a/drivers/net/wireless/morsemicro/mm81x/command.c b/drivers/net/wireless/morsemicro/mm81x/command.c
new file mode 100644
index 00000000000000..afb7ee9bb236f1
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/command.c
@@ -0,0 +1,563 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#include <linux/types.h>
+#include <linux/atomic.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include "command.h"
+#include "mac.h"
+#include "ps.h"
+#include "hif.h"
+
+#define MM_MAX_COMMAND_RETRY 2
+#define HOST_CMD_DEFAULT_TIMEOUT_MS 600
+#define HOST_CMD_POWERSAVE_TIMEOUT_MS 2000
+
+#define INIT_CMD_HDR(_req, _cmd, _vif_id)				\
+	((struct host_cmd_header){					\
+		.message_id = cpu_to_le16(_cmd),			\
+		.len = cpu_to_le16(sizeof(_req) - sizeof((_req).hdr)),	\
+		.vif_id = cpu_to_le16(_vif_id),				\
+	})
+
+struct host_cmd_resp_cb {
+	int ret;
+	u32 length;
+	struct host_cmd_resp *dest_resp;
+};
+
+static int mm81x_cmd_tx(struct mm81x *mors, struct host_cmd_resp *resp,
+			struct host_cmd_req *req, u32 length, u32 timeout)
+{
+	int cmd_len;
+	int ret = 0;
+	u16 host_id;
+	int retry = 0;
+	unsigned long wait_ret = 0;
+	struct sk_buff *skb;
+	struct mm81x_skbq *cmd_q = mm81x_hif_get_tx_cmd_queue(mors);
+	struct host_cmd_resp_cb *resp_cb;
+	DECLARE_COMPLETION_ONSTACK(cmd_comp);
+
+	BUILD_BUG_ON(sizeof(struct host_cmd_resp_cb) >
+		     IEEE80211_TX_INFO_DRIVER_DATA_SIZE);
+
+	cmd_len = sizeof(*req) + le16_to_cpu(req->hdr.len);
+	req->hdr.flags = cpu_to_le16(HOST_CMD_TYPE_REQ);
+
+	mutex_lock(&mors->cmd_wait);
+	mors->cmd_seq++;
+	if (mors->cmd_seq > HOST_CMD_HOST_ID_SEQ_MAX)
+		mors->cmd_seq = 1;
+	host_id = mors->cmd_seq << HOST_CMD_HOST_ID_SEQ_SHIFT;
+
+	mm81x_ps_disable(mors);
+
+	do {
+		req->hdr.host_id = cpu_to_le16(host_id | retry);
+
+		skb = mm81x_skbq_alloc_skb(cmd_q, cmd_len);
+		if (!skb) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		memcpy(skb->data, req, cmd_len);
+		resp_cb = (struct host_cmd_resp_cb *)IEEE80211_SKB_CB(skb)
+				  ->driver_data;
+		resp_cb->length = length;
+		resp_cb->dest_resp = resp;
+
+		dev_dbg(mors->dev, "CMD 0x%04x:%04x",
+			le16_to_cpu(req->hdr.message_id),
+			le16_to_cpu(req->hdr.host_id));
+
+		mutex_lock(&mors->cmd_lock);
+		mors->cmd_comp = &cmd_comp;
+		if (retry > 0)
+			reinit_completion(&cmd_comp);
+		timeout = timeout ? timeout : HOST_CMD_DEFAULT_TIMEOUT_MS;
+		ret = mm81x_skbq_skb_tx(cmd_q, &skb, NULL,
+					MM81X_SKB_CHAN_COMMAND);
+		mutex_unlock(&mors->cmd_lock);
+
+		if (ret) {
+			dev_err(mors->dev, "mm81x_skbq_tx fail: %d", ret);
+			break;
+		}
+
+		wait_ret = wait_for_completion_timeout(
+			&cmd_comp, msecs_to_jiffies(timeout));
+		mutex_lock(&mors->cmd_lock);
+		mors->cmd_comp = NULL;
+
+		if (!wait_ret) {
+			dev_err(mors->dev,
+				"Try:%d Command %04x:%04x timeout after %u ms",
+				retry, le16_to_cpu(req->hdr.message_id),
+				le16_to_cpu(req->hdr.host_id), timeout);
+			ret = -ETIMEDOUT;
+		} else {
+			ret = (length && resp) ? le32_to_cpu(resp->status) :
+						 resp_cb->ret;
+			if (ret > 0 || ret < -MAX_ERRNO)
+				ret = -EIO;
+
+			dev_dbg(mors->dev, "Command 0x%04x:%04x status 0x%08x",
+				le16_to_cpu(req->hdr.message_id),
+				le16_to_cpu(req->hdr.host_id), ret);
+			if (ret) {
+				dev_err(mors->dev,
+					"Command 0x%04x:%04x error %d",
+					le16_to_cpu(req->hdr.message_id),
+					le16_to_cpu(req->hdr.host_id), ret);
+			}
+		}
+		/* Free the command request */
+		spin_lock_bh(&cmd_q->lock);
+		mm81x_skbq_skb_finish(cmd_q, skb, NULL);
+		spin_unlock_bh(&cmd_q->lock);
+		mutex_unlock(&mors->cmd_lock);
+
+		retry++;
+	} while ((ret == -ETIMEDOUT) && retry < MM_MAX_COMMAND_RETRY);
+
+	mm81x_ps_enable(mors);
+	mutex_unlock(&mors->cmd_wait);
+
+	if (ret == -ETIMEDOUT) {
+		dev_err(mors->dev, "Command %02x:%02x timed out",
+			le16_to_cpu(req->hdr.message_id),
+			le16_to_cpu(req->hdr.host_id));
+	} else if (ret != 0) {
+		dev_err(mors->dev,
+			"Command %02x:%02x failed with rc %d (0x%x)\n",
+			le16_to_cpu(req->hdr.message_id),
+			le16_to_cpu(req->hdr.host_id), ret, ret);
+	}
+
+	return ret;
+}
+
+int mm81x_cmd_resp_process(struct mm81x *mors, struct sk_buff *skb)
+{
+	int length, ret = -ESRCH; /* No such process */
+	struct mm81x_skbq *cmd_q = mm81x_hif_get_tx_cmd_queue(mors);
+	struct host_cmd_resp *src_resp = (struct host_cmd_resp *)(skb->data);
+	struct sk_buff *cmd_skb = NULL;
+	struct host_cmd_resp_cb *resp_cb;
+	struct host_cmd_resp *dest_resp;
+	struct host_cmd_req *req;
+	u16 message_id = 0;
+	u16 host_id = 0;
+	u16 resp_message_id = le16_to_cpu(src_resp->hdr.message_id);
+	u16 resp_host_id = le16_to_cpu(src_resp->hdr.host_id);
+	bool is_late_response = false;
+
+	dev_dbg(mors->dev, "EVT 0x%04x:0x%04x", resp_message_id, resp_host_id);
+
+	if (!HOST_CMD_IS_RESP(src_resp)) {
+		ret = mm81x_mac_event_recv(mors, skb);
+		goto exit_free;
+	}
+
+	mutex_lock(&mors->cmd_lock);
+
+	cmd_skb = mm81x_skbq_tx_pending(cmd_q);
+	if (cmd_skb) {
+		mm81x_skbq_pull_hdr_post_tx(cmd_skb);
+		req = (struct host_cmd_req *)cmd_skb->data;
+		message_id = le16_to_cpu(req->hdr.message_id);
+		host_id = le16_to_cpu(req->hdr.host_id);
+	}
+
+	/*
+	 * If there is no pending command or the sequence ID does not match,
+	 * this is a late response for a timed out command which has been
+	 * cleaned up, so just free up the response. If a command was retried,
+	 * the response may be from the retry or from the original command
+	 * (late response) but not from both because the firmware will silently
+	 * drop a retry if it received the initial request. So a mismatched
+	 * retry counter is treated as a matched command and response.
+	 */
+	if (!cmd_skb || message_id != resp_message_id ||
+	    (host_id & HOST_CMD_HOST_ID_SEQ_MASK) !=
+		    (resp_host_id & HOST_CMD_HOST_ID_SEQ_MASK)) {
+		dev_err(mors->dev,
+			"Late response for timed out req 0x%04x:%04x have 0x%04x:%04x 0x%04x",
+			resp_message_id, resp_host_id, message_id, host_id,
+			mors->cmd_seq);
+		is_late_response = true;
+		goto exit;
+	}
+	if ((host_id & HOST_CMD_HOST_ID_RETRY_MASK) !=
+	    (resp_host_id & HOST_CMD_HOST_ID_RETRY_MASK))
+		dev_dbg(mors->dev,
+			"Command retry mismatch 0x%04x:%04x 0x%04x:%04x",
+			message_id, host_id, resp_message_id, resp_host_id);
+
+	resp_cb = (struct host_cmd_resp_cb *)IEEE80211_SKB_CB(cmd_skb)
+			  ->driver_data;
+	length = resp_cb->length;
+	dest_resp = resp_cb->dest_resp;
+	if (length >= sizeof(struct host_cmd_resp) && dest_resp) {
+		ret = 0;
+		length = min_t(int, length,
+			       le16_to_cpu(src_resp->hdr.len) +
+				       sizeof(struct host_cmd_header));
+		memcpy(dest_resp, src_resp, length);
+	} else {
+		ret = le32_to_cpu(src_resp->status);
+	}
+
+	resp_cb->ret = ret;
+
+exit:
+	if (cmd_skb && !is_late_response) {
+		/* Complete if not already timed out */
+		if (mors->cmd_comp)
+			complete(mors->cmd_comp);
+	}
+
+	mutex_unlock(&mors->cmd_lock);
+exit_free:
+	dev_kfree_skb(skb);
+	return 0;
+}
+
+int mm81x_cmd_sta_state(struct mm81x *mors, struct mm81x_vif *mors_vif, u16 aid,
+			struct ieee80211_sta *sta,
+			enum ieee80211_sta_state state)
+{
+	struct host_cmd_req_set_sta_state req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_STA_STATE,
+				    mors_vif->id),
+		.aid = cpu_to_le16(aid),
+		.state = cpu_to_le16(state),
+		.uapsd_queues = sta->uapsd_queues,
+	};
+
+	memcpy(req.sta_addr, sta->addr, sizeof(req.sta_addr));
+
+	return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
+}
+
+int mm81x_cmd_add_if(struct mm81x *mors, u16 *vif_id, const u8 *addr,
+		     enum nl80211_iftype type)
+{
+	int ret;
+	struct host_cmd_req_add_interface req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_ADD_INTERFACE, 0),
+	};
+	struct host_cmd_resp_add_interface resp;
+
+	switch (type) {
+	case NL80211_IFTYPE_STATION:
+		req.interface_type = cpu_to_le32(HOST_CMD_INTERFACE_TYPE_STA);
+		break;
+	case NL80211_IFTYPE_AP:
+		req.interface_type = cpu_to_le32(HOST_CMD_INTERFACE_TYPE_AP);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	memcpy(req.addr.octet, addr, sizeof(req.addr.octet));
+
+	ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
+			   (struct host_cmd_req *)&req, sizeof(resp), 0);
+	if (!ret)
+		*vif_id = le16_to_cpu(resp.hdr.vif_id);
+
+	return ret;
+}
+
+int mm81x_cmd_get_capabilities(struct mm81x *mors, u16 vif_id,
+			       struct mm81x_fw_caps *capabilities)
+{
+	int ret;
+	int i;
+	struct host_cmd_req_get_capabilities req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_CAPABILITIES, vif_id),
+	};
+	struct host_cmd_resp_get_capabilities rsp;
+
+	ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&rsp,
+			   (struct host_cmd_req *)&req, sizeof(rsp), 0);
+	if (ret)
+		return ret;
+
+	capabilities->ampdu_mss = rsp.capabilities.ampdu_mss;
+	capabilities->mm81x_mmss_offset = rsp.morse_mmss_offset;
+	capabilities->beamformee_sts_capability =
+		rsp.capabilities.beamformee_sts_capability;
+	capabilities->maximum_ampdu_length_exponent =
+		rsp.capabilities.maximum_ampdu_length_exponent;
+	capabilities->number_sounding_dimensions =
+		rsp.capabilities.number_sounding_dimensions;
+	for (i = 0; i < FW_CAPABILITIES_FLAGS_WIDTH; i++)
+		capabilities->flags[i] = le32_to_cpu(rsp.capabilities.flags[i]);
+
+	return ret;
+}
+
+int mm81x_cmd_get_max_txpower(struct mm81x *mors, s32 *out_power_mbm)
+{
+	int ret;
+	struct host_cmd_req_get_max_txpower req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_MAX_TXPOWER, 0),
+	};
+	struct host_cmd_resp_get_max_txpower resp;
+
+	ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
+			   (struct host_cmd_req *)&req, sizeof(resp), 0);
+	if (!ret)
+		*out_power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm));
+
+	return ret;
+}
+
+int mm81x_cmd_hw_scan(struct mm81x *mors, struct mm81x_hw_scan_params *params,
+		      bool store)
+{
+	int ret;
+	struct host_cmd_req_hw_scan *req;
+	size_t cmd_size;
+	u8 *buf;
+	u32 flags = 0;
+
+	cmd_size = mm81x_hw_scan_h_get_cmd_size(params);
+	cmd_size = ROUND_BYTES_TO_WORD(cmd_size);
+
+	req = kzalloc(cmd_size, GFP_KERNEL);
+	if (!req)
+		return -ENOMEM;
+
+	buf = req->variable;
+
+	if (store)
+		flags = HOST_CMD_HW_SCAN_FLAGS_STORE;
+	else if (params->operation == MM81X_HW_SCAN_OP_START)
+		flags |= HOST_CMD_HW_SCAN_FLAGS_START;
+	else if (params->operation == MM81X_HW_SCAN_OP_STOP)
+		flags |= HOST_CMD_HW_SCAN_FLAGS_ABORT;
+
+	flags |= HOST_CMD_HW_SCAN_FLAGS_1MHZ_PROBES;
+
+	if (params->operation == MM81X_HW_SCAN_OP_START) {
+		req->dwell_time_ms = cpu_to_le32(params->dwell_time_ms);
+		buf = mm81x_hw_scan_h_insert_tlvs(params, buf);
+	}
+
+	req->flags = cpu_to_le32(flags);
+	req->hdr = INIT_CMD_HDR((*req), HOST_CMD_ID_HW_SCAN, 0);
+	req->hdr.len = cpu_to_le16((u16)((buf - (u8 *)req) - sizeof(req->hdr)));
+	ret = mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)req, 0, 0);
+	kfree(req);
+
+	return ret;
+}
+
+int mm81x_cmd_set_txpower(struct mm81x *mors, s32 *out_power_mbm,
+			  int txpower_mbm)
+{
+	int ret;
+	struct host_cmd_req_set_txpower req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_TXPOWER, 0),
+		.power_qdbm = cpu_to_le32(MBM_TO_QDBM(txpower_mbm)),
+	};
+	struct host_cmd_resp_set_txpower resp;
+
+	ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
+			   (struct host_cmd_req *)&req, sizeof(resp), 0);
+	if (!ret)
+		*out_power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm));
+
+	return ret;
+}
+
+int mm81x_cmd_set_channel(struct mm81x *mors, u32 op_chan_freq_hz,
+			  u8 pri_1mhz_chan_idx, u8 op_bw_mhz, u8 pri_bw_mhz,
+			  s32 *power_mbm)
+{
+	int ret;
+	struct host_cmd_req_set_channel req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_CHANNEL, 0),
+		.op_chan_freq_hz = cpu_to_le32(op_chan_freq_hz),
+		.op_bw_mhz = op_bw_mhz,
+		.pri_bw_mhz = pri_bw_mhz,
+		.pri_1mhz_chan_idx = pri_1mhz_chan_idx,
+		.dot11_mode = HOST_CMD_DOT11_PROTO_MODE_AH,
+	};
+	struct host_cmd_resp_set_channel resp;
+
+	ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
+			   (struct host_cmd_req *)&req, sizeof(resp), 0);
+	if (!ret)
+		*power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm));
+
+	return ret;
+}
+
+int mm81x_cmd_disable_key(struct mm81x *mors, struct mm81x_vif *mors_vif,
+			  u16 aid, struct ieee80211_key_conf *key)
+{
+	struct host_cmd_req_disable_key req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_DISABLE_KEY, mors_vif->id),
+		.aid = cpu_to_le32(aid),
+		.key_idx = key->hw_key_idx,
+		.key_type =
+			cpu_to_le32((key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ?
+					    HOST_CMD_TEMPORAL_KEY_TYPE_PTK :
+					    HOST_CMD_TEMPORAL_KEY_TYPE_GTK),
+	};
+
+	return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
+}
+
+int mm81x_cmd_install_key(struct mm81x *mors, struct mm81x_vif *mors_vif,
+			  u16 aid, struct ieee80211_key_conf *key,
+			  enum host_cmd_key_cipher cipher,
+			  enum host_cmd_aes_key_len length)
+{
+	int ret;
+	struct host_cmd_req_install_key req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_INSTALL_KEY, mors_vif->id),
+		.pn = cpu_to_le64(atomic64_read(&key->tx_pn)),
+		.aid = cpu_to_le32(aid),
+		.cipher = cipher,
+		.key_length = length,
+		.key_idx = key->keyidx,
+		.key_type = (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ?
+				    HOST_CMD_TEMPORAL_KEY_TYPE_PTK :
+				    HOST_CMD_TEMPORAL_KEY_TYPE_GTK,
+	};
+	struct host_cmd_resp_install_key resp;
+
+	if (key->keylen > sizeof(req.key))
+		return -EINVAL;
+
+	memcpy(req.key, key->key, key->keylen);
+
+	ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
+			   (struct host_cmd_req *)&req, sizeof(resp), 0);
+	if (!ret) {
+		key->hw_key_idx = resp.key_idx;
+		dev_dbg(mors->dev, "Installed key @ hw index: %d",
+			resp.key_idx);
+	}
+
+	return ret;
+}
+
+int mm81x_cmd_cfg_multicast_filter(struct mm81x *mors,
+				   struct mm81x_vif *mors_vif)
+{
+	struct host_cmd_req_mcast_filter *req;
+	struct mcast_filter *filter = mors->mcast_filter;
+	u16 filter_list_len = sizeof(filter->addr_list[0]) * filter->count;
+	u16 alloc_len = filter_list_len + sizeof(*req);
+	int ret = 0;
+
+	req = kzalloc(alloc_len, GFP_KERNEL);
+	if (!req)
+		return -ENOMEM;
+
+	req->hdr = INIT_CMD_HDR((*req), HOST_CMD_ID_MCAST_FILTER, mors_vif->id);
+	req->hdr.len = cpu_to_le16(alloc_len - sizeof(req->hdr));
+	req->count = filter->count;
+	memcpy(req->hw_addr, filter->addr_list, filter_list_len);
+
+	ret = mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)req, 0, 0);
+	kfree(req);
+	return ret;
+}
+
+int mm81x_cmd_cfg_bss(struct mm81x *mors, u16 vif_id, u16 beacon_int,
+		      u16 dtim_period, u32 cssid)
+{
+	struct host_cmd_req_bss_config req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_BSS_CONFIG, vif_id),
+		.beacon_interval_tu = cpu_to_le16(beacon_int),
+		.cssid = cpu_to_le32(cssid),
+		.dtim_period = cpu_to_le16(dtim_period),
+	};
+
+	return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
+}
+
+int mm81x_cmd_config_beacon_timer(struct mm81x *mors, void *mm81x_vif,
+				  bool enabled)
+{
+	struct mm81x_vif *vif = mm81x_vif;
+	struct host_cmd_req_bss_beacon_config req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_BSS_BEACON_CONFIG,
+				    vif->id),
+		.enable = enabled,
+	};
+
+	return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
+}
+
+int mm81x_cmd_set_ps(struct mm81x *mors, bool enabled)
+{
+	struct host_cmd_req_config_ps req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_CONFIG_PS, 0),
+		.enabled = (u8)enabled,
+	};
+
+	return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0,
+			    HOST_CMD_POWERSAVE_TIMEOUT_MS);
+}
+
+int mm81x_cmd_cfg_qos(struct mm81x *mors, struct mm81x_queue_params *params)
+{
+	struct host_cmd_req_set_qos_params req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_QOS_PARAMS, 0),
+		.uapsd = params->uapsd,
+		.queue_idx = params->aci,
+		.aifs_slot_count = params->aifs,
+		.contention_window_min = cpu_to_le16(params->cw_min),
+		.contention_window_max = cpu_to_le16(params->cw_max),
+		.max_txop_usec = cpu_to_le32(params->txop),
+	};
+
+	return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
+}
+
+int mm81x_cmd_rm_if(struct mm81x *mors, u16 vif_id)
+{
+	struct host_cmd_req_remove_interface req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_REMOVE_INTERFACE, vif_id),
+	};
+
+	return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
+}
+
+int mm81x_cmd_set_frag_threshold(struct mm81x *mors, u32 frag_threshold)
+{
+	struct host_cmd_req_get_set_generic_param req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_SET_GENERIC_PARAM, 0),
+		.param_id = cpu_to_le32(HOST_CMD_PARAM_ID_FRAGMENT_THRESHOLD),
+		.action = cpu_to_le32(HOST_CMD_PARAM_ACTION_SET),
+		.value = cpu_to_le32(frag_threshold),
+	};
+
+	return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
+}
+
+int mm81x_cmd_get_disabled_channels(
+	struct mm81x *mors, struct host_cmd_resp_get_disabled_channels *resp,
+	uint resp_len)
+{
+	struct host_cmd_req req = {
+		.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_DISABLED_CHANNELS, 0),
+	};
+
+	return mm81x_cmd_tx(mors, (struct host_cmd_resp *)resp, &req, resp_len,
+			    0);
+}
diff --git a/drivers/net/wireless/morsemicro/mm81x/command.h b/drivers/net/wireless/morsemicro/mm81x/command.h
new file mode 100644
index 00000000000000..0ea796f1d878ae
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/command.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_COMMAND_H_
+#define _MM81X_COMMAND_H_
+
+#include <linux/skbuff.h>
+#include <linux/workqueue.h>
+#include "core.h"
+#include "command_defs.h"
+
+#define HOST_CMD_IS_REQ(cmd) (le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_REQ)
+#define HOST_CMD_IS_RESP(cmd) \
+	(le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_RESP)
+#define HOST_CMD_IS_EVT(cmd) (le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_EVT)
+
+struct mm81x_queue_params;
+
+enum mm81x_cmd_return_code {
+	MM81X_RET_SUCCESS = 0,
+	MM81X_RET_EPERM = -1,
+	MM81X_RET_ENOMEM = -12,
+	MM81X_RET_CMD_NOT_HANDLED = -32757,
+};
+
+#define HOST_CMD_HOST_ID_SEQ_MAX 0xFFF
+#define HOST_CMD_HOST_ID_RETRY_MASK 0x000F
+#define HOST_CMD_HOST_ID_SEQ_SHIFT 4
+#define HOST_CMD_HOST_ID_SEQ_MASK 0xFFF0
+
+struct host_cmd_req {
+	struct host_cmd_header hdr;
+	u8 data[];
+} __packed;
+
+struct host_cmd_resp {
+	struct host_cmd_header hdr;
+	__le32 status;
+	u8 data[];
+} __packed;
+
+struct host_cmd_event {
+	struct host_cmd_header hdr;
+	u8 data[];
+} __packed;
+
+int mm81x_cmd_resp_process(struct mm81x *mors, struct sk_buff *skb);
+int mm81x_cmd_add_if(struct mm81x *mors, u16 *vif_id, const u8 *addr,
+		     enum nl80211_iftype type);
+int mm81x_cmd_get_capabilities(struct mm81x *mors, u16 vif_id,
+			       struct mm81x_fw_caps *capabilities);
+int mm81x_cmd_cfg_qos(struct mm81x *mors, struct mm81x_queue_params *params);
+int mm81x_cmd_config_beacon_timer(struct mm81x *mors, void *mm81x_vif,
+				  bool enabled);
+int mm81x_cmd_cfg_bss(struct mm81x *mors, u16 vif_id, u16 beacon_int,
+		      u16 dtim_period, u32 cssid);
+int mm81x_cmd_set_channel(struct mm81x *mors, u32 op_chan_freq_hz,
+			  u8 pri_1mhz_chan_idx, u8 op_bw_mhz, u8 pri_bw_mhz,
+			  s32 *power_mbm);
+int mm81x_cmd_get_max_txpower(struct mm81x *mors, s32 *out_power_mbm);
+int mm81x_cmd_set_txpower(struct mm81x *mors, s32 *out_power_mbm,
+			  int txpower_mbm);
+int mm81x_cmd_hw_scan(struct mm81x *mors, struct mm81x_hw_scan_params *params,
+		      bool store);
+int mm81x_cmd_set_ps(struct mm81x *mors, bool enabled);
+int mm81x_cmd_cfg_multicast_filter(struct mm81x *mors,
+				   struct mm81x_vif *mors_vif);
+int mm81x_cmd_sta_state(struct mm81x *mors, struct mm81x_vif *mors_vif, u16 aid,
+			struct ieee80211_sta *sta,
+			enum ieee80211_sta_state state);
+int mm81x_cmd_install_key(struct mm81x *mors, struct mm81x_vif *mors_vif,
+			  u16 aid, struct ieee80211_key_conf *key,
+			  enum host_cmd_key_cipher cipher,
+			  enum host_cmd_aes_key_len length);
+int mm81x_cmd_disable_key(struct mm81x *mors, struct mm81x_vif *mors_vif,
+			  u16 aid, struct ieee80211_key_conf *key);
+int mm81x_cmd_rm_if(struct mm81x *mors, u16 vif_id);
+int mm81x_cmd_set_frag_threshold(struct mm81x *mors, u32 frag_threshold);
+int mm81x_cmd_get_disabled_channels(
+	struct mm81x *mors, struct host_cmd_resp_get_disabled_channels *resp,
+	uint resp_len);
+
+#endif /* !_MM81X_COMMAND_H_ */
diff --git a/drivers/net/wireless/morsemicro/mm81x/command_defs.h b/drivers/net/wireless/morsemicro/mm81x/command_defs.h
new file mode 100644
index 00000000000000..91a4ac09ad8080
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/command_defs.h
@@ -0,0 +1,1658 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#ifndef _MM81X_COMMAND_DEFS_H_
+#define _MM81X_COMMAND_DEFS_H_
+
+#include <linux/types.h>
+
+#define __sle16 __le16
+#define __sle32 __le32
+#define __sle64 __le64
+
+#define HOST_CMD_SEMVER_MAJOR 56
+#define HOST_CMD_SEMVER_MINOR 17
+#define HOST_CMD_SEMVER_PATCH 0
+
+#define HOST_CMD_TYPE_REQ BIT(0)
+#define HOST_CMD_TYPE_RESP BIT(1)
+#define HOST_CMD_TYPE_EVT BIT(2)
+
+#define HOST_CMD_SSID_MAX_LEN 32
+#define HOST_CMD_MAC_ADDR_LEN 6
+
+enum host_cmd_id {
+	HOST_CMD_ID_SET_CHANNEL = 0x0001,
+	HOST_CMD_ID_GET_CHANNEL = 0x001D,
+	HOST_CMD_ID_GET_CHANNEL_FULL = 0x0013,
+	HOST_CMD_ID_GET_CHANNEL_DTIM = 0x001C,
+	HOST_CMD_ID_GET_VERSION = 0x0002,
+	HOST_CMD_ID_SET_TXPOWER = 0x0003,
+	HOST_CMD_ID_GET_MAX_TXPOWER = 0x0024,
+	HOST_CMD_ID_ADD_INTERFACE = 0x0004,
+	HOST_CMD_ID_REMOVE_INTERFACE = 0x0005,
+	HOST_CMD_ID_BSS_CONFIG = 0x0006,
+	HOST_CMD_ID_SCAN_CONFIG = 0x0010,
+	HOST_CMD_ID_SET_QOS_PARAMS = 0x0011,
+	HOST_CMD_ID_GET_QOS_PARAMS = 0x0012,
+	HOST_CMD_ID_SET_STA_STATE = 0x0014,
+	HOST_CMD_ID_SET_BSS_COLOR = 0x0015,
+	HOST_CMD_ID_CONFIG_PS = 0x0016,
+	HOST_CMD_ID_HEALTH_CHECK = 0x0019,
+	HOST_CMD_ID_CTS_SELF_PS = 0x001A,
+	HOST_CMD_ID_DTIM_CHANNEL_ENABLE = 0x001B,
+	HOST_CMD_ID_ARP_OFFLOAD = 0x0020,
+	HOST_CMD_ID_SET_LONG_SLEEP_CONFIG = 0x0021,
+	HOST_CMD_ID_SET_DUTY_CYCLE = 0x0022,
+	HOST_CMD_ID_GET_DUTY_CYCLE = 0x0023,
+	HOST_CMD_ID_GET_CAPABILITIES = 0x0025,
+	HOST_CMD_ID_TWT_AGREEMENT_INSTALL = 0x0026,
+	HOST_CMD_ID_TWT_AGREEMENT_VALIDATE = 0x0036,
+	HOST_CMD_ID_TWT_AGREEMENT_REMOVE = 0x0027,
+	HOST_CMD_ID_GET_TSF = 0x0028,
+	HOST_CMD_ID_MAC_ADDR = 0x0029,
+	HOST_CMD_ID_MPSW_CONFIG = 0x0030,
+	HOST_CMD_ID_INSTALL_KEY = 0x000A,
+	HOST_CMD_ID_DISABLE_KEY = 0x000B,
+	HOST_CMD_ID_DHCP_OFFLOAD = 0x0032,
+	HOST_CMD_ID_SET_KEEP_ALIVE_OFFLOAD = 0x0033,
+	HOST_CMD_ID_UPDATE_OUI_FILTER = 0x0034,
+	HOST_CMD_ID_IBSS_CONFIG = 0x0035,
+	HOST_CMD_ID_OCS = 0x0038,
+	HOST_CMD_ID_MESH_CONFIG = 0x0039,
+	HOST_CMD_ID_SET_OFFSET_TSF = 0x003A,
+	HOST_CMD_ID_GET_CHANNEL_USAGE = 0x003B,
+	HOST_CMD_ID_MCAST_FILTER = 0x003C,
+	HOST_CMD_ID_BSS_BEACON_CONFIG = 0x003D,
+	HOST_CMD_ID_UAPSD_CONFIG = 0x0040,
+	HOST_CMD_ID_PAGE_SLICING_CONFIG = 0x0043,
+	HOST_CMD_ID_HW_SCAN = 0x0044,
+	HOST_CMD_ID_SET_WHITELIST = 0x0045,
+	HOST_CMD_ID_ARP_PERIODIC_REFRESH = 0x0046,
+	HOST_CMD_ID_SET_TCP_KEEPALIVE = 0x0047,
+	HOST_CMD_ID_FORCE_POWER_MODE = 0x0048,
+	HOST_CMD_ID_LI_SLEEP = 0x0049,
+	HOST_CMD_ID_GET_DISABLED_CHANNELS = 0x004A,
+	HOST_CMD_ID_SET_CQM_RSSI = 0x004F,
+	HOST_CMD_ID_GET_APF_CAPABILITIES = 0x0050,
+	HOST_CMD_ID_READ_WRITE_APF = 0x0051,
+	HOST_CMD_ID_BSSID_SET = 0x0052,
+	HOST_CMD_ID_BEACON_OFFLOAD = 0x0053,
+	HOST_CMD_ID_PROBE_RESPONSE_OFFLOAD = 0x0054,
+	HOST_CMD_ID_HOST_STATS_LOG = 0x2007,
+	HOST_CMD_ID_HOST_STATS_RESET = 0x2008,
+	HOST_CMD_ID_MAC_STATS_LOG = 0x200C,
+	HOST_CMD_ID_MAC_STATS_RESET = 0x200D,
+	HOST_CMD_ID_UPHY_STATS_LOG = 0x200E,
+	HOST_CMD_ID_UPHY_STATS_RESET = 0x200F,
+	HOST_CMD_ID_SET_STA_TYPE = 0xA000,
+	HOST_CMD_ID_SET_ENC_MODE = 0xA001,
+	HOST_CMD_ID_TEST_BA = 0xA002,
+	HOST_CMD_ID_SET_LISTEN_INTERVAL = 0xA003,
+	HOST_CMD_ID_SET_AMPDU = 0xA004,
+	HOST_CMD_ID_COREDUMP = 0xA006,
+	HOST_CMD_ID_SET_S1G_OP_CLASS = 0xA007,
+	HOST_CMD_ID_SEND_WAKE_ACTION_FRAME = 0xA008,
+	HOST_CMD_ID_VENDOR_IE_CONFIG = 0xA009,
+	HOST_CMD_ID_SET_TWT_CONF = 0xA010,
+	HOST_CMD_ID_GET_AVAILABLE_CHANNELS = 0xA011,
+	HOST_CMD_ID_SET_ECSA_S1G_INFO = 0xA012,
+	HOST_CMD_ID_GET_HW_VERSION = 0xA013,
+	HOST_CMD_ID_CAC = 0xA014,
+	HOST_CMD_ID_DRIVER_SET_DUTY_CYCLE = 0xA015,
+	HOST_CMD_ID_OCS_DRIVER = 0xA017,
+	HOST_CMD_ID_MBSSID = 0xA016,
+	HOST_CMD_ID_SET_MESH_CONFIG = 0xA018,
+	HOST_CMD_ID_SET_MCBA_CONF = 0xA019,
+	HOST_CMD_ID_DYNAMIC_PEERING_CONFIG = 0xA020,
+	HOST_CMD_ID_CONFIG_RAW = 0xA021,
+	HOST_CMD_ID_CONFIG_BSS_STATS = 0xA022,
+	HOST_CMD_ID_GET_RSSI = 0x1002,
+	HOST_CMD_ID_SET_IFS = 0x1003,
+	HOST_CMD_ID_SET_FEM_SETTINGS = 0x1005,
+	HOST_CMD_ID_SET_TXOP = 0x1008,
+	HOST_CMD_ID_SET_CONTROL_RESPONSE = 0x1009,
+	HOST_CMD_ID_SET_PERIODIC_CAL = 0x100A,
+	HOST_CMD_ID_SET_BCN_RSSI_THRESHOLD = 0x100B,
+	HOST_CMD_ID_SET_TX_PKT_LIFETIME_USECS = 0x100C,
+	HOST_CMD_ID_SET_PHYSM_WATCHDOG = 0x100D,
+	HOST_CMD_ID_TX_POLAR = 0x100E,
+	HOST_CMD_ID_EVT_STA_STATE = 0x4001,
+	HOST_CMD_ID_EVT_BEACON_LOSS = 0x4002,
+	HOST_CMD_ID_EVT_SIG_FIELD_ERROR = 0x4003,
+	HOST_CMD_ID_EVT_UMAC_TRAFFIC_CONTROL = 0x4004,
+	HOST_CMD_ID_EVT_DHCP_LEASE_UPDATE = 0x4005,
+	HOST_CMD_ID_EVT_OCS_DONE = 0x4006,
+	HOST_CMD_ID_EVT_HW_SCAN_DONE = 0x4011,
+	HOST_CMD_ID_EVT_CHANNEL_USAGE = 0x4012,
+	HOST_CMD_ID_EVT_CONNECTION_LOSS = 0x4013,
+	HOST_CMD_ID_EVT_SCHED_SCAN_RESULTS = 0x4014,
+	HOST_CMD_ID_EVT_CQM_RSSI_NOTIFY = 0x4015,
+	HOST_CMD_ID_EVT_SCAN_DONE = 0x4007,
+	HOST_CMD_ID_EVT_SCAN_RESULT = 0x4008,
+	HOST_CMD_ID_EVT_CONNECTED = 0x4009,
+	HOST_CMD_ID_EVT_DISCONNECTED = 0x4010,
+	HOST_CMD_ID_EVT_BEACON_FILTER_MATCH = 0x4016,
+	HOST_CMD_ID_SET_CAPABILITIES = 0x8118,
+	HOST_CMD_ID_SET_TRANSMISSION_RATE = 0x8009,
+	HOST_CMD_ID_FORCE_ASSERT = 0x800E,
+	HOST_CMD_ID_GET_SET_GENERIC_PARAM = 0x003E,
+};
+
+struct host_cmd_mac_addr {
+	u8 octet[HOST_CMD_MAC_ADDR_LEN];
+};
+
+enum host_cmd_ocs_subcmd {
+	HOST_CMD_OCS_SUBCMD_CONFIG = 1,
+	HOST_CMD_OCS_SUBCMD_STATUS = 2,
+};
+
+enum host_cmd_headless_cfg_option {
+	HOST_CMD_HEADLESS_CFG_OPTION_KEEP_IFACES = BIT(0),
+	HOST_CMD_HEADLESS_CFG_OPTION_BUFFER_RX = BIT(1),
+	HOST_CMD_HEADLESS_CFG_OPTION_NOTIFY_ON_ANY_RX = BIT(2),
+};
+
+struct host_cmd_header {
+	__le16 flags;
+	__le16 message_id;
+	__le16 len;
+	__le16 host_id;
+	__le16 vif_id;
+	__le16 pad;
+};
+
+#define HOST_CMD_CHANNEL_BW_NOT_SET 0xFF
+#define HOST_CMD_CHANNEL_IDX_NOT_SET 0xFF
+#define HOST_CMD_CHANNEL_FREQ_NOT_SET 0xFFFFFFFF
+
+enum host_cmd_dot11_proto_mode {
+	HOST_CMD_DOT11_PROTO_MODE_AH = 0,
+};
+
+struct host_cmd_req_set_channel {
+	struct host_cmd_header hdr;
+	__le32 op_chan_freq_hz;
+	u8 op_bw_mhz;
+	u8 pri_bw_mhz;
+	u8 pri_1mhz_chan_idx;
+	u8 dot11_mode;
+	u8 __deprecated_reg_tx_power_set;
+	u8 is_off_channel;
+} __packed;
+
+struct host_cmd_resp_set_channel {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__sle32 power_qdbm;
+} __packed;
+
+struct host_cmd_req_get_channel {
+	struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_channel {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__le32 op_chan_freq_hz;
+	u8 op_chan_bw_mhz;
+	u8 pri_chan_bw_mhz;
+	u8 pri_1mhz_chan_idx;
+} __packed;
+
+#define HOST_CMD_MAX_VERSION_LEN 128
+
+struct host_cmd_req_get_version {
+	struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_version {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__sle32 length;
+	u8 version[];
+} __packed;
+
+struct host_cmd_req_set_txpower {
+	struct host_cmd_header hdr;
+	__sle32 power_qdbm;
+} __packed;
+
+struct host_cmd_resp_set_txpower {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__sle32 power_qdbm;
+} __packed;
+
+struct host_cmd_req_get_max_txpower {
+	struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_max_txpower {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__sle32 power_qdbm;
+} __packed;
+
+enum host_cmd_interface_type {
+	HOST_CMD_INTERFACE_TYPE_INVALID = 0,
+	HOST_CMD_INTERFACE_TYPE_STA = 1,
+	HOST_CMD_INTERFACE_TYPE_AP = 2,
+	HOST_CMD_INTERFACE_TYPE_MON = 3,
+	HOST_CMD_INTERFACE_TYPE_ADHOC = 4,
+	HOST_CMD_INTERFACE_TYPE_MESH = 5,
+	HOST_CMD_INTERFACE_TYPE_LAST = HOST_CMD_INTERFACE_TYPE_MESH,
+};
+
+struct host_cmd_req_add_interface {
+	struct host_cmd_header hdr;
+	struct host_cmd_mac_addr addr;
+	__le32 interface_type;
+} __packed;
+
+struct host_cmd_resp_add_interface {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_remove_interface {
+	struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_remove_interface {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_bss_config {
+	struct host_cmd_header hdr;
+	__le16 beacon_interval_tu;
+	__le16 dtim_period;
+	u8 __padding[2];
+	__le32 cssid;
+} __packed;
+
+struct host_cmd_resp_bss_config {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_scan_config {
+	struct host_cmd_header hdr;
+	u8 enabled;
+	u8 is_survey;
+} __packed;
+
+struct host_cmd_resp_scan_config {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_set_qos_params {
+	struct host_cmd_header hdr;
+	u8 uapsd;
+	u8 queue_idx;
+	u8 aifs_slot_count;
+	__le16 contention_window_min;
+	__le16 contention_window_max;
+	__le32 max_txop_usec;
+} __packed;
+
+struct host_cmd_resp_set_qos_params {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_get_qos_params {
+	struct host_cmd_header hdr;
+	u8 queue_idx;
+} __packed;
+
+struct host_cmd_resp_get_qos_params {
+	struct host_cmd_header hdr;
+	__le32 status;
+	u8 aifs_slot_count;
+	__le16 contention_window_min;
+	__le16 contention_window_max;
+	__le32 max_txop_usec;
+} __packed;
+
+struct host_cmd_req_set_sta_state {
+	struct host_cmd_header hdr;
+	u8 sta_addr[HOST_CMD_MAC_ADDR_LEN];
+	__le16 aid;
+	__le16 state;
+	u8 uapsd_queues;
+	__le32 flags;
+} __packed;
+
+struct host_cmd_resp_set_sta_state {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_set_bss_color {
+	struct host_cmd_header hdr;
+	u8 bss_color;
+} __packed;
+
+struct host_cmd_resp_set_bss_color {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_config_ps {
+	struct host_cmd_header hdr;
+	u8 enabled;
+	u8 dynamic_ps_offload;
+} __packed;
+
+struct host_cmd_resp_config_ps {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_health_check {
+	struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_health_check {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_cts_self_ps {
+	struct host_cmd_header hdr;
+	u8 enable;
+} __packed;
+
+struct host_cmd_resp_cts_self_ps {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_dtim_channel_enable {
+	struct host_cmd_header hdr;
+	u8 enable;
+} __packed;
+
+struct host_cmd_resp_dtim_channel_enable {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+#define HOST_CMD_ARP_OFFLOAD_MAX_IP_ADDRESSES 4
+
+struct host_cmd_req_arp_offload {
+	struct host_cmd_header hdr;
+	__be32 ip_table[HOST_CMD_ARP_OFFLOAD_MAX_IP_ADDRESSES];
+} __packed;
+
+struct host_cmd_resp_arp_offload {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_set_long_sleep_config {
+	struct host_cmd_header hdr;
+	u8 enabled;
+} __packed;
+
+struct host_cmd_resp_set_long_sleep_config {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+#define HOST_CMD_DUTY_CYCLE_SET_CFG_DUTY_CYCLE BIT(0)
+#define HOST_CMD_DUTY_CYCLE_SET_CFG_OMIT_CONTROL_RESP BIT(1)
+#define HOST_CMD_DUTY_CYCLE_SET_CFG_EXT BIT(2)
+#define HOST_CMD_DUTY_CYCLE_SET_CFG_BURST_RECORD_UNIT BIT(3)
+
+enum host_cmd_duty_cycle_mode {
+	HOST_CMD_DUTY_CYCLE_MODE_SPREAD = 0,
+	HOST_CMD_DUTY_CYCLE_MODE_BURST = 1,
+	HOST_CMD_DUTY_CYCLE_MODE_LAST = HOST_CMD_DUTY_CYCLE_MODE_BURST,
+};
+
+struct host_cmd_duty_cycle_configuration {
+	u8 omit_control_responses;
+	__le32 duty_cycle;
+} __packed;
+
+struct host_cmd_duty_cycle_set_configuration_ext {
+	__le32 burst_record_unit_us;
+	u8 mode;
+} __packed;
+
+struct host_cmd_duty_cycle_configuration_ext {
+	__le32 airtime_remaining_us;
+	__le32 burst_window_duration_us;
+	struct host_cmd_duty_cycle_set_configuration_ext set;
+} __packed;
+
+struct host_cmd_req_set_duty_cycle {
+	struct host_cmd_header hdr;
+	struct host_cmd_duty_cycle_configuration config;
+	u8 set_cfgs;
+	struct host_cmd_duty_cycle_set_configuration_ext config_ext;
+} __packed;
+
+struct host_cmd_resp_get_duty_cycle {
+	struct host_cmd_header hdr;
+	__le32 status;
+	struct host_cmd_duty_cycle_configuration config;
+	struct host_cmd_duty_cycle_configuration_ext config_ext;
+} __packed;
+
+#define HOST_CMD_SET_S1G_CAP_FLAGS BIT(0)
+#define HOST_CMD_SET_S1G_CAP_AMPDU_MSS BIT(1)
+#define HOST_CMD_SET_S1G_CAP_BEAM_STS BIT(2)
+#define HOST_CMD_SET_S1G_CAP_NUM_SOUND_DIMS BIT(3)
+#define HOST_CMD_SET_S1G_CAP_MAX_AMPDU_LEXP BIT(4)
+#define HOST_CMD_SET_MORSE_CAP_MMSS_OFFSET BIT(5)
+#define HOST_CMD_S1G_CAPABILITY_FLAGS_WIDTH 4
+
+struct host_cmd_mm_capabilities {
+	__le32 flags[HOST_CMD_S1G_CAPABILITY_FLAGS_WIDTH];
+	u8 ampdu_mss;
+	u8 beamformee_sts_capability;
+	u8 number_sounding_dimensions;
+	u8 maximum_ampdu_length_exponent;
+} __packed;
+
+struct host_cmd_req_get_capabilities {
+	struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_capabilities {
+	struct host_cmd_header hdr;
+	__le32 status;
+	struct host_cmd_mm_capabilities capabilities;
+	u8 morse_mmss_offset;
+} __packed;
+
+#define HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN 20
+
+struct host_cmd_req_twt_agreement_install {
+	struct host_cmd_header hdr;
+	u8 flow_id;
+	u8 agreement_len;
+	u8 agreement[HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN];
+} __packed;
+
+struct host_cmd_resp_twt_agreement_install {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_twt_agreement_validate {
+	struct host_cmd_header hdr;
+	u8 flow_id;
+	u8 agreement_len;
+	u8 agreement[HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN];
+} __packed;
+
+struct host_cmd_resp_twt_agreement_validate {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_twt_agreement_remove {
+	struct host_cmd_header hdr;
+	u8 flow_id;
+} __packed;
+
+struct host_cmd_req_get_tsf {
+	struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_tsf {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__le64 now_tsf;
+	__le64 now_chip_ts;
+} __packed;
+
+struct host_cmd_req_mac_addr {
+	struct host_cmd_header hdr;
+	u8 write;
+	u8 octet[HOST_CMD_MAC_ADDR_LEN];
+} __packed;
+
+struct host_cmd_resp_mac_addr {
+	struct host_cmd_header hdr;
+	__le32 status;
+	u8 octet[HOST_CMD_MAC_ADDR_LEN];
+} __packed;
+
+#define HOST_CMD_SET_MPSW_CFG_AIRTIME_BOUNDS BIT(0)
+#define HOST_CMD_SET_MPSW_CFG_PKT_SPC_WIN_LEN BIT(1)
+#define HOST_CMD_SET_MPSW_CFG_ENABLED BIT(2)
+
+struct host_cmd_mpsw_configuration {
+	__le32 airtime_max_us;
+	__le32 airtime_min_us;
+	__le32 packet_space_window_length_us;
+	u8 enable;
+} __packed;
+
+struct host_cmd_req_mpsw_config {
+	struct host_cmd_header hdr;
+	struct host_cmd_mpsw_configuration config;
+	u8 set_cfgs;
+} __packed;
+
+struct host_cmd_resp_mpsw_config {
+	struct host_cmd_header hdr;
+	__le32 status;
+	struct host_cmd_mpsw_configuration config;
+} __packed;
+
+#define HOST_CMD_MAX_KEY_LEN 32
+
+enum host_cmd_key_cipher {
+	HOST_CMD_KEY_CIPHER_INVALID = 0,
+	HOST_CMD_KEY_CIPHER_AES_CCM = 1,
+	HOST_CMD_KEY_CIPHER_AES_GCM = 2,
+	HOST_CMD_KEY_CIPHER_AES_CMAC = 3,
+	HOST_CMD_KEY_CIPHER_AES_GMAC = 4,
+	HOST_CMD_KEY_CIPHER_LAST = HOST_CMD_KEY_CIPHER_AES_GMAC,
+};
+
+enum host_cmd_aes_key_len {
+	HOST_CMD_AES_KEY_LEN_INVALID = 0,
+	HOST_CMD_AES_KEY_LEN_LENGTH_128 = 1,
+	HOST_CMD_AES_KEY_LEN_LENGTH_256 = 2,
+	HOST_CMD_AES_KEY_LEN_LENGTH_LAST = HOST_CMD_AES_KEY_LEN_LENGTH_256,
+};
+
+enum host_cmd_temporal_key_type {
+	HOST_CMD_TEMPORAL_KEY_TYPE_INVALID = 0,
+	HOST_CMD_TEMPORAL_KEY_TYPE_GTK = 1,
+	HOST_CMD_TEMPORAL_KEY_TYPE_PTK = 2,
+	HOST_CMD_TEMPORAL_KEY_TYPE_IGTK = 3,
+	HOST_CMD_TEMPORAL_KEY_TYPE_LAST = HOST_CMD_TEMPORAL_KEY_TYPE_IGTK,
+};
+
+struct host_cmd_req_install_key {
+	struct host_cmd_header hdr;
+	__le64 pn;
+	__le32 aid;
+	u8 key_idx;
+	u8 cipher;
+	u8 key_length;
+	u8 key_type;
+	u8 __padding[2];
+	u8 key[HOST_CMD_MAX_KEY_LEN];
+} __packed;
+
+struct host_cmd_resp_install_key {
+	struct host_cmd_header hdr;
+	__le32 status;
+	u8 key_idx;
+} __packed;
+
+struct host_cmd_req_disable_key {
+	struct host_cmd_header hdr;
+	__le32 key_type;
+	__le32 aid;
+	u8 key_idx;
+} __packed;
+
+struct host_cmd_resp_disable_key {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+enum host_cmd_dhcp_opcode {
+	HOST_CMD_DHCP_OPCODE_ENABLE = 0,
+	HOST_CMD_DHCP_OPCODE_DO_DISCOVERY = 1,
+	HOST_CMD_DHCP_OPCODE_GET_LEASE = 2,
+	HOST_CMD_DHCP_OPCODE_CLEAR_LEASE = 3,
+	HOST_CMD_DHCP_OPCODE_RENEW_LEASE = 4,
+	HOST_CMD_DHCP_OPCODE_REBIND_LEASE = 5,
+	HOST_CMD_DHCP_OPCODE_SEND_LEASE_UPDATE = 6,
+};
+
+enum host_cmd_dhcp_retcode {
+	HOST_CMD_DHCP_RETCODE_SUCCESS = 0,
+	HOST_CMD_DHCP_RETCODE_NOT_ENABLED = 1,
+	HOST_CMD_DHCP_RETCODE_ALREADY_ENABLED = 2,
+	HOST_CMD_DHCP_RETCODE_NO_LEASE = 3,
+	HOST_CMD_DHCP_RETCODE_HAVE_LEASE = 4,
+	HOST_CMD_DHCP_RETCODE_BUSY = 5,
+	HOST_CMD_DHCP_RETCODE_BAD_VIF = 6,
+};
+
+struct host_cmd_req_dhcp_offload {
+	struct host_cmd_header hdr;
+	__le32 opcode;
+} __packed;
+
+struct host_cmd_resp_dhcp_offload {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__le32 retcode;
+	__le32 my_ip;
+	__le32 netmask;
+	__le32 router;
+	__le32 dns;
+} __packed;
+
+struct host_cmd_req_set_keep_alive_offload {
+	struct host_cmd_header hdr;
+	__le16 bss_max_idle_period;
+	u8 interpret_as_11ah;
+} __packed;
+
+#define HOST_CMD_MAX_OUI_FILTERS 5
+#define HOST_CMD_OUI_SIZE 3
+#define HOST_CMD_MAX_OUI_FILTER_ARRAY_SIZE 15
+
+struct host_cmd_req_update_oui_filter {
+	struct host_cmd_header hdr;
+	u8 n_ouis;
+	u8 ouis[HOST_CMD_MAX_OUI_FILTERS][HOST_CMD_OUI_SIZE];
+} __packed;
+
+enum host_cmd_ibss_config_opcode {
+	HOST_CMD_IBSS_CONFIG_OPCODE_CREATE = 0,
+	HOST_CMD_IBSS_CONFIG_OPCODE_JOIN = 1,
+	HOST_CMD_IBSS_CONFIG_OPCODE_STOP = 2,
+};
+
+struct host_cmd_req_ibss_config {
+	struct host_cmd_header hdr;
+	u8 ibss_bssid[HOST_CMD_MAC_ADDR_LEN];
+	u8 ibss_cfg_opcode;
+	u8 ibss_probe_filtering;
+} __packed;
+
+enum host_cmd_ocs_type {
+	HOST_CMD_OCS_TYPE_QNULL = 0,
+	HOST_CMD_OCS_TYPE_RAW = 1,
+};
+
+struct host_cmd_ocs_config_req {
+	__le32 op_channel_freq_hz;
+	u8 op_channel_bw_mhz;
+	u8 pri_channel_bw_mhz;
+	u8 pri_1mhz_channel_index;
+	__le16 aid;
+	u8 type;
+} __packed;
+
+struct host_cmd_ocs_status_resp {
+	u8 running;
+} __packed;
+
+struct host_cmd_req_ocs {
+	struct host_cmd_header hdr;
+	__le32 subcmd;
+	union {
+		u8 opaque[0];
+		struct host_cmd_ocs_config_req config;
+	};
+} __packed;
+
+struct host_cmd_resp_ocs {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__le32 subcmd;
+	union {
+		u8 opaque[0];
+		struct host_cmd_ocs_status_resp ocs_status;
+	};
+} __packed;
+
+enum host_cmd_mesh_config_opcode {
+	HOST_CMD_MESH_CONFIG_OPCODE_START = 0,
+	HOST_CMD_MESH_CONFIG_OPCODE_STOP = 1,
+};
+
+struct host_cmd_req_mesh_config {
+	struct host_cmd_header hdr;
+	u8 mesh_cfg_opcode;
+	u8 enable_beaconing;
+	u8 mbca_config;
+	u8 min_beacon_gap_ms;
+	__le16 mbss_start_scan_duration_ms;
+	__le16 tbtt_adj_timer_interval_ms;
+} __packed;
+
+struct host_cmd_req_set_offset_tsf {
+	struct host_cmd_header hdr;
+	__sle64 offset_tsf;
+} __packed;
+
+struct host_cmd_req_get_channel_usage {
+	struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_channel_usage {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__le64 time_listen;
+	__le64 busy_time;
+	__le32 freq_hz;
+	s8 noise;
+	u8 bw_mhz;
+} __packed;
+
+#define HOST_CMD_MAX_MCAST_FILTERS 12
+
+struct host_cmd_req_mcast_filter {
+	struct host_cmd_header hdr;
+	u8 count;
+	__le32 hw_addr[];
+} __packed;
+
+struct host_cmd_req_bss_beacon_config {
+	struct host_cmd_header hdr;
+	u8 enable;
+} __packed;
+
+struct host_cmd_resp_bss_beacon_config {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__le16 interface_id;
+} __packed;
+
+struct host_cmd_req_uapsd_config {
+	struct host_cmd_header hdr;
+	u8 auto_trigger_enabled;
+	__le32 auto_trigger_timeout;
+} __packed;
+
+struct host_cmd_resp_uapsd_config {
+	struct host_cmd_header hdr;
+	__le32 status;
+	u8 auto_trigger_enabled;
+} __packed;
+
+struct host_cmd_req_page_slicing_config {
+	struct host_cmd_header hdr;
+	u8 enable;
+} __packed;
+
+#define HOST_CMD_HW_SCAN_FLAGS_START BIT(0)
+#define HOST_CMD_HW_SCAN_FLAGS_ABORT BIT(1)
+#define HOST_CMD_HW_SCAN_FLAGS_SURVEY BIT(2)
+#define HOST_CMD_HW_SCAN_FLAGS_STORE BIT(3)
+#define HOST_CMD_HW_SCAN_FLAGS_1MHZ_PROBES BIT(4)
+#define HOST_CMD_HW_SCAN_FLAGS_SCHED_START BIT(5)
+#define HOST_CMD_HW_SCAN_FLAGS_SCHED_STOP BIT(6)
+#define HOST_CMD_HW_SCAN_FLAGS_PROBE_ON_DOZE_BEACON BIT(7)
+
+enum host_cmd_hw_scan_tlv_tag {
+	HOST_CMD_HW_SCAN_TLV_TAG_PAD = 0,
+	HOST_CMD_HW_SCAN_TLV_TAG_PROBE_REQ = 1,
+	HOST_CMD_HW_SCAN_TLV_TAG_CHAN_LIST = 2,
+	HOST_CMD_HW_SCAN_TLV_TAG_POWER_LIST = 3,
+	HOST_CMD_HW_SCAN_TLV_TAG_DWELL_ON_HOME = 4,
+	HOST_CMD_HW_SCAN_TLV_TAG_SCHED = 5,
+	HOST_CMD_HW_SCAN_TLV_TAG_FILTER = 6,
+	HOST_CMD_HW_SCAN_TLV_TAG_SCHED_PARAMS = 7,
+};
+
+struct host_cmd_hw_scan_tlv {
+	__le16 tag;
+	__le16 len;
+	u8 value[];
+} __packed;
+
+struct host_cmd_req_hw_scan {
+	struct host_cmd_header hdr;
+	__le32 flags;
+	__le32 dwell_time_ms;
+	u8 variable[];
+} __packed;
+
+#define HOST_CMD_WHITELIST_FLAGS_CLEAR BIT(0)
+
+struct host_cmd_req_set_whitelist {
+	struct host_cmd_header hdr;
+	u8 flags;
+	u8 ip_protocol;
+	__be16 llc_protocol;
+	__be32 src_ip;
+	__be32 dest_ip;
+	__be32 netmask;
+	__be16 src_port;
+	__be16 dest_port;
+} __packed;
+
+struct host_cmd_arp_periodic_params {
+	__le32 refresh_period_s;
+	__le32 destination_ip;
+	u8 send_as_garp;
+} __packed;
+
+struct host_cmd_req_arp_periodic_refresh {
+	struct host_cmd_header hdr;
+	struct host_cmd_arp_periodic_params config;
+} __packed;
+
+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_PERIOD BIT(0)
+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_RETRY_COUNT BIT(1)
+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_RETRY_INTERVAL BIT(2)
+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_SRC_IP_ADDR BIT(3)
+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_DEST_IP_ADDR BIT(4)
+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_SRC_PORT BIT(5)
+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_DEST_PORT BIT(6)
+
+struct host_cmd_req_set_tcp_keepalive {
+	struct host_cmd_header hdr;
+	u8 enabled;
+	u8 retry_count;
+	u8 retry_interval_s;
+	u8 set_cfgs;
+	__be32 src_ip;
+	__be32 dest_ip;
+	__be16 src_port;
+	__be16 dest_port;
+	__le16 period_s;
+} __packed;
+
+enum host_cmd_power_mode {
+	HOST_CMD_POWER_MODE_SNOOZE = 0,
+	HOST_CMD_POWER_MODE_DEEP_SLEEP = 1,
+	HOST_CMD_POWER_MODE_HIBERNATE = 2,
+};
+
+struct host_cmd_req_force_power_mode {
+	struct host_cmd_header hdr;
+	__le32 mode;
+} __packed;
+
+struct host_cmd_req_li_sleep {
+	struct host_cmd_header hdr;
+	__le32 listen_interval;
+} __packed;
+
+struct host_cmd_disabled_channel_entry {
+	__le16 freq_100khz;
+	u8 bw_mhz;
+} __packed;
+
+struct host_cmd_resp_get_disabled_channels {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__le32 n_channels;
+	struct host_cmd_disabled_channel_entry channels[];
+} __packed;
+
+struct host_cmd_req_set_cqm_rssi {
+	struct host_cmd_header hdr;
+	__sle32 threshold;
+	__le32 hysteresis;
+} __packed;
+
+struct host_cmd_req_get_apf_capabilities {
+	struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_apf_capabilities {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__le32 max_length;
+	u8 version;
+} __packed;
+
+struct host_cmd_req_read_write_apf {
+	struct host_cmd_header hdr;
+	__le32 offset;
+	__le16 program_length;
+	u8 write;
+	u8 program[];
+} __packed;
+
+struct host_cmd_resp_read_write_apf {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__le16 program_length;
+	u8 program[];
+} __packed;
+
+struct host_cmd_req_bssid_set {
+	struct host_cmd_header hdr;
+	struct host_cmd_mac_addr bssid;
+} __packed;
+
+#define HOST_CMD_BEACON_OFFLOAD_FLAGS_START BIT(0)
+#define HOST_CMD_BEACON_OFFLOAD_FLAGS_STOP BIT(1)
+#define HOST_CMD_BEACON_OFFLOAD_CSSID_LEN 4
+
+enum host_cmd_beacon_offload_tlv_tag {
+	HOST_CMD_BEACON_OFFLOAD_TLV_TAG_DTIM_CNT = 0,
+	HOST_CMD_BEACON_OFFLOAD_TLV_TAG_FRAME_CTRL = 1,
+	HOST_CMD_BEACON_OFFLOAD_TLV_TAG_CHANGE_SEQ = 2,
+	HOST_CMD_BEACON_OFFLOAD_TLV_TAG_CSSID = 3,
+	HOST_CMD_BEACON_OFFLOAD_TLV_TAG_IES = 4,
+	HOST_CMD_BEACON_OFFLOAD_TLV_TAG_TX_INFO = 5,
+};
+
+struct host_cmd_beacon_offload_tlv_hdr {
+	__le16 tag;
+	__le16 len;
+} __packed;
+
+struct host_cmd_beacon_offload_tlv_generic {
+	struct host_cmd_beacon_offload_tlv_hdr hdr;
+	u8 value[];
+} __packed;
+
+struct host_cmd_beacon_offload_tlv_dtim_cnt {
+	struct host_cmd_beacon_offload_tlv_hdr hdr;
+	__le16 dtim_cnt;
+} __packed;
+
+struct host_cmd_beacon_offload_tlv_frame_ctrl {
+	struct host_cmd_beacon_offload_tlv_hdr hdr;
+	u8 frame_ctrl[2];
+} __packed;
+
+struct host_cmd_beacon_offload_tlv_change_seq {
+	struct host_cmd_beacon_offload_tlv_hdr hdr;
+	__le16 change_seq;
+} __packed;
+
+struct host_cmd_beacon_offload_tlv_tx_info {
+	struct host_cmd_beacon_offload_tlv_hdr hdr;
+	u8 bw_mhz;
+} __packed;
+
+struct host_cmd_beacon_offload_tlv_cssid {
+	struct host_cmd_beacon_offload_tlv_hdr hdr;
+	u8 cssid[HOST_CMD_BEACON_OFFLOAD_CSSID_LEN];
+} __packed;
+
+struct host_cmd_beacon_offload_tlv_ies {
+	struct host_cmd_beacon_offload_tlv_hdr hdr;
+	u8 buf[];
+} __packed;
+
+struct host_cmd_req_beacon_offload {
+	struct host_cmd_header hdr;
+	__le32 flags;
+	u8 variable[];
+} __packed;
+
+struct host_cmd_resp_beacon_offload {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__le16 dtim_count;
+} __packed;
+
+struct host_cmd_req_probe_response_offload {
+	struct host_cmd_header hdr;
+	u8 enable;
+	__le16 probe_resp_len;
+	u8 probe_resp_buf[];
+} __packed;
+
+struct host_cmd_resp_probe_response_offload {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_set_sta_type {
+	struct host_cmd_header hdr;
+	u8 sta_type;
+} __packed;
+
+struct host_cmd_req_set_enc_mode {
+	struct host_cmd_header hdr;
+	u8 enc_mode;
+} __packed;
+
+struct host_cmd_req_test_ba {
+	struct host_cmd_header hdr;
+	u8 addr[HOST_CMD_MAC_ADDR_LEN];
+	u8 start;
+	u8 tx;
+	__le32 tid;
+} __packed;
+
+struct host_cmd_req_set_listen_interval {
+	struct host_cmd_header hdr;
+	__le16 listen_interval;
+} __packed;
+
+struct host_cmd_req_set_ampdu {
+	struct host_cmd_header hdr;
+	u8 ampdu_enabled;
+} __packed;
+
+struct host_cmd_req_set_s1g_op_class {
+	struct host_cmd_header hdr;
+	u8 opclass;
+	u8 prim_opclass;
+} __packed;
+
+struct host_cmd_req_send_wake_action_frame {
+	struct host_cmd_header hdr;
+	u8 dest_addr[HOST_CMD_MAC_ADDR_LEN];
+	__le32 payload_size;
+	u8 payload[];
+} __packed;
+
+#define HOST_CMD_MAX_VENDOR_IE_LENGTH 255
+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_BEACON BIT(0)
+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_PROBE_REQ BIT(1)
+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_PROBE_RESP BIT(2)
+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_ASSOC_REQ BIT(3)
+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_ASSOC_RESP BIT(4)
+
+enum host_cmd_vendor_ie_op {
+	HOST_CMD_VENDOR_IE_OP_ADD_ELEMENT = 0,
+	HOST_CMD_VENDOR_IE_OP_CLEAR_ELEMENTS = 1,
+	HOST_CMD_VENDOR_IE_OP_ADD_FILTER = 2,
+	HOST_CMD_VENDOR_IE_OP_CLEAR_FILTERS = 3,
+	HOST_CMD_VENDOR_IE_OP_INVALID = U16_MAX,
+};
+
+struct host_cmd_req_vendor_ie_config {
+	struct host_cmd_header hdr;
+	__le16 opcode;
+	__le16 mgmt_type_mask;
+	u8 data[HOST_CMD_MAX_VENDOR_IE_LENGTH];
+} __packed;
+
+struct host_cmd_resp_vendor_ie_config {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+enum host_cmd_twt_conf_op {
+	HOST_CMD_TWT_CONF_OP_CONFIGURE = 0,
+	HOST_CMD_TWT_CONF_OP_FORCE_INSTALL_AGREEMENT = 1,
+	HOST_CMD_TWT_CONF_OP_REMOVE_AGREEMENT = 2,
+	HOST_CMD_TWT_CONF_OP_CONFIGURE_EXPLICIT = 3,
+};
+
+struct host_cmd_explicit_twt_wake_interval {
+	__le16 wake_interval_mantissa;
+	u8 wake_interval_exponent;
+	u8 __padding[5];
+} __packed;
+
+union host_cmd_wake_interval {
+	__le64 wake_interval_us;
+	struct host_cmd_explicit_twt_wake_interval explicit_twt;
+} __packed;
+
+struct host_cmd_req_set_twt_conf {
+	struct host_cmd_header hdr;
+	u8 opcode;
+	u8 flow_id;
+	__le64 target_wake_time;
+	union host_cmd_wake_interval wake_interval;
+	__le32 wake_duration_us;
+	u8 twt_setup_command;
+	u8 __padding[3];
+} __packed;
+
+#define HOST_CMD_MAX_AVAILABLE_CHANNELS 255
+
+struct host_cmd_channel_info {
+	__le32 frequency_khz;
+	u8 channel_5g;
+	u8 channel_s1g;
+	u8 bandwidth_mhz;
+} __packed;
+
+struct host_cmd_resp_get_available_channels {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__le32 num_channels;
+	struct host_cmd_channel_info channels[HOST_CMD_MAX_AVAILABLE_CHANNELS];
+} __packed;
+
+#define HOST_CMD_S1G_CAP0_S1G_LONG BIT(0)
+#define HOST_CMD_S1G_CAP0_SGI_1MHZ BIT(1)
+#define HOST_CMD_S1G_CAP0_SGI_2MHZ BIT(2)
+#define HOST_CMD_S1G_CAP0_SGI_4MHZ BIT(3)
+#define HOST_CMD_S1G_CAP0_SGI_8MHZ BIT(4)
+#define HOST_CMD_S1G_CAP0_SGI_16MHZ BIT(5)
+
+struct host_cmd_req_set_ecsa_s1g_info {
+	struct host_cmd_header hdr;
+	__le32 operating_channel_freq_hz;
+	u8 opclass;
+	u8 primary_channel_bw_mhz;
+	u8 prim_1mhz_ch_idx;
+	u8 operating_channel_bw_mhz;
+	u8 prim_opclass;
+	u8 s1g_cap0;
+	u8 s1g_cap1;
+	u8 s1g_cap2;
+	u8 s1g_cap3;
+} __packed;
+
+struct host_cmd_resp_get_hw_version {
+	struct host_cmd_header hdr;
+	__le32 status;
+	u8 hw_version[64];
+} __packed;
+
+#define HOST_CMD_CAC_CFG_CHANGE_RULE_MAX 8
+#define HOST_CMD_CAC_CFG_ARFS_MAX 99
+#define HOST_CMD_CAC_CFG_CHANGE_MAX 99
+#define HOST_CMD_CAC_CFG_CHANGE_STEP 5
+
+enum host_cmd_cac_op {
+	HOST_CMD_CAC_OP_DISABLE = 0,
+	HOST_CMD_CAC_OP_ENABLE = 1,
+	HOST_CMD_CAC_OP_CFG_GET = 2,
+	HOST_CMD_CAC_OP_CFG_SET = 3,
+};
+
+struct host_cmd_cac_change_rule {
+	__le16 arfs;
+	__sle16 threshold_change;
+} __packed;
+
+struct host_cmd_req_cac {
+	struct host_cmd_header hdr;
+	u8 opcode;
+	u8 rule_tot;
+	struct host_cmd_cac_change_rule rule[HOST_CMD_CAC_CFG_CHANGE_RULE_MAX];
+} __packed;
+
+struct host_cmd_resp_cac {
+	struct host_cmd_header hdr;
+	__le32 status;
+	u8 rule_tot;
+	struct host_cmd_cac_change_rule rule[HOST_CMD_CAC_CFG_CHANGE_RULE_MAX];
+} __packed;
+
+struct host_cmd_ocs_driver_req {
+	__le32 op_channel_freq_hz;
+	u8 op_channel_bw_mhz;
+	u8 pri_channel_bw_mhz;
+	u8 pri_1mhz_channel_index;
+} __packed;
+
+struct host_cmd_ocs_driver_resp {
+	u8 running;
+} __packed;
+
+struct host_cmd_req_ocs_driver {
+	struct host_cmd_header hdr;
+	__le32 subcmd;
+	union {
+		u8 opaque[0];
+		struct host_cmd_ocs_driver_req config;
+	};
+} __packed;
+
+struct host_cmd_resp_ocs_driver {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__le32 subcmd;
+	union {
+		u8 opaque[0];
+		struct host_cmd_ocs_driver_resp ocs_status;
+	};
+} __packed;
+
+#define HOST_CMD_IFNAMSIZ 16
+
+struct host_cmd_req_mbssid {
+	struct host_cmd_header hdr;
+	u8 max_bssid_indicator;
+	s8 transmitter_iface[HOST_CMD_IFNAMSIZ];
+} __packed;
+
+#define HOST_CMD_MESH_ID_LEN_MAX 32
+#define HOST_CMD_MESH_BEACONLESS_MODE_DISABLE 0
+#define HOST_CMD_MESH_BEACONLESS_MODE_ENABLE 1
+#define HOST_CMD_MESH_PEER_LINKS_MIN 0
+#define HOST_CMD_MESH_PEER_LINKS_MAX 10
+
+struct host_cmd_req_set_mesh_config {
+	struct host_cmd_header hdr;
+	u8 mesh_id_len;
+	u8 mesh_id[HOST_CMD_MESH_ID_LEN_MAX];
+	u8 mesh_beaconless_mode;
+	u8 max_plinks;
+} __packed;
+
+struct host_cmd_req_set_mcba_conf {
+	struct host_cmd_header hdr;
+	u8 mbca_config;
+	u8 beacon_timing_report_interval;
+	u8 min_beacon_gap_ms;
+	__le16 mbss_start_scan_duration_ms;
+	__le16 tbtt_adj_interval_ms;
+} __packed;
+
+struct host_cmd_req_dynamic_peering_config {
+	struct host_cmd_header hdr;
+	u8 enabled;
+	u8 rssi_margin;
+	__le32 blacklist_timeout;
+} __packed;
+
+#define HOST_CMD_CFG_RAW_FLAG_ENABLE BIT(0)
+#define HOST_CMD_CFG_RAW_FLAG_DELETE BIT(1)
+#define HOST_CMD_CFG_RAW_FLAG_UPDATE BIT(2)
+#define HOST_CMD_CFG_RAW_FLAG_DYNAMIC BIT(3)
+#define HOST_CMD_RAW_RESERVED_AID_DCS 2008
+#define HOST_CMD_RAW_RESERVED_AID_DOWNLINK 2009
+
+enum host_cmd_raw_tlv_tag {
+	HOST_CMD_RAW_TLV_TAG_SLOT_DEF = 0,
+	HOST_CMD_RAW_TLV_TAG_GROUP = 1,
+	HOST_CMD_RAW_TLV_TAG_START_TIME = 2,
+	HOST_CMD_RAW_TLV_TAG_PRAW = 3,
+	HOST_CMD_RAW_TLV_TAG_BCN_SPREAD = 4,
+	HOST_CMD_RAW_TLV_TAG_DYN_GLOBAL = 5,
+	HOST_CMD_RAW_TLV_TAG_DYN_CONFIG = 6,
+	HOST_CMD_RAW_TLV_TAG_LAST = 7,
+};
+
+struct host_cmd_raw_tlv_slot_def {
+	u8 tag;
+	__le32 raw_duration_us;
+	u8 num_slots;
+	u8 cross_slot_bleed;
+} __packed;
+
+struct host_cmd_raw_tlv_group {
+	u8 tag;
+	__le16 aid_start;
+	__le16 aid_end;
+} __packed;
+
+struct host_cmd_raw_tlv_start_time {
+	u8 tag;
+	__le32 start_time_us;
+} __packed;
+
+struct host_cmd_raw_tlv_praw {
+	u8 tag;
+	u8 periodicity;
+	u8 validity;
+	u8 start_offset;
+	u8 refresh_on_expiry;
+} __packed;
+
+struct host_cmd_raw_tlv_bcn_spread {
+	u8 tag;
+	__le16 max_spread;
+	__le16 nominal_sta_per_bcn;
+} __packed;
+
+struct host_cmd_raw_tlv_dyn_global {
+	u8 tag;
+	__le16 num_configs;
+	__le16 num_bcn_indexes;
+} __packed;
+
+struct host_cmd_raw_tlv_dyn_config {
+	u8 tag;
+	__le16 id;
+	__le16 index;
+	__le16 len;
+	u8 variable[];
+} __packed;
+
+union host_cmd_raw_tlvs {
+	u8 tag;
+	struct host_cmd_raw_tlv_slot_def slot_def;
+	struct host_cmd_raw_tlv_group group;
+	struct host_cmd_raw_tlv_start_time start_time;
+	struct host_cmd_raw_tlv_praw praw;
+	struct host_cmd_raw_tlv_bcn_spread bcn_spread;
+	struct host_cmd_raw_tlv_dyn_global dyn_global;
+	struct host_cmd_raw_tlv_dyn_config dyn_config;
+} __packed;
+
+struct host_cmd_req_config_raw {
+	struct host_cmd_header hdr;
+	__le32 flags;
+	__le16 id;
+	u8 variable[];
+} __packed;
+
+struct host_cmd_req_config_bss_stats {
+	struct host_cmd_header hdr;
+	u8 enable;
+	__le32 monitor_window_ms;
+} __packed;
+
+struct host_cmd_req_get_rssi {
+	struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_rssi {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__sle32 rssi0;
+	__sle32 rssi1;
+	__sle32 rssi2;
+	__sle32 rssi3;
+	__sle32 rssi4;
+	__sle32 rssi5;
+	__sle32 rssi6;
+	__sle32 rssi7;
+} __packed;
+
+#define HOST_CMD_SET_IFS_MIN_USECS 160
+
+struct host_cmd_req_set_ifs {
+	struct host_cmd_header hdr;
+	__le32 period_usecs;
+} __packed;
+
+struct host_cmd_resp_set_ifs {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_set_fem_settings {
+	struct host_cmd_header hdr;
+	__le32 tx_antenna;
+	__le32 rx_antenna;
+	__le32 lna_enabled;
+	__le32 pa_enabled;
+} __packed;
+
+struct host_cmd_resp_set_fem_settings {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_set_txop {
+	struct host_cmd_header hdr;
+	u8 min_packet_count;
+} __packed;
+
+struct host_cmd_resp_set_txop {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_set_control_response {
+	struct host_cmd_header hdr;
+	u8 direction;
+	u8 control_response_1mhz_en;
+} __packed;
+
+struct host_cmd_resp_set_control_response {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_set_periodic_cal {
+	struct host_cmd_header hdr;
+	__le32 periodic_cal_en_mask;
+} __packed;
+
+struct host_cmd_resp_set_periodic_cal {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_set_bcn_rssi_threshold {
+	struct host_cmd_header hdr;
+	u8 threshold_db;
+} __packed;
+
+struct host_cmd_resp_set_bcn_rssi_threshold {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_set_tx_pkt_lifetime_usecs {
+	struct host_cmd_header hdr;
+	__le32 lifetime_usecs;
+} __packed;
+
+struct host_cmd_resp_set_tx_pkt_lifetime_usecs {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_set_physm_watchdog {
+	struct host_cmd_header hdr;
+	u8 physm_watchdog_en;
+} __packed;
+
+struct host_cmd_req_tx_polar {
+	struct host_cmd_header hdr;
+	u8 enable;
+} __packed;
+
+struct host_cmd_evt_sta_state {
+	struct host_cmd_header hdr;
+	u8 sta_addr[HOST_CMD_MAC_ADDR_LEN];
+	__le16 aid;
+	__le16 state;
+} __packed;
+
+struct host_cmd_evt_beacon_loss {
+	struct host_cmd_header hdr;
+	__le32 num_bcns;
+} __packed;
+
+struct host_cmd_evt_sig_field_error {
+	struct host_cmd_header hdr;
+	__le64 start_timestamp;
+	__le64 end_timestamp;
+} __packed;
+
+#define HOST_CMD_UMAC_TRAFFIC_CONTROL_SOURCE_TWT BIT(0)
+#define HOST_CMD_UMAC_TRAFFIC_CONTROL_SOURCE_DUTY_CYCLE BIT(1)
+
+struct host_cmd_evt_umac_traffic_control {
+	struct host_cmd_header hdr;
+	u8 pause_data_traffic;
+	__le32 sources;
+} __packed;
+
+struct host_cmd_evt_dhcp_lease_update {
+	struct host_cmd_header hdr;
+	__le32 my_ip;
+	__le32 netmask;
+	__le32 router;
+	__le32 dns;
+} __packed;
+
+struct host_cmd_evt_ocs_done {
+	struct host_cmd_header hdr;
+	__le64 time_listen;
+	__le64 time_rx;
+	s8 noise;
+	u8 metric;
+} __packed;
+
+struct host_cmd_evt_hw_scan_done {
+	struct host_cmd_header hdr;
+	u8 aborted;
+} __packed;
+
+struct host_cmd_evt_channel_usage {
+	struct host_cmd_header hdr;
+	__le64 time_listen;
+	__le64 busy_time;
+	__le32 freq_hz;
+	u8 noise;
+	u8 bw_mhz;
+} __packed;
+
+enum host_cmd_connection_loss_reason {
+	HOST_CMD_CONNECTION_LOSS_REASON_TSF_RESET = 0,
+};
+
+struct host_cmd_evt_connection_loss {
+	struct host_cmd_header hdr;
+	__le32 reason;
+} __packed;
+
+struct host_cmd_evt_sched_scan_results {
+	struct host_cmd_header hdr;
+} __packed;
+
+enum host_cmd_cqm_rssi_threshold_event {
+	HOST_CMD_CQM_RSSI_THRESHOLD_EVENT_LOW = 0,
+	HOST_CMD_CQM_RSSI_THRESHOLD_EVENT_HIGH = 1,
+};
+
+struct host_cmd_evt_cqm_rssi_notify {
+	struct host_cmd_header hdr;
+	__sle16 rssi;
+	__le16 event;
+} __packed;
+
+struct host_cmd_evt_scan_done {
+	struct host_cmd_header hdr;
+	u8 aborted;
+} __packed;
+
+enum host_cmd_scan_result_frame {
+	HOST_CMD_SCAN_RESULT_FRAME_UNKNOWN = 0,
+	HOST_CMD_SCAN_RESULT_FRAME_BEACON = 1,
+	HOST_CMD_SCAN_RESULT_FRAME_PROBE_RESPONSE = 2,
+};
+
+struct host_cmd_evt_scan_result {
+	struct host_cmd_header hdr;
+	__le32 channel_freq_hz;
+	u8 bw_mhz;
+	u8 frame_type;
+	__sle16 rssi;
+	u8 bssid[HOST_CMD_MAC_ADDR_LEN];
+	__le16 beacon_interval;
+	__le16 capability_info;
+	__le64 tsf;
+	__le16 ies_len;
+	u8 ies[];
+} __packed;
+
+struct host_cmd_evt_connected {
+	struct host_cmd_header hdr;
+	u8 bssid[HOST_CMD_MAC_ADDR_LEN];
+	__sle16 rssi;
+	u8 padding_0[8];
+	__le16 assoc_resp_ies_len;
+	u8 assoc_resp_ies[];
+} __packed;
+
+struct host_cmd_evt_beacon_filter_match {
+	struct host_cmd_header hdr;
+	u8 padding_0[4];
+	__le32 ies_len;
+	u8 ies[];
+} __packed;
+
+struct host_cmd_req_set_capabilities {
+	struct host_cmd_header hdr;
+	struct host_cmd_mm_capabilities capabilities;
+	u8 set_caps;
+	u8 morse_mmss_offset;
+} __packed;
+
+struct host_cmd_resp_set_capabilities {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+struct host_cmd_req_set_transmission_rate {
+	struct host_cmd_header hdr;
+	__sle32 mcs_index;
+	__sle32 bandwidth_mhz;
+	__sle32 tx_80211ah_format;
+	s8 use_traveling_pilots;
+	s8 use_sgi;
+	u8 enabled;
+	s8 nss_idx;
+	s8 use_ldpc;
+	s8 use_stbc;
+} __packed;
+
+struct host_cmd_resp_set_transmission_rate {
+	struct host_cmd_header hdr;
+	__le32 status;
+} __packed;
+
+enum host_cmd_hart_id {
+	HOST_CMD_HART_ID_HOST = 0,
+	HOST_CMD_HART_ID_MAC = 1,
+	HOST_CMD_HART_ID_UPHY = 2,
+	HOST_CMD_HART_ID_LPHY = 3,
+};
+
+struct host_cmd_req_force_assert {
+	struct host_cmd_header hdr;
+	__le32 hart_id;
+} __packed;
+
+#define HOST_CMD_HOST_BLOCK_TX_FRAMES BIT(0)
+#define HOST_CMD_HOST_BLOCK_TX_CMD BIT(1)
+
+enum host_cmd_param_action {
+	HOST_CMD_PARAM_ACTION_SET = 0,
+	HOST_CMD_PARAM_ACTION_GET = 1,
+	HOST_CMD_PARAM_ACTION_LAST = 2,
+};
+
+enum host_cmd_slow_clock_mode {
+	HOST_CMD_SLOW_CLOCK_MODE_AUTO = 0,
+	HOST_CMD_SLOW_CLOCK_MODE_INTERNAL = 1,
+};
+
+enum host_cmd_param_id {
+	HOST_CMD_PARAM_ID_MAX_TRAFFIC_DELIVERY_WAIT_US = 0,
+	HOST_CMD_PARAM_ID_EXTRA_ACK_TIMEOUT_ADJUST_US = 1,
+	HOST_CMD_PARAM_ID_TX_STATUS_FLUSH_WATERMARK = 2,
+	HOST_CMD_PARAM_ID_TX_STATUS_FLUSH_MIN_AMPDU_SIZE = 3,
+	HOST_CMD_PARAM_ID_POWERSAVE_TYPE = 4,
+	HOST_CMD_PARAM_ID_SNOOZE_DURATION_ADJUST_US = 5,
+	HOST_CMD_PARAM_ID_TX_BLOCK = 6,
+	HOST_CMD_PARAM_ID_FORCED_SNOOZE_PERIOD_US = 7,
+	HOST_CMD_PARAM_ID_WAKE_ACTION_GPIO = 8,
+	HOST_CMD_PARAM_ID_WAKE_ACTION_GPIO_PULSE_MS = 9,
+	HOST_CMD_PARAM_ID_CONNECTION_MONITOR_GPIO = 10,
+	HOST_CMD_PARAM_ID_INPUT_TRIGGER_GPIO = 11,
+	HOST_CMD_PARAM_ID_INPUT_TRIGGER_MODE = 12,
+	HOST_CMD_PARAM_ID_COUNTRY = 13,
+	HOST_CMD_PARAM_ID_RTS_THRESHOLD = 14,
+	HOST_CMD_PARAM_ID_HOST_TX_BLOCK = 15,
+	HOST_CMD_PARAM_ID_MEM_RETENTION_CODE = 16,
+	HOST_CMD_PARAM_ID_NON_TIM_MODE = 17,
+	HOST_CMD_PARAM_ID_DYNAMIC_PS_TIMEOUT_MS = 18,
+	HOST_CMD_PARAM_ID_HOME_CHANNEL_DWELL_MS = 19,
+	HOST_CMD_PARAM_ID_SLOW_CLOCK_MODE = 20,
+	HOST_CMD_PARAM_ID_FRAGMENT_THRESHOLD = 21,
+	HOST_CMD_PARAM_ID_BEACON_LOSS_COUNT = 22,
+	HOST_CMD_PARAM_ID_AP_POWER_SAVE = 23,
+	HOST_CMD_PARAM_ID_BEACON_OFFLOAD = 24,
+	HOST_CMD_PARAM_ID_PROBE_RESP_OFFLOAD = 25,
+	HOST_CMD_PARAM_ID_BSS_MAX_AWAY_DURATION = 26,
+	HOST_CMD_PARAM_ID_DEFAULT_ACTIVE_SCAN_DWELL_MS = 27,
+	HOST_CMD_PARAM_ID_CTS_TO_SELF = 28,
+	HOST_CMD_PARAM_ID_CHANNELIZATION = 29,
+	HOST_CMD_PARAM_ID_LAST = 30,
+};
+
+struct host_cmd_req_get_set_generic_param {
+	struct host_cmd_header hdr;
+	__le32 param_id;
+	__le32 action;
+	__le32 flags;
+	__le32 value;
+} __packed;
+
+struct host_cmd_resp_get_set_generic_param {
+	struct host_cmd_header hdr;
+	__le32 status;
+	__le32 flags;
+	__le32 value;
+} __packed;
+
+#endif
diff --git a/drivers/net/wireless/morsemicro/mm81x/core.c b/drivers/net/wireless/morsemicro/mm81x/core.c
new file mode 100644
index 00000000000000..33548a6aea6c31
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/core.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include <linux/module.h>
+#include "core.h"
+#include "bus.h"
+#include "hif.h"
+#include "mac.h"
+
+static int mm81x_core_attach_regs(struct mm81x *mors)
+{
+	int ret = 0;
+
+	mm81x_claim_bus(mors);
+	ret = mm81x_reg32_read(mors, MM8108_REG_CHIP_ID, &mors->chip_id);
+	mm81x_release_bus(mors);
+
+	if (ret < 0) {
+		dev_err(mors->dev, "failed to read chip id %d", ret);
+		return ret;
+	}
+
+	switch (mors->chip_id) {
+	case (MM8108B2_ID):
+		mors->regs = &mm8108_regs;
+		mors->hif.ops = &mm81x_yaps_ops;
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	return ret;
+}
+
+static char *mm81x_core_get_revision_string(u32 chip_id)
+{
+	u8 chip_rev = MM81X_DEVICE_GET_CHIP_REV(chip_id);
+
+	switch (chip_rev) {
+	case MM8108B2_REV:
+		return MM8108B2_REV_STRING;
+	default:
+		return "??";
+	}
+}
+
+static void mm81x_core_init_mac_addr(struct mm81x *mors)
+{
+	int ret = mm81x_hw_otp_get_mac_addr(mors);
+
+	if (ret || !is_valid_ether_addr(mors->macaddr))
+		eth_random_addr(mors->macaddr);
+}
+
+char *mm81x_core_get_fw_path(u32 chip_id)
+{
+	return kasprintf(GFP_KERNEL,
+			 MM81X_FW_DIR "/" MM8108_FW_BASE
+				      "%s" FW_ROM_LINKED_STRING MM81X_FW_EXT,
+			 mm81x_core_get_revision_string(chip_id));
+}
+EXPORT_SYMBOL_GPL(mm81x_core_get_fw_path);
+
+struct mm81x *mm81x_core_alloc(size_t priv_size, struct device *dev)
+{
+	return mm81x_mac_alloc(priv_size, dev);
+}
+EXPORT_SYMBOL_GPL(mm81x_core_alloc);
+
+int mm81x_core_init(struct mm81x *mors)
+{
+	int ret;
+
+	set_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags);
+	set_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mors->state_flags);
+
+	mm81x_core_init_mac_addr(mors);
+
+	ret = mm81x_core_attach_regs(mors);
+	if (ret)
+		return ret;
+
+	mors->chip_wq = create_singlethread_workqueue("chip_wq");
+	if (!mors->chip_wq)
+		return -ENOMEM;
+
+	mors->net_wq = create_singlethread_workqueue("net_wq");
+	if (!mors->net_wq) {
+		ret = -ENOMEM;
+		goto err_chip_wq;
+	}
+
+	ret = mm81x_hif_init(mors);
+	if (ret)
+		goto err_wqs;
+
+	return 0;
+
+err_wqs:
+	flush_workqueue(mors->net_wq);
+	destroy_workqueue(mors->net_wq);
+
+err_chip_wq:
+	flush_workqueue(mors->chip_wq);
+	destroy_workqueue(mors->chip_wq);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(mm81x_core_init);
+
+int mm81x_core_register(struct mm81x *mors)
+{
+	return mm81x_mac_register(mors);
+}
+EXPORT_SYMBOL_GPL(mm81x_core_register);
+
+void mm81x_core_unregister(struct mm81x *mors)
+{
+	mm81x_mac_unregister(mors);
+}
+EXPORT_SYMBOL_GPL(mm81x_core_unregister);
+
+void mm81x_core_deinit(struct mm81x *mors)
+{
+	mm81x_hif_finish(mors);
+	flush_workqueue(mors->net_wq);
+	destroy_workqueue(mors->net_wq);
+	flush_workqueue(mors->chip_wq);
+	destroy_workqueue(mors->chip_wq);
+}
+EXPORT_SYMBOL_GPL(mm81x_core_deinit);
+
+void mm81x_core_free(struct mm81x *mors)
+{
+	mm81x_mac_free(mors);
+}
+EXPORT_SYMBOL_GPL(mm81x_core_free);
+
+MODULE_AUTHOR("Morse Micro");
+MODULE_DESCRIPTION("Driver support for Morse Micro MM81X core");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/wireless/morsemicro/mm81x/core.h b/drivers/net/wireless/morsemicro/mm81x/core.h
new file mode 100644
index 00000000000000..d5b6dae59bcf1c
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/core.h
@@ -0,0 +1,464 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_CORE_H_
+#define _MM81X_CORE_H_
+
+#include <net/mac80211.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/types.h>
+#include <linux/version.h>
+#include <linux/crc32.h>
+#include <linux/notifier.h>
+#include <linux/nospec.h>
+#include <linux/wait.h>
+#include "yaps.h"
+#include "yaps_hw.h"
+#include "hw.h"
+#include "fw.h"
+#include "rc.h"
+
+#define MM81X_DRIVER_SEMVER_MAJOR 56
+#define MM81X_DRIVER_SEMVER_MINOR 3
+#define MM81X_DRIVER_SEMVER_PATCH 0
+
+#define MM81X_SEMVER_GET_MAJOR(x) (((x) >> 22) & 0x3FF)
+#define MM81X_SEMVER_GET_MINOR(x) (((x) >> 10) & 0xFFF)
+#define MM81X_SEMVER_GET_PATCH(x) ((x) & 0x3FF)
+
+#define DRV_VERSION __stringify(MM81X_VERSION)
+
+#define MM8108_FW_BASE "mm8108"
+
+#define BCF_SIZE_MAX 48
+
+/* Generate a device ID from chip ID, revision, and chip type */
+#define MM81X_DEVICE_ID(chip_id, chip_rev, chip_type) \
+	((chip_id) | ((chip_rev) << 8) | ((chip_type) << 12))
+
+/* Get constituents of the device ID */
+#define MM81X_DEVICE_GET_CHIP_ID(device_id) ((device_id) & 0xff)
+#define MM81X_DEVICE_GET_CHIP_REV(device_id) ((((device_id) >> 8) & 0xf))
+#define MM81X_DEVICE_GET_CHIP_TYPE(device_id) ((((device_id) >> 12) & 0xf))
+
+#define KHZ100_TO_MHZ(x) ((x) / 10)
+#define KHZ100_TO_KHZ(freq) ((freq) * 100)
+#define KHZ100_TO_HZ(freq) ((freq) * 100000)
+
+#define QDBM_TO_MBM(gain) (((gain) * 100) >> 2)
+#define MBM_TO_QDBM(gain) (((gain) << 2) / 100)
+#define QDBM_TO_DBM(gain) ((gain) / 4)
+
+#define BPS_TO_KBPS(x) ((x) / 1000)
+
+#define NSS_IDX_TO_NSS(x) ((x) + 1)
+#define NSS_TO_NSS_IDX(x) ((x) - 1)
+
+#define ROUND_BYTES_TO_WORD(_nbytes) \
+	(((_nbytes) + 3) & ~((typeof(_nbytes))0x03))
+
+struct mm81x_bus_ops;
+struct mm81x_hif_ops;
+
+#define MM81X_CAPS_MAX_FW_VAL (128)
+
+/* Max number of interfaces */
+#define MM81X_MAX_IF (2)
+
+enum mm81x_caps_flags {
+	MM81X_CAPS_FW_START = 0,
+	MM81X_CAPS_2MHZ = MM81X_CAPS_FW_START,
+	MM81X_CAPS_4MHZ,
+	MM81X_CAPS_8MHZ,
+	MM81X_CAPS_16MHZ,
+	MM81X_CAPS_SGI,
+	MM81X_CAPS_S1G_LONG,
+	MM81X_CAPS_TRAVELING_PILOT_ONE_STREAM,
+	MM81X_CAPS_TRAVELING_PILOT_TWO_STREAM,
+	MM81X_CAPS_MU_BEAMFORMEE,
+	MM81X_CAPS_MU_BEAMFORMER,
+	MM81X_CAPS_RD_RESPONDER,
+	MM81X_CAPS_STA_TYPE_SENSOR,
+	MM81X_CAPS_STA_TYPE_NON_SENSOR,
+	MM81X_CAPS_GROUP_AID,
+	MM81X_CAPS_NON_TIM,
+	MM81X_CAPS_TIM_ADE,
+	MM81X_CAPS_BAT,
+	MM81X_CAPS_DYNAMIC_AID,
+	MM81X_CAPS_UPLINK_SYNC,
+	MM81X_CAPS_FLOW_CONTROL,
+	MM81X_CAPS_AMPDU,
+	MM81X_CAPS_AMSDU,
+	MM81X_CAPS_1MHZ_CONTROL_RESPONSE_PREAMBLE,
+	MM81X_CAPS_PAGE_SLICING,
+	MM81X_CAPS_RAW,
+	MM81X_CAPS_MCS8,
+	MM81X_CAPS_MCS9,
+	MM81X_CAPS_ASYMMETRIC_BA_SUPPORT,
+	MM81X_CAPS_DAC,
+	MM81X_CAPS_CAC,
+	MM81X_CAPS_TXOP_SHARING_IMPLICIT_ACK,
+	MM81X_CAPS_NDP_PSPOLL,
+	MM81X_CAPS_FRAGMENT_BA,
+	MM81X_CAPS_OBSS_MITIGATION,
+	MM81X_CAPS_TMP_PS_MODE_SWITCH,
+	MM81X_CAPS_SECTOR_TRAINING,
+	MM81X_CAPS_UNSOLICIT_DYNAMIC_AID,
+	MM81X_CAPS_NDP_BEAMFORMING_REPORT,
+	MM81X_CAPS_MCS_NEGOTIATION,
+	MM81X_CAPS_DUPLICATE_1MHZ,
+	MM81X_CAPS_TACK_AS_PSPOLL,
+	MM81X_CAPS_PV1,
+	MM81X_CAPS_TWT_RESPONDER,
+	MM81X_CAPS_TWT_REQUESTER,
+	MM81X_CAPS_BDT,
+	MM81X_CAPS_TWT_GROUPING,
+	MM81X_CAPS_LINK_ADAPTATION_WO_NDP_CMAC,
+	MM81X_CAPS_LONG_MPDU,
+	MM81X_CAPS_TXOP_SECTORIZATION,
+	MM81X_CAPS_GROUP_SECTORIZATION,
+	MM81X_CAPS_HTC_VHT,
+	MM81X_CAPS_HTC_VHT_MFB,
+	MM81X_CAPS_HTC_VHT_MRQ,
+	MM81X_CAPS_2SS,
+	MM81X_CAPS_3SS,
+	MM81X_CAPS_4SS,
+	MM81X_CAPS_SU_BEAMFORMEE,
+	MM81X_CAPS_SU_BEAMFORMER,
+	MM81X_CAPS_RX_STBC,
+	MM81X_CAPS_TX_STBC,
+	MM81X_CAPS_RX_LDPC,
+	MM81X_CAPS_HW_FRAGMENT,
+
+	MM81X_CAPS_FW_END = MM81X_CAPS_MAX_FW_VAL,
+	MM81X_CAPS_LAST = MM81X_CAPS_FW_END,
+};
+
+struct mm81x_fw_caps {
+	u32 flags[FW_CAPABILITIES_FLAGS_WIDTH];
+	u8 ampdu_mss;
+	u8 beamformee_sts_capability;
+	u8 number_sounding_dimensions;
+	u8 maximum_ampdu_length_exponent;
+	u8 mm81x_mmss_offset;
+};
+
+#define MM81X_FW_SUPP(MM81X_CAPS, CAPABILITY) \
+	mm81x_caps_supported(MM81X_CAPS, MM81X_CAPS_##CAPABILITY)
+
+static inline bool mm81x_caps_supported(struct mm81x_fw_caps *caps,
+					enum mm81x_caps_flags flag)
+{
+	const unsigned long *flags_ptr = (unsigned long *)caps->flags;
+
+	return test_bit(flag, flags_ptr);
+}
+
+struct mm81x_ps {
+	u32 wakers;
+	bool enable;
+	bool suspended;
+	/* PS state lock */
+	struct mutex lock;
+	struct delayed_work delayed_eval_work;
+};
+
+enum mm81x_page_aci {
+	MM81X_ACI_BE = 0,
+	MM81X_ACI_BK = 1,
+	MM81X_ACI_VI = 2,
+	MM81X_ACI_VO = 3,
+};
+
+enum mm81x_qos_tid_up_index {
+	MM81X_QOS_TID_UP_BK = 1,
+	MM81X_QOS_TID_UP_XX = 2,
+	MM81X_QOS_TID_UP_BE = 0,
+	MM81X_QOS_TID_UP_EE = 3,
+	MM81X_QOS_TID_UP_CL = 4,
+	MM81X_QOS_TID_UP_VI = 5,
+	MM81X_QOS_TID_UP_VO = 6,
+	MM81X_QOS_TID_UP_NC = 7,
+
+	MM81X_QOS_TID_UP_LOWEST = MM81X_QOS_TID_UP_BK,
+	MM81X_QOS_TID_UP_HIGHEST = MM81X_QOS_TID_UP_NC
+};
+
+struct mm81x_sw_version {
+	u8 major;
+	u8 minor;
+	u8 patch;
+};
+
+struct mm81x_sta {
+	const struct ieee80211_vif *vif;
+	u8 addr[ETH_ALEN];
+	enum ieee80211_sta_state state;
+	bool tid_tx[IEEE80211_NUM_TIDS];
+	bool tid_start_tx[IEEE80211_NUM_TIDS];
+	u8 tid_params[IEEE80211_NUM_TIDS];
+	int max_bw_mhz;
+	struct mm81x_rc_sta rc;
+	struct mmrc_rate last_sta_tx_rate;
+	s16 avg_rssi;
+	bool tx_ps_filter_en;
+};
+
+struct mm81x_vif {
+	struct mm81x *mors;
+	u16 id;
+
+	union {
+		struct {
+			bool is_assoc;
+		} sta;
+		struct {
+			u32 num_stas;
+			struct work_struct beacon_work;
+		} ap;
+	} u;
+};
+
+struct mm81x_stale_tx_status {
+	/* Stale Tx lock */
+	spinlock_t lock;
+	struct timer_list timer;
+};
+
+struct mcast_filter {
+	u8 count;
+	/*
+	 * Integer representation of the last four bytes of a multicast MAC
+	 * address. The first two bytes are always 0x0100 (IPv4) or 0x3333
+	 * (IPv6).
+	 */
+	__le32 addr_list[];
+};
+
+enum mm81x_hw_scan_op {
+	MM81X_HW_SCAN_OP_START,
+	MM81X_HW_SCAN_OP_STOP,
+};
+
+struct mm81x_hw_scan_params {
+	struct ieee80211_hw *hw;
+
+	/* vif which initiated the scan */
+	struct ieee80211_vif *vif;
+	bool has_directed_ssid;
+	u32 dwell_time_ms;
+	u32 dwell_on_home_ms;
+	enum mm81x_hw_scan_op operation;
+	bool store;
+	struct sk_buff *probe_req;
+	u16 num_chans;
+	u16 allocated_chans;
+
+	struct {
+		struct ieee80211_channel *channel;
+		/* Index into @ref powers_qdbm for the power of this channel */
+		u8 power_idx;
+	} *channels;
+
+	s32 *powers_qdbm;
+	u8 n_powers;
+};
+
+enum mm81x_hw_scan_state {
+	HW_SCAN_STATE_IDLE,
+	HW_SCAN_STATE_RUNNING,
+	HW_SCAN_STATE_ABORTING,
+};
+
+struct mm81x_hw_scan {
+	enum mm81x_hw_scan_state state;
+	struct completion scan_done;
+	struct mm81x_hw_scan_params *params;
+	struct delayed_work timeout;
+	u32 home_dwell_ms;
+};
+
+enum mm81x_hif_event_flags {
+	MM81X_HIF_EVT_RX_PEND,
+	MM81X_HIF_EVT_PAGE_RETURN_PEND,
+	MM81X_HIF_EVT_TX_COMMAND_PEND,
+	MM81X_HIF_EVT_TX_BEACON_PEND,
+	MM81X_HIF_EVT_TX_MGMT_PEND,
+	MM81X_HIF_EVT_TX_DATA_PEND,
+	MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND,
+	MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND,
+	MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND,
+	MM81X_HIF_EVT_UPDATE_HW_CLOCK_REFERENCE,
+};
+
+enum mm81x_state_flags {
+	MM81X_STATE_CHIP_UNRESPONSIVE,
+	MM81X_STATE_DATA_QS_STOPPED,
+	MM81X_STATE_DATA_TX_STOPPED,
+	MM81X_STATE_REGDOM_SET_BY_USER,
+	MM81X_STATE_REGDOM_SET_BY_OTP,
+	MM81X_STATE_RELOAD_FW_AFTER_START,
+	MM81X_STATE_HOST_TO_CHIP_TX_BLOCKED,
+	MM81X_STATE_HOST_TO_CHIP_CMD_BLOCKED,
+};
+
+#define MM81X_COUNTRY_LEN (3)
+#define INVALID_VIF_INDEX 0xFF
+
+struct mm81x {
+	u32 chip_id;
+	u32 host_table_ptr;
+
+	/* Refer to @enum mm81x_bus_type */
+	u32 bus_type;
+	u32 bcf_address;
+
+	/*
+	 * Parsed from the release tag, which should be in the format
+	 * 'rel_<major>_<minor>_<patch>'. If the tag is not in this format
+	 * then corresponding version field will be 0.
+	 */
+	struct mm81x_sw_version sw_ver;
+	u8 macaddr[ETH_ALEN];
+	u8 country[MM81X_COUNTRY_LEN];
+
+	/* Mask of type @enum host_table_firmware_flags */
+	u32 firmware_flags;
+	struct mm81x_fw_caps fw_caps;
+	bool started;
+	bool chip_was_reset;
+	struct wiphy *wiphy;
+	struct mm81x_hw_scan hw_scan;
+	struct ieee80211_hw *hw;
+	struct device *dev;
+
+	struct ieee80211_vif __rcu *vifs[MM81X_MAX_IF];
+
+	/* @mm81x_state_flags */
+	unsigned long state_flags;
+
+	u16 cmd_seq;
+	struct completion *cmd_comp;
+	/* Serialises commands */
+	struct mutex cmd_lock;
+
+	/* Serialises command completion */
+	struct mutex cmd_wait;
+
+	const struct mm81x_regs *regs;
+
+	struct {
+		union {
+			struct mm81x_yaps yaps;
+		} u;
+		const struct mm81x_hif_ops *ops;
+		/* See @enum mm81x_hif_event_flags for values */
+		unsigned long event_flags;
+		bool validate_skb_checksum;
+	} hif;
+
+	struct workqueue_struct *chip_wq;
+	struct work_struct hif_work;
+	struct work_struct usb_irq_work;
+	struct mm81x_stale_tx_status stale_status;
+	bool config_ps;
+	struct mm81x_ps ps;
+
+	/* Tx power in mBm received from the FW before association */
+	s32 tx_power_mbm;
+	s32 tx_max_power_mbm;
+
+	const struct mm81x_bus_ops *bus_ops;
+	struct mm81x_rc mrc;
+	int rts_threshold;
+	struct workqueue_struct *net_wq;
+	struct work_struct tx_stale_work;
+	wait_queue_head_t tx_empty_waitq;
+
+	struct cfg80211_chan_def chandef;
+	struct mcast_filter *mcast_filter;
+	atomic_t num_bcn_vifs;
+	unsigned long beacon_irqs_enabled;
+	u8 drv_priv[] __aligned(sizeof(void *));
+};
+
+/* Map from mac80211 queue to Morse ACI value for page metadata */
+static inline u8 map_mac80211q_2_mm81x_aci(u16 mac80211queue)
+{
+	switch (mac80211queue) {
+	case IEEE80211_AC_VO:
+		return MM81X_ACI_VO;
+	case IEEE80211_AC_VI:
+		return MM81X_ACI_VI;
+	case IEEE80211_AC_BK:
+		return MM81X_ACI_BK;
+	default:
+		return MM81X_ACI_BE;
+	}
+}
+
+static inline enum mm81x_page_aci
+dot11_tid_to_ac(enum mm81x_qos_tid_up_index tid)
+{
+	switch (tid) {
+	case MM81X_QOS_TID_UP_BK:
+	case MM81X_QOS_TID_UP_XX:
+		return MM81X_ACI_BK;
+	case MM81X_QOS_TID_UP_CL:
+	case MM81X_QOS_TID_UP_VI:
+		return MM81X_ACI_VI;
+	case MM81X_QOS_TID_UP_VO:
+	case MM81X_QOS_TID_UP_NC:
+		return MM81X_ACI_VO;
+	case MM81X_QOS_TID_UP_BE:
+	case MM81X_QOS_TID_UP_EE:
+	default:
+		return MM81X_ACI_BE;
+	}
+}
+
+static inline bool mm81x_is_data_tx_allowed(struct mm81x *mors)
+{
+	return !test_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags) &&
+	       !test_bit(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND,
+			 &mors->hif.event_flags);
+}
+
+static inline struct ieee80211_vif *
+mm81x_vif_to_ieee80211_vif(struct mm81x_vif *mors_vif)
+{
+	return container_of((void *)mors_vif, struct ieee80211_vif, drv_priv);
+}
+
+static inline struct mm81x_vif *
+ieee80211_vif_to_mors_vif(struct ieee80211_vif *vif)
+{
+	return (struct mm81x_vif *)vif->drv_priv;
+}
+
+static inline struct mm81x *mm81x_vif_to_mors(struct mm81x_vif *mors_vif)
+{
+	return mors_vif->mors;
+}
+
+static inline u32 mm81x_generate_cssid(const u8 *ssid, u8 len)
+{
+	return ~crc32(~0, ssid, len);
+}
+
+int mm81x_beacon_init(struct mm81x_vif *mors_vif);
+void mm81x_beacon_finish(struct mm81x_vif *mors_vif);
+void mm81x_beacon_irq_handle(struct mm81x *mors, u32 status);
+char *mm81x_core_get_fw_path(u32 chip_id);
+struct mm81x *mm81x_core_alloc(size_t priv_size, struct device *dev);
+int mm81x_core_init(struct mm81x *mors);
+int mm81x_core_register(struct mm81x *mors);
+void mm81x_core_unregister(struct mm81x *mors);
+void mm81x_core_deinit(struct mm81x *mors);
+void mm81x_core_free(struct mm81x *mors);
+
+#endif /* !_MM81X_MM81X_H_ */
diff --git a/drivers/net/wireless/morsemicro/mm81x/fw.c b/drivers/net/wireless/morsemicro/mm81x/fw.c
new file mode 100644
index 00000000000000..337b809571d2bb
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/fw.c
@@ -0,0 +1,732 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include <linux/kernel.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <linux/string_choices.h>
+#include <net/mac80211.h>
+#include <linux/elf.h>
+#include <linux/crc32.h>
+#include "fw.h"
+#include "mac.h"
+#include "bus.h"
+
+/*
+ * Maximum wait time (microseconds) for firmware to boot (for host table
+ * pointer to be available)
+ */
+#define HOST_TABLE_PTR_POLL_TIMEOUT_US 1200000
+#define HOST_TABLE_PTR_POLL_PERIOD_US 10000
+
+/* Number of times to attempt flashing FW */
+#define FW_FLASH_ATTEMPT_COUNT 3
+
+static int mm81x_fw_get_header(const u8 *data, Elf32_Ehdr *ehdr)
+{
+	const struct mm81x_elf32_ehdr *p =
+		(const struct mm81x_elf32_ehdr *)data;
+
+	/* Magic check */
+	if (p->e_ident[EI_MAG0] != ELFMAG0 || p->e_ident[EI_MAG1] != ELFMAG1 ||
+	    p->e_ident[EI_MAG2] != ELFMAG2 || p->e_ident[EI_MAG3] != ELFMAG3)
+		return -EINVAL;
+
+	/* elf32 and little endian */
+	if (p->e_ident[EI_DATA] != ELFDATA2LSB ||
+	    p->e_ident[EI_CLASS] != ELFCLASS32)
+		return -EINVAL;
+
+	ehdr->e_phoff = le32_to_cpu(p->e_phoff);
+	ehdr->e_phentsize = le16_to_cpu(p->e_phentsize);
+	ehdr->e_phnum = le16_to_cpu(p->e_phnum);
+	ehdr->e_shoff = le32_to_cpu(p->e_shoff);
+	ehdr->e_shentsize = le16_to_cpu(p->e_shentsize);
+	ehdr->e_shnum = le16_to_cpu(p->e_shnum);
+	ehdr->e_shstrndx = le16_to_cpu(p->e_shstrndx);
+	ehdr->e_entry = le32_to_cpu(p->e_entry);
+
+	return 0;
+}
+
+static void mm81x_fw_parse_info(struct mm81x *mors, const u8 *data, int length)
+{
+	const struct mm81x_fw_info_tlv *tlv =
+		(const struct mm81x_fw_info_tlv *)data;
+
+	while ((u8 *)tlv < (data + length)) {
+		switch (le16_to_cpu(tlv->type)) {
+		case MM81X_FW_INFO_TLV_BCF_ADDR:
+			mors->bcf_address = get_unaligned_le32(tlv->val);
+			break;
+		default:
+			break;
+		}
+		tlv = (const struct mm81x_fw_info_tlv *)((u8 *)tlv +
+							 le16_to_cpu(
+								 tlv->length) +
+							 sizeof(*tlv));
+	}
+}
+
+static int mm81x_fw_get_section_header(const u8 *data, Elf32_Ehdr *ehdr,
+				       Elf32_Shdr *shdr, int i)
+{
+	const struct mm81x_elf32_shdr *p =
+		(void *)(data + ehdr->e_shoff + (i * ehdr->e_shentsize));
+
+	shdr->sh_name = le32_to_cpu(p->sh_name);
+	shdr->sh_type = le32_to_cpu(p->sh_type);
+	shdr->sh_offset = le32_to_cpu(p->sh_offset);
+	shdr->sh_addr = le32_to_cpu(p->sh_addr);
+	shdr->sh_size = le32_to_cpu(p->sh_size);
+	shdr->sh_flags = le32_to_cpu(p->sh_flags);
+
+	return 0;
+}
+
+static int mm81x_fw_set_boot_addr(struct mm81x *mors, uint32_t addr)
+{
+	int status;
+
+	dev_dbg(mors->dev, "Overwriting boot address to 0x%x", addr);
+	mm81x_claim_bus(mors);
+	status = mm81x_reg32_write(mors, MM81X_REG_BOOT_ADDR(mors), addr);
+	mm81x_release_bus(mors);
+	return status;
+}
+
+static int mm81x_fw_load_fw(struct mm81x *mors, const struct firmware *fw)
+{
+	int i;
+	int ret = 0;
+	Elf32_Ehdr ehdr;
+	Elf32_Phdr phdr;
+	Elf32_Shdr shdr;
+	Elf32_Shdr sh_strtab;
+	const char *sh_strs;
+
+	u8 *fw_buf = devm_kmalloc(mors->dev, ROUND_BYTES_TO_WORD(fw->size),
+				  GFP_KERNEL);
+
+	if (!fw_buf)
+		return -ENOMEM;
+
+	if (mm81x_fw_get_header(fw->data, &ehdr)) {
+		dev_err(mors->dev, "Wrong file format");
+		return -EINVAL;
+	}
+
+	if (mm81x_fw_get_section_header(fw->data, &ehdr, &sh_strtab,
+					ehdr.e_shstrndx)) {
+		dev_err(mors->dev, "Invalid firmware. Missing string table");
+		return -ENOENT;
+	}
+
+	sh_strs = (const char *)fw->data + sh_strtab.sh_offset;
+
+	for (i = 0; i < ehdr.e_phnum; i++) {
+		int status;
+		int address;
+		const struct mm81x_elf32_phdr *p =
+			(void *)(fw->data + ehdr.e_phoff +
+				 i * ehdr.e_phentsize);
+
+		phdr.p_type = le32_to_cpu(p->p_type);
+		phdr.p_offset = le32_to_cpu(p->p_offset);
+		phdr.p_paddr = le32_to_cpu(p->p_paddr);
+		phdr.p_filesz = le32_to_cpu(p->p_filesz);
+		phdr.p_memsz = le32_to_cpu(p->p_memsz);
+
+		address = phdr.p_paddr;
+
+		if (phdr.p_type != PT_LOAD || !phdr.p_memsz)
+			continue;
+
+		if (phdr.p_filesz && phdr.p_offset &&
+		    (phdr.p_offset + phdr.p_filesz) < fw->size) {
+			u32 padded_size = ROUND_BYTES_TO_WORD(phdr.p_filesz);
+
+			memcpy(fw_buf, fw->data + phdr.p_offset, padded_size);
+			/* Set padding to 0xff */
+			memset(fw_buf + phdr.p_filesz, 0xff,
+			       padded_size - phdr.p_filesz);
+			mm81x_claim_bus(mors);
+			status = mm81x_dm_write(mors, address, fw_buf,
+						padded_size);
+			mm81x_release_bus(mors);
+			if (status) {
+				ret = -EIO;
+				break;
+			}
+		}
+	}
+
+	for (i = 0; i < ehdr.e_shnum; i++) {
+		if (mm81x_fw_get_section_header(fw->data, &ehdr, &shdr, i))
+			continue;
+
+		/* This is the firmware info. Parse it */
+		if (!strncmp(sh_strs + shdr.sh_name, ".fw_info",
+			     sizeof(".fw_info")))
+			mm81x_fw_parse_info(mors, fw->data + shdr.sh_offset,
+					    shdr.sh_size);
+	}
+
+	if (ehdr.e_entry)
+		ret = mm81x_fw_set_boot_addr(mors, ehdr.e_entry);
+
+	devm_kfree(mors->dev, fw_buf);
+	return ret;
+}
+
+static int __mm81x_fw_load_bcf(struct mm81x *mors, unsigned int addr,
+			       const void *src, size_t src_len, u8 *scratch,
+			       size_t scratch_cap)
+{
+	size_t rounded = ROUND_BYTES_TO_WORD(src_len);
+	int st;
+
+	if (rounded > scratch_cap)
+		return -EINVAL;
+	if (rounded > BCF_DATABASE_SIZE)
+		return -EFBIG;
+
+	memcpy(scratch, src, src_len);
+	if (rounded > src_len)
+		memset(scratch + src_len, 0xff, rounded - src_len);
+
+	mm81x_claim_bus(mors);
+	st = mm81x_dm_write(mors, addr, scratch, rounded);
+	mm81x_release_bus(mors);
+
+	return st ? -EIO : 0;
+}
+
+static int mm81x_fw_load_bcf(struct mm81x *mors, const struct firmware *bcf,
+			     unsigned int bcf_address)
+{
+	int i, ret = 0;
+	size_t reg_prefix_len, cfg_len_rounded = 0, reg_len_rounded;
+	Elf32_Ehdr ehdr;
+	Elf32_Shdr shdr, sh_strtab;
+	const char *sh_strs, *reg_prefix = ".regdom_", *reg_src;
+	size_t reg_len;
+	u8 *bcf_buf;
+
+	bcf_buf = devm_kmalloc(mors->dev, ROUND_BYTES_TO_WORD(bcf->size),
+			       GFP_KERNEL);
+	if (!bcf_buf)
+		return -ENOMEM;
+
+	if (mm81x_fw_get_header(bcf->data, &ehdr)) {
+		dev_err(mors->dev, "Wrong file format");
+		ret = -EINVAL;
+		goto out_free;
+	}
+
+	if (mm81x_fw_get_section_header(bcf->data, &ehdr, &sh_strtab,
+					ehdr.e_shstrndx)) {
+		dev_err(mors->dev, "Invalid BCF - missing string table");
+		ret = -ENOENT;
+		goto out_free;
+	}
+
+	sh_strs = (const char *)bcf->data + sh_strtab.sh_offset;
+	reg_prefix_len = strlen(reg_prefix);
+
+	for (i = 0; i < ehdr.e_shnum; i++) {
+		if (mm81x_fw_get_section_header(bcf->data, &ehdr, &shdr, i))
+			continue;
+		if (strcmp(sh_strs + shdr.sh_name, ".board_config"))
+			continue;
+
+		cfg_len_rounded = ROUND_BYTES_TO_WORD(shdr.sh_size);
+		dev_dbg(mors->dev,
+			"Write BCF board_config - addr 0x%x size %zu",
+			bcf_address, cfg_len_rounded);
+
+		ret = __mm81x_fw_load_bcf(mors, bcf_address,
+					  bcf->data + shdr.sh_offset,
+					  shdr.sh_size, bcf_buf,
+					  ROUND_BYTES_TO_WORD(bcf->size));
+		if (ret)
+			goto out_free;
+
+		bcf_address += cfg_len_rounded;
+		break;
+	}
+
+	ret = -EINVAL;
+	for (; i < ehdr.e_shnum; i++) {
+		if (mm81x_fw_get_section_header(bcf->data, &ehdr, &shdr, i))
+			continue;
+		if (strncmp(sh_strs + shdr.sh_name, reg_prefix, reg_prefix_len))
+			continue;
+		if (strncmp(sh_strs + shdr.sh_name + reg_prefix_len,
+			    mors->country, 2))
+			continue;
+
+		reg_src = bcf->data + shdr.sh_offset;
+		reg_len = shdr.sh_size;
+		dev_dbg(mors->dev, "Write BCF %s - addr 0x%x size %zu",
+			sh_strs + shdr.sh_name, bcf_address,
+			ROUND_BYTES_TO_WORD(reg_len));
+		ret = 0;
+		break;
+	}
+
+	if (ret)
+		goto out_free;
+
+	reg_len_rounded = ROUND_BYTES_TO_WORD(reg_len);
+	if ((cfg_len_rounded + reg_len_rounded) > BCF_DATABASE_SIZE) {
+		ret = -EFBIG;
+		goto out_free;
+	}
+
+	ret = __mm81x_fw_load_bcf(mors, bcf_address, reg_src, reg_len, bcf_buf,
+				  ROUND_BYTES_TO_WORD(bcf->size));
+
+out_free:
+	devm_kfree(mors->dev, bcf_buf);
+	return ret;
+}
+
+static void mm81x_fw_clear_aon(struct mm81x *mors)
+{
+	int idx;
+	u8 count = MM81X_REG_AON_COUNT(mors);
+	u32 address = MM81X_REG_AON_ADDR(mors);
+
+	for (idx = 0; idx < count; idx++, address += 4) {
+		if (mors->bus_type == MM81X_BUS_TYPE_USB && idx == 0)
+			/* Keep the USB power domain enabled in AON. */
+			mm81x_reg32_write(mors, address,
+					  MM81X_REG_AON_USB_RESET(mors));
+		else
+			/* clear AON */
+			mm81x_reg32_write(mors, address, 0x0);
+	}
+
+	mm81x_hw_toggle_aon_latch(mors);
+}
+
+static void mm81x_fw_trigger(struct mm81x *mors)
+{
+	const unsigned int wait_after_msi_trigger_ms = 1;
+
+	mm81x_claim_bus(mors);
+	/*
+	 * If not coming from a full reset, some AON flags may be latched.
+	 * Make sure to clear any hanging AON bits (can affect booting).
+	 */
+	mm81x_fw_clear_aon(mors);
+
+	if (MM81X_REG_CLK_CTRL(mors))
+		mm81x_reg32_write(mors, MM81X_REG_CLK_CTRL(mors),
+				  MM81X_REG_CLK_CTRL_VALUE(mors));
+
+	mm81x_reg32_write(mors, MM81X_REG_MSI(mors),
+			  MM81X_REG_MSI_HOST_INT(mors));
+	mm81x_release_bus(mors);
+
+	/* Give the chip a chance to boot */
+	mdelay(wait_after_msi_trigger_ms);
+}
+
+static int mm81x_fw_verify_magic(struct mm81x *mors)
+{
+	int ret = 0;
+	int magic = ~MM81X_REG_HOST_MAGIC_VALUE(mors);
+
+	mm81x_claim_bus(mors);
+	mm81x_reg32_read(mors,
+			 mors->host_table_ptr +
+				 offsetof(struct host_table, magic_number),
+			 &magic);
+
+	if (magic != MM81X_REG_HOST_MAGIC_VALUE(mors)) {
+		dev_err(mors->dev, "FW magic mismatch 0x%08x:0x%08x",
+			MM81X_REG_HOST_MAGIC_VALUE(mors), magic);
+		ret = -EIO;
+	}
+
+	mm81x_release_bus(mors);
+	return ret;
+}
+
+static int mm81x_fw_get_flags(struct mm81x *mors)
+{
+	int ret = 0;
+	int fw_flags = 0;
+
+	mm81x_claim_bus(mors);
+	ret = mm81x_reg32_read(mors,
+			       mors->host_table_ptr +
+				       offsetof(struct host_table,
+						firmware_flags),
+			       &fw_flags);
+	mors->firmware_flags = fw_flags;
+	mm81x_release_bus(mors);
+
+	return ret;
+}
+
+static int mm81x_fw_check_compatibility(struct mm81x *mors)
+{
+	int ret = 0;
+	u32 fw_version;
+	u32 major;
+	u32 minor;
+	u32 patch;
+
+	mm81x_claim_bus(mors);
+	ret = mm81x_reg32_read(mors,
+			       mors->host_table_ptr +
+				       offsetof(struct host_table,
+						fw_version_number),
+			       &fw_version);
+	mm81x_release_bus(mors);
+
+	major = MM81X_SEMVER_GET_MAJOR(fw_version);
+	minor = MM81X_SEMVER_GET_MINOR(fw_version);
+	patch = MM81X_SEMVER_GET_PATCH(fw_version);
+
+	/* Firmware on device must be recent enough for driver */
+	if (ret == 0 && major != HOST_CMD_SEMVER_MAJOR) {
+		dev_err(mors->dev,
+			"Incompatible FW version: (Driver) %d.%d.%d, (Chip) %d.%d.%d\n",
+			HOST_CMD_SEMVER_MAJOR, HOST_CMD_SEMVER_MINOR,
+			HOST_CMD_SEMVER_PATCH, major, minor, patch);
+		ret = -EPERM;
+	} else if (ret == 0 && minor != HOST_CMD_SEMVER_MINOR) {
+		dev_warn(
+			mors->dev,
+			"FW version mismatch, some features might not be supported: (Driver) %d.%d.%d, (Chip) %d.%d.%d",
+			HOST_CMD_SEMVER_MAJOR, HOST_CMD_SEMVER_MINOR,
+			HOST_CMD_SEMVER_PATCH, major, minor, patch);
+	}
+
+	return ret;
+}
+
+static int mm81x_fw_invalidate_host_ptr(struct mm81x *mors)
+{
+	int ret;
+
+	mors->host_table_ptr = 0;
+	mm81x_claim_bus(mors);
+	ret = mm81x_reg32_write(mors, MM81X_REG_HOST_MANIFEST_PTR(mors), 0);
+	mm81x_release_bus(mors);
+	return ret;
+}
+
+static int mm81x_fw_get_host_table_ptr(struct mm81x *mors)
+{
+	int ret, err;
+
+	mm81x_claim_bus(mors);
+	ret = read_poll_timeout(mm81x_reg32_read, err,
+				err || mors->host_table_ptr,
+				HOST_TABLE_PTR_POLL_PERIOD_US,
+				HOST_TABLE_PTR_POLL_TIMEOUT_US, false, mors,
+				MM81X_REG_HOST_MANIFEST_PTR(mors),
+				&mors->host_table_ptr);
+	mm81x_release_bus(mors);
+
+	return ret ? ret : err;
+}
+
+static int mm81x_fw_read_ext_host_table(struct mm81x *mors,
+					struct ext_host_tbl **ext_host_table)
+{
+	int ret = 0;
+	u32 host_tbl_ptr = mors->host_table_ptr;
+	u32 ext_host_tbl_ptr;
+	u32 ext_host_tbl_ptr_addr =
+		host_tbl_ptr + offsetof(struct host_table, ext_host_tbl_addr);
+	u32 ext_host_tbl_len;
+	u32 ext_host_tbl_len_ptr_addr;
+	struct ext_host_tbl *host_tbl = NULL;
+
+	mm81x_claim_bus(mors);
+	ret = mm81x_reg32_read(mors, ext_host_tbl_ptr_addr, &ext_host_tbl_ptr);
+	if (ret)
+		goto exit;
+
+	if (!ext_host_tbl_ptr) {
+		ret = -ENXIO;
+		goto exit;
+	}
+
+	ext_host_tbl_len_ptr_addr =
+		ext_host_tbl_ptr +
+		offsetof(struct ext_host_tbl, ext_host_tbl_length);
+
+	ret = mm81x_reg32_read(mors, ext_host_tbl_len_ptr_addr,
+			       &ext_host_tbl_len);
+	if (ret)
+		goto exit;
+
+	ext_host_tbl_len = ROUND_BYTES_TO_WORD(ext_host_tbl_len);
+	if (WARN_ON(ext_host_tbl_len == 0 || ext_host_tbl_len > INT_MAX)) {
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	host_tbl = kmalloc(ext_host_tbl_len, GFP_KERNEL);
+	if (!host_tbl) {
+		ret = -ENOMEM;
+		goto exit;
+	}
+
+	ret = mm81x_dm_read(mors, ext_host_tbl_ptr, (u8 *)host_tbl,
+			    (int)ext_host_tbl_len);
+	if (ret)
+		goto exit;
+
+	mm81x_release_bus(mors);
+	*ext_host_table = host_tbl;
+	return ret;
+
+exit:
+	mm81x_release_bus(mors);
+	kfree(host_tbl);
+	return ret;
+}
+
+static void mm81x_fw_update_capabilities(struct mm81x *mors,
+					 struct ext_host_tbl_s1g_caps *caps)
+{
+	int i;
+
+	for (i = 0; i < FW_CAPABILITIES_FLAGS_WIDTH; i++) {
+		mors->fw_caps.flags[i] = le32_to_cpu(caps->flags[i]);
+		dev_dbg(mors->dev, "Firmware Manifest Flags%d: 0x%x", i,
+			le32_to_cpu(caps->flags[i]));
+	}
+	mors->fw_caps.ampdu_mss = caps->ampdu_mss;
+	mors->fw_caps.mm81x_mmss_offset = caps->mm81x_mmss_offset;
+	mors->fw_caps.beamformee_sts_capability =
+		caps->beamformee_sts_capability;
+	mors->fw_caps.maximum_ampdu_length_exponent =
+		caps->maximum_ampdu_length;
+	mors->fw_caps.number_sounding_dimensions =
+		caps->number_sounding_dimensions;
+
+	dev_dbg(mors->dev, "\tAMPDU Minimum start spacing: %u",
+		caps->ampdu_mss);
+	dev_dbg(mors->dev, "\tMorse Minimum Start Spacing offset: %u",
+		caps->mm81x_mmss_offset);
+	dev_dbg(mors->dev, "\tBeamformee STS Capability: %u",
+		caps->beamformee_sts_capability);
+	dev_dbg(mors->dev, "\tNumber of Sounding Dimensions: %u",
+		caps->number_sounding_dimensions);
+	dev_dbg(mors->dev, "\tMaximum AMPDU Length Exponent: %u",
+		caps->maximum_ampdu_length);
+}
+
+static void mm81x_fw_update_validate_skb_checksum(
+	struct mm81x *mors,
+	struct ext_host_tbl_insert_skb_checksum *validate_checksum)
+{
+	mors->hif.validate_skb_checksum =
+		validate_checksum->insert_and_validate_checksum;
+	dev_dbg(mors->dev, "Validate checksum inserted by fw %s",
+		str_enabled_disabled(mors->hif.validate_skb_checksum));
+}
+
+int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors)
+{
+	int ret;
+	u8 *head;
+	u8 *end;
+	struct ext_host_tbl *ext_host_table = NULL;
+
+	ret = mm81x_fw_read_ext_host_table(mors, &ext_host_table);
+	if (ret || !ext_host_table)
+		goto exit;
+
+	/* Parse the TLVs */
+	head = ext_host_table->ext_host_table_data_tlvs;
+	end = ((u8 *)ext_host_table) +
+	      le32_to_cpu(ext_host_table->ext_host_tbl_length);
+
+	while (head < end) {
+		struct ext_host_tbl_tlv_hdr *hdr =
+			(struct ext_host_tbl_tlv_hdr *)head;
+
+		switch (le16_to_cpu(hdr->tag)) {
+		case MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES:
+			mm81x_fw_update_capabilities(
+				mors, (struct ext_host_tbl_s1g_caps *)hdr);
+			break;
+
+		case MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM:
+			mm81x_fw_update_validate_skb_checksum(
+				mors,
+				(struct ext_host_tbl_insert_skb_checksum *)hdr);
+			break;
+
+		case MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE:
+			mm81x_yaps_hw_read_table(
+				mors, &((struct ext_host_tbl_yaps_table *)hdr)
+					       ->yaps_table);
+			break;
+		default:
+			break;
+		}
+
+		head += le16_to_cpu(hdr->length);
+		if (!hdr->length)
+			break;
+	}
+
+	kfree(ext_host_table);
+	return ret;
+exit:
+	dev_err(mors->dev, "failed to parse ext host table %d", ret);
+	return ret;
+}
+
+static int __mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw,
+			    const struct firmware *bcf, bool reset)
+{
+	int ret;
+
+	if (reset || !mors->chip_was_reset) {
+		ret = mm81x_hw_digital_reset(mors);
+		if (ret)
+			return ret;
+	}
+
+	mm81x_hw_pre_firmware_ndr_hook(mors);
+
+	ret = mm81x_fw_invalidate_host_ptr(mors);
+	if (ret)
+		return ret;
+
+	ret = mm81x_fw_load_fw(mors, fw);
+	if (ret)
+		return ret;
+
+	ret = mm81x_fw_load_bcf(mors, bcf, mors->bcf_address);
+	if (ret)
+		return ret;
+
+	mm81x_fw_trigger(mors);
+	mm81x_hw_post_firmware_ndr_hook(mors);
+
+	ret = mm81x_fw_get_host_table_ptr(mors);
+	if (ret)
+		return ret;
+
+	ret = mm81x_fw_verify_magic(mors);
+	if (ret)
+		return ret;
+
+	return mm81x_fw_check_compatibility(mors);
+}
+
+static int mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw,
+			  const struct firmware *bcf, bool reset)
+{
+	int ret;
+	int retries = FW_FLASH_ATTEMPT_COUNT;
+
+	while (retries--) {
+		ret = __mm81x_fw_flash(mors, fw, bcf, reset);
+		if (!ret)
+			return 0;
+
+		mors->chip_was_reset = false;
+	}
+
+	return ret;
+}
+
+static uint32_t binary_crc(const struct firmware *fw)
+{
+	return ~crc32_le(~0, (unsigned char const *)fw->data, fw->size) &
+	       0xffffffff;
+}
+
+int mm81x_fw_init(struct mm81x *mors, bool reset)
+{
+	int ret;
+	int n;
+	int board_id;
+	char *fw_path;
+	char bcf_path[MAX_BCF_NAME_LEN];
+	const struct firmware *fw = NULL;
+	const struct firmware *bcf = NULL;
+
+	fw_path = mm81x_core_get_fw_path(mors->chip_id);
+	if (!fw_path)
+		return -ENOMEM;
+
+	board_id = mm81x_hw_otp_get_board_type(mors);
+
+	if (mm81x_hw_otp_valid_board_type(board_id)) {
+		dev_dbg(mors->dev, "Using board type 0x%04x from OTP",
+			board_id);
+		n = snprintf(bcf_path, sizeof(bcf_path),
+			     "%s/bcf_boardtype_%04x.bin", MM81X_FW_DIR,
+			     board_id);
+	} else {
+		dev_err(mors->dev,
+			"OTP not set, unable to determine BCF to use");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (n < 0 || n >= sizeof(bcf_path)) {
+		dev_err(mors->dev, "Failed to create BCF path");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = request_firmware(&fw, fw_path, mors->dev);
+	if (ret) {
+		if (ret == -ENOENT)
+			dev_err(mors->dev, "Firmware %s not found\n", fw_path);
+		goto out;
+	}
+
+	dev_info(mors->dev, "Loaded firmware from %s, size %zu, crc32 0x%08x\n",
+		 fw_path, fw->size, binary_crc(fw));
+
+	ret = request_firmware(&bcf, bcf_path, mors->dev);
+	if (ret) {
+		if (ret == -ENOENT)
+			dev_err(mors->dev, "BCF %s not found\n", bcf_path);
+		goto out;
+	}
+
+	dev_info(mors->dev, "Loaded BCF from %s, size %zu, crc32 0x%08x\n",
+		 bcf_path, bcf->size, binary_crc(bcf));
+
+	ret = mm81x_fw_flash(mors, fw, bcf, reset);
+	if (ret) {
+		dev_err(mors->dev, "failed to flash firmware: %d", ret);
+		goto out;
+	}
+
+	ret = mm81x_fw_get_flags(mors);
+
+out:
+	release_firmware(fw);
+	release_firmware(bcf);
+	kfree(fw_path);
+
+	if (ret)
+		dev_err(mors->dev, "failed to init firmware: %d", ret);
+	else
+		dev_dbg(mors->dev, "firmware initialised");
+
+	return ret;
+}
diff --git a/drivers/net/wireless/morsemicro/mm81x/fw.h b/drivers/net/wireless/morsemicro/mm81x/fw.h
new file mode 100644
index 00000000000000..d590ad3121c136
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/fw.h
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_FW_H_
+#define _MM81X_FW_H_
+
+#include <linux/firmware.h>
+#include <linux/completion.h>
+#include <linux/elf.h>
+#include "yaps_hw.h"
+
+#define BCF_DATABASE_SIZE (1024)
+#define MM81X_FW_DIR "morsemicro"
+#define MM81X_FW_EXT ".bin"
+
+#define MAX_BCF_NAME_LEN 64
+
+/* FW_CAPABILITIES_FLAGS_WIDTH = ceil(MM81X_CAPS_MAX_HW_LEN / 32) */
+#define FW_CAPABILITIES_FLAGS_WIDTH (4)
+
+struct mm81x_elf32_ehdr {
+	unsigned char e_ident[EI_NIDENT];
+	__le16 e_type;
+	__le16 e_machine;
+	__le32 e_version;
+	__le32 e_entry;
+	__le32 e_phoff;
+	__le32 e_shoff;
+	__le32 e_flags;
+	__le16 e_ehsize;
+	__le16 e_phentsize;
+	__le16 e_phnum;
+	__le16 e_shentsize;
+	__le16 e_shnum;
+	__le16 e_shstrndx;
+} __packed;
+
+struct mm81x_elf32_shdr {
+	__le32 sh_name;
+	__le32 sh_type;
+	__le32 sh_flags;
+	__le32 sh_addr;
+	__le32 sh_offset;
+	__le32 sh_size;
+	__le32 sh_link;
+	__le32 sh_info;
+	__le32 sh_addralign;
+	__le32 sh_entsize;
+} __packed;
+
+struct mm81x_elf32_phdr {
+	__le32 p_type;
+	__le32 p_offset;
+	__le32 p_vaddr;
+	__le32 p_paddr;
+	__le32 p_filesz;
+	__le32 p_memsz;
+	__le32 p_flags;
+	__le32 p_align;
+} __packed;
+
+enum mm81x_fw_info_tlv_type {
+	MM81X_FW_INFO_TLV_BCF_ADDR = 1,
+};
+
+struct mm81x_fw_info_tlv {
+	__le16 type;
+	__le16 length;
+	u8 val[];
+} __packed;
+
+enum mm81x_fw_ext_host_tbl_tag {
+	/* The S1G capability tag */
+	MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES = 0,
+	MM81X_FW_HOST_TABLE_TAG_PAGER_BYPASS_TX_STATUS = 1,
+	MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM = 2,
+	MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE = 3,
+	MM81X_FW_HOST_TABLE_TAG_PAGER_PKT_MEMORY = 4,
+	MM81X_FW_HOST_TABLE_TAG_PAGER_BYPASS_CMD_RESP = 5,
+};
+
+struct ext_host_tbl_tlv_hdr {
+	/* The tag used to identify which capability this represents */
+	__le16 tag;
+	/* The length of the capability structure including this header */
+	__le16 length;
+} __packed;
+
+struct ext_host_tbl_s1g_caps {
+	struct ext_host_tbl_tlv_hdr header;
+	__le32 flags[FW_CAPABILITIES_FLAGS_WIDTH];
+	/*
+	 * The minimum A-MPDU start spacing required by firmware.
+	 * Value | Description
+	 * ------|------------
+	 * 0     | No restriction
+	 * 1     | 1/4 us
+	 * 2     | 1/2 us
+	 * 3     | 1 us
+	 * 4     | 2 us
+	 * 5     | 4 us
+	 * 6     | 8 us
+	 * 7     | 16 us
+	 */
+	u8 ampdu_mss;
+	u8 beamformee_sts_capability;
+	u8 number_sounding_dimensions;
+	/*
+	 * The maximum A-MPDU length. This is the exponent value such that
+	 * (2^(13 + exponent) - 1) is the length
+	 */
+	u8 maximum_ampdu_length;
+	/*
+	 * Offset to apply to the specification's MMSS table to signal further
+	 * minimum MPDU start spacing.
+	 */
+	u8 mm81x_mmss_offset;
+} __packed;
+
+struct ext_host_tbl_insert_skb_checksum {
+	struct ext_host_tbl_tlv_hdr header;
+	u8 insert_and_validate_checksum;
+};
+
+struct ext_host_tbl_yaps_table {
+	struct ext_host_tbl_tlv_hdr header;
+	struct mm81x_yaps_hw_table yaps_table;
+} __packed;
+
+struct ext_host_tbl {
+	__le32 ext_host_tbl_length;
+	u8 dev_mac_addr[6];
+	u8 ext_host_table_data_tlvs[];
+} __packed;
+
+int mm81x_fw_init(struct mm81x *mors, bool reset);
+int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors);
+
+#endif /* !_MM81X_FW_H_ */
diff --git a/drivers/net/wireless/morsemicro/mm81x/hif.h b/drivers/net/wireless/morsemicro/mm81x/hif.h
new file mode 100644
index 00000000000000..e3d23423049ae5
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/hif.h
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_HIF_H_
+#define _MM81X_HIF_H_
+
+#include "core.h"
+
+struct mm81x_skbq;
+
+#define MM81X_HIF_BYPASS_TX_STATUS_IRQ_NUM (15)
+#define MM81X_HIF_BYPASS_CMD_RESP_IRQ_NUM (29)
+#define MM81X_HIF_IRQ_BYPASS_TX_STATUS_AVAILABLE \
+	BIT(MM81X_HIF_BYPASS_TX_STATUS_IRQ_NUM)
+#define MM81X_HIF_IRQ_BYPASS_CMD_RESP_AVAILABLE \
+	BIT(MM81X_HIF_BYPASS_CMD_RESP_IRQ_NUM)
+
+/* Hardware IF interrupt mask. We may use any interrupts in this range */
+#define MM81X_HIF_IRQ_MASK_ALL                                       \
+	(GENMASK(13, 0) | MM81X_HIF_IRQ_BYPASS_TX_STATUS_AVAILABLE | \
+	 MM81X_HIF_IRQ_BYPASS_CMD_RESP_AVAILABLE)
+
+enum mm81x_hif_flags {
+	MM81X_HIF_FLAGS_DIR_TO_HOST = BIT(0),
+	MM81X_HIF_FLAGS_DIR_TO_CHIP = BIT(1),
+	MM81X_HIF_FLAGS_COMMAND = BIT(2),
+	MM81X_HIF_FLAGS_BEACON = BIT(3),
+	MM81X_HIF_FLAGS_DATA = BIT(4)
+};
+
+struct mm81x_hif_ops {
+	int (*init)(struct mm81x *mors);
+	void (*flush_tx_data)(struct mm81x *mors);
+	void (*flush_cmds)(struct mm81x *mors);
+	void (*finish)(struct mm81x *mors);
+	void (*skbq_get_tx_qs)(struct mm81x *mors, struct mm81x_skbq **qs,
+			       int *num_qs);
+	struct mm81x_skbq *(*get_tx_cmd_queue)(struct mm81x *mors);
+	struct mm81x_skbq *(*get_tx_beacon_queue)(struct mm81x *mors);
+	struct mm81x_skbq *(*get_tx_mgmt_queue)(struct mm81x *mors);
+	struct mm81x_skbq *(*get_tx_data_queue)(struct mm81x *mors, int aci);
+	int (*handle_irq)(struct mm81x *mors, u32 status);
+	int (*get_tx_buffered_count)(struct mm81x *mors);
+	int (*get_tx_status_pending_count)(struct mm81x *mors);
+};
+
+static inline void mm81x_hif_clear_events(struct mm81x *mors)
+{
+	mors->hif.event_flags = 0;
+}
+
+static inline int mm81x_hif_init(struct mm81x *mors)
+{
+	return mors->hif.ops->init(mors);
+}
+
+static inline void mm81x_hif_flush_tx_data(struct mm81x *mors)
+{
+	mors->hif.ops->flush_tx_data(mors);
+}
+
+static inline void mm81x_hif_flush_cmds(struct mm81x *mors)
+{
+	mors->hif.ops->flush_cmds(mors);
+}
+
+static inline void mm81x_hif_finish(struct mm81x *mors)
+{
+	mors->hif.ops->finish(mors);
+}
+
+static inline void mm81x_hif_skbq_get_tx_qs(struct mm81x *mors,
+					    struct mm81x_skbq **qs, int *num_qs)
+{
+	mors->hif.ops->skbq_get_tx_qs(mors, qs, num_qs);
+}
+
+static inline struct mm81x_skbq *mm81x_hif_get_tx_cmd_queue(struct mm81x *mors)
+{
+	return mors->hif.ops->get_tx_cmd_queue(mors);
+}
+
+static inline struct mm81x_skbq *
+mm81x_hif_get_tx_beacon_queue(struct mm81x *mors)
+{
+	return mors->hif.ops->get_tx_beacon_queue(mors);
+}
+
+static inline struct mm81x_skbq *mm81x_hif_get_tx_mgmt_queue(struct mm81x *mors)
+{
+	return mors->hif.ops->get_tx_mgmt_queue(mors);
+}
+
+static inline struct mm81x_skbq *mm81x_hif_get_tx_data_queue(struct mm81x *mors,
+							     int aci)
+{
+	return mors->hif.ops->get_tx_data_queue(mors, aci);
+}
+
+static inline int mm81x_hif_handle_irq(struct mm81x *mors, u32 status)
+{
+	return mors->hif.ops->handle_irq(mors, status);
+}
+
+static inline int mm81x_hif_get_tx_buffered_count(struct mm81x *mors)
+{
+	return mors->hif.ops->get_tx_buffered_count(mors);
+}
+
+static inline int mm81x_hif_get_tx_status_pending_count(struct mm81x *mors)
+{
+	return mors->hif.ops->get_tx_status_pending_count(mors);
+}
+
+#endif /* _MM81X_HIF_H_ */
diff --git a/drivers/net/wireless/morsemicro/mm81x/hw.c b/drivers/net/wireless/morsemicro/mm81x/hw.c
new file mode 100644
index 00000000000000..081360455e7e8d
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/hw.c
@@ -0,0 +1,367 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+#include <linux/gpio.h>
+#include "hif.h"
+#include "mac.h"
+#include "bus.h"
+#include "core.h"
+#include "fw.h"
+#include "yaps.h"
+
+#define MM8108_REG_HOST_MAGIC_VALUE 0xDEADBEEF
+#define MM8108_REG_RESET_VALUE 0xDEAD
+
+#define MM8108_REG_SDIO_DEVICE_ADDR 0x0000207C
+
+#define MM8108_REG_SDIO_DEVICE_BURST_OFFSET 9
+#define MM8108_REG_TRGR_BASE 0x00003c00
+#define MM8108_REG_INT_BASE 0x00003c50
+#define MM8108_REG_MSI_ADDRESS 0x00004100
+#define MM8108_REG_MSI_VALUE 0x1
+#define MM8108_REG_MANIFEST_PTR_ADDRESS 0x00002d40
+#define MM8108_REG_APPS_BOOT_ADDR 0x00002084
+#define MM8108_REG_RESET 0x000020AC
+#define MM8108_REG_AON_COUNT 2
+
+#define MM8108_REG_AON_ADDR 0x00002114
+#define MM8108_REG_AON_LATCH_ADDR 0x00405020
+#define MM8108_REG_AON_LATCH_MASK 0x1
+#define MM8108_REG_AON_RESET_USB_VALUE 0x8
+#define MM8108_APPS_MAC_DMEM_ADDR_START 0x00100000
+
+#define MM8108_REG_RC_CLK_POWER_OFF_ADDR 0x00405020
+#define MM8108_REG_RC_CLK_POWER_OFF_MASK 0x00000040
+#define MM8108_SLOW_RC_POWER_ON_DELAY_MS 2
+
+#define MM8108_RESET_DELAY_TIME_MS 400
+
+#define MM8108_REG_OTPCTRL_PLDO 0x00004014
+#define MM8108_REG_OTPCTRL_PENVDD2 0x00004010
+#define MM8108_REG_OTPCTRL_PDSTB 0x00004018
+#define MM8108_REG_OTPCTRL_PTM 0x0000401c
+#define MM8108_REG_OTPCTRL_PCE 0x00004020
+#define MM8108_REG_OTPCTRL_PA 0x00004034
+#define MM8108_REG_OTPCTRL_PECCRDB 0x00004048
+#define MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START 0x0000400c
+#define MM8108_REG_OTPCTRL_PDOUT 0x00004040
+
+#define MM81X_OTP_MAC_ADDR_2_BANK_NUM 27
+#define MM81X_OTP_MAC_ADDR_1_BANK_NUM 26
+#define MM81X_OTP_MAC_ADDR_1_MASK GENMASK(31, 16)
+#define MM81X_OTP_BOARD_TYPE_BANK_NUM 26
+#define MM81X_OTP_BOARD_TYPE_MASK GENMASK(15, 0)
+
+#define MM810X_BOARD_TYPE_MAX_VALUE (MM81X_OTP_BOARD_TYPE_MASK - 1)
+
+static void mm81x_hw_otp_power_up(struct mm81x *mors)
+{
+	mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PENVDD2, 1);
+	udelay(2);
+
+	mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PLDO, 1);
+	usleep_range(10, 20);
+
+	mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PDSTB, 1);
+	udelay(3);
+}
+
+static void mm81x_hw_otp_power_down(struct mm81x *mors)
+{
+	mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PDSTB, 0);
+	mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PLDO, 0);
+	mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PENVDD2, 0);
+}
+
+static void mm81x_hw_otp_read_enable(struct mm81x *mors)
+{
+	mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PTM, 0);
+	mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PCE, 1);
+	usleep_range(10, 20);
+}
+
+static void mm81x_hw_otp_read_disable(struct mm81x *mors)
+{
+	mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PCE, 0);
+	udelay(1);
+}
+
+static int mm81x_hw_otp_read(struct mm81x *mors, u8 bank_num, u32 *buf,
+			     u8 ignore_ecc)
+{
+	u32 auto_rd_start_tmp;
+	u32 auto_rd_start = 1;
+	int i;
+
+	mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PA, bank_num);
+	mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PECCRDB, ignore_ecc);
+
+	mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START,
+			 &auto_rd_start_tmp);
+	auto_rd_start_tmp &= 0xfffffffe;
+
+	mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START,
+			  auto_rd_start | auto_rd_start_tmp);
+
+	/* Attempt reading up to 5 times. */
+	for (i = 0; i < 5 && auto_rd_start; i++) {
+		usleep_range(15, 20);
+		mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START,
+				 &auto_rd_start_tmp);
+		auto_rd_start = auto_rd_start_tmp & 0x1;
+	}
+
+	if (i == 5)
+		return -EIO;
+
+	mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_PDOUT, buf);
+
+	return 0;
+}
+
+int mm81x_hw_otp_get_board_type(struct mm81x *mors)
+{
+	int board_type = 0;
+	u32 otp_word = 0;
+	int ret;
+
+	mm81x_claim_bus(mors);
+	mm81x_hw_otp_power_up(mors);
+	mm81x_hw_otp_read_enable(mors);
+
+	ret = mm81x_hw_otp_read(mors, MM81X_OTP_BOARD_TYPE_BANK_NUM, &otp_word,
+				1);
+
+	mm81x_hw_otp_read_disable(mors);
+	mm81x_hw_otp_power_down(mors);
+	mm81x_release_bus(mors);
+
+	if (ret)
+		return -EINVAL;
+
+	board_type = otp_word & MM81X_OTP_BOARD_TYPE_MASK;
+
+	return board_type;
+}
+
+bool mm81x_hw_otp_valid_board_type(u32 board_type)
+{
+	return board_type > 0 && board_type < MM810X_BOARD_TYPE_MAX_VALUE;
+}
+
+int mm81x_hw_otp_get_mac_addr(struct mm81x *mors)
+{
+	u32 mac1 = 0;
+	u32 mac2 = 0;
+	int ret = 0;
+
+	mm81x_claim_bus(mors);
+	mm81x_hw_otp_power_up(mors);
+	mm81x_hw_otp_read_enable(mors);
+
+	ret = mm81x_hw_otp_read(mors, MM81X_OTP_MAC_ADDR_1_BANK_NUM, &mac1, 1);
+	if (ret)
+		goto exit;
+
+	ret = mm81x_hw_otp_read(mors, MM81X_OTP_MAC_ADDR_2_BANK_NUM, &mac2, 1);
+	if (ret)
+		goto exit;
+
+	put_unaligned_le16((mac1 & MM81X_OTP_MAC_ADDR_1_MASK) >> 16,
+			   &mors->macaddr[0]);
+	put_unaligned_le32(mac2, &mors->macaddr[2]);
+
+exit:
+	mm81x_hw_otp_read_disable(mors);
+	mm81x_hw_otp_power_down(mors);
+	mm81x_release_bus(mors);
+
+	return ret;
+}
+
+void mm81x_hw_irq_enable(struct mm81x *mors, u32 irq, bool enable)
+{
+	u32 irq_en, irq_en_addr = irq < 32 ? MM81X_REG_INT1_EN(mors) :
+					     MM81X_REG_INT2_EN(mors);
+	u32 irq_clr_addr = irq < 32 ? MM81X_REG_INT1_CLR(mors) :
+				      MM81X_REG_INT2_CLR(mors);
+	u32 mask = irq < 32 ? (1 << irq) : (1 << (irq - 32));
+
+	mm81x_claim_bus(mors);
+	mm81x_reg32_read(mors, irq_en_addr, &irq_en);
+	if (enable)
+		irq_en |= (mask);
+	else
+		irq_en &= ~(mask);
+	mm81x_reg32_write(mors, irq_clr_addr, mask);
+	mm81x_reg32_write(mors, irq_en_addr, irq_en);
+	mm81x_release_bus(mors);
+}
+
+int mm81x_hw_irq_handle(struct mm81x *mors)
+{
+	u32 status1 = 0;
+
+	mm81x_reg32_read(mors, MM81X_REG_INT1_STS(mors), &status1);
+
+	if (status1 & MM81X_HIF_IRQ_MASK_ALL)
+		mm81x_hif_handle_irq(mors, status1);
+
+	if (status1 & MM81X_INT_BEACON_VIF_MASK_ALL)
+		mm81x_mac_beacon_irq_handle(mors, status1);
+
+	mm81x_reg32_write(mors, MM81X_REG_INT1_CLR(mors), status1);
+
+	return status1 ? 1 : 0;
+}
+EXPORT_SYMBOL_GPL(mm81x_hw_irq_handle);
+
+void mm81x_hw_irq_clear(struct mm81x *mors)
+{
+	mm81x_claim_bus(mors);
+	mm81x_reg32_write(mors, MM81X_REG_INT1_CLR(mors), 0xFFFFFFFF);
+	mm81x_reg32_write(mors, MM81X_REG_INT2_CLR(mors), 0xFFFFFFFF);
+	mm81x_release_bus(mors);
+}
+
+void mm81x_hw_toggle_aon_latch(struct mm81x *mors)
+{
+	u32 address = MM81X_REG_AON_LATCH_ADDR(mors);
+	u32 mask = MM81X_REG_AON_LATCH_MASK(mors);
+	u32 latch;
+
+	mm81x_reg32_read(mors, address, &latch);
+	mm81x_reg32_write(mors, address, latch & ~(mask));
+	mdelay(5);
+	mm81x_reg32_write(mors, address, latch | mask);
+	mdelay(5);
+	mm81x_reg32_write(mors, address, latch & ~(mask));
+	mdelay(5);
+}
+
+void mm81x_hw_enable_stop_notifications(struct mm81x *mors, bool enable)
+{
+	mm81x_hw_irq_enable(mors, MM81X_INT_HW_STOP_NOTIFICATION_NUM, enable);
+}
+
+void mm81x_hw_enable_burst_mode(struct mm81x *mors, const u8 burst_mode)
+{
+	u32 reg32_value;
+
+	mm81x_claim_bus(mors);
+	if (mm81x_reg32_read(mors, MM8108_REG_SDIO_DEVICE_ADDR, &reg32_value))
+		goto end;
+
+	reg32_value &= ~(u32)(SDIO_WORD_BURST_MASK
+			      << MM8108_REG_SDIO_DEVICE_BURST_OFFSET);
+	reg32_value |= (u32)(burst_mode << MM8108_REG_SDIO_DEVICE_BURST_OFFSET);
+
+	dev_dbg(mors->dev,
+		"Setting Burst mode to %d Writing 0x%08X to the register",
+		burst_mode, reg32_value);
+
+	if (mm81x_reg32_write(mors, MM8108_REG_SDIO_DEVICE_ADDR, reg32_value))
+		goto end;
+
+end:
+	mm81x_release_bus(mors);
+}
+EXPORT_SYMBOL_GPL(mm81x_hw_enable_burst_mode);
+
+static int mm81x_hw_enable_internal_slow_clock(struct mm81x *mors)
+{
+	u32 rc_clock_reg_value;
+	int ret = 0;
+
+	dev_dbg(mors->dev, "Enabling internal slow clock");
+
+	ret = mm81x_reg32_read(mors, MM8108_REG_RC_CLK_POWER_OFF_ADDR,
+			       &rc_clock_reg_value);
+	if (ret)
+		goto exit;
+
+	rc_clock_reg_value &= ~MM8108_REG_RC_CLK_POWER_OFF_MASK;
+	ret = mm81x_reg32_write(mors, MM8108_REG_RC_CLK_POWER_OFF_ADDR,
+				rc_clock_reg_value);
+	if (ret)
+		goto exit;
+
+	mm81x_hw_toggle_aon_latch(mors);
+
+	/* Wait for the clock to turn on and settle */
+	mdelay(MM8108_SLOW_RC_POWER_ON_DELAY_MS);
+exit:
+	return ret;
+}
+
+int mm81x_hw_digital_reset(struct mm81x *mors)
+{
+	int ret = 0;
+
+	mm81x_claim_bus(mors);
+
+	/* This should be the first step in digital reset, do not reorder */
+	ret = mm81x_hw_enable_internal_slow_clock(mors);
+	if (ret)
+		goto exit;
+
+	if (mors->bus_type == MM81X_BUS_TYPE_USB) {
+		ret = mm81x_bus_digital_reset(mors);
+		goto usb_done;
+	}
+
+	if (MM81X_REG_RESET(mors) != 0)
+		ret = mm81x_reg32_write(mors, MM81X_REG_RESET(mors),
+					MM81X_REG_RESET_VALUE(mors));
+
+usb_done:
+	msleep(MM8108_RESET_DELAY_TIME_MS);
+exit:
+	mm81x_release_bus(mors);
+
+	if (!ret)
+		mors->chip_was_reset = true;
+
+	return ret;
+}
+
+void mm81x_hw_pre_firmware_ndr_hook(struct mm81x *mors)
+{
+	/* We need disable bursting for firmware download/init procedure */
+	mm81x_bus_config_burst_mode(mors, false);
+}
+
+void mm81x_hw_post_firmware_ndr_hook(struct mm81x *mors)
+{
+	/* We are safe here to re-enable bursting again, if supported */
+	mm81x_bus_config_burst_mode(mors, true);
+}
+
+const struct mm81x_regs mm8108_regs = {
+	.chip_id_address = MM8108_REG_CHIP_ID,
+	.irq_base_address = MM8108_REG_INT_BASE,
+	.trgr_base_address = MM8108_REG_TRGR_BASE,
+	.cpu_reset_address = MM8108_REG_RESET,
+	.cpu_reset_value = MM8108_REG_RESET_VALUE,
+	.manifest_ptr_address = MM8108_REG_MANIFEST_PTR_ADDRESS,
+	.msi_address = MM8108_REG_MSI_ADDRESS,
+	.msi_value = MM8108_REG_MSI_VALUE,
+	.magic_num_value = MM8108_REG_HOST_MAGIC_VALUE,
+	.early_clk_ctrl_value = 0,
+	.pager_base_address = MM8108_APPS_MAC_DMEM_ADDR_START,
+	.aon_latch = MM8108_REG_AON_LATCH_ADDR,
+	.aon_latch_mask = MM8108_REG_AON_LATCH_MASK,
+	.aon_reset_usb_value = MM8108_REG_AON_RESET_USB_VALUE,
+	.aon = MM8108_REG_AON_ADDR,
+	.aon_count = MM8108_REG_AON_COUNT,
+	.boot_address = MM8108_REG_APPS_BOOT_ADDR,
+};
+
+/* B2 ROM_LINKED */
+MODULE_FIRMWARE(MM81X_FW_DIR "/" MM8108_FW_BASE MM8108B2_REV_STRING
+			FW_ROM_LINKED_STRING MM81X_FW_EXT);
diff --git a/drivers/net/wireless/morsemicro/mm81x/hw.h b/drivers/net/wireless/morsemicro/mm81x/hw.h
new file mode 100644
index 00000000000000..e22948f037bf90
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/hw.h
@@ -0,0 +1,176 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_HW_H_
+#define _MM81X_HW_H_
+
+#include <linux/gpio/consumer.h>
+#include "core.h"
+#include "command_defs.h"
+
+/* This should be at a fixed location for a family of chipset */
+#define MM8108_REG_CHIP_ID 0x00002d20
+
+#define MM81X_SDIO_RW_ADDR_BOUNDARY_MASK ((u32)0xFFFF0000)
+
+#define MM81X_CONFIG_ACCESS_1BYTE 0
+#define MM81X_CONFIG_ACCESS_2BYTE 1
+#define MM81X_CONFIG_ACCESS_4BYTE 2
+
+#define MM81X_REG_TRGR_BASE(mors) ((mors)->regs->trgr_base_address)
+#define MM81X_REG_TRGR1_STS(mors) (MM81X_REG_TRGR_BASE(mors) + 0x00)
+#define MM81X_REG_TRGR1_SET(mors) (MM81X_REG_TRGR_BASE(mors) + 0x04)
+#define MM81X_REG_TRGR1_CLR(mors) (MM81X_REG_TRGR_BASE(mors) + 0x08)
+#define MM81X_REG_TRGR1_EN(mors) (MM81X_REG_TRGR_BASE(mors) + 0x0C)
+#define MM81X_REG_TRGR2_STS(mors) (MM81X_REG_TRGR_BASE(mors) + 0x10)
+#define MM81X_REG_TRGR2_SET(mors) (MM81X_REG_TRGR_BASE(mors) + 0x14)
+#define MM81X_REG_TRGR2_CLR(mors) (MM81X_REG_TRGR_BASE(mors) + 0x18)
+#define MM81X_REG_TRGR2_EN(mors) (MM81X_REG_TRGR_BASE(mors) + 0x1C)
+
+#define MM81X_REG_INT_BASE(mors) ((mors)->regs->irq_base_address)
+#define MM81X_REG_INT1_STS(mors) (MM81X_REG_INT_BASE(mors) + 0x00)
+#define MM81X_REG_INT1_SET(mors) (MM81X_REG_INT_BASE(mors) + 0x04)
+#define MM81X_REG_INT1_CLR(mors) (MM81X_REG_INT_BASE(mors) + 0x08)
+#define MM81X_REG_INT1_EN(mors) (MM81X_REG_INT_BASE(mors) + 0x0C)
+#define MM81X_REG_INT2_STS(mors) (MM81X_REG_INT_BASE(mors) + 0x10)
+#define MM81X_REG_INT2_SET(mors) (MM81X_REG_INT_BASE(mors) + 0x14)
+#define MM81X_REG_INT2_CLR(mors) (MM81X_REG_INT_BASE(mors) + 0x18)
+#define MM81X_REG_INT2_EN(mors) (MM81X_REG_INT_BASE(mors) + 0x1C)
+
+#define MM81X_REG_CHIP_ID(mors) ((mors)->regs->chip_id_address)
+
+#define MM81X_REG_MSI(mors) ((mors)->regs->msi_address)
+#define MM81X_REG_MSI_HOST_INT(mors) ((mors)->regs->msi_value)
+
+#define MM81X_REG_HOST_MAGIC_VALUE(mors) ((mors)->regs->magic_num_value)
+
+#define MM81X_REG_RESET(mors) ((mors)->regs->cpu_reset_address)
+#define MM81X_REG_RESET_VALUE(mors) ((mors)->regs->cpu_reset_value)
+
+#define MM81X_REG_HOST_MANIFEST_PTR(mors) ((mors)->regs->manifest_ptr_address)
+
+#define MM81X_REG_EARLY_CLK_CTRL_VALUE(mors) \
+	((mors)->regs->early_clk_ctrl_value)
+
+#define MM81X_REG_CLK_CTRL(mors) ((mors)->regs->clk_ctrl_address)
+#define MM81X_REG_CLK_CTRL_VALUE(mors) ((mors)->regs->clk_ctrl_value)
+
+#define MM81X_REG_BOOT_ADDR(mors) ((mors)->regs->boot_address)
+#define MM81X_REG_BOOT_ADDR_VALUE(mors) ((mors)->regs->boot_value)
+
+#define MM81X_REG_AON_ADDR(mors) ((mors)->regs->aon)
+#define MM81X_REG_AON_COUNT(mors) ((mors)->regs->aon_count)
+#define MM81X_REG_AON_LATCH_ADDR(mors) ((mors)->regs->aon_latch)
+#define MM81X_REG_AON_LATCH_MASK(mors) ((mors)->regs->aon_latch_mask)
+#define MM81X_REG_AON_USB_RESET(mors) ((mors)->regs->aon_reset_usb_value)
+
+/* Bit 17 to 24 reserved for the beacon VIF 0 to 7 interrupts */
+#define MM81X_INT_BEACON_VIF_MASK_ALL (GENMASK(24, 17))
+#define MM81X_INT_BEACON_BASE_NUM (17)
+
+/* PV0 NDP probe interrupts (VIF 0 and 1). */
+#define MM81X_INT_NDP_PROBE_REQ_PV0_VIF_MASK_ALL (GENMASK(26, 25))
+#define MM81X_INT_NDP_PROBE_REQ_PV0_BASE_NUM (25)
+
+/* Bit 27 Chip to Host stop notify */
+#define MM81X_INT_HW_STOP_NOTIFICATION_NUM (27)
+#define MM81X_INT_HW_STOP_NOTIFICATION BIT(MM81X_INT_HW_STOP_NOTIFICATION_NUM)
+
+#define CHIP_TYPE_SILICON 0x0
+
+/* Chip ID */
+#define MM8108XX_ID 0x9
+
+/* Chip Rev */
+#define MM8108B2_REV 0x8
+
+/* Chip Rev String */
+#define MM8108B_STRING "b"
+#define MM8108B2_REV_STRING MM8108B_STRING "2"
+
+/* Chip ID for MM8108 */
+#define MM8108B2_ID \
+	MM81X_DEVICE_ID(MM8108XX_ID, MM8108B2_REV, CHIP_TYPE_SILICON)
+
+#define FW_RAM_ONLY_STRING ""
+#define FW_ROM_LINKED_STRING "-rl"
+#define FW_ROM_ALL_STRING "-ro"
+
+/*
+ * Minimum time we must wait between attempting to reload the HW after a
+ * stop notification
+ */
+#define HW_RELOAD_AFTER_STOP_WINDOW 5
+
+enum host_table_firmware_flags {
+	MM81X_FW_FLAGS_SUPPORT_S1G = BIT(0),
+	MM81X_FW_FLAGS_BUSY_ACTIVE_LOW = BIT(1),
+	MM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION = BIT(2),
+	MM81X_FW_FLAGS_SUPPORT_HW_SCAN = BIT(3),
+	MM81X_FW_FLAGS_SUPPORT_CHIP_HALT_IRQ = BIT(4),
+};
+
+struct host_table {
+	__le32 magic_number;
+	__le32 fw_version_number;
+	__le32 host_flags;
+	__le32 firmware_flags;
+	__le32 memcmd_cmd_addr;
+	__le32 memcmd_resp_addr;
+	__le32 ext_host_tbl_addr;
+} __packed;
+
+struct mm81x_regs {
+	u32 chip_id_address;
+	u32 irq_base_address;
+	u32 trgr_base_address;
+	u32 cpu_reset_address;
+	u32 cpu_reset_value;
+	u32 msi_address;
+	u32 msi_value;
+	u32 manifest_ptr_address;
+	u32 magic_num_value;
+	u32 clk_ctrl_address;
+	u32 clk_ctrl_value;
+	u32 early_clk_ctrl_value;
+	u32 boot_address;
+	u32 boot_value;
+	u32 pager_base_address;
+	u32 aon_latch;
+	u32 aon_latch_mask;
+	u32 aon_reset_usb_value;
+	u32 aon;
+	u8 aon_count;
+};
+
+int mm81x_hw_otp_get_board_type(struct mm81x *mors);
+bool mm81x_hw_otp_valid_board_type(u32 board_type);
+int mm81x_hw_otp_get_mac_addr(struct mm81x *mors);
+
+void mm81x_hw_irq_enable(struct mm81x *mors, u32 irq, bool enable);
+int mm81x_hw_irq_handle(struct mm81x *mors);
+void mm81x_hw_irq_clear(struct mm81x *mors);
+void mm81x_hw_toggle_aon_latch(struct mm81x *mors);
+void mm81x_hw_enable_burst_mode(struct mm81x *mors, const u8 burst_mode);
+int mm81x_hw_digital_reset(struct mm81x *mors);
+void mm81x_hw_pre_firmware_ndr_hook(struct mm81x *mors);
+void mm81x_hw_post_firmware_ndr_hook(struct mm81x *mors);
+
+enum sdio_burst_mode {
+	SDIO_WORD_BURST_DISABLE =
+		0, /* Intentionally duplicate to make it clear it's disabled */
+	SDIO_WORD_BURST_SIZE_0 = 0, /* 000: no bursting (single 32bit word) */
+	SDIO_WORD_BURST_SIZE_2 = 1, /* 001: bursts of 2 words */
+	SDIO_WORD_BURST_SIZE_4 = 2, /* 010: bursts of 4 words */
+	SDIO_WORD_BURST_SIZE_8 = 3, /* 011: bursts of 8 words */
+	SDIO_WORD_BURST_SIZE_16 = 4, /* 100: bursts of 16 words */
+	SDIO_WORD_BURST_MASK = 7,
+};
+
+extern const struct mm81x_regs mm8108_regs;
+
+void mm81x_hw_enable_stop_notifications(struct mm81x *mors, bool enable);
+
+#endif /* !_MM81X_HW_H_ */
diff --git a/drivers/net/wireless/morsemicro/mm81x/mac.c b/drivers/net/wireless/morsemicro/mm81x/mac.c
new file mode 100644
index 00000000000000..71d559be71ff3a
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/mac.c
@@ -0,0 +1,2443 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include "core.h"
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/crc32.h>
+#include <net/mac80211.h>
+#include <asm/div64.h>
+#include <linux/kernel.h>
+#include "hif.h"
+#include "mac.h"
+#include "bus.h"
+#include "ps.h"
+#include "rc.h"
+
+/*
+ * Arbitrary size limit for the filter command address list, to ensure that
+ * the command does not exceed page/MTU size. This will be far greater than
+ * the number of filters supported by the firmware.
+ */
+#define MCAST_FILTER_COUNT_MAX (1024 / sizeof(filter->addr_list[0]))
+
+/* Calculate average RSSI for Rx status */
+#define CALC_AVG_RSSI(_avg, _sample) ((((_avg) * 9 + (_sample)) / 10))
+
+/*
+ * When automatically trying MCS0 before MCS10, this is how many
+ * MCS0 attempts to make
+ */
+#define MCS0_BEFORE_MCS10_COUNT (1)
+
+/* Maximum TX power (default) */
+#define MAX_TX_POWER_MBM (2200)
+
+/*
+ * Since S1G runs at 1/10th the clockrate of VHT, the worst-case
+ * transmission time is significantly longer then that of non-S1G
+ * PHYs.
+ */
+#define MM81X_FLUSH_TIMEOUT (16 * HZ)
+
+/* Default queue count */
+#define MM81X_HW_QUEUE_COUNT (4)
+
+/* Max rates per skb */
+#define MM81X_HW_MAX_RATES (4)
+
+/* Max reported rates */
+#define MM81X_HW_MAX_REPORT_RATES (4)
+
+/* Max rate attempts */
+#define MM81X_HW_MAX_RATE_TRIES (1)
+
+/* Max sk pacing shift */
+#define MM81X_HW_TX_SK_PACING_SHIFT (3)
+
+/* NSS/MCS map values */
+#define MM81X_NSS_MCS_BYTE_0 0xfe /* 1SS */
+#define MM81X_NSS_MCS_BYTE_1 0x00
+#define MM81X_NSS_MCS_BYTE_2 0xfc /* 1SS */
+#define MM81X_NSS_MCS_BYTE_3 0x01
+#define MM81X_NSS_MCS_BYTE_4 0x00
+
+/* HW restart delay time before terminating hardware IF work items */
+#define MM81X_HW_RESTART_DELAY_MS 20
+
+/* clang-format off */
+
+/* mm81x chips do not support 16MHz */
+#define CHANS1G(channel, frequency, offset, chan_flags)		\
+{								\
+	.band = NL80211_BAND_S1GHZ,				\
+	.center_freq = (frequency),				\
+	.freq_offset = (offset),				\
+	.hw_value = (channel),					\
+	.flags = ((chan_flags) | IEEE80211_CHAN_NO_16MHZ),	\
+	.max_antenna_gain = 0,					\
+	.max_power = 30,					\
+}
+
+static struct ieee80211_channel mors_s1ghz_channels[] = {
+	CHANS1G(1, 902, 500, IEEE80211_CHAN_S1G_NO_PRIMARY),
+	CHANS1G(3, 903, 500, 0),
+	CHANS1G(5, 904, 500, 0),
+	CHANS1G(7, 905, 500, 0),
+	CHANS1G(9, 906, 500, 0),
+	CHANS1G(11, 907, 500, 0),
+	CHANS1G(13, 908, 500, 0),
+	CHANS1G(15, 909, 500, 0),
+	CHANS1G(17, 910, 500, 0),
+	CHANS1G(19, 911, 500, 0),
+	CHANS1G(21, 912, 500, 0),
+	CHANS1G(23, 913, 500, 0),
+	CHANS1G(25, 914, 500, 0),
+	CHANS1G(27, 915, 500, 0),
+	CHANS1G(29, 916, 500, 0),
+	CHANS1G(31, 917, 500, 0),
+	CHANS1G(33, 918, 500, 0),
+	CHANS1G(35, 919, 500, 0),
+	CHANS1G(37, 920, 500, 0),
+	CHANS1G(39, 921, 500, 0),
+	CHANS1G(41, 922, 500, 0),
+	CHANS1G(43, 923, 500, 0),
+	CHANS1G(45, 924, 500, 0),
+	CHANS1G(47, 925, 500, 0),
+	CHANS1G(49, 926, 500, 0),
+	CHANS1G(51, 927, 500, IEEE80211_CHAN_S1G_NO_PRIMARY),
+};
+
+/* clang-format on */
+
+static struct ieee80211_supported_band mors_band_s1ghz = {
+	.band = NL80211_BAND_S1GHZ,
+	.s1g_cap.s1g = true,
+	.channels = mors_s1ghz_channels,
+	.n_channels = ARRAY_SIZE(mors_s1ghz_channels),
+	.bitrates = NULL,
+	.n_bitrates = 0,
+	.s1g_cap.cap[4] = 0x80 /* STA type sensor only for AP & STA */
+};
+
+static struct ieee80211_iface_limit mors_if_limits[] = {
+	{
+		.max = MM81X_MAX_IF,
+		.types = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP),
+	},
+};
+
+static struct ieee80211_iface_combination mors_if_combs[] = {
+	{
+		.limits = mors_if_limits,
+		.n_limits = ARRAY_SIZE(mors_if_limits),
+		.max_interfaces = MM81X_MAX_IF,
+		.num_different_channels = 1,
+	},
+};
+
+/* Convert from a time in time units (1024us) to us */
+#define MM81X_TU_TO_US(x) ((x) * 1024UL)
+
+/* Convert from a time in time units (1024us) to ms */
+#define MM81X_TU_TO_MS(x) (MM81X_TU_TO_US(x) / 1000UL)
+
+/* Default time to dwell on a scan channel */
+#define MM81X_HWSCAN_DEFAULT_DWELL_TIME_MS (30)
+
+/* Default time to dwell on a scan channel for passive scan */
+#define MM81X_HWSCAN_DEFAULT_PASSIVE_DWELL_TIME_MS (110)
+
+/* Default time to dwell on home channel, in between scan channels */
+#define MM81X_HWSCAN_DEFAULT_DWELL_ON_HOME_MS (200)
+
+/* Typical time it takes to send the probe */
+#define MM81X_HWSCAN_PROBE_DELAY_MS (30)
+
+/* A margin to account for event/command processing */
+#define MM81X_HWSCAN_TIMEOUT_OVERHEAD_MS (2000)
+
+/* Scan channel frequency mask */
+#define HW_SCAN_CH_LIST_FREQ_KHZ GENMASK(19, 0)
+
+/*
+ * Scan channel bandwidth mask.
+ * Encoded as: 0 = 1MHz, 1 = 2MHz, 2 = 4MHz, 3 = 8MHz
+ */
+#define HW_SCAN_CH_LIST_OP_BW GENMASK(21, 20)
+
+/*
+ * Scan channel primary channel width.
+ * Encoded as: 0 = 1MHz, 1 = 2MHz
+ */
+#define HW_SCAN_CH_LIST_PRIM_CH_WIDTH BIT(22)
+
+/* Index into power_list for tx power of channel */
+#define HW_SCAN_CH_LIST_PWR_LIST_IDX GENMASK(31, 26)
+
+struct hw_scan_tlv_hdr {
+	__le16 tag;
+	__le16 len;
+} __packed;
+
+struct hw_scan_tlv_channel_list {
+	struct hw_scan_tlv_hdr hdr;
+	__le32 channels[];
+} __packed;
+
+struct hw_scan_tlv_power_list {
+	struct hw_scan_tlv_hdr hdr;
+	s32 tx_power_qdbm[];
+} __packed;
+
+struct hw_scan_tlv_probe_req {
+	struct hw_scan_tlv_hdr hdr;
+	/* Probe request frame template (including SSIDs) */
+	u8 buf[];
+} __packed;
+
+struct hw_scan_tlv_dwell_on_home {
+	struct hw_scan_tlv_hdr hdr;
+	/* Time to dwell on home between scan channels */
+	__le32 home_dwell_time_ms;
+} __packed;
+
+#define DOT11AH_BA_MAX_MPDU_PER_AMPDU (32)
+
+/* wiphy scan params */
+#define MM81X_MAX_SCAN_IE_LEN 512
+#define MM81X_MAX_SCAN_SSIDS 1
+#define MM81X_MAX_REMAIN_ON_CHAN_DURATION 10000
+
+static bool mm81x_reg_h_cc_equal(const char *cc1, const char *cc2)
+{
+	return (cc1[0] == cc2[0]) && (cc1[1] == cc2[1]);
+}
+
+static bool mm81x_tx_h_pkt_over_rts_threshold(struct mm81x *mors,
+					      struct ieee80211_tx_info *info,
+					      struct sk_buff *skb)
+{
+	u8 ccmp_len;
+
+	if (!info->control.hw_key)
+		return ((skb->len + FCS_LEN) > mors->rts_threshold);
+
+	if (info->control.hw_key->keylen == 32)
+		ccmp_len =
+			IEEE80211_CCMP_256_HDR_LEN + IEEE80211_CCMP_256_MIC_LEN;
+	else if (info->control.hw_key->keylen == 16)
+		ccmp_len = IEEE80211_CCMP_HDR_LEN + IEEE80211_CCMP_MIC_LEN;
+	else
+		ccmp_len = 0;
+
+	return ((skb->len + FCS_LEN + ccmp_len) > mors->rts_threshold);
+}
+
+static bool mm81x_tx_h_ps_filtered_for_sta(struct mm81x *mors,
+					   struct sk_buff *skb,
+					   struct ieee80211_sta *sta)
+{
+	struct mm81x_sta *mors_sta;
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+
+	if (!sta)
+		return false;
+
+	mors_sta = (struct mm81x_sta *)sta->drv_priv;
+
+	if (!mors_sta->tx_ps_filter_en)
+		return false;
+
+	dev_dbg(mors->dev, "Frame for sta[%pM] PS filtered", mors_sta->addr);
+
+	info->flags |= IEEE80211_TX_STAT_TX_FILTERED;
+	info->flags &= ~IEEE80211_TX_CTL_AMPDU;
+
+	ieee80211_tx_status_skb(mors->hw, skb);
+	return true;
+}
+
+static void mm81x_mac_check_fw_disabled_chans(struct ieee80211_hw *hw)
+{
+	int ret = 0;
+	u32 i;
+	struct mm81x *mors = hw->priv;
+	struct host_cmd_resp_get_disabled_channels *resp;
+	u32 resp_len = sizeof(struct host_cmd_disabled_channel_entry) *
+			       ARRAY_SIZE(mors_s1ghz_channels) +
+		       sizeof(*resp);
+
+	resp = kzalloc(resp_len, GFP_KERNEL);
+	if (!resp) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = mm81x_cmd_get_disabled_channels(mors, resp, resp_len);
+	if (ret)
+		goto out;
+
+	for (i = 0; i < ARRAY_SIZE(mors_s1ghz_channels); i++) {
+		struct ieee80211_channel *ch = &mors_s1ghz_channels[i];
+
+		if (ch->flags & IEEE80211_CHAN_DISABLED)
+			continue;
+
+		ch->flags &= ~IEEE80211_CHAN_S1G_NO_PRIMARY;
+	}
+
+	for (i = 0; i < le32_to_cpu(resp->n_channels); i++) {
+		struct ieee80211_channel *ch;
+		struct host_cmd_disabled_channel_entry *entry =
+			&resp->channels[i];
+
+		if (entry->bw_mhz != 1)
+			continue;
+
+		ch = ieee80211_get_channel_khz(
+			hw->wiphy,
+			KHZ100_TO_KHZ(le16_to_cpu(entry->freq_100khz)));
+		if (!ch)
+			continue;
+
+		ch->flags |= IEEE80211_CHAN_S1G_NO_PRIMARY;
+		dev_dbg(mors->dev, "set NO_PRIMARY on %u KHz",
+			ieee80211_channel_to_khz(ch));
+	}
+
+out:
+	if (ret)
+		dev_err(mors->dev, "failed to set disabled primary channels");
+
+	kfree(resp);
+}
+
+static int mm81x_mac_ops_start(struct ieee80211_hw *hw)
+{
+	struct mm81x *mors = hw->priv;
+
+	mors->started = true;
+	return 0;
+}
+
+static int mm81x_tx_h_get_max_bw(struct mm81x *mors)
+{
+	return MM81X_FW_SUPP(&mors->fw_caps, 8MHZ) ? 8 :
+	       MM81X_FW_SUPP(&mors->fw_caps, 4MHZ) ? 4 :
+	       MM81X_FW_SUPP(&mors->fw_caps, 2MHZ) ? 2 :
+						     1;
+}
+
+static void mm81x_mac_caps_init(struct mm81x *mors)
+{
+	struct mm81x_fw_caps *fw_caps = &mors->fw_caps;
+	struct ieee80211_sta_s1g_cap *s1g = &mors_band_s1ghz.s1g_cap;
+
+#define __FW_CAP_N(_n, _cap, _bit)                \
+	do {                                      \
+		if (MM81X_FW_SUPP(fw_caps, _cap)) \
+			s1g->cap[_n] |= (_bit);   \
+	} while (0)
+
+#define FW_CAP0(_cap, _bit) __FW_CAP_N(0, _cap, _bit)
+#define FW_CAP3(_cap, _bit) __FW_CAP_N(3, _cap, _bit)
+#define FW_CAP5(_cap, _bit) __FW_CAP_N(5, _cap, _bit)
+#define FW_CAP6(_cap, _bit) __FW_CAP_N(6, _cap, _bit)
+#define FW_CAP7(_cap, _bit) __FW_CAP_N(7, _cap, _bit)
+#define FW_CAP8(_cap, _bit) __FW_CAP_N(8, _cap, _bit)
+#define FW_CAP9(_cap, _bit) __FW_CAP_N(9, _cap, _bit)
+
+	FW_CAP0(S1G_LONG, S1G_CAP0_S1G_LONG);
+
+	s1g->cap[0] |= S1G_CAP0_SGI_1MHZ;
+	if (MM81X_FW_SUPP(fw_caps, SGI)) {
+		FW_CAP0(2MHZ, S1G_CAP0_SGI_2MHZ);
+		FW_CAP0(4MHZ, S1G_CAP0_SGI_4MHZ);
+		FW_CAP0(8MHZ, S1G_CAP0_SGI_8MHZ);
+	}
+
+	if (MM81X_FW_SUPP(fw_caps, 8MHZ))
+		s1g->cap[0] |= S1G_SUPP_CH_WIDTH_8;
+	else if (MM81X_FW_SUPP(fw_caps, 4MHZ))
+		s1g->cap[0] |= S1G_SUPP_CH_WIDTH_4;
+	else if (MM81X_FW_SUPP(fw_caps, 2MHZ))
+		s1g->cap[0] |= S1G_SUPP_CH_WIDTH_2;
+
+	FW_CAP3(RD_RESPONDER, S1G_CAP3_RD_RESPONDER);
+	FW_CAP3(LONG_MPDU, S1G_CAP3_MAX_MPDU_LEN);
+
+	FW_CAP5(AMSDU, S1G_CAP5_AMSDU);
+	FW_CAP5(AMPDU, S1G_CAP5_AMPDU);
+	FW_CAP5(ASYMMETRIC_BA_SUPPORT, S1G_CAP5_ASYMMETRIC_BA);
+	FW_CAP5(FLOW_CONTROL, S1G_CAP5_FLOW_CONTROL);
+
+	FW_CAP6(OBSS_MITIGATION, S1G_CAP6_OBSS_MITIGATION);
+	FW_CAP6(FRAGMENT_BA, S1G_CAP6_FRAGMENT_BA);
+	FW_CAP6(NDP_PSPOLL, S1G_CAP6_NDP_PS_POLL);
+	FW_CAP6(TXOP_SHARING_IMPLICIT_ACK, S1G_CAP6_TXOP_SHARING_IMP_ACK);
+	FW_CAP6(HTC_VHT_MFB, S1G_CAP6_VHT_LINK_ADAPT);
+
+	FW_CAP7(TACK_AS_PSPOLL, S1G_CAP7_TACK_AS_PS_POLL);
+	FW_CAP7(DUPLICATE_1MHZ, S1G_CAP7_DUP_1MHZ);
+	FW_CAP7(MCS_NEGOTIATION, S1G_CAP7_MCS_NEGOTIATION);
+	FW_CAP7(1MHZ_CONTROL_RESPONSE_PREAMBLE,
+		S1G_CAP7_1MHZ_CTL_RESPONSE_PREAMBLE);
+	FW_CAP7(SECTOR_TRAINING, S1G_CAP7_SECTOR_TRAINING_OPERATION);
+	FW_CAP7(TMP_PS_MODE_SWITCH, S1G_CAP7_TEMP_PS_MODE_SWITCH);
+
+	FW_CAP8(BDT, S1G_CAP8_BDT);
+
+	FW_CAP9(LINK_ADAPTATION_WO_NDP_CMAC,
+		S1G_CAP9_LINK_ADAPT_PER_CONTROL_RESPONSE);
+
+	/* 1SS MCS 9 for Rx / Tx map */
+	s1g->nss_mcs[0] = MM81X_NSS_MCS_BYTE_0;
+	s1g->nss_mcs[1] = MM81X_NSS_MCS_BYTE_1;
+	s1g->nss_mcs[2] = MM81X_NSS_MCS_BYTE_2;
+	s1g->nss_mcs[3] = MM81X_NSS_MCS_BYTE_3;
+	s1g->nss_mcs[4] = MM81X_NSS_MCS_BYTE_4;
+
+#undef FW_CAP0
+#undef FW_CAP3
+#undef FW_CAP5
+#undef FW_CAP6
+#undef FW_CAP7
+#undef FW_CAP8
+#undef FW_CAP9
+#undef __FW_CAP_N
+}
+
+static void mm81x_mac_beacon_irq_enable(struct mm81x_vif *mors_vif, bool enable)
+{
+	struct mm81x *mors = mm81x_vif_to_mors(mors_vif);
+	u8 beacon_irq_num = MM81X_INT_BEACON_BASE_NUM + mors_vif->id;
+
+	enable ? set_bit(beacon_irq_num, &mors->beacon_irqs_enabled) :
+		 clear_bit(beacon_irq_num, &mors->beacon_irqs_enabled);
+
+	mm81x_hw_irq_enable(mors, beacon_irq_num, enable);
+}
+
+static void mm81x_beacon_h_fill_tx_info(struct mm81x *mors,
+					struct mm81x_skb_tx_info *tx_info,
+					struct mm81x_vif *mors_vif,
+					int tx_bw_mhz)
+{
+	enum dot11_bandwidth bw_idx =
+		mm81x_ratecode_bw_mhz_to_bw_index(tx_bw_mhz);
+	enum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT;
+
+	tx_info->flags |=
+		cpu_to_le32(MM81X_TX_CONF_FLAGS_VIF_ID_SET(mors_vif->id));
+
+	if (bw_idx == DOT11_BANDWIDTH_1MHZ)
+		pream = MM81X_RATE_PREAMBLE_S1G_1M;
+
+	tx_info->rates[0].count = 1;
+	tx_info->rates[1].count = 0;
+	tx_info->rates[0].mm81x_ratecode =
+		mm81x_ratecode_init(bw_idx, 0, 0, pream);
+
+	if (mors->firmware_flags & MM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION)
+		tx_info->flags |=
+			cpu_to_le32(MM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT);
+}
+
+static void mm81x_mac_beacon_work(struct work_struct *work)
+{
+	struct mm81x_vif *mors_vif =
+		from_work(mors_vif, work, u.ap.beacon_work);
+	struct mm81x *mors = mm81x_vif_to_mors(mors_vif);
+	struct mm81x_skbq *mq;
+	struct sk_buff *beacon;
+	struct ieee80211_vif *vif = mm81x_vif_to_ieee80211_vif(mors_vif);
+	struct mm81x_skb_tx_info tx_info = { 0 };
+	int num_bcn_vifs = atomic_read(&mors->num_bcn_vifs);
+
+	mq = mm81x_hif_get_tx_beacon_queue(mors);
+	if (!mq) {
+		dev_err(mors->dev, "no matching beacon Q found");
+		return;
+	}
+
+	if (mm81x_skbq_count(mq) >= num_bcn_vifs) {
+		dev_err(mors->dev,
+			"previous beacon not consumed, dropping req [id:%d]",
+			mors_vif->id);
+		return;
+	}
+
+	beacon = ieee80211_beacon_get(mors->hw, vif, false);
+	if (!beacon)
+		return;
+
+	mm81x_beacon_h_fill_tx_info(mors, &tx_info, mors_vif,
+				    cfg80211_chandef_s1g_pri_width(&mors->chandef));
+	mm81x_skbq_skb_tx(mq, &beacon, &tx_info, MM81X_SKB_CHAN_BEACON);
+}
+
+void mm81x_mac_beacon_irq_handle(struct mm81x *mors, u32 status)
+{
+	int vif_id;
+	unsigned long masked_status = (status & mors->beacon_irqs_enabled) >>
+				      MM81X_INT_BEACON_BASE_NUM;
+
+	guard(rcu)();
+	for_each_set_bit(vif_id, &masked_status, MM81X_MAX_IF) {
+		struct mm81x_vif *mors_vif;
+		struct ieee80211_vif *vif;
+
+		vif = mm81x_rcu_dereference_vif_id(mors, vif_id, true);
+		if (vif) {
+			mors_vif = ieee80211_vif_to_mors_vif(vif);
+			queue_work(system_bh_wq, &mors_vif->u.ap.beacon_work);
+		}
+	}
+}
+
+static void mm81x_mac_beacon_init(struct mm81x_vif *mors_vif)
+{
+	struct mm81x *mors = mm81x_vif_to_mors(mors_vif);
+
+	INIT_WORK(&mors_vif->u.ap.beacon_work, mm81x_mac_beacon_work);
+	mm81x_mac_beacon_irq_enable(mors_vif, true);
+	atomic_inc(&mors->num_bcn_vifs);
+}
+
+static struct hw_scan_tlv_hdr mm81x_hw_scan_h_pack_tlv_hdr(u16 tag, u16 len)
+{
+	struct hw_scan_tlv_hdr hdr = { .tag = cpu_to_le16(tag),
+				       .len = cpu_to_le16(len) };
+	return hdr;
+}
+
+static __le32 mm81x_hw_scan_h_pack_channel(struct ieee80211_channel *chan,
+					   u8 pwr_idx)
+{
+	__le32 packed = 0;
+	u32 freq_khz = ieee80211_channel_to_khz(chan);
+
+	packed |= le32_encode_bits(freq_khz, HW_SCAN_CH_LIST_FREQ_KHZ);
+	packed |= le32_encode_bits(mm81x_ratecode_bw_mhz_to_bw_index(1),
+				   HW_SCAN_CH_LIST_OP_BW);
+	packed |= le32_encode_bits(mm81x_ratecode_bw_mhz_to_bw_index(1),
+				   HW_SCAN_CH_LIST_PRIM_CH_WIDTH);
+	packed |= le32_encode_bits(pwr_idx, HW_SCAN_CH_LIST_PWR_LIST_IDX);
+
+	return packed;
+}
+
+static u8 *
+mm81x_hw_scan_h_add_channel_list_tlv(u8 *buf,
+				     struct mm81x_hw_scan_params *params)
+{
+	int i;
+	struct hw_scan_tlv_channel_list *ch_list =
+		(struct hw_scan_tlv_channel_list *)buf;
+
+	ch_list->hdr = mm81x_hw_scan_h_pack_tlv_hdr(
+		HOST_CMD_HW_SCAN_TLV_TAG_CHAN_LIST,
+		params->num_chans * sizeof(ch_list->channels[0]));
+
+	for (i = 0; i < params->num_chans; i++) {
+		struct ieee80211_channel *chan = params->channels[i].channel;
+
+		ch_list->channels[i] = mm81x_hw_scan_h_pack_channel(
+			chan, params->channels[i].power_idx);
+	}
+
+	return (u8 *)&ch_list->channels[i];
+}
+
+static u8 *
+mm81x_hw_scan_h_add_power_list_tlv(u8 *buf, struct mm81x_hw_scan_params *params)
+{
+	int i;
+	struct hw_scan_tlv_power_list *pwr_list =
+		(struct hw_scan_tlv_power_list *)buf;
+	size_t size = sizeof(pwr_list->tx_power_qdbm[0]) * params->n_powers;
+
+	pwr_list->hdr = mm81x_hw_scan_h_pack_tlv_hdr(
+		HOST_CMD_HW_SCAN_TLV_TAG_POWER_LIST, size);
+
+	for (i = 0; i < params->n_powers; i++)
+		pwr_list->tx_power_qdbm[i] = params->powers_qdbm[i];
+
+	return (u8 *)&pwr_list->tx_power_qdbm[i];
+}
+
+static u8 *
+mm81x_hw_scan_h_add_probe_req_tlv(u8 *buf, struct mm81x_hw_scan_params *params)
+{
+	struct sk_buff *skb = params->probe_req;
+	struct hw_scan_tlv_probe_req *probe_req =
+		(struct hw_scan_tlv_probe_req *)buf;
+
+	probe_req->hdr = mm81x_hw_scan_h_pack_tlv_hdr(
+		HOST_CMD_HW_SCAN_TLV_TAG_PROBE_REQ, skb->len);
+	memcpy(probe_req->buf, skb->data, skb->len);
+
+	return buf + sizeof(*probe_req) + skb->len;
+}
+
+static u8 *
+mm81x_hw_scan_h_insert_dwell_time_tlv(u8 *buf,
+				      struct mm81x_hw_scan_params *params)
+{
+	struct hw_scan_tlv_dwell_on_home *dwell =
+		(struct hw_scan_tlv_dwell_on_home *)buf;
+
+	dwell->hdr = mm81x_hw_scan_h_pack_tlv_hdr(
+		HOST_CMD_HW_SCAN_TLV_TAG_DWELL_ON_HOME,
+		sizeof(*dwell) - sizeof(dwell->hdr));
+	dwell->home_dwell_time_ms = cpu_to_le32(params->dwell_on_home_ms);
+
+	return buf + sizeof(*dwell);
+}
+
+static int __mm81x_hw_scan_h_init_probe_req(struct mm81x_hw_scan_params *params,
+					    u8 *ssid, u8 ssid_len,
+					    struct ieee80211_scan_ies *ies)
+{
+	u8 *pos;
+	struct sk_buff *probe_req;
+	struct ieee80211_tx_info *info;
+	u16 ies_len = ies->len[NL80211_BAND_S1GHZ] + ies->common_ie_len;
+
+	probe_req = ieee80211_probereq_get(params->hw, params->vif->addr, ssid,
+					   ssid_len, ies_len);
+	if (!probe_req)
+		return -ENOMEM;
+
+	pos = skb_put(probe_req, ies_len);
+	memcpy(pos, ies->common_ies, ies->common_ie_len);
+	pos += ies->common_ie_len;
+	memcpy(pos, ies->ies[NL80211_BAND_S1GHZ], ies->len[NL80211_BAND_S1GHZ]);
+
+	info = IEEE80211_SKB_CB(probe_req);
+	info->control.vif = params->vif;
+	params->probe_req = probe_req;
+
+	return 0;
+}
+
+static void mm81x_hw_scan_h_init_ssid(struct mm81x *mors,
+				      struct cfg80211_ssid *ssids, int n_ssids,
+				      u8 **out_ssid, u8 *out_ssid_len)
+{
+	*out_ssid = NULL;
+	*out_ssid_len = 0;
+
+	if (n_ssids > 0) {
+		if (n_ssids > 1) {
+			dev_warn(
+				mors->dev,
+				"Multiple SSIDs found when only one supported. Using the first only.");
+		}
+		*out_ssid_len = ssids[0].ssid_len;
+		*out_ssid = ssids[0].ssid;
+	}
+}
+
+static int
+mm81x_hw_scan_h_init_probe_req(struct mm81x_hw_scan_params *params,
+			       struct ieee80211_scan_request *scan_req)
+{
+	struct mm81x *mors = params->hw->priv;
+	struct cfg80211_scan_request *req = &scan_req->req;
+	struct ieee80211_scan_ies *ies = &scan_req->ies;
+	u8 ssid_len = 0;
+	u8 *ssid = NULL;
+
+	mm81x_hw_scan_h_init_ssid(mors, req->ssids, req->n_ssids, &ssid,
+				  &ssid_len);
+
+	return __mm81x_hw_scan_h_init_probe_req(params, ssid, ssid_len, ies);
+}
+
+static bool
+mm81x_hw_scan_h_is_chan_present(const struct mm81x_hw_scan_params *params,
+				const struct ieee80211_channel *chan)
+{
+	int channel;
+
+	for (channel = 0; channel < params->num_chans; channel++) {
+		if (params->channels[channel].channel == chan)
+			return true;
+	}
+
+	return false;
+}
+
+static int mm81x_hw_scan_h_insert_chan(struct mm81x_hw_scan_params *params,
+				       struct ieee80211_channel *chan)
+{
+	if (!params->channels)
+		return -EFAULT;
+
+	if (!chan)
+		return -EFAULT;
+
+	if (params->num_chans >= params->allocated_chans)
+		return -ENOMEM;
+
+	if (mm81x_hw_scan_h_is_chan_present(params, chan))
+		return 0;
+
+	params->channels[params->num_chans].channel = chan;
+	params->num_chans++;
+	return 0;
+}
+
+static int mm81x_hw_scan_h_init_chan_list(struct mm81x_hw_scan_params *params,
+					  struct ieee80211_channel **chans,
+					  u32 n_channels)
+{
+	int i, j;
+	int num_pwrs_coarse = 0;
+	int last_pwr = INT_MIN;
+	int chans_to_allocate = 0;
+
+	for (i = 0; i < n_channels; i++)
+		if (chans[i])
+			chans_to_allocate++;
+
+	params->num_chans = 0;
+	params->allocated_chans = 0;
+	params->channels = kcalloc(chans_to_allocate, sizeof(*params->channels),
+				   GFP_KERNEL);
+	if (!params->channels)
+		return -ENOMEM;
+
+	params->allocated_chans = chans_to_allocate;
+
+	for (i = 0; i < n_channels; i++)
+		if (chans[i])
+			mm81x_hw_scan_h_insert_chan(params, chans[i]);
+
+	/*
+	 * Calculate a rough estimate of number of different channel
+	 * powers required
+	 */
+	for (i = 0; i < params->num_chans; i++) {
+		if (chans[i]->max_reg_power != last_pwr) {
+			last_pwr = chans[i]->max_reg_power;
+			num_pwrs_coarse++;
+		}
+	}
+
+	params->powers_qdbm = kmalloc_array(
+		num_pwrs_coarse, sizeof(*params->powers_qdbm), GFP_KERNEL);
+	if (!params->powers_qdbm)
+		return -ENOMEM;
+
+	params->n_powers = 0;
+
+	for (i = 0; i < params->num_chans; i++) {
+		s32 power_qdbm =
+			MBM_TO_QDBM(DBM_TO_MBM(chans[i]->max_reg_power));
+
+		/* Try and find the power in the list */
+		for (j = 0; j < params->n_powers; j++)
+			if (params->powers_qdbm[j] == power_qdbm)
+				break;
+
+		/* Reached the end of the list - add the new power option */
+		if (j == params->n_powers) {
+			params->powers_qdbm[j] = power_qdbm;
+			params->n_powers++;
+			if (params->n_powers > num_pwrs_coarse) {
+				WARN_ON(1);
+				return -EFAULT;
+			}
+		}
+
+		/* Give the index of the power level to the channel */
+		params->channels[i].power_idx = j;
+	}
+	return 0;
+}
+
+static void mm81x_hw_scan_h_clean_params(struct mm81x_hw_scan_params *params)
+{
+	if (params->probe_req)
+		dev_kfree_skb_any(params->probe_req);
+	kfree(params->channels);
+	kfree(params->powers_qdbm);
+
+	params->num_chans = 0;
+	params->allocated_chans = 0;
+}
+
+size_t mm81x_hw_scan_h_get_cmd_size(struct mm81x_hw_scan_params *params)
+{
+	struct hw_scan_tlv_channel_list *ch_list;
+	struct hw_scan_tlv_power_list *pwr_list;
+	struct hw_scan_tlv_probe_req *probe_req;
+	struct hw_scan_tlv_dwell_on_home *dwell;
+	struct host_cmd_req_hw_scan *req;
+	size_t cmd_size = sizeof(*req);
+
+	/* No TLVs if simple abort command */
+	if (params->operation != MM81X_HW_SCAN_OP_START)
+		return cmd_size;
+
+	cmd_size += struct_size(ch_list, channels, params->num_chans);
+	cmd_size += struct_size(pwr_list, tx_power_qdbm, params->n_powers);
+
+	if (params->probe_req)
+		cmd_size += struct_size(probe_req, buf, params->probe_req->len);
+	if (params->dwell_on_home_ms)
+		cmd_size += sizeof(*dwell);
+
+	return cmd_size;
+}
+
+u8 *mm81x_hw_scan_h_insert_tlvs(struct mm81x_hw_scan_params *params, u8 *buf)
+{
+	buf = mm81x_hw_scan_h_add_channel_list_tlv(buf, params);
+	buf = mm81x_hw_scan_h_add_power_list_tlv(buf, params);
+
+	if (params->dwell_on_home_ms)
+		buf = mm81x_hw_scan_h_insert_dwell_time_tlv(buf, params);
+	if (params->probe_req)
+		buf = mm81x_hw_scan_h_add_probe_req_tlv(buf, params);
+
+	return buf;
+}
+
+static u32 mm81x_hw_scan_h_get_dwell_on_home(struct mm81x *mors,
+					     struct ieee80211_vif *vif)
+{
+	if (vif->type == NL80211_IFTYPE_STATION && vif->cfg.assoc)
+		return mors->hw_scan.home_dwell_ms;
+	return 0;
+}
+
+static struct mm81x_hw_scan_params *
+__mm81x_hw_scan_h_init_params(struct mm81x *mors)
+{
+	struct mm81x_hw_scan_params *params = mors->hw_scan.params;
+
+	if (!params) {
+		params = kzalloc_obj(*params, GFP_KERNEL);
+		if (params)
+			mors->hw_scan.params = params;
+	} else {
+		mm81x_hw_scan_h_clean_params(params);
+		memset(params, 0, sizeof(*params));
+	}
+
+	return params;
+}
+
+static int mm81x_hw_scan_h_init_params(struct mm81x *mors,
+				       struct ieee80211_hw *hw,
+				       struct ieee80211_vif *vif,
+				       struct cfg80211_scan_request *req)
+{
+	struct mm81x_hw_scan_params *params = mors->hw_scan.params;
+
+	params = __mm81x_hw_scan_h_init_params(mors);
+	if (!params) {
+		mors->hw_scan.state = HW_SCAN_STATE_IDLE;
+		return -ENOMEM;
+	}
+
+	params->hw = hw;
+	params->vif = vif;
+	params->has_directed_ssid = (req->ssids && req->ssids[0].ssid_len > 0);
+	params->operation = MM81X_HW_SCAN_OP_START;
+	params->dwell_on_home_ms = mm81x_hw_scan_h_get_dwell_on_home(mors, vif);
+
+	if (req->duration)
+		params->dwell_time_ms = MM81X_TU_TO_MS(req->duration);
+	else if (req->n_ssids == 0)
+		params->dwell_time_ms =
+			MM81X_HWSCAN_DEFAULT_PASSIVE_DWELL_TIME_MS;
+	else
+		params->dwell_time_ms = MM81X_HWSCAN_DEFAULT_DWELL_TIME_MS;
+
+	return 0;
+}
+
+static u32 mm81x_hw_scan_h_calc_timeout(struct mm81x_hw_scan_params *params)
+{
+	u32 ret = 0;
+
+	ret = params->dwell_time_ms + params->dwell_on_home_ms;
+	if (params->probe_req)
+		ret += MM81X_HWSCAN_PROBE_DELAY_MS;
+
+	ret *= params->num_chans;
+	ret += MM81X_HWSCAN_TIMEOUT_OVERHEAD_MS;
+
+	return ret;
+}
+
+static int mm81x_mac_ops_hw_scan(struct ieee80211_hw *hw,
+				 struct ieee80211_vif *vif,
+				 struct ieee80211_scan_request *hw_req)
+{
+	int ret = 0;
+	struct mm81x *mors = hw->priv;
+	struct cfg80211_scan_request *req = &hw_req->req;
+	struct mm81x_hw_scan_params *params;
+	struct ieee80211_channel **chans = hw_req->req.channels;
+
+	dev_dbg(mors->dev, "state %d", mors->hw_scan.state);
+
+	if (!mors->started) {
+		dev_warn(mors->dev, "device not ready");
+		ret = -ENODEV;
+		goto exit;
+	}
+
+	switch (mors->hw_scan.state) {
+	case HW_SCAN_STATE_IDLE:
+		mors->hw_scan.state = HW_SCAN_STATE_RUNNING;
+		reinit_completion(&mors->hw_scan.scan_done);
+		break;
+	case HW_SCAN_STATE_RUNNING:
+	case HW_SCAN_STATE_ABORTING:
+		ret = -EBUSY;
+		goto exit;
+	}
+
+	ret = mm81x_hw_scan_h_init_params(mors, hw, vif, req);
+	if (ret)
+		goto exit;
+
+	params = mors->hw_scan.params;
+
+	ret = mm81x_hw_scan_h_init_chan_list(params, chans,
+					     hw_req->req.n_channels);
+	if (ret)
+		goto exit;
+
+	/* Only init the probe request template if this is an active scan */
+	if (req->n_ssids > 0) {
+		ret = mm81x_hw_scan_h_init_probe_req(params, hw_req);
+		if (ret) {
+			dev_err(mors->dev, "Failed to init probe req %d", ret);
+			goto exit;
+		}
+	}
+
+	ret = mm81x_cmd_hw_scan(mors, params, false);
+	if (ret) {
+		mors->hw_scan.state = HW_SCAN_STATE_IDLE;
+		goto exit;
+	}
+
+	ieee80211_queue_delayed_work(
+		mors->hw, &mors->hw_scan.timeout,
+		msecs_to_jiffies(mm81x_hw_scan_h_calc_timeout(params)));
+exit:
+	return ret;
+}
+
+static void mm81x_hw_scan_abort(struct mm81x *mors)
+{
+	int ret;
+	struct mm81x_hw_scan_params params = { 0 };
+
+	switch (mors->hw_scan.state) {
+	case HW_SCAN_STATE_IDLE:
+	case HW_SCAN_STATE_ABORTING:
+		/* scan not running */
+		return;
+	case HW_SCAN_STATE_RUNNING:
+		mors->hw_scan.state = HW_SCAN_STATE_ABORTING;
+		break;
+	}
+
+	params.operation = MM81X_HW_SCAN_OP_STOP;
+
+	ret = mm81x_cmd_hw_scan(mors, &params, false);
+
+	if (ret || !mors->started ||
+	    !wait_for_completion_timeout(&mors->hw_scan.scan_done, 1 * HZ)) {
+		/*
+		 * We may have lost the event on the bus, the chip could be
+		 * wedged, or the cmd failed for another reason. Nevertheless,
+		 * we should call the done event so mac80211 knows to unblock
+		 * itself.
+		 */
+		struct cfg80211_scan_info info = { .aborted = true };
+
+		ieee80211_scan_completed(mors->hw, &info);
+		mors->hw_scan.state = HW_SCAN_STATE_IDLE;
+	}
+}
+
+static void mm81x_mac_ops_cancel_hw_scan(struct ieee80211_hw *hw,
+					 struct ieee80211_vif *vif)
+{
+	struct mm81x *mors = hw->priv;
+
+	cancel_delayed_work_sync(&mors->hw_scan.timeout);
+	mm81x_hw_scan_abort(mors);
+}
+
+static void mm81x_mac_hw_scan_done_event(struct ieee80211_hw *hw)
+{
+	struct mm81x *mors = hw->priv;
+	struct cfg80211_scan_info info = { 0 };
+
+	dev_dbg(mors->dev, "completing hw scan");
+
+	switch (mors->hw_scan.state) {
+	case HW_SCAN_STATE_IDLE:
+		/* Scan has already been stopped. Just continue */
+		goto exit;
+	case HW_SCAN_STATE_RUNNING:
+	case HW_SCAN_STATE_ABORTING:
+		info.aborted = (mors->hw_scan.state == HW_SCAN_STATE_ABORTING);
+		mors->hw_scan.state = HW_SCAN_STATE_IDLE;
+	}
+
+	ieee80211_scan_completed(mors->hw, &info);
+exit:
+	complete(&mors->hw_scan.scan_done);
+	cancel_delayed_work_sync(&mors->hw_scan.timeout);
+}
+
+static void mm81x_mac_hw_scan_timeout_work(struct work_struct *work)
+{
+	struct mm81x *mors =
+		container_of(work, struct mm81x, hw_scan.timeout.work);
+
+	dev_err(mors->dev, "hw scan timed out, aborting");
+	mm81x_hw_scan_abort(mors);
+}
+
+static void mm81x_mac_hw_scan_init(struct mm81x *mors)
+{
+	mors->hw_scan.state = HW_SCAN_STATE_IDLE;
+	mors->hw_scan.params = NULL;
+	mors->hw_scan.home_dwell_ms = MM81X_HWSCAN_DEFAULT_DWELL_ON_HOME_MS;
+
+	init_completion(&mors->hw_scan.scan_done);
+	INIT_DELAYED_WORK(&mors->hw_scan.timeout,
+			  mm81x_mac_hw_scan_timeout_work);
+}
+
+static void mm81x_mac_hw_scan_destroy(struct mm81x *mors)
+{
+	cancel_delayed_work_sync(&mors->hw_scan.timeout);
+	if (mors->hw_scan.params)
+		mm81x_hw_scan_h_clean_params(mors->hw_scan.params);
+	kfree(mors->hw_scan.params);
+	mors->hw_scan.params = NULL;
+}
+
+static void mm81x_mac_hw_scan_finish(struct mm81x *mors)
+{
+	struct cfg80211_scan_info info = {
+		.aborted = true,
+	};
+
+	if (mors->hw_scan.state == HW_SCAN_STATE_IDLE)
+		return;
+
+	ieee80211_scan_completed(mors->hw, &info);
+	complete(&mors->hw_scan.scan_done);
+	mors->hw_scan.state = HW_SCAN_STATE_IDLE;
+	cancel_delayed_work_sync(&mors->hw_scan.timeout);
+}
+
+int mm81x_mac_event_recv(struct mm81x *mors, struct sk_buff *skb)
+{
+	struct host_cmd_event *event = (struct host_cmd_event *)(skb->data);
+	u16 event_id = le16_to_cpu(event->hdr.message_id);
+	u16 event_iid = le16_to_cpu(event->hdr.host_id);
+	u16 vif_id = le16_to_cpu(event->hdr.vif_id);
+	struct ieee80211_vif *vif;
+
+	if (!HOST_CMD_IS_EVT(event) || event_iid != 0)
+		return -EINVAL;
+
+	switch (event_id) {
+	case HOST_CMD_ID_EVT_HW_SCAN_DONE:
+		dev_dbg(mors->dev,
+			"Event: HOST_CMD_ID_EVT_HW_SCAN_DONE Received.");
+		mm81x_mac_hw_scan_done_event(mors->hw);
+		break;
+	case HOST_CMD_ID_EVT_BEACON_LOSS:
+		dev_dbg(mors->dev,
+			"Event: HOST_CMD_ID_EVT_BEACON_LOSS Received");
+		scoped_guard(rcu) {
+			vif = mm81x_rcu_dereference_vif_id(mors, vif_id, true);
+			if (vif)
+				ieee80211_beacon_loss(vif);
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static void mm81x_tx_h_apply_mcs10(struct mm81x *mors,
+				   struct mm81x_skb_tx_info *tx_info)
+{
+	u8 i;
+	u8 j;
+	int mcs0_first_idx = -1;
+	int mcs0_last_idx = -1;
+
+	/* Find out where our first and last MCS0 entries are. */
+	for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) {
+		enum dot11_bandwidth bw_idx = mm81x_ratecode_bw_index_get(
+			tx_info->rates[i].mm81x_ratecode);
+
+		if (bw_idx == DOT11_BANDWIDTH_1MHZ) {
+			mcs0_last_idx = i;
+			if (mcs0_first_idx == -1)
+				mcs0_first_idx = i;
+		}
+
+		/*
+		 * If the count is 0 then we are at the end of the table.
+		 * Break to allow us to reuse i indicating the end of the
+		 * table.
+		 */
+		if (tx_info->rates[i].count == 0)
+			break;
+	}
+
+	/* If there aren't any MCS0 (at 1MHz) entries we are done. */
+	if (mcs0_first_idx < 0)
+		return;
+
+	/*
+	 * If we are in MCS10_MODE_AUTO add MCS10 counts to the table if they
+	 * will fit. There should be three cases:
+	 *
+	 * - There is one MSC0 entry and the table is full -> do nothing
+	 * - There is one MSC0 entry and the table has space -> adjust MSC0
+	 *   down and add MCS 10
+	 * - There are multiple MCS0 entries -> replace entries after the first
+	 *   with MCS 10
+	 */
+	/* Case 3 - replace additional entries. */
+	if (mcs0_last_idx > mcs0_first_idx) {
+		for (j = mcs0_first_idx + 1; j < i; j++) {
+			enum dot11_bandwidth bw_idx =
+				mm81x_ratecode_bw_index_get(
+					tx_info->rates[j].mm81x_ratecode);
+			u8 mcs_index = mm81x_ratecode_mcs_index_get(
+				tx_info->rates[j].mm81x_ratecode);
+			if (mcs_index == 0 && bw_idx == DOT11_BANDWIDTH_1MHZ) {
+				mm81x_ratecode_mcs_index_set(
+					&tx_info->rates[j].mm81x_ratecode, 10);
+			}
+		}
+		/* Case 2 - add additional MCS10 entry. */
+	} else if (mcs0_last_idx == mcs0_first_idx &&
+		   i < (IEEE80211_TX_MAX_RATES)) {
+		int pre_mcs10_mcs0_count =
+			min_t(u8, tx_info->rates[mcs0_last_idx].count,
+			      MCS0_BEFORE_MCS10_COUNT);
+		int mcs10_count = tx_info->rates[mcs0_last_idx].count -
+				  pre_mcs10_mcs0_count;
+
+		/*
+		 * If there were less retries than our desired minimum MCS0 we
+		 * don't add MCS10 retries.
+		 */
+		if (mcs10_count > 0) {
+			/* Use the same flags for MCS10 as MCS0. */
+			tx_info->rates[i].mm81x_ratecode =
+				tx_info->rates[mcs0_last_idx].mm81x_ratecode;
+			mm81x_ratecode_mcs_index_set(
+				&tx_info->rates[i].mm81x_ratecode, 10);
+			tx_info->rates[mcs0_last_idx].count =
+				pre_mcs10_mcs0_count;
+			tx_info->rates[i].count = mcs10_count;
+		}
+	}
+}
+
+void mm81x_tx_h_check_aggr(struct ieee80211_sta *pubsta, struct sk_buff *skb)
+{
+	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+	struct mm81x_sta *mors_sta = (struct mm81x_sta *)pubsta->drv_priv;
+	u8 tid = ieee80211_get_tid(hdr);
+
+	/* we are already aggregating */
+	if (mors_sta->tid_tx[tid] || mors_sta->tid_start_tx[tid])
+		return;
+
+	if (mors_sta->state < IEEE80211_STA_AUTHORIZED)
+		return;
+
+	if (skb_get_queue_mapping(skb) == IEEE80211_AC_VO)
+		return;
+
+	if (unlikely(!ieee80211_is_data_qos(hdr->frame_control)))
+		return;
+
+	if (unlikely(skb->protocol == cpu_to_be16(ETH_P_PAE)))
+		return;
+
+	mors_sta->tid_start_tx[tid] = true;
+	ieee80211_start_tx_ba_session(pubsta, tid, 0);
+}
+
+int mm81x_tx_h_get_attempts(struct mm81x *mors,
+			    struct mm81x_skb_tx_status *tx_sts)
+{
+	int attempts = 0;
+	int i;
+	int count = min_t(int, MM81X_SKB_MAX_RATES, IEEE80211_TX_MAX_RATES);
+
+	for (i = 0; i < count; i++) {
+		if (tx_sts->rates[i].count > 0)
+			attempts += tx_sts->rates[i].count;
+		else
+			break;
+	}
+
+	return attempts;
+}
+
+static void mm81x_tx_h_fill_info(struct mm81x *mors,
+				 struct mm81x_skb_tx_info *tx_info,
+				 struct sk_buff *skb, struct ieee80211_vif *vif,
+				 int tx_bw_mhz, struct ieee80211_sta *sta)
+{
+	int i;
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+	struct mm81x_vif *mors_vif = ieee80211_vif_to_mors_vif(vif);
+	struct mm81x_sta *mors_sta = NULL;
+	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+	int op_bw_mhz = cfg80211_chandef_get_width(&mors->chandef);
+	u8 tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
+	bool rts_allowed = op_bw_mhz < 8;
+
+	if (sta)
+		mors_sta = (struct mm81x_sta *)sta->drv_priv;
+
+	rts_allowed &= mm81x_tx_h_pkt_over_rts_threshold(mors, info, skb);
+
+	mm81x_rc_sta_fill_tx_rates(mors, tx_info, skb, sta, tx_bw_mhz,
+				   rts_allowed);
+
+	for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) {
+		if (rts_allowed)
+			mm81x_ratecode_enable_rts(
+				&tx_info->rates[i].mm81x_ratecode);
+
+		if (info->control.rates[i].flags & IEEE80211_TX_RC_SHORT_GI)
+			mm81x_ratecode_enable_sgi(
+				&tx_info->rates[i].mm81x_ratecode);
+	}
+
+	/* Apply change of MCS0 to MCS10 if required. */
+	mm81x_tx_h_apply_mcs10(mors, tx_info);
+
+	tx_info->flags |=
+		cpu_to_le32(MM81X_TX_CONF_FLAGS_VIF_ID_SET(mors_vif->id));
+
+	if (info->flags & IEEE80211_TX_CTL_AMPDU)
+		tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_CTL_AMPDU);
+
+	if (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM)
+		tx_info->flags |=
+			cpu_to_le32(MM81X_TX_CONF_FLAGS_SEND_AFTER_DTIM);
+
+	if (info->flags & IEEE80211_TX_CTL_NO_PS_BUFFER) {
+		tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_NO_PS_BUFFER);
+
+		if (info->flags & IEEE80211_TX_STATUS_EOSP)
+			tx_info->flags |= cpu_to_le32(
+				MM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT);
+	} else if (ieee80211_is_mgmt(hdr->frame_control) &&
+		   !ieee80211_is_bufferable_mmpdu(skb)) {
+		tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_NO_PS_BUFFER);
+	}
+
+	if (info->control.hw_key) {
+		tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_HW_ENCRYPT);
+		tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_KEY_IDX_SET(
+			info->control.hw_key->hw_key_idx));
+	}
+
+	tx_info->tid = tid;
+	if (mors_sta) {
+		tx_info->tid_params = mors_sta->tid_params[tid];
+
+		if (info->flags & IEEE80211_TX_CTL_CLEAR_PS_FILT) {
+			if (mors_sta->tx_ps_filter_en)
+				dev_dbg(mors->dev,
+					"TX ps filter cleared sta[%pM]",
+					mors_sta->addr);
+			mors_sta->tx_ps_filter_en = false;
+		}
+	}
+}
+
+static void mm81x_mac_ops_tx(struct ieee80211_hw *hw,
+			     struct ieee80211_tx_control *control,
+			     struct sk_buff *skb)
+{
+	struct mm81x *mors = hw->priv;
+	struct mm81x_skbq *mq = NULL;
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+	struct ieee80211_vif *vif = info->control.vif;
+	struct mm81x_skb_tx_info tx_info = { 0 };
+	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+	bool is_mgmt = ieee80211_is_mgmt(hdr->frame_control);
+	int tx_bw_mhz = cfg80211_chandef_get_width(&mors->chandef);
+	struct ieee80211_sta *sta = control->sta;
+	int max_tx_bw = 0, sta_max_bw_mhz = 0;
+
+	if (sta) {
+		struct mm81x_sta *mors_sta = (struct mm81x_sta *)sta->drv_priv;
+
+		sta_max_bw_mhz = mors_sta->max_bw_mhz;
+	}
+
+	max_tx_bw = mm81x_tx_h_get_max_bw(mors);
+	tx_bw_mhz = min(max_tx_bw, tx_bw_mhz);
+
+	if (is_mgmt)
+		tx_bw_mhz = cfg80211_chandef_s1g_pri_width(&mors->chandef);
+	if (sta_max_bw_mhz)
+		tx_bw_mhz = min(tx_bw_mhz, sta_max_bw_mhz);
+	if (ieee80211_is_probe_resp(hdr->frame_control))
+		tx_bw_mhz = 1;
+
+	mm81x_tx_h_fill_info(mors, &tx_info, skb, vif, tx_bw_mhz, sta);
+
+	if (mm81x_tx_h_ps_filtered_for_sta(mors, skb, sta))
+		return;
+
+	if (is_mgmt)
+		mq = mm81x_hif_get_tx_mgmt_queue(mors);
+	else
+		mq = mm81x_hif_get_tx_data_queue(mors,
+						 dot11_tid_to_ac(tx_info.tid));
+
+	mm81x_skbq_skb_tx(mq, &skb, &tx_info,
+			  (is_mgmt) ? MM81X_SKB_CHAN_MGMT :
+				      MM81X_SKB_CHAN_DATA);
+}
+
+static void mm81x_mac_ops_stop(struct ieee80211_hw *hw, bool suspend)
+{
+	struct mm81x *mors = hw->priv;
+
+	mors->started = false;
+}
+
+static void mm81x_mac_beacon_finish(struct mm81x_vif *mors_vif)
+{
+	struct mm81x *mors = mm81x_vif_to_mors(mors_vif);
+
+	mm81x_mac_beacon_irq_enable(mors_vif, false);
+	cancel_work_sync(&mors_vif->u.ap.beacon_work);
+	/*
+	 * Side effect of the restarting required when
+	 * reacting to regdom changes...
+	 */
+	atomic_add_unless(&mors->num_bcn_vifs, -1, 0);
+}
+
+static void mm81x_mac_ops_remove_interface(struct ieee80211_hw *hw,
+					   struct ieee80211_vif *vif)
+{
+	int ret;
+	struct mm81x *mors = hw->priv;
+	struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv;
+
+	ret = mm81x_cmd_rm_if(mors, mors_vif->id);
+	if (ret)
+		dev_err(mors->dev, "mm81x_cmd_rm_if failed %d", ret);
+
+	RCU_INIT_POINTER(mors->vifs[mors_vif->id], NULL);
+}
+
+static s32 mm81x_mac_get_max_txpower(struct mm81x *mors)
+{
+	int ret;
+	s32 power_mbm;
+
+	/* Retrieve maximum TX power the chip can transmit */
+	ret = mm81x_cmd_get_max_txpower(mors, &power_mbm);
+	if (ret) {
+		dev_err(mors->dev, "using default tx max power %d mBm",
+			MAX_TX_POWER_MBM);
+		return MAX_TX_POWER_MBM;
+	}
+
+	dev_dbg(mors->dev, "Max tx power detected %d mBm", power_mbm);
+	return power_mbm;
+}
+
+static s32 mm81x_mac_set_txpower(struct mm81x *mors, s32 power_mbm)
+{
+	int ret;
+	s32 out_power_mbm;
+
+	if (mors->tx_max_power_mbm == INT_MAX)
+		mors->tx_max_power_mbm = mm81x_mac_get_max_txpower(mors);
+
+	power_mbm = min(power_mbm, mors->tx_max_power_mbm);
+	if (power_mbm == mors->tx_power_mbm)
+		return mors->tx_power_mbm;
+
+	ret = mm81x_cmd_set_txpower(mors, &out_power_mbm, power_mbm);
+	if (ret) {
+		dev_err(mors->dev, "failed, power %d mBm ret %d", power_mbm,
+			ret);
+		return mors->tx_power_mbm;
+	}
+
+	if (out_power_mbm != mors->tx_power_mbm) {
+		dev_dbg(mors->dev, "%d -> %d mBm", mors->tx_power_mbm,
+			out_power_mbm);
+		mors->tx_power_mbm = out_power_mbm;
+	}
+
+	return mors->tx_power_mbm;
+}
+
+static int mm81x_mac_set_channel(struct mm81x *mors, u32 op_chan_freq_hz,
+				 u8 pri_1mhz_chan_idx, u8 op_bw_mhz,
+				 u8 pri_bw_mhz)
+{
+	int ret;
+
+	ret = mm81x_cmd_set_channel(mors, op_chan_freq_hz, pri_1mhz_chan_idx,
+				    op_bw_mhz, pri_bw_mhz, &mors->tx_power_mbm);
+	if (ret) {
+		dev_err(mors->dev, "mm81x_cmd_set_channel() failed, ret %d",
+			ret);
+		return ret;
+	}
+
+	mm81x_mac_set_txpower(mors, mors->tx_power_mbm);
+	return 0;
+}
+
+static u8 mm81x_mac_pri_chan_to_index(const struct cfg80211_chan_def *chandef)
+{
+	u32 bw_mhz = cfg80211_chandef_get_width(chandef);
+	u32 op_center_khz = ieee80211_chandef_to_khz(chandef);
+	u32 first_1mhz_center_khz = op_center_khz - (bw_mhz * 500) + 500;
+	u32 pri_1mhz_khz = ieee80211_channel_to_khz(chandef->chan);
+
+	return (pri_1mhz_khz - first_1mhz_center_khz) / 1000;
+}
+
+static int mm81x_mac_ops_change_channel(struct ieee80211_hw *hw,
+					struct cfg80211_chan_def *chandef)
+{
+	int ret;
+	struct mm81x *mors = hw->priv;
+	u64 freq_hz = KHZ_TO_HZ(ieee80211_chandef_to_khz(chandef));
+	u8 op_bw_mhz = cfg80211_chandef_get_width(chandef);
+	u8 pri_1mhz_idx = mm81x_mac_pri_chan_to_index(chandef);
+	int pri_chan_width_mhz = cfg80211_chandef_s1g_pri_width(chandef);
+
+	dev_dbg(mors->dev, "ch: freq=%llu Hz bw=%u pri_idx=%d pri_bw=%d",
+		freq_hz, op_bw_mhz, pri_1mhz_idx, pri_chan_width_mhz);
+
+	ret = mm81x_mac_set_channel(mors, freq_hz, (u8)pri_1mhz_idx, op_bw_mhz,
+				    pri_chan_width_mhz);
+	if (ret)
+		return ret;
+
+	memcpy(&mors->chandef, chandef, sizeof(mors->chandef));
+	return 0;
+}
+
+static int mm81x_mac_ops_config(struct ieee80211_hw *hw, int radio_idx,
+				u32 changed)
+{
+	int ret;
+	struct mm81x *mors = hw->priv;
+	struct ieee80211_conf *conf = &hw->conf;
+	struct ieee80211_channel *channel = conf->chandef.chan;
+
+	if (!mors->started)
+		return 0;
+
+	if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
+		ret = mm81x_mac_ops_change_channel(hw, &conf->chandef);
+		if (ret < 0)
+			return ret;
+	}
+
+	if ((changed & IEEE80211_CONF_CHANGE_POWER) &&
+	    !(changed & IEEE80211_CONF_CHANGE_CHANNEL) &&
+	    !(conf->flags & IEEE80211_CONF_MONITOR)) {
+		s32 power_mbm = DBM_TO_MBM(conf->power_level);
+
+		power_mbm = min(DBM_TO_MBM(channel->max_reg_power), power_mbm);
+		power_mbm = mm81x_mac_set_txpower(mors, power_mbm);
+		conf->power_level = MBM_TO_DBM(power_mbm);
+	}
+
+	return 0;
+}
+
+static int mm81x_mac_ops_get_txpower(struct ieee80211_hw *hw,
+				     struct ieee80211_vif *vif,
+				     unsigned int link_id, int *dbm)
+{
+	struct mm81x *mors = hw->priv;
+	struct ieee80211_chanctx_conf *chanctx_conf;
+	struct cfg80211_chan_def *chandef = &vif->bss_conf.chanreq.oper;
+
+	scoped_guard(rcu) {
+		chanctx_conf = rcu_access_pointer(vif->bss_conf.chanctx_conf);
+		if (!chanctx_conf ||
+		    !cfg80211_chandef_identical(chandef, &chanctx_conf->def))
+			return -ENODATA;
+	}
+
+	*dbm = MBM_TO_DBM(mors->tx_power_mbm);
+	return 0;
+}
+
+static void mm81x_mac_config_ps(struct mm81x *mors, struct ieee80211_vif *vif)
+{
+	bool en_ps = vif->cfg.ps;
+
+	if (vif->type == NL80211_IFTYPE_AP || !mors->ps.enable)
+		return;
+
+	if (mors->config_ps == en_ps)
+		return;
+
+	dev_dbg(mors->dev, "change powersave mode: %d (current %d)", en_ps,
+		mors->config_ps);
+
+	mors->config_ps = en_ps;
+
+	if (en_ps) {
+		mm81x_cmd_set_ps(mors, true);
+		mm81x_ps_enable(mors);
+	} else {
+		mm81x_ps_disable(mors);
+		mm81x_cmd_set_ps(mors, false);
+	}
+}
+
+static void mm81x_mac_ops_bss_info_changed(struct ieee80211_hw *hw,
+					   struct ieee80211_vif *vif,
+					   struct ieee80211_bss_conf *info,
+					   u64 changed)
+{
+	int ret;
+	struct mm81x *mors = hw->priv;
+	struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv;
+
+	if (changed & BSS_CHANGED_PS)
+		mm81x_mac_config_ps(mors, vif);
+
+	if (changed & BSS_CHANGED_BEACON_ENABLED) {
+		mm81x_cmd_config_beacon_timer(mors, mors_vif,
+					      info->enable_beacon);
+
+		if (!info->enable_beacon)
+			mm81x_mac_beacon_finish(mors_vif);
+	}
+
+	if (changed & BSS_CHANGED_BEACON_INT || changed & BSS_CHANGED_SSID) {
+		ret = mm81x_cmd_cfg_bss(mors, mors_vif->id, info->beacon_int,
+					info->dtim_period,
+					mm81x_vif_generate_cssid(vif));
+		if (ret)
+			dev_err(mors->dev, "mm81x_cmd_cfg_bss failed %d", ret);
+	}
+}
+
+static u64 mm81x_mac_ops_prepare_multicast(struct ieee80211_hw *hw,
+					   struct netdev_hw_addr_list *mc_list)
+{
+	struct mm81x *mors = hw->priv;
+	struct mcast_filter *filter;
+	struct netdev_hw_addr *addr;
+	u16 addr_count = netdev_hw_addr_list_count(mc_list);
+	u16 len = sizeof(*filter) + addr_count * sizeof(filter->addr_list[0]);
+
+	filter = kzalloc(len, GFP_ATOMIC);
+	if (!filter)
+		return 0;
+
+	if (addr_count > MCAST_FILTER_COUNT_MAX) {
+		dev_warn(
+			mors->dev,
+			"Multicast filtering disabled - too many groups (%d) > %u",
+			addr_count, (u16)MCAST_FILTER_COUNT_MAX);
+		filter->count = 0;
+	} else {
+		netdev_hw_addr_list_for_each(addr, mc_list) {
+			dev_dbg(mors->dev, "mcast whitelist (%d): %pM",
+				filter->count, addr->addr);
+			filter->addr_list[filter->count++] =
+				mac2le32(addr->addr);
+		}
+	}
+
+	return (u64)(unsigned long)filter;
+}
+
+static void mm81x_mac_ops_configure_filter(struct ieee80211_hw *hw,
+					   unsigned int changed_flags,
+					   unsigned int *total_flags,
+					   u64 multicast)
+{
+	struct mm81x *mors = hw->priv;
+	struct mcast_filter *cmd = (void *)(unsigned long)multicast;
+	struct mm81x_vif *mors_vif = NULL;
+	struct ieee80211_vif *vif = NULL;
+	int vif_id = 0;
+	int ret = 0;
+
+	if (!cmd)
+		goto out;
+
+	kfree(mors->mcast_filter);
+	mors->mcast_filter = cmd;
+
+	for (vif_id = 0; vif_id < ARRAY_SIZE(mors->vifs); vif_id++) {
+		vif = mm81x_rcu_dereference_vif_id(mors, vif_id, false);
+		if (!vif)
+			continue;
+
+		mors_vif = ieee80211_vif_to_mors_vif(vif);
+
+		ret = mm81x_cmd_cfg_multicast_filter(mors, mors_vif);
+		if (!ret)
+			continue;
+
+		dev_err(mors->dev, "Multicast filtering failed - rc=%d", ret);
+		mors->mcast_filter = NULL;
+		kfree(cmd);
+		break;
+	}
+
+out:
+	*total_flags &= 0;
+}
+
+static int mm81x_mac_ops_conf_tx(struct ieee80211_hw *hw,
+				 struct ieee80211_vif *vif,
+				 unsigned int link_id, u16 ac,
+				 const struct ieee80211_tx_queue_params *params)
+{
+	int ret;
+	struct mm81x *mors = hw->priv;
+	struct mm81x_queue_params mqp;
+
+	mqp.aci = map_mac80211q_2_mm81x_aci(ac);
+	mqp.aifs = params->aifs;
+	mqp.cw_max = params->cw_max;
+	mqp.cw_min = params->cw_min;
+	mqp.uapsd = params->uapsd;
+	mqp.txop = params->txop << 5;
+
+	dev_dbg(mors->dev, "queue:%d txop:%d cw_min:%d cw_max:%d aifs:%d",
+		mqp.aci, mqp.txop, mqp.cw_min, mqp.cw_max, mqp.aifs);
+
+	ret = mm81x_cmd_cfg_qos(mors, &mqp);
+	if (ret)
+		dev_dbg(mors->dev, "mm81x_cmd_cfg_qos failed %d", ret);
+	return ret;
+}
+
+static int mm81x_mac_ops_sta_state(struct ieee80211_hw *hw,
+				   struct ieee80211_vif *vif,
+				   struct ieee80211_sta *sta,
+				   enum ieee80211_sta_state old_state,
+				   enum ieee80211_sta_state new_state)
+{
+	u16 aid;
+	int ret;
+	struct mm81x *mors = hw->priv;
+	struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv;
+	struct mm81x_sta *mors_sta = (struct mm81x_sta *)sta->drv_priv;
+
+	/* Ignore both NOTEXIST to NONE and NONE to NOTEXIST */
+	if ((old_state == IEEE80211_STA_NOTEXIST &&
+	     new_state == IEEE80211_STA_NONE) ||
+	    (old_state == IEEE80211_STA_NONE &&
+	     new_state == IEEE80211_STA_NOTEXIST))
+		return 0;
+
+	if (vif->type == NL80211_IFTYPE_STATION)
+		aid = vif->cfg.aid;
+	else
+		aid = sta->aid;
+
+	ret = mm81x_cmd_sta_state(mors, mors_vif, aid, sta, new_state);
+	if (ret < 0)
+		goto exit;
+
+	ether_addr_copy(mors_sta->addr, sta->addr);
+	mors_sta->state = new_state;
+
+	if (new_state > old_state && new_state == IEEE80211_STA_ASSOC) {
+		if (vif->type == NL80211_IFTYPE_AP)
+			mors_vif->u.ap.num_stas++;
+		else if (vif->type == NL80211_IFTYPE_STATION)
+			mors_vif->u.sta.is_assoc = true;
+	}
+
+	if (new_state < old_state && new_state == IEEE80211_STA_NONE) {
+		if (vif->type == NL80211_IFTYPE_AP)
+			mors_vif->u.ap.num_stas--;
+		else if (vif->type == NL80211_IFTYPE_STATION)
+			mors_vif->u.sta.is_assoc = false;
+	}
+
+exit:
+	/*
+	 * Always update our mmrc sta state even on failure to ensure
+	 * we don't hold a dangling sta on error
+	 */
+	mm81x_rc_sta_state_check(mors, vif, sta, old_state, new_state);
+	return new_state < old_state ? 0 : ret;
+}
+
+static int mm81x_mac_ops_ampdu_action(struct ieee80211_hw *hw,
+				      struct ieee80211_vif *vif,
+				      struct ieee80211_ampdu_params *params)
+{
+	u16 tid = params->tid;
+	struct mm81x *mors = hw->priv;
+	struct ieee80211_sta *sta = params->sta;
+	struct mm81x_sta *mors_sta = (struct mm81x_sta *)sta->drv_priv;
+	u16 buf_size =
+		min_t(u16, params->buf_size, DOT11AH_BA_MAX_MPDU_PER_AMPDU);
+
+	switch (params->action) {
+	case IEEE80211_AMPDU_TX_START:
+		dev_dbg(mors->dev, "%pM.%d A-MPDU TX start", mors_sta->addr,
+			tid);
+		ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid);
+		break;
+	case IEEE80211_AMPDU_TX_STOP_CONT:
+	case IEEE80211_AMPDU_TX_STOP_FLUSH:
+	case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
+		dev_dbg(mors->dev, "%pM.%d A-MPDU TX flush", mors_sta->addr,
+			tid);
+		mors_sta->tid_start_tx[tid] = false;
+		mors_sta->tid_tx[tid] = false;
+		mors_sta->tid_params[tid] = 0;
+		ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
+		break;
+	case IEEE80211_AMPDU_TX_OPERATIONAL:
+		dev_dbg(mors->dev, "%pM.%d A-MPDU TX oper", mors_sta->addr,
+			tid);
+		mors_sta->tid_tx[tid] = true;
+		if (!buf_size) {
+			dev_err(mors->dev, "%pM.%d A-MPDU Invalid buf size",
+				mors_sta->addr, tid);
+			break;
+		}
+		mors_sta->tid_params[tid] =
+			u8_encode_bits(buf_size - 1,
+				       TX_INFO_TID_PARAMS_MAX_REORDER_BUF) |
+			u8_encode_bits(1, TX_INFO_TID_PARAMS_AMPDU_ENABLED) |
+			u8_encode_bits(params->amsdu,
+				       TX_INFO_TID_PARAMS_AMSDU_SUPPORTED);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int mm81x_mac_ops_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+				 struct ieee80211_vif *vif,
+				 struct ieee80211_sta *sta,
+				 struct ieee80211_key_conf *key)
+{
+	u16 aid;
+	int ret = -EOPNOTSUPP;
+	struct mm81x *mors = hw->priv;
+	struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv;
+	enum host_cmd_key_cipher cipher;
+	enum host_cmd_aes_key_len length;
+
+	if (vif->type == NL80211_IFTYPE_STATION) {
+		aid = vif->cfg.aid;
+	} else if (sta) {
+		aid = sta->aid;
+	} else {
+		/* Is a group key - AID is unused */
+		WARN_ON(key->flags & IEEE80211_KEY_FLAG_PAIRWISE);
+		aid = 0;
+	}
+
+	switch (cmd) {
+	case SET_KEY: {
+		switch (key->cipher) {
+		case WLAN_CIPHER_SUITE_CCMP:
+		case WLAN_CIPHER_SUITE_CCMP_256:
+			cipher = HOST_CMD_KEY_CIPHER_AES_CCM;
+			break;
+		case WLAN_CIPHER_SUITE_GCMP:
+		case WLAN_CIPHER_SUITE_GCMP_256:
+			cipher = HOST_CMD_KEY_CIPHER_AES_GCM;
+			break;
+		default:
+			/* Cipher suite currently not supported */
+			ret = -EOPNOTSUPP;
+			goto exit;
+		}
+
+		switch (key->keylen) {
+		case 16:
+			length = HOST_CMD_AES_KEY_LEN_LENGTH_128;
+			break;
+		case 32:
+			length = HOST_CMD_AES_KEY_LEN_LENGTH_256;
+			break;
+		default:
+			/* Key length not supported */
+			ret = -EOPNOTSUPP;
+			goto exit;
+		}
+
+		ret = mm81x_cmd_install_key(mors, mors_vif, aid, key, cipher,
+					    length);
+		break;
+	}
+	case DISABLE_KEY:
+		ret = mm81x_cmd_disable_key(mors, mors_vif, aid, key);
+		if (ret) {
+			/* Must return 0 */
+			dev_warn(mors->dev, "Failed to remove key");
+			ret = 0;
+		}
+		break;
+	default:
+		WARN_ON(1);
+	}
+
+	if (ret) {
+		dev_dbg(mors->dev, "Falling back to software crypto");
+		ret = 1;
+	}
+
+exit:
+	return ret;
+}
+
+static int mm81x_mac_set_frag_threshold(struct ieee80211_hw *hw, int radio_idx,
+					u32 value)
+{
+	struct mm81x *mors = hw->priv;
+
+	return mm81x_cmd_set_frag_threshold(mors, value);
+}
+
+static u8 mm81x_rx_h_rc_bw_to_rx_bw(__le32 ratecode)
+{
+	enum dot11_bandwidth bw = mm81x_ratecode_bw_index_get(ratecode);
+
+	switch (bw) {
+	case DOT11_BANDWIDTH_1MHZ:
+		return RATE_INFO_BW_1;
+	case DOT11_BANDWIDTH_2MHZ:
+		return RATE_INFO_BW_2;
+	case DOT11_BANDWIDTH_4MHZ:
+		return RATE_INFO_BW_4;
+	case DOT11_BANDWIDTH_8MHZ:
+		return RATE_INFO_BW_8;
+	default:
+		return RATE_INFO_BW_1;
+	}
+}
+
+static void mm81x_rx_h_fill_status(struct mm81x *mors,
+				   struct mm81x_skb_rx_status *hdr_rx_status,
+				   struct ieee80211_rx_status *rx_status,
+				   struct sk_buff *skb)
+{
+	u32 flags = le32_to_cpu(hdr_rx_status->flags);
+	u16 freq_100khz = le16_to_cpu(hdr_rx_status->freq_100khz);
+	__le32 ratecode = hdr_rx_status->mm81x_ratecode;
+
+	rx_status->signal = le16_to_cpu(hdr_rx_status->rssi);
+	rx_status->encoding = RX_ENC_S1G;
+	rx_status->band = NL80211_BAND_S1GHZ;
+	rx_status->freq = KHZ100_TO_MHZ(freq_100khz);
+	rx_status->freq_offset = (freq_100khz % 10) ? 1 : 0;
+	rx_status->nss = NSS_IDX_TO_NSS(mm81x_ratecode_nss_index_get(ratecode));
+
+	if (flags & MM81X_RX_STATUS_FLAGS_DECRYPTED)
+		rx_status->flag |= RX_FLAG_DECRYPTED;
+
+	rx_status->rate_idx = mm81x_ratecode_mcs_index_get(ratecode);
+	rx_status->bw = mm81x_rx_h_rc_bw_to_rx_bw(ratecode);
+
+	if (mm81x_ratecode_sgi_get(ratecode))
+		rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI;
+}
+
+static void mm81x_rx_h_update_sta(struct ieee80211_vif *vif,
+				  struct ieee80211_hdr *hdr,
+				  struct ieee80211_rx_status *rx_status)
+{
+	struct ieee80211_sta *sta;
+	struct mm81x_sta *msta;
+	u8 *lookup = ieee80211_is_s1g_beacon(hdr->frame_control) ? hdr->addr1 :
+								   hdr->addr2;
+
+	lockdep_assert_in_rcu_read_lock();
+
+	sta = ieee80211_find_sta(vif, lookup);
+	if (!sta)
+		return;
+
+	msta = (void *)sta->drv_priv;
+	if (msta->avg_rssi) {
+		msta->avg_rssi =
+			CALC_AVG_RSSI(msta->avg_rssi, rx_status->signal);
+	} else {
+		msta->avg_rssi = rx_status->signal;
+	}
+}
+
+static struct ieee80211_vif *
+mm81x_rx_h_skb_get_vif(struct mm81x *mors, struct sk_buff *skb,
+		       struct mm81x_skb_rx_status *hdr_rx_status)
+{
+	u8 vif_id = u32_get_bits(le32_to_cpu(hdr_rx_status->flags),
+				 MM81X_RX_STATUS_FLAGS_VIF_ID);
+
+	lockdep_assert_in_rcu_read_lock();
+
+	if (vif_id == INVALID_VIF_INDEX)
+		return NULL;
+
+	return mm81x_rcu_dereference_vif_id(mors, vif_id, true);
+}
+
+void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb,
+		      struct mm81x_skb_rx_status *hdr_rx_status)
+{
+	struct ieee80211_vif *vif;
+	struct ieee80211_hw *hw = mors->hw;
+	struct ieee80211_rx_status rx_status;
+	struct ieee80211_hdr *hdr = (void *)skb->data;
+
+	memset(&rx_status, 0, sizeof(rx_status));
+
+	if (!mors->started || !skb->data || !skb->len) {
+		dev_kfree_skb_any(skb);
+		return;
+	}
+
+	mm81x_rx_h_fill_status(mors, hdr_rx_status, &rx_status, skb);
+
+	scoped_guard(rcu) {
+		vif = mm81x_rx_h_skb_get_vif(mors, skb, hdr_rx_status);
+		if (!vif)
+			goto rx;
+
+		mm81x_rx_h_update_sta(vif, hdr, &rx_status);
+	}
+
+rx:
+	memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status));
+	ieee80211_rx_ni(hw, skb);
+}
+
+static void mm81x_mac_flush_queues(struct mm81x *mors)
+{
+	/*
+	 * No need to call mm81x_skbq_stop_tx_queues as mac80211
+	 * has already cancelled each queue prior to calling .flush()
+	 */
+	mm81x_skbq_data_traffic_pause(mors);
+
+	flush_work(&mors->hif_work);
+	flush_work(&mors->tx_stale_work);
+
+	mm81x_hif_clear_events(mors);
+	mm81x_hif_flush_tx_data(mors);
+	mm81x_hif_flush_cmds(mors);
+
+	/* Re-enable data, not that there will be any */
+	mm81x_skbq_data_traffic_resume(mors);
+}
+
+static bool mm81x_mac_has_tx_pending(struct mm81x *mors)
+{
+	struct mm81x_skbq *mgmt_q = mm81x_hif_get_tx_mgmt_queue(mors);
+	struct mm81x_skbq *tx_qs;
+	int num_qs, i;
+
+	mm81x_hif_skbq_get_tx_qs(mors, &tx_qs, &num_qs);
+	for (i = 0; i < num_qs; i++)
+		if (mm81x_skbq_count(&tx_qs[i]) ||
+		    mm81x_skbq_pending_count(&tx_qs[i]))
+			return true;
+
+	if (mm81x_skbq_count(mgmt_q) || mm81x_skbq_pending_count(mgmt_q))
+		return true;
+
+	return false;
+}
+
+static void mm81x_mac_wait_queues(struct mm81x *mors)
+{
+	if (!wait_event_timeout(mors->tx_empty_waitq,
+				!mm81x_mac_has_tx_pending(mors),
+				MM81X_FLUSH_TIMEOUT))
+		dev_warn(mors->dev, "Unable to empty queues before timeout");
+}
+
+static void mm81x_mac_ops_flush(struct ieee80211_hw *hw,
+				struct ieee80211_vif *vif, u32 queues,
+				bool drop)
+{
+	struct mm81x *mors = hw->priv;
+
+	/* We don't support IEEE80211_HW_QUEUE_CONTROL so flush all queues */
+	if (drop)
+		mm81x_mac_flush_queues(mors);
+	else
+		mm81x_mac_wait_queues(mors);
+}
+
+static int mm81x_mac_ops_set_rts_threshold(struct ieee80211_hw *hw,
+					   int radio_idx, u32 value)
+{
+	struct mm81x *mors = hw->priv;
+
+	mors->rts_threshold = value;
+	return 0;
+}
+
+static void mm81x_mac_ops_sta_statistics(struct ieee80211_hw *hw,
+					 struct ieee80211_vif *vif,
+					 struct ieee80211_sta *sta,
+					 struct station_info *sinfo)
+{
+	struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv;
+	struct mm81x *mors = hw->priv;
+	const struct mmrc_table *tb = msta->rc.tb;
+	struct mmrc_rate rate;
+
+	if (!tb || tb->best_tp.rate == MMRC_MCS_UNUSED) {
+		sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_TX_BITRATE);
+		return;
+	}
+
+	rate = tb->best_tp;
+	sinfo->txrate.mcs = rate.rate;
+	sinfo->txrate.nss = NSS_IDX_TO_NSS(rate.ss);
+	sinfo->txrate.flags = RATE_INFO_FLAGS_S1G_MCS;
+	switch (rate.bw) {
+	case MMRC_BW_1MHZ:
+		sinfo->txrate.bw = RATE_INFO_BW_1;
+		break;
+	case MMRC_BW_2MHZ:
+		sinfo->txrate.bw = RATE_INFO_BW_2;
+		break;
+	case MMRC_BW_4MHZ:
+		sinfo->txrate.bw = RATE_INFO_BW_4;
+		break;
+	case MMRC_BW_8MHZ:
+		sinfo->txrate.bw = RATE_INFO_BW_8;
+		break;
+	default:
+		break;
+	}
+
+	if (rate.guard == MMRC_GUARD_SHORT)
+		sinfo->txrate.flags |= (RATE_INFO_FLAGS_SHORT_GI);
+
+	dev_dbg(mors->dev, "mcs: %d, bw: %d, flag: 0x%x", rate.rate, rate.bw,
+		sinfo->txrate.flags);
+	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE);
+}
+
+static u32 mm81x_get_expected_throughput(struct ieee80211_hw *hw,
+					 struct ieee80211_sta *sta)
+{
+	struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv;
+	struct mm81x *mors = hw->priv;
+	const struct mmrc_table *tb = msta->rc.tb;
+	struct mmrc_rate rate;
+	u32 tput;
+
+	if (!tb || tb->best_tp.rate == MMRC_MCS_UNUSED)
+		return 0;
+
+	rate = tb->best_tp;
+	tput = BPS_TO_KBPS(mmrc_calculate_theoretical_throughput(rate));
+	dev_dbg(mors->dev, "Throughput: MCS: %d, BW: %d, GI: %d -> %u",
+		rate.rate, 1 << rate.bw, rate.guard, tput);
+
+	return tput;
+}
+
+static void mm81x_mac_restart_cleanup_iter(void *data, u8 *mac,
+					   struct ieee80211_vif *vif)
+{
+	if (vif->type == NL80211_IFTYPE_AP)
+		mm81x_mac_beacon_finish((struct mm81x_vif *)vif->drv_priv);
+}
+
+static void mm81x_mac_restart_cleanup(struct mm81x *mors)
+{
+	ieee80211_iterate_active_interfaces(mors->hw,
+					    IEEE80211_IFACE_ITER_NORMAL,
+					    mm81x_mac_restart_cleanup_iter,
+					    NULL);
+	mm81x_mac_hw_scan_finish(mors);
+}
+
+static int mm81x_mac_restart(struct mm81x *mors)
+{
+	int ret;
+	u32 chip_id;
+
+	mors->started = false;
+	mm81x_ps_disable(mors);
+	mm81x_bus_set_irq(mors, false);
+	mm81x_hw_irq_clear(mors);
+	ieee80211_stop_queues(mors->hw);
+
+	set_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags);
+	set_bit(MM81X_STATE_DATA_QS_STOPPED, &mors->state_flags);
+
+	/* Allow time for in-transit tx/rx packets to settle */
+	mdelay(MM81X_HW_RESTART_DELAY_MS);
+	flush_work(&mors->hif_work);
+	flush_work(&mors->tx_stale_work);
+	mm81x_hif_clear_events(mors);
+	mm81x_hif_flush_tx_data(mors);
+	mm81x_hif_flush_cmds(mors);
+
+	mm81x_claim_bus(mors);
+	ret = mm81x_reg32_read(mors, MM81X_REG_CHIP_ID(mors), &chip_id);
+	mm81x_release_bus(mors);
+
+	if (ret < 0) {
+		dev_err(mors->dev, "Failed to access HW: %d", ret);
+		goto exit;
+	}
+
+	mm81x_mac_restart_cleanup(mors);
+
+	ret = mm81x_fw_init(mors, true);
+	if (ret < 0) {
+		dev_err(mors->dev, "Failed to init firmware: %d", ret);
+		goto exit;
+	}
+
+	mm81x_hw_irq_enable(mors, MM81X_INT_HW_STOP_NOTIFICATION_NUM, true);
+
+	ret = mm81x_fw_parse_ext_host_tbl(mors);
+	if (ret) {
+		dev_err(mors->dev, "failed to parse extended host table: %d",
+			ret);
+		goto exit;
+	}
+
+	mm81x_mac_caps_init(mors);
+
+	mm81x_bus_set_irq(mors, true);
+	clear_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags);
+	clear_bit(MM81X_STATE_DATA_QS_STOPPED, &mors->state_flags);
+	clear_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags);
+	clear_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mors->state_flags);
+	mm81x_mac_check_fw_disabled_chans(mors->hw);
+	ieee80211_restart_hw(mors->hw);
+
+exit:
+	mm81x_ps_enable(mors);
+	return ret;
+}
+
+static int mm81x_mac_ops_add_interface(struct ieee80211_hw *hw,
+				       struct ieee80211_vif *vif)
+{
+	int ret = 0;
+	struct mm81x *mors = hw->priv;
+	struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv;
+
+	if (test_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mors->state_flags)) {
+		dev_info(mors->dev, "Restarting chip with regdom: %s",
+			 mors->country);
+
+		ret = mm81x_mac_restart(mors);
+		if (ret) {
+			dev_err(mors->dev, "Failed to restart chip");
+			return ret;
+		}
+
+		/*
+		 * mac_restart will trigger ieee80211_hw_restart and
+		 * add_interface will re-enter. just exit here instead.
+		 */
+		return 0;
+	}
+
+	vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER;
+	mors_vif->mors = mors;
+
+	ret = mm81x_cmd_add_if(mors, &mors_vif->id, vif->addr, vif->type);
+	if (ret) {
+		dev_err(mors->dev, "mm81x_cmd_add_if failed %d", ret);
+		return ret;
+	}
+
+	if (mors_vif->id >= ARRAY_SIZE(mors->vifs)) {
+		dev_err(mors->dev, "vif_id is too large %u", mors_vif->id);
+		ret = -EOPNOTSUPP;
+		return ret;
+	}
+
+	if (mors_vif->id != (mors_vif->id & MM81X_TX_CONF_FLAGS_VIF_ID_MASK)) {
+		dev_err(mors->dev, "invalid vif_id %u", mors_vif->id);
+		ret = -EOPNOTSUPP;
+		return ret;
+	}
+
+	rcu_assign_pointer(mors->vifs[mors_vif->id], vif);
+
+	if (vif->type == NL80211_IFTYPE_AP)
+		mm81x_mac_beacon_init(mors_vif);
+
+	ret = mm81x_cmd_get_capabilities(mors, mors_vif->id, &mors->fw_caps);
+	if (ret) {
+		dev_err(mors->dev,
+			"mm81x_cmd_get_capabilities failed for vif %d",
+			mors_vif->id);
+		return ret;
+	}
+
+	ieee80211_wake_queues(mors->hw);
+	return ret;
+}
+
+static const struct ieee80211_ops mm81x_ops = {
+	.start = mm81x_mac_ops_start,
+	.stop = mm81x_mac_ops_stop,
+	.config = mm81x_mac_ops_config,
+	.wake_tx_queue = ieee80211_handle_wake_tx_queue,
+	.tx = mm81x_mac_ops_tx,
+	.add_interface = mm81x_mac_ops_add_interface,
+	.remove_interface = mm81x_mac_ops_remove_interface,
+	.configure_filter = mm81x_mac_ops_configure_filter,
+	.sta_state = mm81x_mac_ops_sta_state,
+	.flush = mm81x_mac_ops_flush,
+	.set_frag_threshold = mm81x_mac_set_frag_threshold,
+	.set_rts_threshold = mm81x_mac_ops_set_rts_threshold,
+	.sta_statistics = mm81x_mac_ops_sta_statistics,
+	.get_expected_throughput = mm81x_get_expected_throughput,
+	.hw_scan = mm81x_mac_ops_hw_scan,
+	.cancel_hw_scan = mm81x_mac_ops_cancel_hw_scan,
+	.get_txpower = mm81x_mac_ops_get_txpower,
+	.bss_info_changed = mm81x_mac_ops_bss_info_changed,
+	.prepare_multicast = mm81x_mac_ops_prepare_multicast,
+	.conf_tx = mm81x_mac_ops_conf_tx,
+	.ampdu_action = mm81x_mac_ops_ampdu_action,
+	.set_key = mm81x_mac_ops_set_key,
+	.add_chanctx = ieee80211_emulate_add_chanctx,
+	.remove_chanctx = ieee80211_emulate_remove_chanctx,
+	.change_chanctx = ieee80211_emulate_change_chanctx,
+	.switch_vif_chanctx = ieee80211_emulate_switch_vif_chanctx,
+};
+
+static void mm81x_reg_notifier(struct wiphy *wiphy,
+			       struct regulatory_request *request)
+{
+	int ret;
+	struct mm81x *mors = wiphy_to_ieee80211_hw(wiphy)->priv;
+
+	if (mm81x_reg_h_cc_equal(request->alpha2, "00") ||
+	    mm81x_reg_h_cc_equal(request->alpha2, mors->country))
+		return;
+
+	memcpy(mors->country, request->alpha2, sizeof(mors->country));
+
+	ret = mm81x_mac_restart(mors);
+	if (ret)
+		dev_err(mors->dev, "Failed to restart chip: %d", ret);
+}
+
+static void mm81x_mac_config_hw(struct mm81x *mors)
+{
+	int i;
+	struct ieee80211_hw *hw = mors->hw;
+	struct wiphy *wiphy;
+
+	for (i = 0; i < NUM_NL80211_BANDS; i++)
+		hw->wiphy->bands[i] = NULL;
+
+	hw->wiphy->bands[NL80211_BAND_S1GHZ] = &mors_band_s1ghz;
+	hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_AP) |
+				     BIT(NL80211_IFTYPE_STATION);
+	hw->wiphy->reg_notifier = mm81x_reg_notifier;
+	hw->queues = MM81X_HW_QUEUE_COUNT;
+	hw->max_rates = MM81X_HW_MAX_RATES;
+	hw->max_report_rates = MM81X_HW_MAX_REPORT_RATES;
+	hw->max_rate_tries = MM81X_HW_MAX_RATE_TRIES;
+	hw->tx_sk_pacing_shift = MM81X_HW_TX_SK_PACING_SHIFT;
+	hw->vif_data_size = sizeof(struct mm81x_vif);
+	hw->sta_data_size = sizeof(struct mm81x_sta);
+	hw->extra_tx_headroom =
+		sizeof(struct mm81x_skb_hdr) + mm81x_bus_get_alignment(mors);
+
+	mors->wiphy = hw->wiphy;
+
+	ieee80211_hw_set(hw, SIGNAL_DBM);
+	ieee80211_hw_set(hw, MFP_CAPABLE);
+	ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS);
+	ieee80211_hw_set(hw, AMPDU_AGGREGATION);
+	ieee80211_hw_set(hw, HOST_BROADCAST_PS_BUFFERING);
+	ieee80211_hw_set(hw, HAS_RATE_CONTROL);
+	ieee80211_hw_set(hw, SUPPORTS_PS);
+	ieee80211_hw_set(hw, NEED_DTIM_BEFORE_ASSOC);
+	ieee80211_hw_set(hw, PS_NULLFUNC_STACK);
+	ieee80211_hw_set(hw, SUPPORTS_TX_FRAG);
+	ieee80211_hw_set(hw, SUPPORTS_NDP_BLOCKACK);
+
+	SET_IEEE80211_PERM_ADDR(hw, mors->macaddr);
+
+	wiphy = mors->wiphy;
+
+	wiphy->flags |= WIPHY_FLAG_AP_UAPSD;
+	wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT;
+
+	if (!mors->ps.enable)
+		wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT;
+
+	wiphy->features |= NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE |
+			   NL80211_FEATURE_TX_POWER_INSERTION;
+
+	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_AIRTIME_FAIRNESS);
+	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SET_SCAN_DWELL);
+
+	wiphy->iface_combinations = mors_if_combs;
+	wiphy->n_iface_combinations = ARRAY_SIZE(mors_if_combs);
+	wiphy->max_scan_ie_len = MM81X_MAX_SCAN_IE_LEN;
+	wiphy->max_scan_ssids = MM81X_MAX_SCAN_SSIDS;
+	wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
+	wiphy->max_remain_on_channel_duration =
+		MM81X_MAX_REMAIN_ON_CHAN_DURATION;
+}
+
+static void mm81x_stale_tx_status_timer(struct timer_list *t)
+{
+	struct mm81x *mors = timer_container_of(mors, t, stale_status.timer);
+
+	spin_lock_bh(&mors->stale_status.lock);
+	if (mm81x_hif_get_tx_status_pending_count(mors))
+		queue_work(mors->net_wq, &mors->tx_stale_work);
+	spin_unlock_bh(&mors->stale_status.lock);
+}
+
+static void mm81x_stale_tx_status_timer_finish(struct mm81x *mors)
+{
+	timer_delete_sync_try(&mors->stale_status.timer);
+}
+
+static void mm81x_mac_stale_tx_status_timer_init(struct mm81x *mors)
+{
+	spin_lock_init(&mors->stale_status.lock);
+	timer_setup(&mors->stale_status.timer, mm81x_stale_tx_status_timer, 0);
+}
+
+int mm81x_mac_register(struct mm81x *mors)
+{
+	int ret;
+	struct ieee80211_hw *hw = mors->hw;
+
+	mors->tx_power_mbm = INT_MAX;
+	mors->tx_max_power_mbm = INT_MAX;
+	mors->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD;
+
+	ret = mm81x_ps_init(mors);
+	if (ret)
+		return ret;
+
+	mm81x_mac_config_hw(mors);
+	mm81x_mac_hw_scan_init(mors);
+	mm81x_mac_stale_tx_status_timer_init(mors);
+
+	ret = ieee80211_register_hw(hw);
+	if (ret) {
+		dev_err(mors->dev, "ieee80211_register_hw failed %d", ret);
+		mm81x_mac_unregister(mors);
+		return ret;
+	}
+
+	mm81x_rc_init(mors);
+
+	/*
+	 * At this stage, we know bus and pager system interrupts are enabled.
+	 * Trigger the receive workqueue to drain any incoming chip-to-host
+	 * pending packets been pushed in the period between the firmware
+	 * initialization and interrupts being enabled.
+	 */
+	set_bit(MM81X_HIF_EVT_RX_PEND, &mors->hif.event_flags);
+	queue_work(mors->chip_wq, &mors->hif_work);
+
+	return ret;
+}
+
+void mm81x_mac_unregister(struct mm81x *mors)
+{
+	mm81x_ps_disable(mors);
+	mm81x_rc_deinit(mors);
+	mm81x_mac_hw_scan_destroy(mors);
+
+	ieee80211_stop_queues(mors->hw);
+	ieee80211_unregister_hw(mors->hw);
+
+	mm81x_hif_flush_tx_data(mors);
+	mm81x_hif_flush_cmds(mors);
+	mm81x_stale_tx_status_timer_finish(mors);
+	mm81x_ps_finish(mors);
+
+	kfree(mors->mcast_filter);
+}
+
+struct mm81x *mm81x_mac_alloc(size_t priv_size, struct device *dev)
+{
+	struct ieee80211_hw *hw;
+	struct mm81x *mors;
+
+	hw = ieee80211_alloc_hw(sizeof(*mors) + priv_size, &mm81x_ops);
+	if (!hw) {
+		dev_err(dev, "ieee80211_alloc_hw failed\r\n");
+		return NULL;
+	}
+
+	SET_IEEE80211_DEV(hw, dev);
+	memset(hw->priv, 0, sizeof(*mors));
+
+	mors = hw->priv;
+	mors->hw = hw;
+	mors->dev = dev;
+	mutex_init(&mors->cmd_lock);
+	mutex_init(&mors->cmd_wait);
+	init_waitqueue_head(&mors->tx_empty_waitq);
+
+	return mors;
+}
+
+void mm81x_mac_free(struct mm81x *mors)
+{
+	ieee80211_free_hw(mors->hw);
+}
diff --git a/drivers/net/wireless/morsemicro/mm81x/mac.h b/drivers/net/wireless/morsemicro/mm81x/mac.h
new file mode 100644
index 00000000000000..c540471f274eaf
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/mac.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_MAC_H_
+#define _MM81X_MAC_H_
+
+#include "core.h"
+#include "command.h"
+
+struct mm81x_queue_params {
+	u8 uapsd;
+	u8 aci;
+	u8 aifs;
+	u16 cw_min;
+	u16 cw_max;
+	u32 txop;
+};
+
+static inline u32 mm81x_vif_generate_cssid(struct ieee80211_vif *vif)
+{
+	return mm81x_generate_cssid(vif->cfg.ssid, vif->cfg.ssid_len);
+}
+
+/*
+ * Build a little-endian word from the last four octets of a MAC address;
+ * the first two octets are dropped.
+ */
+static inline __le32 mac2le32(const unsigned char *addr)
+{
+	return cpu_to_le32(((u32)(addr[2]) << 24) | ((u32)(addr[3]) << 16) |
+			   ((u32)(addr[4]) << 8) | ((u32)(addr[5])));
+}
+
+static inline struct ieee80211_vif *
+mm81x_rcu_dereference_vif_id(struct mm81x *mors, u8 vif_id, bool rcu)
+{
+	if (WARN_ON(vif_id >= ARRAY_SIZE(mors->vifs)))
+		return NULL;
+
+	if (rcu)
+		return rcu_dereference(mors->vifs[vif_id]);
+
+	return rcu_dereference_protected(mors->vifs[vif_id],
+					 lockdep_is_held(&mors->hw->wiphy->mtx));
+}
+
+int mm81x_tx_h_get_attempts(struct mm81x *mors,
+			    struct mm81x_skb_tx_status *tx_sts);
+struct mm81x *mm81x_mac_alloc(size_t priv_size, struct device *dev);
+int mm81x_mac_register(struct mm81x *mors);
+void mm81x_mac_free(struct mm81x *mors);
+void mm81x_mac_unregister(struct mm81x *mors);
+int mm81x_mac_event_recv(struct mm81x *mors, struct sk_buff *skb);
+void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb,
+		      struct mm81x_skb_rx_status *hdr_rx_status);
+void mm81x_mac_beacon_irq_handle(struct mm81x *mors, u32 status);
+
+u8 *mm81x_hw_scan_h_insert_tlvs(struct mm81x_hw_scan_params *params, u8 *buf);
+size_t mm81x_hw_scan_h_get_cmd_size(struct mm81x_hw_scan_params *params);
+void mm81x_tx_h_check_aggr(struct ieee80211_sta *pubsta, struct sk_buff *skb);
+#endif /* !_MM81X_MAC_H_ */
diff --git a/drivers/net/wireless/morsemicro/mm81x/mmrc.c b/drivers/net/wireless/morsemicro/mm81x/mmrc.c
new file mode 100644
index 00000000000000..fe7e4f501d6c23
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/mmrc.c
@@ -0,0 +1,1354 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include "mmrc.h"
+
+/*
+ * The default packet size in bits used for calculated throughput of a given
+ * rate
+ */
+#define DEFAULT_PACKET_SIZE_BITS 9600
+
+/*
+ * The default packet size in bytes used for calculating retries for a given
+ * rate
+ */
+#define DEFAULT_PACKET_SIZE_BYTES 1200
+
+/* The sample frequencies at different stages */
+#define LOOKAROUND_RATE_INIT 5
+#define LOOKAROUND_RATE_NORMAL 50
+#define LOOKAROUND_RATE_STABLE 100
+
+/* The thresholds for stability stages */
+#define STABILITY_CNT_THRESHOLD_INIT 20
+#define STABILITY_CNT_THRESHOLD_NORMAL 50
+#define STABILITY_CNT_THRESHOLD_STABLE 100
+
+/* The backoff step size for the counter */
+#define STABILITY_BACKOFF_STEP 2
+
+/*
+ * The packet success threshold for attempting slower lookaround rates
+ */
+/*
+ * Force a look around if there haven't been any for this number of cycles
+ */
+#define LOOKAROUND_MAX_RC_CYCLES 5
+
+/*
+ * Number of attempts for each lookaround rate within at most two RC cycles
+ * if there are enough packets
+ */
+#define LOOKAROUND_RATE_ATTEMPTS 4
+
+/*
+ * Limit the number of times we try to pick a theoretically better rate to
+ * sample. Necessary so we don't stall the CPU, due to constantly picking worse
+ * rates.
+ */
+#define LOOKAROUND_FAIL_MAX 200
+
+/*
+ * Initial and reset probability per rate in the table
+ * Changing this value will have a severe implication on the current heuristic
+ * It could mean that some rates will have better probability throughput even
+ * with no edivence and so will cause unexpected changes in the rate table
+ */
+#define RATE_INIT_PROBABILITY 0
+
+/*
+ * The lowest number of MPDUs within acknowledged AMPDUs that can be used for
+ * rate stats
+ */
+#define AMPDU_STATS_MIN 2
+
+/*
+ * The lowest number of stats to be used for processing in NORMAL lookaround
+ * mode
+ */
+#define STATS_MIN_NORMAL 2
+
+/*
+ * The lowest number of stats to be used for processing in INIT lookaround
+ * mode
+ */
+#define STATS_MIN_INIT 1
+
+/* The lowest probability value considered for recognising a dip */
+#define PROBABILITY_DIP_MIN 20
+
+/* The lowest probability value for recovering from a dip */
+#define PROBABILITY_DIP_RECOVERY_MIN 40
+
+/*
+ * The time cap on rate allocation for multiple attempts. If a single attempt
+ * exceeds this window, no additional attempts will be generated
+ */
+#define MAX_WINDOW_ATTEMPT_TIME 4000
+
+/* The time window for all rates in rate table */
+#define RATE_WINDOW_MICROSECONDS 24000
+
+/*
+ * EWMA is the alpha coefficient in the exponential weighting moving average
+ * filter used for probability updates.
+ *
+ * Y[n] = X[n] * (100 - EWMA) + (Y[n-1] * EWMA)
+ *	  -------------------------------------
+ *			   100
+ *
+ */
+#define EWMA 75
+
+/*
+ * Evidence scaling to allow for one decimal place. Needed for low
+ * throughput, otherwise the history decays in a single cycle.
+ */
+#define EVIDENCE_SCALE 5
+
+/*
+ * Evidence maximum to ensure history doesn't decay too slowly when
+ * there is a lot of historical data.
+ */
+#define EVIDENCE_MAX 100
+
+/*
+ * This fixed point conversion multiplies a value by one and shifts it
+ * accordingly to account for the fixed point shifting at the return of a
+ * function
+ */
+#define FP_8_MULT_1 256
+
+/* Fixed point conversion for 2.1 * 2^8 used for 4MHz symbol multiplication */
+#define FP_8_4MHZ 537
+
+/* Fixed point conversion for 4.5 * 2^8 used for 8MHz symbol multiplication */
+#define FP_8_8MHZ 1152
+
+/* Fixed point conversion for 9.0 * 2^8 used for 16MHz symbol multiplication */
+#define FP_8_16MHZ 2301
+
+/*
+ * Fixed point conversion for 3.6 * 2^8 used for long guard symbol tx time
+ * multiplication
+ */
+#define FP_8_LONG_GUARD_SYMBOL_TIME 1024
+
+/*
+ * Fixed point conversion for 4.0 * 2^8 used for short guard symbol tx time
+ * multiplication
+ */
+#define FP_8_SHORT_GUARD_SYMBOL_TIME 921
+
+/*
+ * Shift value to shift back our FP conversions
+ */
+#define FP_8_SHIFT 8
+
+/*
+ * Limit to count of consecutive variations in one direction
+ */
+#define MAX_VARIATION_DIRECTION 5
+
+/*
+ * Threshold for considering consecutive variation direction as variation
+ * or not
+ */
+#define VARIATION_DIRECTION_THRESHOLD 3
+
+/* EWMA percentage value for averaging the best rate probability variation */
+#define VARIATION_EWMA 95
+
+/* Percentage variation regarded as minor */
+#define MINOR_VARIATION_THRESHOLD 1
+
+/* Percentage variation regarded as moderate */
+#define MODERATE_VARIATION_THRESHOLD 3
+
+/* Percentage variation regarded as significant */
+#define SIGNIFICANT_VARIATION_THRESHOLD 5
+
+/* If the best rate changes twice in this number of cycles, it is unstable */
+#define BEST_RATE_UNSTABLE_THRESHOLD 4
+
+/*
+ * Once the best rate is unchanged for this number of cycles it has
+ * converged
+ */
+#define BEST_RATE_CONVERGED_THRESHOLD 10
+
+/* RSSI threshold for short range */
+#define MMRC_SHORT_RANGE_RSSI_LIMIT -70
+
+/* RSSI threshold for mid range */
+#define MMRC_MID_RANGE_RSSI_LIMIT -85
+
+#define MMRC_MAX_BW(bw_caps)                                \
+	(((bw_caps) & BIT(MMRC_BW_16MHZ)) ? MMRC_BW_16MHZ : \
+	 ((bw_caps) & BIT(MMRC_BW_8MHZ))  ? MMRC_BW_8MHZ :  \
+	 ((bw_caps) & BIT(MMRC_BW_4MHZ))  ? MMRC_BW_4MHZ :  \
+	 ((bw_caps) & BIT(MMRC_BW_2MHZ))  ? MMRC_BW_2MHZ :  \
+					    MMRC_BW_1MHZ)
+
+/*
+ * This table stores the number of bits per symbols used for MCS0-MCS9 based
+ * on 20MHz and 1SS
+ */
+static const u32 sym_table[10] = { 24, 36, 48, 72, 96, 144, 192, 216, 256, 288 };
+
+/*
+ * Calculate which bit is the nth bit set in an integer based flag.
+ */
+static u8 nth_bit(u16 in, u16 index)
+{
+	u32 i;
+	u8 count = 0;
+
+	for (i = 0; count != index + 1; i++) {
+		if (((1u << i) & in) != 0)
+			count++;
+	}
+
+	return i - 1;
+}
+
+/*
+ * Calculate the input bit's index among all the set bits in an integer
+ * based flag.
+ */
+static u16 bit_index(u16 in, u32 bit_pos)
+{
+	u16 i;
+	u16 index = 0;
+
+	for (i = 0; i != bit_pos + 1; i++) {
+		if (((1u << i) & in) != 0)
+			index++;
+	}
+
+	if (index == 0) {
+		/* Could not match bit pos to caps */
+		return 0;
+	}
+
+	return index - 1;
+}
+
+static u16 rows_from_sta_caps(struct mmrc_sta_capabilities *caps)
+{
+	u16 rows = 0;
+	u8 n_rates = hweight_long(caps->rates);
+
+	/* Taking MCS10 into account as it is relevant for 1 MHz entries */
+	if (caps->rates & BIT(MMRC_MCS10)) {
+		n_rates -= 1;
+		rows = 2;
+	}
+
+	rows += (hweight_long(caps->bandwidth) * n_rates *
+		 hweight_long(caps->guard) *
+		 hweight_long(caps->spatial_streams));
+
+	return rows;
+}
+
+static void rate_update_index(struct mmrc_table *tb, struct mmrc_rate *rate)
+{
+	u16 index = 0;
+	/* Information about our rates */
+	u16 bw = hweight_long(tb->caps.bandwidth);
+	u16 streams = hweight_long(tb->caps.spatial_streams);
+	u16 guard = hweight_long(tb->caps.guard);
+	u16 rows = rows_from_sta_caps(&tb->caps);
+
+	index = bit_index(tb->caps.guard, rate->guard) +
+		bit_index(tb->caps.bandwidth, rate->bw) * guard +
+		bit_index(tb->caps.spatial_streams, rate->ss) * guard * bw +
+		bit_index(tb->caps.rates, rate->rate) * bw * streams * guard;
+
+	if (index >= rows)
+		index = 0;
+
+	rate->index = index;
+}
+
+static struct mmrc_rate get_rate_row(struct mmrc_table *tb, u16 index)
+{
+	struct mmrc_rate rate;
+	u16 ss_index;
+
+	/* Information about our rates */
+	u16 mcs = hweight_long(tb->caps.rates);
+	u16 bw = hweight_long(tb->caps.bandwidth);
+	u16 streams = hweight_long(tb->caps.spatial_streams);
+	u16 guard = hweight_long(tb->caps.guard);
+	u16 total_caps = mcs * bw * streams * guard;
+
+	/* Find our MCS */
+	u16 rows = total_caps / mcs;
+	u16 mcs_index = index / rows;
+	u16 mcs_modulo = index % rows;
+
+	mcs = nth_bit(tb->caps.rates, mcs_index);
+
+	/* Find our spatial stream */
+	rows = rows / streams;
+	streams = nth_bit(tb->caps.spatial_streams, mcs_modulo / rows);
+
+	/* Find our bandwidth */
+	ss_index = index % rows;
+	rows = rows / bw;
+	bw = nth_bit(tb->caps.bandwidth, ss_index / rows);
+
+	/* Find our guard */
+	guard = nth_bit(tb->caps.guard, index % guard);
+
+	/* Add range checks to keep scan-build happy */
+	if (bw >= MMRC_BW_MAX)
+		bw = MMRC_BW_1MHZ;
+
+	if (guard >= MMRC_GUARD_MAX)
+		guard = MMRC_GUARD_LONG;
+
+	/* Validate guard against capability */
+	if (guard == MMRC_GUARD_SHORT &&
+	    !(tb->caps.sgi_per_bw & SGI_PER_BW(bw)))
+		guard = MMRC_GUARD_LONG;
+
+	/* Create our rate row and send it */
+	rate.bw = MMRC_BW_TO_BITFIELD(bw);
+	rate.ss = MMRC_SS_TO_BITFIELD(streams);
+	rate.rate = MMRC_RATE_TO_BITFIELD(mcs);
+	rate.guard = MMRC_GUARD_TO_BITFIELD(guard);
+	rate.attempts = 0;
+	rate.flags = 0;
+
+	/* Update index as bw or guard may have changed */
+	rate_update_index(tb, &rate);
+
+	return rate;
+}
+
+size_t mmrc_memory_required_for_caps(struct mmrc_sta_capabilities *caps)
+{
+	return sizeof(struct mmrc_table) +
+	       rows_from_sta_caps(caps) * sizeof(struct mmrc_stats_table);
+}
+
+static u32 calculate_bits_per_symbol(struct mmrc_rate *rate)
+{
+	u32 bps;
+
+	/* If MCS10 is selected we return 2*MCS0 Symbols */
+	if (rate->rate == MMRC_MCS10)
+		return 6;
+
+	/* Confirm that the rate is valid for the sym_table lookup */
+	if (rate->rate >= MMRC_MCS_UNUSED) {
+		pr_err("%s: Invalid MCS rate %d for sym_table lookup\n",
+		       __func__, rate->rate);
+		return 1;
+	}
+
+	/*
+	 * Coversion from 20MHz as in sym_table to:
+	 * 40MHz   ==	x 2.1
+	 * 80MHz   ==	x 4.5
+	 * 160MHz  ==	x 9.0
+	 */
+	bps = sym_table[rate->rate];
+	switch (rate->bw) {
+	case (MMRC_BW_4MHZ):
+		bps *= FP_8_4MHZ;
+		break;
+	case (MMRC_BW_8MHZ):
+		bps *= FP_8_8MHZ;
+		break;
+	case (MMRC_BW_16MHZ):
+		bps *= FP_8_16MHZ;
+		break;
+	case (MMRC_BW_1MHZ):
+		bps = sym_table[rate->rate] * 24 / 52;
+		bps *= FP_8_MULT_1;
+		break;
+	case (MMRC_BW_2MHZ):
+	case (MMRC_BW_MAX):
+	default:
+		bps *= FP_8_MULT_1;
+		break;
+	}
+	/* SS + 1 because mmrc_spatial_stream starts at 0 */
+	return ((rate->ss + 1) * bps) >> FP_8_SHIFT;
+}
+
+static u32 get_tx_time(struct mmrc_rate *rate)
+{
+	u32 tx = 0;
+	u32 n_sym;
+	u32 avg_bits;
+
+	/* Calculate tx time based on a default packet size */
+	avg_bits = DEFAULT_PACKET_SIZE_BITS;
+
+	/* Number of bits per symbol for this rate */
+	n_sym = calculate_bits_per_symbol(rate);
+
+	/* In case of bad calcuation/parameter use lowest value */
+	n_sym = n_sym == 0 ? sym_table[0] : n_sym;
+
+	/* number of symbols in default packet size */
+	n_sym = avg_bits / n_sym;
+
+	/* tx is time to transmit average packet in us */
+	switch (rate->guard) {
+	case (MMRC_GUARD_LONG):
+		tx = n_sym * FP_8_LONG_GUARD_SYMBOL_TIME;
+		break;
+	case (MMRC_GUARD_SHORT):
+		tx = n_sym * FP_8_SHORT_GUARD_SYMBOL_TIME;
+		break;
+	default:
+		return 0;
+	}
+
+	return (tx * 10) >> FP_8_SHIFT;
+}
+
+u32 mmrc_calculate_theoretical_throughput(struct mmrc_rate rate)
+{
+	static const u32 s1g_tpt_lgi[4][11] = {
+		{ 300, 600, 900, 1200, 1800, 2400, 2700, 3000, 3600, 4000,
+		  150 },
+		{ 650, 1300, 1950, 2600, 3900, 5200, 5850, 6500, 7800, 0, 0 },
+		{ 1350, 2700, 4050, 5400, 8100, 10800, 12150, 13500, 16200,
+		  18000, 0 },
+		{ 2925, 5850, 8775, 11700, 17550, 23400, 26325, 29250, 35100,
+		  39000, 0 },
+	};
+
+	static const u32 s1g_tpt_sgi[4][11] = {
+		{ 333, 666, 1000, 1333, 2000, 2666, 3000, 3333, 4000, 4444,
+		  166 },
+		{ 722, 1444, 2166, 2888, 4333, 5777, 6500, 7222, 8666, 0, 0 },
+		{ 1500, 3000, 4500, 6000, 9000, 12000, 13500, 15000, 18000,
+		  20000, 0 },
+		{ 3250, 6500, 9750, 13000, 19500, 26000, 29250, 32500, 39000,
+		  43333, 0 },
+	};
+
+	if (rate.guard)
+		return s1g_tpt_sgi[rate.bw][rate.rate] * 1000 * (rate.ss + 1);
+
+	return s1g_tpt_lgi[rate.bw][rate.rate] * 1000 * (rate.ss + 1);
+}
+
+static u32 calculate_throughput(struct mmrc_table *tb, u8 index)
+{
+	struct mmrc_rate rate = get_rate_row(tb, index);
+
+	/*
+	 * Avoid the overflow (observed for 8MHz MCS9 rate: 43333) by dividing
+	 * first before multiplying. Should not experience any loss of
+	 * precision as the throughput is already multiplied by 1000 in
+	 * mmrc_calculate_theoretical_throughput (returned as bits/sec)
+	 */
+	if (tb->table[rate.index].prob < 10)
+		return 0;
+	else if (rate.index == tb->best_tp.index && tb->interference_likely)
+		/*
+		 * Assist the best rate by increasing the probability by the
+		 * averaged variation
+		 */
+		return (mmrc_calculate_theoretical_throughput(rate) / 100) *
+		       (tb->table[rate.index].prob + tb->probability_variation);
+	else
+		return (mmrc_calculate_theoretical_throughput(rate) / 100) *
+		       tb->table[rate.index].prob;
+}
+
+static bool validate_rate(struct mmrc_table *tb, struct mmrc_rate *rate)
+{
+	if (rate->rate == MMRC_MCS10 &&
+	    (rate->bw != MMRC_BW_1MHZ || rate->ss != MMRC_SPATIAL_STREAM_1)) {
+		/*
+		 * 802.11ah does not support MCS10 with BW that is not 1MHz or
+		 * not 1 spatial stream.
+		 */
+		return false;
+	}
+
+	if (rate->rate == MMRC_MCS9 && rate->bw == MMRC_BW_2MHZ &&
+	    rate->ss != MMRC_SPATIAL_STREAM_3) {
+		/*
+		 * 802.11ah does not support MCS9 at 2MHz for 1, 2 or 4 spatial
+		 * streams
+		 */
+		return false;
+	}
+
+	if (rate->guard == MMRC_GUARD_SHORT &&
+	    !(tb->caps.sgi_per_bw & SGI_PER_BW(rate->bw)))
+		return false;
+
+	return true;
+}
+
+static u16 find_baseline_index(struct mmrc_table *tb)
+{
+	u32 i, theoretical_tp, min_theoretical_tp;
+	u16 row_count = rows_from_sta_caps(&tb->caps);
+	u16 min_theoretical_tp_index = 0;
+	struct mmrc_rate rate;
+
+	if (tb->caps.rates & BIT(MMRC_MCS10))
+		return 0;
+
+	min_theoretical_tp =
+		mmrc_calculate_theoretical_throughput(get_rate_row(tb, 0));
+	for (i = 0; i < row_count; i++) {
+		rate = get_rate_row(tb, i);
+		if (!validate_rate(tb, &rate))
+			continue;
+
+		theoretical_tp = mmrc_calculate_theoretical_throughput(rate);
+		if (min_theoretical_tp > theoretical_tp) {
+			min_theoretical_tp = theoretical_tp;
+			min_theoretical_tp_index = rate.index;
+		}
+	}
+
+	return min_theoretical_tp_index;
+}
+
+/*
+ * Fill out the remaining rates to be used once the best rate is selected.
+ * Normally the retry rates are one MCS lower than the previous, however in
+ * unconverged mode we limit the 3 respective retry rates to MCS 4, 2 and 0
+ * respectively. The last retry rate is always MCS 0
+ */
+static void mmrc_fill_retry_rates(struct mmrc_table *tb)
+{
+	tb->second_tp = tb->best_tp;
+	if (tb->second_tp.rate != MMRC_MCS0) {
+		tb->second_tp.rate--;
+		if (tb->unconverged && tb->second_tp.rate > MMRC_MCS4)
+			tb->second_tp.rate = MMRC_MCS4;
+		rate_update_index(tb, &tb->second_tp);
+	} else if (tb->second_tp.bw > MMRC_BW_1MHZ) {
+		tb->second_tp.bw--;
+		rate_update_index(tb, &tb->second_tp);
+	}
+
+	tb->best_prob = tb->second_tp;
+	if (tb->best_prob.rate != MMRC_MCS0) {
+		tb->best_prob.rate--;
+		if (tb->unconverged && tb->best_prob.rate > MMRC_MCS2)
+			tb->best_prob.rate = MMRC_MCS2;
+		rate_update_index(tb, &tb->best_prob);
+	} else if (tb->best_prob.bw > MMRC_BW_1MHZ) {
+		tb->best_prob.bw--;
+		rate_update_index(tb, &tb->best_prob);
+	}
+
+	tb->baseline = tb->best_prob;
+	if (tb->baseline.rate != MMRC_MCS0) {
+		tb->baseline.rate = MMRC_MCS0;
+		rate_update_index(tb, &tb->baseline);
+	} else if (tb->baseline.bw > MMRC_BW_1MHZ) {
+		tb->baseline.bw--;
+		rate_update_index(tb, &tb->baseline);
+	}
+}
+
+/*
+ * Updates the mmrc_table with the appropriate rate priority based on the
+ * latest update statistics
+ */
+static void generate_table_priority(struct mmrc_table *tb, u32 new_stats)
+{
+	u16 i;
+	u16 best_row = tb->best_tp.index;
+	u16 prev_best_row = best_row;
+	u8 prev_best_rate = tb->best_tp.rate;
+	u16 second_best_row = tb->second_tp.index;
+	u32 best_tp = calculate_throughput(tb, best_row);
+	u32 second_best_tp = calculate_throughput(tb, second_best_row);
+	u32 last_nonzero_prob = 0;
+	struct mmrc_rate tmp;
+	u32 tmp_tp;
+
+	/* Use fixed rate if set */
+	if (tb->fixed_rate.rate != MMRC_MCS_UNUSED) {
+		tb->best_tp = tb->fixed_rate;
+		tb->second_tp = tb->fixed_rate;
+		tb->best_prob = tb->fixed_rate;
+		return;
+	}
+
+	for (i = 0; i < rows_from_sta_caps(&tb->caps); i++) {
+		tmp = get_rate_row(tb, i);
+		if (!validate_rate(tb, &tmp))
+			continue;
+
+		if (tb->table[tmp.index].evidence == 0)
+			continue;
+
+		/*
+		 * Besides better throughput, also consider this rate better if
+		 * lower rates had worse probability. That indicates the rate
+		 * itself is not the problem. Only do the probability check for
+		 * rates up to the previous best rate.
+		 */
+		tmp_tp = calculate_throughput(tb, tmp.index);
+
+		if (tmp_tp > best_tp ||
+		    (tb->table[tmp.index].max_throughput <=
+			     tb->table[prev_best_row].max_throughput &&
+		     tb->table[tmp.index].prob >=
+			     PROBABILITY_DIP_RECOVERY_MIN &&
+		     tb->table[tmp.index].prob >
+			     tb->table[last_nonzero_prob].prob)) {
+			second_best_row = best_row;
+			second_best_tp = best_tp;
+
+			best_tp = tmp_tp;
+			best_row = tmp.index;
+		} else if (tmp_tp > second_best_tp && best_row != tmp.index) {
+			second_best_tp = tmp_tp;
+			second_best_row = tmp.index;
+		}
+
+		if (tb->table[tmp.index].prob >= PROBABILITY_DIP_MIN &&
+		    tb->table[tmp.index].max_throughput >=
+			    tb->table[last_nonzero_prob].max_throughput)
+			last_nonzero_prob = tmp.index;
+	}
+
+	/* Only update rates and stability when there are new statistics */
+	if (!new_stats)
+		return;
+
+	tb->best_tp = get_rate_row(tb, best_row);
+	if (best_tp == 0 && tb->best_tp.rate > MMRC_MCS0) {
+		/* Drop one rate, as the best throughput is zero */
+		tb->best_tp.rate--;
+		rate_update_index(tb, &tb->best_tp);
+	}
+	tb->second_tp = get_rate_row(tb, second_best_row);
+	mmrc_fill_retry_rates(tb);
+
+	if (tb->best_tp.rate > MMRC_MCS1 && prev_best_row == best_row) {
+		/* Increase the counter when the best rate is not changed */
+		tb->stability_cnt++;
+	} else if (tb->stability_cnt > STABILITY_BACKOFF_STEP) {
+		/* Back off the counter when there is a new best rate */
+		tb->stability_cnt -= STABILITY_BACKOFF_STEP;
+	} else {
+		tb->stability_cnt = 0;
+	}
+
+	if (prev_best_row != best_row) {
+		s8 latest_best_rate_diff = prev_best_rate - tb->best_tp.rate;
+		u8 total_abs_best_rate_diff =
+			abs(tb->best_rate_diff[0] + tb->best_rate_diff[1] +
+			    latest_best_rate_diff);
+
+		if (!tb->interference_likely) {
+			tb->probability_variation = 0;
+			if (!tb->unconverged &&
+			    tb->best_rate_cycle_count <=
+				    BEST_RATE_UNSTABLE_THRESHOLD &&
+			    total_abs_best_rate_diff >= 2) {
+				/*
+				 * Best rate has changed twice in a few cycles
+				 * and moved at least 2 MCSs from where it was
+				 * 3 best rate changes ago
+				 */
+				tb->unconverged = true;
+				tb->newly_unconverged = true;
+			}
+		}
+		if (tb->unconverged && !tb->newly_unconverged &&
+		    total_abs_best_rate_diff < 2) {
+			/*
+			 * Best rate has been relatively stable (not moved more
+			 * than 1 MCS after the last 3 rate changes), go back
+			 * to converged
+			 */
+			tb->unconverged = false;
+		}
+		tb->probability_variation_direction = 0;
+		tb->best_rate_cycle_count = 0;
+		tb->best_rate_diff[0] = tb->best_rate_diff[1];
+		tb->best_rate_diff[1] = latest_best_rate_diff;
+	} else {
+		tb->best_rate_cycle_count++;
+		if (tb->unconverged && !tb->newly_unconverged &&
+		    tb->best_rate_cycle_count >=
+			    BEST_RATE_CONVERGED_THRESHOLD) {
+			/*
+			 * Best rate has been stable for a while, go back to
+			 * converged
+			 */
+			tb->unconverged = false;
+		}
+	}
+
+	if (tb->newly_unconverged)
+		tb->newly_unconverged = false;
+}
+
+static u32 calculate_attempt_time(struct mmrc_rate *rate, size_t size)
+{
+	u32 time;
+
+	time = get_tx_time(rate);
+
+	if (size > DEFAULT_PACKET_SIZE_BYTES)
+		time = (time * ((size * 1000) / DEFAULT_PACKET_SIZE_BYTES)) /
+		       1000;
+	else
+		time = (time * 1000) /
+		       ((DEFAULT_PACKET_SIZE_BYTES * 1000) / size);
+
+	return time;
+}
+
+u32 mmrc_calculate_rate_tx_time(struct mmrc_rate *rate, size_t size)
+{
+	u8 i;
+	u32 total_time = 0;
+
+	for (i = 0; i < rate->attempts; i++)
+		total_time += calculate_attempt_time(rate, size);
+
+	return total_time;
+}
+
+/*
+ * Calculates the appropriate amount of additional attempts to make based on
+ * packet size and theoretical throughput.
+ */
+static void calculate_remaining_attempts(struct mmrc_table *tb,
+					 struct mmrc_rate_table *rate,
+					 s32 *rem_time, size_t size)
+{
+	size_t i;
+
+	if (*rem_time <= 0)
+		return;
+
+	for (i = 0; i < MMRC_MAX_CHAIN_LENGTH; i++) {
+		u32 attempt_time;
+		u32 attempt;
+
+		if (rate->rates[i].rate == MMRC_MCS_UNUSED)
+			break;
+
+		/*
+		 * The attempts for these rates were calculated in the initial
+		 * attempt allocation
+		 */
+		if (tb->table[rate->rates[i].index].prob < 20)
+			continue;
+
+		if (i == 0 && (calculate_throughput(tb, rate->rates[i].index) <
+			       calculate_throughput(tb, tb->best_prob.index)))
+			continue;
+
+		attempt_time = calculate_attempt_time(&rate->rates[i], size);
+		if (!attempt_time)
+			continue;
+
+		attempt = (*rem_time / tb->caps.max_rates) / attempt_time;
+		attempt += rate->rates[i].attempts;
+
+		rate->rates[i].attempts = MMRC_ATTEMPTS_TO_BITFIELD(
+			attempt > MMRC_MAX_CHAIN_ATTEMPTS ?
+				MMRC_MAX_CHAIN_ATTEMPTS :
+				attempt);
+	}
+}
+
+/* Allocate initial attempts to all rates in a rate table */
+static void allocate_initial_attempts(struct mmrc_rate_table *rate,
+				      s32 *rem_time, size_t size)
+{
+	u32 i;
+
+	for (i = 0; i < MMRC_MAX_CHAIN_LENGTH; i++) {
+		u32 attempt_time;
+
+		if (rate->rates[i].rate == MMRC_MCS_UNUSED)
+			break;
+
+		attempt_time = calculate_attempt_time(&rate->rates[i], size);
+
+		/*
+		 * if the time for a single attempt is very long, lets just
+		 * try once
+		 */
+		if (attempt_time > MAX_WINDOW_ATTEMPT_TIME) {
+			*rem_time -= attempt_time;
+			rate->rates[i].attempts = MMRC_ATTEMPTS_TO_BITFIELD(1);
+		} else {
+			*rem_time -= attempt_time * 2;
+			rate->rates[i].attempts = MMRC_ATTEMPTS_TO_BITFIELD(2);
+		}
+	}
+}
+
+void mmrc_get_rates(struct mmrc_table *tb, struct mmrc_rate_table *out,
+		    size_t size)
+{
+	u8 i;
+	u16 random_index;
+	struct mmrc_rate random;
+	struct mmrc_rate lookaround0 = tb->best_tp;
+	struct mmrc_rate lookaround1 = tb->second_tp;
+	bool is_lookaround;
+	int lookaround_index = -1;
+	int best_index = 0;
+	int random_tp = 0;
+	int best_tp;
+	int lookaround_fail_count;
+	bool try_current_lookaround = false;
+
+	s32 rem_time = RATE_WINDOW_MICROSECONDS;
+
+	memset(out, 0, sizeof(*out));
+
+	tb->lookaround_cnt = (tb->lookaround_cnt + 1) % tb->lookaround_wrap;
+	/*
+	 * Look around if the counter wraps or there has been no look around
+	 * for a number of rate control cycles.
+	 */
+	is_lookaround = (tb->fixed_rate.rate == MMRC_MCS_UNUSED) &&
+			((tb->lookaround_cnt == 0) ||
+			 ((tb->last_lookaround_cycle +
+			   LOOKAROUND_MAX_RC_CYCLES) <= tb->cycle_cnt));
+
+	/* Also skip sampling if we don't yet have data for our best rate */
+	if (tb->table[tb->best_tp.index].evidence == 0)
+		is_lookaround = false;
+
+	if (tb->lookaround_wrap != LOOKAROUND_RATE_STABLE) {
+		if (tb->stability_cnt >= tb->stability_cnt_threshold) {
+			tb->lookaround_wrap = LOOKAROUND_RATE_STABLE;
+			tb->stability_cnt_threshold =
+				STABILITY_CNT_THRESHOLD_STABLE;
+			tb->stability_cnt = STABILITY_CNT_THRESHOLD_STABLE * 2;
+			is_lookaround = false;
+		}
+	} else if (tb->stability_cnt < tb->stability_cnt_threshold) {
+		tb->stability_cnt_threshold = STABILITY_CNT_THRESHOLD_NORMAL;
+		tb->lookaround_wrap = LOOKAROUND_RATE_NORMAL;
+		tb->stability_cnt = 0;
+	}
+
+	/* Look around only when the fixed rate is not set */
+	if (is_lookaround) {
+		tb->total_lookaround++;
+		tb->forced_lookaround =
+			(tb->forced_lookaround + 1) % LOOKAROUND_RATE_NORMAL;
+		tb->last_lookaround_cycle = tb->cycle_cnt;
+
+		if (tb->current_lookaround_rate_attempts <
+		    LOOKAROUND_RATE_ATTEMPTS)
+			try_current_lookaround = true;
+
+		best_tp = calculate_throughput(tb, tb->best_tp.index);
+
+		for (lookaround_fail_count = 0;
+		     lookaround_fail_count < LOOKAROUND_FAIL_MAX;
+		     lookaround_fail_count++) {
+			if (try_current_lookaround) {
+				random_index =
+					tb->current_lookaround_rate_index;
+				try_current_lookaround = false;
+			} else {
+				random_index = get_random_u32_below(
+					rows_from_sta_caps(&tb->caps));
+			}
+			random = get_rate_row(tb, random_index);
+
+			if (!validate_rate(tb, &random))
+				continue;
+
+			if (random.rate == MMRC_MCS10)
+				continue;
+
+			if (tb->table[random_index].evidence > 0)
+				random_tp =
+					calculate_throughput(tb, random_index);
+			else
+				random_tp =
+					mmrc_calculate_theoretical_throughput(
+						random);
+
+			/*
+			 * Skip rates that can only be worse than the current
+			 * best
+			 */
+			if (random_tp <= best_tp)
+				continue;
+
+			/*
+			 * Force looking up the rate no more that one MCS.
+			 * It will avoid looking for rates with very low
+			 * success rate. In case of better environment
+			 * conditions MMRC will collect enough statistics to
+			 * climb up the rates one by one.
+			 */
+			if (random.rate > tb->best_tp.rate + 1 ||
+			    random.bw > tb->best_tp.bw + 1 ||
+			    (random.rate > tb->best_tp.rate &&
+			     random.bw > tb->best_tp.bw))
+				continue;
+
+			if (tb->current_lookaround_rate_index == random_index) {
+				tb->current_lookaround_rate_attempts++;
+			} else {
+				tb->current_lookaround_rate_attempts = 0;
+				tb->current_lookaround_rate_index =
+					random_index;
+			}
+
+			break;
+		}
+
+		if (lookaround_fail_count >= LOOKAROUND_FAIL_MAX) {
+			is_lookaround = false;
+			tb->current_lookaround_rate_index = tb->best_tp.index;
+		} else {
+			lookaround0 = random;
+			lookaround1 = tb->best_tp;
+			lookaround_index = 0;
+			best_index = 1;
+		}
+	}
+
+	if (tb->caps.max_rates == 1) {
+		out->rates[0] = (is_lookaround) ? lookaround0 : tb->best_tp;
+		out->rates[1].rate = MMRC_MCS_UNUSED;
+		out->rates[2].rate = MMRC_MCS_UNUSED;
+		out->rates[3].rate = MMRC_MCS_UNUSED;
+	} else if (tb->caps.max_rates == 2) {
+		out->rates[0] = (is_lookaround) ? lookaround0 : tb->best_tp;
+		out->rates[1] = (is_lookaround) ? lookaround1 : tb->best_prob;
+		out->rates[2].rate = MMRC_MCS_UNUSED;
+		out->rates[3].rate = MMRC_MCS_UNUSED;
+	} else if (tb->caps.max_rates == 3) {
+		out->rates[0] = (is_lookaround) ? lookaround0 : tb->best_tp;
+		out->rates[1] = (is_lookaround) ? lookaround1 : tb->second_tp;
+		out->rates[2] = tb->best_prob;
+		out->rates[3].rate = MMRC_MCS_UNUSED;
+	} else {
+		out->rates[0] = (is_lookaround) ? lookaround0 : tb->best_tp;
+		out->rates[1] = (is_lookaround) ? lookaround1 : tb->second_tp;
+		out->rates[2] = tb->best_prob;
+		out->rates[3] = tb->baseline;
+	}
+
+	/* For fallback rates, set RTS/CTS */
+	for (i = 1; i < MMRC_MAX_CHAIN_LENGTH; i++)
+		out->rates[i].flags |= BIT(MMRC_FLAGS_CTS_RTS);
+
+	/* Allocate initial attempts for rate */
+	allocate_initial_attempts(out, &rem_time, size);
+
+	/* Calculate and allocate remaining attempts */
+	calculate_remaining_attempts(tb, out, &rem_time, size);
+
+	/* Enforce limits on each attempts */
+	for (i = 0; i < MMRC_MAX_CHAIN_LENGTH; i++) {
+		if (out->rates[i].rate != MMRC_MCS_UNUSED) {
+			out->rates[i].attempts =
+				out->rates[i].attempts == 0 ?
+					MMRC_ATTEMPTS_TO_BITFIELD(
+						MMRC_MIN_CHAIN_ATTEMPTS) :
+					out->rates[i].attempts;
+			out->rates[i].attempts =
+				out->rates[i].attempts >
+						MMRC_MAX_CHAIN_ATTEMPTS ?
+					MMRC_ATTEMPTS_TO_BITFIELD(
+						MMRC_MAX_CHAIN_ATTEMPTS) :
+					out->rates[i].attempts;
+			if (i == lookaround_index &&
+			    tb->lookaround_wrap != LOOKAROUND_RATE_INIT)
+				out->rates[i].attempts =
+					MMRC_ATTEMPTS_TO_BITFIELD(1);
+		}
+	}
+
+	/*
+	 * Give the best rate at least 2 attempts to keep peak throughput
+	 * unless it is too low
+	 */
+	if (out->rates[best_index].attempts == 1 &&
+	    out->rates[best_index].rate > MMRC_MCS1)
+		out->rates[best_index].attempts = MMRC_ATTEMPTS_TO_BITFIELD(2);
+	else if (out->rates[best_index].rate <= MMRC_MCS1)
+		out->rates[best_index].attempts = 1;
+}
+
+static u32 calc_ewma_average(u32 avg, u32 latest, u32 weight)
+{
+	WARN_ON_ONCE(!(weight <= 100));
+
+	if (avg == 0)
+		return latest;
+
+	return ((latest * (100 - weight)) + (avg * weight)) / 100;
+}
+
+static void mmrc_process_variation(struct mmrc_table *tb, u16 current_success,
+				   u32 index)
+{
+	u32 current_variation;
+
+	/*
+	 * Only process probability variation for the best rate. It is likely
+	 * the only rate to have enough data to see the variation and its
+	 * statistics are more affected because they are usually collected over
+	 * the full period.
+	 */
+	if (index != tb->best_tp.index)
+		return;
+
+	if (current_success == 0) {
+		if (!tb->unconverged) {
+			/*
+			 * Best rate is failing completely, go to unconverged
+			 * mode
+			 */
+			tb->unconverged = true;
+			tb->newly_unconverged = true;
+		}
+		return;
+	}
+
+	if (tb->table[index].prob == 0)
+		return;
+
+	/* Don't process variation while converging after association */
+	if (tb->lookaround_wrap == LOOKAROUND_RATE_INIT)
+		return;
+
+	current_variation = abs(current_success - tb->table[index].prob);
+
+	/* Calculate the EWMA of the probability variation */
+	tb->probability_variation = calc_ewma_average(
+		tb->probability_variation, current_variation, VARIATION_EWMA);
+
+	/*
+	 * Process the variation direction to distinguish converged and
+	 * unconverged scenarios
+	 */
+	if (tb->probability_variation >= MODERATE_VARIATION_THRESHOLD ||
+	    tb->interference_likely) {
+		if ((current_success - tb->table[index].prob) *
+			    tb->probability_variation_direction <
+		    0)
+			tb->probability_variation_direction = 0;
+		else if (current_success > tb->table[index].prob)
+			tb->probability_variation_direction =
+				min(tb->probability_variation_direction + 1,
+				    MAX_VARIATION_DIRECTION);
+		else if (current_success < tb->table[index].prob)
+			tb->probability_variation_direction =
+				max(tb->probability_variation_direction - 1,
+				    -MAX_VARIATION_DIRECTION);
+	}
+
+	if (tb->best_rate_cycle_count > VARIATION_DIRECTION_THRESHOLD &&
+	    tb->probability_variation >= SIGNIFICANT_VARIATION_THRESHOLD) {
+		/*
+		 * Only enter interference mode if the best rate is stable for
+		 * enough cycles to determine the direction is random and not
+		 * in one direction only
+		 */
+		if (abs(tb->probability_variation_direction) <=
+			    VARIATION_DIRECTION_THRESHOLD &&
+		    !tb->interference_likely) {
+			tb->interference_likely = true;
+		}
+	} else if (tb->interference_likely &&
+		   (tb->probability_variation <= MINOR_VARIATION_THRESHOLD ||
+		    abs(tb->probability_variation_direction) ==
+			    MAX_VARIATION_DIRECTION)) {
+		/*
+		 * Exit interference mode if the variability drops or the
+		 * direction stops being random
+		 */
+		tb->interference_likely = false;
+	}
+}
+
+void mmrc_update(struct mmrc_table *tb)
+{
+	u32 i;
+	u16 this_success;
+	u32 scale;
+	u32 scaled_ewma;
+	u32 new_stats = 0;
+	u32 attempts_for_stats;
+	u32 success_for_stats;
+	u32 min_stats;
+	u32 throughput;
+	u32 evidence_sent;
+
+	tb->cycle_cnt++;
+
+	/* Allow less minimum stats when converging */
+	if (tb->lookaround_wrap != LOOKAROUND_RATE_INIT)
+		min_stats = STATS_MIN_NORMAL;
+	else
+		min_stats = STATS_MIN_INIT;
+
+	for (i = 0; i < rows_from_sta_caps(&tb->caps); i++) {
+		/* This algorithm is keeping track of the amount of evidence,
+		 * being packets that have been recently sent at this rate.
+		 * This value is smoothed with an EWMA function over time and
+		 * used to update the probability of a rate succeeding
+		 * dynamically. This method allows MMRC to react timely if a
+		 * new rate is used that hasn't been used recently
+		 */
+
+		/* Necessary to prevent a divide by 0 */
+		if (tb->table[i].evidence == 0)
+			scale = 0;
+		else
+			scale = ((tb->table[i].evidence * 2) * 100) /
+				((tb->table[i].sent * EVIDENCE_SCALE) +
+				 tb->table[i].evidence);
+
+		/* Restrict scale to appropriate values */
+		if (scale > 100)
+			scale = 100;
+
+		scaled_ewma = scale * EWMA / 100;
+
+		/*
+		 * Only count new packets for evidence if we will process
+		 * them
+		 */
+		evidence_sent =
+			tb->table[i].sent >= min_stats ? tb->table[i].sent : 0;
+		tb->table[i].evidence = calc_ewma_average(
+			tb->table[i].evidence, evidence_sent * EVIDENCE_SCALE,
+			scaled_ewma);
+
+		if (tb->table[i].evidence > EVIDENCE_MAX)
+			tb->table[i].evidence = EVIDENCE_MAX;
+
+		/* Try to use statistics from acknowledged AMPDUs first */
+		attempts_for_stats = tb->table[i].back_mpdu_success +
+				     tb->table[i].back_mpdu_failure;
+		success_for_stats = tb->table[i].back_mpdu_success;
+
+		/*
+		 * Use the full statistics if rates are not converged or there
+		 * were no AMPDUs for this rate or the remaining attempts are
+		 * less than half of what we have from AMPDUs.
+		 */
+		if (!tb->table[i].have_sent_ampdus || tb->unconverged ||
+		    attempts_for_stats < AMPDU_STATS_MIN ||
+		    (tb->table[i].sent - attempts_for_stats <
+		     attempts_for_stats / 2)) {
+			attempts_for_stats = tb->table[i].sent;
+			success_for_stats = tb->table[i].sent_success;
+		}
+
+		if (attempts_for_stats >= min_stats ||
+		    (attempts_for_stats > 0 && tb->table[i].prob > 0)) {
+			new_stats = 1;
+			this_success =
+				(100 * success_for_stats) / attempts_for_stats;
+
+			if (scaled_ewma)
+				mmrc_process_variation(tb, this_success, i);
+
+			tb->table[i].prob = calc_ewma_average(
+				tb->table[i].prob, this_success, scaled_ewma);
+
+			/* Clear our sent statistics and update totals */
+			tb->table[i].total_sent += tb->table[i].sent;
+			tb->table[i].sent = 0;
+
+			tb->table[i].total_success += tb->table[i].sent_success;
+			tb->table[i].sent_success = 0;
+
+			tb->table[i].back_mpdu_failure = 0;
+			tb->table[i].back_mpdu_success = 0;
+			tb->table[i].have_sent_ampdus = false;
+		}
+
+		throughput = calculate_throughput(tb, i);
+		if (tb->table[i].max_throughput < throughput)
+			tb->table[i].max_throughput = throughput;
+
+		/*
+		 * Reset the running average windows if reached collector
+		 * limits
+		 */
+		if (tb->table[i].sum_throughput > (0xFFFFFFFF - throughput)) {
+			tb->table[i].sum_throughput /=
+				tb->table[i].avg_throughput_counter;
+			tb->table[i].avg_throughput_counter = 1;
+		}
+		/* Update the sum and counter so it will be possible later to
+		 * calculate the running average throughput
+		 */
+		tb->table[i].sum_throughput += throughput;
+		tb->table[i].avg_throughput_counter++;
+	}
+
+	generate_table_priority(tb, new_stats);
+
+	/*
+	 * Switch to faster lookaround mode if rates drop low at very low
+	 * bandwidth or we are in unconverged mode. Switching at low bandwidth
+	 * and rate is to help recover quickly from rates where we would need
+	 * to fragment standard MTU size packets.
+	 */
+	if (tb->lookaround_wrap != LOOKAROUND_RATE_INIT &&
+	    (tb->unconverged || (tb->best_tp.bw == MMRC_BW_1MHZ &&
+				 tb->best_tp.rate <= MMRC_MCS2))) {
+		tb->lookaround_cnt = 0;
+		tb->lookaround_wrap = LOOKAROUND_RATE_INIT;
+		tb->stability_cnt_threshold = STABILITY_CNT_THRESHOLD_INIT;
+	}
+
+	/*
+	 * If it is unlikely we can do the lookaround attempts in two RC cycles
+	 * choose a new rate
+	 */
+	if (tb->current_lookaround_rate_attempts <=
+	    (LOOKAROUND_RATE_ATTEMPTS / 2))
+		tb->current_lookaround_rate_attempts = LOOKAROUND_RATE_ATTEMPTS;
+}
+
+void mmrc_feedback(struct mmrc_table *tb, struct mmrc_rate_table *rates,
+		   s32 retry_count, bool was_aggregated)
+{
+	s32 ind = retry_count;
+	u32 i;
+
+	for (i = 0; i < MMRC_MAX_CHAIN_LENGTH; i++) {
+		rate_update_index(tb, &rates->rates[i]);
+		tb->table[rates->rates[i].index].have_sent_ampdus |=
+			was_aggregated;
+
+		if ((s32)rates->rates[i].attempts < ind) {
+			ind = ind - rates->rates[i].attempts;
+			tb->table[rates->rates[i].index].sent +=
+				rates->rates[i].attempts;
+			if (was_aggregated) {
+				tb->table[rates->rates[i].index]
+					.back_mpdu_failure +=
+					rates->rates[i].attempts;
+			}
+		} else {
+			tb->table[rates->rates[i].index].sent += ind;
+			tb->table[rates->rates[i].index].sent_success += 1;
+			if (was_aggregated) {
+				tb->table[rates->rates[i].index]
+					.back_mpdu_success += 1;
+				tb->table[rates->rates[i].index]
+					.back_mpdu_failure +=
+					ind > 1 ? ind - 1 : 0;
+			}
+			return;
+		}
+	}
+}
+
+/*
+ * Chooses a reasonable starting rate based on range (gathered from
+ * RSSI measurements) or bandwidth. Then fills out the 3 retry rates
+ * so a full set of rates is available.
+ */
+static void mmrc_init_rates(struct mmrc_table *tb, s8 rssi)
+{
+	tb->best_tp.bw = MMRC_MAX_BW(tb->caps.bandwidth);
+	if (tb->caps.sgi_per_bw & SGI_PER_BW(tb->best_tp.bw))
+		tb->best_tp.guard = MMRC_GUARD_TO_BITFIELD(MMRC_GUARD_SHORT);
+	else
+		tb->best_tp.guard = MMRC_GUARD_TO_BITFIELD(MMRC_GUARD_LONG);
+	tb->best_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS0);
+
+	if (rssi >= MMRC_SHORT_RANGE_RSSI_LIMIT)
+		tb->best_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS7);
+	else if (rssi < MMRC_SHORT_RANGE_RSSI_LIMIT &&
+		 rssi >= MMRC_MID_RANGE_RSSI_LIMIT)
+		tb->best_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS3);
+	else if (tb->best_tp.bw == MMRC_BW_1MHZ ||
+		 tb->best_tp.bw == MMRC_BW_2MHZ)
+		/*
+		 * To compensate for slow feedback when running with 1 and 2
+		 * MHz bandwidth, we start from MCS3 which will correspond to
+		 * reasonable feedback and will avoid resetting the rate table
+		 * evidence.
+		 */
+		tb->best_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS3);
+
+	tb->best_tp.ss = MMRC_SS_TO_BITFIELD(MMRC_SPATIAL_STREAM_1);
+	rate_update_index(tb, &tb->best_tp);
+	/* Init every rate in case they are needed to set the retry rates */
+	tb->second_tp = tb->best_tp;
+	tb->best_prob = tb->best_tp;
+	tb->baseline = tb->best_tp;
+	mmrc_fill_retry_rates(tb);
+}
+
+void mmrc_sta_init(struct mmrc_table *tb, struct mmrc_sta_capabilities *caps,
+		   s8 rssi)
+{
+	u32 i;
+	u16 row_count = rows_from_sta_caps(caps);
+
+	memset(tb, 0, mmrc_memory_required_for_caps(caps));
+	memcpy(&tb->caps, caps, sizeof(tb->caps));
+
+	for (i = 0; i < row_count; i++) {
+		tb->table[i].prob = RATE_INIT_PROBABILITY;
+		tb->table[i].evidence = 0;
+		tb->table[i].sum_throughput = 0;
+		tb->table[i].avg_throughput_counter = 0;
+		tb->table[i].max_throughput = 0;
+	}
+
+	tb->fixed_rate.rate = MMRC_MCS_UNUSED;
+	tb->cycle_cnt = 0;
+	tb->last_lookaround_cycle = 0;
+	tb->lookaround_cnt = 0;
+	tb->lookaround_wrap = LOOKAROUND_RATE_INIT;
+	tb->unconverged = true;
+	tb->newly_unconverged = true;
+	tb->stability_cnt_threshold = STABILITY_CNT_THRESHOLD_INIT;
+	tb->baseline = get_rate_row(tb, find_baseline_index(tb));
+	mmrc_init_rates(tb, rssi);
+}
+
+bool mmrc_set_fixed_rate(struct mmrc_table *tb, struct mmrc_rate fixed_rate)
+{
+	bool caps_support_rate = true;
+
+	/* Do not accept rate which does not support the STA capabilities */
+	if ((BIT(fixed_rate.rate) & tb->caps.rates) == 0 ||
+	    (BIT(fixed_rate.bw) & tb->caps.bandwidth) == 0 ||
+	    (BIT(fixed_rate.ss) & tb->caps.spatial_streams) == 0 ||
+	    (BIT(fixed_rate.guard) & tb->caps.guard) == 0)
+		caps_support_rate = false;
+
+	if (validate_rate(tb, &fixed_rate) && caps_support_rate) {
+		tb->fixed_rate = fixed_rate;
+		rate_update_index(tb, &tb->fixed_rate);
+		return true;
+	}
+
+	return false;
+}
diff --git a/drivers/net/wireless/morsemicro/mm81x/mmrc.h b/drivers/net/wireless/morsemicro/mm81x/mmrc.h
new file mode 100644
index 00000000000000..a4c7d941ad55c1
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/mmrc.h
@@ -0,0 +1,193 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_MMRC_H_
+#define _MM81X_MMRC_H_
+
+#include <linux/version.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/bitops.h>
+#include <linux/random.h>
+#include <linux/time.h>
+
+/* The max length of a retry chain for a single packet transmission */
+#define MMRC_MAX_CHAIN_LENGTH 4
+
+/* Rate minimum allowed attempts */
+#define MMRC_MIN_CHAIN_ATTEMPTS 1
+
+/* Rate upper limit for attempts */
+#define MMRC_MAX_CHAIN_ATTEMPTS 2
+
+/* The frequency of MMRC stat table updates */
+#define MMRC_UPDATE_FREQUENCY_MS 100
+
+enum mmrc_flags {
+	MMRC_FLAGS_CTS_RTS,
+};
+
+enum mmrc_mcs_rate {
+	MMRC_MCS0,
+	MMRC_MCS1,
+	MMRC_MCS2,
+	MMRC_MCS3,
+	MMRC_MCS4,
+	MMRC_MCS5,
+	MMRC_MCS6,
+	MMRC_MCS7,
+	MMRC_MCS8,
+	MMRC_MCS9,
+	MMRC_MCS10,
+	MMRC_MCS_UNUSED,
+};
+
+enum mmrc_bw {
+	MMRC_BW_1MHZ = 0,
+	MMRC_BW_2MHZ = 1,
+	MMRC_BW_4MHZ = 2,
+	MMRC_BW_8MHZ = 3,
+	MMRC_BW_16MHZ = 4,
+	MMRC_BW_MAX = 5,
+};
+
+enum mmrc_spatial_stream {
+	MMRC_SPATIAL_STREAM_1 = 0,
+	MMRC_SPATIAL_STREAM_2 = 1,
+	MMRC_SPATIAL_STREAM_3 = 2,
+	MMRC_SPATIAL_STREAM_4 = 3,
+	MMRC_SPATIAL_STREAM_MAX,
+};
+
+enum mmrc_guard {
+	MMRC_GUARD_LONG = 0,
+	MMRC_GUARD_SHORT = 1,
+	MMRC_GUARD_MAX,
+};
+
+#define MMRC_RATE_TO_BITFIELD(x) ((x) & 0xF)
+#define MMRC_ATTEMPTS_TO_BITFIELD(x) ((x) & 0x7)
+#define MMRC_GUARD_TO_BITFIELD(x) ((x) & 0x1)
+#define MMRC_SS_TO_BITFIELD(x) ((x) & 0x3)
+#define MMRC_BW_TO_BITFIELD(x) ((x) & 0x7)
+#define MMRC_FLAGS_TO_BITFIELD(x) ((x) & 0x7)
+
+struct mmrc_rate {
+	u8 rate : 4;
+	u8 attempts : 3;
+	u8 guard : 1;
+	u8 ss : 2;
+	u8 bw : 3;
+	u8 flags : 3;
+	u16 index;
+};
+
+struct mmrc_rate_table {
+	struct mmrc_rate rates[MMRC_MAX_CHAIN_LENGTH];
+};
+
+#define SGI_PER_BW(bw) (1 << (bw))
+
+struct mmrc_sta_capabilities {
+	u8 max_rates : 3;
+	u8 max_retries : 3;
+	u8 bandwidth : 5;
+	u8 spatial_streams : 4;
+	u16 rates : 11;
+	u8 guard : 2;
+	u8 sta_flags : 4;
+	u8 sgi_per_bw : 5;
+};
+
+struct mmrc_stats_table {
+	u32 avg_throughput_counter;
+	u32 sum_throughput;
+	u32 max_throughput;
+	u16 sent;
+	u16 sent_success;
+	u16 back_mpdu_success;
+	u16 back_mpdu_failure;
+	u32 total_sent;
+	u32 total_success;
+	u16 evidence;
+	u8 prob;
+	bool have_sent_ampdus;
+};
+
+struct mmrc_table {
+	struct mmrc_sta_capabilities caps;
+	struct mmrc_rate best_tp;
+	struct mmrc_rate second_tp;
+	struct mmrc_rate baseline;
+	struct mmrc_rate best_prob;
+	struct mmrc_rate fixed_rate;
+	u32 cycle_cnt;
+	u32 last_lookaround_cycle;
+	u8 lookaround_cnt;
+
+	/* The ratio of using normal rate and sampling */
+	u8 lookaround_wrap;
+
+	/*
+	 * A counter that is used to determine when we should force a
+	 * lookaround. Should be a portion of the above lookaround with
+	 * less constraints
+	 */
+	u8 forced_lookaround;
+
+	u8 current_lookaround_rate_attempts;
+	u16 current_lookaround_rate_index;
+	u32 total_lookaround;
+
+	/*
+	 * A counter to detect if the current best rate is optimal
+	 * and may slow down sample frequency.
+	 */
+	u32 stability_cnt;
+
+	u32 stability_cnt_threshold;
+	u8 probability_variation;
+
+	/* The difference in MCS from each of the last 2 rate changes */
+	s8 best_rate_diff[2];
+
+	/* Indication of random versus consistently one-sided variation */
+	s8 probability_variation_direction;
+
+	/* Has rate control detected possible interference */
+	bool interference_likely;
+
+	/* Has rate control detected the best rate is no longer converged */
+	bool unconverged;
+
+	/* Is rate control just entering unconverged state */
+	bool newly_unconverged;
+
+	/*
+	 * Number of rate control cycles the best rate has remained
+	 * unchanged
+	 */
+	s32 best_rate_cycle_count;
+
+	/*
+	 * The probability table for the STA. This MUST always be the last
+	 * element in the struct.
+	 */
+	struct mmrc_stats_table table[];
+};
+
+void mmrc_sta_init(struct mmrc_table *tb, struct mmrc_sta_capabilities *caps,
+		   s8 rssi);
+size_t mmrc_memory_required_for_caps(struct mmrc_sta_capabilities *caps);
+void mmrc_get_rates(struct mmrc_table *tb, struct mmrc_rate_table *out,
+		    size_t size);
+void mmrc_feedback(struct mmrc_table *tb, struct mmrc_rate_table *rates,
+		   s32 retry_count, bool was_aggregated);
+void mmrc_update(struct mmrc_table *tb);
+bool mmrc_set_fixed_rate(struct mmrc_table *tb, struct mmrc_rate fixed_rate);
+u32 mmrc_calculate_theoretical_throughput(struct mmrc_rate rate);
+u32 mmrc_calculate_rate_tx_time(struct mmrc_rate *rate, size_t size);
+
+#endif /* _MMRC_H_ */
diff --git a/drivers/net/wireless/morsemicro/mm81x/ps.c b/drivers/net/wireless/morsemicro/mm81x/ps.c
new file mode 100644
index 00000000000000..ab67823452eeb8
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/ps.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include "hif.h"
+#include "skbq.h"
+#include "mac.h"
+#include "bus.h"
+#include "ps.h"
+
+static void mm81x_ps_wakeup(struct mm81x_ps *mps)
+{
+	struct mm81x *mors = container_of(mps, struct mm81x, ps);
+
+	if (!mps->enable || !mps->suspended)
+		return;
+
+	mm81x_set_bus_enable(mors, true);
+	mps->suspended = false;
+}
+
+static void mm81x_ps_sleep(struct mm81x_ps *mps)
+{
+	struct mm81x *mors = container_of(mps, struct mm81x, ps);
+
+	if (!mps->enable || mps->suspended)
+		return;
+
+	mps->suspended = true;
+	mm81x_set_bus_enable(mors, false);
+}
+
+static void mm81x_ps_evaluate(struct mm81x_ps *mps)
+{
+	struct mm81x *mors = container_of(mps, struct mm81x, ps);
+	bool needs_wake = false;
+	unsigned long flags_on_entry =
+		(mors->hif.event_flags &
+		 ~BIT(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND));
+
+	if (!mps->enable)
+		return;
+
+	needs_wake = (mps->wakers > 0);
+	needs_wake |= (flags_on_entry > 0);
+	needs_wake |= (mm81x_hif_get_tx_buffered_count(mors) > 0);
+
+	if (needs_wake) {
+		mm81x_ps_wakeup(mps);
+		return;
+	}
+
+	mm81x_ps_sleep(mps);
+}
+
+static void mm81x_ps_evaluate_work(struct work_struct *work)
+{
+	struct mm81x_ps *mps =
+		container_of(work, struct mm81x_ps, delayed_eval_work.work);
+
+	if (mps->enable) {
+		mutex_lock(&mps->lock);
+		mm81x_ps_evaluate(mps);
+		mutex_unlock(&mps->lock);
+	}
+}
+
+void mm81x_ps_enable(struct mm81x *mors)
+{
+	struct mm81x_ps *mps = &mors->ps;
+
+	if (mps->enable) {
+		mutex_lock(&mps->lock);
+		if (mps->wakers == 0) {
+			WARN_ON_ONCE(1);
+		} else {
+			mps->wakers--;
+			mm81x_ps_evaluate(mps);
+		}
+		mutex_unlock(&mps->lock);
+	}
+}
+
+void mm81x_ps_disable(struct mm81x *mors)
+{
+	struct mm81x_ps *mps = &mors->ps;
+
+	if (mps->enable) {
+		mutex_lock(&mps->lock);
+		mps->wakers++;
+		mm81x_ps_evaluate(mps);
+		mutex_unlock(&mps->lock);
+	}
+}
+
+int mm81x_ps_init(struct mm81x *mors)
+{
+	struct mm81x_ps *mps = &mors->ps;
+
+	mps->enable = (mors->bus_type == MM81X_BUS_TYPE_USB);
+	mps->suspended = true;
+	mps->wakers = 1; /* we default to being on */
+	mutex_init(&mps->lock);
+	INIT_DELAYED_WORK(&mps->delayed_eval_work, mm81x_ps_evaluate_work);
+
+	return 0;
+}
+
+void mm81x_ps_finish(struct mm81x *mors)
+{
+	struct mm81x_ps *mps = &mors->ps;
+
+	if (mps->enable) {
+		mps->enable = false;
+		cancel_delayed_work_sync(&mps->delayed_eval_work);
+	}
+}
diff --git a/drivers/net/wireless/morsemicro/mm81x/ps.h b/drivers/net/wireless/morsemicro/mm81x/ps.h
new file mode 100644
index 00000000000000..0b59bb4145ab9c
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/ps.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_PS_H_
+#define _MM81X_PS_H_
+
+#include "core.h"
+
+/* This should be nominally <= the dynamic ps timeout */
+#define NETWORK_BUS_TIMEOUT_MS (90)
+
+/* The default period of time to wait to re-evaluate powersave */
+#define DEFAULT_BUS_TIMEOUT_MS (50)
+
+void mm81x_ps_disable(struct mm81x *mors);
+void mm81x_ps_enable(struct mm81x *mors);
+int mm81x_ps_init(struct mm81x *mors);
+void mm81x_ps_finish(struct mm81x *mors);
+
+#endif /* !_MM81X_PS_H_ */
diff --git a/drivers/net/wireless/morsemicro/mm81x/rate_code.h b/drivers/net/wireless/morsemicro/mm81x/rate_code.h
new file mode 100644
index 00000000000000..c60fcb9447c4ab
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/rate_code.h
@@ -0,0 +1,177 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_RATE_CODE_H_
+#define _MM81X_RATE_CODE_H_
+
+#include <linux/types.h>
+
+enum dot11_bandwidth {
+	DOT11_BANDWIDTH_1MHZ = 0,
+	DOT11_BANDWIDTH_2MHZ = 1,
+	DOT11_BANDWIDTH_4MHZ = 2,
+	DOT11_BANDWIDTH_8MHZ = 3,
+	DOT11_BANDWIDTH_16MHZ = 4,
+
+	DOT11_MAX_BANDWIDTH = DOT11_BANDWIDTH_16MHZ,
+	DOT11_INVALID_BANDWIDTH = 5
+};
+
+enum mm81x_rate_preamble {
+	/* S1G LONG format (with SIG-A and SIG-B) */
+	MM81X_RATE_PREAMBLE_S1G_LONG = 0,
+	/* This is the most common format used */
+	MM81X_RATE_PREAMBLE_S1G_SHORT = 1,
+	/* S1G 1M format */
+	MM81X_RATE_PREAMBLE_S1G_1M = 2,
+
+	MM81X_RATE_MAX_PREAMBLE = MM81X_RATE_PREAMBLE_S1G_1M,
+	MM81X_RATE_INVALID_PREAMBLE = 7
+};
+
+typedef __le32 mm81x_rate_code_t;
+
+#define MM81X_RATECODE_PREAMBLE (0x0000000F)
+#define MM81X_RATECODE_MCS_INDEX (0x000000F0)
+#define MM81X_RATECODE_NSS_INDEX (0x00000700)
+#define MM81X_RATECODE_BW_INDEX (0x00003800)
+#define MM81X_RATECODE_RTS_FLAG (0x00010000)
+#define MM81X_RATECODE_SHORT_GI_FLAG (0x00040000)
+#define MM81X_RATECODE_DUP_BW_INDEX (0x01C00000)
+
+static inline enum mm81x_rate_preamble
+mm81x_ratecode_preamble_get(mm81x_rate_code_t rc)
+{
+	return (enum mm81x_rate_preamble)(
+		le32_get_bits(rc, MM81X_RATECODE_PREAMBLE));
+}
+
+static inline u8 mm81x_ratecode_mcs_index_get(mm81x_rate_code_t rc)
+{
+	return le32_get_bits(rc, MM81X_RATECODE_MCS_INDEX);
+}
+
+static inline u8 mm81x_ratecode_nss_index_get(mm81x_rate_code_t rc)
+{
+	return le32_get_bits(rc, MM81X_RATECODE_NSS_INDEX);
+}
+
+static inline enum dot11_bandwidth
+mm81x_ratecode_bw_index_get(mm81x_rate_code_t rc)
+{
+	return (enum dot11_bandwidth)(
+		le32_get_bits(rc, MM81X_RATECODE_BW_INDEX));
+}
+
+static inline bool mm81x_ratecode_rts_get(mm81x_rate_code_t rc)
+{
+	return le32_get_bits(rc, MM81X_RATECODE_RTS_FLAG);
+}
+
+static inline bool mm81x_ratecode_sgi_get(mm81x_rate_code_t rc)
+{
+	return le32_get_bits(rc, MM81X_RATECODE_SHORT_GI_FLAG);
+}
+
+static inline enum dot11_bandwidth
+mm81x_ratecode_dup_bw_index_get(mm81x_rate_code_t rc)
+{
+	return (enum dot11_bandwidth)(
+		le32_get_bits(rc, MM81X_RATECODE_DUP_BW_INDEX));
+}
+
+#define MM81X_RATECODE_INIT(bw_idx, nss_idx, mcs_idx, preamble)  \
+	(le32_encode_bits((bw_idx), MM81X_RATECODE_BW_INDEX) |   \
+	 le32_encode_bits((nss_idx), MM81X_RATECODE_NSS_INDEX) | \
+	 le32_encode_bits((mcs_idx), MM81X_RATECODE_MCS_INDEX) | \
+	 le32_encode_bits((preamble), MM81X_RATECODE_PREAMBLE))
+
+static inline mm81x_rate_code_t
+mm81x_ratecode_init(enum dot11_bandwidth bw_index, u32 nss_index, u32 mcs_index,
+		    enum mm81x_rate_preamble preamble)
+{
+	return MM81X_RATECODE_INIT(bw_index, nss_index, mcs_index, preamble);
+}
+
+static inline void
+mm81x_ratecode_preamble_set(mm81x_rate_code_t *rc,
+			    enum mm81x_rate_preamble preamble)
+{
+	*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_PREAMBLE)) |
+	      le32_encode_bits(preamble, MM81X_RATECODE_PREAMBLE);
+}
+
+static inline void mm81x_ratecode_mcs_index_set(mm81x_rate_code_t *rc,
+						u32 mcs_index)
+{
+	*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_MCS_INDEX)) |
+	      le32_encode_bits(mcs_index, MM81X_RATECODE_MCS_INDEX);
+}
+
+static inline void mm81x_ratecode_nss_index_set(mm81x_rate_code_t *rc,
+						u32 nss_index)
+{
+	*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_NSS_INDEX)) |
+	      le32_encode_bits(nss_index, MM81X_RATECODE_NSS_INDEX);
+}
+
+static inline void mm81x_ratecode_bw_index_set(mm81x_rate_code_t *rc,
+					       enum dot11_bandwidth bw_index)
+{
+	*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_BW_INDEX)) |
+	      le32_encode_bits(bw_index, MM81X_RATECODE_BW_INDEX);
+}
+
+static inline void
+mm81x_ratecode_update_s1g_bw_preamble(mm81x_rate_code_t *rc,
+				      enum dot11_bandwidth bw_index)
+{
+	enum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT;
+
+	if (bw_index == DOT11_BANDWIDTH_1MHZ)
+		pream = MM81X_RATE_PREAMBLE_S1G_1M;
+
+	mm81x_ratecode_preamble_set(rc, pream);
+	mm81x_ratecode_bw_index_set(rc, bw_index);
+}
+
+static inline void
+mm81x_ratecode_dup_bw_index_set(mm81x_rate_code_t *rc,
+				enum dot11_bandwidth dup_bw_index)
+{
+	*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_DUP_BW_INDEX)) |
+	      le32_encode_bits(dup_bw_index, MM81X_RATECODE_DUP_BW_INDEX);
+}
+
+static inline void mm81x_ratecode_enable_rts(mm81x_rate_code_t *rc)
+{
+	*rc |= cpu_to_le32(MM81X_RATECODE_RTS_FLAG);
+}
+
+static inline void mm81x_ratecode_enable_sgi(mm81x_rate_code_t *rc)
+{
+	*rc |= cpu_to_le32(MM81X_RATECODE_SHORT_GI_FLAG);
+}
+
+static inline enum dot11_bandwidth mm81x_ratecode_bw_mhz_to_bw_index(u8 bw_mhz)
+{
+	return ((bw_mhz == 1) ? DOT11_BANDWIDTH_1MHZ :
+		(bw_mhz == 2) ? DOT11_BANDWIDTH_2MHZ :
+		(bw_mhz == 4) ? DOT11_BANDWIDTH_4MHZ :
+		(bw_mhz == 8) ? DOT11_BANDWIDTH_8MHZ :
+				DOT11_BANDWIDTH_2MHZ);
+}
+
+static inline u8
+mm81x_ratecode_bw_index_to_s1g_bw_mhz(enum dot11_bandwidth bw_idx)
+{
+	return ((bw_idx == DOT11_BANDWIDTH_1MHZ) ? 1 :
+		(bw_idx == DOT11_BANDWIDTH_2MHZ) ? 2 :
+		(bw_idx == DOT11_BANDWIDTH_4MHZ) ? 4 :
+		(bw_idx == DOT11_BANDWIDTH_8MHZ) ? 8 :
+						   2);
+}
+
+#endif
diff --git a/drivers/net/wireless/morsemicro/mm81x/rc.c b/drivers/net/wireless/morsemicro/mm81x/rc.c
new file mode 100644
index 00000000000000..04aff66de4bd96
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/rc.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include "core.h"
+#include "mac.h"
+#include "bus.h"
+#include "rc.h"
+
+#define MM81X_RC_BW_TO_MMRC_BW(X)    \
+	(((X) == 1) ? MMRC_BW_1MHZ : \
+	 ((X) == 2) ? MMRC_BW_2MHZ : \
+	 ((X) == 4) ? MMRC_BW_4MHZ : \
+	 ((X) == 8) ? MMRC_BW_8MHZ : \
+		      MMRC_BW_2MHZ)
+
+static void mm81x_rc_work(struct work_struct *work)
+{
+	struct mm81x_rc *mrc = container_of(work, struct mm81x_rc, work);
+	struct list_head *pos;
+
+	spin_lock_bh(&mrc->lock);
+
+	list_for_each(pos, &mrc->stas) {
+		struct mm81x_rc_sta *mrc_sta =
+			container_of(pos, struct mm81x_rc_sta, list);
+		unsigned long now = jiffies;
+
+		mrc_sta->last_update = now;
+
+		mmrc_update(mrc_sta->tb);
+	}
+
+	spin_unlock_bh(&mrc->lock);
+
+	mod_timer(&mrc->timer, jiffies + msecs_to_jiffies(100));
+}
+
+static void mm81x_rc_timer(struct timer_list *t)
+{
+	struct mm81x_rc *mrc = timer_container_of(mrc, t, timer);
+	struct mm81x *mors = mrc->mors;
+
+	queue_work(mors->net_wq, &mors->mrc.work);
+}
+
+void mm81x_rc_init(struct mm81x *mors)
+{
+	INIT_LIST_HEAD(&mors->mrc.stas);
+	spin_lock_init(&mors->mrc.lock);
+
+	INIT_WORK(&mors->mrc.work, mm81x_rc_work);
+	timer_setup(&mors->mrc.timer, mm81x_rc_timer, 0);
+
+	mors->mrc.mors = mors;
+	mod_timer(&mors->mrc.timer, jiffies + msecs_to_jiffies(100));
+}
+
+void mm81x_rc_deinit(struct mm81x *mors)
+{
+	cancel_work_sync(&mors->mrc.work);
+	timer_delete_sync_try(&mors->mrc.timer);
+}
+
+static void mm81x_rc_sta_config_guard_per_bw(struct ieee80211_sta *sta,
+					     struct mmrc_sta_capabilities *caps)
+{
+	caps->guard = BIT(MMRC_GUARD_LONG);
+
+	if (caps->bandwidth & BIT(MMRC_BW_1MHZ)) {
+		caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_1MHZ);
+		caps->guard |= BIT(MMRC_GUARD_SHORT);
+	}
+
+	if (caps->bandwidth & BIT(MMRC_BW_2MHZ)) {
+		caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_2MHZ);
+		caps->guard |= BIT(MMRC_GUARD_SHORT);
+	}
+
+	if (caps->bandwidth & BIT(MMRC_BW_4MHZ)) {
+		caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_4MHZ);
+		caps->guard |= BIT(MMRC_GUARD_SHORT);
+	}
+
+	if (caps->bandwidth & BIT(MMRC_BW_8MHZ)) {
+		caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_8MHZ);
+		caps->guard |= BIT(MMRC_GUARD_SHORT);
+	}
+}
+
+static void mm81x_rc_sta_add_s1g_sta_caps(struct mm81x *mors,
+					  struct mmrc_sta_capabilities *caps,
+					  struct ieee80211_sta_s1g_cap *s1g_cap)
+{
+	int nss_idx = 0;
+	u8 rx_mcs = s1g_cap->nss_mcs[0] & 0x3; /* 1SS */
+	u8 tx_mcs = (s1g_cap->nss_mcs[2] >> 1) & 0x3; /* 1SS */
+	u8 mcs = min(rx_mcs, tx_mcs);
+
+	switch (mcs) {
+	case IEEE80211_VHT_MCS_SUPPORT_0_9: /* VHT 9 ->  S1G 9 */
+		caps->rates |= BIT(MMRC_MCS9) | BIT(MMRC_MCS8);
+		fallthrough;
+	case IEEE80211_VHT_MCS_SUPPORT_0_8: /* VHT 8 -> S1G 7 */
+		caps->rates |= BIT(MMRC_MCS7) | BIT(MMRC_MCS6) |
+			       BIT(MMRC_MCS5) | BIT(MMRC_MCS4) | BIT(MMRC_MCS3);
+		fallthrough;
+	case IEEE80211_VHT_MCS_SUPPORT_0_7: /* VHT 7 -> S1G 2 */
+		caps->rates |= BIT(MMRC_MCS2) | BIT(MMRC_MCS1) |
+			       BIT(MMRC_MCS0) | BIT(MMRC_MCS10);
+		caps->spatial_streams |= (BIT(nss_idx) & 0x0F);
+		break;
+
+	default:
+		dev_warn(mors->dev, "Invalid MCS encoding 0x%02x for stream %d",
+			 mcs, nss_idx);
+	}
+}
+
+int mm81x_rc_sta_add(struct mm81x *mors, struct ieee80211_vif *vif,
+		     struct ieee80211_sta *sta)
+{
+	struct ieee80211_sta_s1g_cap *s1g_cap = &sta->deflink.s1g_cap;
+	struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv;
+	struct mmrc_sta_capabilities caps;
+	int oper_bw_mhz = cfg80211_chandef_get_width(&mors->chandef);
+	size_t table_mem_size;
+	struct mmrc_table *tb;
+
+	memset(&caps, 0, sizeof(caps));
+
+	mm81x_rc_sta_add_s1g_sta_caps(mors, &caps, s1g_cap);
+
+	/* Configure STA for support up to 8MHZ */
+	while (oper_bw_mhz > 0) {
+		caps.bandwidth |= BIT(MM81X_RC_BW_TO_MMRC_BW(oper_bw_mhz));
+		oper_bw_mhz >>= 1;
+	}
+
+	/* Configure STA for short and long guard */
+	mm81x_rc_sta_config_guard_per_bw(sta, &caps);
+
+	/* Set max rates */
+	if (mors->hw->max_rates > 0 &&
+	    mors->hw->max_rates < IEEE80211_TX_MAX_RATES)
+		caps.max_rates = mors->hw->max_rates;
+	else
+		caps.max_rates = IEEE80211_TX_MAX_RATES;
+
+	/* Set max reties */
+	if (mors->hw->max_rate_tries >= MMRC_MIN_CHAIN_ATTEMPTS &&
+	    mors->hw->max_rate_tries < MMRC_MAX_CHAIN_ATTEMPTS)
+		caps.max_retries = mors->hw->max_rate_tries;
+	else
+		caps.max_retries = MMRC_MAX_CHAIN_ATTEMPTS;
+
+	WARN_ON(msta->rc.tb);
+	table_mem_size = mmrc_memory_required_for_caps(&caps);
+	tb = kzalloc(table_mem_size, GFP_KERNEL);
+	if (!tb)
+		return -ENOMEM;
+
+	/* Initialise the STA rate control table */
+	mmrc_sta_init(tb, &caps, msta->avg_rssi);
+
+	spin_lock_bh(&mors->mrc.lock);
+	kfree(msta->rc.tb);
+	msta->rc.tb = tb;
+	list_add(&msta->rc.list, &mors->mrc.stas);
+	msta->rc.last_update = jiffies;
+	spin_unlock_bh(&mors->mrc.lock);
+
+	return 0;
+}
+
+void mm81x_rc_sta_remove(struct mm81x *mors, struct ieee80211_sta *sta)
+{
+	struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv;
+
+	spin_lock_bh(&mors->mrc.lock);
+	if (msta->rc.tb) {
+		list_del_init(&msta->rc.list);
+		kfree(msta->rc.tb);
+		msta->rc.tb = NULL;
+	}
+	spin_unlock_bh(&mors->mrc.lock);
+}
+
+static void mm81x_rc_sta_fill_basic_rates(struct mm81x_skb_tx_info *tx_info,
+					  struct ieee80211_tx_info *info,
+					  int tx_bw)
+{
+	int i;
+	enum dot11_bandwidth bw_idx = mm81x_ratecode_bw_mhz_to_bw_index(tx_bw);
+	enum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT;
+
+	mm81x_ratecode_mcs_index_set(&tx_info->rates[0].mm81x_ratecode, 0);
+	mm81x_ratecode_nss_index_set(&tx_info->rates[0].mm81x_ratecode,
+				     NSS_TO_NSS_IDX(1));
+	mm81x_ratecode_bw_index_set(&tx_info->rates[0].mm81x_ratecode, bw_idx);
+	if (bw_idx == DOT11_BANDWIDTH_1MHZ)
+		pream = MM81X_RATE_PREAMBLE_S1G_1M;
+	mm81x_ratecode_preamble_set(&tx_info->rates[0].mm81x_ratecode, pream);
+	tx_info->rates[0].count = 4;
+
+	for (i = 1; i < IEEE80211_TX_MAX_RATES; i++)
+		tx_info->rates[i].count = 0;
+
+	info->control.rates[0].idx = 0;
+	info->control.rates[0].count = tx_info->rates[0].count;
+	info->control.rates[0].flags = 0;
+	info->control.rates[1].idx = -1;
+}
+
+static int mm81x_rc_sta_get_rates(struct mm81x *mors, struct mm81x_sta *msta,
+				  struct mmrc_rate_table *rates, size_t size)
+{
+	int ret = -ENOENT;
+	struct list_head *pos;
+
+	spin_lock_bh(&mors->mrc.lock);
+	list_for_each(pos, &mors->mrc.stas) {
+		struct mm81x_rc_sta *mrc_sta =
+			list_entry(pos, struct mm81x_rc_sta, list);
+
+		if (&msta->rc == mrc_sta) {
+			ret = 0;
+			mmrc_get_rates(msta->rc.tb, rates, size);
+			break;
+		}
+	}
+	spin_unlock_bh(&mors->mrc.lock);
+
+	return ret;
+}
+
+static bool mm81x_rc_use_basic_rates(struct ieee80211_sta *sta,
+				     struct sk_buff *skb,
+				     struct ieee80211_hdr *hdr)
+{
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+
+	if (!sta)
+		return true;
+
+	if (ieee80211_is_qos_nullfunc(hdr->frame_control) ||
+	    ieee80211_is_nullfunc(hdr->frame_control))
+		return true;
+
+	if (!ieee80211_is_data_qos(hdr->frame_control))
+		return true;
+
+	/* Use basic rates for EAPOL exchanges or when instructed */
+	if (unlikely((skb->protocol == cpu_to_be16(ETH_P_PAE) ||
+		      info->flags & IEEE80211_TX_CTL_USE_MINRATE)))
+		return true;
+
+	return false;
+}
+
+void mm81x_rc_sta_fill_tx_rates(struct mm81x *mors,
+				struct mm81x_skb_tx_info *tx_info,
+				struct sk_buff *skb, struct ieee80211_sta *sta,
+				int tx_bw, bool rts_allowed)
+{
+	int ret, i;
+	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+	struct mm81x_sta *msta;
+	struct mmrc_rate_table rates;
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+
+	BUILD_BUG_ON((MMRC_BW_1MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_1MHZ ||
+		      MMRC_BW_2MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_2MHZ ||
+		      MMRC_BW_4MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_4MHZ ||
+		      MMRC_BW_16MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_16MHZ));
+
+	memset(&info->control.rates, 0, sizeof(info->control.rates));
+	memset(&info->status.rates, 0, sizeof(info->status.rates));
+	mm81x_rc_sta_fill_basic_rates(tx_info, info, tx_bw);
+
+	/* Use basic rates for non data packets */
+	if (mm81x_rc_use_basic_rates(sta, skb, hdr))
+		return;
+
+	msta = (struct mm81x_sta *)sta->drv_priv;
+	if (!msta)
+		return;
+
+	ret = mm81x_rc_sta_get_rates(mors, msta, &rates, skb->len);
+	if (ret != 0)
+		return;
+
+	for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) {
+		info->control.rates[i].flags = 0;
+		if (rates.rates[i].rate != MMRC_MCS_UNUSED) {
+			u8 mcs = rates.rates[i].rate;
+			u8 nss_index = rates.rates[i].ss;
+			enum dot11_bandwidth bw_idx =
+				(enum dot11_bandwidth)rates.rates[i].bw;
+			enum mm81x_rate_preamble pream =
+				MM81X_RATE_PREAMBLE_S1G_SHORT;
+
+			mm81x_ratecode_bw_index_set(
+				&tx_info->rates[i].mm81x_ratecode, bw_idx);
+			mm81x_ratecode_mcs_index_set(
+				&tx_info->rates[i].mm81x_ratecode, mcs);
+			mm81x_ratecode_nss_index_set(
+				&tx_info->rates[i].mm81x_ratecode, nss_index);
+			if (bw_idx == DOT11_BANDWIDTH_1MHZ)
+				pream = MM81X_RATE_PREAMBLE_S1G_1M;
+			mm81x_ratecode_preamble_set(
+				&tx_info->rates[i].mm81x_ratecode, pream);
+			tx_info->rates[i].count = rates.rates[i].attempts;
+
+			if (rts_allowed &&
+			    (rates.rates[i].flags & BIT(MMRC_FLAGS_CTS_RTS))) {
+				mm81x_ratecode_enable_rts(
+					&tx_info->rates[i].mm81x_ratecode);
+				info->control.rates[i].flags |=
+					IEEE80211_TX_RC_USE_RTS_CTS;
+			}
+
+			if (rates.rates[i].guard == MMRC_GUARD_SHORT) {
+				mm81x_ratecode_enable_sgi(
+					&tx_info->rates[i].mm81x_ratecode);
+				info->control.rates[i].flags |=
+					IEEE80211_TX_RC_SHORT_GI;
+			}
+
+			/* Update skb tx_info */
+			info->control.rates[i].idx = rates.rates[i].rate;
+			info->control.rates[i].count = rates.rates[i].attempts;
+		} else {
+			info->control.rates[i].idx = -1;
+			info->control.rates[i].count = 0;
+			tx_info->rates[i].count = 0;
+		}
+	}
+}
+
+static void mm81x_rc_sta_set_rates(struct mm81x *mors, struct mm81x_sta *msta,
+				   struct mmrc_rate_table *rates, int attempts,
+				   bool was_aggregated)
+{
+	struct list_head *pos;
+
+	spin_lock_bh(&mors->mrc.lock);
+	list_for_each(pos, &mors->mrc.stas) {
+		struct mm81x_rc_sta *mrc_sta =
+			list_entry(pos, struct mm81x_rc_sta, list);
+
+		if (&msta->rc == mrc_sta) {
+			mmrc_feedback(msta->rc.tb, rates, attempts,
+				      was_aggregated);
+			break;
+		}
+	}
+	spin_unlock_bh(&mors->mrc.lock);
+}
+
+void mm81x_rc_sta_feedback_rates(struct mm81x *mors, struct sk_buff *skb,
+				 struct ieee80211_sta *sta,
+				 struct mm81x_skb_tx_status *tx_sts,
+				 int attempts)
+{
+	int i;
+	u32 tx_airtime = 0;
+	struct mmrc_rate_table rates;
+	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+	struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);
+	struct ieee80211_tx_rate *r = &txi->status.rates[0];
+	int count = min_t(int, MM81X_SKB_MAX_RATES, IEEE80211_TX_MAX_RATES);
+	struct mm81x_sta *msta = msta = (struct mm81x_sta *)sta->drv_priv;
+
+	/* Don't update rate info if basic rates were used */
+	if (mm81x_rc_use_basic_rates(sta, skb, hdr))
+		goto exit;
+
+	if (attempts <= 0)
+		/* Did we really send the packet? */
+		goto exit;
+
+	for (i = 0; i < count; i++) {
+		rates.rates[i].rate = mm81x_ratecode_mcs_index_get(
+			tx_sts->rates[i].mm81x_ratecode);
+		rates.rates[i].ss = mm81x_ratecode_nss_index_get(
+			tx_sts->rates[i].mm81x_ratecode);
+		rates.rates[i].guard =
+			mm81x_ratecode_sgi_get(tx_sts->rates[i].mm81x_ratecode);
+		rates.rates[i].bw = mm81x_ratecode_bw_index_get(
+			tx_sts->rates[i].mm81x_ratecode);
+		rates.rates[i].flags =
+			mm81x_ratecode_rts_get(tx_sts->rates[i].mm81x_ratecode);
+		rates.rates[i].attempts = tx_sts->rates[i].count;
+
+		tx_airtime +=
+			mmrc_calculate_rate_tx_time(&rates.rates[i], skb->len);
+	}
+
+	if (msta) {
+		/*
+		 * Save the rate information. This will be used to update
+		 * station's tx rate stats
+		 */
+		msta->last_sta_tx_rate.bw = rates.rates[0].bw;
+		msta->last_sta_tx_rate.rate = rates.rates[0].rate;
+		msta->last_sta_tx_rate.ss = rates.rates[0].ss;
+		msta->last_sta_tx_rate.guard = rates.rates[0].guard;
+	}
+
+	mm81x_rc_sta_set_rates(mors, msta, &rates, attempts,
+			       !!(le32_to_cpu(tx_sts->flags) &
+				  MM81X_TX_STATUS_WAS_AGGREGATED));
+
+	ieee80211_sta_register_airtime(sta, tx_sts->tid, tx_airtime, 0);
+
+exit:
+	ieee80211_tx_info_clear_status(txi);
+
+	if (!(le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_FLAGS_NO_ACK) &&
+	    !(txi->flags & IEEE80211_TX_CTL_NO_ACK))
+		txi->flags |= IEEE80211_TX_STAT_ACK;
+
+	if (le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_FLAGS_PS_FILTERED) {
+		txi->flags |= IEEE80211_TX_STAT_TX_FILTERED;
+
+		/*
+		 * Clear TX CTL AMPDU flag so that this frame gets rescheduled
+		 * in ieee80211_handle_filtered_frame(). This flag will get set
+		 * again by mac80211's tx path on rescheduling.
+		 */
+		txi->flags &= ~IEEE80211_TX_CTL_AMPDU;
+		if (msta) {
+			if (!msta->tx_ps_filter_en)
+				dev_dbg(mors->dev, "TX ps filter set sta[%pM]",
+					msta->addr);
+			msta->tx_ps_filter_en = true;
+		}
+	}
+
+	for (i = 0; i < count; i++) {
+		if (tx_sts->rates[i].count > 0) {
+			r[i].count = tx_sts->rates[i].count;
+			r[i].flags |= IEEE80211_TX_RC_MCS;
+		} else {
+			r[i].idx = -1;
+		}
+	}
+
+	/* single packet per A-MPDU (for now) */
+	if (txi->flags & IEEE80211_TX_CTL_AMPDU) {
+		txi->flags |= IEEE80211_TX_STAT_AMPDU;
+		txi->status.ampdu_len = 1;
+		txi->status.ampdu_ack_len =
+			txi->flags & IEEE80211_TX_STAT_ACK ? 1 : 0;
+	}
+
+	/*
+	 * Inform mac80211 that the SP (elicited by a PS-Poll or u-APSD) is
+	 * over
+	 */
+	if (sta && (txi->flags & IEEE80211_TX_STATUS_EOSP)) {
+		txi->flags &= ~IEEE80211_TX_STATUS_EOSP;
+		ieee80211_sta_eosp(sta);
+	}
+}
+
+void mm81x_rc_sta_state_check(struct mm81x *mors, struct ieee80211_vif *vif,
+			      struct ieee80211_sta *sta,
+			      enum ieee80211_sta_state old_state,
+			      enum ieee80211_sta_state new_state)
+{
+	struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv;
+
+	/* Add to Morse RC STA list */
+	if (old_state < new_state && new_state == IEEE80211_STA_ASSOC) {
+		/* Newly associated, add to RC */
+		mm81x_rc_sta_add(mors, vif, sta);
+	} else if (old_state > new_state && (old_state == IEEE80211_STA_ASSOC ||
+					     old_state == IEEE80211_STA_AUTH)) {
+		/* Lost or failed association; remove from list */
+		mm81x_rc_sta_remove(mors, sta);
+	} else if (old_state < new_state && old_state == IEEE80211_STA_NONE &&
+		   msta->rc.list.prev) {
+		/*
+		 * Special case for driver warning issue causing a sta to be
+		 * left on the list
+		 */
+		dev_dbg(mors->dev, "Remove stale sta from rc list");
+		mm81x_rc_sta_remove(mors, sta);
+	}
+}
diff --git a/drivers/net/wireless/morsemicro/mm81x/rc.h b/drivers/net/wireless/morsemicro/mm81x/rc.h
new file mode 100644
index 00000000000000..53f12902440891
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/rc.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_RC_H_
+#define _MM81X_RC_H_
+
+#include <linux/list.h>
+#include <linux/workqueue.h>
+#include "core.h"
+#include "mmrc.h"
+
+struct mm81x_vif;
+
+#define INIT_MAX_RATES_NUM 4
+
+struct mm81x_rc {
+	/* Serialise rate control queue manipulation and timer functions */
+	spinlock_t lock;
+	struct list_head stas;
+	struct timer_list timer;
+	struct work_struct work;
+	struct mm81x *mors;
+};
+
+struct mm81x_rc_sta {
+	struct mmrc_table *tb;
+	struct list_head list;
+	unsigned long last_update;
+};
+
+void mm81x_rc_init(struct mm81x *mors);
+void mm81x_rc_deinit(struct mm81x *mors);
+int mm81x_rc_sta_add(struct mm81x *mors, struct ieee80211_vif *vif,
+		     struct ieee80211_sta *sta);
+void mm81x_rc_sta_remove(struct mm81x *mors, struct ieee80211_sta *sta);
+void mm81x_rc_sta_fill_tx_rates(struct mm81x *mors,
+				struct mm81x_skb_tx_info *tx_info,
+				struct sk_buff *skb, struct ieee80211_sta *sta,
+				int tx_bw, bool rts_allowed);
+void mm81x_rc_sta_feedback_rates(struct mm81x *mors, struct sk_buff *skb,
+				 struct ieee80211_sta *sta,
+				 struct mm81x_skb_tx_status *tx_sts,
+				 int tx_attempts);
+void mm81x_rc_sta_state_check(struct mm81x *mors, struct ieee80211_vif *vif,
+			      struct ieee80211_sta *sta,
+			      enum ieee80211_sta_state old_state,
+			      enum ieee80211_sta_state new_state);
+
+#endif /* !_MM81X_RC_H_ */
diff --git a/drivers/net/wireless/morsemicro/mm81x/sdio.c b/drivers/net/wireless/morsemicro/mm81x/sdio.c
new file mode 100644
index 00000000000000..96fce187dd3522
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/sdio.c
@@ -0,0 +1,613 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sd.h>
+#include "hw.h"
+#include "core.h"
+#include "bus.h"
+#include "mac.h"
+#include "fw.h"
+#include "hif.h"
+
+/*
+ * Value to indicate that the base address for bulk/register
+ * read/writes has yet to be set
+ */
+#define MM81X_SDIO_BASE_ADDR_UNSET 0xFFFFFFFF
+
+#define MM81X_SDIO_ALIGNMENT (8)
+
+#define MM81X_SDIO_REG_ADDRESS_BASE 0x10000
+#define MM81X_SDIO_REG_ADDRESS_WINDOW_0 MM81X_SDIO_REG_ADDRESS_BASE
+#define MM81X_SDIO_REG_ADDRESS_WINDOW_1 (MM81X_SDIO_REG_ADDRESS_BASE + 1)
+#define MM81X_SDIO_REG_ADDRESS_CONFIG (MM81X_SDIO_REG_ADDRESS_BASE + 2)
+
+struct mm81x_sdio {
+	bool enabled;
+	u32 bulk_addr_base;
+	u32 register_addr_base;
+	struct sdio_func *func;
+	const struct sdio_device_id *id;
+};
+
+static void irq_handler(struct sdio_func *func1)
+{
+	struct sdio_func *func = func1->card->sdio_func[1];
+	struct mm81x *mors = sdio_get_drvdata(func);
+
+	mm81x_hw_irq_handle(mors);
+}
+
+static int mm81x_sdio_enable_irq(struct mm81x_sdio *sdio)
+{
+	int ret;
+	struct sdio_func *func = sdio->func;
+	struct sdio_func *func1 = func->card->sdio_func[0];
+	struct mm81x *mors = sdio_get_drvdata(func);
+
+	sdio_claim_host(func);
+	ret = sdio_claim_irq(func1, irq_handler);
+	if (ret)
+		dev_err(mors->dev, "Failed to enable sdio irq: %d\n", ret);
+
+	sdio_release_host(func);
+	return ret;
+}
+
+static void mm81x_sdio_disable_irq(struct mm81x_sdio *sdio)
+{
+	struct sdio_func *func = sdio->func;
+	struct sdio_func *func1 = func->card->sdio_func[0];
+
+	sdio_claim_host(func);
+	sdio_release_irq(func1);
+	sdio_release_host(func);
+}
+
+static void mm81x_sdio_set_irq(struct mm81x *mors, bool enable)
+{
+	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
+
+	if (enable)
+		mm81x_sdio_enable_irq(sdio);
+	else
+		mm81x_sdio_disable_irq(sdio);
+}
+
+static u32 mm81x_sdio_calculate_base_address(u32 address, u8 access)
+{
+	return (address & MM81X_SDIO_RW_ADDR_BOUNDARY_MASK) | (access & 0x3);
+}
+
+static void mm81x_sdio_reset_base_address(struct mm81x_sdio *sdio)
+{
+	sdio->bulk_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;
+	sdio->register_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;
+}
+
+static int mm81x_sdio_set_func_address_base(struct mm81x_sdio *sdio,
+					    struct sdio_func *func, u32 address,
+					    u8 access)
+{
+	int ret = 0;
+	int retries = 0;
+	static const int max_retries = 3;
+	struct sdio_func *func2 = sdio->func;
+	struct mm81x *mors = sdio_get_drvdata(sdio->func);
+	s32 calculated_addr_base =
+		mm81x_sdio_calculate_base_address(address, access);
+	u32 *current_addr_base = func == func2 ? &sdio->bulk_addr_base :
+						 &sdio->register_addr_base;
+
+	if ((*current_addr_base) == calculated_addr_base &&
+	    *current_addr_base != MM81X_SDIO_BASE_ADDR_UNSET)
+		return ret;
+
+retry:
+	sdio_writeb(func, (u8)u32_get_bits(address, GENMASK(23, 16)),
+		    MM81X_SDIO_REG_ADDRESS_WINDOW_0, &ret);
+	if (ret)
+		goto err;
+
+	sdio_writeb(func, (u8)u32_get_bits(address, GENMASK(31, 24)),
+		    MM81X_SDIO_REG_ADDRESS_WINDOW_1, &ret);
+	if (ret)
+		goto err;
+
+	sdio_writeb(func, access & 0x3, MM81X_SDIO_REG_ADDRESS_CONFIG, &ret);
+	if (ret)
+		goto err;
+
+	*current_addr_base = calculated_addr_base;
+	if (retries)
+		dev_dbg(mors->dev, "%s succeeded after %d retries\n", __func__,
+			retries);
+
+	return ret;
+err:
+	retries++;
+	if (ret == -ETIMEDOUT && retries <= max_retries) {
+		dev_dbg(mors->dev, "%s failed (%d), retrying (%d/%d)\n",
+			__func__, ret, retries, max_retries);
+		goto retry;
+	}
+
+	*current_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;
+	return ret;
+}
+
+static int mm81x_sdio_mem_write_block(struct mm81x_sdio *sdio, u32 address,
+				      u8 *data, ssize_t size)
+{
+	int ret;
+	struct sdio_func *func2 = sdio->func;
+	struct mm81x *mors = sdio_get_drvdata(sdio->func);
+
+	mm81x_sdio_set_func_address_base(sdio, func2, address,
+					 MM81X_CONFIG_ACCESS_4BYTE);
+	if (unlikely(!IS_ALIGNED((uintptr_t)data,
+				 mors->bus_ops->bulk_alignment))) {
+		ret = -EBADE;
+		goto exit;
+	}
+
+	address &= 0x0000FFFF; /* remove base and keep offset */
+	ret = sdio_memcpy_toio(func2, address, data, size);
+	if (ret)
+		goto exit;
+
+	ret = size;
+exit:
+	return ret;
+}
+
+static int mm81x_sdio_mem_write_byte(struct mm81x_sdio *sdio, u32 address,
+				     u8 *data, ssize_t size)
+{
+	int i, ret;
+	struct sdio_func *func1 = sdio->func->card->sdio_func[0];
+
+	mm81x_sdio_set_func_address_base(sdio, func1, address,
+					 MM81X_CONFIG_ACCESS_1BYTE);
+
+	address &= 0x0000FFFF; /* remove base and keep offset */
+	for (i = 0; i < size; i++) {
+		sdio_writeb(func1, data[i], address + i, (int *)&ret);
+		if (ret)
+			goto exit;
+	}
+
+	ret = size;
+exit:
+	return ret;
+}
+
+static void mm81x_sdio_claim_host(struct mm81x *mors)
+{
+	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
+	struct sdio_func *func = sdio->func;
+
+	sdio_claim_host(func);
+}
+
+static void mm81x_sdio_release_host(struct mm81x *mors)
+{
+	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
+	struct sdio_func *func = sdio->func;
+
+	sdio_release_host(func);
+}
+
+static int mm81x_sdio_mem_read_block(struct mm81x_sdio *sdio, u32 address,
+				     u8 *data, ssize_t size)
+{
+	int ret;
+	struct sdio_func *func2 = sdio->func;
+	struct mm81x *mors = sdio_get_drvdata(sdio->func);
+
+	mm81x_sdio_set_func_address_base(sdio, func2, address,
+					 MM81X_CONFIG_ACCESS_4BYTE);
+	if (unlikely(!IS_ALIGNED((uintptr_t)data,
+				 mors->bus_ops->bulk_alignment))) {
+		ret = -EBADE;
+		goto exit;
+	}
+
+	address &= 0x0000FFFF; /* remove base and keep offset */
+	ret = sdio_memcpy_fromio(func2, data, address, size);
+	if (ret)
+		goto exit;
+
+	/*
+	 * Observed sometimes that SDIO read repeats the first 4-bytes
+	 * word twice, overwriting second word (hence, tail will be
+	 * overwritten with 'sync' byte). When this happens, reading
+	 * will fetch the correct word. NB: if repeated again, pass it
+	 * anyway and upper layers will handle it
+	 */
+
+	if (size >= 8 && memcmp(data, data + 4, 4) == 0)
+		sdio_memcpy_fromio(func2, data, address, 8);
+
+	ret = size;
+exit:
+	return ret;
+}
+
+static int mm81x_sdio_mem_read_byte(struct mm81x_sdio *sdio, u32 address,
+				    u8 *data, ssize_t size)
+{
+	int i, ret;
+	struct sdio_func *func1 = sdio->func->card->sdio_func[0];
+
+	mm81x_sdio_set_func_address_base(sdio, func1, address,
+					 MM81X_CONFIG_ACCESS_1BYTE);
+
+	address &= 0x0000FFFF; /* remove base and keep offset */
+	for (i = 0; i < size; i++) {
+		data[i] = sdio_readb(func1, address + i, (int *)&ret);
+		if (ret)
+			goto exit;
+	}
+
+	ret = size;
+exit:
+	return ret;
+}
+
+static int mm81x_sdio_dm_write(struct mm81x *mors, u32 address, const u8 *data,
+			       int len)
+{
+	int ret = 0;
+	int block_len, byte_len;
+	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
+	int remaining = len;
+	int offset = 0;
+
+	if (remaining > 0 && address & 0x3) {
+		len = 4 - (address & 0x3);
+		ret = mm81x_sdio_mem_write_byte(sdio, address, (u8 *)data, len);
+		if (ret != len)
+			return -EIO;
+
+		offset += len;
+		remaining -= len;
+	}
+
+	while ((remaining) > 0) {
+		/*
+		 * We can only write up to the end of a single window in
+		 * each write operation.
+		 */
+		u32 window_end = (address + offset) |
+				 ~MM81X_SDIO_RW_ADDR_BOUNDARY_MASK;
+
+		len = min(remaining, (int)(window_end + 1 - address - offset));
+		block_len = len & ~0x3;
+		byte_len = len & 0x3;
+
+		if (block_len) {
+			ret = mm81x_sdio_mem_write_block(sdio, address + offset,
+							 (u8 *)(data + offset),
+							 block_len);
+			if (ret != block_len)
+				return -EIO;
+
+			offset += block_len;
+		}
+
+		if (byte_len) {
+			ret = mm81x_sdio_mem_write_byte(sdio, address + offset,
+							(u8 *)(data + offset),
+							byte_len);
+			if (ret != byte_len)
+				return -EIO;
+
+			offset += byte_len;
+		}
+
+		remaining -= len;
+	}
+
+	return 0;
+}
+
+static int mm81x_sdio_dm_read(struct mm81x *mors, u32 address, u8 *data,
+			      int len)
+{
+	int ret = 0;
+	int block_len, byte_len;
+	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
+	int remaining = len;
+	int offset = 0;
+
+	if (remaining > 0 && address & 0x3) {
+		len = 4 - (address & 0x3);
+		ret = mm81x_sdio_mem_read_byte(sdio, address, data, len);
+		if (ret != len)
+			return -EIO;
+
+		offset += len;
+		remaining -= len;
+	}
+
+	while (remaining > 0) {
+		/*
+		 * We can only read up to the end of a single window in
+		 * each read operation.
+		 */
+		u32 window_end = (address + offset) |
+				 ~MM81X_SDIO_RW_ADDR_BOUNDARY_MASK;
+
+		len = min(remaining, (int)(window_end + 1 - address - offset));
+		block_len = len & ~0x3;
+		byte_len = len & 0x3;
+
+		if (block_len) {
+			ret = mm81x_sdio_mem_read_block(sdio, address + offset,
+							data + offset,
+							block_len);
+			if (ret != block_len)
+				return -EIO;
+
+			offset += block_len;
+		}
+
+		if (byte_len) {
+			ret = mm81x_sdio_mem_read_byte(sdio, address + offset,
+						       data + offset, byte_len);
+			if (ret != byte_len)
+				return -EIO;
+
+			offset += byte_len;
+		}
+
+		remaining -= len;
+	}
+
+	return 0;
+}
+
+static int mm81x_sdio_reg32_write(struct mm81x *mors, u32 address, u32 val)
+{
+	ssize_t ret = 0;
+	u32 original_address = address;
+	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
+	struct sdio_func *func1 = sdio->func->card->sdio_func[0];
+
+	mm81x_sdio_set_func_address_base(sdio, func1, address,
+					 MM81X_CONFIG_ACCESS_4BYTE);
+
+	address &= 0x0000FFFF;
+	sdio_writel(func1, (__force u32)cpu_to_le32(val),
+		    (__force u32)cpu_to_le32(address), (int *)&ret);
+	if (ret)
+		goto error;
+
+	return 0;
+
+error:
+	if (original_address == MM81X_REG_RESET(mors) &&
+	    val == MM81X_REG_RESET_VALUE(mors)) {
+		dev_dbg(mors->dev,
+			"SDIO reset detected, invalidating base addr\n");
+		mm81x_sdio_reset_base_address(sdio);
+	}
+
+	return -EIO;
+}
+
+static int mm81x_sdio_reg32_read(struct mm81x *mors, u32 address, u32 *val)
+{
+	u32 value;
+	ssize_t ret = 0;
+	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
+	struct sdio_func *func1 = sdio->func->card->sdio_func[0];
+
+	mm81x_sdio_set_func_address_base(sdio, func1, address,
+					 MM81X_CONFIG_ACCESS_4BYTE);
+
+	address &= 0x0000FFFF;
+	value = sdio_readl(func1, (__force u32)cpu_to_le32(address),
+			   (int *)&ret);
+	if (ret)
+		return ret;
+
+	*val = le32_to_cpup((__le32 *)&value);
+	return 0;
+}
+
+static void mm81x_sdio_bus_enable(struct mm81x *mors, bool enable)
+{
+	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
+	struct sdio_func *func = sdio->func;
+	struct mmc_host *host = func->card->host;
+
+	sdio_claim_host(func);
+
+	if (enable) {
+		/*
+		 * No need to do anything special to re-enable the sdio bus.
+		 * This will happen automatically when a read/write is
+		 * attempted and sdio->bulk_addr_base == 0.
+		 */
+		sdio->enabled = true;
+		host->ops->enable_sdio_irq(host, 1);
+		dev_dbg(mors->dev, "%s: enabling bus\n", __func__);
+	} else {
+		host->ops->enable_sdio_irq(host, 0);
+		mm81x_sdio_reset_base_address(sdio);
+		sdio->enabled = false;
+		dev_dbg(mors->dev, "%s: disabling bus\n", __func__);
+	}
+
+	sdio_release_host(func);
+}
+
+static void mm81x_sdio_reset(struct sdio_func *func)
+{
+	sdio_claim_host(func);
+	sdio_disable_func(func);
+	sdio_release_host(func);
+
+	mdelay(20);
+
+	sdio_claim_host(func);
+	sdio_disable_func(func);
+	mmc_hw_reset(func->card);
+	sdio_enable_func(func);
+	sdio_release_host(func);
+}
+
+static void mm81x_sdio_config_burst_mode(struct mm81x *mors, bool enable_burst)
+{
+	u8 burst_mode = (enable_burst) ? SDIO_WORD_BURST_SIZE_16 :
+					 SDIO_WORD_BURST_DISABLE;
+
+	mm81x_hw_enable_burst_mode(mors, burst_mode);
+}
+
+static const struct mm81x_bus_ops mm81x_sdio_ops = {
+	.dm_read = mm81x_sdio_dm_read,
+	.dm_write = mm81x_sdio_dm_write,
+	.reg32_read = mm81x_sdio_reg32_read,
+	.reg32_write = mm81x_sdio_reg32_write,
+	.set_bus_enable = mm81x_sdio_bus_enable,
+	.claim = mm81x_sdio_claim_host,
+	.release = mm81x_sdio_release_host,
+	.config_burst_mode = mm81x_sdio_config_burst_mode,
+	.set_irq = mm81x_sdio_set_irq,
+	.bulk_alignment = MM81X_SDIO_ALIGNMENT
+};
+
+static int mm81x_sdio_enable(struct mm81x_sdio *sdio)
+{
+	int ret;
+	struct sdio_func *func = sdio->func;
+	struct mm81x *mors = sdio_get_drvdata(func);
+
+	sdio_claim_host(func);
+	ret = sdio_enable_func(func);
+	if (ret)
+		dev_err(mors->dev, "sdio_enable_func failed: %d\n", ret);
+	sdio_release_host(func);
+	return ret;
+}
+
+static void mm81x_sdio_release(struct mm81x_sdio *sdio)
+{
+	struct sdio_func *func = sdio->func;
+
+	sdio_claim_host(func);
+	sdio_disable_func(func);
+	sdio_release_host(func);
+}
+
+static int mm81x_sdio_probe(struct sdio_func *func,
+			    const struct sdio_device_id *id)
+{
+	int ret = 0;
+	struct mm81x *mors = NULL;
+	struct mm81x_sdio *sdio;
+	struct device *dev = &func->dev;
+
+	if (func->num == 1)
+		return 0;
+
+	if (func->num != 2)
+		return -ENODEV;
+
+	mors = mm81x_core_alloc(sizeof(*sdio), dev);
+	if (!mors)
+		return -ENOMEM;
+
+	mors->bus_ops = &mm81x_sdio_ops;
+	mors->bus_type = MM81X_BUS_TYPE_SDIO;
+
+	sdio = (struct mm81x_sdio *)mors->drv_priv;
+	sdio->func = func;
+	sdio->id = id;
+	sdio->enabled = true;
+	mm81x_sdio_reset_base_address(sdio);
+
+	sdio_set_drvdata(func, mors);
+
+	ret = mm81x_sdio_enable(sdio);
+	if (ret)
+		goto err_core_free;
+
+	mm81x_sdio_config_burst_mode(mors, true);
+
+	ret = mm81x_core_init(mors);
+	if (ret)
+		goto err_sdio_release;
+
+	ret = mm81x_sdio_enable_irq(sdio);
+	if (ret)
+		goto err_core_deinit;
+
+	ret = mm81x_core_register(mors);
+	if (ret)
+		goto err_disable_irq;
+
+	return 0;
+
+err_disable_irq:
+	mm81x_sdio_disable_irq(sdio);
+err_core_deinit:
+	mm81x_core_deinit(mors);
+err_sdio_release:
+	mm81x_sdio_release(sdio);
+err_core_free:
+	mm81x_core_free(mors);
+	return ret;
+}
+
+static void mm81x_sdio_remove(struct sdio_func *func)
+{
+	struct mm81x *mors = sdio_get_drvdata(func);
+	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
+
+	if (!mors)
+		return;
+
+	mm81x_core_unregister(mors);
+	mm81x_sdio_disable_irq(sdio);
+	mm81x_core_deinit(mors);
+	mm81x_sdio_release(sdio);
+	mm81x_sdio_reset(func);
+	mm81x_core_free(mors);
+	sdio_set_drvdata(func, NULL);
+}
+
+static const struct sdio_device_id mm81x_sdio_devices[] = {
+	{ SDIO_DEVICE(SDIO_VENDOR_ID_MORSEMICRO,
+		      SDIO_DEVICE_ID_MORSEMICRO_MM8108) },
+	{},
+};
+
+MODULE_DEVICE_TABLE(sdio, mm81x_sdio_devices);
+
+static struct sdio_driver mm81x_sdio_driver = {
+	.name = "mm81x_sdio",
+	.id_table = mm81x_sdio_devices,
+	.probe = mm81x_sdio_probe,
+	.remove = mm81x_sdio_remove,
+};
+
+module_sdio_driver(mm81x_sdio_driver);
+
+MODULE_AUTHOR("Morse Micro");
+MODULE_DESCRIPTION("Driver support for Morse Micro MM81X SDIO devices");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/wireless/morsemicro/mm81x/skbq.c b/drivers/net/wireless/morsemicro/mm81x/skbq.c
new file mode 100644
index 00000000000000..9bc7899c05a861
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/skbq.c
@@ -0,0 +1,1065 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/ktime.h>
+#include <linux/skbuff.h>
+#include <linux/jiffies.h>
+#include "hif.h"
+#include "skbq.h"
+#include "mac.h"
+#include "command.h"
+#include "bus.h"
+
+/* Returns number of bytes needed to word align */
+#define BYTES_NEEDED_TO_WORD_ALIGN(bytes) \
+	((bytes) & 0x3 ? (4 - ((bytes) & 0x3)) : 0)
+
+/* Rounds down to the nearest word boundary */
+#define ROUND_DOWN_TO_WORD(bytes)                                  \
+	(BYTES_NEEDED_TO_WORD_ALIGN(bytes) ?                       \
+		 bytes - (4 - BYTES_NEEDED_TO_WORD_ALIGN(bytes)) : \
+		 bytes)
+
+#define MM81X_SKBQ_MAX_TXQ_LEN 32
+#define MM81X_SKBQ_TX_QUEUED_LIFETIME_MS 1000
+#define MM81X_SKBQ_TX_STATUS_LIFETIME_MS (15 * 1000)
+
+/* Returns padding needed to align x up to a 4-byte boundary */
+#define MM81X_PAD4(x) (((x) & 0x3) ? (4 - ((x) & 0x3)) : 0)
+
+struct mm81x_tx_status_priv {
+	/*
+	 * Time (jiffies) at which this packet has spent too long the pending
+	 * queue, waiting for status notification from the firmware, and
+	 * should be considered lost.
+	 */
+	unsigned long tx_status_expiry;
+};
+
+static struct mm81x_tx_status_priv *
+__mm81x_skbq_tx_status_priv(struct sk_buff *skb)
+{
+	struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
+
+	BUILD_BUG_ON(sizeof(struct mm81x_tx_status_priv) >
+		     sizeof(tx_info->status.status_driver_data));
+	return (struct mm81x_tx_status_priv *)&tx_info->status
+		.status_driver_data[0];
+}
+
+static bool __mm81x_skbq_has_pending_tx_skb_timed_out(struct sk_buff *skb)
+{
+	struct mm81x_tx_status_priv *info = __mm81x_skbq_tx_status_priv(skb);
+
+	/* If our timestamp value is in the past then we have timed out. */
+	return time_is_before_jiffies(info->tx_status_expiry);
+}
+
+static u32 __mm81x_skbq_size(const struct mm81x_skbq *mq)
+{
+	return mq->skbq_size;
+}
+
+static u32 __mm81x_skbq_space(const struct mm81x_skbq *mq)
+{
+	return MM81X_SKBQ_SIZE - __mm81x_skbq_size(mq);
+}
+
+static bool __mm81x_skbq_over_threshold(struct mm81x_skbq *mq)
+{
+	return skb_queue_len(&mq->skbq) >= MM81X_SKBQ_MAX_TXQ_LEN;
+}
+
+static bool __mm81x_skbq_under_threshold(struct mm81x_skbq *mq)
+{
+	return skb_queue_len(&mq->skbq) < (MM81X_SKBQ_MAX_TXQ_LEN - 2);
+}
+
+static void __mm81x_skbq_unlink(struct mm81x_skbq *mq,
+				struct sk_buff_head *queue, struct sk_buff *skb)
+{
+	if (queue == &mq->skbq) {
+		WARN_ON(skb->len > mq->skbq_size);
+		mq->skbq_size -= min(skb->len, mq->skbq_size);
+	}
+
+	__skb_unlink(skb, queue);
+}
+
+static int __mm81x_skbq_put(struct mm81x_skbq *mq, struct sk_buff_head *queue,
+			    struct sk_buff *skb, bool queue_at_head,
+			    struct sk_buff *queue_before)
+{
+	/* Limit the size of the Tx queue, but not the pending queue */
+	if (queue == &mq->skbq) {
+		if (skb->len > __mm81x_skbq_space(mq))
+			return -ENOMEM;
+
+		mq->skbq_size += skb->len;
+	}
+
+	if (queue_before)
+		__skb_queue_before(queue, queue_before, skb);
+	else if (queue_at_head)
+		__skb_queue_head(queue, skb);
+	else
+		__skb_queue_tail(queue, skb);
+
+	return 0;
+}
+
+static void __mm81x_skbq_pkt_id(struct mm81x_skbq *mq, struct sk_buff *skb)
+{
+	struct mm81x_skb_hdr *hdr = (struct mm81x_skb_hdr *)skb->data;
+
+	hdr->tx_info.pkt_id = cpu_to_le32(mq->pkt_seq++);
+}
+
+static struct mm81x_skbq *
+__mm81x_skbq_tx_status_to_skbq(struct mm81x *mors,
+			       const struct mm81x_skb_tx_status *tx_sts)
+{
+	int aci;
+	struct mm81x_skbq *mq = NULL;
+
+	switch (tx_sts->channel) {
+	case MM81X_SKB_CHAN_DATA:
+	case MM81X_SKB_CHAN_DATA_NOACK:
+		aci = dot11_tid_to_ac(tx_sts->tid);
+		mq = mm81x_hif_get_tx_data_queue(mors, aci);
+		break;
+	case MM81X_SKB_CHAN_MGMT:
+		mq = mm81x_hif_get_tx_mgmt_queue(mors);
+		break;
+	case MM81X_SKB_CHAN_BEACON:
+		mq = mm81x_hif_get_tx_beacon_queue(mors);
+		break;
+	default:
+		dev_err(mors->dev,
+			"unexpected channel on reported tx status [%d]",
+			tx_sts->channel);
+	}
+
+	return mq;
+}
+
+void mm81x_skbq_pull_hdr_post_tx(struct sk_buff *skb)
+{
+	skb_pull(skb, sizeof(struct mm81x_skb_hdr) +
+			      ((struct mm81x_skb_hdr *)skb->data)->offset);
+}
+
+static void mm81x_skbq_insert_pending(struct mm81x_skbq *mq,
+				      struct sk_buff *skb, __le32 insertion_id)
+{
+	struct sk_buff *pfirst, *pnext;
+	struct mm81x_skb_hdr *mhdr;
+	struct sk_buff *tail = skb_peek_tail(&mq->skbq);
+
+	__mm81x_skbq_unlink(mq, &mq->pending, skb);
+
+	if (!tail) {
+		__mm81x_skbq_put(mq, &mq->skbq, skb, false, NULL);
+		return;
+	}
+
+	/* Check if it should just be inserted on to the end */
+	mhdr = (struct mm81x_skb_hdr *)tail->data;
+	WARN_ON(insertion_id == mhdr->tx_info.pkt_id);
+	if (le32_to_cpu(insertion_id) >= le32_to_cpu(mhdr->tx_info.pkt_id)) {
+		__mm81x_skbq_put(mq, &mq->skbq, skb, false, NULL);
+		return;
+	}
+
+	/* Otherwise, re-insert to correct spot in skbq */
+	skb_queue_walk_safe(&mq->skbq, pfirst, pnext) {
+		mhdr = (struct mm81x_skb_hdr *)pfirst->data;
+
+		WARN_ON(insertion_id == mhdr->tx_info.pkt_id);
+		if (le32_to_cpu(insertion_id) <=
+		    le32_to_cpu(mhdr->tx_info.pkt_id)) {
+			__mm81x_skbq_put(mq, &mq->skbq, skb, false, pfirst);
+			return;
+		}
+	}
+
+	WARN_ON_ONCE(1);
+}
+
+static void mm81x_skbq_sta_eosp(struct mm81x *mors, struct sk_buff *skb)
+{
+	struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);
+	struct ieee80211_vif *vif = txi->control.vif;
+
+	mm81x_skbq_pull_hdr_post_tx(skb);
+
+	/*
+	 * If this frame is the last frame in a PS-Poll or u-APSD SP,
+	 * then mac80211 must be informed that the SP is now over.
+	 */
+	if (txi->flags & IEEE80211_TX_STATUS_EOSP) {
+		struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+		struct ieee80211_sta *sta;
+
+		scoped_guard(rcu) {
+			sta = ieee80211_find_sta(vif, hdr->addr1);
+			if (sta)
+				ieee80211_sta_eosp(sta);
+		}
+	}
+}
+
+static void __mm81x_skbq_drop_pending_skb(struct mm81x_skbq *mq,
+					  struct sk_buff *skb)
+{
+	__mm81x_skbq_unlink(mq, &mq->pending, skb);
+	mm81x_skbq_sta_eosp(mq->mors, skb);
+	ieee80211_free_txskb(mq->mors->hw, skb);
+}
+
+static bool mm81x_tx_h_is_ps_filtered(struct mm81x_skbq *mq,
+				      struct sk_buff *skb,
+				      struct mm81x_skb_tx_status *tx_sts)
+{
+	struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);
+	struct ieee80211_vif *vif = txi->control.vif;
+
+	WARN_ON_ONCE(!(le32_to_cpu(tx_sts->flags) &
+		       MM81X_TX_STATUS_FLAGS_PS_FILTERED));
+
+	if (vif->type == NL80211_IFTYPE_AP) {
+		__mm81x_skbq_drop_pending_skb(mq, skb);
+		return true;
+	}
+
+	if (vif->type == NL80211_IFTYPE_STATION) {
+		mm81x_skbq_insert_pending(mq, skb, tx_sts->pkt_id);
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Get a pending frame by its ID. This will also drop frames with
+ * older packet ids that are in the list
+ */
+static struct sk_buff *__mm81x_skbq_get_pending_by_id(struct mm81x *mors,
+						      struct mm81x_skbq *mq,
+						      u32 pkt_id)
+{
+	struct sk_buff *pfirst, *pnext;
+	struct sk_buff *ret = NULL;
+
+	/* Move sent packets to pending list waiting for feedback */
+	skb_queue_walk_safe(&mq->pending, pfirst, pnext) {
+		struct mm81x_skb_hdr *hdr =
+			(struct mm81x_skb_hdr *)pfirst->data;
+
+		if (le32_to_cpu(hdr->tx_info.pkt_id) == pkt_id) {
+			ret = pfirst;
+			break;
+
+		} else if (le32_to_cpu(hdr->tx_info.pkt_id) < pkt_id &&
+			   __mm81x_skbq_has_pending_tx_skb_timed_out(pfirst)) {
+			__mm81x_skbq_drop_pending_skb(mq, pfirst);
+		}
+	}
+
+	return ret;
+}
+
+static void mm81x_skbq_check_tx_empty(struct mm81x *mors, struct mm81x_skbq *mq)
+{
+	lockdep_assert_held(&mq->lock);
+
+	if (mq->flags & MM81X_HIF_FLAGS_BEACON)
+		return;
+
+	if (skb_queue_len(&mq->skbq) + skb_queue_len(&mq->pending) == 0)
+		wake_up(&mq->mors->tx_empty_waitq);
+}
+
+static void __mm81x_skbq_tx_status_process(struct mm81x *mors,
+					   struct mm81x_skbq *mq,
+					   struct mm81x_skb_tx_status *tx_sts)
+{
+	struct sk_buff *skb;
+
+	lockdep_assert_held(&mq->lock);
+
+	skb = __mm81x_skbq_get_pending_by_id(mors, mq,
+					     le32_to_cpu(tx_sts->pkt_id));
+	if (!skb) {
+		dev_dbg(mors->dev,
+			"No pending pkt match found [pktid:%d chan:%d]",
+			tx_sts->pkt_id, tx_sts->channel);
+		goto out;
+	}
+
+	if (le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_PAGE_INVALID) {
+		__mm81x_skbq_drop_pending_skb(mq, skb);
+		goto out;
+	}
+
+	if (le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_FLAGS_PS_FILTERED &&
+	    mm81x_tx_h_is_ps_filtered(mq, skb, tx_sts))
+		/* Has been consumed by mm81x_tx_h_is_ps_filtered */
+		goto out;
+
+	mm81x_skbq_pull_hdr_post_tx(skb);
+	mm81x_skbq_skb_finish(mq, skb, tx_sts);
+
+out:
+	mm81x_skbq_check_tx_empty(mors, mq);
+}
+
+static void mm81x_skbq_tx_status_process(struct mm81x *mors,
+					 struct sk_buff *skb)
+{
+	int i;
+	struct mm81x_skb_tx_status *tx_sts =
+		(struct mm81x_skb_tx_status *)skb->data;
+	int count = skb->len / sizeof(*tx_sts);
+
+	for (i = 0; i < count; tx_sts++, i++) {
+		struct mm81x_skbq *mq =
+			__mm81x_skbq_tx_status_to_skbq(mors, tx_sts);
+
+		if (mq) {
+			spin_lock_bh(&mq->lock);
+			__mm81x_skbq_tx_status_process(mors, mq, tx_sts);
+			spin_unlock_bh(&mq->lock);
+		}
+	}
+
+	if (mors->ps.enable && !mors->ps.suspended &&
+	    (mm81x_hif_get_tx_buffered_count(mors) == 0)) {
+		/* Evaluate ps, check if it was gated on a pending tx status */
+		queue_delayed_work(mors->chip_wq, &mors->ps.delayed_eval_work,
+				   0);
+	}
+}
+
+static void mm81x_skbq_dispatch_work(struct work_struct *dispatch_work)
+{
+	struct mm81x_skbq *mq =
+		container_of(dispatch_work, struct mm81x_skbq, dispatch_work);
+	struct mm81x *mors = mq->mors;
+	struct mm81x_skb_hdr *hdr;
+	struct sk_buff_head skbq;
+	struct sk_buff *pfirst, *pnext;
+	u8 channel;
+
+	__skb_queue_head_init(&skbq);
+
+	mm81x_skbq_deq_num_skb(mq, &skbq, mm81x_skbq_count(mq));
+
+	skb_queue_walk_safe(&skbq, pfirst, pnext) {
+		__skb_unlink(pfirst, &skbq);
+		/* Header endianness has already be adjusted */
+		hdr = (struct mm81x_skb_hdr *)pfirst->data;
+		channel = hdr->channel;
+		/* Remove mm81x header and padding */
+		__skb_pull(pfirst, sizeof(*hdr) + hdr->offset);
+
+		switch (channel) {
+		case MM81X_SKB_CHAN_COMMAND:
+			mm81x_cmd_resp_process(mors, pfirst);
+			break;
+		case MM81X_SKB_CHAN_TX_STATUS:
+			mm81x_skbq_tx_status_process(mors, pfirst);
+			dev_kfree_skb_any(pfirst);
+			break;
+		default:
+			mm81x_mac_rx_skb(mors, pfirst, &hdr->rx_status);
+			break;
+		}
+	}
+
+	if (mm81x_skbq_count(mq))
+		queue_work(mors->net_wq, &mq->dispatch_work);
+}
+
+int mm81x_skbq_put(struct mm81x_skbq *mq, struct sk_buff *skb)
+{
+	int ret;
+
+	spin_lock_bh(&mq->lock);
+	ret = __mm81x_skbq_put(mq, &mq->skbq, skb, false, NULL);
+	spin_unlock_bh(&mq->lock);
+	return ret;
+}
+
+static void mm81x_skbq_set_queued_tx_skb_expiry(struct sk_buff *skb)
+{
+	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+	struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);
+
+	if (ieee80211_is_probe_req(hdr->frame_control) ||
+	    ieee80211_is_probe_resp(hdr->frame_control) ||
+	    ieee80211_is_auth(hdr->frame_control)) {
+		txi->control.enqueue_time = (u32)jiffies;
+	} else {
+		txi->control.enqueue_time = 0;
+	}
+}
+
+static bool mm81x_skbq_has_queued_tx_skb_expired(struct sk_buff *skb)
+{
+	struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);
+
+	if (txi->control.enqueue_time > 0) {
+		u32 expiry_time =
+			txi->control.enqueue_time +
+			msecs_to_jiffies(MM81X_SKBQ_TX_QUEUED_LIFETIME_MS);
+
+		return (s32)((u32)jiffies - expiry_time) > 0;
+	}
+
+	return false;
+}
+
+/*
+ * Drop selected frames (those with an expiry time set) that could not
+ * be sent within a reasonable timeframe due to congestion. These would
+ * only be rejected or ignored by the peer, so are only contributing to
+ * the problem.
+ */
+void mm81x_skbq_purge_aged(struct mm81x *mors, struct mm81x_skbq *mq)
+{
+	struct sk_buff *pfirst;
+	struct sk_buff *pnext;
+
+	spin_lock_bh(&mq->lock);
+	skb_queue_walk_safe(&mq->skbq, pfirst, pnext) {
+		if (!mm81x_skbq_has_queued_tx_skb_expired(pfirst))
+			break;
+		__mm81x_skbq_unlink(mq, &mq->skbq, pfirst);
+		ieee80211_free_txskb(mors->hw, pfirst);
+	}
+
+	spin_unlock_bh(&mq->lock);
+}
+
+void mm81x_skbq_purge(struct mm81x_skbq *mq, struct sk_buff_head *skbq)
+{
+	struct sk_buff *skb;
+
+	spin_lock_bh(&mq->lock);
+	while ((skb = __skb_dequeue(skbq)))
+		dev_kfree_skb_any(skb);
+	spin_unlock_bh(&mq->lock);
+}
+
+void mm81x_skbq_enq(struct mm81x_skbq *mq, struct sk_buff_head *skbq)
+{
+	int size;
+	struct sk_buff *pfirst, *pnext;
+
+	spin_lock_bh(&mq->lock);
+	size = __mm81x_skbq_space(mq);
+	skb_queue_walk_safe(skbq, pfirst, pnext) {
+		if (pfirst->len > size)
+			break;
+		__skb_unlink(pfirst, skbq);
+		__mm81x_skbq_put(mq, &mq->skbq, pfirst, false, NULL);
+		size -= pfirst->len;
+	}
+
+	spin_unlock_bh(&mq->lock);
+}
+
+int mm81x_skbq_deq_num_skb(struct mm81x_skbq *mq, struct sk_buff_head *skbq,
+			   int num_skb)
+{
+	int count = 0;
+	struct sk_buff *pfirst, *pnext;
+
+	spin_lock_bh(&mq->lock);
+	skb_queue_walk_safe(&mq->skbq, pfirst, pnext) {
+		if (count >= num_skb)
+			break;
+		__mm81x_skbq_unlink(mq, &mq->skbq, pfirst);
+		__skb_queue_tail(skbq, pfirst);
+		++count;
+	}
+
+	spin_unlock_bh(&mq->lock);
+	return count;
+}
+
+void mm81x_skbq_enq_prepend(struct mm81x_skbq *mq, struct sk_buff_head *skbq)
+{
+	int size;
+	struct sk_buff *pfirst, *pnext;
+
+	spin_lock_bh(&mq->lock);
+	size = __mm81x_skbq_space(mq);
+
+	/*
+	 * We are doing a reverse walk here to ensure the order remains the
+	 * same. This means the last member of the queue goes in, on top of
+	 * the queue first and gets pushed down as more members get added to
+	 * the top of the queue.
+	 */
+	skb_queue_reverse_walk_safe(skbq, pfirst, pnext) {
+		if (pfirst->len > size)
+			break;
+		__skb_unlink(pfirst, skbq);
+		__mm81x_skbq_put(mq, &mq->skbq, pfirst, true, NULL);
+		size -= pfirst->len;
+	}
+
+	spin_unlock_bh(&mq->lock);
+}
+
+static void mm81x_skbq_stop_tx_queues(struct mm81x *mors)
+{
+	int queue;
+
+	if (!mors->started)
+		return;
+	for (queue = IEEE80211_AC_VO; queue <= IEEE80211_AC_BK; queue++)
+		ieee80211_stop_queue(mors->hw, queue);
+
+	set_bit(MM81X_STATE_DATA_QS_STOPPED, &mors->state_flags);
+}
+
+/* Wake all Tx queues if all queues are below threshold */
+void mm81x_skbq_may_wake_tx_queues(struct mm81x *mors)
+{
+	int queue;
+	struct mm81x_skbq *qs;
+	int num_qs;
+	bool could_wake;
+
+	if (!mors->started)
+		return;
+
+	could_wake = true;
+	mm81x_hif_skbq_get_tx_qs(mors, &qs, &num_qs);
+	for (queue = 0; queue < num_qs; queue++) {
+		struct mm81x_skbq *mq = &qs[queue];
+
+		if (!could_wake)
+			break;
+
+		spin_lock_bh(&mq->lock);
+		could_wake &= (__mm81x_skbq_under_threshold(mq));
+		spin_unlock_bh(&mq->lock);
+	}
+
+	if (!could_wake)
+		return;
+
+	for (queue = IEEE80211_AC_VO; queue <= IEEE80211_AC_BK; queue++)
+		ieee80211_wake_queue(mors->hw, queue);
+
+	clear_bit(MM81X_STATE_DATA_QS_STOPPED, &mors->state_flags);
+}
+
+static int mm81x_skbq_tx(struct mm81x_skbq *mq, struct sk_buff *skb, u8 channel)
+{
+	int rc;
+	bool mq_over_threshold;
+	struct mm81x *mors = mq->mors;
+
+	spin_lock_bh(&mq->lock);
+	rc = __mm81x_skbq_put(mq, &mq->skbq, skb, false, NULL);
+	if (rc) {
+		dev_err(mors->dev, "skb put chan %d failed (%d)", channel, rc);
+		if (channel == MM81X_SKB_CHAN_DATA) {
+			u16 queue = skb_get_queue_mapping(skb);
+
+			dev_err(mors->dev, "skb put queue %d status %d", queue,
+				ieee80211_queue_stopped(mors->hw, queue));
+		}
+	}
+
+	/* Fill packet ID in TX info */
+	__mm81x_skbq_pkt_id(mq, skb);
+
+	mq_over_threshold = __mm81x_skbq_over_threshold(mq);
+	spin_unlock_bh(&mq->lock);
+
+	/* For data packets stop queues */
+	if (channel == MM81X_SKB_CHAN_DATA && mq_over_threshold)
+		mm81x_skbq_stop_tx_queues(mors);
+
+	switch (channel) {
+	case MM81X_SKB_CHAN_DATA:
+	case MM81X_SKB_CHAN_DATA_NOACK:
+		if (mm81x_is_data_tx_allowed(mors)) {
+			set_bit(MM81X_HIF_EVT_TX_DATA_PEND,
+				&mors->hif.event_flags);
+			queue_work(mors->chip_wq, &mors->hif_work);
+		}
+		break;
+	case MM81X_SKB_CHAN_MGMT:
+		set_bit(MM81X_HIF_EVT_TX_MGMT_PEND, &mors->hif.event_flags);
+		queue_work(mors->chip_wq, &mors->hif_work);
+		break;
+	case MM81X_SKB_CHAN_BEACON:
+		set_bit(MM81X_HIF_EVT_TX_BEACON_PEND, &mors->hif.event_flags);
+		queue_work(mors->chip_wq, &mors->hif_work);
+		break;
+	case MM81X_SKB_CHAN_COMMAND:
+		set_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, &mors->hif.event_flags);
+		queue_work(mors->chip_wq, &mors->hif_work);
+		break;
+	default:
+		dev_err(mors->dev, "Invalid skb channel: %d", channel);
+		break;
+	}
+
+	return rc;
+}
+
+static void __mm81x_skbq_tx_move_to_pending(struct mm81x_skbq *mq,
+					    struct sk_buff *skb)
+{
+	struct mm81x_tx_status_priv *pend_info =
+		__mm81x_skbq_tx_status_priv(skb);
+
+	pend_info->tx_status_expiry =
+		jiffies + msecs_to_jiffies(MM81X_SKBQ_TX_STATUS_LIFETIME_MS);
+	__mm81x_skbq_put(mq, &mq->pending, skb, false, NULL);
+}
+
+void mm81x_skbq_tx_complete(struct mm81x_skbq *mq, struct sk_buff_head *skbq)
+{
+	bool skb_awaits_tx_status = false;
+	struct mm81x *mors = mq->mors;
+	struct sk_buff *pfirst, *pnext;
+	struct sk_buff *peek = skb_peek(skbq);
+	struct mm81x_skb_hdr *hdr;
+	const bool fw_reports_bcn_tx_status =
+		mors->firmware_flags &
+		MM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION;
+
+	if (!peek)
+		return;
+
+	/* Move sent packets to pending list waiting for feedback */
+	spin_lock_bh(&mq->lock);
+	skb_queue_walk_safe(skbq, pfirst, pnext) {
+		__skb_unlink(pfirst, skbq);
+		hdr = (struct mm81x_skb_hdr *)pfirst->data;
+		/*
+		 * If firmware doesn't give status on beacons just free
+		 * them, otherwise queue and wait for response.
+		 */
+		switch (hdr->channel) {
+		case MM81X_SKB_CHAN_BEACON:
+			if (fw_reports_bcn_tx_status) {
+				__mm81x_skbq_tx_move_to_pending(mq, pfirst);
+				skb_awaits_tx_status = true;
+				break;
+			}
+			/*
+			 * If the FW doesn't give statuses on beacon's,
+			 * then mark them as done.
+			 */
+			mm81x_skbq_pull_hdr_post_tx(pfirst);
+			dev_kfree_skb_any(pfirst);
+			break;
+		default:
+			if (le32_to_cpu(hdr->tx_info.flags) &
+			    MM81X_TX_STATUS_FLAGS_NO_REPORT) {
+				dev_kfree_skb_any(pfirst);
+			} else {
+				/*
+				 * skb has been given to the chip. Store the
+				 * time and queue the skb onto the pending
+				 * queue while we wait for the tx_status.
+				 */
+				__mm81x_skbq_tx_move_to_pending(mq, pfirst);
+				skb_awaits_tx_status = true;
+			}
+			break;
+		}
+	}
+	spin_unlock_bh(&mq->lock);
+
+	if (skb_awaits_tx_status) {
+		spin_lock_bh(&mors->stale_status.lock);
+		mod_timer(&mors->stale_status.timer,
+			  jiffies + msecs_to_jiffies(
+					    MM81X_SKBQ_TX_STATUS_LIFETIME_MS));
+		spin_unlock_bh(&mors->stale_status.lock);
+	}
+}
+
+/* Returns the first skb in the pending list. */
+struct sk_buff *mm81x_skbq_tx_pending(struct mm81x_skbq *mq)
+{
+	struct sk_buff *pfirst;
+
+	spin_lock_bh(&mq->lock);
+	pfirst = skb_peek(&mq->pending);
+	spin_unlock_bh(&mq->lock);
+	return pfirst;
+}
+
+int mm81x_skbq_check_for_stale_tx(struct mm81x *mors, struct mm81x_skbq *mq)
+{
+	int flushed = 0;
+	struct sk_buff *pfirst;
+	struct sk_buff *pnext;
+
+	if (!skb_queue_len(&mq->pending))
+		return 0;
+
+	/* Move sent packets to pending list waiting for feedback */
+	spin_lock_bh(&mq->lock);
+	skb_queue_walk_safe(&mq->pending, pfirst, pnext) {
+		struct mm81x_skb_hdr *hdr =
+			(struct mm81x_skb_hdr *)pfirst->data;
+
+		if (__mm81x_skbq_has_pending_tx_skb_timed_out(pfirst)) {
+			dev_dbg(mors->dev, "TX skb timed out [id:%d,chan:%d]",
+				hdr->tx_info.pkt_id, hdr->channel);
+
+			__mm81x_skbq_drop_pending_skb(mq, pfirst);
+			flushed++;
+		}
+	}
+
+	if (flushed)
+		mm81x_skbq_check_tx_empty(mors, mq);
+
+	spin_unlock_bh(&mq->lock);
+	return flushed;
+}
+
+/* Remove commands from pending (or skbq if not sent) */
+static void __skbq_cmd_finish(struct mm81x_skbq *mq, struct sk_buff *skb)
+{
+	struct mm81x *mors = mq->mors;
+
+	if (skb_queue_len(&mq->pending)) {
+		__mm81x_skbq_unlink(mq, &mq->pending, skb);
+		dev_kfree_skb(skb);
+	} else if (skb_queue_len(&mq->skbq)) {
+		/* Command was probably timed out before being sent */
+		dev_dbg(mors->dev,
+			"Command pending queue empty. Removing from SKBQ.");
+		__mm81x_skbq_unlink(mq, &mq->skbq, skb);
+		dev_kfree_skb(skb);
+	} else {
+		dev_dbg(mors->dev, "Command Q not found");
+	}
+}
+
+struct mm81x_update_sta_iter_data {
+	struct mm81x *mors;
+	struct sk_buff *skb;
+	struct mm81x_skb_tx_status *tx_sts;
+	int tx_attempts;
+	bool updated;
+};
+
+static void mm81x_tx_h_update_sta_iter(void *data, u8 *mac,
+				       struct ieee80211_vif *vif)
+{
+	struct mm81x_update_sta_iter_data *iter = data;
+	struct ieee80211_hdr *hdr;
+	struct ieee80211_sta *sta;
+
+	if (iter->updated || !iter->skb || !iter->skb->data)
+		return;
+
+	hdr = (struct ieee80211_hdr *)iter->skb->data;
+
+	/*
+	 * Note that each iteration via
+	 * ieee80211_iterate_active_interfaces_atomic is under an RCU critical
+	 * section so there is no need for a local critical section within here
+	 * when looking up the station.
+	 */
+	sta = ieee80211_find_sta(vif, hdr->addr1);
+	if (!sta)
+		return;
+
+	mm81x_rc_sta_feedback_rates(iter->mors, iter->skb, sta, iter->tx_sts,
+				    iter->tx_attempts);
+	mm81x_tx_h_check_aggr(sta, iter->skb);
+
+	/*
+	 * In situations with multiple virtual interfaces, finish iteration
+	 * once we have found our STA to prevent further iteration.
+	 */
+	iter->updated = true;
+}
+
+/* TX status/Response received remove packet from pending TX finish */
+static void __skbq_data_tx_finish(struct mm81x_skbq *mq, struct sk_buff *skb,
+				  struct mm81x_skb_tx_status *tx_sts)
+{
+	struct mm81x *mors = mq->mors;
+	struct mm81x_update_sta_iter_data iter = {};
+
+	__mm81x_skbq_unlink(mq, &mq->pending, skb);
+	iter.mors = mors;
+	iter.skb = skb;
+	iter.tx_sts = tx_sts;
+	iter.tx_attempts = mm81x_tx_h_get_attempts(mors, tx_sts);
+
+	ieee80211_iterate_active_interfaces_atomic(mors->hw,
+						   IEEE80211_IFACE_ITER_NORMAL,
+						   mm81x_tx_h_update_sta_iter,
+						   &iter);
+
+	ieee80211_tx_status_skb(mors->hw, skb);
+}
+
+void mm81x_skbq_skb_finish(struct mm81x_skbq *mq, struct sk_buff *skb,
+			   struct mm81x_skb_tx_status *tx_sts)
+{
+	if (mq->flags & MM81X_HIF_FLAGS_COMMAND)
+		__skbq_cmd_finish(mq, skb);
+	else
+		__skbq_data_tx_finish(mq, skb, tx_sts);
+}
+
+void mm81x_skbq_tx_flush(struct mm81x_skbq *mq)
+{
+	struct sk_buff *pfirst, *pnext;
+
+	spin_lock_bh(&mq->lock);
+	skb_queue_walk_safe(&mq->pending, pfirst, pnext) {
+		__mm81x_skbq_unlink(mq, &mq->pending, pfirst);
+		ieee80211_free_txskb(mq->mors->hw, pfirst);
+	}
+
+	skb_queue_walk_safe(&mq->skbq, pfirst, pnext) {
+		__mm81x_skbq_unlink(mq, &mq->skbq, pfirst);
+		ieee80211_free_txskb(mq->mors->hw, pfirst);
+	}
+	spin_unlock_bh(&mq->lock);
+}
+
+void mm81x_skbq_init(struct mm81x *mors, struct mm81x_skbq *mq, u16 flags)
+{
+	spin_lock_init(&mq->lock);
+	__skb_queue_head_init(&mq->skbq);
+	__skb_queue_head_init(&mq->pending);
+	mq->mors = mors;
+	mq->skbq_size = 0;
+	mq->flags = flags;
+	mq->pkt_seq = 0;
+	if (flags & MM81X_HIF_FLAGS_DIR_TO_HOST)
+		INIT_WORK(&mq->dispatch_work, mm81x_skbq_dispatch_work);
+}
+
+void mm81x_skbq_finish(struct mm81x_skbq *mq)
+{
+	if (mq->skbq_size > 0)
+		dev_dbg(mq->mors->dev,
+			"Purging a non empty MorseQ. Dropping data!");
+
+	/* Clean up link to hif */
+	if (mq->flags & MM81X_HIF_FLAGS_DIR_TO_HOST)
+		cancel_work_sync(&mq->dispatch_work);
+	mm81x_skbq_purge(mq, &mq->skbq);
+	mm81x_skbq_purge(mq, &mq->pending);
+	mq->skbq_size = 0;
+}
+
+u32 mm81x_skbq_size(struct mm81x_skbq *mq)
+{
+	u32 count;
+
+	spin_lock_bh(&mq->lock);
+	count = __mm81x_skbq_size(mq);
+	spin_unlock_bh(&mq->lock);
+	return count;
+}
+
+u32 mm81x_skbq_count(struct mm81x_skbq *mq)
+{
+	u32 count = 0;
+
+	spin_lock_bh(&mq->lock);
+	count += skb_queue_len(&mq->skbq);
+	spin_unlock_bh(&mq->lock);
+	return count;
+}
+
+u32 mm81x_skbq_pending_count(struct mm81x_skbq *mq)
+{
+	u32 count;
+
+	spin_lock_bh(&mq->lock);
+	count = skb_queue_len(&mq->pending);
+	spin_unlock_bh(&mq->lock);
+	return count;
+}
+
+u32 mm81x_skbq_count_tx_ready(struct mm81x_skbq *mq)
+{
+	struct mm81x *mors = mq->mors;
+
+	if (!mm81x_is_data_tx_allowed(mors))
+		return 0;
+
+	return mm81x_skbq_count(mq);
+}
+
+u32 mm81x_skbq_space(struct mm81x_skbq *mq)
+{
+	u32 space;
+
+	spin_lock_bh(&mq->lock);
+	space = __mm81x_skbq_space(mq);
+	spin_unlock_bh(&mq->lock);
+
+	return space;
+}
+
+struct sk_buff *mm81x_skbq_alloc_skb(struct mm81x_skbq *mq, unsigned int length)
+{
+	struct sk_buff *skb;
+	int tx_headroom = sizeof(struct mm81x_skb_hdr) +
+			  mm81x_bus_get_alignment(mq->mors);
+	int skb_len = tx_headroom + length + MM81X_PAD4(length);
+
+	skb = dev_alloc_skb(skb_len);
+	if (!skb)
+		return NULL;
+
+	skb_reserve(skb, tx_headroom);
+	skb_put(skb, length);
+	return skb;
+}
+
+static int mm81x_skb_tx_h_validate_channel(const struct mm81x *mors, u8 channel)
+{
+	if (channel == MM81X_SKB_CHAN_COMMAND) {
+		if (test_bit(MM81X_STATE_HOST_TO_CHIP_CMD_BLOCKED,
+			     &mors->state_flags))
+			return -EPERM;
+	} else {
+		if (test_bit(MM81X_STATE_HOST_TO_CHIP_TX_BLOCKED,
+			     &mors->state_flags))
+			return -EPERM;
+	}
+
+	return 0;
+}
+
+int mm81x_skbq_skb_tx(struct mm81x_skbq *mq, struct sk_buff **skb_orig,
+		      struct mm81x_skb_tx_info *tx_info, u8 channel)
+{
+	int ret;
+	struct mm81x_skb_hdr hdr;
+	struct mm81x *mors = mq->mors;
+	size_t end_of_skb_pad;
+	struct sk_buff *skb = *skb_orig;
+	u8 *aligned_head, *data;
+
+	if (test_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags)) {
+		dev_kfree_skb_any(skb);
+		return -ENODEV;
+	}
+
+	ret = mm81x_skb_tx_h_validate_channel(mors, channel);
+	if (ret) {
+		dev_kfree_skb_any(skb);
+		return ret;
+	}
+
+	mm81x_skbq_set_queued_tx_skb_expiry(skb);
+
+	data = skb->data;
+	aligned_head = PTR_ALIGN_DOWN((data - sizeof(hdr)),
+				      mm81x_bus_get_alignment(mors));
+	hdr.sync = MM81X_SKB_HEADER_SYNC;
+	hdr.channel = channel;
+	hdr.len = cpu_to_le16(skb->len);
+	hdr.offset = data - (aligned_head + sizeof(hdr));
+	hdr.checksum_upper = 0;
+	hdr.checksum_lower = 0;
+	if (tx_info)
+		memcpy(&hdr.tx_info, tx_info, sizeof(*tx_info));
+	else
+		memset(&hdr.tx_info, 0, sizeof(hdr.tx_info));
+
+	skb_push(skb, data - aligned_head);
+	memcpy(skb->data, &hdr, sizeof(hdr));
+
+	end_of_skb_pad = MM81X_PAD4(skb->len);
+	if (end_of_skb_pad && skb_pad(skb, end_of_skb_pad))
+		return -EINVAL;
+
+	ret = mm81x_skbq_tx(mq, skb, channel);
+	if (ret) {
+		dev_err(mors->dev, "mm81x_skbq_tx fail: %d", ret);
+		dev_kfree_skb_any(skb);
+	}
+
+	return ret;
+}
+
+void mm81x_skbq_data_traffic_pause(struct mm81x *mors)
+{
+	set_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags);
+	/* power-save requirements will be re-evaluated by the caller */
+}
+
+void mm81x_skbq_data_traffic_resume(struct mm81x *mors)
+{
+	clear_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags);
+
+	/* Set the TX_DATA_PEND bit. This will kick the transmission path to
+	 * send any frames pending in the TX buffers, and wake the mac80211
+	 * data Qs if they were previously stopped.
+	 */
+	set_bit(MM81X_HIF_EVT_TX_DATA_PEND, &mors->hif.event_flags);
+}
+
+bool mm81x_skbq_validate_checksum(u8 *data)
+{
+	int i;
+	u32 xor = 0;
+	struct mm81x_skb_hdr *skb_hdr = (struct mm81x_skb_hdr *)data;
+	struct ieee80211_hdr *hdr =
+		(struct ieee80211_hdr *)(data + sizeof(*skb_hdr));
+	u16 len = le16_to_cpu(skb_hdr->len) + sizeof(*skb_hdr);
+	u32 *data_to_xor = (u32 *)data;
+	u32 header_xor = (le16_to_cpu(skb_hdr->checksum_upper) << 8) |
+			 (skb_hdr->checksum_lower);
+
+	/*
+	 * For data frames the calculate the xor for skb header, mac header
+	 * and ccmp header. For all other channel the xor is calculated for
+	 * the full skb.
+	 */
+	if (skb_hdr->channel == MM81X_SKB_CHAN_DATA &&
+	    (ieee80211_is_data(hdr->frame_control) ||
+	     ieee80211_is_data_qos(hdr->frame_control))) {
+		u16 data_len = sizeof(*skb_hdr) +
+			       sizeof(struct ieee80211_qos_hdr) +
+			       IEEE80211_CCMP_HDR_LEN;
+
+		len = min(len, data_len);
+		len = ROUND_DOWN_TO_WORD(len);
+	}
+
+	skb_hdr->checksum_upper = 0;
+	skb_hdr->checksum_lower = 0;
+
+	for (i = 0; i < len; i += 4) {
+		xor ^= *data_to_xor;
+		data_to_xor++;
+	}
+
+	xor &= 0x00FFFFFF;
+
+	return xor == header_xor;
+}
diff --git a/drivers/net/wireless/morsemicro/mm81x/skbq.h b/drivers/net/wireless/morsemicro/mm81x/skbq.h
new file mode 100644
index 00000000000000..9930493141cfb7
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/skbq.h
@@ -0,0 +1,218 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_SKBQ_H_
+#define _MM81X_SKBQ_H_
+
+#include <linux/skbuff.h>
+#include <linux/workqueue.h>
+#include "rate_code.h"
+
+/* Sync value of skb header to indicate a valid skb */
+#define MM81X_SKB_HEADER_SYNC (0xAA)
+/* Sync value indicating that the chip owns this skb */
+#define MM81X_SKB_HEADER_CHIP_OWNED_SYNC (0xBB)
+
+enum mm81x_tx_status_and_conf_flags {
+	MM81X_TX_STATUS_FLAGS_NO_ACK = BIT(0),
+	MM81X_TX_STATUS_FLAGS_NO_REPORT = BIT(1),
+	MM81X_TX_CONF_FLAGS_CTL_AMPDU = BIT(2),
+	MM81X_TX_CONF_FLAGS_HW_ENCRYPT = BIT(3),
+	MM81X_TX_CONF_FLAGS_VIF_ID = (BIT(4) | BIT(5) | BIT(6) | BIT(7) |
+				      BIT(8) | BIT(9) | BIT(10) | BIT(11)),
+	MM81X_TX_CONF_FLAGS_KEY_IDX = (BIT(12) | BIT(13) | BIT(14)),
+	MM81X_TX_STATUS_FLAGS_PS_FILTERED = (BIT(15)),
+	MM81X_TX_CONF_IGNORE_TWT = (BIT(16)),
+	MM81X_TX_STATUS_PAGE_INVALID = (BIT(17)),
+	MM81X_TX_CONF_NO_PS_BUFFER = (BIT(18)),
+	MM81X_TX_STATUS_DUTY_CYCLE_CANT_SEND = (BIT(19)),
+	MM81X_TX_CONF_HAS_PV1_BPN_IN_BODY = (BIT(21)),
+	MM81X_TX_CONF_FLAGS_SEND_AFTER_DTIM = (BIT(22)),
+	MM81X_TX_STATUS_WAS_AGGREGATED = (BIT(23)),
+	MM81X_TX_CONF_FLAGS_FULLMAC_REPORT = BIT(24),
+	MM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT = (BIT(31))
+};
+
+/* Getter and setter macros for vif id */
+#define MM81X_TX_CONF_FLAGS_VIF_ID_MASK (0xFF)
+#define MM81X_TX_CONF_FLAGS_VIF_ID_SET(x) \
+	(((x) & MM81X_TX_CONF_FLAGS_VIF_ID_MASK) << 4)
+#define MM81X_TX_CONF_FLAGS_VIF_ID_GET(x) \
+	(((x) & MM81X_TX_CONF_FLAGS_VIF_ID) >> 4)
+
+/* Getter and setter macros for key index */
+#define MM81X_TX_CONF_FLAGS_KEY_IDX_SET(x) (((x) & 0x07) << 12)
+#define MM81X_TX_CONF_FLAGS_KEY_IDX_GET(x) \
+	(((x) & MM81X_TX_CONF_FLAGS_KEY_IDX) >> 12)
+
+enum mm81x_rx_status_flags {
+	MM81X_RX_STATUS_FLAGS_ERROR = BIT(0),
+	MM81X_RX_STATUS_FLAGS_DECRYPTED = BIT(1),
+	MM81X_RX_STATUS_FLAGS_FCS_INCLUDED = BIT(2),
+	MM81X_RX_STATUS_FLAGS_EOF = BIT(3),
+	MM81X_RX_STATUS_FLAGS_AMPDU = BIT(4),
+	MM81X_RX_STATUS_FLAGS_NDP = BIT(7),
+	MM81X_RX_STATUS_FLAGS_UPLINK = BIT(8),
+	MM81X_RX_STATUS_FLAGS_RI = (BIT(9) | BIT(10)),
+	MM81X_RX_STATUS_FLAGS_NDP_TYPE = (BIT(11) | BIT(12) | BIT(13)),
+	MM81X_RX_STATUS_FLAGS_CRC_ERROR = BIT(14),
+	MM81X_RX_STATUS_FLAGS_VIF_ID = GENMASK(24, 17),
+};
+
+/* Getter and Setter macros for vif id */
+#define MM81X_RX_STATUS_FLAGS_VIF_ID_MASK (0xFF)
+#define MM81X_RX_STATUS_FLAGS_VIF_ID_SET(x) \
+	(((x) & MM81X_RX_STATUS_FLAGS_VIF_ID_MASK) << 17)
+#define MM81X_RX_STATUS_FLAGS_VIF_ID_GET(x) \
+	(((x) & MM81X_RX_STATUS_FLAGS_VIF_ID) >> 17)
+#define MM81X_RX_STATUS_FLAGS_VIF_ID_CLEAR(x) \
+	((x) & ~(MM81X_RX_STATUS_FLAGS_VIF_ID_MASK << 17))
+
+/* Getter macro for guard interval */
+#define MM81X_RX_STATUS_FLAGS_UPL_IND_GET(x) \
+	(((x) & MM81X_RX_STATUS_FLAGS_UPLINK) >> 8)
+
+/* Getter macro for response indication */
+#define MM81X_RX_STATUS_FLAGS_RI_GET(x) (((x) & MM81X_RX_STATUS_FLAGS_RI) >> 9)
+
+/* Getter macro for NDP type */
+#define MM81X_RX_STATUS_FLAGS_NDP_TYPE_GET(x) \
+	(((x) & MM81X_RX_STATUS_FLAGS_NDP_TYPE) >> 11)
+
+enum mm81x_skb_channel {
+	MM81X_SKB_CHAN_DATA = 0x0,
+	MM81X_SKB_CHAN_NDP_FRAMES = 0x1,
+	MM81X_SKB_CHAN_DATA_NOACK = 0x2,
+	MM81X_SKB_CHAN_BEACON = 0x3,
+	MM81X_SKB_CHAN_MGMT = 0x4,
+	MM81X_SKB_CHAN_INTERNAL_CRIT_BEACON = 0x80,
+	MM81X_SKB_CHAN_COMMAND = 0xFE,
+	MM81X_SKB_CHAN_TX_STATUS = 0xFF
+};
+
+#define MM81X_SKB_MAX_RATES (4)
+
+struct mm81x_skb_rate_info {
+	mm81x_rate_code_t mm81x_ratecode;
+	u8 count;
+} __packed;
+
+struct mm81x_skb_tx_status {
+	__le32 flags;
+	__le32 pkt_id;
+	u8 tid;
+	u8 channel;
+	__le16 ampdu_info;
+	struct mm81x_skb_rate_info rates[MM81X_SKB_MAX_RATES];
+} __packed;
+
+#define MM81X_TXSTS_AMPDU_INFO_GET_TAG(x) (((x) >> 10) & 0x3F)
+#define MM81X_TXSTS_AMPDU_INFO_GET_LEN(x) (((x) >> 5) & 0x1F)
+#define MM81X_TXSTS_AMPDU_INFO_GET_SUC(x) ((x) & 0x1F)
+
+struct mm81x_skb_tx_info {
+	__le32 flags;
+	__le32 pkt_id;
+	u8 tid;
+	u8 tid_params;
+	u8 mmss_params;
+	u8 padding[1];
+	struct mm81x_skb_rate_info rates[MM81X_SKB_MAX_RATES];
+} __packed;
+
+#define TX_INFO_TID_PARAMS_MAX_REORDER_BUF 0x1f
+#define TX_INFO_TID_PARAMS_AMPDU_ENABLED 0x20
+#define TX_INFO_TID_PARAMS_AMSDU_SUPPORTED 0x40
+#define TX_INFO_TID_PARAMS_USE_LEGACY_BA 0x80
+
+/* Bitmap for MMSS (Minimum MPDU start spacing) parameters
+ * +-----------+-----------+
+ * | Morse     | MMSS set  |
+ * | MMSS      | by S1G cap|
+ * | offset    | IE        |
+ * |-----------|-----------|
+ * |b7|b6|b5|b4|b3|b2|b1|b0|
+ */
+#define TX_INFO_MMSS_PARAMS_MMSS_MASK GENMASK(3, 0)
+#define TX_INFO_MMSS_PARAMS_MMSS_OFFSET_START 4
+#define TX_INFO_MMSS_PARAMS_MMSS_OFFSET_MASK GENMASK(7, 4)
+#define TX_INFO_MMSS_PARAMS_SET_MMSS(x) ((x) & TX_INFO_MMSS_PARAMS_MMSS_MASK)
+#define TX_INFO_MMSS_PARAMS_SET_MMSS_OFFSET(x)            \
+	(((x) << TX_INFO_MMSS_PARAMS_MMSS_OFFSET_START) & \
+	 TX_INFO_MMSS_PARAMS_MMSS_OFFSET_MASK)
+
+struct mm81x_skb_rx_status {
+	__le32 flags;
+	mm81x_rate_code_t mm81x_ratecode;
+	__le16 rssi;
+	__le16 freq_100khz;
+	u8 bss_color;
+	s8 noise_dbm;
+	/** Padding for word alignment */
+	u8 padding[2];
+	__le64 rx_timestamp_us;
+} __packed;
+
+struct mm81x_skb_hdr {
+	u8 sync;
+	u8 channel;
+	__le16 len;
+	u8 offset;
+	u8 checksum_lower;
+	__le16 checksum_upper;
+	union {
+		struct mm81x_skb_tx_info tx_info;
+		struct mm81x_skb_tx_status tx_status;
+		struct mm81x_skb_rx_status rx_status;
+	};
+} __packed;
+
+#define MM81X_SKBQ_SIZE (4 * 128 * 1024)
+
+struct mm81x;
+
+struct mm81x_skbq {
+	struct mm81x *mors;
+	u32 pkt_seq; /* SKB sequence used in tx_status */
+	u16 flags;
+	u32 skbq_size; /* current off loaded size */
+	spinlock_t lock;
+	struct sk_buff_head skbq;
+	struct sk_buff_head pending; /* packets sent pending feedback */
+	struct work_struct dispatch_work;
+};
+
+void mm81x_skbq_purge(struct mm81x_skbq *mq, struct sk_buff_head *skbq);
+void mm81x_skbq_purge_aged(struct mm81x *mors, struct mm81x_skbq *mq);
+u32 mm81x_skbq_space(struct mm81x_skbq *mq);
+u32 mm81x_skbq_size(struct mm81x_skbq *mq);
+int mm81x_skbq_deq_num_skb(struct mm81x_skbq *mq, struct sk_buff_head *skbq,
+			   int num_skb);
+struct sk_buff *mm81x_skbq_alloc_skb(struct mm81x_skbq *mq,
+				     unsigned int length);
+int mm81x_skbq_skb_tx(struct mm81x_skbq *mq, struct sk_buff **skb,
+		      struct mm81x_skb_tx_info *tx_info, u8 channel);
+int mm81x_skbq_put(struct mm81x_skbq *mq, struct sk_buff *skb);
+void mm81x_skbq_enq(struct mm81x_skbq *mq, struct sk_buff_head *skbq);
+void mm81x_skbq_enq_prepend(struct mm81x_skbq *mq, struct sk_buff_head *skbq);
+void mm81x_skbq_tx_complete(struct mm81x_skbq *mq, struct sk_buff_head *skbq);
+struct sk_buff *mm81x_skbq_tx_pending(struct mm81x_skbq *mq);
+void mm81x_skbq_init(struct mm81x *mors, struct mm81x_skbq *mq, u16 flags);
+void mm81x_skbq_finish(struct mm81x_skbq *mq);
+void mm81x_skbq_pull_hdr_post_tx(struct sk_buff *skb);
+void mm81x_skbq_mon_dump(struct mm81x *mors, struct seq_file *file);
+void mm81x_skbq_skb_finish(struct mm81x_skbq *mq, struct sk_buff *skb,
+			   struct mm81x_skb_tx_status *tx_sts);
+void mm81x_skbq_tx_flush(struct mm81x_skbq *mq);
+int mm81x_skbq_check_for_stale_tx(struct mm81x *mors, struct mm81x_skbq *mq);
+void mm81x_skbq_may_wake_tx_queues(struct mm81x *mors);
+u32 mm81x_skbq_count_tx_ready(struct mm81x_skbq *mq);
+u32 mm81x_skbq_count(struct mm81x_skbq *mq);
+u32 mm81x_skbq_pending_count(struct mm81x_skbq *mq);
+void mm81x_skbq_data_traffic_pause(struct mm81x *mors);
+void mm81x_skbq_data_traffic_resume(struct mm81x *mors);
+bool mm81x_skbq_validate_checksum(u8 *data);
+
+#endif /* !_MM81X_SKBQ_H_ */
diff --git a/drivers/net/wireless/morsemicro/mm81x/usb.c b/drivers/net/wireless/morsemicro/mm81x/usb.c
new file mode 100644
index 00000000000000..ec94936b157bbd
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/usb.c
@@ -0,0 +1,943 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include "hif.h"
+#include "bus.h"
+#include "mac.h"
+#include "core.h"
+
+/*
+ * URB timeout in milliseconds. If an URB does not complete within this
+ * time, it will be killed. This timeout needs to account for USB suspendand
+ * resume occurring before the URB can be transferred, and it also needs to
+ * account for transferring USB_MAX_TRANSFER_SIZE bytes over a potentially
+ * slow, congested USB Full Speed link.
+ */
+#define URB_TIMEOUT_MS 250
+
+/* High speed USB 2^(4-1) * 125usec = 1msec */
+#define MM81X_USB_INTERRUPT_INTERVAL 4
+
+/* Max bytes per USB read/write */
+#define USB_MAX_TRANSFER_SIZE (16 * 1024)
+
+/* INT EP buffer size */
+#define MM81X_EP_INT_BUFFER_SIZE 8
+
+/* Morse vendor IDs*/
+#define MM81X_VENDOR_ID 0x325b
+#define MM81X_MM810X_PRODUCT_ID 0x8100
+
+/* Power management runtime auto-suspend delay value in milliseconds */
+#define PM_RUNTIME_AUTOSUSPEND_DELAY_MS 100
+
+enum mm81x_usb_endpoints {
+	MM81X_EP_CMD = 0,
+	MM81X_EP_INT,
+	MM81X_EP_MEM_RD,
+	MM81X_EP_MEM_WR,
+	MM81X_EP_REG_RD,
+	MM81X_EP_REG_WR,
+	MM81X_EP_EP_MAX,
+};
+
+struct mm81x_usb_endpoint {
+	unsigned char *buffer;
+	struct urb *urb;
+	__u8 addr;
+	int size;
+};
+
+enum mm81x_usb_flags { MM81X_USB_FLAG_ATTACHED, MM81X_USB_FLAG_SUSPENDED };
+
+struct mm81x_usb {
+	struct usb_device *udev;
+	struct usb_interface *interface;
+	struct mm81x_usb_endpoint endpoints[MM81X_EP_EP_MAX];
+	int errors;
+
+	/* serialise USB device struct */
+	struct mutex lock;
+
+	/* serialise USB bus access */
+	struct mutex bus_lock;
+
+	bool ongoing_cmd;
+	bool ongoing_rw;
+	wait_queue_head_t rw_in_wait;
+	unsigned long flags;
+};
+
+enum mm81x_usb_command_direction {
+	MM81X_USB_WRITE = 0x00,
+	MM81X_USB_READ = 0x80,
+	MM81X_USB_RESET = 0x02,
+};
+
+struct mm81x_usb_command {
+	__le32 dir; /* Next BULK direction */
+	__le32 address; /* Next BULK address */
+	__le32 length; /* Next BULK size */
+};
+
+static const struct usb_device_id mm81x_usb_table[] = {
+	{ USB_DEVICE(MM81X_VENDOR_ID, MM81X_MM810X_PRODUCT_ID) },
+	{} /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, mm81x_usb_table);
+
+static void mm81x_usb_irq_work(struct work_struct *work)
+{
+	struct mm81x *mors = container_of(work, struct mm81x, usb_irq_work);
+
+	mm81x_claim_bus(mors);
+	mm81x_hw_irq_handle(mors);
+	mm81x_release_bus(mors);
+}
+
+static bool mm81x_usb_urb_status_is_disconnect(const struct urb *urb)
+{
+	return ((urb->status == -EPROTO) || (urb->status == -EILSEQ) ||
+		(urb->status == -ETIME) || (urb->status == -EPIPE));
+}
+
+static void mm81x_usb_int_handler(struct urb *urb)
+{
+	int ret;
+	struct mm81x *mors = urb->context;
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+
+	if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+		return;
+
+	if (urb->status) {
+		if (mm81x_usb_urb_status_is_disconnect(urb)) {
+			clear_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags);
+			set_bit(MM81X_STATE_CHIP_UNRESPONSIVE,
+				&mors->state_flags);
+			dev_dbg(mors->dev,
+				"USB sudden disconnect detected in %s",
+				__func__);
+			return;
+		}
+
+		if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
+		      urb->status == -ESHUTDOWN))
+			dev_err(mors->dev, "- nonzero read status received: %d",
+				urb->status);
+	}
+
+	ret = usb_submit_urb(urb, GFP_ATOMIC);
+
+	/* usb_kill_urb has been called */
+	if (ret == -EPERM)
+		return;
+	else if (ret)
+		dev_err(mors->dev, "error: resubmit urb %p err code %d", urb,
+			ret);
+
+	queue_work(mors->chip_wq, &mors->usb_irq_work);
+}
+
+static int mm81x_usb_int_enable(struct mm81x *mors)
+{
+	int ret = 0;
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+	struct urb *urb;
+
+	if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+		return -ENODEV;
+
+	urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!urb) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	musb->endpoints[MM81X_EP_INT].urb = urb;
+
+	musb->endpoints[MM81X_EP_INT].buffer =
+		usb_alloc_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE,
+				   GFP_KERNEL, &urb->transfer_dma);
+	if (!musb->endpoints[MM81X_EP_INT].buffer) {
+		dev_err(mors->dev, "couldn't allocate transfer_buffer");
+		ret = -ENOMEM;
+		goto error_set_urb_null;
+	}
+
+	usb_fill_int_urb(
+		musb->endpoints[MM81X_EP_INT].urb, musb->udev,
+		usb_rcvintpipe(musb->udev, musb->endpoints[MM81X_EP_INT].addr),
+		musb->endpoints[MM81X_EP_INT].buffer, MM81X_EP_INT_BUFFER_SIZE,
+		mm81x_usb_int_handler, mors, MM81X_USB_INTERRUPT_INTERVAL);
+	urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+	ret = usb_submit_urb(urb, GFP_KERNEL);
+	if (ret) {
+		dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret);
+		goto error;
+	}
+
+	return 0;
+
+error:
+	usb_free_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE,
+			  musb->endpoints[MM81X_EP_INT].buffer,
+			  urb->transfer_dma);
+error_set_urb_null:
+	musb->endpoints[MM81X_EP_INT].urb = NULL;
+	usb_free_urb(urb);
+out:
+	return ret;
+}
+
+static void mm81x_usb_int_stop(struct mm81x *mors)
+{
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+
+	usb_kill_urb(musb->endpoints[MM81X_EP_INT].urb);
+	cancel_work_sync(&mors->usb_irq_work);
+}
+
+static void mm81x_usb_cmd_callback(struct urb *urb)
+{
+	struct mm81x *mors = urb->context;
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+
+	/* sync/async unlink faults aren't errors */
+	if (urb->status) {
+		if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
+		      urb->status == -ESHUTDOWN))
+			dev_err(mors->dev,
+				"nonzero write bulk status received: %d",
+				urb->status);
+
+		musb->errors = urb->status;
+	}
+
+	musb->ongoing_cmd = false;
+	wake_up(&musb->rw_in_wait);
+}
+
+static int mm81x_usb_cmd(struct mm81x_usb *musb,
+			 const struct mm81x_usb_command *cmd)
+{
+	int retval = 0;
+	struct mm81x *mors = usb_get_intfdata(musb->interface);
+	struct mm81x_usb_endpoint *ep = &musb->endpoints[MM81X_EP_CMD];
+	size_t writesize = sizeof(*cmd);
+
+	if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+		return -ENODEV;
+
+	memcpy(ep->buffer, cmd, writesize);
+
+	usb_fill_bulk_urb(ep->urb, musb->udev,
+			  usb_sndbulkpipe(musb->udev, ep->addr), ep->buffer,
+			  writesize, mm81x_usb_cmd_callback, mors);
+	ep->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+	musb->ongoing_cmd = true;
+
+	retval = usb_submit_urb(ep->urb, GFP_KERNEL);
+	if (retval) {
+		dev_err(mors->dev, "- failed submitting write urb, error %d",
+			retval);
+
+		goto error;
+	}
+
+	retval = wait_event_interruptible_timeout(
+		musb->rw_in_wait, (!musb->ongoing_cmd),
+		msecs_to_jiffies(URB_TIMEOUT_MS));
+	if (retval < 0) {
+		dev_err(mors->dev, "error waiting for urb %d", retval);
+		goto error;
+	} else if (retval == 0) {
+		dev_err(mors->dev, "timed out waiting for urb");
+		usb_kill_urb(ep->urb);
+		retval = -ETIMEDOUT;
+		goto error;
+	}
+
+	musb->ongoing_cmd = false;
+	return writesize;
+
+error:
+	musb->ongoing_cmd = false;
+	return retval;
+}
+
+static int mm81x_usb_ndr_reset(struct mm81x *mors)
+{
+	int ret;
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+	struct mm81x_usb_command cmd;
+
+	mutex_lock(&musb->lock);
+
+	musb->ongoing_rw = true;
+	musb->errors = 0;
+
+	cmd.dir = cpu_to_le32(MM81X_USB_RESET);
+	cmd.address = cpu_to_le32(0);
+	cmd.length = cpu_to_le32(0);
+
+	ret = mm81x_usb_cmd(musb, &cmd);
+	if (ret < 0)
+		dev_err(mors->dev, "mm81x_usb_cmd (MM81X_USB_RESET) error %d\n",
+			ret);
+	else
+		ret = 0;
+
+	musb->ongoing_rw = false;
+	mutex_unlock(&musb->lock);
+	return ret;
+}
+
+static void mm81x_usb_mem_rw_callback(struct urb *urb)
+{
+	struct mm81x *mors = urb->context;
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+
+	/* sync/async unlink faults aren't errors */
+	if (urb->status) {
+		if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
+		      urb->status == -ESHUTDOWN))
+			dev_err(mors->dev,
+				"nonzero write bulk status received: %d",
+				urb->status);
+
+		musb->errors = urb->status;
+	}
+
+	musb->ongoing_rw = false;
+	wake_up(&musb->rw_in_wait);
+}
+
+static int mm81x_usb_mem_read(struct mm81x_usb *musb, u32 address, u8 *data,
+			      ssize_t size)
+{
+	int ret;
+	struct mm81x_usb_command cmd;
+	struct mm81x *mors = usb_get_intfdata(musb->interface);
+
+	if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+		return -ENODEV;
+
+	mutex_lock(&musb->lock);
+
+	musb->ongoing_rw = true;
+	musb->errors = 0;
+
+	/* Send command ahead to prepare for Tokens */
+	cmd.dir = cpu_to_le32(MM81X_USB_READ);
+	cmd.address = cpu_to_le32(address);
+	cmd.length = cpu_to_le32(size);
+
+	ret = mm81x_usb_cmd(musb, &cmd);
+	if (ret < 0) {
+		dev_err(mors->dev, "mm81x_usb_cmd error %d", ret);
+		goto error;
+	}
+
+	/* Let's be fast push the next URB, don't wait until command is done */
+	usb_fill_bulk_urb(
+		musb->endpoints[MM81X_EP_MEM_RD].urb, musb->udev,
+		usb_rcvbulkpipe(musb->udev,
+				musb->endpoints[MM81X_EP_MEM_RD].addr),
+		musb->endpoints[MM81X_EP_MEM_RD].buffer, size,
+		mm81x_usb_mem_rw_callback, mors);
+
+	ret = usb_submit_urb(musb->endpoints[MM81X_EP_MEM_RD].urb, GFP_ATOMIC);
+	if (ret < 0) {
+		dev_err(mors->dev, "failed submitting read urb, error %d", ret);
+		ret = (ret == -ENOMEM) ? ret : -EIO;
+		goto error;
+	}
+
+	ret = wait_event_interruptible_timeout(
+		musb->rw_in_wait, (!musb->ongoing_rw),
+		msecs_to_jiffies(URB_TIMEOUT_MS));
+	if (ret < 0) {
+		dev_err(mors->dev, "wait_event_interruptible: error %d", ret);
+		goto error;
+	} else if (ret == 0) {
+		/* Timed out. */
+		usb_kill_urb(musb->endpoints[MM81X_EP_MEM_RD].urb);
+	}
+
+	if (musb->errors) {
+		ret = musb->errors;
+		dev_err(mors->dev, "mem read error %d", ret);
+		goto error;
+	}
+
+	memcpy(data, musb->endpoints[MM81X_EP_MEM_RD].buffer, size);
+	ret = size;
+
+error:
+	musb->ongoing_rw = false;
+	mutex_unlock(&musb->lock);
+
+	return ret;
+}
+
+static int mm81x_usb_mem_write(struct mm81x_usb *musb, u32 address, u8 *data,
+			       ssize_t size)
+{
+	int ret;
+	struct mm81x_usb_command cmd;
+	struct mm81x *mors = usb_get_intfdata(musb->interface);
+
+	if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+		return -ENODEV;
+
+	mutex_lock(&musb->lock);
+
+	musb->ongoing_rw = true;
+	musb->errors = 0;
+
+	/* Send command ahead to prepare for Tokens */
+	cmd.dir = cpu_to_le32(MM81X_USB_WRITE);
+	cmd.address = cpu_to_le32(address);
+	cmd.length = cpu_to_le32(size);
+	ret = mm81x_usb_cmd(musb, &cmd);
+	if (ret < 0) {
+		dev_err(mors->dev, "mm81x_usb_mem_read error %d", ret);
+		goto error;
+	}
+
+	memcpy(musb->endpoints[MM81X_EP_MEM_WR].buffer, data, size);
+
+	/* prepare a read */
+	usb_fill_bulk_urb(
+		musb->endpoints[MM81X_EP_MEM_WR].urb, musb->udev,
+		usb_sndbulkpipe(musb->udev,
+				musb->endpoints[MM81X_EP_MEM_WR].addr),
+		musb->endpoints[MM81X_EP_MEM_WR].buffer, size,
+		mm81x_usb_mem_rw_callback, mors);
+
+	ret = usb_submit_urb(musb->endpoints[MM81X_EP_MEM_WR].urb, GFP_ATOMIC);
+	if (ret < 0) {
+		dev_err(mors->dev, "- failed submitting write urb, error %d",
+			ret);
+		ret = (ret == -ENOMEM) ? ret : -EIO;
+		goto error;
+	}
+
+	ret = wait_event_interruptible_timeout(
+		musb->rw_in_wait, (!musb->ongoing_rw),
+		msecs_to_jiffies(URB_TIMEOUT_MS));
+	if (ret < 0) {
+		dev_err(mors->dev, "error %d", ret);
+		goto error;
+	} else if (ret == 0) {
+		/* Timed out. */
+		usb_kill_urb(musb->endpoints[MM81X_EP_MEM_WR].urb);
+	}
+
+	if (musb->errors) {
+		ret = musb->errors;
+		dev_err(mors->dev, "error %d", ret);
+		goto error;
+	}
+
+	ret = size;
+
+error:
+	musb->ongoing_rw = false;
+	mutex_unlock(&musb->lock);
+	return ret;
+}
+
+static int mm81x_usb_dm_read(struct mm81x *mors, u32 address, u8 *data, int len)
+{
+	ssize_t offset = 0;
+	int ret;
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+
+	while (offset < len) {
+		ret = mm81x_usb_mem_read(musb, address + offset,
+					 (u8 *)(data + offset),
+					 min((ssize_t)(len - offset),
+					     (ssize_t)USB_MAX_TRANSFER_SIZE));
+		if (ret < 0) {
+			dev_err(mors->dev, "%s failed (errno=%d)", __func__,
+				ret);
+			return ret;
+		}
+
+		offset += ret;
+	}
+
+	return 0;
+}
+
+static int mm81x_usb_dm_write(struct mm81x *mors, u32 address, const u8 *data,
+			      int len)
+{
+	ssize_t offset = 0;
+	int ret;
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+
+	while (offset < len) {
+		ret = mm81x_usb_mem_write(musb, address + offset,
+					  (u8 *)(data + offset),
+					  min((ssize_t)(len - offset),
+					      (ssize_t)USB_MAX_TRANSFER_SIZE));
+		if (ret < 0) {
+			dev_err(mors->dev, "%s failed (errno=%d)", __func__,
+				ret);
+			return ret;
+		}
+
+		offset += ret;
+	}
+
+	return 0;
+}
+
+static int mm81x_usb_reg32_read(struct mm81x *mors, u32 address, u32 *val)
+{
+	int ret = 0;
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+
+	ret = mm81x_usb_mem_read(musb, address, (u8 *)val, sizeof(*val));
+	if (ret == sizeof(*val)) {
+		*val = le32_to_cpup((__le32 *)val);
+		return 0;
+	}
+
+	dev_err(mors->dev, "usb reg32 read failed %d", ret);
+	return ret;
+}
+
+static int mm81x_usb_reg32_write(struct mm81x *mors, u32 address, u32 val)
+{
+	int ret = 0;
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+	__le32 val_le = cpu_to_le32(val);
+
+	ret = mm81x_usb_mem_write(musb, address, (u8 *)&val_le, sizeof(val_le));
+	if (ret == sizeof(val_le))
+		return 0;
+
+	dev_err(mors->dev, "usb reg32 write failed %d", ret);
+	return ret;
+}
+
+static void mm81x_usb_bus_enable(struct mm81x *mors, bool enable)
+{
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+
+	if (enable)
+		usb_autopm_get_interface(musb->interface);
+	else
+		usb_autopm_put_interface(musb->interface);
+}
+
+static void mm81x_usb_claim_bus(struct mm81x *mors)
+{
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+
+	mutex_lock(&musb->bus_lock);
+}
+
+static void mm81x_usb_release_bus(struct mm81x *mors)
+{
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+
+	mutex_unlock(&musb->bus_lock);
+}
+
+static void mm81x_usb_set_irq(struct mm81x *mors, bool enable)
+{
+}
+
+static const struct mm81x_bus_ops mm81x_usb_ops = {
+	.dm_read = mm81x_usb_dm_read,
+	.dm_write = mm81x_usb_dm_write,
+	.reg32_read = mm81x_usb_reg32_read,
+	.reg32_write = mm81x_usb_reg32_write,
+	.digital_reset = mm81x_usb_ndr_reset,
+	.set_bus_enable = mm81x_usb_bus_enable,
+	.claim = mm81x_usb_claim_bus,
+	.release = mm81x_usb_release_bus,
+	.set_irq = mm81x_usb_set_irq,
+	.bulk_alignment = MM81X_BUS_DEFAULT_BULK_ALIGNMENT,
+};
+
+static int mm81x_usb_detect_endpoints(struct mm81x *mors,
+				      const struct usb_interface *intf)
+{
+	int ret;
+	unsigned int i;
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+	struct usb_endpoint_descriptor *ep_desc;
+	struct usb_host_interface *intf_desc = intf->cur_altsetting;
+
+	for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) {
+		ep_desc = &intf_desc->endpoint[i].desc;
+
+		if (usb_endpoint_is_bulk_in(ep_desc)) {
+			if (!musb->endpoints[MM81X_EP_MEM_RD].addr) {
+				musb->endpoints[MM81X_EP_MEM_RD].addr =
+					usb_endpoint_num(ep_desc);
+				musb->endpoints[MM81X_EP_MEM_RD].size =
+					usb_endpoint_maxp(ep_desc);
+			} else if (!musb->endpoints[MM81X_EP_REG_RD].addr) {
+				musb->endpoints[MM81X_EP_REG_RD].addr =
+					usb_endpoint_num(ep_desc);
+				musb->endpoints[MM81X_EP_REG_RD].size =
+					usb_endpoint_maxp(ep_desc);
+			}
+		} else if (usb_endpoint_is_bulk_out(ep_desc)) {
+			if (!musb->endpoints[MM81X_EP_MEM_WR].addr) {
+				musb->endpoints[MM81X_EP_MEM_WR].addr =
+					usb_endpoint_num(ep_desc);
+				musb->endpoints[MM81X_EP_MEM_WR].size =
+					usb_endpoint_maxp(ep_desc);
+			} else if (!musb->endpoints[MM81X_EP_REG_WR].addr) {
+				musb->endpoints[MM81X_EP_REG_WR].addr =
+					usb_endpoint_num(ep_desc);
+				musb->endpoints[MM81X_EP_REG_WR].size =
+					usb_endpoint_maxp(ep_desc);
+			}
+		} else if (usb_endpoint_is_int_in(ep_desc)) {
+			musb->endpoints[MM81X_EP_INT].addr =
+				usb_endpoint_num(ep_desc);
+			musb->endpoints[MM81X_EP_INT].size =
+				usb_endpoint_maxp(ep_desc);
+		}
+	}
+
+	dev_dbg(mors->dev, "\tMemory Endpoint IN %s detected: %u size %u",
+		musb->endpoints[MM81X_EP_MEM_RD].addr ? "" : "not",
+		musb->endpoints[MM81X_EP_MEM_RD].addr,
+		musb->endpoints[MM81X_EP_MEM_RD].size);
+	dev_dbg(mors->dev, "\tMemory Endpoint OUT %s detected: %u size %u",
+		musb->endpoints[MM81X_EP_MEM_WR].addr ? "" : "not",
+		musb->endpoints[MM81X_EP_MEM_WR].addr,
+		musb->endpoints[MM81X_EP_MEM_WR].size);
+	dev_dbg(mors->dev, "\tRegister Endpoint IN %s detected: %u",
+		musb->endpoints[MM81X_EP_REG_RD].addr ? "" : "not",
+		musb->endpoints[MM81X_EP_REG_RD].addr);
+	dev_dbg(mors->dev, "\tRegister Endpoint OUT %s detected: %u",
+		musb->endpoints[MM81X_EP_REG_WR].addr ? "" : "not",
+		musb->endpoints[MM81X_EP_REG_WR].addr);
+	dev_dbg(mors->dev, "\tStats IN endpoint %s detected: %u",
+		musb->endpoints[MM81X_EP_INT].addr ? "" : "not",
+		musb->endpoints[MM81X_EP_INT].addr);
+
+	/* Verify we have an IN and OUT */
+	if (!(musb->endpoints[MM81X_EP_MEM_RD].addr &&
+	      musb->endpoints[MM81X_EP_MEM_WR].addr))
+		return -ENODEV;
+
+	/* Verify the stats MM81X_EP_INT is detected */
+	if (!musb->endpoints[MM81X_EP_INT].addr)
+		return -ENODEV;
+
+	/* Verify minimum interrupt status read */
+	if (musb->endpoints[MM81X_EP_INT].size < 8)
+		return -ENODEV;
+
+	musb->endpoints[MM81X_EP_CMD].urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!musb->endpoints[MM81X_EP_CMD].urb) {
+		ret = -ENOMEM;
+		goto err_ep;
+	}
+
+	musb->endpoints[MM81X_EP_MEM_RD].urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!musb->endpoints[MM81X_EP_MEM_RD].urb) {
+		ret = -ENOMEM;
+		goto err_ep;
+	}
+
+	musb->endpoints[MM81X_EP_MEM_WR].urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!musb->endpoints[MM81X_EP_MEM_WR].urb) {
+		ret = -ENOMEM;
+		goto err_ep;
+	}
+
+	musb->endpoints[MM81X_EP_MEM_RD].buffer =
+		kmalloc(USB_MAX_TRANSFER_SIZE, GFP_KERNEL);
+	if (!musb->endpoints[MM81X_EP_MEM_RD].buffer) {
+		ret = -ENOMEM;
+		goto err_ep;
+	}
+
+	musb->endpoints[MM81X_EP_MEM_WR].buffer =
+		kmalloc(USB_MAX_TRANSFER_SIZE, GFP_KERNEL);
+	if (!musb->endpoints[MM81X_EP_MEM_WR].buffer) {
+		ret = -ENOMEM;
+		goto err_ep;
+	}
+
+	musb->endpoints[MM81X_EP_CMD].buffer = usb_alloc_coherent(
+		musb->udev, sizeof(struct mm81x_usb_command), GFP_KERNEL,
+		&musb->endpoints[MM81X_EP_CMD].urb->transfer_dma);
+
+	if (!musb->endpoints[MM81X_EP_CMD].buffer) {
+		ret = -ENOMEM;
+		goto err_ep;
+	}
+
+	/* Assign command to memory out end point */
+	musb->endpoints[MM81X_EP_CMD].addr =
+		musb->endpoints[MM81X_EP_MEM_WR].addr;
+	musb->endpoints[MM81X_EP_CMD].size =
+		musb->endpoints[MM81X_EP_MEM_WR].size;
+
+	return 0;
+
+err_ep:
+	if (musb->endpoints[MM81X_EP_CMD].urb &&
+	    musb->endpoints[MM81X_EP_CMD].buffer)
+		usb_free_coherent(
+			musb->udev, sizeof(struct mm81x_usb_command),
+			musb->endpoints[MM81X_EP_CMD].buffer,
+			musb->endpoints[MM81X_EP_CMD].urb->transfer_dma);
+	usb_free_urb(musb->endpoints[MM81X_EP_MEM_RD].urb);
+	usb_free_urb(musb->endpoints[MM81X_EP_CMD].urb);
+	usb_free_urb(musb->endpoints[MM81X_EP_MEM_WR].urb);
+	kfree(musb->endpoints[MM81X_EP_MEM_RD].buffer);
+	kfree(musb->endpoints[MM81X_EP_MEM_WR].buffer);
+
+	return ret;
+}
+
+static void mm81x_urb_cleanup(struct mm81x *mors)
+{
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+	struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
+	struct mm81x_usb_endpoint *rd_ep = &musb->endpoints[MM81X_EP_MEM_RD];
+	struct mm81x_usb_endpoint *wr_ep = &musb->endpoints[MM81X_EP_MEM_WR];
+	struct mm81x_usb_endpoint *cmd_ep = &musb->endpoints[MM81X_EP_CMD];
+
+	usb_kill_urb(rd_ep->urb);
+	usb_kill_urb(wr_ep->urb);
+	usb_kill_urb(cmd_ep->urb);
+
+	if (int_ep->urb)
+		usb_free_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE,
+				  int_ep->buffer, int_ep->urb->transfer_dma);
+
+	if (cmd_ep->urb)
+		usb_free_coherent(musb->udev, sizeof(struct mm81x_usb_command),
+				  cmd_ep->buffer, cmd_ep->urb->transfer_dma);
+
+	kfree(wr_ep->buffer);
+	kfree(rd_ep->buffer);
+
+	usb_free_urb(int_ep->urb);
+	usb_free_urb(wr_ep->urb);
+	usb_free_urb(rd_ep->urb);
+	usb_free_urb(cmd_ep->urb);
+}
+
+static int mm81x_usb_probe(struct usb_interface *interface,
+			   const struct usb_device_id *id)
+{
+	int ret;
+	struct mm81x *mors;
+	struct mm81x_usb *musb;
+
+	mors = mm81x_core_alloc(sizeof(*musb), &interface->dev);
+	if (!mors)
+		return -ENOMEM;
+
+	mors->bus_ops = &mm81x_usb_ops;
+	mors->bus_type = MM81X_BUS_TYPE_USB;
+
+	musb = (struct mm81x_usb *)mors->drv_priv;
+	musb->udev = usb_get_dev(interface_to_usbdev(interface));
+	musb->interface = usb_get_intf(interface);
+
+	mutex_init(&musb->lock);
+	mutex_init(&musb->bus_lock);
+	init_waitqueue_head(&musb->rw_in_wait);
+	usb_set_intfdata(interface, mors);
+
+	ret = mm81x_usb_detect_endpoints(mors, interface);
+	if (ret < 0)
+		goto err_core_free;
+
+	set_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags);
+
+	ret = mm81x_core_init(mors);
+	if (ret)
+		goto err_urb_cleanup;
+
+	INIT_WORK(&mors->usb_irq_work, mm81x_usb_irq_work);
+
+	ret = mm81x_usb_int_enable(mors);
+	if (ret)
+		goto err_core_deinit;
+
+	ret = mm81x_core_register(mors);
+	if (ret)
+		goto err_usb_int_stop;
+
+	/* USB requires remote wakeup functionality for suspend */
+	clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
+	musb->interface->needs_remote_wakeup = 1;
+	usb_enable_autosuspend(musb->udev);
+	pm_runtime_set_autosuspend_delay(&musb->udev->dev,
+					 PM_RUNTIME_AUTOSUSPEND_DELAY_MS);
+
+	usb_autopm_get_interface(interface);
+	return 0;
+
+err_usb_int_stop:
+	mm81x_usb_int_stop(mors);
+err_core_deinit:
+	mm81x_core_deinit(mors);
+err_urb_cleanup:
+	mm81x_urb_cleanup(mors);
+err_core_free:
+	mm81x_core_free(mors);
+	usb_put_intf(interface);
+	usb_put_dev(interface_to_usbdev(interface));
+	return ret;
+}
+
+static void mm81x_usb_disconnect(struct usb_interface *interface)
+{
+	struct mm81x *mors = usb_get_intfdata(interface);
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+	int minor = interface->minor;
+	struct usb_device *udev = interface_to_usbdev(interface);
+
+	if (udev->state == USB_STATE_NOTATTACHED) {
+		clear_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags);
+		set_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags);
+		dev_dbg(mors->dev, "USB suddenly unplugged");
+	}
+
+	usb_disable_autosuspend(udev);
+
+	if (test_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags)) {
+		dev_dbg(mors->dev, "USB was suspended: release locks");
+		mm81x_usb_release_bus(mors);
+		mutex_unlock(&musb->lock);
+	}
+
+	clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
+
+	mm81x_core_unregister(mors);
+	mm81x_usb_int_stop(mors);
+	mm81x_core_deinit(mors);
+	mm81x_urb_cleanup(mors);
+	mm81x_core_free(mors);
+
+	usb_autopm_put_interface(interface);
+	usb_set_intfdata(interface, NULL);
+	dev_info(&interface->dev, "USB Morse #%d now disconnected", minor);
+	usb_put_intf(interface);
+	usb_put_dev(udev);
+}
+
+static int mm81x_usb_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct mm81x *mors = usb_get_intfdata(intf);
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+	struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
+	struct mm81x_usb_endpoint *rd_ep = &musb->endpoints[MM81X_EP_MEM_RD];
+	struct mm81x_usb_endpoint *wr_ep = &musb->endpoints[MM81X_EP_MEM_WR];
+	struct mm81x_usb_endpoint *cmd_ep = &musb->endpoints[MM81X_EP_CMD];
+
+	if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+		return -ENODEV;
+
+	usb_kill_urb(int_ep->urb);
+	usb_kill_urb(rd_ep->urb);
+	usb_kill_urb(wr_ep->urb);
+	usb_kill_urb(cmd_ep->urb);
+
+	/* Locking the bus. No USB communication after this point */
+	mm81x_usb_claim_bus(mors);
+	mutex_lock(&musb->lock);
+
+	set_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
+	return 0;
+}
+
+static int mm81x_usb_resume(struct usb_interface *intf)
+{
+	struct mm81x *mors = usb_get_intfdata(intf);
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+	int ret;
+	struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
+
+	if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+		return -ENODEV;
+
+	ret = usb_submit_urb(int_ep->urb, GFP_KERNEL);
+	if (ret)
+		dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret);
+
+	mm81x_usb_release_bus(mors);
+	mutex_unlock(&musb->lock);
+
+	clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
+	return 0;
+}
+
+static int mm81x_usb_reset_resume(struct usb_interface *intf)
+{
+	struct mm81x *mors = usb_get_intfdata(intf);
+	struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
+	int ret;
+	struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
+
+	if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+		return -ENODEV;
+
+	ret = usb_submit_urb(int_ep->urb, GFP_KERNEL);
+	if (ret)
+		dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret);
+
+	mm81x_usb_release_bus(mors);
+	mutex_unlock(&musb->lock);
+
+	clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
+
+	return 0;
+}
+
+static int mm81x_usb_pre_reset(struct usb_interface *intf)
+{
+	return 0;
+}
+
+static int mm81x_usb_post_reset(struct usb_interface *intf)
+{
+	return 0;
+}
+
+static struct usb_driver mm81x_usb_driver = {
+	.name = "mm81x_usb",
+	.probe = mm81x_usb_probe,
+	.disconnect = mm81x_usb_disconnect,
+	.suspend = mm81x_usb_suspend,
+	.resume = mm81x_usb_resume,
+	.reset_resume = mm81x_usb_reset_resume,
+	.pre_reset = mm81x_usb_pre_reset,
+	.post_reset = mm81x_usb_post_reset,
+	.id_table = mm81x_usb_table,
+	.supports_autosuspend = 1,
+	.soft_unbind = 1,
+};
+
+module_usb_driver(mm81x_usb_driver);
+
+MODULE_AUTHOR("Morse Micro");
+MODULE_DESCRIPTION("Driver support for Morse Micro MM81X USB devices");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps.c b/drivers/net/wireless/morsemicro/mm81x/yaps.c
new file mode 100644
index 00000000000000..bdadb822bf9a1b
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/yaps.c
@@ -0,0 +1,704 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include <linux/gpio.h>
+#include <linux/random.h>
+#include <linux/timer.h>
+#include <linux/bitops.h>
+#include <linux/slab.h>
+#include "hif.h"
+#include "ps.h"
+#include "bus.h"
+#include "command.h"
+#include "skbq.h"
+
+/* This is a fail safe timeout */
+#define CHIP_FULL_RECOVERY_TIMEOUT_MS 30
+
+/* Defined as the max number of MPDUs per AMPDU */
+#define MAX_PKTS_PER_TX_TXN 16
+#define MAX_PKTS_PER_RX_TXN 32
+
+static int mm81x_yaps_alloc_pkt_buffers(struct mm81x_yaps *yaps)
+{
+	yaps->hw.to_chip_pkts = kcalloc(MAX_PKTS_PER_TX_TXN,
+					sizeof(*yaps->hw.to_chip_pkts),
+					GFP_KERNEL);
+	if (!yaps->hw.to_chip_pkts)
+		return -ENOMEM;
+
+	yaps->hw.from_chip_pkts = kcalloc(MAX_PKTS_PER_RX_TXN,
+					  sizeof(*yaps->hw.from_chip_pkts),
+					  GFP_KERNEL);
+	if (!yaps->hw.from_chip_pkts) {
+		kfree(yaps->hw.to_chip_pkts);
+		yaps->hw.to_chip_pkts = NULL;
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static void mm81x_yaps_free_pkt_buffers(struct mm81x_yaps *yaps)
+{
+	kfree(yaps->hw.from_chip_pkts);
+	yaps->hw.from_chip_pkts = NULL;
+	kfree(yaps->hw.to_chip_pkts);
+	yaps->hw.to_chip_pkts = NULL;
+}
+
+static int mm81x_yaps_write_pkts(struct mm81x_yaps *yaps,
+				 struct mm81x_yaps_pkt *pkts, int num_pkts,
+				 int *num_pkts_sent)
+{
+	return yaps->ops->write_pkts(yaps, pkts, num_pkts, num_pkts_sent);
+}
+
+static int mm81x_yaps_read_pkts(struct mm81x_yaps *yaps,
+				struct mm81x_yaps_pkt *pkts, int num_pkts_max,
+				int *num_pkts_received)
+{
+	return yaps->ops->read_pkts(yaps, pkts, num_pkts_max,
+				    num_pkts_received);
+}
+
+static int mm81x_yaps_update_status(struct mm81x_yaps *yaps)
+{
+	return yaps->ops->update_status(yaps);
+}
+
+/* Mappings between sk_buff, skbq and yaps */
+static struct mm81x_skbq *mm81x_yaps_tc_q_from_aci(struct mm81x *mors, int aci)
+{
+	struct mm81x_yaps *yaps = &mors->hif.u.yaps;
+
+	if (aci >= ARRAY_SIZE(yaps->data_tx_qs))
+		return NULL;
+	return &yaps->data_tx_qs[aci];
+}
+
+static void mm81x_yaps_get_tx_qs(struct mm81x *mors, struct mm81x_skbq **qs,
+				 int *num_qs)
+{
+	*qs = mors->hif.u.yaps.data_tx_qs;
+	*num_qs = YAPS_TX_SKBQ_MAX;
+}
+
+static struct mm81x_skbq *mm81x_yaps_get_bcn_tc_q(struct mm81x *mors)
+{
+	return &mors->hif.u.yaps.beacon_q;
+}
+
+static struct mm81x_skbq *mm81x_yaps_get_mgmt_tc_q(struct mm81x *mors)
+{
+	return &mors->hif.u.yaps.mgmt_q;
+}
+
+static struct mm81x_skbq *mm81x_yaps_get_tx_cmd_queue(struct mm81x *mors)
+{
+	return &mors->hif.u.yaps.cmd_q;
+}
+
+static int mm81x_yaps_irq_handler(struct mm81x *mors, u32 status)
+{
+	if (status & BIT(MM81X_INT_YAPS_FC_PKT_WAITING_IRQN))
+		set_bit(MM81X_HIF_EVT_RX_PEND, &mors->hif.event_flags);
+
+	if (status & BIT(MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN)) {
+		timer_delete_sync_try(&mors->hif.u.yaps.chip_queue_full.timer);
+		set_bit(MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND,
+			&mors->hif.event_flags);
+	}
+
+	queue_work(mors->chip_wq, &mors->hif_work);
+	return 0;
+}
+
+const struct mm81x_hif_ops mm81x_yaps_ops = {
+	.init = mm81x_yaps_init,
+	.flush_tx_data = mm81x_yaps_flush_tx_data,
+	.flush_cmds = mm81x_yaps_flush_cmds,
+	.get_tx_status_pending_count = mm81x_yaps_get_tx_status_pending_count,
+	.get_tx_buffered_count = mm81x_yaps_get_tx_buffered_count,
+	.finish = mm81x_yaps_finish,
+	.skbq_get_tx_qs = mm81x_yaps_get_tx_qs,
+	.get_tx_beacon_queue = mm81x_yaps_get_bcn_tc_q,
+	.get_tx_mgmt_queue = mm81x_yaps_get_mgmt_tc_q,
+	.get_tx_cmd_queue = mm81x_yaps_get_tx_cmd_queue,
+	.get_tx_data_queue = mm81x_yaps_tc_q_from_aci,
+	.handle_irq = mm81x_yaps_irq_handler
+};
+
+static int mm81x_yaps_read_pkt(struct mm81x_yaps *yaps, struct sk_buff *skb)
+{
+	struct mm81x *mors = yaps->mors;
+	struct sk_buff_head skbq;
+	struct mm81x_skbq *mq = NULL;
+	struct mm81x_skb_hdr *hdr;
+	int skb_bytes_remaining;
+	int skb_len;
+	int ret = 0;
+
+	if (!skb) {
+		ret = -EINVAL;
+		goto exit_return_page;
+	}
+
+	__skb_queue_head_init(&skbq);
+
+	hdr = (struct mm81x_skb_hdr *)skb->data;
+	if (hdr->sync != MM81X_SKB_HEADER_SYNC) {
+		dev_err(mors->dev, "sync value error [0xAA:%d], hdr.len %d",
+			hdr->sync, hdr->len);
+		ret = -EIO;
+		goto exit_return_page;
+	}
+
+	if (yaps->mors->hif.validate_skb_checksum &&
+	    !mm81x_skbq_validate_checksum(skb->data)) {
+		dev_dbg(yaps->mors->dev,
+			"SKB checksum is invalid hdr:[c:%02X s:%02X len:%d]",
+			hdr->channel, hdr->sync, hdr->len);
+
+		if (hdr->channel != MM81X_SKB_CHAN_TX_STATUS) {
+			ret = -EIO;
+			goto exit;
+		}
+	}
+
+	switch (hdr->channel) {
+	case MM81X_SKB_CHAN_DATA:
+	case MM81X_SKB_CHAN_NDP_FRAMES:
+	case MM81X_SKB_CHAN_TX_STATUS:
+	case MM81X_SKB_CHAN_DATA_NOACK:
+	case MM81X_SKB_CHAN_BEACON:
+	case MM81X_SKB_CHAN_MGMT:
+		mq = &yaps->data_rx_q;
+		break;
+	case MM81X_SKB_CHAN_COMMAND:
+		mq = &yaps->cmd_resp_q;
+		break;
+	default:
+		dev_err(mors->dev, "channel value error [%d]", hdr->channel);
+		ret = -EIO;
+		goto exit_return_page;
+	}
+
+	skb_len = sizeof(*hdr) + hdr->offset + le16_to_cpu(hdr->len);
+	skb_bytes_remaining = mm81x_skbq_space(mq);
+
+	if (skb_len > skb_bytes_remaining) {
+		dev_err(mors->dev,
+			"Page will not fit in SKBQ, dropping - len %d remain %d",
+			skb_len, skb_bytes_remaining);
+		ret = -ENOMEM;
+		/* Queue work to clear backlog */
+		queue_work(mors->net_wq, &mq->dispatch_work);
+		goto exit_return_page;
+	}
+
+	skb_trim(skb, skb_len);
+	__skb_queue_tail(&skbq, skb);
+
+	if (skb_queue_len(&skbq))
+		mm81x_skbq_enq(mq, &skbq);
+
+	/* push packets up in a different context */
+	queue_work(mors->net_wq, &mq->dispatch_work);
+
+	goto exit;
+
+exit_return_page:
+	if (ret && mq) {
+		dev_err(mors->dev, "failed %d", ret);
+		mm81x_skbq_purge(mq, &skbq);
+		goto exit;
+	}
+
+exit:
+	if (ret && skb)
+		dev_kfree_skb(skb);
+
+	return ret;
+}
+
+static int mm81x_yaps_tx(struct mm81x_yaps *yaps, struct mm81x_skbq *mq)
+{
+	int i;
+	int ret = 0;
+	int num_skbs = 0;
+	int tc_pkt_idx = 0;
+	int num_pkts_sent = 0;
+	struct sk_buff *skb;
+	struct sk_buff_head skbq_to_send;
+	struct sk_buff_head skbq_sent;
+	struct sk_buff_head skbq_failed;
+	struct sk_buff *pfirst, *pnext;
+	struct mm81x *mors = yaps->mors;
+	struct mm81x_skb_hdr *hdr;
+
+	/* Check there is something on the queue */
+	spin_lock_bh(&mq->lock);
+	skb = skb_peek(&mq->skbq);
+	spin_unlock_bh(&mq->lock);
+	if (!skb)
+		return 0;
+
+	__skb_queue_head_init(&skbq_to_send);
+	__skb_queue_head_init(&skbq_sent);
+	__skb_queue_head_init(&skbq_failed);
+
+	if (mq == &yaps->cmd_q)
+		/* Purge timed-out commands (this should not happen) */
+		mm81x_skbq_purge(mq, &mq->pending);
+	else if (mq == &yaps->mgmt_q && skb_queue_len(&mq->skbq) > 0)
+		/*
+		 * Purge old mgmt frames that have not been sent due to
+		 * congestion
+		 */
+		mm81x_skbq_purge_aged(mors, mq);
+
+	num_skbs =
+		mm81x_skbq_deq_num_skb(mq, &skbq_to_send, MAX_PKTS_PER_TX_TXN);
+
+	skb_queue_walk_safe(&skbq_to_send, pfirst, pnext) {
+		enum mm81x_yaps_to_chip_q tc_queue;
+
+		hdr = (struct mm81x_skb_hdr *)pfirst->data;
+		switch (hdr->channel) {
+		case MM81X_SKB_CHAN_COMMAND:
+			tc_queue = MM81X_YAPS_CMD_Q;
+			break;
+		case MM81X_SKB_CHAN_BEACON:
+			tc_queue = MM81X_YAPS_BEACON_Q;
+			break;
+		case MM81X_SKB_CHAN_MGMT:
+			tc_queue = MM81X_YAPS_MGMT_Q;
+			break;
+		default:
+			tc_queue = MM81X_YAPS_TX_Q;
+			break;
+		}
+		yaps->hw.to_chip_pkts[tc_pkt_idx].tc_queue = tc_queue;
+		yaps->hw.to_chip_pkts[tc_pkt_idx].skb = pfirst;
+		tc_pkt_idx++;
+	}
+
+	/* Send queued packets to chip */
+	ret = mm81x_yaps_update_status(yaps);
+	if (ret)
+		return ret;
+
+	ret = mm81x_yaps_write_pkts(yaps, yaps->hw.to_chip_pkts, tc_pkt_idx,
+				    &num_pkts_sent);
+
+	/* Move sent packets to done queue */
+	for (i = 0; i < num_pkts_sent; ++i) {
+		pfirst = __skb_dequeue(&skbq_to_send);
+		__skb_queue_tail(&skbq_sent, pfirst);
+	}
+
+	for (i = num_pkts_sent; i < num_skbs; ++i) {
+		pfirst = __skb_dequeue(&skbq_to_send);
+		__skb_queue_tail(&skbq_failed, pfirst);
+	}
+
+	if (skb_queue_len(&skbq_failed) > 0) {
+		mm81x_skbq_enq_prepend(mq, &skbq_failed);
+
+		/* queue full, can't requeue */
+		if (skb_queue_len(&skbq_failed) > 0) {
+			dev_warn(mors->dev,
+				 "can't requeue failed pkts, purging");
+			__skb_queue_purge(&skbq_failed);
+		}
+	}
+
+	if (skb_queue_len(&skbq_sent) > 0)
+		mm81x_skbq_tx_complete(mq, &skbq_sent);
+
+	return ret;
+}
+
+/* Returns true if there are TX data pages waiting to be sent */
+static bool mm81x_yaps_tx_data_handler(struct mm81x_yaps *yaps)
+{
+	s16 aci;
+	u32 count = 0;
+	struct mm81x *mors = yaps->mors;
+
+	for (aci = MM81X_ACI_VO; aci >= 0; aci--) {
+		struct mm81x_skbq *data_q = mm81x_yaps_tc_q_from_aci(mors, aci);
+
+		if (!mm81x_is_data_tx_allowed(mors))
+			break;
+
+		yaps->chip_queue_full.is_full = mm81x_yaps_tx(yaps, data_q);
+		count += mm81x_skbq_count(data_q);
+
+		if (yaps->chip_queue_full.is_full)
+			break;
+
+		if (aci == MM81X_ACI_BE)
+			break;
+	}
+
+	/*
+	 * Data has potentially been transmitted from the data SKBQs.
+	 * If the mac80211 TX data Qs were previously stopped, now would
+	 * be a good time to check if they can be started again.
+	 */
+	mm81x_skbq_may_wake_tx_queues(mors);
+
+	return (count > 0) && mm81x_is_data_tx_allowed(mors);
+}
+
+/* Returns true if there are commands waiting to be sent */
+static bool mm81x_yaps_tx_cmd_handler(struct mm81x_yaps *yaps)
+{
+	struct mm81x_skbq *cmd_q = &yaps->cmd_q;
+
+	mm81x_yaps_tx(yaps, cmd_q);
+
+	return mm81x_skbq_count(cmd_q) > 0;
+}
+
+static bool mm81x_yaps_tx_beacon_handler(struct mm81x_yaps *yaps)
+{
+	struct mm81x_skbq *beacon_q = &yaps->beacon_q;
+
+	mm81x_yaps_tx(yaps, beacon_q);
+
+	return mm81x_skbq_count(beacon_q) > 0;
+}
+
+static bool mm81x_yaps_tx_mgmt_handler(struct mm81x_yaps *yaps)
+{
+	struct mm81x_skbq *mgmt_q = &yaps->mgmt_q;
+
+	mm81x_yaps_tx(yaps, mgmt_q);
+
+	return mm81x_skbq_count(mgmt_q) > 0;
+}
+
+/* Returns true if there are populated RX pages left in the device */
+static bool mm81x_yaps_rx_handler(struct mm81x_yaps *yaps)
+{
+	int ret = 0;
+	int i;
+	int num_pks_received;
+
+	ret = mm81x_yaps_update_status(yaps);
+	if (ret)
+		goto exit;
+
+	ret = mm81x_yaps_read_pkts(yaps, yaps->hw.from_chip_pkts,
+				   MAX_PKTS_PER_RX_TXN, &num_pks_received);
+	if (ret && ret != -EAGAIN) {
+		dev_err(yaps->mors->dev, "YAPS read_pkts fail: %d", ret);
+		goto exit;
+	}
+
+	for (i = 0; i < num_pks_received; ++i) {
+		mm81x_yaps_read_pkt(yaps, yaps->hw.from_chip_pkts[i].skb);
+		yaps->hw.from_chip_pkts[i].skb = NULL;
+	}
+
+exit:
+	if (ret == -ENOMEM || ret == -EAGAIN)
+		return true;
+	else
+		return false;
+}
+
+void mm81x_yaps_stale_tx_work(struct work_struct *work)
+{
+	int i;
+	int flushed = 0;
+	struct mm81x *mors = container_of(work, struct mm81x, tx_stale_work);
+	struct mm81x_yaps *yaps;
+
+	yaps = &mors->hif.u.yaps;
+	flushed += mm81x_skbq_check_for_stale_tx(mors, &yaps->beacon_q);
+	flushed += mm81x_skbq_check_for_stale_tx(mors, &yaps->mgmt_q);
+
+	for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
+		flushed += mm81x_skbq_check_for_stale_tx(mors,
+							 &yaps->data_tx_qs[i]);
+
+	if (!flushed)
+		return;
+
+	dev_dbg(mors->dev, "Flushed %d stale TX SKBs", flushed);
+
+	if (mors->ps.enable && !mors->ps.suspended &&
+	    (mm81x_yaps_get_tx_buffered_count(mors) == 0)) {
+		/* Evaluate ps to check if it was gated on a stale tx status */
+		queue_delayed_work(mors->chip_wq, &mors->ps.delayed_eval_work,
+				   0);
+	}
+}
+
+void mm81x_yaps_work(struct work_struct *work)
+{
+	struct mm81x *mors = container_of(work, struct mm81x, hif_work);
+	unsigned long *flags = &mors->hif.event_flags;
+	struct mm81x_yaps *yaps = &mors->hif.u.yaps;
+
+	if (test_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags))
+		return;
+
+	if (!*flags)
+		return;
+
+	/* Disable power save in case it is running */
+	mm81x_ps_disable(mors);
+	mm81x_claim_bus(mors);
+
+	/*
+	 * Handle any populated RX pages from chip first to
+	 * avoid dropping pkts due to full on-chip buffers.
+	 * Check if all pages were removed, set event flags if not.
+	 */
+	if (test_and_clear_bit(MM81X_HIF_EVT_RX_PEND, flags)) {
+		if (mm81x_yaps_rx_handler(yaps))
+			set_bit(MM81X_HIF_EVT_RX_PEND, flags);
+	}
+
+	/* TX any commands before considering data */
+	if (test_and_clear_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, flags)) {
+		if (mm81x_yaps_tx_cmd_handler(yaps))
+			set_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, flags);
+	}
+
+	/* TX beacons before considering mgmt/data */
+	if (test_and_clear_bit(MM81X_HIF_EVT_TX_BEACON_PEND, flags)) {
+		if (mm81x_yaps_tx_beacon_handler(yaps))
+			set_bit(MM81X_HIF_EVT_TX_BEACON_PEND, flags);
+	}
+
+	/* TX mgmt before considering data */
+	if (test_and_clear_bit(MM81X_HIF_EVT_TX_MGMT_PEND, flags)) {
+		if (mm81x_yaps_tx_mgmt_handler(yaps))
+			set_bit(MM81X_HIF_EVT_TX_MGMT_PEND, flags);
+	}
+
+	/* Pause TX data Qs */
+	if (test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND, flags)) {
+		test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND,
+				   flags);
+		mm81x_skbq_data_traffic_pause(mors);
+	}
+
+	/* Resume TX data Qs  */
+	if (test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND, flags))
+		mm81x_skbq_data_traffic_resume(mors);
+
+	/* Handle chip queue status */
+	if (test_and_clear_bit(MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND, flags))
+		yaps->chip_queue_full.is_full = false;
+
+	/* Check to see if the queue is full or
+	 * long enough has past since the queue was full
+	 */
+	if (yaps->chip_queue_full.is_full &&
+	    time_before(jiffies, yaps->chip_queue_full.retry_expiry))
+		goto exit;
+
+	/* Finally TX any data */
+	if (test_and_clear_bit(MM81X_HIF_EVT_TX_DATA_PEND, flags)) {
+		if (mm81x_yaps_tx_data_handler(yaps))
+			set_bit(MM81X_HIF_EVT_TX_DATA_PEND, flags);
+
+		if (yaps->chip_queue_full.is_full) {
+			yaps->chip_queue_full.retry_expiry =
+				jiffies +
+				msecs_to_jiffies(CHIP_FULL_RECOVERY_TIMEOUT_MS);
+			mod_timer(&yaps->chip_queue_full.timer,
+				  yaps->chip_queue_full.retry_expiry);
+		}
+	}
+
+exit:
+
+	/* Disable power save in case it is running */
+	mm81x_release_bus(mors);
+	mm81x_ps_enable(mors);
+
+	/* Don't requeue work if we are shutting down. */
+	if (yaps->finish)
+		return;
+	/*
+	 * Evaluate all events except MM81X_HIF_EVT_TX_DATA_PEND in case data
+	 * tx queue is full
+	 */
+	if ((*flags) & ~(1 << MM81X_HIF_EVT_TX_DATA_PEND))
+		queue_work(mors->chip_wq, &mors->hif_work);
+	/*
+	 * if data tx queue is not full and the work hasn't been queued let's
+	 * queue it
+	 */
+	else if (!yaps->chip_queue_full.is_full && *flags)
+		queue_work(mors->chip_wq, &mors->hif_work);
+}
+
+int mm81x_yaps_get_tx_status_pending_count(struct mm81x *mors)
+{
+	int i = 0;
+	int count = 0;
+	struct mm81x_yaps *yaps;
+
+	yaps = &mors->hif.u.yaps;
+	count += skb_queue_len(&yaps->beacon_q.pending);
+	count += skb_queue_len(&yaps->mgmt_q.pending);
+	count += skb_queue_len(&yaps->cmd_q.pending);
+
+	for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
+		count += skb_queue_len(&yaps->data_tx_qs[i].pending);
+
+	return count;
+}
+
+int mm81x_yaps_get_tx_buffered_count(struct mm81x *mors)
+{
+	int i = 0;
+	int count = 0;
+	struct mm81x_yaps *yaps;
+
+	yaps = &mors->hif.u.yaps;
+	count += skb_queue_len(&yaps->beacon_q.skbq) +
+		 skb_queue_len(&yaps->beacon_q.pending);
+	count += skb_queue_len(&yaps->mgmt_q.skbq) +
+		 skb_queue_len(&yaps->mgmt_q.pending);
+	count += skb_queue_len(&yaps->cmd_q.skbq) +
+		 skb_queue_len(&yaps->cmd_q.pending);
+
+	for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
+		count += mm81x_skbq_count_tx_ready(&yaps->data_tx_qs[i]) +
+			 skb_queue_len(&yaps->data_tx_qs[i].pending);
+
+	return count;
+}
+
+static void mm81x_yaps_tx_q_full_timer(struct timer_list *t)
+{
+	struct mm81x_yaps *yaps =
+		timer_container_of(yaps, t, chip_queue_full.timer);
+
+	queue_work(yaps->mors->chip_wq, &yaps->mors->hif_work);
+}
+
+static void mm81x_yaps_q_chip_full_timer_init(struct mm81x_yaps *yaps)
+{
+	timer_setup(&yaps->chip_queue_full.timer, mm81x_yaps_tx_q_full_timer,
+		    0);
+}
+
+static void mm81x_yaps_q_chip_full_timer_finish(struct mm81x_yaps *yaps)
+{
+	timer_delete_sync_try(&yaps->chip_queue_full.timer);
+}
+
+int mm81x_yaps_init(struct mm81x *mors)
+{
+	int i, ret;
+	struct mm81x_yaps *yaps;
+
+	ret = mm81x_yaps_hw_init(mors);
+	if (ret) {
+		dev_err(mors->dev, "mm81x_yaps_hw_init failed %d", ret);
+		return ret;
+	}
+
+	yaps = &mors->hif.u.yaps;
+	yaps->mors = mors;
+
+	mm81x_claim_bus(mors);
+
+	ret = mm81x_yaps_alloc_pkt_buffers(yaps);
+	if (ret) {
+		dev_err(mors->dev, "Failed to allocate YAPS packet buffers: %d",
+			ret);
+		mm81x_yaps_hw_finish(mors);
+		mm81x_release_bus(mors);
+		return ret;
+	}
+
+	/* YAPS is bi-directional */
+	mm81x_skbq_init(mors, &yaps->data_rx_q,
+			MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);
+	mm81x_skbq_init(mors, &yaps->beacon_q,
+			MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);
+	mm81x_skbq_init(mors, &yaps->mgmt_q,
+			MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);
+
+	for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) {
+		mm81x_skbq_init(mors, &yaps->data_tx_qs[i],
+				MM81X_HIF_FLAGS_DATA |
+					MM81X_HIF_FLAGS_DIR_TO_CHIP);
+	}
+
+	mm81x_skbq_init(mors, &yaps->cmd_q,
+			MM81X_HIF_FLAGS_COMMAND | MM81X_HIF_FLAGS_DIR_TO_CHIP);
+	mm81x_skbq_init(mors, &yaps->cmd_resp_q,
+			MM81X_HIF_FLAGS_COMMAND | MM81X_HIF_FLAGS_DIR_TO_HOST);
+
+	mm81x_yaps_q_chip_full_timer_init(yaps);
+	INIT_WORK(&mors->hif_work, mm81x_yaps_work);
+	INIT_WORK(&mors->tx_stale_work, mm81x_yaps_stale_tx_work);
+	mm81x_release_bus(mors);
+	mm81x_hw_enable_stop_notifications(mors, true);
+	return 0;
+}
+
+void mm81x_yaps_finish(struct mm81x *mors)
+{
+	int i;
+	struct mm81x_yaps *yaps;
+
+	mm81x_yaps_hw_enable_irqs(mors, false);
+
+	yaps = &mors->hif.u.yaps;
+	yaps->finish = true;
+
+	mm81x_skbq_finish(&yaps->data_rx_q);
+	mm81x_skbq_finish(&yaps->beacon_q);
+	mm81x_skbq_finish(&yaps->mgmt_q);
+
+	for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
+		mm81x_skbq_finish(&yaps->data_tx_qs[i]);
+
+	mm81x_skbq_finish(&yaps->cmd_q);
+	mm81x_skbq_finish(&yaps->cmd_resp_q);
+
+	mm81x_yaps_q_chip_full_timer_finish(yaps);
+
+	cancel_work_sync(&mors->hif_work);
+	cancel_work_sync(&mors->tx_stale_work);
+
+	mm81x_yaps_free_pkt_buffers(yaps);
+	mm81x_yaps_hw_finish(mors);
+}
+
+void mm81x_yaps_flush_tx_data(struct mm81x *mors)
+{
+	int i;
+	struct mm81x_yaps *yaps = &mors->hif.u.yaps;
+
+	mm81x_skbq_tx_flush(&yaps->beacon_q);
+	mm81x_skbq_tx_flush(&yaps->mgmt_q);
+
+	for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
+		mm81x_skbq_tx_flush(&yaps->data_tx_qs[i]);
+}
+
+void mm81x_yaps_flush_cmds(struct mm81x *mors)
+{
+	struct mm81x_yaps *yaps = &mors->hif.u.yaps;
+
+	if (yaps->flags & MM81X_HIF_FLAGS_COMMAND) {
+		mm81x_skbq_finish(&yaps->cmd_q);
+		mm81x_skbq_finish(&yaps->cmd_resp_q);
+	}
+}
diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps.h b/drivers/net/wireless/morsemicro/mm81x/yaps.h
new file mode 100644
index 00000000000000..2b2bb5f6e3990a
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/yaps.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_YAPS_H_
+#define _MM81X_YAPS_H_
+
+#include <linux/skbuff.h>
+#include <linux/workqueue.h>
+#include "skbq.h"
+
+#define YAPS_TX_SKBQ_MAX 4
+
+struct mm81x_hif_ops;
+extern const struct mm81x_hif_ops mm81x_yaps_ops;
+
+enum mm81x_yaps_to_chip_q {
+	MM81X_YAPS_TX_Q = 0,
+	MM81X_YAPS_CMD_Q,
+	MM81X_YAPS_BEACON_Q,
+	MM81X_YAPS_MGMT_Q,
+	/* Keep this last */
+	MM81X_YAPS_NUM_TC_Q
+};
+
+struct mm81x_yaps_pkt {
+	struct sk_buff *skb;
+	enum mm81x_yaps_to_chip_q tc_queue;
+};
+
+struct mm81x_yaps {
+	struct mm81x *mors;
+	struct mm81x_yaps_hw_aux_data *aux_data;
+	const struct mm81x_yaps_ops *ops;
+	u8 flags;
+	struct {
+		struct mm81x_yaps_pkt *to_chip_pkts;
+		struct mm81x_yaps_pkt *from_chip_pkts;
+	} hw;
+
+	/* Chip interface is stopping, new work should not be enqueued. */
+	bool finish;
+
+	struct mm81x_skbq data_tx_qs[YAPS_TX_SKBQ_MAX];
+	struct mm81x_skbq beacon_q;
+	struct mm81x_skbq mgmt_q;
+	struct mm81x_skbq data_rx_q;
+	struct mm81x_skbq cmd_q;
+	struct mm81x_skbq cmd_resp_q;
+
+	struct {
+		struct timer_list timer;
+		unsigned long retry_expiry;
+		bool is_full;
+	} chip_queue_full;
+};
+
+struct mm81x_yaps_ops {
+	int (*write_pkts)(struct mm81x_yaps *yaps, struct mm81x_yaps_pkt *pkts,
+			  int num_pkts, int *num_pkts_sent);
+	int (*read_pkts)(struct mm81x_yaps *yaps, struct mm81x_yaps_pkt *pkts,
+			 int num_pkts_max, int *num_pkts_received);
+	int (*update_status)(struct mm81x_yaps *yaps);
+};
+
+int mm81x_yaps_init(struct mm81x *mors);
+void mm81x_yaps_show(struct mm81x_yaps *yaps, struct seq_file *file);
+void mm81x_yaps_finish(struct mm81x *mors);
+void mm81x_yaps_flush_tx_data(struct mm81x *mors);
+void mm81x_yaps_flush_cmds(struct mm81x *mors);
+void mm81x_yaps_work(struct work_struct *work);
+void mm81x_yaps_stale_tx_work(struct work_struct *work);
+int mm81x_yaps_get_tx_status_pending_count(struct mm81x *mors);
+int mm81x_yaps_get_tx_buffered_count(struct mm81x *mors);
+
+#endif /* !_MM81X_YAPS_H_ */
diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c
new file mode 100644
index 00000000000000..3d641d23c35ba7
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c
@@ -0,0 +1,702 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include "yaps_hw.h"
+#include "bus.h"
+#include "hif.h"
+#include "yaps.h"
+
+#define YAPS_HW_WINDOW_SIZE_BYTES 32768
+#define YAPS_MAX_PKT_SIZE_BYTES 16128
+#define YAPS_METADATA_PAGE_COUNT 1
+
+#define YAPS_PHANDLE_CORRUPTION_WAR_EXTRA_PAGE 1
+
+#define YAPS_PAGE_SIZE 256
+
+/* Calculate padding required for yaps transaction */
+#define YAPS_CALC_PADDING(_bytes) ((_bytes) & 0x3 ? (4 - ((_bytes) & 0x3)) : 0)
+
+#define YAPS_RESERVED_PAGE_SIZE 256
+
+/*
+ * Yaps data stream delimiter is a 32 bit word with the following fields:
+ *
+ * pkt_size (14 bits) - Packet size not including delimiter or padding
+ * pool_id  (3  bits) - Pool that pages should be allocated from.
+ * padding  (2  bits) - Padding required to bring packet to word (4 byte)
+ * irq      (1  bit ) - Raise a PKT_IRQ on the YDS this is sent to
+ * reserved (5  bits) - Reserved, must write as 0
+ * crc      (7  bits) - YAPS CRC
+ */
+
+/* Packet size not including delimiter or padding */
+#define YAPS_DELIM_GET_PKT_SIZE(_delim) \
+	(((_delim) & 0x3FFF) - YAPS_RESERVED_PAGE_SIZE)
+#define YAPS_DELIM_SET_PKT_SIZE(_pkt_size) \
+	(((_pkt_size) & 0x3FFF) + YAPS_RESERVED_PAGE_SIZE)
+#define YAPS_DELIM_GET_PHANDLE_SIZE(_delim) (((_delim) & 0x3FFF))
+
+/* Pool that pages should be allocated from. */
+#define YAPS_DELIM_SET_POOL_ID(_pool_id) (((_pool_id) & 0x7) << 14)
+
+/* Padding required to bring packet to word (4 byte) boundary */
+#define YAPS_DELIM_GET_PADDING(_delim) (((_delim) >> 17) & 0x3)
+#define YAPS_DELIM_SET_PADDING(_padding) (((_padding) & 0x3) << 17)
+
+/* Raise a PKT_IRQ on the YDS this is sent to */
+#define YAPS_DELIM_SET_IRQ(_irq) (((_irq) & 0x1) << 19)
+
+/* YAPS CRC */
+#define YAPS_DELIM_GET_CRC(_delim) (((_delim) >> 25) & 0x7F)
+#define YAPS_DELIM_SET_CRC(_crc) (((_crc) & 0x7F) << 25)
+
+struct mm81x_yaps_status_regs {
+	/* Allocation pools */
+	u32 tc_tx_pool_num_pages;
+	u32 tc_cmd_pool_num_pages;
+	u32 tc_beacon_pool_num_pages;
+	u32 tc_mgmt_pool_num_pages;
+	u32 fc_rx_pool_num_pages;
+	u32 fc_resp_pool_num_pages;
+	u32 fc_tx_sts_pool_num_pages;
+	u32 fc_aux_pool_num_pages;
+	u32 tc_tx_num_pkts;
+	u32 tc_cmd_num_pkts;
+	u32 tc_beacon_num_pkts;
+	u32 tc_mgmt_num_pkts;
+	u32 fc_num_pkts;
+	u32 fc_done_num_pkts;
+	u32 fc_rx_bytes_in_queue;
+	u32 tc_delim_crc_fail_detected;
+	u32 fc_host_ysl_status;
+	u32 lock;
+} __packed __aligned(8);
+
+struct mm81x_yaps_hw_status_regs {
+	__le32 tc_tx_pool_num_pages;
+	__le32 tc_cmd_pool_num_pages;
+	__le32 tc_beacon_pool_num_pages;
+	__le32 tc_mgmt_pool_num_pages;
+	__le32 fc_rx_pool_num_pages;
+	__le32 fc_resp_pool_num_pages;
+	__le32 fc_tx_sts_pool_num_pages;
+	__le32 fc_aux_pool_num_pages;
+	__le32 tc_tx_num_pkts;
+	__le32 tc_cmd_num_pkts;
+	__le32 tc_beacon_num_pkts;
+	__le32 tc_mgmt_num_pkts;
+	__le32 fc_num_pkts;
+	__le32 fc_done_num_pkts;
+	__le32 fc_rx_bytes_in_queue;
+	__le32 tc_delim_crc_fail_detected;
+	__le32 fc_host_ysl_status;
+	__le32 lock;
+} __packed __aligned(8);
+
+struct mm81x_yaps_hw_aux_data {
+	unsigned long access_lock;
+
+	u32 yds_addr;
+	u32 ysl_addr;
+	u32 status_regs_addr;
+
+	/* Alloc pool sizes */
+	u16 tc_tx_pool_size;
+	u16 tc_cmd_pool_size;
+	u8 tc_beacon_pool_size;
+	u8 tc_mgmt_pool_size;
+	u8 fc_rx_pool_size;
+	u8 fc_resp_pool_size;
+	u8 fc_tx_sts_pool_size;
+	u8 fc_aux_pool_size;
+
+	/* To chip/from chip queue sizes */
+	u8 tc_tx_q_size;
+	u8 tc_cmd_q_size;
+	u8 tc_beacon_q_size;
+	u8 tc_mgmt_q_size;
+	u8 fc_q_size;
+	u8 fc_done_q_size;
+
+	u16 reserved_yaps_page_size;
+
+	/* Buffers to/from chip to support large contiguous reads/writes */
+	char *to_chip_buffer;
+	char *from_chip_buffer;
+
+	/* status registers in host endian */
+	struct mm81x_yaps_status_regs status_regs;
+
+	/* DMA target buffer in firmware endian */
+	struct mm81x_yaps_hw_status_regs hw_status_regs;
+};
+
+static int mm81x_yaps_hw_lock(struct mm81x_yaps *yaps)
+{
+	if (test_and_set_bit_lock(0, &yaps->aux_data->access_lock))
+		return -1;
+	return 0;
+}
+
+static void mm81x_yaps_hw_unlock(struct mm81x_yaps *yaps)
+{
+	clear_bit_unlock(0, &yaps->aux_data->access_lock);
+}
+
+static void
+mm81x_yaps_hw_fill_aux_data_from_hw_tbl(struct mm81x_yaps_hw_aux_data *a,
+					struct mm81x_yaps_hw_table *t)
+{
+	a->ysl_addr = __le32_to_cpu(t->ysl_addr);
+	a->yds_addr = __le32_to_cpu(t->yds_addr);
+	a->status_regs_addr = __le32_to_cpu(t->status_regs_addr);
+	a->tc_tx_pool_size = __le16_to_cpu(t->tc_tx_pool_size);
+	a->fc_rx_pool_size = __le16_to_cpu(t->fc_rx_pool_size);
+	a->tc_cmd_pool_size = t->tc_cmd_pool_size;
+	a->tc_beacon_pool_size = t->tc_beacon_pool_size;
+	a->tc_mgmt_pool_size = t->tc_mgmt_pool_size;
+	a->fc_resp_pool_size = t->fc_resp_pool_size;
+	a->fc_tx_sts_pool_size = t->fc_tx_sts_pool_size;
+	a->fc_aux_pool_size = t->fc_aux_pool_size;
+	a->tc_tx_q_size = t->tc_tx_q_size;
+	a->tc_cmd_q_size = t->tc_cmd_q_size;
+	a->tc_beacon_q_size = t->tc_beacon_q_size;
+	a->tc_mgmt_q_size = t->tc_mgmt_q_size;
+	a->fc_q_size = t->fc_q_size;
+	a->fc_done_q_size = t->fc_done_q_size;
+	a->reserved_yaps_page_size = le16_to_cpu(t->yaps_reserved_page_size);
+}
+
+static u8 mm81x_yaps_hw_crc(u32 word)
+{
+	u8 crc = 0;
+	u8 byte;
+	int i;
+
+	/* Mask to look at only non-CRC bits */
+	word &= 0x1ffffff;
+
+	for (i = 0; i < 4; i++) {
+		byte = (word >> 24) & 0xff;
+		crc = crc7_be(crc, &byte, 1);
+		word <<= 8;
+	}
+
+	return crc >> 1;
+}
+
+static u32 mm81x_write_pkts_h_build_delim(struct mm81x_yaps *yaps,
+					  unsigned int size, u8 pool_id,
+					  bool irq)
+{
+	u32 delim = 0;
+
+	delim |= YAPS_DELIM_SET_PKT_SIZE(size);
+	delim |= YAPS_DELIM_SET_PADDING(YAPS_CALC_PADDING(size));
+	delim |= YAPS_DELIM_SET_POOL_ID(pool_id);
+	delim |= YAPS_DELIM_SET_IRQ(irq);
+	delim |= YAPS_DELIM_SET_CRC(mm81x_yaps_hw_crc(delim));
+	return delim;
+}
+
+void mm81x_yaps_hw_enable_irqs(struct mm81x *mors, bool enable)
+{
+	mm81x_hw_irq_enable(mors, MM81X_INT_YAPS_FC_PKT_WAITING_IRQN, enable);
+	mm81x_hw_irq_enable(mors, MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN,
+			    enable);
+}
+
+void mm81x_yaps_hw_read_table(struct mm81x *mors,
+			      struct mm81x_yaps_hw_table *tbl_ptr)
+{
+	mm81x_yaps_hw_fill_aux_data_from_hw_tbl(mors->hif.u.yaps.aux_data,
+						tbl_ptr);
+	mm81x_yaps_hw_enable_irqs(mors, true);
+}
+
+static unsigned int mm81x_write_pkts_h_pages_required(struct mm81x_yaps *yaps,
+						      unsigned int size_bytes)
+{
+	/* Always account for the first metadata page */
+	return DIV_ROUND_UP(size_bytes +
+				    yaps->aux_data->reserved_yaps_page_size,
+			    YAPS_PAGE_SIZE) +
+	       YAPS_METADATA_PAGE_COUNT +
+	       YAPS_PHANDLE_CORRUPTION_WAR_EXTRA_PAGE;
+}
+
+/*
+ * Checks if a single pkt will fit in the chip using the pool/alloc holding
+ * information from the last status register read.
+ */
+static bool mm81x_write_pkts_h_will_fit(struct mm81x_yaps *yaps,
+					struct mm81x_yaps_pkt *pkt, bool update)
+{
+	bool will_fit = true;
+	const int pages_required =
+		mm81x_write_pkts_h_pages_required(yaps, pkt->skb->len);
+	int *pool_pages_avail = NULL;
+	int *pkts_in_queue = NULL;
+	int queue_pkts_avail = 0;
+
+	switch (pkt->tc_queue) {
+	case MM81X_YAPS_TX_Q:
+		pool_pages_avail =
+			&yaps->aux_data->status_regs.tc_tx_pool_num_pages;
+		pkts_in_queue = &yaps->aux_data->status_regs.tc_tx_num_pkts;
+		queue_pkts_avail =
+			yaps->aux_data->tc_tx_q_size - *pkts_in_queue;
+		break;
+	case MM81X_YAPS_CMD_Q:
+		pool_pages_avail =
+			&yaps->aux_data->status_regs.tc_cmd_pool_num_pages;
+		pkts_in_queue = &yaps->aux_data->status_regs.tc_cmd_num_pkts;
+		queue_pkts_avail =
+			yaps->aux_data->tc_cmd_q_size - *pkts_in_queue;
+		break;
+	case MM81X_YAPS_BEACON_Q:
+		pool_pages_avail =
+			&yaps->aux_data->status_regs.tc_beacon_pool_num_pages;
+		pkts_in_queue = &yaps->aux_data->status_regs.tc_beacon_num_pkts;
+		queue_pkts_avail =
+			yaps->aux_data->tc_beacon_q_size - *pkts_in_queue;
+		break;
+	case MM81X_YAPS_MGMT_Q:
+		pool_pages_avail =
+			&yaps->aux_data->status_regs.tc_mgmt_pool_num_pages;
+		pkts_in_queue = &yaps->aux_data->status_regs.tc_mgmt_num_pkts;
+		queue_pkts_avail =
+			yaps->aux_data->tc_mgmt_q_size - *pkts_in_queue;
+		break;
+	default:
+		dev_err(yaps->mors->dev, "yaps invalid tc queue");
+		return false;
+	}
+
+	WARN_ON(queue_pkts_avail < 0);
+
+	if (pages_required > *pool_pages_avail)
+		will_fit = false;
+
+	if (queue_pkts_avail == 0)
+		will_fit = false;
+
+	if (will_fit && update) {
+		*pool_pages_avail -= pages_required;
+		*pkts_in_queue += 1;
+	}
+
+	return will_fit;
+}
+
+static int mm81x_write_pkts_h_err_check(struct mm81x_yaps *yaps,
+					struct mm81x_yaps_pkt *pkt)
+{
+	if (pkt->skb->len + yaps->aux_data->reserved_yaps_page_size >
+	    YAPS_MAX_PKT_SIZE_BYTES)
+		return -EMSGSIZE;
+	if (pkt->tc_queue >= MM81X_YAPS_NUM_TC_Q)
+		return -EINVAL;
+	if (!mm81x_write_pkts_h_will_fit(yaps, pkt, true))
+		return -EAGAIN;
+
+	return 0;
+}
+
+static int mm81x_yaps_hw_write_pkts(struct mm81x_yaps *yaps,
+				    struct mm81x_yaps_pkt *pkts, int num_pkts,
+				    int *num_pkts_sent)
+{
+	int ret = 0;
+	int i;
+	u32 delim = 0;
+	int tx_len;
+	int batch_txn_len = 0;
+	int pkts_pending = 0;
+	bool delim_irq = false;
+	char *to_chip_buffer_aligned =
+		PTR_ALIGN(yaps->aux_data->to_chip_buffer,
+			  mm81x_bus_get_alignment(yaps->mors));
+	char *write_buf = to_chip_buffer_aligned;
+
+	ret = mm81x_yaps_hw_lock(yaps);
+	if (ret) {
+		dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret);
+		return ret;
+	}
+
+	*num_pkts_sent = 0;
+
+	/* Check packet conditions */
+	ret = mm81x_write_pkts_h_err_check(yaps, &pkts[0]);
+	if (ret)
+		goto exit;
+
+	/* Batch packets into larger transactions */
+	for (i = 0; i < num_pkts; ++i) {
+		u32 pkt_size =
+			pkts[i].skb->len + YAPS_CALC_PADDING(pkts[i].skb->len);
+		tx_len = pkt_size + sizeof(delim);
+
+		/*
+		 * Send when we have reached window size, don't split pkt over
+		 * boundary
+		 */
+		if ((batch_txn_len + tx_len) > YAPS_HW_WINDOW_SIZE_BYTES) {
+			ret = mm81x_dm_write(yaps->mors,
+					     yaps->aux_data->yds_addr,
+					     to_chip_buffer_aligned,
+					     batch_txn_len);
+
+			batch_txn_len = 0;
+			if (ret)
+				goto exit;
+			write_buf = to_chip_buffer_aligned;
+			*num_pkts_sent += pkts_pending;
+			pkts_pending = 0;
+		}
+
+		if ((i + 1) == num_pkts) {
+			/* The last packet in the queue has IRQ set */
+			delim_irq = true;
+		} else {
+			/*
+			 * Since this is not the last packet, we can check for
+			 * the next one. In case of errors in the next packet
+			 * set the IRQ
+			 */
+			ret = mm81x_write_pkts_h_err_check(yaps, &pkts[i + 1]);
+			if (ret)
+				delim_irq = true;
+		}
+
+		/* Build stream header*/
+		delim = mm81x_write_pkts_h_build_delim(
+			yaps, pkt_size, pkts[i].tc_queue, delim_irq);
+		*((__le32 *)write_buf) = cpu_to_le32(delim);
+		memcpy(write_buf + sizeof(delim), pkts[i].skb->data,
+		       pkts[i].skb->len);
+
+		write_buf += tx_len;
+		batch_txn_len += tx_len;
+		pkts_pending++;
+
+		if (ret)
+			goto exit;
+	}
+
+exit:
+	if (batch_txn_len > 0) {
+		ret = mm81x_dm_write(yaps->mors, yaps->aux_data->yds_addr,
+				     to_chip_buffer_aligned, batch_txn_len);
+		*num_pkts_sent += pkts_pending;
+	}
+
+	mm81x_yaps_hw_unlock(yaps);
+	return ret;
+}
+
+static bool mm81x_read_pkts_h_is_valid_delim(u32 delim)
+{
+	u8 calc_crc = mm81x_yaps_hw_crc(delim);
+	int pkt_size = YAPS_DELIM_GET_PHANDLE_SIZE(delim);
+	int padding = YAPS_DELIM_GET_PADDING(delim);
+
+	if (calc_crc != YAPS_DELIM_GET_CRC(delim))
+		return false;
+
+	if (pkt_size == 0)
+		return false;
+
+	if ((pkt_size + padding) > YAPS_MAX_PKT_SIZE_BYTES)
+		return false;
+
+	/* Pkt length + padding should not require more padding */
+	if (YAPS_CALC_PADDING(pkt_size) != padding)
+		return false;
+
+	return true;
+}
+
+static int mm81x_read_pkts_h_bytes_remaining(struct mm81x_yaps *yaps)
+{
+	u32 bytes_in_queue = yaps->aux_data->status_regs.fc_rx_bytes_in_queue;
+	u32 delim_overhead =
+		yaps->aux_data->status_regs.fc_num_pkts * sizeof(u32);
+	u32 reserved_bytes = yaps->aux_data->status_regs.fc_num_pkts *
+			     yaps->aux_data->reserved_yaps_page_size;
+
+	if (WARN_ON(bytes_in_queue > INT_MAX) ||
+	    WARN_ON(delim_overhead > INT_MAX) ||
+	    WARN_ON(reserved_bytes > INT_MAX))
+		return -EIO;
+
+	return (int)bytes_in_queue;
+}
+
+static int mm81x_yaps_hw_read_pkts(struct mm81x_yaps *yaps,
+				   struct mm81x_yaps_pkt *pkts,
+				   int num_pkts_max, int *num_pkts_received)
+{
+	int ret;
+	int i = 0;
+	char *from_chip_buffer_aligned =
+		PTR_ALIGN(yaps->aux_data->from_chip_buffer,
+			  mm81x_bus_get_alignment(yaps->mors));
+	char *read_ptr = from_chip_buffer_aligned;
+	int bytes_remaining = mm81x_read_pkts_h_bytes_remaining(yaps);
+	bool again = false;
+
+	*num_pkts_received = 0;
+
+	if (num_pkts_max == 0 || bytes_remaining == 0)
+		return 0;
+	if (bytes_remaining < 0)
+		return bytes_remaining;
+
+	if (bytes_remaining > YAPS_HW_WINDOW_SIZE_BYTES) {
+		bytes_remaining = YAPS_HW_WINDOW_SIZE_BYTES;
+		again = true;
+	}
+
+	/*
+	 * This is more coarse-grained than it needs to be - once the data
+	 * is read into a local buffer the lock can be released, however
+	 * access to from_chip_buffer will need to be protected with its
+	 * own lock
+	 */
+	ret = mm81x_yaps_hw_lock(yaps);
+	if (ret) {
+		dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret);
+		return ret;
+	}
+
+	/* Read all available packets to the buffer */
+	ret = mm81x_dm_read(yaps->mors, yaps->aux_data->ysl_addr,
+			    from_chip_buffer_aligned, bytes_remaining);
+
+	if (ret)
+		goto exit;
+
+	/* Split serialised packets from buffer */
+	while (i < num_pkts_max && bytes_remaining > 0) {
+		u32 delim;
+		int total_len;
+		int pkt_size;
+
+		delim = le32_to_cpu(*((__le32 *)read_ptr));
+		read_ptr += sizeof(delim);
+		bytes_remaining -= sizeof(delim);
+
+		/* End of stream */
+		if (!delim)
+			break;
+
+		if (!mm81x_read_pkts_h_is_valid_delim(delim)) {
+			/*
+			 * This will start a hunt for a valid delimiter. Given
+			 * the CRC is only 7 bit it's possible to find an
+			 * invalid block with a valid delimiter, leading to
+			 * desynchronisation.
+			 */
+			dev_warn(yaps->mors->dev, "yaps invalid delim");
+			break;
+		}
+
+		/* Total length in chip */
+		pkt_size = YAPS_DELIM_GET_PKT_SIZE(delim);
+		total_len = pkt_size + YAPS_DELIM_GET_PADDING(delim);
+
+		if (pkts[i].skb)
+			dev_err(yaps->mors->dev, "yaps packet leak");
+
+		/* SKB doesn't want padding */
+		pkts[i].skb = dev_alloc_skb(pkt_size);
+		if (!pkts[i].skb) {
+			ret = -ENOMEM;
+			dev_err(yaps->mors->dev, "yaps no mem for skb");
+			goto exit;
+		}
+		skb_put(pkts[i].skb, pkt_size);
+
+		if (total_len <= bytes_remaining) {
+			memcpy(pkts[i].skb->data, read_ptr, pkt_size);
+			read_ptr += total_len;
+			bytes_remaining -= total_len;
+		} else {
+			const int read_overhang_len =
+				total_len - bytes_remaining;
+			const int pkt_overhang_len = pkt_size - bytes_remaining;
+
+			memcpy(pkts[i].skb->data, read_ptr, bytes_remaining);
+			read_ptr = from_chip_buffer_aligned;
+
+			ret = mm81x_dm_read(
+				yaps->mors,
+				/* Offset by 4 to avoid retry logic */
+				yaps->aux_data->ysl_addr + 4, read_ptr,
+				read_overhang_len);
+
+			if (ret)
+				goto exit;
+
+			memcpy(pkts[i].skb->data + bytes_remaining, read_ptr,
+			       pkt_overhang_len);
+			read_ptr += read_overhang_len;
+			bytes_remaining = 0;
+		}
+
+		*num_pkts_received += 1;
+		i++;
+	}
+
+	if (again)
+		ret = -EAGAIN;
+
+exit:
+	mm81x_yaps_hw_unlock(yaps);
+	return ret;
+}
+
+static int mm81x_yaps_hw_update_status(struct mm81x_yaps *yaps)
+{
+	int ret;
+	int tc_total_pkt_count;
+	unsigned long reg_read_timeout;
+	struct mm81x_yaps_status_regs *r = &yaps->aux_data->status_regs;
+	struct mm81x_yaps_hw_status_regs *hw_r = &yaps->aux_data->hw_status_regs;
+
+	ret = mm81x_yaps_hw_lock(yaps);
+	if (ret) {
+		dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret);
+		return ret;
+	}
+
+	reg_read_timeout = jiffies + msecs_to_jiffies(100);
+	do {
+		if (time_after(jiffies, reg_read_timeout)) {
+			dev_err(yaps->mors->dev,
+				"timed out reading status registers: %d", ret);
+			ret = -ETIMEDOUT;
+			break;
+		}
+
+		ret = mm81x_dm_read(yaps->mors,
+				    yaps->aux_data->status_regs_addr,
+				    (u8 *)hw_r, sizeof(*hw_r));
+	} while (!ret && le32_to_cpu(hw_r->lock));
+
+	if (ret) {
+		if (ret != -ENODEV) {
+			dev_err(yaps->mors->dev,
+				"error reading yaps status registers: %d", ret);
+		}
+		goto exit_unlock;
+	}
+
+	r->tc_tx_pool_num_pages = le32_to_cpu(hw_r->tc_tx_pool_num_pages);
+	r->tc_cmd_pool_num_pages = le32_to_cpu(hw_r->tc_cmd_pool_num_pages);
+	r->tc_beacon_pool_num_pages = le32_to_cpu(hw_r->tc_beacon_pool_num_pages);
+	r->tc_mgmt_pool_num_pages = le32_to_cpu(hw_r->tc_mgmt_pool_num_pages);
+	r->fc_rx_pool_num_pages = le32_to_cpu(hw_r->fc_rx_pool_num_pages);
+	r->fc_resp_pool_num_pages = le32_to_cpu(hw_r->fc_resp_pool_num_pages);
+	r->fc_tx_sts_pool_num_pages = le32_to_cpu(hw_r->fc_tx_sts_pool_num_pages);
+	r->fc_aux_pool_num_pages = le32_to_cpu(hw_r->fc_aux_pool_num_pages);
+	r->tc_tx_num_pkts = le32_to_cpu(hw_r->tc_tx_num_pkts);
+	r->tc_cmd_num_pkts = le32_to_cpu(hw_r->tc_cmd_num_pkts);
+	r->tc_beacon_num_pkts = le32_to_cpu(hw_r->tc_beacon_num_pkts);
+	r->tc_mgmt_num_pkts = le32_to_cpu(hw_r->tc_mgmt_num_pkts);
+	r->fc_num_pkts = le32_to_cpu(hw_r->fc_num_pkts);
+	r->fc_done_num_pkts = le32_to_cpu(hw_r->fc_done_num_pkts);
+	r->fc_rx_bytes_in_queue = le32_to_cpu(hw_r->fc_rx_bytes_in_queue);
+	r->tc_delim_crc_fail_detected = le32_to_cpu(hw_r->tc_delim_crc_fail_detected);
+	r->lock = le32_to_cpu(hw_r->lock);
+	r->fc_host_ysl_status = le32_to_cpu(hw_r->fc_host_ysl_status);
+
+	tc_total_pkt_count = r->tc_tx_num_pkts + r->tc_cmd_num_pkts +
+			     r->tc_beacon_num_pkts + r->tc_mgmt_num_pkts;
+
+	if (r->tc_delim_crc_fail_detected) {
+		/*
+		 * Host and chip have become desynchronised. This can happen if
+		 * the chip crashes during a YAPS transaction. We cannot
+		 * recover from this.
+		 */
+		dev_err(yaps->mors->dev,
+			"to-chip yaps delimiter CRC fail, pkt_count=%d",
+			tc_total_pkt_count);
+		ret = -EIO;
+	}
+
+	if (mm81x_read_pkts_h_bytes_remaining(yaps))
+		set_bit(MM81X_HIF_EVT_RX_PEND, &yaps->mors->hif.event_flags);
+
+exit_unlock:
+	mm81x_yaps_hw_unlock(yaps);
+	return ret;
+}
+
+static const struct mm81x_yaps_ops mm81x_yaps_hw_ops = {
+	.write_pkts = mm81x_yaps_hw_write_pkts,
+	.read_pkts = mm81x_yaps_hw_read_pkts,
+	.update_status = mm81x_yaps_hw_update_status,
+};
+
+int mm81x_yaps_hw_init(struct mm81x *mors)
+{
+	int ret = 0;
+	struct mm81x_yaps *yaps = NULL;
+	int aux_data_len = sizeof(struct mm81x_yaps_hw_aux_data);
+	int alignment = mm81x_bus_get_alignment(mors);
+
+	yaps = &mors->hif.u.yaps;
+	yaps->aux_data = kzalloc(aux_data_len, GFP_KERNEL);
+	if (!yaps->aux_data) {
+		ret = -ENOMEM;
+		goto err_exit;
+	}
+
+	yaps->aux_data->to_chip_buffer =
+		kzalloc(YAPS_HW_WINDOW_SIZE_BYTES + alignment - 1, GFP_KERNEL);
+	if (!yaps->aux_data->to_chip_buffer) {
+		ret = -ENOMEM;
+		goto err_exit;
+	}
+
+	yaps->aux_data->from_chip_buffer =
+		kzalloc(YAPS_HW_WINDOW_SIZE_BYTES + alignment - 1, GFP_KERNEL);
+	if (!yaps->aux_data->from_chip_buffer) {
+		ret = -ENOMEM;
+		goto err_exit;
+	}
+
+	if (!IS_ALIGNED((uintptr_t)&yaps->aux_data->status_regs, alignment)) {
+		dev_warn(mors->dev,
+			 "Status registers are not aligned to %d bytes",
+			 alignment);
+	}
+
+	yaps->ops = &mm81x_yaps_hw_ops;
+	return ret;
+
+err_exit:
+	mm81x_yaps_hw_finish(mors);
+	return ret;
+}
+
+void mm81x_yaps_hw_finish(struct mm81x *mors)
+{
+	struct mm81x_yaps *yaps;
+
+	yaps = &mors->hif.u.yaps;
+	if (yaps->aux_data) {
+		kfree(yaps->aux_data->from_chip_buffer);
+		yaps->aux_data->from_chip_buffer = NULL;
+		kfree(yaps->aux_data->to_chip_buffer);
+		yaps->aux_data->to_chip_buffer = NULL;
+		kfree(yaps->aux_data);
+		yaps->aux_data = NULL;
+	}
+}
diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps_hw.h b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.h
new file mode 100644
index 00000000000000..89e15375aabc1b
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_YAPS_HW_H_
+#define _MM81X_YAPS_HW_H_
+
+#include <linux/types.h>
+#include <linux/crc7.h>
+
+#define MM81X_INT_YAPS_FC_PKT_WAITING_IRQN 0
+#define MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN 1
+
+struct mm81x_yaps_hw_table {
+	/* NOTE: We need these padding bytes for yaps to work */
+	u8 padding[4];
+	__le32 ysl_addr;
+	__le32 yds_addr;
+	__le32 status_regs_addr;
+
+	/* Alloc pool sizes */
+	__le16 tc_tx_pool_size;
+	__le16 fc_rx_pool_size;
+	u8 tc_cmd_pool_size;
+	u8 tc_beacon_pool_size;
+	u8 tc_mgmt_pool_size;
+	u8 fc_resp_pool_size;
+	u8 fc_tx_sts_pool_size;
+	u8 fc_aux_pool_size;
+
+	/* To chip/from chip queue sizes */
+	u8 tc_tx_q_size;
+	u8 tc_cmd_q_size;
+	u8 tc_beacon_q_size;
+	u8 tc_mgmt_q_size;
+	u8 fc_q_size;
+	u8 fc_done_q_size;
+
+	__le16 yaps_reserved_page_size;
+	__le16 reserved_unused;
+} __packed;
+
+struct mm81x;
+
+void mm81x_yaps_hw_enable_irqs(struct mm81x *mors, bool enable);
+int mm81x_yaps_hw_init(struct mm81x *mors);
+void mm81x_yaps_hw_finish(struct mm81x *mors);
+void mm81x_yaps_hw_read_table(struct mm81x *mors,
+			      struct mm81x_yaps_hw_table *tbl_ptr);
+
+#endif /* !_MM81X_YAPS_HW_H_ */
diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index d40484451e9a28..084ad45aa2d8dd 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -2616,6 +2616,7 @@ static inline int ieee80211_get_tdls_action(struct sk_buff *skb)
 /* convert frequencies */
 #define MHZ_TO_KHZ(freq) ((freq) * 1000)
 #define KHZ_TO_MHZ(freq) ((freq) / 1000)
+#define KHZ_TO_HZ(x) ((x) * 1000)
 #define PR_KHZ(f) KHZ_TO_MHZ(f), f % 1000
 #define KHZ_F "%d.%03d"
 
diff --git a/include/linux/mmc/sdio_ids.h b/include/linux/mmc/sdio_ids.h
index 0685dd717e855c..bbffad9ae88e86 100644
--- a/include/linux/mmc/sdio_ids.h
+++ b/include/linux/mmc/sdio_ids.h
@@ -117,6 +117,9 @@
 #define SDIO_VENDOR_ID_MICROCHIP_WILC		0x0296
 #define SDIO_DEVICE_ID_MICROCHIP_WILC1000	0x5347
 
+#define SDIO_VENDOR_ID_MORSEMICRO		0x325b
+#define SDIO_DEVICE_ID_MORSEMICRO_MM8108	0x0809
+
 #define SDIO_VENDOR_ID_NXP			0x0471
 #define SDIO_DEVICE_ID_NXP_IW61X		0x0205
 
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 8188ad200de539..a5d54d70d72b12 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -1236,6 +1236,26 @@ ieee80211_chandef_max_power(struct cfg80211_chan_def *chandef)
 	return chandef->chan->max_power;
 }
 
+/**
+ * cfg80211_chandef_s1g_pri_width - return S1G primary width in MHz
+ *
+ * An S1G interface may have a primary channel width of either 1
+ * or 2MHz depending on whether chandef::s1g_primary_2mhz is set.
+ *
+ * Note: There is _always_ a 1MHz primary subchannel, regardless
+ * of the primary width. So chandef::chan always points to this
+ * 1MHz primary channel.
+ *
+ * @chandef: the chandef to use
+ *
+ * Returns: width in MHz of the S1G primary channel in use
+ */
+static inline int
+cfg80211_chandef_s1g_pri_width(struct cfg80211_chan_def *chandef)
+{
+	return chandef->s1g_primary_2mhz ? 2 : 1;
+}
+
 /**
  * cfg80211_any_usable_channels - check for usable channels
  * @wiphy: the wiphy to check for

3/2 2026/06/26 06:48 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 185220
							output: 267
							thoughts: 1637
						

Thoughts:
**Fuzzing Target Identification: Morse Micro MM81x Wireless Driver**

Okay, so we've got a new patch adding a wireless driver for Morse Micro's MM81x devices. It's a mac80211 driver, specifically designed to support USB and SDIO interfaces. I immediately think, is this fuzzable? And the answer is likely yes.

Looking at the driver structure, it's got both a USB driver (`mm81x_usb.c`) and an SDIO driver (`mm81x_sdio.c`). USB is particularly promising, as we can easily emulate USB devices using frameworks like dummy_hcd or, if we are feeling fancy, vhci, which is what syzkaller employs. SDIO is a bit trickier, requiring some level of SDIO emulation. I'll need to double check the state of SDIO emulation in our environment, but USB gives us a clear path. This is a good sign.

Next step: let's identify the required configurations to build this driver and make it accessible. It looks like we need: `CONFIG_WLAN_VENDOR_MORSEMICRO`, `CONFIG_MM81X`, `CONFIG_MM81X_USB`, and `CONFIG_MM81X_SDIO`. Looking into the Kconfig for the driver confirms those requirements and dependencies on MAC80211, FW_LOADER, and CRC7. Good to know - we need to make sure those are enabled in our config.

Now, let's pinpoint some interesting functions that are good candidates for fuzzing targets. I see some likely spots:

*   The USB driver's `mm81x_usb_probe`, `mm81x_usb_int_handler`, `mm81x_usb_cmd_callback`, and `mm81x_usb_mem_rw_callback`.
*   The SDIO driver has `mm81x_sdio_probe`.
*   The core MAC driver provides `mm81x_mac_event_recv` and `mm81x_mac_rx_skb`.
*   The HIF/YAPS driver has `mm81x_yaps_work`, and `mm81x_yaps_hw_read_pkts`.
*   `mm81x_cmd_resp_process` also looks interesting.

Let's verify those names are correct.  I'll just quickly check their prototypes within the source. Looks like the prototypes match up perfectly with the functions in the source:

*   `mm81x_usb_probe`, `mm81x_usb_int_handler`, `mm81x_usb_cmd_callback`, `mm81x_usb_mem_rw_callback`, `mm81x_sdio_probe`, `mm81x_mac_event_recv`, `mm81x_mac_rx_skb`, `mm81x_yaps_work`, `mm81x_yaps_hw_read_pkts`, and `mm81x_cmd_resp_process`

Now, let's consider the fuzzing strategy. Since we can emulate the USB interface via vhci, we'll focus there.  We've got a solid set of focus functions.  I think this is ready for fuzzing.
Time to call `set-results`.



4/2 2026/06/26 06:49 tool set-results
0m Args:
{
  "EnableConfigs": [
    "WLAN_VENDOR_MORSEMICRO",
    "MM81X",
    "MM81X_USB",
    "MM81X_SDIO"
  ],
  "FocusSymbols": [
    "mm81x_usb_probe",
    "mm81x_usb_int_handler",
    "mm81x_usb_cmd_callback",
    "mm81x_usb_mem_rw_callback",
    "mm81x_sdio_probe",
    "mm81x_mac_event_recv",
    "mm81x_mac_rx_skb",
    "mm81x_yaps_work",
    "mm81x_yaps_hw_read_pkts",
    "mm81x_cmd_resp_process"
  ],
  "Reasoning": "The patch introduces a new mac80211 wireless driver for Morse Micro MM81x devices. It includes both USB and SDIO interfaces. The USB interface can be easily fuzzed using virtual USB host controllers (vhci) in virtualized environments. Therefore, this driver is reachable and should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "WLAN_VENDOR_MORSEMICRO",
    "MM81X",
    "MM81X_USB",
    "MM81X_SDIO"
  ],
  "FocusSymbols": [
    "mm81x_usb_probe",
    "mm81x_usb_int_handler",
    "mm81x_usb_cmd_callback",
    "mm81x_usb_mem_rw_callback",
    "mm81x_sdio_probe",
    "mm81x_mac_event_recv",
    "mm81x_mac_rx_skb",
    "mm81x_yaps_work",
    "mm81x_yaps_hw_read_pkts",
    "mm81x_cmd_resp_process"
  ],
  "Reasoning": "The patch introduces a new mac80211 wireless driver for Morse Micro MM81x devices. It includes both USB and SDIO interfaces. The USB interface can be easily fuzzed using virtual USB host controllers (vhci) in virtualized environments. Therefore, this driver is reachable and should be fuzzed.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)