From: "Yanli Yang" <yanli.yang@bedmex.com>
To: "Johannes Berg" <johannes@sipsolutions.net>
Cc: <linux-wireless@vger.kernel.org>,
"Zhirun Liu" <zhirunliu@aicsemi.com>,
"Dijia Xu" <dijiaxu@aicsemi.com>,
"Chunqiu Liu" <chunqiuliu@aicsemi.com>,
"Liheng Wei" <liheng.wei@bedmex.com>
Subject: [RFC PATCH wireless-next v3 3/4] wifi: aic: add FullMAC WLAN driver
Date: Fri, 28 Aug 2026 20:11:32 +0800 [thread overview]
Message-ID: <f22a875eefa398eea9fc80bff1468dfacc47c683.1787903339.git.yanli.yang@bedmex.com> (raw)
In-Reply-To: <cover.1787903339.git.yanli.yang@bedmex.com>
From: Zhirun Liu <zhirunliu@aicsemi.com>
Add the cfg80211 FullMAC implementation, TX/RX data paths, SDIO host glue and
AIC8800D80 U02 chip operations. Normal Wi-Fi control is exposed through
cfg80211/nl80211 and system suspend is handled through the SDIO dev_pm_ops.
The Android private ioctl, Wireless Extensions, GKI shim, vendor-HAL stubs,
Bluetooth character device and bridge extension present in earlier RFCs are
not included.
Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
.../wireless/aic/aic8800_fdrv/aic_priv_cmd.c | 766 +++
.../wireless/aic/aic8800_fdrv/aic_priv_cmd.h | 103 +
.../aic/aic8800_fdrv/aicwf_chip_8800d80.c | 291 +
.../aic/aic8800_fdrv/aicwf_chip_8800dc.c | 300 +
.../aic/aic8800_fdrv/aicwf_chip_8801.c | 279 +
.../aic/aic8800_fdrv/aicwf_chip_ops.c | 251 +
.../aic/aic8800_fdrv/aicwf_chip_ops.h | 184 +
.../aic/aic8800_fdrv/aicwf_compat_8800d80.c | 96 +
.../aic/aic8800_fdrv/aicwf_compat_8800d80.h | 16 +
.../aic/aic8800_fdrv/aicwf_compat_8800dc.c | 505 ++
.../aic/aic8800_fdrv/aicwf_compat_8800dc.h | 22 +
.../wireless/aic/aic8800_fdrv/aicwf_genl.c | 699 +++
.../wireless/aic/aic8800_fdrv/aicwf_genl.h | 84 +
.../aic/aic8800_fdrv/aicwf_rx_prealloc.c | 102 +
.../aic/aic8800_fdrv/aicwf_rx_prealloc.h | 34 +
.../wireless/aic/aic8800_fdrv/aicwf_sdio.c | 2831 +++++++++
.../wireless/aic/aic8800_fdrv/aicwf_sdio.h | 275 +
.../wireless/aic/aic8800_fdrv/aicwf_tcp_ack.c | 565 ++
.../wireless/aic/aic8800_fdrv/aicwf_tcp_ack.h | 104 +
.../wireless/aic/aic8800_fdrv/aicwf_txrxif.c | 752 +++
.../wireless/aic/aic8800_fdrv/aicwf_txrxif.h | 217 +
drivers/net/wireless/aic/aic8800_fdrv/regdb.c | 2772 +++++++++
.../wireless/aic/aic8800_fdrv/rwnx_bfmer.c | 93 +
.../wireless/aic/aic8800_fdrv/rwnx_bfmer.h | 100 +
.../wireless/aic/aic8800_fdrv/rwnx_cfgfile.c | 265 +
.../wireless/aic/aic8800_fdrv/rwnx_cfgfile.h | 33 +
.../net/wireless/aic/aic8800_fdrv/rwnx_main.c | 5439 +++++++++++++++++
.../net/wireless/aic/aic8800_fdrv/rwnx_main.h | 35 +
.../net/wireless/aic/aic8800_fdrv/rwnx_mesh.c | 33 +
.../net/wireless/aic/aic8800_fdrv/rwnx_mesh.h | 35 +
.../aic/aic8800_fdrv/rwnx_mod_params.c | 985 +++
.../aic/aic8800_fdrv/rwnx_mod_params.h | 74 +
.../wireless/aic/aic8800_fdrv/rwnx_mu_group.c | 653 ++
.../wireless/aic/aic8800_fdrv/rwnx_mu_group.h | 159 +
.../wireless/aic/aic8800_fdrv/rwnx_radar.c | 1970 ++++++
.../wireless/aic/aic8800_fdrv/rwnx_radar.h | 285 +
.../net/wireless/aic/aic8800_fdrv/rwnx_rx.c | 2320 +++++++
.../net/wireless/aic/aic8800_fdrv/rwnx_rx.h | 358 ++
.../net/wireless/aic/aic8800_fdrv/rwnx_tdls.c | 773 +++
.../net/wireless/aic/aic8800_fdrv/rwnx_tdls.h | 57 +
.../wireless/aic/aic8800_fdrv/rwnx_testmode.c | 217 +
.../wireless/aic/aic8800_fdrv/rwnx_testmode.h | 68 +
.../wireless/aic/aic8800_fdrv/rwnx_trace.c | 5 +
.../net/wireless/aic/aic8800_fdrv/rwnx_tx.c | 1635 +++++
.../net/wireless/aic/aic8800_fdrv/rwnx_tx.h | 176 +
.../net/wireless/aic/aic8800_fdrv/rwnx_txq.c | 1362 +++++
.../net/wireless/aic/aic8800_fdrv/rwnx_txq.h | 358 ++
.../net/wireless/aic/aic8800_fdrv/sdio_host.c | 132 +
.../net/wireless/aic/aic8800_fdrv/sdio_host.h | 39 +
49 files changed, 28907 insertions(+)
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8800d80.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8800dc.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8801.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_ops.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_ops.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800d80.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800d80.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800dc.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800dc.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_genl.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_genl.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_rx_prealloc.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_rx_prealloc.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_sdio.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_sdio.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_tcp_ack.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_tcp_ack.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_txrxif.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_txrxif.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/regdb.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_bfmer.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_bfmer.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_cfgfile.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_cfgfile.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_main.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_main.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_mesh.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_mesh.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_mod_params.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_mod_params.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_mu_group.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_mu_group.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_radar.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_radar.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_rx.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_rx.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_tdls.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_tdls.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_testmode.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_testmode.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_trace.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_tx.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_tx.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_txq.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_txq.h
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/sdio_host.c
create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/sdio_host.h
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.c b/drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.c
new file mode 100644
index 0000000000000..b5f5869284dfa
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.c
@@ -0,0 +1,766 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief private command definition
+ *
+ ******************************************************************************
+ */
+
+#include "aic_priv_cmd.h"
+#include "aicwf_sdio.h"
+#include "rwnx_defs.h"
+#include "rwnx_main.h"
+#include "rwnx_mod_params.h"
+#include "rwnx_msg_tx.h"
+#include "rwnx_platform.h"
+#include <linux/ctype.h>
+#include <linux/netdevice.h>
+#include <net/cfg80211.h>
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+#include "aicwf_compat_8800d80.h"
+#endif
+
+static void print_help(const char *cmd);
+struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+}};
+
+struct cmd_ef_usrdata {
+ u8 func;
+ u8 cnt;
+ u8 reserved[2];
+ u32 usrdata[3];
+};
+
+#define CMD_MAXARGS 224
+
+static int parse_line(char *line, char *argv[])
+{
+ int nargs = 0;
+
+ while (nargs < CMD_MAXARGS) {
+ /* skip any white space */
+ while ((*line == ' ') || (*line == '\t'))
+ ++line;
+
+ if (*line == '\0') { /* end of line, no more args */
+ argv[nargs] = NULL;
+ return nargs;
+ }
+
+ /* Argument include space should be bracketed by quotation mark */
+ if (*line == '\"') {
+ /* Skip quotation mark */
+ line++;
+
+ /* Begin of argument string */
+ argv[nargs++] = line;
+
+ /* Until end of argument */
+ while (*line && (*line != '\"'))
+ ++line;
+ } else {
+ argv[nargs++] = line; /* begin of argument string */
+
+ /* find end of string */
+ while (*line && (*line != ' ') && (*line != '\t'))
+ ++line;
+ }
+
+ if (*line == '\0') { /* end of line, no more args */
+ argv[nargs] = NULL;
+ return nargs;
+ }
+
+ *line++ = '\0'; /* terminate current arg */
+ }
+
+ pr_info("** Too many args (max. %d) **\n", CMD_MAXARGS);
+
+ return nargs;
+}
+
+unsigned int command_strtoul(const char *cp, char **endp, unsigned int base)
+{
+ unsigned int result = 0, value, is_neg = 0;
+
+ if (*cp == '0') {
+ cp++;
+ if ((*cp == 'x') && isxdigit(cp[1])) {
+ base = 16;
+ cp++;
+ }
+ if (!base)
+ base = 8;
+ }
+ if (!base)
+ base = 10;
+ if (*cp == '-') {
+ is_neg = 1;
+ cp++;
+ }
+ while (isxdigit(*cp) &&
+ (value = isdigit(*cp) ? *cp - '0'
+ : (islower(*cp) ? toupper(*cp) : *cp) - 'A' +
+ 10) < base) {
+ result = result * base + value;
+ cp++;
+ }
+ if (is_neg)
+ result = (unsigned int)((int)result * (-1));
+
+ if (endp)
+ *endp = (char *)cp;
+ return result;
+}
+
+/*
+ * aic_priv_cmd handers.
+ */
+
+static int aic_priv_cmd_set_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 mac_addr[6];
+
+ if (argc < 7)
+ return -EINVAL;
+
+ mac_addr[5] = command_strtoul(argv[1], NULL, 16);
+ mac_addr[4] = command_strtoul(argv[2], NULL, 16);
+ mac_addr[3] = command_strtoul(argv[3], NULL, 16);
+ mac_addr[2] = command_strtoul(argv[4], NULL, 16);
+ mac_addr[1] = command_strtoul(argv[5], NULL, 16);
+ mac_addr[0] = command_strtoul(argv[6], NULL, 16);
+ AICWFDBG(LOGINFO, "set macaddr:%x,%x,%x,%x,%x,%x\n", mac_addr[5],
+ mac_addr[4], mac_addr[3], mac_addr[2], mac_addr[1], mac_addr[0]);
+ rwnx_send_rftest_req(rwnx_hw, SET_MAC_ADDR, sizeof(mac_addr),
+ (u8 *)&mac_addr, NULL);
+ return 0;
+}
+
+static int aic_priv_cmd_get_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ aic_chip_priv_cmd_get_mac_addr(rwnx_hw, argc, argv, command);
+ return 8;
+}
+
+static int aic_priv_cmd_set_bt_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 mac_addr[6];
+
+ if (argc < 7)
+ return -EINVAL;
+
+ mac_addr[5] = command_strtoul(argv[1], NULL, 16);
+ mac_addr[4] = command_strtoul(argv[2], NULL, 16);
+ mac_addr[3] = command_strtoul(argv[3], NULL, 16);
+ mac_addr[2] = command_strtoul(argv[4], NULL, 16);
+ mac_addr[1] = command_strtoul(argv[5], NULL, 16);
+ mac_addr[0] = command_strtoul(argv[6], NULL, 16);
+ AICWFDBG(LOGINFO, "set bt macaddr:%x,%x,%x,%x,%x,%x\n", mac_addr[5],
+ mac_addr[4], mac_addr[3], mac_addr[2], mac_addr[1], mac_addr[0]);
+ rwnx_send_rftest_req(rwnx_hw, SET_BT_MAC_ADDR, sizeof(mac_addr),
+ (u8 *)&mac_addr, NULL);
+ return 0;
+}
+
+static int aic_priv_cmd_get_bt_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ aic_chip_priv_cmd_get_bt_mac_addr(rwnx_hw, argc, argv, command);
+ return 8;
+}
+
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+static int aic_priv_cmd_country_set(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ int ret = 0;
+
+ if (argc < 2) {
+ AICWFDBG(LOGINFO, "%s param err\n", __func__);
+ return -1;
+ }
+
+ AICWFDBG(LOGINFO, "cmd country_set: %s\n", argv[1]);
+ if (strncmp(argv[1], "AUTO", 4) == 0) {
+ rwnx_hw->ccode.auto_set = true;
+ rwnx_hw->ccode.ccode_set = false;
+ rwnx_hw->ccode.ccode_cnt = 0;
+ rwnx_hw->ccode.ccode_rssi = -100;
+ } else if (strncmp(argv[1], "MANUAL", 6) == 0) {
+ rwnx_hw->ccode.auto_set = false;
+ rwnx_hw->ccode.ccode_set = true;
+ } else {
+ rwnx_hw->ccode.ccode_set = true;
+ ret = rwnx_regulatory_set_wiphy_regd(rwnx_hw->wiphy,
+ get_regdomain_from_rwnx_db(rwnx_hw->wiphy,
+ argv[1]));
+ memcpy(rwnx_hw->country_abbr, argv[1], 2);
+ rwnx_hw->ccode.ccode_cnt = 0;
+#ifdef CONFIG_AIC8800_REGION_PW
+ rwnx_send_txpwr_lvl_v3_req(rwnx_hw, get_ccode_region(rwnx_hw->country_abbr));
+#endif
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+ aic_chip_powerlimit_load(rwnx_hw);
+ if (!rwnx_hw->testmode)
+ rwnx_send_me_chan_config_req(rwnx_hw);
+#endif
+ }
+ return ret;
+}
+
+static int aic_priv_cmd_country_get(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ const struct ieee80211_regdomain *regd;
+ u8 buf[3];
+ int bytes_written = 0;
+
+ rcu_read_lock();
+ regd = rcu_dereference(rwnx_hw->wiphy->regd);
+ if (!regd) {
+ rcu_read_unlock();
+ return -ENODATA;
+ }
+ buf[0] = regd->alpha2[0];
+ buf[1] = regd->alpha2[1];
+ rcu_read_unlock();
+ buf[2] = rwnx_hw->ccode.auto_set;
+ memcpy(command, &buf[0], 3);
+ bytes_written = 3;
+ AICWFDBG(LOGINFO, "cmd country_get: %c%c\n", command[0], command[1]);
+
+ return bytes_written;
+}
+#endif
+
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+static int aic_priv_cmd_temp_ctrl_sw(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ if (argc < 2) {
+ AICWFDBG(LOGINFO, "%s param err\n", __func__);
+ return -1;
+ }
+
+ if (command_strtoul(argv[1], NULL, 10) == 0) {
+ AICWFDBG(LOGINFO, "tp to off\n");
+ rwnx_hw->sdiodev->tp_ctrl.on_off = false;
+ rwnx_hw->sdiodev->tp_ctrl.get_level = 0;
+ spin_lock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock);
+ rwnx_hw->sdiodev->tp_ctrl.tm_start = 0;
+ if (timer_pending(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer))
+ //del_timer_sync(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer);
+ timer_delete_sync(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer);
+ spin_unlock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock);
+ } else if (command_strtoul(argv[1], NULL, 10) == 1) {
+ AICWFDBG(LOGINFO, "tp to on\n");
+ rwnx_hw->sdiodev->tp_ctrl.on_off = true;
+ spin_lock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock);
+ rwnx_hw->sdiodev->tp_ctrl.tm_start = 1;
+ mod_timer(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer,
+ jiffies + msecs_to_jiffies(TEMP_GET_INTERVAL));
+ spin_unlock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock);
+ } else {
+ AICWFDBG(LOGINFO, "tp err param\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int aic_priv_cmd_temp_sget(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 func = 0;
+ int bytes_written = 0;
+ s8 tp_res[4];
+
+ if (argc < 2) {
+ AICWFDBG(LOGINFO, "%s param err\n", __func__);
+ return -1;
+ }
+
+ func = (u8)command_strtoul(argv[1], NULL, 10);
+ if (func == 0) { // get
+ if (rwnx_hw->sdiodev->tp_ctrl.on_off) { // on
+ tp_res[0] = 1;
+ if (rwnx_hw->sdiodev->tp_ctrl.set_level == 0)
+ tp_res[1] = rwnx_hw->sdiodev->tp_ctrl.get_level;
+ else
+ tp_res[1] = rwnx_hw->sdiodev->tp_ctrl.set_level;
+ AICWFDBG(LOGINFO, "tp_get on-off: %d, ctrl-level: %d\n", tp_res[0],
+ tp_res[1]);
+ memcpy(command, &tp_res[0], 2);
+ bytes_written = 2;
+ } else { // off
+ tp_res[0] = 0;
+ AICWFDBG(LOGINFO, "tp_get on-off: %d\n", tp_res[0]);
+ memcpy(command, &tp_res[0], 1);
+ bytes_written = 1;
+ }
+ } else if (func == 1) { // set
+ if (!rwnx_hw->sdiodev->tp_ctrl.on_off) {
+ AICWFDBG(LOGINFO, "tp_set sw is off, return\n");
+ tp_res[0] = 0;
+ memcpy(command, &tp_res[0], 1);
+ bytes_written = 1;
+ } else {
+ if (argc < 3) {
+ AICWFDBG(LOGINFO, "%s param err\n", __func__);
+ return -1;
+ }
+ rwnx_hw->sdiodev->tp_ctrl.set_level =
+ command_strtoul(argv[2], NULL, 10);
+ if (rwnx_hw->sdiodev->tp_ctrl.set_level < 0 ||
+ rwnx_hw->sdiodev->tp_ctrl.set_level > 2) {
+ AICWFDBG(LOGINFO, "set_level out of range\n");
+ rwnx_hw->sdiodev->tp_ctrl.set_level = 0;
+ }
+ rwnx_hw->sdiodev->tp_ctrl.get_level = 0;
+ tp_res[0] = 1;
+ tp_res[1] = rwnx_hw->sdiodev->tp_ctrl.set_level;
+ AICWFDBG(LOGINFO, "tp_set ctrl-level: %d\n",
+ rwnx_hw->sdiodev->tp_ctrl.set_level);
+ memcpy(command, &tp_res[0], 2);
+ bytes_written = 2;
+
+ if (rwnx_hw->sdiodev->tp_ctrl.set_level != 0) {
+ spin_lock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock);
+ rwnx_hw->sdiodev->tp_ctrl.tm_start = 0;
+ if (timer_pending(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer))
+ //del_timer_sync(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer);
+ timer_delete_sync(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer);
+ spin_unlock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock);
+ } else if (rwnx_hw->sdiodev->tp_ctrl.set_level == 0) {
+ spin_lock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock);
+ rwnx_hw->sdiodev->tp_ctrl.tm_start = 1;
+ mod_timer(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer,
+ jiffies + msecs_to_jiffies(TEMP_GET_INTERVAL));
+ spin_unlock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock);
+ }
+ }
+ } else {
+ AICWFDBG(LOGINFO, "tp command err\n");
+ return -1;
+ }
+
+ return bytes_written;
+}
+
+static int aic_priv_cmd_set_tmr_intval(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 func = 0;
+ int bytes_written = 0;
+
+ if (argc < 3) {
+ AICWFDBG(LOGINFO, "%s param err\n", __func__);
+ return -1;
+ }
+
+ func = (u8)command_strtoul(argv[1], NULL, 10);
+ if (func == 1) {
+ rwnx_hw->sdiodev->tp_ctrl.interval_t1 =
+ command_strtoul(argv[2], NULL, 10);
+ AICWFDBG(LOGDEBUG, "set tmr_intval_1: %d\n",
+ rwnx_hw->sdiodev->tp_ctrl.interval_t1);
+ memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.interval_t1, 4);
+ bytes_written = 4;
+ } else if (func == 2) {
+ rwnx_hw->sdiodev->tp_ctrl.interval_t2 =
+ command_strtoul(argv[2], NULL, 10);
+ AICWFDBG(LOGDEBUG, "set tmr_intval_2: %d\n",
+ rwnx_hw->sdiodev->tp_ctrl.interval_t2);
+ memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.interval_t2, 4);
+ bytes_written = 4;
+ } else {
+ AICWFDBG(LOGERROR, "%s command err\n", __func__);
+ return -1;
+ }
+
+ return bytes_written;
+}
+
+static int aic_priv_cmd_get_tmr_intval(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 func = 0;
+ int bytes_written = 0;
+
+ if (argc < 2) {
+ AICWFDBG(LOGINFO, "%s param err\n", __func__);
+ return -1;
+ }
+ func = (u8)command_strtoul(argv[1], NULL, 10);
+ if (func == 1) {
+ AICWFDBG(LOGDEBUG, "get tmr_intval_1: %d\n",
+ rwnx_hw->sdiodev->tp_ctrl.interval_t1);
+ memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.interval_t1, 4);
+ bytes_written = 4;
+ } else if (func == 2) {
+ AICWFDBG(LOGDEBUG, "get tmr_intval_1: %d\n",
+ rwnx_hw->sdiodev->tp_ctrl.interval_t2);
+ memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.interval_t2, 4);
+ bytes_written = 4;
+ } else {
+ AICWFDBG(LOGERROR, "%s command err\n", __func__);
+ return -1;
+ }
+
+ return bytes_written;
+}
+
+static int aic_priv_cmd_temp_get(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ int bytes_written = 0;
+ struct mm_set_vendor_swconfig_cfm tp_cfm;
+
+ if (timer_pending(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer)) {
+ if (jiffies_to_msecs(jiffies - rwnx_hw->started_jiffies) < 5000) {
+ AICWFDBG(LOGINFO, "tp_get temp_1: %d\n", rwnx_hw->temp);
+ memcpy(command, &rwnx_hw->temp, 1);
+ } else {
+ if (rwnx_send_get_temp_req(rwnx_hw, &tp_cfm))
+ return -1;
+ AICWFDBG(LOGINFO, "tp_get temp_2: %d\n",
+ tp_cfm.temp_comp_get_cfm.degree);
+ rwnx_hw->sdiodev->tp_ctrl.cur_temp =
+ tp_cfm.temp_comp_get_cfm.degree;
+ memcpy(command, &tp_cfm.temp_comp_get_cfm.degree, 1);
+ }
+ } else {
+ if (rwnx_send_get_temp_req(rwnx_hw, &tp_cfm))
+ return -1;
+ AICWFDBG(LOGINFO, "tp_get temp_3: %d\n",
+ tp_cfm.temp_comp_get_cfm.degree);
+ memcpy(command, &tp_cfm.temp_comp_get_cfm.degree, 1);
+ }
+ bytes_written = 1;
+
+ return bytes_written;
+}
+
+static int aic_priv_cmd_tp_thd_set(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 func = 0;
+ int bytes_written = 0;
+
+ if (argc < 3) {
+ AICWFDBG(LOGERROR, "%s param err\n", __func__);
+ return -1;
+ }
+ func = (u8)command_strtoul(argv[1], NULL, 10);
+
+ if (func == 1) {
+ rwnx_hw->sdiodev->tp_ctrl.tp_thd_1 = command_strtoul(argv[2], NULL, 10);
+ AICWFDBG(LOGINFO, "set tp_thd_1: %d\n",
+ rwnx_hw->sdiodev->tp_ctrl.tp_thd_1);
+ memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.tp_thd_1, 1);
+ bytes_written = 1;
+ } else if (func == 2) {
+ rwnx_hw->sdiodev->tp_ctrl.tp_thd_2 = command_strtoul(argv[2], NULL, 10);
+ AICWFDBG(LOGINFO, "set tp_thd_2: %d\n",
+ rwnx_hw->sdiodev->tp_ctrl.tp_thd_2);
+ memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.tp_thd_2, 1);
+ bytes_written = 1;
+ } else {
+ AICWFDBG(LOGERROR, "%s command err\n", __func__);
+ return -1;
+ }
+ return bytes_written;
+}
+
+static int aic_priv_cmd_tp_thd_get(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 func = 0;
+ int bytes_written = 0;
+
+ if (argc < 2) {
+ AICWFDBG(LOGERROR, "%s param err\n", __func__);
+ return -1;
+ }
+ func = (u8)command_strtoul(argv[1], NULL, 10);
+
+ if (func == 1) {
+ AICWFDBG(LOGINFO, "get tp_thd_1: %d\n",
+ rwnx_hw->sdiodev->tp_ctrl.tp_thd_1);
+ memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.tp_thd_1, 1);
+ bytes_written = 1;
+ } else if (func == 2) {
+ AICWFDBG(LOGINFO, "set tp_thd_2: %d\n",
+ rwnx_hw->sdiodev->tp_ctrl.tp_thd_2);
+ memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.tp_thd_2, 1);
+ bytes_written = 1;
+ } else {
+ AICWFDBG(LOGERROR, "%s command err\n", __func__);
+ return -1;
+ }
+ return bytes_written;
+}
+
+#endif
+static int aic_priv_cmd_set_suspend(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 func = 0;
+ s8 err = -1;
+ int ret = 0;
+
+ if (argc < 2 || rwnx_hw->testmode != 0) {
+ AICWFDBG(LOGERROR, "%s param err or in rf_test mode\n", __func__);
+ return -1;
+ }
+ func = (u8)command_strtoul(argv[1], NULL, 10);
+
+#ifndef CONFIG_AIC8800_AUTO_POWERSAVE
+ if (func == 0) {
+ AICWFDBG(LOGINFO, "priv_cmd suspend 0\n");
+ ret = rwnx_send_me_set_lp_level(rwnx_hw, 0, 1);
+ } else if (func == 1) {
+ AICWFDBG(LOGINFO, "priv_cmd suspend 1\n");
+ ret = rwnx_send_me_set_lp_level(rwnx_hw, 1, 0);
+ if (rwnx_hw->scan_request && rwnx_hw->scanning) {
+ pr_info("AICWF enter suspend, stop scan\n");
+ ret = rwnx_send_scanu_cancel_req(rwnx_hw, NULL);
+ /* make sure fw take effect */
+ msleep(50);
+ if (ret) {
+ pr_info("AICWF %s scanu_cancel fail\n", __func__);
+ return ret;
+ }
+ }
+ } else {
+ AICWFDBG(LOGERROR, "param err\n");
+ ret = -1;
+ }
+#else
+ func = 15;
+#endif
+
+ if (ret == 0) {
+ memcpy(command, &func, 1);
+ return 1;
+ }
+ memcpy(command, &err, 1);
+ return 1;
+}
+
+static int aic_priv_cmd_get_version(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ int bytes_written = 0;
+
+ AICWFDBG(LOGINFO, "Firmware Version: %s\n", rwnx_hw->fw_version);
+ memcpy(command, rwnx_hw->fw_version, 32);
+ bytes_written = 32;
+ return bytes_written;
+}
+
+static int aic_priv_cmd_get_link_status(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ struct rwnx_vif *rwnx_vif = NULL;
+ struct rwnx_vif *rwnx_vif_st = NULL;
+ struct wf_bss_info bi;
+
+ bi.length = 0;
+ list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
+ if (rwnx_vif && rwnx_vif->up &&
+ (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION))
+ rwnx_vif_st = rwnx_vif;
+ }
+ if (!rwnx_vif_st) {
+ AICWFDBG(LOGINFO, "rwnx_vif_st is NULL\n");
+ memcpy(command, &bi, 4);
+ return 4;
+ }
+ if (atomic_read(&rwnx_vif_st->drv_conn_state) !=
+ (int)RWNX_DRV_STATUS_CONNECTED) {
+ AICWFDBG(LOGINFO, "rwnx_vif_st is not conncet\n");
+ memcpy(command, &bi, 4);
+ return 4;
+ }
+
+ bi.ssid_len = rwnx_vif_st->sta.ssid_len;
+ bi.band = rwnx_vif_st->sta.ap->band;
+ bi.width = rwnx_vif_st->sta.ap->width;
+ bi.center_freq = rwnx_vif_st->sta.ap->center_freq;
+ bi.center_freq1 = rwnx_vif_st->sta.ap->center_freq1;
+ bi.center_freq2 = rwnx_vif_st->sta.ap->center_freq2;
+ bi.ht = rwnx_vif_st->sta.ap->ht;
+ bi.vht = rwnx_vif_st->sta.ap->vht;
+ bi.chan = ieee80211_frequency_to_channel(bi.center_freq);
+ memcpy(bi.bssid, rwnx_vif_st->sta.bssid, ETH_ALEN);
+ memset(bi.ssid, 0, sizeof(bi.ssid));
+ memcpy(bi.ssid, rwnx_vif_st->sta.ssid, rwnx_vif_st->sta.ssid_len);
+ bi.length = sizeof(bi);
+
+ memcpy(command, &bi, bi.length);
+ return bi.length;
+}
+
+static int aic_priv_cmd_get_auth_type(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ struct rwnx_vif *rwnx_vif = NULL;
+ s32 val = -1;
+
+ list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
+ if (rwnx_vif && rwnx_vif->up &&
+ (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION) &&
+ atomic_read(&rwnx_vif->drv_conn_state) ==
+ (int)RWNX_DRV_STATUS_CONNECTED)
+ val = rwnx_vif->sta.auth_type;
+ }
+
+ memcpy(command, &val, 4);
+ return 4;
+}
+
+static int aic_priv_cmd_help(struct rwnx_hw *rwnx_hw, int argc, char *argv[],
+ char *command)
+{
+ print_help(argc > 0 ? argv[0] : NULL);
+ return 0;
+}
+
+struct aic_priv_cmd {
+ const char *cmd;
+ int (*handler)(struct rwnx_hw *rwnx_hw, int argc, char *argv[],
+ char *command);
+ const char *usage;
+};
+
+static const struct aic_priv_cmd aic_priv_commands[] = {
+ {"set_mac_addr", aic_priv_cmd_set_mac_addr,
+ "= write WiFi MAC into efuse or flash is limited to a maximum of two times"
+ },
+ {"get_mac_addr", aic_priv_cmd_get_mac_addr,
+ "= display WiFi MAC stored in efuse or flash"},
+ {"set_bt_mac_addr", aic_priv_cmd_set_bt_mac_addr,
+ "= write BT MAC into efuse or flash is limited to a maximum of two times"},
+ {"get_bt_mac_addr", aic_priv_cmd_get_bt_mac_addr,
+ "= display BT MAC stored in efuse or flash"},
+
+ /* The following is not an RF cmd */
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+ {"country_set", aic_priv_cmd_country_set, "<ccode>"},
+ {"country_get", aic_priv_cmd_country_get, "no param"},
+#endif
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+ {"TEMP_CTRL_SW", aic_priv_cmd_temp_ctrl_sw, "<val> 1--open, 0--close"},
+ {"TEMP_CTRL_SET_GET", aic_priv_cmd_temp_sget,
+ "<option> <val> option--0-get,1-set; val--0/1/2"},
+ {"SET_TMR_INTVAL", aic_priv_cmd_set_tmr_intval,
+ "<index> <time> index--0/1, time ms"},
+ {"GET_TMR_INTVAL", aic_priv_cmd_get_tmr_intval, "<index> index--0/1"},
+ {"TEMP_GET", aic_priv_cmd_temp_get, "no param"},
+ {"TEMP_THRESHOLD_SET", aic_priv_cmd_tp_thd_set,
+ "<index> <val> index--0/1, val--degree centigrade"},
+ {"TEMP_THRESHOLD_GET", aic_priv_cmd_tp_thd_get, "<index> inddex--0/1"},
+#endif
+ {"set_suspend", aic_priv_cmd_set_suspend,
+ "<mode>, 1/0----enter/exit lp_level"},
+ {"get_version", aic_priv_cmd_get_version, "no param, get fw version"},
+ {"status", aic_priv_cmd_get_link_status, "no param, get link status"},
+ {"wpa_auth", aic_priv_cmd_get_auth_type,
+ "no param, get AuthenticationType"},
+
+ // Reserve for new aic_priv_cmd.
+ {"help", aic_priv_cmd_help, "= show usage help"},
+ {NULL, NULL, NULL}
+
+};
+
+/*
+ * Prints command usage, lines are padded with the specified string.
+ */
+static void print_help(const char *cmd)
+{
+ int n;
+
+ pr_info("commands:\n");
+ for (n = 0; aic_priv_commands[n].cmd; n++) {
+ if (cmd)
+ pr_info("%s %s\n", aic_priv_commands[n].cmd,
+ aic_priv_commands[n].usage);
+ }
+}
+
+int handle_private_cmd(struct net_device *net, char *command, u32 cmd_len)
+{
+ const struct aic_priv_cmd *cmd, *match = NULL;
+ int count;
+ int bytes_written = 0;
+ char **argv = NULL;
+ int argc;
+ struct rwnx_vif *vif =
+ container_of(net->ieee80211_ptr, struct rwnx_vif, wdev);
+ struct rwnx_hw *p_rwnx_hw = vif->rwnx_hw;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ argv = kzalloc((CMD_MAXARGS + 1) * sizeof(char *), GFP_KERNEL);
+ if (!argv) {
+ AICWFDBG(LOGERROR, "%s alloc argv fail\n", __func__);
+ return -ENOMEM;
+ }
+
+ argc = parse_line(command, argv);
+ if (argc == 0 || argc > CMD_MAXARGS) {
+ AICWFDBG(LOGERROR, "%s params error, count: %d\n", __func__, argc);
+ kfree(argv);
+ return -EINVAL;
+ }
+
+ count = 0;
+ cmd = aic_priv_commands;
+ while (cmd->cmd) {
+ if (strncasecmp(cmd->cmd, argv[0], strlen(argv[0])) == 0 &&
+ strncasecmp(cmd->cmd, argv[0], strlen(cmd->cmd)) == 0) {
+ match = cmd;
+ if (strcasecmp(cmd->cmd, argv[0]) == 0) {
+ /* we have an exact match */
+ count = 1;
+ break;
+ }
+ count++;
+ }
+ cmd++;
+ }
+
+ if (count > 1) {
+ AICWFDBG(LOGINFO,
+ "Ambiguous command '%s'; possible commands:", argv[0]);
+ cmd = aic_priv_commands;
+ while (cmd->cmd) {
+ if (strncasecmp(cmd->cmd, argv[0], strlen(argv[0])) == 0)
+ AICWFDBG(LOGINFO, " %s", cmd->cmd);
+ cmd++;
+ }
+ AICWFDBG(LOGINFO, "\n");
+ } else if (count == 0) {
+ AICWFDBG(LOGERROR, "Unknown command '%s'\n", argv[0]);
+ kfree(argv);
+ return -EINVAL;
+ }
+ AICWFDBG(LOGINFO, "match %s", match->cmd);
+ bytes_written = match->handler(p_rwnx_hw, argc, &argv[0], command);
+
+ if (bytes_written < 0)
+ AICWFDBG(LOGERROR, "wrong param\n");
+
+ kfree(argv);
+ return bytes_written;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.h b/drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.h
new file mode 100644
index 0000000000000..5dbc4c0e3d23d
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.h
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AIC_PRIV_CMD_H_
+#define _AIC_PRIV_CMD_H_
+
+#include "rwnx_defs.h"
+
+enum {
+ SET_TX,
+ SET_TXSTOP,
+ SET_TXTONE,
+ SET_RX,
+ GET_RX_RESULT,
+ SET_RXSTOP,
+ SET_RX_METER,
+ SET_POWER,
+ SET_XTAL_CAP,
+ SET_XTAL_CAP_FINE,
+ GET_EFUSE_BLOCK,
+ SET_FREQ_CAL,
+ SET_FREQ_CAL_FINE,
+ GET_FREQ_CAL,
+ SET_MAC_ADDR,
+ GET_MAC_ADDR,
+ SET_BT_MAC_ADDR,
+ GET_BT_MAC_ADDR,
+ SET_VENDOR_INFO,
+ GET_VENDOR_INFO,
+ RDWR_PWRMM,
+ RDWR_PWRIDX,
+ RDWR_PWRLVL = RDWR_PWRIDX,
+ RDWR_PWROFST,
+ RDWR_DRVIBIT,
+ RDWR_EFUSE_PWROFST,
+ RDWR_EFUSE_DRVIBIT,
+ SET_PAPR,
+ SET_CAL_XTAL,
+ GET_CAL_XTAL_RES,
+ SET_COB_CAL,
+ GET_COB_CAL_RES,
+ RDWR_EFUSE_USRDATA,
+ SET_NOTCH,
+ RDWR_PWROFSTFINE,
+ RDWR_EFUSE_PWROFSTFINE,
+ RDWR_EFUSE_SDIOCFG,
+ RDWR_EFUSE_USBVIDPID,
+ SET_SRRC,
+ SET_FSS,
+ RDWR_EFUSE_HE_OFF,
+ SET_USB_OFF,
+ SET_PLL_TEST,
+ SET_ANT_MODE,
+ GET_NOISE,
+ RDWR_BT_EFUSE_PWROFST,
+ EXEC_FLASH_OPER,
+ RDWR_PWRADD2X,
+ RDWR_EFUSE_PWRADD2X,
+
+};
+
+struct cmd_rf_settx {
+ u8 chan;
+ u8 bw;
+ u8 mode;
+ u8 rate;
+ u16 length;
+ u16 tx_intv_us;
+ s8 max_pwr;
+};
+
+struct cmd_rf_setfreq {
+ u8 val;
+};
+
+struct cmd_rf_rx {
+ u8 chan;
+ u8 bw;
+};
+
+struct cmd_rf_getefuse {
+ u8 block;
+};
+
+struct cmd_rf_setcobcal {
+ u8 dutid;
+ u8 chip_num;
+ u8 dis_xtal;
+};
+
+struct cob_result_ptr {
+ u16 dut_rcv_golden_num;
+ u8 golden_rcv_dut_num;
+ s8 rssi_static;
+ s8 snr_static;
+ s8 dut_rssi_static;
+ u16 reserved;
+};
+
+extern int reg_regdb_size;
+
+unsigned int command_strtoul(const char *cp, char **endp, unsigned int base);
+int handle_private_cmd(struct net_device *net, char *command, u32 cmd_len);
+
+#endif /* _AIC_PRIV_CMD_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8800d80.c b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8800d80.c
new file mode 100644
index 0000000000000..aa297de71256b
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8800d80.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 AIC semiconductor.
+ *
+ * @file aicwf_chip_8800d80.c
+ * @brief Chip operations for AIC8800D80
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/ieee80211.h>
+#include "aicwf_chip_ops.h"
+#include "rwnx_defs.h"
+#include "aicwf_sdio.h"
+#include "aicwf_compat_8800dc.h"
+#include "aicwf_compat_8800d80.h"
+#include "aicwf_txrxif.h"
+#include "aic_priv_cmd.h"
+
+/* ========== 8800D80 specific ========== */
+
+static int aic8800d80_init_capa(struct rwnx_hw *rwnx_hw)
+{
+ rwnx_hw->mod_params->sgi80 = true;
+ rwnx_hw->mod_params->use_80 = true;
+ return 0;
+}
+
+static int aic8800d80_userconfig_load(struct rwnx_hw *rwnx_hw)
+{
+ return rwnx_plat_userconfig_load_8800d80(rwnx_hw);
+}
+
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+static int aic8800d80_powerlimit_load(struct rwnx_hw *rwnx_hw)
+{
+ return rwnx_plat_powerlimit_load_8800d80(rwnx_hw);
+}
+#endif
+
+static int aic8800d80_set_rf_config(struct rwnx_hw *rwnx_hw,
+ struct mm_set_rf_calib_cfm *cfm)
+{
+ return aicwf_set_rf_config_8800d80(rwnx_hw, cfm);
+}
+
+static void aic8800d80_set_rf_calib_cfg(struct mm_set_rf_calib_req *rf_calib_req)
+{
+ rf_calib_req->cal_cfg_24g = 0x0f8f;
+ rf_calib_req->cal_cfg_5g = 0x0f0f;
+}
+
+static u8 aic8800d80_cmd_hdr_checksum(u8 *hdr, int len)
+{
+ return crc8_ponl_107(hdr, 3);
+}
+
+static int aic8800d80_set_stack_start(struct rwnx_hw *rwnx_hw,
+ struct mm_set_stack_start_cfm *cfm)
+{
+ return rwnx_send_set_stack_start_req(rwnx_hw, 1, 0, CO_BIT(5), 0, cfm);
+}
+
+static int aic8800d80_get_userconfig_txpwr_ofst2x(struct txpwr_ofst2x_conf *txpwr_ofst2x)
+{
+ get_userconfig_txpwr_ofst2x_in_fdrv(txpwr_ofst2x);
+ return 0;
+}
+
+static int aic8800d80_ic_system_init(struct rwnx_hw *rwnx_hw, struct dbg_mem_read_cfm *cfm)
+{
+ u32 memdata_temp = 0x00000006;
+ int ret;
+
+ ret = rwnx_send_dbg_mem_block_write_req(rwnx_hw, 0x40504084, 4,
+ &memdata_temp);
+ if (ret) {
+ AICWFDBG(LOGERROR, "[0x40504084] write fail: %d\n", ret);
+ return -1;
+ }
+
+ ret = rwnx_send_dbg_mem_read_req(rwnx_hw, 0x40504084, cfm);
+ if (ret) {
+ AICWFDBG(LOGERROR, "[0x40504084] rd fail\n");
+ return -1;
+ }
+ AICWFDBG(LOGINFO, "rd [0x40504084] = %x\n", cfm->memdata);
+ return ret;
+}
+
+static int aic8800d80_priv_cmd_set_power(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 ana_pwr;
+ u8 pwr;
+
+ ana_pwr = command_strtoul(argv[1], NULL, 10);
+ pwr = ana_pwr;
+ if (ana_pwr > 0x1e)
+ return -EINVAL;
+
+ AICWFDBG(LOGINFO, "pwr =%x\r\n", pwr);
+ rwnx_send_rftest_req(rwnx_hw, SET_POWER, sizeof(pwr), (u8 *)&pwr, NULL);
+ return 0;
+}
+
+static int aic8800d80_priv_cmd_get_freq_cal(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 val;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ rwnx_send_rftest_req(rwnx_hw, GET_FREQ_CAL, 0, NULL, &cfm);
+ memcpy(command, &cfm.rftest_result[0], 4);
+ val = cfm.rftest_result[0];
+
+ AICWFDBG(LOGINFO, "cap=0x%x, cap_fine=0x%x\n", val & 0xff,
+ (val >> 8) & 0xff);
+
+ return 0;
+}
+
+static int aic8800d80_priv_cmd_get_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u32 addr0, addr1;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ rwnx_send_rftest_req(rwnx_hw, GET_MAC_ADDR, 0, NULL, &cfm);
+ memcpy(command, &cfm.rftest_result[0], 8);
+
+ addr0 = cfm.rftest_result[0];
+ addr1 = cfm.rftest_result[1];
+ AICWFDBG(LOGINFO, "0x%x,0x%x\n", addr0, addr1);
+ return 0;
+}
+
+static int aic8800d80_priv_cmd_get_bt_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u32 addr0, addr1;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ rwnx_send_rftest_req(rwnx_hw, GET_BT_MAC_ADDR, 0, NULL, &cfm);
+ memcpy(command, &cfm.rftest_result[0], 8);
+
+ addr0 = cfm.rftest_result[0];
+ addr1 = cfm.rftest_result[1];
+ AICWFDBG(LOGINFO, "0x%x,0x%x\n", addr0, addr1);
+
+ return 0;
+}
+
+static int aic8800d80_priv_cmd_set_vendor_info(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 vendor_info;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ vendor_info = command_strtoul(argv[1], NULL, 16);
+ AICWFDBG(LOGINFO, "set vendor info:%x\n", vendor_info);
+ rwnx_send_rftest_req(rwnx_hw, SET_VENDOR_INFO, 1, &vendor_info, &cfm);
+
+ memcpy(command, &cfm.rftest_result[0], 1);
+ AICWFDBG(LOGINFO, "0x%x\n", cfm.rftest_result[0]);
+ return 1;
+}
+
+static int aic8800d80_priv_cmd_get_vendor_info(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+ rwnx_send_rftest_req(rwnx_hw, GET_VENDOR_INFO, 0, NULL, &cfm);
+
+ memcpy(command, &cfm.rftest_result[0], 1);
+ AICWFDBG(LOGINFO, "0x%x\n", cfm.rftest_result[0]);
+ return 1;
+}
+
+static int aic8800d80_priv_cmd_rdwr_pwrofst(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 func = 0;
+ int res_len = 0;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ if (argc > 1)
+ func = (u8)command_strtoul(argv[1], NULL, 16);
+ if (func == 0) { // read cur
+ rwnx_send_rftest_req(rwnx_hw, RDWR_PWROFST, 0, NULL, &cfm);
+ } else if (func <= 2) { // write 2.4g/5g pwr ofst
+ if (argc > 4) {
+ u8 type = (u8)command_strtoul(argv[2], NULL, 16);
+ u8 chgrp = (u8)command_strtoul(argv[3], NULL, 16);
+ s8 pwrofst = (u8)command_strtoul(argv[4], NULL, 10);
+ u8 buf[4] = {func, type, chgrp, (u8)pwrofst};
+
+ AICWFDBG(LOGINFO, "set pwrofst_%s:[%x][%x]=%d\r\n",
+ (func == 1) ? "2.4g" : "5g", type, chgrp, pwrofst);
+ rwnx_send_rftest_req(rwnx_hw, RDWR_PWROFST, sizeof(buf), buf, &cfm);
+ } else {
+ return -EINVAL;
+ }
+ } else {
+ AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+ return -EINVAL;
+ }
+ res_len = 3 * 3 + 3 * 6;
+ memcpy(command, &cfm.rftest_result[0], res_len);
+ return res_len;
+}
+
+static int aic8800d80_priv_cmd_rdwr_efuse_pwrofst(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 func = 0;
+ int res_len = 0;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ if (argc > 1)
+ func = (u8)command_strtoul(argv[1], NULL, 16);
+ if (func == 0) { // read cur
+ rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_PWROFST, 0, NULL, &cfm);
+ } else if (func <= 2) { // write 2.4g/5g pwr ofst
+ if (argc > 4) {
+ u8 type = (u8)command_strtoul(argv[2], NULL, 16);
+ u8 chgrp = (u8)command_strtoul(argv[3], NULL, 16);
+ s8 pwrofst = (u8)command_strtoul(argv[4], NULL, 10);
+ u8 buf[4] = {func, type, chgrp, (u8)pwrofst};
+
+ AICWFDBG(LOGINFO, "set efuse pwrofst_%s:[%x][%x]=%d\r\n",
+ (func == 1) ? "2.4g" : "5g", type, chgrp, pwrofst);
+ rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_PWROFST, sizeof(buf), buf, &cfm);
+ } else {
+ AICWFDBG(LOGERROR, "wrong args\n");
+ return -EINVAL;
+ }
+ } else {
+ AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+ return -EINVAL;
+ }
+ res_len = (3 * 3 + 3 * 6) * 2; // 3 * 2 (2.4g) + 3 * 6 (5g)
+ memcpy(command, &cfm.rftest_result[0], res_len);
+ return res_len;
+}
+
+/* ========== Ops instances ========== */
+
+const struct aic_chip_ops aic_chip_aic8800d80_ops = {
+ .name = "AIC8800D80",
+ .is_old_ic = false,
+ .limit_by_testmode = true,
+ .use_80_bandwidth = true,
+ .support_priv_cmd_rdwr_pwridx = false,
+ .support_priv_cmd_rdwr_pwrlvl = true,
+ .pwrlvl_result_copy_len = 6,
+ .support_priv_cmd_set_txpwr_loss = true,
+ .init_capa = aic8800d80_init_capa,
+ .userconfig_load = aic8800d80_userconfig_load,
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+ .powerlimit_load = aic8800d80_powerlimit_load,
+#endif
+ .set_rf_calib_cfg = aic8800d80_set_rf_calib_cfg,
+ .set_rf_config = aic8800d80_set_rf_config,
+ .cmd_hdr_checksum = aic8800d80_cmd_hdr_checksum,
+ .set_stack_start = aic8800d80_set_stack_start,
+ .get_userconfig_txpwr_ofst2x = aic8800d80_get_userconfig_txpwr_ofst2x,
+ .ic_system_init = aic8800d80_ic_system_init,
+ .priv_cmd_set_power = aic8800d80_priv_cmd_set_power,
+ .priv_cmd_get_freq_cal = aic8800d80_priv_cmd_get_freq_cal,
+ .priv_cmd_get_mac_addr = aic8800d80_priv_cmd_get_mac_addr,
+ .priv_cmd_get_bt_mac_addr = aic8800d80_priv_cmd_get_bt_mac_addr,
+ .priv_cmd_set_vendor_info = aic8800d80_priv_cmd_set_vendor_info,
+ .priv_cmd_get_vendor_info = aic8800d80_priv_cmd_get_vendor_info,
+ .priv_cmd_rdwr_pwrofst = aic8800d80_priv_cmd_rdwr_pwrofst,
+ .priv_cmd_rdwr_efuse_pwrofst = aic8800d80_priv_cmd_rdwr_efuse_pwrofst,
+};
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8800dc.c b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8800dc.c
new file mode 100644
index 0000000000000..8dab695a5d2a1
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8800dc.c
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 AIC semiconductor.
+ *
+ * @file aicwf_chip_8800dc.c
+ * @brief Chip operations for AIC8801 / AIC8800DC / AIC8800DW
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/ieee80211.h>
+#include "aicwf_chip_ops.h"
+#include "rwnx_defs.h"
+#include "aicwf_sdio.h"
+#include "aicwf_compat_8800dc.h"
+#include "aicwf_compat_8800d80.h"
+#include "rwnx_msg_tx.h"
+#include "aicwf_txrxif.h"
+#include "aic_priv_cmd.h"
+
+static int aic8800dc_init_capa(struct rwnx_hw *rwnx_hw)
+{
+ if (rwnx_hw->mod_params->he_mcs_map == IEEE80211_HE_MCS_SUPPORT_0_11)
+ rwnx_hw->mod_params->he_mcs_map = IEEE80211_HE_MCS_SUPPORT_0_9;
+ return 0;
+}
+
+static int aic8800dc_userconfig_load(struct rwnx_hw *rwnx_hw)
+{
+ return rwnx_plat_userconfig_load_8800dc(rwnx_hw);
+}
+
+static void aic8800dc_set_rf_calib_cfg(struct mm_set_rf_calib_req *rf_calib_req)
+{
+ rf_calib_req->cal_cfg_24g = 0x0f8f;
+ rf_calib_req->cal_cfg_5g = 0;
+}
+
+static int aic8800dc_set_rf_config(struct rwnx_hw *rwnx_hw,
+ struct mm_set_rf_calib_cfm *cfm)
+{
+ return aicwf_set_rf_config_8800dc(rwnx_hw, cfm);
+}
+
+static int aic8800dc_misc_ram_init(struct rwnx_hw *rwnx_hw)
+{
+ return aicwf_fdrv_misc_ram_init_8800dc(rwnx_hw);
+}
+
+static u8 aic8800_cmd_hdr_checksum(u8 *hdr, int len)
+{
+ return 0x00;
+}
+
+static int aic8800dc_set_stack_start(struct rwnx_hw *rwnx_hw,
+ struct mm_set_stack_start_cfm *cfm)
+{
+ int ret = rwnx_send_set_stack_start_req(rwnx_hw, 1, 0, 0, 0, cfm);
+
+ cfm->is_5g_support = false;
+ return ret;
+}
+
+static int aic8800dc_get_userconfig_txpwr_ofst(struct txpwr_ofst_conf *txpwr_ofst)
+{
+ get_userconfig_txpwr_ofst_in_fdrv(txpwr_ofst);
+ return 0;
+}
+
+static int aic8800dc_set_rx_gain(struct rwnx_hw *rwnx_hw, u8 rwnx_rx_gain)
+{
+ return rwnx_send_dbg_mem_mask_write_req(rwnx_hw, 0x4033b300, 0xFF,
+ rwnx_rx_gain);
+}
+
+static int aic8800dc_get_temp(struct rwnx_hw *rwnx_hw, u32 *param_out)
+{
+ int error = 0;
+ struct mm_set_vendor_hwconfig_cfm cfm = {
+ 0,
+ };
+
+ error = rwnx_send_get_temp_hwconfig_req(rwnx_hw, &cfm);
+ if (error) {
+ AICWFDBG(LOGDEBUG, "get_chip_temp err=%d\n", error);
+ return error;
+ }
+
+ if (param_out)
+ param_out[0] = (int32_t)cfm.chip_temp_cfm.degree;
+ else
+ AICWFDBG(LOGDEBUG, "get_chip_temp param_out is NULL\n");
+
+ return error;
+}
+
+static int aic8800dc_priv_cmd_set_power(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 ana_pwr;
+ u8 pwr;
+
+ ana_pwr = command_strtoul(argv[1], NULL, 10);
+ pwr = ana_pwr;
+ if (ana_pwr > 0x1e)
+ return -EINVAL;
+
+ AICWFDBG(LOGINFO, "pwr =%x\r\n", pwr);
+ rwnx_send_rftest_req(rwnx_hw, SET_POWER, sizeof(pwr), (u8 *)&pwr, NULL);
+ return 0;
+}
+
+static int aic8800dc_priv_cmd_get_freq_cal(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 val;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ rwnx_send_rftest_req(rwnx_hw, GET_FREQ_CAL, 0, NULL, &cfm);
+ memcpy(command, &cfm.rftest_result[0], 4);
+ val = cfm.rftest_result[0];
+
+ AICWFDBG(LOGINFO, "cap=0x%x, cap_fine=0x%x\n", val & 0xff,
+ (val >> 8) & 0xff);
+
+ return 0;
+}
+
+static int aic8800dc_priv_cmd_get_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u32 addr0, addr1;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ rwnx_send_rftest_req(rwnx_hw, GET_MAC_ADDR, 0, NULL, &cfm);
+ memcpy(command, &cfm.rftest_result[0], 8);
+
+ addr0 = cfm.rftest_result[0];
+ int rem_cnt = (cfm.rftest_result[1] >> 16) & 0x00FF;
+
+ addr1 = cfm.rftest_result[1] & 0x0000FFFF;
+ AICWFDBG(LOGINFO, "0x%x,0x%x (remain:%x)\n", addr0, addr1, rem_cnt);
+
+ return 0;
+}
+
+static int aic8800dc_priv_cmd_get_bt_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u32 addr0, addr1;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ rwnx_send_rftest_req(rwnx_hw, GET_BT_MAC_ADDR, 0, NULL, &cfm);
+ memcpy(command, &cfm.rftest_result[0], 8);
+
+ addr0 = cfm.rftest_result[0];
+ int rem_cnt = (cfm.rftest_result[1] >> 16) & 0x00FF;
+
+ addr1 = cfm.rftest_result[1] & 0x0000FFFF;
+ AICWFDBG(LOGINFO, "0x%x,0x%x (remain:%x)\n", addr0, addr1, rem_cnt);
+
+ return 0;
+}
+
+static int aic8800dc_priv_cmd_set_vendor_info(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 vendor_info;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ vendor_info = command_strtoul(argv[1], NULL, 16);
+ AICWFDBG(LOGINFO, "set vendor info:%x\n", vendor_info);
+ rwnx_send_rftest_req(rwnx_hw, SET_VENDOR_INFO, 1, &vendor_info, &cfm);
+
+ memcpy(command, &cfm.rftest_result[0], 2);
+ AICWFDBG(LOGINFO, "0x%x\n", cfm.rftest_result[0]);
+ return 2;
+}
+
+static int aic8800dc_priv_cmd_get_vendor_info(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+ rwnx_send_rftest_req(rwnx_hw, GET_VENDOR_INFO, 0, NULL, &cfm);
+
+ memcpy(command, &cfm.rftest_result[0], 2);
+ AICWFDBG(LOGINFO, "0x%x\n", cfm.rftest_result[0]);
+ return 2;
+}
+
+static int aic8800dc_priv_cmd_rdwr_pwrofst(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 func = 0;
+ int res_len = 0;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ if (argc > 1)
+ func = (u8)command_strtoul(argv[1], NULL, 16);
+ if (func == 0) { // read cur
+ rwnx_send_rftest_req(rwnx_hw, RDWR_PWROFST, 0, NULL, &cfm);
+ } else if (func <= 2) { // write 2.4g/5g pwr ofst
+ if (argc > 3) {
+ u8 chgrp = (u8)command_strtoul(argv[2], NULL, 16);
+ s8 pwrofst = (u8)command_strtoul(argv[3], NULL, 10);
+ u8 buf[3] = {func, chgrp, (u8)pwrofst};
+
+ AICWFDBG(LOGINFO, "set pwrofst_%s:[%x]=%d\r\n",
+ (func == 1) ? "2.4g" : "5g", chgrp, pwrofst);
+ rwnx_send_rftest_req(rwnx_hw, RDWR_PWROFST, sizeof(buf), buf, &cfm);
+ } else {
+ return -EINVAL;
+ }
+ } else {
+ AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+ return -EINVAL;
+ }
+ res_len = 3;
+ memcpy(command, &cfm.rftest_result[0], res_len);
+ return res_len;
+}
+
+static int aic8800dc_priv_cmd_rdwr_efuse_pwrofst(struct rwnx_hw *rwnx_hw,
+ int argc, char *argv[],
+ char *command)
+{
+ u8 func = 0;
+ int res_len = 0;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ if (argc > 1)
+ func = (u8)command_strtoul(argv[1], NULL, 16);
+ if (func == 0) { // read cur
+ rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_PWROFST, 0, NULL, &cfm);
+ } else if (func <= 2) { // write 2.4g/5g pwr ofst
+ if (argc > 3) {
+ u8 chgrp = (u8)command_strtoul(argv[2], NULL, 16);
+ s8 pwrofst = (u8)command_strtoul(argv[3], NULL, 10);
+ u8 buf[3] = {func, chgrp, (u8)pwrofst};
+
+ AICWFDBG(LOGINFO, "set efuse pwrofst_%s:[%x]=%d\r\n",
+ (func == 1) ? "2.4g" : "5g", chgrp, pwrofst);
+ rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_PWROFST, sizeof(buf), buf, &cfm);
+ } else {
+ AICWFDBG(LOGERROR, "wrong args\n");
+ return -EINVAL;
+ }
+ } else {
+ AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+ return -EINVAL;
+ }
+ res_len = 3 * 2; // 6 = 3 (2.4g) * 2
+ memcpy(command, &cfm.rftest_result[0], res_len);
+ return res_len;
+}
+
+/* ========== Ops instances ========== */
+
+const struct aic_chip_ops aic_chip_aic8800dc_ops = {
+ .name = "AIC8800DC",
+ .is_old_ic = true,
+ .limit_by_testmode = true,
+ .use_80_bandwidth = false,
+ .support_priv_cmd_rdwr_pwridx = false,
+ .support_priv_cmd_rdwr_pwrlvl = true,
+ .pwrlvl_result_copy_len = 3,
+ .support_priv_cmd_set_txpwr_loss = false,
+ .init_capa = aic8800dc_init_capa,
+ .userconfig_load = aic8800dc_userconfig_load,
+ .set_rf_calib_cfg = aic8800dc_set_rf_calib_cfg,
+ .set_rf_config = aic8800dc_set_rf_config,
+ .misc_ram_init = aic8800dc_misc_ram_init,
+ .cmd_hdr_checksum = aic8800_cmd_hdr_checksum,
+ .set_stack_start = aic8800dc_set_stack_start,
+ .get_userconfig_txpwr_ofst = aic8800dc_get_userconfig_txpwr_ofst,
+ .set_rx_gain = aic8800dc_set_rx_gain,
+ .get_temp = aic8800dc_get_temp,
+ .priv_cmd_set_power = aic8800dc_priv_cmd_set_power,
+ .priv_cmd_get_freq_cal = aic8800dc_priv_cmd_get_freq_cal,
+ .priv_cmd_get_mac_addr = aic8800dc_priv_cmd_get_mac_addr,
+ .priv_cmd_get_bt_mac_addr = aic8800dc_priv_cmd_get_bt_mac_addr,
+ .priv_cmd_set_vendor_info = aic8800dc_priv_cmd_set_vendor_info,
+ .priv_cmd_get_vendor_info = aic8800dc_priv_cmd_get_vendor_info,
+ .priv_cmd_rdwr_pwrofst = aic8800dc_priv_cmd_rdwr_pwrofst,
+ .priv_cmd_rdwr_efuse_pwrofst = aic8800dc_priv_cmd_rdwr_efuse_pwrofst,
+};
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8801.c b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8801.c
new file mode 100644
index 0000000000000..c46930949bdfb
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8801.c
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 AIC semiconductor.
+ *
+ * @file aic_chip_8801.c
+ * @brief Chip operations for AIC8801 / AIC8800DC / AIC8800DW
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/ieee80211.h>
+#include "aicwf_chip_ops.h"
+#include "rwnx_defs.h"
+#include "aicwf_sdio.h"
+#include "aicwf_compat_8800dc.h"
+#include "aicwf_compat_8800d80.h"
+#include "rwnx_msg_tx.h"
+#include "aicwf_txrxif.h"
+#include "aic_priv_cmd.h"
+
+#define FW_USERCONFIG_NAME "aic_userconfig.txt"
+
+static int aic8801_init_capa(struct rwnx_hw *rwnx_hw)
+{
+ if (rwnx_hw->mod_params->he_mcs_map == IEEE80211_HE_MCS_SUPPORT_0_11)
+ rwnx_hw->mod_params->he_mcs_map = IEEE80211_HE_MCS_SUPPORT_0_9;
+ return 0;
+}
+
+static int aic8801_userconfig_load(struct rwnx_hw *rwnx_hw)
+{
+ return rwnx_plat_userconfig_upload(rwnx_hw, FW_USERCONFIG_NAME);
+}
+
+static void aic8801_set_rf_calib_cfg(struct mm_set_rf_calib_req *rf_calib_req)
+{
+ rf_calib_req->cal_cfg_24g = 0xbf;
+ rf_calib_req->cal_cfg_5g = 0x3f;
+}
+
+static int aic8801_set_rf_config(struct rwnx_hw *rwnx_hw,
+ struct mm_set_rf_calib_cfm *cfm)
+{
+ int ret = 0;
+
+ ret = rwnx_send_txpwr_idx_req(rwnx_hw);
+ if (ret)
+ return -1;
+ ret = rwnx_send_txpwr_ofst_req(rwnx_hw);
+ if (ret)
+ return -1;
+
+ if (rwnx_hw->testmode == 0) {
+ ret = rwnx_send_rf_calib_req(rwnx_hw, cfm);
+ if (ret)
+ return -1;
+ }
+ return ret;
+}
+
+static u8 aic8801_cmd_hdr_checksum(u8 *hdr, int len)
+{
+ return 0x00;
+}
+
+static int aic8801_set_stack_start(struct rwnx_hw *rwnx_hw,
+ struct mm_set_stack_start_cfm *cfm)
+{
+#ifdef AIC8800_USE_5G
+ return rwnx_send_set_stack_start_req(rwnx_hw, 1, 0, CO_BIT(5), 0, cfm);
+#else
+ return rwnx_send_set_stack_start_req(rwnx_hw, 1, 0, 0, 0, cfm);
+#endif
+}
+
+static int aic8801_get_userconfig_txpwr_ofst(struct txpwr_ofst_conf *txpwr_ofst)
+{
+ get_userconfig_txpwr_ofst(txpwr_ofst);
+ return 0;
+}
+
+static int aic8801_priv_cmd_set_power(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 ana_pwr;
+ u8 dig_pwr;
+ u8 pwr;
+
+ ana_pwr = command_strtoul(argv[1], NULL, 16);
+ dig_pwr = command_strtoul(argv[2], NULL, 16);
+ pwr = (ana_pwr << 4 | dig_pwr);
+ if (ana_pwr > 0xf || dig_pwr > 0xf)
+ return -EINVAL;
+
+ AICWFDBG(LOGINFO, "pwr =%x\r\n", pwr);
+ rwnx_send_rftest_req(rwnx_hw, SET_POWER, sizeof(pwr), (u8 *)&pwr, NULL);
+ return 0;
+}
+
+static int aic8801_priv_cmd_get_freq_cal(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 val;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ rwnx_send_rftest_req(rwnx_hw, GET_FREQ_CAL, 0, NULL, &cfm);
+ memcpy(command, &cfm.rftest_result[0], 4);
+ val = cfm.rftest_result[0];
+
+ AICWFDBG(LOGINFO, "cap=0x%x (remain:%x), cap_fine=%x (remain:%x)\n",
+ val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff,
+ (val >> 24) & 0xff);
+
+ return 0;
+}
+
+static int aic8801_priv_cmd_get_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u32 addr0, addr1;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ rwnx_send_rftest_req(rwnx_hw, GET_MAC_ADDR, 0, NULL, &cfm);
+ memcpy(command, &cfm.rftest_result[0], 8);
+
+ addr0 = cfm.rftest_result[0];
+ addr1 = cfm.rftest_result[1];
+ AICWFDBG(LOGINFO, "0x%x,0x%x\n", addr0, addr1);
+ return 0;
+}
+
+static int aic8801_priv_cmd_get_bt_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u32 addr0, addr1;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ rwnx_send_rftest_req(rwnx_hw, GET_BT_MAC_ADDR, 0, NULL, &cfm);
+ memcpy(command, &cfm.rftest_result[0], 8);
+
+ addr0 = cfm.rftest_result[0];
+ addr1 = cfm.rftest_result[1];
+ AICWFDBG(LOGINFO, "0x%x,0x%x\n", addr0, addr1);
+
+ return 0;
+}
+
+static int aic8801_priv_cmd_set_vendor_info(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 vendor_info;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ vendor_info = command_strtoul(argv[1], NULL, 16);
+ AICWFDBG(LOGINFO, "set vendor info:%x\n", vendor_info);
+ rwnx_send_rftest_req(rwnx_hw, SET_VENDOR_INFO, 1, &vendor_info, &cfm);
+
+ memcpy(command, &cfm.rftest_result[0], 1);
+ AICWFDBG(LOGINFO, "0x%x\n", cfm.rftest_result[0]);
+ return 1;
+}
+
+static int aic8801_priv_cmd_get_vendor_info(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+ rwnx_send_rftest_req(rwnx_hw, GET_VENDOR_INFO, 0, NULL, &cfm);
+
+ memcpy(command, &cfm.rftest_result[0], 1);
+ AICWFDBG(LOGINFO, "0x%x\n", cfm.rftest_result[0]);
+ return 1;
+}
+
+static int aic8801_priv_cmd_rdwr_pwrofst(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 func = 0;
+ int res_len = 0;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ if (argc > 1)
+ func = (u8)command_strtoul(argv[1], NULL, 16);
+ if (func == 0) { // read cur
+ rwnx_send_rftest_req(rwnx_hw, RDWR_PWROFST, 0, NULL, &cfm);
+ } else if (func <= 2) { // write 2.4g/5g pwr ofst
+ if (argc > 3) {
+ u8 chgrp = (u8)command_strtoul(argv[2], NULL, 16);
+ s8 pwrofst = (u8)command_strtoul(argv[3], NULL, 10);
+ u8 buf[3] = {func, chgrp, (u8)pwrofst};
+
+ AICWFDBG(LOGINFO, "set pwrofst_%s:[%x]=%d\r\n",
+ (func == 1) ? "2.4g" : "5g", chgrp, pwrofst);
+ rwnx_send_rftest_req(rwnx_hw, RDWR_PWROFST, sizeof(buf), buf, &cfm);
+ } else {
+ return -EINVAL;
+ }
+ } else {
+ AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+ return -EINVAL;
+ }
+ res_len = 3 + 4;
+ memcpy(command, &cfm.rftest_result[0], res_len);
+ return res_len;
+}
+
+static int aic8801_priv_cmd_rdwr_efuse_pwrofst(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ u8 func = 0;
+ int res_len = 0;
+ struct dbg_rftest_cmd_cfm cfm = {{
+ 0,
+ }};
+
+ if (argc > 1)
+ func = (u8)command_strtoul(argv[1], NULL, 16);
+ if (func == 0) { // read cur
+ rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_PWROFST, 0, NULL, &cfm);
+ } else if (func <= 2) { // write 2.4g/5g pwr ofst
+ if (argc > 3) {
+ u8 chgrp = (u8)command_strtoul(argv[2], NULL, 16);
+ s8 pwrofst = (u8)command_strtoul(argv[3], NULL, 10);
+ u8 buf[3] = {func, chgrp, (u8)pwrofst};
+
+ AICWFDBG(LOGINFO, "set efuse pwrofst_%s:[%x]=%d\r\n",
+ (func == 1) ? "2.4g" : "5g", chgrp, pwrofst);
+ rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_PWROFST, sizeof(buf), buf, &cfm);
+ } else {
+ AICWFDBG(LOGERROR, "wrong args\n");
+ return -EINVAL;
+ }
+ } else {
+ AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+ return -EINVAL;
+ }
+ res_len = 3 + 4; // 7 = 3(2.4g) + 4(5g)
+ memcpy(command, &cfm.rftest_result[0], res_len);
+ return res_len;
+}
+
+/* ========== Ops instances ========== */
+
+const struct aic_chip_ops aic_chip_aic8801_ops = {
+ .name = "AIC8801",
+ .is_old_ic = true,
+ .limit_by_testmode = false,
+ .use_80_bandwidth = false,
+ .support_priv_cmd_rdwr_pwridx = true,
+ .support_priv_cmd_rdwr_pwrlvl = false,
+ .pwrlvl_result_copy_len = 0,
+ .support_priv_cmd_set_txpwr_loss = false,
+ .init_capa = aic8801_init_capa,
+ .userconfig_load = aic8801_userconfig_load,
+ .set_rf_calib_cfg = aic8801_set_rf_calib_cfg,
+ .set_rf_config = aic8801_set_rf_config,
+ .cmd_hdr_checksum = aic8801_cmd_hdr_checksum,
+ .set_stack_start = aic8801_set_stack_start,
+ .get_userconfig_txpwr_ofst = aic8801_get_userconfig_txpwr_ofst,
+ .priv_cmd_set_power = aic8801_priv_cmd_set_power,
+ .priv_cmd_get_freq_cal = aic8801_priv_cmd_get_freq_cal,
+ .priv_cmd_get_mac_addr = aic8801_priv_cmd_get_mac_addr,
+ .priv_cmd_get_bt_mac_addr = aic8801_priv_cmd_get_bt_mac_addr,
+ .priv_cmd_set_vendor_info = aic8801_priv_cmd_set_vendor_info,
+ .priv_cmd_get_vendor_info = aic8801_priv_cmd_get_vendor_info,
+ .priv_cmd_rdwr_pwrofst = aic8801_priv_cmd_rdwr_pwrofst,
+ .priv_cmd_rdwr_efuse_pwrofst = aic8801_priv_cmd_rdwr_efuse_pwrofst,
+};
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_ops.c b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_ops.c
new file mode 100644
index 0000000000000..6a6f8dc108e95
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_ops.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 AIC semiconductor.
+ *
+ * @file aicwf_chip_ops.c
+ * @brief Wrapper function implementations for chip-specific operations.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include "aicwf_chip_ops.h"
+#include "rwnx_defs.h"
+#include "aicwf_sdio.h"
+#include "lmac_msg.h"
+
+/* ========== SDIO chip properties ========== */
+static const struct aic_sdio_chip_hw aic_sdio_chip_tbl[] = {
+ [PRODUCT_ID_AIC8801] = {
+ .oob_support = false,
+ .use_func2 = true,
+ .use_sdiov3_func = false,
+ .need_flowctrl_mask = true,
+ .use_flowctrl_msg = true,
+ .hdr_cksum = false,
+ .need_func0_intr = false,
+#ifdef CONFIG_AIC8800_AUTO_POWERSAVE
+ .auto_ps_support = false,
+#endif
+ .wakeup_reg_val = 1,
+ .use_hdr_checksum = false,
+ .need_fix_hdr_len = true,
+ },
+ [PRODUCT_ID_AIC8800DC] = {
+ .oob_support = true,
+ .use_func2 = true,
+ .use_sdiov3_func = false,
+ .need_flowctrl_mask = true,
+ .use_flowctrl_msg = false,
+ .hdr_cksum = false,
+ .need_func0_intr = false,
+#ifdef CONFIG_AIC8800_AUTO_POWERSAVE
+ .auto_ps_support = false,
+#endif
+ .wakeup_reg_val = 1,
+ .use_hdr_checksum = false,
+ .need_fix_hdr_len = true,
+ },
+ [PRODUCT_ID_AIC8800D80] = {
+ .oob_support = true,
+ .use_func2 = false,
+ .use_sdiov3_func = true,
+ .need_flowctrl_mask = false,
+ .use_flowctrl_msg = true,
+ .hdr_cksum = true,
+ .need_func0_intr = true,
+#ifdef CONFIG_AIC8800_AUTO_POWERSAVE
+ .auto_ps_support = true,
+#endif
+ .wakeup_reg_val = 0x11,
+ .use_hdr_checksum = true,
+ .need_fix_hdr_len = false,
+ },
+};
+
+int aicwf_sdio_chipmatch(u16 vid, u16 did, u16 *chipid)
+{
+ if (vid != SDIO_VENDOR_ID_AIC8800D80 ||
+ did != SDIO_DEVICE_ID_AIC8800D80)
+ return -ENODEV;
+
+ *chipid = PRODUCT_ID_AIC8800D80;
+ AICWFDBG(LOGINFO, "%s USE AIC8800D80\r\n", __func__);
+ return 0;
+}
+
+const struct aic_sdio_chip_hw *aic_sdio_get_props(u16 chipid)
+{
+ if (chipid < ARRAY_SIZE(aic_sdio_chip_tbl))
+ return &aic_sdio_chip_tbl[chipid];
+ return NULL;
+}
+
+/*========== Chip ops wrappers ========== */
+int aic_chip_init_capa(struct rwnx_hw *rwnx_hw)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->init_capa)
+ return rwnx_hw->chip_ops->init_capa(rwnx_hw);
+ return 0;
+}
+
+int aic_chip_userconfig_load(struct rwnx_hw *rwnx_hw)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->userconfig_load)
+ return rwnx_hw->chip_ops->userconfig_load(rwnx_hw);
+ return -1;
+}
+
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+int aic_chip_powerlimit_load(struct rwnx_hw *rwnx_hw)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->powerlimit_load)
+ return rwnx_hw->chip_ops->powerlimit_load(rwnx_hw);
+ return 0;
+}
+#endif
+
+void aic_chip_set_rf_calib_cfg(struct rwnx_hw *rwnx_hw,
+ struct mm_set_rf_calib_req *rf_calib_req)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->set_rf_calib_cfg)
+ rwnx_hw->chip_ops->set_rf_calib_cfg(rf_calib_req);
+}
+
+int aic_chip_set_rf_config(struct rwnx_hw *rwnx_hw,
+ struct mm_set_rf_calib_cfm *cfm)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->set_rf_config)
+ return rwnx_hw->chip_ops->set_rf_config(rwnx_hw, cfm);
+ return -1;
+}
+
+u8 aic_chip_cmd_hdr_checksum(struct rwnx_hw *rwnx_hw, u8 *hdr, int len)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->cmd_hdr_checksum)
+ return rwnx_hw->chip_ops->cmd_hdr_checksum(hdr, len);
+ return 0x00;
+}
+
+int aic_chip_misc_ram_init(struct rwnx_hw *rwnx_hw)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->misc_ram_init)
+ return rwnx_hw->chip_ops->misc_ram_init(rwnx_hw);
+ return 0;
+}
+
+int aic_chip_set_stack_start(struct rwnx_hw *rwnx_hw,
+ struct mm_set_stack_start_cfm *cfm)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->set_stack_start)
+ return rwnx_hw->chip_ops->set_stack_start(rwnx_hw, cfm);
+ return 0;
+}
+
+int aic_chip_get_userconfig_txpwr_ofst(struct rwnx_hw *rwnx_hw,
+ struct txpwr_ofst_conf *txpwr_ofst)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->get_userconfig_txpwr_ofst)
+ return rwnx_hw->chip_ops->get_userconfig_txpwr_ofst(txpwr_ofst);
+ return 0;
+}
+
+int aic_chip_get_userconfig_txpwr_ofst2x(struct rwnx_hw *rwnx_hw,
+ struct txpwr_ofst2x_conf *txpwr_ofst2x)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->get_userconfig_txpwr_ofst2x)
+ return rwnx_hw->chip_ops->get_userconfig_txpwr_ofst2x(txpwr_ofst2x);
+ return 0;
+}
+
+int aic_chip_set_rx_gain(struct rwnx_hw *rwnx_hw, u8 rwnx_rx_gain)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->set_rx_gain)
+ return rwnx_hw->chip_ops->set_rx_gain(rwnx_hw, rwnx_rx_gain);
+ return 0;
+}
+
+int aic_chip_ic_system_init(struct rwnx_hw *rwnx_hw, struct dbg_mem_read_cfm *cfm)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->ic_system_init)
+ return rwnx_hw->chip_ops->ic_system_init(rwnx_hw, cfm);
+ return 0;
+}
+
+int aic_chip_get_temp(struct rwnx_hw *rwnx_hw, u32 *param_out)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->get_temp)
+ return rwnx_hw->chip_ops->get_temp(rwnx_hw, param_out);
+ return 0;
+}
+
+int aic_chip_priv_cmd_set_power(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->priv_cmd_set_power)
+ return rwnx_hw->chip_ops->priv_cmd_set_power(rwnx_hw, argc, argv, command);
+ return 0;
+}
+
+int aic_chip_priv_cmd_get_freq_cal(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->priv_cmd_get_freq_cal)
+ return rwnx_hw->chip_ops->priv_cmd_get_freq_cal(rwnx_hw, argc, argv, command);
+ return 0;
+}
+
+int aic_chip_priv_cmd_get_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->priv_cmd_get_mac_addr)
+ return rwnx_hw->chip_ops->priv_cmd_get_mac_addr(rwnx_hw, argc, argv, command);
+ return 0;
+}
+
+int aic_chip_priv_cmd_get_bt_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->priv_cmd_get_bt_mac_addr)
+ return rwnx_hw->chip_ops->priv_cmd_get_bt_mac_addr(rwnx_hw, argc, argv, command);
+ return 0;
+}
+
+int aic_chip_priv_cmd_set_vendor_info(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->priv_cmd_set_vendor_info)
+ return rwnx_hw->chip_ops->priv_cmd_set_vendor_info(rwnx_hw, argc, argv, command);
+ return 0;
+}
+
+int aic_chip_priv_cmd_get_vendor_info(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->priv_cmd_get_vendor_info)
+ return rwnx_hw->chip_ops->priv_cmd_get_vendor_info(rwnx_hw, argc, argv, command);
+ return 0;
+}
+
+int aic_chip_priv_cmd_rdwr_pwrofst(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->priv_cmd_rdwr_pwrofst)
+ return rwnx_hw->chip_ops->priv_cmd_rdwr_pwrofst(rwnx_hw, argc, argv, command);
+ return 0;
+}
+
+int aic_chip_priv_cmd_rdwr_efuse_pwrofst(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command)
+{
+ if (rwnx_hw->chip_ops && rwnx_hw->chip_ops->priv_cmd_rdwr_efuse_pwrofst)
+ return rwnx_hw->chip_ops->priv_cmd_rdwr_efuse_pwrofst(rwnx_hw, argc, argv, command);
+ return 0;
+}
+
+const struct aic_chip_ops *aic_chip_ops_select(u16 chipid)
+{
+ if (chipid == PRODUCT_ID_AIC8800D80)
+ return &aic_chip_aic8800d80_ops;
+
+ return NULL;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_ops.h b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_ops.h
new file mode 100644
index 0000000000000..c8423bbba48c1
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_ops.h
@@ -0,0 +1,184 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2024 AIC semiconductor.
+ *
+ * @file aicwf_chip_ops.h
+ * @brief Chip-specific operation interfaces for AIC8800 series.
+ */
+#ifndef _AICWF_CHIP_OPS_H_
+#define _AICWF_CHIP_OPS_H_
+
+#include <linux/types.h>
+#include "lmac_types.h"
+#include "lmac_msg.h"
+
+struct rwnx_hw;
+struct mm_set_rf_calib_req;
+struct mm_set_rf_calib_cfm;
+struct mm_set_stack_start_cfm;
+struct aic_sdio_dev;
+
+/* SDIO chip hardware properties */
+struct aic_sdio_chip_hw {
+ bool oob_support;
+ bool use_func2;
+ bool use_sdiov3_func;
+ bool need_flowctrl_mask;
+ bool use_flowctrl_msg;
+ bool hdr_cksum;
+ bool need_func0_intr;
+#ifdef CONFIG_AIC8800_AUTO_POWERSAVE
+ bool auto_ps_support;
+#endif
+ u8 wakeup_reg_val;
+ bool use_hdr_checksum;
+ bool need_fix_hdr_len;
+};
+
+/* Match SDIO vendor/device IDs to a chip product ID */
+int aicwf_sdio_chipmatch(u16 vendor, u16 device, u16 *chipid);
+
+/* Get SDIO hardware properties for a given chip product ID */
+const struct aic_sdio_chip_hw *aic_sdio_get_props(u16 chipid);
+
+/*
+ * struct aic_chip_ops - Chip-specific operations
+ *
+ * Each chip variant provides an instance of this struct.
+ * Selected at probe time based on chipid, then used to
+ * eliminate chipid-based if-else branching throughout the driver.
+ */
+struct aic_chip_ops {
+ const char *name;
+
+ const bool is_old_ic;
+
+ const bool limit_by_testmode;
+
+ const bool use_80_bandwidth;
+
+ const bool support_priv_cmd_rdwr_pwridx;
+
+ const bool support_priv_cmd_rdwr_pwrlvl;
+
+ const int pwrlvl_result_copy_len;
+
+ const bool support_priv_cmd_set_txpwr_loss;
+
+ /* Initialize chip-specific capabilities in mod_params */
+ int (*init_capa)(struct rwnx_hw *rwnx_hw);
+
+ /* Load chip-specific user configuration */
+ int (*userconfig_load)(struct rwnx_hw *rwnx_hw);
+
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+ /* Load power limit table */
+ int (*powerlimit_load)(struct rwnx_hw *rwnx_hw);
+#endif
+
+ /* Set RF calibration config values (cal_cfg_24g / cal_cfg_5g) */
+ void (*set_rf_calib_cfg)(struct mm_set_rf_calib_req *rf_calib_req);
+
+ /* Chip-specific RF init sequence (txpwr, RF config, etc.) */
+ int (*set_rf_config)(struct rwnx_hw *rwnx_hw,
+ struct mm_set_rf_calib_cfm *cfm);
+
+ /* Misc RAM initialization (DPD, etc.) */
+ int (*misc_ram_init)(struct rwnx_hw *rwnx_hw);
+
+ /* Compute checksum for firmware command header */
+ u8 (*cmd_hdr_checksum)(u8 *hdr, int len);
+
+ /* Initialize the FW stack start (params differ per chip) */
+ int (*set_stack_start)(struct rwnx_hw *rwnx_hw,
+ struct mm_set_stack_start_cfm *cfm);
+
+ /* Get userconfig TX power offset values */
+ int (*get_userconfig_txpwr_ofst)(struct txpwr_ofst_conf *txpwr_ofst);
+
+ int (*get_userconfig_txpwr_ofst2x)(struct txpwr_ofst2x_conf *txpwr_ofst2x);
+
+ /* Set RX gain */
+ int (*set_rx_gain)(struct rwnx_hw *rwnx_hw, u8 rwnx_rx_gain);
+
+ int (*ic_system_init)(struct rwnx_hw *rwnx_hw, struct dbg_mem_read_cfm *cfm);
+
+ /* Get chip temperature */
+ int (*get_temp)(struct rwnx_hw *rwnx_hw, u32 *param_out);
+
+ /* Handle private command to set power */
+ int (*priv_cmd_set_power)(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+
+ /* Handle private command to get frequency calibration */
+ int (*priv_cmd_get_freq_cal)(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+
+ /* Handle private command to get MAC address */
+ int (*priv_cmd_get_mac_addr)(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+
+ /* Handle private command to set BT MAC address */
+ int (*priv_cmd_get_bt_mac_addr)(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+
+ /* Handle private command to set vendor info */
+ int (*priv_cmd_set_vendor_info)(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+
+ /* Handle private command to get vendor info */
+ int (*priv_cmd_get_vendor_info)(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+
+ /* Handle private command to read/write power offset */
+ int (*priv_cmd_rdwr_pwrofst)(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+
+ /* Handle private command to read/write efuse power offset */
+ int (*priv_cmd_rdwr_efuse_pwrofst)(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+
+};
+
+extern const struct aic_chip_ops aic_chip_aic8800d80_ops;
+
+/* Select the appropriate chip ops based on chip product ID */
+const struct aic_chip_ops *aic_chip_ops_select(u16 chipid);
+
+/* Wrapper function declarations (implemented in aic_chip_ops.c) */
+int aic_chip_init_capa(struct rwnx_hw *rwnx_hw);
+int aic_chip_userconfig_load(struct rwnx_hw *rwnx_hw);
+int aic_chip_powerlimit_load(struct rwnx_hw *rwnx_hw);
+void aic_chip_set_rf_calib_cfg(struct rwnx_hw *rwnx_hw,
+ struct mm_set_rf_calib_req *rf_calib_req);
+int aic_chip_set_rf_config(struct rwnx_hw *rwnx_hw,
+ struct mm_set_rf_calib_cfm *cfm);
+u8 aic_chip_cmd_hdr_checksum(struct rwnx_hw *rwnx_hw, u8 *hdr, int len);
+int aic_chip_misc_ram_init(struct rwnx_hw *rwnx_hw);
+int aic_chip_set_stack_start(struct rwnx_hw *rwnx_hw,
+ struct mm_set_stack_start_cfm *cfm);
+int aic_chip_get_userconfig_txpwr_ofst(struct rwnx_hw *rwnx_hw,
+ struct txpwr_ofst_conf *txpwr_ofst);
+int aic_chip_get_userconfig_txpwr_ofst2x(struct rwnx_hw *rwnx_hw,
+ struct txpwr_ofst2x_conf *txpwr_ofst2x);
+int aic_chip_set_rx_gain(struct rwnx_hw *rwnx_hw, u8 rwnx_rx_gain);
+int aic_chip_ic_system_init(struct rwnx_hw *rwnx_hw,
+ struct dbg_mem_read_cfm *cfm);
+int aic_chip_get_temp(struct rwnx_hw *rwnx_hw, u32 *param_out);
+int aic_chip_priv_cmd_set_power(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+int aic_chip_priv_cmd_get_freq_cal(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+int aic_chip_priv_cmd_get_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+int aic_chip_priv_cmd_get_bt_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+int aic_chip_priv_cmd_set_vendor_info(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+int aic_chip_priv_cmd_get_vendor_info(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+int aic_chip_priv_cmd_rdwr_pwrofst(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+int aic_chip_priv_cmd_rdwr_efuse_pwrofst(struct rwnx_hw *rwnx_hw, int argc,
+ char *argv[], char *command);
+#endif /* _AICWF_CHIP_OPS_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800d80.c b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800d80.c
new file mode 100644
index 0000000000000..55f5d92d1e8a2
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800d80.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief 8800d80 compatibility configuration
+ *
+ ******************************************************************************
+ */
+
+#include "aicwf_compat_8800d80.h"
+#include "rwnx_platform.h"
+
+#define FW_USERCONFIG_NAME_8800D80 "aic_userconfig_8800d80.txt"
+#define FW_POWERLIMIT_NAME_8800D80 "aic_powerlimit_8800d80.txt"
+
+int aicwf_set_rf_config_8800d80(struct rwnx_hw *rwnx_hw,
+ struct mm_set_rf_calib_cfm *cfm)
+{
+ int ret = 0;
+
+ ret = rwnx_send_txpwr_lvl_v3_req(rwnx_hw, 0);
+ if (ret)
+ return -1;
+
+ ret = rwnx_send_txpwr_lvl_adj_req(rwnx_hw);
+ if (ret)
+ return -1;
+
+ ret = rwnx_send_txpwr_ofst2x_req(rwnx_hw);
+ if (ret)
+ return -1;
+
+ ret = rwnx_send_rf_calib_req(rwnx_hw, cfm);
+ if (ret)
+ return -1;
+
+ return 0;
+}
+
+int rwnx_plat_userconfig_load_8800d80(struct rwnx_hw *rwnx_hw)
+{
+ int size;
+ u32 *dst = NULL;
+ char *filename = FW_USERCONFIG_NAME_8800D80;
+
+ AICWFDBG(LOGINFO, "userconfig file path:%s \r\n", filename);
+
+ /* load file */
+ size = rwnx_request_firmware_common(rwnx_hw, &dst, filename);
+ if (size <= 0) {
+ AICWFDBG(LOGERROR, "wrong size of firmware file\n");
+ dst = NULL;
+ return 0;
+ }
+
+ /* Copy the file on the Embedded side */
+ AICWFDBG(LOGINFO, "### Load file done: %s, size=%d\n", filename, size);
+
+ rwnx_plat_userconfig_parsing3((char *)dst, size);
+
+ rwnx_release_firmware_common(&dst);
+
+ AICWFDBG(LOGINFO, "userconfig download complete\n\n");
+ return 0;
+}
+
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+int rwnx_plat_powerlimit_load_8800d80(struct rwnx_hw *rwnx_hw)
+{
+ int size;
+ u32 *dst = NULL;
+ char *filename = FW_POWERLIMIT_NAME_8800D80;
+
+ AICWFDBG(LOGDEBUG, "powerlimit file path:%s \r\n", filename);
+
+ /* load file */
+ size = rwnx_request_firmware_common(rwnx_hw, &dst, filename);
+ if (size <= 0) {
+ AICWFDBG(LOGERROR, "wrong size of cfg file\n");
+ dst = NULL;
+ return 0;
+ }
+
+ AICWFDBG(LOGDEBUG, "### Load file done: %s, size=%d\n", filename, size);
+
+ /* parsing the file */
+ rwnx_plat_powerlimit_parsing((char *)dst, size, rwnx_hw->country_abbr);
+
+ rwnx_release_firmware_common(&dst);
+
+ AICWFDBG(LOGDEBUG, "powerlimit download complete\n\n");
+ return 0;
+}
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800d80.h b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800d80.h
new file mode 100644
index 0000000000000..22f882d3aa3a2
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800d80.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AICWF_COMPAT_8800D80_H_
+#define _AICWF_COMPAT_8800D80_H_
+#include "reg_access.h"
+#include "rwnx_main.h"
+#include "rwnx_msg_tx.h"
+#include <linux/types.h>
+
+int aicwf_set_rf_config_8800d80(struct rwnx_hw *rwnx_hw,
+ struct mm_set_rf_calib_cfm *cfm);
+int rwnx_plat_userconfig_load_8800d80(struct rwnx_hw *rwnx_hw);
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+int rwnx_plat_powerlimit_load_8800d80(struct rwnx_hw *rwnx_hw);
+#endif
+
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800dc.c b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800dc.c
new file mode 100644
index 0000000000000..9a443ee55a768
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800dc.c
@@ -0,0 +1,505 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief 8800dc compatibility configuration
+ *
+ ******************************************************************************
+ */
+
+#include "aic_bsp_export.h"
+#include "reg_access.h"
+#include "rwnx_main.h"
+#include "rwnx_msg_tx.h"
+#include "aicwf_compat_8800dc.h"
+#include "rwnx_platform.h"
+
+#define RWNX_MAC_RF_PATCH_BASE_NAME_8800DC "fmacfw_rf_patch_8800dc"
+#define RWNX_MAC_RF_PATCH_NAME_8800DC RWNX_MAC_RF_PATCH_BASE_NAME_8800DC ".bin"
+#define FW_USERCONFIG_NAME_8800DC "aic_userconfig_8800dc.txt"
+#define FW_USERCONFIG_NAME_8800DW "aic_userconfig_8800dw.txt"
+
+static u32 wifi_txgain_table_24g_8800dcdw[32] = {0xA4B22189, // index 0
+ 0x00007825,
+ 0xA4B2214B, // index 1
+ 0x00007825,
+ 0xA4B2214F, // index 2
+ 0x00007825,
+ 0xA4B221D5, // index 3
+ 0x00007825,
+ 0xA4B221DC, // index 4
+ 0x00007825,
+ 0xA4B221E5, // index 5
+ 0x00007825,
+ 0xAC9221E5, // index 6
+ 0x00006825,
+ 0xAC9221EF, // index 7
+ 0x00006825,
+ 0xBC9221EE, // index 8
+ 0x00006825,
+ 0xBC9221FF, // index 9
+ 0x00006825,
+ 0xBC9221FF, // index 10
+ 0x00004025,
+ 0xB792203F, // index 11
+ 0x00004026,
+ 0xDC92203F, // index 12
+ 0x00004025,
+ 0xE692203F, // index 13
+ 0x00004025,
+ 0xFF92203F, // index 14
+ 0x00004035,
+ 0xFFFE203F, // index 15
+ 0x00004832};
+
+static u32 wifi_txgain_table_24g_1_8800dcdw[32] = {
+ 0x096E2011, // index 0
+ 0x00004001,
+ 0x096E2015, // index 1
+ 0x00004001,
+ 0x096E201B, // index 2
+ 0x00004001,
+ 0x116E2018, // index 3
+ 0x00004001,
+ 0x116E201E, // index 4
+ 0x00004001,
+ 0x116E2023, // index 5
+ 0x00004001,
+ 0x196E2021, // index 6
+ 0x00004001,
+ 0x196E202B, // index 7
+ 0x00004001,
+ 0x216E202B, // index 8
+ 0x00004001,
+ 0x236E2027, // index 9
+ 0x00004001,
+ 0x236E2031, // index 10
+ 0x00004001,
+ 0x246E2039, // index 11
+ 0x00004001,
+ 0x26922039, // index 12
+ 0x00004001,
+ 0x2E92203F, // index 13
+ 0x00004001,
+ 0x3692203F, // index 14
+ 0x00004001,
+ 0x3FF2203F, // index 15
+ 0x00004001,
+};
+
+static u32 wifi_txgain_table_24g_8800dcdw_h[32] = {
+ 0xA55629C9, // index 0
+ 0x00005825,
+ 0xAE5629C9, // index 1
+ 0x00005825,
+ 0xAD5629CD, // index 2
+ 0x00005825,
+ 0xAD5629D1, // index 3
+ 0x00005825,
+ 0xAD5629D7, // index 4
+ 0x00005825,
+ 0xAD5629DE, // index 5
+ 0x00005825,
+ 0xAD5629E6, // index 6
+ 0x00005825,
+ 0xBD5629E6, // index 7
+ 0x00005825,
+ 0xBD5629F0, // index 8
+ 0x00005825,
+ 0xCD5629F0, // index 9
+ 0x00005825,
+ 0xE55629F0, // index 10
+ 0x00005825,
+ 0xE55629FF, // index 11
+ 0x00005825,
+ 0xE55629FF, // index 12
+ 0x00002825,
+ 0xE75629FF, // index 13
+ 0x00002825,
+ 0xFF5629FF, // index 14
+ 0x00001825,
+ 0xFF5628FF, // index 15
+ 0x00001025,
+};
+
+static u32 wifi_txgain_table_24g_1_8800dcdw_h[32] = {
+ 0x941A2048, // index 0
+ 0x00001825,
+ 0x961A2048, // index 1
+ 0x00001825,
+ 0x9D1A2048, // index 2
+ 0x00001825,
+ 0x9A1A204F, // index 3
+ 0x00001825,
+ 0x961A204F, // index 4
+ 0x00001825,
+ 0x9A1A2057, // index 5
+ 0x00001825,
+ 0x9C1A2057, // index 6
+ 0x00001825,
+ 0xA31A205B, // index 7
+ 0x00001825,
+ 0xAB1A205B, // index 8
+ 0x00001825,
+ 0xAD1A205B, // index 9
+ 0x00001825,
+ 0xA71A2064, // index 10
+ 0x00001825,
+ 0xAD1A2070, // index 11
+ 0x00001825,
+ 0xAD72207F, // index 12
+ 0x00001825,
+ 0xBCAE207F, // index 13
+ 0x00001825,
+ 0xBFB2207F, // index 14
+ 0x00001825,
+ 0xD73A207F, // index 15
+ 0x00001825,
+};
+
+static u32 wifi_rxgain_table_24g_20m_8800dcdw[64] = {
+ 0x82f282d1, // index 0
+ 0x9591a324, 0x80808419, 0x000000f0,
+ 0x42f282d1, // index 1
+ 0x95923524, 0x80808419, 0x000000f0,
+ 0x22f282d1, // index 2
+ 0x9592c724, 0x80808419, 0x000000f0,
+ 0x02f282d1, // index 3
+ 0x9591a324, 0x80808419, 0x000000f0,
+ 0x06f282d1, // index 4
+ 0x9591a324, 0x80808419, 0x000000f0,
+ 0x0ef29ad1, // index 5
+ 0x9591a324, 0x80808419, 0x000000f0,
+ 0x0ef29ad3, // index 6
+ 0x95923524, 0x80808419, 0x000000f0,
+ 0x0ef29ad7, // index 7
+ 0x9595a324, 0x80808419, 0x000000f0,
+ 0x02f282d2, // index 8
+ 0x95951124, 0x80808419, 0x000000f0,
+ 0x02f282f4, // index 9
+ 0x95951124, 0x80808419, 0x000000f0,
+ 0x02f282e6, // index 10
+ 0x9595a324, 0x80808419, 0x000000f0,
+ 0x02f282e6, // index 11
+ 0x9599a324, 0x80808419, 0x000000f0,
+ 0x02f282e6, // index 12
+ 0x959da324, 0x80808419, 0x000000f0,
+ 0x02f282e6, // index 13
+ 0x959f5924, 0x80808419, 0x000000f0,
+ 0x06f282e6, // index 14
+ 0x959f5924, 0x80808419, 0x000000f0,
+ 0x0ef29ae6, // index 15
+ 0x959f5924, // loft [35:34]=3
+ 0x80808419, 0x000000f0};
+
+static u32 wifi_rxgain_table_24g_40m_8800dcdw[64] = {
+ 0x83428151, // index 0
+ 0x9631a328, 0x80808419, 0x000000f0,
+ 0x43428151, // index 1
+ 0x96323528, 0x80808419, 0x000000f0,
+ 0x23428151, // index 2
+ 0x9632c728, 0x80808419, 0x000000f0,
+ 0x03428151, // index 3
+ 0x9631a328, 0x80808419, 0x000000f0,
+ 0x07429951, // index 4
+ 0x9631a328, 0x80808419, 0x000000f0,
+ 0x0f42d151, // index 5
+ 0x9631a328, 0x80808419, 0x000000f0,
+ 0x0f42d153, // index 6
+ 0x96323528, 0x80808419, 0x000000f0,
+ 0x0f42d157, // index 7
+ 0x9635a328, 0x80808419, 0x000000f0,
+ 0x03428152, // index 8
+ 0x96351128, 0x80808419, 0x000000f0,
+ 0x03428174, // index 9
+ 0x96351128, 0x80808419, 0x000000f0,
+ 0x03428166, // index 10
+ 0x9635a328, 0x80808419, 0x000000f0,
+ 0x03428166, // index 11
+ 0x9639a328, 0x80808419, 0x000000f0,
+ 0x03428166, // index 12
+ 0x963da328, 0x80808419, 0x000000f0,
+ 0x03428166, // index 13
+ 0x963f5928, 0x80808419, 0x000000f0,
+ 0x07429966, // index 14
+ 0x963f5928, 0x80808419, 0x000000f0,
+ 0x0f42d166, // index 15
+ 0x963f5928, 0x80808419, 0x000000f0};
+
+#define RAM_LMAC_FW_ADDR 0x00150000
+#ifdef CONFIG_DPD
+#if (defined(CONFIG_DPD) && !defined(CONFIG_FORCE_DPD_CALIB))
+extern int is_file_exist(char *name);
+#endif
+extern struct rf_misc_ram_lite_t dpd_res;
+
+int aicwf_fdrv_dpd_result_apply_8800dc(struct rwnx_hw *rwnx_hw,
+ struct rf_misc_ram_lite_t *dpd_res)
+{
+ int ret = 0;
+ u32 cfg_base = 0x10164;
+ struct dbg_mem_read_cfm cfm;
+ u32 misc_ram_addr;
+ u32 ram_base_addr, ram_byte_cnt;
+
+ AICWFDBG(LOGINFO, "bit_mask[1]=%x\n", dpd_res->bit_mask[1]);
+ if (dpd_res->bit_mask[1] == 0) {
+ AICWFDBG(LOGERROR, "void dpd_res, bypass it.\n");
+ return 0;
+ }
+ if (rwnx_hw->testmode == 1)
+ cfg_base = RAM_LMAC_FW_ADDR + 0x0164;
+ ret = rwnx_send_dbg_mem_read_req(rwnx_hw, cfg_base + 0x14, &cfm);
+ if (ret) {
+ AICWFDBG(LOGERROR, "rf misc ram[0x%x] rd fail: %d\n", cfg_base + 0x14,
+ ret);
+ return ret;
+ }
+ misc_ram_addr = cfm.memdata;
+ AICWFDBG(LOGINFO, "misc_ram_addr: %x\n", misc_ram_addr);
+ /* Copy dpd_res on the Embedded side */
+ // bit_mask
+ AICWFDBG(LOGINFO, "bit_mask[0]=%x\n", dpd_res->bit_mask[0]);
+ ram_base_addr = misc_ram_addr + offsetof(struct rf_misc_ram_t, bit_mask);
+ ram_byte_cnt = MEMBER_SIZE(struct rf_misc_ram_t, bit_mask) +
+ MEMBER_SIZE(struct rf_misc_ram_t, reserved);
+ ret = rwnx_send_dbg_mem_block_write_req(rwnx_hw, ram_base_addr, ram_byte_cnt,
+ (u32 *)&dpd_res->bit_mask[0]);
+ if (ret) {
+ AICWFDBG(LOGERROR, "bit_mask wr fail: %x, ret:%d\r\n", ram_base_addr,
+ ret);
+ return ret;
+ }
+ // dpd_high
+ AICWFDBG(LOGINFO, "dpd_high[0]=%x\n", dpd_res->dpd_high[0]);
+ ram_base_addr = misc_ram_addr + offsetof(struct rf_misc_ram_t, dpd_high);
+ ram_byte_cnt = MEMBER_SIZE(struct rf_misc_ram_t, dpd_high);
+ ret = rwnx_send_dbg_mem_block_write_req(rwnx_hw, ram_base_addr, ram_byte_cnt,
+ (u32 *)&dpd_res->dpd_high[0]);
+ if (ret) {
+ AICWFDBG(LOGERROR, "dpd_high wr fail: %x, ret:%d\r\n", ram_base_addr,
+ ret);
+ return ret;
+ }
+ // loft_res
+ AICWFDBG(LOGINFO, "loft_res[0]=%x\n", dpd_res->loft_res[0]);
+ ram_base_addr = misc_ram_addr + offsetof(struct rf_misc_ram_t, loft_res);
+ ram_byte_cnt = MEMBER_SIZE(struct rf_misc_ram_t, loft_res);
+ ret = rwnx_send_dbg_mem_block_write_req(rwnx_hw, ram_base_addr, ram_byte_cnt,
+ (u32 *)&dpd_res->loft_res[0]);
+ if (ret) {
+ AICWFDBG(LOGERROR, "loft_res wr fail: %x, ret:%d\r\n", ram_base_addr,
+ ret);
+ return ret;
+ }
+ return ret;
+}
+
+#ifndef CONFIG_FORCE_DPD_CALIB
+int aicwf_fdrv_dpd_result_load_8800dc(struct rwnx_hw *rwnx_hw,
+ struct rf_misc_ram_lite_t *dpd_res)
+{
+ int ret = 0;
+ int size;
+ u32 *dst = NULL;
+ char *filename = FW_DPDRESULT_NAME_8800DC;
+
+ AICWFDBG(LOGINFO, "dpd_res file path:%s \r\n", filename);
+ /* load file */
+ size = rwnx_request_firmware_common(rwnx_hw, &dst, filename);
+ if (size <= 0) {
+ AICWFDBG(LOGERROR, "wrong size of dpd_res file\n");
+ dst = NULL;
+ return -1;
+ }
+ AICWFDBG(LOGINFO, "### Load file done: %s, size=%d, dst[0]=%x\n", filename,
+ size, dst[0]);
+ memcpy((u8 *)dpd_res, (u8 *)dst, sizeof(struct rf_misc_ram_lite_t));
+ if (dst)
+ rwnx_release_firmware_common(&dst);
+ return ret;
+}
+#endif
+#endif
+
+int aicwf_fdrv_misc_ram_init_8800dc(struct rwnx_hw *rwnx_hw)
+{
+ int ret = 0;
+ u32 cfg_base = 0x10164;
+ struct dbg_mem_read_cfm cfm;
+ u32 misc_ram_addr;
+ u32 misc_ram_size = 12;
+ int i;
+
+ if (rwnx_hw->testmode == 1)
+ cfg_base = RAM_LMAC_FW_ADDR + 0x0164;
+ // init misc ram
+ ret = rwnx_send_dbg_mem_read_req(rwnx_hw, cfg_base + 0x14, &cfm);
+ if (ret) {
+ AICWFDBG(LOGERROR, "rf misc ram[0x%x] rd fail: %d\n", cfg_base + 0x14,
+ ret);
+ return ret;
+ }
+ misc_ram_addr = cfm.memdata;
+ AICWFDBG(LOGERROR, "misc_ram_addr=%x\n", misc_ram_addr);
+ for (i = 0; i < (misc_ram_size / 4); i++) {
+ ret = rwnx_send_dbg_mem_write_req(rwnx_hw, misc_ram_addr + i * 4, 0);
+ if (ret) {
+ AICWFDBG(LOGERROR, "rf misc ram[0x%x] wr fail: %d\n",
+ misc_ram_addr + i * 4, ret);
+ return ret;
+ }
+ }
+ return ret;
+}
+
+int aicwf_set_rf_config_8800dc(struct rwnx_hw *rwnx_hw,
+ struct mm_set_rf_calib_cfm *cfm)
+{
+ int ret = 0;
+
+ ret = rwnx_send_txpwr_lvl_req(rwnx_hw);
+ if (ret)
+ return -1;
+
+ ret = rwnx_send_txpwr_ofst_req(rwnx_hw);
+ if (ret)
+ return -1;
+
+ if (rwnx_hw->testmode == 0) {
+ if (IS_CHIP_ID_H()) {
+ ret = rwnx_send_rf_config_req(rwnx_hw, 0, 1,
+ (u8 *)wifi_txgain_table_24g_8800dcdw_h,
+ 128);
+ if (ret)
+ return -1;
+
+ ret = rwnx_send_rf_config_req(rwnx_hw, 16, 1,
+ (u8 *)wifi_txgain_table_24g_1_8800dcdw_h,
+ 128);
+ if (ret)
+ return -1;
+ } else {
+ ret = rwnx_send_rf_config_req(rwnx_hw, 0, 1,
+ (u8 *)wifi_txgain_table_24g_8800dcdw,
+ 128);
+ if (ret)
+ return -1;
+
+ ret = rwnx_send_rf_config_req(rwnx_hw, 16, 1,
+ (u8 *)wifi_txgain_table_24g_1_8800dcdw,
+ 128);
+ if (ret)
+ return -1;
+ }
+
+ ret = rwnx_send_rf_config_req(rwnx_hw, 0, 0,
+ (u8 *)wifi_rxgain_table_24g_20m_8800dcdw,
+ 256);
+ if (ret)
+ return -1;
+
+ ret = rwnx_send_rf_config_req(rwnx_hw, 32, 0,
+ (u8 *)wifi_rxgain_table_24g_40m_8800dcdw,
+ 256);
+ if (ret)
+ return -1;
+
+ ret = rwnx_send_rf_calib_req(rwnx_hw, cfm);
+ if (ret)
+ return -1;
+ } else if (rwnx_hw->testmode == 1) {
+ if (chip_sub_id >= 1) {
+#ifdef CONFIG_DPD
+#ifndef CONFIG_FORCE_DPD_CALIB
+ if (is_file_exist(FW_DPDRESULT_NAME_8800DC) == 1) {
+ AICWFDBG(LOGINFO, "%s load dpd bin\n", __func__);
+ ret = aicwf_fdrv_dpd_result_load_8800dc(rwnx_hw, &dpd_res);
+ if (ret) {
+ AICWFDBG(LOGINFO, "load dpd bin fail: %d\n", ret);
+ return ret;
+ }
+ }
+#endif
+ if (dpd_res.bit_mask[1]) {
+ ret = aicwf_fdrv_dpd_result_apply_8800dc(rwnx_hw, &dpd_res);
+ if (ret) {
+ AICWFDBG(LOGINFO, "apply dpd bin fail: %d\n", ret);
+ return ret;
+ }
+ }
+#else
+ {
+ ret = aicwf_fdrv_misc_ram_init_8800dc(rwnx_hw);
+ if (ret) {
+ AICWFDBG(LOGINFO, "misc ram init fail: %d\n", ret);
+ return ret;
+ }
+ }
+#endif
+ ret = rwnx_send_rf_calib_req(rwnx_hw, cfm);
+ if (ret) {
+ AICWFDBG(LOGINFO, "rf calib req fail: %d\n", ret);
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int rwnx_plat_userconfig_load_8800dc(struct rwnx_hw *rwnx_hw)
+{
+ int size;
+ u32 *dst = NULL;
+ char *filename = FW_USERCONFIG_NAME_8800DC;
+
+ AICWFDBG(LOGINFO, "userconfig file path:%s \r\n", filename);
+
+ /* load file */
+ size = rwnx_request_firmware_common(rwnx_hw, &dst, filename);
+ if (size <= 0) {
+ AICWFDBG(LOGERROR, "wrong size of firmware file\n");
+ dst = NULL;
+ return 0;
+ }
+
+ /* Copy the file on the Embedded side */
+ AICWFDBG(LOGINFO, "### Load file done: %s, size=%d\n", filename, size);
+
+ rwnx_plat_userconfig_parsing2((char *)dst, size);
+
+ rwnx_release_firmware_common(&dst);
+
+ AICWFDBG(LOGINFO, "userconfig download complete\n\n");
+ return 0;
+}
+
+int rwnx_plat_userconfig_load_8800dw(struct rwnx_hw *rwnx_hw)
+{
+ int size;
+ u32 *dst = NULL;
+ char *filename = FW_USERCONFIG_NAME_8800DW;
+
+ AICWFDBG(LOGINFO, "userconfig file path:%s \r\n", filename);
+
+ /* load file */
+ size = rwnx_request_firmware_common(rwnx_hw, &dst, filename);
+ if (size <= 0) {
+ AICWFDBG(LOGERROR, "wrong size of firmware file\n");
+ dst = NULL;
+ return 0;
+ }
+
+ /* Copy the file on the Embedded side */
+ AICWFDBG(LOGINFO, "### Load file done: %s, size=%d\n", filename, size);
+
+ rwnx_plat_userconfig_parsing2((char *)dst, size);
+
+ rwnx_release_firmware_common(&dst);
+
+ AICWFDBG(LOGINFO, "userconfig download complete\n\n");
+ return 0;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800dc.h b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800dc.h
new file mode 100644
index 0000000000000..12e8d60150452
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800dc.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AICWF_COMPAT_8800DC_H_
+#define _AICWF_COMPAT_8800DC_H_
+
+#include "aic_bsp_export.h"
+#include <linux/types.h>
+
+#ifdef CONFIG_DPD
+int aicwf_fdrv_dpd_result_apply_8800dc(struct rwnx_hw *rwnx_hw,
+ struct rf_misc_ram_lite_t *dpd_res);
+#ifndef CONFIG_FORCE_DPD_CALIB
+int aicwf_fdrv_dpd_result_load_8800dc(struct rwnx_hw *rwnx_hw,
+ struct rf_misc_ram_lite_t *dpd_res);
+#endif
+#endif
+int aicwf_fdrv_misc_ram_init_8800dc(struct rwnx_hw *rwnx_hw);
+int aicwf_set_rf_config_8800dc(struct rwnx_hw *rwnx_hw,
+ struct mm_set_rf_calib_cfm *cfm);
+int rwnx_plat_userconfig_load_8800dc(struct rwnx_hw *rwnx_hw);
+int rwnx_plat_userconfig_load_8800dw(struct rwnx_hw *rwnx_hw);
+
+#endif /* _AICWF_COMPAT_8800DC_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_genl.c b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_genl.c
new file mode 100644
index 0000000000000..a815d6c143f82
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_genl.c
@@ -0,0 +1,699 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief netlink interface private command definition
+ *
+ ******************************************************************************
+ */
+
+#include "aicwf_genl.h"
+#include "aic_priv_cmd.h"
+
+int aic_debug_lvl = LOG_INFO;
+module_param(aic_debug_lvl, int, 0660);
+
+static struct genl_family aicwf_genl_family;
+static struct rwnx_hw *aicwf_genl_hw;
+static bool aicwf_genl_registered;
+static DEFINE_MUTEX(aicwf_genl_cmd_lock);
+
+#define AICWF_GENL_MAX_ARGS 224
+#define AICWF_GENL_MAX_CMD_LEN 1024
+#define AICWF_GENL_REPLY_MAX_LEN 1024
+#define RF_MODE_ERR (-127)
+
+static int genl_parse_sep(char *dst[], size_t dst_len, char *src,
+ const char *delimiter)
+{
+ size_t count = 0;
+ char *token;
+
+ while ((token = strsep(&src, delimiter))) {
+ if (!token[0])
+ continue;
+ if (count == dst_len)
+ return -E2BIG;
+ dst[count++] = token;
+ }
+
+ return count;
+}
+
+static int genl_parse_u8(const char *str, unsigned int base, u8 *value)
+{
+ u8 parsed;
+ int ret;
+
+ if (!str || !str[0])
+ return -EINVAL;
+
+ ret = kstrtou8(str, base, &parsed);
+ if (ret)
+ return ret;
+
+ *value = parsed;
+ return 0;
+}
+
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+int cmd_country_set(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len)
+{
+ int ret = 0;
+
+ if (len != 1) {
+ genl_debug(LOG_INFO, "%s param err\n", __func__);
+ return -EINVAL;
+ }
+
+ genl_debug(LOG_DEBUG, "%s, %s\n", __func__, s_buf[0]);
+
+ if (!strcmp(s_buf[0], "AUTO")) {
+ rwnx_hw->ccode.auto_set = true;
+ rwnx_hw->ccode.ccode_set = false;
+ rwnx_hw->ccode.ccode_cnt = 0;
+ rwnx_hw->ccode.ccode_rssi = -100;
+ } else if (!strcmp(s_buf[0], "MANUAL")) {
+ rwnx_hw->ccode.auto_set = false;
+ rwnx_hw->ccode.ccode_set = true;
+ } else {
+ if (strlen(s_buf[0]) != 2 || !isalpha(s_buf[0][0]) ||
+ !isalpha(s_buf[0][1]))
+ return -EINVAL;
+ rwnx_hw->ccode.ccode_set = true;
+ ret =
+ rwnx_regulatory_set_wiphy_regd(rwnx_hw->wiphy,
+ get_regdomain_from_rwnx_db(rwnx_hw->wiphy,
+ s_buf[0]));
+ memcpy(rwnx_hw->country_abbr, s_buf[0], 2);
+ rwnx_hw->ccode.ccode_cnt = 0;
+#ifdef CONFIG_AIC8800_REGION_PW
+ rwnx_send_txpwr_lvl_v3_req(rwnx_hw, get_ccode_region(rwnx_hw->country_abbr));
+#endif
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+ aic_chip_powerlimit_load(rwnx_hw);
+ if (!rwnx_hw->testmode)
+ rwnx_send_me_chan_config_req(rwnx_hw);
+#endif
+ }
+ return ret;
+}
+
+int cmd_country_get(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len)
+{
+ const struct ieee80211_regdomain *regd;
+ int ret = 0;
+ u8 buf[3];
+
+ if (len)
+ return -EINVAL;
+
+ rcu_read_lock();
+ regd = rcu_dereference(rwnx_hw->wiphy->regd);
+ if (!regd) {
+ rcu_read_unlock();
+ return -ENODATA;
+ }
+ buf[0] = regd->alpha2[0];
+ buf[1] = regd->alpha2[1];
+ rcu_read_unlock();
+ buf[2] = rwnx_hw->ccode.auto_set;
+ memcpy(r_buf, &buf[0], 3);
+ *r_len = 3;
+
+ genl_debug(LOG_DEBUG, "%c%c\n", r_buf[0], r_buf[1]);
+ return ret;
+}
+#endif
+
+int cmd_temp_get(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len)
+{
+ int ret = 0;
+ struct mm_set_vendor_swconfig_cfm tp_cfm;
+
+ if (len)
+ return -EINVAL;
+
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+ if (timer_pending(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer)) {
+ if (jiffies_to_msecs(jiffies - rwnx_hw->started_jiffies) < 5000) {
+ genl_debug(LOG_INFO, "tp_get temp_1: %d\n", rwnx_hw->temp);
+ memcpy(r_buf, &rwnx_hw->temp, 1);
+ } else {
+ if (rwnx_send_get_temp_req(rwnx_hw, &tp_cfm))
+ return -1;
+ genl_debug(LOGINFO, "tp_get temp_2: %d\n",
+ tp_cfm.temp_comp_get_cfm.degree);
+ rwnx_hw->sdiodev->tp_ctrl.cur_temp =
+ tp_cfm.temp_comp_get_cfm.degree;
+ memcpy(r_buf, &tp_cfm.temp_comp_get_cfm.degree, 1);
+ }
+ } else
+#endif
+ {
+ if (rwnx_send_get_temp_req(rwnx_hw, &tp_cfm))
+ return -1;
+ genl_debug(LOGINFO, "tp_get temp_3: %d\n",
+ tp_cfm.temp_comp_get_cfm.degree);
+ memcpy(r_buf, &tp_cfm.temp_comp_get_cfm.degree, 1);
+ }
+ *r_len = 1;
+
+ genl_debug(LOG_DEBUG, "%c\n", r_buf[0]);
+ return ret;
+}
+
+int cmd_get_fw_version(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len)
+{
+ int ret = 0;
+
+ if (len)
+ return -EINVAL;
+
+ genl_debug(LOG_DEBUG, "Firmware Version: %s\n", rwnx_hw->fw_version);
+ memcpy(r_buf, rwnx_hw->fw_version, 32);
+ *r_len = 32;
+ return ret;
+}
+
+int cmd_get_link_status(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len)
+{
+ struct rwnx_vif *rwnx_vif = NULL;
+ struct rwnx_vif *rwnx_vif_st = NULL;
+ struct wf_bss_info bi;
+
+ if (len)
+ return -EINVAL;
+
+ bi.length = 0;
+ list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
+ if (rwnx_vif && rwnx_vif->up &&
+ (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION))
+ rwnx_vif_st = rwnx_vif;
+ }
+ if (!rwnx_vif_st) {
+ genl_debug(LOG_INFO, "rwnx_vif_st is NULL\n");
+ memcpy(r_buf, &bi, 4);
+ *r_len = 4;
+ return 0;
+ }
+ if (atomic_read(&rwnx_vif_st->drv_conn_state) !=
+ (int)RWNX_DRV_STATUS_CONNECTED) {
+ genl_debug(LOG_INFO, "rwnx_vif_st is not conncet\n");
+ memcpy(r_buf, &bi, 4);
+ *r_len = 4;
+ return 0;
+ }
+
+ bi.ssid_len = rwnx_vif_st->sta.ssid_len;
+ bi.band = rwnx_vif_st->sta.ap->band;
+ bi.width = rwnx_vif_st->sta.ap->width;
+ bi.center_freq = rwnx_vif_st->sta.ap->center_freq;
+ bi.center_freq1 = rwnx_vif_st->sta.ap->center_freq1;
+ bi.center_freq2 = rwnx_vif_st->sta.ap->center_freq2;
+ bi.ht = rwnx_vif_st->sta.ap->ht;
+ bi.vht = rwnx_vif_st->sta.ap->vht;
+ bi.chan = ieee80211_frequency_to_channel(bi.center_freq);
+ memcpy(bi.bssid, rwnx_vif_st->sta.bssid, ETH_ALEN);
+ memset(bi.ssid, 0, sizeof(bi.ssid));
+ memcpy(bi.ssid, rwnx_vif_st->sta.ssid, rwnx_vif_st->sta.ssid_len);
+ bi.length = sizeof(bi);
+
+ memcpy(r_buf, &bi, bi.length);
+ *r_len = bi.length;
+ return 0;
+}
+
+int cmd_get_auth_type(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len)
+{
+ struct rwnx_vif *rwnx_vif = NULL;
+ s32 val = -1;
+
+ if (len)
+ return -EINVAL;
+
+ list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
+ if (rwnx_vif && rwnx_vif->up &&
+ (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION) &&
+ atomic_read(&rwnx_vif->drv_conn_state) ==
+ (int)RWNX_DRV_STATUS_CONNECTED)
+ val = rwnx_vif->sta.auth_type;
+ }
+
+ memcpy(r_buf, &val, 4);
+ *r_len = 4;
+ return 0;
+}
+
+int cmd_set_suspend_mode(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len)
+{
+#ifndef CONFIG_AIC8800_AUTO_POWERSAVE
+ u8 val;
+#endif
+ int ret = 0;
+
+ if (len != 1)
+ return -EINVAL;
+
+ if (rwnx_hw->testmode == 1) {
+ pr_err("AICWF %s, sleep cmd is not supported in RF test mode\n", __func__);
+ return -EOPNOTSUPP;
+ }
+
+#ifndef CONFIG_AIC8800_AUTO_POWERSAVE
+ ret = genl_parse_u8(s_buf[0], 10, &val);
+ if (ret)
+ return ret;
+ if (val > 1)
+ return -ERANGE;
+
+ if (val == 1) {
+ ret = rwnx_send_me_set_lp_level(rwnx_hw, 1, 0); //dynamic switch
+
+ if (rwnx_hw->scan_request && rwnx_hw->scanning) {
+ pr_info("AICWF enter suspend, stop scan\n");
+ ret = rwnx_send_scanu_cancel_req(rwnx_hw, NULL);
+ /* make sure fw take effect */
+ msleep(50);
+ if (ret) {
+ pr_info("AICWF %s scanu_cancel fail\n", __func__);
+ return ret;
+ }
+ }
+ } else if (val == 0) {
+ ret = rwnx_send_me_set_lp_level(rwnx_hw, 0, 1); //dynamic switch
+ }
+
+ if (ret == 0) {
+ r_buf[0] = (u8)val;
+ *r_len = 1;
+ }
+#else
+ r_buf[0] = 15;
+ *r_len = 1;
+#endif
+
+ return ret;
+}
+
+int cmd_set_mac_addr(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len)
+{
+ u8 mac_addr[6];
+ int i, ret;
+
+ if (!rwnx_hw->testmode) {
+ genl_debug(LOG_ERROR, "%s not in testmode !!!\n", __func__);
+ ret = RF_MODE_ERR;
+ return ret;
+ }
+
+ if (len != (int)ARRAY_SIZE(mac_addr)) {
+ genl_debug(LOG_ERROR, "%s param err\n", __func__);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < (int)ARRAY_SIZE(mac_addr); i++) {
+ ret = genl_parse_u8(s_buf[i], 16,
+ &mac_addr[ARRAY_SIZE(mac_addr) - 1 - i]);
+ if (ret)
+ return ret;
+ }
+
+ genl_debug(LOG_INFO, "set macaddr:%x,%x,%x,%x,%x,%x\n", mac_addr[5],
+ mac_addr[4], mac_addr[3], mac_addr[2], mac_addr[1], mac_addr[0]);
+ ret = rwnx_send_rftest_req(rwnx_hw, SET_MAC_ADDR, sizeof(mac_addr),
+ (u8 *)&mac_addr, NULL);
+ return ret;
+}
+
+int cmd_get_mac_addr(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len)
+{
+ u32 addr0, addr1;
+ int ret = 0;
+
+ if (len)
+ return -EINVAL;
+
+ if (!rwnx_hw->testmode) {
+ genl_debug(LOG_ERROR, "ERROR, %s not in testmode !!!\n", __func__);
+ ret = RF_MODE_ERR;
+ return ret;
+ }
+
+ ret = rwnx_send_rftest_req(rwnx_hw, GET_MAC_ADDR, 0, NULL, &cfm);
+ memcpy(r_buf, &cfm.rftest_result[0], 8);
+ addr0 = cfm.rftest_result[0];
+ addr1 = cfm.rftest_result[1];
+ genl_debug(LOG_INFO, "0x%x,0x%x\n", addr0, addr1);
+ *r_len = 8;
+
+ return ret;
+}
+
+int cmd_set_bt_mac_addr(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len)
+{
+ u8 mac_addr[6];
+ int i, ret;
+
+ if (!rwnx_hw->testmode) {
+ genl_debug(LOG_ERROR, "%s not in testmode !!!\n", __func__);
+ ret = RF_MODE_ERR;
+ return ret;
+ }
+
+ if (len != (int)ARRAY_SIZE(mac_addr)) {
+ genl_debug(LOG_ERROR, "%s param err\n", __func__);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < (int)ARRAY_SIZE(mac_addr); i++) {
+ ret = genl_parse_u8(s_buf[i], 16,
+ &mac_addr[ARRAY_SIZE(mac_addr) - 1 - i]);
+ if (ret)
+ return ret;
+ }
+
+ genl_debug(LOG_INFO, "set bt macaddr:%x,%x,%x,%x,%x,%x\n", mac_addr[5],
+ mac_addr[4], mac_addr[3], mac_addr[2], mac_addr[1], mac_addr[0]);
+ ret = rwnx_send_rftest_req(rwnx_hw, SET_BT_MAC_ADDR, sizeof(mac_addr),
+ (u8 *)&mac_addr, NULL);
+ return ret;
+}
+
+int cmd_get_bt_mac_addr(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len)
+{
+ u32 addr0, addr1;
+ int ret = 0;
+
+ if (len)
+ return -EINVAL;
+
+ if (!rwnx_hw->testmode) {
+ genl_debug(LOG_ERROR, "ERROR, %s not in testmode !!!\n", __func__);
+ ret = RF_MODE_ERR;
+ return ret;
+ }
+
+ ret = rwnx_send_rftest_req(rwnx_hw, GET_BT_MAC_ADDR, 0, NULL, &cfm);
+ memcpy(r_buf, &cfm.rftest_result[0], 8);
+ addr0 = cfm.rftest_result[0];
+ addr1 = cfm.rftest_result[1];
+ genl_debug(LOG_INFO, "0x%x,0x%x\n", addr0, addr1);
+ *r_len = 8;
+
+ return ret;
+}
+
+static const struct cmd_table_ops g_cmd_table[] = {
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+ {
+ .name = "country_set",
+ .func = cmd_country_set,
+ },
+ {
+ .name = "country_get",
+ .func = cmd_country_get,
+ },
+#endif
+ {
+ .name = "temp_get",
+ .func = cmd_temp_get,
+ },
+ {
+ .name = "get_version",
+ .func = cmd_get_fw_version,
+ },
+ {
+ .name = "status",
+ .func = cmd_get_link_status,
+ },
+ {
+ .name = "wpa_auth",
+ .func = cmd_get_auth_type,
+ },
+ {
+ .name = "set_suspend",
+ .func = cmd_set_suspend_mode,
+ },
+ {
+ .name = "set_mac_addr",
+ .func = cmd_set_mac_addr,
+ },
+ {
+ .name = "get_mac_addr",
+ .func = cmd_get_mac_addr,
+ },
+ {
+ .name = "set_bt_mac_addr",
+ .func = cmd_set_bt_mac_addr,
+ },
+ {
+ .name = "get_bt_mac_addr",
+ .func = cmd_get_bt_mac_addr,
+ },
+};
+
+int match_cmd_table(struct rwnx_hw *rwnx_hw, char *s_buf, char *r_buf,
+ unsigned long *r_len)
+{
+ const struct cmd_table_ops *cmd_table = NULL;
+ size_t i;
+ int ret, len;
+ char **cmd = NULL;
+
+ cmd = kcalloc(AICWF_GENL_MAX_ARGS, sizeof(*cmd), GFP_KERNEL);
+ if (!cmd)
+ return -ENOMEM;
+
+ len = genl_parse_sep(cmd, AICWF_GENL_MAX_ARGS, s_buf, " \t");
+ if (len <= 0) {
+ genl_debug(LOG_ERROR, "%s params error, count: %d\n", __func__, len);
+ kfree(cmd);
+ return len ? len : -EINVAL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(g_cmd_table); i++) {
+ if (strcmp(cmd[0], g_cmd_table[i].name) != 0)
+ continue;
+
+ cmd_table = &g_cmd_table[i];
+ break;
+ }
+
+ if (!cmd_table) {
+ genl_debug(LOG_INFO, "%s cmd does not exist\n", __func__);
+ kfree(cmd);
+ return -EINVAL;
+ }
+
+ ret = cmd_table->func(rwnx_hw, &cmd[1], len - 1, r_buf, r_len);
+
+ kfree(cmd);
+ return ret;
+}
+
+static int genl_send_generic(struct genl_info *info, u8 attr, u8 cmd, u32 len,
+ u8 *data)
+{
+ struct sk_buff *skb;
+ void *hdr;
+ int ret;
+
+ skb = genlmsg_new(nla_total_size(len), GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+ hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, &aicwf_genl_family,
+ 0, cmd);
+ if (!hdr) {
+ genl_debug(LOG_INFO, "%s fail\n", __func__);
+ ret = -EMSGSIZE;
+ goto err_put;
+ }
+ if (nla_put(skb, attr, len, data)) {
+ ret = -EMSGSIZE;
+ goto err_put;
+ }
+
+ genlmsg_end(skb, hdr);
+ return genlmsg_reply(skb, info);
+
+err_put:
+ nlmsg_free(skb);
+ return ret;
+}
+
+static int aicwf_genl_cmd_handler(struct sk_buff *skb, struct genl_info *info)
+{
+ struct rwnx_hw *rwnx_hw;
+ unsigned long r_len = 0;
+ char *s_buf, *r_buf;
+ int ret;
+
+ if (!info) {
+ genl_debug(LOG_INFO, "%s genl_info is NULL\n", __func__);
+ return -EINVAL;
+ }
+
+ if (!info->attrs[WL_NL_ATTR_AP2CP]) {
+ genl_debug(LOG_INFO, "%s invalid content\n", __func__);
+ return -EINVAL;
+ }
+
+ rwnx_hw = READ_ONCE(aicwf_genl_hw);
+ if (!rwnx_hw)
+ return -ENODEV;
+
+ s_buf = nla_strdup(info->attrs[WL_NL_ATTR_AP2CP], GFP_KERNEL);
+ if (!s_buf)
+ return -ENOMEM;
+
+ r_buf = kzalloc_objs(*r_buf, AICWF_GENL_REPLY_MAX_LEN, GFP_KERNEL);
+ if (!r_buf) {
+ ret = -ENOMEM;
+ goto out_free_cmd;
+ }
+
+ genl_debug(LOG_DEBUG, "s_len: %d, %s\n",
+ nla_len(info->attrs[WL_NL_ATTR_AP2CP]), s_buf);
+
+ mutex_lock(&aicwf_genl_cmd_lock);
+ if (rwnx_hw != READ_ONCE(aicwf_genl_hw))
+ ret = -ENODEV;
+ else
+ ret = match_cmd_table(rwnx_hw, s_buf, r_buf, &r_len);
+ mutex_unlock(&aicwf_genl_cmd_lock);
+ if (ret) {
+ if (ret == RF_MODE_ERR) {
+ strscpy(r_buf, "aic: cmd_error, enter this cmd in testmode",
+ AICWF_GENL_REPLY_MAX_LEN);
+ r_len = strlen(r_buf) + 1;
+ } else {
+ genl_debug(LOG_ERROR, "%s handler fail\n", __func__);
+ goto out_free_reply;
+ }
+ }
+
+ if (r_len == 0) {
+ strscpy(r_buf, "aic genl_msg send ok",
+ AICWF_GENL_REPLY_MAX_LEN);
+ r_len = strlen(r_buf) + 1;
+ }
+ if (r_len > AICWF_GENL_REPLY_MAX_LEN) {
+ ret = -EMSGSIZE;
+ goto out_free_reply;
+ }
+ genl_debug(LOG_DEBUG, "r_len: %lu\n", r_len);
+
+ ret = genl_send_generic(info, WL_NL_ATTR_CP2AP, WL_NL_CMD_MSG, r_len,
+ (u8 *)r_buf);
+
+out_free_reply:
+ kfree(r_buf);
+out_free_cmd:
+ kfree(s_buf);
+ return ret;
+}
+
+static int aicwf_genl_get_info_handler(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ unsigned char r_buf = 1;
+
+ if (!info)
+ return -EINVAL;
+ if (!READ_ONCE(aicwf_genl_hw))
+ return -ENODEV;
+
+ return genl_send_generic(info, WL_NL_ATTR_CP2AP, WL_NL_CMD_GET_INFO,
+ sizeof(r_buf), &r_buf);
+}
+
+static const struct nla_policy genl_policy[WL_NL_ATTR_MAX + 1] = {
+ [WL_NL_ATTR_IFINDEX] = {.type = NLA_U32},
+ [WL_NL_ATTR_AP2CP] = {
+ .type = NLA_NUL_STRING,
+ .len = AICWF_GENL_MAX_CMD_LEN,
+ },
+ [WL_NL_ATTR_CP2AP] = {.type = NLA_REJECT},
+};
+
+static const struct genl_ops aicwf_genl_ops[] = {
+ {
+ .cmd = WL_NL_CMD_MSG,
+ .flags = GENL_ADMIN_PERM,
+ .doit = aicwf_genl_cmd_handler,
+ },
+ {
+ .cmd = WL_NL_CMD_GET_INFO,
+ .flags = GENL_ADMIN_PERM,
+ .doit = aicwf_genl_get_info_handler,
+ },
+};
+
+static struct genl_family aicwf_genl_family = {
+ .hdrsize = 0,
+ .name = GENL_FAMILY_NAME,
+ .version = 1,
+ .maxattr = WL_NL_ATTR_MAX,
+ .policy = genl_policy,
+ .module = THIS_MODULE,
+ .n_ops = ARRAY_SIZE(aicwf_genl_ops),
+ .ops = aicwf_genl_ops,
+};
+
+int aicwf_init_genl(struct rwnx_hw *rwnx_hw)
+{
+ int ret;
+
+ if (!rwnx_hw)
+ return -EINVAL;
+ if (aicwf_genl_registered)
+ return -EALREADY;
+
+ WRITE_ONCE(aicwf_genl_hw, rwnx_hw);
+ ret = genl_register_family(&aicwf_genl_family);
+ if (ret) {
+ WRITE_ONCE(aicwf_genl_hw, NULL);
+ genl_debug(LOG_INFO, "genl_register_family error: %d\n", ret);
+ return ret;
+ }
+
+ aicwf_genl_registered = true;
+ return 0;
+}
+
+void aicwf_deinit_genl(struct rwnx_hw *rwnx_hw)
+{
+ int ret;
+
+ if (!aicwf_genl_registered)
+ return;
+ if (rwnx_hw != READ_ONCE(aicwf_genl_hw)) {
+ genl_debug(LOG_ERROR, "generic netlink device mismatch\n");
+ return;
+ }
+
+ ret = genl_unregister_family(&aicwf_genl_family);
+ if (ret) {
+ genl_debug(LOG_INFO, "unregister family %d\n", ret);
+ return;
+ }
+
+ mutex_lock(&aicwf_genl_cmd_lock);
+ aicwf_genl_registered = false;
+ WRITE_ONCE(aicwf_genl_hw, NULL);
+ mutex_unlock(&aicwf_genl_cmd_lock);
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_genl.h b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_genl.h
new file mode 100644
index 0000000000000..df272312f6e1c
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_genl.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __AICWF_GENL_H__
+#define __AICWF_GENL_H__
+
+#include <linux/ctype.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/version.h>
+#include <net/genetlink.h>
+
+#include "rwnx_mod_params.h"
+#include "rwnx_msg_tx.h"
+#include "rwnx_platform.h"
+#include "aicwf_compat_8800d80.h"
+
+#define GENL_FAMILY_NAME ("AICWF_NL")
+
+enum aicwf_nl_commands {
+ WL_NL_CMD_UNSPEC,
+ WL_NL_CMD_MSG,
+ WL_NL_CMD_GET_INFO,
+ WL_NL_CMD_MAX,
+};
+
+enum aicwf_nl_attrs {
+ WL_NL_ATTR_UNSPEC,
+ WL_NL_ATTR_IFINDEX,
+ WL_NL_ATTR_AP2CP,
+ WL_NL_ATTR_CP2AP,
+ WL_NL_ATTR_MAX,
+};
+
+enum { LOG_EXCESSIVE, LOG_DUMP, LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_ERROR };
+
+extern int aic_debug_lvl;
+extern struct dbg_rftest_cmd_cfm cfm;
+
+#define genl_debug(level, fmt, args...) \
+ do { \
+ if ((level) >= aic_debug_lvl) { \
+ pr_debug("AICGENL: " fmt, ##args); \
+ } \
+ } while (0)
+
+typedef int (*func_cmd)(struct rwnx_hw *, char *[], int, char *,
+ unsigned long *);
+struct cmd_table_ops {
+ const char *name;
+ func_cmd func;
+};
+
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+int cmd_country_set(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len);
+int cmd_country_get(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len);
+#endif
+int cmd_temp_get(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len);
+int cmd_get_fw_version(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len);
+int cmd_get_link_status(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len);
+int cmd_get_auth_type(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len);
+int cmd_set_suspend_mode(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len);
+int cmd_set_mac_addr(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len);
+int cmd_get_mac_addr(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len);
+int cmd_set_bt_mac_addr(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len);
+int cmd_get_bt_mac_addr(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+ char *r_buf, unsigned long *r_len);
+
+int match_cmd_table(struct rwnx_hw *rwnx_hw, char *s_buf, char *r_buf,
+ unsigned long *r_len);
+int aicwf_init_genl(struct rwnx_hw *rwnx_hw);
+void aicwf_deinit_genl(struct rwnx_hw *rwnx_hw);
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_rx_prealloc.c b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_rx_prealloc.c
new file mode 100644
index 0000000000000..1258e8a054d47
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_rx_prealloc.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief rx memory prealloc function
+ *
+ ******************************************************************************
+ */
+
+#include "aicwf_rx_prealloc.h"
+
+#ifdef CONFIG_PREALLOC_RX_SKB
+struct aicwf_rx_buff_list aic_rx_buff_list;
+
+static int aic_rxbuff_num_max = 30;
+
+static int aic_rxbuff_size = (64 * 512);
+
+struct rx_buff *aicwf_prealloc_rxbuff_alloc(spinlock_t *lock)
+{
+ unsigned long flags;
+ struct rx_buff *rxbuff = NULL;
+
+ spin_lock_irqsave(lock, flags);
+ if (list_empty(&aic_rx_buff_list.rxbuff_list)) {
+ spin_unlock_irqrestore(lock, flags);
+ pr_err("%s %d, rxbuff list is empty\n", __func__, __LINE__);
+ return NULL;
+ }
+ rxbuff = list_first_entry(&aic_rx_buff_list.rxbuff_list, struct rx_buff,
+ queue);
+ list_del_init(&rxbuff->queue);
+ atomic_dec(&aic_rx_buff_list.rxbuff_list_len);
+
+ spin_unlock_irqrestore(lock, flags);
+ // printk("len:%d\n", aic_rx_buff_list.rxbuff_list_len); //debug trace
+ memset(rxbuff->data, 0, aic_rxbuff_size);
+ rxbuff->len = 0;
+ rxbuff->start = NULL;
+ rxbuff->read = NULL;
+ rxbuff->end = NULL;
+
+ return rxbuff;
+}
+
+void aicwf_prealloc_rxbuff_free(struct rx_buff *rxbuff, spinlock_t *lock)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(lock, flags);
+ list_add_tail(&rxbuff->queue, &aic_rx_buff_list.rxbuff_list);
+ atomic_inc(&aic_rx_buff_list.rxbuff_list_len);
+ spin_unlock_irqrestore(lock, flags);
+}
+
+int aicwf_prealloc_init(void)
+{
+ struct rx_buff *rxbuff;
+ int i = 0;
+
+ INIT_LIST_HEAD(&aic_rx_buff_list.rxbuff_list);
+
+ for (i = 0; i < aic_rxbuff_num_max; i++) {
+ rxbuff = kzalloc_obj(*rxbuff, GFP_KERNEL);
+ if (rxbuff) {
+ rxbuff->data =
+ kzalloc(aic_rxbuff_size, GFP_KERNEL);
+ if (!rxbuff->data) {
+ pr_err("failed to alloc rxbuff data\n");
+ kfree(rxbuff);
+ continue;
+ }
+ rxbuff->len = 0;
+ rxbuff->start = NULL;
+ rxbuff->read = NULL;
+ rxbuff->end = NULL;
+ list_add_tail(&rxbuff->queue, &aic_rx_buff_list.rxbuff_list);
+ atomic_inc(&aic_rx_buff_list.rxbuff_list_len);
+ }
+ }
+
+ pr_info("pre alloc rxbuff list len: %d\n",
+ (int)atomic_read(&aic_rx_buff_list.rxbuff_list_len));
+ return 0;
+}
+
+void aicwf_prealloc_exit(void)
+{
+ struct rx_buff *rxbuff;
+ struct rx_buff *pos;
+
+ pr_info("free pre alloc rxbuff list %d\n",
+ (int)atomic_read(&aic_rx_buff_list.rxbuff_list_len));
+ list_for_each_entry_safe(rxbuff, pos, &aic_rx_buff_list.rxbuff_list, queue) {
+ list_del_init(&rxbuff->queue);
+ kfree(rxbuff->data);
+ kfree(rxbuff);
+ }
+}
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_rx_prealloc.h b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_rx_prealloc.h
new file mode 100644
index 0000000000000..666e2f0027102
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_rx_prealloc.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AICWF_RX_PREALLOC_H_
+#define _AICWF_RX_PREALLOC_H_
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/version.h>
+#include <linux/atomic.h>
+
+#ifdef CONFIG_PREALLOC_RX_SKB
+struct rx_buff {
+ struct list_head queue;
+ unsigned char *data;
+ u32 len;
+ u8 *start;
+ u8 *end;
+ u8 *read;
+};
+
+struct aicwf_rx_buff_list {
+ struct list_head rxbuff_list;
+ atomic_t rxbuff_list_len;
+};
+
+extern struct aicwf_rx_buff_list aic_rx_buff_list;
+
+struct rx_buff *aicwf_prealloc_rxbuff_alloc(spinlock_t *lock);
+void aicwf_prealloc_rxbuff_free(struct rx_buff *rxbuff, spinlock_t *lock);
+int aicwf_prealloc_init(void);
+void aicwf_prealloc_exit(void);
+#endif
+#endif /* _AICWF_RX_PREALLOC_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_sdio.c b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_sdio.c
new file mode 100644
index 0000000000000..b13fd9a3981f3
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_sdio.c
@@ -0,0 +1,2831 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief SDIO function declarations
+ *
+ ******************************************************************************
+ */
+
+#include <linux/completion.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/of.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/pm.h>
+#include <linux/semaphore.h>
+#include <linux/suspend.h>
+
+#include "aicwf_sdio.h"
+#include "aicwf_txrxif.h"
+#include "rwnx_defs.h"
+#include "rwnx_msg_tx.h"
+#include "rwnx_platform.h"
+#include "sdio_host.h"
+#include <linux/pm_wakeirq.h>
+
+#include "aic_bsp_export.h"
+#include "aicwf_chip_ops.h"
+
+static const struct aic_sdio_chip_hw *aic_sdio_hw;
+
+/*
+ * Wakeup sources report PM events; they are not a driver-side completion
+ * mechanism. Track SDIO work explicitly so suspend can quiesce it without
+ * inspecting wakeup-source internals.
+ */
+bool aicwf_sdio_activity_get(struct rwnx_hw *rwnx_hw,
+ struct wakeup_source *ws)
+{
+ struct aic_sdio_dev *sdiodev;
+
+ if (!rwnx_hw || !ws)
+ return false;
+
+ sdiodev = rwnx_hw->sdiodev;
+ if (!sdiodev || unlikely(atomic_read(&sdiodev->suspending)))
+ return false;
+
+ atomic_inc(&sdiodev->activity_count);
+ smp_mb__after_atomic();
+ if (unlikely(atomic_read(&sdiodev->suspending))) {
+ if (atomic_dec_and_test(&sdiodev->activity_count))
+ wake_up_all(&sdiodev->activity_waitq);
+ return false;
+ }
+
+ __pm_stay_awake(ws);
+ return true;
+}
+
+void aicwf_sdio_activity_put(struct rwnx_hw *rwnx_hw,
+ struct wakeup_source *ws)
+{
+ struct aic_sdio_dev *sdiodev;
+
+ if (!rwnx_hw || !ws)
+ return;
+
+ sdiodev = rwnx_hw->sdiodev;
+ if (!sdiodev)
+ return;
+
+ __pm_relax(ws);
+ /*
+ * Keep the PM release before exposing count == 0 to a suspend waiter.
+ * atomic_add_unless() prevents a bad duplicate put from underflowing the
+ * accounting and making all later suspend attempts time out.
+ */
+ if (WARN_ON_ONCE(!atomic_add_unless(&sdiodev->activity_count, -1, 0)))
+ return;
+
+ if (atomic_read(&sdiodev->activity_count) == 0)
+ wake_up_all(&sdiodev->activity_waitq);
+}
+
+bool aicwf_sdio_scan_activity_get(struct rwnx_hw *rwnx_hw)
+{
+ if (!rwnx_hw)
+ return false;
+
+ /* Reserve the terminal put before taking the shared wakeup source. */
+ if (WARN_ON_ONCE(atomic_cmpxchg(&rwnx_hw->scan_activity_held, 0, 1)))
+ return false;
+
+ if (!aicwf_sdio_activity_get(rwnx_hw, rwnx_hw->ws_scan)) {
+ atomic_set(&rwnx_hw->scan_activity_held, 0);
+ return false;
+ }
+
+ return true;
+}
+
+void aicwf_sdio_scan_activity_put(struct rwnx_hw *rwnx_hw)
+{
+ if (rwnx_hw && atomic_xchg(&rwnx_hw->scan_activity_held, 0))
+ aicwf_sdio_activity_put(rwnx_hw, rwnx_hw->ws_scan);
+}
+
+
+static int tx_aggr_counter = 32;
+module_param_named(tx_aggr_counter, tx_aggr_counter, int, 0644);
+
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+int tx_fc_low_water = AICWF_SDIO_TX_LOW_WATER;
+module_param_named(tx_fc_low_water, tx_fc_low_water, int, 0644);
+
+int tx_fc_high_water = AICWF_SDIO_TX_HIGH_WATER;
+module_param_named(tx_fc_high_water, tx_fc_high_water, int, 0644);
+#endif
+
+
+int aicwf_sdio_readb(struct aic_sdio_dev *sdiodev, uint regaddr, u8 *val)
+{
+ int ret;
+
+ if (!sdiodev->func) {
+ pr_warn("%s, NULL sdio func\n", __func__);
+ return 0;
+ }
+ sdio_claim_host(sdiodev->func);
+ *val = sdio_readb(sdiodev->func, regaddr, &ret);
+ sdio_release_host(sdiodev->func);
+ return ret;
+}
+
+int aicwf_sdio_writeb(struct aic_sdio_dev *sdiodev, uint regaddr, u8 val)
+{
+ int ret;
+
+ if (!sdiodev->func) {
+ pr_warn("%s, NULL sdio func\n", __func__);
+ return 0;
+ }
+ sdio_claim_host(sdiodev->func);
+ sdio_writeb(sdiodev->func, val, regaddr, &ret);
+ sdio_release_host(sdiodev->func);
+ return ret;
+}
+
+int aicwf_sdio_func2_readb(struct aic_sdio_dev *sdiodev, uint regaddr, u8 *val)
+{
+ int ret;
+
+ sdio_claim_host(sdiodev->func2);
+ *val = sdio_readb(sdiodev->func2, regaddr, &ret);
+ sdio_release_host(sdiodev->func2);
+ return ret;
+}
+
+int aicwf_sdio_func2_writeb(struct aic_sdio_dev *sdiodev, uint regaddr, u8 val)
+{
+ int ret;
+
+ sdio_claim_host(sdiodev->func2);
+ sdio_writeb(sdiodev->func2, val, regaddr, &ret);
+ sdio_release_host(sdiodev->func2);
+ return ret;
+}
+
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+void aicwf_sdio_tx_netif_flowctrl(struct rwnx_hw *rwnx_hw, bool state)
+{
+ struct rwnx_vif *rwnx_vif;
+
+ list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
+ if (!rwnx_vif->up)
+ continue;
+ if (state)
+ netif_tx_stop_all_queues(rwnx_vif->ndev);
+ else
+ netif_tx_wake_all_queues(rwnx_vif->ndev);
+ }
+}
+#endif
+
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+static int update_state(struct aic_sdio_dev *sdiodev)
+{
+ s8 value = sdiodev->tp_ctrl.cur_temp;
+ u8 current_state = sdiodev->tp_ctrl.cur_stat;
+ s8 thd_1 = sdiodev->tp_ctrl.tp_thd_1;
+ s8 thd_2 = sdiodev->tp_ctrl.tp_thd_2;
+
+ if (value > thd_2)
+ return 2;
+ else if (value > (thd_2 - BUFFERING_V2) && (current_state == 2))
+ return 2;
+ else if (value > thd_1 && current_state != 2)
+ return 1;
+ else if (value > (thd_1 - BUFFERING_V1) && current_state == 1)
+ return 1;
+ else if (current_state == 0)
+ return 0;
+ else
+ return 1;
+}
+
+void aicwf_netif_ctrl(struct aic_sdio_dev *sdiodev, int val)
+{
+ unsigned long flags;
+ struct rwnx_vif *rwnx_vif;
+
+ if (sdiodev->tp_ctrl.net_stop)
+ return;
+
+ spin_lock_irqsave(&sdiodev->tx_tp_lock, flags);
+ list_for_each_entry(rwnx_vif, &sdiodev->rwnx_hw->vifs, list) {
+ if (!rwnx_vif || !rwnx_vif->ndev || !rwnx_vif->up)
+ continue;
+ netif_tx_stop_all_queues(rwnx_vif->ndev);
+ }
+ spin_unlock_irqrestore(&sdiodev->tx_tp_lock, flags);
+ sdiodev->tp_ctrl.net_stop = true;
+ mod_timer(&sdiodev->tp_ctrl.netif_timer, jiffies + msecs_to_jiffies(val));
+}
+
+void aicwf_temp_ctrl(struct aic_sdio_dev *sdiodev)
+{
+ if (sdiodev->tp_ctrl.set_level) {
+ if (sdiodev->tp_ctrl.set_level == 1) {
+ sdiodev->tp_ctrl.get_level = 1;
+ aicwf_netif_ctrl(sdiodev,
+ sdiodev->tp_ctrl.interval_t1 /* TMR_INTERVAL_1 */);
+ } else if (sdiodev->tp_ctrl.set_level == 2) {
+ sdiodev->tp_ctrl.get_level = 2;
+ aicwf_netif_ctrl(sdiodev,
+ sdiodev->tp_ctrl.interval_t2 /* TMR_INTERVAL_2 */);
+ }
+ return;
+ }
+ if (sdiodev->tp_ctrl.cur_temp >
+ (sdiodev->tp_ctrl.tp_thd_1 - BUFFERING_V1)) {
+ if (update_state(sdiodev) == 1) {
+ sdiodev->tp_ctrl.get_level = 1;
+ sdiodev->tp_ctrl.cur_stat = 1;
+ aicwf_netif_ctrl(sdiodev,
+ sdiodev->tp_ctrl.interval_t1 /*TMR_INTERVAL_1 */);
+ } else if (update_state(sdiodev) == 2) {
+ sdiodev->tp_ctrl.get_level = 2;
+ sdiodev->tp_ctrl.cur_stat = 2;
+ aicwf_netif_ctrl(sdiodev,
+ sdiodev->tp_ctrl.interval_t2 /*TMR_INTERVAL_2 */);
+ }
+ return;
+ }
+
+ if (sdiodev->tp_ctrl.cur_stat) {
+ AICWFDBG(LOGINFO, "reset cur_stat");
+ sdiodev->tp_ctrl.cur_stat = 0;
+ sdiodev->tp_ctrl.get_level = 0;
+ }
+}
+
+void aicwf_netif_worker(struct work_struct *work)
+{
+ struct temp_ctrl *tc = container_of(work, struct temp_ctrl, netif_work);
+ struct aic_sdio_dev *sdiodev =
+ container_of(tc, struct aic_sdio_dev, tp_ctrl);
+ unsigned long flags;
+ struct rwnx_vif *rwnx_vif;
+
+ if (sdiodev->bus_if->state == BUS_DOWN_ST) {
+ AICWFDBG(LOGERROR, "%s bus down\n", __func__);
+ return;
+ }
+
+ spin_lock_irqsave(&sdiodev->tx_tp_lock, flags);
+ list_for_each_entry(rwnx_vif, &sdiodev->rwnx_hw->vifs, list) {
+ if (!rwnx_vif || !rwnx_vif->ndev || !rwnx_vif->up)
+ continue;
+ netif_tx_wake_all_queues(rwnx_vif->ndev); // netif_wake_queue(rwnx_vif->ndev);
+ }
+ spin_unlock_irqrestore(&sdiodev->tx_tp_lock, flags);
+ sdiodev->tp_ctrl.net_stop = false;
+}
+
+static void aicwf_netif_timer(struct timer_list *t)
+{
+ struct temp_ctrl *tc = container_of(t, struct temp_ctrl, netif_timer);
+ struct aic_sdio_dev *sdiodev =
+ container_of(tc, struct aic_sdio_dev, tp_ctrl);
+
+ if (sdiodev->bus_if->state == BUS_DOWN_ST) {
+ AICWFDBG(LOGERROR, "%s bus down\n", __func__);
+ return;
+ }
+
+ if (!work_pending(&sdiodev->tp_ctrl.netif_work))
+ schedule_work(&sdiodev->tp_ctrl.netif_work);
+}
+
+void aicwf_temp_ctrl_worker(struct work_struct *work)
+{
+ struct rwnx_hw *rwnx_hw;
+ struct mm_set_vendor_swconfig_cfm cfm;
+ struct temp_ctrl *tc = container_of(work, struct temp_ctrl, tp_ctrl_work);
+ struct aic_sdio_dev *sdiodev =
+ container_of(tc, struct aic_sdio_dev, tp_ctrl);
+
+ if (sdiodev->bus_if->state == BUS_DOWN_ST) {
+ AICWFDBG(LOGERROR, "%s bus down\n", __func__);
+ return;
+ }
+
+ spin_lock_bh(&sdiodev->tp_ctrl.tm_lock);
+ if (!sdiodev->tp_ctrl.tm_start) {
+ spin_unlock_bh(&sdiodev->tp_ctrl.tm_lock);
+ AICWFDBG(LOGERROR, "tp_timer should stop_1\n");
+ return;
+ }
+ spin_unlock_bh(&sdiodev->tp_ctrl.tm_lock);
+
+ rwnx_hw = sdiodev->rwnx_hw;
+ rwnx_hw->started_jiffies = jiffies;
+
+ rwnx_send_get_temp_req(rwnx_hw, &cfm);
+ sdiodev->tp_ctrl.cur_temp = cfm.temp_comp_get_cfm.degree;
+
+ spin_lock_bh(&sdiodev->tp_ctrl.tm_lock);
+ if (sdiodev->tp_ctrl.tm_start)
+ mod_timer(&sdiodev->tp_ctrl.tp_ctrl_timer,
+ jiffies + msecs_to_jiffies(TEMP_GET_INTERVAL));
+ else
+ AICWFDBG(LOGERROR, "tp_timer should stop_2\n");
+ spin_unlock_bh(&sdiodev->tp_ctrl.tm_lock);
+}
+
+static void aicwf_temp_ctrl_timer(struct timer_list *t)
+{
+ struct temp_ctrl *tc = container_of(t, struct temp_ctrl, tp_ctrl_timer);
+ struct aic_sdio_dev *sdiodev =
+ container_of(tc, struct aic_sdio_dev, tp_ctrl);
+
+ if (sdiodev->bus_if->state == BUS_DOWN_ST) {
+ AICWFDBG(LOGERROR, "%s bus down\n", __func__);
+ return;
+ }
+
+ if (!work_pending(&sdiodev->tp_ctrl.tp_ctrl_work))
+ schedule_work(&sdiodev->tp_ctrl.tp_ctrl_work);
+}
+
+void aicwf_tp_ctrl_init(struct aic_sdio_dev *sdiodev)
+{
+ spin_lock_init(&sdiodev->tp_ctrl.tm_lock);
+ sdiodev->tp_ctrl.net_stop = false;
+ sdiodev->tp_ctrl.on_off = true;
+ sdiodev->tp_ctrl.cur_temp = 0;
+ sdiodev->tp_ctrl.get_level = 0;
+ sdiodev->tp_ctrl.set_level = 0;
+ sdiodev->tp_ctrl.interval_t1 = TMR_INTERVAL_1;
+ sdiodev->tp_ctrl.interval_t2 = TMR_INTERVAL_2;
+ sdiodev->tp_ctrl.cur_stat = 0;
+ sdiodev->tp_ctrl.tp_thd_1 = TEMP_THD_1;
+ sdiodev->tp_ctrl.tp_thd_2 = TEMP_THD_2;
+ sdiodev->tp_ctrl.tm_start = 1;
+
+ timer_setup(&sdiodev->tp_ctrl.tp_ctrl_timer, aicwf_temp_ctrl_timer, 0);
+ timer_setup(&sdiodev->tp_ctrl.netif_timer, aicwf_netif_timer, 0);
+
+ INIT_WORK(&sdiodev->tp_ctrl.tp_ctrl_work, aicwf_temp_ctrl_worker);
+ INIT_WORK(&sdiodev->tp_ctrl.netif_work, aicwf_netif_worker);
+ mod_timer(&sdiodev->tp_ctrl.tp_ctrl_timer,
+ jiffies + msecs_to_jiffies(TEMP_GET_INTERVAL));
+}
+
+void aicwf_tp_ctrl_deinit(struct aic_sdio_dev *sdiodev)
+{
+ spin_lock_bh(&sdiodev->tp_ctrl.tm_lock);
+ sdiodev->tp_ctrl.tm_start = 0;
+ if (timer_pending(&sdiodev->tp_ctrl.tp_ctrl_timer)) {
+ AICWFDBG(LOGINFO, "del tp_ctrl_timer\n");
+ //del_timer_sync(&sdiodev->tp_ctrl.tp_ctrl_timer);
+ timer_delete_sync(&sdiodev->tp_ctrl.tp_ctrl_timer);
+ }
+ spin_unlock_bh(&sdiodev->tp_ctrl.tm_lock);
+
+ cancel_work_sync(&sdiodev->tp_ctrl.tp_ctrl_work);
+
+ if (timer_pending(&sdiodev->tp_ctrl.netif_timer)) {
+ AICWFDBG(LOGINFO, "del netif_timer\n");
+ //del_timer_sync(&sdiodev->tp_ctrl.netif_timer);
+ timer_delete_sync(&sdiodev->tp_ctrl.netif_timer);
+ }
+
+ cancel_work_sync(&sdiodev->tp_ctrl.netif_work);
+}
+
+#endif
+
+int aicwf_sdio_flow_ctrl_msg(struct aic_sdio_dev *sdiodev)
+{
+ int ret = -1;
+ u8 fc_reg = 0;
+ u32 count = 0;
+
+ while (true) {
+ ret =
+ aicwf_sdio_readb(sdiodev, sdiodev->sdio_reg.flow_ctrl_reg, &fc_reg);
+ if (ret)
+ return -1;
+
+ if (aic_sdio_hw->need_flowctrl_mask)
+ fc_reg &= SDIOWIFI_FLOWCTRL_MASK_REG;
+
+ if (fc_reg != 0) {
+ ret = fc_reg;
+ if (ret > tx_aggr_counter)
+ ret = tx_aggr_counter;
+ return ret;
+ }
+ if (count >= FLOW_CTRL_RETRY_COUNT) {
+ ret = -fc_reg;
+ break;
+ }
+ count++;
+ if (count < 30)
+ usleep_range(50, 100);
+ else if (count < 40)
+ usleep_range(2000, 2500);
+ else
+ usleep_range(10000, 12000);
+ }
+
+ return ret;
+}
+
+int aicwf_sdio_flow_ctrl(struct aic_sdio_dev *sdiodev)
+{
+ int ret = -1;
+ u8 fc_reg = 0;
+ u32 count = 0;
+
+ while (true) {
+ ret =
+ aicwf_sdio_readb(sdiodev, sdiodev->sdio_reg.flow_ctrl_reg, &fc_reg);
+ if (ret)
+ return -1;
+
+ if (aic_sdio_hw->need_flowctrl_mask)
+ fc_reg &= SDIOWIFI_FLOWCTRL_MASK_REG;
+
+ if (fc_reg > DATA_FLOW_CTRL_THRESH) {
+ ret = fc_reg;
+ if (ret > tx_aggr_counter)
+ ret = tx_aggr_counter;
+ return ret;
+ }
+ if (count >= FLOW_CTRL_RETRY_COUNT) {
+ ret = -fc_reg;
+ break;
+ }
+ count++;
+ if (count < 30)
+ usleep_range(50, 50);
+ else if (count < 40)
+ usleep_range(2000, 2500);
+ else
+ usleep_range(10000, 12000);
+ }
+
+ return ret;
+}
+
+int aicwf_sdio_send_pkt(struct aic_sdio_dev *sdiodev, u8 *buf, uint count)
+{
+ int ret = 0;
+
+ if (!sdiodev->func) {
+ pr_warn("%s, NULL sdio func\n", __func__);
+ return 0;
+ }
+
+ sdio_claim_host(sdiodev->func);
+ ret =
+ sdio_writesb(sdiodev->func, sdiodev->sdio_reg.wr_fifo_addr, buf, count);
+ sdio_release_host(sdiodev->func);
+
+ return ret;
+}
+
+#ifdef CONFIG_PREALLOC_RX_SKB
+int aicwf_sdio_recv_pkt(struct aic_sdio_dev *sdiodev, struct rx_buff *rxbuff,
+ u32 size)
+{
+ int ret;
+
+ if (!rxbuff->data || !size)
+ return -EINVAL;
+ if (!sdiodev->func) {
+ pr_warn("%s, NULL sdio func\n", __func__);
+ return 0;
+ }
+
+ sdio_claim_host(sdiodev->func);
+ ret = sdio_readsb(sdiodev->func, rxbuff->data,
+ sdiodev->sdio_reg.rd_fifo_addr, size);
+ sdio_release_host(sdiodev->func);
+
+ if (ret < 0)
+ return ret;
+ rxbuff->len = size;
+
+ return ret;
+}
+#else
+int aicwf_sdio_recv_pkt(struct aic_sdio_dev *sdiodev, struct sk_buff *skbbuf,
+ u32 size)
+{
+ int ret;
+
+ if (!skbbuf || !size)
+ return -EINVAL;
+ if (!sdiodev->func) {
+ pr_warn("%s, NULL sdio func\n", __func__);
+ return 0;
+ }
+
+ sdio_claim_host(sdiodev->func);
+ ret = sdio_readsb(sdiodev->func, skbbuf->data,
+ sdiodev->sdio_reg.rd_fifo_addr, size);
+ sdio_release_host(sdiodev->func);
+
+ if (ret < 0)
+ return ret;
+ skbbuf->len = size;
+
+ return ret;
+}
+#endif
+
+#ifndef CONFIG_PLATFORM_EXTERNAL
+#ifdef CONFIG_GPIO_WAKEUP
+static int wakeup_enable;
+static u32 hostwake_irq_num;
+#endif
+#endif
+
+/* debug trace */
+// static struct wakeup_source *ws_rx_sdio;
+// static struct wakeup_source *ws_sdio_pwrctrl;
+// static struct wakeup_source *ws_tx_sdio;
+#ifdef CONFIG_GPIO_WAKEUP
+// static struct wakeup_source *ws;
+#endif
+
+#ifdef CONFIG_PLATFORM_EXTERNAL
+
+#define ENTER() (pr_info("AICWF enter %s\n", __func__))
+#define LEAVE() (pr_info("AICWF exit %s\n", __func__))
+
+int regist_wakeup_irq(struct aic_sdio_dev *sdiodev)
+{
+ int ret = -EINVAL;
+ struct device *dev = sdiodev->dev;
+ struct device_node *node;
+
+ ENTER();
+
+ node = of_find_compatible_node(NULL, NULL, "aic,wifi-wake-host");
+ if (!node)
+ goto err_exit;
+
+ sdiodev->wf_wake_host = irq_of_parse_and_map(node, 0);
+ if (!sdiodev->wf_wake_host) {
+ AICWFDBG(LOGERROR, "fail to parse wf_wake_host from device tree\n");
+ goto err_exit;
+ }
+
+ ret = devm_request_threaded_irq(dev, sdiodev->wf_wake_host,
+ NULL, wakeup_irq_handler,
+ IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
+ "wifi_wakeup", sdiodev);
+ if (ret) {
+ AICWFDBG(LOGERROR, "Failed to request wf_wake_host %d (%d)\n",
+ sdiodev->wf_wake_host, ret);
+ goto err_exit;
+ }
+
+ disable_irq(sdiodev->wf_wake_host);
+
+ sdiodev->host_wake_wf =
+ desc_to_gpio(gpiod_get_index_optional(dev, "host_wake_wifi", 0,
+ GPIOD_OUT_HIGH));
+
+ if (!gpio_is_valid(sdiodev->host_wake_wf)) {
+ AICWFDBG(LOGERROR, "get gpio host_wake_wifi failed\n");
+ goto err_exit;
+ }
+ ret = devm_gpio_request_one(dev, sdiodev->host_wake_wf, GPIOD_OUT_HIGH, "host_wake_wifi");
+ if (ret < 0) {
+ AICWFDBG(LOGERROR, "can't request host_wake_wifi gpio %d\n",
+ sdiodev->host_wake_wf);
+ goto err_exit;
+ }
+ ret = gpio_direction_output(sdiodev->host_wake_wf, 1);
+ if (ret < 0) {
+ AICWFDBG(LOGERROR,
+ "can't request output direction host_wake_bt gpio %d\n",
+ sdiodev->host_wake_wf);
+ goto err_exit;
+ }
+
+ // debug trace
+ // AICWFDBG(LOGINFO, "%s GPIO: %d, %d\n", __func__, sdiodev->wf_wake_host,
+ // sdiodev->host_wake_wf);
+
+ LEAVE();
+ return ret;
+
+err_exit:
+ sdiodev->wf_wake_host = -1;
+ sdiodev->host_wake_wf = -1;
+ return ret;
+}
+
+void unregist_wakeup_irq(struct aic_sdio_dev *sdiodev)
+{
+ // struct device *dev = sdiodev->dev;
+
+ // ENTER();
+ //LEAVE();
+}
+
+void disable_wakeup_irq(struct aic_sdio_dev *sdiodev)
+{
+ if (sdiodev->wf_wake_host >= 0) {
+ if (sdiodev->wake_by_wifi) {
+ disable_irq_wake(sdiodev->wf_wake_host);
+ } else {
+ disable_irq_wake(sdiodev->wf_wake_host);
+ disable_irq(sdiodev->wf_wake_host);
+ }
+ }
+}
+
+void enable_wakeup_irq(struct aic_sdio_dev *sdiodev)
+{
+ if (sdiodev->wf_wake_host >= 0) {
+ sdiodev->wake_by_wifi = false;
+ enable_irq(sdiodev->wf_wake_host);
+ enable_irq_wake(sdiodev->wf_wake_host);
+ }
+}
+
+irqreturn_t wakeup_irq_handler(int irq, void *priv)
+{
+ struct aic_sdio_dev *sdiodev = priv;
+ struct device *dev = sdiodev->dev;
+
+ // ENTER(); //debug trace
+
+ sdiodev->wake_by_wifi = true;
+ disable_irq_nosync(irq);
+
+ pm_wakeup_event(dev, 0);
+ pm_system_wakeup();
+
+ // LEAVE(); //debug trace
+ return IRQ_HANDLED;
+}
+#endif
+
+
+#ifndef CONFIG_PLATFORM_EXTERNAL
+#ifdef CONFIG_GPIO_WAKEUP
+static irqreturn_t rwnx_hostwake_irq_handler(int irq, void *para)
+{
+ static int wake_cnt;
+
+ wake_cnt++;
+
+ /* One-shot OOB wake event; it is not a paired SDIO activity unit. */
+ __pm_wakeup_event(g_rwnx_plat->sdiodev->rwnx_hw->ws_rx, 1000);
+
+ AICWFDBG(LOGIRQ, "%s(%d): wake_irq_cnt = %d\n", __func__, __LINE__,
+ wake_cnt);
+
+#ifdef CONFIG_OOB
+ if (g_rwnx_plat->sdiodev->oob_enable)
+ complete(&g_rwnx_plat->sdiodev->bus_if->busirq_trgg);
+#endif
+
+ return IRQ_HANDLED;
+}
+
+static int rwnx_disable_hostwake_irq(void);
+static int rwnx_enable_hostwake_irq(void);
+#endif
+
+static int rwnx_register_hostwake_irq(struct device *dev)
+{
+ int ret = 0; //-1;
+#ifdef CONFIG_GPIO_WAKEUP
+ unsigned long flag_edge;
+ struct aicbsp_feature_t aicwf_feature;
+
+ aicbsp_get_feature(&aicwf_feature);
+ if (aicwf_feature.irqf == 0)
+ flag_edge = IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND;
+ else
+ flag_edge = IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND;
+
+ if (wakeup_enable) {
+ /* debug func */
+ // ws = wakeup_source_register(dev, "wifisleep");
+ // ws_tx_sdio = wakeup_source_register(dev, "wifi_tx_sleep");
+ // ws_rx_sdio = wakeup_source_register(dev, "wifi_rx_sleep");
+ // ws_sdio_pwrctrl = wakeup_source_register(dev, "sdio_pwrctrl_sleep");
+ ret = device_init_wakeup(dev, true);
+ if (ret < 0) {
+ pr_err("%s(%d): device init wakeup failed!\n", __func__, __LINE__);
+ return ret;
+ }
+
+ ret = dev_pm_set_wake_irq(dev, hostwake_irq_num);
+ if (ret < 0) {
+ pr_err("%s(%d): can't enable wakeup src!\n", __func__, __LINE__);
+ goto fail1;
+ }
+
+ ret = request_irq(hostwake_irq_num, rwnx_hostwake_irq_handler,
+ flag_edge, "rwnx_hostwake_irq", NULL);
+
+ if (ret < 0) {
+ pr_err("%s(%d): request_irq fail! ret = %d\n", __func__, __LINE__,
+ ret);
+ goto fail2;
+ }
+ }
+ rwnx_disable_hostwake_irq();
+ dev_pm_clear_wake_irq(dev);
+ rwnx_enable_hostwake_irq();
+ AICWFDBG(LOGINFO, "%s(%d)\n", __func__, __LINE__);
+ return ret;
+
+fail2:
+ dev_pm_clear_wake_irq(dev);
+fail1:
+ device_init_wakeup(dev, false);
+ /* debug trace */
+ // wakeup_source_unregister(ws);
+ // wakeup_source_unregister(ws_tx_sdio);
+ // wakeup_source_unregister(ws_rx_sdio);
+ // wakeup_source_unregister(ws_sdio_pwrctrl);
+#endif // CONFIG_GPIO_WAKEUP
+ return ret;
+}
+
+static int rwnx_unregister_hostwake_irq(struct device *dev)
+{
+#ifdef CONFIG_GPIO_WAKEUP
+ rwnx_disable_hostwake_irq();
+ if (wakeup_enable) {
+ device_init_wakeup(dev, false);
+ dev_pm_clear_wake_irq(dev);
+ /* debug trace */
+ // wakeup_source_unregister(ws);
+ // wakeup_source_unregister(ws_tx_sdio);
+ // wakeup_source_unregister(ws_rx_sdio);
+ // wakeup_source_unregister(ws_sdio_pwrctrl);
+ }
+ free_irq(hostwake_irq_num, NULL);
+#endif // CONFIG_GPIO_WAKEUP
+ AICWFDBG(LOGINFO, "%s(%d)\n", __func__, __LINE__);
+ return 0;
+}
+
+#ifdef CONFIG_GPIO_WAKEUP
+static int rwnx_enable_hostwake_irq(void)
+{
+#ifdef CONFIG_GPIO_WAKEUP
+ enable_irq(hostwake_irq_num);
+ enable_irq_wake(hostwake_irq_num);
+#endif // CONFIG_GPIO_WAKEUP
+ AICWFDBG(LOGINFO, "%s(%d)\n", __func__, __LINE__);
+ return 0;
+}
+
+static int rwnx_disable_hostwake_irq(void)
+{
+ AICWFDBG(LOGINFO, "%s(%d)\n", __func__, __LINE__);
+#ifdef CONFIG_GPIO_WAKEUP
+ disable_irq_nosync(hostwake_irq_num);
+#endif // CONFIG_GPIO_WAKEUP
+ return 0;
+}
+#endif
+#endif
+
+//extern int rwnx_send_me_set_lp_level(struct rwnx_hw *rwnx_hw, u8 lp_level, u8 disable_filter);
+
+static int aicwf_sdio_probe(struct sdio_func *func,
+ const struct sdio_device_id *id)
+{
+ struct mmc_host *host;
+ struct aic_sdio_dev *sdiodev;
+ struct aicwf_bus *bus_if;
+ int err = -ENODEV;
+ u16 chipid;
+
+ AICWFDBG(LOGDEBUG, "%s:%d\n", __func__, func->num);
+ AICWFDBG(LOGDEBUG, "Class=%x\n", func->class);
+ AICWFDBG(LOGDEBUG, "sdio vendor ID: 0x%04x\n", func->vendor);
+ AICWFDBG(LOGDEBUG, "sdio device ID: 0x%04x\n", func->device);
+ AICWFDBG(LOGDEBUG, "Function#: %d\n", func->num);
+
+ if (!func) {
+ pr_warn("%s, NULL sdio func\n", __func__);
+ return 0;
+ }
+
+ host = func->card->host;
+ if (func->num != 1)
+ return err;
+
+ bus_if =
+ kzalloc_obj(*bus_if, GFP_KERNEL);
+ if (!bus_if) {
+ sdio_err("alloc bus fail\n");
+ return -ENOMEM;
+ }
+
+ sdiodev =
+ kzalloc_obj(*sdiodev, GFP_KERNEL);
+ if (!sdiodev) {
+ sdio_err("alloc sdiodev fail\n");
+ kfree(bus_if);
+ return -ENOMEM;
+ }
+
+ err = aicwf_sdio_chipmatch(func->vendor, func->device, &chipid);
+ sdiodev->chipid = chipid;
+ aic_sdio_hw = aic_sdio_get_props(chipid);
+
+ sdiodev->func = func;
+ sdiodev->bus_if = bus_if;
+
+#ifdef CONFIG_OOB
+ sdiodev->oob_enable = aic_sdio_hw->oob_support;
+#else
+ sdiodev->oob_enable = false;
+#endif
+
+ atomic_set(&sdiodev->is_bus_suspend, 0);
+ bus_if->bus_priv.sdio = sdiodev;
+
+ dev_set_drvdata(&func->dev, bus_if);
+ sdiodev->dev = &func->dev;
+
+ if (aic_sdio_hw->use_func2)
+ sdiodev->func2 = func->card->sdio_func[1];
+
+ //sdio func init start
+ if (!aic_sdio_hw->use_sdiov3_func)
+ err = aicwf_sdio_func_init(sdiodev);
+ else
+ err = aicwf_sdiov3_func_init(sdiodev);
+
+ if (err < 0) {
+ sdio_err("sdio func init fail\n");
+ goto fail;
+ }
+ // sdio func init end
+
+ if (!aicwf_sdio_bus_init(sdiodev)) {
+ sdio_err("sdio bus init fail\n");
+ err = -1;
+ goto fail;
+ }
+
+ host->caps |= MMC_CAP_NONREMOVABLE;
+ aicwf_rwnx_sdio_platform_init(sdiodev);
+ aicwf_hostif_ready();
+#ifdef CONFIG_PLATFORM_EXTERNAL
+ regist_wakeup_irq(sdiodev);
+#else
+ err = rwnx_register_hostwake_irq(sdiodev->dev);
+#endif
+ if (err != 0)
+ return err;
+
+#ifdef CONFIG_GPIO_WAKEUP
+#ifdef CONFIG_OOB
+ if (sdiodev->oob_enable) {
+ AICWFDBG(LOGINFO, "%s SDIOWIFI_INTR_CONFIG_REG Disable\n", __func__);
+ sdio_claim_host(sdiodev->func);
+ // disable sdio interrupt
+ err = aicwf_sdio_writeb(sdiodev, SDIOWIFI_INTR_CONFIG_REG, 0x0);
+ if (err < 0)
+ sdio_err("reg:%d write failed!\n", SDIOWIFI_INTR_CONFIG_REG);
+ sdio_release_irq(sdiodev->func);
+ sdio_release_host(sdiodev->func);
+ }
+#endif
+
+#endif // CONFIG_GPIO_WAKEUP
+ device_disable_async_suspend(sdiodev->dev);
+
+ return 0;
+fail:
+ aicwf_sdio_func_deinit(sdiodev);
+ dev_set_drvdata(&func->dev, NULL);
+ kfree(sdiodev);
+ kfree(bus_if);
+ aicwf_hostif_fail();
+ return err;
+}
+
+void aicwf_sdio_probe_(struct sdio_func *func, const struct sdio_device_id *id)
+{
+ aicwf_sdio_probe(func, NULL);
+}
+
+static void aicwf_sdio_remove(struct sdio_func *func)
+{
+ struct mmc_host *host;
+ struct aicwf_bus *bus_if = NULL;
+ struct aic_sdio_dev *sdiodev = NULL;
+
+ host = func->card->host;
+ host->caps &= ~MMC_CAP_NONREMOVABLE;
+ bus_if = dev_get_drvdata(&func->dev);
+ if (!bus_if)
+ return;
+
+ sdiodev = bus_if->bus_priv.sdio;
+ if (!sdiodev)
+ return;
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+ aicwf_sdio_pwr_stctl(sdiodev, SDIO_SLEEP_ST);
+#endif
+
+ sdiodev->bus_if->state = BUS_DOWN_ST;
+ aicwf_sdio_release(sdiodev);
+ aicwf_sdio_func_deinit(sdiodev);
+#ifdef CONFIG_PLATFORM_EXTERNAL
+ unregist_wakeup_irq(sdiodev);
+#else
+ rwnx_unregister_hostwake_irq(sdiodev->dev);
+#endif
+ dev_set_drvdata(&sdiodev->func->dev, NULL);
+ kfree(sdiodev);
+ kfree(bus_if);
+}
+
+void aicwf_sdio_remove_(struct sdio_func *func)
+{
+ aicwf_sdio_remove(func);
+}
+
+#ifndef CONFIG_FDRV_NO_REG_SDIO
+static int aicwf_sdio_suspend(struct device *dev)
+{
+ int ret = 0;
+ struct aicwf_bus *bus_if = dev_get_drvdata(dev);
+ struct aic_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ mmc_pm_flag_t sdio_flags;
+ struct rwnx_vif *rwnx_vif, *tmp;
+#ifdef CONFIG_AIC8800_NO_WAKE_COUNT
+ int cnt = 0;
+#endif
+ int sus_abort = 0;
+
+ list_for_each_entry_safe(rwnx_vif, tmp, &sdiodev->rwnx_hw->vifs, list) {
+ if (rwnx_vif->ndev)
+ netif_device_detach(rwnx_vif->ndev);
+ }
+
+ if (sdiodev->rwnx_hw->testmode == 1)
+ pr_err("AICWF %s, sleep cmd is not supported in RF test mode\n", __func__);
+#ifdef CONFIG_AIC8800_NO_WAKE_COUNT //move to when set supend is received
+ else
+ rwnx_send_me_set_lp_level(sdiodev->rwnx_hw, 1, 0); //dynamic switch
+#endif
+
+#if (defined(CONFIG_AIC8800_AUTO_POWERSAVE) && defined(CONFIG_SDIO_PWRCTRL))
+ aicwf_sdio_pwr_stctl(sdiodev, SDIO_ACTIVE_ST);
+ if (aic_sdio_hw->auto_ps_support) {
+ AICWFDBG(LOGDEBUG, "auto_ps enter\n");
+ ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.wakeup_reg, 0x8);
+ if (ret)
+ AICWFDBG(LOGERROR, "sdio enter auto_ps fail\n");
+ }
+#endif
+
+#ifdef CONFIG_AIC8800_NO_WAKE_COUNT
+ while (&sdiodev->rwnx_hw->scan_request && rwnx_hw->scanning) {
+ /* move to when set supend is received, the theory does not enforce this condition,
+ * and the code is retained for now
+ */
+ pr_info("AICWF wait scan_cfm\n");
+ if (cnt == 0) {
+ rwnx_send_scanu_cancel_req(sdiodev->rwnx_hw, NULL);
+ continue;
+ }
+ msleep(20);
+ cnt += 1;
+ if (cnt >= 40)
+ break;
+ }
+#endif
+
+ sdio_flags = sdio_get_host_pm_caps(sdiodev->func);
+ if (!(sdio_flags & MMC_PM_KEEP_POWER))
+ return -EINVAL;
+ ret = sdio_set_host_pm_flags(sdiodev->func, MMC_PM_KEEP_POWER);
+ if (ret)
+ return ret;
+
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+ //del_timer_sync(&sdiodev->tp_ctrl.tp_ctrl_timer);
+ timer_delete_sync(&sdiodev->tp_ctrl.tp_ctrl_timer);
+ cancel_work_sync(&sdiodev->tp_ctrl.tp_ctrl_work);
+#endif
+
+ atomic_set(&sdiodev->suspending, 1);
+ smp_mb__after_atomic();
+ if (!wait_event_timeout(sdiodev->activity_waitq,
+ atomic_read(&sdiodev->activity_count) == 0,
+ msecs_to_jiffies(600)))
+ sus_abort = 1;
+ if (sus_abort) {
+ atomic_set(&sdiodev->suspending, 0);
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+ mod_timer(&sdiodev->tp_ctrl.tp_ctrl_timer,
+ jiffies + msecs_to_jiffies(TEMP_GET_INTERVAL));
+#endif
+ pr_info("AICWF ws active dont suspend\n");
+
+#if defined(CONFIG_AIC8800_AUTO_POWERSAVE)
+ aicwf_sdio_pwr_stctl(sdiodev, SDIO_ACTIVE_ST);
+ if (aic_sdio_hw->auto_ps_support) {
+ AICWFDBG(LOGDEBUG, "auto_ps abort\n");
+ ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.wakeup_reg, 0x8);
+ if (ret)
+ AICWFDBG(LOGERROR, "sdio exit auto_ps fail\n");
+ }
+#elif defined(CONFIG_AIC8800_NO_WAKE_COUNT)
+ if (sdiodev->rwnx_hw->testmode == 0)
+ rwnx_send_me_set_lp_level(sdiodev->rwnx_hw, 0, 1); //dynamic switch
+#endif
+ list_for_each_entry_safe(rwnx_vif, tmp, &sdiodev->rwnx_hw->vifs, list) {
+ if (rwnx_vif->ndev) {
+ pr_info("AICWF suspend restore\n");
+ netif_device_attach(rwnx_vif->ndev);
+ }
+ }
+ return -EBUSY;
+ }
+
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+ //del_timer_sync(&sdiodev->tp_ctrl.netif_timer);
+ timer_delete_sync(&sdiodev->tp_ctrl.netif_timer);
+ cancel_work_sync(&sdiodev->tp_ctrl.netif_work);
+#endif
+
+#ifndef CONFIG_AIC8800_AUTO_POWERSAVE
+ while (sdiodev->state == SDIO_ACTIVE_ST) {
+ if (down_interruptible(&sdiodev->tx_priv->txctl_sema))
+ continue;
+#if defined(CONFIG_SDIO_PWRCTRL)
+ aicwf_sdio_pwr_stctl(sdiodev, SDIO_SLEEP_ST);
+#endif
+ up(&sdiodev->tx_priv->txctl_sema);
+ break;
+ }
+#else
+#if defined(CONFIG_SDIO_PWRCTRL)
+ aicwf_sdio_pwr_stctl(sdiodev, SDIO_SLEEP_ST);
+#endif
+#endif
+
+#ifdef CONFIG_GPIO_WAKEUP
+// rwnx_enable_hostwake_irq();
+#endif
+
+#ifdef CONFIG_PLATFORM_EXTERNAL
+ enable_wakeup_irq(sdiodev);
+#endif
+
+ atomic_set(&sdiodev->is_bus_suspend, 1);
+
+ return 0;
+}
+
+static int aicwf_sdio_resume(struct device *dev)
+{
+#if defined(CONFIG_AIC8800_AUTO_POWERSAVE) && defined(CONFIG_SDIO_PWRCTRL)
+ int ret;
+#endif
+ struct aicwf_bus *bus_if = dev_get_drvdata(dev);
+ struct aic_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct rwnx_vif *rwnx_vif, *tmp;
+
+#ifdef CONFIG_AIC8800_NO_WAKE_COUNT
+ if (sdiodev->rwnx_hw->testmode == 0)
+ rwnx_send_me_set_lp_level(sdiodev->rwnx_hw, 0, 1); //dynamic switch
+#endif
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+ mod_timer(&sdiodev->tp_ctrl.tp_ctrl_timer,
+ jiffies + msecs_to_jiffies(TEMP_GET_INTERVAL));
+#endif
+ list_for_each_entry_safe(rwnx_vif, tmp, &sdiodev->rwnx_hw->vifs, list) {
+ if (rwnx_vif->ndev) {
+ AICWFDBG(LOGDEBUG, "resume netif_attach\n");
+ netif_device_attach(rwnx_vif->ndev);
+ }
+ }
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+ aicwf_sdio_pwr_stctl(sdiodev, SDIO_ACTIVE_ST);
+#endif
+
+#if defined(CONFIG_AIC8800_AUTO_POWERSAVE) && defined(CONFIG_SDIO_PWRCTRL)
+ if (aic_sdio_hw->auto_ps_support) {
+ AICWFDBG(LOGDEBUG, "auto_ps exit\n");
+ ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.wakeup_reg, 0x8);
+ if (ret)
+ AICWFDBG(LOGERROR, "sdio exit auto_ps fail\n");
+ }
+#endif
+
+ atomic_set(&sdiodev->is_bus_suspend, 0);
+ atomic_set(&sdiodev->suspending, 0);
+
+#ifdef CONFIG_PLATFORM_EXTERNAL
+ disable_wakeup_irq(sdiodev);
+#endif
+
+ return 0;
+}
+#endif
+
+static const struct sdio_device_id aicwf_sdmmc_ids[] = {
+ {SDIO_DEVICE(SDIO_VENDOR_ID_AIC8800D80, SDIO_DEVICE_ID_AIC8800D80)},
+ {},
+};
+
+MODULE_DEVICE_TABLE(sdio, aicwf_sdmmc_ids);
+
+#ifndef CONFIG_FDRV_NO_REG_SDIO
+static const struct dev_pm_ops aicwf_sdio_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(aicwf_sdio_suspend, aicwf_sdio_resume)
+};
+
+static struct sdio_driver aicwf_sdio_driver = {
+ .probe = aicwf_sdio_probe,
+ .remove = aicwf_sdio_remove,
+ .name = AICWF_SDIO_NAME,
+ .id_table = aicwf_sdmmc_ids,
+ .drv = {
+ .pm = &aicwf_sdio_pm_ops,
+ },
+};
+#endif
+
+#ifdef CONFIG_FDRV_NO_REG_SDIO
+extern struct sdio_func *get_sdio_func(void);
+#endif
+
+void aicwf_sdio_register(void)
+{
+#ifndef CONFIG_FDRV_NO_REG_SDIO
+ if (sdio_register_driver(&aicwf_sdio_driver))
+ AICWFDBG(LOGERROR, "%s sdio_register_driver\r\n", __func__);
+#else
+ aicwf_sdio_probe_(get_sdio_func(), NULL);
+#endif
+}
+
+void aicwf_sdio_exit(void)
+{
+ if (g_rwnx_plat && g_rwnx_plat->enabled)
+ rwnx_platform_deinit(g_rwnx_plat->sdiodev->rwnx_hw);
+ else
+ AICWFDBG(LOGERROR, "%s g_rwnx_plat is not ready \r\n", __func__);
+
+ usleep_range(50, 60);
+
+#ifndef CONFIG_FDRV_NO_REG_SDIO
+ sdio_unregister_driver(&aicwf_sdio_driver);
+#else
+ aicwf_sdio_remove_(get_sdio_func());
+#endif
+
+ kfree(g_rwnx_plat);
+}
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+int aicwf_sdio_wakeup(struct aic_sdio_dev *sdiodev)
+{
+ int ret = 0;
+ int read_retry;
+ int write_retry = 1;
+ int wakeup_reg_val = 0;
+
+ wakeup_reg_val = aic_sdio_hw->wakeup_reg_val;
+
+ if (sdiodev->state == SDIO_SLEEP_ST) {
+ AICWFDBG(LOGSDPWRC, "%s w\n", __func__);
+
+ while (write_retry) {
+ ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.wakeup_reg,
+ wakeup_reg_val);
+ if (ret) {
+ txrx_err("sdio wakeup fail\n");
+ ret = -1;
+ } else {
+ read_retry = 50;
+ while (read_retry) {
+ u8 val;
+
+ ret = aicwf_sdio_readb(sdiodev,
+ sdiodev->sdio_reg.wakeup_reg,
+ &val);
+ if (ret < 0)
+ txrx_err("sdio wakeup read fail\n");
+ else if ((val & 0x1) == 0)
+ break;
+ read_retry--;
+ usleep_range(50, 60);
+ }
+ if (read_retry != 0)
+ break;
+ }
+ sdio_dbg("write retry: %d\n", write_retry);
+ write_retry--;
+ usleep_range(40, 50);
+ }
+
+ sdiodev->state = SDIO_ACTIVE_ST;
+ aicwf_sdio_pwrctl_timer(sdiodev, sdiodev->active_duration);
+ }
+ return ret;
+}
+
+int aicwf_sdio_sleep_allow(struct aic_sdio_dev *sdiodev)
+{
+ int ret = 0;
+ struct aicwf_bus *bus_if = sdiodev->bus_if;
+ struct rwnx_hw *rwnx_hw = sdiodev->rwnx_hw;
+ u8 read_retry;
+ u8 val;
+
+ if (bus_if->state == BUS_DOWN_ST) {
+ ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.sleep_reg, 0x10);
+ if (ret)
+ sdio_err("Write sleep fail!\n");
+ aicwf_sdio_pwrctl_timer(sdiodev, 0);
+ return ret;
+ }
+
+ sdio_info("sleep: %d, %d\n", sdiodev->state, rwnx_hw->scanning);
+ if (sdiodev->state == SDIO_ACTIVE_ST && !rwnx_hw->scanning && !rwnx_hw->is_p2p_alive &&
+ !rwnx_hw->is_p2p_connected &&
+ (int)(atomic_read(&sdiodev->tx_priv->tx_pktcnt) <= 0) &&
+ !sdiodev->tx_priv->cmd_txstate &&
+ (int)(atomic_read(&sdiodev->rx_priv->rx_cnt) == 0)) {
+ AICWFDBG(LOGSDPWRC, "%s s\n", __func__);
+ if (!aic_sdio_hw->use_func2) {
+ if (aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.wakeup_reg, 0x02) < 0)
+ sdio_err("reg:%d write failed!\n", sdiodev->sdio_reg.wakeup_reg);
+ } else {
+ if (aicwf_sdio_func2_writeb(sdiodev, sdiodev->sdio_reg.wakeup_reg, 0x2) < 0)
+ sdio_err("reg:%d write failed!\n", sdiodev->sdio_reg.wakeup_reg);
+ read_retry = 100;
+ while (read_retry) {
+ val = 0;
+ if (aicwf_sdio_func2_readb(sdiodev,
+ sdiodev->sdio_reg.wakeup_reg,
+ &val) < 0)
+ sdio_err("reg %d read fail\n",
+ sdiodev->sdio_reg.wakeup_reg);
+ else if ((val & 0x2) == 0)
+ break;
+ sdio_err("val:%d\n", val);
+ read_retry--;
+ if (read_retry < 90)
+ pr_warn("warning: read cnt %d\n", read_retry);
+ usleep_range(50, 60);
+ }
+ }
+ sdiodev->state = SDIO_SLEEP_ST;
+ aicwf_sdio_pwrctl_timer(sdiodev, 0);
+ } else {
+ aicwf_sdio_pwrctl_timer(sdiodev, sdiodev->active_duration);
+ }
+
+ return ret;
+}
+
+int aicwf_sdio_pwr_stctl(struct aic_sdio_dev *sdiodev, uint target)
+{
+ int ret = 0;
+
+ if (sdiodev->bus_if->state == BUS_DOWN_ST)
+ return -1;
+
+ down(&sdiodev->pwrctl_wakeup_sema);
+
+ if (sdiodev->state == target) {
+ if (target == SDIO_ACTIVE_ST)
+ aicwf_sdio_pwrctl_timer(sdiodev, sdiodev->active_duration);
+ up(&sdiodev->pwrctl_wakeup_sema);
+ return ret;
+ }
+
+ switch (target) {
+ case SDIO_ACTIVE_ST:
+ aicwf_sdio_wakeup(sdiodev);
+ break;
+ case SDIO_SLEEP_ST:
+ aicwf_sdio_sleep_allow(sdiodev);
+ break;
+ }
+
+ up(&sdiodev->pwrctl_wakeup_sema);
+ return ret;
+}
+#endif
+
+int aicwf_sdio_txpkt(struct aic_sdio_dev *sdiodev, struct sk_buff *pkt)
+{
+ int ret = 0;
+ u32 len = 0;
+ struct aicwf_bus *bus_if = dev_get_drvdata(sdiodev->dev);
+
+ if (bus_if->state == BUS_DOWN_ST) {
+ sdio_dbg("tx bus is down!\n");
+ return -EINVAL;
+ }
+
+ len = pkt->len;
+ len = (len + SDIOWIFI_FUNC_BLOCKSIZE - 1) / SDIOWIFI_FUNC_BLOCKSIZE *
+ SDIOWIFI_FUNC_BLOCKSIZE;
+
+ ret = aicwf_sdio_send_pkt(sdiodev, pkt->data, len);
+ if (ret)
+ sdio_err("aicwf_sdio_send_pkt fail%d\n", ret);
+ return ret;
+}
+
+static int aicwf_sdio_intr_get_len_bytemode(struct aic_sdio_dev *sdiodev,
+ u8 *byte_len)
+{
+ int ret = 0;
+
+ if (!byte_len)
+ return -EBADE;
+
+ if (sdiodev->bus_if->state == BUS_DOWN_ST) {
+ *byte_len = 0;
+ } else {
+ ret = aicwf_sdio_readb(sdiodev, sdiodev->sdio_reg.bytemode_len_reg,
+ byte_len);
+ sdiodev->rx_priv->data_len = (*byte_len) * 4;
+ }
+
+ return ret;
+}
+
+static void aicwf_sdio_bus_stop(struct device *dev)
+{
+ struct aicwf_bus *bus_if = dev_get_drvdata(dev);
+ struct aic_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ int ret = 0;
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+ aicwf_sdio_pwrctl_timer(sdiodev, 0);
+#endif
+
+ bus_if->state = BUS_DOWN_ST;
+ if (sdiodev->tx_priv) {
+ ret = down_interruptible(&sdiodev->tx_priv->txctl_sema);
+ if (ret)
+ AICWFDBG(LOGERROR, "down txctl_sema fail\n");
+ }
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+ aicwf_sdio_pwr_stctl(sdiodev, SDIO_SLEEP_ST);
+#endif
+
+ if (sdiodev->tx_priv) {
+ if (!ret)
+ up(&sdiodev->tx_priv->txctl_sema);
+ aicwf_frame_queue_flush(&sdiodev->tx_priv->txq);
+ }
+}
+
+#ifdef CONFIG_PREALLOC_RX_SKB
+struct rx_buff *aicwf_sdio_readframes(struct aic_sdio_dev *sdiodev)
+{
+ int ret = 0;
+ u32 size = 0;
+ struct aicwf_bus *bus_if = dev_get_drvdata(sdiodev->dev);
+ struct rx_buff *rxbuff;
+
+ if (bus_if->state == BUS_DOWN_ST) {
+ sdio_dbg("bus down\n");
+ return NULL;
+ }
+
+ size = sdiodev->rx_priv->data_len;
+ rxbuff = aicwf_prealloc_rxbuff_alloc(&sdiodev->rx_priv->rxbuff_lock);
+ if (!rxbuff) {
+ pr_err("failed to alloc rxbuff\n");
+ return NULL;
+ }
+ rxbuff->len = 0;
+ rxbuff->start = rxbuff->data;
+ rxbuff->read = rxbuff->start;
+ rxbuff->end = rxbuff->data + size;
+
+ ret = aicwf_sdio_recv_pkt(sdiodev, rxbuff, size);
+ if (ret) {
+ pr_err("%s %d, sdio recv pkt fail\n", __func__, __LINE__);
+ aicwf_prealloc_rxbuff_free(rxbuff, &sdiodev->rx_priv->rxbuff_lock);
+ return NULL;
+ }
+
+ return rxbuff;
+}
+#else
+struct sk_buff *aicwf_sdio_readframes(struct aic_sdio_dev *sdiodev)
+{
+ int ret = 0;
+ u32 size = 0;
+ struct sk_buff *skb = NULL;
+ struct aicwf_bus *bus_if = dev_get_drvdata(sdiodev->dev);
+
+ if (bus_if->state == BUS_DOWN_ST) {
+ sdio_dbg("bus down\n");
+ return NULL;
+ }
+
+ size = sdiodev->rx_priv->data_len;
+ skb = __dev_alloc_skb(size, GFP_KERNEL);
+ if (!skb)
+ return NULL;
+
+ ret = aicwf_sdio_recv_pkt(sdiodev, skb, size);
+ if (ret) {
+ dev_kfree_skb(skb);
+ skb = NULL;
+ }
+
+ return skb;
+}
+#endif
+
+static int aicwf_sdio_tx_msg(struct aic_sdio_dev *sdiodev)
+{
+ int err = 0;
+ u16 len;
+ u8 *payload = sdiodev->tx_priv->cmd_buf;
+ u16 payload_len = sdiodev->tx_priv->cmd_len;
+ u8 adjust_str[4] = {0, 0, 0, 0};
+ int adjust_len = 0;
+ int buffer_cnt = 0;
+ u8 retry = 0;
+
+ len = payload_len;
+ if ((len % TX_ALIGNMENT) != 0) {
+ adjust_len = roundup(len, TX_ALIGNMENT);
+ memcpy(payload + payload_len, adjust_str, (adjust_len - len));
+ payload_len += (adjust_len - len);
+ }
+ len = payload_len;
+
+ // link tail is necessary
+ if ((len % SDIOWIFI_FUNC_BLOCKSIZE) != 0) {
+ memset(payload + payload_len, 0, TAIL_LEN);
+ payload_len += TAIL_LEN;
+ len = (payload_len / SDIOWIFI_FUNC_BLOCKSIZE + 1) *
+ SDIOWIFI_FUNC_BLOCKSIZE;
+ } else {
+ len = payload_len;
+ }
+
+ if (aic_sdio_hw->use_flowctrl_msg) {
+ buffer_cnt = aicwf_sdio_flow_ctrl_msg(sdiodev);
+ while ((buffer_cnt <= 0 ||
+ (buffer_cnt > 0 && len > (buffer_cnt * BUFFER_SIZE))) &&
+ retry < 10) {
+ retry++;
+ buffer_cnt = aicwf_sdio_flow_ctrl_msg(sdiodev);
+ pr_info("buffer_cnt = %d\n", buffer_cnt);
+ }
+ }
+ down(&sdiodev->tx_priv->cmd_txsema);
+
+ if (aic_sdio_hw->use_flowctrl_msg) {
+ if (buffer_cnt > 0 && len < (buffer_cnt * BUFFER_SIZE)) {
+ err = aicwf_sdio_send_pkt(sdiodev, payload, len);
+ if (err)
+ sdio_err("aicwf_sdio_send_pkt fail%d\n", err);
+ } else {
+ sdio_err("tx msg fc retry fail:%d, %d\n", buffer_cnt, len);
+ up(&sdiodev->tx_priv->cmd_txsema);
+ return -1;
+ }
+ } else {
+ err = aicwf_sdio_send_pkt(sdiodev, payload, len);
+ if (err)
+ sdio_err("aicwf_sdio_send_pkt fail%d\n", err);
+ }
+
+ sdiodev->tx_priv->cmd_txstate = false;
+ if (!err)
+ sdiodev->tx_priv->cmd_tx_succ = true;
+ else
+ sdiodev->tx_priv->cmd_tx_succ = false;
+
+ up(&sdiodev->tx_priv->cmd_txsema);
+
+ return err;
+}
+
+static void aicwf_sdio_tx_process(struct aic_sdio_dev *sdiodev)
+{
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+ unsigned long flags;
+#endif
+
+ if (sdiodev->bus_if->state == BUS_DOWN_ST) {
+ sdio_err("Bus is down\n");
+ return;
+ }
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+ aicwf_sdio_pwr_stctl(sdiodev, SDIO_ACTIVE_ST);
+#endif
+
+ // config
+ sdio_info("send cmd\n");
+ if (sdiodev->tx_priv->cmd_txstate) {
+ if (down_interruptible(&sdiodev->tx_priv->txctl_sema)) {
+ txrx_err("txctl down bus->txctl_sema fail\n");
+ return;
+ }
+ if (sdiodev->state != SDIO_ACTIVE_ST) {
+ txrx_err("state err\n");
+ up(&sdiodev->tx_priv->txctl_sema);
+ txrx_err("txctl up bus->txctl_sema fail\n");
+ return;
+ }
+
+ if (aicwf_sdio_tx_msg(sdiodev))
+ sdio_err("failed to send command\n");
+ up(&sdiodev->tx_priv->txctl_sema);
+ // Only wake up if someone is actually waiting
+ if (waitqueue_active(&sdiodev->tx_priv->cmd_txdone_wait))
+ wake_up(&sdiodev->tx_priv->cmd_txdone_wait);
+ }
+
+ // data
+ sdio_info("send data\n");
+ if (down_interruptible(&sdiodev->tx_priv->txctl_sema)) {
+ txrx_err("txdata down bus->txctl_sema\n");
+ return;
+ }
+
+ if (sdiodev->state != SDIO_ACTIVE_ST) {
+ txrx_err("sdio state err\n");
+ up(&sdiodev->tx_priv->txctl_sema);
+ return;
+ }
+
+ if (!aicwf_is_framequeue_empty(&sdiodev->tx_priv->txq))
+ sdiodev->tx_priv->fw_avail_bufcnt = aicwf_sdio_flow_ctrl(sdiodev);
+ while (!aicwf_is_framequeue_empty(&sdiodev->tx_priv->txq)) {
+ if (sdiodev->bus_if->state == BUS_DOWN_ST)
+ break;
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+ if (sdiodev->tp_ctrl.on_off)
+ aicwf_temp_ctrl(sdiodev);
+#endif
+ if (sdiodev->tx_priv->fw_avail_bufcnt <= DATA_FLOW_CTRL_THRESH) {
+ if (sdiodev->tx_priv->cmd_txstate)
+ break;
+ sdiodev->tx_priv->fw_avail_bufcnt = aicwf_sdio_flow_ctrl(sdiodev);
+ } else {
+ if (sdiodev->tx_priv->cmd_txstate) {
+ aicwf_sdio_send(sdiodev->tx_priv, 1);
+ break;
+ }
+ aicwf_sdio_send(sdiodev->tx_priv, 0);
+ }
+ }
+
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+ spin_lock_irqsave(&sdiodev->tx_flow_lock, flags);
+ if (atomic_read(&sdiodev->tx_priv->tx_pktcnt) < tx_fc_low_water) {
+ /* debug trace */
+ // printk("sdiodev->tx_priv->tx_pktcnt < tx_fc_low_water:%d %d\r\n",
+ // atomic_read(&sdiodev->tx_priv->tx_pktcnt), tx_fc_low_water);
+ if (sdiodev->flowctrl) {
+ sdiodev->flowctrl = 0;
+ aicwf_sdio_tx_netif_flowctrl(sdiodev->rwnx_hw, false);
+ }
+ }
+ spin_unlock_irqrestore(&sdiodev->tx_flow_lock, flags);
+#endif
+
+ up(&sdiodev->tx_priv->txctl_sema);
+}
+
+static int aicwf_sdio_bus_txdata(struct device *dev, struct sk_buff *pkt)
+{
+ uint prio;
+ int ret = -EBADE;
+ struct rwnx_txhdr *txhdr = NULL;
+ int headroom = 0;
+ struct aicwf_bus *bus_if = dev_get_drvdata(dev);
+ struct aic_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+ unsigned long flags;
+#endif
+
+ if (bus_if->state == BUS_DOWN_ST) {
+ sdio_err("bus_if stopped\n");
+ txhdr = (struct rwnx_txhdr *)pkt->data;
+ headroom = txhdr->sw_hdr->headroom;
+ kmem_cache_free(txhdr->sw_hdr->rwnx_vif->rwnx_hw->sw_txhdr_cache,
+ txhdr->sw_hdr);
+ skb_pull(pkt, headroom);
+ consume_skb(pkt);
+ return -1;
+ }
+
+ prio = (pkt->priority & 0x7);
+ spin_lock_bh(&sdiodev->tx_priv->txqlock);
+ if (!aicwf_frame_enq(sdiodev->dev, &sdiodev->tx_priv->txq, pkt, prio)) {
+ txhdr = (struct rwnx_txhdr *)pkt->data;
+ headroom = txhdr->sw_hdr->headroom;
+ kmem_cache_free(txhdr->sw_hdr->rwnx_vif->rwnx_hw->sw_txhdr_cache,
+ txhdr->sw_hdr);
+ skb_pull(pkt, headroom);
+ consume_skb(pkt);
+ spin_unlock_bh(&sdiodev->tx_priv->txqlock);
+ return -ENOSR;
+ goto flowctrl;
+ } else {
+ ret = 0;
+ }
+
+ atomic_inc(&sdiodev->tx_priv->tx_pktcnt);
+ spin_unlock_bh(&sdiodev->tx_priv->txqlock);
+ complete(&bus_if->bustx_trgg);
+
+flowctrl:
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+ spin_lock_irqsave(&sdiodev->tx_flow_lock, flags);
+ if (atomic_read(&sdiodev->tx_priv->tx_pktcnt) >= tx_fc_high_water) {
+ /* debug trace */
+ // printk("sdiodev->tx_priv->tx_pktcnt >= tx_fc_high_water:%d %d\r\n",
+ // atomic_read(&sdiodev->tx_priv->tx_pktcnt), tx_fc_high_water);
+ if (!sdiodev->flowctrl) {
+ sdiodev->flowctrl = 1;
+ aicwf_sdio_tx_netif_flowctrl(sdiodev->rwnx_hw, true);
+ }
+ }
+ spin_unlock_irqrestore(&sdiodev->tx_flow_lock, flags);
+#endif
+
+ return ret;
+}
+
+static int aicwf_sdio_bus_txmsg(struct device *dev, u8 *msg, uint msglen)
+{
+ struct aicwf_bus *bus_if = dev_get_drvdata(dev);
+ struct aic_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+
+ down(&sdiodev->tx_priv->cmd_txsema);
+ sdiodev->tx_priv->cmd_txstate = true;
+ sdiodev->tx_priv->cmd_tx_succ = false;
+ sdiodev->tx_priv->cmd_buf = msg;
+ sdiodev->tx_priv->cmd_len = msglen;
+ up(&sdiodev->tx_priv->cmd_txsema);
+
+ if (bus_if->state != BUS_UP_ST) {
+ sdio_err("bus has stop\n");
+ return -1;
+ }
+
+ complete(&bus_if->bustx_trgg);
+ return 0;
+}
+
+int aicwf_sdio_send(struct aicwf_tx_priv *tx_priv, u8 txnow)
+{
+ struct sk_buff *pkt;
+ struct aic_sdio_dev *sdiodev = tx_priv->sdiodev;
+ u32 aggr_len = 0;
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+ unsigned long flags;
+#endif
+
+ aggr_len = (tx_priv->tail - tx_priv->head);
+ if (((atomic_read(&tx_priv->aggr_count) == 0) && aggr_len != 0) ||
+ ((atomic_read(&tx_priv->aggr_count) != 0) && aggr_len == 0)) {
+ if (aggr_len > 0)
+ aicwf_sdio_aggrbuf_reset(tx_priv);
+ return 0;
+ }
+
+ if (atomic_read(&tx_priv->aggr_count) ==
+ (tx_priv->fw_avail_bufcnt - DATA_FLOW_CTRL_THRESH)) {
+ if (atomic_read(&tx_priv->aggr_count) > 0) {
+ tx_priv->fw_avail_bufcnt -= atomic_read(&tx_priv->aggr_count);
+ aicwf_sdio_aggr_send(tx_priv); // send and check the next pkt;
+ }
+ return 0;
+ }
+ spin_lock_bh(&sdiodev->tx_priv->txqlock);
+ pkt = aicwf_frame_dequeue(&sdiodev->tx_priv->txq);
+ if (!pkt) {
+ sdio_err("txq no pkt\n");
+ spin_unlock_bh(&sdiodev->tx_priv->txqlock);
+ return 0;
+ }
+ //atomic_dec(&sdiodev->tx_priv->tx_pktcnt);
+ spin_unlock_bh(&sdiodev->tx_priv->txqlock);
+
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+ spin_lock_irqsave(&sdiodev->tx_flow_lock, flags);
+ if (atomic_read(&sdiodev->tx_priv->tx_pktcnt) < tx_fc_low_water) {
+ /* debug trace */
+ // printk("sdiodev->tx_priv->tx_pktcnt < tx_fc_low_water:%d %d\r\n",
+ // atomic_read(&sdiodev->tx_priv->tx_pktcnt), tx_fc_low_water);
+ if (sdiodev->flowctrl) {
+ sdiodev->flowctrl = 0;
+ aicwf_sdio_tx_netif_flowctrl(sdiodev->rwnx_hw, false);
+ }
+ }
+ spin_unlock_irqrestore(&sdiodev->tx_flow_lock, flags);
+#endif
+
+ if (!tx_priv || !tx_priv->tail || !pkt)
+ txrx_err("null error\n");
+ if (aicwf_sdio_aggr(tx_priv, pkt)) {
+ aicwf_sdio_aggrbuf_reset(tx_priv);
+ sdio_err("add aggr pkts failed!\n");
+ atomic_dec(&sdiodev->tx_priv->tx_pktcnt);
+ return 0;
+ }
+
+ //when aggr finish or there is cmd to send, just send this aggr pkt to fw
+ if ((int)atomic_read(&sdiodev->tx_priv->tx_pktcnt) == 1 ||
+ txnow || (atomic_read(&tx_priv->aggr_count) ==
+ (tx_priv->fw_avail_bufcnt - DATA_FLOW_CTRL_THRESH))) {
+ tx_priv->fw_avail_bufcnt -= atomic_read(&tx_priv->aggr_count);
+ aicwf_sdio_aggr_send(tx_priv);
+ atomic_dec(&sdiodev->tx_priv->tx_pktcnt);
+ return 0;
+ }
+ atomic_dec(&sdiodev->tx_priv->tx_pktcnt);
+ return 0;
+}
+
+int aicwf_sdio_aggr(struct aicwf_tx_priv *tx_priv, struct sk_buff *pkt)
+{
+ struct rwnx_txhdr *txhdr = (struct rwnx_txhdr *)pkt->data;
+ u8 *start_ptr = tx_priv->tail;
+ u8 sdio_header[4];
+ u8 adjust_str[4] = {0, 0, 0, 0};
+ u32 curr_len = 0;
+ int allign_len = 0;
+ int headroom;
+
+ sdio_header[0] =
+ ((pkt->len - txhdr->sw_hdr->headroom + sizeof(struct txdesc_api)) &
+ 0xff);
+ sdio_header[1] =
+ (((pkt->len - txhdr->sw_hdr->headroom + sizeof(struct txdesc_api)) >>
+ 8) &
+ 0x0f);
+ sdio_header[2] = 0x01; // data
+ if (!aic_sdio_hw->use_hdr_checksum)
+ sdio_header[3] = 0; // reserved
+ else if (aic_sdio_hw->use_hdr_checksum)
+ sdio_header[3] = crc8_ponl_107(&sdio_header[0], 3); // crc8
+
+ memcpy(tx_priv->tail, (u8 *)&sdio_header, sizeof(sdio_header));
+ tx_priv->tail += sizeof(sdio_header);
+ // payload
+ memcpy(tx_priv->tail, (u8 *)(long)&txhdr->sw_hdr->desc,
+ sizeof(struct txdesc_api));
+ tx_priv->tail += sizeof(struct txdesc_api); // hostdesc
+ memcpy(tx_priv->tail, (u8 *)((u8 *)txhdr + txhdr->sw_hdr->headroom),
+ pkt->len - txhdr->sw_hdr->headroom);
+ tx_priv->tail += (pkt->len - txhdr->sw_hdr->headroom);
+
+ // word alignment
+ curr_len = tx_priv->tail - tx_priv->head;
+ if (curr_len & (TX_ALIGNMENT - 1)) {
+ allign_len = roundup(curr_len, TX_ALIGNMENT) - curr_len;
+ memcpy(tx_priv->tail, adjust_str, allign_len);
+ tx_priv->tail += allign_len;
+ }
+
+ if (aic_sdio_hw->need_fix_hdr_len) {
+ start_ptr[0] = ((tx_priv->tail - start_ptr - 4) & 0xff);
+ start_ptr[1] = (((tx_priv->tail - start_ptr - 4) >> 8) & 0x0f);
+ }
+ tx_priv->aggr_buf->dev = pkt->dev;
+
+ if (!txhdr->sw_hdr->need_cfm) {
+ headroom = txhdr->sw_hdr->headroom;
+ kmem_cache_free(txhdr->sw_hdr->rwnx_vif->rwnx_hw->sw_txhdr_cache,
+ txhdr->sw_hdr);
+ skb_pull(pkt, headroom);
+ consume_skb(pkt);
+ }
+
+ atomic_inc(&tx_priv->aggr_count);
+ return 0;
+}
+
+void aicwf_sdio_aggr_send(struct aicwf_tx_priv *tx_priv)
+{
+ struct sk_buff *tx_buf = tx_priv->aggr_buf;
+ int ret = 0;
+ int curr_len = 0;
+
+ // link tail is necessary
+ curr_len = tx_priv->tail - tx_priv->head;
+ if ((curr_len % TXPKT_BLOCKSIZE) != 0) {
+ memset(tx_priv->tail, 0, TAIL_LEN);
+ tx_priv->tail += TAIL_LEN;
+ }
+
+ tx_buf->len = tx_priv->tail - tx_priv->head;
+ ret = aicwf_sdio_txpkt(tx_priv->sdiodev, tx_buf);
+ if (ret < 0)
+ sdio_err("fail to send aggr pkt!\n");
+
+ aicwf_sdio_aggrbuf_reset(tx_priv);
+}
+
+void aicwf_sdio_aggrbuf_reset(struct aicwf_tx_priv *tx_priv)
+{
+ struct sk_buff *aggr_buf = tx_priv->aggr_buf;
+
+ tx_priv->tail = tx_priv->head;
+ aggr_buf->len = 0;
+ atomic_set(&tx_priv->aggr_count, 0);
+}
+
+extern void set_irq_handler(void *fn);
+
+static int aicwf_sdio_bus_start(struct device *dev)
+{
+ struct aicwf_bus *bus_if = dev_get_drvdata(dev);
+ struct aic_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ int ret = 0;
+
+ if (!sdiodev->func) {
+ pr_warn("%s, NULL sdio func\n", __func__);
+ return 0;
+ }
+
+ sdio_claim_host(sdiodev->func);
+#ifndef CONFIG_FDRV_NO_REG_SDIO
+ sdio_claim_irq(sdiodev->func, aicwf_sdio_hal_irqhandler);
+#else
+ set_irq_handler(aicwf_sdio_hal_irqhandler);
+#endif
+ if (aic_sdio_hw->need_func0_intr) {
+ sdio_f0_writeb(sdiodev->func, 0x07, 0x04, &ret);
+ if (ret)
+ sdio_err("set func0 int en fail %d\n", ret);
+ }
+ sdio_release_host(sdiodev->func);
+
+ // enable sdio interrupt
+ ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.intr_config_reg, 0x07);
+ if (ret != 0)
+ sdio_err("intr register failed:%d\n", ret);
+
+ bus_if->state = BUS_UP_ST;
+
+ return ret;
+}
+
+#ifdef CONFIG_AIC8800_TXRX_THREAD_PRIO
+
+#include "uapi/linux/sched/types.h"
+
+static int bustx_thread_prio = 1;
+module_param_named(bustx_thread_prio, bustx_thread_prio, int, 0644);
+// module_param(bustx_thread_prio, int, 0);
+static int busrx_thread_prio = 1;
+module_param_named(busrx_thread_prio, busrx_thread_prio, int, 0644);
+// module_param(busrx_thread_prio, int, 0);
+#endif
+
+#ifdef CONFIG_OOB
+static int rx_thread_wait_to = 1000;
+module_param_named(rx_thread_wait_to, rx_thread_wait_to, int, 0644);
+
+// new oob feature
+int sdio_busirq_thread(void *data)
+{
+ struct aicwf_rx_priv *rx_priv = (struct aicwf_rx_priv *)data;
+ struct aicwf_bus *bus_if = rx_priv->sdiodev->bus_if;
+
+#ifdef CONFIG_AIC8800_TXRX_THREAD_PRIO
+ if (busrx_thread_prio > 0)
+ sched_set_fifo_low(current);
+#endif
+
+ AICWFDBG(LOGINFO, "%s the policy of current thread is:%d\n", __func__,
+ current->policy);
+ AICWFDBG(LOGINFO, "%s the rt_priority of current thread is:%d\n", __func__,
+ current->rt_priority);
+ AICWFDBG(LOGINFO, "%s the current pid is:%d\n", __func__, current->pid);
+
+ while (1) {
+ if (kthread_should_stop()) {
+ AICWFDBG(LOGERROR, "sdio busirq thread stop\n");
+ break;
+ }
+
+ if (!wait_for_completion_timeout(&bus_if->busirq_trgg,
+ msecs_to_jiffies(rx_thread_wait_to))) {
+ AICWFDBG(LOGRXPOLL, "%s wait for completion timeout \r\n", __func__);
+ }
+
+ if (bus_if->state == BUS_DOWN_ST)
+ continue;
+
+#ifdef CONFIG_SDIO_PWRCTRL
+ while (atomic_read(&bus_if->bus_priv.sdio->is_bus_suspend) == 1) {
+ AICWFDBG(LOGDEBUG, "%s waiting for sdio bus resume \r\n", __func__);
+ msleep(100);
+ }
+ aicwf_sdio_pwr_stctl(bus_if->bus_priv.sdio, SDIO_ACTIVE_ST);
+#endif // CONFIG_SDIO_PWRCTRL
+ aicwf_sdio_hal_irqhandler(bus_if->bus_priv.sdio->func);
+ }
+
+ return 0;
+}
+
+#endif // CONFIG_OOB
+
+int sdio_bustx_thread(void *data)
+{
+ struct aicwf_bus *bus = (struct aicwf_bus *)data;
+ struct aic_sdio_dev *sdiodev = bus->bus_priv.sdio;
+
+#ifdef CONFIG_AIC8800_TXRX_THREAD_PRIO
+ if (bustx_thread_prio > 0)
+ sched_set_fifo_low(current);
+#endif
+
+ AICWFDBG(LOGINFO, "%s the policy of current thread is:%d\n", __func__,
+ current->policy);
+ AICWFDBG(LOGINFO, "%s the rt_priority of current thread is:%d\n", __func__,
+ current->rt_priority);
+ AICWFDBG(LOGINFO, "%s the current pid is:%d\n", __func__, current->pid);
+
+ while (1) {
+ if (kthread_should_stop()) {
+ AICWFDBG(LOGERROR, "sdio bustx thread stop\n");
+ break;
+ }
+
+ if (!wait_for_completion_interruptible(&bus->bustx_trgg)) {
+ if (sdiodev->bus_if->state == BUS_DOWN_ST)
+ continue;
+
+ if (!aicwf_sdio_activity_get(sdiodev->rwnx_hw,
+ sdiodev->rwnx_hw->ws_tx))
+ continue;
+ if ((int)(atomic_read(&sdiodev->tx_priv->tx_pktcnt) > 0) ||
+ sdiodev->tx_priv->cmd_txstate)
+ aicwf_sdio_tx_process(sdiodev);
+ aicwf_sdio_activity_put(sdiodev->rwnx_hw,
+ sdiodev->rwnx_hw->ws_tx);
+ }
+ }
+
+ return 0;
+}
+
+int sdio_busrx_thread(void *data)
+{
+ struct aicwf_rx_priv *rx_priv = (struct aicwf_rx_priv *)data;
+ struct aicwf_bus *bus_if = rx_priv->sdiodev->bus_if;
+
+#ifdef CONFIG_AIC8800_TXRX_THREAD_PRIO
+ if (busrx_thread_prio > 0)
+ sched_set_fifo_low(current);
+#endif
+
+ AICWFDBG(LOGINFO, "%s the policy of current thread is:%d\n", __func__,
+ current->policy);
+ AICWFDBG(LOGINFO, "%s the rt_priority of current thread is:%d\n", __func__,
+ current->rt_priority);
+ AICWFDBG(LOGINFO, "%s the current pid is:%d\n", __func__, current->pid);
+
+ while (1) {
+ if (kthread_should_stop()) {
+ AICWFDBG(LOGERROR, "sdio busrx thread stop\n");
+ break;
+ }
+ if (!wait_for_completion_interruptible(&bus_if->busrx_trgg)) {
+ if (bus_if->state == BUS_DOWN_ST)
+ continue;
+ if (!aicwf_sdio_activity_get(rx_priv->sdiodev->rwnx_hw,
+ rx_priv->sdiodev->rwnx_hw->ws_rx))
+ continue;
+ aicwf_process_rxframes(rx_priv);
+ aicwf_sdio_activity_put(rx_priv->sdiodev->rwnx_hw,
+ rx_priv->sdiodev->rwnx_hw->ws_rx);
+ }
+ }
+ return 0;
+}
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+static int aicwf_sdio_pwrctl_thread(void *data)
+{
+ struct aic_sdio_dev *sdiodev = (struct aic_sdio_dev *)data;
+
+ while (1) {
+ if (kthread_should_stop()) {
+ AICWFDBG(LOGINFO, "sdio pwrctl thread stop\n");
+ break;
+ }
+ if (!wait_for_completion_interruptible(&sdiodev->pwrctrl_trgg)) {
+ if (sdiodev->bus_if->state == BUS_DOWN_ST)
+ continue;
+
+ if (!aicwf_sdio_activity_get(sdiodev->rwnx_hw,
+ sdiodev->rwnx_hw->ws_pwrctrl))
+ continue;
+
+ if ((int)(atomic_read(&sdiodev->tx_priv->tx_pktcnt) <= 0) &&
+ !sdiodev->tx_priv->cmd_txstate &&
+ atomic_read(&sdiodev->rx_priv->rx_cnt) == 0)
+ aicwf_sdio_pwr_stctl(sdiodev, SDIO_SLEEP_ST);
+ else
+ aicwf_sdio_pwrctl_timer(sdiodev, sdiodev->active_duration);
+
+ aicwf_sdio_activity_put(sdiodev->rwnx_hw,
+ sdiodev->rwnx_hw->ws_pwrctrl);
+ }
+ }
+
+ return 0;
+}
+
+static void aicwf_sdio_bus_pwrctl(struct timer_list *t)
+{
+ //struct aic_sdio_dev *sdiodev = from_timer(sdiodev, t, timer);
+ struct aic_sdio_dev *sdiodev = timer_container_of(sdiodev, t, timer);
+
+ if (sdiodev->bus_if->state == BUS_DOWN_ST) {
+ sdio_err("bus down\n");
+ return;
+ }
+
+ if (sdiodev->pwrctl_tsk)
+ complete(&sdiodev->pwrctrl_trgg);
+}
+#endif
+
+#ifdef CONFIG_PREALLOC_RX_SKB
+static void aicwf_sdio_enq_rxpkt(struct aic_sdio_dev *sdiodev,
+ struct rx_buff *pkt)
+#else
+static void aicwf_sdio_enq_rxpkt(struct aic_sdio_dev *sdiodev,
+ struct sk_buff *pkt)
+#endif
+{
+ struct aicwf_rx_priv *rx_priv = sdiodev->rx_priv;
+ unsigned long flags = 0;
+
+ spin_lock_irqsave(&rx_priv->rxqlock, flags);
+#ifdef CONFIG_PREALLOC_RX_SKB
+ if (!aicwf_rxbuff_enqueue(sdiodev->dev, &rx_priv->rxq, pkt)) {
+ spin_unlock_irqrestore(&rx_priv->rxqlock, flags);
+ pr_err("%s %d, enqueue rxq fail\n", __func__, __LINE__);
+ aicwf_prealloc_rxbuff_free(pkt, &rx_priv->rxbuff_lock);
+ return;
+ }
+#else
+ if (!aicwf_rxframe_enqueue(sdiodev->dev, &rx_priv->rxq, pkt)) {
+ spin_unlock_irqrestore(&rx_priv->rxqlock, flags);
+ aicwf_dev_skb_free(pkt);
+ return;
+ }
+#endif
+ spin_unlock_irqrestore(&rx_priv->rxqlock, flags);
+
+ atomic_inc(&rx_priv->rx_cnt);
+}
+
+#define SDIO_OTHER_INTERRUPT (0x1ul << 7)
+
+void aicwf_sdio_hal_irqhandler(struct sdio_func *func)
+{
+ struct aicwf_bus *bus_if = NULL;
+ struct aic_sdio_dev *sdiodev = NULL;
+ u8 intstatus = 0;
+ u8 byte_len = 0;
+#ifdef CONFIG_PREALLOC_RX_SKB
+ struct rx_buff *pkt = NULL;
+#else
+ struct sk_buff *pkt = NULL;
+#endif
+ int ret;
+
+ if (!func) {
+ AICWFDBG(LOGERROR, "fdrv %s func is null\n", __func__);
+ return;
+ }
+
+ bus_if = dev_get_drvdata(&func->dev);
+
+ if (!bus_if->bus_priv.sdio) {
+ AICWFDBG(LOGERROR, "fdrv %s sdiodev is null\n", __func__);
+ return;
+ }
+
+ sdiodev = bus_if->bus_priv.sdio;
+
+ if (!aicwf_sdio_activity_get(sdiodev->rwnx_hw,
+ sdiodev->rwnx_hw->ws_irqrx))
+ return;
+
+ if (!bus_if || bus_if->state == BUS_DOWN_ST) {
+ sdio_err("bus err\n");
+ aicwf_sdio_activity_put(sdiodev->rwnx_hw,
+ sdiodev->rwnx_hw->ws_irqrx);
+ return;
+ }
+
+#ifdef CONFIG_PREALLOC_RX_SKB
+ if (list_empty(&aic_rx_buff_list.rxbuff_list)) {
+ pr_warn("%s %d, rxbuff list is empty\n", __func__, __LINE__);
+ aicwf_sdio_activity_put(sdiodev->rwnx_hw,
+ sdiodev->rwnx_hw->ws_irqrx);
+ return;
+ }
+#endif
+
+ if (aic_sdio_hw->use_func2) {
+ ret = aicwf_sdio_readb(sdiodev, sdiodev->sdio_reg.block_cnt_reg,
+ &intstatus);
+ while (ret || (intstatus & SDIO_OTHER_INTERRUPT)) {
+ sdio_err("ret=%d, intstatus=%x\r\n", ret, intstatus);
+ ret = aicwf_sdio_readb(sdiodev, sdiodev->sdio_reg.block_cnt_reg,
+ &intstatus);
+ }
+ sdiodev->rx_priv->data_len = intstatus * SDIOWIFI_FUNC_BLOCKSIZE;
+
+ if (intstatus > 0) {
+ if (intstatus < 64) {
+ pkt = aicwf_sdio_readframes(sdiodev);
+ } else {
+ // byte_len must<= 128
+ aicwf_sdio_intr_get_len_bytemode(sdiodev, &byte_len);
+ sdio_info("byte mode len=%d\r\n", byte_len);
+ pkt = aicwf_sdio_readframes(sdiodev);
+ }
+ } else {
+ // sdio_err("Interrupt but no data\n"); //debug trace
+ }
+
+ if (pkt)
+ aicwf_sdio_enq_rxpkt(sdiodev, pkt);
+
+ if (atomic_read(&sdiodev->rx_priv->rx_cnt) == 1)
+ complete(&bus_if->busrx_trgg);
+
+ } else {
+ do {
+ ret = aicwf_sdio_readb(sdiodev,
+ sdiodev->sdio_reg.misc_int_status_reg,
+ &intstatus);
+ if (!ret)
+ break;
+ sdio_err("ret=%d, intstatus=%x\r\n", ret, intstatus);
+ } while (1);
+ if (intstatus & SDIO_OTHER_INTERRUPT) {
+ u8 int_pending;
+
+ ret = aicwf_sdio_readb(sdiodev, sdiodev->sdio_reg.sleep_reg,
+ &int_pending);
+ if (ret < 0)
+ sdio_err("reg:%d read failed!\n", sdiodev->sdio_reg.sleep_reg);
+ int_pending &= ~0x01; // dev to host soft irq
+ ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.sleep_reg,
+ int_pending);
+ if (ret < 0)
+ sdio_err("reg:%d write failed!\n", sdiodev->sdio_reg.sleep_reg);
+ }
+
+ if (intstatus > 0) {
+ u8 intmaskf2 = intstatus | (0x1UL << 3);
+
+ if (intmaskf2 > 120U) { // func2
+ if (intmaskf2 == 127U) { // byte mode
+ // aicwf_sdio_intr_get_len_bytemode(sdiodev, &byte_len,
+ // 1);//byte_len must<= 128
+ // byte_len must<= 128
+ aicwf_sdio_intr_get_len_bytemode(sdiodev,
+ &byte_len);
+ sdio_info("byte mode len=%d\r\n", byte_len);
+ // pkt = aicwf_sdio_readframes(sdiodev, 1);
+ pkt = aicwf_sdio_readframes(sdiodev);
+ } else { // block mode
+ sdiodev->rx_priv->data_len =
+ (intstatus & 0x7U) * SDIOWIFI_FUNC_BLOCKSIZE;
+ // pkt = aicwf_sdio_readframes(sdiodev, 1);
+ pkt = aicwf_sdio_readframes(sdiodev);
+ }
+ } else { // func1
+ if (intstatus == 120U) { // byte mode
+ // aicwf_sdio_intr_get_len_bytemode(sdiodev, &byte_len,
+ // 0);//byte_len must<= 128
+ // byte_len must<= 128
+ aicwf_sdio_intr_get_len_bytemode(sdiodev, &byte_len);
+ sdio_info("byte mode len=%d\r\n", byte_len);
+ // pkt = aicwf_sdio_readframes(sdiodev, 0);
+ pkt = aicwf_sdio_readframes(sdiodev);
+ } else { // block mode
+ sdiodev->rx_priv->data_len =
+ (intstatus & 0x7FU) * SDIOWIFI_FUNC_BLOCKSIZE;
+ // pkt = aicwf_sdio_readframes(sdiodev, 0);
+ pkt = aicwf_sdio_readframes(sdiodev);
+ }
+ }
+ } else {
+ // sdio_err("Interrupt but no data\n");
+ }
+
+ if (pkt)
+ aicwf_sdio_enq_rxpkt(sdiodev, pkt);
+
+ if (atomic_read(&sdiodev->rx_priv->rx_cnt) == 1)
+ complete(&bus_if->busrx_trgg);
+ }
+
+ aicwf_sdio_activity_put(sdiodev->rwnx_hw,
+ sdiodev->rwnx_hw->ws_irqrx);
+}
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+void aicwf_sdio_pwrctl_timer(struct aic_sdio_dev *sdiodev, uint duration)
+{
+ uint timeout;
+
+ if (sdiodev->bus_if->state == BUS_DOWN_ST && duration)
+ return;
+
+ spin_lock_bh(&sdiodev->pwrctl_lock);
+ if (!duration) {
+ if (timer_pending(&sdiodev->timer))
+ //del_timer_sync(&sdiodev->timer);
+ timer_delete_sync(&sdiodev->timer);
+ } else {
+ sdiodev->active_duration = duration;
+ timeout = msecs_to_jiffies(sdiodev->active_duration);
+ mod_timer(&sdiodev->timer, jiffies + timeout);
+ }
+ spin_unlock_bh(&sdiodev->pwrctl_lock);
+}
+#endif
+
+/* Forward declarations for bus_ops callbacks */
+static int aicwf_sdio_bus_read_reg(struct device *dev, u32 regaddr, u8 *val);
+static int aicwf_sdio_bus_write_reg(struct device *dev, u32 regaddr, u8 val);
+static int aicwf_sdio_bus_send_pkt(struct device *dev, u8 *buf, uint count);
+static int aicwf_sdio_bus_recv_pkt(struct device *dev, u8 *buf, u32 size);
+static int aicwf_sdio_bus_enable_irq(struct device *dev);
+static void aicwf_sdio_bus_disable_irq(struct device *dev);
+static bool aicwf_sdio_bus_flow_ctrl(struct device *dev);
+static int aicwf_sdio_bus_sleep_allow(struct device *dev);
+static int aicwf_sdio_bus_wakeup(struct device *dev);
+static const void *aicwf_sdio_bus_get_hw_props(struct device *dev);
+
+static struct aicwf_bus_ops aicwf_sdio_bus_ops = {
+ .start = aicwf_sdio_bus_start,
+ .stop = aicwf_sdio_bus_stop,
+ .txdata = aicwf_sdio_bus_txdata,
+ .txmsg = aicwf_sdio_bus_txmsg,
+ .read_reg = aicwf_sdio_bus_read_reg,
+ .write_reg = aicwf_sdio_bus_write_reg,
+ .send_pkt = aicwf_sdio_bus_send_pkt,
+ .recv_pkt = aicwf_sdio_bus_recv_pkt,
+ .enable_irq = aicwf_sdio_bus_enable_irq,
+ .disable_irq = aicwf_sdio_bus_disable_irq,
+ .flow_ctrl = aicwf_sdio_bus_flow_ctrl,
+ .sleep_allow = aicwf_sdio_bus_sleep_allow,
+ .wakeup = aicwf_sdio_bus_wakeup,
+ .get_hw_props = aicwf_sdio_bus_get_hw_props,
+};
+
+void aicwf_sdio_release(struct aic_sdio_dev *sdiodev)
+{
+ struct aicwf_bus *bus_if;
+ int ret;
+
+ bus_if = dev_get_drvdata(sdiodev->dev);
+ bus_if->state = BUS_DOWN_ST;
+#ifdef CONFIG_OOB
+ if (sdiodev->oob_enable) {
+ if (!sdiodev->func) {
+ pr_warn("%s, NULL sdio func\n", __func__);
+ return;
+ }
+ sdio_claim_host(sdiodev->func);
+ // disable sdio interrupt
+ ret =
+ aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.intr_config_reg, 0x0);
+ if (ret < 0) {
+ AICWFDBG(LOGERROR, "reg:%d write failed!\n",
+ sdiodev->sdio_reg.intr_config_reg);
+ }
+ sdio_release_irq(sdiodev->func);
+ sdio_release_host(sdiodev->func);
+ }
+#else
+ sdio_claim_host(sdiodev->func);
+ // disable sdio interrupt
+ ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.intr_config_reg, 0x0);
+ if (ret < 0)
+ sdio_err("reg:%d write failed!\n", sdiodev->sdio_reg.intr_config_reg);
+ sdio_release_irq(sdiodev->func);
+ sdio_release_host(sdiodev->func);
+#endif
+ if (sdiodev->dev)
+ aicwf_bus_deinit(sdiodev->dev);
+
+ if (sdiodev->tx_priv)
+ aicwf_tx_deinit(sdiodev->tx_priv);
+
+ if (sdiodev->rx_priv)
+ aicwf_rx_deinit(sdiodev->rx_priv);
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+ if (sdiodev->pwrctl_tsk) {
+ complete_all(&sdiodev->pwrctrl_trgg);
+ kthread_stop(sdiodev->pwrctl_tsk);
+ sdiodev->pwrctl_tsk = NULL;
+ }
+
+ AICWFDBG(LOGINFO, "%s:pwrctl stopped\n", __func__);
+#endif
+
+ if (sdiodev->cmd_mgr.state == RWNX_CMD_MGR_STATE_INITED)
+ rwnx_cmd_mgr_deinit(&sdiodev->cmd_mgr);
+}
+
+void aicwf_sdio_reg_init(struct aic_sdio_dev *sdiodev)
+{
+ if (aic_sdio_hw->use_func2) {
+ sdiodev->sdio_reg.bytemode_len_reg = SDIOWIFI_BYTEMODE_LEN_REG;
+ sdiodev->sdio_reg.intr_config_reg = SDIOWIFI_INTR_CONFIG_REG;
+ sdiodev->sdio_reg.sleep_reg = SDIOWIFI_SLEEP_REG;
+ sdiodev->sdio_reg.wakeup_reg = SDIOWIFI_WAKEUP_REG;
+ sdiodev->sdio_reg.flow_ctrl_reg = SDIOWIFI_FLOW_CTRL_REG;
+ sdiodev->sdio_reg.register_block = SDIOWIFI_REGISTER_BLOCK;
+ sdiodev->sdio_reg.bytemode_enable_reg = SDIOWIFI_BYTEMODE_ENABLE_REG;
+ sdiodev->sdio_reg.block_cnt_reg = SDIOWIFI_BLOCK_CNT_REG;
+ sdiodev->sdio_reg.rd_fifo_addr = SDIOWIFI_RD_FIFO_ADDR;
+ sdiodev->sdio_reg.wr_fifo_addr = SDIOWIFI_WR_FIFO_ADDR;
+ } else {
+ sdiodev->sdio_reg.bytemode_len_reg = SDIOWIFI_BYTEMODE_LEN_REG_V3;
+ sdiodev->sdio_reg.intr_config_reg = SDIOWIFI_INTR_ENABLE_REG_V3;
+ sdiodev->sdio_reg.sleep_reg = SDIOWIFI_INTR_PENDING_REG_V3;
+ sdiodev->sdio_reg.wakeup_reg = SDIOWIFI_INTR_TO_DEVICE_REG_V3;
+ sdiodev->sdio_reg.flow_ctrl_reg = SDIOWIFI_FLOW_CTRL_Q1_REG_V3;
+ sdiodev->sdio_reg.bytemode_enable_reg = SDIOWIFI_BYTEMODE_ENABLE_REG_V3;
+ sdiodev->sdio_reg.misc_int_status_reg = SDIOWIFI_MISC_INT_STATUS_REG_V3;
+ sdiodev->sdio_reg.rd_fifo_addr = SDIOWIFI_RD_FIFO_ADDR_V3;
+ sdiodev->sdio_reg.wr_fifo_addr = SDIOWIFI_WR_FIFO_ADDR_V3;
+ }
+}
+
+int aicwf_sdio_func_init(struct aic_sdio_dev *sdiodev)
+{
+ struct mmc_host *host;
+ u8 block_bit0 = 0x1;
+ u8 byte_mode_disable = 0x1; // 1: no byte mode
+ int ret = 0;
+ struct aicbsp_feature_t feature;
+ //u8 val = 0;
+
+ aicbsp_get_feature(&feature);
+ aicwf_sdio_reg_init(sdiodev);
+ if (!sdiodev->func) {
+ pr_warn("%s, NULL sdio func\n", __func__);
+ return 0;
+ }
+
+ host = sdiodev->func->card->host;
+
+ sdio_claim_host(sdiodev->func);
+ ret = sdio_set_block_size(sdiodev->func, SDIOWIFI_FUNC_BLOCKSIZE);
+ if (ret < 0) {
+ AICWFDBG(LOGERROR, "set blocksize fail %d\n", ret);
+ sdio_release_host(sdiodev->func);
+ return ret;
+ }
+ ret = sdio_enable_func(sdiodev->func);
+ if (ret < 0) {
+ sdio_release_host(sdiodev->func);
+ AICWFDBG(LOGERROR, "enable func fail %d.\n", ret);
+ return ret;
+ }
+
+ if (feature.sdio_clock > 0) {
+ host->ios.clock = feature.sdio_clock;
+ host->ops->set_ios(host, &host->ios);
+ AICWFDBG(LOGINFO, "Set SDIO Clock %d MHz\n", host->ios.clock / 1000000);
+ }
+
+ sdio_release_host(sdiodev->func);
+
+ if (aic_sdio_hw->use_func2) {
+ sdio_claim_host(sdiodev->func2);
+ //set sdio blocksize
+ ret = sdio_set_block_size(sdiodev->func2, SDIOWIFI_FUNC_BLOCKSIZE);
+ if (ret < 0) {
+ AICWFDBG(LOGERROR, "set func2 blocksize fail %d\n", ret);
+ sdio_release_host(sdiodev->func2);
+ return ret;
+ }
+
+ //set sdio enable func
+ ret = sdio_enable_func(sdiodev->func2);
+ if (ret < 0) {
+ sdio_release_host(sdiodev->func2);
+ AICWFDBG(LOGERROR, "enable func2 fail %d.\n", ret);
+ return ret;
+ }
+
+ sdio_release_host(sdiodev->func2);
+
+ ret = aicwf_sdio_func2_writeb(sdiodev,
+ sdiodev->sdio_reg.register_block, block_bit0);
+ if (ret < 0) {
+ AICWFDBG(LOGERROR, "reg:%d write failed!\n",
+ sdiodev->sdio_reg.register_block);
+ return ret;
+ }
+
+ //1: no byte mode
+ ret = aicwf_sdio_func2_writeb(sdiodev, sdiodev->sdio_reg.bytemode_enable_reg,
+ byte_mode_disable);
+ if (ret < 0) {
+ AICWFDBG(LOGERROR, "reg:%d write failed!\n",
+ sdiodev->sdio_reg.bytemode_enable_reg);
+ return ret;
+ }
+ }
+
+ ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.register_block, block_bit0);
+ if (ret < 0) {
+ AICWFDBG(LOGERROR, "reg:%d write failed!\n", sdiodev->sdio_reg.register_block);
+ return ret;
+ }
+
+ //1: no byte mode
+ ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.bytemode_enable_reg, byte_mode_disable);
+ if (ret < 0) {
+ AICWFDBG(LOGERROR, "reg:%d write failed!\n", sdiodev->sdio_reg.bytemode_enable_reg);
+ return ret;
+ }
+
+ mdelay(10);
+ return ret;
+}
+
+int aicwf_sdiov3_func_init(struct aic_sdio_dev *sdiodev)
+{
+ struct mmc_host *host;
+ u8 byte_mode_disable = 0x1; // 1: no byte mode
+ int ret = 0;
+ struct aicbsp_feature_t feature;
+ u8 val1 = 0;
+ int val;
+
+ ret = aicbsp_get_feature(&feature);
+ if (ret < 0)
+ return ret;
+ aicwf_sdio_reg_init(sdiodev);
+
+ if (!sdiodev->func) {
+ pr_warn("%s, NULL sdio func\n", __func__);
+ return 0;
+ }
+
+ host = sdiodev->func->card->host;
+
+ sdio_claim_host(sdiodev->func);
+ sdiodev->func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
+
+ ret = sdio_set_block_size(sdiodev->func, SDIOWIFI_FUNC_BLOCKSIZE);
+ if (ret < 0) {
+ AICWFDBG(LOGERROR, "set blocksize fail %d\n", ret);
+ sdio_release_host(sdiodev->func);
+ return ret;
+ }
+ ret = sdio_enable_func(sdiodev->func);
+ if (ret < 0) {
+ sdio_release_host(sdiodev->func);
+ AICWFDBG(LOGERROR, "enable func fail %d.\n", ret);
+ return ret;
+ }
+
+ sdio_f0_writeb(sdiodev->func, 0x7F, 0xF2, &ret);
+ if (ret) {
+ sdio_err("set fn0 0xF2 fail %d\n", ret);
+ sdio_release_host(sdiodev->func);
+ return ret;
+ }
+ if (host->ios.timing == MMC_TIMING_UHS_DDR50)
+ val = 0x20; // 0x21; //0x1D; //0x5;
+ else
+ val = 0x00; // 0x01; //0x19; //0x1;
+ val |= SDIOCLK_FREE_RUNNING_BIT;
+ sdio_f0_writeb(sdiodev->func, val, 0xF0, &ret);
+ if (ret) {
+ sdio_err("set iopad ctrl fail %d\n", ret);
+ sdio_release_host(sdiodev->func);
+ return ret;
+ }
+ sdio_f0_writeb(sdiodev->func, 0x0, 0xF8, &ret);
+ if (ret) {
+ sdio_err("set iopad delay2 fail %d\n", ret);
+ sdio_release_host(sdiodev->func);
+ return ret;
+ }
+ sdio_f0_writeb(sdiodev->func, 0x00, 0xF1, &ret);
+ if (ret) {
+ sdio_err("set iopad delay1 fail %d\n", ret);
+ sdio_release_host(sdiodev->func);
+ return ret;
+ }
+ usleep_range(1000, 1500);
+ sdio_release_host(sdiodev->func);
+
+ // 1: no byte mode
+ ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.bytemode_enable_reg,
+ byte_mode_disable);
+ if (ret < 0) {
+ AICWFDBG(LOGERROR, "reg:%d write failed!\n",
+ sdiodev->sdio_reg.bytemode_enable_reg);
+ return ret;
+ }
+
+ ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.wakeup_reg, 0x11);
+ if (ret < 0) {
+ AICWFDBG(LOGERROR, "reg:%d write failed!\n",
+ sdiodev->sdio_reg.wakeup_reg);
+ return ret;
+ }
+
+ mdelay(5);
+ ret = aicwf_sdio_readb(sdiodev, sdiodev->sdio_reg.sleep_reg, &val1);
+ if (ret < 0) {
+ AICWFDBG(LOGERROR, "reg:%d read failed!\n",
+ sdiodev->sdio_reg.sleep_reg);
+ return ret;
+ }
+
+ if (!(val1 & 0x10))
+ AICWFDBG(LOGERROR, "wakeup fail\n");
+ else
+ AICWFDBG(LOGINFO, "sdio ready\n");
+
+ return ret;
+}
+
+void aicwf_sdio_func_deinit(struct aic_sdio_dev *sdiodev)
+{
+ if (!sdiodev->func) {
+ pr_warn("%s, NULL sdio func\n", __func__);
+ return;
+ }
+
+ sdio_claim_host(sdiodev->func);
+ sdio_disable_func(sdiodev->func);
+ sdio_release_host(sdiodev->func);
+
+ if (aic_sdio_hw->use_func2) {
+ sdio_claim_host(sdiodev->func2);
+ sdio_disable_func(sdiodev->func2);
+ sdio_release_host(sdiodev->func2);
+ }
+}
+
+void *aicwf_sdio_bus_init(struct aic_sdio_dev *sdiodev)
+{
+ int ret;
+ struct aicwf_bus *bus_if;
+ struct aicwf_rx_priv *rx_priv;
+ struct aicwf_tx_priv *tx_priv;
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+ spin_lock_init(&sdiodev->pwrctl_lock);
+ sema_init(&sdiodev->pwrctl_wakeup_sema, 1);
+#endif
+
+ bus_if = sdiodev->bus_if;
+ bus_if->dev = sdiodev->dev;
+ bus_if->ops = &aicwf_sdio_bus_ops;
+ bus_if->state = BUS_DOWN_ST;
+#if defined(CONFIG_SDIO_PWRCTRL)
+ sdiodev->state = SDIO_SLEEP_ST;
+ sdiodev->active_duration = SDIOWIFI_PWR_CTRL_INTERVAL;
+#else
+ sdiodev->state = SDIO_ACTIVE_ST;
+#endif
+
+ rx_priv = aicwf_rx_init(sdiodev);
+ if (!rx_priv) {
+ sdio_err("rx init fail\n");
+ goto fail;
+ }
+ sdiodev->rx_priv = rx_priv;
+
+ tx_priv = aicwf_tx_init(sdiodev);
+ if (!tx_priv) {
+ sdio_err("tx init fail\n");
+ goto fail;
+ }
+ sdiodev->tx_priv = tx_priv;
+ aicwf_frame_queue_init(&tx_priv->txq, 8, TXQLEN);
+ spin_lock_init(&tx_priv->txqlock);
+ sema_init(&tx_priv->txctl_sema, 1);
+ sema_init(&tx_priv->cmd_txsema, 1);
+ init_waitqueue_head(&tx_priv->cmd_txdone_wait);
+ atomic_set(&tx_priv->tx_pktcnt, 0);
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+ timer_setup(&sdiodev->timer, aicwf_sdio_bus_pwrctl, 0);
+ init_completion(&sdiodev->pwrctrl_trgg);
+#ifdef AICWF_SDIO_SUPPORT
+ sdiodev->pwrctl_tsk =
+ kthread_run(aicwf_sdio_pwrctl_thread, sdiodev, "aicwf_pwrctl");
+#endif
+ if (IS_ERR(sdiodev->pwrctl_tsk))
+ sdiodev->pwrctl_tsk = NULL;
+#endif
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+ sdiodev->flowctrl = 0;
+ spin_lock_init(&sdiodev->tx_flow_lock);
+#endif
+
+ spin_lock_init(&sdiodev->tx_tp_lock);
+ atomic_set(&sdiodev->suspending, 0);
+ atomic_set(&sdiodev->activity_count, 0);
+ init_waitqueue_head(&sdiodev->activity_waitq);
+ ret = aicwf_bus_init(0, sdiodev->dev);
+ if (ret < 0) {
+ sdio_err("bus init fail\n");
+ goto fail;
+ }
+
+ ret = aicwf_bus_start(bus_if);
+ if (ret != 0) {
+ sdio_err("bus start fail\n");
+ goto fail;
+ }
+
+ return sdiodev;
+
+fail:
+ aicwf_sdio_release(sdiodev);
+ return NULL;
+}
+
+u8 crc8_ponl_107(u8 *p_buffer, uint16_t cal_size)
+{
+ u8 i;
+ u8 crc = 0;
+
+ if (cal_size == 0)
+ return crc;
+ while (cal_size--) {
+ for (i = 0x80; i > 0; i /= 2) {
+ if (crc & 0x80) {
+ crc *= 2;
+ crc ^= 0x07; // polynomial X8 + X2 + X + 1,(0x107)
+ } else {
+ crc *= 2;
+ }
+ if ((*p_buffer) & i)
+ crc ^= 0x07;
+ }
+ p_buffer++;
+ }
+
+ return crc;
+}
+
+/* ================================================================
+ * Bus abstraction layer - SDIO bus_ops callbacks
+ *
+ * These thin wrappers translate the generic bus_ops interface
+ * (which receives a struct device *) back to the SDIO-specific
+ * aic_sdio_dev. Upper layers never see the SDIO internals.
+ * ================================================================
+ */
+
+static inline struct aic_sdio_dev *aicwf_bus_to_sdiodev(struct device *dev)
+{
+ struct aicwf_bus *bus_if = dev_get_drvdata(dev);
+
+ return bus_if->bus_priv.sdio;
+}
+
+static int aicwf_sdio_bus_read_reg(struct device *dev, u32 regaddr, u8 *val)
+{
+ return aicwf_sdio_readb(aicwf_bus_to_sdiodev(dev), regaddr, val);
+}
+
+static int aicwf_sdio_bus_write_reg(struct device *dev, u32 regaddr, u8 val)
+{
+ return aicwf_sdio_writeb(aicwf_bus_to_sdiodev(dev), regaddr, val);
+}
+
+static int aicwf_sdio_bus_send_pkt(struct device *dev, u8 *buf, uint count)
+{
+ return aicwf_sdio_send_pkt(aicwf_bus_to_sdiodev(dev), buf, count);
+}
+
+static int aicwf_sdio_bus_recv_pkt(struct device *dev, u8 *buf, u32 size)
+{
+ struct aic_sdio_dev *sdiodev = aicwf_bus_to_sdiodev(dev);
+ int ret;
+
+ sdio_claim_host(sdiodev->func);
+ ret = sdio_readsb(sdiodev->func, buf,
+ sdiodev->sdio_reg.rd_fifo_addr, size);
+ sdio_release_host(sdiodev->func);
+
+ return ret;
+}
+
+static int aicwf_sdio_bus_enable_irq(struct device *dev)
+{
+ struct aic_sdio_dev *sdiodev = aicwf_bus_to_sdiodev(dev);
+ int ret;
+
+ sdio_claim_host(sdiodev->func);
+#ifndef CONFIG_FDRV_NO_REG_SDIO
+ sdio_claim_irq(sdiodev->func, aicwf_sdio_hal_irqhandler);
+#else
+ set_irq_handler(aicwf_sdio_hal_irqhandler);
+#endif
+ if (aic_sdio_hw->need_func0_intr) {
+ sdio_f0_writeb(sdiodev->func, 0x07, 0x04, &ret);
+ if (ret)
+ sdio_err("set func0 int en fail %d\n", ret);
+ }
+ sdio_release_host(sdiodev->func);
+
+ ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.intr_config_reg, 0x07);
+ return ret;
+}
+
+static void aicwf_sdio_bus_disable_irq(struct device *dev)
+{
+ struct aic_sdio_dev *sdiodev = aicwf_bus_to_sdiodev(dev);
+
+ sdio_claim_host(sdiodev->func);
+ aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.intr_config_reg, 0x0);
+ sdio_release_irq(sdiodev->func);
+ sdio_release_host(sdiodev->func);
+}
+
+static bool aicwf_sdio_bus_flow_ctrl(struct device *dev)
+{
+ int ret;
+
+ ret = aicwf_sdio_flow_ctrl(aicwf_bus_to_sdiodev(dev));
+ return (ret > 0);
+}
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+static int aicwf_sdio_bus_sleep_allow(struct device *dev)
+{
+ return aicwf_sdio_sleep_allow(aicwf_bus_to_sdiodev(dev));
+}
+
+static int aicwf_sdio_bus_wakeup(struct device *dev)
+{
+ return aicwf_sdio_wakeup(aicwf_bus_to_sdiodev(dev));
+}
+#else
+static int aicwf_sdio_bus_sleep_allow(struct device *dev)
+{
+ return 0;
+}
+
+static int aicwf_sdio_bus_wakeup(struct device *dev)
+{
+ return 0;
+}
+#endif /* CONFIG_SDIO_PWRCTRL */
+
+static const void *aicwf_sdio_bus_get_hw_props(struct device *dev)
+{
+ return aic_sdio_hw;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_sdio.h b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_sdio.h
new file mode 100644
index 0000000000000..249f3d40a7965
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_sdio.h
@@ -0,0 +1,275 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AICWF_SDMMC_H_
+#define _AICWF_SDMMC_H_
+
+#ifdef AICWF_SDIO_SUPPORT
+#include "aicwf_rx_prealloc.h"
+#include "rwnx_cmds.h"
+#include <linux/ieee80211.h>
+#include <linux/if_ether.h>
+#include <linux/semaphore.h>
+#include <linux/skbuff.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/wait.h>
+
+#define AICWF_SDIO_NAME "aicwf_sdio"
+#define SDIOWIFI_FUNC_BLOCKSIZE 512
+
+#define SDIOWIFI_BYTEMODE_LEN_REG 0x02
+#define SDIOWIFI_INTR_CONFIG_REG 0x04
+#define SDIOWIFI_SLEEP_REG 0x05
+#define SDIOWIFI_WAKEUP_REG 0x09
+#define SDIOWIFI_FLOW_CTRL_REG 0x0A
+#define SDIOWIFI_REGISTER_BLOCK 0x0B
+#define SDIOWIFI_BYTEMODE_ENABLE_REG 0x11
+#define SDIOWIFI_BLOCK_CNT_REG 0x12
+#define SDIOWIFI_FLOWCTRL_MASK_REG 0x7F
+#define SDIOWIFI_WR_FIFO_ADDR 0x07
+#define SDIOWIFI_RD_FIFO_ADDR 0x08
+
+#define SDIOWIFI_INTR_ENABLE_REG_V3 0x00
+#define SDIOWIFI_INTR_PENDING_REG_V3 0x01
+#define SDIOWIFI_INTR_TO_DEVICE_REG_V3 0x02
+#define SDIOWIFI_FLOW_CTRL_Q1_REG_V3 0x03
+#define SDIOWIFI_MISC_INT_STATUS_REG_V3 0x04
+#define SDIOWIFI_BYTEMODE_LEN_REG_V3 0x05
+#define SDIOWIFI_BYTEMODE_LEN_MSB_REG_V3 0x06
+#define SDIOWIFI_BYTEMODE_ENABLE_REG_V3 0x07
+#define SDIOWIFI_MISC_CTRL_REG_V3 0x08
+#define SDIOWIFI_FLOW_CTRL_Q2_REG_V3 0x09
+#define SDIOWIFI_CLK_TEST_RESULT_REG_V3 0x0A
+#define SDIOWIFI_RD_FIFO_ADDR_V3 0x0F
+#define SDIOWIFI_WR_FIFO_ADDR_V3 0x10
+
+#define SDIOCLK_FREE_RUNNING_BIT BIT(6)
+
+#define SDIOWIFI_PWR_CTRL_INTERVAL 30
+#define FLOW_CTRL_RETRY_COUNT 50
+#define BUFFER_SIZE 1536
+#define TAIL_LEN 4
+#define TXQLEN (2048 * 4)
+
+#define SDIO_SLEEP_ST 0
+#define SDIO_ACTIVE_ST 1
+
+#define DATA_FLOW_CTRL_THRESH 2
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+#define AICWF_SDIO_TX_LOW_WATER 100
+#define AICWF_SDIO_TX_HIGH_WATER 500
+#endif
+
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+#define TEMP_GET_INTERVAL (10 * 1000)
+#define TEMP_THD_1 92 // temperature 1 (celsius)
+#define TEMP_THD_2 103 // temperature 2 (celsius)
+#define BUFFERING_V1 8
+#define BUFFERING_V2 8
+#define TMR_INTERVAL_1 60 // timer_1 60ms
+#define TMR_INTERVAL_2 180 // timer_2 130ms
+#endif
+
+enum sdio_type {
+ SDIO_TYPE_DATA = 0X00,
+ SDIO_TYPE_CFG = 0X10,
+ SDIO_TYPE_CFG_CMD_RSP = 0X11,
+ SDIO_TYPE_CFG_DATA_CFM = 0X12,
+ SDIO_TYPE_CFG_PRINT = 0X13
+};
+
+/* SDIO Device ID */
+#define SDIO_VENDOR_ID_AIC8801 0x5449
+#define SDIO_VENDOR_ID_AIC8800DC 0xc8a1
+#define SDIO_VENDOR_ID_AIC8800D80 0xc8a1
+
+#define SDIO_DEVICE_ID_AIC8801 0x0145
+#define SDIO_DEVICE_ID_AIC8800DC 0xc08d
+#define SDIO_DEVICE_ID_AIC8800D80 0x0082
+
+enum AICWF_IC {
+ PRODUCT_ID_AIC8801 = 0,
+ PRODUCT_ID_AIC8800DC,
+ PRODUCT_ID_AIC8800DW,
+ PRODUCT_ID_AIC8800D80
+};
+
+struct rwnx_hw;
+struct wakeup_source;
+
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+struct temp_ctrl {
+ struct timer_list netif_timer;
+ struct timer_list tp_ctrl_timer;
+ struct work_struct tp_ctrl_work;
+ struct work_struct netif_work;
+ /* lock for temp */
+ spinlock_t tm_lock;
+ s8 cur_temp;
+ bool net_stop;
+ bool on_off; // for command, 0 - off, 1 - on
+ s8 get_level; // for command, 0 - 100%, 1 - 12%, 2 - 3%
+ s8 set_level; // for command, 0 - driver auto, 1 - 12%, 2 - 3%
+ int interval_t1;
+ int interval_t2;
+ u8 cur_stat; // 0--normal temp, 1/2--buffering temp
+ s8 tp_thd_1; // temperature threshold 1
+ s8 tp_thd_2; // temperature threshold 2
+ s8 tm_start; //timer start flag
+};
+#endif
+
+struct aic_sdio_reg {
+ u8 bytemode_len_reg;
+ u8 intr_config_reg;
+ u8 sleep_reg;
+ u8 wakeup_reg;
+ u8 flow_ctrl_reg;
+ u8 flowctrl_mask_reg;
+ u8 register_block;
+ u8 bytemode_enable_reg;
+ u8 block_cnt_reg;
+ u8 misc_int_status_reg;
+ u8 rd_fifo_addr;
+ u8 wr_fifo_addr;
+};
+
+struct aic_sdio_dev {
+ struct rwnx_hw *rwnx_hw;
+ struct sdio_func *func;
+ struct sdio_func *func2;
+ struct device *dev;
+ struct aicwf_bus *bus_if;
+ struct rwnx_cmd_mgr cmd_mgr;
+
+ struct aicwf_rx_priv *rx_priv;
+ struct aicwf_tx_priv *tx_priv;
+ u32 state;
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+ u8 flowctrl;
+ /* lock for tx flow */
+ spinlock_t tx_flow_lock;
+#endif
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+ // for sdio pwr ctrl
+ struct timer_list timer;
+ uint active_duration;
+ struct completion pwrctrl_trgg;
+ struct task_struct *pwrctl_tsk;
+ /* lock for pwrctl */
+ spinlock_t pwrctl_lock;
+ struct semaphore pwrctl_wakeup_sema;
+#endif
+ u16 chipid;
+ struct aic_sdio_reg sdio_reg;
+
+ /* lock for ws */
+ spinlock_t wslock;
+ bool oob_enable;
+ atomic_t is_bus_suspend;
+ atomic_t suspending;
+ atomic_t activity_count;
+ wait_queue_head_t activity_waitq;
+ /* lock for tx tp */
+ spinlock_t tx_tp_lock;
+
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+ struct temp_ctrl tp_ctrl;
+#endif
+
+#ifdef CONFIG_PLATFORM_EXTERNAL
+ int wf_wake_host;
+ bool wake_by_wifi;
+ int host_wake_wf;
+#endif
+};
+
+extern struct aicwf_rx_buff_list aic_rx_buff_list;
+int aicwf_sdio_readb(struct aic_sdio_dev *sdiodev, uint regaddr, u8 *val);
+int aicwf_sdio_writeb(struct aic_sdio_dev *sdiodev, uint regaddr, u8 val);
+void aicwf_sdio_hal_irqhandler(struct sdio_func *func);
+bool aicwf_sdio_activity_get(struct rwnx_hw *rwnx_hw,
+ struct wakeup_source *ws);
+void aicwf_sdio_activity_put(struct rwnx_hw *rwnx_hw,
+ struct wakeup_source *ws);
+bool aicwf_sdio_scan_activity_get(struct rwnx_hw *rwnx_hw);
+void aicwf_sdio_scan_activity_put(struct rwnx_hw *rwnx_hw);
+int aicwf_sdio_func2_readb(struct aic_sdio_dev *sdiodev, uint regaddr, u8 *val);
+int aicwf_sdio_func2_writeb(struct aic_sdio_dev *sdiodev, uint regaddr, u8 val);
+
+#ifdef CONFIG_PREALLOC_RX_SKB
+struct rx_buff *aicwf_sdio_readframes(struct aic_sdio_dev *sdiodev);
+#else
+struct sk_buff *aicwf_sdio_readframes(struct aic_sdio_dev *sdiodev);
+#endif
+
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+void aicwf_netif_worker(struct work_struct *work);
+void aicwf_temp_ctrl_worker(struct work_struct *work);
+void aicwf_temp_ctrl(struct aic_sdio_dev *sdiodev);
+void aicwf_netif_ctrl(struct aic_sdio_dev *sdiodev, int val);
+void aicwf_tp_ctrl_init(struct aic_sdio_dev *sdiodev);
+void aicwf_tp_ctrl_deinit(struct aic_sdio_dev *sdiodev);
+#endif
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+void aicwf_sdio_pwrctl_timer(struct aic_sdio_dev *sdiodev, uint duration);
+int aicwf_sdio_pwr_stctl(struct aic_sdio_dev *sdiodev, uint target);
+int aicwf_sdio_sleep_allow(struct aic_sdio_dev *sdiodev);
+int aicwf_sdio_wakeup(struct aic_sdio_dev *sdiodev);
+#endif
+
+void aicwf_sdio_probe_(struct sdio_func *func, const struct sdio_device_id *id);
+void aicwf_sdio_remove_(struct sdio_func *func);
+
+void aicwf_sdio_reg_init(struct aic_sdio_dev *sdiodev);
+int aicwf_sdio_func_init(struct aic_sdio_dev *sdiodev);
+int aicwf_sdiov3_func_init(struct aic_sdio_dev *sdiodev);
+void aicwf_sdio_func_deinit(struct aic_sdio_dev *sdiodev);
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+void aicwf_sdio_tx_netif_flowctrl(struct rwnx_hw *rwnx_hw, bool state);
+#endif
+int aicwf_sdio_flow_ctrl(struct aic_sdio_dev *sdiodev);
+int aicwf_sdio_flow_ctrl_msg(struct aic_sdio_dev *sdiodev);
+#ifdef CONFIG_PREALLOC_RX_SKB
+int aicwf_sdio_recv_pkt(struct aic_sdio_dev *sdiodev, struct rx_buff *rxbbuf,
+ u32 size);
+#else
+int aicwf_sdio_recv_pkt(struct aic_sdio_dev *sdiodev, struct sk_buff *skbbuf,
+ u32 size);
+#endif
+int aicwf_sdio_send_pkt(struct aic_sdio_dev *sdiodev, u8 *buf, uint count);
+void *aicwf_sdio_bus_init(struct aic_sdio_dev *sdiodev);
+void aicwf_sdio_release(struct aic_sdio_dev *sdiodev);
+void aicwf_sdio_exit(void);
+void aicwf_sdio_register(void);
+int aicwf_sdio_txpkt(struct aic_sdio_dev *sdiodev, struct sk_buff *pkt);
+int sdio_bustx_thread(void *data);
+int sdio_busrx_thread(void *data);
+#ifdef CONFIG_OOB
+// new oob feature
+int sdio_busirq_thread(void *data);
+#endif // CONFIG_OOB
+int aicwf_sdio_aggr(struct aicwf_tx_priv *tx_priv, struct sk_buff *pkt);
+int aicwf_sdio_send(struct aicwf_tx_priv *tx_priv, u8 txnow);
+void aicwf_sdio_aggr_send(struct aicwf_tx_priv *tx_priv);
+void aicwf_sdio_aggrbuf_reset(struct aicwf_tx_priv *tx_priv);
+void aicwf_hostif_ready(void);
+void aicwf_hostif_fail(void);
+
+#ifdef CONFIG_PLATFORM_EXTERNAL
+int regist_wakeup_irq(struct aic_sdio_dev *sdiodev);
+void unregist_wakeup_irq(struct aic_sdio_dev *sdiodev);
+void disable_wakeup_irq(struct aic_sdio_dev *sdiodev);
+void enable_wakeup_irq(struct aic_sdio_dev *sdiodev);
+irqreturn_t wakeup_irq_handler(int irq, void *priv);
+#endif
+
+u8 crc8_ponl_107(u8 *p_buffer, uint16_t cal_size);
+
+#endif /* AICWF_SDIO_SUPPORT */
+
+#endif /*_AICWF_SDMMC_H_*/
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_tcp_ack.c b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_tcp_ack.c
new file mode 100644
index 0000000000000..2830a5daae5c0
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_tcp_ack.c
@@ -0,0 +1,565 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief tcp ack filter
+ *
+ ******************************************************************************
+ */
+
+#include "aicwf_tcp_ack.h"
+#include "rwnx_defs.h"
+
+struct msg_buf *intf_tcp_alloc_msg(void)
+{
+ return kzalloc_obj(struct msg_buf, GFP_ATOMIC);
+}
+
+void intf_tcp_drop_msg(struct rwnx_hw *priv, struct msg_buf *msg)
+{
+ if (msg->skb)
+ dev_kfree_skb_any(msg->skb);
+
+ kfree(msg);
+}
+
+void tcp_ack_timeout(struct timer_list *t)
+{
+ struct tcp_ack_info *ack_info;
+ struct msg_buf *msg;
+ struct tcp_ack_manage *ack_m = NULL;
+
+ ack_info = container_of(t, struct tcp_ack_info, timer);
+
+ ack_m = container_of(ack_info, struct tcp_ack_manage,
+ ack_info[ack_info->ack_info_num]);
+
+ write_seqlock_bh(&ack_info->seqlock);
+ msg = ack_info->msgbuf;
+ if (ack_info->busy && msg && !ack_info->in_send_msg) {
+ ack_info->msgbuf = NULL;
+ ack_info->drop_cnt = 0;
+ ack_info->in_send_msg = msg;
+ write_sequnlock_bh(&ack_info->seqlock);
+ intf_tx(ack_m->priv, msg); // send skb
+ return;
+ }
+ write_sequnlock_bh(&ack_info->seqlock);
+}
+
+void tcp_ack_init(struct rwnx_hw *priv)
+{
+ int i;
+ struct tcp_ack_info *ack_info;
+ struct tcp_ack_manage *ack_m = &priv->ack_m;
+
+ pr_info("AICWF %s\n", __func__);
+ memset(ack_m, 0, sizeof(struct tcp_ack_manage));
+ ack_m->priv = priv;
+ spin_lock_init(&ack_m->lock);
+ atomic_set(&ack_m->max_drop_cnt, TCP_ACK_DROP_CNT);
+ ack_m->last_time = jiffies;
+ ack_m->timeout = msecs_to_jiffies(ACK_OLD_TIME);
+
+ for (i = 0; i < TCP_ACK_NUM; i++) {
+ ack_info = &ack_m->ack_info[i];
+ ack_info->ack_info_num = i;
+ seqlock_init(&ack_info->seqlock);
+ ack_info->last_time = jiffies;
+ ack_info->timeout = msecs_to_jiffies(ACK_OLD_TIME);
+
+ timer_setup(&ack_info->timer, tcp_ack_timeout, 0);
+ }
+
+ atomic_set(&ack_m->enable, 1);
+ ack_m->ack_winsize = MIN_WIN;
+}
+
+void tcp_ack_deinit(struct rwnx_hw *priv)
+{
+ int i;
+ struct tcp_ack_manage *ack_m = &priv->ack_m;
+ struct msg_buf *drop_msg = NULL;
+
+ pr_info("AICWF %s\n", __func__);
+ atomic_set(&ack_m->enable, 0);
+
+ for (i = 0; i < TCP_ACK_NUM; i++) {
+ drop_msg = NULL;
+
+ write_seqlock_bh(&ack_m->ack_info[i].seqlock);
+ //del_timer(&ack_m->ack_info[i].timer);
+ timer_delete(&ack_m->ack_info[i].timer);
+ drop_msg = ack_m->ack_info[i].msgbuf;
+ ack_m->ack_info[i].msgbuf = NULL;
+ write_sequnlock_bh(&ack_m->ack_info[i].seqlock);
+
+ if (drop_msg)
+ intf_tcp_drop_msg(priv, drop_msg); // drop skb
+ }
+}
+
+int tcp_check_quick_ack(unsigned char *buf, struct tcp_ack_msg *msg)
+{
+ int ip_hdr_len;
+ unsigned char *temp;
+ struct ethhdr *ethhdr;
+ struct iphdr *iphdr;
+ struct tcphdr *tcphdr;
+
+ ethhdr = (struct ethhdr *)buf;
+ if (ethhdr->h_proto != htons(ETH_P_IP))
+ return 0;
+ iphdr = (struct iphdr *)(ethhdr + 1);
+ if (iphdr->version != 4 || iphdr->protocol != IPPROTO_TCP)
+ return 0;
+ ip_hdr_len = iphdr->ihl * 4;
+ temp = (unsigned char *)(iphdr) + ip_hdr_len;
+ tcphdr = (struct tcphdr *)temp;
+ /* TCP_FLAG_ACK */
+ if (!(temp[13] & 0x10))
+ return 0;
+
+ if (temp[13] & 0x8) {
+ msg->saddr = iphdr->daddr;
+ msg->daddr = iphdr->saddr;
+ msg->source = tcphdr->dest;
+ msg->dest = tcphdr->source;
+ msg->seq = ntohl(tcphdr->seq);
+ return 1;
+ }
+
+ return 0;
+}
+
+int is_drop_tcp_ack(struct tcphdr *tcphdr, int tcp_tot_len,
+ unsigned short *win_scale)
+{
+ int drop = 1;
+ int len = tcphdr->doff * 4;
+ unsigned char *ptr;
+
+ if (tcp_tot_len > len) {
+ drop = 0;
+ } else {
+ len -= sizeof(struct tcphdr);
+ ptr = (unsigned char *)(tcphdr + 1);
+
+ while ((len > 0) && drop) {
+ int opcode = *ptr++;
+ int opsize;
+
+ switch (opcode) {
+ case TCPOPT_EOL:
+ break;
+ case TCPOPT_NOP:
+ len--;
+ continue;
+ default:
+ opsize = *ptr++;
+ if (opsize < 2)
+ break;
+ if (opsize > len)
+ break;
+
+ switch (opcode) {
+ /* TODO: Add other ignore opt */
+ case TCPOPT_TIMESTAMP:
+ break;
+ case TCPOPT_WINDOW:
+ if (*ptr < 15)
+ *win_scale = (1 << (*ptr));
+ //printk("AICWF %d\n", *win_scale);
+ break;
+ default:
+ drop = 2;
+ }
+
+ ptr += opsize - 2;
+ len -= opsize;
+ }
+ }
+ }
+
+ return drop;
+}
+
+/* flag:0 for not tcp ack
+ * 1 for ack which can be drop
+ * 2 for other ack whith more info
+ */
+
+int tcp_check_ack(unsigned char *buf, struct tcp_ack_msg *msg,
+ unsigned short *win_scale)
+{
+ int ret;
+ int ip_hdr_len;
+ int tcp_tot_len;
+ unsigned char *temp;
+ struct ethhdr *ethhdr;
+ struct iphdr *iphdr;
+ struct tcphdr *tcphdr;
+
+ ethhdr = (struct ethhdr *)buf;
+ if (ethhdr->h_proto != htons(ETH_P_IP))
+ return 0;
+
+ iphdr = (struct iphdr *)(ethhdr + 1);
+ if (iphdr->version != 4 || iphdr->protocol != IPPROTO_TCP)
+ return 0;
+
+ ip_hdr_len = iphdr->ihl * 4;
+ temp = (unsigned char *)(iphdr) + ip_hdr_len;
+ tcphdr = (struct tcphdr *)temp;
+ /* TCP_FLAG_ACK */
+ if (!(temp[13] & 0x10))
+ return 0;
+
+ tcp_tot_len = ntohs(iphdr->tot_len) - ip_hdr_len;
+ ret = is_drop_tcp_ack(tcphdr, tcp_tot_len, win_scale);
+
+ if (ret > 0) {
+ msg->saddr = iphdr->saddr;
+ msg->daddr = iphdr->daddr;
+ msg->source = tcphdr->source;
+ msg->dest = tcphdr->dest;
+ msg->seq = ntohl(tcphdr->ack_seq);
+ msg->win = ntohs(tcphdr->window);
+ }
+
+ return ret;
+}
+
+/* return val: -1 for not match, others for match */
+int tcp_ack_match(struct tcp_ack_manage *ack_m, struct tcp_ack_msg *ack_msg)
+{
+ int i, ret = -1;
+ unsigned int start;
+ struct tcp_ack_info *ack_info;
+ struct tcp_ack_msg *ack;
+
+ for (i = 0; ((ret < 0) && (i < TCP_ACK_NUM)); i++) {
+ ack_info = &ack_m->ack_info[i];
+ do {
+ start = read_seqbegin(&ack_info->seqlock);
+ ret = -1;
+
+ ack = &ack_info->ack_msg;
+ if (ack_info->busy && ack->dest == ack_msg->dest &&
+ ack->source == ack_msg->source &&
+ ack->saddr == ack_msg->saddr && ack->daddr == ack_msg->daddr)
+ ret = i;
+ } while (read_seqretry(&ack_info->seqlock, start));
+ }
+
+ return ret;
+}
+
+void tcp_ack_update(struct tcp_ack_manage *ack_m)
+{
+ int i;
+ struct tcp_ack_info *ack_info;
+
+ if (time_after(jiffies, ack_m->last_time + ack_m->timeout)) {
+ spin_lock_bh(&ack_m->lock);
+ ack_m->last_time = jiffies;
+ for (i = TCP_ACK_NUM - 1; i >= 0; i--) {
+ ack_info = &ack_m->ack_info[i];
+ write_seqlock_bh(&ack_info->seqlock);
+ if (ack_info->busy &&
+ time_after(jiffies, ack_info->last_time + ack_info->timeout)) {
+ ack_m->free_index = i;
+ ack_m->max_num--;
+ ack_info->busy = 0;
+ }
+ write_sequnlock_bh(&ack_info->seqlock);
+ }
+ spin_unlock_bh(&ack_m->lock);
+ }
+}
+
+/* return val: -1 for no index, others for index */
+int tcp_ack_alloc_index(struct tcp_ack_manage *ack_m)
+{
+ int i, ret = -1;
+ struct tcp_ack_info *ack_info;
+ unsigned int start;
+
+ spin_lock_bh(&ack_m->lock);
+ if (ack_m->max_num == TCP_ACK_NUM) {
+ spin_unlock_bh(&ack_m->lock);
+ return -1;
+ }
+
+ if (ack_m->free_index >= 0) {
+ i = ack_m->free_index;
+ ack_m->free_index = -1;
+ ack_m->max_num++;
+ spin_unlock_bh(&ack_m->lock);
+ return i;
+ }
+
+ for (i = 0; ((ret < 0) && (i < TCP_ACK_NUM)); i++) {
+ ack_info = &ack_m->ack_info[i];
+ do {
+ start = read_seqbegin(&ack_info->seqlock);
+ ret = -1;
+ if (!ack_info->busy) {
+ ack_m->free_index = -1;
+ ack_m->max_num++;
+ ret = i;
+ }
+ } while (read_seqretry(&ack_info->seqlock, start));
+ }
+ spin_unlock_bh(&ack_m->lock);
+
+ return ret;
+}
+
+/* return val: 0 for not handle tx, 1 for handle tx */
+int tcp_ack_handle(struct msg_buf *new_msgbuf, struct tcp_ack_manage *ack_m,
+ struct tcp_ack_info *ack_info, struct tcp_ack_msg *ack_msg,
+ int type)
+{
+ int quick_ack = 0;
+ struct tcp_ack_msg *ack;
+ int ret = 0;
+ struct msg_buf *drop_msg = NULL;
+
+ write_seqlock_bh(&ack_info->seqlock);
+
+ ack_info->last_time = jiffies;
+ ack = &ack_info->ack_msg;
+
+ if (type == 2) {
+ if (U32_BEFORE(ack->seq, ack_msg->seq)) {
+ ack->seq = ack_msg->seq;
+ if (ack_info->psh_flag &&
+ !U32_BEFORE(ack_msg->seq, ack_info->psh_seq)) {
+ ack_info->psh_flag = 0;
+ }
+
+ if (ack_info->msgbuf) {
+ drop_msg = ack_info->msgbuf;
+ ack_info->msgbuf = NULL;
+ //del_timer(&ack_info->timer);
+ timer_delete(&ack_info->timer);
+ } else {
+ /* debug trace */
+ // printk("AICWF msgbuf is NULL\n");
+ }
+
+ ack_info->in_send_msg = NULL;
+ ack_info->drop_cnt = atomic_read(&ack_m->max_drop_cnt);
+ } else {
+ pr_info("AICWF %s before abnormal ack: %d, %d\n", __func__, ack->seq,
+ ack_msg->seq);
+ drop_msg = new_msgbuf;
+ ret = 1;
+ }
+ } else if (U32_BEFORE(ack->seq, ack_msg->seq)) {
+ if (ack_info->msgbuf) {
+ drop_msg = ack_info->msgbuf;
+ ack_info->msgbuf = NULL;
+ }
+
+ if (ack_info->psh_flag &&
+ !U32_BEFORE(ack_msg->seq, ack_info->psh_seq)) {
+ ack_info->psh_flag = 0;
+ quick_ack = 1;
+ } else {
+ ack_info->drop_cnt++;
+ }
+
+ ack->seq = ack_msg->seq;
+
+ if (quick_ack ||
+ (!ack_info->in_send_msg &&
+ ack_info->drop_cnt >= atomic_read(&ack_m->max_drop_cnt))) {
+ ack_info->drop_cnt = 0;
+ ack_info->in_send_msg = new_msgbuf;
+ //del_timer(&ack_info->timer);
+ timer_delete(&ack_info->timer);
+ } else {
+ ret = 1;
+ ack_info->msgbuf = new_msgbuf;
+ if (!timer_pending(&ack_info->timer))
+ mod_timer(&ack_info->timer, (jiffies + msecs_to_jiffies(5)));
+ }
+ } else {
+ pr_info("AICWF %s before ack: %d, %d\n", __func__, ack->seq, ack_msg->seq);
+ drop_msg = new_msgbuf;
+ ret = 1;
+ }
+
+ write_sequnlock_bh(&ack_info->seqlock);
+
+ if (drop_msg)
+ intf_tcp_drop_msg(ack_m->priv, drop_msg); // drop skb
+
+ return ret;
+}
+
+int tcp_ack_handle_new(struct msg_buf *new_msgbuf, struct tcp_ack_manage *ack_m,
+ struct tcp_ack_info *ack_info,
+ struct tcp_ack_msg *ack_msg, int type)
+{
+ int quick_ack = 0;
+ struct tcp_ack_msg *ack;
+ int ret = 0;
+ struct msg_buf *drop_msg = NULL;
+
+ write_seqlock_bh(&ack_info->seqlock);
+
+ ack_info->last_time = jiffies;
+ ack = &ack_info->ack_msg;
+
+ if (U32_BEFORE(ack->seq, ack_msg->seq)) {
+ if (ack_info->msgbuf) {
+ drop_msg = ack_info->msgbuf;
+ ack_info->msgbuf = NULL;
+ // ack_info->drop_cnt++; /* debug trace */
+ }
+
+ if (ack_info->psh_flag &&
+ !U32_BEFORE(ack_msg->seq, ack_info->psh_seq)) {
+ ack_info->psh_flag = 0;
+ quick_ack = 1;
+ } else {
+ ack_info->drop_cnt++;
+ }
+
+ ack->seq = ack_msg->seq;
+
+ if (quick_ack ||
+ (!ack_info->in_send_msg &&
+ ack_info->drop_cnt >= atomic_read(&ack_m->max_drop_cnt))) {
+ ack_info->drop_cnt = 0;
+ ack_info->in_send_msg = new_msgbuf;
+ //del_timer(&ack_info->timer);
+ timer_delete(&ack_info->timer);
+ } else {
+ ret = 1;
+ ack_info->msgbuf = new_msgbuf;
+ if (!timer_pending(&ack_info->timer))
+ mod_timer(&ack_info->timer, (jiffies + msecs_to_jiffies(5)));
+ }
+ } else {
+ pr_info("AICWF %s before ack: %d, %d\n", __func__, ack->seq, ack_msg->seq);
+ drop_msg = new_msgbuf;
+ ret = 1;
+ }
+
+ write_sequnlock_bh(&ack_info->seqlock);
+
+ if (drop_msg)
+ intf_tcp_drop_msg(ack_m->priv, drop_msg); // drop skb
+
+ return ret;
+}
+
+void filter_rx_tcp_ack(struct rwnx_hw *priv, unsigned char *buf, unsigned int plen)
+{
+ int index;
+ struct tcp_ack_msg ack_msg;
+ struct tcp_ack_info *ack_info;
+ struct tcp_ack_manage *ack_m = &priv->ack_m;
+
+ if (!atomic_read(&ack_m->enable))
+ return;
+
+ if (plen > MAX_TCP_ACK || !tcp_check_quick_ack(buf, &ack_msg))
+ return;
+
+ index = tcp_ack_match(ack_m, &ack_msg);
+ if (index >= 0) {
+ ack_info = ack_m->ack_info + index;
+ write_seqlock_bh(&ack_info->seqlock);
+ ack_info->psh_flag = 1;
+ ack_info->psh_seq = ack_msg.seq;
+ write_sequnlock_bh(&ack_info->seqlock);
+ }
+}
+
+/* return val: 0 for not filter, 1 for filter */
+int filter_send_tcp_ack(struct rwnx_hw *priv, struct msg_buf *msgbuf,
+ unsigned char *buf, unsigned int plen)
+{
+ int ret = 0;
+ int index, drop;
+ unsigned short win_scale = 0;
+ unsigned int win = 0;
+ struct tcp_ack_msg ack_msg;
+ struct tcp_ack_msg *ack;
+ struct tcp_ack_info *ack_info;
+ struct tcp_ack_manage *ack_m = &priv->ack_m;
+
+ tcp_ack_update(ack_m);
+ drop = tcp_check_ack(buf, &ack_msg, &win_scale);
+ // printk("AICWF drop:%d win_scale:%d",drop,win_scale); /* debug trace */
+ if (!drop && win_scale == 0)
+ return 0;
+
+ index = tcp_ack_match(ack_m, &ack_msg);
+ if (index >= 0) {
+ ack_info = ack_m->ack_info + index;
+ if (win_scale != 0 && ack_info->win_scale != win_scale) {
+ write_seqlock_bh(&ack_info->seqlock);
+ ack_info->win_scale = win_scale;
+ write_sequnlock_bh(&ack_info->seqlock);
+ }
+
+ if (drop > 0 && atomic_read(&ack_m->enable)) {
+ win = ack_info->win_scale * ack_msg.win;
+ if (win_scale != 0 && (win < (ack_m->ack_winsize * SIZE_KB))) {
+ drop = 2;
+ pr_info("AICWF %d %d %d", win_scale, win,
+ (ack_m->ack_winsize * SIZE_KB));
+ }
+ ret = tcp_ack_handle_new(msgbuf, ack_m, ack_info, &ack_msg, drop);
+ }
+
+ goto out;
+ }
+
+ index = tcp_ack_alloc_index(ack_m);
+ if (index >= 0) {
+ write_seqlock_bh(&ack_m->ack_info[index].seqlock);
+ ack_m->ack_info[index].busy = 1;
+ ack_m->ack_info[index].psh_flag = 0;
+ ack_m->ack_info[index].last_time = jiffies;
+ ack_m->ack_info[index].drop_cnt = atomic_read(&ack_m->max_drop_cnt);
+ ack_m->ack_info[index].win_scale = (win_scale != 0) ? win_scale : 1;
+
+ ack = &ack_m->ack_info[index].ack_msg;
+ ack->dest = ack_msg.dest;
+ ack->source = ack_msg.source;
+ ack->saddr = ack_msg.saddr;
+ ack->daddr = ack_msg.daddr;
+ ack->seq = ack_msg.seq;
+ write_sequnlock_bh(&ack_m->ack_info[index].seqlock);
+ }
+
+out:
+ return ret;
+}
+
+void move_tcpack_msg(struct rwnx_hw *priv, struct msg_buf *msg)
+{
+ struct tcp_ack_info *ack_info;
+ struct tcp_ack_manage *ack_m = &priv->ack_m;
+ int i = 0;
+
+ if (!atomic_read(&ack_m->enable))
+ return;
+
+ for (i = 0; i < TCP_ACK_NUM; i++) {
+ ack_info = &ack_m->ack_info[i];
+ write_seqlock_bh(&ack_info->seqlock);
+ if (ack_info->busy && ack_info->in_send_msg == msg)
+ ack_info->in_send_msg = NULL;
+ write_sequnlock_bh(&ack_info->seqlock);
+ }
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_tcp_ack.h b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_tcp_ack.h
new file mode 100644
index 0000000000000..70cbf59c7c59e
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_tcp_ack.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AICWF_TCP_ACK_H_
+#define _AICWF_TCP_ACK_H_
+
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <net/tcp.h>
+#include <uapi/linux/if_ether.h>
+#include <uapi/linux/in.h>
+#include <uapi/linux/ip.h>
+#include <uapi/linux/tcp.h>
+#include <linux/version.h>
+
+#define TCP_ACK_NUM 32
+#define TCP_ACK_EXIT_VAL 0x800
+#define TCP_ACK_DROP_CNT 10
+
+#define ACK_OLD_TIME 4000
+#define U32_BEFORE(a, b) ((__s32)((__u32)(a) - (__u32)b) <= 0)
+
+#define MAX_TCP_ACK 200
+/*min window size in KB, it's 256KB*/
+#define MIN_WIN 256
+#define SIZE_KB 1024
+
+struct msg_buf {
+ struct sk_buff *skb;
+ struct rwnx_vif *rwnx_vif;
+};
+
+struct tcp_ack_msg {
+ __be16 source;
+ __be16 dest;
+ __be32 saddr;
+ __be32 daddr;
+ u32 seq;
+ u16 win;
+};
+
+struct tcp_ack_info {
+ int ack_info_num;
+ int busy;
+ int drop_cnt;
+ int psh_flag;
+ u32 psh_seq;
+ u16 win_scale;
+ /* seqlock for ack info */
+ seqlock_t seqlock;
+ unsigned long last_time;
+ unsigned long timeout;
+ struct timer_list timer;
+ struct msg_buf *msgbuf;
+ struct msg_buf *in_send_msg;
+ struct tcp_ack_msg ack_msg;
+};
+
+struct tcp_ack_manage {
+ atomic_t enable;
+ int max_num;
+ int free_index;
+ unsigned long last_time;
+ unsigned long timeout;
+ atomic_t max_drop_cnt;
+ /* lock for tcp ack alloc and free */
+ spinlock_t lock;
+ struct rwnx_hw *priv;
+ struct tcp_ack_info ack_info[TCP_ACK_NUM];
+ /*size in KB */
+ unsigned int ack_winsize;
+};
+
+struct msg_buf *intf_tcp_alloc_msg(void);
+
+void tcp_ack_init(struct rwnx_hw *priv);
+
+void tcp_ack_deinit(struct rwnx_hw *priv);
+
+int is_drop_tcp_ack(struct tcphdr *tcphdr, int tcp_tot_len,
+ unsigned short *win_scale);
+
+int is_tcp_ack(struct sk_buff *skb, unsigned short *win_scale);
+
+int filter_send_tcp_ack(struct rwnx_hw *priv, struct msg_buf *msgbuf,
+ unsigned char *buf, unsigned int plen);
+
+void filter_rx_tcp_ack(struct rwnx_hw *priv, unsigned char *buf, unsigned int plen);
+
+void move_tcpack_msg(struct rwnx_hw *priv, struct msg_buf *msg);
+void intf_tcp_drop_msg(struct rwnx_hw *priv, struct msg_buf *msg);
+void tcp_ack_timeout(struct timer_list *t);
+int tcp_check_quick_ack(unsigned char *buf, struct tcp_ack_msg *msg);
+int tcp_check_ack(unsigned char *buf, struct tcp_ack_msg *msg,
+ unsigned short *win_scale);
+int tcp_ack_match(struct tcp_ack_manage *ack_m, struct tcp_ack_msg *ack_msg);
+void tcp_ack_update(struct tcp_ack_manage *ack_m);
+int tcp_ack_alloc_index(struct tcp_ack_manage *ack_m);
+int tcp_ack_handle(struct msg_buf *new_msgbuf, struct tcp_ack_manage *ack_m,
+ struct tcp_ack_info *ack_info, struct tcp_ack_msg *ack_msg,
+ int type);
+int tcp_ack_handle_new(struct msg_buf *new_msgbuf, struct tcp_ack_manage *ack_m,
+ struct tcp_ack_info *ack_info,
+ struct tcp_ack_msg *ack_msg, int type);
+
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_txrxif.c b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_txrxif.c
new file mode 100644
index 0000000000000..cea5896632d02
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_txrxif.c
@@ -0,0 +1,752 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief tx/rx bus function declarations
+ *
+ ******************************************************************************
+ */
+
+#include "aicwf_txrxif.h"
+#include "aicwf_rx_prealloc.h"
+#include "lmac_msg.h"
+#include "rwnx_defs.h"
+#include "rwnx_msg_rx.h"
+#include "rwnx_platform.h"
+#include "rwnx_rx.h"
+#include <linux/atomic.h>
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/netdevice.h>
+#include <linux/printk.h>
+#include <linux/sched.h>
+#include <linux/semaphore.h>
+#include <linux/types.h>
+#include <linux/unistd.h>
+#include <linux/vmalloc.h>
+#include <uapi/linux/sched/types.h>
+#ifdef AICWF_SDIO_SUPPORT
+#include "sdio_host.h"
+#endif
+#include "aic_bsp_export.h"
+
+#ifdef CONFIG_PREALLOC_RX_SKB
+void aicwf_rxframe_queue_init_2(struct rx_frame_queue *pq, int max_len)
+{
+ memset(pq, 0,
+ offsetof(struct rx_frame_queue, queuelist) +
+ (sizeof(struct list_head)));
+ pq->qmax = (u16)max_len;
+ INIT_LIST_HEAD(&pq->queuelist);
+}
+
+void rxbuff_queue_flush(struct aicwf_rx_priv *rx_priv)
+{
+ struct rx_frame_queue *pq = &rx_priv->rxq;
+ struct list_head *pos;
+ struct list_head *n;
+ struct list_head *head;
+ struct rx_buff *tempbuf = NULL;
+
+ head = &pq->queuelist;
+ list_for_each_safe(pos, n, head) {
+ tempbuf = list_entry(pos, struct rx_buff, queue);
+ list_del_init(&tempbuf->queue);
+ aicwf_prealloc_rxbuff_free(tempbuf, &rx_priv->rxbuff_lock);
+ pq->qcnt--;
+ }
+}
+#endif
+
+int aicwf_bus_init(uint bus_hdrlen, struct device *dev)
+{
+ int ret = 0;
+ struct aicwf_bus *bus_if;
+
+ if (!dev) {
+ txrx_err("device not found\n");
+ return -1;
+ }
+ bus_if = dev_get_drvdata(dev);
+ bus_if->cmd_buf = kzalloc(CMD_BUF_MAX, GFP_KERNEL);
+ if (!bus_if->cmd_buf) {
+ ret = -ENOMEM;
+ txrx_err("proto_attach failed\n");
+ goto out;
+ }
+ memset(bus_if->cmd_buf, '\0', CMD_BUF_MAX);
+
+ init_completion(&bus_if->bustx_trgg);
+ init_completion(&bus_if->busrx_trgg);
+ // new oob feature
+ init_completion(&bus_if->busirq_trgg);
+#ifdef AICWF_SDIO_SUPPORT
+ spin_lock_init(&bus_if->bus_priv.sdio->wslock);
+ bus_if->bustx_thread =
+ kthread_run(sdio_bustx_thread, (void *)bus_if, "aicwf_bustx_thread");
+ bus_if->busrx_thread =
+ kthread_run(sdio_busrx_thread, (void *)bus_if->bus_priv.sdio->rx_priv,
+ "aicwf_busrx_thread");
+ // new oob feature
+#ifdef CONFIG_OOB
+ if (bus_if->bus_priv.sdio->oob_enable) {
+ bus_if->busirq_thread = kthread_run(sdio_busirq_thread,
+ (void *)bus_if->bus_priv.sdio->rx_priv,
+ "aicwf_busirq_thread");
+ }
+#endif // CONFIG_OOB
+#endif
+
+ if (IS_ERR(bus_if->bustx_thread)) {
+ bus_if->bustx_thread = NULL;
+ txrx_err("aicwf_bustx_thread run fail\n");
+ ret = -1;
+ }
+
+ if (IS_ERR(bus_if->busrx_thread)) {
+ bus_if->busrx_thread = NULL;
+ txrx_err("aicwf_bustx_thread run fail\n");
+ ret = -1;
+ }
+
+ if (ret < 0)
+ goto out;
+
+ return ret;
+
+out:
+ return ret;
+}
+
+void aicwf_bus_deinit(struct device *dev)
+{
+ struct aicwf_bus *bus_if;
+#ifdef AICWF_SDIO_SUPPORT
+ struct aic_sdio_dev *sdiodev;
+#endif
+
+ if (!dev) {
+ txrx_err("device not found\n");
+ return;
+ }
+ AICWFDBG(LOGINFO, "%s Enter\r\n", __func__);
+ bus_if = dev_get_drvdata(dev);
+ aicwf_bus_stop(bus_if);
+
+#ifdef AICWF_SDIO_SUPPORT
+ sdiodev = bus_if->bus_priv.sdio;
+ if (g_rwnx_plat && g_rwnx_plat->enabled)
+ rwnx_platform_deinit(sdiodev->rwnx_hw);
+#endif
+
+ kfree(bus_if->cmd_buf);
+ if (bus_if->cmd_buf)
+ bus_if->cmd_buf = NULL;
+
+ if (bus_if->bustx_thread) {
+ sched_set_normal(bus_if->bustx_thread, 0);
+ complete_all(&bus_if->bustx_trgg);
+ kthread_stop(bus_if->bustx_thread);
+ bus_if->bustx_thread = NULL;
+ }
+ AICWFDBG(LOGINFO, "%s Exit\r\n", __func__);
+}
+
+void aicwf_frame_tx(void *dev, struct sk_buff *skb)
+{
+#ifdef AICWF_SDIO_SUPPORT
+ struct aic_sdio_dev *sdiodev = (struct aic_sdio_dev *)dev;
+
+ aicwf_bus_txdata(sdiodev->bus_if, skb);
+#else
+ struct aic_usb_dev *usbdev = (struct aic_usb_dev *)dev;
+
+ if (!usbdev->state) {
+ txrx_err("down\n");
+ aicwf_usb_tx_flowctrl(usbdev->rwnx_hw, true);
+ dev_kfree_skb(skb);
+ return;
+ }
+ aicwf_bus_txdata(usbdev->bus_if, skb);
+#endif
+}
+
+struct aicwf_tx_priv *aicwf_tx_init(void *arg)
+{
+ struct aicwf_tx_priv *tx_priv;
+
+ tx_priv =
+ kzalloc_obj(*tx_priv, GFP_KERNEL);
+ if (!tx_priv)
+ return NULL;
+
+#ifdef AICWF_SDIO_SUPPORT
+ tx_priv->sdiodev = (struct aic_sdio_dev *)arg;
+#else
+ tx_priv->usbdev = (struct aic_usb_dev *)arg;
+#endif
+
+ atomic_set(&tx_priv->aggr_count, 0);
+#ifdef CONFIG_RESV_MEM_SUPPORT
+ tx_priv->aggr_buf =
+ aicbsp_resv_mem_alloc_skb(MAX_AGGR_TXPKT_LEN, AIC_RESV_MEM_TXDATA);
+#else
+ tx_priv->aggr_buf =
+ dev_alloc_skb(MAX_AGGR_TXPKT_LEN);
+#endif
+ if (!tx_priv->aggr_buf) {
+ txrx_err("Alloc bus->txdata_buf failed!\n");
+ kfree(tx_priv);
+ return NULL;
+ }
+ tx_priv->head = tx_priv->aggr_buf->data;
+ tx_priv->tail = tx_priv->aggr_buf->data;
+
+ return tx_priv;
+}
+
+void aicwf_tx_deinit(struct aicwf_tx_priv *tx_priv)
+{
+ if (tx_priv && tx_priv->aggr_buf) {
+#ifdef CONFIG_RESV_MEM_SUPPORT
+ aicbsp_resv_mem_kfree_skb(tx_priv->aggr_buf, AIC_RESV_MEM_TXDATA);
+#else
+ dev_kfree_skb(tx_priv->aggr_buf);
+#endif
+ kfree(tx_priv);
+ }
+}
+
+#ifdef AICWF_SDIO_SUPPORT
+#ifdef CONFIG_PREALLOC_RX_SKB
+static bool aicwf_another_ptk_1(struct rx_buff *buffer)
+{
+ u8 *read = buffer->read;
+ u16 aggr_len = 0;
+
+ WARN_ON_ONCE((read - buffer->start) % 4 != 0);
+
+ if (!read || read >= buffer->end)
+ return false;
+
+ aggr_len = (*read | (*(read + 1) << 8));
+ if (aggr_len == 0)
+ return false;
+
+ return true;
+}
+#else
+static bool aicwf_another_ptk(struct sk_buff *skb)
+{
+ u16 aggr_len = 0;
+
+ if (!skb->data || skb->len == 0)
+ return false;
+ aggr_len = (*skb->data | (*(skb->data + 1) << 8));
+ if (aggr_len == 0)
+ return false;
+
+ return true;
+}
+#endif
+#endif
+
+int aicwf_process_rxframes(struct aicwf_rx_priv *rx_priv)
+{
+#ifdef AICWF_SDIO_SUPPORT
+ int ret = 0;
+ unsigned long flags = 0;
+#ifndef CONFIG_PREALLOC_RX_SKB
+ struct sk_buff *skb = NULL;
+#endif
+ u16 pkt_len = 0;
+ struct sk_buff *skb_inblock = NULL;
+ u16 aggr_len = 0, adjust_len = 0;
+ u8 *data = NULL;
+ u8 *msg = NULL;
+#ifdef CONFIG_PREALLOC_RX_SKB
+ struct rx_buff *buffer = NULL;
+
+ while (1) {
+ spin_lock_irqsave(&rx_priv->rxqlock, flags);
+ if (!rx_priv->rxq.qcnt) {
+ spin_unlock_irqrestore(&rx_priv->rxqlock, flags);
+ break;
+ }
+ buffer = rxbuff_dequeue(&rx_priv->rxq);
+ spin_unlock_irqrestore(&rx_priv->rxqlock, flags);
+ if (!buffer) {
+ txrx_err("skb_error\r\n");
+ break;
+ }
+ while (aicwf_another_ptk_1(buffer)) {
+ data = buffer->read;
+ pkt_len = (*data | (*(data + 1) << 8));
+
+ if ((data[2] & SDIO_TYPE_CFG) != SDIO_TYPE_CFG) {
+ // type: data
+ aggr_len = pkt_len + RX_HWHRD_LEN;
+
+ if (aggr_len & (RX_ALIGNMENT - 1))
+ adjust_len = roundup(aggr_len, RX_ALIGNMENT);
+ else
+ adjust_len = aggr_len;
+
+ skb_inblock =
+ __dev_alloc_skb(aggr_len + CCMP_OR_WEP_INFO, GFP_KERNEL);
+ if (!skb_inblock) {
+ txrx_err("no more space! skip\n");
+ buffer->read = buffer->read + adjust_len;
+ continue;
+ }
+
+ skb_put(skb_inblock, aggr_len);
+ memcpy(skb_inblock->data, data, aggr_len);
+ rwnx_rxdataind_aicwf(rx_priv->sdiodev->rwnx_hw, skb_inblock,
+ (void *)rx_priv);
+ buffer->read = buffer->read + adjust_len;
+ } else {
+ // type: config
+ aggr_len = pkt_len;
+
+ if (aggr_len & (RX_ALIGNMENT - 1))
+ adjust_len = roundup(aggr_len, RX_ALIGNMENT);
+ else
+ adjust_len = aggr_len;
+
+ msg =
+ kmalloc(aggr_len + 4, GFP_KERNEL);
+ if (!msg) {
+ txrx_err("no more space for msg!\n");
+ aicwf_prealloc_rxbuff_free(buffer, &rx_priv->rxbuff_lock);
+ return -EBADE;
+ }
+
+ memcpy(msg, data, aggr_len + 4);
+ if (((*(msg + 2) & 0x7f) == SDIO_TYPE_CFG_CMD_RSP) &&
+ rx_priv->sdiodev->bus_if->state != BUS_DOWN_ST)
+ rwnx_rx_handle_msg(rx_priv->sdiodev->rwnx_hw,
+ (struct ipc_e2a_msg *)(msg + 4));
+
+ if ((*(msg + 2) & 0x7f) == SDIO_TYPE_CFG_DATA_CFM)
+ aicwf_sdio_host_tx_cfm_handler(&(rx_priv->sdiodev
+ ->rwnx_hw->sdio_env),
+ (u32 *)(msg + 4));
+
+ if ((*(msg + 2) & 0x7f) == SDIO_TYPE_CFG_PRINT)
+ rwnx_rx_handle_print(rx_priv->sdiodev->rwnx_hw, msg + 4,
+ aggr_len);
+
+ buffer->read = buffer->read + (adjust_len + 4);
+ kfree(msg);
+ }
+ }
+
+ aicwf_prealloc_rxbuff_free(buffer, &rx_priv->rxbuff_lock);
+
+ atomic_dec(&rx_priv->rx_cnt);
+ }
+
+#else
+
+ while (1) {
+ spin_lock_irqsave(&rx_priv->rxqlock, flags);
+ if (aicwf_is_framequeue_empty(&rx_priv->rxq)) {
+ spin_unlock_irqrestore(&rx_priv->rxqlock, flags);
+ break;
+ }
+ skb = aicwf_frame_dequeue(&rx_priv->rxq);
+ spin_unlock_irqrestore(&rx_priv->rxqlock, flags);
+ if (!skb) {
+ txrx_err("skb_error\r\n");
+ break;
+ }
+ while (aicwf_another_ptk(skb)) {
+ data = skb->data;
+ pkt_len = (*skb->data | (*(skb->data + 1) << 8));
+
+ if ((skb->data[2] & SDIO_TYPE_CFG) != SDIO_TYPE_CFG) {
+ // type: data
+ aggr_len = pkt_len + RX_HWHRD_LEN;
+
+ if (aggr_len & (RX_ALIGNMENT - 1))
+ adjust_len = roundup(aggr_len, RX_ALIGNMENT);
+ else
+ adjust_len = aggr_len;
+
+ skb_inblock =
+ __dev_alloc_skb(aggr_len + CCMP_OR_WEP_INFO, GFP_KERNEL);
+ if (!skb_inblock) {
+ txrx_err("no more space! skip\n");
+ skb_pull(skb, adjust_len);
+ continue;
+ }
+
+ skb_put(skb_inblock, aggr_len);
+ memcpy(skb_inblock->data, data, aggr_len);
+ rwnx_rxdataind_aicwf(rx_priv->sdiodev->rwnx_hw, skb_inblock,
+ (void *)rx_priv);
+ skb_pull(skb, adjust_len);
+ } else {
+ // type: config
+ aggr_len = pkt_len;
+
+ if (aggr_len & (RX_ALIGNMENT - 1))
+ adjust_len = roundup(aggr_len, RX_ALIGNMENT);
+ else
+ adjust_len = aggr_len;
+
+ msg =
+ kmalloc(aggr_len + 4, GFP_KERNEL);
+ if (!msg) {
+ txrx_err("no more space for msg!\n");
+ aicwf_dev_skb_free(skb);
+ return -EBADE;
+ }
+
+ memcpy(msg, data, aggr_len + 4);
+ if (((*(msg + 2) & 0x7f) == SDIO_TYPE_CFG_CMD_RSP) &&
+ rx_priv->sdiodev->bus_if->state != BUS_DOWN_ST)
+ rwnx_rx_handle_msg(rx_priv->sdiodev->rwnx_hw,
+ (struct ipc_e2a_msg *)(msg + 4));
+
+ struct sdio_host_env_tag *env =
+ &rx_priv->sdiodev->rwnx_hw->sdio_env;
+ if ((*(msg + 2) & 0x7f) == SDIO_TYPE_CFG_DATA_CFM)
+ aicwf_sdio_host_tx_cfm_handler(env, (u32 *)(msg + 4));
+
+ if ((*(msg + 2) & 0x7f) == SDIO_TYPE_CFG_PRINT)
+ rwnx_rx_handle_print(rx_priv->sdiodev->rwnx_hw,
+ msg + 4, aggr_len);
+
+ skb_pull(skb, adjust_len + 4);
+ kfree(msg);
+ }
+ }
+
+ dev_kfree_skb(skb);
+ atomic_dec(&rx_priv->rx_cnt);
+ }
+#endif
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+ aicwf_sdio_pwr_stctl(rx_priv->sdiodev, SDIO_ACTIVE_ST);
+#endif
+
+ return ret;
+#endif
+}
+
+static struct recv_msdu *aicwf_rxframe_queue_init(struct list_head *q,
+ int qsize)
+{
+ int i;
+ struct recv_msdu *req, *reqs;
+
+ reqs = vmalloc(qsize * sizeof(struct recv_msdu));
+ if (!reqs)
+ return NULL;
+
+ req = reqs;
+ for (i = 0; i < qsize; i++) {
+ INIT_LIST_HEAD(&req->rxframe_list);
+ list_add(&req->rxframe_list, q);
+ req++;
+ }
+
+ return reqs;
+}
+
+struct aicwf_rx_priv *aicwf_rx_init(void *arg)
+{
+ struct aicwf_rx_priv *rx_priv;
+
+ rx_priv = kzalloc_obj(*rx_priv, GFP_KERNEL);
+ if (!rx_priv)
+ return NULL;
+
+#ifdef AICWF_SDIO_SUPPORT
+ rx_priv->sdiodev = (struct aic_sdio_dev *)arg;
+#else
+ rx_priv->usbdev = (struct aic_usb_dev *)arg;
+#endif
+
+#ifdef CONFIG_PREALLOC_RX_SKB
+ aicwf_rxframe_queue_init_2(&rx_priv->rxq, MAX_RXQLEN);
+#else
+ aicwf_frame_queue_init(&rx_priv->rxq, 1, MAX_RXQLEN);
+#endif
+ spin_lock_init(&rx_priv->rxqlock);
+#ifdef CONFIG_PREALLOC_RX_SKB
+ spin_lock_init(&rx_priv->rxbuff_lock);
+ aicwf_prealloc_init();
+#endif
+ atomic_set(&rx_priv->rx_cnt, 0);
+
+#ifdef AICWF_RX_REORDER
+ struct list_head *q = &rx_priv->rxframes_freequeue;
+
+ INIT_LIST_HEAD(&rx_priv->rxframes_freequeue);
+ spin_lock_init(&rx_priv->freeq_lock);
+ rx_priv->recv_frames = aicwf_rxframe_queue_init(q, MAX_REORD_RXFRAME);
+ if (!rx_priv->recv_frames) {
+ txrx_err("no enough buffer for free recv frame queue!\n");
+ kfree(rx_priv);
+ return NULL;
+ }
+ spin_lock_init(&rx_priv->stas_reord_lock);
+ INIT_LIST_HEAD(&rx_priv->stas_reord_list);
+#endif
+
+ return rx_priv;
+}
+
+static void aicwf_recvframe_queue_deinit(struct list_head *q)
+{
+ struct recv_msdu *req, *next;
+
+ list_for_each_entry_safe(req, next, q, rxframe_list) {
+ list_del_init(&req->rxframe_list);
+ }
+}
+
+void aicwf_rx_deinit(struct aicwf_rx_priv *rx_priv)
+{
+#ifdef AICWF_RX_REORDER
+ struct reord_ctrl_info *reord_info, *tmp;
+
+ spin_lock_bh(&rx_priv->stas_reord_lock);
+ list_for_each_entry_safe(reord_info, tmp, &rx_priv->stas_reord_list, list) {
+ reord_deinit_sta(rx_priv, reord_info);
+ }
+ spin_unlock_bh(&rx_priv->stas_reord_lock);
+#endif
+
+#ifdef AICWF_SDIO_SUPPORT
+ AICWFDBG(LOGINFO, "sdio rx thread\n");
+ if (rx_priv->sdiodev->bus_if->busrx_thread) {
+ sched_set_normal(rx_priv->sdiodev->bus_if->busrx_thread, 0);
+ complete_all(&rx_priv->sdiodev->bus_if->busrx_trgg);
+ kthread_stop(rx_priv->sdiodev->bus_if->busrx_thread);
+ rx_priv->sdiodev->bus_if->busrx_thread = NULL;
+ }
+#ifdef CONFIG_OOB
+ if (rx_priv->sdiodev->oob_enable) {
+ // new oob feature
+ if (rx_priv->sdiodev->bus_if->busirq_thread) {
+ complete_all(&rx_priv->sdiodev->bus_if->busirq_trgg);
+ kthread_stop(rx_priv->sdiodev->bus_if->busirq_thread);
+ rx_priv->sdiodev->bus_if->busirq_thread = NULL;
+ }
+ }
+#endif // CONFIG_OOB
+#endif
+
+#ifdef CONFIG_PREALLOC_RX_SKB
+ rxbuff_queue_flush(rx_priv);
+#else
+ aicwf_frame_queue_flush(&rx_priv->rxq);
+#endif
+
+#ifdef AICWF_RX_REORDER
+ aicwf_recvframe_queue_deinit(&rx_priv->rxframes_freequeue);
+ if (rx_priv->recv_frames)
+ vfree(rx_priv->recv_frames);
+#endif
+
+#ifdef CONFIG_PREALLOC_RX_SKB
+ aicwf_prealloc_exit();
+#endif
+ kfree(rx_priv);
+}
+
+bool aicwf_rxframe_enqueue(struct device *dev, struct frame_queue *q,
+ struct sk_buff *pkt)
+{
+ return aicwf_frame_enq(dev, q, pkt, 0);
+}
+
+void aicwf_dev_skb_free(struct sk_buff *skb)
+{
+ if (!skb)
+ return;
+
+ dev_kfree_skb_any(skb);
+}
+
+static struct sk_buff *aicwf_frame_queue_penq(struct frame_queue *pq, int prio,
+ struct sk_buff *p)
+{
+ struct sk_buff_head *q;
+
+ if (pq->queuelist[prio].qlen >= pq->qmax)
+ return NULL;
+
+ q = &pq->queuelist[prio];
+ __skb_queue_tail(q, p);
+ pq->qcnt++;
+ if (pq->hi_prio < prio)
+ pq->hi_prio = (u16)prio;
+
+ return p;
+}
+
+void aicwf_frame_queue_flush(struct frame_queue *pq)
+{
+ int prio;
+ struct sk_buff_head *q;
+ struct sk_buff *p, *next;
+
+ for (prio = 0; prio < pq->num_prio; prio++) {
+ q = &pq->queuelist[prio];
+ skb_queue_walk_safe(q, p, next)
+ {
+ skb_unlink(p, q);
+ aicwf_dev_skb_free(p);
+ pq->qcnt--;
+ }
+ }
+}
+
+void aicwf_frame_queue_init(struct frame_queue *pq, int num_prio, int max_len)
+{
+ int prio;
+
+ memset(pq, 0,
+ offsetof(struct frame_queue, queuelist) +
+ (sizeof(struct sk_buff_head) * num_prio));
+ pq->num_prio = (u16)num_prio;
+ pq->qmax = (u16)max_len;
+
+ for (prio = 0; prio < num_prio; prio++)
+ skb_queue_head_init(&pq->queuelist[prio]);
+}
+
+struct sk_buff *aicwf_frame_queue_peek_tail(struct frame_queue *pq,
+ int *prio_out)
+{
+ int prio;
+
+ if (pq->qcnt == 0)
+ return NULL;
+
+ for (prio = 0; prio < pq->hi_prio; prio++)
+ if (!skb_queue_empty(&pq->queuelist[prio]))
+ break;
+
+ if (prio_out)
+ *prio_out = prio;
+
+ return skb_peek_tail(&pq->queuelist[prio]);
+}
+
+bool aicwf_is_framequeue_empty(struct frame_queue *pq)
+{
+ int prio, len = 0;
+
+ for (prio = 0; prio <= pq->hi_prio; prio++)
+ len += pq->queuelist[prio].qlen;
+
+ if (len > 0)
+ return false;
+ else
+ return true;
+}
+
+struct sk_buff *aicwf_frame_dequeue(struct frame_queue *pq)
+{
+ struct sk_buff_head *q;
+ struct sk_buff *p;
+ int prio;
+
+ if (pq->qcnt == 0)
+ return NULL;
+
+ while ((prio = pq->hi_prio) > 0 && skb_queue_empty(&pq->queuelist[prio]))
+ pq->hi_prio--;
+
+ q = &pq->queuelist[prio];
+ p = __skb_dequeue(q);
+ if (!p)
+ return NULL;
+
+ pq->qcnt--;
+
+ return p;
+}
+
+bool aicwf_frame_enq(struct device *dev, struct frame_queue *q,
+ struct sk_buff *pkt, int prio)
+{
+ if (q->queuelist[prio].qlen < q->qmax && q->qcnt < q->qmax) {
+ aicwf_frame_queue_penq(q, prio, pkt);
+ return true;
+ }
+ return false;
+}
+
+#ifdef CONFIG_PREALLOC_RX_SKB
+void rxbuff_free(struct rx_buff *rxbuff)
+{
+ kfree(rxbuff->data);
+ kfree(rxbuff);
+}
+
+static struct rx_buff *rxbuff_queue_penq(struct rx_frame_queue *pq,
+ struct rx_buff *p)
+{
+ struct list_head *q;
+
+ if (pq->qcnt >= pq->qmax)
+ return NULL;
+
+ q = &pq->queuelist;
+ list_add_tail(&p->queue, q);
+
+ pq->qcnt++;
+
+ return p;
+}
+
+struct rx_buff *rxbuff_dequeue(struct rx_frame_queue *pq)
+{
+ struct rx_buff *p = NULL;
+
+ if (pq->qcnt == 0) {
+ pr_err("%s %d, rxq is empty\n", __func__, __LINE__);
+ return NULL;
+ }
+
+ if (list_empty(&pq->queuelist)) {
+ pr_err("%s %d, rxq is empty\n", __func__, __LINE__);
+ return NULL;
+ }
+ p = list_first_entry(&pq->queuelist, struct rx_buff, queue);
+ list_del_init(&p->queue);
+ pq->qcnt--;
+
+ return p;
+}
+
+bool aicwf_rxbuff_enqueue(struct device *dev, struct rx_frame_queue *rxq,
+ struct rx_buff *pkt)
+{
+ if (!rxq || !pkt) {
+ pr_err("%s %d, rxq or pkt is NULL\n", __func__, __LINE__);
+ return false;
+ }
+
+ if (rxq->qcnt < rxq->qmax) {
+ if (rxbuff_queue_penq(rxq, pkt))
+ return true;
+ pr_err("%s %d, rxbuff enqueue fail\n", __func__, __LINE__);
+ return false;
+ }
+ pr_err("%s %d, rxq or pkt is full\n", __func__, __LINE__);
+ return false;
+}
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_txrxif.h b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_txrxif.h
new file mode 100644
index 0000000000000..439bc50bd394f
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_txrxif.h
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AICWF_TXRXIF_H_
+#define _AICWF_TXRXIF_H_
+
+#include "aicwf_rx_prealloc.h"
+#include "ipc_shared.h"
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#ifdef AICWF_SDIO_SUPPORT
+#include "aicwf_sdio.h"
+#else
+#include "aicwf_usb.h"
+#endif
+#include "aic_bus.h"
+
+#define CMD_BUF_MAX 1536
+#define TXPKT_BLOCKSIZE 512
+#define MAX_AGGR_TXPKT_LEN (1536 * 64)
+#define CMD_TX_TIMEOUT 5000
+#define TX_ALIGNMENT 4
+
+#define RX_HWHRD_LEN 60 // 58->60 word allined
+#define CCMP_OR_WEP_INFO 8
+#define MAX_RXQLEN 2000
+#define RX_ALIGNMENT 4
+
+#define DEBUG_ERROR_LEVEL 0
+#define DEBUG_DEBUG_LEVEL 1
+#define DEBUG_INFO_LEVEL 2
+
+#define DBG_LEVEL DEBUG_ERROR_LEVEL
+
+#define txrx_err(fmt, ...) \
+ pr_err("txrx_err:<%s,%d>: " fmt, __func__, __LINE__, ##__VA_ARGS__)
+#define sdio_err(fmt, ...) \
+ pr_err("sdio_err:<%s,%d>: " fmt, __func__, __LINE__, ##__VA_ARGS__)
+#define usb_err(fmt, ...) \
+ pr_err("usb_err:<%s,%d>: " fmt, __func__, __LINE__, ##__VA_ARGS__)
+#if DBG_LEVEL >= DEBUG_DEBUG_LEVEL
+#define txrx_dbg(fmt, ...) pr_debug("txrx: " fmt, ##__VA_ARGS__)
+#define sdio_dbg(fmt, ...) pr_debug("aicsdio: " fmt, ##__VA_ARGS__)
+#define usb_dbg(fmt, ...) pr_debug("aicusb: " fmt, ##__VA_ARGS__)
+#else
+#define txrx_dbg(fmt, ...)
+#define sdio_dbg(fmt, ...)
+#define usb_dbg(fmt, ...)
+#endif
+#if DBG_LEVEL >= DEBUG_INFO_LEVEL
+#define txrx_info(fmt, ...) pr_info("aicsdio: " fmt, ##__VA_ARGS__)
+#define sdio_info(fmt, ...) pr_info("aicsdio: " fmt, ##__VA_ARGS__)
+#define usb_info(fmt, ...) pr_info("aicusb: " fmt, ##__VA_ARGS__)
+#else
+#define txrx_info(fmt, ...)
+#define sdio_info(fmt, ...)
+#define usb_info(fmt, ...)
+#endif
+
+/* aicwf_bus_state is now defined in aic_bus.h */
+/* aicwf_bus_ops is now defined in aic_bus.h */
+
+struct frame_queue {
+ u16 num_prio;
+ u16 hi_prio;
+ u16 qmax; /* max number of queued frames */
+ u16 qcnt;
+ struct sk_buff_head queuelist[8];
+};
+
+#ifdef CONFIG_PREALLOC_RX_SKB
+struct rx_frame_queue {
+ u16 qmax; /* max number of queued frames */
+ u16 qcnt;
+ struct list_head queuelist;
+};
+#endif
+
+struct aicwf_tx_priv {
+#ifdef AICWF_SDIO_SUPPORT
+ struct aic_sdio_dev *sdiodev;
+ int fw_avail_bufcnt;
+ // for cmd tx
+ u8 *cmd_buf;
+ uint cmd_len;
+ bool cmd_txstate;
+ bool cmd_tx_succ;
+ struct semaphore cmd_txsema;
+ wait_queue_head_t cmd_txdone_wait;
+ // for data tx
+ atomic_t tx_pktcnt;
+
+ struct frame_queue txq;
+ /* lock for tx */
+ spinlock_t txqlock;
+ struct semaphore txctl_sema;
+#endif
+ struct sk_buff *aggr_buf;
+ atomic_t aggr_count;
+ u8 *head;
+ u8 *tail;
+};
+
+#define DEFRAG_MAX_WAIT 40 // 100
+#ifdef AICWF_RX_REORDER
+#define MAX_REORD_RXFRAME 250
+#define REORDER_UPDATE_TIME 100//50
+#define AICWF_REORDER_WINSIZE 64
+#define SN_LESS(a, b) ((((a) - (b)) & 0x800) != 0)
+#define SN_EQUAL(a, b) ((a) == (b))
+
+struct reord_ctrl {
+ struct aicwf_rx_priv *rx_priv;
+ u8 enable;
+ u16 ind_sn;
+ u8 wsize_b;
+ /* lock for reord_list */
+ spinlock_t reord_list_lock;
+ struct list_head reord_list;
+ struct timer_list reord_timer;
+ struct work_struct reord_timer_work;
+};
+
+struct reord_ctrl_info {
+ u8 mac_addr[6];
+ struct reord_ctrl preorder_ctrl[8];
+ struct list_head list;
+};
+
+struct recv_msdu {
+ struct sk_buff *pkt;
+ u8 tid;
+ u16 seq_num;
+ u8 forward;
+ u32 is_amsdu;
+ u8 *rx_data;
+ // for pending rx reorder list
+ struct list_head reord_pending_list;
+ // for total frame list, when rxframe from busif, dequeue, when submit frame
+ // to net, enqueue
+ struct list_head rxframe_list;
+ struct reord_ctrl *preorder_ctrl;
+};
+#endif
+
+struct aicwf_rx_priv {
+#ifdef AICWF_SDIO_SUPPORT
+ struct aic_sdio_dev *sdiodev;
+#endif
+ void *rwnx_vif;
+ atomic_t rx_cnt;
+ u32 data_len;
+ /* lock for aicwf_rx */
+ spinlock_t rxqlock;
+#ifdef CONFIG_PREALLOC_RX_SKB
+ struct rx_frame_queue rxq;
+#else
+ struct frame_queue rxq;
+#endif
+
+#ifdef AICWF_RX_REORDER
+ /* lock for freeq */
+ spinlock_t freeq_lock;
+ struct list_head rxframes_freequeue;
+ struct list_head stas_reord_list;
+ /* lock for stas reord */
+ spinlock_t stas_reord_lock;
+ struct recv_msdu *recv_frames;
+#endif
+#ifdef CONFIG_PREALLOC_RX_SKB
+ /* lock for rxbuff */
+ spinlock_t rxbuff_lock;
+#endif
+};
+
+/* Inline bus wrappers are now defined in aic_bus.h */
+/* aicwf_bus_init / aicwf_bus_deinit are now declared in aic_bus.h */
+
+static inline void aicwf_sched_timeout(u32 millisec)
+{
+ ulong timeout = 0, expires = 0;
+
+ expires = jiffies + msecs_to_jiffies(millisec);
+ timeout = millisec;
+
+ while (timeout) {
+ timeout = schedule_timeout(timeout);
+ if (time_after(jiffies, expires))
+ break;
+ }
+}
+
+void aicwf_tx_deinit(struct aicwf_tx_priv *tx_priv);
+void aicwf_rx_deinit(struct aicwf_rx_priv *rx_priv);
+struct aicwf_tx_priv *aicwf_tx_init(void *arg);
+struct aicwf_rx_priv *aicwf_rx_init(void *arg);
+void aicwf_frame_queue_init(struct frame_queue *pq, int num_prio, int max_len);
+void aicwf_frame_queue_flush(struct frame_queue *pq);
+bool aicwf_frame_enq(struct device *dev, struct frame_queue *q,
+ struct sk_buff *pkt, int prio);
+bool aicwf_rxframe_enqueue(struct device *dev, struct frame_queue *q,
+ struct sk_buff *pkt);
+bool aicwf_is_framequeue_empty(struct frame_queue *pq);
+void aicwf_frame_tx(void *dev, struct sk_buff *skb);
+void aicwf_dev_skb_free(struct sk_buff *skb);
+struct sk_buff *aicwf_frame_dequeue(struct frame_queue *pq);
+struct sk_buff *aicwf_frame_queue_peek_tail(struct frame_queue *pq,
+ int *prio_out);
+#ifdef CONFIG_PREALLOC_RX_SKB
+void rxbuff_queue_flush(struct aicwf_rx_priv *rx_priv);
+void aicwf_rxframe_queue_init_2(struct rx_frame_queue *pq, int max_len);
+void rxbuff_free(struct rx_buff *rxbuff);
+struct rx_buff *rxbuff_dequeue(struct rx_frame_queue *pq);
+bool aicwf_rxbuff_enqueue(struct device *dev, struct rx_frame_queue *rxq,
+ struct rx_buff *pkt);
+extern struct aicwf_rx_buff_list aic_rx_buff_list;
+#endif
+
+#endif /* _AICWF_TXRXIF_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/regdb.c b/drivers/net/wireless/aic/aic8800_fdrv/regdb.c
new file mode 100644
index 0000000000000..a795c9b55112b
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/regdb.c
@@ -0,0 +1,2772 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief country code Dependent document
+ * https://mirrors.edge.kernel.org/pub/software/network/wireless-regdb/
+ * wireless-regdb-2024.05.08
+ *
+ ******************************************************************************
+ */
+
+#include <linux/nl80211.h>
+#include <linux/version.h>
+#include <net/cfg80211.h>
+
+#include "rwnx_mod_params.h"
+
+static const struct ieee80211_regdomain regdom_00 = {
+ .n_reg_rules = 8,
+ .alpha2 = "00",
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 20, 0, 0),
+ // 12 - 13
+ REG_RULE_EXT(2457, 2482, 20, 0, 20, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_IR | 0),
+ // 14
+ REG_RULE_EXT(2474, 2494, 20, 0, 20, 0,
+ NL80211_RRF_NO_IR | NL80211_RRF_NO_OFDM | 0),
+ // 36 - 48
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_IR | 0),
+ // 52 - 64
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_IR | NL80211_RRF_DFS | 0),
+ // 100 - 144
+ REG_RULE_EXT(5490, 5730, 160, 0, 20, 0,
+ NL80211_RRF_NO_IR | NL80211_RRF_DFS | 0),
+ //149 - 165
+ REG_RULE_EXT(5735, 5835, 80, 0, 20, 0,
+ NL80211_RRF_NO_IR | 0),
+ REG_RULE_EXT(57240, 63720, 2160, 0, 0, 0, 0),
+ }};
+
+static const struct ieee80211_regdomain regdom_AD = {
+ .alpha2 = "AD",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW |
+ NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_AE = {
+ .alpha2 = "AE",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ //REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5490, 5725, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_AF = {
+ .alpha2 = "AF",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_AI = {
+ .alpha2 = "AI",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_AL = {
+ .alpha2 = "AL",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW |
+ NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_AM = {
+ .alpha2 = "AM",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 20, 0, 18, 0, 0),
+ //REG_RULE_EXT(5250, 5330, 20, 0, 18, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 2};
+
+static const struct ieee80211_regdomain regdom_AN = {
+ .alpha2 = "AN",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_AR = {
+ .alpha2 = "AR",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 2};
+
+static const struct ieee80211_regdomain regdom_AS = {
+ .alpha2 = "AS",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_AT = {
+ .alpha2 = "AT",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW |
+ NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_AU = {
+ .alpha2 = "AU",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_AW = {
+ .alpha2 = "AW",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_AZ = {
+ .alpha2 = "AZ",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_BA = {
+ .alpha2 = "BA",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_BB = {
+ .alpha2 = "BB",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 23, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 23, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+//Certified laboratory requirements
+static const struct ieee80211_regdomain regdom_BD = {
+ .alpha2 = "BD",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_BE = {
+ .alpha2 = "BE",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_BF = {
+ .alpha2 = "BF",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_BG = {
+ .alpha2 = "BG",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_BH = {
+ .alpha2 = "BH",
+ .dfs_region = NL80211_DFS_JP,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 20, 0, 20, 0, 0),
+ REG_RULE_EXT(5250, 5330, 20, 0, 20, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5735, 5835, 20, 0, 20, 0, 0),
+ },
+ .n_reg_rules = 3};
+
+static const struct ieee80211_regdomain regdom_BL = {
+ .alpha2 = "BL",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_BM = {
+ .alpha2 = "BM",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_BN = {
+ .alpha2 = "BN",
+ .dfs_region = NL80211_DFS_JP,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 20, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_BO = {
+ .alpha2 = "BO",
+ .dfs_region = NL80211_DFS_JP,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 30, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 3};
+
+static const struct ieee80211_regdomain regdom_BR = {
+ .alpha2 = "BR",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 27, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 27, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW |
+ NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5725, 5850, 80, 0, 30, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5925, 7125, 320, 0, 12, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_NO_IR | 0),
+ REG_RULE_EXT(57000, 71000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_BS = {
+ .alpha2 = "BS",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_BT = {
+ .alpha2 = "BT",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_BW = {
+ .alpha2 = "BW",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_BY = {
+ .alpha2 = "BY",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_BZ = {
+ .alpha2 = "BZ",
+ .dfs_region = NL80211_DFS_JP,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 2};
+
+static const struct ieee80211_regdomain regdom_CA = {
+ .alpha2 = "CA",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_CF = {
+ .alpha2 = "CF",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 40, 0, 17, 0, 0),
+ REG_RULE_EXT(5250, 5330, 40, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5490, 5730, 40, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 40, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_CH = {
+ .alpha2 = "CH",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 71000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_CI = {
+ .alpha2 = "CI",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_CL = {
+ .alpha2 = "CL",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_CN = {
+ .alpha2 = "CN",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5350, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW |
+ NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5725, 5850, 80, 0, 33, 0, 0),
+ REG_RULE_EXT(57240, 59400, 2160, 0, 28, 0, 0),
+ REG_RULE_EXT(59400, 63720, 2160, 0, 44, 0, 0),
+ REG_RULE_EXT(63720, 65880, 2160, 0, 28, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_CO = {
+ .alpha2 = "CO",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_CR = {
+ .alpha2 = "CR",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 20, 0, 17, 0, 0),
+ REG_RULE_EXT(5250, 5330, 20, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5490, 5730, 20, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 20, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_CU = {
+ .alpha2 = "CU",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 23, 0, 0),
+ REG_RULE_EXT(5150, 5350, 80, 0, 23, 0,
+ NL80211_RRF_NO_IR | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5470, 5725, 80, 0, 24, 0, NL80211_RRF_NO_IR | 0),
+ REG_RULE_EXT(5725, 5850, 80, 0, 23, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_CX = {
+ .alpha2 = "CX",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_CY = {
+ .alpha2 = "CY",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_CZ = {
+ .alpha2 = "CZ",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_DE = {
+ .alpha2 = "DE",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_DK = {
+ .alpha2 = "DK",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_DM = {
+ .alpha2 = "DM",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 23, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_DO = {
+ .alpha2 = "DO",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 23, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+//Certified laboratory requirements
+static const struct ieee80211_regdomain regdom_DZ = {
+ .alpha2 = "DZ",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_EC = {
+ .alpha2 = "EC",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 17, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 21, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 21, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5850, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_EE = {
+ .alpha2 = "EE",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_EG = {
+ .alpha2 = "EG",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_ES = {
+ .alpha2 = "ES",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_ET = {
+ .alpha2 = "ET",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_FI = {
+ .alpha2 = "FI",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_FM = {
+ .alpha2 = "FM",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_FR = {
+ .alpha2 = "FR",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_GB = {
+ .alpha2 = "GB",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5470, 5730, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5850, 80, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5925, 6425, 160, 0, 24, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 71000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_GD = {
+ .alpha2 = "GD",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_GE = {
+ .alpha2 = "GE",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 18, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 18, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_GF = {
+ .alpha2 = "GF",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+//Certified laboratory requirements
+static const struct ieee80211_regdomain regdom_GH = {
+ .alpha2 = "GH",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_GI = {
+ .alpha2 = "GI",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_GL = {
+ .alpha2 = "GL",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_GP = {
+ .alpha2 = "GP",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_GR = {
+ .alpha2 = "GR",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_GT = {
+ .alpha2 = "GT",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 23, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_GU = {
+ .alpha2 = "GU",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 20, 0, 17, 0, 0),
+ REG_RULE_EXT(5250, 5330, 20, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5490, 5730, 20, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 20, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_GY = {
+ .alpha2 = "GY",
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 23, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 23, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 23, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_HK = {
+ .alpha2 = "HK",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 36, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 23, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW |
+ NL80211_RRF_NO_OUTDOOR | 0),
+ //REG_RULE_EXT(5470, 5730, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5730, 5850, 80, 0, 36, 0, 0),
+ REG_RULE_EXT(5925, 6425, 160, 0, 14, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_HN = {
+ .alpha2 = "HN",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_HR = {
+ .alpha2 = "HR",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 23, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_HT = {
+ .alpha2 = "HT",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_HU = {
+ .alpha2 = "HU",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_ID = {
+ .alpha2 = "ID",
+ .dfs_region = NL80211_DFS_JP,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 27, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5150, 5350, 80, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5725, 5825, 80, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ },
+ .n_reg_rules = 3};
+
+static const struct ieee80211_regdomain regdom_IE = {
+ .alpha2 = "IE",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_IL = {
+ .alpha2 = "IL",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW |
+ NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 23, 0, NL80211_RRF_AUTO_BW | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_IN = {
+ .alpha2 = "IN",
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 30, 0, 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_IQ = {
+ .alpha2 = "IQ",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_IR = {
+ .alpha2 = "IR",
+ .dfs_region = NL80211_DFS_JP,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 2};
+
+static const struct ieee80211_regdomain regdom_IS = {
+ .alpha2 = "IS",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW |
+ NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_IT = {
+ .alpha2 = "IT",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_JM = {
+ .alpha2 = "JM",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_JO = {
+ .alpha2 = "JO",
+ .dfs_region = NL80211_DFS_JP,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 23, 0, 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 23, 0, 0),
+ },
+ .n_reg_rules = 3};
+
+static const struct ieee80211_regdomain regdom_JP = {
+ .alpha2 = "JP",
+ .dfs_region = NL80211_DFS_JP,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(2474, 2494, 20, 0, 20, 0, NL80211_RRF_NO_OFDM | 0),
+ REG_RULE_EXT(4910, 4990, 40, 0, 23, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 23, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5925, 6425, 320, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 10, 0, 0),
+ },
+ .n_reg_rules = 8};
+
+static const struct ieee80211_regdomain regdom_KE = {
+ .alpha2 = "KE",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_KG = {
+ .alpha2 = "KG",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_KH = {
+ .alpha2 = "KH",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_KN = {
+ .alpha2 = "KN",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 30, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5815, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_KP = {
+ .alpha2 = "KP",
+ .dfs_region = NL80211_DFS_JP,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 20, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 20, 0, 20, 0, 0),
+ REG_RULE_EXT(5250, 5330, 20, 0, 20, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5490, 5630, 20, 0, 30, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5815, 20, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_KR = {
+ .alpha2 = "KR",
+ .dfs_region = NL80211_DFS_JP,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 23, 0, 0),
+ REG_RULE_EXT(5150, 5230, 40, 0, 23, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5230, 5250, 20, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 20, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5850, 80, 0, 23, 0, 0),
+ REG_RULE_EXT(5925, 7125, 160, 0, 15, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 43, 0, 0),
+ },
+ .n_reg_rules = 8};
+
+static const struct ieee80211_regdomain regdom_KW = {
+ .alpha2 = "KW",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_KY = {
+ .alpha2 = "KY",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_KZ = {
+ .alpha2 = "KZ",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5725, 5850, 80, 0, 20, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_LB = {
+ .alpha2 = "LB",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ //REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5490, 5725, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_LC = {
+ .alpha2 = "LC",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 30, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5815, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_LI = {
+ .alpha2 = "LI",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_LK = {
+ .alpha2 = "LK",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 20, 0, 17, 0, 0),
+ REG_RULE_EXT(5250, 5330, 20, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5490, 5730, 20, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 20, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_LS = {
+ .alpha2 = "LS",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_LT = {
+ .alpha2 = "LT",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_LU = {
+ .alpha2 = "LU",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_LV = {
+ .alpha2 = "LV",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_LY = {
+ .alpha2 = "LY",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_MA = {
+ .alpha2 = "MA",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ },
+ .n_reg_rules = 3};
+
+static const struct ieee80211_regdomain regdom_MC = {
+ .alpha2 = "MC",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_MD = {
+ .alpha2 = "MD",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_ME = {
+ .alpha2 = "ME",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_MF = {
+ .alpha2 = "MF",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_MH = {
+ .alpha2 = "MH",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_MK = {
+ .alpha2 = "MK",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_MN = {
+ .alpha2 = "MN",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ //REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5490, 5725, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_MO = {
+ .alpha2 = "MO",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 23, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 23, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 23, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ //REG_RULE_EXT(5490, 5730, 160, 0, 30, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5490, 5725, 160, 0, 30, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_MP = {
+ .alpha2 = "MP",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_MQ = {
+ .alpha2 = "MQ",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_MR = {
+ .alpha2 = "MR",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_MT = {
+ .alpha2 = "MT",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_MU = {
+ .alpha2 = "MU",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_MV = {
+ .alpha2 = "MV",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5850, 80, 0, 20, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_MW = {
+ .alpha2 = "MW",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_MX = {
+ .alpha2 = "MX",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+//Certified laboratory requirements
+static const struct ieee80211_regdomain regdom_MY = {
+ .alpha2 = "MY",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ //REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5470, 5650, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_NA = {
+ .alpha2 = "NA",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_NG = {
+ .alpha2 = "NG",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_NI = {
+ .alpha2 = "NI",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_NL = {
+ .alpha2 = "NL",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_NO = {
+ .alpha2 = "NO",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 71000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_NP = {
+ .alpha2 = "NP",
+ .dfs_region = NL80211_DFS_JP,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 20, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_NZ = {
+ .alpha2 = "NZ",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ //REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5490, 5725, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_OM = {
+ .alpha2 = "OM",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_PA = {
+ .alpha2 = "PA",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 36, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 36, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 30, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 30, 0, 0),
+ REG_RULE_EXT(5725, 5850, 80, 0, 36, 0, 0),
+ REG_RULE_EXT(57000, 64000, 2160, 0, 43, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+//Certified laboratory requirements
+static const struct ieee80211_regdomain regdom_PE = {
+ .alpha2 = "PE",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_PF = {
+ .alpha2 = "PF",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_PG = {
+ .alpha2 = "PG",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+//Certified laboratory requirements
+static const struct ieee80211_regdomain regdom_PH = {
+ .alpha2 = "PH",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_PK = {
+ .alpha2 = "PK",
+ .dfs_region = NL80211_DFS_JP,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5270, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5270, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW |
+ NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5470, 5610, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW |
+ NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5610, 5725, 80, 0, 23, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 30, 0, 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_PL = {
+ .alpha2 = "PL",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_PM = {
+ .alpha2 = "PM",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_PR = {
+ .alpha2 = "PR",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_PT = {
+ .alpha2 = "PT",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_PW = {
+ .alpha2 = "PW",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_PY = {
+ .alpha2 = "PY",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_QA = {
+ .alpha2 = "QA",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW |
+ NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS | 0),
+ // REG_RULE_EXT(5725, 5875, 80, 0, 20, 0,
+ // NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_RE = {
+ .alpha2 = "RE",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_RO = {
+ .alpha2 = "RO",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_RS = {
+ .alpha2 = "RS",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW |
+ NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_RU = {
+ .alpha2 = "RU",
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_RW = {
+ .alpha2 = "RW",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_SA = {
+ .alpha2 = "SA",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_SE = {
+ .alpha2 = "SE",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_SG = {
+ .alpha2 = "SG",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 23, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ //REG_RULE_EXT(5470, 5730, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5850, 80, 0, 30, 0, 0),
+ REG_RULE_EXT(5945, 6425, 320, 0, 24, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_SI = {
+ .alpha2 = "SI",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_SK = {
+ .alpha2 = "SK",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_SM = {
+ .alpha2 = "SM",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_SN = {
+ .alpha2 = "SN",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_SR = {
+ .alpha2 = "SR",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_SV = {
+ .alpha2 = "SV",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 20, 0, 17, 0, 0),
+ REG_RULE_EXT(5250, 5330, 20, 0, 23, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 20, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_SY = {
+ .alpha2 = "SY",
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ },
+ .n_reg_rules = 1};
+
+static const struct ieee80211_regdomain regdom_TC = {
+ .alpha2 = "TC",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_TD = {
+ .alpha2 = "TD",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_TG = {
+ .alpha2 = "TG",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5250, 5330, 40, 0, 20, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5490, 5710, 40, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+//Certified laboratory requirements
+static const struct ieee80211_regdomain regdom_TH = {
+ .alpha2 = "TH",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_TJ = {
+ .alpha2 = "TJ",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ //REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_TM = {
+ .alpha2 = "TM",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_TN = {
+ .alpha2 = "TN",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ },
+ .n_reg_rules = 3};
+
+static const struct ieee80211_regdomain regdom_TR = {
+ .alpha2 = "TR",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5495, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_TT = {
+ .alpha2 = "TT",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_TW = {
+ .alpha2 = "TW",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ //REG_RULE_EXT(2400, 2483, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(2400, 2473, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 23, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ //REG_RULE_EXT(5470, 5730, 160, 0, 23, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 23, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5850, 80, 0, 30, 0, 0),
+ REG_RULE_EXT(5945, 6425, 320, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_TZ = {
+ .alpha2 = "TZ",
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 2};
+
+static const struct ieee80211_regdomain regdom_UA = {
+ .alpha2 = "UA",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_NO_OUTDOOR |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5725, 5850, 80, 0, 20, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 16, 0,
+ NL80211_RRF_NO_OUTDOOR | 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_UG = {
+ .alpha2 = "UG",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_US = {
+ .alpha2 = "US",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ // 1...11
+ REG_RULE_EXT(2400, 2472, 40, 0, 30, 0, 0),
+ // 36 40 44 48
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0, NL80211_RRF_AUTO_BW | 0),
+ // 52 56 60 64
+ REG_RULE_EXT(5250, 5350, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ // 100 104 108 112 116 120 124
+ REG_RULE_EXT(5470, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ // 128 132 136 140
+ REG_RULE_EXT(5730, 5850, 80, 0, 30, 0, NL80211_RRF_AUTO_BW | 0),
+ // 149 153 157 161 165
+ REG_RULE_EXT(5850, 5895, 40, 0, 27, 0,
+ NL80211_RRF_AUTO_BW | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(5925, 7125, 320, 0, 12, 0,
+ NL80211_RRF_NO_IR | NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57240, 71000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 8};
+
+//Certified laboratory requirements
+static const struct ieee80211_regdomain regdom_UY = {
+ .alpha2 = "UY",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ //REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_UZ = {
+ .alpha2 = "UZ",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ },
+ .n_reg_rules = 3};
+
+static const struct ieee80211_regdomain regdom_VC = {
+ .alpha2 = "VC",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_VE = {
+ .alpha2 = "VE",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 23, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 23, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_VI = {
+ .alpha2 = "VI",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2472, 40, 0, 30, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 24, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_VN = {
+ .alpha2 = "VN",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5490, 5730, 80, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5490, 5725, 80, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_VU = {
+ .alpha2 = "VU",
+ .dfs_region = NL80211_DFS_FCC,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 17, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5730, 160, 0, 24, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 5};
+
+static const struct ieee80211_regdomain regdom_WF = {
+ .alpha2 = "WF",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_WS = {
+ .alpha2 = "WS",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5250, 5330, 40, 0, 20, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5490, 5710, 40, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_FK = {
+ .alpha2 = "FK",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_FO = {
+ .alpha2 = "FO",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+static const struct ieee80211_regdomain regdom_YE = {
+ .alpha2 = "YE",
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ },
+ .n_reg_rules = 1};
+
+static const struct ieee80211_regdomain regdom_YT = {
+ .alpha2 = "YT",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+static const struct ieee80211_regdomain regdom_ZA = {
+ .alpha2 = "ZA",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 30, 0, 0),
+ },
+ .n_reg_rules = 4};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_ZM = {
+ .alpha2 = "ZM",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 7};
+
+static const struct ieee80211_regdomain regdom_ZW = {
+ .alpha2 = "ZW",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5170, 5250, 80, 0, 20, 0, NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+ NL80211_RRF_DFS | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5490, 5710, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ },
+ .n_reg_rules = 4};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_XK = {
+ .alpha2 = "XK",
+ .dfs_region = NL80211_DFS_ETSI,
+ .reg_rules = {
+ REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+ REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+ NL80211_RRF_NO_OUTDOOR | NL80211_RRF_DFS |
+ NL80211_RRF_AUTO_BW | 0),
+ REG_RULE_EXT(5470, 5725, 160, 0, 27, 0, NL80211_RRF_DFS | 0),
+ //REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+ REG_RULE_EXT(5945, 6425, 160, 0, 23, 0, NL80211_RRF_NO_OUTDOOR | 0),
+ REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+ },
+ .n_reg_rules = 6};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_WW = {
+ .n_reg_rules = 2,
+ .alpha2 = "WW",
+ .reg_rules = {
+ // 1...14
+ REG_RULE(2390 - 10, 2510 + 10, 40, 0, 20, 0),
+ // 36...165
+ REG_RULE(5150 - 10, 5970 + 10, 80, 0, 20, 0),
+ },
+};
+
+//kernel undefined
+static const struct ieee80211_regdomain regdom_XX = {
+ .n_reg_rules = 1,
+ .alpha2 = "XX",
+ .reg_rules = {
+ /* IEEE 802.11b/g, channels 1..11 */
+ REG_RULE(2412 - 10, 2462 + 10, 40, 0, 20, 0),
+ },
+};
+
+//Keep "XX" at the end when inserting
+const struct ieee80211_regdomain *const reg_regdb[] = {
+ ®dom_00, ®dom_AD, ®dom_AE, ®dom_AF, ®dom_AI, ®dom_AL,
+ ®dom_AM, ®dom_AN, ®dom_AR, ®dom_AS, ®dom_AT, ®dom_AU,
+ ®dom_AW, ®dom_AZ, ®dom_BA, ®dom_BB, ®dom_BD, ®dom_BE,
+ ®dom_BF, ®dom_BG, ®dom_BH, ®dom_BL, ®dom_BM, ®dom_BN,
+ ®dom_BO, ®dom_BR, ®dom_BS, ®dom_BT, ®dom_BY, ®dom_BZ,
+ ®dom_CA, ®dom_CF, ®dom_CH, ®dom_CI, ®dom_CL, ®dom_CN,
+ ®dom_CO, ®dom_CR, ®dom_CX, ®dom_CY, ®dom_CZ, ®dom_DE,
+ ®dom_DK, ®dom_DM, ®dom_DO, ®dom_DZ, ®dom_EC, ®dom_EE,
+ ®dom_EG, ®dom_ES, ®dom_ET, ®dom_FI, ®dom_FM, ®dom_FR,
+ ®dom_GB, ®dom_GD, ®dom_GE, ®dom_GF, ®dom_GH, ®dom_GL,
+ ®dom_GP, ®dom_GR, ®dom_GT, ®dom_GU, ®dom_GY, ®dom_HK,
+ ®dom_HN, ®dom_HR, ®dom_HT, ®dom_HU, ®dom_ID, ®dom_IE,
+ ®dom_IL, ®dom_IN, ®dom_IR, ®dom_IS, ®dom_IT, ®dom_JM,
+ ®dom_JO, ®dom_JP, ®dom_KE, ®dom_KH, ®dom_KN, ®dom_KP,
+ ®dom_KR, ®dom_KW, ®dom_KY, ®dom_KZ, ®dom_LB, ®dom_LC,
+ ®dom_LI, ®dom_LK, ®dom_LS, ®dom_LT, ®dom_LU, ®dom_LV,
+ ®dom_MA, ®dom_MC, ®dom_MD, ®dom_ME, ®dom_MF, ®dom_MH,
+ ®dom_MK, ®dom_MN, ®dom_MO, ®dom_MP, ®dom_MQ, ®dom_MR,
+ ®dom_MT, ®dom_MU, ®dom_MW, ®dom_MX, ®dom_MY, ®dom_NI,
+ ®dom_NL, ®dom_NO, ®dom_NP, ®dom_NZ, ®dom_OM, ®dom_PA,
+ ®dom_PE, ®dom_PF, ®dom_PG, ®dom_PH, ®dom_PK, ®dom_PL,
+ ®dom_PM, ®dom_PR, ®dom_PT, ®dom_PW, ®dom_PY, ®dom_QA,
+ ®dom_RE, ®dom_RO, ®dom_RS, ®dom_RU, ®dom_RW, ®dom_SA,
+ ®dom_SE, ®dom_SG, ®dom_SI, ®dom_SK, ®dom_SN, ®dom_SR,
+ ®dom_SV, ®dom_SY, ®dom_TC, ®dom_TD, ®dom_TG, ®dom_TH,
+ ®dom_TN, ®dom_TR, ®dom_TT, ®dom_TW, ®dom_UA, ®dom_UG,
+ ®dom_US, ®dom_UY, ®dom_UZ, ®dom_VC, ®dom_VE, ®dom_VI,
+ ®dom_VN, ®dom_VU, ®dom_WF, ®dom_YE, ®dom_YT, ®dom_ZA,
+ ®dom_ZW, ®dom_BW, ®dom_GI, ®dom_IQ, ®dom_KG, ®dom_LY,
+ ®dom_MV, ®dom_NA, ®dom_NG, ®dom_SM, ®dom_TJ, ®dom_TM,
+ ®dom_FK, ®dom_FO, ®dom_ZM, ®dom_CU, ®dom_TZ, ®dom_WS,
+ ®dom_XK, ®dom_WW, ®dom_XX,
+};
+
+int reg_regdb_size = ARRAY_SIZE(reg_regdb);
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_bfmer.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_bfmer.c
new file mode 100644
index 0000000000000..6b619bf8f02bb
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_bfmer.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief VHT Beamformer function definitions
+ *
+ ******************************************************************************
+ */
+
+#include "rwnx_bfmer.h"
+#include <linux/slab.h>
+
+int rwnx_bfmer_report_add(struct rwnx_hw *rwnx_hw, struct rwnx_sta *rwnx_sta,
+ unsigned int length)
+{
+ gfp_t flags;
+ struct rwnx_bfmer_report *bfm_report;
+
+ if (in_softirq())
+ flags = GFP_ATOMIC;
+ else
+ flags = GFP_KERNEL;
+
+ /* Allocate a structure that will contain the beamforming report */
+ bfm_report = kmalloc(sizeof(*bfm_report) + length, flags);
+
+ /* Check report allocation */
+ if (!bfm_report) {
+ /* Do not use beamforming */
+ return -1;
+ }
+
+ /* Store report length */
+ bfm_report->length = length;
+
+ /*
+ * Need to provide a Virtual Address to the MAC so that it can
+ * upload the received Beamforming Report in driver memory
+ */
+ bfm_report->dma_addr = dma_map_single(rwnx_hw->dev, &bfm_report->report[0],
+ length, DMA_FROM_DEVICE);
+
+ /* Check DMA mapping result */
+ if (dma_mapping_error(rwnx_hw->dev, bfm_report->dma_addr)) {
+ /* Free allocated report */
+ kfree(bfm_report);
+ /* And leave */
+ return -1;
+ }
+
+ /* Store report structure */
+ rwnx_sta->bfm_report = bfm_report;
+
+ return 0;
+}
+
+void rwnx_bfmer_report_del(struct rwnx_hw *rwnx_hw, struct rwnx_sta *rwnx_sta)
+{
+ /* Verify if a report has been allocated */
+ if (rwnx_sta->bfm_report) {
+ struct rwnx_bfmer_report *bfm_report = rwnx_sta->bfm_report;
+
+ /* Unmap DMA region */
+ dma_unmap_single(rwnx_hw->dev, bfm_report->dma_addr, bfm_report->length,
+ DMA_BIDIRECTIONAL);
+
+ /* Free allocated report structure and clean the pointer */
+ kfree(bfm_report);
+ rwnx_sta->bfm_report = NULL;
+ }
+}
+
+#ifdef CONFIG_RWNX_FULLMAC
+u8 rwnx_bfmer_get_rx_nss(const struct ieee80211_vht_cap *vht_capa)
+{
+ int i;
+ u8 rx_nss = 0;
+ u16 rx_mcs_map = le16_to_cpu(vht_capa->supp_mcs.rx_mcs_map);
+
+ for (i = 7; i >= 0; i--) {
+ u8 mcs = (rx_mcs_map >> (2 * i)) & 3;
+
+ if (mcs != IEEE80211_VHT_MCS_NOT_SUPPORTED) {
+ rx_nss = i + 1;
+ break;
+ }
+ }
+
+ return rx_nss;
+}
+#endif /* CONFIG_RWNX_FULLMAC */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_bfmer.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_bfmer.h
new file mode 100644
index 0000000000000..5737d504b0251
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_bfmer.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief VHT Beamformer function definitions
+ *
+ ******************************************************************************
+ */
+
+#ifndef _RWNX_BFMER_H_
+#define _RWNX_BFMER_H_
+
+/*
+ * INCLUDE FILES
+ ******************************************************************************
+ */
+
+#include "rwnx_defs.h"
+
+/*
+ * DEFINES
+ ******************************************************************************
+ */
+
+/// Maximal supported report length (in bytes)
+#define RWNX_BFMER_REPORT_MAX_LEN 2048
+
+/// Size of the allocated report space (twice the maximum report length)
+#define RWNX_BFMER_REPORT_SPACE_SIZE (RWNX_BFMER_REPORT_MAX_LEN * 2)
+
+/*
+ * TYPE DEFINITIONS
+ ******************************************************************************
+ */
+
+/*
+ * Structure used to store a beamforming report.
+ */
+struct rwnx_bfmer_report {
+ dma_addr_t dma_addr; /* Virtual address provided to MAC for
+ * DMA transfer of the Beamforming Report
+ */
+ unsigned int length; /* Report Length */
+ u8 report[1]; /* Report to be used for VHT TX Beamforming */
+};
+
+/*
+ * FUNCTION DECLARATIONS
+ ******************************************************************************
+ */
+
+/*
+ ******************************************************************************
+ * @brief Allocate memory aiming to contains the Beamforming Report received
+ * from a Beamformee capable.
+ * The providing length shall be large enough to contain the VHT Compressed
+ * Beaforming Report and the MU Exclusive part.
+ * It also perform a DMA Mapping providing an address to be provided to the HW
+ * responsible for the DMA transfer of the report.
+ * If successful a struct rwnx_bfmer_report object is allocated, it's address
+ * is stored in rwnx_sta->bfm_report.
+ *
+ * @param[in] rwnx_hw PHY Information
+ * @param[in] rwnx_sta Peer STA Information
+ * @param[in] length Memory size to be allocated
+ *
+ * @return 0 if operation is successful, else -1.
+ ******************************************************************************
+ */
+int rwnx_bfmer_report_add(struct rwnx_hw *rwnx_hw, struct rwnx_sta *rwnx_sta,
+ unsigned int length);
+
+/*
+ ******************************************************************************
+ * @brief Free a previously allocated memory intended to be used for
+ * Beamforming Reports.
+ *
+ * @param[in] rwnx_hw PHY Information
+ * @param[in] rwnx_sta Peer STA Information
+ *
+ ******************************************************************************
+ */
+void rwnx_bfmer_report_del(struct rwnx_hw *rwnx_hw, struct rwnx_sta *rwnx_sta);
+
+#ifdef CONFIG_RWNX_FULLMAC
+/*
+ ******************************************************************************
+ * @brief Parse a Rx VHT-MCS map in order to deduce the maximum number of
+ * Spatial Streams supported by a beamformee.
+ *
+ * @param[in] vht_capa Received VHT Capability field.
+ *
+ ******************************************************************************
+ */
+u8 rwnx_bfmer_get_rx_nss(const struct ieee80211_vht_cap *vht_capa);
+#endif /* CONFIG_RWNX_FULLMAC */
+
+#endif /* _RWNX_BFMER_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_cfgfile.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_cfgfile.c
new file mode 100644
index 0000000000000..db8e2e4e4f629
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_cfgfile.c
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief parse config
+ *
+ ****************************************************************************************
+ */
+
+#include "rwnx_cfgfile.h"
+#include "rwnx_platform.h"
+
+/*
+ *
+ */
+static const char *rwnx_find_tag(const u8 *file_data, unsigned int file_size,
+ const char *tag_name, unsigned int tag_len)
+{
+ unsigned int curr, line_start = 0, line_size;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ /* Walk through all the lines of the configuration file */
+ while (line_start < file_size) {
+ /* Search the end of the current line (or the end of the file) */
+ for (curr = line_start; curr < file_size; curr++)
+ if (file_data[curr] == '\n')
+ break;
+
+ /* Compute the line size */
+ line_size = curr - line_start;
+
+ /* Check if this line contains the expected tag */
+ if (line_size == (strlen(tag_name) + tag_len) &&
+ !strncmp(&file_data[line_start], tag_name, strlen(tag_name)))
+ return &file_data[line_start + strlen(tag_name)];
+
+ /* Move to next line */
+ line_start = curr + 1;
+ }
+
+ /* Tag not found */
+ return NULL;
+}
+
+/*
+ * Parse the Config file used at init time
+ */
+int rwnx_parse_configfile(struct rwnx_hw *rwnx_hw, const char *filename,
+ struct rwnx_conf_file *config)
+{
+ const struct firmware *config_fw;
+ u8 dflt_mac[ETH_ALEN] = {0, 111, 111, 111, 111, 0};
+ int ret;
+ const u8 *tag_ptr;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ ret = aicwf_request_firmware(&config_fw, filename, rwnx_hw->dev);
+ if (ret) {
+ AICWFDBG(LOGDEBUG, KERN_CRIT "%s: Failed to get %s (%d)\n",
+ __func__, filename, ret);
+ return ret;
+ }
+
+ /* Get MAC Address */
+ tag_ptr = rwnx_find_tag(config_fw->data, config_fw->size,
+ "MAC_ADDR=", strlen("00:00:00:00:00:00"));
+ if (tag_ptr) {
+ u8 *addr = config->mac_addr;
+
+ if (sscanf(tag_ptr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", addr + 0, addr + 1,
+ addr + 2, addr + 3, addr + 4, addr + 5) != ETH_ALEN)
+ memcpy(config->mac_addr, dflt_mac, ETH_ALEN);
+ } else {
+ memcpy(config->mac_addr, dflt_mac, ETH_ALEN);
+ }
+
+ RWNX_DBG("MAC Address is:\n%pM\n", config->mac_addr);
+
+ /* Release the configuration file */
+ release_firmware(config_fw);
+
+ return 0;
+}
+
+/*
+ * Parse the Config file used at init time
+ */
+int rwnx_parse_phy_configfile(struct rwnx_hw *rwnx_hw, const char *filename,
+ struct rwnx_phy_conf_file *config, int path)
+{
+ const struct firmware *config_fw;
+ int ret;
+ const u8 *tag_ptr;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ ret = aicwf_request_firmware(&config_fw, filename, rwnx_hw->dev);
+ if (ret) {
+ AICWFDBG(LOGDEBUG, KERN_CRIT "%s: Failed to get %s (%d)\n",
+ __func__, filename, ret);
+ return ret;
+ }
+
+ /* Get Trident path mapping */
+ tag_ptr = rwnx_find_tag(config_fw->data, config_fw->size,
+ "TRD_PATH_MAPPING=", strlen("00"));
+ if (tag_ptr) {
+ u8 val;
+
+ if (kstrtou8(tag_ptr, 16, &val) == 0)
+ config->trd.path_mapping = val;
+ else
+ config->trd.path_mapping = path;
+ } else {
+ config->trd.path_mapping = path;
+ }
+
+ RWNX_DBG("Trident path mapping is: %d\n", config->trd.path_mapping);
+
+ /* Get DC offset compensation */
+ tag_ptr = rwnx_find_tag(config_fw->data, config_fw->size,
+ "TX_DC_OFF_COMP=", strlen("00000000"));
+ if (tag_ptr) {
+ if (sscanf(tag_ptr, "%08x", &config->trd.tx_dc_off_comp) != 1)
+ config->trd.tx_dc_off_comp = 0;
+ } else {
+ config->trd.tx_dc_off_comp = 0;
+ }
+
+ RWNX_DBG("TX DC offset compensation is: %08X\n",
+ config->trd.tx_dc_off_comp);
+
+ /* Get Karst TX IQ compensation value for path0 on 2.4GHz */
+ tag_ptr =
+ rwnx_find_tag(config_fw->data, config_fw->size,
+ "KARST_TX_IQ_COMP_2_4G_PATH_0=", strlen("00000000"));
+ if (tag_ptr) {
+ if (sscanf(tag_ptr, "%08x", &config->karst.tx_iq_comp_2_4G[0]) != 1)
+ config->karst.tx_iq_comp_2_4G[0] = 0x01000000;
+ } else {
+ config->karst.tx_iq_comp_2_4G[0] = 0x01000000;
+ }
+
+ RWNX_DBG("Karst TX IQ compensation for path 0 on 2.4GHz is: %08X\n",
+ config->karst.tx_iq_comp_2_4G[0]);
+
+ /* Get Karst TX IQ compensation value for path1 on 2.4GHz */
+ tag_ptr =
+ rwnx_find_tag(config_fw->data, config_fw->size,
+ "KARST_TX_IQ_COMP_2_4G_PATH_1=", strlen("00000000"));
+ if (tag_ptr) {
+ if (sscanf(tag_ptr, "%08x", &config->karst.tx_iq_comp_2_4G[1]) != 1)
+ config->karst.tx_iq_comp_2_4G[1] = 0x01000000;
+ } else {
+ config->karst.tx_iq_comp_2_4G[1] = 0x01000000;
+ }
+
+ RWNX_DBG("Karst TX IQ compensation for path 1 on 2.4GHz is: %08X\n",
+ config->karst.tx_iq_comp_2_4G[1]);
+
+ /* Get Karst RX IQ compensation value for path0 on 2.4GHz */
+ tag_ptr =
+ rwnx_find_tag(config_fw->data, config_fw->size,
+ "KARST_RX_IQ_COMP_2_4G_PATH_0=", strlen("00000000"));
+ if (tag_ptr) {
+ if (sscanf(tag_ptr, "%08x", &config->karst.rx_iq_comp_2_4G[0]) != 1)
+ config->karst.rx_iq_comp_2_4G[0] = 0x01000000;
+ } else {
+ config->karst.rx_iq_comp_2_4G[0] = 0x01000000;
+ }
+
+ RWNX_DBG("Karst RX IQ compensation for path 0 on 2.4GHz is: %08X\n",
+ config->karst.rx_iq_comp_2_4G[0]);
+
+ /* Get Karst RX IQ compensation value for path1 on 2.4GHz */
+ tag_ptr =
+ rwnx_find_tag(config_fw->data, config_fw->size,
+ "KARST_RX_IQ_COMP_2_4G_PATH_1=", strlen("00000000"));
+ if (tag_ptr) {
+ if (sscanf(tag_ptr, "%08x", &config->karst.rx_iq_comp_2_4G[1]) != 1)
+ config->karst.rx_iq_comp_2_4G[1] = 0x01000000;
+ } else {
+ config->karst.rx_iq_comp_2_4G[1] = 0x01000000;
+ }
+
+ RWNX_DBG("Karst RX IQ compensation for path 1 on 2.4GHz is: %08X\n",
+ config->karst.rx_iq_comp_2_4G[1]);
+
+ /* Get Karst TX IQ compensation value for path0 on 5GHz */
+ tag_ptr = rwnx_find_tag(config_fw->data, config_fw->size,
+ "KARST_TX_IQ_COMP_5G_PATH_0=", strlen("00000000"));
+ if (tag_ptr) {
+ if (sscanf(tag_ptr, "%08x", &config->karst.tx_iq_comp_5G[0]) != 1)
+ config->karst.tx_iq_comp_5G[0] = 0x01000000;
+ } else {
+ config->karst.tx_iq_comp_5G[0] = 0x01000000;
+ }
+
+ RWNX_DBG("Karst TX IQ compensation for path 0 on 5GHz is: %08X\n",
+ config->karst.tx_iq_comp_5G[0]);
+
+ /* Get Karst TX IQ compensation value for path1 on 5GHz */
+ tag_ptr = rwnx_find_tag(config_fw->data, config_fw->size,
+ "KARST_TX_IQ_COMP_5G_PATH_1=", strlen("00000000"));
+ if (tag_ptr) {
+ if (sscanf(tag_ptr, "%08x", &config->karst.tx_iq_comp_5G[1]) != 1)
+ config->karst.tx_iq_comp_5G[1] = 0x01000000;
+ } else {
+ config->karst.tx_iq_comp_5G[1] = 0x01000000;
+ }
+
+ RWNX_DBG("Karst TX IQ compensation for path 1 on 5GHz is: %08X\n",
+ config->karst.tx_iq_comp_5G[1]);
+
+ /* Get Karst RX IQ compensation value for path0 on 5GHz */
+ tag_ptr = rwnx_find_tag(config_fw->data, config_fw->size,
+ "KARST_RX_IQ_COMP_5G_PATH_0=", strlen("00000000"));
+ if (tag_ptr) {
+ if (sscanf(tag_ptr, "%08x", &config->karst.rx_iq_comp_5G[0]) != 1)
+ config->karst.rx_iq_comp_5G[0] = 0x01000000;
+ } else {
+ config->karst.rx_iq_comp_5G[0] = 0x01000000;
+ }
+
+ RWNX_DBG("Karst RX IQ compensation for path 0 on 5GHz is: %08X\n",
+ config->karst.rx_iq_comp_5G[0]);
+
+ /* Get Karst RX IQ compensation value for path1 on 5GHz */
+ tag_ptr = rwnx_find_tag(config_fw->data, config_fw->size,
+ "KARST_RX_IQ_COMP_5G_PATH_1=", strlen("00000000"));
+ if (tag_ptr) {
+ if (sscanf(tag_ptr, "%08x", &config->karst.rx_iq_comp_5G[1]) != 1)
+ config->karst.rx_iq_comp_5G[1] = 0x01000000;
+ } else {
+ config->karst.rx_iq_comp_5G[1] = 0x01000000;
+ }
+
+ RWNX_DBG("Karst RX IQ compensation for path 1 on 5GHz is: %08X\n",
+ config->karst.rx_iq_comp_5G[1]);
+
+ /* Get Karst default path */
+ tag_ptr = rwnx_find_tag(config_fw->data, config_fw->size,
+ "KARST_DEFAULT_PATH=", strlen("00"));
+ if (tag_ptr) {
+ u8 val;
+
+ if (kstrtou8(tag_ptr, 16, &val) == 0)
+ config->karst.path_used = val;
+ else
+ config->karst.path_used = path;
+ } else {
+ config->karst.path_used = path;
+ }
+
+ RWNX_DBG("Karst default path is: %d\n", config->karst.path_used);
+
+ /* Release the configuration file */
+ release_firmware(config_fw);
+
+ return 0;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_cfgfile.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_cfgfile.h
new file mode 100644
index 0000000000000..d76179316fe2a
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_cfgfile.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_CFGFILE_H_
+#define _RWNX_CFGFILE_H_
+
+#include "lmac_msg.h"
+#include "rwnx_defs.h"
+#include <linux/firmware.h>
+#include <linux/if_ether.h>
+
+/*
+ * Structure used to retrieve information from the Config file used at
+ * Initialization time
+ */
+struct rwnx_conf_file {
+ u8 mac_addr[ETH_ALEN];
+};
+
+/*
+ * Structure used to retrieve information from the PHY Config file used at
+ * Initialization time
+ */
+struct rwnx_phy_conf_file {
+ struct phy_trd_cfg_tag trd;
+ struct phy_karst_cfg_tag karst;
+};
+
+int rwnx_parse_configfile(struct rwnx_hw *rwnx_hw, const char *filename,
+ struct rwnx_conf_file *config);
+
+int rwnx_parse_phy_configfile(struct rwnx_hw *rwnx_hw, const char *filename,
+ struct rwnx_phy_conf_file *config, int path);
+
+#endif /* _RWNX_CFGFILE_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_main.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_main.c
new file mode 100644
index 0000000000000..42708d549c3d8
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_main.c
@@ -0,0 +1,5439 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief Entry point of the driver
+ *
+ ******************************************************************************
+ */
+
+#include "hal_desc.h"
+#include "reg_access.h"
+#include "rwnx_cfgfile.h"
+#include "rwnx_defs.h"
+#include "rwnx_irqs.h"
+#include "rwnx_msg_tx.h"
+#include "rwnx_radar.h"
+#include <linux/ctype.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <linux/inetdevice.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/pci.h>
+#include <linux/pm.h>
+#include <linux/random.h>
+#include <linux/vmalloc.h>
+#include <generated/utsrelease.h>
+#include <net/cfg80211.h>
+#include <net/ip.h>
+#include <net/netlink.h>
+#ifdef CONFIG_RWNX_BFMER
+#include "rwnx_bfmer.h"
+#endif //(CONFIG_RWNX_BFMER)
+#include "aicwf_txrxif.h"
+#include "rwnx_compat.h"
+#include "rwnx_events.h"
+#include "rwnx_main.h"
+#include "rwnx_tdls.h"
+#ifdef AICWF_SDIO_SUPPORT
+#include "aicwf_sdio.h"
+#endif
+#include "aic_bsp_export.h"
+#include "aicwf_compat_8800d80.h"
+#include "aicwf_compat_8800dc.h"
+#include "aicwf_genl.h"
+#include "aicwf_chip_ops.h"
+#include "aicwf_txq_prealloc.h"
+
+
+#define RW_DRV_DESCRIPTION "RivieraWaves 11nac driver for Linux cfg80211"
+#define RW_DRV_COPYRIGHT "Copyright(c) 2015-2017 RivieraWaves"
+#define RW_DRV_AUTHOR "RivieraWaves S.A.S"
+
+#define RWNX_PRINT_CFM_ERR(req) \
+ AICWFDBG(LOGDEBUG, KERN_CRIT "AICWF %s: Status Error(%d)\n", #req, (&req##_cfm)->status)
+
+#define RWNX_HT_CAPABILITIES \
+ { \
+ .ht_supported = true, \
+ .cap = 0, \
+ .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K, \
+ .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16, \
+ .mcs = \
+ { \
+ .rx_mask = \
+ { \
+ 0xff, \
+ 0, \
+ 0, \
+ 0, \
+ 0, \
+ 0, \
+ 0, \
+ 0, \
+ 0, \
+ 0, \
+ }, \
+ .rx_highest = cpu_to_le16(65), \
+ .tx_params = IEEE80211_HT_MCS_TX_DEFINED, \
+ }, \
+ }
+
+#define RWNX_VHT_CAPABILITIES \
+ { \
+ .vht_supported = false, \
+ .cap = (7 << IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT), \
+ .vht_mcs = { \
+ .rx_mcs_map = cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 | \
+ IEEE80211_VHT_MCS_NOT_SUPPORTED << 2 | \
+ IEEE80211_VHT_MCS_NOT_SUPPORTED << 4 | \
+ IEEE80211_VHT_MCS_NOT_SUPPORTED << 6 | \
+ IEEE80211_VHT_MCS_NOT_SUPPORTED << 8 | \
+ IEEE80211_VHT_MCS_NOT_SUPPORTED << 10 | \
+ IEEE80211_VHT_MCS_NOT_SUPPORTED << 12 | \
+ IEEE80211_VHT_MCS_NOT_SUPPORTED << 14), \
+ .tx_mcs_map = cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 | \
+ IEEE80211_VHT_MCS_NOT_SUPPORTED << 2 | \
+ IEEE80211_VHT_MCS_NOT_SUPPORTED << 4 | \
+ IEEE80211_VHT_MCS_NOT_SUPPORTED << 6 | \
+ IEEE80211_VHT_MCS_NOT_SUPPORTED << 8 | \
+ IEEE80211_VHT_MCS_NOT_SUPPORTED << 10 | \
+ IEEE80211_VHT_MCS_NOT_SUPPORTED << 12 | \
+ IEEE80211_VHT_MCS_NOT_SUPPORTED << 14), \
+ } \
+ }
+
+#define RWNX_HE_CAPABILITIES \
+ { \
+ .has_he = false, \
+ .he_cap_elem = { \
+ .mac_cap_info[0] = 0, \
+ .mac_cap_info[1] = 0, \
+ .mac_cap_info[2] = 0, \
+ .mac_cap_info[3] = 0, \
+ .mac_cap_info[4] = 0, \
+ .mac_cap_info[5] = 0, \
+ .phy_cap_info[0] = 0, \
+ .phy_cap_info[1] = 0, \
+ .phy_cap_info[2] = 0, \
+ .phy_cap_info[3] = 0, \
+ .phy_cap_info[4] = 0, \
+ .phy_cap_info[5] = 0, \
+ .phy_cap_info[6] = 0, \
+ .phy_cap_info[7] = 0, \
+ .phy_cap_info[8] = 0, \
+ .phy_cap_info[9] = 0, \
+ .phy_cap_info[10] = 0, \
+ }, \
+ .he_mcs_nss_supp = { \
+ .rx_mcs_80 = cpu_to_le16(0xfffa), \
+ .tx_mcs_80 = cpu_to_le16(0xfffa), \
+ .rx_mcs_160 = cpu_to_le16(0xffff), \
+ .tx_mcs_160 = cpu_to_le16(0xffff), \
+ .rx_mcs_80p80 = cpu_to_le16(0xffff), \
+ .tx_mcs_80p80 = cpu_to_le16(0xffff), \
+ }, \
+ .ppe_thres = {0x08, 0x1c, 0x07}, \
+ }
+
+#define RATE(_bitrate, _hw_rate, _flags) \
+ { \
+ .bitrate = (_bitrate), \
+ .flags = (_flags), \
+ .hw_value = (_hw_rate), \
+ }
+
+#define CHAN(_freq) \
+ { \
+ .center_freq = (_freq), .max_power = 30, /* FIXME */ \
+ }
+
+static struct ieee80211_rate rwnx_ratetable[] = {
+ RATE(10, 0x00, 0),
+ RATE(20, 0x01, IEEE80211_RATE_SHORT_PREAMBLE),
+ RATE(55, 0x02, IEEE80211_RATE_SHORT_PREAMBLE),
+ RATE(110, 0x03, IEEE80211_RATE_SHORT_PREAMBLE),
+ RATE(60, 0x04, 0),
+ RATE(90, 0x05, 0),
+ RATE(120, 0x06, 0),
+ RATE(180, 0x07, 0),
+ RATE(240, 0x08, 0),
+ RATE(360, 0x09, 0),
+ RATE(480, 0x0A, 0),
+ RATE(540, 0x0B, 0),
+};
+
+/* The channels indexes here are not used anymore */
+static struct ieee80211_channel rwnx_2ghz_channels[] = {
+ CHAN(2412),
+ CHAN(2417),
+ CHAN(2422),
+ CHAN(2427),
+ CHAN(2432),
+ CHAN(2437),
+ CHAN(2442),
+ CHAN(2447),
+ CHAN(2452),
+ CHAN(2457),
+ CHAN(2462),
+ CHAN(2467),
+ CHAN(2472),
+ CHAN(2484),
+ // Extra channels defined only to be used for PHY measures.
+ // Enabled only if custregd and custchan parameters are set
+ CHAN(2390),
+ CHAN(2400),
+ CHAN(2410),
+ CHAN(2420),
+ CHAN(2430),
+ CHAN(2440),
+ CHAN(2450),
+ CHAN(2460),
+ CHAN(2470),
+ CHAN(2480),
+ CHAN(2490),
+ CHAN(2500),
+ CHAN(2510),
+};
+
+static struct ieee80211_channel rwnx_5ghz_channels[] = {
+ CHAN(5180), // 36 - 20MHz
+ CHAN(5200), // 40 - 20MHz
+ CHAN(5220), // 44 - 20MHz
+ CHAN(5240), // 48 - 20MHz
+ CHAN(5260), // 52 - 20MHz
+ CHAN(5280), // 56 - 20MHz
+ CHAN(5300), // 60 - 20MHz
+ CHAN(5320), // 64 - 20MHz
+ CHAN(5500), // 100 - 20MHz
+ CHAN(5520), // 104 - 20MHz
+ CHAN(5540), // 108 - 20MHz
+ CHAN(5560), // 112 - 20MHz
+ CHAN(5580), // 116 - 20MHz
+ CHAN(5600), // 120 - 20MHz
+ CHAN(5620), // 124 - 20MHz
+ CHAN(5640), // 128 - 20MHz
+ CHAN(5660), // 132 - 20MHz
+ CHAN(5680), // 136 - 20MHz
+ CHAN(5700), // 140 - 20MHz
+ CHAN(5720), // 144 - 20MHz
+ CHAN(5745), // 149 - 20MHz
+ CHAN(5765), // 153 - 20MHz
+ CHAN(5785), // 157 - 20MHz
+ CHAN(5805), // 161 - 20MHz
+ CHAN(5825), // 165 - 20MHz
+ // Extra channels defined only to be used for PHY measures.
+ // Enabled only if custregd and custchan parameters are set
+ CHAN(5190),
+ CHAN(5210),
+ CHAN(5230),
+ CHAN(5250),
+ CHAN(5270),
+ CHAN(5290),
+ CHAN(5310),
+ CHAN(5330),
+ CHAN(5340),
+ CHAN(5350),
+ CHAN(5360),
+ CHAN(5370),
+ CHAN(5380),
+ CHAN(5390),
+ CHAN(5400),
+ CHAN(5410),
+ CHAN(5420),
+ CHAN(5430),
+ CHAN(5440),
+ CHAN(5450),
+ CHAN(5460),
+ CHAN(5470),
+ CHAN(5480),
+ CHAN(5490),
+ CHAN(5510),
+ CHAN(5530),
+ CHAN(5550),
+ CHAN(5570),
+ CHAN(5590),
+ CHAN(5610),
+ CHAN(5630),
+ CHAN(5650),
+ CHAN(5670),
+ CHAN(5690),
+ CHAN(5710),
+ CHAN(5730),
+ CHAN(5750),
+ CHAN(5760),
+ CHAN(5770),
+ CHAN(5780),
+ CHAN(5790),
+ CHAN(5800),
+ CHAN(5810),
+ CHAN(5820),
+ CHAN(5830),
+ CHAN(5840),
+ CHAN(5850),
+ CHAN(5860),
+ CHAN(5870),
+ CHAN(5880),
+ CHAN(5890),
+ CHAN(5900),
+ CHAN(5910),
+ CHAN(5920),
+ CHAN(5930),
+ CHAN(5940),
+ CHAN(5950),
+ CHAN(5960),
+ CHAN(5970),
+};
+
+struct ieee80211_sband_iftype_data rwnx_he_capa[1] = {
+ {
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ .types_mask = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP),
+#else
+ .types_mask = BIT(NL80211_IFTYPE_STATION),
+#endif
+ .he_cap = RWNX_HE_CAPABILITIES,
+ },
+};
+
+static struct ieee80211_supported_band rwnx_band_2ghz = {
+ .channels = rwnx_2ghz_channels,
+ .n_channels =
+ ARRAY_SIZE(rwnx_2ghz_channels) - 13, // -13 to exclude extra channels
+ .bitrates = rwnx_ratetable,
+ .n_bitrates = ARRAY_SIZE(rwnx_ratetable),
+ .ht_cap = RWNX_HT_CAPABILITIES,
+ .vht_cap = RWNX_VHT_CAPABILITIES,
+};
+
+static struct ieee80211_supported_band rwnx_band_5ghz = {
+ .channels = rwnx_5ghz_channels,
+ .n_channels =
+ ARRAY_SIZE(rwnx_5ghz_channels) - 59, // -59 to exclude extra channels
+ .bitrates = &rwnx_ratetable[4],
+ .n_bitrates = ARRAY_SIZE(rwnx_ratetable) - 4,
+ .ht_cap = RWNX_HT_CAPABILITIES,
+ .vht_cap = RWNX_VHT_CAPABILITIES,
+};
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+static struct ieee80211_iface_limit rwnx_limits[] = {
+ {.max = 1, .types = BIT(NL80211_IFTYPE_STATION)},
+ {.max = 1, .types = BIT(NL80211_IFTYPE_AP)},
+#ifdef CONFIG_AIC8800_USE_P2P0
+ {.max = 2,
+#else
+ {.max = 1,
+#endif
+ .types = BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO)},
+#ifndef CONFIG_AIC8800_USE_P2P0
+ {
+ .max = 1,
+ .types = BIT(NL80211_IFTYPE_P2P_DEVICE),
+ }
+#endif
+};
+
+static struct ieee80211_iface_limit rwnx_limits_dfs[] = {
+ {.max = NX_VIRT_DEV_MAX, .types = BIT(NL80211_IFTYPE_AP)}};
+
+static const struct ieee80211_iface_combination rwnx_combinations[] = {
+ {
+ .limits = rwnx_limits,
+ .n_limits = ARRAY_SIZE(rwnx_limits),
+#ifdef CONFIG_AIC8800_MCC
+ .num_different_channels = NX_CHAN_CTXT_CNT,
+#else
+ .num_different_channels = 1,
+#endif
+ .max_interfaces = NX_VIRT_DEV_MAX,
+ },
+ /* Keep this combination as the last one */
+ {
+ .limits = rwnx_limits_dfs,
+ .n_limits = ARRAY_SIZE(rwnx_limits_dfs),
+ .num_different_channels = 1,
+ .max_interfaces = NX_VIRT_DEV_MAX,
+ .radar_detect_widths =
+ (BIT(NL80211_CHAN_WIDTH_20_NOHT) | BIT(NL80211_CHAN_WIDTH_20) |
+ BIT(NL80211_CHAN_WIDTH_40) | BIT(NL80211_CHAN_WIDTH_80)),
+ }};
+#endif
+
+/* There isn't a lot of sense in it, but you can transmit anything you like */
+static struct ieee80211_txrx_stypes
+ rwnx_default_mgmt_stypes[NUM_NL80211_IFTYPES] = {
+ [NL80211_IFTYPE_STATION] = {
+ .tx = 0xffff,
+ .rx = (BIT(IEEE80211_STYPE_ACTION >> 4) |
+ BIT(IEEE80211_STYPE_PROBE_REQ >> 4) |
+ BIT(IEEE80211_STYPE_AUTH >> 4)),
+ },
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ [NL80211_IFTYPE_AP] = {
+ .tx = 0xffff,
+ .rx = (BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) |
+ BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) |
+ BIT(IEEE80211_STYPE_PROBE_REQ >> 4) |
+ BIT(IEEE80211_STYPE_DISASSOC >> 4) |
+ BIT(IEEE80211_STYPE_AUTH >> 4) |
+ BIT(IEEE80211_STYPE_DEAUTH >> 4) |
+ BIT(IEEE80211_STYPE_ACTION >> 4)),
+ },
+ [NL80211_IFTYPE_AP_VLAN] = {
+ /* copy AP */
+ .tx = 0xffff,
+ .rx = (BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) |
+ BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) |
+ BIT(IEEE80211_STYPE_PROBE_REQ >> 4) |
+ BIT(IEEE80211_STYPE_DISASSOC >> 4) |
+ BIT(IEEE80211_STYPE_AUTH >> 4) |
+ BIT(IEEE80211_STYPE_DEAUTH >> 4) |
+ BIT(IEEE80211_STYPE_ACTION >> 4)),
+ },
+ [NL80211_IFTYPE_P2P_CLIENT] = {
+ .tx = 0xffff,
+ .rx = (BIT(IEEE80211_STYPE_ACTION >> 4) |
+ BIT(IEEE80211_STYPE_PROBE_REQ >> 4)),
+ },
+ [NL80211_IFTYPE_P2P_GO] = {
+ .tx = 0xffff,
+ .rx = (BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) |
+ BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) |
+ BIT(IEEE80211_STYPE_PROBE_REQ >> 4) |
+ BIT(IEEE80211_STYPE_DISASSOC >> 4) |
+ BIT(IEEE80211_STYPE_AUTH >> 4) |
+ BIT(IEEE80211_STYPE_DEAUTH >> 4) |
+ BIT(IEEE80211_STYPE_ACTION >> 4)),
+ },
+ [NL80211_IFTYPE_P2P_DEVICE] = {
+ .tx = 0xffff,
+ .rx = (BIT(IEEE80211_STYPE_ACTION >> 4) |
+ BIT(IEEE80211_STYPE_PROBE_REQ >> 4)),
+ },
+ [NL80211_IFTYPE_MESH_POINT] = {
+ .tx = 0xffff,
+ .rx = (BIT(IEEE80211_STYPE_ACTION >> 4) |
+ BIT(IEEE80211_STYPE_AUTH >> 4) |
+ BIT(IEEE80211_STYPE_DEAUTH >> 4)),
+ },
+#endif
+};
+
+static u32 cipher_suites[] = {
+ WLAN_CIPHER_SUITE_WEP40,
+ WLAN_CIPHER_SUITE_WEP104,
+ WLAN_CIPHER_SUITE_TKIP,
+ WLAN_CIPHER_SUITE_CCMP,
+ WLAN_CIPHER_SUITE_AES_CMAC, // reserved entries to enable AES-CMAC and/or
+ // SMS4
+ WLAN_CIPHER_SUITE_SMS4,
+ 0,
+};
+
+#define NB_RESERVED_CIPHER 1
+
+static const int rwnx_ac2hwq[1][NL80211_NUM_ACS] = {
+ {[NL80211_TXQ_Q_VO] = RWNX_HWQ_VO,
+ [NL80211_TXQ_Q_VI] = RWNX_HWQ_VI,
+ [NL80211_TXQ_Q_BE] = RWNX_HWQ_BE,
+ [NL80211_TXQ_Q_BK] = RWNX_HWQ_BK}};
+
+const int rwnx_tid2hwq[IEEE80211_NUM_TIDS] = {
+ RWNX_HWQ_BE,
+ RWNX_HWQ_BK,
+ RWNX_HWQ_BK,
+ RWNX_HWQ_BE,
+ RWNX_HWQ_VI,
+ RWNX_HWQ_VI,
+ RWNX_HWQ_VO,
+ RWNX_HWQ_VO,
+ /* TID_8 is used for management frames */
+ RWNX_HWQ_VO,
+ /* At the moment, all others TID are mapped to BE */
+ RWNX_HWQ_BE,
+ RWNX_HWQ_BE,
+ RWNX_HWQ_BE,
+ RWNX_HWQ_BE,
+ RWNX_HWQ_BE,
+ RWNX_HWQ_BE,
+ RWNX_HWQ_BE,
+};
+
+static const int rwnx_hwq2uapsd[NL80211_NUM_ACS] = {
+ [RWNX_HWQ_VO] = IEEE80211_WMM_IE_STA_QOSINFO_AC_VO,
+ [RWNX_HWQ_VI] = IEEE80211_WMM_IE_STA_QOSINFO_AC_VI,
+ [RWNX_HWQ_BE] = IEEE80211_WMM_IE_STA_QOSINFO_AC_BE,
+ [RWNX_HWQ_BK] = IEEE80211_WMM_IE_STA_QOSINFO_AC_BK,
+};
+
+#define P2P_ALIVE_TIME_MS (1 * 1000)
+#define P2P_ALIVE_TIME_COUNT 200
+
+int aicwf_dbg_level = LOGERROR; // | LOGINFO; | LOGDEBUG | LOGTRACE;
+module_param(aicwf_dbg_level, int, 0660);
+
+u8 chip_sub_id;
+u8 chip_mcu_id;
+u8 chip_id;
+
+/*********************************************************************
+ * helper
+ *********************************************************************/
+struct rwnx_sta *rwnx_get_sta(struct rwnx_hw *rwnx_hw, const u8 *mac_addr)
+{
+ int i;
+
+ for (i = 0; i < NX_REMOTE_STA_MAX; i++) {
+ struct rwnx_sta *sta = &rwnx_hw->sta_table[i];
+
+ if (sta->valid && memcmp(mac_addr, &sta->mac_addr, 6) == 0)
+ return sta;
+ }
+ return NULL;
+}
+
+void rwnx_enable_wapi(struct rwnx_hw *rwnx_hw)
+{
+ rwnx_hw->wiphy->n_cipher_suites++;
+ rwnx_hw->wiphy->flags |= WIPHY_FLAG_CONTROL_PORT_PROTOCOL;
+}
+
+void rwnx_enable_mfp(struct rwnx_hw *rwnx_hw)
+{
+ cipher_suites[rwnx_hw->wiphy->n_cipher_suites] = WLAN_CIPHER_SUITE_AES_CMAC;
+ rwnx_hw->wiphy->n_cipher_suites++;
+}
+
+u8 *rwnx_build_bcn(struct rwnx_bcn *bcn, struct cfg80211_beacon_data *new)
+{
+ u8 *buf, *pos;
+
+ if (new->head) {
+ u8 *head = kmalloc(new->head_len, GFP_KERNEL);
+
+ if (!head)
+ return NULL;
+
+ kfree(bcn->head);
+
+ bcn->head = head;
+ bcn->head_len = new->head_len;
+ memcpy(bcn->head, new->head, new->head_len);
+ }
+ if (new->tail) {
+ u8 *tail = kmalloc(new->tail_len, GFP_KERNEL);
+
+ if (!tail)
+ return NULL;
+
+ kfree(bcn->tail);
+
+ bcn->tail = tail;
+ bcn->tail_len = new->tail_len;
+ memcpy(bcn->tail, new->tail, new->tail_len);
+ }
+
+ if (!bcn->head)
+ return NULL;
+
+ bcn->tim_len = 6;
+ bcn->len = bcn->head_len + bcn->tail_len + bcn->ies_len + bcn->tim_len;
+
+ buf = kmalloc(bcn->len, GFP_KERNEL);
+ if (!buf)
+ return NULL;
+
+ // Build the beacon buffer
+ pos = buf;
+ memcpy(pos, bcn->head, bcn->head_len);
+ pos += bcn->head_len;
+ *pos++ = WLAN_EID_TIM;
+ *pos++ = 4;
+ *pos++ = 0;
+ *pos++ = bcn->dtim;
+ *pos++ = 0;
+ *pos++ = 0;
+
+ if (bcn->tail) {
+ memcpy(pos, bcn->tail, bcn->tail_len);
+ pos += bcn->tail_len;
+ }
+ if (bcn->ies)
+ memcpy(pos, bcn->ies, bcn->ies_len);
+
+ return buf;
+}
+
+static void rwnx_del_bcn(struct rwnx_bcn *bcn)
+{
+ kfree(bcn->head);
+ bcn->head = NULL;
+ bcn->head_len = 0;
+
+ kfree(bcn->tail);
+ bcn->tail = NULL;
+
+ bcn->tail_len = 0;
+
+ kfree(bcn->ies);
+ bcn->ies = NULL;
+ bcn->ies_len = 0;
+ bcn->tim_len = 0;
+ bcn->dtim = 0;
+ bcn->len = 0;
+}
+
+/*
+ * Link channel ctxt to a vif and thus increments count for this context.
+ */
+void rwnx_chanctx_link(struct rwnx_vif *vif, u8 ch_idx,
+ struct cfg80211_chan_def *chandef)
+{
+ struct rwnx_chanctx *ctxt;
+
+ if (ch_idx >= NX_CHAN_CTXT_CNT) {
+ WARN(1, "Invalid channel ctxt id %d", ch_idx);
+ return;
+ }
+
+ vif->ch_index = ch_idx;
+ ctxt = &vif->rwnx_hw->chanctx_table[ch_idx];
+ ctxt->count++;
+
+ // For now chandef is NULL for STATION interface
+ if (chandef && !ctxt->chan_def.chan)
+ ctxt->chan_def = *chandef;
+}
+
+/*
+ * Unlink channel ctxt from a vif and thus decrements count for this context
+ */
+void rwnx_chanctx_unlink(struct rwnx_vif *vif)
+{
+ struct rwnx_chanctx *ctxt;
+
+ if (vif->ch_index == RWNX_CH_NOT_SET)
+ return;
+
+ ctxt = &vif->rwnx_hw->chanctx_table[vif->ch_index];
+
+ if (ctxt->count == 0)
+ WARN(1, "Chan ctxt ref count is already 0");
+ else
+ ctxt->count--;
+
+ if (ctxt->count == 0) {
+ if (vif->ch_index == vif->rwnx_hw->cur_chanctx) {
+ /* If current chan ctxt is no longer linked to a vif
+ * disable radar detection (no need to check if it was activated)
+ */
+ rwnx_radar_detection_enable(&vif->rwnx_hw->radar,
+ RWNX_RADAR_DETECT_DISABLE,
+ RWNX_RADAR_RIU);
+ }
+ /* set chan to null, so that if this ctxt is relinked to a vif that
+ * don't have channel information, don't use wrong information
+ */
+ ctxt->chan_def.chan = NULL;
+ }
+ vif->ch_index = RWNX_CH_NOT_SET;
+}
+
+int rwnx_chanctx_valid(struct rwnx_hw *rwnx_hw, u8 ch_idx)
+{
+ if (ch_idx >= NX_CHAN_CTXT_CNT ||
+ !rwnx_hw->chanctx_table[ch_idx].chan_def.chan) {
+ return 0;
+ }
+
+ return 1;
+}
+
+static void rwnx_del_csa(struct rwnx_vif *vif)
+{
+ struct rwnx_csa *csa = vif->ap.csa;
+
+ if (!csa)
+ return;
+
+ rwnx_del_bcn(&csa->bcn);
+ kfree(csa);
+ vif->ap.csa = NULL;
+}
+
+static void rwnx_csa_finish(struct work_struct *ws)
+{
+ struct rwnx_csa *csa = container_of(ws, struct rwnx_csa, work);
+ struct rwnx_vif *vif = csa->vif;
+ struct rwnx_hw *rwnx_hw = vif->rwnx_hw;
+ int error = csa->status;
+ u8 *buf, *pos;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ buf = kmalloc(csa->bcn.len, GFP_KERNEL);
+ if (!buf) {
+ AICWFDBG(LOGDEBUG, "AICWF %s buf fail\n", __func__);
+ return;
+ }
+ pos = buf;
+
+ memcpy(pos, csa->bcn.head, csa->bcn.head_len);
+ pos += csa->bcn.head_len;
+ *pos++ = WLAN_EID_TIM;
+ *pos++ = 4;
+ *pos++ = 0;
+ *pos++ = csa->bcn.dtim;
+ *pos++ = 0;
+ *pos++ = 0;
+ if (csa->bcn.tail) {
+ memcpy(pos, csa->bcn.tail, csa->bcn.tail_len);
+ pos += csa->bcn.tail_len;
+ }
+ if (csa->bcn.ies)
+ memcpy(pos, csa->bcn.ies, csa->bcn.ies_len);
+
+ if (!error) {
+ error = rwnx_send_bcn(rwnx_hw, buf, vif->vif_index, csa->bcn.len);
+ if (error)
+ return;
+ error = rwnx_send_bcn_change(rwnx_hw, vif->vif_index, 0, csa->bcn.len,
+ csa->bcn.head_len, csa->bcn.tim_len, NULL);
+ }
+
+ if (error) {
+ cfg80211_stop_iface(rwnx_hw->wiphy, &vif->wdev, GFP_KERNEL);
+ } else {
+ wiphy_lock(rwnx_hw->wiphy);
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ rwnx_chanctx_unlink(vif);
+ rwnx_chanctx_link(vif, csa->ch_idx, &csa->chandef);
+ if (rwnx_hw->cur_chanctx == csa->ch_idx) {
+ rwnx_radar_detection_enable_on_cur_channel(rwnx_hw);
+ rwnx_txq_vif_start(vif, RWNX_TXQ_STOP_CHAN, rwnx_hw);
+ } else {
+ rwnx_txq_vif_stop(vif, RWNX_TXQ_STOP_CHAN, rwnx_hw);
+ }
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+ cfg80211_ch_switch_notify(vif->ndev, &csa->chandef, 0);
+ wiphy_unlock(rwnx_hw->wiphy);
+ }
+
+ rwnx_del_csa(vif);
+}
+
+/**
+ * rwnx_external_auth_enable - Enable external authentication on a vif
+ *
+ * @vif: VIF on which external authentication must be enabled
+ *
+ * External authentication requires to start TXQ for unknown STA in
+ * order to send auth frame pusehd by user space.
+ * Note: It is assumed that fw is on the correct channel.
+ */
+void rwnx_external_auth_enable(struct rwnx_vif *vif)
+{
+ vif->sta.external_auth = true;
+ rwnx_txq_unk_vif_init(vif);
+ rwnx_txq_start(rwnx_txq_vif_get(vif, NX_UNK_TXQ_TYPE), 0);
+}
+
+/**
+ * rwnx_external_auth_disable - Disable external authentication on a vif
+ *
+ * @vif: VIF on which external authentication must be disabled
+ */
+void rwnx_external_auth_disable(struct rwnx_vif *vif)
+{
+ if (!vif->sta.external_auth)
+ return;
+
+ vif->sta.external_auth = false;
+ rwnx_txq_unk_vif_deinit(vif);
+}
+
+/**
+ * rwnx_update_mesh_power_mode - Recompute the power mode of a mesh VIF
+ *
+ * @vif: mesh VIF for which power mode is updated
+ *
+ * Does nothing if vif is not a mesh point interface.
+ * Since firmware doesn't support one power save mode per link select the
+ * most "active" power mode among all mesh links.
+ * Indeed as soon as we have to be active on one link we might as well be
+ * active on all links.
+ *
+ * If there is no link then the power mode for next peer is used;
+ */
+void rwnx_update_mesh_power_mode(struct rwnx_vif *vif)
+{
+ enum nl80211_mesh_power_mode mesh_pm;
+ struct rwnx_sta *sta;
+ struct mesh_config mesh_conf;
+ struct mesh_update_cfm cfm;
+ u32 mask;
+
+ if (RWNX_VIF_TYPE(vif) != NL80211_IFTYPE_MESH_POINT)
+ return;
+
+ if (list_empty(&vif->ap.sta_list)) {
+ mesh_pm = vif->ap.next_mesh_pm;
+ } else {
+ mesh_pm = NL80211_MESH_POWER_DEEP_SLEEP;
+ list_for_each_entry(sta, &vif->ap.sta_list, list) {
+ if (sta->valid && sta->mesh_pm < mesh_pm)
+ mesh_pm = sta->mesh_pm;
+ }
+ }
+
+ if (mesh_pm == vif->ap.mesh_pm)
+ return;
+
+ mask = BIT(NL80211_MESHCONF_POWER_MODE - 1);
+ mesh_conf.power_mode = mesh_pm;
+ if (rwnx_send_mesh_update_req(vif->rwnx_hw, vif, mask, &mesh_conf, &cfm) ||
+ cfm.status)
+ return;
+
+ vif->ap.mesh_pm = mesh_pm;
+}
+
+static const char *const s_conn_state[] = {
+ "RWNX_DRV_STATUS_DISCONNECTED",
+ "RWNX_DRV_STATUS_DISCONNECTING",
+ "RWNX_DRV_STATUS_CONNECTING",
+ "RWNX_DRV_STATUS_CONNECTED",
+ "RWNX_DRV_STATUS_ROAMING",
+};
+
+void rwnx_set_conn_state(atomic_t *drv_conn_state, int state)
+{
+ if ((int)atomic_read(drv_conn_state) != state) {
+ AICWFDBG(LOGDEBUG, "%s drv_conn_state:%p %s --> %s \r\n", __func__,
+ drv_conn_state,
+ s_conn_state[(int)atomic_read(drv_conn_state)],
+ s_conn_state[state]);
+ atomic_set(drv_conn_state, state);
+ }
+}
+
+/*********************************************************************
+ * netdev callbacks
+ ********************************************************************/
+/*
+ * int (*ndo_open)(struct net_device *dev);
+ * This function is called when network device transistions to the up
+ * state.
+ *
+ * - Start FW if this is the first interface opened
+ * - Add interface at fw level
+ */
+static int rwnx_open(struct net_device *dev)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+ struct mm_add_if_cfm add_if_cfm;
+ int error = 0;
+ u8 rwnx_rx_gain = 0x0E;
+ int err = 0;
+ int waiting_counter = 10;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ while (test_bit(RWNX_DEV_STARTED, &rwnx_vif->drv_flags)) {
+ msleep(100);
+ AICWFDBG(LOGDEBUG, "%s waiting for rwnx_close \r\n", __func__);
+ waiting_counter--;
+ if (waiting_counter == 0) {
+ AICWFDBG(LOGERROR, "%s error waiting for close time out \r\n",
+ __func__);
+ break;
+ }
+ }
+
+ // Check if it is the first opened VIF
+ if (rwnx_hw->vif_started == 0) {
+ // Start the FW
+ error = rwnx_send_start(rwnx_hw);
+ if (error)
+ return error;
+
+ error = aic_chip_set_rx_gain(rwnx_hw, rwnx_rx_gain);
+ if (error)
+ return error;
+
+#ifdef CONFIG_AIC8800_COEX
+ if ((!rwnx_hw->chip_ops->limit_by_testmode ||
+ !rwnx_hw->testmode) &&
+ (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION ||
+ RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_CLIENT))
+ rwnx_send_coex_req(rwnx_hw, 0, 1);
+#endif
+ /* Device is now started */
+ }
+#ifdef CONFIG_AIC8800_COEX
+ else if ((RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_AP ||
+ RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_GO)) {
+ rwnx_send_coex_req(rwnx_hw, 1, 0);
+ }
+#endif
+
+ set_bit(RWNX_DEV_STARTED, &rwnx_vif->drv_flags);
+ rwnx_set_conn_state(&rwnx_vif->drv_conn_state, RWNX_DRV_STATUS_DISCONNECTED);
+ AICWFDBG(LOGDEBUG, "%s rwnx_vif->drv_flags:%d\r\n", __func__,
+ (int)rwnx_vif->drv_flags);
+
+ if (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_CLIENT ||
+ RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_GO) {
+ if (!rwnx_hw->is_p2p_alive &&
+ (rwnx_hw->p2p_dev_vif && !rwnx_hw->p2p_dev_vif->up)) {
+ err = rwnx_send_add_if(rwnx_hw,
+ rwnx_hw->p2p_dev_vif->wdev.address,
+ RWNX_VIF_TYPE(rwnx_hw->p2p_dev_vif),
+ false, &add_if_cfm);
+ if (err)
+ return -EIO;
+
+ if (add_if_cfm.status != 0)
+ return -EIO;
+
+ /* Save the index retrieved from LMAC */
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ rwnx_hw->p2p_dev_vif->vif_index = add_if_cfm.inst_nbr;
+ rwnx_hw->p2p_dev_vif->up = true;
+ rwnx_hw->vif_started++;
+ rwnx_hw->vif_table[add_if_cfm.inst_nbr] = rwnx_hw->p2p_dev_vif;
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+ rwnx_hw->is_p2p_alive = 1;
+#ifndef CONFIG_AIC8800_USE_P2P0
+ mod_timer(&rwnx_hw->p2p_alive_timer,
+ jiffies + msecs_to_jiffies(1000));
+ atomic_set(&rwnx_hw->p2p_alive_timer_count, 0);
+#endif
+ }
+ }
+
+ if (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_AP_VLAN) {
+ /* For AP_vlan use same fw and drv indexes. We ensure that this index
+ * will not be used by fw for another vif by taking index >=
+ * NX_VIRT_DEV_MAX
+ */
+ add_if_cfm.inst_nbr = rwnx_vif->drv_vif_index;
+ netif_tx_stop_all_queues(dev);
+
+ /* Save the index retrieved from LMAC */
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ rwnx_vif->vif_index = add_if_cfm.inst_nbr;
+ rwnx_vif->up = true;
+ rwnx_hw->vif_started++;
+ rwnx_hw->vif_table[add_if_cfm.inst_nbr] = rwnx_vif;
+ AICWFDBG(LOGDEBUG, "%s ap create vif in rwnx_hw->vif_table[%d] \r\n",
+ __func__, rwnx_vif->vif_index);
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+ } else {
+ /* Forward the information to the LMAC,
+ * p2p value not used in FMAC configuration, iftype is sufficient
+ */
+ error = rwnx_send_add_if(rwnx_hw, rwnx_vif->wdev.address,
+ RWNX_VIF_TYPE(rwnx_vif), false, &add_if_cfm);
+ if (error) {
+ AICWFDBG(LOGDEBUG, "AICWF add if fail\n");
+ return error;
+ }
+
+ if (add_if_cfm.status != 0) {
+ RWNX_PRINT_CFM_ERR(add_if);
+ return -EIO;
+ }
+
+ /* Save the index retrieved from LMAC */
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ rwnx_vif->vif_index = add_if_cfm.inst_nbr;
+ rwnx_vif->up = true;
+ rwnx_hw->vif_started++;
+ rwnx_hw->vif_table[add_if_cfm.inst_nbr] = rwnx_vif;
+ AICWFDBG(LOGDEBUG, "%s sta create vif in rwnx_hw->vif_table[%d] \r\n",
+ __func__, rwnx_vif->vif_index);
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+
+#ifdef CONFIG_AIC8800_USE_P2P0
+ if (rwnx_vif->is_p2p_vif) {
+ rwnx_hw->p2p_dev_vif = rwnx_vif;
+ rwnx_hw->is_p2p_alive = 1;
+ }
+#endif
+ }
+
+ if (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_MONITOR) {
+ rwnx_hw->monitor_vif = rwnx_vif->vif_index;
+ if (rwnx_vif->ch_index != RWNX_CH_NOT_SET) {
+ // Configure the monitor channel
+ error = rwnx_send_config_monitor_req
+ (rwnx_hw,
+ &rwnx_hw->chanctx_table[rwnx_vif->ch_index].chan_def,
+ NULL);
+ }
+ }
+
+ netif_start_queue(dev);
+
+ return error;
+}
+
+/*
+ * int (*ndo_stop)(struct net_device *dev);
+ * This function is called when network device transistions to the down
+ * state.
+ *
+ * - Remove interface at fw level
+ * - Reset FW if this is the last interface opened
+ */
+static int rwnx_close(struct net_device *dev)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+ int ret;
+ struct aicwf_bus *bus_if = NULL;
+ struct aic_sdio_dev *sdiodev = NULL;
+ int waiting_counter = 20;
+ int test_counter = 0;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ test_counter = waiting_counter;
+ while (atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_DISCONNECTING ||
+ atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_CONNECTING) {
+ AICWFDBG(LOGDEBUG,
+ "%s wifi is connecting or disconnecting, waiting 200ms for state to stable\r\n",
+ __func__);
+ msleep(200);
+ test_counter--;
+ if (test_counter == 0) {
+ AICWFDBG(LOGERROR, "%s connecting or disconnecting, not finish\r\n",
+ __func__);
+ //WARN_ON(1);
+ break;
+ }
+ }
+
+#if defined(AICWF_SDIO_SUPPORT)
+ if (rwnx_hw->scanning)
+ rwnx_hw->scanning = false;
+#endif
+
+ netdev_info(dev, "CLOSE");
+
+ rwnx_radar_cancel_cac(&rwnx_hw->radar);
+
+ /* Abort scan request on the vif */
+ if (rwnx_hw->scan_request &&
+ rwnx_hw->scan_request->wdev == &rwnx_vif->wdev) {
+ struct cfg80211_scan_info info = {
+ .aborted = true,
+ };
+
+ cfg80211_scan_done(rwnx_hw->scan_request, &info);
+ aicwf_sdio_scan_activity_put(rwnx_hw);
+ rwnx_hw->scan_request = NULL;
+
+ ret = rwnx_send_scanu_cancel_req(rwnx_hw, NULL);
+ mdelay(35); // make sure firmware take affect
+ if (ret) {
+ AICWFDBG(LOGDEBUG, "AICWF scanu_cancel fail\n");
+ return ret;
+ }
+ }
+
+ if (rwnx_hw->roc_elem && rwnx_hw->roc_elem->wdev == &rwnx_vif->wdev) {
+ AICWFDBG(LOGDEBUG, KERN_CRIT "AICWF %s clear roc\n", __func__);
+ /* Initialize RoC element pointer to NULL, indicate that RoC can be
+ * started
+ */
+ kfree(rwnx_hw->roc_elem);
+ rwnx_hw->roc_elem = NULL;
+ }
+
+ rwnx_vif->up = false;
+ AICWFDBG(LOGDEBUG, "%s rwnx_vif[%d] down \r\n", __func__,
+ rwnx_vif->vif_index);
+
+ if (netif_carrier_ok(dev)) {
+ if (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION ||
+ RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_CLIENT) {
+ cfg80211_disconnected(dev, WLAN_REASON_DEAUTH_LEAVING, NULL, 0,
+ true, GFP_ATOMIC);
+ netif_tx_stop_all_queues(dev);
+ netif_carrier_off(dev);
+ usleep_range(1000, 2000);
+ } else if (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_AP_VLAN) {
+ netif_carrier_off(dev);
+ } else {
+ netdev_warn(dev, "AP not stopped when disabling interface");
+ }
+
+ }
+
+#if defined(AICWF_SDIO_SUPPORT)
+ bus_if = dev_get_drvdata(rwnx_hw->dev);
+ if (bus_if)
+ sdiodev = bus_if->bus_priv.sdio;
+ if (sdiodev && sdiodev->bus_if->state != BUS_DOWN_ST) {
+ if (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION ||
+ RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_CLIENT) {
+ test_counter = waiting_counter;
+ if (atomic_read(&rwnx_vif->drv_conn_state) ==
+ (int)RWNX_DRV_STATUS_CONNECTED) {
+ rwnx_set_conn_state(&rwnx_vif->drv_conn_state,
+ RWNX_DRV_STATUS_DISCONNECTING);
+ rwnx_send_sm_disconnect_req(rwnx_hw, rwnx_vif, 3);
+ while (atomic_read(&rwnx_vif->drv_conn_state) ==
+ (int)RWNX_DRV_STATUS_DISCONNECTING) {
+ AICWFDBG(LOGDEBUG,
+ "%s wifi is disconnecting, waiting 100ms for state to stable\r\n",
+ __func__);
+ msleep(100);
+ test_counter--;
+ if (test_counter == 0)
+ break;
+ }
+ }
+ }
+#ifdef CONFIG_AIC8800_USE_P2P0
+ if (!rwnx_vif->is_p2p_vif ||
+ (rwnx_vif->is_p2p_vif && rwnx_hw->is_p2p_alive)) {
+ if (rwnx_vif->is_p2p_vif)
+ rwnx_hw->is_p2p_alive = 0;
+#endif
+ rwnx_send_remove_if(rwnx_hw, rwnx_vif->vif_index, false);
+#ifdef CONFIG_AIC8800_USE_P2P0
+ }
+#endif
+ }
+#endif
+ /* Ensure that we won't process disconnect ind */
+ spin_lock_bh(&rwnx_hw->cb_lock);
+
+ rwnx_hw->vif_table[rwnx_vif->vif_index] = NULL;
+
+ rwnx_chanctx_unlink(rwnx_vif);
+
+ if (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_MONITOR)
+ rwnx_hw->monitor_vif = RWNX_INVALID_VIF;
+
+ rwnx_hw->vif_started--;
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+
+ if (rwnx_hw->vif_started == 0) {
+ /* This also lets both ipc sides remain in sync before resetting */
+ if (sdiodev->bus_if->state != BUS_DOWN_ST) {
+#ifdef CONFIG_AIC8800_COEX
+ if (rwnx_hw->testmode == 0)
+ rwnx_send_coex_req(rwnx_hw, 1, 0);
+#endif
+ rwnx_send_reset(rwnx_hw);
+ // Set parameters to firmware
+ if (rwnx_hw->testmode == 0) {
+ rwnx_send_me_config_req(rwnx_hw);
+ // Set channel parameters to firmware
+ rwnx_send_me_chan_config_req(rwnx_hw);
+ }
+ }
+ } else {
+#ifdef CONFIG_AIC8800_COEX
+ if (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_AP ||
+ RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_GO)
+ rwnx_send_coex_req(rwnx_hw, 0, 1);
+#endif
+ }
+
+ clear_bit(RWNX_DEV_STARTED, &rwnx_vif->drv_flags);
+ AICWFDBG(LOGDEBUG, "%s rwnx_vif->drv_flags:%d\r\n", __func__,
+ (int)rwnx_vif->drv_flags);
+
+ return 0;
+}
+
+/*
+ * struct net_device_stats* (*ndo_get_stats)(struct net_device *dev);
+ * Called when a user wants to get the network device usage
+ * statistics. Drivers must do one of the following:
+ * 1. Define @ndo_get_stats64 to fill in a zero-initialised
+ * rtnl_link_stats64 structure passed by the caller.
+ * 2. Define @ndo_get_stats to update a net_device_stats structure
+ * (which should normally be dev->stats) and return a pointer to
+ * it. The structure may be changed asynchronously only if each
+ * field is written atomically.
+ * 3. Update dev->stats asynchronously and atomically, and define
+ * neither operation.
+ */
+static struct net_device_stats *rwnx_get_stats(struct net_device *dev)
+{
+ struct rwnx_vif *vif = netdev_priv(dev);
+
+ return &vif->net_stats;
+}
+
+/*
+ * u16 (*ndo_select_queue)(struct net_device *dev, struct sk_buff *skb,
+ * struct net_device *sb_dev);
+ * Called to decide which queue to when device supports multiple
+ * transmit queues.
+ */
+
+#ifndef CONFIG_AIC8800_ONE_TXQ
+static u16 rwnx_select_queue(struct net_device *dev,
+ struct sk_buff *skb,
+ struct net_device *sb_dev)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+
+ return rwnx_select_txq(rwnx_vif, skb);
+}
+#endif
+
+/*
+ * int (*ndo_set_mac_address)(struct net_device *dev, void *addr);
+ * This function is called when the Media Access Control address
+ * needs to be changed. If this interface is not defined, the
+ * mac address can not be changed.
+ */
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY) || defined(CONFIG_AIC8800_SUPPORT_REALTIME_CHANGE_MAC)
+static int rwnx_set_mac_address(struct net_device *dev, void *addr)
+{
+ struct sockaddr *sa = addr;
+ int ret = 0;
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+
+ AICWFDBG(LOGTRACE, "%s enter \r\n", __func__);
+
+ ret = eth_mac_addr(dev, sa);
+ AICWFDBG(LOGINFO, "%s set %02X:%02X:%02X:%02X:%02X:%02X\r\n", __func__,
+ dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2],
+ dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);
+ memcpy(rwnx_vif->wdev.address, dev->dev_addr, 6);
+
+ return ret;
+}
+#endif
+
+static const struct net_device_ops rwnx_netdev_ops = {
+ .ndo_open = rwnx_open,
+ .ndo_stop = rwnx_close,
+ .ndo_start_xmit = rwnx_start_xmit,
+ .ndo_get_stats = rwnx_get_stats,
+#ifndef CONFIG_AIC8800_ONE_TXQ
+ .ndo_select_queue = rwnx_select_queue,
+#endif
+#ifdef CONFIG_AIC8800_SUPPORT_REALTIME_CHANGE_MAC
+ .ndo_set_mac_address = rwnx_set_mac_address
+#endif
+ /* unused interface */
+ // .ndo_set_features = rwnx_set_features,
+ // .ndo_set_rx_mode = rwnx_set_multicast_list,
+};
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+static const struct net_device_ops rwnx_netdev_monitor_ops = {
+ .ndo_open = rwnx_open,
+ .ndo_stop = rwnx_close,
+ .ndo_get_stats = rwnx_get_stats,
+ .ndo_set_mac_address = rwnx_set_mac_address,
+};
+#endif
+
+static void rwnx_netdev_setup(struct net_device *dev)
+{
+ ether_setup(dev);
+ dev->priv_flags &= ~IFF_TX_SKB_SHARING;
+ dev->netdev_ops = &rwnx_netdev_ops;
+ dev->needs_free_netdev = true;
+ dev->watchdog_timeo = RWNX_TX_LIFETIME_MS;
+
+ dev->needed_headroom = sizeof(struct rwnx_txhdr) + RWNX_SWTXHDR_ALIGN_SZ;
+#ifdef CONFIG_RWNX_AMSDUS_TX
+ dev->needed_headroom =
+ max(dev->needed_headroom,
+ (unsigned short)(sizeof(struct rwnx_amsdu_txhdr) +
+ sizeof(struct ethhdr) + 4 +
+ sizeof(rfc1042_header) + 2));
+#endif /* CONFIG_RWNX_AMSDUS_TX */
+
+ dev->hw_features = 0;
+}
+
+/*********************************************************************
+ * Cfg80211 callbacks (and helper)
+ *********************************************************************/
+static struct rwnx_vif *rwnx_interface_add(struct rwnx_hw *rwnx_hw,
+ const char *name,
+ unsigned char name_assign_type,
+ enum nl80211_iftype type,
+ struct vif_params *params)
+{
+ struct net_device *ndev;
+ struct rwnx_vif *vif;
+ int min_idx, max_idx;
+ int vif_idx = -1;
+ int i;
+
+ if (IS_ENABLED(CONFIG_AIC8800_STA_ONLY) &&
+ type != NL80211_IFTYPE_STATION)
+ return NULL;
+
+#ifndef CONFIG_AIC8800_ONE_TXQ
+ int nx_nb_ndev_txq = NX_NB_NDEV_TXQ;
+
+ if (g_rwnx_plat->sdiodev->rwnx_hw->chip_ops->is_old_ic)
+ nx_nb_ndev_txq = NX_NB_NDEV_TXQ_FOR_OLD_IC;
+#endif
+
+ AICWFDBG(LOGINFO, "%s: %s, %d\r\n", __func__, name, type);
+ // Look for an available VIF
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ if (type == NL80211_IFTYPE_AP_VLAN) {
+ min_idx = NX_VIRT_DEV_MAX;
+ max_idx = NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX;
+ } else {
+#endif
+ min_idx = 0;
+ max_idx = NX_VIRT_DEV_MAX;
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ }
+#endif
+
+ for (i = min_idx; i < max_idx; i++) {
+ if (rwnx_hw->avail_idx_map & BIT(i)) {
+ vif_idx = i;
+ break;
+ }
+ }
+ if (vif_idx < 0)
+ return NULL;
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY) && !defined(CONFIG_RWNX_MON_DATA)
+ list_for_each_entry(vif, &rwnx_hw->vifs, list) {
+ // Check if monitor interface already exists or type is monitor
+ if (RWNX_VIF_TYPE(vif) == NL80211_IFTYPE_MONITOR ||
+ type == NL80211_IFTYPE_MONITOR) {
+ wiphy_err(rwnx_hw->wiphy,
+ "Monitor+Data interface support (MON_DATA) disabled\n");
+ return NULL;
+ }
+ }
+#endif
+
+#ifndef CONFIG_AIC8800_ONE_TXQ
+ ndev = alloc_netdev_mqs(sizeof(*vif), name, name_assign_type,
+ rwnx_netdev_setup, nx_nb_ndev_txq, 1);
+#else
+ ndev = alloc_netdev_mqs(sizeof(*vif), name, name_assign_type,
+ rwnx_netdev_setup, 1, 1);
+#endif
+
+ if (!ndev)
+ return NULL;
+
+ vif = netdev_priv(ndev);
+ vif->key_has_add = 0;
+ ndev->ieee80211_ptr = &vif->wdev;
+ vif->wdev.wiphy = rwnx_hw->wiphy;
+ vif->rwnx_hw = rwnx_hw;
+ vif->ndev = ndev;
+ vif->drv_vif_index = vif_idx;
+ SET_NETDEV_DEV(ndev, wiphy_dev(vif->wdev.wiphy));
+ vif->wdev.netdev = ndev;
+ vif->wdev.iftype = type;
+ vif->up = false;
+ vif->ch_index = RWNX_CH_NOT_SET;
+ memset(&vif->net_stats, 0, sizeof(vif->net_stats));
+ vif->is_p2p_vif = 0;
+
+ switch (type) {
+ case NL80211_IFTYPE_STATION:
+ vif->sta.ap = NULL;
+ vif->sta.tdls_sta = NULL;
+ vif->sta.external_auth = false;
+ break;
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ case NL80211_IFTYPE_P2P_CLIENT:
+ vif->sta.ap = NULL;
+ vif->sta.tdls_sta = NULL;
+ vif->sta.external_auth = false;
+ vif->is_p2p_vif = 1;
+ break;
+ case NL80211_IFTYPE_MESH_POINT:
+ INIT_LIST_HEAD(&vif->ap.mpath_list);
+ INIT_LIST_HEAD(&vif->ap.proxy_list);
+ vif->ap.create_path = false;
+ vif->ap.generation = 0;
+ vif->ap.mesh_pm = NL80211_MESH_POWER_ACTIVE;
+ vif->ap.next_mesh_pm = NL80211_MESH_POWER_ACTIVE;
+ fallthrough;
+ // no break
+ case NL80211_IFTYPE_AP:
+ INIT_LIST_HEAD(&vif->ap.sta_list);
+ memset(&vif->ap.bcn, 0, sizeof(vif->ap.bcn));
+ break;
+ case NL80211_IFTYPE_P2P_GO:
+ INIT_LIST_HEAD(&vif->ap.sta_list);
+ memset(&vif->ap.bcn, 0, sizeof(vif->ap.bcn));
+ vif->is_p2p_vif = 1;
+ break;
+ case NL80211_IFTYPE_AP_VLAN: {
+ struct rwnx_vif *master_vif;
+ bool found = false;
+
+ list_for_each_entry(master_vif, &rwnx_hw->vifs, list) {
+ if (RWNX_VIF_TYPE(master_vif) == NL80211_IFTYPE_AP &&
+ !(!memcmp(master_vif->ndev->dev_addr, params->macaddr,
+ ETH_ALEN))) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ goto err;
+
+ vif->ap_vlan.master = master_vif;
+ vif->ap_vlan.sta_4a = NULL;
+ break;
+ }
+ case NL80211_IFTYPE_MONITOR:
+ ndev->type = ARPHRD_IEEE80211_RADIOTAP;
+ ndev->netdev_ops = &rwnx_netdev_monitor_ops;
+ break;
+#endif
+ default:
+ break;
+ }
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ if (type == NL80211_IFTYPE_AP_VLAN) {
+ memcpy((void *)ndev->dev_addr, (const void *)params->macaddr, ETH_ALEN);
+ memcpy((void *)vif->wdev.address, (const void *)params->macaddr,
+ ETH_ALEN);
+ } else {
+#endif
+ unsigned char mac_addr[6];
+
+ memcpy(mac_addr, rwnx_hw->wiphy->perm_addr, ETH_ALEN);
+ mac_addr[5] ^= vif_idx;
+ eth_hw_addr_set(ndev, mac_addr);
+ memcpy(vif->wdev.address, mac_addr, ETH_ALEN);
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ }
+#endif
+
+ AICWFDBG(LOGINFO, "interface add:%x %x %x %x %x %x\n", vif->wdev.address[0],
+ vif->wdev.address[1], vif->wdev.address[2], vif->wdev.address[3],
+ vif->wdev.address[4], vif->wdev.address[5]);
+
+ if (params) {
+ vif->use_4addr = params->use_4addr;
+ ndev->ieee80211_ptr->use_4addr = params->use_4addr;
+ } else {
+ vif->use_4addr = false;
+ }
+ if (cfg80211_register_netdevice(ndev))
+ goto err;
+
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ list_add_tail(&vif->list, &rwnx_hw->vifs);
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+ rwnx_hw->avail_idx_map &= ~BIT(vif_idx);
+
+ return vif;
+
+err:
+ free_netdev(ndev);
+ return NULL;
+}
+
+void aicwf_p2p_alive_timeout(struct timer_list *t)
+{
+ struct rwnx_hw *rwnx_hw;
+ struct rwnx_vif *rwnx_vif;
+ struct rwnx_vif *rwnx_vif1, *tmp;
+ u8 p2p = 0;
+
+ //rwnx_hw = from_timer(rwnx_hw, t, p2p_alive_timer);
+ rwnx_hw = timer_container_of(rwnx_hw, t, p2p_alive_timer);
+ rwnx_vif = rwnx_hw->p2p_dev_vif;
+
+ list_for_each_entry_safe(rwnx_vif1, tmp, &rwnx_hw->vifs, list) {
+ if ((rwnx_hw->avail_idx_map & BIT(rwnx_vif1->drv_vif_index)) == 0) {
+ switch (RWNX_VIF_TYPE(rwnx_vif1)) {
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ case NL80211_IFTYPE_P2P_CLIENT:
+ case NL80211_IFTYPE_P2P_GO:
+ rwnx_hw->is_p2p_alive = 1;
+ p2p = 1;
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ }
+
+ if (p2p)
+ atomic_set(&rwnx_hw->p2p_alive_timer_count, 0);
+ else
+ atomic_inc(&rwnx_hw->p2p_alive_timer_count);
+
+ if (atomic_read(&rwnx_hw->p2p_alive_timer_count) < P2P_ALIVE_TIME_COUNT) {
+ mod_timer(&rwnx_hw->p2p_alive_timer,
+ jiffies + msecs_to_jiffies(P2P_ALIVE_TIME_MS));
+ return;
+ }
+ atomic_set(&rwnx_hw->p2p_alive_timer_count, 0);
+
+ rwnx_hw->is_p2p_alive = 0;
+ if (rwnx_vif->up) {
+ rwnx_send_remove_if(rwnx_hw, rwnx_vif->vif_index, true);
+
+ /* Ensure that we won't process disconnect ind */
+ spin_lock_bh(&rwnx_hw->cb_lock);
+
+ rwnx_vif->up = false;
+ rwnx_hw->vif_table[rwnx_vif->vif_index] = NULL;
+ AICWFDBG(LOGDEBUG, "%s rwnx_vif[%d] down \r\n", __func__,
+ rwnx_vif->vif_index);
+ rwnx_hw->vif_started--;
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+ }
+}
+
+/*********************************************************************
+ * Cfg80211 callbacks (and helper)
+ *********************************************************************/
+static struct wireless_dev *
+rwnx_virtual_interface_add(struct rwnx_hw *rwnx_hw, const char *name,
+ unsigned char name_assign_type,
+ enum nl80211_iftype type, struct vif_params *params)
+{
+ struct wireless_dev *wdev = NULL;
+ struct rwnx_vif *vif;
+ int min_idx, max_idx;
+ int vif_idx = -1;
+ int i;
+
+ AICWFDBG(LOGDEBUG, "AICWF %s: %d, %s\n", __func__, type, name);
+
+ if (type == NL80211_IFTYPE_AP_VLAN) {
+ min_idx = NX_VIRT_DEV_MAX;
+ max_idx = NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX;
+ } else {
+ min_idx = 0;
+ max_idx = NX_VIRT_DEV_MAX;
+ }
+
+ for (i = min_idx; i < max_idx; i++) {
+ if (rwnx_hw->avail_idx_map & BIT(i)) {
+ vif_idx = i;
+ break;
+ }
+ }
+
+ if (vif_idx < 0) {
+ AICWFDBG(LOGDEBUG, "AICWF virtual_interface_add %s fail\n", name);
+ return NULL;
+ }
+
+ vif = kzalloc_obj(*vif, GFP_KERNEL);
+ if (unlikely(!vif)) {
+ AICWFDBG(LOGDEBUG, "AICWF Could not allocate wireless device\n");
+ return NULL;
+ }
+ wdev = &vif->wdev;
+ wdev->wiphy = rwnx_hw->wiphy;
+ wdev->iftype = type;
+
+ AICWFDBG(LOGDEBUG,
+ "AICWF %s, ifname=%s, wdev=%p, vif_idx=%d\n",
+ __func__, name,
+ wdev, vif_idx);
+
+#ifndef CONFIG_AIC8800_USE_P2P0
+ vif->is_p2p_vif = 1;
+ vif->rwnx_hw = rwnx_hw;
+ vif->vif_index = vif_idx;
+ vif->wdev.wiphy = rwnx_hw->wiphy;
+ vif->drv_vif_index = vif_idx;
+ vif->up = false;
+ vif->ch_index = RWNX_CH_NOT_SET;
+ memset(&vif->net_stats, 0, sizeof(vif->net_stats));
+ vif->use_4addr = false;
+
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ list_add_tail(&vif->list, &rwnx_hw->vifs);
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+
+ if (rwnx_hw->is_p2p_alive == 0) {
+ timer_setup(&rwnx_hw->p2p_alive_timer, aicwf_p2p_alive_timeout, 0);
+ rwnx_hw->is_p2p_alive = 0;
+ rwnx_hw->is_p2p_connected = 0;
+ rwnx_hw->p2p_dev_vif = vif;
+ atomic_set(&rwnx_hw->p2p_alive_timer_count, 0);
+ }
+#endif
+ rwnx_hw->avail_idx_map &= ~BIT(vif_idx);
+
+ memcpy(vif->wdev.address, rwnx_hw->wiphy->perm_addr, ETH_ALEN);
+ vif->wdev.address[5] ^= vif_idx;
+ AICWFDBG(LOGDEBUG, "AICWF p2p dev addr=%x %x %x %x %x %x\n", vif->wdev.address[0],
+ vif->wdev.address[1], vif->wdev.address[2], vif->wdev.address[3],
+ vif->wdev.address[4], vif->wdev.address[5]);
+
+ return wdev;
+}
+
+/*
+ * @brief Retrieve the rwnx_sta object allocated for a given MAC address
+ * and a given role.
+ */
+static struct rwnx_sta *rwnx_retrieve_sta(struct rwnx_hw *rwnx_hw,
+ struct rwnx_vif *rwnx_vif, u8 *addr,
+ __le16 fc, bool ap)
+{
+ if (ap) {
+ /* only deauth, disassoc and action are bufferable MMPDUs */
+ bool bufferable = ieee80211_is_deauth(fc) ||
+ ieee80211_is_disassoc(fc) ||
+ ieee80211_is_action(fc);
+
+ /* Check if the packet is bufferable or not */
+ if (bufferable) {
+ /* Check if address is a broadcast or a multicast address */
+ if (is_broadcast_ether_addr(addr) ||
+ is_multicast_ether_addr(addr)) {
+ /* Returned STA pointer */
+ struct rwnx_sta *rwnx_sta =
+ &rwnx_hw->sta_table[rwnx_vif->ap.bcmc_index];
+
+ if (rwnx_sta->valid)
+ return rwnx_sta;
+ } else {
+ /* Returned STA pointer */
+ struct rwnx_sta *rwnx_sta;
+
+ /* Go through list of STAs linked with the provided VIF */
+ spin_lock_bh(&rwnx_vif->rwnx_hw->cb_lock);
+ list_for_each_entry(rwnx_sta, &rwnx_vif->ap.sta_list, list) {
+ if (rwnx_sta->valid &&
+ ether_addr_equal(rwnx_sta->mac_addr, addr)) {
+ /* Return the found STA */
+ spin_unlock_bh(&rwnx_vif->rwnx_hw->cb_lock);
+ return rwnx_sta;
+ }
+ }
+ spin_unlock_bh(&rwnx_vif->rwnx_hw->cb_lock);
+ }
+ }
+ } else {
+ return rwnx_vif->sta.ap;
+ }
+
+ return NULL;
+}
+
+/*
+ * @add_virtual_intf: create a new virtual interface with the given name,
+ * must set the struct wireless_dev's iftype. Beware: You must create
+ * the new netdev in the wiphy's network namespace! Returns the struct
+ * wireless_dev, or an ERR_PTR. For P2P device wdevs, the driver must
+ * also set the address member in the wdev.
+ */
+static struct wireless_dev *
+rwnx_cfg80211_add_iface(struct wiphy *wiphy, const char *name,
+ unsigned char name_assign_type,
+ enum nl80211_iftype type, struct vif_params *params)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct wireless_dev *wdev;
+
+ if (IS_ENABLED(CONFIG_AIC8800_STA_ONLY) &&
+ type != NL80211_IFTYPE_STATION)
+ return ERR_PTR(-EOPNOTSUPP);
+
+ if (type != NL80211_IFTYPE_P2P_DEVICE) {
+ struct rwnx_vif *vif =
+ rwnx_interface_add(rwnx_hw, name, name_assign_type, type, params);
+ if (!vif)
+ return ERR_PTR(-EINVAL);
+ return &vif->wdev;
+ }
+ wdev = rwnx_virtual_interface_add(rwnx_hw, name, name_assign_type, type,
+ params);
+ if (!wdev)
+ return ERR_PTR(-EINVAL);
+ return wdev;
+}
+
+/*
+ * @del_virtual_intf: remove the virtual interface
+ */
+static int rwnx_cfg80211_del_iface(struct wiphy *wiphy,
+ struct wireless_dev *wdev)
+{
+ struct net_device *dev = wdev->netdev;
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = container_of(wdev, struct rwnx_vif, wdev);
+
+ AICWFDBG(LOGINFO, "del_iface: %p, %x\n", wdev, wdev->address[5]);
+
+ if (!dev || !rwnx_vif->ndev) {
+ cfg80211_unregister_wdev(wdev);
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ list_del(&rwnx_vif->list);
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+ rwnx_hw->avail_idx_map |= BIT(rwnx_vif->drv_vif_index);
+ rwnx_vif->ndev = NULL;
+ kfree(rwnx_vif);
+ return 0;
+ }
+
+ netdev_info(dev, "Remove Interface");
+
+ if (dev->reg_state == NETREG_REGISTERED) {
+ /* Will call rwnx_close if interface is UP */
+ cfg80211_unregister_netdevice(dev);
+ }
+
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ list_del(&rwnx_vif->list);
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+ rwnx_hw->avail_idx_map |= BIT(rwnx_vif->drv_vif_index);
+ rwnx_vif->ndev = NULL;
+
+ /* Clear the priv in adapter */
+ dev->ieee80211_ptr = NULL;
+
+ return 0;
+}
+
+/*
+ * @change_virtual_intf: change type/configuration of virtual interface,
+ * keep the struct wireless_dev's iftype updated.
+ */
+static int rwnx_cfg80211_change_iface(struct wiphy *wiphy,
+ struct net_device *dev,
+ enum nl80211_iftype type,
+ struct vif_params *params)
+{
+#ifndef CONFIG_RWNX_MON_DATA
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+#endif
+ struct rwnx_vif *vif = netdev_priv(dev);
+ struct mm_add_if_cfm add_if_cfm;
+ bool p2p = false;
+ int ret;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+ AICWFDBG(LOGDEBUG, "AICWF change_if: %d to %d, %d, %d", vif->wdev.iftype, type,
+ NL80211_IFTYPE_P2P_CLIENT, NL80211_IFTYPE_STATION);
+
+ if (IS_ENABLED(CONFIG_AIC8800_STA_ONLY) &&
+ type != NL80211_IFTYPE_STATION)
+ return -EOPNOTSUPP;
+
+#ifdef CONFIG_AIC8800_COEX
+ if (type == NL80211_IFTYPE_AP || type == NL80211_IFTYPE_P2P_GO)
+ rwnx_send_coex_req(vif->rwnx_hw, 1, 0);
+ if (RWNX_VIF_TYPE(vif) == NL80211_IFTYPE_AP ||
+ RWNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_GO)
+ rwnx_send_coex_req(vif->rwnx_hw, 0, 1);
+#endif
+#ifndef CONFIG_RWNX_MON_DATA
+ if (type == NL80211_IFTYPE_MONITOR &&
+ RWNX_VIF_TYPE(vif) != NL80211_IFTYPE_MONITOR) {
+ struct rwnx_vif *vif_el;
+
+ list_for_each_entry(vif_el, &rwnx_hw->vifs, list) {
+ // Check if data interface already exists
+ if (vif_el != vif &&
+ RWNX_VIF_TYPE(vif) != NL80211_IFTYPE_MONITOR) {
+ wiphy_err(rwnx_hw->wiphy,
+ "Monitor+Data interface support (MON_DATA) disabled\n");
+ return -EIO;
+ }
+ }
+ }
+#endif
+
+ // Reset to default case (i.e. not monitor)
+ dev->type = ARPHRD_ETHER;
+ dev->netdev_ops = &rwnx_netdev_ops;
+
+ switch (type) {
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ case NL80211_IFTYPE_P2P_CLIENT:
+#endif
+ case NL80211_IFTYPE_STATION:
+ vif->sta.ap = NULL;
+ vif->sta.tdls_sta = NULL;
+ vif->sta.external_auth = false;
+ break;
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ case NL80211_IFTYPE_MESH_POINT:
+ INIT_LIST_HEAD(&vif->ap.mpath_list);
+ INIT_LIST_HEAD(&vif->ap.proxy_list);
+ vif->ap.create_path = false;
+ vif->ap.generation = 0;
+ fallthrough;
+ // no break
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_P2P_GO:
+ INIT_LIST_HEAD(&vif->ap.sta_list);
+ memset(&vif->ap.bcn, 0, sizeof(vif->ap.bcn));
+ break;
+ case NL80211_IFTYPE_AP_VLAN:
+ return -EPERM;
+ case NL80211_IFTYPE_MONITOR:
+ dev->type = ARPHRD_IEEE80211_RADIOTAP;
+ dev->netdev_ops = &rwnx_netdev_monitor_ops;
+ break;
+#endif
+ default:
+ break;
+ }
+
+ vif->wdev.iftype = type;
+ if (params->use_4addr != -1)
+ vif->use_4addr = params->use_4addr;
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ if (type == NL80211_IFTYPE_P2P_CLIENT || type == NL80211_IFTYPE_P2P_GO)
+ p2p = true;
+#endif
+
+ if (vif->up) {
+ /* Abort scan request on the vif */
+ if (vif->rwnx_hw->scan_request &&
+ vif->rwnx_hw->scan_request->wdev == &vif->wdev) {
+ ret = rwnx_send_scanu_cancel_req(vif->rwnx_hw, NULL);
+ if (ret) {
+ AICWFDBG(LOGERROR, "scanu_cancel fail\n");
+ return ret;
+ }
+ }
+ ret = rwnx_send_remove_if(vif->rwnx_hw, vif->vif_index, false);
+ if (ret) {
+ AICWFDBG(LOGDEBUG, "AICWF remove_if fail\n");
+ return ret;
+ }
+ vif->rwnx_hw->vif_table[vif->vif_index] = NULL;
+ AICWFDBG(LOGDEBUG, "AICWF change_if from %d\n", vif->vif_index);
+ ret = rwnx_send_add_if(vif->rwnx_hw, vif->wdev.address,
+ RWNX_VIF_TYPE(vif), p2p, &add_if_cfm);
+ if (ret) {
+ AICWFDBG(LOGDEBUG, "AICWF add if fail\n");
+ return ret;
+ }
+ if (add_if_cfm.status != 0) {
+ AICWFDBG(LOGDEBUG, "AICWF add if status fail\n");
+ return -EIO;
+ }
+
+ AICWFDBG(LOGDEBUG, "AICWF change_if to %d\n", add_if_cfm.inst_nbr);
+ /* Save the index retrieved from LMAC */
+ spin_lock_bh(&vif->rwnx_hw->cb_lock);
+ vif->vif_index = add_if_cfm.inst_nbr;
+ vif->rwnx_hw->vif_table[add_if_cfm.inst_nbr] = vif;
+ spin_unlock_bh(&vif->rwnx_hw->cb_lock);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused rwnx_cfgp2p_start_p2p_device(struct wiphy *wiphy,
+ struct wireless_dev *wdev)
+{
+ int ret = 0;
+
+ // do nothing
+ AICWFDBG(LOGDEBUG, "AICWF P2P interface started\n");
+
+ return ret;
+}
+
+static void __maybe_unused rwnx_cfgp2p_stop_p2p_device(struct wiphy *wiphy,
+ struct wireless_dev *wdev)
+{
+ int ret = 0;
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = container_of(wdev, struct rwnx_vif, wdev);
+ /* Abort scan request on the vif */
+ if (rwnx_hw->scan_request &&
+ rwnx_hw->scan_request->wdev == &rwnx_vif->wdev) {
+ struct cfg80211_scan_info info = {
+ .aborted = true,
+ };
+
+ cfg80211_scan_done(rwnx_hw->scan_request, &info);
+ aicwf_sdio_scan_activity_put(rwnx_hw);
+ rwnx_hw->scan_request = NULL;
+ ret = rwnx_send_scanu_cancel_req(rwnx_hw, NULL);
+ if (ret)
+ AICWFDBG(LOGDEBUG, "AICWF scanu_cancel fail\n");
+ }
+
+ if (rwnx_vif == rwnx_hw->p2p_dev_vif) {
+ rwnx_hw->is_p2p_alive = 0;
+ if (timer_pending(&rwnx_hw->p2p_alive_timer))
+ //del_timer_sync(&rwnx_hw->p2p_alive_timer);
+ timer_delete_sync(&rwnx_hw->p2p_alive_timer);
+
+ if (rwnx_vif->up) {
+ rwnx_send_remove_if(rwnx_hw, rwnx_vif->vif_index, true);
+ /* Ensure that we won't process disconnect ind */
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ rwnx_vif->up = false;
+ rwnx_hw->vif_table[rwnx_vif->vif_index] = NULL;
+ AICWFDBG(LOGDEBUG, "%s rwnx_vif[%d] down \r\n", __func__,
+ rwnx_vif->vif_index);
+ rwnx_hw->vif_started--;
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+ }
+ }
+ AICWFDBG(LOGDEBUG, "AICWF Exit. P2P interface stopped\n");
+}
+
+/*
+ * @scan: Request to do a scan. If returning zero, the scan request is given
+ * the driver, and will be valid until passed to cfg80211_scan_done().
+ * For scan results, call cfg80211_inform_bss(); you can call this outside
+ * the scan/scan_done bracket too.
+ */
+static int rwnx_cfg80211_scan(struct wiphy *wiphy,
+ struct cfg80211_scan_request *request)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif =
+ container_of(request->wdev, struct rwnx_vif, wdev);
+ int error;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ if (rwnx_hw->testmode)
+ return -EBUSY;
+
+ if ((int)atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_CONNECTING) {
+ AICWFDBG(LOGERROR, "%s wifi is connecting, return it\r\n", __func__);
+ return -EBUSY;
+ }
+
+ if (rwnx_hw->scanning) {
+ AICWFDBG(LOGERROR, "%s is scanning, abort\n", __func__);
+ return -EBUSY;
+ }
+
+ if ((RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION ||
+ RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_CLIENT) &&
+ rwnx_vif->sta.external_auth) {
+ AICWFDBG(LOGERROR, "scan about: external auth\r\n");
+ return -EBUSY;
+ }
+
+ if (!aicwf_sdio_scan_activity_get(rwnx_hw))
+ return -EBUSY;
+
+ rwnx_hw->scan_request = request;
+ error = rwnx_send_scanu_req(rwnx_hw, rwnx_vif, request);
+ if (error) {
+ rwnx_hw->scan_request = NULL;
+ rwnx_hw->scanning = 0;
+ aicwf_sdio_scan_activity_put(rwnx_hw);
+ return error;
+ }
+
+ return 0;
+}
+
+static bool key_flag;
+/*
+ * @add_key: add a key with the given parameters. @mac_addr will be %NULL
+ * when adding a group key.
+ */
+static int rwnx_cfg80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
+ int link_id,
+ u8 key_index, bool pairwise,
+ const u8 *mac_addr, struct key_params *params)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct net_device *netdev = wdev->netdev;
+ struct rwnx_vif *vif = netdev_priv(netdev);
+ int i, error = 0;
+ struct mm_key_add_cfm key_add_cfm;
+ u8 cipher = 0;
+ struct rwnx_sta *sta = NULL;
+ struct rwnx_key *rwnx_key;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ if (mac_addr) {
+ sta = rwnx_get_sta(rwnx_hw, mac_addr);
+ if (!sta)
+ return -EINVAL;
+ rwnx_key = &sta->key;
+ if (vif->wdev.iftype == NL80211_IFTYPE_STATION ||
+ vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT)
+ vif->sta.paired_cipher_type = params->cipher;
+ } else {
+ rwnx_key = &vif->key[key_index];
+ vif->key_has_add = 1;
+ if (vif->wdev.iftype == NL80211_IFTYPE_STATION ||
+ vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT)
+ vif->sta.group_cipher_type = params->cipher;
+ }
+
+ /* Retrieve the cipher suite selector */
+ switch (params->cipher) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ cipher = MAC_CIPHER_WEP40;
+ break;
+ case WLAN_CIPHER_SUITE_WEP104:
+ cipher = MAC_CIPHER_WEP104;
+ break;
+ case WLAN_CIPHER_SUITE_TKIP:
+ cipher = MAC_CIPHER_TKIP;
+ break;
+ case WLAN_CIPHER_SUITE_CCMP:
+ cipher = MAC_CIPHER_CCMP;
+ break;
+ case WLAN_CIPHER_SUITE_AES_CMAC:
+ cipher = MAC_CIPHER_BIP_CMAC_128;
+ break;
+ case WLAN_CIPHER_SUITE_SMS4: {
+ // Need to reverse key order
+ u8 tmp, *key = (u8 *)params->key;
+
+ cipher = MAC_CIPHER_WPI_SMS4;
+ for (i = 0; i < WPI_SUBKEY_LEN / 2; i++) {
+ tmp = key[i];
+ key[i] = key[WPI_SUBKEY_LEN - 1 - i];
+ key[WPI_SUBKEY_LEN - 1 - i] = tmp;
+ }
+ for (i = 0; i < WPI_SUBKEY_LEN / 2; i++) {
+ tmp = key[i + WPI_SUBKEY_LEN];
+ key[i + WPI_SUBKEY_LEN] = key[WPI_KEY_LEN - 1 - i];
+ key[WPI_KEY_LEN - 1 - i] = tmp;
+ }
+ break;
+ }
+ default:
+ return -EINVAL;
+ }
+
+ key_flag = false;
+ error = rwnx_send_key_add(rwnx_hw, vif->vif_index,
+ (sta ? sta->sta_idx : 0xFF), pairwise,
+ (u8 *)params->key, params->key_len,
+ key_index, cipher, &key_add_cfm);
+ if (error)
+ return error;
+
+ if (key_add_cfm.status != 0) {
+ RWNX_PRINT_CFM_ERR(key_add);
+ return -EIO;
+ }
+
+ /* Save the index retrieved from LMAC */
+ rwnx_key->hw_idx = key_add_cfm.hw_key_idx;
+
+ return 0;
+}
+
+/*
+ * @get_key: get information about the key with the given parameters.
+ * @mac_addr will be %NULL when requesting information for a group
+ * key. All pointers given to the @callback function need not be valid
+ * after it returns. This function should return an error if it is
+ * not possible to retrieve the key, -ENOENT if it doesn't exist.
+ *
+ */
+static int rwnx_cfg80211_get_key(struct wiphy *wiphy, struct wireless_dev *wdev,
+ int link_id,
+ u8 key_index, bool pairwise,
+ const u8 *mac_addr, void *cookie,
+ void (*callback)(void *cookie, struct key_params *))
+{
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+ return -1;
+}
+
+/*
+ * @del_key: remove a key given the @mac_addr (%NULL for a group key)
+ * and @key_index, return -ENOENT if the key doesn't exist.
+ */
+static int rwnx_cfg80211_del_key(struct wiphy *wiphy, struct wireless_dev *wdev,
+ int link_id,
+ u8 key_index, bool pairwise,
+ const u8 *mac_addr)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct net_device *netdev = wdev->netdev;
+ struct rwnx_vif *vif = netdev_priv(netdev);
+ int error;
+ struct rwnx_sta *sta = NULL;
+ struct rwnx_key *rwnx_key;
+
+ if (!key_flag && vif->wdev.iftype == NL80211_IFTYPE_STATION)
+ return 0;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+ if (mac_addr) {
+ sta = rwnx_get_sta(rwnx_hw, mac_addr);
+ if (!sta)
+ return -EINVAL;
+ rwnx_key = &sta->key;
+ if (vif->wdev.iftype == NL80211_IFTYPE_STATION ||
+ vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT)
+ vif->sta.paired_cipher_type = 0xff;
+ } else {
+ rwnx_key = &vif->key[key_index];
+ vif->key_has_add = 0;
+ if (vif->wdev.iftype == NL80211_IFTYPE_STATION ||
+ vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT)
+ vif->sta.group_cipher_type = 0xff;
+ }
+
+ error = rwnx_send_key_del(rwnx_hw, rwnx_key->hw_idx);
+
+ rwnx_key->hw_idx = 0;
+ return error;
+}
+
+/*
+ * @set_default_key: set the default key on an interface
+ */
+static int rwnx_cfg80211_set_default_key(struct wiphy *wiphy,
+ struct net_device *netdev,
+ int link_id,
+ u8 key_index, bool unicast,
+ bool multicast)
+{
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ return 0;
+}
+
+/*
+ * @set_default_mgmt_key: set the default management frame key on an interface
+ */
+static int rwnx_cfg80211_set_default_mgmt_key(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ int link_id,
+ u8 key_index)
+{
+ return 0;
+}
+
+/*
+ * @connect: Connect to the ESS with the specified parameters. When connected,
+ * call cfg80211_connect_result() with status code %WLAN_STATUS_SUCCESS.
+ * If the connection fails for some reason, call cfg80211_connect_result()
+ * with the status from the AP.
+ * (invoked with the wireless_dev mutex held)
+ */
+static int rwnx_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_connect_params *sme)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct sm_connect_cfm sm_connect_cfm;
+ int error = 0;
+ int is_wep = (sme->crypto.cipher_group == WLAN_CIPHER_SUITE_WEP40 ||
+ sme->crypto.cipher_group == WLAN_CIPHER_SUITE_WEP104 ||
+ sme->crypto.ciphers_pairwise[0] == WLAN_CIPHER_SUITE_WEP40 ||
+ sme->crypto.ciphers_pairwise[0] == WLAN_CIPHER_SUITE_WEP104);
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ if ((int)atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_CONNECTED) {
+ AICWFDBG(LOGDEBUG, "%s this connection is roam \r\n", __func__);
+ rwnx_vif->sta.is_roam = true;
+ } else {
+ rwnx_vif->sta.is_roam = false;
+ }
+
+ if ((int)atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_DISCONNECTING ||
+ (int)atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_CONNECTING) {
+ AICWFDBG(LOGERROR,
+ "%s driver is disconnecting or connecting ,return it \r\n",
+ __func__);
+ return 0;
+ }
+
+ if (rwnx_vif->sta.is_roam)
+ rwnx_set_conn_state(&rwnx_vif->drv_conn_state, (int)RWNX_DRV_STATUS_ROAMING);
+ else
+ rwnx_set_conn_state(&rwnx_vif->drv_conn_state, (int)RWNX_DRV_STATUS_CONNECTING);
+
+ if (is_wep) {
+ if (sme->auth_type == NL80211_AUTHTYPE_AUTOMATIC) {
+ if (rwnx_vif->wep_enabled && rwnx_vif->wep_auth_err) {
+ if (rwnx_vif->last_auth_type == NL80211_AUTHTYPE_SHARED_KEY)
+ sme->auth_type = NL80211_AUTHTYPE_OPEN_SYSTEM;
+ else
+ sme->auth_type = NL80211_AUTHTYPE_SHARED_KEY;
+ } else {
+ if (rwnx_vif->wep_enabled && !rwnx_vif->wep_auth_err)
+ sme->auth_type = rwnx_vif->last_auth_type;
+ else
+ sme->auth_type = NL80211_AUTHTYPE_SHARED_KEY;
+ }
+ AICWFDBG(LOGDEBUG, "AICWF auto: use sme->auth_type = %d\r\n",
+ sme->auth_type);
+ } else {
+ if (rwnx_vif->wep_enabled && rwnx_vif->wep_auth_err &&
+ sme->auth_type == rwnx_vif->last_auth_type) {
+ if (sme->auth_type == NL80211_AUTHTYPE_SHARED_KEY) {
+ sme->auth_type = NL80211_AUTHTYPE_OPEN_SYSTEM;
+ AICWFDBG(LOGDEBUG,
+ "AICWF start connect, auth_type changed, shared --> open\n");
+ } else if (sme->auth_type == NL80211_AUTHTYPE_OPEN_SYSTEM) {
+ sme->auth_type = NL80211_AUTHTYPE_SHARED_KEY;
+ AICWFDBG(LOGDEBUG,
+ "AICWF start connect, auth_type changed, open --> shared\n");
+ }
+ }
+ }
+ }
+
+ /* For SHARED-KEY authentication, must install key first */
+ if (sme->auth_type == NL80211_AUTHTYPE_SHARED_KEY && sme->key) {
+ struct key_params key_params;
+
+ key_params.key = (u8 *)sme->key;
+ key_params.seq = NULL;
+ key_params.key_len = sme->key_len;
+ key_params.seq_len = 0;
+ key_params.cipher = sme->crypto.cipher_group;
+ rwnx_cfg80211_add_key(wiphy, &rwnx_vif->wdev, 0,
+ sme->key_idx, false, NULL, &key_params);
+ } else if (sme->auth_type == NL80211_AUTHTYPE_SAE &&
+ !(sme->flags & CONNECT_REQ_EXTERNAL_AUTH_SUPPORT)) {
+ netdev_err(dev,
+ "Doesn't support SAE without external authentication\n");
+ return -EINVAL;
+ }
+
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT)
+ rwnx_hw->is_p2p_connected = 1;
+
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_STATION ||
+ rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) {
+ rwnx_vif->sta.paired_cipher_type = 0xff;
+ rwnx_vif->sta.group_cipher_type = 0xff;
+ }
+
+ /* Forward the information to the LMAC */
+ error = rwnx_send_sm_connect_req(rwnx_hw, rwnx_vif, sme, &sm_connect_cfm);
+ if (error)
+ return error;
+
+ // Check the status
+ switch (sm_connect_cfm.status) {
+ case CO_OK:
+ error = 0;
+ break;
+ case CO_BUSY:
+ error = -EINPROGRESS;
+ break;
+ case CO_OP_IN_PROGRESS:
+ error = -EALREADY;
+ break;
+ default:
+ error = -EIO;
+ break;
+ }
+
+ return error;
+}
+
+/*
+ * @disconnect: Disconnect from the BSS/ESS.
+ * (invoked with the wireless_dev mutex held)
+ */
+static int rwnx_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev,
+ u16 reason_code)
+{
+ int ret = 0;
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ AICWFDBG(LOGINFO, "%s drv_vif_index:%d disconnect reason:%d \r\n", __func__,
+ rwnx_vif->drv_vif_index, reason_code);
+
+ if (atomic_read(&rwnx_vif->drv_conn_state) == RWNX_DRV_STATUS_DISCONNECTED) {
+ AICWFDBG(LOGERROR, "%s this\r\n", __func__);
+ WARN_ON(1);
+ return -EBUSY;
+ }
+
+ if (atomic_read(&rwnx_vif->drv_conn_state) == RWNX_DRV_STATUS_DISCONNECTING) {
+ AICWFDBG(LOGERROR, "%s wifi is disconnecting, return it:%d \r\n",
+ __func__, reason_code);
+ return -EBUSY;
+ }
+
+ if (atomic_read(&rwnx_vif->drv_conn_state) == RWNX_DRV_STATUS_CONNECTED ||
+ atomic_read(&rwnx_vif->drv_conn_state) == RWNX_DRV_STATUS_CONNECTING ||
+ atomic_read(&rwnx_vif->drv_conn_state) == RWNX_DRV_STATUS_ROAMING) {
+ rwnx_set_conn_state(&rwnx_vif->drv_conn_state, RWNX_DRV_STATUS_DISCONNECTING);
+ key_flag = true;
+ ret = rwnx_send_sm_disconnect_req(rwnx_hw, rwnx_vif, reason_code);
+#ifdef AICWF_SDIO_SUPPORT
+ if (rwnx_hw->sdiodev->bus_if->state == BUS_DOWN_ST) {
+ AICWFDBG(LOGINFO, "%s bus is down %d\n", __func__, ret);
+ rwnx_set_conn_state(&rwnx_vif->drv_conn_state,
+ RWNX_DRV_STATUS_DISCONNECTED);
+ }
+#endif
+ return ret;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_AIC8800_SCHED_SCAN
+static int rwnx_cfg80211_sched_scan_stop(struct wiphy *wiphy,
+ struct net_device *ndev,
+ u64 reqid)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ // struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ AICWFDBG(LOGINFO, "%s enter wiphy:%p\r\n", __func__, wiphy);
+
+ if (rwnx_hw->scan_request) {
+ AICWFDBG(LOGINFO, "%s rwnx_send_scanu_cancel_req\r\n", __func__);
+ return rwnx_send_scanu_cancel_req(rwnx_hw, NULL);
+ } else {
+ return 0;
+ }
+}
+
+static int
+rwnx_cfg80211_sched_scan_start(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_sched_scan_request *request)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct cfg80211_scan_request *scan_request = NULL;
+
+ int ret = 0;
+ int index = 0;
+
+ AICWFDBG(LOGINFO, "%s enter wiphy:%p\r\n", __func__, wiphy);
+
+ if (rwnx_hw->is_sched_scan || rwnx_hw->scanning) {
+ AICWFDBG(LOGERROR, "%s is_sched_scanning and scanning, busy", __func__);
+ return -EBUSY;
+ }
+
+ scan_request = (struct cfg80211_scan_request *)
+ kmalloc_obj(struct cfg80211_scan_request, GFP_KERNEL);
+
+ scan_request->ssids = request->ssids;
+ scan_request->n_channels = request->n_channels;
+ scan_request->n_ssids = request->n_match_sets;
+ scan_request->no_cck = false;
+ scan_request->ie = request->ie;
+ scan_request->ie_len = request->ie_len;
+ scan_request->flags = request->flags;
+
+ scan_request->wiphy = wiphy;
+ scan_request->scan_start = request->scan_start;
+ memcpy(scan_request->mac_addr, request->mac_addr, ETH_ALEN);
+ memcpy(scan_request->mac_addr_mask, request->mac_addr_mask, ETH_ALEN);
+ rwnx_hw->sched_scan_req = request;
+ scan_request->wdev = &rwnx_vif->wdev;
+ AICWFDBG(LOGDEBUG, "%s scan_request->n_channels:%d \r\n", __func__,
+ scan_request->n_channels);
+ AICWFDBG(LOGDEBUG, "%s scan_request->n_ssids:%d \r\n", __func__,
+ scan_request->n_ssids);
+
+ for (index = 0; index < scan_request->n_ssids; index++) {
+ memset(scan_request->ssids[index].ssid, 0, IEEE80211_MAX_SSID_LEN);
+
+ memcpy(scan_request->ssids[index].ssid,
+ request->match_sets[index].ssid.ssid, IEEE80211_MAX_SSID_LEN);
+
+ scan_request->ssids[index].ssid_len = request->match_sets[index].ssid.ssid_len;
+
+ AICWFDBG(LOGDEBUG, "%s request ssid:%s len:%d \r\n", __func__,
+ scan_request->ssids[index].ssid,
+ scan_request->ssids[index].ssid_len);
+ }
+
+ for (index = 0; index < scan_request->n_channels; index++) {
+ scan_request->channels[index] = request->channels[index];
+
+ AICWFDBG(LOGDEBUG, "%s scan_request->channels[%d]:%d \r\n", __func__,
+ index, scan_request->channels[index]->center_freq);
+
+ if (!scan_request->channels[index]) {
+ AICWFDBG(LOGERROR, "%s ERROR!!! channels is NULL", __func__);
+ continue;
+ }
+ }
+
+ rwnx_hw->is_sched_scan = true;
+
+ if (rwnx_hw->scanning) {
+ AICWFDBG(LOGERROR, "%s scanning, about it", __func__);
+ kfree(scan_request);
+ return -EBUSY;
+ }
+ ret = rwnx_cfg80211_scan(wiphy, scan_request);
+ return ret;
+}
+#endif // CONFIG_AIC8800_SCHED_SCAN
+
+/*
+ * @external_auth: indicates result of offloaded authentication processing from
+ * user space
+ */
+static int
+rwnx_cfg80211_external_auth(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_external_auth_params *params)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+
+ if (!rwnx_vif->sta.external_auth)
+ return -EINVAL;
+
+ rwnx_external_auth_disable(rwnx_vif);
+ return rwnx_send_sm_external_auth_required_rsp(rwnx_hw, rwnx_vif,
+ params->status);
+}
+
+/*
+ * @add_station: Add a new station.
+ */
+static int __maybe_unused rwnx_cfg80211_add_station(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ const u8 *mac,
+ struct station_parameters *params)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct net_device *dev = wdev->netdev;
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct me_sta_add_cfm me_sta_add_cfm;
+ int error = 0;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ WARN_ON(RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_AP_VLAN);
+
+ /* Do not add TDLS station */
+ if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))
+ return 0;
+
+ /* Indicate we are in a STA addition process - This will allow handling
+ * potential PS mode change indications correctly
+ */
+ rwnx_hw->adding_sta = true;
+
+ /* Forward the information to the LMAC */
+ error = rwnx_send_me_sta_add(rwnx_hw, params, mac, rwnx_vif->vif_index,
+ &me_sta_add_cfm);
+ if (error)
+ return error;
+
+ // Check the status
+ switch (me_sta_add_cfm.status) {
+ case CO_OK: {
+ struct rwnx_sta *sta = &rwnx_hw->sta_table[me_sta_add_cfm.sta_idx];
+ int tid;
+
+ sta->aid = params->aid;
+ sta->sta_idx = me_sta_add_cfm.sta_idx;
+ sta->ch_idx = rwnx_vif->ch_index;
+ sta->vif_idx = rwnx_vif->vif_index;
+ sta->vlan_idx = sta->vif_idx;
+ sta->qos = (params->sta_flags_set & BIT(NL80211_STA_FLAG_WME)) != 0;
+ sta->ht = params->link_sta_params.ht_capa ? 1 : 0;
+ sta->vht = params->link_sta_params.vht_capa ? 1 : 0;
+ sta->acm = 0;
+ sta->key.hw_idx = 0;
+
+ if (params->local_pm != NL80211_MESH_POWER_UNKNOWN)
+ sta->mesh_pm = params->local_pm;
+ else
+ sta->mesh_pm = rwnx_vif->ap.next_mesh_pm;
+ rwnx_update_mesh_power_mode(rwnx_vif);
+
+ for (tid = 0; tid < NX_NB_TXQ_PER_STA; tid++) {
+ int uapsd_bit = rwnx_hwq2uapsd[rwnx_tid2hwq[tid]];
+
+ if (params->uapsd_queues & uapsd_bit)
+ sta->uapsd_tids |= 1 << tid;
+ else
+ sta->uapsd_tids &= ~(1 << tid);
+ }
+ memcpy(sta->mac_addr, mac, ETH_ALEN);
+
+ /* Ensure that we won't process PS change or channel switch ind */
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ rwnx_txq_sta_init(rwnx_hw, sta, rwnx_txq_vif_get_status(rwnx_vif));
+ list_add_tail(&sta->list, &rwnx_vif->ap.sta_list);
+ sta->valid = true;
+ rwnx_ps_bh_enable(rwnx_hw, sta,
+ sta->ps.active || me_sta_add_cfm.pm_state);
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+
+ error = 0;
+
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_AP ||
+ rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_GO) {
+ struct station_info sinfo;
+
+ memset(&sinfo, 0, sizeof(struct station_info));
+ sinfo.assoc_req_ies = NULL;
+ sinfo.assoc_req_ies_len = 0;
+ cfg80211_new_sta(&rwnx_vif->wdev, sta->mac_addr, &sinfo,
+ GFP_KERNEL);
+ }
+#ifdef CONFIG_RWNX_BFMER
+ if (rwnx_hw->mod_params->bfmer)
+ rwnx_send_bfmer_enable(rwnx_hw, sta,
+ params->link_sta_params.vht_capa);
+
+ rwnx_mu_group_sta_init(sta, params->link_sta_params.vht_capa);
+#endif /* CONFIG_RWNX_BFMER */
+
+#define PRINT_STA_FLAG(f) \
+ (params->sta_flags_set & BIT(NL80211_STA_FLAG_##f) ? "[" #f "]" : "")
+
+ netdev_info(dev, "Add sta %d (%pM) flags=%s%s%s%s%s%s%s", sta->sta_idx,
+ mac, PRINT_STA_FLAG(AUTHORIZED),
+ PRINT_STA_FLAG(SHORT_PREAMBLE), PRINT_STA_FLAG(WME),
+ PRINT_STA_FLAG(MFP), PRINT_STA_FLAG(AUTHENTICATED),
+ PRINT_STA_FLAG(TDLS_PEER), PRINT_STA_FLAG(ASSOCIATED));
+#undef PRINT_STA_FLAG
+ break;
+ }
+ default:
+ error = -EBUSY;
+ break;
+ }
+
+ rwnx_hw->adding_sta = false;
+
+ return error;
+}
+
+/*
+ * @del_station: Remove a station
+ */
+static int rwnx_cfg80211_del_station_compat(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ struct station_del_parameters *params)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct net_device *dev = wdev->netdev;
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_sta *cur, *tmp;
+ int error = 0, found = 0;
+ const u8 *mac = NULL;
+#ifdef AICWF_RX_REORDER
+ struct reord_ctrl_info *reord_info, *reord_tmp;
+ u8 *macaddr;
+ struct aicwf_rx_priv *rx_priv;
+#endif
+ AICWFDBG(LOGDEBUG, "AICWF %s: %pM\n", __func__, mac);
+
+ if (params)
+ mac = params->mac;
+
+ do {
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ if (list_empty(&rwnx_vif->ap.sta_list)) {
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+ break;
+ }
+
+ list_for_each_entry_safe(cur, tmp, &rwnx_vif->ap.sta_list, list) {
+ if (!mac || !memcmp(cur->mac_addr, mac, ETH_ALEN)) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (found) {
+ cur->ps.active = false;
+ cur->valid = false;
+ list_del(&cur->list);
+ }
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+
+ if (found) {
+ netdev_info(dev, "Del sta %d (%pM)", cur->sta_idx, cur->mac_addr);
+ if (cur->vif_idx != cur->vlan_idx) {
+ struct rwnx_vif *vlan_vif;
+
+ vlan_vif = rwnx_hw->vif_table[cur->vlan_idx];
+ if (vlan_vif->up) {
+ if (RWNX_VIF_TYPE(vlan_vif) == NL80211_IFTYPE_AP_VLAN &&
+ vlan_vif->use_4addr) {
+ vlan_vif->ap_vlan.sta_4a = NULL;
+ } else {
+ WARN(1,
+ "Deleting sta belonging to VLAN other than AP_VLAN 4A");
+ }
+ }
+ }
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_AP ||
+ rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_GO) {
+ cfg80211_del_sta(&rwnx_vif->wdev, cur->mac_addr,
+ GFP_KERNEL);
+ }
+
+#ifdef AICWF_RX_REORDER
+#ifdef AICWF_SDIO_SUPPORT
+ rx_priv = rwnx_hw->sdiodev->rx_priv;
+#else
+ rx_priv = rwnx_hw->usbdev->rx_priv;
+#endif
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_STATION ||
+ rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) {
+ //WARN_ON_ONCE(); // should be other function
+ } else if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_AP ||
+ rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_GO) {
+ macaddr = cur->mac_addr;
+ AICWFDBG(LOGDEBUG, "AICWF deinit:macaddr:%x,%x,%x,%x,%x,%x\r\n",
+ macaddr[0], macaddr[1], macaddr[2],
+ macaddr[3], macaddr[4], macaddr[5]);
+ spin_lock_bh(&rx_priv->stas_reord_lock);
+ list_for_each_entry_safe(reord_info, reord_tmp,
+ &rx_priv->stas_reord_list, list) {
+ AICWFDBG(LOGDEBUG, "AICWF reord_mac:%x,%x,%x,%x,%x,%x\r\n",
+ reord_info->mac_addr[0], reord_info->mac_addr[1],
+ reord_info->mac_addr[2], reord_info->mac_addr[3],
+ reord_info->mac_addr[4], reord_info->mac_addr[5]);
+ if (!memcmp(reord_info->mac_addr, macaddr, 6)) {
+ reord_deinit_sta(rx_priv, reord_info);
+ break;
+ }
+ }
+ spin_unlock_bh(&rx_priv->stas_reord_lock);
+ }
+#endif
+ rwnx_txq_sta_deinit(rwnx_hw, cur);
+ error = rwnx_send_me_sta_del(rwnx_hw, cur->sta_idx, false);
+ if (error != 0 && error != -EPIPE)
+ return error;
+
+#ifdef CONFIG_RWNX_BFMER
+ // Disable Beamformer if supported
+ rwnx_bfmer_report_del(rwnx_hw, cur);
+ rwnx_mu_group_sta_del(rwnx_hw, cur);
+#endif /* CONFIG_RWNX_BFMER */
+
+ }
+
+ if (mac)
+ break;
+ } while (1);
+
+ rwnx_update_mesh_power_mode(rwnx_vif);
+
+ if (!found && mac)
+ return -ENOENT;
+ else
+ return 0;
+}
+
+void apm_staloss_work_process(struct work_struct *work)
+{
+ struct rwnx_hw *rwnx_hw = container_of(work, struct rwnx_hw, apm_staloss_work);
+ struct rwnx_sta *cur, *tmp;
+ int error = 0;
+
+#ifdef AICWF_RX_REORDER
+ struct reord_ctrl_info *reord_info, *reord_tmp;
+ u8 *macaddr;
+ struct aicwf_rx_priv *rx_priv;
+#endif
+ struct rwnx_vif *rwnx_vif;
+ bool found = false;
+ const u8 *mac = rwnx_hw->sta_mac_addr;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ // Look for VIF entry
+ list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
+ if (rwnx_vif->vif_index == rwnx_hw->apm_vif_idx) {
+ found = true;
+ break;
+ }
+ }
+
+ AICWFDBG(LOGDEBUG, "AICWF apm vif idx=%d, found=%d, mac addr=%pM\n",
+ rwnx_hw->apm_vif_idx, found, mac);
+ if (!found || !rwnx_vif ||
+ (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_AP &&
+ RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_P2P_GO)) {
+ return;
+ }
+
+ found = false;
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ list_for_each_entry_safe(cur, tmp, &rwnx_vif->ap.sta_list, list) {
+ if (mac && !memcmp(cur->mac_addr, mac, ETH_ALEN)) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ cur->ps.active = false;
+ cur->valid = false;
+ list_del(&cur->list);
+ }
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+
+ if (found) {
+ netdev_info(rwnx_vif->ndev, "Del sta %d (%pM)", cur->sta_idx,
+ cur->mac_addr);
+ if (cur->vif_idx != cur->vlan_idx) {
+ struct rwnx_vif *vlan_vif;
+
+ vlan_vif = rwnx_hw->vif_table[cur->vlan_idx];
+ if (vlan_vif->up) {
+ if (RWNX_VIF_TYPE(vlan_vif) == NL80211_IFTYPE_AP_VLAN &&
+ vlan_vif->use_4addr)
+ vlan_vif->ap_vlan.sta_4a = NULL;
+ WARN(1, "Deleting sta belonging to VLAN other than AP_VLAN 4A");
+ }
+ }
+
+#ifdef AICWF_RX_REORDER
+#ifdef AICWF_SDIO_SUPPORT
+ rx_priv = rwnx_hw->sdiodev->rx_priv;
+#else
+ rx_priv = rwnx_hw->usbdev->rx_priv;
+#endif
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_STATION ||
+ rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) {
+ //WARN_ON_ONCE(); // should be other function
+ } else if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_AP ||
+ rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_GO) {
+ macaddr = cur->mac_addr;
+ AICWFDBG(LOGDEBUG, "AICWF deinit:macaddr:%x,%x,%x,%x,%x,%x\r\n",
+ macaddr[0], macaddr[1], macaddr[2],
+ macaddr[3], macaddr[4], macaddr[5]);
+ spin_lock_bh(&rx_priv->stas_reord_lock);
+ list_for_each_entry_safe(reord_info, reord_tmp,
+ &rx_priv->stas_reord_list, list) {
+ AICWFDBG(LOGDEBUG, "AICWF reord_mac:%x,%x,%x,%x,%x,%x\r\n",
+ reord_info->mac_addr[0], reord_info->mac_addr[1],
+ reord_info->mac_addr[2], reord_info->mac_addr[3],
+ reord_info->mac_addr[4], reord_info->mac_addr[5]);
+ if (!memcmp(reord_info->mac_addr, macaddr, 6)) {
+ reord_deinit_sta(rx_priv, reord_info);
+ break;
+ }
+ }
+ spin_unlock_bh(&rx_priv->stas_reord_lock);
+ }
+#endif
+ rwnx_txq_sta_deinit(rwnx_hw, cur);
+ error = rwnx_send_me_sta_del(rwnx_hw, cur->sta_idx, false);
+ if (error != 0 && error != -EPIPE)
+ return;
+#ifdef CONFIG_RWNX_BFMER
+ // Disable Beamformer if supported
+ rwnx_bfmer_report_del(rwnx_hw, cur);
+ rwnx_mu_group_sta_del(rwnx_hw, cur);
+#endif /* CONFIG_RWNX_BFMER */
+
+ } else {
+ AICWFDBG(LOGDEBUG, "AICWF sta not found: %pM\n", mac);
+ return;
+ }
+
+ rwnx_update_mesh_power_mode(rwnx_vif);
+}
+
+void apm_probe_sta_work_process(struct work_struct *work)
+{
+ struct apm_probe_sta *probe_sta =
+ container_of(work, struct apm_probe_sta, apmprobesta_work);
+ struct rwnx_vif *rwnx_vif =
+ container_of(probe_sta, struct rwnx_vif, sta_probe);
+ bool found = false;
+ struct rwnx_sta *cur, *tmp;
+
+ u8 *mac = rwnx_vif->sta_probe.sta_mac_addr;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ spin_lock_bh(&rwnx_vif->rwnx_hw->cb_lock);
+ list_for_each_entry_safe(cur, tmp, &rwnx_vif->ap.sta_list, list) {
+ if (!memcmp(cur->mac_addr, mac, ETH_ALEN)) {
+ found = true;
+ break;
+ }
+ }
+ spin_unlock_bh(&rwnx_vif->rwnx_hw->cb_lock);
+
+ AICWFDBG(LOGDEBUG, "AICWF sta %pM found = %d\n", mac, found);
+ if (found)
+ cfg80211_probe_status(rwnx_vif->ndev, mac,
+ (u64)rwnx_vif->sta_probe.probe_id, -1,
+ true, 0, false, GFP_ATOMIC);
+ else
+ cfg80211_probe_status(rwnx_vif->ndev, mac,
+ (u64)rwnx_vif->sta_probe.probe_id, -1,
+ false, 0, false, GFP_ATOMIC);
+ rwnx_vif->sta_probe.probe_id++;
+}
+
+/*
+ * @change_station: Modify a given station. Note that flags changes are not much
+ * validated in cfg80211, in particular the auth/assoc/authorized flags
+ * might come to the driver in invalid combinations -- make sure to check
+ * them, also against the existing state! Drivers must call
+ * cfg80211_check_station_change() to validate the information.
+ */
+static int __maybe_unused rwnx_cfg80211_change_station(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ const u8 *mac,
+ struct station_parameters *params)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct net_device *dev = wdev->netdev;
+ struct rwnx_vif *vif = netdev_priv(dev);
+ struct rwnx_sta *sta;
+
+ sta = rwnx_get_sta(rwnx_hw, mac);
+ if (!sta) {
+ /* Add the TDLS station */
+ if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)) {
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct me_sta_add_cfm me_sta_add_cfm;
+ int error = 0;
+
+ /* Indicate we are in a STA addition process - This will allow
+ * handling potential PS mode change indications correctly
+ */
+ rwnx_hw->adding_sta = true;
+
+ /* Forward the information to the LMAC */
+ error = rwnx_send_me_sta_add(rwnx_hw, params, mac,
+ rwnx_vif->vif_index, &me_sta_add_cfm);
+ if (error)
+ return error;
+
+ // Check the status
+ switch (me_sta_add_cfm.status) {
+ case CO_OK: {
+ int tid;
+
+ sta = &rwnx_hw->sta_table[me_sta_add_cfm.sta_idx];
+ sta->aid = params->aid;
+ sta->sta_idx = me_sta_add_cfm.sta_idx;
+ sta->ch_idx = rwnx_vif->ch_index;
+ sta->vif_idx = rwnx_vif->vif_index;
+ sta->vlan_idx = sta->vif_idx;
+ sta->qos =
+ (params->sta_flags_set & BIT(NL80211_STA_FLAG_WME)) != 0;
+ sta->ht = params->link_sta_params.ht_capa ? 1 : 0;
+ sta->vht = params->link_sta_params.vht_capa ? 1 : 0;
+ sta->acm = 0;
+ for (tid = 0; tid < NX_NB_TXQ_PER_STA; tid++) {
+ int uapsd_bit = rwnx_hwq2uapsd[rwnx_tid2hwq[tid]];
+
+ if (params->uapsd_queues & uapsd_bit)
+ sta->uapsd_tids |= 1 << tid;
+ else
+ sta->uapsd_tids &= ~(1 << tid);
+ }
+ memcpy(sta->mac_addr, mac, ETH_ALEN);
+ /* Ensure that we won't process PS change or channel switch ind
+ */
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ rwnx_txq_sta_init(rwnx_hw, sta,
+ rwnx_txq_vif_get_status(rwnx_vif));
+ if (rwnx_vif->tdls_status == TDLS_SETUP_RSP_TX) {
+ rwnx_vif->tdls_status = TDLS_LINK_ACTIVE;
+ sta->tdls.initiator = true;
+ sta->tdls.active = true;
+ }
+ /* Set TDLS channel switch capability */
+ if ((params->ext_capab[3] & WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH) &&
+ !rwnx_vif->tdls_chsw_prohibited)
+ sta->tdls.chsw_allowed = true;
+ rwnx_vif->sta.tdls_sta = sta;
+ sta->valid = true;
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+#ifdef CONFIG_RWNX_BFMER
+ if (rwnx_hw->mod_params->bfmer)
+ rwnx_send_bfmer_enable(rwnx_hw, sta,
+ params->link_sta_params.vht_capa);
+
+ rwnx_mu_group_sta_init(sta, NULL);
+#endif /* CONFIG_RWNX_BFMER */
+
+#define PRINT_STA_FLAG(f) \
+ (params->sta_flags_set & BIT(NL80211_STA_FLAG_##f) ? "[" #f "]" : "")
+
+ netdev_info(dev, "Add %s TDLS sta %d (%pM) flags=%s%s%s%s%s%s%s",
+ sta->tdls.initiator ? "initiator" : "responder",
+ sta->sta_idx, mac, PRINT_STA_FLAG(AUTHORIZED),
+ PRINT_STA_FLAG(SHORT_PREAMBLE), PRINT_STA_FLAG(WME),
+ PRINT_STA_FLAG(MFP), PRINT_STA_FLAG(AUTHENTICATED),
+ PRINT_STA_FLAG(TDLS_PEER), PRINT_STA_FLAG(ASSOCIATED));
+#undef PRINT_STA_FLAG
+
+ break;
+ }
+ default:
+ error = -EBUSY;
+ break;
+ }
+ rwnx_hw->adding_sta = false;
+ } else {
+ return -EINVAL;
+ }
+ }
+
+ if (params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED))
+ rwnx_send_me_set_control_port_req
+ (rwnx_hw,
+ (params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED)) != 0,
+ sta->sta_idx);
+
+ if (RWNX_VIF_TYPE(vif) == NL80211_IFTYPE_MESH_POINT) {
+ if (params->sta_modify_mask & STATION_PARAM_APPLY_PLINK_STATE) {
+ if (params->plink_state < NUM_NL80211_PLINK_STATES) {
+ rwnx_send_mesh_peer_update_ntf(rwnx_hw, vif, sta->sta_idx,
+ params->plink_state);
+ }
+ }
+
+ if (params->local_pm != NL80211_MESH_POWER_UNKNOWN) {
+ sta->mesh_pm = params->local_pm;
+ rwnx_update_mesh_power_mode(vif);
+ }
+ }
+
+ if (params->vlan) {
+ u8 vlan_idx;
+
+ vif = netdev_priv(params->vlan);
+ vlan_idx = vif->vif_index;
+
+ if (sta->vlan_idx != vlan_idx) {
+ struct rwnx_vif *old_vif;
+
+ old_vif = rwnx_hw->vif_table[sta->vlan_idx];
+ rwnx_txq_sta_switch_vif(sta, old_vif, vif);
+ sta->vlan_idx = vlan_idx;
+
+ if (RWNX_VIF_TYPE(vif) == NL80211_IFTYPE_AP_VLAN &&
+ vif->use_4addr) {
+ WARN(vif->ap_vlan.sta_4a,
+ "4A AP_VLAN interface with more than one sta");
+ vif->ap_vlan.sta_4a = sta;
+ }
+
+ if (RWNX_VIF_TYPE(old_vif) == NL80211_IFTYPE_AP_VLAN &&
+ old_vif->use_4addr) {
+ old_vif->ap_vlan.sta_4a = NULL;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+ * @start_ap: Start acting in AP mode defined by the parameters.
+ */
+static int __maybe_unused rwnx_cfg80211_start_ap(struct wiphy *wiphy,
+ struct net_device *dev,
+ struct cfg80211_ap_settings *settings)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct apm_start_cfm apm_start_cfm;
+ struct rwnx_ipc_elem_var elem;
+ struct rwnx_sta *sta;
+ int error = 0;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ INIT_WORK(&rwnx_vif->sta_probe.apmprobesta_work, apm_probe_sta_work_process);
+ rwnx_vif->sta_probe.apmprobesta_wq =
+ create_singlethread_workqueue("apmprobe_wq");
+ if (!rwnx_vif->sta_probe.apmprobesta_wq) {
+ txrx_err("insufficient memory to create apmprobe_wq.\n");
+ return -ENOBUFS;
+ }
+
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_GO)
+ rwnx_hw->is_p2p_connected = 1;
+ /* Forward the information to the LMAC */
+ error = rwnx_send_apm_start_req(rwnx_hw, rwnx_vif, settings,
+ &apm_start_cfm, &elem);
+ if (error)
+ goto end;
+
+ // Check the status
+ switch (apm_start_cfm.status) {
+ case CO_OK: {
+ u8 txq_status = 0;
+
+ rwnx_vif->ap.bcmc_index = apm_start_cfm.bcmc_idx;
+ rwnx_vif->ap.flags = 0;
+ rwnx_vif->ap.csa = NULL;
+ sta = &rwnx_hw->sta_table[apm_start_cfm.bcmc_idx];
+ sta->valid = true;
+ sta->aid = 0;
+ sta->sta_idx = apm_start_cfm.bcmc_idx;
+ sta->ch_idx = apm_start_cfm.ch_idx;
+ sta->vif_idx = rwnx_vif->vif_index;
+ sta->qos = false;
+ sta->acm = 0;
+ sta->ps.active = false;
+ rwnx_mu_group_sta_init(sta, NULL);
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ rwnx_chanctx_link(rwnx_vif, apm_start_cfm.ch_idx, &settings->chandef);
+ if (rwnx_hw->cur_chanctx != apm_start_cfm.ch_idx)
+ txq_status = RWNX_TXQ_STOP_CHAN;
+
+ rwnx_txq_vif_init(rwnx_hw, rwnx_vif, txq_status);
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+
+ netif_tx_start_all_queues(dev);
+ netif_carrier_on(dev);
+ error = 0;
+ /* If the AP channel is already the active, we probably skip radar
+ * activation on MM_CHANNEL_SWITCH_IND (unless another vif use this
+ * ctxt). In anycase retest if radar detection must be activated
+ */
+ if (txq_status == 0)
+ rwnx_radar_detection_enable_on_cur_channel(rwnx_hw);
+ break;
+ }
+ case CO_BUSY:
+ error = -EINPROGRESS;
+ break;
+ case CO_OP_IN_PROGRESS:
+ error = -EALREADY;
+ break;
+ default:
+ error = -EIO;
+ break;
+ }
+
+ if (error)
+ netdev_info(dev, "Failed to start AP (%d)", error);
+ else
+ netdev_info(dev, "AP started: ch=%d, bcmc_idx=%d channel=%d bw=%d",
+ rwnx_vif->ch_index, rwnx_vif->ap.bcmc_index,
+ ((settings->chandef).chan)->center_freq,
+ ((settings->chandef).width));
+
+end:
+ return error;
+}
+
+/*
+ * @change_beacon: Change the beacon parameters for an access point mode
+ * interface. This should reject the call when AP mode wasn't started.
+ */
+
+static int __maybe_unused rwnx_cfg80211_change_beacon(struct wiphy *wiphy,
+ struct net_device *dev,
+ struct cfg80211_ap_update *info)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *vif = netdev_priv(dev);
+ struct rwnx_bcn *bcn = &vif->ap.bcn;
+ u8 *buf;
+ int error = 0;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ // Build the beacon
+ buf = rwnx_build_bcn(bcn, &info->beacon);
+ if (!buf)
+ return -ENOMEM;
+
+ rwnx_send_bcn(rwnx_hw, buf, vif->vif_index, bcn->len);
+
+ // Forward the information to the LMAC
+ error = rwnx_send_bcn_change(rwnx_hw, vif->vif_index, 0, bcn->len,
+ bcn->head_len, bcn->tim_len, NULL);
+
+ return error;
+}
+
+/*
+ * * @stop_ap: Stop being an AP, including stopping beaconing.
+ */
+static int __maybe_unused rwnx_cfg80211_stop_ap(struct wiphy *wiphy,
+ struct net_device *dev,
+ unsigned int link_id)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ netif_tx_stop_all_queues(dev);
+ netif_carrier_off(dev);
+
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_GO)
+ rwnx_hw->is_p2p_connected = 0;
+ rwnx_radar_cancel_cac(&rwnx_hw->radar);
+ rwnx_send_apm_stop_req(rwnx_hw, rwnx_vif);
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ rwnx_chanctx_unlink(rwnx_vif);
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+
+ /* delete any remaining STA */
+ while (!list_empty(&rwnx_vif->ap.sta_list))
+ rwnx_cfg80211_del_station_compat(wiphy, &rwnx_vif->wdev, NULL);
+
+ /* delete BC/MC STA */
+ rwnx_txq_vif_deinit(rwnx_hw, rwnx_vif);
+ rwnx_del_bcn(&rwnx_vif->ap.bcn);
+ rwnx_del_csa(rwnx_vif);
+
+ flush_workqueue(rwnx_vif->sta_probe.apmprobesta_wq);
+ destroy_workqueue(rwnx_vif->sta_probe.apmprobesta_wq);
+
+ netdev_info(dev, "AP Stopped");
+
+ return 0;
+}
+
+/*
+ * @set_monitor_channel: Set the monitor mode channel for the device. If other
+ * interfaces are active this callback should reject the configuration.
+ * If no interfaces are active or the device is down, the channel should
+ * be stored for when a monitor interface becomes active.
+ *
+ * Also called internaly with chandef set to NULL simply to retrieve the channel
+ * configured at firmware level.
+ */
+static int rwnx_cfg80211_set_monitor_channel(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_chan_def *chandef)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif;
+ struct me_config_monitor_cfm cfm;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+ if (rwnx_hw->monitor_vif == RWNX_INVALID_VIF)
+ return -EINVAL;
+
+ rwnx_vif = rwnx_hw->vif_table[rwnx_hw->monitor_vif];
+
+ // Do nothing if monitor interface is already configured with the requested
+ // channel
+ if (rwnx_chanctx_valid(rwnx_hw, rwnx_vif->ch_index)) {
+ struct rwnx_chanctx *ctxt;
+
+ ctxt = &rwnx_vif->rwnx_hw->chanctx_table[rwnx_vif->ch_index];
+ if (chandef && cfg80211_chandef_identical(&ctxt->chan_def, chandef))
+ return 0;
+ }
+
+ // Always send command to firmware. It allows to retrieve channel context
+ // index and its configuration.
+ if (rwnx_send_config_monitor_req(rwnx_hw, chandef, &cfm))
+ return -EIO;
+
+ // Always re-set channel context info
+ rwnx_chanctx_unlink(rwnx_vif);
+
+ // If there is also a STA interface not yet connected then monitor interface
+ // will only have a channel context after the connection of the STA
+ // interface.
+ if (cfm.chan_index != RWNX_CH_NOT_SET) {
+ struct cfg80211_chan_def mon_chandef;
+
+ if (rwnx_hw->vif_started > 1) {
+ // In this case we just want to update the channel context index not
+ // the channel configuration
+ rwnx_chanctx_link(rwnx_vif, cfm.chan_index, NULL);
+ return -EBUSY;
+ }
+
+ mon_chandef.chan = ieee80211_get_channel(wiphy, cfm.chan.prim20_freq);
+ mon_chandef.center_freq1 = cfm.chan.center1_freq;
+ mon_chandef.center_freq2 = cfm.chan.center2_freq;
+ mon_chandef.width = chnl2bw[cfm.chan.type];
+ rwnx_chanctx_link(rwnx_vif, cfm.chan_index, &mon_chandef);
+ }
+
+ return 0;
+}
+
+/*
+ * @probe_client: probe an associated client, must return a cookie that it
+ * later passes to cfg80211_probe_status().
+ */
+int rwnx_cfg80211_probe_client(struct wiphy *wiphy, struct net_device *dev,
+ const u8 *peer, u64 *cookie)
+{
+ struct rwnx_vif *vif = netdev_priv(dev);
+ struct rwnx_sta *sta = NULL;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ if (RWNX_VIF_TYPE(vif) != NL80211_IFTYPE_AP &&
+ RWNX_VIF_TYPE(vif) != NL80211_IFTYPE_P2P_GO &&
+ RWNX_VIF_TYPE(vif) != NL80211_IFTYPE_AP_VLAN)
+ return -EINVAL;
+ spin_lock_bh(&vif->rwnx_hw->cb_lock);
+ list_for_each_entry(sta, &vif->ap.sta_list, list) {
+ if (sta->valid && ether_addr_equal(sta->mac_addr, peer))
+ break;
+ }
+ spin_unlock_bh(&vif->rwnx_hw->cb_lock);
+
+ if (!sta)
+ return -ENOENT;
+
+ memcpy(vif->sta_probe.sta_mac_addr, peer, 6);
+ queue_work(vif->sta_probe.apmprobesta_wq, &vif->sta_probe.apmprobesta_work);
+
+ *cookie = vif->sta_probe.probe_id;
+
+ return 0;
+}
+
+/*
+ * @set_wiphy_params: Notify that wiphy parameters have changed;
+ * @changed bitfield (see &enum wiphy_params_flags) describes which values
+ * have changed. The actual parameter values are available in
+ * struct wiphy. If returning an error, no value should be changed.
+ */
+static int rwnx_cfg80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx,
+ u32 changed)
+{
+ return 0;
+}
+
+/*
+ * @set_tx_power: set the transmit power according to the parameters,
+ * the power passed is in mBm, to get dBm use MBM_TO_DBM(). The
+ * wdev may be %NULL if power was set for the wiphy, and will
+ * always be %NULL unless the driver supports per-vif TX power
+ * (as advertised by the nl80211 feature flag.)
+ */
+static int rwnx_cfg80211_set_tx_power(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ int radio_idx,
+ enum nl80211_tx_power_setting type,
+ int mbm)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *vif;
+ s8 pwr;
+ int res = 0;
+
+ if (type == NL80211_TX_POWER_AUTOMATIC)
+ pwr = 0x7f;
+ else
+ pwr = MBM_TO_DBM(mbm);
+
+ if (wdev) {
+ vif = container_of(wdev, struct rwnx_vif, wdev);
+ res = rwnx_send_set_power(rwnx_hw, vif->vif_index, pwr, NULL);
+ } else {
+ list_for_each_entry(vif, &rwnx_hw->vifs, list) {
+ res = rwnx_send_set_power(rwnx_hw, vif->vif_index, pwr, NULL);
+ if (res)
+ break;
+ }
+ }
+
+ return res;
+}
+
+/*
+ * @set_power_mgmt: set the power save to one of those two modes:
+ * Power-save off
+ * Power-save on - Dynamic mode
+ */
+static int rwnx_cfg80211_set_power_mgmt(struct wiphy *wiphy,
+ struct net_device *dev, bool enabled,
+ int timeout)
+{
+ return 0;
+}
+
+static int rwnx_cfg80211_set_txq_params(struct wiphy *wiphy,
+ struct net_device *dev,
+ struct ieee80211_txq_params *params)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ u8 hw_queue, aifs, cwmin, cwmax;
+ u32 param;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ hw_queue = rwnx_ac2hwq[0][params->ac];
+
+ aifs = params->aifs;
+ cwmin = fls(params->cwmin);
+ cwmax = fls(params->cwmax);
+
+ /* Store queue information in general structure */
+ param = (u32)(aifs << 0);
+ param |= (u32)(cwmin << 4);
+ param |= (u32)(cwmax << 8);
+ param |= (u32)(params->txop) << 12;
+
+ /* Send the MM_SET_EDCA_REQ message to the FW */
+ return rwnx_send_set_edca(rwnx_hw, hw_queue, param, false,
+ rwnx_vif->vif_index);
+}
+
+/*
+ * @remain_on_channel: Request the driver to remain awake on the specified
+ * channel for the specified duration to complete an off-channel
+ * operation (e.g., public action frame exchange). When the driver is
+ * ready on the requested channel, it must indicate this with an event
+ * notification by calling cfg80211_ready_on_channel().
+ */
+static int rwnx_cfg80211_remain_on_channel_(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ struct ieee80211_channel *chan,
+ unsigned int duration, u64 *cookie,
+ bool mgmt_roc_flag)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ // struct rwnx_vif *rwnx_vif = netdev_priv(wdev->netdev);
+ struct rwnx_vif *rwnx_vif = container_of(wdev, struct rwnx_vif, wdev);
+ struct rwnx_roc_elem *roc_elem;
+ struct mm_add_if_cfm add_if_cfm;
+ struct mm_remain_on_channel_cfm roc_cfm;
+ int error;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ /* For debug purpose (use ftrace kernel option) */
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_roc(rwnx_vif->vif_index, chan->center_freq, duration);
+#endif
+ /* Check that no other RoC procedure has been launched */
+ if (rwnx_hw->roc_elem) {
+ usleep_range(2000, 4000);
+ if (rwnx_hw->roc_elem) {
+ AICWFDBG(LOGDEBUG, "AICWF remain_on_channel fail\n");
+ return -EBUSY;
+ }
+ }
+
+ AICWFDBG(LOGDEBUG, "AICWF remain:%d,%d,%d\n",
+ rwnx_vif->vif_index, rwnx_vif->is_p2p_vif,
+ rwnx_hw->is_p2p_alive);
+#ifdef CONFIG_AIC8800_USE_P2P0
+ if (rwnx_vif->is_p2p_vif) {
+#else
+ if (rwnx_vif == rwnx_hw->p2p_dev_vif && !rwnx_vif->up) {
+#endif
+ if (!rwnx_hw->is_p2p_alive) {
+ error = rwnx_send_add_if(rwnx_hw, rwnx_vif->wdev.address,
+ // wdev->netdev->dev_addr,
+ RWNX_VIF_TYPE(rwnx_vif),
+ false, &add_if_cfm);
+ if (error)
+ return -EIO;
+
+ if (add_if_cfm.status != 0)
+ return -EIO;
+
+ /* Save the index retrieved from LMAC */
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ rwnx_vif->vif_index = add_if_cfm.inst_nbr;
+ rwnx_vif->up = true;
+ rwnx_hw->vif_started++;
+ rwnx_hw->vif_table[add_if_cfm.inst_nbr] = rwnx_vif;
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+ rwnx_hw->is_p2p_alive = 1;
+#ifndef CONFIG_AIC8800_USE_P2P0
+ mod_timer(&rwnx_hw->p2p_alive_timer,
+ jiffies + msecs_to_jiffies(1000));
+ atomic_set(&rwnx_hw->p2p_alive_timer_count, 0);
+#endif
+ } else {
+#ifndef CONFIG_AIC8800_USE_P2P0
+ mod_timer(&rwnx_hw->p2p_alive_timer,
+ jiffies + msecs_to_jiffies(1000));
+ atomic_set(&rwnx_hw->p2p_alive_timer_count, 0);
+#endif
+ }
+ }
+ /* Allocate a temporary RoC element */
+ roc_elem = kmalloc_obj(*roc_elem, GFP_KERNEL);
+
+ /* Verify that element has well been allocated */
+ if (!roc_elem)
+ return -ENOMEM;
+
+ /* Initialize the RoC information element */
+ roc_elem->wdev = wdev;
+ roc_elem->chan = chan;
+ roc_elem->duration = duration;
+ roc_elem->mgmt_roc = mgmt_roc_flag;
+ roc_elem->on_chan = false;
+
+ /* Initialize the OFFCHAN TX queue to allow off-channel transmissions */
+ rwnx_txq_offchan_init(rwnx_vif);
+
+ /* Forward the information to the FMAC */
+ rwnx_hw->roc_elem = roc_elem;
+ error = rwnx_send_roc(rwnx_hw, rwnx_vif, chan, duration, &roc_cfm);
+
+ /* If no error, keep all the information for handling of end of procedure */
+ if (error == 0) {
+ /* Set the cookie value */
+ *cookie = (u64)(rwnx_hw->roc_cookie_cnt);
+ if (roc_cfm.status) {
+ // failed to roc
+ rwnx_hw->roc_elem = NULL;
+ kfree(roc_elem);
+ rwnx_txq_offchan_deinit(rwnx_vif);
+ return -EBUSY;
+ }
+ } else {
+ /* Free the allocated element */
+ rwnx_hw->roc_elem = NULL;
+ kfree(roc_elem);
+ rwnx_txq_offchan_deinit(rwnx_vif);
+ }
+
+ return error;
+}
+
+static int rwnx_cfg80211_remain_on_channel(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ struct ieee80211_channel *chan,
+ unsigned int duration, u64 *cookie,
+ const u8 *rx_addr)
+{
+ return rwnx_cfg80211_remain_on_channel_(wiphy, wdev, chan,
+ duration, cookie, false);
+}
+
+/*
+ * @cancel_remain_on_channel: Cancel an on-going remain-on-channel operation.
+ * This allows the operation to be terminated prior to timeout based on
+ * the duration value.
+ */
+static int rwnx_cfg80211_cancel_remain_on_channel(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ u64 cookie)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ struct rwnx_vif *rwnx_vif =
+ container_of(wdev, struct rwnx_vif, wdev); // netdev_priv(wdev->netdev);
+#endif
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ /* For debug purpose (use ftrace kernel option) */
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_cancel_roc(rwnx_vif->vif_index);
+#endif
+ /* Check if a RoC procedure is pending */
+ if (!rwnx_hw->roc_elem)
+ return 0;
+
+ /* Forward the information to the FMAC */
+ return rwnx_send_cancel_roc(rwnx_hw);
+}
+
+static inline bool IS_2P4GHZ(int freq)
+{
+ return (freq >= 2412 && freq <= 2484);
+}
+
+static inline bool IS_5GHZ(int freq)
+{
+ return (freq >= 4000 && freq <= 5895);
+}
+
+#define DEFAULT_NOISE_FLOOR_2GHZ (-89)
+#define DEFAULT_NOISE_FLOOR_5GHZ (-92)
+
+/*
+ * @dump_survey: get site survey information.
+ */
+static int rwnx_cfg80211_dump_survey(struct wiphy *wiphy,
+ struct net_device *netdev, int idx,
+ struct survey_info *info)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct ieee80211_supported_band *sband;
+ struct rwnx_survey_info *rwnx_survey;
+
+ // RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ if (idx >= ARRAY_SIZE(rwnx_hw->survey))
+ return -ENOENT;
+
+ rwnx_survey = &rwnx_hw->survey[idx];
+
+ // Check if provided index matches with a supported 2.4GHz channel
+ sband = wiphy->bands[NL80211_BAND_2GHZ];
+ if (sband && idx >= sband->n_channels) {
+ idx -= sband->n_channels;
+ sband = NULL;
+ }
+
+ if (rwnx_hw->band_5g_support) {
+ if (!sband) {
+ // Check if provided index matches with a supported 5GHz channel
+ sband = wiphy->bands[NL80211_BAND_5GHZ];
+
+ if (!sband || idx >= sband->n_channels)
+ return -ENOENT;
+ }
+ } else {
+ if (!sband || idx >= sband->n_channels)
+ return -ENOENT;
+ }
+
+ // Fill the survey
+ info->channel = &sband->channels[idx];
+ info->filled = rwnx_survey->filled;
+
+ if (rwnx_survey->filled != 0) {
+ SURVEY_TIME(info) = (u64)rwnx_survey->chan_time_ms;
+ SURVEY_TIME_BUSY(info) = (u64)rwnx_survey->chan_time_busy_ms;
+ // info->noise = rwnx_survey->noise_dbm;
+ info->noise =
+ ((IS_2P4GHZ(info->channel->center_freq)) ? DEFAULT_NOISE_FLOOR_2GHZ
+ : (IS_5GHZ(info->channel->center_freq))
+ ? DEFAULT_NOISE_FLOOR_5GHZ
+ : DEFAULT_NOISE_FLOOR_5GHZ);
+
+ // Set the survey report as not used
+ if (info->noise == 0)
+ rwnx_survey->filled = 0;
+ else
+ rwnx_survey->filled |= SURVEY_INFO_NOISE_DBM;
+ }
+
+ return 0;
+}
+
+/*
+ * @get_channel: Get the current operating channel for the virtual interface.
+ * For monitor interfaces, it should return %NULL unless there's a single
+ * current monitoring channel.
+ */
+static int rwnx_cfg80211_get_channel(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ unsigned int link_id,
+ struct cfg80211_chan_def *chandef)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = container_of(wdev, struct rwnx_vif, wdev);
+ struct rwnx_chanctx *ctxt;
+
+ if (!rwnx_vif->up)
+ return -ENODATA;
+
+ if (rwnx_vif->vif_index == rwnx_hw->monitor_vif)
+ // retrieve channel from firmware
+ rwnx_cfg80211_set_monitor_channel(wiphy, rwnx_vif->ndev, NULL);
+
+ // Check if channel context is valid
+ if (!rwnx_chanctx_valid(rwnx_hw, rwnx_vif->ch_index))
+ return -ENODATA;
+
+ ctxt = &rwnx_hw->chanctx_table[rwnx_vif->ch_index];
+ *chandef = ctxt->chan_def;
+ return 0;
+}
+
+/*
+ * @mgmt_tx: Transmit a management frame.
+ */
+
+static int rwnx_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
+ struct cfg80211_mgmt_tx_params *params,
+ u64 *cookie)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif =
+ container_of(wdev, struct rwnx_vif, wdev); // netdev_priv(wdev->netdev);
+ struct rwnx_sta *rwnx_sta;
+ struct ieee80211_channel *channel = params->chan;
+ const u8 *buf = params->buf;
+ struct ieee80211_mgmt *mgmt = (void *)buf;
+ bool ap = false;
+ bool offchan = false;
+
+ /* Check if provided VIF is an AP or a STA one */
+ switch (RWNX_VIF_TYPE(rwnx_vif)) {
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ case NL80211_IFTYPE_AP_VLAN:
+ rwnx_vif = rwnx_vif->ap_vlan.master;
+ fallthrough;
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_P2P_GO:
+ case NL80211_IFTYPE_MESH_POINT:
+ ap = true;
+ break;
+ case NL80211_IFTYPE_P2P_CLIENT:
+#endif
+ case NL80211_IFTYPE_STATION:
+ default:
+ break;
+ }
+
+ /* Get STA on which management frame has to be sent */
+ rwnx_sta =
+ rwnx_retrieve_sta(rwnx_hw, rwnx_vif, mgmt->da, mgmt->frame_control, ap);
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_mgmt_tx((channel) ? channel->center_freq : 0, rwnx_vif->vif_index,
+ (rwnx_sta) ? rwnx_sta->sta_idx : 0xFF, mgmt);
+#endif
+ if (ap || rwnx_sta)
+ goto send_frame;
+
+ /* Not an AP interface sending frame to unknown STA:
+ * This is allowed for external authetication
+ */
+ if (rwnx_vif->sta.external_auth && ieee80211_is_auth(mgmt->frame_control))
+ goto send_frame;
+
+ /* Otherwise ROC is needed */
+ if (!channel) {
+ AICWFDBG(LOGDEBUG, "AICWF mgmt_tx fail since channel\n");
+ return -EINVAL;
+ }
+
+ /* Check that a RoC is already pending */
+ if (rwnx_hw->roc_elem) {
+ /* Get VIF used for current ROC */
+ struct rwnx_vif *rwnx_roc_vif =
+ container_of(rwnx_hw->roc_elem->wdev, struct rwnx_vif,
+ wdev); // netdev_priv(rwnx_hw->roc_elem->wdev->netdev);
+
+ /* Check if RoC channel is the same than the required one */
+ if (rwnx_hw->roc_elem->chan->center_freq != channel->center_freq ||
+ rwnx_vif->vif_index != rwnx_roc_vif->vif_index) {
+ AICWFDBG(LOGDEBUG, "AICWF mgmt rx chan invalid: %d, %d",
+ rwnx_hw->roc_elem->chan->center_freq, channel->center_freq);
+ return -EINVAL;
+ }
+ } else {
+ u64 cookie;
+ int error;
+
+ AICWFDBG(LOGDEBUG, "AICWF mgmt rx remain on chan\n");
+
+ /* Start a ROC procedure for 30ms */
+ error = rwnx_cfg80211_remain_on_channel_(wiphy, wdev, channel, 30,
+ &cookie, true);
+ if (error) {
+ AICWFDBG(LOGDEBUG, "AICWF mgmt rx chan err\n");
+ return error;
+ }
+ }
+ offchan = true;
+
+send_frame:
+ return rwnx_start_mgmt_xmit(rwnx_vif, rwnx_sta, params, offchan, cookie);
+}
+
+/*
+ * @start_radar_detection: Start radar detection in the driver.
+ */
+static int __maybe_unused rwnx_cfg80211_start_radar_detection(struct wiphy *wiphy,
+ struct net_device *dev,
+ struct cfg80211_chan_def *chandef,
+ u32 cac_time_ms, int link_id)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct apm_start_cac_cfm cfm;
+
+ rwnx_radar_start_cac(&rwnx_hw->radar, cac_time_ms, rwnx_vif);
+ rwnx_send_apm_start_cac_req(rwnx_hw, rwnx_vif, chandef, &cfm);
+
+ if (cfm.status == CO_OK) {
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ rwnx_chanctx_link(rwnx_vif, cfm.ch_idx, chandef);
+ if (rwnx_hw->cur_chanctx == rwnx_vif->ch_index)
+ rwnx_radar_detection_enable(&rwnx_hw->radar,
+ RWNX_RADAR_DETECT_REPORT,
+ RWNX_RADAR_RIU);
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+ } else {
+ return -EIO;
+ }
+ return 0;
+}
+
+/*
+ * @update_ft_ies: Provide updated Fast BSS Transition information to the
+ * driver. If the SME is in the driver/firmware, this information can be
+ * used in building Authentication and Reassociation Request frames.
+ */
+static int rwnx_cfg80211_update_ft_ies(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_update_ft_ies_params *ftie)
+{
+ AICWFDBG(LOGDEBUG, "AICWF %s\n", __func__);
+ return 0;
+}
+
+/*
+ * @set_cqm_rssi_config: Configure connection quality monitor RSSI threshold.
+ */
+static int rwnx_cfg80211_set_cqm_rssi_config(struct wiphy *wiphy,
+ struct net_device *dev,
+ s32 rssi_thold,
+ u32 rssi_hyst)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+
+ return rwnx_send_cfg_rssi_req(rwnx_hw, rwnx_vif->vif_index, rssi_thold,
+ rssi_hyst);
+}
+
+/*
+ *
+ * @channel_switch: initiate channel-switch procedure (with CSA). Driver is
+ * responsible for veryfing if the switch is possible. Since this is
+ * inherently tricky driver may decide to disconnect an interface later
+ * with cfg80211_stop_iface(). This doesn't mean driver can accept
+ * everything. It should do it's best to verify requests and reject them
+ * as soon as possible.
+ */
+static int __maybe_unused rwnx_cfg80211_channel_switch(struct wiphy *wiphy,
+ struct net_device *dev,
+ struct cfg80211_csa_settings *params)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *vif = netdev_priv(dev);
+ struct rwnx_bcn *bcn, *bcn_after;
+ struct rwnx_csa *csa;
+ u16 csa_oft[BCN_MAX_CSA_CPT];
+ u8 *buf;
+ int i, error = 0;
+
+ if (vif->ap.csa)
+ return -EBUSY;
+
+ if (params->n_counter_offsets_beacon > BCN_MAX_CSA_CPT)
+ return -EINVAL;
+
+ /* Build the new beacon with CSA IE */
+ bcn = &vif->ap.bcn;
+ buf = rwnx_build_bcn(bcn, ¶ms->beacon_csa);
+ if (!buf)
+ return -ENOMEM;
+
+ memset(csa_oft, 0, sizeof(csa_oft));
+ for (i = 0; i < params->n_counter_offsets_beacon; i++)
+ csa_oft[i] =
+ params->counter_offsets_beacon[i] + bcn->head_len + bcn->tim_len;
+
+ /* If count is set to 0 (i.e anytime after this beacon) force it to 2 */
+ if (params->count == 0) {
+ params->count = 2;
+ for (i = 0; i < params->n_counter_offsets_beacon; i++)
+ buf[csa_oft[i]] = 2;
+ }
+
+ error = rwnx_send_bcn(rwnx_hw, buf, vif->vif_index, bcn->len);
+ if (error)
+ goto end;
+
+ /* Build the beacon to use after CSA. It will only be sent to fw once
+ * CSA is over, but do it before sending the beacon as it must be ready
+ * when CSA is finished.
+ */
+ csa = kzalloc_obj(*csa, GFP_KERNEL);
+ if (!csa) {
+ error = -ENOMEM;
+ goto end;
+ }
+
+ bcn_after = &csa->bcn;
+ buf = rwnx_build_bcn(bcn_after, ¶ms->beacon_after);
+ if (!buf) {
+ error = -ENOMEM;
+ rwnx_del_csa(vif);
+ goto end;
+ }
+
+ error = rwnx_send_bcn(rwnx_hw, buf, vif->vif_index, bcn_after->len);
+ if (error)
+ goto end;
+
+ vif->ap.csa = csa;
+ csa->vif = vif;
+ csa->chandef = params->chandef;
+
+ /* Send new Beacon. FW will extract channel and count from the beacon */
+ error = rwnx_send_bcn_change(rwnx_hw, vif->vif_index, 0, bcn->len,
+ bcn->head_len, bcn->tim_len, csa_oft);
+
+ if (error) {
+ rwnx_del_csa(vif);
+ goto end;
+ } else {
+ INIT_WORK(&csa->work, rwnx_csa_finish);
+ cfg80211_ch_switch_started_notify(dev, &csa->chandef, 0, params->count,
+ false);
+ }
+end:
+ return error;
+}
+
+/*
+ * @tdls_oper: execute TDLS operation
+ */
+static int __maybe_unused rwnx_cfg80211_tdls_oper(struct wiphy *wiphy,
+ struct net_device *dev,
+ const u8 *peer, enum nl80211_tdls_operation oper)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ int error;
+
+ if (oper != NL80211_TDLS_DISABLE_LINK)
+ return 0;
+
+ if (!rwnx_vif->sta.tdls_sta) {
+ AICWFDBG(LOGDEBUG, "AICWF %s: TDLS station %pM does not exist\n", __func__, peer);
+ return -ENOLINK;
+ }
+
+ if (memcmp(rwnx_vif->sta.tdls_sta->mac_addr, peer, ETH_ALEN) == 0) {
+ /* Disable Channel Switch */
+ if (!rwnx_send_tdls_cancel_chan_switch_req(rwnx_hw, rwnx_vif,
+ rwnx_vif->sta.tdls_sta, NULL))
+ rwnx_vif->sta.tdls_sta->tdls.chsw_en = false;
+
+ netdev_info(dev, "Del TDLS sta %d (%pM)",
+ rwnx_vif->sta.tdls_sta->sta_idx,
+ rwnx_vif->sta.tdls_sta->mac_addr);
+ /* Ensure that we won't process PS change ind */
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ rwnx_vif->sta.tdls_sta->ps.active = false;
+ rwnx_vif->sta.tdls_sta->valid = false;
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+ rwnx_txq_sta_deinit(rwnx_hw, rwnx_vif->sta.tdls_sta);
+ error = rwnx_send_me_sta_del(rwnx_hw,
+ rwnx_vif->sta.tdls_sta->sta_idx,
+ true);
+ if (error != 0 && error != -EPIPE)
+ return error;
+
+#ifdef CONFIG_RWNX_BFMER
+ // Disable Beamformer if supported
+ rwnx_bfmer_report_del(rwnx_hw, rwnx_vif->sta.tdls_sta);
+ rwnx_mu_group_sta_del(rwnx_hw, rwnx_vif->sta.tdls_sta);
+#endif /* CONFIG_RWNX_BFMER */
+
+ /* Set TDLS not active */
+ rwnx_vif->sta.tdls_sta->tdls.active = false;
+ // Remove TDLS station
+ rwnx_vif->tdls_status = TDLS_LINK_IDLE;
+ rwnx_vif->sta.tdls_sta = NULL;
+ }
+
+ return 0;
+}
+
+/*
+ * @tdls_channel_switch: enable TDLS channel switch
+ */
+static int __maybe_unused rwnx_cfg80211_tdls_channel_switch(struct wiphy *wiphy,
+ struct net_device *dev,
+ const u8 *addr, u8 oper_class,
+ struct cfg80211_chan_def *chandef)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_sta *rwnx_sta = rwnx_vif->sta.tdls_sta;
+ struct tdls_chan_switch_cfm cfm;
+ int error;
+
+ if (!rwnx_sta || memcmp(addr, rwnx_sta->mac_addr, ETH_ALEN)) {
+ AICWFDBG(LOGDEBUG, "AICWF %s: TDLS station %pM doesn't exist\n", __func__, addr);
+ return -ENOLINK;
+ }
+
+ if (!rwnx_sta->tdls.chsw_allowed) {
+ AICWFDBG(LOGDEBUG,
+ "AICWF %s: TDLS station %pM does not support TDLS channel switch\n",
+ __func__, addr);
+ return -EOPNOTSUPP;
+ }
+
+ error = rwnx_send_tdls_chan_switch_req(rwnx_hw, rwnx_vif, rwnx_sta,
+ rwnx_sta->tdls.initiator, oper_class,
+ chandef, &cfm);
+ if (error)
+ return error;
+
+ if (!cfm.status) {
+ rwnx_sta->tdls.chsw_en = true;
+ return 0;
+ }
+ AICWFDBG(LOGDEBUG,
+ "AICWF %s: TDLS channel switch already enabled and only one is supported\n",
+ __func__);
+ return -EALREADY;
+}
+
+/*
+ * @tdls_cancel_channel_switch: disable TDLS channel switch
+ */
+static void __maybe_unused
+rwnx_cfg80211_tdls_cancel_channel_switch(struct wiphy *wiphy,
+ struct net_device *dev,
+ const u8 *addr)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_sta *rwnx_sta = rwnx_vif->sta.tdls_sta;
+ struct tdls_cancel_chan_switch_cfm cfm;
+
+ if (!rwnx_sta)
+ return;
+
+ if (!rwnx_send_tdls_cancel_chan_switch_req(rwnx_hw, rwnx_vif, rwnx_sta,
+ &cfm))
+ rwnx_sta->tdls.chsw_en = false;
+}
+
+/*
+ * @change_bss: Modify parameters for a given BSS (mainly for AP mode).
+ */
+static int __maybe_unused rwnx_cfg80211_change_bss(struct wiphy *wiphy,
+ struct net_device *dev,
+ struct bss_parameters *params)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ int res = -EOPNOTSUPP;
+
+ if ((RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_AP ||
+ RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_GO) &&
+ params->ap_isolate > -1) {
+ if (params->ap_isolate)
+ rwnx_vif->ap.flags |= RWNX_AP_ISOLATE;
+ else
+ rwnx_vif->ap.flags &= ~RWNX_AP_ISOLATE;
+ res = 0;
+ }
+ return res;
+}
+
+static void rwnx_convert_sta_flags(u32 flags, struct station_info *sinfo)
+{
+ struct nl80211_sta_flag_update *sfu;
+
+ sinfo->filled |= BIT(NL80211_STA_INFO_STA_FLAGS);
+ sfu = &sinfo->sta_flags;
+ sfu->mask = BIT(NL80211_STA_FLAG_WME) | BIT(NL80211_STA_FLAG_AUTHORIZED) |
+ BIT(NL80211_STA_FLAG_ASSOCIATED) |
+ BIT(NL80211_STA_FLAG_AUTHENTICATED);
+
+ if (flags == (u32)RWNX_DRV_STATUS_CONNECTED) {
+ sfu->set |= (BIT(NL80211_STA_FLAG_WME) | BIT(NL80211_STA_FLAG_AUTHORIZED) |
+ BIT(NL80211_STA_FLAG_ASSOCIATED) |
+ BIT(NL80211_STA_FLAG_AUTHENTICATED));
+ }
+ sfu->mask |= BIT(NL80211_STA_FLAG_TDLS_PEER);
+ sfu->set &= ~BIT(NL80211_STA_FLAG_TDLS_PEER);
+}
+
+static void rwnx_fill_bss_param(struct station_info *sinfo,
+ struct rwnx_vif *vif)
+{
+ u16 capability = 0;
+
+ sinfo->filled |= BIT(NL80211_STA_INFO_BSS_PARAM);
+ sinfo->bss_param.beacon_interval =
+ (vif->sta.b_interval > 0) ? vif->sta.b_interval : 100;
+ sinfo->bss_param.dtim_period =
+ (vif->sta.dtim_peroid > 0) ? vif->sta.dtim_peroid : 1;
+
+ capability = vif->sta.capability;
+ if (capability & WLAN_CAPABILITY_SHORT_PREAMBLE)
+ sinfo->bss_param.flags |= BSS_PARAM_FLAGS_SHORT_PREAMBLE;
+ if (capability & WLAN_CAPABILITY_SHORT_SLOT_TIME)
+ sinfo->bss_param.flags |= BSS_PARAM_FLAGS_SHORT_SLOT_TIME;
+}
+
+static int rwnx_fill_station_info(struct rwnx_sta *sta, struct rwnx_vif *vif,
+ struct station_info *sinfo)
+{
+ struct rwnx_sta_stats *stats = &sta->stats;
+ struct rx_vector_1 *rx_vect1 = &stats->last_rx.rx_vect1;
+ union rwnx_rate_ctrl_info *rate_info;
+ struct mm_get_sta_info_cfm cfm;
+
+ rwnx_send_get_sta_info_req(vif->rwnx_hw, sta->sta_idx, &cfm);
+ sinfo->tx_failed = cfm.txfailed;
+ rate_info = (union rwnx_rate_ctrl_info *)&cfm.rate_info;
+
+ AICWFDBG(LOGDEBUG,
+ "%s ModTx(%d):%d TxIndex:%d ModRx(%d):%d RxHTIndex:%d RxVHTIndex:%d RxHEIndex:%d RSSI:%d \r\n",
+ __func__, rate_info->bw_tx, rate_info->format_mod_tx,
+ rate_info->mcs_index_tx, rx_vect1->ch_bw, rx_vect1->format_mod,
+ rx_vect1->ht.mcs, rx_vect1->vht.mcs, rx_vect1->he.mcs,
+ (s8)cfm.rssi);
+
+ switch (rate_info->format_mod_tx) {
+ case FORMATMOD_NON_HT:
+ case FORMATMOD_NON_HT_DUP_OFDM:
+ sinfo->txrate.flags = 0;
+ sinfo->txrate.legacy = tx_legrates_lut_rate[rate_info->mcs_index_tx];
+ break;
+ case FORMATMOD_HT_MF:
+ case FORMATMOD_HT_GF:
+ sinfo->txrate.flags = RATE_INFO_FLAGS_MCS;
+ sinfo->txrate.mcs = rate_info->mcs_index_tx;
+ break;
+ case FORMATMOD_VHT:
+ sinfo->txrate.flags = RATE_INFO_FLAGS_VHT_MCS;
+ sinfo->txrate.mcs = rate_info->mcs_index_tx;
+ break;
+ case FORMATMOD_HE_MU:
+ case FORMATMOD_HE_SU:
+ case FORMATMOD_HE_ER:
+ sinfo->txrate.flags = RATE_INFO_FLAGS_HE_MCS;
+ sinfo->txrate.mcs = rate_info->mcs_index_tx;
+ break;
+ default:
+ return -EINVAL;
+ }
+ switch (rate_info->bw_tx) {
+ case PHY_CHNL_BW_20:
+ sinfo->txrate.bw = RATE_INFO_BW_20;
+ break;
+ case PHY_CHNL_BW_40:
+ sinfo->txrate.bw = RATE_INFO_BW_40;
+ break;
+ case PHY_CHNL_BW_80:
+ sinfo->txrate.bw = RATE_INFO_BW_80;
+ break;
+ case PHY_CHNL_BW_160:
+ sinfo->txrate.bw = RATE_INFO_BW_160;
+ break;
+ default:
+ sinfo->txrate.bw = RATE_INFO_BW_HE_RU;
+ break;
+ }
+
+ sinfo->txrate.nss = 1;
+ sinfo->filled |=
+ (BIT(NL80211_STA_INFO_TX_BITRATE) | BIT(NL80211_STA_INFO_TX_FAILED));
+
+ if (RWNX_VIF_TYPE(vif) == NL80211_IFTYPE_STATION ||
+ RWNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_CLIENT) {
+ sinfo->connected_time =
+ jiffies_to_msecs(jiffies - vif->sta.link_time) / 1000;
+ sinfo->filled |= BIT(NL80211_STA_INFO_CONNECTED_TIME);
+ }
+
+ sinfo->inactive_time =
+ jiffies_to_msecs(jiffies - vif->rwnx_hw->stats.last_tx);
+ sinfo->rx_bytes = vif->net_stats.rx_bytes;
+ sinfo->tx_bytes = vif->net_stats.tx_bytes;
+ sinfo->tx_packets = vif->net_stats.tx_packets;
+ sinfo->rx_packets = vif->net_stats.rx_packets;
+ sinfo->signal = (s8)cfm.rssi;
+ sinfo->rxrate.nss = 1;
+
+ switch (rx_vect1->ch_bw) {
+ case PHY_CHNL_BW_20:
+ sinfo->rxrate.bw = RATE_INFO_BW_20;
+ break;
+ case PHY_CHNL_BW_40:
+ sinfo->rxrate.bw = RATE_INFO_BW_40;
+ break;
+ case PHY_CHNL_BW_80:
+ sinfo->rxrate.bw = RATE_INFO_BW_80;
+ break;
+ case PHY_CHNL_BW_160:
+ sinfo->rxrate.bw = RATE_INFO_BW_160;
+ break;
+ default:
+ sinfo->rxrate.bw = RATE_INFO_BW_HE_RU;
+ break;
+ }
+
+ switch (rx_vect1->format_mod) {
+ case FORMATMOD_NON_HT:
+ case FORMATMOD_NON_HT_DUP_OFDM:
+ sinfo->rxrate.flags = 0;
+ sinfo->rxrate.legacy = legrates_lut[rx_vect1->leg_rate].rate;
+ break;
+ case FORMATMOD_HT_MF:
+ case FORMATMOD_HT_GF:
+ sinfo->rxrate.flags = RATE_INFO_FLAGS_MCS;
+ if (rx_vect1->ht.short_gi)
+ sinfo->rxrate.flags |= RATE_INFO_FLAGS_SHORT_GI;
+ sinfo->rxrate.mcs = rx_vect1->ht.mcs;
+ break;
+ case FORMATMOD_VHT:
+ sinfo->rxrate.flags = RATE_INFO_FLAGS_VHT_MCS;
+ if (rx_vect1->vht.short_gi)
+ sinfo->rxrate.flags |= RATE_INFO_FLAGS_SHORT_GI;
+ sinfo->rxrate.mcs = rx_vect1->vht.mcs;
+ break;
+ case FORMATMOD_HE_MU:
+ sinfo->rxrate.he_ru_alloc = rx_vect1->he.ru_size;
+ fallthrough;
+ case FORMATMOD_HE_SU:
+ case FORMATMOD_HE_ER:
+ sinfo->rxrate.flags = RATE_INFO_FLAGS_HE_MCS;
+ sinfo->rxrate.mcs = rx_vect1->he.mcs;
+ sinfo->rxrate.he_gi = rx_vect1->he.gi_type;
+ sinfo->rxrate.he_dcm = rx_vect1->he.dcm;
+ break;
+ default:
+ return -EINVAL;
+ }
+ sinfo->filled |=
+ (BIT(NL80211_STA_INFO_INACTIVE_TIME) |
+ BIT(NL80211_STA_INFO_RX_BYTES64) | BIT(NL80211_STA_INFO_TX_BYTES64) |
+ BIT(NL80211_STA_INFO_RX_PACKETS) | BIT(NL80211_STA_INFO_TX_PACKETS) |
+ BIT(NL80211_STA_INFO_SIGNAL) | BIT(NL80211_STA_INFO_RX_BITRATE));
+ return 0;
+}
+
+/*
+ * @get_station: get station information for the station identified by @mac
+ */
+static int rwnx_cfg80211_get_station(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ const u8 *mac,
+ struct station_info *sinfo)
+{
+ struct net_device *dev = wdev->netdev;
+ struct rwnx_vif *vif = netdev_priv(dev);
+ struct rwnx_sta *sta = NULL;
+
+ if (RWNX_VIF_TYPE(vif) == NL80211_IFTYPE_MONITOR) {
+ return -EINVAL;
+ } else if (RWNX_VIF_TYPE(vif) == NL80211_IFTYPE_STATION ||
+ RWNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_CLIENT) {
+ if (vif->sta.ap && ether_addr_equal(vif->sta.ap->mac_addr, mac))
+ sta = vif->sta.ap;
+ if (sta) {
+ rwnx_convert_sta_flags((u32)atomic_read(&vif->drv_conn_state),
+ sinfo);
+ rwnx_fill_bss_param(sinfo, vif);
+ return rwnx_fill_station_info(sta, vif, sinfo);
+ }
+ } else {
+ struct rwnx_sta *sta_iter;
+
+ spin_lock_bh(&vif->rwnx_hw->cb_lock);
+ list_for_each_entry(sta_iter, &vif->ap.sta_list, list) {
+ if (sta_iter->valid && ether_addr_equal(sta_iter->mac_addr, mac)) {
+ sta = sta_iter;
+ break;
+ }
+ }
+ spin_unlock_bh(&vif->rwnx_hw->cb_lock);
+ if (sta)
+ return rwnx_fill_station_info(sta, vif, sinfo);
+ }
+
+ return -ENOENT;
+}
+
+/*
+ * @dump_station: dump station callback -- resume dump at index @idx
+ */
+static int rwnx_cfg80211_dump_station(struct wiphy *wiphy,
+ struct wireless_dev *wdev, int idx, u8 *mac,
+ struct station_info *sinfo)
+{
+ struct net_device *dev = wdev->netdev;
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_sta *sta_iter, *sta = NULL;
+ struct mesh_peer_info_cfm peer_info_cfm;
+ struct cfg80211_bss *bss = NULL;
+ const struct cfg80211_bss_ies *ies;
+ const u8 *tim = NULL;
+ int i = 0;
+
+ if (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION ||
+ RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_CLIENT) {
+ if (idx == 0) {
+ bss = cfg80211_get_bss(wiphy, NULL, rwnx_vif->sta.bssid,
+ rwnx_vif->sta.ssid, rwnx_vif->sta.ssid_len,
+ dev->ieee80211_ptr->conn_bss_type,
+ IEEE80211_PRIVACY_ANY);
+ if (bss) {
+ rwnx_vif->sta.capability = bss->capability;
+ rwnx_vif->sta.b_interval = bss->beacon_interval;
+ rcu_read_lock();
+ ies = rcu_dereference(bss->beacon_ies);
+ if (ies && ies->len > 0) {
+ tim = cfg80211_find_ie(WLAN_EID_TIM,
+ ies->data, ies->len);
+ if (tim && tim[1] >= 2)
+ rwnx_vif->sta.dtim_peroid = *(tim + 3);
+ }
+ rcu_read_unlock();
+ cfg80211_put_bss(wiphy, bss);
+ }
+ memcpy(mac, rwnx_vif->sta.bssid, ETH_ALEN);
+ return rwnx_cfg80211_get_station(wiphy, wdev, mac, sinfo);
+ }
+ return -ENOENT;
+ }
+
+ if (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT)
+ return -EOPNOTSUPP;
+
+ list_for_each_entry(sta_iter, &rwnx_vif->ap.sta_list, list) {
+ if (i < idx) {
+ i++;
+ continue;
+ }
+ sta = sta_iter;
+ break;
+ }
+
+ if (!sta)
+ return -ENOENT;
+
+ /* Forward the information to the UMAC */
+ if (rwnx_send_mesh_peer_info_req(rwnx_hw, rwnx_vif, sta->sta_idx,
+ &peer_info_cfm))
+ return -ENOMEM;
+
+ /* Copy peer MAC address */
+ memcpy(mac, &sta->mac_addr, ETH_ALEN);
+
+ /* Fill station information */
+ sinfo->llid = peer_info_cfm.local_link_id;
+ sinfo->plid = peer_info_cfm.peer_link_id;
+ sinfo->plink_state = peer_info_cfm.link_state;
+ sinfo->local_pm = peer_info_cfm.local_ps_mode;
+ sinfo->peer_pm = peer_info_cfm.peer_ps_mode;
+ sinfo->nonpeer_pm = peer_info_cfm.non_peer_ps_mode;
+ sinfo->filled =
+ (BIT(NL80211_STA_INFO_LLID) | BIT(NL80211_STA_INFO_PLID) |
+ BIT(NL80211_STA_INFO_PLINK_STATE) | BIT(NL80211_STA_INFO_LOCAL_PM) |
+ BIT(NL80211_STA_INFO_PEER_PM) | BIT(NL80211_STA_INFO_NONPEER_PM));
+
+ return 0;
+}
+
+/*
+ * @add_mpath: add a fixed mesh path
+ */
+static int __maybe_unused rwnx_cfg80211_add_mpath(struct wiphy *wiphy,
+ struct net_device *dev,
+ const u8 *dst, const u8 *next_hop)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct mesh_path_update_cfm cfm;
+
+ if (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT)
+ return -EOPNOTSUPP;
+
+ return rwnx_send_mesh_path_update_req(rwnx_hw, rwnx_vif, dst, next_hop,
+ &cfm);
+}
+
+/*
+ * @del_mpath: delete a given mesh path
+ */
+static int __maybe_unused rwnx_cfg80211_del_mpath(struct wiphy *wiphy,
+ struct net_device *dev,
+ const u8 *dst)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct mesh_path_update_cfm cfm;
+
+ if (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT)
+ return -EOPNOTSUPP;
+
+ return rwnx_send_mesh_path_update_req(rwnx_hw, rwnx_vif, dst, NULL, &cfm);
+}
+
+/*
+ * @change_mpath: change a given mesh path
+ */
+static int __maybe_unused rwnx_cfg80211_change_mpath(struct wiphy *wiphy,
+ struct net_device *dev,
+ const u8 *dst, const u8 *next_hop)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct mesh_path_update_cfm cfm;
+
+ if (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT)
+ return -EOPNOTSUPP;
+
+ return rwnx_send_mesh_path_update_req(rwnx_hw, rwnx_vif,
+ dst, next_hop, &cfm);
+}
+
+/*
+ * @get_mpath: get a mesh path for the given parameters
+ */
+static int __maybe_unused rwnx_cfg80211_get_mpath(struct wiphy *wiphy,
+ struct net_device *dev,
+ u8 *dst, u8 *next_hop,
+ struct mpath_info *pinfo)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_mesh_path *mesh_path = NULL;
+ struct rwnx_mesh_path *cur;
+
+ if (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT)
+ return -EOPNOTSUPP;
+
+ list_for_each_entry(cur, &rwnx_vif->ap.mpath_list, list) {
+ /*
+ * Compare the path target address and the provided destination address
+ */
+ if (memcmp(dst, &cur->tgt_mac_addr, ETH_ALEN))
+ continue;
+
+ mesh_path = cur;
+ break;
+ }
+
+ if (!mesh_path)
+ return -ENOENT;
+
+ /* Copy next HOP MAC address */
+ if (mesh_path->p_nhop_sta)
+ memcpy(next_hop, &mesh_path->p_nhop_sta->mac_addr, ETH_ALEN);
+
+ /* Fill path information */
+ pinfo->filled = 0;
+ pinfo->generation = rwnx_vif->ap.generation;
+
+ return 0;
+}
+
+/*
+ * @dump_mpath: dump mesh path callback -- resume dump at index @idx
+ */
+static int __maybe_unused rwnx_cfg80211_dump_mpath(struct wiphy *wiphy,
+ struct net_device *dev,
+ int idx, u8 *dst, u8 *next_hop,
+ struct mpath_info *pinfo)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_mesh_path *mesh_path = NULL;
+ struct rwnx_mesh_path *cur;
+ int i = 0;
+
+ if (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT)
+ return -EOPNOTSUPP;
+
+ list_for_each_entry(cur, &rwnx_vif->ap.mpath_list, list) {
+ if (i < idx) {
+ i++;
+ continue;
+ }
+
+ mesh_path = cur;
+ break;
+ }
+
+ if (!mesh_path)
+ return -ENOENT;
+
+ /* Copy target and next hop MAC address */
+ memcpy(dst, &mesh_path->tgt_mac_addr, ETH_ALEN);
+ if (mesh_path->p_nhop_sta)
+ memcpy(next_hop, &mesh_path->p_nhop_sta->mac_addr, ETH_ALEN);
+
+ /* Fill path information */
+ pinfo->filled = 0;
+ pinfo->generation = rwnx_vif->ap.generation;
+
+ return 0;
+}
+
+/*
+ * @get_mpp: get a mesh proxy path for the given parameters
+ */
+static int __maybe_unused rwnx_cfg80211_get_mpp(struct wiphy *wiphy,
+ struct net_device *dev,
+ u8 *dst, u8 *mpp, struct mpath_info *pinfo)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_mesh_proxy *mesh_proxy = NULL;
+ struct rwnx_mesh_proxy *cur;
+
+ if (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT)
+ return -EOPNOTSUPP;
+
+ list_for_each_entry(cur, &rwnx_vif->ap.proxy_list, list) {
+ if (cur->local)
+ continue;
+
+ /*
+ * Compare the path target address and the provided destination address
+ */
+ if (memcmp(dst, &cur->ext_sta_addr, ETH_ALEN))
+ continue;
+
+ mesh_proxy = cur;
+ break;
+ }
+
+ if (!mesh_proxy)
+ return -ENOENT;
+
+ memcpy(mpp, &mesh_proxy->proxy_addr, ETH_ALEN);
+
+ /* Fill path information */
+ pinfo->filled = 0;
+ pinfo->generation = rwnx_vif->ap.generation;
+
+ return 0;
+}
+
+/*
+ * @dump_mpp: dump mesh proxy path callback -- resume dump at index @idx
+ */
+static int __maybe_unused rwnx_cfg80211_dump_mpp(struct wiphy *wiphy,
+ struct net_device *dev,
+ int idx, u8 *dst, u8 *mpp,
+ struct mpath_info *pinfo)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_mesh_proxy *mesh_proxy = NULL;
+ struct rwnx_mesh_proxy *cur;
+ int i = 0;
+
+ if (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT)
+ return -EOPNOTSUPP;
+
+ list_for_each_entry(cur, &rwnx_vif->ap.proxy_list, list) {
+ if (cur->local)
+ continue;
+
+ if (i < idx) {
+ i++;
+ continue;
+ }
+
+ mesh_proxy = cur;
+ break;
+ }
+
+ if (!mesh_proxy)
+ return -ENOENT;
+
+ /* Copy target MAC address */
+ memcpy(dst, &mesh_proxy->ext_sta_addr, ETH_ALEN);
+ memcpy(mpp, &mesh_proxy->proxy_addr, ETH_ALEN);
+
+ /* Fill path information */
+ pinfo->filled = 0;
+ pinfo->generation = rwnx_vif->ap.generation;
+
+ return 0;
+}
+
+/*
+ * @get_mesh_config: Get the current mesh configuration
+ */
+static int __maybe_unused rwnx_cfg80211_get_mesh_config(struct wiphy *wiphy,
+ struct net_device *dev,
+ struct mesh_config *conf)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+
+ if (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+/*
+ * @update_mesh_config: Update mesh parameters on a running mesh.
+ */
+static int __maybe_unused rwnx_cfg80211_update_mesh_config(struct wiphy *wiphy,
+ struct net_device *dev, u32 mask,
+ const struct mesh_config *nconf)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct mesh_update_cfm cfm;
+ int status;
+
+ if (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT)
+ return -EOPNOTSUPP;
+
+ if (mask & CO_BIT(NL80211_MESHCONF_POWER_MODE - 1)) {
+ rwnx_vif->ap.next_mesh_pm = nconf->power_mode;
+
+ if (!list_empty(&rwnx_vif->ap.sta_list)) {
+ // If there are mesh links we don't want to update the power mode
+ // It will be updated with rwnx_update_mesh_power_mode() when the
+ // ps mode of a link is updated or when a new link is added/removed
+ mask &= ~BIT(NL80211_MESHCONF_POWER_MODE - 1);
+
+ if (!mask)
+ return 0;
+ }
+ }
+
+ status = rwnx_send_mesh_update_req(rwnx_hw, rwnx_vif, mask, nconf, &cfm);
+
+ if (!status && cfm.status != 0)
+ status = -EINVAL;
+
+ return status;
+}
+
+/*
+ * @join_mesh: join the mesh network with the specified parameters
+ * (invoked with the wireless_dev mutex held)
+ */
+static int __maybe_unused rwnx_cfg80211_join_mesh(struct wiphy *wiphy,
+ struct net_device *dev,
+ const struct mesh_config *conf,
+ const struct mesh_setup *setup)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct mesh_start_cfm mesh_start_cfm;
+ int error = 0;
+ u8 txq_status = 0;
+ /* STA for BC/MC traffic */
+ struct rwnx_sta *sta;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ if (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT)
+ return -EOPNOTSUPP;
+
+ /* Forward the information to the UMAC */
+ error = rwnx_send_mesh_start_req(rwnx_hw, rwnx_vif, conf, setup,
+ &mesh_start_cfm);
+ if (error)
+ return error;
+
+ /* Check the status */
+ switch (mesh_start_cfm.status) {
+ case CO_OK:
+ rwnx_vif->ap.bcmc_index = mesh_start_cfm.bcmc_idx;
+ rwnx_vif->ap.flags = 0;
+ rwnx_vif->use_4addr = true;
+ rwnx_vif->user_mpm = setup->user_mpm;
+
+ sta = &rwnx_hw->sta_table[mesh_start_cfm.bcmc_idx];
+ sta->valid = true;
+ sta->aid = 0;
+ sta->sta_idx = mesh_start_cfm.bcmc_idx;
+ sta->ch_idx = mesh_start_cfm.ch_idx;
+ sta->vif_idx = rwnx_vif->vif_index;
+ sta->qos = true;
+ sta->acm = 0;
+ sta->ps.active = false;
+ rwnx_mu_group_sta_init(sta, NULL);
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ rwnx_chanctx_link(rwnx_vif, mesh_start_cfm.ch_idx,
+ (struct cfg80211_chan_def *)(&setup->chandef));
+
+ if (rwnx_hw->cur_chanctx != mesh_start_cfm.ch_idx)
+ txq_status = RWNX_TXQ_STOP_CHAN;
+
+ rwnx_txq_vif_init(rwnx_hw, rwnx_vif, txq_status);
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+
+ netif_tx_start_all_queues(dev);
+ netif_carrier_on(dev);
+
+ /* If the AP channel is already the active, we probably skip radar
+ * activation on MM_CHANNEL_SWITCH_IND (unless another vif use this
+ * ctxt). In anycase retest if radar detection must be activated
+ */
+ if (rwnx_hw->cur_chanctx == mesh_start_cfm.ch_idx)
+ rwnx_radar_detection_enable_on_cur_channel(rwnx_hw);
+
+ break;
+
+ case CO_BUSY:
+ error = -EINPROGRESS;
+ break;
+
+ default:
+ error = -EIO;
+ break;
+ }
+
+ /* Print information about the operation */
+ if (error)
+ netdev_info(dev, "Failed to start MP (%d)", error);
+ else
+ netdev_info(dev, "MP started: ch=%d, bcmc_idx=%d", rwnx_vif->ch_index,
+ rwnx_vif->ap.bcmc_index);
+
+ return error;
+}
+
+/*
+ * @leave_mesh: leave the current mesh network
+ * (invoked with the wireless_dev mutex held)
+ */
+static int __maybe_unused rwnx_cfg80211_leave_mesh(struct wiphy *wiphy,
+ struct net_device *dev)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct mesh_stop_cfm mesh_stop_cfm;
+ int error = 0;
+
+ error = rwnx_send_mesh_stop_req(rwnx_hw, rwnx_vif, &mesh_stop_cfm);
+
+ if (error == 0) {
+ /* Check the status */
+ switch (mesh_stop_cfm.status) {
+ case CO_OK:
+ spin_lock_bh(&rwnx_hw->cb_lock);
+ rwnx_chanctx_unlink(rwnx_vif);
+ rwnx_radar_cancel_cac(&rwnx_hw->radar);
+ spin_unlock_bh(&rwnx_hw->cb_lock);
+ /* delete BC/MC STA */
+ rwnx_txq_vif_deinit(rwnx_hw, rwnx_vif);
+ rwnx_del_bcn(&rwnx_vif->ap.bcn);
+
+ netif_tx_stop_all_queues(dev);
+ netif_carrier_off(dev);
+
+ break;
+
+ default:
+ error = -EIO;
+ break;
+ }
+ }
+
+ if (error)
+ netdev_info(dev, "Failed to stop MP");
+ else
+ netdev_info(dev, "MP Stopped");
+
+ return 0;
+}
+
+static struct cfg80211_ops rwnx_cfg80211_ops = {
+ .add_virtual_intf = rwnx_cfg80211_add_iface,
+ .del_virtual_intf = rwnx_cfg80211_del_iface,
+ .change_virtual_intf = rwnx_cfg80211_change_iface,
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ .start_p2p_device = rwnx_cfgp2p_start_p2p_device,
+ .stop_p2p_device = rwnx_cfgp2p_stop_p2p_device,
+#endif
+ .scan = rwnx_cfg80211_scan,
+ .connect = rwnx_cfg80211_connect,
+ .disconnect = rwnx_cfg80211_disconnect,
+ .add_key = rwnx_cfg80211_add_key,
+ .get_key = rwnx_cfg80211_get_key,
+ .del_key = rwnx_cfg80211_del_key,
+ .set_default_key = rwnx_cfg80211_set_default_key,
+ .set_default_mgmt_key = rwnx_cfg80211_set_default_mgmt_key,
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ .add_station = rwnx_cfg80211_add_station,
+ .del_station = rwnx_cfg80211_del_station_compat,
+ .change_station = rwnx_cfg80211_change_station,
+#endif
+ .mgmt_tx = rwnx_cfg80211_mgmt_tx,
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ .start_ap = rwnx_cfg80211_start_ap,
+ .change_beacon = rwnx_cfg80211_change_beacon,
+ .stop_ap = rwnx_cfg80211_stop_ap,
+ .set_monitor_channel = rwnx_cfg80211_set_monitor_channel,
+#endif
+ // .mgmt_frame_register = rwnx_cfg80211_mgmt_frame_register,
+ /*
+ * unused interface
+ */
+ .set_wiphy_params = rwnx_cfg80211_set_wiphy_params,
+ .set_txq_params = rwnx_cfg80211_set_txq_params,
+ .set_tx_power = rwnx_cfg80211_set_tx_power,
+ // .get_tx_power = rwnx_cfg80211_get_tx_power,
+ /*
+ * unused interface
+ */
+ .set_power_mgmt = rwnx_cfg80211_set_power_mgmt,
+ .get_station = rwnx_cfg80211_get_station,
+ .dump_station = rwnx_cfg80211_dump_station,
+ .remain_on_channel = rwnx_cfg80211_remain_on_channel,
+ .cancel_remain_on_channel = rwnx_cfg80211_cancel_remain_on_channel,
+ .dump_survey = rwnx_cfg80211_dump_survey,
+ .get_channel = rwnx_cfg80211_get_channel,
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ .start_radar_detection = rwnx_cfg80211_start_radar_detection,
+#endif
+ .update_ft_ies = rwnx_cfg80211_update_ft_ies,
+ .set_cqm_rssi_config = rwnx_cfg80211_set_cqm_rssi_config,
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ .channel_switch = rwnx_cfg80211_channel_switch,
+ .tdls_channel_switch = rwnx_cfg80211_tdls_channel_switch,
+ .tdls_cancel_channel_switch = rwnx_cfg80211_tdls_cancel_channel_switch,
+ //.tdls_mgmt = rwnx_cfg80211_tdls_mgmt,
+ /*
+ * unused interface
+ */
+ .tdls_oper = rwnx_cfg80211_tdls_oper,
+ .change_bss = rwnx_cfg80211_change_bss,
+#endif
+ .external_auth = rwnx_cfg80211_external_auth,
+#ifdef CONFIG_AIC8800_SCHED_SCAN
+ .sched_scan_start = rwnx_cfg80211_sched_scan_start,
+ .sched_scan_stop = rwnx_cfg80211_sched_scan_stop,
+#endif
+};
+
+/*********************************************************************
+ * Init/Exit functions
+ *********************************************************************/
+static void rwnx_wdev_unregister(struct rwnx_hw *rwnx_hw)
+{
+ struct rwnx_vif *rwnx_vif, *tmp;
+
+ rtnl_lock();
+ list_for_each_entry_safe(rwnx_vif, tmp, &rwnx_hw->vifs, list) {
+ rwnx_cfg80211_del_iface(rwnx_hw->wiphy, &rwnx_vif->wdev);
+ }
+ rtnl_unlock();
+}
+
+static void rwnx_set_vers(struct rwnx_hw *rwnx_hw)
+{
+ u32 vers = rwnx_hw->version_cfm.version_lmac;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ snprintf(rwnx_hw->wiphy->fw_version, sizeof(rwnx_hw->wiphy->fw_version),
+ "%d.%d.%d.%d", (vers & (0xff << 24)) >> 24,
+ (vers & (0xff << 16)) >> 16, (vers & (0xff << 8)) >> 8,
+ (vers & (0xff << 0)) >> 0);
+}
+
+static void rwnx_reg_notifier(struct wiphy *wiphy,
+ struct regulatory_request *request)
+{
+ struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+
+ // For now trust all initiator
+ rwnx_radar_set_domain(&rwnx_hw->radar, request->dfs_region);
+ if (!rwnx_hw->chip_ops->limit_by_testmode || !rwnx_hw->testmode)
+ rwnx_send_me_chan_config_req(rwnx_hw);
+}
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+static void rwnx_enable_mesh(struct rwnx_hw *rwnx_hw)
+{
+ struct wiphy *wiphy = rwnx_hw->wiphy;
+
+ if (!rwnx_mod_params.mesh)
+ return;
+
+ rwnx_cfg80211_ops.get_station = rwnx_cfg80211_get_station;
+ rwnx_cfg80211_ops.dump_station = rwnx_cfg80211_dump_station;
+ rwnx_cfg80211_ops.add_mpath = rwnx_cfg80211_add_mpath;
+ rwnx_cfg80211_ops.del_mpath = rwnx_cfg80211_del_mpath;
+ rwnx_cfg80211_ops.change_mpath = rwnx_cfg80211_change_mpath;
+ rwnx_cfg80211_ops.get_mpath = rwnx_cfg80211_get_mpath;
+ rwnx_cfg80211_ops.dump_mpath = rwnx_cfg80211_dump_mpath;
+ rwnx_cfg80211_ops.get_mpp = rwnx_cfg80211_get_mpp;
+ rwnx_cfg80211_ops.dump_mpp = rwnx_cfg80211_dump_mpp;
+ rwnx_cfg80211_ops.get_mesh_config = rwnx_cfg80211_get_mesh_config;
+ rwnx_cfg80211_ops.update_mesh_config = rwnx_cfg80211_update_mesh_config;
+ rwnx_cfg80211_ops.join_mesh = rwnx_cfg80211_join_mesh;
+ rwnx_cfg80211_ops.leave_mesh = rwnx_cfg80211_leave_mesh;
+
+ wiphy->flags |= (WIPHY_FLAG_MESH_AUTH | WIPHY_FLAG_IBSS_RSN);
+ wiphy->features |= NL80211_FEATURE_USERSPACE_MPM;
+ wiphy->interface_modes |= BIT(NL80211_IFTYPE_MESH_POINT);
+
+ rwnx_limits[0].types |= BIT(NL80211_IFTYPE_MESH_POINT);
+ rwnx_limits_dfs[0].types |= BIT(NL80211_IFTYPE_MESH_POINT);
+}
+#endif
+
+#if IS_ENABLED(CONFIG_PM)
+static const struct wiphy_wowlan_support aic_wowlan_support = {
+ .flags = WIPHY_WOWLAN_ANY | WIPHY_WOWLAN_MAGIC_PKT,
+};
+#endif
+/*
+ *
+ */
+
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+void aicwf_pwlt_worker(struct work_struct *work)
+{
+ struct rwnx_hw *rwnx_hw = container_of(work, struct rwnx_hw, pwlt_wk);
+
+ aic_chip_powerlimit_load(rwnx_hw);
+ if (!rwnx_hw->testmode)
+ rwnx_send_me_chan_config_req(rwnx_hw);
+}
+#endif
+
+#ifdef CONFIG_AIC8800_REGION_PW
+void aicwf_region_worker(struct work_struct *work)
+{
+ int idx;
+ struct rwnx_hw *rwnx_hw = container_of(work, struct rwnx_hw, region_wk);
+
+ idx = get_ccode_region(rwnx_hw->country_abbr);
+ rwnx_send_txpwr_lvl_v3_req(rwnx_hw, idx);
+}
+#endif
+
+int rwnx_ic_system_init(struct rwnx_hw *rwnx_hw)
+{
+ u32 mem_addr;
+ struct dbg_mem_read_cfm rd_mem_addr_cfm;
+
+ mem_addr = 0x40500000;
+
+ if (rwnx_send_dbg_mem_read_req(rwnx_hw, mem_addr, &rd_mem_addr_cfm))
+ return -1;
+
+ chip_id = (u8)(rd_mem_addr_cfm.memdata >> 16);
+
+ if (rwnx_send_dbg_mem_read_req(rwnx_hw, 0x00000020, &rd_mem_addr_cfm)) {
+ AICWFDBG(LOGERROR, "[0x00000020] rd fail\n");
+ return -1;
+ }
+ chip_sub_id = (u8)(rd_mem_addr_cfm.memdata);
+
+ AICWFDBG(LOGINFO, "FDRV chip_id=%x, chip_sub_id=%x!!\n", chip_id,
+ chip_sub_id);
+
+#ifdef CONFIG_OOB
+ int ret;
+
+ ret = aic_chip_ic_system_init(rwnx_hw, &rd_mem_addr_cfm);
+ if (ret) {
+ AICWFDBG(LOGERROR, "aic_chip_ic_system_init fail: %d\n", ret);
+ return -1;
+ }
+#endif
+
+ if (rwnx_platform_on(rwnx_hw, NULL))
+ return -1;
+ return 0;
+}
+
+int rwnx_ic_rf_init(struct rwnx_hw *rwnx_hw)
+{
+ struct mm_set_rf_calib_cfm cfm;
+ int ret = 0;
+
+ ret = aic_chip_set_rf_config(rwnx_hw, &cfm);
+ if (ret)
+ return -1;
+ return 0;
+}
+
+static void rwnx_wakeup_sources_deinit(struct rwnx_hw *rwnx_hw)
+{
+ wakeup_source_unregister(rwnx_hw->ws_tx);
+ wakeup_source_unregister(rwnx_hw->ws_rx);
+ wakeup_source_unregister(rwnx_hw->ws_irqrx);
+ wakeup_source_unregister(rwnx_hw->ws_pwrctrl);
+ wakeup_source_unregister(rwnx_hw->ws_scan);
+ rwnx_hw->ws_tx = NULL;
+ rwnx_hw->ws_rx = NULL;
+ rwnx_hw->ws_irqrx = NULL;
+ rwnx_hw->ws_pwrctrl = NULL;
+ rwnx_hw->ws_scan = NULL;
+}
+
+static int rwnx_wakeup_sources_init(struct rwnx_hw *rwnx_hw)
+{
+ rwnx_hw->ws_tx = wakeup_source_register(rwnx_hw->dev, "rwnx-tx");
+ if (!rwnx_hw->ws_tx)
+ goto err;
+
+ rwnx_hw->ws_rx = wakeup_source_register(rwnx_hw->dev, "rwnx-rx");
+ if (!rwnx_hw->ws_rx)
+ goto err;
+
+ rwnx_hw->ws_irqrx = wakeup_source_register(rwnx_hw->dev, "rwnx-irq-rx");
+ if (!rwnx_hw->ws_irqrx)
+ goto err;
+
+ rwnx_hw->ws_pwrctrl = wakeup_source_register(rwnx_hw->dev, "rwnx-pwrctrl");
+ if (!rwnx_hw->ws_pwrctrl)
+ goto err;
+
+ rwnx_hw->ws_scan = wakeup_source_register(rwnx_hw->dev, "rwnx-scan");
+ if (rwnx_hw->ws_scan)
+ return 0;
+
+err:
+ rwnx_wakeup_sources_deinit(rwnx_hw);
+ return -ENOMEM;
+}
+
+int rwnx_cfg80211_init(struct rwnx_plat *rwnx_plat, void **platform_data)
+{
+ struct rwnx_hw *rwnx_hw;
+ struct rwnx_conf_file init_conf;
+ int ret = 0;
+ struct wiphy *wiphy;
+ struct rwnx_vif *vif;
+ int i;
+ // u8 dflt_mac[ETH_ALEN] = { 0x88, 0x00, 0x33, 0x77, 0x10, 0x99 };
+ // //internal debug
+ u8 dflt_mac[ETH_ALEN] = {0x00, 0x25, 0x92, 0x31, 0xE2, 0x78}; // for CVTE
+ u8 addr_str[20];
+ struct mm_get_fw_version_cfm fw_version;
+ u8 mac_addr_efuse[ETH_ALEN];
+ struct aicbsp_feature_t feature;
+ struct mm_set_stack_start_cfm set_start_cfm;
+#ifdef CONFIG_AIC8800_TEMP_COMP
+ struct mm_set_vendor_swconfig_cfm swconfig_cfm;
+#endif
+#ifdef CONFIG_AIC8800_WOWLAN_PM
+ struct cfg80211_wowlan *aic_wowlan_config = NULL;
+#endif
+ (void)addr_str;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+ aicbsp_get_feature(&feature);
+
+ get_random_bytes(&dflt_mac[4], 2);
+
+ /* create a new wiphy for use with cfg80211 */
+ AICWFDBG(LOGINFO, "%s sizeof(struct rwnx_hw):%d \r\n", __func__,
+ (int)sizeof(struct rwnx_hw));
+ wiphy = wiphy_new(&rwnx_cfg80211_ops, sizeof(struct rwnx_hw));
+
+ if (!wiphy) {
+ dev_err(rwnx_platform_get_dev(rwnx_plat),
+ "Failed to create new wiphy\n");
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ rwnx_hw = wiphy_priv(wiphy);
+ rwnx_hw->wiphy = wiphy;
+ rwnx_hw->plat = rwnx_plat;
+ rwnx_hw->dev = rwnx_platform_get_dev(rwnx_plat);
+ strscpy(rwnx_hw->country_abbr, "WW", 2); //"00"
+#ifdef AICWF_SDIO_SUPPORT
+ rwnx_hw->sdiodev = rwnx_plat->sdiodev;
+ rwnx_hw->chip_ops = aic_chip_ops_select(rwnx_hw->sdiodev->chipid);
+ rwnx_plat->sdiodev->rwnx_hw = rwnx_hw;
+ rwnx_hw->cmd_mgr = &rwnx_plat->sdiodev->cmd_mgr;
+#else
+ rwnx_hw->usbdev = rwnx_plat->usbdev;
+ rwnx_hw->chip_ops = aic_chip_ops_select(rwnx_hw->usbdev->chipid);
+ rwnx_plat->usbdev->rwnx_hw = rwnx_hw;
+ rwnx_hw->cmd_mgr = &rwnx_plat->usbdev->cmd_mgr;
+#endif
+ rwnx_hw->mod_params = &rwnx_mod_params;
+ rwnx_hw->tcp_pacing_shift = 7;
+ atomic_set(&rwnx_hw->scan_activity_held, 0);
+
+#ifdef CONFIG_AIC8800_SCHED_SCAN
+ rwnx_hw->is_sched_scan = false;
+#endif // CONFIG_AIC8800_SCHED_SCAN
+
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+ rwnx_hw->ccode.auto_set = false;
+ rwnx_hw->ccode.ccode_find = false;
+ rwnx_hw->ccode.ccode_set = false;
+ rwnx_hw->ccode.ccode_cnt = 0;
+ rwnx_hw->ccode.ccode_rssi = -100;
+ atomic_set(&rwnx_hw->ccode.link_scan, 0);
+ memset(&rwnx_hw->ccode.bssid, 0, sizeof(rwnx_hw->ccode.bssid));
+#endif
+
+ ret = rwnx_wakeup_sources_init(rwnx_hw);
+ if (ret)
+ goto err_wakeup_sources;
+ rwnx_init_aic(rwnx_hw);
+ /* set device pointer for wiphy */
+ set_wiphy_dev(wiphy, rwnx_hw->dev);
+
+ /* Create cache to allocate sw_txhdr */
+ rwnx_hw->sw_txhdr_cache = KMEM_CACHE(rwnx_sw_txhdr, 0);
+ if (!rwnx_hw->sw_txhdr_cache) {
+ wiphy_err(wiphy, "Cannot allocate cache for sw TX header\n");
+ ret = -ENOMEM;
+ goto err_cache;
+ }
+
+#ifdef CONFIG_AIC8800_FILTER_TCP_ACK
+ tcp_ack_init(rwnx_hw);
+#endif
+
+ memcpy(init_conf.mac_addr, dflt_mac, ETH_ALEN);
+
+ rwnx_hw->vif_started = 0;
+ rwnx_hw->monitor_vif = RWNX_INVALID_VIF;
+ rwnx_hw->adding_sta = false;
+
+ rwnx_hw->scan_ie.addr = NULL;
+
+ for (i = 0; i < NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX; i++)
+ rwnx_hw->avail_idx_map |= BIT(i);
+
+ rwnx_hwq_init(rwnx_hw);
+
+#ifdef CONFIG_PREALLOC_TXQ
+ rwnx_hw->txq =
+ (struct rwnx_txq *)aicwf_prealloc_txq_alloc(sizeof(struct rwnx_txq) * NX_NB_TXQ);
+#endif
+
+ for (i = 0; i < NX_NB_TXQ; i++)
+ rwnx_hw->txq[i].idx = TXQ_INACTIVE;
+
+ rwnx_mu_group_init(rwnx_hw);
+
+ /*
+ * Initialize RoC element pointer to NULL, indicate that RoC can be started
+ */
+ rwnx_hw->roc_elem = NULL;
+ /* Cookie can not be 0 */
+ rwnx_hw->roc_cookie_cnt = 1;
+
+ INIT_LIST_HEAD(&rwnx_hw->vifs);
+ INIT_LIST_HEAD(&rwnx_hw->defrag_list);
+ spin_lock_init(&rwnx_hw->defrag_lock);
+ mutex_init(&rwnx_hw->mutex);
+ mutex_init(&rwnx_hw->dbgdump_elem.mutex);
+ spin_lock_init(&rwnx_hw->tx_lock);
+ spin_lock_init(&rwnx_hw->cb_lock);
+
+ INIT_WORK(&rwnx_hw->apm_staloss_work, apm_staloss_work_process);
+ rwnx_hw->apm_staloss_wq = create_singlethread_workqueue("apm_staloss_wq");
+ if (!rwnx_hw->apm_staloss_wq) {
+ txrx_err("insufficient memory to create apm_staloss workqueue.\n");
+ goto err_cache;
+ }
+
+ wiphy->mgmt_stypes = rwnx_default_mgmt_stypes;
+ rwnx_hw->fwlog_en = feature.fwlog_en;
+
+ // init ic system
+ ret = rwnx_ic_system_init(rwnx_hw);
+ if (ret)
+ goto err_lmac_reqs;
+
+ ret = aic_chip_set_stack_start(rwnx_hw, &set_start_cfm);
+ if (ret)
+ goto err_lmac_reqs;
+
+ AICWFDBG(LOGINFO, "is 5g support = %d, vendor_info = 0x%02X\n",
+ set_start_cfm.is_5g_support, set_start_cfm.vendor_info);
+ rwnx_hw->band_5g_support = set_start_cfm.is_5g_support;
+ rwnx_hw->vendor_info =
+ (feature.hwinfo < 0) ? set_start_cfm.vendor_info : feature.hwinfo;
+
+ ret = rwnx_send_get_fw_version_req(rwnx_hw, &fw_version);
+ memcpy(wiphy->fw_version, fw_version.fw_version,
+ fw_version.fw_version_len > 32 ? 32 : fw_version.fw_version_len);
+ pr_info("AICWF Firmware Version: %s\n", fw_version.fw_version);
+ memcpy(rwnx_hw->fw_version, fw_version.fw_version,
+ fw_version.fw_version_len > 32 ? 32 : fw_version.fw_version_len);
+
+ ieee80211_set_sband_iftype_data(&rwnx_band_2ghz, rwnx_he_capa);
+ wiphy->bands[NL80211_BAND_2GHZ] = &rwnx_band_2ghz;
+ if (rwnx_hw->band_5g_support) {
+ ieee80211_set_sband_iftype_data(&rwnx_band_5ghz, rwnx_he_capa);
+ wiphy->bands[NL80211_BAND_5GHZ] = &rwnx_band_5ghz;
+ }
+
+ wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION);
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ wiphy->interface_modes |=
+ BIT(NL80211_IFTYPE_AP) |
+ BIT(NL80211_IFTYPE_AP_VLAN) | BIT(NL80211_IFTYPE_P2P_CLIENT) |
+ BIT(NL80211_IFTYPE_P2P_GO) |
+#ifndef CONFIG_AIC8800_USE_P2P0
+ BIT(NL80211_IFTYPE_P2P_DEVICE) |
+#endif
+ BIT(NL80211_IFTYPE_MONITOR);
+#endif
+
+#if IS_ENABLED(CONFIG_PM)
+ /* Set WoWLAN flags */
+ wiphy->wowlan = &aic_wowlan_support;
+#endif
+ wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH |
+ WIPHY_FLAG_4ADDR_STATION | WIPHY_FLAG_4ADDR_AP;
+ wiphy->max_num_csa_counters = BCN_MAX_CSA_CPT;
+#endif
+
+#ifdef CONFIG_AIC8800_WOWLAN_PM
+ aic_wowlan_config = kmalloc_obj(*aic_wowlan_config, GFP_KERNEL);
+ if (aic_wowlan_config) {
+ aic_wowlan_config->disconnect = true;
+ aic_wowlan_config->gtk_rekey_failure = true;
+ aic_wowlan_config->eap_identity_req = true;
+ aic_wowlan_config->four_way_handshake = true;
+ aic_wowlan_config->patterns = NULL;
+ aic_wowlan_config->n_patterns = 0;
+ aic_wowlan_config->tcp = NULL;
+ aic_wowlan_config->nd_config = NULL;
+ } else {
+ AICWFDBG(LOGINFO, "aic_wowlan_config is null\n");
+ }
+ wiphy->wowlan_config = aic_wowlan_config;
+#endif
+ wiphy->max_remain_on_channel_duration = rwnx_hw->mod_params->roc_dur_max;
+ wiphy->features |= NL80211_FEATURE_SK_TX_STATUS |
+ NL80211_FEATURE_VIF_TXPOWER;
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ wiphy->features |= NL80211_FEATURE_NEED_OBSS_SCAN |
+ NL80211_FEATURE_ACTIVE_MONITOR |
+ NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE;
+#endif
+ wiphy->features |= NL80211_FEATURE_SAE;
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ if (rwnx_mod_params.tdls)
+ /* TDLS support */
+ wiphy->features |= NL80211_FEATURE_TDLS_CHANNEL_SWITCH;
+ wiphy->iface_combinations = rwnx_combinations;
+ /* -1 not to include combination with radar detection, will be re-added in
+ * rwnx_handle_dynparams if supported
+ */
+ wiphy->n_iface_combinations = ARRAY_SIZE(rwnx_combinations) - 1;
+#endif
+ wiphy->reg_notifier = rwnx_reg_notifier;
+ wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
+ rwnx_enable_wapi(rwnx_hw);
+ wiphy->cipher_suites = cipher_suites;
+ wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites) - NB_RESERVED_CIPHER;
+ rwnx_hw->ext_capa[0] = WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING;
+ rwnx_hw->ext_capa[7] = WLAN_EXT_CAPA8_OPMODE_NOTIF;
+ wiphy->extended_capabilities = rwnx_hw->ext_capa;
+ wiphy->extended_capabilities_mask = rwnx_hw->ext_capa;
+ wiphy->extended_capabilities_len = ARRAY_SIZE(rwnx_hw->ext_capa);
+
+#ifdef CONFIG_AIC8800_SCHED_SCAN
+ wiphy->max_sched_scan_reqs = 1;
+ wiphy->max_sched_scan_ssids = SCAN_SSID_MAX; // 16;
+ wiphy->max_match_sets = SCAN_SSID_MAX; // 16;
+ wiphy->max_sched_scan_ie_len = 2048;
+#endif // CONFIG_AIC8800_SCHED_SCAN
+
+ tasklet_init(&rwnx_hw->task, rwnx_task, (unsigned long)rwnx_hw);
+
+ // init ic rf
+ ret = rwnx_ic_rf_init(rwnx_hw);
+ if (ret)
+ goto err_lmac_reqs;
+
+ ret = rwnx_send_get_macaddr_req(rwnx_hw,
+ (struct mm_get_mac_addr_cfm *)mac_addr_efuse);
+ if (ret)
+ goto err_lmac_reqs;
+
+ if (mac_addr_efuse[0] | mac_addr_efuse[1] | mac_addr_efuse[2] |
+ mac_addr_efuse[3]) {
+ memcpy(init_conf.mac_addr, mac_addr_efuse, ETH_ALEN);
+ } else {
+ memcpy(init_conf.mac_addr, dflt_mac, ETH_ALEN);
+ }
+
+ AICWFDBG(LOGINFO, "get macaddr: %02x:%02x:%02x:%02x:%02x:%02x\r\n",
+ mac_addr_efuse[0], mac_addr_efuse[1], mac_addr_efuse[2],
+ mac_addr_efuse[3], mac_addr_efuse[4], mac_addr_efuse[5]);
+ memcpy(wiphy->perm_addr, init_conf.mac_addr, ETH_ALEN);
+
+ /* Reset FW */
+ ret = rwnx_send_reset(rwnx_hw);
+ if (ret)
+ goto err_lmac_reqs;
+
+#ifdef CONFIG_AIC8800_TEMP_COMP
+ rwnx_send_set_temp_comp_req(rwnx_hw, &swconfig_cfm);
+#endif
+
+ ret = rwnx_send_version_req(rwnx_hw, &rwnx_hw->version_cfm);
+ if (ret)
+ goto err_lmac_reqs;
+ rwnx_set_vers(rwnx_hw);
+
+ ret = rwnx_handle_dynparams(rwnx_hw, rwnx_hw->wiphy);
+ if (ret)
+ goto err_lmac_reqs;
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ rwnx_enable_mesh(rwnx_hw);
+#endif
+ rwnx_radar_detection_init(&rwnx_hw->radar);
+
+ /* Set parameters to firmware */
+ if (!rwnx_hw->chip_ops->limit_by_testmode || !rwnx_hw->testmode)
+ rwnx_send_me_config_req(rwnx_hw);
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ /* Only monitor mode supported when custom channels are enabled */
+ if (rwnx_mod_params.custchan) {
+ rwnx_limits[0].types = BIT(NL80211_IFTYPE_MONITOR);
+ rwnx_limits_dfs[0].types = BIT(NL80211_IFTYPE_MONITOR);
+ }
+#endif
+
+ ret = wiphy_register(wiphy);
+ if (ret) {
+ wiphy_err(wiphy, "Could not register wiphy device\n");
+ goto err_register_wiphy;
+ }
+
+ /* Update regulatory (if needed) and set channel parameters to firmware
+ * (must be done after WiPHY registration)
+ */
+ rwnx_custregd(rwnx_hw, wiphy);
+
+ if (!rwnx_hw->chip_ops->limit_by_testmode || !rwnx_hw->testmode)
+ rwnx_send_me_chan_config_req(rwnx_hw);
+
+ *platform_data = rwnx_hw;
+
+ rtnl_lock();
+
+ /* Add an initial station interface */
+ vif = rwnx_interface_add(rwnx_hw, "wlan%d", NET_NAME_UNKNOWN,
+ NL80211_IFTYPE_STATION, NULL);
+
+ rtnl_unlock();
+
+ if (!vif) {
+ wiphy_err(wiphy, "Failed to instantiate a network device\n");
+ ret = -ENOMEM;
+ goto err_add_interface;
+ }
+
+#ifdef CONFIG_AIC8800_GENL
+ ret = aicwf_init_genl(rwnx_hw);
+ if (ret)
+ wiphy_warn(wiphy, "failed to register private generic netlink interface: %d\n",
+ ret);
+#endif
+ AICWFDBG(LOGINFO, "New interface create %s \r\n", vif->ndev->name);
+
+#if defined(CONFIG_AIC8800_USE_P2P0) && \
+ !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+
+ rtnl_lock();
+ /* Add an initial p2p0 interface */
+ vif = rwnx_interface_add(rwnx_hw, "p2p%d", NET_NAME_UNKNOWN,
+ NL80211_IFTYPE_STATION, NULL);
+ vif->is_p2p_vif = 1;
+ rtnl_unlock();
+
+ if (!vif) {
+ wiphy_err(wiphy, "Failed to instantiate a network device\n");
+ ret = -ENOMEM;
+ goto err_add_interface;
+ }
+
+ AICWFDBG(LOGINFO, "New interface create %s \r\n", vif->ndev->name);
+ timer_setup(&rwnx_hw->p2p_alive_timer, aicwf_p2p_alive_timeout, 0);
+ rwnx_hw->is_p2p_alive = 0;
+ rwnx_hw->is_p2p_connected = 0;
+ atomic_set(&rwnx_hw->p2p_alive_timer_count, 0);
+#endif
+
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+ rwnx_hw->started_jiffies = 0;
+ rwnx_hw->temp = 0;
+ aicwf_tp_ctrl_init(rwnx_hw->sdiodev);
+#endif
+
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+ INIT_WORK(&rwnx_hw->pwlt_wk, aicwf_pwlt_worker);
+#endif
+
+#ifdef CONFIG_AIC8800_REGION_PW
+ INIT_WORK(&rwnx_hw->region_wk, aicwf_region_worker);
+#endif
+
+ return 0;
+
+err_add_interface:
+#ifdef CONFIG_AIC8800_GENL
+ aicwf_deinit_genl(rwnx_hw);
+#endif
+ wiphy_unregister(rwnx_hw->wiphy);
+err_register_wiphy:
+err_lmac_reqs:
+ AICWFDBG(LOGERROR, "AICWF err_lmac_reqs\n");
+ rwnx_platform_off(rwnx_hw, NULL);
+ kmem_cache_destroy(rwnx_hw->sw_txhdr_cache);
+err_cache:
+ rwnx_wakeup_sources_deinit(rwnx_hw);
+err_wakeup_sources:
+ wiphy_free(wiphy);
+err_out:
+ return ret;
+}
+
+void rwnx_cfg80211_deinit(struct rwnx_hw *rwnx_hw)
+{
+ struct mm_set_stack_start_cfm set_start_cfm;
+ struct defrag_ctrl_info *defrag_ctrl = NULL;
+
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+#ifdef CONFIG_AIC8800_GENL
+ aicwf_deinit_genl(rwnx_hw);
+#endif
+
+ if (rwnx_hw->sdiodev->bus_if->state != BUS_DOWN_ST)
+ rwnx_send_set_stack_start_req(rwnx_hw, 0, 0, 0, 0, &set_start_cfm);
+
+ rwnx_hw->fwlog_en = 0;
+ spin_lock_bh(&rwnx_hw->defrag_lock);
+ if (!list_empty(&rwnx_hw->defrag_list)) {
+ list_for_each_entry(defrag_ctrl, &rwnx_hw->defrag_list, list) {
+ list_del_init(&defrag_ctrl->list);
+ if (timer_pending(&defrag_ctrl->defrag_timer))
+ //del_timer_sync(&defrag_ctrl->defrag_timer);
+ timer_delete_sync(&defrag_ctrl->defrag_timer);
+ dev_kfree_skb(defrag_ctrl->skb);
+ kfree(defrag_ctrl);
+ }
+ }
+ spin_unlock_bh(&rwnx_hw->defrag_lock);
+
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+ aicwf_tp_ctrl_deinit(rwnx_hw->sdiodev);
+#endif
+
+ flush_workqueue(rwnx_hw->apm_staloss_wq);
+ destroy_workqueue(rwnx_hw->apm_staloss_wq);
+
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+ cancel_work_sync(&rwnx_hw->pwlt_wk);
+#endif
+#ifdef CONFIG_AIC8800_REGION_PW
+ cancel_work_sync(&rwnx_hw->region_wk);
+#endif
+
+ rwnx_wdev_unregister(rwnx_hw);
+ wiphy_unregister(rwnx_hw->wiphy);
+ rwnx_radar_detection_deinit(&rwnx_hw->radar);
+ rwnx_platform_off(rwnx_hw, NULL);
+ kmem_cache_destroy(rwnx_hw->sw_txhdr_cache);
+#ifdef CONFIG_AIC8800_FILTER_TCP_ACK
+ tcp_ack_deinit(rwnx_hw);
+#endif
+ rwnx_wakeup_sources_deinit(rwnx_hw);
+ wiphy_free(rwnx_hw->wiphy);
+}
+
+static void aicsmac_driver_register(void)
+{
+#ifdef AICWF_SDIO_SUPPORT
+ aicwf_sdio_register();
+#endif
+}
+
+static struct completion hostif_register_done;
+static int rwnx_driver_err = -1;
+
+#define REGISTRATION_TIMEOUT 9000
+
+void aicwf_hostif_ready(void)
+{
+ rwnx_driver_err = 0;
+ g_rwnx_plat->enabled = true;
+ complete(&hostif_register_done);
+}
+
+void aicwf_hostif_fail(void)
+{
+ rwnx_driver_err = 1;
+ complete(&hostif_register_done);
+}
+
+static int __init rwnx_mod_init(void)
+{
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+ rwnx_init_cmd_array();
+
+ pr_info("AIC8800 WiFi driver, built against Linux %s\n", UTS_RELEASE);
+
+ if (aicbsp_set_subsys(AIC_WIFI, AIC_PWR_ON) < 0) {
+ AICWFDBG(LOGERROR, "%s, set power on fail!\n", __func__);
+ return -ENODEV;
+ }
+
+ init_completion(&hostif_register_done);
+ aicsmac_driver_register();
+
+ if (wait_for_completion_timeout(&hostif_register_done,
+ msecs_to_jiffies(REGISTRATION_TIMEOUT)) == 0 ||
+ rwnx_driver_err) {
+ AICWFDBG(LOGERROR, "register_driver timeout or error\n");
+#ifdef AICWF_SDIO_SUPPORT
+ aicwf_sdio_exit();
+#endif /* AICWF_SDIO_SUPPORT */
+ aicbsp_set_subsys(AIC_WIFI, AIC_PWR_OFF);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void __exit rwnx_mod_exit(void)
+{
+ RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+#ifdef AICWF_SDIO_SUPPORT
+ aicwf_sdio_exit();
+#endif
+
+ aicbsp_set_subsys(AIC_WIFI, AIC_PWR_OFF);
+ rwnx_free_cmd_array();
+}
+
+module_init(rwnx_mod_init);
+module_exit(rwnx_mod_exit);
+
+MODULE_FIRMWARE(RWNX_CONFIG_FW_NAME);
+
+MODULE_DESCRIPTION(RW_DRV_DESCRIPTION);
+MODULE_AUTHOR(RW_DRV_COPYRIGHT " " RW_DRV_AUTHOR);
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_main.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_main.h
new file mode 100644
index 0000000000000..e03321c99c42c
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_main.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_MAIN_H_
+#define _RWNX_MAIN_H_
+
+#include "rwnx_defs.h"
+#include <linux/delay.h>
+
+int rwnx_cfg80211_init(struct rwnx_plat *rwnx_plat, void **platform_data);
+void rwnx_cfg80211_deinit(struct rwnx_hw *rwnx_hw);
+int rwnx_ic_rf_init(struct rwnx_hw *rwnx_hw);
+int rwnx_ic_system_init(struct rwnx_hw *rwnx_hw);
+
+#ifdef CONFIG_AIC8800_REGION_PW
+void aicwf_region_worker(struct work_struct *work);
+#endif
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+void aicwf_pwlt_worker(struct work_struct *work);
+#endif
+
+void rwnx_update_mesh_power_mode(struct rwnx_vif *vif);
+void aicwf_p2p_alive_timeout(struct timer_list *t);
+void apm_staloss_work_process(struct work_struct *work);
+void apm_probe_sta_work_process(struct work_struct *work);
+int rwnx_cfg80211_probe_client(struct wiphy *wiphy, struct net_device *dev,
+ const u8 *peer, u64 *cookie);
+
+extern u8 chip_sub_id;
+extern u8 chip_mcu_id;
+extern u8 chip_id;
+extern struct ieee80211_sband_iftype_data rwnx_he_capa[1];
+
+#define CHIP_ID_H_MASK 0xC0
+#define IS_CHIP_ID_H() ((chip_id & CHIP_ID_H_MASK) == CHIP_ID_H_MASK)
+
+#endif /* _RWNX_MAIN_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mesh.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mesh.c
new file mode 100644
index 0000000000000..69d37ecb63be2
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mesh.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief mesh function
+ *
+ ******************************************************************************
+ */
+
+#include "rwnx_mesh.h"
+
+struct rwnx_mesh_proxy *rwnx_get_mesh_proxy_info(struct rwnx_vif *p_rwnx_vif,
+ u8 *p_sta_addr, bool local)
+{
+ struct rwnx_mesh_proxy *p_mesh_proxy = NULL;
+ struct rwnx_mesh_proxy *p_cur_proxy;
+
+ /* Look for proxied devices with provided address */
+ list_for_each_entry(p_cur_proxy, &p_rwnx_vif->ap.proxy_list, list) {
+ if (p_cur_proxy->local != local)
+ continue;
+
+ if (!memcmp(&p_cur_proxy->ext_sta_addr, p_sta_addr, ETH_ALEN)) {
+ p_mesh_proxy = p_cur_proxy;
+ break;
+ }
+ }
+
+ /* Return the found information */
+ return p_mesh_proxy;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mesh.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mesh.h
new file mode 100644
index 0000000000000..44de24ea705a1
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mesh.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_MESH_H_
+#define _RWNX_MESH_H_
+
+/*
+ * INCLUDE FILES
+ ****************************************************************************************
+ */
+
+#include "rwnx_defs.h"
+
+/*
+ * DEFINES
+ ****************************************************************************************
+ */
+
+/*
+ * TYPE DEFINITIONS
+ ****************************************************************************************
+ */
+
+/*
+ * FUNCTION DECLARATIONS
+ ****************************************************************************************
+ */
+
+/*
+ ****************************************************************************************
+ * @brief TODO [LT]
+ ****************************************************************************************
+ */
+struct rwnx_mesh_proxy *rwnx_get_mesh_proxy_info(struct rwnx_vif *p_rwnx_vif,
+ u8 *p_sta_addr, bool local);
+
+#endif /* _RWNX_MESH_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mod_params.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mod_params.c
new file mode 100644
index 0000000000000..3ee122090261e
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mod_params.c
@@ -0,0 +1,985 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief Set configuration according to modules parameters
+ *
+ ******************************************************************************
+ */
+#include <linux/module.h>
+#include <linux/rtnetlink.h>
+
+#include "hal_desc.h"
+#include "reg_access.h"
+#include "rwnx_cfgfile.h"
+#include "rwnx_compat.h"
+#include "rwnx_defs.h"
+#include "rwnx_main.h"
+#include "rwnx_tx.h"
+
+#ifdef CONFIG_RWNX_FULLMAC
+#define COMMON_PARAM(name, default) .name = default,
+#define SOFTMAC_PARAM(name, default)
+#define FULLMAC_PARAM(name, default) .name = default,
+#endif /* CONFIG_RWNX_FULLMAC */
+
+struct rwnx_mod_params rwnx_mod_params = {
+ /* common parameters */
+ COMMON_PARAM(ht_on, true)
+ COMMON_PARAM(vht_on, true)
+ COMMON_PARAM(he_on, true)
+ COMMON_PARAM(mcs_map, IEEE80211_VHT_MCS_SUPPORT_0_9)
+ COMMON_PARAM(he_mcs_map, IEEE80211_HE_MCS_SUPPORT_0_11)
+ COMMON_PARAM(he_ul_on, false)
+ COMMON_PARAM(ldpc_on, true)
+ COMMON_PARAM(stbc_on, true)
+ COMMON_PARAM(gf_rx_on, false)
+ COMMON_PARAM(phy_cfg, 2)
+ COMMON_PARAM(uapsd_timeout, 300)
+ COMMON_PARAM(ap_uapsd_on, false)
+ COMMON_PARAM(sgi, true)
+ COMMON_PARAM(sgi80, false)
+ COMMON_PARAM(use_2040, 1)
+ COMMON_PARAM(nss, 1)
+ COMMON_PARAM(amsdu_rx_max, 2)
+ COMMON_PARAM(bfmee, true)
+ COMMON_PARAM(bfmer, false)
+ COMMON_PARAM(mesh, false)
+ COMMON_PARAM(murx, true)
+ COMMON_PARAM(mutx, true)
+ COMMON_PARAM(mutx_on, true)
+ COMMON_PARAM(use_80, false)
+ COMMON_PARAM(custregd, true)
+ COMMON_PARAM(custchan, false)
+ COMMON_PARAM(roc_dur_max, 500)
+ COMMON_PARAM(listen_itv, 0)
+ COMMON_PARAM(listen_bcmc, true)
+ COMMON_PARAM(lp_clk_ppm, 20)
+ COMMON_PARAM(ps_on, true)
+ COMMON_PARAM(tx_lft, RWNX_TX_LIFETIME_MS)
+ COMMON_PARAM(amsdu_maxnb, NX_TX_PAYLOAD_MAX)
+ // By default, only enable UAPSD for Voice queue (see
+ // IEEE80211_DEFAULT_UAPSD_QUEUE comment)
+ COMMON_PARAM(uapsd_queues, IEEE80211_WMM_IE_STA_QOSINFO_AC_VO)
+ COMMON_PARAM(tdls, false)
+ COMMON_PARAM(uf, false)
+ COMMON_PARAM(auto_reply, false)
+ COMMON_PARAM(ftl, "")
+ COMMON_PARAM(dpsm, false)
+
+ /* SOFTMAC only parameters */
+ SOFTMAC_PARAM(mfp_on, false)
+ SOFTMAC_PARAM(gf_on, false)
+ SOFTMAC_PARAM(bwsig_on, true)
+ SOFTMAC_PARAM(dynbw_on, true)
+ SOFTMAC_PARAM(agg_tx, true)
+ SOFTMAC_PARAM(amsdu_force, 2)
+ SOFTMAC_PARAM(rc_probes_on, false)
+ SOFTMAC_PARAM(cmon, true)
+ SOFTMAC_PARAM(hwscan, true)
+ SOFTMAC_PARAM(autobcn, true)
+ SOFTMAC_PARAM(dpsm, true)
+
+ /* FULLMAC only parameters */
+ FULLMAC_PARAM(ant_div, true)
+};
+
+#ifdef CONFIG_RWNX_FULLMAC
+/* FULLMAC specific parameters*/
+module_param_named(ant_div, rwnx_mod_params.ant_div, bool, 0444);
+MODULE_PARM_DESC(ant_div, "Enable Antenna Diversity (Default: 1)");
+#endif /* CONFIG_RWNX_FULLMAC */
+
+module_param_named(ht_on, rwnx_mod_params.ht_on, bool, 0444);
+MODULE_PARM_DESC(ht_on, "Enable HT (Default: 1)");
+
+module_param_named(vht_on, rwnx_mod_params.vht_on, bool, 0444);
+MODULE_PARM_DESC(vht_on, "Enable VHT (Default: 1)");
+
+module_param_named(he_on, rwnx_mod_params.he_on, bool, 0444);
+MODULE_PARM_DESC(he_on, "Enable HE (Default: 1)");
+
+module_param_named(mcs_map, rwnx_mod_params.mcs_map, int, 0444);
+MODULE_PARM_DESC(mcs_map,
+ "VHT MCS map value 0: MCS0_7, 1: MCS0_8, 2: MCS0_9 (Default: 2)");
+
+module_param_named(he_mcs_map, rwnx_mod_params.he_mcs_map, int, 0444);
+MODULE_PARM_DESC(he_mcs_map,
+ "HE MCS map value 0: MCS0_7, 1: MCS0_9, 2: MCS0_11 (Default: 2)");
+
+module_param_named(he_ul_on, rwnx_mod_params.he_ul_on, bool, 0444);
+MODULE_PARM_DESC(he_ul_on, "Enable HE OFDMA UL (Default: 0)");
+
+module_param_named(amsdu_maxnb, rwnx_mod_params.amsdu_maxnb, int,
+ 0444 | 0200);
+MODULE_PARM_DESC(amsdu_maxnb,
+ "Maximum number of MSDUs inside an A-MSDU in TX: (Default: NX_TX_PAYLOAD_MAX)");
+
+module_param_named(ps_on, rwnx_mod_params.ps_on, bool, 0444);
+MODULE_PARM_DESC(ps_on, "Enable PowerSaving (Default: 1-Enabled)");
+
+module_param_named(tx_lft, rwnx_mod_params.tx_lft, int, 0644);
+MODULE_PARM_DESC(tx_lft,
+ "Tx lifetime (ms) - setting it to 0 disables retries (Default: "
+ __stringify(RWNX_TX_LIFETIME_MS) ")");
+
+module_param_named(ldpc_on, rwnx_mod_params.ldpc_on, bool, 0444);
+MODULE_PARM_DESC(ldpc_on, "Enable LDPC (Default: 1)");
+
+module_param_named(stbc_on, rwnx_mod_params.stbc_on, bool, 0444);
+MODULE_PARM_DESC(stbc_on, "Enable STBC in RX (Default: 1)");
+
+module_param_named(gf_rx_on, rwnx_mod_params.gf_rx_on, bool, 0444);
+MODULE_PARM_DESC(gf_rx_on, "Enable HT greenfield in reception (Default: 1)");
+
+module_param_named(phycfg, rwnx_mod_params.phy_cfg, int, 0444);
+MODULE_PARM_DESC(phycfg, "0 <= phycfg <= 5 : RF Channel Conf (Default: 2(C0-A1-B2))");
+
+module_param_named(uapsd_timeout, rwnx_mod_params.uapsd_timeout, int,
+ 0444 | 0200);
+MODULE_PARM_DESC(uapsd_timeout,
+ "UAPSD Timer timeout, in ms (Default: 300). If 0, UAPSD is disabled");
+
+module_param_named(uapsd_queues, rwnx_mod_params.uapsd_queues, int,
+ 0444 | 0200);
+MODULE_PARM_DESC(uapsd_queues,
+ "UAPSD Queues, integer value, must be seen as a bitfield\n"
+ " Bit 0 = VO\n"
+ " Bit 1 = VI\n"
+ " Bit 2 = BK\n"
+ " Bit 3 = BE\n"
+ " -> uapsd_queues=7 will enable uapsd for VO, VI and BK queues");
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+module_param_named(ap_uapsd_on, rwnx_mod_params.ap_uapsd_on, bool, 0444);
+MODULE_PARM_DESC(ap_uapsd_on, "Enable UAPSD in AP mode (Default: 0)");
+#endif
+
+module_param_named(sgi, rwnx_mod_params.sgi, bool, 0444);
+MODULE_PARM_DESC(sgi, "Advertise Short Guard Interval support (Default: 1)");
+
+module_param_named(sgi80, rwnx_mod_params.sgi80, bool, 0444);
+MODULE_PARM_DESC(sgi80, "Advertise Short Guard Interval support for 80MHz (Default: 1)");
+
+module_param_named(use_2040, rwnx_mod_params.use_2040, bool, 0444);
+MODULE_PARM_DESC(use_2040, "Use tweaked 20-40MHz mode (Default: 1)");
+
+module_param_named(use_80, rwnx_mod_params.use_80, bool, 0444);
+MODULE_PARM_DESC(use_80, "Enable 80MHz (Default: 1)");
+
+module_param_named(custregd, rwnx_mod_params.custregd, bool, 0444);
+MODULE_PARM_DESC(custregd,
+ "Use permissive custom regulatory rules (for testing ONLY) (Default: 0)");
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+module_param_named(custchan, rwnx_mod_params.custchan, bool, 0444);
+MODULE_PARM_DESC(custchan,
+ "Extend channel set to non-standard channels (for testing ONLY) (Default: 0)");
+#endif
+
+module_param_named(nss, rwnx_mod_params.nss, int, 0444);
+MODULE_PARM_DESC(nss,
+ "1 <= nss <= 2 : Supported number of Spatial Streams (Default: 1)");
+
+module_param_named(amsdu_rx_max, rwnx_mod_params.amsdu_rx_max, int, 0444);
+MODULE_PARM_DESC(amsdu_rx_max,
+ "0 <= amsdu_rx_max <= 2 : Maximum A-MSDU size supported in RX\n"
+ " 0: 3895 bytes\n"
+ " 1: 7991 bytes\n"
+ " 2: 11454 bytes\n"
+ " This value might be reduced according to the FW capabilities.\n"
+ " Default: 2");
+
+module_param_named(bfmee, rwnx_mod_params.bfmee, bool, 0444);
+MODULE_PARM_DESC(bfmee, "Enable Beamformee Capability (Default: 1-Enabled)");
+
+module_param_named(bfmer, rwnx_mod_params.bfmer, bool, 0444);
+MODULE_PARM_DESC(bfmer, "Enable Beamformer Capability (Default: 0-Disabled)");
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+module_param_named(mesh, rwnx_mod_params.mesh, bool, 0444);
+MODULE_PARM_DESC(mesh, "Enable Meshing Capability (Default: 0-Disabled)");
+#endif
+
+module_param_named(murx, rwnx_mod_params.murx, bool, 0444);
+MODULE_PARM_DESC(murx, "Enable MU-MIMO RX Capability (Default: 1-Enabled)");
+
+module_param_named(mutx, rwnx_mod_params.mutx, bool, 0444);
+MODULE_PARM_DESC(mutx, "Enable MU-MIMO TX Capability (Default: 1-Enabled)");
+
+module_param_named(mutx_on, rwnx_mod_params.mutx_on, bool, 0444 | 0200);
+MODULE_PARM_DESC(mutx_on, "Enable MU-MIMO transmissions (Default: 1-Enabled)");
+
+module_param_named(roc_dur_max, rwnx_mod_params.roc_dur_max, int, 0444);
+MODULE_PARM_DESC(roc_dur_max, "Maximum Remain on Channel duration");
+
+module_param_named(listen_itv, rwnx_mod_params.listen_itv, int, 0444);
+MODULE_PARM_DESC(listen_itv, "Maximum listen interval");
+
+module_param_named(listen_bcmc, rwnx_mod_params.listen_bcmc, bool, 0444);
+MODULE_PARM_DESC(listen_bcmc, "Wait for BC/MC traffic following DTIM beacon");
+
+module_param_named(lp_clk_ppm, rwnx_mod_params.lp_clk_ppm, int, 0444);
+MODULE_PARM_DESC(lp_clk_ppm, "Low Power Clock accuracy of the local device");
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+module_param_named(tdls, rwnx_mod_params.tdls, bool, 0444);
+MODULE_PARM_DESC(tdls, "Enable TDLS (Default: 0-Disabled)");
+#endif
+
+module_param_named(uf, rwnx_mod_params.uf, bool, 0444 | 0200);
+MODULE_PARM_DESC(uf,
+ "Enable Unsupported HT Frame Logging (Default: 0-Disabled)");
+
+module_param_named(auto_reply, rwnx_mod_params.auto_reply, bool,
+ 0444 | 0200);
+MODULE_PARM_DESC(auto_reply,
+ "Enable Monitor MacAddr Auto-Reply (Default: 0-Disabled)");
+
+module_param_named(ftl, rwnx_mod_params.ftl, charp, 0444);
+MODULE_PARM_DESC(ftl, "Firmware trace level (Default: \"\")");
+
+module_param_named(dpsm, rwnx_mod_params.dpsm, bool, 0444);
+MODULE_PARM_DESC(dpsm, "Enable Dynamic PowerSaving (Default: 1-Enabled)");
+
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+static char default_ccode[4] = "XX";
+#else
+static char default_ccode[4] = "00";
+#endif
+
+static char country_code[4];
+module_param_string(country_code, country_code, 4, 0600);
+
+static const int mcs_map_to_rate[4][3] = {
+ [PHY_CHNL_BW_20][IEEE80211_VHT_MCS_SUPPORT_0_7] = 65,
+ [PHY_CHNL_BW_20][IEEE80211_VHT_MCS_SUPPORT_0_8] = 78,
+ [PHY_CHNL_BW_20][IEEE80211_VHT_MCS_SUPPORT_0_9] = 78,
+ [PHY_CHNL_BW_40][IEEE80211_VHT_MCS_SUPPORT_0_7] = 135,
+ [PHY_CHNL_BW_40][IEEE80211_VHT_MCS_SUPPORT_0_8] = 162,
+ [PHY_CHNL_BW_40][IEEE80211_VHT_MCS_SUPPORT_0_9] = 180,
+ [PHY_CHNL_BW_80][IEEE80211_VHT_MCS_SUPPORT_0_7] = 292,
+ [PHY_CHNL_BW_80][IEEE80211_VHT_MCS_SUPPORT_0_8] = 351,
+ [PHY_CHNL_BW_80][IEEE80211_VHT_MCS_SUPPORT_0_9] = 390,
+ [PHY_CHNL_BW_160][IEEE80211_VHT_MCS_SUPPORT_0_7] = 585,
+ [PHY_CHNL_BW_160][IEEE80211_VHT_MCS_SUPPORT_0_8] = 702,
+ [PHY_CHNL_BW_160][IEEE80211_VHT_MCS_SUPPORT_0_9] = 780,
+};
+
+#define MAX_VHT_RATE(map, nss, bw) (mcs_map_to_rate[bw][map] * (nss))
+
+static char ccode_channels[200];
+static int index_for_channel_list;
+module_param_string(ccode_channels, ccode_channels, 200, 0600);
+
+void rwnx_get_countrycode_channels(struct wiphy *wiphy,
+ const struct ieee80211_regdomain *regdomain)
+{
+ enum nl80211_band band;
+ struct ieee80211_supported_band *sband;
+ int channel_index;
+ int rule_index;
+ int band_num = 0;
+ int rule_num = regdomain->n_reg_rules;
+ int start_freq = 0;
+ int end_freq = 0;
+ int center_freq = 0;
+ char channel[4];
+
+ band_num = NUM_NL80211_BANDS;
+
+ memset(ccode_channels, 0, 200);
+ index_for_channel_list = 0;
+
+ for (band = 0; band < band_num; band++) {
+ sband = wiphy->bands[band]; // bands: 0:2.4G 1:5G 2:60G
+ if (!sband)
+ continue;
+
+ for (channel_index = 0; channel_index < sband->n_channels;
+ channel_index++) {
+ for (rule_index = 0; rule_index < rule_num; rule_index++) {
+ start_freq =
+ regdomain->reg_rules[rule_index].freq_range.start_freq_khz / 1000;
+ end_freq =
+ regdomain->reg_rules[rule_index].freq_range.end_freq_khz / 1000;
+ center_freq = sband->channels[channel_index].center_freq;
+ if ((center_freq - 10) >= start_freq &&
+ (center_freq + 10) <= end_freq) {
+ sprintf(channel, "%d",
+ ieee80211_frequency_to_channel(center_freq));
+ memcpy(ccode_channels + index_for_channel_list, channel,
+ strlen(channel));
+ index_for_channel_list += strlen(channel);
+ memcpy(ccode_channels + index_for_channel_list, " ", 1);
+ index_for_channel_list += 1;
+ break;
+ }
+ }
+ }
+ }
+ AICWFDBG(LOGDEBUG, "%s support channel:%s\r\n", __func__, ccode_channels);
+}
+
+const struct ieee80211_regdomain *
+get_regdomain_from_rwnx_db_index(struct wiphy *wiphy, int index)
+{
+ u8 idx;
+
+ idx = index;
+
+ memset(country_code, 0, 4);
+ country_code[0] = reg_regdb[idx]->alpha2[0];
+ country_code[1] = reg_regdb[idx]->alpha2[1];
+
+ AICWFDBG(LOGDEBUG, "%s set ccode:%s \r\n", __func__, country_code);
+
+ rwnx_get_countrycode_channels(wiphy, reg_regdb[idx]);
+
+ return reg_regdb[idx];
+}
+
+const struct ieee80211_regdomain *
+get_regdomain_from_rwnx_db(struct wiphy *wiphy, const char *alpha2)
+{
+ u8 idx;
+
+ memset(country_code, 0, 4);
+
+ AICWFDBG(LOGDEBUG, "%s set ccode:%s \r\n", __func__, alpha2);
+ idx = 0;
+
+ while (reg_regdb[idx] && idx < reg_regdb_size) {
+ if (reg_regdb[idx]->alpha2[0] == alpha2[0] &&
+ reg_regdb[idx]->alpha2[1] == alpha2[1]) {
+ memcpy(country_code, alpha2, 2);
+ rwnx_get_countrycode_channels(wiphy, reg_regdb[idx]);
+ return reg_regdb[idx];
+ }
+ idx++;
+ }
+
+ AICWFDBG(LOGERROR, "%s(): Error, wrong country = %s\n", __func__, alpha2);
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+ AICWFDBG(LOGERROR, "Set as default XX\n");
+ memcpy(country_code, default_ccode, sizeof(default_ccode));
+ rwnx_get_countrycode_channels(wiphy, reg_regdb[reg_regdb_size - 1]);
+ return reg_regdb[reg_regdb_size - 1];
+#else
+ AICWFDBG(LOGERROR, "Set as default 00\n");
+ memcpy(country_code, default_ccode, sizeof(default_ccode));
+ rwnx_get_countrycode_channels(wiphy, reg_regdb[0]);
+ return reg_regdb[0];
+#endif
+}
+
+int rwnx_regulatory_set_wiphy_regd(struct wiphy *wiphy,
+ const struct ieee80211_regdomain *regd)
+{
+ /*
+ * cfg80211 copies the regulatory domain and does not modify the source,
+ * but regulatory_set_wiphy_regd() is not const-qualified.
+ */
+ return regulatory_set_wiphy_regd(wiphy,
+ (struct ieee80211_regdomain *)regd);
+}
+
+int rwnx_regulatory_set_wiphy_regd_sync(struct wiphy *wiphy,
+ const struct ieee80211_regdomain *regd)
+{
+ /* See rwnx_regulatory_set_wiphy_regd(). */
+ return regulatory_set_wiphy_regd_sync(wiphy,
+ (struct ieee80211_regdomain *)regd);
+}
+
+static void rwnx_set_vht_capa(struct rwnx_hw *rwnx_hw, struct wiphy *wiphy)
+{
+ struct ieee80211_supported_band *band_5ghz =
+ wiphy->bands[NL80211_BAND_5GHZ];
+ struct ieee80211_supported_band *band_2ghz =
+ wiphy->bands[NL80211_BAND_2GHZ];
+
+ int i;
+ int nss = rwnx_hw->mod_params->nss;
+ int mcs_map;
+ int mcs_map_max;
+ int bw_max;
+
+ if (!rwnx_hw->mod_params->vht_on)
+ return;
+
+ band_2ghz->vht_cap.vht_supported = true;
+ if (rwnx_hw->mod_params->sgi80)
+ band_2ghz->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80;
+ if (rwnx_hw->mod_params->stbc_on)
+ band_2ghz->vht_cap.cap |= IEEE80211_VHT_CAP_RXSTBC_1;
+ if (rwnx_hw->mod_params->ldpc_on)
+ band_2ghz->vht_cap.cap |= IEEE80211_VHT_CAP_RXLDPC;
+ if (rwnx_hw->mod_params->bfmee) {
+ band_2ghz->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
+ band_2ghz->vht_cap.cap |= 3 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT;
+ }
+ if (nss > 1)
+ band_2ghz->vht_cap.cap |= IEEE80211_VHT_CAP_TXSTBC;
+
+ // Update the AMSDU max RX size (not shifted as located at offset 0 of the
+ // VHT cap)
+ band_2ghz->vht_cap.cap |= rwnx_hw->mod_params->amsdu_rx_max;
+
+ if (rwnx_hw->mod_params->bfmer) {
+ band_2ghz->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE;
+ /* Set number of sounding dimensions */
+ band_2ghz->vht_cap.cap |=
+ (nss - 1) << IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT;
+ }
+ if (rwnx_hw->mod_params->murx)
+ band_2ghz->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
+ if (rwnx_hw->mod_params->mutx)
+ band_2ghz->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE;
+
+ /*
+ * MCS map:
+ * This capabilities are filled according to the mcs_map module parameter.
+ * However currently we have some limitations due to FPGA clock constraints
+ * that prevent always using the range of MCS that is defined by the
+ * parameter:
+ * - in RX, 2SS, we support up to MCS7
+ * - in TX, 2SS, we support up to MCS8
+ */
+ // Get max supported BW
+ if (rwnx_hw->mod_params->use_80)
+ bw_max = PHY_CHNL_BW_80;
+ else if (rwnx_hw->mod_params->use_2040)
+ bw_max = PHY_CHNL_BW_40;
+ else
+ bw_max = PHY_CHNL_BW_20;
+
+ // Check if MCS map should be limited to MCS0_8 due to the standard. Indeed
+ // in BW20, MCS9 is not supported in 1 and 2 SS
+ if (rwnx_hw->mod_params->use_2040)
+ mcs_map_max = IEEE80211_VHT_MCS_SUPPORT_0_9;
+ else
+ mcs_map_max = IEEE80211_VHT_MCS_SUPPORT_0_8;
+
+ mcs_map = min_t(int, rwnx_hw->mod_params->mcs_map, mcs_map_max);
+
+ nss = min_t(int, nss, 16);
+
+ band_2ghz->vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(0);
+ for (i = 0; i < nss; i++) {
+ band_2ghz->vht_cap.vht_mcs.rx_mcs_map |=
+ cpu_to_le16(mcs_map << (i * 2));
+ band_2ghz->vht_cap.vht_mcs.rx_highest =
+ cpu_to_le16(MAX_VHT_RATE(mcs_map, nss, bw_max));
+ mcs_map = IEEE80211_VHT_MCS_SUPPORT_0_7;
+ }
+ for (; i < 8; i++) {
+ band_2ghz->vht_cap.vht_mcs.rx_mcs_map |=
+ cpu_to_le16(IEEE80211_VHT_MCS_NOT_SUPPORTED << (i * 2));
+ }
+
+ mcs_map = min_t(int, rwnx_hw->mod_params->mcs_map, mcs_map_max);
+ band_2ghz->vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(0);
+ for (i = 0; i < nss; i++) {
+ band_2ghz->vht_cap.vht_mcs.tx_mcs_map |=
+ cpu_to_le16(mcs_map << (i * 2));
+ band_2ghz->vht_cap.vht_mcs.tx_highest =
+ cpu_to_le16(MAX_VHT_RATE(mcs_map, nss, bw_max));
+ mcs_map = min_t(int, rwnx_hw->mod_params->mcs_map,
+ IEEE80211_VHT_MCS_SUPPORT_0_8);
+ }
+ for (; i < 8; i++) {
+ band_2ghz->vht_cap.vht_mcs.tx_mcs_map |=
+ cpu_to_le16(IEEE80211_VHT_MCS_NOT_SUPPORTED << (i * 2));
+ }
+
+ if (!rwnx_hw->mod_params->use_80) {
+#ifdef CONFIG_VENDOR_RWNX_VHT_NO80
+ band_2ghz->vht_cap.cap |= IEEE80211_VHT_CAP_NOT_SUP_WIDTH_80;
+#endif
+ band_2ghz->vht_cap.cap &= ~IEEE80211_VHT_CAP_SHORT_GI_80;
+ }
+
+ if (rwnx_hw->band_5g_support) {
+ band_5ghz->vht_cap.vht_supported = true;
+ if (rwnx_hw->mod_params->sgi80)
+ band_5ghz->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80;
+ if (rwnx_hw->mod_params->stbc_on)
+ band_5ghz->vht_cap.cap |= IEEE80211_VHT_CAP_RXSTBC_1;
+ if (rwnx_hw->mod_params->ldpc_on)
+ band_5ghz->vht_cap.cap |= IEEE80211_VHT_CAP_RXLDPC;
+ if (rwnx_hw->mod_params->bfmee) {
+ band_5ghz->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
+ band_5ghz->vht_cap.cap |= 3 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT;
+ }
+ if (nss > 1)
+ band_5ghz->vht_cap.cap |= IEEE80211_VHT_CAP_TXSTBC;
+
+ // Update the AMSDU max RX size (not shifted as located at offset 0 of
+ // the VHT cap)
+ band_5ghz->vht_cap.cap |= rwnx_hw->mod_params->amsdu_rx_max;
+
+ if (rwnx_hw->mod_params->bfmer) {
+ band_5ghz->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE;
+ /* Set number of sounding dimensions */
+ band_5ghz->vht_cap.cap |=
+ (nss - 1) << IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT;
+ }
+ if (rwnx_hw->mod_params->murx)
+ band_5ghz->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
+ if (rwnx_hw->mod_params->mutx)
+ band_5ghz->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE;
+
+ /*
+ * MCS map:
+ * This capabilities are filled according to the mcs_map module
+ * parameter. However currently we have some limitations due to FPGA
+ * clock constraints that prevent always using the range of MCS that is
+ * defined by the parameter:
+ * - in RX, 2SS, we support up to MCS7
+ * - in TX, 2SS, we support up to MCS8
+ */
+ // Get max supported BW
+ if (rwnx_hw->mod_params->use_80)
+ bw_max = PHY_CHNL_BW_80;
+ else if (rwnx_hw->mod_params->use_2040)
+ bw_max = PHY_CHNL_BW_40;
+ else
+ bw_max = PHY_CHNL_BW_20;
+
+ // Check if MCS map should be limited to MCS0_8 due to the standard.
+ // Indeed in BW20, MCS9 is not supported in 1 and 2 SS
+ if (rwnx_hw->mod_params->use_2040)
+ mcs_map_max = IEEE80211_VHT_MCS_SUPPORT_0_9;
+ else
+ mcs_map_max = IEEE80211_VHT_MCS_SUPPORT_0_8;
+
+ mcs_map = min_t(int, rwnx_hw->mod_params->mcs_map, mcs_map_max);
+ band_5ghz->vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(0);
+ for (i = 0; i < nss; i++) {
+ band_5ghz->vht_cap.vht_mcs.rx_mcs_map |=
+ cpu_to_le16(mcs_map << (i * 2));
+ band_5ghz->vht_cap.vht_mcs.rx_highest =
+ cpu_to_le16(MAX_VHT_RATE(mcs_map, nss, bw_max));
+ mcs_map = IEEE80211_VHT_MCS_SUPPORT_0_7;
+ }
+ for (; i < 8; i++) {
+ band_5ghz->vht_cap.vht_mcs.rx_mcs_map |=
+ cpu_to_le16(IEEE80211_VHT_MCS_NOT_SUPPORTED << (i * 2));
+ }
+
+ mcs_map = min_t(int, rwnx_hw->mod_params->mcs_map, mcs_map_max);
+ band_5ghz->vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(0);
+ for (i = 0; i < nss; i++) {
+ band_5ghz->vht_cap.vht_mcs.tx_mcs_map |=
+ cpu_to_le16(mcs_map << (i * 2));
+ band_5ghz->vht_cap.vht_mcs.tx_highest =
+ cpu_to_le16(MAX_VHT_RATE(mcs_map, nss, bw_max));
+ mcs_map = min_t(int, rwnx_hw->mod_params->mcs_map,
+ IEEE80211_VHT_MCS_SUPPORT_0_8);
+ }
+ for (; i < 8; i++) {
+ band_5ghz->vht_cap.vht_mcs.tx_mcs_map |=
+ cpu_to_le16(IEEE80211_VHT_MCS_NOT_SUPPORTED << (i * 2));
+ }
+
+ if (!rwnx_hw->mod_params->use_80) {
+#ifdef CONFIG_VENDOR_RWNX_VHT_NO80
+ band_5ghz->vht_cap.cap |= IEEE80211_VHT_CAP_NOT_SUP_WIDTH_80;
+#endif
+ band_5ghz->vht_cap.cap &= ~IEEE80211_VHT_CAP_SHORT_GI_80;
+ }
+ }
+}
+
+static void rwnx_set_ht_capa(struct rwnx_hw *rwnx_hw, struct wiphy *wiphy)
+{
+ struct ieee80211_supported_band *band_5ghz =
+ wiphy->bands[NL80211_BAND_5GHZ];
+ struct ieee80211_supported_band *band_2ghz =
+ wiphy->bands[NL80211_BAND_2GHZ];
+ int i;
+ int nss = rwnx_hw->mod_params->nss;
+
+ if (!rwnx_hw->mod_params->ht_on) {
+ band_2ghz->ht_cap.ht_supported = false;
+ if (rwnx_hw->band_5g_support)
+ band_5ghz->ht_cap.ht_supported = false;
+ return;
+ }
+
+ if (rwnx_hw->mod_params->stbc_on)
+ band_2ghz->ht_cap.cap |= 1 << IEEE80211_HT_CAP_RX_STBC_SHIFT;
+ if (rwnx_hw->mod_params->ldpc_on)
+ band_2ghz->ht_cap.cap |= IEEE80211_HT_CAP_LDPC_CODING;
+ if (rwnx_hw->mod_params->use_2040) {
+ band_2ghz->ht_cap.mcs.rx_mask[4] = 0x1; /* MCS32 */
+ band_2ghz->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40;
+ band_2ghz->ht_cap.mcs.rx_highest = cpu_to_le16(135 * nss);
+ } else {
+ band_2ghz->ht_cap.mcs.rx_highest = cpu_to_le16(65 * nss);
+ }
+ if (nss > 1)
+ band_2ghz->ht_cap.cap |= IEEE80211_HT_CAP_TX_STBC;
+
+ // Update the AMSDU max RX size
+ if (rwnx_hw->mod_params->amsdu_rx_max)
+ band_2ghz->ht_cap.cap |= IEEE80211_HT_CAP_MAX_AMSDU;
+
+ if (rwnx_hw->mod_params->sgi) {
+ band_2ghz->ht_cap.cap |= IEEE80211_HT_CAP_SGI_20;
+ if (rwnx_hw->mod_params->use_2040) {
+ band_2ghz->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40;
+ band_2ghz->ht_cap.mcs.rx_highest = cpu_to_le16(150 * nss);
+ } else {
+ band_2ghz->ht_cap.mcs.rx_highest = cpu_to_le16(72 * nss);
+ }
+ }
+ if (rwnx_hw->mod_params->gf_rx_on)
+ band_2ghz->ht_cap.cap |= IEEE80211_HT_CAP_GRN_FLD;
+
+ nss = min_t(int, nss, IEEE80211_HT_MCS_MASK_LEN);
+
+ for (i = 0; i < nss; i++)
+ band_2ghz->ht_cap.mcs.rx_mask[i] = 0xFF;
+
+ if (rwnx_hw->band_5g_support)
+ band_5ghz->ht_cap = band_2ghz->ht_cap;
+}
+
+static void rwnx_set_he_capa(struct rwnx_hw *rwnx_hw, struct wiphy *wiphy)
+{
+ struct ieee80211_supported_band *band_5ghz =
+ wiphy->bands[NL80211_BAND_5GHZ];
+ struct ieee80211_supported_band *band_2ghz =
+ wiphy->bands[NL80211_BAND_2GHZ];
+ int i;
+ int nss = rwnx_hw->mod_params->nss;
+ struct ieee80211_sta_he_cap *he_cap;
+ int mcs_map;
+
+ if (!rwnx_hw->mod_params->he_on) {
+ band_2ghz->iftype_data = NULL;
+ band_2ghz->n_iftype_data = 0;
+
+ if (rwnx_hw->band_5g_support) {
+ band_5ghz->iftype_data = NULL;
+ band_5ghz->n_iftype_data = 0;
+ }
+ return;
+ }
+ he_cap = &rwnx_he_capa[0].he_cap;
+ he_cap->has_he = true;
+ he_cap->he_cap_elem.mac_cap_info[2] |= IEEE80211_HE_MAC_CAP2_ALL_ACK;
+
+ if (rwnx_hw->mod_params->use_2040) {
+ he_cap->he_cap_elem.phy_cap_info[0] |=
+ IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G;
+ he_cap->ppe_thres[0] |= 0x10;
+ }
+ if (rwnx_hw->mod_params->use_80) {
+ he_cap->ppe_thres[0] |= 0x20;
+ he_cap->ppe_thres[2] |= 0xc0;
+ he_cap->ppe_thres[3] |= 0x07;
+ }
+ he_cap->he_cap_elem.phy_cap_info[0] |=
+ IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G;
+ if (rwnx_hw->mod_params->ldpc_on) {
+ he_cap->he_cap_elem.phy_cap_info[1] |=
+ IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD;
+ } else {
+ // If no LDPC is supported, we have to limit to MCS0_9, as LDPC is
+ // mandatory for MCS 10 and 11
+ rwnx_hw->mod_params->he_mcs_map = min_t(int, rwnx_hw->mod_params->mcs_map,
+ IEEE80211_HE_MCS_SUPPORT_0_9);
+ }
+ he_cap->he_cap_elem.phy_cap_info[1] |=
+ IEEE80211_HE_PHY_CAP1_HE_LTF_AND_GI_FOR_HE_PPDUS_0_8US |
+ IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS;
+
+ he_cap->he_cap_elem.phy_cap_info[2] |=
+ IEEE80211_HE_PHY_CAP2_MIDAMBLE_RX_TX_MAX_NSTS |
+ IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US |
+ IEEE80211_HE_PHY_CAP2_DOPPLER_RX;
+ if (rwnx_hw->mod_params->stbc_on)
+ he_cap->he_cap_elem.phy_cap_info[2] |=
+ IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ;
+
+ he_cap->he_cap_elem.phy_cap_info[3] |=
+ IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_16_QAM |
+ IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1 |
+ IEEE80211_HE_PHY_CAP3_RX_PARTIAL_BW_SU_IN_20MHZ_MU;
+
+ if (rwnx_hw->mod_params->bfmee) {
+ he_cap->he_cap_elem.phy_cap_info[4] |=
+ IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE;
+ he_cap->he_cap_elem.phy_cap_info[4] |=
+ IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_4;
+ }
+ he_cap->he_cap_elem.phy_cap_info[5] |=
+ IEEE80211_HE_PHY_CAP5_NG16_SU_FEEDBACK |
+ IEEE80211_HE_PHY_CAP5_NG16_MU_FEEDBACK;
+ he_cap->he_cap_elem.phy_cap_info[6] |=
+ IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_42_SU |
+ IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_75_MU |
+ IEEE80211_HE_PHY_CAP6_TRIG_SU_BEAMFORMING_FB |
+ IEEE80211_HE_PHY_CAP6_TRIG_MU_BEAMFORMING_PARTIAL_BW_FB |
+ IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT |
+ IEEE80211_HE_PHY_CAP6_PARTIAL_BANDWIDTH_DL_MUMIMO;
+ he_cap->he_cap_elem.phy_cap_info[7] |=
+ IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI;
+ he_cap->he_cap_elem.phy_cap_info[8] |=
+ IEEE80211_HE_PHY_CAP8_20MHZ_IN_40MHZ_HE_PPDU_IN_2G;
+ he_cap->he_cap_elem.phy_cap_info[9] |=
+ IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_COMP_SIGB |
+ IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_NON_COMP_SIGB;
+
+ if (rwnx_hw->chip_ops->is_old_ic) {
+ mcs_map = min_t(int, rwnx_hw->mod_params->he_mcs_map,
+ IEEE80211_HE_MCS_SUPPORT_0_9);
+ } else {
+ mcs_map = rwnx_hw->mod_params->he_mcs_map;
+ }
+
+ memset(&he_cap->he_mcs_nss_supp, 0, sizeof(he_cap->he_mcs_nss_supp));
+
+ nss = min_t(int, nss, 16);
+
+ for (i = 0; i < nss; i++) {
+ __le16 unsup_for_ss =
+ cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+ he_cap->he_mcs_nss_supp.rx_mcs_80 |= cpu_to_le16(mcs_map << (i * 2));
+ he_cap->he_mcs_nss_supp.rx_mcs_160 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.rx_mcs_80p80 |= unsup_for_ss;
+ mcs_map = IEEE80211_HE_MCS_SUPPORT_0_7;
+ }
+ for (; i < 8; i++) {
+ __le16 unsup_for_ss =
+ cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+ he_cap->he_mcs_nss_supp.rx_mcs_80 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.rx_mcs_160 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.rx_mcs_80p80 |= unsup_for_ss;
+ }
+ mcs_map = rwnx_hw->mod_params->he_mcs_map;
+ for (i = 0; i < nss; i++) {
+ __le16 unsup_for_ss =
+ cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+ he_cap->he_mcs_nss_supp.tx_mcs_80 |= cpu_to_le16(mcs_map << (i * 2));
+ he_cap->he_mcs_nss_supp.tx_mcs_160 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.tx_mcs_80p80 |= unsup_for_ss;
+ mcs_map = min_t(int, rwnx_hw->mod_params->he_mcs_map,
+ IEEE80211_HE_MCS_SUPPORT_0_7);
+ }
+ for (; i < 8; i++) {
+ __le16 unsup_for_ss =
+ cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+ he_cap->he_mcs_nss_supp.tx_mcs_80 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.tx_mcs_160 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.tx_mcs_80p80 |= unsup_for_ss;
+ }
+
+ if (rwnx_hw->band_5g_support) {
+ he_cap = &rwnx_he_capa[0].he_cap;
+ he_cap->has_he = true;
+ he_cap->he_cap_elem.mac_cap_info[2] |= IEEE80211_HE_MAC_CAP2_ALL_ACK;
+ if (rwnx_hw->mod_params->use_2040) {
+ he_cap->he_cap_elem.phy_cap_info[0] |=
+ IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G;
+ he_cap->ppe_thres[0] |= 0x10;
+ }
+ if (rwnx_hw->mod_params->use_80) {
+ he_cap->ppe_thres[0] |= 0x20;
+ he_cap->ppe_thres[2] |= 0xc0;
+ he_cap->ppe_thres[3] |= 0x07;
+ }
+
+ he_cap->he_cap_elem.phy_cap_info[0] |=
+ IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G;
+
+ if (rwnx_hw->mod_params->ldpc_on) {
+ he_cap->he_cap_elem.phy_cap_info[1] |=
+ IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD;
+ } else {
+ // If no LDPC is supported, we have to limit to MCS0_9, as LDPC is
+ // mandatory for MCS 10 and 11
+ rwnx_hw->mod_params->he_mcs_map = min_t(int, rwnx_hw->mod_params->mcs_map,
+ IEEE80211_HE_MCS_SUPPORT_0_9);
+ }
+ he_cap->he_cap_elem.phy_cap_info[1] |=
+ IEEE80211_HE_PHY_CAP1_HE_LTF_AND_GI_FOR_HE_PPDUS_0_8US |
+ IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS;
+ he_cap->he_cap_elem.phy_cap_info[2] |=
+ IEEE80211_HE_PHY_CAP2_MIDAMBLE_RX_TX_MAX_NSTS |
+ IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US |
+ IEEE80211_HE_PHY_CAP2_DOPPLER_RX;
+ if (rwnx_hw->mod_params->stbc_on)
+ he_cap->he_cap_elem.phy_cap_info[2] |=
+ IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ;
+ he_cap->he_cap_elem.phy_cap_info[3] |=
+ IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_16_QAM |
+ IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1 |
+ IEEE80211_HE_PHY_CAP3_RX_PARTIAL_BW_SU_IN_20MHZ_MU;
+ if (rwnx_hw->mod_params->bfmee) {
+ he_cap->he_cap_elem.phy_cap_info[4] |=
+ IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE;
+ he_cap->he_cap_elem.phy_cap_info[4] |=
+ IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_4;
+ }
+ he_cap->he_cap_elem.phy_cap_info[5] |=
+ IEEE80211_HE_PHY_CAP5_NG16_SU_FEEDBACK |
+ IEEE80211_HE_PHY_CAP5_NG16_MU_FEEDBACK;
+ he_cap->he_cap_elem.phy_cap_info[6] |=
+ IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_42_SU |
+ IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_75_MU |
+ IEEE80211_HE_PHY_CAP6_TRIG_SU_BEAMFORMING_FB |
+ IEEE80211_HE_PHY_CAP6_TRIG_MU_BEAMFORMING_PARTIAL_BW_FB |
+ IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT |
+ IEEE80211_HE_PHY_CAP6_PARTIAL_BANDWIDTH_DL_MUMIMO;
+
+ he_cap->he_cap_elem.phy_cap_info[7] |=
+ IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI;
+ he_cap->he_cap_elem.phy_cap_info[8] |=
+ IEEE80211_HE_PHY_CAP8_20MHZ_IN_40MHZ_HE_PPDU_IN_2G;
+ he_cap->he_cap_elem.phy_cap_info[9] |=
+ IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_COMP_SIGB |
+ IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_NON_COMP_SIGB;
+
+ if (rwnx_hw->chip_ops->is_old_ic) {
+ mcs_map = min_t(int, rwnx_hw->mod_params->he_mcs_map,
+ IEEE80211_HE_MCS_SUPPORT_0_9);
+ } else {
+ mcs_map = rwnx_hw->mod_params->he_mcs_map;
+ }
+
+ memset(&he_cap->he_mcs_nss_supp, 0, sizeof(he_cap->he_mcs_nss_supp));
+ for (i = 0; i < nss; i++) {
+ __le16 unsup_for_ss =
+ cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+ he_cap->he_mcs_nss_supp.rx_mcs_80 |=
+ cpu_to_le16(mcs_map << (i * 2));
+ he_cap->he_mcs_nss_supp.rx_mcs_160 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.rx_mcs_80p80 |= unsup_for_ss;
+ mcs_map = IEEE80211_HE_MCS_SUPPORT_0_7;
+ }
+ for (; i < 8; i++) {
+ __le16 unsup_for_ss =
+ cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+ he_cap->he_mcs_nss_supp.rx_mcs_80 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.rx_mcs_160 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.rx_mcs_80p80 |= unsup_for_ss;
+ }
+ mcs_map = rwnx_hw->mod_params->he_mcs_map;
+ for (i = 0; i < nss; i++) {
+ __le16 unsup_for_ss =
+ cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+ he_cap->he_mcs_nss_supp.tx_mcs_80 |=
+ cpu_to_le16(mcs_map << (i * 2));
+ he_cap->he_mcs_nss_supp.tx_mcs_160 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.tx_mcs_80p80 |= unsup_for_ss;
+ mcs_map = min_t(int, rwnx_hw->mod_params->he_mcs_map,
+ IEEE80211_HE_MCS_SUPPORT_0_7);
+ }
+ for (; i < 8; i++) {
+ __le16 unsup_for_ss =
+ cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+ he_cap->he_mcs_nss_supp.tx_mcs_80 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.tx_mcs_160 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.tx_mcs_80p80 |= unsup_for_ss;
+ }
+ }
+}
+
+static void rwnx_set_wiphy_params(struct rwnx_hw *rwnx_hw, struct wiphy *wiphy)
+{
+#ifdef CONFIG_RWNX_FULLMAC
+ /* FULLMAC specific parameters */
+ wiphy->flags |= WIPHY_FLAG_REPORTS_OBSS;
+ wiphy->max_scan_ssids = SCAN_SSID_MAX;
+ wiphy->max_scan_ie_len = SCANU_MAX_IE_LEN;
+#endif /* CONFIG_RWNX_FULLMAC */
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ if (rwnx_hw->mod_params->tdls) {
+ /* TDLS support */
+ wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS;
+#ifdef CONFIG_RWNX_FULLMAC
+ /* TDLS external setup support */
+ wiphy->flags |= WIPHY_FLAG_TDLS_EXTERNAL_SETUP;
+#endif
+ }
+
+ if (rwnx_hw->mod_params->ap_uapsd_on)
+ wiphy->flags |= WIPHY_FLAG_AP_UAPSD;
+#endif
+
+#ifdef CONFIG_RWNX_FULLMAC
+ if (rwnx_hw->mod_params->ps_on)
+ wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT;
+ else
+ wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT;
+#endif
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ if (rwnx_hw->mod_params->custregd) {
+ // Check if custom channel set shall be enabled. In such case only
+ // monitor mode is supported
+ if (rwnx_hw->mod_params->custchan) {
+ wiphy->interface_modes = BIT(NL80211_IFTYPE_MONITOR);
+
+ // Enable "extra" channels
+ wiphy->bands[NL80211_BAND_2GHZ]->n_channels += 13;
+ if (rwnx_hw->band_5g_support)
+ wiphy->bands[NL80211_BAND_5GHZ]->n_channels += 59;
+ }
+ }
+#endif
+}
+
+int rwnx_handle_dynparams(struct rwnx_hw *rwnx_hw, struct wiphy *wiphy)
+{
+ /* Set chip capabilities */
+ aic_chip_init_capa(rwnx_hw);
+ /* Set wiphy parameters */
+ rwnx_set_wiphy_params(rwnx_hw, wiphy);
+ /* Set VHT capabilities */
+ rwnx_set_vht_capa(rwnx_hw, wiphy);
+ /* Set HE capabilities */
+ rwnx_set_he_capa(rwnx_hw, wiphy);
+ /* Set HT capabilities */
+ rwnx_set_ht_capa(rwnx_hw, wiphy);
+ /* Set RF specific parameters (shall be done last as it might change some
+ * capabilities previously set)
+ */
+ return 0;
+}
+
+void rwnx_custregd(struct rwnx_hw *rwnx_hw, struct wiphy *wiphy)
+{
+// For older kernel version, the custom regulatory is applied before the wiphy
+// registration (in rwnx_set_wiphy_params()), so nothing has to be done here
+ if (!rwnx_hw->mod_params->custregd)
+ return;
+
+ wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED;
+
+ rtnl_lock();
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+ if (rwnx_regulatory_set_wiphy_regd_sync(wiphy,
+ get_regdomain_from_rwnx_db(wiphy,
+ rwnx_hw->country_abbr)))
+ AICWFDBG(LOGERROR, "Failed to set custom regdomain\n");
+ else
+ AICWFDBG(LOGINFO, "USING REGULATORY_WIPHY_SELF_MANAGED\n");
+#else
+ if (rwnx_regulatory_set_wiphy_regd_sync(wiphy,
+ get_regdomain_from_rwnx_db(wiphy,
+ default_ccode))) {
+ AICWFDBG(LOGERROR, "Failed to set custom regdomain\n");
+ } else {
+ AICWFDBG(LOGINFO, "USING REGULATORY_WIPHY_SELF_MANAGED\n");
+ }
+#endif
+ rtnl_unlock();
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mod_params.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mod_params.h
new file mode 100644
index 0000000000000..8f6a8394c2c74
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mod_params.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_MOD_PARAM_H_
+#define _RWNX_MOD_PARAM_H_
+
+struct rwnx_mod_params {
+ bool ht_on;
+ bool vht_on;
+ bool he_on;
+ int mcs_map;
+ int he_mcs_map;
+ bool he_ul_on;
+ bool ldpc_on;
+ bool stbc_on;
+ bool gf_rx_on;
+ int phy_cfg;
+ int uapsd_timeout;
+ bool ap_uapsd_on;
+ bool sgi;
+ bool sgi80;
+ bool use_2040;
+ bool use_80;
+ bool custregd;
+ bool custchan;
+ int nss;
+ int amsdu_rx_max;
+ bool bfmee;
+ bool bfmer;
+ bool mesh;
+ bool murx;
+ bool mutx;
+ bool mutx_on;
+ unsigned int roc_dur_max;
+ int listen_itv;
+ bool listen_bcmc;
+ int lp_clk_ppm;
+ bool ps_on;
+ int tx_lft;
+ int amsdu_maxnb;
+ int uapsd_queues;
+ bool tdls;
+ bool uf;
+ bool auto_reply;
+ char *ftl;
+ bool dpsm;
+#ifdef CONFIG_RWNX_FULLMAC
+ bool ant_div;
+#endif /* CONFIG_RWNX_FULLMAC */
+};
+
+extern struct rwnx_mod_params rwnx_mod_params;
+
+struct rwnx_hw;
+struct wiphy;
+struct ieee80211_regdomain;
+
+extern const struct ieee80211_regdomain *const reg_regdb[];
+extern int reg_regdb_size;
+
+int rwnx_handle_dynparams(struct rwnx_hw *rwnx_hw, struct wiphy *wiphy);
+void rwnx_custregd(struct rwnx_hw *rwnx_hw, struct wiphy *wiphy);
+void rwnx_enable_wapi(struct rwnx_hw *rwnx_hw);
+void rwnx_enable_mfp(struct rwnx_hw *rwnx_hw);
+void rwnx_get_countrycode_channels(struct wiphy *wiphy,
+ const struct ieee80211_regdomain *regdomain);
+const struct ieee80211_regdomain *
+get_regdomain_from_rwnx_db(struct wiphy *wiphy, const char *alpha2);
+const struct ieee80211_regdomain *
+get_regdomain_from_rwnx_db_index(struct wiphy *wiphy, int index);
+int rwnx_regulatory_set_wiphy_regd(struct wiphy *wiphy,
+ const struct ieee80211_regdomain *regd);
+int rwnx_regulatory_set_wiphy_regd_sync(struct wiphy *wiphy,
+ const struct ieee80211_regdomain *regd);
+
+#endif /* _RWNX_MOD_PARAM_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mu_group.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mu_group.c
new file mode 100644
index 0000000000000..3df201ac533ba
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mu_group.c
@@ -0,0 +1,653 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief mu group information
+ *
+ ******************************************************************************
+ */
+
+#include "rwnx_defs.h"
+#include "rwnx_events.h"
+#include "rwnx_msg_tx.h"
+
+/**
+ * rwnx_mu_group_sta_init - Initialize group information for a STA
+ * @sta: STA to initialize
+ * @vht_cap: VHT capabilities advertised by the STA, or %NULL
+ */
+void rwnx_mu_group_sta_init(struct rwnx_sta *sta,
+ const struct ieee80211_vht_cap *vht_cap)
+{
+ sta->group_info.map = 0;
+ sta->group_info.cnt = 0;
+ sta->group_info.active.next = LIST_POISON1;
+ sta->group_info.update.next = LIST_POISON1;
+ sta->group_info.last_update = 0;
+ sta->group_info.traffic = 0;
+ sta->group_info.group = 0;
+
+ if (!vht_cap ||
+ !(le32_to_cpu(vht_cap->vht_cap_info) &
+ IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE)) {
+ sta->group_info.map = RWNX_SU_GROUP;
+ }
+}
+
+/**
+ * rwnx_mu_group_sta_del - Remove a sta from all MU group
+ *
+ * @rwnx_hw: main driver data
+ * @sta: STA to remove
+ *
+ * Remove one sta from all the MU groups it belongs to.
+ */
+void rwnx_mu_group_sta_del(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta)
+{
+ struct rwnx_mu_info *mu = &rwnx_hw->mu;
+ int i, j, group_id;
+ bool lock_taken;
+ u64 map;
+
+ lock_taken = (down_interruptible(&mu->lock) == 0);
+
+ map = sta->group_info.map & RWNX_MU_GROUP_MASK;
+ for (group_id = (fls64(map) - 1); group_id > 0;
+ map &= ~(u64)BIT_ULL(group_id), group_id = (fls64(map) - 1)) {
+ struct rwnx_mu_group *group = rwnx_mu_group_from_id(mu, group_id);
+
+ for (i = 0; i < CONFIG_USER_MAX && group->users[i] == sta; i++) {
+ group->users[i] = NULL;
+ group->user_cnt--;
+ /* Don't keep group with only one user */
+ if (group->user_cnt == 1) {
+ for (j = 0; j < CONFIG_USER_MAX && group->users[j]; j++) {
+ group->users[j]->group_info.cnt--;
+ group->users[j]->group_info.map &=
+ ~BIT_ULL(group->group_id);
+ if (group->users[j]->group_info.group == group_id)
+ group->users[j]->group_info.group = 0;
+ group->user_cnt--;
+ break;
+ }
+ mu->group_cnt--;
+ trace_mu_group_delete(group->group_id);
+ } else {
+ trace_mu_group_update(group);
+ }
+ break;
+ }
+
+ WARN((i == CONFIG_USER_MAX), "sta %d doesn't belongs to group %d",
+ sta->sta_idx, group_id);
+ }
+
+ sta->group_info.map = 0;
+ sta->group_info.cnt = 0;
+ sta->group_info.traffic = 0;
+
+ if (sta->group_info.active.next != LIST_POISON1)
+ list_del(&sta->group_info.active);
+
+ if (sta->group_info.update.next != LIST_POISON1)
+ list_del(&sta->group_info.update);
+
+ if (lock_taken)
+ up(&mu->lock);
+}
+
+/**
+ * rwnx_mu_group_sta_get_map - Get the list of group a STA belongs to
+ *
+ * @sta: pointer to the sta
+ *
+ * Return: Bitfield of groups to which the STA belongs, or 0 for a NULL STA.
+ */
+u64 rwnx_mu_group_sta_get_map(struct rwnx_sta *sta)
+{
+ if (sta)
+ return sta->group_info.map;
+ return 0;
+}
+
+/**
+ * rwnx_mu_group_sta_get_pos - Get sta position in a group
+ *
+ * @rwnx_hw: main driver data
+ * @sta: pointer to the sta
+ * @group_id: Group id
+ *
+ * Return: Position of @sta in @group_id, or -1 if the STA does not belong to
+ * the group or the group identifier is invalid.
+ */
+int rwnx_mu_group_sta_get_pos(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta,
+ int group_id)
+{
+ struct rwnx_mu_group *group;
+ int i;
+
+ group = rwnx_mu_group_from_id(&rwnx_hw->mu, group_id);
+ if (!group)
+ return -1;
+
+ for (i = 0; i < CONFIG_USER_MAX; i++) {
+ if (group->users[i] == sta)
+ return i;
+ }
+
+ WARN(1, "sta %d doesn't belongs to group %d", sta->sta_idx, group_id);
+ return -1;
+}
+
+/**
+ * rwnx_mu_group_move_head - Move (or add) one element at the top of a list
+ *
+ * @list: list pointer
+ * @elem: element to move (or add) at the top of @list
+ *
+ */
+static inline void rwnx_mu_group_move_head(struct list_head *list,
+ struct list_head *elem)
+{
+ if (elem->next != LIST_POISON1)
+ __list_del_entry(elem);
+
+ list_add(elem, list);
+}
+
+/**
+ * rwnx_mu_group_remove_users - Remove all the users of a group
+ *
+ * @mu: pointer on MU info
+ * @group: pointer on group to remove users from
+ *
+ * Loop over all users one group and remove this group from their
+ * map (and count).
+ * Each users is also added to the update_sta list, so that group info
+ * will be resent to fw for this user.
+ */
+static inline void rwnx_mu_group_remove_users(struct rwnx_mu_info *mu,
+ struct rwnx_mu_group *group)
+{
+ struct rwnx_sta *sta;
+ int i, group_id = group->group_id;
+
+ for (i = 0; i < CONFIG_USER_MAX && group->users[i]; i++) {
+ sta = group->users[i];
+ group->users[i] = NULL;
+ sta->group_info.cnt--;
+ sta->group_info.map &= ~BIT_ULL(group_id);
+ rwnx_mu_group_move_head(&mu->update_sta, &sta->group_info.update);
+ }
+
+ if (group->user_cnt)
+ mu->group_cnt--;
+ group->user_cnt = 0;
+ trace_mu_group_delete(group_id);
+}
+
+/**
+ * rwnx_mu_group_add_users - Add users to a group
+ *
+ * @mu: pointer on MU info
+ * @group: pointer on group to add users in
+ * @nb_user: number of users to ad
+ * @users: table of user to add
+ *
+ * Add @nb_users to @group (which may already have users)
+ * Each new users is added to the first free position.
+ * It is assume that @group has at least @nb_user free position. If it is not
+ * case it only add the number of users needed to complete the group.
+ * Each users (effectively added to @group) is also added to the update_sta
+ * list, so that group info will be resent to fw for this user.
+ */
+static inline void rwnx_mu_group_add_users(struct rwnx_mu_info *mu,
+ struct rwnx_mu_group *group,
+ int nb_user, struct rwnx_sta **users)
+{
+ int i, j, group_id = group->group_id;
+
+ if (!group->user_cnt)
+ mu->group_cnt++;
+
+ j = 0;
+
+ for (i = 0; i < nb_user; i++) {
+ for (; j < CONFIG_USER_MAX; j++) {
+ if (!group->users[j]) {
+ group->users[j] = users[i];
+ users[i]->group_info.cnt++;
+ users[i]->group_info.map |= BIT_ULL(group_id);
+
+ rwnx_mu_group_move_head(&mu->update_sta,
+ &users[i]->group_info.update);
+ group->user_cnt++;
+ j++;
+ break;
+ }
+
+ WARN(j == (CONFIG_USER_MAX - 1),
+ "Too many user for group %d (nb_user=%d)", group_id,
+ group->user_cnt + nb_user - i);
+ }
+ }
+
+ trace_mu_group_update(group);
+}
+
+/*
+ * rwnx_mu_group_create_one - create on group with a specific group of user
+ *
+ * @mu: pointer on MU info
+ * @nb_user: number of user to include in the group (<= CONFIG_USER_MAX)
+ * @users: table of users
+ *
+ * Try to create a new group with a specific group of users.
+ * 1- First it checks if a group containing all this users already exists.
+ *
+ * 2- Then it checks if it is possible to complete a group which already
+ * contains at least one user.
+ *
+ * 3- Finally it create a new group. To do so, it take the last group of
+ * the active_groups list, remove all its current users and add the new ones
+ *
+ * In all cases, the group selected is moved at the top of the active_groups
+ * list
+ *
+ * @return 1 if a new group has been created and 0 otherwise
+ */
+static int rwnx_mu_group_create_one(struct rwnx_mu_info *mu, int nb_user,
+ struct rwnx_sta **users, int *nb_group_left)
+{
+ int i, group_id;
+ struct rwnx_mu_group *group;
+ u64 group_match;
+#if CONFIG_USER_MAX > 2
+ u64 group_avail;
+#endif
+
+ group_match = users[0]->group_info.map;
+#if CONFIG_USER_MAX > 2
+ group_avail = users[0]->group_info.map;
+#endif
+ for (i = 1; i < nb_user; i++) {
+ group_match &= users[i]->group_info.map;
+#if CONFIG_USER_MAX > 2
+ group_avail |= users[i]->group_info.map;
+#endif
+ }
+
+ if (group_match) {
+ /* a group (or more) with all the users already exist */
+ group_id = RWNX_GET_FIRST_GROUP_ID(group_match);
+ group = rwnx_mu_group_from_id(mu, group_id);
+ rwnx_mu_group_move_head(&mu->active_groups, &group->list);
+ return 0;
+ }
+#if CONFIG_USER_MAX > 2
+ if (group_avail) {
+ /* check if we can complete a group */
+ struct rwnx_sta *users2[CONFIG_USER_MAX];
+ int nb_user2;
+
+ for (group_id = (fls64(group_avail) - 1); group_id > 0;
+ group_avail &= ~(u64)BIT_ULL(group_id),
+ group_id = (fls64(group_avail) - 1)) {
+ group = rwnx_mu_group_from_id(mu, group_id);
+ if (group->user_cnt == CONFIG_USER_MAX)
+ continue;
+
+ nb_user2 = 0;
+ for (i = 0; i < nb_user; i++) {
+ if (!(users[i]->group_info.map & BIT_ULL(group_id))) {
+ users2[nb_user2] = users[i];
+ nb_user2++;
+ }
+ }
+
+ if ((group->user_cnt + nb_user2) <= CONFIG_USER_MAX) {
+ rwnx_mu_group_add_users(mu, group, nb_user2, users2);
+ rwnx_mu_group_move_head(&mu->active_groups, &group->list);
+ return 0;
+ }
+ }
+ }
+#endif /* CONFIG_USER_MAX > 2 */
+
+ /* create a new group */
+ group = list_last_entry(&mu->active_groups, struct rwnx_mu_group, list);
+ rwnx_mu_group_remove_users(mu, group);
+ rwnx_mu_group_add_users(mu, group, nb_user, users);
+ rwnx_mu_group_move_head(&mu->active_groups, &group->list);
+ (*nb_group_left)--;
+
+ return 1;
+}
+
+/**
+ * rwnx_mu_group_create - Create new groups containing one specific sta
+ *
+ * @mu: pointer on MU info
+ * @sta: sta to add in each group
+ * @nb_group_left: maximum number to new group allowed. (updated on exit)
+ *
+ * This will try to create "all the possible" group with a specific sta being
+ * a member of all these group.
+ * The function simply loops over the @active_sta list (starting from @sta).
+ * When it has (CONFIG_USER_MAX - 1) users it try to create a new group with
+ * these users (plus @sta).
+ * Loops end when there is no more users, or no more new group is allowed
+ *
+ */
+static void rwnx_mu_group_create(struct rwnx_mu_info *mu, struct rwnx_sta *sta,
+ int *nb_group_left)
+{
+ struct rwnx_sta *user_sta = sta;
+ struct rwnx_sta *users[CONFIG_USER_MAX];
+ int nb_user = 1;
+
+ users[0] = sta;
+ while (*nb_group_left) {
+ list_for_each_entry_continue(user_sta, &mu->active_sta,
+ group_info.active) {
+ users[nb_user] = user_sta;
+ if (++nb_user == CONFIG_USER_MAX)
+ break;
+ }
+
+ if (nb_user > 1) {
+ if (rwnx_mu_group_create_one(mu, nb_user, users, nb_group_left))
+ (*nb_group_left)--;
+
+ if (nb_user < CONFIG_USER_MAX)
+ break;
+ nb_user = 1;
+ } else {
+ break;
+ }
+ }
+}
+
+/*
+ * rwnx_mu_group_work - process function of the "group_work"
+ *
+ * The work is scheduled when several sta (MU beamformee capable) are active.
+ * When called, the @active_sta contains the list of the active sta (starting
+ * from the most recent one), and @active_groups is the list of all possible
+ * groups ordered so that the first one is the most recently used.
+ *
+ * This function will create new groups, starting from group containing the
+ * most "active" sta.
+ * For example if the list of sta is :
+ * sta8 -> sta3 -> sta4 -> sta7 -> sta1
+ * and the number of user per group is 3, it will create grooups :
+ * - sta8 / sta3 / sta4
+ * - sta8 / sta7 / sta1
+ * - sta3 / sta4 / sta7
+ * - sta3 / sta1
+ * - sta4 / sta7 / sta1
+ * - sta7 / sta1
+ *
+ * To create new group, the least used group are first selected.
+ * It is only allowed to create NX_MU_GROUP_MAX per iteration.
+ *
+ * Once groups have been updated, mu group information is update to the fw.
+ * To do so it use the @update_sta list to know which sta has been affected.
+ * As it is necessary to wait for fw confirmation before using this new group
+ * MU is temporarily disabled during group update
+ *
+ * Work is then rescheduled.
+ *
+ * At the end of the function, both @active_sta and @update_sta list are empty.
+ *
+ * Note:
+ * - This is still a WIP, and will require more tuning
+ * - not all combinations are created, to avoid to much processing.
+ * - reschedule delay should be adaptative
+ */
+static void rwnx_mu_group_work(struct work_struct *ws)
+{
+ struct delayed_work *dw = container_of(ws, struct delayed_work, work);
+ struct rwnx_mu_info *mu = container_of(dw, struct rwnx_mu_info, group_work);
+ struct rwnx_hw *rwnx_hw = container_of(mu, struct rwnx_hw, mu);
+ struct rwnx_sta *sta, *next;
+ int nb_group_left = NX_MU_GROUP_MAX;
+
+ if (WARN(!rwnx_hw->mod_params->mutx,
+ "In group formation work, but mutx disabled"))
+ return;
+
+ if (down_interruptible(&mu->lock) != 0)
+ return;
+
+ mu->update_count++;
+ if (!mu->update_count)
+ mu->update_count++;
+
+ list_for_each_entry_safe(sta, next, &mu->active_sta, group_info.active) {
+ if (nb_group_left)
+ rwnx_mu_group_create(mu, sta, &nb_group_left);
+
+ sta->group_info.last_update = mu->update_count;
+ list_del(&sta->group_info.active);
+ }
+
+ if (!list_empty(&mu->update_sta)) {
+ list_for_each_entry_safe(sta, next, &mu->update_sta, group_info.update) {
+ rwnx_send_mu_group_update_req(rwnx_hw, sta);
+ list_del(&sta->group_info.update);
+ }
+ }
+
+ mu->next_group_select = jiffies;
+ rwnx_mu_group_sta_select(rwnx_hw);
+ up(&mu->lock);
+}
+
+/**
+ * rwnx_mu_group_init - Initialize MU groups
+ *
+ * @rwnx_hw: main driver data
+ *
+ * Initialize all MU group
+ */
+void rwnx_mu_group_init(struct rwnx_hw *rwnx_hw)
+{
+ struct rwnx_mu_info *mu = &rwnx_hw->mu;
+ int i;
+
+ INIT_LIST_HEAD(&mu->active_groups);
+ INIT_LIST_HEAD(&mu->active_sta);
+ INIT_LIST_HEAD(&mu->update_sta);
+
+ for (i = 0; i < NX_MU_GROUP_MAX; i++) {
+ int j;
+
+ mu->groups[i].user_cnt = 0;
+ mu->groups[i].group_id = i + 1;
+ for (j = 0; j < CONFIG_USER_MAX; j++)
+ mu->groups[i].users[j] = NULL;
+ list_add(&mu->groups[i].list, &mu->active_groups);
+ }
+
+ mu->update_count = 1;
+ mu->group_cnt = 0;
+ mu->next_group_select = jiffies;
+ INIT_DELAYED_WORK(&mu->group_work, rwnx_mu_group_work);
+ sema_init(&mu->lock, 1);
+}
+
+/**
+ * rwnx_mu_set_active_sta - mark a STA as active
+ *
+ * @rwnx_hw: main driver data
+ * @sta: pointer to the sta
+ * @traffic: Number of buffers to add in the sta's traffic counter
+ *
+ * If @sta is MU beamformee capable (and MU-MIMO tx is enabled) move the
+ * sta at the top of the @active_sta list.
+ * It also schedule the group_work if not already scheduled and the list
+ * contains more than one sta.
+ *
+ * If a STA was already in the list during the last group update
+ * (i.e. sta->group_info.last_update == mu->update_count) it is not added
+ * back to the list until a sta that wasn't active during the last update is
+ * added. This is to avoid scheduling group update with a list of sta that
+ * were all already in the list during previous update.
+ *
+ * It is called with mu->lock taken.
+ */
+void rwnx_mu_set_active_sta(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta,
+ int traffic)
+{
+ struct rwnx_mu_info *mu = &rwnx_hw->mu;
+
+ if (!sta || (sta->group_info.map & RWNX_SU_GROUP))
+ return;
+
+ sta->group_info.traffic += traffic;
+
+ if (sta->group_info.last_update != mu->update_count ||
+ !list_empty(&mu->active_sta)) {
+ rwnx_mu_group_move_head(&mu->active_sta, &sta->group_info.active);
+
+ if (!delayed_work_pending(&mu->group_work) &&
+ !list_is_singular(&mu->active_sta)) {
+ schedule_delayed_work(&mu->group_work,
+ msecs_to_jiffies(RWNX_MU_GROUP_INTERVAL));
+ }
+ }
+}
+
+/**
+ * rwnx_mu_set_active_group - mark a MU group as active
+ *
+ * @rwnx_hw: main driver data
+ * @group_id: Group id
+ *
+ * move a group at the top of the @active_groups list
+ */
+void rwnx_mu_set_active_group(struct rwnx_hw *rwnx_hw, int group_id)
+{
+ struct rwnx_mu_info *mu = &rwnx_hw->mu;
+ struct rwnx_mu_group *group = rwnx_mu_group_from_id(mu, group_id);
+
+ rwnx_mu_group_move_head(&mu->active_groups, &group->list);
+}
+
+/**
+ * rwnx_mu_group_sta_select - Select the best group for MU stas
+ *
+ * @rwnx_hw: main driver data
+ *
+ * For each MU capable client of AP interfaces this function tries to select
+ * the best group to use.
+ *
+ * In first pass, gather information from all stations to form statistics
+ * for each group for the previous @RWNX_MU_GROUP_SELECT_INTERVAL interval:
+ * - number of buffers transmitted
+ * - number of user
+ *
+ * Then groups with more than 2 active users, are assigned after being ordered
+ * by traffic :
+ * - group with highest traffic is selected: set this group for all its users
+ * - update nb_users for all others group (as one sta may be in several groups)
+ * - select the next group that have still mor than 2 users and assign it.
+ * - continue until all group are processed
+ *
+ */
+void rwnx_mu_group_sta_select(struct rwnx_hw *rwnx_hw)
+{
+ struct rwnx_mu_info *mu = &rwnx_hw->mu;
+ int nb_users[NX_MU_GROUP_MAX + 1];
+ int traffic[NX_MU_GROUP_MAX + 1];
+ int order[NX_MU_GROUP_MAX + 1];
+ struct rwnx_sta *sta;
+ struct rwnx_vif *vif;
+ struct list_head *head;
+ u64 map;
+ int i, j, update, group_id, tmp, cnt = 0;
+
+ if (!mu->group_cnt || time_before(jiffies, mu->next_group_select))
+ return;
+
+ list_for_each_entry(vif, &rwnx_hw->vifs, list) {
+ if (RWNX_VIF_TYPE(vif) != NL80211_IFTYPE_AP)
+ continue;
+
+#ifdef CONFIG_RWNX_FULLMAC
+ head = &vif->ap.sta_list;
+#else
+ head = &vif->stations;
+#endif /* CONFIG_RWNX_FULLMAC */
+
+ memset(nb_users, 0, sizeof(nb_users));
+ memset(traffic, 0, sizeof(traffic));
+ list_for_each_entry(sta, head, list) {
+ int sta_traffic = sta->group_info.traffic;
+
+ /* reset statistics for next selection */
+ sta->group_info.traffic = 0;
+ if (sta->group_info.group)
+ trace_mu_group_selection(sta, 0);
+ sta->group_info.group = 0;
+
+ if (sta->group_info.cnt == 0 ||
+ sta_traffic < RWNX_MU_GROUP_MIN_TRAFFIC)
+ continue;
+
+ map = sta->group_info.map & RWNX_MU_GROUP_MASK;
+ for (group_id = (fls64(map) - 1); group_id > 0;
+ map &= ~(u64)BIT_ULL(group_id), group_id = (fls64(map) - 1)) {
+ nb_users[group_id]++;
+ traffic[group_id] += sta_traffic;
+
+ /* list group with 2 users or more */
+ if (nb_users[group_id] == 2)
+ order[cnt++] = group_id;
+ }
+ }
+
+ /* reorder list of group with more that 2 users */
+ update = 1;
+ while (update) {
+ update = 0;
+ for (i = 0; i < cnt - 1; i++) {
+ if (traffic[order[i]] < traffic[order[i + 1]]) {
+ tmp = order[i];
+ order[i] = order[i + 1];
+ order[i + 1] = tmp;
+ update = 1;
+ }
+ }
+ }
+
+ /* now assign group in traffic order */
+ for (i = 0; i < cnt; i++) {
+ struct rwnx_mu_group *group;
+
+ group_id = order[i];
+ if (nb_users[group_id] < 2)
+ continue;
+
+ group = rwnx_mu_group_from_id(mu, group_id);
+ for (j = 0; j < CONFIG_USER_MAX && group->users[j]; j++) {
+ trace_mu_group_selection(group->users[j], group_id);
+ group->users[j]->group_info.group = group_id;
+
+ map = group->users[j]->group_info.map & RWNX_MU_GROUP_MASK;
+ for (tmp = (fls64(map) - 1); tmp > 0;
+ map &= ~(u64)BIT_ULL(tmp), tmp = (fls64(map) - 1)) {
+ if (group_id != tmp)
+ nb_users[tmp]--;
+ }
+ }
+ }
+ }
+
+ mu->next_group_select =
+ jiffies + msecs_to_jiffies(RWNX_MU_GROUP_SELECT_INTERVAL);
+ mu->next_group_select |= 1;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mu_group.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mu_group.h
new file mode 100644
index 0000000000000..3324a242d9fca
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mu_group.h
@@ -0,0 +1,159 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_MU_GROUP_H_
+#define _RWNX_MU_GROUP_H_
+
+#include <linux/semaphore.h>
+#include <linux/workqueue.h>
+
+struct rwnx_hw;
+struct rwnx_sta;
+
+#ifdef CONFIG_RWNX_MUMIMO_TX
+
+/**
+ * struct rwnx_sta_group_info - Group information for a STA
+ *
+ * @active: Node in the active-STA list
+ * @update: Node in the STA-update list
+ * @last_update: Identifier of the last group update applied to the STA
+ * @cnt: Number of groups the STA belongs to
+ * @map: Bitfield of groups the STA belongs to
+ * @traffic: Number of buffers sent since previous group selection
+ * @group: Id of the group selected by previous group selection
+ * (cf @rwnx_mu_group_sta_select)
+ */
+struct rwnx_sta_group_info {
+ struct list_head active;
+ struct list_head update;
+ u16 last_update;
+ int cnt;
+ u64 map;
+ int traffic;
+ u8 group;
+};
+
+/**
+ * struct rwnx_mu_group - Information about the users of a group
+ *
+ * @list: node for mu->active_groups
+ * @group_id: Group identifier
+ * @user_cnt: Number of the users in the group
+ * @users: Pointer to the sta, ordered by user position
+ */
+struct rwnx_mu_group {
+ struct list_head list;
+ int group_id;
+ int user_cnt;
+ struct rwnx_sta *users[CONFIG_USER_MAX];
+};
+
+/**
+ * struct rwnx_mu_info - Information about all MU groups
+ *
+ * @active_groups: List of all possible groups. Ordered from the most recently
+ * used one to the least one (and possibly never used)
+ * @active_sta: List of MU beamformee sta that have been active (since previous
+ * group update). Ordered from the most recently active.
+ * @update_sta: List of sta whose group information has changed and need to be
+ * updated in the firmware
+ * @groups: Table of all groups
+ * @group_work: Work item used to schedule group update
+ * @update_count: Counter used to identify the last group formation update.
+ * (cf rwnx_sta_group_info.last_update)
+ * @lock: Lock held during group updates; TX does not use MU while it is held
+ * @next_group_select: Next time the group selection should be run
+ * (ref rwnx_mu_group_sta_select())
+ * @group_cnt: Number of group created
+ */
+struct rwnx_mu_info {
+ struct list_head active_groups;
+ struct list_head active_sta;
+ struct list_head update_sta;
+ struct rwnx_mu_group groups[NX_MU_GROUP_MAX];
+ struct delayed_work group_work;
+ u16 update_count;
+ struct semaphore lock;
+ unsigned long next_group_select;
+ u8 group_cnt;
+};
+
+#define RWNX_SU_GROUP BIT_ULL(0)
+#define RWNX_MU_GROUP_MASK 0x7ffffffffffffffeULL
+#define RWNX_MU_GROUP_INTERVAL 200 /* in ms */
+#define RWNX_MU_GROUP_SELECT_INTERVAL 100 /* in ms */
+// minimum traffic in a RWNX_MU_GROUP_SELECT_INTERVAL to consider the sta
+#define RWNX_MU_GROUP_MIN_TRAFFIC 50 /* in number of packet */
+
+#define RWNX_GET_FIRST_GROUP_ID(map) (fls64(map) - 1)
+
+#define RWNX_MUMIMO_INFO_POS_ID(info) (((info) >> 6) & 0x3)
+#define RWNX_MUMIMO_INFO_GROUP_ID(info) ((info) & 0x3f)
+
+static inline struct rwnx_mu_group *
+rwnx_mu_group_from_id(struct rwnx_mu_info *mu, int id)
+{
+ if (id > NX_MU_GROUP_MAX)
+ return NULL;
+
+ return &mu->groups[id - 1];
+}
+
+void rwnx_mu_group_sta_init(struct rwnx_sta *sta,
+ const struct ieee80211_vht_cap *vht_cap);
+void rwnx_mu_group_sta_del(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta);
+u64 rwnx_mu_group_sta_get_map(struct rwnx_sta *sta);
+int rwnx_mu_group_sta_get_pos(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta,
+ int group_id);
+
+void rwnx_mu_group_init(struct rwnx_hw *rwnx_hw);
+
+void rwnx_mu_set_active_sta(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta,
+ int traffic);
+void rwnx_mu_set_active_group(struct rwnx_hw *rwnx_hw, int group_id);
+void rwnx_mu_group_sta_select(struct rwnx_hw *rwnx_hw);
+
+#else /* ! CONFIG_RWNX_MUMIMO_TX */
+
+static inline void
+rwnx_mu_group_sta_init(struct rwnx_sta *sta,
+ const struct ieee80211_vht_cap *vht_cap)
+{
+}
+
+static inline void rwnx_mu_group_sta_del(struct rwnx_hw *rwnx_hw,
+ struct rwnx_sta *sta)
+{
+}
+
+static inline u64 rwnx_mu_group_sta_get_map(struct rwnx_sta *sta)
+{
+ return 0;
+}
+
+static inline int rwnx_mu_group_sta_get_pos(struct rwnx_hw *rwnx_hw,
+ struct rwnx_sta *sta, int group_id)
+{
+ return 0;
+}
+
+static inline void rwnx_mu_group_init(struct rwnx_hw *rwnx_hw)
+{
+}
+
+static inline void rwnx_mu_set_active_sta(struct rwnx_hw *rwnx_hw,
+ struct rwnx_sta *sta, int traffic)
+{
+}
+
+static inline void rwnx_mu_set_active_group(struct rwnx_hw *rwnx_hw,
+ int group_id)
+{
+}
+
+static inline void rwnx_mu_group_sta_select(struct rwnx_hw *rwnx_hw)
+{
+}
+
+#endif /* CONFIG_RWNX_MUMIMO_TX */
+
+#endif /* _RWNX_MU_GROUP_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_radar.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_radar.c
new file mode 100644
index 0000000000000..d455a11cfcaf2
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_radar.c
@@ -0,0 +1,1970 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_radar.c
+ *
+ * @brief Functions to handle radar detection
+ * Radar detection is copied (and adapted) from ath driver source code.
+ *
+ ******************************************************************************
+ */
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/list.h>
+#include <net/mac80211.h>
+
+#include "rwnx_compat.h"
+#include "rwnx_defs.h"
+#include "rwnx_events.h"
+#include "rwnx_msg_tx.h"
+#include "rwnx_radar.h"
+
+/*
+ * tolerated deviation of radar time stamp in usecs on both sides
+ * TODO: this might need to be HW-dependent
+ */
+#define PRI_TOLERANCE 16
+
+/**
+ * struct radar_types - contains array of patterns defined for one DFS domain
+ * @region: DFS regulatory domain
+ * @num_radar_types: number of radar types to follow
+ * @spec_riu: radar patterns for the RIU detector
+ * @spec_fcu: radar patterns for the FCU detector
+ */
+struct radar_types {
+ enum nl80211_dfs_regions region;
+ u32 num_radar_types;
+ const struct radar_detector_specs *spec_riu;
+ const struct radar_detector_specs *spec_fcu;
+};
+
+/* percentage on ppb threshold to trigger detection */
+#define MIN_PPB_THRESH 50
+#define PPB_THRESH(PPB) (((PPB) * MIN_PPB_THRESH + 50) / 100)
+//#define PRF2PRI(PRF) ((1000000 + (PRF) / 2) / (PRF))
+#define PRF2PRI(PRF) \
+ ({ \
+ typeof(PRF) _prf = (PRF); \
+ ((1000000 + (_prf) / 2) / (_prf)); \
+ })
+
+/* width tolerance */
+#define WIDTH_TOLERANCE 2
+#define WIDTH_LOWER(X) (X)
+#define WIDTH_UPPER(X) (X)
+
+static inline struct radar_detector_specs
+ ETSI_PATTERN_SHORT(u8 ID, u8 WMIN, u8 WMAX, u16 PMIN, u16 PMAX, u8 PPB)
+{
+ struct radar_detector_specs spec = {
+ .type_id = ID,
+ .width_min = WIDTH_LOWER(WMIN),
+ .width_max = WIDTH_UPPER(WMAX),
+ .pri_min = (PRF2PRI(PMAX) - PRI_TOLERANCE),
+ .pri_max = (PRF2PRI(PMIN) + PRI_TOLERANCE),
+ .num_pri = 1,
+ .ppb = PPB,
+ .ppb_thresh = PPB_THRESH(PPB),
+ .max_pri_tolerance = PRI_TOLERANCE,
+ .type = RADAR_WAVEFORM_SHORT
+ };
+ return spec;
+}
+
+static inline struct radar_detector_specs
+ ETSI_PATTERN_INTERLEAVED(u8 ID, u8 WMIN, u8 WMAX, u16 PMIN, u16 PMAX,
+ u16 PRFMIN, u16 PRFMAX, u8 PPB)
+{
+ struct radar_detector_specs spec = {
+ .type_id = ID,
+ .width_min = WIDTH_LOWER(WMIN),
+ .width_max = WIDTH_UPPER(WMAX),
+ .pri_min = (PRF2PRI(PMAX) * (PRFMIN) - PRI_TOLERANCE),
+ .pri_max = (PRF2PRI(PMIN) * (PRFMAX) + PRI_TOLERANCE),
+ .num_pri = PRFMAX,
+ .ppb = (PPB) * (PRFMAX),
+ .ppb_thresh = PPB_THRESH(PPB),
+ .max_pri_tolerance = PRI_TOLERANCE,
+ .type = RADAR_WAVEFORM_INTERLEAVED
+ };
+ return spec;
+}
+
+/* radar types as defined by ETSI EN-301-893 v1.7.1 */
+static const struct radar_detector_specs etsi_radar_ref_types_v17_riu[] = {
+ {0,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ (((1000000 + (700) / 2) / (700)) - PRI_TOLERANCE),
+ (((1000000 + (700) / 2) / (700)) + PRI_TOLERANCE),
+ 1,
+ 18,
+ PPB_THRESH(18),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {1,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(10),
+ (((1000000 + (1000) / 2) / (1000)) - PRI_TOLERANCE),
+ (((1000000 + (200) / 2) / (200)) + PRI_TOLERANCE),
+ 1,
+ 10,
+ PPB_THRESH(10),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {2,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(22),
+ (((1000000 + (1600) / 2) / (1600)) - PRI_TOLERANCE),
+ (((1000000 + (200) / 2) / (200)) + PRI_TOLERANCE),
+ 1,
+ 15,
+ PPB_THRESH(15),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {3,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(22),
+ (((1000000 + (4000) / 2) / (4000)) - PRI_TOLERANCE),
+ (((1000000 + (2300) / 2) / (2300)) + PRI_TOLERANCE),
+ 1,
+ 25,
+ PPB_THRESH(25),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {4,
+ WIDTH_LOWER(20),
+ WIDTH_UPPER(38),
+ (((1000000 + (4000) / 2) / (4000)) - PRI_TOLERANCE),
+ (((1000000 + (2000) / 2) / (2000)) + PRI_TOLERANCE),
+ 1,
+ 20,
+ PPB_THRESH(20),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {5,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ (((1000000 + (400) / 2) / (400)) * 2 - PRI_TOLERANCE),
+ (((1000000 + (300) / 2) / (300)) * 3 + PRI_TOLERANCE),
+ 3,
+ 10 * 3,
+ PPB_THRESH(10),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_INTERLEAVED},
+ {6,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ (((1000000 + (1200) / 2) / (1200)) * 2 - PRI_TOLERANCE),
+ (((1000000 + (400) / 2) / (400)) * 3 + PRI_TOLERANCE),
+ 3,
+ 15 * 3,
+ PPB_THRESH(15),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_INTERLEAVED},
+};
+
+static const struct radar_detector_specs etsi_radar_ref_types_v17_fcu[] = {
+ {0,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ (((1000000 + (700) / 2) / (700)) - PRI_TOLERANCE),
+ (((1000000 + (700) / 2) / (700)) + PRI_TOLERANCE),
+ 1,
+ 18,
+ PPB_THRESH(18),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {1,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ (((1000000 + (1000) / 2) / (1000)) - PRI_TOLERANCE),
+ (((1000000 + (200) / 2) / (200)) + PRI_TOLERANCE),
+ 1,
+ 10,
+ PPB_THRESH(10),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {2,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(16),
+ (((1000000 + (1600) / 2) / (1600)) - PRI_TOLERANCE),
+ (((1000000 + (200) / 2) / (200)) + PRI_TOLERANCE),
+ 1,
+ 15,
+ PPB_THRESH(15),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {3,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(16),
+ (((1000000 + (4000) / 2) / (4000)) - PRI_TOLERANCE),
+ (((1000000 + (2300) / 2) / (2300)) + PRI_TOLERANCE),
+ 1,
+ 25,
+ PPB_THRESH(25),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {4,
+ WIDTH_LOWER(20),
+ WIDTH_UPPER(34),
+ (((1000000 + (4000) / 2) / (4000)) - PRI_TOLERANCE),
+ (((1000000 + (2000) / 2) / (2000)) + PRI_TOLERANCE),
+ 1,
+ 20,
+ PPB_THRESH(20),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {5,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ (((1000000 + (400) / 2) / (400)) * 2 - PRI_TOLERANCE),
+ (((1000000 + (300) / 2) / (300)) * 3 + PRI_TOLERANCE),
+ 3,
+ 10 * 3,
+ PPB_THRESH(10),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_INTERLEAVED},
+ {6,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ (((1000000 + (1200) / 2) / (1200)) * 2 - PRI_TOLERANCE),
+ (((1000000 + (400) / 2) / (400)) * 3 + PRI_TOLERANCE),
+ 3,
+ 15 * 3,
+ PPB_THRESH(15),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_INTERLEAVED},
+};
+
+static const struct radar_types etsi_radar_types_v17 = {
+ .region = NL80211_DFS_ETSI,
+ .num_radar_types = ARRAY_SIZE(etsi_radar_ref_types_v17_riu),
+ .spec_riu = etsi_radar_ref_types_v17_riu,
+ .spec_fcu = etsi_radar_ref_types_v17_fcu,
+};
+
+static inline struct radar_detector_specs
+ FCC_PATTERN(u8 ID, u8 WMIN, u8 WMAX, u16 PMIN, u16 PMAX,
+ u16 PRF, u8 PPB, enum radar_waveform_type TYPE)
+{
+ struct radar_detector_specs spec = {
+ .type_id = ID,
+ .width_min = WIDTH_LOWER(WMIN),
+ .width_max = WIDTH_UPPER(WMAX),
+ .pri_min = (PMIN) - PRI_TOLERANCE,
+ .pri_max = (PMAX) * (PRF) + PRI_TOLERANCE,
+ .num_pri = PRF,
+ .ppb = (PPB) * (PRF),
+ .ppb_thresh = PPB_THRESH(PPB),
+ .max_pri_tolerance = PRI_TOLERANCE,
+ .type = TYPE
+ };
+ return spec;
+}
+
+static const struct radar_detector_specs fcc_radar_ref_types_riu[] = {
+ {0,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 1428 - PRI_TOLERANCE,
+ 1428 * 1 + PRI_TOLERANCE,
+ 1,
+ 18 * 1,
+ PPB_THRESH(18),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {1,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 518 - PRI_TOLERANCE,
+ 3066 * 1 + PRI_TOLERANCE,
+ 1,
+ 102 * 1,
+ PPB_THRESH(102),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_WEATHER},
+ {2,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 150 - PRI_TOLERANCE,
+ 230 * 1 + PRI_TOLERANCE,
+ 1,
+ 23 * 1,
+ PPB_THRESH(23),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {3,
+ WIDTH_LOWER(6),
+ WIDTH_UPPER(20),
+ 200 - PRI_TOLERANCE,
+ 500 * 1 + PRI_TOLERANCE,
+ 1,
+ 16 * 1,
+ PPB_THRESH(16),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {4,
+ WIDTH_LOWER(10),
+ WIDTH_UPPER(28),
+ 200 - PRI_TOLERANCE,
+ 500 * 1 + PRI_TOLERANCE,
+ 1,
+ 12 * 1,
+ PPB_THRESH(12),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {5,
+ WIDTH_LOWER(50),
+ WIDTH_UPPER(110),
+ 1000 - PRI_TOLERANCE,
+ 2000 * 1 + PRI_TOLERANCE,
+ 1,
+ 8 * 1,
+ PPB_THRESH(8),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_LONG},
+ {6,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 333 - PRI_TOLERANCE,
+ 333 * 1 + PRI_TOLERANCE,
+ 1,
+ 9 * 1,
+ PPB_THRESH(9),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+};
+
+static const struct radar_detector_specs fcc_radar_ref_types_fcu[] = {
+ {0,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 1428 - PRI_TOLERANCE,
+ 1428 * 1 + PRI_TOLERANCE,
+ 1,
+ 18 * 1,
+ PPB_THRESH(18),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {1,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 518 - PRI_TOLERANCE,
+ 3066 * 1 + PRI_TOLERANCE,
+ 1,
+ 102 * 1,
+ PPB_THRESH(102),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_WEATHER},
+ {2,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 150 - PRI_TOLERANCE,
+ 230 * 1 + PRI_TOLERANCE,
+ 1,
+ 23 * 1,
+ PPB_THRESH(23),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {3,
+ WIDTH_LOWER(6),
+ WIDTH_UPPER(12),
+ 200 - PRI_TOLERANCE,
+ 500 * 1 + PRI_TOLERANCE,
+ 1,
+ 16 * 1,
+ PPB_THRESH(16),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {4,
+ WIDTH_LOWER(10),
+ WIDTH_UPPER(22),
+ 200 - PRI_TOLERANCE,
+ 500 * 1 + PRI_TOLERANCE,
+ 1,
+ 12 * 1,
+ PPB_THRESH(12),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {5,
+ WIDTH_LOWER(50),
+ WIDTH_UPPER(104),
+ 1000 - PRI_TOLERANCE,
+ 2000 * 1 + PRI_TOLERANCE,
+ 1,
+ 8 * 1,
+ PPB_THRESH(8),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_LONG},
+ {6,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 333 - PRI_TOLERANCE,
+ 333 * 1 + PRI_TOLERANCE,
+ 1,
+ 9 * 1,
+ PPB_THRESH(9),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+};
+
+static const struct radar_types fcc_radar_types = {
+ .region = NL80211_DFS_FCC,
+ .num_radar_types = ARRAY_SIZE(fcc_radar_ref_types_riu),
+ .spec_riu = fcc_radar_ref_types_riu,
+ .spec_fcu = fcc_radar_ref_types_fcu,
+};
+
+#define JP_PATTERN FCC_PATTERN
+static const struct radar_detector_specs jp_radar_ref_types_riu[] = {
+ {0,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 1428 - PRI_TOLERANCE,
+ 1428 * 1 + PRI_TOLERANCE,
+ 1,
+ 18 * 1,
+ PPB_THRESH(18),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {1,
+ WIDTH_LOWER(2),
+ WIDTH_UPPER(8),
+ 3846 - PRI_TOLERANCE,
+ 3846 * 1 + PRI_TOLERANCE,
+ 1,
+ 18 * 1,
+ PPB_THRESH(18),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {2,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 1388 - PRI_TOLERANCE,
+ 1388 * 1 + PRI_TOLERANCE,
+ 1,
+ 18 * 1,
+ PPB_THRESH(18),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {3,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 4000 - PRI_TOLERANCE,
+ 4000 * 1 + PRI_TOLERANCE,
+ 1,
+ 18 * 1,
+ PPB_THRESH(18),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {4,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 150 - PRI_TOLERANCE,
+ 230 * 1 + PRI_TOLERANCE,
+ 1,
+ 23 * 1,
+ PPB_THRESH(23),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {5,
+ WIDTH_LOWER(6),
+ WIDTH_UPPER(20),
+ 200 - PRI_TOLERANCE,
+ 500 * 1 + PRI_TOLERANCE,
+ 1,
+ 16 * 1,
+ PPB_THRESH(16),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {6,
+ WIDTH_LOWER(10),
+ WIDTH_UPPER(28),
+ 200 - PRI_TOLERANCE,
+ 500 * 1 + PRI_TOLERANCE,
+ 1,
+ 12 * 1,
+ PPB_THRESH(12),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {7,
+ WIDTH_LOWER(50),
+ WIDTH_UPPER(110),
+ 1000 - PRI_TOLERANCE,
+ 2000 * 1 + PRI_TOLERANCE,
+ 1,
+ 8 * 1,
+ PPB_THRESH(8),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_LONG},
+ {8,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 333 - PRI_TOLERANCE,
+ 333 * 1 + PRI_TOLERANCE,
+ 1,
+ 9 * 1,
+ PPB_THRESH(9),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+};
+
+static const struct radar_detector_specs jp_radar_ref_types_fcu[] = {
+ {0,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 1428 - PRI_TOLERANCE,
+ 1428 * 1 + PRI_TOLERANCE,
+ 1,
+ 18 * 1,
+ PPB_THRESH(18),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {1,
+ WIDTH_LOWER(2),
+ WIDTH_UPPER(6),
+ 3846 - PRI_TOLERANCE,
+ 3846 * 1 + PRI_TOLERANCE,
+ 1,
+ 18 * 1,
+ PPB_THRESH(18),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {2,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 1388 - PRI_TOLERANCE,
+ 1388 * 1 + PRI_TOLERANCE,
+ 1,
+ 18 * 1,
+ PPB_THRESH(18),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {3,
+ WIDTH_LOWER(2),
+ WIDTH_UPPER(2),
+ 4000 - PRI_TOLERANCE,
+ 4000 * 1 + PRI_TOLERANCE,
+ 1,
+ 18 * 1,
+ PPB_THRESH(18),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {4,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 150 - PRI_TOLERANCE,
+ 230 * 1 + PRI_TOLERANCE,
+ 1,
+ 23 * 1,
+ PPB_THRESH(23),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {5,
+ WIDTH_LOWER(6),
+ WIDTH_UPPER(12),
+ 200 - PRI_TOLERANCE,
+ 500 * 1 + PRI_TOLERANCE,
+ 1,
+ 16 * 1,
+ PPB_THRESH(16),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {6,
+ WIDTH_LOWER(10),
+ WIDTH_UPPER(22),
+ 200 - PRI_TOLERANCE,
+ 500 * 1 + PRI_TOLERANCE,
+ 1,
+ 12 * 1,
+ PPB_THRESH(12),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+ {7,
+ WIDTH_LOWER(50),
+ WIDTH_UPPER(104),
+ 1000 - PRI_TOLERANCE,
+ 2000 * 1 + PRI_TOLERANCE,
+ 1,
+ 8 * 1,
+ PPB_THRESH(8),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_LONG},
+ {8,
+ WIDTH_LOWER(0),
+ WIDTH_UPPER(8),
+ 333 - PRI_TOLERANCE,
+ 333 * 1 + PRI_TOLERANCE,
+ 1,
+ 9 * 1,
+ PPB_THRESH(9),
+ PRI_TOLERANCE,
+ RADAR_WAVEFORM_SHORT},
+};
+
+static const struct radar_types jp_radar_types = {
+ .region = NL80211_DFS_JP,
+ .num_radar_types = ARRAY_SIZE(jp_radar_ref_types_riu),
+ .spec_riu = jp_radar_ref_types_riu,
+ .spec_fcu = jp_radar_ref_types_fcu,
+};
+
+static const struct radar_types *dfs_domains[] = {
+ &etsi_radar_types_v17,
+ &fcc_radar_types,
+ &jp_radar_types,
+};
+
+/**
+ * struct pri_detector_ops - PRI detector ops (dependent of waveform type)
+ * @init : Initialize pri_detector structure
+ * @add_pulse : Add a pulse to the pri-detector
+ * @reset_on_pri_overflow : Should the pri_detector be resetted when pri
+ * overflow
+ */
+struct pri_detector_ops {
+ void (*init)(struct pri_detector *pde);
+ struct pri_sequence *(*add_pulse)(struct pri_detector *pde, u16 len, u64 ts,
+ u16 pri);
+ int reset_on_pri_overflow;
+};
+
+/******************************************************************************
+ * PRI (pulse repetition interval) sequence detection
+ *****************************************************************************/
+/*
+ * Singleton Pulse and Sequence Pools
+ *
+ * Instances of pri_sequence and pulse_elem are kept in singleton pools to
+ * reduce the number of dynamic allocations. They are shared between all
+ * instances and grow up to the peak number of simultaneously used objects.
+ *
+ * Memory is freed after all references to the pools are released.
+ */
+static u32 singleton_pool_references;
+static LIST_HEAD(pulse_pool);
+static LIST_HEAD(pseq_pool);
+static DEFINE_SPINLOCK(pool_lock);
+
+static void pool_register_ref(void)
+{
+ spin_lock_bh(&pool_lock);
+ singleton_pool_references++;
+ spin_unlock_bh(&pool_lock);
+}
+
+static void pool_deregister_ref(void)
+{
+ spin_lock_bh(&pool_lock);
+ singleton_pool_references--;
+ if (singleton_pool_references == 0) {
+ /* free singleton pools with no references left */
+ struct pri_sequence *ps, *ps0;
+ struct pulse_elem *p, *p0;
+
+ list_for_each_entry_safe(p, p0, &pulse_pool, head) {
+ list_del(&p->head);
+ kfree(p);
+ }
+ list_for_each_entry_safe(ps, ps0, &pseq_pool, head) {
+ list_del(&ps->head);
+ kfree(ps);
+ }
+ }
+ spin_unlock_bh(&pool_lock);
+}
+
+static void pool_put_pulse_elem(struct pulse_elem *pe)
+{
+ spin_lock_bh(&pool_lock);
+ list_add(&pe->head, &pulse_pool);
+ spin_unlock_bh(&pool_lock);
+}
+
+static void pool_put_pseq_elem(struct pri_sequence *pse)
+{
+ spin_lock_bh(&pool_lock);
+ list_add(&pse->head, &pseq_pool);
+ spin_unlock_bh(&pool_lock);
+}
+
+static struct pri_sequence *pool_get_pseq_elem(void)
+{
+ struct pri_sequence *pse = NULL;
+
+ spin_lock_bh(&pool_lock);
+ if (!list_empty(&pseq_pool)) {
+ pse = list_first_entry(&pseq_pool, struct pri_sequence, head);
+ list_del(&pse->head);
+ }
+ spin_unlock_bh(&pool_lock);
+
+ if (!pse)
+ pse = kmalloc_obj(*pse, GFP_ATOMIC);
+ return pse;
+}
+
+static struct pulse_elem *pool_get_pulse_elem(void)
+{
+ struct pulse_elem *pe = NULL;
+
+ spin_lock_bh(&pool_lock);
+ if (!list_empty(&pulse_pool)) {
+ pe = list_first_entry(&pulse_pool, struct pulse_elem, head);
+ list_del(&pe->head);
+ }
+ spin_unlock_bh(&pool_lock);
+ return pe;
+}
+
+static struct pulse_elem *pulse_queue_get_tail(struct pri_detector *pde)
+{
+ struct list_head *l = &pde->pulses;
+
+ if (list_empty(l))
+ return NULL;
+ return list_entry(l->prev, struct pulse_elem, head);
+}
+
+static bool pulse_queue_dequeue(struct pri_detector *pde)
+{
+ struct pulse_elem *p = pulse_queue_get_tail(pde);
+
+ if (p) {
+ list_del_init(&p->head);
+ pde->count--;
+ /* give it back to pool */
+ pool_put_pulse_elem(p);
+ }
+ return (pde->count > 0);
+}
+
+/**
+ * pulse_queue_check_window - remove pulses older than window
+ * @pde: pointer on pri_detector
+ *
+ * dequeue pulse that are too old.
+ */
+static void pulse_queue_check_window(struct pri_detector *pde)
+{
+ u64 min_valid_ts;
+ struct pulse_elem *p;
+
+ /* there is no delta time with less than 2 pulses */
+ if (pde->count < 2)
+ return;
+
+ if (pde->last_ts <= pde->window_size)
+ return;
+
+ min_valid_ts = pde->last_ts - pde->window_size;
+ while ((p = pulse_queue_get_tail(pde)) != NULL) {
+ if (p->ts >= min_valid_ts)
+ return;
+ pulse_queue_dequeue(pde);
+ }
+}
+
+/*
+ * pulse_queue_enqueue - Queue one pulse
+ * @pde: pointer on pri_detector
+ *
+ * Add one pulse to the list. If the maximum number of pulses
+ * if reached, remove oldest one.
+ */
+static bool pulse_queue_enqueue(struct pri_detector *pde, u64 ts)
+{
+ struct pulse_elem *p = pool_get_pulse_elem();
+
+ if (!p) {
+ p = kmalloc_obj(*p, GFP_ATOMIC);
+ if (!p)
+ return false;
+ }
+ INIT_LIST_HEAD(&p->head);
+ p->ts = ts;
+ list_add(&p->head, &pde->pulses);
+ pde->count++;
+ pde->last_ts = ts;
+ pulse_queue_check_window(pde);
+ if (pde->count >= pde->max_count)
+ pulse_queue_dequeue(pde);
+
+ return true;
+}
+
+/***************************************************************************
+ * Short waveform
+ **************************************************************************/
+/*
+ * pde_get_multiple() - get number of multiples considering a given tolerance
+ * @return factor if abs(val - factor*fraction) <= tolerance, 0 otherwise
+ */
+static u32 pde_get_multiple(u32 val, u32 fraction, u32 tolerance)
+{
+ u32 remainder;
+ u32 factor;
+ u32 delta;
+
+ if (fraction == 0)
+ return 0;
+
+ delta = (val < fraction) ? (fraction - val) : (val - fraction);
+
+ if (delta <= tolerance)
+ /* val and fraction are within tolerance */
+ return 1;
+
+ factor = val / fraction;
+ remainder = val % fraction;
+ if (remainder > tolerance) {
+ /* no exact match */
+ if ((fraction - remainder) <= tolerance)
+ /* remainder is within tolerance */
+ factor++;
+ else
+ factor = 0;
+ }
+ return factor;
+}
+
+/**
+ * pde_short_create_sequences - create_sequences function for
+ * SHORT/WEATHER/INTERLEAVED radar waveform
+ * @pde: pointer on pri_detector
+ * @ts: timestamp of the pulse
+ * @min_count: Minimum number of pulse to be present in the sequence.
+ * (With this pulse there is already a sequence with @min_count
+ * pulse, so if we can't create a sequence with more pulse don't
+ * create it)
+ * @return: false if an error occurred (memory allocation) true otherwise
+ *
+ * For each pulses queued check if we can create a sequence with
+ * pri = (ts - pulse_queued.ts) which contains more than @min_count pulses.
+ *
+ */
+static bool pde_short_create_sequences(struct pri_detector *pde, u64 ts,
+ u32 min_count)
+{
+ struct pulse_elem *p;
+ u16 pulse_idx = 0;
+
+ list_for_each_entry(p, &pde->pulses, head) {
+ struct pri_sequence ps, *new_ps;
+ struct pulse_elem *p2;
+ u32 tmp_false_count;
+ u64 min_valid_ts;
+ u32 delta_ts = ts - p->ts;
+
+ pulse_idx++;
+
+ if (delta_ts < pde->rs->pri_min)
+ /* ignore too small pri */
+ continue;
+
+ if (delta_ts > pde->rs->pri_max)
+ /* stop on too large pri (sorted list) */
+ break;
+
+ /* build a new sequence with new potential pri */
+ ps.count = 2;
+ ps.count_falses = pulse_idx - 1;
+ ps.first_ts = p->ts;
+ ps.last_ts = ts;
+ ps.pri = ts - p->ts;
+ ps.dur = ps.pri * (pde->rs->ppb - 1) + 2 * pde->rs->max_pri_tolerance;
+
+ p2 = p;
+ tmp_false_count = 0;
+ if (ps.dur > ts)
+ min_valid_ts = 0;
+ else
+ min_valid_ts = ts - ps.dur;
+ /* check which past pulses are candidates for new sequence */
+ list_for_each_entry_continue(p2, &pde->pulses, head) {
+ u32 factor;
+
+ if (p2->ts < min_valid_ts)
+ /* stop on crossing window border */
+ break;
+ /* check if pulse match (multi)PRI */
+ factor = pde_get_multiple(ps.last_ts - p2->ts, ps.pri,
+ pde->rs->max_pri_tolerance);
+ if (factor > 0) {
+ ps.count++;
+ ps.first_ts = p2->ts;
+ /*
+ * on match, add the intermediate falses
+ * and reset counter
+ */
+ ps.count_falses += tmp_false_count;
+ tmp_false_count = 0;
+ } else {
+ /* this is a potential false one */
+ tmp_false_count++;
+ }
+ }
+ if (ps.count <= min_count) {
+ /* did not reach minimum count, drop sequence */
+ continue;
+ }
+ /* this is a valid one, add it */
+ ps.deadline_ts = ps.first_ts + ps.dur;
+ if (pde->rs->type == RADAR_WAVEFORM_WEATHER) {
+ ps.ppb_thresh = 19000000 / (360 * ps.pri);
+ ps.ppb_thresh = PPB_THRESH(ps.ppb_thresh);
+ } else {
+ ps.ppb_thresh = pde->rs->ppb_thresh;
+ }
+
+ new_ps = pool_get_pseq_elem();
+ if (!new_ps)
+ return false;
+ memcpy(new_ps, &ps, sizeof(ps));
+ INIT_LIST_HEAD(&new_ps->head);
+ list_add(&new_ps->head, &pde->sequences);
+ }
+ return true;
+}
+
+/**
+ * pde_short_add_to_existing_seqs - add_to_existing_seqs function for
+ * SHORT/WEATHER/INTERLEAVED radar waveform
+ * @pde: pointer on pri_detector
+ * @ts: timestamp of the pulse
+ *
+ * Check all sequemces created for this pde.
+ * - If the sequence is too old delete it.
+ * - Else if the delta with the previous pulse match the pri of the sequence
+ * add the pulse to this sequence. If the pulse cannot be added it is added
+ * to the false pulses for this sequence
+ *
+ * Return: Length of the longest sequence to which the pulse was added.
+ */
+static u32 pde_short_add_to_existing_seqs(struct pri_detector *pde, u64 ts)
+{
+ u32 max_count = 0;
+ struct pri_sequence *ps, *ps2;
+
+ list_for_each_entry_safe(ps, ps2, &pde->sequences, head) {
+ u32 delta_ts;
+ u32 factor;
+
+ /* first ensure that sequence is within window */
+ if (ts > ps->deadline_ts) {
+ list_del_init(&ps->head);
+ pool_put_pseq_elem(ps);
+ continue;
+ }
+
+ delta_ts = ts - ps->last_ts;
+ factor =
+ pde_get_multiple(delta_ts, ps->pri, pde->rs->max_pri_tolerance);
+
+ if (factor > 0) {
+ ps->last_ts = ts;
+ ps->count++;
+
+ if (max_count < ps->count)
+ max_count = ps->count;
+ } else {
+ ps->count_falses++;
+ }
+ }
+ return max_count;
+}
+
+/**
+ * pde_short_check_detection - check_detection function for
+ * SHORT/WEATHER/INTERLEAVED radar waveform
+ * @pde: pointer on pri_detector
+ *
+ * Check all sequemces created for this pde.
+ * - If a sequence contains more pulses than the threshold and more matching
+ * that false pulses.
+ *
+ * Return: The first complete sequence, or %NULL if none is complete.
+ */
+static struct pri_sequence *pde_short_check_detection(struct pri_detector *pde)
+{
+ struct pri_sequence *ps;
+
+ if (list_empty(&pde->sequences))
+ return NULL;
+
+ list_for_each_entry(ps, &pde->sequences, head) {
+ /*
+ * we assume to have enough matching confidence if we
+ * 1) have enough pulses
+ * 2) have more matching than false pulses
+ */
+ if (ps->count >= ps->ppb_thresh &&
+ ps->count * pde->rs->num_pri > ps->count_falses) {
+ return ps;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * pde_short_init - init function for
+ * SHORT/WEATHER/INTERLEAVED radar waveform
+ * @pde: pointer on pri_detector
+ *
+ * Initialize pri_detector window size to the maximun size of one burst
+ * for the radar specification associated.
+ */
+static void pde_short_init(struct pri_detector *pde)
+{
+ pde->window_size = pde->rs->pri_max * pde->rs->ppb * pde->rs->num_pri;
+ pde->max_count = pde->rs->ppb * 2;
+}
+
+static void pri_detector_reset(struct pri_detector *pde, u64 ts);
+/**
+ * pde_short_add_pulse - Add pulse to a pri_detector for
+ * SHORT/WEATHER/INTERLEAVED radar waveform
+ *
+ * @pde : pointer on pri_detector
+ * @len : width of the pulse
+ * @ts : timestamp of the pulse received
+ * @pri : Delta in us with the previous pulse.
+ * (0 means that delta in bigger than 65535 us)
+ *
+ * Process on pulse within this pri_detector
+ * - First try to add it to existing sequence
+ * - Then try to create a new and longest sequence
+ * - Check if this pulse complete a sequence
+ * - If not save this pulse in the list
+ *
+ * Return: The completed sequence, or %NULL if the pulse did not complete one.
+ */
+static struct pri_sequence *pde_short_add_pulse(struct pri_detector *pde,
+ u16 len, u64 ts, u16 pri)
+{
+ u32 max_updated_seq;
+ struct pri_sequence *ps;
+ const struct radar_detector_specs *rs = pde->rs;
+
+ if (pde->count == 0) {
+ /* This is the first pulse after reset, no need to check sequences */
+ pulse_queue_enqueue(pde, ts);
+ return NULL;
+ }
+
+ if ((ts - pde->last_ts) < rs->max_pri_tolerance) {
+ /* if delta to last pulse is too short, don't use this pulse */
+ return NULL;
+ }
+
+ max_updated_seq = pde_short_add_to_existing_seqs(pde, ts);
+
+ if (!pde_short_create_sequences(pde, ts, max_updated_seq)) {
+ pri_detector_reset(pde, ts);
+ return NULL;
+ }
+
+ ps = pde_short_check_detection(pde);
+
+ if (!ps)
+ pulse_queue_enqueue(pde, ts);
+
+ return ps;
+}
+
+/*
+ * pri detector ops to detect short radar waveform
+ * A Short waveform is defined by :
+ * The width of pulses.
+ * The interval between two pulses inside a burst (called pri)
+ * (some waveform may have or 2/3 interleaved pri)
+ * The number of pulses per burst (ppb)
+ */
+static struct pri_detector_ops pri_detector_short = {
+ .init = pde_short_init,
+ .add_pulse = pde_short_add_pulse,
+ .reset_on_pri_overflow = 1,
+};
+
+/***************************************************************************
+ * Long waveform
+ **************************************************************************/
+#define LONG_RADAR_DURATION 12000000
+#define LONG_RADAR_BURST_MIN_DURATION (12000000 / 20)
+#define LONG_RADAR_MAX_BURST 20
+
+/**
+ * pde_long_init - init function for LONG radar waveform
+ * @pde: pointer on pri_detector
+ *
+ * Initialize pri_detector window size to the long waveform radar
+ * waveform (ie. 12s) and max_count
+ */
+static void pde_long_init(struct pri_detector *pde)
+{
+ pde->window_size = LONG_RADAR_DURATION;
+ pde->max_count = LONG_RADAR_MAX_BURST; /* only count burst not pulses */
+}
+
+/**
+ * pde_long_add_pulse - Add pulse to a pri_detector for
+ * LONG radar waveform
+ *
+ * @pde : pointer on pri_detector
+ * @len : width of the pulse
+ * @ts : timestamp of the pulse received
+ * @pri : Delta in us with the previous pulse.
+ *
+ *
+ * For long pulse we only handle one sequence. Since each burst
+ * have a different set of parameters (number of pulse, pri) than
+ * the previous one we only use pulse width to add the pulse in the
+ * sequence.
+ * We only queue one pulse per burst and valid the radar when enough burst
+ * has been detected.
+ *
+ * Return: The completed sequence, or %NULL if radar was not detected.
+ */
+static struct pri_sequence *pde_long_add_pulse(struct pri_detector *pde,
+ u16 len, u64 ts, u16 pri)
+{
+ struct pri_sequence *ps;
+ const struct radar_detector_specs *rs = pde->rs;
+
+ if (list_empty(&pde->sequences)) {
+ /* First pulse, create a new sequence */
+ ps = pool_get_pseq_elem();
+ if (!ps)
+ return NULL;
+
+ /*For long waveform, "count" represents the number of burst detected */
+ ps->count = 1;
+ /*"count_false" represents the number of pulse in the current burst */
+ ps->count_falses = 1;
+ ps->first_ts = ts;
+ ps->last_ts = ts;
+ ps->deadline_ts = ts + pde->window_size;
+ ps->pri = 0;
+ INIT_LIST_HEAD(&ps->head);
+ list_add(&ps->head, &pde->sequences);
+ pulse_queue_enqueue(pde, ts);
+ } else {
+ u32 delta_ts;
+
+ ps = (struct pri_sequence *)pde->sequences.next;
+
+ delta_ts = ts - ps->last_ts;
+ ps->last_ts = ts;
+
+ if (delta_ts < rs->pri_max) {
+ /* ignore pulse too close from previous one */
+ } else if ((delta_ts >= rs->pri_min) && (delta_ts <= rs->pri_max)) {
+ /*
+ * this is a new pulse in the current burst, ignore it
+ * (i.e don't queue it)
+ */
+ ps->count_falses++;
+ } else if ((ps->count > 2) &&
+ (ps->dur + delta_ts) < LONG_RADAR_BURST_MIN_DURATION) {
+ /* not enough time between burst, ignore pulse */
+ } else {
+ /* a new burst */
+ ps->count++;
+ ps->count_falses = 1;
+
+ /* reset the start of the sequence if deadline reached */
+ if (ts > ps->deadline_ts) {
+ struct pulse_elem *p;
+ u64 min_valid_ts;
+
+ min_valid_ts = ts - pde->window_size;
+ while ((p = pulse_queue_get_tail(pde)) != NULL) {
+ if (p->ts >= min_valid_ts) {
+ ps->first_ts = p->ts;
+ ps->deadline_ts = p->ts + pde->window_size;
+ break;
+ }
+ pulse_queue_dequeue(pde);
+ ps->count--;
+ }
+ }
+
+ /*
+ * valid radar if enough burst detected and delta with first burst
+ * is at least duration/2
+ */
+ if (ps->count > pde->rs->ppb_thresh &&
+ (ts - ps->first_ts) > (pde->window_size / 2)) {
+ return ps;
+ }
+ pulse_queue_enqueue(pde, ts);
+ ps->dur = delta_ts;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * pri detector ops to detect long radar waveform
+ */
+static struct pri_detector_ops pri_detector_long = {
+ .init = pde_long_init,
+ .add_pulse = pde_long_add_pulse,
+ .reset_on_pri_overflow = 0,
+};
+
+/***************************************************************************
+ * PRI detector init/reset/exit/get
+ **************************************************************************/
+/**
+ * pri_detector_init - Create a new PRI detector
+ *
+ * @dpd: dfs_pattern_detector instance pointer
+ * @radar_type: index of radar pattern
+ * @freq: Frequency of the pri detector
+ *
+ * Return: The allocated detector, or %NULL if allocation failed.
+ */
+struct pri_detector *pri_detector_init(struct dfs_pattern_detector *dpd,
+ u16 radar_type, u16 freq)
+{
+ struct pri_detector *pde;
+
+ pde = kzalloc_obj(*pde, GFP_ATOMIC);
+ if (!pde)
+ return NULL;
+
+ INIT_LIST_HEAD(&pde->sequences);
+ INIT_LIST_HEAD(&pde->pulses);
+ INIT_LIST_HEAD(&pde->head);
+ list_add(&pde->head, &dpd->detectors[radar_type]);
+
+ pde->rs = &dpd->radar_spec[radar_type];
+ pde->freq = freq;
+
+ if (pde->rs->type == RADAR_WAVEFORM_LONG) {
+ /* for LONG WAVEFORM */
+ pde->ops = &pri_detector_long;
+ } else {
+ /* for SHORT, WEATHER and INTERLEAVED */
+ pde->ops = &pri_detector_short;
+ }
+
+ /* Init dependent of specs */
+ pde->ops->init(pde);
+
+ pool_register_ref();
+ return pde;
+}
+
+/**
+ * pri_detector_reset - Reset pri_detector
+ *
+ * @pde: pointer on pri_detector
+ * @ts: New ts reference for the pri_detector
+ *
+ * free pulse queue and sequences list and give objects back to pools
+ */
+static void pri_detector_reset(struct pri_detector *pde, u64 ts)
+{
+ struct pri_sequence *ps, *ps0;
+ struct pulse_elem *p, *p0;
+
+ list_for_each_entry_safe(ps, ps0, &pde->sequences, head) {
+ list_del_init(&ps->head);
+ pool_put_pseq_elem(ps);
+ }
+ list_for_each_entry_safe(p, p0, &pde->pulses, head) {
+ list_del_init(&p->head);
+ pool_put_pulse_elem(p);
+ }
+ pde->count = 0;
+ pde->last_ts = ts;
+}
+
+/**
+ * pri_detector_exit - Delete pri_detector
+ *
+ * @pde: pointer on pri_detector
+ */
+static void pri_detector_exit(struct pri_detector *pde)
+{
+ pri_detector_reset(pde, 0);
+ pool_deregister_ref();
+ list_del(&pde->head);
+ kfree(pde);
+}
+
+/**
+ * pri_detector_get - Get a PRI detector for a given frequency and type
+ * @dpd: dfs_pattern_detector instance pointer
+ * @freq: frequency in MHz
+ * @radar_type: index of radar pattern
+ *
+ * Return existing pri detector for the given frequency or return a
+ * newly create one.
+ * Pri detector are "merged" by frequency so that if a pri detector for a freq
+ * of +/- 2Mhz already exists don't create a new one.
+ *
+ * Maybe will need to adapt frequency merge for pattern with chirp.
+ *
+ * Return: An existing or newly allocated detector, or %NULL on allocation
+ * failure.
+ */
+static struct pri_detector *pri_detector_get(struct dfs_pattern_detector *dpd,
+ u16 freq, u16 radar_type)
+{
+ struct pri_detector *pde, *cur = NULL;
+
+ list_for_each_entry(pde, &dpd->detectors[radar_type], head) {
+ if (pde->freq == freq) {
+ if (pde->count)
+ return pde;
+ cur = pde;
+ } else if (pde->freq - 2 == freq && pde->count) {
+ return pde;
+ } else if (pde->freq + 2 == freq && pde->count) {
+ return pde;
+ }
+ }
+
+ if (cur)
+ return cur;
+ else
+ return pri_detector_init(dpd, radar_type, freq);
+}
+
+/******************************************************************************
+ * DFS Pattern Detector
+ *****************************************************************************/
+/**
+ * dfs_pattern_detector_reset() - reset all channel detectors
+ *
+ * @dpd: dfs_pattern_detector
+ */
+static void dfs_pattern_detector_reset(struct dfs_pattern_detector *dpd)
+{
+ struct pri_detector *pde;
+ int i;
+
+ for (i = 0; i < dpd->num_radar_types; i++) {
+ if (!list_empty(&dpd->detectors[i]))
+ list_for_each_entry(pde, &dpd->detectors[i], head)
+ pri_detector_reset(pde, dpd->last_pulse_ts);
+ }
+
+ dpd->last_pulse_ts = 0;
+ dpd->prev_jiffies = jiffies;
+}
+
+/*
+ * dfs_pattern_detector_reset() - delete all channel detectors
+ *
+ * @dpd: dfs_pattern_detector
+ */
+static void dfs_pattern_detector_exit(struct dfs_pattern_detector *dpd)
+{
+ struct pri_detector *pde, *pde0;
+ int i;
+
+ for (i = 0; i < dpd->num_radar_types; i++) {
+ if (!list_empty(&dpd->detectors[i]))
+ list_for_each_entry_safe(pde, pde0, &dpd->detectors[i], head)
+ pri_detector_exit(pde);
+ }
+
+ kfree(dpd);
+}
+
+/**
+ * dfs_pattern_detector_pri_overflow - reset all channel detectors on pri
+ * overflow
+ * @dpd: dfs_pattern_detector
+ */
+static void dfs_pattern_detector_pri_overflow(struct dfs_pattern_detector *dpd)
+{
+ struct pri_detector *pde;
+ int i;
+
+ for (i = 0; i < dpd->num_radar_types; i++) {
+ if (!list_empty(&dpd->detectors[i]))
+ list_for_each_entry(pde, &dpd->detectors[i], head)
+ if (pde->ops->reset_on_pri_overflow)
+ pri_detector_reset(pde, dpd->last_pulse_ts);
+ }
+}
+
+/**
+ * dfs_pattern_detector_add_pulse - Process one pulse
+ *
+ * @dpd: dfs_pattern_detector
+ * @chain: Chain that correspond to this pattern_detector (only for debug)
+ * @freq: frequency of the pulse
+ * @pri: Delta with previous pulse. (0 if delta is too big for u16)
+ * @len: width of the pulse
+ * @now: jiffies value when pulse was received
+ *
+ * Get (or create) the channel_detector for this frequency. Then add the pulse
+ * in each pri_detector created in this channel_detector.
+ *
+ *
+ * Return: %true if the pulse completes a radar pattern, otherwise %false.
+ */
+static bool dfs_pattern_detector_add_pulse(struct dfs_pattern_detector *dpd,
+ enum rwnx_radar_chain chain,
+ u16 freq, u16 pri, u16 len, u32 now)
+{
+ u32 i;
+
+ /*
+ * pulses received for a non-supported or un-initialized
+ * domain are treated as detected radars for fail-safety
+ */
+ if (dpd->region == NL80211_DFS_UNSET)
+ return true;
+
+ /* Compute pulse time stamp */
+ if (pri == 0) {
+ u32 delta_jiffie;
+
+ if (unlikely(now < dpd->prev_jiffies))
+ delta_jiffie = 0xffffffff - dpd->prev_jiffies + now;
+ else
+ delta_jiffie = now - dpd->prev_jiffies;
+ dpd->last_pulse_ts += jiffies_to_usecs(delta_jiffie);
+ dpd->prev_jiffies = now;
+ dfs_pattern_detector_pri_overflow(dpd);
+ } else {
+ dpd->last_pulse_ts += pri;
+ }
+
+ for (i = 0; i < dpd->num_radar_types; i++) {
+ struct pri_sequence *ps;
+ struct pri_detector *pde;
+ const struct radar_detector_specs *rs = &dpd->radar_spec[i];
+
+ /* no need to look up for pde if len is not within range */
+ if (rs->width_min > len || rs->width_max < len)
+ continue;
+
+ pde = pri_detector_get(dpd, freq, i);
+ ps = pde->ops->add_pulse(pde, len, dpd->last_pulse_ts, pri);
+
+ if (ps) {
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_radar_detected(chain, dpd->region, pde->freq, i, ps->pri);
+#endif
+ // reset everything instead of just the channel detector
+ dfs_pattern_detector_reset(dpd);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * get_dfs_domain_radar_types() - get radar types for a given DFS domain
+ * @param domain DFS domain
+ * @return radar_types ptr on success, NULL if DFS domain is not supported
+ */
+static const struct radar_types *
+get_dfs_domain_radar_types(enum nl80211_dfs_regions region)
+{
+ u32 i;
+
+ for (i = 0; i < ARRAY_SIZE(dfs_domains); i++) {
+ if (dfs_domains[i]->region == region)
+ return dfs_domains[i];
+ }
+ return NULL;
+}
+
+/**
+ * get_dfs_max_radar_types - Get the maximum number of radar types
+ *
+ * Return: Maximum number of radar patterns supported by any region.
+ */
+static u16 get_dfs_max_radar_types(void)
+{
+ u32 i;
+ u16 max = 0;
+
+ for (i = 0; i < ARRAY_SIZE(dfs_domains); i++) {
+ if (dfs_domains[i]->num_radar_types > max)
+ max = dfs_domains[i]->num_radar_types;
+ }
+ return max;
+}
+
+/*
+ * dfs_pattern_detector_set_domain - set DFS domain
+ *
+ * @dpd: dfs_pattern_detector
+ * @region: DFS region
+ *
+ * set DFS domain, resets detector lines upon domain changes
+ */
+static bool dfs_pattern_detector_set_domain(struct dfs_pattern_detector *dpd,
+ enum nl80211_dfs_regions region,
+ u8 chain)
+{
+ const struct radar_types *rt;
+ struct pri_detector *pde, *pde0;
+ int i;
+
+ if (dpd->region == region)
+ return true;
+
+ dpd->region = NL80211_DFS_UNSET;
+
+ rt = get_dfs_domain_radar_types(region);
+ if (!rt)
+ return false;
+
+ /* delete all pri detectors for previous DFS domain */
+ for (i = 0; i < dpd->num_radar_types; i++) {
+ if (!list_empty(&dpd->detectors[i]))
+ list_for_each_entry_safe(pde, pde0, &dpd->detectors[i], head)
+ pri_detector_exit(pde);
+ }
+
+ if (chain == RWNX_RADAR_RIU)
+ dpd->radar_spec = rt->spec_riu;
+ else
+ dpd->radar_spec = rt->spec_fcu;
+ dpd->num_radar_types = rt->num_radar_types;
+
+ dpd->region = region;
+ return true;
+}
+
+/*
+ * dfs_pattern_detector_init - Initialize dfs_pattern_detector
+ *
+ * @region: DFS region
+ * @return: pointer on dfs_pattern_detector
+ *
+ */
+static struct dfs_pattern_detector *
+dfs_pattern_detector_init(enum nl80211_dfs_regions region, u8 chain)
+{
+ struct dfs_pattern_detector *dpd;
+ u16 i, max_radar_type = get_dfs_max_radar_types();
+
+ dpd = kmalloc(sizeof(*dpd) + max_radar_type * sizeof(dpd->detectors[0]),
+ GFP_KERNEL);
+ if (!dpd)
+ return NULL;
+
+ dpd->region = NL80211_DFS_UNSET;
+ dpd->enabled = RWNX_RADAR_DETECT_DISABLE;
+ dpd->last_pulse_ts = 0;
+ dpd->prev_jiffies = jiffies;
+ dpd->num_radar_types = 0;
+ for (i = 0; i < max_radar_type; i++)
+ INIT_LIST_HEAD(&dpd->detectors[i]);
+
+ if (dfs_pattern_detector_set_domain(dpd, region, chain))
+ return dpd;
+
+ kfree(dpd);
+ return NULL;
+}
+
+/******************************************************************************
+ * driver interface
+ *****************************************************************************/
+static u16 rwnx_radar_get_center_freq(struct rwnx_hw *rwnx_hw, u8 chain)
+{
+ if (chain == RWNX_RADAR_FCU)
+ return rwnx_hw->phy.sec_chan.center_freq1;
+
+ if (chain == RWNX_RADAR_RIU) {
+#ifdef CONFIG_RWNX_FULLMAC
+ if (!rwnx_chanctx_valid(rwnx_hw, rwnx_hw->cur_chanctx)) {
+ WARN(1, "Radar pulse without channel information");
+ } else {
+ return rwnx_hw->chanctx_table[rwnx_hw->cur_chanctx]
+ .chan_def.center_freq1;
+ }
+#endif /* CONFIG_RWNX_FULLMAC */
+ }
+
+ return 0;
+}
+
+static void rwnx_radar_detected(struct rwnx_hw *rwnx_hw)
+{
+#ifdef CONFIG_RWNX_FULLMAC
+ struct cfg80211_chan_def chan_def;
+
+ if (!rwnx_chanctx_valid(rwnx_hw, rwnx_hw->cur_chanctx)) {
+ WARN(1, "Radar detected without channel information");
+ return;
+ }
+
+ /*
+ * recopy chan_def in local variable because rwnx_radar_cancel_cac may
+ * clean the variable (if in CAC and it's the only vif using this context)
+ * and CAC should be aborted before reporting the radar.
+ */
+ chan_def = rwnx_hw->chanctx_table[rwnx_hw->cur_chanctx].chan_def;
+
+ rwnx_radar_cancel_cac(&rwnx_hw->radar);
+ cfg80211_radar_event(rwnx_hw->wiphy, &chan_def, GFP_KERNEL);
+
+#endif /* CONFIG_RWNX_FULLMAC */
+}
+
+static void rwnx_radar_process_pulse(struct work_struct *ws)
+{
+ struct rwnx_radar *radar =
+ container_of(ws, struct rwnx_radar, detection_work);
+ struct rwnx_hw *rwnx_hw = container_of(radar, struct rwnx_hw, radar);
+ int chain;
+ u32 pulses[RWNX_RADAR_LAST][RWNX_RADAR_PULSE_MAX];
+ u16 pulses_count[RWNX_RADAR_LAST];
+ u32 now =
+ jiffies; /* would be better to store jiffies value in IT handler */
+
+ /* recopy pulses locally to avoid too long spin_lock */
+ spin_lock_bh(&radar->lock);
+ for (chain = RWNX_RADAR_RIU; chain < RWNX_RADAR_LAST; chain++) {
+ int start, count;
+
+ count = radar->pulses[chain].count;
+ start = radar->pulses[chain].index - count;
+ if (start < 0)
+ start += RWNX_RADAR_PULSE_MAX;
+
+ pulses_count[chain] = count;
+ if (count == 0)
+ continue;
+
+ if ((start + count) > RWNX_RADAR_PULSE_MAX) {
+ u16 count1 = (RWNX_RADAR_PULSE_MAX - start);
+
+ memcpy(&pulses[chain][0], &radar->pulses[chain].buffer[start],
+ count1 * sizeof(struct radar_pulse));
+ memcpy(&pulses[chain][count1], &radar->pulses[chain].buffer[0],
+ (count - count1) * sizeof(struct radar_pulse));
+ } else {
+ memcpy(&pulses[chain][0], &radar->pulses[chain].buffer[start],
+ count * sizeof(struct radar_pulse));
+ }
+ radar->pulses[chain].count = 0;
+ }
+ spin_unlock_bh(&radar->lock);
+
+ /* now process pulses */
+ for (chain = RWNX_RADAR_RIU; chain < RWNX_RADAR_LAST; chain++) {
+ int i;
+ u16 freq;
+
+ if (pulses_count[chain] == 0)
+ continue;
+
+ freq = rwnx_radar_get_center_freq(rwnx_hw, chain);
+
+ for (i = 0; i < pulses_count[chain]; i++) {
+ struct radar_pulse *p = (struct radar_pulse *)&pulses[chain][i];
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_radar_pulse(chain, p);
+#endif
+ if (dfs_pattern_detector_add_pulse(radar->dpd[chain], chain,
+ (s16)freq + (2 * p->freq),
+ p->rep, (p->len * 2), now)) {
+ u16 idx = radar->detected[chain].index;
+
+ if (chain == RWNX_RADAR_RIU) {
+ /* operating chain, inform upper layer to change channel */
+ if (radar->dpd[chain]->enabled ==
+ RWNX_RADAR_DETECT_REPORT) {
+ rwnx_radar_detected(rwnx_hw);
+ /*
+ * no need to report new radar until upper layer
+ * set a new channel. This prevent warning if a
+ * new radar is detected while mac80211 is
+ * changing channel
+ */
+ rwnx_radar_detection_enable
+ (radar, RWNX_RADAR_DETECT_DISABLE, chain);
+ /*
+ * purge any event received since the beginning
+ * of the function (we are sure not to interfer
+ * with tasklet as we disable detection just
+ * before)
+ */
+ radar->pulses[chain].count = 0;
+ }
+ }
+
+ radar->detected[chain].freq[idx] = (s16)freq + (2 * p->freq);
+ radar->detected[chain].time[idx] = ktime_get_real_seconds();
+ radar->detected[chain].index =
+ ((idx + 1) % NX_NB_RADAR_DETECTED);
+ radar->detected[chain].count++;
+ /* no need to process next pulses for this chain */
+ break;
+ }
+ }
+ }
+}
+
+#ifdef CONFIG_RWNX_FULLMAC
+static void rwnx_radar_cac_work(struct work_struct *ws)
+{
+ struct delayed_work *dw = container_of(ws, struct delayed_work, work);
+ struct rwnx_radar *radar = container_of(dw, struct rwnx_radar, cac_work);
+ struct rwnx_hw *rwnx_hw = container_of(radar, struct rwnx_hw, radar);
+ struct rwnx_chanctx *ctxt;
+
+ if (!radar->cac_vif) {
+ WARN(1, "CAC finished but no vif set");
+ return;
+ }
+
+ ctxt = &rwnx_hw->chanctx_table[radar->cac_vif->ch_index];
+ cfg80211_cac_event(radar->cac_vif->ndev, &ctxt->chan_def,
+ NL80211_RADAR_CAC_FINISHED, GFP_KERNEL, 0);
+ rwnx_send_apm_stop_cac_req(rwnx_hw, radar->cac_vif);
+ rwnx_chanctx_unlink(radar->cac_vif);
+
+ radar->cac_vif = NULL;
+}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+bool rwnx_radar_detection_init(struct rwnx_radar *radar)
+{
+ spin_lock_init(&radar->lock);
+
+ radar->dpd[RWNX_RADAR_RIU] =
+ dfs_pattern_detector_init(NL80211_DFS_UNSET, RWNX_RADAR_RIU);
+ if (!radar->dpd[RWNX_RADAR_RIU])
+ return false;
+
+ radar->dpd[RWNX_RADAR_FCU] =
+ dfs_pattern_detector_init(NL80211_DFS_UNSET, RWNX_RADAR_FCU);
+ if (!radar->dpd[RWNX_RADAR_FCU]) {
+ rwnx_radar_detection_deinit(radar);
+ return false;
+ }
+
+ INIT_WORK(&radar->detection_work, rwnx_radar_process_pulse);
+#ifdef CONFIG_RWNX_FULLMAC
+ INIT_DELAYED_WORK(&radar->cac_work, rwnx_radar_cac_work);
+ radar->cac_vif = NULL;
+#endif /* CONFIG_RWNX_FULLMAC */
+ return true;
+}
+
+void rwnx_radar_detection_deinit(struct rwnx_radar *radar)
+{
+ if (radar->dpd[RWNX_RADAR_RIU]) {
+ dfs_pattern_detector_exit(radar->dpd[RWNX_RADAR_RIU]);
+ radar->dpd[RWNX_RADAR_RIU] = NULL;
+ }
+ if (radar->dpd[RWNX_RADAR_FCU]) {
+ dfs_pattern_detector_exit(radar->dpd[RWNX_RADAR_FCU]);
+ radar->dpd[RWNX_RADAR_FCU] = NULL;
+ }
+}
+
+bool rwnx_radar_set_domain(struct rwnx_radar *radar,
+ enum nl80211_dfs_regions region)
+{
+ if (!radar->dpd[0])
+ return false;
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_radar_set_region(region);
+#endif
+ return (dfs_pattern_detector_set_domain(radar->dpd[RWNX_RADAR_RIU], region,
+ RWNX_RADAR_RIU) &&
+ dfs_pattern_detector_set_domain(radar->dpd[RWNX_RADAR_FCU], region,
+ RWNX_RADAR_FCU));
+}
+
+void rwnx_radar_detection_enable(struct rwnx_radar *radar, u8 enable, u8 chain)
+{
+ if (chain < RWNX_RADAR_LAST) {
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_radar_enable_detection(radar->dpd[chain]->region, enable, chain);
+#endif
+ spin_lock_bh(&radar->lock);
+ radar->dpd[chain]->enabled = enable;
+ spin_unlock_bh(&radar->lock);
+ }
+}
+
+bool rwnx_radar_detection_is_enable(struct rwnx_radar *radar, u8 chain)
+{
+ return radar->dpd[chain]->enabled != RWNX_RADAR_DETECT_DISABLE;
+}
+
+#ifdef CONFIG_RWNX_FULLMAC
+void rwnx_radar_start_cac(struct rwnx_radar *radar, u32 cac_time_ms,
+ struct rwnx_vif *vif)
+{
+ WARN(radar->cac_vif, "CAC already in progress");
+ radar->cac_vif = vif;
+ schedule_delayed_work(&radar->cac_work, msecs_to_jiffies(cac_time_ms));
+}
+
+void rwnx_radar_cancel_cac(struct rwnx_radar *radar)
+{
+ struct rwnx_hw *rwnx_hw = container_of(radar, struct rwnx_hw, radar);
+
+ if (!radar->cac_vif)
+ return;
+
+ if (cancel_delayed_work(&radar->cac_work)) {
+ struct rwnx_chanctx *ctxt;
+
+ ctxt = &rwnx_hw->chanctx_table[radar->cac_vif->ch_index];
+ rwnx_send_apm_stop_cac_req(rwnx_hw, radar->cac_vif);
+ cfg80211_cac_event(radar->cac_vif->ndev, &ctxt->chan_def,
+ NL80211_RADAR_CAC_ABORTED, GFP_KERNEL, 0);
+ rwnx_chanctx_unlink(radar->cac_vif);
+ }
+
+ radar->cac_vif = NULL;
+}
+
+void rwnx_radar_detection_enable_on_cur_channel(struct rwnx_hw *rwnx_hw)
+{
+ struct rwnx_chanctx *ctxt;
+
+ /* If no information on current channel do nothing */
+ if (!rwnx_chanctx_valid(rwnx_hw, rwnx_hw->cur_chanctx))
+ return;
+
+ ctxt = &rwnx_hw->chanctx_table[rwnx_hw->cur_chanctx];
+ if (ctxt->chan_def.chan->flags & IEEE80211_CHAN_RADAR) {
+ rwnx_radar_detection_enable(&rwnx_hw->radar, RWNX_RADAR_DETECT_REPORT,
+ RWNX_RADAR_RIU);
+ } else {
+ rwnx_radar_detection_enable(&rwnx_hw->radar, RWNX_RADAR_DETECT_DISABLE,
+ RWNX_RADAR_RIU);
+ }
+}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+/*****************************************************************************
+ * Debug functions
+ *****************************************************************************/
+static int rwnx_radar_dump_pri_detector(char *buf, size_t len,
+ struct pri_detector *pde)
+{
+ char freq_info[] = "Freq = %3.dMhz\n";
+ char seq_info[] = " pri | count | false\n";
+ struct pri_sequence *seq;
+ int res, write = 0;
+
+ if (list_empty(&pde->sequences))
+ return 0;
+
+ if (!buf) {
+ int nb_seq = 1;
+
+ list_for_each_entry(seq, &pde->sequences, head) {
+ nb_seq++;
+ }
+
+ return (sizeof(freq_info) + nb_seq * sizeof(seq_info));
+ }
+
+ res = scnprintf(buf, len, freq_info, pde->freq);
+ write += res;
+ len -= res;
+
+ res = scnprintf(&buf[write], len, "%s", seq_info);
+ write += res;
+ len -= res;
+
+ list_for_each_entry(seq, &pde->sequences, head) {
+ res = scnprintf(&buf[write], len, " %6.d | %2.d | %.2d\n",
+ seq->pri, seq->count, seq->count_falses);
+ write += res;
+ len -= res;
+ }
+
+ return write;
+}
+
+int rwnx_radar_dump_pattern_detector(char *buf, size_t len,
+ struct rwnx_radar *radar, u8 chain)
+{
+ struct dfs_pattern_detector *dpd = radar->dpd[chain];
+ char info[] = "Type = %3.d\n";
+ struct pri_detector *pde;
+ int i, res, write = 0;
+
+ /* if buf is NULL return size needed for dump */
+ if (!buf) {
+ int size_needed = 0;
+
+ for (i = 0; i < dpd->num_radar_types; i++) {
+ list_for_each_entry(pde, &dpd->detectors[i], head) {
+ size_needed += rwnx_radar_dump_pri_detector(NULL, 0, pde);
+ }
+ size_needed += sizeof(info);
+
+ return size_needed;
+ }
+ }
+
+ /* */
+ for (i = 0; i < dpd->num_radar_types; i++) {
+ res = scnprintf(&buf[write], len, info, i);
+
+ write += res;
+ len -= res;
+
+ list_for_each_entry(pde, &dpd->detectors[i], head) {
+ res = rwnx_radar_dump_pri_detector(&buf[write], len, pde);
+ write += res;
+ len -= res;
+ }
+ }
+
+ return write;
+}
+
+int rwnx_radar_dump_radar_detected(char *buf, size_t len,
+ struct rwnx_radar *radar, u8 chain)
+{
+ struct rwnx_radar_detected *detect = &radar->detected[chain];
+ char info[] = "2001/02/02 - 02:20 5126MHz\n";
+ int idx, i, res, write = 0;
+ int count = detect->count;
+
+ if (count > NX_NB_RADAR_DETECTED)
+ count = NX_NB_RADAR_DETECTED;
+
+ if (!buf)
+ return (count * sizeof(info)) + 1;
+
+ idx = (detect->index - detect->count) % NX_NB_RADAR_DETECTED;
+
+ for (i = 0; i < count; i++) {
+ struct tm tm;
+
+ time64_to_tm(detect->time[idx], 0, &tm);
+ res =
+ scnprintf(&buf[write], len, "%.4d/%.2d/%.2d - %.2d:%.2d %4.4dMHz\n",
+ (int)tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, detect->freq[idx]);
+ write += res;
+ len -= res;
+
+ idx = (idx + 1) % NX_NB_RADAR_DETECTED;
+ }
+
+ return write;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_radar.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_radar.h
new file mode 100644
index 0000000000000..e275f352681ea
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_radar.h
@@ -0,0 +1,285 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_RADAR_H_
+#define _RWNX_RADAR_H_
+
+#include <linux/nl80211.h>
+
+struct rwnx_vif;
+struct rwnx_hw;
+
+enum rwnx_radar_chain { RWNX_RADAR_RIU = 0, RWNX_RADAR_FCU, RWNX_RADAR_LAST };
+
+enum rwnx_radar_detector {
+ /* Ignore radar pulses */
+ RWNX_RADAR_DETECT_DISABLE = 0,
+ /*
+ * Process pattern detection but do not
+ * report radar to upper layer (for test)
+ */
+ RWNX_RADAR_DETECT_ENABLE = 1,
+ /*
+ * Process pattern detection and report
+ * radar to upper layer.
+ */
+ RWNX_RADAR_DETECT_REPORT = 2
+};
+
+#ifdef CONFIG_RWNX_RADAR
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+#define RWNX_RADAR_PULSE_MAX 32
+
+/**
+ * struct rwnx_radar_pulses - List of pulses reported by HW
+ * @index: write index
+ * @count: number of valid pulses
+ * @buffer: buffer of pulses
+ */
+struct rwnx_radar_pulses {
+ /* Last radar pulses received */
+ int index;
+ int count;
+ u32 buffer[RWNX_RADAR_PULSE_MAX];
+};
+
+/**
+ * struct dfs_pattern_detector - DFS pattern detector
+ * @enabled: whether radar detection is enabled
+ * @region: active DFS region, NL80211_DFS_UNSET until set
+ * @num_radar_types: number of different radar types
+ * @last_pulse_ts: time stamp of last valid pulse in usecs
+ * @prev_jiffies: jiffies value recorded for the previous pulse
+ * @radar_spec: array of radar detection specifications
+ * @detectors: per-channel detector pointers
+ */
+struct dfs_pattern_detector {
+ u8 enabled;
+ enum nl80211_dfs_regions region;
+ u8 num_radar_types;
+ u64 last_pulse_ts;
+ u32 prev_jiffies;
+ const struct radar_detector_specs *radar_spec;
+ struct list_head detectors[];
+};
+
+#define NX_NB_RADAR_DETECTED 4
+
+/**
+ * struct rwnx_radar_detected - List of radar detected
+ * @index: next entry to replace
+ * @count: number of valid entries
+ * @time: detection timestamps
+ * @freq: detection frequencies
+ */
+struct rwnx_radar_detected {
+ u16 index;
+ u16 count;
+ s64 time[NX_NB_RADAR_DETECTED];
+ s16 freq[NX_NB_RADAR_DETECTED];
+};
+
+struct rwnx_radar {
+ struct rwnx_radar_pulses pulses[RWNX_RADAR_LAST];
+ struct dfs_pattern_detector *dpd[RWNX_RADAR_LAST];
+ struct rwnx_radar_detected detected[RWNX_RADAR_LAST];
+ struct work_struct detection_work; /* Work used to process radar pulses */
+ spinlock_t lock; /* lock for pulses processing */
+
+ /* In softmac cac is handled by mac80211 */
+#ifdef CONFIG_RWNX_FULLMAC
+ struct delayed_work cac_work; /* Work used to handle CAC */
+ struct rwnx_vif *cac_vif; /* vif on which we started CAC */
+#endif
+};
+
+/*
+ * Type of radar waveform:
+ * RADAR_WAVEFORM_SHORT : waveform defined by
+ * - pulse width
+ * - pulse interval in a burst (pri)
+ * - number of pulses in a burst (ppb)
+ *
+ * RADAR_WAVEFORM_WEATHER :
+ * same than SHORT except that ppb is dependent of pri
+ *
+ * RADAR_WAVEFORM_INTERLEAVED :
+ * same than SHORT except there are several value of pri (interleaved)
+ *
+ * RADAR_WAVEFORM_LONG :
+ *
+ */
+enum radar_waveform_type {
+ RADAR_WAVEFORM_SHORT,
+ RADAR_WAVEFORM_WEATHER,
+ RADAR_WAVEFORM_INTERLEAVED,
+ RADAR_WAVEFORM_LONG
+};
+
+/**
+ * struct radar_detector_specs - detector specs for a radar pattern type
+ * @type_id: pattern type, as defined by regulatory
+ * @width_min: minimum radar pulse width in [us]
+ * @width_max: maximum radar pulse width in [us]
+ * @pri_min: minimum pulse repetition interval in [us] (including tolerance)
+ * @pri_max: minimum pri in [us] (including tolerance)
+ * @num_pri: maximum number of different pri for this type
+ * @ppb: pulses per bursts for this type
+ * @ppb_thresh: number of pulses required to trigger detection
+ * @max_pri_tolerance: pulse time stamp tolerance on both sides [us]
+ * @type: Type of radar waveform
+ */
+struct radar_detector_specs {
+ u8 type_id;
+ u8 width_min;
+ u8 width_max;
+ u16 pri_min;
+ u16 pri_max;
+ u8 num_pri;
+ u8 ppb;
+ u8 ppb_thresh;
+ u8 max_pri_tolerance;
+ enum radar_waveform_type type;
+};
+
+/**
+ * struct pri_sequence - sequence of pulses matching one PRI
+ * @head: list_head
+ * @pri: pulse repetition interval (PRI) in usecs
+ * @dur: duration of sequence in usecs
+ * @count: number of pulses in this sequence
+ * @count_falses: number of not matching pulses in this sequence
+ * @first_ts: time stamp of first pulse in usecs
+ * @last_ts: time stamp of last pulse in usecs
+ * @deadline_ts: deadline when this sequence becomes invalid (first_ts + dur)
+ * @ppb_thresh: Number of pulses to validate detection
+ * (need for weather radar whose value depends of pri)
+ */
+struct pri_sequence {
+ struct list_head head;
+ u32 pri;
+ u32 dur;
+ u32 count;
+ u32 count_falses;
+ u64 first_ts;
+ u64 last_ts;
+ u64 deadline_ts;
+ u8 ppb_thresh;
+};
+
+/**
+ * struct pulse_elem - elements in pulse queue
+ * @head: list node
+ * @ts: time stamp in usecs
+ */
+struct pulse_elem {
+ struct list_head head;
+ u64 ts;
+};
+
+/**
+ * struct pri_detector - PRI detector element for a dedicated radar type
+ * @head: list node
+ * @rs: detector specs for this detector element
+ * @last_ts: last pulse time stamp considered for this element in usecs
+ * @sequences: list_head holding potential pulse sequences
+ * @pulses: list connecting pulse_elem objects
+ * @count: number of pulses in queue
+ * @max_count: maximum number of pulses to be queued
+ * @window_size: window size back from newest pulse time stamp in usecs
+ * @ops: detector operations
+ * @freq: channel frequency in MHz
+ */
+struct pri_detector {
+ struct list_head head;
+ const struct radar_detector_specs *rs;
+ u64 last_ts;
+ struct list_head sequences;
+ struct list_head pulses;
+ u32 count;
+ u32 max_count;
+ u32 window_size;
+ struct pri_detector_ops *ops;
+ u16 freq;
+};
+
+struct pri_detector *pri_detector_init(struct dfs_pattern_detector *dpd,
+ u16 radar_type, u16 freq);
+
+bool rwnx_radar_detection_init(struct rwnx_radar *radar);
+void rwnx_radar_detection_deinit(struct rwnx_radar *radar);
+bool rwnx_radar_set_domain(struct rwnx_radar *radar,
+ enum nl80211_dfs_regions region);
+void rwnx_radar_detection_enable(struct rwnx_radar *radar, u8 enable, u8 chain);
+bool rwnx_radar_detection_is_enable(struct rwnx_radar *radar, u8 chain);
+void rwnx_radar_start_cac(struct rwnx_radar *radar, u32 cac_time_ms,
+ struct rwnx_vif *vif);
+void rwnx_radar_cancel_cac(struct rwnx_radar *radar);
+void rwnx_radar_detection_enable_on_cur_channel(struct rwnx_hw *rwnx_hw);
+int rwnx_radar_dump_pattern_detector(char *buf, size_t len,
+ struct rwnx_radar *radar, u8 chain);
+int rwnx_radar_dump_radar_detected(char *buf, size_t len,
+ struct rwnx_radar *radar, u8 chain);
+
+#else
+
+struct rwnx_radar {
+};
+
+static inline bool rwnx_radar_detection_init(struct rwnx_radar *radar)
+{
+ return true;
+}
+
+static inline void rwnx_radar_detection_deinit(struct rwnx_radar *radar)
+{
+}
+
+static inline bool rwnx_radar_set_domain(struct rwnx_radar *radar,
+ enum nl80211_dfs_regions region)
+{
+ return true;
+}
+
+static inline void rwnx_radar_detection_enable(struct rwnx_radar *radar,
+ u8 enable, u8 chain)
+{
+}
+
+static inline bool rwnx_radar_detection_is_enable(struct rwnx_radar *radar,
+ u8 chain)
+{
+ return false;
+}
+
+static inline void rwnx_radar_start_cac(struct rwnx_radar *radar,
+ u32 cac_time_ms, struct rwnx_vif *vif)
+{
+}
+
+static inline void rwnx_radar_cancel_cac(struct rwnx_radar *radar)
+{
+}
+
+static inline void
+rwnx_radar_detection_enable_on_cur_channel(struct rwnx_hw *rwnx_hw)
+{
+}
+
+static inline int rwnx_radar_dump_pattern_detector(char *buf, size_t len,
+ struct rwnx_radar *radar,
+ u8 chain)
+{
+ return 0;
+}
+
+static inline int rwnx_radar_dump_radar_detected(char *buf, size_t len,
+ struct rwnx_radar *radar,
+ u8 chain)
+{
+ return 0;
+}
+
+#endif /* CONFIG_RWNX_RADAR */
+
+#endif // _RWNX_RADAR_H_
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_rx.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_rx.c
new file mode 100644
index 0000000000000..2037800ec656d
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_rx.c
@@ -0,0 +1,2320 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_rx.c
+ *
+ * @brief RX handle
+ *
+ ******************************************************************************
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/etherdevice.h>
+#include <linux/ieee80211.h>
+#include <net/ieee80211_radiotap.h>
+
+#include "aicwf_txrxif.h"
+#include "ipc_host.h"
+#include "rwnx_compat.h"
+#include "rwnx_defs.h"
+#include "rwnx_events.h"
+#include "rwnx_prof.h"
+#include "rwnx_rx.h"
+#include "rwnx_tx.h"
+#ifdef AICWF_ARP_OFFLOAD
+#include "rwnx_msg_tx.h"
+#include <linux/ip.h>
+#include <linux/udp.h>
+#endif
+
+#ifndef IEEE80211_MAX_CHAINS
+#define IEEE80211_MAX_CHAINS 4
+#endif
+
+u16 tx_legrates_lut_rate[] = {10, 20, 55, 110, 60, 90,
+ 120, 180, 240, 360, 480, 540};
+
+struct rwnx_legrate legrates_lut[] = {
+ [0] = {.idx = 0, .rate = 10}, [1] = {.idx = 1, .rate = 20},
+ [2] = {.idx = 2, .rate = 55}, [3] = {.idx = 3, .rate = 110},
+ [4] = {.idx = -1, .rate = 0}, [5] = {.idx = -1, .rate = 0},
+ [6] = {.idx = -1, .rate = 0}, [7] = {.idx = -1, .rate = 0},
+ [8] = {.idx = 10, .rate = 480}, [9] = {.idx = 8, .rate = 240},
+ [10] = {.idx = 6, .rate = 120}, [11] = {.idx = 4, .rate = 60},
+ [12] = {.idx = 11, .rate = 540}, [13] = {.idx = 9, .rate = 360},
+ [14] = {.idx = 7, .rate = 180}, [15] = {.idx = 5, .rate = 90},
+};
+
+struct vendor_radiotap_hdr {
+ u8 oui[3];
+ u8 subns;
+ u16 len;
+ u8 data[];
+};
+
+/**
+ * rwnx_rx_get_vif - Return pointer to the destination vif
+ *
+ * @rwnx_hw: main driver data
+ * @vif_idx: vif index present in rx descriptor
+ *
+ * Select the vif that should receive this frame. Returns NULL if the
+ * destination vif is not active or vif is not specified in the descriptor.
+ *
+ * Return: The active destination VIF, or %NULL if none was selected.
+ */
+static inline struct rwnx_vif *rwnx_rx_get_vif(struct rwnx_hw *rwnx_hw,
+ int vif_idx)
+{
+ struct rwnx_vif *rwnx_vif = NULL;
+
+ if (vif_idx < NX_VIRT_DEV_MAX) {
+ rwnx_vif = rwnx_hw->vif_table[vif_idx];
+
+ if (!rwnx_vif) {
+ AICWFDBG(LOGERROR, "%s rwnx_hw->vif_table[%d] NULL\r\n", __func__,
+ vif_idx);
+ return NULL;
+ } else if (!rwnx_vif->up) {
+ AICWFDBG(LOGERROR, "%s rwnx_hw->vif_table[%d] is down\r\n",
+ __func__, vif_idx);
+ return NULL;
+ }
+ }
+
+ return rwnx_vif;
+}
+
+/**
+ * rwnx_rx_vector_convert - Convert a legacy RX vector into a new RX vector
+ * format
+ *
+ * @rwnx_hw: main driver data.
+ * @rx_vect1: Rx vector 1 descriptor of the received frame.
+ * @rx_vect2: Rx vector 2 descriptor of the received frame.
+ */
+static void rwnx_rx_vector_convert(struct rwnx_hw *rwnx_hw,
+ struct rx_vector_1 *rx_vect1,
+ struct rx_vector_2 *rx_vect2)
+{
+ struct rx_vector_1_old rx_vect1_leg;
+ struct rx_vector_2_old rx_vect2_leg;
+ u32 phy_vers = rwnx_hw->version_cfm.version_phy_2;
+
+ // Check if we need to do the conversion. Only if old modem is used
+ if (__MDM_MAJOR_VERSION(phy_vers) > 0) {
+ rx_vect1->rssi1 = rx_vect1->rssi_leg;
+ return;
+ }
+ // Copy the received vector locally
+ memcpy(&rx_vect1_leg, rx_vect1, sizeof(struct rx_vector_1_old));
+
+ // Reset it
+ memset(rx_vect1, 0, sizeof(struct rx_vector_1));
+
+ // Perform the conversion
+ rx_vect1->format_mod = rx_vect1_leg.format_mod;
+ rx_vect1->ch_bw = rx_vect1_leg.ch_bw;
+ rx_vect1->antenna_set = rx_vect1_leg.antenna_set;
+ rx_vect1->leg_length = rx_vect1_leg.leg_length;
+ rx_vect1->leg_rate = rx_vect1_leg.leg_rate;
+ rx_vect1->rssi1 = rx_vect1_leg.rssi1;
+
+ switch (rx_vect1->format_mod) {
+ case FORMATMOD_NON_HT:
+ case FORMATMOD_NON_HT_DUP_OFDM:
+ rx_vect1->leg.lsig_valid = rx_vect1_leg.lsig_valid;
+ rx_vect1->leg.chn_bw_in_non_ht = rx_vect1_leg.num_extn_ss;
+ rx_vect1->leg.dyn_bw_in_non_ht = rx_vect1_leg.dyn_bw;
+ break;
+ case FORMATMOD_HT_MF:
+ case FORMATMOD_HT_GF:
+ rx_vect1->ht.aggregation = rx_vect1_leg.aggregation;
+ rx_vect1->ht.fec = rx_vect1_leg.fec_coding;
+ rx_vect1->ht.lsig_valid = rx_vect1_leg.lsig_valid;
+ rx_vect1->ht.length = rx_vect1_leg.ht_length;
+ rx_vect1->ht.mcs = rx_vect1_leg.mcs;
+ rx_vect1->ht.num_extn_ss = rx_vect1_leg.num_extn_ss;
+ rx_vect1->ht.short_gi = rx_vect1_leg.short_gi;
+ rx_vect1->ht.smoothing = rx_vect1_leg.smoothing;
+ rx_vect1->ht.sounding = rx_vect1_leg.sounding;
+ rx_vect1->ht.stbc = rx_vect1_leg.stbc;
+ break;
+ case FORMATMOD_VHT:
+ rx_vect1->vht.beamformed = !rx_vect1_leg.smoothing;
+ rx_vect1->vht.fec = rx_vect1_leg.fec_coding;
+ rx_vect1->vht.length = rx_vect1_leg.ht_length | rx_vect1_leg._ht_length << 8;
+ rx_vect1->vht.mcs = rx_vect1_leg.mcs & 0x0F;
+ rx_vect1->vht.nss =
+ rx_vect1_leg.stbc ? rx_vect1_leg.n_sts / 2 : rx_vect1_leg.n_sts;
+ rx_vect1->vht.doze_not_allowed = rx_vect1_leg.doze_not_allowed;
+ rx_vect1->vht.short_gi = rx_vect1_leg.short_gi;
+ rx_vect1->vht.sounding = rx_vect1_leg.sounding;
+ rx_vect1->vht.stbc = rx_vect1_leg.stbc;
+ rx_vect1->vht.group_id = rx_vect1_leg.group_id;
+ rx_vect1->vht.partial_aid = rx_vect1_leg.partial_aid;
+ rx_vect1->vht.first_user = rx_vect1_leg.first_user;
+ break;
+ }
+
+ if (!rx_vect2)
+ return;
+
+ // Copy the received vector 2 locally
+ memcpy(&rx_vect2_leg, rx_vect2, sizeof(struct rx_vector_2_old));
+
+ // Reset it
+ memset(rx_vect2, 0, sizeof(struct rx_vector_2));
+
+ rx_vect2->rcpi1 = rx_vect2_leg.rcpi;
+ rx_vect2->rcpi2 = rx_vect2_leg.rcpi;
+ rx_vect2->rcpi3 = rx_vect2_leg.rcpi;
+ rx_vect2->rcpi4 = rx_vect2_leg.rcpi;
+
+ rx_vect2->evm1 = rx_vect2_leg.evm1;
+ rx_vect2->evm2 = rx_vect2_leg.evm2;
+ rx_vect2->evm3 = rx_vect2_leg.evm3;
+ rx_vect2->evm4 = rx_vect2_leg.evm4;
+}
+
+/**
+ * rwnx_rx_statistic - save some statistics about received frames
+ *
+ * @rwnx_hw: main driver data.
+ * @hw_rxhdr: Rx Hardware descriptor of the received frame.
+ * @sta: STA that sent the frame.
+ */
+static void rwnx_rx_statistic(struct rwnx_hw *rwnx_hw,
+ struct hw_rxhdr *hw_rxhdr, struct rwnx_sta *sta)
+{
+ struct rwnx_stats *stats = &rwnx_hw->stats;
+ struct rwnx_rx_rate_stats *rate_stats = &sta->stats.rx_rate;
+ struct rx_vector_1 *rxvect = &hw_rxhdr->hwvect.rx_vect1;
+ int mpdu, ampdu, mpdu_prev, rate_idx;
+
+ /* save complete hwvect */
+ sta->stats.last_rx = hw_rxhdr->hwvect;
+
+ /* update ampdu rx stats */
+ mpdu = hw_rxhdr->hwvect.mpdu_cnt;
+ ampdu = hw_rxhdr->hwvect.ampdu_cnt;
+ mpdu_prev = stats->ampdus_rx_map[ampdu];
+
+ if (mpdu_prev < mpdu)
+ stats->ampdus_rx_miss += mpdu - mpdu_prev - 1;
+ else
+ stats->ampdus_rx[mpdu_prev]++;
+ stats->ampdus_rx_map[ampdu] = mpdu;
+
+ /* update rx rate statistic */
+ if (!rate_stats->size)
+ return;
+
+ if (rxvect->format_mod > FORMATMOD_NON_HT_DUP_OFDM) {
+ int mcs;
+ int bw = rxvect->ch_bw;
+ int sgi;
+ int nss;
+
+ switch (rxvect->format_mod) {
+ case FORMATMOD_HT_MF:
+ case FORMATMOD_HT_GF:
+ mcs = rxvect->ht.mcs % 8;
+ nss = rxvect->ht.mcs / 8;
+ sgi = rxvect->ht.short_gi;
+ rate_idx = N_CCK + N_OFDM + nss * 32 + mcs * 4 + bw * 2 + sgi;
+ break;
+ case FORMATMOD_VHT:
+ mcs = rxvect->vht.mcs;
+ nss = rxvect->vht.nss;
+ sgi = rxvect->vht.short_gi;
+ rate_idx =
+ N_CCK + N_OFDM + N_HT + nss * 80 + mcs * 8 + bw * 2 + sgi;
+ break;
+ case FORMATMOD_HE_SU:
+ mcs = rxvect->he.mcs;
+ nss = rxvect->he.nss;
+ sgi = rxvect->he.gi_type;
+ rate_idx = N_CCK + N_OFDM + N_HT + N_VHT + nss * 144 + mcs * 12 +
+ bw * 3 + sgi;
+ break;
+ default:
+ mcs = rxvect->he.mcs;
+ nss = rxvect->he.nss;
+ sgi = rxvect->he.gi_type;
+ rate_idx = N_CCK + N_OFDM + N_HT + N_VHT + N_HE_SU + nss * 216 +
+ mcs * 18 + rxvect->he.ru_size * 3 + sgi;
+ break;
+ }
+ } else {
+ int idx = legrates_lut[rxvect->leg_rate].idx;
+
+ if (idx < 4)
+ rate_idx = idx * 2 + rxvect->pre_type;
+ else
+ rate_idx = N_CCK + idx - 4;
+ }
+ if (rate_idx < rate_stats->size) {
+ if (!rate_stats->table[rate_idx])
+ rate_stats->rate_cnt++;
+ rate_stats->table[rate_idx]++;
+ rate_stats->cpt++;
+ } else {
+ wiphy_err(rwnx_hw->wiphy, "RX: Invalid index conversion => %d/%d\n",
+ rate_idx, rate_stats->size);
+ }
+}
+
+/**
+ * rwnx_rx_data_skb_resend - Resend one received data frame
+ *
+ * @rwnx_hw: Main driver data
+ * @rwnx_vif: VIF that received the buffer
+ * @skb: Received socket buffer
+ * @rxhdr: Hardware receive descriptor
+ *
+ * Copy the received frame into a buffer with enough headroom and queue it for
+ * transmission on the same wireless interface.
+ */
+
+void rwnx_rx_data_skb_resend(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+ struct sk_buff *skb, struct hw_rxhdr *rxhdr)
+{
+ struct sk_buff *rx_skb = skb;
+ struct sk_buff *skb_copy;
+
+ rx_skb->dev = rwnx_vif->ndev;
+ skb_reset_mac_header(rx_skb);
+ // AICWFDBG(LOGDEBUG, "resend\n");
+ /* resend pkt on wireless interface */
+ /*
+ * always need to copy buffer when forward=0 to get enough headrom for
+ * tsdesc
+ */
+ skb_copy = skb_copy_expand
+ (rx_skb, sizeof(struct rwnx_txhdr) + RWNX_SWTXHDR_ALIGN_SZ + 3 + 24 + 8,
+ 0, GFP_ATOMIC);
+
+ if (skb_copy) {
+ int res;
+
+ skb_copy->protocol = htons(ETH_P_802_3);
+ skb_reset_network_header(skb_copy);
+ skb_reset_mac_header(skb_copy);
+
+ rwnx_vif->is_resending = true;
+ res = dev_queue_xmit(skb_copy);
+ rwnx_vif->is_resending = false;
+ /* note: buffer is always consummed by dev_queue_xmit */
+ if (res == NET_XMIT_DROP) {
+ rwnx_vif->net_stats.rx_dropped++;
+ rwnx_vif->net_stats.tx_dropped++;
+ } else if (res != NET_XMIT_SUCCESS) {
+ netdev_err(rwnx_vif->ndev,
+ "Failed to re-send buffer to driver (res=%d)", res);
+ rwnx_vif->net_stats.tx_errors++;
+ }
+ } else {
+ netdev_err(rwnx_vif->ndev, "Failed to copy skb");
+ }
+}
+
+static void rwnx_rx_data_skb_forward(struct rwnx_hw *rwnx_hw,
+ struct rwnx_vif *rwnx_vif,
+ struct sk_buff *skb,
+ struct hw_rxhdr *rxhdr)
+{
+ struct sk_buff *rx_skb;
+
+ rx_skb = skb;
+ rx_skb->dev = rwnx_vif->ndev;
+ skb_reset_mac_header(rx_skb);
+
+ /* Update statistics */
+ rwnx_vif->net_stats.rx_packets++;
+ rwnx_vif->net_stats.rx_bytes += rx_skb->len;
+
+ rx_skb->protocol = eth_type_trans(rx_skb, rwnx_vif->ndev);
+ memset(rx_skb->cb, 0, sizeof(rx_skb->cb));
+ REG_SW_SET_PROFILING(rwnx_hw, SW_PROF_IEEE80211RX);
+
+#ifdef CONFIG_AIC8800_FILTER_TCP_ACK
+ filter_rx_tcp_ack(rwnx_hw, rx_skb->data, rx_skb->len);
+#endif
+
+#ifdef CONFIG_AIC8800_RX_NETIF_RECV_SKB // modify by aic
+ local_bh_disable();
+ netif_receive_skb(rx_skb);
+ local_bh_enable();
+#else
+ if (in_interrupt()) {
+ netif_rx(rx_skb);
+ } else {
+ /*
+ * If the receive is not processed inside an ISR, the softirqd must be
+ * woken explicitly to service the NET_RX_SOFTIRQ.
+ * * In 2.6 kernels, this is handledby netif_rx_ni(), but in earlier
+ * kernels, we need to do it manually.
+ */
+ netif_rx_ni(rx_skb);
+ }
+#endif
+ REG_SW_CLEAR_PROFILING(rwnx_hw, SW_PROF_IEEE80211RX);
+
+ rwnx_hw->stats.last_rx = jiffies;
+}
+
+bool rwnx_rx_data_skb(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+ struct sk_buff *skb, struct hw_rxhdr *rxhdr)
+{
+ struct sk_buff_head list;
+ struct sk_buff *rx_skb;
+ bool amsdu = rxhdr->flags_is_amsdu;
+ u8 flags_dst_idx = rxhdr->flags_dst_idx;
+ bool resend = false, forward = true;
+
+ skb->dev = rwnx_vif->ndev;
+
+ __skb_queue_head_init(&list);
+
+ if (amsdu) {
+ rwnx_rxdata_process_amsdu
+ (rwnx_hw, skb, rxhdr->flags_vif_idx,
+ &list); // rxhdr not used below since skb free!
+ } else {
+ rwnx_hw->stats.amsdus_rx[0]++;
+ __skb_queue_head(&list, skb);
+ }
+
+ if ((RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_AP ||
+ RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_AP_VLAN ||
+ RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_GO) &&
+ !(rwnx_vif->ap.flags & RWNX_AP_ISOLATE)) {
+ const struct ethhdr *eth;
+
+ rx_skb = skb_peek(&list);
+ skb_reset_mac_header(rx_skb);
+ eth = eth_hdr(rx_skb);
+
+ if (unlikely(is_multicast_ether_addr(eth->h_dest))) {
+ /*
+ * broadcast pkt need to be forward to upper layer and resent
+ * on wireless interface
+ */
+ resend = true;
+ } else {
+ /*
+ * unicast pkt for STA inside the BSS, no need to forward to upper
+ * layer simply resend on wireless interface
+ */
+ if (flags_dst_idx != RWNX_INVALID_STA) {
+ struct rwnx_sta *sta = &rwnx_hw->sta_table[flags_dst_idx];
+
+ if (sta->valid && sta->vlan_idx == rwnx_vif->vif_index) {
+ forward = false;
+ resend = true;
+ }
+ }
+ }
+ } else if (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_MESH_POINT) {
+ const struct ethhdr *eth;
+
+ rx_skb = skb_peek(&list);
+ skb_reset_mac_header(rx_skb);
+ eth = eth_hdr(rx_skb);
+
+ if (!is_multicast_ether_addr(eth->h_dest)) {
+ /*
+ * unicast pkt for STA inside the BSS, no need to forward to upper
+ * layer simply resend on wireless interface
+ */
+ if (flags_dst_idx != RWNX_INVALID_STA) {
+ forward = false;
+ resend = true;
+ }
+ }
+ }
+
+ while (!skb_queue_empty(&list)) {
+ rx_skb = __skb_dequeue(&list);
+
+ /* resend pkt on wireless interface */
+ if (resend) {
+ struct sk_buff *skb_copy;
+ /*
+ * always need to copy buffer when forward=0 to get enough headrom
+ * for tsdesc
+ */
+ skb_copy = skb_copy_expand(rx_skb,
+ sizeof(struct rwnx_txhdr) +
+ RWNX_SWTXHDR_ALIGN_SZ + 3 + 24 + 8,
+ 0, GFP_ATOMIC);
+
+ if (skb_copy) {
+ int res;
+
+ skb_copy->protocol = htons(ETH_P_802_3);
+ skb_reset_network_header(skb_copy);
+ skb_reset_mac_header(skb_copy);
+
+ rwnx_vif->is_resending = true;
+ res = dev_queue_xmit(skb_copy);
+ rwnx_vif->is_resending = false;
+ /* note: buffer is always consummed by dev_queue_xmit */
+ if (res == NET_XMIT_DROP) {
+ rwnx_vif->net_stats.rx_dropped++;
+ rwnx_vif->net_stats.tx_dropped++;
+ } else if (res != NET_XMIT_SUCCESS) {
+ netdev_err(rwnx_vif->ndev,
+ "Failed to re-send buffer to driver (res=%d)",
+ res);
+ rwnx_vif->net_stats.tx_errors++;
+ }
+ } else {
+ netdev_err(rwnx_vif->ndev, "Failed to copy skb");
+ }
+ }
+
+ /* forward pkt to upper layer */
+ if (forward) {
+ /* Update statistics */
+ rwnx_vif->net_stats.rx_packets++;
+ rwnx_vif->net_stats.rx_bytes += rx_skb->len;
+
+ rx_skb->protocol = eth_type_trans(rx_skb, rwnx_vif->ndev);
+#ifdef AICWF_ARP_OFFLOAD
+ if (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION ||
+ RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_CLIENT)
+ arpoffload_proc(rx_skb, rwnx_vif);
+#endif
+ memset(rx_skb->cb, 0, sizeof(rx_skb->cb));
+ REG_SW_SET_PROFILING(rwnx_hw, SW_PROF_IEEE80211RX);
+
+#ifdef CONFIG_AIC8800_FILTER_TCP_ACK
+ filter_rx_tcp_ack(rwnx_hw, rx_skb->data, rx_skb->len);
+#endif
+
+#ifdef CONFIG_AIC8800_RX_NETIF_RECV_SKB // modify by aic
+ local_bh_disable();
+ netif_receive_skb(rx_skb);
+ local_bh_enable();
+#else
+ if (in_interrupt()) {
+ netif_rx(rx_skb);
+ } else {
+ /*
+ * If the receive is not processed inside an ISR, the softirqd
+ * must be woken explicitly to service the NET_RX_SOFTIRQ.
+ * * In 2.6 kernels, this is handledby netif_rx_ni(), but in
+ * earlier kernels, we need to do it manually.
+ */
+ netif_rx_ni(rx_skb);
+ }
+#endif
+ REG_SW_CLEAR_PROFILING(rwnx_hw, SW_PROF_IEEE80211RX);
+
+ rwnx_hw->stats.last_rx = jiffies;
+ }
+ }
+
+ return forward;
+}
+
+/*
+ * rwnx_rx_mgmt - Process one 802.11 management frame
+ *
+ * @rwnx_hw: main driver data
+ * @rwnx_vif: vif to upload the buffer to
+ * @skb: skb received
+ * @rxhdr: HW rx descriptor
+ *
+ * Forward the management frame to a given interface.
+ */
+static void rwnx_rx_mgmt(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+ struct sk_buff *skb, struct hw_rxhdr *hw_rxhdr)
+{
+ struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+ struct rx_vector_1 *rxvect = &hw_rxhdr->hwvect.rx_vect1;
+ if (ieee80211_is_mgmt(mgmt->frame_control) &&
+ (skb->len <= 24 || skb->len > 768)) {
+ AICWFDBG(LOGDEBUG, "mgmt err\n");
+ return;
+ }
+ if (ieee80211_is_beacon(mgmt->frame_control)) {
+ if ((RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_MESH_POINT) &&
+ hw_rxhdr->flags_new_peer) {
+ /* TODO: the value of parameter sig_dbm need to be confirmed */
+ cfg80211_notify_new_peer_candidate
+ (rwnx_vif->ndev, mgmt->sa, mgmt->u.beacon.variable,
+ skb->len - offsetof(struct ieee80211_mgmt, u.beacon.variable),
+ rxvect->rssi1, GFP_ATOMIC);
+ } else {
+ cfg80211_report_obss_beacon(rwnx_hw->wiphy, skb->data, skb->len,
+ hw_rxhdr->phy_info.phy_prim20_freq,
+ rxvect->rssi1);
+ }
+ } else if ((ieee80211_is_deauth(mgmt->frame_control) ||
+ ieee80211_is_disassoc(mgmt->frame_control)) &&
+ (le16_to_cpu(mgmt->u.deauth.reason_code) ==
+ WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA ||
+ le16_to_cpu(mgmt->u.deauth.reason_code) ==
+ WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA)) {
+ cfg80211_rx_unprot_mlme_mgmt(rwnx_vif->ndev, skb->data, skb->len);
+ } else if ((RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION) &&
+ (ieee80211_is_action(mgmt->frame_control) &&
+ (mgmt->u.action.category == 6))) {
+ struct cfg80211_ft_event_params ft_event;
+
+ ft_event.target_ap = (uint8_t *)&mgmt->u.action + ETH_ALEN + 2;
+ ft_event.ies = (uint8_t *)&mgmt->u.action + ETH_ALEN * 2 + 2;
+ ft_event.ies_len = skb->len - (ft_event.ies - (uint8_t *)mgmt);
+ ft_event.ric_ies = NULL;
+ ft_event.ric_ies_len = 0;
+ cfg80211_ft_event(rwnx_vif->ndev, &ft_event);
+ } else {
+ cfg80211_rx_mgmt(&rwnx_vif->wdev, hw_rxhdr->phy_info.phy_prim20_freq,
+ rxvect->rssi1, skb->data, skb->len, 0);
+ }
+}
+
+/*
+ * rwnx_rx_mgmt_any - Process one 802.11 management frame
+ *
+ * @rwnx_hw: main driver data
+ * @skb: skb received
+ * @rxhdr: HW rx descriptor
+ *
+ * Process the management frame and free the corresponding skb.
+ * If vif is not specified in the rx descriptor, the frame is uploaded
+ * on all active vifs.
+ */
+static void rwnx_rx_mgmt_any(struct rwnx_hw *rwnx_hw, struct sk_buff *skb,
+ struct hw_rxhdr *hw_rxhdr)
+{
+ struct rwnx_vif *rwnx_vif;
+ int vif_idx = hw_rxhdr->flags_vif_idx;
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_mgmt_rx(hw_rxhdr->phy_info.phy_prim20_freq, vif_idx,
+ hw_rxhdr->flags_sta_idx, (struct ieee80211_mgmt *)skb->data);
+#endif
+ if (vif_idx == RWNX_INVALID_VIF) {
+ list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
+ if (!rwnx_vif->up)
+ continue;
+ rwnx_rx_mgmt(rwnx_hw, rwnx_vif, skb, hw_rxhdr);
+ }
+ } else {
+ rwnx_vif = rwnx_rx_get_vif(rwnx_hw, vif_idx);
+ if (rwnx_vif)
+ rwnx_rx_mgmt(rwnx_hw, rwnx_vif, skb, hw_rxhdr);
+ }
+
+ dev_kfree_skb(skb);
+}
+
+/**
+ * rwnx_rx_rtap_hdrlen - Return radiotap header length
+ *
+ * @rxvect: Rx vector used to fill the radiotap header
+ * @has_vend_rtap: boolean indicating if vendor specific data is present
+ *
+ * Compute the length of the radiotap header based on @rxvect and vendor
+ * specific data (if any).
+ *
+ * Return: Required radiotap header length in bytes.
+ */
+static u8 rwnx_rx_rtap_hdrlen(struct rx_vector_1 *rxvect, bool has_vend_rtap)
+{
+ u8 rtap_len;
+
+ /* Compute radiotap header length */
+ rtap_len = sizeof(struct ieee80211_radiotap_header) + 8;
+
+ // Check for multiple antennas
+ if (hweight32(rxvect->antenna_set) > 1)
+ // antenna and antenna signal fields
+ rtap_len += 4 * hweight8(rxvect->antenna_set);
+
+ // TSFT
+ if (!has_vend_rtap) {
+ rtap_len = ALIGN(rtap_len, 8);
+ rtap_len += 8;
+ }
+
+ // IEEE80211_HW_SIGNAL_DBM
+ rtap_len++;
+
+ // Check if single antenna
+ if (hweight32(rxvect->antenna_set) == 1)
+ rtap_len++; // Single antenna
+
+ // padding for RX FLAGS
+ rtap_len = ALIGN(rtap_len, 2);
+
+ // Check for HT frames
+ if (rxvect->format_mod == FORMATMOD_HT_MF ||
+ rxvect->format_mod == FORMATMOD_HT_GF)
+ rtap_len += 3;
+
+ // Check for AMPDU
+ if (!(has_vend_rtap) &&
+ (rxvect->format_mod >= FORMATMOD_VHT ||
+ (rxvect->format_mod > FORMATMOD_NON_HT_DUP_OFDM &&
+ rxvect->ht.aggregation))) {
+ rtap_len = ALIGN(rtap_len, 4);
+ rtap_len += 8;
+ }
+
+ // Check for VHT frames
+ if (rxvect->format_mod == FORMATMOD_VHT) {
+ rtap_len = ALIGN(rtap_len, 2);
+ rtap_len += 12;
+ }
+
+ // Check for HE frames
+ if (rxvect->format_mod == FORMATMOD_HE_SU) {
+ rtap_len = ALIGN(rtap_len, 2);
+ rtap_len += sizeof(struct ieee80211_radiotap_he);
+ }
+
+ // Check for multiple antennas
+ if (hweight32(rxvect->antenna_set) > 1) {
+ // antenna and antenna signal fields
+ rtap_len += 2 * hweight8(rxvect->antenna_set);
+ }
+
+ // Check for vendor specific data
+ if (has_vend_rtap) {
+ /* vendor presence bitmap */
+ rtap_len += 4;
+ /* alignment for fixed 6-byte vendor data header */
+ rtap_len = ALIGN(rtap_len, 2);
+ }
+
+ return rtap_len;
+}
+
+/**
+ * rwnx_rx_add_rtap_hdr - Add radiotap header to sk_buff
+ *
+ * @rwnx_hw: main driver data
+ * @skb: skb received (will include the radiotap header)
+ * @rxvect: Rx vector
+ * @phy_info: Information regarding the phy
+ * @hwvect: HW Info (NULL if vendor specific data is available)
+ * @rtap_len: Length of the radiotap header
+ * @vend_rtap_len: radiotap vendor length (0 if not present)
+ * @vend_it_present: radiotap vendor present
+ *
+ * Builds a radiotap header and add it to @skb.
+ */
+static void rwnx_rx_add_rtap_hdr(struct rwnx_hw *rwnx_hw, struct sk_buff *skb,
+ struct rx_vector_1 *rxvect,
+ struct phy_channel_info_desc *phy_info,
+ struct hw_vect *hwvect, int rtap_len,
+ u8 vend_rtap_len, u32 vend_it_present)
+{
+ struct ieee80211_radiotap_header *rtap;
+ u8 *pos;
+ int rate_idx;
+ __le32 *it_present;
+ u32 it_present_val = 0;
+ bool fec_coding = false;
+ bool short_gi = false;
+ bool stbc = false;
+ bool aggregation = false;
+
+ rtap = (struct ieee80211_radiotap_header *)skb_push(skb, rtap_len);
+ memset((u8 *)rtap, 0, rtap_len);
+
+ rtap->it_version = 0;
+ rtap->it_pad = 0;
+ rtap->it_len = cpu_to_le16(rtap_len + vend_rtap_len);
+
+ it_present = &rtap->it_present;
+
+ // Check for multiple antennas
+ if (hweight32(rxvect->antenna_set) > 1) {
+ int chain;
+ unsigned long chains = rxvect->antenna_set;
+
+ for_each_set_bit(chain, &chains, IEEE80211_MAX_CHAINS) {
+ it_present_val |= BIT(IEEE80211_RADIOTAP_EXT) |
+ BIT(IEEE80211_RADIOTAP_RADIOTAP_NAMESPACE);
+ put_unaligned_le32(it_present_val, it_present);
+ it_present++;
+ it_present_val = BIT(IEEE80211_RADIOTAP_ANTENNA) |
+ BIT(IEEE80211_RADIOTAP_DBM_ANTSIGNAL);
+ }
+ }
+
+ // Check if vendor specific data is present
+ if (vend_rtap_len) {
+ it_present_val |= BIT(IEEE80211_RADIOTAP_VENDOR_NAMESPACE) |
+ BIT(IEEE80211_RADIOTAP_EXT);
+ put_unaligned_le32(it_present_val, it_present);
+ it_present++;
+ it_present_val = vend_it_present;
+ }
+
+ put_unaligned_le32(it_present_val, it_present);
+ pos = (void *)(it_present + 1);
+
+ // IEEE80211_RADIOTAP_TSFT
+ if (hwvect) {
+ rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_TSFT);
+ // padding
+ while ((pos - (u8 *)rtap) & 7)
+ *pos++ = 0;
+ put_unaligned_le64((((u64)le32_to_cpu(hwvect->tsf_hi) << 32) +
+ (u64)le32_to_cpu(hwvect->tsf_lo)),
+ pos);
+ pos += 8;
+ }
+
+ // IEEE80211_RADIOTAP_FLAGS
+ rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_FLAGS);
+ if (hwvect && !hwvect->frm_successful_rx)
+ *pos |= IEEE80211_RADIOTAP_F_BADFCS;
+ if (!rxvect->pre_type && rxvect->format_mod <= FORMATMOD_NON_HT_DUP_OFDM)
+ *pos |= IEEE80211_RADIOTAP_F_SHORTPRE;
+ pos++;
+
+ // IEEE80211_RADIOTAP_RATE
+ // check for HT, VHT or HE frames
+ if (rxvect->format_mod >= FORMATMOD_HE_SU) {
+ rate_idx = rxvect->he.mcs;
+ fec_coding = rxvect->he.fec;
+ stbc = rxvect->he.stbc;
+ aggregation = true;
+ *pos = 0;
+ } else if (rxvect->format_mod == FORMATMOD_VHT) {
+ rate_idx = rxvect->vht.mcs;
+ fec_coding = rxvect->vht.fec;
+ short_gi = rxvect->vht.short_gi;
+ stbc = rxvect->vht.stbc;
+ aggregation = true;
+ *pos = 0;
+ } else if (rxvect->format_mod > FORMATMOD_NON_HT_DUP_OFDM) {
+ rate_idx = rxvect->ht.mcs;
+ fec_coding = rxvect->ht.fec;
+ short_gi = rxvect->ht.short_gi;
+ stbc = rxvect->ht.stbc;
+ aggregation = rxvect->ht.aggregation;
+ *pos = 0;
+ } else {
+ struct ieee80211_supported_band *band =
+ rwnx_hw->wiphy->bands[phy_info->phy_band];
+ rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_RATE);
+ rate_idx = legrates_lut[rxvect->leg_rate].idx;
+ if (phy_info->phy_band == NL80211_BAND_5GHZ)
+ rate_idx -= 4;
+ if (WARN_ON_ONCE(rate_idx < 0 || rate_idx >= band->n_bitrates))
+ *pos = 0;
+ else
+ *pos = DIV_ROUND_UP(band->bitrates[rate_idx].bitrate, 5);
+ }
+ pos++;
+
+ // IEEE80211_RADIOTAP_CHANNEL
+ rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_CHANNEL);
+ put_unaligned_le16(phy_info->phy_prim20_freq, pos);
+ pos += 2;
+
+ if (phy_info->phy_band == NL80211_BAND_5GHZ)
+ put_unaligned_le16(IEEE80211_CHAN_OFDM | IEEE80211_CHAN_5GHZ, pos);
+ else if (rxvect->format_mod > FORMATMOD_NON_HT_DUP_OFDM)
+ put_unaligned_le16(IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ, pos);
+ else
+ put_unaligned_le16(IEEE80211_CHAN_CCK | IEEE80211_CHAN_2GHZ, pos);
+ pos += 2;
+
+ if (hweight32(rxvect->antenna_set) == 1) {
+ // IEEE80211_RADIOTAP_DBM_ANTSIGNAL
+ rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL);
+ *pos++ = rxvect->rssi1;
+
+ // IEEE80211_RADIOTAP_ANTENNA
+ rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_ANTENNA);
+ *pos++ = rxvect->antenna_set;
+ }
+
+ // IEEE80211_RADIOTAP_RX_FLAGS
+ rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_RX_FLAGS);
+ // 2 byte alignment
+ if ((pos - (u8 *)rtap) & 1)
+ *pos++ = 0;
+ put_unaligned_le16(0, pos);
+ // Right now, we only support fcs error (no RX_FLAG_FAILED_PLCP_CRC)
+ pos += 2;
+
+ // Check if HT
+ if (rxvect->format_mod == FORMATMOD_HT_MF ||
+ rxvect->format_mod == FORMATMOD_HT_GF) {
+ rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_MCS);
+ *pos++ = IEEE80211_RADIOTAP_MCS_HAVE_MCS |
+ IEEE80211_RADIOTAP_MCS_HAVE_GI |
+ IEEE80211_RADIOTAP_MCS_HAVE_BW;
+ *pos = 0;
+ if (short_gi)
+ *pos |= IEEE80211_RADIOTAP_MCS_SGI;
+ if (rxvect->ch_bw == PHY_CHNL_BW_40)
+ *pos |= IEEE80211_RADIOTAP_MCS_BW_40;
+ if (rxvect->format_mod == FORMATMOD_HT_GF)
+ *pos |= IEEE80211_RADIOTAP_MCS_FMT_GF;
+ if (fec_coding)
+ *pos |= IEEE80211_RADIOTAP_MCS_FEC_LDPC;
+ *pos++ |= stbc << IEEE80211_RADIOTAP_MCS_STBC_SHIFT;
+ *pos++ = rate_idx;
+ }
+
+ // check for HT or VHT frames
+ if (aggregation && hwvect) {
+ // 4 byte alignment
+ while ((pos - (u8 *)rtap) & 3)
+ pos++;
+ rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_AMPDU_STATUS);
+ put_unaligned_le32(hwvect->ampdu_cnt, pos);
+ pos += 4;
+ put_unaligned_le32(0, pos);
+ pos += 4;
+ }
+
+ // Check for VHT frames
+ if (rxvect->format_mod == FORMATMOD_VHT) {
+ u16 vht_details = IEEE80211_RADIOTAP_VHT_KNOWN_GI |
+ IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH;
+ u8 vht_nss = rxvect->vht.nss + 1;
+
+ rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_VHT);
+
+ if (rxvect->ch_bw == PHY_CHNL_BW_160 && phy_info->phy_center2_freq)
+ vht_details &= ~IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH;
+ put_unaligned_le16(vht_details, pos);
+ pos += 2;
+
+ // flags
+ if (short_gi)
+ *pos |= IEEE80211_RADIOTAP_VHT_FLAG_SGI;
+ if (stbc)
+ *pos |= IEEE80211_RADIOTAP_VHT_FLAG_STBC;
+ pos++;
+
+ // bandwidth
+ if (rxvect->ch_bw == PHY_CHNL_BW_40)
+ *pos++ = 1;
+ if (rxvect->ch_bw == PHY_CHNL_BW_80)
+ *pos++ = 4;
+ else if ((rxvect->ch_bw == PHY_CHNL_BW_160) &&
+ phy_info->phy_center2_freq)
+ *pos++ = 0; // 80P80
+ else if (rxvect->ch_bw == PHY_CHNL_BW_160)
+ *pos++ = 11;
+ else // 20 MHz
+ *pos++ = 0;
+
+ // MCS/NSS
+ *pos = (rate_idx << 4) | vht_nss;
+ pos += 4;
+ if (fec_coding)
+ *pos |= IEEE80211_RADIOTAP_CODING_LDPC_USER0;
+ pos++;
+ // group ID
+ pos++;
+ // partial_aid
+ pos += 2;
+ }
+
+ // Check for HE frames
+ if (rxvect->format_mod == FORMATMOD_HE_SU) {
+ struct ieee80211_radiotap_he he;
+#define HE_PREP(f, val) cpu_to_le16(FIELD_PREP(IEEE80211_RADIOTAP_HE_##f, val))
+#define D1_KNOWN(f) cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_##f##_KNOWN)
+#define D2_KNOWN(f) cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_##f##_KNOWN)
+
+ he.data1 = D1_KNOWN(DATA_MCS) | D1_KNOWN(BSS_COLOR) |
+ D1_KNOWN(BEAM_CHANGE) | D1_KNOWN(UL_DL) | D1_KNOWN(CODING) |
+ D1_KNOWN(STBC) | D1_KNOWN(BW_RU_ALLOC) | D1_KNOWN(DOPPLER) |
+ D1_KNOWN(DATA_DCM);
+ he.data2 = D2_KNOWN(GI) | D2_KNOWN(TXBF);
+
+ if (stbc) {
+ he.data6 |= HE_PREP(DATA6_NSTS, 2);
+ he.data3 |= HE_PREP(DATA3_STBC, 1);
+ } else {
+ he.data6 |= HE_PREP(DATA6_NSTS, rxvect->he.nss);
+ }
+
+ he.data3 |= HE_PREP(DATA3_BSS_COLOR, rxvect->he.bss_color);
+ he.data3 |= HE_PREP(DATA3_BEAM_CHANGE, rxvect->he.beam_change);
+ he.data3 |= HE_PREP(DATA3_UL_DL, rxvect->he.uplink_flag);
+ he.data3 |= HE_PREP(DATA3_BSS_COLOR, rxvect->he.bss_color);
+ he.data3 |= HE_PREP(DATA3_DATA_MCS, rxvect->he.mcs);
+ he.data3 |= HE_PREP(DATA3_DATA_DCM, rxvect->he.dcm);
+ he.data3 |= HE_PREP(DATA3_CODING, rxvect->he.fec);
+
+ he.data5 |= HE_PREP(DATA5_GI, rxvect->he.gi_type);
+ he.data5 |= HE_PREP(DATA5_TXBF, rxvect->he.beamformed);
+ he.data5 |= HE_PREP(DATA5_LTF_SIZE, rxvect->he.he_ltf_type + 1);
+
+ switch (rxvect->ch_bw) {
+ case PHY_CHNL_BW_20:
+ he.data5 |=
+ HE_PREP(DATA5_DATA_BW_RU_ALLOC,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_20MHZ);
+ break;
+ case PHY_CHNL_BW_40:
+ he.data5 |=
+ HE_PREP(DATA5_DATA_BW_RU_ALLOC,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_40MHZ);
+ break;
+ case PHY_CHNL_BW_80:
+ he.data5 |=
+ HE_PREP(DATA5_DATA_BW_RU_ALLOC,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_80MHZ);
+ break;
+ case PHY_CHNL_BW_160:
+ he.data5 |=
+ HE_PREP(DATA5_DATA_BW_RU_ALLOC,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_160MHZ);
+ break;
+ default:
+ WARN_ONCE(1, "Invalid SU BW %d\n", rxvect->ch_bw);
+ }
+
+ he.data6 |= HE_PREP(DATA6_DOPPLER, rxvect->he.doppler);
+
+ /* ensure 2 byte alignment */
+ while ((pos - (u8 *)rtap) & 1)
+ pos++;
+ rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_HE);
+ if ((pos + sizeof(he)) <= (u8 *)rtap + rtap_len) {
+ u8 *src = (u8 *)&he;
+ u8 *dst = pos;
+
+ for (size_t i = 0; i < sizeof(he); i++)
+ dst[i] = src[i];
+ pos += sizeof(he);
+ } else {
+ AICWFDBG(LOGERROR, "Not enough space for HE radiotap data\n");
+ return;
+ }
+ //memcpy(pos, &he, sizeof(he));
+ //pos += sizeof(he);
+ }
+
+ // Rx Chains
+ if (hweight32(rxvect->antenna_set) > 1) {
+ int chain;
+ unsigned long chains = rxvect->antenna_set;
+ u8 rssis[4] = {rxvect->rssi1, rxvect->rssi1, rxvect->rssi1,
+ rxvect->rssi1};
+
+ for_each_set_bit(chain, &chains, IEEE80211_MAX_CHAINS) {
+ *pos++ = rssis[chain];
+ *pos++ = chain;
+ }
+ }
+}
+
+/**
+ * rwnx_rx_monitor - Add a radiotap header and deliver a monitor frame
+ *
+ * @rwnx_hw: main driver data
+ * @rwnx_vif: vif that received the buffer
+ * @skb: sk_buff received
+ * @hw_rxhdr_ptr: Pointer to HW RX header
+ * @rtap_len: Radiotap Header length
+ *
+ * Add a radiotap header to the received socket buffer and send it to netdev.
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+static int rwnx_rx_monitor(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+ struct sk_buff *skb, struct hw_rxhdr *hw_rxhdr_ptr,
+ u8 rtap_len)
+{
+ skb->dev = rwnx_vif->ndev;
+
+ if (rwnx_vif->wdev.iftype != NL80211_IFTYPE_MONITOR) {
+ netdev_err(rwnx_vif->ndev, "not a monitor vif\n");
+ return -1;
+ }
+
+ /* Add RadioTap Header */
+ if (skb_headroom(skb) < rtap_len) {
+ if (pskb_expand_head(skb, rtap_len, 0, GFP_ATOMIC)) {
+ AICWFDBG(LOGERROR, "Failed to expand skb headroom for radiotap header\n");
+ return -ENOMEM;
+ }
+ rwnx_rx_add_rtap_hdr(rwnx_hw, skb, &hw_rxhdr_ptr->hwvect.rx_vect1,
+ &hw_rxhdr_ptr->phy_info, &hw_rxhdr_ptr->hwvect,
+ rtap_len, 0, 0);
+ } else {
+ rwnx_rx_add_rtap_hdr(rwnx_hw, skb, &hw_rxhdr_ptr->hwvect.rx_vect1,
+ &hw_rxhdr_ptr->phy_info, &hw_rxhdr_ptr->hwvect,
+ rtap_len, 0, 0);
+ }
+
+ skb_reset_mac_header(skb);
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ skb->pkt_type = PACKET_OTHERHOST;
+ skb->protocol = htons(ETH_P_802_2);
+
+#ifdef CONFIG_AIC8800_FILTER_TCP_ACK
+ filter_rx_tcp_ack(rwnx_hw, skb->data, skb->len);
+#endif
+
+ local_bh_disable();
+ netif_receive_skb(skb);
+ local_bh_enable();
+
+ return 0;
+}
+
+#ifdef AICWF_ARP_OFFLOAD
+
+static void handle_dhcp_ack(u8 *option, u16 offset, struct dhcpinfo *dhcph,
+ struct rwnx_vif *rwnx_vif)
+{
+ if (option[offset] == DHCP_OPTION_MESSAGE_TYPE &&
+ option[offset + 2] == DHCP_ACK) {
+ AICWFDBG(LOGINFO, "paired=%x, should=%x\n",
+ rwnx_vif->sta.paired_cipher_type,
+ WLAN_CIPHER_SUITE_CCMP);
+
+ if (rwnx_vif->sta.paired_cipher_type == WLAN_CIPHER_SUITE_CCMP ||
+ rwnx_vif->sta.paired_cipher_type == WLAN_CIPHER_SUITE_AES_CMAC ||
+ (rwnx_vif->sta.group_cipher_type == 0xff &&
+ rwnx_vif->sta.paired_cipher_type == 0xff)) {
+ rwnx_send_arpoffload_en_req(rwnx_vif->rwnx_hw, rwnx_vif, dhcph->yiaddr, 1);
+ } else {
+ rwnx_send_arpoffload_en_req(rwnx_vif->rwnx_hw, rwnx_vif, dhcph->yiaddr, 0);
+ }
+ }
+}
+
+void arpoffload_proc(struct sk_buff *skb, struct rwnx_vif *rwnx_vif)
+{
+ struct iphdr *iphead = (struct iphdr *)(skb->data);
+ struct udphdr *udph;
+ struct dhcpinfo *dhcph;
+
+ if (skb->protocol == htons(ETH_P_IP) && iphead->protocol == IPPROTO_UDP) { // IP && UDP
+ udph = (struct udphdr *)((u8 *)iphead + (iphead->ihl << 2));
+ if (udph->source == htons(SERVER_PORT) &&
+ udph->dest == htons(CLIENT_PORT)) { // DHCP offset/ack
+ dhcph = (struct dhcpinfo *)((u8 *)udph + sizeof(struct udphdr));
+ if (dhcph->cookie == htonl(DHCP_MAGIC) && dhcph->op == 2 &&
+ !memcmp(dhcph->chaddr, rwnx_vif->ndev->dev_addr, 6)) {
+ // match magic word
+ u32 length = ntohs(udph->len) - sizeof(struct udphdr) -
+ offsetof(struct dhcpinfo, options);
+ u16 offset = 0;
+ u8 *option = dhcph->options;
+
+ while (option[offset] != DHCP_OPTION_END && offset < length) {
+ handle_dhcp_ack(option, offset, dhcph, rwnx_vif);
+ offset += 2 + option[offset + 1];
+ }
+ }
+ }
+ }
+}
+#endif
+
+#ifdef AICWF_RX_REORDER
+void reord_rxframe_free(spinlock_t *lock, struct list_head *q,
+ struct list_head *list)
+{
+ spin_lock_bh(lock);
+ list_add(list, q);
+ spin_unlock_bh(lock);
+}
+
+struct recv_msdu *reord_rxframe_alloc(spinlock_t *lock, struct list_head *q)
+{
+ struct recv_msdu *rxframe;
+
+ spin_lock_bh(lock);
+ if (list_empty(q)) {
+ spin_unlock_bh(lock);
+ return NULL;
+ }
+ rxframe = list_entry(q->next, struct recv_msdu, rxframe_list);
+ list_del_init(q->next);
+ spin_unlock_bh(lock);
+ return rxframe;
+}
+
+struct reord_ctrl_info *reord_init_sta(struct aicwf_rx_priv *rx_priv,
+ const u8 *mac_addr)
+{
+ u8 i = 0;
+ struct reord_ctrl *preorder_ctrl = NULL;
+ struct reord_ctrl_info *reord_info;
+#ifdef AICWF_SDIO_SUPPORT
+ struct aicwf_bus *bus_if = rx_priv->sdiodev->bus_if;
+#else
+ struct aicwf_bus *bus_if = rx_priv->usbdev->bus_if;
+#endif
+
+ if (bus_if->state == BUS_DOWN_ST || !rx_priv) {
+ AICWFDBG(LOGERROR, "bad stat!\n");
+ return NULL;
+ }
+
+ AICWFDBG(LOGINFO, "%s:%pM\n", __func__, mac_addr);
+ reord_info = kmalloc_obj(*reord_info, GFP_ATOMIC);
+ if (!reord_info)
+ return NULL;
+
+ memcpy(reord_info->mac_addr, mac_addr, ETH_ALEN);
+ for (i = 0; i < 8; i++) {
+ preorder_ctrl = &reord_info->preorder_ctrl[i];
+ preorder_ctrl->enable = true;
+ preorder_ctrl->ind_sn = 0xffff;
+ preorder_ctrl->wsize_b = AICWF_REORDER_WINSIZE;
+ preorder_ctrl->rx_priv = rx_priv;
+ INIT_LIST_HEAD(&preorder_ctrl->reord_list);
+ spin_lock_init(&preorder_ctrl->reord_list_lock);
+ timer_setup(&preorder_ctrl->reord_timer, reord_timeout_handler, 0);
+ INIT_WORK(&preorder_ctrl->reord_timer_work, reord_timeout_worker);
+ }
+
+ return reord_info;
+}
+
+int reord_flush_tid(struct aicwf_rx_priv *rx_priv, struct sk_buff *skb, u8 tid)
+{
+ struct reord_ctrl_info *reord_info;
+ struct reord_ctrl *preorder_ctrl;
+ struct rwnx_vif *rwnx_vif = (struct rwnx_vif *)rx_priv->rwnx_vif;
+ struct ethhdr *eh = (struct ethhdr *)(skb->data);
+ u8 *mac;
+ unsigned long flags;
+ u8 found = 0;
+ struct list_head *phead, *plist;
+ struct recv_msdu *prframe;
+
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_STATION ||
+ rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) {
+ mac = eh->h_dest;
+ } else if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_AP ||
+ rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_GO) {
+ mac = eh->h_source;
+ } else {
+ AICWFDBG(LOGERROR, "error mode:%d!\n", rwnx_vif->wdev.iftype);
+ dev_kfree_skb(skb);
+ return -1;
+ }
+
+ spin_lock_bh(&rx_priv->stas_reord_lock);
+ list_for_each_entry(reord_info, &rx_priv->stas_reord_list, list) {
+ if (!memcmp(mac, reord_info->mac_addr, ETH_ALEN)) {
+ found = 1;
+ preorder_ctrl = &reord_info->preorder_ctrl[tid];
+ break;
+ }
+ }
+ if (!found) {
+ spin_unlock_bh(&rx_priv->stas_reord_lock);
+ return 0;
+ }
+ spin_unlock_bh(&rx_priv->stas_reord_lock);
+
+ if (!preorder_ctrl->enable)
+ return 0;
+ spin_lock_irqsave(&preorder_ctrl->reord_list_lock, flags);
+ phead = &preorder_ctrl->reord_list;
+ while (1) {
+ if (list_empty(phead))
+ break;
+ plist = phead->next;
+ prframe = list_entry(plist, struct recv_msdu, reord_pending_list);
+ reord_single_frame_ind(rx_priv, prframe);
+ list_del_init(&prframe->reord_pending_list);
+ }
+
+ AICWFDBG(LOGINFO, "flush:tid=%d", tid);
+ preorder_ctrl->enable = false;
+ spin_unlock_irqrestore(&preorder_ctrl->reord_list_lock, flags);
+ if (timer_pending(&preorder_ctrl->reord_timer))
+ timer_delete_sync(&preorder_ctrl->reord_timer);
+ cancel_work_sync(&preorder_ctrl->reord_timer_work);
+
+ return 0;
+}
+
+void reord_deinit_sta(struct aicwf_rx_priv *rx_priv,
+ struct reord_ctrl_info *reord_info)
+{
+ u8 i = 0;
+ unsigned long flags;
+ struct reord_ctrl *preorder_ctrl = NULL;
+
+ if (!rx_priv) {
+ txrx_err("bad rx_priv!\n");
+ return;
+ }
+
+ for (i = 0; i < 8; i++) {
+ struct recv_msdu *req, *next;
+
+ preorder_ctrl = &reord_info->preorder_ctrl[i];
+ spin_lock_irqsave(&preorder_ctrl->reord_list_lock, flags);
+ list_for_each_entry_safe(req, next, &preorder_ctrl->reord_list,
+ reord_pending_list) {
+ list_del_init(&req->reord_pending_list);
+ if (req->pkt)
+ dev_kfree_skb(req->pkt);
+ req->pkt = NULL;
+ reord_rxframe_free(&rx_priv->freeq_lock,
+ &rx_priv->rxframes_freequeue,
+ &req->rxframe_list);
+ }
+ spin_unlock_irqrestore(&preorder_ctrl->reord_list_lock, flags);
+ if (timer_pending(&preorder_ctrl->reord_timer))
+ timer_delete_sync(&preorder_ctrl->reord_timer);
+ cancel_work_sync(&preorder_ctrl->reord_timer_work);
+ }
+ list_del(&reord_info->list);
+ kfree(reord_info);
+}
+
+int reord_single_frame_ind(struct aicwf_rx_priv *rx_priv,
+ struct recv_msdu *prframe)
+{
+ struct list_head *rxframes_freequeue = NULL;
+ struct sk_buff *skb = NULL;
+ struct rwnx_vif *rwnx_vif = (struct rwnx_vif *)rx_priv->rwnx_vif;
+ struct sk_buff_head list;
+ struct sk_buff *rx_skb;
+
+ rxframes_freequeue = &rx_priv->rxframes_freequeue;
+ skb = prframe->pkt;
+
+ if (!skb) {
+ txrx_err("skb is NULL\n");
+ return -1;
+ }
+
+ if (!prframe->forward) {
+ dev_kfree_skb(skb);
+ prframe->pkt = NULL;
+ reord_rxframe_free(&rx_priv->freeq_lock, rxframes_freequeue,
+ &prframe->rxframe_list);
+ return 0;
+ }
+
+ __skb_queue_head_init(&list);
+ if (prframe->is_amsdu) {
+ rwnx_rxdata_process_amsdu
+ (rwnx_vif->rwnx_hw, skb, rwnx_vif->vif_index,
+ &list); // rxhdr not used below since skb free!
+ } else {
+ __skb_queue_head(&list, skb);
+ }
+
+ while (!skb_queue_empty(&list)) {
+ rx_skb = __skb_dequeue(&list);
+
+ rwnx_vif->net_stats.rx_packets++;
+ rwnx_vif->net_stats.rx_bytes += rx_skb->len;
+
+ rx_skb->dev = rwnx_vif->ndev;
+ rx_skb->protocol = eth_type_trans(rx_skb, rwnx_vif->ndev);
+
+#ifdef AICWF_ARP_OFFLOAD
+ if (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION ||
+ RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_CLIENT) {
+ arpoffload_proc(rx_skb, rwnx_vif);
+ }
+#endif
+ memset(rx_skb->cb, 0, sizeof(rx_skb->cb));
+
+#ifdef CONFIG_AIC8800_FILTER_TCP_ACK
+ filter_rx_tcp_ack(rwnx_vif->rwnx_hw, rx_skb->data, rx_skb->len);
+#endif
+
+#ifdef CONFIG_AIC8800_RX_NETIF_RECV_SKB // AIDEN test
+ local_bh_disable();
+ netif_receive_skb(rx_skb);
+ local_bh_enable();
+#else
+ if (in_interrupt()) {
+ netif_rx(rx_skb);
+ } else {
+ /*
+ * If the receive is not processed inside an ISR, the softirqd must
+ * be woken explicitly to service the NET_RX_SOFTIRQ.
+ * * In 2.6 kernels, this is handledby netif_rx_ni(), but in earlier
+ * kernels, we need to do it manually.
+ */
+ netif_rx_ni(rx_skb);
+ }
+#endif /* CONFIG_AIC8800_RX_NETIF_RECV_SKB */
+ }
+
+ prframe->pkt = NULL;
+ reord_rxframe_free(&rx_priv->freeq_lock, rxframes_freequeue,
+ &prframe->rxframe_list);
+
+ return 0;
+}
+
+bool reord_rxframes_process(struct aicwf_rx_priv *rx_priv,
+ struct reord_ctrl *preorder_ctrl, int bforced)
+{
+ struct list_head *phead, *plist;
+ struct recv_msdu *prframe;
+ bool b_pkt_in_buf = false;
+
+ if (bforced) {
+ phead = &preorder_ctrl->reord_list;
+ if (list_empty(phead))
+ return false;
+
+ plist = phead->next;
+ prframe = list_entry(plist, struct recv_msdu, reord_pending_list);
+ preorder_ctrl->ind_sn = prframe->seq_num;
+ }
+
+ phead = &preorder_ctrl->reord_list;
+ if (list_empty(phead))
+ return b_pkt_in_buf;
+
+ list_for_each_entry(prframe, phead, reord_pending_list) {
+ if (!SN_LESS(preorder_ctrl->ind_sn, prframe->seq_num)) {
+ if (SN_EQUAL(preorder_ctrl->ind_sn, prframe->seq_num))
+ preorder_ctrl->ind_sn = (preorder_ctrl->ind_sn + 1) & 0xFFF;
+ } else {
+ b_pkt_in_buf = true;
+ break;
+ }
+ }
+
+ return b_pkt_in_buf;
+}
+
+void reord_rxframes_ind(struct aicwf_rx_priv *rx_priv,
+ struct reord_ctrl *preorder_ctrl)
+{
+ struct list_head *phead, *plist;
+ struct recv_msdu *prframe;
+
+ phead = &preorder_ctrl->reord_list;
+ while (1) {
+ if (list_empty(phead))
+ break;
+
+ plist = phead->next;
+ prframe = list_entry(plist, struct recv_msdu, reord_pending_list);
+
+ if (!SN_LESS(preorder_ctrl->ind_sn, prframe->seq_num)) {
+ list_del_init(&prframe->reord_pending_list);
+ reord_single_frame_ind(rx_priv, prframe);
+ } else {
+ break;
+ }
+ }
+}
+
+static int reorder_timeout = REORDER_UPDATE_TIME;
+module_param(reorder_timeout, int, 0660);
+
+void reord_timeout_handler(struct timer_list *t)
+{
+ //struct reord_ctrl *preorder_ctrl = from_timer(preorder_ctrl, t, reord_timer);
+ struct reord_ctrl *preorder_ctrl = timer_container_of(preorder_ctrl, t, reord_timer);
+
+ if (!work_pending(&preorder_ctrl->reord_timer_work))
+ schedule_work(&preorder_ctrl->reord_timer_work);
+}
+
+void reord_timeout_worker(struct work_struct *work)
+{
+ struct reord_ctrl *preorder_ctrl =
+ container_of(work, struct reord_ctrl, reord_timer_work);
+ struct aicwf_rx_priv *rx_priv = preorder_ctrl->rx_priv;
+
+ spin_lock_bh(&preorder_ctrl->reord_list_lock);
+ if (reord_rxframes_process(rx_priv, preorder_ctrl, true) == true) {
+ mod_timer(&preorder_ctrl->reord_timer,
+ jiffies + msecs_to_jiffies
+ (reorder_timeout /*REORDER_UPDATE_TIME */));
+ }
+
+ reord_rxframes_ind(rx_priv, preorder_ctrl);
+ spin_unlock_bh(&preorder_ctrl->reord_list_lock);
+}
+
+int reord_process_unit(struct aicwf_rx_priv *rx_priv, struct sk_buff *skb,
+ u16 seq_num, u8 tid, u8 forward, u8 is_amsdu)
+{
+ int ret = 0;
+ u8 *mac;
+ struct recv_msdu *pframe;
+ struct reord_ctrl *preorder_ctrl;
+ struct reord_ctrl_info *reord_info;
+ struct rwnx_vif *rwnx_vif = (struct rwnx_vif *)rx_priv->rwnx_vif;
+ struct ethhdr *eh = (struct ethhdr *)(skb->data);
+ u8 *da = eh->h_dest;
+ u8 is_mcast = ((*da) & 0x01) ? 1 : 0;
+
+ if (!rwnx_vif || skb->len <= 14) {
+ dev_kfree_skb(skb);
+ return -1;
+ }
+
+ pframe =
+ reord_rxframe_alloc(&rx_priv->freeq_lock, &rx_priv->rxframes_freequeue);
+ if (!pframe) {
+ dev_kfree_skb(skb);
+ return -1;
+ }
+
+ INIT_LIST_HEAD(&pframe->reord_pending_list);
+ pframe->seq_num = seq_num;
+ pframe->tid = tid;
+ pframe->rx_data = skb->data;
+ pframe->pkt = skb;
+ pframe->forward = forward;
+ preorder_ctrl = pframe->preorder_ctrl;
+ pframe->is_amsdu = is_amsdu;
+
+ if ((ntohs(eh->h_proto) == ETH_P_PAE) || is_mcast)
+ return reord_single_frame_ind(rx_priv, pframe);
+
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_STATION ||
+ rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) {
+ mac = eh->h_dest;
+ } else if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_AP ||
+ rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_GO) {
+ mac = eh->h_source;
+ } else {
+ dev_kfree_skb(skb);
+ return -1;
+ }
+
+ spin_lock_bh(&rx_priv->stas_reord_lock);
+ list_for_each_entry(reord_info, &rx_priv->stas_reord_list, list) {
+ if (!memcmp(mac, reord_info->mac_addr, ETH_ALEN)) {
+ preorder_ctrl = &reord_info->preorder_ctrl[pframe->tid];
+ break;
+ }
+ }
+
+ if (&reord_info->list == &rx_priv->stas_reord_list) {
+ reord_info = reord_init_sta(rx_priv, mac);
+ if (!reord_info) {
+ spin_unlock_bh(&rx_priv->stas_reord_lock);
+ dev_kfree_skb(skb);
+ return -1;
+ }
+ list_add_tail(&reord_info->list, &rx_priv->stas_reord_list);
+ preorder_ctrl = &reord_info->preorder_ctrl[pframe->tid];
+ } else {
+ if (!preorder_ctrl->enable) {
+ preorder_ctrl->enable = true;
+ preorder_ctrl->ind_sn = 0xffff;
+ preorder_ctrl->wsize_b = AICWF_REORDER_WINSIZE;
+ preorder_ctrl->rx_priv = rx_priv;
+ }
+ }
+ spin_unlock_bh(&rx_priv->stas_reord_lock);
+
+ if (!preorder_ctrl->enable) {
+ spin_lock_bh(&preorder_ctrl->reord_list_lock);
+ preorder_ctrl->ind_sn = pframe->seq_num;
+ reord_single_frame_ind(rx_priv, pframe);
+ preorder_ctrl->ind_sn = (preorder_ctrl->ind_sn + 1) % 4096;
+ spin_unlock_bh(&rx_priv->stas_reord_lock);
+ return 0;
+ }
+
+ spin_lock_bh(&preorder_ctrl->reord_list_lock);
+ if (reord_need_check(preorder_ctrl, pframe->seq_num)) {
+ if (pframe->rx_data[42] == 0x80) { // this is rtp package
+ if (pframe->seq_num == preorder_ctrl->ind_sn) {
+ AICWFDBG(LOGDEBUG, "%s pframe->seq_num1:%d \r\n", __func__,
+ pframe->seq_num);
+ reord_single_frame_ind(rx_priv, pframe); // not need to reorder
+ } else {
+ AICWFDBG(LOGDEBUG, "%s free pframe->seq_num:%d \r\n", __func__,
+ pframe->seq_num);
+ if (pframe->pkt) {
+ dev_kfree_skb(pframe->pkt);
+ pframe->pkt = NULL;
+ }
+ reord_rxframe_free(&rx_priv->freeq_lock,
+ &rx_priv->rxframes_freequeue,
+ &pframe->rxframe_list);
+ }
+ } else {
+ reord_single_frame_ind(rx_priv, pframe); // not need to reorder
+ }
+ spin_unlock_bh(&preorder_ctrl->reord_list_lock);
+ return 0;
+ }
+
+ if (reord_rxframe_enqueue(preorder_ctrl, pframe)) {
+ spin_unlock_bh(&preorder_ctrl->reord_list_lock);
+ goto fail;
+ }
+
+ if (reord_rxframes_process(rx_priv, preorder_ctrl, false) == true) {
+ if (!timer_pending(&preorder_ctrl->reord_timer)) {
+ ret = mod_timer(&preorder_ctrl->reord_timer,
+ jiffies + msecs_to_jiffies(reorder_timeout
+ /* REORDER_UPDATE_TIME */));
+ }
+ } else {
+ if (timer_pending(&preorder_ctrl->reord_timer))
+ //ret = del_timer(&preorder_ctrl->reord_timer);
+ ret = timer_delete(&preorder_ctrl->reord_timer);
+ }
+
+ reord_rxframes_ind(rx_priv, preorder_ctrl);
+ spin_unlock_bh(&preorder_ctrl->reord_list_lock);
+
+ return 0;
+
+fail:
+ if (pframe->pkt) {
+ dev_kfree_skb(pframe->pkt);
+ pframe->pkt = NULL;
+ }
+ reord_rxframe_free(&rx_priv->freeq_lock, &rx_priv->rxframes_freequeue,
+ &pframe->rxframe_list);
+ return ret;
+}
+
+int reord_need_check(struct reord_ctrl *preorder_ctrl, u16 seq_num)
+{
+ u8 wsize = preorder_ctrl->wsize_b;
+ u16 wend = (preorder_ctrl->ind_sn + wsize - 1) & 0xFFF;
+
+ if (preorder_ctrl->ind_sn == 0xFFFF)
+ preorder_ctrl->ind_sn = seq_num;
+
+ if (SN_LESS(seq_num, preorder_ctrl->ind_sn))
+ return -1;
+
+ if (SN_EQUAL(seq_num, preorder_ctrl->ind_sn)) {
+ preorder_ctrl->ind_sn = (preorder_ctrl->ind_sn + 1) & 0xFFF;
+ } else if (SN_LESS(wend, seq_num)) {
+ if (seq_num >= (wsize - 1))
+ preorder_ctrl->ind_sn = seq_num - (wsize - 1);
+ else
+ preorder_ctrl->ind_sn = 0xFFF - (wsize - (seq_num + 1)) + 1;
+ }
+
+ return 0;
+}
+
+int reord_rxframe_enqueue(struct reord_ctrl *preorder_ctrl,
+ struct recv_msdu *prframe)
+{
+ struct list_head *preord_list = &preorder_ctrl->reord_list;
+ struct list_head *phead, *plist;
+ struct recv_msdu *pnextrframe;
+
+ phead = preord_list;
+ plist = phead->next;
+
+ while (phead != plist) {
+ pnextrframe = list_entry(plist, struct recv_msdu, reord_pending_list);
+ if (SN_LESS(pnextrframe->seq_num, prframe->seq_num)) {
+ plist = plist->next;
+ continue;
+ } else if (SN_EQUAL(pnextrframe->seq_num, prframe->seq_num)) {
+ return -1;
+ }
+ break;
+ }
+ list_add_tail(&prframe->reord_pending_list, plist);
+
+ return 0;
+}
+#endif /* AICWF_RX_REORDER */
+
+void remove_sec_hdr_mgmt_frame(struct hw_rxhdr *hw_rxhdr, struct sk_buff *skb)
+{
+ u8 hdr_len = 24;
+ u8 mgmt_header[24] = {0};
+
+ if (!hw_rxhdr->hwvect.ga_frame) {
+ if (((skb->data[0] & 0x0C) == 0) &&
+ (skb->data[1] & 0x40) == 0x40) { // protect management frame
+ AICWFDBG(LOGDEBUG, "frame type %x\n", skb->data[0]);
+ if (hw_rxhdr->hwvect.decr_status == RWNX_RX_HD_DECR_CCMP128) {
+ memcpy(mgmt_header, skb->data, hdr_len);
+ skb_pull(skb, 8);
+ memcpy(skb->data, mgmt_header, hdr_len);
+ hw_rxhdr->hwvect.len -= 8;
+ } else {
+ AICWFDBG(LOGDEBUG, "unsupport decr_status:%d\n",
+ hw_rxhdr->hwvect.decr_status);
+ }
+ }
+ }
+}
+
+void defrag_timeout_cb(struct timer_list *t)
+{
+ struct defrag_ctrl_info *defrag_ctrl = NULL;
+
+ //defrag_ctrl = from_timer(defrag_ctrl, t, defrag_timer);
+ defrag_ctrl = timer_container_of(defrag_ctrl, t, defrag_timer);
+
+ AICWFDBG(LOGDEBUG, "%s:%p\r\n", __func__, defrag_ctrl);
+ spin_lock_bh(&defrag_ctrl->rwnx_hw->defrag_lock);
+ list_del_init(&defrag_ctrl->list);
+ dev_kfree_skb(defrag_ctrl->skb);
+ kfree(defrag_ctrl);
+ spin_unlock_bh(&defrag_ctrl->rwnx_hw->defrag_lock);
+}
+
+void rwnx_rxdata_process_amsdu(struct rwnx_hw *rwnx_hw, struct sk_buff *skb,
+ u8 vif_idx, struct sk_buff_head *list)
+{
+ u16 len_alligned = 0;
+ u16 sublen = 0;
+ struct sk_buff *sub_skb = NULL;
+ struct rwnx_vif *rwnx_vif;
+
+ /* |amsdu sub1 | amsdu sub2 | ... */
+ len_alligned = 0;
+ sublen = 0;
+ sub_skb = NULL;
+ while (skb->len > 16) {
+ sublen = (skb->data[12] << 8) | (skb->data[13]);
+ if (skb->len > (sublen + 14)) {
+ len_alligned = roundup(sublen + 14, 4);
+ } else if (skb->len == (sublen + 14)) {
+ len_alligned = sublen + 14;
+ } else {
+ AICWFDBG(LOGDEBUG, "accroding to amsdu: this will not happen\n");
+ break;
+ }
+ /* debug trace */
+ // AICWFDBG(LOGDEBUG, "sublen = %d, %x, %x, %x, %x\r\n",
+ // sublen,skb->data[0], skb->data[1], skb->data[12],
+ // skb->data[13]);
+ sub_skb = __dev_alloc_skb(sublen - 6 + 12, GFP_ATOMIC);
+ if (!sub_skb) {
+ AICWFDBG(LOGDEBUG, "sub_skb alloc fail:%d\n", sublen);
+ break;
+ }
+ skb_put(sub_skb, sublen - 6 + 12);
+ memcpy(sub_skb->data, skb->data, MAC_ADDR_LEN);
+ memcpy(&sub_skb->data[6], &skb->data[6], MAC_ADDR_LEN);
+ memcpy(&sub_skb->data[12], &skb->data[14 + 6], sublen - 6);
+
+ rwnx_vif = rwnx_rx_get_vif(rwnx_hw, vif_idx);
+ if (!rwnx_vif) {
+ AICWFDBG(LOGDEBUG, "Frame received but no active vif (%d)", vif_idx);
+ // dev_kfree_skb(sub_skb);
+ break;
+ }
+
+ __skb_queue_tail(list, sub_skb);
+
+ /* debug trace */
+ // AICWFDBG(LOGDEBUG, "a:%p\n", sub_skb);
+ // if (!rwnx_rx_data_skb(rwnx_hw, rwnx_vif, sub_skb, hw_rxhdr))
+ // dev_kfree_skb(sub_skb);
+ skb_pull(skb, len_alligned);
+ }
+ dev_kfree_skb(skb);
+}
+
+static void find_defrag_info(struct rwnx_hw *rwnx_hw, u16 seq_num,
+ u8 tid, u8 sta_idx,
+ struct defrag_ctrl_info **defrag_info,
+ struct defrag_ctrl_info **defrag_info_tmp)
+{
+ struct defrag_ctrl_info *iter;
+
+ if (!list_empty(&rwnx_hw->defrag_list)) {
+ list_for_each_entry(iter, &rwnx_hw->defrag_list, list) {
+ if (iter->sn == seq_num &&
+ iter->tid == tid &&
+ iter->sta_idx == sta_idx) {
+ *defrag_info_tmp = iter;
+ *defrag_info = *defrag_info_tmp;
+ break;
+ }
+ }
+ }
+}
+
+static int process_fragment_reassembly(struct defrag_ctrl_info *defrag_info,
+ u16 frag_num, struct sk_buff *skb,
+ u8 pull_len, struct rwnx_hw *rwnx_hw,
+ u8 *is_frag)
+{
+ if (!defrag_info)
+ return 1;
+
+ *is_frag = 1;
+ if (defrag_info->next_fn != frag_num) {
+ AICWFDBG(LOGDEBUG, "discard:%d:%d\n",
+ defrag_info->next_fn, frag_num);
+ dev_kfree_skb(skb);
+ spin_unlock_bh(&rwnx_hw->defrag_lock);
+ return 0;
+ }
+
+ skb_put(defrag_info->skb, skb->len - (pull_len - 8));
+ memcpy(&defrag_info->skb->data[defrag_info->frm_len],
+ &skb->data[pull_len - 8],
+ skb->len - (pull_len - 8));
+
+ defrag_info->frm_len += skb->len - (pull_len - 8);
+ defrag_info->next_fn++;
+
+ dev_kfree_skb(skb);
+ spin_unlock_bh(&rwnx_hw->defrag_lock);
+
+ return 0;
+}
+
+static inline void find_matching_defrag_info(struct rwnx_hw *rwnx_hw,
+ u16 seq_num, u8 tid, u8 sta_idx,
+ struct defrag_ctrl_info **defrag_info,
+ struct defrag_ctrl_info **defrag_info_tmp)
+{
+ struct defrag_ctrl_info *iter;
+
+ list_for_each_entry(iter, &rwnx_hw->defrag_list, list) {
+ if (iter->sn == seq_num &&
+ iter->tid == tid &&
+ iter->sta_idx == sta_idx) {
+ *defrag_info_tmp = iter;
+ *defrag_info = *defrag_info_tmp;
+ break;
+ }
+ }
+}
+
+static int process_defrag_reassembly(struct rwnx_hw *rwnx_hw,
+ struct defrag_ctrl_info *defrag_info,
+ u16 frag_num,
+ struct sk_buff *skb,
+ u8 pull_len,
+ struct hw_rxhdr *hw_rxhdr,
+ u8 *is_frag,
+ struct rwnx_vif **rwnx_vif,
+ struct sk_buff **skb_tmp)
+{
+ if (!defrag_info) {
+ spin_unlock_bh(&rwnx_hw->defrag_lock);
+ return 1;
+ }
+
+ if (defrag_info->next_fn != frag_num) {
+ AICWFDBG(LOGDEBUG, "discard:%d:%d\n",
+ defrag_info->next_fn, frag_num);
+ dev_kfree_skb(skb);
+ spin_unlock_bh(&rwnx_hw->defrag_lock);
+ return 0;
+ }
+
+ skb_put(defrag_info->skb, skb->len - (pull_len - 8));
+ memcpy(&defrag_info->skb->data[defrag_info->frm_len],
+ &skb->data[pull_len - 8],
+ skb->len - (pull_len - 8));
+ defrag_info->frm_len += skb->len - (pull_len - 8);
+ *is_frag = 1;
+
+ *rwnx_vif = rwnx_rx_get_vif(rwnx_hw, hw_rxhdr->flags_vif_idx);
+ if (!*rwnx_vif) {
+ AICWFDBG(LOGDEBUG, "Frame received but no active vif (%d)",
+ hw_rxhdr->flags_vif_idx);
+ dev_kfree_skb(skb);
+ spin_unlock_bh(&rwnx_hw->defrag_lock);
+ return 0;
+ }
+
+ dev_kfree_skb(skb);
+ *skb_tmp = defrag_info->skb;
+ list_del_init(&defrag_info->list);
+ if (timer_pending(&defrag_info->defrag_timer))
+ //del_timer(&defrag_info->defrag_timer);
+ timer_delete(&defrag_info->defrag_timer);
+ kfree(defrag_info);
+ spin_unlock_bh(&rwnx_hw->defrag_lock);
+
+ if (!rwnx_rx_data_skb(rwnx_hw, *rwnx_vif, *skb_tmp, hw_rxhdr))
+ dev_kfree_skb(*skb_tmp);
+
+ return 0;
+}
+
+static inline void check_packet_forward_flags(struct rwnx_hw *rwnx_hw,
+ struct hw_rxhdr *hw_rxhdr,
+ struct rwnx_vif *rwnx_vif,
+ bool *resend,
+ bool *forward)
+{
+ if (hw_rxhdr->flags_dst_idx != RWNX_INVALID_STA) {
+ struct rwnx_sta *sta = &rwnx_hw->sta_table[hw_rxhdr->flags_dst_idx];
+
+ if (sta->valid && sta->vlan_idx == rwnx_vif->vif_index) {
+ *resend = true;
+ *forward = false;
+ }
+ }
+}
+
+u8 rwnx_rxdataind_aicwf(struct rwnx_hw *rwnx_hw, void *hostid, void *rx_priv)
+{
+ struct hw_rxhdr *hw_rxhdr;
+ struct rxdesc_tag *rxdesc = NULL;
+ struct rwnx_vif *rwnx_vif;
+ struct sk_buff *skb = hostid;
+ int msdu_offset = sizeof(struct hw_rxhdr) + 2;
+ u16 status = 0;
+ struct aicwf_rx_priv *rx_priv_tmp;
+ u8 hdr_len = 24;
+ u8 ra[MAC_ADDR_LEN] = {0};
+ u8 ta[MAC_ADDR_LEN] = {0};
+ u8 ether_type[2] = {0};
+ u8 pull_len = 0;
+ u16 seq_num = 0;
+ u8 frag_num = 0;
+ u8 tid = 0;
+ u8 is_qos = 0;
+ u8 is_frag = 0;
+ struct defrag_ctrl_info *defrag_info = NULL;
+ struct defrag_ctrl_info *defrag_info_tmp = NULL;
+ struct sk_buff *skb_tmp = NULL;
+ u8 sta_idx = 0;
+ u16 frame_ctrl;
+ u8 is_amsdu = 0;
+ bool resend = false, forward = true;
+ const struct ethhdr *eth;
+
+ hw_rxhdr = (struct hw_rxhdr *)skb->data;
+
+ if (hw_rxhdr->is_monitor_vif) {
+ status = RX_STAT_MONITOR;
+ AICWFDBG(LOGDEBUG, "monitor rx\n");
+ }
+
+ if (hw_rxhdr->flags_upload)
+ status |= RX_STAT_FORWARD;
+
+ /* Check if we need to delete the buffer */
+ if (status & RX_STAT_DELETE) {
+ /* Remove the SK buffer from the rxbuf_elems table */
+ /* Free the buffer */
+ dev_kfree_skb(skb);
+ goto end;
+ }
+
+ /* Check if we need to forward the buffer coming from a monitor interface */
+ if (status & RX_STAT_MONITOR) {
+ struct sk_buff *skb_monitor;
+ struct hw_rxhdr hw_rxhdr_copy;
+ u8 rtap_len;
+ u16 frm_len;
+
+ // Check if monitor interface exists and is open
+ rwnx_vif = rwnx_rx_get_vif(rwnx_hw, rwnx_hw->monitor_vif);
+ if (!rwnx_vif) {
+ dev_err(rwnx_hw->dev,
+ "Received monitor frame but there is no monitor interface open\n");
+ goto check_len_update;
+ }
+
+ rwnx_rx_vector_convert(rwnx_hw, &hw_rxhdr->hwvect.rx_vect1,
+ &hw_rxhdr->hwvect.rx_vect2);
+ rtap_len = rwnx_rx_rtap_hdrlen(&hw_rxhdr->hwvect.rx_vect1, false);
+
+ // Move skb->data pointer to MAC Header or Ethernet header
+ skb->data += (msdu_offset + 2); // sdio/usb word align
+
+ // Save frame length
+ frm_len = hw_rxhdr->hwvect.len;
+
+ // Reserve space for frame
+ skb->len = frm_len;
+
+ if (status == RX_STAT_MONITOR) {
+ /*
+ * Remove the SK buffer from the rxbuf_elems table. It will also
+ * unmap the buffer and then sync the buffer for the cpu
+ */
+
+ // Check if there is enough space to add the radiotap header
+ if (skb_headroom(skb) > rtap_len) {
+ skb_monitor = skb;
+
+ // Duplicate the HW Rx Header to override with the radiotap
+ // header
+ memcpy(&hw_rxhdr_copy, hw_rxhdr, sizeof(hw_rxhdr_copy));
+
+ hw_rxhdr = &hw_rxhdr_copy;
+
+ skb_reset_tail_pointer(skb);
+ skb->len = 0;
+ skb_reset_tail_pointer(skb_monitor);
+ skb_monitor->len = 0;
+ skb_put(skb_monitor, frm_len);
+ if (rwnx_rx_monitor(rwnx_hw, rwnx_vif,
+ skb_monitor, hw_rxhdr, rtap_len))
+ dev_kfree_skb(skb_monitor);
+ } else {
+ // Duplicate the skb and extend the headroom
+ skb_monitor = skb_copy_expand(skb, rtap_len, 0, GFP_ATOMIC);
+
+ // Reset original skb->data pointer
+ skb->data = (void *)hw_rxhdr;
+
+ skb_reset_tail_pointer(skb);
+ skb->len = 0;
+ skb_reset_tail_pointer(skb_monitor);
+ skb_monitor->len = 0;
+ skb_put(skb_monitor, frm_len);
+ if (rwnx_rx_monitor(rwnx_hw, rwnx_vif,
+ skb_monitor, hw_rxhdr, rtap_len))
+ dev_kfree_skb(skb_monitor);
+ }
+ } else {
+ skb->data = (void *)hw_rxhdr;
+
+ wiphy_err(rwnx_hw->wiphy,
+ "RX status %d is invalid when MON_DATA is disabled\n",
+ status);
+ goto check_len_update;
+ }
+
+ if (status == RX_STAT_MONITOR) {
+ status |= RX_STAT_ALLOC;
+ if (skb_monitor != skb)
+ dev_kfree_skb(skb);
+ }
+ }
+
+check_len_update:
+ /* Check if we need to update the length */
+ if (status & RX_STAT_LEN_UPDATE) {
+ if (rxdesc)
+ hw_rxhdr->hwvect.len = rxdesc->frame_len;
+
+ if (status & RX_STAT_ETH_LEN_UPDATE) {
+ /* Update Length Field inside the Ethernet Header */
+ struct ethhdr *hdr =
+ (struct ethhdr *)((u8 *)hw_rxhdr + msdu_offset);
+
+ if (rxdesc)
+ hdr->h_proto = htons(rxdesc->frame_len - sizeof(struct ethhdr));
+ }
+
+ goto end;
+ }
+
+ /* Check if it must be discarded after informing upper layer */
+ if (status & RX_STAT_SPURIOUS) {
+ struct ieee80211_hdr *hdr;
+
+ hdr = (struct ieee80211_hdr *)(skb->data + msdu_offset);
+ rwnx_vif = rwnx_rx_get_vif(rwnx_hw, hw_rxhdr->flags_vif_idx);
+ if (rwnx_vif) {
+ cfg80211_rx_spurious_frame(rwnx_vif->ndev, hdr->addr2, 0,
+ GFP_ATOMIC);
+ }
+ goto end;
+ }
+
+ /* Check if we need to forward the buffer */
+ if (status & RX_STAT_FORWARD) {
+ rwnx_rx_vector_convert(rwnx_hw, &hw_rxhdr->hwvect.rx_vect1,
+ &hw_rxhdr->hwvect.rx_vect2);
+ skb_pull(skb, msdu_offset + 2); //+2 since sdio align 58->60
+
+#define MAC_FCTRL_MOREFRAG 0x0400
+ frame_ctrl = (skb->data[1] << 8) | skb->data[0];
+ seq_num = ((skb->data[22] & 0xf0) >> 4) | (skb->data[23] << 4);
+ frag_num = (skb->data[22] & 0x0f);
+ is_amsdu = 0;
+
+ if ((skb->data[0] & 0x0f) == 0x08) {
+ if ((skb->data[0] & 0x80) == 0x80) { // qos data
+ hdr_len = 26;
+ tid = skb->data[24] & 0x0F;
+ is_qos = 1;
+ if (skb->data[24] & 0x80)
+ is_amsdu = 1;
+ }
+
+ if (skb->data[1] & 0x80) // htc
+ hdr_len += 4;
+
+ if ((skb->data[1] & 0x3) == 0x1) { // to ds
+ memcpy(ra, &skb->data[16], MAC_ADDR_LEN);
+ memcpy(ta, &skb->data[10], MAC_ADDR_LEN);
+ } else if ((skb->data[1] & 0x3) == 0x2) { // from ds
+ memcpy(ta, &skb->data[16], MAC_ADDR_LEN);
+ memcpy(ra, &skb->data[4], MAC_ADDR_LEN);
+ }
+
+ pull_len += (hdr_len + 8);
+
+ switch (hw_rxhdr->hwvect.decr_status) {
+ case RWNX_RX_HD_DECR_CCMP128:
+ pull_len += 8; // ccmp_header
+ memcpy(ether_type, &skb->data[hdr_len + 6 + 8], 2);
+ break;
+ case RWNX_RX_HD_DECR_TKIP:
+ pull_len += 8; // tkip_header
+ memcpy(ether_type, &skb->data[hdr_len + 6 + 8], 2);
+ break;
+ case RWNX_RX_HD_DECR_WEP:
+ pull_len += 4; // wep_header
+ memcpy(ether_type, &skb->data[hdr_len + 6 + 4], 2);
+ break;
+ case RWNX_RX_HD_DECR_WAPI:
+ pull_len += 18; // wapi_header
+ memcpy(ether_type, &skb->data[hdr_len + 6 + 18], 2);
+ break;
+
+ default:
+ memcpy(ether_type, &skb->data[hdr_len + 6], 2);
+ break;
+ }
+ if (is_amsdu)
+ hw_rxhdr->flags_is_amsdu = 1;
+ else
+ hw_rxhdr->flags_is_amsdu = 0;
+
+ if (is_amsdu)
+ skb_pull(skb, pull_len - 8);
+
+ if (hw_rxhdr->flags_dst_idx != RWNX_INVALID_STA)
+ sta_idx = hw_rxhdr->flags_dst_idx;
+
+ if (!hw_rxhdr->flags_need_reord &&
+ ((frame_ctrl & MAC_FCTRL_MOREFRAG) || frag_num)) {
+ AICWFDBG(LOGDEBUG, "rxfrag:%d,%d,%d,sn=%d,%d\r\n",
+ (frame_ctrl & MAC_FCTRL_MOREFRAG), frag_num, skb->len,
+ seq_num, pull_len);
+ if (frame_ctrl & MAC_FCTRL_MOREFRAG) {
+ spin_lock_bh(&rwnx_hw->defrag_lock);
+ if (!list_empty(&rwnx_hw->defrag_list)) {
+ find_defrag_info(rwnx_hw, seq_num, tid, sta_idx,
+ &defrag_info, &defrag_info_tmp);
+ }
+ /* debug trace */
+ // AICWFDBG(LOGDEBUG,
+ // "rx frag: sn=%d, fn=%d, skb->len=%d\r\n", seq_num,
+ // frag_num, skb->len);
+ if (process_fragment_reassembly(defrag_info, frag_num,
+ skb, pull_len,
+ rwnx_hw, &is_frag) == 0)
+ return 0;
+ defrag_info =
+ kzalloc_obj(struct defrag_ctrl_info, GFP_KERNEL);
+ if (!defrag_info) {
+ AICWFDBG(LOGDEBUG,
+ "no defrag_ctrl_info\r\n");
+ dev_kfree_skb(skb);
+ spin_unlock_bh(&rwnx_hw->defrag_lock);
+ return 0;
+ }
+ defrag_info->skb = __dev_alloc_skb(2000, GFP_KERNEL);
+ if (!defrag_info->skb) {
+ AICWFDBG(LOGDEBUG, "no fragment skb\r\n");
+ dev_kfree_skb(skb);
+ kfree(defrag_info);
+ spin_unlock_bh(&rwnx_hw->defrag_lock);
+ return 0;
+ }
+ is_frag = 1;
+ skb_pull(skb, pull_len);
+ skb_push(skb, 14);
+ memcpy(skb->data, ra, MAC_ADDR_LEN);
+ memcpy(&skb->data[6], ta, MAC_ADDR_LEN);
+ memcpy(&skb->data[12], ether_type, 2);
+ defrag_info->sn = seq_num;
+ defrag_info->next_fn = 1;
+ defrag_info->tid = tid;
+ defrag_info->sta_idx = sta_idx;
+
+ skb_put(defrag_info->skb, skb->len);
+ memcpy(defrag_info->skb->data, skb->data, skb->len);
+ defrag_info->frm_len = skb->len;
+ defrag_info->rwnx_hw = rwnx_hw;
+ /* debug trace */
+ // AICWFDBG(LOGDEBUG,
+ // "first:%p,%p,%p,%p,%p, %d,%d\r\n",defrag_info,
+ // defrag_info->skb, defrag_info->skb->head,
+ // defrag_info->skb->tail, defrag_info->skb->end,
+ // defrag_info->frm_len, skb->len);
+ list_add_tail(&defrag_info->list,
+ &rwnx_hw->defrag_list);
+ spin_unlock_bh(&rwnx_hw->defrag_lock);
+ timer_setup(&defrag_info->defrag_timer,
+ defrag_timeout_cb, 0);
+ mod_timer(&defrag_info->defrag_timer,
+ jiffies +
+ msecs_to_jiffies(DEFRAG_MAX_WAIT));
+ dev_kfree_skb(skb);
+ return 0;
+ }
+ // check whether the last fragment
+ if (!list_empty(&rwnx_hw->defrag_list)) {
+ spin_lock_bh(&rwnx_hw->defrag_lock);
+ find_matching_defrag_info(rwnx_hw, seq_num, tid, sta_idx,
+ &defrag_info, &defrag_info_tmp);
+
+ if (process_defrag_reassembly(rwnx_hw, defrag_info,
+ frag_num, skb,
+ pull_len, hw_rxhdr,
+ &is_frag, &rwnx_vif,
+ &skb_tmp) == 0)
+ return 0;
+ }
+ }
+
+ if (!is_frag && !is_amsdu) {
+ skb_pull(skb, pull_len);
+ skb_push(skb, 14);
+ memcpy(skb->data, ra, MAC_ADDR_LEN);
+ memcpy(&skb->data[6], ta, MAC_ADDR_LEN);
+ memcpy(&skb->data[12], ether_type, 2);
+ }
+ }
+
+ if (hw_rxhdr->flags_is_80211_mpdu) {
+ remove_sec_hdr_mgmt_frame(hw_rxhdr, skb);
+ rwnx_rx_mgmt_any(rwnx_hw, skb, hw_rxhdr);
+ } else {
+ rwnx_vif = rwnx_rx_get_vif(rwnx_hw, hw_rxhdr->flags_vif_idx);
+
+ if (!rwnx_vif) {
+ dev_err(rwnx_hw->dev, "Frame received but no active vif(%d)",
+ hw_rxhdr->flags_vif_idx);
+ dev_kfree_skb(skb);
+ goto end;
+ }
+
+ if (hw_rxhdr->flags_sta_idx != RWNX_INVALID_STA) {
+ struct rwnx_sta *sta;
+
+ sta = &rwnx_hw->sta_table[hw_rxhdr->flags_sta_idx];
+ rwnx_rx_statistic(rwnx_hw, hw_rxhdr, sta);
+
+ if (sta->vlan_idx != rwnx_vif->vif_index) {
+ rwnx_vif = rwnx_hw->vif_table[sta->vlan_idx];
+ if (!rwnx_vif) {
+ dev_kfree_skb(skb);
+ goto end;
+ }
+ }
+
+ if (hw_rxhdr->flags_is_4addr && !rwnx_vif->use_4addr) {
+ cfg80211_rx_unexpected_4addr_frame
+ (rwnx_vif->ndev, sta->mac_addr, 0,
+ GFP_ATOMIC);
+ }
+ }
+
+ skb->priority = 256 + tid; // hw_rxhdr->flags_user_prio;
+
+#ifdef AICWF_RX_REORDER
+ rx_priv_tmp = rx_priv;
+ rx_priv_tmp->rwnx_vif = (void *)rwnx_vif;
+
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_STATION ||
+ rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) {
+ if (is_qos && hw_rxhdr->flags_need_reord)
+ reord_process_unit((struct aicwf_rx_priv *)rx_priv, skb,
+ seq_num, tid, 1,
+ hw_rxhdr->flags_is_amsdu);
+ else if (is_qos && !hw_rxhdr->flags_need_reord) {
+ reord_flush_tid((struct aicwf_rx_priv *)rx_priv, skb, tid);
+ if (!rwnx_rx_data_skb(rwnx_hw, rwnx_vif, skb, hw_rxhdr) &&
+ !hw_rxhdr->flags_is_amsdu)
+ dev_kfree_skb(skb);
+ } else {
+ if (!rwnx_rx_data_skb(rwnx_hw, rwnx_vif, skb, hw_rxhdr) &&
+ !hw_rxhdr->flags_is_amsdu)
+ dev_kfree_skb(skb);
+ }
+ } else if ((rwnx_vif->wdev.iftype == NL80211_IFTYPE_AP) ||
+ (rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_GO)) {
+ skb_reset_mac_header(skb);
+ eth = eth_hdr(skb);
+ /* debug trace */
+ // AICWFDBG(LOGDEBUG,
+ // "da:%pM, %x,%x, len=%d\n", eth->h_dest, skb->data[12],
+ // skb->data[13], skb->len);
+
+ if (unlikely(is_multicast_ether_addr(eth->h_dest))) {
+ /*
+ * broadcast pkt need to be forwarded to upper layer and
+ * resent on wireless interface
+ */
+ resend = true;
+ } else {
+ /*
+ * unicast pkt for STA inside the BSS, no need to forward to
+ * upper layer simply resend on wireless interface
+ */
+ check_packet_forward_flags(rwnx_hw, hw_rxhdr,
+ rwnx_vif, &resend,
+ &forward);
+ }
+
+ if (resend)
+ rwnx_rx_data_skb_resend(rwnx_hw, rwnx_vif, skb, hw_rxhdr);
+
+ if (forward) {
+ if (is_qos && hw_rxhdr->flags_need_reord) {
+ reord_process_unit
+ ((struct aicwf_rx_priv *)rx_priv, skb,
+ seq_num, tid, 1,
+ hw_rxhdr->flags_is_amsdu);
+ } else if (is_qos && !hw_rxhdr->flags_need_reord) {
+ reord_flush_tid
+ ((struct aicwf_rx_priv *)rx_priv, skb,
+ tid);
+ rwnx_rx_data_skb_forward(rwnx_hw, rwnx_vif, skb,
+ hw_rxhdr);
+ } else {
+ rwnx_rx_data_skb_forward(rwnx_hw, rwnx_vif, skb,
+ hw_rxhdr);
+ }
+ } else if (resend) {
+ if (is_qos && hw_rxhdr->flags_need_reord)
+ reord_process_unit
+ ((struct aicwf_rx_priv *)rx_priv, skb,
+ seq_num, tid, 0,
+ hw_rxhdr->flags_is_amsdu);
+ else if (is_qos && !hw_rxhdr->flags_need_reord) {
+ reord_flush_tid
+ ((struct aicwf_rx_priv *)rx_priv, skb,
+ tid);
+ dev_kfree_skb(skb);
+ }
+ } else {
+ dev_kfree_skb(skb);
+ }
+ }
+#else
+ if (!rwnx_rx_data_skb(rwnx_hw, rwnx_vif, skb, hw_rxhdr))
+ dev_kfree_skb(skb);
+#endif
+ }
+ }
+
+end:
+ return 0;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_rx.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_rx.h
new file mode 100644
index 0000000000000..3b1e2a0b210c3
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_rx.h
@@ -0,0 +1,358 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_RX_H_
+#define _RWNX_RX_H_
+
+#include "aicwf_txrxif.h"
+
+#define SERVER_PORT 67
+#define CLIENT_PORT 68
+#define DHCP_MAGIC 0x63825363
+#define DHCP_ACK 5
+#define DHCP_OPTION_MESSAGE_TYPE 53 /* RFC 2132 9.6, important for DHCP */
+#define DHCP_OPTION_END 255
+
+enum rx_status_bits {
+ /// The buffer can be forwarded to the networking stack
+ RX_STAT_FORWARD = 1 << 0,
+ /// A new buffer has to be allocated
+ RX_STAT_ALLOC = 1 << 1,
+ /// The buffer has to be deleted
+ RX_STAT_DELETE = 1 << 2,
+ /// The length of the buffer has to be updated
+ RX_STAT_LEN_UPDATE = 1 << 3,
+ /// The length in the Ethernet header has to be updated
+ RX_STAT_ETH_LEN_UPDATE = 1 << 4,
+ /// Simple copy
+ RX_STAT_COPY = 1 << 5,
+ /// Spurious frame (inform upper layer and discard)
+ RX_STAT_SPURIOUS = 1 << 6,
+ /// packet for monitor interface
+ RX_STAT_MONITOR = 1 << 7,
+};
+
+/*
+ * Decryption status subfields.
+ * {
+ */
+#define RWNX_RX_HD_DECR_UNENC 0 // ENCRYPTION TYPE NONE
+#define RWNX_RX_HD_DECR_WEP 1 // ENCRYPTION TYPE WEP
+#define RWNX_RX_HD_DECR_TKIP 2 // ENCRYPTION TYPE TKIP
+#define RWNX_RX_HD_DECR_CCMP128 3 // ENCRYPTION TYPE CCMP128
+#define RWNX_RX_HD_DECR_CCMP256 4 // ENCRYPTION TYPE CCMP256
+#define RWNX_RX_HD_DECR_GCMP128 5 // ENCRYPTION TYPE GCMP128
+#define RWNX_RX_HD_DECR_GCMP256 6 // ENCRYPTION TYPE GCMP256
+#define RWNX_RX_HD_DECR_WAPI 7 // ENCRYPTION TYPE WAPI
+// @}
+
+struct rx_vector_1_old {
+ /** Receive Vector 1a */
+ u32 leg_length : 12;
+ u32 leg_rate : 4;
+ u32 ht_length : 16;
+
+ /** Receive Vector 1b */
+ u32 _ht_length : 4; // FIXME
+ u32 short_gi : 1;
+ u32 stbc : 2;
+ u32 smoothing : 1;
+ u32 mcs : 7;
+ u32 pre_type : 1;
+ u32 format_mod : 3;
+ u32 ch_bw : 2;
+ u32 n_sts : 3;
+ u32 lsig_valid : 1;
+ u32 sounding : 1;
+ u32 num_extn_ss : 2;
+ u32 aggregation : 1;
+ u32 fec_coding : 1;
+ u32 dyn_bw : 1;
+ u32 doze_not_allowed : 1;
+
+ /** Receive Vector 1c */
+ u32 antenna_set : 8;
+ u32 partial_aid : 9;
+ u32 group_id : 6;
+ u32 first_user : 1;
+ s32 rssi1 : 8;
+
+ /** Receive Vector 1d */
+ s32 rssi2 : 8;
+ s32 rssi3 : 8;
+ s32 rssi4 : 8;
+ u32 reserved_1d : 8;
+};
+
+struct rx_leg_vect {
+ u8 dyn_bw_in_non_ht : 1;
+ u8 chn_bw_in_non_ht : 2;
+ u8 rsvd_nht : 4;
+ u8 lsig_valid : 1;
+} __packed;
+
+struct rx_ht_vect {
+ u16 sounding : 1;
+ u16 smoothing : 1;
+ u16 short_gi : 1;
+ u16 aggregation : 1;
+ u16 stbc : 1;
+ u16 num_extn_ss : 2;
+ u16 lsig_valid : 1;
+ u16 mcs : 7;
+ u16 fec : 1;
+ u16 length : 16;
+} __packed;
+
+struct rx_vht_vect {
+ u8 sounding : 1;
+ u8 beamformed : 1;
+ u8 short_gi : 1;
+ u8 rsvd_vht1 : 1;
+ u8 stbc : 1;
+ u8 doze_not_allowed : 1;
+ u8 first_user : 1;
+ u8 rsvd_vht2 : 1;
+ u16 partial_aid : 9;
+ u16 group_id : 6;
+ u16 rsvd_vht3 : 1;
+ u32 mcs : 4;
+ u32 nss : 3;
+ u32 fec : 1;
+ u32 length : 20;
+ u32 rsvd_vht4 : 4;
+} __packed;
+
+struct rx_he_vect {
+ u8 sounding : 1;
+ u8 beamformed : 1;
+ u8 gi_type : 2;
+ u8 stbc : 1;
+ u8 rsvd_he1 : 3;
+
+ u8 uplink_flag : 1;
+ u8 beam_change : 1;
+ u8 dcm : 1;
+ u8 he_ltf_type : 2;
+ u8 doppler : 1;
+ u8 rsvd_he2 : 2;
+
+ u8 bss_color : 6;
+ u8 rsvd_he3 : 2;
+
+ u8 txop_duration : 7;
+ u8 rsvd_he4 : 1;
+
+ u8 pe_duration : 4;
+ u8 spatial_reuse : 4;
+
+ u8 sig_b_comp_mode : 1;
+ u8 dcm_sig_b : 1;
+ u8 mcs_sig_b : 3;
+ u8 ru_size : 3;
+
+ u32 mcs : 4;
+ u32 nss : 3;
+ u32 fec : 1;
+ u32 length : 20;
+ u32 rsvd_he6 : 4;
+} __packed;
+
+struct rx_vector_1 {
+ u8 format_mod : 4;
+ u8 ch_bw : 3;
+ u8 pre_type : 1;
+ u8 antenna_set : 8;
+ s32 rssi_leg : 8;
+ u32 leg_length : 12;
+ u32 leg_rate : 4;
+ s32 rssi1 : 8;
+
+ union {
+ struct rx_leg_vect leg;
+ struct rx_ht_vect ht;
+ struct rx_vht_vect vht;
+ struct rx_he_vect he;
+ };
+} __packed;
+
+struct rx_vector_2_old {
+ /** Receive Vector 2a */
+ u32 rcpi : 8;
+ u32 evm1 : 8;
+ u32 evm2 : 8;
+ u32 evm3 : 8;
+
+ /** Receive Vector 2b */
+ u32 evm4 : 8;
+ u32 reserved2b_1 : 8;
+ u32 reserved2b_2 : 8;
+ u32 reserved2b_3 : 8;
+};
+
+struct rx_vector_2 {
+ /** Receive Vector 2a */
+ u32 rcpi1 : 8;
+ u32 rcpi2 : 8;
+ u32 rcpi3 : 8;
+ u32 rcpi4 : 8;
+
+ /** Receive Vector 2b */
+ u32 evm1 : 8;
+ u32 evm2 : 8;
+ u32 evm3 : 8;
+ u32 evm4 : 8;
+};
+
+struct phy_channel_info_desc {
+ /** PHY channel information 1 */
+ u32 phy_band : 8;
+ u32 phy_channel_type : 8;
+ u32 phy_prim20_freq : 16;
+ /** PHY channel information 2 */
+ u32 phy_center1_freq : 16;
+ u32 phy_center2_freq : 16;
+};
+
+struct hw_vect {
+ /** Total length for the MPDU transfer */
+ u32 len : 16;
+
+ u32 reserved : 8; // data type is included
+ /** AMPDU Status Information */
+ u32 mpdu_cnt : 6;
+ u32 ampdu_cnt : 2;
+
+ /** TSF Low */
+ __le32 tsf_lo;
+ /** TSF High */
+ __le32 tsf_hi;
+
+ /** Receive Vector 1 */
+ struct rx_vector_1 rx_vect1;
+ /** Receive Vector 2 */
+ struct rx_vector_2 rx_vect2;
+
+ /** Status **/
+ u32 rx_vect2_valid : 1;
+ u32 resp_frame : 1;
+ /** Decryption Status */
+ u32 decr_status : 3;
+ u32 rx_fifo_oflow : 1;
+
+ /** Frame Unsuccessful */
+ u32 undef_err : 1;
+ u32 phy_err : 1;
+ u32 fcs_err : 1;
+ u32 addr_mismatch : 1;
+ u32 ga_frame : 1;
+ u32 current_ac : 2;
+
+ u32 frm_successful_rx : 1;
+ /** Descriptor Done */
+ u32 desc_done_rx : 1;
+ /** Key Storage RAM Index */
+ u32 key_sram_index : 10;
+ /** Key Storage RAM Index Valid */
+ u32 key_sram_v : 1;
+ u32 type : 2;
+ u32 subtype : 4;
+};
+
+struct hw_rxhdr {
+ /** RX vector */
+ struct hw_vect hwvect;
+
+ /** PHY channel information */
+ struct phy_channel_info_desc phy_info;
+
+ /** RX flags */
+ u32 flags_is_amsdu : 1;
+ u32 flags_is_80211_mpdu : 1;
+ u32 flags_is_4addr : 1;
+ u32 flags_new_peer : 1;
+#if defined(AICWF_SDIO_SUPPORT)
+ u32 flags_user_prio : 1; // aic: fw not fill any more
+ u32 flags_need_reord : 1;
+ u32 flags_upload : 1;
+#else
+ u32 flags_user_prio : 3;
+#endif
+#ifndef AICWF_RX_REORDER
+ u32 flags_rsvd0 : 1;
+#else
+ u32 is_monitor_vif : 1;
+#endif
+ u32 flags_vif_idx : 8; // 0xFF if invalid VIF index
+ u32 flags_sta_idx : 8; // 0xFF if invalid STA index
+ u32 flags_dst_idx : 8; // 0xFF if unknown destination STA
+ /** Pattern indicating if the buffer is available for the driver */
+ u32 pattern;
+};
+
+struct rwnx_legrate {
+ int idx;
+ int rate;
+};
+
+extern struct rwnx_legrate legrates_lut[];
+extern u16 tx_legrates_lut_rate[];
+
+struct dhcpinfo {
+ u8 op;
+ u8 htype;
+ u8 hlen;
+ u8 hops;
+ u32 xid;
+ u16 secs;
+ u16 flags;
+ u32 ciaddr;
+ u32 yiaddr;
+ u32 siaddr;
+ u32 giaddr;
+ u8 chaddr[16];
+ u8 sname[64];
+ u8 file[128];
+ __be32 cookie;
+ u8 options[308]; /* 312 - cookie */
+};
+
+bool rwnx_rx_data_skb(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+ struct sk_buff *skb, struct hw_rxhdr *rxhdr);
+u8 rwnx_rxdataind_aicwf(struct rwnx_hw *rwnx_hw, void *hostid, void *rx_priv);
+int aicwf_process_rxframes(struct aicwf_rx_priv *rx_priv);
+
+#ifdef AICWF_ARP_OFFLOAD
+void arpoffload_proc(struct sk_buff *skb, struct rwnx_vif *rwnx_vif);
+#endif
+#ifdef AICWF_RX_REORDER
+struct recv_msdu *reord_rxframe_alloc(spinlock_t *lock, struct list_head *q);
+void reord_rxframe_free(spinlock_t *lock, struct list_head *q,
+ struct list_head *list);
+struct reord_ctrl_info *reord_init_sta(struct aicwf_rx_priv *rx_priv,
+ const u8 *mac_addr);
+void reord_deinit_sta(struct aicwf_rx_priv *rx_priv,
+ struct reord_ctrl_info *reord_info);
+int reord_need_check(struct reord_ctrl *preorder_ctrl, u16 seq_num);
+int reord_rxframe_enqueue(struct reord_ctrl *preorder_ctrl,
+ struct recv_msdu *prframe);
+void reord_timeout_worker(struct work_struct *work);
+int reord_single_frame_ind(struct aicwf_rx_priv *rx_priv,
+ struct recv_msdu *prframe);
+void reord_timeout_handler(struct timer_list *t);
+
+#endif
+void rwnx_rxdata_process_amsdu(struct rwnx_hw *rwnx_hw, struct sk_buff *skb,
+ u8 vif_idx, struct sk_buff_head *list);
+
+void rwnx_rx_data_skb_resend(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+ struct sk_buff *skb, struct hw_rxhdr *rxhdr);
+int reord_flush_tid(struct aicwf_rx_priv *rx_priv, struct sk_buff *skb, u8 tid);
+bool reord_rxframes_process(struct aicwf_rx_priv *rx_priv,
+ struct reord_ctrl *preorder_ctrl, int bforced);
+void reord_rxframes_ind(struct aicwf_rx_priv *rx_priv, struct reord_ctrl *preorder_ctrl);
+int reord_process_unit(struct aicwf_rx_priv *rx_priv, struct sk_buff *skb,
+ u16 seq_num, u8 tid, u8 forward, u8 is_amsdu);
+void remove_sec_hdr_mgmt_frame(struct hw_rxhdr *hw_rxhdr, struct sk_buff *skb);
+
+void defrag_timeout_cb(struct timer_list *t);
+
+#endif /* _RWNX_RX_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tdls.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tdls.c
new file mode 100644
index 0000000000000..a803d5c861eae
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tdls.c
@@ -0,0 +1,773 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_tdls.c
+ *
+ * @brief TDLS function declarations
+ *
+ ******************************************************************************
+ */
+
+#include "rwnx_tdls.h"
+#include "rwnx_compat.h"
+
+static u16 rwnx_get_tdls_sta_capab(struct rwnx_vif *rwnx_vif, u16 status_code)
+{
+ u16 capab = 0;
+
+ /* The capability will be 0 when sending a failure code */
+ if (status_code != 0)
+ return capab;
+
+ if (rwnx_vif->sta.ap->band != NL80211_BAND_2GHZ)
+ return capab;
+
+ capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME;
+ capab |= WLAN_CAPABILITY_SHORT_PREAMBLE;
+
+ return capab;
+}
+
+static int rwnx_tdls_prepare_encap_data(struct rwnx_hw *rwnx_hw,
+ struct rwnx_vif *rwnx_vif,
+ const u8 *peer, u8 action_code,
+ u8 dialog_token, u16 status_code,
+ struct sk_buff *skb)
+{
+ struct ieee80211_tdls_data *tf;
+
+ tf = (void *)skb_put(skb,
+ sizeof(struct ieee80211_tdls_data) - sizeof(tf->u));
+
+ // set eth header
+ memcpy(tf->da, peer, ETH_ALEN);
+ memcpy(tf->sa, rwnx_hw->wiphy->perm_addr, ETH_ALEN);
+ tf->ether_type = cpu_to_be16(ETH_P_TDLS);
+
+ // set common TDLS info
+ tf->payload_type = WLAN_TDLS_SNAP_RFTYPE;
+ tf->category = WLAN_CATEGORY_TDLS;
+ tf->action_code = action_code;
+
+ // set action specific TDLS info
+ switch (action_code) {
+ case WLAN_TDLS_SETUP_REQUEST:
+ skb_put(skb, sizeof(tf->u.setup_req));
+ tf->u.setup_req.dialog_token = dialog_token;
+ tf->u.setup_req.capability =
+ cpu_to_le16(rwnx_get_tdls_sta_capab(rwnx_vif, status_code));
+ break;
+
+ case WLAN_TDLS_SETUP_RESPONSE:
+ skb_put(skb, sizeof(tf->u.setup_resp));
+ tf->u.setup_resp.status_code = cpu_to_le16(status_code);
+ tf->u.setup_resp.dialog_token = dialog_token;
+ tf->u.setup_resp.capability =
+ cpu_to_le16(rwnx_get_tdls_sta_capab(rwnx_vif, status_code));
+ break;
+
+ case WLAN_TDLS_SETUP_CONFIRM:
+ skb_put(skb, sizeof(tf->u.setup_cfm));
+ tf->u.setup_cfm.status_code = cpu_to_le16(status_code);
+ tf->u.setup_cfm.dialog_token = dialog_token;
+ break;
+
+ case WLAN_TDLS_TEARDOWN:
+ skb_put(skb, sizeof(tf->u.teardown));
+ tf->u.teardown.reason_code = cpu_to_le16(status_code);
+ break;
+
+ case WLAN_TDLS_DISCOVERY_REQUEST:
+ skb_put(skb, sizeof(tf->u.discover_req));
+ tf->u.discover_req.dialog_token = dialog_token;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rwnx_prep_tdls_direct(struct rwnx_hw *rwnx_hw,
+ struct rwnx_vif *rwnx_vif, const u8 *peer,
+ u8 action_code, u8 dialog_token,
+ u16 status_code, struct sk_buff *skb)
+{
+ struct ieee80211_mgmt *mgmt;
+
+ mgmt = (void *)skb_put(skb, 24);
+ memset(mgmt, 0, 24);
+ memcpy(mgmt->da, peer, ETH_ALEN);
+ memcpy(mgmt->sa, rwnx_hw->wiphy->perm_addr, ETH_ALEN);
+ memcpy(mgmt->bssid, rwnx_vif->sta.ap->mac_addr, ETH_ALEN);
+
+ mgmt->frame_control =
+ cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ACTION);
+
+ switch (action_code) {
+ case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
+ skb_put(skb, 1 + sizeof(mgmt->u.action.tdls_discover_resp));
+ mgmt->u.action.category = WLAN_CATEGORY_PUBLIC;
+ mgmt->u.action.action_code = WLAN_PUB_ACTION_TDLS_DISCOVER_RES;
+ mgmt->u.action.tdls_discover_resp.dialog_token = dialog_token;
+ mgmt->u.action.tdls_discover_resp.capability =
+ cpu_to_le16(rwnx_get_tdls_sta_capab(rwnx_vif, status_code));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rwnx_add_srates_ie(struct rwnx_hw *rwnx_hw, struct sk_buff *skb)
+{
+ u8 i, rates, *pos;
+ int rate;
+ struct ieee80211_supported_band *rwnx_band_2ghz =
+ rwnx_hw->wiphy->bands[NL80211_BAND_2GHZ];
+
+ rates = 8;
+
+ if (skb_tailroom(skb) < rates + 2)
+ return -ENOMEM;
+
+ pos = skb_put(skb, rates + 2);
+ *pos++ = WLAN_EID_SUPP_RATES;
+ *pos++ = rates;
+ for (i = 0; i < rates; i++) {
+ rate = rwnx_band_2ghz->bitrates[i].bitrate;
+ rate = DIV_ROUND_UP(rate, 5);
+ *pos++ = (u8)rate;
+ }
+
+ return 0;
+}
+
+static int rwnx_add_ext_srates_ie(struct rwnx_hw *rwnx_hw, struct sk_buff *skb)
+{
+ u8 i, exrates, *pos;
+ int rate;
+ struct ieee80211_supported_band *rwnx_band_2ghz =
+ rwnx_hw->wiphy->bands[NL80211_BAND_2GHZ];
+
+ exrates = rwnx_band_2ghz->n_bitrates - 8;
+
+ if (skb_tailroom(skb) < exrates + 2)
+ return -ENOMEM;
+
+ pos = skb_put(skb, exrates + 2);
+ *pos++ = WLAN_EID_EXT_SUPP_RATES;
+ *pos++ = exrates;
+ for (i = 8; i < (8 + exrates); i++) {
+ rate = rwnx_band_2ghz->bitrates[i].bitrate;
+ rate = DIV_ROUND_UP(rate, 5);
+ *pos++ = (u8)rate;
+ }
+
+ return 0;
+}
+
+static void rwnx_tdls_add_supp_channels(struct rwnx_hw *rwnx_hw,
+ struct sk_buff *skb)
+{
+ /*
+ * Add possible channels for TDLS. These are channels that are allowed
+ * to be active.
+ */
+ u8 subband_cnt = 0;
+ u8 *pos_subband;
+ u8 *pos = skb_put(skb, 2);
+ struct ieee80211_supported_band *rwnx_band_2ghz =
+ rwnx_hw->wiphy->bands[NL80211_BAND_2GHZ];
+ struct ieee80211_supported_band *rwnx_band_5ghz =
+ rwnx_hw->wiphy->bands[NL80211_BAND_5GHZ];
+
+ *pos++ = WLAN_EID_SUPPORTED_CHANNELS;
+
+ /*
+ * 5GHz and 2GHz channels numbers can overlap. Ignore this for now, as
+ * this doesn't happen in real world scenarios.
+ */
+
+ /* 2GHz, with 5MHz spacing */
+ pos_subband = skb_put(skb, 2);
+ if (rwnx_band_2ghz->n_channels > 0) {
+ *pos_subband++ = ieee80211_frequency_to_channel
+ (rwnx_band_2ghz->channels[0].center_freq);
+ *pos_subband++ = rwnx_band_2ghz->n_channels;
+ subband_cnt++;
+ }
+
+ /* 5GHz, with 20MHz spacing */
+ pos_subband = skb_put(skb, 2);
+ if (rwnx_hw->band_5g_support) {
+ if (rwnx_band_5ghz->n_channels > 0) {
+ *pos_subband++ = ieee80211_frequency_to_channel
+ (rwnx_band_5ghz->channels[0].center_freq);
+ *pos_subband++ = rwnx_band_5ghz->n_channels;
+ subband_cnt++;
+ }
+ }
+ /* length */
+ *pos = 2 * subband_cnt;
+}
+
+static void rwnx_tdls_add_ext_capab(struct rwnx_hw *rwnx_hw,
+ struct sk_buff *skb)
+{
+ u8 *pos = (void *)skb_put(skb, 7);
+ bool chan_switch =
+ rwnx_hw->wiphy->features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH;
+
+ *pos++ = WLAN_EID_EXT_CAPABILITY;
+ *pos++ = 5; /* len */
+ *pos++ = 0x0;
+ *pos++ = 0x0;
+ *pos++ = 0x0;
+ *pos++ = WLAN_EXT_CAPA4_TDLS_BUFFER_STA |
+ (chan_switch ? WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH : 0);
+ *pos++ = WLAN_EXT_CAPA5_TDLS_ENABLED;
+}
+
+static void rwnx_add_wmm_info_ie(struct sk_buff *skb, u8 qosinfo)
+{
+ u8 *pos = (void *)skb_put(skb, 9);
+
+ *pos++ = WLAN_EID_VENDOR_SPECIFIC;
+ *pos++ = 7; /* len */
+ *pos++ = 0x00; /* Microsoft OUI 00:50:F2 */
+ *pos++ = 0x50;
+ *pos++ = 0xf2;
+ *pos++ = 2; /* WME */
+ *pos++ = 0; /* WME info */
+ *pos++ = 1; /* WME ver */
+ *pos++ = qosinfo; /* U-APSD no in use */
+}
+
+/* translate numbering in the WMM parameter IE to the mac80211 notation */
+static u8 rwnx_ac_from_wmm(int ac)
+{
+ switch (ac) {
+ default:
+ WARN_ON_ONCE(1);
+ fallthrough;
+ case 0:
+ return AC_BE;
+ case 1:
+ return AC_BK;
+ case 2:
+ return AC_VI;
+ case 3:
+ return AC_VO;
+ }
+}
+
+static void rwnx_add_wmm_param_ie(struct sk_buff *skb, u8 acm_bits,
+ u32 *ac_params)
+{
+ struct ieee80211_wmm_param_ie *wmm;
+ int i, j;
+ u8 cw_min, cw_max;
+ bool acm;
+
+ wmm = (void *)skb_put(skb, sizeof(struct ieee80211_wmm_param_ie));
+ memset(wmm, 0, sizeof(*wmm));
+
+ wmm->element_id = WLAN_EID_VENDOR_SPECIFIC;
+ wmm->len = sizeof(*wmm) - 2;
+
+ wmm->oui[0] = 0x00; /* Microsoft OUI 00:50:F2 */
+ wmm->oui[1] = 0x50;
+ wmm->oui[2] = 0xf2;
+ wmm->oui_type = 2; /* WME */
+ wmm->oui_subtype = 1; /* WME param */
+ wmm->version = 1; /* WME ver */
+ wmm->qos_info = 0; /* U-APSD not in use */
+
+ /*
+ * Use the EDCA parameters defined for the BSS, or default if the AP
+ * doesn't support it, as mandated by 802.11-2012 section 10.22.4
+ */
+ for (i = 0; i < AC_MAX; i++) {
+ j = rwnx_ac_from_wmm(i);
+ cw_min = (ac_params[j] & 0xF0) >> 4;
+ cw_max = (ac_params[j] & 0xF00) >> 8;
+ acm = (acm_bits & (1 << j)) != 0;
+
+ wmm->ac[i].aci_aifsn = (i << 5) | (acm << 4) | (ac_params[j] & 0xF);
+ wmm->ac[i].cw = (cw_max << 4) | cw_min;
+ wmm->ac[i].txop_limit =
+ cpu_to_le16((ac_params[j] & 0x0FFFF000) >> 12);
+ }
+}
+
+static void rwnx_tdls_add_oper_classes(struct rwnx_vif *rwnx_vif,
+ struct sk_buff *skb)
+{
+ u8 *pos;
+ u8 op_class;
+ struct cfg80211_chan_def chan_def;
+ struct ieee80211_channel chan;
+
+ chan.band = rwnx_vif->sta.ap->band;
+ chan.center_freq = rwnx_vif->sta.ap->center_freq;
+ chan_def.chan = &chan;
+ chan_def.width = rwnx_vif->sta.ap->width;
+ chan_def.center_freq1 = rwnx_vif->sta.ap->center_freq1;
+ chan_def.center_freq2 = rwnx_vif->sta.ap->center_freq2;
+ if (!ieee80211_chandef_to_operating_class(&chan_def, &op_class))
+ return;
+
+ pos = skb_put(skb, 4);
+ *pos++ = WLAN_EID_SUPPORTED_REGULATORY_CLASSES;
+ *pos++ = 2; /* len */
+
+ // current op class
+ *pos++ = op_class;
+ *pos++ = op_class; /* give current operating class as alternate too */
+
+ // need to add 5GHz classes?
+}
+
+static void rwnx_ie_build_ht_cap(struct sk_buff *skb,
+ struct ieee80211_sta_ht_cap *ht_cap, u16 cap)
+{
+ u8 *pos;
+ __le16 tmp;
+
+ pos = skb_put(skb, sizeof(struct ieee80211_ht_cap) + 2);
+ *pos++ = WLAN_EID_HT_CAPABILITY;
+ *pos++ = sizeof(struct ieee80211_ht_cap);
+ memset(pos, 0, sizeof(struct ieee80211_ht_cap));
+
+ /* capability flags */
+ tmp = cpu_to_le16(cap);
+ memcpy(pos, &tmp, sizeof(u16));
+ pos += sizeof(u16);
+
+ /* AMPDU parameters */
+ *pos++ = ht_cap->ampdu_factor |
+ (ht_cap->ampdu_density << IEEE80211_HT_AMPDU_PARM_DENSITY_SHIFT);
+
+ /* MCS set */
+ memcpy(pos, &ht_cap->mcs, sizeof(ht_cap->mcs));
+ pos += sizeof(ht_cap->mcs);
+
+ /* extended capabilities */
+ pos += sizeof(__le16);
+
+ /* BF capabilities */
+ pos += sizeof(__le32);
+
+ /* antenna selection */
+ pos += sizeof(u8);
+}
+
+static void rwnx_ie_build_vht_cap(struct sk_buff *skb,
+ struct ieee80211_sta_vht_cap *vht_cap,
+ u32 cap)
+{
+ u8 *pos;
+ __le32 tmp;
+
+ pos = skb_put(skb, 14);
+
+ *pos++ = WLAN_EID_VHT_CAPABILITY;
+ *pos++ = sizeof(struct ieee80211_vht_cap);
+ memset(pos, 0, sizeof(struct ieee80211_vht_cap));
+
+ /* capability flags */
+ tmp = cpu_to_le32(cap);
+ memcpy(pos, &tmp, sizeof(u32));
+ pos += sizeof(u32);
+
+ /* VHT MCS set */
+ memcpy(pos, &vht_cap->vht_mcs, sizeof(vht_cap->vht_mcs));
+ pos += sizeof(vht_cap->vht_mcs);
+}
+
+static void rwnx_tdls_add_bss_coex_ie(struct sk_buff *skb)
+{
+ u8 *pos = (void *)skb_put(skb, 3);
+
+ *pos++ = WLAN_EID_BSS_COEX_2040;
+ *pos++ = 1; /* len */
+
+ *pos++ = WLAN_BSS_COEX_INFORMATION_REQUEST;
+}
+
+static void rwnx_tdls_add_link_ie(struct rwnx_hw *rwnx_hw,
+ struct rwnx_vif *rwnx_vif,
+ struct sk_buff *skb, const u8 *peer,
+ bool initiator)
+{
+ struct ieee80211_tdls_lnkie *lnkid;
+ const u8 *init_addr, *rsp_addr;
+
+ if (initiator) {
+ init_addr = rwnx_hw->wiphy->perm_addr;
+ rsp_addr = peer;
+ } else {
+ init_addr = peer;
+ rsp_addr = rwnx_hw->wiphy->perm_addr;
+ }
+
+ lnkid = (void *)skb_put(skb, sizeof(struct ieee80211_tdls_lnkie));
+
+ lnkid->ie_type = WLAN_EID_LINK_ID;
+ lnkid->ie_len = sizeof(struct ieee80211_tdls_lnkie) - 2;
+
+ memcpy(lnkid->bssid, rwnx_vif->sta.ap->mac_addr, ETH_ALEN);
+ memcpy(lnkid->init_sta, init_addr, ETH_ALEN);
+ memcpy(lnkid->resp_sta, rsp_addr, ETH_ALEN);
+}
+
+static void rwnx_tdls_add_aid_ie(struct rwnx_vif *rwnx_vif, struct sk_buff *skb)
+{
+ u8 *pos = (void *)skb_put(skb, 4);
+
+ *pos++ = WLAN_EID_AID;
+ *pos++ = 2; /* len */
+ *pos++ = rwnx_vif->sta.ap->aid;
+}
+
+static u8 *rwnx_ie_build_ht_oper(struct rwnx_hw *rwnx_hw,
+ struct rwnx_vif *rwnx_vif, u8 *pos,
+ struct ieee80211_sta_ht_cap *ht_cap,
+ u16 prot_mode)
+{
+ struct ieee80211_ht_operation *ht_oper;
+ /* Build HT Information */
+ *pos++ = WLAN_EID_HT_OPERATION;
+ *pos++ = sizeof(struct ieee80211_ht_operation);
+ ht_oper = (struct ieee80211_ht_operation *)pos;
+ ht_oper->primary_chan =
+ ieee80211_frequency_to_channel(rwnx_vif->sta.ap->center_freq);
+ switch (rwnx_vif->sta.ap->width) {
+ case NL80211_CHAN_WIDTH_160:
+ case NL80211_CHAN_WIDTH_80P80:
+ case NL80211_CHAN_WIDTH_80:
+ case NL80211_CHAN_WIDTH_40:
+ if (rwnx_vif->sta.ap->center_freq1 > rwnx_vif->sta.ap->center_freq)
+ ht_oper->ht_param = IEEE80211_HT_PARAM_CHA_SEC_ABOVE;
+ else
+ ht_oper->ht_param = IEEE80211_HT_PARAM_CHA_SEC_BELOW;
+ break;
+ default:
+ ht_oper->ht_param = IEEE80211_HT_PARAM_CHA_SEC_NONE;
+ break;
+ }
+ if (ht_cap->cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 &&
+ rwnx_vif->sta.ap->width != NL80211_CHAN_WIDTH_20_NOHT &&
+ rwnx_vif->sta.ap->width != NL80211_CHAN_WIDTH_20)
+ ht_oper->ht_param |= IEEE80211_HT_PARAM_CHAN_WIDTH_ANY;
+
+ ht_oper->operation_mode = cpu_to_le16(prot_mode);
+ ht_oper->stbc_param = 0x0000;
+
+ /* It seems that Basic MCS set and Supported MCS set
+ * are identical for the first 10 bytes
+ */
+ memset(&ht_oper->basic_set, 0, 16);
+ memcpy(&ht_oper->basic_set, &ht_cap->mcs, 10);
+
+ return pos + sizeof(struct ieee80211_ht_operation);
+}
+
+static u8 *rwnx_ie_build_vht_oper(struct rwnx_hw *rwnx_hw,
+ struct rwnx_vif *rwnx_vif, u8 *pos,
+ struct ieee80211_sta_ht_cap *ht_cap,
+ u16 prot_mode)
+{
+ struct ieee80211_vht_operation *vht_oper;
+ /* Build HT Information */
+ *pos++ = WLAN_EID_VHT_OPERATION;
+ *pos++ = sizeof(struct ieee80211_vht_operation);
+ vht_oper = (struct ieee80211_vht_operation *)pos;
+
+ switch (rwnx_vif->sta.ap->width) {
+ case NL80211_CHAN_WIDTH_80:
+ vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_80MHZ; // Channel Width
+ CCFS0(vht_oper) = ieee80211_frequency_to_channel
+ (rwnx_vif->sta.ap->center_freq);
+ // Channel Center Frequency Segment 0
+ CCFS1(vht_oper) = 0; // Channel Center Frequency Segment 1 (N.A.)
+ break;
+ case NL80211_CHAN_WIDTH_160:
+ vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_160MHZ; // Channel Width
+ CCFS0(vht_oper) = ieee80211_frequency_to_channel
+ (rwnx_vif->sta.ap->center_freq);
+ // Channel Center Frequency Segment 0
+ CCFS1(vht_oper) = 0; // Channel Center Frequency Segment 1 (N.A.)
+ break;
+ case NL80211_CHAN_WIDTH_80P80:
+ vht_oper->chan_width =
+ IEEE80211_VHT_CHANWIDTH_80P80MHZ; // Channel Width
+ CCFS0(vht_oper) = ieee80211_frequency_to_channel
+ (rwnx_vif->sta.ap->center_freq1);
+ // Channel Center Frequency Segment 0
+ CCFS1(vht_oper) = ieee80211_frequency_to_channel
+ (rwnx_vif->sta.ap->center_freq2);
+ // Channel Center Frequency Segment 1
+ break;
+ default:
+ vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_USE_HT;
+ CCFS0(vht_oper) = 0;
+ CCFS1(vht_oper) = 0;
+ break;
+ }
+
+ vht_oper->basic_mcs_set = cpu_to_le16(rwnx_hw->mod_params->mcs_map);
+
+ return pos + sizeof(struct ieee80211_vht_operation);
+}
+
+static void rwnx_tdls_add_setup_start_ies(struct rwnx_hw *rwnx_hw,
+ struct rwnx_vif *rwnx_vif,
+ struct sk_buff *skb, const u8 *peer,
+ u8 action_code, bool initiator,
+ const u8 *extra_ies,
+ size_t extra_ies_len)
+{
+ enum nl80211_band band = rwnx_vif->sta.ap->band;
+ struct ieee80211_supported_band *sband;
+ struct ieee80211_sta_ht_cap ht_cap;
+ struct ieee80211_sta_vht_cap vht_cap;
+ size_t offset = 0, noffset;
+ u8 *pos;
+
+ rcu_read_lock();
+
+ rwnx_add_srates_ie(rwnx_hw, skb);
+ rwnx_add_ext_srates_ie(rwnx_hw, skb);
+ rwnx_tdls_add_supp_channels(rwnx_hw, skb);
+ rwnx_tdls_add_ext_capab(rwnx_hw, skb);
+
+ /* add the QoS element if we support it */
+ if (/*local->hw.queues >= IEEE80211_NUM_ACS && */
+ action_code != WLAN_PUB_ACTION_TDLS_DISCOVER_RES)
+ rwnx_add_wmm_info_ie(skb, 0); /* no U-APSD */
+
+ rwnx_tdls_add_oper_classes(rwnx_vif, skb);
+
+ /*
+ * with TDLS we can switch channels, and HT-caps are not necessarily
+ * the same on all bands. The specification limits the setup to a
+ * single HT-cap, so use the current band for now.
+ */
+ sband = rwnx_hw->wiphy->bands[band];
+ memcpy(&ht_cap, &sband->ht_cap, sizeof(ht_cap));
+ if ((action_code == WLAN_TDLS_SETUP_REQUEST ||
+ action_code == WLAN_TDLS_SETUP_RESPONSE ||
+ action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) &&
+ ht_cap.ht_supported /* (!sta || sta->sta.ht_cap.ht_supported) */) {
+ rwnx_ie_build_ht_cap(skb, &ht_cap, ht_cap.cap);
+ }
+
+ if (ht_cap.ht_supported && (ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40))
+ rwnx_tdls_add_bss_coex_ie(skb);
+
+ rwnx_tdls_add_link_ie(rwnx_hw, rwnx_vif, skb, peer, initiator);
+
+ memcpy(&vht_cap, &sband->vht_cap, sizeof(vht_cap));
+ if (vht_cap.vht_supported) {
+ rwnx_tdls_add_aid_ie(rwnx_vif, skb);
+ rwnx_ie_build_vht_cap(skb, &vht_cap, vht_cap.cap);
+ // Operating mode Notification (optional)
+ }
+
+ /* add any remaining IEs */
+ if (extra_ies_len) {
+ noffset = extra_ies_len;
+ pos = skb_put(skb, noffset - offset);
+ memcpy(pos, extra_ies + offset, noffset - offset);
+ }
+
+ rcu_read_unlock();
+}
+
+static void rwnx_tdls_add_setup_cfm_ies(struct rwnx_hw *rwnx_hw,
+ struct rwnx_vif *rwnx_vif,
+ struct sk_buff *skb, const u8 *peer,
+ bool initiator, const u8 *extra_ies,
+ size_t extra_ies_len)
+{
+ struct ieee80211_supported_band *sband;
+ enum nl80211_band band = rwnx_vif->sta.ap->band;
+ struct ieee80211_sta_ht_cap ht_cap;
+ struct ieee80211_sta_vht_cap vht_cap;
+
+ size_t offset = 0, noffset;
+ struct rwnx_sta *sta, *ap_sta;
+ u8 *pos;
+
+ rcu_read_lock();
+
+ sta = rwnx_get_sta(rwnx_hw, peer);
+ ap_sta = rwnx_vif->sta.ap;
+ if (WARN_ON_ONCE(!sta || !ap_sta)) {
+ rcu_read_unlock();
+ return;
+ }
+
+ /* add the QoS param IE if both the peer and we support it */
+ if (sta->qos)
+ rwnx_add_wmm_param_ie(skb, ap_sta->acm, ap_sta->ac_param);
+
+ /* if HT support is only added in TDLS, we need an HT-operation IE */
+ sband = rwnx_hw->wiphy->bands[band];
+ memcpy(&ht_cap, &sband->ht_cap, sizeof(ht_cap));
+ if (ht_cap.ht_supported && !ap_sta->ht && sta->ht) {
+ pos = skb_put(skb, 2 + sizeof(struct ieee80211_ht_operation));
+ /* send an empty HT operation IE */
+ rwnx_ie_build_ht_oper(rwnx_hw, rwnx_vif, pos, &ht_cap, 0);
+ }
+
+ rwnx_tdls_add_link_ie(rwnx_hw, rwnx_vif, skb, peer, initiator);
+
+ memcpy(&vht_cap, &sband->vht_cap, sizeof(vht_cap));
+ if (vht_cap.vht_supported && !ap_sta->vht && sta->vht) {
+ pos = skb_put(skb, 2 + sizeof(struct ieee80211_vht_operation));
+ rwnx_ie_build_vht_oper(rwnx_hw, rwnx_vif, pos, &ht_cap, 0);
+ // Operating mode Notification (optional)
+ }
+
+ /* add any remaining IEs */
+ if (extra_ies_len) {
+ noffset = extra_ies_len;
+ pos = skb_put(skb, noffset - offset);
+ memcpy(pos, extra_ies + offset, noffset - offset);
+ }
+
+ rcu_read_unlock();
+}
+
+static void rwnx_tdls_add_ies(struct rwnx_hw *rwnx_hw,
+ struct rwnx_vif *rwnx_vif, struct sk_buff *skb,
+ const u8 *peer, u8 action_code, u16 status_code,
+ bool initiator, const u8 *extra_ies,
+ size_t extra_ies_len, u8 oper_class,
+ struct cfg80211_chan_def *chandef)
+{
+ switch (action_code) {
+ case WLAN_TDLS_SETUP_REQUEST:
+ case WLAN_TDLS_SETUP_RESPONSE:
+ case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
+ if (status_code == 0)
+ rwnx_tdls_add_setup_start_ies(rwnx_hw, rwnx_vif, skb, peer,
+ action_code, initiator, extra_ies,
+ extra_ies_len);
+ break;
+ case WLAN_TDLS_SETUP_CONFIRM:
+ if (status_code == 0)
+ rwnx_tdls_add_setup_cfm_ies(rwnx_hw, rwnx_vif, skb, peer, initiator,
+ extra_ies, extra_ies_len);
+ break;
+
+ case WLAN_TDLS_TEARDOWN:
+ case WLAN_TDLS_DISCOVERY_REQUEST:
+ if (extra_ies_len)
+ memcpy(skb_put(skb, extra_ies_len), extra_ies, extra_ies_len);
+ if (status_code == 0 || action_code == WLAN_TDLS_TEARDOWN)
+ rwnx_tdls_add_link_ie(rwnx_hw, rwnx_vif, skb, peer, initiator);
+ break;
+ }
+}
+
+int rwnx_tdls_send_mgmt_packet_data(struct rwnx_hw *rwnx_hw,
+ struct rwnx_vif *rwnx_vif, const u8 *peer,
+ u8 action_code, u8 dialog_token,
+ u16 status_code, u32 peer_capability,
+ bool initiator, const u8 *extra_ies,
+ size_t extra_ies_len, u8 oper_class,
+ struct cfg80211_chan_def *chandef)
+{
+ struct sk_buff *skb;
+ int ret = 0;
+ struct ieee80211_supported_band *rwnx_band_2ghz =
+ rwnx_hw->wiphy->bands[NL80211_BAND_2GHZ];
+ struct ieee80211_supported_band *rwnx_band_5ghz =
+ rwnx_hw->wiphy->bands[NL80211_BAND_5GHZ];
+ int channels = rwnx_band_2ghz->n_channels;
+
+ if (rwnx_hw->band_5g_support)
+ channels += rwnx_band_5ghz->n_channels;
+
+ skb = netdev_alloc_skb
+ (rwnx_vif->ndev,
+ sizeof(struct ieee80211_tdls_data) + // ethhdr + TDLS info
+ 10 + /* supported rates */
+ 6 + /* extended supported rates */
+ (2 + channels) + /* supported channels */
+ sizeof(struct ieee_types_extcap) +
+ sizeof(struct ieee80211_wmm_param_ie) + 4 + /* oper classes */
+ 28 + // sizeof(struct ieee80211_ht_cap) +
+ sizeof(struct ieee_types_bss_co_2040) +
+ sizeof(struct ieee80211_tdls_lnkie) +
+ (2 + sizeof(struct ieee80211_vht_cap)) + 4 + /*AID*/
+ (2 + sizeof(struct ieee80211_ht_operation)) + extra_ies_len);
+
+ if (!skb)
+ return 0;
+
+ switch (action_code) {
+ case WLAN_TDLS_SETUP_REQUEST:
+ case WLAN_TDLS_SETUP_RESPONSE:
+ case WLAN_TDLS_SETUP_CONFIRM:
+ case WLAN_TDLS_TEARDOWN:
+ case WLAN_TDLS_DISCOVERY_REQUEST:
+ ret = rwnx_tdls_prepare_encap_data(rwnx_hw, rwnx_vif, peer, action_code,
+ dialog_token, status_code, skb);
+ break;
+
+ case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
+ ret = rwnx_prep_tdls_direct(rwnx_hw, rwnx_vif, peer, action_code,
+ dialog_token, status_code, skb);
+ break;
+
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ if (ret < 0)
+ goto fail;
+
+ rwnx_tdls_add_ies(rwnx_hw, rwnx_vif, skb, peer, action_code, status_code,
+ initiator, extra_ies, extra_ies_len, oper_class, chandef);
+
+ if (action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) {
+ u64 cookie;
+ struct cfg80211_mgmt_tx_params params;
+
+ params.len = skb->len;
+ params.buf = skb->data;
+ ret = rwnx_start_mgmt_xmit(rwnx_vif, NULL, ¶ms, false, &cookie);
+ return ret;
+ }
+
+ switch (action_code) {
+ case WLAN_TDLS_SETUP_REQUEST:
+ case WLAN_TDLS_SETUP_RESPONSE:
+ case WLAN_TDLS_SETUP_CONFIRM:
+ skb->priority = 2;
+ break;
+ default:
+ skb->priority = 5;
+ break;
+ }
+
+ ret = rwnx_select_txq(rwnx_vif, skb);
+ ret = rwnx_start_xmit(skb, rwnx_vif->ndev);
+
+ return ret;
+
+fail:
+ dev_kfree_skb(skb);
+ return ret;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tdls.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tdls.h
new file mode 100644
index 0000000000000..3fb0cec2fc1a6
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tdls.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_tdls.h
+ *
+ * @brief TDLS function declarations
+ *
+ ******************************************************************************
+ */
+
+#ifndef RWNX_TDLS_H_
+#define RWNX_TDLS_H_
+
+#include "rwnx_defs.h"
+
+struct ieee_types_header {
+ u8 element_id;
+ u8 len;
+} __packed;
+
+struct ieee_types_bss_co_2040 {
+ struct ieee_types_header ieee_hdr;
+ u8 bss_2040co;
+} __packed;
+
+struct ieee_types_extcap {
+ struct ieee_types_header ieee_hdr;
+ u8 ext_capab[8];
+} __packed;
+
+struct ieee_types_vht_cap {
+ struct ieee_types_header ieee_hdr;
+ struct ieee80211_vht_cap vhtcap;
+} __packed;
+
+struct ieee_types_vht_oper {
+ struct ieee_types_header ieee_hdr;
+ struct ieee80211_vht_operation vhtoper;
+} __packed;
+
+struct ieee_types_aid {
+ struct ieee_types_header ieee_hdr;
+ u16 aid;
+} __packed;
+
+int rwnx_tdls_send_mgmt_packet_data(struct rwnx_hw *rwnx_hw,
+ struct rwnx_vif *rwnx_vif, const u8 *peer,
+ u8 action_code, u8 dialog_token,
+ u16 status_code, u32 peer_capability,
+ bool initiator, const u8 *extra_ies,
+ size_t extra_ies_len, u8 oper_class,
+ struct cfg80211_chan_def *chandef);
+
+#endif /* RWNX_TDLS_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_testmode.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_testmode.c
new file mode 100644
index 0000000000000..340cbda41d92d
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_testmode.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_testmode.c
+ *
+ * @brief Test mode function declarations
+ *
+ ****************************************************************************************
+ */
+
+#include <net/mac80211.h>
+#include <net/netlink.h>
+
+#include "reg_access.h"
+#include "rwnx_msg_tx.h"
+#include "rwnx_testmode.h"
+
+/*
+ * This function handles the user application commands for register access.
+ *
+ * It retrieves command ID carried with RWNX_TM_ATTR_COMMAND and calls to the
+ * handlers respectively.
+ *
+ * If it's an unknown commdn ID, -ENOSYS is returned; or -ENOMSG if the
+ * mandatory fields(RWNX_TM_ATTR_REG_OFFSET,RWNX_TM_ATTR_REG_VALUE32)
+ * are missing; Otherwise 0 is replied indicating the success of the command
+ * execution.
+ *
+ * If RWNX_TM_ATTR_COMMAND is RWNX_TM_CMD_APP2DEV_REG_READ, the register read
+ * value is returned with RWNX_TM_ATTR_REG_VALUE32.
+ *
+ * @hw: ieee80211_hw object that represents the device
+ * @tb: general message fields from the user space
+ */
+int rwnx_testmode_reg(struct ieee80211_hw *hw, struct nlattr **tb)
+{
+ struct rwnx_hw *rwnx_hw = hw->priv;
+ u32 mem_addr, val32;
+ struct sk_buff *skb;
+ int status = 0;
+
+ /* First check if register address is there */
+ if (!tb[RWNX_TM_ATTR_REG_OFFSET]) {
+ AICWFDBG(LOGDEBUG, "Error finding register offset\n");
+ return -ENOMSG;
+ }
+
+ mem_addr = nla_get_u32(tb[RWNX_TM_ATTR_REG_OFFSET]);
+
+ switch (nla_get_u32(tb[RWNX_TM_ATTR_COMMAND])) {
+ case RWNX_TM_CMD_APP2DEV_REG_READ: {
+ struct dbg_mem_read_cfm mem_read_cfm;
+
+ /*** Send the command to the LMAC ***/
+ status = rwnx_send_dbg_mem_read_req(rwnx_hw, mem_addr, &mem_read_cfm);
+ if (status)
+ return status;
+
+ /* Allocate the answer message */
+ skb = cfg80211_testmode_alloc_reply_skb(hw->wiphy, 20);
+ if (!skb) {
+ AICWFDBG(LOGDEBUG, "Error allocating memory\n");
+ return -ENOMEM;
+ }
+
+ val32 = mem_read_cfm.memdata;
+ if (nla_put_u32(skb, RWNX_TM_ATTR_REG_VALUE32, val32))
+ goto nla_put_failure;
+
+ /* Send the answer to upper layer */
+ status = cfg80211_testmode_reply(skb);
+ if (status < 0)
+ AICWFDBG(LOGDEBUG, "Error sending msg : %d\n", status);
+ } break;
+
+ case RWNX_TM_CMD_APP2DEV_REG_WRITE: {
+ if (!tb[RWNX_TM_ATTR_REG_VALUE32]) {
+ AICWFDBG(LOGDEBUG, "Error finding value to write\n");
+ return -ENOMSG;
+ }
+ val32 = nla_get_u32(tb[RWNX_TM_ATTR_REG_VALUE32]);
+ /* Send the command to the LMAC */
+ status = rwnx_send_dbg_mem_write_req(rwnx_hw, mem_addr, val32);
+ if (status)
+ return status;
+ } break;
+
+ default:
+ AICWFDBG(LOGDEBUG, "Unknown testmode register command ID\n");
+ return -EOPNOTSUPP;
+ }
+
+ return status;
+
+nla_put_failure:
+ kfree_skb(skb);
+ return -EMSGSIZE;
+}
+
+/*
+ * This function handles the user application commands for Debug filter
+ * settings.
+ *
+ * @hw: ieee80211_hw object that represents the device
+ * @tb: general message fields from the user space
+ */
+int rwnx_testmode_dbg_filter(struct ieee80211_hw *hw, struct nlattr **tb)
+{
+ struct rwnx_hw *rwnx_hw = hw->priv;
+ u32 filter;
+ int status = 0;
+
+ /* First check if the filter is there */
+ if (!tb[RWNX_TM_ATTR_REG_FILTER]) {
+ AICWFDBG(LOGDEBUG, "Error finding filter value\n");
+ return -ENOMSG;
+ }
+
+ filter = nla_get_u32(tb[RWNX_TM_ATTR_REG_FILTER]);
+ RWNX_DBG("testmode debug filter, setting: 0x%x\n", filter);
+
+ switch (nla_get_u32(tb[RWNX_TM_ATTR_COMMAND])) {
+ case RWNX_TM_CMD_APP2DEV_SET_DBGMODFILTER: {
+ /* Send the command to the LMAC */
+ status = rwnx_send_dbg_set_mod_filter_req(rwnx_hw, filter);
+ if (status)
+ return status;
+ } break;
+ case RWNX_TM_CMD_APP2DEV_SET_DBGSEVFILTER: {
+ /* Send the command to the LMAC */
+ status = rwnx_send_dbg_set_sev_filter_req(rwnx_hw, filter);
+ if (status)
+ return status;
+ } break;
+
+ default:
+ AICWFDBG(LOGDEBUG, "Unknown testmode register command ID\n");
+ return -EOPNOTSUPP;
+ }
+
+ return status;
+}
+
+/*
+ * This function handles the user application commands for register access
+ * without using the normal LMAC messaging way. This time register access will
+ * be done through direct PCI BAR windows. This can be used to access registers
+ * even when the :AMC FW is stuck.
+ *
+ * @hw: ieee80211_hw object that represents the device
+ * @tb: general message fields from the user space
+ */
+int rwnx_testmode_reg_dbg(struct ieee80211_hw *hw, struct nlattr **tb)
+{
+ struct rwnx_hw *rwnx_hw = hw->priv;
+ struct rwnx_plat *rwnx_plat = rwnx_hw->plat;
+ u32 mem_addr;
+ struct sk_buff *skb;
+ int status = 0;
+ unsigned int reg_value = 0;
+ unsigned int offset;
+
+ /* First check if register address is there */
+ if (!tb[RWNX_TM_ATTR_REG_OFFSET]) {
+ AICWFDBG(LOGDEBUG, "Error finding register offset\n");
+ return -ENOMSG;
+ }
+
+ mem_addr = nla_get_u32(tb[RWNX_TM_ATTR_REG_OFFSET]);
+ offset = mem_addr & 0x00FFFFFF;
+
+ switch (nla_get_u32(tb[RWNX_TM_ATTR_COMMAND])) {
+ case RWNX_TM_CMD_APP2DEV_REG_READ_DBG: {
+ /*** Send the command to the LMAC ***/
+ reg_value = RWNX_REG_READ(rwnx_plat, RWNX_ADDR_SYSTEM, offset);
+
+ /* Allocate the answer message */
+ skb = cfg80211_testmode_alloc_reply_skb(hw->wiphy, 20);
+ if (!skb) {
+ AICWFDBG(LOGDEBUG, "Error allocating memory\n");
+ return -ENOMEM;
+ }
+
+ if (nla_put_u32(skb, RWNX_TM_ATTR_REG_VALUE32, reg_value))
+ goto nla_put_failure;
+
+ /* Send the answer to upper layer */
+ status = cfg80211_testmode_reply(skb);
+ if (status < 0)
+ AICWFDBG(LOGDEBUG, "Error sending msg : %d\n", status);
+ } break;
+
+ case RWNX_TM_CMD_APP2DEV_REG_WRITE_DBG: {
+ if (!tb[RWNX_TM_ATTR_REG_VALUE32]) {
+ AICWFDBG(LOGDEBUG, "Error finding value to write\n");
+ return -ENOMSG;
+ }
+ reg_value = nla_get_u32(tb[RWNX_TM_ATTR_REG_VALUE32]);
+
+ /* Send the command to the LMAC */
+ RWNX_REG_WRITE(reg_value, rwnx_plat, RWNX_ADDR_SYSTEM, offset);
+ } break;
+
+ default:
+ AICWFDBG(LOGDEBUG, "Unknown testmode register command ID\n");
+ return -EOPNOTSUPP;
+ }
+
+ return status;
+
+nla_put_failure:
+ kfree_skb(skb);
+ return -EMSGSIZE;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_testmode.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_testmode.h
new file mode 100644
index 0000000000000..2d5f1470babea
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_testmode.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_testmode.h
+ *
+ * @brief Test mode function declarations
+ *
+ ****************************************************************************************
+ */
+
+#ifndef RWNX_TESTMODE_H_
+#define RWNX_TESTMODE_H_
+
+#include <net/mac80211.h>
+#include <net/netlink.h>
+
+/* Commands from user space to kernel space(RWNX_TM_CMD_APP2DEV_XX) and
+ * from and kernel space to user space(RWNX_TM_CMD_DEV2APP_XX).
+ * The command ID is carried with RWNX_TM_ATTR_COMMAND.
+ */
+enum rwnx_tm_cmd_t {
+ /* commands from user application to access register */
+ RWNX_TM_CMD_APP2DEV_REG_READ = 1,
+ RWNX_TM_CMD_APP2DEV_REG_WRITE,
+
+ /* commands from user application to select the Debug levels */
+ RWNX_TM_CMD_APP2DEV_SET_DBGMODFILTER,
+ RWNX_TM_CMD_APP2DEV_SET_DBGSEVFILTER,
+
+ /* commands to access registers without sending messages to LMAC layer,
+ * this must be used when LMAC FW is stuck.
+ */
+ RWNX_TM_CMD_APP2DEV_REG_READ_DBG,
+ RWNX_TM_CMD_APP2DEV_REG_WRITE_DBG,
+
+ RWNX_TM_CMD_MAX,
+};
+
+enum rwnx_tm_attr_t {
+ RWNX_TM_ATTR_NOT_APPLICABLE = 0,
+
+ RWNX_TM_ATTR_COMMAND,
+
+ /* When RWNX_TM_ATTR_COMMAND is RWNX_TM_CMD_APP2DEV_REG_XXX,
+ * The mandatory fields are:
+ * RWNX_TM_ATTR_REG_OFFSET for the offset of the target register;
+ * RWNX_TM_ATTR_REG_VALUE32 for value
+ */
+ RWNX_TM_ATTR_REG_OFFSET,
+ RWNX_TM_ATTR_REG_VALUE32,
+
+ /* When RWNX_TM_ATTR_COMMAND is RWNX_TM_CMD_APP2DEV_SET_DBGXXXFILTER,
+ * The mandatory field is RWNX_TM_ATTR_REG_FILTER.
+ */
+ RWNX_TM_ATTR_REG_FILTER,
+
+ RWNX_TM_ATTR_MAX,
+};
+
+/***********************************************************************/
+int rwnx_testmode_reg(struct ieee80211_hw *hw, struct nlattr **tb);
+int rwnx_testmode_dbg_filter(struct ieee80211_hw *hw, struct nlattr **tb);
+int rwnx_testmode_reg_dbg(struct ieee80211_hw *hw, struct nlattr **tb);
+
+#endif /* RWNX_TESTMODE_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_trace.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_trace.c
new file mode 100644
index 0000000000000..732fca321d965
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_trace.c
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "rwnx_defs.h"
+
+#define CREATE_TRACE_POINTS
+#include "rwnx_events.h"
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tx.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tx.c
new file mode 100644
index 0000000000000..e3ed2892f1784
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tx.c
@@ -0,0 +1,1635 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_tx.c
+ *
+ * @brief tx handle
+ *
+ ******************************************************************************
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/etherdevice.h>
+#include <net/sock.h>
+
+#include "aicwf_txrxif.h"
+#include "rwnx_compat.h"
+#include "rwnx_defs.h"
+#include "rwnx_events.h"
+#include "rwnx_mesh.h"
+#include "rwnx_msg_tx.h"
+#include "rwnx_tx.h"
+
+/******************************************************************************
+ * Power Save functions
+ *****************************************************************************/
+/**
+ * rwnx_set_traffic_status - Inform FW if traffic is available for STA in PS
+ *
+ * @rwnx_hw: Driver main data
+ * @sta: Sta in PS mode
+ * @available: whether traffic is buffered for the STA
+ * @ps_id: type of PS data requested (@LEGACY_PS_ID or @UAPSD_ID)
+ */
+void rwnx_set_traffic_status(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta,
+ bool available, u8 ps_id)
+{
+ if (sta->tdls.active) {
+ rwnx_send_tdls_peer_traffic_ind_req(rwnx_hw,
+ rwnx_hw->vif_table[sta->vif_idx]);
+ } else {
+ bool uapsd = (ps_id != LEGACY_PS_ID);
+
+ rwnx_send_me_traffic_ind(rwnx_hw, sta->sta_idx, uapsd, available);
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_ps_traffic_update(sta->sta_idx, available, uapsd);
+#endif
+ }
+}
+
+/**
+ * rwnx_ps_bh_enable - Enable/disable PS mode for one STA
+ *
+ * @rwnx_hw: Driver main data
+ * @sta: Sta which enters/leaves PS mode
+ * @enable: PS mode status
+ *
+ * This function will enable/disable PS mode for one STA.
+ * When enabling PS mode:
+ * - Stop all STA's txq for RWNX_TXQ_STOP_STA_PS reason
+ * - Count how many buffers are already ready for this STA
+ * - For BC/MC sta, update all queued SKB to use hw_queue BCMC
+ * - Update TIM if some packet are ready
+ *
+ * When disabling PS mode:
+ * - Start all STA's txq for RWNX_TXQ_STOP_STA_PS reason
+ * - For BC/MC sta, update all queued SKB to use hw_queue AC_BE
+ * - Update TIM if some packet are ready (otherwise fw will not update TIM
+ * in beacon for this STA)
+ *
+ * All counter/skb updates are protected from TX path by taking tx_lock
+ *
+ * NOTE: _bh_ in function name indicates that this function is called
+ * from a bottom_half tasklet.
+ */
+void rwnx_ps_bh_enable(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta,
+ bool enable)
+{
+ struct rwnx_txq *txq;
+
+ if (enable) {
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_ps_enable(sta);
+#endif
+ spin_lock_bh(&rwnx_hw->tx_lock);
+ sta->ps.active = true;
+ sta->ps.sp_cnt[LEGACY_PS_ID] = 0;
+ sta->ps.sp_cnt[UAPSD_ID] = 0;
+ rwnx_txq_sta_stop(sta, RWNX_TXQ_STOP_STA_PS, rwnx_hw);
+
+ if (is_multicast_sta(sta->sta_idx)) {
+ txq = rwnx_txq_sta_get(sta, 0, rwnx_hw);
+ sta->ps.pkt_ready[LEGACY_PS_ID] = skb_queue_len(&txq->sk_list);
+ sta->ps.pkt_ready[UAPSD_ID] = 0;
+ // txq->hwq = &rwnx_hw->hwq[RWNX_HWQ_BCMC];
+ } else {
+ int i;
+
+ sta->ps.pkt_ready[LEGACY_PS_ID] = 0;
+ sta->ps.pkt_ready[UAPSD_ID] = 0;
+
+#ifdef CONFIG_MAC80211_TXQ
+ for (i = 0, txq = rwnx_txq_sta_get(sta, 0);
+ i < NX_NB_TXQ_PER_STA;
+ i++, txq = rwnx_txq_sta_get(sta, i))
+#elif defined(CONFIG_RWNX_FULLMAC)
+ for (i = 0, txq = rwnx_txq_sta_get(sta,
+ 0, rwnx_hw);
+ i < (is_multicast_sta(sta->sta_idx) ? 1 : NX_NB_TXQ_PER_STA);
+ i++, txq++)
+#endif
+ {
+ sta->ps.pkt_ready[txq->ps_id] += skb_queue_len(&txq->sk_list);
+ }
+ }
+
+ spin_unlock_bh(&rwnx_hw->tx_lock);
+ } else {
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_ps_disable(sta->sta_idx);
+#endif
+ spin_lock_bh(&rwnx_hw->tx_lock);
+ sta->ps.active = false;
+
+ if (is_multicast_sta(sta->sta_idx)) {
+ txq = rwnx_txq_sta_get(sta, 0, rwnx_hw);
+ txq->hwq = &rwnx_hw->hwq[RWNX_HWQ_BE];
+ txq->push_limit = 0;
+ } else {
+ int i;
+
+#ifdef CONFIG_MAC80211_TXQ
+ for (i = 0, txq = rwnx_txq_sta_get(sta, 0);
+ i < NX_NB_TXQ_PER_STA;
+ i++, txq = rwnx_txq_sta_get(sta, i))
+#elif defined(CONFIG_RWNX_FULLMAC)
+ for (i = 0, txq = rwnx_txq_sta_get(sta,
+ 0, rwnx_hw);
+ i < (is_multicast_sta(sta->sta_idx) ? 1 : NX_NB_TXQ_PER_STA);
+ i++, txq++)
+#endif
+ {
+ txq->push_limit = 0;
+ }
+ }
+
+ rwnx_txq_sta_start(sta, RWNX_TXQ_STOP_STA_PS, rwnx_hw);
+ spin_unlock_bh(&rwnx_hw->tx_lock);
+ tasklet_schedule(&rwnx_hw->task);
+ }
+}
+
+/**
+ * rwnx_ps_bh_traffic_req - Handle traffic request for STA in PS mode
+ *
+ * @rwnx_hw: Driver main data
+ * @sta: Sta which enters/leaves PS mode
+ * @pkt_req: number of pkt to push
+ * @ps_id: type of PS data requested (@LEGACY_PS_ID or @UAPSD_ID)
+ *
+ * This function will make sure that @pkt_req are pushed to fw
+ * whereas the STA is in PS mode.
+ * If request is 0, send all traffic
+ * If request is greater than available pkt, reduce request
+ * Note: request will also be reduce if txq credits are not available
+ *
+ * All counter updates are protected from TX path by taking tx_lock
+ *
+ * NOTE: _bh_ in function name indicates that this function is called
+ * from the bottom_half tasklet.
+ */
+void rwnx_ps_bh_traffic_req(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta,
+ u16 pkt_req, u8 ps_id)
+{
+ int pkt_ready_all;
+ struct rwnx_txq *txq;
+ int schedule = 0;
+
+ if (!sta->ps.active) {
+ AICWFDBG(LOGTRACE, "sta %pM is not in Power Save mode", sta->mac_addr);
+ return;
+ }
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_ps_traffic_req(sta, pkt_req, ps_id);
+#endif
+ spin_lock_bh(&rwnx_hw->tx_lock);
+
+ /*
+ * Fw may ask to stop a service period with PS_SP_INTERRUPTED. This only
+ * happens for p2p-go interface if NOA starts during a service period
+ */
+ if (pkt_req == PS_SP_INTERRUPTED && ps_id == UAPSD_ID) {
+ int tid;
+
+ sta->ps.sp_cnt[ps_id] = 0;
+#ifdef CONFIG_MAC80211_TXQ
+ for (tid = 0, txq = rwnx_txq_sta_get(sta, 0); tid < NX_NB_TXQ_PER_STA;
+ tid++, txq = rwnx_txq_sta_get(sta, tid))
+#elif defined(CONFIG_RWNX_FULLMAC)
+ for (tid = 0, txq = rwnx_txq_sta_get(sta, 0, rwnx_hw);
+ tid < (is_multicast_sta(sta->sta_idx) ? 1 : NX_NB_TXQ_PER_STA);
+ tid++, txq++)
+#endif
+ {
+ txq->push_limit = 0;
+ }
+
+ goto done;
+ }
+
+ pkt_ready_all = (sta->ps.pkt_ready[ps_id] - sta->ps.sp_cnt[ps_id]);
+
+ /*
+ * Don't start SP until previous one is finished or we don't have
+ * packet ready (which must not happen for U-APSD)
+ */
+ if (sta->ps.sp_cnt[ps_id] || pkt_ready_all <= 0)
+ goto done;
+
+ /* Adapt request to what is available. */
+ if (pkt_req == 0 || pkt_req > pkt_ready_all)
+ pkt_req = pkt_ready_all;
+
+ /* Reset the SP counter */
+ sta->ps.sp_cnt[ps_id] = 0;
+ schedule = 1;
+
+ /* "dispatch" the request between txq */
+ if (is_multicast_sta(sta->sta_idx)) {
+ txq = rwnx_txq_sta_get(sta, 0, rwnx_hw);
+ // if (txq->credits <= 0)
+ // goto done;
+ if (pkt_req > txq->credits)
+ pkt_req = txq->credits;
+ txq->push_limit = pkt_req;
+ sta->ps.sp_cnt[ps_id] = pkt_req;
+ rwnx_txq_add_to_hw_list(txq);
+ } else {
+ int i, tid;
+#ifdef CONFIG_RWNX_FULLMAC
+ for (i = 0, tid = nx_tid_prio[0],
+ txq = rwnx_txq_sta_get(sta, tid, rwnx_hw);
+ i < NX_NB_TID_PER_STA;
+ i++, tid = nx_tid_prio[i], txq = rwnx_txq_sta_get(sta, tid, rwnx_hw))
+#endif
+ {
+ u16 txq_len = skb_queue_len(&txq->sk_list);
+
+ if (txq->ps_id != ps_id)
+ continue;
+
+ if (txq_len > txq->credits)
+ txq_len = txq->credits;
+
+ if (txq_len == 0)
+ continue;
+
+ if (txq_len < pkt_req) {
+ /*
+ * Not enough pkt queued in this txq, add this
+ * txq to hwq list and process next txq
+ */
+ pkt_req -= txq_len;
+ txq->push_limit = txq_len;
+ sta->ps.sp_cnt[ps_id] += txq_len;
+ rwnx_txq_add_to_hw_list(txq);
+ } else {
+ /*
+ * Enough pkt in this txq to comlete the request
+ * add this txq to hwq list and stop processing txq
+ */
+ txq->push_limit = pkt_req;
+ sta->ps.sp_cnt[ps_id] += pkt_req;
+ rwnx_txq_add_to_hw_list(txq);
+ break;
+ }
+ }
+ }
+
+done:
+ spin_unlock_bh(&rwnx_hw->tx_lock);
+ if (schedule)
+ tasklet_schedule(&rwnx_hw->task);
+}
+
+/******************************************************************************
+ * TX functions
+ *****************************************************************************/
+#define PRIO_STA_NULL 0xAA
+
+static const int rwnx_down_hwq2tid[3] = {
+ [RWNX_HWQ_BK] = 2,
+ [RWNX_HWQ_BE] = 3,
+ [RWNX_HWQ_VI] = 5,
+};
+
+static void rwnx_downgrade_ac(struct rwnx_sta *sta, struct sk_buff *skb)
+{
+ s8 ac = rwnx_tid2hwq[skb->priority];
+
+ if (WARN(ac > RWNX_HWQ_VO, "Unexepcted ac %d for skb before downgrade", ac))
+ ac = RWNX_HWQ_VO;
+
+ while (sta->acm & BIT(ac)) {
+ if (ac == RWNX_HWQ_BK) {
+ skb->priority = 1;
+ return;
+ }
+ ac--;
+ skb->priority = rwnx_down_hwq2tid[ac];
+ }
+}
+
+u16 rwnx_select_txq(struct rwnx_vif *rwnx_vif, struct sk_buff *skb)
+{
+ struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+ struct wireless_dev *wdev = &rwnx_vif->wdev;
+ struct rwnx_sta *sta = NULL;
+ struct rwnx_txq *txq;
+ u16 netdev_queue;
+ bool tdls_mgmgt_frame = false;
+ int nx_bcmc_txq_ndev_idx = NX_BCMC_TXQ_NDEV_IDX;
+
+ if (g_rwnx_plat->sdiodev->rwnx_hw->chip_ops->is_old_ic)
+ nx_bcmc_txq_ndev_idx = NX_BCMC_TXQ_NDEV_IDX_FOR_OLD_IC;
+
+ switch (wdev->iftype) {
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ case NL80211_IFTYPE_P2P_CLIENT:
+#endif
+ case NL80211_IFTYPE_STATION: {
+ struct ethhdr *eth;
+
+ eth = (struct ethhdr *)skb->data;
+ if (eth->h_proto == cpu_to_be16(ETH_P_TDLS))
+ tdls_mgmgt_frame = true;
+
+ if (rwnx_vif->tdls_status == TDLS_LINK_ACTIVE &&
+ rwnx_vif->sta.tdls_sta &&
+ memcmp(eth->h_dest, rwnx_vif->sta.tdls_sta->mac_addr, ETH_ALEN) == 0)
+ sta = rwnx_vif->sta.tdls_sta;
+ else
+ sta = rwnx_vif->sta.ap;
+ break;
+ }
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ case NL80211_IFTYPE_AP_VLAN:
+ if (rwnx_vif->ap_vlan.sta_4a) {
+ sta = rwnx_vif->ap_vlan.sta_4a;
+ break;
+ }
+
+ /*
+ * AP_VLAN interface is not used for a 4A STA,
+ * fallback searching sta amongs all AP's clients
+ */
+ rwnx_vif = rwnx_vif->ap_vlan.master;
+ fallthrough;
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_P2P_GO: {
+ struct rwnx_sta *cur;
+ struct ethhdr *eth = (struct ethhdr *)skb->data;
+
+ if (is_multicast_ether_addr(eth->h_dest)) {
+ sta = &rwnx_hw->sta_table[rwnx_vif->ap.bcmc_index];
+ } else {
+ spin_lock_bh(&rwnx_vif->rwnx_hw->cb_lock);
+ list_for_each_entry(cur, &rwnx_vif->ap.sta_list, list) {
+ if (!memcmp(cur->mac_addr, eth->h_dest, ETH_ALEN)) {
+ sta = cur;
+ break;
+ }
+ }
+ spin_unlock_bh(&rwnx_vif->rwnx_hw->cb_lock);
+ }
+
+ break;
+ }
+ case NL80211_IFTYPE_MESH_POINT: {
+ struct ethhdr *eth = (struct ethhdr *)skb->data;
+
+ if (!rwnx_vif->is_resending) {
+ /*
+ * If ethernet source address is not the address of a mesh wireless
+ * interface, we are proxy for this address and have to inform the
+ * HW
+ */
+ if (memcmp(ð->h_source[0], &rwnx_vif->ndev->perm_addr[0],
+ ETH_ALEN)) {
+ /* Check if LMAC is already informed */
+ if (!rwnx_get_mesh_proxy_info(rwnx_vif, (u8 *)ð->h_source,
+ true)) {
+ rwnx_send_mesh_proxy_add_req(rwnx_hw, rwnx_vif,
+ (u8 *)ð->h_source);
+ }
+ }
+ }
+
+ if (is_multicast_ether_addr(eth->h_dest)) {
+ sta = &rwnx_hw->sta_table[rwnx_vif->ap.bcmc_index];
+ } else {
+ /* Path to be used */
+ struct rwnx_mesh_path *p_mesh_path = NULL;
+ struct rwnx_mesh_path *p_cur_path;
+ /* Check if destination is proxied by a peer Mesh STA */
+ struct rwnx_mesh_proxy *p_mesh_proxy =
+ rwnx_get_mesh_proxy_info(rwnx_vif, (u8 *)ð->h_dest, false);
+ /* Mesh Target address */
+ struct mac_addr *p_tgt_mac_addr;
+
+ if (p_mesh_proxy)
+ p_tgt_mac_addr = &p_mesh_proxy->proxy_addr;
+ else
+ p_tgt_mac_addr = (struct mac_addr *)ð->h_dest;
+
+ /* Look for path with provided target address */
+ list_for_each_entry(p_cur_path, &rwnx_vif->ap.mpath_list, list) {
+ if (!memcmp(&p_cur_path->tgt_mac_addr, p_tgt_mac_addr,
+ ETH_ALEN)) {
+ p_mesh_path = p_cur_path;
+ break;
+ }
+ }
+
+ if (p_mesh_path) {
+ sta = p_mesh_path->p_nhop_sta;
+ } else {
+ rwnx_send_mesh_path_create_req(rwnx_hw, rwnx_vif,
+ (u8 *)p_tgt_mac_addr);
+ }
+ }
+
+ break;
+ }
+#endif
+ default:
+ break;
+ }
+
+ if (sta && sta->qos) {
+ if (tdls_mgmgt_frame) {
+ skb_set_queue_mapping(skb,
+ NX_STA_NDEV_IDX(skb->priority, sta->sta_idx));
+ } else {
+ /* use the data classifier to determine what 802.1d tag the
+ * data frame has
+ */
+ skb->priority = cfg80211_classify8021d(skb, NULL) &
+ IEEE80211_QOS_CTL_TAG1D_MASK;
+ }
+ if (sta->acm)
+ rwnx_downgrade_ac(sta, skb);
+
+ txq = rwnx_txq_sta_get(sta, skb->priority, rwnx_hw);
+ netdev_queue = txq->ndev_idx;
+ } else if (sta) {
+ skb->priority = 0xFF;
+ txq = rwnx_txq_sta_get(sta, 0, rwnx_hw);
+ netdev_queue = txq->ndev_idx;
+ } else {
+ /*
+ * This packet will be dropped in xmit function, still need to select
+ * an active queue for xmit to be called. As it most likely to happen
+ * for AP interface, select BCMC queue
+ * (TODO: select another queue if BCMC queue is stopped)
+ */
+ skb->priority = PRIO_STA_NULL;
+ netdev_queue = nx_bcmc_txq_ndev_idx;
+ }
+
+#ifndef CONFIG_AIC8800_ONE_TXQ
+ WARN_ON_ONCE(netdev_queue >= NX_NB_NDEV_TXQ);
+#endif
+
+ return netdev_queue;
+}
+
+/**
+ * rwnx_set_more_data_flag - Update MORE_DATA flag in tx sw desc
+ *
+ * @rwnx_hw: Driver main data
+ * @sw_txhdr: Header for pkt to be pushed
+ *
+ * If STA is in PS mode
+ * - Set EOSP in case the packet is the last of the UAPSD service period
+ * - Set MORE_DATA flag if more pkt are ready for this sta
+ * - Update TIM if this is the last pkt buffered for this sta
+ *
+ * note: tx_lock already taken.
+ */
+static inline void rwnx_set_more_data_flag(struct rwnx_hw *rwnx_hw,
+ struct rwnx_sw_txhdr *sw_txhdr)
+{
+ struct rwnx_sta *sta = sw_txhdr->rwnx_sta;
+ struct rwnx_vif *vif = sw_txhdr->rwnx_vif;
+ struct rwnx_txq *txq = sw_txhdr->txq;
+
+ if (unlikely(sta->ps.active)) {
+ sta->ps.pkt_ready[txq->ps_id]--;
+ sta->ps.sp_cnt[txq->ps_id]--;
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_ps_push(sta);
+#endif
+ if ((txq->ps_id == UAPSD_ID ||
+ vif->wdev.iftype == NL80211_IFTYPE_MESH_POINT ||
+ sta->tdls.active) &&
+ !sta->ps.sp_cnt[txq->ps_id]) {
+ sw_txhdr->desc.host.flags |= TXU_CNTRL_EOSP;
+ }
+
+ if (sta->ps.pkt_ready[txq->ps_id])
+ sw_txhdr->desc.host.flags |= TXU_CNTRL_MORE_DATA;
+ else
+ rwnx_set_traffic_status(rwnx_hw, sta, false, txq->ps_id);
+ }
+}
+
+/**
+ * rwnx_get_tx_priv - Get STA and tid for one skb
+ *
+ * @rwnx_vif: vif ptr
+ * @skb: skb
+ * @tid: pointer updated with the tid to use for this skb
+ *
+ * @return: pointer on the destination STA (may be NULL)
+ *
+ * skb has already been parsed in rwnx_select_queue function
+ * simply re-read information form skb.
+ */
+static struct rwnx_sta *rwnx_get_tx_priv(struct rwnx_vif *rwnx_vif,
+ struct sk_buff *skb, u8 *tid)
+{
+ static struct rwnx_hw *rwnx_hw;
+ struct rwnx_sta *sta;
+ int sta_idx;
+ int nx_remote_sta_max = NX_REMOTE_STA_MAX;
+ int nx_bcmc_txq_ndev_idx = NX_BCMC_TXQ_NDEV_IDX;
+
+ if (g_rwnx_plat->sdiodev->rwnx_hw->chip_ops->is_old_ic) {
+ nx_remote_sta_max = NX_REMOTE_STA_MAX_FOR_OLD_IC;
+ nx_bcmc_txq_ndev_idx = NX_BCMC_TXQ_NDEV_IDX_FOR_OLD_IC;
+ }
+
+ rwnx_hw = rwnx_vif->rwnx_hw;
+ *tid = skb->priority;
+ if (unlikely(skb->priority == PRIO_STA_NULL))
+ return NULL;
+
+ int ndev_idx = skb_get_queue_mapping(skb);
+
+ if (ndev_idx == nx_bcmc_txq_ndev_idx)
+ sta_idx = nx_remote_sta_max + master_vif_idx(rwnx_vif);
+ else
+ sta_idx = ndev_idx / NX_NB_TID_PER_STA;
+ sta = &rwnx_hw->sta_table[sta_idx];
+
+ return sta;
+}
+
+/**
+ * rwnx_prep_tx - Prepare buffer for DMA transmission
+ *
+ * @rwnx_hw: Driver main data
+ * @txhdr: Tx descriptor
+ *
+ * Maps hw_txhdr and buffer data for transmission via DMA.
+ * - Data buffer with be downloaded by embebded side.
+ * - hw_txhdr will be uploaded by embedded side when buffer has been
+ * transmitted over the air.
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+static int rwnx_prep_tx(struct rwnx_hw *rwnx_hw, struct rwnx_txhdr *txhdr)
+{
+ return 0;
+}
+
+/**
+ * rwnx_tx_push - Push one packet to fw
+ *
+ * @rwnx_hw: Driver main data
+ * @txhdr: tx desc of the buffer to push
+ * @flags: push flags (see @rwnx_push_flags)
+ *
+ * Push one packet to fw. Sw desc of the packet has already been updated.
+ * Only MORE_DATA flag will be set if needed.
+ */
+void rwnx_tx_push(struct rwnx_hw *rwnx_hw, struct rwnx_txhdr *txhdr, int flags)
+{
+ struct rwnx_sw_txhdr *sw_txhdr = txhdr->sw_hdr;
+ struct sk_buff *skb = sw_txhdr->skb;
+ struct rwnx_txq *txq = sw_txhdr->txq;
+ u16 hw_queue = txq->hwq->id;
+ int user = 0;
+
+ lockdep_assert_held(&rwnx_hw->tx_lock);
+
+ /* RETRY flag is not always set so retest here */
+ if (txq->nb_retry) {
+ flags |= RWNX_PUSH_RETRY;
+ txq->nb_retry--;
+ if (txq->nb_retry == 0) {
+ WARN(skb != txq->last_retry_skb,
+ "last retry buffer is not the expected one");
+ txq->last_retry_skb = NULL;
+ }
+ } else if (!(flags & RWNX_PUSH_RETRY)) {
+ txq->pkt_sent++;
+ }
+#ifdef CONFIG_RWNX_AMSDUS_TX
+ if (txq->amsdu == sw_txhdr) {
+ WARN((flags & RWNX_PUSH_RETRY), "End A-MSDU on a retry");
+ rwnx_hw->stats.amsdus[sw_txhdr->amsdu.nb - 1].done++;
+ txq->amsdu = NULL;
+ } else if (!(flags & RWNX_PUSH_RETRY) &&
+ !(sw_txhdr->desc.host.flags & TXU_CNTRL_AMSDU)) {
+ rwnx_hw->stats.amsdus[0].done++;
+ }
+#endif /* CONFIG_RWNX_AMSDUS_TX */
+
+ /*
+ * Wait here to update hw_queue, as for multicast STA hwq may change
+ * between queue and push (because of PS)
+ */
+ sw_txhdr->hw_queue = hw_queue;
+
+ // sw_txhdr->desc.host.packet_addr = hw_queue; //use packet_addr field for
+ // hw_txq
+ sw_txhdr->desc.host.ac = hw_queue; // use ac field for hw_txq
+#ifdef CONFIG_RWNX_MUMIMO_TX
+ /* MU group is only selected during hwq processing */
+ sw_txhdr->desc.host.mumimo_info = txq->mumimo_info;
+ user = RWNX_TXQ_POS_ID(txq);
+#endif /* CONFIG_RWNX_MUMIMO_TX */
+
+ if (sw_txhdr->rwnx_sta) {
+ /* only for AP mode */
+ rwnx_set_more_data_flag(rwnx_hw, sw_txhdr);
+ }
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_push_desc(skb, sw_txhdr, flags);
+#endif
+
+ txq->pkt_pushed[user]++;
+
+ if (txq->push_limit)
+ txq->push_limit--;
+
+#ifdef AICWF_SDIO_SUPPORT
+ if (((sw_txhdr->desc.host.flags & TXU_CNTRL_MGMT) &&
+ (*(skb->data + sw_txhdr->headroom) == 0xd0 ||
+ *(skb->data + sw_txhdr->headroom) == 0x10 ||
+ *(skb->data + sw_txhdr->headroom) == 0x30)) ||
+ sw_txhdr->desc.host.ethertype == cpu_to_be16(ETH_P_PAE) ||
+ sw_txhdr->desc.host.ethertype == cpu_to_be16(0x88b4)) {
+ sw_txhdr->need_cfm = 1;
+ sw_txhdr->desc.host.hostid =
+ ((1 << 31) | rwnx_hw->sdio_env.txdesc_free_idx[0]);
+ aicwf_sdio_host_txdesc_push(&rwnx_hw->sdio_env, 0, (long)skb);
+ AICWFDBG(LOGINFO, "need cfm ethertype:%8x,user_idx=%d, skb=%p\n",
+ sw_txhdr->desc.host.ethertype,
+ rwnx_hw->sdio_env.txdesc_free_idx[0], skb);
+ } else {
+ sw_txhdr->need_cfm = 0;
+ sw_txhdr->desc.host.hostid = 0;
+
+ sw_txhdr->rwnx_vif->net_stats.tx_packets++;
+ sw_txhdr->rwnx_vif->net_stats.tx_bytes += sw_txhdr->frame_len;
+ rwnx_hw->stats.last_tx = jiffies;
+ }
+ aicwf_frame_tx((void *)(rwnx_hw->sdiodev), skb);
+#endif
+ rwnx_hw->stats.cfm_balance[hw_queue]++;
+}
+
+/**
+ * rwnx_tx_retry - Push an AMPDU pkt that need to be retried
+ *
+ * @rwnx_hw: Driver main data
+ * @skb: pkt to re-push
+ * @txhdr: tx desc of the pkt to re-push
+ * @sw_retry: Indicates if fw decide to retry this buffer
+ * (i.e. it has never been transmitted over the air)
+ *
+ * Called when a packet needs to be repushed to the firmware.
+ * First update sw descriptor and then queue it in the retry list.
+ */
+static void rwnx_tx_retry(struct rwnx_hw *rwnx_hw, struct sk_buff *skb,
+ struct rwnx_txhdr *txhdr, bool sw_retry)
+{
+ struct rwnx_sw_txhdr *sw_txhdr = txhdr->sw_hdr;
+ struct tx_cfm_tag *cfm = &txhdr->hw_hdr.cfm;
+ struct rwnx_txq *txq = sw_txhdr->txq;
+ int peek_off = offsetof(struct rwnx_hw_txhdr, cfm);
+ int peek_len = sizeof(((struct rwnx_hw_txhdr *)0)->cfm);
+
+ if (!sw_retry) {
+ /* update sw desc */
+ sw_txhdr->desc.host.flags |= TXU_CNTRL_RETRY;
+
+#ifdef CONFIG_RWNX_AMSDUS_TX
+ if (sw_txhdr->desc.host.flags & TXU_CNTRL_AMSDU)
+ rwnx_hw->stats.amsdus[sw_txhdr->amsdu.nb - 1].failed++;
+#endif
+ }
+
+ /* MORE_DATA will be re-set if needed when pkt will be repushed */
+ sw_txhdr->desc.host.flags &= ~TXU_CNTRL_MORE_DATA;
+
+ cfm->status.value = 0;
+ dma_sync_single_for_device(rwnx_hw->dev, sw_txhdr->dma_addr + peek_off,
+ peek_len, DMA_BIDIRECTIONAL);
+
+ txq->credits++;
+ if (txq->credits > 0)
+ rwnx_txq_start(txq, RWNX_TXQ_STOP_FULL);
+
+ /* Queue the buffer */
+ rwnx_txq_queue_skb(skb, txq, rwnx_hw, true);
+}
+
+#ifdef CONFIG_RWNX_AMSDUS_TX
+/* return size of subframe (including header) */
+static inline int rwnx_amsdu_subframe_length(struct ethhdr *eth, int eth_len)
+{
+ /*
+ * ethernet header is replaced with amdsu header that have the same size
+ * Only need to check if LLC/SNAP header will be added
+ */
+ int len = eth_len;
+
+ if (ntohs(eth->h_proto) >= ETH_P_802_3_MIN)
+ len += sizeof(rfc1042_header) + 2;
+ return len;
+}
+
+static inline bool rwnx_amsdu_is_aggregable(struct sk_buff *skb)
+{
+ /* need to add some check on buffer to see if it can be aggregated ? */
+ return true;
+}
+
+/*
+ * rwnx_amsdu_del_subframe_header - remove AMSDU header
+ *
+ * amsdu_txhdr: amsdu tx descriptor
+ *
+ * Move back the ethernet header at the "beginning" of the data buffer.
+ * (which has been moved in @rwnx_amsdu_add_subframe_header)
+ */
+static void rwnx_amsdu_del_subframe_header(struct rwnx_amsdu_txhdr *amsdu_txhdr)
+{
+ struct sk_buff *skb = amsdu_txhdr->skb;
+ struct ethhdr *eth;
+ u8 *pos;
+
+ pos = skb->data;
+ pos += sizeof(struct rwnx_amsdu_txhdr);
+ eth = (struct ethhdr *)pos;
+ pos += amsdu_txhdr->pad + sizeof(struct ethhdr);
+
+ if (ntohs(eth->h_proto) >= ETH_P_802_3_MIN)
+ pos += sizeof(rfc1042_header) + 2;
+
+ memmove(pos, eth, sizeof(*eth));
+ skb_pull(skb, (pos - skb->data));
+}
+
+/*
+ * rwnx_amsdu_add_subframe_header - Add AMSDU header and link subframe
+ *
+ * @rwnx_hw Driver main data
+ * @skb Buffer to aggregate
+ * @sw_txhdr Tx descriptor for the first A-MSDU subframe
+ *
+ * return 0 on success, -1 otherwise
+ *
+ * This functions Add A-MSDU header and LLC/SNAP header in the buffer
+ * and update sw_txhdr of the first subframe to link this buffer.
+ * If an error happens, the buffer will be queued as a normal buffer.
+ *
+ *
+ * Before After
+ * +-------------+ +-------------+
+ * | HEADROOM | | HEADROOM |
+ * | | +-------------+ <- data
+ * | | | amsdu_txhdr |
+ * | | | * pad size |
+ * | | +-------------+
+ * | | | ETH hdr | keep original eth hdr
+ * | | | | to restore it once transmitted
+ * | | +-------------+ <- packet_addr[x]
+ * | | | Pad |
+ * | | +-------------+
+ * data -> +-------------+ | AMSDU HDR |
+ * | ETH hdr | +-------------+
+ * | | | LLC/SNAP |
+ * +-------------+ +-------------+
+ * | DATA | | DATA |
+ * | | | |
+ * +-------------+ +-------------+
+ *
+ * Called with tx_lock hold
+ */
+static int rwnx_amsdu_add_subframe_header(struct rwnx_hw *rwnx_hw,
+ struct sk_buff *skb,
+ struct rwnx_sw_txhdr *sw_txhdr)
+{
+ struct rwnx_amsdu *amsdu = &sw_txhdr->amsdu;
+ struct rwnx_amsdu_txhdr *amsdu_txhdr;
+ struct ethhdr *amsdu_hdr, *eth = (struct ethhdr *)skb->data;
+ int headroom_need, map_len, msdu_len;
+ dma_addr_t dma_addr;
+ u8 *pos, *map_start;
+
+ msdu_len = skb->len - sizeof(*eth);
+ headroom_need = sizeof(*amsdu_txhdr) + amsdu->pad + sizeof(*amsdu_hdr);
+ if (ntohs(eth->h_proto) >= ETH_P_802_3_MIN) {
+ headroom_need += sizeof(rfc1042_header) + 2;
+ msdu_len += sizeof(rfc1042_header) + 2;
+ }
+
+ /* we should have enough headroom (checked in xmit) */
+ if (WARN_ON(skb_headroom(skb) < headroom_need))
+ return -1;
+
+ /* allocate headroom */
+ pos = skb_push(skb, headroom_need);
+ amsdu_txhdr = (struct rwnx_amsdu_txhdr *)pos;
+ pos += sizeof(*amsdu_txhdr);
+
+ /* move eth header */
+ memmove(pos, eth, sizeof(*eth));
+ eth = (struct ethhdr *)pos;
+ pos += sizeof(*eth);
+
+ /* Add padding from previous subframe */
+ map_start = pos;
+ memset(pos, 0, amsdu->pad);
+ pos += amsdu->pad;
+
+ /* Add AMSDU hdr */
+ amsdu_hdr = (struct ethhdr *)pos;
+ memcpy(amsdu_hdr->h_dest, eth->h_dest, ETH_ALEN);
+ memcpy(amsdu_hdr->h_source, eth->h_source, ETH_ALEN);
+ amsdu_hdr->h_proto = htons(msdu_len);
+ pos += sizeof(*amsdu_hdr);
+
+ if (ntohs(eth->h_proto) >= ETH_P_802_3_MIN) {
+ memcpy(pos, rfc1042_header, sizeof(rfc1042_header));
+ pos += sizeof(rfc1042_header);
+ }
+
+ /* MAP (and sync) memory for DMA */
+ map_len = msdu_len + amsdu->pad + sizeof(*amsdu_hdr);
+ dma_addr =
+ dma_map_single(rwnx_hw->dev, map_start, map_len, DMA_BIDIRECTIONAL);
+ if (WARN_ON(dma_mapping_error(rwnx_hw->dev, dma_addr))) {
+ pos -= sizeof(*eth);
+ memmove(pos, eth, sizeof(*eth));
+ skb_pull(skb, headroom_need);
+ return -1;
+ }
+
+ /* update amdsu_txhdr */
+ amsdu_txhdr->map_len = map_len;
+ amsdu_txhdr->dma_addr = dma_addr;
+ amsdu_txhdr->skb = skb;
+ amsdu_txhdr->pad = amsdu->pad;
+ amsdu_txhdr->msdu_len = msdu_len;
+
+ /* update rwnx_sw_txhdr (of the first subframe) */
+ WARN_ON_ONCE(amsdu->nb != sw_txhdr->desc.host.packet_cnt);
+
+ sw_txhdr->desc.host.packet_addr[amsdu->nb] = dma_addr;
+ sw_txhdr->desc.host.packet_len[amsdu->nb] = map_len;
+ sw_txhdr->desc.host.packet_cnt++;
+ amsdu->nb++;
+
+ amsdu->pad = AMSDU_PADDING(map_len - amsdu->pad);
+ list_add_tail(&amsdu_txhdr->list, &amsdu->hdrs);
+ amsdu->len += map_len;
+
+ trace_amsdu_subframe(sw_txhdr);
+ return 0;
+}
+
+/*
+ * rwnx_amsdu_add_subframe - Add this buffer as an A-MSDU subframe if possible
+ *
+ * @rwnx_hw Driver main data
+ * @skb Buffer to aggregate if possible
+ * @sta Destination STA
+ * @txq sta's txq used for this buffer
+ *
+ * Tyr to aggregate the buffer in an A-MSDU. If it succeed then the
+ * buffer is added as a new A-MSDU subframe with AMSDU and LLC/SNAP
+ * headers added (so FW won't have to modify this subframe).
+ *
+ * To be added as subframe :
+ * - sta must allow amsdu
+ * - buffer must be aggregable (to be defined)
+ * - at least one other aggregable buffer is pending in the queue
+ * or an a-msdu (with enough free space) is currently in progress
+ *
+ * returns true if buffer has been added as A-MDSP subframe, false otherwise
+ *
+ */
+static bool rwnx_amsdu_add_subframe(struct rwnx_hw *rwnx_hw,
+ struct sk_buff *skb, struct rwnx_sta *sta,
+ struct rwnx_txq *txq)
+{
+ bool res = false;
+ struct ethhdr *eth;
+
+ /* immediately return if amsdu are not allowed for this sta */
+ if (!txq->amsdu_len || rwnx_hw->mod_params->amsdu_maxnb < 2 ||
+ !rwnx_amsdu_is_aggregable(skb))
+ return false;
+
+ spin_lock_bh(&rwnx_hw->tx_lock);
+ if (txq->amsdu) {
+ /*
+ * aggreagation already in progress, add this buffer if enough space
+ * available, otherwise end the current amsdu
+ */
+ struct rwnx_sw_txhdr *sw_txhdr = txq->amsdu;
+
+ eth = (struct ethhdr *)(skb->data);
+
+ if (((sw_txhdr->amsdu.len + sw_txhdr->amsdu.pad +
+ rwnx_amsdu_subframe_length(eth, skb->len)) > txq->amsdu_len) ||
+ rwnx_amsdu_add_subframe_header(rwnx_hw, skb, sw_txhdr)) {
+ txq->amsdu = NULL;
+ goto end;
+ }
+
+ if (sw_txhdr->amsdu.nb >= rwnx_hw->mod_params->amsdu_maxnb) {
+ rwnx_hw->stats.amsdus[sw_txhdr->amsdu.nb - 1].done++;
+ /* max number of subframes reached */
+ txq->amsdu = NULL;
+ }
+ } else {
+ /*
+ * Check if a new amsdu can be started with the previous buffer
+ * (if any) and this one
+ */
+ struct sk_buff *skb_prev = skb_peek_tail(&txq->sk_list);
+ struct rwnx_txhdr *txhdr;
+ struct rwnx_sw_txhdr *sw_txhdr;
+ int len1, len2;
+
+ if (!skb_prev || !rwnx_amsdu_is_aggregable(skb_prev))
+ goto end;
+
+ txhdr = (struct rwnx_txhdr *)skb_prev->data;
+ sw_txhdr = txhdr->sw_hdr;
+ if (sw_txhdr->amsdu.len ||
+ (sw_txhdr->desc.host.flags & TXU_CNTRL_RETRY))
+ /* previous buffer is already a complete amsdu or a retry */
+ goto end;
+
+ eth = (struct ethhdr *)(skb_prev->data + sw_txhdr->headroom);
+ len1 = rwnx_amsdu_subframe_length(eth,
+ (sw_txhdr->frame_len + sizeof(struct ethhdr)));
+
+ eth = (struct ethhdr *)(skb->data);
+ len2 = rwnx_amsdu_subframe_length(eth, skb->len);
+
+ if (len1 + AMSDU_PADDING(len1) + len2 > txq->amsdu_len)
+ /* not enough space to aggregate those two buffers */
+ goto end;
+
+ /*
+ * Add subframe header.
+ * Note: Fw will take care of adding AMDSU header for the first
+ * subframe while generating 802.11 MAC header
+ */
+ INIT_LIST_HEAD(&sw_txhdr->amsdu.hdrs);
+ sw_txhdr->amsdu.len = len1;
+ sw_txhdr->amsdu.nb = 1;
+ sw_txhdr->amsdu.pad = AMSDU_PADDING(len1);
+ if (rwnx_amsdu_add_subframe_header(rwnx_hw, skb, sw_txhdr))
+ goto end;
+
+ sw_txhdr->desc.host.flags |= TXU_CNTRL_AMSDU;
+
+ if (sw_txhdr->amsdu.nb < rwnx_hw->mod_params->amsdu_maxnb)
+ txq->amsdu = sw_txhdr;
+ else
+ rwnx_hw->stats.amsdus[sw_txhdr->amsdu.nb - 1].done++;
+ }
+
+ res = true;
+
+end:
+ spin_unlock_bh(&rwnx_hw->tx_lock);
+ return res;
+}
+#endif /* CONFIG_RWNX_AMSDUS_TX */
+
+#ifdef CONFIG_AIC8800_FILTER_TCP_ACK
+/* return:
+ * 0, msg buf freed by the real driver
+ * others, skb need free by the caller,remember not use msg->skb!
+ */
+
+int intf_tx(struct rwnx_hw *priv, struct msg_buf *msg)
+{
+ struct rwnx_vif *rwnx_vif = msg->rwnx_vif;
+ struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+ struct rwnx_txhdr *txhdr;
+ struct rwnx_sw_txhdr *sw_txhdr;
+ struct txdesc_api *desc;
+ struct rwnx_sta *sta;
+ struct rwnx_txq *txq;
+ int headroom;
+ // int max_headroom;
+ int hdr_pads;
+
+ u16 frame_len;
+ u8 tid;
+ struct sk_buff *skb = msg->skb;
+ struct ethhdr eth_t;
+
+ move_tcpack_msg(rwnx_hw, msg);
+ kfree(msg);
+
+ memcpy(ð_t, skb->data, sizeof(struct ethhdr));
+
+ /* Get the STA id and TID information */
+ sta = rwnx_get_tx_priv(rwnx_vif, skb, &tid);
+ if (!sta)
+ goto free;
+
+ txq = rwnx_txq_sta_get(sta, tid, rwnx_hw);
+ if (txq->idx == TXQ_INACTIVE)
+ goto free;
+
+#ifdef CONFIG_RWNX_AMSDUS_TX
+ if (rwnx_amsdu_add_subframe(rwnx_hw, skb, sta, txq))
+ return NETDEV_TX_OK;
+#endif
+
+ /* Retrieve the pointer to the Ethernet data */
+ // eth = (struct ethhdr *)skb->data;
+
+ skb_pull(skb, 14);
+ // hdr_pads = RWNX_SWTXHDR_ALIGN_PADS((long)eth);
+ hdr_pads = RWNX_SWTXHDR_ALIGN_PADS((long)skb->data);
+ headroom = sizeof(struct rwnx_txhdr) + hdr_pads;
+
+ skb_push(skb, headroom);
+
+ txhdr = (struct rwnx_txhdr *)skb->data;
+ sw_txhdr = kmem_cache_alloc(rwnx_hw->sw_txhdr_cache, GFP_ATOMIC);
+ if (unlikely(!sw_txhdr))
+ goto free;
+ txhdr->sw_hdr = sw_txhdr;
+ desc = &sw_txhdr->desc;
+
+ frame_len = (u16)skb->len - headroom; // - sizeof(*eth);
+
+ sw_txhdr->txq = txq;
+ sw_txhdr->frame_len = frame_len;
+ sw_txhdr->rwnx_sta = sta;
+ sw_txhdr->rwnx_vif = rwnx_vif;
+ sw_txhdr->skb = skb;
+ sw_txhdr->headroom = headroom;
+ sw_txhdr->map_len = skb->len - offsetof(struct rwnx_txhdr, hw_hdr);
+
+#ifdef CONFIG_RWNX_AMSDUS_TX
+ sw_txhdr->amsdu.len = 0;
+ sw_txhdr->amsdu.nb = 0;
+#endif
+ // Fill-in the descriptor
+ memcpy(&desc->host.eth_dest_addr, eth_t.h_dest, ETH_ALEN);
+ memcpy(&desc->host.eth_src_addr, eth_t.h_source, ETH_ALEN);
+ desc->host.ethertype = eth_t.h_proto;
+ desc->host.staid = sta->sta_idx;
+ desc->host.tid = tid;
+ desc->host.vif_idx = rwnx_vif->vif_index;
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ if (unlikely(rwnx_vif->wdev.iftype == NL80211_IFTYPE_AP_VLAN))
+ desc->host.vif_idx = rwnx_vif->ap_vlan.master->vif_index;
+#endif
+
+ if (rwnx_vif->use_4addr && sta->sta_idx < NX_REMOTE_STA_MAX)
+ desc->host.flags = TXU_CNTRL_USE_4ADDR;
+ else
+ desc->host.flags = 0;
+
+ if (rwnx_vif->tdls_status == TDLS_LINK_ACTIVE && rwnx_vif->sta.tdls_sta &&
+ memcmp(desc->host.eth_dest_addr.array,
+ rwnx_vif->sta.tdls_sta->mac_addr, ETH_ALEN) == 0) {
+ desc->host.flags |= TXU_CNTRL_TDLS;
+ rwnx_vif->sta.tdls_sta->tdls.last_tid = desc->host.tid;
+ // rwnx_vif->sta.tdls_sta->tdls.last_sn = desc->host.sn;
+ }
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_MESH_POINT) {
+ if (rwnx_vif->is_resending)
+ desc->host.flags |= TXU_CNTRL_MESH_FWD;
+ }
+#endif
+
+#ifdef CONFIG_RWNX_SPLIT_TX_BUF
+ desc->host.packet_len[0] = frame_len;
+#else
+ desc->host.packet_len = frame_len;
+#endif
+
+ txhdr->hw_hdr.cfm.status.value = 0;
+
+ if (unlikely(rwnx_prep_tx(rwnx_hw, txhdr))) {
+ kmem_cache_free(rwnx_hw->sw_txhdr_cache, sw_txhdr);
+ skb_pull(skb, headroom);
+ dev_kfree_skb_any(skb);
+ return NETDEV_TX_BUSY;
+ }
+
+ /* Fill-in TX descriptor */
+ desc->host.hostid = sw_txhdr->dma_addr;
+
+ spin_lock_bh(&rwnx_hw->tx_lock);
+ if (rwnx_txq_queue_skb(skb, txq, rwnx_hw, false))
+ rwnx_hwq_process(rwnx_hw, txq->hwq);
+ spin_unlock_bh(&rwnx_hw->tx_lock);
+
+ return 0; // NETDEV_TX_OK
+
+free:
+ dev_kfree_skb_any(skb);
+
+ return 0; // NETDEV_TX_OK
+}
+#endif
+
+/*
+ * netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb,
+ * struct net_device *dev);
+ * Called when a packet needs to be transmitted.
+ * Must return NETDEV_TX_OK , NETDEV_TX_BUSY.
+ * (can also return NETDEV_TX_LOCKED if NETIF_F_LLTX)
+ *
+ * - Initialize the descriptor for this pkt (stored in skb before data)
+ * - Push the pkt in the corresponding Txq
+ * - If possible (i.e. credit available and not in PS) the pkt is pushed
+ * to fw
+ */
+netdev_tx_t rwnx_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+ struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+ struct rwnx_txhdr *txhdr;
+ struct rwnx_sw_txhdr *sw_txhdr;
+ struct txdesc_api *desc;
+ struct rwnx_sta *sta;
+ struct rwnx_txq *txq;
+ int headroom;
+ int max_headroom;
+ int hdr_pads;
+
+ u16 frame_len;
+ u8 tid;
+
+ struct ethhdr eth_t;
+#ifdef CONFIG_AIC8800_FILTER_TCP_ACK
+ struct msg_buf *msgbuf;
+#endif
+
+#ifdef CONFIG_AIC8800_ONE_TXQ
+ skb->queue_mapping = rwnx_select_txq(rwnx_vif, skb);
+#endif
+
+ sk_pacing_shift_update(skb->sk, rwnx_hw->tcp_pacing_shift);
+ max_headroom = sizeof(struct rwnx_txhdr);
+
+ /* check whether the current skb can be used */
+ if (skb_shared(skb) || (skb_headroom(skb) < max_headroom) ||
+ (skb_cloned(skb) && (dev->priv_flags & IFF_BRIDGE_PORT))) {
+ struct sk_buff *newskb =
+ skb_copy_expand(skb, max_headroom, 0, GFP_ATOMIC);
+ if (unlikely(!newskb))
+ goto free;
+
+ dev_kfree_skb_any(skb);
+
+ skb = newskb;
+ }
+
+ //if (skb->priority < 3)
+ // skb->priority = 0;
+
+#ifdef CONFIG_AIC8800_FILTER_TCP_ACK
+ if (skb->len <= MAX_TCP_ACK) {
+ msgbuf = intf_tcp_alloc_msg();
+ if (msgbuf) {
+ msgbuf->rwnx_vif = rwnx_vif;
+ msgbuf->skb = skb;
+ if (filter_send_tcp_ack(rwnx_hw, msgbuf, skb->data,
+ skb->len))
+ return NETDEV_TX_OK;
+ move_tcpack_msg(rwnx_hw, msgbuf);
+ kfree(msgbuf);
+ }
+ }
+#endif
+
+ memcpy(ð_t, skb->data, sizeof(struct ethhdr));
+
+ /* Get the STA id and TID information */
+ sta = rwnx_get_tx_priv(rwnx_vif, skb, &tid);
+ if (!sta)
+ goto free;
+
+ txq = rwnx_txq_sta_get(sta, tid, rwnx_hw);
+ if (txq->idx == TXQ_INACTIVE)
+ goto free;
+
+#ifdef CONFIG_RWNX_AMSDUS_TX
+ if (rwnx_amsdu_add_subframe(rwnx_hw, skb, sta, txq))
+ return NETDEV_TX_OK;
+#endif
+
+ /* Retrieve the pointer to the Ethernet data */
+ // eth = (struct ethhdr *)skb->data;
+
+ skb_pull(skb, 14);
+ // hdr_pads = RWNX_SWTXHDR_ALIGN_PADS((long)eth);
+ hdr_pads = RWNX_SWTXHDR_ALIGN_PADS((long)skb->data);
+ headroom = sizeof(struct rwnx_txhdr) + hdr_pads;
+
+ skb_push(skb, headroom);
+
+ txhdr = (struct rwnx_txhdr *)skb->data;
+ sw_txhdr = kmem_cache_alloc(rwnx_hw->sw_txhdr_cache, GFP_ATOMIC);
+ if (unlikely(!sw_txhdr))
+ goto free;
+ txhdr->sw_hdr = sw_txhdr;
+ desc = &sw_txhdr->desc;
+
+ frame_len = (u16)skb->len - headroom; // - sizeof(*eth);
+
+ sw_txhdr->txq = txq;
+ sw_txhdr->frame_len = frame_len;
+ sw_txhdr->rwnx_sta = sta;
+ sw_txhdr->rwnx_vif = rwnx_vif;
+ sw_txhdr->skb = skb;
+ sw_txhdr->headroom = headroom;
+ sw_txhdr->map_len = skb->len - offsetof(struct rwnx_txhdr, hw_hdr);
+
+#ifdef CONFIG_RWNX_AMSDUS_TX
+ sw_txhdr->amsdu.len = 0;
+ sw_txhdr->amsdu.nb = 0;
+#endif
+ // Fill-in the descriptor
+ memcpy(&desc->host.eth_dest_addr, eth_t.h_dest, ETH_ALEN);
+ memcpy(&desc->host.eth_src_addr, eth_t.h_source, ETH_ALEN);
+ desc->host.ethertype = eth_t.h_proto;
+ desc->host.staid = sta->sta_idx;
+ desc->host.tid = tid;
+ desc->host.vif_idx = rwnx_vif->vif_index;
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ if (unlikely(rwnx_vif->wdev.iftype == NL80211_IFTYPE_AP_VLAN))
+ desc->host.vif_idx = rwnx_vif->ap_vlan.master->vif_index;
+#endif
+
+ if (rwnx_vif->use_4addr && sta->sta_idx < NX_REMOTE_STA_MAX)
+ desc->host.flags = TXU_CNTRL_USE_4ADDR;
+ else
+ desc->host.flags = 0;
+
+ if (rwnx_vif->tdls_status == TDLS_LINK_ACTIVE && rwnx_vif->sta.tdls_sta &&
+ (memcmp(desc->host.eth_dest_addr.array,
+ rwnx_vif->sta.tdls_sta->mac_addr, ETH_ALEN) == 0)) {
+ desc->host.flags |= TXU_CNTRL_TDLS;
+ rwnx_vif->sta.tdls_sta->tdls.last_tid = desc->host.tid;
+ // rwnx_vif->sta.tdls_sta->tdls.last_sn = desc->host.sn;
+ }
+
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_MESH_POINT) {
+ if (rwnx_vif->is_resending)
+ desc->host.flags |= TXU_CNTRL_MESH_FWD;
+ }
+#endif
+
+#ifdef CONFIG_RWNX_SPLIT_TX_BUF
+ desc->host.packet_len[0] = frame_len;
+#else
+ desc->host.packet_len = frame_len;
+#endif
+
+ txhdr->hw_hdr.cfm.status.value = 0;
+
+ if (unlikely(rwnx_prep_tx(rwnx_hw, txhdr))) {
+ kmem_cache_free(rwnx_hw->sw_txhdr_cache, sw_txhdr);
+ skb_pull(skb, headroom);
+ dev_kfree_skb_any(skb);
+ return NETDEV_TX_BUSY;
+ }
+
+ /* Fill-in TX descriptor */
+ desc->host.hostid = sw_txhdr->dma_addr;
+
+ spin_lock_bh(&rwnx_hw->tx_lock);
+ if (rwnx_txq_queue_skb(skb, txq, rwnx_hw, false))
+ rwnx_hwq_process(rwnx_hw, txq->hwq);
+ spin_unlock_bh(&rwnx_hw->tx_lock);
+
+ return NETDEV_TX_OK;
+
+free:
+ dev_kfree_skb_any(skb);
+
+ return NETDEV_TX_OK;
+}
+
+/**
+ * rwnx_start_mgmt_xmit - Transmit a management frame
+ *
+ * @vif: Vif that send the frame
+ * @sta: Destination of the frame. May be NULL if the destiantion is unknown
+ * to the AP.
+ * @params: Mgmt frame parameters
+ * @offchan: Indicate whether the frame must be send via the offchan TXQ.
+ * (is is redundant with params->offchan ?)
+ * @cookie: updated with a unique value to identify the frame with upper layer
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+
+int rwnx_start_mgmt_xmit(struct rwnx_vif *vif, struct rwnx_sta *sta,
+ struct cfg80211_mgmt_tx_params *params, bool offchan,
+ u64 *cookie)
+{
+ struct rwnx_hw *rwnx_hw = vif->rwnx_hw;
+ struct rwnx_txhdr *txhdr;
+ struct rwnx_sw_txhdr *sw_txhdr;
+ struct txdesc_api *desc;
+ struct sk_buff *skb;
+ u16 frame_len, headroom;
+ u8 *data;
+ int nx_off_chan_txq_idx = NX_OFF_CHAN_TXQ_IDX;
+ struct rwnx_txq *txq;
+ bool robust;
+ const u8 *buf = params->buf;
+ size_t len = params->len;
+ bool no_cck = params->no_cck;
+
+ AICWFDBG(LOGDEBUG, "mgmt xmit %x %x ", buf[0], buf[1]);
+
+ if (g_rwnx_plat->sdiodev->rwnx_hw->chip_ops->is_old_ic)
+ nx_off_chan_txq_idx = NX_OFF_CHAN_TXQ_IDX_FOR_OLD_IC;
+
+ headroom = sizeof(struct rwnx_txhdr);
+ frame_len = len;
+
+ /* Set TID and Queues indexes */
+ if (sta) {
+ txq = rwnx_txq_sta_get(sta, 8, rwnx_hw);
+ } else {
+ if (offchan)
+ txq = &rwnx_hw->txq[nx_off_chan_txq_idx];
+ else
+ txq = rwnx_txq_vif_get(vif, NX_UNK_TXQ_TYPE);
+ }
+
+ /* Ensure that TXQ is active */
+ if (txq->idx == TXQ_INACTIVE) {
+ netdev_dbg(vif->ndev, "TXQ inactive\n");
+ return -EBUSY;
+ }
+
+ /*
+ * Create a SK Buff object that will contain the provided data
+ */
+ skb = dev_alloc_skb(headroom + frame_len);
+
+ if (!skb)
+ return -ENOMEM;
+
+ *cookie = (unsigned long)skb;
+
+ /*
+ * Move skb->data pointer in order to reserve room for rwnx_txhdr
+ * headroom value will be equal to sizeof(struct rwnx_txhdr)
+ */
+ skb_reserve(skb, headroom);
+
+ /*
+ * Extend the buffer data area in order to contain the provided packet
+ * len value (for skb) will be equal to param->len
+ */
+ data = skb_put(skb, frame_len);
+ /* Copy the provided data */
+ memcpy(data, buf, frame_len);
+
+ robust = ieee80211_is_robust_mgmt_frame(skb);
+
+ /* Update CSA counter if present */
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ if (unlikely(params->n_csa_offsets) &&
+ vif->wdev.iftype == NL80211_IFTYPE_AP && vif->ap.csa) {
+ int i;
+
+ data = skb->data;
+ for (i = 0; i < params->n_csa_offsets; i++)
+ data[params->csa_offsets[i]] = vif->ap.csa->count;
+ }
+#endif
+
+ /*
+ * Go back to the beginning of the allocated data area
+ * skb->data pointer will move backward
+ */
+ skb_push(skb, headroom);
+
+ /* Fill the TX Header */
+ txhdr = (struct rwnx_txhdr *)skb->data;
+
+ txhdr->hw_hdr.cfm.status.value = 0;
+
+ /* Fill the SW TX Header */
+ sw_txhdr = kmem_cache_alloc(rwnx_hw->sw_txhdr_cache, GFP_ATOMIC);
+ if (unlikely(!sw_txhdr)) {
+ dev_kfree_skb(skb);
+ return -ENOMEM;
+ }
+ txhdr->sw_hdr = sw_txhdr;
+
+ sw_txhdr->txq = txq;
+ sw_txhdr->frame_len = frame_len;
+ sw_txhdr->rwnx_sta = sta;
+ sw_txhdr->rwnx_vif = vif;
+ sw_txhdr->skb = skb;
+ sw_txhdr->headroom = headroom;
+ sw_txhdr->map_len = skb->len - offsetof(struct rwnx_txhdr, hw_hdr);
+#ifdef CONFIG_RWNX_AMSDUS_TX
+ sw_txhdr->amsdu.len = 0;
+ sw_txhdr->amsdu.nb = 0;
+#endif
+
+ /* Fill the Descriptor to be provided to the MAC SW */
+ desc = &sw_txhdr->desc;
+
+ desc->host.ethertype = 0;
+ desc->host.staid = (sta) ? sta->sta_idx : 0xFF;
+ desc->host.vif_idx = vif->vif_index;
+ desc->host.tid = 0xFF;
+ desc->host.flags = TXU_CNTRL_MGMT;
+ if (robust)
+ desc->host.flags |= TXU_CNTRL_MGMT_ROBUST;
+
+#ifdef CONFIG_RWNX_SPLIT_TX_BUF
+ desc->host.packet_len[0] = frame_len;
+#else
+ desc->host.packet_len = frame_len;
+#endif
+
+ if (no_cck)
+ desc->host.flags |= TXU_CNTRL_MGMT_NO_CCK;
+
+ /* Get DMA Address */
+ if (unlikely(rwnx_prep_tx(rwnx_hw, txhdr))) {
+ kmem_cache_free(rwnx_hw->sw_txhdr_cache, sw_txhdr);
+ dev_kfree_skb(skb);
+ return -EBUSY;
+ }
+
+ desc->host.hostid = sw_txhdr->dma_addr;
+
+ spin_lock_bh(&rwnx_hw->tx_lock);
+ if (rwnx_txq_queue_skb(skb, txq, rwnx_hw, false))
+ rwnx_hwq_process(rwnx_hw, txq->hwq);
+ spin_unlock_bh(&rwnx_hw->tx_lock);
+
+ return 0;
+}
+
+/**
+ * rwnx_txdatacfm - FW callback for TX confirmation
+ * @pthis: Main driver data
+ * @host_id: Socket buffer associated with the confirmation
+ *
+ * Context: Called with the TX lock held.
+ * Return: 0 when the confirmation was handled, or a negative error code.
+ */
+int rwnx_txdatacfm(void *pthis, void *host_id)
+{
+ struct rwnx_hw *rwnx_hw = (struct rwnx_hw *)pthis;
+ struct sk_buff *skb = host_id;
+ struct rwnx_txhdr *txhdr;
+ union rwnx_hw_txstatus rwnx_txst;
+ struct rwnx_sw_txhdr *sw_txhdr;
+ struct rwnx_hwq *hwq;
+ struct rwnx_txq *txq;
+ int headroom;
+
+ txhdr = (struct rwnx_txhdr *)skb->data;
+ sw_txhdr = txhdr->sw_hdr;
+
+ /* Read status in the TX control header */
+ rwnx_txst = txhdr->hw_hdr.cfm.status;
+
+ /* Check status in the header. If status is null, it means that the buffer
+ * was not transmitted and we have to return immediately
+ */
+ if (rwnx_txst.value == 0)
+ return -1;
+
+#ifdef AICWF_SDIO_SUPPORT
+ if (rwnx_hw->sdiodev->bus_if->state == BUS_DOWN_ST) {
+ headroom = sw_txhdr->headroom;
+ kmem_cache_free(rwnx_hw->sw_txhdr_cache, sw_txhdr);
+ skb_pull(skb, headroom);
+ consume_skb(skb);
+ return 0;
+ }
+#endif
+
+ txq = sw_txhdr->txq;
+ /* don't use txq->hwq as it may have changed between push and confirm */
+ hwq = &rwnx_hw->hwq[sw_txhdr->hw_queue];
+ rwnx_txq_confirm_any(rwnx_hw, txq, hwq, sw_txhdr);
+
+ /* Update txq and HW queue credits */
+ if (sw_txhdr->desc.host.flags & TXU_CNTRL_MGMT) {
+ AICWFDBG(LOGDEBUG,
+ "done=%d retry_required=%d sw_retry_required=%d acknowledged=%d\n",
+ rwnx_txst.tx_done, rwnx_txst.retry_required,
+ rwnx_txst.sw_retry_required, rwnx_txst.acknowledged);
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_mgmt_cfm(sw_txhdr->rwnx_vif->vif_index,
+ (sw_txhdr->rwnx_sta) ? sw_txhdr->rwnx_sta->sta_idx : 0xFF,
+ rwnx_txst.acknowledged);
+#endif
+ /* Confirm transmission to CFG80211 */
+ cfg80211_mgmt_tx_status(&sw_txhdr->rwnx_vif->wdev, (unsigned long)skb,
+ (skb->data + sw_txhdr->headroom),
+ sw_txhdr->frame_len, rwnx_txst.acknowledged,
+ GFP_ATOMIC);
+ } else if ((txq->idx != TXQ_INACTIVE) &&
+ (rwnx_txst.retry_required || rwnx_txst.sw_retry_required)) {
+ bool sw_retry = (rwnx_txst.sw_retry_required) ? true : false;
+
+ /* Reset the status */
+ txhdr->hw_hdr.cfm.status.value = 0;
+
+ /* The confirmed packet was part of an AMPDU and not acked
+ * correctly, so reinject it in the TX path to be retried
+ */
+ rwnx_tx_retry(rwnx_hw, skb, txhdr, sw_retry);
+ return 0;
+ }
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_skb_confirm(skb, txq, hwq, &txhdr->hw_hdr.cfm);
+#endif
+ /* STA may have disconnect (and txq stopped) when buffers were stored
+ * in fw. In this case do nothing when they're returned
+ */
+ if (txq->idx != TXQ_INACTIVE) {
+ /* continue service period */
+ if (unlikely(txq->push_limit && !rwnx_txq_is_full(txq)))
+ rwnx_txq_add_to_hw_list(txq);
+ }
+
+ if (txhdr->hw_hdr.cfm.ampdu_size)
+ rwnx_hw->stats.ampdus_tx[txhdr->hw_hdr.cfm.ampdu_size - 1]++;
+
+#ifdef CONFIG_RWNX_AMSDUS_TX
+ txq->amsdu_len = txhdr->hw_hdr.cfm.amsdu_size;
+#endif
+
+ /* Update statistics */
+ sw_txhdr->rwnx_vif->net_stats.tx_packets++;
+ sw_txhdr->rwnx_vif->net_stats.tx_bytes += sw_txhdr->frame_len;
+
+ /* Release SKBs */
+#ifdef CONFIG_RWNX_AMSDUS_TX
+ if (sw_txhdr->desc.host.flags & TXU_CNTRL_AMSDU) {
+ struct rwnx_amsdu_txhdr *amsdu_txhdr;
+
+ list_for_each_entry(amsdu_txhdr, &sw_txhdr->amsdu.hdrs, list) {
+ rwnx_amsdu_del_subframe_header(amsdu_txhdr);
+ consume_skb(amsdu_txhdr->skb);
+ }
+ }
+#endif /* CONFIG_RWNX_AMSDUS_TX */
+
+ headroom = sw_txhdr->headroom;
+ kmem_cache_free(rwnx_hw->sw_txhdr_cache, sw_txhdr);
+ skb_pull(skb, headroom);
+ consume_skb(skb);
+
+ return 0;
+}
+
+/**
+ * rwnx_txq_credit_update - Update credit for one txq
+ *
+ * @rwnx_hw: Driver main data
+ * @sta_idx: STA idx
+ * @tid: TID
+ * @update: offset to apply in txq credits
+ *
+ * Called when fw send ME_TX_CREDITS_UPDATE_IND message.
+ * Apply @update to txq credits, and stop/start the txq if needed
+ */
+void rwnx_txq_credit_update(struct rwnx_hw *rwnx_hw, int sta_idx, u8 tid,
+ s8 update)
+{
+ struct rwnx_sta *sta = &rwnx_hw->sta_table[sta_idx];
+ struct rwnx_txq *txq;
+
+ txq = rwnx_txq_sta_get(sta, tid, rwnx_hw);
+
+ spin_lock_bh(&rwnx_hw->tx_lock);
+
+ if (txq->idx != TXQ_INACTIVE) {
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_credit_update(txq, update);
+#endif
+ if (txq->credits <= 0)
+ rwnx_txq_stop(txq, RWNX_TXQ_STOP_FULL);
+ else
+ rwnx_txq_start(txq, RWNX_TXQ_STOP_FULL);
+ }
+
+ spin_unlock_bh(&rwnx_hw->tx_lock);
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tx.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tx.h
new file mode 100644
index 0000000000000..d61b55bbf8276
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tx.h
@@ -0,0 +1,176 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_tx.h
+ *
+ * @brief tx handle
+ *
+ ******************************************************************************
+ */
+#ifndef _RWNX_TX_H_
+#define _RWNX_TX_H_
+
+#include "hal_desc.h"
+#include "ipc_shared.h"
+#include "lmac_types.h"
+#include "rwnx_txq.h"
+#include "aicwf_tcp_ack.h"
+#include <linux/ieee80211.h>
+#include <linux/netdevice.h>
+#include <net/cfg80211.h>
+
+#define RWNX_HWQ_BK 0
+#define RWNX_HWQ_BE 1
+#define RWNX_HWQ_VI 2
+#define RWNX_HWQ_VO 3
+#define RWNX_HWQ_BCMC 4
+#define RWNX_HWQ_NB NX_TXQ_CNT
+#define RWNX_HWQ_ALL_ACS (RWNX_HWQ_BK | RWNX_HWQ_BE | RWNX_HWQ_VI | RWNX_HWQ_VO)
+#define RWNX_HWQ_ALL_ACS_BIT \
+ (BIT(RWNX_HWQ_BK) | BIT(RWNX_HWQ_BE) | BIT(RWNX_HWQ_VI) | BIT(RWNX_HWQ_VO))
+
+#define RWNX_TX_LIFETIME_MS 1000
+#define RWNX_TX_MAX_RATES NX_TX_MAX_RATES
+
+#define RWNX_SWTXHDR_ALIGN_SZ 4
+#define RWNX_SWTXHDR_ALIGN_MSK (RWNX_SWTXHDR_ALIGN_SZ - 1)
+#define RWNX_SWTXHDR_ALIGN_PADS(x) \
+ ((RWNX_SWTXHDR_ALIGN_SZ - ((x) & RWNX_SWTXHDR_ALIGN_MSK)) & \
+ RWNX_SWTXHDR_ALIGN_MSK)
+#if RWNX_SWTXHDR_ALIGN_SZ & RWNX_SWTXHDR_ALIGN_MSK
+#error bad RWNX_SWTXHDR_ALIGN_SZ
+#endif
+
+#define AMSDU_PADDING(x) ((4 - ((x) & 0x3)) & 0x3)
+
+#define TXU_CNTRL_RETRY BIT(0)
+#define TXU_CNTRL_MORE_DATA BIT(2)
+#define TXU_CNTRL_MGMT BIT(3)
+#define TXU_CNTRL_MGMT_NO_CCK BIT(4)
+#define TXU_CNTRL_AMSDU BIT(6)
+#define TXU_CNTRL_MGMT_ROBUST BIT(7)
+#define TXU_CNTRL_USE_4ADDR BIT(8)
+#define TXU_CNTRL_EOSP BIT(9)
+#define TXU_CNTRL_MESH_FWD BIT(10)
+#define TXU_CNTRL_TDLS BIT(11)
+
+extern const int rwnx_tid2hwq[IEEE80211_NUM_TIDS];
+
+/**
+ * struct rwnx_amsdu_txhdr - Header for a noninitial A-MSDU subframe
+ *
+ * @list: Node in the list of additional A-MSDU subframes
+ * @map_len: Number of bytes mapped for this subframe
+ * @dma_addr: DMA address of the mapped subframe
+ * @skb: Socket buffer containing the subframe
+ * @pad: Padding before this subframe when dismantling the A-MSDU
+ * @msdu_len: MSDU size in bytes, excluding padding and the A-MSDU header
+ */
+struct rwnx_amsdu_txhdr {
+ struct list_head list;
+ size_t map_len;
+ dma_addr_t dma_addr;
+ struct sk_buff *skb;
+ u16 pad;
+ u16 msdu_len;
+};
+
+/**
+ * struct rwnx_amsdu - State used while building an A-MSDU
+ *
+ * @hdrs: List of additional subframes
+ * @len: Current A-MSDU size excluding padding, or 0 when none is in progress
+ * @nb: Number of subframes in the A-MSDU
+ * @pad: Padding required before the next subframe
+ */
+struct rwnx_amsdu {
+ struct list_head hdrs;
+ u16 len;
+ u8 nb;
+ u8 pad;
+};
+
+/**
+ * struct rwnx_sw_txhdr - Software part of tx header
+ *
+ * @rwnx_sta: Destination STA
+ * @rwnx_vif: VIF transmitting the buffer
+ * @txq: TX queue used to send the buffer
+ * @hw_queue: Hardware queue index used to push the buffer
+ * @frame_len: Frame size excluding the MAC header
+ * @headroom: Headroom added for &struct rwnx_txhdr
+ * @amsdu: A-MSDU state when this buffer contains the first subframe
+ * @need_cfm: Whether the firmware must confirm this transmission
+ * @skb: Socket buffer being transmitted
+ * @map_len: Number of bytes mapped for DMA
+ * @dma_addr: DMA address of the mapped data
+ * @desc: Descriptor copied to firmware-visible shared memory
+ */
+struct rwnx_sw_txhdr {
+ struct rwnx_sta *rwnx_sta;
+ struct rwnx_vif *rwnx_vif;
+ struct rwnx_txq *txq;
+ u8 hw_queue;
+ u16 frame_len;
+ u16 headroom;
+#ifdef CONFIG_RWNX_AMSDUS_TX
+ struct rwnx_amsdu amsdu;
+#endif
+ u32 need_cfm;
+ struct sk_buff *skb;
+
+ size_t map_len;
+ dma_addr_t dma_addr;
+ struct txdesc_api desc;
+};
+
+/**
+ * struct rwnx_txhdr - Structure to control transimission of packet
+ * (Added in skb headroom)
+ *
+ * @sw_hdr: Information from driver
+ * @cache_guard:
+ * @hw_hdr: Information for/from hardware
+ */
+struct rwnx_txhdr {
+ struct rwnx_sw_txhdr *sw_hdr;
+ char cache_guard[L1_CACHE_BYTES];
+ struct rwnx_hw_txhdr hw_hdr;
+};
+
+u16 rwnx_select_txq(struct rwnx_vif *rwnx_vif, struct sk_buff *skb);
+netdev_tx_t rwnx_start_xmit(struct sk_buff *skb, struct net_device *dev);
+
+int rwnx_start_mgmt_xmit(struct rwnx_vif *vif, struct rwnx_sta *sta,
+ struct cfg80211_mgmt_tx_params *params, bool offchan,
+ u64 *cookie);
+
+int rwnx_txdatacfm(void *pthis, void *host_id);
+
+struct rwnx_hw;
+struct rwnx_sta;
+void rwnx_set_traffic_status(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta,
+ bool available, u8 ps_id);
+
+void rwnx_ps_bh_enable(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta,
+ bool enable);
+
+void rwnx_ps_bh_traffic_req(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta,
+ u16 pkt_req, u8 ps_id);
+
+void rwnx_switch_vif_sta_txq(struct rwnx_sta *sta, struct rwnx_vif *old_vif,
+ struct rwnx_vif *new_vif);
+
+void rwnx_txq_credit_update(struct rwnx_hw *rwnx_hw, int sta_idx, u8 tid,
+ s8 update);
+
+void rwnx_tx_push(struct rwnx_hw *rwnx_hw, struct rwnx_txhdr *txhdr, int flags);
+
+#ifdef CONFIG_AIC8800_FILTER_TCP_ACK
+int intf_tx(struct rwnx_hw *priv, struct msg_buf *msg);
+#endif
+
+#endif /* _RWNX_TX_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_txq.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_txq.c
new file mode 100644
index 0000000000000..317338b18c775
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_txq.c
@@ -0,0 +1,1362 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_txq.c
+ *
+ * @brief tx queue function
+ *
+ ****************************************************************************************
+ */
+
+#include "ipc_host.h"
+#include "rwnx_defs.h"
+#include "rwnx_events.h"
+#include "rwnx_tx.h"
+
+/******************************************************************************
+ * Utils functions
+ *****************************************************************************/
+#ifdef CONFIG_RWNX_FULLMAC
+const int nx_tid_prio[NX_NB_TID_PER_STA] = {7, 6, 5, 4, 3, 0, 2, 1};
+
+static inline int rwnx_txq_sta_idx(struct rwnx_sta *sta, u8 tid)
+{
+ if (is_multicast_sta(sta->sta_idx)) {
+ if (g_rwnx_plat->sdiodev->rwnx_hw->chip_ops->is_old_ic)
+ return NX_FIRST_VIF_TXQ_IDX_FOR_OLD_IC + sta->vif_idx;
+
+ return NX_FIRST_VIF_TXQ_IDX + sta->vif_idx;
+ }
+
+ return (sta->sta_idx * NX_NB_TXQ_PER_STA) + tid;
+}
+
+static inline int rwnx_txq_vif_idx(struct rwnx_vif *vif, u8 type)
+{
+ if (g_rwnx_plat->sdiodev->rwnx_hw->chip_ops->is_old_ic)
+ return NX_FIRST_VIF_TXQ_IDX_FOR_OLD_IC + master_vif_idx(vif) +
+ (type * NX_VIRT_DEV_MAX);
+
+ return NX_FIRST_VIF_TXQ_IDX + master_vif_idx(vif) +
+ (type * NX_VIRT_DEV_MAX);
+}
+
+struct rwnx_txq *rwnx_txq_sta_get(struct rwnx_sta *sta, u8 tid,
+ struct rwnx_hw *rwnx_hw)
+{
+ if (tid >= NX_NB_TXQ_PER_STA)
+ tid = 0;
+
+ return &rwnx_hw->txq[rwnx_txq_sta_idx(sta, tid)];
+}
+
+struct rwnx_txq *rwnx_txq_vif_get(struct rwnx_vif *vif, u8 type)
+{
+ if (type > NX_UNK_TXQ_TYPE)
+ type = NX_BCMC_TXQ_TYPE;
+
+ return &vif->rwnx_hw->txq[rwnx_txq_vif_idx(vif, type)];
+}
+
+static inline struct rwnx_sta *rwnx_txq_2_sta(struct rwnx_txq *txq)
+{
+ return txq->sta;
+}
+
+#endif /* CONFIG_RWNX_FULLMAC */
+
+/******************************************************************************
+ * Init/Deinit functions
+ *****************************************************************************/
+/*
+ * rwnx_txq_init - Initialize a TX queue
+ *
+ * @txq: TX queue to be initialized
+ * @idx: TX queue index
+ * @status: TX queue initial status
+ * @hwq: Associated HW queue
+ * @ndev: Net device this queue belongs to
+ * (may be null for non netdev txq)
+ *
+ * Each queue is initialized with the credit of @NX_TXQ_INITIAL_CREDITS.
+ */
+static void rwnx_txq_init(struct rwnx_txq *txq, int idx, u8 status,
+ struct rwnx_hwq *hwq, int tid,
+#ifdef CONFIG_RWNX_FULLMAC
+ struct rwnx_sta *sta, struct net_device *ndev
+#endif
+)
+{
+ int i;
+ int nx_first_unk_txq_idx = NX_FIRST_UNK_TXQ_IDX;
+ int nx_bcmc_txq_ndev_idx = NX_BCMC_TXQ_NDEV_IDX;
+ int nx_first_vif_txq_idx = NX_FIRST_VIF_TXQ_IDX;
+
+ if (g_rwnx_plat->sdiodev->rwnx_hw->chip_ops->is_old_ic) {
+ nx_first_unk_txq_idx = NX_FIRST_UNK_TXQ_IDX_FOR_OLD_IC;
+ nx_bcmc_txq_ndev_idx = NX_BCMC_TXQ_NDEV_IDX_FOR_OLD_IC;
+ nx_first_vif_txq_idx = NX_FIRST_VIF_TXQ_IDX_FOR_OLD_IC;
+ }
+
+ txq->idx = idx;
+ txq->status = status;
+ txq->credits = NX_TXQ_INITIAL_CREDITS;
+ txq->pkt_sent = 0;
+ skb_queue_head_init(&txq->sk_list);
+ txq->last_retry_skb = NULL;
+ txq->nb_retry = 0;
+ txq->hwq = hwq;
+ txq->sta = sta;
+ for (i = 0; i < CONFIG_USER_MAX; i++)
+ txq->pkt_pushed[i] = 0;
+ txq->push_limit = 0;
+ txq->tid = tid;
+#ifdef CONFIG_MAC80211_TXQ
+ txq->nb_ready_mac80211 = 0;
+#endif
+#ifdef CONFIG_RWNX_FULLMAC
+ txq->ps_id = LEGACY_PS_ID;
+ if (idx < nx_first_vif_txq_idx) {
+ int sta_idx = sta->sta_idx;
+ int tid = idx - (sta_idx * NX_NB_TXQ_PER_STA);
+
+ if (tid < NX_NB_TID_PER_STA)
+ txq->ndev_idx = NX_STA_NDEV_IDX(tid, sta_idx);
+ else
+ txq->ndev_idx = NDEV_NO_TXQ;
+ } else if (idx < nx_first_unk_txq_idx) {
+ txq->ndev_idx = nx_bcmc_txq_ndev_idx;
+ } else {
+ txq->ndev_idx = NDEV_NO_TXQ;
+ }
+ txq->ndev = ndev;
+#ifdef CONFIG_RWNX_AMSDUS_TX
+ txq->amsdu = NULL;
+ txq->amsdu_len = 0;
+#endif /* CONFIG_RWNX_AMSDUS_TX */
+#endif /* CONFIG_RWNX_FULLMAC */
+}
+
+/**
+ * rwnx_txq_flush - Flush all buffers queued for a TXQ
+ *
+ * @rwnx_hw: main driver data
+ * @txq: txq to flush
+ */
+void rwnx_txq_flush(struct rwnx_hw *rwnx_hw, struct rwnx_txq *txq)
+{
+ struct sk_buff *skb;
+
+ while ((skb = skb_dequeue(&txq->sk_list)) != NULL) {
+ struct rwnx_sw_txhdr *sw_txhdr =
+ ((struct rwnx_txhdr *)skb->data)->sw_hdr;
+
+#ifdef CONFIG_RWNX_AMSDUS_TX
+ if (sw_txhdr->desc.host.packet_cnt > 1) {
+ struct rwnx_amsdu_txhdr *amsdu_txhdr;
+
+ list_for_each_entry(amsdu_txhdr, &sw_txhdr->amsdu.hdrs, list) {
+ dev_kfree_skb_any(amsdu_txhdr->skb);
+ }
+ }
+#endif
+ kmem_cache_free(rwnx_hw->sw_txhdr_cache, sw_txhdr);
+
+#ifdef CONFIG_RWNX_FULLMAC
+ dev_kfree_skb_any(skb);
+#endif /* CONFIG_RWNX_FULLMAC */
+ }
+}
+
+/**
+ * rwnx_txq_deinit - De-initialize a TX queue
+ *
+ * @rwnx_hw: Driver main data
+ * @txq: TX queue to be de-initialized
+ * Any buffer stuck in a queue will be freed.
+ */
+static void rwnx_txq_deinit(struct rwnx_hw *rwnx_hw, struct rwnx_txq *txq)
+{
+ if (txq->idx == TXQ_INACTIVE)
+ return;
+
+ spin_lock_bh(&rwnx_hw->tx_lock);
+ rwnx_txq_del_from_hw_list(txq);
+ txq->idx = TXQ_INACTIVE;
+ spin_unlock_bh(&rwnx_hw->tx_lock);
+
+ rwnx_txq_flush(rwnx_hw, txq);
+}
+
+/**
+ * rwnx_txq_vif_init - Initialize all TXQ linked to a vif
+ *
+ * @rwnx_hw: main driver data
+ * @rwnx_vif: Pointer on VIF
+ * @status: Initial txq status
+ *
+ * Softmac : 1 VIF TXQ per HWQ
+ *
+ * Fullmac : 1 VIF TXQ for BC/MC
+ * 1 VIF TXQ for MGMT to unknown STA
+ */
+void rwnx_txq_vif_init(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+ u8 status)
+{
+ struct rwnx_txq *txq;
+ int idx;
+
+#ifdef CONFIG_RWNX_FULLMAC
+ txq = rwnx_txq_vif_get(rwnx_vif, NX_BCMC_TXQ_TYPE);
+ idx = rwnx_txq_vif_idx(rwnx_vif, NX_BCMC_TXQ_TYPE);
+ rwnx_txq_init(txq, idx, status, &rwnx_hw->hwq[RWNX_HWQ_BE], 0,
+ &rwnx_hw->sta_table[rwnx_vif->ap.bcmc_index], rwnx_vif->ndev);
+
+ txq = rwnx_txq_vif_get(rwnx_vif, NX_UNK_TXQ_TYPE);
+ idx = rwnx_txq_vif_idx(rwnx_vif, NX_UNK_TXQ_TYPE);
+ rwnx_txq_init(txq, idx, status, &rwnx_hw->hwq[RWNX_HWQ_VO], TID_MGT, NULL,
+ rwnx_vif->ndev);
+
+#endif /* CONFIG_RWNX_FULLMAC */
+}
+
+/**
+ * rwnx_txq_vif_deinit - Deinitialize all TXQ linked to a vif
+ *
+ * @rwnx_hw: main driver data
+ * @rwnx_vif: Pointer on VIF
+ */
+void rwnx_txq_vif_deinit(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif)
+{
+ struct rwnx_txq *txq;
+
+#ifdef CONFIG_RWNX_FULLMAC
+ txq = rwnx_txq_vif_get(rwnx_vif, NX_BCMC_TXQ_TYPE);
+ rwnx_txq_deinit(rwnx_hw, txq);
+
+ txq = rwnx_txq_vif_get(rwnx_vif, NX_UNK_TXQ_TYPE);
+ rwnx_txq_deinit(rwnx_hw, txq);
+
+#endif /* CONFIG_RWNX_FULLMAC */
+}
+
+/**
+ * rwnx_txq_sta_init - Initialize TX queues for a STA
+ *
+ * @rwnx_hw: Main driver data
+ * @rwnx_sta: STA for which tx queues need to be initialized
+ * @status: Initial txq status
+ *
+ * This function initialize all the TXQ associated to a STA.
+ * Softmac : 1 TXQ per TID
+ *
+ * Fullmac : 1 TXQ per TID (limited to 8)
+ * 1 TXQ for MGMT
+ */
+void rwnx_txq_sta_init(struct rwnx_hw *rwnx_hw, struct rwnx_sta *rwnx_sta,
+ u8 status)
+{
+ struct rwnx_txq *txq;
+ int tid, idx;
+
+#ifdef CONFIG_RWNX_FULLMAC
+ struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[rwnx_sta->vif_idx];
+
+ idx = rwnx_txq_sta_idx(rwnx_sta, 0);
+
+#ifdef CONFIG_MAC80211_TXQ
+ for (tid = 0, txq = rwnx_txq_sta_get(rwnx_sta, 0); tid < NX_NB_TXQ_PER_STA;
+ tid++, txq = rwnx_txq_sta_get(rwnx_sta, tid))
+#elif defined(CONFIG_RWNX_FULLMAC)
+ for (tid = 0, txq = rwnx_txq_sta_get(rwnx_sta, 0, rwnx_hw);
+ tid < (is_multicast_sta(rwnx_sta->sta_idx) ? 1 : NX_NB_TXQ_PER_STA);
+ tid++, txq++)
+#endif
+ {
+ rwnx_txq_init(txq, idx, status, &rwnx_hw->hwq[rwnx_tid2hwq[tid]], tid,
+ rwnx_sta, rwnx_vif->ndev);
+ txq->ps_id =
+ rwnx_sta->uapsd_tids & (1 << tid) ? UAPSD_ID : LEGACY_PS_ID;
+ idx++;
+ }
+
+#endif /* CONFIG_RWNX_FULLMAC */
+}
+
+/**
+ * rwnx_txq_sta_deinit - Deinitialize TX queues for a STA
+ *
+ * @rwnx_hw: Main driver data
+ * @rwnx_sta: STA for which tx queues need to be deinitialized
+ */
+void rwnx_txq_sta_deinit(struct rwnx_hw *rwnx_hw, struct rwnx_sta *rwnx_sta)
+{
+ struct rwnx_txq *txq;
+ int tid;
+
+#ifdef CONFIG_MAC80211_TXQ
+ for (tid = 0, txq = rwnx_txq_sta_get(rwnx_sta, 0); tid < NX_NB_TXQ_PER_STA;
+ tid++, txq = rwnx_txq_sta_get(rwnx_sta, tid))
+#elif defined(CONFIG_RWNX_FULLMAC)
+ for (tid = 0, txq = rwnx_txq_sta_get(rwnx_sta, 0, rwnx_hw);
+ tid < (is_multicast_sta(rwnx_sta->sta_idx) ? 1 : NX_NB_TXQ_PER_STA);
+ tid++, txq++)
+#endif
+ {
+ rwnx_txq_deinit(rwnx_hw, txq);
+ }
+}
+
+#ifdef CONFIG_RWNX_FULLMAC
+/**
+ * rwnx_txq_unk_vif_init - Initialize TXQ for unknown STA linked to a vif
+ *
+ * @rwnx_vif: Pointer on VIF
+ */
+void rwnx_txq_unk_vif_init(struct rwnx_vif *rwnx_vif)
+{
+ struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+ struct rwnx_txq *txq;
+ int idx;
+
+ txq = rwnx_txq_vif_get(rwnx_vif, NX_UNK_TXQ_TYPE);
+ idx = rwnx_txq_vif_idx(rwnx_vif, NX_UNK_TXQ_TYPE);
+ rwnx_txq_init(txq, idx, 0, &rwnx_hw->hwq[RWNX_HWQ_VO], TID_MGT, NULL,
+ rwnx_vif->ndev);
+}
+
+/**
+ * rwnx_txq_unk_vif_deinit - Deinitialize the unknown-STA TX queue for a VIF
+ *
+ * @rwnx_vif: VIF that owns the queue
+ */
+void rwnx_txq_unk_vif_deinit(struct rwnx_vif *rwnx_vif)
+{
+ struct rwnx_txq *txq;
+
+ txq = rwnx_txq_vif_get(rwnx_vif, NX_UNK_TXQ_TYPE);
+ rwnx_txq_deinit(rwnx_vif->rwnx_hw, txq);
+}
+
+/**
+ * rwnx_txq_offchan_init - Initialize the off-channel TX queue
+ *
+ * @rwnx_vif: VIF that owns the queue
+ *
+ * The off-channel queue is active only for the duration of remain-on-channel.
+ */
+void rwnx_txq_offchan_init(struct rwnx_vif *rwnx_vif)
+{
+ struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+ struct rwnx_txq *txq;
+ int nx_off_chan_txq_idx = NX_OFF_CHAN_TXQ_IDX;
+
+ if (g_rwnx_plat->sdiodev->rwnx_hw->chip_ops->is_old_ic)
+ nx_off_chan_txq_idx = NX_OFF_CHAN_TXQ_IDX_FOR_OLD_IC;
+
+ txq = &rwnx_hw->txq[nx_off_chan_txq_idx];
+ rwnx_txq_init(txq, nx_off_chan_txq_idx, RWNX_TXQ_STOP_CHAN,
+ &rwnx_hw->hwq[RWNX_HWQ_VO], TID_MGT, NULL, rwnx_vif->ndev);
+}
+
+/**
+ * rwnx_txq_offchan_deinit - Deinitialize the off-channel TX queue
+ *
+ * @rwnx_vif: Interface that owns the queue
+ *
+ * Any buffer remaining in the queue is freed.
+ */
+void rwnx_txq_offchan_deinit(struct rwnx_vif *rwnx_vif)
+{
+ struct rwnx_txq *txq;
+
+ int nx_off_chan_txq_idx = NX_OFF_CHAN_TXQ_IDX;
+
+ if (g_rwnx_plat->sdiodev->rwnx_hw->chip_ops->is_old_ic)
+ nx_off_chan_txq_idx = NX_OFF_CHAN_TXQ_IDX_FOR_OLD_IC;
+
+ txq = &rwnx_vif->rwnx_hw->txq[nx_off_chan_txq_idx];
+ rwnx_txq_deinit(rwnx_vif->rwnx_hw, txq);
+}
+
+/**
+ * rwnx_txq_tdls_vif_init - Initialize TXQ vif for TDLS
+ *
+ * @rwnx_vif: Pointer on VIF
+ */
+void rwnx_txq_tdls_vif_init(struct rwnx_vif *rwnx_vif)
+{
+ struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+
+ if (!(rwnx_hw->wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS))
+ return;
+
+ rwnx_txq_unk_vif_init(rwnx_vif);
+}
+
+/**
+ * rwnx_txq_tdls_vif_deinit - Deinitialize TXQ vif for TDLS
+ *
+ * @rwnx_vif: Pointer on VIF
+ */
+void rwnx_txq_tdls_vif_deinit(struct rwnx_vif *rwnx_vif)
+{
+ struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+
+ if (!(rwnx_hw->wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS))
+ return;
+
+ rwnx_txq_unk_vif_deinit(rwnx_vif);
+}
+#endif
+
+/******************************************************************************
+ * Start/Stop functions
+ *****************************************************************************/
+/**
+ * rwnx_txq_add_to_hw_list - Add TX queue to a HW queue schedule list.
+ *
+ * @txq: TX queue to add
+ *
+ * Add the TX queue if not already present in the HW queue list.
+ * To be called with tx_lock hold
+ */
+void rwnx_txq_add_to_hw_list(struct rwnx_txq *txq)
+{
+ if (!(txq->status & RWNX_TXQ_IN_HWQ_LIST)) {
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_txq_add_to_hw(txq);
+#endif
+ txq->status |= RWNX_TXQ_IN_HWQ_LIST;
+ list_add_tail(&txq->sched_list, &txq->hwq->list);
+ txq->hwq->need_processing = true;
+ }
+}
+
+/**
+ * rwnx_txq_del_from_hw_list - Delete TX queue from a HW queue schedule list.
+ *
+ * @txq: TX queue to delete
+ *
+ * Remove the TX queue from the HW queue list if present.
+ * To be called with tx_lock hold
+ */
+void rwnx_txq_del_from_hw_list(struct rwnx_txq *txq)
+{
+ if (txq->status & RWNX_TXQ_IN_HWQ_LIST) {
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_txq_del_from_hw(txq);
+#endif
+ txq->status &= ~RWNX_TXQ_IN_HWQ_LIST;
+ list_del(&txq->sched_list);
+ }
+}
+
+/**
+ * rwnx_txq_skb_ready - Check if skb are available for the txq
+ *
+ * @txq: Pointer on txq
+ * Return: %true if buffers are ready to be pushed, otherwise %false.
+ */
+static inline bool rwnx_txq_skb_ready(struct rwnx_txq *txq)
+{
+#ifdef CONFIG_MAC80211_TXQ
+ if (txq->nb_ready_mac80211 != NOT_MAC80211_TXQ)
+ return ((txq->nb_ready_mac80211 > 0) ||
+ !skb_queue_empty(&txq->sk_list));
+ else
+#endif
+ return !skb_queue_empty(&txq->sk_list);
+}
+
+/**
+ * rwnx_txq_start - Try to Start one TX queue
+ *
+ * @txq: TX queue to start
+ * @reason: reason why the TX queue is started (among RWNX_TXQ_STOP_xxx)
+ *
+ * Re-start the TX queue for one reason.
+ * If after this the txq is no longer stopped and some buffers are ready,
+ * the TX queue is also added to HW queue list.
+ * To be called with tx_lock hold
+ */
+void rwnx_txq_start(struct rwnx_txq *txq, u16 reason)
+{
+ WARN_ON_ONCE(!txq);
+ if (txq->idx != TXQ_INACTIVE && (txq->status & reason)) {
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_txq_start(txq, reason);
+#endif
+ txq->status &= ~reason;
+ if (!rwnx_txq_is_stopped(txq) && rwnx_txq_skb_ready(txq))
+ rwnx_txq_add_to_hw_list(txq);
+ }
+}
+
+/**
+ * rwnx_txq_stop - Stop one TX queue
+ *
+ * @txq: TX queue to stop
+ * @reason: reason why the TX queue is stopped (among RWNX_TXQ_STOP_xxx)
+ *
+ * Stop the TX queue. It will remove the TX queue from HW queue list
+ * To be called with tx_lock hold
+ */
+void rwnx_txq_stop(struct rwnx_txq *txq, u16 reason)
+{
+ WARN_ON_ONCE(!txq);
+ if (txq->idx != TXQ_INACTIVE) {
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_txq_stop(txq, reason);
+#endif
+ txq->status |= reason;
+ rwnx_txq_del_from_hw_list(txq);
+ }
+}
+
+/**
+ * rwnx_txq_sta_start - Start all TX queues linked to a STA
+ *
+ * @rwnx_sta: STA whose TX queues must be re-started
+ * @reason: Stop-reason bits to clear
+ * @rwnx_hw: Driver main data
+ *
+ * Restart all the TX queues of the STA for the specified reason:
+ * - RWNX_TXQ_STOP_STA_PS: the STA is no longer in power save mode
+ * - RWNX_TXQ_STOP_VIF_PS: the VIF is in power save mode (p2p absence)
+ * - RWNX_TXQ_STOP_CHAN: the STA's VIF is now on the current active channel
+ *
+ * A queue with a ready buffer and no other stop reason is added to its hardware
+ * queue list.
+ *
+ * Context: Called with the TX lock held.
+ */
+void rwnx_txq_sta_start(struct rwnx_sta *rwnx_sta, u16 reason
+#ifdef CONFIG_RWNX_FULLMAC
+ ,
+ struct rwnx_hw *rwnx_hw
+#endif
+)
+{
+ struct rwnx_txq *txq;
+ int tid;
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_txq_sta_start(rwnx_sta->sta_idx);
+#endif
+#ifdef CONFIG_MAC80211_TXQ
+ for (tid = 0, txq = rwnx_txq_sta_get(rwnx_sta, 0); tid < NX_NB_TXQ_PER_STA;
+ tid++, txq = rwnx_txq_sta_get(rwnx_sta, tid))
+#elif defined(CONFIG_RWNX_FULLMAC)
+ for (tid = 0, txq = rwnx_txq_sta_get(rwnx_sta, 0, rwnx_hw);
+ tid < (is_multicast_sta(rwnx_sta->sta_idx) ? 1 : NX_NB_TXQ_PER_STA);
+ tid++, txq++)
+#endif
+ {
+ rwnx_txq_start(txq, reason);
+ }
+}
+
+/**
+ * rwnx_txq_sta_stop - Stop all TX queues linked to a STA
+ *
+ * @rwnx_sta: STA whose TX queues must be stopped
+ * @reason: Stop-reason bits to set
+ * @rwnx_hw: Driver main data
+ *
+ * Stop all the TX queues of the STA for the specified reason:
+ * - RWNX_TXQ_STOP_STA_PS: the STA is in power save mode
+ * - RWNX_TXQ_STOP_VIF_PS: the VIF is in power save mode (p2p absence)
+ * - RWNX_TXQ_STOP_CHAN: the STA's VIF is not on the current active channel
+ *
+ * Affected queues are removed from their hardware queue lists.
+ *
+ * Context: Called with the TX lock held.
+ */
+void rwnx_txq_sta_stop(struct rwnx_sta *rwnx_sta, u16 reason
+#ifdef CONFIG_RWNX_FULLMAC
+ ,
+ struct rwnx_hw *rwnx_hw
+#endif
+)
+{
+ struct rwnx_txq *txq;
+ int tid;
+
+ if (!rwnx_sta)
+ return;
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_txq_sta_stop(rwnx_sta->sta_idx);
+#endif
+#ifdef CONFIG_MAC80211_TXQ
+ for (tid = 0, txq = rwnx_txq_sta_get(rwnx_sta, 0); tid < NX_NB_TXQ_PER_STA;
+ tid++, txq = rwnx_txq_sta_get(rwnx_sta, tid))
+#elif defined(CONFIG_RWNX_FULLMAC)
+ for (tid = 0, txq = rwnx_txq_sta_get(rwnx_sta, 0, rwnx_hw);
+ tid < (is_multicast_sta(rwnx_sta->sta_idx) ? 1 : NX_NB_TXQ_PER_STA);
+ tid++, txq++)
+#endif
+ {
+ rwnx_txq_stop(txq, reason);
+ }
+}
+
+#ifdef CONFIG_RWNX_FULLMAC
+void rwnx_txq_tdls_sta_start(struct rwnx_vif *rwnx_vif, u16 reason,
+ struct rwnx_hw *rwnx_hw)
+{
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_txq_vif_start(rwnx_vif->vif_index);
+#endif
+ spin_lock_bh(&rwnx_hw->tx_lock);
+
+ if (rwnx_vif->sta.tdls_sta)
+ rwnx_txq_sta_start(rwnx_vif->sta.tdls_sta, reason, rwnx_hw);
+
+ spin_unlock_bh(&rwnx_hw->tx_lock);
+}
+#endif
+
+#ifdef CONFIG_RWNX_FULLMAC
+void rwnx_txq_tdls_sta_stop(struct rwnx_vif *rwnx_vif, u16 reason,
+ struct rwnx_hw *rwnx_hw)
+{
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_txq_vif_stop(rwnx_vif->vif_index);
+#endif
+ spin_lock_bh(&rwnx_hw->tx_lock);
+
+ if (rwnx_vif->sta.tdls_sta)
+ rwnx_txq_sta_stop(rwnx_vif->sta.tdls_sta, reason, rwnx_hw);
+
+ spin_unlock_bh(&rwnx_hw->tx_lock);
+}
+#endif
+
+#ifdef CONFIG_RWNX_FULLMAC
+static inline void
+rwnx_txq_vif_for_each_sta(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+ void (*f)(struct rwnx_sta *, u16, struct rwnx_hw *),
+ u16 reason) {
+ switch (RWNX_VIF_TYPE(rwnx_vif)) {
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ case NL80211_IFTYPE_P2P_CLIENT:
+#endif
+ case NL80211_IFTYPE_STATION: {
+ if (rwnx_vif->tdls_status == TDLS_LINK_ACTIVE)
+ f(rwnx_vif->sta.tdls_sta, reason, rwnx_hw);
+ if (!WARN_ON(!rwnx_vif->sta.ap))
+ f(rwnx_vif->sta.ap, reason, rwnx_hw);
+ break;
+ }
+#if !IS_ENABLED(CONFIG_AIC8800_STA_ONLY)
+ case NL80211_IFTYPE_AP_VLAN:
+ rwnx_vif = rwnx_vif->ap_vlan.master;
+ fallthrough;
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_MESH_POINT:
+ case NL80211_IFTYPE_P2P_GO: {
+ struct rwnx_sta *sta;
+
+ list_for_each_entry(sta, &rwnx_vif->ap.sta_list, list) {
+ f(sta, reason, rwnx_hw);
+ }
+ break;
+ }
+#endif
+ default:
+ //WARN_ON_ONCE();
+ break;
+ }
+}
+#endif
+
+/**
+ * rwnx_txq_vif_start - Start the TX queues associated with a VIF
+ *
+ * @rwnx_vif: VIF whose queues should be started
+ * @reason: Stop-reason bits to clear
+ * @rwnx_hw: Driver main data
+ *
+ * Iterate over all STAs associated with the VIF and restart their queues for
+ * @reason. This function takes the TX lock.
+ */
+void rwnx_txq_vif_start(struct rwnx_vif *rwnx_vif, u16 reason,
+ struct rwnx_hw *rwnx_hw)
+{
+ struct rwnx_txq *txq;
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_txq_vif_start(rwnx_vif->vif_index);
+#endif
+ spin_lock_bh(&rwnx_hw->tx_lock);
+
+#ifdef CONFIG_RWNX_FULLMAC
+ // Reject if monitor interface
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_MONITOR)
+ goto end;
+
+ if (rwnx_vif->roc_tdls && rwnx_vif->sta.tdls_sta &&
+ rwnx_vif->sta.tdls_sta->tdls.chsw_en) {
+ rwnx_txq_sta_start(rwnx_vif->sta.tdls_sta, reason, rwnx_hw);
+ }
+ if (!rwnx_vif->roc_tdls) {
+ rwnx_txq_vif_for_each_sta(rwnx_hw, rwnx_vif, rwnx_txq_sta_start,
+ reason);
+ }
+
+ txq = rwnx_txq_vif_get(rwnx_vif, NX_BCMC_TXQ_TYPE);
+ rwnx_txq_start(txq, reason);
+ txq = rwnx_txq_vif_get(rwnx_vif, NX_UNK_TXQ_TYPE);
+ rwnx_txq_start(txq, reason);
+
+end:
+#endif /* CONFIG_RWNX_FULLMAC */
+
+ spin_unlock_bh(&rwnx_hw->tx_lock);
+}
+
+/**
+ * rwnx_txq_vif_stop - Stop the TX queues associated with a VIF
+ *
+ * @rwnx_vif: VIF whose queues should be stopped
+ * @reason: Stop-reason bits to set
+ * @rwnx_hw: Driver main data
+ *
+ * Iterate over all STAs associated with the VIF and stop their queues for
+ * @reason. This function takes the TX lock.
+ */
+void rwnx_txq_vif_stop(struct rwnx_vif *rwnx_vif, u16 reason,
+ struct rwnx_hw *rwnx_hw)
+{
+ struct rwnx_txq *txq;
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_txq_vif_stop(rwnx_vif->vif_index);
+#endif
+ spin_lock_bh(&rwnx_hw->tx_lock);
+
+#ifdef CONFIG_RWNX_FULLMAC
+ // Reject if monitor interface
+ if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_MONITOR)
+ goto end;
+
+ rwnx_txq_vif_for_each_sta(rwnx_hw, rwnx_vif, rwnx_txq_sta_stop, reason);
+
+ txq = rwnx_txq_vif_get(rwnx_vif, NX_BCMC_TXQ_TYPE);
+ rwnx_txq_stop(txq, reason);
+ txq = rwnx_txq_vif_get(rwnx_vif, NX_UNK_TXQ_TYPE);
+ rwnx_txq_stop(txq, reason);
+
+end:
+#endif /* CONFIG_RWNX_FULLMAC */
+
+ spin_unlock_bh(&rwnx_hw->tx_lock);
+}
+
+#ifdef CONFIG_RWNX_FULLMAC
+
+/**
+ * rwnx_txq_offchan_start - Start the TX queue for off-channel frames
+ *
+ * @rwnx_hw: Driver main data
+ */
+void rwnx_txq_offchan_start(struct rwnx_hw *rwnx_hw)
+{
+ struct rwnx_txq *txq;
+ int nx_off_chan_txq_idx = NX_OFF_CHAN_TXQ_IDX;
+
+ if (g_rwnx_plat->sdiodev->rwnx_hw->chip_ops->is_old_ic)
+ nx_off_chan_txq_idx = NX_OFF_CHAN_TXQ_IDX_FOR_OLD_IC;
+
+ txq = &rwnx_hw->txq[nx_off_chan_txq_idx];
+ spin_lock_bh(&rwnx_hw->tx_lock);
+ rwnx_txq_start(txq, RWNX_TXQ_STOP_CHAN);
+ spin_unlock_bh(&rwnx_hw->tx_lock);
+}
+
+/**
+ * rwnx_txq_sta_switch_vif - Associate TXQs linked to a STA with a new VIF
+ *
+ * @sta: STA whose TX queues must be switched
+ * @old_vif: VIF currently associated with the STA (may no longer be active)
+ * @new_vif: New VIF to associate with the STA
+ *
+ * This function will switch the vif (i.e. the netdev) associated to all STA's
+ * TX queues. This is used when AP_VLAN interfaces are created or removed.
+ */
+void rwnx_txq_sta_switch_vif(struct rwnx_sta *sta, struct rwnx_vif *old_vif,
+ struct rwnx_vif *new_vif)
+{
+ struct rwnx_hw *rwnx_hw = new_vif->rwnx_hw;
+ struct rwnx_txq *txq;
+ int i;
+
+ /* start TXQ on the new interface, and update ndev field in txq */
+ if (!netif_carrier_ok(new_vif->ndev))
+ netif_carrier_on(new_vif->ndev);
+ txq = rwnx_txq_sta_get(sta, 0, rwnx_hw);
+ for (i = 0; i < NX_NB_TID_PER_STA; i++, txq++) {
+ txq->ndev = new_vif->ndev;
+ netif_wake_subqueue(txq->ndev, txq->ndev_idx);
+ }
+}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+/******************************************************************************
+ * TXQ queue/schedule functions
+ *****************************************************************************/
+/**
+ * rwnx_txq_queue_skb - Queue a buffer in a TX queue
+ *
+ * @skb: Buffer to queue
+ * @txq: TX Queue in which the buffer must be added
+ * @rwnx_hw: Driver main data
+ * @retry: Should it be queued in the retry list
+ *
+ * @return: Return 1 if txq has been added to hwq list, 0 otherwise
+ *
+ * Add a buffer in the buffer list of the TX queue
+ * and add this TX queue in the HW queue list if the txq is not stopped.
+ * If this is a retry packet it is added after the last retry packet or at the
+ * beginning if there is no retry packet queued.
+ *
+ * If the STA is in PS mode and this is the first packet queued for this txq
+ * update TIM.
+ *
+ * To be called with tx_lock hold
+ */
+int rwnx_txq_queue_skb(struct sk_buff *skb, struct rwnx_txq *txq,
+ struct rwnx_hw *rwnx_hw, bool retry)
+{
+#ifdef CONFIG_RWNX_FULLMAC
+ if (unlikely(txq->sta && txq->sta->ps.active)) {
+ txq->sta->ps.pkt_ready[txq->ps_id]++;
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_ps_queue(txq->sta);
+#endif
+ if (txq->sta->ps.pkt_ready[txq->ps_id] == 1)
+ rwnx_set_traffic_status(rwnx_hw, txq->sta, true, txq->ps_id);
+ }
+#endif
+
+ if (!retry) {
+ /* add buffer in the sk_list */
+ skb_queue_tail(&txq->sk_list, skb);
+ } else {
+ if (txq->last_retry_skb)
+ skb_append(txq->last_retry_skb, skb, &txq->sk_list);
+ else
+ skb_queue_head(&txq->sk_list, skb);
+
+ txq->last_retry_skb = skb;
+ txq->nb_retry++;
+ }
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_txq_queue_skb(skb, txq, retry);
+#endif
+ /* Flowctrl corresponding netdev queue if needed */
+#ifdef CONFIG_RWNX_FULLMAC
+#ifndef CONFIG_AIC8800_ONE_TXQ
+
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+ if (txq->ndev_idx != NDEV_NO_TXQ &&
+ ((skb_queue_len(&txq->sk_list) > RWNX_NDEV_FLOW_CTRL_STOP) &&
+ !rwnx_hw->sdiodev->flowctrl)) {
+// (atomic_read(&rwnx_hw->sdiodev->tx_priv->tx_pktcnt) >=
+// tx_fc_high_water))) {
+#else
+ /* If too many buffer are queued for this TXQ stop netdev queue */
+ if (txq->ndev_idx != NDEV_NO_TXQ &&
+ (skb_queue_len(&txq->sk_list) > RWNX_NDEV_FLOW_CTRL_STOP)) {
+#endif
+
+ txq->status |= RWNX_TXQ_NDEV_FLOW_CTRL;
+ netif_stop_subqueue(txq->ndev, txq->ndev_idx);
+#ifdef CREATE_TRACE_POINT
+ trace_txq_flowctrl_stop(txq);
+#endif
+ }
+#endif /* CONFIG_AIC8800_ONE_TXQ */
+#else /* ! CONFIG_RWNX_FULLMAC */
+
+ if (!retry && ++txq->hwq->len == txq->hwq->len_stop) {
+ trace_hwq_flowctrl_stop(txq->hwq->id);
+ ieee80211_stop_queue(rwnx_hw->hw, txq->hwq->id);
+ rwnx_hw->stats.queues_stops++;
+ }
+#endif /* CONFIG_RWNX_FULLMAC */
+
+ /* add it in the hwq list if not stopped and not yet present */
+ if (!rwnx_txq_is_stopped(txq)) {
+ rwnx_txq_add_to_hw_list(txq);
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * rwnx_txq_confirm_any - Process buffer confirmed by fw
+ *
+ * @rwnx_hw: Driver main data
+ * @txq: TX Queue
+ * @hwq: HW Queue
+ * @sw_txhdr: software descriptor of the confirmed packet
+ *
+ * Process a buffer returned by the fw. It doesn't check buffer status
+ * and only does systematic counter update:
+ * - hw credit
+ * - buffer pushed to fw
+ *
+ * To be called with tx_lock hold
+ */
+void rwnx_txq_confirm_any(struct rwnx_hw *rwnx_hw, struct rwnx_txq *txq,
+ struct rwnx_hwq *hwq, struct rwnx_sw_txhdr *sw_txhdr)
+{
+ int user = 0;
+
+#ifdef CONFIG_RWNX_MUMIMO_TX
+ user = RWNX_MUMIMO_INFO_POS_ID(sw_txhdr->desc.host.mumimo_info);
+
+ if (txq->idx != TXQ_INACTIVE && txq->pkt_pushed[user] == 1 &&
+ txq->status & RWNX_TXQ_STOP_MU_POS)
+ rwnx_txq_start(txq, RWNX_TXQ_STOP_MU_POS);
+
+#endif /* CONFIG_RWNX_MUMIMO_TX */
+
+ if (txq->pkt_pushed[user])
+ txq->pkt_pushed[user]--;
+
+ hwq->need_processing = true;
+ rwnx_hw->stats.cfm_balance[hwq->id]--;
+}
+
+/******************************************************************************
+ * HWQ processing
+ *****************************************************************************/
+static inline bool rwnx_txq_take_mu_lock(struct rwnx_hw *rwnx_hw)
+{
+ bool res = false;
+#ifdef CONFIG_RWNX_MUMIMO_TX
+ if (rwnx_hw->mod_params->mutx)
+ res = (down_trylock(&rwnx_hw->mu.lock) == 0);
+#endif /* CONFIG_RWNX_MUMIMO_TX */
+ return res;
+}
+
+static inline void rwnx_txq_release_mu_lock(struct rwnx_hw *rwnx_hw)
+{
+#ifdef CONFIG_RWNX_MUMIMO_TX
+ up(&rwnx_hw->mu.lock);
+#endif /* CONFIG_RWNX_MUMIMO_TX */
+}
+
+static inline void rwnx_txq_set_mu_info(struct rwnx_hw *rwnx_hw,
+ struct rwnx_txq *txq, int group_id,
+ int pos)
+{
+#ifdef CONFIG_RWNX_MUMIMO_TX
+ trace_txq_select_mu_group(txq, group_id, pos);
+ if (group_id) {
+ txq->mumimo_info = group_id | (pos << 6);
+ rwnx_mu_set_active_group(rwnx_hw, group_id);
+ } else {
+ txq->mumimo_info = 0;
+ }
+#endif /* CONFIG_RWNX_MUMIMO_TX */
+}
+
+static inline s8 rwnx_txq_get_credits(struct rwnx_txq *txq)
+{
+ s8 cred = txq->credits;
+ /*
+ * if destination is in PS mode, push_limit indicates the maximum
+ * number of packet that can be pushed on this txq.
+ */
+ if (txq->push_limit && cred > txq->push_limit)
+ cred = txq->push_limit;
+ return cred;
+}
+
+/**
+ * skb_queue_extract - Extract buffer from skb list
+ *
+ * @list: List of skb to extract from
+ * @head: List of skb to append to
+ * @nb_elt: Number of skb to extract
+ *
+ * extract the first @nb_elt of @list and append them to @head
+ * It is assume that:
+ * - @list contains more that @nb_elt
+ * - There is no need to take @list nor @head lock to modify them
+ */
+static inline void skb_queue_extract(struct sk_buff_head *list,
+ struct sk_buff_head *head, int nb_elt)
+{
+ int i;
+ struct sk_buff *first, *last, *ptr;
+
+ ptr = list->next;
+ first = ptr;
+ for (i = 0; i < nb_elt; i++)
+ ptr = ptr->next;
+
+ last = ptr->prev;
+
+ /* unlink nb_elt in list */
+ list->qlen -= nb_elt;
+ list->next = ptr;
+ ptr->prev = (struct sk_buff *)list;
+
+ /* append nb_elt at end of head */
+ head->qlen += nb_elt;
+ last->next = (struct sk_buff *)head;
+ head->prev->next = first;
+ first->prev = head->prev;
+ head->prev = last;
+}
+
+#ifdef CONFIG_MAC80211_TXQ
+/**
+ * rwnx_txq_mac80211_dequeue - Dequeue buffer from mac80211 txq and
+ * add them to push list
+ *
+ * @rwnx_hw: Main driver data
+ * @sk_list: List of buffer to push (initialized without lock)
+ * @txq: TXQ to dequeue buffers from
+ * @max: Max number of buffer to dequeue
+ *
+ * Dequeue buffer from mac80211 txq, prepare them for transmission and chain
+ * them to the list of buffer to push.
+ *
+ * Return: %true if no buffers remain in the mac80211 TX queue, otherwise
+ * %false.
+ */
+static bool rwnx_txq_mac80211_dequeue(struct rwnx_hw *rwnx_hw,
+ struct sk_buff_head *sk_list,
+ struct rwnx_txq *txq, int max)
+{
+ struct ieee80211_txq *mac_txq;
+ struct sk_buff *skb;
+ unsigned long mac_txq_len;
+
+ if (txq->nb_ready_mac80211 == NOT_MAC80211_TXQ)
+ return true;
+
+ mac_txq = container_of((void *)txq, struct ieee80211_txq, drv_priv);
+
+ for (; max > 0; max--) {
+ skb = rwnx_tx_dequeue_prep(rwnx_hw, mac_txq);
+ if (!skb)
+ return true;
+
+ __skb_queue_tail(sk_list, skb);
+ }
+
+ /*
+ * re-read mac80211 txq current length.
+ * It is mainly for debug purpose to trace dropped packet. There is no
+ * problems to have nb_ready_mac80211 != actual mac80211 txq length
+ */
+ ieee80211_txq_get_depth(mac_txq, &mac_txq_len, NULL);
+ if (txq->nb_ready_mac80211 > mac_txq_len)
+ trace_txq_drop(txq, txq->nb_ready_mac80211 - mac_txq_len);
+ txq->nb_ready_mac80211 = mac_txq_len;
+
+ return (txq->nb_ready_mac80211 == 0);
+}
+#endif
+
+/**
+ * rwnx_txq_get_skb_to_push - Get list of buffer to push for one txq
+ *
+ * @rwnx_hw: main driver data
+ * @hwq: HWQ on wich buffers will be pushed
+ * @txq: TXQ to get buffers from
+ * @user: user position to use
+ * @sk_list_push: list to update
+ *
+ *
+ * This function will returned a list of buffer to push for one txq.
+ * It will take into account the number of credit of the HWQ for this user
+ * position and TXQ (and push_limit).
+ * This allow to get a list that can be pushed without having to test for
+ * hwq/txq status after each push
+ *
+ * If a MU group has been selected for this txq, it will also update the
+ * counter for the group
+ *
+ * Return: %true if the TX queue has no ready buffer after this batch,
+ * otherwise %false.
+ */
+static bool rwnx_txq_get_skb_to_push(struct rwnx_hw *rwnx_hw,
+ struct rwnx_hwq *hwq, struct rwnx_txq *txq,
+ int user,
+ struct sk_buff_head *sk_list_push)
+{
+ int nb_ready = skb_queue_len(&txq->sk_list);
+ int credits = rwnx_txq_get_credits(txq);
+ bool res = false;
+
+ __skb_queue_head_init(sk_list_push);
+
+ if (credits >= nb_ready) {
+ skb_queue_splice_init(&txq->sk_list, sk_list_push);
+#ifdef CONFIG_MAC80211_TXQ
+ res = rwnx_txq_mac80211_dequeue(rwnx_hw, sk_list_push, txq,
+ credits - nb_ready);
+ credits = skb_queue_len(sk_list_push);
+#else
+ res = true;
+ credits = nb_ready;
+#endif
+ } else {
+ skb_queue_extract(&txq->sk_list, sk_list_push, credits);
+
+ /*
+ * When processing PS service period (i.e. push_limit != 0), no longer
+ * process this txq if the buffers extracted will complete the SP for
+ * this txq
+ */
+ if (txq->push_limit && credits == txq->push_limit)
+ res = true;
+ }
+
+ rwnx_mu_set_active_sta(rwnx_hw, rwnx_txq_2_sta(txq), credits);
+
+ return res;
+}
+
+/**
+ * rwnx_txq_select_user - Select User queue for a txq
+ *
+ * @rwnx_hw: main driver data
+ * @mu_lock: true is MU lock is taken
+ * @txq: TXQ to select MU group for
+ * @hwq: HWQ for the TXQ
+ * @user: Updated with user position selected
+ *
+ * Return: %true if the TX queue can be processed, otherwise %false.
+ *
+ * This function selects the MU group to use for a TXQ.
+ * The selection is done as follow:
+ *
+ * - return immediately for STA that don't belongs to any group and select
+ * group 0 / user 0
+ *
+ * - If MU tx is disabled (by user mutx_on, or because mu group are being
+ * updated !mu_lock), select group 0 / user 0
+ *
+ * - Use the best group selected by @rwnx_mu_group_sta_select.
+ *
+ * Each time a group is selected (except for the first case where sta
+ * doesn't belongs to a MU group), the function checks that no buffer is
+ * pending for this txq on another user position. If this is the case stop
+ * the txq (RWNX_TXQ_STOP_MU_POS) and return false.
+ *
+ */
+static bool rwnx_txq_select_user(struct rwnx_hw *rwnx_hw, bool mu_lock,
+ struct rwnx_txq *txq, struct rwnx_hwq *hwq,
+ int *user)
+{
+ int pos = 0;
+#ifdef CONFIG_RWNX_MUMIMO_TX
+ int id, group_id = 0;
+ struct rwnx_sta *sta = rwnx_txq_2_sta(txq);
+
+ /* for sta that belong to no group return immediately */
+ if (!sta || !sta->group_info.cnt)
+ goto end;
+
+ /* If MU is disabled, need to check user */
+ if (!rwnx_hw->mod_params->mutx_on || !mu_lock)
+ goto check_user;
+
+ /* Use the "best" group selected */
+ group_id = sta->group_info.group;
+
+ if (group_id > 0)
+ pos = rwnx_mu_group_sta_get_pos(rwnx_hw, sta, group_id);
+
+check_user:
+ /* check that we can push on this user position */
+#if CONFIG_USER_MAX == 2
+ id = (pos + 1) & 0x1;
+ if (txq->pkt_pushed[id]) {
+ rwnx_txq_stop(txq, RWNX_TXQ_STOP_MU_POS);
+ return false;
+ }
+
+#else
+ for (id = 0; id < CONFIG_USER_MAX; id++) {
+ if (id != pos && txq->pkt_pushed[id]) {
+ rwnx_txq_stop(txq, RWNX_TXQ_STOP_MU_POS);
+ return false;
+ }
+ }
+#endif
+
+end:
+ rwnx_txq_set_mu_info(rwnx_hw, txq, group_id, pos);
+#endif /* CONFIG_RWNX_MUMIMO_TX */
+
+ *user = pos;
+ return true;
+}
+
+#define ALL_HWQ_MASK ((1 << CONFIG_USER_MAX) - 1)
+
+/**
+ * rwnx_hwq_process - Process one HW queue list
+ *
+ * @rwnx_hw: Driver main data
+ * @hwq: HW queue to process
+ *
+ * Iterate over all TX queues linked to this hardware queue and push as many
+ * buffers as possible. A TX queue on the list has at least one buffer.
+ * - If a TX queue has no buffer, remove it and process the next queue.
+ * - If a TX queue has no credit or reaches its push limit, remove it and
+ * process the next queue.
+ * - If the hardware queue is full, preserve fair ordering for the next call.
+ *
+ * To be called when HW queue list is modified:
+ * - when a buffer is pushed on a TX queue
+ * - when new credits are received
+ * - when a STA returns from Power Save mode or receives traffic request.
+ * - when Channel context change
+ *
+ * Context: Called with the TX lock held.
+ */
+void rwnx_hwq_process(struct rwnx_hw *rwnx_hw, struct rwnx_hwq *hwq)
+{
+ struct rwnx_txq *txq, *next;
+ int user;
+ bool mu_enable;
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_process_hw_queue(hwq);
+#endif
+ hwq->need_processing = false;
+
+ mu_enable = rwnx_txq_take_mu_lock(rwnx_hw);
+
+ list_for_each_entry_safe(txq, next, &hwq->list, sched_list) {
+ struct rwnx_txhdr *txhdr = NULL;
+ struct sk_buff_head sk_list_push;
+ struct sk_buff *skb;
+ bool txq_empty;
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_process_txq(txq);
+#endif
+ /* sanity check for debug */
+ WARN_ON_ONCE(!(txq->status & RWNX_TXQ_IN_HWQ_LIST));
+ if (txq->idx == TXQ_INACTIVE) {
+ AICWFDBG(LOGDEBUG, "%s txq->idx == TXQ_INACTIVE \r\n", __func__);
+ rwnx_txq_del_from_hw_list(txq);
+ rwnx_txq_flush(rwnx_hw, txq);
+ continue;
+ }
+ WARN_ON_ONCE(txq->idx == TXQ_INACTIVE);
+ WARN_ON_ONCE(txq->credits <= 0);
+ WARN_ON_ONCE(!rwnx_txq_skb_ready(txq));
+
+ if (!rwnx_txq_select_user(rwnx_hw, mu_enable, txq, hwq, &user)) {
+ AICWFDBG(LOGDEBUG, "select user:%d\n", user);
+ continue;
+ }
+
+ txq_empty =
+ rwnx_txq_get_skb_to_push(rwnx_hw, hwq, txq, user, &sk_list_push);
+ while ((skb = __skb_dequeue(&sk_list_push)) != NULL) {
+ txhdr = (struct rwnx_txhdr *)skb->data;
+ rwnx_tx_push(rwnx_hw, txhdr, 0);
+ }
+
+ if (txq_empty) {
+ rwnx_txq_del_from_hw_list(txq);
+ txq->pkt_sent = 0;
+ } else if (rwnx_txq_is_scheduled(txq)) {
+ /* txq not empty,
+ * To avoid starving need to process other txq in the list
+ * For better aggregation, need to send "as many consecutive
+ * pkt as possible" for he same txq
+ * ==> Add counter to trigger txq switch
+ */
+ if (txq->pkt_sent > hwq->size) {
+ txq->pkt_sent = 0;
+ list_rotate_left(&hwq->list);
+ }
+ }
+
+#ifdef CONFIG_RWNX_FULLMAC
+ /* Unable to complete PS traffic request because of hwq credit */
+ if (txq->push_limit && txq->sta) {
+ if (txq->ps_id == LEGACY_PS_ID) {
+ /* for legacy PS abort SP and wait next ps-poll */
+ txq->sta->ps.sp_cnt[txq->ps_id] -= txq->push_limit;
+ txq->push_limit = 0;
+ }
+ /* for u-apsd need to complete the SP to send EOSP frame */
+ }
+#ifndef CONFIG_AIC8800_ONE_TXQ
+ /* restart netdev queue if number of queued buffer is below threshold */
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+ if (unlikely(txq->status & RWNX_TXQ_NDEV_FLOW_CTRL) &&
+ (skb_queue_len(&txq->sk_list) < RWNX_NDEV_FLOW_CTRL_RESTART)) {
+#else
+ if (unlikely(txq->status & RWNX_TXQ_NDEV_FLOW_CTRL) &&
+ skb_queue_len(&txq->sk_list) < RWNX_NDEV_FLOW_CTRL_RESTART) {
+#endif
+
+ txq->status &= ~RWNX_TXQ_NDEV_FLOW_CTRL;
+ netif_wake_subqueue(txq->ndev, txq->ndev_idx);
+#ifdef AIC8800_CREATE_TRACE_POINTS
+ trace_txq_flowctrl_restart(txq);
+#endif
+ }
+#endif /* CONFIG_AIC8800_ONE_TXQ */
+#endif /* CONFIG_RWNX_FULLMAC */
+ }
+
+ if (mu_enable)
+ rwnx_txq_release_mu_lock(rwnx_hw);
+}
+
+/**
+ * rwnx_hwq_process_all - Process all HW queue list
+ *
+ * @rwnx_hw: Driver main data
+ *
+ * Loop over all HWQ, and process them if needed
+ * To be called with tx_lock hold
+ */
+void rwnx_hwq_process_all(struct rwnx_hw *rwnx_hw)
+{
+ int id;
+
+ rwnx_mu_group_sta_select(rwnx_hw);
+
+ for (id = ARRAY_SIZE(rwnx_hw->hwq) - 1; id >= 0; id--) {
+ if (rwnx_hw->hwq[id].need_processing)
+ rwnx_hwq_process(rwnx_hw, &rwnx_hw->hwq[id]);
+ }
+}
+
+/**
+ * rwnx_hwq_init - Initialize all hwq structures
+ *
+ * @rwnx_hw: Driver main data
+ *
+ */
+void rwnx_hwq_init(struct rwnx_hw *rwnx_hw)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(rwnx_hw->hwq); i++) {
+ struct rwnx_hwq *hwq = &rwnx_hw->hwq[i];
+
+ hwq->id = i;
+ hwq->size = nx_txdesc_cnt[i];
+ INIT_LIST_HEAD(&hwq->list);
+ }
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_txq.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_txq.h
new file mode 100644
index 0000000000000..2f32fda1be1d0
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_txq.h
@@ -0,0 +1,358 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_txq.h
+ *
+ * @brief tx queue function
+ *
+ ****************************************************************************************
+ */
+#ifndef _RWNX_TXQ_H_
+#define _RWNX_TXQ_H_
+
+#include <linux/bitops.h>
+#include <linux/ieee80211.h>
+#include <linux/types.h>
+#include "rwnx_compat.h"
+
+#ifdef CONFIG_RWNX_FULLMAC
+/*
+ * Fullmac TXQ configuration:
+ * - STA: 1 TXQ per TID (limited to 8)
+ * 1 TXQ for bufferable MGT frames
+ * - VIF: 1 TXQ for Multi/Broadcast +
+ * 1 TXQ for MGT for unknown STAs or non-bufferable MGT frames
+ * - 1 TXQ for offchannel transmissions
+ *
+ *
+ * Txq mapping looks like
+ * for NX_REMOTE_STA_MAX=10 and NX_VIRT_DEV_MAX=4
+ *
+ * | TXQ | NDEV_ID | VIF | STA | TID | HWQ |
+ * |-----+---------+-----+-------+------+-----|-
+ * | 0 | 0 | | 0 | 0 | 1 | 9 TXQ per STA
+ * | 1 | 1 | | 0 | 1 | 0 | (8 data + 1 mgmt)
+ * | 2 | 2 | | 0 | 2 | 0 |
+ * | 3 | 3 | | 0 | 3 | 1 |
+ * | 4 | 4 | | 0 | 4 | 2 |
+ * | 5 | 5 | | 0 | 5 | 2 |
+ * | 6 | 6 | | 0 | 6 | 3 |
+ * | 7 | 7 | | 0 | 7 | 3 |
+ * | 8 | N/A | | 0 | MGMT | 3 |
+ * |-----+---------+-----+-------+------+-----|-
+ * | ... | | | | | | Same for all STAs
+ * |-----+---------+-----+-------+------+-----|-
+ * | 90 | 80 | 0 | BC/MC | 0 | 1/4 | 1 TXQ for BC/MC per VIF
+ * | ... | | | | | |
+ * | 93 | 80 | 3 | BC/MC | 0 | 1/4 |
+ * |-----+---------+-----+-------+------+-----|-
+ * | 94 | N/A | 0 | N/A | MGMT | 3 | 1 TXQ for unknown STA per VIF
+ * | ... | | | | | |
+ * | 97 | N/A | 3 | N/A | MGMT | 3 |
+ * |-----+---------+-----+-------+------+-----|-
+ * | 98 | N/A | | N/A | MGMT | 3 | 1 TXQ for offchannel frame
+ */
+
+#ifdef CONFIG_AIC8800_TX_NETIF_FLOWCTRL
+extern int tx_fc_low_water;
+extern int tx_fc_high_water;
+#endif
+
+#define NX_NB_TID_PER_STA 8
+#define NX_NB_TXQ_PER_STA (NX_NB_TID_PER_STA + 1)
+#define NX_NB_TXQ_PER_VIF 2
+#define NX_NB_TXQ \
+ ((NX_NB_TXQ_PER_STA * NX_REMOTE_STA_MAX) + \
+ (NX_NB_TXQ_PER_VIF * NX_VIRT_DEV_MAX) + 1)
+
+#define NX_FIRST_VIF_TXQ_IDX (NX_REMOTE_STA_MAX * NX_NB_TXQ_PER_STA)
+#define NX_FIRST_BCMC_TXQ_IDX NX_FIRST_VIF_TXQ_IDX
+#define NX_FIRST_UNK_TXQ_IDX (NX_FIRST_BCMC_TXQ_IDX + NX_VIRT_DEV_MAX)
+
+#define NX_OFF_CHAN_TXQ_IDX \
+ (NX_FIRST_VIF_TXQ_IDX + (NX_VIRT_DEV_MAX * NX_NB_TXQ_PER_VIF))
+
+#define NX_FIRST_VIF_TXQ_IDX_FOR_OLD_IC \
+ (NX_REMOTE_STA_MAX_FOR_OLD_IC * NX_NB_TXQ_PER_STA)
+#define NX_FIRST_BCMC_TXQ_IDX_FOR_OLD_IC NX_FIRST_VIF_TXQ_IDX_FOR_OLD_IC
+#define NX_FIRST_UNK_TXQ_IDX_FOR_OLD_IC \
+ (NX_FIRST_BCMC_TXQ_IDX_FOR_OLD_IC + NX_VIRT_DEV_MAX)
+
+#define NX_OFF_CHAN_TXQ_IDX_FOR_OLD_IC \
+ (NX_FIRST_VIF_TXQ_IDX_FOR_OLD_IC + (NX_VIRT_DEV_MAX * NX_NB_TXQ_PER_VIF))
+
+#define NX_BCMC_TXQ_TYPE 0
+#define NX_UNK_TXQ_TYPE 1
+
+/*
+ * Each data TXQ is a netdev queue. TXQ to send MGT are not data TXQ as
+ * they did not received buffer from netdev interface.
+ * Need to allocate the maximum case.
+ * AP : all STAs + 1 BC/MC
+ */
+#define NX_NB_NDEV_TXQ ((NX_NB_TID_PER_STA * NX_REMOTE_STA_MAX) + 1)
+#define NX_NB_NDEV_TXQ_FOR_OLD_IC \
+ ((NX_NB_TID_PER_STA * NX_REMOTE_STA_MAX_FOR_OLD_IC) + 1)
+
+#define NX_BCMC_TXQ_NDEV_IDX (NX_NB_TID_PER_STA * NX_REMOTE_STA_MAX)
+#define NX_BCMC_TXQ_NDEV_IDX_FOR_OLD_IC \
+ (NX_NB_TID_PER_STA * NX_REMOTE_STA_MAX_FOR_OLD_IC)
+#define NX_STA_NDEV_IDX(tid, sta_idx) ((tid) + (sta_idx) * NX_NB_TID_PER_STA)
+#define NDEV_NO_TXQ 0xffff
+#if (NX_NB_NDEV_TXQ >= NDEV_NO_TXQ)
+#error("Need to increase struct rwnx_txq->ndev_idx size")
+#endif
+
+/* stop netdev queue when number of queued buffers if greater than this */
+#define RWNX_NDEV_FLOW_CTRL_STOP 64
+/* restart netdev queue when number of queued buffers is lower than this */
+#define RWNX_NDEV_FLOW_CTRL_RESTART 64
+
+#endif /* CONFIG_RWNX_FULLMAC */
+
+#define TXQ_INACTIVE 0xffff
+#if (NX_NB_TXQ >= TXQ_INACTIVE)
+#error("Need to increase struct rwnx_txq->idx size")
+#endif
+
+#define NX_TXQ_INITIAL_CREDITS 64
+
+/*
+ * TXQ tid sorted by decreasing priority
+ */
+extern const int nx_tid_prio[NX_NB_TID_PER_STA];
+
+/**
+ * struct rwnx_hwq - Structure used to save information relative to
+ * an AC TX queue (aka HW queue)
+ * @list: TX queues with buffers ready for this hardware queue
+ * @size: Queue capacity
+ * @id: Hardware queue identifier
+ * @need_processing: Whether the queue needs processing
+ */
+struct rwnx_hwq {
+ struct list_head list;
+ u8 size;
+ u8 id;
+ bool need_processing;
+};
+
+/**
+ * enum rwnx_push_flags - Flags of pushed buffer
+ *
+ * @RWNX_PUSH_RETRY: Push a buffer for retry
+ * @RWNX_PUSH_IMMEDIATE: Push a buffer without queuing it first
+ */
+enum rwnx_push_flags {
+ RWNX_PUSH_RETRY = BIT(0),
+ RWNX_PUSH_IMMEDIATE = BIT(1),
+};
+
+/**
+ * enum rwnx_txq_flags - TXQ status flag
+ *
+ * @RWNX_TXQ_IN_HWQ_LIST: The queue is scheduled for transmission
+ * @RWNX_TXQ_STOP_FULL: No more credits for the queue
+ * @RWNX_TXQ_STOP_CSA: CSA is in progress
+ * @RWNX_TXQ_STOP_STA_PS: Destiniation sta is currently in power save mode
+ * @RWNX_TXQ_STOP_VIF_PS: Vif owning this queue is currently in power save mode
+ * @RWNX_TXQ_STOP_CHAN: Channel of this queue is not the current active channel
+ * @RWNX_TXQ_STOP_MU_POS: TXQ is stopped waiting for all the buffers pushed to
+ * fw to be confirmed
+ * @RWNX_TXQ_STOP: All possible reason to have a txq stopped
+ * @RWNX_TXQ_NDEV_FLOW_CTRL: associated netdev queue is currently stopped.
+ * Note: when a TXQ is flowctrl it is NOT stopped
+ */
+enum rwnx_txq_flags {
+ RWNX_TXQ_IN_HWQ_LIST = BIT(0),
+ RWNX_TXQ_STOP_FULL = BIT(1),
+ RWNX_TXQ_STOP_CSA = BIT(2),
+ RWNX_TXQ_STOP_STA_PS = BIT(3),
+ RWNX_TXQ_STOP_VIF_PS = BIT(4),
+ RWNX_TXQ_STOP_CHAN = BIT(5),
+ RWNX_TXQ_STOP_MU_POS = BIT(6),
+ RWNX_TXQ_STOP =
+ (RWNX_TXQ_STOP_FULL | RWNX_TXQ_STOP_CSA | RWNX_TXQ_STOP_STA_PS |
+ RWNX_TXQ_STOP_VIF_PS | RWNX_TXQ_STOP_CHAN),
+ RWNX_TXQ_NDEV_FLOW_CTRL = BIT(7),
+};
+
+/**
+ * struct rwnx_txq - Structure used to save information relative to
+ * a RA/TID TX queue
+ *
+ * @idx: Unique txq idx. Set to TXQ_INACTIVE if txq is not used.
+ * @status: Bitfield of &enum rwnx_txq_flags
+ * @credits: Number of buffers that can be pushed to the firmware
+ * @pkt_sent: Number of consecutive packets sent while on the HW queue list
+ * @pkt_pushed: Number of packets awaiting transmission confirmation per user
+ * @sched_list: Node in the hardware queue scheduling list
+ * @sk_list: Buffers waiting to be pushed to the firmware
+ * @last_retry_skb: Last retry buffer in @sk_list, or %NULL when none is queued
+ * @nb_retry: Number of queued retry packets
+ * @hwq: Associated hardware queue
+ * @push_limit: Maximum packets to push before leaving the hardware queue list
+ * @tid: TID
+ *
+ * @nb_ready_mac80211: Number of buffers ready in the mac80211 TX queue
+ *
+ * FULLMAC specific
+ * @sta: STA associated with the queue
+ * @ps_id: Power-save queue identifier (legacy or U-APSD)
+ * @ndev_idx: Netdev queue index, or 0xff for a non-netdev queue
+ * @ndev: Net device of the corresponding VIF
+ * @amsdu: TX header of the first A-MSDU subframe, or %NULL
+ * @amsdu_len: Maximum A-MSDU size, or 0 when A-MSDU is disabled
+ * @mumimo_info: MU-MIMO group and user-position information
+ */
+struct rwnx_txq {
+ u16 idx;
+ u8 status;
+ s8 credits;
+ u8 pkt_sent;
+ u8 pkt_pushed[CONFIG_USER_MAX];
+ struct list_head sched_list;
+ struct sk_buff_head sk_list;
+ struct sk_buff *last_retry_skb;
+ struct rwnx_hwq *hwq;
+ int nb_retry;
+ u8 push_limit;
+ u8 tid;
+#ifdef CONFIG_MAC80211_TXQ
+ unsigned long nb_ready_mac80211;
+#endif
+#ifdef CONFIG_RWNX_FULLMAC
+ struct rwnx_sta *sta;
+ u8 ps_id;
+ u16 ndev_idx;
+ struct net_device *ndev;
+#ifdef CONFIG_RWNX_AMSDUS_TX
+ struct rwnx_sw_txhdr *amsdu;
+ u16 amsdu_len;
+#endif /* CONFIG_RWNX_AMSDUS_TX */
+#endif /* CONFIG_RWNX_FULLMAC */
+#ifdef CONFIG_RWNX_MUMIMO_TX
+ u8 mumimo_info;
+#endif
+};
+
+struct rwnx_sta;
+struct rwnx_vif;
+struct rwnx_hw;
+struct rwnx_sw_txhdr;
+
+#ifdef CONFIG_RWNX_MUMIMO_TX
+#define RWNX_TXQ_GROUP_ID(txq) ((txq)->mumimo_info & 0x3f)
+#define RWNX_TXQ_POS_ID(txq) (((txq)->mumimo_info >> 6) & 0x3)
+#else
+#define RWNX_TXQ_GROUP_ID(txq) 0
+#define RWNX_TXQ_POS_ID(txq) 0
+#endif /* CONFIG_RWNX_MUMIMO_TX */
+
+static inline bool rwnx_txq_is_stopped(struct rwnx_txq *txq)
+{
+ return (txq->status & RWNX_TXQ_STOP);
+}
+
+static inline bool rwnx_txq_is_full(struct rwnx_txq *txq)
+{
+ return (txq->status & RWNX_TXQ_STOP_FULL);
+}
+
+static inline bool rwnx_txq_is_scheduled(struct rwnx_txq *txq)
+{
+ return (txq->status & RWNX_TXQ_IN_HWQ_LIST);
+}
+
+/*
+ * foreach_vif_txq - Macro to iterate over all TXQ of a VIF (in AC order)
+ *
+ * @vif: pointer to rwnx_vif
+ * @txq: pointer to rwnx_txq updated with the next TXQ at each iteration
+ * @ac: int updated with the TXQ ac at each iteration
+ */
+/*
+ *#ifdef CONFIG_MAC80211_TXQ
+ *#define foreach_vif_txq(vif, txq, ac) \
+ * for (ac = RWNX_HWQ_BK, txq = rwnx_txq_vif_get(vif, ac); \
+ * ac < NX_NB_TXQ_PER_VIF; ac++, txq = rwnx_txq_vif_get(vif, ac))
+ *#else
+ *#define foreach_vif_txq(vif, txq, ac) \
+ * for (ac = RWNX_HWQ_BK, txq = &vif->txqs[0]; ac < NX_NB_TXQ_PER_VIF; \
+ * ac++, txq++)
+ *#endif
+ */
+
+#ifdef CONFIG_RWNX_FULLMAC
+struct rwnx_txq *rwnx_txq_sta_get(struct rwnx_sta *sta, u8 tid,
+ struct rwnx_hw *rwnx_hw);
+struct rwnx_txq *rwnx_txq_vif_get(struct rwnx_vif *vif, u8 type);
+#endif /* CONFIG_RWNX_FULLMAC */
+
+/**
+ * rwnx_txq_vif_get_status - return status bits related to the vif
+ *
+ * @rwnx_vif: Pointer to vif structure
+ *
+ * Return: VIF-related stop-reason bits from the first VIF TX queue.
+ */
+static inline u8 rwnx_txq_vif_get_status(struct rwnx_vif *rwnx_vif)
+{
+ struct rwnx_txq *txq = rwnx_txq_vif_get(rwnx_vif, 0);
+
+ return (txq->status & (RWNX_TXQ_STOP_CHAN | RWNX_TXQ_STOP_VIF_PS));
+}
+
+void rwnx_txq_vif_init(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+ u8 status);
+void rwnx_txq_vif_deinit(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif);
+void rwnx_txq_sta_init(struct rwnx_hw *rwnx_hw, struct rwnx_sta *rwnx_sta,
+ u8 status);
+void rwnx_txq_sta_deinit(struct rwnx_hw *rwnx_hw, struct rwnx_sta *rwnx_sta);
+#ifdef CONFIG_RWNX_FULLMAC
+void rwnx_txq_unk_vif_init(struct rwnx_vif *rwnx_vif);
+void rwnx_txq_unk_vif_deinit(struct rwnx_vif *vif);
+void rwnx_txq_offchan_init(struct rwnx_vif *rwnx_vif);
+void rwnx_txq_offchan_deinit(struct rwnx_vif *rwnx_vif);
+void rwnx_txq_tdls_vif_init(struct rwnx_vif *rwnx_vif);
+void rwnx_txq_tdls_vif_deinit(struct rwnx_vif *vif);
+void rwnx_txq_tdls_sta_start(struct rwnx_vif *rwnx_vif, u16 reason,
+ struct rwnx_hw *rwnx_hw);
+void rwnx_txq_tdls_sta_stop(struct rwnx_vif *rwnx_vif, u16 reason,
+ struct rwnx_hw *rwnx_hw);
+#endif
+
+void rwnx_txq_add_to_hw_list(struct rwnx_txq *txq);
+void rwnx_txq_del_from_hw_list(struct rwnx_txq *txq);
+void rwnx_txq_stop(struct rwnx_txq *txq, u16 reason);
+void rwnx_txq_start(struct rwnx_txq *txq, u16 reason);
+void rwnx_txq_vif_start(struct rwnx_vif *vif, u16 reason,
+ struct rwnx_hw *rwnx_hw);
+void rwnx_txq_vif_stop(struct rwnx_vif *vif, u16 reason,
+ struct rwnx_hw *rwnx_hw);
+
+#ifdef CONFIG_RWNX_FULLMAC
+void rwnx_txq_sta_start(struct rwnx_sta *sta, u16 reason,
+ struct rwnx_hw *rwnx_hw);
+void rwnx_txq_sta_stop(struct rwnx_sta *sta, u16 reason,
+ struct rwnx_hw *rwnx_hw);
+void rwnx_txq_offchan_start(struct rwnx_hw *rwnx_hw);
+void rwnx_txq_sta_switch_vif(struct rwnx_sta *sta, struct rwnx_vif *old_vif,
+ struct rwnx_vif *new_vif);
+
+#endif /* CONFIG_RWNX_FULLMAC */
+
+int rwnx_txq_queue_skb(struct sk_buff *skb, struct rwnx_txq *txq,
+ struct rwnx_hw *rwnx_hw, bool retry);
+void rwnx_txq_confirm_any(struct rwnx_hw *rwnx_hw, struct rwnx_txq *txq,
+ struct rwnx_hwq *hwq, struct rwnx_sw_txhdr *sw_txhdr);
+
+void rwnx_hwq_init(struct rwnx_hw *rwnx_hw);
+void rwnx_hwq_process(struct rwnx_hw *rwnx_hw, struct rwnx_hwq *hwq);
+void rwnx_hwq_process_all(struct rwnx_hw *rwnx_hw);
+void rwnx_txq_flush(struct rwnx_hw *rwnx_hw, struct rwnx_txq *txq);
+
+#endif /* _RWNX_TXQ_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/sdio_host.c b/drivers/net/wireless/aic/aic8800_fdrv/sdio_host.c
new file mode 100644
index 0000000000000..afce72e59a48b
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/sdio_host.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file sdio_host.c
+ *
+ * @brief SDIO host function declarations
+ *
+ ****************************************************************************************
+ */
+
+#include "sdio_host.h"
+#include "aicwf_debug.h"
+#include "rwnx_platform.h"
+#include "rwnx_tx.h"
+
+/*
+ ****************************************************************************************
+ */
+void aicwf_sdio_host_init(struct sdio_host_env_tag *env, void *cb,
+ void *shared_env_ptr, void *pthis)
+{
+ // Reset the environments
+
+ // Reset the Host environment
+ memset(env, 0, sizeof(struct sdio_host_env_tag));
+ // Save the pointer to the register base
+ env->pthis = pthis;
+}
+
+/*
+ ****************************************************************************************
+ */
+struct txdesc_host *
+aicwf_sdio_host_txdesc_get(struct sdio_host_env_tag *env, const int queue_idx)
+{
+ struct txdesc_host *txdesc_free = NULL;
+ u32 used_idx = env->txdesc_used_idx[queue_idx];
+ u32 free_idx = env->txdesc_free_idx[queue_idx];
+
+ // Check if a free descriptor is available
+ if (free_idx != (used_idx + SDIO_TXDESC_CNT)) {
+ // Get the pointer to the first free descriptor
+ // txdesc_free = shared_env_ptr->txdesc[queue_idx] + (free_idx %
+ // IPC_TXDESC_CNT);
+ } else {
+ txdesc_free = NULL;
+ }
+
+ return txdesc_free;
+}
+
+/*
+ ****************************************************************************************
+ */
+void aicwf_sdio_host_txdesc_push(struct sdio_host_env_tag *env,
+ const int queue_idx, const uint64_t host_id)
+{
+ // Save the host id in the environment
+ env->tx_host_id[queue_idx][env->txdesc_free_idx[queue_idx] %
+ SDIO_TXDESC_CNT] = host_id;
+
+ // Increment the index
+ env->txdesc_free_idx[queue_idx]++;
+ if (env->txdesc_free_idx[queue_idx] == 0x80000000)
+ env->txdesc_free_idx[queue_idx] = 0;
+}
+
+/*
+ ****************************************************************************************
+ */
+void aicwf_sdio_host_tx_cfm_handler(struct sdio_host_env_tag *env, u32 *data)
+{
+ u32 queue_idx = 0;
+ struct sk_buff *skb = NULL;
+ struct rwnx_txhdr *txhdr;
+
+ // Get the used index and increase it. We do the increase before knowing if
+ // the current buffer is confirmed because the callback function may call
+ // the ipc_host_txdesc_get() in case flow control was enabled and the index
+ // has to be already at the good value to ensure that the test of FIFO full
+ // is correct
+ u32 used_idx = data[1];
+ unsigned long host_id =
+ env->tx_host_id[queue_idx][used_idx % SDIO_TXDESC_CNT];
+
+ // Reset the host id in the array
+ env->tx_host_id[queue_idx][used_idx % SDIO_TXDESC_CNT] = 0;
+
+ // call the external function to indicate that a TX packet is freed
+ if (host_id == 0) {
+ // No more confirmations, so put back the used index at its initial
+ // value
+ env->txdesc_used_idx[queue_idx] = used_idx;
+ AICWFDBG(LOGERROR, "ERROR:No more confirmations\r\n");
+ }
+ // set the cfm status
+ skb = (struct sk_buff *)host_id;
+ txhdr = (struct rwnx_txhdr *)skb->data;
+ txhdr->hw_hdr.cfm.status = (union rwnx_hw_txstatus)data[0];
+ AICWFDBG(LOGINFO,
+ "sdio_host_tx_cfm_handler:used_idx=%d, 0x%p, status=%x\r\n",
+ used_idx, env->pthis, txhdr->hw_hdr.cfm.status.value);
+ // if (env->cb.send_data_cfm(env->pthis, host_id) != 0)
+ if (rwnx_txdatacfm(env->pthis, (void *)host_id) != 0) {
+ // No more confirmations, so put back the used index at its initial
+ // value
+ env->txdesc_used_idx[queue_idx] = used_idx;
+ env->tx_host_id[queue_idx][used_idx % SDIO_TXDESC_CNT] = host_id;
+ // and exit the loop
+ AICWFDBG(LOGERROR, "ERROR:rwnx_txdatacfm,\r\n");
+ }
+}
+
+int aicwf_rwnx_sdio_platform_init(struct aic_sdio_dev *sdiodev)
+{
+ struct rwnx_plat *rwnx_plat = NULL;
+ void *drvdata;
+ int ret = -ENODEV;
+
+ rwnx_plat = kzalloc_obj(*rwnx_plat, GFP_KERNEL);
+
+ if (!rwnx_plat)
+ return -ENOMEM;
+
+ rwnx_plat->sdiodev = sdiodev;
+ ret = rwnx_platform_init(rwnx_plat, &drvdata);
+
+ return ret;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/sdio_host.h b/drivers/net/wireless/aic/aic8800_fdrv/sdio_host.h
new file mode 100644
index 0000000000000..17a1cde69c27d
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/sdio_host.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SDIO_HOST_H_
+#define _SDIO_HOST_H_
+
+#include "aicwf_sdio.h"
+#include "lmac_types.h"
+
+#define SDIO_TXQUEUE_CNT NX_TXQ_CNT
+#define SDIO_TXDESC_CNT NX_TXDESC_CNT
+
+/// Definition of the IPC Host environment structure.
+struct sdio_host_env_tag {
+ // Index used that points to the first free TX desc
+ u32 txdesc_free_idx[SDIO_TXQUEUE_CNT];
+ // Index used that points to the first used TX desc
+ u32 txdesc_used_idx[SDIO_TXQUEUE_CNT];
+ // Array storing the currently pushed host ids, per IPC queue
+ u64 tx_host_id[SDIO_TXQUEUE_CNT][SDIO_TXDESC_CNT];
+
+ /// Pointer to the attached object (used in callbacks and register accesses)
+ void *pthis;
+};
+
+struct txdesc_host *
+aicwf_sdio_host_txdesc_get(struct sdio_host_env_tag *env, const int queue_idx);
+
+void aicwf_sdio_host_init(struct sdio_host_env_tag *env, void *cb,
+ void *shared_env_ptr, void *pthis);
+
+void aicwf_sdio_host_txdesc_push(struct sdio_host_env_tag *env,
+ const int queue_idx,
+ const uint64_t host_id);
+
+void aicwf_sdio_host_tx_cfm_handler(struct sdio_host_env_tag *env,
+ u32 *data);
+
+int aicwf_rwnx_sdio_platform_init(struct aic_sdio_dev *sdiodev);
+
+#endif
--
2.34.1
next prev parent reply other threads:[~2026-08-28 12:12 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-23 2:13 [RFC PATCH wireless-next v2 0/4] wifi: aic: add AIC8800 SDIO FullMAC driver Yanli Yang
2026-07-23 2:13 ` [RFC PATCH wireless-next v2 1/4] wifi: aic: add SDIO BSP and build configuration Yanli Yang
2026-07-23 2:13 ` [RFC PATCH wireless-next v2 2/4] wifi: aic: add FullMAC firmware and platform interface Yanli Yang
2026-07-23 2:13 ` [RFC PATCH wireless-next v2 3/4] wifi: aic: add FullMAC WLAN driver Yanli Yang
2026-07-23 2:13 ` [RFC PATCH wireless-next v2 4/4] wifi: aic: integrate AIC8800 driver Yanli Yang
2026-07-23 8:42 ` [RFC PATCH wireless-next v2 0/4] wifi: aic: add AIC8800 SDIO FullMAC driver Johannes Berg
2026-07-23 8:48 ` Johannes Berg
2026-08-28 12:11 ` [RFC PATCH wireless-next v3 0/4] wifi: aic: add AIC8800D80 " Yanli Yang
2026-08-28 12:11 ` [RFC PATCH wireless-next v3 1/4] wifi: aic: add SDIO BSP and build configuration Yanli Yang
2026-08-29 8:18 ` Krzysztof Kozlowski
2026-08-28 12:11 ` [RFC PATCH wireless-next v3 2/4] wifi: aic: add FullMAC firmware and platform interface Yanli Yang
2026-08-28 12:11 ` Yanli Yang [this message]
2026-08-29 8:21 ` [RFC PATCH wireless-next v3 3/4] wifi: aic: add FullMAC WLAN driver Krzysztof Kozlowski
2026-08-28 12:11 ` [RFC PATCH wireless-next v3 4/4] wifi: aic: integrate AIC8800 driver Yanli Yang
2026-08-29 8:21 ` [RFC PATCH wireless-next v4 0/4] wifi: aic: add AIC8800D80 SDIO FullMAC driver Yanli Yang
2026-08-29 8:21 ` [RFC PATCH wireless-next v4 1/4] wifi: aic: add SDIO BSP and build configuration Yanli Yang
2026-08-29 8:21 ` [RFC PATCH wireless-next v4 2/4] wifi: aic: add FullMAC firmware and platform interface Yanli Yang
2026-08-29 8:21 ` [RFC PATCH wireless-next v4 3/4] wifi: aic: add FullMAC WLAN driver Yanli Yang
2026-08-29 8:21 ` [RFC PATCH wireless-next v4 4/4] wifi: aic: integrate AIC8800 driver Yanli Yang
2026-08-31 7:16 ` [RFC PATCH wireless-next v4 0/4] wifi: aic: add AIC8800D80 SDIO FullMAC driver Christian Hewitt
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=f22a875eefa398eea9fc80bff1468dfacc47c683.1787903339.git.yanli.yang@bedmex.com \
--to=yanli.yang@bedmex.com \
--cc=chunqiuliu@aicsemi.com \
--cc=dijiaxu@aicsemi.com \
--cc=johannes@sipsolutions.net \
--cc=liheng.wei@bedmex.com \
--cc=linux-wireless@vger.kernel.org \
--cc=zhirunliu@aicsemi.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox